How to evaluate nested properties of performed actions

Hi

I have this model below, where I want to calculate the sum
of the costs of the tasks performed by a part.

package costRollUp{
private import ScalarValues::*;
action def Task{
attribute cost : Real default 0;
}
action t1 : Task{
attribute :>> cost = 5;
}
action t2 : Task{
attribute :>> cost = 7;
}
action t3 : Task{
attribute :>> cost = 10;
}
part p{
perform t1;
perform t2;
}
part costAnalysis{
attribute totalCost : Real = RealFunctions::sum((p.performedActions as Task).cost);
}
}

When running the code below, totalCost the result is 0 instead of 12:
costRollUp::Task::cost: 0

costRollUp::t1::cost: 5

costRollUp::t2::cost: 7

costRollUp::t3::cost: 10

costRollUp::costAnalysis::totalCost: 0

`def find_expression_attribute_values(element, syside_mod, level: int = 0) → None:
“”"
One expression per AttributeUsage — no duplicate lines.

Prefer feature.feature_value_expression (Sensmetry / rollups). If missing, use the CTO
rule: first owned child if it is an Expression (matches jl_sysmlv2_api).
"""
indent = "  " * level
compiler = syside_mod.Compiler()
lib = syside_mod.Environment.get_default().lib

if hasattr(element, "try_cast") and element.try_cast(syside_mod.AttributeUsage):
    attr = element.cast(syside_mod.AttributeUsage)
    owner_scope = getattr(attr, "owner", attr)

    expr = getattr(attr, "feature_value_expression", None) or getattr(
        attr, "featureValueExpression", None
    )
    if expr is None:
        try:
            expression_a1 = next(iter(attr.owned_elements), None)
        except Exception:
            expression_a1 = None
        if expression_a1 is not None and isinstance(
            expression_a1, syside_mod.Expression
        ):
            expr = expression_a1

    if expr is not None and isinstance(expr, syside_mod.Expression):
        try:
            value, report = compiler.evaluate(
                expr,
                scope=owner_scope,
                stdlib=lib,
                experimental_quantities=True,
            )
        except TypeError:
            value, report = compiler.evaluate(expr)
        if not getattr(report, "fatal", False):
            name = (
                getattr(attr, "qualified_name", None)
                or getattr(attr, "declared_name", None)
                or getattr(attr, "name", None)
                or "<unnamed>"
            )
            print(f"{indent}{name}: {value}")

try:
    element.owned_elements.for_each(
        lambda owned_element: find_expression_attribute_values(
            owned_element, syside_mod, level + 1
        )
    )
except Exception:
    try:
        for owned_element in element.owned_elements:
            find_expression_attribute_values(owned_element, syside_mod, level + 1)
    except Exception:
        pass

def run(model, syside_mod) → None:
attr_iter = None
if hasattr(model, “elements”) and callable(model.elements):
try:
attr_iter = model.elements(syside_mod.AttributeUsage) # type: ignore[arg-type]
except TypeError:
attr_iter = model.elements()
elif hasattr(model, “nodes”) and callable(model.nodes):
try:
attr_iter = model.nodes(syside_mod.AttributeUsage) # type: ignore[arg-type]
except TypeError:
attr_iter = model.nodes()
if attr_iter is None:
print(“Model exposes neither elements() nor nodes()”, file=sys.stderr)
return

for el in attr_iter:
    try:
        if hasattr(el, "try_cast") and el.try_cast(syside_mod.AttributeUsage):
            find_expression_attribute_values(el, syside_mod)
    except Exception:
        pass`

Hi,

This happens because p.performedActions evaluates to an empty sequence. Beyond reflection, none of the standard members except self have any other special meaning internally. They are treated the same way as all the other members.

The specification also does not give nor require any meaning from those members given the short descriptions. At least that was my understanding from how the vague specification is. Maybe in the future we can provide reflection-like capabilities to at least some of those members.

Yeah, the spec needs to be clearer but evaluation is key for adoption and build correct and useful models. That’s an important lesson learned from v1. I think we have to make the spec useful for users and then fix it.

This is the response from Ed on this topic in the google group:

How to access attributes of performed actions

emilee...@planetaryutilities.com

ed...@modeldriven.com

Mar 18

to SysML v2 Release

Emilee,

You can simply access the “cost” attributes of the actions to sum them:

part costAnalysis{

**attribute** totalCost : Real = p.t1.cost + p.t2.cost;

}

Or, more interestingly, you can generalize to sum the cost of any Tasks performed by p, without specifically naming them:

part costAnalysis{

**attribute** totalCost : Real = RealFunctions::sum((p.performedActions **as** Task).cost);

}

The feature “performedActions” is implicitly inherited from the library model for Parts. The “as Task” operation filters out the performed actions that are Tasks. The “.cost” collects the cost from each of the Tasks, and “RealFunctions::sum” sums them up. (Note that the Pilot Implementation can evaluate the first form above, but it is not currently able to properly valuate the more general second version.)

Also, while this does not effect the ability to do the summation, you should probably make “cost” and “out” parameter, to indicate that it is an output of the actions:

action def Task{

out attribute cost : Real default 0;

}

And similarly for t1, t2 and t3.

Ed