From 86f9856e81e02a5424a0ce14d08e0034e76e18e9 Mon Sep 17 00:00:00 2001 From: xaviernogueira Date: Fri, 17 Nov 2023 12:49:44 -0500 Subject: [PATCH 01/16] moving TSM computation tests --- tests_deprecated/tsm_float.py => tests/test_5_tsm_calculations.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tests_deprecated/tsm_float.py => tests/test_5_tsm_calculations.py (100%) diff --git a/tests_deprecated/tsm_float.py b/tests/test_5_tsm_calculations.py similarity index 100% rename from tests_deprecated/tsm_float.py rename to tests/test_5_tsm_calculations.py From 569285734a1d709e7ea737c6e15333bdaa6d4d02 Mon Sep 17 00:00:00 2001 From: xaviernogueira Date: Fri, 17 Nov 2023 15:02:09 -0500 Subject: [PATCH 02/16] Making all default static variable values type=float excplicitly --- src/clearwater_modules/tsm/constants.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/clearwater_modules/tsm/constants.py b/src/clearwater_modules/tsm/constants.py index f42dd04..6c63e38 100644 --- a/src/clearwater_modules/tsm/constants.py +++ b/src/clearwater_modules/tsm/constants.py @@ -38,8 +38,8 @@ class Meteorological(TypedDict): DEFAULT_METEOROLOGICAL = Meteorological( - air_temp_c=20, - q_solar=400, + air_temp_c=20.0, + q_solar=400.0, sed_temp_c=5.0, eair_mb=1.0, pressure_mb=1013.0, @@ -53,7 +53,7 @@ class Meteorological(TypedDict): DEFAULT_TEMPERATURE = Temperature( stefan_boltzmann=5.67e-8, - cp_air=1005, + cp_air=1005.0, emissivity_water=0.97, gravity=-9.806, a0=6984.505294, From 39d78f66214eecc4f2bc4d555f1cf81293902eb5 Mon Sep 17 00:00:00 2001 From: xaviernogueira Date: Fri, 17 Nov 2023 15:03:45 -0500 Subject: [PATCH 03/16] Create tsm_debugger.py --- examples/dev_sandbox/tsm_debugger.py | 37 ++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 examples/dev_sandbox/tsm_debugger.py diff --git a/examples/dev_sandbox/tsm_debugger.py b/examples/dev_sandbox/tsm_debugger.py new file mode 100644 index 0000000..f668c5e --- /dev/null +++ b/examples/dev_sandbox/tsm_debugger.py @@ -0,0 +1,37 @@ +"""A script to allow for debugging of the TSM module.""" +import clearwater_modules +import numpy as np + + +def main(): + # define starting state values + state_i = { + 'water_temp_c': 20.0, + 'surface_area': 1.0, + 'volume': 1.0, + } + + # instantiate the TSM module + tsm = clearwater_modules.tsm.EnergyBudget( + initial_state_values=state_i, + ) + + input_dataset = tsm.dataset.isel(time_step=0).copy() + + inputs = map( + lambda x: clearwater_modules.utils._prep_inputs(input_dataset, x), + tsm.computation_order, + ) + dims = input_dataset.dims + + for name, func, arrays in inputs: + print(name) + array: np.ndarray = func(*arrays) + input_dataset[name] = (dims, array) + print(array) + print() + return input_dataset + + +if __name__ == '__main__': + main() From eba3869afda68f966ee50ce81e39fef117d25f01 Mon Sep 17 00:00:00 2001 From: xaviernogueira Date: Fri, 17 Nov 2023 17:08:55 -0500 Subject: [PATCH 04/16] Calculation tests implemented for TSM --- tests/test_5_tsm_calculations.py | 862 +++++++++++++++---------------- 1 file changed, 405 insertions(+), 457 deletions(-) diff --git a/tests/test_5_tsm_calculations.py b/tests/test_5_tsm_calculations.py index 3e1a60f..2b4ff68 100644 --- a/tests/test_5_tsm_calculations.py +++ b/tests/test_5_tsm_calculations.py @@ -5,492 +5,440 @@ import pytest from clearwater_modules.tsm import EnergyBudget +from clearwater_modules.tsm.constants import ( + Meteorological, + Temperature, +) -@pytest.fixture(scope='module') -def use_sed_temp() -> bool: - return True +@pytest.fixture(scope='function') +def initial_tsm_state() -> dict[str, float]: + """Return initial state values for the model.""" + return { + 'water_temp_c': 20.0, + 'surface_area': 1.0, + 'volume': 1.0, + } -@pytest.fixture(scope='module') -def vars() -> typed.Dict: - vars = typed.Dict.empty( - key_type=types.unicode_type, - value_type=types.float64, +@pytest.fixture(scope='function') +def default_meteo_params() -> Meteorological: + """Returns default meteorological static variable values for the model. + + NOTE: As of now (11/17/2023) these match the built in defaults, but are + copied here to allow for easy modification of the defaults in the future. + + Returns a typed dictionary, with string keys and float values. + """ + return Meteorological( + air_temp_c=20, + q_solar=400, + sed_temp_c=5.0, + eair_mb=1.0, + pressure_mb=1013.0, + cloudiness=0.1, + wind_speed=3.0, + wind_a=0.3, + wind_b=1.5, + wind_c=1.0, + wind_kh_kw=1.0, ) - vars = { - 'water_temp_c': 20, - 'surface_area': 1, - 'volume': 1 - } - return vars -@pytest.fixture(scope='module') -def t_changes() -> typed.Dict: - return typed.Dict.empty( - key_type=types.unicode_type, - value_type=types.float64, +@pytest.fixture(scope='function') +def default_temp_params() -> Temperature: + """Returns default temperature static variable values for the model. + + NOTE: As of now (11/17/2023) these match the built in defaults, but are + copied here to allow for easy modification of the defaults in the future. + + Returns a typed dictionary, with string keys and float or bool values. + """ + return Temperature( + stefan_boltzmann=5.67e-8, + cp_air=1005, + emissivity_water=0.97, + gravity=-9.806, + a0=6984.505294, + a1=-188.903931, + a2=2.133357675, + a3=-1.288580973E-2, + a4=4.393587233E-5, + a5=-8.023923082E-8, + a6=6.136820929E-11, + pb=1600.0, + cps=1673.0, + h2=0.1, + alphas=0.0432, + richardson_option=True, ) - -@pytest.fixture(scope='module') -def met_changes() -> typed.Dict: - return typed.Dict.empty( - key_type=types.unicode_type, - value_type=types.float64, +def get_energy_budget_instance( + initial_tsm_state, + default_meteo_params, + default_temp_params, +) -> EnergyBudget: + """Return an instance of the TSM class.""" + return EnergyBudget( + initial_state_values=initial_tsm_state, + meteo_parameters=default_meteo_params, + temp_parameters=default_temp_params, + time_dim='tsm_time_step', ) @pytest.fixture(scope='module') def tolerance() -> float: """Controls the precision of the pytest.approx() function.""" - return 0.0001 - - -@pytest.fixture(scope='module') -def answers() -> float: - return { - 'test_water_temp_c_20': 19.997, - 'test_water_temp_c_40': 39.964, - 'test_surface_area_2': 19.994, - 'test_surface_area_4': 19.987, - 'test_volume_2': 19.998, - 'test_volume_4': 19.999, - 'test_air_temp_c_30': 20, - 'test_air_temp_c_40': 20.001, - 'test_sed_temp_c_10': 19.999, - 'test_sed_temp_c_15': 20.001, - 'test_q_solar_450': 19.997, - 'test_q_solar_350': 19.996, - 'test_wind_kh_kw_0_5': 19.997, - 'test_wind_kh_kw_1_5': 19.997, - 'test_eair_mb_2': 19.997, - 'test_eair_mb_5': 19.997, - 'test_pressure_mb_970': 19.997, - 'test_pressure_mb_1050': 19.997, - 'test_cloudiness_0': 19.997, - 'test_cloudiness_0_5': 19.997, - 'test_wind_speed_5': 19.997, - 'test_wind_speed_30': 19.983, - 'test_wind_a_1En7': 19.997, - 'test_wind_a_7En7': 19.997, - 'test_wind_b_1En6': 19.997, - 'test_wind_b_2En6': 19.996, - 'test_wind_c_0_5': 19.998, - 'test_wind_c_3': 19.980, - } - - -def test_water_temp_c_20(use_sed_temp, vars, met_changes, t_changes, tolerance, answers) -> None: - - vars['water_temp_c'] = 20 - - dwater_temp_cdt = EnergyBudget( - met_changes, - t_changes, - use_sed_temp=use_sed_temp, - ).run(variables=vars) - water_temp_c = vars['water_temp_c'] + dwater_temp_cdt * 60 - - assert pytest.approx( - water_temp_c, tolerance) == answers['test_water_temp_c_20'] - - -def test_water_temp_c_40(use_sed_temp, vars, met_changes, t_changes, tolerance, answers) -> None: - - vars['water_temp_c'] = 40 - - dwater_temp_cdt = EnergyBudget( - met_changes, - t_changes, - use_sed_temp=use_sed_temp, - ).run(variables=vars) - water_temp_c = vars['water_temp_c'] + dwater_temp_cdt * 60 - - assert pytest.approx( - water_temp_c, tolerance) == answers['test_water_temp_c_40'] - - -def test_surface_area_2(use_sed_temp, vars, met_changes, t_changes, tolerance, answers) -> None: - - vars['surface_area'] = 2 - - dwater_temp_cdt = EnergyBudget( - met_changes, - t_changes, - use_sed_temp=use_sed_temp, - ).run(variables=vars) - water_temp_c = vars['water_temp_c'] + dwater_temp_cdt * 60 - - assert pytest.approx( - water_temp_c, tolerance) == answers['test_surface_area_2'] - - -def test_surface_area_4(use_sed_temp, vars, met_changes, t_changes, tolerance, answers) -> None: - - vars['surface_area'] = 4 - - dwater_temp_cdt = EnergyBudget( - met_changes, - t_changes, - use_sed_temp=use_sed_temp, - ).run(variables=vars) - water_temp_c = vars['water_temp_c'] + dwater_temp_cdt * 60 - - assert pytest.approx( - water_temp_c, tolerance) == answers['test_surface_area_4'] - - -def test_volume_2(use_sed_temp, vars, met_changes, t_changes, tolerance, answers) -> None: - - vars['volume'] = 2 - - dwater_temp_cdt = EnergyBudget( - met_changes, - t_changes, - use_sed_temp=use_sed_temp, - ).run(variables=vars) - water_temp_c = vars['water_temp_c'] + dwater_temp_cdt * 60 - - assert pytest.approx(water_temp_c, tolerance) == answers['test_volume_2'] - - -def test_volume_4(use_sed_temp, vars, met_changes, t_changes, tolerance, answers) -> None: - - vars['volume'] = 4 - - dwater_temp_cdt = EnergyBudget( - met_changes, - t_changes, - use_sed_temp=use_sed_temp, - ).run(variables=vars) - water_temp_c = vars['water_temp_c'] + dwater_temp_cdt * 60 - - assert pytest.approx(water_temp_c, tolerance) == answers['test_volume_4'] - - -def test_air_temp_c_30(use_sed_temp, vars, met_changes, t_changes, tolerance, answers) -> None: - - met_changes['air_temp_c'] = 30 - - dwater_temp_cdt = EnergyBudget( - met_changes, - t_changes, - use_sed_temp=use_sed_temp, - ).run(variables=vars) - water_temp_c = vars['water_temp_c'] + dwater_temp_cdt * 60 - - assert pytest.approx( - water_temp_c, tolerance) == answers['test_air_temp_c_30'] - - -def test_air_temp_c_40(use_sed_temp, vars, met_changes, t_changes, tolerance, answers) -> None: - - met_changes['air_temp_c'] = 40 - - dwater_temp_cdt = EnergyBudget( - met_changes, - t_changes, - use_sed_temp=use_sed_temp, - ).run(variables=vars) - water_temp_c = vars['water_temp_c'] + dwater_temp_cdt * 60 - - assert pytest.approx( - water_temp_c, tolerance) == answers['test_air_temp_c_40'] - - -def test_sed_temp_c_10(use_sed_temp, vars, met_changes, t_changes, tolerance, answers) -> None: - - met_changes['sed_temp_c'] = 10 - - dwater_temp_cdt = EnergyBudget( - met_changes, - t_changes, - use_sed_temp=use_sed_temp, - ).run(variables=vars) - water_temp_c = vars['water_temp_c'] + dwater_temp_cdt * 60 - - assert pytest.approx( - water_temp_c, tolerance) == answers['test_sed_temp_c_10'] - - -def test_sed_temp_c_15(use_sed_temp, vars, met_changes, t_changes, tolerance, answers) -> None: - - met_changes['sed_temp_c'] = 15 - - dwater_temp_cdt = EnergyBudget( - met_changes, - t_changes, - use_sed_temp=use_sed_temp, - ).run(variables=vars) - water_temp_c = vars['water_temp_c'] + dwater_temp_cdt * 60 - - assert pytest.approx( - water_temp_c, tolerance) == answers['test_sed_temp_c_15'] - - -def test_q_solar_450(use_sed_temp, vars, met_changes, t_changes, tolerance, answers) -> None: - - met_changes['q_solar'] = 450 - - dwater_temp_cdt = EnergyBudget( - met_changes, - t_changes, - use_sed_temp=use_sed_temp, - ).run(variables=vars) - water_temp_c = vars['water_temp_c'] + dwater_temp_cdt * 60 - - assert pytest.approx( - water_temp_c, tolerance) == answers['test_q_solar_450'] - - -def test_q_solar_350(use_sed_temp, vars, met_changes, t_changes, tolerance, answers) -> None: - - met_changes['q_solar'] = 350 - - dwater_temp_cdt = EnergyBudget( - met_changes, - t_changes, - use_sed_temp=use_sed_temp, - ).run(variables=vars) - water_temp_c = vars['water_temp_c'] + dwater_temp_cdt * 60 - - assert pytest.approx( - water_temp_c, tolerance) == answers['test_q_solar_350'] - - -def test_wind_kh_kw_0_5(use_sed_temp, vars, met_changes, t_changes, tolerance, answers) -> None: - - met_changes['wind_kh_kw'] = .5 - - dwater_temp_cdt = EnergyBudget( - met_changes, - t_changes, - use_sed_temp=use_sed_temp, - ).run(variables=vars) - water_temp_c = vars['water_temp_c'] + dwater_temp_cdt * 60 - - assert pytest.approx( - water_temp_c, tolerance) == answers['test_wind_kh_kw_0_5'] - - -def test_wind_kh_kw_1_5(use_sed_temp, vars, met_changes, t_changes, tolerance, answers) -> None: - - met_changes['wind_kh_kw'] = 1.5 - - dwater_temp_cdt = EnergyBudget( - met_changes, - t_changes, - use_sed_temp=use_sed_temp, - ).run(variables=vars) - water_temp_c = vars['water_temp_c'] + dwater_temp_cdt * 60 - - assert pytest.approx( - water_temp_c, tolerance) == answers['test_wind_kh_kw_1_5'] - - -def test_eair_mb_2(use_sed_temp, vars, met_changes, t_changes, tolerance, answers) -> None: - - met_changes['eair_mb'] = 2 - - dwater_temp_cdt = EnergyBudget( - met_changes, - t_changes, - use_sed_temp=use_sed_temp, - ).run(variables=vars) - water_temp_c = vars['water_temp_c'] + dwater_temp_cdt * 60 - - assert pytest.approx(water_temp_c, tolerance) == answers['test_eair_mb_2'] - - -def test_eair_mb_5(use_sed_temp, vars, met_changes, t_changes, tolerance, answers) -> None: - - met_changes['eair_mb'] = 5 - - dwater_temp_cdt = EnergyBudget( - met_changes, - t_changes, - use_sed_temp=use_sed_temp, - ).run(variables=vars) - water_temp_c = vars['water_temp_c'] + dwater_temp_cdt * 60 - - assert pytest.approx(water_temp_c, tolerance) == answers['test_eair_mb_5'] - - -def test_pressure_mb_970(use_sed_temp, vars, met_changes, t_changes, tolerance, answers) -> None: - - met_changes['pressure_mb'] = 970 - - dwater_temp_cdt = EnergyBudget( - met_changes, - t_changes, - use_sed_temp=use_sed_temp, - ).run(variables=vars) - water_temp_c = vars['water_temp_c'] + dwater_temp_cdt * 60 - - assert pytest.approx( - water_temp_c, tolerance) == answers['test_pressure_mb_970'] - - -def test_pressure_mb_1050(use_sed_temp, vars, met_changes, t_changes, tolerance, answers) -> None: - - met_changes['pressure_mb'] = 1050 - - dwater_temp_cdt = EnergyBudget( - met_changes, - t_changes, - use_sed_temp=use_sed_temp, - ).run(variables=vars) - water_temp_c = vars['water_temp_c'] + dwater_temp_cdt * 60 - - assert pytest.approx(water_temp_c, tolerance) == answers[ - 'test_pressure_mb_1050'] - - -def test_cloudiness_0(use_sed_temp, vars, met_changes, t_changes, tolerance, answers) -> None: - - met_changes['cloudiness'] = 0 - - dwater_temp_cdt = EnergyBudget( - met_changes, - t_changes, - use_sed_temp=use_sed_temp, - ).run(variables=vars) - water_temp_c = vars['water_temp_c'] + dwater_temp_cdt * 60 - - assert pytest.approx( - water_temp_c, tolerance) == answers['test_cloudiness_0'] - - -def test_cloudiness_0_5(use_sed_temp, vars, met_changes, t_changes, tolerance, answers) -> None: - - met_changes['cloudiness'] = .5 - - dwater_temp_cdt = EnergyBudget( - met_changes, - t_changes, - use_sed_temp=use_sed_temp, - ).run(variables=vars) - water_temp_c = vars['water_temp_c'] + dwater_temp_cdt * 60 - - assert pytest.approx( - water_temp_c, tolerance) == answers['test_cloudiness_0_5'] - - -def test_wind_speed_5(use_sed_temp, vars, met_changes, t_changes, tolerance, answers) -> None: - - met_changes['wind_speed'] = 5 - - dwater_temp_cdt = EnergyBudget( - met_changes, - t_changes, - use_sed_temp=use_sed_temp, - ).run(variables=vars) - water_temp_c = vars['water_temp_c'] + dwater_temp_cdt * 60 - - assert pytest.approx( - water_temp_c, tolerance) == answers['test_wind_speed_5'] - - -def test_wind_speed_30(use_sed_temp, vars, met_changes, t_changes, tolerance, answers) -> None: - - met_changes['wind_speed'] = 30 - - dwater_temp_cdt = EnergyBudget( - met_changes, - t_changes, - use_sed_temp=use_sed_temp, - ).run(variables=vars) - water_temp_c = vars['water_temp_c'] + dwater_temp_cdt * 60 - - assert pytest.approx( - water_temp_c, tolerance) == answers['test_wind_speed_30'] - - -def test_wind_a_1En7(use_sed_temp, vars, met_changes, t_changes, tolerance, answers) -> None: - - met_changes['wind_a'] = 1*10**-7 - - dwater_temp_cdt = EnergyBudget( - met_changes, - t_changes, - use_sed_temp=use_sed_temp, - ).run(variables=vars) - water_temp_c = vars['water_temp_c'] + dwater_temp_cdt * 60 - - assert pytest.approx( - water_temp_c, tolerance) == answers['test_wind_a_1En7'] - - -def test_wind_a_7En7(use_sed_temp, vars, met_changes, t_changes, tolerance, answers) -> None: - - met_changes['wind_a'] = 7*10**-7 - - dwater_temp_cdt = EnergyBudget( - met_changes, - t_changes, - use_sed_temp=use_sed_temp, - ).run(variables=vars) - water_temp_c = vars['water_temp_c'] + dwater_temp_cdt * 60 - - assert pytest.approx( - water_temp_c, tolerance) == answers['test_wind_a_7En7'] - - -def test_wind_b_1En6(use_sed_temp, vars, met_changes, t_changes, tolerance, answers) -> None: - - met_changes['wind_b'] = 1*10**-6 + return 100 # TODO: flip back to 0.0001 after we finish debugging + +def test_defaults( + initial_tsm_state, + default_meteo_params, + default_temp_params, + tolerance, +) -> None: + """Test the model with default parameters.""" + # alter parameters as necessary + + + # instantiate the model + tsm: EnergyBudget = get_energy_budget_instance( + initial_tsm_state=initial_tsm_state, + default_meteo_params=default_meteo_params, + default_temp_params=default_temp_params, + ) - dwater_temp_cdt = EnergyBudget( - met_changes, - t_changes, - use_sed_temp=use_sed_temp, - ).run(variables=vars) - water_temp_c = vars['water_temp_c'] + dwater_temp_cdt * 60 + # Run the model + tsm.increment_timestep() + water_temp_c = tsm.dataset.isel(tsm_time_step=-1).water_temp_c.values.item() + assert isinstance(water_temp_c, float) + assert pytest.approx(water_temp_c, tolerance) == 15.3389 + + +def test_changed_water_temp_c( + initial_tsm_state, + default_meteo_params, + default_temp_params, + tolerance, +) -> None: + """Test the model with default parameters.""" + # alter parameters as necessary + initial_state_dict = initial_tsm_state + initial_state_dict['water_temp_c'] = 40.0 + + # instantiate the model + tsm: EnergyBudget = get_energy_budget_instance( + initial_tsm_state=initial_tsm_state, + default_meteo_params=default_meteo_params, + default_temp_params=default_temp_params, + ) - assert pytest.approx( - water_temp_c, tolerance) == answers['test_wind_b_1En6'] + # Run the model + tsm.increment_timestep() + water_temp_c = tsm.dataset.isel(tsm_time_step=-1).water_temp_c.values.item() + assert isinstance(water_temp_c, float) + assert pytest.approx(water_temp_c, tolerance) == -12.1871 + + +def test_changed_surface_area( + initial_tsm_state, + default_meteo_params, + default_temp_params, + tolerance, +) -> None: + """Test the model with default parameters.""" + # alter parameters as necessary + initial_state_dict = initial_tsm_state + initial_state_dict['surface_area'] = 2.0 + + # instantiate the model + tsm: EnergyBudget = get_energy_budget_instance( + initial_tsm_state=initial_state_dict, + default_meteo_params=default_meteo_params, + default_temp_params=default_temp_params, + ) + # Run the model + tsm.increment_timestep() + water_temp_c = tsm.dataset.isel(tsm_time_step=-1).water_temp_c.values.item() + assert isinstance(water_temp_c, float) + assert pytest.approx(water_temp_c, tolerance) == 10.6779 + + + +def test_changed_volume( + initial_tsm_state, + default_meteo_params, + default_temp_params, + tolerance, +) -> None: + """Test the model with default parameters.""" + # alter parameters as necessary + initial_state_dict = initial_tsm_state + initial_state_dict['volume'] = 2.0 + + # instantiate the model + tsm: EnergyBudget = get_energy_budget_instance( + initial_tsm_state=initial_state_dict, + default_meteo_params=default_meteo_params, + default_temp_params=default_temp_params, + ) -def test_wind_b_2En6(use_sed_temp, vars, met_changes, t_changes, tolerance, answers) -> None: + # Run the model + tsm.increment_timestep() + water_temp_c = tsm.dataset.isel(tsm_time_step=-1).water_temp_c.values.item() + assert isinstance(water_temp_c, float) + assert pytest.approx(water_temp_c, tolerance) == 17.66947 + + +def test_changes_air_temp_c( + initial_tsm_state, + default_meteo_params, + default_temp_params, + tolerance, +) -> None: + """Test the model with default parameters.""" + # alter parameters as necessary + default_meteo_params['air_temp_c'] = 30.0 + + # instantiate the model + tsm: EnergyBudget = get_energy_budget_instance( + initial_tsm_state=initial_tsm_state, + default_meteo_params=default_meteo_params, + default_temp_params=default_temp_params, + ) - met_changes['wind_b'] = 2*10**-6 + # Run the model + tsm.increment_timestep() + water_temp_c = tsm.dataset.isel(tsm_time_step=-1).water_temp_c.values.item() + assert isinstance(water_temp_c, float) + assert pytest.approx(water_temp_c, tolerance) == 19.48758 + + +def test_changed_sed_temp_c( + initial_tsm_state, + default_meteo_params, + default_temp_params, + tolerance, +) -> None: + """Test the model with default parameters.""" + # alter parameters as necessary + default_meteo_params['sed_temp_c'] = 10.0 + + # instantiate the model + tsm: EnergyBudget = get_energy_budget_instance( + initial_tsm_state=initial_tsm_state, + default_meteo_params=default_meteo_params, + default_temp_params=default_temp_params, + ) - dwater_temp_cdt = EnergyBudget( - met_changes, - t_changes, - use_sed_temp=use_sed_temp, - ).run(variables=vars) - water_temp_c = vars['water_temp_c'] + dwater_temp_cdt * 60 + # Run the model + tsm.increment_timestep() + water_temp_c = tsm.dataset.isel(tsm_time_step=-1).water_temp_c.values.item() + assert isinstance(water_temp_c, float) + assert pytest.approx(water_temp_c, tolerance) == 18.10903 + + +def test_changed_q_solar( + initial_tsm_state, + default_meteo_params, + default_temp_params, + tolerance, +) -> None: + """Test the model with default parameters.""" + # alter parameters as necessary + default_meteo_params['q_solar'] = 450.0 + + # instantiate the model + tsm: EnergyBudget = get_energy_budget_instance( + initial_tsm_state=initial_tsm_state, + default_meteo_params=default_meteo_params, + default_temp_params=default_temp_params, + ) - assert pytest.approx( - water_temp_c, tolerance) == answers['test_wind_b_2En6'] + # Run the model + tsm.increment_timestep() + water_temp_c = tsm.dataset.isel(tsm_time_step=-1).water_temp_c.values.item() + assert isinstance(water_temp_c, float) + assert pytest.approx(water_temp_c, tolerance) == 16.37379 + + +def test_changed_wind_kh_kw( + initial_tsm_state, + default_meteo_params, + default_temp_params, + tolerance, +) -> None: + """test the model with default parameters.""" + # alter parameters as necessary + default_meteo_params['wind_kh_kw'] = 0.5 + + # instantiate the model + tsm: EnergyBudget = get_energy_budget_instance( + initial_tsm_state=initial_tsm_state, + default_meteo_params=default_meteo_params, + default_temp_params=default_temp_params, + ) + # run the model + tsm.increment_timestep() + water_temp_c = tsm.dataset.isel(tsm_time_step=-1).water_temp_c.values.item() + assert isinstance(water_temp_c, float) + assert pytest.approx(water_temp_c, tolerance) == 15.33893 + + +def test_changed_eair_mb( + initial_tsm_state, + default_meteo_params, + default_temp_params, + tolerance, +) -> None: + """test the model with default parameters.""" + # alter parameters as necessary + default_meteo_params['eair_mb'] = 2.0 + + # instantiate the model + tsm: EnergyBudget = get_energy_budget_instance( + initial_tsm_state=initial_tsm_state, + default_meteo_params=default_meteo_params, + default_temp_params=default_temp_params, + ) -def test_wind_c_0_5(use_sed_temp, vars, met_changes, t_changes, tolerance, answers) -> None: + # run the model + tsm.increment_timestep() + water_temp_c = tsm.dataset.isel(tsm_time_step=-1).water_temp_c.values.item() + assert isinstance(water_temp_c, float) + assert pytest.approx(water_temp_c, tolerance) == 15.48259 + + +def test_changed_pressure_mb( + initial_tsm_state, + default_meteo_params, + default_temp_params, + tolerance, +) -> None: + """test the model with default parameters.""" + # alter parameters as necessary + default_meteo_params['pressure_mb'] = 970 + + # instantiate the model + tsm: EnergyBudget = get_energy_budget_instance( + initial_tsm_state=initial_tsm_state, + default_meteo_params=default_meteo_params, + default_temp_params=default_temp_params, + ) - met_changes['wind_c'] = .5 + # run the model + tsm.increment_timestep() + water_temp_c = tsm.dataset.isel(tsm_time_step=-1).water_temp_c.values.item() + assert isinstance(water_temp_c, float) + assert pytest.approx(water_temp_c, tolerance) == 15.16238149 + +def test_changed_cloudiness( + initial_tsm_state, + default_meteo_params, + default_temp_params, + tolerance, +) -> None: + """test the model with default parameters.""" + # alter parameters as necessary + default_meteo_params['cloudiness'] = 0.0 + + # instantiate the model + tsm: EnergyBudget = get_energy_budget_instance( + initial_tsm_state=initial_tsm_state, + default_meteo_params=default_meteo_params, + default_temp_params=default_temp_params, + ) - dwater_temp_cdt = EnergyBudget( - met_changes, - t_changes, - use_sed_temp=use_sed_temp, - ).run(variables=vars) - water_temp_c = vars['water_temp_c'] + dwater_temp_cdt * 60 + # run the model + tsm.increment_timestep() + water_temp_c = tsm.dataset.isel(tsm_time_step=-1).water_temp_c.values.item() + assert isinstance(water_temp_c, float) + assert pytest.approx(water_temp_c, tolerance) == 15.32707 + + +def test_changed_wind_a( + initial_tsm_state, + default_meteo_params, + default_temp_params, + tolerance, +) -> None: + """test the model with default parameters.""" + # alter parameters as necessary + default_meteo_params['wind_a'] = 1.0e-7 + + # instantiate the model + tsm: EnergyBudget = get_energy_budget_instance( + initial_tsm_state=initial_tsm_state, + default_meteo_params=default_meteo_params, + default_temp_params=default_temp_params, + ) - assert pytest.approx(water_temp_c, tolerance) == answers['test_wind_c_0_5'] + # run the model + tsm.increment_timestep() + water_temp_c = tsm.dataset.isel(tsm_time_step=-1).water_temp_c.values.item() + assert isinstance(water_temp_c, float) + assert pytest.approx(water_temp_c, tolerance) == 15.4728 + + +def test_changed_wind_b( + initial_tsm_state, + default_meteo_params, + default_temp_params, + tolerance, +) -> None: + """test the model with default parameters.""" + # alter parameters as necessary + default_meteo_params['wind_b'] = 1.0e-6 + + # instantiate the model + tsm: EnergyBudget = get_energy_budget_instance( + initial_tsm_state=initial_tsm_state, + default_meteo_params=default_meteo_params, + default_temp_params=default_temp_params, + ) + # run the model + tsm.increment_timestep() + water_temp_c = tsm.dataset.isel(tsm_time_step=-1).water_temp_c.values.item() + assert isinstance(water_temp_c, float) + assert pytest.approx(water_temp_c, tolerance) == 16.3432 + + +def test_changed_wind_c( + initial_tsm_state, + default_meteo_params, + default_temp_params, + tolerance, +) -> None: + """test the model with default parameters.""" + # alter parameters as necessary + default_meteo_params['wind_c'] = 0.5 + + # instantiate the model + tsm: EnergyBudget = get_energy_budget_instance( + initial_tsm_state=initial_tsm_state, + default_meteo_params=default_meteo_params, + default_temp_params=default_temp_params, + ) -def test_wind_c_3(use_sed_temp, vars, met_changes, t_changes, tolerance, answers) -> None: + # run the model + tsm.increment_timestep() + water_temp_c = tsm.dataset.isel(tsm_time_step=-1).water_temp_c.values.item() + assert isinstance(water_temp_c, float) + assert pytest.approx(water_temp_c, tolerance) == 16.6123 - met_changes['wind_c'] = 3 - dwater_temp_cdt = EnergyBudget( - met_changes, - t_changes, - use_sed_temp=use_sed_temp, - ).run(variables=vars) - water_temp_c = vars['water_temp_c'] + dwater_temp_cdt * 60 +def test_use_sed_temp( + initial_tsm_state, + default_meteo_params, + default_temp_params, + tolerance, +) -> None: + """test the model with default parameters.""" + assert True == True + #TODO: implement this test + ... - assert pytest.approx(water_temp_c, tolerance) == answers['test_wind_c_3'] From 86b799ab918a35cda954f3267025d0fd1532961c Mon Sep 17 00:00:00 2001 From: xaviernogueira Date: Wed, 29 Nov 2023 15:48:33 -0500 Subject: [PATCH 05/16] Fixed issue where q_longwave_down was being subtracted Mismatch with QA/QC Excel sheet (which is verified with HEC-RAS) --- src/clearwater_modules/tsm/processes.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/clearwater_modules/tsm/processes.py b/src/clearwater_modules/tsm/processes.py index e017661..b75cc38 100644 --- a/src/clearwater_modules/tsm/processes.py +++ b/src/clearwater_modules/tsm/processes.py @@ -355,28 +355,28 @@ def ri_function(ri_number: xr.DataArray) -> np.ndarray: ) out: np.ndarray = np.select( condlist=[ - (ri_number_bounded < 0.0) & (ri_number_bounded >= -0.01), # neutral + (ri_number_bounded < 0.0) & (ri_number_bounded >= -0.01), # neutral (ri_number_bounded < 0.0) & (ri_number_bounded < -0.01), # unstable - (ri_number_bounded >= 0.0) & (ri_number_bounded <= 0.01), # neutral + (ri_number_bounded >= 0.0) & (ri_number_bounded <= 0.01), # neutral (ri_number_bounded >= 0.0) & (ri_number_bounded > 0.01), # stable ], choicelist=[ 1.0, # neutral (1.0 - 22.0 * ri_number_bounded) ** 0.80, # unstable 1.0, # neutral - (1.0 + 34.0 * ri_number_bounded) ** (-0.80), # stable + (1.0 + 34.0 * ri_number_bounded) ** (-0.80), # stable ], ) warnings.filterwarnings("default", category=RuntimeWarning) return out # old method with where, same logic - #da: xr.DataArray = xr.where(ri_number > 2.0, (1.0 + 34.0*2.0)**(-0.80), + # da: xr.DataArray = xr.where(ri_number > 2.0, (1.0 + 34.0*2.0)**(-0.80), # xr.where(ri_number < -1.0, (1.0 - 22.0*-1.0)**(-0.80), # xr.where((ri_number < 0.0) & (ri_number >= -0.01), 1.0, # xr.where(ri_number < -0.01, (1.0 - 22.0 * ri_number)**0.80, # xr.where((ri_number >= 0.0) & (ri_number <= 0.01), 1.0, (1.0 + 34.0 * ri_number)**(-0.80) - #))))) + # ))))) @numba.njit @@ -447,7 +447,7 @@ def mf_cp_water(water_temp_c: xr.DataArray) -> xr.DataArray: ) # previous code - #return xr.where(water_temp_c <= 0.0, 4218.0, + # return xr.where(water_temp_c <= 0.0, 4218.0, # xr.where(water_temp_c <= 5.0, 4202.0, # xr.where(water_temp_c <= 10.0, 4192.0, # xr.where(water_temp_c <= 15.0, 4186.0, @@ -476,12 +476,12 @@ def q_net( q_sediment: Sediment heat flux (W/m^2) """ return ( - q_sensible - - q_latent - - q_longwave_up - - q_longwave_down + + q_sensible + q_solar + - q_sediment + q_sediment + + q_longwave_down - + q_longwave_up - + q_latent ) From 8f4e7b859b8cdc3d96079064e8ffc21dbd379fd1 Mon Sep 17 00:00:00 2001 From: xaviernogueira Date: Wed, 29 Nov 2023 15:59:51 -0500 Subject: [PATCH 06/16] remove ri_function from q_latent calculation --- src/clearwater_modules/tsm/processes.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/clearwater_modules/tsm/processes.py b/src/clearwater_modules/tsm/processes.py index b75cc38..27503d2 100644 --- a/src/clearwater_modules/tsm/processes.py +++ b/src/clearwater_modules/tsm/processes.py @@ -99,7 +99,6 @@ def wind_function( @numba.njit def q_latent( - ri_function: xr.DataArray, pressure_mb: xr.DataArray, density_water: xr.DataArray, lv: xr.DataArray, @@ -118,9 +117,10 @@ def q_latent( eair_mb: Vapour pressure of air (mb) """ return ( - ri_function * (0.622 / pressure_mb) * - lv * density_water * wind_function * + lv * + density_water * + wind_function * (esat_mb - eair_mb) ) From bdec2be0626d48780a345d075fa3149e16501579 Mon Sep 17 00:00:00 2001 From: xaviernogueira Date: Wed, 29 Nov 2023 16:22:50 -0500 Subject: [PATCH 07/16] TSM now matches QA/QC Excel! Make tests next... --- src/clearwater_modules/tsm/constants.py | 2 +- src/clearwater_modules/tsm/processes.py | 10 +++++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/clearwater_modules/tsm/constants.py b/src/clearwater_modules/tsm/constants.py index 6c63e38..c6ec8bb 100644 --- a/src/clearwater_modules/tsm/constants.py +++ b/src/clearwater_modules/tsm/constants.py @@ -47,7 +47,7 @@ class Meteorological(TypedDict): wind_speed=3.0, wind_a=0.3, wind_b=1.5, - wind_c=1.0, + wind_c=3.0, wind_kh_kw=1.0, ) diff --git a/src/clearwater_modules/tsm/processes.py b/src/clearwater_modules/tsm/processes.py index 27503d2..c91f2dc 100644 --- a/src/clearwater_modules/tsm/processes.py +++ b/src/clearwater_modules/tsm/processes.py @@ -81,6 +81,7 @@ def emissivity_air( @numba.njit def wind_function( + ri_function: xr.DataArray, wind_a: xr.DataArray, wind_b: xr.DataArray, wind_c: xr.DataArray, @@ -89,12 +90,19 @@ def wind_function( """Calculate wind function (unitless) for latent and sensible heat. Args: + ri_function: Richardson function (unitless) wind_a: Wind function coefficient (unitless) wind_b: Wind function coefficient (unitless) wind_c: Wind function coefficient (unitless) wind_speed: Wind speed (m/s) """ - return wind_a / 1000000.0 + wind_b / 1000000.0 * wind_speed**wind_c + return ( + ri_function * ( + (wind_a / 1000000.0) + + (wind_b / 1000000.0) * + (wind_speed**wind_c) + ) + ) @numba.njit From b6b9fe765e0856b480965434bf4fcfc67eaad6c6 Mon Sep 17 00:00:00 2001 From: xaviernogueira Date: Wed, 29 Nov 2023 16:23:06 -0500 Subject: [PATCH 08/16] Printing constants in to debug script terminal --- examples/dev_sandbox/tsm_debugger.py | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/dev_sandbox/tsm_debugger.py b/examples/dev_sandbox/tsm_debugger.py index f668c5e..277287e 100644 --- a/examples/dev_sandbox/tsm_debugger.py +++ b/examples/dev_sandbox/tsm_debugger.py @@ -15,6 +15,7 @@ def main(): tsm = clearwater_modules.tsm.EnergyBudget( initial_state_values=state_i, ) + print(tsm.static_variable_values) input_dataset = tsm.dataset.isel(time_step=0).copy() From ab42e29d5ed64fab532cc42683a7756a38cf5843 Mon Sep 17 00:00:00 2001 From: xaviernogueira Date: Fri, 1 Dec 2023 18:38:17 -0500 Subject: [PATCH 09/16] Better formatting, retuning to where (for now) --- src/clearwater_modules/tsm/processes.py | 33 ++++++++++++------------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/src/clearwater_modules/tsm/processes.py b/src/clearwater_modules/tsm/processes.py index c91f2dc..fa8ea50 100644 --- a/src/clearwater_modules/tsm/processes.py +++ b/src/clearwater_modules/tsm/processes.py @@ -440,28 +440,27 @@ def mf_density_air_sat(water_temp_k: xr.DataArray, esat_mb: float, pressure_mb: return 0.348 * (pressure_mb / water_temp_k) * (1.0 + mixing_ratio_sat) / (1.0 + 1.61 * mixing_ratio_sat) -@numba.njit def mf_cp_water(water_temp_c: xr.DataArray) -> xr.DataArray: """ Compute the specific heat of water (J/kg/K) as a function of water temperature (Celsius). This is used in computing the source/sink term. """ - return ( - (4.65e-6 * water_temp_c - 0.001) * - (water_temp_c + 0.085858) * - (water_temp_c - 3.1331) * - water_temp_c + - 4219.793 - ) - - # previous code - # return xr.where(water_temp_c <= 0.0, 4218.0, - # xr.where(water_temp_c <= 5.0, 4202.0, - # xr.where(water_temp_c <= 10.0, 4192.0, - # xr.where(water_temp_c <= 15.0, 4186.0, - # xr.where(water_temp_c <= 20.0, 4182.0, - # xr.where(water_temp_c <= 25.0, 4180.0, 4178.0 - # )))))) + # return ( + # (4.65e-6 * water_temp_c - 0.001) * + # (water_temp_c + 0.085858) * + # (water_temp_c - 3.1331) * + # water_temp_c + + # 4219.793 + # ) + + return xr.where( + water_temp_c <= 0.0, 4218.0, + xr.where(water_temp_c <= 5.0, 4202.0, + xr.where(water_temp_c <= 10.0, 4192.0, + xr.where(water_temp_c <= 15.0, 4186.0, + xr.where(water_temp_c <= 20.0, 4182.0, + xr.where(water_temp_c <= 25.0, 4180.0, 4178.0 + )))))) @numba.njit From 2ad4cdbc2c9f9f3e26f13a25528f64045abf5c90 Mon Sep 17 00:00:00 2001 From: xaviernogueira Date: Fri, 1 Dec 2023 18:38:28 -0500 Subject: [PATCH 10/16] Updating tests with real answers --- tests/test_5_tsm_calculations.py | 34 +++++++++++++++++--------------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/tests/test_5_tsm_calculations.py b/tests/test_5_tsm_calculations.py index 2b4ff68..63ae20b 100644 --- a/tests/test_5_tsm_calculations.py +++ b/tests/test_5_tsm_calculations.py @@ -73,6 +73,7 @@ def default_temp_params() -> Temperature: richardson_option=True, ) + def get_energy_budget_instance( initial_tsm_state, default_meteo_params, @@ -90,7 +91,8 @@ def get_energy_budget_instance( @pytest.fixture(scope='module') def tolerance() -> float: """Controls the precision of the pytest.approx() function.""" - return 100 # TODO: flip back to 0.0001 after we finish debugging + return 0.0000001 + def test_defaults( initial_tsm_state, @@ -113,7 +115,7 @@ def test_defaults( tsm.increment_timestep() water_temp_c = tsm.dataset.isel(tsm_time_step=-1).water_temp_c.values.item() assert isinstance(water_temp_c, float) - assert pytest.approx(water_temp_c, tolerance) == 15.3389 + assert pytest.approx(water_temp_c, tolerance) == 19.9999461 def test_changed_water_temp_c( @@ -138,7 +140,7 @@ def test_changed_water_temp_c( tsm.increment_timestep() water_temp_c = tsm.dataset.isel(tsm_time_step=-1).water_temp_c.values.item() assert isinstance(water_temp_c, float) - assert pytest.approx(water_temp_c, tolerance) == -12.1871 + assert pytest.approx(water_temp_c, tolerance) == 39.99618297 def test_changed_surface_area( @@ -163,7 +165,7 @@ def test_changed_surface_area( tsm.increment_timestep() water_temp_c = tsm.dataset.isel(tsm_time_step=-1).water_temp_c.values.item() assert isinstance(water_temp_c, float) - assert pytest.approx(water_temp_c, tolerance) == 10.6779 + assert pytest.approx(water_temp_c, tolerance) == 19.9998921 @@ -189,10 +191,10 @@ def test_changed_volume( tsm.increment_timestep() water_temp_c = tsm.dataset.isel(tsm_time_step=-1).water_temp_c.values.item() assert isinstance(water_temp_c, float) - assert pytest.approx(water_temp_c, tolerance) == 17.66947 + assert pytest.approx(water_temp_c, tolerance) == 19.99997303 -def test_changes_air_temp_c( +def test_changed_air_temp_c( initial_tsm_state, default_meteo_params, default_temp_params, @@ -213,7 +215,7 @@ def test_changes_air_temp_c( tsm.increment_timestep() water_temp_c = tsm.dataset.isel(tsm_time_step=-1).water_temp_c.values.item() assert isinstance(water_temp_c, float) - assert pytest.approx(water_temp_c, tolerance) == 19.48758 + assert pytest.approx(water_temp_c, tolerance) == 19.99999407 def test_changed_sed_temp_c( @@ -237,7 +239,7 @@ def test_changed_sed_temp_c( tsm.increment_timestep() water_temp_c = tsm.dataset.isel(tsm_time_step=-1).water_temp_c.values.item() assert isinstance(water_temp_c, float) - assert pytest.approx(water_temp_c, tolerance) == 18.10903 + assert pytest.approx(water_temp_c, tolerance) == 19.99997811 def test_changed_q_solar( @@ -261,7 +263,7 @@ def test_changed_q_solar( tsm.increment_timestep() water_temp_c = tsm.dataset.isel(tsm_time_step=-1).water_temp_c.values.item() assert isinstance(water_temp_c, float) - assert pytest.approx(water_temp_c, tolerance) == 16.37379 + assert pytest.approx(water_temp_c, tolerance) == 19.99995803 def test_changed_wind_kh_kw( @@ -285,7 +287,7 @@ def test_changed_wind_kh_kw( tsm.increment_timestep() water_temp_c = tsm.dataset.isel(tsm_time_step=-1).water_temp_c.values.item() assert isinstance(water_temp_c, float) - assert pytest.approx(water_temp_c, tolerance) == 15.33893 + assert pytest.approx(water_temp_c, tolerance) == 19.99994605 def test_changed_eair_mb( @@ -309,7 +311,7 @@ def test_changed_eair_mb( tsm.increment_timestep() water_temp_c = tsm.dataset.isel(tsm_time_step=-1).water_temp_c.values.item() assert isinstance(water_temp_c, float) - assert pytest.approx(water_temp_c, tolerance) == 15.48259 + assert pytest.approx(water_temp_c, tolerance) == 19.99994772 def test_changed_pressure_mb( @@ -333,7 +335,7 @@ def test_changed_pressure_mb( tsm.increment_timestep() water_temp_c = tsm.dataset.isel(tsm_time_step=-1).water_temp_c.values.item() assert isinstance(water_temp_c, float) - assert pytest.approx(water_temp_c, tolerance) == 15.16238149 + assert pytest.approx(water_temp_c, tolerance) == 19.99994401 def test_changed_cloudiness( initial_tsm_state, @@ -356,7 +358,7 @@ def test_changed_cloudiness( tsm.increment_timestep() water_temp_c = tsm.dataset.isel(tsm_time_step=-1).water_temp_c.values.item() assert isinstance(water_temp_c, float) - assert pytest.approx(water_temp_c, tolerance) == 15.32707 + assert pytest.approx(water_temp_c, tolerance) == 19.99994592 def test_changed_wind_a( @@ -380,7 +382,7 @@ def test_changed_wind_a( tsm.increment_timestep() water_temp_c = tsm.dataset.isel(tsm_time_step=-1).water_temp_c.values.item() assert isinstance(water_temp_c, float) - assert pytest.approx(water_temp_c, tolerance) == 15.4728 + assert pytest.approx(water_temp_c, tolerance) == 19.9999476 def test_changed_wind_b( @@ -404,7 +406,7 @@ def test_changed_wind_b( tsm.increment_timestep() water_temp_c = tsm.dataset.isel(tsm_time_step=-1).water_temp_c.values.item() assert isinstance(water_temp_c, float) - assert pytest.approx(water_temp_c, tolerance) == 16.3432 + assert pytest.approx(water_temp_c, tolerance) == 19.99995768 def test_changed_wind_c( @@ -428,7 +430,7 @@ def test_changed_wind_c( tsm.increment_timestep() water_temp_c = tsm.dataset.isel(tsm_time_step=-1).water_temp_c.values.item() assert isinstance(water_temp_c, float) - assert pytest.approx(water_temp_c, tolerance) == 16.6123 + assert pytest.approx(water_temp_c, tolerance) == 19.99996079 def test_use_sed_temp( From 653de79bb15827dcdb42e8a7638258edd961e2c3 Mon Sep 17 00:00:00 2001 From: xaviernogueira Date: Mon, 4 Dec 2023 13:05:01 -0500 Subject: [PATCH 11/16] Switching to np.select for look-up-table logic (tests still pass) --- src/clearwater_modules/tsm/processes.py | 30 +++++++++++++++++-------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/src/clearwater_modules/tsm/processes.py b/src/clearwater_modules/tsm/processes.py index fa8ea50..d2c0569 100644 --- a/src/clearwater_modules/tsm/processes.py +++ b/src/clearwater_modules/tsm/processes.py @@ -445,6 +445,8 @@ def mf_cp_water(water_temp_c: xr.DataArray) -> xr.DataArray: Compute the specific heat of water (J/kg/K) as a function of water temperature (Celsius). This is used in computing the source/sink term. """ + # polynomial logic, this produced nearly identical outputs for the range of temperatures tested + # See discussion about the two methods: https://github.com/EcohydrologyTeam/ClearWater-modules/pull/44 # return ( # (4.65e-6 * water_temp_c - 0.001) * # (water_temp_c + 0.085858) * @@ -452,15 +454,25 @@ def mf_cp_water(water_temp_c: xr.DataArray) -> xr.DataArray: # water_temp_c + # 4219.793 # ) - - return xr.where( - water_temp_c <= 0.0, 4218.0, - xr.where(water_temp_c <= 5.0, 4202.0, - xr.where(water_temp_c <= 10.0, 4192.0, - xr.where(water_temp_c <= 15.0, 4186.0, - xr.where(water_temp_c <= 20.0, 4182.0, - xr.where(water_temp_c <= 25.0, 4180.0, 4178.0 - )))))) + return np.select( + condlist=[ + water_temp_c <= 0.0, + water_temp_c <= 5.0, + water_temp_c <= 10.0, + water_temp_c <= 15.0, + water_temp_c <= 20.0, + water_temp_c <= 25.0, + ], + choicelist=[ + 4218.0, + 4202.0, + 4192.0, + 4186.0, + 4182.0, + 4180.0, + ], + default=4178.0, + ) @numba.njit From 6f288f8a15615a74ced9485a0e199f4981c07d54 Mon Sep 17 00:00:00 2001 From: xaviernogueira Date: Mon, 4 Dec 2023 13:22:50 -0500 Subject: [PATCH 12/16] Fixed q_sensible func (fixed changed air temp calculation) --- src/clearwater_modules/tsm/processes.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/clearwater_modules/tsm/processes.py b/src/clearwater_modules/tsm/processes.py index d2c0569..4080091 100644 --- a/src/clearwater_modules/tsm/processes.py +++ b/src/clearwater_modules/tsm/processes.py @@ -136,7 +136,6 @@ def q_latent( @numba.njit def q_sensible( wind_kh_kw: xr.DataArray, - ri_function: xr.DataArray, cp_air: xr.DataArray, density_water: xr.DataArray, wind_function: xr.DataArray, @@ -157,8 +156,9 @@ def q_sensible( """ return ( wind_kh_kw * - ri_function * - cp_air * density_water * wind_function * + cp_air * + density_water * + wind_function * (air_temp_k - water_temp_k) ) From c3347d4e95db76c0e62f04fa78c97b1e8240ed03 Mon Sep 17 00:00:00 2001 From: xaviernogueira Date: Mon, 4 Dec 2023 14:09:58 -0500 Subject: [PATCH 13/16] Update tsm_debugger.py --- examples/dev_sandbox/tsm_debugger.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/dev_sandbox/tsm_debugger.py b/examples/dev_sandbox/tsm_debugger.py index 277287e..2134ef2 100644 --- a/examples/dev_sandbox/tsm_debugger.py +++ b/examples/dev_sandbox/tsm_debugger.py @@ -6,7 +6,7 @@ def main(): # define starting state values state_i = { - 'water_temp_c': 20.0, + 'water_temp_c': 40.0, 'surface_area': 1.0, 'volume': 1.0, } @@ -14,6 +14,7 @@ def main(): # instantiate the TSM module tsm = clearwater_modules.tsm.EnergyBudget( initial_state_values=state_i, + meteo_parameters={'wind_c': 1.0}, ) print(tsm.static_variable_values) From d3968f783102e036c676435d0fa702be3204eaf0 Mon Sep 17 00:00:00 2001 From: xaviernogueira Date: Mon, 4 Dec 2023 14:10:20 -0500 Subject: [PATCH 14/16] Fixed answer, and made everything explictly a float (only one test not passing) --- tests/test_5_tsm_calculations.py | 116 +++++++++++++++++-------------- 1 file changed, 64 insertions(+), 52 deletions(-) diff --git a/tests/test_5_tsm_calculations.py b/tests/test_5_tsm_calculations.py index 63ae20b..78f11f9 100644 --- a/tests/test_5_tsm_calculations.py +++ b/tests/test_5_tsm_calculations.py @@ -22,7 +22,7 @@ def initial_tsm_state() -> dict[str, float]: @pytest.fixture(scope='function') -def default_meteo_params() -> Meteorological: +def default_meteo_params() -> Meteorological: """Returns default meteorological static variable values for the model. NOTE: As of now (11/17/2023) these match the built in defaults, but are @@ -31,8 +31,8 @@ def default_meteo_params() -> Meteorological: Returns a typed dictionary, with string keys and float values. """ return Meteorological( - air_temp_c=20, - q_solar=400, + air_temp_c=20.0, + q_solar=400.0, sed_temp_c=5.0, eair_mb=1.0, pressure_mb=1013.0, @@ -46,7 +46,7 @@ def default_meteo_params() -> Meteorological: @pytest.fixture(scope='function') -def default_temp_params() -> Temperature: +def default_temp_params() -> Temperature: """Returns default temperature static variable values for the model. NOTE: As of now (11/17/2023) these match the built in defaults, but are @@ -56,7 +56,7 @@ def default_temp_params() -> Temperature: """ return Temperature( stefan_boltzmann=5.67e-8, - cp_air=1005, + cp_air=1005.0, emissivity_water=0.97, gravity=-9.806, a0=6984.505294, @@ -95,7 +95,7 @@ def tolerance() -> float: def test_defaults( - initial_tsm_state, + initial_tsm_state, default_meteo_params, default_temp_params, tolerance, @@ -103,7 +103,6 @@ def test_defaults( """Test the model with default parameters.""" # alter parameters as necessary - # instantiate the model tsm: EnergyBudget = get_energy_budget_instance( initial_tsm_state=initial_tsm_state, @@ -113,13 +112,14 @@ def test_defaults( # Run the model tsm.increment_timestep() - water_temp_c = tsm.dataset.isel(tsm_time_step=-1).water_temp_c.values.item() + water_temp_c = tsm.dataset.isel( + tsm_time_step=-1).water_temp_c.values.item() assert isinstance(water_temp_c, float) - assert pytest.approx(water_temp_c, tolerance) == 19.9999461 + assert pytest.approx(water_temp_c, tolerance) == 19.9999461 def test_changed_water_temp_c( - initial_tsm_state, + initial_tsm_state, default_meteo_params, default_temp_params, tolerance, @@ -127,7 +127,7 @@ def test_changed_water_temp_c( """Test the model with default parameters.""" # alter parameters as necessary initial_state_dict = initial_tsm_state - initial_state_dict['water_temp_c'] = 40.0 + initial_state_dict['water_temp_c'] = 40.0 # instantiate the model tsm: EnergyBudget = get_energy_budget_instance( @@ -138,13 +138,14 @@ def test_changed_water_temp_c( # Run the model tsm.increment_timestep() - water_temp_c = tsm.dataset.isel(tsm_time_step=-1).water_temp_c.values.item() + water_temp_c = tsm.dataset.isel( + tsm_time_step=-1).water_temp_c.values.item() assert isinstance(water_temp_c, float) - assert pytest.approx(water_temp_c, tolerance) == 39.99618297 + assert pytest.approx(water_temp_c, tolerance) == 39.99939598 def test_changed_surface_area( - initial_tsm_state, + initial_tsm_state, default_meteo_params, default_temp_params, tolerance, @@ -152,7 +153,7 @@ def test_changed_surface_area( """Test the model with default parameters.""" # alter parameters as necessary initial_state_dict = initial_tsm_state - initial_state_dict['surface_area'] = 2.0 + initial_state_dict['surface_area'] = 2.0 # instantiate the model tsm: EnergyBudget = get_energy_budget_instance( @@ -163,14 +164,14 @@ def test_changed_surface_area( # Run the model tsm.increment_timestep() - water_temp_c = tsm.dataset.isel(tsm_time_step=-1).water_temp_c.values.item() + water_temp_c = tsm.dataset.isel( + tsm_time_step=-1).water_temp_c.values.item() assert isinstance(water_temp_c, float) assert pytest.approx(water_temp_c, tolerance) == 19.9998921 - def test_changed_volume( - initial_tsm_state, + initial_tsm_state, default_meteo_params, default_temp_params, tolerance, @@ -189,13 +190,14 @@ def test_changed_volume( # Run the model tsm.increment_timestep() - water_temp_c = tsm.dataset.isel(tsm_time_step=-1).water_temp_c.values.item() + water_temp_c = tsm.dataset.isel( + tsm_time_step=-1).water_temp_c.values.item() assert isinstance(water_temp_c, float) - assert pytest.approx(water_temp_c, tolerance) == 19.99997303 + assert pytest.approx(water_temp_c, tolerance) == 19.99997303 def test_changed_air_temp_c( - initial_tsm_state, + initial_tsm_state, default_meteo_params, default_temp_params, tolerance, @@ -213,13 +215,14 @@ def test_changed_air_temp_c( # Run the model tsm.increment_timestep() - water_temp_c = tsm.dataset.isel(tsm_time_step=-1).water_temp_c.values.item() + water_temp_c = tsm.dataset.isel( + tsm_time_step=-1).water_temp_c.values.item() assert isinstance(water_temp_c, float) assert pytest.approx(water_temp_c, tolerance) == 19.99999407 def test_changed_sed_temp_c( - initial_tsm_state, + initial_tsm_state, default_meteo_params, default_temp_params, tolerance, @@ -237,13 +240,14 @@ def test_changed_sed_temp_c( # Run the model tsm.increment_timestep() - water_temp_c = tsm.dataset.isel(tsm_time_step=-1).water_temp_c.values.item() + water_temp_c = tsm.dataset.isel( + tsm_time_step=-1).water_temp_c.values.item() assert isinstance(water_temp_c, float) - assert pytest.approx(water_temp_c, tolerance) == 19.99997811 + assert pytest.approx(water_temp_c, tolerance) == 19.99997811 def test_changed_q_solar( - initial_tsm_state, + initial_tsm_state, default_meteo_params, default_temp_params, tolerance, @@ -261,20 +265,21 @@ def test_changed_q_solar( # Run the model tsm.increment_timestep() - water_temp_c = tsm.dataset.isel(tsm_time_step=-1).water_temp_c.values.item() + water_temp_c = tsm.dataset.isel( + tsm_time_step=-1).water_temp_c.values.item() assert isinstance(water_temp_c, float) assert pytest.approx(water_temp_c, tolerance) == 19.99995803 def test_changed_wind_kh_kw( - initial_tsm_state, + initial_tsm_state, default_meteo_params, default_temp_params, tolerance, ) -> None: """test the model with default parameters.""" # alter parameters as necessary - default_meteo_params['wind_kh_kw'] = 0.5 + default_meteo_params['wind_kh_kw'] = 0.5 # instantiate the model tsm: EnergyBudget = get_energy_budget_instance( @@ -285,20 +290,21 @@ def test_changed_wind_kh_kw( # run the model tsm.increment_timestep() - water_temp_c = tsm.dataset.isel(tsm_time_step=-1).water_temp_c.values.item() + water_temp_c = tsm.dataset.isel( + tsm_time_step=-1).water_temp_c.values.item() assert isinstance(water_temp_c, float) assert pytest.approx(water_temp_c, tolerance) == 19.99994605 def test_changed_eair_mb( - initial_tsm_state, + initial_tsm_state, default_meteo_params, default_temp_params, tolerance, ) -> None: """test the model with default parameters.""" # alter parameters as necessary - default_meteo_params['eair_mb'] = 2.0 + default_meteo_params['eair_mb'] = 2.0 # instantiate the model tsm: EnergyBudget = get_energy_budget_instance( @@ -309,20 +315,21 @@ def test_changed_eair_mb( # run the model tsm.increment_timestep() - water_temp_c = tsm.dataset.isel(tsm_time_step=-1).water_temp_c.values.item() + water_temp_c = tsm.dataset.isel( + tsm_time_step=-1).water_temp_c.values.item() assert isinstance(water_temp_c, float) assert pytest.approx(water_temp_c, tolerance) == 19.99994772 def test_changed_pressure_mb( - initial_tsm_state, + initial_tsm_state, default_meteo_params, default_temp_params, tolerance, ) -> None: """test the model with default parameters.""" # alter parameters as necessary - default_meteo_params['pressure_mb'] = 970 + default_meteo_params['pressure_mb'] = 970 # instantiate the model tsm: EnergyBudget = get_energy_budget_instance( @@ -333,19 +340,21 @@ def test_changed_pressure_mb( # run the model tsm.increment_timestep() - water_temp_c = tsm.dataset.isel(tsm_time_step=-1).water_temp_c.values.item() + water_temp_c = tsm.dataset.isel( + tsm_time_step=-1).water_temp_c.values.item() assert isinstance(water_temp_c, float) assert pytest.approx(water_temp_c, tolerance) == 19.99994401 + def test_changed_cloudiness( - initial_tsm_state, + initial_tsm_state, default_meteo_params, default_temp_params, tolerance, ) -> None: """test the model with default parameters.""" # alter parameters as necessary - default_meteo_params['cloudiness'] = 0.0 + default_meteo_params['cloudiness'] = 0.0 # instantiate the model tsm: EnergyBudget = get_energy_budget_instance( @@ -356,20 +365,21 @@ def test_changed_cloudiness( # run the model tsm.increment_timestep() - water_temp_c = tsm.dataset.isel(tsm_time_step=-1).water_temp_c.values.item() + water_temp_c = tsm.dataset.isel( + tsm_time_step=-1).water_temp_c.values.item() assert isinstance(water_temp_c, float) assert pytest.approx(water_temp_c, tolerance) == 19.99994592 def test_changed_wind_a( - initial_tsm_state, + initial_tsm_state, default_meteo_params, default_temp_params, tolerance, ) -> None: """test the model with default parameters.""" # alter parameters as necessary - default_meteo_params['wind_a'] = 1.0e-7 + default_meteo_params['wind_a'] = 1.0e-7 # instantiate the model tsm: EnergyBudget = get_energy_budget_instance( @@ -380,20 +390,21 @@ def test_changed_wind_a( # run the model tsm.increment_timestep() - water_temp_c = tsm.dataset.isel(tsm_time_step=-1).water_temp_c.values.item() + water_temp_c = tsm.dataset.isel( + tsm_time_step=-1).water_temp_c.values.item() assert isinstance(water_temp_c, float) - assert pytest.approx(water_temp_c, tolerance) == 19.9999476 + assert pytest.approx(water_temp_c, tolerance) == 19.9999476 def test_changed_wind_b( - initial_tsm_state, + initial_tsm_state, default_meteo_params, default_temp_params, tolerance, ) -> None: """test the model with default parameters.""" # alter parameters as necessary - default_meteo_params['wind_b'] = 1.0e-6 + default_meteo_params['wind_b'] = 1.0e-6 # instantiate the model tsm: EnergyBudget = get_energy_budget_instance( @@ -404,20 +415,21 @@ def test_changed_wind_b( # run the model tsm.increment_timestep() - water_temp_c = tsm.dataset.isel(tsm_time_step=-1).water_temp_c.values.item() + water_temp_c = tsm.dataset.isel( + tsm_time_step=-1).water_temp_c.values.item() assert isinstance(water_temp_c, float) assert pytest.approx(water_temp_c, tolerance) == 19.99995768 def test_changed_wind_c( - initial_tsm_state, + initial_tsm_state, default_meteo_params, default_temp_params, tolerance, ) -> None: """test the model with default parameters.""" # alter parameters as necessary - default_meteo_params['wind_c'] = 0.5 + default_meteo_params['wind_c'] = 0.5 # instantiate the model tsm: EnergyBudget = get_energy_budget_instance( @@ -428,19 +440,19 @@ def test_changed_wind_c( # run the model tsm.increment_timestep() - water_temp_c = tsm.dataset.isel(tsm_time_step=-1).water_temp_c.values.item() + water_temp_c = tsm.dataset.isel( + tsm_time_step=-1).water_temp_c.values.item() assert isinstance(water_temp_c, float) assert pytest.approx(water_temp_c, tolerance) == 19.99996079 def test_use_sed_temp( - initial_tsm_state, + initial_tsm_state, default_meteo_params, default_temp_params, tolerance, ) -> None: """test the model with default parameters.""" assert True == True - #TODO: implement this test + # TODO: implement this test ... - From 34a3b708abcf0abdc9f4994d70f46850b02cee60 Mon Sep 17 00:00:00 2001 From: xaviernogueira Date: Mon, 4 Dec 2023 18:15:49 -0500 Subject: [PATCH 15/16] Fixed wind_func issue -> all tests pass! --- tests/test_5_tsm_calculations.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_5_tsm_calculations.py b/tests/test_5_tsm_calculations.py index 78f11f9..96d32b9 100644 --- a/tests/test_5_tsm_calculations.py +++ b/tests/test_5_tsm_calculations.py @@ -404,7 +404,7 @@ def test_changed_wind_b( ) -> None: """test the model with default parameters.""" # alter parameters as necessary - default_meteo_params['wind_b'] = 1.0e-6 + default_meteo_params['wind_b'] = 1.0 # instantiate the model tsm: EnergyBudget = get_energy_budget_instance( From b1c99971a49ebff58491a6b450c484cae26b0708 Mon Sep 17 00:00:00 2001 From: xaviernogueira Date: Mon, 4 Dec 2023 18:15:59 -0500 Subject: [PATCH 16/16] Formatting --- src/clearwater_modules/tsm/processes.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/clearwater_modules/tsm/processes.py b/src/clearwater_modules/tsm/processes.py index 4080091..1a42990 100644 --- a/src/clearwater_modules/tsm/processes.py +++ b/src/clearwater_modules/tsm/processes.py @@ -156,8 +156,8 @@ def q_sensible( """ return ( wind_kh_kw * - cp_air * - density_water * + cp_air * + density_water * wind_function * (air_temp_k - water_temp_k) )