diff --git a/ra2ce/analysis/analysis_config_data/analysis_config_data.py b/ra2ce/analysis/analysis_config_data/analysis_config_data.py index 500d84405..2f2a891f5 100644 --- a/ra2ce/analysis/analysis_config_data/analysis_config_data.py +++ b/ra2ce/analysis/analysis_config_data/analysis_config_data.py @@ -29,6 +29,7 @@ from ra2ce.analysis.analysis_config_data.enums.analysis_damages_enum import ( AnalysisDamagesEnum, ) +from ra2ce.analysis.analysis_config_data.enums.analysis_enum import AnalysisEnum from ra2ce.analysis.analysis_config_data.enums.analysis_losses_enum import ( AnalysisLossesEnum, ) @@ -150,6 +151,42 @@ class AnalysisSectionDamages(AnalysisSectionBase): file_name: Optional[Path] = None +@dataclass +class AnalysisSectionAdaptation(AnalysisSectionBase): + """ + Reflects all possible settings that an adaptation analysis section might contain. + """ + + analysis: AnalysisEnum = AnalysisEnum.ADAPTATION + losses_analysis: AnalysisLossesEnum = AnalysisLossesEnum.SINGLE_LINK_LOSSES + discount_rate: float = 0.0 + time_horizon: float = 0.0 + vat: float = 0.0 + climate_factor: float = 0.0 + initial_frequency: float = 0.0 + # The option to not implement any adaptation measure + no_adaptation_option: AnalysisSectionAdaptationOption = field( + default_factory=lambda: AnalysisSectionAdaptationOption() + ) + adaptation_options: list[AnalysisSectionAdaptationOption] = field( + default_factory=list + ) + + +@dataclass +class AnalysisSectionAdaptationOption: + """ + Reflects all possible settings that an adaptation option might contain. + The id should be unique and is used to determine the location of the input and output files. + """ + + id: str = "" + name: str = "" + construction_cost: float = 0.0 + maintenance_interval: float = math.inf + maintenance_cost: float = 0.0 + + @dataclass class AnalysisConfigData(ConfigDataProtocol): """ @@ -194,6 +231,18 @@ def losses_list(self) -> list[AnalysisSectionLosses]: filter(lambda x: isinstance(x, AnalysisSectionLosses), self.analyses) ) + @property + def adaptation(self) -> AnalysisSectionAdaptation: + """ + Get the adaptation analysis from config. + + Returns: + AnalysisSectionAdaptation: Adaptation analysis. + """ + return next( + filter(lambda x: isinstance(x, AnalysisSectionAdaptation), self.analyses) + ) + @staticmethod def get_data_output(ini_file: Path) -> Path: return ini_file.parent.joinpath("output") diff --git a/ra2ce/analysis/analysis_config_data/analysis_config_data_reader.py b/ra2ce/analysis/analysis_config_data/analysis_config_data_reader.py index 236811499..55098a7cb 100644 --- a/ra2ce/analysis/analysis_config_data/analysis_config_data_reader.py +++ b/ra2ce/analysis/analysis_config_data/analysis_config_data_reader.py @@ -26,6 +26,8 @@ from ra2ce.analysis.analysis_config_data.analysis_config_data import ( AnalysisConfigData, + AnalysisSectionAdaptation, + AnalysisSectionAdaptationOption, AnalysisSectionBase, AnalysisSectionDamages, AnalysisSectionLosses, @@ -36,6 +38,7 @@ from ra2ce.analysis.analysis_config_data.enums.analysis_damages_enum import ( AnalysisDamagesEnum, ) +from ra2ce.analysis.analysis_config_data.enums.analysis_enum import AnalysisEnum from ra2ce.analysis.analysis_config_data.enums.analysis_losses_enum import ( AnalysisLossesEnum, ) @@ -241,14 +244,45 @@ def _get_analysis_section_damages( ) return _section + def _get_analysis_section_adaptation( + self, section_name: str + ) -> AnalysisSectionAdaptation: + def _get_adaptation_option( + section_name: str, + ) -> AnalysisSectionAdaptationOption: + return AnalysisSectionAdaptationOption(**self._parser[section_name]) + + _section = AnalysisSectionAdaptation(**self._parser[section_name]) + _section.losses_analysis = ( + _section.losses_analysis + ) = AnalysisDamagesEnum.get_enum( + self._parser.get(section_name, "losses_analysis", fallback=None) + ) + + _adaptation_options = list( + _adaptation_option + for _adaptation_option in self._parser.sections() + if "adaptationoption" in _adaptation_option + ) + if len(_adaptation_options) > 0: + _section.no_adaptation_option = _get_adaptation_option( + _adaptation_options[0] + ) + for _adaptation_option in _adaptation_options[1:]: + _section.adaptation_options.append( + _get_adaptation_option(_adaptation_option) + ) + + return _section + def get_analysis_sections(self) -> list[AnalysisSectionBase]: """ Extracts info from [analysis] sections Returns: - list[AnalysisSection]: List of analyses (both damages and losses) + list[AnalysisSection]: List of analyses (damages, losses and adaptation) """ - _analysis_sections = [] + _analysis_sections: list[AnalysisSectionBase] = [] _section_names = list( section_name @@ -261,6 +295,8 @@ def get_analysis_sections(self) -> list[AnalysisSectionBase]: _analysis_section = self._get_analysis_section_damages(_section_name) elif _analysis_type in LossesAnalysisNameList: _analysis_section = self._get_analysis_section_losses(_section_name) + elif _analysis_type == AnalysisEnum.ADAPTATION.config_value: + _analysis_section = self._get_analysis_section_adaptation(_section_name) else: raise ValueError(f"Analysis {_analysis_type} not supported.") _analysis_sections.append(_analysis_section) diff --git a/ra2ce/analysis/analysis_config_data/enums/analysis_enum.py b/ra2ce/analysis/analysis_config_data/enums/analysis_enum.py new file mode 100644 index 000000000..b6f4758aa --- /dev/null +++ b/ra2ce/analysis/analysis_config_data/enums/analysis_enum.py @@ -0,0 +1,6 @@ +from ra2ce.configuration.ra2ce_enum_base import Ra2ceEnumBase + + +class AnalysisEnum(Ra2ceEnumBase): + ADAPTATION = 2 + INVALID = 99 diff --git a/tests/analysis/analysis_config_data/test_analysis_config_data.py b/tests/analysis/analysis_config_data/test_analysis_config_data.py index 9861f7277..d4a3f1774 100644 --- a/tests/analysis/analysis_config_data/test_analysis_config_data.py +++ b/tests/analysis/analysis_config_data/test_analysis_config_data.py @@ -2,6 +2,8 @@ from ra2ce.analysis.analysis_config_data.analysis_config_data import ( AnalysisConfigData, + AnalysisSectionAdaptation, + AnalysisSectionAdaptationOption, AnalysisSectionDamages, AnalysisSectionLosses, DamagesAnalysisNameList, @@ -35,6 +37,15 @@ def valid_config(self) -> AnalysisConfigData: _config.analyses.append( AnalysisSectionDamages(analysis=AnalysisDamagesEnum.get_enum(_damages)) ) + _adaptation_config = AnalysisSectionAdaptation() + _adaptation_config.no_adaptation_option = AnalysisSectionAdaptationOption( + id="AO0" + ) + _adaptation_config.adaptation_options = [ + AnalysisSectionAdaptationOption(id="AO1"), + AnalysisSectionAdaptationOption(id="AO2"), + ] + _config.analyses.append(AnalysisSectionAdaptation()) yield _config def test_losses(self, valid_config: AnalysisConfigData): @@ -59,6 +70,20 @@ def test_damages(self, valid_config: AnalysisConfigData): # 3. Verify expectations assert all(item in _damages for item in DamagesAnalysisNameList) + def test_adaptation(self, valid_config: AnalysisConfigData): + # 1./2. Define test data/Run test + _adaptation = valid_config.adaptation + + # 3. Verify expectations + assert isinstance(_adaptation, AnalysisSectionAdaptation) + assert isinstance( + _adaptation.no_adaptation_option, AnalysisSectionAdaptationOption + ) + assert all( + isinstance(_item, AnalysisSectionAdaptationOption) + for _item in _adaptation.adaptation_options + ) + def test_get_data_output(self): # 1. Define test data _test_ini = test_results / "non_existing.ini" diff --git a/tests/test_data/acceptance_test_data/analyses.ini b/tests/test_data/acceptance_test_data/analyses.ini index 322946365..a9dea79a7 100644 --- a/tests/test_data/acceptance_test_data/analyses.ini +++ b/tests/test_data/acceptance_test_data/analyses.ini @@ -47,4 +47,34 @@ weighing = distance buffer_meters = 40 category_field_name = category save_gpkg = True -save_csv = True \ No newline at end of file +save_csv = True + +[analysis7] +name = adaptation module +analysis = adaptation +losses_analysis = single_link_losses +discount_rate = 0.05 +time_horizon = 10 +vat = 0.21 +climate_factor = 0.2 +initial_frequency = 0.2 +save_gpkg = True +save_csv = True + +[adaptation_option0] +id = AO0 +name = no adaptation + +[adaptation_option1] +id = AO1 +name = first adaptation option +construction_cost = 1000 +maintenance_interval = 5 +maintenance_cost = 100 + +[adaptation_option2] +id = AO2 +name = second adaptation option +construction_cost = 2000 +maintenance_interval = 2 +maintenance_cost = 400 \ No newline at end of file