From 141cab9c46cc7799f31180b6c1b60d66cfd9a8ac Mon Sep 17 00:00:00 2001 From: Gabriel Soares <70075435+soaressgabriel@users.noreply.github.com> Date: Fri, 15 Mar 2024 16:25:10 -0300 Subject: [PATCH 01/10] Add alignment parameter to `SimulationPeriod` constructor --- rubem/configuration/simulation_period.py | 37 +++++++++++++++++------- 1 file changed, 27 insertions(+), 10 deletions(-) diff --git a/rubem/configuration/simulation_period.py b/rubem/configuration/simulation_period.py index bbc8e84..2cb989f 100644 --- a/rubem/configuration/simulation_period.py +++ b/rubem/configuration/simulation_period.py @@ -1,5 +1,6 @@ from datetime import date import logging +from typing import Optional class SimulationPeriod: @@ -12,24 +13,40 @@ class SimulationPeriod: :param end: The end date of the simulation period. :type end: date + :param alignment: The date to align the simulation period to. If not provided, the start date is used. + :type alignment: Optional[date] + :raises ValueError: If the start date is not before the end date. """ - def __init__(self, start: date, end: date): + def __init__(self, start: date, end: date, alignment: Optional[date] = None): self.logger = logging.getLogger(__name__) + if start >= end: - self.logger.error("Start date must be before end date.") - raise ValueError("Start date must be before end date.") + self.logger.error("Start date (%s) must be before end date (%s).", start, end) + raise ValueError(f"Start date ({start}) must be before end date ({end}).") + self.start_date = start self.end_date = end - self.total_steps = ( - (self.end_date.year - self.start_date.year) * 12 - + (self.end_date.month - self.start_date.month) - + 1 - ) - self.first_step = 1 # PCRaster Dynamic Framework uses 1-based indexing - self.last_step = self.total_steps + if not alignment: + self.logger.info("No alignment date provided. Using start date as alignment.") + alignment = self.start_date + + if alignment < self.start_date or alignment > self.end_date: + self.logger.error( + "Date alignment must be between start (%s) and end (%s) dates.", + self.start_date, + self.end_date, + ) + raise ValueError( + f"Date alignment must be between start ({self.start_date}) and end ({self.end_date}) dates." + ) + + self.first_step = (start.year - alignment.year) * 12 + (start.month - alignment.month) + 1 + self.last_step = (end.year - alignment.year) * 12 + (end.month - alignment.month) + 1 + + self.total_steps = self.last_step - self.first_step + 1 def __str__(self) -> str: return f"{self.start_date} to {self.end_date}" From 18909ce3c744343028e32e60d7ecabf8ba04abad Mon Sep 17 00:00:00 2001 From: Gabriel Soares <70075435+soaressgabriel@users.noreply.github.com> Date: Fri, 15 Mar 2024 16:49:13 -0300 Subject: [PATCH 02/10] Fix alignment date validation in `SimulationPeriod` class --- rubem/configuration/simulation_period.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/rubem/configuration/simulation_period.py b/rubem/configuration/simulation_period.py index 2cb989f..97f900d 100644 --- a/rubem/configuration/simulation_period.py +++ b/rubem/configuration/simulation_period.py @@ -33,14 +33,12 @@ def __init__(self, start: date, end: date, alignment: Optional[date] = None): self.logger.info("No alignment date provided. Using start date as alignment.") alignment = self.start_date - if alignment < self.start_date or alignment > self.end_date: + if alignment > self.start_date: self.logger.error( - "Date alignment must be between start (%s) and end (%s) dates.", - self.start_date, - self.end_date, + "Alignment date (%s) is after start date (%s).", alignment, self.start_date ) raise ValueError( - f"Date alignment must be between start ({self.start_date}) and end ({self.end_date}) dates." + f"Alignment date ({alignment}) must be before start date ({self.start_date})." ) self.first_step = (start.year - alignment.year) * 12 + (start.month - alignment.month) + 1 From 8ed5e633fcc2d24ed77a2f5fdccb1dba526dbd92 Mon Sep 17 00:00:00 2001 From: Gabriel Soares <70075435+soaressgabriel@users.noreply.github.com> Date: Fri, 15 Mar 2024 16:49:33 -0300 Subject: [PATCH 03/10] Add unit tests for simulation period alignment --- .../configuration/test_simulation_period.py | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/tests/unit/configuration/test_simulation_period.py b/tests/unit/configuration/test_simulation_period.py index 2e6f948..0c217ea 100644 --- a/tests/unit/configuration/test_simulation_period.py +++ b/tests/unit/configuration/test_simulation_period.py @@ -45,3 +45,36 @@ def test_simulation_period_step_calculation( assert sp.first_step == first_step assert sp.last_step == last_step assert sp.total_steps == total_steps + + @pytest.mark.unit + @pytest.mark.parametrize( + "start, end, alignment, expected_first_step, expected_last_step, expected_total_steps", + [ + (date(2024, 1, 1), date(2024, 3, 31), date(2024, 1, 1), 1, 3, 3), + (date(2024, 1, 1), date(2024, 12, 31), date(2019, 1, 1), 61, 72, 12), + (date(2020, 1, 1), date(2024, 12, 31), date(2020, 1, 1), 1, 60, 60), + (date(2021, 1, 1), date(2024, 3, 31), date(2019, 1, 1), 25, 63, 39), + (date(2022, 1, 1), date(2024, 12, 31), date(2019, 1, 1), 37, 72, 36), + (date(2020, 1, 1), date(2024, 12, 31), date(2019, 1, 1), 13, 72, 60), + ], + ) + def test_simulation_period_alignment( + self, start, end, alignment, expected_first_step, expected_last_step, expected_total_steps + ): + sp = SimulationPeriod(start=start, end=end, alignment=alignment) + assert sp.first_step == expected_first_step + assert sp.last_step == expected_last_step + assert sp.total_steps == expected_total_steps + + @pytest.mark.unit + @pytest.mark.parametrize( + "start, end, alignment", + [ + (date(2024, 1, 1), date(2024, 3, 31), date(2024, 1, 2)), + (date(2024, 1, 1), date(2024, 12, 31), date(2024, 12, 31)), + (date(2020, 1, 1), date(2024, 12, 31), date(2025, 1, 1)), + ], + ) + def test_simulation_period_alignment_bad_args(self, start, end, alignment): + with pytest.raises(Exception): + SimulationPeriod(start=start, end=end, alignment=alignment) From 39632eb57e3930fa0d8adefd56b52826982cf70a Mon Sep 17 00:00:00 2001 From: Gabriel Soares <70075435+soaressgabriel@users.noreply.github.com> Date: Fri, 15 Mar 2024 17:10:08 -0300 Subject: [PATCH 04/10] Add optional configuration key `alignment` to section `SIM_TIME` --- rubem/configuration/model_configuration.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/rubem/configuration/model_configuration.py b/rubem/configuration/model_configuration.py index 7e07458..29d3ebf 100644 --- a/rubem/configuration/model_configuration.py +++ b/rubem/configuration/model_configuration.py @@ -69,8 +69,15 @@ def __init__( self.logger.debug("Loading configuration...") self.simulation_period = SimulationPeriod( - datetime.strptime(self.__get_setting("SIM_TIME", "start"), "%d/%m/%Y").date(), - datetime.strptime(self.__get_setting("SIM_TIME", "end"), "%d/%m/%Y").date(), + start=datetime.strptime(self.__get_setting("SIM_TIME", "start"), "%d/%m/%Y"), + end=datetime.strptime(self.__get_setting("SIM_TIME", "end"), "%d/%m/%Y"), + alignment=( + datetime.strptime( + self.__get_setting("SIM_TIME", "alignment", optional=True), "%d/%m/%Y" + ) + if self.__get_setting("SIM_TIME", "alignment", optional=True) + else None + ), ) self.grid = RasterGrid(float(self.__get_setting("GRID", "grid"))) self.calibration_parameters = CalibrationParameters( From 30c339842d1a4264e52ca9c5e7101a7b20b569d7 Mon Sep 17 00:00:00 2001 From: Gabriel Soares <70075435+soaressgabriel@users.noreply.github.com> Date: Fri, 15 Mar 2024 17:10:21 -0300 Subject: [PATCH 05/10] Update `SimulationPeriod` class to accept `datetime` objects --- rubem/configuration/simulation_period.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/rubem/configuration/simulation_period.py b/rubem/configuration/simulation_period.py index 97f900d..1d9fbc5 100644 --- a/rubem/configuration/simulation_period.py +++ b/rubem/configuration/simulation_period.py @@ -1,6 +1,6 @@ -from datetime import date +from datetime import date, datetime import logging -from typing import Optional +from typing import Optional, Union class SimulationPeriod: @@ -8,18 +8,23 @@ class SimulationPeriod: Represents a period of time for simulation. :param start: The start date of the simulation period. - :type start: date + :type start: Union[date, datetime] :param end: The end date of the simulation period. - :type end: date + :type end: Union[date, datetime] :param alignment: The date to align the simulation period to. If not provided, the start date is used. - :type alignment: Optional[date] + :type alignment: Optional[Union[date, datetime]] :raises ValueError: If the start date is not before the end date. """ - def __init__(self, start: date, end: date, alignment: Optional[date] = None): + def __init__( + self, + start: Union[date, datetime], + end: Union[date, datetime], + alignment: Optional[Union[date, datetime]] = None, + ): self.logger = logging.getLogger(__name__) if start >= end: From f2142ca9504e5e0d703c596efe8ccbd126fbf5b8 Mon Sep 17 00:00:00 2001 From: Gabriel Soares <70075435+soaressgabriel@users.noreply.github.com> Date: Fri, 15 Mar 2024 17:23:50 -0300 Subject: [PATCH 06/10] Remove supress_errors argument from readmap function calls --- rubem/_dynamic_model.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/rubem/_dynamic_model.py b/rubem/_dynamic_model.py index e9b40f9..34dc98e 100644 --- a/rubem/_dynamic_model.py +++ b/rubem/_dynamic_model.py @@ -210,7 +210,6 @@ def dynamic(self): current_ndvi = self.__readmap_series_wrapper( files_partial_path=self.config.raster_series.ndvi, dynamic_readmap_func=self.readmap, - supress_errors=True, ) self.previous_ndvi = current_ndvi except RuntimeError: @@ -226,7 +225,6 @@ def dynamic(self): current_landuse = self.__readmap_series_wrapper( files_partial_path=self.config.raster_series.landuse, dynamic_readmap_func=self.readmap, - supress_errors=True, ) self.previous_landuse = current_landuse except RuntimeError: From 43222c2be0f7e1f6ea5473bd1a5ea7ae2bbad971 Mon Sep 17 00:00:00 2001 From: Gabriel Soares <70075435+soaressgabriel@users.noreply.github.com> Date: Fri, 15 Mar 2024 17:38:35 -0300 Subject: [PATCH 07/10] Refactor date validation and error messages --- rubem/configuration/simulation_period.py | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/rubem/configuration/simulation_period.py b/rubem/configuration/simulation_period.py index 1d9fbc5..5a3b3d5 100644 --- a/rubem/configuration/simulation_period.py +++ b/rubem/configuration/simulation_period.py @@ -2,6 +2,8 @@ import logging from typing import Optional, Union +DATE_FORMAT = "%d/%m/%Y" + class SimulationPeriod: """ @@ -28,8 +30,14 @@ def __init__( self.logger = logging.getLogger(__name__) if start >= end: - self.logger.error("Start date (%s) must be before end date (%s).", start, end) - raise ValueError(f"Start date ({start}) must be before end date ({end}).") + self.logger.error( + "Start date (%s) must be before end date (%s).", + start.strftime(DATE_FORMAT), + end.strftime(DATE_FORMAT), + ) + raise ValueError( + f"Start date ({start.strftime(DATE_FORMAT)}) must be before end date ({end.strftime(DATE_FORMAT)})." + ) self.start_date = start self.end_date = end @@ -40,10 +48,12 @@ def __init__( if alignment > self.start_date: self.logger.error( - "Alignment date (%s) is after start date (%s).", alignment, self.start_date + "Alignment date (%s) is after start date (%s).", + alignment.strftime(DATE_FORMAT), + self.start_date.strftime(DATE_FORMAT), ) raise ValueError( - f"Alignment date ({alignment}) must be before start date ({self.start_date})." + f"Alignment date ({alignment.strftime(DATE_FORMAT)}) must be before start date ({self.start_date.strftime(DATE_FORMAT)})." ) self.first_step = (start.year - alignment.year) * 12 + (start.month - alignment.month) + 1 From dad67fae59b1431a7d25f5f92316001dbc1d8858 Mon Sep 17 00:00:00 2001 From: Gabriel Soares <70075435+soaressgabriel@users.noreply.github.com> Date: Fri, 15 Mar 2024 17:38:53 -0300 Subject: [PATCH 08/10] Update date format in logging message --- rubem/_dynamic_model.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rubem/_dynamic_model.py b/rubem/_dynamic_model.py index 34dc98e..798bca9 100644 --- a/rubem/_dynamic_model.py +++ b/rubem/_dynamic_model.py @@ -195,13 +195,13 @@ def dynamic(self): """ current_timestep = self.currentStep current_date = self.config.simulation_period.start_date + relativedelta( - months=current_timestep - 1 + months=(current_timestep - self.config.simulation_period.first_step) ) self.logger.info( "Cycle %s of %s (%s)", current_timestep, self.config.simulation_period.last_step, - current_date.strftime("%d/%m/%Y"), + current_date.strftime("%b/%Y"), ) print(f"## Timestep {current_timestep} of {self.config.simulation_period.last_step}") From b235a73e9357094b7ce2f5d25e06eb64958bfc76 Mon Sep 17 00:00:00 2001 From: Gabriel Soares <70075435+soaressgabriel@users.noreply.github.com> Date: Wed, 20 Mar 2024 15:39:40 -0300 Subject: [PATCH 09/10] Update userguide.rst with simulation configuration details for alignment date --- doc/source/userguide.rst | 44 ++++++++++++++++++++++++++++++++-------- 1 file changed, 35 insertions(+), 9 deletions(-) diff --git a/doc/source/userguide.rst b/doc/source/userguide.rst index 82bd1eb..1fc3525 100644 --- a/doc/source/userguide.rst +++ b/doc/source/userguide.rst @@ -32,10 +32,10 @@ Digital Elevation Map (DEM) Mandatory path to Digital Elevation Map (DEM) file `[masl] `_ in PCRaster map format :file:`*.map`: this map contains topographic ground elevation in meters. Must be a valid file path to a PCRaster map format :file:`*.map` file. :ref:`See more. ` - .. code-block:: dosini - - [RASTERS] - dem = /Dataset/UIGCRB/input/maps/dem/dem.map +.. code-block:: dosini + + [RASTERS] + dem = /Dataset/UIGCRB/input/maps/dem/dem.map Mask of Catchment (Clone) `````````````````````````` @@ -53,7 +53,7 @@ Local Drain Direction (LDD) Optional path to Local Drain Direction (LDD) file in PCRaster map format :file:`*.map`. This map contains the local flow direction of each cell in the catchment. Must be a valid file path to a PCRaster map format :file:`*.map` file. :ref:`See more. ` -.. note:: +.. warning:: If not specified in the simulation configuration, it will be automatically generated using ``lddcreate`` from `PCRaster `_. @@ -100,30 +100,55 @@ Mandatory cell dimension value in meters. Value has to correspond to the pixel r Simulation Period ````````````````` +.. warning:: + + All dates must be valid and fall within between the time period of the dataset input time range. + Start Date '''''''''' -Mandatory date of the first time step of the simulation scenario (day, month and year of the start period of simulation scenario). Start date must be before the end date. +Mandatory date of the first time step of the simulation scenario (day, month and year of the start period of simulation scenario). .. code-block:: dosini [SIM_TIME] start = 01/01/2000 +.. warning:: + + The start date must be less than the :ref:`end date. ` + End Date '''''''' -Mandatory date of the last time step of the simulation scenario (day, month and year of the last period of simulation scenario). End date must be after the start date. +Mandatory date of the last time step of the simulation scenario (day, month and year of the last period of simulation scenario). .. code-block:: dosini [SIM_TIME] end = 01/08/2000 -.. note:: +.. warning:: + + The end date must be greater than the :ref:`start date. ` + +Alignment Date +'''''''''''''' + +Optional date of the alignment time step of the simulation scenario (day, month and year of the alignment period of simulation scenario). If not specified, the alignment date will be the same as the :ref:`start date. ` + +.. code-block:: dosini - Both dates must be valid and fall within between the time period of the dataset input time range. The ``end`` date must be greater than the ``start`` date. + [SIM_TIME] + alignment = 01/01/2000 +.. warning:: + + The alignment date must be before the :ref:`start date. ` + +.. note:: + + In certain scenarios, modelers may need to initiate simulations from a date other than the one corresponding to file ``*.001``. This will allow the transformation of time steps accordingly, ensuring alignment with the desired starting date for the simulation. Soil Parameters ---------------- @@ -152,6 +177,7 @@ Mandatory path to a tabular file with values :raw-html:`[g/cm3]` of B ```````````````````````````````````````````````````````````````````````````````` Mandatory path to a tabular file with values [mm/month] of saturated hydraulic conductivity for each soil class. Must be a valid path to an existing text file :file:`*.txt` or comma-separated values (CSV) file :file:`*.csv`. :ref:`See more. ` + .. code-block:: dosini [TABLES] From a5512bf555271e66f30379ebc4d580022a6d73b1 Mon Sep 17 00:00:00 2001 From: Gabriel Soares <70075435+soaressgabriel@users.noreply.github.com> Date: Wed, 20 Mar 2024 15:41:33 -0300 Subject: [PATCH 10/10] Delete old rubem.core.Model doc class --- doc/source/generated/rubem.core.Model.rst | 26 ----------------------- 1 file changed, 26 deletions(-) delete mode 100644 doc/source/generated/rubem.core.Model.rst diff --git a/doc/source/generated/rubem.core.Model.rst b/doc/source/generated/rubem.core.Model.rst deleted file mode 100644 index 5d5d90a..0000000 --- a/doc/source/generated/rubem.core.Model.rst +++ /dev/null @@ -1,26 +0,0 @@ -rubem.core.Model -================ - -.. currentmodule:: rubem.core - -.. autoclass:: Model - :members: - :show-inheritance: - :inherited-members: - :special-members: __call__, __add__, __mul__ - - - - .. rubric:: Methods - - .. autosummary:: - :nosignatures: - - ~Model.load - ~Model.run - - - - - - \ No newline at end of file