Longeron
The spine of your system model — a Python package that defines,
exports, imports, and executes SysML v2 models (import name:
longeron). The parsers are generated with ANTLR 4 from combined grammars
for SysML v2 and KerML, taken from
hivecore-dev/hcf-runtime
(SysML.g4, KerML.g4, with local patches — see
Grammar patches).
SysML® is a registered trademark of the Object Management Group. This project is not affiliated with or endorsed by OMG, and is not a conformance-certified implementation.
Capabilities
| Verb | What you get |
|---|---|
| Define | Parse SysML v2 textual notation into a fully-typed Python object model, import a model from its JSON export, or build models programmatically from dataclasses. Multi-file workspaces merge under one root; a content-addressed cache makes warm loads ~1000x faster. |
| Export | Serialize any model to JSON, back to parseable SysML v2 text, project it onto KerML, or emit OMG Systems-Modeling-API JSON records. Parse → print → parse round-trips preserve the model; JSON → model → JSON is lossless. |
| Validate | longeron.validate() / longeron lint: dangling references, expression-name typos, duplicate names, specialization cycles, state-machine problems; diagnostics carry file:line:column. Names resolve against the vendored standard library (a bare Real passes with no import; a typo like Reall warns), and plain definitions carry their implied specializations (part def → Parts::Part, action def → Actions::Action, which is how start/done resolve); opt out with stdlib=False / --no-stdlib. |
| Execute | Evaluate expressions, run calc definitions, instantiate part definitions (against the bundled standard library if you opt in), check constraints and requirements, run action definitions with succession-driven control flow, and simulate hierarchical/parallel state machines with a clock. |
| Visualize | longeron.diagrams: interactive ELK diagrams in JupyterLab (structure, state machines, action flow) with click-selection that resolves back to model elements. |
| Query & retrieve | Project any model onto RDF (longeron.rdf, rdflib) and ask SPARQL questions over structure, specializations, typed attribute values, variation points, and requirements. A dependency-free RAG substrate (longeron.rag) chunks the model into stable, re-parseable SysML fragments keyed by qualified name, walks semantic neighborhoods, and does keyword search — retrieval for LLM agents that cite names and resolve them through the interpreter for ground truth. |
| Serve & sync | longeron serve exposes any workspace as an OMG Systems-Modeling-API server with honest git-backed history: API commits are the git commits touching your .sysml sources, and pushed changes are materialized as text for you to review and commit — never auto-committed. longeron.client.Client fetches models from (and pushes changes to) any pilot-style server, and /x/ extension endpoints add validate/instantiate/simulate/render over HTTP. |
| Full loop | Read a model, execute it, snapshot the results back into the model as bound part usages, and save (.sysml, .json, or .kerml). |
The builder covers the full grammar: every construct the SysML grammar
accepts (interfaces, views, flows, allocations, metadata annotations,
satisfy/verify/frame, filtered imports, ...) maps to a model class — there
is no lossy fallback. KerML support is asymmetric by design:
parse_kerml_text validates KerML sources syntactically, and to_kerml
projects SysML models onto the kernel language.
Installation
python -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]"
make check # ruff + mypy + the full pytest suite
Optional: pip install -e ".[ecore]" enables the OMG spec-metamodel
projection and API JSON (pyecore), pip install -e ".[rdf]" the RDF
projection (rdflib; the longeron.rag retrieval substrate needs no extra),
and pip install -e ".[server]" / ".[client]" the Systems Modeling API
server (longeron serve) and REST client;
pre-commit install wires ruff+mypy
into every commit.
Renamed from
sysml2: the import package islongeronas of 0.3.0. The old names still work with no changes — the package ships a built-insysml2compatibility shim (import sysml2hands back longeron's own modules) and keeps thesysml2console command; thesysml2PyPI distribution remains as a metadata-only alias oflongeron.
With pixi (optional)
The repo also carries [tool.pixi] config in pyproject.toml (dependency
truth stays in [project]; pixi adds the locked toolchain on top):
pixi run check # lint + mypy + tests in a locked environment
pixi run -e py310 test # any supported Python: py310 | py311 | py312 | py313
pixi run parsers # regenerate ANTLR parsers -- no manual Java setup:
# conda-forge's antlr 4.13.2 ships the tool + JDK
pixi run lab # JupyterLab in notebooks/ (vendored ipyelk extension
# pre-registered -- diagrams render interactively)
pixi run stdlib | demo | coverage | format | notebooks
CI runs entirely on pixi (prefix-dev/setup-pixi, cached by pixi.lock):
a check job (lint + mypy + coverage), a test matrix across the four
Python environments, and a grammar-regen job that fails if the committed
parsers drift from the .g4 sources. The parsers task is
input/output-cached locally and produces byte-identical output.
The generated ANTLR parsers are committed under src/longeron/_gen/, so no
Java toolchain is needed to install or use the package. Java is only needed
to regenerate the parsers after a grammar change:
# any Java 11+ works; for example: mamba create -n jdk openjdk
python scripts/generate_parsers.py
Quick start
import longeron
model = longeron.loads("""
package Demo {
part def Vehicle {
attribute mass : Real = 1200.0;
attribute maxMass : Real = 2000.0;
part wheels : Wheel[4];
assert constraint massLimit { mass <= maxMass }
}
part def Wheel { attribute diameter : Real = 0.66; }
calc def Double { in x : Real; return : Real = 2.0 * x; }
}
""")
# --- export -------------------------------------------------------------
print(longeron.to_sysml(model)) # regenerated textual notation
print(longeron.to_json(model)) # structured JSON
# --- execute ------------------------------------------------------------
interp = longeron.Interpreter(model)
interp.call("Demo::Double", 21.0) # -> 42.0
car = interp.instantiate("Demo::Vehicle") # attributes evaluated
car.get("wheels") # -> [Instance, ...] (4 wheels)
interp.check(car)[0].passed # -> True (mass <= maxMass)
interp.evaluate("(1, 2, 3)->select { in x; x > 1 }") # -> [2, 3]
Actions and state machines:
model = longeron.loads("""
package Ops {
action def Plan {
in distance : Real;
out fuel : Real;
assign fuel := distance * 0.08;
}
state def Machine {
entry; then off;
state off;
transition first off accept start then on;
state on;
}
}
""")
interp = longeron.Interpreter(model)
interp.run_action("Ops::Plan", inputs={"distance": 100.0}).outputs
# -> {'fuel': 8.0}
interp.simulate("Ops::Machine", events=["start"]).final_state
# -> 'on'
A complete walk-through lives in examples/demo.py, and nine executable
tutorials live in notebooks/:
| Notebook | Covers |
|---|---|
01_define_and_explore |
parsing, the object model, programmatic authoring, workspaces |
02_export_and_interchange |
SysML/JSON round-trips, save/load, KerML, spec metamodel, API JSON |
03_calculations_and_constraints |
expressions, calcs, instantiation, constraints, requirements, the full loop |
04_actions_and_states |
action graphs, hierarchical/parallel state machines, time |
05_stdlib_and_validation |
the vendored standard library, longeron lint |
06_interactive_diagrams |
ipyelk structure/state/action diagrams, click-selection |
07_analysis_and_trades |
multi-mission UAV trade studies (interpreter-exact), OpenMDAO sizing + external-analysis binding, Z3 requirement consistency, 3D design views |
08_semantic_web_and_rag |
RDF projection + SPARQL queries, deterministic retrieval chunks, semantic neighborhoods, keyword search, the agent tool-use loop |
09_m0_interpretations |
M0 populations of identified individuals: stable ids, Annex A sequences, roll-ups over actual instances, seeded random sampling, occurrence individuals from executions, the trade-study bridge |
The notebooks are executed by the test suite (tests/test_notebooks.py) and
can be refreshed with pixi run notebooks.
python examples/demo.py
The full loop: read → run → save
model = longeron.load("examples/drone.sysml")
interp = longeron.Interpreter(model)
# run
flown = interp.instantiate("Drone::QuadCopter", payloadMass=0.35)
# write computed values back into the model as a bound part usage
model.find("Drone").add(interp.snapshot(flown, name="asFlown"))
# save in any format (inferred from the suffix)
longeron.save(model, "drone_with_results.sysml")
longeron.save(model, "drone_with_results.json")
longeron.save(model, "drone_with_results.kerml")
# the JSON export is lossless: reload and keep executing
again = longeron.load("drone_with_results.json")
longeron.Interpreter(again).instantiate("Drone::QuadCopter")
SysML or KerML text can be generated from just the JSON definition:
model = longeron.from_json(json_text) # or longeron.from_dict(data)
print(longeron.to_sysml(model))
print(longeron.to_kerml(model)) # kernel-language projection
Multi-file projects and caching
load() accepts a single .sysml file, a .json export, or a directory:
model = longeron.load("models/") # every *.sysml file, merged
model = longeron.load_many(["lib.sysml", "app.json"]) # explicit set
Directory loads merge all files under one root namespace, so cross-file
imports (private import Units::*;) and qualified references resolve.
Files load in sorted path order for determinism.
Built models are cached (as JSON — the same lossless schema as to_json,
never pickles) in ~/.cache/longeron (override with $LONGERON_CACHE_DIR;
the pre-rename $SYSML2_CACHE_DIR is still honored),
keyed by source content plus a fingerprint of the generated parser and
builder code — edits, grammar regeneration, and package upgrades invalidate
cleanly. Caching is on by default -- for single files as well as
directories, so repeat CLI invocations are fast
(cache=False opts out; longeron.clear_cache() wipes it). Warm loads
are ~1000x faster than cold parses with the ANTLR Python runtime.
Command line
longeron parse examples/drone.sysml # syntax check (file or dir)
longeron export examples/drone.sysml --format sysml # json | sysml | kerml
longeron export model.json --format sysml # JSON in, SysML out
longeron export models/ --format json # whole directory, merged
longeron calc examples/drone.sysml Drone::HoverTime capacity=5200
longeron check examples/drone.sysml Drone::QuadCopter payloadMass=0.9
longeron run examples/drone.sysml Drone::PlanBattery distanceKm=20
longeron simulate examples/drone.sysml Drone::FlightStates --events launch,airborne
Every model-consuming command accepts .sysml, .json, or a directory;
--no-cache bypasses the model cache.
Project layout
grammars/ SysML.g4 + KerML.g4 (upstream + local patches)
scripts/generate_parsers.py regenerate src/longeron/_gen from the grammars
scripts/check_corpus.py reproduce the corpus badge: sweep SysML-v2-Release
src/longeron/
_gen/ generated ANTLR lexers/parsers (committed)
parser.py text -> parse tree, error collection
builder.py parse tree -> model (the SysML front-end)
model.py model element dataclasses (Literal-typed vocabularies)
ast.py expression AST + precedence-aware printer
export.py model -> JSON / SysML text, save()
importer.py JSON -> model (lossless round-trip)
workspace.py multi-file loading + content-addressed model cache
kerml.py model -> KerML projection
validation.py longeron lint / validate()
stdlib.py + _stdlib/ vendored OMG standard library (+ prebuilt JSON)
ecore.py + _spec/ projection onto the OMG spec metamodel (pyecore)
api.py OMG Systems Modeling API JSON interchange
rdf.py RDF projection + SPARQL convenience (rdflib)
rag.py LLM retrieval substrate: chunks, neighborhoods, search
diagrams.py interactive ELK diagrams (ipyelk)
render.py + _js/ headless SVG/PNG export (vendored elkjs via node)
vendor/ipyelk/ vendored ipyelk 2.1.1 + local fixes (editable)
interpreter.py evaluation, instantiation, actions, states, snapshot
cli.py the `longeron` console command
src/sysml2/ compatibility shim: `import sysml2` is longeron
examples/ drone.sysml + kernel.kerml + demo.py
tests/ pytest suite (see the coverage badge above)
.github/workflows/ci.yml pixi-based: check + test matrix (3.10-3.13)
+ grammar-regen drift check (antlr/JDK from lock)
Makefile make check = ruff + mypy + pytest (venv/pip route)
How a model flows through the package
parser.pyruns the generated ANTLR parser and collects syntax errors.builder.pywalks the parse tree and producesmodel.pydataclasses. Expressions become compact AST nodes (ast.py), not parse-tree references.export.pyrenders the model to JSON or textual notation;importer.pyreads the JSON back;kerml.pyprojects onto KerML.interpreter.pyresolves qualified names (imports, aliases, specialization) and executes the model;snapshotconverts runtime instances back into model elements.
Code quality
- Typing: modern PEP 585/604 annotations throughout; closed string
vocabularies (
kind,direction,visibility, operators, ...) aretyping.Literalaliases (model.UsageKind,ast.BinaryOp, ...).mypyruns clean oversrc/longeron(generated code excluded). - Linting:
ruffwithE, W, F, I, UP, B, C4, RUFrules. make checkruns ruff + mypy + the full test suite.
Execution semantics (and their limits)
This is a modeling sandbox, not a full KerML semantic engine. What executes:
- Actions: bodies without successions run in declaration order. Bodies
with explicit successions (
first start then a; first a then b;) run as a control-flow graph: unreachable steps do not execute,decidenodes choose the first satisfied guard (withelsefallback), guarded loops back-edge, andfork/joinbranches run sequentially in declaration order (no interleaving).accept after d/accept at tadvance the action's clock (ActionResult.time);accept when craises on a false condition (a would-be deadlock). - State machines are hierarchical: composite states enter through their
own
entry; then S;transition, inner states get the first chance to consume an event, and exits cascade innermost-first.parallelstates activate all child regions concurrently (SimulationResult.active_states). Time triggers (accept after/accept at) fire when a plain number in the event list advances the simulation clock;accept when ctransitions fire as soon as their condition holds. - Quantities evaluate to their magnitude:
10 [SI::m]evaluates to10. - Standard library: a curated subset of the official model library ships
with the package (all 21 Systems Library files + core Quantities/Units +
a KerML-kernel shim; see
longeron/_stdlib/README.md). Opt in withlongeron.add_standard_library(model)or--stdlibon the CLI: library types resolve (Parts::Part,ISQ::mass,SI::kg),public importre-exports and aliases follow, andistypechecks work against library definitions. A bundled prebuilt JSON snapshot makes loading instant; the KerML Kernel Libraries themselves are not loaded (KerML is parse-only), so inherited library defaults that need unimplemented kernel functions degrade toNoneinstead of failing. The prebuilt ships as plain JSON (_stdlib/prebuilt.json) — inspectable text, no pickles anywhere. - Multiplicity expansion: exact bounds (
[4]) expand fully; ranges populate their lower bound ([0..*]gives an empty list), which keeps the library's self-referential compositions finite.
Interactive diagrams
longeron.diagrams renders models as interactive ELK diagrams in JupyterLab
(see notebooks/06_interactive_diagrams.ipynb):
from longeron import diagrams
diagrams.structure_diagram(model) # defs, compartments, edges
diagrams.state_diagram(model.find("P::Machine")) # hierarchical states
diagrams.action_diagram(model.find("P::Flow")) # the executed succession graph
diagrams.diagram(element) # dispatch by kind
diagrams.on_select(widget, model, callback) # clicks -> model elements
Node ids are qualified names, so browser selections resolve straight back to model elements. Layout runs in the browser (elkjs), so diagrams also build headlessly (tests, nbclient).
The same views export to images without a browser — longeron.render runs
the vendored elkjs (0.9.3, EPL-2.0, longeron/_js/) in a node subprocess and
draws styled SVG, with PNG via cairosvg (node + cairo ship in the pixi
environments):
from longeron import render
render.to_svg(diagrams.state_diagram(machine), "machine.svg")
render.to_png(model, "model.png") # builds a view automatically
State-machine simulations replay over that same diagram: longeron.replay
records a simulation (the Interpreter.simulate event protocol -- names
send events, numbers advance the clock) and animates it in the notebook
with play/pause, speed, and scrubbing. Active states light up green,
fired transitions pulse orange, and a readout line follows the scalar
env values. Action executions replay the same way over the action
diagram (replay_widget auto-detects action definitions, or pass
kind="action"), scrubbing over the executed named steps. Needs the
replay extra (pip install "longeron[replay]", anywidget):
from longeron import replay
replay.replay_widget(interp, "Machines::Player",
events=["play", 3600.0, "play"])
replay.replay_widget(interp, "Ops::Deploy", inputs={"tested": True})
ipyelk is vendored (vendor/ipyelk, BSD-3-Clause, tag v2.1.1) and
installed editable (pip install -e vendor/ipyelk; pixi does this
automatically) so it can be patched as needed. Current local fixes, all
marked LOCAL PATCH and tracked by git log -- vendor/ipyelk:
- Headless-safe scheduling —
Pipe.schedule_runraisedRuntimeError: no running event loopoutside Jupyter (plain scripts, pytest); it now no-ops cleanly when there is no frontend to lay out for. - Prebuilt labextension grafted — the git tree only carries TypeScript
sources; the built JupyterLab extension from the 2.1.1 wheel is vendored
under
src/_d/so the editable install renders without a node toolchain.
Spec-metamodel projection and API interchange
With the ecore extra installed, models project onto the OMG abstract
syntax (the pilot implementation's SysML.ecore, 175 metaclasses, vendored
under longeron/_spec/):
from longeron import ecore, api
spec = ecore.to_spec(model) # reified memberships, FeatureTyping, ...
spec.report # what was covered / skipped
spec.save_xmi("model.xmi") # EMF XMI
api.to_api_json(model) # OMG Systems Modeling API records
api.from_api_json(text) # records -> spec instances
Both are structural prototypes: names, flags, ownership, and
specialization/typing relationships are mapped; expression trees are not
(counted in SpecReport, never silently dropped).
Grammar patches
Deviations from the upstream grammars, each marked with a LOCAL PATCH
comment in the .g4 files:
importvisibility (SysML.g4). Upstream required a visibility keyword before everyimport, which rejects the spec's own examples (import ScalarValues::*;). Aligned with KerML.g4's optional prefix.- Entry transitions (SysML.g4). Upstream required
entry; then then S;becausetargetSuccessionalready containsthen. The patch accepts the spec formentry; then S;. - Unary operator precedence (both grammars). Upstream parsed
-3 + 1as-(3 + 1)because the unary alternative sat below the binary alternatives with a non-rightmost recursion. The patch moves unary above the binary operators, so-3 + 1is(-3) + 1. @vsat(SysML.g4, four sites). In SysML,ATis the keywordat(trigger times) and the@symbol isAT_SIGN; upstream usedATin the metadata and classification rules copied from KerML (whereATitself is'@'). Upstream therefore requiredat Safetyinstead of@Safety, andx at Tinstead ofx @ T.- Flow ends (SysML.g4).
flowEndSubsettingdropped the spec's'.'afterQualifiedName, soflow from a.out to b.incould not parse. - Target transition clause order (SysML.g4). Upstream put
ActionBodybefore thethenclause intargetTransitionUsage, so state-body transitions likeaccept s : Sig then b;or a barethen off;after a nested state could not parse. The release BNF (andtransitionUsageitself) put'then' TransitionSuccessionMemberfirst andActionBodylast. - Optional
standard(SysML.g4). Upstream required the fullstandard library package, rejecting a plainlibrary package P;. The spec marksstandardas optional (isStandard ?= 'standard'). - Named send nodes (SysML.g4). The spec declares a send node as
ActionUsageDeclaration? 'send' ...with noactionkeyword, but the pilot-implementation corpus writesaction publish send X() via p;(mirroringacceptNode, whoseaction x accept ...form is spec-blessed) — and this library's own exporter prints named send actions that way. Here the release BNF contradicts the corpus; we follow the corpus and accept both forms. - One-line multiline notes (both grammars).
SINGLE_LINE_NOTE('//' ~[\r\n]*) out-competedMULTILINE_NOTE('//*' .*? '*/') via ANTLR's longest-match rule whenever the note closed on the same line, sox = ( //* elided */ 4 );swallowed everything after*/. Single-line notes now exclude a leading*. - Metadata prefixes on enumerated values (SysML.g4). The release BNF
declares
EnumeratedValue = 'enum'? Usagewith no extension keywords, but the pilot corpus writes#Security enum secret : Level = 2;inside enum bodies. We follow the corpus and acceptUsageExtensionKeyword*there, asusagePrefixalready does.
One known deviation from the OMG spec remains, inherited from upstream: the
grammar groups ??/or/and/implies at one precedence level and
|/&/xor at another, and ** is left-associative. Parenthesize when in
doubt; the exporter always prints round-trip-safe parentheses.
Regenerating the parsers
python scripts/generate_parsers.py
The script finds Java via JAVA_HOME, PATH, or a conda/mamba env, and the
ANTLR 4.13.2 jar via ANTLR_JAR, ~/.m2, or Maven Central. Regenerate
whenever a .g4 file changes, then run pytest.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file longeron-0.6.0.tar.gz.
File metadata
- Download URL: longeron-0.6.0.tar.gz
- Upload date:
- Size: 1.2 MB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
aa4e8288f8436e2179a1658f108855141f43e31f7c47e6956fab0fe8ab9e1369
|
|
| MD5 |
5b1f4cb69c234d882e6d9be6235074b7
|
|
| BLAKE2b-256 |
688c14c10efb002cf27ba248f12131e61010fa8901150663dd456bd2d5ddeb27
|
Provenance
The following attestation bundles were made for longeron-0.6.0.tar.gz:
Publisher:
release.yml on sanbales/longeron
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
longeron-0.6.0.tar.gz -
Subject digest:
aa4e8288f8436e2179a1658f108855141f43e31f7c47e6956fab0fe8ab9e1369 - Sigstore transparency entry: 2568146071
- Sigstore integration time:
-
Permalink:
sanbales/longeron@9f0403c3249c3beef8255632e4d5f7e1b961d46f -
Branch / Tag:
refs/tags/v0.6.0 - Owner: https://github.com/sanbales
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@9f0403c3249c3beef8255632e4d5f7e1b961d46f -
Trigger Event:
push
-
Statement type:
File details
Details for the file longeron-0.6.0-py3-none-any.whl.
File metadata
- Download URL: longeron-0.6.0-py3-none-any.whl
- Upload date:
- Size: 1.2 MB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1417937882b3127be41095fc7f2008ca5649ba2f52d856a01ca231ad57e05365
|
|
| MD5 |
098d37daa9e7c5bdba4515a3fb2d75ad
|
|
| BLAKE2b-256 |
19910232885b7953d57cfce9e52f6fb748cdc0dd8002bdf3226c6a39afb2828b
|
Provenance
The following attestation bundles were made for longeron-0.6.0-py3-none-any.whl:
Publisher:
release.yml on sanbales/longeron
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
longeron-0.6.0-py3-none-any.whl -
Subject digest:
1417937882b3127be41095fc7f2008ca5649ba2f52d856a01ca231ad57e05365 - Sigstore transparency entry: 2568146075
- Sigstore integration time:
-
Permalink:
sanbales/longeron@9f0403c3249c3beef8255632e4d5f7e1b961d46f -
Branch / Tag:
refs/tags/v0.6.0 - Owner: https://github.com/sanbales
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@9f0403c3249c3beef8255632e4d5f7e1b961d46f -
Trigger Event:
push
-
Statement type: