diff --git a/docs/api_reference.md b/docs/api_reference.md index c0b48ee..44f848f 100644 --- a/docs/api_reference.md +++ b/docs/api_reference.md @@ -38,7 +38,7 @@ hide: ::: diffwofost.physical_models.engine.Engine -::: diffwofost.physical_models.utils.EngineTestHelper +::: diffwofost.physical_models.test.EngineTestHelper ## **Other classes (for developers)** diff --git a/src/diffwofost/physical_models/test.py b/src/diffwofost/physical_models/test.py new file mode 100644 index 0000000..bd2817a --- /dev/null +++ b/src/diffwofost/physical_models/test.py @@ -0,0 +1,158 @@ +import torch +import yaml +from pcse import signals +from pcse.base.parameter_providers import ParameterProvider +from pcse.base.weather import WeatherDataContainer +from pcse.base.weather import WeatherDataProvider +from pcse.settings import settings +from diffwofost.physical_models.config import ComputeConfig +from diffwofost.physical_models.engine import Engine + + +class EngineTestHelper(Engine): + """An engine which is purely for running the YAML unit tests.""" + + def _run(self): + """Make one time step of the simulation.""" + # Update timer + self.day, delt = self.timer() + + self.kiosk(self.day) + # When the list of external states is exhausted, send crop_finish to + # end the test run + if self.kiosk.external_states_exhausted: + self._send_signal( + signal=signals.crop_finish, day=self.day, finish_type="maturity", crop_delete=False + ) + + # State integration and update to forced variables + self.integrate(self.day, delt) + + # Driving variables + self.drv = self._get_driving_variables(self.day) + + # Agromanagement decisions + self.agromanager(self.day, self.drv) + + # Rate calculation + self.calc_rates(self.day, self.drv) + + if self.flag_terminate is True: + self._terminate_simulation(self.day) + + +class WeatherDataProviderTestHelper(WeatherDataProvider): + """It stores the weatherdata contained within the YAML tests.""" + + def __init__(self, yaml_weather, meteo_range_checks=True): + super().__init__() + # This is a temporary workaround. The `METEO_RANGE_CHECKS` logic in + # `__setattr__` method in `WeatherDataContainer` is not vector compatible + # yet. So we can disable it here when creating the `WeatherDataContainer` + # instances with arrays. + settings.METEO_RANGE_CHECKS = meteo_range_checks + for weather in yaml_weather: + weather_inputs = {k: v for k, v in weather.items() if k != "SNOWDEPTH"} + wdc = WeatherDataContainer(**weather_inputs) + self._store_WeatherDataContainer(wdc, wdc.DAY) + + +def prepare_engine_input( + test_data, crop_model_params, device=None, dtype=None, meteo_range_checks=True +): + """Prepare the inputs for the engine from the YAML file.""" + # If not specified, use default dtype and device + if device is None: + device = ComputeConfig.get_device() + if dtype is None: + dtype = ComputeConfig.get_dtype() + + agro_management_inputs = test_data["AgroManagement"] + cropd = test_data["ModelParameters"] + + weather_data_provider = WeatherDataProviderTestHelper( + test_data["WeatherVariables"], meteo_range_checks=meteo_range_checks + ) + + # The PCSE WeatherDataContainer stores required variables as Python floats. + # Some of our tests rely on weather inputs being torch.Tensors (e.g. to + # broadcast/batch weather variables). We only do this conversion when + # METEO_RANGE_CHECKS is disabled because the PCSE range checks assume + # scalar floats. + if not meteo_range_checks: + for (_, _), wdc in weather_data_provider.store.items(): + for varname in ( + "IRRAD", + "TMIN", + "TMAX", + "TEMP", + "VAP", + "RAIN", + "WIND", + "E0", + "ES0", + "ET0", + ): + if hasattr(wdc, varname): + value = getattr(wdc, varname) + if not isinstance(value, torch.Tensor): + setattr(wdc, varname, torch.tensor(value, dtype=dtype, device=device)) + crop_model_params_provider = ParameterProvider(cropdata=cropd) + external_states = test_data.get("ExternalStates") or [] + + # convert parameters to tensors + crop_model_params_provider.clear_override() + for name in crop_model_params: + # if name is missing in the YAML, skip it + if name in crop_model_params_provider: + value = torch.tensor(crop_model_params_provider[name], dtype=dtype, device=device) + crop_model_params_provider.set_override(name, value, check=False) + + # convert external states to tensors + tensor_external_states = [ + { + k: v if k == "DAY" else torch.tensor(v, dtype=dtype, device=device) + for k, v in item.items() + } + for item in external_states + ] + return ( + crop_model_params_provider, + weather_data_provider, + agro_management_inputs, + tensor_external_states, + ) + + +def get_test_data(test_data_path): + """Get the test data from the YAML file.""" + with open(test_data_path) as f: + return yaml.safe_load(f) + + +def calculate_numerical_grad(get_model_fn, param_name, param_value, out_name): + """Calculate the numerical gradient of output with respect to a parameter.""" + delta = 1e-6 + + # Parameters like RDRRTB are batched tables, so we need to compute + # the gradient for each table element separately. + # Flatten for easier indexing; clone once so we can restore in-place. + param_flat = param_value.detach().reshape(-1).clone() + grad_flat = torch.zeros_like(param_flat) + + with torch.no_grad(): + for i in range(param_flat.numel()): + orig = param_flat[i].item() + + param_flat[i] = orig + delta + model = get_model_fn() + loss_plus = model({param_name: param_flat.view_as(param_value)})[out_name].sum() + + param_flat[i] = orig - delta + model = get_model_fn() + loss_minus = model({param_name: param_flat.view_as(param_value)})[out_name].sum() + + grad_flat[i] = (loss_plus - loss_minus) / (2 * delta) + param_flat[i] = orig # restore for next iteration + + return grad_flat.view_as(param_value) diff --git a/src/diffwofost/physical_models/utils.py b/src/diffwofost/physical_models/utils.py index d7434af..23de91e 100644 --- a/src/diffwofost/physical_models/utils.py +++ b/src/diffwofost/physical_models/utils.py @@ -1,181 +1,15 @@ -"""This file contains code that is required to run the YAML unit tests. - -It contains: - - EngineTestHelper: engine specifically for running the YAML tests. - - WeatherDataProviderTestHelper: a weatherdata provides that takes the weather - inputs from the YAML file. - -Note that the code here is *not* python2 compatible. -""" - import logging import math from collections import namedtuple from collections.abc import Iterable import torch -import yaml -from pcse import signals -from pcse.base.parameter_providers import ParameterProvider -from pcse.base.weather import WeatherDataContainer -from pcse.base.weather import WeatherDataProvider -from pcse.settings import settings from pcse.traitlets import TraitType from pcse.util import doy from diffwofost.physical_models.config import ComputeConfig -from diffwofost.physical_models.engine import Engine logging.disable(logging.CRITICAL) -class EngineTestHelper(Engine): - """An engine which is purely for running the YAML unit tests.""" - - def _run(self): - """Make one time step of the simulation.""" - # Update timer - self.day, delt = self.timer() - - self.kiosk(self.day) - # When the list of external states is exhausted, send crop_finish to - # end the test run - if self.kiosk.external_states_exhausted: - self._send_signal( - signal=signals.crop_finish, day=self.day, finish_type="maturity", crop_delete=False - ) - - # State integration and update to forced variables - self.integrate(self.day, delt) - - # Driving variables - self.drv = self._get_driving_variables(self.day) - - # Agromanagement decisions - self.agromanager(self.day, self.drv) - - # Rate calculation - self.calc_rates(self.day, self.drv) - - if self.flag_terminate is True: - self._terminate_simulation(self.day) - - -class WeatherDataProviderTestHelper(WeatherDataProvider): - """It stores the weatherdata contained within the YAML tests.""" - - def __init__(self, yaml_weather, meteo_range_checks=True): - super().__init__() - # This is a temporary workaround. The `METEO_RANGE_CHECKS` logic in - # `__setattr__` method in `WeatherDataContainer` is not vector compatible - # yet. So we can disable it here when creating the `WeatherDataContainer` - # instances with arrays. - settings.METEO_RANGE_CHECKS = meteo_range_checks - for weather in yaml_weather: - weather_inputs = {k: v for k, v in weather.items() if k != "SNOWDEPTH"} - wdc = WeatherDataContainer(**weather_inputs) - self._store_WeatherDataContainer(wdc, wdc.DAY) - - -def prepare_engine_input( - test_data, crop_model_params, device=None, dtype=None, meteo_range_checks=True -): - """Prepare the inputs for the engine from the YAML file.""" - # If not specified, use default dtype and device - if device is None: - device = ComputeConfig.get_device() - if dtype is None: - dtype = ComputeConfig.get_dtype() - - agro_management_inputs = test_data["AgroManagement"] - cropd = test_data["ModelParameters"] - - weather_data_provider = WeatherDataProviderTestHelper( - test_data["WeatherVariables"], meteo_range_checks=meteo_range_checks - ) - - # The PCSE WeatherDataContainer stores required variables as Python floats. - # Some of our tests rely on weather inputs being torch.Tensors (e.g. to - # broadcast/batch weather variables). We only do this conversion when - # METEO_RANGE_CHECKS is disabled because the PCSE range checks assume - # scalar floats. - if not meteo_range_checks: - for (_, _), wdc in weather_data_provider.store.items(): - for varname in ( - "IRRAD", - "TMIN", - "TMAX", - "TEMP", - "VAP", - "RAIN", - "WIND", - "E0", - "ES0", - "ET0", - ): - if hasattr(wdc, varname): - value = getattr(wdc, varname) - if not isinstance(value, torch.Tensor): - setattr(wdc, varname, torch.tensor(value, dtype=dtype, device=device)) - crop_model_params_provider = ParameterProvider(cropdata=cropd) - external_states = test_data.get("ExternalStates") or [] - - # convert parameters to tensors - crop_model_params_provider.clear_override() - for name in crop_model_params: - # if name is missing in the YAML, skip it - if name in crop_model_params_provider: - value = torch.tensor(crop_model_params_provider[name], dtype=dtype, device=device) - crop_model_params_provider.set_override(name, value, check=False) - - # convert external states to tensors - tensor_external_states = [ - { - k: v if k == "DAY" else torch.tensor(v, dtype=dtype, device=device) - for k, v in item.items() - } - for item in external_states - ] - return ( - crop_model_params_provider, - weather_data_provider, - agro_management_inputs, - tensor_external_states, - ) - - -def get_test_data(test_data_path): - """Get the test data from the YAML file.""" - with open(test_data_path) as f: - return yaml.safe_load(f) - - -def calculate_numerical_grad(get_model_fn, param_name, param_value, out_name): - """Calculate the numerical gradient of output with respect to a parameter.""" - delta = 1e-6 - - # Parameters like RDRRTB are batched tables, so we need to compute - # the gradient for each table element separately. - # Flatten for easier indexing; clone once so we can restore in-place. - param_flat = param_value.detach().reshape(-1).clone() - grad_flat = torch.zeros_like(param_flat) - - with torch.no_grad(): - for i in range(param_flat.numel()): - orig = param_flat[i].item() - - param_flat[i] = orig + delta - model = get_model_fn() - loss_plus = model({param_name: param_flat.view_as(param_value)})[out_name].sum() - - param_flat[i] = orig - delta - model = get_model_fn() - loss_minus = model({param_name: param_flat.view_as(param_value)})[out_name].sum() - - grad_flat[i] = (loss_plus - loss_minus) / (2 * delta) - param_flat[i] = orig # restore for next iteration - - return grad_flat.view_as(param_value) - - def daylength(day, latitude, angle=-4, dtype=None, device=None): """PyTorch-vectorized daylength calculation for a given day, latitude and base angle. diff --git a/tests/ml_models/crop/test_partitioning.py b/tests/ml_models/crop/test_partitioning.py index bb78928..4409782 100644 --- a/tests/ml_models/crop/test_partitioning.py +++ b/tests/ml_models/crop/test_partitioning.py @@ -6,9 +6,9 @@ from diffwofost.physical_models.config import ComputeConfig from diffwofost.physical_models.config import Configuration from diffwofost.physical_models.crop.partitioning import PartioningFactors -from diffwofost.physical_models.utils import EngineTestHelper -from diffwofost.physical_models.utils import get_test_data -from diffwofost.physical_models.utils import prepare_engine_input +from diffwofost.physical_models.test import EngineTestHelper +from diffwofost.physical_models.test import get_test_data +from diffwofost.physical_models.test import prepare_engine_input from diffwofost.physical_models.variablekiosk import VariableKiosk from .. import phy_data_folder diff --git a/tests/physical_models/crop/test_assimilation.py b/tests/physical_models/crop/test_assimilation.py index aac4c6a..2231911 100644 --- a/tests/physical_models/crop/test_assimilation.py +++ b/tests/physical_models/crop/test_assimilation.py @@ -4,11 +4,11 @@ from pcse.models import Wofost72_PP from diffwofost.physical_models.config import Configuration from diffwofost.physical_models.crop.assimilation import WOFOST72_Assimilation -from diffwofost.physical_models.utils import EngineTestHelper +from diffwofost.physical_models.test import EngineTestHelper +from diffwofost.physical_models.test import calculate_numerical_grad +from diffwofost.physical_models.test import get_test_data +from diffwofost.physical_models.test import prepare_engine_input from diffwofost.physical_models.utils import _afgen_y_mask -from diffwofost.physical_models.utils import calculate_numerical_grad -from diffwofost.physical_models.utils import get_test_data -from diffwofost.physical_models.utils import prepare_engine_input from .. import phy_data_folder assimilation_config = Configuration( diff --git a/tests/physical_models/crop/test_evapotranspiration.py b/tests/physical_models/crop/test_evapotranspiration.py index 5095b01..1a83b08 100644 --- a/tests/physical_models/crop/test_evapotranspiration.py +++ b/tests/physical_models/crop/test_evapotranspiration.py @@ -12,10 +12,10 @@ from diffwofost.physical_models.crop.evapotranspiration import EvapotranspirationCO2 from diffwofost.physical_models.crop.evapotranspiration import EvapotranspirationCO2Layered from diffwofost.physical_models.crop.evapotranspiration import EvapotranspirationWrapper -from diffwofost.physical_models.utils import EngineTestHelper -from diffwofost.physical_models.utils import calculate_numerical_grad -from diffwofost.physical_models.utils import get_test_data -from diffwofost.physical_models.utils import prepare_engine_input +from diffwofost.physical_models.test import EngineTestHelper +from diffwofost.physical_models.test import calculate_numerical_grad +from diffwofost.physical_models.test import get_test_data +from diffwofost.physical_models.test import prepare_engine_input from .. import phy_data_folder evapotranspiration_config = Configuration( diff --git a/tests/physical_models/crop/test_leaf_dynamics.py b/tests/physical_models/crop/test_leaf_dynamics.py index 8adc607..6e28f60 100644 --- a/tests/physical_models/crop/test_leaf_dynamics.py +++ b/tests/physical_models/crop/test_leaf_dynamics.py @@ -5,10 +5,10 @@ from pcse.models import Wofost72_PP from diffwofost.physical_models.config import Configuration from diffwofost.physical_models.crop.leaf_dynamics import WOFOST_Leaf_Dynamics -from diffwofost.physical_models.utils import EngineTestHelper -from diffwofost.physical_models.utils import calculate_numerical_grad -from diffwofost.physical_models.utils import get_test_data -from diffwofost.physical_models.utils import prepare_engine_input +from diffwofost.physical_models.test import EngineTestHelper +from diffwofost.physical_models.test import calculate_numerical_grad +from diffwofost.physical_models.test import get_test_data +from diffwofost.physical_models.test import prepare_engine_input from .. import phy_data_folder leaf_dynamics_config = Configuration( diff --git a/tests/physical_models/crop/test_partitioning.py b/tests/physical_models/crop/test_partitioning.py index 6ced4a7..d794082 100644 --- a/tests/physical_models/crop/test_partitioning.py +++ b/tests/physical_models/crop/test_partitioning.py @@ -6,10 +6,10 @@ from pcse.models import Wofost72_PP from diffwofost.physical_models.config import Configuration from diffwofost.physical_models.crop.partitioning import DVS_Partitioning -from diffwofost.physical_models.utils import EngineTestHelper -from diffwofost.physical_models.utils import calculate_numerical_grad -from diffwofost.physical_models.utils import get_test_data -from diffwofost.physical_models.utils import prepare_engine_input +from diffwofost.physical_models.test import EngineTestHelper +from diffwofost.physical_models.test import calculate_numerical_grad +from diffwofost.physical_models.test import get_test_data +from diffwofost.physical_models.test import prepare_engine_input from .. import phy_data_folder partitioning_config = Configuration(CROP=DVS_Partitioning, OUTPUT_VARS=["FR", "FL", "FS", "FO"]) diff --git a/tests/physical_models/crop/test_phenology.py b/tests/physical_models/crop/test_phenology.py index ff5dde7..b25afe5 100644 --- a/tests/physical_models/crop/test_phenology.py +++ b/tests/physical_models/crop/test_phenology.py @@ -5,10 +5,10 @@ from pcse.models import Wofost72_PP from diffwofost.physical_models.config import Configuration from diffwofost.physical_models.crop.phenology import DVS_Phenology -from diffwofost.physical_models.utils import EngineTestHelper -from diffwofost.physical_models.utils import calculate_numerical_grad -from diffwofost.physical_models.utils import get_test_data -from diffwofost.physical_models.utils import prepare_engine_input +from diffwofost.physical_models.test import EngineTestHelper +from diffwofost.physical_models.test import calculate_numerical_grad +from diffwofost.physical_models.test import get_test_data +from diffwofost.physical_models.test import prepare_engine_input from .. import phy_data_folder phenology_config = Configuration( diff --git a/tests/physical_models/crop/test_respiration.py b/tests/physical_models/crop/test_respiration.py index e028e69..0edb19e 100644 --- a/tests/physical_models/crop/test_respiration.py +++ b/tests/physical_models/crop/test_respiration.py @@ -5,10 +5,10 @@ from pcse.models import Wofost72_PP from diffwofost.physical_models.config import Configuration from diffwofost.physical_models.crop.respiration import WOFOST_Maintenance_Respiration -from diffwofost.physical_models.utils import EngineTestHelper -from diffwofost.physical_models.utils import calculate_numerical_grad -from diffwofost.physical_models.utils import get_test_data -from diffwofost.physical_models.utils import prepare_engine_input +from diffwofost.physical_models.test import EngineTestHelper +from diffwofost.physical_models.test import calculate_numerical_grad +from diffwofost.physical_models.test import get_test_data +from diffwofost.physical_models.test import prepare_engine_input from .. import phy_data_folder respiration_config = Configuration( diff --git a/tests/physical_models/crop/test_root_dynamics.py b/tests/physical_models/crop/test_root_dynamics.py index e1cde25..ecead5d 100644 --- a/tests/physical_models/crop/test_root_dynamics.py +++ b/tests/physical_models/crop/test_root_dynamics.py @@ -6,10 +6,10 @@ from pcse.models import Wofost72_PP from diffwofost.physical_models.config import Configuration from diffwofost.physical_models.crop.root_dynamics import WOFOST_Root_Dynamics -from diffwofost.physical_models.utils import EngineTestHelper -from diffwofost.physical_models.utils import calculate_numerical_grad -from diffwofost.physical_models.utils import get_test_data -from diffwofost.physical_models.utils import prepare_engine_input +from diffwofost.physical_models.test import EngineTestHelper +from diffwofost.physical_models.test import calculate_numerical_grad +from diffwofost.physical_models.test import get_test_data +from diffwofost.physical_models.test import prepare_engine_input from .. import phy_data_folder root_dynamics_config = Configuration( diff --git a/tests/physical_models/crop/test_stem_dynamics.py b/tests/physical_models/crop/test_stem_dynamics.py index 5348b33..2c5268f 100644 --- a/tests/physical_models/crop/test_stem_dynamics.py +++ b/tests/physical_models/crop/test_stem_dynamics.py @@ -5,10 +5,10 @@ from pcse.models import Wofost72_PP from diffwofost.physical_models.config import Configuration from diffwofost.physical_models.crop.stem_dynamics import WOFOST_Stem_Dynamics -from diffwofost.physical_models.utils import EngineTestHelper -from diffwofost.physical_models.utils import calculate_numerical_grad -from diffwofost.physical_models.utils import get_test_data -from diffwofost.physical_models.utils import prepare_engine_input +from diffwofost.physical_models.test import EngineTestHelper +from diffwofost.physical_models.test import calculate_numerical_grad +from diffwofost.physical_models.test import get_test_data +from diffwofost.physical_models.test import prepare_engine_input from .. import phy_data_folder stem_dynamics_config = Configuration( diff --git a/tests/physical_models/crop/test_storage_organ_dynamics.py b/tests/physical_models/crop/test_storage_organ_dynamics.py index 247bbda..1d7f5ef 100644 --- a/tests/physical_models/crop/test_storage_organ_dynamics.py +++ b/tests/physical_models/crop/test_storage_organ_dynamics.py @@ -5,10 +5,10 @@ from pcse.models import Wofost72_PP from diffwofost.physical_models.config import Configuration from diffwofost.physical_models.crop.storage_organ_dynamics import WOFOST_Storage_Organ_Dynamics -from diffwofost.physical_models.utils import EngineTestHelper -from diffwofost.physical_models.utils import calculate_numerical_grad -from diffwofost.physical_models.utils import get_test_data -from diffwofost.physical_models.utils import prepare_engine_input +from diffwofost.physical_models.test import EngineTestHelper +from diffwofost.physical_models.test import calculate_numerical_grad +from diffwofost.physical_models.test import get_test_data +from diffwofost.physical_models.test import prepare_engine_input from .. import phy_data_folder storage_dynamics_config = Configuration( diff --git a/tests/physical_models/crop/test_wofost72.py b/tests/physical_models/crop/test_wofost72.py index 5b4cf19..215b10c 100644 --- a/tests/physical_models/crop/test_wofost72.py +++ b/tests/physical_models/crop/test_wofost72.py @@ -15,11 +15,11 @@ from diffwofost.physical_models.override import normalize_components from diffwofost.physical_models.soil.classic_waterbalance import WaterbalanceFD from diffwofost.physical_models.soil.classic_waterbalance import WaterbalancePP -from diffwofost.physical_models.utils import EngineTestHelper +from diffwofost.physical_models.test import EngineTestHelper +from diffwofost.physical_models.test import calculate_numerical_grad +from diffwofost.physical_models.test import get_test_data +from diffwofost.physical_models.test import prepare_engine_input from diffwofost.physical_models.utils import _afgen_y_mask -from diffwofost.physical_models.utils import calculate_numerical_grad -from diffwofost.physical_models.utils import get_test_data -from diffwofost.physical_models.utils import prepare_engine_input from .. import phy_data_folder wofost72_config = Configuration( diff --git a/tests/physical_models/soil/test_waterbalance.py b/tests/physical_models/soil/test_waterbalance.py index c78fefe..58f478a 100644 --- a/tests/physical_models/soil/test_waterbalance.py +++ b/tests/physical_models/soil/test_waterbalance.py @@ -8,10 +8,10 @@ from diffwofost.physical_models.crop.wofost72 import Wofost72 from diffwofost.physical_models.soil.classic_waterbalance import WaterbalanceFD from diffwofost.physical_models.soil.classic_waterbalance import WaterbalancePP -from diffwofost.physical_models.utils import EngineTestHelper -from diffwofost.physical_models.utils import calculate_numerical_grad -from diffwofost.physical_models.utils import get_test_data -from diffwofost.physical_models.utils import prepare_engine_input +from diffwofost.physical_models.test import EngineTestHelper +from diffwofost.physical_models.test import calculate_numerical_grad +from diffwofost.physical_models.test import get_test_data +from diffwofost.physical_models.test import prepare_engine_input from .. import phy_data_folder waterbalance_config = Configuration( diff --git a/tests/physical_models/test_engine.py b/tests/physical_models/test_engine.py index ed9ee81..b7e760d 100644 --- a/tests/physical_models/test_engine.py +++ b/tests/physical_models/test_engine.py @@ -11,8 +11,8 @@ from diffwofost.physical_models.engine import Engine from diffwofost.physical_models.engine import _get_params_shape from diffwofost.physical_models.soil.classic_waterbalance import WaterbalancePP -from diffwofost.physical_models.utils import get_test_data -from diffwofost.physical_models.utils import prepare_engine_input +from diffwofost.physical_models.test import get_test_data +from diffwofost.physical_models.test import prepare_engine_input from . import phy_data_folder config = Configuration( diff --git a/tests/physical_models/test_utils.py b/tests/physical_models/test_utils.py index c1e976c..ef70beb 100644 --- a/tests/physical_models/test_utils.py +++ b/tests/physical_models/test_utils.py @@ -4,14 +4,14 @@ import pytest import torch from diffwofost.physical_models.config import ComputeConfig +from diffwofost.physical_models.test import WeatherDataProviderTestHelper +from diffwofost.physical_models.test import get_test_data +from diffwofost.physical_models.test import prepare_engine_input from diffwofost.physical_models.utils import Afgen from diffwofost.physical_models.utils import AfgenTrait -from diffwofost.physical_models.utils import WeatherDataProviderTestHelper from diffwofost.physical_models.utils import _get_drv from diffwofost.physical_models.utils import astro from diffwofost.physical_models.utils import daylength -from diffwofost.physical_models.utils import get_test_data -from diffwofost.physical_models.utils import prepare_engine_input from . import phy_data_folder ComputeConfig.set_dtype(torch.float64)