Skip to content

Commit

Permalink
Add get_average_container_dwell_time method (#187)
Browse files Browse the repository at this point in the history
* Add get_average_container_dwell_time method

* Add InboundAndOutboundVehicleCapacityCalculatorService

* Add AverageContainerDwellTimeCalculatorService

* Remove link to http://www.instances.de/dfg/

---------

Co-authored-by: Luc Edes <luc.edes@tuhh.de>
  • Loading branch information
lucedes27 and Luc Edes committed Aug 7, 2023
1 parent 0993d00 commit 5e68ff0
Show file tree
Hide file tree
Showing 18 changed files with 621 additions and 136 deletions.
26 changes: 23 additions & 3 deletions conflowgen/api/container_dwell_time_distribution_manager.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import typing
import datetime

from conflowgen.api import AbstractDistributionManager
from conflowgen.data_summaries.data_summaries_cache import DataSummariesCache
from conflowgen.domain_models.data_types.mode_of_transport import ModeOfTransport
from conflowgen.domain_models.data_types.storage_requirement import StorageRequirement
from conflowgen.api import AbstractDistributionManager
from conflowgen.domain_models.distribution_repositories.container_dwell_time_distribution_repository import \
ContainerDwellTimeDistributionRepository
from conflowgen.domain_models.data_types.mode_of_transport import ModeOfTransport
from conflowgen.application.services.average_container_dwell_time_calculator_service import \
AverageContainerDwellTimeCalculatorService
from conflowgen.tools.continuous_distribution import ContinuousDistribution


Expand All @@ -21,7 +24,7 @@ def __init__(self):
def get_container_dwell_time_distribution(
self
) -> typing.Dict[ModeOfTransport, typing.Dict[
ModeOfTransport, typing.Dict[StorageRequirement, ContinuousDistribution]]]:
ModeOfTransport, typing.Dict[StorageRequirement, ContinuousDistribution]]]:
"""
Returns:
Expand Down Expand Up @@ -58,3 +61,20 @@ def set_container_dwell_time_distribution(
sanitized_distribution
)
DataSummariesCache.reset_cache()

def get_average_container_dwell_time(self, start_date: datetime.date, end_date: datetime.date) -> float:
"""
Uses :class:`.ModeOfTransportDistributionManager` to calculate the expected average container dwell time
based on the scheduled container flow.
Args:
start_date: The earliest day to consider for scheduled vehicles
end_date: The latest day to consider for scheduled vehicles
Returns:
Weighted average of all container dwell times based on inbound and outbound vehicle capacities
"""
return AverageContainerDwellTimeCalculatorService().get_average_container_dwell_time(
start_date=start_date,
end_date=end_date
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import datetime

from conflowgen.api.container_length_distribution_manager import ContainerLengthDistributionManager
from conflowgen.api.mode_of_transport_distribution_manager import ModeOfTransportDistributionManager
from conflowgen.api.storage_requirement_distribution_manager import StorageRequirementDistributionManager
from conflowgen.application.services.inbound_and_outbound_vehicle_capacity_calculator_service import \
InboundAndOutboundVehicleCapacityCalculatorService
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.distribution_repositories.container_dwell_time_distribution_repository import \
ContainerDwellTimeDistributionRepository


class AverageContainerDwellTimeCalculatorService:

def get_average_container_dwell_time(self, start_date: datetime.date, end_date: datetime.date) -> float:
inbound_vehicle_capacity = InboundAndOutboundVehicleCapacityCalculatorService.get_inbound_capacity_of_vehicles(
start_date=start_date,
end_date=end_date
)
mode_of_transport_distribution = ModeOfTransportDistributionManager().get_mode_of_transport_distribution()
container_length_distribution = ContainerLengthDistributionManager().get_container_length_distribution()
container_storage_requirement_distribution = \
StorageRequirementDistributionManager().get_storage_requirement_distribution()
container_dwell_time_distribution = ContainerDwellTimeDistributionRepository(). \
get_distributions()
average_container_dwell_time = 0
total_containers = 0
for delivering_vehicle_type in ModeOfTransport:
for picking_up_vehicle_type in ModeOfTransport:
for container_length in ContainerLength:
for storage_requirement in StorageRequirement:
num_containers = inbound_vehicle_capacity.containers[delivering_vehicle_type] * \
mode_of_transport_distribution[delivering_vehicle_type][
picking_up_vehicle_type] * \
container_length_distribution[container_length] * \
container_storage_requirement_distribution[container_length][
storage_requirement]
total_containers += num_containers
average_container_dwell_time += \
container_dwell_time_distribution[delivering_vehicle_type][picking_up_vehicle_type][
storage_requirement].average * num_containers

average_container_dwell_time /= total_containers
return average_container_dwell_time
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
from typing import Dict

import numpy as np

from conflowgen.data_summaries.data_summaries_cache import DataSummariesCache
from conflowgen.descriptive_datatypes import ContainerVolumeByVehicleType
from conflowgen.domain_models.data_types.mode_of_transport import ModeOfTransport
from conflowgen.descriptive_datatypes import OutboundUsedAndMaximumCapacity
from conflowgen.domain_models.distribution_repositories.container_length_distribution_repository import \
ContainerLengthDistributionRepository
from conflowgen.domain_models.distribution_repositories.mode_of_transport_distribution_repository import \
ModeOfTransportDistributionRepository
from conflowgen.domain_models.factories.fleet_factory import create_arrivals_within_time_range
from conflowgen.domain_models.large_vehicle_schedule import Schedule


class InboundAndOutboundVehicleCapacityCalculatorService:

@staticmethod
@DataSummariesCache.cache_result
def get_truck_capacity_for_export_containers(
inbound_capacity_of_vehicles: Dict[ModeOfTransport, float]
) -> float:
"""
Get the capacity in TEU which is transported by truck. Currently, during the generation process each import
container is picked up by one truck and for each import container, in the next step one export container is
created.
Thus, this method accounts for both import and export.
"""
truck_capacity = 0
for vehicle_type in ModeOfTransport.get_scheduled_vehicles():
number_of_containers_delivered_to_terminal_by_vehicle_type = inbound_capacity_of_vehicles[vehicle_type]
mode_of_transport_distribution_of_vehicle_type = \
ModeOfTransportDistributionRepository().get_distribution()[vehicle_type]
vehicle_to_truck_fraction = mode_of_transport_distribution_of_vehicle_type[ModeOfTransport.truck]
number_of_containers_to_pick_up_by_truck_from_vehicle_type = \
number_of_containers_delivered_to_terminal_by_vehicle_type * vehicle_to_truck_fraction
truck_capacity += number_of_containers_to_pick_up_by_truck_from_vehicle_type
return truck_capacity

@staticmethod
@DataSummariesCache.cache_result
def get_inbound_capacity_of_vehicles(start_date, end_date) -> ContainerVolumeByVehicleType:
"""
For the inbound capacity, first vehicles that adhere to a schedule are considered. Trucks, which are created
depending on the outbound distribution, are created based on the assumptions of the further container flow
generation process.
"""
containers: Dict[ModeOfTransport, float] = {
vehicle_type: 0
for vehicle_type in ModeOfTransport
}
inbound_capacity_in_teu: Dict[ModeOfTransport, float] = {
vehicle_type: 0
for vehicle_type in ModeOfTransport
}

for schedule in Schedule.select():
arrivals = create_arrivals_within_time_range(
start_date,
schedule.vehicle_arrives_at,
end_date,
schedule.vehicle_arrives_every_k_days,
schedule.vehicle_arrives_at_time
)
total_capacity_moved_by_vessel = (len(arrivals) # number of vehicles that are planned
* schedule.average_moved_capacity) # TEU capacity of each vehicle
containers[schedule.vehicle_type] += total_capacity_moved_by_vessel / \
(ContainerLengthDistributionRepository.get_teu_factor() * 20)
inbound_capacity_in_teu[schedule.vehicle_type] += total_capacity_moved_by_vessel

inbound_capacity_in_teu[ModeOfTransport.truck] = \
InboundAndOutboundVehicleCapacityCalculatorService.get_truck_capacity_for_export_containers(
inbound_capacity_in_teu
)
containers[ModeOfTransport.truck] = \
inbound_capacity_in_teu[ModeOfTransport.truck] / \
(ContainerLengthDistributionRepository.get_teu_factor() * 20)

return ContainerVolumeByVehicleType(
containers=containers,
teu=inbound_capacity_in_teu
)

@staticmethod
@DataSummariesCache.cache_result
def get_outbound_capacity_of_vehicles(start_date, end_date, transportation_buffer) \
-> OutboundUsedAndMaximumCapacity:
"""
For the outbound capacity, both the used outbound capacity (estimated) and the maximum outbound capacity is
reported. If a vehicle type reaches the maximum outbound capacity, this means that containers need to be
redistributed to other vehicle types due to a lack of capacity. The capacities are only calculated in TEU, not
in containers.
"""
outbound_used_containers: Dict[ModeOfTransport, float] = {
vehicle_type: 0
for vehicle_type in ModeOfTransport
}
outbound_maximum_containers: Dict[ModeOfTransport, float] = {
vehicle_type: 0
for vehicle_type in ModeOfTransport
}
outbound_used_capacity_in_teu: Dict[ModeOfTransport, float] = {
vehicle_type: 0
for vehicle_type in ModeOfTransport
}
outbound_maximum_capacity_in_teu: Dict[ModeOfTransport, float] = {
vehicle_type: 0
for vehicle_type in ModeOfTransport
}

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}."

arrivals = create_arrivals_within_time_range(
start_date,
schedule.vehicle_arrives_at,
end_date,
schedule.vehicle_arrives_every_k_days,
schedule.vehicle_arrives_at_time
)

# If all container flows are balanced, only the average moved capacity is required
total_average_capacity_moved_by_vessel_in_teu = len(arrivals) * schedule.average_moved_capacity
outbound_used_capacity_in_teu[schedule.vehicle_type] += total_average_capacity_moved_by_vessel_in_teu
outbound_used_containers[schedule.vehicle_type] += total_average_capacity_moved_by_vessel_in_teu / \
(ContainerLengthDistributionRepository.get_teu_factor() * 20)

# 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_vehicle_capacity
)
total_maximum_capacity_moved_by_vessel = len(arrivals) * maximum_capacity_of_vehicle_in_teu
outbound_maximum_capacity_in_teu[schedule.vehicle_type] += total_maximum_capacity_moved_by_vessel
outbound_maximum_containers[schedule.vehicle_type] += total_maximum_capacity_moved_by_vessel / \
(ContainerLengthDistributionRepository.get_teu_factor() * 20)

inbound_capacity = InboundAndOutboundVehicleCapacityCalculatorService.\
get_inbound_capacity_of_vehicles(start_date, end_date)
outbound_used_capacity_in_teu[ModeOfTransport.truck] = \
InboundAndOutboundVehicleCapacityCalculatorService.get_truck_capacity_for_export_containers(
inbound_capacity.teu
)
outbound_used_containers[ModeOfTransport.truck] = \
outbound_used_capacity_in_teu[ModeOfTransport.truck] / \
(ContainerLengthDistributionRepository.get_teu_factor() * 20)

outbound_maximum_capacity_in_teu[ModeOfTransport.truck] = np.nan # Trucks can always be added as required
outbound_maximum_containers[ModeOfTransport.truck] = np.nan

return OutboundUsedAndMaximumCapacity(
used=ContainerVolumeByVehicleType(
containers=outbound_used_containers,
teu=outbound_used_capacity_in_teu
),
maximum=ContainerVolumeByVehicleType(
containers=outbound_maximum_containers,
teu=outbound_maximum_capacity_in_teu
)
)

0 comments on commit 5e68ff0

Please sign in to comment.