Skip to content

Commit 35f9517

Browse files
luseverinpeanutfun
andauthored
Hazard forecast class (#1171)
* Create hazardForecast base class * Update datetime to timedelta for lead_time * Add tests --------- Co-authored-by: luseverin <[email protected]> Co-authored-by: Lukas Riedel <[email protected]>
1 parent d66c8dd commit 35f9517

File tree

3 files changed

+149
-32
lines changed

3 files changed

+149
-32
lines changed

climada/hazard/forecast.py

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,74 @@
1818
1919
Define Forecast variant of Hazard.
2020
"""
21+
22+
import logging
23+
24+
import numpy as np
25+
26+
from climada.hazard.base import Hazard
27+
from climada.util.forecast import Forecast
28+
29+
LOGGER = logging.getLogger(__name__)
30+
31+
32+
class HazardForecast(Forecast, Hazard):
33+
"""A hazard object with forecast information"""
34+
35+
def __init__(
36+
self,
37+
lead_time: np.ndarray | None = None,
38+
member: np.ndarray | None = None,
39+
**hazard_kwargs,
40+
):
41+
"""
42+
Initialize a HazardForecast object.
43+
44+
Parameters
45+
----------
46+
lead_time : np.ndarray of np.timedelta64 or None, optional
47+
Forecast lead times. Default is empty array.
48+
member : np.ndarray or None, optional
49+
Ensemble member identifiers as integers. Default is empty array.
50+
**hazard_kwargs
51+
keyword arguments to pass to :py:class:`~climada.hazard.base.Hazard` See
52+
py:meth`~climada.hazard.base.Hazard.__init__` for details.
53+
"""
54+
super().__init__(lead_time=lead_time, member=member, **hazard_kwargs)
55+
56+
@classmethod
57+
def from_hazard(cls, hazard: Hazard, lead_time: np.ndarray, member: np.ndarray):
58+
"""
59+
Create a HazardForecast object from a Hazard object.
60+
61+
Parameters
62+
----------
63+
hazard : climada.hazard.base.Hazard
64+
Hazard object to convert into a HazardForecast.
65+
lead_time : np.ndarray of np.timedelta64 or None, optional
66+
Forecast lead times. Default is empty array.
67+
member : np.ndarray or None, optional
68+
Ensemble member identifiers as integers. Default is empty array.
69+
70+
Returns
71+
-------
72+
HazardForecast
73+
A HazardForecast object with the same attributes as the input hazard,
74+
but with lead_time and member attributes set from instance of HazardForecast.
75+
"""
76+
return cls(
77+
lead_time=lead_time,
78+
member=member,
79+
haz_type=hazard.haz_type,
80+
pool=hazard.pool,
81+
units=hazard.units,
82+
centroids=hazard.centroids,
83+
event_id=hazard.event_id,
84+
frequency=hazard.frequency,
85+
frequency_unit=hazard.frequency_unit,
86+
event_name=hazard.event_name,
87+
date=hazard.date,
88+
orig=hazard.orig,
89+
intensity=hazard.intensity,
90+
fraction=hazard.fraction,
91+
)

climada/hazard/test/test_base.py

Lines changed: 22 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -46,27 +46,29 @@
4646
"""
4747

4848

49+
def hazard_kwargs():
50+
return {
51+
"haz_type": "TC",
52+
"pool": None,
53+
"intensity": sparse.csr_matrix(
54+
[[0.2, 0.3, 0.4], [0.1, 0.1, 0.01], [4.3, 2.1, 1.0], [5.3, 0.2, 0.0]]
55+
),
56+
"fraction": sparse.csr_matrix(
57+
[[0.02, 0.03, 0.04], [0.01, 0.01, 0.01], [0.3, 0.1, 0.0], [0.3, 0.2, 0.0]]
58+
),
59+
"centroids": Centroids(lat=np.array([1, 3, 5]), lon=np.array([2, 4, 6])),
60+
"event_id": np.array([1, 2, 3, 4]),
61+
"event_name": ["ev1", "ev2", "ev3", "ev4"],
62+
"date": np.array([1, 2, 3, 4]),
63+
"orig": np.array([True, False, False, True]),
64+
"frequency": np.array([0.1, 0.5, 0.5, 0.2]),
65+
"frequency_unit": "1/week",
66+
"units": "m/s",
67+
}
68+
69+
4970
def dummy_hazard():
50-
fraction = sparse.csr_matrix(
51-
[[0.02, 0.03, 0.04], [0.01, 0.01, 0.01], [0.3, 0.1, 0.0], [0.3, 0.2, 0.0]]
52-
)
53-
intensity = sparse.csr_matrix(
54-
[[0.2, 0.3, 0.4], [0.1, 0.1, 0.01], [4.3, 2.1, 1.0], [5.3, 0.2, 0.0]]
55-
)
56-
57-
return Hazard(
58-
"TC",
59-
intensity=intensity,
60-
fraction=fraction,
61-
centroids=Centroids(lat=np.array([1, 3, 5]), lon=np.array([2, 4, 6])),
62-
event_id=np.array([1, 2, 3, 4]),
63-
event_name=["ev1", "ev2", "ev3", "ev4"],
64-
date=np.array([1, 2, 3, 4]),
65-
orig=np.array([True, False, False, True]),
66-
frequency=np.array([0.1, 0.5, 0.5, 0.2]),
67-
frequency_unit="1/week",
68-
units="m/s",
69-
)
71+
return Hazard(**hazard_kwargs())
7072

7173

7274
class TestLoader(unittest.TestCase):

climada/hazard/test/test_forecast.py

Lines changed: 56 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -19,28 +19,72 @@
1919
Tests for Hazard Forecast.
2020
"""
2121

22+
import numpy as np
23+
import numpy.testing as npt
24+
import pandas as pd
2225
import pytest
26+
from scipy.sparse import csr_matrix
2327

24-
from climada.hazard import Hazard
28+
from climada.hazard.base import Hazard
29+
from climada.hazard.forecast import HazardForecast
30+
from climada.hazard.test.test_base import hazard_kwargs
2531

2632
# --- Examples for fixtures and test organization --- #
2733

2834

2935
@pytest.fixture
30-
def hazard():
31-
return Hazard()
36+
def haz_kwargs():
37+
return hazard_kwargs()
3238

3339

34-
def test_empty_hazard(hazard):
35-
assert hazard.size == 0
36-
assert hazard.haz_type == ""
40+
@pytest.fixture
41+
def hazard(haz_kwargs):
42+
return Hazard(**haz_kwargs)
43+
44+
45+
@pytest.fixture
46+
def lead_time():
47+
return pd.timedelta_range("1h", periods=6).to_numpy()
3748

3849

39-
class TestSomething:
50+
@pytest.fixture
51+
def member():
52+
return np.arange(6)
4053

41-
@pytest.fixture(autouse=True)
42-
def haz_type(self, hazard):
43-
hazard.haz_type = "foo"
4454

45-
def test_haz_type(self, hazard):
46-
assert hazard.haz_type == "foo"
55+
@pytest.fixture
56+
def haz_fc(lead_time, member, haz_kwargs):
57+
return HazardForecast(
58+
lead_time=lead_time,
59+
member=member,
60+
**haz_kwargs,
61+
)
62+
63+
64+
def assert_hazard_kwargs(hazard: Hazard, **kwargs):
65+
for key, value in kwargs.items():
66+
attr = getattr(hazard, key)
67+
if isinstance(value, (np.ndarray, list)):
68+
npt.assert_array_equal(attr, value)
69+
elif isinstance(value, csr_matrix):
70+
npt.assert_array_equal(attr.todense(), value.todense())
71+
else:
72+
assert attr == value
73+
74+
75+
def test_init_hazard_forecast(haz_fc, member, lead_time, haz_kwargs):
76+
assert isinstance(haz_fc, HazardForecast)
77+
npt.assert_array_equal(haz_fc.lead_time, lead_time)
78+
assert haz_fc.lead_time.dtype == lead_time.dtype
79+
npt.assert_array_equal(haz_fc.member, member)
80+
assert_hazard_kwargs(haz_fc, **haz_kwargs)
81+
82+
83+
def test_from_hazard(lead_time, member, hazard, haz_kwargs):
84+
haz_fc_from_haz = HazardForecast.from_hazard(
85+
hazard, lead_time=lead_time, member=member
86+
)
87+
assert isinstance(haz_fc_from_haz, HazardForecast)
88+
npt.assert_array_equal(haz_fc_from_haz.lead_time, lead_time)
89+
npt.assert_array_equal(haz_fc_from_haz.member, member)
90+
assert_hazard_kwargs(haz_fc_from_haz, **haz_kwargs)

0 commit comments

Comments
 (0)