SCPN-MIF-CORE merge-trigger quickstart¶
This notebook runs one public scpn_mif_core merge-trigger decision. It uses the same typed API as scripts and tests: a kinematic moving-frame model, a merge-window monitor, a kinematic safety certificate, and a capacitor-bank feasibility check.
The result is a software decision artifact, not a hardware firing command.
In [1]:
try:
import scpn_mif_core # noqa: F401
except ModuleNotFoundError:
%pip install scpn-mif-core[demo]
In [2]:
import matplotlib.pyplot as plt
import numpy as np
from scpn_mif_core import (
CapacitorBankSpec,
KinematicSafetySpec,
MergeWindowSpec,
MovingFrameUPDESpec,
PulseSpec,
evaluate_merge_trigger,
)
from scpn_mif_core.merge_trigger import MergeTriggerScenario
In [3]:
scenario = MergeTriggerScenario(
moving_frame=MovingFrameUPDESpec(
omega_rad_s=np.asarray([1.0, 1.0]),
coupling_rad_s=np.asarray([[0.0, 50.0], [50.0, 0.0]]),
doppler_strength_rad_s=0.0,
distance_scale_m=1.0,
),
initial_phases_rad=np.asarray([0.0, 0.004]),
initial_positions_m=np.asarray([-5.0e-4, 5.0e-4]),
velocities_m_s=np.asarray([0.0, 0.0]),
dt_s=1.0e-3,
steps=20,
merge_window=MergeWindowSpec(
phase_tolerance_rad=0.01,
spatial_tolerance_m=0.002,
consecutive_samples=3,
),
safety=KinematicSafetySpec(),
bank=CapacitorBankSpec(
capacitance_F=1.0e-3,
inductance_H=1.0e-6,
series_resistance_ohm=1.0e-3,
voltage_max_V=2.0e4,
recharge_power_kW=10.0,
),
bank_initial_voltage_V=2.0e4,
compression_pulse=PulseSpec(
peak_current_A=1.0e5,
duration_s=1.0e-5,
waveform="half_sine",
),
)
report = evaluate_merge_trigger(scenario)
report.outcome.value, report.reason
Out[3]:
('fire', 'locked and safe at t = 0.002 s with a feasible compression pulse')
In [4]:
summary = {
"outcome": report.outcome.value,
"reason": report.reason,
"lock_achieved": report.lock_achieved,
"first_lock_time_s": report.first_lock_time_s,
"safety_passed": report.safety_passed,
"bank_feasible": report.bank_feasible,
"min_separation_m": report.min_separation_m,
}
summary
Out[4]:
{'outcome': 'fire',
'reason': 'locked and safe at t = 0.002 s with a feasible compression pulse',
'lock_achieved': True,
'first_lock_time_s': 0.002,
'safety_passed': True,
'bank_feasible': True,
'min_separation_m': 0.001}
In [5]:
samples = report.merge_trace.samples
times = np.array([s.t_s for s in samples])
phase_err = np.array([s.phase_lock_error_rad for s in samples])
separation = np.array([s.separation_m for s in samples])
fig, ax = plt.subplots(2, 1, figsize=(8, 5), sharex=True)
ax[0].plot(times, phase_err, label="phase error")
ax[0].axhline(scenario.merge_window.phase_tolerance_rad, color="tab:red", linestyle="--", label="phase tolerance")
ax[0].set_ylabel("rad")
ax[0].legend(loc="best")
ax[1].plot(times, separation, label="separation")
ax[1].axhline(scenario.merge_window.spatial_tolerance_m, color="tab:red", linestyle="--", label="spatial tolerance")
ax[1].set_xlabel("time [s]")
ax[1].set_ylabel("m")
ax[1].legend(loc="best")
fig.tight_layout()