Files
2025-11-23 14:09:20 -06:00

213 lines
5.6 KiB
Python

"""
Top-level script for calling simulate_maglev_control
Ported from topSimulate.m to Python
"""
import numpy as np
import matplotlib.pyplot as plt
from parameters import QuadParams, Constants
from utils import euler2dcm, fmag2
from simulate import simulate_maglev_control
from visualize import visualize_quad
import os
def main():
"""Main simulation script"""
# Create output directory if it doesn't exist
output_dir = 'sim_results'
os.makedirs(output_dir, exist_ok=True)
# Total simulation time, in seconds
Tsim = 2
# Update interval, in seconds. This value should be small relative to the
# shortest time constant of your system.
delt = 0.005 # sampling interval
# Maglev parameters and constants
quad_params = QuadParams()
constants = Constants()
m = quad_params.m
g = constants.g
J = quad_params.Jq
# Time vector, in seconds
N = int(np.floor(Tsim / delt))
tVec = np.arange(N) * delt
# Matrix of disturbance forces acting on the body, in Newtons, expressed in I
distMat = np.random.normal(0, 0, (N-1, 3))
# Oversampling factor
oversampFact = 10
# Check nominal gap
print(f"Force check: {4*fmag2(0, 10.830e-3) - m*g}")
# SET REFERENCE HERE
ref_gap = 10.830e-3 # from python code
z0 = ref_gap - 2e-3
# Create reference trajectories
rIstar = np.zeros((N, 3))
vIstar = np.zeros((N, 3))
aIstar = np.zeros((N, 3))
xIstar = np.zeros((N, 3))
for k in range(N):
rIstar[k, :] = [0, 0, -ref_gap]
vIstar[k, :] = [0, 0, 0]
aIstar[k, :] = [0, 0, 0]
xIstar[k, :] = [0, 1, 0]
# Setup reference structure
R = {
'tVec': tVec,
'rIstar': rIstar,
'vIstar': vIstar,
'aIstar': aIstar,
'xIstar': xIstar
}
# Initial state
state0 = {
'r': np.array([0, 0, -(z0 + quad_params.yh)]),
'v': np.array([0, 0, 0]),
'e': np.array([0.01, 0.01, np.pi/2]), # xyz euler angles
'omegaB': np.array([0.00, 0.00, 0])
}
# Setup simulation structure
S = {
'tVec': tVec,
'distMat': distMat,
'oversampFact': oversampFact,
'state0': state0
}
# Setup parameters structure
P = {
'quadParams': quad_params,
'constants': constants
}
# Run simulation
print("Running simulation...")
P0 = simulate_maglev_control(R, S, P)
print("Simulation complete!")
# Extract results
tVec_out = P0['tVec']
state = P0['state']
rMat = state['rMat']
eMat = state['eMat']
vMat = state['vMat']
gaps = state['gaps']
currents = state['currents']
omegaBMat = state['omegaBMat']
# Calculate forces
Fm = fmag2(currents[:, 0], gaps[:, 0])
# Calculate ex and ey for visualization
N_out = len(eMat)
ex = np.zeros(N_out)
ey = np.zeros(N_out)
for k in range(N_out):
Rk = euler2dcm(eMat[k, :])
x = Rk @ np.array([1, 0, 0])
ex[k] = x[0]
ey[k] = x[1]
# Visualize the quad motion
print("Generating 3D visualization...")
S2 = {
'tVec': tVec_out,
'rMat': rMat,
'eMat': eMat,
'plotFrequency': 20,
'makeGifFlag': True,
'gifFileName': 'sim_results/testGif.gif',
'bounds': [-1, 1, -1, 1, -300e-3, 0.000]
}
visualize_quad(S2)
# Create plots
fig = plt.figure(figsize=(12, 8))
# Plot 1: Gaps
ax1 = plt.subplot(3, 1, 1)
plt.plot(tVec_out, gaps * 1e3)
plt.axhline(y=ref_gap * 1e3, color='k', linestyle='-', linewidth=1)
plt.ylabel('Vertical (mm)')
plt.title('Gaps')
plt.grid(True)
plt.xticks([])
# Plot 2: Currents
ax2 = plt.subplot(3, 1, 2)
plt.plot(tVec_out, currents)
plt.ylabel('Current (Amps)')
plt.title('Power')
plt.grid(True)
plt.xticks([])
# Plot 3: Forces
ax3 = plt.subplot(3, 1, 3)
plt.plot(tVec_out, Fm)
plt.xlabel('Time (sec)')
plt.ylabel('Fm (N)')
plt.title('Forcing')
plt.grid(True)
plt.tight_layout()
plt.savefig('sim_results/simulation_results.png', dpi=150)
print("Saved plot to simulation_results.png")
# Commented out horizontal position plot (as in MATLAB)
# fig_horizontal = plt.figure()
# plt.plot(rMat[:, 0], rMat[:, 1])
# spacing = 300
# plt.quiver(rMat[::spacing, 0], rMat[::spacing, 1],
# ex[::spacing], -ey[::spacing])
# plt.axis('equal')
# plt.grid(True)
# plt.xlabel('X (m)')
# plt.ylabel('Y (m)')
# plt.title('Horizontal position of CM')
# Frequency analysis (mech freq)
Fs = 1/delt * oversampFact # Sampling frequency
T = 1/Fs # Sampling period
L = len(tVec_out) # Length of signal
Y = np.fft.fft(Fm)
frequencies = Fs / L * np.arange(L)
fig2 = plt.figure(figsize=(10, 6))
plt.semilogx(frequencies, np.abs(Y), linewidth=3)
plt.title("Complex Magnitude of fft Spectrum")
plt.xlabel("f (Hz)")
plt.ylabel("|fft(X)|")
# Exclude DC component (first element) from scaling to see AC components better
plt.ylim([0, np.max(np.abs(Y[1:])) * 1.05]) # Dynamic y-axis excluding DC
plt.grid(True)
plt.savefig('sim_results/fft_spectrum.png', dpi=150)
print("Saved FFT plot to fft_spectrum.png")
# Show all plots
plt.show()
return P0
if __name__ == '__main__':
results = main()
print("\nSimulation completed successfully!")
print(f"Final time: {results['tVec'][-1]:.3f} seconds")
print(f"Number of time points: {len(results['tVec'])}")