Skip to content

Commit

Permalink
feat: It is now possible to run an analysis by simply providing datac…
Browse files Browse the repository at this point in the history
…lasses or ini files. (#461)

* feat: It is now possible to run an analysis by simply providing either the ini files or their config data representations; Added tests

* test: Quick clean up of test code

* test: Improved test fixtures for the handler

* test: Removed double yield as the tear-down does not know where to continue from

* test: Streamlined some tests

* test: Added conftests to `tests/network/graph_files` to reduce code duplication on fixture

* test: Added extended validation of run results

* chore: Small corrections after merge from `master`

* test: Corrected wrong import after merge

* docs: Removed output from example

* docs: Updated tech meeting with new way of running analyses

* docs: Fixed tech meeting example

* test: Processed review remarks

* chore: It is now possible to call the new static methods with `None` as arguments
  • Loading branch information
Carsopre authored May 31, 2024
1 parent d133861 commit d52feb9
Show file tree
Hide file tree
Showing 17 changed files with 358 additions and 164 deletions.
2 changes: 1 addition & 1 deletion examples/example_single_link_losses.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
"from ra2ce.ra2ce_handler import Ra2ceHandler\n",
"\n",
"# Import config data\n",
"root_dir_multi = Path(\".\\\\data\\\\single_link_losses\")\n",
"root_dir_multi = Path(\"data\", \"single_link_losses\")\n",
"assert root_dir_multi.exists()\n",
"\n",
"# Load network data.\n",
Expand Down
96 changes: 93 additions & 3 deletions examples/tech_meetings/20240403_run_without_ini_files_DIY.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@
"from ra2ce.ra2ce_handler import Ra2ceHandler\n",
"\n",
"# Define the location of our example test data.\n",
"_root_dir = Path(\"data\").joinpath(\"damages_analysis\")\n",
"_root_dir = Path(\"...\", \"data\", \"damages_analysis\")\n",
"assert _root_dir.exists()\n",
"\n",
"_network_file = _root_dir.joinpath(\"network.ini\")\n",
Expand Down Expand Up @@ -201,7 +201,7 @@
"from ra2ce.ra2ce_logging import Ra2ceLogger\n",
"\n",
"# Initialize logger.\n",
"_output_logger_path = Path(\"data\").joinpath(\"logging\")\n",
"_output_logger_path = Path(\"..\", \"data\", \"logging\")\n",
"if _output_logger_path.exists():\n",
" import shutil\n",
" shutil.rmtree(_output_logger_path)\n",
Expand Down Expand Up @@ -299,7 +299,7 @@
"from ra2ce.runners import AnalysisRunnerFactory\n",
"\n",
"# Initialize configuration (replace this with your own configuration)\n",
"_data_dir = Path(\"../data/damages_analysis\")\n",
"_data_dir = Path(\"..\", \"data\", \"damages_analysis\")\n",
"_network_ini = _data_dir.joinpath(\"network.ini\")\n",
"_network = NetworkConfigDataReader().read(_network_ini)\n",
"_network.root_path = _data_dir.parent\n",
Expand All @@ -319,6 +319,96 @@
"_runner = AnalysisRunnerFactory.get_runner(_handler.input_config)\n",
"_runner.run(_handler.input_config.analysis_config)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### 1.4. Run an analysis with one line of code!\n",
"\n",
"From issue [#460](https://github.com/Deltares/ra2ce/issues/460) it is possible to simply run an anlysis without having to do the two extra steps `.configure()` and `.run_analysis()`.\n",
"\n",
"We can now chose to:\n",
"- 1. Run directly an analysis providing two files with (`Ra2ceHandler.run_with_ini_files(Path, Path)`).\n",
"- 2. Run an analysis by providing two configuration files (`Ra2ceHandler.run_with_config_data(NetworkConfigData, AnalysisConfigData)`)."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### 1.4.1. Run an analysis providing two files."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from pathlib import Path\n",
"\n",
"from ra2ce.ra2ce_handler import Ra2ceHandler, AnalysisResultWrapper\n",
"from ra2ce.analysis.analysis_config_data.analysis_config_data_reader import AnalysisConfigDataReader\n",
"from ra2ce.network.network_config_data.network_config_data_reader import NetworkConfigDataReader\n",
"\n",
"# Initialize configuration (replace this with your own configuration)\n",
"_data_dir = Path(\"..\", \"data\", \"single_link_redun\")\n",
"\n",
"# Network file\n",
"_network_ini = _data_dir.joinpath(\"network.ini\")\n",
"assert _network_ini.exists()\n",
"\n",
"# Analysis file\n",
"_analysis_ini = _data_dir.joinpath(\"analyses.ini\")\n",
"assert _analysis_ini.exists()\n",
"\n",
"# Get directly the results without extra steps.\n",
"_results = Ra2ceHandler.run_with_ini_files(_network_ini, _analysis_ini)\n",
"\n",
"assert any(_results)\n",
"assert all(isinstance(_result_wrapper, AnalysisResultWrapper) for _result_wrapper in _results)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### 1.4.2. Run an anlysis providing `NetworkConfigData` and `AnalysisConfigData`"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from pathlib import Path\n",
"\n",
"from ra2ce.ra2ce_handler import Ra2ceHandler, AnalysisResultWrapper\n",
"from ra2ce.analysis.analysis_config_data.analysis_config_data_reader import AnalysisConfigDataReader, AnalysisConfigData\n",
"from ra2ce.network.network_config_data.network_config_data_reader import NetworkConfigDataReader, NetworkConfigData\n",
"\n",
"# Initialize configuration (replace this with your own configuration)\n",
"_data_dir = Path(\"..\", \"data\", \"single_link_redun\")\n",
"\n",
"# NOTE! For simplicity we just load \"valid\" ini files instead of generating our own\n",
"# config data.\n",
"\n",
"# Network file\n",
"_network_config_data = NetworkConfigDataReader().read(_data_dir.joinpath(\"network.ini\"))\n",
"assert isinstance(_network_config_data, NetworkConfigData)\n",
"\n",
"# Analysis file\n",
"_analysis_config_data = AnalysisConfigDataReader().read(_data_dir.joinpath(\"analyses.ini\"))\n",
"assert isinstance(_analysis_config_data, AnalysisConfigData)\n",
"\n",
"# Get directly the results without extra steps.\n",
"_results = Ra2ceHandler.run_with_config_data(_network_config_data, _analysis_config_data)\n",
"\n",
"assert any(_results)\n",
"assert all(isinstance(_result_wrapper, AnalysisResultWrapper) for _result_wrapper in _results)"
]
}
],
"metadata": {
Expand Down
51 changes: 51 additions & 0 deletions ra2ce/ra2ce_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,10 @@ def _initialize_logger_from_config(self) -> None:
Ra2ceLogger.initialize_console_logger(logger_name="RA2CE")

def configure(self) -> None:
"""
Configures the `ConfigWrapper` with the current `AnalysisConfigData` and
`NetworkConfigData` so that the analyses can be succesfully run.
"""
self.input_config.configure()

def run_analysis(self) -> list[AnalysisResultWrapper]:
Expand All @@ -173,3 +177,50 @@ def run_analysis(self) -> list[AnalysisResultWrapper]:

_runner = AnalysisRunnerFactory.get_runner(self.input_config)
return _runner.run(self.input_config.analysis_config)

@staticmethod
def run_with_ini_files(
network_ini_file: Path | None, analysis_ini_file: Path | None
) -> list[AnalysisResultWrapper]:
"""
Streamlined method to directly run a `Ra2ce` analysis based
on the provided `network` and `analysis` `.ini` files.
This streamlined method allows for automatic initialization of the
logger.
Args:
network_ini_file (Path | None): Location of the network file (`*.ini`).
analysis_ini_file (Path | None): Location of the analysis file (`*.ini`).
Returns:
list[AnalysisResultWrapper]: A list of analyses results.
"""
_handler = Ra2ceHandler(network_ini_file, analysis_ini_file)
_handler.configure()
return _handler.run_analysis()

@staticmethod
def run_with_config_data(
network: NetworkConfigData | None, analysis: AnalysisConfigData | None
) -> list[AnalysisResultWrapper]:
"""
Streamlined method to directly run a `Ra2ce` analysis based
on the dataclasses for `Network` and `Analysis` instead of
using `.ini` files.
This streamlined method allows for automatic initialization of the
logger.
Args:
network (NetworkConfigData | None):
Dataclass containing all the information for the network.
analysis (AnalysisConfigData | None):
Dataclass containing all the information related to analyses.
Returns:
list[AnalysisResultWrapper]: A list of analyses results.
"""
_handler = Ra2ceHandler.from_config(network, analysis)
_handler.configure()
return _handler.run_analysis()
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
import shutil

import pytest

from ra2ce.analysis.analysis_config_data.analysis_config_data import (
AnalysisConfigData,
AnalysisSectionDamages,
Expand All @@ -13,7 +17,7 @@
from ra2ce.analysis.analysis_config_data.enums.event_type_enum import EventTypeEnum
from ra2ce.common.validation.ra2ce_validator_protocol import Ra2ceIoValidator
from ra2ce.common.validation.validation_report import ValidationReport
from tests import test_data, test_results
from tests import test_results


class TestAnalysisConfigDataValidator:
Expand All @@ -32,10 +36,12 @@ def _validate_config(self, config_data: AnalysisConfigData) -> ValidationReport:
_validator = AnalysisConfigDataValidator(config_data)
return _validator.validate()

def test_validate_with_required_headers(self):
def test_validate_with_required_headers(self, request: pytest.FixtureRequest):
# 1. Define test data.
_output_test_dir = test_data.joinpath("acceptance_test_data")
assert _output_test_dir.is_dir()
_output_dir = test_results.joinpath(request.node.name)
if _output_dir.exists():
shutil.rmtree(_output_dir)
_output_dir.mkdir(parents=True)

# 2. Run test.
_test_config_data = AnalysisConfigData(
Expand All @@ -45,7 +51,7 @@ def test_validate_with_required_headers(self):
event_type=EventTypeEnum.EVENT,
damage_curve=DamageCurveEnum.HZ,
),
output_path=_output_test_dir,
output_path=_output_dir,
)
_report = self._validate_config(_test_config_data)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from ra2ce.analysis.analysis_config_data.analysis_config_data_reader import (
AnalysisConfigDataReader,
)
from tests import test_data
from tests import acceptance_test_data


class TestAnalysisConfigReader:
Expand Down Expand Up @@ -36,10 +36,8 @@ def test_read_without_ini_file_raises_error(self, ini_file: Any):

def test_read_succeeds(self):
# 1. Define test data
_ini_file = test_data.joinpath("acceptance_test_data", "analyses.ini")
_ini_file_output = test_data.joinpath(
"acceptance_test_data", "output", "analyses.ini"
)
_ini_file = acceptance_test_data.joinpath("analyses.ini")
_ini_file_output = acceptance_test_data.joinpath("output", "analyses.ini")
if _ini_file_output.exists():
_ini_file_output.unlink()

Expand Down
7 changes: 0 additions & 7 deletions tests/analysis/test_analysis_collection.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
from ra2ce.analysis.analysis_config_wrapper import AnalysisConfigWrapper
from ra2ce.analysis.damages.analysis_damages_protocol import AnalysisDamagesProtocol
from ra2ce.analysis.losses.analysis_losses_protocol import AnalysisLossesProtocol
from tests import test_data

_unsupported_damages_analyses = [
AnalysisDamagesEnum.EFFECTIVENESS_MEASURES,
Expand All @@ -35,12 +34,6 @@ class MockAnalysisSectionDamages(AnalysisSectionDamages):
class MockAnalysisSectionLosses(AnalysisSectionLosses):
analysis: AnalysisLossesEnum = None

@pytest.fixture(autouse=False)
def valid_analysis_ini(self) -> Path:
_ini_file = test_data / "acceptance_test_data" / "analyses.ini"
assert _ini_file.exists()
return _ini_file

def test_initialize(self):
# 1./2. Define test data / Run test.
_collection = AnalysisCollection(None, None)
Expand Down
8 changes: 1 addition & 7 deletions tests/analysis/test_analysis_config_wrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
from ra2ce.analysis.analysis_config_data.enums.event_type_enum import EventTypeEnum
from ra2ce.analysis.analysis_config_wrapper import AnalysisConfigWrapper
from ra2ce.network.network_config_wrapper import NetworkConfigWrapper
from tests import test_data, test_results
from tests import test_results


class TestAnalysisConfigWrapper:
Expand All @@ -31,12 +31,6 @@ def test_initialize(self):
assert isinstance(_config, AnalysisConfigWrapper)
assert isinstance(_config.config_data, AnalysisConfigData)

@pytest.fixture(autouse=False)
def valid_analysis_ini(self) -> Path:
_ini_file = test_data / "acceptance_test_data" / "analyses.ini"
assert _ini_file.exists()
return _ini_file

def test_from_data_network_not_provided(self, valid_analysis_ini: Path):
# 1. Define test data.
_config_data = AnalysisConfigData()
Expand Down
39 changes: 24 additions & 15 deletions tests/configuration/test_config_factory.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
from pathlib import Path
from typing import Iterator

import pytest

from ra2ce.analysis.analysis_config_wrapper import AnalysisConfigWrapper
from ra2ce.configuration.config_factory import ConfigFactory
from ra2ce.configuration.config_wrapper import ConfigWrapper
Expand All @@ -7,15 +12,23 @@


class TestConfigFactory:
def test_get_config_wrapper_with_valid_input(self):
# 1. Define test data.
_test_dir = test_data / "simple_inputs"
@pytest.fixture(name="simple_inputs")
def _get_simple_inputs_acceptance_case(self) -> Iterator[tuple[Path, Path]]:
_test_dir = test_data.joinpath("simple_inputs")
assert _test_dir.is_dir()
_analysis_ini = _test_dir / "analysis.ini"
_network_ini = _test_dir / "network.ini"

assert _analysis_ini.is_file() and _network_ini.is_file()

yield (_network_ini, _analysis_ini)

def test_get_config_wrapper_with_valid_input(
self, simple_inputs: tuple[Path, Path]
):
# 1. Define test data.
_network_ini, _analysis_ini = simple_inputs

# 2. Run test.
_input_config = ConfigFactory.get_config_wrapper(_network_ini, _analysis_ini)

Expand All @@ -26,13 +39,11 @@ def test_get_config_wrapper_with_valid_input(self):
assert isinstance(_input_config.network_config, NetworkConfigWrapper)
assert isinstance(_input_config.network_config.config_data, NetworkConfigData)

def test_from_input_paths_given_only_analysis(self):
def test_from_input_paths_given_only_analysis(
self, simple_inputs: tuple[Path, Path]
):
# 1. Define test data.
_test_dir = test_data / "simple_inputs"
assert _test_dir.is_dir()
_analysis_ini = _test_dir / "analysis.ini"

assert _analysis_ini.is_file()
_, _analysis_ini = simple_inputs

# 2. Run test.
_input_config = ConfigFactory.get_config_wrapper(None, _analysis_ini)
Expand All @@ -43,13 +54,11 @@ def test_from_input_paths_given_only_analysis(self):
assert isinstance(_input_config.analysis_config, AnalysisConfigWrapper)
assert not _input_config.network_config

def test_from_input_paths_given_only_network(self):
def test_from_input_paths_given_only_network(
self, simple_inputs: tuple[Path, Path]
):
# 1. Define test data.
_test_dir = test_data / "simple_inputs"
assert _test_dir.is_dir()
_network_ini = _test_dir / "network.ini"

assert _network_ini.is_file()
_network_ini, _ = simple_inputs

# 2. Run test.
_input_config = ConfigFactory.get_config_wrapper(_network_ini, None)
Expand Down
13 changes: 13 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from pathlib import Path
from typing import Iterator

import pytest

from tests import acceptance_test_data


@pytest.fixture(name="valid_analysis_ini")
def _get_valid_analysis_ini_fixture() -> Iterator[Path]:
_ini_file = acceptance_test_data.joinpath("analyses.ini")
assert _ini_file.exists()
yield _ini_file
11 changes: 11 additions & 0 deletions tests/network/graph_files/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from pathlib import Path
from typing import Iterator

import pytest

from tests import test_data


@pytest.fixture(name="graph_folder")
def _get_graph_test_folder_fixture() -> Iterator[Path]:
yield test_data.joinpath("simple_inputs", "static", "output_graph")
Loading

0 comments on commit d52feb9

Please sign in to comment.