From 141ebddde0095580dd40039e61075b274d95369c Mon Sep 17 00:00:00 2001 From: Edwin Lee Date: Mon, 15 Apr 2024 09:34:23 -0500 Subject: [PATCH 1/8] Continued interpolation cleanup, fixed up setup.py, added cli entry point --- glhe/profiles/external_base.py | 7 +- glhe/properties/fluid_properties.py | 27 ++++---- {standalone => glhe/standalone}/__init__.py | 0 {standalone => glhe/standalone}/plant_loop.py | 6 +- .../single_u_tube_grouted_borehole.py | 31 ++++----- .../topology/single_u_tube_grouted_segment.py | 68 +++++++++++-------- .../single_u_tube_pass_through_segment.py | 5 +- glhe/utilities/functions.py | 2 +- requirements.txt | 19 ++++-- setup.py | 16 ++++- .../test_single_u_tube_grouted_segment.py | 11 ++- unit_tests/standalone/test_plant_loop.py | 2 +- 12 files changed, 115 insertions(+), 79 deletions(-) rename {standalone => glhe/standalone}/__init__.py (100%) rename {standalone => glhe/standalone}/plant_loop.py (99%) diff --git a/glhe/profiles/external_base.py b/glhe/profiles/external_base.py index 195d7409..26a04494 100644 --- a/glhe/profiles/external_base.py +++ b/glhe/profiles/external_base.py @@ -1,5 +1,6 @@ import pandas as pd -from scipy.interpolate import interp1d + +from glhe.utilities.functions import Interpolator1D class ExternalBase(object): @@ -18,7 +19,7 @@ def __init__(self, path, col_num): y_range.append(y_range[0]) self.max_time = x_range[-1] - self._interp_values = interp1d(x_range, y_range) + self._interp_values = Interpolator1D(x_range, y_range) def get_value(self, time) -> float: - return float(self._interp_values(time % self.max_time)) + return float(self._interp_values.interpolate(time % self.max_time)) diff --git a/glhe/properties/fluid_properties.py b/glhe/properties/fluid_properties.py index 3222ffa9..dadcf92e 100644 --- a/glhe/properties/fluid_properties.py +++ b/glhe/properties/fluid_properties.py @@ -1,10 +1,9 @@ from CoolProp.CoolProp import PropsSI from numpy import arange -from scipy.interpolate import interp1d from glhe.properties.fluid_property_types import FluidPropertyType from glhe.properties.fluid_types import FluidType -from glhe.utilities.functions import c_to_k, k_to_c +from glhe.utilities.functions import c_to_k, k_to_c, Interpolator1D class Fluid: @@ -39,12 +38,12 @@ def __init__(self, inputs: dict): rho_vals = [self.calc_density(x) for x in temps] rho_cp_vals = [self.calc_vol_heat_capacity(x) for x in temps] - self.cp_interp = interp1d(temps, cp_vals) - self.k_interp = interp1d(temps, k_vals) - self.mu_interp = interp1d(temps, mu_vals) - self.pr_interp = interp1d(temps, pr_vals) - self.rho_interp = interp1d(temps, rho_vals) - self.rho_cp_interp = interp1d(temps, rho_cp_vals) + self.cp_interp = Interpolator1D(temps, cp_vals) + self.k_interp = Interpolator1D(temps, k_vals) + self.mu_interp = Interpolator1D(temps, mu_vals) + self.pr_interp = Interpolator1D(temps, pr_vals) + self.rho_interp = Interpolator1D(temps, rho_vals) + self.rho_cp_interp = Interpolator1D(temps, rho_cp_vals) @staticmethod def get_fluid_str(fluid_enum, concentration: int | float) -> str: @@ -99,7 +98,7 @@ def get_cp(self, temperature: int | float) -> float: :returns fluid specific heat in [J/kg-K] """ - return float(self.cp_interp(temperature)) + return self.cp_interp.interpolate(temperature) def get_k(self, temperature: int | float) -> float: """ @@ -109,7 +108,7 @@ def get_k(self, temperature: int | float) -> float: :return: fluid conductivity in [W/m-K] """ - return float(self.k_interp(temperature)) + return self.k_interp.interpolate(temperature) def get_mu(self, temperature: int | float) -> float: """ @@ -119,7 +118,7 @@ def get_mu(self, temperature: int | float) -> float: :return: fluid viscosity in [Pa-s] """ - return float(self.mu_interp(temperature)) + return self.mu_interp.interpolate(temperature) def get_pr(self, temperature: int | float) -> float: """ @@ -129,7 +128,7 @@ def get_pr(self, temperature: int | float) -> float: :return: fluid Prandtl number """ - return float(self.pr_interp(temperature)) + return self.pr_interp.interpolate(temperature) def get_rho(self, temperature: int | float) -> float: """ @@ -139,7 +138,7 @@ def get_rho(self, temperature: int | float) -> float: :return: fluid density in [kg/m^3] """ - return float(self.rho_interp(temperature)) + return self.rho_interp.interpolate(temperature) def get_rho_cp(self, temperature: int | float) -> float: """ @@ -149,7 +148,7 @@ def get_rho_cp(self, temperature: int | float) -> float: :return: fluid volume-specific heat capacity in [J/m3-K] """ - return float(self.rho_cp_interp(temperature)) + return self.rho_cp_interp.interpolate(temperature) def calc_conductivity(self, temperature: int | float) -> float: """ diff --git a/standalone/__init__.py b/glhe/standalone/__init__.py similarity index 100% rename from standalone/__init__.py rename to glhe/standalone/__init__.py diff --git a/standalone/plant_loop.py b/glhe/standalone/plant_loop.py similarity index 99% rename from standalone/plant_loop.py rename to glhe/standalone/plant_loop.py index 842d4140..715ffa47 100644 --- a/standalone/plant_loop.py +++ b/glhe/standalone/plant_loop.py @@ -148,5 +148,9 @@ def collect_outputs(self, sim_time): self.op.collect_output(d) -if __name__ == "__main__": +def main(): PlantLoop(sys.argv[1]).simulate() + + +if __name__ == "__main__": + main() diff --git a/glhe/topology/single_u_tube_grouted_borehole.py b/glhe/topology/single_u_tube_grouted_borehole.py index cd628185..57719cfe 100644 --- a/glhe/topology/single_u_tube_grouted_borehole.py +++ b/glhe/topology/single_u_tube_grouted_borehole.py @@ -6,7 +6,7 @@ from glhe.output_processor.report_types import ReportTypes from glhe.properties.base_properties import PropertiesBase from glhe.topology.pipe import Pipe -from glhe.topology.single_u_tube_grouted_segment import SingleUTubeGroutedSegment +from glhe.topology.single_u_tube_grouted_segment import SingleUTubeGroutedSegment, TimeStepStructure from glhe.topology.single_u_tube_pass_through_segment import SingleUTubePassThroughSegment from glhe.utilities.functions import merge_dicts @@ -313,10 +313,7 @@ def simulate_time_step(self, inputs: SimulationResponse) -> SimulationResponse: r_12, r_b = self.calc_direct_coupling_resistance(inlet_temp, flow_rate=flow_rate) - seg_inputs = {'boundary-temperature': bh_wall_temp, - 'rb': r_b, - 'flow-rate': flow_rate, - 'dc-resist': r_12} + seg_inputs = TimeStepStructure(boundary_temp=bh_wall_temp, bh_resist=r_b, flow_rate=flow_rate, dc_resist=r_12) self.pipe_1.simulate_time_step(SimulationResponse(time, time_step, flow_rate, inlet_temp)) @@ -325,13 +322,13 @@ def simulate_time_step(self, inputs: SimulationResponse) -> SimulationResponse: for idx, seg in enumerate(self.segments): if idx == 0: - seg_inputs['inlet-1-temp'] = self.pipe_1.outlet_temperature - seg_inputs['inlet-2-temp'] = self.segments[idx + 1].get_outlet_2_temp() + seg_inputs.inlet_temp_1 = self.pipe_1.outlet_temperature + seg_inputs.inlet_temp_2 = self.segments[idx + 1].get_outlet_2_temp() elif idx == self.num_segments: - seg_inputs['inlet-1-temp'] = self.segments[idx - 1].get_outlet_1_temp() + seg_inputs.inlet_temp_1 = self.segments[idx - 1].get_outlet_1_temp() else: - seg_inputs['inlet-1-temp'] = self.segments[idx - 1].get_outlet_1_temp() - seg_inputs['inlet-2-temp'] = self.segments[idx + 1].get_outlet_2_temp() + seg_inputs.inlet_temp_1 = self.segments[idx - 1].get_outlet_1_temp() + seg_inputs.inlet_temp_2 = self.segments[idx + 1].get_outlet_2_temp() seg.simulate_time_step(time_step, seg_inputs) @@ -363,12 +360,12 @@ def report_outputs(self) -> dict: d = merge_dicts(d, self.pipe_1.report_outputs()) - d_self = {'{:s}:{:s}:{:s}'.format(self.Type, self.name, ReportTypes.HeatRate): self.heat_rate, - '{:s}:{:s}:{:s}'.format(self.Type, self.name, ReportTypes.HeatRateBH): self.heat_rate_bh, - '{:s}:{:s}:{:s}'.format(self.Type, self.name, ReportTypes.InletTemp): self.inlet_temperature, - '{:s}:{:s}:{:s}'.format(self.Type, self.name, ReportTypes.OutletTemp): self.outlet_temperature, - '{:s}:{:s}:{:s}'.format(self.Type, self.name, ReportTypes.BHResist): self.resist_bh_ave, - '{:s}:{:s}:{:s}'.format(self.Type, self.name, ReportTypes.BHIntResist): self.resist_bh_total_internal, - '{:s}:{:s}:{:s}'.format(self.Type, self.name, ReportTypes.BHDCResist): self.resist_bh_direct_coupling} + d_self = {f"{self.Type}:{self.name}:{ReportTypes.HeatRate}": self.heat_rate, + f"{self.Type}:{self.name}:{ReportTypes.HeatRateBH}": self.heat_rate_bh, + f"{self.Type}:{self.name}:{ReportTypes.InletTemp}": self.inlet_temperature, + f"{self.Type}:{self.name}:{ReportTypes.OutletTemp}": self.outlet_temperature, + f"{self.Type}:{self.name}:{ReportTypes.BHResist}": self.resist_bh_ave, + f"{self.Type}:{self.name}:{ReportTypes.BHIntResist}": self.resist_bh_total_internal, + f"{self.Type}:{self.name}:{ReportTypes.BHDCResist}": self.resist_bh_direct_coupling} return merge_dicts(d, d_self) diff --git a/glhe/topology/single_u_tube_grouted_segment.py b/glhe/topology/single_u_tube_grouted_segment.py index 77557991..497497e3 100644 --- a/glhe/topology/single_u_tube_grouted_segment.py +++ b/glhe/topology/single_u_tube_grouted_segment.py @@ -1,7 +1,8 @@ +from dataclasses import dataclass from math import pi import numpy as np -from scipy.integrate import solve_ivp +from scipy.integrate import RK45 from glhe.input_processor.component_types import ComponentTypes from glhe.output_processor.report_types import ReportTypes @@ -9,21 +10,26 @@ from glhe.topology.pipe import Pipe -class SingleUTubeGroutedSegment(object): +@dataclass +class TimeStepStructure: + flow_rate: float = 0.0 + inlet_temp_1: float = 0.0 + inlet_temp_2: float = 0.0 + boundary_temp: float = 0.0 + bh_resist: float = 0.0 + dc_resist: float = 0.0 + + +class SingleUTubeGroutedSegment: Type = ComponentTypes.SegmentSingleUTubeGrouted def __init__(self, inputs, ip, op): self.name = inputs['segment-name'] - self.ip = ip - self.op = op - self.fluid = ip.props_mgr.fluid self.soil = ip.props_mgr.soil if 'average-pipe' in inputs: - - pipe_inputs = {'average-pipe': inputs['average-pipe'], - 'length': inputs['length']} + pipe_inputs = {'average-pipe': inputs['average-pipe'], 'length': inputs['length']} else: pipe_inputs = {'pipe-def-name': inputs['pipe-def-name'], 'length': inputs['length']} @@ -79,8 +85,7 @@ def calc_seg_volume(self): return pi / 4 * self.diameter ** 2 * self.length def right_hand_side(self, _, y): - num_equations = self.num_equations - r = np.zeros(num_equations) + r = np.zeros(self.num_equations) dz = self.length t_b = self.boundary_temp @@ -135,18 +140,27 @@ def get_outlet_1_temp(self): def get_outlet_2_temp(self): return self.y[1] - def simulate_time_step(self, time_step: int, inputs: dict) -> np.ndarray: - self.flow_rate = inputs['flow-rate'] - self.inlet_temp_1 = inputs['inlet-1-temp'] - self.inlet_temp_2 = inputs['inlet-2-temp'] - self.boundary_temp = inputs['boundary-temperature'] - self.bh_resist = inputs['rb'] - self.dc_resist = inputs['dc-resist'] - self.fluid_cp = self.fluid.get_cp(inputs['inlet-1-temp']) - self.fluid_heat_capacity = self.fluid.get_rho(inputs['inlet-1-temp']) * self.fluid_cp - - ret = solve_ivp(self.right_hand_side, [0, time_step], self.y) - self.y = ret.y[:, -1] + def simulate_time_step(self, time_step: float, inputs: TimeStepStructure) -> np.ndarray: + """ + Simulate a single time step for this segment. Solves the equations simultaneously using + RK45 method. + Parameters + :param time_step: float Time step in seconds? + :param inputs: TimeStepStructure of data to begin this time step. + """ + self.flow_rate = inputs.flow_rate + self.inlet_temp_1 = inputs.inlet_temp_1 + self.inlet_temp_2 = inputs.inlet_temp_2 + self.boundary_temp = inputs.boundary_temp + self.bh_resist = inputs.bh_resist + self.dc_resist = inputs.dc_resist + self.fluid_cp = self.fluid.get_cp(self.inlet_temp_1) + self.fluid_heat_capacity = self.fluid.get_rho(self.inlet_temp_1) * self.fluid_cp + + solver = RK45(self.right_hand_side, 0, self.y, time_step) + while solver.status != 'finished': + solver.step() + self.y = solver.y # update report vars self.heat_rate_bh = self.get_heat_rate_bh() @@ -155,8 +169,8 @@ def simulate_time_step(self, time_step: int, inputs: dict) -> np.ndarray: return self.y def report_outputs(self) -> dict: - return {'{:s}:{:s}:{:s}'.format(self.Type, self.name, ReportTypes.InletTemp_Leg1): self.inlet_temp_1, - '{:s}:{:s}:{:s}'.format(self.Type, self.name, ReportTypes.OutletTemp_Leg1): self.outlet_temp_1, - '{:s}:{:s}:{:s}'.format(self.Type, self.name, ReportTypes.InletTemp_Leg2): self.inlet_temp_2, - '{:s}:{:s}:{:s}'.format(self.Type, self.name, ReportTypes.OutletTemp_Leg2): self.outlet_temp_2, - '{:s}:{:s}:{:s}'.format(self.Type, self.name, ReportTypes.HeatRateBH): self.heat_rate_bh} + return {f"{self.Type}:{self.name}:{ReportTypes.InletTemp_Leg1}": self.inlet_temp_1, + f"{self.Type}:{self.name}:{ReportTypes.OutletTemp_Leg1}": self.outlet_temp_1, + f"{self.Type}:{self.name}:{ReportTypes.InletTemp_Leg2}": self.inlet_temp_2, + f"{self.Type}:{self.name}:{ReportTypes.OutletTemp_Leg2}": self.outlet_temp_2, + f"{self.Type}:{self.name}:{ReportTypes.HeatRateBH}": self.heat_rate_bh} diff --git a/glhe/topology/single_u_tube_pass_through_segment.py b/glhe/topology/single_u_tube_pass_through_segment.py index fc10ffdc..a06b0954 100644 --- a/glhe/topology/single_u_tube_pass_through_segment.py +++ b/glhe/topology/single_u_tube_pass_through_segment.py @@ -1,5 +1,6 @@ from glhe.input_processor.component_types import ComponentTypes from glhe.output_processor.report_types import ReportTypes +from glhe.topology.single_u_tube_grouted_segment import TimeStepStructure class SingleUTubePassThroughSegment(object): @@ -16,8 +17,8 @@ def __init__(self, inputs, ip, op): def get_outlet_2_temp(self): return self.temperature - def simulate_time_step(self, _, inputs: dict): - self.temperature = inputs['inlet-1-temp'] + def simulate_time_step(self, _, inputs: TimeStepStructure): + self.temperature = inputs.inlet_temp_1 def report_outputs(self) -> dict: return {'{:s}:{:s}:{:s}'.format(self.Type, self.name, ReportTypes.OutletTemp): self.temperature} diff --git a/glhe/utilities/functions.py b/glhe/utilities/functions.py index f48c29a1..d3e9c9d7 100644 --- a/glhe/utilities/functions.py +++ b/glhe/utilities/functions.py @@ -482,7 +482,7 @@ def write_arrays_to_csv(path: str, arrays: list | np.ndarray) -> None: class Interpolator1D: - def __init__(self, x_data: np.ndarray, y_data: np.ndarray): # TODO: Try to use pathlib internally everywhere + def __init__(self, x_data: np.ndarray, y_data: np.ndarray | list[float]): # TODO: Use pathlib internally everywhere """ 1D Interpolation Class, currently a wrapper for scipy interpolator, but soon just a simple interpolator diff --git a/requirements.txt b/requirements.txt index e6457273..f7f28901 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,14 +1,23 @@ +# Core library dependencies CoolProp -coverage -coveralls jsonschema -matplotlib numpy pandas pygfunction scipy + +# Dependencies needed to build wheels for PyPi +wheel + +# Dependencies for building documentation (for RTD) Sphinx +sphinx-rtd-theme + +# Dependencies needed to assist in running linting, unit tests and coverage pytest flake8 -wheel -sphinx-rtd-theme +coverage +coveralls + +# Dependencies needed for running the extra validation studies and such +matplotlib diff --git a/setup.py b/setup.py index 7e4a8438..3cf49f17 100755 --- a/setup.py +++ b/setup.py @@ -11,9 +11,23 @@ version='0.1', author="Matt Mitchell", author_email="mitchute@gmail.com", - packages=['glhe'], + packages=[ + 'glhe', + 'glhe.aggregation', + 'glhe.g_function', + 'glhe.ground_temps', + 'glhe.input_processor', + 'glhe.interface', + 'glhe.output_processor', + 'glhe.profiles', + 'glhe.properties', + 'glhe.standalone', + 'glhe.topology', + 'glhe.utilities', + ], license='MIT', install_requires=requirements, long_description=open('README.md').read(), long_description_content_type='text/markdown', + entry_points={'console_scripts': ['glhe=glhe.standalone.plant_loop:main',]} ) diff --git a/unit_tests/glhe/topology/test_single_u_tube_grouted_segment.py b/unit_tests/glhe/topology/test_single_u_tube_grouted_segment.py index 369fb1a3..ce158a32 100644 --- a/unit_tests/glhe/topology/test_single_u_tube_grouted_segment.py +++ b/unit_tests/glhe/topology/test_single_u_tube_grouted_segment.py @@ -4,7 +4,7 @@ from glhe.input_processor.input_processor import InputProcessor from glhe.output_processor.output_processor import OutputProcessor -from glhe.topology.single_u_tube_grouted_segment import SingleUTubeGroutedSegment +from glhe.topology.single_u_tube_grouted_segment import SingleUTubeGroutedSegment, TimeStepStructure from glhe.utilities.functions import write_json @@ -62,12 +62,9 @@ def test_volume(self): def test_simulate_time_step(self): tst = self.add_instance() - inputs = {'boundary-temperature': 20, - 'inlet-1-temp': 30, - 'inlet-2-temp': 25, - 'flow-rate': 0.2, - 'rb': 0.16, - 'dc-resist': 2.28} + inputs = TimeStepStructure( + flow_rate=0.2, inlet_temp_1=30.0, inlet_temp_2=25.0, boundary_temp=20.0, bh_resist=0.16, dc_resist=2.28 + ) ret_temps = tst.simulate_time_step(1, inputs) diff --git a/unit_tests/standalone/test_plant_loop.py b/unit_tests/standalone/test_plant_loop.py index 97a73aa4..6b8cad46 100644 --- a/unit_tests/standalone/test_plant_loop.py +++ b/unit_tests/standalone/test_plant_loop.py @@ -4,7 +4,7 @@ from glhe.utilities.functions import load_json from glhe.utilities.functions import write_json -from standalone.plant_loop import PlantLoop +from glhe.standalone.plant_loop import PlantLoop norm = os.path.normpath join = os.path.join From de819fa3681993f5f9e14b3937cfbe070fa6ed4e Mon Sep 17 00:00:00 2001 From: Edwin Lee Date: Mon, 15 Apr 2024 13:57:16 -0500 Subject: [PATCH 2/8] Use Path library all over the place --- glhe/aggregation/base_agg.py | 24 +++++------ glhe/aggregation/dynamic.py | 28 +++++-------- glhe/aggregation/no_agg.py | 1 + glhe/aggregation/static.py | 3 +- glhe/ground_temps/base.py | 4 +- glhe/input_processor/input_processor.py | 10 +++-- glhe/output_processor/output_processor.py | 13 +++--- glhe/profiles/external_base.py | 6 ++- glhe/standalone/plant_loop.py | 20 +++++---- glhe/utilities/functions.py | 40 ++++++++++-------- .../input_processor/test_input_processor.py | 26 +++++------- .../test_plant_loop_component_factory.py | 24 +++++------ .../output_processor/test_output_processor.py | 6 +-- .../glhe/profiles/test_constant_flow.py | 6 +-- .../glhe/profiles/test_constant_load.py | 6 +-- .../glhe/profiles/test_constant_temp.py | 6 +-- .../glhe/profiles/test_external_base.py | 8 ++-- .../glhe/profiles/test_external_flow.py | 41 ++++++++++--------- .../glhe/profiles/test_external_load.py | 16 ++++---- .../glhe/profiles/test_external_temps.py | 16 +++++--- unit_tests/glhe/profiles/test_flow_factory.py | 22 +++++----- unit_tests/glhe/profiles/test_load_factory.py | 19 +++++---- unit_tests/glhe/profiles/test_pulse_load.py | 6 +-- .../glhe/profiles/test_sinusoid_load.py | 6 +-- .../glhe/profiles/test_synthetic_load.py | 6 +-- .../glhe/properties/test_fluid_types.py | 16 ++++---- .../topology/test_ground_heat_exhanger.py | 12 +++--- ...st_ground_heat_exhanger_short_time_step.py | 14 ++++--- unit_tests/glhe/topology/test_path.py | 11 ++--- unit_tests/glhe/topology/test_pipe.py | 6 +-- .../test_single_u_tube_grouted_borehole.py | 6 +-- .../test_single_u_tube_grouted_segment.py | 6 +-- .../glhe/topology/test_swedish_heat_pump.py | 12 +++--- unit_tests/glhe/utilities/test_functions.py | 35 ++++++++-------- unit_tests/standalone/test_plant_loop.py | 23 +++++------ 35 files changed, 255 insertions(+), 249 deletions(-) diff --git a/glhe/aggregation/base_agg.py b/glhe/aggregation/base_agg.py index 43ecef7a..36d734b3 100644 --- a/glhe/aggregation/base_agg.py +++ b/glhe/aggregation/base_agg.py @@ -1,14 +1,9 @@ -import os from abc import ABC, abstractmethod +from pathlib import Path import numpy as np -from glhe.utilities.functions import Interpolator1D, Interpolator1DFromFile -from glhe.utilities.functions import Interpolator2DFromFile - -join = os.path.join -norm = os.path.normpath -cwd = os.getcwd() +from glhe.utilities.functions import InterpolatorBase, Interpolator1D, Interpolator1DFromFile, Interpolator2DFromFile class BaseAgg(ABC): @@ -16,21 +11,26 @@ class BaseAgg(ABC): def __init__(self, inputs: dict): # g-function values if 'g-function-path' in inputs: - path_g = norm(join(cwd, inputs['g-function-path'])) - self.interp_g = Interpolator1DFromFile(path_g) + p = Path(inputs['g-function-path']) + if not p.is_absolute(): + p = Path.cwd() / inputs['g-function-path'] + self.interp_g = Interpolator1DFromFile(p) elif 'lntts' and 'g-values' in inputs: data_g = np.transpose(np.array([inputs['lntts'], inputs['g-values']])) - self.interp_g = Interpolator1D(data_g[:, 0], data_g[:, 1]) + self.interp_g: InterpolatorBase = Interpolator1D(data_g[:, 0], data_g[:, 1]) else: raise KeyError('g-function data not found.') # g_b-function values self.interp_g_b = None if 'g_b-function-path' in inputs: + p = Path(inputs['g_b-function-path']) + if not p.is_absolute(): + p = Path.cwd() / inputs['g_b-function-path'] if 'g_b-flow-rates' in inputs: - self.interp_g_b = Interpolator2DFromFile(inputs['g_b-function-path'], inputs['g_b-flow-rates']) + self.interp_g_b: InterpolatorBase = Interpolator2DFromFile(p, inputs['g_b-flow-rates']) else: - self.interp_g_b = Interpolator1DFromFile(inputs['g_b-function-path']) + self.interp_g_b: InterpolatorBase = Interpolator1DFromFile(p) elif 'lntts_b' and 'g_b-values' in inputs: data_g_b = np.transpose(np.array([inputs['lntts_b'], inputs['g_b-values']])) self.interp_g_b = Interpolator1D(data_g_b[:, 0], data_g_b[:, 1]) diff --git a/glhe/aggregation/dynamic.py b/glhe/aggregation/dynamic.py index 5cb0e517..6b9d59aa 100644 --- a/glhe/aggregation/dynamic.py +++ b/glhe/aggregation/dynamic.py @@ -23,16 +23,14 @@ def __init__(self, inputs: dict): self.sub_hr = SubHour(inputs) # set expansion rate. apply default if needed. - try: + self.exp_rate = 1.62 + if 'expansion-rate' in inputs: self.exp_rate = inputs['expansion-rate'] - except KeyError: # pragma: no cover - self.exp_rate = 1.62 # pragma: no cover # set the number of bins per level. apply default if needed. - try: + self.bins_per_level = 9 + if 'number-bins-per-level' in inputs: self.bins_per_level = inputs['number-bins-per-level'] - except KeyError: # pragma: no cover - self.bins_per_level = 9 # pragma: no cover # total simulation runtime to make available for method run_time = inputs['runtime'] @@ -47,17 +45,13 @@ def __init__(self, inputs: dict): t = SEC_IN_HOUR # initialize the dynamic method - initialized = False - while not initialized: + while True: for _ in range(self.bins_per_level): t += dt self.energy = np.insert(self.energy, 0, 0) self.dts = np.insert(self.dts, 0, dt) - if t >= run_time: - initialized = True - break - + return dt *= self.exp_rate def aggregate(self, time: int, energy: float): @@ -111,7 +105,8 @@ def calc_temporal_superposition(self, time_step: int, flow_rate: float = None) - if not flow_rate: g_b = self.interp_g_b.interpolate(lntts) else: - g_b = np.flipud(self.interp_g_b(lntts, flow_rate)) + # TODO: This is not covered by tests, and may need adjusting the interpolator class to work properly + g_b = np.flipud(self.interp_g_b.interpolate(lntts, flow_rate)) return float(np.dot(dq, g)), float(np.dot(dq, g_b)) else: # convolution for "g" g-functions only @@ -119,14 +114,11 @@ def calc_temporal_superposition(self, time_step: int, flow_rate: float = None) - def get_g_value(self, time_step: int) -> float: lntts = np.log(time_step / self.ts) - return float(self.interp_g.interpolate(lntts)) + return self.interp_g.interpolate(lntts) def get_g_b_value(self, time_step: int, flow_rate: float = None) -> float: lntts = np.log(time_step / self.ts) - if not flow_rate: - return float(self.interp_g_b.interpolate(lntts)) - else: - return float(self.interp_g_b.interpolate(lntts, flow_rate)) + return self.interp_g_b.interpolate(lntts, flow_rate) def get_q_prev(self) -> float: return float(self.sub_hr.energy[-1] / self.sub_hr.dts[-1]) diff --git a/glhe/aggregation/no_agg.py b/glhe/aggregation/no_agg.py index f8bbf0f9..545fb5a4 100644 --- a/glhe/aggregation/no_agg.py +++ b/glhe/aggregation/no_agg.py @@ -48,6 +48,7 @@ def calc_temporal_superposition(self, time_step: int, _: float = None) -> float: # convolution for "g" g-functions only return float(np.dot(dq, g)) + # TODO: Should these abstract methods be populated to do anything? If not, should they actually be abstract methods? def get_g_value(self, time_step: int) -> float: pass # pragma: no cover diff --git a/glhe/aggregation/static.py b/glhe/aggregation/static.py index 7d05424e..633f12b9 100644 --- a/glhe/aggregation/static.py +++ b/glhe/aggregation/static.py @@ -63,13 +63,14 @@ def aggregate(self, time: int, energy: float): # store whatever rolls off of the sub-hourly method # need to think about this + # TODO: If this is returning early, what about the prev_update_time assignments below? self.energy[-1] += e_1 return else: # aggregate # TODO: The next two lines are showing as unused in Pycharm, is this IF block needed then? # dts_flip = np.flipud(self.dts) - # vals, idxs, cnts = np.unique(dts_flip, return_counts=True, return_index=True) + # vals, indexes, counts = np.unique(dts_flip, return_counts=True, return_index=True) self.energy[-1] += e_1 # numpy split will do a lot of work too diff --git a/glhe/ground_temps/base.py b/glhe/ground_temps/base.py index a18c4722..dd7a25a0 100644 --- a/glhe/ground_temps/base.py +++ b/glhe/ground_temps/base.py @@ -7,11 +7,11 @@ class BaseGroundTemp(ABC): """ @abstractmethod - def get_temp(self, time: int, depth: float): + def get_temp(self, time: int, depth: float) -> float: """ Getter method for ground temperatures - :param time: time for ground temperature [s] + :param time: time for ground temperature [s] # TODO: Should we use float instead of int for time? :param depth: depth for ground temperature [m] :return: ground temperature [C] """ diff --git a/glhe/input_processor/input_processor.py b/glhe/input_processor/input_processor.py index bdc24a3c..bd33647a 100644 --- a/glhe/input_processor/input_processor.py +++ b/glhe/input_processor/input_processor.py @@ -1,4 +1,6 @@ +from json import loads import os +from pathlib import Path from jsonschema import SchemaError, ValidationError, validate @@ -8,7 +10,7 @@ class InputProcessor: - def __init__(self, json_input_path: str): + def __init__(self, json_input_path: Path): """ Initialize the input processor, process input file, and store the input information. @@ -18,11 +20,11 @@ def __init__(self, json_input_path: str): """ # check if file exists - if not os.path.exists(json_input_path): - raise FileNotFoundError("Input file: '{}' does not exist.".format(json_input_path)) + if not json_input_path.exists(): + raise FileNotFoundError(f"Input file: '{json_input_path}' does not exist.") # load the input file - self.input_dict = lower_obj(load_json(json_input_path)) + self.input_dict: dict = lower_obj(loads(json_input_path.read_text())) # validate the inputs self.validate_inputs(self.input_dict) diff --git a/glhe/output_processor/output_processor.py b/glhe/output_processor/output_processor.py index de40b759..1adf37a3 100644 --- a/glhe/output_processor/output_processor.py +++ b/glhe/output_processor/output_processor.py @@ -1,20 +1,19 @@ import datetime as dt -import os -from os.path import join, normpath +from pathlib import Path import pandas as pd class OutputProcessor: - def __init__(self, output_dir: str, output_name: str): + def __init__(self, output_dir: Path, output_name: str): """ Output processor manages output data """ self.output_dir = output_dir self.output_file = output_name - self.write_path = normpath(join(output_dir, output_name)) + self.write_path = output_dir / output_name self.df = pd.DataFrame() self.idx_count = 0 @@ -33,11 +32,11 @@ def write_to_file(self) -> None: """ Write the DataFrame holding the simulation data to a file. """ - if os.path.exists(self.write_path): - os.remove(self.write_path) + if self.write_path.exists(): + self.write_path.unlink() self.convert_time_to_timestamp() - self.df.to_csv(self.write_path) + self.df.to_csv(str(self.write_path)) # TODO: can probably just write it ourselves def convert_time_to_timestamp(self) -> None: """ diff --git a/glhe/profiles/external_base.py b/glhe/profiles/external_base.py index 26a04494..fca397da 100644 --- a/glhe/profiles/external_base.py +++ b/glhe/profiles/external_base.py @@ -1,3 +1,5 @@ +from pathlib import Path + import pandas as pd from glhe.utilities.functions import Interpolator1D @@ -5,9 +7,9 @@ class ExternalBase(object): - def __init__(self, path, col_num): + def __init__(self, path: Path, col_num: int): - df = pd.read_csv(path, index_col=0, parse_dates=True) + df = pd.read_csv(str(path), index_col=0, parse_dates=True) df['delta t'] = df.index.to_series().diff().dt.total_seconds() df['delta t'].iat[0] = 0 x_range = df['delta t'].cumsum().tolist() diff --git a/glhe/standalone/plant_loop.py b/glhe/standalone/plant_loop.py index 715ffa47..6614fbe3 100644 --- a/glhe/standalone/plant_loop.py +++ b/glhe/standalone/plant_loop.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 import datetime as dt -import os +from pathlib import Path import sys from glhe.input_processor.component_types import ComponentTypes @@ -16,7 +16,7 @@ class PlantLoop: Type = ComponentTypes.PlantLoop - def __init__(self, json_file_path: str): + def __init__(self, json_file_path: Path): """ Initialize the plant loop and all components on it. @@ -30,11 +30,11 @@ def __init__(self, json_file_path: str): try: # setup output processor - self.op = OutputProcessor(self.ip.input_dict['simulation']['output-path'], - self.ip.input_dict['simulation']['output-csv-name']) + output_path = Path(self.ip.input_dict['simulation']['output-path']) + self.op = OutputProcessor(output_path, self.ip.input_dict['simulation']['output-csv-name']) except KeyError: # paths were not provided. apply default paths. - self.op = OutputProcessor(os.getcwd(), 'out.csv') + self.op = OutputProcessor(Path.cwd(), 'out.csv') # init plant-level variables self.demand_inlet_temp = self.ip.input_dict['simulation']['initial-temperature'] @@ -86,11 +86,11 @@ def simulate(self) -> bool: if status: print('Simulation time: {}'.format(dt.datetime.now() - self.start_time)) - with open('{}.txt'.format(os.path.join(self.op.output_dir, self.op.output_file[:-4])), 'w+') as f: + with open('{}.txt'.format(self.op.output_dir / self.op.output_file[:-4]), 'w+') as f: f.write('Simulation time: {}\n'.format(dt.datetime.now() - self.start_time)) else: print('Simulation FAILED!') - with open('{}.txt'.format(os.path.join(self.op.output_dir, self.op.output_file[:-4])), 'w+') as f: + with open('{}.txt'.format(self.op.output_dir / self.op.output_file[:-4]), 'w+') as f: f.write('Simulation FAILED!\n') return status @@ -149,7 +149,11 @@ def collect_outputs(self, sim_time): def main(): - PlantLoop(sys.argv[1]).simulate() + p = Path(sys.argv[1]) + if not p.is_absolute(): + p = Path.cwd() / p + loop = PlantLoop(p) + loop.simulate() if __name__ == "__main__": diff --git a/glhe/utilities/functions.py b/glhe/utilities/functions.py index d3e9c9d7..4d730e2c 100644 --- a/glhe/utilities/functions.py +++ b/glhe/utilities/functions.py @@ -1,5 +1,7 @@ import json +from abc import ABC, abstractmethod from math import ceil, exp, factorial, floor +from pathlib import Path from typing import Callable, overload import numpy as np @@ -218,8 +220,8 @@ def load_json(input_path: str) -> dict: return json.loads(json_blob) -def write_json(write_path: str, input_dict: dict, indent: int = 2) -> None: - with open(write_path, 'w') as f: +def write_json(write_path: Path, input_dict: dict, indent: int = 2) -> None: + with write_path.open('w') as f: f.write(json.dumps(input_dict, sort_keys=True, indent=indent, separators=(',', ': '))) @@ -468,7 +470,7 @@ def un_reverse_idx(length: int, reversed_idx: int) -> int: return length - 1 - reversed_idx -def write_arrays_to_csv(path: str, arrays: list | np.ndarray) -> None: +def write_arrays_to_csv(path: Path, arrays: list | np.ndarray) -> None: _arrays = None if isinstance(arrays, list): _arrays = np.array(arrays) @@ -477,12 +479,18 @@ def write_arrays_to_csv(path: str, arrays: list | np.ndarray) -> None: df = pd.DataFrame.from_records(_arrays) df = df.T - df.to_csv(path, header=False, index=False) + df.to_csv(str(path), header=False, index=False) -class Interpolator1D: +class InterpolatorBase(ABC): + @abstractmethod + def interpolate(self, *args: list[float]) -> float: + pass - def __init__(self, x_data: np.ndarray, y_data: np.ndarray | list[float]): # TODO: Use pathlib internally everywhere + +class Interpolator1D(InterpolatorBase): + + def __init__(self, x_data: np.ndarray, y_data: np.ndarray | list[float]): """ 1D Interpolation Class, currently a wrapper for scipy interpolator, but soon just a simple interpolator @@ -491,26 +499,26 @@ def __init__(self, x_data: np.ndarray, y_data: np.ndarray | list[float]): # TOD """ self.interp = interp1d(x_data, y_data, fill_value='extrapolate') - def interpolate(self, x: float) -> float: - return self.interp(x) + def interpolate(self, *args: list[float]) -> float: + return self.interp(args[0]) class Interpolator1DFromFile(Interpolator1D): - def __init__(self, data_path: str): # TODO: Try to use pathlib internally everywhere + def __init__(self, data_path: Path): """ 1D Interpolation Class, currently a wrapper for scipy interpolator, but soon just a simple interpolator :param data_path: path to csv file with columned data, e.g. 'x1,y1' """ - data = np.genfromtxt(data_path, delimiter=',') + data = np.genfromtxt(str(data_path), delimiter=',') _, num_col = data.shape if num_col != 2: raise ValueError("Number of columns in '{}' must be 2".format(data_path)) super().__init__(data[:, 0], data[:, 1]) -class Interpolator2DFromFile: - def __init__(self, xz_data_path: str, y: list): +class Interpolator2DFromFile(InterpolatorBase): + def __init__(self, xz_data_path: Path, y: list): """ 2D interpolation class, currently a wrapper for scipy interpolator, but soon just a simple interpolator @@ -532,7 +540,7 @@ def __init__(self, xz_data_path: str, y: list): :param y: list of *constant* values for the second independent variable """ - data = np.genfromtxt(xz_data_path, delimiter=',') + data = np.genfromtxt(str(xz_data_path), delimiter=',') _, num_col = data.shape num_series = num_col - 1 @@ -548,9 +556,9 @@ def __init__(self, xz_data_path: str, y: list): self.interp = RegularGridInterpolator((x, y), z) - def interpolate(self, x: float, y: float) -> float: - x = self.interp((x, y)) - return x.min() + def interpolate(self, *args: list[float]) -> float: + x = self.interp(args) + return x.min() # just want to get the first item, this works fine, but could do better def resample_g_functions(lntts, g, lntts_interval=0.1): diff --git a/unit_tests/glhe/input_processor/test_input_processor.py b/unit_tests/glhe/input_processor/test_input_processor.py index 42fedd29..78af5dbd 100644 --- a/unit_tests/glhe/input_processor/test_input_processor.py +++ b/unit_tests/glhe/input_processor/test_input_processor.py @@ -1,7 +1,7 @@ import os +from pathlib import Path import tempfile import unittest -from contextlib import contextmanager from jsonschema.exceptions import ValidationError @@ -11,20 +11,15 @@ class TestInputProcessor(unittest.TestCase): - @contextmanager - def assertNotRaise(self, exc_type): - try: - yield None - except exc_type: # pragma: no cover - raise self.failureException('{} raised'.format(exc_type.__name__)) # pragma: no cover - def run_validate(self, inputs): - temp_dir = tempfile.mkdtemp() - temp_file = os.path.join(temp_dir, 'temp.json') + temp_dir = Path(tempfile.mkdtemp()) + temp_file = temp_dir / 'temp.json' write_json(temp_file, inputs) - with self.assertNotRaise(ValidationError): + try: InputProcessor(temp_file) + except ValidationError: + self.fail('Input processor validation failed') def test_validate_pipe_definitions(self): d = {'pipe-definitions': [{ @@ -235,7 +230,8 @@ def test_all_schema_tests_implemented(self): self.assertEqual(test_count, schema_count) def test_file_not_found(self): - self.assertRaises(FileNotFoundError, lambda: InputProcessor('some path')) + with self.assertRaises(FileNotFoundError): + InputProcessor(Path('some path')) def test_validate_validation_error(self): d = {'soil': {'name': 'Some Rock', @@ -252,9 +248,9 @@ def test_get_definition_object_fail(self): 'name': 'my name', 'length': 100}]} - temp_dir = tempfile.mkdtemp() - f_path = os.path.join(temp_dir, 'temp.json') + temp_dir = Path(tempfile.mkdtemp()) + f_path = temp_dir / 'temp.json' write_json(f_path, d) ip = InputProcessor(f_path) - with self.assertRaises(KeyError) as _: + with self.assertRaises(KeyError): ip.get_definition_object('pipe-definitions', 'not-implemented') diff --git a/unit_tests/glhe/input_processor/test_plant_loop_component_factory.py b/unit_tests/glhe/input_processor/test_plant_loop_component_factory.py index e29f3116..0d57d9cf 100644 --- a/unit_tests/glhe/input_processor/test_plant_loop_component_factory.py +++ b/unit_tests/glhe/input_processor/test_plant_loop_component_factory.py @@ -1,4 +1,4 @@ -import os +from pathlib import Path import tempfile import unittest @@ -12,17 +12,15 @@ from glhe.topology.pipe import Pipe from glhe.utilities.functions import write_json -join = os.path.join -norm = os.path.normpath -cwd = os.getcwd() - class TestPLCompFactory(unittest.TestCase): @staticmethod def add_instance(): - f_path = os.path.dirname(os.path.abspath(__file__)) - d = { + f_path = Path(__file__).parent + root = f_path.parent.parent.parent + g_func_file = root / 'validation' / 'MFRTRT_EWT_g_functions' / 'EWT_experimental_g_functions.csv' + d: dict = { "borehole-definitions": [ { "borehole-type": "single-grouted", @@ -79,10 +77,8 @@ def add_instance(): { "name": "GHE 1", "simulation-mode": "enhanced", - "g-function-path": norm(join(f_path, '..', '..', '..', 'validation', 'MFRTRT_EWT_g_functions', - 'EWT_experimental_g_functions.csv')), - "g_b-function-path": norm(join(f_path, '..', '..', '..', 'validation', 'MFRTRT_EWT_g_functions', - 'EWT_experimental_g_functions.csv')), + "g-function-path": str(g_func_file), + "g_b-function-path": str(g_func_file), "flow-paths": [ { "name": "path 1", @@ -151,9 +147,9 @@ def add_instance(): } } - temp_dir = tempfile.mkdtemp() - temp_file = join(temp_dir, 'in.json') - d['simulation']['output-path'] = temp_dir + temp_dir = Path(tempfile.mkdtemp()) + temp_file = temp_dir / 'in.json' + d['simulation']['output-path'] = str(temp_dir) write_json(temp_file, d) ip = InputProcessor(temp_file) op = OutputProcessor(temp_dir, 'out.csv') diff --git a/unit_tests/glhe/output_processor/test_output_processor.py b/unit_tests/glhe/output_processor/test_output_processor.py index 98fad526..386705b1 100644 --- a/unit_tests/glhe/output_processor/test_output_processor.py +++ b/unit_tests/glhe/output_processor/test_output_processor.py @@ -1,4 +1,4 @@ -import os +from pathlib import Path import tempfile import unittest @@ -11,7 +11,7 @@ class TestOutputProcessor(unittest.TestCase): @staticmethod def add_instance(): - temp_dir = tempfile.mkdtemp() + temp_dir = Path(tempfile.mkdtemp()) temp_file_name = 'temp.csv' return OutputProcessor(temp_dir, temp_file_name) @@ -36,7 +36,7 @@ def test_write_to_file(self): tst.write_to_file() # check that the file was written - self.assertTrue(os.path.exists(tst.write_path)) + self.assertTrue(tst.write_path.exists()) # make sure the data comes out right df = pd.read_csv(tst.write_path) diff --git a/unit_tests/glhe/profiles/test_constant_flow.py b/unit_tests/glhe/profiles/test_constant_flow.py index 770d894b..b785d524 100644 --- a/unit_tests/glhe/profiles/test_constant_flow.py +++ b/unit_tests/glhe/profiles/test_constant_flow.py @@ -1,4 +1,4 @@ -import os +from pathlib import Path import tempfile import unittest @@ -15,8 +15,8 @@ class TestConstantFlow(unittest.TestCase): def add_instance(): d = {'flow-profile': [{'flow-profile-type': 'constant', 'name': 'my name', 'value': 0.1}]} - temp_dir = tempfile.mkdtemp() - temp_file = os.path.join(temp_dir, 'temp.json') + temp_dir = Path(tempfile.mkdtemp()) + temp_file = temp_dir / 'temp.json' write_json(temp_file, d) diff --git a/unit_tests/glhe/profiles/test_constant_load.py b/unit_tests/glhe/profiles/test_constant_load.py index c50ff610..3775224f 100644 --- a/unit_tests/glhe/profiles/test_constant_load.py +++ b/unit_tests/glhe/profiles/test_constant_load.py @@ -1,4 +1,4 @@ -import os +from pathlib import Path import tempfile import unittest @@ -16,8 +16,8 @@ def add_instance(): d = {'fluid': {'fluid-type': 'water'}, 'load-profile': [{'load-profile-type': 'constant', 'name': 'my name', 'value': 4000}]} - temp_dir = tempfile.mkdtemp() - temp_file = os.path.join(temp_dir, 'temp.json') + temp_dir = Path(tempfile.mkdtemp()) + temp_file = temp_dir / 'temp.json' write_json(temp_file, d) diff --git a/unit_tests/glhe/profiles/test_constant_temp.py b/unit_tests/glhe/profiles/test_constant_temp.py index b829710e..ccb67de1 100644 --- a/unit_tests/glhe/profiles/test_constant_temp.py +++ b/unit_tests/glhe/profiles/test_constant_temp.py @@ -1,4 +1,4 @@ -import os +from pathlib import Path import tempfile import unittest @@ -15,8 +15,8 @@ class TestConstantFlow(unittest.TestCase): def add_instance(): d = {'temperature-profile': [{'temperature-profile-type': 'constant', 'name': 'my name', 'value': 20}]} - temp_dir = tempfile.mkdtemp() - temp_file = os.path.join(temp_dir, 'temp.json') + temp_dir = Path(tempfile.mkdtemp()) + temp_file = temp_dir / 'temp.json' write_json(temp_file, d) diff --git a/unit_tests/glhe/profiles/test_external_base.py b/unit_tests/glhe/profiles/test_external_base.py index bde042f8..a7631fcb 100644 --- a/unit_tests/glhe/profiles/test_external_base.py +++ b/unit_tests/glhe/profiles/test_external_base.py @@ -1,4 +1,4 @@ -import os +from pathlib import Path import tempfile import unittest @@ -9,10 +9,10 @@ class TestExternalBase(unittest.TestCase): @staticmethod def add_instance(): - temp_dir = tempfile.mkdtemp() - temp_csv = os.path.join(temp_dir, 'temp.csv') + temp_dir = Path(tempfile.mkdtemp()) + temp_csv = temp_dir / 'temp.csv' - with open(temp_csv, 'w') as f: + with temp_csv.open('w') as f: f.write(',b,c\n1/1/2018 0:00,1,4\n1/1/2018 0:02,2,3\n1/1/2018 0:04,3,6\n') return ExternalBase(temp_csv, 0) diff --git a/unit_tests/glhe/profiles/test_external_flow.py b/unit_tests/glhe/profiles/test_external_flow.py index 21065ee6..08b09b33 100644 --- a/unit_tests/glhe/profiles/test_external_flow.py +++ b/unit_tests/glhe/profiles/test_external_flow.py @@ -1,4 +1,4 @@ -import os +from pathlib import Path import tempfile import unittest @@ -12,11 +12,11 @@ class TestExternalFlow(unittest.TestCase): @staticmethod - def add_instance(path): - d = {'flow-profile': [{'flow-profile-type': 'external', 'name': 'my name', 'path': path}]} + def add_instance(path: Path): + d = {'flow-profile': [{'flow-profile-type': 'external', 'name': 'my name', 'path': str(path)}]} - temp_dir = tempfile.mkdtemp() - temp_file = os.path.join(temp_dir, 'temp.json') + temp_dir = Path(tempfile.mkdtemp()) + temp_file = temp_dir / 'temp.json' write_json(temp_file, d) @@ -26,18 +26,19 @@ def add_instance(path): return ExternalFlow(d['flow-profile'][0], ip, op) def test_get_value(self): - dir_name = os.path.dirname(__file__) - relative_path = '../../../glhe/profiles/external_data/GSHP-GLHE_USA_IL_Chicago-OHare.Intl.AP.725300_TMY3.csv' - path = os.path.normpath(os.path.join(dir_name, relative_path)) + dir_name = Path(__file__).parent + root_dir = dir_name.parent.parent.parent + data_folder = root_dir / 'glhe' / 'profiles' / 'external_data' + path = data_folder / 'GSHP-GLHE_USA_IL_Chicago-OHare.Intl.AP.725300_TMY3.csv' tst = self.add_instance(path) self.assertEqual(tst.get_value(0), 0) self.assertEqual(tst.get_value(10 * 3600), 1) self.assertEqual(tst.get_value(8759 * 3600), 0) def test_start_end_points(self): - temp_dir = tempfile.mkdtemp() - temp_data = os.path.join(temp_dir, 'temp_data.csv') - with open(temp_data, 'w') as f: + temp_dir = Path(tempfile.mkdtemp()) + temp_data = temp_dir / 'temp_data.csv' + with temp_data.open('w') as f: f.write('Date/Time, Meas. Total Power [W], mdot [kg/s]\n' '2018-01-01 00:00:00, 1, 1\n' '2018-01-01 01:00:00, 2, 2\n' @@ -53,9 +54,9 @@ def test_start_end_points(self): self.assertEqual(tst.get_value(3.0 * 3600), 4.0) def test_repeated_points(self): - temp_dir = tempfile.mkdtemp() - temp_data = os.path.join(temp_dir, 'temp_data.csv') - with open(temp_data, 'w') as f: + temp_dir = Path(tempfile.mkdtemp()) + temp_data = temp_dir / 'temp_data.csv' + with temp_data.open('w') as f: f.write('Date/Time, Meas. Total Power [W], mdot [kg/s]\n' '2018-01-01 00:00:00, 1, 1\n' '2018-01-01 01:00:00, 2, 2\n' @@ -76,9 +77,9 @@ def test_repeated_points(self): self.assertEqual(tst.get_value(12.0 * 3600), 1.0) def test_simulate_time_step(self): - temp_dir = tempfile.mkdtemp() - temp_data = os.path.join(temp_dir, 'temp_data.csv') - with open(temp_data, 'w') as f: + temp_dir = Path(tempfile.mkdtemp()) + temp_data = temp_dir / 'temp_data.csv' + with temp_data.open('w') as f: f.write('Date/Time, Meas. Total Power [W], mdot [kg/s]\n' '2018-01-01 00:00:00, 1, 1\n' '2018-01-01 01:00:00, 2, 2\n' @@ -93,9 +94,9 @@ def test_simulate_time_step(self): self.assertEqual(res.temperature, 10) def test_report_outputs(self): - temp_dir = tempfile.mkdtemp() - temp_data = os.path.join(temp_dir, 'temp_data.csv') - with open(temp_data, 'w') as f: + temp_dir = Path(tempfile.mkdtemp()) + temp_data = temp_dir / 'temp_data.csv' + with temp_data.open('w') as f: f.write('Date/Time, Meas. Total Power [W], mdot [kg/s]\n' '2018-01-01 00:00:00, 1, 1\n' '2018-01-01 01:00:00, 2, 2\n' diff --git a/unit_tests/glhe/profiles/test_external_load.py b/unit_tests/glhe/profiles/test_external_load.py index 73cf975b..d8d475d8 100644 --- a/unit_tests/glhe/profiles/test_external_load.py +++ b/unit_tests/glhe/profiles/test_external_load.py @@ -1,4 +1,5 @@ import os +from pathlib import Path import tempfile import unittest @@ -12,12 +13,12 @@ class TestExternalLoad(unittest.TestCase): @staticmethod - def add_instance(path): + def add_instance(path: Path): d = {'fluid': {'fluid-type': 'water'}, - 'load-profile': [{'load-profile-type': 'external', 'name': 'my name', 'path': path}]} + 'load-profile': [{'load-profile-type': 'external', 'name': 'my name', 'path': str(path)}]} - temp_dir = tempfile.mkdtemp() - temp_file = os.path.join(temp_dir, 'temp.json') + temp_dir = Path(tempfile.mkdtemp()) + temp_file = temp_dir / 'temp.json' write_json(temp_file, d) @@ -27,9 +28,10 @@ def add_instance(path): return ExternalLoad(d['load-profile'][0], ip, op) def test_get_value(self): - dir_name = os.path.dirname(__file__) - relative_path = '../../../glhe/profiles/external_data/GSHP-GLHE_USA_IL_Chicago-OHare.Intl.AP.725300_TMY3.csv' - path = os.path.normpath(os.path.join(dir_name, relative_path)) + dir_name = Path(__file__).parent + root_dir = dir_name.parent.parent.parent + data_folder = root_dir / 'glhe' / 'profiles' / 'external_data' + path = data_folder / 'GSHP-GLHE_USA_IL_Chicago-OHare.Intl.AP.725300_TMY3.csv' tst = self.add_instance(path) self.assertEqual(tst.get_value(0), 0) self.assertEqual(tst.get_value(10 * 3600), -4980.600013) diff --git a/unit_tests/glhe/profiles/test_external_temps.py b/unit_tests/glhe/profiles/test_external_temps.py index df6fa802..6fd2bfed 100644 --- a/unit_tests/glhe/profiles/test_external_temps.py +++ b/unit_tests/glhe/profiles/test_external_temps.py @@ -1,4 +1,4 @@ -import os +from pathlib import Path import tempfile import unittest @@ -13,18 +13,22 @@ class TestExternalTemps(unittest.TestCase): @staticmethod def add_instance(): - temp_dir = tempfile.mkdtemp() - temp_data = os.path.join(temp_dir, 'temp_data.csv') + temp_dir = Path(tempfile.mkdtemp()) + temp_data = temp_dir / 'temp_data.csv' - with open(temp_data, 'w') as f: + with temp_data.open('w') as f: f.write('Date/Time, Meas. Total Power [W], mdot [kg/s], temperature[C]\n' '2018-01-01 00:00:00, 1, 1, 1\n' '2018-01-01 01:00:00, 2, 2, 2\n' '2018-01-01 02:00:00, 3, 3, 3\n' '2018-01-01 03:00:00, 4, 4, 4\n') - d = {'temperature-profile': [{'temperature-profile-type': 'external', 'name': 'my name', 'path': temp_data}]} - temp_file = os.path.join(temp_dir, 'temp.json') + d = { + 'temperature-profile': [ + {'temperature-profile-type': 'external', 'name': 'my name', 'path': str(temp_data)} + ] + } + temp_file = temp_dir / 'temp.json' write_json(temp_file, d) ip = InputProcessor(temp_file) diff --git a/unit_tests/glhe/profiles/test_flow_factory.py b/unit_tests/glhe/profiles/test_flow_factory.py index e4573764..1a3940a6 100644 --- a/unit_tests/glhe/profiles/test_flow_factory.py +++ b/unit_tests/glhe/profiles/test_flow_factory.py @@ -1,4 +1,4 @@ -import os +from pathlib import Path import tempfile import unittest @@ -15,8 +15,8 @@ class TestFlowFactory(unittest.TestCase): def test_constant_flow(self): d = {'flow-profile': [{'flow-profile-type': 'constant', 'name': 'my name', 'value': 1}]} - temp_dir = tempfile.mkdtemp() - temp_file = os.path.join(temp_dir, 'temp.json') + temp_dir = Path(tempfile.mkdtemp()) + temp_file = temp_dir / 'temp.json' write_json(temp_file, d) @@ -27,17 +27,19 @@ def test_constant_flow(self): self.assertIsInstance(tst, ConstantFlow) def test_external_flow(self): - fpath = os.path.dirname(os.path.abspath(__file__)) - rel_path = '../../../glhe/profiles/external_data/GSHP-GLHE_USA_IL_Chicago-OHare.Intl.AP.725300_TMY3.csv' + dir_name = Path(__file__).parent + root_dir = dir_name.parent.parent.parent + data_folder = root_dir / 'glhe' / 'profiles' / 'external_data' + path = data_folder / 'GSHP-GLHE_USA_IL_Chicago-OHare.Intl.AP.725300_TMY3.csv' d = { 'flow-profile': [{'flow-profile-type': 'external', 'name': 'my name', - 'path': os.path.join(fpath, rel_path)}]} + 'path': str(path)}]} - temp_dir = tempfile.mkdtemp() - temp_file = os.path.join(temp_dir, 'temp.json') + temp_dir = Path(tempfile.mkdtemp()) + temp_file = temp_dir / 'temp.json' write_json(temp_file, d) @@ -50,8 +52,8 @@ def test_external_flow(self): def test_fail(self): d = {'flow-profile': [{'flow-profile-type': 'constant', 'name': 'my name', 'value': 1}]} - temp_dir = tempfile.mkdtemp() - temp_file = os.path.join(temp_dir, 'temp.json') + temp_dir = Path(tempfile.mkdtemp()) + temp_file = temp_dir / 'temp.json' write_json(temp_file, d) diff --git a/unit_tests/glhe/profiles/test_load_factory.py b/unit_tests/glhe/profiles/test_load_factory.py index d6f46535..d53f8e79 100644 --- a/unit_tests/glhe/profiles/test_load_factory.py +++ b/unit_tests/glhe/profiles/test_load_factory.py @@ -1,4 +1,4 @@ -import os +from pathlib import Path import tempfile import unittest @@ -17,14 +17,15 @@ class TestLoadFactory(unittest.TestCase): @staticmethod def add_instance(method): - fpath = os.path.dirname(os.path.abspath(__file__)) - data_str = '../../../glhe/profiles/external_data/GSHP-GLHE_USA_IL_Chicago-OHare.Intl.AP.725300_TMY3.csv' - data_path = os.path.normpath(os.path.join(fpath, data_str)) + fpath = Path(__file__).parent + root = fpath.parent.parent.parent + data_dir = root / 'glhe' / 'profiles' / 'external_data' + data_path = data_dir / 'GSHP-GLHE_USA_IL_Chicago-OHare.Intl.AP.725300_TMY3.csv' d = {'fluid': {'fluid-type': 'water'}, 'load-profile': [{'load-profile-type': method, 'value': 10, 'name': 'my name', - 'path': data_path, + 'path': str(data_path), 'start-time': 1, 'end-time': 10, 'amplitude': 100, @@ -32,8 +33,8 @@ def add_instance(method): 'period': 10, 'synthetic-method': 'symmetric'}]} - temp_dir = tempfile.mkdtemp() - temp_file = os.path.join(temp_dir, 'temp.json') + temp_dir = Path(tempfile.mkdtemp()) + temp_file = temp_dir / 'temp.json' write_json(temp_file, d) @@ -68,8 +69,8 @@ def test_fail(self): 'value': 10, 'name': 'my name'}]} - temp_dir = tempfile.mkdtemp() - temp_file = os.path.join(temp_dir, 'temp.json') + temp_dir = Path(tempfile.mkdtemp()) + temp_file = temp_dir / 'temp.json' write_json(temp_file, d) diff --git a/unit_tests/glhe/profiles/test_pulse_load.py b/unit_tests/glhe/profiles/test_pulse_load.py index 8fa7535d..93ae29d7 100644 --- a/unit_tests/glhe/profiles/test_pulse_load.py +++ b/unit_tests/glhe/profiles/test_pulse_load.py @@ -1,4 +1,4 @@ -import os +from pathlib import Path import tempfile import unittest @@ -20,8 +20,8 @@ def add_instance(): 'start-time': 0, 'end-time': 200}]} - temp_dir = tempfile.mkdtemp() - temp_file = os.path.join(temp_dir, 'temp.json') + temp_dir = Path(tempfile.mkdtemp()) + temp_file = temp_dir / 'temp.json' write_json(temp_file, d) diff --git a/unit_tests/glhe/profiles/test_sinusoid_load.py b/unit_tests/glhe/profiles/test_sinusoid_load.py index 13e904e7..ca56ab75 100644 --- a/unit_tests/glhe/profiles/test_sinusoid_load.py +++ b/unit_tests/glhe/profiles/test_sinusoid_load.py @@ -1,4 +1,4 @@ -import os +from pathlib import Path import tempfile import unittest @@ -22,8 +22,8 @@ def add_instance(): 'offset': 0, 'period': 2 * pi}]} - temp_dir = tempfile.mkdtemp() - temp_file = os.path.join(temp_dir, 'temp.json') + temp_dir = Path(tempfile.mkdtemp()) + temp_file = temp_dir / 'temp.json' write_json(temp_file, d) diff --git a/unit_tests/glhe/profiles/test_synthetic_load.py b/unit_tests/glhe/profiles/test_synthetic_load.py index ae4cd72c..1a300a81 100644 --- a/unit_tests/glhe/profiles/test_synthetic_load.py +++ b/unit_tests/glhe/profiles/test_synthetic_load.py @@ -1,4 +1,4 @@ -import os +from pathlib import Path import tempfile import unittest @@ -19,8 +19,8 @@ def add_instance(method): 'amplitude': 1000, 'synthetic-method': method}]} - temp_dir = tempfile.mkdtemp() - temp_file = os.path.join(temp_dir, 'temp.json') + temp_dir = Path(tempfile.mkdtemp()) + temp_file = temp_dir / 'temp.json' write_json(temp_file, d) diff --git a/unit_tests/glhe/properties/test_fluid_types.py b/unit_tests/glhe/properties/test_fluid_types.py index bc580ad7..f6b7342a 100644 --- a/unit_tests/glhe/properties/test_fluid_types.py +++ b/unit_tests/glhe/properties/test_fluid_types.py @@ -6,14 +6,14 @@ class TestFluidType(unittest.TestCase): def test_init(self): - tst_W = FluidType.WATER - self.assertEqual(tst_W, FluidType.WATER) + tst_w = FluidType.WATER + self.assertEqual(tst_w, FluidType.WATER) - tst_EA = FluidType.ETHYL_ALCOHOL - self.assertEqual(tst_EA, FluidType.ETHYL_ALCOHOL) + tst_ea = FluidType.ETHYL_ALCOHOL + self.assertEqual(tst_ea, FluidType.ETHYL_ALCOHOL) - tst_EG = FluidType.ETHYLENE_GLYCOL - self.assertEqual(tst_EG, FluidType.ETHYLENE_GLYCOL) + tst_eg = FluidType.ETHYLENE_GLYCOL + self.assertEqual(tst_eg, FluidType.ETHYLENE_GLYCOL) - tst_PG = FluidType.PROPYLENE_GLYCOL - self.assertEqual(tst_PG, FluidType.PROPYLENE_GLYCOL) + tst_pg = FluidType.PROPYLENE_GLYCOL + self.assertEqual(tst_pg, FluidType.PROPYLENE_GLYCOL) diff --git a/unit_tests/glhe/topology/test_ground_heat_exhanger.py b/unit_tests/glhe/topology/test_ground_heat_exhanger.py index dc71f5ab..c356db32 100644 --- a/unit_tests/glhe/topology/test_ground_heat_exhanger.py +++ b/unit_tests/glhe/topology/test_ground_heat_exhanger.py @@ -1,4 +1,4 @@ -import os +from pathlib import Path import tempfile import unittest @@ -12,7 +12,7 @@ class TestGroundHeatExchanger(unittest.TestCase): @staticmethod def add_instance(): - f_path = os.path.dirname(os.path.abspath(__file__)) + f_path = Path(__file__).parent d = { "borehole-definitions": [ { @@ -70,8 +70,8 @@ def add_instance(): { "name": "GHE 1", "simulation-mode": "enhanced", - "g-function-path": os.path.join(f_path, '..', '..', '..', 'validation', 'MFRTRT_LTS', 'g.csv'), - "g_b-function-path": os.path.join(f_path, '..', '..', '..', 'validation', 'MFRTRT_LTS', 'g_b.csv'), + "g-function-path": str(f_path.parent.parent.parent / 'validation' / 'MFRTRT_LTS' / 'g.csv'), + "g_b-function-path": str(f_path.parent.parent.parent / 'validation' / 'MFRTRT_LTS' / 'g_b.csv'), "flow-paths": [ { "name": "path 1", @@ -131,8 +131,8 @@ def add_instance(): "specific-heat": 880 } } - temp_dir = tempfile.mkdtemp() - temp_file = os.path.join(temp_dir, 'temp.json') + temp_dir = Path(tempfile.mkdtemp()) + temp_file = temp_dir / 'temp.json' write_json(temp_file, d) ip = InputProcessor(temp_file) diff --git a/unit_tests/glhe/topology/test_ground_heat_exhanger_short_time_step.py b/unit_tests/glhe/topology/test_ground_heat_exhanger_short_time_step.py index 97450d4d..cc280525 100644 --- a/unit_tests/glhe/topology/test_ground_heat_exhanger_short_time_step.py +++ b/unit_tests/glhe/topology/test_ground_heat_exhanger_short_time_step.py @@ -1,4 +1,4 @@ -import os +from pathlib import Path import tempfile import unittest @@ -12,7 +12,8 @@ class TestGroundHeatExchangerShortTimeStep(unittest.TestCase): @staticmethod def add_instance(): - f_path = os.path.dirname(os.path.abspath(__file__)) + f_path = Path(__file__).parent + root_dir = f_path.parent.parent.parent d = { "borehole-definitions": [ { @@ -70,8 +71,9 @@ def add_instance(): { "name": "GHE 1", "simulation-mode": "direct", - "g-function-path": os.path.join(f_path, '..', '..', '..', 'validation', 'MFRTRT_EWT_g_functions', - 'EWT_experimental_g_functions.csv'), + "g-function-path": str( + root_dir / 'validation' / 'MFRTRT_EWT_g_functions' / 'EWT_experimental_g_functions.csv' + ), "flow-paths": [ { "name": "path 1", @@ -131,8 +133,8 @@ def add_instance(): "specific-heat": 880 } } - temp_dir = tempfile.mkdtemp() - temp_file = os.path.join(temp_dir, 'temp.json') + temp_dir = Path(tempfile.mkdtemp()) + temp_file = temp_dir / 'temp.json' write_json(temp_file, d) ip = InputProcessor(temp_file) diff --git a/unit_tests/glhe/topology/test_path.py b/unit_tests/glhe/topology/test_path.py index 30be723e..1e1ec5a1 100644 --- a/unit_tests/glhe/topology/test_path.py +++ b/unit_tests/glhe/topology/test_path.py @@ -1,4 +1,4 @@ -import os +from pathlib import Path as PythonPath import tempfile import unittest @@ -7,14 +7,11 @@ from glhe.topology.path import Path from glhe.utilities.functions import write_json -join = os.path.join -norm = os.path.normpath - class TestPath(unittest.TestCase): def setUp(self): - self.this_file_directory = os.path.dirname(os.path.realpath(__file__)) + self.this_file_directory = PythonPath(__file__).parent @staticmethod def add_instance(): @@ -100,8 +97,8 @@ def add_instance(): } } - temp_dir = tempfile.mkdtemp() - temp_file = norm(join(temp_dir, 'temp.json')) + temp_dir = PythonPath(tempfile.mkdtemp()) + temp_file = temp_dir / 'temp.json' write_json(temp_file, d) ip = InputProcessor(temp_file) op = OutputProcessor(temp_dir, 'out.csv') diff --git a/unit_tests/glhe/topology/test_pipe.py b/unit_tests/glhe/topology/test_pipe.py index d81acc9b..d9adcc6b 100644 --- a/unit_tests/glhe/topology/test_pipe.py +++ b/unit_tests/glhe/topology/test_pipe.py @@ -1,4 +1,4 @@ -import os +from pathlib import Path import tempfile import unittest from math import log @@ -27,8 +27,8 @@ def add_instance(): 'name': 'pipe 1', 'length': 100}]} - temp_dir = tempfile.mkdtemp() - temp_file = os.path.join(temp_dir, 'temp.json') + temp_dir = Path(tempfile.mkdtemp()) + temp_file = temp_dir / 'temp.json' write_json(temp_file, inputs) ip = InputProcessor(temp_file) diff --git a/unit_tests/glhe/topology/test_single_u_tube_grouted_borehole.py b/unit_tests/glhe/topology/test_single_u_tube_grouted_borehole.py index 6a34debe..2704f215 100644 --- a/unit_tests/glhe/topology/test_single_u_tube_grouted_borehole.py +++ b/unit_tests/glhe/topology/test_single_u_tube_grouted_borehole.py @@ -1,4 +1,4 @@ -import os +from pathlib import Path import sys import tempfile import unittest @@ -58,8 +58,8 @@ def add_instance(inputs: dict = None): 'specific-heat': 1000}] } - temp_dir = tempfile.mkdtemp() - temp_file = os.path.join(temp_dir, 'temp.json') + temp_dir = Path(tempfile.mkdtemp()) + temp_file = temp_dir / 'temp.json' write_json(temp_file, d) ip = InputProcessor(temp_file) diff --git a/unit_tests/glhe/topology/test_single_u_tube_grouted_segment.py b/unit_tests/glhe/topology/test_single_u_tube_grouted_segment.py index ce158a32..ba421335 100644 --- a/unit_tests/glhe/topology/test_single_u_tube_grouted_segment.py +++ b/unit_tests/glhe/topology/test_single_u_tube_grouted_segment.py @@ -1,4 +1,4 @@ -import os +from pathlib import Path import tempfile import unittest @@ -37,8 +37,8 @@ def add_instance(): 'specific-heat': 1900}] } - temp_dir = tempfile.mkdtemp() - temp_file = os.path.join(temp_dir, 'temp.json') + temp_dir = Path(tempfile.mkdtemp()) + temp_file = temp_dir / 'temp.json' write_json(temp_file, d) ip = InputProcessor(temp_file) diff --git a/unit_tests/glhe/topology/test_swedish_heat_pump.py b/unit_tests/glhe/topology/test_swedish_heat_pump.py index a6da4737..a621ef98 100644 --- a/unit_tests/glhe/topology/test_swedish_heat_pump.py +++ b/unit_tests/glhe/topology/test_swedish_heat_pump.py @@ -1,4 +1,4 @@ -import os +from pathlib import Path import tempfile import unittest @@ -14,9 +14,9 @@ class TestSwedishHP(unittest.TestCase): @staticmethod def add_instance(): - temp_dir = tempfile.mkdtemp() + temp_dir = Path(tempfile.mkdtemp()) - with open(os.path.join(temp_dir, 'temp.csv'), 'w') as f: + with (temp_dir / 'temp.csv').open('w') as f: f.write('Date/Time,Heating Loads (W),Water Heating Loads (W),Outdoor Air Temperature (C)\n') f.write('1/1/2019 0:00,751.231087,0,4\n') f.write('1/1/2019 1:00,609.682528,60.515364,5.3\n') @@ -51,14 +51,14 @@ def add_instance(): 'outdoor-air-temperature-at-max-heating-set-point': -10, 'outdoor-air-temperature-at-min-heating-set-point': 20, 'immersion-heater-capacity': 7000, - 'load-data-path': os.path.join(temp_dir, 'temp.csv'), + 'load-data-path': str(temp_dir / 'temp.csv'), 'capacity-coefficients': [8.536666667, -0.007266667, -0.00084, 0.263666667], 'coefficient-of-performance-coefficients': [7.641839817, -0.075098454, -0.000208441, 0.109423218], }]} - temp_file = os.path.join(temp_dir, 'temp.json') + temp_file = temp_dir / 'temp.json' write_json(temp_file, inputs) ip = InputProcessor(temp_file) @@ -70,7 +70,7 @@ def test_init(self): tst = self.add_instance() self.assertIsInstance(tst, SwedishHP) - def test_set_htg_exft(self): + def test_set_htg_exiting_ft(self): tst = self.add_instance() tol = 0.001 # checked against JDS VBA code diff --git a/unit_tests/glhe/utilities/test_functions.py b/unit_tests/glhe/utilities/test_functions.py index 205cac17..b4444f70 100644 --- a/unit_tests/glhe/utilities/test_functions.py +++ b/unit_tests/glhe/utilities/test_functions.py @@ -1,11 +1,11 @@ -import os +from json import loads +from pathlib import Path import tempfile import unittest from math import cos, sin from numpy import arange, array from numpy.linalg import solve as lin_alg_solve -from scipy.interpolate import interp2d from glhe.utilities.functions import c_to_k from glhe.utilities.functions import hanby @@ -14,7 +14,6 @@ from glhe.utilities.functions import lin_interp from glhe.utilities.functions import Interpolator1DFromFile from glhe.utilities.functions import Interpolator2DFromFile -from glhe.utilities.functions import load_json from glhe.utilities.functions import lower_obj from glhe.utilities.functions import merge_dicts from glhe.utilities.functions import num_ts_per_hour_to_sec_per_ts @@ -53,19 +52,19 @@ def test_set_time_step(self): self.assertEqual(num_ts_per_hour_to_sec_per_ts(1), 3600) def test_load_json(self): - temp_directory = tempfile.mkdtemp() - temp_json_file = os.path.join(temp_directory, 'temp.json') - with open(temp_json_file, 'w') as f: + temp_directory = Path(tempfile.mkdtemp()) + temp_json_file = temp_directory / 'temp.json' + with temp_json_file.open('w') as f: f.write('{"key": "value", "key 2": 1}') - d = load_json(temp_json_file) + d = loads(temp_json_file.read_text()) self.assertEqual(d["key"], "value") self.assertEqual(d["key 2"], 1) def test_write_json(self): - temp_directory = tempfile.mkdtemp() - temp_file = os.path.join(temp_directory, 'temp.json') + temp_directory = Path(tempfile.mkdtemp()) + temp_file = temp_directory / 'temp.json' d = { "key": "value", @@ -73,7 +72,7 @@ def test_write_json(self): } write_json(temp_file, d) - loaded = load_json(temp_file) + loaded = loads(temp_file.read_text()) self.assertEqual(d, loaded) def test_hanby(self): @@ -217,9 +216,9 @@ def test_lin_interp(self): self.assertEqual(lin_interp(2, 0, 2, 0, 2), 2) def test_write_arrays_to_csv(self): - temp_dir = tempfile.mkdtemp() + temp_dir = Path(tempfile.mkdtemp()) file_name = 'out.csv' - path = os.path.join(temp_dir, file_name) + path = temp_dir / file_name a_1 = [1, 2, 3] a_2 = [4, 5, 6] write_arrays_to_csv(path, [a_1, a_2]) @@ -259,9 +258,9 @@ class TestInterp1D(unittest.TestCase): @staticmethod def add_instance(): - temp_dir = tempfile.mkdtemp() - f_path = os.path.join(temp_dir, 'temp.csv') - with open(f_path, 'w') as f: + temp_dir = Path(tempfile.mkdtemp()) + f_path = temp_dir / 'temp.csv' + with f_path.open('w') as f: f.write('0,5\n') f.write('1,6\n') f.write('2,7\n') @@ -280,9 +279,9 @@ class TestInterp2D(unittest.TestCase): @staticmethod def add_instance(): - temp_dir = tempfile.mkdtemp() - f_path = os.path.join(temp_dir, 'temp.csv') - with open(f_path, 'w') as f: + temp_dir = Path(tempfile.mkdtemp()) + f_path = temp_dir / 'temp.csv' + with f_path.open('w') as f: f.write('0,5,8\n') f.write('1,6,9\n') f.write('2,7,10\n') diff --git a/unit_tests/standalone/test_plant_loop.py b/unit_tests/standalone/test_plant_loop.py index 6b8cad46..038f5654 100644 --- a/unit_tests/standalone/test_plant_loop.py +++ b/unit_tests/standalone/test_plant_loop.py @@ -1,28 +1,25 @@ -import os +from json import loads +from pathlib import Path import tempfile import unittest -from glhe.utilities.functions import load_json from glhe.utilities.functions import write_json from glhe.standalone.plant_loop import PlantLoop -norm = os.path.normpath -join = os.path.join - class TestPlantLoop(unittest.TestCase): def setUp(self): - self.this_file_directory = os.path.dirname(os.path.realpath(__file__)) + self.this_file_directory = Path(__file__).parent def test_simulate(self): - temp_dir = tempfile.mkdtemp() - temp_file = join(temp_dir, 'in.json') - input_path = norm(join(self.this_file_directory, '..', '..', 'test_files', 'single.json')) - d = load_json(input_path) - g_path = norm(join(self.this_file_directory, '..', '..', 'test_files', 'single_g_functions.csv')) - d['ground-heat-exchanger'][0]['g-function-path'] = g_path - d['simulation']['output-path'] = temp_dir + temp_dir = Path(tempfile.mkdtemp()) + temp_file = temp_dir / 'in.json' + input_path = self.this_file_directory.parent.parent / 'test_files' / 'single.json' + d = loads(input_path.read_text()) + g_path = self.this_file_directory.parent.parent / 'test_files' / 'single_g_functions.csv' + d['ground-heat-exchanger'][0]['g-function-path'] = str(g_path) + d['simulation']['output-path'] = str(temp_dir) write_json(temp_file, d) p = PlantLoop(temp_file) self.assertTrue(p.simulate()) From f48ac14bb204d0b563225667ec74399e1fe1a269 Mon Sep 17 00:00:00 2001 From: Edwin Lee Date: Mon, 15 Apr 2024 16:08:38 -0500 Subject: [PATCH 3/8] Eliminated pandas from external_base --- glhe/profiles/external_base.py | 66 +++++++++++++++++++++++++++------- glhe/utilities/functions.py | 10 +++--- 2 files changed, 59 insertions(+), 17 deletions(-) diff --git a/glhe/profiles/external_base.py b/glhe/profiles/external_base.py index fca397da..79b346aa 100644 --- a/glhe/profiles/external_base.py +++ b/glhe/profiles/external_base.py @@ -1,27 +1,69 @@ from pathlib import Path -import pandas as pd - +import csv +from datetime import datetime from glhe.utilities.functions import Interpolator1D class ExternalBase(object): def __init__(self, path: Path, col_num: int): - - df = pd.read_csv(str(path), index_col=0, parse_dates=True) - df['delta t'] = df.index.to_series().diff().dt.total_seconds() - df['delta t'].iat[0] = 0 - x_range = df['delta t'].cumsum().tolist() - y_range = df.iloc[:, col_num].tolist() - + x_range, y_range = self.read_csv_and_compute_delta_t(path, col_num) # added to allow multi-year simulations self.max_time = 0 - x_range.append(x_range[-1] + (x_range[-1] - x_range[-2])) - y_range.append(y_range[0]) self.max_time = x_range[-1] - self._interp_values = Interpolator1D(x_range, y_range) + @staticmethod + def read_csv_and_compute_delta_t(file_path: Path, col_num: int) -> [list, list]: + delta_t_values = [] + y_values = [] + last_timestamp = None + + # Open CSV file and read data + with open(file_path, 'r') as csvfile: + csvreader = csv.reader(csvfile) + next(csvreader) # Read header + + for row in csvreader: + timestamp_str = row[0] + + # Convert timestamp string to datetime object + current_timestamp = None + time_stamp_formats = ['%Y-%m-%d %H:%M:%S', '%m/%d/%Y %H:%M', '%m/%d/%Y %H:%M:%S'] + for t in time_stamp_formats: + try: + current_timestamp = datetime.strptime(timestamp_str, t) + break + except ValueError: + continue + if not current_timestamp: + raise ValueError(f"Bad timestamp format for timestamp: {timestamp_str}") + + if last_timestamp is not None: + # Calculate delta t in seconds + delta_t = (current_timestamp - last_timestamp).total_seconds() + delta_t_values.append(delta_t) + + # Append value to y_values + y_values.append(row[col_num + 1]) + + last_timestamp = current_timestamp + + # Initialize x_range with cumulative sum of delta_t_values + x_range = [0] + cumulative_sum = 0 + for delta_t in delta_t_values: + cumulative_sum += delta_t + x_range.append(cumulative_sum) + + # Append an extrapolated value to x_range + x_range.append(x_range[-1] + (x_range[-1] - x_range[-2])) + + # Append the first y_value to y_range + y_values.append(y_values[0]) + + return x_range, y_values + def get_value(self, time) -> float: return float(self._interp_values.interpolate(time % self.max_time)) diff --git a/glhe/utilities/functions.py b/glhe/utilities/functions.py index 4d730e2c..58467bfb 100644 --- a/glhe/utilities/functions.py +++ b/glhe/utilities/functions.py @@ -1,3 +1,4 @@ +import csv import json from abc import ABC, abstractmethod from math import ceil, exp, factorial, floor @@ -5,7 +6,6 @@ from typing import Callable, overload import numpy as np -import pandas as pd from scipy.interpolate import RegularGridInterpolator from scipy.interpolate import interp1d @@ -476,10 +476,10 @@ def write_arrays_to_csv(path: Path, arrays: list | np.ndarray) -> None: _arrays = np.array(arrays) else: _arrays = arrays - - df = pd.DataFrame.from_records(_arrays) - df = df.T - df.to_csv(str(path), header=False, index=False) + data = _arrays.T + with open(path, 'w') as f: + writer = csv.writer(f) + writer.writerows(data) class InterpolatorBase(ABC): From a4521d5c7009f11467e5e1836c42a63868cc8432 Mon Sep 17 00:00:00 2001 From: Edwin Lee Date: Mon, 15 Apr 2024 16:43:49 -0500 Subject: [PATCH 4/8] Eliminate Pandas from required dependencies --- glhe/output_processor/output_processor.py | 37 ++++++++++++------- requirements.txt | 8 +++- setup.py | 3 +- .../output_processor/test_output_processor.py | 19 ++++++---- 4 files changed, 44 insertions(+), 23 deletions(-) diff --git a/glhe/output_processor/output_processor.py b/glhe/output_processor/output_processor.py index 1adf37a3..fd78d693 100644 --- a/glhe/output_processor/output_processor.py +++ b/glhe/output_processor/output_processor.py @@ -1,8 +1,6 @@ import datetime as dt from pathlib import Path -import pandas as pd - class OutputProcessor: @@ -14,7 +12,8 @@ def __init__(self, output_dir: Path, output_name: str): self.output_dir = output_dir self.output_file = output_name self.write_path = output_dir / output_name - self.df = pd.DataFrame() + # self.df = pd.DataFrame() + self.output_data = [] self.idx_count = 0 def collect_output(self, data_dict: dict) -> None: @@ -23,10 +22,10 @@ def collect_output(self, data_dict: dict) -> None: :param data_dict: dictionary of data to be logged """ - - df_temp = pd.DataFrame(data_dict, index=[self.idx_count]) - self.df = pd.concat([self.df, df_temp], axis=0, sort=True) - self.idx_count += 1 + self.output_data.append(data_dict) + # df_temp = pd.DataFrame(data_dict, index=[self.idx_count]) + # self.df = pd.concat([self.df, df_temp], axis=0, sort=True) + # self.idx_count += 1 def write_to_file(self) -> None: """ @@ -35,18 +34,28 @@ def write_to_file(self) -> None: if self.write_path.exists(): self.write_path.unlink() - self.convert_time_to_timestamp() - self.df.to_csv(str(self.write_path)) # TODO: can probably just write it ourselves + header_row = ['Date/Time'] + header_row.extend([key for key in sorted(self.output_data[0].keys()) if key != 'Elapsed Time [s]']) + + time_stamps = self.convert_time_to_timestamp() + with self.write_path.open('w') as f: + for i, d in enumerate(self.output_data): + if i == 0: + f.write(','.join(header_row) + '\n') + row = [time_stamps[i]] + sorted_values_without_time = [str(d[key]) for key in sorted(d.keys()) if key != 'Elapsed Time [s]'] + row.extend(sorted_values_without_time) + f.write(','.join(row) + '\n') - def convert_time_to_timestamp(self) -> None: + def convert_time_to_timestamp(self) -> list[str]: """ Convert the 'Elapsed Time' column to a standardized date/time format. """ try: - dts = [dt.timedelta(seconds=x) for x in self.df['Elapsed Time [s]'].values.tolist()] + raw_dts = [d['Elapsed Time [s]'] for d in self.output_data] + dts = [dt.timedelta(seconds=x) for x in raw_dts] start_time = dt.datetime(year=dt.datetime.now().year, month=1, day=1, hour=0, minute=0) - time_stamps = [start_time + x for x in dts] - self.df['Date/Time'] = time_stamps - self.df.set_index('Date/Time', inplace=True) + time_stamps = [str(start_time + x) for x in dts] + return time_stamps except KeyError: pass diff --git a/requirements.txt b/requirements.txt index f7f28901..e279ecfb 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,10 +2,15 @@ CoolProp jsonschema numpy -pandas pygfunction scipy +###### AFTER THIS LINE ARE OPTIONAL DEVELOPER DEPENDENCIES ###### +######### KEEP THE LINE SAYING "OPTIONAL" IN PLACE HERE ######### +######### IT IS USED BY SETUP.PY WHEN BUILDING PACKAGES ######### + +# OPTIONAL # + # Dependencies needed to build wheels for PyPi wheel @@ -18,6 +23,7 @@ pytest flake8 coverage coveralls +pandas # Dependencies needed for running the extra validation studies and such matplotlib diff --git a/setup.py b/setup.py index 3cf49f17..8d4eded7 100755 --- a/setup.py +++ b/setup.py @@ -4,7 +4,8 @@ setup_py_file = Path(__file__).resolve() repo_root = setup_py_file.parent requirements_path = repo_root / 'requirements.txt' -requirements = requirements_path.read_text().strip().splitlines() +requirements_contents = requirements_path.read_text().split('# OPTIONAL #')[0] +requirements = requirements_contents.strip().splitlines() setup( name='GLHE', diff --git a/unit_tests/glhe/output_processor/test_output_processor.py b/unit_tests/glhe/output_processor/test_output_processor.py index 386705b1..78dbb008 100644 --- a/unit_tests/glhe/output_processor/test_output_processor.py +++ b/unit_tests/glhe/output_processor/test_output_processor.py @@ -1,4 +1,5 @@ from pathlib import Path +from datetime import datetime, timedelta import tempfile import unittest @@ -23,14 +24,13 @@ def test_collect_output(self): tst.collect_output(d) - self.assertEqual(tst.df['foo'][0], 1) - self.assertEqual(tst.df['bar'][0], 2) + self.assertEqual(tst.output_data[0]['foo'], 1) + self.assertEqual(tst.output_data[0]['bar'], 2) def test_write_to_file(self): tst = self.add_instance() - d = {'foo': 1, - 'bar': 2} + d = {'Elapsed Time [s]': 60, 'foo': 1, 'bar': 2} tst.collect_output(d) tst.write_to_file() @@ -51,6 +51,11 @@ def test_write_to_file(self): def test_convert_time_to_timestamp(self): tst = self.add_instance() - tst.df = pd.DataFrame({'Elapsed Time [s]': [0, 60, 120], 'Variable': [1, 2, 3]}) - tst.convert_time_to_timestamp() - self.assertTrue(tst.df.index.name == 'Date/Time') + tst.output_data = [ + {'Elapsed Time [s]': 0, 'Variable': 1}, + {'Elapsed Time [s]': 60, 'Variable': 2}, + {'Elapsed Time [s]': 120, 'Variable': 3}, + ] + time_stamps = tst.convert_time_to_timestamp() + date_times = [datetime.strptime(ts, "%Y-%m-%d %H:%M:%S") for ts in time_stamps] + self.assertEqual(date_times[1] - date_times[0], timedelta(minutes=1)) From 0945bce042d0d8b6c327dca3af7e905dfffdec20 Mon Sep 17 00:00:00 2001 From: Edwin Lee Date: Mon, 15 Apr 2024 16:50:08 -0500 Subject: [PATCH 5/8] Add comment placeholder for using our own RK45 integrator on the u-tube segment; one step closer to eliminating scipy --- glhe/topology/single_u_tube_grouted_segment.py | 2 ++ glhe/utilities/functions.py | 6 +++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/glhe/topology/single_u_tube_grouted_segment.py b/glhe/topology/single_u_tube_grouted_segment.py index 497497e3..99e5d518 100644 --- a/glhe/topology/single_u_tube_grouted_segment.py +++ b/glhe/topology/single_u_tube_grouted_segment.py @@ -8,6 +8,7 @@ from glhe.output_processor.report_types import ReportTypes from glhe.properties.base_properties import PropertiesBase from glhe.topology.pipe import Pipe +# from glhe.utilities.functions import runge_kutta_fourth_y @dataclass @@ -160,6 +161,7 @@ def simulate_time_step(self, time_step: float, inputs: TimeStepStructure) -> np. solver = RK45(self.right_hand_side, 0, self.y, time_step) while solver.status != 'finished': solver.step() + # solver_2 = runge_kutta_fourth_y(self.right_hand_side, time_step, self.y) self.y = solver.y # update report vars diff --git a/glhe/utilities/functions.py b/glhe/utilities/functions.py index 58467bfb..fe300f83 100644 --- a/glhe/utilities/functions.py +++ b/glhe/utilities/functions.py @@ -492,7 +492,7 @@ class Interpolator1D(InterpolatorBase): def __init__(self, x_data: np.ndarray, y_data: np.ndarray | list[float]): """ - 1D Interpolation Class, currently a wrapper for scipy interpolator, but soon just a simple interpolator + 1D Interpolation Class, currently a wrapper for sci py interpolator, but soon just a simple interpolator :param x_data: Numpy array of x-value floats :param y_data: Numpy array of x-value floats @@ -506,7 +506,7 @@ def interpolate(self, *args: list[float]) -> float: class Interpolator1DFromFile(Interpolator1D): def __init__(self, data_path: Path): """ - 1D Interpolation Class, currently a wrapper for scipy interpolator, but soon just a simple interpolator + 1D Interpolation Class, currently a wrapper for sci py interpolator, but soon just a simple interpolator :param data_path: path to csv file with columned data, e.g. 'x1,y1' """ @@ -520,7 +520,7 @@ def __init__(self, data_path: Path): class Interpolator2DFromFile(InterpolatorBase): def __init__(self, xz_data_path: Path, y: list): """ - 2D interpolation class, currently a wrapper for scipy interpolator, but soon just a simple interpolator + 2D interpolation class, currently a wrapper for sci py interpolator, but soon just a simple interpolator Example data: x1, y1, z1, x2, y2, z2 From 338458294e2c13fd58599fbfe6a446d4f3c5645c Mon Sep 17 00:00:00 2001 From: Edwin Lee Date: Tue, 16 Apr 2024 09:14:00 -0500 Subject: [PATCH 6/8] Trim whitespace from csv before reading --- unit_tests/glhe/utilities/test_functions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/unit_tests/glhe/utilities/test_functions.py b/unit_tests/glhe/utilities/test_functions.py index b4444f70..338d3ec9 100644 --- a/unit_tests/glhe/utilities/test_functions.py +++ b/unit_tests/glhe/utilities/test_functions.py @@ -225,7 +225,7 @@ def test_write_arrays_to_csv(self): with open(path, 'r') as f: for idx, line in enumerate(f): - tokens = line.split(',') + tokens = line.strip().split(',') self.assertEqual(float(tokens[0]), a_1[idx]) self.assertEqual(float(tokens[1]), a_2[idx]) From b4e25b7e355d460442b7e7380a40b2de32656926 Mon Sep 17 00:00:00 2001 From: Edwin Lee Date: Tue, 16 Apr 2024 09:20:20 -0500 Subject: [PATCH 7/8] Maybe it is a blank line causing problems --- unit_tests/glhe/utilities/test_functions.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/unit_tests/glhe/utilities/test_functions.py b/unit_tests/glhe/utilities/test_functions.py index 338d3ec9..5c0025db 100644 --- a/unit_tests/glhe/utilities/test_functions.py +++ b/unit_tests/glhe/utilities/test_functions.py @@ -225,6 +225,8 @@ def test_write_arrays_to_csv(self): with open(path, 'r') as f: for idx, line in enumerate(f): + if line.strip() == '': + break tokens = line.strip().split(',') self.assertEqual(float(tokens[0]), a_1[idx]) self.assertEqual(float(tokens[1]), a_2[idx]) From 0f246da68b7ca3d0cc3a524e09adf1b8d018b6c9 Mon Sep 17 00:00:00 2001 From: Edwin Lee Date: Tue, 16 Apr 2024 09:30:13 -0500 Subject: [PATCH 8/8] Stripped whitespace in another spot --- unit_tests/glhe/utilities/test_functions.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/unit_tests/glhe/utilities/test_functions.py b/unit_tests/glhe/utilities/test_functions.py index 5c0025db..f8cd7b43 100644 --- a/unit_tests/glhe/utilities/test_functions.py +++ b/unit_tests/glhe/utilities/test_functions.py @@ -239,7 +239,9 @@ def test_write_arrays_to_csv(self): with open(path, 'r') as f: for idx, line in enumerate(f): - tokens = line.split(',') + if line.strip() == '': + break + tokens = line.strip().split(',') self.assertEqual(float(tokens[0]), a_1[idx]) self.assertEqual(float(tokens[1]), a_2[idx])