Source code for udaan.models.base.pointmass_suspended_payload
import numpy as np
import scipy.linalg
from ... import manif
from ..base import BaseModel
[docs]
class PointmassSuspendedPayload(BaseModel):
"""Single pointmass quadrotor with cable-suspended payload.
State: payload_position (3), payload_velocity (3),
cable_attitude (3, S2), cable_ang_velocity (3)
-> n_state=12, n_action=3
"""
[docs]
class State:
[docs]
def __init__(self):
self.payload_position = np.zeros(3)
self.payload_velocity = np.zeros(3)
self.cable_attitude = np.array([0.0, 0.0, -1.0])
self.cable_ang_velocity = np.zeros(3)
return
[docs]
def reset(self):
self.payload_position = np.zeros(3)
self.payload_velocity = np.zeros(3)
self.cable_attitude = np.array([0.0, 0.0, -1.0])
self.cable_ang_velocity = np.zeros(3)
return
@property
def cable_dq(self):
return np.cross(self.cable_ang_velocity, self.cable_attitude)
@property
def quadrotor_position(self):
return None # requires cable_length; set via parent model
@property
def quadrotor_velocity(self):
return None # requires cable_length; set via parent model
[docs]
def __init__(self, **kwargs):
super().__init__(**kwargs)
self._n_state = 12
self._n_action = 3
self.mQ = 1.0
self.mL = 0.15
self.cable_length = 1.0
self.state = PointmassSuspendedPayload.State()
self._parse_args(**kwargs)
return
@property
def quadrotor_position(self):
return self.state.payload_position - self.cable_length * self.state.cable_attitude
@property
def quadrotor_velocity(self):
return self.state.payload_velocity - self.cable_length * np.cross(
self.state.cable_ang_velocity, self.state.cable_attitude
)
def _zoh(self, force):
"""Zero-order hold Euler integration for payload-quadrotor system."""
dt = self.sim_timestep
q = self.state.cable_attitude
om = self.state.cable_ang_velocity
dq = np.cross(om, q)
net_accel = (
-self._ge3
+ (
(np.dot(q, force) - self.mQ * self.cable_length * np.dot(dq, dq))
/ (self.mQ + self.mL)
)
* q
)
self.state.payload_position += dt * self.state.payload_velocity + 0.5 * net_accel * dt * dt
self.state.payload_velocity += dt * net_accel
# S2 update for cable attitude via SO(3) exponential map
self.state.cable_attitude = scipy.linalg.expm(manif.hat(om * dt)) @ q
# angular velocity update
self.state.cable_ang_velocity = om + (dt / (self.mQ * self.cable_length)) * np.cross(
-q, force
)
[docs]
def step(self, action):
self._zoh(action)
self.t += self.sim_timestep
[docs]
def reset(self, **kwargs):
self.t = 0.0
self.state.reset()
for key in [
"payload_position",
"payload_velocity",
"cable_attitude",
"cable_ang_velocity",
]:
if key in kwargs:
setattr(self.state, key, kwargs[key])
return
[docs]
def get_rand_init_state(self, rand=False):
rng = np.random.default_rng()
init_pos = -5.0 + 10 * rng.random(3) if rand else np.zeros(3)
return {
"payload_position": init_pos,
"payload_velocity": np.zeros(3),
"cable_attitude": np.array([0.0, 0.0, -1.0]),
"cable_ang_velocity": np.zeros(3),
}
[docs]
@staticmethod
def err_q(qd, q):
return np.cross(qd, q)
[docs]
@staticmethod
def err_om(omd, om, q):
return om + manif.hat(q) @ manif.hat(q) @ omd