Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 12 additions & 3 deletions docs/examples/examples/thermostat.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,15 @@ flowchart TD

## Files

- [model.py](https://github.com/DynamicalSystemsGroup/gds-examples/blob/main/thermostat/model.py)
- [test_model.py](https://github.com/DynamicalSystemsGroup/gds-examples/blob/main/thermostat/test_model.py)
- [VIEWS.md](https://github.com/DynamicalSystemsGroup/gds-examples/blob/main/thermostat/VIEWS.md)
- [model.py](https://github.com/DynamicalSystemsGroup/gds-core/blob/main/packages/gds-examples/control/thermostat/model.py)
- [test_model.py](https://github.com/DynamicalSystemsGroup/gds-core/blob/main/packages/gds-examples/control/thermostat/test_model.py)
- [VIEWS.md](https://github.com/DynamicalSystemsGroup/gds-core/blob/main/packages/gds-examples/control/thermostat/VIEWS.md)

## Experimental SysML v2 view

The [SysML export experiment](../sysml/README.md) projects this model into four
parts and three forward connections, with a companion mapping/loss manifest.
That artifact passed the pinned official Pilot validator with zero diagnostics.
The backward Energy Cost feedback is omitted with diagnostics; the view does
not reproduce the thermostat's execution behavior. See the
[interoperability guide](../../guides/sysml-v2.md) for tool integration status.
92 changes: 92 additions & 0 deletions docs/examples/sysml/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
# Thermostat SysML v2 structural export

This first interoperability experiment projects the existing
[thermostat](../examples/thermostat.md) into a
flat architecture view. It generates [thermostat.sysml](thermostat.sysml) and
[thermostat.mapping.json](thermostat.mapping.json). Keep the two files together.

## Generate

Generation requires a development checkout containing the experimental
`gds_interchange.sysml` exporter and `thermostat.export_sysml` example; a released
PyPI package is not sufficient. The downloadable artifacts above can be
inspected without installing the exporter.

Run from that repository root with the workspace environment already installed:

```bash
PYTHONPATH=packages/gds-examples/control uv run --no-sync python -m thermostat.export_sysml --output-dir docs/examples/sysml
```

The command replaces these two generated files in the selected directory.
The source revision is the SHA-256 of `model.py`; it identifies those source
bytes, not the complete Python environment. The manifest records the mapping
version and all caller-supplied wire mappings.

## What the view means

| Generated part | GDS block |
|---|---|
| `block0` | PID Controller |
| `block1` | Room Plant |
| `block2` | Temperature Sensor |
| `block3` | Update Room |

Three `connect` relationships describe forward connectivity. Payload spaces
become item definitions containing scalar attributes; outgoing ports use a
channel definition and incoming ports use its conjugate. These connections do
not encode transfer scheduling or state updates. The representation deliberately
chooses parts as an architecture view, rather than claiming GDS blocks are
universally equivalent to SysML parts.

The manifest maps generated identifiers back to source registry entries and
port slots using JSON pointers. Names appear as data in the manifest, so spaces,
punctuation, keywords, and repeated port names cannot inject syntax or merge
elements. Generated identifiers are snapshot-local and may change after edits;
they are not persistent SysML repository identities. All exports currently use
the `GDSExport` package name; load them separately to avoid namespace collisions.

## Explicit limits

- The backward Energy Cost ports and feedback connection are omitted with
diagnostics. Temporal connections are likewise unsupported.
- Entities, state, parameters, execution contracts, admissibility constraints,
metrics, and transition signatures are outside this projection.
- Only built-in Python `bool`, `int`, `float`, and `str` payloads are projected.
`Real` does not preserve IEEE floating-point behavior. Units and value
constraints are not enforced; custom payload types are rejected from the view
with diagnostics. Unbound forward ports are emitted untyped with diagnostics.
- Only flat atomic-block registries are accepted. Hierarchical composition and
reusable component definitions/usages remain future work.
- There is no importer, API synchronization, or execution-equivalence claim.

## Validation status

The exported thermostat was accepted by the official SysML v2 Pilot on
**2026-09-25**, using release `2026-08`, editor build
`0.62.0.202609111953`, and Temurin Java `21.0.12.1+1`. The validator loaded
94 standard-library resources and reported **0 errors and 0 diagnostics**.
Malformed syntax and an unresolved type were rejected as negative controls.

The [validation evidence](validation/README.md) includes the original logs,
artifact hashes, negative-control models, and Java harness. The recorded model
hash matches the downloadable `thermostat.sysml` above. This is a recorded
validation of one structural artifact, not a claim that every generated model
passes or that GDS and SysML execution are equivalent.

Python tests separately cover endpoint preservation, manifest serialization,
identifier safety, unsupported semantics, and rejection of invalid or ambiguous
mappings. They do not substitute for a SysML validator.

The manifest's `validation` field describes generation-time status. Exporting
alone does not invoke a validator, so that field remains unchanged; subsequent
validation belongs in a separate record associated with the artifact hash.

The next gate is to load the same file and negative controls in a pinned
OpenSysML version, compare diagnostics, and inspect the four parts and three
connection endpoints. OpenSysML, repository synchronization, and reverse import
remain untested integrations.

See the [SysML interoperability guide](../../guides/sysml-v2.md) for the tool
roles and next steps, and the [comparison study](../../research/sysml-v2-overlap.md)
for the mapping rationale.
270 changes: 270 additions & 0 deletions docs/examples/sysml/thermostat.mapping.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,270 @@
{
"mapping_version": "gds-sysml-structural/0.1",
"source": {
"uri": "repo:packages/gds-examples/control/thermostat/model.py",
"revision": "sha256:8df2face868474aa60f657b17bfdc35116435106eae0346c96869ec66dc86a55",
"name": "Thermostat PID"
},
"scope": "flat architecture projection; no execution semantics",
"validation": "not validated by a SysML implementation",
"elements": [
{
"source_pointer": "",
"target": "GDSExport",
"name": "Thermostat PID"
},
{
"source_pointer": "/spaces/CommandSpace",
"target": "GDSExport::Payload0",
"name": "CommandSpace"
},
{
"source_pointer": "/spaces/CommandSpace/fields/heater_power",
"target": "GDSExport::Payload0::field0",
"name": "heater_power",
"type_name": "HeaterPower",
"units": null
},
{
"source_pointer": "/spaces/EnergyCostSpace",
"target": "GDSExport::Payload1",
"name": "EnergyCostSpace"
},
{
"source_pointer": "/spaces/EnergyCostSpace/fields/cost",
"target": "GDSExport::Payload1::field0",
"name": "cost",
"type_name": "EnergyCost",
"units": null
},
{
"source_pointer": "/spaces/RoomStateSpace",
"target": "GDSExport::Payload2",
"name": "RoomStateSpace"
},
{
"source_pointer": "/spaces/RoomStateSpace/fields/energy",
"target": "GDSExport::Payload2::field0",
"name": "energy",
"type_name": "EnergyCost",
"units": null
},
{
"source_pointer": "/spaces/RoomStateSpace/fields/temperature",
"target": "GDSExport::Payload2::field1",
"name": "temperature",
"type_name": "Temperature",
"units": null
},
{
"source_pointer": "/spaces/TemperatureSpace",
"target": "GDSExport::Payload3",
"name": "TemperatureSpace"
},
{
"source_pointer": "/spaces/TemperatureSpace/fields/temperature",
"target": "GDSExport::Payload3::field0",
"name": "temperature",
"type_name": "Temperature",
"units": null
},
{
"source_pointer": "/blocks/PID Controller",
"target": "GDSExport::system::block0",
"name": "PID Controller",
"role": "policy"
},
{
"source_pointer": "/blocks/PID Controller/interface/forward_in/0",
"target": "GDSExport::system::block0::forward_in0",
"name": "Measured Temperature",
"slot": "forward_in"
},
{
"source_pointer": "/blocks/PID Controller/interface/forward_out/0",
"target": "GDSExport::system::block0::forward_out0",
"name": "Heater Command",
"slot": "forward_out"
},
{
"source_pointer": "/blocks/Room Plant",
"target": "GDSExport::system::block1",
"name": "Room Plant",
"role": "control"
},
{
"source_pointer": "/blocks/Room Plant/interface/forward_in/0",
"target": "GDSExport::system::block1::forward_in0",
"name": "Heater Command",
"slot": "forward_in"
},
{
"source_pointer": "/blocks/Room Plant/interface/forward_out/0",
"target": "GDSExport::system::block1::forward_out0",
"name": "Room State",
"slot": "forward_out"
},
{
"source_pointer": "/blocks/Temperature Sensor",
"target": "GDSExport::system::block2",
"name": "Temperature Sensor",
"role": "boundary"
},
{
"source_pointer": "/blocks/Temperature Sensor/interface/forward_out/0",
"target": "GDSExport::system::block2::forward_out0",
"name": "Measured Temperature",
"slot": "forward_out"
},
{
"source_pointer": "/blocks/Update Room",
"target": "GDSExport::system::block3",
"name": "Update Room",
"role": "mechanism"
},
{
"source_pointer": "/blocks/Update Room/interface/forward_in/0",
"target": "GDSExport::system::block3::forward_in0",
"name": "Room State",
"slot": "forward_in"
},
{
"source_pointer": "/wirings/Thermostat Loop/wires/0",
"target": "GDSExport::system::wire0",
"source_port": "block2.forward_out0",
"target_port": "block0.forward_in0",
"kind": "forward"
},
{
"source_pointer": "/wirings/Thermostat Loop/wires/1",
"target": "GDSExport::system::wire1",
"source_port": "block0.forward_out0",
"target_port": "block1.forward_in0",
"kind": "forward"
},
{
"source_pointer": "/wirings/Thermostat Loop/wires/2",
"target": "GDSExport::system::wire2",
"source_port": "block1.forward_out0",
"target_port": "block3.forward_in0",
"kind": "forward"
}
],
"diagnostics": [
{
"code": "STRUCTURAL_ONLY",
"source_pointer": "",
"message": "Behavior, roles and scheduling are not executable."
},
{
"code": "ANNOTATIONS_OMITTED",
"source_pointer": "",
"message": "Descriptions, tags and logic are not projected."
},
{
"code": "OUT_OF_SCOPE",
"source_pointer": "/entities",
"message": "entities is not projected."
},
{
"code": "OUT_OF_SCOPE",
"source_pointer": "/parameter_schema",
"message": "parameter_schema is not projected."
},
{
"code": "OUT_OF_SCOPE",
"source_pointer": "/admissibility_constraints",
"message": "admissibility_constraints is not projected."
},
{
"code": "SCALAR_APPROXIMATION",
"source_pointer": "/spaces/CommandSpace/fields/heater_power",
"message": "Python float projected as Real."
},
{
"code": "TYPE_SEMANTICS_OMITTED",
"source_pointer": "/spaces/CommandSpace/fields/heater_power",
"message": "Units and value constraints are not enforced in this view."
},
{
"code": "SCALAR_APPROXIMATION",
"source_pointer": "/spaces/EnergyCostSpace/fields/cost",
"message": "Python float projected as Real."
},
{
"code": "TYPE_SEMANTICS_OMITTED",
"source_pointer": "/spaces/EnergyCostSpace/fields/cost",
"message": "Units and value constraints are not enforced in this view."
},
{
"code": "SCALAR_APPROXIMATION",
"source_pointer": "/spaces/RoomStateSpace/fields/energy",
"message": "Python float projected as Real."
},
{
"code": "TYPE_SEMANTICS_OMITTED",
"source_pointer": "/spaces/RoomStateSpace/fields/energy",
"message": "Units and value constraints are not enforced in this view."
},
{
"code": "SCALAR_APPROXIMATION",
"source_pointer": "/spaces/RoomStateSpace/fields/temperature",
"message": "Python float projected as Real."
},
{
"code": "SCALAR_APPROXIMATION",
"source_pointer": "/spaces/TemperatureSpace/fields/temperature",
"message": "Python float projected as Real."
},
{
"code": "TYPE_REGISTRY_PROJECTED",
"source_pointer": "/types",
"message": "Only space field scalar types are projected; named types are not defined."
},
{
"code": "UNSUPPORTED_CONNECTION",
"source_pointer": "/wirings/Thermostat Loop/wires/3",
"message": "feedback connection omitted; no execution interpretation."
},
{
"code": "BACKWARD_PORT",
"source_pointer": "/blocks/PID Controller/interface/backward_in/0",
"message": "Backward channel omitted."
},
{
"code": "BACKWARD_PORT",
"source_pointer": "/blocks/Room Plant/interface/backward_out/0",
"message": "Backward channel omitted."
}
],
"connections": [
{
"wiring": "Thermostat Loop",
"wire_index": 0,
"source_port": "Measured Temperature",
"target_port": "Measured Temperature",
"kind": "forward"
},
{
"wiring": "Thermostat Loop",
"wire_index": 1,
"source_port": "Heater Command",
"target_port": "Heater Command",
"kind": "forward"
},
{
"wiring": "Thermostat Loop",
"wire_index": 2,
"source_port": "Room State",
"target_port": "Room State",
"kind": "forward"
},
{
"wiring": "Thermostat Loop",
"wire_index": 3,
"source_port": "Energy Cost",
"target_port": "Energy Cost",
"kind": "feedback"
}
]
}
Loading
Loading