Skip to content

Commit

Permalink
feat: 681 remove need for having input config as input to analysisfac…
Browse files Browse the repository at this point in the history
…toryrun (#682)
  • Loading branch information
ArdtK authored Jan 20, 2025
1 parent 7482165 commit af2dbd8
Show file tree
Hide file tree
Showing 16 changed files with 231 additions and 263 deletions.
Binary file modified docs/_diagrams/ra2ce_analysis_class_diagram.drawio.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/_diagrams/ra2ce_package_overview.drawio.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
13 changes: 13 additions & 0 deletions ra2ce/analysis/analysis_collection.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,19 @@ class AnalysisCollection:
losses_analyses: list[AnalysisLossesProtocol] = field(default_factory=list)
adaptation_analysis: Adaptation = None

@property
def analyses(self) -> list[AnalysisProtocol]:
"""
Get all analyses in the collection.
Returns:
list[AnalysisProtocol]: All analyses in the collection.
"""
_analyses = self.damages_analyses + self.losses_analyses
if self.adaptation_analysis:
_analyses.append(self.adaptation_analysis)
return _analyses

@classmethod
def from_config(
cls, analysis_config: AnalysisConfigWrapper
Expand Down
2 changes: 1 addition & 1 deletion ra2ce/ra2ce_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ def run_analysis(self) -> list[AnalysisResultWrapper]:
logging.error(_error)
raise ValueError(_error)

return AnalysisRunnerFactory.run(self.input_config, self.analysis_collection)
return AnalysisRunnerFactory.run(self.analysis_collection)

@staticmethod
def run_with_ini_files(
Expand Down
2 changes: 1 addition & 1 deletion ra2ce/runners/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

This module contains all the available runners that can be used in this tool.

Each runner is specific for a given analysis type, to decide which one should be picked we make use of a _factory_ (`AnalysisRunnerFactory`). In addition, you can directly run all available (and supported) analyses for a given configuration (`ConfigWrapper`) simply by doing `AnalysisRunnerFactory.run(config)`.
Each runner is specific for a given analysis type, to decide which one should be picked we make use of a _factory_ (`AnalysisRunnerFactory`). In addition, you can directly run all available (and supported) analyses for a given collection of analyses (`AnalysisCollection`) simply by doing `AnalysisRunnerFactory.run(analysis_collection)`.

The result of an analysis runner __execution__ will be a collection of `AnalysisResultWrapper`, an object containing information of the type of analysis ran and its result.

Expand Down
31 changes: 20 additions & 11 deletions ra2ce/runners/adaptation_analysis_runner.py
Original file line number Diff line number Diff line change
@@ -1,23 +1,32 @@
"""
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Risk Assessment and Adaptation for Critical Infrastructure (RA2CE).
Copyright (C) 2023 Stichting Deltares
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
from ra2ce.analysis.adaptation.adaptation import Adaptation
from ra2ce.analysis.analysis_collection import AnalysisCollection
from ra2ce.configuration.config_wrapper import ConfigWrapper
from ra2ce.runners.damages_analysis_runner import DamagesAnalysisRunner
from ra2ce.runners.simple_analysis_runner_base import SimpleAnalysisRunnerBase


class AdaptationAnalysisRunner(SimpleAnalysisRunnerBase):
def __str__(self):
return "Adaptation Analysis Runner"

@staticmethod
def can_run(ra2ce_input: ConfigWrapper) -> bool:
if (
not ra2ce_input.analysis_config
or not ra2ce_input.analysis_config.config_data.adaptation
):
return False
return DamagesAnalysisRunner.can_run(ra2ce_input)

@staticmethod
def filter_supported_analyses(
analysis_collection: AnalysisCollection,
Expand Down
57 changes: 30 additions & 27 deletions ra2ce/runners/analysis_runner_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,9 @@
"""

import logging
from typing import Type

from ra2ce.analysis.analysis_collection import AnalysisCollection
from ra2ce.analysis.analysis_result.analysis_result_wrapper import AnalysisResultWrapper
from ra2ce.configuration.config_wrapper import ConfigWrapper
from ra2ce.runners.adaptation_analysis_runner import AdaptationAnalysisRunner
from ra2ce.runners.analysis_runner_protocol import AnalysisRunner
from ra2ce.runners.damages_analysis_runner import DamagesAnalysisRunner
Expand All @@ -33,56 +31,61 @@

class AnalysisRunnerFactory:
@staticmethod
def get_supported_runners(ra2ce_input: ConfigWrapper) -> list[Type[AnalysisRunner]]:
def get_supported_runners(
analysis_collection: AnalysisCollection,
) -> list[AnalysisRunner]:
"""
Gets the supported analysis runners for the given input.
Args:
ra2ce_input (Ra2ceInput): Input representing a set of network and analysis ini configurations.
analysis_collection (AnalysisCollection): Collection of analyses to run.
Returns:
list[AnalysisRunner]: Supported runners for the given configuration.
"""
if not ra2ce_input.analysis_config:
if not analysis_collection:
return []
_available_runners = [
DamagesAnalysisRunner,
LossesAnalysisRunner,
AdaptationAnalysisRunner,
]
_supported_runners = [
_runner for _runner in _available_runners if _runner.can_run(ra2ce_input)
_available_runners: list[AnalysisRunner] = [
DamagesAnalysisRunner(),
LossesAnalysisRunner(),
AdaptationAnalysisRunner(),
]
if not _supported_runners:
_err_mssg = "No analysis runner found for the given configuration."
logging.error(_err_mssg)
raise ValueError(_err_mssg)

# Initialized selected supported runner (First one available).
if len(_supported_runners) > 1:
logging.warning(
"More than one runner available, computation time could be longer than expected."
_supported_runners = []
for _analysis in analysis_collection.analyses:
_supported_runner = next(
(
_runner
for _runner in _available_runners
if _runner.can_run(_analysis, analysis_collection)
),
None,
)
return _supported_runners
if not _supported_runner:
_err_mssg = "No analysis runner found for the given configuration."
logging.error(_err_mssg)
raise ValueError(_err_mssg)
_supported_runners.append(_supported_runner)

return list(set(_supported_runners))

@staticmethod
def run(
ra2ce_input: ConfigWrapper,
analysis_collection: AnalysisCollection,
) -> list[AnalysisResultWrapper]:
"""
Runs the given analysis collection.
Args:
ra2ce_input (Ra2ceInput): Input representing a set of network and analysis ini configurations.
analysis_collection (AnalysisCollection): Collection of analyses to run.
Returns:
list[AnalysisResultWrapper]: List of analysis results.
"""
_supported_runners = AnalysisRunnerFactory.get_supported_runners(ra2ce_input)
_supported_runners = AnalysisRunnerFactory.get_supported_runners(
analysis_collection
)
_results = []
for _runner_type in _supported_runners:
_run_results = _runner_type().run(analysis_collection)
for _runner in _supported_runners:
_run_results = _runner.run(analysis_collection)
_results.extend(_run_results)
return _results
10 changes: 6 additions & 4 deletions ra2ce/runners/analysis_runner_protocol.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,21 +22,23 @@
from typing import Protocol, runtime_checkable

from ra2ce.analysis.analysis_collection import AnalysisCollection
from ra2ce.analysis.analysis_protocol import AnalysisProtocol
from ra2ce.analysis.analysis_result.analysis_result_wrapper_protocol import (
AnalysisResultWrapperProtocol,
)
from ra2ce.configuration.config_wrapper import ConfigWrapper


@runtime_checkable
class AnalysisRunner(Protocol):
@staticmethod
def can_run(ra2ce_input: ConfigWrapper) -> bool:
def can_run(
self, analysis: AnalysisProtocol, analysis_collection: AnalysisCollection
) -> bool:
"""
Validates whether the given `ConfigWrapper` is eligibile for this `AnalysisRunner`.
Args:
ra2ce_input (ConfigWrapper): Configuration desired to run.
analysis (AnalysisProtocol): Analysis to be run on this `AnalysisRunner`.
analysis_collection (AnalysisCollection): Collection of analyses to be run on this `AnalysisRunner`.
Returns:
bool: Whether the `ConfigWrapper` can be run or not.
Expand Down
20 changes: 9 additions & 11 deletions ra2ce/runners/damages_analysis_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,11 @@

import logging

from geopandas import GeoDataFrame

from ra2ce.analysis.analysis_collection import AnalysisCollection
from ra2ce.analysis.analysis_protocol import AnalysisProtocol
from ra2ce.analysis.damages.analysis_damages_protocol import AnalysisDamagesProtocol
from ra2ce.configuration.config_wrapper import ConfigWrapper
from ra2ce.runners.simple_analysis_runner_base import SimpleAnalysisRunnerBase


Expand All @@ -37,17 +39,13 @@ def filter_supported_analyses(
) -> list[AnalysisDamagesProtocol]:
return analysis_collection.damages_analyses

@staticmethod
def can_run(ra2ce_input: ConfigWrapper) -> bool:
if (
not ra2ce_input.analysis_config
or not ra2ce_input.analysis_config.config_data.damages_list
):
return False
if not ra2ce_input.network_config:
def can_run(
self, analysis: AnalysisProtocol, analysis_collection: AnalysisCollection
) -> bool:
if not super().can_run(analysis, analysis_collection):
return False
_network_config = ra2ce_input.network_config.config_data
if not _network_config.hazard or not _network_config.hazard.hazard_map:
_graph = analysis_collection.damages_analyses[0].graph_file_hazard.get_graph()
if not isinstance(_graph, GeoDataFrame):
logging.error(
"Please define a hazard map in your network.ini file. Unable to calculate damages."
)
Expand Down
10 changes: 0 additions & 10 deletions ra2ce/runners/losses_analysis_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
"""
from ra2ce.analysis.analysis_collection import AnalysisCollection
from ra2ce.analysis.losses.analysis_losses_protocol import AnalysisLossesProtocol
from ra2ce.configuration.config_wrapper import ConfigWrapper
from ra2ce.runners.simple_analysis_runner_base import SimpleAnalysisRunnerBase


Expand All @@ -33,12 +32,3 @@ def filter_supported_analyses(
analysis_collection: AnalysisCollection,
) -> list[AnalysisLossesProtocol]:
return analysis_collection.losses_analyses

@staticmethod
def can_run(ra2ce_input: ConfigWrapper) -> bool:
if (
not ra2ce_input.analysis_config
or not ra2ce_input.analysis_config.config_data.losses_list
):
return False
return True
11 changes: 6 additions & 5 deletions ra2ce/runners/simple_analysis_runner_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@
from ra2ce.analysis.analysis_result.analysis_result_wrapper_exporter import (
AnalysisResultWrapperExporter,
)
from ra2ce.configuration.config_wrapper import ConfigWrapper
from ra2ce.runners.analysis_runner_protocol import AnalysisRunner


Expand All @@ -54,10 +53,12 @@ def filter_supported_analyses(
"""
raise NotImplementedError()

@staticmethod
@abstractmethod
def can_run(ra2ce_input: ConfigWrapper) -> bool:
raise NotImplementedError()
def can_run(
self, analysis: AnalysisProtocol, analysis_collection: AnalysisCollection
) -> bool:
return analysis is not None and analysis in self.filter_supported_analyses(
analysis_collection
)

def run(
self, analysis_collection: AnalysisCollection
Expand Down
Loading

0 comments on commit af2dbd8

Please sign in to comment.