From 0716a11f16951f7896c0030e4299086c61d7f1b8 Mon Sep 17 00:00:00 2001 From: Timothy Nunn Date: Wed, 4 Mar 2026 15:09:35 +0000 Subject: [PATCH 1/4] Convert wateruse to model with dataclass variables --- process/core/caller.py | 2 +- process/core/init.py | 2 - process/core/model.py | 36 ++++++ process/core/output.py | 2 +- .../data_structure/water_usage_variables.py | 119 +++++++----------- process/main.py | 17 +++ process/models/water_use.py | 98 +++++++-------- tests/conftest.py | 12 ++ tests/unit/test_water_usage.py | 81 ++++++------ 9 files changed, 203 insertions(+), 166 deletions(-) create mode 100644 process/core/model.py diff --git a/process/core/caller.py b/process/core/caller.py index 6de2edcd93..4802730b1a 100644 --- a/process/core/caller.py +++ b/process/core/caller.py @@ -366,7 +366,7 @@ def _call_models_once(self, xc: np.ndarray): self.models.availability.run(output=False) # Water usage in secondary cooling system - self.models.water_use.run(output=False) + self.models.water_use.run() # Costs model """Cost switch values diff --git a/process/core/init.py b/process/core/init.py index fe4fb5c090..19ef843b0e 100644 --- a/process/core/init.py +++ b/process/core/init.py @@ -59,7 +59,6 @@ from process.data_structure.tfcoil_variables import init_tfcoil_variables from process.data_structure.times_variables import init_times_variables from process.data_structure.vacuum_variables import init_vacuum_variables -from process.data_structure.water_usage_variables import init_watuse_variables from process.models.stellarator.initialization import st_init @@ -288,7 +287,6 @@ def init_all_module_vars(): init_pulse_variables() init_rebco_variables() init_reinke_variables() - init_watuse_variables() init_cs_fatigue_variables() init_blanket_library() init_dcll_module() diff --git a/process/core/model.py b/process/core/model.py new file mode 100644 index 0000000000..4cfa60747f --- /dev/null +++ b/process/core/model.py @@ -0,0 +1,36 @@ +import abc +from dataclasses import dataclass, fields + +from process.data_structure.water_usage_variables import WaterUseData + +initialise_later = object() + + +@dataclass(kw_only=True) +class DataStructure: + water_use: WaterUseData = initialise_later + + def __post_init__(self): + for f in fields(self): + if getattr(self, f.name) is initialise_later: + setattr(self, f.name, f.type()) + + +class Model(abc.ABC): + data: DataStructure + + @abc.abstractmethod + def run(self) -> None: + """Run the model. + + The run method is resposible for 'running' the model, ensuring it updates the data + structure with variables that subsequent models will require. + """ + + @abc.abstractmethod + def output(self) -> None: + """Output model data. + + This method will always be called after run method and should output the model data to the + output files. + """ diff --git a/process/core/output.py b/process/core/output.py index 34481f90cf..16df87c9ca 100644 --- a/process/core/output.py +++ b/process/core/output.py @@ -158,7 +158,7 @@ def write(models, _outfile): models.power.output_power_profiles_over_time() # Water usage in secondary cooling system - models.water_use.run(output=True) + models.water_use.output() # stop capturing warnings so that Outfile does not end up with # a lot of non-model logs diff --git a/process/data_structure/water_usage_variables.py b/process/data_structure/water_usage_variables.py index 955c057071..c3bfbe976e 100644 --- a/process/data_structure/water_usage_variables.py +++ b/process/data_structure/water_usage_variables.py @@ -6,79 +6,46 @@ https://www.thermal-engineering.org/what-is-latent-heat-of-vaporization-definition/ """ -airtemp: float = None -"""ambient air temperature (degrees Celsius)""" - -watertemp: float = None -"""water temperature (degrees Celsius)""" - -windspeed: float = None -"""wind speed (m/s)""" - -waterdens: float = None -"""density of water (kg/m3) -for simplicity, set to static value applicable to water at 21 degC -""" - -latentheat: float = None -"""latent heat of vaporization (J/kg) -for simplicity, set to static value applicable at 1 atm (100 kPa) air pressure -""" - -volheat: float = None -"""volumetric heat of vaporization (J/m3)""" - -evapratio: float = None -"""evaporation ratio: ratio of the heat used to evaporate water -to the total heat discharged through the tower -""" - -evapvol: float = None -"""evaporated volume of water (m3)""" - -energypervol: float = None -"""input waste (heat) energy cooled per evaporated volume (J/m3)""" - -volperenergy: float = None -"""volume evaporated by units of heat energy (m3/MJ)""" - -waterusetower: float = None -"""total volume of water used in cooling tower (m3)""" - -wateruserecirc: float = None -"""total volume of water used in recirculating system (m3)""" - -wateruseonethru: float = None -"""total volume of water used in once-through system (m3)""" - - -def init_watuse_variables(): - """Initialise water variables""" - global \ - airtemp, \ - watertemp, \ - windspeed, \ - waterdens, \ - latentheat, \ - volheat, \ - evapratio, \ - evapvol, \ - energypervol, \ - volperenergy, \ - waterusetower, \ - wateruserecirc, \ - wateruseonethru - - airtemp = 15.0 - watertemp = 5.0 - windspeed = 4.0 - waterdens = 998.02 - latentheat = 2257000.0 - volheat = 0.0 - evapratio = 0.0 - evapvol = 0.0 - energypervol = 0.0 - volperenergy = 0.0 - waterusetower = 0.0 - wateruserecirc = 0.0 - wateruseonethru = 0.0 +from dataclasses import dataclass + + +@dataclass +class WaterUseData: + airtemp: float = 15.0 + """ambient air temperature (degrees Celsius)""" + watertemp: float = 5.0 + """water temperature (degrees Celsius)""" + windspeed: float = 4.0 + """wind speed (m/s)""" + waterdens: float = 998.02 + """density of water (kg/m3) + for simplicity, set to static value applicable to water at 21 degC + """ + latentheat: float = 2257000.0 + """latent heat of vaporization (J/kg) + for simplicity, set to static value applicable at 1 atm (100 kPa) air pressure + """ + volheat: float = 0.0 + """volumetric heat of vaporization (J/m3)""" + evapratio: float = 0.0 + """evaporation ratio: ratio of the heat used to evaporate water + to the total heat discharged through the tower + """ + + evapvol: float = 0.0 + """evaporated volume of water (m3)""" + + energypervol: float = 0.0 + """input waste (heat) energy cooled per evaporated volume (J/m3)""" + + volperenergy: float = 0.0 + """volume evaporated by units of heat energy (m3/MJ)""" + + waterusetower: float = 0.0 + """total volume of water used in cooling tower (m3)""" + + wateruserecirc: float = 0.0 + """total volume of water used in recirculating system (m3)""" + + wateruseonethru: float = 0.0 + """total volume of water used in once-through system (m3)""" diff --git a/process/main.py b/process/main.py index 97e2db0eb6..091d2ecb1e 100644 --- a/process/main.py +++ b/process/main.py @@ -68,6 +68,7 @@ vary_iteration_variables, ) from process.core.log import logging_model_handler, show_errors +from process.core.model import DataStructure, Model from process.core.process_output import OutputFileManager, oheadr from process.core.scan import Scan from process.models.availability import Availability @@ -731,6 +732,8 @@ def __init__(self): self.dcll = DCLL(fw=self.fw) + self.setup_data_structure() + @property def costs(self) -> CostsProtocol: if data_structure.cost_variables.cost_model == 0: @@ -748,6 +751,20 @@ def costs(self) -> CostsProtocol: def costs(self, value: CostsProtocol): self._costs_custom = value + @property + def models(self) -> tuple[Model, ...]: + # At the moment, this property just returns models that implement the Model interface. + # Eventually every Model will comply and then this method can be used as the caller/outputter! + return (self.water_use,) + + def setup_data_structure(self): + # This Models class should be replaced with a dataclass so we can + # iterate over the `fields`. + # This can be a disgusting temporary measure :( + data = DataStructure() + for model in self.models: + model.data = data + # setup handlers for writing to terminal (on warnings+) # or writing to the log file (on info+) diff --git a/process/models/water_use.py b/process/models/water_use.py index c6511d75e8..c2e669b683 100644 --- a/process/models/water_use.py +++ b/process/models/water_use.py @@ -2,16 +2,20 @@ from process.core import constants from process.core import process_output as po -from process.data_structure import heat_transport_variables, water_usage_variables +from process.core.model import Model +from process.data_structure import heat_transport_variables SECDAY = 86400e0 -class WaterUse: +class WaterUse(Model): def __init__(self): self.outfile = constants.NOUT - def run(self, output: bool): + def output(self): + self.run(output=True) + + def run(self, output: bool = False): """Routine to call the water usage calculation routines. This routine calls the different water usage routines. @@ -58,33 +62,33 @@ def cooling_towers(self, wastetherm: float, output: bool): output: """ - water_usage_variables.evapratio = 1.0e0 - ( + self.data.water_use.evapratio = 1.0e0 - ( ( - -0.000279e0 * water_usage_variables.airtemp**3 - + 0.00109e0 * water_usage_variables.airtemp**2 - - 0.345e0 * water_usage_variables.airtemp + -0.000279e0 * self.data.water_use.airtemp**3 + + 0.00109e0 * self.data.water_use.airtemp**2 + - 0.345e0 * self.data.water_use.airtemp + 26.7e0 ) / 100.0e0 ) # Diehl et al. USGS Report 2013-5188, http://dx.doi.org/10.3133/sir20135188 - water_usage_variables.volheat = ( - water_usage_variables.waterdens * water_usage_variables.latentheat + self.data.water_use.volheat = ( + self.data.water_use.waterdens * self.data.water_use.latentheat ) - water_usage_variables.energypervol = ( - water_usage_variables.volheat / water_usage_variables.evapratio + self.data.water_use.energypervol = ( + self.data.water_use.volheat / self.data.water_use.evapratio ) - water_usage_variables.volperenergy = ( - 1.0e0 / water_usage_variables.energypervol * 1000000.0e0 + self.data.water_use.volperenergy = ( + 1.0e0 / self.data.water_use.energypervol * 1000000.0e0 ) - water_usage_variables.evapvol = wastetherm * water_usage_variables.volperenergy + self.data.water_use.evapvol = wastetherm * self.data.water_use.volperenergy # find water withdrawn from external source - water_usage_variables.waterusetower = 1.4e0 * water_usage_variables.evapvol + self.data.water_use.waterusetower = 1.4e0 * self.data.water_use.evapvol # Estimated as a ratio to evaporated water (averaged across observed dataset) # as per Diehl et al. USGS Report 2014-5184, http://dx.doi.org/10.3133/sir20145184 @@ -96,7 +100,7 @@ def cooling_towers(self, wastetherm: float, output: bool): self.outfile, "Volume used in cooling tower (m3/day)", "(waterusetower)", - water_usage_variables.waterusetower, + self.data.water_use.waterusetower, "OP ", ) @@ -175,12 +179,10 @@ def cooling_water_body(self, wastetherm: float, output: bool): # Unfortunately, the source spreadsheet was from the US, so the fits for # water body heating due to heat loading and the cooling wind functions # are in non-metric units, hence the conversions required here. - # Limitations: maximum wind speed of ~5 m/s; initial water_usage_variables.watertemp < 25 degC + # Limitations: maximum wind speed of ~5 m/s; initial self.data.water_use.watertemp < 25 degC - # convert water_usage_variables.windspeed to mph - water_usage_variables.windspeedmph = ( - water_usage_variables.windspeed * 2.237e0 - ) + # convert self.data.water_use.windspeed to mph + self.data.water_use.windspeedmph = self.data.water_use.windspeed * 2.237e0 # convert heat loading into cal/(cm2.sec) heatloadimp = heatload * 1000000.0e0 * 0.239e0 / 40469000.0e0 @@ -188,45 +190,45 @@ def cooling_water_body(self, wastetherm: float, output: bool): # estimate how heat loading will raise temperature, for this water body heatratio = ( d - + (e * water_usage_variables.watertemp) - + (f * water_usage_variables.windspeedmph) + + (e * self.data.water_use.watertemp) + + (f * self.data.water_use.windspeedmph) + (g * heatload) - + (h * water_usage_variables.watertemp**2) - + (i * water_usage_variables.windspeedmph**2) + + (h * self.data.water_use.watertemp**2) + + (i * self.data.water_use.windspeedmph**2) + (j * heatload**2) ) # estimate resultant heated water temperature - water_usage_variables.watertempheated = water_usage_variables.watertemp + ( + self.data.water_use.watertempheated = self.data.water_use.watertemp + ( heatloadimp * heatratio ) # find wind function, m/(day.kPa), applicable to this water body: windfunction = ( a - + (b * water_usage_variables.windspeed) - + (c * water_usage_variables.windspeed**2) + + (b * self.data.water_use.windspeed) + + (c * self.data.water_use.windspeed**2) ) / 1000.0e0 # difference in saturation vapour pressure (Clausius-Clapeyron approximation) satvapdelta = ( 0.611e0 * np.exp( - (17.27e0 * water_usage_variables.watertempheated) - / (237.3e0 + water_usage_variables.watertempheated) + (17.27e0 * self.data.water_use.watertempheated) + / (237.3e0 + self.data.water_use.watertempheated) ) ) - ( 0.611e0 * np.exp( - (17.27e0 * water_usage_variables.watertemp) - / (237.3e0 + water_usage_variables.watertemp) + (17.27e0 * self.data.water_use.watertemp) + / (237.3e0 + self.data.water_use.watertemp) ) ) # find 'forced evaporation' driven by heat inserted into system deltae = ( - water_usage_variables.waterdens - * water_usage_variables.latentheat + self.data.water_use.waterdens + * self.data.water_use.latentheat * windfunction * satvapdelta ) @@ -236,28 +238,26 @@ def cooling_water_body(self, wastetherm: float, output: bool): # find evaporation ratio: ratio of the heat used to evaporate water # to the total heat discharged through the tower - water_usage_variables.evapratio = deltae / heatloadmet + self.data.water_use.evapratio = deltae / heatloadmet # Diehl et al. USGS Report 2013-5188, http://dx.doi.org/10.3133/sir20135188 - water_usage_variables.volheat = ( - water_usage_variables.waterdens * water_usage_variables.latentheat + self.data.water_use.volheat = ( + self.data.water_use.waterdens * self.data.water_use.latentheat ) - water_usage_variables.energypervol = ( - water_usage_variables.volheat / water_usage_variables.evapratio + self.data.water_use.energypervol = ( + self.data.water_use.volheat / self.data.water_use.evapratio ) - water_usage_variables.volperenergy = ( - 1.0e0 / water_usage_variables.energypervol * 1000000.0e0 + self.data.water_use.volperenergy = ( + 1.0e0 / self.data.water_use.energypervol * 1000000.0e0 ) - water_usage_variables.evapvol = ( - wastetherm * water_usage_variables.volperenergy - ) + self.data.water_use.evapvol = wastetherm * self.data.water_use.volperenergy # using this method the estimates for pond, lake and river evaporation produce similar results, # the average will be taken and used in the next stage of calculation - evapsum = evapsum + water_usage_variables.evapvol + evapsum = evapsum + self.data.water_use.evapvol evapsum = evapsum / icool @@ -266,10 +266,10 @@ def cooling_water_body(self, wastetherm: float, output: bool): # as per Diehl et al. USGS Report 2014-5184, http://dx.doi.org/10.3133/sir20145184 # recirculating water system: - water_usage_variables.wateruserecirc = 1.0e0 * evapsum + self.data.water_use.wateruserecirc = 1.0e0 * evapsum # once-through water system: - water_usage_variables.wateruseonethru = 98.0e0 * evapsum + self.data.water_use.wateruseonethru = 98.0e0 * evapsum # end break @@ -279,13 +279,13 @@ def cooling_water_body(self, wastetherm: float, output: bool): self.outfile, "Volume used in recirculating water system (m3/day)", "(wateruserecirc)", - water_usage_variables.wateruserecirc, + self.data.water_use.wateruserecirc, "OP ", ) po.ovarre( self.outfile, "Volume used in once-through water system (m3/day)", "(wateruseonethru)", - water_usage_variables.wateruseonethru, + self.data.water_use.wateruseonethru, "OP ", ) diff --git a/tests/conftest.py b/tests/conftest.py index af219229b4..7bf03b584e 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -13,6 +13,8 @@ from process import main from process.core.log import logging_model_handler +from process.core.model import DataStructure +from process.main import Models def pytest_addoption(parser): @@ -230,3 +232,13 @@ def _plot_show_and_close_class(request): plt.close() else: yield + + +@pytest.fixture +def process_data_structure(): + return DataStructure() + + +@pytest.fixture +def process_models(): + return Models() diff --git a/tests/unit/test_water_usage.py b/tests/unit/test_water_usage.py index bb28a18c93..55a9cae221 100644 --- a/tests/unit/test_water_usage.py +++ b/tests/unit/test_water_usage.py @@ -2,18 +2,15 @@ import pytest -from process.data_structure import water_usage_variables -from process.models.water_use import WaterUse - @pytest.fixture -def water_use(): +def water_use(process_models): """Provides WaterUse object for testing. :return water_use: initialised WaterUse object :type water_use: process.water_use.WaterUse """ - return WaterUse() + return process_models.water_use class CoolingTowersParam(NamedTuple): @@ -83,42 +80,46 @@ class CoolingTowersParam(NamedTuple): ), ) def test_cooling_towers(coolingtowersparam, monkeypatch, water_use): - monkeypatch.setattr(water_usage_variables, "airtemp", coolingtowersparam.airtemp) - monkeypatch.setattr(water_usage_variables, "waterdens", coolingtowersparam.waterdens) + monkeypatch.setattr(water_use.data.water_use, "airtemp", coolingtowersparam.airtemp) + monkeypatch.setattr( + water_use.data.water_use, "waterdens", coolingtowersparam.waterdens + ) + monkeypatch.setattr( + water_use.data.water_use, "latentheat", coolingtowersparam.latentheat + ) + monkeypatch.setattr(water_use.data.water_use, "volheat", coolingtowersparam.volheat) monkeypatch.setattr( - water_usage_variables, "latentheat", coolingtowersparam.latentheat + water_use.data.water_use, "evapratio", coolingtowersparam.evapratio ) - monkeypatch.setattr(water_usage_variables, "volheat", coolingtowersparam.volheat) - monkeypatch.setattr(water_usage_variables, "evapratio", coolingtowersparam.evapratio) - monkeypatch.setattr(water_usage_variables, "evapvol", coolingtowersparam.evapvol) + monkeypatch.setattr(water_use.data.water_use, "evapvol", coolingtowersparam.evapvol) monkeypatch.setattr( - water_usage_variables, "energypervol", coolingtowersparam.energypervol + water_use.data.water_use, "energypervol", coolingtowersparam.energypervol ) monkeypatch.setattr( - water_usage_variables, "volperenergy", coolingtowersparam.volperenergy + water_use.data.water_use, "volperenergy", coolingtowersparam.volperenergy ) monkeypatch.setattr( - water_usage_variables, "waterusetower", coolingtowersparam.waterusetower + water_use.data.water_use, "waterusetower", coolingtowersparam.waterusetower ) water_use.cooling_towers(wastetherm=coolingtowersparam.wastetherm, output=False) - assert water_usage_variables.volheat == pytest.approx( + assert water_use.data.water_use.volheat == pytest.approx( coolingtowersparam.expected_volheat ) - assert water_usage_variables.evapratio == pytest.approx( + assert water_use.data.water_use.evapratio == pytest.approx( coolingtowersparam.expected_evapratio ) - assert water_usage_variables.evapvol == pytest.approx( + assert water_use.data.water_use.evapvol == pytest.approx( coolingtowersparam.expected_evapvol ) - assert water_usage_variables.energypervol == pytest.approx( + assert water_use.data.water_use.energypervol == pytest.approx( coolingtowersparam.expected_energypervol ) - assert water_usage_variables.volperenergy == pytest.approx( + assert water_use.data.water_use.volperenergy == pytest.approx( coolingtowersparam.expected_volperenergy ) - assert water_usage_variables.waterusetower == pytest.approx( + assert water_use.data.water_use.waterusetower == pytest.approx( coolingtowersparam.expected_waterusetower ) @@ -197,54 +198,60 @@ class CoolingWaterBodyParam(NamedTuple): ) def test_cooling_water_body(coolingwaterbodyparam, monkeypatch, water_use): monkeypatch.setattr( - water_usage_variables, "watertemp", coolingwaterbodyparam.watertemp + water_use.data.water_use, "watertemp", coolingwaterbodyparam.watertemp + ) + monkeypatch.setattr( + water_use.data.water_use, "windspeed", coolingwaterbodyparam.windspeed + ) + monkeypatch.setattr( + water_use.data.water_use, "waterdens", coolingwaterbodyparam.waterdens ) monkeypatch.setattr( - water_usage_variables, "windspeed", coolingwaterbodyparam.windspeed + water_use.data.water_use, "latentheat", coolingwaterbodyparam.latentheat ) monkeypatch.setattr( - water_usage_variables, "waterdens", coolingwaterbodyparam.waterdens + water_use.data.water_use, "volheat", coolingwaterbodyparam.volheat ) monkeypatch.setattr( - water_usage_variables, "latentheat", coolingwaterbodyparam.latentheat + water_use.data.water_use, "evapratio", coolingwaterbodyparam.evapratio ) - monkeypatch.setattr(water_usage_variables, "volheat", coolingwaterbodyparam.volheat) monkeypatch.setattr( - water_usage_variables, "evapratio", coolingwaterbodyparam.evapratio + water_use.data.water_use, "evapvol", coolingwaterbodyparam.evapvol ) - monkeypatch.setattr(water_usage_variables, "evapvol", coolingwaterbodyparam.evapvol) monkeypatch.setattr( - water_usage_variables, "energypervol", coolingwaterbodyparam.energypervol + water_use.data.water_use, "energypervol", coolingwaterbodyparam.energypervol ) monkeypatch.setattr( - water_usage_variables, "volperenergy", coolingwaterbodyparam.volperenergy + water_use.data.water_use, "volperenergy", coolingwaterbodyparam.volperenergy ) monkeypatch.setattr( - water_usage_variables, "wateruserecirc", coolingwaterbodyparam.wateruserecirc + water_use.data.water_use, "wateruserecirc", coolingwaterbodyparam.wateruserecirc ) monkeypatch.setattr( - water_usage_variables, "wateruseonethru", coolingwaterbodyparam.wateruseonethru + water_use.data.water_use, + "wateruseonethru", + coolingwaterbodyparam.wateruseonethru, ) water_use.cooling_water_body( wastetherm=coolingwaterbodyparam.wastetherm, output=False ) - assert water_usage_variables.evapratio == pytest.approx( + assert water_use.data.water_use.evapratio == pytest.approx( coolingwaterbodyparam.expected_evapratio ) - assert water_usage_variables.evapvol == pytest.approx( + assert water_use.data.water_use.evapvol == pytest.approx( coolingwaterbodyparam.expected_evapvol ) - assert water_usage_variables.energypervol == pytest.approx( + assert water_use.data.water_use.energypervol == pytest.approx( coolingwaterbodyparam.expected_energypervol ) - assert water_usage_variables.volperenergy == pytest.approx( + assert water_use.data.water_use.volperenergy == pytest.approx( coolingwaterbodyparam.expected_volperenergy ) - assert water_usage_variables.wateruserecirc == pytest.approx( + assert water_use.data.water_use.wateruserecirc == pytest.approx( coolingwaterbodyparam.expected_wateruserecirc ) - assert water_usage_variables.wateruseonethru == pytest.approx( + assert water_use.data.water_use.wateruseonethru == pytest.approx( coolingwaterbodyparam.expected_wateruseonethru ) From fb755c43bccefcde1d3c7e7efaff0650fe0db77b Mon Sep 17 00:00:00 2001 From: Timothy Nunn Date: Wed, 4 Mar 2026 16:06:09 +0000 Subject: [PATCH 2/4] Remove unused fixture --- tests/conftest.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 7bf03b584e..1123481eaf 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -13,7 +13,6 @@ from process import main from process.core.log import logging_model_handler -from process.core.model import DataStructure from process.main import Models @@ -234,11 +233,6 @@ def _plot_show_and_close_class(request): yield -@pytest.fixture -def process_data_structure(): - return DataStructure() - - @pytest.fixture def process_models(): return Models() From 0c622f08c15fba978d305418ddfd0e80c7635841 Mon Sep 17 00:00:00 2001 From: Timothy Nunn Date: Wed, 4 Mar 2026 17:27:30 +0000 Subject: [PATCH 3/4] Add temporary way for dicts to get initial values from global and dataclass data structure --- process/core/io/data_structure_dicts.py | 10 +++++++++- process/data_structure/water_usage_variables.py | 8 ++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/process/core/io/data_structure_dicts.py b/process/core/io/data_structure_dicts.py index 0baa23d447..8dfa29e4c6 100644 --- a/process/core/io/data_structure_dicts.py +++ b/process/core/io/data_structure_dicts.py @@ -403,6 +403,14 @@ def get_dicts(): dict_module_entry = {} variable_types = {} + # Check whether to get the initial value from the global data structure + # or some dataclass + object_containing_initial_values = ( + module + if not hasattr(module, "CREATE_DICTS_FROM_DATACLASS") + else module.CREATE_DICTS_FROM_DATACLASS() + ) + # get the variable names and initial values for node in ast.walk(module_tree): if isinstance(node, ast.AnnAssign): @@ -410,7 +418,7 @@ def get_dicts(): # (either is None, or value initialised in init_variables fn) # set default to be None if variable is not being initialised eg if you # just have `example_double: float` instead of `example_double: float = None` - initial_value = getattr(module, node.target.id) + initial_value = getattr(object_containing_initial_values, node.target.id) # JSON doesn't like np arrays if type(initial_value) is np.ndarray: initial_value = initial_value.tolist() diff --git a/process/data_structure/water_usage_variables.py b/process/data_structure/water_usage_variables.py index c3bfbe976e..a555412776 100644 --- a/process/data_structure/water_usage_variables.py +++ b/process/data_structure/water_usage_variables.py @@ -49,3 +49,11 @@ class WaterUseData: wateruseonethru: float = 0.0 """total volume of water used in once-through system (m3)""" + + +# Another disgusting we may need to do in the transition period to support the dicts. +# Once all variables in the new data structure we can make the dicts from the DataStructure... +# and then in the long term put metadata on these classes and entierly remove the dicts +# In the meantime... the dicts will check each module for a '_CREATE_DICTS_FROM_DATACLASS' attribute +# and, if present, use this to create the dict ... +CREATE_DICTS_FROM_DATACLASS = WaterUseData From dd6d4b2d826ffb85fef3a5ed9148597f88f4c553 Mon Sep 17 00:00:00 2001 From: Timothy Nunn Date: Wed, 25 Mar 2026 10:20:56 +0000 Subject: [PATCH 4/4] Use dataclasses in water test --- tests/unit/test_water_usage.py | 174 +++++++++++++-------------------- 1 file changed, 70 insertions(+), 104 deletions(-) diff --git a/tests/unit/test_water_usage.py b/tests/unit/test_water_usage.py index 55a9cae221..11bbbc7853 100644 --- a/tests/unit/test_water_usage.py +++ b/tests/unit/test_water_usage.py @@ -1,4 +1,4 @@ -from typing import Any, NamedTuple +from dataclasses import dataclass import pytest @@ -13,25 +13,24 @@ def water_use(process_models): return process_models.water_use -class CoolingTowersParam(NamedTuple): - airtemp: Any - waterdens: Any - latentheat: Any - volheat: Any - evapratio: Any - evapvol: Any - energypervol: Any - volperenergy: Any - waterusetower: Any - outfile: Any - iprint: Any - wastetherm: Any - expected_volheat: Any - expected_evapratio: Any - expected_evapvol: Any - expected_energypervol: Any - expected_volperenergy: Any - expected_waterusetower: Any +@dataclass +class CoolingTowersParam: + airtemp: float + waterdens: float + latentheat: float + volheat: float + evapratio: float + evapvol: float + energypervol: float + volperenergy: float + waterusetower: float + wastetherm: float + expected_volheat: float + expected_evapratio: float + expected_evapvol: float + expected_energypervol: float + expected_volperenergy: float + expected_waterusetower: float @pytest.mark.parametrize( @@ -47,8 +46,6 @@ class CoolingTowersParam(NamedTuple): energypervol=0, volperenergy=0, waterusetower=0, - outfile=11, - iprint=0, wastetherm=141491977.80211401, expected_volheat=2252531140, expected_evapratio=0.79171374999999999, @@ -67,8 +64,6 @@ class CoolingTowersParam(NamedTuple): energypervol=6071017075.9073286, volperenergy=0.00016471704617146833, waterusetower=69623.722083984059, - outfile=11, - iprint=0, wastetherm=141448808.82309783, expected_volheat=2252531140, expected_evapratio=0.79171374999999999, @@ -80,27 +75,20 @@ class CoolingTowersParam(NamedTuple): ), ) def test_cooling_towers(coolingtowersparam, monkeypatch, water_use): - monkeypatch.setattr(water_use.data.water_use, "airtemp", coolingtowersparam.airtemp) - monkeypatch.setattr( - water_use.data.water_use, "waterdens", coolingtowersparam.waterdens - ) - monkeypatch.setattr( - water_use.data.water_use, "latentheat", coolingtowersparam.latentheat - ) - monkeypatch.setattr(water_use.data.water_use, "volheat", coolingtowersparam.volheat) - monkeypatch.setattr( - water_use.data.water_use, "evapratio", coolingtowersparam.evapratio - ) - monkeypatch.setattr(water_use.data.water_use, "evapvol", coolingtowersparam.evapvol) - monkeypatch.setattr( - water_use.data.water_use, "energypervol", coolingtowersparam.energypervol - ) - monkeypatch.setattr( - water_use.data.water_use, "volperenergy", coolingtowersparam.volperenergy - ) - monkeypatch.setattr( - water_use.data.water_use, "waterusetower", coolingtowersparam.waterusetower - ) + for field in [ + "airtemp", + "waterdens", + "latentheat", + "volheat", + "evapratio", + "evapvol", + "energypervol", + "volperenergy", + "waterusetower", + ]: + monkeypatch.setattr( + water_use.data.water_use, field, getattr(coolingtowersparam, field) + ) water_use.cooling_towers(wastetherm=coolingtowersparam.wastetherm, output=False) @@ -124,27 +112,26 @@ def test_cooling_towers(coolingtowersparam, monkeypatch, water_use): ) -class CoolingWaterBodyParam(NamedTuple): - watertemp: Any - windspeed: Any - waterdens: Any - latentheat: Any - volheat: Any - evapratio: Any - evapvol: Any - energypervol: Any - volperenergy: Any - wateruserecirc: Any - wateruseonethru: Any - outfile: Any - iprint: Any - wastetherm: Any - expected_evapratio: Any - expected_evapvol: Any - expected_energypervol: Any - expected_volperenergy: Any - expected_wateruserecirc: Any - expected_wateruseonethru: Any +@dataclass +class CoolingWaterBodyParam: + watertemp: float + windspeed: float + waterdens: float + latentheat: float + volheat: float + evapratio: float + evapvol: float + energypervol: float + volperenergy: float + wateruserecirc: float + wateruseonethru: float + wastetherm: float + expected_evapratio: float + expected_evapvol: float + expected_energypervol: float + expected_volperenergy: float + expected_wateruserecirc: float + expected_wateruseonethru: float @pytest.mark.parametrize( @@ -162,8 +149,6 @@ class CoolingWaterBodyParam(NamedTuple): volperenergy=0.000351477382905348, wateruserecirc=0, wateruseonethru=0, - outfile=11, - iprint=0, wastetherm=141491977.80211401, expected_evapratio=0.37103027579005016, expected_evapvol=23306.140640523186, @@ -184,8 +169,6 @@ class CoolingWaterBodyParam(NamedTuple): volperenergy=0.000351477382905348, wateruserecirc=23306.140640523186, wateruseonethru=2284001.7827712721, - outfile=11, - iprint=0, wastetherm=141448808.82309783, expected_evapratio=0.37103027579005016, expected_evapvol=23299.029973813402, @@ -197,41 +180,24 @@ class CoolingWaterBodyParam(NamedTuple): ), ) def test_cooling_water_body(coolingwaterbodyparam, monkeypatch, water_use): - monkeypatch.setattr( - water_use.data.water_use, "watertemp", coolingwaterbodyparam.watertemp - ) - monkeypatch.setattr( - water_use.data.water_use, "windspeed", coolingwaterbodyparam.windspeed - ) - monkeypatch.setattr( - water_use.data.water_use, "waterdens", coolingwaterbodyparam.waterdens - ) - monkeypatch.setattr( - water_use.data.water_use, "latentheat", coolingwaterbodyparam.latentheat - ) - monkeypatch.setattr( - water_use.data.water_use, "volheat", coolingwaterbodyparam.volheat - ) - monkeypatch.setattr( - water_use.data.water_use, "evapratio", coolingwaterbodyparam.evapratio - ) - monkeypatch.setattr( - water_use.data.water_use, "evapvol", coolingwaterbodyparam.evapvol - ) - monkeypatch.setattr( - water_use.data.water_use, "energypervol", coolingwaterbodyparam.energypervol - ) - monkeypatch.setattr( - water_use.data.water_use, "volperenergy", coolingwaterbodyparam.volperenergy - ) - monkeypatch.setattr( - water_use.data.water_use, "wateruserecirc", coolingwaterbodyparam.wateruserecirc - ) - monkeypatch.setattr( - water_use.data.water_use, + for field in [ + "watertemp", + "windspeed", + "waterdens", + "latentheat", + "volheat", + "evapratio", + "energypervol", + "energypervol", + "volperenergy", + "wateruserecirc", "wateruseonethru", - coolingwaterbodyparam.wateruseonethru, - ) + ]: + monkeypatch.setattr( + water_use.data.water_use, + field, + getattr(coolingwaterbodyparam, field), + ) water_use.cooling_water_body( wastetherm=coolingwaterbodyparam.wastetherm, output=False