|
| 1 | +# Codegen block inventory (issue #48) |
| 2 | + |
| 3 | +Verified classification of every public block against the C code generator. The |
| 4 | +issue asks for two things: |
| 5 | + |
| 6 | +1. **All eventful blocks _without an internal solver_ must be handled by codegen.** |
| 7 | +2. **All codegen settings must produce SiL-identical results** to the Rust path. |
| 8 | + |
| 9 | +This document covers point 1 (the acceptance gap). Point 2 (numeric parity across |
| 10 | +the setting permutations) is the job of the fuzzing harness and is tracked |
| 11 | +separately — a block appearing as `OK` below means codegen *emits* C for it, not |
| 12 | +yet that the emitted C is bit-for-bit faithful. |
| 13 | + |
| 14 | +Reproduce with: |
| 15 | + |
| 16 | +``` |
| 17 | +maturin develop # build the extension with the codegen feature |
| 18 | +python scripts/codegen_block_inventory.py |
| 19 | +``` |
| 20 | + |
| 21 | +The tool constructs a representative instance of each block, assembles a minimal |
| 22 | +closed sim, snapshots the IR (`sim.to_ir`) to count events / opaque events / |
| 23 | +extern blocks / continuous state, and then tries `sim.to_c()`. |
| 24 | + |
| 25 | +## Summary |
| 26 | + |
| 27 | +| Category | Count | Blocks | |
| 28 | +|---|---|---| |
| 29 | +| Codegen-accepted | 94 | all the leaf math/source/discrete/filter/LTI blocks, incl. every op-expressible eventful block | |
| 30 | +| **Eventful, opaque → rejected (the #48 gap)** | **2** | **`Wrapper`, `PinkNoise`** | |
| 31 | +| Internal-solver / extern → rejected (out of scope) | 5 | `AlgebraicConstraint`, `BVP1D`, `FullyImplicitDAE`, `MassMatrixDAE`, `SemiExplicitDAE` | |
| 32 | +| Needs external resource (out of scope) | 2 | `CoSimulationFMU`, `ModelExchangeFMU` (need an FMU file; opaque co-sim) | |
| 33 | + |
| 34 | +## The gap: eventful blocks codegen cannot yet lower |
| 35 | + |
| 36 | +Both have an op-expressible representation available — they are lowerable, they |
| 37 | +just still emit their sampling event as an opaque host closure. |
| 38 | + |
| 39 | +### `Wrapper` |
| 40 | +`_trace_wrapper` (`src/pybindings/py/jit.rs`) already JIT-traces `func(u)` into a |
| 41 | +`LazyTraced` op-graph. But the scheduled re-evaluation is pushed as a runtime |
| 42 | +`Schedule` event whose action is a host closure (`t_evt.call_into`), so the IR |
| 43 | +event is `opaque = true` and `collect_events` rejects it |
| 44 | +(`src/codegen/system.rs:1319`). |
| 45 | + |
| 46 | +**Fix direction:** express the sampling as an op-expressible discrete event — a |
| 47 | +memory (ZOH) slot written by the traced graph in a `Schedule` effect region, |
| 48 | +exactly the pattern `SampleHold` / `ZeroOrderHold` already use (both `OK`). The |
| 49 | +traced op-graph is in hand, so this is a lowering change, not new math. |
| 50 | + |
| 51 | +### `PinkNoise` |
| 52 | +Voss-McCartney: `num_octaves` independent random values plus a sample counter; |
| 53 | +each sample updates one octave selected by the counter's bit pattern |
| 54 | +(`src/blocks/constructors/noise.rs`). The update runs in an opaque event. |
| 55 | + |
| 56 | +**Fix direction:** carry the octaves + counter as memory slots and express the |
| 57 | +per-sample octave update as an op-expressible periodic event, reusing the |
| 58 | +`fastsim_rand_*` header helpers (the same helpers that already let `WhiteNoise` |
| 59 | +and `RandomNumberGenerator` lower). Heavier than `Wrapper` because of the |
| 60 | +counter-bit octave selection. |
| 61 | + |
| 62 | +## Note: the noise blocks that _are_ already handled |
| 63 | + |
| 64 | +`WhiteNoise` (sampling-period mode), `RandomNumberGenerator`, |
| 65 | +`SinusoidalPhaseNoiseSource` and `Chirp*` lower with **zero events**: their sample |
| 66 | +is a deterministic pure function of `t` (`out = scale · normal(key(t))`, |
| 67 | +`noise.rs`), so there is no event to drop and they are SiL-reproducible by |
| 68 | +construction. The `ev=0 / codegen=OK` rows for them are correct, not a silently |
| 69 | +dropped event. |
| 70 | + |
| 71 | +## Out of scope for #48 |
| 72 | + |
| 73 | +`AlgebraicConstraint`, `BVP1D`, `FullyImplicitDAE`, `MassMatrixDAE`, |
| 74 | +`SemiExplicitDAE` carry an **internal solver** (Newton / collocation) and are |
| 75 | +represented as opaque extern blocks (`extern=1`, `ev=0`); the issue explicitly |
| 76 | +excludes internal-solver blocks. The two FMU blocks wrap external co-simulation |
| 77 | +binaries and are opaque by nature. |
| 78 | + |
| 79 | +## Full table |
| 80 | + |
| 81 | +``` |
| 82 | +block ev opq ext st codegen |
| 83 | +---------------------------------------------------- |
| 84 | +ADC 1 0 0 1 OK |
| 85 | +Abs 0 0 0 1 OK |
| 86 | +Adder 0 0 0 1 OK |
| 87 | +AlgebraicConstraint 0 0 1 1 REJECT (internal solver) |
| 88 | +Alias 0 0 0 1 OK |
| 89 | +AllpassFilter 0 0 0 2 OK |
| 90 | +Amplifier 0 0 0 1 OK |
| 91 | +AntiWindupPID 0 0 0 3 OK |
| 92 | +Atan / Atan2 0 0 0 1 OK |
| 93 | +Backlash 0 0 0 2 OK |
| 94 | +Butterworth{LP,HP,BP,BS} 0 0 0 3-5 OK |
| 95 | +Chirp[PhaseNoise]Source 0 0 0 2 OK |
| 96 | +Clip 0 0 0 1 OK |
| 97 | +Clock / ClockSource 0 0 0 1 OK |
| 98 | +CoSimulationFMU - - - - SKIP (needs FMU file) |
| 99 | +Comparator 0 0 0 1 OK |
| 100 | +Constant 0 0 0 1 OK |
| 101 | +Cos / Cosh 0 0 0 1 OK |
| 102 | +Counter[Up,Down] 1 0 0 1 OK |
| 103 | +DAC 1 0 0 1 OK |
| 104 | +Deadband 0 0 0 1 OK |
| 105 | +Delay 1 0 0 1 OK |
| 106 | +Differentiator 0 0 0 2 OK |
| 107 | +DiscreteDerivative 1 0 0 1 OK |
| 108 | +DiscreteIntegrator 1 0 0 1 OK |
| 109 | +DiscreteStateSpace 1 0 0 1 OK |
| 110 | +DiscreteTransferFunction 1 0 0 1 OK |
| 111 | +Divider 0 0 0 1 OK |
| 112 | +DynamicalFunction 0 0 0 1 OK |
| 113 | +DynamicalSystem 0 0 0 2 OK |
| 114 | +Equal 0 0 0 1 OK |
| 115 | +Exp 0 0 0 1 OK |
| 116 | +FIR 1 0 0 1 OK |
| 117 | +FirstOrderHold 1 0 0 1 OK |
| 118 | +FullyImplicitDAE 0 0 1 1 REJECT (internal solver) |
| 119 | +Function 0 0 0 1 OK |
| 120 | +GaussianPulseSource 0 0 0 1 OK |
| 121 | +GreaterThan / LessThan 0 0 0 1 OK |
| 122 | +Integrator 0 0 0 2 OK |
| 123 | +LUT1D 0 0 0 1 OK |
| 124 | +LeadLag 0 0 0 2 OK |
| 125 | +Log / Log10 0 0 0 1 OK |
| 126 | +Logic{And,Or,Not} 0 0 0 1 OK |
| 127 | +MassMatrixDAE 0 0 1 1 REJECT (internal solver) |
| 128 | +Matrix 0 0 0 1 OK |
| 129 | +Mod 0 0 0 1 OK |
| 130 | +ModelExchangeFMU - - - - SKIP (needs FMU file) |
| 131 | +Multiplier / Norm 0 0 0 1 OK |
| 132 | +ODE 0 0 0 2 OK |
| 133 | +PID 0 0 0 3 OK |
| 134 | +PT1 / PT2 0 0 0 2-3 OK |
| 135 | +PinkNoise 1 1 1 1 REJECT (opaque event) <-- #48 gap |
| 136 | +Polynomial 0 0 0 1 OK |
| 137 | +Pow / PowProd 0 0 0 1 OK |
| 138 | +Pulse / PulseSource 4 0 0 1 OK |
| 139 | +RandomNumberGenerator 0 0 0 1 OK |
| 140 | +RateLimiter 0 0 0 2 OK |
| 141 | +Relay 2 0 0 1 OK |
| 142 | +Rescale 0 0 0 1 OK |
| 143 | +SampleHold 1 0 0 1 OK |
| 144 | +Scope 0 0 1 0 OK (sink, dropped) |
| 145 | +SemiExplicitDAE 0 0 1 1 REJECT (internal solver) |
| 146 | +Sin / Sinh 0 0 0 1 OK |
| 147 | +SinusoidalPhaseNoiseSource 0 0 0 2 OK |
| 148 | +SinusoidalSource 0 0 0 1 OK |
| 149 | +Source 0 0 0 1 OK |
| 150 | +Spectrum 0 0 1 0 OK (sink, dropped) |
| 151 | +Sqrt 0 0 0 1 OK |
| 152 | +SquareWaveSource 0 0 0 1 OK |
| 153 | +StateSpace 0 0 0 3 OK |
| 154 | +Step / StepSource 1 0 0 1 OK |
| 155 | +Switch 0 0 0 1 OK |
| 156 | +Tan / Tanh 0 0 0 1 OK |
| 157 | +TappedDelay 1 0 0 1 OK |
| 158 | +TransferFunction[NumDen,PRC,ZPG] 0 0 0 2 OK |
| 159 | +TriangleWaveSource 0 0 0 1 OK |
| 160 | +WhiteNoise 0 0 0 1 OK (deterministic-of-t) |
| 161 | +Wrapper 1 1 1 1 REJECT (opaque event) <-- #48 gap |
| 162 | +ZeroOrderHold 1 0 0 1 OK |
| 163 | +``` |
0 commit comments