From 309c9913682d8c514d80c724cafb18a57274aa62 Mon Sep 17 00:00:00 2001 From: Marvin Kastner Date: Tue, 21 May 2024 21:23:16 +0200 Subject: [PATCH 01/50] 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 0ce2c03..ed6abad 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 02/50] 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 f34893c..ada21a3 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 69e40b5..4651d00 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 03/50] 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 1bb2ded..326c66d 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 04/50] 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 c090e7e..e03025b 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 05/50] 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 5ccaa40..38260ef 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 06/50] 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 369f65c..941dcc3 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 07/50] 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 4e74955..f5afb33 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 08/50] 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 55286a7..4001d95 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 09/50] 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 fdda11d..79c0d0e 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 10/50] 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 5bf2df3..9c63530 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 11/50] 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 9c63530..97ae702 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 12/50] 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 234b389..d9e8469 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 13/50] 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 97ae702..7fdf81d 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 14/50] 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 0000000..66eec26 --- /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 15/50] 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 7fdf81d..afdc411 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 d9e8469..15629c6 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 16/50] 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 66eec26..322413c 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 17/50] 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 322413c..fcd5dd9 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 18/50] ...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 fcd5dd9..1fc5283 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 19/50] 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 bd3aab8..01e6b42 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 acd5b4e..bd7d33f 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 170d6a0..6254578 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 e03025b..03cc0a9 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 ccd8495..bf8410a 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 4001d95..5495080 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 8bf2826..4c03414 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 20/50] 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 79c0d0e..012dfc2 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 21/50] 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 bd7d33f..7f695bf 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 22/50] 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 5495080..41c853b 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 23/50] 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 426fa67..1ab3baa 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 000026d..5cb8f4a 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 0000000..780bf87 --- /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 0000000..10e5936 --- /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 0d5a058..a2672a1 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 a02d4e6..a8d226a 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 3362655..010c49e 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 d9742c8..6c23c06 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 3f4a58f..06430e3 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 36bd2d3..3f592e9 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 4d17a8f..89cb749 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 d551351..feda190 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 ed6abad..a36f9d0 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 68cde3e..b0b58ea 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 24/50] 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 326c66d..4ac0357 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 25/50] Add parameterized as dependency --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index 245d657..9abae58 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 26/50] 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 5da8191..9e062bb 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 27/50] 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 65b15e3..15d64e5 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 1fc5283..2587729 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 4e9de1e..f269c0b 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 fb0a184..f326e3c 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 28/50] 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 7703be3..9de3ffb 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 5b5583f..6377cf6 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 0000000..74470ab --- /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 0000000..ab9a1f1 --- /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 012dfc2..4bc8a27 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 af9fd78..cca7668 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 186e094..231804a 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 03cc0a9..2b281f0 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 29/50] 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 638eab6..46a03e1 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 010c49e..f84df5d 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 01e6b42..ad241bf 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 f721fa1..de873ad 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 1670c3f..8a41f0d 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 d675c21..cba2da0 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 b54ccd6..d51bfb9 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 b0b830a..67f3a68 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 55bd85c..eeee565 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 b0f27b3..29bcc6e 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 6571427..f19dd4b 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 dd3a4e7..0acb779 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 3f592e9..6f30462 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 05e41a3..3751a51 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 bffa99a..80ab527 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 5522c95..923cd77 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 89cb749..afd2b1d 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 feda190..887474d 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 6a5bd5a..dc18598 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 8d090bc..4f34c40 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 448b2a0..7f452fe 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 22c54ea..a88a612 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 57a380b..679935e 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 daaf4f0..e75c19e 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 08f55dc..3c7ff8f 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 9b4c678..76bb2f4 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 04381d3..a626576 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 7e53b88..46f9321 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 0000000..55c781e --- /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 0000000..2e644cf --- /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 6448b8d..df41612 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 b527c24..09b706d 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 bf8410a..f80e2c4 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 b873d5e..d9ed2b8 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 03e00bf..315ff44 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 a40d20a..f4c9d77 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 5daf03f..cf8c426 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 d68204c..4704b6e 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 d7894cf..0951793 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 d613dbc..2aa44c1 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 afdc411..0000000 --- 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 15629c6..bd323fe 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 941dcc3..1ac2056 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 ee046b5..2d11790 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 f5afb33..8f5bbbb 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 6818e4c..c7f9717 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 4ac0357..fbdb1d8 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 3963a61..efdd5c5 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 41c853b..c89dc84 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 4bb684c..0204345 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 515cbf4..a534655 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 77fd501..7b2b02a 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 3734cf9..93aed9a 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 f4f290e..cb45034 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 0148dba..6963d7c 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 ea168ae..33be55e 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 f6950b4..3a59b87 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 0c84cb4..6035915 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 fa070c1..4bbd353 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 7e89322..a5622db 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 5244710..ba8fd2c 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 f1460e9..f94d173 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 6223bd3..1f6e4b5 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 48e3e5b..3aae61d 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 30/50] 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 f1eb505..487aaeb 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 0000000..e69de29 From d8036a88d57f66f58f245776de8b5bece9b30a75 Mon Sep 17 00:00:00 2001 From: Marvin Kastner Date: Sun, 26 May 2024 19:27:05 +0200 Subject: [PATCH 31/50] 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 9e062bb..671c490 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 32/50] 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 74470ab..cadb170 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 33/50] 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 15d64e5..d4a4428 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 2587729..9a72999 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 f269c0b..cf103c2 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 f326e3c..a352594 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 34/50] 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 d4a4428..e804c37 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 9a72999..ec65c0a 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 cf103c2..a8e8e8c 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 a352594..3216ecf 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 35/50] 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 9b3ff56..a686bd9 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 36/50] 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 5ececb9..bcc31d3 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 37/50] 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 3216ecf..bc4f7e5 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 38/50] 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 c06d213..ed8e159 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 39/50] 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 671c490..46b8d9e 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 6377cf6..5d75e61 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 cadb170..23b08a3 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 67f3a68..c1a42f5 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 55c781e..cc12a47 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 2e644cf..d62c844 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 40/50] 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 6c23c06..c88e44d 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 41/50] 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 f80e2c4..63c1335 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 e804c37..0e52dcd 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 ec65c0a..838bb2e 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 a8e8e8c..70d33e5 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 bc4f7e5..90adeae 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 42/50] 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 0e52dcd..922db61 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 838bb2e..8292bde 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 70d33e5..0560470 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 90adeae..5c9a81c 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 43/50] 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 5c9a81c..10aa4d4 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 44/50] 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 e8e466e..c5fbad4 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 822e42b..8a30db7 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 780bf87..4e92e4d 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 10e5936..a6327e6 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 a2672a1..f77ea07 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 45/50] 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 441e3bb..1f149d9 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 46/50] 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 497fbc8..0326e22 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 c598277..321eec9 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 47/50] 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 321eec9..3a3e301 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 97447c9814d8f17a640a9c91f977257f19622fee Mon Sep 17 00:00:00 2001 From: 1kastner Date: Fri, 2 Aug 2024 17:58:58 +0200 Subject: [PATCH 48/50] Fix pillow issue in unittest (#212) --- .github/workflows/installation-from-remote.yaml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/installation-from-remote.yaml b/.github/workflows/installation-from-remote.yaml index 44cb46d..85939fd 100644 --- a/.github/workflows/installation-from-remote.yaml +++ b/.github/workflows/installation-from-remote.yaml @@ -29,13 +29,15 @@ jobs: - name: Install ConFlowGen run: | + conda info + conda update conda conda info conda create -n test-install-conflowgen -c conda-forge conflowgen pytest - name: Prepare tests run: | conda activate test-install-conflowgen - conda install pillow>=9.0 + conda install -c conda-forge pillow>=9.0 - name: Run tests run: | @@ -66,6 +68,8 @@ jobs: conda init bash eval "$(conda shell.bash hook)" conda info + conda update conda + conda info conda activate base conda create -n test-install-conflowgen -c conda-forge conflowgen pytest From 9a7c46f1829f40d843560e7166fd93f846d481a9 Mon Sep 17 00:00:00 2001 From: Marvin Kastner Date: Wed, 14 Aug 2024 16:22:06 +0200 Subject: [PATCH 49/50] 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 3a3e301..de7196b 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 50/50] 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 de7196b..e81bd73 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 }