Skip to content

Commit d335ca0

Browse files
committed
Applies additional comments from Lukas
1 parent 48ed8ac commit d335ca0

2 files changed

Lines changed: 63 additions & 28 deletions

File tree

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
"""
2+
This file is part of CLIMADA.
3+
4+
Copyright (C) 2017 ETH Zurich, CLIMADA contributors listed in AUTHORS.
5+
6+
CLIMADA is free software: you can redistribute it and/or modify it under the
7+
terms of the GNU General Public License as published by the Free
8+
Software Foundation, version 3.
9+
10+
CLIMADA is distributed in the hope that it will be useful, but WITHOUT ANY
11+
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
12+
PARTICULAR PURPOSE. See the GNU General Public License for more details.
13+
14+
You should have received a copy of the GNU General Public License along
15+
with CLIMADA. If not, see <https://www.gnu.org/licenses/>.
16+
17+
---
18+
19+
This module implements measure and measure set objects, as well as cost income
20+
objects which enable the definition of adapation measures and their associated
21+
effects on each part of risk/impacts (exposure, vulnerability and hazard).
22+
23+
"""
24+
25+
from .measure_config import MeasureConfig
26+
27+
__all__ = ["MeasureConfig"]

climada/entity/measures/measure_config.py

Lines changed: 36 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -23,19 +23,27 @@
2323

2424
import logging
2525
import warnings
26-
from abc import ABC
27-
from dataclasses import asdict, dataclass, field, fields
26+
from dataclasses import MISSING, dataclass, field, fields
2827
from datetime import datetime
29-
from typing import Dict, Optional, Tuple, Union
28+
from typing import Any, Dict, Optional, Tuple
3029

3130
import numpy as np
3231
import pandas as pd
32+
import yaml
3333

3434
LOGGER = logging.getLogger(__name__)
3535

36+
__all__ = [
37+
"HazardModifierConfig",
38+
"ExposuresModifierConfig",
39+
"ImpfsetModifierConfig",
40+
"CostIncomeConfig",
41+
"MeasureConfig",
42+
]
43+
3644

3745
@dataclass
38-
class _ModifierConfig:
46+
class ModifierConfig:
3947
"""
4048
Abstract base class for all modifier configuration dataclasses.
4149
@@ -44,7 +52,7 @@ class _ModifierConfig:
4452
be instantiated directly.
4553
"""
4654

47-
def _filter_out_default_fields(self) -> dict[str, Any]:
55+
def _filter_out_default_fields(self) -> tuple[dict[str, Any], dict[str, Any]]:
4856
"""
4957
Partition the instance's fields into non-default and default groups.
5058
@@ -64,7 +72,7 @@ def _filter_out_default_fields(self) -> dict[str, Any]:
6472
for defined_field in fields(self):
6573
val = getattr(self, defined_field.name)
6674
default = defined_field.default
67-
if default is MISSING:
75+
if defined_field.default_factory is not MISSING:
6876
default = defined_field.default_factory()
6977

7078
if val != default:
@@ -76,7 +84,7 @@ def _filter_out_default_fields(self) -> dict[str, Any]:
7684
non_defaults.pop("haz_type")
7785
return non_defaults, defaults
7886

79-
def to_dict(self, omit_default : bool = True) -> dict[str, Any]:
87+
def to_dict(self, omit_default: bool = True) -> dict[str, Any]:
8088
"""
8189
Serialize the config to a flat dictionary, omitting default values.
8290
@@ -89,8 +97,11 @@ def to_dict(self, omit_default : bool = True) -> dict[str, Any]:
8997
Dictionary containing only fields whose values differ from
9098
their dataclass defaults.
9199
"""
92-
non_default, _ = self._filter_out_default_fields()
93-
return non_default
100+
non_defaults, defaults = self._filter_out_default_fields()
101+
if omit_default:
102+
return non_defaults
103+
104+
return defaults | non_defaults
94105

95106
@classmethod
96107
def from_dict(cls, kwargs_dict: dict):
@@ -129,8 +140,8 @@ def _filter_dict_to_fields(cls, to_filter: dict):
129140
dataclass fields on this class.
130141
"""
131142

132-
fields = [f.name for f in fields(cls)]
133-
return {key: val for key, val in to_filter.items() if key in fields}
143+
field_names = [f.name for f in fields(cls)]
144+
return {key: val for key, val in to_filter.items() if key in field_names}
134145

135146
def __repr__(self) -> str:
136147
"""
@@ -166,7 +177,7 @@ def __repr__(self) -> str:
166177

167178

168179
@dataclass(repr=False)
169-
class ImpfsetModifierConfig(_ModifierConfig):
180+
class ImpfsetModifierConfig(ModifierConfig):
170181
"""
171182
Configuration for modifications to an impact function set.
172183
@@ -242,7 +253,7 @@ def __post_init__(self):
242253

243254

244255
@dataclass(repr=False)
245-
class HazardModifierConfig(_ModifierConfig):
256+
class HazardModifierConfig(ModifierConfig):
246257
"""
247258
Configuration for modifications to a hazard.
248259
@@ -303,7 +314,7 @@ def __post_init__(self):
303314

304315

305316
@dataclass(repr=False)
306-
class ExposuresModifierConfig(_ModifierConfig):
317+
class ExposuresModifierConfig(ModifierConfig):
307318
"""
308319
Configuration for modifications to an exposures object.
309320
@@ -349,7 +360,7 @@ def __post_init__(self):
349360

350361

351362
@dataclass(repr=False)
352-
class CostIncomeConfig(_ModifierConfig):
363+
class CostIncomeConfig(ModifierConfig):
353364
"""
354365
Serializable configuration for a ``CostIncome`` object.
355366
@@ -371,13 +382,14 @@ class CostIncomeConfig(_ModifierConfig):
371382
income_yearly_growth_rate : float, optional
372383
Annual growth rate applied to periodic income. Default is ``0.0``.
373384
freq : str, optional
374-
Pandas offset alias defining the period length (e.g. ``"Y"`` for
385+
Pandas period alias defining the period length (e.g. ``"Y"`` for
375386
yearly, ``"M"`` for monthly). Default is ``"Y"``.
387+
See [pandas documentation](https://pandas.pydata.org/pandas-docs/stable/user_guide/timeseries.html#period-aliases).
376388
custom_cash_flows : list of dict, optional
377389
Explicit cash flow schedule as a list of records with at minimum
378390
a ``"date"`` key (ISO 8601 string) and a value key. If provided,
379391
overrides the periodic cost/income logic.
380-
"""
392+
""" # noqa
381393

382394
mkt_price_year: Optional[int] = field(default_factory=lambda: datetime.today().year)
383395
init_cost: float = 0.0
@@ -426,7 +438,7 @@ def from_cost_income(cls, cost_income: "CostIncome") -> "CostIncomeConfig":
426438

427439

428440
@dataclass(repr=False)
429-
class MeasureConfig(_ModifierConfig):
441+
class MeasureConfig(ModifierConfig):
430442
"""
431443
Top-level serializable configuration for a single adaptation measure.
432444
@@ -453,7 +465,7 @@ class MeasureConfig(_ModifierConfig):
453465
cost_income : CostIncomeConfig
454466
Financial parameters associated with implementing this measure.
455467
implementation_duration : str, optional
456-
Pandas offset alias (e.g. ``"2Y"``) representing the time before
468+
Pandas period alias (e.g. ``"2Y"``) representing the time before
457469
the measure is fully operational. If ``None``, the measure takes
458470
effect immediately.
459471
color_rgb : tuple of float, optional
@@ -486,7 +498,7 @@ def __repr__(self) -> str:
486498
fields_str = "\n\t".join(f"{k}={v!r}" for k, v in self.__dict__.items())
487499
return f"{self.__class__.__name__}(\n\t{fields_str})"
488500

489-
def to_dict(self) -> dict:
501+
def to_dict(self, omit_default: bool = True) -> dict:
490502
"""
491503
Serialize the measure configuration to a flat dictionary.
492504
@@ -504,10 +516,10 @@ def to_dict(self) -> dict:
504516
return {
505517
"name": self.name,
506518
"haz_type": self.haz_type,
507-
**self.impfset_modifier.to_dict(),
508-
**self.hazard_modifier.to_dict(),
509-
**self.exposures_modifier.to_dict(),
510-
**self.cost_income.to_dict(),
519+
**self.impfset_modifier.to_dict(omit_default),
520+
**self.hazard_modifier.to_dict(omit_default),
521+
**self.exposures_modifier.to_dict(omit_default),
522+
**self.cost_income.to_dict(omit_default),
511523
"implementation_duration": self.implementation_duration,
512524
"color_rgb": list(self.color_rgb) if self.color_rgb is not None else None,
513525
}
@@ -579,8 +591,6 @@ def to_yaml(self, path: str) -> None:
579591
Destination file path. Will be created or overwritten.
580592
"""
581593

582-
import yaml
583-
584594
with open(path, "w") as opened_file:
585595
yaml.dump(
586596
{"measures": [self.to_dict()]},
@@ -609,8 +619,6 @@ def from_yaml(cls, path: str) -> "MeasureConfig":
609619
``measures``.
610620
"""
611621

612-
import yaml
613-
614622
with open(path) as opened_file:
615623
return cls.from_dict(yaml.safe_load(opened_file)["measures"][0])
616624

0 commit comments

Comments
 (0)