179 lines
5.1 KiB
Python
179 lines
5.1 KiB
Python
"""
|
|
Simulate maglev control
|
|
Ported from simulateMaglevControl.m
|
|
"""
|
|
|
|
import numpy as np
|
|
from scipy.integrate import solve_ivp
|
|
from utils import euler2dcm, dcm2euler
|
|
from dynamics import quad_ode_function_hf
|
|
from MAGLEV_DIGITALTWIN_PYTHON.additiveController import AdditivePIDController
|
|
|
|
|
|
def simulate_maglev_control(R, S, P):
|
|
"""
|
|
Simulates closed-loop control of a maglev quadrotor.
|
|
|
|
Parameters
|
|
----------
|
|
R : dict
|
|
Reference structure with the following elements:
|
|
- tVec : (N,) array of uniformly-sampled time offsets from initial time (seconds)
|
|
- rIstar : (N, 3) array of desired CM positions in I frame (meters)
|
|
- vIstar : (N, 3) array of desired CM velocities (meters/sec)
|
|
- aIstar : (N, 3) array of desired CM accelerations (meters/sec^2)
|
|
- xIstar : (N, 3) array of desired body x-axis direction (unit vectors)
|
|
|
|
S : dict
|
|
Simulation structure with the following elements:
|
|
- oversampFact : Oversampling factor (must be >= 1)
|
|
- state0 : Initial state dict with:
|
|
- r : (3,) position in world frame (meters)
|
|
- e : (3,) Euler angles (radians)
|
|
- v : (3,) velocity (meters/sec)
|
|
- omegaB : (3,) angular rate vector (rad/sec)
|
|
- distMat : (N-1, 3) array of disturbance forces (Newtons)
|
|
|
|
P : dict
|
|
Parameters structure with:
|
|
- quadParams : QuadParams object
|
|
- constants : Constants object
|
|
|
|
Returns
|
|
-------
|
|
Q : dict
|
|
Output structure with:
|
|
- tVec : (M,) array of output sample time points
|
|
- state : dict with:
|
|
- rMat : (M, 3) position matrix
|
|
- eMat : (M, 3) Euler angles matrix
|
|
- vMat : (M, 3) velocity matrix
|
|
- omegaBMat : (M, 3) angular rate matrix
|
|
- gaps : (M, 4) gap measurements
|
|
- currents : (M, 4) yoke currents
|
|
"""
|
|
# Extract initial state
|
|
s0 = S['state0']
|
|
r0 = s0['r']
|
|
e0 = s0['e']
|
|
v0 = s0['v']
|
|
w0 = s0['omegaB']
|
|
currents0 = np.zeros(4)
|
|
|
|
R0 = euler2dcm(e0).flatten()
|
|
|
|
x0 = np.concatenate([r0, v0, R0, w0, currents0])
|
|
|
|
# Setup simulation parameters
|
|
N = len(R['tVec'])
|
|
dtIn = R['tVec'][1] - R['tVec'][0]
|
|
dt = dtIn / S['oversampFact']
|
|
|
|
p = {
|
|
'quadParams': P['quadParams'],
|
|
'constants': P['constants']
|
|
}
|
|
|
|
rl = P['quadParams'].rotor_loc
|
|
|
|
# Initialize outputs
|
|
tOut = []
|
|
xOut = []
|
|
gapsOut = []
|
|
|
|
xk = x0
|
|
|
|
# Create controller instance to maintain state
|
|
controller = AdditivePIDController()
|
|
|
|
for k in range(N - 1): # loop through each time step
|
|
tspan = np.arange(S['tVec'][k], S['tVec'][k+1] + dt/2, dt)
|
|
|
|
# Setup reference for this time step
|
|
Rk = {
|
|
'rIstark': R['rIstar'][k, :],
|
|
'vIstark': R['vIstar'][k, :],
|
|
'aIstark': R['aIstar'][k, :],
|
|
'xIstark': R['xIstar'][k, :]
|
|
}
|
|
|
|
# Setup state for this time step
|
|
Sk = {
|
|
'statek': {
|
|
'rI': xk[0:3],
|
|
'vI': xk[3:6],
|
|
'RBI': xk[6:15],
|
|
'omegaB': xk[15:18],
|
|
'currents': xk[18:22]
|
|
}
|
|
}
|
|
|
|
# Compute control voltage with noise
|
|
eak = controller.control(Rk, Sk, P) + np.random.normal(0, 0.01, 4)
|
|
|
|
# Disturbance for this time step
|
|
dk = S['distMat'][k, :]
|
|
|
|
# Integrate ODE
|
|
sol = solve_ivp(
|
|
lambda t, X: quad_ode_function_hf(t, X, eak, dk, p),
|
|
[tspan[0], tspan[-1]],
|
|
xk,
|
|
t_eval=tspan,
|
|
method='RK45',
|
|
max_step=dt
|
|
)
|
|
|
|
tk = sol.t
|
|
xk_traj = sol.y.T # Transpose to get (time, state) shape
|
|
|
|
# Calculate gaps for all time points in this segment
|
|
NTK = len(tk)
|
|
gapsk = np.zeros((NTK, 4))
|
|
for q in range(NTK):
|
|
R_q = xk_traj[q, 6:15].reshape(3, 3)
|
|
gapskq = np.abs(xk_traj[q, 2]) - np.array([0, 0, 1]) @ R_q.T @ rl
|
|
gapsk[q, :] = gapskq.flatten()
|
|
|
|
# Store results (excluding last point to avoid duplication)
|
|
tOut.append(tk[:-1])
|
|
xOut.append(xk_traj[:-1, :])
|
|
gapsOut.append(gapsk[:-1, :])
|
|
|
|
# Prepare for next iteration
|
|
xk = xk_traj[-1, :]
|
|
|
|
# Concatenate all segments
|
|
tOut = np.concatenate(tOut)
|
|
xOut = np.vstack(xOut)
|
|
gapsOut = np.vstack(gapsOut)
|
|
|
|
# Add final point
|
|
tOut = np.append(tOut, tk[-1])
|
|
xOut = np.vstack([xOut, xk_traj[-1, :]])
|
|
gapsOut = np.vstack([gapsOut, gapsk[-1, :]])
|
|
|
|
M = len(tOut)
|
|
|
|
# Form Euler angles from rotation matrices
|
|
eMat = np.zeros((M, 3))
|
|
for k in range(M):
|
|
Rk = xOut[k, 6:15].reshape(3, 3)
|
|
ek = dcm2euler(Rk)
|
|
eMat[k, :] = np.real(ek)
|
|
|
|
# Assemble output structure
|
|
Q = {
|
|
'tVec': tOut,
|
|
'state': {
|
|
'rMat': xOut[:, 0:3],
|
|
'vMat': xOut[:, 3:6],
|
|
'eMat': eMat,
|
|
'omegaBMat': xOut[:, 15:18],
|
|
'gaps': gapsOut,
|
|
'currents': xOut[:, 18:22]
|
|
}
|
|
}
|
|
|
|
return Q
|