@@ -81,6 +81,8 @@ class Multirotor(object):
8181 'cmd_vel': the controller commands a velocity vector in the world frame.
8282 'cmd_acc': the controller commands a mass normalized thrust vector (acceleration) in the world frame.
8383 aero: boolean, determines whether or not aerodynamic drag forces are computed.
84+ enable_ground: boolean, determines whether or not ground contact is enabled.
85+ integrator_kwargs: dictionary of keyword arguments passed to scipy.integrate.solve_ivp
8486 """
8587 def __init__ (self , quad_params , initial_state = {'x' : np .array ([0 ,0 ,0 ]),
8688 'v' : np .zeros (3 ,),
@@ -90,7 +92,8 @@ def __init__(self, quad_params, initial_state = {'x': np.array([0,0,0]),
9092 'rotor_speeds' : np .array ([1788.53 , 1788.53 , 1788.53 , 1788.53 ])},
9193 control_abstraction = 'cmd_motor_speeds' ,
9294 aero = True ,
93- enable_ground = False
95+ enable_ground = False ,
96+ integrator_kwargs = None ,
9497 ):
9598 """
9699 Initialize quadrotor physical parameters.
@@ -173,6 +176,12 @@ def __init__(self, quad_params, initial_state = {'x': np.array([0,0,0]),
173176
174177 self .aero = aero
175178
179+ # Integrator settings.
180+ if integrator_kwargs is None :
181+ self .integrator_kwargs = {'method' :'RK45' }
182+ else :
183+ self .integrator_kwargs = integrator_kwargs
184+
176185 def extract_geometry (self ):
177186 """
178187 Extracts the geometry in self.rotors for efficient use later on in the computation of
@@ -222,35 +231,20 @@ def step(self, state, control, t_step):
222231 cmd_rotor_speeds = np .clip (cmd_rotor_speeds , self .rotor_speed_min , self .rotor_speed_max )
223232
224233 # Form autonomous ODE for constant inputs and integrate one time step.
234+ def s_dot_fn (t , s ):
235+ return self ._s_dot_fn (t , s , cmd_rotor_speeds )
225236 s = Multirotor ._pack_state (state )
226237
227- # Use analytic motor dynamics during the integration window to avoid stiffness
228- w0 = s [16 :].copy ()
229- tau = self .tau_m
230-
231- def s_dot_fn_analytic (t , s_vec ):
232- # Analytic motor update: w(t) = w_cmd + (w0 - w_cmd) * exp(-t/tau)
233- w_t = cmd_rotor_speeds + (w0 - cmd_rotor_speeds ) * np .exp (- t / tau )
234- s_eff = s_vec .copy ()
235- s_eff [16 :] = w_t
236- s_dot = self ._s_dot_fn (t , s_eff , cmd_rotor_speeds )
237- # Zero motor derivatives since handled analytically
238- s_dot [16 :] = 0.0
239- return s_dot
240-
238+ # Integrate
241239 sol = scipy .integrate .solve_ivp (
242- s_dot_fn_analytic ,
240+ s_dot_fn ,
243241 (0.0 , t_step ),
244242 s ,
245- method = 'Radau' ,
246- max_step = t_step ,
247- rtol = 1e-3 ,
248- atol = 1e-6 ,
243+ ** self .integrator_kwargs
249244 )
250245 s = sol ['y' ][:, - 1 ]
251- # Set final rotor speeds to analytic value at t_step
252- s [16 :] = cmd_rotor_speeds + (w0 - cmd_rotor_speeds ) * np .exp (- t_step / tau )
253246
247+ # Unpack the state vector.
254248 state = Multirotor ._unpack_state (s )
255249
256250 # Re-normalize unit quaternion.
@@ -298,30 +292,9 @@ def _s_dot_fn(self, t, s, cmd_rotor_speeds):
298292 # Rotate the force from the body frame to the inertial frame
299293 Ftot = R @FtotB
300294
301- # Ground contact handling
302- if self ._on_ground (state ) and self ._enable_ground :
303- # Calculate the total force without ground contact
304- total_force_no_ground = self .weight + Ftot
305-
306- # If the vehicle is on the ground and the total downward force would cause
307- # further descent, apply a normal force to counteract it
308- if total_force_no_ground [2 ] < 0 : # Downward force (negative z)
309- # Apply normal force to prevent going through ground
310- ground_normal_force = np .array ([0 , 0 , - total_force_no_ground [2 ]])
311- Ftot += ground_normal_force
312-
313295 # Velocity derivative.
314296 v_dot = (self .weight + Ftot ) / self .mass
315297
316- # Additional ground contact constraints
317- if self ._on_ground (state ) and self ._enable_ground :
318- # Prevent downward velocity when on ground
319- if v_dot [2 ] < 0 : # Downward acceleration
320- v_dot [2 ] = 0.0
321- # Also damp any existing downward velocity when on ground
322- if state ['v' ][2 ] < 0 : # Currently moving downward
323- v_dot [2 ] = max (v_dot [2 ], - 10.0 * state ['v' ][2 ]) # Damping term
324-
325298 # Angular velocity derivative.
326299 w = state ['w' ]
327300 w_hat = Multirotor .hat_map (w )
0 commit comments