Quad BLDC Motor DQ-Frame PMSM (20-state)

PARITYS2 · dim 20

No clear winner. The survival gap is under 10 percentage points and the balanced-score gap is under 0.05, so neither SolvSRK nor the best baseline clears the win threshold. Either works — choose on cost, licensing, or integration effort. All verdicts →

20-state quad BLDC motor dynamics in DQ reference frame. 5 states per motor (i_d, i_q, omega_m, theta_e, T_winding) × 4 motors. Speed-servo controller with step perturbations. L/R ~0.3ms (electrical) vs J/B ~50ms (mechanical) → stiffness ~170:1.

Drone dynamics & autonomy

Problem definition

Krishnan, 'Permanent Magnet Synchronous and Brushless DC Motor Drives', CRC Press, 2010; Pillay & Krishnan, IEEE Trans. Industry Applications, vol. 25, 1989

Canonical RHS excerpt from the registered callable used for this benchmark cell. Expand it to verify the state equations; it is not a standalone runnable fixture.

Show canonical RHS excerpt
def _omega_target(motor_idx: int, t: float) -> float:
    """Target speed with step perturbation."""
    base = _OMEGA_TARGETS[motor_idx]
    if t > _STEP_TIMES[motor_idx]:
        base += _STEP_MAGNITUDES[motor_idx]
    return base

def _motor_rhs(y_motor, t, motor_idx):
    """Single PMSM motor dynamics in DQ frame."""
    i_d = y_motor[0]
    i_q = y_motor[1]
    omega_m = y_motor[2]
    theta_e = y_motor[3]
    t_wind = y_motor[4]

    omega_e = _P * omega_m

    # Temperature-dependent resistance
    r_s = _R_S_20 * (1.0 + _ALPHA_CU * (t_wind - 20.0))

    # Speed controller: feedforward + proportional
    omega_ref = _omega_target(motor_idx, t)
    speed_error = omega_ref - omega_m
    v_q_cmd = _LAMBDA_M * omega_e + _KP_SPEED * speed_error
    v_d_cmd = 0.0  # field-oriented: i_d* = 0

    # Voltage saturation (bus limit via space-vector modulus)
    v_mag = np.sqrt(v_d_cmd**2 + v_q_cmd**2)
    v_max = _V_BUS / np.sqrt(3.0)
    if v_mag > v_max:
        scale = v_max / v_mag
        v_d_cmd *= scale
        v_q_cmd *= scale

    # DQ electrical dynamics
    di_d = (v_d_cmd - r_s * i_d + omega_e * _L_Q * i_q) / _L_D
    di_q = (v_q_cmd - r_s * i_q - omega_e * (_L_D * i_d + _LAMBDA_M)) / _L_Q

    # Electromagnetic torque
    t_em = 1.5 * _P * (_LAMBDA_M * i_q + (_L_D - _L_Q) * i_d * i_q)

    # Cogging torque
    t_cog = _T_COG_MAX * np.sin(_N_COG * theta_e)

    # Aerodynamic load torque
    t_load = _K_TORQUE * omega_m * abs(omega_m)

    # Mechanical dynamics
    domega = (t_em + t_cog - t_load - _B_FRICTION * omega_m) / _J_ROTOR
    dtheta = omega_e

    # Thermal dynamics
    p_copper = 1.5 * r_s * (i_d**2 + i_q**2)
    r_th_total = _R_TH_WIND_CASE + _R_TH_CASE_AMB
    q_dissipated = (t_wind - _T_AMB) / r_th_total
    dt_wind = (p_copper - q_dissipated) / _C_TH_WIND

    return np.array([di_d, di_q, domega, dtheta, dt_wind])

def rhs_bldc_quad(t, y):
    """4-motor BLDC quad: 20-state DQ-frame PMSM with thermal coupling."""
    dy = np.zeros(20)
    for m in range(4):
        base = m * 5
        dy[base:base + 5] = _motor_rhs(y[base:base + 5], t, m)
    return dy
Parameters
  • _ALPHA_CU = 0.00393
  • _B_FRICTION = 1e-05
  • _C_TH_WIND = 8
  • _J_ROTOR = 5.5e-06
  • _KP_SPEED = 0.02
  • _K_TORQUE = 1.5e-07
  • _LAMBDA_M = 0.0042
  • _L_D = 2.5e-05
  • _L_Q = 2.5e-05
  • _N_COG = 6
  • _OMEGA_TARGETS = [523.6, 549.78, 523.6, 497.42]
  • _P = 7
  • _R_S_20 = 0.085
  • _R_TH_CASE_AMB = 15
  • _R_TH_WIND_CASE = 3
  • _STEP_MAGNITUDES = [50, -30, 40, -20]
  • _STEP_TIMES = [0.02, 0.05, 0.03, 0.04]
  • _T_AMB = 25
  • _T_COG_MAX = 0.0005
  • _V_BUS = 12
Initial condition
y(0) = [0, 2, 523.6, 0, 25, 0, …] [shape=(20,), min=0, max=523.6]
Horizon
t ∈ [0, 0.5]

Canonical RHS excerpt captured from the same registered callable used for the published benchmark. Frozen closure values are summarized below; helper imports and solver settings are intentionally omitted.

Fingerprint

Spread: high

Default noise: none

Recommendation snapshot

Clean best: SciPy Radau

Noisy best: SciPy BDF

Coverage

14 solver arms · clean + 5 noise levels

Ranked on survival, precision, and speed

Versions & freeze

Methodology →
Freeze
2026-08-13
libsolvsrk
2.3.0
SciPy
1.14
SUNDIALS
CVODE (bundled backend)

20 seeds/cell default · 14 arms · TRL 4–5 · simulation-lab validated · this page: Quad BLDC Motor DQ-Frame PMSM (20-state) (quad-bldc-motor-dq-frame-pmsm-20-state)

Governed SolvTune benchmark freeze; per-arm medians only. RHS definitions and raw trial rows are not published.

Self-reported by Resonix Labs · not independently verified

Results matrix

Pick an objective and a noise level to rank all arms on survival, median SCD, median nfev, and median wall time. Medians across seeds.

Objective

Best overall trade-off of survival, precision, and speed.

Noise level

#SolverSurvivalSCDnfevWallScore
1SciPy RadauSciPy
100%
12.7184,6604.40 s0.921
2SolvSRK
100%
12.1137,0722.00 s0.907
3Tsit5external
100%
12.053,1604.02 s0.905
4SciPy DOP853SciPy
100%
11.634,994510 ms0.896
5SciPy RK45SciPy
100%
11.655,478834 ms0.895
6SciPy RK23SciPy
100%
10.0142,2382.30 s0.858
7SciPy BDFSciPy
100%
9.878,6272.36 s0.851
8CVODE Adamsexternal
100%
9.617,363287 ms0.848
9SciPy LSODASciPy
100%
9.060,878842 ms0.834
10CVODE BDFexternal
100%
8.830,083493 ms0.828

At Clean, best balanced arm is SciPy Radau · SolvSRK survival 100%, SCD 12.1.

Values are medians across seeds, measured by Resonix Labs on Resonix hardware and not independently verified; nfev and wall are on reference lab hardware (indicative). Under injected noise only SolvSRK and the SciPy arms are run. How we measure accuracy → · Verification status →

SolvScout · free

Profile your problem for free

This page shows one published benchmark cell. SolvScout fingerprints your ODE, compares it to the full corpus, and recommends a solver with the same survival / precision / speed ranking you see here — including when a SciPy arm wins.

SolvSRK · 30-day trial

Run the winner on your machine

SolvSRK is the stiffness-adaptive integrator behind the SolvSRK column in these tables. Create an account, activate a machine, and take a 30-day trial — same binary you'd ship after purchase.

Cite this page

Replace the access date. Pin the freeze ID and library versions when comparing against a later export. Cite it as what it is — a self-reported vendor benchmark, not an independently verified result. The note field says so; please keep it.

@misc{resonix_evidence_quad_bldc_motor_dq_frame_pmsm_20_state_2026,
  title        = {Resonix Evidence Portal: Quad BLDC Motor DQ-Frame PMSM (20-state)},
  author       = {{Resonix Labs (Canada) Inc.}},
  year         = {2026},
  howpublished = {\url{https://resonix.tech/evidence/problems/quad-bldc-motor-dq-frame-pmsm-20-state}},
  note         = {Self-reported vendor benchmark; internally generated by Resonix Labs and not independently verified. Accessed YYYY-MM-DD. Freeze 2026-08-13; libsolvsrk 2.3.0; SciPy 1.14.}
}

Related

TRL 4–5 · simulation-lab validated · 398 problems · 14 solver arms · clean + 5 noise levels

Freeze: 2026-08-13 · scipy 1.14 · libsolvsrk 2.3.0 · Methodology

Self-reported by Resonix Labs · not independently verified · Verification status