Linked models, jobs and results¶
Save and pin linked subsystems¶
Dependencies reference saved revisions. A root’s source declares usages of the types defined in those dependency documents:
library_source = """package Cells {
part def Cell { attribute charge = 10; }
}"""
root_source = """package Assembly {
part cell : Cells::Cell;
}"""
# Inside an authenticated Client context:
library = studio.submit_model("Cells", library_source)
assembly = studio.submit_model(
"Assembly", root_source,
dependencies=[{"project_id": library.id, "version": library.version}],
)
bundle = studio.resolve_model(assembly.id, version=assembly.version)
local_model = studio.get_connected_model(assembly.id, version=assembly.version)
resolve_model returns the authorized source bundle without needing the local
engine. get_connected_model resolves that bundle into a local executable
object and needs sysml2py. Model.to_connected_model() handles standalone
downloaded models; it rejects external dependency pins because it cannot fetch
them. Mixed SysML/Python sources are resolved together.
Update a model using the snapshot’s revision:
current = studio.get_model(library.id)
saved = studio.save_model(current, source=current.source.replace("10", "20"))
The returned object is a new revision. Existing assemblies still refer to their
previous dependency revision until their dependency pins are updated. A stale
save raises APIError with status 409; reload and reconcile the source before
trying again.
Simulation inputs and clocks¶
Inputs use fully qualified paths to declared, undriven attributes. Stored initial
values use initial_values. Connected or bound targets cannot be overridden.
The model’s declared unit is used when an explicit input unit is omitted.
job = studio.simulate(
model.id, version=model.version,
end_time=2, step_size=0.25,
inputs={
"Motion.cart.speed": {
"unit": "m/s",
"samples": [{"time": 0, "value": 2}, {"time": 1, "value": 4}],
},
},
initial_values={"Motion.cart.distance": {"value": 50, "unit": "cm"}},
time_attribute="Motion.cart.time",
step_attribute="Motion.cart.dt",
events=[{"time": 1.5, "target": "Motion.cart.mode", "event": "Stop"}],
)
Paths depend on the actual model; the snippet assumes the corresponding input and state-machine declarations exist. The checked local example on Executable local examples shows a complete model. Sampled signals are piecewise constant. Signal and event times use seconds. Clock bindings populate duration inputs automatically, including the final partial step.
Keep long-running jobs resumable¶
Convenience methods such as simulate submit and wait. To retain the job ID
before waiting, submit explicitly:
from uuid import uuid4
from sysml_studio import APIError, JobFailed
request_key = str(uuid4()) # Persist and reuse only for this exact request.
job = studio.start_job(model.id, "simulate", version=model.version,
end_time=20, step_size=1,
idempotency_key=request_key)
print("Retain this job ID:", job.id)
try:
finished = studio.wait(job.id, timeout=60, poll_interval=1)
except TimeoutError:
# Waiting stopped; the worker continues. Poll later or explicitly cancel.
snapshot = studio.get_job(job.id)
except JobFailed as error:
print("Worker failure or cancellation:", error)
except APIError as error:
print(error.status_code, error.detail, error.request_id)
Use studio.cancel_job(job.id) to request cancellation. Transport errors remain
httpx exceptions; they are separate from worker failures. No automatic retry
occurs. After an ambiguous submission failure, reuse the same idempotency key
and payload to recover an accepted response. The current server retains these
keys for seven days. For HTTP 429, inspect the raw retry_after header before
deciding when to retry.
Read and export results¶
Job.result contains job-kind-specific metadata. Fetch numerical samples with
results:
payload = studio.results(job.id)
assert payload["schema"] == "sysml2py.results.v1"
columns, units, rows = payload["columns"], payload["units"], payload["rows"]
index = columns.index("Motion.cart.distance")
series = [(row[0], row[index]) for row in rows]
print("Distance unit:", units[index])
files = studio.download_results(job.id, "results/run-001")
The first column is time. Use column names instead of assuming an attribute
order. The remote JSON envelope and local ConnectedModel.simulate dictionary
have different metadata layouts, described in their references.
Downloads include advertised result artifacts and a client job.json manifest.
Archives are saved without extraction. Existing destinations are never replaced.
Completed files remain after a failed download; retry into a new directory.
Catalogs and sharing¶
list_components(search=..., offset=...) returns items and next_offset;
continue until the latter is None. instantiate_component saves a model from
a template. list_spaces, create_space and space_items manage project
containers. share("projects", model.id, visibility="project",
project_space_id=space_id) assigns a permitted sharing scope. The server
enforces visibility, ownership and organization membership for all of these
operations. See Client API reference for each method’s parameters and results.