From cc2945ac31559c54a2d6b9abd14468ca6ebb30a4 Mon Sep 17 00:00:00 2001 From: Timothy Nunn Date: Wed, 17 Jun 2026 17:01:36 +0100 Subject: [PATCH 1/7] Update docs for adding variables, inputs, scans, FoMs, and iteration variables --- documentation/source/development/add-vars.md | 190 ++++++++++--------- 1 file changed, 100 insertions(+), 90 deletions(-) diff --git a/documentation/source/development/add-vars.md b/documentation/source/development/add-vars.md index f6cbd1a625..95e12ce555 100644 --- a/documentation/source/development/add-vars.md +++ b/documentation/source/development/add-vars.md @@ -1,142 +1,152 @@ -# Guide for adding Variables & Constraints +# Guide for adding variables and constraints -Specific instructions must be followed to add an input, iteration variable, -optimisation figure of merit and constraints to the `PROCESS` code. +A guide for how to add new inputs, constraints, figures of merit, and scan variables. - **At all times the [`PROCESS` style guide](../development/standards.md) must be used.** +**At all times ensure new variables and functions adhere to the [`PROCESS` style guide](../development/standards.md).** -!!! note - As the code is quickly converging towards a wholly Python codebase the respective files may change in type from `.f90` to `.py`. +All of these features rely on 'variables' which belong to a 'data structure'. All of the data structures can be found in `process/data_structure`. ------------------ +In general, a variable within a data structure could act as an: +- Input variable: is specified by the user in an `IN.DAT`/has a default value and is not changed once PROCESS is running. +- Iteration variable: is modified by the solver to try and optimise for some figure of merit. +- Scan variable: is sequentially modified by the `Scan` class to some `IN.DAT`-defined values. +- Intermediate variable: is calculated within a model and then used within other models. +- Output variable: is calculated within a model and then written out the `MFILE.DAT`. + +It is advised that a variable is either used to define a particular PROCESS run (input variable, iteration variable, or scan variable) or mutated within a PROCESS run (output variable or intermediate variable). Mixing the two classes of variable (e.g. having a variable that can be input but is also mutated within a model) will lead to confusing, dangerous, and incorrect results. -## Add an input -To add a `PROCESS` input, please follow below: +----------------- -1. Choose the most relevant module `XX` and add the variable in the `XX_variables` defined in `XX_variables.f90`. - -2. Add a description of the input variable below the declaration, using the FORD formatting described in the standards section specifying the units. - -3. Specify a sensible default value in the `init_XX_variables()` function within the corresponding model `.py` main file - -4. Add the parameter to the `INPUT_VARIABLES` dictionary in `input.py`. +## Add a new variable +You may need to add a variable to PROCESS when changing or creating models. In most cases, you will want to add your variable to an existing data structure. Creating an entierly new data structure is beyond the scope of this guide, so please seek support from the PROCESS maintainers. -Here is an example of the code to add: - +For example, if you are adding a new variable that relates to the blanket model, you would add the variable to `process/data_structure/blanket_variables.py` as part of the `BlanketData` dataclass. + +```python +@dataclass(slots=True) +class BlanketData: + <... existing variables ...> -Variable definition example in `tfcoil_variables.f90`: -```fortran - real(dp) :: rho_tf_joints - !! TF joints surfacic resistivity [ohm.m] - !! Feldmetal joints assumed. + my_new_blanket_variable: float = 0.0 + """my variable description""" ``` -Variable initialization example in `tf_coil.py`: +This variable could then be used within a model + ```python - def init_tfcoil_variables(): - ... - tfv.rho_tf_joints = 2.5e-10 +self.data.blanket.my_new_blanket_variable = 1.0 +... +another_variable = self.data.blanket.my_new_blanket_variable / 2.0 ``` -Code example in the `input.py` file: +## Add a new input +Adding an input in PROCESS means that some variable in a data structure can be set from the `IN.DAT`. Inputs are defined in the `process/core/input.py` file in the `INPUT_VARIABLES` dictionary. Adding a new entry to this dictionary will create a new input. +Continuing with the example from the previous section: ```python - INPUT_VARIABLES = { +INPUT_VARIABLES = { ... - "rho_tf_joints": InputVariable("tfcoil", float, range=(0.0, 0.01)), + "my_new_blanket_variable": InputVariable("blanket", str), +} ``` ------------------ +You would replace `"blanket"` with the name of the data structure your specific variable belongs to (found by looking at `DataStructure` in `process/core/model.py`). -## Add an iteration variable +Now, in the `IN.DAT`, you could set `my_new_blanket_variable` by writing: -To add a `PROCESS` iteration variable please follow the steps below, in addition to the instructions for adding an input variable: +``` +my_new_blanket_variable = 1.0 +``` +----------------- -1. The parameter `IPNVARS` in module `numerics` of `numerics.f90` will normally be greater than the actual number of iteration variables, and does not need to be changed. -2. Append a new iteration number key to the end of the `ITERATION_VARIABLES` dictionary in `iteration_variables.py`. The associated variable is the corresponding key value. -3. Set the variable origin file and then the associated lower and upper bounds -4. Update the `lablxc` description in `numerics.f90`. - -It should be noted that iteration variables must not be reset elsewhere in the -code. That is, they may only be assigned new values when originally -initialised (in the relevant module, or in the input file if required). -Otherwise, the numerical procedure cannot adjust the value as it requires, and -the program will fail. +## Add an iteration variable + +Adding an iteration variable allows the PROCESS solver to change the variable as part of the optimisation/solving loop. Iteration variables are defined in `process/core/solver/iteration_variables.py` in the `ITERATION_VARIABLES` dictionary. You would add a new entry to this dictionary to create a new iteration variable: -Here is a code snippet showing how `rmajor` is defined in `iteration_variables.py` ```python ITERATION_VARIABLES = { - ... - 3: IterationVariable("rmajor", "physics", 0.1, 50.00), + 123: IterationVariable("my_new_blanket_variable", "blanket", 0.1, 1.0), +} +``` + +In this example: +- `123` is the identifier of the iteration variable, and must be unique. +- `"blanket"` is the data structure the variable will be set on. +- `0.1` is the default lower bound of the variable. +- `1.0` is the default upper bound of the variable. + +You will often want to add a variable as an input if it is an iteration variable. That way, you can specify the initial value of the iteration variable. + +The iteration variable can be enabled in the `IN.DAT` by: +``` +ixc = 123 + +my_new_blanket_variable = 0.5 * initial value (optional) ``` ----------------- ## Add a figure of merit -New figures of merit are added to `PROCESS` in the following way: +A figure of merit is the scalar that the optimiser (e.g. VMCON) will try and minimise or maximise. The figures of merit are specified in `process/core/solver/objectives.py` in the `objective_function` function. + +To add a new figure of merit, first create a new entry in the `FiguresOfMerit` enum in `process/data_structure/numerics.py`: -1. Increment the parameter `IPNFOMS` in module `numerics` in source file `numerics.py` to accommodate the new figure of merit. - -2. Assign the new integer value and description string of the new figure of merit to the `FiguresOfMerit` enumerator in `numerics.py`. - -3. Add the new figure of merit equation to `objective_function()` in `objectives.py`, following the method used in the existing examples. The value of figure of merit case should be of order unity, so select a reasonable scaling factor if necessary. - -An example can be found below: +```python +class FiguresOfMerit(IntEnum): + ... + BLANKET_FIGURE_MERIT = (20, "my FOM description") +``` +Here `20` will be the identifier of the figure of merit, and must be unique. +Next, increment `IPNFOMS` in `process/data_structure/numerics.py`. +Finally, add the equation to `process/core/solver/objectives.py`: ```python -objective_function(): - ... - try: - figure_of_merit = FiguresOfMerit(abs(minmax)) - ... - if figure_of_merit == FiguresOfMerit.MAJOR_RADIUS: - objective_metric = 0.2 * data.physics.rmajor +elif figure_of_merit == FiguresOfMerit.BLANKET_FIGURE_MERIT: + objective_metric = data.blanket.my_new_blanket_variable / 10.0 ``` ------------ +Here the `10.0` is optional, but highlights that you will want to scale the figure of merit to be of order unity. -## Add a scan variable +The figure of merit can be selected in the `IN.DAT`: +``` +minmax = 20 +``` +Remember, setting `minmax = -20` would minimise instead of maximise our new variable. -After following the instruction to add an input variable, you can make the variable a scan variable by following these steps: +----------- -1. Increment the parameter `IPNSCNV` defined in `scan_variables.py` in the data_structure directory, to accommodate the new scanning variable. The incremented value will identify your scan variable. - -2. Add a short description of the new scanning variable in the `nsweep` comment in `scan_variables.py`, alongside its identification number. - -3. Update the `ScanVariables` enum in the `scan.py` file by adding a new case statement connecting the variable to the scan integer switch, the variable name and a short description. - -4. Add a comment in the corresponding variable file in the data_structure directory, eg, `data_structure/[XX]_variables.py`, to add the variable description indicating the scan switch number. - +## Add a scan variable -`nsweep` comment example: -```fortran +After following the instruction to add an input variable, you can make then create a scan variable. - integer :: nsweep = 1 - !! nsweep /1/ : switch denoting quantity to scan: +First, add the variable to the `ScanVariables` enum in `process/core/scan.py`. +```python +class ScanVariables(Enum): + ... + blanket_scan_variable = ScanVariable( + "my_new_blanket_variable", "A blanket variable", 82 + ) ``` +Here, `82` is the identifier of the scan variable and must be unique. -`SCAN_VARIABLES` case example: +Next, increment the parameter `IPNSCNV` in `process/data_structure/scan_variables.py` and be sure to add a description of the scan variable in the docstring of the `nsweep` variable. +Finally, in `process/core/scan.py`, add the scan variable to the `Scan.scan_select` method. ```python - class ScanVariables(Enum): - aspect: ScanVariable("aspect", "Aspect_ratio", 1), - pflux_div_heat_load_max_mw: ScanVariable("pflux_div_heat_load_max_mw", "Div_heat_limit_(MW/m2)", 2), - ... - Bc2_0K: ScanVariable("Bc2(0K)", "GL_NbTi Bc2(0K)", 54), - dr_shld_inboard : ScanVariable("dr_shld_inboard", "Inboard neutronic shield", 55), +match nwp: + ... + case 82: + self.data.tfcoil.my_new_blanket_variable = swp[iscn - 1] ``` +Please see the scan documentation for how to setup a scan `IN.DAT` + --------------- ## Add a constraint equation @@ -153,7 +163,7 @@ The arguments to the `register_constraint` function are: - Name (again, currently an integer) - Unit (for output reporting purposes) -- Symbol (e.g. =, >=, <=. Again, for output reporting purposes) +- Symbol (e.g. `=`, `>=`, `<=`. Again, for output reporting purposes) `my_constraint_function` should be named appropriately and return a `ConstraintResult` which contains the: @@ -168,5 +178,5 @@ The recommended way to do this is using one of the functions `geq`, `leq`, or `e ```python @ConstraintManager.register_constraint(1234, "m", "=") def my_constraint_function(constraint_registration): - return geq(value, bound, constraint_registration) + return geq(value, bound, constraint_registration) ``` From a6ebb51299bf6a1d4e6efa408ca5c677c0451546 Mon Sep 17 00:00:00 2001 From: Timothy Nunn Date: Wed, 17 Jun 2026 17:16:40 +0100 Subject: [PATCH 2/7] Remove need to increment numerics IP variables --- documentation/source/development/add-vars.md | 2 -- process/data_structure/numerics.py | 9 ++++++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/documentation/source/development/add-vars.md b/documentation/source/development/add-vars.md index 95e12ce555..ff949abd24 100644 --- a/documentation/source/development/add-vars.md +++ b/documentation/source/development/add-vars.md @@ -103,8 +103,6 @@ class FiguresOfMerit(IntEnum): ``` Here `20` will be the identifier of the figure of merit, and must be unique. -Next, increment `IPNFOMS` in `process/data_structure/numerics.py`. - Finally, add the equation to `process/core/solver/objectives.py`: ```python elif figure_of_merit == FiguresOfMerit.BLANKET_FIGURE_MERIT: diff --git a/process/data_structure/numerics.py b/process/data_structure/numerics.py index 427467d30e..9adff8cd6d 100644 --- a/process/data_structure/numerics.py +++ b/process/data_structure/numerics.py @@ -4,6 +4,9 @@ import numpy as np +from process.core.solver.constraints import ConstraintManager +from process.core.solver.iteration_variables import ITERATION_VARIABLES + class PROCESSRunMode(IntEnum): """Enumeration of the available PROCESS run modes, which determine the behaviour @@ -100,13 +103,13 @@ def description(self): return self._description_ -IPNVARS = 177 +IPNVARS = max(ITERATION_VARIABLES.keys()) """total number of variables available for iteration""" -IPEQNS = 92 +IPEQNS = ConstraintManager.num_constraints() """number of constraint equations available""" -IPNFOMS = 19 +IPNFOMS = len(FiguresOfMerit) """number of available figures of merit""" From 25439b3591fbaaa07589e0f48639537d1c0d362b Mon Sep 17 00:00:00 2001 From: Timothy Nunn Date: Wed, 17 Jun 2026 17:27:39 +0100 Subject: [PATCH 3/7] Correct bullet list formatting --- documentation/source/development/add-vars.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/documentation/source/development/add-vars.md b/documentation/source/development/add-vars.md index ff949abd24..c02413428b 100644 --- a/documentation/source/development/add-vars.md +++ b/documentation/source/development/add-vars.md @@ -8,6 +8,7 @@ A guide for how to add new inputs, constraints, figures of merit, and scan varia All of these features rely on 'variables' which belong to a 'data structure'. All of the data structures can be found in `process/data_structure`. In general, a variable within a data structure could act as an: + - Input variable: is specified by the user in an `IN.DAT`/has a default value and is not changed once PROCESS is running. - Iteration variable: is modified by the solver to try and optimise for some figure of merit. - Scan variable: is sequentially modified by the `Scan` class to some `IN.DAT`-defined values. @@ -74,6 +75,7 @@ ITERATION_VARIABLES = { ``` In this example: + - `123` is the identifier of the iteration variable, and must be unique. - `"blanket"` is the data structure the variable will be set on. - `0.1` is the default lower bound of the variable. From d1dd3706f277a55e2f2a1d8037fac3aed46c77af Mon Sep 17 00:00:00 2001 From: Timothy Nunn Date: Wed, 17 Jun 2026 17:28:45 +0100 Subject: [PATCH 4/7] Ensure ipeqns is maximum constraint ID --- process/core/solver/constraints.py | 4 ++++ process/data_structure/numerics.py | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/process/core/solver/constraints.py b/process/core/solver/constraints.py index 4f33c2a930..40f0ea7e82 100644 --- a/process/core/solver/constraints.py +++ b/process/core/solver/constraints.py @@ -77,6 +77,10 @@ def num_constraints(cls): """Return the number of constraints currently in the registry""" return len(cls._constraint_registry) + @classmethod + def maximum_constraint_id(cls): + return max(c for c in cls._constraint_registry) + @classmethod def register_constraint( cls, name: Hashable, units: str, symbol: ConstraintSymbolType diff --git a/process/data_structure/numerics.py b/process/data_structure/numerics.py index 9adff8cd6d..aea124b3a3 100644 --- a/process/data_structure/numerics.py +++ b/process/data_structure/numerics.py @@ -106,7 +106,7 @@ def description(self): IPNVARS = max(ITERATION_VARIABLES.keys()) """total number of variables available for iteration""" -IPEQNS = ConstraintManager.num_constraints() +IPEQNS = ConstraintManager.maximum_constraint_id() """number of constraint equations available""" IPNFOMS = len(FiguresOfMerit) From 5f0c0e2c7f415d6a0ea2ff5e7421578501b42501 Mon Sep 17 00:00:00 2001 From: Timothy Nunn Date: Thu, 18 Jun 2026 16:14:16 +0100 Subject: [PATCH 5/7] Fix cyclic import --- process/core/input.py | 4 ++-- process/core/solver/constraints.py | 4 ++-- process/core/solver/iteration_variables.py | 8 ++++++-- process/data_structure/numerics.py | 5 ++--- 4 files changed, 12 insertions(+), 9 deletions(-) diff --git a/process/core/input.py b/process/core/input.py index 644078ef83..8ff3339cf8 100644 --- a/process/core/input.py +++ b/process/core/input.py @@ -16,7 +16,7 @@ ) from process.core.solver.constraints import ConstraintManager from process.data_structure.impurity_radiation_variables import N_IMPURITIES -from process.data_structure.numerics import IPEQNS, IPNVARS +from process.data_structure.numerics import IPNVARS from process.data_structure.pfcoil_variables import N_PF_GROUPS_MAX from process.data_structure.physics_variables import N_CONFINEMENT_SCALINGS from process.data_structure.scan_variables import IPNSCNS, IPNSCNV @@ -1133,7 +1133,7 @@ def __post_init__(self): "icc": InputVariable( None, int, - range=(1, IPEQNS), + choices=ConstraintManager.constraint_ids(), additional_actions=_icc_additional_actions, set_variable=False, ), diff --git a/process/core/solver/constraints.py b/process/core/solver/constraints.py index 40f0ea7e82..862f44d4fb 100644 --- a/process/core/solver/constraints.py +++ b/process/core/solver/constraints.py @@ -78,8 +78,8 @@ def num_constraints(cls): return len(cls._constraint_registry) @classmethod - def maximum_constraint_id(cls): - return max(c for c in cls._constraint_registry) + def constraint_ids(cls): + return tuple(cls._constraint_registry.keys()) @classmethod def register_constraint( diff --git a/process/core/solver/iteration_variables.py b/process/core/solver/iteration_variables.py index 06a1e7ac34..01480c2a4d 100644 --- a/process/core/solver/iteration_variables.py +++ b/process/core/solver/iteration_variables.py @@ -1,12 +1,16 @@ +from __future__ import annotations + from copy import deepcopy from dataclasses import dataclass -from typing import Any +from typing import TYPE_CHECKING, Any from warnings import warn import numpy as np from process.core.exceptions import ProcessValueError -from process.core.model import DataStructure + +if TYPE_CHECKING: + from process.core.model import DataStructure @dataclass diff --git a/process/data_structure/numerics.py b/process/data_structure/numerics.py index aea124b3a3..fbb0eb009a 100644 --- a/process/data_structure/numerics.py +++ b/process/data_structure/numerics.py @@ -4,7 +4,6 @@ import numpy as np -from process.core.solver.constraints import ConstraintManager from process.core.solver.iteration_variables import ITERATION_VARIABLES @@ -106,8 +105,8 @@ def description(self): IPNVARS = max(ITERATION_VARIABLES.keys()) """total number of variables available for iteration""" -IPEQNS = ConstraintManager.maximum_constraint_id() -"""number of constraint equations available""" +IPEQNS = 500 +"""maximum number of constraint equations available""" IPNFOMS = len(FiguresOfMerit) """number of available figures of merit""" From 88112e6b8edccb960292e7dc6b6010d431e56eca Mon Sep 17 00:00:00 2001 From: Timothy <75321887+timothy-nunn@users.noreply.github.com> Date: Tue, 23 Jun 2026 14:14:11 +0100 Subject: [PATCH 6/7] Fix spelling/grammar errors Co-authored-by: clmould <86794332+clmould@users.noreply.github.com> Co-authored-by: Christopher Ashe <91618944+chris-ashe@users.noreply.github.com> --- documentation/source/development/add-vars.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/documentation/source/development/add-vars.md b/documentation/source/development/add-vars.md index c02413428b..83efe6cdc8 100644 --- a/documentation/source/development/add-vars.md +++ b/documentation/source/development/add-vars.md @@ -9,11 +9,11 @@ All of these features rely on 'variables' which belong to a 'data structure'. Al In general, a variable within a data structure could act as an: -- Input variable: is specified by the user in an `IN.DAT`/has a default value and is not changed once PROCESS is running. +- Input variable: is specified by the user in an `IN.DAT` and its value is not changed once PROCESS is running. - Iteration variable: is modified by the solver to try and optimise for some figure of merit. - Scan variable: is sequentially modified by the `Scan` class to some `IN.DAT`-defined values. - Intermediate variable: is calculated within a model and then used within other models. -- Output variable: is calculated within a model and then written out the `MFILE.DAT`. +- Output variable: is calculated within a model and then written out to the `MFILE.DAT`. It is advised that a variable is either used to define a particular PROCESS run (input variable, iteration variable, or scan variable) or mutated within a PROCESS run (output variable or intermediate variable). Mixing the two classes of variable (e.g. having a variable that can be input but is also mutated within a model) will lead to confusing, dangerous, and incorrect results. @@ -21,7 +21,7 @@ It is advised that a variable is either used to define a particular PROCESS run ----------------- ## Add a new variable -You may need to add a variable to PROCESS when changing or creating models. In most cases, you will want to add your variable to an existing data structure. Creating an entierly new data structure is beyond the scope of this guide, so please seek support from the PROCESS maintainers. +You may need to add a variable to PROCESS when changing or creating models. In most cases, you will want to add your variable to an existing data structure. Creating an entirely new data structure is beyond the scope of this guide, so please seek support from the PROCESS maintainers. For example, if you are adding a new variable that relates to the blanket model, you would add the variable to `process/data_structure/blanket_variables.py` as part of the `BlanketData` dataclass. @@ -49,7 +49,7 @@ Continuing with the example from the previous section: ```python INPUT_VARIABLES = { ... - "my_new_blanket_variable": InputVariable("blanket", str), + "my_new_blanket_variable": InputVariable("blanket", float), } ``` @@ -94,20 +94,20 @@ my_new_blanket_variable = 0.5 * initial value (optional) ## Add a figure of merit -A figure of merit is the scalar that the optimiser (e.g. VMCON) will try and minimise or maximise. The figures of merit are specified in `process/core/solver/objectives.py` in the `objective_function` function. +A figure of merit is the scalar that the optimiser (e.g. VMCON) will try and minimise or maximise. The figures of merit are specified in `process/core/solver/objectives.py` in the `objective_function()` function. To add a new figure of merit, first create a new entry in the `FiguresOfMerit` enum in `process/data_structure/numerics.py`: ```python class FiguresOfMerit(IntEnum): ... - BLANKET_FIGURE_MERIT = (20, "my FOM description") + BLANKET_FIGURE_OF_MERIT = (20, "my FOM description") ``` -Here `20` will be the identifier of the figure of merit, and must be unique. +Here `20` will be the identifier of the figure of merit, and **must** be unique. Finally, add the equation to `process/core/solver/objectives.py`: ```python -elif figure_of_merit == FiguresOfMerit.BLANKET_FIGURE_MERIT: +elif figure_of_merit == FiguresOfMerit.BLANKET_FIGURE_OF_MERIT: objective_metric = data.blanket.my_new_blanket_variable / 10.0 ``` @@ -123,7 +123,7 @@ Remember, setting `minmax = -20` would minimise instead of maximise our new vari ## Add a scan variable -After following the instruction to add an input variable, you can make then create a scan variable. +After following the instruction to add an input variable, you can then make a scan variable. First, add the variable to the `ScanVariables` enum in `process/core/scan.py`. ```python @@ -178,5 +178,5 @@ The recommended way to do this is using one of the functions `geq`, `leq`, or `e ```python @ConstraintManager.register_constraint(1234, "m", "=") def my_constraint_function(constraint_registration): - return geq(value, bound, constraint_registration) + return eq(value, bound, constraint_registration) ``` From a1eca61dc846cbdd9af0ae7e0d7bdc911d29861d Mon Sep 17 00:00:00 2001 From: Timothy Nunn Date: Tue, 23 Jun 2026 14:32:18 +0100 Subject: [PATCH 7/7] Clarify sections of add-vars.md --- documentation/source/development/add-vars.md | 34 ++++++++++++-------- process/core/input.py | 2 +- process/data_structure/numerics.py | 6 ++-- 3 files changed, 24 insertions(+), 18 deletions(-) diff --git a/documentation/source/development/add-vars.md b/documentation/source/development/add-vars.md index 83efe6cdc8..3cee31fcb2 100644 --- a/documentation/source/development/add-vars.md +++ b/documentation/source/development/add-vars.md @@ -9,7 +9,7 @@ All of these features rely on 'variables' which belong to a 'data structure'. Al In general, a variable within a data structure could act as an: -- Input variable: is specified by the user in an `IN.DAT` and its value is not changed once PROCESS is running. +- Input variable: is specified by the user in an `IN.DAT` and its value is not changed once PROCESS is running. - Iteration variable: is modified by the solver to try and optimise for some figure of merit. - Scan variable: is sequentially modified by the `Scan` class to some `IN.DAT`-defined values. - Intermediate variable: is calculated within a model and then used within other models. @@ -31,9 +31,11 @@ class BlanketData: <... existing variables ...> my_new_blanket_variable: float = 0.0 - """my variable description""" + """my variable description [m]""" ``` +Here, `[m]` is the units and should be replaced with the appropriate units for the variable being added. + This variable could then be used within a model ```python @@ -42,6 +44,8 @@ self.data.blanket.my_new_blanket_variable = 1.0 another_variable = self.data.blanket.my_new_blanket_variable / 2.0 ``` +----------------- + ## Add a new input Adding an input in PROCESS means that some variable in a data structure can be set from the `IN.DAT`. Inputs are defined in the `process/core/input.py` file in the `INPUT_VARIABLES` dictionary. Adding a new entry to this dictionary will create a new input. @@ -53,6 +57,8 @@ INPUT_VARIABLES = { } ``` +`InputVariable` has several additional fields to support validation and the parsing of arrays, please consult the dataclass for these additional arguments. + You would replace `"blanket"` with the name of the data structure your specific variable belongs to (found by looking at `DataStructure` in `process/core/model.py`). Now, in the `IN.DAT`, you could set `my_new_blanket_variable` by writing: @@ -81,15 +87,17 @@ In this example: - `0.1` is the default lower bound of the variable. - `1.0` is the default upper bound of the variable. -You will often want to add a variable as an input if it is an iteration variable. That way, you can specify the initial value of the iteration variable. +You will often want to [add a variable as an input](#add-a-new-input) if it is an iteration variable. That way, you can specify the initial value of the iteration variable in the `IN.DAT`. The iteration variable can be enabled in the `IN.DAT` by: ``` ixc = 123 -my_new_blanket_variable = 0.5 * initial value (optional) +my_new_blanket_variable = 0.5 ``` +Note you can omit the `my_new_blanket_variable = 0.5` line and the initial value would just be whatever the variables default value is (`0.0` in this example, this is the default we assigned [earlier](#add-a-new-variable)). + ----------------- ## Add a figure of merit @@ -108,10 +116,10 @@ Here `20` will be the identifier of the figure of merit, and **must** be unique. Finally, add the equation to `process/core/solver/objectives.py`: ```python elif figure_of_merit == FiguresOfMerit.BLANKET_FIGURE_OF_MERIT: - objective_metric = data.blanket.my_new_blanket_variable / 10.0 + objective_metric = data.blanket.my_new_blanket_variable ``` -Here the `10.0` is optional, but highlights that you will want to scale the figure of merit to be of order unity. +Note that you will want to scale the `objective_metric` such that it is on the order unity if the variable is not already. The figure of merit can be selected in the `IN.DAT`: ``` @@ -119,7 +127,7 @@ minmax = 20 ``` Remember, setting `minmax = -20` would minimise instead of maximise our new variable. ------------ +----------------- ## Add a scan variable @@ -137,7 +145,7 @@ Here, `82` is the identifier of the scan variable and must be unique. Next, increment the parameter `IPNSCNV` in `process/data_structure/scan_variables.py` and be sure to add a description of the scan variable in the docstring of the `nsweep` variable. -Finally, in `process/core/scan.py`, add the scan variable to the `Scan.scan_select` method. +Finally, in `process/core/scan.py`, add the scan variable to the `Scan.scan_select()` method. ```python match nwp: ... @@ -145,13 +153,13 @@ match nwp: self.data.tfcoil.my_new_blanket_variable = swp[iscn - 1] ``` -Please see the scan documentation for how to setup a scan `IN.DAT` +Please see the [scan documentation](../usage/running-process.md#running-process) for how to setup a scan `IN.DAT` ---------------- +----------------- ## Add a constraint equation -Constraint equations are added to *PROCESS* in the `process/core/solver/constraints.py` file. They are registered with the `ConstraintManager` whenever the application is run. Each equation has a unique name that is currently an integer, however upgrades to the input file format in the future will allow arbitrary hashable constraint names. +Constraint equations are added to PROCESS in the `process/core/solver/constraints.py` file. They are registered with the `ConstraintManager` whenever the application is run. Each equation has a unique name that is currently an integer, however upgrades to the input file format in the future will allow arbitrary hashable constraint names. A constraint is simply added by registering the constraint to the manager using a decorator. @@ -168,10 +176,10 @@ The arguments to the `register_constraint` function are: `my_constraint_function` should be named appropriately and return a `ConstraintResult` which contains the: -- Normalised residual error +- Constraint residual +- Normalised residual - Constraint value - Constraint bound -- Constraint residual The recommended way to do this is using one of the functions `geq`, `leq`, or `eq` depending on whether the constraint is desired to be $v\geq b$, $v\leq b$, or $v=b$, respectively. diff --git a/process/core/input.py b/process/core/input.py index 8ff3339cf8..ecb565f4ca 100644 --- a/process/core/input.py +++ b/process/core/input.py @@ -47,7 +47,7 @@ def _icc_additional_actions( data.numerics.n_constraints += 1 -@dataclass +@dataclass(slots=True) class InputVariable: """A variable to be parsed from the input file.""" diff --git a/process/data_structure/numerics.py b/process/data_structure/numerics.py index fbb0eb009a..104e01d829 100644 --- a/process/data_structure/numerics.py +++ b/process/data_structure/numerics.py @@ -105,11 +105,9 @@ def description(self): IPNVARS = max(ITERATION_VARIABLES.keys()) """total number of variables available for iteration""" +# Set to a really large number so that it should never need to be changed IPEQNS = 500 -"""maximum number of constraint equations available""" - -IPNFOMS = len(FiguresOfMerit) -"""number of available figures of merit""" +"""Maximum number of constraint equations available""" @dataclass(slots=True)