3-DOF planar arm with Hill-type muscles

ADVANTAGES1 · dim 12

SolvSRK wins. At the comparison noise level, SolvSRK beats the best baseline by at least 10 percentage points of survival, or by at least 0.05 balanced score when survival is tied. Use SolvSRK for this class of problem. All verdicts →

12D 3-DOF planar arm with 6 Hill-type muscles - joint angles, velocities, and muscle fiber dynamics

Robotics

Problem definition

Millard et al. (2013); Zajac (1989); Haeufle et al. (2014)

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 _f_pe(l_ce, l_opt):
    """Parallel elastic element force (exponential toe region)."""
    strain = (l_ce - l_opt) / max(l_opt, 1e-6)
    if strain > 0:
        return _K_PE * strain * strain
    return 0.0

def _fl_active(l_ce, l_opt):
    """Active force-length relationship (Gaussian approximation)."""
    x = (l_ce / max(l_opt, 1e-6) - 1.0) / _WIDTH
    return max(1.0 - x * x, 0.0)

def musculoskeletal_arm_3dof_rhs(t, y):
    q  = y[0:3]
    dq = y[3:6]
    lce = np.maximum(y[6:12], 1e-4)

    dy = np.empty(12)

    # --- Muscle forces and CE dynamics ---
    tau_muscle = np.zeros(_N_JOINTS)
    for m in range(_N_MUSCLES):
        fl = _fl_active(lce[m], _L_OPT[m])

        # Tendon length (simplified: l_mt ~ l_slack + l_opt, constant path)
        l_mt = _L_SLACK[m] + _L_OPT[m]
        l_tendon = l_mt - lce[m]
        k_tendon = 35.0 * _F_MAX[m] / max(_L_SLACK[m], 1e-6)
        f_tendon = max(k_tendon * (l_tendon - _L_SLACK[m]), 0.0)
        f_tendon += _DAMPING * (-dy[6 + m] if m > 0 else 0.0)  # approx

        # CE force at current v_ce (we solve for v_ce from force balance)
        # F_CE + F_PE = F_tendon  ->  a*fl*fv*Fmax + f_pe = f_tendon
        f_pe = _f_pe(lce[m], _L_OPT[m]) * _F_MAX[m]
        target_ce_force = max(f_tendon - f_pe, 0.0)

        # Invert Hill equation for v_ce:
        # target = a * fl * fv(v) * Fmax
        a_fl_fmax = _A_TONIC * fl * _F_MAX[m]
        if a_fl_fmax > 1e-6:
            ratio = target_ce_force / a_fl_fmax
            # From fv = (1+v)/(1-v/0.25), solve for v:
            # ratio*(1 - v/0.25) = 1 + v  =>  v = (ratio - 1) / (ratio/0.25 + 1)
            ratio = min(ratio, 1.39)  # cap at lengthening limit
            v_norm = (ratio - 1.0) / (ratio / 0.25 + 1.0)
            v_ce = v_norm * _L_OPT[m] * _V_MAX
        else:
            # Very low activation: CE extends passively under tendon pull
            v_ce = 0.01 * (f_tendon - f_pe) / max(_F_MAX[m], 1.0)

        dy[6 + m] = v_ce

        # Torque contribution
        f_muscle = f_tendon
        j = _JOINT_MAP[m]
        tau_muscle[j] += _MOMENT[m] * f_muscle

    # --- Joint dynamics: q'' = M^{-1} * (tau - C*q' - G) ---
    # Diagonal inertia (simplified)
    M_diag = _I_LINK.copy()

    # Gravity torques
    G_torque = np.empty(3)
    for j in range(3):
        G_torque[j] = -_M_LINK[j] * _G * _L_LINK[j] * np.sin(q[j])

    # Viscous damping (simplified Coriolis/centrifugal substitute)
    C_damp = 0.5 * dq

    ddq = (tau_muscle - C_damp - G_torque) / M_diag

    dy[0:3] = dq
    dy[3:6] = ddq

    return dy
Parameters
  • _A_TONIC = 0.05
  • _DAMPING = 0.1
  • _F_MAX = [300, 250, 200, 300, 250, 200]
  • _G = 9.81
  • _I_LINK = [0.05, 0.03, 0.01]
  • _JOINT_MAP = [0, 0, 1, 1, 2, 2]
  • _K_PE = 5
  • _L_LINK = [0.3, 0.25, 0.15]
  • _L_OPT = [0.1, 0.1, 0.08, 0.08, 0.06, 0.06]
  • _L_SLACK = [0.15, 0.15, 0.12, 0.12, 0.1, 0.1]
  • _MOMENT = [0.04, -0.04, 0.03, -0.03, 0.02, -0.02]
  • _M_LINK = [2, 1.5, 0.5]
  • _N_JOINTS = 3
  • _N_MUSCLES = 6
  • _V_MAX = 10
  • _WIDTH = 0.56
Initial condition
y(0) = [0.785, 1.571, 0, 0, 0, 0, 0.1, 0.1, 0.08, 0.08, 0.06, 0.06]
Horizon
t ∈ [0, 2]

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: low

Default noise: none

Recommendation snapshot

Clean best: SciPy RK45

Noisy best: SolvSRK

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: 3-DOF planar arm with Hill-type muscles (3-dof-planar-arm-with-hill-type-muscles)

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 RK45SciPy
100%
21,836768 ms0.809
2SciPy DOP853SciPy
100%
109,1663.77 s0.809
3SciPy RK23SciPy
100%
71,1532.65 s0.809
4CVODE BDFexternal
100%
50424 ms0.809
5CVODE Adamsexternal
100%
1,36754 ms0.809
6Tsit5external
100%
4,1881.23 s0.809
7Vern7external
100%
4,3523.93 s0.809
8Vern9external
100%
7,2024.01 s0.809
9TRBDF2external
100%
2,7054.88 s0.809
10FBDFexternal
100%
1,0144.73 s0.809
11SolvSRK
100%
5,055155 ms0.809
SciPy BDFSciPy
0%
SciPy RadauSciPy
0%
SciPy LSODASciPy
0%

At Clean, best balanced arm is SciPy RK45 · SolvSRK survival 100%.

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_3_dof_planar_arm_with_hill_type_muscles_2026,
  title        = {Resonix Evidence Portal: 3-DOF planar arm with Hill-type muscles},
  author       = {{Resonix Labs (Canada) Inc.}},
  year         = {2026},
  howpublished = {\url{https://resonix.tech/evidence/problems/3-dof-planar-arm-with-hill-type-muscles}},
  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