Skip to content

Modeling product lines and variability

A product line — a vehicle sold with two engines and two transmissions, a spacecraft bus flown with or without a sunroof-sized radiator panel — is one design with choices left open, and a product is that design with every choice made. SysML v2 writes the open design as a variation whose variants are the choices, ties the choices together with assertions, and writes a product as a specialization that picks one variant per choice. This chapter builds the vehicle family of the OMG training corpus from its superset model to its solved configurations, and shows what the validator, the REPL, the solver and the renderer each say about it. Every command and every output below was run on the develop build.

The worked model is the three files of examples/sysml-v2-training/36. Variability/, fetched with ./scripts/download-training-examples.sh. Three larger models are referenced where they add something the small one lacks; they arrive with ./scripts/download-pilot-corpora.sh:

  • examples/pilot-corpora/sysml-examples/Variability Examples/VehicleVariabilityModel.sysml, the OMG example the training files are cut from, with an optional sunroof and variation points inside actions;
  • examples/pilot-corpora/sysml-validation/07-Variant Configuration/7a-Variant Configuration - General Concept.sysml, nested variation points and two configurations;
  • examples/pilot-corpora/sysml-validation/07-Variant Configuration/7b-Variant Configurations.sysml, a family whose assertions tie an engine, a transmission and a requirement choice together, with a nested variation on the rear axle.

The vocabulary is that of SysML v2.1 Beta 2 §7.6.7 "Variations and Variants" and of Annex A.12, the variability example; ./scripts/download-spec-examples.sh puts the text at build/spec-docs/sysml.txt. Build the tools once with make build; %configure needs an SMT solver, see Solving the family.

The superset model

A product line is written once as a superset model: every part the family can have, with the choices marked. The mark is the variation prefix. SysML v2.1 Beta 2 §7.6.7 defines it: "A definition or usage is specified as a variation by placing the keyword variation before its kind keyword. A variation is always abstract, so the abstract keyword is not used on a variation." The members of a variation are its variants, each prefixed variant, and "variant usages may only be declared within a variation."

Variation Definitions.sysml declares the engine choice as a variation definition whose variants are two separately declared parts, and the cylinder-bore choice as a variation attribute definition whose variants carry values:

variation attribute def DiameterChoices :> Diameter {
    variant attribute diameterSmall = 70[mm];
    variant attribute diameterLarge = 100[mm];
}

variation part def EngineChoices :> Engine {
    variant '4cylEngine';
    variant '6cylEngine';
}

A variation definition specializes the definition its variants are kinds of (EngineChoices :> Engine), so a usage typed by it is an engine whichever variant is chosen. The bare variant '4cylEngine'; form is the one §7.6.7 describes for "a non-variant usage … declared to act as a variant": the variant is the part '4cylEngine' declared earlier in the package, which redefines cylinder[4] on Engine.

Variation Usages.sysml builds the family. engine is a usage typed by the variation definition; transmission is a variation usage, with its variants declared in place; and an assert constraint says which engine goes with which transmission:

abstract part vehicleFamily : Vehicle {
    part engine : EngineChoices[1];

    variation part transmission : Transmission[1] {
        variant manualTransmission;
        variant automaticTransmission;
    }

    assert constraint {
        (engine == engine::'4cylEngine' and
            transmission == transmission::manualTransmission) xor
        (engine == engine::'6cylEngine' and
            transmission == transmission::automaticTransmission)
    }
}

Each variation point is a feature of the family, and each variant is reached through it: engine::'4cylEngine' is the variant of the type of engine, transmission::manualTransmission the variant owned by transmission. The xor is what makes this a product line rather than a parts list: four combinations are expressible, two are products. The family itself is abstract because it is not a vehicle anyone builds.

An enumeration is a variation too. §7.8.2 says "an enumeration definition is always considered to be a variation" whose enumerated values are its variants; enum def is the short form, and the rules of the next section apply to it unchanged.

Variants nest. In VehicleVariabilityModel.sysml the '6cylEngine' variant owns a variation port and binds its cylinder bore to DiameterChoices, with an assertion of its own tying the two together — a choice that only exists once the six-cylinder engine is chosen:

variation part def EngineChoices :> Engine {
    variant '4cylEngine';
    variant '6cylEngine' {
        variation port :>> autoPort {
            variant port autoPort1;
            variant port autoPort2;
        }

        part :>> cylinder {
            attribute :>> diameter : DiameterChoices;
        }

        assert constraint {
            (autoPort == autoPort::autoPort1 and cylinder.diameter == cylinder::diameter::diameterSmall) xor
            (autoPort == autoPort::autoPort2 and cylinder.diameter == cylinder::diameter::diameterLarge)
        }
    }
}

The same model puts a variation point on a multiplicity (sunroof with variants withSunroof[1] and withoutSunroof[0]) and on actions (generateTorque, amplifyTorque), and 7a nests one variation point inside another (subsystemA and subsystemB under a vehicle). Annex A.12 walks the vehicle example in prose; its vehicleFamily is the one the training files are cut from.

What you have learned: a variation is an abstract definition or usage whose members are variants; a variation point is a feature typed by a variation definition or declared as a variation usage; the assertions of the family say which variants go together; enumerations are variations; and variants can own variation points of their own.

What the validator enforces

The three training files validate clean together:

$ bin/sysml -validate "examples/sysml-v2-training/36. Variability/Variation Definitions.sysml" \
    "examples/sysml-v2-training/36. Variability/Variation Usages.sysml" \
    "examples/sysml-v2-training/36. Variability/Variation Configuration.sysml"
✓ package 'Variation Definitions'
✓ package 'Variation Usages'
✓ package 'Variation Configuration'
✓ examples/sysml-v2-training/36. Variability/Variation Definitions.sysml, examples/sysml-v2-training/36. Variability/Variation Usages.sysml, examples/sysml-v2-training/36. Variability/Variation Configuration.sysml: no errors

So do VehicleVariabilityModel.sysml, 7a and 7b. Three rules of §7.6.7 are checked on every file; each is shown here on a small model that breaks it, with the diagnostic as the build prints it.

A variation must not specialize another variation. A second layer of choices is written as a nested variation point, not as a subtype:

variation part def MoreEngineChoices :> EngineChoices {
    variant part huge : Engine;
}
/tmp/ple/p1.sysml:7:45: error[variation-specialization]: A variation must not specialize another variation.
    variation part def MoreEngineChoices :> EngineChoices {
                                            ^~~~~~~~~~~~~

The same rule reaches a variation usage typed by a variation definition — variation part transmission : TransmissionChoices; is refused at the type with the same text — which is what the last section is about.

A variant belongs to a variation, and nothing else owns one:

part def Vehicle {
    variant part spare : Engine;
}
/tmp/ple/p2.sysml:4:9: error[variant-outside-variation]: A variant must be an owned member of a variation.
        variant part spare : Engine;
        ^~~~~~~~~~~~~~~~~~~~~~~~~~~~

And the converse: every usage a variation owns is a variant. A plain part inside a variation is a modelling slip, not a shared member:

variation part def EngineChoices :> Engine {
    variant part small : Engine;
    part big : Engine;
}
/tmp/ple/p3.sysml:5:9: error[variation-member-not-variant]: An owned usage of a variation must be a variant.
        part big : Engine;
        ^~~~~~~~~~~~~~~~~~

Because an enumeration definition is a variation, the first rule forbids specializing one — by a declared variation or by another enum def. The diagnostic says why:

enum def Color { red; green; }
variation attribute def ColorChoices :> Color {
    variant attribute red2 : Color;
}
enum def Shade :> Color { dark; }
/tmp/ple/p4.sysml:4:45: error[variation-specialization]: variation `ColorChoices` must not specialize enumeration definition `Color`: every enumeration definition is a variation whose enumerated values are its variants, and a variation must not specialize another variation
    variation attribute def ColorChoices :> Color {
                                            ^~~~~
/tmp/ple/p4.sysml:7:23: error[variation-specialization]: enumeration definition `Shade` must not specialize enumeration definition `Color`: every enumeration definition is a variation whose enumerated values are its variants, and a variation must not specialize another variation; declare the enumerated values of `Shade` directly
    enum def Shade :> Color { dark; }
                      ^~~~~

One more is caught by the parser, from the sentence of §7.6.7 that a variation "is always abstract, so the abstract keyword is not used":

/tmp/ple/p5.sysml:3:14: error[syntax]: 'variation' cannot follow 'abstract': a prefix says 'abstract' or 'variation', not both
    abstract variation part def EngineChoices :> Engine {
             ^~~~~~~~~

Each of these ends sysml: <file> did not analyse cleanly; no check was made, with exit status 2: a file that breaks a variability rule is not checked further. The probe files are a few lines each and worth having students write themselves; the ones above were run as /tmp/ple/p1.sysml to p5.sysml, each a package around the lines shown.

What you have learned: the validator holds the shape of a product line — variations do not specialize variations (enumerations included), variants live only in variations, and a variation owns nothing but variants — and says which rule and which line when a model breaks it.

Configurations

A product is a configuration: a specialization of the family that redefines each variation point to one of its variants. Variation Configuration.sysml has two:

part vehicle4Cyl :> vehicleFamily {
    part redefines engine = engine::'4cylEngine';
    part redefines transmission = transmission::manualTransmission;
}

part vehicle6Cyl :> vehicleFamily {
    part redefines engine = engine::'6cylEngine';
    part redefines transmission = transmission::manualTransmission;
}

part redefines engine = … and part :>> engine = … are the same declaration. The configuration inherits the family's assertion, and that is the point: vehicle4Cyl picks the four-cylinder engine with the manual transmission, one of the two products the xor admits; vehicle6Cyl picks the six-cylinder engine with the manual transmission, a pair the family forbids. The training corpus ships the inconsistent one deliberately — it is the configuration the rest of this chapter catches.

Open the three files in the REPL (bin/sysml with the three paths) and ask what each configuration selected. A selection is a value, and == compares it with the variant:

sysml> %eval 'Variation Configuration'::vehicle4Cyl.engine == 'Variation Definitions'::EngineChoices::'4cylEngine'
✓ 'Variation Configuration'::vehicle4Cyl.engine == 'Variation Definitions'::EngineChoices::'4cylEngine'
  = true
sysml> %eval 'Variation Configuration'::vehicle4Cyl.transmission == 'Variation Usages'::vehicleFamily::transmission::manualTransmission
✓ 'Variation Configuration'::vehicle4Cyl.transmission == 'Variation Usages'::vehicleFamily::transmission::manualTransmission
  = true
sysml> %eval 'Variation Configuration'::vehicle6Cyl.engine == 'Variation Definitions'::EngineChoices::'4cylEngine'
✓ 'Variation Configuration'::vehicle6Cyl.engine == 'Variation Definitions'::EngineChoices::'4cylEngine'
  = false

The inherited assertion is checked the way any assertion is: %instantiate a configuration, then %validate it (%instantiate and %validate in the command reference). The consistent configuration holds:

sysml> %instantiate 'Variation Configuration'::vehicle4Cyl
✓ Created instance of 'Variation Configuration'::vehicle4Cyl
  ID: 6
  Use %features 'Variation Configuration'::vehicle4Cyl to inspect
sysml> %validate vehicle4Cyl
✓ assert constraint holds (on 'Variation Configuration'::vehicle4Cyl ID: 6)
✓ 'Variation Configuration'::vehicle4Cyl is valid: its 1 assertion holds
  standing: holds (observed: 1 run under reverse)

The inconsistent one fails, and the report prints the assertion that failed:

sysml> %instantiate 'Variation Configuration'::vehicle6Cyl
✓ Created instance of 'Variation Configuration'::vehicle6Cyl
  ID: 13
  Use %features 'Variation Configuration'::vehicle6Cyl to inspect
sysml> %validate vehicle6Cyl
✗ assert constraint fails (on 'Variation Configuration'::vehicle6Cyl ID: 13)
  Assertion evaluated to false: engine == engine::4cylEngine and transmission == transmission::manualTransmission xor engine == engine::6cylEngine and transmission == transmission::automaticTransmission
✗ 'Variation Configuration'::vehicle6Cyl is not valid: 1 of 1 assertion fails
  standing: violated (witnessed: 1 run under reverse)

standing is the runtime's own word for what it established (the standing of an answer): holds (observed: 1 run …) is one evaluation of the assertion on this object, not a proof over every configuration. The proof is the next section.

What you have learned: a configuration is a specialization that redefines each variation point with = to one variant; the family's assertion is inherited; %eval reads a selection back, and %instantiate + %validate check one configuration against the assertion and name it when it fails.

Solving the family

%validate checks the configuration you wrote. %configure turns the question around: given the family, which selections of variants satisfy its assertions? It hands the assertions to an SMT solver, so it needs z3 (or cvc5) on PATH or named by OPENSYSML_SMT; chapter 1 says how to install one. Without a solver the command refuses rather than guessing:

error: no SMT solver found: install z3 (`apt install z3`, `brew install z3`) or cvc5, or set OPENSYSML_SMT to a solver executable; looked for [z3 cvc5] on PATH
  standing: not covered (solve refused: no SMT solver found: install z3 (`apt install z3`, `brew install z3`) or cvc5, or set OPENSYSML_SMT to a solver executable; looked for [z3 cvc5] on PATH)

Named with the family alone, %configure returns one consistent selection:

sysml> %configure 'Variation Usages'::vehicleFamily
✓ Abstract part vehicleFamily permits a selection of variants (z3, 16ms)
  'Variation Usages'::vehicleFamily::engine = 'Variation Definitions'::EngineChoices::'4cylEngine'
  'Variation Usages'::vehicleFamily::transmission = 'Variation Usages'::vehicleFamily::transmission::manualTransmission
  One witness: use %configure 'Variation Usages'::vehicleFamily all for every consistent selection.
  standing: satisfiable (witnessed: 1 query by solve)

Fix some variation points yourself with <variation>=<variant> and the solver fills in the rest — or refuses, naming the assertion that refuses and the source line it is on. The four-cylinder engine with the automatic transmission is refused:

sysml> %configure 'Variation Usages'::vehicleFamily engine='4cylEngine' transmission=automaticTransmission
✗ the chosen variants are not consistent with Abstract part vehicleFamily (z3, 14ms)
  Already fixed:
    'Variation Usages'::vehicleFamily::engine = 'Variation Definitions'::EngineChoices::'4cylEngine'  (chosen)
    'Variation Usages'::vehicleFamily::transmission = 'Variation Usages'::vehicleFamily::transmission::automaticTransmission  (chosen)
  In the conflict: 'Variation Usages'::vehicleFamily::engine = 'Variation Definitions'::EngineChoices::'4cylEngine', 'Variation Usages'::vehicleFamily::transmission = 'Variation Usages'::vehicleFamily::transmission::automaticTransmission
  Against: required condition { engine == engine::'4cylEngine' and transmission == transmission::manualTransmission xor engine == engine::'6cylEngine' and transmission == transmission::automaticTransmission } of vehicleFamily at examples/sysml-v2-training/36. Variability/Variation Usages.sysml:18:4
  standing: unsatisfiable (proved over inputs: 1 query by solve)

all enumerates every consistent selection and says whether the list is complete — here two, "which are all of them". That line is the product line's catalogue, derived from the superset model rather than maintained beside it:

sysml> %configure 'Variation Usages'::vehicleFamily all
✓ Abstract part vehicleFamily permits 2 selections of variants, which are all of them (z3, 15ms)
  1.
    'Variation Usages'::vehicleFamily::engine = 'Variation Definitions'::EngineChoices::'4cylEngine'
    'Variation Usages'::vehicleFamily::transmission = 'Variation Usages'::vehicleFamily::transmission::manualTransmission
  2.
    'Variation Usages'::vehicleFamily::engine = 'Variation Definitions'::EngineChoices::'6cylEngine'
    'Variation Usages'::vehicleFamily::transmission = 'Variation Usages'::vehicleFamily::transmission::automaticTransmission
  standing: satisfiable (witnessed: 1 query by solve)

Named with a configuration, %configure takes the values the configuration declares as fixed ((declared) rather than (chosen)) and asks whether any selection is consistent with them. For vehicle4Cyl there is one; for vehicle6Cyl there is none, and the report is the same conflict the %validate run found, this time proved rather than observed:

sysml> %configure 'Variation Configuration'::vehicle4Cyl
✓ Part vehicle4Cyl permits a selection of variants (z3, 14ms)
  Already fixed:
    'Variation Configuration'::vehicle4Cyl::engine = 'Variation Definitions'::EngineChoices::'4cylEngine'  (declared)
    'Variation Configuration'::vehicle4Cyl::transmission = 'Variation Usages'::vehicleFamily::transmission::manualTransmission  (declared)
  'Variation Configuration'::vehicle4Cyl::engine = 'Variation Definitions'::EngineChoices::'4cylEngine'
  'Variation Configuration'::vehicle4Cyl::transmission = 'Variation Usages'::vehicleFamily::transmission::manualTransmission
  One witness: use %configure 'Variation Configuration'::vehicle4Cyl all for every consistent selection.
  standing: satisfiable (witnessed: 1 query by solve)
sysml> %configure 'Variation Configuration'::vehicle6Cyl
✗ Part vehicle6Cyl permits no selection of variants consistent with the values it fixes (z3, 14ms)
  Already fixed:
    'Variation Configuration'::vehicle6Cyl::engine = Variation Definitions::EngineChoices::6cylEngine  (declared)
    'Variation Configuration'::vehicle6Cyl::transmission = Variation Usages::vehicleFamily::transmission::manualTransmission  (declared)
  In the conflict: 'Variation Configuration'::vehicle6Cyl::engine = Variation Definitions::EngineChoices::6cylEngine, 'Variation Configuration'::vehicle6Cyl::transmission = Variation Usages::vehicleFamily::transmission::manualTransmission
  Against: required condition { engine == engine::4cylEngine and transmission == transmission::manualTransmission xor engine == engine::6cylEngine and transmission == transmission::automaticTransmission } of vehicleFamily at examples/sysml-v2-training/36. Variability/Variation Usages.sysml:18:4
  standing: unsatisfiable (proved over inputs: 1 query by solve)

The query is the conjunction of every assertion about the named part's objects — its own and inherited asserted constraints, the requirements it carries and the satisfactions it states, the same collection %validate checks — what its nested parts' own objects assert included, read through the holding feature, as deep as %validate reaches. A nested part the query cannot read as one object is named with the reason instead. 7b's anyVehicleConfig ties an engine, a transmission and a performance requirement together, and owns a rearAxleAssembly whose own rearWheelChoice is asserted one level down with ->forAll:

sysml> %configure '7b-Variant Configurations'::VariabilityModel::anyVehicleConfig all
✓ Part anyVehicleConfig permits 2 selections of variants, which are all of them (z3, 15ms)
  1.
    '7b-Variant Configurations'::VariabilityModel::anyVehicleConfig::engineChoice = '7b-Variant Configurations'::VariabilityModel::anyVehicleConfig::engineChoice::'6cylEngine'
    '7b-Variant Configurations'::VariabilityModel::anyVehicleConfig::engineRqtChoice = '7b-Variant Configurations'::VariabilityModel::anyVehicleConfig::engineRqtChoice::highPerformanceRequirement
    '7b-Variant Configurations'::VariabilityModel::anyVehicleConfig::'rearAxleAssembly.rearWheelChoice' = '7b-Variant Configurations'::VariabilityModel::anyVehicleConfig::rearAxleAssembly::rearWheelChoice::wideRimWheel
    '7b-Variant Configurations'::VariabilityModel::anyVehicleConfig::transmissionChoice = '7b-Variant Configurations'::VariabilityModel::anyVehicleConfig::transmissionChoice::automaticTransmission
  2.
    '7b-Variant Configurations'::VariabilityModel::anyVehicleConfig::engineChoice = '7b-Variant Configurations'::VariabilityModel::anyVehicleConfig::engineChoice::'4cylEngine'
    '7b-Variant Configurations'::VariabilityModel::anyVehicleConfig::engineRqtChoice = '7b-Variant Configurations'::VariabilityModel::anyVehicleConfig::engineRqtChoice::normalPerformanceRequirement
    '7b-Variant Configurations'::VariabilityModel::anyVehicleConfig::'rearAxleAssembly.rearWheelChoice' = '7b-Variant Configurations'::VariabilityModel::anyVehicleConfig::rearAxleAssembly::rearWheelChoice::narrowRimWheel
    '7b-Variant Configurations'::VariabilityModel::anyVehicleConfig::transmissionChoice = '7b-Variant Configurations'::VariabilityModel::anyVehicleConfig::transmissionChoice::manualTransmission
  standing: satisfiable (witnessed: 1 query by solve)

The two selections are the whole configuration: the rear-axle constraint quantifies with ->forAll over rearWheelChoice, and a configuration holds only the variant a variation point selects (SysML v2.1 §8.3.6, §7.6.7), so rearWheelChoice->forAll { in ref w; w == rearWheelChoice::narrowRimWheel } is the selection rearWheelChoice == rearWheelChoice::narrowRimWheel, read through the holding part as 'rearAxleAssembly.rearWheelChoice'. The solver states that only for a point holding at least one object; one whose multiplicity admits none is refused by name with that reason, as is a quantifier body comparing the iterate with anything but a variant. Nothing is left out silently: a nested part is either in the query or named with why not.

VehicleVariabilityModel.sysml shows the nested case the solver does state. Its '6cylEngine' variant alone declares the autoPort variation and the bore's DiameterChoices, and asserts over them, so those are variation points only where engine selects that variant: the family permits three selections, the four-cylinder one listing no port or bore, and asking for engine='4cylEngine' engine.autoPort=autoPort1 is refused with engine.autoPort is a variation point only when engine selects '6cylEngine'.

sysml> %configure VehicleVariabilityModel::'150% Model'::PartsTree::vehicleFamily all
✓ Abstract part vehicleFamily permits 3 selections of variants, which are all of them (z3, 16ms)
  1.
    VehicleVariabilityModel::'150% Model'::PartsTree::vehicleFamily::engine = VehicleVariabilityModel::'150% Model'::PartsTree::EngineChoices::'4cylEngine'
    VehicleVariabilityModel::'150% Model'::PartsTree::vehicleFamily::transmission = VehicleVariabilityModel::'150% Model'::PartsTree::vehicleFamily::transmission::manualTransmission
  2.
    VehicleVariabilityModel::'150% Model'::PartsTree::vehicleFamily::engine = VehicleVariabilityModel::'150% Model'::PartsTree::EngineChoices::'6cylEngine'
    VehicleVariabilityModel::'150% Model'::PartsTree::vehicleFamily::'engine.autoPort' = VehicleVariabilityModel::'150% Model'::PartsTree::EngineChoices::'6cylEngine'::autoPort::autoPort1
    VehicleVariabilityModel::'150% Model'::PartsTree::vehicleFamily::'engine.cylinder.diameter' = VehicleVariabilityModel::'150% Model'::PartsTree::DiameterChoices::diameterSmall
    VehicleVariabilityModel::'150% Model'::PartsTree::vehicleFamily::transmission = VehicleVariabilityModel::'150% Model'::PartsTree::vehicleFamily::transmission::automaticTransmission
  3.
    VehicleVariabilityModel::'150% Model'::PartsTree::vehicleFamily::engine = VehicleVariabilityModel::'150% Model'::PartsTree::EngineChoices::'6cylEngine'
    VehicleVariabilityModel::'150% Model'::PartsTree::vehicleFamily::'engine.autoPort' = VehicleVariabilityModel::'150% Model'::PartsTree::EngineChoices::'6cylEngine'::autoPort::autoPort2
    VehicleVariabilityModel::'150% Model'::PartsTree::vehicleFamily::'engine.cylinder.diameter' = VehicleVariabilityModel::'150% Model'::PartsTree::DiameterChoices::diameterLarge
    VehicleVariabilityModel::'150% Model'::PartsTree::vehicleFamily::transmission = VehicleVariabilityModel::'150% Model'::PartsTree::vehicleFamily::transmission::automaticTransmission
  standing: satisfiable (witnessed: 1 query by solve)

The same solver is reachable from an agent as the MCP configure tool (sysml-mcp reference), and from a notebook through the Jupyter kernel. It has no flag on the sysml command line: bin/sysml -help lists -validate but no -configure or -solve, so a script drives it through the REPL's standard input or through MCP.

What you have learned: %configure <family> finds one consistent selection, <variation>=<variant> fixes some and solves the rest or names the refusing assertion, all enumerates with a completeness verdict, a configuration is solved over the values it declares, nested parts' own assertions are in the query with a variant's applying only under that variant, and what the solver cannot state is named with the reason — that is where %validate takes over.

Seeing variability

A view over the family shows the choices as rows. Put this beside the three training files (as a fourth file on the command line) and render it:

package 'Variation Views' {
    private import 'Variation Definitions'::*;
    private import 'Variation Usages'::*;
    private import Views::*;

    view familyTree {
        expose EngineChoices::**;
        expose vehicleFamily::**;
        render asTreeDiagram;
    }
    view familyInterconnection {
        expose vehicleFamily::**;
        render asInterconnectionDiagram;
    }
}
sysml> %render 'Variation Views'::familyTree
'Variation Views'::familyTree - tree rendering (render asTreeDiagram)

variation part def EngineChoices
  variant '4cylEngine'
    part cylinder
      attribute diameter
  variant '6cylEngine'
    part cylinder
      attribute diameter
  part cylinder
    attribute diameter
part vehicleFamily
  part engine
    part cylinder
      attribute diameter
  variation part transmission
    variant manualTransmission
    variant automaticTransmission
  assert constraint

relationships:
  vehicleFamily *-- EngineChoices: engine

The text form writes the keywords; the diagram forms (mermaid, dot, plantuml, d2) write them as stereotype rows, «variation part def» and «variant», on the box, and the editor diagram panel renders the same views. A tree is not written as svg directly — that form is for run renderings — so a slide takes the Mermaid or DOT source through its own converter. cylinder appears under EngineChoices and under each variant because every engine has cylinders and each variant redefines how many. The interconnection rendering keeps the variation rows and draws the connections between the exposed parts; this family declares none, so it is the parts alone:

sysml> %render 'Variation Views'::familyInterconnection
'Variation Views'::familyInterconnection - interconnection rendering (render asInterconnectionDiagram)

part vehicleFamily
  part engine
    part cylinder
      attribute diameter
  variation part transmission
    variant manualTransmission
    variant automaticTransmission
  assert constraint

The same views render from the command line with -render <view> and -render-form <form> (CLI reference); the forms and what each draws are listed with %render.

What you have learned: a view that exposes the family draws every variation point and its variants as rows marked variation and variant, in text or as a diagram, so a class can see the superset model it has been reading.

What the specification says that tools disagree on

Annex A.12 of SysML v2.1 Beta 2 writes the transmission variation point of its vehicle family as

variation part transmission : TransmissionChoices;

a variation usage typed by a variation definition. That declaration violates the specification's own constraint validateUsageVariationSpecialization, the rule of §7.6.7 that a variation must not specialize another variation; OpenSysML rejects it with the variation-specialization diagnostic shown above, and the OMG pilot implementation rejects it too. It is a known inconsistency between the specification's example and its normative text, recorded in the specification–pilot gap register and the specification compliance tables. The training files and VehicleVariabilityModel.sysml avoid it by declaring the variants of transmission in place, which is the form to teach.

What you have learned: when an example in the specification and a validator disagree, the constraint in the normative text decides, and both implementations here apply it.