From ce1a3a887d44bcbb0a32215541337696bee5c990 Mon Sep 17 00:00:00 2001 From: Shubhangi Gupta Date: Thu, 16 May 2024 15:52:29 +0200 Subject: [PATCH 01/54] algorithm modification for transshipment --- conflowgen/__init__.py | 2 +- .../container_dwell_time_analysis_report.py | 2 +- ...by_vehicle_type_analysis_summary_report.py | 2 +- ..._adjustment_per_vehicle_analysis_report.py | 2 +- ...le_capacity_utilization_analysis_report.py | 2 +- .../analyses/modal_split_analysis_report.py | 2 +- .../analyses/yard_capacity_analysis_report.py | 2 +- .../api/container_flow_generation_manager.py | 14 ++- .../container_flow_generation_properties.py | 10 +++ .../large_scheduled_vehicle_repository.py | 34 +++++++- .../container_flow_generation_service.py | 6 ++ ...hicle_for_onward_transportation_manager.py | 6 +- conflowgen/log/__init__.py | 0 conflowgen/log/log.py | 85 +++++++++++++++++++ conflowgen/reporting/__init__.py | 2 +- conflowgen/reporting/output_style.py | 2 +- .../tests/analyses/test_run_all_analyses.py | 16 ++-- .../test_container_flow_generation_manager.py | 6 +- ...d_and_outbound_vehicle_capacity_preview.py | 2 +- 19 files changed, 174 insertions(+), 23 deletions(-) create mode 100644 conflowgen/log/__init__.py create mode 100644 conflowgen/log/log.py diff --git a/conflowgen/__init__.py b/conflowgen/__init__.py index 7eaeb02f..426fa678 100644 --- a/conflowgen/__init__.py +++ b/conflowgen/__init__.py @@ -83,7 +83,7 @@ from conflowgen.domain_models.data_types.storage_requirement import StorageRequirement # List of functions -from conflowgen.logging.logging import setup_logger +from conflowgen.log.log import setup_logger from conflowgen.analyses import run_all_analyses from conflowgen.previews import run_all_previews diff --git a/conflowgen/analyses/container_dwell_time_analysis_report.py b/conflowgen/analyses/container_dwell_time_analysis_report.py index ce494cd8..63d3a5d0 100644 --- a/conflowgen/analyses/container_dwell_time_analysis_report.py +++ b/conflowgen/analyses/container_dwell_time_analysis_report.py @@ -33,7 +33,7 @@ def __init__(self): def get_report_as_text(self, **kwargs) -> str: """ - The report as a text is represented as a table suitable for logging. + The report as a text is represented as a table suitable for log. It uses a human-readable formatting style. For the exact interpretation of the parameter, check :meth:`.ContainerDwellTimeAnalysis.get_container_dwell_times`. diff --git a/conflowgen/analyses/container_flow_adjustment_by_vehicle_type_analysis_summary_report.py b/conflowgen/analyses/container_flow_adjustment_by_vehicle_type_analysis_summary_report.py index c1fb92c1..bb52d960 100644 --- a/conflowgen/analyses/container_flow_adjustment_by_vehicle_type_analysis_summary_report.py +++ b/conflowgen/analyses/container_flow_adjustment_by_vehicle_type_analysis_summary_report.py @@ -35,7 +35,7 @@ def get_report_as_text( self, **kwargs ) -> str: """ - The report as a text is represented as a table suitable for logging. + The report as a text is represented as a table suitable for log. It uses a human-readable formatting style. Keyword Args: diff --git a/conflowgen/analyses/container_flow_vehicle_type_adjustment_per_vehicle_analysis_report.py b/conflowgen/analyses/container_flow_vehicle_type_adjustment_per_vehicle_analysis_report.py index 3680f26d..a02d4e66 100644 --- a/conflowgen/analyses/container_flow_vehicle_type_adjustment_per_vehicle_analysis_report.py +++ b/conflowgen/analyses/container_flow_vehicle_type_adjustment_per_vehicle_analysis_report.py @@ -49,7 +49,7 @@ def __init__(self): def get_report_as_text(self, **kwargs) -> str: """ - The report as a text is represented as a table suitable for logging. It uses a human-readable formatting style. + The report as a text is represented as a table suitable for log. It uses a human-readable formatting style. Keyword Args: initial_vehicle_type (:obj:`typing.Any`): Either ``"all"``, a single vehicle of type diff --git a/conflowgen/analyses/inbound_to_outbound_vehicle_capacity_utilization_analysis_report.py b/conflowgen/analyses/inbound_to_outbound_vehicle_capacity_utilization_analysis_report.py index 4e82ef4e..d9742c8a 100644 --- a/conflowgen/analyses/inbound_to_outbound_vehicle_capacity_utilization_analysis_report.py +++ b/conflowgen/analyses/inbound_to_outbound_vehicle_capacity_utilization_analysis_report.py @@ -52,7 +52,7 @@ def __init__(self): def get_report_as_text(self, **kwargs) -> str: """ - The report as a text is represented as a table suitable for logging. It uses a human-readable formatting style. + The report as a text is represented as a table suitable for log. It uses a human-readable formatting style. Keyword Args: vehicle_type (:py:obj:`Any`): Either ``"scheduled vehicles"``, a single vehicle of type diff --git a/conflowgen/analyses/modal_split_analysis_report.py b/conflowgen/analyses/modal_split_analysis_report.py index 48cb36af..441e3bbd 100644 --- a/conflowgen/analyses/modal_split_analysis_report.py +++ b/conflowgen/analyses/modal_split_analysis_report.py @@ -30,7 +30,7 @@ def get_report_as_text( self, **kwargs ) -> str: """ - The report as a text is represented as a table suitable for logging. It uses a human-readable formatting style. + The report as a text is represented as a table suitable for log. It uses a human-readable formatting style. Keyword Args: start_date (datetime.datetime): diff --git a/conflowgen/analyses/yard_capacity_analysis_report.py b/conflowgen/analyses/yard_capacity_analysis_report.py index c66cc9e3..2ef81ded 100644 --- a/conflowgen/analyses/yard_capacity_analysis_report.py +++ b/conflowgen/analyses/yard_capacity_analysis_report.py @@ -40,7 +40,7 @@ def __init__(self): def get_report_as_text(self, **kwargs) -> str: """ - The report as a text is represented as a table suitable for logging. It uses a human-readable formatting style. + The report as a text is represented as a table suitable for log. It uses a human-readable formatting style. Keyword Args: storage_requirement: Either a single storage requirement of type :class:`.StorageRequirement` or a whole diff --git a/conflowgen/api/container_flow_generation_manager.py b/conflowgen/api/container_flow_generation_manager.py index 4cb3e0ea..97ca187f 100644 --- a/conflowgen/api/container_flow_generation_manager.py +++ b/conflowgen/api/container_flow_generation_manager.py @@ -27,7 +27,9 @@ def set_properties( start_date: datetime.date, end_date: datetime.date, name: typing.Optional[str] = None, - transportation_buffer: typing.Optional[float] = None + transportation_buffer: typing.Optional[float] = None, + ramp_up_period: typing.Optional[datetime.timedelta] = None, + ramp_down_period: typing.Optional[datetime.timedelta] = None, ) -> None: """ Args: @@ -38,7 +40,12 @@ def set_properties( name: The name of the generated synthetic container flow which helps to distinguish different scenarios. transportation_buffer: Determines how many percent more of the inbound journey capacity is used at most to transport containers on the outbound journey. + ramp_up_period: The period at the beginning during which operations gradually increases to full capacity. + This simulates the initial phase where container flow and terminal activities are scaled up. + ramp_down_period: The period at the end during which operations gradually decrease from full capacity. + This simulates the final phase where container flow and terminal activities are scaled down. """ + properties = self.container_flow_generation_properties_repository.get_container_flow_generation_properties() if name is not None: @@ -47,6 +54,9 @@ def set_properties( properties.start_date = start_date properties.end_date = end_date + properties.ramp_up_period = ramp_up_period.total_seconds() / 86400 # in days as float + properties.ramp_down_period = ramp_down_period.total_seconds() / 86400 # in days as float + if transportation_buffer is not None: properties.transportation_buffer = transportation_buffer @@ -67,6 +77,8 @@ def get_properties(self) -> typing.Dict[str, typing.Union[str, datetime.date, fl 'start_date': properties.start_date, 'end_date': properties.end_date, 'transportation_buffer': properties.transportation_buffer, + 'ramp_up_period': properties.ramp_up_period, + 'ramp_down_period': properties.ramp_down_period, } def container_flow_data_exists(self) -> bool: diff --git a/conflowgen/application/models/container_flow_generation_properties.py b/conflowgen/application/models/container_flow_generation_properties.py index 82a033d5..81b3a665 100644 --- a/conflowgen/application/models/container_flow_generation_properties.py +++ b/conflowgen/application/models/container_flow_generation_properties.py @@ -27,6 +27,16 @@ class ContainerFlowGenerationProperties(BaseModel): help_text="The last day of the generated container flow" ) + ramp_up_period= FloatField( + default=0, + help_text="Number of days for the ramp-up period" + ) + + ramp_down_period = FloatField( + default=0, + help_text="Number of days for the ramp-down period" + ) + generated_at = DateTimeField( default=lambda: datetime.datetime.now().replace(microsecond=0), help_text="The date the these properties have been created" diff --git a/conflowgen/domain_models/repositories/large_scheduled_vehicle_repository.py b/conflowgen/domain_models/repositories/large_scheduled_vehicle_repository.py index 05563689..dcf2989a 100644 --- a/conflowgen/domain_models/repositories/large_scheduled_vehicle_repository.py +++ b/conflowgen/domain_models/repositories/large_scheduled_vehicle_repository.py @@ -1,4 +1,7 @@ +import datetime +import enum import logging +import typing from typing import Dict, List, Callable, Type from conflowgen.domain_models.container import Container @@ -7,12 +10,19 @@ from conflowgen.domain_models.vehicle import LargeScheduledVehicle, AbstractLargeScheduledVehicle +class _Direction(enum.Enum): + inbound = 0 + outbound = 1 + + class LargeScheduledVehicleRepository: ignored_capacity = ContainerLength.get_teu_factor(ContainerLength.other) def __init__(self): self.transportation_buffer = None + self.ramp_up_period_end = None + self.ramp_down_period_start = None self.free_capacity_for_outbound_journey_buffer: Dict[Type[AbstractLargeScheduledVehicle], float] = {} self.free_capacity_for_inbound_journey_buffer: Dict[Type[AbstractLargeScheduledVehicle], float] = {} self.logger = logging.getLogger("conflowgen") @@ -21,6 +31,14 @@ def set_transportation_buffer(self, transportation_buffer: float): assert -1 < transportation_buffer self.transportation_buffer = transportation_buffer + def set_ramp_up_and_down_times( + self, + ramp_up_period_end: typing.Optional[datetime.datetime], + ramp_down_period_start: typing.Optional[datetime.datetime] + ): + self.ramp_up_period_end = ramp_up_period_end + self.ramp_down_period_start = ramp_down_period_start + def reset_cache(self): self.free_capacity_for_outbound_journey_buffer = {} self.free_capacity_for_inbound_journey_buffer = {} @@ -87,7 +105,8 @@ def get_free_capacity_for_inbound_journey(self, vehicle: Type[AbstractLargeSched free_capacity_in_teu = self._get_free_capacity_in_teu( vehicle=vehicle, maximum_capacity=total_moved_capacity_for_inbound_transportation_in_teu, - container_counter=self._get_number_containers_for_inbound_journey + container_counter=self._get_number_containers_for_inbound_journey, + direction=_Direction.inbound ) self.free_capacity_for_inbound_journey_buffer[vehicle] = free_capacity_in_teu return free_capacity_in_teu @@ -115,7 +134,8 @@ def get_free_capacity_for_outbound_journey(self, vehicle: Type[AbstractLargeSche free_capacity_in_teu = self._get_free_capacity_in_teu( vehicle=vehicle, maximum_capacity=total_moved_capacity_for_onward_transportation_in_teu, - container_counter=self._get_number_containers_for_outbound_journey + container_counter=self._get_number_containers_for_outbound_journey, + direction=_Direction.outbound ) self.free_capacity_for_outbound_journey_buffer[vehicle] = free_capacity_in_teu return free_capacity_in_teu @@ -125,7 +145,8 @@ def get_free_capacity_for_outbound_journey(self, vehicle: Type[AbstractLargeSche def _get_free_capacity_in_teu( vehicle: Type[AbstractLargeScheduledVehicle], maximum_capacity: int, - container_counter: Callable[[Type[AbstractLargeScheduledVehicle], ContainerLength], int] + container_counter: Callable[[Type[AbstractLargeScheduledVehicle], ContainerLength], int], + direction: _Direction ) -> float: loaded_20_foot_containers = container_counter(vehicle, ContainerLength.twenty_feet) loaded_40_foot_containers = container_counter(vehicle, ContainerLength.forty_feet) @@ -148,6 +169,13 @@ def _get_free_capacity_in_teu( f"loaded_40_foot_containers: {loaded_40_foot_containers}, " \ f"loaded_45_foot_containers: {loaded_45_foot_containers} and " \ f"loaded_other_containers: {loaded_other_containers}" + + arrival_time: datetime.datetime = vehicle.large_scheduled_vehicle.scheduled_arrival + if direction == _Direction.outbound and arrival_time < ramp_up_period_end: + free_capacity_in_teu = maximum_capacity * 0.1 + elif direction == _Direction.inbound and arrival_time >= ramp_down_period_start: + free_capacity_in_teu = maximum_capacity * 0.1 + return free_capacity_in_teu @classmethod diff --git a/conflowgen/flow_generator/container_flow_generation_service.py b/conflowgen/flow_generator/container_flow_generation_service.py index 9a772284..acd5b4e9 100644 --- a/conflowgen/flow_generator/container_flow_generation_service.py +++ b/conflowgen/flow_generator/container_flow_generation_service.py @@ -43,6 +43,12 @@ def _update_generation_properties_and_distributions(self): self.container_flow_end_date: datetime.date = container_flow_generation_properties.end_date assert self.container_flow_start_date < self.container_flow_end_date + ramp_up_period = container_flow_generation_properties.ramp_up_period + ramp_down_period = container_flow_generation_properties.ramp_down_period + self.ramp_up_period_end = self.container_flow_start_date + datetime.timedelta(days=ramp_up_period) + self.ramp_down_period_start = self.container_flow_end_date - datetime.timedelta(days=ramp_down_period) + assert self.ramp_up_period_end <= self.ramp_down_period_start + self.transportation_buffer: float = container_flow_generation_properties.transportation_buffer assert -1 < self.transportation_buffer diff --git a/conflowgen/flow_generator/large_scheduled_vehicle_for_onward_transportation_manager.py b/conflowgen/flow_generator/large_scheduled_vehicle_for_onward_transportation_manager.py index f0819892..a3189540 100644 --- a/conflowgen/flow_generator/large_scheduled_vehicle_for_onward_transportation_manager.py +++ b/conflowgen/flow_generator/large_scheduled_vehicle_for_onward_transportation_manager.py @@ -2,6 +2,7 @@ import datetime import logging import math +import typing from typing import Tuple, List, Dict, Type, Sequence import numpy as np @@ -42,10 +43,13 @@ def __init__(self): def reload_properties( self, - transportation_buffer: float + transportation_buffer: float, + ramp_up_period_end: typing.Optional[datetime.datetime], + ramp_down_period_start: typing.Optional[datetime.datetime], ): assert -1 < transportation_buffer self.schedule_repository.set_transportation_buffer(transportation_buffer) + self.logger.debug(f"Using transportation buffer of {transportation_buffer} when choosing the departing " f"vehicles that adhere a schedule.") diff --git a/conflowgen/log/__init__.py b/conflowgen/log/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/conflowgen/log/log.py b/conflowgen/log/log.py new file mode 100644 index 00000000..39e2e5ef --- /dev/null +++ b/conflowgen/log/log.py @@ -0,0 +1,85 @@ +import datetime +import logging +import os +import sys +from typing import Optional + +from conflowgen.tools import docstring_parameter + +LOGGING_DEFAULT_DIR = os.path.abspath( + os.path.join( + os.path.dirname(os.path.realpath(__file__)), + os.pardir, + "data", + "logs" + ) +) + +# noinspection SpellCheckingInspection +DEFAULT_LOGGING_FORMAT_STRING: str = '%(asctime)s - %(name)s - %(levelname)s - %(message)s' + + +@docstring_parameter(DEFAULT_LOGGING_FORMAT_STRING=DEFAULT_LOGGING_FORMAT_STRING) +def setup_logger( + logging_directory: Optional[str] = None, + format_string: Optional[str] = None +) -> logging.Logger: + """ + This sets up the default logger with the name 'conflowgen'. + Several classes and functions use the same logger to inform the user about the current progress. + This is just a convenience function, you can easily set up your own logger that uses the same name. + See e.g. + https://docs.python.org/3/howto/logging.html#configuring-logging + for how to set up your own logger. + + Args: + logging_directory: + The path of the directory where to store log files. + Defaults to ``/data/logs/``. + format_string: + The format string to use. + See e.g. + https://docs.python.org/3/library/logging.html#logrecord-attributes + for how to create your own format string. + Defaults to ``{DEFAULT_LOGGING_FORMAT_STRING}``. + + Returns: + The set-up logger instance. + """ + if format_string is None: + format_string = DEFAULT_LOGGING_FORMAT_STRING + + if logging_directory is None: + logging_directory = LOGGING_DEFAULT_DIR + + time_prefix = str(datetime.datetime.now()).replace(":", "-").replace(" ", "--").split(".", maxsplit=1)[0] + + formatter = logging.Formatter(format_string, datefmt="%d.%m.%Y %H:%M:%S %z") + + logger = logging.getLogger("conflowgen") + logger.setLevel(logging.DEBUG) + + stream_handlers = [handler for handler in logger.handlers if isinstance(handler, logging.StreamHandler)] + if any(handler.stream == sys.stdout for handler in stream_handlers): + logger.warning("Duplicate StreamHandler streaming to sys.stdout detected. " + "Skipping adding another StreamHandler.") + else: + stream_handler = logging.StreamHandler(stream=sys.stdout) + stream_handler.setLevel(logging.DEBUG) + stream_handler.setFormatter(formatter) + logger.addHandler(stream_handler) + + if not os.path.isdir(logging_directory): + logger.debug(f"Creating log directory at {logging_directory}") + os.makedirs(logging_directory, exist_ok=True) + path_to_log_file = os.path.join( + logging_directory, + time_prefix + ".log" + ) + logger.debug(f"Creating log file at {path_to_log_file}") + file_handler = logging.FileHandler(path_to_log_file) + file_handler.setFormatter(formatter) + file_handler.setLevel(logging.DEBUG) + logger.addHandler(file_handler) + + return logger diff --git a/conflowgen/reporting/__init__.py b/conflowgen/reporting/__init__.py index 3d2f71b4..4b5c0b81 100644 --- a/conflowgen/reporting/__init__.py +++ b/conflowgen/reporting/__init__.py @@ -67,7 +67,7 @@ def reload(self): @abc.abstractmethod def get_report_as_text(self, **kwargs) -> str: """ - The report as a text is represented as a table suitable for logging. + The report as a text is represented as a table suitable for log. It uses a human-readable formatting style. The additional keyword arguments are passed to the analysis instance in case it accepts them. diff --git a/conflowgen/reporting/output_style.py b/conflowgen/reporting/output_style.py index dae131ca..2b5da15f 100644 --- a/conflowgen/reporting/output_style.py +++ b/conflowgen/reporting/output_style.py @@ -49,7 +49,7 @@ def display_explanation(self, text: str) -> None: class DisplayAsPlainText(DisplayAsMarkupLanguage): """ With this style, the output is simply returned in a plain manner. - This is, e.g., helpful when logging the text. + This is, e.g., helpful when log the text. """ DESIRED_LINE_LENGTH = 80 # doc: The console width used for wrapping output to new lines. This is not mandatory. diff --git a/conflowgen/tests/analyses/test_run_all_analyses.py b/conflowgen/tests/analyses/test_run_all_analyses.py index fee4bdfe..4bf39f50 100644 --- a/conflowgen/tests/analyses/test_run_all_analyses.py +++ b/conflowgen/tests/analyses/test_run_all_analyses.py @@ -1,6 +1,5 @@ import datetime -import unittest -import unittest.mock +from unittest import mock from conflowgen.analyses import run_all_analyses from conflowgen.application.models.container_flow_generation_properties import ContainerFlowGenerationProperties @@ -25,8 +24,11 @@ def test_with_no_data(self): run_all_analyses() self.assertEqual(len(context.output), 35) - def test_with_no_data_as_graph(self): - with unittest.mock.patch('matplotlib.pyplot.show'): - with self.assertLogs('conflowgen', level='INFO') as context: - run_all_analyses(as_text=False, as_graph=True, static_graphs=True) - self.assertEqual(len(context.output), 27) + @mock.patch('matplotlib.pyplot.show') + #@mock.patch('conflowgen.reporting.auto_reporter.AutoReporter.present_reports') + def test_with_no_data_as_graph(self, mock_pyplot): + # with unittest.mock.patch("matplotlib.pyplot.show"): + with self.assertLogs('conflowgen', level='INFO') as context: + print("Before run_all_analyses") + run_all_analyses(as_text=False, as_graph=True, static_graphs=True) + self.assertEqual(len(context.output), 27) diff --git a/conflowgen/tests/api/test_container_flow_generation_manager.py b/conflowgen/tests/api/test_container_flow_generation_manager.py index 62c03e3c..5ccaa400 100644 --- a/conflowgen/tests/api/test_container_flow_generation_manager.py +++ b/conflowgen/tests/api/test_container_flow_generation_manager.py @@ -66,6 +66,8 @@ class MockedProperties: start_date = datetime.date(2030, 1, 1) end_date = datetime.date(2030, 12, 31) transportation_buffer = 0.2 + ramp_up_period = 10.0 + ramp_down_period = 5.0 minimum_dwell_time_of_import_containers_in_hours = 3 minimum_dwell_time_of_export_containers_in_hours = 4 minimum_dwell_time_of_transshipment_containers_in_hours = 5 @@ -77,7 +79,9 @@ class MockedProperties: 'name': "my test data", 'start_date': datetime.date(2030, 1, 1), 'end_date': datetime.date(2030, 12, 31), - 'transportation_buffer': 0.2 + 'transportation_buffer': 0.2, + 'ramp_up_period': 10.0, + 'ramp_down_period': 5.0, } with unittest.mock.patch.object( diff --git a/conflowgen/tests/previews/test_inbound_and_outbound_vehicle_capacity_preview.py b/conflowgen/tests/previews/test_inbound_and_outbound_vehicle_capacity_preview.py index 7e7e340d..f4f290e3 100644 --- a/conflowgen/tests/previews/test_inbound_and_outbound_vehicle_capacity_preview.py +++ b/conflowgen/tests/previews/test_inbound_and_outbound_vehicle_capacity_preview.py @@ -82,7 +82,7 @@ def setUp(self) -> None: def test_inbound_with_no_schedules(self): """If no schedules are provided, no capacity is needed""" - empty_capacity = self.preview.get_inbound_capacity_of_vehicles().teu + empty_capacity: dict = self.preview.get_inbound_capacity_of_vehicles().teu self.assertSetEqual(set(ModeOfTransport), set(empty_capacity.keys())) for mode_of_transport in ModeOfTransport: capacity_in_teu = empty_capacity[mode_of_transport] From c45afa085e9c74f34ef1e347608c9dd340b3c5e3 Mon Sep 17 00:00:00 2001 From: Marvin Kastner Date: Tue, 21 May 2024 10:35:15 +0200 Subject: [PATCH 02/54] Add flow_direction as Container property, use it exemplarily in some cases, add the argument to get_free_capacity_for_outbound_journey --- conflowgen/domain_models/container.py | 11 +++++++++++ .../large_scheduled_vehicle_repository.py | 13 ++++++++++--- ...ace_for_containers_delivered_by_truck_service.py | 2 +- ...led_vehicle_for_onward_transportation_manager.py | 6 +++++- 4 files changed, 27 insertions(+), 5 deletions(-) diff --git a/conflowgen/domain_models/container.py b/conflowgen/domain_models/container.py index 043c9ad4..0aeed162 100644 --- a/conflowgen/domain_models/container.py +++ b/conflowgen/domain_models/container.py @@ -116,6 +116,17 @@ class Container(BaseModel): def occupied_teu(self) -> float: return CONTAINER_LENGTH_TO_OCCUPIED_TEU[self.length] + @property + def flow_direction(self) -> str: + if (self.delivered_by in [ModeOfTransport.truck, ModeOfTransport.train, ModeOfTransport.barge] + and self.picked_up_by in [ModeOfTransport.feeder, ModeOfTransport.deep_sea_vessel]): + return "export" + if (self.picked_up_by in [ModeOfTransport.truck, ModeOfTransport.train, ModeOfTransport.barge] + and self.delivered_by in [ModeOfTransport.feeder, ModeOfTransport.deep_sea_vessel]): + return "import" + else: + return "transshipment" + def get_arrival_time(self) -> datetime.datetime: if self.cached_arrival_time is not None: diff --git a/conflowgen/domain_models/repositories/large_scheduled_vehicle_repository.py b/conflowgen/domain_models/repositories/large_scheduled_vehicle_repository.py index dcf2989a..577461ed 100644 --- a/conflowgen/domain_models/repositories/large_scheduled_vehicle_repository.py +++ b/conflowgen/domain_models/repositories/large_scheduled_vehicle_repository.py @@ -111,7 +111,10 @@ def get_free_capacity_for_inbound_journey(self, vehicle: Type[AbstractLargeSched self.free_capacity_for_inbound_journey_buffer[vehicle] = free_capacity_in_teu return free_capacity_in_teu - def get_free_capacity_for_outbound_journey(self, vehicle: Type[AbstractLargeScheduledVehicle]) -> float: + def get_free_capacity_for_outbound_journey( + self, vehicle: Type[AbstractLargeScheduledVehicle], + flow_direction: str + ) -> float: """Get the free capacity for the outbound journey on a vehicle that moves according to a schedule in TEU. """ assert self.transportation_buffer is not None, "First set the value!" @@ -135,7 +138,8 @@ def get_free_capacity_for_outbound_journey(self, vehicle: Type[AbstractLargeSche vehicle=vehicle, maximum_capacity=total_moved_capacity_for_onward_transportation_in_teu, container_counter=self._get_number_containers_for_outbound_journey, - direction=_Direction.outbound + direction=_Direction.outbound, + flow_direction=flow_direction ) self.free_capacity_for_outbound_journey_buffer[vehicle] = free_capacity_in_teu return free_capacity_in_teu @@ -146,7 +150,8 @@ def _get_free_capacity_in_teu( vehicle: Type[AbstractLargeScheduledVehicle], maximum_capacity: int, container_counter: Callable[[Type[AbstractLargeScheduledVehicle], ContainerLength], int], - direction: _Direction + direction: _Direction, + flow_direction: str ) -> float: loaded_20_foot_containers = container_counter(vehicle, ContainerLength.twenty_feet) loaded_40_foot_containers = container_counter(vehicle, ContainerLength.forty_feet) @@ -171,6 +176,8 @@ def _get_free_capacity_in_teu( f"loaded_other_containers: {loaded_other_containers}" arrival_time: datetime.datetime = vehicle.large_scheduled_vehicle.scheduled_arrival + + #TODO use flow_direction if direction == _Direction.outbound and arrival_time < ramp_up_period_end: free_capacity_in_teu = maximum_capacity * 0.1 elif direction == _Direction.inbound and arrival_time >= ramp_down_period_start: diff --git a/conflowgen/flow_generator/allocate_space_for_containers_delivered_by_truck_service.py b/conflowgen/flow_generator/allocate_space_for_containers_delivered_by_truck_service.py index c506dba8..8db7db3f 100644 --- a/conflowgen/flow_generator/allocate_space_for_containers_delivered_by_truck_service.py +++ b/conflowgen/flow_generator/allocate_space_for_containers_delivered_by_truck_service.py @@ -115,7 +115,7 @@ def allocate(self) -> None: continue # try again with another vehicle type (refers to while loop) free_capacity_of_vehicle = self.large_scheduled_vehicle_repository.\ - get_free_capacity_for_outbound_journey(vehicle) + get_free_capacity_for_outbound_journey(vehicle, flow_direction="export") if free_capacity_of_vehicle <= self.ignored_capacity: diff --git a/conflowgen/flow_generator/large_scheduled_vehicle_for_onward_transportation_manager.py b/conflowgen/flow_generator/large_scheduled_vehicle_for_onward_transportation_manager.py index a3189540..e8b2a9df 100644 --- a/conflowgen/flow_generator/large_scheduled_vehicle_for_onward_transportation_manager.py +++ b/conflowgen/flow_generator/large_scheduled_vehicle_for_onward_transportation_manager.py @@ -197,8 +197,12 @@ def _draw_vehicle( return available_vehicles[0] vehicles_and_their_respective_free_capacity = {} + for vehicle in available_vehicles: - free_capacity = self.large_scheduled_vehicle_repository.get_free_capacity_for_outbound_journey(vehicle) + + free_capacity = self.large_scheduled_vehicle_repository.get_free_capacity_for_outbound_journey( + vehicle, container.flow_direction + ) if free_capacity >= ContainerLength.get_teu_factor(ContainerLength.other): vehicles_and_their_respective_free_capacity[vehicle] = free_capacity From a8af15903989703bb99ea468f26f92c3db302910 Mon Sep 17 00:00:00 2001 From: Marvin Kastner Date: Tue, 21 May 2024 12:42:25 +0200 Subject: [PATCH 03/54] Distinguish betwee flow_direction and journey_direction, fix tests. Ran the demo scripts without any modification for ramp-up and ramp-down. Things still look fine. --- .../api/container_flow_generation_manager.py | 11 +++- .../container_flow_generation_properties.py | 2 +- .../container_flow_statistics_report.py | 4 +- conflowgen/descriptive_datatypes/__init__.py | 16 ++++++ conflowgen/domain_models/container.py | 13 +++-- .../large_scheduled_vehicle_repository.py | 50 +++++++++++++------ .../repositories/schedule_repository.py | 17 ++++++- ...r_containers_delivered_by_truck_service.py | 6 ++- ...hicle_for_onward_transportation_manager.py | 17 +++++-- ...test_large_scheduled_vehicle_repository.py | 17 +++++-- .../reposistories/test_schedule_repository.py | 45 +++++++++-------- 11 files changed, 139 insertions(+), 59 deletions(-) diff --git a/conflowgen/api/container_flow_generation_manager.py b/conflowgen/api/container_flow_generation_manager.py index 97ca187f..a3481da1 100644 --- a/conflowgen/api/container_flow_generation_manager.py +++ b/conflowgen/api/container_flow_generation_manager.py @@ -54,8 +54,15 @@ def set_properties( properties.start_date = start_date properties.end_date = end_date - properties.ramp_up_period = ramp_up_period.total_seconds() / 86400 # in days as float - properties.ramp_down_period = ramp_down_period.total_seconds() / 86400 # in days as float + if ramp_up_period: + properties.ramp_up_period = ramp_up_period.total_seconds() / 86400 # in days as float + else: + properties.ramp_up_period = 0 + + if ramp_down_period: + properties.ramp_down_period = ramp_down_period.total_seconds() / 86400 # in days as float + else: + properties.ramp_down_period = 0 if transportation_buffer is not None: properties.transportation_buffer = transportation_buffer diff --git a/conflowgen/application/models/container_flow_generation_properties.py b/conflowgen/application/models/container_flow_generation_properties.py index 81b3a665..69e40b52 100644 --- a/conflowgen/application/models/container_flow_generation_properties.py +++ b/conflowgen/application/models/container_flow_generation_properties.py @@ -27,7 +27,7 @@ class ContainerFlowGenerationProperties(BaseModel): help_text="The last day of the generated container flow" ) - ramp_up_period= FloatField( + ramp_up_period = FloatField( default=0, help_text="Number of days for the ramp-up period" ) diff --git a/conflowgen/application/reports/container_flow_statistics_report.py b/conflowgen/application/reports/container_flow_statistics_report.py index b673610a..7703be34 100644 --- a/conflowgen/application/reports/container_flow_statistics_report.py +++ b/conflowgen/application/reports/container_flow_statistics_report.py @@ -4,6 +4,7 @@ import statistics from typing import List, Type, Dict +from conflowgen.descriptive_datatypes import FlowDirection from conflowgen.domain_models.data_types.mode_of_transport import ModeOfTransport from conflowgen.domain_models.repositories.large_scheduled_vehicle_repository import LargeScheduledVehicleRepository from conflowgen.domain_models.vehicle import AbstractLargeScheduledVehicle, LargeScheduledVehicle @@ -44,7 +45,8 @@ def _generate_free_capacity_statistics(self, vehicles_of_types): vehicle ) free_capacity_outbound = self.large_scheduled_vehicle_repository.get_free_capacity_for_outbound_journey( - vehicle + vehicle, + FlowDirection.undefined ) assert free_capacity_inbound <= large_scheduled_vehicle.capacity_in_teu, \ f"A vehicle can only load at maximum its capacity, but for vehicle {vehicle} the free capacity " \ diff --git a/conflowgen/descriptive_datatypes/__init__.py b/conflowgen/descriptive_datatypes/__init__.py index c8d25ad0..3f4a58f8 100644 --- a/conflowgen/descriptive_datatypes/__init__.py +++ b/conflowgen/descriptive_datatypes/__init__.py @@ -1,6 +1,7 @@ from __future__ import annotations import datetime +import enum import typing from conflowgen.domain_models.data_types.mode_of_transport import ModeOfTransport @@ -127,3 +128,18 @@ class ContainersTransportedByTruck(typing.NamedTuple): #: The number of containers moved on the outbound journey outbound: float + + +class FlowDirection(enum.Enum): + """ + Represents the flow direction based on the terminology of + *Handbook of Terminal Planning*, edited by Jürgen W. Böse (https://link.springer.com/book/10.1007/978-3-030-39990-0) + """ + + import_flow = "import" + + export_flow = "export" + + transshipment_flow = "transshipment" + + undefined = "undefined" diff --git a/conflowgen/domain_models/container.py b/conflowgen/domain_models/container.py index 0aeed162..9da6453b 100644 --- a/conflowgen/domain_models/container.py +++ b/conflowgen/domain_models/container.py @@ -14,6 +14,7 @@ from .vehicle import LargeScheduledVehicle from .vehicle import Truck from .data_types.storage_requirement import StorageRequirement +from ..descriptive_datatypes import FlowDirection from ..domain_models.data_types.mode_of_transport import ModeOfTransport @@ -117,15 +118,17 @@ def occupied_teu(self) -> float: return CONTAINER_LENGTH_TO_OCCUPIED_TEU[self.length] @property - def flow_direction(self) -> str: + def flow_direction(self) -> FlowDirection: if (self.delivered_by in [ModeOfTransport.truck, ModeOfTransport.train, ModeOfTransport.barge] and self.picked_up_by in [ModeOfTransport.feeder, ModeOfTransport.deep_sea_vessel]): - return "export" + return FlowDirection.export_flow if (self.picked_up_by in [ModeOfTransport.truck, ModeOfTransport.train, ModeOfTransport.barge] and self.delivered_by in [ModeOfTransport.feeder, ModeOfTransport.deep_sea_vessel]): - return "import" - else: - return "transshipment" + return FlowDirection.import_flow + if (self.picked_up_by in [ModeOfTransport.feeder, ModeOfTransport.deep_sea_vessel] + and self.delivered_by in [ModeOfTransport.feeder, ModeOfTransport.deep_sea_vessel]): + return FlowDirection.transshipment_flow + return FlowDirection.undefined def get_arrival_time(self) -> datetime.datetime: diff --git a/conflowgen/domain_models/repositories/large_scheduled_vehicle_repository.py b/conflowgen/domain_models/repositories/large_scheduled_vehicle_repository.py index 577461ed..fdda11d6 100644 --- a/conflowgen/domain_models/repositories/large_scheduled_vehicle_repository.py +++ b/conflowgen/domain_models/repositories/large_scheduled_vehicle_repository.py @@ -4,15 +4,16 @@ import typing from typing import Dict, List, Callable, Type +from conflowgen.descriptive_datatypes import FlowDirection from conflowgen.domain_models.container import Container from conflowgen.domain_models.data_types.container_length import ContainerLength from conflowgen.domain_models.data_types.mode_of_transport import ModeOfTransport from conflowgen.domain_models.vehicle import LargeScheduledVehicle, AbstractLargeScheduledVehicle -class _Direction(enum.Enum): - inbound = 0 - outbound = 1 +class JourneyDirection(enum.Enum): + inbound = "inbound" + outbound = "outbound" class LargeScheduledVehicleRepository: @@ -33,9 +34,9 @@ def set_transportation_buffer(self, transportation_buffer: float): def set_ramp_up_and_down_times( self, - ramp_up_period_end: typing.Optional[datetime.datetime], - ramp_down_period_start: typing.Optional[datetime.datetime] - ): + ramp_up_period_end: typing.Optional[datetime.datetime] = None, + ramp_down_period_start: typing.Optional[datetime.datetime] = None + ) -> None: self.ramp_up_period_end = ramp_up_period_end self.ramp_down_period_start = ramp_down_period_start @@ -106,14 +107,15 @@ def get_free_capacity_for_inbound_journey(self, vehicle: Type[AbstractLargeSched vehicle=vehicle, maximum_capacity=total_moved_capacity_for_inbound_transportation_in_teu, container_counter=self._get_number_containers_for_inbound_journey, - direction=_Direction.inbound + journey_direction=JourneyDirection.inbound, + flow_direction=FlowDirection.undefined ) self.free_capacity_for_inbound_journey_buffer[vehicle] = free_capacity_in_teu return free_capacity_in_teu def get_free_capacity_for_outbound_journey( self, vehicle: Type[AbstractLargeScheduledVehicle], - flow_direction: str + flow_direction: FlowDirection ) -> float: """Get the free capacity for the outbound journey on a vehicle that moves according to a schedule in TEU. """ @@ -138,20 +140,19 @@ def get_free_capacity_for_outbound_journey( vehicle=vehicle, maximum_capacity=total_moved_capacity_for_onward_transportation_in_teu, container_counter=self._get_number_containers_for_outbound_journey, - direction=_Direction.outbound, + journey_direction=JourneyDirection.outbound, flow_direction=flow_direction ) self.free_capacity_for_outbound_journey_buffer[vehicle] = free_capacity_in_teu return free_capacity_in_teu - # noinspection PyUnresolvedReferences - @staticmethod def _get_free_capacity_in_teu( + self, vehicle: Type[AbstractLargeScheduledVehicle], maximum_capacity: int, container_counter: Callable[[Type[AbstractLargeScheduledVehicle], ContainerLength], int], - direction: _Direction, - flow_direction: str + journey_direction: JourneyDirection, + flow_direction: FlowDirection ) -> float: loaded_20_foot_containers = container_counter(vehicle, ContainerLength.twenty_feet) loaded_40_foot_containers = container_counter(vehicle, ContainerLength.forty_feet) @@ -164,7 +165,10 @@ def _get_free_capacity_in_teu( - loaded_45_foot_containers * ContainerLength.get_teu_factor(ContainerLength.forty_five_feet) - loaded_other_containers * ContainerLength.get_teu_factor(ContainerLength.other) ) + + # noinspection PyUnresolvedReferences vehicle_name = vehicle.large_scheduled_vehicle.vehicle_name + assert free_capacity_in_teu >= 0, f"vehicle {vehicle} of type {vehicle.get_mode_of_transport()} with the " \ f"name '{vehicle_name}' " \ f"is overloaded, " \ @@ -175,12 +179,26 @@ def _get_free_capacity_in_teu( f"loaded_45_foot_containers: {loaded_45_foot_containers} and " \ f"loaded_other_containers: {loaded_other_containers}" + # noinspection PyUnresolvedReferences arrival_time: datetime.datetime = vehicle.large_scheduled_vehicle.scheduled_arrival - #TODO use flow_direction - if direction == _Direction.outbound and arrival_time < ramp_up_period_end: + if ( + journey_direction == JourneyDirection.outbound + and flow_direction == FlowDirection.transshipment_flow + and self.ramp_up_period_end is not None + and arrival_time < self.ramp_up_period_end + ): + # keep transshipment containers in the yard longer during the ramp-up period to fill the yard faster + # by offering less transport capacity on the outbound journey of deep sea vessels and feeders free_capacity_in_teu = maximum_capacity * 0.1 - elif direction == _Direction.inbound and arrival_time >= ramp_down_period_start: + + elif ( + journey_direction == JourneyDirection.inbound + and self.ramp_down_period_start is not None + and arrival_time >= self.ramp_down_period_start + ): + # decrease number of inbound containers (any direction) during the ramp-down period + # by offering less transport capacity on the inbound journey (all types of vehicles, excluding trucks) free_capacity_in_teu = maximum_capacity * 0.1 return free_capacity_in_teu diff --git a/conflowgen/domain_models/repositories/schedule_repository.py b/conflowgen/domain_models/repositories/schedule_repository.py index b45382a3..af9fd78e 100644 --- a/conflowgen/domain_models/repositories/schedule_repository.py +++ b/conflowgen/domain_models/repositories/schedule_repository.py @@ -1,7 +1,9 @@ import datetime +import typing from typing import List, Type import logging +from conflowgen.descriptive_datatypes import FlowDirection from conflowgen.domain_models.container import Container from conflowgen.domain_models.data_types.container_length import ContainerLength from conflowgen.domain_models.data_types.mode_of_transport import ModeOfTransport @@ -18,12 +20,23 @@ def __init__(self): def set_transportation_buffer(self, transportation_buffer: float): self.large_scheduled_vehicle_repository.set_transportation_buffer(transportation_buffer) + def set_ramp_up_and_down_times( + self, + ramp_up_period_end: typing.Optional[datetime.datetime] = None, + ramp_down_period_start: typing.Optional[datetime.datetime] = None + ): + self.large_scheduled_vehicle_repository.set_ramp_up_and_down_times( + ramp_up_period_end=ramp_up_period_end, + ramp_down_period_start=ramp_down_period_start + ) + def get_departing_vehicles( self, start: datetime.datetime, end: datetime.datetime, vehicle_type: ModeOfTransport, - required_capacity: ContainerLength + required_capacity: ContainerLength, + flow_direction: FlowDirection ) -> List[Type[AbstractLargeScheduledVehicle]]: """Gets the available vehicles for the required capacity of the required type and within the time range. """ @@ -46,7 +59,7 @@ def get_departing_vehicles( vehicle: Type[AbstractLargeScheduledVehicle] for vehicle in vehicles: free_capacity_in_teu = self.large_scheduled_vehicle_repository.get_free_capacity_for_outbound_journey( - vehicle + vehicle, flow_direction ) if free_capacity_in_teu >= required_capacity_in_teu: vehicles_with_sufficient_capacity.append(vehicle) diff --git a/conflowgen/flow_generator/allocate_space_for_containers_delivered_by_truck_service.py b/conflowgen/flow_generator/allocate_space_for_containers_delivered_by_truck_service.py index 8db7db3f..186e094a 100644 --- a/conflowgen/flow_generator/allocate_space_for_containers_delivered_by_truck_service.py +++ b/conflowgen/flow_generator/allocate_space_for_containers_delivered_by_truck_service.py @@ -3,6 +3,7 @@ from typing import Dict, Type, List from conflowgen.application.repositories.random_seed_store_repository import get_initialised_random_object +from conflowgen.descriptive_datatypes import FlowDirection from conflowgen.domain_models.container import Container from conflowgen.domain_models.distribution_repositories.mode_of_transport_distribution_repository import \ ModeOfTransportDistributionRepository @@ -115,7 +116,7 @@ def allocate(self) -> None: continue # try again with another vehicle type (refers to while loop) free_capacity_of_vehicle = self.large_scheduled_vehicle_repository.\ - get_free_capacity_for_outbound_journey(vehicle, flow_direction="export") + get_free_capacity_for_outbound_journey(vehicle, flow_direction=FlowDirection.export_flow) if free_capacity_of_vehicle <= self.ignored_capacity: @@ -175,7 +176,8 @@ def _pick_vehicle( # Make it more likely that a container ends up on a large vessel than on a smaller one vehicle: Type[AbstractLargeScheduledVehicle] vehicle_distribution: Dict[Type[AbstractLargeScheduledVehicle], float] = { - vehicle: self.large_scheduled_vehicle_repository.get_free_capacity_for_outbound_journey(vehicle) + vehicle: self.large_scheduled_vehicle_repository.get_free_capacity_for_outbound_journey( + vehicle, FlowDirection.export_flow) for vehicle in vehicles_of_type } all_free_capacities = list(vehicle_distribution.values()) diff --git a/conflowgen/flow_generator/large_scheduled_vehicle_for_onward_transportation_manager.py b/conflowgen/flow_generator/large_scheduled_vehicle_for_onward_transportation_manager.py index e8b2a9df..c090e7ec 100644 --- a/conflowgen/flow_generator/large_scheduled_vehicle_for_onward_transportation_manager.py +++ b/conflowgen/flow_generator/large_scheduled_vehicle_for_onward_transportation_manager.py @@ -44,12 +44,17 @@ def __init__(self): def reload_properties( self, transportation_buffer: float, - ramp_up_period_end: typing.Optional[datetime.datetime], - ramp_down_period_start: typing.Optional[datetime.datetime], + ramp_up_period_end: typing.Optional[datetime.datetime] = None, + ramp_down_period_start: typing.Optional[datetime.datetime] = None, ): assert -1 < transportation_buffer self.schedule_repository.set_transportation_buffer(transportation_buffer) + self.schedule_repository.set_ramp_up_and_down_times( + ramp_up_period_end=ramp_up_period_end, + ramp_down_period_start=ramp_down_period_start + ) + self.logger.debug(f"Using transportation buffer of {transportation_buffer} when choosing the departing " f"vehicles that adhere a schedule.") @@ -129,7 +134,8 @@ def choose_departing_vehicle_for_containers(self) -> None: start=(container_arrival + datetime.timedelta(hours=minimum_dwell_time_in_hours)), end=(container_arrival + datetime.timedelta(hours=maximum_dwell_time_in_hours)), vehicle_type=initial_departing_vehicle_type, - required_capacity=container.length + required_capacity=container.length, + flow_direction=container.flow_direction ) if len(available_vehicles) > 0: @@ -197,7 +203,7 @@ def _draw_vehicle( return available_vehicles[0] vehicles_and_their_respective_free_capacity = {} - + for vehicle in available_vehicles: free_capacity = self.large_scheduled_vehicle_repository.get_free_capacity_for_outbound_journey( @@ -321,7 +327,8 @@ def _find_alternative_mode_of_transportation( start=(container_arrival + datetime.timedelta(hours=minimum_dwell_time_in_hours)), end=(container_arrival + datetime.timedelta(hours=maximum_dwell_time_in_hours)), vehicle_type=vehicle_type, - required_capacity=container.length + required_capacity=container.length, + flow_direction=container.flow_direction ) if len(available_vehicles) > 0: # There is a vehicle of a new type available, so it is picked vehicle = self._pick_vehicle_for_container(available_vehicles, container) diff --git a/conflowgen/tests/domain_models/reposistories/test_large_scheduled_vehicle_repository.py b/conflowgen/tests/domain_models/reposistories/test_large_scheduled_vehicle_repository.py index 423db374..5bf2df3c 100644 --- a/conflowgen/tests/domain_models/reposistories/test_large_scheduled_vehicle_repository.py +++ b/conflowgen/tests/domain_models/reposistories/test_large_scheduled_vehicle_repository.py @@ -1,6 +1,7 @@ import datetime import unittest +from conflowgen.descriptive_datatypes import FlowDirection from conflowgen.domain_models.container import Container from conflowgen.domain_models.data_types.container_length import ContainerLength from conflowgen.domain_models.data_types.mode_of_transport import ModeOfTransport @@ -58,7 +59,9 @@ def test_free_capacity_for_one_teu(self): picked_up_by_large_scheduled_vehicle=self.train_lsv, ) - free_capacity_in_teu = self.lsv_repository.get_free_capacity_for_outbound_journey(self.train) + free_capacity_in_teu = self.lsv_repository.get_free_capacity_for_outbound_journey( + self.train, FlowDirection.undefined + ) self.assertEqual(free_capacity_in_teu, 2) def test_free_capacity_for_one_ffe(self): @@ -72,7 +75,9 @@ def test_free_capacity_for_one_ffe(self): picked_up_by_large_scheduled_vehicle=self.train_lsv, ) - free_capacity_in_teu = self.lsv_repository.get_free_capacity_for_outbound_journey(self.train) + free_capacity_in_teu = self.lsv_repository.get_free_capacity_for_outbound_journey( + self.train, FlowDirection.undefined + ) self.assertEqual(free_capacity_in_teu, 1) def test_free_capacity_for_45_foot_container(self): @@ -86,7 +91,9 @@ def test_free_capacity_for_45_foot_container(self): picked_up_by_large_scheduled_vehicle=self.train_lsv, ) - free_capacity_in_teu = self.lsv_repository.get_free_capacity_for_outbound_journey(self.train) + free_capacity_in_teu = self.lsv_repository.get_free_capacity_for_outbound_journey( + self.train, FlowDirection.undefined + ) self.assertEqual(free_capacity_in_teu, 0.75) def test_free_capacity_for_other_container(self): @@ -100,5 +107,7 @@ def test_free_capacity_for_other_container(self): picked_up_by_large_scheduled_vehicle=self.train_lsv, ) - free_capacity_in_teu = self.lsv_repository.get_free_capacity_for_outbound_journey(self.train) + free_capacity_in_teu = self.lsv_repository.get_free_capacity_for_outbound_journey( + self.train, FlowDirection.undefined + ) self.assertEqual(free_capacity_in_teu, 0.5) diff --git a/conflowgen/tests/domain_models/reposistories/test_schedule_repository.py b/conflowgen/tests/domain_models/reposistories/test_schedule_repository.py index d926101a..234b389d 100644 --- a/conflowgen/tests/domain_models/reposistories/test_schedule_repository.py +++ b/conflowgen/tests/domain_models/reposistories/test_schedule_repository.py @@ -2,6 +2,7 @@ import unittest import unittest.mock +from conflowgen.descriptive_datatypes import FlowDirection from conflowgen.domain_models.container import Container from conflowgen.domain_models.data_types.container_length import ContainerLength from conflowgen.domain_models.data_types.mode_of_transport import ModeOfTransport @@ -36,7 +37,8 @@ def test_empty_schedule_database_throws_no_exception(self): datetime.datetime.now(), datetime.datetime.now() + datetime.timedelta(days=21), vehicle_type=ModeOfTransport.train, - required_capacity=ContainerLength.twenty_feet + required_capacity=ContainerLength.twenty_feet, + flow_direction=FlowDirection.undefined ) self.assertEqual(len(vehicles_and_frequency), 0) @@ -58,17 +60,16 @@ def test_find_vehicle_if_in_time_range(self): scheduled_arrival=datetime.datetime(year=2021, month=8, day=7, hour=13, minute=15), schedule=schedule ) - train_lsv.save() - train = Train.create( + Train.create( large_scheduled_vehicle=train_lsv ) - train.save() vehicles = self.schedule_repository.get_departing_vehicles( start=datetime.datetime(year=2021, month=8, day=5, hour=0, minute=0), end=datetime.datetime(year=2021, month=8, day=10, hour=23, minute=59), vehicle_type=ModeOfTransport.train, - required_capacity=ContainerLength.twenty_feet + required_capacity=ContainerLength.twenty_feet, + flow_direction=FlowDirection.undefined ) self.assertEqual(len(vehicles), 1) @@ -102,7 +103,8 @@ def test_export_buffer_below_capacity(self): start=datetime.datetime(year=2021, month=8, day=5, hour=0, minute=0), end=datetime.datetime(year=2021, month=8, day=10, hour=23, minute=59), vehicle_type=ModeOfTransport.train, - required_capacity=ContainerLength.twenty_feet + required_capacity=ContainerLength.twenty_feet, + flow_direction=FlowDirection.undefined ) self.assertEqual(len(vehicles), 1) @@ -125,17 +127,16 @@ def test_export_buffer_above_capacity(self): scheduled_arrival=datetime.datetime(year=2021, month=8, day=7, hour=13, minute=15), schedule=schedule ) - train_lsv.save() - train = Train.create( + Train.create( large_scheduled_vehicle=train_lsv ) - train.save() vehicles = self.schedule_repository.get_departing_vehicles( start=datetime.datetime(year=2021, month=8, day=5, hour=0, minute=0), end=datetime.datetime(year=2021, month=8, day=10, hour=23, minute=59), vehicle_type=ModeOfTransport.train, - required_capacity=ContainerLength.twenty_feet + required_capacity=ContainerLength.twenty_feet, + flow_direction=FlowDirection.undefined ) self.assertEqual(len(vehicles), 1) @@ -150,20 +151,20 @@ def test_ignore_vehicle_outside_time_range(self): average_moved_capacity=1, ) train_moves_this_capacity = 7 - train = LargeScheduledVehicle.create( + LargeScheduledVehicle.create( vehicle_name="TestTrain1", capacity_in_teu=90, moved_capacity=train_moves_this_capacity, scheduled_arrival=datetime.datetime(year=2021, month=8, day=7, hour=13, minute=15), schedule=schedule ) - train.save() vehicles_and_frequency = self.schedule_repository.get_departing_vehicles( start=datetime.datetime(year=2021, month=8, day=10, hour=13, minute=15), end=datetime.datetime(year=2021, month=8, day=15, hour=13, minute=15), vehicle_type=ModeOfTransport.train, - required_capacity=ContainerLength.forty_feet + required_capacity=ContainerLength.forty_feet, + flow_direction=FlowDirection.undefined ) self.assertEqual(len(vehicles_and_frequency), 0) @@ -192,7 +193,7 @@ def test_check_used_20_foot_containers(self): train.save() # This container is already loaded on the train - Container.create( + container = Container.create( weight=20, length=ContainerLength.twenty_feet, storage_requirement=StorageRequirement.standard, @@ -205,7 +206,8 @@ def test_check_used_20_foot_containers(self): start=datetime.datetime(year=2021, month=8, day=5, hour=0, minute=0), end=datetime.datetime(year=2021, month=8, day=10, hour=23, minute=59), vehicle_type=ModeOfTransport.train, - required_capacity=ContainerLength.twenty_feet + required_capacity=ContainerLength.twenty_feet, + flow_direction=container.flow_direction ) self.assertEqual(len(available_vehicles), 1) @@ -235,7 +237,7 @@ def test_check_used_40_foot_containers(self): train.save() # This container is already loaded on the train - Container.create( + container = Container.create( weight=20, length=ContainerLength.forty_feet, storage_requirement=StorageRequirement.standard, @@ -248,7 +250,8 @@ def test_check_used_40_foot_containers(self): start=datetime.datetime(year=2021, month=8, day=5, hour=0, minute=0), end=datetime.datetime(year=2021, month=8, day=10, hour=23, minute=59), vehicle_type=ModeOfTransport.train, - required_capacity=ContainerLength.twenty_feet + required_capacity=ContainerLength.twenty_feet, + flow_direction=container.flow_direction ) self.assertEqual(len(vehicles), 1) @@ -274,10 +277,9 @@ def test_use_lsv_repository(self): train = Train.create( large_scheduled_vehicle=train_lsv ) - train.save() # This container is already loaded on the train - Container.create( + container = Container.create( weight=20, length=ContainerLength.forty_feet, storage_requirement=StorageRequirement.standard, @@ -295,6 +297,7 @@ def test_use_lsv_repository(self): start=datetime.datetime(year=2021, month=8, day=5, hour=0, minute=0), end=datetime.datetime(year=2021, month=8, day=10, hour=23, minute=59), vehicle_type=ModeOfTransport.train, - required_capacity=ContainerLength.twenty_feet + required_capacity=ContainerLength.twenty_feet, + flow_direction=container.flow_direction ) - mock_method.assert_called_once_with(train) + mock_method.assert_called_once_with(train, FlowDirection.undefined) From 677d93bd6589433dc1a6668675055f90dab01607 Mon Sep 17 00:00:00 2001 From: Marvin Kastner Date: Tue, 21 May 2024 21:06:08 +0200 Subject: [PATCH 04/54] Improve description text --- conflowgen/api/container_flow_generation_manager.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/conflowgen/api/container_flow_generation_manager.py b/conflowgen/api/container_flow_generation_manager.py index a3481da1..f34893c8 100644 --- a/conflowgen/api/container_flow_generation_manager.py +++ b/conflowgen/api/container_flow_generation_manager.py @@ -40,10 +40,11 @@ def set_properties( name: The name of the generated synthetic container flow which helps to distinguish different scenarios. transportation_buffer: Determines how many percent more of the inbound journey capacity is used at most to transport containers on the outbound journey. - ramp_up_period: The period at the beginning during which operations gradually increases to full capacity. - This simulates the initial phase where container flow and terminal activities are scaled up. - ramp_down_period: The period at the end during which operations gradually decrease from full capacity. - This simulates the final phase where container flow and terminal activities are scaled down. + ramp_up_period: The period at the beginning during which yard occupancy gradually increases. + During the ramp-up period, the share of transshipment containers on outbound journey is reduced. + ramp_down_period: The period at the end during which operations gradually fade out. + During the ramp-down period, inbound container volumes are scaled down, reducing the number of new + import, export, and transshipment containers in the yard during this period.. """ properties = self.container_flow_generation_properties_repository.get_container_flow_generation_properties() From da9767c3d86597962a2fa2effa43927dc47ec1d9 Mon Sep 17 00:00:00 2001 From: Marvin Kastner Date: Tue, 21 May 2024 21:08:22 +0200 Subject: [PATCH 05/54] Undo changes --- conflowgen/tests/analyses/test_run_all_analyses.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/conflowgen/tests/analyses/test_run_all_analyses.py b/conflowgen/tests/analyses/test_run_all_analyses.py index 4bf39f50..0ce2c03b 100644 --- a/conflowgen/tests/analyses/test_run_all_analyses.py +++ b/conflowgen/tests/analyses/test_run_all_analyses.py @@ -1,5 +1,5 @@ import datetime -from unittest import mock +import unittest.mock from conflowgen.analyses import run_all_analyses from conflowgen.application.models.container_flow_generation_properties import ContainerFlowGenerationProperties @@ -24,11 +24,9 @@ def test_with_no_data(self): run_all_analyses() self.assertEqual(len(context.output), 35) - @mock.patch('matplotlib.pyplot.show') - #@mock.patch('conflowgen.reporting.auto_reporter.AutoReporter.present_reports') def test_with_no_data_as_graph(self, mock_pyplot): - # with unittest.mock.patch("matplotlib.pyplot.show"): - with self.assertLogs('conflowgen', level='INFO') as context: - print("Before run_all_analyses") - run_all_analyses(as_text=False, as_graph=True, static_graphs=True) - self.assertEqual(len(context.output), 27) + with unittest.mock.patch("matplotlib.pyplot.show"): + with self.assertLogs('conflowgen', level='INFO') as context: + print("Before run_all_analyses") + run_all_analyses(as_text=False, as_graph=True, static_graphs=True) + self.assertEqual(len(context.output), 27) From 309c9913682d8c514d80c724cafb18a57274aa62 Mon Sep 17 00:00:00 2001 From: Marvin Kastner Date: Tue, 21 May 2024 21:23:16 +0200 Subject: [PATCH 06/54] fix mock_pyplot issue --- conflowgen/tests/analyses/test_run_all_analyses.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conflowgen/tests/analyses/test_run_all_analyses.py b/conflowgen/tests/analyses/test_run_all_analyses.py index 0ce2c03b..ed6abadb 100644 --- a/conflowgen/tests/analyses/test_run_all_analyses.py +++ b/conflowgen/tests/analyses/test_run_all_analyses.py @@ -24,7 +24,7 @@ def test_with_no_data(self): run_all_analyses() self.assertEqual(len(context.output), 35) - def test_with_no_data_as_graph(self, mock_pyplot): + def test_with_no_data_as_graph(self): with unittest.mock.patch("matplotlib.pyplot.show"): with self.assertLogs('conflowgen', level='INFO') as context: print("Before run_all_analyses") From 36200ecf33c9646b7c25956f2b9ea4d650ea98b2 Mon Sep 17 00:00:00 2001 From: Marvin Kastner Date: Tue, 21 May 2024 22:02:14 +0200 Subject: [PATCH 07/54] Save ConFlowGen version in SQLITE table --- conflowgen/api/container_flow_generation_manager.py | 4 ++++ .../models/container_flow_generation_properties.py | 7 ++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/conflowgen/api/container_flow_generation_manager.py b/conflowgen/api/container_flow_generation_manager.py index f34893c8..ada21a3a 100644 --- a/conflowgen/api/container_flow_generation_manager.py +++ b/conflowgen/api/container_flow_generation_manager.py @@ -8,6 +8,7 @@ ContainerFlowGenerationPropertiesRepository from conflowgen.flow_generator.container_flow_generation_service import \ ContainerFlowGenerationService +from conflowgen.metadata import __version__ class ContainerFlowGenerationManager: @@ -68,6 +69,8 @@ def set_properties( if transportation_buffer is not None: properties.transportation_buffer = transportation_buffer + properties.conflowgen_version = __version__ + self.container_flow_generation_properties_repository.set_container_flow_generation_properties( properties ) @@ -87,6 +90,7 @@ def get_properties(self) -> typing.Dict[str, typing.Union[str, datetime.date, fl 'transportation_buffer': properties.transportation_buffer, 'ramp_up_period': properties.ramp_up_period, 'ramp_down_period': properties.ramp_down_period, + 'conflowgen_version': properties.conflowgen_version } def container_flow_data_exists(self) -> bool: diff --git a/conflowgen/application/models/container_flow_generation_properties.py b/conflowgen/application/models/container_flow_generation_properties.py index 69e40b52..4651d002 100644 --- a/conflowgen/application/models/container_flow_generation_properties.py +++ b/conflowgen/application/models/container_flow_generation_properties.py @@ -4,6 +4,7 @@ from conflowgen.domain_models.seeders import DEFAULT_TRANSPORTATION_BUFFER from conflowgen.domain_models.base_model import BaseModel +from conflowgen.metadata import __version__ class ContainerFlowGenerationProperties(BaseModel): @@ -14,7 +15,7 @@ class ContainerFlowGenerationProperties(BaseModel): name = CharField( null=True, - help_text="The name of the generated container flow, e.g. a scenario" + help_text="The name of the generated container flow, e.g., describing the scenario" ) start_date = DateField( @@ -49,3 +50,7 @@ class ContainerFlowGenerationProperties(BaseModel): transportation_buffer = FloatField( default=DEFAULT_TRANSPORTATION_BUFFER, ) + + conflowgen_version = CharField( + default=__version__ + ) From fcad055e7b94f1d2a278b93f02b8abaffff13db3 Mon Sep 17 00:00:00 2001 From: Marvin Kastner Date: Tue, 21 May 2024 22:05:15 +0200 Subject: [PATCH 08/54] Add happy path with ramp-up and ramp-down --- ...tainer_flow_generator_service__generate.py | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/conflowgen/tests/flow_generator/test_container_flow_generator_service__generate.py b/conflowgen/tests/flow_generator/test_container_flow_generator_service__generate.py index 1bb2ded2..326c66d9 100644 --- a/conflowgen/tests/flow_generator/test_container_flow_generator_service__generate.py +++ b/conflowgen/tests/flow_generator/test_container_flow_generator_service__generate.py @@ -73,3 +73,44 @@ def test_nothing_to_do(self): create_tables(self.sqlite_db) seed_all_distributions() self.container_flow_generator_service.generate() + + def test_happy_path_no_mocking_with_ramp_up_and_ramp_down(self): + create_tables(self.sqlite_db) + seed_all_distributions() + + container_flow_generation_properties_manager = ContainerFlowGenerationPropertiesRepository() + properties: ContainerFlowGenerationProperties = (container_flow_generation_properties_manager + .get_container_flow_generation_properties()) + properties.ramp_up_period = 5 + properties.ramp_down_period = 5 + container_flow_generation_properties_manager.set_container_flow_generation_properties(properties) + + port_call_manager = PortCallManager() + port_call_manager.add_vehicle( + vehicle_type=ModeOfTransport.feeder, + service_name="TestFeeder", + vehicle_arrives_at=properties.start_date + datetime.timedelta(days=3), + vehicle_arrives_at_time=datetime.time(11), + average_vehicle_capacity=800, + average_moved_capacity=100, + next_destinations=None + ) + port_call_manager.add_vehicle( + vehicle_type=ModeOfTransport.deep_sea_vessel, + service_name="TestDeepSeaVessel", + vehicle_arrives_at=properties.start_date + datetime.timedelta(days=8), + vehicle_arrives_at_time=datetime.time(11), + average_vehicle_capacity=800, + average_moved_capacity=100, + next_destinations=None + ) + port_call_manager.add_vehicle( + vehicle_type=ModeOfTransport.deep_sea_vessel, + service_name="TestDeepSeaVessel2", + vehicle_arrives_at=properties.end_date - datetime.timedelta(days=2), + vehicle_arrives_at_time=datetime.time(11), + average_vehicle_capacity=800, + average_moved_capacity=100, + next_destinations=None + ) + self.container_flow_generator_service.generate() From a02e90ab56e9489921fd2ab9d8103df534922e2a Mon Sep 17 00:00:00 2001 From: Marvin Kastner Date: Tue, 21 May 2024 22:05:41 +0200 Subject: [PATCH 09/54] Fix typing hint --- ..._scheduled_vehicle_for_onward_transportation_manager.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/conflowgen/flow_generator/large_scheduled_vehicle_for_onward_transportation_manager.py b/conflowgen/flow_generator/large_scheduled_vehicle_for_onward_transportation_manager.py index c090e7ec..e03025bd 100644 --- a/conflowgen/flow_generator/large_scheduled_vehicle_for_onward_transportation_manager.py +++ b/conflowgen/flow_generator/large_scheduled_vehicle_for_onward_transportation_manager.py @@ -2,8 +2,7 @@ import datetime import logging import math -import typing -from typing import Tuple, List, Dict, Type, Sequence +from typing import Tuple, List, Dict, Type, Sequence, Optional import numpy as np # noinspection PyProtectedMember @@ -44,8 +43,8 @@ def __init__(self): def reload_properties( self, transportation_buffer: float, - ramp_up_period_end: typing.Optional[datetime.datetime] = None, - ramp_down_period_start: typing.Optional[datetime.datetime] = None, + ramp_up_period_end: Optional[datetime.datetime] = None, + ramp_down_period_start: Optional[datetime.datetime] = None, ): assert -1 < transportation_buffer self.schedule_repository.set_transportation_buffer(transportation_buffer) From 1f50039aa82dbb900f612f769eb890aab2e1f793 Mon Sep 17 00:00:00 2001 From: Marvin Kastner Date: Tue, 21 May 2024 22:06:10 +0200 Subject: [PATCH 10/54] Add test case for ConFlowGen version --- conflowgen/tests/api/test_container_flow_generation_manager.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/conflowgen/tests/api/test_container_flow_generation_manager.py b/conflowgen/tests/api/test_container_flow_generation_manager.py index 5ccaa400..38260ef0 100644 --- a/conflowgen/tests/api/test_container_flow_generation_manager.py +++ b/conflowgen/tests/api/test_container_flow_generation_manager.py @@ -74,6 +74,7 @@ class MockedProperties: maximum_dwell_time_of_import_containers_in_hours = 40 maximum_dwell_time_of_export_containers_in_hours = 50 maximum_dwell_time_of_transshipment_containers_in_hours = 60 + conflowgen_version = '2.1.1' dict_properties = { 'name': "my test data", @@ -82,6 +83,7 @@ class MockedProperties: 'transportation_buffer': 0.2, 'ramp_up_period': 10.0, 'ramp_down_period': 5.0, + 'conflowgen_version': '2.1.1', } with unittest.mock.patch.object( From f8f5a74ea546495c2442e538f9a41699d124783c Mon Sep 17 00:00:00 2001 From: Marvin Kastner Date: Tue, 21 May 2024 22:07:03 +0200 Subject: [PATCH 11/54] Addtwo working tests and some todo's --- .../tests/domain_models/test_container.py | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/conflowgen/tests/domain_models/test_container.py b/conflowgen/tests/domain_models/test_container.py index 369f65cd..941dcc3f 100644 --- a/conflowgen/tests/domain_models/test_container.py +++ b/conflowgen/tests/domain_models/test_container.py @@ -7,6 +7,7 @@ from peewee import IntegrityError +from conflowgen.descriptive_datatypes import FlowDirection from conflowgen.domain_models.container import Container, FaultyDataException, NoPickupVehicleException from conflowgen.domain_models.data_types.container_length import ContainerLength from conflowgen.domain_models.data_types.mode_of_transport import ModeOfTransport @@ -150,3 +151,29 @@ def __init__(self, value: int, name: str): with self.assertRaises(NoPickupVehicleException): container.get_departure_time() + + def test_occupied_teu(self): + """Test whether the container size is correctly converted to TEU""" + container = Container.create( + weight=10, + delivered_by=ModeOfTransport.barge, + picked_up_by=ModeOfTransport.truck, + picked_up_by_initial=ModeOfTransport.deep_sea_vessel, + length=ContainerLength.forty_feet, + storage_requirement=StorageRequirement.standard + ) + self.assertEqual(2, container.occupied_teu) + ... # TODO: also test other cases: 20', 45', other + + def test_flow_direction(self): + """Test whether all flow directions are detected correctly""" + container = Container.create( + weight=10, + delivered_by=ModeOfTransport.deep_sea_vessel, + picked_up_by=ModeOfTransport.truck, + picked_up_by_initial=ModeOfTransport.truck, + length=ContainerLength.forty_feet, + storage_requirement=StorageRequirement.standard + ) + self.assertEqual(FlowDirection.import_flow, container.flow_direction) + ... # TODO: also test other cases: export, transshipment, undefined From e6848080863be6a84b7854fe7725babcefa71256 Mon Sep 17 00:00:00 2001 From: Marvin Kastner Date: Tue, 21 May 2024 22:07:31 +0200 Subject: [PATCH 12/54] drop unnecessary save statements --- ...cate_space_for_containers_delivered_by_truck_service.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/conflowgen/tests/flow_generator/test_allocate_space_for_containers_delivered_by_truck_service.py b/conflowgen/tests/flow_generator/test_allocate_space_for_containers_delivered_by_truck_service.py index 4e749554..f5afb334 100644 --- a/conflowgen/tests/flow_generator/test_allocate_space_for_containers_delivered_by_truck_service.py +++ b/conflowgen/tests/flow_generator/test_allocate_space_for_containers_delivered_by_truck_service.py @@ -71,7 +71,6 @@ def _create_feeder(scheduled_arrival: datetime.datetime) -> Feeder: average_vehicle_capacity=300, average_moved_capacity=300, ) - schedule.save() feeder_lsv = LargeScheduledVehicle.create( vehicle_name="TestFeeder1", capacity_in_teu=schedule.average_vehicle_capacity, @@ -79,11 +78,9 @@ def _create_feeder(scheduled_arrival: datetime.datetime) -> Feeder: scheduled_arrival=scheduled_arrival, schedule=schedule ) - feeder_lsv.save() feeder = Feeder.create( large_scheduled_vehicle=feeder_lsv ) - feeder.save() return feeder @staticmethod @@ -96,7 +93,6 @@ def _create_train(scheduled_arrival: datetime.datetime) -> Train: average_vehicle_capacity=90, average_moved_capacity=90, ) - schedule.save() train_lsv = LargeScheduledVehicle.create( vehicle_name="TestTrain1", capacity_in_teu=96, @@ -104,11 +100,9 @@ def _create_train(scheduled_arrival: datetime.datetime) -> Train: scheduled_arrival=scheduled_arrival, schedule=schedule ) - train_lsv.save() train = Train.create( large_scheduled_vehicle=train_lsv ) - train.save() return train @staticmethod @@ -151,7 +145,6 @@ def _create_container_for_large_scheduled_vehicle(vehicle: AbstractLargeSchedule picked_up_by=ModeOfTransport.feeder, picked_up_by_initial=ModeOfTransport.feeder ) - container.save() return container def test_does_nothing_if_no_vehicle_is_available(self): From 5f96ee554c2903093b0dae47b7a34f57131c9fd7 Mon Sep 17 00:00:00 2001 From: Marvin Kastner Date: Tue, 21 May 2024 22:41:59 +0200 Subject: [PATCH 13/54] add test placeholder for transshipment - test behavior here! --- ...e_scheduled_vehicle_for_onward_transportation_manager.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/conflowgen/tests/flow_generator/test_large_scheduled_vehicle_for_onward_transportation_manager.py b/conflowgen/tests/flow_generator/test_large_scheduled_vehicle_for_onward_transportation_manager.py index 55286a7b..4001d950 100644 --- a/conflowgen/tests/flow_generator/test_large_scheduled_vehicle_for_onward_transportation_manager.py +++ b/conflowgen/tests/flow_generator/test_large_scheduled_vehicle_for_onward_transportation_manager.py @@ -316,3 +316,9 @@ def test_nothing_to_do(self): self.manager.schedule_repository, "get_departing_vehicles", return_value=None) as get_vehicles_method: self.manager.choose_departing_vehicle_for_containers() get_vehicles_method.assert_not_called() + + def test_behavior_during_ramp_up_period(self): + ... # TODO! + + def test_behavior_during_ramp_down_period(self): + ... # TODO! From 5f412564887ad5fc6052b1081b677f332160981c Mon Sep 17 00:00:00 2001 From: Marvin Kastner Date: Tue, 21 May 2024 22:42:33 +0200 Subject: [PATCH 14/54] fix free capacity modification --- .../repositories/large_scheduled_vehicle_repository.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/conflowgen/domain_models/repositories/large_scheduled_vehicle_repository.py b/conflowgen/domain_models/repositories/large_scheduled_vehicle_repository.py index fdda11d6..79c0d0ec 100644 --- a/conflowgen/domain_models/repositories/large_scheduled_vehicle_repository.py +++ b/conflowgen/domain_models/repositories/large_scheduled_vehicle_repository.py @@ -190,7 +190,7 @@ def _get_free_capacity_in_teu( ): # keep transshipment containers in the yard longer during the ramp-up period to fill the yard faster # by offering less transport capacity on the outbound journey of deep sea vessels and feeders - free_capacity_in_teu = maximum_capacity * 0.1 + free_capacity_in_teu *= 0.1 elif ( journey_direction == JourneyDirection.inbound @@ -199,7 +199,7 @@ def _get_free_capacity_in_teu( ): # decrease number of inbound containers (any direction) during the ramp-down period # by offering less transport capacity on the inbound journey (all types of vehicles, excluding trucks) - free_capacity_in_teu = maximum_capacity * 0.1 + free_capacity_in_teu *= 0.1 return free_capacity_in_teu From 8f5956a2d2b0acf17a4a1dfc2cc81590baacd780 Mon Sep 17 00:00:00 2001 From: Marvin Kastner Date: Tue, 21 May 2024 22:43:00 +0200 Subject: [PATCH 15/54] add test to check free capacity --- ...test_large_scheduled_vehicle_repository.py | 42 +++++++++++++++++-- 1 file changed, 38 insertions(+), 4 deletions(-) diff --git a/conflowgen/tests/domain_models/reposistories/test_large_scheduled_vehicle_repository.py b/conflowgen/tests/domain_models/reposistories/test_large_scheduled_vehicle_repository.py index 5bf2df3c..9c63530e 100644 --- a/conflowgen/tests/domain_models/reposistories/test_large_scheduled_vehicle_repository.py +++ b/conflowgen/tests/domain_models/reposistories/test_large_scheduled_vehicle_repository.py @@ -38,15 +38,13 @@ def setUp(self) -> None: self.train_lsv = LargeScheduledVehicle.create( vehicle_name="TestTrain1", capacity_in_teu=90, - moved_capacity=3, + moved_capacity=30, scheduled_arrival=datetime.datetime(year=2021, month=8, day=7, hour=13, minute=15), schedule=schedule ) - self.train_lsv.save() self.train = Train.create( large_scheduled_vehicle=self.train_lsv ) - self.train.save() def test_free_capacity_for_one_teu(self): Container.create( @@ -62,7 +60,43 @@ def test_free_capacity_for_one_teu(self): free_capacity_in_teu = self.lsv_repository.get_free_capacity_for_outbound_journey( self.train, FlowDirection.undefined ) - self.assertEqual(free_capacity_in_teu, 2) + self.assertEqual(free_capacity_in_teu, 29) + + def test_free_capacity_during_ramp_down_period_for_one_teu(self): + Container.create( + weight=20, + length=ContainerLength.twenty_feet, + storage_requirement=StorageRequirement.standard, + delivered_by=ModeOfTransport.truck, + picked_up_by=ModeOfTransport.train, + picked_up_by_initial=ModeOfTransport.feeder, + picked_up_by_large_scheduled_vehicle=self.train_lsv, + ) + self.lsv_repository.set_ramp_up_and_down_times( + ramp_up_period_end=datetime.datetime(year=2021, month=8, day=8, hour=13, minute=15) # one day after the train + ) + free_capacity_in_teu = self.lsv_repository.get_free_capacity_for_outbound_journey( + self.train, FlowDirection.transshipment_flow + ) + self.assertAlmostEquals(free_capacity_in_teu, 2.9) # (30 - 1) * 10% + + def test_free_capacity_during_ramp_up_period_for_one_teu(self): + Container.create( + weight=20, + length=ContainerLength.twenty_feet, + storage_requirement=StorageRequirement.standard, + delivered_by=ModeOfTransport.train, + picked_up_by=ModeOfTransport.feeder, + picked_up_by_initial=ModeOfTransport.feeder, + delivered_by_large_scheduled_vehicle=self.train_lsv, + ) + self.lsv_repository.set_ramp_up_and_down_times( + ramp_down_period_start=datetime.datetime(year=2021, month=8, day=6, hour=13, minute=15) # one day before the train + ) + free_capacity_in_teu = self.lsv_repository.get_free_capacity_for_inbound_journey( + self.train + ) + self.assertAlmostEquals(free_capacity_in_teu, 2.9) # (30 - 1) * 10% def test_free_capacity_for_one_ffe(self): Container.create( From 5c5870645c46da1966611ea09490392e8701d3f8 Mon Sep 17 00:00:00 2001 From: Marvin Kastner Date: Tue, 21 May 2024 22:43:59 +0200 Subject: [PATCH 16/54] fix failing test --- .../test_large_scheduled_vehicle_repository.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/conflowgen/tests/domain_models/reposistories/test_large_scheduled_vehicle_repository.py b/conflowgen/tests/domain_models/reposistories/test_large_scheduled_vehicle_repository.py index 9c63530e..97ae7023 100644 --- a/conflowgen/tests/domain_models/reposistories/test_large_scheduled_vehicle_repository.py +++ b/conflowgen/tests/domain_models/reposistories/test_large_scheduled_vehicle_repository.py @@ -78,7 +78,7 @@ def test_free_capacity_during_ramp_down_period_for_one_teu(self): free_capacity_in_teu = self.lsv_repository.get_free_capacity_for_outbound_journey( self.train, FlowDirection.transshipment_flow ) - self.assertAlmostEquals(free_capacity_in_teu, 2.9) # (30 - 1) * 10% + self.assertAlmostEqual(free_capacity_in_teu, 2.9) # (30 - 1) * 10% def test_free_capacity_during_ramp_up_period_for_one_teu(self): Container.create( @@ -96,7 +96,7 @@ def test_free_capacity_during_ramp_up_period_for_one_teu(self): free_capacity_in_teu = self.lsv_repository.get_free_capacity_for_inbound_journey( self.train ) - self.assertAlmostEquals(free_capacity_in_teu, 2.9) # (30 - 1) * 10% + self.assertAlmostEqual(free_capacity_in_teu, 2.9) # (30 - 1) * 10% def test_free_capacity_for_one_ffe(self): Container.create( @@ -128,7 +128,7 @@ def test_free_capacity_for_45_foot_container(self): free_capacity_in_teu = self.lsv_repository.get_free_capacity_for_outbound_journey( self.train, FlowDirection.undefined ) - self.assertEqual(free_capacity_in_teu, 0.75) + self.assertEqual(free_capacity_in_teu, 27.75) def test_free_capacity_for_other_container(self): Container.create( From ba0a3652b7ca7d90ab733d02f7c26edbf0d78327 Mon Sep 17 00:00:00 2001 From: Marvin Kastner Date: Tue, 21 May 2024 22:47:56 +0200 Subject: [PATCH 17/54] add mock test, drop saves --- .../reposistories/test_schedule_repository.py | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/conflowgen/tests/domain_models/reposistories/test_schedule_repository.py b/conflowgen/tests/domain_models/reposistories/test_schedule_repository.py index 234b389d..d9e8469f 100644 --- a/conflowgen/tests/domain_models/reposistories/test_schedule_repository.py +++ b/conflowgen/tests/domain_models/reposistories/test_schedule_repository.py @@ -93,11 +93,9 @@ def test_export_buffer_below_capacity(self): scheduled_arrival=datetime.datetime(year=2021, month=8, day=7, hour=13, minute=15), schedule=schedule ) - train_lsv.save() train = Train.create( large_scheduled_vehicle=train_lsv ) - train.save() vehicles = self.schedule_repository.get_departing_vehicles( start=datetime.datetime(year=2021, month=8, day=5, hour=0, minute=0), @@ -186,11 +184,9 @@ def test_check_used_20_foot_containers(self): scheduled_arrival=datetime.datetime(year=2021, month=8, day=7, hour=13, minute=15), schedule=schedule ) - train_lsv.save() train = Train.create( large_scheduled_vehicle=train_lsv ) - train.save() # This container is already loaded on the train container = Container.create( @@ -230,11 +226,9 @@ def test_check_used_40_foot_containers(self): scheduled_arrival=datetime.datetime(year=2021, month=8, day=7, hour=13, minute=15), schedule=schedule ) - train_lsv.save() - train = Train.create( + Train.create( large_scheduled_vehicle=train_lsv ) - train.save() # This container is already loaded on the train container = Container.create( @@ -273,7 +267,6 @@ def test_use_lsv_repository(self): scheduled_arrival=datetime.datetime(year=2021, month=8, day=7, hour=13, minute=15), schedule=schedule ) - train_lsv.save() train = Train.create( large_scheduled_vehicle=train_lsv ) @@ -301,3 +294,16 @@ def test_use_lsv_repository(self): flow_direction=container.flow_direction ) mock_method.assert_called_once_with(train, FlowDirection.undefined) + + def test_set_ramp_up_and_down_period(self): + with unittest.mock.patch.object( + self.schedule_repository.large_scheduled_vehicle_repository, + 'set_ramp_up_and_down_times', + return_value=None) as mock_method: + self.schedule_repository.set_ramp_up_and_down_times( + datetime.datetime(2023, 1, 1), datetime.datetime(2024, 1, 1) + ) + mock_method.assert_called_once_with( + ramp_up_period_end=datetime.datetime(2023, 1, 1), + ramp_down_period_start=datetime.datetime(2024, 1, 1) + ) From 36a8ac4d5f81c417c69f0b22a1d5bf97f1c33076 Mon Sep 17 00:00:00 2001 From: Marvin Kastner Date: Tue, 21 May 2024 22:48:38 +0200 Subject: [PATCH 18/54] fix test --- .../test_large_scheduled_vehicle_repository.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/conflowgen/tests/domain_models/reposistories/test_large_scheduled_vehicle_repository.py b/conflowgen/tests/domain_models/reposistories/test_large_scheduled_vehicle_repository.py index 97ae7023..7fdf81d4 100644 --- a/conflowgen/tests/domain_models/reposistories/test_large_scheduled_vehicle_repository.py +++ b/conflowgen/tests/domain_models/reposistories/test_large_scheduled_vehicle_repository.py @@ -60,7 +60,7 @@ def test_free_capacity_for_one_teu(self): free_capacity_in_teu = self.lsv_repository.get_free_capacity_for_outbound_journey( self.train, FlowDirection.undefined ) - self.assertEqual(free_capacity_in_teu, 29) + self.assertEqual(free_capacity_in_teu, 29) # 30 - 1 def test_free_capacity_during_ramp_down_period_for_one_teu(self): Container.create( @@ -112,7 +112,7 @@ def test_free_capacity_for_one_ffe(self): free_capacity_in_teu = self.lsv_repository.get_free_capacity_for_outbound_journey( self.train, FlowDirection.undefined ) - self.assertEqual(free_capacity_in_teu, 1) + self.assertEqual(free_capacity_in_teu, 28) # 30 - 2.5 def test_free_capacity_for_45_foot_container(self): Container.create( @@ -128,7 +128,7 @@ def test_free_capacity_for_45_foot_container(self): free_capacity_in_teu = self.lsv_repository.get_free_capacity_for_outbound_journey( self.train, FlowDirection.undefined ) - self.assertEqual(free_capacity_in_teu, 27.75) + self.assertEqual(free_capacity_in_teu, 27.75) # 30 - 2.25 def test_free_capacity_for_other_container(self): Container.create( @@ -144,4 +144,4 @@ def test_free_capacity_for_other_container(self): free_capacity_in_teu = self.lsv_repository.get_free_capacity_for_outbound_journey( self.train, FlowDirection.undefined ) - self.assertEqual(free_capacity_in_teu, 0.5) + self.assertEqual(free_capacity_in_teu, 27.5) # 30 - 2.5 From 253f4408cb2ba0d3fa27e9413bed4313ba6021bd Mon Sep 17 00:00:00 2001 From: Marvin Kastner Date: Tue, 21 May 2024 22:50:04 +0200 Subject: [PATCH 19/54] Modify CTA script and add 10 days of ramp-up and ramp-down --- ..._CTA__with_ramp_up_and_ramp_down_period.py | 329 ++++++++++++++++++ 1 file changed, 329 insertions(+) create mode 100644 examples/Python_Script/demo_DEHAM_CTA__with_ramp_up_and_ramp_down_period.py diff --git a/examples/Python_Script/demo_DEHAM_CTA__with_ramp_up_and_ramp_down_period.py b/examples/Python_Script/demo_DEHAM_CTA__with_ramp_up_and_ramp_down_period.py new file mode 100644 index 00000000..66eec26c --- /dev/null +++ b/examples/Python_Script/demo_DEHAM_CTA__with_ramp_up_and_ramp_down_period.py @@ -0,0 +1,329 @@ +""" +Demo for DEHAM CTA +================== + +This is a demo based on some publicly available figures, some educated guesses, and some random assumptions due to the +lack of information regarding the Container Terminal Altenwerder (CTA) in the port of Hamburg. While this demo only +poorly reflects processes in place, in addition this is only a (poor) snapshot of what has been happening in July +2021. + +No affiliations with the container terminal operators exist. Then why this example was chosen? ConFlowGen is an +extension of the work of Sönke Hartmann [1] and he presented some sample figures. For showing the similarities and +differences, similar assumptions have been made throughout this demo. + +The intention of this script is to provide a demonstration of how ConFlowGen is supposed to be used as a library. +It is, by design, a stateful library that persists all input in an SQL database format to enable reproducibility. +The intention of this demo is further explained in the logs it generated. + + +[1] Hartmann, S.: Generating scenarios for simulation and optimization of container terminal logistics. OR Spectrum, +vol. 26, 171–192 (2004). doi: 10.1007/s00291-003-0150-6 +""" + +import datetime +import os.path +import random +import sys +import pandas as pd + +try: + import conflowgen + install_dir = os.path.abspath( + os.path.join(conflowgen.__file__, os.path.pardir) + ) + print(f"Importing ConFlowGen version {conflowgen.__version__} installed at {install_dir}.") +except ImportError as exc: + print("Please first install ConFlowGen, e.g. with conda or pip") + raise exc + +# The seed of x=1 guarantees that the same traffic data is generated as input data in this script. However, it does not +# affect the container generation or the assignment of containers to vehicles. +seeded_random = random.Random(x=1) + +with_visuals = False +if len(sys.argv) > 2: + if sys.argv[2] == "--with-visuals": + with_visuals = True + +this_dir = os.path.dirname(__file__) + +import_deham_dir = os.path.join( + this_dir, + "data", + "DEHAM", + "CT Altenwerder" +) +df_deep_sea_vessels = pd.read_csv( + os.path.join( + import_deham_dir, + "deep_sea_vessel_input.csv" + ), + index_col=[0] +) +df_feeders = pd.read_csv( + os.path.join( + import_deham_dir, + "feeder_input.csv" + ), + index_col=[0] +) +df_barges = pd.read_csv( + os.path.join( + import_deham_dir, + "barge_input.csv" + ), + index_col=[0] +) +df_trains = pd.read_csv( + os.path.join( + import_deham_dir, + "train_input.csv" + ), + index_col=[0] +) + +# Start logging +logger = conflowgen.setup_logger() +logger.info(__doc__) + +# Pick database +database_chooser = conflowgen.DatabaseChooser( + sqlite_databases_directory=os.path.join(this_dir, "databases") +) +demo_file_name = "demo_deham_cta__with_ramp_up_and_ramp_down_period.sqlite" +database_chooser.create_new_sqlite_database( + demo_file_name, + assume_tas=True, + overwrite=True +) + +# Set settings +container_flow_generation_manager = conflowgen.ContainerFlowGenerationManager() +# Data is available for 01.07.2021 to 31.07.2021 - you can also pick a shorter time period. However, there are some +# artifacts in the generated data in the beginning and in the end of the time range because containers can not continue +# their journey as intended, i.e. they must be delivered by or picked up by a truck since no vehicles that move +# according to a schedule are generated before the start and after the end. +container_flow_start_date = datetime.date(year=2021, month=7, day=1) +container_flow_end_date = datetime.date(year=2021, month=7, day=31) +container_flow_generation_manager.set_properties( + name="Demo DEHAM CTA", + start_date=container_flow_start_date, + end_date=container_flow_end_date, + ramp_up_period=datetime.timedelta(days=10), + ramp_down_period=datetime.timedelta(days=10), +) + +# Set some general assumptions regarding the container properties +container_length_distribution_manager = conflowgen.ContainerLengthDistributionManager() +container_length_distribution_manager.set_container_length_distribution({ + conflowgen.ContainerLength.twenty_feet: 0.33, + conflowgen.ContainerLength.forty_feet: 0.67, + conflowgen.ContainerLength.forty_five_feet: 0, + conflowgen.ContainerLength.other: 0 +}) + +# Add vehicles that frequently visit the terminal. +port_call_manager = conflowgen.PortCallManager() + +logger.info("Start importing feeder vessels...") +for i, row in df_feeders.iterrows(): + feeder_vehicle_name = row["vehicle_name"] + capacity = row["capacity"] + vessel_arrives_at_as_pandas_type = row["arrival (planned)"] + vessel_arrives_at_as_datetime_type = pd.to_datetime(vessel_arrives_at_as_pandas_type) + + if vessel_arrives_at_as_datetime_type.date() < container_flow_start_date: + logger.info(f"Skipping feeder '{feeder_vehicle_name}' because it arrives before the start") + continue + + if vessel_arrives_at_as_datetime_type.date() > container_flow_end_date: + logger.info(f"Skipping feeder '{feeder_vehicle_name}' because it arrives after the end") + continue + + if port_call_manager.has_schedule(feeder_vehicle_name, vehicle_type=conflowgen.ModeOfTransport.feeder): + logger.info(f"Skipping feeder '{feeder_vehicle_name}' because it already exists") + continue + + logger.info(f"Add feeder '{feeder_vehicle_name}' to database") + # The estimate is based on the reported lifts per call in "Tendency toward Mega Containerships and the Constraints + # of Container Terminals" of Park and Suh (2019) J. Mar. Sci. Eng, URL: https://www.mdpi.com/2077-1312/7/5/131/htm + # lifts per call refer to both the inbound and outbound journey of the vessel + moved_capacity = int(round(capacity * seeded_random.triangular(0.3, 0.8) / 2)) # this is only the inbound journey + + # The actual name of the port is not important as it is only used to group containers that are destined for the same + # vessel in the yard for faster loading (less reshuffling). The assumption that the same amount of containers is + # destined for each of the following ports is a simplification. + number_ports = int(round(seeded_random.triangular(low=2, high=4))) + next_ports = [ + (port_name, 1/number_ports) + for port_name in [ + f"port {i + 1} of {feeder_vehicle_name}" + for i in range(number_ports) + ] + ] + + port_call_manager.add_vehicle( + vehicle_type=conflowgen.ModeOfTransport.feeder, + service_name=feeder_vehicle_name, + vehicle_arrives_at=vessel_arrives_at_as_datetime_type.date(), + vehicle_arrives_at_time=vessel_arrives_at_as_datetime_type.time(), + average_vehicle_capacity=capacity, + average_moved_capacity=moved_capacity, + vehicle_arrives_every_k_days=-1, # single arrival, no frequent schedule + next_destinations=next_ports + ) +logger.info("Feeder vessels are imported") + +logger.info("Start importing deep sea vessels...") +for i, row in df_deep_sea_vessels.iterrows(): + deep_sea_vessel_vehicle_name = row["vehicle_name"] + capacity = row["capacity"] + vessel_arrives_at_as_pandas_type = row["arrival (planned)"] + vessel_arrives_at_as_datetime_type = pd.to_datetime(vessel_arrives_at_as_pandas_type) + + if vessel_arrives_at_as_datetime_type.date() < container_flow_start_date: + logger.info(f"Skipping deep sea vessel '{deep_sea_vessel_vehicle_name}' because it arrives before the start") + continue + + if vessel_arrives_at_as_datetime_type.date() > container_flow_end_date: + logger.info(f"Skipping deep sea vessel '{deep_sea_vessel_vehicle_name}' because it arrives after the end") + continue + + if port_call_manager.has_schedule(deep_sea_vessel_vehicle_name, + vehicle_type=conflowgen.ModeOfTransport.deep_sea_vessel): + logger.info(f"Skipping deep sea service '{deep_sea_vessel_vehicle_name}' because it already exists") + continue + + logger.info(f"Add deep sea vessel '{deep_sea_vessel_vehicle_name}' to database") + # The estimate is based on the reported lifts per call in "Tendency toward Mega Containerships and the Constraints + # of Container Terminals" of Park and Suh (2019) J. Mar. Sci. Eng, URL: https://www.mdpi.com/2077-1312/7/5/131/htm + # lifts per call refer to both the inbound and outbound journey of the vessel + moved_capacity = int(round(capacity * seeded_random.triangular(0.3, 0.6) / 2)) # this is only the inbound journey + + # The actual name of the port is not important as it is only used to group containers that are destined for the same + # vessel in the yard for faster loading (less reshuffling). The assumption that the same amount of containers is + # destined for each of the following ports is a simplification + number_ports = int(round(seeded_random.triangular(low=3, high=15, mode=7))) + next_ports = [ + (port_name, 1/number_ports) + for port_name in [ + f"port {i + 1} of {deep_sea_vessel_vehicle_name}" + for i in range(number_ports) + ] + ] + + port_call_manager.add_vehicle( + vehicle_type=conflowgen.ModeOfTransport.deep_sea_vessel, + service_name=deep_sea_vessel_vehicle_name, + vehicle_arrives_at=vessel_arrives_at_as_datetime_type.date(), + vehicle_arrives_at_time=vessel_arrives_at_as_datetime_type.time(), + average_vehicle_capacity=capacity, + average_moved_capacity=moved_capacity, + vehicle_arrives_every_k_days=-1, # single arrival, no frequent schedule + next_destinations=next_ports + ) +logger.info("Deep sea vessels are imported") + +logger.info("Start importing barges...") +for i, row in df_barges.iterrows(): + barge_vehicle_name = row["vehicle_name"] + capacity = row["capacity"] + vessel_arrives_at_as_pandas_type = row["arrival (planned)"] + vessel_arrives_at_as_datetime_type = pd.to_datetime(vessel_arrives_at_as_pandas_type) + + if vessel_arrives_at_as_datetime_type.date() < container_flow_start_date: + logger.info(f"Skipping barge '{barge_vehicle_name}' because it arrives before the start") + continue + + if vessel_arrives_at_as_datetime_type.date() > container_flow_end_date: + logger.info(f"Skipping barge '{barge_vehicle_name}' because it arrives after the end") + continue + + if port_call_manager.has_schedule(barge_vehicle_name, vehicle_type=conflowgen.ModeOfTransport.barge): + logger.info(f"Skipping barge '{barge_vehicle_name}' because it already exists") + continue + + logger.info(f"Add barge '{barge_vehicle_name}' to database") + # assume that the barge approaches 2 or more terminals, thus not the whole barge is available for CTA + moved_capacity = int(round(capacity * seeded_random.uniform(0.3, 0.6))) + port_call_manager.add_vehicle( + vehicle_type=conflowgen.ModeOfTransport.barge, + service_name=barge_vehicle_name, + vehicle_arrives_at=vessel_arrives_at_as_datetime_type.date(), + vehicle_arrives_at_time=vessel_arrives_at_as_datetime_type.time(), + average_vehicle_capacity=capacity, + average_moved_capacity=moved_capacity, + vehicle_arrives_every_k_days=-1 # single arrival, no frequent schedule + ) +logger.info("Barges are imported") + +logger.info("Start importing trains...") +for i, row in df_trains.iterrows(): + train_vehicle_name = row["vehicle_name"] + vessel_arrives_at_as_pandas_type = row["arrival_day"] + vessel_arrives_at_as_datetime_type = pd.to_datetime(vessel_arrives_at_as_pandas_type) + + if port_call_manager.has_schedule(train_vehicle_name, vehicle_type=conflowgen.ModeOfTransport.train): + logger.info(f"Train service '{train_vehicle_name}' already exists") + continue + + capacity = 96 # in TEU, see https://www.intermodal-info.com/verkehrstraeger/ + earliest_time = datetime.time(hour=1, minute=0) + earliest_time_as_delta = datetime.timedelta(hours=earliest_time.hour, minutes=earliest_time.minute) + latest_time = datetime.time(hour=5, minute=30) + latest_time_as_delta = datetime.timedelta(hours=latest_time.hour, minutes=latest_time.minute) + number_slots_minus_one = int((latest_time_as_delta - earliest_time_as_delta) / datetime.timedelta(minutes=30)) + + drawn_slot = seeded_random.randint(0, number_slots_minus_one) + vehicle_arrives_at_time_as_delta = earliest_time_as_delta + datetime.timedelta(hours=0.5 * drawn_slot) + vehicle_arrives_at_time = (datetime.datetime.min + vehicle_arrives_at_time_as_delta).time() + logger.info(f"Add train '{train_vehicle_name}' to database") + port_call_manager.add_vehicle( + vehicle_type=conflowgen.ModeOfTransport.train, + service_name=train_vehicle_name, + vehicle_arrives_at=vessel_arrives_at_as_datetime_type.date(), + vehicle_arrives_at_time=vehicle_arrives_at_time, + average_vehicle_capacity=capacity, + average_moved_capacity=capacity, # discharge everything + vehicle_arrives_every_k_days=7 # weekly arrival + ) + +logger.info("All trains are imported") + +logger.info("All vehicles have been imported") + +### +# Now, all schedules and input distributions are set up - no further inputs are required +### + +logger.info("Preview the results with some light-weight approaches.") + +conflowgen.run_all_previews( + as_graph=with_visuals +) + +logger.info("Generate all fleets with all vehicles. This is the core of the whole project.") +container_flow_generation_manager.generate() + +logger.info("The container flow data have been generated, run analyses on them.") + +conflowgen.run_all_analyses( + as_graph=with_visuals +) + +logger.info("For a better understanding of the data, it is advised to study the logs and compare the preview with the " + "analysis results.") + +logger.info("Start data export...") + +# Export important entries from SQL to CSV so that it can be further processed, e.g., by a simulation software +export_container_flow_manager = conflowgen.ExportContainerFlowManager() +export_container_flow_manager.export( + folder_name="demo-DEHAM-of-day--" + str(datetime.datetime.now()).replace(":", "-").replace(" ", "--").split(".")[0], + path_to_export_folder=os.path.join(this_dir, "export"), + file_format=conflowgen.ExportFileFormat.csv +) + +# Gracefully close everything +database_chooser.close_current_connection() +logger.info("Demo 'demo_DEHAM_CTA' finished successfully.") From 0b9ce1064d514549d3c0df58b969867a311014e1 Mon Sep 17 00:00:00 2001 From: Marvin Kastner Date: Tue, 21 May 2024 22:54:08 +0200 Subject: [PATCH 20/54] fix linting issues --- .../test_large_scheduled_vehicle_repository.py | 6 ++++-- .../domain_models/reposistories/test_schedule_repository.py | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/conflowgen/tests/domain_models/reposistories/test_large_scheduled_vehicle_repository.py b/conflowgen/tests/domain_models/reposistories/test_large_scheduled_vehicle_repository.py index 7fdf81d4..afdc411f 100644 --- a/conflowgen/tests/domain_models/reposistories/test_large_scheduled_vehicle_repository.py +++ b/conflowgen/tests/domain_models/reposistories/test_large_scheduled_vehicle_repository.py @@ -73,7 +73,8 @@ def test_free_capacity_during_ramp_down_period_for_one_teu(self): picked_up_by_large_scheduled_vehicle=self.train_lsv, ) self.lsv_repository.set_ramp_up_and_down_times( - ramp_up_period_end=datetime.datetime(year=2021, month=8, day=8, hour=13, minute=15) # one day after the train + # one day after the train + ramp_up_period_end=datetime.datetime(year=2021, month=8, day=8, hour=13, minute=15) ) free_capacity_in_teu = self.lsv_repository.get_free_capacity_for_outbound_journey( self.train, FlowDirection.transshipment_flow @@ -91,7 +92,8 @@ def test_free_capacity_during_ramp_up_period_for_one_teu(self): delivered_by_large_scheduled_vehicle=self.train_lsv, ) self.lsv_repository.set_ramp_up_and_down_times( - ramp_down_period_start=datetime.datetime(year=2021, month=8, day=6, hour=13, minute=15) # one day before the train + # one day before the train + ramp_down_period_start=datetime.datetime(year=2021, month=8, day=6, hour=13, minute=15) ) free_capacity_in_teu = self.lsv_repository.get_free_capacity_for_inbound_journey( self.train diff --git a/conflowgen/tests/domain_models/reposistories/test_schedule_repository.py b/conflowgen/tests/domain_models/reposistories/test_schedule_repository.py index d9e8469f..15629c6d 100644 --- a/conflowgen/tests/domain_models/reposistories/test_schedule_repository.py +++ b/conflowgen/tests/domain_models/reposistories/test_schedule_repository.py @@ -93,7 +93,7 @@ def test_export_buffer_below_capacity(self): scheduled_arrival=datetime.datetime(year=2021, month=8, day=7, hour=13, minute=15), schedule=schedule ) - train = Train.create( + Train.create( large_scheduled_vehicle=train_lsv ) From d9e31f4b3fa20bb167adcdc21b2e53d7464120b6 Mon Sep 17 00:00:00 2001 From: Marvin Kastner Date: Thu, 23 May 2024 14:23:37 +0200 Subject: [PATCH 21/54] show difference of demo script in folder name --- .../demo_DEHAM_CTA__with_ramp_up_and_ramp_down_period.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/examples/Python_Script/demo_DEHAM_CTA__with_ramp_up_and_ramp_down_period.py b/examples/Python_Script/demo_DEHAM_CTA__with_ramp_up_and_ramp_down_period.py index 66eec26c..322413c6 100644 --- a/examples/Python_Script/demo_DEHAM_CTA__with_ramp_up_and_ramp_down_period.py +++ b/examples/Python_Script/demo_DEHAM_CTA__with_ramp_up_and_ramp_down_period.py @@ -319,7 +319,10 @@ # Export important entries from SQL to CSV so that it can be further processed, e.g., by a simulation software export_container_flow_manager = conflowgen.ExportContainerFlowManager() export_container_flow_manager.export( - folder_name="demo-DEHAM-of-day--" + str(datetime.datetime.now()).replace(":", "-").replace(" ", "--").split(".")[0], + folder_name=( + "demo-DEHAM-inc-ramp-up-and-down-of-day--" + + str(datetime.datetime.now()).replace(":", "-").replace(" ", "--").split(".")[0] + ), path_to_export_folder=os.path.join(this_dir, "export"), file_format=conflowgen.ExportFileFormat.csv ) From bff40ac21b60de1a42bff549e3c7794ed2cd84df Mon Sep 17 00:00:00 2001 From: Marvin Kastner Date: Thu, 23 May 2024 14:24:46 +0200 Subject: [PATCH 22/54] adjust last message --- .../demo_DEHAM_CTA__with_ramp_up_and_ramp_down_period.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/Python_Script/demo_DEHAM_CTA__with_ramp_up_and_ramp_down_period.py b/examples/Python_Script/demo_DEHAM_CTA__with_ramp_up_and_ramp_down_period.py index 322413c6..fcd5dd96 100644 --- a/examples/Python_Script/demo_DEHAM_CTA__with_ramp_up_and_ramp_down_period.py +++ b/examples/Python_Script/demo_DEHAM_CTA__with_ramp_up_and_ramp_down_period.py @@ -329,4 +329,4 @@ # Gracefully close everything database_chooser.close_current_connection() -logger.info("Demo 'demo_DEHAM_CTA' finished successfully.") +logger.info("Demo 'demo_DEHAM_CTA_with_ramp_up_and_down_period' finished successfully.") From 24406621726261e73ec1649a816a7e4816e73172 Mon Sep 17 00:00:00 2001 From: Marvin Kastner Date: Thu, 23 May 2024 14:27:16 +0200 Subject: [PATCH 23/54] ...again --- .../demo_DEHAM_CTA__with_ramp_up_and_ramp_down_period.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/Python_Script/demo_DEHAM_CTA__with_ramp_up_and_ramp_down_period.py b/examples/Python_Script/demo_DEHAM_CTA__with_ramp_up_and_ramp_down_period.py index fcd5dd96..1fc52836 100644 --- a/examples/Python_Script/demo_DEHAM_CTA__with_ramp_up_and_ramp_down_period.py +++ b/examples/Python_Script/demo_DEHAM_CTA__with_ramp_up_and_ramp_down_period.py @@ -1,6 +1,6 @@ """ -Demo for DEHAM CTA -================== +Demo for DEHAM CTA with ramp-up and ramp-down period +==================================================== This is a demo based on some publicly available figures, some educated guesses, and some random assumptions due to the lack of information regarding the Container Terminal Altenwerder (CTA) in the port of Hamburg. While this demo only From 78a558b1b07aafea8e2e2be13ff93ce04112c6f1 Mon Sep 17 00:00:00 2001 From: Marvin Kastner Date: Thu, 23 May 2024 20:53:38 +0200 Subject: [PATCH 24/54] pass around ramp-up and ramp-down periods --- .../factories/container_factory.py | 12 ++++ .../container_flow_generation_service.py | 11 ++- ...arge_scheduled_vehicle_creation_service.py | 11 ++- ...hicle_for_onward_transportation_manager.py | 4 +- ...ory__create_for_large_scheduled_vehicle.py | 69 ++++++++++++++++++- ...hicle_for_onward_transportation_manager.py | 23 ++++++- .../analyses_with_missing_data.ipynb | 2 +- 7 files changed, 122 insertions(+), 10 deletions(-) diff --git a/conflowgen/domain_models/factories/container_factory.py b/conflowgen/domain_models/factories/container_factory.py index bd3aab8a..01e6b424 100644 --- a/conflowgen/domain_models/factories/container_factory.py +++ b/conflowgen/domain_models/factories/container_factory.py @@ -1,6 +1,8 @@ from __future__ import annotations +import datetime import math +import typing from typing import Dict, MutableSequence, Sequence, Type from conflowgen.domain_models.container import Container @@ -36,6 +38,16 @@ def __init__(self): self.storage_requirement_distribution: dict[ContainerLength, dict[StorageRequirement, float]] | None = None self.large_scheduled_vehicle_repository = LargeScheduledVehicleRepository() + def set_ramp_up_and_down_times( + self, + ramp_up_period_end: typing.Optional[datetime.datetime], + ramp_down_period_start: typing.Optional[datetime.datetime], + ) -> None: + self.large_scheduled_vehicle_repository.set_ramp_up_and_down_times( + ramp_up_period_end=ramp_up_period_end, + ramp_down_period_start=ramp_down_period_start + ) + def reload_distributions(self): """The user might change the distributions at any time, so reload them at a meaningful point of time!""" self.mode_of_transportation_distribution = ModeOfTransportDistributionRepository.get_distribution() diff --git a/conflowgen/flow_generator/container_flow_generation_service.py b/conflowgen/flow_generator/container_flow_generation_service.py index acd5b4e9..bd7d33f2 100644 --- a/conflowgen/flow_generator/container_flow_generation_service.py +++ b/conflowgen/flow_generator/container_flow_generation_service.py @@ -53,17 +53,24 @@ def _update_generation_properties_and_distributions(self): assert -1 < self.transportation_buffer self.large_scheduled_vehicle_for_onward_transportation_manager.reload_properties( - transportation_buffer=self.transportation_buffer + transportation_buffer=self.transportation_buffer, + ramp_up_period_end=self.ramp_up_period_end, + ramp_down_period_start=self.ramp_down_period_start, ) self.allocate_space_for_containers_delivered_by_truck_service.reload_distribution( transportation_buffer=self.transportation_buffer ) + self.truck_for_import_containers_manager.reload_distributions() self.truck_for_export_containers_manager.reload_distributions() + self.large_scheduled_vehicle_creation_service.reload_properties( container_flow_start_date=self.container_flow_start_date, - container_flow_end_date=self.container_flow_end_date + container_flow_end_date=self.container_flow_end_date, + ramp_up_period_end=self.ramp_up_period_end, + ramp_down_period_start=self.ramp_down_period_start, ) + self.assign_destination_to_container_service.reload_distributions() @staticmethod diff --git a/conflowgen/flow_generator/large_scheduled_vehicle_creation_service.py b/conflowgen/flow_generator/large_scheduled_vehicle_creation_service.py index 170d6a0b..62545780 100644 --- a/conflowgen/flow_generator/large_scheduled_vehicle_creation_service.py +++ b/conflowgen/flow_generator/large_scheduled_vehicle_creation_service.py @@ -1,4 +1,7 @@ +from __future__ import annotations + import datetime +import typing from typing import List, Type import logging @@ -29,11 +32,17 @@ def __init__(self): def reload_properties( self, container_flow_start_date: datetime.date, - container_flow_end_date: datetime.date + container_flow_end_date: datetime.date, + ramp_up_period_end: typing.Optional[datetime.datetime | datetime.date], + ramp_down_period_start: typing.Optional[datetime.datetime | datetime.date], ): assert container_flow_start_date < container_flow_end_date self.container_flow_start_date = container_flow_start_date self.container_flow_end_date = container_flow_end_date + self.container_factory.set_ramp_up_and_down_times( + ramp_up_period_end=ramp_up_period_end, + ramp_down_period_start=ramp_down_period_start + ) self.container_factory.reload_distributions() def create(self) -> None: diff --git a/conflowgen/flow_generator/large_scheduled_vehicle_for_onward_transportation_manager.py b/conflowgen/flow_generator/large_scheduled_vehicle_for_onward_transportation_manager.py index e03025bd..03cc0a98 100644 --- a/conflowgen/flow_generator/large_scheduled_vehicle_for_onward_transportation_manager.py +++ b/conflowgen/flow_generator/large_scheduled_vehicle_for_onward_transportation_manager.py @@ -43,8 +43,8 @@ def __init__(self): def reload_properties( self, transportation_buffer: float, - ramp_up_period_end: Optional[datetime.datetime] = None, - ramp_down_period_start: Optional[datetime.datetime] = None, + ramp_up_period_end: Optional[datetime.datetime | datetime.date] = None, + ramp_down_period_start: Optional[datetime.datetime | datetime.date] = None, ): assert -1 < transportation_buffer self.schedule_repository.set_transportation_buffer(transportation_buffer) diff --git a/conflowgen/tests/domain_models/factories/test_container_factory__create_for_large_scheduled_vehicle.py b/conflowgen/tests/domain_models/factories/test_container_factory__create_for_large_scheduled_vehicle.py index ccd84951..bf8410a8 100644 --- a/conflowgen/tests/domain_models/factories/test_container_factory__create_for_large_scheduled_vehicle.py +++ b/conflowgen/tests/domain_models/factories/test_container_factory__create_for_large_scheduled_vehicle.py @@ -14,7 +14,7 @@ from conflowgen.domain_models.factories.fleet_factory import FleetFactory from conflowgen.domain_models.data_types.mode_of_transport import ModeOfTransport from conflowgen.domain_models.large_vehicle_schedule import Destination -from conflowgen.domain_models.vehicle import Feeder, LargeScheduledVehicle, Schedule, Truck +from conflowgen.domain_models.vehicle import Feeder, LargeScheduledVehicle, Schedule, Truck, DeepSeaVessel from conflowgen.tests.substitute_peewee_database import setup_sqlite_in_memory_db @@ -25,6 +25,7 @@ def setUp(self) -> None: sqlite_db = setup_sqlite_in_memory_db() sqlite_db.create_tables([ Feeder, + DeepSeaVessel, LargeScheduledVehicle, Schedule, Container, @@ -81,3 +82,69 @@ def test_create_containers_for_feeder_vessel(self) -> None: feeder_1.large_scheduled_vehicle ) self.assertIsNone(containers[0].delivered_by_truck) + + def test_create_containers_for_single_deep_sea_vessel_during_ramp_up_period(self): + schedule = Schedule.create( + service_name="SunExpress", + vehicle_type=ModeOfTransport.deep_sea_vessel, + vehicle_arrives_at=datetime.date(2021, 7, 9), + vehicle_arrives_at_time=datetime.time(11), + average_vehicle_capacity=24000, + average_moved_capacity=3000 + ) + vessels = FleetFactory().create_deep_sea_vessel_fleet( + schedule=schedule, + first_at=datetime.date(2021, 7, 8), + latest_at=datetime.date(2021, 7, 10) + ) + self.assertEqual( + len(vessels), + 1 + ) + vessel = vessels[0] + + self.container_factory.set_ramp_up_and_down_times( + ramp_up_period_end=datetime.datetime(2021, 7, 10), + ramp_down_period_start=None + ) + + # noinspection PyTypeChecker + containers = self.container_factory.create_containers_for_large_scheduled_vehicle(vessel) + + container_volume = sum([c.occupied_teu for c in containers]) + + self.assertGreater(container_volume, 2900, "A bit less than 3000 is acceptable but common!") + self.assertLess(container_volume, 3100, "A bit more than 3000 is acceptable but common!") + + def test_create_containers_for_single_deep_sea_vessel_during_ramp_down_period(self): + schedule = Schedule.create( + service_name="SunExpress", + vehicle_type=ModeOfTransport.deep_sea_vessel, + vehicle_arrives_at=datetime.date(2021, 7, 9), + vehicle_arrives_at_time=datetime.time(11), + average_vehicle_capacity=24000, + average_moved_capacity=3000 + ) + vessels = FleetFactory().create_deep_sea_vessel_fleet( + schedule=schedule, + first_at=datetime.date(2021, 7, 8), + latest_at=datetime.date(2021, 7, 10) + ) + self.assertEqual( + len(vessels), + 1 + ) + vessel = vessels[0] + + self.container_factory.set_ramp_up_and_down_times( + ramp_up_period_end=None, + ramp_down_period_start=datetime.datetime(2021, 7, 8) + ) + + # noinspection PyTypeChecker + containers = self.container_factory.create_containers_for_large_scheduled_vehicle(vessel) + + container_volume = sum([c.occupied_teu for c in containers]) + + self.assertGreater(container_volume, 290, "A bit less than 3000 is acceptable but common!") + self.assertLess(container_volume, 310, "A bit more than 3000 is acceptable but common!") diff --git a/conflowgen/tests/flow_generator/test_large_scheduled_vehicle_for_onward_transportation_manager.py b/conflowgen/tests/flow_generator/test_large_scheduled_vehicle_for_onward_transportation_manager.py index 4001d950..54950805 100644 --- a/conflowgen/tests/flow_generator/test_large_scheduled_vehicle_for_onward_transportation_manager.py +++ b/conflowgen/tests/flow_generator/test_large_scheduled_vehicle_for_onward_transportation_manager.py @@ -70,7 +70,6 @@ def _create_feeder( average_vehicle_capacity=300, average_moved_capacity=300, ) - schedule.save() feeder_lsv = LargeScheduledVehicle.create( vehicle_name="TestFeeder1", capacity_in_teu=schedule.average_vehicle_capacity, @@ -82,7 +81,6 @@ def _create_feeder( feeder = Feeder.create( large_scheduled_vehicle=feeder_lsv ) - feeder.save() return feeder @staticmethod @@ -318,7 +316,26 @@ def test_nothing_to_do(self): get_vehicles_method.assert_not_called() def test_behavior_during_ramp_up_period(self): - ... # TODO! + feeder = self._create_feeder(datetime.datetime(year=2022, month=8, day=7, hour=13, minute=15)) + feeder.large_scheduled_vehicle.moved_capacity = 100 # in TEU + feeder.save() + self.manager.reload_properties( + transportation_buffer=0, + ramp_up_period_end=datetime.date(2022, 8, 8) + ) + + # run actual function + self.manager.choose_departing_vehicle_for_containers() + + containers_reloaded: Iterable[Container] = Container.select().where( + Container.picked_up_by_large_scheduled_vehicle == feeder + ) + teu_loaded = 0 + for container in containers_reloaded: # pylint: disable=not-an-iterable + self.assertEqual(container.picked_up_by_large_scheduled_vehicle, feeder.large_scheduled_vehicle) + teu_loaded += ContainerLength.get_teu_factor(container.length) + self.assertLessEqual(teu_loaded, 10, "Feeder must have loaded much less containers because this is the" + "ramp-up period!") def test_behavior_during_ramp_down_period(self): ... # TODO! diff --git a/conflowgen/tests/notebooks/analyses_with_missing_data.ipynb b/conflowgen/tests/notebooks/analyses_with_missing_data.ipynb index 8bf2826e..4c03414a 100644 --- a/conflowgen/tests/notebooks/analyses_with_missing_data.ipynb +++ b/conflowgen/tests/notebooks/analyses_with_missing_data.ipynb @@ -197,7 +197,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.1" + "version": "3.9.7" } }, "nbformat": 4, From 30760e5a82f8624168cee12aca3ceff7bbb323fc Mon Sep 17 00:00:00 2001 From: Marvin Kastner Date: Thu, 23 May 2024 21:59:08 +0200 Subject: [PATCH 25/54] Find two nice names for the scaling factor of 0.1 / 10%, work with factored and unfactored capacity: always factored for the inbound part and factored-on-demand for the outbound part (only transshipment, only during ramp-up, only if ramp-up exists) --- .../large_scheduled_vehicle_repository.py | 53 +++++++++++++++---- 1 file changed, 42 insertions(+), 11 deletions(-) diff --git a/conflowgen/domain_models/repositories/large_scheduled_vehicle_repository.py b/conflowgen/domain_models/repositories/large_scheduled_vehicle_repository.py index 79c0d0ec..012dfc25 100644 --- a/conflowgen/domain_models/repositories/large_scheduled_vehicle_repository.py +++ b/conflowgen/domain_models/repositories/large_scheduled_vehicle_repository.py @@ -20,6 +20,10 @@ class LargeScheduledVehicleRepository: ignored_capacity = ContainerLength.get_teu_factor(ContainerLength.other) + downscale_factor_during_ramp_up_for_outbound_transshipment = 0.1 + + downscale_factor_during_ramp_down_for_inbound_all_kinds = 0.1 + def __init__(self): self.transportation_buffer = None self.ramp_up_period_end = None @@ -96,35 +100,52 @@ def block_capacity_for_outbound_journey( # noinspection PyTypeChecker def get_free_capacity_for_inbound_journey(self, vehicle: Type[AbstractLargeScheduledVehicle]) -> float: - """Get the free capacity for the inbound journey on a vehicle that moves according to a schedule in TEU. + """ + Get the free capacity for the inbound journey on a vehicle that moves according to a schedule in TEU. + During the ramp-down period (if existent), all inbound traffic is scaled down, no matter what. """ if vehicle in self.free_capacity_for_inbound_journey_buffer: return self.free_capacity_for_inbound_journey_buffer[vehicle] large_scheduled_vehicle: LargeScheduledVehicle = vehicle.large_scheduled_vehicle total_moved_capacity_for_inbound_transportation_in_teu = large_scheduled_vehicle.moved_capacity - free_capacity_in_teu = self._get_free_capacity_in_teu( + factored_free_capacity_in_teu, free_capacity_in_teu = self._get_free_capacity_in_teu( vehicle=vehicle, maximum_capacity=total_moved_capacity_for_inbound_transportation_in_teu, container_counter=self._get_number_containers_for_inbound_journey, journey_direction=JourneyDirection.inbound, flow_direction=FlowDirection.undefined ) - self.free_capacity_for_inbound_journey_buffer[vehicle] = free_capacity_in_teu - return free_capacity_in_teu + self.free_capacity_for_inbound_journey_buffer[vehicle] = factored_free_capacity_in_teu + return factored_free_capacity_in_teu def get_free_capacity_for_outbound_journey( self, vehicle: Type[AbstractLargeScheduledVehicle], flow_direction: FlowDirection ) -> float: - """Get the free capacity for the outbound journey on a vehicle that moves according to a schedule in TEU. + """ + Get the free capacity for the outbound journey on a vehicle that moves according to a schedule in TEU. + During the ramp-up period (if existent), all outbound traffic that constitutes transshipment, is scaled down. """ assert self.transportation_buffer is not None, "First set the value!" assert -1 < self.transportation_buffer, "Must be larger than -1" if vehicle in self.free_capacity_for_outbound_journey_buffer: + if flow_direction == FlowDirection.transshipment_flow and self.ramp_up_period_end is not None: + + # noinspection PyUnresolvedReferences + arrival_time: datetime.datetime = vehicle.large_scheduled_vehicle.scheduled_arrival + + if arrival_time < self.ramp_up_period_end: + return ( # factored capacity + self.free_capacity_for_outbound_journey_buffer[vehicle] + * self.downscale_factor_during_ramp_up_for_outbound_transshipment + ) + # capacity without factor return self.free_capacity_for_outbound_journey_buffer[vehicle] + # if not yet buffered: + # noinspection PyTypeChecker large_scheduled_vehicle: LargeScheduledVehicle = vehicle.large_scheduled_vehicle @@ -136,15 +157,19 @@ def get_free_capacity_for_outbound_journey( maximum_capacity_of_vehicle ) - free_capacity_in_teu = self._get_free_capacity_in_teu( + factored_free_capacity_in_teu, free_capacity_in_teu = self._get_free_capacity_in_teu( vehicle=vehicle, maximum_capacity=total_moved_capacity_for_onward_transportation_in_teu, container_counter=self._get_number_containers_for_outbound_journey, journey_direction=JourneyDirection.outbound, flow_direction=flow_direction ) + + # always cache the free capacity without a factor, as it only applies in some situations self.free_capacity_for_outbound_journey_buffer[vehicle] = free_capacity_in_teu - return free_capacity_in_teu + + # always report the factored capacity to the user of this class + return factored_free_capacity_in_teu def _get_free_capacity_in_teu( self, @@ -153,7 +178,7 @@ def _get_free_capacity_in_teu( container_counter: Callable[[Type[AbstractLargeScheduledVehicle], ContainerLength], int], journey_direction: JourneyDirection, flow_direction: FlowDirection - ) -> float: + ) -> (float, float): loaded_20_foot_containers = container_counter(vehicle, ContainerLength.twenty_feet) loaded_40_foot_containers = container_counter(vehicle, ContainerLength.forty_feet) loaded_45_foot_containers = container_counter(vehicle, ContainerLength.forty_five_feet) @@ -182,6 +207,8 @@ def _get_free_capacity_in_teu( # noinspection PyUnresolvedReferences arrival_time: datetime.datetime = vehicle.large_scheduled_vehicle.scheduled_arrival + factored_free_capacity_in_teu = free_capacity_in_teu + if ( journey_direction == JourneyDirection.outbound and flow_direction == FlowDirection.transshipment_flow @@ -190,7 +217,9 @@ def _get_free_capacity_in_teu( ): # keep transshipment containers in the yard longer during the ramp-up period to fill the yard faster # by offering less transport capacity on the outbound journey of deep sea vessels and feeders - free_capacity_in_teu *= 0.1 + factored_free_capacity_in_teu = ( + free_capacity_in_teu * self.downscale_factor_during_ramp_up_for_outbound_transshipment + ) elif ( journey_direction == JourneyDirection.inbound @@ -199,9 +228,11 @@ def _get_free_capacity_in_teu( ): # decrease number of inbound containers (any direction) during the ramp-down period # by offering less transport capacity on the inbound journey (all types of vehicles, excluding trucks) - free_capacity_in_teu *= 0.1 + factored_free_capacity_in_teu = ( + free_capacity_in_teu * self.downscale_factor_during_ramp_down_for_inbound_all_kinds + ) - return free_capacity_in_teu + return factored_free_capacity_in_teu, free_capacity_in_teu @classmethod def _get_number_containers_for_outbound_journey( From 25c142719da3770a6d64cb3caa91f3b94ae89e7a Mon Sep 17 00:00:00 2001 From: Marvin Kastner Date: Thu, 23 May 2024 21:59:40 +0200 Subject: [PATCH 26/54] Use datetime for ramp-up calculations --- .../flow_generator/container_flow_generation_service.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/conflowgen/flow_generator/container_flow_generation_service.py b/conflowgen/flow_generator/container_flow_generation_service.py index bd7d33f2..7f695bf0 100644 --- a/conflowgen/flow_generator/container_flow_generation_service.py +++ b/conflowgen/flow_generator/container_flow_generation_service.py @@ -45,8 +45,12 @@ def _update_generation_properties_and_distributions(self): ramp_up_period = container_flow_generation_properties.ramp_up_period ramp_down_period = container_flow_generation_properties.ramp_down_period - self.ramp_up_period_end = self.container_flow_start_date + datetime.timedelta(days=ramp_up_period) - self.ramp_down_period_start = self.container_flow_end_date - datetime.timedelta(days=ramp_down_period) + self.ramp_up_period_end = datetime.datetime.combine( + self.container_flow_start_date, datetime.time(hour=0, minute=0, second=0) + ) + datetime.timedelta(days=ramp_up_period) + self.ramp_down_period_start = datetime.datetime.combine( + self.container_flow_end_date, datetime.time(hour=0, minute=0, second=0) + ) - datetime.timedelta(days=ramp_down_period) assert self.ramp_up_period_end <= self.ramp_down_period_start self.transportation_buffer: float = container_flow_generation_properties.transportation_buffer From 5dbb4bd0a29660b27876ddcf552a1d0a8feeda15 Mon Sep 17 00:00:00 2001 From: Marvin Kastner Date: Thu, 23 May 2024 22:01:05 +0200 Subject: [PATCH 27/54] Add simple test --- ...hicle_for_onward_transportation_manager.py | 35 ++++++++++++++----- 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/conflowgen/tests/flow_generator/test_large_scheduled_vehicle_for_onward_transportation_manager.py b/conflowgen/tests/flow_generator/test_large_scheduled_vehicle_for_onward_transportation_manager.py index 54950805..41c853b9 100644 --- a/conflowgen/tests/flow_generator/test_large_scheduled_vehicle_for_onward_transportation_manager.py +++ b/conflowgen/tests/flow_generator/test_large_scheduled_vehicle_for_onward_transportation_manager.py @@ -77,7 +77,6 @@ def _create_feeder( scheduled_arrival=scheduled_arrival, schedule=schedule ) - feeder_lsv.save() feeder = Feeder.create( large_scheduled_vehicle=feeder_lsv ) @@ -316,26 +315,46 @@ def test_nothing_to_do(self): get_vehicles_method.assert_not_called() def test_behavior_during_ramp_up_period(self): - feeder = self._create_feeder(datetime.datetime(year=2022, month=8, day=7, hour=13, minute=15)) - feeder.large_scheduled_vehicle.moved_capacity = 100 # in TEU - feeder.save() + """During ramp-up, the capacity of vessels is artificially reduced""" + + # the + feeder_1 = self._create_feeder( + datetime.datetime(year=2022, month=8, day=7, hour=13, minute=15), "1" + ) + + feeder_2 = self._create_feeder( + datetime.datetime(year=2021, month=8, day=10, hour=15, minute=0), "2" + ) + + feeder_1.large_scheduled_vehicle.moved_capacity = 100 # in TEU + feeder_1.save() + + containers = [ + self._create_container_for_large_scheduled_vehicle(feeder_1) + for _ in range(feeder_1.large_scheduled_vehicle.moved_capacity) # here only 20' containers + ] + self.manager.reload_properties( transportation_buffer=0, - ramp_up_period_end=datetime.date(2022, 8, 8) + ramp_up_period_end=datetime.date(2021, 8, 12) ) # run actual function self.manager.choose_departing_vehicle_for_containers() containers_reloaded: Iterable[Container] = Container.select().where( - Container.picked_up_by_large_scheduled_vehicle == feeder + Container.picked_up_by_large_scheduled_vehicle == feeder_2 ) + self.assertTrue(set(containers_reloaded).issubset(set(containers)), "Feeder must only load generated " + "containers") + teu_loaded = 0 for container in containers_reloaded: # pylint: disable=not-an-iterable - self.assertEqual(container.picked_up_by_large_scheduled_vehicle, feeder.large_scheduled_vehicle) + self.assertEqual(container.picked_up_by_large_scheduled_vehicle, feeder_2.large_scheduled_vehicle) teu_loaded += ContainerLength.get_teu_factor(container.length) - self.assertLessEqual(teu_loaded, 10, "Feeder must have loaded much less containers because this is the" + self.assertLessEqual(teu_loaded, 30, "Feeder must have loaded much less containers because this is the" "ramp-up period!") def test_behavior_during_ramp_down_period(self): + """During ramp-down, transshipment flows should not be affected!""" ... # TODO! From 70512931cbeffc9e2efda392fa568911aaec5266 Mon Sep 17 00:00:00 2001 From: Marvin Kastner Date: Fri, 24 May 2024 18:32:59 +0200 Subject: [PATCH 28/54] Add ContainerFlowByVehicleInstanceAnalysis and its report incl. docs, fix name of OutboundToInboundVehicleCapacityUtilizationAnalysis and its report --- conflowgen/__init__.py | 11 +- conflowgen/analyses/__init__.py | 8 +- ...ainer_flow_by_vehicle_instance_analysis.py | 107 +++++++++++++++ ...low_by_vehicle_instance_analysis_report.py | 125 ++++++++++++++++++ ...le_type_adjustment_per_vehicle_analysis.py | 2 + ..._adjustment_per_vehicle_analysis_report.py | 2 +- ..._vehicle_capacity_utilization_analysis.py} | 3 +- ...e_capacity_utilization_analysis_report.py} | 8 +- conflowgen/descriptive_datatypes/__init__.py | 9 +- ...le_type_adjustment_per_vehicle_analysis.py | 2 + ..._outbound_capacity_utilization_analysis.py | 8 +- ...nd_capacity_utilization_analysis_report.py | 6 +- .../tests/analyses/test_run_all_analyses.py | 4 +- docs/api.rst | 10 +- 14 files changed, 280 insertions(+), 25 deletions(-) create mode 100644 conflowgen/analyses/container_flow_by_vehicle_instance_analysis.py create mode 100644 conflowgen/analyses/container_flow_by_vehicle_instance_analysis_report.py rename conflowgen/analyses/{inbound_to_outbound_vehicle_capacity_utilization_analysis.py => outbound_to_inbound_vehicle_capacity_utilization_analysis.py} (97%) rename conflowgen/analyses/{inbound_to_outbound_vehicle_capacity_utilization_analysis_report.py => outbound_to_inbound_vehicle_capacity_utilization_analysis_report.py} (97%) diff --git a/conflowgen/__init__.py b/conflowgen/__init__.py index 426fa678..1ab3baa9 100644 --- a/conflowgen/__init__.py +++ b/conflowgen/__init__.py @@ -35,10 +35,10 @@ InboundAndOutboundVehicleCapacityAnalysis from conflowgen.analyses.inbound_and_outbound_vehicle_capacity_analysis_report import \ InboundAndOutboundVehicleCapacityAnalysisReport -from conflowgen.analyses.inbound_to_outbound_vehicle_capacity_utilization_analysis import \ - InboundToOutboundVehicleCapacityUtilizationAnalysis -from conflowgen.analyses.inbound_to_outbound_vehicle_capacity_utilization_analysis_report import \ - InboundToOutboundVehicleCapacityUtilizationAnalysisReport +from conflowgen.analyses.outbound_to_inbound_vehicle_capacity_utilization_analysis import \ + OutboundToInboundVehicleCapacityUtilizationAnalysis +from conflowgen.analyses.outbound_to_inbound_vehicle_capacity_utilization_analysis_report import \ + OutboundToInboundVehicleCapacityUtilizationAnalysisReport from conflowgen.analyses.container_flow_by_vehicle_type_analysis import ContainerFlowByVehicleTypeAnalysis from conflowgen.analyses.container_flow_by_vehicle_type_analysis_report import \ ContainerFlowByVehicleTypeAnalysisReport @@ -64,6 +64,9 @@ ContainerFlowVehicleTypeAdjustmentPerVehicleAnalysis from conflowgen.analyses.container_flow_vehicle_type_adjustment_per_vehicle_analysis_report import \ ContainerFlowVehicleTypeAdjustmentPerVehicleAnalysisReport +from conflowgen.analyses.container_flow_by_vehicle_instance_analysis import ContainerFlowByVehicleInstanceAnalysis +from conflowgen.analyses.container_flow_by_vehicle_instance_analysis_report import ( + ContainerFlowByVehicleInstanceAnalysisReport) # Cache for analyses and previews from conflowgen.data_summaries.data_summaries_cache import DataSummariesCache diff --git a/conflowgen/analyses/__init__.py b/conflowgen/analyses/__init__.py index 000026d6..5cb8f4a2 100644 --- a/conflowgen/analyses/__init__.py +++ b/conflowgen/analyses/__init__.py @@ -7,12 +7,13 @@ ContainerFlowAdjustmentByVehicleTypeAnalysisReport from .container_flow_adjustment_by_vehicle_type_analysis_summary_report import \ ContainerFlowAdjustmentByVehicleTypeAnalysisSummaryReport +from .container_flow_by_vehicle_instance_analysis_report import ContainerFlowByVehicleInstanceAnalysisReport from .container_flow_by_vehicle_type_analysis_report import ContainerFlowByVehicleTypeAnalysisReport from .container_flow_vehicle_type_adjustment_per_vehicle_analysis_report import \ ContainerFlowVehicleTypeAdjustmentPerVehicleAnalysisReport from .inbound_and_outbound_vehicle_capacity_analysis_report import InboundAndOutboundVehicleCapacityAnalysisReport -from .inbound_to_outbound_vehicle_capacity_utilization_analysis_report import \ - InboundToOutboundVehicleCapacityUtilizationAnalysisReport +from .outbound_to_inbound_vehicle_capacity_utilization_analysis_report import \ + OutboundToInboundVehicleCapacityUtilizationAnalysisReport from .modal_split_analysis_report import ModalSplitAnalysisReport from .quay_side_throughput_analysis_report import QuaySideThroughputAnalysisReport from .truck_gate_throughput_analysis_report import TruckGateThroughputAnalysisReport @@ -26,6 +27,7 @@ reports: typing.Iterable[typing.Type[AbstractReport]] = [ InboundAndOutboundVehicleCapacityAnalysisReport, ContainerFlowByVehicleTypeAnalysisReport, + ContainerFlowByVehicleInstanceAnalysisReport, ContainerFlowAdjustmentByVehicleTypeAnalysisReport, ContainerFlowAdjustmentByVehicleTypeAnalysisSummaryReport, ContainerFlowVehicleTypeAdjustmentPerVehicleAnalysisReport, @@ -34,7 +36,7 @@ QuaySideThroughputAnalysisReport, TruckGateThroughputAnalysisReport, YardCapacityAnalysisReport, - InboundToOutboundVehicleCapacityUtilizationAnalysisReport + OutboundToInboundVehicleCapacityUtilizationAnalysisReport ] diff --git a/conflowgen/analyses/container_flow_by_vehicle_instance_analysis.py b/conflowgen/analyses/container_flow_by_vehicle_instance_analysis.py new file mode 100644 index 00000000..780bf87b --- /dev/null +++ b/conflowgen/analyses/container_flow_by_vehicle_instance_analysis.py @@ -0,0 +1,107 @@ +from __future__ import annotations + +import datetime +import typing + +from conflowgen.data_summaries.data_summaries_cache import DataSummariesCache +from conflowgen.domain_models.container import Container +from conflowgen.domain_models.data_types.mode_of_transport import ModeOfTransport +from conflowgen.analyses.abstract_analysis import AbstractAnalysis +from conflowgen.descriptive_datatypes import VehicleIdentifier, FlowDirection + + +class ContainerFlowByVehicleInstanceAnalysis(AbstractAnalysis): + """ + This analysis can be run after the synthetic data has been generated. + The analysis returns a data structure that can be used for generating reports (e.g., in text or as a figure) + as it is the case with :class:`.ContainerFlowByVehicleInstanceAnalysisReport`. + """ + + @staticmethod + @DataSummariesCache.cache_result + def get_container_flow_by_vehicle( + start_date: typing.Optional[datetime.datetime] = None, + end_date: typing.Optional[datetime.datetime] = None + ) -> typing.Dict[ + ModeOfTransport, typing.Dict[VehicleIdentifier, typing.Dict[FlowDirection, (int, int)]] + ]: + """ + This shows for each of the vehicles + + Args: + start_date: + The earliest arriving container that is included. Consider all containers if :obj:`None`. + end_date: + The latest departing container that is included. Consider all containers if :obj:`None`. + + Returns: + Grouped by vehicle type and vehicle instance, how much import, export, and transshipment is unloaded and + loaded. + """ + container_flow_by_vehicle: typing.Dict[ + ModeOfTransport, typing.Dict[VehicleIdentifier, + typing.Dict[FlowDirection, typing.Dict[str, int]]]] = { + vehicle_type: {} + for vehicle_type in ModeOfTransport + } + + vehicle_identifier_cache = {} + + container: Container + for container in Container.select(): + if start_date and container.get_arrival_time() < start_date: + continue + if end_date and container.get_departure_time() > end_date: + continue + + if container.delivered_by_large_scheduled_vehicle is not None: # if not transported by truck + + if container.delivered_by_large_scheduled_vehicle not in vehicle_identifier_cache: + vehicle_id_inbound = VehicleIdentifier( + id=container.delivered_by_large_scheduled_vehicle.id, + mode_of_transport=container.delivered_by, + service_name=container.delivered_by_large_scheduled_vehicle.schedule.service_name, + vehicle_name=container.delivered_by_large_scheduled_vehicle.vehicle_name, + vehicle_arrival_time=container.get_arrival_time(), + ) + vehicle_identifier_cache[container.delivered_by_large_scheduled_vehicle] = vehicle_id_inbound + + vehicle_id_inbound = vehicle_identifier_cache[container.delivered_by_large_scheduled_vehicle] + + if vehicle_id_inbound not in container_flow_by_vehicle[container.delivered_by]: + container_flow_by_vehicle[container.delivered_by][vehicle_id_inbound] = { + flow_direction: { + "inbound": 0, + "outbound": 0, + } + for flow_direction in FlowDirection + } + container_flow_by_vehicle[ + container.delivered_by][vehicle_id_inbound][container.flow_direction]["inbound"] += 1 + + if container.picked_up_by_large_scheduled_vehicle is not None: # if not transported by truck + + if container.picked_up_by_large_scheduled_vehicle not in vehicle_identifier_cache: + vehicle_id_outbound = VehicleIdentifier( + id=container.picked_up_by_large_scheduled_vehicle.id, + mode_of_transport=container.picked_up_by, + service_name=container.picked_up_by_large_scheduled_vehicle.schedule.service_name, + vehicle_name=container.picked_up_by_large_scheduled_vehicle.vehicle_name, + vehicle_arrival_time=container.get_departure_time(), + ) + vehicle_identifier_cache[container.picked_up_by_large_scheduled_vehicle] = vehicle_id_outbound + + vehicle_id_outbound = vehicle_identifier_cache[container.picked_up_by_large_scheduled_vehicle] + + if vehicle_id_outbound not in container_flow_by_vehicle[container.picked_up_by]: + container_flow_by_vehicle[container.picked_up_by][vehicle_id_outbound] = { + flow_direction: { + "inbound": 0, + "outbound": 0, + } + for flow_direction in FlowDirection + } + container_flow_by_vehicle[ + container.picked_up_by][vehicle_id_outbound][container.flow_direction]["outbound"] += 1 + + return container_flow_by_vehicle diff --git a/conflowgen/analyses/container_flow_by_vehicle_instance_analysis_report.py b/conflowgen/analyses/container_flow_by_vehicle_instance_analysis_report.py new file mode 100644 index 00000000..10e59360 --- /dev/null +++ b/conflowgen/analyses/container_flow_by_vehicle_instance_analysis_report.py @@ -0,0 +1,125 @@ +from __future__ import annotations + +import logging +import typing + +import matplotlib.figure +import matplotlib.pyplot as plt +import pandas as pd + +from conflowgen.analyses.container_flow_by_vehicle_instance_analysis import ContainerFlowByVehicleInstanceAnalysis +from conflowgen.descriptive_datatypes import VehicleIdentifier, FlowDirection +from conflowgen.domain_models.data_types.mode_of_transport import ModeOfTransport +from conflowgen.reporting import AbstractReportWithMatplotlib +from conflowgen.reporting.no_data_plot import no_data_graph + + +class ContainerFlowByVehicleInstanceAnalysisReport(AbstractReportWithMatplotlib): + """ + This analysis report takes the data structure as generated by :class:`.ContainerFlowByVehicleInstanceAnalysis` + and creates a comprehensible representation for the user, either as text or as a graph. + The visual and table are expected to approximately look like in the + `example ContainerFlowByVehicleInstanceAnalysisReport \ + `_. + """ + + report_description = """ + Analyze how many import, export, and transshipment containers were unloaded and loaded on each vehicle. + """ + + logger = logging.getLogger("conflowgen") + + def __init__(self): + super().__init__() + self._df = None + self.analysis = ContainerFlowByVehicleInstanceAnalysis() + + def get_report_as_text( + self, **kwargs + ) -> str: + """ + Keyword Args: + start_date (datetime.datetime): The earliest arriving container that is included. + Consider all containers if :obj:`None`. + end_date (datetime.datetime): The latest departing container that is included. + Consider all containers if :obj:`None`. + + Returns: + The report in human-readable text format + """ + container_flow = self._get_analysis(kwargs) + + report = "(pending)" + + # create string representation + return report + + def _get_analysis(self, kwargs: dict) -> typing.Dict[ + ModeOfTransport, typing.Dict[VehicleIdentifier, typing.Dict[FlowDirection, (int, int)]] + ]: + start_date = kwargs.pop("start_date", None) + end_date = kwargs.pop("end_date", None) + assert len(kwargs) == 0, f"Keyword(s) {kwargs.keys()} have not been processed" + + container_flow = self.analysis.get_container_flow_by_vehicle( + start_date=start_date, + end_date=end_date + ) + return container_flow + + def get_report_as_graph(self, **kwargs) -> matplotlib.figure.Figure: + """ + Visualize the container flows (import, export, transshipment) over time. + + Returns: + The diagram. + """ + container_flow = self._get_analysis(kwargs) + + plain_table = [] + + for mode_of_transport in (set(container_flow.keys()) - set([ModeOfTransport.truck])): + for vehicle_identifier in container_flow[mode_of_transport].keys(): + for flow_direction in container_flow[mode_of_transport][vehicle_identifier]: + for journey_direction in container_flow[mode_of_transport][vehicle_identifier][flow_direction]: + handled_volume = container_flow[ + mode_of_transport][vehicle_identifier][flow_direction][journey_direction] + + plain_table.append( + (mode_of_transport, vehicle_identifier.id, vehicle_identifier.vehicle_name, + vehicle_identifier.service_name, vehicle_identifier.vehicle_arrival_time, + str(flow_direction), journey_direction, handled_volume) + ) + + plot_title = "Container Flow By Vehicle Instance Analysis Report" + + if len(plain_table) == 0: + fig, ax = no_data_graph() + ax.set_title(plot_title) + return fig + + column_names = ("mode_of_transport", "vehicle_id", "vehicle_name", "service_name", "vehicle_arrival_time", + "flow_direction", "journey_direction", "handled_volume") + + df = pd.DataFrame(plain_table) + df.columns = column_names + df.set_index("vehicle_arrival_time", inplace=True) + + self._df = df + + fig, axes = plt.subplots(nrows=2 * (len(ModeOfTransport) - 1), figsize=(7, 20)) + i = 0 + for mode_of_transport in (set(ModeOfTransport) - set([ModeOfTransport.truck])): + for journey_direction in ["inbound", "outbound"]: + ax = axes[i] + i += 1 + ax.set_title(f"{mode_of_transport} - {journey_direction}") + df[(df["mode_of_transport"] == mode_of_transport) + & (df["journey_direction"] == journey_direction) + ].groupby("flow_direction")["handled_volume"].plot(ax=ax, linestyle=":", marker=".") + + plt.legend(loc='center left', bbox_to_anchor=(1, 0.5)) + + plt.tight_layout() + + return fig diff --git a/conflowgen/analyses/container_flow_vehicle_type_adjustment_per_vehicle_analysis.py b/conflowgen/analyses/container_flow_vehicle_type_adjustment_per_vehicle_analysis.py index 0d5a0589..a2672a1c 100644 --- a/conflowgen/analyses/container_flow_vehicle_type_adjustment_per_vehicle_analysis.py +++ b/conflowgen/analyses/container_flow_vehicle_type_adjustment_per_vehicle_analysis.py @@ -99,6 +99,7 @@ def get_vehicle_type_adjustments_per_vehicle( def _get_vehicle_identifier_for_vehicle_picking_up_the_container(container: Container) -> VehicleIdentifier: if container.picked_up_by == ModeOfTransport.truck: vehicle_identifier = VehicleIdentifier( + id=None, mode_of_transport=ModeOfTransport.truck, vehicle_arrival_time=container.get_departure_time(), service_name=None, @@ -106,6 +107,7 @@ def _get_vehicle_identifier_for_vehicle_picking_up_the_container(container: Cont ) else: vehicle_identifier = VehicleIdentifier( + id=container.picked_up_by_large_scheduled_vehicle.id, mode_of_transport=container.picked_up_by, vehicle_arrival_time=container.get_departure_time(), service_name=container.picked_up_by_large_scheduled_vehicle.schedule.service_name, diff --git a/conflowgen/analyses/container_flow_vehicle_type_adjustment_per_vehicle_analysis_report.py b/conflowgen/analyses/container_flow_vehicle_type_adjustment_per_vehicle_analysis_report.py index a02d4e66..a8d226a2 100644 --- a/conflowgen/analyses/container_flow_vehicle_type_adjustment_per_vehicle_analysis_report.py +++ b/conflowgen/analyses/container_flow_vehicle_type_adjustment_per_vehicle_analysis_report.py @@ -10,7 +10,7 @@ from conflowgen.analyses.container_flow_vehicle_type_adjustment_per_vehicle_analysis import \ ContainerFlowVehicleTypeAdjustmentPerVehicleAnalysis -from conflowgen.analyses.inbound_to_outbound_vehicle_capacity_utilization_analysis import VehicleIdentifier +from conflowgen.analyses.outbound_to_inbound_vehicle_capacity_utilization_analysis import VehicleIdentifier from conflowgen.reporting import AbstractReportWithMatplotlib from conflowgen.reporting.no_data_plot import no_data_graph diff --git a/conflowgen/analyses/inbound_to_outbound_vehicle_capacity_utilization_analysis.py b/conflowgen/analyses/outbound_to_inbound_vehicle_capacity_utilization_analysis.py similarity index 97% rename from conflowgen/analyses/inbound_to_outbound_vehicle_capacity_utilization_analysis.py rename to conflowgen/analyses/outbound_to_inbound_vehicle_capacity_utilization_analysis.py index 33626555..010c49e9 100644 --- a/conflowgen/analyses/inbound_to_outbound_vehicle_capacity_utilization_analysis.py +++ b/conflowgen/analyses/outbound_to_inbound_vehicle_capacity_utilization_analysis.py @@ -23,7 +23,7 @@ class InboundAndOutboundCapacity(typing.NamedTuple): outbound_capacity: float -class InboundToOutboundVehicleCapacityUtilizationAnalysis(AbstractAnalysis): +class OutboundToInboundVehicleCapacityUtilizationAnalysis(AbstractAnalysis): """ This analysis can be run after the synthetic data has been generated. The analysis returns a data structure that can be used for generating reports (e.g., in text or as a figure) @@ -87,6 +87,7 @@ def get_inbound_and_outbound_capacity_of_each_vehicle( used_capacity_on_outbound_journey += container.occupied_teu vehicle_id = VehicleIdentifier( + id=vehicle.id, mode_of_transport=mode_of_transport, service_name=service_name, vehicle_name=vehicle_name, diff --git a/conflowgen/analyses/inbound_to_outbound_vehicle_capacity_utilization_analysis_report.py b/conflowgen/analyses/outbound_to_inbound_vehicle_capacity_utilization_analysis_report.py similarity index 97% rename from conflowgen/analyses/inbound_to_outbound_vehicle_capacity_utilization_analysis_report.py rename to conflowgen/analyses/outbound_to_inbound_vehicle_capacity_utilization_analysis_report.py index d9742c8a..6c23c065 100644 --- a/conflowgen/analyses/inbound_to_outbound_vehicle_capacity_utilization_analysis_report.py +++ b/conflowgen/analyses/outbound_to_inbound_vehicle_capacity_utilization_analysis_report.py @@ -9,8 +9,8 @@ import pandas as pd from conflowgen.domain_models.data_types.mode_of_transport import ModeOfTransport -from conflowgen.analyses.inbound_to_outbound_vehicle_capacity_utilization_analysis import \ - InboundToOutboundVehicleCapacityUtilizationAnalysis, VehicleIdentifier +from conflowgen.analyses.outbound_to_inbound_vehicle_capacity_utilization_analysis import \ + OutboundToInboundVehicleCapacityUtilizationAnalysis, VehicleIdentifier from conflowgen.reporting import AbstractReportWithMatplotlib from conflowgen.reporting.no_data_plot import no_data_graph @@ -19,7 +19,7 @@ class UnsupportedPlotTypeException(Exception): pass -class InboundToOutboundVehicleCapacityUtilizationAnalysisReport(AbstractReportWithMatplotlib): +class OutboundToInboundVehicleCapacityUtilizationAnalysisReport(AbstractReportWithMatplotlib): """ This analysis report takes the data structure as generated by :class:`.InboundToOutboundCapacityUtilizationAnalysis` and creates a comprehensible representation for the user, either as text or as a graph. @@ -45,7 +45,7 @@ def __init__(self): self._start_date = None self._vehicle_type_description = None self.vehicle_type_description = None - self.analysis = InboundToOutboundVehicleCapacityUtilizationAnalysis( + self.analysis = OutboundToInboundVehicleCapacityUtilizationAnalysis( transportation_buffer=self.transportation_buffer ) self._df = None diff --git a/conflowgen/descriptive_datatypes/__init__.py b/conflowgen/descriptive_datatypes/__init__.py index 3f4a58f8..06430e32 100644 --- a/conflowgen/descriptive_datatypes/__init__.py +++ b/conflowgen/descriptive_datatypes/__init__.py @@ -93,13 +93,16 @@ class VehicleIdentifier(typing.NamedTuple): A vehicle identifier is a composition of the vehicle type, its service name, and the actual vehicle name """ + #: The vehicle identifier as it is used in the CSV export. + id: typing.Optional[int] + #: The vehicle type, e.g., 'deep_sea_vessel' or 'truck'. mode_of_transport: ModeOfTransport #: The service name, such as the name of the container service the vessel operates in. Not set for trucks. service_name: typing.Optional[str] - #: The name of the vehicle if given. + #: The name of the vehicle if given. Not set for trucks. vehicle_name: typing.Optional[str] #: The time of arrival of the vehicle at the terminal. @@ -143,3 +146,7 @@ class FlowDirection(enum.Enum): transshipment_flow = "transshipment" undefined = "undefined" + + def __str__(self) -> str: + # noinspection PyTypeChecker + return f"{self.value}" diff --git a/conflowgen/tests/analyses/test_container_flow_vehicle_type_adjustment_per_vehicle_analysis.py b/conflowgen/tests/analyses/test_container_flow_vehicle_type_adjustment_per_vehicle_analysis.py index 36bd2d36..3f592e92 100644 --- a/conflowgen/tests/analyses/test_container_flow_vehicle_type_adjustment_per_vehicle_analysis.py +++ b/conflowgen/tests/analyses/test_container_flow_vehicle_type_adjustment_per_vehicle_analysis.py @@ -91,6 +91,7 @@ def test_with_feeder_and_truck_and_no_adjustment(self): self.assertListEqual(list(no_adjustment.values()), [0]) vehicle_identifier = list(no_adjustment.keys())[0] self.assertEqual(vehicle_identifier, VehicleIdentifier( + id=outbound_feeder_lsv.id, mode_of_transport=ModeOfTransport.feeder, vehicle_arrival_time=vessel_arrival, service_name="TestFeederService", @@ -243,6 +244,7 @@ def test_with_truck_and_feeder_and_no_adjustment(self): vehicle_identifier = list(no_adjustment.keys())[0] self.assertEqual(vehicle_identifier, VehicleIdentifier( + id=feeder_lsv.id, mode_of_transport=ModeOfTransport.feeder, vehicle_arrival_time=feeder_arrival, service_name="TestFeederService", diff --git a/conflowgen/tests/analyses/test_inbound_to_outbound_capacity_utilization_analysis.py b/conflowgen/tests/analyses/test_inbound_to_outbound_capacity_utilization_analysis.py index 4d17a8f6..89cb7490 100644 --- a/conflowgen/tests/analyses/test_inbound_to_outbound_capacity_utilization_analysis.py +++ b/conflowgen/tests/analyses/test_inbound_to_outbound_capacity_utilization_analysis.py @@ -2,8 +2,8 @@ import unittest from conflowgen.descriptive_datatypes import VehicleIdentifier -from conflowgen.analyses.inbound_to_outbound_vehicle_capacity_utilization_analysis import \ - InboundToOutboundVehicleCapacityUtilizationAnalysis +from conflowgen.analyses.outbound_to_inbound_vehicle_capacity_utilization_analysis import \ + OutboundToInboundVehicleCapacityUtilizationAnalysis from conflowgen.domain_models.container import Container from conflowgen.domain_models.data_types.container_length import ContainerLength from conflowgen.domain_models.data_types.mode_of_transport import ModeOfTransport @@ -29,7 +29,7 @@ def setUp(self) -> None: Destination ]) mode_of_transport_distribution_seeder.seed() - self.analysis = InboundToOutboundVehicleCapacityUtilizationAnalysis( + self.analysis = OutboundToInboundVehicleCapacityUtilizationAnalysis( transportation_buffer=0.2 ) @@ -82,7 +82,7 @@ def test_inbound_with_single_feeder(self): self.assertEqual(len(capacities_with_one_feeder), 1, "There is only one vehicle") key_of_entry: VehicleIdentifier = list(capacities_with_one_feeder.keys())[0] - self.assertEqual(len(key_of_entry), 4, "Key consists of four components") + self.assertEqual(len(key_of_entry), 5, "Key consists of five components") self.assertEqual(key_of_entry.mode_of_transport, ModeOfTransport.feeder) self.assertEqual(key_of_entry.service_name, "TestFeederService") self.assertEqual(key_of_entry.vehicle_name, "TestFeeder1") diff --git a/conflowgen/tests/analyses/test_inbound_to_outbound_capacity_utilization_analysis_report.py b/conflowgen/tests/analyses/test_inbound_to_outbound_capacity_utilization_analysis_report.py index d5513518..feda190d 100644 --- a/conflowgen/tests/analyses/test_inbound_to_outbound_capacity_utilization_analysis_report.py +++ b/conflowgen/tests/analyses/test_inbound_to_outbound_capacity_utilization_analysis_report.py @@ -1,7 +1,7 @@ import datetime -from conflowgen.analyses.inbound_to_outbound_vehicle_capacity_utilization_analysis_report import \ - InboundToOutboundVehicleCapacityUtilizationAnalysisReport +from conflowgen.analyses.outbound_to_inbound_vehicle_capacity_utilization_analysis_report import \ + OutboundToInboundVehicleCapacityUtilizationAnalysisReport from conflowgen.application.models.container_flow_generation_properties import ContainerFlowGenerationProperties from conflowgen.domain_models.container import Container from conflowgen.domain_models.data_types.container_length import ContainerLength @@ -66,7 +66,7 @@ def setUp(self) -> None: start_date=datetime.date(2021, 12, 15), end_date=datetime.date(2021, 12, 17) ) - self.report = InboundToOutboundVehicleCapacityUtilizationAnalysisReport() + self.report = OutboundToInboundVehicleCapacityUtilizationAnalysisReport() def test_with_no_data(self): actual_report = self.report.get_report_as_text() diff --git a/conflowgen/tests/analyses/test_run_all_analyses.py b/conflowgen/tests/analyses/test_run_all_analyses.py index ed6abadb..a36f9d07 100644 --- a/conflowgen/tests/analyses/test_run_all_analyses.py +++ b/conflowgen/tests/analyses/test_run_all_analyses.py @@ -22,11 +22,11 @@ def setUp(self) -> None: def test_with_no_data(self): with self.assertLogs('conflowgen', level='INFO') as context: run_all_analyses() - self.assertEqual(len(context.output), 35) + self.assertEqual(len(context.output), 38) def test_with_no_data_as_graph(self): with unittest.mock.patch("matplotlib.pyplot.show"): with self.assertLogs('conflowgen', level='INFO') as context: print("Before run_all_analyses") run_all_analyses(as_text=False, as_graph=True, static_graphs=True) - self.assertEqual(len(context.output), 27) + self.assertEqual(len(context.output), 29) diff --git a/docs/api.rst b/docs/api.rst index 68cde3e6..b0b58ea4 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -143,6 +143,12 @@ Running analyses .. autoclass:: conflowgen.ContainerFlowAdjustmentByVehicleTypeAnalysisSummaryReport :members: +.. autoclass:: conflowgen.ContainerFlowByVehicleInstanceAnalysis + :members: + +.. autoclass:: conflowgen.ContainerFlowByVehicleInstanceAnalysisReport + :members: + .. autoclass:: conflowgen.ContainerFlowByVehicleTypeAnalysis :members: @@ -155,10 +161,10 @@ Running analyses .. autoclass:: conflowgen.InboundAndOutboundVehicleCapacityAnalysisReport :members: -.. autoclass:: conflowgen.InboundToOutboundVehicleCapacityUtilizationAnalysis +.. autoclass:: conflowgen.OutboundToInboundVehicleCapacityUtilizationAnalysis :members: -.. autoclass:: conflowgen.InboundToOutboundVehicleCapacityUtilizationAnalysisReport +.. autoclass:: conflowgen.OutboundToInboundVehicleCapacityUtilizationAnalysisReport :members: .. autoclass:: conflowgen.ModalSplitAnalysis From 34498a2ef39213f2f3cd9f18adcef2286d9efedc Mon Sep 17 00:00:00 2001 From: Marvin Kastner Date: Fri, 24 May 2024 19:56:18 +0200 Subject: [PATCH 29/54] Add test for container flow generator --- ...tainer_flow_generator_service__generate.py | 61 +++++++++++++++++-- 1 file changed, 57 insertions(+), 4 deletions(-) diff --git a/conflowgen/tests/flow_generator/test_container_flow_generator_service__generate.py b/conflowgen/tests/flow_generator/test_container_flow_generator_service__generate.py index 326c66d9..4ac0357a 100644 --- a/conflowgen/tests/flow_generator/test_container_flow_generator_service__generate.py +++ b/conflowgen/tests/flow_generator/test_container_flow_generator_service__generate.py @@ -7,6 +7,7 @@ from conflowgen.application.repositories.container_flow_generation_properties_repository import \ ContainerFlowGenerationPropertiesRepository from conflowgen.database_connection.create_tables import create_tables +from conflowgen.domain_models.container import Container from conflowgen.domain_models.distribution_models.container_dwell_time_distribution import \ ContainerDwellTimeDistribution from conflowgen.domain_models.distribution_models.mode_of_transport_distribution import ModeOfTransportDistribution @@ -14,6 +15,7 @@ from conflowgen.domain_models.distribution_seeders import mode_of_transport_distribution_seeder, \ seed_all_distributions, container_dwell_time_distribution_seeder from conflowgen.domain_models.data_types.mode_of_transport import ModeOfTransport +from conflowgen.domain_models.vehicle import Feeder, DeepSeaVessel from conflowgen.flow_generator.container_flow_generation_service import \ ContainerFlowGenerationService from conflowgen.domain_models.large_vehicle_schedule import Schedule @@ -100,8 +102,8 @@ def test_happy_path_no_mocking_with_ramp_up_and_ramp_down(self): service_name="TestDeepSeaVessel", vehicle_arrives_at=properties.start_date + datetime.timedelta(days=8), vehicle_arrives_at_time=datetime.time(11), - average_vehicle_capacity=800, - average_moved_capacity=100, + average_vehicle_capacity=12000, + average_moved_capacity=1000, next_destinations=None ) port_call_manager.add_vehicle( @@ -109,8 +111,59 @@ def test_happy_path_no_mocking_with_ramp_up_and_ramp_down(self): service_name="TestDeepSeaVessel2", vehicle_arrives_at=properties.end_date - datetime.timedelta(days=2), vehicle_arrives_at_time=datetime.time(11), - average_vehicle_capacity=800, - average_moved_capacity=100, + average_vehicle_capacity=12000, + average_moved_capacity=1000, next_destinations=None ) self.container_flow_generator_service.generate() + + reference = list(Container.select()) + + # Vehicle 1 - inbound during ramp-up untouched + feeder = Feeder.select().where( + Feeder.large_scheduled_vehicle.name == "TestFeeder" + ) + feeder_instance = list(feeder)[0] + number_containers_during_ramp_up = Container.select().where( + Container.delivered_by_large_scheduled_vehicle == feeder + ).count() + self.assertLess( + number_containers_during_ramp_up, + 100 + ) + self.assertGreater( + number_containers_during_ramp_up, + 50 + ) + + # Vehicle 2 - no effect + deep_sea_vessel_1 = DeepSeaVessel.select().where( + DeepSeaVessel.large_scheduled_vehicle.name == "TestDeepSeaVessel" + ) + number_containers_in_normal_phase = Container.select().where( + Container.picked_up_by_large_scheduled_vehicle == deep_sea_vessel_1 + ).count() + self.assertLess( + number_containers_in_normal_phase, + 1000 + ) + self.assertGreater( + number_containers_in_normal_phase, + 500 + ) + + # Vehicle 3 - inbound volume during ramp-down throttled + deep_sea_vessel_2 = DeepSeaVessel.select().where( + DeepSeaVessel.large_scheduled_vehicle.name == "TestDeepSeaVessel2" + ) + number_containers_during_ramp_down = Container.select().where( + Container.delivered_by_large_scheduled_vehicle == deep_sea_vessel_2 + ).count() + self.assertLess( + number_containers_during_ramp_down, + 1000 + ) + self.assertGreater( + number_containers_during_ramp_down, + 500 + ) From fa239038ddd0aba808089f9afb0aa7426aa5974b Mon Sep 17 00:00:00 2001 From: Marvin Kastner Date: Sun, 26 May 2024 18:53:20 +0200 Subject: [PATCH 30/54] Add parameterized as dependency --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index 245d6576..9abae58b 100644 --- a/setup.py +++ b/setup.py @@ -50,6 +50,7 @@ 'pytest-cov', # create coverage report 'pytest-xdist', # use several processes to speed up the testing process 'pytest-github-actions-annotate-failures', # turns pytest failures into action annotations + 'parameterized', # for parameterized testing 'seaborn', # some visuals in unittests are generated by seaborn 'nbconvert', # used to run tests in Jupyter notebooks, see ./test/notebooks/test_run_notebooks.py From 53b99aaf7d1c0a8dc053357b42dbb3fdab4ed929 Mon Sep 17 00:00:00 2001 From: Marvin Kastner Date: Sun, 26 May 2024 18:54:22 +0200 Subject: [PATCH 31/54] Change API of PortCallManager --- conflowgen/api/port_call_manager.py | 95 +++++++++++++++++++++++++---- 1 file changed, 82 insertions(+), 13 deletions(-) diff --git a/conflowgen/api/port_call_manager.py b/conflowgen/api/port_call_manager.py index 5da8191d..9e062bb8 100644 --- a/conflowgen/api/port_call_manager.py +++ b/conflowgen/api/port_call_manager.py @@ -1,6 +1,8 @@ from __future__ import annotations import datetime import typing +import uuid +import warnings from conflowgen.data_summaries.data_summaries_cache import DataSummariesCache from conflowgen.domain_models.factories.schedule_factory import ScheduleFactory @@ -22,20 +24,21 @@ class PortCallManager: def __init__(self): self.schedule_factory = ScheduleFactory() - def add_vehicle( + def add_service_that_calls_terminal( self, vehicle_type: ModeOfTransport, service_name: str, vehicle_arrives_at: datetime.date, vehicle_arrives_at_time: datetime.time, average_vehicle_capacity: int, - average_moved_capacity: int, + average_inbound_container_volume: int, next_destinations: typing.Optional[typing.List[typing.Tuple[str, float]]] = None, - vehicle_arrives_every_k_days: typing.Optional[int] = None + vehicle_arrives_every_k_days: int = 7 ) -> None: r""" - Add the schedule of a ship of any size or a train. The concrete vehicle instances are automatically generated - when :meth:`.ContainerFlowGenerationManager.generate` is invoked. + Add a service that frequently calls the terminal, both on the seaside and landside. + The vehicle instances are automatically generated when :meth:`.ContainerFlowGenerationManager.generate` is + invoked. Args: vehicle_type: One of @@ -55,10 +58,10 @@ def add_vehicle( Number of TEU that can be transported with the vehicle at most. The number of moved containers can never exceed this number, no matter what the value for the ``transportation_buffer`` is set to. - average_moved_capacity: - The average moved capacity describes the number of TEU which the vehicle delivers to the terminal on its - inbound journey. - When summing up the TEU factors of each of the loaded containers on the inbound journey, this value is + average_inbound_container_volume: + The average moved container volume describes the number of TEU which the vehicle delivers to the + terminal on its inbound journey. + When summing up the TEU values of each of the loaded containers on the inbound journey, this value is never exceeded but closely approximated. For the outbound journey, the containers are assigned depending on the distribution kept in :class:`.ModeOfTransportDistributionManager`. @@ -67,13 +70,13 @@ def add_vehicle( .. math:: min( - \text{average_moved_capacity} \cdot \text{transportation_buffer},\text{average_vehicle_capacity} + \text{average_inbound_container_volume} \cdot \text{transportation_buffer},\text{average_vehicle_capacity} ) If you have calibrated the aforementioned distribution accordingly, the actual number of containers on the outbound journey in TEU should be on average the same as on the inbound journey. - In that case, the vehicle moves ``average_moved_capacity`` number of containers in TEU on its inbound - journey and the same number of containers in TEU again on its outbound journey. + In that case, the vehicle moves ``average_inbound_container_volume`` number of containers in TEU on its + inbound journey and the same number of containers in TEU again on its outbound journey. next_destinations: Pairs of destination and frequency of the destination being chosen. vehicle_arrives_every_k_days: @@ -98,12 +101,78 @@ def add_vehicle( vehicle_arrives_at=vehicle_arrives_at, vehicle_arrives_at_time=vehicle_arrives_at_time, average_vehicle_capacity=average_vehicle_capacity, - average_moved_capacity=average_moved_capacity, + average_inbound_container_volume=average_inbound_container_volume, next_destinations=next_destinations, vehicle_arrives_every_k_days=vehicle_arrives_every_k_days ) DataSummariesCache.reset_cache() + def add_vehicle( + self, + vehicle_type: ModeOfTransport, + vehicle_arrives_at: datetime.date, + vehicle_arrives_at_time: datetime.time, + vehicle_capacity: int, + inbound_container_volume: int, + next_destinations: typing.Optional[typing.List[typing.Tuple[str, float]]] = None, + service_name: typing.Optional[str] = None + ) -> None: + r""" + Add a service that frequently calls the terminal, both on the seaside and landside. + The vehicle instances are automatically generated when :meth:`.ContainerFlowGenerationManager.generate` is + invoked. + + Args: + vehicle_type: One of + :class:`ModeOfTransport.deep_sea_vessel`, + :class:`ModeOfTransport.feeder`, + :class:`ModeOfTransport.barge`, or + :class:`ModeOfTransport.train` + service_name: + The name of the service, i.e., the shipping line or rail freight line + vehicle_arrives_at: + A date the service would arrive at the terminal. This can, e.g., point at the week day for weekly + services. In any case, this is combined with the parameter ``vehicle_arrives_every_k_days`` and only + arrivals within the time scope between ``start_date`` and ``end_date`` are considered. + vehicle_arrives_at_time: + A time of the day (between 00:00 and 23:59). + vehicle_capacity: + Number of TEU that can be transported with the vehicle at most. + The number of moved containers can never exceed this number, no matter what the value for the + ``transportation_buffer`` is set to. + inbound_container_volume: + This describes the number of TEU which the vehicle delivers to the + terminal on its inbound journey. + When summing up the TEU values of each of the loaded containers on the inbound journey, this value is + never exceeded but closely approximated. + For the outbound journey, the containers are assigned depending on the distribution kept in + :class:`.ModeOfTransportDistributionManager`. + The maximum number of containers in TEU on the outbound journey of the vehicle is bound by + + .. math:: + + min( + \text{average_inbound_container_volume} \cdot \text{transportation_buffer},\text{average_vehicle_capacity} + ) + + If you have calibrated the aforementioned distribution accordingly, the actual number of containers on + the outbound journey in TEU should be on average the same as on the inbound journey. + In that case, the vehicle moves ``average_inbound_container_volume`` number of containers in TEU on its + inbound journey and the same number of containers in TEU again on its outbound journey. + next_destinations: + Pairs of destination and frequency of the destination being chosen. + """ + return self.add_service_that_calls_terminal( + vehicle_type=vehicle_type, + service_name=service_name if service_name is not None else str(uuid.uuid4()), + vehicle_arrives_at=vehicle_arrives_at, + vehicle_arrives_at_time=vehicle_arrives_at_time, + average_vehicle_capacity=vehicle_capacity, + average_inbound_container_volume=inbound_container_volume, + next_destinations=next_destinations, + vehicle_arrives_every_k_days=-1 + ) + def has_schedule( self, service_name: str, From 2b1ada6d8d2cfe1bb48d685643b9a021b17ddb2d Mon Sep 17 00:00:00 2001 From: Marvin Kastner Date: Sun, 26 May 2024 18:54:51 +0200 Subject: [PATCH 32/54] Adjust demo scripts to new API --- examples/Python_Script/demo_DEHAM_CTA.py | 21 ++++++++----------- ..._CTA__with_ramp_up_and_ramp_down_period.py | 20 +++++++----------- .../Python_Script/demo_continental_gateway.py | 5 ++--- examples/Python_Script/demo_poc.py | 12 +++++------ 4 files changed, 25 insertions(+), 33 deletions(-) diff --git a/examples/Python_Script/demo_DEHAM_CTA.py b/examples/Python_Script/demo_DEHAM_CTA.py index 65b15e3a..15d64e57 100644 --- a/examples/Python_Script/demo_DEHAM_CTA.py +++ b/examples/Python_Script/demo_DEHAM_CTA.py @@ -120,6 +120,7 @@ conflowgen.ContainerLength.other: 0 }) + # Add vehicles that frequently visit the terminal. port_call_manager = conflowgen.PortCallManager() @@ -165,9 +166,8 @@ service_name=feeder_vehicle_name, vehicle_arrives_at=vessel_arrives_at_as_datetime_type.date(), vehicle_arrives_at_time=vessel_arrives_at_as_datetime_type.time(), - average_vehicle_capacity=capacity, - average_moved_capacity=moved_capacity, - vehicle_arrives_every_k_days=-1, # single arrival, no frequent schedule + vehicle_capacity=capacity, + inbound_container_volume=moved_capacity, next_destinations=next_ports ) logger.info("Feeder vessels are imported") @@ -215,9 +215,8 @@ service_name=deep_sea_vessel_vehicle_name, vehicle_arrives_at=vessel_arrives_at_as_datetime_type.date(), vehicle_arrives_at_time=vessel_arrives_at_as_datetime_type.time(), - average_vehicle_capacity=capacity, - average_moved_capacity=moved_capacity, - vehicle_arrives_every_k_days=-1, # single arrival, no frequent schedule + vehicle_capacity=capacity, + inbound_container_volume=moved_capacity, next_destinations=next_ports ) logger.info("Deep sea vessels are imported") @@ -249,9 +248,8 @@ service_name=barge_vehicle_name, vehicle_arrives_at=vessel_arrives_at_as_datetime_type.date(), vehicle_arrives_at_time=vessel_arrives_at_as_datetime_type.time(), - average_vehicle_capacity=capacity, - average_moved_capacity=moved_capacity, - vehicle_arrives_every_k_days=-1 # single arrival, no frequent schedule + vehicle_capacity=capacity, + inbound_container_volume=moved_capacity, ) logger.info("Barges are imported") @@ -276,14 +274,13 @@ vehicle_arrives_at_time_as_delta = earliest_time_as_delta + datetime.timedelta(hours=0.5 * drawn_slot) vehicle_arrives_at_time = (datetime.datetime.min + vehicle_arrives_at_time_as_delta).time() logger.info(f"Add train '{train_vehicle_name}' to database") - port_call_manager.add_vehicle( + port_call_manager.add_service_that_calls_terminal( vehicle_type=conflowgen.ModeOfTransport.train, service_name=train_vehicle_name, vehicle_arrives_at=vessel_arrives_at_as_datetime_type.date(), vehicle_arrives_at_time=vehicle_arrives_at_time, average_vehicle_capacity=capacity, - average_moved_capacity=capacity, # discharge everything - vehicle_arrives_every_k_days=7 # weekly arrival + average_inbound_container_volume=capacity, ) logger.info("All trains are imported") diff --git a/examples/Python_Script/demo_DEHAM_CTA__with_ramp_up_and_ramp_down_period.py b/examples/Python_Script/demo_DEHAM_CTA__with_ramp_up_and_ramp_down_period.py index 1fc52836..2587729e 100644 --- a/examples/Python_Script/demo_DEHAM_CTA__with_ramp_up_and_ramp_down_period.py +++ b/examples/Python_Script/demo_DEHAM_CTA__with_ramp_up_and_ramp_down_period.py @@ -167,9 +167,8 @@ service_name=feeder_vehicle_name, vehicle_arrives_at=vessel_arrives_at_as_datetime_type.date(), vehicle_arrives_at_time=vessel_arrives_at_as_datetime_type.time(), - average_vehicle_capacity=capacity, - average_moved_capacity=moved_capacity, - vehicle_arrives_every_k_days=-1, # single arrival, no frequent schedule + vehicle_capacity=capacity, + inbound_container_volume=moved_capacity, next_destinations=next_ports ) logger.info("Feeder vessels are imported") @@ -217,9 +216,8 @@ service_name=deep_sea_vessel_vehicle_name, vehicle_arrives_at=vessel_arrives_at_as_datetime_type.date(), vehicle_arrives_at_time=vessel_arrives_at_as_datetime_type.time(), - average_vehicle_capacity=capacity, - average_moved_capacity=moved_capacity, - vehicle_arrives_every_k_days=-1, # single arrival, no frequent schedule + vehicle_capacity=capacity, + inbound_container_volume=moved_capacity, next_destinations=next_ports ) logger.info("Deep sea vessels are imported") @@ -251,9 +249,8 @@ service_name=barge_vehicle_name, vehicle_arrives_at=vessel_arrives_at_as_datetime_type.date(), vehicle_arrives_at_time=vessel_arrives_at_as_datetime_type.time(), - average_vehicle_capacity=capacity, - average_moved_capacity=moved_capacity, - vehicle_arrives_every_k_days=-1 # single arrival, no frequent schedule + vehicle_capacity=capacity, + inbound_container_volume=moved_capacity, ) logger.info("Barges are imported") @@ -278,14 +275,13 @@ vehicle_arrives_at_time_as_delta = earliest_time_as_delta + datetime.timedelta(hours=0.5 * drawn_slot) vehicle_arrives_at_time = (datetime.datetime.min + vehicle_arrives_at_time_as_delta).time() logger.info(f"Add train '{train_vehicle_name}' to database") - port_call_manager.add_vehicle( + port_call_manager.add_service_that_calls_terminal( vehicle_type=conflowgen.ModeOfTransport.train, service_name=train_vehicle_name, vehicle_arrives_at=vessel_arrives_at_as_datetime_type.date(), vehicle_arrives_at_time=vehicle_arrives_at_time, average_vehicle_capacity=capacity, - average_moved_capacity=capacity, # discharge everything - vehicle_arrives_every_k_days=7 # weekly arrival + average_inbound_container_volume=capacity, ) logger.info("All trains are imported") diff --git a/examples/Python_Script/demo_continental_gateway.py b/examples/Python_Script/demo_continental_gateway.py index 4e9de1e5..f269c0b6 100644 --- a/examples/Python_Script/demo_continental_gateway.py +++ b/examples/Python_Script/demo_continental_gateway.py @@ -168,9 +168,8 @@ service_name=feeder_vehicle_name, vehicle_arrives_at=vessel_arrives_at_as_datetime_type.date(), vehicle_arrives_at_time=vessel_arrives_at_as_datetime_type.time(), - average_vehicle_capacity=capacity, - average_moved_capacity=moved_capacity, - vehicle_arrives_every_k_days=-1, # single arrival, no frequent schedule + vehicle_capacity=capacity, + inbound_container_volume=moved_capacity, next_destinations=next_ports ) logger.info("Feeder vessels are imported") diff --git a/examples/Python_Script/demo_poc.py b/examples/Python_Script/demo_poc.py index fb0a1844..f326e3c1 100644 --- a/examples/Python_Script/demo_poc.py +++ b/examples/Python_Script/demo_poc.py @@ -66,8 +66,8 @@ service_name=feeder_service_name, vehicle_arrives_at=datetime.date(2021, 7, 9), vehicle_arrives_at_time=datetime.time(11), - average_vehicle_capacity=800, - average_moved_capacity=100, + vehicle_capacity=800, + inbound_container_volume=100, next_destinations=[ ("DEBRV", 0.4), # 50% of the containers (in boxes) go here... ("RULED", 0.6) # and the other 50% of the containers (in boxes) go here. @@ -81,8 +81,8 @@ service_name=train_service_name, vehicle_arrives_at=datetime.date(2021, 7, 12), vehicle_arrives_at_time=datetime.time(17), - average_vehicle_capacity=90, - average_moved_capacity=90, + vehicle_capacity=90, + inbound_container_volume=90, next_destinations=None # Here we don't have containers that need to be grouped by destination ) @@ -93,8 +93,8 @@ service_name=deep_sea_service_name, vehicle_arrives_at=datetime.date(2021, 7, 10), vehicle_arrives_at_time=datetime.time(19), - average_vehicle_capacity=16000, - average_moved_capacity=150, # for faster demo + vehicle_capacity=16000, + inbound_container_volume=150, # for faster demo next_destinations=[ ("ZADUR", 0.3), # 30% of the containers (in boxes) go here... ("CNSHG", 0.7) # and the other 70% of the containers (in boxes) go here. From 5def1d3ec36f9d3e2fefe7f4ef7115d4ba664d5c Mon Sep 17 00:00:00 2001 From: Marvin Kastner Date: Sun, 26 May 2024 18:57:10 +0200 Subject: [PATCH 33/54] Create VehicleCapacityManager and VehicleContainerVolumeCalculator to decrease the complexity of the LargeScheduledVehicleRepository - that one was just dealing with too many aspects at the same time! --- .../container_flow_statistics_report.py | 16 +- ...und_vehicle_capacity_calculator_service.py | 19 +- .../services/vehicle_capacity_manager.py | 210 +++++++++++++++ .../vehicle_container_volume_calculator.py | 115 ++++++++ .../large_scheduled_vehicle_repository.py | 253 +----------------- .../repositories/schedule_repository.py | 18 +- ...r_containers_delivered_by_truck_service.py | 12 +- ...hicle_for_onward_transportation_manager.py | 8 +- 8 files changed, 368 insertions(+), 283 deletions(-) create mode 100644 conflowgen/application/services/vehicle_capacity_manager.py create mode 100644 conflowgen/application/services/vehicle_container_volume_calculator.py diff --git a/conflowgen/application/reports/container_flow_statistics_report.py b/conflowgen/application/reports/container_flow_statistics_report.py index 7703be34..9de3ffba 100644 --- a/conflowgen/application/reports/container_flow_statistics_report.py +++ b/conflowgen/application/reports/container_flow_statistics_report.py @@ -4,6 +4,7 @@ import statistics from typing import List, Type, Dict +from conflowgen.application.services.vehicle_capacity_manager import VehicleCapacityManager from conflowgen.descriptive_datatypes import FlowDirection from conflowgen.domain_models.data_types.mode_of_transport import ModeOfTransport from conflowgen.domain_models.repositories.large_scheduled_vehicle_repository import LargeScheduledVehicleRepository @@ -13,6 +14,7 @@ class ContainerFlowStatisticsReport: def __init__(self, transportation_buffer=None): self.large_scheduled_vehicle_repository = LargeScheduledVehicleRepository() + self.vehicle_capacity_manager = VehicleCapacityManager() self.logger = logging.getLogger("conflowgen") self.free_capacity_inbound_statistics = {} self.free_capacity_outbound_statistics = {} @@ -20,7 +22,7 @@ def __init__(self, transportation_buffer=None): self.set_transportation_buffer(transportation_buffer=transportation_buffer) def set_transportation_buffer(self, transportation_buffer: float) -> None: - self.large_scheduled_vehicle_repository.set_transportation_buffer(transportation_buffer) + self.vehicle_capacity_manager.set_transportation_buffer(transportation_buffer) self.logger.info(f"Use transportation buffer of {transportation_buffer} for reporting statistics.") def generate(self) -> None: @@ -31,7 +33,7 @@ def generate(self) -> None: self._generate_free_capacity_statistics(vehicles_of_types) def _generate_free_capacity_statistics(self, vehicles_of_types): - buffer_factor = 1 + self.large_scheduled_vehicle_repository.transportation_buffer + buffer_factor = 1 + self.vehicle_capacity_manager.vehicle_container_volume_calculator.transportation_buffer free_capacities_inbound = {} free_capacities_outbound = {} vehicle_type: ModeOfTransport @@ -41,10 +43,10 @@ def _generate_free_capacity_statistics(self, vehicles_of_types): # noinspection PyTypeChecker large_scheduled_vehicle: LargeScheduledVehicle = vehicle.large_scheduled_vehicle - free_capacity_inbound = self.large_scheduled_vehicle_repository.get_free_capacity_for_inbound_journey( + free_capacity_inbound = self.vehicle_capacity_manager.get_free_capacity_for_inbound_journey( vehicle ) - free_capacity_outbound = self.large_scheduled_vehicle_repository.get_free_capacity_for_outbound_journey( + free_capacity_outbound = self.vehicle_capacity_manager.get_free_capacity_for_outbound_journey( vehicle, FlowDirection.undefined ) @@ -57,11 +59,11 @@ def _generate_free_capacity_statistics(self, vehicles_of_types): f"of {free_capacity_outbound} for outbound does not match with the capacity of the vehicle of " \ f"{large_scheduled_vehicle.capacity_in_teu} TEU" - assert (free_capacity_inbound <= large_scheduled_vehicle.moved_capacity), \ + assert (free_capacity_inbound <= large_scheduled_vehicle.inbound_container_volume), \ f"A vehicle must not exceed its moved capacity, but for vehicle {vehicle} the free " \ f"capacity of {free_capacity_inbound} TEU for inbound does not match with the moved capacity " \ - f"of {large_scheduled_vehicle.moved_capacity}" - moved_capacity_with_outbound_buffer = (large_scheduled_vehicle.moved_capacity * buffer_factor) + f"of {large_scheduled_vehicle.inbound_container_volume}" + moved_capacity_with_outbound_buffer = (large_scheduled_vehicle.inbound_container_volume * buffer_factor) assert (free_capacity_outbound <= moved_capacity_with_outbound_buffer), \ f"A vehicle must not exceed its transportation buffer, but for vehicle {vehicle} the free " \ f"capacity of {free_capacity_outbound} for outbound does not match with the moved capacity " \ diff --git a/conflowgen/application/services/inbound_and_outbound_vehicle_capacity_calculator_service.py b/conflowgen/application/services/inbound_and_outbound_vehicle_capacity_calculator_service.py index 5b5583fe..6377cf6f 100644 --- a/conflowgen/application/services/inbound_and_outbound_vehicle_capacity_calculator_service.py +++ b/conflowgen/application/services/inbound_and_outbound_vehicle_capacity_calculator_service.py @@ -62,6 +62,7 @@ def get_inbound_capacity_of_vehicles( at_least_one_schedule_exists: bool = False + schedule: Schedule for schedule in Schedule.select(): at_least_one_schedule_exists = True arrivals = create_arrivals_within_time_range( @@ -72,7 +73,7 @@ def get_inbound_capacity_of_vehicles( schedule.vehicle_arrives_at_time ) moved_inbound_volumes = (len(arrivals) # number of vehicles that are planned - * schedule.average_moved_capacity) # moved TEU capacity of each vehicle + * schedule.average_inbound_container_volume) # moved TEU capacity of each vehicle inbound_container_volume_in_teu[schedule.vehicle_type] += moved_inbound_volumes inbound_container_volume_in_containers[schedule.vehicle_type] += moved_inbound_volumes / \ ContainerLengthDistributionRepository.get_teu_factor() @@ -120,11 +121,13 @@ def get_outbound_capacity_of_vehicles(start_date, end_date, transportation_buffe schedule: Schedule for schedule in Schedule.select(): - assert schedule.average_moved_capacity <= schedule.average_vehicle_capacity, \ - "A vehicle cannot move a larger amount of containers (in TEU) than its capacity, " \ - f"the input data is malformed. Schedule '{schedule.service_name}' of vehicle type " \ - f"{schedule.vehicle_type} has an average moved capacity of {schedule.average_moved_capacity} but an " \ - f"averaged vehicle capacity of {schedule.average_vehicle_capacity}." + assert ( + schedule.average_inbound_container_volume <= schedule.average_vehicle_capacity, + "A vehicle cannot move a larger amount of containers (in TEU) than its capacity, " + f"the input data is malformed. Schedule '{schedule.service_name}' of vehicle type " + f"{schedule.vehicle_type} has an average moved capacity of {schedule.average_inbound_container_volume} " + f"but an averaged vehicle capacity of {schedule.average_vehicle_capacity}." + ) arrivals = create_arrivals_within_time_range( start_date, @@ -135,14 +138,14 @@ def get_outbound_capacity_of_vehicles(start_date, end_date, transportation_buffe ) # If all container flows are balanced, only the average moved capacity is required - container_volume_moved_by_vessels_in_teu = len(arrivals) * schedule.average_moved_capacity + container_volume_moved_by_vessels_in_teu = len(arrivals) * schedule.average_inbound_container_volume outbound_used_capacity_in_teu[schedule.vehicle_type] += container_volume_moved_by_vessels_in_teu outbound_used_containers[schedule.vehicle_type] += container_volume_moved_by_vessels_in_teu / \ ContainerLengthDistributionRepository.get_teu_factor() # If there are unbalanced container flows, a vehicle departs with more containers than it delivered maximum_capacity_of_vehicle_in_teu = min( - schedule.average_moved_capacity * (1 + transportation_buffer), + schedule.average_inbound_container_volume * (1 + transportation_buffer), schedule.average_vehicle_capacity ) total_maximum_capacity_moved_by_vessel = len(arrivals) * maximum_capacity_of_vehicle_in_teu diff --git a/conflowgen/application/services/vehicle_capacity_manager.py b/conflowgen/application/services/vehicle_capacity_manager.py new file mode 100644 index 00000000..74470ab3 --- /dev/null +++ b/conflowgen/application/services/vehicle_capacity_manager.py @@ -0,0 +1,210 @@ +import datetime +import logging +import typing +from typing import Dict, Callable, Type + +from conflowgen.application.services.vehicle_container_volume_calculator import VehicleContainerVolumeCalculator +from conflowgen.descriptive_datatypes import FlowDirection +from conflowgen.domain_models.container import Container +from conflowgen.domain_models.data_types.container_length import ContainerLength +from conflowgen.domain_models.vehicle import LargeScheduledVehicle, AbstractLargeScheduledVehicle + + +class VehicleCapacityManager: + + ignored_capacity = ContainerLength.get_teu_factor(ContainerLength.other) + + def __init__(self): + self.vehicle_container_volume_calculator = VehicleContainerVolumeCalculator() + self.occupied_capacity_for_outbound_journey_buffer: ( + Dict)[Type[AbstractLargeScheduledVehicle], float] = {} + self.occupied_capacity_for_inbound_journey_buffer: ( + Dict)[Type[AbstractLargeScheduledVehicle], float] = {} + self.logger = logging.getLogger("conflowgen") + + def set_transportation_buffer(self, transportation_buffer: float): + self.vehicle_container_volume_calculator.set_transportation_buffer(transportation_buffer) + + def set_ramp_up_and_down_times( + self, + ramp_up_period_end: typing.Optional[datetime.datetime] = None, + ramp_down_period_start: typing.Optional[datetime.datetime] = None, + ) -> None: + self.vehicle_container_volume_calculator.set_ramp_up_and_down_times( + ramp_up_period_end=ramp_up_period_end, + ramp_down_period_start=ramp_down_period_start, + ) + + def reset_cache(self): + self.occupied_capacity_for_inbound_journey_buffer = {} + self.occupied_capacity_for_outbound_journey_buffer = {} + + def block_capacity_for_inbound_journey( + self, + vehicle: Type[AbstractLargeScheduledVehicle], + container: Container + ) -> bool: + assert vehicle in self.occupied_capacity_for_inbound_journey_buffer, \ + "First .get_free_capacity_for_inbound_journey(vehicle) must be invoked" + + usable_vessel_capacity = self.vehicle_container_volume_calculator.\ + get_transported_container_volume_on_inbound_journey(vehicle) + + occupied_capacity_in_teu = self.occupied_capacity_for_inbound_journey_buffer[vehicle] + used_capacity_in_teu = ContainerLength.get_teu_factor(container_length=container.length) + new_occupied_capacity_in_teu = occupied_capacity_in_teu + used_capacity_in_teu + + new_free_capacity_in_teu = usable_vessel_capacity - new_occupied_capacity_in_teu + assert ( + new_free_capacity_in_teu >= 0, + f"vehicle {vehicle} is overloaded, " + f"usable_vessel_capacity: {usable_vessel_capacity}, " + f"occupied_capacity_in_teu: {occupied_capacity_in_teu}, " + f"used_capacity_in_teu: {used_capacity_in_teu}, " + f"new_free_capacity_in_teu: {new_free_capacity_in_teu}" + ) + + self.occupied_capacity_for_inbound_journey_buffer[vehicle] = new_occupied_capacity_in_teu + vehicle_capacity_is_exhausted = new_free_capacity_in_teu < self.ignored_capacity + return vehicle_capacity_is_exhausted + + def block_capacity_for_outbound_journey( + self, + vehicle: Type[AbstractLargeScheduledVehicle], + container: Container + ) -> bool: + assert vehicle in self.occupied_capacity_for_outbound_journey_buffer, \ + "First .get_free_capacity_for_outbound_journey(vehicle) must be invoked" + + scaled_moved_container_volume, unscaled_moved_container_volume = self.vehicle_container_volume_calculator.\ + get_maximum_transported_container_volume_on_outbound_journey(vehicle, container.flow_direction) + + # calculate new free capacity + occupied_capacity_in_teu = self.occupied_capacity_for_outbound_journey_buffer[vehicle] + used_capacity_in_teu = ContainerLength.get_teu_factor(container_length=container.length) + new_occupied_capacity_in_teu = occupied_capacity_in_teu + used_capacity_in_teu + + new_scaled_free_capacity_in_teu = scaled_moved_container_volume - new_occupied_capacity_in_teu + + new_unscaled_free_capacity_in_teu = unscaled_moved_container_volume - new_occupied_capacity_in_teu + assert ( + new_unscaled_free_capacity_in_teu >= 0, + f"vehicle {vehicle} is overloaded, " + f"scaled_moved_container_volume: {scaled_moved_container_volume}, " + f"unscaled_moved_container_volume: {unscaled_moved_container_volume}, " + f"occupied_capacity_in_teu: {occupied_capacity_in_teu}, " + f"used_capacity_in_teu: {used_capacity_in_teu}, " + f"new_scaled_free_capacity_in_teu: {new_scaled_free_capacity_in_teu} " + f"new_unscaled_free_capacity_in_teu: {new_unscaled_free_capacity_in_teu}" + ) + + self.occupied_capacity_for_outbound_journey_buffer[vehicle] = new_occupied_capacity_in_teu + + space_is_exhausted = (new_scaled_free_capacity_in_teu <= self.ignored_capacity) + return space_is_exhausted + + # noinspection PyTypeChecker + def get_free_capacity_for_inbound_journey(self, vehicle: Type[AbstractLargeScheduledVehicle]) -> float: + """ + Get the free capacity for the inbound journey on a vehicle that moves according to a schedule in TEU. + During the ramp-down period (if existent), all inbound traffic is scaled down, no matter what. + """ + inbound_container_volume = self.vehicle_container_volume_calculator\ + .get_transported_container_volume_on_inbound_journey(vehicle) + + if vehicle in self.occupied_capacity_for_inbound_journey_buffer: + occupied_capacity_in_teu = self.occupied_capacity_for_inbound_journey_buffer[vehicle] + else: + occupied_capacity_in_teu = self._get_occupied_capacity_in_teu( + vehicle=vehicle, + container_counter=self._get_number_containers_for_inbound_journey, + ) + self.occupied_capacity_for_inbound_journey_buffer[vehicle] = occupied_capacity_in_teu + + free_capacity_in_teu = inbound_container_volume - occupied_capacity_in_teu + return free_capacity_in_teu + + def get_free_capacity_for_outbound_journey( + self, vehicle: Type[AbstractLargeScheduledVehicle], + flow_direction: FlowDirection + ) -> float: + """ + Get the free capacity for the outbound journey on a vehicle that moves according to a schedule in TEU. + During the ramp-up period (if existent), all outbound traffic that constitutes transshipment, is scaled down. + """ + + # During the ramp-up period, the container volume is reduced at this stage + maximum_transported_container_volume, unscaled_moved_container_volume = \ + self.vehicle_container_volume_calculator.get_maximum_transported_container_volume_on_outbound_journey( + vehicle, flow_direction + ) + + if vehicle in self.occupied_capacity_for_outbound_journey_buffer: + occupied_capacity_in_teu = self.occupied_capacity_for_outbound_journey_buffer[vehicle] + else: + occupied_capacity_in_teu = self._get_occupied_capacity_in_teu( + vehicle=vehicle, + container_counter=self._get_number_containers_for_outbound_journey, + ) + self.occupied_capacity_for_outbound_journey_buffer[vehicle] = occupied_capacity_in_teu + + assert ( + unscaled_moved_container_volume - occupied_capacity_in_teu >= 0, + f"vehicle {vehicle} is overloaded, " + f"maximum_transported_container_volume: {maximum_transported_container_volume}, " + f"unscaled_moved_container_volume: {unscaled_moved_container_volume}, " + f"occupied_capacity_in_teu: {occupied_capacity_in_teu}" + ) + + free_capacity = max(maximum_transported_container_volume - occupied_capacity_in_teu, 0) + + return free_capacity + + @staticmethod + def _get_occupied_capacity_in_teu( + vehicle: Type[AbstractLargeScheduledVehicle], + container_counter: Callable[[Type[AbstractLargeScheduledVehicle], ContainerLength], int], + ) -> (float, float): + loaded_20_foot_containers = container_counter(vehicle, ContainerLength.twenty_feet) + loaded_40_foot_containers = container_counter(vehicle, ContainerLength.forty_feet) + loaded_45_foot_containers = container_counter(vehicle, ContainerLength.forty_five_feet) + loaded_other_containers = container_counter(vehicle, ContainerLength.other) + occupied_capacity = ( + loaded_20_foot_containers * ContainerLength.get_teu_factor(ContainerLength.twenty_feet) + + loaded_40_foot_containers * ContainerLength.get_teu_factor(ContainerLength.forty_feet) + + loaded_45_foot_containers * ContainerLength.get_teu_factor(ContainerLength.forty_five_feet) + + loaded_other_containers * ContainerLength.get_teu_factor(ContainerLength.other) + ) + return occupied_capacity + + @classmethod + def _get_number_containers_for_outbound_journey( + cls, + vehicle: Type[AbstractLargeScheduledVehicle], + container_length: ContainerLength + ) -> int: + """Returns the number of containers on a specific vehicle of a specific container length that are picked up by + the vehicle""" + # noinspection PyTypeChecker + large_scheduled_vehicle: LargeScheduledVehicle = vehicle.large_scheduled_vehicle + number_loaded_containers = Container.select().where( + (Container.picked_up_by_large_scheduled_vehicle == large_scheduled_vehicle) + & (Container.length == container_length) + ).count() + return number_loaded_containers + + @classmethod + def _get_number_containers_for_inbound_journey( + cls, + vehicle: AbstractLargeScheduledVehicle, + container_length: ContainerLength + ) -> int: + """Returns the number of containers on a specific vehicle of a specific container length that are delivered by + the vehicle""" + + large_scheduled_vehicle: LargeScheduledVehicle = vehicle.large_scheduled_vehicle + number_loaded_containers = Container.select().where( + (Container.delivered_by_large_scheduled_vehicle == large_scheduled_vehicle) + & (Container.length == container_length) + ).count() + return number_loaded_containers diff --git a/conflowgen/application/services/vehicle_container_volume_calculator.py b/conflowgen/application/services/vehicle_container_volume_calculator.py new file mode 100644 index 00000000..ab9a1f12 --- /dev/null +++ b/conflowgen/application/services/vehicle_container_volume_calculator.py @@ -0,0 +1,115 @@ +from __future__ import annotations + +import datetime +import logging +import typing + +from conflowgen.descriptive_datatypes import FlowDirection +from conflowgen.domain_models.vehicle import LargeScheduledVehicle, AbstractLargeScheduledVehicle + + +class VehicleContainerVolumeCalculator: + + downscale_factor_during_ramp_up_period_for_outbound_transshipment = 0.2 + + downscale_factor_during_ramp_down_period_for_inbound_all_kinds = 0.2 + + def __init__(self): + self.transportation_buffer = None + self.ramp_up_period_end = None + self.ramp_down_period_start = None + self.logger = logging.getLogger("conflowgen") + + def set_transportation_buffer( + self, + transportation_buffer: float + ) -> None: + assert -1 < transportation_buffer + self.transportation_buffer = transportation_buffer + + def set_ramp_up_and_down_times( + self, + ramp_up_period_end: typing.Optional[datetime.datetime] = None, + ramp_down_period_start: typing.Optional[datetime.datetime] = None + ) -> None: + self.ramp_up_period_end = ramp_up_period_end + self.ramp_down_period_start = ramp_down_period_start + + def get_transported_container_volume_on_inbound_journey( + self, + large_scheduled_vehicle: LargeScheduledVehicle | typing.Type[AbstractLargeScheduledVehicle], + ) -> float: + """ + + Args: + large_scheduled_vehicle: The vehicle for which the transported container volume is calculated for + + Returns: + The container volume in TEU that are transported by this vehicle on its inbound journey. The volume is + scaled down during the ramp-down period if present. + """ + + # auto-cast vehicles to their LargeScheduledVehicle reference + if hasattr(large_scheduled_vehicle, "large_scheduled_vehicle"): + large_scheduled_vehicle = large_scheduled_vehicle.large_scheduled_vehicle + + # This is our default + moved_container_volume = large_scheduled_vehicle.inbound_container_volume + + if self.ramp_down_period_start is not None: + arrival_time: datetime.datetime = large_scheduled_vehicle.scheduled_arrival + if arrival_time >= self.ramp_down_period_start: + moved_container_volume *= self.downscale_factor_during_ramp_down_period_for_inbound_all_kinds + + return moved_container_volume + + def get_maximum_transported_container_volume_on_outbound_journey( + self, + large_scheduled_vehicle: LargeScheduledVehicle | typing.Type[AbstractLargeScheduledVehicle], + flow_direction: FlowDirection, + ) -> (float, float): + """ + + Args: + large_scheduled_vehicle: The vehicle for which the expected transported container volume is calculated for + flow_direction: The flow direction to consider. + + Returns: + The container volume in TEU that are transported by this vehicle on its outbound journey. The volume is + scaled down during the ramp-down period if present for transshipment containers. + """ + + assert self.transportation_buffer is not None, "Transportation buffer must be set" + assert -1 < self.transportation_buffer, "Transportation buffer must be larger than -1" + + # auto-cast vehicles to their LargeScheduledVehicle reference + if hasattr(large_scheduled_vehicle, "large_scheduled_vehicle"): + large_scheduled_vehicle = large_scheduled_vehicle.large_scheduled_vehicle + + # this is our default + unscaled_moved_container_volume = self._get_maximum_outbound_capacity_in_teu(large_scheduled_vehicle) + scaled_moved_container_volume = unscaled_moved_container_volume + + if self.ramp_up_period_end is not None and flow_direction == FlowDirection.transshipment_flow: + arrival_time: datetime.datetime = large_scheduled_vehicle.scheduled_arrival + if arrival_time <= self.ramp_up_period_end: + scaled_moved_container_volume = ( + unscaled_moved_container_volume + * self.downscale_factor_during_ramp_up_period_for_outbound_transshipment + ) + + return scaled_moved_container_volume, unscaled_moved_container_volume + + def _get_maximum_outbound_capacity_in_teu( + self, + large_scheduled_vehicle: LargeScheduledVehicle + ) -> int: + assert self.transportation_buffer is not None, "Please first set the transportation buffer!" + expected_outbound_capacity_including_transportation_buffer = \ + large_scheduled_vehicle.inbound_container_volume * (1 + self.transportation_buffer) + maximum_capacity_of_vehicle = large_scheduled_vehicle.capacity_in_teu + maximum_outbound_capacity_in_teu = min( + expected_outbound_capacity_including_transportation_buffer, + maximum_capacity_of_vehicle + ) + return maximum_outbound_capacity_in_teu diff --git a/conflowgen/domain_models/repositories/large_scheduled_vehicle_repository.py b/conflowgen/domain_models/repositories/large_scheduled_vehicle_repository.py index 012dfc25..4bc8a27f 100644 --- a/conflowgen/domain_models/repositories/large_scheduled_vehicle_repository.py +++ b/conflowgen/domain_models/repositories/large_scheduled_vehicle_repository.py @@ -1,53 +1,11 @@ -import datetime -import enum -import logging -import typing -from typing import Dict, List, Callable, Type +from typing import Dict, List, Type -from conflowgen.descriptive_datatypes import FlowDirection -from conflowgen.domain_models.container import Container -from conflowgen.domain_models.data_types.container_length import ContainerLength from conflowgen.domain_models.data_types.mode_of_transport import ModeOfTransport from conflowgen.domain_models.vehicle import LargeScheduledVehicle, AbstractLargeScheduledVehicle -class JourneyDirection(enum.Enum): - inbound = "inbound" - outbound = "outbound" - - class LargeScheduledVehicleRepository: - ignored_capacity = ContainerLength.get_teu_factor(ContainerLength.other) - - downscale_factor_during_ramp_up_for_outbound_transshipment = 0.1 - - downscale_factor_during_ramp_down_for_inbound_all_kinds = 0.1 - - def __init__(self): - self.transportation_buffer = None - self.ramp_up_period_end = None - self.ramp_down_period_start = None - self.free_capacity_for_outbound_journey_buffer: Dict[Type[AbstractLargeScheduledVehicle], float] = {} - self.free_capacity_for_inbound_journey_buffer: Dict[Type[AbstractLargeScheduledVehicle], float] = {} - self.logger = logging.getLogger("conflowgen") - - def set_transportation_buffer(self, transportation_buffer: float): - assert -1 < transportation_buffer - self.transportation_buffer = transportation_buffer - - def set_ramp_up_and_down_times( - self, - ramp_up_period_end: typing.Optional[datetime.datetime] = None, - ramp_down_period_start: typing.Optional[datetime.datetime] = None - ) -> None: - self.ramp_up_period_end = ramp_up_period_end - self.ramp_down_period_start = ramp_down_period_start - - def reset_cache(self): - self.free_capacity_for_outbound_journey_buffer = {} - self.free_capacity_for_inbound_journey_buffer = {} - @staticmethod def load_all_vehicles() -> Dict[ModeOfTransport, List[Type[AbstractLargeScheduledVehicle]]]: result = {} @@ -56,212 +14,3 @@ def load_all_vehicles() -> Dict[ModeOfTransport, List[Type[AbstractLargeSchedule vehicle_type) result[vehicle_type] = list(large_schedule_vehicle_as_subtype.select().join(LargeScheduledVehicle)) return result - - def block_capacity_for_inbound_journey( - self, - vehicle: Type[AbstractLargeScheduledVehicle], - container: Container - ) -> bool: - assert vehicle in self.free_capacity_for_inbound_journey_buffer, \ - "First .get_free_capacity_for_inbound_journey(vehicle) must be invoked" - - # calculate new free capacity - free_capacity_in_teu = self.free_capacity_for_inbound_journey_buffer[vehicle] - used_capacity_in_teu = ContainerLength.get_teu_factor(container_length=container.length) - new_free_capacity_in_teu = free_capacity_in_teu - used_capacity_in_teu - assert new_free_capacity_in_teu >= 0, f"vehicle {vehicle} is overloaded, " \ - f"free_capacity_in_teu: {free_capacity_in_teu}, " \ - f"used_capacity_in_teu: {used_capacity_in_teu}, " \ - f"new_free_capacity_in_teu: {new_free_capacity_in_teu}" - - self.free_capacity_for_inbound_journey_buffer[vehicle] = new_free_capacity_in_teu - vehicle_capacity_is_exhausted = new_free_capacity_in_teu < self.ignored_capacity - return vehicle_capacity_is_exhausted - - def block_capacity_for_outbound_journey( - self, - vehicle: Type[AbstractLargeScheduledVehicle], - container: Container - ) -> bool: - assert vehicle in self.free_capacity_for_outbound_journey_buffer, \ - "First .get_free_capacity_for_outbound_journey(vehicle) must be invoked" - - # calculate new free capacity - free_capacity_in_teu = self.free_capacity_for_outbound_journey_buffer[vehicle] - used_capacity_in_teu = ContainerLength.get_teu_factor(container_length=container.length) - new_free_capacity_in_teu = free_capacity_in_teu - used_capacity_in_teu - assert new_free_capacity_in_teu >= 0, f"vehicle {vehicle} is overloaded, " \ - f"free_capacity_in_teu: {free_capacity_in_teu}, " \ - f"used_capacity_in_teu: {used_capacity_in_teu}, " \ - f"new_free_capacity_in_teu: {new_free_capacity_in_teu}" - - self.free_capacity_for_outbound_journey_buffer[vehicle] = new_free_capacity_in_teu - return new_free_capacity_in_teu <= self.ignored_capacity - - # noinspection PyTypeChecker - def get_free_capacity_for_inbound_journey(self, vehicle: Type[AbstractLargeScheduledVehicle]) -> float: - """ - Get the free capacity for the inbound journey on a vehicle that moves according to a schedule in TEU. - During the ramp-down period (if existent), all inbound traffic is scaled down, no matter what. - """ - if vehicle in self.free_capacity_for_inbound_journey_buffer: - return self.free_capacity_for_inbound_journey_buffer[vehicle] - - large_scheduled_vehicle: LargeScheduledVehicle = vehicle.large_scheduled_vehicle - total_moved_capacity_for_inbound_transportation_in_teu = large_scheduled_vehicle.moved_capacity - factored_free_capacity_in_teu, free_capacity_in_teu = self._get_free_capacity_in_teu( - vehicle=vehicle, - maximum_capacity=total_moved_capacity_for_inbound_transportation_in_teu, - container_counter=self._get_number_containers_for_inbound_journey, - journey_direction=JourneyDirection.inbound, - flow_direction=FlowDirection.undefined - ) - self.free_capacity_for_inbound_journey_buffer[vehicle] = factored_free_capacity_in_teu - return factored_free_capacity_in_teu - - def get_free_capacity_for_outbound_journey( - self, vehicle: Type[AbstractLargeScheduledVehicle], - flow_direction: FlowDirection - ) -> float: - """ - Get the free capacity for the outbound journey on a vehicle that moves according to a schedule in TEU. - During the ramp-up period (if existent), all outbound traffic that constitutes transshipment, is scaled down. - """ - assert self.transportation_buffer is not None, "First set the value!" - assert -1 < self.transportation_buffer, "Must be larger than -1" - - if vehicle in self.free_capacity_for_outbound_journey_buffer: - if flow_direction == FlowDirection.transshipment_flow and self.ramp_up_period_end is not None: - - # noinspection PyUnresolvedReferences - arrival_time: datetime.datetime = vehicle.large_scheduled_vehicle.scheduled_arrival - - if arrival_time < self.ramp_up_period_end: - return ( # factored capacity - self.free_capacity_for_outbound_journey_buffer[vehicle] - * self.downscale_factor_during_ramp_up_for_outbound_transshipment - ) - # capacity without factor - return self.free_capacity_for_outbound_journey_buffer[vehicle] - - # if not yet buffered: - - # noinspection PyTypeChecker - large_scheduled_vehicle: LargeScheduledVehicle = vehicle.large_scheduled_vehicle - - total_moved_capacity_for_onward_transportation_in_teu = \ - large_scheduled_vehicle.moved_capacity * (1 + self.transportation_buffer) - maximum_capacity_of_vehicle = large_scheduled_vehicle.capacity_in_teu - total_moved_capacity_for_onward_transportation_in_teu = min( - total_moved_capacity_for_onward_transportation_in_teu, - maximum_capacity_of_vehicle - ) - - factored_free_capacity_in_teu, free_capacity_in_teu = self._get_free_capacity_in_teu( - vehicle=vehicle, - maximum_capacity=total_moved_capacity_for_onward_transportation_in_teu, - container_counter=self._get_number_containers_for_outbound_journey, - journey_direction=JourneyDirection.outbound, - flow_direction=flow_direction - ) - - # always cache the free capacity without a factor, as it only applies in some situations - self.free_capacity_for_outbound_journey_buffer[vehicle] = free_capacity_in_teu - - # always report the factored capacity to the user of this class - return factored_free_capacity_in_teu - - def _get_free_capacity_in_teu( - self, - vehicle: Type[AbstractLargeScheduledVehicle], - maximum_capacity: int, - container_counter: Callable[[Type[AbstractLargeScheduledVehicle], ContainerLength], int], - journey_direction: JourneyDirection, - flow_direction: FlowDirection - ) -> (float, float): - loaded_20_foot_containers = container_counter(vehicle, ContainerLength.twenty_feet) - loaded_40_foot_containers = container_counter(vehicle, ContainerLength.forty_feet) - loaded_45_foot_containers = container_counter(vehicle, ContainerLength.forty_five_feet) - loaded_other_containers = container_counter(vehicle, ContainerLength.other) - free_capacity_in_teu = ( - maximum_capacity - - loaded_20_foot_containers * ContainerLength.get_teu_factor(ContainerLength.twenty_feet) - - loaded_40_foot_containers * ContainerLength.get_teu_factor(ContainerLength.forty_feet) - - loaded_45_foot_containers * ContainerLength.get_teu_factor(ContainerLength.forty_five_feet) - - loaded_other_containers * ContainerLength.get_teu_factor(ContainerLength.other) - ) - - # noinspection PyUnresolvedReferences - vehicle_name = vehicle.large_scheduled_vehicle.vehicle_name - - assert free_capacity_in_teu >= 0, f"vehicle {vehicle} of type {vehicle.get_mode_of_transport()} with the " \ - f"name '{vehicle_name}' " \ - f"is overloaded, " \ - f"free_capacity_in_teu: {free_capacity_in_teu} with " \ - f"maximum_capacity: {maximum_capacity}, " \ - f"loaded_20_foot_containers: {loaded_20_foot_containers}, " \ - f"loaded_40_foot_containers: {loaded_40_foot_containers}, " \ - f"loaded_45_foot_containers: {loaded_45_foot_containers} and " \ - f"loaded_other_containers: {loaded_other_containers}" - - # noinspection PyUnresolvedReferences - arrival_time: datetime.datetime = vehicle.large_scheduled_vehicle.scheduled_arrival - - factored_free_capacity_in_teu = free_capacity_in_teu - - if ( - journey_direction == JourneyDirection.outbound - and flow_direction == FlowDirection.transshipment_flow - and self.ramp_up_period_end is not None - and arrival_time < self.ramp_up_period_end - ): - # keep transshipment containers in the yard longer during the ramp-up period to fill the yard faster - # by offering less transport capacity on the outbound journey of deep sea vessels and feeders - factored_free_capacity_in_teu = ( - free_capacity_in_teu * self.downscale_factor_during_ramp_up_for_outbound_transshipment - ) - - elif ( - journey_direction == JourneyDirection.inbound - and self.ramp_down_period_start is not None - and arrival_time >= self.ramp_down_period_start - ): - # decrease number of inbound containers (any direction) during the ramp-down period - # by offering less transport capacity on the inbound journey (all types of vehicles, excluding trucks) - factored_free_capacity_in_teu = ( - free_capacity_in_teu * self.downscale_factor_during_ramp_down_for_inbound_all_kinds - ) - - return factored_free_capacity_in_teu, free_capacity_in_teu - - @classmethod - def _get_number_containers_for_outbound_journey( - cls, - vehicle: Type[AbstractLargeScheduledVehicle], - container_length: ContainerLength - ) -> int: - """Returns the number of containers on a specific vehicle of a specific container length that are picked up by - the vehicle""" - # noinspection PyTypeChecker - large_scheduled_vehicle: LargeScheduledVehicle = vehicle.large_scheduled_vehicle - number_loaded_containers = Container.select().where( - (Container.picked_up_by_large_scheduled_vehicle == large_scheduled_vehicle) - & (Container.length == container_length) - ).count() - return number_loaded_containers - - @classmethod - def _get_number_containers_for_inbound_journey( - cls, - vehicle: AbstractLargeScheduledVehicle, - container_length: ContainerLength - ) -> int: - """Returns the number of containers on a specific vehicle of a specific container length that are delivered by - the vehicle""" - - large_scheduled_vehicle: LargeScheduledVehicle = vehicle.large_scheduled_vehicle - number_loaded_containers = Container.select().where( - (Container.delivered_by_large_scheduled_vehicle == large_scheduled_vehicle) - & (Container.length == container_length) - ).count() - return number_loaded_containers diff --git a/conflowgen/domain_models/repositories/schedule_repository.py b/conflowgen/domain_models/repositories/schedule_repository.py index af9fd78e..cca76684 100644 --- a/conflowgen/domain_models/repositories/schedule_repository.py +++ b/conflowgen/domain_models/repositories/schedule_repository.py @@ -3,6 +3,7 @@ from typing import List, Type import logging +from conflowgen.application.services.vehicle_capacity_manager import VehicleCapacityManager from conflowgen.descriptive_datatypes import FlowDirection from conflowgen.domain_models.container import Container from conflowgen.domain_models.data_types.container_length import ContainerLength @@ -16,16 +17,17 @@ class ScheduleRepository: def __init__(self): self.logger = logging.getLogger("conflowgen") self.large_scheduled_vehicle_repository = LargeScheduledVehicleRepository() + self.vehicle_capacity_manager = VehicleCapacityManager() def set_transportation_buffer(self, transportation_buffer: float): - self.large_scheduled_vehicle_repository.set_transportation_buffer(transportation_buffer) + self.vehicle_capacity_manager.set_transportation_buffer(transportation_buffer) def set_ramp_up_and_down_times( self, ramp_up_period_end: typing.Optional[datetime.datetime] = None, ramp_down_period_start: typing.Optional[datetime.datetime] = None ): - self.large_scheduled_vehicle_repository.set_ramp_up_and_down_times( + self.vehicle_capacity_manager.set_ramp_up_and_down_times( ramp_up_period_end=ramp_up_period_end, ramp_down_period_start=ramp_down_period_start ) @@ -58,9 +60,11 @@ def get_departing_vehicles( vehicles_with_sufficient_capacity = [] vehicle: Type[AbstractLargeScheduledVehicle] for vehicle in vehicles: - free_capacity_in_teu = self.large_scheduled_vehicle_repository.get_free_capacity_for_outbound_journey( - vehicle, flow_direction - ) + free_capacity_in_teu = self.vehicle_capacity_manager.\ + get_free_capacity_for_outbound_journey( + vehicle, flow_direction + ) + if free_capacity_in_teu >= required_capacity_in_teu: vehicles_with_sufficient_capacity.append(vehicle) assert free_capacity_in_teu >= 0, f"Vehicle {vehicle} is overloaded, checking for " \ @@ -73,11 +77,11 @@ def get_departing_vehicles( def block_capacity_for_outbound_journey( self, vehicle: Type[AbstractLargeScheduledVehicle], - container: Container + container: Container, ) -> bool: """Updates the cache for faster execution """ - return self.large_scheduled_vehicle_repository.block_capacity_for_outbound_journey( + return self.vehicle_capacity_manager.block_capacity_for_outbound_journey( vehicle=vehicle, container=container ) diff --git a/conflowgen/flow_generator/allocate_space_for_containers_delivered_by_truck_service.py b/conflowgen/flow_generator/allocate_space_for_containers_delivered_by_truck_service.py index 186e094a..231804ae 100644 --- a/conflowgen/flow_generator/allocate_space_for_containers_delivered_by_truck_service.py +++ b/conflowgen/flow_generator/allocate_space_for_containers_delivered_by_truck_service.py @@ -3,6 +3,7 @@ from typing import Dict, Type, List from conflowgen.application.repositories.random_seed_store_repository import get_initialised_random_object +from conflowgen.application.services.vehicle_capacity_manager import VehicleCapacityManager from conflowgen.descriptive_datatypes import FlowDirection from conflowgen.domain_models.container import Container from conflowgen.domain_models.distribution_repositories.mode_of_transport_distribution_repository import \ @@ -25,11 +26,12 @@ def __init__(self): self.mode_of_transport_distribution_repository = ModeOfTransportDistributionRepository() self.mode_of_transport_distribution: Dict[ModeOfTransport, Dict[ModeOfTransport, float]] | None = None self.large_scheduled_vehicle_repository = LargeScheduledVehicleRepository() + self.vehicle_capacity_manager = VehicleCapacityManager() self.container_factory = ContainerFactory() def reload_distribution(self, transportation_buffer: float): self.mode_of_transport_distribution = self.mode_of_transport_distribution_repository.get_distribution() - self.large_scheduled_vehicle_repository.set_transportation_buffer( + self.vehicle_capacity_manager.set_transportation_buffer( transportation_buffer=transportation_buffer ) self.logger.info(f"Use transport buffer of {transportation_buffer} for allocating containers delivered by " @@ -63,7 +65,7 @@ def allocate(self) -> None: if truck_to_other_vehicle_distribution[ModeOfTransport.truck] > 0: raise NotImplementedError("Truck to truck traffic is not supported.") - self.large_scheduled_vehicle_repository.reset_cache() + self.vehicle_capacity_manager.reset_cache() number_containers_to_allocate = self._get_number_containers_to_allocate() @@ -115,7 +117,7 @@ def allocate(self) -> None: del truck_to_other_vehicle_distribution[selected_mode_of_transport] # drop this type continue # try again with another vehicle type (refers to while loop) - free_capacity_of_vehicle = self.large_scheduled_vehicle_repository.\ + free_capacity_of_vehicle = self.vehicle_capacity_manager.\ get_free_capacity_for_outbound_journey(vehicle, flow_direction=FlowDirection.export_flow) if free_capacity_of_vehicle <= self.ignored_capacity: @@ -139,7 +141,7 @@ def allocate(self) -> None: container = self.container_factory.create_container_for_delivering_truck(vehicle) teu_total += ContainerLength.get_teu_factor(container.length) - self.large_scheduled_vehicle_repository.block_capacity_for_outbound_journey(vehicle, container) + self.vehicle_capacity_manager.block_capacity_for_outbound_journey(vehicle, container) successful_assignment += 1 break # success, no further looping to search for a suitable vehicle @@ -176,7 +178,7 @@ def _pick_vehicle( # Make it more likely that a container ends up on a large vessel than on a smaller one vehicle: Type[AbstractLargeScheduledVehicle] vehicle_distribution: Dict[Type[AbstractLargeScheduledVehicle], float] = { - vehicle: self.large_scheduled_vehicle_repository.get_free_capacity_for_outbound_journey( + vehicle: self.vehicle_capacity_manager.get_free_capacity_for_outbound_journey( vehicle, FlowDirection.export_flow) for vehicle in vehicles_of_type } diff --git a/conflowgen/flow_generator/large_scheduled_vehicle_for_onward_transportation_manager.py b/conflowgen/flow_generator/large_scheduled_vehicle_for_onward_transportation_manager.py index 03cc0a98..2b281f0a 100644 --- a/conflowgen/flow_generator/large_scheduled_vehicle_for_onward_transportation_manager.py +++ b/conflowgen/flow_generator/large_scheduled_vehicle_for_onward_transportation_manager.py @@ -31,7 +31,7 @@ def __init__(self): self.logger = logging.getLogger("conflowgen") self.schedule_repository = ScheduleRepository() - self.large_scheduled_vehicle_repository = self.schedule_repository.large_scheduled_vehicle_repository + self.vehicle_capacity_manager = self.schedule_repository.vehicle_capacity_manager self.mode_of_transport_distribution_repository = ModeOfTransportDistributionRepository() self.mode_of_transport_distribution = self.mode_of_transport_distribution_repository.get_distribution() @@ -58,7 +58,7 @@ def reload_properties( f"vehicles that adhere a schedule.") self.container_dwell_time_distributions = self.container_dwell_time_distribution_repository.get_distributions() - self.large_scheduled_vehicle_repository = self.schedule_repository.large_scheduled_vehicle_repository + self.vehicle_capacity_manager = self.schedule_repository.vehicle_capacity_manager self.mode_of_transport_distribution = self.mode_of_transport_distribution_repository.get_distribution() def choose_departing_vehicle_for_containers(self) -> None: @@ -74,7 +74,7 @@ def choose_departing_vehicle_for_containers(self) -> None: number_assigned_containers = 0 number_not_assignable_containers = 0 - self.large_scheduled_vehicle_repository.reset_cache() + self.vehicle_capacity_manager.reset_cache() self.logger.info("Assign containers to departing vehicles that move according to a schedule...") @@ -205,7 +205,7 @@ def _draw_vehicle( for vehicle in available_vehicles: - free_capacity = self.large_scheduled_vehicle_repository.get_free_capacity_for_outbound_journey( + free_capacity = self.vehicle_capacity_manager.get_free_capacity_for_outbound_journey( vehicle, container.flow_direction ) if free_capacity >= ContainerLength.get_teu_factor(ContainerLength.other): From cec7a5e54e87b7a2c9e69e51ef96ea65846c9af8 Mon Sep 17 00:00:00 2001 From: Marvin Kastner Date: Sun, 26 May 2024 18:59:18 +0200 Subject: [PATCH 34/54] Adjust tests to new separation of tasks, avoid using "capacity" when actually referring to container volumes --- ..._and_outbound_vehicle_capacity_analysis.py | 2 +- ...d_vehicle_capacity_utilization_analysis.py | 2 +- .../factories/container_factory.py | 23 +- .../domain_models/factories/fleet_factory.py | 16 +- .../factories/schedule_factory.py | 6 +- .../factories/vehicle_factory.py | 28 +- .../domain_models/large_vehicle_schedule.py | 2 +- conflowgen/domain_models/vehicle.py | 5 +- .../test_container_dwell_time_analysis.py | 16 +- ...st_container_dwell_time_analysis_report.py | 4 +- ...container_flow_by_vehicle_type_analysis.py | 8 +- ...er_flow_by_vehicle_type_analysis_report.py | 4 +- ...le_type_adjustment_per_vehicle_analysis.py | 20 +- ..._adjustment_per_vehicle_analysis_report.py | 6 +- ..._and_outbound_vehicle_capacity_analysis.py | 8 +- ...tbound_vehicle_capacity_analysis_report.py | 4 +- ..._outbound_capacity_utilization_analysis.py | 4 +- ...nd_capacity_utilization_analysis_report.py | 4 +- .../analyses/test_modal_split_analysis.py | 12 +- .../test_modal_split_analysis_report.py | 4 +- .../test_quay_side_throughput_analysis.py | 8 +- ...st_quay_side_throughput_analysis_report.py | 8 +- .../test_truck_gate_throughput_analysis.py | 12 +- ...t_truck_gate_throughput_analysis_report.py | 8 +- .../analyses/test_yard_capacity_analysis.py | 14 +- .../test_yard_capacity_analysis_report.py | 4 +- .../tests/api/test_port_call_manager.py | 50 +++- .../test_container_flow_statistics_report.py | 20 +- .../services/test_vehicle_capacity_manager.py | 275 ++++++++++++++++++ ...st_vehicle_countainer_volume_calculator.py | 191 ++++++++++++ .../test_data_summaries_cache.py | 4 +- ...ner_destination_distribution_repository.py | 16 +- ...ory__create_for_large_scheduled_vehicle.py | 20 +- ...est_container_factory__create_for_truck.py | 2 +- ...test_fleet_factory__create_feeder_fleet.py | 2 +- .../factories/test_schedule_factory.py | 12 +- .../test_vehicle_factory__create_barge.py | 10 +- ...vehicle_factory__create_deep_sea_vessel.py | 12 +- .../test_vehicle_factory__create_feeder.py | 10 +- .../test_vehicle_factory__create_train.py | 8 +- ...test_large_scheduled_vehicle_repository.py | 149 ---------- .../reposistories/test_schedule_repository.py | 34 +-- .../tests/domain_models/test_container.py | 58 +++- .../tests/domain_models/test_vehicle.py | 24 +- ...r_containers_delivered_by_truck_service.py | 8 +- ...assign_destination_to_container_service.py | 8 +- ...tainer_flow_generator_service__generate.py | 61 +--- ...xport_container_flow_service__container.py | 2 +- ...hicle_for_onward_transportation_manager.py | 34 +-- ...est_truck_for_export_containers_manager.py | 4 +- ...est_truck_for_import_containers_manager.py | 4 +- ..._container_flow_by_vehicle_type_preview.py | 2 +- ...ner_flow_by_vehicle_type_preview_report.py | 4 +- ...d_and_outbound_vehicle_capacity_preview.py | 8 +- ...utbound_vehicle_capacity_preview_report.py | 4 +- ...preview__get_modal_split_for_hinterland.py | 2 +- ..._modal_split_preview__get_transshipment.py | 2 +- .../test_modal_split_preview_report.py | 4 +- .../test_quay_side_throughput_preview.py | 2 +- ...est_quay_side_throughput_preview_report.py | 4 +- .../test_truck_gate_throughput_preview.py | 6 +- ...st_truck_gate_throughput_preview_report.py | 4 +- .../test_vehicle_capacity_exceeded_preview.py | 2 +- ...ehicle_capacity_exceeded_preview_report.py | 4 +- 64 files changed, 829 insertions(+), 469 deletions(-) create mode 100644 conflowgen/tests/application/services/test_vehicle_capacity_manager.py create mode 100644 conflowgen/tests/application/services/test_vehicle_countainer_volume_calculator.py delete mode 100644 conflowgen/tests/domain_models/reposistories/test_large_scheduled_vehicle_repository.py diff --git a/conflowgen/analyses/inbound_and_outbound_vehicle_capacity_analysis.py b/conflowgen/analyses/inbound_and_outbound_vehicle_capacity_analysis.py index 638eab61..46a03e14 100644 --- a/conflowgen/analyses/inbound_and_outbound_vehicle_capacity_analysis.py +++ b/conflowgen/analyses/inbound_and_outbound_vehicle_capacity_analysis.py @@ -109,7 +109,7 @@ def get_outbound_container_volume_by_vehicle_type( large_scheduled_vehicle: LargeScheduledVehicle for large_scheduled_vehicle in LargeScheduledVehicle.select(): maximum_capacity_of_vehicle = min( - int(large_scheduled_vehicle.moved_capacity * (1 + self.transportation_buffer)), + int(large_scheduled_vehicle.inbound_container_volume * (1 + self.transportation_buffer)), large_scheduled_vehicle.capacity_in_teu ) vehicle_type: ModeOfTransport = large_scheduled_vehicle.schedule.vehicle_type diff --git a/conflowgen/analyses/outbound_to_inbound_vehicle_capacity_utilization_analysis.py b/conflowgen/analyses/outbound_to_inbound_vehicle_capacity_utilization_analysis.py index 010c49e9..f84df5db 100644 --- a/conflowgen/analyses/outbound_to_inbound_vehicle_capacity_utilization_analysis.py +++ b/conflowgen/analyses/outbound_to_inbound_vehicle_capacity_utilization_analysis.py @@ -66,7 +66,7 @@ def get_inbound_and_outbound_capacity_of_each_vehicle( # vehicle properties vehicle_name = vehicle.vehicle_name vehicle_arrival_time = vehicle.get_arrival_time() - used_capacity_on_inbound_journey = vehicle.moved_capacity + used_capacity_on_inbound_journey = vehicle.inbound_container_volume if start_date and vehicle_arrival_time < start_date: continue diff --git a/conflowgen/domain_models/factories/container_factory.py b/conflowgen/domain_models/factories/container_factory.py index 01e6b424..ad241bf5 100644 --- a/conflowgen/domain_models/factories/container_factory.py +++ b/conflowgen/domain_models/factories/container_factory.py @@ -5,6 +5,7 @@ import typing from typing import Dict, MutableSequence, Sequence, Type +from conflowgen.application.services.vehicle_capacity_manager import VehicleCapacityManager from conflowgen.domain_models.container import Container from conflowgen.domain_models.distribution_repositories.container_length_distribution_repository import \ ContainerLengthDistributionRepository @@ -17,7 +18,6 @@ from conflowgen.domain_models.data_types.container_length import ContainerLength from conflowgen.domain_models.data_types.mode_of_transport import ModeOfTransport from conflowgen.domain_models.data_types.storage_requirement import StorageRequirement -from conflowgen.domain_models.repositories.large_scheduled_vehicle_repository import LargeScheduledVehicleRepository from conflowgen.domain_models.vehicle import AbstractLargeScheduledVehicle, LargeScheduledVehicle from conflowgen.tools.distribution_approximator import DistributionApproximator from conflowgen.application.repositories.random_seed_store_repository import get_initialised_random_object @@ -36,14 +36,14 @@ def __init__(self): self.container_length_distribution: dict[ContainerLength, float] | None = None self.container_weight_distribution: dict[ContainerLength, dict[int, float]] | None = None self.storage_requirement_distribution: dict[ContainerLength, dict[StorageRequirement, float]] | None = None - self.large_scheduled_vehicle_repository = LargeScheduledVehicleRepository() + self.vehicle_capacity_manager = VehicleCapacityManager() def set_ramp_up_and_down_times( self, ramp_up_period_end: typing.Optional[datetime.datetime], ramp_down_period_start: typing.Optional[datetime.datetime], ) -> None: - self.large_scheduled_vehicle_repository.set_ramp_up_and_down_times( + self.vehicle_capacity_manager.set_ramp_up_and_down_times( ramp_up_period_end=ramp_up_period_end, ramp_down_period_start=ramp_down_period_start ) @@ -63,7 +63,7 @@ def create_containers_for_large_scheduled_vehicle( Creates all containers a large vehicle delivers to a terminal. """ - self.large_scheduled_vehicle_repository.reset_cache() + self.vehicle_capacity_manager.reset_cache() created_containers: MutableSequence[Container] = [] @@ -72,7 +72,7 @@ def create_containers_for_large_scheduled_vehicle( # noinspection PyTypeChecker large_scheduled_vehicle: LargeScheduledVehicle = large_scheduled_vehicle_as_subtype.large_scheduled_vehicle - free_capacity_in_teu = self.large_scheduled_vehicle_repository.get_free_capacity_for_inbound_journey( + free_capacity_in_teu = self.vehicle_capacity_manager.get_free_capacity_for_inbound_journey( large_scheduled_vehicle_as_subtype ) @@ -80,22 +80,25 @@ def create_containers_for_large_scheduled_vehicle( maximum_number_of_containers = int(math.ceil(free_capacity_in_teu)) self._load_distribution_approximators(maximum_number_of_containers, delivered_by) - while (self.large_scheduled_vehicle_repository.get_free_capacity_for_inbound_journey( - large_scheduled_vehicle_as_subtype - ) > self.ignored_capacity): + while free_capacity_in_teu > self.ignored_capacity: container = self._create_single_container_for_large_scheduled_vehicle( delivered_by_large_scheduled_vehicle_as_subtype=large_scheduled_vehicle_as_subtype ) created_containers.append(container) - is_exhausted = self.large_scheduled_vehicle_repository.block_capacity_for_inbound_journey( + is_exhausted = self.vehicle_capacity_manager.block_capacity_for_inbound_journey( vehicle=large_scheduled_vehicle_as_subtype, container=container ) if is_exhausted and not large_scheduled_vehicle.capacity_exhausted_while_determining_onward_transportation: large_scheduled_vehicle.capacity_exhausted_while_determining_onward_transportation = True large_scheduled_vehicle.save() + break + + free_capacity_in_teu = self.vehicle_capacity_manager.get_free_capacity_for_inbound_journey( + large_scheduled_vehicle_as_subtype + ) - free_capacity = self.large_scheduled_vehicle_repository.get_free_capacity_for_inbound_journey( + free_capacity = self.vehicle_capacity_manager.get_free_capacity_for_inbound_journey( large_scheduled_vehicle_as_subtype ) diff --git a/conflowgen/domain_models/factories/fleet_factory.py b/conflowgen/domain_models/factories/fleet_factory.py index f721fa1d..de873adb 100644 --- a/conflowgen/domain_models/factories/fleet_factory.py +++ b/conflowgen/domain_models/factories/fleet_factory.py @@ -77,12 +77,12 @@ def create_feeder_fleet( ) for i, arrival in enumerate(arrivals): - moved_capacity = schedule.average_moved_capacity # here we can add randomness later + inbound_container_volume = schedule.average_inbound_container_volume # here we can add randomness later vehicle_name = f"{i + 1}" feeder = self.vehicle_factory.create_feeder( vehicle_name=vehicle_name, capacity_in_teu=schedule.average_vehicle_capacity, - moved_capacity=moved_capacity, + inbound_container_volume=inbound_container_volume, scheduled_arrival=arrival, schedule=schedule ) @@ -112,13 +112,13 @@ def create_deep_sea_vessel_fleet( ) for i, arrival in enumerate(arrivals): - moved_capacity = schedule.average_moved_capacity # here we can add randomness later + inbound_container_volume = schedule.average_inbound_container_volume # here we can add randomness later vehicle_name = f"{i + 1}" deep_sea_vessel = self.vehicle_factory.create_deep_sea_vessel( vehicle_name=vehicle_name, capacity_in_teu=schedule.average_vehicle_capacity, - moved_capacity=moved_capacity, + inbound_container_volume=inbound_container_volume, scheduled_arrival=arrival, schedule=schedule ) @@ -146,13 +146,13 @@ def create_train_fleet( ) for i, arrival in enumerate(arrivals): - moved_capacity = schedule.average_moved_capacity # here we can add randomness later + inbound_container_volume = schedule.average_inbound_container_volume # here we can add randomness later vehicle_name = f"{i + 1}" train = self.vehicle_factory.create_train( vehicle_name=vehicle_name, capacity_in_teu=schedule.average_vehicle_capacity, - moved_capacity=moved_capacity, + inbound_container_volume=inbound_container_volume, scheduled_arrival=arrival, schedule=schedule ) @@ -180,13 +180,13 @@ def create_barge_fleet( ) for i, arrival in enumerate(arrivals): - moved_capacity = schedule.average_moved_capacity # here we can add randomness later + inbound_container_volume = schedule.average_inbound_container_volume # here we can add randomness later vehicle_name = f"{i + 1}" barge = self.vehicle_factory.create_barge( vehicle_name=vehicle_name, capacity_in_teu=schedule.average_vehicle_capacity, - moved_capacity=moved_capacity, + inbound_container_volume=inbound_container_volume, scheduled_arrival=arrival, schedule=schedule ) diff --git a/conflowgen/domain_models/factories/schedule_factory.py b/conflowgen/domain_models/factories/schedule_factory.py index 1670c3f4..8a41f0da 100644 --- a/conflowgen/domain_models/factories/schedule_factory.py +++ b/conflowgen/domain_models/factories/schedule_factory.py @@ -19,7 +19,7 @@ def add_schedule( vehicle_arrives_at: datetime.date, vehicle_arrives_at_time: datetime.time, average_vehicle_capacity: int, - average_moved_capacity: int, + average_inbound_container_volume: int, next_destinations: Optional[List[Tuple[str, float]]], vehicle_arrives_every_k_days: Optional[int] = None ) -> None: @@ -31,7 +31,7 @@ def add_schedule( vehicle_arrives_at: date of day vehicle_arrives_at_time: time of the day average_vehicle_capacity: in TEU - average_moved_capacity: in TEU + average_inbound_container_volume: in TEU next_destinations: distribution vehicle_arrives_every_k_days: Be aware of special meaning of None and -1! """ @@ -44,7 +44,7 @@ def add_schedule( vehicle_arrives_at=vehicle_arrives_at, vehicle_arrives_at_time=vehicle_arrives_at_time, average_vehicle_capacity=average_vehicle_capacity, - average_moved_capacity=average_moved_capacity + average_inbound_container_volume=average_inbound_container_volume ) # if it is None, use the default set in peewee, otherwise overwrite if vehicle_arrives_every_k_days is not None: diff --git a/conflowgen/domain_models/factories/vehicle_factory.py b/conflowgen/domain_models/factories/vehicle_factory.py index d675c21d..cba2da08 100644 --- a/conflowgen/domain_models/factories/vehicle_factory.py +++ b/conflowgen/domain_models/factories/vehicle_factory.py @@ -78,7 +78,7 @@ def create_truck( def _create_large_vehicle( self, capacity_in_teu: int, - moved_capacity: int, + inbound_container_volume: int, scheduled_arrival: datetime.datetime, realized_arrival: datetime.datetime, schedule: Schedule, @@ -91,13 +91,13 @@ def _create_large_vehicle( if capacity_in_teu < 0: raise UnrealisticValuesException(f"Vehicle capacity must be positive but it was {capacity_in_teu}") - if moved_capacity < 0: - raise UnrealisticValuesException(f"Vehicle must move positive amount but it was {moved_capacity}") + if inbound_container_volume < 0: + raise UnrealisticValuesException(f"Vehicle must move positive amount but it was {inbound_container_volume}") - if moved_capacity > capacity_in_teu: + if inbound_container_volume > capacity_in_teu: raise UnrealisticValuesException( f"Vehicle can't move more than its capacity but for the vehicle with an overall capacity of " - f"{capacity_in_teu} the moved capacity was set to {moved_capacity}" + f"{capacity_in_teu} the moved capacity was set to {inbound_container_volume}" ) if vehicle_name is None: @@ -106,7 +106,7 @@ def _create_large_vehicle( lsv = LargeScheduledVehicle.create( vehicle_name=vehicle_name, capacity_in_teu=capacity_in_teu, - moved_capacity=moved_capacity, + inbound_container_volume=inbound_container_volume, scheduled_arrival=scheduled_arrival, realized_arrival=realized_arrival, schedule=schedule @@ -116,7 +116,7 @@ def _create_large_vehicle( def create_feeder( self, capacity_in_teu: int, - moved_capacity: int, + inbound_container_volume: int, scheduled_arrival: datetime.datetime, schedule: Schedule, vehicle_name: Optional[str] = None @@ -126,7 +126,7 @@ def create_feeder( lsv = self._create_large_vehicle( vehicle_name=vehicle_name, capacity_in_teu=capacity_in_teu, - moved_capacity=moved_capacity, + inbound_container_volume=inbound_container_volume, scheduled_arrival=scheduled_arrival, realized_arrival=scheduled_arrival, schedule=schedule @@ -139,7 +139,7 @@ def create_feeder( def create_deep_sea_vessel( self, capacity_in_teu: int, - moved_capacity: int, + inbound_container_volume: int, scheduled_arrival: datetime.datetime, schedule: Schedule, vehicle_name: Optional[str] = None @@ -149,7 +149,7 @@ def create_deep_sea_vessel( lsv = self._create_large_vehicle( vehicle_name=vehicle_name, capacity_in_teu=capacity_in_teu, - moved_capacity=moved_capacity, + inbound_container_volume=inbound_container_volume, scheduled_arrival=scheduled_arrival, realized_arrival=scheduled_arrival, schedule=schedule @@ -162,7 +162,7 @@ def create_deep_sea_vessel( def create_train( self, capacity_in_teu: int, - moved_capacity: int, + inbound_container_volume: int, scheduled_arrival: datetime.datetime, schedule: Schedule, vehicle_name: Optional[str] = None @@ -172,7 +172,7 @@ def create_train( lsv = self._create_large_vehicle( vehicle_name=vehicle_name, capacity_in_teu=capacity_in_teu, - moved_capacity=moved_capacity, + inbound_container_volume=inbound_container_volume, scheduled_arrival=scheduled_arrival, realized_arrival=scheduled_arrival, schedule=schedule @@ -185,7 +185,7 @@ def create_train( def create_barge( self, capacity_in_teu: int, - moved_capacity: int, + inbound_container_volume: int, scheduled_arrival: datetime.datetime, schedule: Schedule, vehicle_name: Optional[str] = None @@ -195,7 +195,7 @@ def create_barge( lsv = self._create_large_vehicle( vehicle_name=vehicle_name, capacity_in_teu=capacity_in_teu, - moved_capacity=moved_capacity, + inbound_container_volume=inbound_container_volume, scheduled_arrival=scheduled_arrival, realized_arrival=scheduled_arrival, schedule=schedule diff --git a/conflowgen/domain_models/large_vehicle_schedule.py b/conflowgen/domain_models/large_vehicle_schedule.py index b54ccd67..d51bfb9a 100644 --- a/conflowgen/domain_models/large_vehicle_schedule.py +++ b/conflowgen/domain_models/large_vehicle_schedule.py @@ -26,7 +26,7 @@ class Schedule(BaseModel): "This determines the number of ship-to-shore gantry cranes that can serve the vessel " "or the length of the train that must be served by portal cranes in the subsequent model." ) - average_moved_capacity = IntegerField( + average_inbound_container_volume = IntegerField( null=False, help_text="All vehicles moving according to this schedule move approx. this amount of TEU. " "The actual amount of moved TEU might deviate if necessary to realize the provided modal split." diff --git a/conflowgen/domain_models/vehicle.py b/conflowgen/domain_models/vehicle.py index b0b830ae..67f3a687 100644 --- a/conflowgen/domain_models/vehicle.py +++ b/conflowgen/domain_models/vehicle.py @@ -63,10 +63,9 @@ class LargeScheduledVehicle(BaseModel): help_text="This is the vehicle capacity. It can be used, e.g., to determine how many cranes can serve it in " "the subsequent model that reads in this data." ) - moved_capacity = IntegerField( + inbound_container_volume = IntegerField( null=False, - help_text="This is the actually moved container volume in TEU for a single terminal visit on the inbound " - "journey." + help_text="This is the actually moved container volume in TEU for a single terminal visit on the inbound journey." ) scheduled_arrival = DateTimeField( null=False, diff --git a/conflowgen/tests/analyses/test_container_dwell_time_analysis.py b/conflowgen/tests/analyses/test_container_dwell_time_analysis.py index 55bd85c6..eeee565c 100644 --- a/conflowgen/tests/analyses/test_container_dwell_time_analysis.py +++ b/conflowgen/tests/analyses/test_container_dwell_time_analysis.py @@ -53,12 +53,12 @@ def test_with_single_container(self): vehicle_arrives_at=now.date(), vehicle_arrives_at_time=now.time(), average_vehicle_capacity=300, - average_moved_capacity=300, + average_inbound_container_volume=300, ) feeder_lsv = LargeScheduledVehicle.create( vehicle_name="TestFeeder1", capacity_in_teu=300, - moved_capacity=schedule.average_moved_capacity, + inbound_container_volume=schedule.average_inbound_container_volume, scheduled_arrival=now, schedule=schedule ) @@ -98,12 +98,12 @@ def test_with_two_containers(self): vehicle_arrives_at=now.date(), vehicle_arrives_at_time=now.time(), average_vehicle_capacity=300, - average_moved_capacity=300, + average_inbound_container_volume=300, ) feeder_lsv = LargeScheduledVehicle.create( vehicle_name="TestFeeder1", capacity_in_teu=300, - moved_capacity=schedule.average_moved_capacity, + inbound_container_volume=schedule.average_inbound_container_volume, scheduled_arrival=now, schedule=schedule ) @@ -166,12 +166,12 @@ def test_with_two_containers_and_end_time(self): vehicle_arrives_at=now.date(), vehicle_arrives_at_time=now.time(), average_vehicle_capacity=300, - average_moved_capacity=300, + average_inbound_container_volume=300, ) feeder_lsv = LargeScheduledVehicle.create( vehicle_name="TestFeeder1", capacity_in_teu=300, - moved_capacity=schedule.average_moved_capacity, + inbound_container_volume=schedule.average_inbound_container_volume, scheduled_arrival=now, schedule=schedule ) @@ -235,12 +235,12 @@ def test_with_two_containers_and_start_and_end_time(self): vehicle_arrives_at=now.date(), vehicle_arrives_at_time=now.time(), average_vehicle_capacity=300, - average_moved_capacity=300, + average_inbound_container_volume=300, ) feeder_lsv = LargeScheduledVehicle.create( vehicle_name="TestFeeder1", capacity_in_teu=300, - moved_capacity=schedule.average_moved_capacity, + inbound_container_volume=schedule.average_inbound_container_volume, scheduled_arrival=now, schedule=schedule ) diff --git a/conflowgen/tests/analyses/test_container_dwell_time_analysis_report.py b/conflowgen/tests/analyses/test_container_dwell_time_analysis_report.py index b0f27b3d..29bcc6e8 100644 --- a/conflowgen/tests/analyses/test_container_dwell_time_analysis_report.py +++ b/conflowgen/tests/analyses/test_container_dwell_time_analysis_report.py @@ -27,12 +27,12 @@ def setup_feeder_data(): vehicle_arrives_at=now.date(), vehicle_arrives_at_time=now.time(), average_vehicle_capacity=300, - average_moved_capacity=300, + average_inbound_container_volume=300, ) feeder_lsv = LargeScheduledVehicle.create( vehicle_name="TestFeeder1", capacity_in_teu=300, - moved_capacity=schedule.average_moved_capacity, + inbound_container_volume=schedule.average_inbound_container_volume, scheduled_arrival=now, schedule=schedule ) diff --git a/conflowgen/tests/analyses/test_container_flow_by_vehicle_type_analysis.py b/conflowgen/tests/analyses/test_container_flow_by_vehicle_type_analysis.py index 65714274..f19dd4b0 100644 --- a/conflowgen/tests/analyses/test_container_flow_by_vehicle_type_analysis.py +++ b/conflowgen/tests/analyses/test_container_flow_by_vehicle_type_analysis.py @@ -61,13 +61,13 @@ def test_with_single_feeder(self): vehicle_arrives_at=one_week_later.date(), vehicle_arrives_at_time=one_week_later.time(), average_vehicle_capacity=300, - average_moved_capacity=300, + average_inbound_container_volume=300, vehicle_arrives_every_k_days=-1 ) feeder_lsv = LargeScheduledVehicle.create( vehicle_name="TestFeeder1", capacity_in_teu=300, - moved_capacity=schedule.average_moved_capacity, + inbound_container_volume=schedule.average_inbound_container_volume, scheduled_arrival=datetime.datetime.now(), schedule=schedule ) @@ -105,13 +105,13 @@ def test_with_single_feeder_with_time_window(self): vehicle_arrives_at=one_week_later.date(), vehicle_arrives_at_time=one_week_later.time(), average_vehicle_capacity=300, - average_moved_capacity=300, + average_inbound_container_volume=300, vehicle_arrives_every_k_days=-1 ) feeder_lsv = LargeScheduledVehicle.create( vehicle_name="TestFeeder1", capacity_in_teu=300, - moved_capacity=schedule.average_moved_capacity, + inbound_container_volume=schedule.average_inbound_container_volume, scheduled_arrival=one_week_later, schedule=schedule ) diff --git a/conflowgen/tests/analyses/test_container_flow_by_vehicle_type_analysis_report.py b/conflowgen/tests/analyses/test_container_flow_by_vehicle_type_analysis_report.py index dd3a4e79..0acb7794 100644 --- a/conflowgen/tests/analyses/test_container_flow_by_vehicle_type_analysis_report.py +++ b/conflowgen/tests/analyses/test_container_flow_by_vehicle_type_analysis_report.py @@ -23,13 +23,13 @@ def setup_feeder_data(): vehicle_arrives_at=one_week_later.date(), vehicle_arrives_at_time=one_week_later.time(), average_vehicle_capacity=300, - average_moved_capacity=300, + average_inbound_container_volume=300, vehicle_arrives_every_k_days=-1 ) feeder_lsv = LargeScheduledVehicle.create( vehicle_name="TestFeeder1", capacity_in_teu=300, - moved_capacity=schedule.average_moved_capacity, + inbound_container_volume=schedule.average_inbound_container_volume, scheduled_arrival=datetime.datetime.now(), schedule=schedule ) diff --git a/conflowgen/tests/analyses/test_container_flow_vehicle_type_adjustment_per_vehicle_analysis.py b/conflowgen/tests/analyses/test_container_flow_vehicle_type_adjustment_per_vehicle_analysis.py index 3f592e92..6f304623 100644 --- a/conflowgen/tests/analyses/test_container_flow_vehicle_type_adjustment_per_vehicle_analysis.py +++ b/conflowgen/tests/analyses/test_container_flow_vehicle_type_adjustment_per_vehicle_analysis.py @@ -48,13 +48,13 @@ def test_with_feeder_and_truck_and_no_adjustment(self): vehicle_arrives_at=one_week_later.date(), vehicle_arrives_at_time=one_week_later.time(), average_vehicle_capacity=300, - average_moved_capacity=300, + average_inbound_container_volume=300, vehicle_arrives_every_k_days=-1 ) inbound_feeder_lsv = LargeScheduledVehicle.create( vehicle_name="TestFeeder1", capacity_in_teu=300, - moved_capacity=schedule.average_moved_capacity, + inbound_container_volume=schedule.average_inbound_container_volume, scheduled_arrival=datetime.datetime.now(), schedule=schedule ) @@ -64,7 +64,7 @@ def test_with_feeder_and_truck_and_no_adjustment(self): inbound_feeder_lsv = LargeScheduledVehicle.create( vehicle_name="TestFeeder1", capacity_in_teu=300, - moved_capacity=schedule.average_moved_capacity, + inbound_container_volume=schedule.average_inbound_container_volume, scheduled_arrival=datetime.datetime.now(), schedule=schedule ) @@ -72,7 +72,7 @@ def test_with_feeder_and_truck_and_no_adjustment(self): outbound_feeder_lsv = LargeScheduledVehicle.create( vehicle_name="TestFeeder2", capacity_in_teu=300, - moved_capacity=schedule.average_moved_capacity, + inbound_container_volume=schedule.average_inbound_container_volume, scheduled_arrival=vessel_arrival, schedule=schedule ) @@ -106,13 +106,13 @@ def test_with_feeder_and_truck_and_one_adjusted_box(self): vehicle_arrives_at=one_week_later.date(), vehicle_arrives_at_time=one_week_later.time(), average_vehicle_capacity=300, - average_moved_capacity=300, + average_inbound_container_volume=300, vehicle_arrives_every_k_days=-1 ) feeder_lsv = LargeScheduledVehicle.create( vehicle_name="TestFeeder1", capacity_in_teu=300, - moved_capacity=schedule.average_moved_capacity, + inbound_container_volume=schedule.average_inbound_container_volume, scheduled_arrival=datetime.datetime.now(), schedule=schedule ) @@ -150,13 +150,13 @@ def test_with_feeder_and_truck_and_some_adjustments(self): vehicle_arrives_at=one_week_later.date(), vehicle_arrives_at_time=one_week_later.time(), average_vehicle_capacity=300, - average_moved_capacity=300, + average_inbound_container_volume=300, vehicle_arrives_every_k_days=-1 ) feeder_lsv = LargeScheduledVehicle.create( vehicle_name="TestFeeder1", capacity_in_teu=300, - moved_capacity=schedule.average_moved_capacity, + inbound_container_volume=schedule.average_inbound_container_volume, scheduled_arrival=datetime.datetime.now(), schedule=schedule ) @@ -204,14 +204,14 @@ def test_with_truck_and_feeder_and_no_adjustment(self): vehicle_arrives_at=one_week_later.date(), vehicle_arrives_at_time=one_week_later.time(), average_vehicle_capacity=300, - average_moved_capacity=300, + average_inbound_container_volume=300, vehicle_arrives_every_k_days=-1 ) feeder_arrival = datetime.datetime.now() feeder_lsv = LargeScheduledVehicle.create( vehicle_name="TestFeeder1", capacity_in_teu=300, - moved_capacity=schedule.average_moved_capacity, + inbound_container_volume=schedule.average_inbound_container_volume, scheduled_arrival=feeder_arrival, schedule=schedule ) diff --git a/conflowgen/tests/analyses/test_container_flow_vehicle_type_adjustment_per_vehicle_analysis_report.py b/conflowgen/tests/analyses/test_container_flow_vehicle_type_adjustment_per_vehicle_analysis_report.py index 05e41a3f..3751a510 100644 --- a/conflowgen/tests/analyses/test_container_flow_vehicle_type_adjustment_per_vehicle_analysis_report.py +++ b/conflowgen/tests/analyses/test_container_flow_vehicle_type_adjustment_per_vehicle_analysis_report.py @@ -25,13 +25,13 @@ def setup_feeder_data(): vehicle_arrives_at=when.date(), vehicle_arrives_at_time=when.time(), average_vehicle_capacity=300, - average_moved_capacity=300, + average_inbound_container_volume=300, vehicle_arrives_every_k_days=-1 ) inbound_feeder_lsv = LargeScheduledVehicle.create( vehicle_name="TestFeeder1", capacity_in_teu=300, - moved_capacity=schedule.average_moved_capacity, + inbound_container_volume=schedule.average_inbound_container_volume, scheduled_arrival=datetime.datetime.now(), schedule=schedule ) @@ -42,7 +42,7 @@ def setup_feeder_data(): outbound_feeder_lsv = LargeScheduledVehicle.create( vehicle_name="TestFeeder2", capacity_in_teu=300, - moved_capacity=schedule.average_moved_capacity, + inbound_container_volume=schedule.average_inbound_container_volume, scheduled_arrival=vessel_arrival, schedule=schedule ) diff --git a/conflowgen/tests/analyses/test_inbound_and_outbound_vehicle_capacity_analysis.py b/conflowgen/tests/analyses/test_inbound_and_outbound_vehicle_capacity_analysis.py index bffa99a4..80ab527f 100644 --- a/conflowgen/tests/analyses/test_inbound_and_outbound_vehicle_capacity_analysis.py +++ b/conflowgen/tests/analyses/test_inbound_and_outbound_vehicle_capacity_analysis.py @@ -66,13 +66,13 @@ def test_inbound_with_single_feeder(self): vehicle_arrives_at=one_week_later.date(), vehicle_arrives_at_time=one_week_later.time(), average_vehicle_capacity=300, - average_moved_capacity=300, + average_inbound_container_volume=300, vehicle_arrives_every_k_days=-1 ) feeder_lsv = LargeScheduledVehicle.create( vehicle_name="TestFeeder1", capacity_in_teu=300, - moved_capacity=schedule.average_moved_capacity, + inbound_container_volume=schedule.average_inbound_container_volume, scheduled_arrival=datetime.datetime.now(), schedule=schedule ) @@ -110,13 +110,13 @@ def test_outbound_with_single_feeder(self): vehicle_arrives_at=one_week_later.date(), vehicle_arrives_at_time=one_week_later.time(), average_vehicle_capacity=300, - average_moved_capacity=300, + average_inbound_container_volume=300, vehicle_arrives_every_k_days=-1 ) feeder_lsv = LargeScheduledVehicle.create( vehicle_name="TestFeeder1", capacity_in_teu=300, - moved_capacity=schedule.average_moved_capacity, + inbound_container_volume=schedule.average_inbound_container_volume, scheduled_arrival=datetime.datetime.now(), schedule=schedule ) diff --git a/conflowgen/tests/analyses/test_inbound_and_outbound_vehicle_capacity_analysis_report.py b/conflowgen/tests/analyses/test_inbound_and_outbound_vehicle_capacity_analysis_report.py index 5522c95a..923cd77b 100644 --- a/conflowgen/tests/analyses/test_inbound_and_outbound_vehicle_capacity_analysis_report.py +++ b/conflowgen/tests/analyses/test_inbound_and_outbound_vehicle_capacity_analysis_report.py @@ -23,14 +23,14 @@ def setup_feeder_data(): vehicle_arrives_at=one_week_later.date(), vehicle_arrives_at_time=one_week_later.time(), average_vehicle_capacity=300, - average_moved_capacity=300, + average_inbound_container_volume=300, vehicle_arrives_every_k_days=-1 ) schedule.save() feeder_lsv = LargeScheduledVehicle.create( vehicle_name="TestFeeder1", capacity_in_teu=300, - moved_capacity=schedule.average_moved_capacity, + inbound_container_volume=schedule.average_inbound_container_volume, scheduled_arrival=datetime.datetime.now(), schedule=schedule ) diff --git a/conflowgen/tests/analyses/test_inbound_to_outbound_capacity_utilization_analysis.py b/conflowgen/tests/analyses/test_inbound_to_outbound_capacity_utilization_analysis.py index 89cb7490..afd2b1dc 100644 --- a/conflowgen/tests/analyses/test_inbound_to_outbound_capacity_utilization_analysis.py +++ b/conflowgen/tests/analyses/test_inbound_to_outbound_capacity_utilization_analysis.py @@ -53,14 +53,14 @@ def test_inbound_with_single_feeder(self): vehicle_arrives_at=one_week_later.date(), vehicle_arrives_at_time=one_week_later.time(), average_vehicle_capacity=300, - average_moved_capacity=250, + average_inbound_container_volume=250, vehicle_arrives_every_k_days=-1 ) now = datetime.datetime.now() feeder_lsv = LargeScheduledVehicle.create( vehicle_name="TestFeeder1", capacity_in_teu=schedule.average_vehicle_capacity, - moved_capacity=schedule.average_moved_capacity, + inbound_container_volume=schedule.average_inbound_container_volume, scheduled_arrival=now, schedule=schedule ) diff --git a/conflowgen/tests/analyses/test_inbound_to_outbound_capacity_utilization_analysis_report.py b/conflowgen/tests/analyses/test_inbound_to_outbound_capacity_utilization_analysis_report.py index feda190d..887474db 100644 --- a/conflowgen/tests/analyses/test_inbound_to_outbound_capacity_utilization_analysis_report.py +++ b/conflowgen/tests/analyses/test_inbound_to_outbound_capacity_utilization_analysis_report.py @@ -23,13 +23,13 @@ def setup_feeder_data(): vehicle_arrives_at=one_week_later.date(), vehicle_arrives_at_time=one_week_later.time(), average_vehicle_capacity=300, - average_moved_capacity=250, + average_inbound_container_volume=250, vehicle_arrives_every_k_days=-1 ) feeder_lsv = LargeScheduledVehicle.create( vehicle_name="TestFeeder1", capacity_in_teu=schedule.average_vehicle_capacity, - moved_capacity=schedule.average_moved_capacity, + inbound_container_volume=schedule.average_inbound_container_volume, scheduled_arrival=datetime.datetime.now(), schedule=schedule ) diff --git a/conflowgen/tests/analyses/test_modal_split_analysis.py b/conflowgen/tests/analyses/test_modal_split_analysis.py index 6a5bd5a1..dc18598c 100644 --- a/conflowgen/tests/analyses/test_modal_split_analysis.py +++ b/conflowgen/tests/analyses/test_modal_split_analysis.py @@ -49,13 +49,13 @@ def test_transshipment_share_with_single_feeder(self): vehicle_arrives_at=one_week_later.date(), vehicle_arrives_at_time=one_week_later.time(), average_vehicle_capacity=300, - average_moved_capacity=300, + average_inbound_container_volume=300, vehicle_arrives_every_k_days=-1 ) feeder_lsv = LargeScheduledVehicle.create( vehicle_name="TestFeeder1", capacity_in_teu=300, - moved_capacity=schedule.average_moved_capacity, + inbound_container_volume=schedule.average_inbound_container_volume, scheduled_arrival=datetime.datetime.now(), schedule=schedule ) @@ -84,13 +84,13 @@ def test_outbound_with_single_feeder(self): vehicle_arrives_at=one_week_later.date(), vehicle_arrives_at_time=one_week_later.time(), average_vehicle_capacity=300, - average_moved_capacity=300, + average_inbound_container_volume=300, vehicle_arrives_every_k_days=-1 ) feeder_lsv = LargeScheduledVehicle.create( vehicle_name="TestFeeder1", capacity_in_teu=300, - moved_capacity=schedule.average_moved_capacity, + inbound_container_volume=schedule.average_inbound_container_volume, scheduled_arrival=datetime.datetime.now(), schedule=schedule ) @@ -120,13 +120,13 @@ def test_outbound_with_single_feeder_and_not_affecting_start_and_end(self): vehicle_arrives_at=one_week_later.date(), vehicle_arrives_at_time=one_week_later.time(), average_vehicle_capacity=300, - average_moved_capacity=300, + average_inbound_container_volume=300, vehicle_arrives_every_k_days=-1 ) feeder_lsv = LargeScheduledVehicle.create( vehicle_name="TestFeeder1", capacity_in_teu=300, - moved_capacity=schedule.average_moved_capacity, + inbound_container_volume=schedule.average_inbound_container_volume, scheduled_arrival=datetime.datetime.now(), schedule=schedule ) diff --git a/conflowgen/tests/analyses/test_modal_split_analysis_report.py b/conflowgen/tests/analyses/test_modal_split_analysis_report.py index 8d090bc4..4f34c408 100644 --- a/conflowgen/tests/analyses/test_modal_split_analysis_report.py +++ b/conflowgen/tests/analyses/test_modal_split_analysis_report.py @@ -22,14 +22,14 @@ def setup_feeder_data(): vehicle_arrives_at=one_week_later.date(), vehicle_arrives_at_time=one_week_later.time(), average_vehicle_capacity=300, - average_moved_capacity=300, + average_inbound_container_volume=300, vehicle_arrives_every_k_days=-1 ) schedule.save() feeder_lsv = LargeScheduledVehicle.create( vehicle_name="TestFeeder1", capacity_in_teu=300, - moved_capacity=schedule.average_moved_capacity, + inbound_container_volume=schedule.average_inbound_container_volume, scheduled_arrival=datetime.datetime.now(), schedule=schedule ) diff --git a/conflowgen/tests/analyses/test_quay_side_throughput_analysis.py b/conflowgen/tests/analyses/test_quay_side_throughput_analysis.py index 448b2a0b..7f452fea 100644 --- a/conflowgen/tests/analyses/test_quay_side_throughput_analysis.py +++ b/conflowgen/tests/analyses/test_quay_side_throughput_analysis.py @@ -48,12 +48,12 @@ def test_with_single_container(self): vehicle_arrives_at=now.date(), vehicle_arrives_at_time=now.time(), average_vehicle_capacity=300, - average_moved_capacity=300, + average_inbound_container_volume=300, ) feeder_lsv = LargeScheduledVehicle.create( vehicle_name="TestFeeder1", capacity_in_teu=300, - moved_capacity=schedule.average_moved_capacity, + inbound_container_volume=schedule.average_inbound_container_volume, scheduled_arrival=now, schedule=schedule ) @@ -92,12 +92,12 @@ def test_with_two_containers(self): vehicle_arrives_at=now.date(), vehicle_arrives_at_time=now.time(), average_vehicle_capacity=300, - average_moved_capacity=300, + average_inbound_container_volume=300, ) feeder_lsv = LargeScheduledVehicle.create( vehicle_name="TestFeeder1", capacity_in_teu=300, - moved_capacity=schedule.average_moved_capacity, + inbound_container_volume=schedule.average_inbound_container_volume, scheduled_arrival=now, schedule=schedule ) diff --git a/conflowgen/tests/analyses/test_quay_side_throughput_analysis_report.py b/conflowgen/tests/analyses/test_quay_side_throughput_analysis_report.py index 22c54ea3..a88a6127 100644 --- a/conflowgen/tests/analyses/test_quay_side_throughput_analysis_report.py +++ b/conflowgen/tests/analyses/test_quay_side_throughput_analysis_report.py @@ -25,12 +25,12 @@ def setup_feeder_data(): vehicle_arrives_at=now.date(), vehicle_arrives_at_time=now.time(), average_vehicle_capacity=300, - average_moved_capacity=300, + average_inbound_container_volume=300, ) feeder_lsv = LargeScheduledVehicle.create( vehicle_name="TestFeeder1", capacity_in_teu=300, - moved_capacity=schedule.average_moved_capacity, + inbound_container_volume=schedule.average_inbound_container_volume, scheduled_arrival=now, schedule=schedule ) @@ -142,12 +142,12 @@ def test_with_train(self): vehicle_arrives_at=now.date(), vehicle_arrives_at_time=now.time(), average_vehicle_capacity=90, - average_moved_capacity=90, + average_inbound_container_volume=90, ) train_lsv = LargeScheduledVehicle.create( vehicle_name="TestTrain1", capacity_in_teu=90, - moved_capacity=schedule.average_moved_capacity, + inbound_container_volume=schedule.average_inbound_container_volume, scheduled_arrival=now, schedule=schedule ) diff --git a/conflowgen/tests/analyses/test_truck_gate_throughput_analysis.py b/conflowgen/tests/analyses/test_truck_gate_throughput_analysis.py index 57a380bc..679935e5 100644 --- a/conflowgen/tests/analyses/test_truck_gate_throughput_analysis.py +++ b/conflowgen/tests/analyses/test_truck_gate_throughput_analysis.py @@ -46,12 +46,12 @@ def test_with_single_container(self): vehicle_arrives_at=now.date(), vehicle_arrives_at_time=now.time(), average_vehicle_capacity=300, - average_moved_capacity=300, + average_inbound_container_volume=300, ) feeder_lsv = LargeScheduledVehicle.create( vehicle_name="TestFeeder1", capacity_in_teu=300, - moved_capacity=schedule.average_moved_capacity, + inbound_container_volume=schedule.average_inbound_container_volume, scheduled_arrival=now, schedule=schedule ) @@ -90,12 +90,12 @@ def test_with_two_containers(self): vehicle_arrives_at=now.date(), vehicle_arrives_at_time=now.time(), average_vehicle_capacity=300, - average_moved_capacity=300, + average_inbound_container_volume=300, ) feeder_lsv = LargeScheduledVehicle.create( vehicle_name="TestFeeder1", capacity_in_teu=300, - moved_capacity=schedule.average_moved_capacity, + inbound_container_volume=schedule.average_inbound_container_volume, scheduled_arrival=now, schedule=schedule ) @@ -153,12 +153,12 @@ def test_with_two_containers_and_end_time(self): vehicle_arrives_at=now.date(), vehicle_arrives_at_time=now.time(), average_vehicle_capacity=300, - average_moved_capacity=300, + average_inbound_container_volume=300, ) feeder_lsv = LargeScheduledVehicle.create( vehicle_name="TestFeeder1", capacity_in_teu=300, - moved_capacity=schedule.average_moved_capacity, + inbound_container_volume=schedule.average_inbound_container_volume, scheduled_arrival=now, schedule=schedule ) diff --git a/conflowgen/tests/analyses/test_truck_gate_throughput_analysis_report.py b/conflowgen/tests/analyses/test_truck_gate_throughput_analysis_report.py index daaf4f01..e75c19eb 100644 --- a/conflowgen/tests/analyses/test_truck_gate_throughput_analysis_report.py +++ b/conflowgen/tests/analyses/test_truck_gate_throughput_analysis_report.py @@ -26,12 +26,12 @@ def setup_feeder_data(): vehicle_arrives_at=now.date(), vehicle_arrives_at_time=now.time(), average_vehicle_capacity=300, - average_moved_capacity=300, + average_inbound_container_volume=300, ) feeder_lsv = LargeScheduledVehicle.create( vehicle_name="TestFeeder1", capacity_in_teu=300, - moved_capacity=schedule.average_moved_capacity, + inbound_container_volume=schedule.average_inbound_container_volume, scheduled_arrival=now, schedule=schedule ) @@ -136,12 +136,12 @@ def test_with_train(self): vehicle_arrives_at=now.date(), vehicle_arrives_at_time=now.time(), average_vehicle_capacity=90, - average_moved_capacity=90, + average_inbound_container_volume=90, ) train_lsv = LargeScheduledVehicle.create( vehicle_name="TestTrain1", capacity_in_teu=90, - moved_capacity=schedule.average_moved_capacity, + inbound_container_volume=schedule.average_inbound_container_volume, scheduled_arrival=now, schedule=schedule ) diff --git a/conflowgen/tests/analyses/test_yard_capacity_analysis.py b/conflowgen/tests/analyses/test_yard_capacity_analysis.py index 08f55dc6..3c7ff8f8 100644 --- a/conflowgen/tests/analyses/test_yard_capacity_analysis.py +++ b/conflowgen/tests/analyses/test_yard_capacity_analysis.py @@ -48,12 +48,12 @@ def test_with_single_container(self): vehicle_arrives_at=now.date(), vehicle_arrives_at_time=now.time(), average_vehicle_capacity=300, - average_moved_capacity=300, + average_inbound_container_volume=300, ) feeder_lsv = LargeScheduledVehicle.create( vehicle_name="TestFeeder1", capacity_in_teu=300, - moved_capacity=schedule.average_moved_capacity, + inbound_container_volume=schedule.average_inbound_container_volume, scheduled_arrival=now, schedule=schedule ) @@ -96,12 +96,12 @@ def test_with_two_containers(self): vehicle_arrives_at=now.date(), vehicle_arrives_at_time=now.time(), average_vehicle_capacity=300, - average_moved_capacity=300, + average_inbound_container_volume=300, ) feeder_lsv = LargeScheduledVehicle.create( vehicle_name="TestFeeder1", capacity_in_teu=300, - moved_capacity=schedule.average_moved_capacity, + inbound_container_volume=schedule.average_inbound_container_volume, scheduled_arrival=now, schedule=schedule ) @@ -163,12 +163,12 @@ def test_with_container_group(self): vehicle_arrives_at=now.date(), vehicle_arrives_at_time=now.time(), average_vehicle_capacity=300, - average_moved_capacity=300, + average_inbound_container_volume=300, ) feeder_lsv_1 = LargeScheduledVehicle.create( vehicle_name="TestFeeder1", capacity_in_teu=300, - moved_capacity=schedule.average_moved_capacity, + inbound_container_volume=schedule.average_inbound_container_volume, scheduled_arrival=now, schedule=schedule ) @@ -178,7 +178,7 @@ def test_with_container_group(self): feeder_lsv_2 = LargeScheduledVehicle.create( vehicle_name="TestFeeder2", capacity_in_teu=300, - moved_capacity=schedule.average_moved_capacity, + inbound_container_volume=schedule.average_inbound_container_volume, scheduled_arrival=now + datetime.timedelta(hours=72), schedule=schedule ) diff --git a/conflowgen/tests/analyses/test_yard_capacity_analysis_report.py b/conflowgen/tests/analyses/test_yard_capacity_analysis_report.py index 9b4c6783..76bb2f48 100644 --- a/conflowgen/tests/analyses/test_yard_capacity_analysis_report.py +++ b/conflowgen/tests/analyses/test_yard_capacity_analysis_report.py @@ -24,12 +24,12 @@ def setup_feeder_data(): vehicle_arrives_at=now.date(), vehicle_arrives_at_time=now.time(), average_vehicle_capacity=300, - average_moved_capacity=300, + average_inbound_container_volume=300, ) feeder_lsv = LargeScheduledVehicle.create( vehicle_name="TestFeeder1", capacity_in_teu=300, - moved_capacity=schedule.average_moved_capacity, + inbound_container_volume=schedule.average_inbound_container_volume, scheduled_arrival=now, schedule=schedule ) diff --git a/conflowgen/tests/api/test_port_call_manager.py b/conflowgen/tests/api/test_port_call_manager.py index 04381d35..a6265761 100644 --- a/conflowgen/tests/api/test_port_call_manager.py +++ b/conflowgen/tests/api/test_port_call_manager.py @@ -11,7 +11,7 @@ class TestPortCallManager(unittest.TestCase): def setUp(self) -> None: self.port_call_manager = PortCallManager() - def test_add_vehicle(self): + def test_add_service_that_calls_terminal(self): feeder_service_name = "LX050" arrives_at = datetime.date(2021, 7, 9) time_of_the_day = datetime.time(hour=11) @@ -29,13 +29,13 @@ def test_add_vehicle(self): self.port_call_manager, 'has_schedule', return_value=False) as mock_has_schedule: - self.port_call_manager.add_vehicle( + self.port_call_manager.add_service_that_calls_terminal( vehicle_type=ModeOfTransport.feeder, service_name=feeder_service_name, vehicle_arrives_at=arrives_at, vehicle_arrives_at_time=time_of_the_day, average_vehicle_capacity=total_capacity, - average_moved_capacity=moved_capacity, + average_inbound_container_volume=moved_capacity, next_destinations=next_destinations ) mock_has_schedule.assert_called_once_with( @@ -48,9 +48,49 @@ def test_add_vehicle(self): vehicle_arrives_at=arrives_at, vehicle_arrives_at_time=time_of_the_day, average_vehicle_capacity=total_capacity, - average_moved_capacity=moved_capacity, + average_inbound_container_volume=moved_capacity, + next_destinations=next_destinations, + vehicle_arrives_every_k_days=7 + ) + + def test_add_vehicle(self): + arrives_at = datetime.date(2021, 7, 9) + time_of_the_day = datetime.time(hour=11) + total_capacity = 100 + moved_capacity = 3 + next_destinations = [ + ("DEBRV", 0.6), # 60% of the containers (in boxes) go here... + ("RULED", 0.4) # and the other 40% of the containers (in boxes) go here. + ] + with unittest.mock.patch.object( + self.port_call_manager.schedule_factory, + 'add_schedule', + return_value=None) as mock_add_schedule: + with unittest.mock.patch.object( + self.port_call_manager, + 'has_schedule', + return_value=False) as mock_has_schedule: + self.port_call_manager.add_vehicle( + vehicle_type=ModeOfTransport.feeder, + vehicle_arrives_at=arrives_at, + vehicle_arrives_at_time=time_of_the_day, + vehicle_capacity=total_capacity, + inbound_container_volume=moved_capacity, + next_destinations=next_destinations + ) + mock_has_schedule.assert_called_once_with( + vehicle_type=ModeOfTransport.feeder, + service_name=unittest.mock.ANY, + ) + mock_add_schedule.assert_called_once_with( + vehicle_type=ModeOfTransport.feeder, + service_name=unittest.mock.ANY, + vehicle_arrives_at=arrives_at, + vehicle_arrives_at_time=time_of_the_day, + average_vehicle_capacity=total_capacity, + average_inbound_container_volume=moved_capacity, next_destinations=next_destinations, - vehicle_arrives_every_k_days=None + vehicle_arrives_every_k_days=-1 ) def test_get(self): diff --git a/conflowgen/tests/application/reports/test_container_flow_statistics_report.py b/conflowgen/tests/application/reports/test_container_flow_statistics_report.py index 7e53b888..46f9321f 100644 --- a/conflowgen/tests/application/reports/test_container_flow_statistics_report.py +++ b/conflowgen/tests/application/reports/test_container_flow_statistics_report.py @@ -46,12 +46,12 @@ def _create_feeder(scheduled_arrival: datetime.datetime, service_name_suffix: st vehicle_arrives_at=scheduled_arrival.date(), vehicle_arrives_at_time=scheduled_arrival.time(), average_vehicle_capacity=300, - average_moved_capacity=300, + average_inbound_container_volume=300, ) feeder_lsv = LargeScheduledVehicle.create( vehicle_name="TestFeeder1", capacity_in_teu=schedule.average_vehicle_capacity, - moved_capacity=schedule.average_moved_capacity, + inbound_container_volume=schedule.average_inbound_container_volume, scheduled_arrival=scheduled_arrival, schedule=schedule ) @@ -135,7 +135,7 @@ def test_empty_ship_using_capacity_as_maximum(self): def test_empty_ship_using_buffer_as_maximum(self): now = datetime.datetime.now() feeder = self._create_feeder(scheduled_arrival=now) - feeder.large_scheduled_vehicle.moved_capacity = 20 + feeder.large_scheduled_vehicle.inbound_container_volume = 20 feeder.large_scheduled_vehicle.save() truck = self._create_truck(arrival=now) self._create_container_delivered_by_truck(truck) @@ -160,7 +160,7 @@ def test_empty_ship_using_buffer_as_maximum(self): def test_inbound_loaded_ship_using_capacity_as_maximum(self): now = datetime.datetime.now() feeder = self._create_feeder(scheduled_arrival=now) - feeder.large_scheduled_vehicle.moved_capacity = 20 + feeder.large_scheduled_vehicle.inbound_container_volume = 20 feeder.large_scheduled_vehicle.save() self._create_container_delivered_by_large_scheduled_vehicle(feeder) self.report.generate() @@ -184,7 +184,7 @@ def test_inbound_loaded_ship_using_capacity_as_maximum(self): def test_outbound_loaded_ship_using_buffer_as_maximum(self): now = datetime.datetime.now() feeder = self._create_feeder(scheduled_arrival=now) - feeder.large_scheduled_vehicle.moved_capacity = 20 + feeder.large_scheduled_vehicle.inbound_container_volume = 20 feeder.large_scheduled_vehicle.save() truck = self._create_truck(arrival=now) container = self._create_container_delivered_by_truck(truck) @@ -214,7 +214,7 @@ def test_outbound_loaded_ship_using_capacity_as_maximum(self): now = datetime.datetime.now() feeder = self._create_feeder(scheduled_arrival=now) feeder.large_scheduled_vehicle.capacity_in_teu = 20 - feeder.large_scheduled_vehicle.moved_capacity = 20 + feeder.large_scheduled_vehicle.inbound_container_volume = 20 feeder.large_scheduled_vehicle.save() truck = self._create_truck(arrival=now) container = self._create_container_delivered_by_truck(truck) @@ -244,11 +244,11 @@ def test_two_ships_one_with_inbound_traffic(self): now = datetime.datetime.now() feeder_1 = self._create_feeder(scheduled_arrival=now, service_name_suffix="1") feeder_1.large_scheduled_vehicle.capacity_in_teu = 20 - feeder_1.large_scheduled_vehicle.moved_capacity = 20 + feeder_1.large_scheduled_vehicle.inbound_container_volume = 20 feeder_1.large_scheduled_vehicle.save() feeder_2 = self._create_feeder(scheduled_arrival=now, service_name_suffix="2") feeder_2.large_scheduled_vehicle.capacity_in_teu = 20 - feeder_2.large_scheduled_vehicle.moved_capacity = 20 + feeder_2.large_scheduled_vehicle.inbound_container_volume = 20 feeder_2.large_scheduled_vehicle.save() self._create_container_delivered_by_large_scheduled_vehicle(feeder_1) @@ -276,11 +276,11 @@ def test_two_loaded_ships_one_with_outbound_traffic(self): now = datetime.datetime.now() feeder_1 = self._create_feeder(scheduled_arrival=now, service_name_suffix="1") feeder_1.large_scheduled_vehicle.capacity_in_teu = 20 - feeder_1.large_scheduled_vehicle.moved_capacity = 20 + feeder_1.large_scheduled_vehicle.inbound_container_volume = 20 feeder_1.large_scheduled_vehicle.save() feeder_2 = self._create_feeder(scheduled_arrival=now, service_name_suffix="2") feeder_2.large_scheduled_vehicle.capacity_in_teu = 20 - feeder_2.large_scheduled_vehicle.moved_capacity = 20 + feeder_2.large_scheduled_vehicle.inbound_container_volume = 20 feeder_2.large_scheduled_vehicle.save() truck = self._create_truck(arrival=now) diff --git a/conflowgen/tests/application/services/test_vehicle_capacity_manager.py b/conflowgen/tests/application/services/test_vehicle_capacity_manager.py new file mode 100644 index 00000000..55c781ef --- /dev/null +++ b/conflowgen/tests/application/services/test_vehicle_capacity_manager.py @@ -0,0 +1,275 @@ +import datetime +import unittest + +import parameterized + +from conflowgen.application.services.vehicle_capacity_manager import VehicleCapacityManager +from conflowgen.descriptive_datatypes import FlowDirection +from conflowgen.domain_models.container import Container +from conflowgen.domain_models.data_types.container_length import ContainerLength +from conflowgen.domain_models.data_types.mode_of_transport import ModeOfTransport +from conflowgen.domain_models.data_types.storage_requirement import StorageRequirement +from conflowgen.domain_models.large_vehicle_schedule import Schedule, Destination +from conflowgen.domain_models.vehicle import Train, LargeScheduledVehicle, Truck, Feeder +from conflowgen.tests.substitute_peewee_database import setup_sqlite_in_memory_db + + +class TestVehicleCapacityManager(unittest.TestCase): + + def setUp(self) -> None: + """Create container database in memory""" + sqlite_db = setup_sqlite_in_memory_db() + sqlite_db.create_tables([ + Container, + Schedule, + LargeScheduledVehicle, + Train, + Destination, + Truck, + Feeder, + ]) + + self.vehicle_capacity_manager = VehicleCapacityManager() + self.vehicle_capacity_manager.set_transportation_buffer(transportation_buffer=0) + + schedule_train = Schedule.create( + vehicle_type=ModeOfTransport.train, + service_name="TestServiceTrain", + vehicle_arrives_at=datetime.date(year=2024, month=5, day=26), + vehicle_arrives_at_time=datetime.time(hour=13, minute=15), + average_vehicle_capacity=90, + average_inbound_container_volume=90, + ) + self.train_lsv = LargeScheduledVehicle.create( + vehicle_name="TestTrain1", + capacity_in_teu=90, + inbound_container_volume=30, + scheduled_arrival=datetime.datetime(year=2024, month=5, day=26, hour=13, minute=15), + schedule=schedule_train + ) + self.train = Train.create( + large_scheduled_vehicle=self.train_lsv + ) + + schedule_feeder = Schedule.create( + vehicle_type=ModeOfTransport.feeder, + service_name="TestServiceFeeder", + vehicle_arrives_at=datetime.date(year=2024, month=5, day=26), + vehicle_arrives_at_time=datetime.time(hour=11, minute=15), + average_vehicle_capacity=1200, + average_inbound_container_volume=600, + ) + self.feeder_lsv = LargeScheduledVehicle.create( + vehicle_name="TestFeeder1", + capacity_in_teu=1200, + inbound_container_volume=600, + scheduled_arrival=datetime.datetime(year=2024, month=5, day=26, hour=11, minute=45), + schedule=schedule_feeder + ) + self.feeder = Feeder.create( + large_scheduled_vehicle=self.feeder_lsv + ) + + @parameterized.parameterized.expand([ + [FlowDirection.export_flow], + [FlowDirection.transshipment_flow] + ]) + def test_free_capacity_on_outbound_journey_without_any_containers_and_no_ramp_up_period( + self, flow_direction: FlowDirection + ): + """ + Independent of the flow direction, the outbound capacity should not change as long as no ramp-up period is + defined. + """ + free_capacity_on_train = self.vehicle_capacity_manager.get_free_capacity_for_outbound_journey( + self.train, flow_direction + ) + self.assertEqual(free_capacity_on_train, 30) + + free_capacity_on_feeder = self.vehicle_capacity_manager.get_free_capacity_for_outbound_journey( + self.feeder, flow_direction + ) + self.assertEqual(free_capacity_on_feeder, 600) + + + @parameterized.parameterized.expand([ + [FlowDirection.import_flow, 30, 600], + [FlowDirection.export_flow, 30, 600], + [FlowDirection.transshipment_flow, 6, 120], # downscale by 20% + ]) + def test_free_capacity_on_outbound_journey_without_any_containers_and_a_ramp_up_period( + self, flow_direction: FlowDirection, train_volume: int, feeder_volume: int + ): + """ + The outbound capacity should change for transshipment when a ramp-up period is defined and is applicable. + """ + self.vehicle_capacity_manager.set_ramp_up_and_down_times( + ramp_up_period_end=datetime.datetime(year=2024, month=5, day=27) # one day after the feeder and train + ) + train_volume_calc = self.vehicle_capacity_manager.get_free_capacity_for_outbound_journey( + self.train, flow_direction + ) + self.assertEqual( + train_volume, train_volume_calc, f"The used TEU capacity of the train is {train_volume} TEU." + ) + + feeder_volume_calc = self.vehicle_capacity_manager.get_free_capacity_for_outbound_journey( + self.feeder, flow_direction + ) + self.assertEqual( + feeder_volume, feeder_volume_calc, f"The used TEU capacity of the feeder is {feeder_volume} TEU." + ) + + def test_free_capacity_on_inbound_journey_without_any_containers_and_no_ramp_down_period(self): + """ + Independent of the flow direction, the inbound capacity should not change as long as no ramp-down period is + defined. + """ + free_capacity_in_teu = self.vehicle_capacity_manager.get_free_capacity_for_inbound_journey( + self.train + ) + self.assertEqual(free_capacity_in_teu, 30, "The used TEU capacity of the train is 30 TEU.") + + free_capacity_in_teu = self.vehicle_capacity_manager.get_free_capacity_for_inbound_journey( + self.feeder + ) + self.assertEqual(free_capacity_in_teu, 600, "The used TEU capacity of the feeder is 600 TEU.") + + def test_free_capacity_on_inbound_journey_without_any_containers_and_a_ramp_down_period(self): + """ + Independent of the flow direction, the inbound capacity is capped during the ramp-down period. + """ + self.vehicle_capacity_manager.set_ramp_up_and_down_times( + ramp_down_period_start=datetime.datetime(year=2024, month=5, day=25) # one day before the feeder and train + ) + + free_capacity_in_teu = self.vehicle_capacity_manager.get_free_capacity_for_inbound_journey( + self.train + ) + self.assertEqual( + free_capacity_in_teu, 6, "The used TEU capacity of the train is 20% of 30 TEU." + ) + + free_capacity_in_teu = self.vehicle_capacity_manager.get_free_capacity_for_inbound_journey( + self.feeder + ) + self.assertEqual( + free_capacity_in_teu, 120, "The used TEU capacity of the feeder is 20% of 600 TEU." + ) + + def test_free_capacity_for_one_teu(self): + """No ramp-up or ramp-down period applied""" + Container.create( + weight=20, + length=ContainerLength.twenty_feet, + storage_requirement=StorageRequirement.standard, + delivered_by=ModeOfTransport.truck, + picked_up_by=ModeOfTransport.train, + picked_up_by_initial=ModeOfTransport.feeder, + picked_up_by_large_scheduled_vehicle=self.train_lsv, + ) + + free_capacity_in_teu = self.vehicle_capacity_manager.get_free_capacity_for_outbound_journey( + self.train, FlowDirection.undefined + ) + self.assertEqual( + free_capacity_in_teu, 29, "The used TEU capacity of the train is 30 TEU, and 1 TEU is used by " + "the one container we just created.") + + def test_free_capacity_during_ramp_up_period_for_one_teu(self): + Container.create( + weight=20, + length=ContainerLength.forty_feet, + storage_requirement=StorageRequirement.standard, + delivered_by=ModeOfTransport.truck, + picked_up_by=ModeOfTransport.feeder, + picked_up_by_initial=ModeOfTransport.feeder, + picked_up_by_large_scheduled_vehicle=self.feeder_lsv, + ) + self.vehicle_capacity_manager.set_ramp_up_and_down_times( + # one day after the feeder + ramp_up_period_end=datetime.datetime(year=2024, month=5, day=27) + ) + free_capacity_in_teu = self.vehicle_capacity_manager.get_free_capacity_for_outbound_journey( + self.feeder, FlowDirection.transshipment_flow + ) + self.assertAlmostEqual(free_capacity_in_teu, 118, msg="20% of 600 TEU is 120, of that 2 TEU minus") + + def test_free_capacity_during_ramp_down_period_for_one_teu(self): + Container.create( + weight=20, + length=ContainerLength.twenty_feet, + storage_requirement=StorageRequirement.standard, + delivered_by=ModeOfTransport.train, + picked_up_by=ModeOfTransport.feeder, + picked_up_by_initial=ModeOfTransport.feeder, + delivered_by_large_scheduled_vehicle=self.train_lsv, + ) + self.vehicle_capacity_manager.set_ramp_up_and_down_times( + # one day before the train + ramp_down_period_start=datetime.datetime(year=2024, month=5, day=25) + ) + free_capacity_in_teu = self.vehicle_capacity_manager.get_free_capacity_for_inbound_journey( + self.train + ) + self.assertAlmostEqual( + free_capacity_in_teu, 5.0, msg="20% of 30 TEU is 6 TEU, of that 1 TEU is used " + ) + + def test_free_capacity_during_ramp_up_period_without_load(self): + self.vehicle_capacity_manager.set_ramp_up_and_down_times( + # one day before the train + ramp_down_period_start=datetime.datetime(year=2024, month=5, day=25) + ) + free_capacity_in_teu = self.vehicle_capacity_manager.get_free_capacity_for_inbound_journey( + self.train + ) + self.assertAlmostEqual(free_capacity_in_teu, 6) + + def test_free_capacity_for_one_ffe(self): + Container.create( + weight=20, + length=ContainerLength.forty_feet, + storage_requirement=StorageRequirement.standard, + delivered_by=ModeOfTransport.truck, + picked_up_by=ModeOfTransport.train, + picked_up_by_initial=ModeOfTransport.train, + picked_up_by_large_scheduled_vehicle=self.train_lsv, + ) + + free_capacity_in_teu = self.vehicle_capacity_manager.get_free_capacity_for_outbound_journey( + self.train, FlowDirection.undefined + ) + self.assertEqual(free_capacity_in_teu, 28) # 30 - 2.5 + + def test_free_capacity_for_45_foot_container(self): + Container.create( + weight=20, + length=ContainerLength.forty_five_feet, + storage_requirement=StorageRequirement.standard, + delivered_by=ModeOfTransport.truck, + picked_up_by=ModeOfTransport.train, + picked_up_by_initial=ModeOfTransport.feeder, + picked_up_by_large_scheduled_vehicle=self.train_lsv, + ) + + free_capacity_in_teu = self.vehicle_capacity_manager.get_free_capacity_for_outbound_journey( + self.train, FlowDirection.undefined + ) + self.assertEqual(free_capacity_in_teu, 27.75, "A 45' container uses 2.25 TEU") + + def test_free_capacity_for_other_container(self): + Container.create( + weight=20, + length=ContainerLength.other, + storage_requirement=StorageRequirement.standard, + delivered_by=ModeOfTransport.truck, + picked_up_by=ModeOfTransport.train, + picked_up_by_initial=ModeOfTransport.feeder, + picked_up_by_large_scheduled_vehicle=self.train_lsv, + ) + + free_capacity_in_teu = self.vehicle_capacity_manager.get_free_capacity_for_outbound_journey( + self.train, FlowDirection.undefined + ) + self.assertEqual(free_capacity_in_teu, 27.5, "30 TEU minus 2.5 TEU") diff --git a/conflowgen/tests/application/services/test_vehicle_countainer_volume_calculator.py b/conflowgen/tests/application/services/test_vehicle_countainer_volume_calculator.py new file mode 100644 index 00000000..2e644cf3 --- /dev/null +++ b/conflowgen/tests/application/services/test_vehicle_countainer_volume_calculator.py @@ -0,0 +1,191 @@ +import datetime +import unittest + +import parameterized + +from conflowgen.descriptive_datatypes import FlowDirection +from conflowgen.domain_models.data_types.mode_of_transport import ModeOfTransport +from conflowgen.application.services.vehicle_container_volume_calculator import VehicleContainerVolumeCalculator +from conflowgen.domain_models.large_vehicle_schedule import Schedule +from conflowgen.domain_models.vehicle import LargeScheduledVehicle, DeepSeaVessel, Feeder +from conflowgen.tests.substitute_peewee_database import setup_sqlite_in_memory_db + + +class TestVehicleContainerVolumeCalculator(unittest.TestCase): + + def setUp(self) -> None: + """Create database in memory""" + self.sqlite_db = setup_sqlite_in_memory_db() + self.sqlite_db.create_tables([ + LargeScheduledVehicle, + Schedule, + DeepSeaVessel, + Feeder, + ]) + self.calculator = VehicleContainerVolumeCalculator() + self.calculator.set_transportation_buffer(0) + + schedule_deep_sea_vessel = Schedule.create( + vehicle_type=ModeOfTransport.deep_sea_vessel, + service_name="TestService-DeepSeaVessel", + vehicle_arrives_at=datetime.date(year=2024, month=8, day=7), + vehicle_arrives_at_time=datetime.time(hour=13, minute=15), + average_vehicle_capacity=12000, + average_inbound_container_volume=3000, + ) + self.lsv_deep_sea_vessel = LargeScheduledVehicle.create( + vehicle_name="TestShip-DeepSeaVessel-1", + capacity_in_teu=12000, + inbound_container_volume=3000, + scheduled_arrival=datetime.datetime(year=2024, month=8, day=7, hour=13, minute=15), + schedule=schedule_deep_sea_vessel + ) + self.deep_sea_vessel = DeepSeaVessel.create( + large_scheduled_vehicle=self.lsv_deep_sea_vessel + ) + + schedule_feeder = Schedule.create( + vehicle_type=ModeOfTransport.feeder, + service_name="TestService-Feeder", + vehicle_arrives_at=datetime.date(year=2024, month=8, day=7), + vehicle_arrives_at_time=datetime.time(hour=13, minute=15), + average_vehicle_capacity=1200, + average_inbound_container_volume=600, + ) + self.lsv_feeder = LargeScheduledVehicle.create( + vehicle_name="TestShip-Feeder1", + capacity_in_teu=1200, + inbound_container_volume=600, + scheduled_arrival=datetime.datetime(year=2024, month=8, day=7, hour=11, minute=9), + schedule=schedule_feeder + ) + self.feeder = Feeder.create( + large_scheduled_vehicle=self.lsv_feeder + ) + + @parameterized.parameterized.expand([ + [flow_direction] for flow_direction in FlowDirection + ]) + def test_get_maximum_transported_container_volume_on_outbound_journey_and_no_ramp_up_period( + self, flow_direction: FlowDirection + ): + """ + Independent of the flow direction, the outbound capacity should not change as long as no ramp-up period is + defined. + """ + feeder_vessel_calc = self.calculator.get_maximum_transported_container_volume_on_outbound_journey( + self.lsv_feeder, flow_direction + ) + scaled_moved_container_volume, unscaled_moved_container_volume = feeder_vessel_calc + self.assertEqual(scaled_moved_container_volume, 600) + + deep_sea_vessel_calc = self.calculator.get_maximum_transported_container_volume_on_outbound_journey( + self.deep_sea_vessel, flow_direction + ) + scaled_moved_container_volume, unscaled_moved_container_volume = deep_sea_vessel_calc + self.assertEqual(scaled_moved_container_volume, 3000) + + + @parameterized.parameterized.expand([ + [FlowDirection.import_flow, 600, 3000], # normal values + [FlowDirection.export_flow, 600, 3000], # normal values + [FlowDirection.transshipment_flow, 120, 600], # downscaled by 20% + ]) + def test_get_maximum_transported_container_volume_on_outbound_journey_and_an_applicable_ramp_up_period( + self, flow_direction: FlowDirection, feeder_volume: int, deep_sea_volume: int + ): + """ + The outbound capacity should change for transshipment when a ramp-up period is defined and is applicable. + """ + self.calculator.set_ramp_up_and_down_times( + ramp_up_period_end=datetime.datetime(year=2024, month=8, day=8) # one day after the two vessels + ) + volume_feeder = self.calculator.get_maximum_transported_container_volume_on_outbound_journey( + self.feeder, flow_direction + ) + scaled_moved_container_volume, unscaled_moved_container_volume = volume_feeder + self.assertEqual(scaled_moved_container_volume, feeder_volume) + + volume_deep_sea_vessel = self.calculator.get_maximum_transported_container_volume_on_outbound_journey( + self.deep_sea_vessel, flow_direction + ) + scaled_moved_container_volume, unscaled_moved_container_volume = volume_deep_sea_vessel + self.assertEqual(scaled_moved_container_volume, deep_sea_volume) + + @parameterized.parameterized.expand([ + [FlowDirection.import_flow, 600, 3000], + [FlowDirection.export_flow, 600, 3000], + [FlowDirection.transshipment_flow, 600, 3000], + ]) + def test_get_maximum_transported_container_volume_on_outbound_journey_and_a_non_applicable_ramp_up_period( + self, flow_direction: FlowDirection, feeder_volume: int, deep_sea_volume: int + ): + """ + The outbound capacity should not change for transshipment when a ramp-up period is defined but lies in the past. + """ + self.calculator.set_ramp_up_and_down_times( + ramp_up_period_end=datetime.datetime(year=2024, month=8, day=6) # one day after the two vessels + ) + feeder_volume_calc = self.calculator.get_maximum_transported_container_volume_on_outbound_journey( + self.feeder, flow_direction + ) + scaled_moved_container_volume, unscaled_moved_container_volume = feeder_volume_calc + self.assertEqual(scaled_moved_container_volume, feeder_volume) + + deep_sea_volume_calc = self.calculator.get_maximum_transported_container_volume_on_outbound_journey( + self.deep_sea_vessel, flow_direction + ) + scaled_moved_container_volume, unscaled_moved_container_volume = deep_sea_volume_calc + self.assertEqual(scaled_moved_container_volume, deep_sea_volume) + + + def test_get_transported_container_volume_on_inbound_journey_and_no_ramp_down_period(self): + """ + Independent of the flow direction, the inbound capacity should not change as long as no ramp-down period is + defined. + """ + feeder_volume_calc = self.calculator.get_transported_container_volume_on_inbound_journey( + self.feeder + ) + self.assertEqual(feeder_volume_calc, 600) + + deep_sea_vessel_calc = self.calculator.get_transported_container_volume_on_inbound_journey( + self.deep_sea_vessel + ) + self.assertEqual(deep_sea_vessel_calc, 3000) + + def test_get_transported_container_volume_on_inbound_journey_and_an_applicable_ramp_down_period(self): + """ + Independent of the flow direction, the inbound capacity is capped during the ramp-down period. + """ + self.calculator.set_ramp_up_and_down_times( + ramp_down_period_start=datetime.datetime(year=2021, month=8, day=6) # one day before the feeder and train + ) + + feeder_vessel_calc = self.calculator.get_transported_container_volume_on_inbound_journey( + self.lsv_feeder + ) + self.assertEqual(feeder_vessel_calc, 120, "Downscaled by 20%") + + deep_sea_vessel_calc = self.calculator.get_transported_container_volume_on_inbound_journey( + self.lsv_deep_sea_vessel + ) + self.assertEqual(deep_sea_vessel_calc, 600, "Downscaled by 20%") + + def test_get_transported_container_volume_on_inbound_journey_and_a_non_applicable_ramp_down_period(self): + """ + Independent of the flow direction, the inbound capacity is capped during the ramp-down period. + """ + self.calculator.set_ramp_up_and_down_times( + ramp_down_period_start=datetime.datetime(year=2024, month=8, day=8) # one day after + ) + + feeder_volume_calc = self.calculator.get_transported_container_volume_on_inbound_journey( + self.lsv_feeder + ) + self.assertEqual(feeder_volume_calc, 600, "Nothing should be downscaled") + + deep_sea_vessel_volume_calc = self.calculator.get_transported_container_volume_on_inbound_journey( + self.lsv_deep_sea_vessel + ) + self.assertEqual(deep_sea_vessel_volume_calc, 3000, "Nothing should be downscaled") diff --git a/conflowgen/tests/data_summaries/test_data_summaries_cache.py b/conflowgen/tests/data_summaries/test_data_summaries_cache.py index 6448b8d9..df416120 100644 --- a/conflowgen/tests/data_summaries/test_data_summaries_cache.py +++ b/conflowgen/tests/data_summaries/test_data_summaries_cache.py @@ -132,7 +132,7 @@ def test_with_preview(self): vehicle_arrives_every_k_days=-1, vehicle_arrives_at_time=two_days_later.time(), average_vehicle_capacity=300, - average_moved_capacity=300 + average_inbound_container_volume=300 ) preview = self.preview.get_weekly_truck_arrivals(True, True) self.assertEqual(preview, {3: 12, 4: 48}, "Uncached result is incorrect") @@ -179,7 +179,7 @@ def test_with_adjusted_preview(self): vehicle_arrives_every_k_days=-1, vehicle_arrives_at_time=two_days_later.time(), average_vehicle_capacity=300, - average_moved_capacity=300 + average_inbound_container_volume=300 ) preview = self.preview.get_weekly_truck_arrivals(True, True) self.assertEqual(preview, {3: 12, 4: 48}, "Uncached result is incorrect") diff --git a/conflowgen/tests/domain_models/distribution_repositories/test_container_destination_distribution_repository.py b/conflowgen/tests/domain_models/distribution_repositories/test_container_destination_distribution_repository.py index b527c249..09b706d7 100644 --- a/conflowgen/tests/domain_models/distribution_repositories/test_container_destination_distribution_repository.py +++ b/conflowgen/tests/domain_models/distribution_repositories/test_container_destination_distribution_repository.py @@ -31,7 +31,7 @@ def test_set_distribution(self): vehicle_arrives_at=one_week_later.date(), vehicle_arrives_at_time=one_week_later.time(), average_vehicle_capacity=300, - average_moved_capacity=300, + average_inbound_container_volume=300, vehicle_arrives_every_k_days=-1 ) Schedule.create( @@ -40,7 +40,7 @@ def test_set_distribution(self): vehicle_arrives_at=one_week_later.date(), vehicle_arrives_at_time=one_week_later.time(), average_vehicle_capacity=300, - average_moved_capacity=300, + average_inbound_container_volume=300, vehicle_arrives_every_k_days=-1 ) destination_1 = Destination.create( @@ -77,7 +77,7 @@ def test_save_and_load_correspond_for_single_entry(self): vehicle_arrives_at=one_week_later.date(), vehicle_arrives_at_time=one_week_later.time(), average_vehicle_capacity=300, - average_moved_capacity=300, + average_inbound_container_volume=300, vehicle_arrives_every_k_days=-1 ) schedule.save() @@ -87,7 +87,7 @@ def test_save_and_load_correspond_for_single_entry(self): vehicle_arrives_at=one_week_later.date(), vehicle_arrives_at_time=one_week_later.time(), average_vehicle_capacity=300, - average_moved_capacity=300, + average_inbound_container_volume=300, vehicle_arrives_every_k_days=-1 ).save() destination_1 = Destination.create( @@ -121,7 +121,7 @@ def test_save_and_load_correspond_for_two_entries(self): vehicle_arrives_at=one_week_later.date(), vehicle_arrives_at_time=one_week_later.time(), average_vehicle_capacity=300, - average_moved_capacity=300, + average_inbound_container_volume=300, vehicle_arrives_every_k_days=-1 ) schedule_2 = Schedule.create( @@ -130,7 +130,7 @@ def test_save_and_load_correspond_for_two_entries(self): vehicle_arrives_at=one_week_later.date(), vehicle_arrives_at_time=one_week_later.time(), average_vehicle_capacity=12000, - average_moved_capacity=1000, + average_inbound_container_volume=1000, vehicle_arrives_every_k_days=-1 ) destination_1 = Destination.create( @@ -180,7 +180,7 @@ def test_validator(self): vehicle_arrives_at=one_week_later.date(), vehicle_arrives_at_time=one_week_later.time(), average_vehicle_capacity=300, - average_moved_capacity=300, + average_inbound_container_volume=300, vehicle_arrives_every_k_days=-1 ) Schedule.create( @@ -189,7 +189,7 @@ def test_validator(self): vehicle_arrives_at=one_week_later.date(), vehicle_arrives_at_time=one_week_later.time(), average_vehicle_capacity=300, - average_moved_capacity=300, + average_inbound_container_volume=300, vehicle_arrives_every_k_days=-1 ) destination_1 = Destination.create( diff --git a/conflowgen/tests/domain_models/factories/test_container_factory__create_for_large_scheduled_vehicle.py b/conflowgen/tests/domain_models/factories/test_container_factory__create_for_large_scheduled_vehicle.py index bf8410a8..f80e2c44 100644 --- a/conflowgen/tests/domain_models/factories/test_container_factory__create_for_large_scheduled_vehicle.py +++ b/conflowgen/tests/domain_models/factories/test_container_factory__create_for_large_scheduled_vehicle.py @@ -48,7 +48,7 @@ def setUp(self) -> None: vehicle_arrives_at=datetime.date(2021, 7, 9), vehicle_arrives_at_time=datetime.time(11), average_vehicle_capacity=800, - average_moved_capacity=3 + average_inbound_container_volume=3 ) self.feeders = FleetFactory().create_feeder_fleet( schedule=schedule, @@ -90,7 +90,7 @@ def test_create_containers_for_single_deep_sea_vessel_during_ramp_up_period(self vehicle_arrives_at=datetime.date(2021, 7, 9), vehicle_arrives_at_time=datetime.time(11), average_vehicle_capacity=24000, - average_moved_capacity=3000 + average_inbound_container_volume=3000 ) vessels = FleetFactory().create_deep_sea_vessel_fleet( schedule=schedule, @@ -120,15 +120,15 @@ def test_create_containers_for_single_deep_sea_vessel_during_ramp_down_period(se schedule = Schedule.create( service_name="SunExpress", vehicle_type=ModeOfTransport.deep_sea_vessel, - vehicle_arrives_at=datetime.date(2021, 7, 9), + vehicle_arrives_at=datetime.date(2024, 7, 9), vehicle_arrives_at_time=datetime.time(11), average_vehicle_capacity=24000, - average_moved_capacity=3000 + average_inbound_container_volume=3000 ) vessels = FleetFactory().create_deep_sea_vessel_fleet( schedule=schedule, - first_at=datetime.date(2021, 7, 8), - latest_at=datetime.date(2021, 7, 10) + first_at=datetime.date(2024, 7, 8), + latest_at=datetime.date(2024, 7, 10) ) self.assertEqual( len(vessels), @@ -138,13 +138,13 @@ def test_create_containers_for_single_deep_sea_vessel_during_ramp_down_period(se self.container_factory.set_ramp_up_and_down_times( ramp_up_period_end=None, - ramp_down_period_start=datetime.datetime(2021, 7, 8) + ramp_down_period_start=datetime.datetime(2024, 7, 8) ) # noinspection PyTypeChecker containers = self.container_factory.create_containers_for_large_scheduled_vehicle(vessel) - container_volume = sum([c.occupied_teu for c in containers]) + container_volume_in_teu = sum([c.occupied_teu for c in containers]) - self.assertGreater(container_volume, 290, "A bit less than 3000 is acceptable but common!") - self.assertLess(container_volume, 310, "A bit more than 3000 is acceptable but common!") + self.assertGreater(container_volume_in_teu, 500, "A bit less than 600 is acceptable but common!") + self.assertLess(container_volume_in_teu, 700, "A bit more than 600 is acceptable but common!") diff --git a/conflowgen/tests/domain_models/factories/test_container_factory__create_for_truck.py b/conflowgen/tests/domain_models/factories/test_container_factory__create_for_truck.py index b873d5e0..d9ed2b85 100644 --- a/conflowgen/tests/domain_models/factories/test_container_factory__create_for_truck.py +++ b/conflowgen/tests/domain_models/factories/test_container_factory__create_for_truck.py @@ -53,7 +53,7 @@ def setUp(self) -> None: vehicle_arrives_at=datetime.date(2021, 7, 9), vehicle_arrives_at_time=datetime.time(11), average_vehicle_capacity=800, - average_moved_capacity=1 + average_inbound_container_volume=1 ) feeders = FleetFactory().create_feeder_fleet( schedule=schedule, diff --git a/conflowgen/tests/domain_models/factories/test_fleet_factory__create_feeder_fleet.py b/conflowgen/tests/domain_models/factories/test_fleet_factory__create_feeder_fleet.py index 03e00bf8..315ff443 100644 --- a/conflowgen/tests/domain_models/factories/test_fleet_factory__create_feeder_fleet.py +++ b/conflowgen/tests/domain_models/factories/test_fleet_factory__create_feeder_fleet.py @@ -26,7 +26,7 @@ def test_create_feeder_fleet(self) -> None: vehicle_arrives_at=datetime.date(2021, 7, 9), vehicle_arrives_at_time=datetime.time(11), average_vehicle_capacity=800, - average_moved_capacity=50 + average_inbound_container_volume=50 ) feeders = self.fleet_factory.create_feeder_fleet( schedule=schedule, diff --git a/conflowgen/tests/domain_models/factories/test_schedule_factory.py b/conflowgen/tests/domain_models/factories/test_schedule_factory.py index a40d20ab..f4c9d77b 100644 --- a/conflowgen/tests/domain_models/factories/test_schedule_factory.py +++ b/conflowgen/tests/domain_models/factories/test_schedule_factory.py @@ -28,7 +28,7 @@ def test_add_schedule(self) -> None: vehicle_arrives_at=datetime.date(2021, 7, 9), vehicle_arrives_at_time=datetime.time(11), average_vehicle_capacity=800, - average_moved_capacity=1, + average_inbound_container_volume=1, next_destinations=[ ("DEBRV", 0.6), ("CNSHG", 0.4) @@ -39,7 +39,7 @@ def test_add_schedule(self) -> None: self.assertEqual(schedule.vehicle_type, ModeOfTransport.feeder) self.assertEqual(schedule.vehicle_arrives_at, datetime.date(2021, 7, 9)) self.assertEqual(schedule.average_vehicle_capacity, 800) - self.assertEqual(schedule.average_moved_capacity, 1) + self.assertEqual(schedule.average_inbound_container_volume, 1) next_destinations = Destination.select().where(Destination.belongs_to_schedule == schedule) next_destinations = list(next_destinations) self.assertEqual(len(next_destinations), 2) @@ -54,7 +54,7 @@ def test_repeated_add_schedule(self) -> None: vehicle_arrives_at=datetime.date(2021, 7, 9), vehicle_arrives_at_time=datetime.time(11), average_vehicle_capacity=800, - average_moved_capacity=1, + average_inbound_container_volume=1, next_destinations=[ ("DEBRV", 0.6), ("CNSHG", 0.4) @@ -67,7 +67,7 @@ def test_repeated_add_schedule(self) -> None: vehicle_arrives_at=datetime.date(2021, 7, 10), vehicle_arrives_at_time=datetime.time(11), average_vehicle_capacity=800, - average_moved_capacity=1, + average_inbound_container_volume=1, next_destinations=[ ("DEBRV", 0.6), ("CNSHG", 0.4) @@ -82,7 +82,7 @@ def test_get_schedule(self) -> None: vehicle_arrives_at=datetime.date(2021, 7, 9), vehicle_arrives_at_time=datetime.time(11), average_vehicle_capacity=800, - average_moved_capacity=1, + average_inbound_container_volume=1, next_destinations=[ ("DEBRV", 0.6), ("CNSHG", 0.4) @@ -93,7 +93,7 @@ def test_get_schedule(self) -> None: self.assertEqual(schedule.vehicle_type, ModeOfTransport.feeder) self.assertEqual(schedule.vehicle_arrives_at, datetime.date(2021, 7, 9)) self.assertEqual(schedule.average_vehicle_capacity, 800) - self.assertEqual(schedule.average_moved_capacity, 1) + self.assertEqual(schedule.average_inbound_container_volume, 1) next_destinations = Destination.select().where(Destination.belongs_to_schedule == schedule) next_destinations = list(next_destinations) self.assertEqual(len(next_destinations), 2) diff --git a/conflowgen/tests/domain_models/factories/test_vehicle_factory__create_barge.py b/conflowgen/tests/domain_models/factories/test_vehicle_factory__create_barge.py index 5daf03f7..cf8c426d 100644 --- a/conflowgen/tests/domain_models/factories/test_vehicle_factory__create_barge.py +++ b/conflowgen/tests/domain_models/factories/test_vehicle_factory__create_barge.py @@ -25,11 +25,11 @@ def test_create_normal_barge(self) -> None: vehicle_type=ModeOfTransport.barge, vehicle_arrives_at=datetime.datetime.now(), average_vehicle_capacity=60, - average_moved_capacity=30 + average_inbound_container_volume=30 ) self.vehicle_factory.create_barge( capacity_in_teu=60, - moved_capacity=30, + inbound_container_volume=30, scheduled_arrival=datetime.datetime.now(), schedule=schedule ) @@ -40,19 +40,19 @@ def test_create_unrealistic_barge(self) -> None: vehicle_type=ModeOfTransport.barge, vehicle_arrives_at=datetime.datetime.now(), average_vehicle_capacity=800, - average_moved_capacity=50 + average_inbound_container_volume=50 ) with self.assertRaises(UnrealisticValuesException): self.vehicle_factory.create_barge( capacity_in_teu=-1, - moved_capacity=1, + inbound_container_volume=1, scheduled_arrival=datetime.datetime.now(), schedule=schedule ) with self.assertRaises(UnrealisticValuesException): self.vehicle_factory.create_barge( capacity_in_teu=1, - moved_capacity=-1, + inbound_container_volume=-1, scheduled_arrival=datetime.datetime.now(), schedule=schedule ) diff --git a/conflowgen/tests/domain_models/factories/test_vehicle_factory__create_deep_sea_vessel.py b/conflowgen/tests/domain_models/factories/test_vehicle_factory__create_deep_sea_vessel.py index d68204c9..4704b6ed 100644 --- a/conflowgen/tests/domain_models/factories/test_vehicle_factory__create_deep_sea_vessel.py +++ b/conflowgen/tests/domain_models/factories/test_vehicle_factory__create_deep_sea_vessel.py @@ -25,11 +25,11 @@ def test_create_normal_deep_sea_vessel(self) -> None: vehicle_type=ModeOfTransport.deep_sea_vessel, vehicle_arrives_at=datetime.datetime.now(), average_vehicle_capacity=800, - average_moved_capacity=50 + average_inbound_container_volume=50 ) self.vehicle_factory.create_deep_sea_vessel( capacity_in_teu=800, - moved_capacity=50, + inbound_container_volume=50, scheduled_arrival=datetime.datetime.now(), schedule=schedule ) @@ -40,26 +40,26 @@ def test_create_unrealistic_deep_sea_vessel(self) -> None: vehicle_type=ModeOfTransport.deep_sea_vessel, vehicle_arrives_at=datetime.datetime.now(), average_vehicle_capacity=800, - average_moved_capacity=50 + average_inbound_container_volume=50 ) with self.assertRaises(UnrealisticValuesException): self.vehicle_factory.create_deep_sea_vessel( capacity_in_teu=-1, - moved_capacity=1, + inbound_container_volume=1, scheduled_arrival=datetime.datetime.now(), schedule=schedule ) with self.assertRaises(UnrealisticValuesException): self.vehicle_factory.create_deep_sea_vessel( capacity_in_teu=1, - moved_capacity=-1, + inbound_container_volume=-1, scheduled_arrival=datetime.datetime.now(), schedule=schedule ) with self.assertRaises(UnrealisticValuesException): self.vehicle_factory.create_deep_sea_vessel( capacity_in_teu=50, - moved_capacity=100, + inbound_container_volume=100, scheduled_arrival=datetime.datetime.now(), schedule=schedule ) diff --git a/conflowgen/tests/domain_models/factories/test_vehicle_factory__create_feeder.py b/conflowgen/tests/domain_models/factories/test_vehicle_factory__create_feeder.py index d7894cfc..09517939 100644 --- a/conflowgen/tests/domain_models/factories/test_vehicle_factory__create_feeder.py +++ b/conflowgen/tests/domain_models/factories/test_vehicle_factory__create_feeder.py @@ -25,11 +25,11 @@ def test_create_normal_feeder(self) -> None: vehicle_type=ModeOfTransport.feeder, vehicle_arrives_at=datetime.datetime.now(), average_vehicle_capacity=800, - average_moved_capacity=50 + average_inbound_container_volume=50 ) self.vehicle_factory.create_feeder( capacity_in_teu=800, - moved_capacity=50, + inbound_container_volume=50, scheduled_arrival=datetime.datetime.now(), schedule=schedule ) @@ -40,12 +40,12 @@ def test_create_unrealistic_feeder(self) -> None: vehicle_type=ModeOfTransport.feeder, vehicle_arrives_at=datetime.datetime.now(), average_vehicle_capacity=800, - average_moved_capacity=50 + average_inbound_container_volume=50 ) with self.assertRaises(UnrealisticValuesException): self.vehicle_factory.create_feeder( capacity_in_teu=-1, - moved_capacity=1, + inbound_container_volume=1, scheduled_arrival=datetime.datetime.now(), schedule=schedule ) @@ -53,7 +53,7 @@ def test_create_unrealistic_feeder(self) -> None: with self.assertRaises(UnrealisticValuesException): self.vehicle_factory.create_feeder( capacity_in_teu=1, - moved_capacity=-1, + inbound_container_volume=-1, scheduled_arrival=datetime.datetime.now(), schedule=schedule ) diff --git a/conflowgen/tests/domain_models/factories/test_vehicle_factory__create_train.py b/conflowgen/tests/domain_models/factories/test_vehicle_factory__create_train.py index d613dbc5..2aa44c11 100644 --- a/conflowgen/tests/domain_models/factories/test_vehicle_factory__create_train.py +++ b/conflowgen/tests/domain_models/factories/test_vehicle_factory__create_train.py @@ -25,11 +25,11 @@ def test_create_normal_train(self) -> None: vehicle_type=ModeOfTransport.train, vehicle_arrives_at=datetime.datetime.now(), average_vehicle_capacity=90, - average_moved_capacity=90 + average_inbound_container_volume=90 ) self.vehicle_factory.create_train( capacity_in_teu=90, - moved_capacity=90, + inbound_container_volume=90, scheduled_arrival=datetime.datetime.now(), schedule=schedule ) @@ -40,12 +40,12 @@ def test_create_unrealistic_train(self) -> None: vehicle_type=ModeOfTransport.train, vehicle_arrives_at=datetime.datetime.now(), average_vehicle_capacity=800, - average_moved_capacity=50 + average_inbound_container_volume=50 ) with self.assertRaises(UnrealisticValuesException): self.vehicle_factory.create_train( capacity_in_teu=-1, - moved_capacity=1, + inbound_container_volume=1, scheduled_arrival=datetime.datetime.now(), schedule=schedule ) diff --git a/conflowgen/tests/domain_models/reposistories/test_large_scheduled_vehicle_repository.py b/conflowgen/tests/domain_models/reposistories/test_large_scheduled_vehicle_repository.py deleted file mode 100644 index afdc411f..00000000 --- a/conflowgen/tests/domain_models/reposistories/test_large_scheduled_vehicle_repository.py +++ /dev/null @@ -1,149 +0,0 @@ -import datetime -import unittest - -from conflowgen.descriptive_datatypes import FlowDirection -from conflowgen.domain_models.container import Container -from conflowgen.domain_models.data_types.container_length import ContainerLength -from conflowgen.domain_models.data_types.mode_of_transport import ModeOfTransport -from conflowgen.domain_models.data_types.storage_requirement import StorageRequirement -from conflowgen.domain_models.large_vehicle_schedule import Schedule, Destination -from conflowgen.domain_models.repositories.large_scheduled_vehicle_repository import LargeScheduledVehicleRepository -from conflowgen.domain_models.vehicle import Train, LargeScheduledVehicle, Truck -from conflowgen.tests.substitute_peewee_database import setup_sqlite_in_memory_db - - -class TestLargeScheduledVehicleRepository(unittest.TestCase): - - def setUp(self) -> None: - """Create container database in memory""" - sqlite_db = setup_sqlite_in_memory_db() - sqlite_db.create_tables([ - Container, - Schedule, - LargeScheduledVehicle, - Train, - Destination, - Truck - ]) - self.lsv_repository = LargeScheduledVehicleRepository() - self.lsv_repository.set_transportation_buffer(transportation_buffer=0) - schedule = Schedule.create( - vehicle_type=ModeOfTransport.train, - service_name="TestService", - vehicle_arrives_at=datetime.date(year=2021, month=8, day=7), - vehicle_arrives_at_time=datetime.time(hour=13, minute=15), - average_vehicle_capacity=90, - average_moved_capacity=90, - ) - self.train_lsv = LargeScheduledVehicle.create( - vehicle_name="TestTrain1", - capacity_in_teu=90, - moved_capacity=30, - scheduled_arrival=datetime.datetime(year=2021, month=8, day=7, hour=13, minute=15), - schedule=schedule - ) - self.train = Train.create( - large_scheduled_vehicle=self.train_lsv - ) - - def test_free_capacity_for_one_teu(self): - Container.create( - weight=20, - length=ContainerLength.twenty_feet, - storage_requirement=StorageRequirement.standard, - delivered_by=ModeOfTransport.truck, - picked_up_by=ModeOfTransport.train, - picked_up_by_initial=ModeOfTransport.feeder, - picked_up_by_large_scheduled_vehicle=self.train_lsv, - ) - - free_capacity_in_teu = self.lsv_repository.get_free_capacity_for_outbound_journey( - self.train, FlowDirection.undefined - ) - self.assertEqual(free_capacity_in_teu, 29) # 30 - 1 - - def test_free_capacity_during_ramp_down_period_for_one_teu(self): - Container.create( - weight=20, - length=ContainerLength.twenty_feet, - storage_requirement=StorageRequirement.standard, - delivered_by=ModeOfTransport.truck, - picked_up_by=ModeOfTransport.train, - picked_up_by_initial=ModeOfTransport.feeder, - picked_up_by_large_scheduled_vehicle=self.train_lsv, - ) - self.lsv_repository.set_ramp_up_and_down_times( - # one day after the train - ramp_up_period_end=datetime.datetime(year=2021, month=8, day=8, hour=13, minute=15) - ) - free_capacity_in_teu = self.lsv_repository.get_free_capacity_for_outbound_journey( - self.train, FlowDirection.transshipment_flow - ) - self.assertAlmostEqual(free_capacity_in_teu, 2.9) # (30 - 1) * 10% - - def test_free_capacity_during_ramp_up_period_for_one_teu(self): - Container.create( - weight=20, - length=ContainerLength.twenty_feet, - storage_requirement=StorageRequirement.standard, - delivered_by=ModeOfTransport.train, - picked_up_by=ModeOfTransport.feeder, - picked_up_by_initial=ModeOfTransport.feeder, - delivered_by_large_scheduled_vehicle=self.train_lsv, - ) - self.lsv_repository.set_ramp_up_and_down_times( - # one day before the train - ramp_down_period_start=datetime.datetime(year=2021, month=8, day=6, hour=13, minute=15) - ) - free_capacity_in_teu = self.lsv_repository.get_free_capacity_for_inbound_journey( - self.train - ) - self.assertAlmostEqual(free_capacity_in_teu, 2.9) # (30 - 1) * 10% - - def test_free_capacity_for_one_ffe(self): - Container.create( - weight=20, - length=ContainerLength.forty_feet, - storage_requirement=StorageRequirement.standard, - delivered_by=ModeOfTransport.truck, - picked_up_by=ModeOfTransport.train, - picked_up_by_initial=ModeOfTransport.train, - picked_up_by_large_scheduled_vehicle=self.train_lsv, - ) - - free_capacity_in_teu = self.lsv_repository.get_free_capacity_for_outbound_journey( - self.train, FlowDirection.undefined - ) - self.assertEqual(free_capacity_in_teu, 28) # 30 - 2.5 - - def test_free_capacity_for_45_foot_container(self): - Container.create( - weight=20, - length=ContainerLength.forty_five_feet, - storage_requirement=StorageRequirement.standard, - delivered_by=ModeOfTransport.truck, - picked_up_by=ModeOfTransport.train, - picked_up_by_initial=ModeOfTransport.feeder, - picked_up_by_large_scheduled_vehicle=self.train_lsv, - ) - - free_capacity_in_teu = self.lsv_repository.get_free_capacity_for_outbound_journey( - self.train, FlowDirection.undefined - ) - self.assertEqual(free_capacity_in_teu, 27.75) # 30 - 2.25 - - def test_free_capacity_for_other_container(self): - Container.create( - weight=20, - length=ContainerLength.other, - storage_requirement=StorageRequirement.standard, - delivered_by=ModeOfTransport.truck, - picked_up_by=ModeOfTransport.train, - picked_up_by_initial=ModeOfTransport.feeder, - picked_up_by_large_scheduled_vehicle=self.train_lsv, - ) - - free_capacity_in_teu = self.lsv_repository.get_free_capacity_for_outbound_journey( - self.train, FlowDirection.undefined - ) - self.assertEqual(free_capacity_in_teu, 27.5) # 30 - 2.5 diff --git a/conflowgen/tests/domain_models/reposistories/test_schedule_repository.py b/conflowgen/tests/domain_models/reposistories/test_schedule_repository.py index 15629c6d..bd323fee 100644 --- a/conflowgen/tests/domain_models/reposistories/test_schedule_repository.py +++ b/conflowgen/tests/domain_models/reposistories/test_schedule_repository.py @@ -50,13 +50,13 @@ def test_find_vehicle_if_in_time_range(self): vehicle_arrives_at=datetime.date(year=2021, month=8, day=7), vehicle_arrives_at_time=datetime.time(hour=13, minute=15), average_vehicle_capacity=90, - average_moved_capacity=1, + average_inbound_container_volume=1, ) train_moves_this_capacity = 7 train_lsv = LargeScheduledVehicle.create( vehicle_name="TestTrain1", capacity_in_teu=90, - moved_capacity=train_moves_this_capacity, + inbound_container_volume=train_moves_this_capacity, scheduled_arrival=datetime.datetime(year=2021, month=8, day=7, hour=13, minute=15), schedule=schedule ) @@ -83,13 +83,13 @@ def test_export_buffer_below_capacity(self): vehicle_arrives_at=datetime.date(year=2021, month=8, day=7), vehicle_arrives_at_time=datetime.time(hour=13, minute=15), average_vehicle_capacity=90, - average_moved_capacity=1, + average_inbound_container_volume=1, ) train_moves_this_capacity = 7 train_lsv = LargeScheduledVehicle.create( vehicle_name="TestTrain1", capacity_in_teu=90, - moved_capacity=train_moves_this_capacity, + inbound_container_volume=train_moves_this_capacity, scheduled_arrival=datetime.datetime(year=2021, month=8, day=7, hour=13, minute=15), schedule=schedule ) @@ -115,13 +115,13 @@ def test_export_buffer_above_capacity(self): vehicle_arrives_at=datetime.date(year=2021, month=8, day=7), vehicle_arrives_at_time=datetime.time(hour=13, minute=15), average_vehicle_capacity=4, - average_moved_capacity=2, + average_inbound_container_volume=2, ) train_moves_this_capacity = 7 train_lsv = LargeScheduledVehicle.create( vehicle_name="TestTrain1", capacity_in_teu=8, - moved_capacity=train_moves_this_capacity, + inbound_container_volume=train_moves_this_capacity, scheduled_arrival=datetime.datetime(year=2021, month=8, day=7, hour=13, minute=15), schedule=schedule ) @@ -146,13 +146,13 @@ def test_ignore_vehicle_outside_time_range(self): vehicle_arrives_at=datetime.date(year=2021, month=8, day=7), vehicle_arrives_at_time=datetime.time(hour=13, minute=15), average_vehicle_capacity=90, - average_moved_capacity=1, + average_inbound_container_volume=1, ) train_moves_this_capacity = 7 LargeScheduledVehicle.create( vehicle_name="TestTrain1", capacity_in_teu=90, - moved_capacity=train_moves_this_capacity, + inbound_container_volume=train_moves_this_capacity, scheduled_arrival=datetime.datetime(year=2021, month=8, day=7, hour=13, minute=15), schedule=schedule ) @@ -174,13 +174,13 @@ def test_check_used_20_foot_containers(self): vehicle_arrives_at=datetime.date(year=2021, month=8, day=7), vehicle_arrives_at_time=datetime.time(hour=13, minute=15), average_vehicle_capacity=90, - average_moved_capacity=90, + average_inbound_container_volume=90, ) train_moves_this_capacity_in_teu = 2 train_lsv = LargeScheduledVehicle.create( vehicle_name="TestTrain1", capacity_in_teu=90, - moved_capacity=train_moves_this_capacity_in_teu, + inbound_container_volume=train_moves_this_capacity_in_teu, scheduled_arrival=datetime.datetime(year=2021, month=8, day=7, hour=13, minute=15), schedule=schedule ) @@ -216,13 +216,13 @@ def test_check_used_40_foot_containers(self): vehicle_arrives_at=datetime.date(year=2021, month=8, day=7), vehicle_arrives_at_time=datetime.time(hour=13, minute=15), average_vehicle_capacity=90, - average_moved_capacity=90, + average_inbound_container_volume=90, ) train_moves_this_capacity_in_teu = 3 train_lsv = LargeScheduledVehicle.create( vehicle_name="TestTrain1", capacity_in_teu=90, - moved_capacity=train_moves_this_capacity_in_teu, + inbound_container_volume=train_moves_this_capacity_in_teu, scheduled_arrival=datetime.datetime(year=2021, month=8, day=7, hour=13, minute=15), schedule=schedule ) @@ -250,20 +250,20 @@ def test_check_used_40_foot_containers(self): self.assertEqual(len(vehicles), 1) - def test_use_lsv_repository(self): + def test_use_vehicle_capacity_manager(self): schedule = Schedule.create( vehicle_type=ModeOfTransport.train, service_name="TestService", vehicle_arrives_at=datetime.date(year=2021, month=8, day=7), vehicle_arrives_at_time=datetime.time(hour=13, minute=15), average_vehicle_capacity=90, - average_moved_capacity=90, + average_inbound_container_volume=90, ) train_moves_this_capacity_in_teu = 3 train_lsv = LargeScheduledVehicle.create( vehicle_name="TestTrain1", capacity_in_teu=90, - moved_capacity=train_moves_this_capacity_in_teu, + inbound_container_volume=train_moves_this_capacity_in_teu, scheduled_arrival=datetime.datetime(year=2021, month=8, day=7, hour=13, minute=15), schedule=schedule ) @@ -283,7 +283,7 @@ def test_use_lsv_repository(self): ) with unittest.mock.patch.object( - self.schedule_repository.large_scheduled_vehicle_repository, + self.schedule_repository.vehicle_capacity_manager, 'get_free_capacity_for_outbound_journey', return_value=1) as mock_method: self.schedule_repository.get_departing_vehicles( @@ -297,7 +297,7 @@ def test_use_lsv_repository(self): def test_set_ramp_up_and_down_period(self): with unittest.mock.patch.object( - self.schedule_repository.large_scheduled_vehicle_repository, + self.schedule_repository.vehicle_capacity_manager, 'set_ramp_up_and_down_times', return_value=None) as mock_method: self.schedule_repository.set_ramp_up_and_down_times( diff --git a/conflowgen/tests/domain_models/test_container.py b/conflowgen/tests/domain_models/test_container.py index 941dcc3f..1ac20569 100644 --- a/conflowgen/tests/domain_models/test_container.py +++ b/conflowgen/tests/domain_models/test_container.py @@ -5,6 +5,7 @@ import unittest from dataclasses import dataclass +import parameterized from peewee import IntegrityError from conflowgen.descriptive_datatypes import FlowDirection @@ -20,6 +21,7 @@ class TestContainer(unittest.TestCase): """ Rudimentarily check if peewee can handle container entries in the database. + Also check whether the class properties work. """ def setUp(self) -> None: @@ -152,28 +154,62 @@ def __init__(self, value: int, name: str): with self.assertRaises(NoPickupVehicleException): container.get_departure_time() - def test_occupied_teu(self): + @parameterized.parameterized.expand([ + [ContainerLength.twenty_feet, 1], + [ContainerLength.forty_feet, 2], + [ContainerLength.forty_five_feet, 2.25], + [ContainerLength.other, 2.5] + ]) + def test_occupied_teu(self, container_size, teu): """Test whether the container size is correctly converted to TEU""" container = Container.create( weight=10, delivered_by=ModeOfTransport.barge, picked_up_by=ModeOfTransport.truck, picked_up_by_initial=ModeOfTransport.deep_sea_vessel, - length=ContainerLength.forty_feet, + length=container_size, storage_requirement=StorageRequirement.standard ) - self.assertEqual(2, container.occupied_teu) - ... # TODO: also test other cases: 20', 45', other - - def test_flow_direction(self): + self.assertEqual(teu, container.occupied_teu) + + @parameterized.parameterized.expand([ + [ModeOfTransport.deep_sea_vessel, ModeOfTransport.deep_sea_vessel, FlowDirection.transshipment_flow], + [ModeOfTransport.deep_sea_vessel, ModeOfTransport.feeder, FlowDirection.transshipment_flow], + [ModeOfTransport.feeder, ModeOfTransport.deep_sea_vessel, FlowDirection.transshipment_flow], + [ModeOfTransport.feeder, ModeOfTransport.feeder, FlowDirection.transshipment_flow], + + [ModeOfTransport.deep_sea_vessel, ModeOfTransport.truck, FlowDirection.import_flow], + [ModeOfTransport.deep_sea_vessel, ModeOfTransport.barge, FlowDirection.import_flow], + [ModeOfTransport.deep_sea_vessel, ModeOfTransport.train, FlowDirection.import_flow], + [ModeOfTransport.feeder, ModeOfTransport.truck, FlowDirection.import_flow], + [ModeOfTransport.feeder, ModeOfTransport.barge, FlowDirection.import_flow], + [ModeOfTransport.feeder, ModeOfTransport.train, FlowDirection.import_flow], + + [ModeOfTransport.truck, ModeOfTransport.deep_sea_vessel, FlowDirection.export_flow], + [ModeOfTransport.truck, ModeOfTransport.feeder, FlowDirection.export_flow], + [ModeOfTransport.barge, ModeOfTransport.deep_sea_vessel, FlowDirection.export_flow], + [ModeOfTransport.barge, ModeOfTransport.feeder, FlowDirection.export_flow], + [ModeOfTransport.train, ModeOfTransport.deep_sea_vessel, FlowDirection.export_flow], + [ModeOfTransport.train, ModeOfTransport.feeder, FlowDirection.export_flow], + + [ModeOfTransport.truck, ModeOfTransport.truck, FlowDirection.undefined], + [ModeOfTransport.truck, ModeOfTransport.barge, FlowDirection.undefined], + [ModeOfTransport.truck, ModeOfTransport.train, FlowDirection.undefined], + [ModeOfTransport.barge, ModeOfTransport.truck, FlowDirection.undefined], + [ModeOfTransport.barge, ModeOfTransport.barge, FlowDirection.undefined], + [ModeOfTransport.barge, ModeOfTransport.train, FlowDirection.undefined], + [ModeOfTransport.train, ModeOfTransport.truck, FlowDirection.undefined], + [ModeOfTransport.train, ModeOfTransport.barge, FlowDirection.undefined], + [ModeOfTransport.train, ModeOfTransport.train, FlowDirection.undefined], + ]) + def test_flow_direction(self, delivered_by, picked_up_by, container_flow): """Test whether all flow directions are detected correctly""" - container = Container.create( + container: Container = Container.create( weight=10, - delivered_by=ModeOfTransport.deep_sea_vessel, - picked_up_by=ModeOfTransport.truck, + delivered_by=delivered_by, + picked_up_by=picked_up_by, picked_up_by_initial=ModeOfTransport.truck, length=ContainerLength.forty_feet, storage_requirement=StorageRequirement.standard ) - self.assertEqual(FlowDirection.import_flow, container.flow_direction) - ... # TODO: also test other cases: export, transshipment, undefined + self.assertEqual(container_flow, container.flow_direction) diff --git a/conflowgen/tests/domain_models/test_vehicle.py b/conflowgen/tests/domain_models/test_vehicle.py index ee046b56..2d11790b 100644 --- a/conflowgen/tests/domain_models/test_vehicle.py +++ b/conflowgen/tests/domain_models/test_vehicle.py @@ -74,12 +74,12 @@ def test_save_feeder_to_database(self) -> None: vehicle_type=ModeOfTransport.feeder, vehicle_arrives_at=now, average_vehicle_capacity=1100, - average_moved_capacity=200 + average_inbound_container_volume=200 ) lsv = LargeScheduledVehicle.create( vehicle_name="TestFeeder1", capacity_in_teu=1000, - moved_capacity=200, + inbound_container_volume=200, scheduled_arrival=now, schedule=schedule ) @@ -95,12 +95,12 @@ def test_repr(self) -> None: vehicle_type=ModeOfTransport.feeder, vehicle_arrives_at=now, average_vehicle_capacity=1100, - average_moved_capacity=200 + average_inbound_container_volume=200 ) lsv = LargeScheduledVehicle.create( vehicle_name="TestFeeder1", capacity_in_teu=1000, - moved_capacity=200, + inbound_container_volume=200, scheduled_arrival=now, schedule=schedule ) @@ -132,12 +132,12 @@ def test_save_barge_to_database(self) -> None: vehicle_type=ModeOfTransport.barge, vehicle_arrives_at=now, average_vehicle_capacity=1100, - average_moved_capacity=200 + average_inbound_container_volume=200 ) lsv = LargeScheduledVehicle.create( vehicle_name="TestBarge1", capacity_in_teu=1000, - moved_capacity=200, + inbound_container_volume=200, scheduled_arrival=now, schedule=schedule ) @@ -153,12 +153,12 @@ def test_repr(self) -> None: vehicle_type=ModeOfTransport.barge, vehicle_arrives_at=now, average_vehicle_capacity=1100, - average_moved_capacity=200 + average_inbound_container_volume=200 ) lsv = LargeScheduledVehicle.create( vehicle_name="TestBarge1", capacity_in_teu=1000, - moved_capacity=200, + inbound_container_volume=200, scheduled_arrival=now, schedule=schedule ) @@ -177,13 +177,13 @@ def test_get_mode_of_transport(self) -> None: service_name="MyTestBargeLine", vehicle_type=ModeOfTransport.barge, vehicle_arrives_at=now, - average_vehicle_capacity=1100, - average_moved_capacity=200 + average_vehicle_capacity=110, + average_inbound_container_volume=100 ) lsv = LargeScheduledVehicle.create( vehicle_name="TestBarge1", - capacity_in_teu=1000, - moved_capacity=200, + capacity_in_teu=110, + inbound_container_volume=100, scheduled_arrival=now, schedule=schedule ) diff --git a/conflowgen/tests/flow_generator/test_allocate_space_for_containers_delivered_by_truck_service.py b/conflowgen/tests/flow_generator/test_allocate_space_for_containers_delivered_by_truck_service.py index f5afb334..8f5bbbb8 100644 --- a/conflowgen/tests/flow_generator/test_allocate_space_for_containers_delivered_by_truck_service.py +++ b/conflowgen/tests/flow_generator/test_allocate_space_for_containers_delivered_by_truck_service.py @@ -69,12 +69,12 @@ def _create_feeder(scheduled_arrival: datetime.datetime) -> Feeder: vehicle_arrives_at=scheduled_arrival.date(), vehicle_arrives_at_time=scheduled_arrival.time(), average_vehicle_capacity=300, - average_moved_capacity=300, + average_inbound_container_volume=300, ) feeder_lsv = LargeScheduledVehicle.create( vehicle_name="TestFeeder1", capacity_in_teu=schedule.average_vehicle_capacity, - moved_capacity=schedule.average_moved_capacity, + inbound_container_volume=schedule.average_inbound_container_volume, scheduled_arrival=scheduled_arrival, schedule=schedule ) @@ -91,12 +91,12 @@ def _create_train(scheduled_arrival: datetime.datetime) -> Train: vehicle_arrives_at=scheduled_arrival.date(), vehicle_arrives_at_time=scheduled_arrival.time(), average_vehicle_capacity=90, - average_moved_capacity=90, + average_inbound_container_volume=90, ) train_lsv = LargeScheduledVehicle.create( vehicle_name="TestTrain1", capacity_in_teu=96, - moved_capacity=schedule.average_moved_capacity, + inbound_container_volume=schedule.average_inbound_container_volume, scheduled_arrival=scheduled_arrival, schedule=schedule ) diff --git a/conflowgen/tests/flow_generator/test_assign_destination_to_container_service.py b/conflowgen/tests/flow_generator/test_assign_destination_to_container_service.py index 6818e4c7..c7f9717a 100644 --- a/conflowgen/tests/flow_generator/test_assign_destination_to_container_service.py +++ b/conflowgen/tests/flow_generator/test_assign_destination_to_container_service.py @@ -48,12 +48,12 @@ def _create_feeder(scheduled_arrival: datetime.datetime) -> Feeder: vehicle_arrives_at=scheduled_arrival.date(), vehicle_arrives_at_time=scheduled_arrival.time(), average_vehicle_capacity=300, - average_moved_capacity=300, + average_inbound_container_volume=300, ) feeder_lsv = LargeScheduledVehicle.create( vehicle_name="TestFeeder1", capacity_in_teu=schedule.average_vehicle_capacity, - moved_capacity=schedule.average_moved_capacity, + inbound_container_volume=schedule.average_inbound_container_volume, scheduled_arrival=scheduled_arrival, schedule=schedule ) @@ -70,12 +70,12 @@ def _create_train(scheduled_arrival: datetime.datetime) -> Train: vehicle_arrives_at=scheduled_arrival.date(), vehicle_arrives_at_time=scheduled_arrival.time(), average_vehicle_capacity=90, - average_moved_capacity=90, + average_inbound_container_volume=90, ) train_lsv = LargeScheduledVehicle.create( vehicle_name="TestTrain1", capacity_in_teu=96, - moved_capacity=schedule.average_moved_capacity, + inbound_container_volume=schedule.average_inbound_container_volume, scheduled_arrival=scheduled_arrival, schedule=schedule ) diff --git a/conflowgen/tests/flow_generator/test_container_flow_generator_service__generate.py b/conflowgen/tests/flow_generator/test_container_flow_generator_service__generate.py index 4ac0357a..fbdb1d8b 100644 --- a/conflowgen/tests/flow_generator/test_container_flow_generator_service__generate.py +++ b/conflowgen/tests/flow_generator/test_container_flow_generator_service__generate.py @@ -15,7 +15,6 @@ from conflowgen.domain_models.distribution_seeders import mode_of_transport_distribution_seeder, \ seed_all_distributions, container_dwell_time_distribution_seeder from conflowgen.domain_models.data_types.mode_of_transport import ModeOfTransport -from conflowgen.domain_models.vehicle import Feeder, DeepSeaVessel from conflowgen.flow_generator.container_flow_generation_service import \ ContainerFlowGenerationService from conflowgen.domain_models.large_vehicle_schedule import Schedule @@ -51,22 +50,22 @@ def test_happy_path_no_mocking(self): create_tables(self.sqlite_db) seed_all_distributions() port_call_manager = PortCallManager() - port_call_manager.add_vehicle( + port_call_manager.add_service_that_calls_terminal( vehicle_type=ModeOfTransport.feeder, service_name="TestFeeder", vehicle_arrives_at=datetime.date(2021, 7, 9), vehicle_arrives_at_time=datetime.time(11), average_vehicle_capacity=800, - average_moved_capacity=100, + average_inbound_container_volume=100, next_destinations=None ) - port_call_manager.add_vehicle( + port_call_manager.add_service_that_calls_terminal( vehicle_type=ModeOfTransport.deep_sea_vessel, service_name="TestDeepSeaVessel", vehicle_arrives_at=datetime.date(2021, 7, 9), vehicle_arrives_at_time=datetime.time(11), average_vehicle_capacity=800, - average_moved_capacity=100, + average_inbound_container_volume=100, next_destinations=None ) self.container_flow_generator_service.generate() @@ -88,44 +87,29 @@ def test_happy_path_no_mocking_with_ramp_up_and_ramp_down(self): container_flow_generation_properties_manager.set_container_flow_generation_properties(properties) port_call_manager = PortCallManager() - port_call_manager.add_vehicle( + port_call_manager.add_service_that_calls_terminal( vehicle_type=ModeOfTransport.feeder, service_name="TestFeeder", vehicle_arrives_at=properties.start_date + datetime.timedelta(days=3), vehicle_arrives_at_time=datetime.time(11), average_vehicle_capacity=800, - average_moved_capacity=100, + average_inbound_container_volume=50, next_destinations=None ) - port_call_manager.add_vehicle( - vehicle_type=ModeOfTransport.deep_sea_vessel, - service_name="TestDeepSeaVessel", - vehicle_arrives_at=properties.start_date + datetime.timedelta(days=8), - vehicle_arrives_at_time=datetime.time(11), - average_vehicle_capacity=12000, - average_moved_capacity=1000, - next_destinations=None - ) - port_call_manager.add_vehicle( + port_call_manager.add_service_that_calls_terminal( vehicle_type=ModeOfTransport.deep_sea_vessel, service_name="TestDeepSeaVessel2", vehicle_arrives_at=properties.end_date - datetime.timedelta(days=2), vehicle_arrives_at_time=datetime.time(11), average_vehicle_capacity=12000, - average_moved_capacity=1000, + average_inbound_container_volume=100, next_destinations=None ) self.container_flow_generator_service.generate() - reference = list(Container.select()) - # Vehicle 1 - inbound during ramp-up untouched - feeder = Feeder.select().where( - Feeder.large_scheduled_vehicle.name == "TestFeeder" - ) - feeder_instance = list(feeder)[0] number_containers_during_ramp_up = Container.select().where( - Container.delivered_by_large_scheduled_vehicle == feeder + Container.delivered_by == ModeOfTransport.feeder ).count() self.assertLess( number_containers_during_ramp_up, @@ -136,34 +120,15 @@ def test_happy_path_no_mocking_with_ramp_up_and_ramp_down(self): 50 ) - # Vehicle 2 - no effect - deep_sea_vessel_1 = DeepSeaVessel.select().where( - DeepSeaVessel.large_scheduled_vehicle.name == "TestDeepSeaVessel" - ) - number_containers_in_normal_phase = Container.select().where( - Container.picked_up_by_large_scheduled_vehicle == deep_sea_vessel_1 - ).count() - self.assertLess( - number_containers_in_normal_phase, - 1000 - ) - self.assertGreater( - number_containers_in_normal_phase, - 500 - ) - - # Vehicle 3 - inbound volume during ramp-down throttled - deep_sea_vessel_2 = DeepSeaVessel.select().where( - DeepSeaVessel.large_scheduled_vehicle.name == "TestDeepSeaVessel2" - ) + # Vehicle 2 - inbound volume during ramp-down throttled number_containers_during_ramp_down = Container.select().where( - Container.delivered_by_large_scheduled_vehicle == deep_sea_vessel_2 + Container.delivered_by == ModeOfTransport.deep_sea_vessel ).count() self.assertLess( number_containers_during_ramp_down, - 1000 + 150 ) self.assertGreater( number_containers_during_ramp_down, - 500 + 50 ) diff --git a/conflowgen/tests/flow_generator/test_export_container_flow_service__container.py b/conflowgen/tests/flow_generator/test_export_container_flow_service__container.py index 3963a612..efdd5c5d 100644 --- a/conflowgen/tests/flow_generator/test_export_container_flow_service__container.py +++ b/conflowgen/tests/flow_generator/test_export_container_flow_service__container.py @@ -113,7 +113,7 @@ def test_convert_table_to_pandas_dataframe_with_container_with_destination(self) vehicle_arrives_at=one_week_later.date(), vehicle_arrives_at_time=one_week_later.time(), average_vehicle_capacity=300, - average_moved_capacity=300, + average_inbound_container_volume=300, vehicle_arrives_every_k_days=-1 ) destination = Destination.create( diff --git a/conflowgen/tests/flow_generator/test_large_scheduled_vehicle_for_onward_transportation_manager.py b/conflowgen/tests/flow_generator/test_large_scheduled_vehicle_for_onward_transportation_manager.py index 41c853b9..c89dc849 100644 --- a/conflowgen/tests/flow_generator/test_large_scheduled_vehicle_for_onward_transportation_manager.py +++ b/conflowgen/tests/flow_generator/test_large_scheduled_vehicle_for_onward_transportation_manager.py @@ -68,12 +68,12 @@ def _create_feeder( vehicle_arrives_at=scheduled_arrival.date(), vehicle_arrives_at_time=scheduled_arrival.time(), average_vehicle_capacity=300, - average_moved_capacity=300, + average_inbound_container_volume=300, ) feeder_lsv = LargeScheduledVehicle.create( vehicle_name="TestFeeder1", capacity_in_teu=schedule.average_vehicle_capacity, - moved_capacity=schedule.average_moved_capacity, + inbound_container_volume=schedule.average_inbound_container_volume, scheduled_arrival=scheduled_arrival, schedule=schedule ) @@ -90,12 +90,12 @@ def _create_train(scheduled_arrival: datetime.datetime, service_suffix: str = "" vehicle_arrives_at=scheduled_arrival.date(), vehicle_arrives_at_time=scheduled_arrival.time(), average_vehicle_capacity=90, - average_moved_capacity=90, + average_inbound_container_volume=90, ) train_lsv = LargeScheduledVehicle.create( vehicle_name="TestTrain1", capacity_in_teu=96, - moved_capacity=schedule.average_moved_capacity, + inbound_container_volume=schedule.average_inbound_container_volume, scheduled_arrival=scheduled_arrival, schedule=schedule ) @@ -167,7 +167,7 @@ def test_load_container_from_feeder_to_feeder(self): def test_do_not_overload_feeder_with_truck_traffic(self): truck = self._create_truck(datetime.datetime(year=2021, month=8, day=5, hour=9, minute=0)) feeder = self._create_feeder(datetime.datetime(year=2021, month=8, day=7, hour=13, minute=15)) - feeder.large_scheduled_vehicle.moved_capacity = 10 # in TEU + feeder.large_scheduled_vehicle.inbound_container_volume = 10 # in TEU containers = [self._create_container_for_truck(truck) for _ in range(10)] self.assertEqual(Container.select().count(), 10) teu_generated = sum((ContainerLength.get_teu_factor(container.length) for container in containers)) @@ -191,11 +191,11 @@ def test_do_not_overload_feeder_with_train_traffic(self): train = self._create_train(datetime.datetime(year=2021, month=8, day=5, hour=9, minute=0)) containers = [ self._create_container_for_large_scheduled_vehicle(train) - for _ in range(train.large_scheduled_vehicle.moved_capacity) # here only 20' containers + for _ in range(train.large_scheduled_vehicle.inbound_container_volume) # here only 20' containers ] feeder = self._create_feeder(datetime.datetime(year=2021, month=8, day=7, hour=13, minute=15)) - feeder.large_scheduled_vehicle.moved_capacity = 80 # in TEU + feeder.large_scheduled_vehicle.inbound_container_volume = 80 # in TEU feeder.save() self.assertEqual(Container.select().count(), 90) @@ -219,11 +219,11 @@ def test_do_not_load_if_the_time_span_is_too_long(self): train = self._create_train(datetime.datetime(year=2021, month=8, day=5, hour=9, minute=0)) containers = [ self._create_container_for_large_scheduled_vehicle(train) - for _ in range(train.large_scheduled_vehicle.moved_capacity) # here only 20' containers + for _ in range(train.large_scheduled_vehicle.inbound_container_volume) # here only 20' containers ] feeder = self._create_feeder(datetime.datetime(year=2022, month=8, day=7, hour=13, minute=15)) - feeder.large_scheduled_vehicle.moved_capacity = 80 # in TEU + feeder.large_scheduled_vehicle.inbound_container_volume = 80 # in TEU feeder.save() self.assertEqual(Container.select().count(), 90) @@ -242,16 +242,16 @@ def test_do_not_overload_feeder_with_train_traffic_of_two_vehicles(self): train_2 = self._create_train(datetime.datetime(year=2021, month=8, day=5, hour=15, minute=0), "2") containers_1 = [ self._create_container_for_large_scheduled_vehicle(train_1) - for _ in range(train_1.large_scheduled_vehicle.moved_capacity) # here only 20' containers + for _ in range(train_1.large_scheduled_vehicle.inbound_container_volume) # here only 20' containers ] containers_2 = [ self._create_container_for_large_scheduled_vehicle(train_2) - for _ in range(train_2.large_scheduled_vehicle.moved_capacity) # here only 20' containers + for _ in range(train_2.large_scheduled_vehicle.inbound_container_volume) # here only 20' containers ] containers = containers_1 + containers_2 feeder = self._create_feeder(datetime.datetime(year=2021, month=8, day=7, hour=13, minute=15)) - feeder.large_scheduled_vehicle.moved_capacity = 80 # in TEU + feeder.large_scheduled_vehicle.inbound_container_volume = 80 # in TEU feeder.save() self.assertEqual(Container.select().count(), 180) @@ -276,11 +276,11 @@ def test_do_not_overload_feeder_with_train_traffic_of_two_vehicles_and_changing_ train_2 = self._create_train(datetime.datetime(year=2021, month=8, day=5, hour=15, minute=0), "2") containers_1 = [ self._create_container_for_large_scheduled_vehicle(train_1) - for _ in range(train_1.large_scheduled_vehicle.moved_capacity) # here only 20' containers + for _ in range(train_1.large_scheduled_vehicle.inbound_container_volume) # here only 20' containers ] containers_2 = [ self._create_container_for_large_scheduled_vehicle(train_2) - for _ in range(train_2.large_scheduled_vehicle.moved_capacity) # here only 20' containers + for _ in range(train_2.large_scheduled_vehicle.inbound_container_volume) # here only 20' containers ] for container in containers_2: container.length = ContainerLength.forty_feet @@ -288,7 +288,7 @@ def test_do_not_overload_feeder_with_train_traffic_of_two_vehicles_and_changing_ containers = containers_1 + containers_2 feeder = self._create_feeder(datetime.datetime(year=2021, month=8, day=7, hour=13, minute=15)) - feeder.large_scheduled_vehicle.moved_capacity = 80 # in TEU + feeder.large_scheduled_vehicle.inbound_container_volume = 80 # in TEU feeder.save() self.assertEqual(Container.select().count(), 180) @@ -326,12 +326,12 @@ def test_behavior_during_ramp_up_period(self): datetime.datetime(year=2021, month=8, day=10, hour=15, minute=0), "2" ) - feeder_1.large_scheduled_vehicle.moved_capacity = 100 # in TEU + feeder_1.large_scheduled_vehicle.inbound_container_volume = 100 # in TEU feeder_1.save() containers = [ self._create_container_for_large_scheduled_vehicle(feeder_1) - for _ in range(feeder_1.large_scheduled_vehicle.moved_capacity) # here only 20' containers + for _ in range(feeder_1.large_scheduled_vehicle.inbound_container_volume) # here only 20' containers ] self.manager.reload_properties( diff --git a/conflowgen/tests/flow_generator/test_truck_for_export_containers_manager.py b/conflowgen/tests/flow_generator/test_truck_for_export_containers_manager.py index 4bb684ce..02043455 100644 --- a/conflowgen/tests/flow_generator/test_truck_for_export_containers_manager.py +++ b/conflowgen/tests/flow_generator/test_truck_for_export_containers_manager.py @@ -255,12 +255,12 @@ def test_happy_path(self): vehicle_arrives_at=container_arrival_time.date(), vehicle_arrives_at_time=container_arrival_time.time(), average_vehicle_capacity=5000, - average_moved_capacity=1200, + average_inbound_container_volume=1200, ) lsv = LargeScheduledVehicle.create( vehicle_name="TestDeepSeaVessel", capacity_in_teu=schedule.average_vehicle_capacity, - moved_capacity=schedule.average_moved_capacity, + inbound_container_volume=schedule.average_inbound_container_volume, scheduled_arrival=container_arrival_time, schedule=schedule ) diff --git a/conflowgen/tests/flow_generator/test_truck_for_import_containers_manager.py b/conflowgen/tests/flow_generator/test_truck_for_import_containers_manager.py index 515cbf47..a534655e 100644 --- a/conflowgen/tests/flow_generator/test_truck_for_import_containers_manager.py +++ b/conflowgen/tests/flow_generator/test_truck_for_import_containers_manager.py @@ -275,12 +275,12 @@ def test_happy_path(self): vehicle_arrives_at=container_arrival_time.date(), vehicle_arrives_at_time=container_arrival_time.time(), average_vehicle_capacity=5000, - average_moved_capacity=1200, + average_inbound_container_volume=1200, ) lsv = LargeScheduledVehicle.create( vehicle_name="TestDeepSeaVessel", capacity_in_teu=schedule.average_vehicle_capacity, - moved_capacity=schedule.average_moved_capacity, + inbound_container_volume=schedule.average_inbound_container_volume, scheduled_arrival=container_arrival_time, schedule=schedule ) diff --git a/conflowgen/tests/previews/test_container_flow_by_vehicle_type_preview.py b/conflowgen/tests/previews/test_container_flow_by_vehicle_type_preview.py index 77fd5011..7b2b02a0 100644 --- a/conflowgen/tests/previews/test_container_flow_by_vehicle_type_preview.py +++ b/conflowgen/tests/previews/test_container_flow_by_vehicle_type_preview.py @@ -99,7 +99,7 @@ def test_with_single_arrival_schedules(self): vehicle_arrives_at=one_week_later.date(), vehicle_arrives_at_time=one_week_later.time(), average_vehicle_capacity=300, - average_moved_capacity=300, + average_inbound_container_volume=300, vehicle_arrives_every_k_days=-1 ) schedule.save() diff --git a/conflowgen/tests/previews/test_container_flow_by_vehicle_type_preview_report.py b/conflowgen/tests/previews/test_container_flow_by_vehicle_type_preview_report.py index 3734cf90..93aed9ab 100644 --- a/conflowgen/tests/previews/test_container_flow_by_vehicle_type_preview_report.py +++ b/conflowgen/tests/previews/test_container_flow_by_vehicle_type_preview_report.py @@ -123,7 +123,7 @@ def test_inbound_with_single_arrival_schedules(self): vehicle_arrives_at=one_week_later.date(), vehicle_arrives_at_time=one_week_later.time(), average_vehicle_capacity=400, - average_moved_capacity=300, + average_inbound_container_volume=300, vehicle_arrives_every_k_days=-1 ) actual_report = self.preview_report.get_report_as_text() @@ -172,7 +172,7 @@ def test_report_with_schedules_as_graph(self): vehicle_arrives_at=one_week_later.date(), vehicle_arrives_at_time=one_week_later.time(), average_vehicle_capacity=400, - average_moved_capacity=300, + average_inbound_container_volume=300, vehicle_arrives_every_k_days=-1 ) fig = self.preview_report.get_report_as_graph() diff --git a/conflowgen/tests/previews/test_inbound_and_outbound_vehicle_capacity_preview.py b/conflowgen/tests/previews/test_inbound_and_outbound_vehicle_capacity_preview.py index f4f290e3..cb45034d 100644 --- a/conflowgen/tests/previews/test_inbound_and_outbound_vehicle_capacity_preview.py +++ b/conflowgen/tests/previews/test_inbound_and_outbound_vehicle_capacity_preview.py @@ -96,7 +96,7 @@ def test_inbound_with_single_arrival_schedules(self): vehicle_arrives_at=one_week_later.date(), vehicle_arrives_at_time=one_week_later.time(), average_vehicle_capacity=300, - average_moved_capacity=300, + average_inbound_container_volume=300, vehicle_arrives_every_k_days=-1 ) capacity_with_one_feeder = self.preview.get_inbound_capacity_of_vehicles().teu @@ -124,7 +124,7 @@ def test_inbound_with_several_arrivals_schedules(self): vehicle_arrives_at=two_days_later.date(), vehicle_arrives_at_time=two_days_later.time(), average_vehicle_capacity=300, - average_moved_capacity=300 + average_inbound_container_volume=300 ) capacity_with_one_feeder = self.preview.get_inbound_capacity_of_vehicles().teu self.assertSetEqual(set(ModeOfTransport), set(capacity_with_one_feeder.keys())) @@ -151,7 +151,7 @@ def test_outbound_average_capacity_with_several_arrivals_schedules(self): vehicle_arrives_at=two_days_later.date(), vehicle_arrives_at_time=two_days_later.time(), average_vehicle_capacity=300, - average_moved_capacity=300 + average_inbound_container_volume=300 ) capacities = self.preview.get_outbound_capacity_of_vehicles() capacity_with_one_feeder = capacities.used.teu @@ -183,7 +183,7 @@ def test_outbound_maximum_capacity_with_several_arrivals_schedules(self): vehicle_arrives_at=two_days_later.date(), vehicle_arrives_at_time=two_days_later.time(), average_vehicle_capacity=400, - average_moved_capacity=300 + average_inbound_container_volume=300 ) capacity_with_one_feeder = self.preview.get_outbound_capacity_of_vehicles().maximum.teu self.assertSetEqual(set(ModeOfTransport), set(capacity_with_one_feeder.keys())) diff --git a/conflowgen/tests/previews/test_inbound_and_outbound_vehicle_capacity_preview_report.py b/conflowgen/tests/previews/test_inbound_and_outbound_vehicle_capacity_preview_report.py index 0148dbaf..6963d7c6 100644 --- a/conflowgen/tests/previews/test_inbound_and_outbound_vehicle_capacity_preview_report.py +++ b/conflowgen/tests/previews/test_inbound_and_outbound_vehicle_capacity_preview_report.py @@ -103,7 +103,7 @@ def test_inbound_with_single_arrival_schedules(self): vehicle_arrives_at=one_week_later.date(), vehicle_arrives_at_time=one_week_later.time(), average_vehicle_capacity=400, - average_moved_capacity=300, + average_inbound_container_volume=300, vehicle_arrives_every_k_days=-1 ) actual_report = self.preview_report.get_report_as_text() @@ -132,7 +132,7 @@ def test_report_with_schedules_as_graph(self): vehicle_arrives_at=one_week_later.date(), vehicle_arrives_at_time=one_week_later.time(), average_vehicle_capacity=400, - average_moved_capacity=300, + average_inbound_container_volume=300, vehicle_arrives_every_k_days=-1 ) fig = self.preview_report.get_report_as_graph() diff --git a/conflowgen/tests/previews/test_modal_split_preview__get_modal_split_for_hinterland.py b/conflowgen/tests/previews/test_modal_split_preview__get_modal_split_for_hinterland.py index ea168ae4..33be55e0 100644 --- a/conflowgen/tests/previews/test_modal_split_preview__get_modal_split_for_hinterland.py +++ b/conflowgen/tests/previews/test_modal_split_preview__get_modal_split_for_hinterland.py @@ -97,7 +97,7 @@ def test_with_single_arrival_schedules(self): vehicle_arrives_at=one_week_later.date(), vehicle_arrives_at_time=one_week_later.time(), average_vehicle_capacity=300, - average_moved_capacity=300, + average_inbound_container_volume=300, vehicle_arrives_every_k_days=-1 ) schedule.save() diff --git a/conflowgen/tests/previews/test_modal_split_preview__get_transshipment.py b/conflowgen/tests/previews/test_modal_split_preview__get_transshipment.py index f6950b48..3a59b87e 100644 --- a/conflowgen/tests/previews/test_modal_split_preview__get_transshipment.py +++ b/conflowgen/tests/previews/test_modal_split_preview__get_transshipment.py @@ -92,7 +92,7 @@ def test_with_single_arrival_schedules(self): vehicle_arrives_at=one_week_later.date(), vehicle_arrives_at_time=one_week_later.time(), average_vehicle_capacity=300, - average_moved_capacity=300, + average_inbound_container_volume=300, vehicle_arrives_every_k_days=-1 ) actual_split = self.preview.get_transshipment_and_hinterland_split() diff --git a/conflowgen/tests/previews/test_modal_split_preview_report.py b/conflowgen/tests/previews/test_modal_split_preview_report.py index 0c84cb43..60359156 100644 --- a/conflowgen/tests/previews/test_modal_split_preview_report.py +++ b/conflowgen/tests/previews/test_modal_split_preview_report.py @@ -113,7 +113,7 @@ def test_inbound_with_single_arrival_schedules(self): vehicle_arrives_at=one_week_later.date(), vehicle_arrives_at_time=one_week_later.time(), average_vehicle_capacity=400, - average_moved_capacity=300, + average_inbound_container_volume=300, vehicle_arrives_every_k_days=-1 ) actual_report = self.preview_report.get_report_as_text() @@ -154,7 +154,7 @@ def test_report_with_schedules_as_graph(self): vehicle_arrives_at=one_week_later.date(), vehicle_arrives_at_time=one_week_later.time(), average_vehicle_capacity=400, - average_moved_capacity=300, + average_inbound_container_volume=300, vehicle_arrives_every_k_days=-1 ) axes = self.preview_report.get_report_as_graph() diff --git a/conflowgen/tests/previews/test_quay_side_throughput_preview.py b/conflowgen/tests/previews/test_quay_side_throughput_preview.py index fa070c1c..4bbd353f 100644 --- a/conflowgen/tests/previews/test_quay_side_throughput_preview.py +++ b/conflowgen/tests/previews/test_quay_side_throughput_preview.py @@ -103,7 +103,7 @@ def test_one_feeder(self): vehicle_arrives_at=one_week_later.date(), vehicle_arrives_at_time=one_week_later.time(), average_vehicle_capacity=300, - average_moved_capacity=150, + average_inbound_container_volume=150, vehicle_arrives_every_k_days=-1 ) volume = self.preview.get_quay_side_throughput() diff --git a/conflowgen/tests/previews/test_quay_side_throughput_preview_report.py b/conflowgen/tests/previews/test_quay_side_throughput_preview_report.py index 7e89322d..a5622db6 100644 --- a/conflowgen/tests/previews/test_quay_side_throughput_preview_report.py +++ b/conflowgen/tests/previews/test_quay_side_throughput_preview_report.py @@ -92,7 +92,7 @@ def test_report_with_schedules_as_graph(self): vehicle_arrives_at=one_week_later.date(), vehicle_arrives_at_time=one_week_later.time(), average_vehicle_capacity=400, - average_moved_capacity=300, + average_inbound_container_volume=300, vehicle_arrives_every_k_days=-1 ) axes = self.preview_report.get_report_as_graph() @@ -108,7 +108,7 @@ def test_text_report(self): vehicle_arrives_every_k_days=-1, vehicle_arrives_at_time=two_days_later.time(), average_vehicle_capacity=24000, - average_moved_capacity=24000 + average_inbound_container_volume=24000 ) report = self.preview_report.get_report_as_text() # flake8: noqa: W291 (ignore trailing whitespace in text report) diff --git a/conflowgen/tests/previews/test_truck_gate_throughput_preview.py b/conflowgen/tests/previews/test_truck_gate_throughput_preview.py index 52447103..ba8fd2cf 100644 --- a/conflowgen/tests/previews/test_truck_gate_throughput_preview.py +++ b/conflowgen/tests/previews/test_truck_gate_throughput_preview.py @@ -97,7 +97,7 @@ def test_get_total_trucks(self): vehicle_arrives_every_k_days=-1, vehicle_arrives_at_time=two_days_later.time(), average_vehicle_capacity=300, - average_moved_capacity=300 + average_inbound_container_volume=300 ) # pylint: disable=protected-access total_trucks = self.preview._get_total_trucks() @@ -118,7 +118,7 @@ def test_get_weekly_trucks(self): vehicle_arrives_every_k_days=-1, vehicle_arrives_at_time=two_days_later.time(), average_vehicle_capacity=300, - average_moved_capacity=300 + average_inbound_container_volume=300 ) weekly_trucks = self.preview._get_number_of_trucks_per_week() # 60 trucks total (from test_get_total_trucks above) @@ -137,7 +137,7 @@ def test_get_truck_distribution(self): vehicle_arrives_every_k_days=-1, vehicle_arrives_at_time=two_days_later.time(), average_vehicle_capacity=300, - average_moved_capacity=300 + average_inbound_container_volume=300 ) weekly_truck_distribution = self.preview.get_weekly_truck_arrivals(True, False) self.assertEqual(weekly_truck_distribution, {3: 6, 4: 24}) diff --git a/conflowgen/tests/previews/test_truck_gate_throughput_preview_report.py b/conflowgen/tests/previews/test_truck_gate_throughput_preview_report.py index f1460e93..f94d173b 100644 --- a/conflowgen/tests/previews/test_truck_gate_throughput_preview_report.py +++ b/conflowgen/tests/previews/test_truck_gate_throughput_preview_report.py @@ -261,7 +261,7 @@ def test_report_with_schedule(self): vehicle_arrives_every_k_days=-1, vehicle_arrives_at_time=two_days_later.time(), average_vehicle_capacity=24000, - average_moved_capacity=24000 + average_inbound_container_volume=24000 ) report = self.preview_report.get_report_as_graph() self.assertIsNotNone(report) @@ -276,7 +276,7 @@ def test_text_report(self): vehicle_arrives_every_k_days=-1, vehicle_arrives_at_time=two_days_later.time(), average_vehicle_capacity=24000, - average_moved_capacity=24000 + average_inbound_container_volume=24000 ) report = self.preview_report.get_report_as_text() # flake8: noqa: W291 (ignore trailing whitespace in text report) diff --git a/conflowgen/tests/previews/test_vehicle_capacity_exceeded_preview.py b/conflowgen/tests/previews/test_vehicle_capacity_exceeded_preview.py index 6223bd39..1f6e4b59 100644 --- a/conflowgen/tests/previews/test_vehicle_capacity_exceeded_preview.py +++ b/conflowgen/tests/previews/test_vehicle_capacity_exceeded_preview.py @@ -113,7 +113,7 @@ def test_with_single_arrival_schedules(self): vehicle_arrives_at=one_week_later.date(), vehicle_arrives_at_time=one_week_later.time(), average_vehicle_capacity=300, - average_moved_capacity=300, + average_inbound_container_volume=300, vehicle_arrives_every_k_days=-1 ) schedule.save() diff --git a/conflowgen/tests/previews/test_vehicle_capacity_exceeded_preview_report.py b/conflowgen/tests/previews/test_vehicle_capacity_exceeded_preview_report.py index 48e3e5b9..3aae61d2 100644 --- a/conflowgen/tests/previews/test_vehicle_capacity_exceeded_preview_report.py +++ b/conflowgen/tests/previews/test_vehicle_capacity_exceeded_preview_report.py @@ -105,7 +105,7 @@ def test_inbound_with_single_arrival_schedules(self): vehicle_arrives_at=one_week_later.date(), vehicle_arrives_at_time=one_week_later.time(), average_vehicle_capacity=400, - average_moved_capacity=300, + average_inbound_container_volume=300, vehicle_arrives_every_k_days=-1 ) actual_report = self.preview_report.get_report_as_text() @@ -134,7 +134,7 @@ def test_report_with_schedules_as_graph(self): vehicle_arrives_at=one_week_later.date(), vehicle_arrives_at_time=one_week_later.time(), average_vehicle_capacity=400, - average_moved_capacity=300, + average_inbound_container_volume=300, vehicle_arrives_every_k_days=-1 ) fig = self.preview_report.get_report_as_graph() From 200260d003981aacd0eae80a1d876074637907d3 Mon Sep 17 00:00:00 2001 From: Marvin Kastner Date: Sun, 26 May 2024 19:26:29 +0200 Subject: [PATCH 35/54] Keep internal files in .tools subfolder --- .gitignore | 1 + .tools/.gitkeep | 0 2 files changed, 1 insertion(+) create mode 100644 .tools/.gitkeep diff --git a/.gitignore b/.gitignore index f1eb5052..487aaeb9 100644 --- a/.gitignore +++ b/.gitignore @@ -64,3 +64,4 @@ examples/Python_Script/databases/ # Ignore local changes as they happen with every execution. If something changes, the commit must be forced. conflowgen/data/tools/ docs/notebooks/data/prepared_dbs/ +.tools/ diff --git a/.tools/.gitkeep b/.tools/.gitkeep new file mode 100644 index 00000000..e69de29b From d8036a88d57f66f58f245776de8b5bece9b30a75 Mon Sep 17 00:00:00 2001 From: Marvin Kastner Date: Sun, 26 May 2024 19:27:05 +0200 Subject: [PATCH 36/54] small fixes --- conflowgen/api/port_call_manager.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/conflowgen/api/port_call_manager.py b/conflowgen/api/port_call_manager.py index 9e062bb8..671c490d 100644 --- a/conflowgen/api/port_call_manager.py +++ b/conflowgen/api/port_call_manager.py @@ -2,7 +2,6 @@ import datetime import typing import uuid -import warnings from conflowgen.data_summaries.data_summaries_cache import DataSummariesCache from conflowgen.domain_models.factories.schedule_factory import ScheduleFactory @@ -115,7 +114,7 @@ def add_vehicle( vehicle_capacity: int, inbound_container_volume: int, next_destinations: typing.Optional[typing.List[typing.Tuple[str, float]]] = None, - service_name: typing.Optional[str] = None + service_name: typing.Optional[str] = None, ) -> None: r""" Add a service that frequently calls the terminal, both on the seaside and landside. @@ -129,7 +128,8 @@ def add_vehicle( :class:`ModeOfTransport.barge`, or :class:`ModeOfTransport.train` service_name: - The name of the service, i.e., the shipping line or rail freight line + The name of the service, i.e., the shipping line or rail freight line. + Defaults to a randomly generated id. vehicle_arrives_at: A date the service would arrive at the terminal. This can, e.g., point at the week day for weekly services. In any case, this is combined with the parameter ``vehicle_arrives_every_k_days`` and only @@ -162,6 +162,7 @@ def add_vehicle( next_destinations: Pairs of destination and frequency of the destination being chosen. """ + return self.add_service_that_calls_terminal( vehicle_type=vehicle_type, service_name=service_name if service_name is not None else str(uuid.uuid4()), From 7d5d74cf3dbc77109f2203f7e214fcf317aada7d Mon Sep 17 00:00:00 2001 From: Marvin Kastner Date: Sun, 26 May 2024 21:55:21 +0200 Subject: [PATCH 37/54] Fix asserts in brackets --- .../services/vehicle_capacity_manager.py | 67 ++++++++++--------- 1 file changed, 35 insertions(+), 32 deletions(-) diff --git a/conflowgen/application/services/vehicle_capacity_manager.py b/conflowgen/application/services/vehicle_capacity_manager.py index 74470ab3..cadb1703 100644 --- a/conflowgen/application/services/vehicle_capacity_manager.py +++ b/conflowgen/application/services/vehicle_capacity_manager.py @@ -11,7 +11,6 @@ class VehicleCapacityManager: - ignored_capacity = ContainerLength.get_teu_factor(ContainerLength.other) def __init__(self): @@ -47,7 +46,7 @@ def block_capacity_for_inbound_journey( assert vehicle in self.occupied_capacity_for_inbound_journey_buffer, \ "First .get_free_capacity_for_inbound_journey(vehicle) must be invoked" - usable_vessel_capacity = self.vehicle_container_volume_calculator.\ + usable_vessel_capacity = self.vehicle_container_volume_calculator. \ get_transported_container_volume_on_inbound_journey(vehicle) occupied_capacity_in_teu = self.occupied_capacity_for_inbound_journey_buffer[vehicle] @@ -55,14 +54,15 @@ def block_capacity_for_inbound_journey( new_occupied_capacity_in_teu = occupied_capacity_in_teu + used_capacity_in_teu new_free_capacity_in_teu = usable_vessel_capacity - new_occupied_capacity_in_teu - assert ( - new_free_capacity_in_teu >= 0, - f"vehicle {vehicle} is overloaded, " - f"usable_vessel_capacity: {usable_vessel_capacity}, " - f"occupied_capacity_in_teu: {occupied_capacity_in_teu}, " - f"used_capacity_in_teu: {used_capacity_in_teu}, " - f"new_free_capacity_in_teu: {new_free_capacity_in_teu}" - ) + assert \ + new_free_capacity_in_teu >= 0, \ + ( + f"vehicle {vehicle} is overloaded, " + f"usable_vessel_capacity: {usable_vessel_capacity}, " + f"occupied_capacity_in_teu: {occupied_capacity_in_teu}, " + f"used_capacity_in_teu: {used_capacity_in_teu}, " + f"new_free_capacity_in_teu: {new_free_capacity_in_teu}" + ) self.occupied_capacity_for_inbound_journey_buffer[vehicle] = new_occupied_capacity_in_teu vehicle_capacity_is_exhausted = new_free_capacity_in_teu < self.ignored_capacity @@ -76,7 +76,7 @@ def block_capacity_for_outbound_journey( assert vehicle in self.occupied_capacity_for_outbound_journey_buffer, \ "First .get_free_capacity_for_outbound_journey(vehicle) must be invoked" - scaled_moved_container_volume, unscaled_moved_container_volume = self.vehicle_container_volume_calculator.\ + scaled_moved_container_volume, unscaled_moved_container_volume = self.vehicle_container_volume_calculator. \ get_maximum_transported_container_volume_on_outbound_journey(vehicle, container.flow_direction) # calculate new free capacity @@ -87,29 +87,29 @@ def block_capacity_for_outbound_journey( new_scaled_free_capacity_in_teu = scaled_moved_container_volume - new_occupied_capacity_in_teu new_unscaled_free_capacity_in_teu = unscaled_moved_container_volume - new_occupied_capacity_in_teu - assert ( - new_unscaled_free_capacity_in_teu >= 0, - f"vehicle {vehicle} is overloaded, " - f"scaled_moved_container_volume: {scaled_moved_container_volume}, " - f"unscaled_moved_container_volume: {unscaled_moved_container_volume}, " - f"occupied_capacity_in_teu: {occupied_capacity_in_teu}, " - f"used_capacity_in_teu: {used_capacity_in_teu}, " - f"new_scaled_free_capacity_in_teu: {new_scaled_free_capacity_in_teu} " - f"new_unscaled_free_capacity_in_teu: {new_unscaled_free_capacity_in_teu}" - ) + assert \ + new_unscaled_free_capacity_in_teu >= 0, \ + ( + f"vehicle {vehicle} is overloaded, " + f"scaled_moved_container_volume: {scaled_moved_container_volume}, " + f"unscaled_moved_container_volume: {unscaled_moved_container_volume}, " + f"occupied_capacity_in_teu: {occupied_capacity_in_teu}, " + f"used_capacity_in_teu: {used_capacity_in_teu}, " + f"new_scaled_free_capacity_in_teu: {new_scaled_free_capacity_in_teu} " + f"new_unscaled_free_capacity_in_teu: {new_unscaled_free_capacity_in_teu}" + ) self.occupied_capacity_for_outbound_journey_buffer[vehicle] = new_occupied_capacity_in_teu space_is_exhausted = (new_scaled_free_capacity_in_teu <= self.ignored_capacity) return space_is_exhausted - # noinspection PyTypeChecker def get_free_capacity_for_inbound_journey(self, vehicle: Type[AbstractLargeScheduledVehicle]) -> float: """ Get the free capacity for the inbound journey on a vehicle that moves according to a schedule in TEU. During the ramp-down period (if existent), all inbound traffic is scaled down, no matter what. """ - inbound_container_volume = self.vehicle_container_volume_calculator\ + inbound_container_volume = self.vehicle_container_volume_calculator \ .get_transported_container_volume_on_inbound_journey(vehicle) if vehicle in self.occupied_capacity_for_inbound_journey_buffer: @@ -125,7 +125,8 @@ def get_free_capacity_for_inbound_journey(self, vehicle: Type[AbstractLargeSched return free_capacity_in_teu def get_free_capacity_for_outbound_journey( - self, vehicle: Type[AbstractLargeScheduledVehicle], + self, + vehicle: Type[AbstractLargeScheduledVehicle], flow_direction: FlowDirection ) -> float: """ @@ -148,13 +149,14 @@ def get_free_capacity_for_outbound_journey( ) self.occupied_capacity_for_outbound_journey_buffer[vehicle] = occupied_capacity_in_teu - assert ( - unscaled_moved_container_volume - occupied_capacity_in_teu >= 0, - f"vehicle {vehicle} is overloaded, " - f"maximum_transported_container_volume: {maximum_transported_container_volume}, " - f"unscaled_moved_container_volume: {unscaled_moved_container_volume}, " - f"occupied_capacity_in_teu: {occupied_capacity_in_teu}" - ) + assert \ + unscaled_moved_container_volume - occupied_capacity_in_teu >= 0, \ + ( + f"vehicle {vehicle} is overloaded, " + f"maximum_transported_container_volume: {maximum_transported_container_volume}, " + f"unscaled_moved_container_volume: {unscaled_moved_container_volume}, " + f"occupied_capacity_in_teu: {occupied_capacity_in_teu}" + ) free_capacity = max(maximum_transported_container_volume - occupied_capacity_in_teu, 0) @@ -193,10 +195,11 @@ def _get_number_containers_for_outbound_journey( ).count() return number_loaded_containers + @classmethod def _get_number_containers_for_inbound_journey( cls, - vehicle: AbstractLargeScheduledVehicle, + vehicle: Type[AbstractLargeScheduledVehicle], container_length: ContainerLength ) -> int: """Returns the number of containers on a specific vehicle of a specific container length that are delivered by From f2d46e8346c7adb0c671ec400d829c845cd81977 Mon Sep 17 00:00:00 2001 From: Marvin Kastner Date: Sun, 26 May 2024 21:56:47 +0200 Subject: [PATCH 38/54] Add hint at the end of the demo script saying where it was from --- examples/Python_Script/demo_DEHAM_CTA.py | 1 + .../demo_DEHAM_CTA__with_ramp_up_and_ramp_down_period.py | 1 + examples/Python_Script/demo_continental_gateway.py | 1 + examples/Python_Script/demo_poc.py | 1 + 4 files changed, 4 insertions(+) diff --git a/examples/Python_Script/demo_DEHAM_CTA.py b/examples/Python_Script/demo_DEHAM_CTA.py index 15d64e57..d4a4428c 100644 --- a/examples/Python_Script/demo_DEHAM_CTA.py +++ b/examples/Python_Script/demo_DEHAM_CTA.py @@ -321,4 +321,5 @@ # Gracefully close everything database_chooser.close_current_connection() +logger.info(f"ConFlowGen {conflowgen.__version__} from {conflowgen.__file__} was used.") logger.info("Demo 'demo_DEHAM_CTA' finished successfully.") diff --git a/examples/Python_Script/demo_DEHAM_CTA__with_ramp_up_and_ramp_down_period.py b/examples/Python_Script/demo_DEHAM_CTA__with_ramp_up_and_ramp_down_period.py index 2587729e..9a729990 100644 --- a/examples/Python_Script/demo_DEHAM_CTA__with_ramp_up_and_ramp_down_period.py +++ b/examples/Python_Script/demo_DEHAM_CTA__with_ramp_up_and_ramp_down_period.py @@ -325,4 +325,5 @@ # Gracefully close everything database_chooser.close_current_connection() +logger.info(f"ConFlowGen {conflowgen.__version__} from {conflowgen.__file__} was used.") logger.info("Demo 'demo_DEHAM_CTA_with_ramp_up_and_down_period' finished successfully.") diff --git a/examples/Python_Script/demo_continental_gateway.py b/examples/Python_Script/demo_continental_gateway.py index f269c0b6..cf103c2e 100644 --- a/examples/Python_Script/demo_continental_gateway.py +++ b/examples/Python_Script/demo_continental_gateway.py @@ -195,4 +195,5 @@ # Gracefully close everything database_chooser.close_current_connection() +logger.info(f"ConFlowGen {conflowgen.__version__} from {conflowgen.__file__} was used.") logger.info("Demo 'demo_continental_gateway' finished successfully.") diff --git a/examples/Python_Script/demo_poc.py b/examples/Python_Script/demo_poc.py index f326e3c1..a3525946 100644 --- a/examples/Python_Script/demo_poc.py +++ b/examples/Python_Script/demo_poc.py @@ -136,4 +136,5 @@ # Gracefully close everything database_chooser.close_current_connection() +logger.info(f"ConFlowGen {conflowgen.__version__} from {conflowgen.__file__} was used.") logger.info("Demo 'demo_poc' finished successfully.") From 5effb755a799b46ad4e51de9df4d21655da2f470 Mon Sep 17 00:00:00 2001 From: Marvin Kastner Date: Sun, 26 May 2024 22:11:21 +0200 Subject: [PATCH 39/54] Add git commit to output of demo scripts --- examples/Python_Script/demo_DEHAM_CTA.py | 6 ++++++ .../demo_DEHAM_CTA__with_ramp_up_and_ramp_down_period.py | 6 ++++++ examples/Python_Script/demo_continental_gateway.py | 6 ++++++ examples/Python_Script/demo_poc.py | 7 +++++++ 4 files changed, 25 insertions(+) diff --git a/examples/Python_Script/demo_DEHAM_CTA.py b/examples/Python_Script/demo_DEHAM_CTA.py index d4a4428c..e804c370 100644 --- a/examples/Python_Script/demo_DEHAM_CTA.py +++ b/examples/Python_Script/demo_DEHAM_CTA.py @@ -25,6 +25,7 @@ import random import sys import pandas as pd +import subprocess try: import conflowgen @@ -322,4 +323,9 @@ # Gracefully close everything database_chooser.close_current_connection() logger.info(f"ConFlowGen {conflowgen.__version__} from {conflowgen.__file__} was used.") +try: + last_git_commit = str(subprocess.check_output(["git", "log", "-1"]).strip()) + logger.info("Used git commit: " + last_git_commit[2:-1]) +except: + logger.debug("The last git commit of this repository could not be retrieved, no further version specification.") logger.info("Demo 'demo_DEHAM_CTA' finished successfully.") diff --git a/examples/Python_Script/demo_DEHAM_CTA__with_ramp_up_and_ramp_down_period.py b/examples/Python_Script/demo_DEHAM_CTA__with_ramp_up_and_ramp_down_period.py index 9a729990..ec65c0a4 100644 --- a/examples/Python_Script/demo_DEHAM_CTA__with_ramp_up_and_ramp_down_period.py +++ b/examples/Python_Script/demo_DEHAM_CTA__with_ramp_up_and_ramp_down_period.py @@ -25,6 +25,7 @@ import random import sys import pandas as pd +import subprocess try: import conflowgen @@ -326,4 +327,9 @@ # Gracefully close everything database_chooser.close_current_connection() logger.info(f"ConFlowGen {conflowgen.__version__} from {conflowgen.__file__} was used.") +try: + last_git_commit = str(subprocess.check_output(["git", "log", "-1"]).strip()) + logger.info("Used git commit: " + last_git_commit[2:-1]) +except: + logger.debug("The last git commit of this repository could not be retrieved, no further version specification.") logger.info("Demo 'demo_DEHAM_CTA_with_ramp_up_and_down_period' finished successfully.") diff --git a/examples/Python_Script/demo_continental_gateway.py b/examples/Python_Script/demo_continental_gateway.py index cf103c2e..a8e8e8cb 100644 --- a/examples/Python_Script/demo_continental_gateway.py +++ b/examples/Python_Script/demo_continental_gateway.py @@ -8,6 +8,7 @@ import random import sys import pandas as pd +import subprocess try: import conflowgen @@ -196,4 +197,9 @@ database_chooser.close_current_connection() logger.info(f"ConFlowGen {conflowgen.__version__} from {conflowgen.__file__} was used.") +try: + last_git_commit = str(subprocess.check_output(["git", "log", "-1"]).strip()) + logger.info("Used git commit: " + last_git_commit[2:-1]) +except: + logger.debug("The last git commit of this repository could not be retrieved, no further version specification.") logger.info("Demo 'demo_continental_gateway' finished successfully.") diff --git a/examples/Python_Script/demo_poc.py b/examples/Python_Script/demo_poc.py index a3525946..3216ecfa 100644 --- a/examples/Python_Script/demo_poc.py +++ b/examples/Python_Script/demo_poc.py @@ -14,6 +14,7 @@ import datetime import os import sys +import subprocess try: import conflowgen @@ -25,6 +26,7 @@ print("Please first install ConFlowGen, e.g. with conda or pip") raise exc + this_dir = os.path.dirname(__file__) with_visuals = False @@ -137,4 +139,9 @@ # Gracefully close everything database_chooser.close_current_connection() logger.info(f"ConFlowGen {conflowgen.__version__} from {conflowgen.__file__} was used.") +try: + last_git_commit = str(subprocess.check_output(["git", "log", "-1"]).strip()) + logger.info("Used git commit: " + last_git_commit[2:-1]) +except: + logger.debug("The last git commit of this repository could not be retrieved, skip this.") logger.info("Demo 'demo_poc' finished successfully.") From c8e9bee5c54916d3454c01434db4f70fc7b7c7d5 Mon Sep 17 00:00:00 2001 From: Marvin Kastner Date: Sun, 26 May 2024 22:37:29 +0200 Subject: [PATCH 40/54] Adjust OutboundToInboundVCUAR --- docs/notebooks/analyses.ipynb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/notebooks/analyses.ipynb b/docs/notebooks/analyses.ipynb index 9b3ff56a..a686bd92 100644 --- a/docs/notebooks/analyses.ipynb +++ b/docs/notebooks/analyses.ipynb @@ -289,12 +289,12 @@ "metadata": {}, "outputs": [], "source": [ - "vehicle_capacity_utilization_report = (\n", - " conflowgen.InboundToOutboundVehicleCapacityUtilizationAnalysisReport()\n", + "outbound_to_inbound_vehicle_capacity_utilization_report = (\n", + " conflowgen.OutboundToInboundVehicleCapacityUtilizationAnalysisReport()\n", ")\n", "\n", "print(\n", - " vehicle_capacity_utilization_report.get_report_as_text(\n", + " outbound_to_inbound_vehicle_capacity_utilization_report.get_report_as_text(\n", " vehicle_type={\n", " conflowgen.ModeOfTransport.deep_sea_vessel,\n", " conflowgen.ModeOfTransport.feeder,\n", From 71b952712ffe0b55cc98c2340b3c09c32e968eea Mon Sep 17 00:00:00 2001 From: Marvin Kastner Date: Sun, 26 May 2024 22:38:13 +0200 Subject: [PATCH 41/54] Use new PortCallManager API --- docs/notebooks/first_steps.ipynb | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/notebooks/first_steps.ipynb b/docs/notebooks/first_steps.ipynb index 5ececb96..bcc31d36 100644 --- a/docs/notebooks/first_steps.ipynb +++ b/docs/notebooks/first_steps.ipynb @@ -209,7 +209,7 @@ }, "source": [ "By using\n", - ":meth:`.PortCallManager.add_vehicle`,\n", + ":meth:`.PortCallManager.add_service_that_calls_terminal`,\n", "we can define the attributes for our feeder service.\n", "\n", "- ``vehicle_type`` defines, that we deal with a feeder as the mode of transport.\n", @@ -221,7 +221,7 @@ " This parameter must be a :py:obj:`datetime.time`.\n", "- ``average_vehicle_capacity`` defines the average capacity of the vessels utilized on this line.\n", " Parameter must be :py:obj:`int` or :py:obj:`float`.\n", - "- ``average_moved_capacity`` sets the capacity which is in average moved between the feeder and the terminal at each call.\n", + "- ``average_inbound_container_volume`` sets the capacity which is in average moved between the feeder and the terminal at each call.\n", " Parameter must be :py:obj:`int` or :py:obj:`float`.\n", "- ``next_destinations`` can be set, consisting of name and frequency which can, e.g., be used as implication for storage and stacking problems.\n", " A list of name-frequency pairs is expected here." @@ -234,13 +234,13 @@ "metadata": {}, "outputs": [], "source": [ - "port_call_manager.add_vehicle(\n", + "port_call_manager.add_service_that_calls_terminal(\n", " vehicle_type=conflowgen.ModeOfTransport.feeder,\n", " service_name=feeder_service_name,\n", " vehicle_arrives_at=datetime.date(2021, 7, 9),\n", " vehicle_arrives_at_time=datetime.time(11),\n", " average_vehicle_capacity=800,\n", - " average_moved_capacity=100,\n", + " average_inbound_container_volume=100,\n", " next_destinations=[\n", " (\"DEBRV\", 0.4), # 40% of the containers go here...\n", " (\"RULED\", 0.6) # and the other 60% of the containers go here.\n", @@ -263,13 +263,13 @@ "metadata": {}, "outputs": [], "source": [ - "port_call_manager.add_vehicle(\n", + "port_call_manager.add_service_that_calls_terminal(\n", " vehicle_type=conflowgen.ModeOfTransport.train,\n", " service_name=\"JR03A\",\n", " vehicle_arrives_at=datetime.date(2021, 7, 12),\n", " vehicle_arrives_at_time=datetime.time(17),\n", " average_vehicle_capacity=90,\n", - " average_moved_capacity=90,\n", + " average_inbound_container_volume=90,\n", " next_destinations=None # Here we don't have containers that need to be grouped by destination\n", ")" ] @@ -281,13 +281,13 @@ "metadata": {}, "outputs": [], "source": [ - "port_call_manager.add_vehicle(\n", + "port_call_manager.add_service_that_calls_terminal(\n", " vehicle_type=conflowgen.ModeOfTransport.deep_sea_vessel,\n", " service_name=\"LX050\",\n", " vehicle_arrives_at=datetime.date(2021, 7, 10),\n", " vehicle_arrives_at_time=datetime.time(19),\n", " average_vehicle_capacity=16000,\n", - " average_moved_capacity=150, # for faster demo\n", + " average_inbound_container_volume=150, # to speed up the code\n", " next_destinations=[\n", " (\"ZADUR\", 0.3), # 30% of the containers go to ZADUR...\n", " (\"CNSHG\", 0.7) # and the other 70% of the containers go to CNSHG.\n", From e13c0b1420f90ec9ce32dc17e569acb00af533ed Mon Sep 17 00:00:00 2001 From: Marvin Kastner Date: Sun, 26 May 2024 22:39:12 +0200 Subject: [PATCH 42/54] Adjust PortCallManager API usage --- examples/Python_Script/demo_poc.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/examples/Python_Script/demo_poc.py b/examples/Python_Script/demo_poc.py index 3216ecfa..bc4f7e5b 100644 --- a/examples/Python_Script/demo_poc.py +++ b/examples/Python_Script/demo_poc.py @@ -63,13 +63,13 @@ # Add vehicles that frequently visit the terminal. feeder_service_name = "LX050" logger.info(f"Add feeder service '{feeder_service_name}' to database") -port_call_manager.add_vehicle( +port_call_manager.add_service_that_calls_terminal( vehicle_type=conflowgen.ModeOfTransport.feeder, service_name=feeder_service_name, vehicle_arrives_at=datetime.date(2021, 7, 9), vehicle_arrives_at_time=datetime.time(11), - vehicle_capacity=800, - inbound_container_volume=100, + average_vehicle_capacity=800, + average_inbound_container_volume=100, next_destinations=[ ("DEBRV", 0.4), # 50% of the containers (in boxes) go here... ("RULED", 0.6) # and the other 50% of the containers (in boxes) go here. @@ -78,25 +78,25 @@ train_service_name = "JR03A" logger.info(f"Add train service '{train_service_name}' to database") -port_call_manager.add_vehicle( +port_call_manager.add_service_that_calls_terminal( vehicle_type=conflowgen.ModeOfTransport.train, service_name=train_service_name, vehicle_arrives_at=datetime.date(2021, 7, 12), vehicle_arrives_at_time=datetime.time(17), - vehicle_capacity=90, - inbound_container_volume=90, + average_vehicle_capacity=90, + average_inbound_container_volume=90, next_destinations=None # Here we don't have containers that need to be grouped by destination ) deep_sea_service_name = "LX050" logger.info(f"Add deep sea vessel service '{deep_sea_service_name}' to database") -port_call_manager.add_vehicle( +port_call_manager.add_service_that_calls_terminal( vehicle_type=conflowgen.ModeOfTransport.deep_sea_vessel, service_name=deep_sea_service_name, vehicle_arrives_at=datetime.date(2021, 7, 10), vehicle_arrives_at_time=datetime.time(19), - vehicle_capacity=16000, - inbound_container_volume=150, # for faster demo + average_vehicle_capacity=16000, + average_inbound_container_volume=150, # for faster demo next_destinations=[ ("ZADUR", 0.3), # 30% of the containers (in boxes) go here... ("CNSHG", 0.7) # and the other 70% of the containers (in boxes) go here. From a44c648760d175f847dd7a7cb2743cf15dec5e55 Mon Sep 17 00:00:00 2001 From: Marvin Kastner Date: Sun, 26 May 2024 22:47:44 +0200 Subject: [PATCH 43/54] Fix TKinter issue --- run_ci_light.bat | 1 + 1 file changed, 1 insertion(+) diff --git a/run_ci_light.bat b/run_ci_light.bat index c06d2132..ed8e1594 100644 --- a/run_ci_light.bat +++ b/run_ci_light.bat @@ -49,6 +49,7 @@ python -m pip install -e .[dev] || ( ) REM run tests +set MPLBACKEND=agg python -m pytest --exitfirst --verbose --failed-first --cov="./conflowgen" --cov-report html || ( ECHO.Tests failed! EXIT /B From c762a1a0d4b55505ccfdd0c8ee840fcb26e73e68 Mon Sep 17 00:00:00 2001 From: Marvin Kastner Date: Sun, 26 May 2024 22:48:36 +0200 Subject: [PATCH 44/54] Fix flake8 issues --- conflowgen/api/port_call_manager.py | 6 ++++-- ...tbound_vehicle_capacity_calculator_service.py | 16 +++++++++------- .../services/vehicle_capacity_manager.py | 1 - conflowgen/domain_models/vehicle.py | 3 ++- .../services/test_vehicle_capacity_manager.py | 1 - .../test_vehicle_countainer_volume_calculator.py | 2 -- 6 files changed, 15 insertions(+), 14 deletions(-) diff --git a/conflowgen/api/port_call_manager.py b/conflowgen/api/port_call_manager.py index 671c490d..46b8d9ee 100644 --- a/conflowgen/api/port_call_manager.py +++ b/conflowgen/api/port_call_manager.py @@ -69,7 +69,8 @@ def add_service_that_calls_terminal( .. math:: min( - \text{average_inbound_container_volume} \cdot \text{transportation_buffer},\text{average_vehicle_capacity} + \text{average_inbound_container_volume} \cdot \text{transportation_buffer}, + \text{average_vehicle_capacity} ) If you have calibrated the aforementioned distribution accordingly, the actual number of containers on @@ -152,7 +153,8 @@ def add_vehicle( .. math:: min( - \text{average_inbound_container_volume} \cdot \text{transportation_buffer},\text{average_vehicle_capacity} + \text{average_inbound_container_volume} \cdot \text{transportation_buffer}, + \text{average_vehicle_capacity} ) If you have calibrated the aforementioned distribution accordingly, the actual number of containers on diff --git a/conflowgen/application/services/inbound_and_outbound_vehicle_capacity_calculator_service.py b/conflowgen/application/services/inbound_and_outbound_vehicle_capacity_calculator_service.py index 6377cf6f..5d75e616 100644 --- a/conflowgen/application/services/inbound_and_outbound_vehicle_capacity_calculator_service.py +++ b/conflowgen/application/services/inbound_and_outbound_vehicle_capacity_calculator_service.py @@ -121,13 +121,15 @@ def get_outbound_capacity_of_vehicles(start_date, end_date, transportation_buffe schedule: Schedule for schedule in Schedule.select(): - assert ( - schedule.average_inbound_container_volume <= schedule.average_vehicle_capacity, - "A vehicle cannot move a larger amount of containers (in TEU) than its capacity, " - f"the input data is malformed. Schedule '{schedule.service_name}' of vehicle type " - f"{schedule.vehicle_type} has an average moved capacity of {schedule.average_inbound_container_volume} " - f"but an averaged vehicle capacity of {schedule.average_vehicle_capacity}." - ) + assert \ + schedule.average_inbound_container_volume <= schedule.average_vehicle_capacity, \ + ( + "A vehicle cannot move a larger amount of containers (in TEU) than its capacity, " + f"the input data is malformed. Schedule '{schedule.service_name}' of vehicle type " + f"{schedule.vehicle_type} has an average moved capacity of " + f"{schedule.average_inbound_container_volume} but an averaged vehicle capacity of " + f"{schedule.average_vehicle_capacity}." + ) arrivals = create_arrivals_within_time_range( start_date, diff --git a/conflowgen/application/services/vehicle_capacity_manager.py b/conflowgen/application/services/vehicle_capacity_manager.py index cadb1703..23b08a36 100644 --- a/conflowgen/application/services/vehicle_capacity_manager.py +++ b/conflowgen/application/services/vehicle_capacity_manager.py @@ -195,7 +195,6 @@ def _get_number_containers_for_outbound_journey( ).count() return number_loaded_containers - @classmethod def _get_number_containers_for_inbound_journey( cls, diff --git a/conflowgen/domain_models/vehicle.py b/conflowgen/domain_models/vehicle.py index 67f3a687..c1a42f56 100644 --- a/conflowgen/domain_models/vehicle.py +++ b/conflowgen/domain_models/vehicle.py @@ -65,7 +65,8 @@ class LargeScheduledVehicle(BaseModel): ) inbound_container_volume = IntegerField( null=False, - help_text="This is the actually moved container volume in TEU for a single terminal visit on the inbound journey." + help_text="This is the actually moved container volume in TEU for a single terminal visit on the inbound " + "journey." ) scheduled_arrival = DateTimeField( null=False, diff --git a/conflowgen/tests/application/services/test_vehicle_capacity_manager.py b/conflowgen/tests/application/services/test_vehicle_capacity_manager.py index 55c781ef..cc12a47d 100644 --- a/conflowgen/tests/application/services/test_vehicle_capacity_manager.py +++ b/conflowgen/tests/application/services/test_vehicle_capacity_manager.py @@ -91,7 +91,6 @@ def test_free_capacity_on_outbound_journey_without_any_containers_and_no_ramp_up ) self.assertEqual(free_capacity_on_feeder, 600) - @parameterized.parameterized.expand([ [FlowDirection.import_flow, 30, 600], [FlowDirection.export_flow, 30, 600], diff --git a/conflowgen/tests/application/services/test_vehicle_countainer_volume_calculator.py b/conflowgen/tests/application/services/test_vehicle_countainer_volume_calculator.py index 2e644cf3..d62c8443 100644 --- a/conflowgen/tests/application/services/test_vehicle_countainer_volume_calculator.py +++ b/conflowgen/tests/application/services/test_vehicle_countainer_volume_calculator.py @@ -85,7 +85,6 @@ def test_get_maximum_transported_container_volume_on_outbound_journey_and_no_ram scaled_moved_container_volume, unscaled_moved_container_volume = deep_sea_vessel_calc self.assertEqual(scaled_moved_container_volume, 3000) - @parameterized.parameterized.expand([ [FlowDirection.import_flow, 600, 3000], # normal values [FlowDirection.export_flow, 600, 3000], # normal values @@ -138,7 +137,6 @@ def test_get_maximum_transported_container_volume_on_outbound_journey_and_a_non_ scaled_moved_container_volume, unscaled_moved_container_volume = deep_sea_volume_calc self.assertEqual(scaled_moved_container_volume, deep_sea_volume) - def test_get_transported_container_volume_on_inbound_journey_and_no_ramp_down_period(self): """ Independent of the flow direction, the inbound capacity should not change as long as no ramp-down period is From 2544d4f0f70238eecf997eaf12255b418b9d0580 Mon Sep 17 00:00:00 2001 From: Marvin Kastner Date: Sun, 26 May 2024 22:53:56 +0200 Subject: [PATCH 45/54] Fix URL --- ...d_to_inbound_vehicle_capacity_utilization_analysis_report.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conflowgen/analyses/outbound_to_inbound_vehicle_capacity_utilization_analysis_report.py b/conflowgen/analyses/outbound_to_inbound_vehicle_capacity_utilization_analysis_report.py index 6c23c065..c88e44dd 100644 --- a/conflowgen/analyses/outbound_to_inbound_vehicle_capacity_utilization_analysis_report.py +++ b/conflowgen/analyses/outbound_to_inbound_vehicle_capacity_utilization_analysis_report.py @@ -25,7 +25,7 @@ class OutboundToInboundVehicleCapacityUtilizationAnalysisReport(AbstractReportWi and creates a comprehensible representation for the user, either as text or as a graph. The visual and table are expected to approximately look like in the `example InboundToOutboundVehicleCapacityUtilizationAnalysisReport \ - `_. + `_. """ report_description = """ From e9b0cf2b73bbdde5282ab047e849042a4bd47dfe Mon Sep 17 00:00:00 2001 From: Marvin Kastner Date: Sun, 26 May 2024 23:02:21 +0200 Subject: [PATCH 46/54] Fix codefactor issues --- ...ontainer_factory__create_for_large_scheduled_vehicle.py | 4 ++-- examples/Python_Script/demo_DEHAM_CTA.py | 6 +++--- .../demo_DEHAM_CTA__with_ramp_up_and_ramp_down_period.py | 6 +++--- examples/Python_Script/demo_continental_gateway.py | 6 +++--- examples/Python_Script/demo_poc.py | 7 ++++--- 5 files changed, 15 insertions(+), 14 deletions(-) diff --git a/conflowgen/tests/domain_models/factories/test_container_factory__create_for_large_scheduled_vehicle.py b/conflowgen/tests/domain_models/factories/test_container_factory__create_for_large_scheduled_vehicle.py index f80e2c44..63c13354 100644 --- a/conflowgen/tests/domain_models/factories/test_container_factory__create_for_large_scheduled_vehicle.py +++ b/conflowgen/tests/domain_models/factories/test_container_factory__create_for_large_scheduled_vehicle.py @@ -111,7 +111,7 @@ def test_create_containers_for_single_deep_sea_vessel_during_ramp_up_period(self # noinspection PyTypeChecker containers = self.container_factory.create_containers_for_large_scheduled_vehicle(vessel) - container_volume = sum([c.occupied_teu for c in containers]) + container_volume = sum(c.occupied_teu for c in containers) self.assertGreater(container_volume, 2900, "A bit less than 3000 is acceptable but common!") self.assertLess(container_volume, 3100, "A bit more than 3000 is acceptable but common!") @@ -144,7 +144,7 @@ def test_create_containers_for_single_deep_sea_vessel_during_ramp_down_period(se # noinspection PyTypeChecker containers = self.container_factory.create_containers_for_large_scheduled_vehicle(vessel) - container_volume_in_teu = sum([c.occupied_teu for c in containers]) + container_volume_in_teu = sum(c.occupied_teu for c in containers) self.assertGreater(container_volume_in_teu, 500, "A bit less than 600 is acceptable but common!") self.assertLess(container_volume_in_teu, 700, "A bit more than 600 is acceptable but common!") diff --git a/examples/Python_Script/demo_DEHAM_CTA.py b/examples/Python_Script/demo_DEHAM_CTA.py index e804c370..0e52dcdc 100644 --- a/examples/Python_Script/demo_DEHAM_CTA.py +++ b/examples/Python_Script/demo_DEHAM_CTA.py @@ -24,8 +24,8 @@ import os.path import random import sys -import pandas as pd import subprocess +import pandas as pd try: import conflowgen @@ -324,8 +324,8 @@ database_chooser.close_current_connection() logger.info(f"ConFlowGen {conflowgen.__version__} from {conflowgen.__file__} was used.") try: - last_git_commit = str(subprocess.check_output(["git", "log", "-1"]).strip()) + last_git_commit = str(subprocess.check_output(["git", "log", "-1"]).strip()) # pylint: disable=B607 logger.info("Used git commit: " + last_git_commit[2:-1]) -except: +except: # pylint: disable=bare-except logger.debug("The last git commit of this repository could not be retrieved, no further version specification.") logger.info("Demo 'demo_DEHAM_CTA' finished successfully.") diff --git a/examples/Python_Script/demo_DEHAM_CTA__with_ramp_up_and_ramp_down_period.py b/examples/Python_Script/demo_DEHAM_CTA__with_ramp_up_and_ramp_down_period.py index ec65c0a4..838bb2ed 100644 --- a/examples/Python_Script/demo_DEHAM_CTA__with_ramp_up_and_ramp_down_period.py +++ b/examples/Python_Script/demo_DEHAM_CTA__with_ramp_up_and_ramp_down_period.py @@ -24,8 +24,8 @@ import os.path import random import sys -import pandas as pd import subprocess +import pandas as pd try: import conflowgen @@ -328,8 +328,8 @@ database_chooser.close_current_connection() logger.info(f"ConFlowGen {conflowgen.__version__} from {conflowgen.__file__} was used.") try: - last_git_commit = str(subprocess.check_output(["git", "log", "-1"]).strip()) + last_git_commit = str(subprocess.check_output(["git", "log", "-1"]).strip()) # pylint: disable=B607 logger.info("Used git commit: " + last_git_commit[2:-1]) -except: +except: # pylint: disable=bare-except logger.debug("The last git commit of this repository could not be retrieved, no further version specification.") logger.info("Demo 'demo_DEHAM_CTA_with_ramp_up_and_down_period' finished successfully.") diff --git a/examples/Python_Script/demo_continental_gateway.py b/examples/Python_Script/demo_continental_gateway.py index a8e8e8cb..70d33e5f 100644 --- a/examples/Python_Script/demo_continental_gateway.py +++ b/examples/Python_Script/demo_continental_gateway.py @@ -7,8 +7,8 @@ import os.path import random import sys -import pandas as pd import subprocess +import pandas as pd try: import conflowgen @@ -198,8 +198,8 @@ logger.info(f"ConFlowGen {conflowgen.__version__} from {conflowgen.__file__} was used.") try: - last_git_commit = str(subprocess.check_output(["git", "log", "-1"]).strip()) + last_git_commit = str(subprocess.check_output(["git", "log", "-1"]).strip()) # pylint: disable=B607 logger.info("Used git commit: " + last_git_commit[2:-1]) -except: +except: # pylint: disable=bare-except logger.debug("The last git commit of this repository could not be retrieved, no further version specification.") logger.info("Demo 'demo_continental_gateway' finished successfully.") diff --git a/examples/Python_Script/demo_poc.py b/examples/Python_Script/demo_poc.py index bc4f7e5b..90adeaec 100644 --- a/examples/Python_Script/demo_poc.py +++ b/examples/Python_Script/demo_poc.py @@ -12,7 +12,8 @@ """ import datetime -import os +import os.path +import random import sys import subprocess @@ -140,8 +141,8 @@ database_chooser.close_current_connection() logger.info(f"ConFlowGen {conflowgen.__version__} from {conflowgen.__file__} was used.") try: - last_git_commit = str(subprocess.check_output(["git", "log", "-1"]).strip()) + last_git_commit = str(subprocess.check_output(["git", "log", "-1"]).strip()) # pylint: disable=B607 logger.info("Used git commit: " + last_git_commit[2:-1]) -except: +except: # pylint: disable=bare-except logger.debug("The last git commit of this repository could not be retrieved, skip this.") logger.info("Demo 'demo_poc' finished successfully.") From 1880dbe672f2185028900f20148238d6a5b2cbac Mon Sep 17 00:00:00 2001 From: Marvin Kastner Date: Sun, 26 May 2024 23:07:26 +0200 Subject: [PATCH 47/54] This is not pylint but Bandit https://bandit.readthedocs.io/en/latest/config.html --- examples/Python_Script/demo_DEHAM_CTA.py | 2 +- .../demo_DEHAM_CTA__with_ramp_up_and_ramp_down_period.py | 2 +- examples/Python_Script/demo_continental_gateway.py | 2 +- examples/Python_Script/demo_poc.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/Python_Script/demo_DEHAM_CTA.py b/examples/Python_Script/demo_DEHAM_CTA.py index 0e52dcdc..922db618 100644 --- a/examples/Python_Script/demo_DEHAM_CTA.py +++ b/examples/Python_Script/demo_DEHAM_CTA.py @@ -324,7 +324,7 @@ database_chooser.close_current_connection() logger.info(f"ConFlowGen {conflowgen.__version__} from {conflowgen.__file__} was used.") try: - last_git_commit = str(subprocess.check_output(["git", "log", "-1"]).strip()) # pylint: disable=B607 + last_git_commit = str(subprocess.check_output(["git", "log", "-1"]).strip()) # nosec B607 logger.info("Used git commit: " + last_git_commit[2:-1]) except: # pylint: disable=bare-except logger.debug("The last git commit of this repository could not be retrieved, no further version specification.") diff --git a/examples/Python_Script/demo_DEHAM_CTA__with_ramp_up_and_ramp_down_period.py b/examples/Python_Script/demo_DEHAM_CTA__with_ramp_up_and_ramp_down_period.py index 838bb2ed..8292bde4 100644 --- a/examples/Python_Script/demo_DEHAM_CTA__with_ramp_up_and_ramp_down_period.py +++ b/examples/Python_Script/demo_DEHAM_CTA__with_ramp_up_and_ramp_down_period.py @@ -328,7 +328,7 @@ database_chooser.close_current_connection() logger.info(f"ConFlowGen {conflowgen.__version__} from {conflowgen.__file__} was used.") try: - last_git_commit = str(subprocess.check_output(["git", "log", "-1"]).strip()) # pylint: disable=B607 + last_git_commit = str(subprocess.check_output(["git", "log", "-1"]).strip()) # nosec B607 logger.info("Used git commit: " + last_git_commit[2:-1]) except: # pylint: disable=bare-except logger.debug("The last git commit of this repository could not be retrieved, no further version specification.") diff --git a/examples/Python_Script/demo_continental_gateway.py b/examples/Python_Script/demo_continental_gateway.py index 70d33e5f..0560470a 100644 --- a/examples/Python_Script/demo_continental_gateway.py +++ b/examples/Python_Script/demo_continental_gateway.py @@ -198,7 +198,7 @@ logger.info(f"ConFlowGen {conflowgen.__version__} from {conflowgen.__file__} was used.") try: - last_git_commit = str(subprocess.check_output(["git", "log", "-1"]).strip()) # pylint: disable=B607 + last_git_commit = str(subprocess.check_output(["git", "log", "-1"]).strip()) # nosec B607 logger.info("Used git commit: " + last_git_commit[2:-1]) except: # pylint: disable=bare-except logger.debug("The last git commit of this repository could not be retrieved, no further version specification.") diff --git a/examples/Python_Script/demo_poc.py b/examples/Python_Script/demo_poc.py index 90adeaec..5c9a81cc 100644 --- a/examples/Python_Script/demo_poc.py +++ b/examples/Python_Script/demo_poc.py @@ -141,7 +141,7 @@ database_chooser.close_current_connection() logger.info(f"ConFlowGen {conflowgen.__version__} from {conflowgen.__file__} was used.") try: - last_git_commit = str(subprocess.check_output(["git", "log", "-1"]).strip()) # pylint: disable=B607 + last_git_commit = str(subprocess.check_output(["git", "log", "-1"]).strip()) # nosec B607 logger.info("Used git commit: " + last_git_commit[2:-1]) except: # pylint: disable=bare-except logger.debug("The last git commit of this repository could not be retrieved, skip this.") From 72502ac549ed2cdf12500787ba17c489eaadc655 Mon Sep 17 00:00:00 2001 From: Marvin Kastner Date: Sun, 26 May 2024 23:07:50 +0200 Subject: [PATCH 48/54] fix unused import --- examples/Python_Script/demo_poc.py | 1 - 1 file changed, 1 deletion(-) diff --git a/examples/Python_Script/demo_poc.py b/examples/Python_Script/demo_poc.py index 5c9a81cc..10aa4d45 100644 --- a/examples/Python_Script/demo_poc.py +++ b/examples/Python_Script/demo_poc.py @@ -13,7 +13,6 @@ import datetime import os.path -import random import sys import subprocess From 955f64d16d483245215b9a49336d4c1ac010678d Mon Sep 17 00:00:00 2001 From: Marvin Kastner Date: Mon, 27 May 2024 23:21:27 +0200 Subject: [PATCH 49/54] allow restricting ContainerFlowByVehicleInstanceAnalysis --- conflowgen/analyses/abstract_analysis.py | 14 +-- .../analyses/container_dwell_time_analysis.py | 4 +- ...ainer_flow_by_vehicle_instance_analysis.py | 36 ++++++-- ...low_by_vehicle_instance_analysis_report.py | 90 ++++++++++++------- ...le_type_adjustment_per_vehicle_analysis.py | 2 +- 5 files changed, 99 insertions(+), 47 deletions(-) diff --git a/conflowgen/analyses/abstract_analysis.py b/conflowgen/analyses/abstract_analysis.py index e8e466eb..c5fbad4d 100644 --- a/conflowgen/analyses/abstract_analysis.py +++ b/conflowgen/analyses/abstract_analysis.py @@ -98,35 +98,39 @@ def _restrict_storage_requirement(selected_containers: ModelSelect, storage_requ @staticmethod def _restrict_container_delivered_by_vehicle_type( selected_containers: ModelSelect, container_delivered_by_vehicle_type: typing.Any - ) -> ModelSelect: + ) -> (ModelSelect, list[ModeOfTransport]): if hashable(container_delivered_by_vehicle_type) \ and container_delivered_by_vehicle_type in set(ModeOfTransport): selected_containers = selected_containers.where( Container.delivered_by == container_delivered_by_vehicle_type ) + list_of_vehicle_types = [container_delivered_by_vehicle_type] else: # assume it is some kind of collection (list, set, ...) selected_containers = selected_containers.where( Container.delivered_by << container_delivered_by_vehicle_type ) - return selected_containers + list_of_vehicle_types = list(container_delivered_by_vehicle_type) + return selected_containers, list_of_vehicle_types @staticmethod def _restrict_container_picked_up_by_vehicle_type( selected_containers: ModelSelect, container_picked_up_by_vehicle_type: typing.Any - ) -> ModelSelect: + ) -> (ModelSelect, list[ModeOfTransport]): if container_picked_up_by_vehicle_type == "scheduled vehicles": container_picked_up_by_vehicle_type = ModeOfTransport.get_scheduled_vehicles() - + list_of_vehicle_types = container_picked_up_by_vehicle_type if hashable(container_picked_up_by_vehicle_type) \ and container_picked_up_by_vehicle_type in set(ModeOfTransport): selected_containers = selected_containers.where( Container.picked_up_by == container_picked_up_by_vehicle_type ) + list_of_vehicle_types = [container_picked_up_by_vehicle_type] else: # assume it is some kind of collection (list, set, ...) selected_containers = selected_containers.where( Container.picked_up_by << container_picked_up_by_vehicle_type ) - return selected_containers + list_of_vehicle_types = list(container_picked_up_by_vehicle_type) + return selected_containers, list_of_vehicle_types @staticmethod def _restrict_container_picked_up_by_initial_vehicle_type( diff --git a/conflowgen/analyses/container_dwell_time_analysis.py b/conflowgen/analyses/container_dwell_time_analysis.py index 822e42b7..8a30db70 100644 --- a/conflowgen/analyses/container_dwell_time_analysis.py +++ b/conflowgen/analyses/container_dwell_time_analysis.py @@ -64,12 +64,12 @@ def get_container_dwell_times( ) if container_delivered_by_vehicle_type != "all": - selected_containers = self._restrict_container_delivered_by_vehicle_type( + selected_containers, vehicle_types = self._restrict_container_delivered_by_vehicle_type( selected_containers, container_delivered_by_vehicle_type ) if container_picked_up_by_vehicle_type != "all": - selected_containers = self._restrict_container_picked_up_by_vehicle_type( + selected_containers, vehicle_types = self._restrict_container_picked_up_by_vehicle_type( selected_containers, container_picked_up_by_vehicle_type ) diff --git a/conflowgen/analyses/container_flow_by_vehicle_instance_analysis.py b/conflowgen/analyses/container_flow_by_vehicle_instance_analysis.py index 780bf87b..4e92e4d6 100644 --- a/conflowgen/analyses/container_flow_by_vehicle_instance_analysis.py +++ b/conflowgen/analyses/container_flow_by_vehicle_instance_analysis.py @@ -3,6 +3,8 @@ import datetime import typing +from peewee import ModelSelect + from conflowgen.data_summaries.data_summaries_cache import DataSummariesCache from conflowgen.domain_models.container import Container from conflowgen.domain_models.data_types.mode_of_transport import ModeOfTransport @@ -17,27 +19,34 @@ class ContainerFlowByVehicleInstanceAnalysis(AbstractAnalysis): as it is the case with :class:`.ContainerFlowByVehicleInstanceAnalysisReport`. """ - @staticmethod @DataSummariesCache.cache_result def get_container_flow_by_vehicle( + self, + vehicle_types: typing.Collection[ModeOfTransport] = ( + ModeOfTransport.train, + ModeOfTransport.feeder, + ModeOfTransport.deep_sea_vessel, + ModeOfTransport.barge + ), start_date: typing.Optional[datetime.datetime] = None, - end_date: typing.Optional[datetime.datetime] = None - ) -> typing.Dict[ + end_date: typing.Optional[datetime.datetime] = None, + ) -> [ ModeOfTransport, typing.Dict[VehicleIdentifier, typing.Dict[FlowDirection, (int, int)]] ]: """ - This shows for each of the vehicles - Args: + vehicle_types: A collection of vehicle types, e.g., passed as a :class:`list` or :class:`set`. + Only the vehicles that correspond to the provided vehicle type(s) are considered in the analysis. start_date: The earliest arriving container that is included. Consider all containers if :obj:`None`. end_date: The latest departing container that is included. Consider all containers if :obj:`None`. Returns: - Grouped by vehicle type and vehicle instance, how much import, export, and transshipment is unloaded and - loaded. + Grouped by vehicle type and vehicle instance, how many import, export, and transshipment containers are + unloaded and loaded (measured in TEU). """ + container_flow_by_vehicle: typing.Dict[ ModeOfTransport, typing.Dict[VehicleIdentifier, typing.Dict[FlowDirection, typing.Dict[str, int]]]] = { @@ -47,8 +56,12 @@ def get_container_flow_by_vehicle( vehicle_identifier_cache = {} + selected_containers: ModelSelect = Container.select().where( + (Container.delivered_by.in_(vehicle_types) | Container.picked_up_by.in_(vehicle_types)) + ) + container: Container - for container in Container.select(): + for container in selected_containers: if start_date and container.get_arrival_time() < start_date: continue if end_date and container.get_departure_time() > end_date: @@ -77,7 +90,8 @@ def get_container_flow_by_vehicle( for flow_direction in FlowDirection } container_flow_by_vehicle[ - container.delivered_by][vehicle_id_inbound][container.flow_direction]["inbound"] += 1 + container.delivered_by + ][vehicle_id_inbound][container.flow_direction]["inbound"] += container.occupied_teu if container.picked_up_by_large_scheduled_vehicle is not None: # if not transported by truck @@ -104,4 +118,8 @@ def get_container_flow_by_vehicle( container_flow_by_vehicle[ container.picked_up_by][vehicle_id_outbound][container.flow_direction]["outbound"] += 1 + for skipped_vehicle_type in set(ModeOfTransport) - set(vehicle_types): + assert len(container_flow_by_vehicle[skipped_vehicle_type]) == 0 + del container_flow_by_vehicle[skipped_vehicle_type] + return container_flow_by_vehicle diff --git a/conflowgen/analyses/container_flow_by_vehicle_instance_analysis_report.py b/conflowgen/analyses/container_flow_by_vehicle_instance_analysis_report.py index 10e59360..a6327e61 100644 --- a/conflowgen/analyses/container_flow_by_vehicle_instance_analysis_report.py +++ b/conflowgen/analyses/container_flow_by_vehicle_instance_analysis_report.py @@ -5,10 +5,10 @@ import matplotlib.figure import matplotlib.pyplot as plt +import numpy as np import pandas as pd from conflowgen.analyses.container_flow_by_vehicle_instance_analysis import ContainerFlowByVehicleInstanceAnalysis -from conflowgen.descriptive_datatypes import VehicleIdentifier, FlowDirection from conflowgen.domain_models.data_types.mode_of_transport import ModeOfTransport from conflowgen.reporting import AbstractReportWithMatplotlib from conflowgen.reporting.no_data_plot import no_data_graph @@ -35,10 +35,15 @@ def __init__(self): self.analysis = ContainerFlowByVehicleInstanceAnalysis() def get_report_as_text( - self, **kwargs + self, + vehicle_types: ModeOfTransport | str | typing.Collection = "scheduled vehicles", + **kwargs ) -> str: """ Keyword Args: + vehicle_types (typing.Collection[ModeOfTransport]): A collection of vehicle types, e.g., passed as a + :class:`list` or :class:`set`. + Only the vehicles that correspond to the provided vehicle type(s) are considered in the analysis. start_date (datetime.datetime): The earliest arriving container that is included. Consider all containers if :obj:`None`. end_date (datetime.datetime): The latest departing container that is included. @@ -47,38 +52,39 @@ def get_report_as_text( Returns: The report in human-readable text format """ - container_flow = self._get_analysis(kwargs) + plain_table, vehicle_types = self._get_analysis(kwargs) - report = "(pending)" + if len(plain_table) > 0: + df = self._get_dataframe_from_plain_table(plain_table) + report = str(df) + else: + report = "(no report feasible because no data is available)" - # create string representation return report - def _get_analysis(self, kwargs: dict) -> typing.Dict[ - ModeOfTransport, typing.Dict[VehicleIdentifier, typing.Dict[FlowDirection, (int, int)]] - ]: + def _get_analysis(self, kwargs: dict) -> (list, list): start_date = kwargs.pop("start_date", None) end_date = kwargs.pop("end_date", None) + vehicle_types = kwargs.pop("vehicle_types", ( + ModeOfTransport.train, + ModeOfTransport.feeder, + ModeOfTransport.deep_sea_vessel, + ModeOfTransport.barge + )) + assert len(kwargs) == 0, f"Keyword(s) {kwargs.keys()} have not been processed" container_flow = self.analysis.get_container_flow_by_vehicle( + vehicle_types=vehicle_types, start_date=start_date, - end_date=end_date + end_date=end_date, ) - return container_flow - - def get_report_as_graph(self, **kwargs) -> matplotlib.figure.Figure: - """ - Visualize the container flows (import, export, transshipment) over time. - Returns: - The diagram. - """ - container_flow = self._get_analysis(kwargs) + vehicle_types = list(container_flow.keys()) plain_table = [] - for mode_of_transport in (set(container_flow.keys()) - set([ModeOfTransport.truck])): + for mode_of_transport in vehicle_types: for vehicle_identifier in container_flow[mode_of_transport].keys(): for flow_direction in container_flow[mode_of_transport][vehicle_identifier]: for journey_direction in container_flow[mode_of_transport][vehicle_identifier][flow_direction]: @@ -91,6 +97,25 @@ def get_report_as_graph(self, **kwargs) -> matplotlib.figure.Figure: str(flow_direction), journey_direction, handled_volume) ) + return plain_table, vehicle_types + + def get_report_as_graph(self, **kwargs) -> matplotlib.figure.Figure: + """ + Keyword Args: + vehicle_types (typing.Collection[ModeOfTransport]): A collection of vehicle types, e.g., passed as a + :class:`list` or :class:`set`. + Only the vehicles that correspond to the provided vehicle type(s) are considered in the analysis. + start_date (datetime.datetime): + The earliest arriving container that is included. Consider all containers if :obj:`None`. + end_date (datetime.datetime): + The latest departing container that is included. Consider all containers if :obj:`None`. + + Returns: + Grouped by vehicle type and vehicle instance, how many import, export, and transshipment containers are + unloaded and loaded (measured in TEU). + """ + plain_table, vehicle_types = self._get_analysis(kwargs) + plot_title = "Container Flow By Vehicle Instance Analysis Report" if len(plain_table) == 0: @@ -98,28 +123,33 @@ def get_report_as_graph(self, **kwargs) -> matplotlib.figure.Figure: ax.set_title(plot_title) return fig - column_names = ("mode_of_transport", "vehicle_id", "vehicle_name", "service_name", "vehicle_arrival_time", - "flow_direction", "journey_direction", "handled_volume") - - df = pd.DataFrame(plain_table) - df.columns = column_names - df.set_index("vehicle_arrival_time", inplace=True) - - self._df = df + df = self._get_dataframe_from_plain_table(plain_table) - fig, axes = plt.subplots(nrows=2 * (len(ModeOfTransport) - 1), figsize=(7, 20)) + number_subplots = 2 * len(vehicle_types) + fig, axes = plt.subplots(nrows=number_subplots, figsize=(7, 4 * number_subplots)) i = 0 - for mode_of_transport in (set(ModeOfTransport) - set([ModeOfTransport.truck])): + for mode_of_transport in vehicle_types: for journey_direction in ["inbound", "outbound"]: ax = axes[i] i += 1 - ax.set_title(f"{mode_of_transport} - {journey_direction}") + ax.set_title(f"{str(mode_of_transport).replace('_', ' ').capitalize()} - {journey_direction}") df[(df["mode_of_transport"] == mode_of_transport) & (df["journey_direction"] == journey_direction) ].groupby("flow_direction")["handled_volume"].plot(ax=ax, linestyle=":", marker=".") + ax.set_ylabel("") plt.legend(loc='center left', bbox_to_anchor=(1, 0.5)) plt.tight_layout() return fig + + def _get_dataframe_from_plain_table(self, plain_table): + column_names = ("mode_of_transport", "vehicle_id", "vehicle_name", "service_name", "vehicle_arrival_time", + "flow_direction", "journey_direction", "handled_volume") + df = pd.DataFrame(plain_table) + df.columns = column_names + df.set_index("vehicle_arrival_time", inplace=True) + df.replace(0, np.nan, inplace=True) + self._df = df + return df diff --git a/conflowgen/analyses/container_flow_vehicle_type_adjustment_per_vehicle_analysis.py b/conflowgen/analyses/container_flow_vehicle_type_adjustment_per_vehicle_analysis.py index a2672a1c..f77ea071 100644 --- a/conflowgen/analyses/container_flow_vehicle_type_adjustment_per_vehicle_analysis.py +++ b/conflowgen/analyses/container_flow_vehicle_type_adjustment_per_vehicle_analysis.py @@ -61,7 +61,7 @@ def get_vehicle_type_adjustments_per_vehicle( ) if adjusted_vehicle_type is not None and adjusted_vehicle_type != "all": - selected_containers = self._restrict_container_picked_up_by_vehicle_type( + selected_containers, list_of_vehicle_types = self._restrict_container_picked_up_by_vehicle_type( selected_containers, adjusted_vehicle_type ) From ef3a103233b93c8e51135815e1bf731db48d30e7 Mon Sep 17 00:00:00 2001 From: Marvin Kastner Date: Mon, 27 May 2024 23:21:53 +0200 Subject: [PATCH 50/54] Highlight counting mechanism behind ModalSplitAnalysis --- conflowgen/analyses/modal_split_analysis_report.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/conflowgen/analyses/modal_split_analysis_report.py b/conflowgen/analyses/modal_split_analysis_report.py index 441e3bbd..1f149d9e 100644 --- a/conflowgen/analyses/modal_split_analysis_report.py +++ b/conflowgen/analyses/modal_split_analysis_report.py @@ -18,8 +18,8 @@ class ModalSplitAnalysisReport(AbstractReportWithMatplotlib): """ report_description = """ - Analyze the amount of containers dedicated for or coming from the hinterland compared to the amount of containers - that are transshipment. + Analyze how many containers are delivered and picked up by which type of vehicle. + This counts the containers in the yard, i.e., transshipment containers are *not* counted twice. """ def __init__(self): From 1a4fefbb0e0819fbc25b4600a54d750bb73b7624 Mon Sep 17 00:00:00 2001 From: Marvin Kastner Date: Thu, 30 May 2024 11:26:31 +0200 Subject: [PATCH 51/54] synchronize and rectify bibtex entry in references.bib and background.rst --- docs/background.rst | 3 ++- docs/references.bib | 17 ++++++++++------- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/docs/background.rst b/docs/background.rst index 497fbc8e..0326e225 100644 --- a/docs/background.rst +++ b/docs/background.rst @@ -220,7 +220,8 @@ If you just need a BibTeX entry for your citation software, this one should do t doi = {10.1007/978-3-031-05359-7_11}, month = {2}, pages = {133--143}, - publisher = {Springer Cham}, + publisher = {Springer International Publishing}, + address = {Cham, CH}, series = {Lecture Notes in Logistics}, title = {Container Flow Generation for Maritime Container Terminals}, year = {2022} diff --git a/docs/references.bib b/docs/references.bib index c5982774..321eec9d 100644 --- a/docs/references.bib +++ b/docs/references.bib @@ -49,16 +49,19 @@ @online{macgregor2016containersecuring } @inproceedings{kastner2022conflowgen, + address = {Bremen, DE}, author = {Kastner, Marvin and Grasse, Ole and Jahn, Carlos}, + editors = {Freitag, Michael and Kinra, Aseem, and Kotzab, Herbert, and Megow, Nicole}, + booktitle = {Dynamics in Logistics. Proceedings of the 8th International Conference {LDIC} 2022, {Bremen, Germany}}, + doi = {10.1007/978-3-031-05359-7_11}, + month = {2}, + pages = {133--143}, + publisher = {Springer International Publishing}, + address = {Cham, CH}, + series = {Lecture Notes in Logistics}, title = {Container Flow Generation for Maritime Container Terminals}, - pages = {133 -- 143}, - booktitle = {Dynamics in Logistics. Proceedings of the 8th International Conference LDIC 2022, Bremen, Germany}, - eventdate = {2022-02-23/2022-02-25}, year = {2022}, - editor = {Freitag, Michael and Kinra, Aseem and Kotzab, Herbert and Megow, Nicole}, - publisher = {Springer}, - address = {Cham, Switzerland}, - doi = {https://doi.org/10.1007/978-3-031-05359-7_11}, + eventdate = {2022-02-23/2022-02-25} } @article{exposito2012marshalling, From 2f2bb001d184d1145543699b1bca918d07763bfb Mon Sep 17 00:00:00 2001 From: Marvin Kastner Date: Thu, 30 May 2024 11:26:56 +0200 Subject: [PATCH 52/54] PIANC Yearbook 2023 is now published --- docs/references.bib | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/docs/references.bib b/docs/references.bib index 321eec9d..3a3e3014 100644 --- a/docs/references.bib +++ b/docs/references.bib @@ -118,7 +118,6 @@ @online{meisel2011unified-software } @inproceedings{edes2024estimating, - abstract = {Vessel delays and increased terminal call sizes negatively impact the ability to properly plan daily operations at seaport container terminals. Such traffic patterns lead to, among others, infrequent peak loads at the seaside of container terminals, complicating terminal operations. Thus, relying on annual or monthly statistics fails to account for these day-to-day fluctuations. When container terminals are planned, be it a greenfield or brownfield terminal, these variations in operations need to be accounted for. The traditional formula-based approach to design terminals uses annual statistics. In this study, it is first used to produce estimates for the required yard capacity for three existing exemplary container terminals. These are then compared to the results of numerical experiments using the synthetic container flow generator ConFlowGen. The findings reveal that yard capacity requirements fluctuate considerably depending on the timing of vessel arrivals and their call sizes. This dynamic modeling proved particularly beneficial for planning gateway traffic, offering more accurate storage capacity predictions. Suggestions are made for how to further develop ConFlowGen for handling transshipment traffic better in future versions.}, author = {Édes, Luc and Kastner, Marvin and Jahn, Carlos}, title = {On Estimating the Required Yard Capacity for Container Terminals}, url = {https://link.springer.com/chapter/10.1007/978-3-031-56826-8_13}, @@ -129,16 +128,16 @@ @inproceedings{edes2024estimating booktitle = {Dynamics in Logistics}, booksubtitle = {Proceedings of the 9th International Conference LDIC 2024, Bremen, Germany}, year = {2024}, - address = {Cham, DE}, + address = {Cham, CH}, doi = {10.1007/978-3-031-56826-8_13}, } @incollection{kastner2023synthetically, - abstract = {More than 80 % of world trade is delivered via sea, making the maritime supply chain a very important backbone for the economy (UNCTAD 2020). Containerized trade regularly outperforms other types of transport in terms of growth, coinciding with consistent increases of average container vessel sizes (UNCTAD 2020). Container terminal operations are heavily affected by this development, since less but larger port calls create unwanted peaks and stress on the terminals and the hinterland. Not all container terminals are affected equally by the described situation. Economic cycles and events such as the global COVID-19 pandemic or the Russian war in Ukraine change the global supply chains, trade characteristics and transport demands between ports in the world. In 2004, Hartmann proposed an approach to create scenarios for simulation and optimization in the sense of container terminal planning and logistics. Due to the significant changes in maritime trade over the years, a new approach for generating synthetic container flow data became practical. In 2021, we introduced a rethought and reworked approach on this topic.The proposed tool, named ConFlowGen, aims to assist planners, scientists, and other maritime experts with providing comprehensive container flow scenarios based on minimal inputs and assumptions of the user. In this paper, we introduce ConFlowGen's general principle of operation in an exemplary use case in the context of container terminal planning.}, author = {Kastner, Marvin and Grasse, Ole}, title = {{Synthetically generating traffic scenarios for simulation-based container terminal planning}}, - booktitle = {{PIANC Yearbook 2023 [Preprint]}}, - year = {2023}, + booktitle = {{PIANC Yearbook 2023}}, + year = {2024}, + pages = {3--17}, doi = {10.15480/882.5156}, - url = {https://tore.tuhh.de/entities/publication/c7ae66d0-4165-44e9-8481-7057af2bc775} + url = {https://pianc.app.box.com/s/vhe61hcxttpakyihjda76pk552itajmc} } From 9a7c46f1829f40d843560e7166fd93f846d481a9 Mon Sep 17 00:00:00 2001 From: Marvin Kastner Date: Wed, 14 Aug 2024 16:22:06 +0200 Subject: [PATCH 53/54] repait bibtex file --- docs/references.bib | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/docs/references.bib b/docs/references.bib index 3a3e3014..de7196b9 100644 --- a/docs/references.bib +++ b/docs/references.bib @@ -49,7 +49,6 @@ @online{macgregor2016containersecuring } @inproceedings{kastner2022conflowgen, - address = {Bremen, DE}, author = {Kastner, Marvin and Grasse, Ole and Jahn, Carlos}, editors = {Freitag, Michael and Kinra, Aseem, and Kotzab, Herbert, and Megow, Nicole}, booktitle = {Dynamics in Logistics. Proceedings of the 8th International Conference {LDIC} 2022, {Bremen, Germany}}, @@ -60,7 +59,7 @@ @inproceedings{kastner2022conflowgen address = {Cham, CH}, series = {Lecture Notes in Logistics}, title = {Container Flow Generation for Maritime Container Terminals}, - year = {2022}, + year = 2022, eventdate = {2022-02-23/2022-02-25} } @@ -88,7 +87,7 @@ @Article{briskorn2019generator title={A generator for test instances of scheduling problems concerning cranes in transshipment terminals}, journal={OR Spectrum}, year={2019}, - month={Mar}, + month=3, day={01}, volume={41}, number={1}, From 994628e012bdf1d86fa90889afa24030f4ddd8e0 Mon Sep 17 00:00:00 2001 From: Marvin Kastner Date: Wed, 14 Aug 2024 17:10:23 +0200 Subject: [PATCH 54/54] Update URL of DESTATIS --- docs/references.bib | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/references.bib b/docs/references.bib index de7196b9..e81bd731 100644 --- a/docs/references.bib +++ b/docs/references.bib @@ -1,8 +1,8 @@ @online{destatis2021seeschifffahrt, author = {{Statistisches Bundesamt (DESTATIS)}}, title = {{Verkehr : Seeschifffahrt : August 2021}}, - howpublished = {\url{https://www.destatis.de/DE/Themen/Branchen-Unternehmen/Transport-Verkehr/Gueterverkehr/Publikationen/Downloads-Schifffahrt/seeschifffahrt-monat-2080500211084.pdf?__blob=publicationFile}}, - note = {Accessed: 2022-01-22}, + howpublished = {\url{https://www.statistischebibliothek.de/mir/receive/DEHeft_mods_00137854}}, + note = {Accessed: 2024-08-14}, year = 2021 }