Dual Simultaneous Engagement (dim=40)

BOUNDARYS2 · dim 40

Partial / unstable. SolvSRK survives more than 0% but less than 90% of runs at the comparison noise level, without being beaten by a baseline there. Usable with margin and monitoring; validate on your own configuration. All verdicts →

Two independent 3D engagements coupled through a shared radar resource scheduler. Tests SolvSRK at dim=40 with weak cross-coupling through sensor allocation.

Defense autonomy

Problem definition

Blackman & Popoli (1999) Ch. 7; Bar-Shalom et al. (2001) Ch. 11

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 _clamp(x: float, lo: float, hi: float) -> float:
    if x < lo:
        return lo
    if x > hi:
        return hi
    return x

def _sigmoid_window(t: float, t_center: float, tau: float) -> float:
    """Unit pulse centred at *t_center*, width ~4*tau, Lipschitz-continuous."""
    arg = (t - t_center) / max(tau, 1e-12)
    s = 1.0 / (1.0 + np.exp(-arg))
    return 4.0 * s * (1.0 - s)

def rhs(t, y):
    d = np.empty(dim)

    # ============== Engagement 1 ==============
    xt1, yt1, zt1 = y[0], y[1], y[2]
    vxt1, vyt1, vzt1 = y[3], y[4], y[5]
    xi1, yi1, zi1 = y[6], y[7], y[8]
    vxi1, vyi1, vzi1 = y[9], y[10], y[11]
    xh1, yh1, zh1 = y[12], y[13], y[14]
    vxh1, vyh1, vzh1 = y[15], y[16], y[17]

    # Target 1: 3-D weave
    d[0] = vxt1
    d[1] = vyt1
    d[2] = vzt1
    d[3] = a_t1 * np.sin(omega_t1 * t)
    d[4] = a_t1 * np.cos(omega_t1 * t)
    d[5] = a_t1 * 0.3 * np.sin(0.7 * omega_t1 * t)

    # Interceptor 1: PN
    rx1 = xh1 - xi1
    ry1 = yh1 - yi1
    rz1 = zh1 - zi1
    vrx1 = vxh1 - vxi1
    vry1 = vyh1 - vyi1
    vrz1 = vzh1 - vzi1
    a_y1, a_z1, lam_h1 = _pn_accel_3d(rx1, ry1, rz1, vrx1, vry1, vrz1)
    cos_h1, sin_h1 = np.cos(lam_h1), np.sin(lam_h1)

    d[6] = vxi1
    d[7] = vyi1
    d[8] = vzi1
    d[9] = -a_y1 * sin_h1
    d[10] = a_y1 * cos_h1
    d[11] = a_z1

    # EKF 1 propagation
    d[12] = vxh1
    d[13] = vyh1
    d[14] = vzh1
    d[15] = 0.0
    d[16] = 0.0
    d[17] = 0.0

    # ============== Engagement 2 ==============
    xt2, yt2, zt2 = y[18], y[19], y[20]
    vxt2, vyt2, vzt2 = y[21], y[22], y[23]
    xi2, yi2, zi2 = y[24], y[25], y[26]
    vxi2, vyi2, vzi2 = y[27], y[28], y[29]
    xh2, yh2, zh2 = y[30], y[31], y[32]
    vxh2, vyh2, vzh2 = y[33], y[34], y[35]

    # Target 2: crossing maneuver
    d[18] = vxt2
    d[19] = vyt2
    d[20] = vzt2
    d[21] = a_t2 * 0.5 * np.sin(omega_t2 * t)
    d[22] = a_t2 * np.cos(omega_t2 * t)
    d[23] = a_t2 * 0.4 * np.cos(0.5 * omega_t2 * t)

    # Interceptor 2: PN
    rx2 = xh2 - xi2
    ry2 = yh2 - yi2
    rz2 = zh2 - zi2
    vrx2 = vxh2 - vxi2
    vry2 = vyh2 - vyi2
    vrz2 = vzh2 - vzi2
    a_y2, a_z2, lam_h2 = _pn_accel_3d(rx2, ry2, rz2, vrx2, vry2, vrz2)
    cos_h2, sin_h2 = np.cos(lam_h2), np.sin(lam_h2)

    d[24] = vxi2
    d[25] = vyi2
    d[26] = vzi2
    d[27] = -a_y2 * sin_h2
    d[28] = a_y2 * cos_h2
    d[29] = a_z2

    # EKF 2 propagation
    d[30] = vxh2
    d[31] = vyh2
    d[32] = vzh2
    d[33] = 0.0
    d[34] = 0.0
    d[35] = 0.0

    # ============== Scheduler ==============
    T_dw1 = y[36]
    T_dw2 = y[37]
    P_det1 = y[38]
    P_det2 = y[39]

    # Required dwell time grows as track quality degrades
    R1 = np.sqrt(rx1**2 + ry1**2 + rz1**2 + 0.01)
    R2 = np.sqrt(rx2**2 + ry2**2 + rz2**2 + 0.01)
    # SNR proxy: dwell requirement proportional to R^4 (radar equation)
    T_req1 = T_frame * 0.5 * (R1 / 10000.0) ** 2
    T_req2 = T_frame * 0.5 * (R2 / 10000.0) ** 2

    # Resource constraint: if both demand too much, scale proportionally
    T_total_req = T_req1 + T_req2
    if T_total_req > T_frame:
        scale = T_frame / T_total_req
        T_req1 *= scale
        T_req2 *= scale

    d[36] = (T_req1 - T_dw1) / tau_sched
    d[37] = (T_req2 - T_dw2) / tau_sched

    # Detection probability tracks dwell allocation
    P_req1 = _clamp(T_dw1 / max(T_req1, 1e-6), 0.0, 1.0)
    P_req2 = _clamp(T_dw2 / max(T_req2, 1e-6), 0.0, 1.0)
    d[38] = (P_req1 - P_det1) / (tau_sched * 2.0)
    d[39] = (P_req2 - P_det2) / (tau_sched * 2.0)

    # ============== Measurement updates ==============
    k = int(t / T_update + 0.5)
    k = min(k, n_updates - 1)
    t_k = update_times[k]
    w = _sigmoid_window(t, t_k, tau_update)

    if w > 1e-6:
        rate = w / max(tau_update, 1e-6)

        # EKF gains scale with detection probability (coupling mechanism)
        K_pos1 = K_pos_base * _clamp(P_det1, 0.1, 1.0)
        K_vel1 = K_vel_base * _clamp(P_det1, 0.1, 1.0)
        K_pos2 = K_pos_base * _clamp(P_det2, 0.1, 1.0)
        K_vel2 = K_vel_base * _clamp(P_det2, 0.1, 1.0)

        _apply_update_3d(
            d, 12, xi1, yi1, zi1, xt1, yt1, zt1,
            xh1, yh1, zh1,
            noise_r1[k], noise_az1[k], noise_el1[k],
            K_pos1, K_vel1, rate,
        )
        _apply_update_3d(
            d, 30, xi2, yi2, zi2, xt2, yt2, zt2,
            xh2, yh2, zh2,
            noise_r2[k], noise_az2[k], noise_el2[k],
            K_pos2, K_vel2, rate,
        )

    return d
Parameters
  • K_pos_base = 0.45
  • K_vel_base = 0.2
  • T_frame = 0.04
  • T_update = 0.01
  • a_t1 = 25
  • a_t2 = 20
  • dim = 40
  • n_updates = 6002
  • noise_az1 = [-0.000571074098886, 0.00613951701106, 0.0111986858872, 0.00251111822914, -0.00828100123578, -0.00509642977247, …] [shape=(6002,), min=-0.0237908612417, max=0.0275451331324]
  • noise_az2 = [-0.00802493120427, -0.00688842493785, 0.00291895658592, 0.00417290572287, 0.00505298315343, 0.00863211210178, …] [shape=(6002,), min=-0.0244772839505, max=0.0253622631068]
  • noise_el1 = [-0.00760456205273, -0.0118415373737, 0.00864477778914, -0.000794919130315, 0.00163065935121, 0.00592640714088, …] [shape=(6002,), min=-0.028086945892, max=0.0266868366956]
  • noise_el2 = [0.00116686219261, -0.0132062108204, 0.00113510407344, 0.00253408933933, -0.0027459250115, 0.000468958680236, …] [shape=(6002,), min=-0.0270059443381, max=0.0267108572122]
  • noise_r1 = [1.00584176875, -1.05683890633, 5.12338120355, 0.839200937224, -4.28535498529, 2.89276043928, …] [shape=(6002,), min=-31.1953738404, max=26.0575925977]
  • noise_r2 = [0.142193525504, -9.16433609358, 15.0045285553, 5.85616472214, 6.95835361039, 8.85529016343, …] [shape=(6002,), min=-35.9529363214, max=31.2179690985]
  • omega_t1 = 0.3
  • omega_t2 = 0.6
  • tau_sched = 0.1
  • tau_update = 0.001
  • update_times = [0, 0.01, 0.02, 0.03, 0.04, 0.05, …] [shape=(6002,), min=0, max=60.01]
Initial condition
y(0) = [10000, 4000, 2000, -200, 0, -30, …] [shape=(40,), min=-3000, max=10000]
Horizon
t ∈ [0, 60]

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

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: Dual Simultaneous Engagement (dim=40) (dual-simultaneous-engagement-dim-40)

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
1SolvSRK
20%
2,038,173116.27 s0.409
SciPy BDFSciPy
0%
SciPy RadauSciPy
0%
SciPy RK45SciPy
0%
SciPy LSODASciPy
0%
SciPy DOP853SciPy
0%
SciPy RK23SciPy
0%
CVODE BDFexternal
0%
CVODE Adamsexternal
0%
Tsit5external
0%

At Clean, best balanced arm is SolvSRK.

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_dual_simultaneous_engagement_dim_40_2026,
  title        = {Resonix Evidence Portal: Dual Simultaneous Engagement (dim=40)},
  author       = {{Resonix Labs (Canada) Inc.}},
  year         = {2026},
  howpublished = {\url{https://resonix.tech/evidence/problems/dual-simultaneous-engagement-dim-40}},
  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