From b5f9c7abcae4dd9d7b41cf206da069a804613bfa Mon Sep 17 00:00:00 2001 From: Clair Mould <86794332+clmould@users.noreply.github.com> Date: Thu, 4 Jun 2026 13:58:45 +0100 Subject: [PATCH] convert global vars to data class --- process/core/caller.py | 14 +-- process/core/init.py | 23 ++-- process/core/input.py | 10 +- process/core/io/vary_run/config.py | 10 +- process/core/model.py | 2 + process/core/process_output.py | 18 +-- process/core/scan.py | 31 +++--- process/core/solver/evaluators.py | 3 +- process/core/solver/iteration_variables.py | 4 +- process/core/solver/solver.py | 17 +-- process/core/solver/solver_handler.py | 2 +- process/data_structure/global_variables.py | 109 ++++++++----------- process/main.py | 25 +++-- process/models/costs/costs_2015.py | 3 +- process/models/stellarator/stellarator.py | 7 +- process/models/tfcoil/base.py | 3 +- tests/integration/test_vmcon.py | 10 +- tests/unit/core/test_input.py | 22 ++-- tests/unit/core/test_mfile.py | 9 +- tests/unit/models/blankets/test_ccfe_hcpb.py | 6 +- tests/unit/models/tfcoil/test_sctfcoil.py | 3 +- tests/unit/test_main.py | 12 +- 22 files changed, 161 insertions(+), 182 deletions(-) diff --git a/process/core/caller.py b/process/core/caller.py index 384e2e3323..963d8d4af5 100644 --- a/process/core/caller.py +++ b/process/core/caller.py @@ -161,15 +161,13 @@ def call_models_and_write_output(self, xc: np.ndarray, ifail: int): for _ in range(10): # Divert OUT.DAT and MFILE.DAT output to scratch files for # idempotence checking - OutputFileManager.open_idempotence_files() + OutputFileManager.open_idempotence_files(self.data.globals.output_prefix) self._call_models_once(xc) # Write mfile finalise(self.models, self.data, ifail) # Extract data from intermediate idempotence-checking mfile - mfile_path = ( - data_structure.global_variables.output_prefix - ) + "IDEM_MFILE.DAT" + mfile_path = (self.data.globals.output_prefix) + "IDEM_MFILE.DAT" mfile = MFile(mfile_path) # Create mfile dict of float values: only compare floats mfile_data = { @@ -204,7 +202,9 @@ def call_models_and_write_output(self, xc: np.ndarray, ifail: int): logger.debug("Mfiles idempotent, returning") # Divert OUT.DAT and MFILE.DAT output back to original files # now idempotence checking complete - OutputFileManager.close_idempotence_files() + OutputFileManager.close_idempotence_files( + self.data.globals.output_prefix + ) # Write final output file and mfile finalise(self.models, self.data, ifail) return @@ -230,12 +230,12 @@ def call_models_and_write_output(self, xc: np.ndarray, ifail: int): ) # Close idempotence files, write final output file and mfile - OutputFileManager.close_idempotence_files() + OutputFileManager.close_idempotence_files(self.data.globals.output_prefix) except Exception: # If exception in model evaluations delete intermediate idempotence # files to clean up - OutputFileManager.close_idempotence_files() + OutputFileManager.close_idempotence_files(self.data.globals.output_prefix) raise else: finalise( diff --git a/process/core/init.py b/process/core/init.py index c567aa3c78..a41da2290c 100644 --- a/process/core/init.py +++ b/process/core/init.py @@ -46,7 +46,7 @@ def init_process(data: DataStructure): iteration_variables.initialise_iteration_variables() # Creating and open the files MFile and OUTFile - process_output.OutputFileManager.open_files() + process_output.OutputFileManager.open_files(data.globals.output_prefix) # Input any desired new initial values inputs = parse_input_file(data) @@ -63,7 +63,7 @@ def init_process(data: DataStructure): # Check input data for errors/ambiguities check_process(inputs, data) - run_summary() + run_summary(data) def get_git_summary() -> tuple[str, str]: @@ -101,7 +101,7 @@ def get_git_summary() -> tuple[str, str]: return git_branch, git_tag -def run_summary(): +def run_summary(data: DataStructure): """Write a summary of the PROCESS run to the output file and MFile""" # Outfile and terminal # for outfile in [constants.NOUT, constants.IOTTY]: @@ -137,12 +137,12 @@ def run_summary(): process_output.ocmmnt(outfile, f"Computer : {machine}") process_output.ocmmnt(outfile, f"Directory : {Path.cwd()}") - fileprefix = data_structure.global_variables.fileprefix + fileprefix = data.globals.fileprefix process_output.ocmmnt( outfile, f"Input : {fileprefix}", ) - runtitle = data_structure.global_variables.runtitle + runtitle = data.globals.runtitle process_output.ocmmnt( outfile, f"Run title : {runtitle}", @@ -150,7 +150,7 @@ def run_summary(): process_output.ocmmnt( outfile, - f"Run type : Reactor concept design: {data_structure.global_variables.icase}, (c) UK Atomic Energy Authority", + f"Run type : Reactor concept design: {data.globals.icase}, (c) UK Atomic Energy Authority", ) process_output.oblnkl(outfile) @@ -175,7 +175,7 @@ def run_summary(): if data_structure.numerics.ioptimz == PROCESSRunMode.OPTIMISATION: process_output.ocmmnt( outfile, - f"Max iterations : {data_structure.global_variables.maxcal}", + f"Max iterations : {data.globals.maxcal}", ) if data_structure.numerics.minmax > 0: @@ -231,7 +231,6 @@ def init_all_module_vars(): """ logging_model_handler.clear_logs() data_structure.numerics.init_numerics() - data_structure.global_variables.init_global_variables() init_scan_variables() constants.init_constants() @@ -559,7 +558,7 @@ def check_process(inputs, data): # noqa: ARG001 # Tight aspect ratio options (ST) if data.physics.itart == 1: - data_structure.global_variables.icase = "Tight aspect ratio tokamak model" + data.globals.icase = "Tight aspect ratio tokamak model" # Disabled Forcing that no inboard breeding blanket is used # Disabled i_blkt_inboard = 0 @@ -745,7 +744,7 @@ def check_process(inputs, data): # noqa: ARG001 # Pulsed power plant model if data.pulse.i_pulsed_plant == 1: - data_structure.global_variables.icase = "Pulsed tokamak model" + data.globals.icase = "Pulsed tokamak model" else: data.buildings.esbldgm3 = 0.0 @@ -1151,6 +1150,6 @@ def set_active_constraints(): def set_device_type(data): if data.ife.ife == 1: - data_structure.global_variables.icase = "Inertial Fusion model" + data.globals.icase = "Inertial Fusion model" elif data.stellarator.istell != 0: - data_structure.global_variables.icase = "Stellarator model" + data.globals.icase = "Stellarator model" diff --git a/process/core/input.py b/process/core/input.py index 540627a0a0..63ec7f2371 100644 --- a/process/core/input.py +++ b/process/core/input.py @@ -103,15 +103,15 @@ def __post_init__(self): INPUT_VARIABLES = { - "runtitle": InputVariable(data_structure.global_variables, str), - "verbose": InputVariable(data_structure.global_variables, int, choices=[0, 1]), - "run_tests": InputVariable(data_structure.global_variables, int, choices=[0, 1]), + "runtitle": InputVariable("globals", str), + "verbose": InputVariable("globals", int, choices=[0, 1]), + "run_tests": InputVariable("globals", int, choices=[0, 1]), "ioptimz": InputVariable(data_structure.numerics, int, choices=[1, -2]), "epsvmc": InputVariable(data_structure.numerics, float, range=(0.0, 1.0)), "boundl": InputVariable(data_structure.numerics, float, array=True), "boundu": InputVariable(data_structure.numerics, float, array=True), "epsfcn": InputVariable(data_structure.numerics, float, range=(0.0, 1.0)), - "maxcal": InputVariable(data_structure.global_variables, int, range=(0, 10000)), + "maxcal": InputVariable("globals", int, range=(0, 10000)), "minmax": InputVariable(data_structure.numerics, int), "neqns": InputVariable( data_structure.numerics, int, range=(0, ConstraintManager.num_constraints()) @@ -1144,7 +1144,7 @@ def __post_init__(self): def parse_input_file(data_structure_obj: DataStructure): - input_file = data_structure.global_variables.fileprefix + input_file = data_structure_obj.globals.fileprefix input_file_path = Path("IN.DAT") if input_file != "": diff --git a/process/core/io/vary_run/config.py b/process/core/io/vary_run/config.py index c267881d6a..c52a27b44d 100644 --- a/process/core/io/vary_run/config.py +++ b/process/core/io/vary_run/config.py @@ -13,7 +13,6 @@ import click from numpy.random import default_rng -from process import data_structure from process.core.io.in_dat import InDat from process.core.io.mfile import MFile from process.core.io.vary_run.tools import ( @@ -26,6 +25,7 @@ process_warnings, set_variable_in_indat, ) +from process.core.model import DataStructure logger = logging.getLogger(__name__) @@ -261,7 +261,7 @@ def error_status2readme(self, mfile): def modify_in_dat(self): """Modifies the original IN.DAT file""" - def setup(self): + def setup(self, data: DataStructure): """Sets up the program for running""" self.echo() @@ -275,10 +275,8 @@ def setup(self): self.generator = default_rng(seed=self.u_seed) - data_structure.global_variables.output_prefix = str( - self.wdir / self.outfile.strip("MFILE.DAT") - ) - data_structure.global_variables.fileprefix = str(self.wdir / self.infile) + data.globals.output_prefix = str(self.wdir / self.outfile.strip("MFILE.DAT")) + data.globals.fileprefix = str(self.wdir / self.infile) def run_process(self, input_path: Path, solver: str = "vmcon"): """Perform a single run of PROCESS, catching any errors. diff --git a/process/core/model.py b/process/core/model.py index dff9ccc1f1..b8acd843d5 100644 --- a/process/core/model.py +++ b/process/core/model.py @@ -14,6 +14,7 @@ from process.data_structure.divertor_variables import DivertorData from process.data_structure.first_wall_variables import FirstWallData from process.data_structure.fwbs_variables import FWBSData +from process.data_structure.global_variables import GlobalData from process.data_structure.heat_transport_variables import HeatTransportData from process.data_structure.ife_variables import IFEData from process.data_structure.impurity_radiation_variables import ImpurityRadiationData @@ -75,6 +76,7 @@ class DataStructure: rebco: RebcoData = initialise_later tfcoil: TFData = initialise_later superconducting_tfcoil: SuperconductingTFData = initialise_later + globals: GlobalData = initialise_later def __post_init__(self): for f in fields(self): diff --git a/process/core/process_output.py b/process/core/process_output.py index ff8956bd8b..fb8340c617 100644 --- a/process/core/process_output.py +++ b/process/core/process_output.py @@ -4,38 +4,38 @@ import numpy as np from process.core import constants -from process.data_structure import global_variables, numerics +from process.data_structure import numerics class OutputFileManager: @classmethod - def open_files(cls, *, mode="w"): + def open_files(cls, output_prefix: str, *, mode="w"): cls._outfile = open( # noqa: SIM115 - Path(global_variables.output_prefix + "OUT.DAT"), mode + Path(output_prefix + "OUT.DAT"), mode ) cls._mfile = open( # noqa: SIM115 - Path(global_variables.output_prefix + "MFILE.DAT"), mode + Path(output_prefix + "MFILE.DAT"), mode ) @classmethod - def open_idempotence_files(cls): + def open_idempotence_files(cls, output_prefix: str): cls._outfile.close() cls._mfile.close() cls._outfile = open( # noqa: SIM115 - Path(global_variables.output_prefix + "IDEM_OUT.DAT"), "w" + Path(output_prefix + "IDEM_OUT.DAT"), "w" ) cls._mfile = open( # noqa: SIM115 - Path(global_variables.output_prefix + "IDEM_MFILE.DAT"), "w" + Path(output_prefix + "IDEM_MFILE.DAT"), "w" ) @classmethod - def close_idempotence_files(cls): + def close_idempotence_files(cls, output_prefix: str): Path(cls._outfile.name).unlink() Path(cls._mfile.name).unlink() cls._outfile.close() cls._mfile.close() - cls.open_files(mode="a") + cls.open_files(mode="a", output_prefix=output_prefix) @classmethod def finish(cls): diff --git a/process/core/scan.py b/process/core/scan.py index 7a33222c49..3111f41a60 100644 --- a/process/core/scan.py +++ b/process/core/scan.py @@ -16,7 +16,6 @@ from process.core.solver import constraints from process.core.solver.solver_handler import SolverHandler from process.data_structure import ( - global_variables, numerics, scan_variables, ) @@ -386,7 +385,7 @@ def post_optimise(self, ifail: int): constants.NOUT, "VMCON convergence parameter", "(convergence_parameter)", - global_variables.convergence_parameter, + self.data.globals.convergence_parameter, "OP ", ) process_output.ovarin( @@ -991,8 +990,8 @@ def scan_2d_init(self): ) def scan_1d_write_point_header(self, iscan: int): - global_variables.iscan_global = iscan - global_variables.vlabel, global_variables.xlabel = self.scan_select( + self.data.globals.iscan_global = iscan + self.data.globals.vlabel, self.data.globals.xlabel = self.scan_select( scan_variables.nsweep, scan_variables.sweep, iscan ) @@ -1001,8 +1000,8 @@ def scan_1d_write_point_header(self, iscan: int): process_output.write( constants.NOUT, - f"***** Scan point {iscan} of {scan_variables.isweep} : {global_variables.xlabel}" - f", {global_variables.vlabel} = {scan_variables.sweep[iscan - 1]} " + f"***** Scan point {iscan} of {scan_variables.isweep} : {self.data.globals.xlabel}" + f", {self.data.globals.vlabel} = {scan_variables.sweep[iscan - 1]} " "*****", ) process_output.ostars(constants.NOUT, 110) @@ -1011,7 +1010,7 @@ def scan_1d_write_point_header(self, iscan: int): print( f"Starting scan point {iscan} of {scan_variables.isweep} : " - f"{global_variables.xlabel} , {global_variables.vlabel}" + f"{self.data.globals.xlabel} , {self.data.globals.vlabel}" f" = {scan_variables.sweep[iscan - 1]}" ) @@ -1019,12 +1018,12 @@ def scan_2d_write_point_header(self, iscan, iscan_1, iscan_2): iscan_r = scan_variables.isweep_2 - iscan_2 + 1 if iscan_1 % 2 == 0 else iscan_2 # Makes iscan available globally (read-only) - global_variables.iscan_global = iscan + self.data.globals.iscan_global = iscan - global_variables.vlabel, global_variables.xlabel = self.scan_select( + self.data.globals.vlabel, self.data.globals.xlabel = self.scan_select( scan_variables.nsweep, scan_variables.sweep, iscan_1 ) - global_variables.vlabel_2, global_variables.xlabel_2 = self.scan_select( + self.data.globals.vlabel_2, self.data.globals.xlabel_2 = self.scan_select( scan_variables.nsweep_2, scan_variables.sweep_2, iscan_r ) @@ -1034,8 +1033,8 @@ def scan_2d_write_point_header(self, iscan, iscan_1, iscan_2): process_output.write( constants.NOUT, f"***** 2D Scan point {iscan} of {scan_variables.isweep * scan_variables.isweep_2} : " - f"{global_variables.vlabel} = {scan_variables.sweep[iscan_1 - 1]} and" - f" {global_variables.vlabel_2} = {scan_variables.sweep_2[iscan_r - 1]} " + f"{self.data.globals.vlabel} = {scan_variables.sweep[iscan_1 - 1]} and" + f" {self.data.globals.vlabel_2} = {scan_variables.sweep_2[iscan_r - 1]} " "*****", ) process_output.ostars(constants.NOUT, 110) @@ -1043,10 +1042,10 @@ def scan_2d_write_point_header(self, iscan, iscan_1, iscan_2): process_output.ovarin(constants.MFILE, "Scan point number", "(iscan)", iscan) print( - f"Starting scan point {iscan}: {global_variables.xlabel}, " - f"{global_variables.vlabel} = {scan_variables.sweep[iscan_1 - 1]}" - f" and {global_variables.xlabel_2}, " - f"{global_variables.vlabel_2} = {scan_variables.sweep_2[iscan_r - 1]} " + f"Starting scan point {iscan}: {self.data.globals.xlabel}, " + f"{self.data.globals.vlabel} = {scan_variables.sweep[iscan_1 - 1]}" + f" and {self.data.globals.xlabel_2}, " + f"{self.data.globals.vlabel_2} = {scan_variables.sweep_2[iscan_r - 1]} " ) return iscan_r diff --git a/process/core/solver/evaluators.py b/process/core/solver/evaluators.py index d892bb6e61..8677e0edd6 100644 --- a/process/core/solver/evaluators.py +++ b/process/core/solver/evaluators.py @@ -5,7 +5,6 @@ from process.core.caller import Caller from process.core.model import DataStructure -from process.data_structure import global_variables as gv from process.data_structure import numerics logger = logging.getLogger(__name__) @@ -64,7 +63,7 @@ def fcnvmc1(self, _n, m, xv, ifail): objf, conf = self.caller.call_models(xv, m) # Verbose diagnostics - if gv.verbose == 1: + if self.data.globals.verbose == 1: summ = 0.0 for i in range(m): summ += conf[i] ** 2 diff --git a/process/core/solver/iteration_variables.py b/process/core/solver/iteration_variables.py index 4b858c7a33..26ef603195 100644 --- a/process/core/solver/iteration_variables.py +++ b/process/core/solver/iteration_variables.py @@ -302,8 +302,8 @@ def load_iteration_variables(data): # warn of the iteration variable is also a scan variable because this will cause # the optimiser and scan to overwrite the same variable and conflict if iteration_variable.name in { - data_structure.global_variables.vlabel, - data_structure.global_variables.vlabel_2, + data.globals.vlabel, + data.globals.vlabel_2, }: warn( ( diff --git a/process/core/solver/solver.py b/process/core/solver/solver.py index a285de39c7..167ff4b2cb 100644 --- a/process/core/solver/solver.py +++ b/process/core/solver/solver.py @@ -17,8 +17,9 @@ from scipy.optimize import fsolve from process.core.exceptions import ProcessValueError +from process.core.model import DataStructure from process.core.solver.evaluators import Evaluators -from process.data_structure import global_variables, numerics +from process.data_structure import numerics logger = logging.getLogger(__name__) @@ -26,12 +27,14 @@ class _Solver(ABC): """Base class for different solver implementations.""" - def __init__(self): + def __init__(self, *, maxcal: int | None = None): """Initialise a solver.""" # Exit code for the solver self.ifail = 0 self.tolerance = numerics.epsvmc self.b: float | None = None + self.convergence_parameter: float | None = None + self.maxcal = maxcal def set_evaluators(self, evaluators: Evaluators): """Set objective and constraint functions and their gradient evaluators. @@ -183,7 +186,7 @@ def solve(self) -> int: def _solver_callback(i: int, _result, _x, convergence_param: float): numerics.nviter = i + 1 - global_variables.convergence_parameter = convergence_param + self.convergence_parameter = convergence_param print( f"{i + 1} | Convergence Parameter: {convergence_param:.3E}", end="\r", @@ -232,7 +235,7 @@ def _ineq_cons_satisfied( np.array(self.x_0), np.array(self.bndl), np.array(self.bndu), - max_iter=global_variables.maxcal, + max_iter=self.maxcal, epsilon=self.tolerance, qsp_options={"solver": cvxpy.CLARABEL}, initial_B=bb, @@ -340,7 +343,7 @@ def solve(self) -> int: return self.info -def get_solver(solver_name: str = "vmcon") -> _Solver: +def get_solver(data: DataStructure, solver_name: str = "vmcon") -> _Solver: """Return a solver instance. Parameters @@ -356,9 +359,9 @@ def get_solver(solver_name: str = "vmcon") -> _Solver: solver: _Solver if solver_name == "vmcon": - solver = Vmcon() + solver = Vmcon(maxcal=data.globals.maxcal) elif solver_name == "vmcon_bounded": - solver = VmconBounded() + solver = VmconBounded(maxcal=data.globals.maxcal) elif solver_name == "fsolve": solver = FSolve() else: diff --git a/process/core/solver/solver_handler.py b/process/core/solver/solver_handler.py index 5094c5a9e2..03e76a687a 100644 --- a/process/core/solver/solver_handler.py +++ b/process/core/solver/solver_handler.py @@ -51,7 +51,7 @@ def run(self): evaluators = Evaluators(self.models, self.data, x) # Configure solver for problem - self.solver = get_solver(self.solver_name) + self.solver = get_solver(self.data, self.solver_name) self.solver.set_evaluators(evaluators) self.solver.set_bounds(bndl, bndu) self.solver.set_opt_params(x) diff --git a/process/data_structure/global_variables.py b/process/data_structure/global_variables.py index b599b85961..80bfc7370f 100644 --- a/process/data_structure/global_variables.py +++ b/process/data_structure/global_variables.py @@ -1,66 +1,43 @@ -icase: str = None -"""Power plant type""" - -runtitle: str -"""A short descriptive title for the run""" - -run_tests: int = None - -verbose: int = None - -maxcal: int = None -"""Maximum number of solver iterations""" - -fileprefix: str = None -"""Path to input file""" - -output_prefix: str = None -"""Output file path prefix""" - -xlabel: str = None -"""Scan parameter description label""" - -vlabel: str = None -"""Scan value name label""" - -xlabel_2: str = None -"""Scan parameter description label (2nd dimension)""" - -vlabel_2: str = None -"""Scan value name label (2nd dimension)""" - -iscan_global: int = None - -convergence_parameter: int = None -"""VMCON convergence parameter 'sum'""" - - -def init_global_variables(): - global \ - icase, \ - runtitle, \ - run_tests, \ - verbose, \ - maxcal, \ - fileprefix, \ - output_prefix, \ - xlabel, \ - vlabel, \ - xlabel_2, \ - vlabel_2, \ - iscan_global, \ - convergence_parameter - - icase = "Steady-state tokamak model" - runtitle = "Run Title (change this line using input variable 'runtitle')" - run_tests = 0 - verbose = 0 - maxcal = 200 - fileprefix = "" - output_prefix = "" - xlabel = "" - vlabel = "" - xlabel_2 = "" - vlabel_2 = "" - iscan_global = 0 - convergence_parameter = 0.0 +from dataclasses import dataclass + + +@dataclass(slots=True) +class GlobalData: + icase: str = "Steady-state tokamak model" + """Power plant type""" + + runtitle: str = "Run Title (change this line using input variable 'runtitle')" + """A short descriptive title for the run""" + + run_tests: int = 0 + + verbose: int = 0 + + maxcal: int = 200 + """Maximum number of solver iterations""" + + fileprefix: str = "" + """Path to input file""" + + output_prefix: str = "" + """Output file path prefix""" + + xlabel: str = "" + """Scan parameter description label""" + + vlabel: str = "" + """Scan value name label""" + + xlabel_2: str = "" + """Scan parameter description label (2nd dimension)""" + + vlabel_2: str = "" + """Scan value name label (2nd dimension)""" + + iscan_global: int = 0 + + convergence_parameter: float = 0.0 + """VMCON convergence parameter 'sum'""" + + +CREATE_DICTS_FROM_DATACLASS = GlobalData diff --git a/process/main.py b/process/main.py index d6835f92c9..45e40ae4d4 100644 --- a/process/main.py +++ b/process/main.py @@ -264,7 +264,12 @@ class VaryRun: README.txt - contains comments from config file """ - def __init__(self, config_file: str, solver: str = "vmcon"): + def __init__( + self, + config_file: str, + solver: str = "vmcon", + data_structure: DataStructure | None = None, + ): """Initialise and perform a VaryRun. Parameters @@ -277,7 +282,7 @@ def __init__(self, config_file: str, solver: str = "vmcon"): # Store the absolute path to the config file immediately: various # dir changes happen in old run_process code self.config = RunProcessConfig.from_file(Path(config_file).resolve(), solver) - self.data = DataStructure() + self.data = data_structure or DataStructure() @property def mfile_path(self): @@ -292,7 +297,7 @@ def run(self): if input file doesn't exist """ init.init_all_module_vars() - self.config.setup() + self.config.setup(self.data) setup_loggers(Path(self.config.wdir) / "process.log") @@ -325,11 +330,11 @@ def __init__( which solver to use, as specified in solver.py """ self.input_file = Path(input_file) + self.data = data_structure or DataStructure() self.validate_input(update_obsolete) self.init_module_vars() self.set_filenames(filepath_out) - self.data = data_structure or DataStructure() self.initialise() self.models = Models(self.data) self.solver = solver @@ -365,7 +370,7 @@ def set_filenames(self, filepath_out): .replace("MFILE.DAT", "") ).strip() self.set_input() - data_structure.global_variables.output_prefix = ( + self.data.globals.output_prefix = ( "" if not self.filename_prefix else Path(self.filepath, self.filename_prefix).as_posix().strip() @@ -394,22 +399,18 @@ def set_input(self): ) # Set the input file in the Fortran - data_structure.global_variables.fileprefix = self.input_path.resolve() + self.data.globals.fileprefix = self.input_path.resolve() def set_output(self): """Set the output file name. Set Path object on the Process object, and set the prefix in the Fortran. """ - self.output_path = Path( - data_structure.global_variables.output_prefix + "OUT.DAT" - ) + self.output_path = Path(self.data.globals.output_prefix + "OUT.DAT") def set_mfile(self): """Set the mfile filename.""" - self.mfile_path = Path( - data_structure.global_variables.output_prefix + "MFILE.DAT" - ) + self.mfile_path = Path(self.data.globals.output_prefix + "MFILE.DAT") def initialise(self): """Run the init module to call all initialisation routines.""" diff --git a/process/models/costs/costs_2015.py b/process/models/costs/costs_2015.py index 69158a8143..c009d6f60e 100644 --- a/process/models/costs/costs_2015.py +++ b/process/models/costs/costs_2015.py @@ -5,7 +5,6 @@ from process.core import constants from process.core import process_output as po from process.core.model import Model -from process.data_structure import global_variables logger = logging.getLogger(__name__) @@ -147,7 +146,7 @@ def calc_fwbs_costs(self): tail_li6 = feed_li6 * 0.75e0 # Built-in test - if global_variables.run_tests == 1: + if self.data.globals.run_tests == 1: product_li6 = 0.3 feed_to_product_mass_ratio = (product_li6 - tail_li6) / (feed_li6 - tail_li6) tail_to_product_mass_ratio = (product_li6 - feed_li6) / (feed_li6 - tail_li6) diff --git a/process/models/stellarator/stellarator.py b/process/models/stellarator/stellarator.py index 4eaa3def11..e50599afee 100644 --- a/process/models/stellarator/stellarator.py +++ b/process/models/stellarator/stellarator.py @@ -13,10 +13,7 @@ from process.core.coolprop_interface import FluidProperties from process.core.exceptions import ProcessValueError from process.core.model import Model -from process.data_structure import ( - global_variables, - numerics, -) +from process.data_structure import numerics from process.models.physics.physics import Physics, rether from process.models.power import PumpingPowerModelTypes from process.models.stellarator.build import st_build @@ -208,7 +205,7 @@ def st_new_config(self): """ load_stellarator_config( self.data.stellarator.istell, - Path(f"{global_variables.output_prefix}stella_conf.json"), + Path(f"{self.data.globals.output_prefix}stella_conf.json"), self.data, ) diff --git a/process/models/tfcoil/base.py b/process/models/tfcoil/base.py index c3ace698bd..dc7b0c8878 100644 --- a/process/models/tfcoil/base.py +++ b/process/models/tfcoil/base.py @@ -16,7 +16,6 @@ from process.core import process_output as po from process.core.exceptions import ProcessValueError from process.core.model import DataStructure, Model -from process.data_structure import global_variables from process.data_structure.physics_variables import DivertorNumberModels logger = logging.getLogger(__name__) @@ -3609,7 +3608,7 @@ def table_format_arrays(a, mult=1, delim="\t\t"): for k, v in sig_file_data.items() } - sig_filename = global_variables.output_prefix + "SIG_TF.json" + sig_filename = self.data.globals.output_prefix + "SIG_TF.json" with open(sig_filename, "w") as f: json.dump(sig_file_data, f) diff --git a/tests/integration/test_vmcon.py b/tests/integration/test_vmcon.py index 1630840081..ed23fede33 100644 --- a/tests/integration/test_vmcon.py +++ b/tests/integration/test_vmcon.py @@ -13,6 +13,7 @@ import pytest from process.core.init import init_all_module_vars +from process.core.model import DataStructure from process.core.solver.evaluators import Evaluators from process.core.solver.solver import get_solver @@ -26,6 +27,11 @@ def reinit(): init_all_module_vars() +@pytest.fixture +def data_structure_obj(): + return DataStructure() + + class Case: """A Vmcon test case.""" @@ -600,7 +606,7 @@ def case(request): return case_fn() -def test_vmcon(case, solver_name): +def test_vmcon(case, solver_name, data_structure_obj): """Integration test for Vmcon. :param case: a Vmcon scenario and its expected result @@ -616,7 +622,7 @@ def test_vmcon(case, solver_name): logger.debug(f"x[{i}] = {case.solver_args.x[i]}") # Configure solver for problem - solver = get_solver(solver_name) + solver = get_solver(data_structure_obj, solver_name) solver.set_evaluators(case.evaluator) solver.set_opt_params(case.solver_args.x) solver.set_bounds( diff --git a/tests/unit/core/test_input.py b/tests/unit/core/test_input.py index ffcba21edb..d11a15415b 100644 --- a/tests/unit/core/test_input.py +++ b/tests/unit/core/test_input.py @@ -66,7 +66,7 @@ def test_parse_real(epsvmc, expected, tmp_path, data_structure_obj): Program to get the expected value for 0.008 provided at https://github.com/ukaea/PROCESS/pull/3067 """ - data_structure.global_variables.fileprefix = _create_input_file( + data_structure_obj.globals.fileprefix = _create_input_file( tmp_path, f"epsvmc = {epsvmc}" ) init.init_process(data_structure_obj) @@ -91,7 +91,7 @@ def test_exact_parsing(value, tmp_path, data_structure_obj): These tests failed using the old input parser and serve to show that the Python parser generally produces more accurate floats and accumulates less error. """ - data_structure.global_variables.fileprefix = _create_input_file( + data_structure_obj.globals.fileprefix = _create_input_file( tmp_path, f"epsvmc = {value}" ) init.init_process(data_structure_obj) @@ -100,20 +100,20 @@ def test_exact_parsing(value, tmp_path, data_structure_obj): def test_parse_input(tmp_path, data_structure_obj): - data_structure.global_variables.fileprefix = _create_input_file( + data_structure_obj.globals.fileprefix = _create_input_file( tmp_path, ("runtitle = my run title\nioptimz = -2\nepsvmc = 0.6\nboundl(1) = 0.5"), ) init.init_process(data_structure_obj) - assert data_structure.global_variables.runtitle == "my run title" + assert data_structure_obj.globals.runtitle == "my run title" assert data_structure.numerics.ioptimz == PROCESSRunMode.EVALUATION assert pytest.approx(data_structure.numerics.epsvmc) == 0.6 assert pytest.approx(data_structure.numerics.boundl[0]) == 0.5 def test_input_choices(tmp_path, data_structure_obj): - data_structure.global_variables.fileprefix = _create_input_file( + data_structure_obj.globals.fileprefix = _create_input_file( tmp_path, ("ioptimz = -1") ) @@ -125,7 +125,7 @@ def test_input_choices(tmp_path, data_structure_obj): ("input_file_value"), [(-0.01,), (1.1,)], ids=("violate lower", "violate upper") ) def test_input_range(tmp_path, input_file_value, data_structure_obj): - data_structure.global_variables.fileprefix = _create_input_file( + data_structure_obj.globals.fileprefix = _create_input_file( tmp_path, (f"epsvmc = {input_file_value}") ) @@ -137,7 +137,7 @@ def test_input_range(tmp_path, input_file_value, data_structure_obj): def test_input_array_when_not(tmp_path, data_structure_obj): - data_structure.global_variables.fileprefix = _create_input_file( + data_structure_obj.globals.fileprefix = _create_input_file( tmp_path, ("epsvmc(1) = 0.5") ) @@ -146,7 +146,7 @@ def test_input_array_when_not(tmp_path, data_structure_obj): def test_input_not_array_when_is(tmp_path, data_structure_obj): - data_structure.global_variables.fileprefix = _create_input_file( + data_structure_obj.globals.fileprefix = _create_input_file( tmp_path, ("boundl = 0.5") ) @@ -155,7 +155,7 @@ def test_input_not_array_when_is(tmp_path, data_structure_obj): def test_input_float_when_int(tmp_path, data_structure_obj): - data_structure.global_variables.fileprefix = _create_input_file( + data_structure_obj.globals.fileprefix = _create_input_file( tmp_path, ("ioptimz = 0.5") ) @@ -164,7 +164,7 @@ def test_input_float_when_int(tmp_path, data_structure_obj): def test_input_array(tmp_path, data_structure_obj): - data_structure.global_variables.fileprefix = _create_input_file( + data_structure_obj.globals.fileprefix = _create_input_file( tmp_path, ("boundl = 0.1, 0.2, 1.0, 0.0, 1.0e2") ) @@ -175,7 +175,7 @@ def test_input_array(tmp_path, data_structure_obj): def test_input_on_new_data_structure(tmp_path, data_structure_obj): - data_structure.global_variables.fileprefix = _create_input_file( + data_structure_obj.globals.fileprefix = _create_input_file( tmp_path, ("windspeed = 1.22") ) diff --git a/tests/unit/core/test_mfile.py b/tests/unit/core/test_mfile.py index 8426e187a8..ed5961a7ac 100644 --- a/tests/unit/core/test_mfile.py +++ b/tests/unit/core/test_mfile.py @@ -9,12 +9,17 @@ from process.core.model import DataStructure -def test_get_mfile_initial_ixc_values(input_file, tmp_path): +@pytest.fixture +def data_structure_obj(): + return DataStructure() + + +def test_get_mfile_initial_ixc_values(input_file, tmp_path, data_structure_obj): tmp_input_file = tmp_path / "IN.DAT" shutil.copy(input_file, tmp_input_file) iteration_variable_names, iteration_variable_values = get_mfile_initial_ixc_values( - Path(tmp_input_file), DataStructure() + Path(tmp_input_file), data_structure_obj ) assert iteration_variable_names[0] == "b_plasma_toroidal_on_axis" diff --git a/tests/unit/models/blankets/test_ccfe_hcpb.py b/tests/unit/models/blankets/test_ccfe_hcpb.py index d1676788bb..367d4508f8 100644 --- a/tests/unit/models/blankets/test_ccfe_hcpb.py +++ b/tests/unit/models/blankets/test_ccfe_hcpb.py @@ -2,8 +2,6 @@ import pytest -from process.data_structure import global_variables - @pytest.fixture def ccfe_hcpb(process_models): @@ -337,7 +335,9 @@ def test_nuclear_heating_magnets(nuclearheatingmagnetsparam, monkeypatch, ccfe_h ccfe_hcpb.data.tfcoil, "whttflgs", nuclearheatingmagnetsparam.whttflgs ) - monkeypatch.setattr(global_variables, "verbose", nuclearheatingmagnetsparam.verbose) + monkeypatch.setattr( + ccfe_hcpb.data.globals, "verbose", nuclearheatingmagnetsparam.verbose + ) monkeypatch.setattr( ccfe_hcpb.data.ccfe_hcpb, diff --git a/tests/unit/models/tfcoil/test_sctfcoil.py b/tests/unit/models/tfcoil/test_sctfcoil.py index 75aedf4398..1f4ed0339c 100644 --- a/tests/unit/models/tfcoil/test_sctfcoil.py +++ b/tests/unit/models/tfcoil/test_sctfcoil.py @@ -3,7 +3,6 @@ import numpy as np import pytest -from process.data_structure import global_variables from process.models.tfcoil import superconducting as sctf @@ -429,7 +428,7 @@ def test_supercon(superconparam, monkeypatch, cicc_sctfcoil): superconparam.f_b_tf_inboard_peak_ripple_symmetric, ) - monkeypatch.setattr(global_variables, "run_tests", superconparam.run_tests) + monkeypatch.setattr(cicc_sctfcoil.data.globals, "run_tests", superconparam.run_tests) tf_limits = cicc_sctfcoil.tf_cable_in_conduit_superconductor_properties( i_tf_superconductor=superconparam.i_tf_superconductor, diff --git a/tests/unit/test_main.py b/tests/unit/test_main.py index a4bab469a4..9529eefaed 100644 --- a/tests/unit/test_main.py +++ b/tests/unit/test_main.py @@ -5,7 +5,6 @@ import pytest -from process import data_structure from process.core.model import DataStructure from process.main import SingleRun, VaryRun @@ -71,7 +70,7 @@ def test_set_input(single_run, monkeypatch, input_file): # Mocks set up, can now run set_input() single_run.set_input() # Check path has been set - assert data_structure.global_variables.fileprefix == expected + assert single_run.data.globals.fileprefix == expected def test_set_output(single_run, monkeypatch): @@ -85,14 +84,11 @@ def test_set_output(single_run, monkeypatch): # Expected output prefix expected = "output_prefix" # Mock self.filename_prefix on single_run with the value of expected - monkeypatch.setattr(data_structure.global_variables, "output_prefix", expected) + monkeypatch.setattr(single_run.data.globals, "output_prefix", expected) - # Mocking undo trys to set the value as none - # monkeypatch.setattr(data_structure.global_variables, "output_prefix", None) - # Run the method, and extract the value single_run.set_output() - assert Path(data_structure.global_variables.output_prefix).name == expected + assert Path(single_run.data.globals.output_prefix).name == expected def test_initialise(single_run, monkeypatch): @@ -117,7 +113,7 @@ def test_set_mfile(single_run, monkeypatch): prefix = "test" expected = Path(prefix + "MFILE.DAT") # Mock filename_prefix and run - monkeypatch.setattr(data_structure.global_variables, "output_prefix", prefix) + monkeypatch.setattr(single_run.data.globals, "output_prefix", prefix) single_run.set_mfile() assert single_run.mfile_path.name == expected.name