Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 15 additions & 19 deletions documentation/source/development/add-vars.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Guide for adding variables and constraints

A guide for how to add new inputs, constraints, figures of merit, and scan variables.
A guide for how to add new inputs, constraints, figures of merit, and scan variables.

**At all times ensure new variables and functions adhere to the [`PROCESS` style guide](../development/standards.md).**

Expand All @@ -23,7 +23,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 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.
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)
Expand Down Expand Up @@ -116,7 +116,7 @@ class FiguresOfMerit(IntEnum):
...
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
Expand All @@ -136,26 +136,22 @@ Remember, setting `minmax = -20` would minimise instead of maximise our new vari

## Add a scan variable

After following the instructions to add an input variable, you can then make a scan variable.
After following the instruction to add an input variable, you can make the variable a scan variable by following these steps:

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.
1. 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.

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.
2. 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.

Finally, in `process/core/scan.py`, add the scan variable to the `Scan.scan_select()` method.
`SCAN_VARIABLES` case example:

Add the variable to the `ScanVariables` enum in `process/core/scan.py`.
```python
match nwp:
...
case 82:
self.data.tfcoil.my_new_blanket_variable = swp[iscn - 1]
class ScanVariables(ScanVariable, Enum):
aspect = (1, SVE.P),
pflux_div_heat_load_max_mw = (2, SVE.D)
...
b_crit_upper_nbti = (54, SVE.T)
dr_shld_inboard = (55, SVE.B),
```

Please see the [scan documentation](../io/input-guide.md#input-guide) for how to set up a scan `IN.DAT`
Expand Down
100 changes: 99 additions & 1 deletion process/core/caller.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,14 @@
from tabulate import tabulate

from process.core import constants
from process.core.final import finalise
from process.core import process_output as po
from process.core.io.mfile import MFile
from process.core.process_output import OutputFileManager, ovarre
from process.core.solver import constraints
from process.core.solver.iteration_variables import set_scaled_iteration_variable
from process.core.solver.objectives import objective_function
from process.data_structure.blanket_variables import BlktModelTypes
from process.data_structure.numerics import PROCESSRunMode
from process.models.tfcoil.base import TFConductorModel
from process.models.tfcoil.superconducting import SuperconductingTFTurnType

Expand Down Expand Up @@ -394,6 +395,103 @@ def _call_models_once(self, xc: np.ndarray):
# FISPACT and LOCA model (not used)- removed


def finalise(models, data, ifail: int, non_idempotent_msg: str | None = None):
"""Routine to print out the final point in the scan.

Writes to OUT.DAT and MFILE.DAT.

Parameters
----------
models : process.main.Models
physics and engineering model objects
data: DataStructure
data structure object to provide data to evaluate the constraints
ifail : int
error flag
non_idempotent_msg : None | str, optional
warning about non-idempotent variables, defaults to None
"""
if ifail == 1:
po.oheadr(constants.NOUT, "Final Feasible Point")
else:
po.oheadr(constants.NOUT, "Final UNFEASIBLE Point")

# Output relevant to no optimisation
if data.numerics.ioptimz == PROCESSRunMode.EVALUATION:
output_evaluation(data)

# Print non-idempotence warning to OUT.DAT only
if non_idempotent_msg:
po.oheadr(constants.NOUT, "NON-IDEMPOTENT VARIABLES")
po.ocmmnt(constants.NOUT, non_idempotent_msg)

# Write output to OUT.DAT and MFILE.DAT
models.write(data, constants.NOUT)


def output_evaluation(data):
"""Write output for an evaluation run of PROCESS

Parameters
----------
data: DataStructure
data structure object to provide data to evaluate the constraints
"""
po.oheadr(constants.NOUT, "Numerics")
po.ocmmnt(constants.NOUT, "PROCESS has performed an evaluation run.")
po.oblnkl(constants.NOUT)

# Evaluate objective function
norm_objf = objective_function(data.numerics.minmax, data)
po.ovarre(constants.MFILE, "Normalised objective function", "(norm_objf)", norm_objf)

# Print the residuals of the constraint equations

residual_error, value, residual, symbols, units = constraints.constraint_eqns(
data.numerics.neqns + data.numerics.nineqns, -1, data
)

labels = [
data.numerics.lablcc[j - 1]
for j in data.numerics.icc[: data.numerics.neqns + data.numerics.nineqns]
]

def _fmt(a, units):
return [f"{c} {u}" for c, u in zip(a, units, strict=False)]

po.write(
constants.NOUT,
tabulate(
{
"Constraint Name": labels,
"Constraint Type": symbols,
"Physical constraint": _fmt(value, units),
"Constraint residual": _fmt(residual, units),
"Normalised residual": residual_error,
},
headers="keys",
),
)

for i in range(data.numerics.neqns):
constraint_id = data.numerics.icc[i]
po.ovarre(
constants.MFILE,
f"{labels[i]} normalised residue",
f"(eq_con{constraint_id:03d})",
residual_error[i],
)

for i in range(data.numerics.nineqns):
constraint_id = data.numerics.icc[data.numerics.neqns + i]
po.ovarre(
constants.MFILE,
f"{labels[data.numerics.neqns + i]}",
f"(ineq_con{constraint_id:03d})",
residual_error[data.numerics.neqns + i],
)


def write_output_files(
models: Models, data: DataStructure, ifail: int, *, runtime: float | None = None
):
Expand Down
105 changes: 0 additions & 105 deletions process/core/final.py
Original file line number Diff line number Diff line change
@@ -1,105 +0,0 @@
"""Final output at the end of a scan."""

from tabulate import tabulate

from process.core import constants
from process.core import output as op
from process.core import process_output as po
from process.core.solver import constraints
from process.core.solver.objectives import objective_function
from process.data_structure.numerics import PROCESSRunMode


def finalise(models, data, ifail: int, non_idempotent_msg: str | None = None):
"""Routine to print out the final point in the scan.

Writes to OUT.DAT and MFILE.DAT.

Parameters
----------
models : process.main.Models
physics and engineering model objects
data: DataStructure
data structure object to provide data to evaluate the constraints
ifail : int
error flag
non_idempotent_msg : None | str, optional
warning about non-idempotent variables, defaults to None
"""
if ifail == 1:
po.oheadr(constants.NOUT, "Final Feasible Point")
else:
po.oheadr(constants.NOUT, "Final UNFEASIBLE Point")

# Output relevant to no optimisation
if data.numerics.ioptimz == PROCESSRunMode.EVALUATION:
output_evaluation(data)

# Print non-idempotence warning to OUT.DAT only
if non_idempotent_msg:
po.oheadr(constants.NOUT, "NON-IDEMPOTENT VARIABLES")
po.ocmmnt(constants.NOUT, non_idempotent_msg)

# Write output to OUT.DAT and MFILE.DAT
op.write(models, data, constants.NOUT)


def output_evaluation(data):
"""Write output for an evaluation run of PROCESS

Parameters
----------
data: DataStructure
data structure object to provide data to evaluate the constraints
"""
po.oheadr(constants.NOUT, "Numerics")
po.ocmmnt(constants.NOUT, "PROCESS has performed an evaluation run.")
po.oblnkl(constants.NOUT)

# Evaluate objective function
norm_objf = objective_function(data.numerics.minmax, data)
po.ovarre(constants.MFILE, "Normalised objective function", "(norm_objf)", norm_objf)

# Print the residuals of the constraint equations

residual_error, value, residual, symbols, units = constraints.constraint_eqns(
data.numerics.neqns + data.numerics.nineqns, -1, data
)

labels = [
data.numerics.lablcc[j]
for j in [
i - 1
for i in data.numerics.icc[: data.numerics.neqns + data.numerics.nineqns]
]
]
physical_constraint = [f"{c} {u}" for c, u in zip(value, units, strict=False)]
physical_residual = [f"{c} {u}" for c, u in zip(residual, units, strict=False)]

table_data = {
"Constraint Name": labels,
"Constraint Type": symbols,
"Physical constraint": physical_constraint,
"Constraint residual": physical_residual,
"Normalised residual": residual_error,
}

po.write(constants.NOUT, tabulate(table_data, headers="keys"))

for i in range(data.numerics.neqns):
constraint_id = data.numerics.icc[i]
po.ovarre(
constants.MFILE,
f"{labels[i]} normalised residue",
f"(eq_con{constraint_id:03d})",
residual_error[i],
)

for i in range(data.numerics.nineqns):
constraint_id = data.numerics.icc[data.numerics.neqns + i]
po.ovarre(
constants.MFILE,
f"{labels[data.numerics.neqns + i]}",
f"(ineq_con{constraint_id:03d})",
residual_error[data.numerics.neqns + i],
)
13 changes: 2 additions & 11 deletions process/core/input.py
Original file line number Diff line number Diff line change
Expand Up @@ -1138,24 +1138,15 @@ def bounds(self) -> tuple[NumberType | None, NumberType | None]:
"scan",
int,
choices=range(IPNSCNS + 1),
array=True,
),
"nsweep": InputVariable(
"scan",
int,
choices=range(1, IPNSCNV + 1),
),
"isweep_2": InputVariable(
"scan",
int,
choices=range(IPNSCNS + 1),
),
"nsweep_2": InputVariable(
"scan",
int,
choices=range(1, IPNSCNV + 1),
array=True,
),
"sweep": InputVariable("scan", float, array=True),
"sweep_2": InputVariable("scan", float, array=True),
"impvardiv": InputVariable(
"reinke",
int,
Expand Down
4 changes: 2 additions & 2 deletions process/core/io/plot/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -241,10 +241,10 @@ def plot_scans_cli(
list(map(float, filter(None, x_axis_max))),
list(map(float, filter(None, x_axis_range))),
y_axis_percent,
y_axis_percent2,
list(map(float, filter(None, y_axis_max))),
list(map(float, filter(None, y_axis2_max))),
list(map(float, filter(None, y_axis_range))),
y_axis_percent2,
list(map(float, filter(None, y_axis2_max))),
list(map(float, filter(None, y_axis_range2))),
label_name,
twod_contour,
Expand Down
Loading
Loading