Julia 1.13 release: OMFrontend 1.3.1 (step 6) - #93
Merged
Merged
Conversation
* Local pinned-OMC alignment for the MMJL old-backend port (LOCAL ONLY, do not push)
Co-Authored-By: JKRT <jtinnerholm@gmail.com>
Co-Authored-By: SVAGEN26 <247156613+SVAGEN26@users.noreply.github.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* LOCAL ONLY, do not push: loosen compareTupleIntGt/Lt tuple signatures
Sort comparators only read the first tuple element; the {T} unification
on the second rejected heterogeneous list payloads (Nil vs Cons).
Co-Authored-By: JKRT <jtinnerholm@gmail.com>
Co-Authored-By: SVAGEN26 <247156613+SVAGEN26@users.noreply.github.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* Frontend: @CUniontype migration + Simplify allocation reductions
Two related bodies of work, both gated at frontend 361/361.
@CUniontype migration (recursive prefix-chain types -> one concrete
struct + tag enum):
- DuplicateTree, NFComponentRef (ComponentRef), NFModTable trees migrated;
in-place mutations rewritten as functional reconstruction.
- Remove @nospecialize from cref-taking functions now that ComponentRef is
a single concrete type.
- Add node-only lookupElementNode fast path (NFClass/NFClassTree) and route
10 ENTRY_INFO-unwrap call sites through it.
- newEmptyClassTree/newEmptyFlatClassTree replace deepcopy of the empty tree.
- Fix latent replaceDuplicates bug (rebuilt tree was discarded).
Simplify allocation reductions (NFSimplifyExp/NFSimplifyModel/NFSubscript):
- referenceEq reuse across every simplify reconstructor (cref, subscripts,
array, binary/unary/logic-binary/call-args, if, box, cast, unbox) so
unchanged subtrees are not rebuilt.
- Per-model constant-fold memo (CONST_FOLD_CACHE) in simplifyCall2: identical
pure constant calls (e.g. the orientation matrices of symmetric MultiBody
bodies) are evaluated once, not once per binding. Cache is cleared at the
start of simplifyFlatModel. DoublePendulum instantiate allocations
3,448,441 -> 2,635,025 (-23.6%); analog/rotational models neutral.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BvuMx11xsCQEgUMW4snDSV
* Ceval: evaluate function-local mutable cells once, not per reference
evalExp of a MUTABLE_EXPRESSION (a function local/output variable's cell)
evaluated the cell's binding expression but discarded the result. Locals that
reference each other form a dependency DAG, so a local referenced N times had
its whole binding subtree re-evaluated N times, expanding the DAG into a tree
and re-evaluating combinatorially.
Concretely, constant-folding one MultiBody Frames.from_nxy(constvec, constvec)
call (a 3x3 orientation matrix whose locals e_x/e_y/e_z/n_z_aux cross-reference)
fired evalCall ~8835 times and ran the function-body interpreter ~4418 times for
only 7 distinct evaluations.
Fix: write the evaluated value back into the mutable cell, so later references
to the same local return the cached value. The cache is scoped to a single
function evaluation (the cell is rebuilt per call by createReplacements) and is
invalidated by algorithm assignments, which overwrite the cell.
DoublePendulum instantiate allocations 2,635,025 -> 1,909,098 (-27.5%; -44.6%
from the pre-optimization baseline). Analog/rotational models unchanged.
Frontend 361/361.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BvuMx11xsCQEgUMW4snDSV
* Frontend: make Component variants immutable (functional @assign updates)
Make TYPE_ATTRIBUTE, TYPED_COMPONENT, UNTYPED_COMPONENT and COMPONENT_DEF
immutable structs. All in-place field mutations of these payloads are converted
to functional `@assign` updates (which reconstruct via Accessors and rebind the
local), threaded back into the owning node via `updateComponent!`. Setters that
mutated in place then relied on node/cell aliasing now return the rebuilt value.
Two latent propagation gaps fixed in TypeBinding.jl: an `@assign c.binding` in the
already-typed branch was only installed inside the condition block, so it was lost
when the condition was unbound; `updateComponent!(c, node)` is now called once,
unconditionally, after both update blocks. Also gave the UNTYPED_COMPONENT binding
path its missing `attrs = c.attributes` local.
Rationale: immutable component payloads remove the "who mutated this and when"
aliasing class and are the correctness foundation for parallel instantiation.
Cost is ~5% more allocations per component-heavy model (DoublePendulum
1,909,098 -> 2,008,347), which is an accepted trade for local reasoning / safety.
Analog/rotational models unaffected. Frontend 361/361.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BvuMx11xsCQEgUMW4snDSV
* Inst A1: thread-safe recursion backstops (parallel-instantiation prep)
Make the instClass runaway backstops safe for parallel instantiation:
- Depth guard now uses the existing per-call-stack `instLevel` parameter instead
of the process-global `INST_CLASS_DEPTH` Ref. instLevel is already threaded
through the whole instantiation chain and increments on every extends/component
descent, so it is a correct per-stack depth proxy and thread-safe by
construction. The rare non-incrementing paths (INSTANCED_CLASS re-inst,
EXPANDED_DERIVED base) are covered by the total-calls backstop below.
- `INST_CLASS_TOTAL_CALLS` and `REINSTANTIATION_COUNT` are now `Threads.Atomic`,
bumped via atomic_add!/atomic_xchg!, so parallel workers can increment them
without a data race.
- Removed the `INST_CLASS_DEPTH` global, its increment/decrement, and the
try/finally that existed only to decrement it.
Behavior-preserving single-threaded (the guards are backstops that do not fire
for valid models). Frontend 361/361.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BvuMx11xsCQEgUMW4snDSV
* Frontend: make Class variants immutable (functional @assign updates)
Make INSTANCED_BUILTIN, INSTANCED_CLASS, EXPANDED_DERIVED, EXPANDED_CLASS,
PARTIAL_BUILTIN and PARTIAL_CLASS immutable structs. In-place field mutations
(setPrefixes/setRestriction/setType/setModifier in NFClass.jl, cls.ty in
NFTyping.jl, cls.baseClass in NFInst.jl instExtends) converted to functional
@assign updates, threaded back into the (still mutable) CLASS_NODE via
updateClass.
Continues the "immutable values, mutable identity cells" model: node shells
(CLASS_NODE/COMPONENT_NODE) stay mutable as the identity/cell layer; the Class
and Component payloads they hold are now immutable, so no propagation-through-
cells refactor is needed. Frontend 361/361.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BvuMx11xsCQEgUMW4snDSV
* Frontend: collapse NFType into a single concrete tagged struct
Replace the abstract NFType + 17 record variants with one immutable concrete
struct (NFType) + an @enum NFTypeTag, so every ::NFType/::M_Type-annotated field
and argument across the frontend is concrete. The recursive spine
(ty/elementType/subscriptedTy/subs/types) is now Union{NFType,Nothing} /
List{NFType} instead of an abstract 17-way union, cutting typing dispatch. Nullary
types (TYPE_REAL/INTEGER/...) are interned const singletons (zero alloc).
Call sites are mostly unchanged: the old TYPE_*(...) constructors are kept as
functions, the old field names are kept as struct slots, and each variant is
registered via MetaModelica.compacted_tag_info so @match TYPE_*(...) (positional
and named) keeps working. `x isa TYPE_*` becomes isvariant(x, TYPE_*).
The struct + enum are defined at the forward-declaration site
(FrontendInterfaces/NFInterfaces.jl) because a concrete struct cannot be
forward-declared and `const M_Type = NFType` binds before NFType.jl loads.
Key correctness fix: MetaModelica's generic valueConstructor(v) = hash(typeof(v))
discriminated variants by runtime type; with one concrete struct it is identical
for every variant, collapsing array-vs-scalar cast-routing guards. Added
MetaModelica.valueConstructor(v::NFType) = Int(v.tag). ENUM_LITERAL_EXPRESSION
made non-parametric (its NFType type param was phantom).
Frontend 361/361. DoublePendulum warm-flatten allocs 2,047,555 -> 2,013,999
(-1.6%), bytes +0.3% (wider data structs).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BvuMx11xsCQEgUMW4snDSV
* Frontend: drop now-redundant @nospecialize on NFType/M_Type args
NFType is now a single concrete struct, so @nospecialize on ::NFType/::M_Type
arguments no longer prevents any specialization blowup (there is only one type
to specialize over) and merely hid the concrete type from inference. Unwrap the
148 per-argument @nospecialize(x::M_Type|NFType) sites to plain x::M_Type. Other
@nospecialize wrappers (Class, Expression, ORIGIN_Type, ... still abstract) and
the two function-level @nospecialize statements are left untouched.
Behavioral no-op; Frontend 361/361.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BvuMx11xsCQEgUMW4snDSV
* Frontend: collapse InstNodeType into a single concrete tagged struct
Same hand-written tagged-struct treatment as NFType: the 9 InstNodeType variants
(field-disjoint, so @CUniontype does not apply) become one immutable struct +
@enum InstNodeTypeTag, with nullable slots (parent/definition/ty/originalType).
Nullary variants are interned singletons; old constructor and field names are
kept so construction / @match / field access are unchanged; each variant is
registered via compacted_tag_info; isa -> isvariant (5 sites); and
MetaModelica.valueConstructor(v::InstNodeType) = Int(v.tag) restores tag-based
variant discrimination.
InstNodeType is referenced only in NFInstNode.jl and NFInst.jl, so the
@UniontypeDecl forward-decls were not load-bearing and were removed; the concrete
struct is defined in-place at the top of NFInstNode.jl. Frontend 361/361.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BvuMx11xsCQEgUMW4snDSV
* Frontend: collapse InstNode into a single concrete tagged struct (Step 1: mutable)
Replace the 9 InstNode variants (EMPTY/VAR/EXP/IMPLICIT_SCOPE/NAME/REF/
INNER_OUTER/COMPONENT/CLASS_NODE) with one concrete tagged struct + @enum
InstNodeTag, so every ::InstNode-annotated field and argument across the frontend
is concrete. Kept MUTABLE for this step (identity, memory-pool and box-on-share
optimisations preserved; in-place mutation sites untouched). Constructors and
field names are unchanged, and each variant is registered via compacted_tag_info
so construction, @match and field access keep working; `x isa VARIANT` becomes
isvariant(x, VARIANT); the few colliding variant-dispatch method pairs
(setParent/setOrphanParent/mapExtends/updateImplicitVariabilityComp/replaceClass/
resolveOuter) are merged with internal tag dispatch.
Both InstNodeType and InstNode are defined at the forward-declaration site
(NFInterfaces.jl) before NFType (whose cls field is ::InstNode); the
InstNodeType<->InstNode field cycle is broken by typing InstNodeType.parent::Any.
Two systematic fixes were required for the concrete type:
- P_Pointer.create/createImmutable derive the pointer's type parameter from
supertype(T); for the concrete InstNode that is Any, giving Pointer{Any} which
cannot enter Pointer{InstNode} containers. Pinned both to InstNode.
- refCompare (the ReplTree key comparator) compared nodes by payload pointer,
which is unstable and collision-prone; switched to node identity (objectid).
Frontend 361/361.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BvuMx11xsCQEgUMW4snDSV
* Frontend: InstNode const+concrete fields; Component as single tagged struct
InstNode: keep it a mutable struct but mark the 11 never-mutated fields const
(compiler-enforced immutable identity) and give all fields concrete types instead
of Any (varPointer::Base.RefValue, exp::NFExpression, definition::SCode.Element,
cls::Union{Class,Pointer{Class}}, caches::Vector{CachedData}). The 5 payload fields
updated in place (name/component/nodeType/definition/cls) stay mutable. An earlier
fully-immutable struct was correct but slow on modifier-heavy models (immutable
copies defeat the referenceEq structural-sharing shortcuts), so this is the workable
concrete state.
Component: collapse the 8 field-disjoint variant structs into ONE concrete tagged
struct ComponentImpl <: Component, keeping the abstract Component supertype so
InstNode.component can forward-reference it without the NFType<->InstNode<->Component
concrete cycle (no Any needed). @match now tag-dispatches; the variant-dispatch
functions (typeComponentBinding2/Ref2, typeIterator2, isDefinition, mergeModifier)
are merged into single tag-branching methods, and a latent TYPED_ATTRIBUTE typo
(undefined; should be TYPE_ATTRIBUTE) is fixed.
361/361 OMFrontend tests pass. DoublePendulum flatten: 2.07 M allocations, 85.3 MiB.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FUN9SaGbh8H5F9o1WDxCRM
* Frontend: ClassTree as a single concrete tagged struct
Collapse the 5 ClassTree variants (EMPTY/FLAT/INSTANTIATED/EXPANDED/PARTIAL) into
one concrete mutable ClassTreeImpl <: ClassTree, keeping the abstract ClassTree
supertype so `::ClassTree` fields forward-reference it without a type cycle. @match
now tag-dispatches; classes/components are a small Union (INSTANTIATED stores
Vector{Pointer{InstNode}}, others Vector{InstNode}). Constructors Base.convert the
vector args to the concrete element type (the old per-variant fields coerced for
free). EMPTY tree is an interned const singleton with a real empty LookupTree so
the general lookup methods handle it; getComponents/resolveClass/replaceDuplicates/
applyLocalComponents(WithInstComponentExpressions)/lookupElement(Node) variant-methods
merged into tag-branching single methods; a latent isEmptyTree bug (typeof(tree) isa
VARIANT, always false) fixed.
Move NFImport + LookupTree/DuplicateTree/JLookupTree + NFClassTree includes up (after
NFComponent) so the compacted_tag_info registrations exist before any file @matches
ClassTree variants (otherwise @match falls back to `isa <function>`).
361/361 tests pass. DP flatten: 2.07 M allocs; EngineV6: 27.31 M allocs (both
unchanged vs the prior checkpoint - allocation-neutral).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FUN9SaGbh8H5F9o1WDxCRM
* Binding and modifier as tagged structs
* Added json export examples
* Removed stale example
* Improve frontend tagged unions
* Box class node payloads
* Preserve record ctor node type
* Immutable InstNode with shared component payload cells
InstNode is now a plain immutable struct; updates rebuild the node shell
and thread it back into the ClassTree pointer cells. Mutable payload
cells (COMPONENT_NODE.component as Pointer{Component}, alongside the
existing cls cell) are shared across shell rebuilds so every alias
observes payload updates; updateComponent! preserves node identity and
copyInstancePtr shares the cell.
Node identity for ordering/hashing goes through _refId (payload cell
objectid) since objectid on the immutable node content-hashes the whole
reachable graph. Compact Base.show keeps accidental prints of the now
cyclic node graph terminating.
Full test suite green: 361/361.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YVTXFa8tEvhXeBUU82jMgV
* Parallel sibling instantiation behind OMFRONTEND_PARALLEL_INST
applyLocalComponents_inst fans sibling components out on Threads.@Spawn
(threshold 4, per-worker attribute out-cell, sync barrier before the
inner/outer pass), following the AbsynToSCode parallelization shape.
One reentrant lock serializes the shared-state writers: INST_CACHE, the
instPackage state machine, first-touch class expansion, and inner-node
generation; a task never fans out while holding it.
Correctness fixes surfaced by concurrent scheduling:
- replaceDuplicates2/3/4 resolved duplicate entries with in-place map! on
children vectors owned by the duplicates tree that every instance of a
class shares; resolution now rebuilds entries functionally. Serially the
last resolver was always the next reader, which hid the aliasing.
- expand's fast path required a fully expanded elements tree, not just an
EXPANDED_CLASS shell, since expansion publishes the shell first.
- SHARE_ATTRS attribute-template sharing is disabled while the flag is on;
templates receive per-context modifier merges through the payload cell,
which is only safe in serial order.
- isIdentical returned nothing on the not-identical paths; checkIdentical
rewritten from @matchcontinue to plain code.
Flag off (default) keeps serial behavior: suite 361/361. Flag on with 8
threads: suite 361/361, EngineV6 flatten 1.18x.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YVTXFa8tEvhXeBUU82jMgV
* Lock-free hot paths for the parallel instantiation caches
INST_CACHE is now an atomic snapshot of a Base.PersistentDict: readers
take the snapshot with no lock (this is the per-builtin-scalar hot path),
inserts replace it copy-on-write under the shared lock so none are lost.
instPackage serves the terminal CACHE_STATE_INSTANTIATED hit without the
lock; that state is immutable once reached.
EngineV6, 8 threads: lock conflicts in the instantiate phase 11003 -> 332,
phase time 1.07s -> 0.88s (2.0x over serial), whole flatten 1.18x -> 1.22x.
Suite green in both modes: 361/361 serial, 361/361 parallel.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YVTXFa8tEvhXeBUU82jMgV
* Parallel typing with per-node claim locks
Fan out typeClass's root typeComponents over top-level components. Cross-node
typing (cref dims, binding evaluation, structural-param marking, function
typing) is serialized by reentrant per-node claim locks keyed by payload-cell
id, acquired along dependency edges only. Shared class nodes and aliased
dimension vectors get their own claims; terminal read-only class states skip
claiming and the claim registry is sharded to keep contention low. Typing
recursion depth is task-local.
Suite 361/361 in both serial and parallel modes; serial allocations unchanged.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* Speed up resolveConnections 3x
ConnectionSets.find used a thrown KeyError as its not-found branch, paying an
exception per first-seen connector; use a get sentinel instead. Make the cref
hash for the connection-graph hashtable structural instead of rendering the
cref to a string. Add EXECSTAT sub-step timers to resolveConnections. The
string-based Entry hash and the extractSets iteration are kept as-is on
purpose: their dict order defines the connection-equation output order that
the reference tests encode.
Suite 361/361 in fresh serial and parallel (-t 8) processes.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* Dict-backed cref tables and exception-free connection-graph walk
Replace the three ported BaseHashTable closure-tuple modules with Dict-backed
tables keyed by CrefHashKey (structural hash and equality). Rewrite the graph
walk plainly: canonical threw an exception per union-find root hit,
connectComponents carried two dead shouldFail branches per edge, and five more
functions used matchcontinue fallthrough on failing table lookups.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* Run parallel frontend paths in tests by default; fix env-flag baking
runtests.jl enables PARALLEL_INST when threads are available unless
OMFRONTEND_PARALLEL_INST is set, and warns when single-threaded. Env-derived
const Refs (PARALLEL_INST, CACHE_INST, ENABLE_EXECSTAT) baked their
precompile-time value; re-read them in __init__ so the environment at load
time wins.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* Nested parallel typing via claim-scope split
Type a component's payload under its claim, release, then type children, so
subtree fan-out never sync-waits while holding a lock. All claim acquisition
goes through _withClaim, which tracks a task-local held count; fan-out sites
require zero held claims and degrade to serial otherwise. typeComponents no
longer needs the root-only fanOut flag: FLAT_TREE walks run unclaimed with a
short claim around the rare inner-outer write-back.
Suites 361/361 fresh -t 8 (parallel default) and -t 1 (serial).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* Gate Connections-operator evaluation on usage and containment
evalConnectionsOperators ran the rebuilding expression map with a heavyweight
closure over every equation to find a handful of operator calls. Track
operator usage at typing time (System.getUsesConnectionsOperators, reset per
translation) so operator-free models skip the walk, and gate the map per
expression on an early-exit contains check. Adds oc:* EXECSTAT sub-timers to
the overconstrained phase.
Suites 361/361 fresh -t 8 (parallel default) and -t 1 (serial).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* Generate equality-constraint equations lazily for broken edges
The collection loop typed the equalityConstraint replacement equations for
every overconstrained connection although only broken edges consume them.
Attach an empty vector per edge, remember the generation inputs keyed by the
vector's identity, and fill the vectors of broken edges after the spanning
tree is computed. Cache the equalityConstraint/fill function refs per class.
Suites 361/361 fresh -t 8 (parallel default) and -t 1 (serial), including
the overconstrained reference tests.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* Parallel inst on by default
* Fix two expandable-connector hangs; FullRobot flattens
addElementsToFlatTree iterated its class/component vectors while appending to
them, which never terminates; append to copies instead (also stops mutating
the input tree). elaborateExpandableSet deduplicated undeclared elements with
an IdSet, whose objectid content-hashes the whole reachable instance graph now
that connectors are deeply immutable; dedupe by node name instead. Remove a
stray debug println. Add an ExpandableBus reference test and a FullRobot
MSL 4.0.0 flatten test.
Suites 363/363 fresh -t 8 (parallel default) and -t 1 (serial).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* Modularize instClassInProgramFM2 into pipeline stage functions
The pipeline driver now reads as its stages: prepareInstEnvironment,
instantiateRootClass, instantiateExpressions, typeClass, flatten,
resolveAndEvaluate (with integrateDOCCEquations! for DOCC models),
simplifyAndCollect, and scalarizeAndVerify. Behavior, EXECSTAT labels, and
dump points unchanged; drops one dead local.
Suites 363/363 fresh -t 8 (parallel default) and -t 1 (serial).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* Parallel binding and section typing
Fan out typeBindingsRefs and typeClassSections with the claim-scope split
used by typeComponents: own payload under the node claim, children after
release, per-worker scratch refs, claimed write-backs. Adds permanent
ty:components/bindings/sections sub-timers to typeClass.
Make lazy function instantiation atomic per function node (the cache check
plus instFunction2 ran unclaimed and corrupted overload lists under parallel
typing), and lock three shared caches reachable from parallel workers:
the inline body-info memo (also reset per translation), the const-fold
cache, and the re-instantiation diagnostics.
FullRobot typeClass 0.50s -> 0.27s warm with identical output.
Suites 363/363 fresh -t 8 (parallel default) and -t 1 (serial).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* Despecialize the expression-typing spine entry
typeExp/typeExp2 carried @nospecialize + @nospecializeinfer, whose entry
boxing cost ~16 allocations and most of a microsecond per expression node —
the dominant cost of binding typing. Removing them makes the literal path
allocation-free with a concrete return type, cuts typeBinding time 44% on
FullRobot, and does not regress precompile time. Keeps the permanent
ty:* sub-timers.
Suites 363/363 fresh -t 8 (parallel default) and -t 1 (serial).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* Document using-OM compaction caveats in INSIGHTS
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* Convert SUBSCRIPTED_EXP subscripts to DAE.Subscript
DAE.ASUB.sub is List{Subscript}; toDAE(::Subscript) yields DAE.INDEX/SLICE/
WHOLEDIM, where toDAEExp gave a bare Exp. Part of the DAE.ASUB.sub migration.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* Document the DAE.ASUB.sub migration in INSIGHTS
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* Document heavy-suite ASUB tail and compaction/solver findings
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* Use isvariant for CLASS_TREE_FLAT_TREE match in instUnqualified
NFImport.jl is included before NFClassTree.jl registers
compacted_tag_info(CLASS_TREE_FLAT_TREE), so a @match pattern on that compacted
type here expands (at macro-expansion time) to a plain isa, which throws on the
constructor function. Use explicit isvariant instead, matching NFInst.jl. Fixes
EngineTest flat-Modelica export (MSL flat-generation tests).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* ErrorExt: implement popCheckPoint/pushMessages/freeMessages/isTopCheckpoint
popCheckPoint returned an undefined local (UndefVarError: handles) and
pushMessages/freeMessages were warn-only stubs. Popped messages now park
in a handle-keyed side table and are re-added or discarded on demand;
isTopCheckpoint reports whether any checkpoint exists (ids are not stored
in this port).
Co-Authored-By: JKRT <jtinnerholm@gmail.com>
Co-Authored-By: SVAGEN26 <247156613+SVAGEN26@users.noreply.github.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R4qFUEPFkDqrnqgpu3buz3
* Sync clocked operator polymorphic names with OMC NFModelicaBuiltin
Operators typed via the NFBuiltinCall special paths bypass the
__OpenModelica_UnboxArguments annotation, so a <T> polymorphic input is
boxed by matchPolymorphic and the box survives into the backend. OMC
declares these operators with __Any (previous with __ComponentExpression),
which never boxes. Affected: shiftSample, backSample, previous, hold,
noClock, subSampleExpression, superSampleExpression.
Co-Authored-By: JKRT <jtinnerholm@gmail.com>
Co-Authored-By: SVAGEN26 <247156613+SVAGEN26@users.noreply.github.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VyEDGUgc4QFuQqfA2XrB9A
* Implement System.getUUIDStr via Base.UUID
Co-Authored-By: JKRT <jtinnerholm@gmail.com>
Co-Authored-By: SVAGEN26 <247156613+SVAGEN26@users.noreply.github.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VyEDGUgc4QFuQqfA2XrB9A
* Implement System runtime stubs: regex, realtimeTick/Tock, numProcessors, appendFile
POSIX-basic group syntax converts to PCRE for the regex; realtime clocks
use a per-index nanosecond tick store.
Co-Authored-By: JKRT <jtinnerholm@gmail.com>
Co-Authored-By: SVAGEN26 <247156613+SVAGEN26@users.noreply.github.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VyEDGUgc4QFuQqfA2XrB9A
* Implement System.launchParallelTasks as an order-preserving serial map
Co-Authored-By: JKRT <jtinnerholm@gmail.com>
Co-Authored-By: SVAGEN26 <247156613+SVAGEN26@users.noreply.github.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VyEDGUgc4QFuQqfA2XrB9A
* Implement System.dladdr as a symbol-name formatter
Co-Authored-By: JKRT <jtinnerholm@gmail.com>
Co-Authored-By: SVAGEN26 <247156613+SVAGEN26@users.noreply.github.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VyEDGUgc4QFuQqfA2XrB9A
* Implement System.unquoteIdentifier and import System into AbsynUtil
Mirrors OMC SystemImpl__unquoteIdentifier: identity unless quoted or
containing a dollar sign, else _omcQ prefix with _XX hex escapes for
non-alphanumerics.
Co-Authored-By: JKRT <jtinnerholm@gmail.com>
Co-Authored-By: SVAGEN26 <247156613+SVAGEN26@users.noreply.github.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VyEDGUgc4QFuQqfA2XrB9A
* Add SCodeUtil.lookupAnnotationBinding
Co-Authored-By: JKRT <jtinnerholm@gmail.com>
Co-Authored-By: SVAGEN26 <247156613+SVAGEN26@users.noreply.github.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VyEDGUgc4QFuQqfA2XrB9A
* Implement System.dgesv (Gaussian elimination, dependency-free)
Accepts untyped lists (callers pass Cons{Any}); partial pivoting with
info = k on a singular pivot and a zero vector result, matching LAPACK
info semantics.
Co-Authored-By: JKRT <jtinnerholm@gmail.com>
Co-Authored-By: SVAGEN26 <247156613+SVAGEN26@users.noreply.github.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VyEDGUgc4QFuQqfA2XrB9A
* Changed uniontytpes into a new macro
* AbsynUtil: add pathSetLastIdent(Path, String) overload matching current OMC
Co-Authored-By: JKRT <jtinnerholm@gmail.com>
Co-Authored-By: SVAGEN26 <247156613+SVAGEN26@users.noreply.github.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018TvSJJcarsPReB19o8SJZH
* Stop binding type variables through list parameters alone
ImmutableList's empty list is a singleton without an element parameter,
so a where-clause variable that a method binds only through a List{T}
parameter is unbound for nil. traverseListGeneric, NFRecord.name, and
DiffAlgorithm.diff take a bare List and no longer use the variable; two
Nil{Any}() pattern spellings in SCodeUtil are nil. Minor version 1.3.0.
Co-Authored-By: JKRT <jtinnerholm@gmail.com>
Co-Authored-By: SVAGEN26 <247156613+SVAGEN26@users.noreply.github.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* Drop the remaining list-bound type variable uses in DiffAlgorithm
Co-Authored-By: JKRT <jtinnerholm@gmail.com>
Co-Authored-By: SVAGEN26 <247156613+SVAGEN26@users.noreply.github.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* INSIGHTS: record the Dimension migration to @T_Uniontype
Notes for the next session: NFDimensionImpl is the concrete type and the remaining hand-written blocks are candidates for the same migration.
Co-Authored-By: JKRT <jtinnerholm@gmail.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* Version 1.3.1; allow OMParser 0.2
OMParser 0.2.0 adds Julia 1.13 support and per-version parser libraries.
Co-Authored-By: JKRT <jtinnerholm@gmail.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: JKRT <jtinnerholm@gmail.com>
Co-authored-by: SVAGEN26 <247156613+SVAGEN26@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
The order of generated connection equations follows the iteration order of the connection-set Dict, which follows the hash of each entry's cref string. The reference tests encode that order. Julia 1.13 replaced hash(::String) (MurmurHash3 -> rapidhash, JuliaLang/julia#57509) and the default seed of hash(x) (0 -> Base.HASH_SEED), so on 1.13 the equations came out permuted and 44 reference tests failed. Hash entries with Julia 1.12's string hash, written in plain Julia (MurmurHash3_x64_128 as in memhash_seed), and seed 0. Same values as Base.hash on 1.12; checked against memhash_seed for 4,260 strings on 1.12 and 1.13. Tests: 363/363 on Julia 1.12.7 and 1.13. Assisted-by: Claude Opus 5.5
The UNTYPED_BINDING arm rebuilt the binding from an undefined variable (`bindinExp`) and without its scope, so it threw whenever the mapped expression changed. The FLAT_BINDING arm built the new binding and then returned the old one, dropping the mapped expression. Both now return the rebuilt binding, as OMC's Binding.mapExp does. New unit tests cover both. Assisted-by: Claude Opus 5.5
The compacted uniontypes (@CUniontype, @T_Uniontype) and isvariant come from MetaModelica 0.4.0; the registered 0.3 releases lack them. Assisted-by: Claude Opus 5.5
- .github/workflows/omjl.yml calls OM.jl's integration workflow (JKRT/OM.jl .github/workflows/omjl-ci.yml) on every pull request and on pushes to master and 1.13: the OMFrontend tests against the other OM.jl packages at the matching branch, then OM.jl's test suite, on Julia 1.12 and 1.13. - ci.yml, Documenter.yml, build-binary.yml: the ten sibling checkouts are one step with OM.jl's checkout-siblings action (the PR branch if a sibling has it, else the PR base branch, else master) instead of master, and from OpenModelica instead of the stale JKRT forks of ArrayUtil, DAE and ListUtil. ci.yml also runs on pushes to 1.13. Assisted-by: Claude Opus 5.5
inlineSimpleCall inlines every simple function, also Inline=false ones, so the relations of a function body (MSL Frames.Internal.maxWithoutEvent: `if u1 > u2 ...`) became event-generating relations of the model. MLS 8.5: relations in function classes are implicitly noEvent. The inlined body's relations are now wrapped in noEvent (the noEvent function is looked up once from the model's top scope, passed down from instantiation). The Tank references in connectGenerated.jl change accordingly. parentScope of a derived class recursed forever when lastBaseClass returned the node itself (a flattened class, e.g. `connector C = A;` as the root, or a short-function alias). As in OpenModelica's NFInstNode.parentScope: go to the parent directly then, and to the top scope for builtin base classes. Assisted-by: Claude Opus 5.5
NFVerifyModel counted the target of a reinit as a variable a when branch solves, so a `when ... elsewhen` whose branches reinit different states (or reinit in one branch only) was rejected. OpenModelica counts only equalities and if-equations (NFVerifyModel.mo); now so does OM.jl. The mismatch path itself crashed: expandCrefSet assigned expandCref's (exp, expanded) tuple to one variable, and ElementSource_getInfo was not qualified (also in four error paths of NFEvalFunction.jl), so an invalid model got an UndefVarError or a conversion error instead of its message. Tests: whenVerifyTests.jl (Models/WhenVerifyTest.mo): an elsewhen with a reinit, and branches that set different variables (the error message). Assisted-by: Claude Opus 5.5
Compile-time function evaluation let an assignment change more than its
target:
- assignArrayElement wrote an element into the array's element vector in
place. Those vectors are shared: fillType, fill() and zeros() repeat
one row, and `v := u` shares u's elements, even a package constant's.
Every row of an unbound output became the last row written, so the
MSL's symmetricTransformationMatrix(m) came out with equal rows and 17
QuasiStatic.FundamentalWave models were unbalanced; writing a copy of
a constant argument changed the constant for later uses. The vector
is now copied first, as the slice arm already did.
- fillType and fillArgsDimVec lifted the dimensions outermost-first, so
a non-square array came out transposed and a function filling an
unbound `output Real y[2, 3]` stayed unevaluated. Innermost-first
again, as in OpenModelica and evalBuiltinFill2. The unused
two-argument fillArgs is deleted.
- A record variable evaluates to a record of its field cells, and
assignments and arguments stored those cells: after `r2 := r1` or
`rs := {r1, r2}`, `r1.x := 7` changed r2 and rs[1], and a callee could
write the caller's record through an output bound to its input.
detachCells replaces the cells with their current values. NFCeval's
RECORD arm lost a loop that did nothing.
Flatten allocations (DoublePendulum, MultiPhaseInductance, AIMC_DOL)
are within noise.
Tests: funcEvalAssignTests.jl (Models/FuncEvalAssign.mo), one case per
path; each fails without its fix.
Assisted-by: Claude Opus 5.5
simplifyIfStmtBranches drops branches whose condition is literal false and replaces the statement with the body of the first literal-true one (an else is one). The port swapped append!'s arguments: the body went into a throwaway vector and was lost. Every function or algorithm whose if/elseif selects a branch on constants lost it: the MSL ReferenceAir specificEntropy (if dT_explicit ... elseif pT_explicit ...) returned 0, and so did cp, cv, kappa, velocityOfSound; `if <false> then A else B` lost B. Upstream prepends to a reversed list; here elements are forward. Assisted-by: Claude Opus 5.5
An instantiated function becomes a root class, and scopePath stopped at root classes, so a function of a protected package inside a function was named from the package: the MSL Media T_h and T_ps each have a package Internal (OneNonLinearEquation), and both became Internal.solve and Internal.f_nonlinear. Flattening keeps the first of equal names, so one inversion ran the other's function (T(s) solved with h(T): a bracket error, depending on the order). The path now continues through a function root, as upstream's does through a function's scope. Assisted-by: Claude Opus 5.5
A Connections.branch under an if-equation on a discrete condition (a
dynamic overconstrained connector, Tinnerholm, Casella, Pop 2022):
if closed then
Connections.branch(port_a.omegaRef, port_b.omegaRef);
port_a.omegaRef = port_b.omegaRef;
end if;
handleOverconstrainedConnections now takes these if-equations out as
conditional branches and resolveModes runs findResultGraph once per
mode (each branch present or not, at most 8 branches). The Connections
operators are evaluated per mode; the equations alike in all modes stay,
the others (the active branches' bodies, the isRoot equations the modes
decide differently) become one if-equation over the conditions. For
System4:
if T2.closed then T2.port_a.omegaRef = T2.port_b.omegaRef;
else G2.port.omegaRef = G2.omega; end if;
The breaker's when changes T2.closed and the event switches the roots;
nothing is rebuilt. Errors name the mode when the modes break different
connections or have different numbers of equations.
resolveAndEvaluate no longer integrates DOCC if-equations
(integrateDOCCEquations!), so DOCC_equations stays empty and the
backend takes its ordinary path. The runtime reconfiguration path is
left in place, unreached.
Test: System5 (the branch under T2.closed) flattens to that
if-equation, G1 the root in every mode.
Assisted-by: Claude Opus 5.5
The equality-constraint equations of a connect the spanning tree breaks are generated lazily (dd374c9): each edge carries an empty vector that the edges which broke fill, found by the vector's identity. But addConnection stored arrayList(vector), nil for an empty one, so the lookup never matched and no broken connect got its equations. A MultiBody loop lost its 3 orientation residues (Loops.Fourbar1: "The system is unbalanced", 3 equations short). The edge now holds the vector itself; after the fill the broken edges carry lists, as NFFlatten.resolveConnections takes them. Fourbar1 flattens with j2.frame_b.R.equalityConstraint(...)[1..3] = 0 and simulates: j1.phi(5) = 40.3292 (reference 40.3282). Found by the review agent of the DOCC stage. Assisted-by: Claude Opus 5.5
…anch per root assignment Review of b14e190: - The conditional branches' bodies go through the Connections operators of their mode too (an isRoot in a body reached the backend). - The equations common to all modes are counted: one that appears twice in every mode is common twice, and a mode with an extra copy keeps it. - The balance check counts a record equation by its scalars (isScalar holds for records: Orientation is 12). - Modes that add the same equations share one branch, its condition the or of theirs: one branch per distinct root assignment, not 2^k. - The partition into operator equations and the rest is one pass; the broken-connect key of the first mode is computed once. - The if-equation keeps the first conditional branch's source; the header states the restrictions (errors) and that the roots depend on the conditions at that instant only (JKRT). - The frontend's conditional-branch test model is System4ConditionalBranch (OM.jl's System5 is the re-closing breaker). Assisted-by: Claude Opus 5.5
…el's DOCC fields The frontend resolves dynamic overconstrained connectors per mode (resolveModes, b14e190); nothing reached the runtime path since. Gone: integrateDOCCEquations!, evalInitialEqMapping, collectDOCCS, containsDOCC, branchDirectiveExists, findRootEquations, evaluateOperatorsReturnTrueIfRoot, and FLAT_MODEL's DOCC_equations, unresolvedConnectEquations and active_DOCC_Equations (with their printing). 175 lines. All packages' tests pass on 1.13 (OMFrontend 375/375, OM.jl 779/779 with DOCC in the suite again). Assisted-by: Claude Opus 5.5
The array-equation branches of scalarize tried tryEvalExp on every side, continuous ones included. A discrete or continuous expression cannot evaluate to a constant: evalExp found that out by throwing, after walking the function bodies it called (0.3 to 7 ms per equation on MultiBody's frame functions from_nxy and resolve1). That was three quarters of Engine1a's scalarize (0.106 s -> 0.044 s; hot flatten 0.298 s -> 0.241 s). The failed evaluations had also folded parameters as a side effect (the thyristor rectifiers' frequency f became a literal). Across the 415 MSL models the frontend passes, the flat models differ only there; the changed models keep their coverage stages (8 validate, 1 simulate). Assisted-by: Claude Opus 5.5
With the parallel path on (PARALLEL_INST, 2 or more threads), 11 of 415 MSL models gave a different flat model on each run: which parameters were folded to literals varied (J = 0.15 or J = dcpm.Js). A parameter bound to a structural parameter's cref becomes structural itself (checkComponentBindingVariability), reading the referenced parameter's variability when the binding is typed; with the bindings typed in parallel that was timing-dependent. - typeBindingsRefs types the bindings serially, also under parallel typing (instantiation and component and section typing stay parallel). - Component read-modify-writes under the node's claim on the component as it is then: typeComponentSections (the section fan-out evaluates parameters, and writing back the copy read before dropped an evaluated binding) and evalComponentStartBinding's start attribute (setStartBinding!; attribute typing writes that node under its own claim). Checked on 415 MSL models: parallel runs are deterministic and equal to the serial flat model; 70.6 s summed hot flatten against 90.3 s serial. Test: "Parallel typing gives the serial flat model" (skipped with one thread). Assisted-by: Claude Opus 5.5
The frontend uses failures as control flow (fail(), @match, @matchcontinue), and a throw records a backtrace: 0.5 to 2 ms per throw on macOS. Both entry points run instClassInProgramFM2 in MetaModelica.with_cheap_throws, where those failures throw without one. Engine1a hot flatten 0.275 s -> 0.204 s (12 threads); one flatten has 57 such throws in the entry's task. - The parallel fan-out's tasks throw normally: opening a scope costs one ordinary throw, and a scope per task made Engine1a 11x slower. - evalComponentBinding2 rethrows with rethrow() instead of throw(e): no new backtrace. - An escaped failure's "Internal stack trace" says to set METAMODELICA_CHEAP_THROWS=false instead of printing unrelated frames. - Requires MetaModelica 0.4.2 (with_cheap_throws). Flat models of the 415 MSL models unchanged. Assisted-by: Claude Opus 5.5
A failed base-class lookup added its error message and then called throw(e) with no e in scope, so it raised an UndefVarError (which @matchcontinue reports and rethrows) instead of failing. Assisted-by: Claude Opus 5.5
Pushes to 1.13 and pull requests into it run OM.jl's integration workflow on Julia 1.13 only; master keeps 1.12 and 1.13. Assisted-by: Claude Opus 5.5
…t converts Scalarization removed every if-branch left without equations or statements. An empty branch before others is not removable: where its condition holds, none of the later branches runs. With mode = 1, `if mode == 1 then else y := 5; end if` set y. Now only trailing empty branches are dropped (scalarizeIfEquation, scalarizeIfStatement). convertIfStatement failed with a MatchFailure when only the else branch was left (the conditions evaluated to false and their branches were removed): the statement is now its body under `if true`. From the audit of silent catch-alls (~/REPORTS/2026-10-01-omjl-catchall-audit, stage A3). Assisted-by: Claude Opus 5.5
- convertWhenEquation built the DAE when-chain from the first branch,
so the last branch became the outermost: every when/elsewhen chain
was reversed (OpenModelica iterates listReverse(whenBranches), as the
statement version already did). Disjoint conditions hid it;
`when initial() then .. elsewhen ..` lost its initial arm.
- simplifyTupleElement took statements only: a tuple initial equation
failed in the frontend.
- typeIterator had a method for a range only: a reduction or a for over
an array (`sum(v[i] for i in {1, 3})`) was a MethodError. The array is
typed as an expression, as OpenModelica's typeIterator types any range.
From the audit of silent catch-alls
(~/REPORTS/2026-10-01-omjl-catchall-audit, stages B2, B3 and B5).
Assisted-by: Claude Opus 5.5
System.sprintff and snprintff called the variadic snprintf through a plain ccall, which passes the double where snprintf does not read it on Apple arm64: a constant String(0.7) evaluated to "1.26481e-321". They use @CCall with the double as a variadic argument now. The format form String(r, format = "8.3f") also lacked the "%" OpenModelica adds (it gave "8.3f"); the format is checked (flags, width, precision, then f, e, E, g or G). Test stringEvalTests.jl. Assisted-by: Claude Opus 5.5
The OM.jl packages are verified on Julia 1.13 only (since 2026-09-28): compat julia = "1.13" (added where it was missing), and every workflow tests on 1.13. Assisted-by: Claude Opus 5.5
One version per package for the release (bumped where the version was already registered in the OpenModelica registry), and every OM.jl sibling dependency bounded below by its release version: the packages are developed in lockstep, and the registry must not pair this version with an older sibling. Assisted-by: Claude Opus 5.5
Windows exports no snprintf symbol (the Universal CRT defines it inline), so evaluating String(r, ...) of a constant threw there (CI on Windows, frontendResultTest "String() of constants"). Printf takes the same C conversions (flags, width, precision; f, e, E, g, G) and needs no variadic ccall; the results equal the OpenModelica references (378/378). Assisted-by: Claude Opus 5.5
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Merges the 1.13 line into master for the Julia 1.13 release of the OM.jl packages: OMFrontend 1.3.1.
Changes (24 commits)
Merge order
The OM.jl packages are released in lockstep. Merge in this order, and register each package in the
OpenModelica registry right after its merge, so that the next one resolves against it:
Please use Create a merge commit (not squash): OM.jl's submodule pointers and the sibling checkouts in CI
refer to these commits.
Verification (local, macOS, Julia 1.13 with threads)
OMFrontend 378/378, OMLibraryTesting 25/25, MetaModelica 262/262, OMRuntimeExternalC 274/274).
release-1.13, and OM.jl's integrationworkflow checks out the siblings at that branch.
Notes
julia = "1.13"in compat, and every workflow tests on 1.13.🤖 Generated with Claude Code