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

calc def

Define reusable calculation functions with typed parameters

Calc invocation

MassCalc(volume, density)

Call a calc with arguments; results of one calc can feed into another

SI quantity types

LengthValue, MassValue

Typed attributes with physical units from the SI library

Attribute definitions

attribute def

Define reusable attribute types with default values

Attribute redefinition

attribute :>> density

Override an inherited attribute value

Abstract attributes

abstract attribute <g> gravity

Package-level constants reusable across parts

Syside API:

API

Purpose

Compiler.evaluate_feature()

Evaluates a feature (attribute, calc usage) within a given scope

Model.elements()

Iterates over semantic elements, optionally including subtypes

Usage.usages

Accesses features (attributes, parts) of a usage or definition

Document.document_tier

Returns the document tier (StandardLibrary, External, or Project)

LazyIterator.collect()

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.