Skip to content

Commit

Permalink
Simplify truck arrival time distribution and smaller technical debts (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
1kastner authored Aug 8, 2022
1 parent 52c4d60 commit 3cd6c27
Show file tree
Hide file tree
Showing 46 changed files with 3,333 additions and 489 deletions.
4 changes: 2 additions & 2 deletions conflowgen/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,13 +79,13 @@
# List of named tuples
from conflowgen.previews.vehicle_capacity_exceeded_preview import RequiredAndMaximumCapacityComparison
from conflowgen.previews.inbound_and_outbound_vehicle_capacity_preview import OutboundUsedAndMaximumCapacity
from conflowgen.analyses.abstract_analysis import ContainerVolume
from conflowgen.analyses.container_flow_adjustment_by_vehicle_type_analysis_summary import \
ContainerFlowAdjustedToVehicleType
from conflowgen.descriptive_datatypes import TransshipmentAndHinterlandSplit
from conflowgen.descriptive_datatypes import TransshipmentAndHinterlandSplit, ContainerVolumeFromOriginToDestination
from conflowgen.descriptive_datatypes import HinterlandModalSplit
from conflowgen.analyses.inbound_to_outbound_vehicle_capacity_utilization_analysis import \
CompleteVehicleIdentifier
from conflowgen.descriptive_datatypes import ContainerVolumeByVehicleType

# Add metadata constants
from .metadata import __version__
Expand Down
17 changes: 1 addition & 16 deletions conflowgen/analyses/abstract_analysis.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,7 @@

import abc
import datetime
from typing import NamedTuple, Dict, List, Optional

from conflowgen.domain_models.data_types.mode_of_transport import ModeOfTransport


class ContainerVolume(NamedTuple):
"""
Several KPIs at container terminals can be both expressed in boxes per hour and TEU per hour (or a different time
range).
"""

#: The container volume expressed in number of boxes
containers: Dict[ModeOfTransport, Dict[ModeOfTransport, float]]

#: The container volume expressed in TEU
TEU: Dict[ModeOfTransport, Dict[ModeOfTransport, float]]
from typing import List, Optional


def get_hour_based_time_window(point_in_time: datetime.datetime) -> datetime.datetime:
Expand Down
37 changes: 23 additions & 14 deletions conflowgen/analyses/container_dwell_time_analysis_report.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,8 @@ def get_report_as_text(self, **kwargs) -> str:
"""

container_delivered_by_vehicle_type = kwargs.pop("container_delivered_by_vehicle_type", "all")
container_picked_up_by_vehicle_type = kwargs.get("container_picked_up_by_vehicle_type", "all")
storage_requirement = kwargs.get("storage_requirement", "all")
container_picked_up_by_vehicle_type = kwargs.pop("container_picked_up_by_vehicle_type", "all")
storage_requirement = kwargs.pop("storage_requirement", "all")
assert len(kwargs) == 0, f"The following keys have not been processed: {list(kwargs.keys())}"

container_dwell_times: set[datetime.timedelta] = self.analysis.get_container_dwell_times(
Expand Down Expand Up @@ -95,13 +95,22 @@ def get_report_as_text(self, **kwargs) -> str:
def get_report_as_graph(self, **kwargs) -> object:
"""
The report as a graph is represented as a line graph using pandas.
For the exact interpretation of the parameter, check
:meth:`.ContainerDwellTimeAnalysis.get_container_dwell_times`.
Keyword Args:
storage_requirement: Either a single storage requirement of type :class:`.StorageRequirement` or a whole
collection of them e.g. passed as a :class:`list` or :class:`set`.
For the exact interpretation of the parameter, check
:meth:`.YardCapacityAnalysis.get_used_yard_capacity_over_time`.
container_delivered_by_vehicle_type: One of
``"all"``,
a collection of :class:`ModeOfTransport` enum values (as a list, set, or similar), or
a single :class:`ModeOfTransport` enum value.
container_picked_up_by_vehicle_type: One of
``"all"``,
a collection of :class:`ModeOfTransport` enum values (as a list, set, or similar), or
a single :class:`ModeOfTransport` enum value.
storage_requirement: One of
``"all"``,
a collection of :class:`StorageRequirement` enum values (as a list, set, or similar), or
a single :class:`StorageRequirement` enum value.
Returns:
The matplotlib axis of the bar chart.
"""
Expand All @@ -119,13 +128,13 @@ def get_report_as_graph(self, **kwargs) -> object:
)

if len(container_dwell_times) == 0:
return no_data_graph()

container_dwell_times_in_hours = [
int(round(dwell_time.total_seconds() / 3600)) for dwell_time in container_dwell_times
]
series = pd.Series(list(container_dwell_times_in_hours))
ax = series.plot.hist()
ax = no_data_graph()
else:
container_dwell_times_in_hours = [
int(round(dwell_time.total_seconds() / 3600)) for dwell_time in container_dwell_times
]
series = pd.Series(list(container_dwell_times_in_hours))
ax = series.plot.hist()

title = ""
title += "container is delivered by vehicle type = " + self._get_vehicle_representation(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
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.analyses.abstract_analysis import AbstractAnalysis, ContainerVolume
from conflowgen.analyses.abstract_analysis import AbstractAnalysis
from conflowgen.descriptive_datatypes import ContainerVolumeFromOriginToDestination


class ContainerFlowAdjustmentByVehicleTypeAnalysis(AbstractAnalysis):
Expand All @@ -15,7 +16,7 @@ class ContainerFlowAdjustmentByVehicleTypeAnalysis(AbstractAnalysis):
"""

@staticmethod
def get_initial_to_adjusted_outbound_flow() -> ContainerVolume:
def get_initial_to_adjusted_outbound_flow() -> ContainerVolumeFromOriginToDestination:
"""
When containers are generated, in order to obey the maximum dwell time, the vehicle type that is used for
onward transportation might change. The initial outbound vehicle type is the vehicle type that is drawn
Expand Down Expand Up @@ -45,7 +46,7 @@ def get_initial_to_adjusted_outbound_flow() -> ContainerVolume:
for vehicle_type_initial in ModeOfTransport
}

# Iterate over all containers and count number of containers / used TEU capacity
# Iterate over all containers and count number of containers / used teu capacity
container: Container
for container in Container.select():
vehicle_type_initial = container.picked_up_by_initial
Expand All @@ -55,7 +56,7 @@ def get_initial_to_adjusted_outbound_flow() -> ContainerVolume:
initial_to_adjusted_outbound_flow_in_teu[vehicle_type_initial][vehicle_type_adjusted] += \
teu_factor_of_container

return ContainerVolume(
return ContainerVolumeFromOriginToDestination(
containers=initial_to_adjusted_outbound_flow_in_containers,
TEU=initial_to_adjusted_outbound_flow_in_teu
teu=initial_to_adjusted_outbound_flow_in_teu
)
Original file line number Diff line number Diff line change
Expand Up @@ -38,14 +38,14 @@ def get_report_as_text(

initial_to_adjusted_outbound_flow = self.analysis.get_initial_to_adjusted_outbound_flow()
initial_to_adjusted_outbound_flow_in_containers = initial_to_adjusted_outbound_flow.containers
initial_to_adjusted_outbound_flow_in_teu = initial_to_adjusted_outbound_flow.TEU
initial_to_adjusted_outbound_flow_in_teu = initial_to_adjusted_outbound_flow.teu

# create string representation
report = "\n"
report += "vehicle type (initial) "
report += "vehicle type (adjusted) "
report += "initial vehicle type "
report += "adjusted vehicle type "
report += "transported capacity (in TEU) "
report += "transported capacity (in containers)"
report += "transported capacity (in boxes)"
report += "\n"
for vehicle_type_initial, vehicle_type_adjusted in itertools.product(
self.order_of_vehicle_types_in_report, repeat=2):
Expand All @@ -55,10 +55,10 @@ def get_report_as_text(
vehicle_type_adjusted]
transported_capacity_in_containers = initial_to_adjusted_outbound_flow_in_containers[vehicle_type_initial][
vehicle_type_adjusted]
report += f"{vehicle_type_from_as_text:<22} "
report += f"{vehicle_type_to_as_text:<24} "
report += f"{vehicle_type_from_as_text:<21} "
report += f"{vehicle_type_to_as_text:<23} "
report += f"{transported_capacity_in_teu:>28.1f}"
report += f"{transported_capacity_in_containers:>37}"
report += f"{transported_capacity_in_containers:>32}"
report += "\n"

report += "(rounding errors might exist)\n"
Expand Down Expand Up @@ -86,7 +86,7 @@ def get_report_as_graph(self, **kwargs) -> object:
assert len(kwargs) == 0, f"No keyword arguments supported for {self.__class__.__name__}"

initial_to_adjusted_outbound_flow = self.analysis.get_initial_to_adjusted_outbound_flow()
initial_to_adjusted_outbound_flow_in_teu = initial_to_adjusted_outbound_flow.TEU
initial_to_adjusted_outbound_flow_in_teu = initial_to_adjusted_outbound_flow.teu

vehicle_types = [
str(vehicle_type).replace("_", " ")
Expand Down Expand Up @@ -145,7 +145,8 @@ def get_report_as_graph(self, **kwargs) -> object:

fig.update_layout(
title_text="Container flow from initial vehicle type A to adjusted vehicle type B in TEU as for some "
"containers<br>the initially intended vehicle type was not available due to constraints "
"containers<br>"
"the initially intended vehicle type was not available due to constraints "
"(schedules, dwell times, etc.).",
font_size=10,
width=900,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ def get_summary(
The capacity is expressed in TEU.
"""
initial_to_adjusted_outbound_flow = self.get_initial_to_adjusted_outbound_flow()
initial_to_adjusted_outbound_flow_in_teu = initial_to_adjusted_outbound_flow.TEU
initial_to_adjusted_outbound_flow_in_teu = initial_to_adjusted_outbound_flow.teu
adjusted_to_dict = {
"unchanged": 0,
**{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from __future__ import annotations

import numpy as np
import pandas as pd

from conflowgen.analyses.container_flow_adjustment_by_vehicle_type_analysis_summary import \
Expand Down Expand Up @@ -31,35 +32,25 @@ def get_report_as_text(

adjusted_to = self.analysis_summary.get_summary()
total_capacity = sum(adjusted_to)
total_capacity = total_capacity if total_capacity else np.nan

if total_capacity > 0:
report = "\n"
report += " Capacity in TEU\n"
report += f"vehicle type unchanged: {adjusted_to.unchanged:<10.1f} " \
f"({adjusted_to.unchanged * 100 / total_capacity:.2f}%)\n"
report += f"changed to deep sea vessel: {adjusted_to.deep_sea_vessel:<10.1f} " \
f"({adjusted_to.deep_sea_vessel * 100 / total_capacity:.2f}%)\n"
report += f"changed to feeder: {adjusted_to.feeder:<10.1f} " \
f"({adjusted_to.feeder * 100 / total_capacity:.2f}%)\n"
report += f"changed to barge: {adjusted_to.barge:<10.1f} " \
f"({adjusted_to.barge * 100 / total_capacity:.2f}%)\n"
report += f"changed to train: {adjusted_to.train:<10.1f} " \
f"({adjusted_to.train * 100 / total_capacity:.2f}%)\n"
report += f"changed to truck: {adjusted_to.truck:<10.1f} " \
f"({adjusted_to.truck * 100 / total_capacity:.2f}%)\n"
report += "(rounding errors might exist)\n"
else:
report = """
Capacity in TEU
vehicle type unchanged: 0.0 (-%)
changed to deep sea vessel: 0.0 (-%)
changed to feeder: 0.0 (-%)
changed to barge: 0.0 (-%)
changed to train: 0.0 (-%)
changed to truck: 0.0 (-%)
(rounding errors might exist)
"""
return report
report = "\n"
report += " Capacity in TEU\n"
report += f"vehicle type unchanged: {adjusted_to.unchanged:>10.1f} " \
f"({adjusted_to.unchanged / total_capacity:>5.2%})\n"
report += f"changed to deep sea vessel: {adjusted_to.deep_sea_vessel:>10.1f} " \
f"({adjusted_to.deep_sea_vessel / total_capacity:>5.2%})\n"
report += f"changed to feeder: {adjusted_to.feeder:>10.1f} " \
f"({adjusted_to.feeder / total_capacity:>5.2%})\n"
report += f"changed to barge: {adjusted_to.barge:>10.1f} " \
f"({adjusted_to.barge / total_capacity:>5.2%})\n"
report += f"changed to train: {adjusted_to.train:>10.1f} " \
f"({adjusted_to.train / total_capacity:>5.2%})\n"
report += f"changed to truck: {adjusted_to.truck:>10.1f} " \
f"({adjusted_to.truck / total_capacity:>5.2%})\n"
report += "(rounding errors might exist)\n"

return report.replace(" nan", "-")

def get_report_as_graph(self, **kwargs) -> object:
"""
Expand All @@ -71,21 +62,25 @@ def get_report_as_graph(self, **kwargs) -> object:
assert len(kwargs) == 0, f"No keyword arguments supported for {self.__class__.__name__}"

adjusted_to = self.analysis_summary.get_summary()
if sum(adjusted_to) == 0:
return no_data_graph()

data_series = pd.Series({
"unchanged": adjusted_to.unchanged,
"deep sea vessel": adjusted_to.deep_sea_vessel,
"feeder": adjusted_to.feeder,
"barge": adjusted_to.barge,
"train": adjusted_to.train,
"truck": adjusted_to.truck
}, name="Transshipment share")
ax = data_series.plot.pie(
legend=False,
autopct='%1.1f%%',
label="",
title="Adjusted vehicle type"
)
plot_title = "Adjusted vehicle type"

if sum(adjusted_to) == 0:
ax = no_data_graph()
ax.set_title(plot_title)
else:
data_series = pd.Series({
"unchanged": adjusted_to.unchanged,
"deep sea vessel": adjusted_to.deep_sea_vessel,
"feeder": adjusted_to.feeder,
"barge": adjusted_to.barge,
"train": adjusted_to.train,
"truck": adjusted_to.truck
}, name="Vehicle type adjustment")
ax = data_series.plot.pie(
legend=False,
autopct='%1.1f%%',
label="",
title=plot_title
)
return ax
Loading

0 comments on commit 3cd6c27

Please sign in to comment.