Skip to content

v0.0.6: Calc release

Choose a tag to compare

@HuiJun HuiJun released this 14 Aug 14:28
5f80892

0.0.6 — 2026-08-13

The executable-model release: a calc body is now a computation rather than a
single expression, collections and sequences are evaluable, specialization,
redefinition and variation resolve to one slot each, and the standard
behavioural and structural notation the public SysML v2 models are written in
parses and runs. Five of the eight Open-MBEE SysML v2 models now load with zero
diagnostics, and a full lunar-descent model — curvilinear equations of motion,
a fixed-step RK4 integrator, and an in-model trajectory history — reproduces a
Python reference to ~1e-11.

Language and semantics

  • A calc body is computational: statements, local declarations, if/else,
    while and for loops, and return. A calculation's declared result is
    answered after its steps have run, so a body may compute towards it instead of
    being one expression; a succession among a calculation's members is not a step.
  • A calc usage's out features are readable — every one of them, not only a
    designated result — and an evaluation of a multi-output calc with no result
    says so, naming the outputs and pointing at the usage, rather than answering
    one of them arbitrarily.
  • An out feature assigned by a body statement is bound, including accumulation
    into it across a loop, so a trajectory history exists in-model. An output given
    both an initializer and a body assignment is a typed error, and a value the
    body returns no longer leaks into an output the same body assigned.
  • A nested calc usage binds its inputs in the environment reading it, so an
    argument naming a feature of the enclosing body resolves to that body's value
    rather than to the declaration's.
  • Inputs bind once per activation (KerML 7.4.9): two outputs of one usage can
    never come from different input bindings, so reading p.a, assigning to the
    usage's input, then reading p.b answers one consistent state instead of
    silently mixing two. A usage declared in an action body or among a state
    machine's members binds from the values the behaviour has reached.
  • Sequence indexing (s#(i)), ranges (1..n, evaluated as the ordered integer
    sequence the library declares), and the KerML collection operations —
    including, excluding, size, isEmpty, head, tail, select,
    collect, sum, min, max and the rest — are evaluable, with a sequence
    expression's results flattened as the library specifies.
  • [*] means 0..*, not *..*, so an unbounded part slot instantiates instead
    of erroring; a feature chain reaches through a multi-valued feature
    (sum(subsystem.volume)), which is what every roll-up model exists to
    demonstrate; and a subset of a redefined collection is populated under either
    name.
  • Specialization and redefinition merge: inherited members are merged through
    redefinition rather than discarded, nested values of a redefined part survive,
    and a renaming redefinition shares one slot whichever of its names carries
    the value — base, intermediate or leaf, at any depth. Supplying a value under
    two names for the same feature is a typed error, not a silent pick.
  • A struct-typed attribute valued by a nested body (attribute :>> material { attribute :>> v = 3.0; }) materializes; a body and = expr on the same
    feature is a typed error.
  • Variation points resolve and a selected variant materialises — once, and per
    owning instance, so two objects of one type may select differently. A variation
    point that inherits its variation-ness is recognised, a variation offers only
    its real variants as choices, a selection naming a non-variant is rejected, and
    a variant outside a variation keeps its value and is reported.
  • Visibility: a protected import is reachable from a specialization, an
    unqualified reference no longer reaches a privately imported name, and an
    expose outside a view usage is reported.
  • view and viewpoint members have their own usage kinds.

Notation

  • Standard behavioural notation parses and executes: named flows
    (flow f from a.out to b.in), accept at / accept when / accept :> e
    (named, short-named and nameless), send … via and send … to self,
    then done and then loop … until, if/else in an action flow,
    exhibit state bodies, transition triggers with payload parameters visible to
    the guard and effect, performed transition effects, entry-succession initial
    states and pending signals.
  • Structural and interface notation: conjugated ports (~P, read from the
    feature typing, with a port-conjugation warning on like-typed interface ends
    and attribute x : ~P rejected), end usages including the default
    end; interface end, require/assume by qualified reference
    (require Pkg::req { }), and snapshot/timeslice occurrence portions. The
    standard view library ships with the binary.
  • The compact transition form with a statement effect
    (transition active to fin do perform Bump;, do assign c := c + 1;) no
    longer demands a second semicolon; ;; is now an error rather than silently
    accepted; and the compact transition <src> to <tgt> spelling is recorded as a
    Systemica extension rather than presented as OMG notation.
  • An anonymous entry action { … } / do action { … } / exit action { … }
    body lowers and executes instead of failing at runtime, and a named or empty
    action body parameter runs as the loop body.

Runtime and tooling

  • A fifth runaway bound, SYSML_MAX_ELEMENTS (default 1 000 000), bounds the
    collection elements one evaluation holds rather than the work a run does: an
    element is a 104-byte Value living as long as the collection holding it, so
    the default holds ~104 MB, in the band the other defaults were sized against.
    Every materialising path is charged — a range, a sequence literal, ->collect
    and the other collection operations — and exceeding it is
    ErrElementLimitExceeded naming the variable, not the step limit: 1..10000000
    used to conjure ~1 GB before the step budget reported it.
  • Activations are scoped, so what a run holds is what it is using: an action
    node's body ends the activation it ran in, and an evaluation outside a body — a
    decision or transition guard, a change condition or duration, an inline node
    expression, an attribute or slot default, an action argument, a constraint
    check — runs in a scope of its own. A decision revisited after its body
    assigned reads its usage again over those values instead of the first
    evaluation's result.
  • %budget prints the five bounds a session runs on with the variable that
    raises each, and a literal expression that spends one is answered with that
    failure instead of "no declarations loaded".
  • Connector usages materialise, including an untyped one
    (interface iface connect a.p to b.q;, previously an <unknown> slot), and a
    connection's ends are bound by reference to the features it connects rather
    than to fresh copies, so a connection identifies what is connected. A selected
    variation-interface connection is realised at runtime and routed for the object
    that selected it, and connector cycles are guarded.
  • Features are read through the occurrence a part usage denotes, an unselected
    variation read as the base of a feature chain is reported, and a REPL session's
    own declaration answers a name the library also declares.
  • Runtime conformance fixtures fail on an undeclared parser diagnostic, closing
    the gate hole that let a fixture execute while emitting a parse error.

Python bindings and sysml-grpc

  • Instantiate returns every instance reachable from the root, so a Python caller
    expands a composite slot (inst.engine.power) instead of holding a bare instance
    id, and a slot the service could not evaluate is reported in SlotValue.error
    rather than as a null value. On the client, slot values convert to Python
    scalars, lists and nested Instances, with the raw protobuf still reachable
    through get_slot()/raw_slots; attribute and item access raise
    AttributeError/KeyError/SlotError rather than returning None.
  • python -m pysysml.generate emits a Python class per SysML definition —
    properties that carry the static type and perform the runtime delegation, so an
    editor completes inst.mass and a type checker rejects inst.mas. GetSymbol
    reports the type facts this needs (type_info with primitive reduction,
    multiplicity, all specialization edges), pysysml ships py.typed, and
    emission is deterministic so the output can be committed.
  • GetServerInfo reports the service's build version and the capabilities it
    supports by name, so a client can require a capability instead of comparing
    version strings. Typed generation requires the type_facts capability and
    fails naming the service in use, where it came from and how to replace it:
    against the v0.0.5 sysml-grpc, which predates type_info, every generated
    feature was typed object, indistinguishable from one that is genuinely
    untyped.
  • A generated module records the model source hash and the generator's emission
    schema, and pysysml.generate --check regenerates in memory and exits non-zero
    when the committed module is missing or would change, writing nothing — a stale
    module was previously found at attribute access, or never.
  • TypedObject.from_instance rejects an instance of another definition, naming
    both types, instead of failing later with a confusing TypeMismatchError on the
    first slot read; unchecked(instance) is the explicit escape hatch.
  • Model.find accepts a symbol's own id, the gRPC instance graph terminates on
    recursive parts, and set-valued and unmaterialized slots serialize correctly.

pysysml on PyPI is versioned and released independently of this tag
(pysysml-v<version>): it resolves a sysml-grpc binary at runtime from
whichever release the caller names, so its version says nothing about which core
release it runs against.

Known limitations

  • No element-wise or broadcast arithmetic over sequences: (a + b) and
    (2.0 * a) are type mismatches, while a#(i) + b#(i) works. A multi-component
    state is written out component by component.
  • A calc whose only output-capable feature is an inout cannot be read and
    reports the misleading "no result expression"; an inout alongside a declared
    out works.
  • Port conjugation is static only — nothing routes differently at runtime for
    ~P.
  • timeslice and snapshot portions parse and resolve but materialize as
    <unknown> slots.
  • A do behaviour runs one action per round and does not repeat without
    re-entry.
  • Mixed named-action-plus-inline-body notation on a state is unsupported rather
    than guessed at.
  • The conformance diagnostic gate matches parser diagnostics only, so a
    fixture with an unresolved reference still passes silently.
  • The OMG training corpus stands at its pinned ceiling of 98/100 files clean; the
    remaining 2 files / 4 errors are OMG source-model bugs.
  • Of the Open-MBEE SysML v2 corpus, spacecraft_example_model,
    Spacecraft_SysMLv2_Simcenter_Studio, starkit, BallAndChain and
    CubesatExample load clean; Modeling_in_Time_And_Space (3),
    Dragon (6, including a bare end; inside a connection def, which is not
    standard notation) and Spacecraft_Example_SysML2 (115) do not. The 2019–20
    pilot-syntax notebooks (block, value type, activity) are not SysML v2 and
    are out of scope.
  • The macOS and Windows binaries are unsigned, so a browser download is
    quarantined by Gatekeeper or flagged by SmartScreen. Install with Homebrew or
    curl; see docs/MACOS_DISTRIBUTION.md.