Skip to content

Latest commit

 

History

History
853 lines (728 loc) · 29.7 KB

File metadata and controls

853 lines (728 loc) · 29.7 KB

Query Cookbook

Every recipe in this chapter runs against one model, examples/cookbook.sysml, and every output shown is what the sysml binary printed. The model is a small observatory:

package Cookbook {
	private import DocumentQueries::*;
	private import KerML::Root::Element;
	private import ScalarValues::*;

	part def Subsystem {
		attribute mass : Real;
	}
	part def OpticalSubsystem :> Subsystem;
	part def MirrorAssembly :> OpticalSubsystem {
		attribute :>> mass = 10.0;
	}

	metadata def Critical;

	port def OpticalPort;
	port def DataPort;

	part telescope {
		part primaryMirror : MirrorAssembly {
			@Critical;
			port opticalOut : OpticalPort;
		}
		part instrumentCluster : Subsystem {
			attribute redefines mass = 4.5;
			port opticalIn : OpticalPort;
			port dataOut : DataPort;
		}
		part mountControl : Subsystem {
			attribute redefines mass = 15.0;
			port dataIn : DataPort;
		}
		connection opticalPath connect primaryMirror.opticalOut to instrumentCluster.opticalIn;
		connection dataPath connect instrumentCluster.dataOut to mountControl.dataIn;
	}

	part def Computer;
	part scienceComputer : Computer;
	allocation processing allocate telescope.instrumentCluster to scienceComputer;

	requirement massRequirement;
	part observatory {
		satisfy massRequirement by telescope;
	}

	verification def MassTest;
	verification massVerification : MassTest {
		objective {
			verify massRequirement;
		}
	}

	// ... the recipe queries below ...
}

Each recipe is a calc def specializing DocumentQueries::Query declared in the same package. Run one with:

$ sysml docs/manual/examples/cookbook.sysml -run-query "<name> [<parameter>=<expression> ...]"

A binding whose expression is a name binds the element it denotes; anything else is evaluated as an expression (strings in quotes, numbers as literals).

Anatomy of a query

calc def MassTable :> Query {
	in root : Element;                 // entry parameters, bound by the caller
	Project(                            // operations compose inside-out
		source = PartsByMass(root = root),  // ... and queries invoke queries
		properties = ("name", "mass", "qualifiedName")
	)
}
  • A query is a calc def specializing DocumentQueries::Query.
  • Its in parameters are the entry bindings a caller supplies — an element, a string, a number, a boolean, or a sequence of them.
  • Its body is one expression composing the library operations; source arguments chain them, innermost first. A name in an argument reads the query's parameter of that name, or binds the model element it refers to (OwnedElements(source = telescope) starts from that part), just as a %run-query binding does. The element is checked against the parameter's type when the query is planned.
  • A query can invoke another query by name, with its own bindings. Invocation is dependency-ordered and cycle-checked, with depth and count budgets.

Results are ordered element sequences. Order is the model's declaration order until an OrderBy says otherwise, and elements are deduplicated by identity, so a query is deterministic by construction.

Parameter defaults

An in parameter may declare a default, and a caller that leaves it unbound gets that default — from %run-query, -run-query, RunDocumentQuery, a document's content block, or another query's invocation alike:

calc def HeavySubsystems :> Query {
	in root : Element = telescope;         // a name binds the element it refers to
	in threshold : String default "10";    // anything else is evaluated
	WhereFeature(
		source = Descendants(source = root, maxDepth = 3),
		'feature' = "mass", operator = ">=", value = threshold
	)
}

calc def LightSubsystems :> HeavySubsystems {
	in redefines threshold default "5";    // a redefining default wins
}
  • A default follows the binding rule of %run-query <p>=<expr>: a default that names a model element binds that element; any other default is an expression. The rule applies wherever a value is expected, so in roots : Element[0..*] = (telescope, groundStation); binds both elements, a list may mix element names with parameters and query invocations, and in candidates : Element[0..*] = OwnedElements(source = telescope); starts the traversal from that part.
  • An expression default is evaluated once per query execution, before any row is produced, in the scope of the query that declared it — it may name that query's other parameters (in candidates : Element[0..*] = OwnedElements(source = root);) or invoke another query, within the usual visit and invocation budgets. Defaults are filled in parameter order after the explicit bindings, so a default may read a parameter bound explicitly or defaulted before it; one that reads a later, still unbound parameter fails as a missing binding.
  • Defaults are inherited: LightSubsystems keeps root = telescope from HeavySubsystems, and its own threshold default replaces the inherited one. The nearest default along the redefinition chain wins.
  • A default is checked against the parameter's type and multiplicity exactly like an explicit binding, and an explicit binding always overrides the default. What the default's text already settles is refused when the query is planned, as document-query-default-type or document-query-default-multiplicity naming the parameter: a literal or named element of the wrong type, and a list or invocation whose size cannot fit (in source : Element = (telescope, groundStation);). A named element is never a data value — = label is refused for a String parameter even when label is a String attribute — except an enumeration literal, which is a value of its enumeration: in hue : Color = Color::red; binds the literal, and a literal of another enumeration is refused. What only the values decide — a parameter reference whose multiplicity is not known statically — is checked when the default is evaluated, with the failures an explicit binding gets.
  • A default the plan cannot represent (a form the query expression language has no operation for) is a planning error naming the parameter, reported with the other document-query-* diagnostics rather than at execution time.

Collection

Direct children: OwnedElements

calc def Children :> Query {
	in root : Element;
	OwnedElements(source = root)
}
$ sysml cookbook.sysml -run-query "Cookbook::Children root=Cookbook::telescope"
✓ Query Cookbook::Children returned 5 rows
  Row 1: Cookbook::telescope::primaryMirror
  Row 2: Cookbook::telescope::instrumentCluster
  Row 3: Cookbook::telescope::mountControl
  Row 4: Cookbook::telescope::opticalPath
  Row 5: Cookbook::telescope::dataPath

Everything the element owns is returned — here the three parts and the two connections, in declaration order. Filter afterwards to narrow.

Descendants to a depth: Descendants

calc def AllParts :> Query {
	in root : Element;
	WhereType(
		source = Descendants(source = root, maxDepth = 10),
		type = "PartUsage"
	)
}
$ sysml cookbook.sysml -run-query "Cookbook::AllParts root=Cookbook::telescope"
✓ Query Cookbook::AllParts returned 5 rows
  Row 1: Cookbook::telescope::primaryMirror
  Row 2: Cookbook::telescope::instrumentCluster
  Row 3: Cookbook::telescope::mountControl
  Row 4: Cookbook::telescope::opticalPath
  Row 5: Cookbook::telescope::dataPath

maxDepth bounds the walk; each level is visited in declaration order. Note that the connections are still here: a connection usage is a PartUsage in the SysML metamodel (its metaclass conforms to it). Use a feature or name filter, or type = "ConnectionUsage", to separate them — see Type filters.

Ancestors: Ancestors

calc def Enclosing :> Query {
	in leaf : Element;
	Ancestors(source = leaf, maxDepth = 2)
}
$ sysml cookbook.sysml -run-query "Cookbook::Enclosing leaf=Cookbook::telescope::primaryMirror::opticalOut"
✓ Query Cookbook::Enclosing returned 2 rows
  Row 1: Cookbook::telescope::primaryMirror
  Row 2: Cookbook::telescope

Owners are returned nearest-first, up to maxDepth levels.

Type filters

WhereType keeps elements whose metamodel type matches — "PartUsage", "ConnectionUsage", "RequirementUsage", "AttributeUsage", "PortUsage", "PartDefinition" and so on — including metaclass conformance, so type = "Usage" keeps every kind of usage. A name that is neither a known metamodel type nor resolvable in the model is a typed unknown-classification error rather than a silently-empty result.

calc def Connections :> Query {
	in root : Element;
	WhereType(
		source = Descendants(source = root, maxDepth = 10),
		type = "ConnectionUsage"
	)
}
$ sysml cookbook.sysml -run-query "Cookbook::Connections root=Cookbook::telescope"
✓ Query Cookbook::Connections returned 2 rows
  Row 1: Cookbook::telescope::opticalPath
  Row 2: Cookbook::telescope::dataPath

To select by a model-defined classification — "every part typed by Subsystem" — filter on what distinguishes those elements instead: a metadata annotation (below) or a characteristic attribute (property filters).

Metadata filters

WhereMetadata keeps elements annotated with a metadata definition, matching specializations of it too. The model marks primaryMirror with @Critical:

calc def CriticalParts :> Query {
	in root : Element;
	WhereMetadata(
		source = AllParts(root = root),
		'metadata' = "Cookbook::Critical"
	)
}
$ sysml cookbook.sysml -run-query "Cookbook::CriticalParts root=Cookbook::telescope"
✓ Query Cookbook::CriticalParts returned 1 row
  Row 1: Cookbook::telescope::primaryMirror

('metadata' is quoted because metadata is a SysML keyword.)

Name filters

WhereName compares each element's effective name against a value:

calc def MirrorParts :> Query {
	in root : Element;
	WhereName(
		source = AllParts(root = root),
		operator = "contains",
		value = "Mirror"
	)
}
$ sysml cookbook.sysml -run-query "Cookbook::MirrorParts root=Cookbook::telescope"
✓ Query Cookbook::MirrorParts returned 1 row
  Row 1: Cookbook::telescope::primaryMirror

Text operators: =/==, !=/<>, contains, startsWith, endsWith (also spelled starts-with/ends-with), and matches with a regular expression.

Property filters

WhereFeature compares an attribute's constant value. The comparison is typed: numbers compare numerically (<, <=, >, >= and equality, with * accepted as infinity), booleans by equality, strings with the text operators above. An element without the attribute simply does not match; a property no element in the source has is a typed unknown-property error.

calc def HeavyParts :> Query {
	in root : Element;
	in threshold : String;
	WhereFeature(
		source = AllParts(root = root),
		'feature' = "mass",
		operator = ">=",
		value = threshold
	)
}
$ sysml cookbook.sysml -run-query "Cookbook::HeavyParts root=Cookbook::telescope threshold=\"10\""
✓ Query Cookbook::HeavyParts returned 2 rows
  Row 1: Cookbook::telescope::primaryMirror
  Row 2: Cookbook::telescope::mountControl

Two details worth noting: value is always written as a string and parsed by the operator's type, and primaryMirror matches through its definitionMirrorAssembly fixes mass = 10.0, and the usage inherits it.

The built-in properties of the projection table are features too, so feature = "shortName" with startsWith selects the requirements whose identifier shares a prefix, and feature = "documentation" with contains selects the elements whose doc text mentions a word — any one of an element's several bodies matching is enough.

Quantities

An attribute declared with a unit — attribute :>> mass = 2290000 [kg]; — is a quantity: a magnitude carried with its unit, never a bare number. The filter's value is a bare number, and it compares against the magnitude in the attribute's own unit: mass >= "1000000" matches 2290000 [kg], and would match 1500000 [g] too, because the threshold is read in each element's unit. Choose the threshold for the unit your model declares, or normalize the unit in the model. A value carrying a unit of its own ("1000 [kg]") is not a number and is refused as a typed invalid-argument error.

calc def HeavyStages :> Query {
	in root : Element;
	WhereFeature(
		source = WhereType(source = Descendants(source = root, maxDepth = 1), type = "PartUsage"),
		'feature' = "mass",
		operator = ">=",
		value = "1000000"
	)
}
$ sysml units.sysml -run-query "UnitsRepro::HeavyStages root=UnitsRepro::rocket"
✓ Query UnitsRepro::HeavyStages returned 1 row
  Row 1: UnitsRepro::rocket::s1

The attribute compared need not be a literal: a mass declared as dryMass + propellantMass is evaluated for each element before the comparison, as derived values describes. Filtering and sorting then follow the same commensurability rules as literal quantities.

Sorting

OrderBy sorts by a property with every policy explicit — there are no defaults to guess:

  • direction: "ascending" or "descending".
  • missing: where elements without the property go — "first", "last", or "error" to refuse them.
  • multiple: which value to sort by when the property has several — "first", "last", or "error".

The sort is stable, so equal keys keep their declaration order. Mixing incomparable value types across elements is a typed invalid-order error.

Quantities sort by converted magnitude when their units are commensurable: 500000 [g] orders below 119000 [kg]. Two Integer magnitudes compare exactly, so neighbours a Real cannot tell apart (9007199254740993 [kg] against 9007199254740992 [kg]) keep their order. Quantities of different dimensions (2290000 [kg] against 42 [m]) are not ordered — that is an invalid-order error naming both units, never a silent comparison of the bare magnitudes.

calc def PartsByMass :> Query {
	in root : Element;
	OrderBy(
		source = AllParts(root = root),
		property = "mass",
		direction = "descending",
		missing = "last",
		multiple = "error"
	)
}
$ sysml cookbook.sysml -run-query "Cookbook::PartsByMass root=Cookbook::telescope"
✓ Query Cookbook::PartsByMass returned 5 rows
  Row 1: Cookbook::telescope::mountControl
  Row 2: Cookbook::telescope::primaryMirror
  Row 3: Cookbook::telescope::instrumentCluster
  Row 4: Cookbook::telescope::opticalPath
  Row 5: Cookbook::telescope::dataPath

The connections have no mass, so missing = "last" places them after the sorted parts.

Projection

Project turns elements into rows of named, typed cells — what a document table renders. Beyond the model's own attributes, these built-in properties are always projectable:

Property Value
name The effective name
declaredName The declared name, absent when the name is derived
shortName The effective short name (<'HLR-R001'>), absent when the element has none
declaredShortName The declared short name, absent when the short name is derived
documentation The body of each doc comment in declaration order, delimiters and indentation removed; absent when undocumented
qualifiedName, @id The fully-qualified name
owner The owner's qualified name
@type The metamodel type (PartUsage, ...)
type The declared type's qualified name
isAbstract Boolean
multiplicityLower, multiplicityUpper Integers, * as unbounded
calc def MassTable :> Query {
	in root : Element;
	Project(
		source = PartsByMass(root = root),
		properties = ("name", "mass", "qualifiedName")
	)
}
$ sysml cookbook.sysml -run-query "Cookbook::MassTable root=Cookbook::telescope"
✓ Query Cookbook::MassTable returned 5 rows
  Columns: name, mass, qualifiedName
  Row 1: Cookbook::telescope::mountControl
    name = "mountControl"
    mass = 15.0
    qualifiedName = "Cookbook::telescope::mountControl"
  Row 2: Cookbook::telescope::primaryMirror
    name = "primaryMirror"
    mass = 10.0
    qualifiedName = "Cookbook::telescope::primaryMirror"
  Row 3: Cookbook::telescope::instrumentCluster
    name = "instrumentCluster"
    mass = 4.5
    qualifiedName = "Cookbook::telescope::instrumentCluster"
  Row 4: Cookbook::telescope::opticalPath
    name = "opticalPath"
    mass = (none)
    qualifiedName = "Cookbook::telescope::opticalPath"
  Row 5: Cookbook::telescope::dataPath
    name = "dataPath"
    mass = (none)
    qualifiedName = "Cookbook::telescope::dataPath"

A cell for a property the element lacks is empty ((none) in the CLI's row listing, an empty table cell in a document).

Quantity cells

A quantity-valued attribute projects as the runtime prints it — magnitude, then the unit in brackets — in the CLI row listing, a Markdown cell (brackets escaped as \[kg\], so they read as text) and an HTML cell, whose <span class="sysml-value" data-value-kind="quantity"> also carries data-magnitude and data-unit apart. The unit is the one the model spelt (kg, km/h), not a reduction to base units.

part def Stage {
	attribute mass :> ISQ::mass;
}
part def FirstStage :> Stage {
	attribute :>> mass = 2290000 [kg];
}
part def UpperStage :> Stage {
	attribute :>> mass = 119000 [kg];
}
part rocket {
	part s1 : FirstStage;
	part s2 : UpperStage;
}

calc def Masses :> Query {
	in root : Element;
	Project(
		source = WhereType(source = Descendants(source = root, maxDepth = 1), type = "PartUsage"),
		properties = ("name", "mass")
	)
}
$ sysml units.sysml -run-query "UnitsRepro::Masses root=UnitsRepro::rocket"
✓ Query UnitsRepro::Masses returned 2 rows
  Columns: name, mass
  Row 1: UnitsRepro::rocket::s1
    name = "s1"
    mass = 2290000 [kg]
  Row 2: UnitsRepro::rocket::s2
    name = "s2"
    mass = 119000 [kg]

Derived values

A value that is a literal, a quantity, or an expression over those alone (2 [kg] * 3 is 6 [kg], 1 [km] + 500 [m] is 1.5 [km]) is folded once, statically, when the model is analysed. A value written over other features — the common shape in a mass or power budget — is instead evaluated by the runtime as seen from the row's element: each leaf is read through the redefinition chain of that concrete carrier, so a type-level :>> and a usage-level :>> both win over the definition's own value, a default applies where nothing binds the feature, and a feature chain (s1.mass) reads the owned part's value. Arithmetic, comparisons, conditionals and the library functions the runtime provides (sum, size, indexing with #, ->collect) all apply, with the runtime's rules: units are kept and converted, Integer stays Integer, and a collection-valued attribute projects as one value per element.

part def Stage {
	attribute dryMass :> ISQ::mass;
	attribute propellantMass :> ISQ::mass;
	attribute mass :> ISQ::mass = dryMass + propellantMass;
}
part def FirstStage :> Stage {
	attribute :>> dryMass default = 130000 [kg];
	attribute :>> propellantMass = 2160000 [kg];
}
part def Vehicle {
	part s1 : FirstStage;
	part s2 : FirstStage {
		attribute :>> dryMass = 120000 [kg];
	}
	attribute liftoffMass :> ISQ::mass = s1.mass + s2.mass;
}
part rocket : Vehicle;
$ sysml derived.sysml -run-query "DerivedRepro::Masses root=DerivedRepro::Vehicle"
✓ Query DerivedRepro::Masses returned 2 rows
  Columns: name, dryMass, mass
  Row 1: DerivedRepro::Vehicle::s1
    name = "s1"
    dryMass = 130000 [kg]
    mass = 2290000 [kg]
  Row 2: DerivedRepro::Vehicle::s2
    name = "s2"
    dryMass = 120000 [kg]
    mass = 2280000 [kg]
$ sysml derived.sysml -run-query "DerivedRepro::Vehicles root=DerivedRepro"
✓ Query DerivedRepro::Vehicles returned 1 row
  Columns: name, liftoffMass
  Row 1: DerivedRepro::rocket
    name = "rocket"
    liftoffMass = 4570000 [kg]

The query and the REPL agree: rocket.s1.mass prints = 2290000 [kg] too. Note that s2's dryMass overrides a default =; a value written with a plain = is fixed for every redefinition, and the analyser refuses the override before any query runs.

What the runtime cannot turn into a value is reported, never guessed:

  • A leaf unbound anywhere in the carrier's chain (an abstract attribute mass :> ISQ::mass; that nothing ever binds) makes the derived value absent — an empty cell, as for a value-less feature — and WhereFeature does not match it, OrderBy places it by its missing policy.
  • A value that genuinely depends on the model running — an in parameter of a calculation, an action's state, a non-constant function — or that the runtime rejects — a cycle (a = b; b = a;), operands of different dimensions (mass + length), a result no cell can hold such as a part (attribute heart = engine.core;) — is a typed unevaluable-feature error naming the query, the property, the row element and the runtime's reason. A table never shows a wrong number or a silently empty cell for a value the model does declare, and a ?? default does not cover it — the feature is present, not absent.

The roll-up a library writes over a possibly empty collection evaluates as written: sum over no quantities is the zero of the collection's declared kind, in its coherent SI unit, so mass + sum(subcomponents.totalMass) on a component whose subcomponents : MassedComponent [*] default null holds nothing is mass (100 [kg] + 0 [kg]), and a default null collection holds the parts that subset it (part b1 : Bolt :> subcomponents;, or with subsets) — the default is only its value where nothing populates it — so the sum rolls up through them recursively. The kind is the collection's declared one, and survives a select, reject or collect that leaves nothing: a collection typed Real[*], or an empty one mapped through an untyped body parameter (->collect { in x; x * x }), still sums to the number 0, and 10 [kg] + 0 [m] or 10 [kg] + 5 remain the incommensurable units error above.

A value written with = holds for as long as the object does, not just on the first read: it is derived from what the object holds now. When a run assigns a feature the expression read (assign a := 9;), when a binding propagates a new value into it, or when a default null collection is superseded by a part that subsets it, the derived value is dropped and derived again the next time it is read — through a part or a binding the expression read through, and on through the values that read it. Nothing is recomputed until something asks, and a value a run assigned is never recomputed: assign d := 100; fixes d whatever a does afterwards, while a dd = d + 1 beside it keeps following d. A probe or transaction that wrote such a feature is rolled back with the values that read it.

The parameters of a calculation or action usage are the boundary. They are bound once, when the invocation starts, and stay bound while its outputs are read — an assignment to a feature an in named does not rebind it for a later output read of the same invocation. Only the = value of an object's own feature follows what it read.

Computed columns

A projection may also derive columns: each Column(name, expression) entry appends a named column whose expression is evaluated once per row over the row element's declared features. Arithmetic (+, -, *, /), string concatenation with + and ?? defaults for absent values are supported:

calc def MassBudget :> Query {
	in root : Element;
	Project(
		source = PartsByMass(root = root),
		properties = ("name", "mass"),
		columns = (
			Column(name = "massLbs", expression = (Subsystem::mass ?? 0.0) * 2.2),
			Column(name = "label", expression = "part: " + Element::name)
		)
	)
}
$ sysml cookbook.sysml -run-query "Cookbook::MassBudget root=Cookbook::telescope"
✓ Query Cookbook::MassBudget returned 5 rows
  Columns: name, mass, massLbs, label
  Row 1: Cookbook::telescope::mountControl
    name = "mountControl"
    mass = 15.0
    massLbs = 33.0
    label = "part: mountControl"
  ...

Feature references name the declaring definition (Subsystem::mass, Element::name); a row element that lacks the feature makes the expression fail with a typed error naming the query, column and row — unless a ?? default covers it, which is why MassBudget's massLbs defaults to 0.0 for the two connections in its results. Computed names join the projection: OrderBy can sort by them and a table's groupBy can group by them.

Every built-in property is reachable the same way — Element::shortName, Element::declaredShortName and Element::documentation included — so (Element::shortName ?? "—") + ": " + Element::name labels a row by its identifier. A column is one value per row: an element carrying two doc bodies fails a column over Element::documentation with a typed column-cardinality error, where the plain "documentation" projection above carries both.

Quantities take part in column arithmetic with the runtime's rules, so a column keeps its unit: Stage::mass * 2 is 4580000 [kg], Stage::mass / 1000 is 2290 [kg], Stage::mass / Stage::length is 54523.8… [kg/m], and a ratio of like quantities (Stage::length / Stage::length) is a bare number. Adding or subtracting quantities converts the right operand into the left operand's unit (1 [km] + 500 [m] is 1.5 [km]); operands of different dimensions — Stage::mass + Stage::length, or a quantity plus a bare number — are a typed column-incommensurable error naming the column, the row and both units.

Query invokes query

MassTable above already shows it: PartsByMass(root = root) invokes the other query with its own bindings, and AllParts invokes Children's sibling the same way. Factoring collection into one base query and deriving filtered/sorted/projected variants from it is the intended style. The engine compiles the invocation graph up front: an unknown name, a cycle, or blowing the depth/count budget is a typed error at that point.

Relationship traversal

RelatedElements walks one named relationship kind from each source element:

RelatedElements(
	source = <elements>,
	relationshipKind = "<kind>",   // specialization, subsetting, redefinition,
	                                // typing, connection, allocation,
	                                // satisfaction or verification
	direction = "<direction>",     // outgoing or incoming
	maxDepth = <n>
)

Direction is from the relationship's own point of view — outgoing follows it as declared, incoming follows it backwards. Traversal is breadth-first to maxDepth, deduplicated, in declaration order, and bounded by a visit budget so a pathological model terminates with a typed error rather than hanging.

Connections

Connection edges run port to port — traverse from the connector's endpoint, not from the part that owns it:

calc def ConnectedTo :> Query {
	in origin : Element;
	RelatedElements(
		source = origin,
		relationshipKind = "connection",
		direction = "outgoing",
		maxDepth = 1
	)
}
$ sysml cookbook.sysml -run-query "Cookbook::ConnectedTo origin=Cookbook::telescope::primaryMirror::opticalOut"
✓ Query Cookbook::ConnectedTo returned 1 row
  Row 1: Cookbook::telescope::instrumentCluster::opticalIn

outgoing follows connect A to B from A's endpoint to B's; incoming follows it the other way. Untyped connect clauses carry connection edges too.

Allocations

allocate X to Y is outgoing from X, incoming to Y:

calc def AllocatedTargets :> Query {
	in origin : Element;
	RelatedElements(
		source = origin,
		relationshipKind = "allocation",
		direction = "outgoing",
		maxDepth = 1
	)
}
$ sysml cookbook.sysml -run-query "Cookbook::AllocatedTargets origin=Cookbook::telescope::instrumentCluster"
✓ Query Cookbook::AllocatedTargets returned 1 row
  Row 1: Cookbook::scienceComputer

Satisfy relationships

satisfy R by P points from the satisfying element to the requirement, so "who satisfies this requirement" is an incoming traversal from the requirement:

calc def SatisfiedBy :> Query {
	in req : Element;
	RelatedElements(
		source = req,
		relationshipKind = "satisfaction",
		direction = "incoming",
		maxDepth = 1
	)
}
$ sysml cookbook.sysml -run-query "Cookbook::SatisfiedBy req=Cookbook::massRequirement"
✓ Query Cookbook::SatisfiedBy returned 1 row
  Row 1: Cookbook::telescope

Verify relationships

Likewise, "which verifications cover this requirement" is incoming from the requirement; the result is the verification usage whose objective verifys it:

calc def VerifiedBy :> Query {
	in req : Element;
	RelatedElements(
		source = req,
		relationshipKind = "verification",
		direction = "incoming",
		maxDepth = 1
	)
}
$ sysml cookbook.sysml -run-query "Cookbook::VerifiedBy req=Cookbook::massRequirement"
✓ Query Cookbook::VerifiedBy returned 1 row
  Row 1: Cookbook::massVerification

Specialization (and the other structural kinds)

specialization, subsetting, redefinition and typing traverse the declaration hierarchy. Incoming specialization from a general type finds what specializes it, transitively to maxDepth:

calc def Specializers :> Query {
	in general : Element;
	RelatedElements(
		source = general,
		relationshipKind = "specialization",
		direction = "incoming",
		maxDepth = 2
	)
}
$ sysml cookbook.sysml -run-query "Cookbook::Specializers general=Cookbook::Subsystem"
✓ Query Cookbook::Specializers returned 2 rows
  Row 1: Cookbook::OpticalSubsystem
  Row 2: Cookbook::MirrorAssembly

Traversal results are elements like any others — feed them into Project for a traceability table, as the worked example does for its requirement section.