Skip to content

Commit

Permalink
feat: 389 allow an analysis run without files 2nd attempt
Browse files Browse the repository at this point in the history
Chosen implementation:
* create a classmethod in Ra2ceHandler to initialize the handler from configs
* initialize the logger from classmethods, depending if it includes file logging or only console logging
  * renamed the logger module to match the classname

Feature commits:
* chore: first setup of classmethod
* chore: add new way to notebook
* test: add valid assert
* chore: rework handler
* chore: rework tech notebook
* chore: change data dir and add some documentation
* chore: process review comments
* chore: improve logging setup from config
* chore: rename logger to match classname
* chore: docstring added
* test: Extended test to create a copy of the reference data and avoid locking issues

---------

Co-authored-by: Carles S. Soriano Perez <[email protected]>
  • Loading branch information
ArdtK and Carsopre authored Apr 11, 2024
1 parent 9c6c522 commit 5c0351a
Show file tree
Hide file tree
Showing 9 changed files with 1,008 additions and 685 deletions.
638 changes: 343 additions & 295 deletions examples/tech_meetings/20240403_run_without_ini_files_DIY.ipynb

Large diffs are not rendered by default.

696 changes: 348 additions & 348 deletions examples/tech_meetings/20240403_using_enums_DIY.ipynb

Large diffs are not rendered by default.

23 changes: 14 additions & 9 deletions infra/workflow/run_race.py
Original file line number Diff line number Diff line change
@@ -1,24 +1,29 @@
from pathlib import Path
from ra2ce.ra2ce_handler import Ra2ceHandler # import the ra2cehandler to run ra2ce analyses

_network_ini_name = "network.ini" # set the name for the network.ini settings file
from ra2ce.ra2ce_handler import (
Ra2ceHandler, # import the ra2cehandler to run ra2ce analyses
)

folder_dir = Path(r'/data')
_network_ini_name = "network.ini" # set the name for the network.ini settings file

root_dir = folder_dir # specify the path to the project folder in the examples folder
network_ini = root_dir / _network_ini_name # we set the _network_ini_name before, so we can use this now for the project
assert network_ini.is_file() # check whether the network.ini file exists
folder_dir = Path(r"/data")

root_dir = folder_dir # specify the path to the project folder in the examples folder
network_ini = (
root_dir / _network_ini_name
) # we set the _network_ini_name before, so we can use this now for the project
assert network_ini.is_file() # check whether the network.ini file exists

handler = Ra2ceHandler(network=network_ini, analysis=None)
handler.configure()

# Set the path to your output_graph folder to find the network/graph creation:
path_output_graph = root_dir / "static" / "output_graph"

# Now we find and inspect the file 'base_graph_edges.gpkg' which holds the 'edges' of the network.
# Now we find and inspect the file 'base_graph_edges.gpkg' which holds the 'edges' of the network.
# An edge (or link) of a network (or graph) represents a connection between two nodes (or vertices) of the network. More information on: https://mathinsight.org/definition/network_edge#:~:text=An%20edge%20(or%20link)%20of,in%20the%20first%20figure%20below.
base_graph_edges = path_output_graph / "base_graph_edges.gpkg"
edges_gdf = gpd.read_file(base_graph_edges, driver = "GPKG")
edges_gdf = gpd.read_file(base_graph_edges, driver="GPKG")
edges_gdf.head()

edges_gdf.explore(column='highway', tiles="CartoDB positron")
edges_gdf.explore(column="highway", tiles="CartoDB positron")
3 changes: 1 addition & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ pytest-notebook = "^0.9.0"

[tool.black]
line-length = 88
target-version = ['py38', 'py39']
target-version = ['py310', 'py311']
exclude = '''
(
/(
Expand All @@ -120,7 +120,6 @@ exclude = '''
[tool.isort]
profile = "black"
multi_line_output = 3
line_length = 88

[tool.commitizen]
name = "cz_conventional_commits"
Expand Down
10 changes: 5 additions & 5 deletions ra2ce/analysis/indirect/multi_link_origin_closest_destination.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
OriginsDestinationsSection,
)
from ra2ce.network.networks_utils import graph_to_gpkg
from ra2ce.ra2ce_logging import logging
from ra2ce.ra2ce_logger import logging


class MultiLinkOriginClosestDestination(AnalysisIndirectProtocol):
Expand Down Expand Up @@ -110,10 +110,10 @@ def _save_gpkg_analysis(
opt_routes_with_hazard,
) = analyzer.multi_link_origin_closest_destination()

(
opt_routes_with_hazard
) = analyzer.difference_length_with_without_hazard(
opt_routes_with_hazard, opt_routes_without_hazard
(opt_routes_with_hazard) = (
analyzer.difference_length_with_without_hazard(
opt_routes_with_hazard, opt_routes_without_hazard
)
)
else:
(
Expand Down
106 changes: 99 additions & 7 deletions ra2ce/ra2ce_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@
"""

# -*- coding: utf-8 -*-

from __future__ import annotations

import logging
import warnings
from pathlib import Path
Expand All @@ -28,11 +31,13 @@
from shapely.errors import ShapelyDeprecationWarning

from ra2ce.analysis.analysis_config_data.analysis_config_data import AnalysisConfigData
from ra2ce.analysis.analysis_config_wrapper import AnalysisConfigWrapper
from ra2ce.analysis.analysis_result_wrapper import AnalysisResultWrapper
from ra2ce.configuration.config_factory import ConfigFactory
from ra2ce.configuration.config_wrapper import ConfigWrapper
from ra2ce.network.network_config_data.network_config_data import NetworkConfigData
from ra2ce.ra2ce_logging import Ra2ceLogger
from ra2ce.network.network_config_wrapper import NetworkConfigWrapper
from ra2ce.ra2ce_logger import Ra2ceLogger
from ra2ce.runners import AnalysisRunnerFactory

warnings.filterwarnings(
Expand All @@ -45,13 +50,86 @@


class Ra2ceHandler:
"""
Top level class to handle the RA2CE analysis process.
This class is used to orchestrate the analysis process based on the provided network and analysis configuration,
including the logging configuration
"""

input_config: ConfigWrapper

def __init__(self, network: Optional[Path], analysis: Optional[Path]) -> None:
self._initialize_logger(network, analysis)
self.input_config = ConfigFactory.get_config_wrapper(network, analysis)
if network or analysis:
self._initialize_logger_from_files(network, analysis)
self.input_config = ConfigFactory.get_config_wrapper(network, analysis)
else:
self._initialize_logger_from_config()

@classmethod
def from_config(
cls, network: NetworkConfigData, analysis: AnalysisConfigData
) -> Ra2ceHandler:
"""
Create a handler from the provided network and analysis configuration.
Args:
network (NetworkConfigData): Network configuration
analysis (AnalysisConfigData): Analysis configuration
Returns:
Ra2ceHandler: The handler object
"""

def set_config_paths(_analysis_config: AnalysisConfigWrapper) -> None:
if not network:
return
_analysis_config.config_data.root_path = network.root_path
_analysis_config.config_data.input_path = network.input_path
_analysis_config.config_data.static_path = network.static_path
_analysis_config.config_data.output_path = network.output_path

def _initialize_logger(
def get_network_config() -> NetworkConfigWrapper | None:
if not isinstance(network, NetworkConfigData):
return None
_network_config = NetworkConfigWrapper()
_network_config.config_data = network
if network.output_graph_dir:
if network.output_graph_dir.is_dir():
_network_config.graph_files = (
_network_config.read_graphs_from_config(
network.output_graph_dir
)
)
else:
network.output_graph_dir.mkdir(parents=True)
return _network_config

def get_analysis_config() -> AnalysisConfigWrapper | None:
if not isinstance(analysis, AnalysisConfigData):
return None
_analysis_config = AnalysisConfigWrapper()
_analysis_config.config_data = analysis
set_config_paths(_analysis_config)
if isinstance(_handler.input_config.network_config, NetworkConfigWrapper):
_analysis_config.config_data.network = (
_handler.input_config.network_config.config_data.network
)
_analysis_config.config_data.origins_destinations = (
_handler.input_config.network_config.config_data.origins_destinations
)
_analysis_config.graph_files = (
_handler.input_config.network_config.graph_files
)
return _analysis_config

_handler = cls(None, None)
_handler.input_config = ConfigWrapper()
_handler.input_config.network_config = get_network_config()
_handler.input_config.analysis_config = get_analysis_config()

return _handler

def _initialize_logger_from_files(
self, network: Optional[Path], analysis: Optional[Path]
) -> None:
_output_config = None
Expand All @@ -60,17 +138,31 @@ def _initialize_logger(
elif analysis:
_output_config = AnalysisConfigData.get_data_output(analysis)
else:
raise ValueError(
"No valid location provided to start logging. Either network or analysis are required."
logging.warning(
"No valid location provided to start logging: no logger and logfile is created."
)
Ra2ceLogger(logging_dir=_output_config, logger_name="RA2CE")
return
Ra2ceLogger.initialize_file_logger(
logging_dir=_output_config, logger_name="RA2CE"
)

def _initialize_logger_from_config(self) -> None:
Ra2ceLogger.initialize_console_logger(logger_name="RA2CE")

def configure(self) -> None:
self.input_config.configure()

def run_analysis(self) -> list[AnalysisResultWrapper]:
"""
Runs a Ra2ce analysis based on the provided network and analysis files.
Args: None
Raises:
ValueError: If the input files are not valid
Returns:
list[AnalysisResultWrapper]: A list of analysis results
"""
if not self.input_config.analysis_config:
return
Expand Down
52 changes: 44 additions & 8 deletions ra2ce/ra2ce_logging.py → ra2ce/ra2ce_logger.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,27 +19,60 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
"""

from __future__ import annotations

import logging
from pathlib import Path
from typing import Optional


class Ra2ceLogger:
log_file: Optional[Path] = None
"""
Class to handle the logging configuration for the RA2CE application.
"""

def __init__(self, logging_dir: Path, logger_name: str) -> None:
if not logging_dir.is_dir():
logging_dir.mkdir(parents=True)
self.log_file = logging_dir.joinpath(f"{logger_name}.log")
if not self.log_file.is_file():
self.log_file.touch()
log_file: Optional[Path] = None
_file_handler: Optional[logging.FileHandler] = None

def __init__(self, log_file: Optional[Path], logger_name: str) -> None:
self.log_file = log_file
self._set_file_handler()
self._set_console_handler()
self._set_formatter()
logging.info(f"{logger_name} logger initialized.")

@classmethod
def initialize_file_logger(cls, logging_dir: Path, logger_name: str) -> Ra2ceLogger:
"""
Initializes a logger that writes to a file and to console.
Args:
logging_dir (Path): Path to the logging directory
logger_name (str): Name of the logger
Returns:
Ra2ceLogger: The logger object
"""
if not logging_dir.is_dir():
logging_dir.mkdir(parents=True)
_log_file = logging_dir.joinpath(f"{logger_name}.log")
if not _log_file.is_file():
_log_file.touch()
return cls(_log_file, logger_name)

@classmethod
def initialize_console_logger(cls, logger_name: str) -> Ra2ceLogger:
"""
Initializes a logger that writes to console only.
Args:
logger_name (str): Name of the logger
Returns:
Ra2ceLogger: The logger object
"""
return cls(None, logger_name)

def _get_logger(self) -> logging.Logger:
"""
Gets the ra2ce logger which by default is the root logging.Logger.
Expand All @@ -51,6 +84,8 @@ def _get_logger(self) -> logging.Logger:

def _set_file_handler(self) -> None:
# Create a root logger and set the minimum logging level.
if not self.log_file:
return
self._get_logger().setLevel(logging.INFO)
self._file_handler = logging.FileHandler(filename=self.log_file, mode="a")
self._file_handler.setLevel(logging.INFO)
Expand All @@ -68,5 +103,6 @@ def _set_formatter(self) -> None:
fmt="%(asctime)s - [%(filename)s:%(lineno)d] - %(name)s - %(levelname)s - %(message)s",
datefmt="%Y-%m-%d %I:%M:%S %p",
)
self._file_handler.setFormatter(_formatter)
if self.log_file:
self._file_handler.setFormatter(_formatter)
self._console_handler.setFormatter(_formatter)
Loading

0 comments on commit 5c0351a

Please sign in to comment.