Skip to content

Commit

Permalink
Add ContainerFlowByVehicleInstanceAnalysis and its report incl. docs,…
Browse files Browse the repository at this point in the history
… fix name of OutboundToInboundVehicleCapacityUtilizationAnalysis and its report
  • Loading branch information
1kastner committed May 24, 2024
1 parent 25c1427 commit 7051293
Show file tree
Hide file tree
Showing 14 changed files with 280 additions and 25 deletions.
11 changes: 7 additions & 4 deletions conflowgen/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
8 changes: 5 additions & 3 deletions conflowgen/analyses/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -26,6 +27,7 @@
reports: typing.Iterable[typing.Type[AbstractReport]] = [
InboundAndOutboundVehicleCapacityAnalysisReport,
ContainerFlowByVehicleTypeAnalysisReport,
ContainerFlowByVehicleInstanceAnalysisReport,
ContainerFlowAdjustmentByVehicleTypeAnalysisReport,
ContainerFlowAdjustmentByVehicleTypeAnalysisSummaryReport,
ContainerFlowVehicleTypeAdjustmentPerVehicleAnalysisReport,
Expand All @@ -34,7 +36,7 @@
QuaySideThroughputAnalysisReport,
TruckGateThroughputAnalysisReport,
YardCapacityAnalysisReport,
InboundToOutboundVehicleCapacityUtilizationAnalysisReport
OutboundToInboundVehicleCapacityUtilizationAnalysisReport
]


Expand Down
107 changes: 107 additions & 0 deletions conflowgen/analyses/container_flow_by_vehicle_instance_analysis.py
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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 \
<notebooks/analyses.ipynb#Container-Flow-By-Vehicle-Instance-Analysis-Report>`_.
"""

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
Original file line number Diff line number Diff line change
Expand Up @@ -99,13 +99,15 @@ 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,
vehicle_name=None
)
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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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.
Expand All @@ -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
Expand Down
9 changes: 8 additions & 1 deletion conflowgen/descriptive_datatypes/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -143,3 +146,7 @@ class FlowDirection(enum.Enum):
transshipment_flow = "transshipment"

undefined = "undefined"

def __str__(self) -> str:
# noinspection PyTypeChecker
return f"{self.value}"
Loading

0 comments on commit 7051293

Please sign in to comment.