Loading model…

Guadaloop
Testrig

Scroll to explore

Click to replay the 3D · minimise ›
0%
press G to edit part groups

Grouping editor

yoke (BabyYoke + coils)
sensors (DW-AD modules)
rails (slide system)
chassis (frame)
other / hidden
Drag to orbit. Click a part to cycle its group. Press E to export JSON, G to exit.
click a part…
✕ close
Copied to clipboard. Paste this into groups.json (replace the whole file), then reload.
Texas Guadaloop · Levitation

LevSim is the simulation environment I built so levitation controllers can be designed and tuned against the rig's real magnetic behaviour — not a hand-derived model that falls apart on contact with hardware.

Ansys FEASciKit-LearnMuJoCoGymnasiumPID · LQR · RL
Role
CAD · All Software Architecture + Implementation · Ansys work guided by Aditya Matam & Neha Ramachandran
Timeline
Nov – Dec 2025 · realism & MuJoCo overhaul Aug 2026
Stack
Ansys Electronics Desktop · SciKit-Learn · MuJoCo · Gymnasium
Status
Actively developed

The problem

Hybrid electromagnetic suspension (HEMS) is a proven low-power way to levitate, but it's brutally nonlinear — and for a student team there's no clean way to simulate one without re-deriving the magnetic flux through the whole structure (and the air around it) for every design change. At best that's tedious; at worst it's a wall that keeps teams without graduate-level magnetostatics from ever touching the control problem.

After two years on the lev sub-team I'd watched the same gap open again and again: controllers that held rock-steady in our MATLAB sim under any disturbance fell apart on the real rig under near-perfect conditions. We were missing a fundamental piece of the analytical model, and MATLAB wasn't scaling past PID anyway. The fix was to stop hand-calculating forces and root the simulation in empirical data instead — Ansys FEA (verified against load-cell measurements) → a polynomial force model → a PyBullet rigid-body sim.

System overview

LevSim system architecture An offline Ansys FEA sweep of a single yoke is fit to a polynomial force model. At run time, the Gymnasium pod environment orchestrates a loop: it reads pod state from MuJoCo, converts controller output to coil currents with maglev_coil.py, queries the MagLev Predictor for per-yoke force and torque, and feeds those back into MuJoCo — all wrapped in a Gymnasium interface a PID, LQR, or RL controller can drive. Ansys FEA single-yoke magnetostatic Force / torque LUT 4 states → force + torque Polynomial fit Maglev_Model.pkl parametric sweep SciKit-Learn fit loaded at startup MagLev Predictor state → force / torque mini physics server Lev_pod_env.py Gymnasium env, connective tissue. implements sensor noise + reward Controllers PID · LQR · RL MuJoCo rigid-body dynamics · 1.2 kHz maglev_coil.py PWM → coil current incl. inductive impedance ↓ force, torque ↑ state + currents state + reward control output per-step force / torque physical state of pod ↓ analog PWM ↑ coil current
Two-phase architecture: an Ansys FEA sweep of a single yoke is distilled offline into a polynomial force model, which the runtime loop (Lev_pod_env.py ⇄ MuJoCo, 240 Hz control over 1.2 kHz physics) queries every step while a controller drives the pod. Redrawn from the project's dataflow diagram.

The pipeline splits in two. Offline, an Ansys magnetostatic model of a single yoke is swept across the coil currents, gap heights and roll angles the rig actually sees; the resulting force/torque table is fit to a polynomial (Maglev_Model.pkl) so it can be evaluated in microseconds. At run time, the Gymnasium pod environment is the orchestrator: each step it pulls the pod's state from MuJoCo, converts the controller's PWM output into real coil currents with maglev_coil.py, asks the MagLev Predictor for the per-yoke force and torque, and feeds those point loads back into MuJoCo — all wrapped in a Gymnasium interface so any controller can drive it. It began life on PyBullet; the Aug 2026 overhaul below moved it to MuJoCo and made the plant a great deal more honest.

Decisions & deep dives

Why FEA, not an analytical model?

I first proved FEA was trustworthy: a load-cell setup measured the force on a single small yoke, and Ansys magnetostatic results at the same gaps agreed within ~10%. That made an FEA-derived lookup a reliable replacement for the broken analytical chain.

Why a polynomial on top of FEA?

Running Ansys inside the sim loop is infeasible. A parametric sweep across the rig's working range of heights, roll angles and coil currents gives a discrete table; fitting it in SciKit-Learn (lev_sim/Function Fitting.ipynb) turns each step into a cheap polynomial evaluation — and makes the pipeline plug-and-play for new yoke designs.

Why MuJoCo + Gymnasium?

The physics engine handles the rigid-body dynamics; a custom maglev_coil component models coil current so the force model gets real currents, and Gymnasium standardizes the interface, so the same environment runs under PID (lev_PID) or RL (lev_PPO). It started on PyBullet; in Aug 2026 I moved it to MuJoCo — a maintained engine with named sites for sensors and force application (replacing a collision-shape-parsing heuristic), world-frame force injection, and an MJX path to thousands of parallel envs when RL training scales up. The visual meshes come from the same CAD GLB as the 3D viewer on this page.

How do you reuse it across controllers?

lev_pod_env.py exposes exactly the I/O a controller sees on the real pod — distance-sensor readings in, motor-controller PWM out — so anything written against the sim can move straight onto the rig with no interface changes.

Making the sim honest (Aug 2026)

Months of "stable" PID and PPO results turned out to rest on three quiet defects. The coil integrator ran forward-Euler right at its stability limit, ringing in a way that made the actuator look ~40% faster than the real coil. The sensor-noise model was computed and then silently discarded. And because a noise-free sim is perfectly symmetric, two unstable axes were simply never excited — every controller that "worked" had never actually been asked to stabilize them. The overhaul rebuilt the plant around honesty: exact coil integration, real noise, decoupled control/physics rates (240 Hz control over 1.2 kHz physics), actuation latency, filtered PID derivatives, the CAD's measured mass properties, and the engine swap to MuJoCo.

The overhauled sim: release from 13 mm, settle at the 11.86 mm target, take a 15 N impulse + random torque at t = 4 s, recover. Gap-cam inset watches the front pole faces; HUD shows gap, attitude, coil currents and per-yoke force.

The bug that hid an unstable axis

With sensor noise accidentally disabled, the sim was perfectly fore-aft symmetric — so the pitch axis was never disturbed, and nobody could see that its magnetic stiffness (~250 N·m/rad, destabilizing) outgunned the tuned controller by ~12×. Restore noise, even 10 µm of it, and pitch ran away, doubling every ~50 ms. The fix was structural: attitude gains in the same class as the height loop, and a tuner search space that stopped capping them an order of magnitude too low.

Two sign errors, hiding each other

Roll was worse: the env fed a left-handed roll angle into the right-handed FEA torque model, which flipped the magnetic roll stiffness from destabilizing to restoring — an artificial 4.3 Hz rocking mode. And the PID's roll error was inverted. Each bug masked the other: on the self-stabilizing fake plant, the tuner quietly learned to keep the wrong-signed roll gain near zero. Found by tilting the pod ±1° with no controller and checking the sign of the angular acceleration against physics — attraction across a gap must grow a tilt.

The flight envelope, from data

With the CAD's real mass properties (10.52 kg — a kilogram heavier than the sim had assumed) the force model dictates a hard envelope: beyond ~14.7 mm gap the ±10.2 A coils can't out-pull gravity, below ~7.5 mm the permanent-magnet bias captures the pod irrecoverably, so the usable release window is ~9–14 mm and hover costs ~10 A total. Numbers a controller can't tune its way around — and exactly what the hardware team needs to know before removing the guide slides.

A live cockpit for the sim

A standalone interactive demo (live_demo.py) runs the tuned closed loop in MuJoCo with a control panel: poke the pod with forces at its corners and midpoints, kick it with torques, start random-disturbance generators, retune the PID gains and target gap live, and watch gap, attitude and all four coil currents on scrolling charts. The retuned controller holds 11.85 ± 0.04 mm and returns to zero-mean roll after kicks up to 12.5 N·m.

Results & honest limits

  • A repeatable simulation process that works for complex systems like maglev, grounded in FEA data and tested with both a hand-tuned PID loop and a trained PPO policy — and, since the overhaul, a plant honest enough to expose controllers that only worked on paper.
  • The tuned loop releases from anywhere in the feasible window, hovers at 11.85 ± 0.04 mm, and recovers from impulse and continuous random disturbances; the residual 0.2° attitude jitter is exactly the sensor-noise floor.
  • The force model is only as good as the swept range — inputs are now clamped to the FEA's validity hull (the 1/gap polynomial predicted 41 kN where the data says 400 N).
  • Still open: gap-dependent coil inductance + motional back-EMF from a new Ansys flux-linkage sweep, sensor-noise calibration against the real Baumer sensors, and system-ID against rig telemetry to close the sim-to-real loop for good.

The team

Videos

Full walkthrough · 8:22
Working demo · 1:43
Safe completion demo · 0:58
Clean levitation run · 0:08
Early instability · 0:42
This is a condensed case study. Want more details? Reach out.