Skip to content

Commit

Permalink
Add quay side throughput preview (#194)
Browse files Browse the repository at this point in the history
* Add typhints to InboundAndOutboundVehicleCapacityCalculatorService

* Add placeholders for tests

* simplify TruckGateThrougputPreview

* Add quayside throughput preview

* Exclude emergency pick-up containers for estimating the number of export trucks
  • Loading branch information
1kastner authored Sep 9, 2023
1 parent acb58ba commit e1783c3
Show file tree
Hide file tree
Showing 19 changed files with 603 additions and 227 deletions.
9 changes: 6 additions & 3 deletions conflowgen/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@
from conflowgen.previews.modal_split_preview_report import ModalSplitPreviewReport
from conflowgen.previews.truck_gate_throughput_preview import TruckGateThroughputPreview
from conflowgen.previews.truck_gate_throughput_preview_report import TruckGateThroughputPreviewReport
from conflowgen.previews.quay_side_throughput_preview import QuaySideThroughputPreview
from conflowgen.previews.quay_side_throughput_preview_report import QuaySideThroughputPreviewReport

# Analyses and their reports
from conflowgen.analyses.inbound_and_outbound_vehicle_capacity_analysis import \
Expand Down Expand Up @@ -90,12 +92,13 @@
from conflowgen.previews.inbound_and_outbound_vehicle_capacity_preview import OutboundUsedAndMaximumCapacity
from conflowgen.analyses.container_flow_adjustment_by_vehicle_type_analysis_summary import \
ContainerFlowAdjustedToVehicleType
from conflowgen.descriptive_datatypes import TransshipmentAndHinterlandSplit, ContainerVolumeFromOriginToDestination
from conflowgen.descriptive_datatypes import TransshipmentAndHinterlandSplit
from conflowgen.descriptive_datatypes import ContainerVolumeFromOriginToDestination
from conflowgen.descriptive_datatypes import HinterlandModalSplit
from conflowgen.descriptive_datatypes import UsedYardCapacityOverTime
from conflowgen.analyses.inbound_to_outbound_vehicle_capacity_utilization_analysis import \
VehicleIdentifier
from conflowgen.descriptive_datatypes import VehicleIdentifier
from conflowgen.descriptive_datatypes import ContainerVolumeByVehicleType
from conflowgen.descriptive_datatypes import ContainersTransportedByTruck

# Add metadata constants
from .metadata import __version__
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import datetime
from typing import Dict

import numpy as np
Expand Down Expand Up @@ -28,6 +29,7 @@ def get_truck_capacity_for_export_containers(
Thus, this method accounts for both import and export.
"""
truck_capacity = 0
vehicle_type: ModeOfTransport
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 = \
Expand All @@ -40,46 +42,53 @@ def get_truck_capacity_for_export_containers(

@staticmethod
@DataSummariesCache.cache_result
def get_inbound_capacity_of_vehicles(start_date, end_date) -> ContainerVolumeByVehicleType:
def get_inbound_capacity_of_vehicles(
start_date: datetime.date,
end_date: datetime.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] = {
inbound_container_volume_in_containers: Dict[ModeOfTransport, float] = {
vehicle_type: 0
for vehicle_type in ModeOfTransport
}
inbound_capacity_in_teu: Dict[ModeOfTransport, float] = {
inbound_container_volume_in_teu: Dict[ModeOfTransport, float] = {
vehicle_type: 0
for vehicle_type in ModeOfTransport
}

at_least_one_schedule_exists: bool = False

for schedule in Schedule.select():
at_least_one_schedule_exists = True
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)
moved_inbound_volumes = (len(arrivals) # number of vehicles that are planned
* schedule.average_moved_capacity) # 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()

if at_least_one_schedule_exists:
inbound_container_volume_in_teu[ModeOfTransport.truck] = \
InboundAndOutboundVehicleCapacityCalculatorService.get_truck_capacity_for_export_containers(
inbound_container_volume_in_teu
)
inbound_container_volume_in_containers[ModeOfTransport.truck] = \
inbound_container_volume_in_teu[ModeOfTransport.truck] / \
ContainerLengthDistributionRepository.get_teu_factor()

return ContainerVolumeByVehicleType(
containers=containers,
teu=inbound_capacity_in_teu
containers=inbound_container_volume_in_containers,
teu=inbound_container_volume_in_teu
)

@staticmethod
Expand Down Expand Up @@ -126,10 +135,10 @@ 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
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)
container_volume_moved_by_vessels_in_teu = len(arrivals) * schedule.average_moved_capacity
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(
Expand All @@ -139,7 +148,7 @@ def get_outbound_capacity_of_vehicles(start_date, end_date, transportation_buffe
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)
ContainerLengthDistributionRepository.get_teu_factor()

inbound_capacity = InboundAndOutboundVehicleCapacityCalculatorService.\
get_inbound_capacity_of_vehicles(start_date, end_date)
Expand All @@ -149,7 +158,7 @@ def get_outbound_capacity_of_vehicles(start_date, end_date, transportation_buffe
)
outbound_used_containers[ModeOfTransport.truck] = \
outbound_used_capacity_in_teu[ModeOfTransport.truck] / \
(ContainerLengthDistributionRepository.get_teu_factor() * 20)
ContainerLengthDistributionRepository.get_teu_factor()

outbound_maximum_capacity_in_teu[ModeOfTransport.truck] = np.nan # Trucks can always be added as required
outbound_maximum_containers[ModeOfTransport.truck] = np.nan
Expand Down
48 changes: 41 additions & 7 deletions conflowgen/descriptive_datatypes/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,17 +26,26 @@ class HinterlandModalSplit(typing.NamedTuple):
truck_capacity: float


class OutboundUsedAndMaximumCapacity(typing.NamedTuple):
class ContainerVolume(typing.NamedTuple):
"""
This tuple keeps track of how much each vehicle type transports on the outbound journey and what the maximum
capacity is.
Several KPIs at container terminals can be both expressed in boxes and TEU.
"""
#: The container volume expressed in TEU
teu: float

#: The container volume that is actually transported, summarized by vehicle type.
used: ContainerVolumeByVehicleType
#: The container volume expressed in number of boxes
containers: float

#: The container volume that could be transported if all capacities had been used, summarized by vehicle type.
maximum: ContainerVolumeByVehicleType

class InboundAndOutboundContainerVolume(typing.NamedTuple):
"""
Note both the inbound and outbound container volume.
"""
#: The container volume transported by vehicles on their inbound journey
inbound: ContainerVolume

#: The container volume transported by vehicles on their outbound journey
outbound: ContainerVolume


class ContainerVolumeByVehicleType(typing.NamedTuple):
Expand All @@ -52,6 +61,19 @@ class ContainerVolumeByVehicleType(typing.NamedTuple):
containers: typing.Optional[typing.Dict[ModeOfTransport, float]]


class OutboundUsedAndMaximumCapacity(typing.NamedTuple):
"""
This tuple keeps track of how much each vehicle type transports on the outbound journey and what the maximum
capacity is.
"""

#: The container volume that is actually transported, summarized by vehicle type.
used: ContainerVolumeByVehicleType

#: The container volume that could be transported if all capacities had been used, summarized by vehicle type.
maximum: ContainerVolumeByVehicleType


class ContainerVolumeFromOriginToDestination(typing.NamedTuple):
"""
Several KPIs at container terminals can be both expressed in boxes per hour and TEU per hour (or a different time
Expand Down Expand Up @@ -93,3 +115,15 @@ class UsedYardCapacityOverTime(typing.NamedTuple):

#: The yard capacity expressed in number of boxes
containers: typing.Dict[datetime.datetime, int]


class ContainersTransportedByTruck(typing.NamedTuple):
"""
Represents the containers moved by trucks.
"""

#: The number of containers moved on the inbound journey
inbound: float

#: The number of containers moved on the outbound journey
outbound: float
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import math
from typing import Dict

from conflowgen.data_summaries.data_summaries_cache import DataSummariesCache
from conflowgen.domain_models.distribution_models.container_length_distribution import ContainerLengthDistribution
from conflowgen.domain_models.data_types.container_length import ContainerLength

Expand Down Expand Up @@ -55,6 +56,7 @@ def set_distribution(cls, container_lengths: Dict[ContainerLength, float]):
).save()

@classmethod
@DataSummariesCache.cache_result
def get_teu_factor(cls) -> float:
"""
Calculates and returns the TEU factor based on the container length distribution.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,14 @@ def _get_number_containers_to_allocate() -> int:
As long as the container length distribution for inbound and outbound containers are the same, using the number
of containers should lead to the same amount of containers as if we had taken the TEU capacity which is more
complex to calculate.
We do not consider the emergency pick-ups, i.e. the cases when a container was picked up by a truck just because
no truck was available.
These trucks artificially increase the import and export flows in case the container was originally a
transshipment container and without this correction out of the sudden we have two containers in the yard.
"""
number_containers: int = Container.select().where(
Container.picked_up_by == ModeOfTransport.truck
(Container.picked_up_by == ModeOfTransport.truck)
& ~Container.emergency_pickup
).count()
return number_containers

Expand Down
2 changes: 2 additions & 0 deletions conflowgen/previews/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from .inbound_and_outbound_vehicle_capacity_preview_report import InboundAndOutboundVehicleCapacityPreviewReport
from .container_flow_by_vehicle_type_preview_report import ContainerFlowByVehicleTypePreviewReport
from .modal_split_preview_report import ModalSplitPreviewReport
from .quay_side_throughput_preview_report import QuaySideThroughputPreviewReport
from .truck_gate_throughput_preview_report import TruckGateThroughputPreviewReport
from .vehicle_capacity_exceeded_preview_report import VehicleCapacityUtilizationOnOutboundJourneyPreviewReport
from ..reporting import AbstractReport
Expand All @@ -15,6 +16,7 @@
VehicleCapacityUtilizationOnOutboundJourneyPreviewReport,
ContainerFlowByVehicleTypePreviewReport,
ModalSplitPreviewReport,
QuaySideThroughputPreviewReport,
TruckGateThroughputPreviewReport
]

Expand Down
82 changes: 82 additions & 0 deletions conflowgen/previews/quay_side_throughput_preview.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import typing
from abc import ABC
from datetime import datetime

from conflowgen.data_summaries.data_summaries_cache import DataSummariesCache
from conflowgen.domain_models.distribution_repositories.container_length_distribution_repository import \
ContainerLengthDistributionRepository
from conflowgen.previews.container_flow_by_vehicle_type_preview import ContainerFlowByVehicleTypePreview
from conflowgen.domain_models.data_types.mode_of_transport import ModeOfTransport
from conflowgen.domain_models.distribution_validators import validate_distribution_with_one_dependent_variable
from conflowgen.previews.abstract_preview import AbstractPreview
from conflowgen.descriptive_datatypes import InboundAndOutboundContainerVolume, ContainerVolume


class QuaySideThroughputPreview(AbstractPreview, ABC):
"""
This preview calculates the quayside throughput based on the schedules.
The preview returns a data structure that can be used for generating reports (e.g., in text or as a figure). The
preview is intended to provide an estimate of the quayside throughput for the given inputs.
"""

QUAY_SIDE_VEHICLES = {
ModeOfTransport.deep_sea_vessel,
ModeOfTransport.feeder,
# barges are counted as hinterland here
}

def __init__(self, start_date: datetime.date, end_date: datetime.date, transportation_buffer: float):
super().__init__(start_date, end_date, transportation_buffer)
self.container_flow_by_vehicle_type = (
ContainerFlowByVehicleTypePreview(
self.start_date,
self.end_date,
self.transportation_buffer,
)
)

@DataSummariesCache.cache_result
def hypothesize_with_mode_of_transport_distribution(
self,
mode_of_transport_distribution: typing.Dict[ModeOfTransport, typing.Dict[ModeOfTransport, float]]
):
validate_distribution_with_one_dependent_variable(
mode_of_transport_distribution, ModeOfTransport, ModeOfTransport, values_are_frequencies=True
)
self.container_flow_by_vehicle_type.hypothesize_with_mode_of_transport_distribution(
mode_of_transport_distribution
)

@DataSummariesCache.cache_result
def get_quay_side_throughput(self) -> InboundAndOutboundContainerVolume:
inbound_to_outbound_flow = self.container_flow_by_vehicle_type.get_inbound_to_outbound_flow()

quayside_inbound_container_volume_in_teu: int = 0
quayside_outbound_container_volume_in_teu: int = 0

inbound_vehicle_type: ModeOfTransport
outbound_vehicle_type: ModeOfTransport
for inbound_vehicle_type, to_outbound_flow in inbound_to_outbound_flow.items():
for outbound_vehicle_type, container_volume in to_outbound_flow.items():
if inbound_vehicle_type in self.QUAY_SIDE_VEHICLES:
quayside_inbound_container_volume_in_teu += container_volume
if outbound_vehicle_type in self.QUAY_SIDE_VEHICLES:
quayside_outbound_container_volume_in_teu += container_volume

teu_factor = ContainerLengthDistributionRepository().get_teu_factor()

epsilon = 0.1

result = InboundAndOutboundContainerVolume(
inbound=ContainerVolume(
teu=quayside_inbound_container_volume_in_teu,
containers=int((quayside_inbound_container_volume_in_teu + epsilon) / teu_factor)
),
outbound=ContainerVolume(
teu=quayside_outbound_container_volume_in_teu,
containers=int((quayside_outbound_container_volume_in_teu + epsilon) / teu_factor)
)
)

return result
Loading

0 comments on commit e1783c3

Please sign in to comment.