Skip to content

Julia 1.13 release: OMFrontend 1.3.1 (step 6) - #93

Merged
JKRT merged 25 commits into
masterfrom
release-1.13
Oct 2, 2026
Merged

JKRT merged 25 commits into
masterfrom
release-1.13

Conversation

@JKRT

@JKRT JKRT commented Oct 2, 2026

Copy link
Copy Markdown
Member

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)

  • Project.toml for the 1.13 release: version and sibling compat
  • Julia 1.13 only: compat and CI
  • String(Real) of constants: variadic snprintf, and the format's "%"
  • When/elsewhen branches in source order; typed tuple initial equations
  • Only trailing empty if-branches are dropped; an else-only if statement converts
  • CI: the 1.13 line is tested on Julia 1.13
  • lookupBaseClassName fails where it threw an undefined variable
  • Frontend failures without backtraces (MetaModelica cheap throws)
  • Parallel typing gives the serial flat model
  • Scalarize evaluates constant and parameter expressions only
  • Remove the runtime DOCC path: integrateDOCCEquations! and the FlatModel's DOCC fields
  • DOCC modes: operators in the branch bodies, counted equations, one branch per root assignment
  • Broken connects get their equalityConstraint equations again
  • DOCC: resolve the OCC graph per breaker state, switch at run time
  • Name a class inside a function by the function's full path
  • Keep the body of an if-statement branch selected by a constant
  • Evaluate assignments in functions without sharing values
  • A reinit is not an equation a when branch solves
  • Keep function bodies free of events when inlining; parentScope guard
  • Test changes together with OM.jl; check out siblings by branch
  • Require MetaModelica 0.4
  • Fix mapExp for untyped and flat bindings
  • Keep the connection-equation order stable across Julia versions
  • 1.13: local working line (v1.3.1) (1.13: local working line (v1.3.1) #92)

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:

Step Package Version
0 OM.jl (CI workflow) (JKRT/OM.jl) -
1 ImmutableList (OpenModelica/ImmutableList.jl) 0.4.2
1 OMRuntimeExternalC (OpenModelica/OMRuntimeExternalC.jl) 0.3.3
2 MetaModelica (OpenModelica/MetaModelica.jl) 0.4.2
3 Absyn (OpenModelica/Absyn.jl) 1.3.5
3 DoubleEnded (OpenModelica/DoubleEnded.jl) 0.2.3
3 ArrayUtil (OpenModelica/ArrayUtil.jl) 0.3.2
4 ListUtil (OpenModelica/ListUtil.jl) 0.4.1
4 SCode (OpenModelica/SCode.jl) 1.2.0
4 OMParser (OpenModelica/OMParser.jl) 0.2.1
5 DAE (OpenModelica/DAE.jl) 1.2.0
6 OMFrontend (OpenModelica/OMFrontend.jl) 1.3.1 this PR
7 OMBackend (OpenModelica/OMBackend.jl) 0.5.1
8 OM (JKRT/OM.jl) 0.5.0
9 OMLibraryTesting (OpenModelica/OMLibraryTesting.jl) 0.1.1

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)

  • Every package's tests and OM.jl's suite, in fresh processes: all green (OM.jl 1012/1012, OMBackend 376/376,
    OMFrontend 378/378, OMLibraryTesting 25/25, MetaModelica 262/262, OMRuntimeExternalC 274/274).
  • MSL coverage (425 models, OMLibraryTesting): frontend 424, backend 400, simulate 327, validate 303.
  • CI here runs the same set: every repo's PR comes from a branch named release-1.13, and OM.jl's integration
    workflow checks out the siblings at that branch.

Notes

  • Julia 1.13 only: julia = "1.13" in compat, and every workflow tests on 1.13.
  • Sibling compat entries are bounded below by their release versions (lockstep).

🤖 Generated with Claude Code

SVAGEN26 and others added 25 commits September 24, 2026 10:29
* 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
@JKRT
JKRT merged commit 732a355 into master Oct 2, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants