Automate your first model check
Build your first Syside Automator scripts against a small automobile model: load it, walk its parts, and evaluate a mass requirement against it, finding the design 100 kg over its limit. Along the way you extend the model itself with attributes and the requirement. You need Syside Automator installed and licensed; everything else is pasted from this page.
Getting started
Start by creating a simple SysML v2 model, used throughout this example. It represents a basic automobile structure with electrical and mechanical components.
Create a new file named example_model.sysml and paste the following model:
package 'Part Tree Example' {
part def Electrical;
part def Mechanical;
part Automobile {
part 'Drive Train' {
part Battery : Electrical;
part Motor : Electrical;
}
part Chassis {
part Suspension : Mechanical;
part Body : Mechanical;
}
}
}
Model validation
Before working with the model, it’s important to validate it. This needs no
script: installing syside also installs its command-line interface, available
as syside, python -m syside or, in a uv project, uv run syside. To
validate your model, open a terminal in the directory containing your model and
run:
syside check example_model.sysml
It checks for any semantic errors or warnings in your model. After running, you should see the following output:
Checks passed!
Basic model analysis
Now analyze that model from Python, starting with a script that prints every element
in a tree-like structure. The script asks its
questions through syside.query, whose calls return plain
Python values: query.contents hands back an element’s children as a
list.
Create a new file right next to the example_model.sysml file named
analyze_model.py with the following code:
import syside
from syside import query
# Load the model - this is the first step for any Syside Automator script
(model, diagnostics) = syside.load_model(["example_model.sysml"])
def walk_ownership_tree(element: syside.Element, level: int = 0) -> None:
"""Recursively print all elements in the model."""
if element.name is not None:
print(" " * level, element.name)
else:
print(" " * level, "anonymous element")
# `contents` returns the owned elements as a plain list
for child in query.contents(element):
walk_ownership_tree(child, level + 1)
# Process each document in the model
for document_resource in model.documents:
with document_resource.lock() as document:
print("Walking the ownership tree printing all elements:")
walk_ownership_tree(document.root_node)
load_model raises on errors, so every line after it works with a
valid model, and diagnostics carries the warnings.
Run the script:
python analyze_model.py
When you run this script, you’ll see the following output:
Walking the ownership tree printing all elements:
anonymous element
Part Tree Example
Electrical
Mechanical
Automobile
Drive Train
Battery
Motor
Chassis
Suspension
Body
Working with part types
The model defines two types of parts, Electrical and Mechanical. Next the script
identifies and displays parts by their type.
Add the following function to your script right after the walk_ownership_tree
function. It looks the part definition up once with
query.find_by_name_and_type, then asks each part usage whether it
specializes that definition:
def show_parts_of_type(model: syside.Model, part_type: str) -> None:
"""Display all parts of a specific type in the model."""
definition = query.find_by_name_and_type(
model, part_type, syside.PartDefinition
)
assert definition is not None, f"no part definition named {part_type}"
# `specializes` follows the whole heritage chain, so a part typed by a
# subtype of `part_type` is found as well.
for part in query.find_elements_of_type(model, syside.PartUsage):
if query.specializes(part, definition):
print("- ", part.name)
print("\nElectrical parts in the model:")
show_parts_of_type(model, "Electrical")
print("\nMechanical parts in the model:")
show_parts_of_type(model, "Mechanical")
This will output:
Electrical parts in the model:
- Battery
- Motor
Mechanical parts in the model:
- Suspension
- Body
Enhancing the model
A mass requirement makes the model more realistic. Give each part a mass attribute, then add a requirement that the total must not exceed 500 kg.
Update your example_model.sysml file with this enhanced version:
package 'Part Tree Example' {
private import ScalarValues;
part def Electrical {
attribute Mass;
}
part def Mechanical {
attribute Mass;
}
part Automobile {
part 'Drive Train' {
part Battery : Electrical {
attribute redefines Mass = 150;
}
part Motor : Electrical {
attribute redefines Mass = 200;
}
attribute DriveTrainMass = Battery.Mass + Motor.Mass;
}
part Chassis {
part Suspension : Mechanical {
attribute redefines Mass = 100;
}
part Body : Mechanical {
attribute redefines Mass = 150;
}
attribute ChassisMass = Suspension.Mass + Body.Mass;
}
attribute TotalMass = 'Drive Train'.DriveTrainMass + 'Chassis'.ChassisMass;
}
requirement def MassLimitation {
doc /* Total mass of the Automobile must not
exceed 500 */
attribute MassActual = Automobile.TotalMass;
attribute MassLimit = 500;
}
}
Validating requirements
Now validate that mass requirement. Add the following code to your Python script:
def show_part_decomposition(
element: syside.Element, part_level: int = 0
) -> None:
"""Display a clean part decomposition tree."""
if element.try_cast(syside.PartUsage):
print(" " * part_level, element.name)
new_part_level = part_level + 1
else:
new_part_level = part_level
for child in query.contents(element):
show_part_decomposition(child, new_part_level)
def attribute_value(model: syside.Model, name: str) -> syside.Value | None:
"""Evaluate the expression assigned to the named attribute."""
attribute = query.find_by_name_and_type(model, name, syside.AttributeUsage)
assert attribute is not None, f"no attribute named {name}"
assert attribute.feature_value_expression is not None
value, report = syside.Compiler().evaluate(
attribute.feature_value_expression
)
if report.fatal:
print(f"Error evaluating {name}")
return value
# Find total mass and mass requirement:
total_mass = attribute_value(model, "MassActual")
mass_limit = attribute_value(model, "MassLimit")
# Display results
print("\nPart decomposition:")
for document_resource in model.documents:
with document_resource.lock() as document:
show_part_decomposition(document.root_node)
print(f"\nTotal mass: {total_mass} kg")
if isinstance(total_mass, (int, float)) and isinstance(
mass_limit, (int, float)
):
if total_mass <= mass_limit:
print("✓ Mass requirement met")
else:
print("✗ Mass requirement not met")
else:
print("✗ Cannot compare mass values - invalid types")
When you run this script, you’ll see:
Part decomposition:
Automobile
Drive Train
Battery
Motor
Chassis
Suspension
Body
Total mass: 600 kg
✗ Mass requirement not met
The automobile design exceeds the mass requirement by 100 kg. To meet the requirement, you would need to reduce the mass of some components or redesign the system.
What’s next
Try modifying the mass values to meet the requirement
Read Models as Python objects for what the script just worked with
Look up single tasks in the how-to guides, starting with Find and navigate elements and Evaluate attribute values
Ask a one-line question of a model from the shell in interactive mode
Check out the Examples collection section for more Automator applications