diff --git a/packages/extension/.gitignore b/packages/extension/.gitignore new file mode 100644 index 00000000..850676dd --- /dev/null +++ b/packages/extension/.gitignore @@ -0,0 +1 @@ +dev/pulseplot_harness/main.js diff --git a/packages/extension/.vscodeignore b/packages/extension/.vscodeignore index 87173b62..9b6d6739 100644 --- a/packages/extension/.vscodeignore +++ b/packages/extension/.vscodeignore @@ -5,6 +5,7 @@ src/** test/** scripts/** +dev/** node_modules/** esbuild.config.mjs tsconfig.json diff --git a/packages/extension/AGENTS.md b/packages/extension/AGENTS.md index 3c18e4cd..73093f03 100644 --- a/packages/extension/AGENTS.md +++ b/packages/extension/AGENTS.md @@ -65,7 +65,22 @@ script, running with cwd = the run dir, must emit: - `AMICODE_ITER iter= f= inf_pr=<…> inf_du=<…>` to stdout, flushed, once per Ipopt iteration (drives the live stats row). This stays on the raw Ipopt callback — it needs the rich IPM state the agnostic callback can't carry. -- `iter_.png` every few iterations (the live plot the Inspector shows). See +- `AMICODE_PULSE_META` (once, before the solve) and `AMICODE_PULSE` (once per + iteration) to stdout, flushed — **this is what the Inspector's live pulse + plot renders**. Prototype-grade line shapes (candidate GA format): + + ``` + AMICODE_PULSE_META drives= knots= labels="a_1","a_2" bounds=:,: + AMICODE_PULSE iter= dt=
a= + ``` + + Use the template's `PulseEmitCallback` idiom (see below): a small + `AbstractIntermediateCallback` that delegates to the PNG callback, syncs the + trajectory from the primal, and prints the lines. **A script that skips these + lines gets a dead live plot** — the Inspector sits on "warming up" until + completion, then shows a no-pulse-data hint. +- `iter_.png` every few iterations — **archival/publication artifact** + (`plot_pulse` is canonical there); the Inspector no longer displays PNGs. See the per-iter plotting idiom below — **`LivePulsePlotCallback`** once the bundled Julia project pins DirectTrajOpt ≥ 0.9.7, else the hand-rolled Ipopt-callback path (the only one that runs on 0.9.6). @@ -88,18 +103,26 @@ primal each iteration and writes `iter_.png` — the same object would instal on MadNLP via `MadNLPOptions(intermediate_callback = …)`: ```julia -live_plot = LivePulsePlotCallback(qtraj, prob.trajectory; every = 6, save_dir = ".") +live_plot = LivePulsePlotCallback(qtraj, prob.trajectory; every = 6, save_dir = ".") +pulse_emit = PulseEmitCallback(live_plot, prob.trajectory) # wraps live_plot; adds AMICODE_PULSE lines solve!(qcp; max_iter = max_iter, - options = IpoptOptions(intermediate_callback = live_plot), # → iter_.png + options = IpoptOptions(intermediate_callback = pulse_emit), # → iter_.png + AMICODE_PULSE callback = CB.callback_factory(cb_log)) # → AMICODE_ITER text ``` +`PulseEmitCallback` is defined in the template — copy it verbatim (it qualifies +`update!` against the Makie name collision and resolves the drive component +`:u`-then-`:a`). It delegates to the PNG callback first, so archival frames and +pulse telemetry ride one hook. + **Fallback on DirectTrajOpt 0.9.6 (no Ipopt `intermediate_callback` field yet): hand-roll the PNG from the raw Ipopt callback** — `IpoptOptions(intermediate_callback=…)` throws at construction on 0.9.6, so if you author a script against a project still pinned to 0.9.6, use the text-callback path instead: in `cb_log`, every few iters call `plot_pulse(qcp; bounds = true, title = …)` and `CairoMakie.save` the figure -(alongside `callback_update_trajectory_factory` to keep the iterate in sync). +(alongside `callback_update_trajectory_factory` to keep the iterate in sync) — +and still print the `AMICODE_PULSE_META`/`AMICODE_PULSE` lines from that same +callback (the synced trajectory has the drive values), or the live plot is dead. The bundled template uses the preferred `LivePulsePlotCallback` path; it lands together with the DirectTrajOpt ≥ 0.9.7 `Manifest.toml` bump (lockstep), so the diff --git a/packages/extension/demo/run/iter_0000.png b/packages/extension/demo/run/iter_0000.png deleted file mode 100644 index e23d51d3..00000000 Binary files a/packages/extension/demo/run/iter_0000.png and /dev/null differ diff --git a/packages/extension/demo/run/iter_0010.png b/packages/extension/demo/run/iter_0010.png deleted file mode 100644 index 078dab2e..00000000 Binary files a/packages/extension/demo/run/iter_0010.png and /dev/null differ diff --git a/packages/extension/demo/run/iter_0020.png b/packages/extension/demo/run/iter_0020.png deleted file mode 100644 index 9e023fd6..00000000 Binary files a/packages/extension/demo/run/iter_0020.png and /dev/null differ diff --git a/packages/extension/demo/run/iter_0030.png b/packages/extension/demo/run/iter_0030.png deleted file mode 100644 index e59b2452..00000000 Binary files a/packages/extension/demo/run/iter_0030.png and /dev/null differ diff --git a/packages/extension/demo/run/iter_0040.png b/packages/extension/demo/run/iter_0040.png deleted file mode 100644 index ecf3caa7..00000000 Binary files a/packages/extension/demo/run/iter_0040.png and /dev/null differ diff --git a/packages/extension/demo/run/iter_0050.png b/packages/extension/demo/run/iter_0050.png deleted file mode 100644 index 28b5145f..00000000 Binary files a/packages/extension/demo/run/iter_0050.png and /dev/null differ diff --git a/packages/extension/demo/run/iter_0060.png b/packages/extension/demo/run/iter_0060.png deleted file mode 100644 index 07718435..00000000 Binary files a/packages/extension/demo/run/iter_0060.png and /dev/null differ diff --git a/packages/extension/demo/run/result.toml b/packages/extension/demo/run/result.toml index 8fd04305..d7c71b6c 100644 --- a/packages/extension/demo/run/result.toml +++ b/packages/extension/demo/run/result.toml @@ -1,4 +1,12 @@ -schema_version = "1" iterations = 60 -fidelity = 0.9999788203047787 -wall_seconds = 101.5023238658905 +fidelity = 0.9998472204856121 +schema_version = "1" +wall_seconds = 109.01594400405884 + +[params] +levels = 3 +drive_max = 0.2 +T = 10.0 +max_iter = 60 +N = 50 +delta = 0.2 diff --git a/packages/extension/demo/run/run.log b/packages/extension/demo/run/run.log index 77f45541..6606c166 100644 --- a/packages/extension/demo/run/run.log +++ b/packages/extension/demo/run/run.log @@ -8,7 +8,7 @@ constructing SmoothPulseProblem [UnitaryTrajectory] │ NamedTrajectory(qtraj, N; Δt_bounds=(1e-3, 0.5)) │ │ Or use timesteps_all_equal=true in problem options to fix timesteps. -└ @ DirectTrajOpt.Problems ~/.julia/packages/DirectTrajOpt/TIf6x/src/problems.jl:66 +└ @ DirectTrajOpt.Problems ~/.julia/packages/DirectTrajOpt/VOKy2/src/problems.jl:66 QuantumControlProblem ├─ UnitaryTrajectory · ZeroOrderPulse · BilinearIntegrator, DerivativeIntegrator, DerivativeIntegrator │ @@ -27,16 +27,16 @@ QuantumControlProblem ├─ Goal │ EmbeddedOperator on [3], subspace dim 2 │ -├─ Objective total = 44.95 @ current x -│ KnotPointObjective w=1 44.25 -│ QuadraticRegularizer(:u) w=1 2.042e-04 -│ QuadraticRegularizer(:du) w=1 9.849e-03 -│ QuadraticRegularizer(:ddu) w=1 0.6964 +├─ Objective total = 80.06 @ current x +│ KnotPointObjective w=1 79.28 +│ QuadraticRegularizer(:u) w=1 2.657e-04 +│ QuadraticRegularizer(:du) w=1 0.01105 +│ QuadraticRegularizer(:ddu) w=1 0.7661 │ NullObjective w=1 0 │ ├─ Constraints 1/14 violated at x₀ -│ [dyn] BilinearIntegrator ✗ (‖c‖∞ = 0.09876) -│ [dyn] DerivativeIntegrator ✓ (‖c‖∞ = 2.776e-17) +│ [dyn] BilinearIntegrator ✗ (‖c‖∞ = 0.1629) +│ [dyn] DerivativeIntegrator ✓ (‖c‖∞ = 5.551e-17) │ [dyn] DerivativeIntegrator ✓ (‖c‖∞ = 2.220e-16) │ [ineq] AllEqualConstraint ✓ (no eval) │ [eq] EqualityConstraint ✓ (no eval) @@ -54,11 +54,13 @@ QuantumControlProblem variables: 1300 (1100 bounded) equality: 52827 inequality: 1 - F (raw) = 0.557531 + F (raw) = 0.207219 Hint: show_problem(qcp; detail=:full) for pulse plot + sparsity -AMICODE_ITER iter=0 f=4.427415e+01 inf_pr=2.715e+00 inf_du=4.401e+00 +AMICODE_PULSE_META drives=2 knots=50 labels="a_1","a_2" bounds=-0.2:0.2,-0.2:0.2 +AMICODE_ITER iter=0 f=7.930693e+01 inf_pr=3.306e+00 inf_du=4.344e-01 +AMICODE_PULSE iter=0 dt=0.204082 a=0.00122269,0.079988,0.0285582,0.0540801,0.159005,-0.163859,0.10975,0.0792598,0.225433,-0.0367432,0.16558,0.14032,0.0462271,0.108334,0.179825,-0.0977399,0.165655,0.246999,-0.0193538,-0.211037,-0.174111,-0.125857,-0.221001,0.215075,-0.0648649,-0.127936,0.127302,0.00942714,-0.00511523,0.0107351,0.149222,0.00920709,-0.024628,-0.0666657,-0.0586522,-0.157342,-0.028419,0.0810429,-0.150415,-0.037007,-0.00844921,-0.0615253,0.176932,-0.0139109,0.138256,0.0532826,0.0636576,-0.0740906,-0.00579776,-0.0402367;-0.0235979,-0.00903938,0.0218624,-0.0386896,0.246838,0.241264,0.0996578,0.17691,0.252771,-0.145362,0.0510349,0.0378336,0.0339014,-0.0113967,0.109632,0.0389645,-0.0589498,-0.0433134,-0.170006,-0.0632173,0.116623,-0.0892016,0.11167,0.168127,0.138323,0.0396419,-0.069816,0.0302567,0.0163749,-0.102325,-0.0864511,0.0495011,-0.0660819,0.107856,0.00929036,0.049109,0.045827,0.178588,-0.196605,-0.0222713,0.0668082,-0.074412,-0.0316369,0.0245491,-0.112651,-0.0198992,0.0551696,0.0887624,-0.010815,-0.177116 ****************************************************************************** This program contains Ipopt, a library for large-scale nonlinear optimization. @@ -66,64 +68,124 @@ This program contains Ipopt, a library for large-scale nonlinear optimization. For more information visit https://github.com/coin-or/Ipopt ****************************************************************************** -AMICODE_ITER iter=1 f=3.312001e+00 inf_pr=1.962e+00 inf_du=1.835e+02 -AMICODE_ITER iter=2 f=1.909965e+01 inf_pr=1.305e-01 inf_du=1.641e+02 -AMICODE_ITER iter=3 f=2.688086e+01 inf_pr=2.734e-03 inf_du=1.741e+02 -AMICODE_ITER iter=4 f=2.499717e+01 inf_pr=2.649e-04 inf_du=1.925e+02 -AMICODE_ITER iter=5 f=2.047457e+01 inf_pr=1.168e-03 inf_du=1.436e+00 -AMICODE_ITER iter=6 f=1.371681e+01 inf_pr=1.334e-03 inf_du=1.135e+00 -AMICODE_ITER iter=7 f=2.567280e+00 inf_pr=5.849e-03 inf_du=4.532e+00 -AMICODE_ITER iter=8 f=1.243844e+00 inf_pr=1.448e-02 inf_du=5.058e+03 -AMICODE_ITER iter=9 f=2.715532e+00 inf_pr=7.488e-03 inf_du=1.266e+05 -AMICODE_ITER iter=10 f=2.760619e+00 inf_pr=2.607e-04 inf_du=2.939e+04 -AMICODE_ITER iter=11 f=2.141433e+00 inf_pr=5.075e-05 inf_du=2.069e+00 -AMICODE_ITER iter=12 f=1.090665e+00 inf_pr=3.007e-04 inf_du=1.943e-01 -AMICODE_ITER iter=13 f=2.750855e-02 inf_pr=7.233e-04 inf_du=1.921e+02 -AMICODE_ITER iter=14 f=7.579673e-01 inf_pr=6.602e-04 inf_du=1.915e+02 -AMICODE_ITER iter=15 f=8.604789e-01 inf_pr=9.254e-07 inf_du=9.379e-01 -AMICODE_ITER iter=16 f=7.798056e-01 inf_pr=4.902e-06 inf_du=1.591e-01 -AMICODE_ITER iter=17 f=5.809716e-01 inf_pr=3.379e-05 inf_du=1.330e-01 -AMICODE_ITER iter=18 f=2.319975e-01 inf_pr=1.465e-04 inf_du=9.073e-02 -AMICODE_ITER iter=19 f=5.657439e-02 inf_pr=2.357e-04 inf_du=1.924e+02 -AMICODE_ITER iter=20 f=1.418744e-01 inf_pr=1.465e-04 inf_du=1.923e+02 -AMICODE_ITER iter=21 f=1.615507e-01 inf_pr=2.529e-07 inf_du=5.215e-01 -AMICODE_ITER iter=22 f=1.421320e-01 inf_pr=1.716e-06 inf_du=6.246e-02 -AMICODE_ITER iter=23 f=9.743153e-02 inf_pr=1.068e-05 inf_du=5.106e-02 -AMICODE_ITER iter=24 f=3.100677e-02 inf_pr=3.676e-05 inf_du=3.206e-02 -AMICODE_ITER iter=25 f=2.149879e-02 inf_pr=4.050e-05 inf_du=1.924e+02 -AMICODE_ITER iter=26 f=2.110705e-02 inf_pr=1.111e-05 inf_du=1.924e+02 -AMICODE_ITER iter=27 f=2.287865e-02 inf_pr=4.976e-08 inf_du=3.663e-01 -AMICODE_ITER iter=28 f=2.057141e-02 inf_pr=2.819e-07 inf_du=1.757e-02 -AMICODE_ITER iter=29 f=1.566875e-02 inf_pr=1.540e-06 inf_du=1.382e-02 -AMICODE_ITER iter=30 f=9.845458e-03 inf_pr=3.980e-06 inf_du=7.545e-03 -AMICODE_ITER iter=31 f=1.037462e-02 inf_pr=3.020e-06 inf_du=1.924e+02 -AMICODE_ITER iter=32 f=9.708787e-03 inf_pr=1.299e-06 inf_du=1.924e+02 -AMICODE_ITER iter=33 f=9.757572e-03 inf_pr=4.887e-08 inf_du=3.527e-01 -AMICODE_ITER iter=34 f=9.648300e-03 inf_pr=2.990e-08 inf_du=3.208e-03 -AMICODE_ITER iter=35 f=9.435164e-03 inf_pr=8.478e-08 inf_du=2.684e-03 -AMICODE_ITER iter=36 f=9.281332e-03 inf_pr=1.850e-07 inf_du=1.924e+02 -AMICODE_ITER iter=37 f=9.315456e-03 inf_pr=3.351e-07 inf_du=1.924e+02 -AMICODE_ITER iter=38 f=9.332486e-03 inf_pr=1.542e-10 inf_du=1.467e-02 -AMICODE_ITER iter=39 f=9.317899e-03 inf_pr=1.125e-09 inf_du=2.186e-03 -AMICODE_ITER iter=40 f=9.283247e-03 inf_pr=7.451e-09 inf_du=2.047e-03 -AMICODE_ITER iter=41 f=9.226014e-03 inf_pr=3.074e-08 inf_du=1.767e-03 -AMICODE_ITER iter=42 f=9.195011e-03 inf_pr=6.195e-08 inf_du=1.924e+02 -AMICODE_ITER iter=43 f=9.136038e-03 inf_pr=3.153e-07 inf_du=1.924e+02 -AMICODE_ITER iter=44 f=9.137616e-03 inf_pr=4.528e-11 inf_du=7.841e-03 -AMICODE_ITER iter=45 f=9.134479e-03 inf_pr=3.146e-10 inf_du=1.453e-03 -AMICODE_ITER iter=46 f=9.126828e-03 inf_pr=2.038e-09 inf_du=1.389e-03 -AMICODE_ITER iter=47 f=9.111456e-03 inf_pr=8.248e-09 inf_du=1.377e-03 -AMICODE_ITER iter=48 f=9.088690e-03 inf_pr=5.431e-08 inf_du=1.924e+02 -AMICODE_ITER iter=49 f=9.001065e-03 inf_pr=4.205e-07 inf_du=1.924e+02 -AMICODE_ITER iter=50 f=9.000330e-03 inf_pr=7.382e-10 inf_du=3.516e-02 -AMICODE_ITER iter=51 f=8.998946e-03 inf_pr=2.533e-10 inf_du=1.200e-03 -AMICODE_ITER iter=52 f=8.994278e-03 inf_pr=1.160e-09 inf_du=1.212e-03 -AMICODE_ITER iter=53 f=8.982299e-03 inf_pr=9.944e-09 inf_du=1.924e+02 -AMICODE_ITER iter=54 f=8.943076e-03 inf_pr=8.767e-08 inf_du=1.924e+02 -AMICODE_ITER iter=55 f=8.942956e-03 inf_pr=6.080e-12 inf_du=3.079e-03 -AMICODE_ITER iter=56 f=8.942226e-03 inf_pr=2.578e-11 inf_du=1.137e-03 -AMICODE_ITER iter=57 f=8.940084e-03 inf_pr=2.319e-10 inf_du=1.141e-03 -AMICODE_ITER iter=58 f=8.933903e-03 inf_pr=2.064e-09 inf_du=1.924e+02 -AMICODE_ITER iter=59 f=8.917972e-03 inf_pr=1.428e-08 inf_du=1.924e+02 -AMICODE_ITER iter=60 f=8.917966e-03 inf_pr=7.926e-13 inf_du=1.106e-03 -DONE fidelity=0.9999788203047787 +AMICODE_ITER iter=1 f=7.870835e+01 inf_pr=3.066e+00 inf_du=2.496e+01 +AMICODE_PULSE iter=1 dt=0.204342 a=0,0.0761166,0.0276556,0.0514594,0.150169,-0.14998,0.10623,0.0787023,0.189412,-0.0268279,0.162244,0.137975,0.0481891,0.105505,0.172624,-0.0861883,0.15878,0.184925,-0.0197106,-0.18673,-0.166997,-0.120434,-0.185,0.181488,-0.0606832,-0.118193,0.120735,0.0103281,-0.00356935,0.0108983,0.13992,0.00767649,-0.0248421,-0.0658597,-0.0584921,-0.151371,-0.0304566,0.0722408,-0.142792,-0.0351706,-0.00745924,-0.0568608,0.166312,-0.0110909,0.131465,0.0517108,0.0618363,-0.0674968,-0.00384559,0;0,-0.0100719,0.0204395,-0.0340945,0.188531,0.19159,0.102659,0.175481,0.192171,-0.128095,0.0533581,0.0391932,0.0354289,-0.008168,0.104286,0.0366967,-0.058238,-0.0454573,-0.165245,-0.0647955,0.106438,-0.0817666,0.109332,0.164113,0.136489,0.0428604,-0.0614412,0.0309017,0.0161749,-0.0970807,-0.083128,0.0444512,-0.0630193,0.100216,0.00861315,0.0470465,0.0435173,0.16767,-0.181332,-0.0193794,0.0623644,-0.0713675,-0.0322729,0.020989,-0.106532,-0.018146,0.0537219,0.0857821,-0.00737216,0 +AMICODE_ITER iter=2 f=7.917154e+01 inf_pr=2.975e+00 inf_du=5.202e+02 +AMICODE_PULSE iter=2 dt=0.204241 a=0,0.0791055,0.0442265,0.0648253,0.147838,-0.143974,0.0980544,0.0791879,0.185999,-0.0164367,0.161875,0.140137,0.0623614,0.11761,0.173877,-0.076436,0.153737,0.179583,-0.0103946,-0.172202,-0.159942,-0.121976,-0.176985,0.178588,-0.0477113,-0.102092,0.123824,0.0175393,-0.00422502,0.0156095,0.135959,0.0100699,-0.0289222,-0.06542,-0.0633202,-0.145431,-0.027588,0.0639173,-0.143568,-0.0461298,-0.0171852,-0.0542278,0.162907,-0.000686273,0.134554,0.0620511,0.0647976,-0.0589134,-0.00531136,0;0,-0.0261608,-0.0196273,-0.0624654,0.157679,0.175746,0.111856,0.181395,0.192533,-0.115622,0.0546441,0.0444968,0.0319146,-0.0086364,0.0921543,0.0266701,-0.0613397,-0.0531365,-0.159995,-0.0581861,0.105666,-0.0727535,0.103759,0.154104,0.130732,0.0501136,-0.0534941,0.0243196,0.00853912,-0.0912705,-0.0754518,0.0426623,-0.0558562,0.0998196,0.0179236,0.0519434,0.0540514,0.167934,-0.169412,-0.0209893,0.0586785,-0.0617277,-0.0237661,0.0186539,-0.103527,-0.0240552,0.0325665,0.0609075,-0.0228865,0 +AMICODE_ITER iter=3 f=7.646464e+01 inf_pr=2.066e+00 inf_du=1.426e+03 +AMICODE_PULSE iter=3 dt=0.209436 a=0,0.0736817,0.0567243,0.0717249,0.12943,-0.0875708,0.0853171,0.0731383,0.15711,0.0167269,0.152828,0.139845,0.0816204,0.115115,0.147891,-0.0416349,0.118384,0.134399,-0.00683545,-0.124142,-0.111535,-0.0835423,-0.124397,0.136078,-0.024191,-0.0593735,0.107152,0.0308191,0.013907,0.0200788,0.0982393,-9.8344e-05,-0.0329402,-0.0635557,-0.062152,-0.121239,-0.0342774,0.0437365,-0.0966342,-0.0217913,-6.05609e-06,-0.0293614,0.124391,0.00153856,0.0970847,0.0418851,0.0446099,-0.0446439,-0.00355776,0;0,-0.060146,-0.0635703,-0.0835959,0.0960676,0.130937,0.100583,0.163868,0.184195,-0.0315958,0.0892486,0.0721135,0.0400174,-0.0151984,0.0365448,-0.0284172,-0.086769,-0.0649839,-0.120866,-0.0313419,0.0906326,-0.0366267,0.0969407,0.134721,0.114412,0.048093,-0.0277545,0.0247706,0.00699846,-0.0729411,-0.0664062,0.017581,-0.0541042,0.0610523,0.0042906,0.0333807,0.0371049,0.122935,-0.118392,-0.00612161,0.0545985,-0.0311393,-0.00171806,0.031721,-0.0564691,-0.000385559,0.0430568,0.0622092,-0.00336103,0 +AMICODE_ITER iter=4 f=7.462925e+01 inf_pr=1.072e+00 inf_du=1.515e+03 +AMICODE_PULSE iter=4 dt=0.211854 a=0,0.065606,0.0676675,0.0762577,0.107091,-0.014684,0.0820203,0.0796041,0.136993,0.0665969,0.146998,0.138971,0.0990871,0.113912,0.128183,0.0162664,0.0963759,0.0908717,-0.00729364,-0.0848308,-0.0825327,-0.063754,-0.0822719,0.0693837,-0.0105912,-0.0200815,0.0796969,0.0383623,0.0281745,0.0246952,0.0604738,-0.00756729,-0.0352254,-0.0610543,-0.0608634,-0.0948477,-0.0413427,0.00968512,-0.060891,-0.00902393,0.00848211,-0.00788148,0.078832,0.0103343,0.0635142,0.0291601,0.0316549,-0.0209037,0.00277061,0;0,-0.0612944,-0.0887285,-0.089973,0.0350987,0.0920895,0.105475,0.159174,0.172877,0.0467667,0.101377,0.0738022,0.0393938,-0.0115223,0.00349965,-0.0465401,-0.0869345,-0.0728006,-0.0970192,-0.0327701,0.0552724,0.00265085,0.0915786,0.118346,0.103231,0.0539077,-0.00212544,0.0171028,-0.00576058,-0.064747,-0.0676044,-0.0192173,-0.0560838,0.0155116,-0.00979867,0.0166818,0.0218101,0.0743475,-0.0557491,0.00926007,0.0404602,-0.0127846,0.00221606,0.0225401,-0.0269466,0.00891658,0.0392409,0.0512267,0.0116282,0 +AMICODE_ITER iter=5 f=7.250157e+01 inf_pr=2.045e-02 inf_du=4.950e+02 +AMICODE_PULSE iter=5 dt=0.212693 a=0,0.0488633,0.0625377,0.0616338,0.0660173,0.0522664,0.0703269,0.0822755,0.113622,0.119123,0.136197,0.129908,0.107062,0.103848,0.103218,0.0809138,0.0750906,0.0424429,-0.01616,-0.0559192,-0.0660594,-0.0553572,-0.0453619,-0.0080069,0.00334182,0.0232614,0.0493308,0.0425576,0.0356377,0.026158,0.0191815,-0.0152925,-0.0381867,-0.0614444,-0.0633223,-0.0721108,-0.05459,-0.0338519,-0.027782,0.000759896,0.0158192,0.0153946,0.032334,0.0256211,0.0346484,0.021985,0.0240084,0.00861398,0.0123208,0;0,-0.0493104,-0.0663783,-0.0480689,0.0105885,0.0733004,0.110863,0.136133,0.135194,0.105747,0.0953211,0.0705287,0.0464304,0.0095166,-0.00888997,-0.0454576,-0.0756882,-0.0773474,-0.0743392,-0.0418512,0.00909331,0.0397067,0.0853401,0.103157,0.0937206,0.0610862,0.0251972,0.0101207,-0.0159748,-0.0510486,-0.0614865,-0.0482321,-0.0466974,-0.0223487,-0.0154714,0.00721131,0.00987345,0.0237093,0.0129858,0.0252218,0.0217986,0.000771351,-0.000585585,0.00564752,-0.0010386,0.0163531,0.0345426,0.0401511,0.0289986,0 +AMICODE_ITER iter=6 f=7.133053e+01 inf_pr=2.256e-03 inf_du=3.926e+02 +AMICODE_PULSE iter=6 dt=0.213466 a=0,0.0497551,0.065289,0.064033,0.0661472,0.0538144,0.0695576,0.0824755,0.113016,0.120651,0.136568,0.132177,0.113647,0.112827,0.112664,0.0912437,0.0793977,0.0415003,-0.0196615,-0.0608907,-0.0726192,-0.0617589,-0.0487722,-0.0120012,0.00279552,0.0239938,0.0486721,0.0435552,0.0372687,0.0280149,0.0200635,-0.012688,-0.0355569,-0.0590736,-0.0631286,-0.0729282,-0.058731,-0.0406124,-0.033435,-0.00562357,0.0110454,0.013915,0.031169,0.0275176,0.0360193,0.0246916,0.0259787,0.0113338,0.0131364,0;0,-0.0481732,-0.0638442,-0.0439465,0.0142222,0.0770059,0.11479,0.137087,0.133482,0.104293,0.0908207,0.0658587,0.04186,0.00567701,-0.0147974,-0.0512776,-0.0800802,-0.0806558,-0.0725891,-0.0367872,0.015453,0.0486678,0.0918165,0.107957,0.097797,0.0659529,0.0317704,0.0168578,-0.00729473,-0.0391319,-0.0487186,-0.0375109,-0.0376626,-0.0188519,-0.0155353,0.00242084,0.00301766,0.0148279,0.00704375,0.019259,0.0178748,0.00117161,0.00174954,0.00898313,0.00476421,0.0216908,0.038612,0.0427868,0.0304378,0 +AMICODE_ITER iter=7 f=5.690671e+01 inf_pr=1.427e-02 inf_du=6.696e+02 +AMICODE_PULSE iter=7 dt=0.214303 a=0,0.0515765,0.0689646,0.0671852,0.066968,0.0539198,0.0680475,0.0810089,0.110579,0.118334,0.133095,0.129055,0.112464,0.112943,0.114093,0.0946295,0.0821686,0.0440286,-0.016657,-0.0585581,-0.0718303,-0.0622324,-0.0484863,-0.0118307,0.0042244,0.0250819,0.047849,0.0425572,0.0359285,0.0267382,0.0191123,-0.0112531,-0.0323952,-0.0543596,-0.0585834,-0.0683573,-0.0556868,-0.0385522,-0.03052,-0.00253863,0.0153844,0.0200806,0.0371834,0.0338444,0.0408493,0.0290672,0.0291043,0.0144285,0.0146261,0;0,-0.0472371,-0.061709,-0.0407975,0.0167639,0.0790124,0.116391,0.13655,0.130903,0.101008,0.0863869,0.0621937,0.0395297,0.00522525,-0.0153286,-0.0513966,-0.0800198,-0.0812789,-0.0722038,-0.0357738,0.0172331,0.0523804,0.095095,0.110979,0.10073,0.0690742,0.0348656,0.0191415,-0.00404953,-0.0332533,-0.0412508,-0.0300261,-0.0300328,-0.0136197,-0.0127494,0.000980272,-0.00151829,0.00693708,-0.00164739,0.00965303,0.0101771,-0.0013133,0.00576851,0.0216915,0.0284564,0.0539937,0.0748342,0.07545,0.050373,0 +AMICODE_ITER iter=8 f=5.118594e+01 inf_pr=3.633e-03 inf_du=9.682e+00 +AMICODE_PULSE iter=8 dt=0.214473 a=0,0.0518753,0.0694583,0.0676127,0.0671993,0.0540579,0.0680543,0.0809158,0.11025,0.117803,0.132424,0.128559,0.11247,0.11356,0.115296,0.0962936,0.0839464,0.0457291,-0.0151978,-0.0575709,-0.0714638,-0.0624013,-0.0489022,-0.012367,0.00368094,0.024362,0.0467606,0.0411795,0.0343545,0.0251792,0.0177735,-0.0121462,-0.0328335,-0.0543338,-0.0580975,-0.067195,-0.0536148,-0.0351845,-0.0255272,0.00401037,0.02311,0.0281954,0.0445405,0.0394834,0.0440762,0.0299882,0.0284248,0.0133136,0.0139112,0;0,-0.0468884,-0.0611045,-0.0400795,0.0173403,0.0793227,0.116318,0.135932,0.129822,0.0997466,0.0851655,0.0612497,0.0388952,0.00484371,-0.0156201,-0.0515649,-0.0799826,-0.080998,-0.0715238,-0.0346952,0.0187325,0.0543317,0.0972527,0.11312,0.102601,0.0705254,0.0359084,0.0199079,-0.00326394,-0.0322327,-0.0400506,-0.0288682,-0.0291591,-0.0134127,-0.0134367,-0.00084883,-0.00452554,0.00274612,-0.00674656,0.00405405,0.00502893,-0.00456896,0.00608133,0.0273457,0.0406068,0.0721207,0.0961754,0.0951233,0.0625512,0 +AMICODE_ITER iter=9 f=1.457634e+01 inf_pr=3.383e-02 inf_du=8.623e+05 +AMICODE_PULSE iter=9 dt=0.214961 a=0,0.052762,0.0706695,0.0685512,0.0677027,0.0542769,0.0681194,0.0807622,0.10965,0.116683,0.13113,0.127786,0.112948,0.115712,0.119065,0.101198,0.0892518,0.0506549,-0.01142,-0.0555419,-0.0714057,-0.0639558,-0.0513304,-0.0150707,0.000822808,0.0210627,0.0427276,0.0364836,0.0294248,0.0206441,0.0141558,-0.0146327,-0.034237,-0.0547096,-0.0571406,-0.0639698,-0.0465768,-0.022489,-0.00577939,0.0309494,0.0555054,0.0623874,0.0755463,0.0623944,0.0556381,0.0301786,0.0208466,0.00380141,0.00780105,0;0,-0.0456678,-0.0591642,-0.038003,0.018961,0.0799763,0.115556,0.133572,0.126217,0.0956924,0.081541,0.0586139,0.0373482,0.00416132,-0.0156907,-0.0511202,-0.0789165,-0.0791744,-0.0688123,-0.0310377,0.0234198,0.0599695,0.103399,0.119016,0.107475,0.0740016,0.0381954,0.0215978,-0.00159836,-0.03037,-0.0382677,-0.0278818,-0.0299026,-0.0166936,-0.0197177,-0.0101575,-0.0165531,-0.0114399,-0.0222861,-0.0115361,-0.008398,-0.0124067,0.00826182,0.0441337,0.0747343,0.121966,0.153783,0.14711,0.0944434,0 +AMICODE_ITER iter=10 f=5.242379e+00 inf_pr=3.542e-02 inf_du=9.850e+06 +AMICODE_PULSE iter=10 dt=0.215302 a=0,0.053455,0.0720239,0.0694362,0.0674196,0.0534527,0.0664275,0.0788663,0.10726,0.114371,0.128662,0.125959,0.112547,0.116577,0.1211,0.104459,0.0925752,0.0540124,-0.00803308,-0.0530359,-0.0707619,-0.0652965,-0.0538278,-0.0187587,-0.00319255,0.0163514,0.0369319,0.0305823,0.0235527,0.0149459,0.00838448,-0.020265,-0.0405265,-0.0616727,-0.064289,-0.0688505,-0.0467595,-0.014649,0.012739,0.0598493,0.0926865,0.10347,0.114076,0.0938807,0.0769388,0.0430989,0.0293998,0.0117936,0.0135441,0;0,-0.0456321,-0.0587012,-0.0369009,0.0200102,0.0812119,0.117063,0.134478,0.126357,0.0954823,0.0807843,0.0580317,0.0371891,0.00467356,-0.0151436,-0.0502513,-0.0777087,-0.0781385,-0.0674703,-0.0297052,0.0248969,0.062473,0.106674,0.12362,0.113628,0.0815308,0.0462912,0.0287251,0.00349545,-0.028627,-0.0418436,-0.0379454,-0.0455596,-0.0365166,-0.0405419,-0.0296151,-0.0324138,-0.0236729,-0.031378,-0.0195804,-0.0158528,-0.0176551,0.0081475,0.0536271,0.0965409,0.152106,0.181727,0.165143,0.104212,0 +AMICODE_ITER iter=11 f=1.370919e+00 inf_pr=3.418e-02 inf_du=5.196e+08 +AMICODE_PULSE iter=11 dt=0.218072 a=0,0.0566923,0.0807724,0.0779654,0.0690894,0.0543277,0.0609748,0.0724016,0.0952763,0.103499,0.115321,0.116644,0.111994,0.118098,0.121365,0.106486,0.0882641,0.0497457,-0.00440766,-0.0449192,-0.0636889,-0.0619438,-0.0489095,-0.0162988,0.00580973,0.0282888,0.0457347,0.0412222,0.030953,0.0166456,0.00259667,-0.0257316,-0.0488996,-0.0730548,-0.0849579,-0.0948273,-0.0827709,-0.0546459,-0.0195901,0.0337012,0.0788408,0.105956,0.123635,0.115606,0.102533,0.0746497,0.0556751,0.0308534,0.0189494,0;0,-0.0405996,-0.0487231,-0.0241173,0.0302968,0.0871875,0.120697,0.131333,0.119187,0.0903125,0.0732348,0.0534161,0.0352445,0.00751267,-0.0147912,-0.0474953,-0.0722222,-0.0752575,-0.0643951,-0.0319043,0.0145387,0.0507608,0.0894634,0.107921,0.105728,0.0860666,0.0622706,0.0501886,0.0347842,0.015448,0.00629897,0.00345989,-0.0130703,-0.0236476,-0.0438563,-0.0517213,-0.0627713,-0.0597002,-0.0611025,-0.0454493,-0.0318896,-0.0218295,0.00473094,0.0416629,0.0762485,0.121491,0.154959,0.145525,0.0928445,0 +AMICODE_ITER iter=12 f=2.330079e+01 inf_pr=4.676e-02 inf_du=2.002e+08 +AMICODE_PULSE iter=12 dt=0.220176 a=0,0.0582926,0.0835931,0.08054,0.0671173,0.0508696,0.0546847,0.0671747,0.089831,0.100768,0.112054,0.114137,0.111023,0.115121,0.115943,0.101007,0.080418,0.0424744,-0.00710039,-0.044184,-0.0611829,-0.0588231,-0.0445967,-0.01362,0.00999536,0.0324134,0.048798,0.0458831,0.0358787,0.0205577,0.00405944,-0.0229099,-0.0447498,-0.0647289,-0.0734065,-0.0779941,-0.063873,-0.0373887,-0.00740944,0.035357,0.0705177,0.0906516,0.102145,0.0909877,0.077393,0.0537296,0.0380222,0.0198864,0.0123277,0;0,-0.0397346,-0.0469511,-0.0215153,0.0324292,0.0887121,0.122656,0.133232,0.121383,0.093749,0.073924,0.053214,0.0347014,0.00922667,-0.0134661,-0.0441695,-0.0680127,-0.0725835,-0.0629386,-0.0333924,0.00896317,0.0444934,0.0799782,0.0975669,0.0963905,0.0788972,0.0551278,0.0384825,0.0198103,0.000250128,-0.00951295,-0.0118275,-0.0221656,-0.0270786,-0.0393937,-0.0444906,-0.0548593,-0.0564461,-0.0609033,-0.0499495,-0.0338594,-0.0125207,0.0254612,0.071256,0.10556,0.125297,0.0976137,0.0668618,0.0520675,0 +AMICODE_ITER iter=13 f=1.617308e+01 inf_pr=4.135e-03 inf_du=8.212e+01 +AMICODE_PULSE iter=13 dt=0.221011 a=0,0.0594531,0.08531,0.0818217,0.0668249,0.0498816,0.0528438,0.0653544,0.0875466,0.0986535,0.109646,0.112255,0.110388,0.115143,0.11635,0.102047,0.0809719,0.0429272,-0.00618739,-0.0434645,-0.0612965,-0.0598856,-0.0458409,-0.0153073,0.00894288,0.0315459,0.047542,0.0449609,0.0351018,0.0199909,0.00372825,-0.0219587,-0.0427868,-0.061695,-0.0700806,-0.0743796,-0.0611229,-0.0358683,-0.00653894,0.0351714,0.0702986,0.0915334,0.1042,0.0953681,0.0833019,0.0614204,0.0462457,0.027766,0.0170611,0;0,-0.03823,-0.0442864,-0.018176,0.0353362,0.0903917,0.12302,0.132027,0.119048,0.0913223,0.0713917,0.0515816,0.0341153,0.00987854,-0.0126245,-0.042826,-0.0663476,-0.0712388,-0.0616865,-0.0327278,0.00882095,0.0443543,0.079212,0.096694,0.0958239,0.0789503,0.0556218,0.0386868,0.0202647,0.00150316,-0.00806406,-0.0108286,-0.0210658,-0.0265701,-0.0389122,-0.0446244,-0.0549163,-0.0568401,-0.0608343,-0.0501529,-0.03381,-0.0113963,0.0274758,0.0747485,0.11199,0.134355,0.11515,0.0858105,0.061926,0 +AMICODE_ITER iter=14 f=6.176326e+00 inf_pr=4.669e-03 inf_du=2.996e+06 +AMICODE_PULSE iter=14 dt=0.221878 a=0,0.061219,0.0878308,0.0837067,0.0664863,0.0481118,0.0494807,0.0612824,0.0822904,0.0931712,0.10397,0.107847,0.108517,0.11546,0.118579,0.106263,0.0856527,0.048088,-0.000495273,-0.038626,-0.0586328,-0.0599192,-0.0478074,-0.0192037,0.00470582,0.0268717,0.0421764,0.0400895,0.0310003,0.0170887,0.00206953,-0.0216803,-0.0416261,-0.0603112,-0.0694545,-0.0737506,-0.060272,-0.0331733,0.000245266,0.0457899,0.0842066,0.106982,0.117741,0.106099,0.0898689,0.0655878,0.0492123,0.0310788,0.0192054,0;0,-0.0345628,-0.0380032,-0.0106139,0.0420289,0.094464,0.124074,0.129929,0.114718,0.0864998,0.0666091,0.0482653,0.0325532,0.0102064,-0.0116713,-0.0410117,-0.0638436,-0.06873,-0.0589857,-0.0304708,0.0104027,0.0463117,0.0811489,0.0993134,0.0995247,0.0838791,0.0613291,0.0440369,0.0252989,0.0060834,-0.00529789,-0.0110295,-0.0235327,-0.0311895,-0.0436497,-0.0485431,-0.0562202,-0.0559968,-0.0582398,-0.0485245,-0.0346494,-0.014888,0.0216565,0.068923,0.109409,0.136004,0.123147,0.0954861,0.0660756,0 +AMICODE_ITER iter=15 f=8.097976e-01 inf_pr=8.202e-03 inf_du=1.486e+02 +AMICODE_PULSE iter=15 dt=0.223129 a=0,0.0634059,0.0908237,0.085728,0.0651959,0.0444503,0.0430606,0.0531309,0.0720182,0.0828811,0.0945128,0.101569,0.106869,0.117273,0.122879,0.11304,0.0932385,0.057063,0.0102359,-0.0284672,-0.0517951,-0.058233,-0.0510376,-0.0272402,-0.00489014,0.0169055,0.0324118,0.0323259,0.0252813,0.0134092,-7.72415e-05,-0.0216874,-0.0411144,-0.0601796,-0.0709513,-0.0756899,-0.062312,-0.0333217,0.00457881,0.0535171,0.0939238,0.116556,0.124114,0.110572,0.0937807,0.0732437,0.0615915,0.0465425,0.0299368,0;0,-0.0262386,-0.0240317,0.00556076,0.0555587,0.101432,0.124164,0.124135,0.105724,0.0777809,0.0590502,0.0433678,0.0301555,0.0102812,-0.0106642,-0.038268,-0.0592811,-0.0634324,-0.0534205,-0.0263301,0.0121933,0.0473856,0.0819734,0.102083,0.105689,0.0938121,0.0736951,0.0559749,0.0355809,0.0136401,-0.00261941,-0.0144489,-0.0311434,-0.0415882,-0.0527058,-0.054084,-0.0554345,-0.0499409,-0.0484609,-0.0402027,-0.0313126,-0.0177002,0.0131086,0.0584022,0.10233,0.134017,0.129947,0.104907,0.0692876,0 +AMICODE_ITER iter=16 f=6.905131e+00 inf_pr=4.255e-03 inf_du=1.932e+06 +AMICODE_PULSE iter=16 dt=0.22464 a=0,0.0662439,0.095633,0.090991,0.0672917,0.0429794,0.0370013,0.0435217,0.0589801,0.0694921,0.0819223,0.0919653,0.101022,0.113068,0.118942,0.109741,0.0898144,0.0555208,0.0128604,-0.0221402,-0.042936,-0.0477836,-0.0390738,-0.0153043,0.00802847,0.0297378,0.0440302,0.043379,0.0344155,0.0192899,0.00145855,-0.0224772,-0.042242,-0.0573552,-0.0616665,-0.0592346,-0.0439482,-0.0203852,0.005742,0.0393094,0.0697971,0.0921022,0.108171,0.10864,0.101201,0.0813668,0.0614457,0.0389664,0.0219782,0;0,-0.016665,-0.00800107,0.0242242,0.071566,0.111112,0.127638,0.122264,0.100292,0.070765,0.0496861,0.0324289,0.0177401,-0.00228947,-0.0231812,-0.0480215,-0.0649147,-0.0654412,-0.0520024,-0.0235428,0.014067,0.0478098,0.0788126,0.0960545,0.0978721,0.0852063,0.0643873,0.0451201,0.025136,0.00678414,-0.00347299,-0.00682431,-0.0123445,-0.014744,-0.0231849,-0.0309428,-0.0445325,-0.0537852,-0.060966,-0.0526036,-0.032487,-0.00303031,0.0360661,0.0753964,0.101473,0.115248,0.101478,0.077895,0.0525207,0 +AMICODE_ITER iter=17 f=6.251329e+00 inf_pr=4.251e-05 inf_du=5.103e+00 +AMICODE_PULSE iter=17 dt=0.224656 a=0,0.066416,0.0958974,0.0912486,0.067442,0.0429766,0.0368365,0.0432706,0.0587521,0.0694179,0.0820511,0.0922762,0.101421,0.113426,0.119192,0.109925,0.0900257,0.0558708,0.0133733,-0.0215782,-0.0425055,-0.0476387,-0.0392702,-0.0158085,0.00734678,0.0290028,0.0433419,0.0428225,0.0340291,0.0190915,0.00144436,-0.022316,-0.041966,-0.0570465,-0.0614324,-0.0591426,-0.0440141,-0.0205178,0.00571029,0.0395176,0.0703209,0.0928983,0.109104,0.109592,0.102108,0.0822747,0.0623839,0.0398618,0.0225663,0;0,-0.0164901,-0.00777229,0.0243827,0.071569,0.110957,0.127413,0.122073,0.100211,0.0708026,0.0497681,0.0324852,0.0177338,-0.0023116,-0.0231245,-0.0477649,-0.0644293,-0.0648364,-0.0514651,-0.0232704,0.0139668,0.0473684,0.0781344,0.0953001,0.0971786,0.0846619,0.0640273,0.0449403,0.0251376,0.00695149,-0.00319874,-0.00653,-0.0121141,-0.0146754,-0.0233118,-0.0312508,-0.044924,-0.0541559,-0.0612155,-0.0527135,-0.0324628,-0.00288698,0.0363311,0.0758554,0.102238,0.11635,0.102801,0.0791024,0.0532124,0 +AMICODE_ITER iter=18 f=5.583934e+00 inf_pr=8.719e-05 inf_du=2.245e+03 +AMICODE_PULSE iter=18 dt=0.224647 a=0,0.0663915,0.0958543,0.0912033,0.0674065,0.0429417,0.0367649,0.0431288,0.0585462,0.0692124,0.0819399,0.0923436,0.101704,0.113902,0.119807,0.110627,0.0907822,0.0566614,0.0141484,-0.0209223,-0.0421025,-0.0475956,-0.0396134,-0.0164726,0.00649397,0.0281065,0.0425268,0.0421795,0.0336042,0.0188798,0.00138232,-0.0223431,-0.0421023,-0.0574136,-0.0620727,-0.0599738,-0.044835,-0.0210661,0.00565066,0.0400056,0.0712191,0.0939328,0.110011,0.110288,0.102763,0.083214,0.063795,0.0415001,0.0237423,0;0,-0.0162648,-0.0074406,0.0246798,0.071713,0.110884,0.127131,0.121659,0.099779,0.0704563,0.0495536,0.0323892,0.0177063,-0.00231242,-0.0231002,-0.0476709,-0.064216,-0.0644975,-0.0510503,-0.0228541,0.0143438,0.0477333,0.0785625,0.0958706,0.0979264,0.0855606,0.064998,0.0458707,0.025902,0.00742842,-0.00310114,-0.00683802,-0.0127571,-0.0154841,-0.0240469,-0.0316827,-0.0449228,-0.0537659,-0.0606529,-0.0522897,-0.032431,-0.00327855,0.0357886,0.0756462,0.102784,0.117719,0.104635,0.0807807,0.0541725,0 +AMICODE_ITER iter=19 f=4.073283e+00 inf_pr=3.346e-04 inf_du=4.667e-01 +AMICODE_PULSE iter=19 dt=0.224621 a=0,0.0663237,0.0957338,0.091074,0.0673029,0.0428362,0.0365607,0.0427389,0.0579936,0.0686698,0.0816586,0.0925413,0.102471,0.115178,0.121443,0.112484,0.0927872,0.0587684,0.0162322,-0.0191335,-0.0409694,-0.0474142,-0.0404663,-0.0181862,0.00427511,0.0257803,0.0404331,0.0405571,0.032569,0.0184035,0.00127273,-0.0224256,-0.0425712,-0.0586084,-0.0640882,-0.0625367,-0.0473242,-0.0227027,0.00548128,0.0414411,0.0738082,0.0968223,0.11238,0.111816,0.103855,0.0847469,0.0663598,0.0446748,0.0260814,0;0,-0.0156934,-0.00659999,0.0254305,0.0720736,0.110689,0.126402,0.120594,0.0986772,0.069581,0.049024,0.0321635,0.0176484,-0.00231473,-0.0230455,-0.047437,-0.0636657,-0.0636102,-0.0499634,-0.0217705,0.0153134,0.0486639,0.0796722,0.0973903,0.0999655,0.0880555,0.067737,0.0485443,0.0281511,0.00889235,-0.00270886,-0.00762516,-0.014556,-0.0178263,-0.0262489,-0.0330684,-0.0450963,-0.0528195,-0.0591734,-0.0511426,-0.0323538,-0.00444448,0.0339859,0.0743424,0.102954,0.119509,0.107267,0.0830936,0.0553711,0 +AMICODE_ITER iter=20 f=1.427347e+00 inf_pr=1.019e-03 inf_du=7.877e-01 +AMICODE_PULSE iter=20 dt=0.224574 a=0,0.0662282,0.0955194,0.0907882,0.0670073,0.0424929,0.036005,0.0418263,0.0567935,0.0675391,0.0810977,0.0929699,0.104041,0.117739,0.124682,0.116142,0.0967743,0.0630731,0.0206659,-0.01511,-0.0381496,-0.0465193,-0.0417902,-0.0214417,-0.000165995,0.0210482,0.0361916,0.0373454,0.0306259,0.0176265,0.00118253,-0.0226413,-0.0438754,-0.0617561,-0.0692194,-0.0689022,-0.053375,-0.0265936,0.00510993,0.0447595,0.0795979,0.102981,0.116968,0.114127,0.104852,0.0864236,0.0700994,0.049796,0.0299599,0;0,-0.0145599,-0.00494678,0.0268924,0.0727603,0.110287,0.124965,0.118535,0.0965989,0.0680056,0.0481689,0.0319096,0.0176835,-0.00224118,-0.0229238,-0.0469826,-0.0625553,-0.0617795,-0.0477096,-0.019556,0.0172156,0.0504073,0.0817787,0.100466,0.104365,0.0937073,0.0741675,0.0549965,0.0337152,0.012638,-0.00154006,-0.00935748,-0.0188337,-0.0235433,-0.0317653,-0.0367628,-0.046026,-0.0511909,-0.0563181,-0.0489971,-0.0325529,-0.00738834,0.0295385,0.0706431,0.101773,0.12089,0.109652,0.0847143,0.0557306,0 +AMICODE_ITER iter=21 f=5.638537e-01 inf_pr=1.866e-03 inf_du=1.449e+02 +AMICODE_PULSE iter=21 dt=0.224546 a=0,0.0662234,0.0952904,0.0902744,0.0663136,0.0417117,0.0350903,0.0406869,0.055487,0.0663809,0.0805475,0.0934233,0.105639,0.120322,0.127938,0.119841,0.100893,0.067687,0.0256553,-0.0102822,-0.0343757,-0.0447127,-0.0424575,-0.0244199,-0.00467449,0.0160612,0.0316921,0.0340091,0.0287469,0.0170613,0.00128151,-0.0230091,-0.0460842,-0.0668885,-0.0774172,-0.078932,-0.0628128,-0.0326879,0.00421716,0.0490583,0.0870715,0.11051,0.121951,0.11594,0.105208,0.0881975,0.0749878,0.0565646,0.0349944,0;0,-0.0134041,-0.00329511,0.0283389,0.0734436,0.109896,0.123543,0.116495,0.0945783,0.0665765,0.0475833,0.0320432,0.0181743,-0.00171274,-0.0223995,-0.0461864,-0.0611431,-0.0596722,-0.0452201,-0.0171931,0.0191407,0.0520816,0.0838781,0.103858,0.109667,0.100991,0.0829154,0.0642281,0.0421529,0.0188811,0.00125351,-0.0107161,-0.0240603,-0.0311763,-0.0394631,-0.0421768,-0.0478479,-0.0498063,-0.0536452,-0.0476312,-0.0344842,-0.0127931,0.0226601,0.065318,0.0999104,0.121884,0.111092,0.0846547,0.0545822,0 +AMICODE_ITER iter=22 f=1.400706e-01 inf_pr=1.416e-03 inf_du=7.204e+01 +AMICODE_PULSE iter=22 dt=0.224401 a=0,0.0661437,0.0954186,0.0907371,0.0669617,0.042206,0.03507,0.0399984,0.0542876,0.0651257,0.0797695,0.093523,0.106698,0.122071,0.129933,0.121727,0.102598,0.0694354,0.0277331,-0.00780789,-0.0317477,-0.0423903,-0.0409049,-0.0238963,-0.00506619,0.0152054,0.0310457,0.0342742,0.0303912,0.0201418,0.00544657,-0.0182567,-0.0410737,-0.0616268,-0.0718852,-0.0735957,-0.0588705,-0.0315778,0.00173306,0.043573,0.0805454,0.105406,0.119875,0.116724,0.107103,0.0891207,0.0738731,0.0540788,0.0329635,0;0,-0.0126974,-0.00217632,0.0294306,0.074093,0.109881,0.122895,0.11546,0.0934939,0.0656965,0.0469137,0.0313482,0.0170841,-0.00346013,-0.0247559,-0.04871,-0.0632137,-0.0608342,-0.0454375,-0.016882,0.0193277,0.0515598,0.0823529,0.101452,0.106792,0.0980943,0.0802533,0.0618235,0.0399903,0.017183,0.000665815,-0.00925208,-0.0198661,-0.0245921,-0.0320917,-0.0362842,-0.0450824,-0.0502608,-0.0557858,-0.0490952,-0.033233,-0.00837656,0.0285264,0.0698441,0.101353,0.120664,0.109142,0.0836562,0.0546408,0 +AMICODE_ITER iter=23 f=2.580582e-01 inf_pr=2.387e-04 inf_du=9.009e+00 +AMICODE_PULSE iter=23 dt=0.224292 a=0,0.0661326,0.0953646,0.0906378,0.0668488,0.0421104,0.0349875,0.0399105,0.0541817,0.0650285,0.0797259,0.0935895,0.106914,0.122438,0.13043,0.122326,0.103263,0.0701345,0.0284157,-0.00722505,-0.0313569,-0.042259,-0.0410454,-0.0242653,-0.0055697,0.0146627,0.0305407,0.0338608,0.0300877,0.0199326,0.00526937,-0.0185159,-0.0415857,-0.0625591,-0.0733193,-0.0754269,-0.0607943,-0.033164,0.000871147,0.0436041,0.0813841,0.106811,0.121646,0.118886,0.10989,0.0927535,0.0781478,0.0581457,0.0355248,0;0,-0.0125437,-0.00195299,0.0296427,0.0742232,0.109886,0.12277,0.115246,0.0932673,0.0655404,0.0468758,0.031442,0.0172855,-0.00318668,-0.0244448,-0.0483713,-0.0628552,-0.0604762,-0.0451101,-0.0166137,0.0195424,0.0517802,0.0826646,0.101945,0.107521,0.0990624,0.0814139,0.0630887,0.0412447,0.0182781,0.00142809,-0.00897327,-0.0201247,-0.0253016,-0.03301,-0.0371185,-0.0456201,-0.0504987,-0.0559219,-0.0494355,-0.0339949,-0.0095168,0.0273611,0.0691493,0.101429,0.12137,0.110019,0.0842433,0.0548146,0 +AMICODE_ITER iter=24 f=5.054476e-02 inf_pr=9.777e-05 inf_du=8.455e-02 +AMICODE_PULSE iter=24 dt=0.224281 a=0,0.0660859,0.0952285,0.0904157,0.0665814,0.0418219,0.0346515,0.0394893,0.0536943,0.0645866,0.0794909,0.0936972,0.107406,0.123248,0.131435,0.123432,0.104455,0.0714529,0.0298565,-0.00578989,-0.030171,-0.0415846,-0.0410322,-0.0248653,-0.0065472,0.0136222,0.0297096,0.033395,0.0300032,0.0201177,0.00550298,-0.0185301,-0.0421555,-0.0638879,-0.0753791,-0.0778827,-0.063064,-0.0346204,0.000617828,0.0444816,0.0828379,0.108029,0.121948,0.118078,0.108374,0.0912797,0.0772947,0.0579519,0.0356048,0;0,-0.0121946,-0.00144364,0.0301055,0.0744654,0.109802,0.122367,0.11465,0.0926811,0.0651471,0.0467514,0.0315353,0.0174635,-0.0030436,-0.0243603,-0.0482536,-0.0625769,-0.0599876,-0.0444902,-0.0160166,0.0200102,0.052159,0.0831448,0.102782,0.108901,0.101005,0.083763,0.0655604,0.0435008,0.0199867,0.00232575,-0.00900569,-0.0209895,-0.0266652,-0.0343736,-0.038006,-0.045792,-0.0500849,-0.0553696,-0.0493045,-0.0346645,-0.0109408,0.0256839,0.0678752,0.100883,0.121285,0.109804,0.083582,0.0540841,0 +AMICODE_ITER iter=25 f=5.034834e-02 inf_pr=1.181e-04 inf_du=1.460e+02 +AMICODE_PULSE iter=25 dt=0.224275 a=0,0.0660497,0.0950822,0.0901483,0.0662471,0.041472,0.0342794,0.0390648,0.053229,0.0641815,0.0792927,0.0938342,0.107919,0.124069,0.132447,0.124546,0.105657,0.0727816,0.0313057,-0.00435173,-0.0289876,-0.0409159,-0.041026,-0.0254752,-0.00753406,0.012575,0.0288749,0.0329277,0.0299151,0.0202933,0.00571667,-0.0185818,-0.0427974,-0.065339,-0.0776129,-0.0805362,-0.065509,-0.0361915,0.000310181,0.0453146,0.0841943,0.109085,0.122118,0.117391,0.107499,0.0911279,0.0783109,0.0596793,0.0369367,0;0,-0.0118265,-0.000916105,0.0305762,0.074701,0.109697,0.12193,0.114012,0.0920572,0.0647342,0.046636,0.0316755,0.0177282,-0.00277769,-0.0241298,-0.0479826,-0.0621579,-0.059389,-0.0437984,-0.0153833,0.0204898,0.0525423,0.0836375,0.103657,0.110356,0.103069,0.0862746,0.068224,0.0459628,0.021896,0.00339501,-0.00891598,-0.0217849,-0.0279948,-0.035702,-0.0388306,-0.0458826,-0.0496307,-0.0548961,-0.0494269,-0.0357406,-0.0128086,0.0236966,0.0665565,0.100557,0.121533,0.109843,0.0829901,0.053302,0 +AMICODE_ITER iter=26 f=4.379572e-02 inf_pr=5.499e-05 inf_du=1.459e+02 +AMICODE_PULSE iter=26 dt=0.224251 a=0,0.0660426,0.0951388,0.0902888,0.0664298,0.041633,0.0343545,0.0390395,0.053119,0.0640409,0.0791632,0.0937566,0.10791,0.124109,0.1325,0.124577,0.105633,0.0727129,0.0312429,-0.00435484,-0.0288815,-0.0406756,-0.0406684,-0.0250784,-0.00718464,0.0127972,0.0289584,0.0329593,0.0300274,0.0206171,0.00632967,-0.0176479,-0.0415611,-0.0638387,-0.0759594,-0.0789543,-0.0643562,-0.0358013,-0.000168775,0.0441869,0.082933,0.108244,0.121974,0.117802,0.107964,0.0911205,0.077603,0.0585712,0.0360859,0;0,-0.0117992,-0.00086007,0.0306552,0.0747857,0.109785,0.122021,0.114091,0.0920991,0.0647158,0.0465223,0.0314532,0.0173887,-0.00322718,-0.0246795,-0.0485835,-0.0627406,-0.0598736,-0.0441139,-0.0155223,0.0204591,0.0524965,0.0834099,0.103126,0.109482,0.101908,0.0849556,0.0668954,0.0447864,0.0210271,0.00297653,-0.00876034,-0.0210072,-0.0267464,-0.0343281,-0.0377774,-0.0454291,-0.0497364,-0.05519,-0.0494412,-0.0351521,-0.0116692,0.0249363,0.0673624,0.100723,0.121295,0.109699,0.0832031,0.0536434,0 +AMICODE_ITER iter=27 f=4.951811e-02 inf_pr=2.316e-07 inf_du=5.834e-01 +AMICODE_PULSE iter=27 dt=0.22425 a=0,0.0660418,0.0951345,0.0902806,0.0664197,0.0416233,0.0343454,0.0390299,0.0531082,0.0640307,0.0791571,0.0937584,0.107922,0.124131,0.13253,0.124613,0.105672,0.0727549,0.0312846,-0.0043185,-0.028857,-0.0406684,-0.04068,-0.0251054,-0.00721974,0.0127617,0.0289283,0.032938,0.0300155,0.0206131,0.00632969,-0.0176505,-0.0415748,-0.0638712,-0.0760132,-0.0790231,-0.0644258,-0.0358543,-0.000193712,0.044189,0.0829488,0.108256,0.121974,0.117799,0.107986,0.0911967,0.0777368,0.0587246,0.0361915,0;0,-0.0117893,-0.000845678,0.0306687,0.0747936,0.109784,0.12201,0.114073,0.0920799,0.064701,0.0465156,0.0314551,0.0173969,-0.00321562,-0.0246667,-0.0485694,-0.0627243,-0.0598553,-0.0440957,-0.0155068,0.0204712,0.0525084,0.0834274,0.103155,0.109526,0.101967,0.0850242,0.0669668,0.0448528,0.0210804,0.00300925,-0.0087529,-0.0210242,-0.0267797,-0.0343636,-0.0378002,-0.0454307,-0.0497204,-0.0551704,-0.0494358,-0.0351715,-0.0117082,0.0248989,0.067352,0.10075,0.121344,0.109741,0.0832183,0.0536381,0 +AMICODE_ITER iter=28 f=4.134160e-02 inf_pr=6.954e-07 inf_du=5.144e-02 +AMICODE_PULSE iter=28 dt=0.224249 a=0,0.0660381,0.0951222,0.0902594,0.0663942,0.0415969,0.0343172,0.0389969,0.0530709,0.0639971,0.0791394,0.0937677,0.107963,0.124198,0.132614,0.124708,0.105775,0.0728669,0.0314044,-0.0042023,-0.028764,-0.0406187,-0.0406841,-0.0251583,-0.00730227,0.0126743,0.0288572,0.0328952,0.0300029,0.0206231,0.00634629,-0.0176495,-0.041614,-0.0639678,-0.0761673,-0.0792104,-0.064602,-0.0359716,-0.000223302,0.044239,0.0830347,0.108321,0.121979,0.117757,0.107958,0.0912555,0.0779098,0.0589537,0.0363571,0;0,-0.01176,-0.000803106,0.0307073,0.0748139,0.109777,0.121976,0.114022,0.0920283,0.0646653,0.0465033,0.0314636,0.0174164,-0.00319491,-0.0246479,-0.0485471,-0.0626905,-0.0598077,-0.044041,-0.0154565,0.0205103,0.0525411,0.0834701,0.103228,0.109645,0.102132,0.0852217,0.0671747,0.0450442,0.0212288,0.00309325,-0.00874362,-0.0210822,-0.0268782,-0.0344616,-0.0378582,-0.0454295,-0.0496727,-0.0551161,-0.0494233,-0.035231,-0.0118297,0.024766,0.0672688,0.100742,0.12138,0.109757,0.0831803,0.0535803,0 +AMICODE_ITER iter=29 f=2.405819e-02 inf_pr=3.848e-06 inf_du=3.652e-02 +AMICODE_PULSE iter=29 dt=0.224247 a=0,0.0660294,0.0950924,0.0902078,0.0663314,0.0415323,0.0342487,0.0389177,0.0529822,0.0639177,0.0790978,0.0937896,0.108057,0.124355,0.132812,0.124928,0.106014,0.073129,0.0316853,-0.00392909,-0.0285444,-0.0404998,-0.0406908,-0.0252792,-0.00749168,0.0124747,0.0286967,0.0328019,0.0299799,0.0206513,0.00638698,-0.0176493,-0.0417118,-0.0642038,-0.0765397,-0.0796592,-0.0650202,-0.0362442,-0.000282133,0.0443733,0.0832553,0.108487,0.121997,0.117646,0.107854,0.0913324,0.0782377,0.0594141,0.0366952,0;0,-0.0116909,-0.000703125,0.0307981,0.0748616,0.10976,0.121896,0.113901,0.0919077,0.0645825,0.0464761,0.0314857,0.0174644,-0.00314394,-0.0246017,-0.048493,-0.0626097,-0.0596948,-0.0439119,-0.0153383,0.0206013,0.052617,0.0835692,0.1034,0.109922,0.102519,0.0856868,0.0676643,0.0454953,0.0215793,0.00329282,-0.00871881,-0.0212145,-0.0271046,-0.0346859,-0.0379897,-0.0454252,-0.0495653,-0.0550015,-0.0494162,-0.0354006,-0.0121494,0.0244189,0.0670409,0.100695,0.121437,0.109772,0.0830731,0.0534345,0 +AMICODE_ITER iter=30 f=3.307937e-03 inf_pr=1.015e-05 inf_du=1.980e-02 +AMICODE_PULSE iter=30 dt=0.224244 a=0,0.066014,0.0950396,0.0901159,0.0662195,0.0414186,0.034131,0.0387854,0.0528365,0.0637888,0.0790307,0.0938258,0.108212,0.12461,0.133132,0.125286,0.106401,0.0735531,0.0321413,-0.00348423,-0.0281854,-0.0403033,-0.0406971,-0.0254701,-0.0077918,0.0121602,0.0284477,0.0326635,0.0299544,0.0207051,0.00645516,-0.0176533,-0.0418818,-0.0646035,-0.077163,-0.0804031,-0.0657063,-0.036683,-0.000364266,0.0446065,0.083621,0.108748,0.121999,0.117426,0.107644,0.0914213,0.0787431,0.0601427,0.0372327,0;0,-0.0115763,-0.000538292,0.030947,0.0749391,0.109733,0.121764,0.113704,0.0917116,0.0644499,0.0464358,0.0315278,0.0175505,-0.00305158,-0.0245168,-0.0483954,-0.06247,-0.0595055,-0.0436987,-0.0151455,0.0207477,0.0527378,0.0837274,0.103676,0.110373,0.103149,0.0864458,0.0684637,0.0462326,0.0221546,0.0036249,-0.00866785,-0.0214143,-0.0274523,-0.0350259,-0.0381782,-0.0453989,-0.0493872,-0.0548349,-0.049451,-0.0357436,-0.0127437,0.0237881,0.0666224,0.10059,0.12151,0.109777,0.0828767,0.0531791,0 +AMICODE_ITER iter=31 f=5.382918e-03 inf_pr=7.151e-06 inf_du=1.459e+02 +AMICODE_PULSE iter=31 dt=0.224244 a=0,0.0659916,0.0949807,0.0900233,0.0661125,0.0413156,0.0340267,0.0386705,0.05271,0.0636793,0.0789739,0.0938567,0.108343,0.124822,0.133395,0.12558,0.10672,0.0739067,0.0325275,-0.00310411,-0.0278765,-0.0401312,-0.0406951,-0.0256255,-0.00803776,0.0119037,0.0282473,0.032558,0.0299424,0.0207543,0.0065079,-0.017667,-0.0420406,-0.0649574,-0.0777045,-0.081039,-0.0662854,-0.0370461,-0.00042341,0.0448028,0.0839087,0.108925,0.121939,0.117183,0.107431,0.0915016,0.0792103,0.0608129,0.0377232,0;0,-0.0114678,-0.000385739,0.0310809,0.0750026,0.109699,0.121642,0.113532,0.0915468,0.0643461,0.0464117,0.0315739,0.0176336,-0.00296063,-0.0244308,-0.0482959,-0.0623351,-0.0593336,-0.0435122,-0.0149827,0.0208656,0.0528328,0.0838521,0.103902,0.11075,0.103682,0.0870924,0.0691444,0.0468616,0.0226493,0.00391645,-0.00861117,-0.0215621,-0.0277185,-0.0352769,-0.0382987,-0.0453412,-0.0492173,-0.0546981,-0.0495144,-0.0360913,-0.0133118,0.0231972,0.06623,0.100482,0.121559,0.109769,0.0826911,0.052939,0 +AMICODE_ITER iter=32 f=3.255077e-03 inf_pr=1.244e-06 inf_du=1.459e+02 +AMICODE_PULSE iter=32 dt=0.224242 a=0,0.0659559,0.094955,0.0900327,0.0661535,0.0413737,0.0340692,0.0386932,0.0527105,0.0636773,0.0789619,0.0938356,0.108311,0.124765,0.133318,0.125498,0.10663,0.0738214,0.0324632,-0.00314611,-0.0278903,-0.0401079,-0.0406294,-0.0255405,-0.0079425,0.0119834,0.028295,0.0325841,0.029952,0.0207598,0.00652344,-0.0176086,-0.0419261,-0.064774,-0.0774683,-0.0807796,-0.0660733,-0.0369424,-0.000447175,0.044665,0.0837353,0.10881,0.121924,0.117259,0.107504,0.091479,0.0790379,0.0605605,0.0375249,0;0,-0.011438,-0.000352832,0.0310978,0.0749899,0.109675,0.121638,0.113559,0.091599,0.064405,0.0464386,0.0315611,0.0175847,-0.00302366,-0.0245032,-0.0483631,-0.0624003,-0.0594043,-0.0435767,-0.0150364,0.0208203,0.0527951,0.0837897,0.103798,0.110598,0.103491,0.0868803,0.0689239,0.0466646,0.0225078,0.00384426,-0.00860719,-0.0214732,-0.0275806,-0.0351305,-0.038208,-0.0453247,-0.0492641,-0.0547371,-0.049488,-0.0359569,-0.0130869,0.0234207,0.0663564,0.100489,0.121489,0.109757,0.0827641,0.0530075,0 +AMICODE_ITER iter=33 f=3.440198e-03 inf_pr=3.882e-08 inf_du=3.002e-01 +AMICODE_PULSE iter=33 dt=0.224242 a=0,0.0659554,0.0949536,0.0900303,0.0661506,0.041371,0.0340665,0.0386901,0.052707,0.0636743,0.0789603,0.0938366,0.108315,0.124772,0.133326,0.125508,0.106641,0.0738331,0.0324754,-0.00313461,-0.0278815,-0.0401037,-0.0406306,-0.0255465,-0.00795121,0.0119743,0.0282873,0.0325791,0.0299499,0.02076,0.00652466,-0.0176082,-0.0419288,-0.0647816,-0.0774811,-0.0807957,-0.066089,-0.0369537,-0.000451786,0.044666,0.0837383,0.108811,0.121924,0.117263,0.107523,0.0915209,0.0791011,0.0606281,0.03757,0;0,-0.0114349,-0.000348367,0.0311019,0.074992,0.109674,0.121635,0.113554,0.091594,0.0644015,0.0464374,0.0315621,0.0175871,-0.0030208,-0.0245003,-0.0483597,-0.062396,-0.059399,-0.043571,-0.0150313,0.0208242,0.0527987,0.0837946,0.103806,0.11061,0.103508,0.0869003,0.0689447,0.0466839,0.0225231,0.0038535,-0.00860501,-0.0214773,-0.0275884,-0.0351379,-0.0382114,-0.0453223,-0.0492577,-0.0547311,-0.0494874,-0.0359643,-0.0130998,0.023409,0.0663537,0.100498,0.121505,0.10977,0.0827688,0.0530058,0 +AMICODE_ITER iter=34 f=3.061413e-03 inf_pr=4.330e-08 inf_du=9.679e-03 +AMICODE_PULSE iter=34 dt=0.224242 a=0,0.065954,0.0949494,0.0900233,0.0661425,0.0413629,0.0340584,0.0386811,0.0526971,0.0636655,0.0789556,0.0938387,0.108325,0.124789,0.133347,0.125532,0.106667,0.0738615,0.0325061,-0.0031048,-0.0278575,-0.0400907,-0.0406311,-0.0255592,-0.00797099,0.0119537,0.0282711,0.0325702,0.0299484,0.0207636,0.00652904,-0.0176085,-0.0419398,-0.0648074,-0.0775213,-0.0808434,-0.0661328,-0.0369814,-0.000456607,0.0446805,0.0837596,0.108824,0.121919,0.117245,0.107509,0.0915322,0.0791441,0.0606869,0.0376125,0;0,-0.0114268,-0.00033688,0.0311122,0.0749973,0.109672,0.121626,0.113541,0.0915809,0.0643929,0.046435,0.0315653,0.0175933,-0.00301399,-0.0244939,-0.0483524,-0.062386,-0.059386,-0.0435568,-0.0150187,0.0208337,0.0528066,0.0838052,0.103825,0.110641,0.10355,0.0869513,0.0689983,0.0467333,0.0225619,0.00387651,-0.00860026,-0.0214885,-0.0276088,-0.0351572,-0.0382201,-0.0453165,-0.0492421,-0.0547171,-0.0494882,-0.0359868,-0.01314,0.0233661,0.0663248,0.100489,0.121507,0.109767,0.0827516,0.0529854,0 +AMICODE_ITER iter=35 f=2.324987e-03 inf_pr=1.978e-07 inf_du=6.728e-03 +AMICODE_PULSE iter=35 dt=0.224242 a=0,0.0659502,0.0949393,0.0900075,0.0661242,0.0413453,0.0340406,0.0386615,0.0526755,0.0636465,0.0789454,0.0938434,0.108347,0.124825,0.133393,0.125583,0.106722,0.0739225,0.0325721,-0.00304033,-0.0278056,-0.0400623,-0.0406316,-0.0255861,-0.00801297,0.0119102,0.0282371,0.0325522,0.0299461,0.0207718,0.00653824,-0.01761,-0.041965,-0.0648647,-0.0776094,-0.0809471,-0.0662271,-0.03704,-0.000465375,0.0447133,0.0838064,0.108851,0.121906,0.117202,0.107473,0.0915483,0.0792275,0.0608052,0.0376989,0;0,-0.0114088,-0.000311392,0.0311348,0.0750084,0.109667,0.121606,0.113513,0.0915531,0.064375,0.0464305,0.0315726,0.0176071,-0.00299877,-0.0244795,-0.0483358,-0.0623636,-0.0593575,-0.0435258,-0.0149914,0.0208538,0.0528233,0.0838273,0.103864,0.110706,0.103641,0.0870609,0.0691133,0.0468393,0.0226453,0.00392598,-0.00858979,-0.0215118,-0.0276513,-0.0351965,-0.0382369,-0.0453029,-0.0492092,-0.0546895,-0.0494944,-0.0360414,-0.0132325,0.0232687,0.0662597,0.100471,0.121514,0.109764,0.0827179,0.0529433,0 +AMICODE_ITER iter=36 f=1.838122e-03 inf_pr=3.560e-07 inf_du=1.459e+02 +AMICODE_PULSE iter=36 dt=0.224242 a=0,0.0659414,0.0949211,0.0899824,0.0660975,0.0413214,0.0340164,0.0386344,0.0526446,0.0636199,0.0789309,0.0938494,0.108376,0.124872,0.133452,0.12565,0.106795,0.0740046,0.0326632,-0.00295033,-0.0277328,-0.0400219,-0.0406307,-0.0256223,-0.00806946,0.0118515,0.0281912,0.0325286,0.0299435,0.0207822,0.00654807,-0.0176152,-0.0420027,-0.0649452,-0.077731,-0.0810876,-0.0663534,-0.0371173,-0.00047482,0.0447573,0.0838651,0.108879,0.121876,0.117132,0.107416,0.0915671,0.0793429,0.0609707,0.0378192,0;0,-0.0113801,-0.000271793,0.0311686,0.0750228,0.109657,0.121576,0.113472,0.091516,0.0643538,0.0464273,0.031585,0.0176273,-0.0029762,-0.0244577,-0.0483098,-0.0623291,-0.0593157,-0.0434818,-0.0149543,0.0208794,0.0528441,0.0838551,0.103916,0.110793,0.103765,0.0872112,0.0692704,0.0469841,0.0227597,0.00399413,-0.00857483,-0.0215414,-0.027706,-0.0352451,-0.0382547,-0.0452794,-0.0491619,-0.0546525,-0.0495078,-0.0361242,-0.0133679,0.0231275,0.0661647,0.100443,0.121522,0.10976,0.0826732,0.0528842,0 +AMICODE_ITER iter=37 f=2.100494e-03 inf_pr=1.752e-07 inf_du=1.459e+02 +AMICODE_PULSE iter=37 dt=0.224242 a=0,0.0659233,0.0949072,0.0899854,0.0661163,0.0413499,0.0340398,0.0386516,0.0526543,0.0636297,0.078934,0.0938429,0.108357,0.124834,0.133398,0.125592,0.106734,0.0739465,0.0326158,-0.00298716,-0.0277563,-0.040027,-0.0406135,-0.0255919,-0.00802843,0.0118915,0.0282208,0.0325478,0.0299483,0.0207725,0.00652833,-0.0176271,-0.0419948,-0.064905,-0.0776597,-0.0809934,-0.0662642,-0.0370593,-0.00045975,0.044727,0.0838147,0.108844,0.121873,0.117162,0.107441,0.0915491,0.0792566,0.0608491,0.0377245,0;0,-0.0113701,-0.000263483,0.0311677,0.0750081,0.109639,0.121572,0.113489,0.0915492,0.0643923,0.046451,0.0315896,0.0176156,-0.00299193,-0.0244739,-0.0483206,-0.0623396,-0.0593338,-0.0435052,-0.0149814,0.0208504,0.0528174,0.0838204,0.103869,0.110731,0.103693,0.0871325,0.0691871,0.0469077,0.0227026,0.00396089,-0.00858344,-0.0215229,-0.027676,-0.0352164,-0.0382482,-0.0452959,-0.0491946,-0.054673,-0.0494968,-0.0360677,-0.0132742,0.0232206,0.0662185,0.100448,0.121495,0.109758,0.0827062,0.0529123,0 +AMICODE_ITER iter=38 f=2.159364e-03 inf_pr=9.848e-10 inf_du=3.647e-02 +AMICODE_PULSE iter=38 dt=0.224242 a=0,0.0659231,0.0949068,0.0899847,0.0661156,0.0413492,0.0340391,0.0386509,0.0526535,0.063629,0.0789336,0.0938431,0.108358,0.124835,0.1334,0.125594,0.106736,0.073949,0.0326185,-0.00298455,-0.0277542,-0.040026,-0.0406136,-0.0255931,-0.00803027,0.0118896,0.0282192,0.0325469,0.029948,0.0207727,0.00652863,-0.0176271,-0.0419956,-0.0649069,-0.0776628,-0.0809972,-0.0662677,-0.0370616,-0.000460377,0.0447276,0.0838157,0.108844,0.121872,0.117161,0.107442,0.0915542,0.0792659,0.0608597,0.0377318,0;0,-0.0113694,-0.000262407,0.0311686,0.0750085,0.109639,0.121572,0.113488,0.0915481,0.0643916,0.0464508,0.0315899,0.0176161,-0.00299125,-0.0244732,-0.0483198,-0.0623385,-0.0593326,-0.043504,-0.0149802,0.0208512,0.0528181,0.0838214,0.10387,0.110734,0.103697,0.087137,0.0691918,0.046912,0.0227061,0.00396295,-0.00858294,-0.0215237,-0.0276776,-0.0352179,-0.0382486,-0.045295,-0.0491929,-0.0546715,-0.0494967,-0.0360695,-0.0132775,0.0232174,0.0662169,0.100449,0.121497,0.10976,0.0827061,0.0529113,0 +AMICODE_ITER iter=39 f=2.111252e-03 inf_pr=3.826e-09 inf_du=6.426e-03 +AMICODE_PULSE iter=39 dt=0.224242 a=0,0.0659226,0.0949055,0.0899828,0.0661135,0.0413472,0.0340372,0.0386487,0.0526511,0.0636269,0.0789325,0.0938436,0.10836,0.124839,0.133405,0.1256,0.106742,0.0739559,0.032626,-0.00297735,-0.0277485,-0.040023,-0.0406139,-0.0255963,-0.00803514,0.0118846,0.0282152,0.0325446,0.0299474,0.0207733,0.00652949,-0.0176272,-0.0419981,-0.0649127,-0.0776719,-0.0810079,-0.0662776,-0.0370678,-0.000461523,0.0447305,0.0838197,0.108846,0.12187,0.117157,0.107441,0.0915611,0.0792823,0.0608801,0.0377461,0;0,-0.0113672,-0.000259418,0.0311712,0.0750098,0.109639,0.121569,0.113484,0.0915449,0.0643896,0.0464502,0.0315907,0.0176177,-0.00298943,-0.0244714,-0.0483177,-0.0623358,-0.0593293,-0.0435004,-0.0149772,0.0208534,0.05282,0.0838239,0.103875,0.110742,0.103707,0.0871492,0.0692046,0.0469238,0.0227154,0.00396855,-0.00858165,-0.0215261,-0.0276821,-0.0352219,-0.03825,-0.0452928,-0.0491884,-0.0546676,-0.0494967,-0.036075,-0.0132873,0.0232073,0.0662108,0.100448,0.121499,0.109761,0.0827033,0.0529069,0 +AMICODE_ITER iter=40 f=1.999882e-03 inf_pr=1.737e-08 inf_du=4.769e-03 +AMICODE_PULSE iter=40 dt=0.224242 a=0,0.0659211,0.0949021,0.0899779,0.066108,0.0413422,0.0340322,0.0386432,0.0526449,0.0636215,0.0789295,0.0938448,0.108367,0.124849,0.133417,0.125614,0.106758,0.0739732,0.0326449,-0.0029589,-0.0277338,-0.040015,-0.0406141,-0.0256041,-0.00804718,0.0118721,0.0282053,0.0325392,0.0299464,0.0207753,0.00653169,-0.0176277,-0.0420051,-0.0649283,-0.0776959,-0.0810361,-0.0663031,-0.0370836,-0.000463764,0.044739,0.083831,0.108851,0.121864,0.117144,0.107433,0.0915715,0.0793145,0.0609226,0.0377764,0;0,-0.0113616,-0.000251634,0.031178,0.0750129,0.109637,0.121563,0.113476,0.091537,0.0643847,0.0464492,0.0315929,0.0176218,-0.00298481,-0.0244669,-0.0483125,-0.0623289,-0.0593208,-0.0434915,-0.0149695,0.0208589,0.0528247,0.0838302,0.103886,0.11076,0.103733,0.0871805,0.0692373,0.0469539,0.0227392,0.00398281,-0.00857836,-0.0215322,-0.0276933,-0.0352318,-0.0382534,-0.0452874,-0.0491776,-0.0546584,-0.0494977,-0.0360902,-0.0133135,0.0231798,0.0661928,0.100444,0.121502,0.109761,0.0826946,0.052895,0 +AMICODE_ITER iter=41 f=1.825478e-03 inf_pr=5.684e-08 inf_du=2.858e-03 +AMICODE_PULSE iter=41 dt=0.224242 a=0,0.0659171,0.0948942,0.0899674,0.0660972,0.0413328,0.0340226,0.0386325,0.0526325,0.0636108,0.0789235,0.093847,0.108378,0.124868,0.13344,0.12564,0.106787,0.074006,0.0326815,-0.00292275,-0.0277046,-0.0399989,-0.0406138,-0.0256187,-0.00806979,0.0118486,0.0281868,0.0325296,0.0299452,0.0207791,0.00653521,-0.01763,-0.0420199,-0.0649598,-0.0777433,-0.0810907,-0.066352,-0.0371131,-0.000466809,0.0447563,0.0838532,0.10886,0.12185,0.117115,0.107411,0.0915823,0.0793657,0.0609943,0.0378281,0;0,-0.0113496,-0.000235191,0.0311919,0.0750186,0.109632,0.121551,0.11346,0.0915223,0.0643766,0.0464482,0.031598,0.0176299,-0.00297561,-0.0244579,-0.0483017,-0.0623147,-0.0593039,-0.0434739,-0.0149549,0.0208689,0.0528328,0.0838412,0.103907,0.110795,0.103783,0.0872408,0.0693002,0.0470119,0.0227851,0.00401034,-0.00857199,-0.0215433,-0.0277143,-0.03525,-0.038259,-0.0452762,-0.0491568,-0.0546418,-0.0495016,-0.0361223,-0.013367,0.0231236,0.0661551,0.100433,0.121505,0.10976,0.0826768,0.052871,0 +AMICODE_ITER iter=42 f=1.786738e-03 inf_pr=7.306e-08 inf_du=1.459e+02 +AMICODE_PULSE iter=42 dt=0.224243 a=0,0.0659068,0.094879,0.0899521,0.0660849,0.0413251,0.034014,0.038621,0.0526172,0.0635981,0.0789159,0.0938487,0.10839,0.124886,0.133461,0.125665,0.106815,0.07404,0.0327232,-0.0028796,-0.0276688,-0.039978,-0.0406106,-0.0256339,-0.00809401,0.0118229,0.0281658,0.0325189,0.0299429,0.0207808,0.00653454,-0.0176366,-0.0420395,-0.0649953,-0.0777942,-0.0811463,-0.0664004,-0.0371411,-0.000466869,0.0447739,0.0838712,0.10886,0.121822,0.117072,0.107378,0.0915906,0.0794212,0.0610741,0.0378847,0;0,-0.0113296,-0.000209093,0.0312119,0.0750231,0.109621,0.121531,0.113439,0.0915077,0.064373,0.0464518,0.0316065,0.0176397,-0.00296451,-0.0244467,-0.0482861,-0.0622943,-0.0592819,-0.0434529,-0.0149396,0.0208766,0.0528384,0.0838491,0.103926,0.110831,0.103838,0.0873091,0.069371,0.047077,0.0228374,0.00404181,-0.00856525,-0.0215554,-0.0277375,-0.0352689,-0.038264,-0.0452616,-0.0491319,-0.0546205,-0.0495042,-0.036157,-0.0134256,0.0230605,0.0661101,0.100417,0.121504,0.109759,0.0826592,0.0528424,0 +AMICODE_ITER iter=43 f=1.791772e-03 inf_pr=4.541e-08 inf_du=1.459e+02 +AMICODE_PULSE iter=43 dt=0.224243 a=0,0.0658798,0.0948523,0.089943,0.066095,0.0413488,0.0340316,0.0386295,0.0526144,0.0635975,0.0789128,0.0938436,0.108381,0.124861,0.133424,0.125627,0.106775,0.0740096,0.0327106,-0.00288007,-0.027662,-0.0399655,-0.0405908,-0.0256167,-0.00807393,0.0118407,0.028177,0.0325285,0.0299451,0.0207718,0.00651337,-0.0176545,-0.042048,-0.0649839,-0.0777642,-0.081097,-0.0663502,-0.0371049,-0.000450305,0.0447599,0.0838378,0.108828,0.1218,0.117073,0.107386,0.0915912,0.0793934,0.0610297,0.0378436,0;0,-0.0113023,-0.000178308,0.031228,0.0750116,0.109593,0.12151,0.113438,0.0915294,0.0644095,0.0464792,0.0316183,0.0176358,-0.00297157,-0.0244542,-0.0482845,-0.062288,-0.059282,-0.0434593,-0.0149544,0.0208531,0.0528156,0.0838205,0.103895,0.110801,0.103813,0.0872909,0.0693506,0.0470587,0.0228272,0.00403648,-0.00856867,-0.0215497,-0.0277319,-0.0352622,-0.0382677,-0.0452697,-0.0491441,-0.0546192,-0.0494879,-0.0361198,-0.0133708,0.0231099,0.066133,0.100413,0.121478,0.109758,0.082679,0.0528473,0 +AMICODE_ITER iter=44 f=1.798270e-03 inf_pr=3.215e-10 inf_du=2.420e-02 +AMICODE_PULSE iter=44 dt=0.224243 a=0,0.0658796,0.094852,0.0899426,0.0660946,0.0413485,0.0340313,0.0386292,0.052614,0.0635971,0.0789126,0.0938437,0.108381,0.124862,0.133424,0.125628,0.106776,0.0740108,0.0327119,-0.00287881,-0.0276611,-0.039965,-0.0405909,-0.0256173,-0.00807481,0.0118398,0.0281763,0.032528,0.0299449,0.0207718,0.00651342,-0.0176545,-0.0420483,-0.0649848,-0.0777655,-0.0810986,-0.0663517,-0.0371059,-0.000450543,0.0447601,0.0838381,0.108828,0.121799,0.117073,0.107387,0.0915944,0.0793989,0.0610358,0.0378477,0;0,-0.0113019,-0.000177686,0.0312285,0.0750118,0.109593,0.12151,0.113438,0.0915289,0.0644093,0.0464791,0.0316185,0.0176361,-0.00297122,-0.0244538,-0.048284,-0.0622874,-0.0592813,-0.0434586,-0.0149539,0.0208535,0.0528159,0.0838209,0.103896,0.110803,0.103815,0.087293,0.0693528,0.0470607,0.0228287,0.00403745,-0.00856843,-0.02155,-0.0277326,-0.0352628,-0.0382678,-0.0452692,-0.0491432,-0.0546185,-0.0494879,-0.0361207,-0.0133724,0.0231084,0.0661325,0.100414,0.12148,0.10976,0.0826797,0.0528472,0 +AMICODE_ITER iter=45 f=1.791118e-03 inf_pr=7.515e-10 inf_du=2.112e-03 +AMICODE_PULSE iter=45 dt=0.224243 a=0,0.065879,0.094851,0.0899415,0.0660936,0.0413478,0.0340305,0.0386282,0.0526129,0.0635961,0.078912,0.0938439,0.108382,0.124864,0.133426,0.12563,0.106779,0.0740138,0.0327153,-0.0028754,-0.0276583,-0.0399635,-0.0405909,-0.0256186,-0.00807696,0.0118375,0.0281744,0.032527,0.0299446,0.0207719,0.00651352,-0.0176548,-0.0420497,-0.0649875,-0.0777696,-0.0811032,-0.0663558,-0.0371084,-0.000450829,0.0447614,0.0838395,0.108828,0.121798,0.11707,0.107385,0.0915971,0.0794061,0.061045,0.0378541,0;0,-0.0113005,-0.000175892,0.0312299,0.0750123,0.109592,0.121509,0.113436,0.0915275,0.0644087,0.0464792,0.0316191,0.0176369,-0.00297031,-0.0244529,-0.0482828,-0.0622859,-0.0592796,-0.0434569,-0.0149526,0.0208543,0.0528166,0.0838218,0.103898,0.110806,0.10382,0.0872986,0.0693586,0.047066,0.022833,0.00404003,-0.00856783,-0.021551,-0.0277344,-0.0352643,-0.0382682,-0.0452679,-0.0491409,-0.0546165,-0.0494879,-0.0361234,-0.013377,0.0231035,0.0661293,0.100413,0.121481,0.10976,0.0826786,0.0528451,0 +AMICODE_ITER iter=46 f=1.775756e-03 inf_pr=3.342e-09 inf_du=1.502e-03 +AMICODE_PULSE iter=46 dt=0.224243 a=0,0.0658773,0.0948484,0.0899386,0.0660911,0.0413461,0.0340287,0.038626,0.05261,0.0635937,0.0789106,0.0938442,0.108385,0.124867,0.133431,0.125635,0.106785,0.0740207,0.0327235,-0.0028671,-0.0276515,-0.0399597,-0.0405905,-0.0256218,-0.00808189,0.0118323,0.0281702,0.0325247,0.029944,0.0207723,0.0065136,-0.0176559,-0.0420533,-0.0649943,-0.0777795,-0.0811143,-0.0663656,-0.0371142,-0.00045113,0.0447648,0.0838431,0.108828,0.121793,0.117062,0.10738,0.0916007,0.0794194,0.0610631,0.0378669,0;0,-0.011297,-0.000171225,0.0312336,0.0750133,0.10959,0.121505,0.113432,0.0915245,0.0644076,0.0464795,0.0316205,0.0176387,-0.00296814,-0.0244507,-0.0482799,-0.0622821,-0.0592755,-0.0434529,-0.0149495,0.020856,0.0528179,0.0838237,0.103902,0.110813,0.103831,0.087312,0.0693725,0.0470788,0.0228433,0.00404621,-0.00856642,-0.0215533,-0.0277389,-0.0352679,-0.0382689,-0.0452647,-0.0491357,-0.054612,-0.049488,-0.0361299,-0.0133884,0.0230913,0.0661209,0.100411,0.121481,0.10976,0.0826753,0.0528397,0 +AMICODE_ITER iter=47 f=1.756168e-03 inf_pr=8.945e-09 inf_du=8.295e-04 +AMICODE_PULSE iter=47 dt=0.224243 a=0,0.0658726,0.0948419,0.0899328,0.0660871,0.0413442,0.0340264,0.0386224,0.0526046,0.0635893,0.0789079,0.0938446,0.108388,0.124872,0.133436,0.125642,0.106793,0.0740313,0.0327375,-0.00285228,-0.0276391,-0.0399523,-0.0405889,-0.0256267,-0.00808974,0.0118239,0.028163,0.0325209,0.0299429,0.0207722,0.00651226,-0.0176589,-0.0420602,-0.0650057,-0.0777954,-0.081131,-0.0663799,-0.0371221,-0.000450377,0.0447701,0.0838475,0.108826,0.121781,0.117046,0.107369,0.0916039,0.0794388,0.0610909,0.0378863,0;0,-0.0112889,-0.00016084,0.0312413,0.0750144,0.109585,0.121497,0.113425,0.0915201,0.0644076,0.0464817,0.0316239,0.017642,-0.00296434,-0.0244467,-0.048274,-0.0622744,-0.0592675,-0.0434457,-0.0149447,0.0208578,0.052819,0.0838253,0.103907,0.110825,0.103849,0.0873347,0.069396,0.0471004,0.0228608,0.00405679,-0.00856425,-0.0215572,-0.0277465,-0.0352738,-0.0382703,-0.0452592,-0.0491266,-0.0546037,-0.0494875,-0.0361401,-0.0134066,0.023071,0.0661058,0.100405,0.12148,0.10976,0.0826702,0.0528301,0 +AMICODE_ITER iter=48 f=1.756479e-03 inf_pr=1.201e-08 inf_du=1.459e+02 +AMICODE_PULSE iter=48 dt=0.224245 a=0,0.0658593,0.0948263,0.0899227,0.0660846,0.0413484,0.0340285,0.0386209,0.052598,0.0635844,0.078904,0.0938435,0.10839,0.124871,0.133433,0.12564,0.106792,0.0740362,0.032751,-0.0028348,-0.0276231,-0.0399411,-0.040583,-0.0256293,-0.00809529,0.0118167,0.0281552,0.0325166,0.0299397,0.0207676,0.00650383,-0.0176673,-0.0420695,-0.0650141,-0.0778037,-0.0811352,-0.0663814,-0.0371204,-0.000444323,0.0447724,0.0838419,0.108811,0.121755,0.117021,0.10735,0.0916042,0.079455,0.0611157,0.0379019,0;0,-0.0112711,-0.000139296,0.0312552,0.0750122,0.10957,0.121481,0.113414,0.0915194,0.0644165,0.046491,0.0316308,0.0176452,-0.00296063,-0.0244422,-0.0482643,-0.0622612,-0.0592564,-0.0434377,-0.0149426,0.0208536,0.0528141,0.0838197,0.103905,0.11083,0.103863,0.0873569,0.0694183,0.0471211,0.0228788,0.00406758,-0.00856345,-0.0215616,-0.0277558,-0.0352805,-0.0382735,-0.045254,-0.0491166,-0.0545898,-0.0494791,-0.0361399,-0.0134131,0.0230589,0.0660922,0.100396,0.121471,0.109761,0.0826707,0.0528201,0 +AMICODE_ITER iter=49 f=1.752293e-03 inf_pr=2.879e-08 inf_du=1.459e+02 +AMICODE_PULSE iter=49 dt=0.224245 a=0,0.0658195,0.0947842,0.0899032,0.0660923,0.0413768,0.03405,0.0386297,0.0525907,0.0635797,0.0788976,0.0938381,0.108383,0.12485,0.133397,0.125605,0.106758,0.0740146,0.0327532,-0.0028183,-0.0276015,-0.039919,-0.0405593,-0.0256159,-0.00808207,0.0118265,0.0281589,0.0325225,0.0299402,0.0207563,0.00647696,-0.0176927,-0.0420889,-0.0650164,-0.0777901,-0.0811003,-0.0663419,-0.0370885,-0.000423643,0.0447641,0.0838107,0.108774,0.12172,0.117008,0.107352,0.091616,0.0794539,0.0611041,0.0378814,0;0,-0.0112322,-9.54524e-05,0.0312779,0.0749959,0.109529,0.121448,0.113407,0.0915422,0.0644619,0.0465279,0.0316499,0.0176449,-0.00296481,-0.024447,-0.0482564,-0.0622459,-0.0592482,-0.0434375,-0.0149545,0.0208291,0.0527907,0.0837914,0.103879,0.110813,0.10386,0.0873655,0.0694248,0.0471273,0.0228891,0.00407493,-0.00856445,-0.02156,-0.0277595,-0.0352816,-0.0382812,-0.045258,-0.0491203,-0.0545782,-0.0494592,-0.0361086,-0.013371,0.0230928,0.0661024,0.100387,0.121442,0.10976,0.0826861,0.0528132,0 +AMICODE_ITER iter=50 f=1.752641e-03 inf_pr=1.524e-10 inf_du=1.681e-02 +AMICODE_PULSE iter=50 dt=0.224245 a=0,0.0658193,0.0947839,0.0899029,0.0660922,0.0413768,0.03405,0.0386296,0.0525905,0.0635796,0.0788975,0.0938381,0.108383,0.12485,0.133397,0.125605,0.106758,0.0740149,0.0327537,-0.00281783,-0.0276011,-0.0399188,-0.0405593,-0.0256161,-0.00808238,0.0118262,0.0281586,0.0325222,0.0299401,0.0207562,0.00647683,-0.0176928,-0.0420891,-0.0650166,-0.0777903,-0.0811006,-0.0663421,-0.0370886,-0.00042361,0.0447641,0.0838106,0.108774,0.121719,0.117008,0.107352,0.0916179,0.0794567,0.0611072,0.0378834,0;0,-0.0112319,-9.50425e-05,0.0312781,0.0749958,0.109529,0.121447,0.113407,0.0915421,0.064462,0.046528,0.03165,0.017645,-0.00296468,-0.0244468,-0.0482561,-0.0622456,-0.0592479,-0.0434373,-0.0149544,0.0208291,0.0527907,0.0837914,0.10388,0.110813,0.103861,0.0873662,0.0694255,0.047128,0.0228896,0.00407525,-0.0085644,-0.0215601,-0.0277598,-0.0352818,-0.0382813,-0.0452579,-0.0491201,-0.054578,-0.0494592,-0.0361089,-0.0133714,0.0230925,0.0661027,0.100388,0.121444,0.109762,0.0826879,0.052814,0 +AMICODE_ITER iter=51 f=1.752105e-03 inf_pr=7.057e-11 inf_du=4.923e-04 +AMICODE_PULSE iter=51 dt=0.224245 a=0,0.0658185,0.094783,0.0899023,0.0660919,0.0413769,0.03405,0.0386294,0.05259,0.0635792,0.0788972,0.0938381,0.108384,0.12485,0.133397,0.125606,0.106758,0.0740156,0.0327548,-0.0028165,-0.0276,-0.039918,-0.040559,-0.0256165,-0.00808298,0.0118255,0.0281579,0.0325218,0.0299398,0.0207559,0.00647641,-0.0176933,-0.0420897,-0.0650175,-0.0777914,-0.0811016,-0.0663429,-0.037089,-0.000423458,0.0447644,0.0838106,0.108773,0.121718,0.117006,0.107351,0.0916184,0.0794586,0.0611098,0.0378851,0;0,-0.0112308,-9.37531e-05,0.031279,0.0749958,0.109528,0.121446,0.113406,0.0915418,0.0644623,0.0465285,0.0316504,0.0176453,-0.00296436,-0.0244465,-0.0482554,-0.0622447,-0.0592471,-0.0434366,-0.0149541,0.0208291,0.0527906,0.0837913,0.10388,0.110814,0.103862,0.087368,0.0694274,0.0471297,0.0228911,0.00407612,-0.00856429,-0.0215605,-0.0277605,-0.0352823,-0.0382814,-0.0452574,-0.0491192,-0.054577,-0.0494588,-0.0361093,-0.0133725,0.0230911,0.0661015,0.100387,0.121444,0.109762,0.0826877,0.0528132,0 +AMICODE_ITER iter=52 f=1.750896e-03 inf_pr=3.981e-10 inf_du=3.741e-04 +AMICODE_PULSE iter=52 dt=0.224245 a=0,0.0658164,0.0947805,0.0899006,0.0660914,0.0413774,0.0340502,0.038629,0.0525888,0.0635783,0.0788966,0.093838,0.108384,0.12485,0.133397,0.125606,0.106758,0.0740168,0.0327574,-0.00281333,-0.0275971,-0.0399161,-0.0405581,-0.0256171,-0.0080842,0.011824,0.0281564,0.0325209,0.0299392,0.0207552,0.00647507,-0.0176947,-0.0420915,-0.0650193,-0.0777935,-0.0811031,-0.066344,-0.0370893,-0.000422728,0.0447648,0.08381,0.108771,0.121714,0.117002,0.107348,0.0916188,0.0794621,0.0611149,0.0378885,0;0,-0.0112279,-9.01857e-05,0.0312813,0.0749956,0.109526,0.121444,0.113404,0.0915415,0.0644636,0.0465299,0.0316516,0.0176459,-0.00296362,-0.0244456,-0.0482537,-0.0622424,-0.0592451,-0.0434351,-0.0149535,0.0208286,0.05279,0.0837906,0.10388,0.110815,0.103865,0.0873722,0.0694316,0.0471336,0.0228944,0.00407811,-0.0085641,-0.0215613,-0.0277621,-0.0352835,-0.0382819,-0.0452563,-0.0491173,-0.0545745,-0.0494575,-0.0361098,-0.0133743,0.0230883,0.0660987,0.100386,0.121442,0.109763,0.0826876,0.0528114,0 +AMICODE_ITER iter=53 f=1.749465e-03 inf_pr=1.439e-09 inf_du=1.459e+02 +AMICODE_PULSE iter=53 dt=0.224246 a=0,0.0658102,0.0947735,0.0898964,0.066091,0.0413801,0.034052,0.0386291,0.0525865,0.0635767,0.0788951,0.0938373,0.108384,0.124848,0.133393,0.125603,0.106756,0.0740169,0.0327615,-0.00280731,-0.0275913,-0.0399118,-0.0405553,-0.0256174,-0.00808539,0.011822,0.0281537,0.0325194,0.0299376,0.0207526,0.00647064,-0.0176986,-0.0420949,-0.0650211,-0.0777942,-0.0811014,-0.0663414,-0.0370866,-0.000419696,0.0447647,0.0838058,0.108764,0.121703,0.116992,0.107341,0.0916185,0.0794665,0.061122,0.0378925,0;0,-0.01122,-8.07526e-05,0.0312872,0.0749941,0.109519,0.121436,0.1134,0.0915421,0.0644685,0.0465344,0.0316545,0.0176468,-0.0029625,-0.0244441,-0.0482498,-0.062237,-0.0592408,-0.0434324,-0.0149534,0.0208259,0.052787,0.083787,0.103877,0.110815,0.103869,0.0873788,0.0694382,0.0471397,0.0229,0.00408144,-0.00856424,-0.0215628,-0.0277655,-0.0352859,-0.0382834,-0.0452548,-0.0491139,-0.0545688,-0.0494527,-0.0361069,-0.0133733,0.0230866,0.0660948,0.100382,0.121438,0.109763,0.0826893,0.0528083,0 +AMICODE_ITER iter=54 f=1.749081e-03 inf_pr=6.355e-09 inf_du=1.459e+02 +AMICODE_PULSE iter=54 dt=0.224246 a=0,0.0657914,0.0947537,0.0898874,0.0660949,0.0413939,0.0340624,0.0386335,0.0525832,0.0635746,0.0788921,0.0938346,0.108381,0.124838,0.133376,0.125586,0.106739,0.0740059,0.0327618,-0.00280018,-0.0275816,-0.0399016,-0.0405439,-0.0256107,-0.00807861,0.0118272,0.0281559,0.0325224,0.0299379,0.0207472,0.0064579,-0.0177105,-0.0421036,-0.0650212,-0.0777862,-0.0810831,-0.066321,-0.0370703,-0.000409395,0.0447607,0.0837906,0.108745,0.121686,0.116986,0.107342,0.091623,0.079464,0.0611143,0.0378811,0;0,-0.0112017,-6.02099e-05,0.0312977,0.0749862,0.1095,0.121421,0.113397,0.0915533,0.0644902,0.046552,0.0316635,0.0176465,-0.00296472,-0.0244466,-0.0482463,-0.06223,-0.0592374,-0.0434328,-0.0149595,0.020814,0.0527757,0.0837733,0.103865,0.110806,0.103866,0.0873819,0.0694402,0.0471417,0.0229042,0.00408451,-0.00856481,-0.021562,-0.0277671,-0.0352863,-0.0382873,-0.0452571,-0.049116,-0.0545633,-0.0494426,-0.0360907,-0.0133514,0.0231044,0.0661006,0.100378,0.121424,0.109762,0.082696,0.0528049,0 +AMICODE_ITER iter=55 f=1.749309e-03 inf_pr=8.059e-12 inf_du=3.421e-03 +AMICODE_PULSE iter=55 dt=0.224246 a=0,0.0657913,0.0947535,0.0898873,0.0660949,0.0413939,0.0340624,0.0386335,0.0525832,0.0635746,0.0788921,0.0938346,0.108381,0.124838,0.133376,0.125586,0.106739,0.074006,0.0327619,-0.00280003,-0.0275815,-0.0399015,-0.0405439,-0.0256108,-0.00807869,0.0118271,0.0281558,0.0325223,0.0299379,0.0207471,0.00645782,-0.0177105,-0.0421036,-0.0650213,-0.0777863,-0.0810832,-0.0663211,-0.0370703,-0.000409371,0.0447607,0.0837905,0.108745,0.121686,0.116986,0.107342,0.0916234,0.0794646,0.0611149,0.0378816,0;0,-0.0112016,-6.00412e-05,0.0312978,0.0749862,0.109499,0.121421,0.113397,0.0915532,0.0644903,0.0465521,0.0316636,0.0176465,-0.00296468,-0.0244465,-0.0482462,-0.0622299,-0.0592373,-0.0434327,-0.0149595,0.0208139,0.0527757,0.0837733,0.103865,0.110807,0.103866,0.0873821,0.0694404,0.0471419,0.0229044,0.0040846,-0.00856481,-0.021562,-0.0277672,-0.0352864,-0.0382873,-0.045257,-0.0491159,-0.0545632,-0.0494426,-0.0360907,-0.0133516,0.0231043,0.0661006,0.100378,0.121424,0.109762,0.0826964,0.0528051,0 +AMICODE_ITER iter=56 f=1.749159e-03 inf_pr=1.343e-11 inf_du=4.302e-04 +AMICODE_PULSE iter=56 dt=0.224246 a=0,0.0657909,0.0947531,0.089887,0.0660948,0.041394,0.0340625,0.0386334,0.052583,0.0635744,0.078892,0.0938346,0.108381,0.124838,0.133376,0.125586,0.106739,0.0740062,0.0327623,-0.00279957,-0.0275811,-0.0399012,-0.0405438,-0.0256109,-0.00807888,0.0118269,0.0281556,0.0325222,0.0299377,0.020747,0.00645759,-0.0177108,-0.0421039,-0.0650215,-0.0777866,-0.0810833,-0.0663212,-0.0370703,-0.000409286,0.0447607,0.0837904,0.108745,0.121685,0.116986,0.107342,0.0916238,0.0794656,0.0611161,0.0378823,0;0,-0.0112011,-5.94888e-05,0.0312982,0.0749861,0.109499,0.12142,0.113396,0.0915532,0.0644905,0.0465523,0.0316637,0.0176466,-0.00296457,-0.0244464,-0.0482459,-0.0622296,-0.0592369,-0.0434325,-0.0149594,0.0208139,0.0527756,0.0837732,0.103865,0.110807,0.103867,0.0873827,0.069441,0.0471424,0.0229048,0.00408487,-0.0085648,-0.0215622,-0.0277674,-0.0352866,-0.0382874,-0.0452569,-0.0491156,-0.0545628,-0.0494424,-0.0360908,-0.0133518,0.023104,0.0661004,0.100378,0.121424,0.109763,0.0826969,0.0528051,0 +AMICODE_ITER iter=57 f=1.748810e-03 inf_pr=5.968e-11 inf_du=3.277e-04 +AMICODE_PULSE iter=57 dt=0.224247 a=0,0.0657899,0.094752,0.0898863,0.0660947,0.0413943,0.0340627,0.0386334,0.0525826,0.0635741,0.0788917,0.0938345,0.108381,0.124838,0.133375,0.125585,0.106739,0.0740065,0.0327632,-0.00279834,-0.02758,-0.0399004,-0.0405434,-0.0256111,-0.00807932,0.0118263,0.0281549,0.0325218,0.0299374,0.0207465,0.00645688,-0.0177114,-0.0421046,-0.0650221,-0.0777872,-0.0810837,-0.0663214,-0.0370703,-0.000408967,0.0447607,0.0837899,0.108744,0.121683,0.116984,0.107341,0.0916243,0.0794673,0.0611185,0.0378839,0;0,-0.0111998,-5.78831e-05,0.0312992,0.074986,0.109498,0.121419,0.113396,0.0915531,0.0644912,0.046553,0.0316642,0.0176468,-0.00296429,-0.024446,-0.0482451,-0.0622285,-0.0592361,-0.0434319,-0.0149592,0.0208135,0.0527752,0.0837727,0.103865,0.110807,0.103868,0.0873842,0.0694425,0.0471438,0.022906,0.00408559,-0.00856479,-0.0215625,-0.0277681,-0.0352871,-0.0382876,-0.0452565,-0.0491149,-0.0545618,-0.0494418,-0.0360907,-0.0133522,0.0231031,0.0660995,0.100378,0.121424,0.109764,0.0826976,0.0528048,0 +AMICODE_ITER iter=58 f=1.748138e-03 inf_pr=3.479e-10 inf_du=2.698e-04 +AMICODE_PULSE iter=58 dt=0.224247 a=0,0.065787,0.0947486,0.0898843,0.0660944,0.0413956,0.0340635,0.0386334,0.0525814,0.0635733,0.0788911,0.0938343,0.108381,0.124837,0.133374,0.125584,0.106738,0.0740066,0.0327652,-0.00279543,-0.0275772,-0.0398984,-0.0405421,-0.0256113,-0.00807999,0.0118252,0.0281535,0.0325209,0.0299365,0.0207452,0.00645472,-0.0177134,-0.0421063,-0.0650231,-0.0777877,-0.0810831,-0.0663204,-0.0370693,-0.000407688,0.0447606,0.083788,0.10874,0.121678,0.116979,0.107338,0.0916245,0.0794699,0.0611224,0.0378861,0;0,-0.0111961,-5.33711e-05,0.031302,0.0749853,0.109495,0.121416,0.113394,0.0915534,0.0644934,0.0465551,0.0316656,0.0176473,-0.00296371,-0.0244453,-0.0482432,-0.0622259,-0.059234,-0.0434305,-0.0149591,0.0208123,0.0527738,0.083771,0.103864,0.110807,0.10387,0.0873873,0.0694457,0.0471467,0.0229087,0.00408718,-0.00856488,-0.0215633,-0.0277697,-0.0352882,-0.0382883,-0.0452557,-0.0491133,-0.054559,-0.0494395,-0.0360895,-0.013352,0.0231021,0.0660975,0.100376,0.121422,0.109764,0.0826987,0.0528035,0 +AMICODE_ITER iter=59 f=1.747468e-03 inf_pr=1.807e-09 inf_du=1.459e+02 +AMICODE_PULSE iter=59 dt=0.224248 a=0,0.0657782,0.094739,0.0898792,0.0660949,0.0414006,0.034067,0.0386343,0.052579,0.0635718,0.0788894,0.0938331,0.10838,0.124833,0.133367,0.125578,0.106732,0.074004,0.0327682,-0.0027895,-0.027571,-0.0398933,-0.0405379,-0.0256106,-0.00807993,0.0118242,0.0281511,0.0325193,0.0299343,0.0207411,0.00644804,-0.0177188,-0.0421099,-0.065023,-0.0777846,-0.0810759,-0.0663126,-0.0370628,-0.00040294,0.0447592,0.0837804,0.108729,0.121664,0.116968,0.10733,0.0916232,0.079472,0.0611266,0.0378874,0;0,-0.0111855,-4.08829e-05,0.0313095,0.0749827,0.109485,0.121406,0.113389,0.0915555,0.0645013,0.0465619,0.0316695,0.0176479,-0.00296294,-0.0244439,-0.0482384,-0.0622192,-0.0592292,-0.0434281,-0.0149603,0.0208075,0.0527688,0.0837649,0.103858,0.110805,0.103871,0.0873923,0.0694503,0.0471511,0.0229134,0.0040899,-0.00856567,-0.0215649,-0.0277733,-0.0352908,-0.0382907,-0.0452549,-0.0491103,-0.0545518,-0.0494318,-0.0360821,-0.0133453,0.023105,0.0660955,0.100372,0.121416,0.109765,0.0827028,0.0528012,0 +AMICODE_ITER iter=60 f=1.745782e-03 inf_pr=1.330e-08 inf_du=1.459e+02 +AMICODE_PULSE iter=60 dt=0.224248 a=0,0.0657515,0.0947106,0.0898657,0.0660996,0.0414192,0.034081,0.0386398,0.0525736,0.0635682,0.0788848,0.0938295,0.108376,0.124819,0.133343,0.125555,0.10671,0.0739907,0.032771,-0.00277716,-0.0275555,-0.0398779,-0.0405219,-0.0256021,-0.00807183,0.01183,0.028153,0.0325229,0.0299346,0.0207337,0.00643018,-0.0177358,-0.0421233,-0.0650254,-0.0777768,-0.0810539,-0.0662872,-0.0370419,-0.000388856,0.0447543,0.08376,0.108704,0.121639,0.116957,0.107329,0.0916315,0.0794734,0.0611219,0.0378759,0;0,-0.0111589,-1.08313e-05,0.0313253,0.074972,0.109457,0.121384,0.113384,0.0915702,0.0645314,0.0465866,0.0316826,0.017648,-0.00296537,-0.0244468,-0.0482327,-0.0622084,-0.0592231,-0.0434273,-0.0149678,0.0207913,0.0527534,0.0837463,0.103842,0.110794,0.103871,0.0874003,0.0694571,0.0471575,0.022922,0.00409592,-0.00856602,-0.0215642,-0.0277765,-0.035292,-0.0382959,-0.045257,-0.0491117,-0.0545431,-0.0494182,-0.0360618,-0.0133186,0.0231259,0.0661011,0.100365,0.121396,0.109764,0.0827117,0.0527952,0 +DONE fidelity=0.9998472204856121 diff --git a/packages/extension/demo/run/run.toml b/packages/extension/demo/run/run.toml index 44342b0e..6a2372e7 100644 --- a/packages/extension/demo/run/run.toml +++ b/packages/extension/demo/run/run.toml @@ -1,11 +1,11 @@ schema_version = "1" -run_id = "r20260617-161814Z-e8cb" -script_path = "/Users/raghavchari/amicode/packages/extension/templates/solve_template.jl" -lab = "default" -lab_id = "default" -created_at = "2026-06-17T16:18:14.159Z" +run_id = "r20260702-232128Z-a0b4" +script_path = "/Users/katebonner/Desktop/harmoniqs_products/amicode/packages/extension/templates/solve_template.jl" +lab = "devlab" +lab_id = "devlab" +created_at = "2026-07-02T23:21:28.731Z" orchestrator_version = "0.1.0" [julia] binary = "julia" -project = "/Users/raghavchari/.amico/julia" +project = "/Users/katebonner/Desktop/harmoniqs_products/amicode/packages/extension/julia" diff --git a/packages/extension/dev/pulseplot_harness/index.html b/packages/extension/dev/pulseplot_harness/index.html new file mode 100644 index 00000000..5e7190e8 --- /dev/null +++ b/packages/extension/dev/pulseplot_harness/index.html @@ -0,0 +1,40 @@ + + + + + pulseplot harness (#66) + + + + + +
+ + + + + +
+
+ + + diff --git a/packages/extension/dev/pulseplot_harness/main.ts b/packages/extension/dev/pulseplot_harness/main.ts new file mode 100644 index 00000000..b7174a72 --- /dev/null +++ b/packages/extension/dev/pulseplot_harness/main.ts @@ -0,0 +1,92 @@ +// Pulseplot dev harness (#66) — standalone browser page for iterating on the +// component's visuals and measuring the AC8 render budget. NOT shipped in the +// VSIX (dev/ is excluded; see .vscodeignore). Run: `pnpm run dev:pulseplot`. +// +// Feeds the component synthetic solve-like data by default; drop a recorded +// `pulse-events.json` (array of pulsemeta/pulse message objects, produced from +// a real run.log) next to index.html to replay a real solve. + +import { pulseplot } from "../../media/ui/components/pulseplot"; + +const HARNESS_META = { drives: 2, knots: 50, labels: ["a_1", "a_2"], bounds: [[-0.2, 0.2], [-0.2, 0.2]] as [number, number][] }; + +const root = document.getElementById("plot-host")!; +const plot = pulseplot("Harness idle — press play."); +root.append(plot.el); + +// --- synthetic data: smooth random-walk pulses converging toward a waveform +function syntheticRecord(iter: number): { iter: number; dt: number; values: number[][] } { + const { knots, drives, bounds } = HARNESS_META; + const dt = 10.0 / knots; + const values = Array.from({ length: drives }, (_, d) => { + const [lo, hi] = bounds[d]; + const amp = (hi - lo) / 2; + return Array.from({ length: knots }, (_, k) => { + const t = k / knots; + const target = amp * 0.85 * Math.sin((d + 1) * Math.PI * t * 2) * Math.sin(Math.PI * t); + const noise = amp * Math.max(0, 1 - iter / 40) * (Math.random() - 0.5); + return target + noise; + }); + }); + return { iter, dt, values }; +} + +// --- controls +const themeBtn = document.getElementById("theme")!; +themeBtn.addEventListener("click", () => document.body.classList.toggle("dark")); + +let timer: ReturnType | undefined; +let iter = 0; +const playBtn = document.getElementById("play")!; +playBtn.addEventListener("click", () => { + if (timer) { clearInterval(timer); timer = undefined; playBtn.textContent = "▶ play"; return; } + plot.meta(HARNESS_META); + playBtn.textContent = "⏸ pause"; + timer = setInterval(() => { + plot.update(syntheticRecord(iter++)); + if (iter > 60) iter = 0; + }, 200); // the host's 5 Hz cadence +}); + +document.getElementById("clear")!.addEventListener("click", () => { plot.clear(); iter = 0; }); + +// --- AC8 budget: median + p95 of component update at fixture scale +document.getElementById("bench")!.addEventListener("click", () => { + plot.meta(HARNESS_META); + const times: number[] = []; + for (let i = 0; i < 300; i++) { + const rec = syntheticRecord(i % 60); + const t0 = performance.now(); + plot.update(rec); + times.push(performance.now() - t0); + } + times.sort((a, b) => a - b); + const med = times[150].toFixed(3), p95 = times[285].toFixed(3), max = times[299].toFixed(3); + const verdict = times[285] <= 16 ? "PASS (≤16ms)" : "FAIL (>16ms)"; + document.getElementById("bench-out")!.textContent = + `update() over 300 frames @ 2×50: median ${med}ms · p95 ${p95}ms · max ${max}ms → ${verdict}`; + console.log("[bench]", { med, p95, max, verdict }); +}); + +// --- optional recorded replay +fetch("./pulse-events.json").then((r) => (r.ok ? r.json() : undefined)).then((events?: Array>) => { + if (!events) return; + const btn = document.createElement("button"); + btn.textContent = "▶ replay recording"; + btn.addEventListener("click", () => { + if (timer) { clearInterval(timer); timer = undefined; } + let i = 0; + timer = setInterval(() => { + const e = events[i++]; + if (!e) { clearInterval(timer!); timer = undefined; return; } + if (e.type === "pulsemeta") plot.meta(e as never); + else if (e.type === "pulse") plot.update(e as never); + }, 200); + }); + document.getElementById("controls")!.append(btn); +}); + +// --- URL-hash automation for headless-ish eyeballing: #autoplay #bench #dark +if (location.hash.includes("dark")) document.body.classList.add("dark"); +if (location.hash.includes("autoplay")) (document.getElementById("play") as HTMLButtonElement).click(); +if (location.hash.includes("bench")) setTimeout(() => (document.getElementById("bench") as HTMLButtonElement).click(), 800); diff --git a/packages/extension/media/brand.css b/packages/extension/media/brand.css index ca649f79..12c0790d 100644 --- a/packages/extension/media/brand.css +++ b/packages/extension/media/brand.css @@ -39,4 +39,14 @@ /* background */ --bg-box: var(--vscode-editorWidget-background, transparent); --bg-plot: var(--vscode-editor-background); + + /* series — categorical, FIXED order (drive 1..4), never cycled. Primary + * values ride VS Code's theme-aware chart tokens; the hex fallbacks are the + * dev-harness set, validated (dataviz six checks) against the dark surface. */ + --series-1: var(--vscode-charts-blue, #3794ff); + --series-2: var(--vscode-charts-orange, #c17800); + --series-3: var(--vscode-charts-purple, #9b6bc4); + --series-4: var(--vscode-charts-green, #4d9e51); + --plot-grid: var(--vscode-charts-lines, color-mix(in srgb, var(--vscode-foreground, #ccc) 18%, transparent)); + --plot-band: color-mix(in srgb, var(--vscode-foreground, #ccc) 6%, transparent); } diff --git a/packages/extension/media/ui/components/preview.ts b/packages/extension/media/ui/components/preview.ts deleted file mode 100644 index fa75eafb..00000000 --- a/packages/extension/media/ui/components/preview.ts +++ /dev/null @@ -1,74 +0,0 @@ -// Preview component — double-buffered image host with a placeholder overlay. -// Preloads each frame into the hidden buffer and flips opacity on decode, so -// iter frames swap at 5 Hz with zero flicker. - -import { defineStyle } from "../style"; -import { text } from "../atoms/text"; -import { mark } from "../atoms/icon"; - -defineStyle("preview", ` - .preview-host { flex: 1 1 240px; min-height: 240px; min-width: 0; position: relative; - background: var(--bg-plot); - border: var(--border-width) solid var(--border-color); - border-radius: var(--border-radius); padding: var(--space-sm); - display: grid; place-items: stretch; overflow: hidden; } - .preview-host img { grid-column: 1; grid-row: 1; width: 100%; height: 100%; - object-fit: contain; display: block; opacity: 0; - transition: opacity 120ms ease; } - .preview-placeholder { place-self: center; text-align: center; opacity: 0.55; - display: flex; flex-direction: column; align-items: center; - gap: var(--space-sm); } - .preview-placeholder .mark { font-size: var(--text-hero); padding: var(--space-xs) var(--space-md); opacity: 0.8; } - .preview-placeholder .hint { font-style: italic; max-width: 240px; line-height: 1.5; } -`); - -export interface Preview { - el: HTMLDivElement; - /** Show a frame; onShown fires after the buffer flip. */ - show(url: string, onShown: () => void): void; - /** Clear both buffers and surface the placeholder with a hint. */ - waiting(hint: string): void; -} - -export function preview(initialHint: string): Preview { - const el = document.createElement("div"); - el.className = "preview-host"; - const a = document.createElement("img"); - const b = document.createElement("img"); - const hint = text("hint", initialHint); - const placeholder = document.createElement("div"); - placeholder.className = "preview-placeholder"; - placeholder.append(mark(), hint.el); - el.append(a, b, placeholder); - let visible = a; - - return { - el, - show(url, onShown) { - placeholder.style.display = "none"; - const incoming = visible === a ? b : a; - const outgoing = visible; - const flip = () => { - incoming.style.opacity = "1"; - outgoing.style.opacity = "0"; - visible = incoming; - onShown(); - }; - incoming.src = url; - if (typeof incoming.decode === "function") { - incoming.decode().then(flip).catch(flip); - } else { - incoming.addEventListener("load", function once() { - incoming.removeEventListener("load", once); - flip(); - }); - } - }, - waiting(hintText) { - a.style.opacity = "0"; - b.style.opacity = "0"; - hint.set(hintText); - placeholder.style.display = "flex"; - }, - }; -} diff --git a/packages/extension/media/ui/components/pulseplot.ts b/packages/extension/media/ui/components/pulseplot.ts new file mode 100644 index 00000000..678e5420 --- /dev/null +++ b/packages/extension/media/ui/components/pulseplot.ts @@ -0,0 +1,190 @@ +// Pulseplot component (#66) — client-side render of the live pulse from raw +// data, matching plot_pulse's stacked layout: one panel per drive, step +// (stairs) rendering — faithful to zero-order hold — bounds band with dashed +// limits, per-drive labels, shared time axis labeled on the bottom panel only. +// +// Hand-rolled SVG (series are tens of points; >512 knots per drive decimated +// by stride — min/max-preserving decimation is the GA refinement). Colors ride +// the fixed-order categorical series tokens in brand.css; identity is carried +// by a colored chip beside the label, text stays in text tokens. + +import { defineStyle } from "../style"; +import { text } from "../atoms/text"; + +defineStyle("pulseplot", ` + .pulseplot { display: flex; flex-direction: column; gap: var(--space-xs); + flex: 1 1 240px; min-width: 0; min-height: 240px; + background: var(--bg-plot); + border: var(--border-width) solid var(--border-color); + border-radius: var(--border-radius); padding: var(--space-sm); } + .pulseplot .pp-panel { flex: 1; min-height: 0; display: flex; flex-direction: column; } + .pulseplot .pp-head { display: flex; align-items: center; gap: var(--space-sm); } + .pulseplot .pp-chip { width: var(--square-dot); height: var(--square-dot); border-radius: 2px; } + .pulseplot svg { flex: 1; width: 100%; min-height: 0; display: block; } + .pulseplot .pp-step { fill: none; stroke-width: 2; vector-effect: non-scaling-stroke; } + .pulseplot .pp-limit { stroke: var(--color-dim); stroke-width: 1; stroke-dasharray: 4 3; + vector-effect: non-scaling-stroke; } + .pulseplot .pp-zero { stroke: var(--plot-grid); stroke-width: 1; vector-effect: non-scaling-stroke; } + .pulseplot .pp-band { fill: var(--plot-band); } + .pulseplot .pp-axis { display: flex; justify-content: space-between; align-items: baseline; } + .pulseplot.pp-empty .pp-panel, .pulseplot.pp-empty .pp-axis { display: none; } + .pulseplot .pp-hint { place-self: center; margin: auto; opacity: 0.55; font-style: italic; } + .pulseplot:not(.pp-empty) .pp-hint { display: none; } +`); + +const MAX_KNOTS = 512; // above this, stride-decimate before rendering +const W = 1000; // viewBox coordinate space (preserveAspectRatio=none) +const H = 100; +const PAD = 0.08; // y-domain padding around the bounds band + +export interface PulsePlotMeta { drives: number; knots: number; labels: string[]; bounds: [number, number][] } +export interface PulsePlotRecord { iter: number; dt: number; values: number[][] } + +interface Panel { + el: HTMLDivElement; + svg: SVGSVGElement; + step: SVGPathElement; + band: SVGRectElement; + limits: [SVGLineElement, SVGLineElement]; + zero: SVGLineElement; + bounds: [number, number]; +} + +const svgEl = (tag: K): SVGElementTagNameMap[K] => + document.createElementNS("http://www.w3.org/2000/svg", tag); + +export interface PulsePlot { + el: HTMLDivElement; + meta(m: PulsePlotMeta): void; + update(r: PulsePlotRecord): void; + clear(): void; + /** Empty-state with a hint — idle / warming / no-pulse-data messaging. */ + waiting(hint: string): void; +} + +export function pulseplot(idleHint = "No pulse data yet."): PulsePlot { + const el = document.createElement("div"); + el.className = "pulseplot pp-empty"; + const hint = text("pp-hint", idleHint); + el.append(hint.el); + + const axis = document.createElement("div"); + axis.className = "pp-axis"; + const t0Label = text("label-k", "0"); + const xLabel = text("label-k", "Time"); + const tEndLabel = text("label-k", ""); + axis.append(t0Label.el, xLabel.el, tEndLabel.el); + + let panels: Panel[] = []; + let currentMeta: PulsePlotMeta | undefined; + + function meta(m: PulsePlotMeta): void { + currentMeta = m; + for (const p of panels) p.el.remove(); + axis.remove(); + panels = m.labels.map((label, i) => { + const panel = document.createElement("div"); + panel.className = "pp-panel"; + + const head = document.createElement("div"); + head.className = "pp-head"; + const chip = document.createElement("span"); + chip.className = "pp-chip"; + chip.style.background = `var(--series-${(i % 4) + 1})`; + head.append(chip, text("label-k", label).el); + + const svg = svgEl("svg"); + svg.setAttribute("viewBox", `0 0 ${W} ${H}`); + svg.setAttribute("preserveAspectRatio", "none"); + + const [lo, hi] = m.bounds[i]; + const y = yScale(lo, hi); + const band = svgEl("rect"); + band.setAttribute("class", "pp-band"); + band.setAttribute("x", "0"); band.setAttribute("width", String(W)); + band.setAttribute("y", String(y(hi))); + band.setAttribute("height", String(y(lo) - y(hi))); + + const mkLine = (cls: string, v: number): SVGLineElement => { + const line = svgEl("line"); + line.setAttribute("class", cls); + line.setAttribute("x1", "0"); line.setAttribute("x2", String(W)); + line.setAttribute("y1", String(y(v))); line.setAttribute("y2", String(y(v))); + return line; + }; + const limits: [SVGLineElement, SVGLineElement] = [mkLine("pp-limit", hi), mkLine("pp-limit", lo)]; + const zero = mkLine("pp-zero", 0); + + const step = svgEl("path"); + step.setAttribute("class", "pp-step"); + step.setAttribute("stroke", `var(--series-${(i % 4) + 1})`); + + svg.append(band, zero, limits[0], limits[1], step); + panel.append(head, svg); + el.append(panel); + return { el: panel, svg, step, band, limits, zero, bounds: m.bounds[i] }; + }); + el.append(axis); // shared time axis, bottom panel only + } + + function update(r: PulsePlotRecord): void { + if (!currentMeta || r.values.length !== panels.length) return; + el.classList.remove("pp-empty"); + const duration = r.dt * currentMeta.knots; + tEndLabel.set(formatT(duration)); + r.values.forEach((drive, i) => { + const p = panels[i]; + const y = yScale(p.bounds[0], p.bounds[1]); + p.step.setAttribute("d", stairsPath(decimate(drive), duration, y)); + }); + } + + function clear(): void { + el.classList.add("pp-empty"); + for (const p of panels) p.step.removeAttribute("d"); + tEndLabel.set(""); + } + + function waiting(hintText: string): void { + hint.set(hintText); + clear(); + } + + return { el, meta, update, clear, waiting }; +} + +/** y-scale: bounds band → viewBox with PAD headroom; non-finite clamps to edge. */ +function yScale(lo: number, hi: number): (v: number) => number { + const pad = PAD * (hi - lo || 1); + const min = lo - pad, max = hi + pad; + return (v) => { + const t = Number.isFinite(v) ? (v - min) / (max - min) : v > 0 ? 1 : 0; + return H - Math.min(1, Math.max(0, t)) * H; + }; +} + +/** Zero-order-hold stairs: each value held for its knot interval. */ +function stairsPath(values: number[], duration: number, y: (v: number) => number): string { + if (values.length === 0 || duration <= 0) return ""; + const x = (k: number) => (k / values.length) * W; + let d = `M0,${round(y(values[0]))}`; + for (let k = 0; k < values.length; k++) { + d += `H${round(x(k + 1))}`; + if (k + 1 < values.length) d += `V${round(y(values[k + 1]))}`; + } + return d; +} + +function decimate(values: number[]): number[] { + if (values.length <= MAX_KNOTS) return values; + const stride = Math.ceil(values.length / MAX_KNOTS); + const out: number[] = []; + for (let k = 0; k < values.length; k += stride) out.push(values[k]); + return out; +} + +const round = (v: number): number => Math.round(v * 100) / 100; + +function formatT(t: number): string { + return t >= 100 ? t.toFixed(0) : t >= 10 ? t.toFixed(1) : t.toFixed(2); +} diff --git a/packages/extension/media/ui/views/inspector.ts b/packages/extension/media/ui/views/inspector.ts index 59f9e235..8ab276b6 100644 --- a/packages/extension/media/ui/views/inspector.ts +++ b/packages/extension/media/ui/views/inspector.ts @@ -1,13 +1,17 @@ // Inspector view — pure composition of atoms/components + layout selectors. // Owns the message protocol (runlabel / iteration / warming / completed / -// refresh / ping) shared with run_inspector.ts. +// pulsemeta / pulse / ping) shared with run_inspector.ts. +// +// The live pulse renders NATIVELY from pulse data (#66) — the per-iter PNGs +// remain run-dir/archival artifacts but are no longer displayed. A run whose +// log carries no pulse lines shows a hint instead of a plot. import { defineStyle } from "../style"; import { mark } from "../atoms/icon"; import { pill } from "../atoms/pill"; import { text } from "../atoms/text"; import { metric } from "../components/metric"; -import { preview } from "../components/preview"; +import { pulseplot } from "../components/pulseplot"; defineStyle("inspector-view", ` body { margin: 0; height: 100vh; font-family: var(--text-font); @@ -16,7 +20,8 @@ defineStyle("inspector-view", ` `); const IDLE_HINT = "No solve in progress — fire one from the Amicode chat, or run “Replay demo run”."; -const WARMING_HINT = "Julia warming up — compiling the solver + plotter (~1–2 min). Frames will stream here."; +const WARMING_HINT = "Julia warming up — compiling the solver (~1–2 min). The pulse will stream here."; +const NO_DATA_HINT = "This run carries no pulse data — re-run with the current solve template to see the live pulse."; export interface InspectorView { el: HTMLElement; @@ -26,13 +31,17 @@ export interface InspectorView { export function createInspectorView(post: (msg: unknown) => void): InspectorView { const status = pill("idle"); const runLabel = text("mono small dim"); - const frames = preview(IDLE_HINT); + const pulse = pulseplot(IDLE_HINT); const hero = metric("objective", { hero: true }); const iteration = metric("iteration"); const feasibility = metric("feasibility"); const optimality = metric("optimality"); const metrics = [hero, iteration, feasibility, optimality]; + /** Whether the current run has delivered pulse data — decides the + * completed-without-data hint. Reset on warming (a NEW run started). */ + let gotPulse = false; + const brand = document.createElement("div"); brand.className = "row gap-sm brand"; brand.append(mark(), text("", "Run Inspector").el); @@ -49,7 +58,7 @@ export function createInspectorView(post: (msg: unknown) => void): InspectorView const el = document.createElement("div"); el.className = "stack pad-lg scroll-y"; el.style.height = "100vh"; - el.append(topbar, frames.el, grid); + el.append(topbar, pulse.el, grid); return { el, @@ -74,10 +83,11 @@ export function createInspectorView(post: (msg: unknown) => void): InspectorView break; } case "warming": { - // A NEW run started but has no frame yet — clear the previous run's - // plot + stats so the old iter-N image doesn't linger while the new - // solve compiles/warms up. - frames.waiting(WARMING_HINT); + // A NEW run started but has no data yet — clear the previous run's + // plot + stats so the old pulse doesn't linger while the new solve + // compiles/warms up. + gotPulse = false; + pulse.waiting(WARMING_HINT); for (const m of metrics) m.clear(); hero.label("objective"); status.set("running", "warming up"); @@ -92,11 +102,18 @@ export function createInspectorView(post: (msg: unknown) => void): InspectorView hero.label("fidelity"); hero.value((msg.fidelity as number).toFixed(5)); } + if (!gotPulse) pulse.waiting(NO_DATA_HINT); // old runs / non-emitting scripts + break; + } + case "pulsemeta": { + pulse.meta({ drives: msg.drives, knots: msg.knots, labels: msg.labels, bounds: msg.bounds }); break; } - case "refresh": { - frames.show(msg.url, () => iteration.value(String(msg.iter))); - status.set("running", "running"); // a new frame means a live solve; completion arrives via "completed" + case "pulse": { + // Plot-only: never touches the status pill (a throttled record can + // legally land after "completed"; the badge must not regress). + gotPulse = true; + pulse.update({ iter: msg.iter, dt: msg.dt, values: msg.values }); break; } } diff --git a/packages/extension/package.json b/packages/extension/package.json index 400d28b9..d1c304ae 100644 --- a/packages/extension/package.json +++ b/packages/extension/package.json @@ -1,12 +1,15 @@ { "name": "amicode-v2", "displayName": "Amicode v2", - "description": "Amico research IDE — opencode-backed chat + native VS Code surfaces for vault, catalog, and live solve inspection.", + "description": "Amico research IDE \u2014 opencode-backed chat + native VS Code surfaces for vault, catalog, and live solve inspection.", "version": "0.0.1", "publisher": "harmoniqs", "license": "Apache-2.0", "private": true, - "repository": { "type": "git", "url": "https://github.com/harmoniqs/amicode" }, + "repository": { + "type": "git", + "url": "https://github.com/harmoniqs/amicode" + }, "engines": { "vscode": "^1.95.0" }, @@ -117,7 +120,8 @@ "test:smoke": "node test/boot_smoke.mjs", "fetch:opencode": "node scripts/fetch_opencode.mjs", "healthcheck": "node scripts/healthcheck.mjs", - "package": "pnpm --filter @amicode/amico-run build && pnpm run build && pnpm run fetch:opencode && vsce package --no-dependencies --allow-missing-repository -o amicode.vsix" + "package": "pnpm --filter @amicode/amico-run build && pnpm run build && pnpm run fetch:opencode && vsce package --no-dependencies --allow-missing-repository -o amicode.vsix", + "dev:pulseplot": "esbuild dev/pulseplot_harness/main.ts --bundle --format=iife --outfile=dev/pulseplot_harness/main.js && open dev/pulseplot_harness/index.html" }, "devDependencies": { "@amicode/amico-run": "workspace:*", @@ -130,4 +134,4 @@ "typescript": "^5.6.0", "vitest": "^2.1.0" } -} +} \ No newline at end of file diff --git a/packages/extension/src/extension.ts b/packages/extension/src/extension.ts index d8ecf7e4..a7171ce5 100644 --- a/packages/extension/src/extension.ts +++ b/packages/extension/src/extension.ts @@ -41,7 +41,7 @@ export async function activate(ctx: vscode.ExtensionContext): Promise { // 1. UI surfaces registerTrees(ctx); - registerRunInspector(ctx, runsRoot); + registerRunInspector(ctx); statusBar = new StatusBarManager(); ctx.subscriptions.push({ dispose: () => statusBar?.dispose() }); diff --git a/packages/extension/src/file_watcher.ts b/packages/extension/src/file_watcher.ts index 8398663b..5a960491 100644 --- a/packages/extension/src/file_watcher.ts +++ b/packages/extension/src/file_watcher.ts @@ -6,16 +6,17 @@ import { getInspector } from "./run_inspector"; import type { StatusBarManager } from "./status_bar"; import type { RunStatus } from "./types"; import { - AMICODE_ITER_RE, ITER_PNG_RE, ingestRunDir, readTomlSafe, parseAmicoNum, SinkDedup, - type IterRecord, type RunCompletion, type PromoteInfo, type RunSink, + AMICODE_ITER_RE, ingestRunDir, readTomlSafe, parseAmicoNum, PulseStream, SinkDedup, + type IterRecord, type PulseEvent, type RunCompletion, type PromoteInfo, type RunSink, } from "./run_dir_reader"; // ============================================================================ // RunsRootWatcher — watches the β.1 run-dir contract and drives the Inspector // + status bar. Follows the `latest` symlink; for the active run it reads: // run.toml → run identity (run_id, lab_id), written FIRST -// iter_.png → live plot frames (unbounded digits) -// run.log → AMICODE_ITER lines → live stats row +// run.log → AMICODE_ITER lines → live stats row · AMICODE_PULSE +// lines → native live pulse plot (#66; iter_.png stays a +// run-dir/archival artifact but is no longer displayed) // result.toml → fidelity (display + promote gate), atomic // FINISHED → authoritative terminal signal {status, exit_code} // @@ -37,6 +38,9 @@ class LiveRunSink implements RunSink { /** Newest-wins guard: frame display vs log-line iters tracked separately so the * log high-water mark can't suppress lagging frames (see SinkDedup). */ private readonly dedup = new SinkDedup(); + /** Live pulse-line gate (#66). Re-armed by replayed meta events so tailed + * records after a mid-flight switch keep their governing shape. */ + private readonly pulses = new PulseStream(); constructor( private readonly opts: RunsRootWatcherOptions, private readonly runId: string, @@ -45,10 +49,6 @@ class LiveRunSink implements RunSink { private readonly promotedRuns: Set, ) {} - image(fsPath: string, iter: number): void { - if (!this.dedup.acceptFrame(iter)) return; // dedup on FRAMES only — see SinkDedup - getInspector()?.setImageSource(fsPath, iter); - } iter(rec: IterRecord): void { this.dedup.noteIter(rec.iter); getInspector()?.postIterationRecord(rec); @@ -74,6 +74,17 @@ class LiveRunSink implements RunSink { this.opts.channel.appendLine(`[runs] see ${path.join(c.runDir, "run.log")}`); } } + pulse(e: PulseEvent): void { + if (e.type === "meta") this.pulses.arm(e.meta); + getInspector()?.postPulse(e); + } + /** Tail path (#66 AC6): feed a raw run.log line through the pulse gate and + * forward any accepted event. On a live run this is the ONLY meta carrier — + * ingest ran against an empty log. */ + pulseLine(line: string): void { + const e = this.pulses.onLine(line); + if (e) this.pulse(e); + } promote(info: PromoteInfo): void { if (this.promotedRuns.has(info.runId)) return; this.promotedRuns.add(info.runId); @@ -102,11 +113,12 @@ export class RunsRootWatcher implements vscode.Disposable { * launch-follows-latest. */ private readonly promotedRuns = new Set(); /** Polling backstop. macOS fs.watch (FSEvents) coalesces and silently drops - * events — especially under load — so the symlink-follow + per-frame watches - * miss `latest` swings and `iter_*.png` creations, leaving the inspector - * stuck (no live frames). A cheap periodic rescan guarantees delivery; the - * fs.watch paths stay for low latency. All sinks are idempotent (frame dedup - * by iter, finishedSeen, log byte-offset), so double-delivery is harmless. */ + * events — especially under load — so the symlink-follow + run-dir watches + * can miss `latest` swings and FINISHED, and the log tailer's change events + * can go quiet. The periodic tick re-resolves `latest`, re-checks FINISHED, + * and pokes the tailer (which self-attaches if run.log appeared unseen); the + * fs.watch paths stay for low latency. All sinks are idempotent + * (finishedSeen, log byte-offset), so double-delivery is harmless. */ private poll?: NodeJS.Timeout; private static readonly POLL_MS = 700; @@ -140,16 +152,6 @@ export class RunsRootWatcher implements vscode.Disposable { if (fs.existsSync(path.join(this.opts.runsRoot, "latest"))) this.followLatest(); const runDir = this.activeRunDir; if (!runDir || !this.sink) return; - // Deliver only the NEWEST frame this tick — frames produced between two - // ticks are intentionally skipped. The inspector shows the latest pulse, - // not an animation, so a coalesced frame is no loss (and the fs.watch path - // still catches most frames at low latency). Not a dropped-frame bug. - let newest = -1, newestPath: string | undefined; - for (const f of fs.readdirSync(runDir)) { - const m = ITER_PNG_RE.exec(f); - if (m) { const k = parseInt(m[1], 10); if (k > newest) { newest = k; newestPath = path.join(runDir, f); } } - } - if (newestPath) this.sink.image(newestPath, newest); // deduped by lastFrameIter if (!this.finishedSeen && fs.existsSync(path.join(runDir, "FINISHED"))) { this.finishedSeen = true; this.onFinished(runDir); } @@ -201,18 +203,15 @@ export class RunsRootWatcher implements vscode.Disposable { catch (err) { this.opts.channel.appendLine(`[runs] replay failed: ${(err as Error).message}`); } this.finishedSeen = finishedAtSwitch; - // Fresh run (manifest but no frames/FINISHED yet) → Julia/Makie warming up; - // show that instead of an idle panel so the ~minute cold start isn't read as frozen. - const hasFrame = fs.readdirSync(runDir).some((f) => ITER_PNG_RE.test(f)); - if (!finishedAtSwitch && !hasFrame) getInspector()?.setWarmingUp(); + // Fresh unfinished run → Julia warming up; show that instead of an idle + // panel so the ~minute cold start isn't read as frozen. The view swaps the + // hint for the plot when the first pulse record arrives. + if (!finishedAtSwitch) getInspector()?.setWarmingUp(); - // Incremental: new iter PNGs + FINISHED. + // Incremental: FINISHED (pulse/iter lines arrive via the log tailer). this.activeRunWatcher = fs.watch(runDir, { persistent: false }, (_e, filename) => { if (!filename) return; - const fp = path.join(runDir, filename); - if (!fs.existsSync(fp)) return; - const m = ITER_PNG_RE.exec(filename); - if (m) { this.sink?.image(fp, parseInt(m[1], 10)); return; } + if (!fs.existsSync(path.join(runDir, filename))) return; if (filename === "FINISHED" && !this.finishedSeen) { this.finishedSeen = true; this.onFinished(runDir); } }); @@ -224,7 +223,8 @@ export class RunsRootWatcher implements vscode.Disposable { channel: this.opts.channel, onLine: (line) => { const m = AMICODE_ITER_RE.exec(line); - if (m) this.sink?.iter({ iter: +m[1], f_val: parseAmicoNum(m[2]), inf_pr: parseAmicoNum(m[3]), inf_du: parseAmicoNum(m[4]) }); + if (m) { this.sink?.iter({ iter: +m[1], f_val: parseAmicoNum(m[2]), inf_pr: parseAmicoNum(m[3]), inf_du: parseAmicoNum(m[4]) }); return; } + this.sink?.pulseLine(line); }, }); this.logTailer.start(); @@ -270,10 +270,14 @@ class LogTailer implements vscode.Disposable { constructor(private readonly opts: LogTailerOptions) {} - /** Backstop drain (called by the watcher's poll). No-op until attach() has set - * the start offset, so it never re-reads lines ingestRunDir already replayed. */ + /** Backstop drain (called by the watcher's poll). Attaches first if run.log + * has appeared since start() (the 250ms retry timer may not have fired yet — + * same coalesced-FSEvents rationale as the poll itself). attach() sets the + * start offset, so it never re-reads lines ingestRunDir already replayed. */ poke(): void { - if (this.attached && !this.disposed) this.drain(); + if (this.disposed) return; + if (!this.attached && fs.existsSync(this.opts.path)) this.attach(); + if (this.attached) this.drain(); } start(): void { @@ -293,7 +297,7 @@ class LogTailer implements vscode.Disposable { } private attach(): void { - if (this.disposed) return; + if (this.disposed || this.attached) return; // Start where ingestRunDir stopped reading (startOffset), not at current EOF — // otherwise lines appended between the replay read and this attach are lost. this.offset = this.opts.startOffset ?? 0; diff --git a/packages/extension/src/opencode_paths.ts b/packages/extension/src/opencode_paths.ts index 5861e6d0..00d08569 100644 --- a/packages/extension/src/opencode_paths.ts +++ b/packages/extension/src/opencode_paths.ts @@ -22,11 +22,9 @@ export function resolveRunsRoot(configValue: string): string { return v; } -/** Local resource roots the inspector webview may load images from. Includes - * the runs-root so live iter_*.png under ~/.amico/runs/... are not CSP-blocked (Q69). */ -export function inspectorResourceRootDirs(extensionRoot: string, runsRoot: string): string[] { - // dist + media (extension assets) and runsRoot (live iter_*.png). No "/tmp": - // the inspector only renders run-dir frames under runsRoot, so a blanket /tmp - // root needlessly broadened the webview's allowed file roots. - return [join(extensionRoot, "dist"), join(extensionRoot, "media"), runsRoot]; +/** Local resource roots the inspector webview may load. Extension assets only + * (script bundle + stylesheets) — the view renders the pulse natively from + * message data (#66), so no run-dir file access is granted. */ +export function inspectorResourceRootDirs(extensionRoot: string): string[] { + return [join(extensionRoot, "dist"), join(extensionRoot, "media")]; } diff --git a/packages/extension/src/run_dir_reader.ts b/packages/extension/src/run_dir_reader.ts index 538af190..c07f5acc 100644 --- a/packages/extension/src/run_dir_reader.ts +++ b/packages/extension/src/run_dir_reader.ts @@ -16,8 +16,6 @@ const NUM = String.raw`-?(?:Inf|NaN|\d+(?:\.\d+)?(?:[eE][-+]?\d+)?)`; export const AMICODE_ITER_RE = new RegExp( String.raw`^AMICODE_ITER\s+iter=(\d+)\s+f=(${NUM})\s+inf_pr=(${NUM})\s+inf_du=(${NUM})\s*$`, ); -export const ITER_PNG_RE = /^iter_(\d+)\.png$/; // unbounded digits (β.1 contract) - /** Parse an AMICODE_ITER numeric field, mapping Julia's Inf/NaN to JS values. */ export function parseAmicoNum(s: string): number { if (s === "Inf") return Infinity; @@ -26,6 +24,86 @@ export function parseAmicoNum(s: string): number { return Number(s); } +// --------------------------------------------------------------------------- +// Pulse-line grammar (#66) — additive lines in the run.log stdout tee feeding +// client-side pulse rendering. Labels are double-quoted strings excluding +// quotes and commas; bounds are lo:hi pairs, one per drive. +// --------------------------------------------------------------------------- + +export const AMICODE_PULSE_META_RE = new RegExp( + String.raw`^AMICODE_PULSE_META\s+drives=(\d+)\s+knots=(\d+)\s+labels=((?:"[^",]*")(?:,"[^",]*")*)\s+bounds=(${NUM}:${NUM}(?:,${NUM}:${NUM})*)\s*$`, +); + +export interface PulseMeta { drives: number; knots: number; labels: string[]; bounds: [number, number][] } + +/** Parse an AMICODE_PULSE_META line. Returns undefined for anything malformed. */ +export function parsePulseMetaLine(line: string): PulseMeta | undefined { + const m = AMICODE_PULSE_META_RE.exec(line); + if (!m) return undefined; + const labels = [...m[3].matchAll(/"([^",]*)"/g)].map((x) => x[1]); + const bounds = m[4].split(",").map((pair) => { + const [lo, hi] = pair.split(":"); + return [parseAmicoNum(lo), parseAmicoNum(hi)] as [number, number]; + }); + return { drives: parseInt(m[1], 10), knots: parseInt(m[2], 10), labels, bounds }; +} + +export const AMICODE_PULSE_RE = new RegExp( + String.raw`^AMICODE_PULSE\s+iter=(\d+)\s+dt=(${NUM})\s+a=(${NUM}(?:,${NUM})*(?:;${NUM}(?:,${NUM})*)*)\s*$`, +); + +export interface PulseRecord { iter: number; dt: number; values: number[][] } + +/** Parse an AMICODE_PULSE record line. Returns undefined for anything malformed. */ +export function parsePulseRecordLine(line: string): PulseRecord | undefined { + const m = AMICODE_PULSE_RE.exec(line); + if (!m) return undefined; + const values = m[3].split(";").map((drive) => drive.split(",").map(parseAmicoNum)); + return { iter: parseInt(m[1], 10), dt: parseAmicoNum(m[2]), values }; +} + +export type PulseEvent = + | { type: "meta"; meta: PulseMeta } + | { type: "record"; record: PulseRecord }; + +/** Cross-line policy for the pulse stream — the single gate BOTH delivery + * paths (replay ingest, live tail) feed lines through. Policy (#66 AC4): + * records before any meta are dropped (nothing to interpret them against); + * the LAST meta wins (duplicate metas are expected — the tailer re-reads from + * offset 0 on truncation) and its shape governs subsequent records; records + * whose drive/knot counts disagree with the governing meta are ignored; a + * meta whose own label/bounds counts disagree with drives= is malformed and + * changes nothing. */ +export class PulseStream { + private meta?: PulseMeta; + + /** Arm the stream with an externally-delivered meta (replay ingest runs its + * own stream; the live sink re-arms from the forwarded meta event so tailed + * records that follow a replayed meta still have a governing shape). */ + arm(meta: PulseMeta): void { + this.meta = meta; + } + + /** Feed one run.log line; returns a routable event or undefined (non-pulse + * lines, malformed lines, and policy-dropped records). */ + onLine(line: string): PulseEvent | undefined { + const meta = parsePulseMetaLine(line); + if (meta) { + if (meta.labels.length !== meta.drives || meta.bounds.length !== meta.drives) return undefined; + this.meta = meta; + return { type: "meta", meta }; + } + const record = parsePulseRecordLine(line); + if (record) { + if (!this.meta) return undefined; // record before meta — nothing to interpret it against + if (record.values.length !== this.meta.drives) return undefined; + if (record.values.some((d) => d.length !== this.meta!.knots)) return undefined; + return { type: "record", record }; + } + return undefined; + } +} + export interface IterRecord { iter: number; f_val: number; inf_pr: number; inf_du: number } export interface RunCompletion { runId: string; runDir: string; status: RunStatus; fidelity?: number } export interface PromoteInfo { runId: string; runDir: string; fidelity: number } @@ -33,34 +111,23 @@ export interface PromoteInfo { runId: string; runDir: string; fidelity: number } /** Where ingestRunDir routes its findings. The live impl carries the * newest-wins + promote-once guards; the test impl is plain spies. */ export interface RunSink { - image(fsPath: string, iter: number): void; iter(rec: IterRecord): void; run(c: RunCompletion): void; promote(info: PromoteInfo): void; + /** Pulse-stream events (#66): a meta or a policy-accepted record. Replay + * forwards meta + the NEWEST record only; the live tail forwards each. */ + pulse(e: PulseEvent): void; } -/** Newest-wins guard for the live sink. Frame display and log-line iters are - * tracked SEPARATELY on purpose: run.log `AMICODE_ITER` lines arrive once per - * iteration and race ahead of the PNG frames (the solver logs `iter=k`, *then* - * writes `iter_k.png`). If frame dedup shared the log high-water mark, every - * frame would test `k <= high` and be dropped — leaving the inspector blank - * for the whole solve. So frames dedup only against prior FRAMES. - * Pure + vscode-free so it's unit-testable (LiveRunSink delegates to it). */ +/** Iteration high-water mark for the live sink — drives the status bar's + * "iter N" and the completion record. Pure + vscode-free (unit-testable). */ export class SinkDedup { - private lastFrameIter = -1; private latestIter = -1; - /** True if this frame is newer than the last DISPLAYED frame (→ forward it). */ - acceptFrame(iter: number): boolean { - if (iter <= this.lastFrameIter) return false; - this.lastFrameIter = iter; - if (iter > this.latestIter) this.latestIter = iter; - return true; - } - /** Record a log-line iter — advances the high-water mark only, never frames. */ + /** Record a log-line iter — advances the high-water mark. */ noteIter(iter: number): void { if (iter > this.latestIter) this.latestIter = iter; } - /** Highest iter seen from any source (drives the status bar / completion). */ + /** Highest iteration seen. */ get high(): number { return this.latestIter; } } @@ -79,24 +146,27 @@ export function ingestRunDir(runDir: string, sink: RunSink, promoteThreshold = 0 if (!manifest || !validateManifest(manifest).ok) return 0; // no valid manifest → not a run dir yet const runId = String(manifest.run_id); - // newest iter PNG - let newest = -1; let newestPath: string | undefined; - for (const f of fs.readdirSync(runDir)) { - const m = ITER_PNG_RE.exec(f); - if (m) { const k = parseInt(m[1], 10); if (k > newest) { newest = k; newestPath = path.join(runDir, f); } } - } - if (newestPath) sink.image(newestPath, newest); - // run.log body → iter records (replay; the live tailer handles appended lines) let logBody: string | undefined; try { logBody = fs.readFileSync(path.join(runDir, "run.log"), "utf8"); } catch { /* none yet */ } let logBytes = 0; if (logBody) { logBytes = Buffer.byteLength(logBody, "utf8"); + // Pulse replay is newest-wins: a finished run's full history would burst + // thousands of identical-end-state messages at the webview. Forward the + // governing meta + the last policy-accepted record only. + const pulses = new PulseStream(); + let pulseMeta: PulseEvent | undefined; + let newestPulse: PulseEvent | undefined; for (const line of logBody.split("\n")) { const m = AMICODE_ITER_RE.exec(line); - if (m) sink.iter({ iter: +m[1], f_val: parseAmicoNum(m[2]), inf_pr: parseAmicoNum(m[3]), inf_du: parseAmicoNum(m[4]) }); + if (m) { sink.iter({ iter: +m[1], f_val: parseAmicoNum(m[2]), inf_pr: parseAmicoNum(m[3]), inf_du: parseAmicoNum(m[4]) }); continue; } + const e = pulses.onLine(line); + if (e?.type === "meta") { pulseMeta = e; newestPulse = undefined; } // new meta governs; stale records don't cross it + else if (e?.type === "record") newestPulse = e; } + if (pulseMeta) sink.pulse(pulseMeta); + if (newestPulse) sink.pulse(newestPulse); } // FINISHED is the authoritative terminal signal diff --git a/packages/extension/src/run_inspector.ts b/packages/extension/src/run_inspector.ts index ad5ff2eb..60009024 100644 --- a/packages/extension/src/run_inspector.ts +++ b/packages/extension/src/run_inspector.ts @@ -1,6 +1,7 @@ import * as vscode from "vscode"; import * as path from "node:path"; import { inspectorResourceRootDirs } from "./opencode_paths"; +import type { PulseEvent, PulseMeta, PulseRecord } from "./run_dir_reader"; // ============================================================================ // Run Inspector — bottom-panel webview that shows the live pulse-plot stream @@ -11,20 +12,16 @@ import { inspectorResourceRootDirs } from "./opencode_paths"; // path). Keeps the throttled image swap + setImageSource / postIteration API. // ============================================================================ -const REFRESH_INTERVAL_MS = 200; // 5 Hz cap on PNG refresh +const REFRESH_INTERVAL_MS = 200; // 5 Hz cap on pulse-record refresh let INSPECTOR: InspectorView | undefined; class InspectorView implements vscode.WebviewViewProvider { private view?: vscode.WebviewView; - private pendingRefresh?: { fsPath: string; iter: number; isFinal: boolean }; - private refreshTimer?: NodeJS.Timeout; - /** Set BEFORE the webview is materialized — replayed in resolveWebviewView. */ - private bufferedImage?: { fsPath: string; iter: number; isFinal: boolean }; /** Terminal state that arrived before the webview existed (e.g. on launch the * watcher follows `latest` → a finished run completes before the panel is - * opened). Replayed after the buffered image so the badge isn't stuck "running". */ + * opened). Replayed after buffered pulse data so the badge isn't stuck "running". */ private bufferedCompletion?: { status: string; fidelity?: number }; /** A run started but hasn't emitted its first frame yet (Julia warming up). * Buffered so the warming state shows even if the panel opens late. */ @@ -32,43 +29,53 @@ class InspectorView implements vscode.WebviewViewProvider { /** Run label (runId) for the topbar — buffered so it shows even if the panel * opens after the run was selected. */ private bufferedRunLabel?: string; - - constructor(private readonly ctx: vscode.ExtensionContext, private readonly runsRoot: string) {} + /** Pulse events that arrived before the webview existed (#66 AC7). Unlike + * PNGs (re-offered by the poll forever), the log line is the canonical + * signal — dropped means gone. Meta + NEWEST record only. */ + private bufferedPulseMeta?: PulseMeta; + private bufferedPulseRecord?: PulseRecord; + /** 5 Hz throttle for live pulse records — same REFRESH_INTERVAL_MS policy as + * PNG frames: leading edge posts, the window coalesces (newest wins), the + * trailing edge flushes. Meta is never throttled (once per run). */ + private pulseTimer?: NodeJS.Timeout; + private pendingPulse?: PulseRecord; + + constructor(private readonly ctx: vscode.ExtensionContext) {} resolveWebviewView(view: vscode.WebviewView): void { this.view = view; - // Q69: include the runs-root, else iter_*.png under ~/.amico/runs/... is - // CSP-blocked → permanently blank inspector. - const resourceRoots: vscode.Uri[] = inspectorResourceRootDirs(this.ctx.extensionUri.fsPath, this.runsRoot) - .map((d) => vscode.Uri.file(d)); - for (const f of vscode.workspace.workspaceFolders ?? []) { - resourceRoots.push(f.uri); - } view.webview.options = { enableScripts: true, - localResourceRoots: resourceRoots, + // Extension assets only — the view renders from message data (#66); + // no run-dir or workspace file access is needed. + localResourceRoots: inspectorResourceRootDirs(this.ctx.extensionUri.fsPath).map((d) => vscode.Uri.file(d)), }; view.webview.html = this.renderHtml(view.webview); - view.onDidDispose(() => { this.view = undefined; this.clearTimer(); }); + view.onDidDispose(() => { this.view = undefined; this.clearPulseTimer(); }); // Topbar run label — replay first so it's set regardless of run state. if (this.bufferedRunLabel) { view.webview.postMessage({ type: "runlabel", text: this.bufferedRunLabel }); this.bufferedRunLabel = undefined; } - // A run is warming up (no frame yet) — show that until the first frame. - if (this.bufferedWarming && !this.bufferedImage) { + // A run is warming up (no data yet) — show that until the first record. + if (this.bufferedWarming) { this.bufferedWarming = false; view.webview.postMessage({ type: "warming" }); } - // Replay the most recent pending image once the webview is alive. - if (this.bufferedImage) { - this.pendingRefresh = this.bufferedImage; - this.bufferedImage = undefined; - this.flushRefresh(); + // Replay buffered pulse events (#66): meta first, then the newest record — + // and BEFORE the buffered completion below, so terminal state stays the + // last word the webview hears. + if (this.bufferedPulseMeta) { + view.webview.postMessage({ type: "pulsemeta", ...this.bufferedPulseMeta }); + this.bufferedPulseMeta = undefined; + } + if (this.bufferedPulseRecord) { + view.webview.postMessage({ type: "pulse", ...this.bufferedPulseRecord }); + this.bufferedPulseRecord = undefined; } // Then replay a terminal state if the run already finished — after the - // image so "converged"/"failed" wins over the replayed frame's "running". + // pulse data so "converged"/"failed" is the last word the webview hears. if (this.bufferedCompletion) { const c = this.bufferedCompletion; this.bufferedCompletion = undefined; @@ -78,30 +85,12 @@ class InspectorView implements vscode.WebviewViewProvider { // -------- public surface used by RunsRootWatcher -------- - setImageSource(fsPath: string, iter: number, isFinal: boolean = false): void { - if (!this.view) { - // Webview not materialized yet — keep only the most recent frame and - // ask VS Code to open the panel. resolveWebviewView will replay it. - this.bufferedImage = { fsPath, iter, isFinal }; - vscode.commands.executeCommand("amicode.runInspector.focus") - .then(undefined, () => undefined); - return; - } - this.pendingRefresh = { fsPath, iter, isFinal }; - if (isFinal) { - this.clearTimer(); - this.flushRefresh(); - return; - } - if (this.refreshTimer) return; - this.refreshTimer = setTimeout(() => { - this.refreshTimer = undefined; - this.flushRefresh(); - }, REFRESH_INTERVAL_MS); - } - postIterationRecord(rec: { iter: number; f_val: number; inf_pr: number; inf_du: number }): void { - if (!this.view) return; // ok to drop iter records if not visible — image stream is the canonical signal + // Ok to drop iter records pre-materialization: the stats row refreshes on + // the next record (seconds away), and switchToRun's log replay re-ingests + // history whenever the run is re-selected. (Pulse events, by contrast, are + // buffered above — the log line is their only delivery.) + if (!this.view) return; this.view.webview.postMessage({ type: "iteration", iter: rec.iter, @@ -114,25 +103,19 @@ class InspectorView implements vscode.WebviewViewProvider { }); } - /** Terminal-state signal so the badge stops saying "running". The watcher - * streams frames without an isFinal marker (it can't know which frame is - * last mid-solve), so completion is delivered separately — on live finish - * AND when switching to an already-finished run. Flush any pending frame - * first so this is the last word the webview hears for the run. */ + /** Terminal-state signal so the badge stops saying "running" — on live + * finish AND when switching to an already-finished run. */ postCompletion(status: string, fidelity?: number): void { if (!this.view) { - // Panel not open yet — stash; resolveWebviewView replays it after the image. + // Panel not open yet — stash; resolveWebviewView replays it after pulse data. this.bufferedCompletion = { status, fidelity }; return; } - this.clearTimer(); - this.flushRefresh(); this.view.webview.postMessage({ type: "completed", status, fidelity }); } - /** A run started but has no frame yet (Julia/Makie warming up) — show that - * instead of an idle panel, so a ~minute of cold start doesn't read as frozen. - * Replaced by the first frame (the refresh handler hides the placeholder). */ + /** A run started but has no data yet (Julia warming up) — show that instead + * of an idle panel, so a ~minute of cold start doesn't read as frozen. */ setWarmingUp(): void { if (!this.view) { this.bufferedWarming = true; @@ -142,6 +125,28 @@ class InspectorView implements vscode.WebviewViewProvider { this.view.webview.postMessage({ type: "warming" }); } + /** Pulse-stream event (#66): forwarded to the view as pulsemeta/pulse + * messages. Buffering for late materialization lands with AC7. */ + postPulse(e: PulseEvent): void { + if (!this.view) { + // Buffer (newest record wins) — replayed in resolveWebviewView. + if (e.type === "meta") this.bufferedPulseMeta = e.meta; + else this.bufferedPulseRecord = e.record; + return; + } + if (e.type === "meta") { this.view.webview.postMessage({ type: "pulsemeta", ...e.meta }); return; } + if (this.pulseTimer) { this.pendingPulse = e.record; return; } // window open — coalesce, newest wins + this.view.webview.postMessage({ type: "pulse", ...e.record }); + this.pulseTimer = setTimeout(() => { + this.pulseTimer = undefined; + if (this.pendingPulse && this.view) { + const rec = this.pendingPulse; + this.pendingPulse = undefined; + this.postPulse({ type: "record", record: rec }); + } + }, REFRESH_INTERVAL_MS); + } + /** Set the topbar run label (runId). Buffered until the webview materializes. */ setRunLabel(label: string): void { if (!this.view) { this.bufferedRunLabel = label; return; } @@ -157,27 +162,12 @@ class InspectorView implements vscode.WebviewViewProvider { // -------- internal -------- - private clearTimer(): void { - if (this.refreshTimer) { - clearTimeout(this.refreshTimer); - this.refreshTimer = undefined; + private clearPulseTimer(): void { + if (this.pulseTimer) { + clearTimeout(this.pulseTimer); + this.pulseTimer = undefined; } - } - - private flushRefresh(): void { - if (!this.pendingRefresh || !this.view) return; - const { fsPath, iter, isFinal } = this.pendingRefresh; - this.pendingRefresh = undefined; - const uri = vscode.Uri.file(fsPath); - const webviewUri = this.view.webview.asWebviewUri(uri).toString(); - const url = webviewUri + (webviewUri.includes("?") ? "&" : "?") + "t=" + Date.now(); - this.view.webview.postMessage({ - type: "refresh", - url, - iter, - t_post: Date.now(), - isFinal, - }); + this.pendingPulse = undefined; } private renderHtml(webview: vscode.Webview): string { @@ -191,17 +181,14 @@ class InspectorView implements vscode.WebviewViewProvider { // URIs. The shell⇄view seam is pinned by inspector_view_contract.test.ts. // // style-src keeps 'unsafe-inline' deliberately — the view sets element - // .style properties at runtime (buffer opacity flips, placeholder toggle); - // those aren't CSP-governed, but keeping the grant future-proofs static - // style attrs the design lane may add. img-src carries the runs-root via - // localResourceRoots for iter-frame PNGs. + // .style properties at runtime; those aren't CSP-governed, but the grant + // future-proofs static style attrs the design lane may add. return /* html */ ` @@ -221,8 +208,8 @@ function newNonce(): string { return s; } -export function registerRunInspector(ctx: vscode.ExtensionContext, runsRoot: string): InspectorView { - INSPECTOR = new InspectorView(ctx, runsRoot); +export function registerRunInspector(ctx: vscode.ExtensionContext): InspectorView { + INSPECTOR = new InspectorView(ctx); ctx.subscriptions.push( vscode.window.registerWebviewViewProvider("amicode.runInspector", INSPECTOR, { webviewOptions: { retainContextWhenHidden: true }, diff --git a/packages/extension/templates/solve_template.jl b/packages/extension/templates/solve_template.jl index 0f93d012..c971dac2 100644 --- a/packages/extension/templates/solve_template.jl +++ b/packages/extension/templates/solve_template.jl @@ -40,6 +40,52 @@ prob = hasproperty(qcp, :prob) ? qcp.prob : qcp const PLOT_EVERY = 6 live_plot = LivePulsePlotCallback(qtraj, prob.trajectory; every = PLOT_EVERY, save_dir = ".") +# Pulse-data telemetry (#66, prototype-grade): raw knot values per iteration as +# AMICODE_PULSE lines on stdout (→ run.log), riding the SAME solver-agnostic +# (primal, iter) hook as the live plot — the inspector renders them natively. +# Additive to the run-dir contract: consumers that don't know the lines ignore +# them. META once (shape + bounds), then one record per iteration (~1KB). +struct PulseEmitCallback <: AbstractIntermediateCallback + inner::Any # delegate (the live plot) — fires first, keeps the PNG cadence + traj::Any # prob.trajectory — synced from the primal, then read +end +function (cb::PulseEmitCallback)(primal, iter) + ok = cb.inner(primal, iter) + try + traj = cb.traj + expected = traj.dim * traj.N + traj.global_dim + if length(primal) == expected + # Own sync — the delegate only updates the trajectory on its plot cadence. + # Qualified: `update!` is also exported by Makie/CairoMakie — the + # unqualified binding is ambiguous once the plotting stack loads. + if traj.global_dim > 0 + Piccolo.NamedTrajectories.update!(traj, collect(view(primal, 1:expected)); type = :both) + else + Piccolo.NamedTrajectories.update!(traj, collect(view(primal, 1:(traj.dim * traj.N))); type = :data) + end + # Drive component name differs by problem flavor (:u current, :a + # legacy). Membership check (not `something(traj.u, traj.a)`): it + # keeps the fallback reachable without leaning on property access + # returning `nothing` for missing components (review nit, #67). + A = :u in traj.names ? traj.u : (:a in traj.names ? traj.a : missing) + A === missing && error("no drive component (:u/:a) on trajectory") + vals = join((join((@sprintf("%.6g", v) for v in row), ",") for row in eachrow(A)), ";") + @printf("AMICODE_PULSE iter=%d dt=%.6g a=%s\n", iter, first(Piccolo.get_timesteps(traj)), vals) + flush(stdout) + end + catch e + @warn "pulse emit failed" exception = e maxlog = 3 # never let telemetry kill the solve + end + return ok +end +pulse_emit = PulseEmitCallback(live_plot, prob.trajectory) + +let ls = join(("\"a_$i\"" for i in 1:sys.n_drives), ","), + bs = join(("$(-drive_max):$(drive_max)" for _ in 1:sys.n_drives), ",") + println("AMICODE_PULSE_META drives=$(sys.n_drives) knots=$N labels=$ls bounds=$bs") + flush(stdout) +end + # AMICODE_ITER text telemetry stays on the RAW Ipopt callback — it needs the rich # IPM state (obj_value/inf_pr/inf_du) that the agnostic `(primal, iter)` contract # doesn't carry. Both callbacks fire once per iteration (DTO composes the raw @@ -55,7 +101,7 @@ end t0 = time() solve!(qcp; max_iter = max_iter, print_level = 1, - options = IpoptOptions(intermediate_callback = live_plot), + options = IpoptOptions(intermediate_callback = pulse_emit), callback = CB.callback_factory(cb_log)) wall = time() - t0 diff --git a/packages/extension/test/inspector_view_contract.test.ts b/packages/extension/test/inspector_view_contract.test.ts index 02a869a3..c996a796 100644 --- a/packages/extension/test/inspector_view_contract.test.ts +++ b/packages/extension/test/inspector_view_contract.test.ts @@ -1,4 +1,4 @@ -import { describe, it, expect } from "vitest"; +import { afterEach, describe, it, expect, vi } from "vitest"; import { existsSync, readFileSync } from "node:fs"; import { join } from "node:path"; import { registerRunInspector } from "../src/run_inspector"; @@ -18,7 +18,7 @@ const PKG_ROOT = join(__dirname, ".."); function renderInspectorHtml(): string { const ctx = { extensionUri: { fsPath: PKG_ROOT }, subscriptions: [] as unknown[] }; - const inspector = registerRunInspector(ctx as never, "/tmp/runs-test"); + const inspector = registerRunInspector(ctx as never); let captured = ""; const view = { webview: { @@ -50,10 +50,7 @@ describe("Run Inspector shell contract (plumbing ⇄ TS-composed view)", () => { expect(styleSrc, "style-src must grant the webview source for the linked stylesheets").toContain("vscode-webview://unit"); expect(styleSrc, "style-src keeps 'unsafe-inline' for design-lane static style attrs").toContain("'unsafe-inline'"); - // iter-frame PNGs load as asWebviewUri → vscode-webview:// URIs; img-src must grant the source. - const imgSrc = html.match(/img-src([^;]*)/)?.[1] ?? ""; - expect(imgSrc, "img-src must grant the webview source for iter-frame PNGs").toContain("vscode-webview://unit"); - + expect(html, "no image grants — the view renders from message data (#66)").not.toMatch(/img-src/); expect(html).toMatch(/script-src 'nonce-/); }); @@ -63,3 +60,72 @@ describe("Run Inspector shell contract (plumbing ⇄ TS-composed view)", () => { expect(brand).toMatch(/--color-accent/); }); }); + +// #66 AC7 — pulse events posted before the webview materializes must not be +// lost: the log line is the canonical signal (nothing re-delivers it, unlike +// PNGs which the poll re-offers). The host buffers meta + the NEWEST record +// and replays them on resolve, BEFORE any buffered terminal state. +describe("Run Inspector host buffering (#66 pulse events)", () => { + const META = { drives: 1, knots: 2, labels: ["a_1"], bounds: [[-0.2, 0.2]] as [number, number][] }; + + function harness() { + const ctx = { extensionUri: { fsPath: PKG_ROOT }, subscriptions: [] as unknown[] }; + const inspector = registerRunInspector(ctx as never); + const posted: Array> = []; + const view = { + webview: { + options: {}, + cspSource: "vscode-webview://unit", + asWebviewUri: (u: { fsPath?: string }) => ({ toString: () => "vscode-webview://unit/" + (u?.fsPath ?? String(u)) }), + postMessage: (m: Record) => { posted.push(m); }, + set html(_v: string) { /* ignore */ }, + get html() { return ""; }, + }, + onDidDispose: () => ({ dispose() {} }), + }; + return { inspector, view, posted }; + } + + it("buffers meta + NEWEST record pre-materialization, replays them before a buffered completion", () => { + const { inspector, view, posted } = harness(); + inspector.postPulse({ type: "meta", meta: META }); + inspector.postPulse({ type: "record", record: { iter: 1, dt: 0.2, values: [[0.1, 0.2]] } }); + inspector.postPulse({ type: "record", record: { iter: 2, dt: 0.2, values: [[0.3, 0.4]] } }); + inspector.postCompletion("completed", 0.9999); + inspector.resolveWebviewView(view as never); + + const types = posted.map((m) => m.type); + expect(types).toContain("pulsemeta"); + expect(types.filter((t) => t === "pulse")).toHaveLength(1); // newest-wins: iter 1 dropped + expect(posted.find((m) => m.type === "pulse")).toMatchObject({ iter: 2 }); + expect(types.indexOf("pulsemeta")).toBeLessThan(types.indexOf("pulse")); + expect(types.indexOf("pulse")).toBeLessThan(types.indexOf("completed")); // terminal state stays the last word + }); + + it("throttles live records to the host's 5 Hz policy — trailing edge carries the NEWEST record", () => { + vi.useFakeTimers(); + const { inspector, view, posted } = harness(); + inspector.resolveWebviewView(view as never); + inspector.postPulse({ type: "meta", meta: META }); + posted.length = 0; + inspector.postPulse({ type: "record", record: { iter: 1, dt: 0.2, values: [[0.1, 0.2]] } }); + expect(posted.map((m) => m.type)).toEqual(["pulse"]); // leading edge posts immediately + inspector.postPulse({ type: "record", record: { iter: 2, dt: 0.2, values: [[0.3, 0.4]] } }); + inspector.postPulse({ type: "record", record: { iter: 3, dt: 0.2, values: [[0.5, 0.6]] } }); + expect(posted).toHaveLength(1); // inside the window: coalesced + vi.advanceTimersByTime(200); + expect(posted).toHaveLength(2); // trailing edge: exactly one flush + expect(posted[1]).toMatchObject({ type: "pulse", iter: 3 }); // …carrying the newest + }); + + it("posts straight through once the webview is live", () => { + const { inspector, view, posted } = harness(); + inspector.resolveWebviewView(view as never); + posted.length = 0; + inspector.postPulse({ type: "meta", meta: META }); + inspector.postPulse({ type: "record", record: { iter: 3, dt: 0.2, values: [[0.5, 0.6]] } }); + expect(posted.map((m) => m.type)).toEqual(["pulsemeta", "pulse"]); + }); +}); + +afterEach(() => { vi.useRealTimers(); }); diff --git a/packages/extension/test/opencode_paths.test.ts b/packages/extension/test/opencode_paths.test.ts index d86c6039..f365766f 100644 --- a/packages/extension/test/opencode_paths.test.ts +++ b/packages/extension/test/opencode_paths.test.ts @@ -36,8 +36,8 @@ describe('resolveRunsRoot', () => { }) describe('inspectorResourceRootDirs', () => { - it('includes the runs-root so live PNGs are not CSP-blocked', () => { - expect(inspectorResourceRootDirs('/ext', '/home/u/.amico/runs/default')) - .toContain('/home/u/.amico/runs/default') + it('grants extension assets only — no run-dir roots (the view renders from message data)', () => { + const roots = inspectorResourceRootDirs('/ext') + expect(roots).toEqual(['/ext/dist', '/ext/media']) }) }) diff --git a/packages/extension/test/watcher_contract.test.ts b/packages/extension/test/watcher_contract.test.ts index d59d3314..ff86d049 100644 --- a/packages/extension/test/watcher_contract.test.ts +++ b/packages/extension/test/watcher_contract.test.ts @@ -2,7 +2,7 @@ import { describe, it, expect, vi } from 'vitest' import { mkdtempSync, mkdirSync, writeFileSync } from 'node:fs' import { tmpdir } from 'node:os' import { join } from 'node:path' -import { ingestRunDir, AMICODE_ITER_RE, parseAmicoNum, SinkDedup } from '../src/run_dir_reader' // pure β.1-contract reader (vscode-free) +import { ingestRunDir, AMICODE_ITER_RE, parseAmicoNum, parsePulseMetaLine, parsePulseRecordLine, PulseStream, SinkDedup } from '../src/run_dir_reader' // pure β.1-contract reader (vscode-free) function stageRun(opts: { status: string; exit: number; iters: number[]; fidelity?: number }): string { const root = mkdtempSync(join(tmpdir(), 'runs-')) @@ -10,20 +10,18 @@ function stageRun(opts: { status: string; exit: number; iters: number[]; fidelit const dir = join(root, runId); mkdirSync(dir, { recursive: true }) writeFileSync(join(dir, 'run.toml'), `schema_version = "1"\nrun_id = "${runId}"\nscript_path = "/s.jl"\nlab = "default"\nlab_id = "default"\ncreated_at = "2026-06-15T00:00:00Z"\norchestrator_version = "0.1.0"\n[julia]\nbinary = "julia"\n`) - for (const k of opts.iters) writeFileSync(join(dir, `iter_${k}.png`), 'PNG') writeFileSync(join(dir, 'run.log'), opts.iters.map(k => `AMICODE_ITER iter=${k} f=0.1 inf_pr=1e-8 inf_du=1e-6`).join('\n') + '\n') if (opts.fidelity !== undefined) writeFileSync(join(dir, 'result.toml'), `schema_version = "1"\nfidelity = ${opts.fidelity}\niterations = ${Math.max(...opts.iters, 0)}\n`) writeFileSync(join(dir, 'FINISHED'), `status = "${opts.status}"\nexit_code = ${opts.exit}\n`) return dir } -const fakeSink = () => ({ image: vi.fn(), iter: vi.fn(), run: vi.fn(), promote: vi.fn() }) +const fakeSink = () => ({ iter: vi.fn(), run: vi.fn(), promote: vi.fn(), pulse: vi.fn() }) describe('ingestRunDir — β.1 contract reading (replay)', () => { - it('completed run: identity from manifest, unbounded iter digits, run.log→iter, FINISHED→completed, promote on F≥0.99', () => { + it('completed run: identity from manifest, run.log→iter, FINISHED→completed, promote on F≥0.99', () => { const sink = fakeSink() ingestRunDir(stageRun({ status: 'completed', exit: 0, iters: [1, 7, 142], fidelity: 0.9991 }), sink) - expect(sink.image).toHaveBeenCalled() // iter_142.png accepted (3 digits) expect(sink.iter).toHaveBeenCalledWith(expect.objectContaining({ iter: 142 })) // run.log parsed on REPLAY expect(sink.run).toHaveBeenCalledWith(expect.objectContaining({ status: 'completed', fidelity: 0.9991 })) expect(sink.promote).toHaveBeenCalled() @@ -50,6 +48,27 @@ describe('ingestRunDir — β.1 contract reading (replay)', () => { const bytes = ingestRunDir(stageRun({ status: 'completed', exit: 0, iters: [1, 2], fidelity: 0.999 }), sink) expect(bytes).toBeGreaterThan(0) // = byte length of run.log consumed during replay }) + + it('pulse lines on replay: forwards the meta plus ONLY the newest record (no history burst at the webview)', () => { + const sink = fakeSink() + const dir = stageRun({ status: 'completed', exit: 0, iters: [1, 2, 3], fidelity: 0.999 }) + writeFileSync(join(dir, 'run.log'), + 'AMICODE_PULSE_META drives=1 knots=2 labels="a_1" bounds=-0.2:0.2\n' + + 'AMICODE_PULSE iter=1 dt=0.2 a=0.1,0.2\n' + + 'AMICODE_ITER iter=1 f=0.1 inf_pr=1e-8 inf_du=1e-6\n' + + 'AMICODE_PULSE iter=2 dt=0.2 a=0.3,0.4\n' + + 'AMICODE_PULSE iter=3 dt=0.2 a=0.5,0.6\n') + ingestRunDir(dir, sink) + expect(sink.pulse).toHaveBeenCalledTimes(2) + expect(sink.pulse).toHaveBeenNthCalledWith(1, expect.objectContaining({ type: 'meta' })) + expect(sink.pulse).toHaveBeenNthCalledWith(2, expect.objectContaining({ type: 'record', record: expect.objectContaining({ iter: 3 }) })) + }) + + it('pulse-less run.log: sink.pulse never fires (runs render exactly as today)', () => { + const sink = fakeSink() + ingestRunDir(stageRun({ status: 'completed', exit: 0, iters: [1], fidelity: 0.999 }), sink) + expect(sink.pulse).not.toHaveBeenCalled() + }) }) describe('AMICODE_ITER parsing — Inf/NaN are kept, not dropped', () => { @@ -65,37 +84,93 @@ describe('AMICODE_ITER parsing — Inf/NaN are kept, not dropped', () => { }) }) -// The live inspector path (LiveRunSink) delegates frame/iter dedup to SinkDedup. -// This is the exact gap Jack flagged on #9 ("no test covering the live status-bar -// / incremental inspector path") — and where a regression silently blanked every -// frame: run.log lines advanced a shared counter past the lagging PNG frames, so -// every image() call was deduped away. -describe('SinkDedup — live frame/iter dedup (the path that blanked the inspector)', () => { - it('accepts a strictly-increasing frame, rejects re-delivery (poll + fs.watch overlap)', () => { - const d = new SinkDedup() - expect(d.acceptFrame(6)).toBe(true) - expect(d.acceptFrame(6)).toBe(false) // same frame re-seen by the poll backstop - expect(d.acceptFrame(12)).toBe(true) - expect(d.acceptFrame(7)).toBe(false) // an older frame can't clobber a newer one +// Pulse-line grammar (#66) — the candidate GA format for client-side pulse +// rendering. Additive to the run.log stdout tee: consumers that don't know +// these lines ignore them (anchored regex no-match), so the β contract freeze +// is untouched. +describe('AMICODE_PULSE_META parsing (#66 pinned grammar)', () => { + it('parses drives/knots/labels/bounds from a well-formed meta line', () => { + const m = parsePulseMetaLine('AMICODE_PULSE_META drives=2 knots=50 labels="a_1","a_2" bounds=-0.2:0.2,-0.2:0.2') + expect(m).toEqual({ + drives: 2, + knots: 50, + labels: ['a_1', 'a_2'], + bounds: [[-0.2, 0.2], [-0.2, 0.2]], + }) }) +}) - it('log-line iters do NOT suppress lagging frames (regression guard)', () => { - const d = new SinkDedup() - // run.log streams iter=1..60 (fast) before the iter_0006.png frame lands. - for (let k = 1; k <= 60; k++) d.noteIter(k) - // The frame for iter 6 must STILL display — it dedups on frames, not log lines. - expect(d.acceptFrame(6)).toBe(true) - expect(d.acceptFrame(12)).toBe(true) - expect(d.acceptFrame(60)).toBe(true) +describe('AMICODE_PULSE record parsing (#66 pinned grammar)', () => { + it('parses iter/dt and per-drive value lists (drives ;-separated, values ,-separated)', () => { + const r = parsePulseRecordLine('AMICODE_PULSE iter=6 dt=0.204082 a=0.021,-0.013,1.2e-3;0.008,0.031,-4e-2') + expect(r).toEqual({ + iter: 6, + dt: 0.204082, + values: [[0.021, -0.013, 0.0012], [0.008, 0.031, -0.04]], + }) + }) + it('keeps Inf/NaN values, matching the stats parser', () => { + const r = parsePulseRecordLine('AMICODE_PULSE iter=3 dt=0.2 a=Inf,-Inf;NaN,0.5') + expect(r!.values[0]).toEqual([Infinity, -Infinity]) + expect(Number.isNaN(r!.values[1][0])).toBe(true) + expect(r!.values[1][1]).toBe(0.5) + }) +}) + +// PulseStream — the cross-line policy both delivery paths (replay ingest, live +// tail) feed lines through. Policy per #66 AC4: records before any meta are +// dropped; the last meta wins and resets state; count-mismatched records and +// internally-inconsistent metas are ignored. +describe('PulseStream — cross-line policy (#66)', () => { + const META = 'AMICODE_PULSE_META drives=2 knots=3 labels="a_1","a_2" bounds=-0.2:0.2,-0.2:0.2' + const REC = 'AMICODE_PULSE iter=6 dt=0.2 a=0.1,0.2,0.3;0.4,0.5,0.6' + + it('drops records that arrive before any meta', () => { + const ps = new PulseStream() + expect(ps.onLine(REC)).toBeUndefined() + expect(ps.onLine(META)).toMatchObject({ type: 'meta' }) + expect(ps.onLine(REC)).toMatchObject({ type: 'record', record: { iter: 6 } }) }) - it('high() tracks the max across both sources (status bar / completion iter N)', () => { + it('ignores records whose drive count or knot count disagree with the current meta', () => { + const ps = new PulseStream() + ps.onLine(META) // drives=2 knots=3 + expect(ps.onLine('AMICODE_PULSE iter=1 dt=0.2 a=0.1,0.2,0.3')).toBeUndefined() // 1 drive ≠ 2 + expect(ps.onLine('AMICODE_PULSE iter=2 dt=0.2 a=0.1,0.2;0.3,0.4')).toBeUndefined() // 2 knots ≠ 3 + expect(ps.onLine(REC)).toMatchObject({ type: 'record' }) // conformant still flows + }) + + it('treats a meta whose label or bounds count disagrees with drives= as malformed (no state change)', () => { + const ps = new PulseStream() + expect(ps.onLine('AMICODE_PULSE_META drives=2 knots=3 labels="a_1" bounds=-0.2:0.2,-0.2:0.2')).toBeUndefined() // 1 label ≠ 2 drives + expect(ps.onLine('AMICODE_PULSE_META drives=2 knots=3 labels="a_1","a_2" bounds=-0.2:0.2')).toBeUndefined() // 1 bound ≠ 2 drives + expect(ps.onLine(REC)).toBeUndefined() // bad metas did NOT arm the stream + }) + + it('last meta wins: a re-read meta (tailer truncation re-read) re-arms cleanly and its shape governs', () => { + const ps = new PulseStream() + ps.onLine(META) + ps.onLine(REC) + // duplicate meta (offset-0 re-read) — same shape, still fine + expect(ps.onLine(META)).toMatchObject({ type: 'meta' }) + expect(ps.onLine(REC)).toMatchObject({ type: 'record' }) + // a NEW meta with a different shape governs subsequent records + expect(ps.onLine('AMICODE_PULSE_META drives=1 knots=2 labels="a_1" bounds=-0.1:0.1')).toMatchObject({ type: 'meta' }) + expect(ps.onLine(REC)).toBeUndefined() // old-shape record now ignored + expect(ps.onLine('AMICODE_PULSE iter=9 dt=0.2 a=0.1,0.2')).toMatchObject({ type: 'record', record: { iter: 9 } }) + }) +}) + +// SinkDedup — the live sink's iteration high-water mark (status bar / completion). +describe('SinkDedup — iteration high-water mark', () => { + it('high() tracks the max iter seen; out-of-order notes never regress it', () => { const d = new SinkDedup() expect(d.high).toBe(-1) - d.acceptFrame(6) d.noteIter(42) expect(d.high).toBe(42) - d.acceptFrame(60) + d.noteIter(7) + expect(d.high).toBe(42) + d.noteIter(60) expect(d.high).toBe(60) }) }) diff --git a/packages/extension/test/watcher_statemachine.test.ts b/packages/extension/test/watcher_statemachine.test.ts index 50ab0319..75dd23bb 100644 --- a/packages/extension/test/watcher_statemachine.test.ts +++ b/packages/extension/test/watcher_statemachine.test.ts @@ -1,5 +1,5 @@ import { describe, it, expect, vi, beforeEach } from "vitest"; -import { mkdtempSync, mkdirSync, writeFileSync, symlinkSync, rmSync } from "node:fs"; +import { appendFileSync, mkdtempSync, mkdirSync, writeFileSync, symlinkSync, rmSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; @@ -13,10 +13,10 @@ import { join } from "node:path"; const { inspector } = vi.hoisted(() => ({ inspector: { - setImageSource: vi.fn(), setWarmingUp: vi.fn(), postCompletion: vi.fn(), postIterationRecord: vi.fn(), + postPulse: vi.fn(), setRunLabel: vi.fn(), reveal: vi.fn(), }, @@ -38,49 +38,38 @@ function setLatest(root: string, target: string): void { symlinkSync(target, link); } const tick = (w: RunsRootWatcher): void => (w as unknown as { tick(): void }).tick(); +const META_LINE = 'AMICODE_PULSE_META drives=1 knots=2 labels="a_1" bounds=-0.2:0.2\n'; describe("RunsRootWatcher state machine", () => { beforeEach(() => { for (const f of Object.values(inspector)) f.mockClear(); }); - it("a run already FINISHED at launch stays idle — no stale plot re-rendered", () => { + it("a run already FINISHED at launch stays idle — nothing re-rendered", () => { const root = mkdtempSync(join(tmpdir(), "runs-")); const run = join(root, "r1"); mkdirSync(run); writeManifest(run, "r1"); - writeFileSync(join(run, "iter_6.png"), "PNG"); // a frame is on disk… writeFileSync(join(run, "FINISHED"), 'status = "completed"\nexit_code = 0\n'); setLatest(root, run); const w = new RunsRootWatcher({ runsRoot: root, channel }); w.start(); tick(w); // even after a poll, a finished-at-launch run must render nothing - expect(inspector.setImageSource).not.toHaveBeenCalled(); // …but it's NOT shown + expect(inspector.postPulse).not.toHaveBeenCalled(); + expect(inspector.postCompletion).not.toHaveBeenCalled(); expect(inspector.setWarmingUp).not.toHaveBeenCalled(); w.dispose(); }); - it("fresh run → warming-up → poll delivers newest frame (newest-wins) → completion", () => { + it("fresh run → warming-up → completion (plot arrives via pulse routing, not frames)", () => { const root = mkdtempSync(join(tmpdir(), "runs-")); const run = join(root, "r2"); mkdirSync(run); - writeManifest(run, "r2"); // manifest only, no frames yet + writeManifest(run, "r2"); // manifest only, no data yet setLatest(root, run); const w = new RunsRootWatcher({ runsRoot: root, channel }); w.start(); - // fresh run with no frames → warming, not idle, not a frame + // fresh run with no data → warming, not idle expect(inspector.setWarmingUp).toHaveBeenCalledTimes(1); expect(inspector.setRunLabel).toHaveBeenCalledWith("r2"); - expect(inspector.setImageSource).not.toHaveBeenCalled(); - - // first frame appears; the poll backstop delivers it (no reliance on fs.watch) - writeFileSync(join(run, "iter_6.png"), "PNG"); - tick(w); - expect(inspector.setImageSource).toHaveBeenLastCalledWith(expect.stringContaining("iter_6.png"), 6); - - // two frames land between ticks → only the NEWEST is delivered - writeFileSync(join(run, "iter_12.png"), "PNG"); - writeFileSync(join(run, "iter_18.png"), "PNG"); - tick(w); - expect(inspector.setImageSource).toHaveBeenLastCalledWith(expect.stringContaining("iter_18.png"), 18); // FINISHED + result → terminal completion delivered once writeFileSync(join(run, "result.toml"), 'schema_version = "1"\nfidelity = 0.9999\niterations = 18\n'); @@ -90,3 +79,50 @@ describe("RunsRootWatcher state machine", () => { w.dispose(); }); }); + +// #66 AC6 — live-tail pulse routing. On a live run the tailer is the ONLY +// carrier of meta (ingest sees an empty log at run start), so the tail path +// must forward meta AND each record, in order. +describe("pulse-line routing (#66)", () => { + beforeEach(() => { for (const f of Object.values(inspector)) f.mockClear(); }); + + it("live tail forwards meta and each record in order as they land", () => { + const root = mkdtempSync(join(tmpdir(), "runs-")); + const run = join(root, "p1"); mkdirSync(run); + writeManifest(run, "p1"); + setLatest(root, run); + const w = new RunsRootWatcher({ runsRoot: root, channel }); + w.start(); // fresh run, empty log → warming + expect(inspector.postPulse).not.toHaveBeenCalled(); + + writeFileSync(join(run, "run.log"), META_LINE + "AMICODE_PULSE iter=1 dt=0.2 a=0.1,0.2\n"); + tick(w); // poll poke drains the tailer + expect(inspector.postPulse).toHaveBeenCalledTimes(2); + expect(inspector.postPulse).toHaveBeenNthCalledWith(1, expect.objectContaining({ type: "meta" })); + expect(inspector.postPulse).toHaveBeenNthCalledWith(2, expect.objectContaining({ type: "record", record: expect.objectContaining({ iter: 1 }) })); + + appendFileSync(join(run, "run.log"), "AMICODE_PULSE iter=2 dt=0.2 a=0.3,0.4\n"); + tick(w); + expect(inspector.postPulse).toHaveBeenCalledTimes(3); + expect(inspector.postPulse).toHaveBeenLastCalledWith(expect.objectContaining({ type: "record", record: expect.objectContaining({ iter: 2 }) })); + w.dispose(); + }); + + it("replay-seeded meta arms the live stream: tailed records flow without a re-sent meta", () => { + const root = mkdtempSync(join(tmpdir(), "runs-")); + const run = join(root, "p2"); mkdirSync(run); + writeManifest(run, "p2"); + // Mid-flight switch: meta + one record ALREADY on disk, run not finished. + writeFileSync(join(run, "run.log"), META_LINE + "AMICODE_PULSE iter=3 dt=0.2 a=0.1,0.2\n"); + setLatest(root, run); + const w = new RunsRootWatcher({ runsRoot: root, channel }); + w.start(); // ingest replays meta + newest record + expect(inspector.postPulse).toHaveBeenCalledTimes(2); + + // a record tailed AFTER attach, with no meta line in the tailed region + appendFileSync(join(run, "run.log"), "AMICODE_PULSE iter=4 dt=0.2 a=0.5,0.6\n"); + tick(w); + expect(inspector.postPulse).toHaveBeenLastCalledWith(expect.objectContaining({ type: "record", record: expect.objectContaining({ iter: 4 }) })); + w.dispose(); + }); +});