Skip to content

Commit

Permalink
Merge pull request #178 from LabSid-USP/feature/155-implement-start-d…
Browse files Browse the repository at this point in the history
…ate-alignment-for-input-raster-series

Implement start date alignment for input raster series
  • Loading branch information
soaressgabriel authored Mar 20, 2024
2 parents e8f9aa4 + a5512bf commit 7402de2
Show file tree
Hide file tree
Showing 6 changed files with 122 additions and 54 deletions.
26 changes: 0 additions & 26 deletions doc/source/generated/rubem.core.Model.rst

This file was deleted.

44 changes: 35 additions & 9 deletions doc/source/userguide.rst
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,10 @@ Digital Elevation Map (DEM)

Mandatory path to Digital Elevation Map (DEM) file `[masl] <https://wiki.gis.com/wiki/index.php/Meters_above_sea_level>`_ 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. <fileformats:Digital Elevation Map (DEM) raster>`

.. 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)
``````````````````````````
Expand All @@ -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. <fileformats:Local Drain Direction (LDD) raster>`

.. note::
.. warning::

If not specified in the simulation configuration, it will be automatically generated using ``lddcreate`` from `PCRaster <https://pcraster.geo.uu.nl/pcraster/latest/documentation/pcraster_manual/sphinx/op_lddcreate.html>`_.

Expand Down Expand Up @@ -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. <userguide: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. <userguide: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. <userguide: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. <userguide: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
----------------
Expand Down Expand Up @@ -152,6 +177,7 @@ Mandatory path to a tabular file with values :raw-html:`[g/cm<sup>3</sup>]` 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. <saturated-hydraulic-conductivity-table>`

.. code-block:: dosini
[TABLES]
Expand Down
6 changes: 2 additions & 4 deletions rubem/_dynamic_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -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}")

Expand All @@ -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:
Expand All @@ -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:
Expand Down
11 changes: 9 additions & 2 deletions rubem/configuration/model_configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,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(
Expand Down
56 changes: 43 additions & 13 deletions rubem/configuration/simulation_period.py
Original file line number Diff line number Diff line change
@@ -1,35 +1,65 @@
from datetime import date
from datetime import date, datetime
import logging
from typing import Optional, Union

DATE_FORMAT = "%d/%m/%Y"


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[Union[date, datetime]]
:raises ValueError: If the start date is not before the end date.
"""

def __init__(self, start: date, end: date):
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:
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.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

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:
self.logger.error(
"Alignment date (%s) is after start date (%s).",
alignment.strftime(DATE_FORMAT),
self.start_date.strftime(DATE_FORMAT),
)
raise ValueError(
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
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}"
33 changes: 33 additions & 0 deletions tests/unit/configuration/test_simulation_period.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)

0 comments on commit 7402de2

Please sign in to comment.