Reachability and agreement¶
Every number the example corpus produces is a measurement of the corpus more than of the
toolchain: fifteen models, each written by the people who wrote the compiler, each chosen
to force exactly one capability. moon run :coverage produces the other kind of number —
over every example model in the Modelica Standard Library at a pinned version, how
far does each one get through this toolchain, and why does it stop where it stops.
Two claims are measured separately, because they fail differently and a single "it works" hides both:
- Reachability — the model parses, flattens, causalizes and simulates. This is a language-coverage question.
- Agreement — it simulates and produces the same trajectory as a tool people trust. This is a semantics question, and it is the one that can be quietly wrong.
There is a published precedent for saying this plainly. Rumoca reports that 28 % of MSL examples reach end-to-end simulation under it, and that about a third of the models that simulate disagree with OpenModelica. Those numbers are unflattering and checkable, which is what makes them worth having — and worth copying as a habit. The landscape survey has the longer argument.
The rules¶
The denominator is the half of a fraction that is easiest to fudge, so its rule is stated
here: an example model is a non-package.mo file under the Modelica package whose
text carries an experiment annotation, named by its path —
Modelica/Blocks/Examples/PID_Controller.mo is Modelica.Blocks.Examples.PID_Controller.
- The MSL is fetched at the pinned version into
.cache/msl/, never vendored. - Every refusal is attributed: a model that does not reach the end records which stage refused it and why, and the report orders the refusals by how many models each one blocks. That work list is the real product — "arrays block N models" is a decision, a percentage alone is a headline.
- The reference for agreement is OpenModelica (
omconPATH), never a second run of this toolchain. Whenomcis absent the agreement number is reported as unmeasured, with the command that would measure it. - Not a CI gate, deliberately. The number moves as language coverage moves, and gating on it would make a green pipeline a matter of taste.
- Measuring and improving are different jobs. The run changes nothing;
coverage.jsonholds the per-model detail for whoever picks up the work list.
The measurements, newest first¶
2026-09-11, after a relation differentiates to zero for a variable it does not contain¶
A Jacobian entry is taken for each variable a torn algebraic block solves for. When a residual carries
a relation or a logical operator (x > 0, off or fire), differentiating it used to be refused
outright, even for a variable the relation does not mention, whose derivative there is simply zero. The
derivative rules now return zero in that case and keep the refusal only for the variable the relation
actually turns on.
| stage | models | of 430 |
|---|---|---|
| parsed | 415 | 96.5 % |
| flattened | 90 | 20.9 % |
| causalized | 41 | 9.5 % |
| simulated | 27 | 6.3 % |
The totals hold at 90 / 41 / 27: no model was blocked only by this. The nine that named it are the ideal
thyristor rectifiers, and there the slack variable s sits inside the relation itself
(off = s < 0 or (pre(off) and not fire)), so the derivative is genuinely undefined. An ideal switch is
a complementarity, a variable-structure element whose active equations change on an event; supporting it
needs event-driven mode switching, not a derivative rule, so that wall stays. This fix is the correctness
half: a relation the solved-for variable does not touch no longer stops a block it happens to share.
2026-09-11, after a redeclaration on an extends clause replaces the type¶
The machines declare a replaceable thermal port of a base type and, in each concrete machine,
redeclare it to the type that carries that machine's own heat ports: extends PartialBasicDCMachine(
redeclare final ThermalPortDCEE internalThermalPort). The redeclaration rides on the modification the
extends clause writes, and that modification was only ever carried as a value on the member, never
applied. So the base type was instantiated, its added heat ports were missing, and a connect to one,
connect(re.heatPort, internalThermalPort.heatPortExcitation), failed as "not a connector".
A redeclaration written on an extends clause now replaces the member's type, the same way one written
at instantiation does. The two spellings meet in the same _redeclared, so there is one rule; a class
redeclaration on an extends clause is still left for _rebind.
| stage | models | of 430 |
|---|---|---|
| parsed | 415 | 96.5 % |
| flattened | 90 | 20.9 % |
| causalized | 41 | 9.5 % |
| simulated | 27 | 6.3 % |
The reach jumps: flattened 86 to 90, causalized 23 to 41, simulated 21 to 27. The DC machines flatten, causalize and simulate now that their thermal ports and power-balance records redeclare to the right types; the induction and synchronous machines flatten past this wall and stop at the next (a space-phasor array shape, a star-point condition). No model regressed, verified by a per-stage set difference.
2026-09-11, after a multi-output function in an expression yields its first output¶
The machine sensors write v_ = ToSpacePhasor(i), and ToSpacePhasor returns two values, the space
phasor and the zero-sequence component. Called where one value is expected, Modelica takes the first
output and drops the rest, but the flattener refused any call to a function with more than one output,
"declares 2 outputs; only one can be inlined". The tuple form (a, b) = f(x) already had its own path.
The single-value inline now reads the first output rather than refusing. The body is run once as
before, so every output is computed and the unused ones are simply not read; only a function that
declares no output at all is refused now, since there is then nothing to inline. So v_ =
ToSpacePhasor(i) becomes the space phasor 2/m * {cos(phi), sin(phi)} * i, the zero sequence dropped.
| stage | models | of 430 |
|---|---|---|
| parsed | 415 | 96.5 % |
| flattened | 86 | 20.0 % |
| causalized | 23 | 5.3 % |
| simulated | 21 | 4.9 % |
Flattened rose from 84 to 86 -- the polyphase rectifier and the sensor testbench, whose space-phasor
readouts this had blocked. The other three that named the refusal step to their next wall (an array
shape, an if-equation condition). Every one of the 86 still flattens, and causalized and simulated hold
at 23 and 21, verified by a per-stage set difference. Agreement is still unmeasured (no omc).
2026-09-10, after size is resolved to its dimension length at flatten¶
The polyphase sensors write u .^ 2 / size(u, 1), and size(u, 1) is the length of the first
dimension of u -- a structural number, three for a three-phase current. The shape reader did not know
size: it took a function's shape to be the shape of its arguments, so size(u, 1) was read as a vector
of length three, and an array vector / a vector was refused, "an array / is not supported". Even had
the shape been right, the element split would then have subscripted u inside the call, size(u[i], 1),
which is not what it means.
size is now resolved to a literal during flatten, the same pass that expands zeros(n) and identity(n):
size(a, k) becomes the k-th entry of a's flat shape, size(a) the vector of its dimension lengths.
Its argument is already flat, so the shape registry has the answer. The shape reader learned size too, so
size(a, k) reads as a scalar wherever it is met. It is a structural read, so it never reaches the element
split or the emitter as a call.
| stage | models | of 430 |
|---|---|---|
| parsed | 415 | 96.5 % |
| flattened | 84 | 19.5 % |
| causalized | 23 | 5.3 % |
| simulated | 21 | 4.9 % |
Flattened rose from 68 to 84 -- size is written across the polyphase and machine library, so the one
fix cleared far more than the six models the array division named. Every one of the 84 flattens with no
size left in its equations, and causalized and simulated hold at 23 and 21, verified by a per-stage set
difference. The leaders are now a subscript on an inner component (parse, 70), the Clock builtin (66),
an array shape unknown at flatten (26 and 24), and an if-equation condition unknown (11). Agreement is
still unmeasured (no omc).
2026-09-10, after a range subscript is read as an array slice¶
The polyphase transformers ground a delta connection with transformerL.i[1:m] = zeros(m) -- a range
subscript on the left, a whole-array constructor on the right. A range in a subscript is a slice, a
sub-array, but the subscript handler only knew the scalar case: every index had to resolve to one
integer, and a range there was refused as "a range is read only in the header of a 'for' equation".
A range subscript now expands to an array literal, one array level per range dimension and a scalar
dimension held fixed: x[1:m] becomes {x[1], ..., x[m]}, A[1:2, 3] becomes {A[1, 3], A[2, 3]}.
The slice then has the shape of its ranges, so _element reads it out exactly as it reads any array,
and an equation between a slice and its right-hand side splits into one scalar equation per element
like the rest. The range enumeration a for header and a comprehension already share is factored into
one helper the slice reuses. Three examples carried the refusal (TransformerYD, TransformerYY,
Rectifier6pulse) and all three now flatten.
| stage | models | of 430 |
|---|---|---|
| parsed | 415 | 96.5 % |
| flattened | 68 | 15.8 % |
| causalized | 23 | 5.3 % |
| simulated | 21 | 4.9 % |
The leaders are a subscript on an inner component (parse, 70), the Clock builtin (66), an if-equation
condition unknown at flatten (27), and an array shape unknown at flatten (24 and 24). Agreement is
still unmeasured (no omc).
2026-09-10, after an if-equation may appear in an initial equation section¶
The SPICE transistors write initial equation if UIC then vcs = IC; end if; -- a parameter-gated
initial condition, the same structural if the ordinary equation section already handles: the condition
is a parameter, evaluated at flatten, and only the chosen branch's a = b survives, into the initial
equations. The initial section refused it anyway. Its guard admitted only a = b, a call equation and a
for equation, so an if there was rejected as "only a = b equations may appear in initial equation"
before it could reach the handler.
The handler needed nothing new: _if_equation already takes the initial flag, threads it through a
deferred condition and into each branch body, so a branch's equations land among the initial equations
on their own. Only the guard had to admit ir.IfEquation. Five examples carried the refusal; three now
flatten (the difference amplifier and the two heating NOR gates), and the other two step to their next
wall (an array shape, a record redeclaration). No model regressed, verified by a per-stage set
difference.
| stage | models | of 430 |
|---|---|---|
| parsed | 415 | 96.5 % |
| flattened | 65 | 15.1 % |
| causalized | 23 | 5.3 % |
| simulated | 21 | 4.9 % |
The leaders are a subscript on an inner component (parse, 70), the Clock builtin (66), an if-equation
condition unknown at flatten (27), and an array shape unknown at flatten (24 and 24). Agreement is still
unmeasured (no omc).
2026-09-10, after a comprehension in an array declaration binding is expanded¶
The machines set a per-phase parameter as a comprehension: parameter Real x[m] = {f(i) for i in 1:m}.
An array declaration is split into one variable per element, and an array constructor in its binding
(zeros(m), identity(m)) was already expanded to the literal array first, so each element could take
its own value. A comprehension was not, so it reached the element split whole -- every element carrying
the entire comprehension, which the emitter then could not read, or, where the binding was prefixed
without the parameter values in hand, refused as "a comprehension is not expanded here".
The comprehension is now expanded the same way a constructor is: to the array it stands for, one element
per iterator value, evaluated against the parameters that are the shape. So x[1] binds f(1), x[2]
binds f(2), each its own value -- and the refusal, twenty-five induction-machine examples, leaves the
list. They do not reach a new stage; each meets the next wall at once, a thermal-port connect the
resolver does not see, so the stage totals hold. No model regressed, and the wrong binding it would have
produced is corrected.
| stage | models | of 430 |
|---|---|---|
| parsed | 415 | 96.5 % |
| flattened | 62 | 14.4 % |
| causalized | 23 | 5.3 % |
| simulated | 21 | 4.9 % |
The leaders are a connect endpoint not seen as a connector (parse), the Clock builtin (62), an
if-equation condition unknown at flatten (27), and an array shape unknown at flatten (24 and 23).
Agreement is still unmeasured (no omc).
2026-09-10, after a whole array of connectors is reached through an array of components¶
The element-wise connect of the previous change found the array only on the last name of a reference --
plug_p.pin, where pin is the array. The polyphase machines write the other shape: connect(resistor.p,
plug_p.pin), one resistor per phase, so resistor.p is a whole array of connectors reached through an
array of components, the array sitting on resistor rather than on p. That earlier change then read one
side as an array and the other as a single connector and refused them as unequal -- a refusal it had newly
introduced, standing in front of sixty-three induction-machine examples.
The array is now found on whichever segment of the reference carries it, last or earlier, and each side is
expanded through that segment: resistor.p becomes resistor[i].p, plug_p.pin becomes plug_p.pin[i],
and the two are joined phase by phase. The refusal leaves sixty-three models. They do not reach a new
stage -- each meets the next wall at once, a comprehension not yet expanded, an if-equation condition or an
array shape unknown at flatten -- so the stage totals hold. No model regressed.
| stage | models | of 430 |
|---|---|---|
| parsed | 415 | 96.5 % |
| flattened | 62 | 14.4 % |
| causalized | 23 | 5.3 % |
| simulated | 21 | 4.9 % |
The leaders are a connect endpoint not seen as a connector (parse), the Clock builtin (62), an
if-equation condition unknown at flatten (27), and an array shape unknown at flatten (24 and 23).
Agreement is still unmeasured (no omc).
2026-09-10, after a conditional component's condition reads an enumeration literal¶
The power converters gate a component on an enumeration: SignalPWM referenceGenerator if refType ==
SingleReferenceType.Sawtooth. The condition on a conditional component was resolved by prefixing its
names, but an enumeration literal is not a name -- SingleReferenceType.Sawtooth has to become its
ordinal first -- so prefixing read it as a variable that does not exist. An if-equation condition
already took that rewrite; a component's condition did not.
It does now, the same literal-then-names order an equation uses, so the literal becomes its number and the comparison against the parameter is a comparison of two numbers. Three DC/DC converter examples flatten end to end -- ChopperBuckBoost_DutyCycle, HBridge_R, HBridge_RL -- so flattened moves 59 to 62. No model regressed.
| stage | models | of 430 |
|---|---|---|
| parsed | 415 | 96.5 % |
| flattened | 62 | 14.4 % |
| causalized | 23 | 5.3 % |
| simulated | 21 | 4.9 % |
The leaders are a connect endpoint not seen as a connector (parse), the Clock builtin (62), an array
shape unknown at flatten (24 and 17), an if-equation condition unknown at flatten (16), and a subscript
on an inner component in parse. Agreement is still unmeasured (no omc).
2026-09-10, after a whole array of connectors is connected element by element¶
The power converters connect two arrays of connectors at once -- connect(internalEnable,
booleanReplicator.y), a replicator's outputs to an enable line, both arrays of the same length --
rather than writing the loop. A connect endpoint was resolved to one connector, so a whole array was
refused, with the advice to connect the elements. It now is: when both sides name a one-dimensional
array of connectors of the same length, the connect is read as the element-wise connect it stands for,
x[i] to y[i] for every i. A plain connect is untouched, and two arrays of different lengths, or an
array joined to a single connector, are refused as the size mismatch they are.
The refusal leaves twenty models. They do not reach a new stage -- each meets the next wall at once, an enumeration literal used as a name, a connect endpoint the resolver still does not see -- so the stage totals hold. No model regressed.
| stage | models | of 430 |
|---|---|---|
| parsed | 415 | 96.5 % |
| flattened | 59 | 13.7 % |
| causalized | 23 | 5.3 % |
| simulated | 21 | 4.9 % |
The leaders are a connect endpoint not seen as a connector (parse), the Clock builtin (62), an array
shape unknown at flatten (24 and 17), a subscript on an inner component in parse, and an if-equation
condition unknown at flatten (14). Agreement is still unmeasured (no omc).
2026-09-10, after an assignment to an array element parses¶
Modelica.Blocks.Math and Modelica.Blocks.Routing did not parse: the FFT block writes buf[iTick] :=
u in an algorithm, the routing block k[i] = 0 in a when, and the statement parser read the target
as a bare name and then expected an assignment where the subscript's [ was. A file that does not parse
takes its dependents with it -- a model that names any class in Blocks.Math, however small, failed at
the lookup -- so this one refusal stood in front of forty-seven example models.
The assignment target now carries a subscript, the way the last name of any reference does, and the two files parse. Writing a single element of a scalarised array (often at a run-time index, as the FFT does) is not carried through the rest of the pipeline yet, so a model that actually flattens such a block is refused there, by the element it names, rather than at the file that defines it.
The dependency refusal leaves forty-seven models. They do not reach a new stage -- each meets the next wall at once, an array shape unknown at flatten or an if-equation condition it cannot evaluate, the counts of which rise as this one empties -- so the stage totals are unchanged. No model regressed.
| stage | models | of 430 |
|---|---|---|
| parsed | 415 | 96.5 % |
| flattened | 59 | 13.7 % |
| causalized | 23 | 5.3 % |
| simulated | 21 | 4.9 % |
The leaders are a connect endpoint not seen as a connector (68 in parse), the Clock builtin (62), an
array shape unknown at flatten (24 and 17), a subscript on an inner component in parse, and an
if-equation condition unknown at flatten (14). Agreement is still unmeasured (no omc).
2026-09-10, after index reduction is capped at a real model's depth¶
Not a reachability change, a measurement-speed one, and a better diagnosis. The three DC machine examples that began to flatten last change turned out to be over-determined once flattened -- five more equations than unknowns, a redundancy in the connected machine our flattener does not yet resolve. Index reduction differentiates such a system looking for the equation that would balance it and never finds one, so it differentiated the same chain nineteen times, each derivative doubling an already-large expression until it reached tens of megabytes. The run took minutes and ended in a timeout.
Index reduction now stops when a chain has been differentiated past twelve, far beyond any real model's
index (a constrained multibody model, the deepest, is three), and reports the same over-determined
diagnosis the equation-count budget already gave -- but at once, naming the equation, before the
expressions explode. The three machines now fail in under a second with a message that points at the
redundant equation (dcpm.inertiaRotor.phi = loadInertia.phi), the whole moon run :coverage is back to
fifteen seconds from three minutes, and no well-posed model is touched. The stage counts are unchanged.
| stage | models | of 430 |
|---|---|---|
| parsed | 415 | 96.5 % |
| flattened | 59 | 13.7 % |
| causalized | 23 | 5.3 % |
| simulated | 21 | 4.9 % |
2026-09-10, after a structural if-equation waits for a parameter a later sibling supplies¶
The largest flatten wall was a structural if-equation whose condition names a parameter not yet known.
The machine friction is written if frictionParameters.PRef <= 0 then tau = 0 else tau = ..., and
frictionParameters is set from a data record declared after the machine in the example, so the
condition could not be evaluated where the if sits. Such an if is now held and resolved in a final pass,
once the whole model's parameters have settled and the condition is a number; a condition that still does
not resolve on that pass is the genuine run-time one (a continuous variable, if pipe.volumeFlow ...)
and is refused as before.
The if-equation-condition wall falls from forty to eight (the eight that remain are the continuous
conditions, which want a hybrid when, not this). Three DC machine examples flatten end to end
(DCPM_Start, DCPM_QuasiStatic, DCPM_withLosses), so flattened moves 56 to 59. Their causalize does not
finish inside the sixty-second budget, though: a permanent-magnet machine is a few hundred equations and
the current causalizer is too slow on one that size, a new frontier this exposes rather than a
regression (they failed earlier, at flatten, before). No model regressed, verified by a per-stage set
difference.
| stage | models | of 430 |
|---|---|---|
| parsed | 415 | 96.5 % |
| flattened | 59 | 13.7 % |
| causalized | 23 | 5.3 % |
| simulated | 21 | 4.9 % |
The leaders are the Clock builtin (62), a subscript on an inner component in parse (51), a connect
endpoint not seen as a connector (48), an array-shaped declaration met in parse (44 and 37), and an
array shape unknown at flatten (21). Agreement is still unmeasured (no omc).
2026-09-10, after a fixed attribute may name a boolean parameter¶
The wall the medium constructor exposed. The fluid models write their outlet temperature as
T(start = T0, fixed = T0fixed), with parameter Boolean T0fixed = false a switch the modeller can
flip, rather than spelling fixed = false at the declaration. The fixed attribute was required to be
a boolean literal, so a parameter there was refused. It is now evaluated against the parameter values
gathered so far, the way a condition or a dimension is, and the parameter was declared before the
component, so its value is already in hand; a fixed that resolves to a number rather than a boolean is
still the error it was.
The ten fluid models leave this refusal and meet the next one, an if-equation condition the flattener cannot yet evaluate (the count there rises from 29 to 38). So the stage counts hold at 56 flattened; this is a wall cleared inside flatten rather than a model carried to the end of it. No model regressed.
| stage | models | of 430 |
|---|---|---|
| parsed | 415 | 96.5 % |
| flattened | 56 | 13.0 % |
| causalized | 23 | 5.3 % |
| simulated | 21 | 4.9 % |
The leaders are the Clock builtin (62), a subscript on an inner component in parse (51), a connect
endpoint not seen as a connector (48), an array-shaped declaration met in parse (44 and 37), and an
if-equation condition unknown at flatten (38, now the largest flatten wall). Agreement is still
unmeasured (no omc).
2026-09-10, after a record constructor binding is read as a modification¶
The follow-up to the whole-record binding, the case it left refused. A record component may be given a
constructor rather than a reference: material = BaseData() for the record's own defaults,
R(a = 5) to set a field. This is read the same way now -- each named or positional argument becomes
that member's modification, an empty constructor leaves every member at the record's default -- so a
field the constructor does not name (a final, most often) keeps its own formula rather than being
copied, which is the cleaner reading. A constructor of a different record is a redeclaration, a
change of type rather than a plain binding, and stays refused as one; the flux-tube examples that pick
a specific magnetic material this way wait for that.
This one moves the tally: three flux-tube examples flatten end to end (QuadraticCoreAirgap,
ToroidalCoreAirgap, ToroidalCoreQuadraticCrossSection), whose cores default their material with an
empty constructor. The fluid models whose medium is Medium() flatten past that binding too, then meet
a fixed attribute that has to be a boolean literal, a new frontier of ten. No model regressed.
| stage | models | of 430 |
|---|---|---|
| parsed | 415 | 96.5 % |
| flattened | 56 | 13.0 % |
| causalized | 23 | 5.3 % |
| simulated | 21 | 4.9 % |
The leaders are the Clock builtin (62), a subscript on an inner component in parse (51), a connect
endpoint not seen as a connector (48), an array-shaped declaration met in parse (44 and 37), and an
if-equation condition unknown at flatten (29). The record-constructor redeclaration (10) and the
fixed literal (10) are the next two flatten walls. Agreement is still unmeasured (no omc).
2026-09-10, after a whole-record binding is read member by member¶
The machine models bind one parameter record to another rather than repeating every field:
frictionParameters = dcpmData.frictionParameters. A composite component given a value was refused
outright, and this was the single largest flatten wall, 70 models. A record standing in for another
record means each of its members equals the matching member of the other, so the binding is now read
that way: the value arrived already prefixed into the flat namespace, and each member's copy is written
by appending the member's name to it. A field derived from the others (a final in the record) comes
out consistent because the fields it reads were copied too. Only a reference to another record is
expanded; a value of any other shape (a record constructor, a medium) has no name to read the members
from and stays refused, now with a message that says so.
The 70-model refusal leaves the top of the list. About fifty-six of those models -- the DC and induction machines, which write the binding as a reference -- flatten past it and meet the next wall at once: a connect endpoint the resolver does not see as a connector, an array shape unknown at flatten, an if-equation condition it cannot yet evaluate. So the stage counts hold at 53 flattened and 21 simulated, the frontier moving without the tally, the same shape the connect-in-for change had. The twenty-two that remain bind a record constructor or a replaceable medium, the natural next case.
| stage | models | of 430 |
|---|---|---|
| parsed | 415 | 96.5 % |
| flattened | 53 | 12.3 % |
| causalized | 23 | 5.3 % |
| simulated | 21 | 4.9 % |
The leaders are now the Clock builtin (62), a subscript on an inner component in parse (51), a connect
endpoint not seen as a connector (48), an array-shaped declaration met in parse as expected '=' (44)
or expected ';' (37), an if-equation condition unknown at flatten (29), and a composite bound to
something other than a name (22, the follow-up to this change). Agreement is still unmeasured (no omc).
2026-09-10, after a connect inside a for-loop is unrolled¶
The polyphase library wires a plug's pins in a loop: for j in 1:m loop connect(plug_p.pin[j], pin_n);
end for;. Unrolling the loop substitutes the index everywhere it appears, but the connect was refused
outright rather than substituted -- the reasoning that a connect names components and a loop index
cannot be one of them missed that the index rides on the endpoints, which are indexed. The index is now
substituted into each endpoint, so the loop comes out one connect per element, which the endpoint
resolver reads like any other.
The refusal leaves the work list -- 23 models carried it, the polyphase machines and rectifiers -- but the stage counts hold at 53 flattened and 21 simulated. The models that clear this wall meet the next one at once: a connect endpoint the resolver does not yet see as a connector, the array-of-connectors subtlety these same models lean on. So the frontier moves without the tally.
| stage | models | of 430 |
|---|---|---|
| parsed | 415 | 96.5 % |
| flattened | 53 | 12.3 % |
| causalized | 23 | 5.3 % |
| simulated | 21 | 4.9 % |
The leaders are a composite component with a value (70), the Clock builtin (62), a subscript on an
inner component in parse (42, now the largest), an array shape not known at flatten (29), and a connect
endpoint not seen as a connector (17). Agreement is still unmeasured (no omc).
2026-09-10, after a conditional component gated by an extends-set parameter resolves¶
A transistor puts its heat port behind a condition it inherits: extends ConditionalHeatPort(useHeatPort
= useTemperatureDependency). The gating parameter is set, through the extends, to another parameter of
the class, and that one is declared after it, so its value was not in hand when the parameter was
instantiated -- the condition on the heat port then failed with "nothing in the environment answers
'...useHeatPort'". A parameter whose binding named another was only recorded when the other happened to
come first. The unconditional members are now instantiated first and their values resolved among
themselves to a fixed point, before the conditions are evaluated, so the heat port is kept or dropped by
what the sibling settles on.
Flattening climbs from 50 to 53 of 430 example models, and the "condition must be known" refusal loses
its ten useHeatPort cases -- the analog examples with a transistor or a diode that carries a heat
port. Three flatten through; the rest meet a later wall (an initial-equation form among them). Causalize
and simulate hold at 23 and 21.
| stage | models | of 430 |
|---|---|---|
| parsed | 415 | 96.5 % |
| flattened | 53 | 12.3 % |
| causalized | 23 | 5.3 % |
| simulated | 21 | 4.9 % |
The leaders are unchanged: a composite component with a value (70), the Clock builtin (62), an array
shape not known at flatten (52), a subscript on an inner component and a type alias to an array type in
parse (32 each), and a condition not known at flatten (down to 21). Agreement is still unmeasured.
2026-09-10, after a boolean latch that reads its own pre value is isolated¶
An on-off controller reads its previous state: on = pre(on) and u < hi or u < lo. pre(on) is the
value at the last event, a known in the current phase, not a reference to the current on -- so on
stands alone on the left and its boolean right-hand side is the answer, read off without a derivative
(a boolean or has none). The check for whether the unknown stood alone counted the pre(on) as
on, so it took the linear-solve path instead and causalization fell over. It ignores a pre now, so
the isolated shape catches the equation, as it already does for an ordinary hot = x > 0.5.
Causalize climbs from 22 to 23 of 430 example models: MSL's HeatTransfer...ControlledTemperature,
whose OnOffController is this latch, gets past the fall-over (it meets an initial-equation wall
after). Reachable simulation holds at 21.
| stage | models | of 430 |
|---|---|---|
| parsed | 415 | 96.5 % |
| flattened | 50 | 11.6 % |
| causalized | 23 | 5.3 % |
| simulated | 21 | 4.9 % |
The leaders are unchanged: a composite component with a value (70), the Clock builtin (62), an array
shape not known at flatten (52), a subscript on an inner component and a type alias to an array type in
parse (32 each), and a condition not known at flatten (31). Agreement is still unmeasured (no omc).
2026-09-10, after a package constant in a function body resolves¶
Modelica.Units.Conversions.to_degC reads Modelica.Constants.T_zero in its body and is inlined
wherever a model converts a temperature. The inlined body was prefixed but never took the rewrite that
turns a package constant into its value -- the rewrite an equation gets -- so the name survived as a
reference that flattening then could not resolve, and every model using the conversion failed with
"unknown name 'Modelica.Constants.T_zero'". The body is now resolved in the function's own scope, so a
package constant or an enumeration literal it reads becomes its value before prefixing sees it.
Flattening climbs from 47 to 50 of 430 example models: the electrical and thermal examples that convert
a temperature clear the wall. Reachable simulation holds at 21 -- the newly flattening models meet a
later wall, a derivative of a boolean or among them -- so causalize edges to 22 while simulate stays.
| stage | models | of 430 |
|---|---|---|
| parsed | 415 | 96.5 % |
| flattened | 50 | 11.6 % |
| causalized | 22 | 5.1 % |
| simulated | 21 | 4.9 % |
The leaders are unchanged: a composite component with a value (70), the Clock builtin (62), an array
shape not known at flatten (52), a subscript on an inner component and a type alias to an array type in
parse (32 each), and a condition not known at flatten (31). Agreement is still unmeasured (no omc).
2026-09-10, after a homotopy written with named arguments resolves¶
homotopy(actual = ..., simplified = ...) is how the standard library's ideal op-amp writes the
operator, and every consumer of the call read the actual off the first positional argument -- so with
the arguments named and none positional, the differentiator indexed an empty tuple and the flatten
stage fell over with a bare "tuple index out of range", an internal error rather than a refusal. The
names are resolved to the positional homotopy(actual, simplified) at flatten now, once, so the
derivative, the emitter and the evaluator each read it the way they always did.
Reachable simulation climbs from 19 to 21 of 430 example models: OpAmps.Multivibrator and
SignalGenerator, which build on the ideal op-amp, run end to end. Causalize and simulate stay level
with each other at 21, every model that causalizes also simulates.
| stage | models | of 430 |
|---|---|---|
| parsed | 415 | 96.5 % |
| flattened | 47 | 10.9 % |
| causalized | 21 | 4.9 % |
| simulated | 21 | 4.9 % |
The leaders are unchanged: a composite component with a value (70), the Clock builtin (62), an array
shape not known at flatten (52), a subscript on an inner component and a type alias to an array type in
parse (32 each), and a condition not known at flatten (31). Agreement is still unmeasured (no omc).
2026-09-10, after the multiple-import list parses¶
import Modelica.Blocks.Types.{ExternalCombiTable1D, Extrapolation, Smoothness} brings several names
from one package in a single clause, each the same as writing the qualified import three times. The
standard library's rotational components -- Clutch, LossyGear and their neighbours -- open with two
such clauses, and the dotted-name reader consumed the dot before the brace and then choked on it, so
those files did not parse and every model that depends on them stopped there. The clause is read a
segment at a time now, and a {...} list becomes one import per name, so nothing below the parser
meets a fourth form.
The refusal leaves the work list -- eleven models carried it -- but the stage counts hold at 47
flattened and 19 simulated: the rotational examples that now get past their dependencies meet the next
wall, an array whose shape is not known at flatten (mu_pos, lossTable), so the frontier moves
without the tally.
| stage | models | of 430 |
|---|---|---|
| parsed | 415 | 96.5 % |
| flattened | 47 | 10.9 % |
| causalized | 19 | 4.4 % |
| simulated | 19 | 4.4 % |
The leaders are unchanged: a composite component with a value (70), the Clock builtin (62), an array
shape not known at flatten (52), a subscript on an inner component and a type alias to an array type in
parse (32 each), and a condition not known at flatten (31). Agreement is still unmeasured (no omc).
2026-09-10, after a qualified call to a builtin reads as the builtin¶
Modelica.Math.sin(x) is the builtin sin under its library path, and the standard library reaches the
maths functions this way where a call is not inlined. Both the ends of the toolchain that consume a
call knew sin but not the qualified name: the emitter refused it, "no translation for the function
'Modelica.Math.sin'", and the differentiator had "no derivative rule" for it. Each now reads a
qualified call as the builtin -- the last segment, and only when it is a name that stage already has,
so an unknown call still fails by its full name.
Reachable simulation climbs from 14 to 19 of 430 example models: the rotational and other examples that call a maths function qualified were causalizing and then failing at emit, so this closes the gap between causalize and simulate -- both now stand at 19, every model that causalizes also simulates.
| stage | models | of 430 |
|---|---|---|
| parsed | 415 | 96.5 % |
| flattened | 47 | 10.9 % |
| causalized | 19 | 4.4 % |
| simulated | 19 | 4.4 % |
The leaders are unchanged, all before causalize: a composite component with a value (70), the Clock
builtin (62), an array shape not known at flatten (52), a subscript on an inner component and a type
alias to an array type in parse (32 each), and a condition not known at flatten (31). Agreement is
still unmeasured (no omc).
2026-09-10, after the same element inherited through two paths is merged¶
The other half of the "twice" refusal: a name that reaches a class through two extends paths.
MSL's battery CellData extends both a base record and an icon record, and each declares an identical
constant String CellType, so the name arrives twice -- the same element, which the tools merge, but
flattening refused it, "CellData inherits 'CellType' twice". The inherited-member loop in effective
now compares the two the same way the re-statement fix compares a re-declaration: identical (but for a
description or a placement) is the one element, kept once; a difference is still a clash. A
modification the second extends writes on the name still applies, on top of the member already
placed.
That empties the "twice" refusal from the work list -- 12 two measurements ago, 3 after the
re-statement fix, 0 now. The stage counts hold at 47 flattened and 14 simulated: the battery examples
that clear this wall meet the next one, an array whose shape is not known at flatten (OCV_SOC[:,2]),
so the frontier moves without the tally.
| stage | models | of 430 |
|---|---|---|
| parsed | 415 | 96.5 % |
| flattened | 47 | 10.9 % |
| causalized | 19 | 4.4 % |
| simulated | 14 | 3.3 % |
The leaders are unchanged: a composite component with a value (70), the Clock builtin (62), an array
shape not known at flatten (52), a subscript on an inner component and a type alias to an array type in
parse (32 each), and a condition not known at flatten (31). Agreement is still unmeasured (no omc).
2026-09-10, after an identical re-statement of an inherited component is accepted¶
A derived class may declare a component its base already declares, identically: MSL's
Electrical.QuasiStatic...Interfaces.TwoPin declares SI.AngularVelocity omega, which its base
TwoPinElementary already has, and the power-converter HBridge does the same with fire_p. That is
the same variable written twice, not a name clash, and the tools accept it. Flattening refused it,
"declares 'omega' twice", because a component whose name matched an inherited member was rejected
outright. It now compares the two declarations: identical but for a description or a diagram placement
is the same element, kept once; a declaration that actually differs (a different type) is still a
clash.
The "declares twice" refusals fall from 12 to 3 -- the 3 that remain are the diamond case, a name
inherited through two extends paths (inherits 'CellType' twice), which is a separate merge. The
stage counts hold at 47 flattened and 14 simulated: the models that clear this wall (the quasi-static
resonance examples, the H-bridge drives) meet the next one right after -- a connect to a signal with
two sources, a subscript on an inner component in Complex, a condition not known at flatten -- so the
frontier moves without the tally moving.
| stage | models | of 430 |
|---|---|---|
| parsed | 415 | 96.5 % |
| flattened | 47 | 10.9 % |
| causalized | 19 | 4.4 % |
| simulated | 14 | 3.3 % |
The leaders are unchanged: a composite component with a value (70), the Clock builtin (62), an array
shape not known at flatten (52), a subscript on an inner component and a type alias to an array type in
parse (32 each), and a condition not known at flatten (31). Agreement is still unmeasured (no omc).
2026-09-09, after an enumeration literal in an if-equation condition resolves¶
The condition of a structural if-equation was prefixed but not put through the rewrite that turns an
enumeration literal into its number. The op-amp and limiter blocks the analog library builds on write
if homotopyType == Modelica.Blocks.Types.LimiterHomotopy.NoHomotopy then, a kept structural if
(its guard strict defaults to true), and the literal on the right stayed a name that flattening then
could not resolve, so the model failed with "unknown name". A top-level equation and a when
condition already took the full rewrite; the if-equation condition was the one place that took only a
bare prefix. The rewrite is now one method, shared by all three, so the literal becomes its ordinal
before the condition is evaluated.
Flattening climbs from 31 to 47 of 430 example models, and reachable simulation from eleven to
fourteen: the op-amp examples (OpAmps.Adder, Comparator, and their neighbours) and the models that
instantiate a limiter clear the wall together. The "unknown name" line, which led the flatten work
list two measurements ago, is off the top of it entirely.
| stage | models | of 430 |
|---|---|---|
| parsed | 415 | 96.5 % |
| flattened | 47 | 10.9 % |
| causalized | 19 | 4.4 % |
| simulated | 14 | 3.3 % |
The leaders are now a composite component with a value (70), the Clock builtin (62), an array shape
not known at flatten (52), a subscript on an inner component and a type alias to an array type in
parse (32 each), and a condition not known at flatten (31). Agreement is still unmeasured (no omc).
2026-09-09, after a package constant resolves unqualified and across packages¶
The Modelica.Constants values the library leans on stopped a run at flatten. Two forms were
missing. A bare constant, brought into a model by import Modelica.Constants.pi; or
import Modelica.Constants.*; or declared in the enclosing package, was read as a component that did
not exist and refused with "unknown name" (commit for 0.0.176). A cross-package constant, one
whose own value names a constant in another package -- Modelica.Constants.eps =
ModelicaServices.Machine.eps is the case that mattered, and it accounted for 36 of the models on the
"unknown name" line -- resolved its qualified reference no further than the package it named, so eps
never became its number (this change, 0.0.177). The evaluation now follows a qualified constant
reference into the package it names, bounded by a hop count so a library whose packages name each
other in a ring fails cleanly instead of looping; a same-package chain is still bounded by name.
Flattening climbs from 12 to 31 across the two fixes (17 after the bare form, 31 after the
cross-package one); reachable simulation moves from eight to eleven. Most of the fourteen models the
cross-package fix newly flattens stop a stage later -- an electrical example that reads eps also
carries a connect to a component with an unknown array shape -- so causalize and simulate move by
one each while flatten moves by fourteen. The "unknown name" line falls from 55 to 24; what remains
there is led by an enumeration literal of a library type used as a default (LimiterHomotopy,
sixteen models), which is a separate lookup.
| stage | models | of 430 |
|---|---|---|
| parsed | 415 | 96.5 % |
| flattened | 31 | 7.2 % |
| causalized | 16 | 3.7 % |
| simulated | 11 | 2.6 % |
The work list's leaders are now all flatten walls that are not about names: a composite component
with a value (70), the Clock builtin (62), an array shape not known at flatten (52), and a
condition not known at flatten (31). Agreement is still unmeasured (no omc on this machine).
2026-09-09, after an if inside a when body parses¶
A when body is a run of statements and may hold an if or a for; Blocks.Math's extremum
blocks latch if u < pre(y_min) then t_min = time; else ... inside a when, and the standard
library's samplers do the same. The when-body parser read only simple assignments, so the
compound form was refused at parse. It reads the algorithm statement forms now, and a plain
assignment body is unchanged.
The stage counts do not move (parsed holds 415 / 430): the files that carry this construct meet
another one right after (Blocks.Math an array-valued modification, the Clocked samplers the
Clock builtin), and flattening refuses an if in a when by name -- a conditional in the
discrete-event pipeline is separate work. What moved is the parse frontier: the if-in-when
refusal is gone from the work list, and IfStatement inside a 'when' is not supported yet is now
the honest message, at flatten, for the models that reach it.
| stage | models | of 430 |
|---|---|---|
| parsed | 415 | 96.5 % |
| flattened | 12 | 2.8 % |
| causalized | 10 | 2.3 % |
| simulated | 8 | 1.9 % |
The largest blockers are unchanged: a composite component with a value (66), an array shape not
known at flatten (52), unqualified constants (50), the Clock builtin (49), and, in parse, a type
alias to an array type and a subscript on an inner component. Agreement is still unmeasured.
2026-09-09, after quoted identifiers and the bare operator class¶
Two constructs the operator-overloading half of the library needs, both on Complex.mo and the
files that import it. A quoted identifier, '+' or 'a b', is a name spelled with an operator
or a space; the lexer reads it now, keeping the quotes so it is its own name and round-trips. A
bare operator class, operator '+' ... end '+', is an overload group named by the operator it
overloads; it reads as a package, a namespace it is never instantiated from, so the file loads. The
overloading itself is still refused where an operator is applied.
The unexpected character ' refusal is gone from the work list, and one more example model parses
end to end (parsed 414 -> 415). The Complex.mo chain does not clear yet: it meets its third
construct, a subscript on an inner component (c1[i].re), which the 18 ComplexBlocks models now
share as their blocker.
| stage | models | of 430 |
|---|---|---|
| parsed | 415 | 96.5 % |
| flattened | 12 | 2.8 % |
| causalized | 10 | 2.3 % |
| simulated | 8 | 1.9 % |
a subscript on an inner component (30) has become the largest parse blocker, ahead of a type
alias to an array type (type QuaternionBase = Real[4], part of the 32 [ refusals). The flatten
walls are unchanged: a composite component with a value (66), an array shape not known at flatten
(52), unqualified constants (50). Agreement is still unmeasured (no omc).
2026-09-09, after an array dimension on the type¶
FixedHeatFlow[n] heat puts the array dimension on the type rather than the name, which is the
same declaration as FixedHeatFlow heat[n] and the Fluid examples write it that way. The clause
reads a dimension after the type name now, shared by every name in it; a name's own dimensions
come first, so Real[3] x[2] is x[2, 3], per the specification. The flattener already expands an
array of components, so nothing below the parser changed.
Modelica.Fluid.Examples.BranchingDynamicPipes and its neighbours parse now (parsed 412 -> 414).
Reachability holds at eight. Two of the three [ refusals remain and are separate constructs: a
type alias to an array type (type QuaternionBase = Real[4]) and a subscripted assignment target
in an algorithm (a[1] := 1).
| stage | models | of 430 |
|---|---|---|
| parsed | 414 | 96.3 % |
| flattened | 12 | 2.8 % |
| causalized | 10 | 2.3 % |
| simulated | 8 | 1.9 % |
The flatten walls that lead the work list are unchanged: a composite component that carries a value
(66), an array shape not known at flatten time (52), and unqualified constants in an equation
(pi, eps, 50). Agreement is still unmeasured (no omc).
2026-09-09, after pure/impure and external functions parse¶
Two function-declaration constructs, both on the utility libraries, both cleared from the work
list. pure function and impure function carry a purity hint the tool does not act on, so the
prefix is accepted and dropped. An external "C" [ref =] call [annotation]; body is the whole of
a function implemented in another language; it cannot run here, so it is read and dropped, and a
function that reaches the simulator this way is refused where a call would inline it (it has no
algorithm), not at parse.
Modelica.Utilities (Streams, System, Internal), Modelica.Blocks.Tables and
Modelica.Math.Random are written this way and imported widely, so refusing them blocked a slice
of the library. Six more files parse (2526 files, 105 → 99 refused), and one more example model
parses end to end (parsed 411 → 412). The models behind the two constructs advance to the same
flatten walls the electrical and machine libraries already lead with, so reachability holds at
eight:
| stage | models | of 430 |
|---|---|---|
| parsed | 412 | 95.8 % |
| flattened | 12 | 2.8 % |
| causalized | 10 | 2.3 % |
| simulated | 8 | 1.9 % |
The work list is now flatten work almost top to bottom: a composite component that carries a value
(66), an array shape not known at flatten time (52), an unqualified name in an equation (pi,
eps, from the Constants the utility files pull in, 50 + rarer), and the Clock builtin (49).
Strings.mo, Random.mo and Files.mo still trip on a [ construct (an array-valued
modification) and so did not clear. Agreement is still unmeasured (no omc).
2026-09-09, after a when tuple equation reads =¶
Eight models simulate now, up from four. A when in an equation section takes equations,
and a multi-output call is an equation written (a, b) = f(x). The parser reads a when body as
statements and its tuple case required :=, so the equation spelling was refused. The one file
that trips on it is Modelica.Blocks.Interfaces (its PartialNoise draws a random pair that
way), and that file is imported across the library, so refusing it blocked well over a hundred
example models at the point they looked it up. Either operator is accepted now.
| stage | models | of 430 |
|---|---|---|
| parsed | 411 | 95.6 % |
| flattened | 12 | 2.8 % |
| causalized | 10 | 2.3 % |
| simulated | 8 | 1.9 % |
The four new reachable models are Mechanics again -- Rotational.Examples.ElasticBearing and
Translational.Examples.{Accelerate, SignConvention, WhyArrows} -- which use Blocks sources and
sensors and so needed that file to load. The parse frontier has moved off the Blocks :=/=;
what leads the work list now is genuine flatten work: a composite component that carries a value
(66) and array shapes not known at flatten time (52). Agreement is still unmeasured (no omc).
2026-09-09, after StateSelect -- the first models reach simulation¶
Reachability moved off zero. Four example models parse, flatten, causalize and simulate end to end, the first that have:
Modelica.Mechanics.Rotational.Examples.RollingWheelModelica.Mechanics.Translational.Examples.DamperModelica.Mechanics.Translational.Examples.ElastoGapModelica.Mechanics.Translational.Examples.InitialConditions
StateSelect is a predefined enumeration the language provides but no file declares, a hint on
which variables a tool should prefer as states. The standard library carries it two ways, and
both blocked models: as an attribute, Real x(stateSelect = StateSelect.prefer), and as a
parameter type, parameter StateSelect ss = StateSelect.default. The attribute joins the
accepted set (the hint has no effect here, state selection is not implemented), and the
enumeration is injected into the flattener as a prelude, so its name resolves and its members
map to 1..5 like any other enumeration's. That was the last thing between these four Mechanics
models and a trajectory: they set stateSelect on their flange states.
| models | stage | refusal |
|---|---|---|
| 139 | parse | := versus = inside a when statement (the Clocked library) |
| 59 | flatten | a composite component cannot carry a value |
| 50 | flatten | an array shape not known at flatten time |
| 32 | parse | expected ';', found '[' (an array-valued modification) |
| 19 | parse | impure on a function |
| 17 | parse | a single-quoted identifier ('+', operator function names) |
| ~110 | parse/flatten | the rest; coverage.json has every one |
Agreement is still unmeasured (no omc on this machine), so what these four produce has not yet
been checked against a reference. Reaching a trajectory is the first claim; agreeing with a tool
people trust is the second, and it is the one worth measuring next. The parse frontier is now
the Clocked library's :=/=, and the flatten frontier is genuine work: composite-component
bindings and array equations.
2026-09-09, after the quantity attribute is accepted¶
quantity is a descriptive type attribute, like unit: type Voltage = Real(quantity=
"ElectricPotential", unit="V"). The whole Modelica.Units.SI type set carries it, so the
flattener refusing it as unknown blocked the electrical and machine libraries wholesale. It is
accepted and ignored now, the way unit and displayUnit already were: one name added to the
attribute set.
This was the dominant flatten wall the operator-record change had just exposed (~230 models on one attribute). It is gone from the work list, and the models behind it advanced deep into flattening, so the frontier is no longer one wall but many smaller ones, spread across the libraries that now load:
| models | stage | refusal |
|---|---|---|
| 119 | parse | := versus = inside a when statement (the Clocked library) |
| 52 | flatten | a composite component cannot carry a value |
| 48 | flatten | an array shape that is not known at flatten time |
| 31 | parse | expected ';', found '[' (an array-valued modification) |
| 29 | flatten | the StateSelect enumeration (nothing named 'StateSelect') |
| 19 | parse | impure on a function |
| 14 | flatten | the stateSelect attribute (the sibling of quantity, next) |
| ~100 | parse/flatten | the rest; coverage.json has every one |
Reachability is still 0 %, because the models now reach real semantic gaps rather than a single
descriptive attribute. The next cheap win is the pair the electrical library keeps hitting:
stateSelect, another accept-and-ignore attribute, and the StateSelect enumeration behind its
value. After that the walls are genuine work: array equations and composite-component bindings.
2026-09-09, after the operator class prefix parses¶
operator record and operator function parse now, each as its plain kind (a record, a
function), carried through and refused where the overloading would be applied. This was the
top of the previous work list: Modelica.Units defines dozens of operator record aliases of
Complex, its file would not parse, and every model that imports Modelica.Units.SI (nearly
all of them) needs that file. The file parses now, and one fewer file refuses across the
library (107 to 106).
The stage line did not move (parsed 411 / 430, reachability 0 %), because those 311 models were
already counted as parsed: they reached parse on their own files and then died at flatten
looking up a name in the unparseable Units.mo, attributed back to it. What moved is the work
list, which is the half that matters. Operator overloading is gone from it. The 311 now
flatten Modelica.Units.SI and get much further, to a new and now dominant wall: the
quantity attribute.
| models | stage | refusal |
|---|---|---|
| ~230 | flatten | unknown attribute(s) ['quantity'] on a Real subtype (summed over its many names: Vrms, VNominal, f, ...) |
| 48 | parse | := versus = inside a when statement |
| 19 | parse | impure on a function |
| 10 | flatten | the Clock builtin (nothing named 'Clock') |
| 7 | flatten | a medium's composite component cannot carry a value |
| 6 | parse | a subscript on an inner component |
| ~40 | parse/flatten | the rest; coverage.json has every one |
The quantity attribute is descriptive, like unit: type Voltage = Real(quantity=
"ElectricPotential", unit="V"). The flattener rejects the unknown attribute rather than
carrying it. Accepting (and ignoring) quantity the way unit is already ignored is the next
construct worth building, and it is now what stands between the bulk of the electrical and
machine libraries and their first flatten.
2026-09-09, first run on macOS, after the array and builtin work¶
The harness ran on this platform for the first time. RLIMIT_AS is a Linux guard that macOS
refuses (current limit exceeds maximum limit), and the bare setrlimit was aborting the
whole run before it started. The request is clamped to the current hard limit now, and a
platform that refuses it outright runs without the guard rather than not running at all, with
a note saying so.
The measurement, against MSL v4.1.0 with modelica 0.0.163: parsed 411 / 430 (95.6 %),
one past the 2026-09-01 row. Reachability is still 0 %, and the work list keeps its shape:
one construct stands between the library and its first flatten, and it is the same one.
| models | stage | refusal |
|---|---|---|
| 311 | parse | operator overloading (operator record, reached through import Modelica.Units.SI) |
| 48 | parse | := versus = inside a when statement |
| 19 | parse | impure on a function |
| 10 | flatten | the Clock builtin (nothing named 'Clock') |
| 7 | flatten | a medium's composite component cannot carry a value |
| 6 | parse | a subscript on an inner component |
| 29 | parse/flatten | twelve rarer refusals; coverage.json has every one |
Operator overloading is three-quarters of the library on one construct, and it is a
prerequisite, not a leaf: Modelica.Units.SI defines its unit types with operator record,
and almost every model imports it. Until that parses, the 311 cannot advance to show what
they would fail on next, so reachability cannot move off 0 % no matter what else lands. That
is the next construct worth building for this number: the array, reduction and builtin work
since 2026-09-01, which the fifteen-model example corpus exercises, does not touch it.
2026-09-01, after the base prefix on short classes¶
connector RealInput = input Real — the MSL's most-used connector. The causality is
carried on the class (it can only come from a short definition, so the class round-trips
through the short form), and flattening applies it to every instance whose declaration
says nothing itself. The 58-model row cleared; ten Clocked models now fail in flatten,
resolving the Clock builtin — the first models to get that far. Parsed holds at
410 / 430 (95.3 %); a :=-versus-= parse point (48) surfaced behind it.
2026-09-01, after bare call statements¶
isValidTable(table); — the statement twin of the bare call equation, with the same
empty output list. Parsed 409 → 410 (95.3 %). The work list is three rows now: operator
overloading (311), input on a short class definition (58 — the
connector RealInput = input Real family), and impure (19).
2026-09-01, after comprehensions, in both spellings¶
{x[i] for i in 1:n} and the reduction call sum(x[i] for i in 1:n) are one
Comprehension node; the printer always writes the braces, which parses back to the same
value. Carried like Array, refused beside it at the point of use. Parsed 406 → 409
(95.1 %). The tail rows keep trading places; operator overloading (311) is now
three-quarters of everything left.
2026-09-01, after ranges become expressions¶
parameter Integer columns[:] = 2:size(table, 2) — a range as a binding value. The range
tail now reads in expression() itself, where the specification's simple_expression
puts it, and the for header stopped being special. Parsed 405 → 406 (94.4 %); the
Tables/Sources chain advanced to array comprehensions (37, next).
2026-09-01, after when in a statement position¶
The when-statement reuses the when-equation's node — branches of statements either way —
and the section decides how an assignment inside it prints, extending the existing
:=/= note. Bodies are full algorithm statements. The fold refuses it by name. The
Sources/Tables chain advanced again, to expected ';', found ':' (32). Parsed holds at
405 / 430 (94.2 %).
2026-09-01, after the statement twins¶
assert(...) as a statement and (a, b) := f(x) — the statement twins of the equation
forms that already existed — parse, round-trip, and are refused by name where a model's
section folds into equations. The Blocks.Sources chain advanced one more construct, to
when inside an algorithm section (31 models, next). Parsed holds at 405 / 430 (94.2 %).
2026-09-01, after initial algorithm folds¶
initial algorithm is to initial equation what algorithm is to equation, and it
now parses into Class.initial_algorithms and folds into the initial equations during
flattening. The 32 models blocked on the section header advanced one construct deeper
(their chain now meets expected '=', found '(', 34 models — next). Parsed holds at
405 / 430 (94.2 %): these were dependency cascades, and the frontier inside them moved.
2026-09-01, after slices read where the grammar expects them¶
x[1:3] and A[:, 1] parse: a range was already readable — but only in the header of a
for, and a subscript is the other place the grammar expects one. The bare : gets its
own node (Colon), because it can only ever be the whole subscript. Both are carried the
way Array is and refused beside it at the point of use. Parsed rose 359 → 405
(94.2 %). The remaining list is operator overloading (305 — now 71 % of the library on
one construct) plus the function-declaration rows (algorithm 32, input 25, impure
19) that are its own prerequisites, and tails of a few models each.
2026-09-01, after final on a component clause¶
final parameter Real THD1 = V3/V1 — the element prefix, accepted and discarded the way
the parser's four other final sites already do: finality forbids later modification,
and nothing downstream modifies further yet. One branch in the prefix loop; parsed
345 → 359 (83.5 %). The remaining work list now reads as one story: the function
declaration chain (algorithm sections 32, input 25, impure 19) feeding into
operator overloading (258), plus array slices (51 + 9).
2026-09-01, after connector-array elements wire¶
connect(plug.pin[2], x) is implemented, not merely parsed: the subscript is structural
the way a dimension is, evaluated during flattening, and the wire lands on the element
the array expansion already declared. Connecting a whole array at once, a subscript
anywhere but the last name, and a connect inside a for loop stay refused by name.
Parsed rose 314 → 345 (80.2 %). The frontier is now two constructs: operator
overloading (240) and whatever expected name, found keyword 'final' turns out to be
(90, suddenly visible now that Modelica.Blocks.Tables loads this far).
| models | stage | refusal |
|---|---|---|
| 240 | parse | operator overloading |
| 90 | parse | expected name, found keyword 'final' |
| 47 | parse | array slices ([:]) |
| 10 | parse | expected name, found keyword 'input' |
| 7 | flatten | a composite component cannot have a value (media bindings) |
| 7 | parse | expected an expression, found operator ':' |
| 29 | parse/flatten | rarer refusals; coverage.json has every model |
2026-09-01, after bare call equations parse¶
Connections.root(a.b); is a function call as an equation — specification 8.3.1 with an
empty output list. It now parses into CallEquation with no targets, and flattening
refuses the form by name; the connection-graph semantics behind Connections.root and
Connections.branch are a separate job. The QuasiStatic interface files load, and
parsed jumped 277 → 314 (73.0 %). Reachability still 0 %; operator overloading now
blocks over half the library on its own.
| models | stage | refusal |
|---|---|---|
| 226 | parse | operator overloading |
| 83 | parse | arrays of connectors |
| 44 | parse | array slices ([:]) |
| 35 | parse | expected name, found keyword 'final' |
| 7 | flatten | a composite component cannot have a value (media bindings) |
| 35 | parse | four rarer refusals; coverage.json has every model |
2026-09-01, after assert equations carry¶
assert(condition, message); in an equation section parses and flattens into a new
FlatModel.asserts — prefixed like every equation, never counted by causalization,
because an assert binds no unknown. The simulator does not evaluate asserts yet; the node
records that instead of the model losing its diagnostics. Modelica.Electrical.Analog.
Basic.Resistor and its neighbours load now; the expected '=', found ';' row shrank
48 → 44, and what remains under that message is a different construct
(Modelica.Electrical.QuasiStatic interfaces — likely the overconstrained-connector
operators Connections.branch/root). Parsed holds at 277 / 430; the cleared models
redistribute (operator overloading 190 → 193).
2026-09-01, after matrix expressions parse¶
[1, 2; 3, 4] becomes an ir.Matrix, following ir.Array's design exactly: it survives
parsing and flattening so the file that holds it loads, and evaluate, derive and emit
refuse it by name at the point of use. One matrix in one file had been keeping whole
packages from loading; with those files in, parsed rose 262 → 277 (64.4 %).
Reachability still 0 %.
| models | stage | refusal |
|---|---|---|
| 190 | parse | operator overloading |
| 80 | parse | arrays of connectors |
| 48 | parse | expected '=', found operator ';' |
| 44 | parse | array slices ([:]) |
| 32 | parse | expected name, found keyword 'final' |
| 8 | flatten | a composite component cannot have a value (media bindings) |
| 28 | parse | six rarer refusals; coverage.json has every model |
2026-09-01, after conditional components work¶
Sensor s if useSensor; is implemented, not merely accepted: the condition is evaluated
during flattening — structural the way a dimension is — and a false one removes the
component together with every connect() touching it. The 53 models this construct
blocked moved to their next refusals; one now parses end-to-end (262 / 430, 60.9 %).
Reachability still 0 %.
| models | stage | refusal |
|---|---|---|
| 183 | parse | operator overloading (operator record, mostly via Modelica.Units) |
| 72 | parse | arrays of connectors |
| 66 | parse | matrix expressions |
| 41 | parse | array slices ([:]) |
| 32 | parse | expected name, found keyword 'final' |
| 8 | flatten | a composite component cannot have a value (media bindings) |
| 28 | parse | ten rarer refusals; coverage.json has every model |
2026-08-31, after expandable connector parses¶
Same day, one construct later: the parser now reads expandable connector (instantiating
one is still refused in flattening, where it would mean something). Reachability did not
move — that is the expected shape of this curve for a while — but the work list did,
which is the half that matters. The 258 models that all stopped at Modelica/Icons.mo:629
spread out to their next blockers:
| models | stage | refusal |
|---|---|---|
| 173 | parse | operator overloading — operator record, mostly reached through import Modelica.Units.SI (Units.mo:1090) |
| 72 | parse | arrays of connectors |
| 53 | parse | expected ';', found keyword 'if' — conditional components |
| 40 | parse | expected ']', found operator ':' — array slices |
| 32 | parse | expected name, found keyword 'final' |
| 24 | parse | matrix expressions |
| 8 | flatten | a composite component cannot have a value (media bindings) |
| 28 | parse | eleven rarer refusals; coverage.json has every model |
The stage table is unchanged from the first measurement below (parsed counts a model's
own file and ancestor packages, and those did not move). The harness also learned to
trace one more failure shape back to its cause: an import that does not resolve
because the imported file refused.
2026-08-31, first measurement¶
Against MSL v4.1.0, with modelica 0.0.96. The library is
2157 .mo files; 484 of them refuse to parse. Of the 430 example models:
| stage | models | of 430 |
|---|---|---|
| parsed | 261 | 60.7 % |
| flattened | 0 | 0.0 % |
| causalized | 0 | 0.0 % |
| simulated | 0 | 0.0 % |
Reachability is 0 % (Rumoca reports 28 % on the same library), and agreement is therefore vacuous: nothing reached the reference. "Parsed" counts a model whose own file and ancestor packages parse; every one of the 261 then dies looking up a dependency whose file refused, which the harness traces back to the construct that refused it.
The work list, first refusal per model — kept as taken; a measurement keeps its date:
| models | stage | refusal |
|---|---|---|
| 258 | parse | expected name, found keyword 'expandable' — one expandable connector at Modelica/Icons.mo:629, imported by nearly everything |
| 44 | parse | expected ';', found keyword 'if' — conditional components (... if condition) |
| 40 | parse | expected ']', found operator ':' — array slices |
| 34 | parse | arrays of connectors |
| 24 | parse | matrix expressions |
| 17 | parse | expected name, found keyword 'final' |
| 13 | parse | five rarer refusals; coverage.json has every model |
One caution against optimism: the list shows each model's first refusal only. Parsing
expandable connector would not make 258 models simulate — it would re-run the
measurement and let each of them advance to its next blocker. That is what the number is
for: rerun after every language-coverage change, and publish the new table with its date.