Part 2 · Model execution and studies
Executing the trade studies on a SysML v2 plant model
Supports the main post.
1Introduction
We want to iterate on engineering designs quickly enough to learn from them. That means varying numerical parameters, comparing component and material choices, and changing how the system is assembled. After each change, we want to evaluate the consequences: what performance does this design deliver, what does it cost, and which engineering limits does it violate?
The earlier post on agentic modeling described why SysML v2 is useful for representing these alternatives. This post explains how we turn those models into executable calculations with sysml-codegen, run them with TEAx, and why we designed the pipeline this way.
1.1Exploring the design space: parameters, components, and architecture
Each kind of design change needs different support from the model and its executable program.
Figure 1 sets the three kinds side by side. Each card names a kind of change and describes it; the band above the cards says how we iterate on it.
Run the existing program with different inputs
Numerical parameters let us explore choices such as dimensions and operating conditions while keeping the same components and equations. The generated program exposes these values as inputs, with initial settings in JSON files. A study can vary the inputs and evaluate the resulting designs without changing the program.
Edit the SysML model and generate a new program
Component and material choices are categorical: we select one alternative or another. These choices do not fit naturally into a numerical parameter sweep, because an alternative may bring different properties, equations, and engineering limits. In SysML, a component definition describes a kind of component and its calculations; a usage places a component of that kind in a particular design. We can create alternative definitions and select which one a design uses.
Architectural choices change the assembly itself: how many components there are and how they connect. The plant model expresses these choices through component usages and their connections. Codegen uses that assembly to construct the calculation network, so changing the assembly changes which calculations run and where they get their inputs.
These choices lead to two ways of iterating. A numerical study runs the existing program with different inputs. Changing component definitions or plant architecture generally means editing the SysML model and generating a new program. Both let us evaluate the consequences of a design decision.
1.2Why build an execution pipeline?
Two reasons. First, when we started, we did not have an out-of-the-box SysML v2 execution tool ready for this workflow. Second, we needed machinery that agents could easily configure and run within a harness. That meant programmatic control over the model's inputs and repeated evaluations, along with the ability to incorporate custom calculations beyond what our SysML execution tools supported.
To support those studies, we built a code-generation pipeline that turns an assembled SysML plant model into an executable Python program. The program accepts design parameters, calculates performance and cost, and checks engineering limits. TEAx runs it repeatedly so a study can compare different design points.
Python also gives us access to more sophisticated computation and flexibility than we can express and execute through our supported SysML subset alone. Later, we will show how a custom calculation, such as a surrogate for a detailed simulation, can become part of the plant model.
1.3From component models to a study
Our demo models and explores stellarator designs. To show how the codegen pipeline works, we will focus on a study of the magnet system: how changing plant radius and coil ampere-turns affects cost and the design's ability to meet engineering limits.
We will follow the component equations and plant connections through code generation, then show how the resulting program evaluates and compares design points.
- SysML parts and connections
- Magnet, blanket, plasma, cooling
- Equations + engineering checks
- Resolve calculation inputs
- Translate equations to Python
- Write the TEAx connections
- Set radius and ampere-turns
- Calculate outputs and cost
- Record constraint results
Example: replace the blanket’s held breeding value with a transport-response calculation.
Example: sweep radius and coil ampere-turns using the same generated package.
The worked example follows our stellarator demo through seven steps:
- 2.1 Model a component: separate the magnet's reusable equations from the values and connections chosen for the plant.
- 2.2 Define acceptable designs: express engineering limits as constraints that each design point must pass.
- 2.3 Construct the calculation network: determine where every calculation gets its inputs and which other calculations it depends on.
- 2.4 Implement the mathematics: translate equations into Python and incorporate custom methods, including a simulation surrogate.
- 2.5 Change the component model: give the blanket a more detailed calculation while keeping its place in the plant.
- 2.6 Assemble the executable program: connect the Python calculations with TEAx and expose the inputs a study can vary.
- 2.7 Run a study: evaluate many design points and compare their feasibility and cost.
All code examples come from the stellarator demo as read on 19 September 2026. Excerpts omit surrounding imports and documentation where indicated.
2Process: a worked example
2.1Modeling a component: separate its equations from the plant's choices
We begin by defining a component’s calculations and connecting their inputs and outputs to the plant model.
The stellarator's magnets produce the magnetic field that confines the plasma. For a proposed design, we want to calculate that field from the chosen coil ampere-turns and geometry. Other calculations then use the result to evaluate the plasma and check the magnet's engineering limits.
The magnet model uses a reusable field equation. The plant supplies the design values and connects the magnet to the plasma's radius. The figure below shows how those values reach the equation and how its result becomes available to the rest of the plant.
calc def 'Coil Set Axis Field' {
in attribute n_coils : Real;
in attribute I_coil : Real;
in attribute k_link : Real;
in attribute R0 : Real;
in attribute mu0 : Real default 1.25663706212e-6;
in attribute two_pi : Real default 6.283185307179586;
out attribute B_axis : Real = mu0 * k_link * n_coils * I_coil / (two_pi * R0);
}
part stellaris : 'MFE Power Plant' {
part :>> magnet {
part :>> coil {
:>> n_coils = 48.0 {}
:>> I_coil = 15400000.0 {}
:>> k_link = 0.7731331164622419 {}
}
}
}
Inherited coil binding from 'MFE Power Plant':
:>> R0 = plasma.R {}
calc field_calc : 'Coil Set Axis Field' {
in n_coils = coil.n_coils;
in I_coil = coil.I_coil;
in k_link = coil.k_link;
in R0 = coil.R0;
}
Owned by 'Magnet System'. mu0 and two_pi retain their defaults.
attribute B : Real = field_calc.B_axis;
models/library/analyses/mfe_magnet_field.sysml, magnet component definition models/library/cost_structure/mfe_power_core.sysml, generic plant connections models/designs/generic_mfe/mfe_plant.sysml, and stellarator design values models/designs/stellarator_09/stellarator_plant.sysml. Documentation and unrelated members are omitted. The calculation usage and exposed attribute belong inside 'Magnet System'; the radius connection is inherited from the generic plant.The walk below follows the numbered boxes in Figure 3: the plant's values (1), the calculation usage that selects them (2), the equation they feed (3), and the attribute that exposes its result (4).
Start with the plant's choices. At the reference point, it specifies 48 coils and 15.4 million ampere-turns per reference coil. The coil's radius attribute, coil.R0, is connected to the plasma's major radius, plasma.R. Changing the plant's plasma radius therefore also changes the radius used by the magnet.
The calculation definition declares the field equation and its inputs. Inside the magnet, the calculation usage, field_calc, connects the coil's attributes to those inputs. For example, it binds the equation's R0 input to coil.R0, completing the connection from the plant's plasma radius to the field equation. The equation's constants, mu0 (vacuum permeability) and two_pi (twice π), come from their declared defaults.
The equation returns the calculated field as B_axis. The magnet exposes that result through an attribute named B, which other components and calculations can read. Downstream calculations use it to determine peak conductor field and plasma beta, the ratio of plasma pressure to magnetic pressure. A new radius or coil ampere-turn value therefore affects both the field calculation and the calculations that depend on its result.
This evaluates the one equation in Figure 3, B_axis = mu0 · k_link · n_coils · I_coil / (two_pi · R0), with n_coils = 48 and k_link = 0.7731 held at the plant's values and the constants at their defaults. Nothing downstream of it is calculated here.
At R0 = m and I_coil = MA-turn, the equation gives B_axis = T. The plant's reference values give 9.0 T, the axis-averaged field of the reference design.
The physical scope of this equation matters when interpreting those results. Here I_coil denotes ampere-turns per reference coil; current in an individual conductor is checked separately. The equation estimates axis-averaged field using a fixed linkage factor from the reference coil set. It is a reference-scaled approximation, not a full three-dimensional magnetic-equilibrium calculation.
2.2Constraints test whether a design point meets engineering limits
Next, we add constraints that test whether a design meets the modeled engineering limits.
Later, we will show how a study sweeps across a wide range of input combinations, such as plant radius and coil ampere-turns. Each complete set of input values defines a design point. The calculations tell us what performance and cost each point produces, but some combinations will violate engineering limits. We need a way to identify those failures as the study runs.
We express engineering limits as constraints: expressions that evaluate to true or false for a design point. A constraint can compare a calculated quantity with a limit, or relate several quantities that must be consistent with one another. In SysML, an assert constraint applies the condition to the model so the generated program can check it during evaluation.
For example, the magnet's conductor is arranged into a winding pack, which must fit inside its casing. A larger pack may provide the needed current capacity, but it also needs enough physical space.
The magnet's fit calculation compares the pack dimensions with the available space, accounting for insulation and assembly allowances. It calculates clearance in two directions and exposes the smaller margin as fit_minimum_margin.
Figure 4 shows what that calculation compares, in a cross-section of one coil. Read it from the outside in: the casing wall, the cavity it leaves, then the required envelope, which is the pack plus its insulation and assembly clearance on every face. The shaded band between the envelope and the cavity is the margin, one value in the radial direction and one in the transverse direction; the check reads the smaller of the two. The proportions are schematic, and the layers are drawn thicker than they are.
models/library/analyses/mfe_winding_pack_fit.sysml.In the modelWhat the fit calculation reads
Inside the magnet, the fit calculation takes the pack's size and build-up from the winding pack, the radial space from the coil, and the cavity from the casing:
calc wp_fit : 'Winding Pack Casing Fit' {
in wp_side = winding_pack.wp_side;
in aspect_ratio = winding_pack.fit_aspect_ratio;
in internal_fraction_x = winding_pack.internal_build_x;
in internal_fraction_y = winding_pack.internal_build_y;
in ground_insulation = winding_pack.ground_insulation;
in radial_allocation = coil.coil_t;
in interior_y = casing.interior_y;
in wall_thickness = casing.wall_thickness;
in assembly_clearance = casing.assembly_clearance;
}
attribute fit_minimum_margin : Real = wp_fit.minimum_margin;
It compares a centered rectangular winding envelope with the casing cavity left after insulation and assembly allowances. The calculation is written by hand in Python; section 2.4 covers calculations like it.
The reusable constraint definition models/library/analyses/mfe_winding_pack_fit.sysml states the condition: the minimum clearance must be nonnegative.
constraint def 'Winding Pack Fits Casing' {
in attribute minimum_margin_in : Real;
minimum_margin_in >= 0.0
}
The stellarator plant applies that condition to its magnet's calculated clearance:
assert constraint wp_fit_ok : 'Winding Pack Fits Casing' {
in minimum_margin_in = magnet.fit_minimum_margin;
}
A negative clearance means the winding pack does not fit. The program still reports the design's calculated performance and cost, alongside the failed check. That lets a study show what a proposed change improves and which engineering limits it violates.
This fit check covers a local rectangular cross-section of the pack and casing cavity. Passing it does not establish that the entire nonplanar coil can be manufactured or assembled.
A design point passes the modeled feasibility checks only when all applicable assertions have been evaluated and satisfied. Results identify which assertions were assessed; an unassessed check cannot count as a pass. Even a point that passes every check still needs evidence for engineering limits the model does not yet represent.
2.3Reconstructing the calculation network
Codegen now identifies the calculations used by the assembled plant and determines where each one gets its inputs.
Those connections determine what must be calculated first. The program cannot check winding-pack clearance until it knows the pack dimensions, and in the version shown here it could not calculate those dimensions until it knew how much conductor the current needed. TEAx will use these dependencies to order the calculations when it runs the program.
SysML organizes the model around physical components. Codegen constructs a second view organized around calculations: each calculation is a node, and a connection from one calculation's output to another's input is an edge. This is the computational graph. It tells the execution system which results must be available before a calculation can run. The following figure shows the magnet branch of the generated stellarator graph.
Each arrow is an actual output-to-input binding. Secondary inputs and other plant branches are omitted. Amber boxes assess values; they do not resize the design.
Fourteen calculations and checks of the magnet branch, joined by the eighteen recorded output-to-input bindings; the panel below the figure lists all eighteen.
archive/write-up/sysml-codegen-assets/graph-evidence.md.In the recordThe 18 connections behind the graph
Each row is one output-to-input binding in the generated stellarator program, as captured on 19 September 2026. A calculation with a single result is named without an output. All eighteen match, name for name, the pipelines frozen with the 15 September and 17 September studies of section 2.7; that comparison covers names, types and bindings, not implementations or results.
| From | To |
|---|---|
radial_build.r_coil_centre | coil_length.a_coil |
field_calc | peak_field_calc.B_axis_in |
radial_build.r_coil_centre | peak_field_calc.a_coil_in |
peak_field_calc | current_sizing.B_peak |
current_sizing.selected_effective_density | wp_sizing.j_wp |
wp_sizing | wp_fit.wp_side |
coil_length | wp_volume.c_coil |
wp_sizing | wp_volume.wp_side |
wp_volume.vol_winding_pack | material_inventory.volume_in |
material_inventory.material_cost | winding_procurement.material_cost_in |
material_inventory.tape_volume | winding_procurement.tape_volume_in |
coil_length | winding_procurement.c_coil |
winding_procurement.conductor_length | conductor_current.conductor_length |
winding_procurement.tape_length | conductor_current.tape_length |
peak_field_calc | conductor_current.B_peak |
peak_field_calc | peak_field_ok.B_peak |
conductor_current.margin_fraction | reference_conductor_current_ok.margin_fraction_in |
wp_fit.minimum_margin | wp_fit_ok.minimum_margin_in |
Read the graph from the top. The calculated field and magnet geometry determine the peak field at the conductor. That field affects how much current the conductor can carry, so it feeds the calculation that sizes the conductor supply. The sizing result then feeds the calculation of winding-pack dimensions.
The pack dimensions feed two branches: one checks whether the pack fits, while the other calculates material quantities and their procurement cost. A separate check determines whether the resulting conductor supply can carry the required current. Following the arrows shows how a change in field can affect both the physical fit and the cost of the magnet.
This graph is the model as it stood on 19 September. The next day we removed the two sizing calculations, because they chose the winding's size from the current instead of evaluating a size the designer supplies (section 2.7.1). The pack side is now a supplied input: the fit and volume calculations read it directly, the cost calculations follow from the volume, and the current check reports whether that winding carries its current. Codegen resolves and orders those connections the same way.
This execution order depends on the model author declaring each calculation's inputs and outputs. If two calculations each need the other's result before they can run, neither can start. The pipeline therefore requires a network without circular dependencies (a directed acyclic graph, or DAG). It does not rearrange equations or build a solver automatically; a coupled calculation needs a numerical method supplied by the author. The direction of the connections also fixes which values a study can set: a value one calculation produces cannot also be supplied as a design choice.
Codegen constructs this network in three steps.
-
SysIDE reads the SysML declarations and resolves references between them.
It gives codegen a structured representation of the components, equations, and connections in the model.
-
Codegen walks the plant's component hierarchy to find each use of a component definition.
These records let components have their own values even when they share a definition.
-
Codegen now follows the model's connections to find the value supplying each calculation input.
It follows connections through attributes until it reaches either an external input or a calculation output.
2.3.1Parse declarations and references
SysIDE reads the SysML declarations and resolves references between them. When a calculation names coil.I_coil, SysIDE identifies the declared attribute that name refers to. It gives codegen a structured representation of the components, equations, and connections in the model.
Identifying a declaration is only the first step. A reusable component definition can be used at several places in a plant, with different values at each place. Codegen must determine which use of the component a calculation belongs to.
2.3.2Expand the assembled design
Codegen walks the plant's component hierarchy to find each use of a component definition. At each location, it applies the definitions and overrides selected by the plant and creates records for that component's values, calculations, and checks. For the stellarator's magnet, this includes the field calculation from section 2.1 and the fit check from section 2.2.
Each component at a particular location in the assembly is an occurrence. The field equation is declared in the reusable magnet definition; its occurrence in this plant belongs to stellaris.magnet. If the same definition is used elsewhere, codegen creates separate records for that occurrence. Components declared as arrays are also expanded into individual occurrences.
These records let components have their own values even when they share a definition. The next step connects calculations to those values, including values shared across components.
2.3.3Resolve each input to its supplier
Codegen now follows the model's connections to find the value supplying each calculation input. The radius connection from section 2.1 shows how this works: the field calculation reads coil.R0, and the plant connects that attribute to plasma.R. Following the chain reaches the plant's plasma-radius parameter, so the generated field calculation reads that input directly.
The pack-sizing calculation in the version shown here illustrates the other case. Its effective current density, the current per unit winding-pack area, came from the current-sizing calculation. Following that connection reaches a calculated output, so codegen adds an edge from current sizing to pack sizing. The program must calculate the density before it can use it to calculate pack dimensions.
In both cases, codegen uses the declaration identified by SysIDE together with its location in the assembled plant to find the supplying value. It follows connections through attributes until it reaches either an external input or a calculation output. Several calculations that reach the same supplier remain connected to that same value.
A missing or ambiguous supplier stops generation with a diagnostic. A broken connection cannot silently become an adjustable study parameter. Once the connections are resolved, codegen has the calculation network it needs to generate the executable program.
2.4Turning modeled mathematics into executable Python
The graph tells us which calculations are needed and how they depend on one another. Each calculation also needs executable code that turns its input numbers into output numbers. For equations written in the supported SysML expression language, codegen generates that code directly, so the author does not have to maintain a second copy of the equation in Python.
Consider the magnetic-field equation from section 2.1. SysIDE represents its arithmetic as nested operations: multiply the numerator's terms, multiply the denominator's terms, then divide. Codegen visits those operations and writes the equivalent Python expression. The equation's I_coil input, for example, becomes inputs.I_coil in Python. The generated function exploration/stellarator_e2e/generated/handwritten/mfe_magnet_field/coil_set_axis_field_impl.py returns:
return ((((inputs.mu0 * inputs.k_link) * inputs.n_coils) * inputs.I_coil)
/ (inputs.two_pi * inputs.R0))
Only the line break has changed for readability. A generated wrapper checks the inputs and packages the result so TEAx can call the calculation.
In the packageThe generated wrapper around it
Beside the implementation file, codegen writes a module wrapper for TEAx. It declares a typed input record (a pydantic model with one field per declared input: two_pi, I_coil, R0, n_coils, k_link, mu0) and a run method that validates the inputs, imports the implementation, runs it and returns the result in TEAx's result type. The method, comments included, as generated:
def run(
self, two_pi: float, I_coil: float, R0: float, n_coils: float, k_link: float, mu0: float ) -> ModuleResult[Float]:
# Validate inputs
validated_inputs = self.validate_and_fill_default(two_pi, I_coil, R0, n_coils, k_link, mu0)
# Import handwritten implementation
from stellarator_tea.handwritten.mfe_magnet_field.coil_set_axis_field_impl import (
run_coil_set_axis_field,
)
# Execute implementation - returns single value
B_axis = run_coil_set_axis_field(validated_inputs)
# Single output - return Float directly (RootModel[float])
# TEAx assigns entire return value to the one channel declared in YAML
return ModuleResult(data=Float(B_axis))
The docstrings are omitted. The wrapper is the same shape whether the implementation was translated from the SysML expression, as here, or written by hand, as in section 2.4.1.
The compiler supports a subset of SysML mathematics. The generated code does not automatically convert units, so equations and supplied values must use consistent units.
2.4.1Incorporating calculations beyond the expression translator
Some engineering calculations need numerical algorithms, external libraries, or a fast approximation to an expensive simulation. For these methods, the model declares the calculation's inputs, outputs, and plant connections. Codegen generates the Python interface, and the author supplies the computational method. TEAx can then run that method alongside the automatically translated equations. An untranslated calculation cannot run until its implementation is supplied.
The blanket surrounding the plasma uses fusion neutrons to breed tritium fuel. We want to explore how its thickness affects breeding, but running a detailed neutron-transport simulation at every design point would be expensive.
Instead, the model uses a response table from prior neutron-transport calculations models/designs/stellarator_09/breeding_response.json. A handwritten Python function exploration/stellarator_e2e/generated/handwritten/mfe_tritium_breeding/blanket_tritium_breeding_impl.py estimates the breeding response between the stored results by interpolation. That function is a surrogate: a cheaper approximation to the detailed simulation, which the plant can call at each design point.
Figure 7 shows the five stored results and the response the function returns between them. Each dot is one transport calculation; the line between two dots is the straight interpolation the function uses. The shaded band spans the stored results.
At 0.800 m, the stored result is a tritium breeding ratio of 1.198 (Li-6 1.195 plus Li-7 0.003). Between two stored thicknesses the function interpolates linearly; outside 0.60 to 1.00 m it returns the response as undefined.
models/designs/stellarator_09/breeding_response.json.The table contains results at five blanket thicknesses spanning 0.60–1.00 m, with the remaining geometry and scenario assumptions fixed. The function checks that the inputs stay within those conditions and marks the response undefined outside them.
In the codeThe response table and the interpolation
The handwritten function embeds the table from the design-owned response asset. Its five nodes, with the standard error of each transport result:
| Thickness (m) | TBR from Li-6 | TBR from Li-7 | Standard error |
|---|---|---|---|
| 0.60 | 1.08812 | 0.00333 | 0.00162 |
| 0.70 | 1.15067 | 0.00335 | 0.00156 |
| 0.80 | 1.19473 | 0.00335 | 0.00096 |
| 0.90 | 1.22760 | 0.00336 | 0.00183 |
| 1.00 | 1.24909 | 0.00336 | 0.00165 |
After checking that every input is finite, that the geometry inputs equal the table's fixed geometry, and that the thickness lies within the table, the function interpolates:
index = min(bisect.bisect_right(thicknesses, thickness) - 1, len(nodes) - 2)
left, right = nodes[index:index+2]
fraction = (thickness - left['thickness_m']) / (right['thickness_m'] - left['thickness_m'])
li6 = left['tbr_li6'] * (1-fraction) + right['tbr_li6'] * fraction
li7 = left['tbr_li7'] * (1-fraction) + right['tbr_li7'] * fraction
mean = li6 + li7
variance = ((1-fraction)*left['std_error'])**2 + (fraction*right['std_error'])**2
It returns the mean with a defined flag of 1, and also a numerical lower estimate, the mean less twice the interpolated standard error and a 0.01 interpolation allowance. That lower estimate is a numerical margin, not a physical confidence bound. When a check fails, the function returns seven zeros with the defined flag at 0; those zeros are not a prediction of zero breeding. The table's provenance (OpenMC 0.15.2, the fixed geometry and material scenario) is recorded in the response asset. The file declares AUTO_IMPLEMENTED = False.
Both translated equations and custom methods expose declared inputs and outputs, so codegen can connect them into the same plant calculation network. The implementation note archive/write-up/sysml-codegen-assets/model-evidence.md, "Implementation preservation", covers how Python bodies are retained when regenerating a package.
In the noteHow Python bodies survive regeneration
Both translated and custom Python implementations live under handwritten/ in the generated package. When a package is regenerated, a preservation option can keep an existing body whose interface is unchanged, even if the SysML equation changed; a fresh generation updates the translated equations, and a retained implementation has to be checked against the revised model.
For a calculation whose expression cannot be translated, codegen writes the function interface with a placeholder body that raises NotImplementedError until the author supplies the method. The declared inputs and outputs already place that calculation in the plant's network, and broken plant wiring still stops generation.
2.5Selecting a different component model
Changing numerical inputs lets us explore a component model’s existing behavior. To explore a different component or add more detailed behavior, we need to select a different definition for that component in the plant.
The blanket provides an example of adding detail. Its base model supplies a fixed breeding estimate, so varying blanket thickness does not change the breeding result. We want to replace that assumption with the thickness-dependent calculation introduced in section 2.4.
The stellarator design inherits a common plant assembly that already includes a blanket and its connections. The more detailed definition extends the base blanket, and the stellarator selects it for the existing blanket usage. Other parts of the plant can continue reading the breeding result from that same component.
The three declarations below express those choices. They come from separate source locations; the bodies are omitted:
part def 'Blanket' :> 'CAS22 Power Core' {
attribute tbr : Real default 1.0;
// ...
}
The kind both other declarations refer to. Its breeding ratio is a held value.
// Specialized component definition
part def 'Transport Calculated Blanket' :> 'Blanket' {
// ...
}
The specialized definition extends that base with :> and adds the breeding calculation inside its omitted body.
// Generic plant: the inherited component usage
part blanket : 'Blanket' {
// ...
}
The common plant assembly declares a blanket component using the base definition.
// Stellarator design: select the specialization
part :>> blanket : 'Transport Calculated Blanket' {
// ...
}
The stellarator design redefines the inherited blanket usage with :>>, selecting the more detailed definition for that component.
In the modelWhat the specialized body adds
The base 'Blanket' holds its breeding ratio as a value, attribute tbr : Real default 1.0;. The specialization owns the breeding calculation and redefines that same attribute to read the calculated mean, so every other part of the plant that reads tbr is unchanged. Members omitted where marked:
part def 'Transport Calculated Blanket' :> 'Blanket' {
attribute breeding_R : Real;
attribute breeding_a : Real;
// ... five more geometry attributes the plant binds
calc breeding : 'Blanket Tritium Breeding' {
in blanket_t_in = blanket_t;
in R_in = breeding_R;
in a_in = breeding_a;
// ... the remaining geometry inputs
}
:>> tbr = breeding.tbr_mean;
attribute tbr_lower : Real = breeding.tbr_lower;
attribute breeding_defined : Real = breeding.defined_flag;
// ...
}
The stellarator's redefinition then binds the geometry the calculation needs from its own parts, for example :>> breeding_R = stellaris.plasma.R; and :>> breeding_a = stellaris.plasma.a;.
After this model change, we regenerate the program. Codegen includes the selected blanket's calculation and connects it to the plant, so the breeding result now comes from the surrogate. A study can vary blanket thickness and evaluate its effect on breeding alongside the other plant calculations.
2.6Putting together the Python computational model
We now have the calculation network and its Python functions, including any custom implementations. To run the plant, we need instructions for passing values between those functions and a way to supply the external inputs. The generated package captures both: a TEAx specification describes the connections, and input files hold the externally supplied parameters.
2.6.1Wiring the functions into an executable calculation network
A Python function for winding-pack size knows how to calculate a size from current density and ampere-turns. It does not decide which plant values to use. Codegen writes those choices into a YAML specification using the connections resolved in section 2.3. TEAx reads that specification to assemble and run the program.
Here is the complete pack-sizing entry from the stellarator pipeline:
stellarator_09__stellaris__magnet__wp_sizing:
module_type: mfe_magnet_field.Winding_Pack_SizingModule
inputs:
j_wp: float stellarator_09__stellaris__magnet__current_sizing__selected_effective_density
I_coil: float stellarator_plant_params.stellarator_09__stellaris__magnet__coil__I_coil
outputs:
root: RootModel[float] stellarator_09__stellaris__magnet__wp_sizing__wp_side
The module_type selects the winding-pack sizing calculation. Its j_wp input receives the density calculated by current sizing, while I_coil receives the supplied coil ampere-turns. The output entry names the calculated pack side so other calculations can read it. The long names identify each value's location in the plant. Generated pipeline: exploration/stellarator_e2e/studies/20260917-pre-reveal-feasible-neighborhood/preparation/package-pipelines/pipeline.yaml.
Figure 8 draws the same entry as connections. Read it left to right: the two inputs and where each comes from, the module in the middle, and the two calculations that read its output, taken from the bindings behind Figure 5.
in j_wp effective current density
in I_coil coil ampere-turns
module_type mfe_magnet_field.Winding_Pack_SizingModule, whose implementation is the handwritten square-root sizing function
out wp_side winding-pack side length
archive/write-up/sysml-codegen-assets/graph-evidence.md.In the packageThe pipeline around this entry
The pipeline frozen with the 17 September study holds 113 entries: an entry point that loads the input files, 90 calculations, 20 constraint checks, one module that gathers the check results into a report, and an exit point. Across the calculations and checks, 423 inputs are bound to keys in the input files and 216 to outputs of other calculations. The field calculation's entry shows both kinds side by side; its constants come from a library parameter file and its four plant values from the plant file:
stellarator_09__stellaris__magnet__field_calc:
module_type: mfe_magnet_field.Coil_Set_Axis_FieldModule
inputs:
two_pi: float mfe_magnet_field_params.stellarator_09__stellaris__magnet__field_calc__two_pi
I_coil: float stellarator_plant_params.stellarator_09__stellaris__magnet__coil__I_coil
R0: float stellarator_plant_params.stellarator_09__stellaris__plasma__R
n_coils: float stellarator_plant_params.stellarator_09__stellaris__magnet__coil__n_coils
k_link: float stellarator_plant_params.stellarator_09__stellaris__magnet__coil__k_link
mu0: float mfe_magnet_field_params.stellarator_09__stellaris__magnet__field_calc__mu0
outputs:
root: RootModel[float] stellarator_09__stellaris__magnet__field_calc__B_axis
The package has grown since: the current generated pipeline holds 269 entries. After the 20 September model changes, the coil ampere-turns are calculated from the supplied turns and current per turn, so this field entry now reads them from that calculation. The pack-sizing entry above no longer exists, because the pack side is now a supplied input. The excerpts on this page are the 17 September package, the one the studies in section 2.7 ran.
TEAx uses these connections to run each calculation after the results it needs are available. It gathers the inputs, calls the Python function, and makes the outputs available to the next calculations. In the 17 September package, it computed field before current-based sizing, sizing before pack dimensions, and dimensions before the fit check.
2.6.2Exposing the free input parameters
The connected calculations still need starting values. In our example, the program calculates magnetic field, but the 17 September study supplied radius and coil ampere-turns; the current model calculates the ampere-turns from the supplied turns and current per turn. These externally supplied values form the program's input interface. Changing them lets us evaluate a new design point without regenerating the program.
Codegen identifies these externally supplied values while resolving the graph and exposes them as public inputs. The model's authored values provide their initial settings. Values supplied by another calculation remain connected to that calculation's output.
Here is an exact subset of the stellarator's generated plant-input JSON:
{
"stellarator_09__stellaris__plasma__R": 12.7,
"stellarator_09__stellaris__magnet__coil__I_coil": 15400000.0,
"stellarator_09__stellaris__magnet__coil__n_coils": 48.0,
"stellarator_09__stellaris__magnet__coil__k_link": 0.7731331164622419
}
These settings supply the plasma radius, coil ampere-turns, coil count, and linkage factor. Changing the radius changes the value received by every calculation connected to that plant attribute. The magnetic field remains a calculated result. Full generated input file: exploration/stellarator_e2e/studies/20260917-pre-reveal-feasible-neighborhood/preparation/package-inputs/stellarator_plant_params.json.
In the packageThe input files
The 17 September package has ten input files with 313 keys between them. Nine hold library defaults grouped by the model source that declares them (for example mfe_magnet_field_params.json, two keys: mu0 and two_pi); the plant file holds the 246 values the stellarator design supplies. Every key is flat and carries the component path, as the four above do.
In this package, eight calculations read the plasma radius, plasma__R, directly: the plasma geometry, radial build, plasma sustainment and divertor heat calculations, and in the magnet the axis field, peak field, stored energy and magnet cost. Seven read the coil ampere-turns, coil__I_coil: the axis field, stored energy, current sizing, pack sizing, winding-pack cost, winding procurement and pack stress calculations. A study that changes either value changes what all of them receive.
The long JSON keys identify each input's location in the plant. File-based runs load these settings from JSON; studies can supply the values directly in memory. The input-generation note archive/write-up/sysml-codegen-assets/model-evidence.md, "Input-generation details", covers file grouping, defaults, and inputs that need values before evaluation.
In the noteInput generation
When a calculation reads an externally supplied attribute, such as the plasma radius, codegen exposes that attribute as a public input, and every calculation that reads it shares the one input. A value supplied by a calculation stays connected to that calculation's output.
Authored values provide the initial settings. A literal bound directly to a calculation input is exposed with that literal as its initial value. An input left unbound becomes a public input with its modeled default, or a null placeholder that must be filled before evaluation. A missing or ambiguous reference stops generation; it does not become a free parameter.
Inputs are grouped into files, normally by their declaring model source. Keys within a file are flat and carry component paths rather than reproducing the nested component tree. Schemas describe types and defaults. File-based runs load JSON; studies can supply typed values in memory.
A study chooses which inputs to vary and which to hold fixed. In the radius–current study, radius and ampere-turns vary while coil count and linkage factor remain fixed.
2.7Running a study: compare feasibility and cost across design points
We can now use the generated program to ask an engineering question: which combinations of geometry and operating parameters meet the modeled limits, and what do they cost?
Figure 9 follows one study from left to right: the inputs it chooses, the program it loads once, the design points it evaluates, and the record it keeps. Below them are the study's two uses on this page, each a section of its own.
-
Chosen inputs
Varied or held fixed
It chooses which inputs to vary, which assumptions to hold fixed, and which results to compare. In the radius–current study, we vary plant radius and coil ampere-turns.
-
The generated program
Loaded once
TEAx loads the generated program once, then evaluates each input combination independently.
-
Each design point
Evaluated independently
For each combination, the program calculates electricity cost and reports which engineering checks pass or fail.
-
The record
Traced to the design that produced it
The study records the inputs, calculated outputs, constraint results, and model version so each result can be traced to the design that produced it.
Two uses on this page
Compare a few proposed design changes
Three cases from the 15 September joint magnet-sizing study.
Map cost and feasibility across many input combinations
256 design points from the 17 September neighborhood study.
The current study runner evaluates grids and prepared lists of candidates. An external optimizer can call the same evaluator; the study-runner note archive/write-up/sysml-codegen-assets/study-evidence.md, "Study runner capabilities", distinguishes that option from the built-in study strategies.
In the recordThe study record and the study runner
The 17 September study is a directory under exploration/stellarator_e2e/studies/20260917-pre-reveal-feasible-neighborhood/. Its parts, in the order a reader would open them:
record.md, the question, the axes, the verification scope and the limitations;report.md, the counts, held assumptions and cost limits;synthesis.md, the reading.snapshot.json, the model version: the package path, its repository commitd53d6ec5…and the executable fingerprintf52729e6…, together with the input files' hashes.preparation/, the frozen package (package-pipelines/pipeline.yaml,package-inputs/, schemas and contracts),resolved-defaults.jsonfor every held value, and the proposals that were evaluated.results/native-cases.json, each case's inputs, output channels and the verdict of every constraint;results/map-data.csvandresults/analysis.json, the plotted coordinates and signed margins.
The study runner evaluates grids and prepared candidate lists. An external optimizer can call the same prepared evaluator, but adaptive optimization inside the runner is not implemented. The results on this page are retained candidate evaluations, not an adaptive search.
The following results illustrate two uses of a study: comparing a few proposed design changes, and mapping cost and feasibility across many input combinations. They come from two dated model snapshots. Their electricity costs reflect the assumptions and incomplete cost scope at that time; later breeding, cooling, and facility work changed the checks and cost estimates.
2.7.1See how a local change affects the rest of the design
With the tape performance we assume, the published Stellaris winding cannot carry its current: its tape would run at about 1.7 times its estimated critical current, where we allow 0.8. The winding also does not fit the radial space our model allocates for it. The 15 September joint magnet-sizing study exploration/stellarator_e2e/studies/20260915-joint-magnet-sizing/report.md evaluated two changes intended to fix this: enough tape to carry the current, then more space for the larger winding.
In that study, the model chose the amount of tape from the current requirement. We removed that option on 20 September, because a calculation that sizes equipment takes the choice away from the designer (Part 4b, section 2). The model now evaluates the winding a designer supplies, both its size and the space allocated for it, and reports any shortfall instead of resizing it. The three cases below are the September study's results: the original design, a design with enough tape to carry the current, and a design with more space for that tape.
Read Figure 10 left to right. Each card is one evaluated case: its electricity cost, the conductor it needs, and four of its check results. Between the cards is the one change made, with what it fixed and what it cost or broke. The small drawing on each card uses the vocabulary of Figure 4: the clear cavity with the required envelope inside it, or spilling out of it.
Tape 36.6 million m · priced magnet subtotal $1.71 billion · peak field 24.90 T
- Conductor currentFAILreference conductor cannot carry the required current
- Pack / casing fitFAILradial margin −0.120 m, transverse +0.021 m
- Peak field limitPASS24.90 T against 24.9 T
- Divertor heat loadFAIL
- Fixes the current check.
- Tape ×2.1; magnet subtotal +$0.84 billion; cost +$18.45/MWh.
- The required envelope grows from 0.370 × 0.379 to 0.535 × 0.548 m, so it fits even less.
Tape 77.9 million m · priced magnet subtotal $2.55 billion · peak field 24.90 T
- Conductor currentPASSfixedsized to carry it
- Pack / casing fitFAILworseradial margin −0.285 m, transverse −0.148 m
- Peak field limitPASS24.90 T against 24.9 T
- Divertor heat loadFAIL
- Fixes the fit: margins +0.012 and +0.049 m.
- The changed radial geometry raises peak field 24.90 → 25.30 T, above the 24.9 T limit.
- Tape +6%; cost +$3.55/MWh.
Tape 82.4 million m · priced magnet subtotal $2.70 billion · peak field 25.30 T
- Conductor currentPASS
- Pack / casing fitPASSfixedradial margin +0.012 m, transverse +0.049 m
- Peak field limitFAILnew25.30 T against 24.9 T
- Divertor heat loadFAIL
archive/write-up/sysml-codegen-assets/magnet-sizing-comparison.csv.In the recordThe three recorded cases
Three matched cases from the 15 September study's own executable (package commit a8589d6b…), candidates c0000, c0005 and c0007, report aliases reference, reference-sized and reference-accommodated. All hold major radius 12.7 m, minor radius 1.3 m and 15.4 million ampere-turns; the field ceiling stays 24.9 T. The casing wall is 0.025 m on each radial side, so the clear radial cavity is the radial allocation less 0.05 m.
| Quantity | Original | Current-sized | Current-sized, enlarged allocation |
|---|---|---|---|
| Radial allocation / transverse cavity (m) | 0.30 / 0.40 | 0.30 / 0.40 | 0.60 / 0.60 |
| Required envelope, radial × transverse (m) | 0.370 × 0.379 | 0.535 × 0.548 | 0.538 × 0.551 |
| Fit margins, radial / transverse (m) | −0.120 / +0.021 | −0.285 / −0.148 | +0.012 / +0.049 |
| Peak conductor field (T), limit 24.9 | 24.900 | 24.900 | 25.297 |
| Tape length (million m) | 36.579 | 77.884 | 82.372 |
| Priced magnet subtotal ($ billion) | 1.707 | 2.552 | 2.695 |
| LCOE ($/MWh) | 144.747 | 163.194 | 166.744 |
| Checks violated | divertor_heat_ok, reference_conductor_current_ok, wp_fit_ok | divertor_heat_ok, wp_fit_ok | divertor_heat_ok, peak_field_ok |
The study's report records the reference divertor load as 10.52 MW/m² against a 10.0 MW/m² limit, and notes that its priced subtotals carry mixed price bases and unresolved manufacturing scope, so they are conditional estimates, not procurement quotations.
In the study, sizing the conductor supply to carry the required current fixed the current-capacity problem but increased pack size and cost. Enlarging the allocated space fixed the fit problem, but the changed radial geometry raised peak conductor field to 25.30 T, above the retained 24.9 T limit.
The 24.9 T limit is Stellaris's published peak field, held as a ceiling. The model is calibrated so the published design sits exactly on it, so any rise in field fails the check. The field calculation also responds only to coil position and leaves out a term that would lower the field for a larger winding, so the model does not establish which way the real field moves.
Evaluating the connected model shows why fixing one limit does not necessarily produce an acceptable design. Each proposed change must be checked against its effects on the rest of the plant.
2.7.2Map feasibility and cost together
The 17 September neighborhood study exploration/stellarator_e2e/studies/20260917-pre-reveal-feasible-neighborhood/report.md provides the broader radius–current sweep introduced earlier. In the results shown below, major radius and coil ampere-turns vary while the other inputs remain fixed. Each mark represents one evaluated design point.
Each mark is one evaluated case at one major radius and one coil ampere-turn value; the same 256 cases appear in both panels. The record behind each mark holds its inputs, its verdict, the checks it failed, its margins and its conditional cost. The 44 passing cases are circles, the 210 failing cases crosses, and the two invalid power accounts diamonds.
archive/write-up/sysml-codegen-assets/feasibility-cost-map.csv.In the recordHeld inputs for the map
Only radius and coil ampere-turns change among the 256 cases; the extraction that produced the plot data checked this against every case's native input dictionary. The held values:
| Held input | Value |
|---|---|
| Minor radius | 1.491 m |
| Peak ion temperature | 14.036 keV |
| Peak electron density | 4.892 × 10²⁰ m⁻³ |
| Density and temperature profile exponents | 0.35 and 1.2 |
| Representative helium loops | 18 |
| Radial allocation / transverse cavity | 0.65 m / 0.65 m |
| Conductor inventory multiplier (current-driven sizing on) | 1.01 |
The remaining defaults are in the study's preparation/resolved-defaults.json. The map is a 256-case fixed-configuration slice of a study with 334 selected cases, of which 103 pass; the two cohorts must not be interchanged. Passing means the twenty screens implemented in that executable passed with a valid power account. At that snapshot the held achieved breeding ratio was 1.074 against a calculated requirement of 1.190, the anchor field exceeded the roughly 24 T extent of the cited conductor measurements, and installed cooling equipment and complete manufacturing were not fully priced.
The passing samples form a narrow band. At fixed ampere-turns, reducing radius increases the modeled field and can violate its limit. Increasing radius reduces field and changes the plasma power balance, eventually running into divertor heat or installed-heating limits. The cost plot explains why minimizing price alone would not answer the design question: low calculated costs also occur at points that fail engineering checks.
This pattern is conditional on the other inputs held fixed for the sweep. The study evidence archive/write-up/sysml-codegen-assets/study-evidence.md, "Held inputs for the radius–current map", summarizes those settings and links to the complete record.
These studies turn design choices into engineering feedback. We can see what a proposed change improves, which limits it violates, and what to investigate next. When that investigation calls for a different component model or additional physics, we revise the model, regenerate the program, and run the next study.
3Sources and figure data
- Original agentic-mbse post: motivation and the modeling workflow.
- Codegen pipeline and TEAx evaluation and studies: execution architecture.
- Stellarator code excerpts and source hashes
archive/write-up/sysml-codegen-assets/model-evidence.md: field, winding fit, handwritten calculations, and blanket specialization. - Graph extraction
archive/write-up/sysml-codegen-assets/graph-evidence.md: exact generated connections and comparison against both frozen study pipelines. - Study extraction
archive/write-up/sysml-codegen-assets/study-evidence.md: selected cases, fixed assumptions, data joins, source hashes, and later-model qualifications. - Figure sources and regeneration instructions
archive/write-up/sysml-codegen-assets/README.md: PNG and SVG files, plotting code, and retained data.