Files
guadaloop_lev_control/MAGLEV_DIGITALTWIN_PYTHON/simulateMultipleWithNoise.py

368 lines
13 KiB
Python

"""
Multiple trial simulation with randomized noise for maglev system
Runs multiple simulations with different parameter variations
"""
import numpy as np
import matplotlib.pyplot as plt
from parameters import QuadParams, Constants, initialize_parameter_variations, reset_parameter_variations
from utils import euler2dcm, fmag2, initialize_magnetic_characteristics, reset_magnetic_characteristics, get_magnetic_characteristics
from simulate import simulate_maglev_control
from visualize import visualize_quad
import os
from concurrent.futures import ProcessPoolExecutor
import multiprocessing
# ===== SIMULATION CONFIGURATION =====
NUM_TRIALS = 5 # Number of trials to run
NUM_CORES_LIMIT = 8 # Max number of CPU cores to use for simulations
# (min of this and num of cores on ur computer will be chosen.)
NOISE_LEVEL = 0.1 # Standard deviation of noise (5%)
# =====================================
def generate_parameter_report(trial_num, quad_params, output_dir):
"""Generate a text report of all parameters for this trial"""
# Get magnetic characteristics
mag_chars = get_magnetic_characteristics()
report_filename = f'{output_dir}/trial_{trial_num:02d}_parameters.txt'
with open(report_filename, 'w') as f:
f.write(f"="*70 + "\n")
f.write(f"Parameter Report for Trial {trial_num}\n")
f.write(f"Noise Level: {NOISE_LEVEL*100:.1f}%\n")
f.write(f"="*70 + "\n\n")
# MECHANICAL PARAMETERS
f.write(f"MECHANICAL PARAMETERS\n")
f.write(f"-" * 70 + "\n")
f.write(f"Mass (m): {quad_params.m:.6f} kg\n")
f.write(f"\nMoment of Inertia (Jq): (kg⋅m²)\n")
f.write(f" Jxx: {quad_params.Jq[0,0]:.9f}\n")
f.write(f" Jyy: {quad_params.Jq[1,1]:.9f}\n")
f.write(f" Jzz: {quad_params.Jq[2,2]:.9f}\n")
f.write(f"\nFrame Dimensions:\n")
f.write(f" Length (frame_l): {quad_params.frame_l:.6f} m ({quad_params.frame_l*1000:.3f} mm)\n")
f.write(f" Width (frame_w): {quad_params.frame_w:.6f} m ({quad_params.frame_w*1000:.3f} mm)\n")
f.write(f" Yoke Height (yh): {quad_params.yh:.6f} m ({quad_params.yh*1000:.3f} mm)\n")
f.write(f"\nRotor/Yoke Locations (m): [x, y, z] for each corner\n")
for i in range(4):
f.write(f" Yoke {i+1}: [{quad_params.rotor_loc[0,i]:8.6f}, "
f"{quad_params.rotor_loc[1,i]:8.6f}, "
f"{quad_params.rotor_loc[2,i]:8.6f}]\n")
f.write(f"\nSensor Locations (m): [x, y, z] for each edge center\n")
for i in range(4):
f.write(f" Sensor {i+1}: [{quad_params.sensor_loc[0,i]:8.6f}, "
f"{quad_params.sensor_loc[1,i]:8.6f}, "
f"{quad_params.sensor_loc[2,i]:8.6f}]\n")
f.write(f"\nSensor Noise: {quad_params.gap_sigma*1e6:.3f} μm (std dev)\n")
# ELECTROMAGNETIC PARAMETERS
f.write(f"\n\nELECTROMAGNETIC PARAMETERS\n")
f.write(f"-" * 70 + "\n")
f.write(f"Yoke Electrical Characteristics (per yoke):\n")
for i in range(4):
f.write(f" Yoke {i+1}:\n")
f.write(f" Resistance (R): {quad_params.yokeR_individual[i]:.6f} Ω\n")
f.write(f" Inductance (L): {quad_params.yokeL_individual[i]:.9f} H ({quad_params.yokeL_individual[i]*1e3:.6f} mH)\n")
f.write(f" Max Voltage: {quad_params.maxVoltage[i]:.3f} V\n")
f.write(f"\nMagnetic Force Model Coefficients:\n")
f.write(f" N (turns): {mag_chars['N']:.3f}\n")
f.write(f" const1: {mag_chars['const1']:.6e}\n")
f.write(f" const2: {mag_chars['const2']:.6e}\n")
f.write(f" const3: {mag_chars['const3']:.6e}\n")
f.write(f" const4: {mag_chars['const4']:.6e}\n")
f.write(f" const5: {mag_chars['const5']:.6e}\n")
f.write(f"\n" + "="*70 + "\n")
f.write(f"End of Report\n")
f.write(f"="*70 + "\n")
print(f" Saved parameter report: {report_filename}")
def run_single_trial(trial_num, Tsim, delt, ref_gap, z0, output_dir):
"""Run a single simulation trial with randomized parameters, including all visualizations"""
# Reset and reinitialize noise for this trial
reset_parameter_variations()
reset_magnetic_characteristics()
initialize_parameter_variations(noise_level=NOISE_LEVEL)
initialize_magnetic_characteristics(noise_level=NOISE_LEVEL)
# Maglev parameters and constants (with new noise)
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"Trial {trial_num}: 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
}
# Generate parameter report for this trial
generate_parameter_report(trial_num, quad_params, output_dir)
# Run simulation
print(f"Trial {trial_num}: Running simulation...")
P0 = simulate_maglev_control(R, S, P)
print(f"Trial {trial_num}: Simulation complete!")
# Extract results
tVec_out = P0['tVec']
state = P0['state']
rMat = state['rMat']
eMat = state['eMat']
gaps = state['gaps']
currents = state['currents']
# Generate 3D visualization (GIF) without displaying
print(f"Trial {trial_num}: Generating 3D visualization...")
S2 = {
'tVec': tVec_out,
'rMat': rMat,
'eMat': eMat,
'plotFrequency': 20,
'makeGifFlag': True,
'gifFileName': f'{output_dir}/trial_{trial_num:02d}_animation.gif',
'bounds': [-1, 1, -1, 1, -300e-3, 0.000]
}
visualize_quad(S2)
plt.close('all') # Close all figures to prevent display
# Calculate forces
Fm = fmag2(currents[:, 0], gaps[:, 0])
# Generate plots immediately after simulation
print(f"Trial {trial_num}: Generating plots...")
# Create plots for this trial
fig = plt.figure(figsize=(12, 8))
fig.suptitle(f'Trial {trial_num} - Noise Level {NOISE_LEVEL*100:.1f}%', fontsize=14, fontweight='bold')
# 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, label='Reference')
plt.ylabel('Gap (mm)')
plt.title('Sensor Gaps')
plt.legend(['Sensor 1', 'Sensor 2', 'Sensor 3', 'Sensor 4', 'Reference'],
loc='upper right', fontsize=8)
plt.grid(True)
plt.xticks([])
# Plot 2: Currents
ax2 = plt.subplot(3, 1, 2)
plt.plot(tVec_out, currents)
plt.ylabel('Current (A)')
plt.title('Yoke Currents')
plt.legend(['Yoke 1', 'Yoke 2', 'Yoke 3', 'Yoke 4'],
loc='upper right', fontsize=8)
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('Force (N)')
plt.title('Magnetic Force (Yoke 1)')
plt.grid(True)
plt.tight_layout()
plt.savefig(f'{output_dir}/trial_{trial_num:02d}_results.png', dpi=150)
plt.close()
# FFT for this trial
Fs = 1/delt * oversampFact
L = len(tVec_out)
Y = np.fft.fft(Fm)
frequencies = Fs / L * np.arange(L)
fig2 = plt.figure(figsize=(10, 6))
plt.semilogx(frequencies, np.abs(Y), linewidth=2)
plt.title(f"FFT Spectrum - Trial {trial_num}")
plt.xlabel("Frequency (Hz)")
plt.ylabel("Magnitude")
plt.ylim([0, np.max(np.abs(Y[1:])) * 1.05])
plt.grid(True)
plt.savefig(f'{output_dir}/trial_{trial_num:02d}_fft.png', dpi=150)
plt.close()
print(f"Trial {trial_num}: COMPLETE (all outputs generated)")
return {
'tVec': tVec_out,
'gaps': gaps,
'currents': currents,
'Fm': Fm,
'quad_params': quad_params
}
def main():
"""Main simulation script - runs multiple trials"""
# Create output directory if it doesn't exist
output_dir = 'sim_results_multi'
os.makedirs(output_dir, exist_ok=True)
# Total simulation time, in seconds
Tsim = 2
# Update interval, in seconds
delt = 0.005 # sampling interval
# Reference gap and initial condition
ref_gap = 10.830e-3
z0 = ref_gap - 2e-3
print(f"\n{'='*60}")
print(f"Running {NUM_TRIALS} trials with noise level {NOISE_LEVEL*100:.1f}%")
num_cores = min(multiprocessing.cpu_count(), NUM_CORES_LIMIT)
print(f"Using {num_cores} CPU cores for parallel processing")
print(f"{'='*60}\n")
# Run all trials in parallel (each trial does simulation + visualization + plots)
trial_results = []
with ProcessPoolExecutor(max_workers=num_cores) as executor:
# Submit all trials
futures = [
executor.submit(run_single_trial, trial, Tsim, delt, ref_gap, z0, output_dir)
for trial in range(1, NUM_TRIALS + 1)
]
# Collect results as they complete
for future in futures:
result = future.result()
trial_results.append(result)
# Create overlay comparison plots
print("\nGenerating comparison plots...")
# Gaps comparison
fig_comp = plt.figure(figsize=(14, 10))
fig_comp.suptitle(f'All Trials Comparison ({NUM_TRIALS} trials, {NOISE_LEVEL*100:.1f}% noise)',
fontsize=14, fontweight='bold')
ax1 = plt.subplot(3, 1, 1)
for i, result in enumerate(trial_results, 1):
avg_gap = np.mean(result['gaps'], axis=1)
plt.plot(result['tVec'], avg_gap * 1e3, alpha=0.7, label=f'Trial {i}')
plt.axhline(y=ref_gap * 1e3, color='k', linestyle='--', linewidth=2, label='Reference')
plt.ylabel('Average Gap (mm)')
plt.title('Average Gap Across All Trials')
plt.legend(loc='upper right', fontsize=8)
plt.grid(True)
plt.xticks([])
# Currents comparison
ax2 = plt.subplot(3, 1, 2)
for i, result in enumerate(trial_results, 1):
avg_current = np.mean(result['currents'], axis=1)
plt.plot(result['tVec'], avg_current, alpha=0.7, label=f'Trial {i}')
plt.ylabel('Average Current (A)')
plt.title('Average Current Across All Trials')
plt.legend(loc='upper right', fontsize=8)
plt.grid(True)
plt.xticks([])
# Forces comparison
ax3 = plt.subplot(3, 1, 3)
for i, result in enumerate(trial_results, 1):
plt.plot(result['tVec'], result['Fm'], alpha=0.7, label=f'Trial {i}')
plt.xlabel('Time (sec)')
plt.ylabel('Force (N)')
plt.title('Magnetic Force Across All Trials')
plt.legend(loc='upper right', fontsize=8)
plt.grid(True)
plt.tight_layout()
plt.savefig(f'{output_dir}/comparison_all_trials.png', dpi=150)
print(f"Saved comparison plot")
plt.close() # Close without displaying
print(f"\n{'='*60}")
print(f"All trials completed!")
print(f"Results saved to: {output_dir}/")
print(f" - Individual trial plots: trial_XX_results.png")
print(f" - Individual trial animations: trial_XX_animation.gif")
print(f" - Individual FFT plots: trial_XX_fft.png")
print(f" - Individual parameter reports: trial_XX_parameters.txt")
print(f" - Comparison plot: comparison_all_trials.png")
print(f"{'='*60}\n")
return trial_results
if __name__ == '__main__':
results = main()
print(f"\nCompleted {len(results)} trials successfully!")