Calc Evaluation v0.8.5 Labs
Experimental feature
This example uses experimental unit conversion functionality.
Enable with experimental_quantities=True.
This example evaluates user-defined calc def expressions using
Compiler.evaluate_feature. It demonstrates
chained calculations where each result feeds into the next, two calc def styles
(result expression vs return parameter), and automatic unit conversion across mixed
units.
The model computes geometry and physical properties of a steel beam from basic dimensions, density, and gravity.
Example Model Structure
The model defines five calculation definitions that form a computation chain.
SteelBeam uses composition to reference a CarbonSteel material and accesses its
density via material.density. Mixed units (cm and m) are automatically
converted during evaluation:
SteelBeam
├── material : CarbonSteel
│ └── density = 7850 [kg⋅m⁻³]
├── width = 10 [cm]
├── height = 5 [cm]
├── depth = 2 [m]
├── topArea = AreaCalc(width, height)
├── surfaceArea = SurfaceAreaCalc(width, height, depth)
├── volume = VolumeCalc(topArea, depth)
├── density = material.density
├── mass = MassCalc(volume, density)
└── weight = WeightCalc(mass, gravity)
Material is modeled using composition (attribute material : CarbonSteel) rather than
inheritance, since a beam is not a material but has one. Material and
CarbonSteel are defined as attribute def types:
abstract attribute def Material {
attribute density : DensityValue;
}
attribute def CarbonSteel :> Material {
attribute :>> density = 7850 ['kg⋅m⁻³'];
}
Two calc def styles are shown. Style 1 uses a result expression (the last expression
without a trailing semicolon):
calc def AreaCalc {
in a : LengthValue;
in b : LengthValue;
a * b
}
Style 2 uses an explicit return parameter. This style also demonstrates a calc
calling another calc internally:
calc def SurfaceAreaCalc {
in width : LengthValue;
in height : LengthValue;
in depth : LengthValue;
return attribute result : AreaValue =
2.0 * (AreaCalc(width, height) + AreaCalc(width, depth) + AreaCalc(height, depth));
}
Both styles are evaluated identically from the API perspective.
Concepts Used
SysML v2 calc concepts:
Concept |
Syntax |
Description |
|---|---|---|
Calc definitions |
|
Define reusable calculation functions with typed parameters |
Calc invocation |
|
Call a calc with arguments; results of one calc can feed into another |
SI quantity types |
|
Typed attributes with physical units from the SI library |
Attribute definitions |
|
Define reusable attribute types with default values |
Attribute redefinition |
|
Override an inherited attribute value |
Abstract attributes |
|
Package-level constants reusable across parts |
Syside API:
API |
Purpose |
|---|---|
Evaluates a feature (attribute, calc usage) within a given scope |
|
Iterates over semantic elements, optionally including subtypes |
|
Accesses features (attributes, parts) of a usage or definition |
|
Returns the document tier (StandardLibrary, External, or Project) |
|
Materializes lazy iterator into a list |
Evaluating Calc Definitions
The script finds the SteelBeam part definition and collects its project-level
attributes. Each attribute is evaluated using Compiler.evaluate_feature with the part definition as scope:
def evaluate_feature(
feature: syside.Feature, scope: syside.Type
) -> syside.Value | None:
compiler = syside.Compiler()
value, compilation_report = compiler.evaluate_feature(
feature=feature,
scope=scope,
stdlib=STANDARD_LIBRARY,
experimental_quantities=True,
)
return value
The experimental_quantities=True flag enables automatic unit conversion. When
width = 10 [cm] and depth = 2 [m] are multiplied, the compiler converts both to
SI base units before computing the result.
Chained calculations work because evaluate_feature resolves attribute references
transitively. When evaluating weight, the compiler follows the chain:
WeightCalc(mass, gravity) → MassCalc(volume, density) → VolumeCalc(topArea,
depth) → AreaCalc(width, height), evaluating each calc definition along the way.
Example Model
package 'Calc Evaluation Example' {
private import ScalarValues::*;
private import SI::*;
// Style 1: result expression (last expression without trailing ;)
calc def AreaCalc {
in a : LengthValue;
in b : LengthValue;
a * b
}
// Style 2: return parameter
calc def SurfaceAreaCalc {
in width : LengthValue;
in height : LengthValue;
in depth : LengthValue;
return attribute result : AreaValue =
2.0 * (AreaCalc(width, height) + AreaCalc(width, depth) + AreaCalc(height, depth));
}
calc def VolumeCalc {
in area : AreaValue;
in depth : LengthValue;
area * depth [m^3]
}
calc def MassCalc {
in volume : VolumeValue;
in density : DensityValue;
volume * density [kg]
}
calc def WeightCalc {
in mass : MassValue;
in gravity : AccelerationValue;
mass * gravity
}
abstract attribute <g> gravity : AccelerationValue = 9.81 ['m/s²'];
abstract attribute def Material {
attribute density : DensityValue;
}
attribute def CarbonSteel :> Material {
attribute :>> density = 7850 ['kg⋅m⁻³'];
}
part def SteelBeam {
attribute material : CarbonSteel;
attribute width : LengthValue = 10 [cm];
attribute height : LengthValue = 5 [cm];
attribute depth : LengthValue = 2 [m];
attribute topArea : AreaValue = AreaCalc(width, height);
attribute surfaceArea : AreaValue = SurfaceAreaCalc(width, height, depth);
attribute volume : VolumeValue = VolumeCalc(topArea, depth);
attribute density : DensityValue = material.density;
attribute mass : MassValue = MassCalc(volume, density);
attribute weight : ForceValue = WeightCalc(mass, gravity);
}
}
Example Script
import pathlib
import syside
EXAMPLE_DIR = pathlib.Path(__file__).parent
MODEL_FILE_PATH = EXAMPLE_DIR / "example_model.sysml"
STANDARD_LIBRARY = syside.Environment.get_default().lib
def find_element_by_name(
model: syside.Model, name: str
) -> syside.Element | None:
"""Search the model for a specific element by name."""
for element in model.elements(syside.Element, include_subtypes=True):
if element.name == name:
return element
return None
def evaluate_feature(
feature: syside.Feature, scope: syside.Type
) -> syside.Value | None:
"""Evaluate a feature within a given scope.
Args:
feature: The feature to evaluate (attribute, calc usage, etc.)
scope: The context in which to evaluate the feature
"""
compiler = syside.Compiler()
value, compilation_report = compiler.evaluate_feature(
feature=feature,
scope=scope,
stdlib=STANDARD_LIBRARY,
experimental_quantities=True,
)
if compilation_report.fatal:
print(compilation_report.diagnostics)
exit(1)
return value
def main() -> None:
# Load SysML model and get diagnostics (errors/warnings)
(model, _) = syside.load_model([MODEL_FILE_PATH], warnings_as_errors=True)
steel_beam = find_element_by_name(model, "SteelBeam")
assert steel_beam is not None and isinstance(
steel_beam, syside.PartDefinition
)
attributes = [
x
for x in steel_beam.usages.collect()
if type(x) is syside.AttributeUsage
and x.document.document_tier is syside.DocumentTier.Project
]
for attr in attributes:
value = evaluate_feature(attr, steel_beam)
# Only print scalar values, skip complex objects like material references
if isinstance(value, (int, float)):
print(f" {attr.name} = {value:.4f}")
if __name__ == "__main__":
main()
Output
width = 0.1000
height = 0.0500
depth = 2.0000
topArea = 0.0050
surfaceArea = 0.6100
volume = 0.0100
density = 7850.0000
mass = 78.5000
weight = 770.0850
Download
Download this example here.