Input Distributions
In this chapter, the default input distributions are presented. These are automatically seeded when a new database is created. They can be simply overwritten with your use-case specific assumptions.
First, the required modules are imported.
[1]:
import datetime
import itertools
import matplotlib
import matplotlib.pyplot as plt
import numpy as np
from IPython.display import Markdown
import conflowgen
Set a style for matplotlib.
[2]:
plt.style.use("seaborn-v0_8-colorblind")
Next, an in-memory SQLite database is opened. This is a fresh database without any content. While creating the database, it is automatically seeded with the default values.
[3]:
database_chooser = conflowgen.DatabaseChooser()
database_chooser.create_new_sqlite_database(":memory:")
If that was too fast, you can switch on logging to have a look behind the scenes.
[4]:
conflowgen.setup_logger(
logging_directory="./data/logger", # use data subdirectory relative to Jupyter Notebook
format_string="%(message)s", # only show log messages, no timestamps etc.
)
Creating log file at ./data/logger/2025-02-16--20-56-30.log
[4]:
<Logger conflowgen (DEBUG)>
Now you can see the same output like in the demo scripts.
[5]:
database_chooser.create_new_sqlite_database(":memory:")
Closing current database connection :memory:.
Opening file :memory:
journal_mode: memory
cache_size: -32768
page_size: 4096
foreign_keys: 1
Creating new database at :memory:
Creating all tables...
Seed with default values...
Number entries in table 'DeepSeaVessel': 0
Number entries in table 'Feeder': 0
Number entries in table 'Barge': 0
Number entries in table 'Train': 0
Number entries in table 'Truck': 0
Number entries in table 'Container': 0
Randomly set seed 1739739390851425151 for 'SqliteDatabaseConnection'
Once the database is set up, we can use the different distribution managers to have a look at the distributions which were automatically seeded.
Container Length Distribution
For each container length, the container length distribution determines its frequency among the containers. The numbers have been determined by the following reasoning:
Note
- DEFAULT_CONTAINER_LENGTH_FREQUENCIES = {ContainerLength.forty_feet: 0.57, ContainerLength.forty_five_feet: 0.029, ContainerLength.other: 0.001, ContainerLength.twenty_feet: 0.4}
In general, most containerized goods are transported in 20’ and 40’ sea containers. In Germany in August 2021, only 1% of containerized goods (measured in weight) were transported in a container different from these two standard sizes [Statistisches Bundesamt (DESTATIS), 2021]. The same statistics says that approximately 30% of the goods (measured in weight again) are transported in 20’ containers, and 40’ containers make up 67%. For ConFlowGen, however, the fraction in numbers of containers is required instead of the fraction based on weight. In an expert interview it was said that the TEU factor in their case is approximately 1.6 and 45 foot containers made up less than 5%.
The numbers used here are inspired by the reported statistics and the expert interview. They are believed to be a reasonable first assumption if no data is available.
The container length distribution is obtained with the following lines of code:
[6]:
container_length_manager = conflowgen.ContainerLengthDistributionManager()
length_distribution = container_length_manager.get_container_length_distribution()
length_distribution
[6]:
{<ContainerLength.other: -1>: 0.001,
<ContainerLength.twenty_feet: 20>: 0.4,
<ContainerLength.forty_feet: 40>: 0.57,
<ContainerLength.forty_five_feet: 45>: 0.029}
We can see that all the keys are enum values of the enum
ContainerLength
.
For a nicer visualization, we can circumvent that by converting the enum values into strings.
[7]:
length_distribution_with_key_as_str = {
str(key): value for (key, value) in length_distribution.items()
}
length_distribution_with_key_as_str
[7]:
{'other': 0.001, '20 feet': 0.4, '40 feet': 0.57, '45 feet': 0.029}
Now we can also plot the same information as a pie chart.
[8]:
length_distribution_with_key_as_str_without_zeros = {
key: value
for (key, value) in length_distribution_with_key_as_str.items()
if value > 0
}
plt.title("Frequency of container lengths")
plt.pie(
list(length_distribution_with_key_as_str_without_zeros.values()),
labels=list(length_distribution_with_key_as_str_without_zeros.keys()),
autopct="%1.0f%%",
)
plt.gcf().set_size_inches(5, 5)
plt.show()
More information on setting and getting the distribution can be found at
ContainerLengthDistributionManager
.
Container Weight Distribution
The container weight of each container is drawn from a distribution. For each container length, a different weight distribution can be provided.
Note
- DEFAULT_CONTAINER_WEIGHT_DISTRIBUTION = {ContainerLength.forty_feet: {2: 0, 4: 2, 6: 5, 8: 6, 10: 9, 12: 11, 14: 11, 16: 9, 18: 4, 20: 3, 22: 3, 24: 3, 26: 1, 28: 1, 30: 1}, ContainerLength.forty_five_feet: {2: 0, 4: 2, 6: 5, 8: 6, 10: 9, 12: 11, 14: 11, 16: 9, 18: 4, 20: 3, 22: 3, 24: 3, 26: 1, 28: 1, 30: 1}, ContainerLength.other: {2: 0, 4: 0, 6: 0, 8: 0, 10: 0, 12: 0, 14: 0, 16: 0, 18: 0, 20: 0, 22: 0, 24: 0, 26: 0, 28: 0, 30: 0}, ContainerLength.twenty_feet: {2: 1, 4: 1, 6: 2, 8: 2.5, 10: 2.5, 12: 3, 14: 3, 16: 3, 18: 2.5, 20: 2.5, 22: 2, 24: 2, 26: 2, 28: 1, 30: 1}}
The initially seeded weight distributions stem from the cargo profile of the brochure [MacGregor, 2016]. The key refers to the weight of the container in metric tonnes. The value describes the relative frequency.
[9]:
container_weight_distribution_manager = conflowgen.ContainerWeightDistributionManager()
weight_distribution = (
container_weight_distribution_manager.get_container_weight_distribution()
)
weight_distribution
[9]:
{<ContainerLength.twenty_feet: 20>: {2: 0.03225806451612903,
4: 0.03225806451612903,
6: 0.06451612903225806,
8: 0.08064516129032258,
10: 0.08064516129032258,
12: 0.0967741935483871,
14: 0.0967741935483871,
16: 0.0967741935483871,
18: 0.08064516129032258,
20: 0.08064516129032258,
22: 0.06451612903225806,
24: 0.06451612903225806,
26: 0.06451612903225806,
28: 0.03225806451612903,
30: 0.03225806451612903},
<ContainerLength.forty_feet: 40>: {2: 0.0,
4: 0.028985507246376812,
6: 0.07246376811594203,
8: 0.08695652173913043,
10: 0.13043478260869565,
12: 0.15942028985507245,
14: 0.15942028985507245,
16: 0.13043478260869565,
18: 0.057971014492753624,
20: 0.043478260869565216,
22: 0.043478260869565216,
24: 0.043478260869565216,
26: 0.014492753623188406,
28: 0.014492753623188406,
30: 0.014492753623188406},
<ContainerLength.forty_five_feet: 45>: {2: 0.0,
4: 0.028985507246376812,
6: 0.07246376811594203,
8: 0.08695652173913043,
10: 0.13043478260869565,
12: 0.15942028985507245,
14: 0.15942028985507245,
16: 0.13043478260869565,
18: 0.057971014492753624,
20: 0.043478260869565216,
22: 0.043478260869565216,
24: 0.043478260869565216,
26: 0.014492753623188406,
28: 0.014492753623188406,
30: 0.014492753623188406},
<ContainerLength.other: -1>: {2: 0.06666666666666667,
4: 0.06666666666666667,
6: 0.06666666666666667,
8: 0.06666666666666667,
10: 0.06666666666666667,
12: 0.06666666666666667,
14: 0.06666666666666667,
16: 0.06666666666666667,
18: 0.06666666666666667,
20: 0.06666666666666667,
22: 0.06666666666666667,
24: 0.06666666666666667,
26: 0.06666666666666667,
28: 0.06666666666666667,
30: 0.06666666666666667}}
By visualizing the previous distribution with matplotlib, it is easier to make sense of it.
[10]:
fig, axes = plt.subplots(nrows=4, sharex=True, sharey=True, figsize=(5, 12))
for ax, container_length in zip(axes, list(conflowgen.ContainerLength)):
x, y = zip(*weight_distribution[container_length].items())
ax.title.set_text(str(container_length))
ax.bar(x, [i * 100 for i in y])
ax.set_ylabel("Share (in percentage)")
plt.xlabel("Weight (in metric tonnes)")
plt.show()
The container weight distributions can only be overwritten all at once. The values are automatically normalized by default.
[11]:
container_weight_distribution_manager.set_container_weight_distribution(
{
conflowgen.ContainerLength.twenty_feet: {10: 20, 20: 50, 30: 30},
conflowgen.ContainerLength.forty_feet: {10: 15, 20: 50, 30: 35},
conflowgen.ContainerLength.forty_five_feet: {10: 10, 20: 5, 30: 85},
conflowgen.ContainerLength.other: {10: 1, 20: 1, 30: 1},
}
)
Sum of fractions was not 1 for '20 feet' and was automatically normalized.
Sum of fractions was not 1 for '40 feet' and was automatically normalized.
Sum of fractions was not 1 for '45 feet' and was automatically normalized.
Sum of fractions was not 1 for 'other' and was automatically normalized.
From now on, ConFlowGen uses the new container weight distribution:
[12]:
container_weight_distribution_manager.get_container_weight_distribution()
[12]:
{<ContainerLength.twenty_feet: 20>: {10: 0.2, 20: 0.5, 30: 0.3},
<ContainerLength.forty_feet: 40>: {10: 0.15, 20: 0.5, 30: 0.35},
<ContainerLength.forty_five_feet: 45>: {10: 0.1, 20: 0.05, 30: 0.85},
<ContainerLength.other: -1>: {10: 0.3333333333333333,
20: 0.3333333333333333,
30: 0.3333333333333333}}
More information on setting and getting the distribution can be found at
ContainerWeightDistributionManager
.
Container Storage Requirement Distribution
The storage requirement of each container is drawn from a distribution. For each container length, a different storage requirement distribution can be provided.
Note
- DEFAULT_STORAGE_REQUIREMENT_DISTRIBUTION = {ContainerLength.forty_feet: {StorageRequirement.dangerous_goods: 0.08100000000000002, StorageRequirement.empty: 0.12, StorageRequirement.reefer: 0.07, StorageRequirement.standard: 0.7290000000000001}, ContainerLength.forty_five_feet: {StorageRequirement.dangerous_goods: 0.08100000000000002, StorageRequirement.empty: 0.12, StorageRequirement.reefer: 0.07, StorageRequirement.standard: 0.7290000000000001}, ContainerLength.other: {StorageRequirement.dangerous_goods: 0.08100000000000002, StorageRequirement.empty: 0.12, StorageRequirement.reefer: 0.07, StorageRequirement.standard: 0.7290000000000001}, ContainerLength.twenty_feet: {StorageRequirement.dangerous_goods: 0.08100000000000002, StorageRequirement.empty: 0.12, StorageRequirement.reefer: 0.07, StorageRequirement.standard: 0.7290000000000001}}
Containers come with different storage requirements. The given distribution is estimated based on the combination of several sources. Currently, no differentiation between the different container lengths exist.
The container storage requirement distribution is obtained with the following lines of code:
[13]:
storage_manager = conflowgen.StorageRequirementDistributionManager()
storage_requirement_distribution = (
storage_manager.get_storage_requirement_distribution()
)
storage_requirement_distribution
[13]:
{<ContainerLength.twenty_feet: 20>: {<StorageRequirement.empty: 'empty'>: 0.12,
<StorageRequirement.standard: 'standard'>: 0.7290000000000001,
<StorageRequirement.reefer: 'reefer'>: 0.07,
<StorageRequirement.dangerous_goods: 'dangerous_goods'>: 0.08100000000000002},
<ContainerLength.forty_feet: 40>: {<StorageRequirement.empty: 'empty'>: 0.12,
<StorageRequirement.standard: 'standard'>: 0.7290000000000001,
<StorageRequirement.reefer: 'reefer'>: 0.07,
<StorageRequirement.dangerous_goods: 'dangerous_goods'>: 0.08100000000000002},
<ContainerLength.forty_five_feet: 45>: {<StorageRequirement.empty: 'empty'>: 0.12,
<StorageRequirement.standard: 'standard'>: 0.7290000000000001,
<StorageRequirement.reefer: 'reefer'>: 0.07,
<StorageRequirement.dangerous_goods: 'dangerous_goods'>: 0.08100000000000002},
<ContainerLength.other: -1>: {<StorageRequirement.empty: 'empty'>: 0.12,
<StorageRequirement.standard: 'standard'>: 0.7290000000000001,
<StorageRequirement.reefer: 'reefer'>: 0.07,
<StorageRequirement.dangerous_goods: 'dangerous_goods'>: 0.08100000000000002}}
This can be visualized e.g. with the help of one pie chart for each container length.
[14]:
fig, axes = plt.subplots(nrows=2, ncols=2, figsize=(8, 8))
axes = list(itertools.chain(*axes))
for ax, container_length in zip(axes, list(conflowgen.ContainerLength)):
keys, values = zip(*storage_requirement_distribution[container_length].items())
labels = list(map(lambda x: str(x).replace("_", "\n"), keys))
ax.title.set_text(str(container_length))
ax.pie(
[i * 100 for i in values],
labels=labels,
autopct="%1.0f%%",
pctdistance=0.7,
labeldistance=1.18,
startangle=45,
)
plt.tight_layout()
plt.show()
The container storage distributions can only be overwritten all at once. The values are automatically normalized by default.
[15]:
storage_manager.set_storage_requirement_distribution(
{
conflowgen.ContainerLength.twenty_feet: {
conflowgen.StorageRequirement.empty: 0.3,
conflowgen.StorageRequirement.standard: 0.5,
conflowgen.StorageRequirement.reefer: 0.1,
conflowgen.StorageRequirement.dangerous_goods: 0.1,
},
conflowgen.ContainerLength.forty_feet: {
conflowgen.StorageRequirement.empty: 0.2,
conflowgen.StorageRequirement.standard: 0.45,
conflowgen.StorageRequirement.reefer: 0.2,
conflowgen.StorageRequirement.dangerous_goods: 0.15,
},
conflowgen.ContainerLength.forty_five_feet: {
conflowgen.StorageRequirement.empty: 0.2,
conflowgen.StorageRequirement.standard: 0.7,
conflowgen.StorageRequirement.reefer: 0.05,
conflowgen.StorageRequirement.dangerous_goods: 0.05,
},
conflowgen.ContainerLength.other: {
conflowgen.StorageRequirement.empty: 0.25,
conflowgen.StorageRequirement.standard: 0.4,
conflowgen.StorageRequirement.reefer: 0.25,
conflowgen.StorageRequirement.dangerous_goods: 0.1,
},
}
)
From now on, ConFlowGen uses the new distribution.
[16]:
storage_manager.get_storage_requirement_distribution()
[16]:
{<ContainerLength.twenty_feet: 20>: {<StorageRequirement.empty: 'empty'>: 0.3,
<StorageRequirement.standard: 'standard'>: 0.5,
<StorageRequirement.reefer: 'reefer'>: 0.1,
<StorageRequirement.dangerous_goods: 'dangerous_goods'>: 0.1},
<ContainerLength.forty_feet: 40>: {<StorageRequirement.empty: 'empty'>: 0.2,
<StorageRequirement.standard: 'standard'>: 0.45,
<StorageRequirement.reefer: 'reefer'>: 0.2,
<StorageRequirement.dangerous_goods: 'dangerous_goods'>: 0.15},
<ContainerLength.forty_five_feet: 45>: {<StorageRequirement.empty: 'empty'>: 0.2,
<StorageRequirement.standard: 'standard'>: 0.7,
<StorageRequirement.reefer: 'reefer'>: 0.05,
<StorageRequirement.dangerous_goods: 'dangerous_goods'>: 0.05},
<ContainerLength.other: -1>: {<StorageRequirement.empty: 'empty'>: 0.25,
<StorageRequirement.standard: 'standard'>: 0.4,
<StorageRequirement.reefer: 'reefer'>: 0.25,
<StorageRequirement.dangerous_goods: 'dangerous_goods'>: 0.1}}
More information on setting and getting the distribution can be found at
StorageRequirementDistributionManager
.
Mode of Transport Distribution
The mode of transport for each container is drawn from a distribution.
Note
- DEFAULT_MODE_OF_TRANSPORT_DISTRIBUTION = {ModeOfTransport.barge: {ModeOfTransport.barge: 0, ModeOfTransport.deep_sea_vessel: 0.8518518518518519, ModeOfTransport.feeder: 0.14814814814814817, ModeOfTransport.train: 0, ModeOfTransport.truck: 0}, ModeOfTransport.deep_sea_vessel: {ModeOfTransport.barge: 0.0019815384615384612, ModeOfTransport.deep_sea_vessel: 0, ModeOfTransport.feeder: 0.29230769230769227, ModeOfTransport.train: 0.3326153846153846, ModeOfTransport.truck: 0.3552615384615384}, ModeOfTransport.feeder: {ModeOfTransport.barge: 0.0008296296296296296, ModeOfTransport.deep_sea_vessel: 0.7037037037037036, ModeOfTransport.feeder: 0, ModeOfTransport.train: 0.13925925925925925, ModeOfTransport.truck: 0.14874074074074073}, ModeOfTransport.train: {ModeOfTransport.barge: 0, ModeOfTransport.deep_sea_vessel: 0.7018518518518518, ModeOfTransport.feeder: 0.29814814814814816, ModeOfTransport.train: 0, ModeOfTransport.truck: 0}, ModeOfTransport.truck: {ModeOfTransport.barge: 0, ModeOfTransport.deep_sea_vessel: 0.7018518518518518, ModeOfTransport.feeder: 0.29814814814814816, ModeOfTransport.train: 0, ModeOfTransport.truck: 0}}
This mode of transport distribution is based on the report [Institut für Seeverkehrswirtschaft und Logistik, 2015]. The exact data for transshipment and hinterland share is taken from page 22, Figure 12 “Containerumschlag des Hafens Hamburg in TEU / Marktsegment 2013”. The modal split of the hinterland is updated based on the figures presented by Hafen Hamburg [2021]. After those adaptions, still there were several imbalances. Thus, some traffic was shifted from deep sea vessels to feeders by adding/subtracting some constants. In summary, this is an educated guess based on several sources.
The mode of transport distribution is obtained by the following code:
[17]:
mode_of_transport = conflowgen.ModeOfTransportDistributionManager()
mode_of_transport_distribution = mode_of_transport.get_mode_of_transport_distribution()
mode_of_transport_distribution
[17]:
{<ModeOfTransport.truck: 'truck'>: {<ModeOfTransport.truck: 'truck'>: 0,
<ModeOfTransport.train: 'train'>: 0,
<ModeOfTransport.feeder: 'feeder'>: 0.29814814814814816,
<ModeOfTransport.deep_sea_vessel: 'deep_sea_vessel'>: 0.7018518518518518,
<ModeOfTransport.barge: 'barge'>: 0},
<ModeOfTransport.train: 'train'>: {<ModeOfTransport.truck: 'truck'>: 0,
<ModeOfTransport.train: 'train'>: 0,
<ModeOfTransport.feeder: 'feeder'>: 0.29814814814814816,
<ModeOfTransport.deep_sea_vessel: 'deep_sea_vessel'>: 0.7018518518518518,
<ModeOfTransport.barge: 'barge'>: 0},
<ModeOfTransport.feeder: 'feeder'>: {<ModeOfTransport.truck: 'truck'>: 0.14985969311600694,
<ModeOfTransport.train: 'train'>: 0.14030688399307423,
<ModeOfTransport.feeder: 'feeder'>: 0,
<ModeOfTransport.deep_sea_vessel: 'deep_sea_vessel'>: 0.7089975520926622,
<ModeOfTransport.barge: 'barge'>: 0.0008358707982566125},
<ModeOfTransport.deep_sea_vessel: 'deep_sea_vessel'>: {<ModeOfTransport.truck: 'truck'>: 0.3617122592448716,
<ModeOfTransport.train: 'train'>: 0.33865490407388377,
<ModeOfTransport.feeder: 'feeder'>: 0.2976153181037831,
<ModeOfTransport.deep_sea_vessel: 'deep_sea_vessel'>: 0,
<ModeOfTransport.barge: 'barge'>: 0.0020175185774614353},
<ModeOfTransport.barge: 'barge'>: {<ModeOfTransport.truck: 'truck'>: 0,
<ModeOfTransport.train: 'train'>: 0,
<ModeOfTransport.feeder: 'feeder'>: 0.14814814814814817,
<ModeOfTransport.deep_sea_vessel: 'deep_sea_vessel'>: 0.8518518518518519,
<ModeOfTransport.barge: 'barge'>: 0}}
The mode of transport distributions can only be overwritten all at once. The values are automatically normalized by default.
[18]:
mode_of_transport.set_mode_of_transport_distribution(
{
conflowgen.ModeOfTransport.feeder: {
conflowgen.ModeOfTransport.train: 0.2,
conflowgen.ModeOfTransport.truck: 0.1,
conflowgen.ModeOfTransport.barge: 0.1,
conflowgen.ModeOfTransport.feeder: 0.4,
conflowgen.ModeOfTransport.deep_sea_vessel: 0.2,
},
conflowgen.ModeOfTransport.truck: {
conflowgen.ModeOfTransport.train: 0.15,
conflowgen.ModeOfTransport.truck: 0.45,
conflowgen.ModeOfTransport.barge: 0.1,
conflowgen.ModeOfTransport.feeder: 0.2,
conflowgen.ModeOfTransport.deep_sea_vessel: 0.1,
},
conflowgen.ModeOfTransport.barge: {
conflowgen.ModeOfTransport.train: 0.2,
conflowgen.ModeOfTransport.truck: 0.15,
conflowgen.ModeOfTransport.barge: 0.25,
conflowgen.ModeOfTransport.feeder: 0.1,
conflowgen.ModeOfTransport.deep_sea_vessel: 0.3,
},
conflowgen.ModeOfTransport.deep_sea_vessel: {
conflowgen.ModeOfTransport.train: 0.25,
conflowgen.ModeOfTransport.truck: 0.1,
conflowgen.ModeOfTransport.barge: 0.2,
conflowgen.ModeOfTransport.feeder: 0.15,
conflowgen.ModeOfTransport.deep_sea_vessel: 0.3,
},
conflowgen.ModeOfTransport.train: {
conflowgen.ModeOfTransport.train: 0.3,
conflowgen.ModeOfTransport.truck: 0.1,
conflowgen.ModeOfTransport.barge: 0.15,
conflowgen.ModeOfTransport.feeder: 0.2,
conflowgen.ModeOfTransport.deep_sea_vessel: 0.25,
},
}
)
From now on, ConFlowGen uses the new distribution.
[19]:
mode_of_transport.get_mode_of_transport_distribution()
[19]:
{<ModeOfTransport.truck: 'truck'>: {<ModeOfTransport.truck: 'truck'>: 0.45,
<ModeOfTransport.train: 'train'>: 0.15,
<ModeOfTransport.feeder: 'feeder'>: 0.2,
<ModeOfTransport.deep_sea_vessel: 'deep_sea_vessel'>: 0.1,
<ModeOfTransport.barge: 'barge'>: 0.1},
<ModeOfTransport.train: 'train'>: {<ModeOfTransport.truck: 'truck'>: 0.1,
<ModeOfTransport.train: 'train'>: 0.3,
<ModeOfTransport.feeder: 'feeder'>: 0.2,
<ModeOfTransport.deep_sea_vessel: 'deep_sea_vessel'>: 0.25,
<ModeOfTransport.barge: 'barge'>: 0.15},
<ModeOfTransport.feeder: 'feeder'>: {<ModeOfTransport.truck: 'truck'>: 0.09999999999999998,
<ModeOfTransport.train: 'train'>: 0.19999999999999996,
<ModeOfTransport.feeder: 'feeder'>: 0.3999999999999999,
<ModeOfTransport.deep_sea_vessel: 'deep_sea_vessel'>: 0.19999999999999996,
<ModeOfTransport.barge: 'barge'>: 0.09999999999999998},
<ModeOfTransport.deep_sea_vessel: 'deep_sea_vessel'>: {<ModeOfTransport.truck: 'truck'>: 0.1,
<ModeOfTransport.train: 'train'>: 0.25,
<ModeOfTransport.feeder: 'feeder'>: 0.15,
<ModeOfTransport.deep_sea_vessel: 'deep_sea_vessel'>: 0.3,
<ModeOfTransport.barge: 'barge'>: 0.2},
<ModeOfTransport.barge: 'barge'>: {<ModeOfTransport.truck: 'truck'>: 0.15,
<ModeOfTransport.train: 'train'>: 0.2,
<ModeOfTransport.feeder: 'feeder'>: 0.1,
<ModeOfTransport.deep_sea_vessel: 'deep_sea_vessel'>: 0.3,
<ModeOfTransport.barge: 'barge'>: 0.25}}
More information on setting and getting the distribution can be found at
ModeOfTransportDistributionManager
.
Truck Arrival Distribution Manager
Each truck arrival time is drawn from a distribution.
Note
- DEFAULT_TRUCK_ARRIVAL_DISTRIBUTION_WITH_NO_ARRIVAL_MANAGEMENT
This distribution assigns each hour of the week to the fraction of trucks that arrive during this time window. The distribution is based on real data of a project that took place approximately between 2013 and 2017. To ensure confidentiality, the distribution has been slightly distorted.
Truck arrival distribution is obtained by the following code:
[20]:
truck_arrival_distribution_manager = conflowgen.TruckArrivalDistributionManager()
truck_arrival_distribution = (
truck_arrival_distribution_manager.get_truck_arrival_distribution()
)
truck_arrival_distribution
[20]:
{0.0: 0.0,
1.0: 0.0,
2.0: 0.0,
3.0: 0.0,
4.0: 0.0,
5.0: 0.0039591265543534575,
6.0: 0.008280755354708402,
7.0: 0.00787052138708076,
8.0: 0.009048448164603814,
9.0: 0.010653252222483504,
10.0: 0.012752141622641803,
11.0: 0.016642037255734387,
12.0: 0.014028517880762,
13.0: 0.014804115031537253,
14.0: 0.014974413128949352,
15.0: 0.011139325718994135,
16.0: 0.013892795598075644,
17.0: 0.01082340227148447,
18.0: 0.008328057746798652,
19.0: 0.006987426702627708,
20.0: 0.005148702946956847,
21.0: 0.0030022110241690898,
22.0: 0.0022556664886468924,
23.0: 0.002490824815783658,
24.0: 0.001903829363512033,
25.0: 0.0021963463393818504,
26.0: 0.001702371138626582,
27.0: 0.0021438383478597847,
28.0: 0.0024202228363111615,
29.0: 0.006458109051981418,
30.0: 0.009296920847765565,
31.0: 0.008129901930327036,
32.0: 0.009348584294496615,
33.0: 0.011340930095712323,
34.0: 0.013698448606867216,
35.0: 0.01296799663104594,
36.0: 0.015331193639106963,
37.0: 0.014188986240397503,
38.0: 0.014878231656167027,
39.0: 0.01218616653188358,
40.0: 0.012107579394020204,
41.0: 0.010917000115475164,
42.0: 0.006806732487834122,
43.0: 0.005802918750649381,
44.0: 0.005287802279192979,
45.0: 0.0028202830127811215,
46.0: 0.0019358005313836828,
47.0: 0.0024196460473237236,
48.0: 0.0016307576755443523,
49.0: 0.0019988666796929904,
50.0: 0.001446034417884346,
51.0: 0.0010097489273788896,
52.0: 0.0022229861377374384,
53.0: 0.008228976109664983,
54.0: 0.00916729394725238,
55.0: 0.008981193048564363,
56.0: 0.010437595120044508,
57.0: 0.011883447250428468,
58.0: 0.013126241314098189,
59.0: 0.0140232137102564,
60.0: 0.014067510063763042,
61.0: 0.017925057408950704,
62.0: 0.014893617277832918,
63.0: 0.01301145426124103,
64.0: 0.012079362990869175,
65.0: 0.010112961782918234,
66.0: 0.00893673181616467,
67.0: 0.006631710275002562,
68.0: 0.004326674554006004,
69.0: 0.004305598082248182,
70.0: 0.0022903162137174965,
71.0: 0.0024661555911701296,
72.0: 0.0011415664927662006,
73.0: 0.0012494109397148158,
74.0: 0.0009989509275061823,
75.0: 0.0009419532259761962,
76.0: 0.002040252335905318,
77.0: 0.00518431625514197,
78.0: 0.009913000508486947,
79.0: 0.010654141394182583,
80.0: 0.010344655620812727,
81.0: 0.012472178423578372,
82.0: 0.015253769000857457,
83.0: 0.015313545656682602,
84.0: 0.01971921057376204,
85.0: 0.016488565599922105,
86.0: 0.016894274684674377,
87.0: 0.015499123208186931,
88.0: 0.01478237177250456,
89.0: 0.012150479118805851,
90.0: 0.008135216144988145,
91.0: 0.006333340451456769,
92.0: 0.006095849295750999,
93.0: 0.004708883365054937,
94.0: 0.003413326087863949,
95.0: 0.0017118289895981984,
96.0: 0.0026912758548089605,
97.0: 0.0021584624941145677,
98.0: 0.0023228922170533146,
99.0: 0.001604168692757123,
100.0: 0.0027305554397402476,
101.0: 0.0065523938632102915,
102.0: 0.009520380832912196,
103.0: 0.010997001773196237,
104.0: 0.010602136875550094,
105.0: 0.015899660970804596,
106.0: 0.018083220148664984,
107.0: 0.0163816471763427,
108.0: 0.01629007302430533,
109.0: 0.019168920074881534,
110.0: 0.01646589595887871,
111.0: 0.013656904790633789,
112.0: 0.012617169136636602,
113.0: 0.009685606800402495,
114.0: 0.009069337128450136,
115.0: 0.004422493262915178,
116.0: 0.0042658850465993456,
117.0: 0.0030436628208826318,
118.0: 0.0016924428501923685,
119.0: 0.002152265219068244,
120.0: 0.0028091995053135693,
121.0: 0.0022128380816916287,
122.0: 0.0020158483718963533,
123.0: 0.0010395871009478725,
124.0: 0.0009474696390102265,
125.0: 0.0011628071003245448,
126.0: 0.001418797422137764,
127.0: 0.0016522620284370162,
128.0: 0.0015376248047583672,
129.0: 0.0014253278743416424,
130.0: 0.0007202777097896012,
131.0: 0.0005074102872076632,
132.0: 0.0004608008040356385,
133.0: 0.0,
134.0: 0.0,
135.0: 0.0,
136.0: 0.0,
137.0: 0.0,
138.0: 0.0,
139.0: 0.0,
140.0: 0.0,
141.0: 0.0,
142.0: 0.0,
143.0: 0.0,
144.0: 0.0,
145.0: 0.0,
146.0: 0.0,
147.0: 0.0,
148.0: 0.0,
149.0: 0.0,
150.0: 0.0,
151.0: 0.0,
152.0: 0.0,
153.0: 0.0,
154.0: 0.0,
155.0: 0.0,
156.0: 0.0,
157.0: 0.0,
158.0: 0.0,
159.0: 0.0,
160.0: 0.0,
161.0: 0.0,
162.0: 0.0,
163.0: 0.0,
164.0: 0.0,
165.0: 0.0,
166.0: 0.0,
167.0: 0.0}
With a few lines of matplotlib code, the truck arrival pattern is visualized.
[21]:
hour_in_week, fraction = zip(*list(sorted(truck_arrival_distribution.items())))
weekday_in_week = [x / 24 + 1 for x in hour_in_week]
percentage = [x * 100 for x in fraction]
fig, ax = plt.subplots(figsize=(15, 3))
plt.plot(weekday_in_week, percentage)
plt.xlim([1, 7]) # plot from Monday to Sunday
ax.xaxis.grid(True, which="minor", color="lightgray") # every hour
ax.xaxis.grid(True, which="major", color="k") # every day
ax.xaxis.set_minor_locator(matplotlib.ticker.MultipleLocator(1 / 24)) # every hour
plt.title("Truck arrival distribution")
ax.set_xticks([i for i in range(1, 8)]) # every day
ax.set_xticklabels(
["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]
)
plt.xlabel("Week day")
plt.ylabel("Share (as percentage overall)")
plt.show()
A second default distribution exists. You need to provide the corresponding keyword when creating the new SQLite database to use it.
Note
- DEFAULT_TRUCK_ARRIVAL_DISTRIBUTION_WITH_SLOT_BOOKING
This distribution assigns each hour of the week to the fraction of trucks that arrive during this time window. The distribution is based on an interview with the operational staff of a container terminal. To ensure confidentiality, the distribution has been slightly distorted.
Thee pattern looks slightly different - the peaks during lunch time are smoothened.
[22]:
database_chooser.close_current_connection()
database_chooser.create_new_sqlite_database(":memory:", assume_tas=True)
truck_arrival_distribution = (
truck_arrival_distribution_manager.get_truck_arrival_distribution()
)
hour_in_week, fraction = zip(*list(sorted(truck_arrival_distribution.items())))
weekday_in_week = [x / 24 + 1 for x in hour_in_week]
percentage = [x * 100 for x in fraction]
fig, ax = plt.subplots(figsize=(15, 3))
plt.plot(weekday_in_week, percentage)
plt.xlim([1, 7]) # plot from Monday to Sunday
ax.xaxis.grid(True, which="minor", color="lightgray") # every hour
ax.xaxis.grid(True, which="major", color="k") # every day
ax.xaxis.set_minor_locator(matplotlib.ticker.MultipleLocator(1 / 24)) # every hour
plt.title("Truck arrival distribution")
ax.set_xticks([i for i in range(1, 8)]) # every day
ax.set_xticklabels(
["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]
)
plt.xlabel("Week day")
plt.ylabel("Share (as percentage overall)")
plt.show()
Closing current database connection :memory:.
Opening file :memory:
journal_mode: memory
cache_size: -32768
page_size: 4096
foreign_keys: 1
Creating new database at :memory:
Creating all tables...
Seed with default values...
Number entries in table 'DeepSeaVessel': 0
Number entries in table 'Feeder': 0
Number entries in table 'Barge': 0
Number entries in table 'Train': 0
Number entries in table 'Truck': 0
Number entries in table 'Container': 0
Randomly set seed 1739739392686485818 for 'SqliteDatabaseConnection'
The truck arrival distributions can only be overwritten for the whole week at once. Each key represents the hour in the week and each value represents the probability of a truck to arrive between that hour and the start of the next time slot.
[23]:
truck_arrival_distribution_manager.set_truck_arrival_distribution(
{
0: 0.006944444444444444,
1: 0.006944444444444444,
2: 0.006944444444444444,
3: 0.006944444444444444,
4: 0.006944444444444444,
5: 0.006944444444444444,
6: 0.006944444444444444,
7: 0.006944444444444444,
8: 0.006944444444444444,
9: 0.006944444444444444,
10: 0.006944444444444444,
11: 0.006944444444444444,
12: 0.006944444444444444,
13: 0.006944444444444444,
14: 0.006944444444444444,
15: 0.006944444444444444,
16: 0.006944444444444444,
17: 0.006944444444444444,
18: 0.006944444444444444,
19: 0.006944444444444444,
20: 0.006944444444444444,
21: 0.006944444444444444,
22: 0.006944444444444444,
23: 0.006944444444444444,
24: 0.006944444444444444,
25: 0.006944444444444444,
26: 0.006944444444444444,
27: 0.006944444444444444,
28: 0.006944444444444444,
29: 0.006944444444444444,
30: 0.006944444444444444,
31: 0.006944444444444444,
32: 0.006944444444444444,
33: 0.006944444444444444,
34: 0.006944444444444444,
35: 0.006944444444444444,
36: 0.006944444444444444,
37: 0.006944444444444444,
38: 0.006944444444444444,
39: 0.006944444444444444,
40: 0.006944444444444444,
41: 0.006944444444444444,
42: 0.006944444444444444,
43: 0.006944444444444444,
44: 0.006944444444444444,
45: 0.006944444444444444,
46: 0.006944444444444444,
47: 0.006944444444444444,
48: 0.006944444444444444,
49: 0.006944444444444444,
50: 0.006944444444444444,
51: 0.006944444444444444,
52: 0.006944444444444444,
53: 0.006944444444444444,
54: 0.006944444444444444,
55: 0.006944444444444444,
56: 0.006944444444444444,
57: 0.006944444444444444,
58: 0.006944444444444444,
59: 0.006944444444444444,
60: 0.006944444444444444,
61: 0.006944444444444444,
62: 0.006944444444444444,
63: 0.006944444444444444,
64: 0.006944444444444444,
65: 0.006944444444444444,
66: 0.006944444444444444,
67: 0.006944444444444444,
68: 0.006944444444444444,
69: 0.006944444444444444,
70: 0.006944444444444444,
71: 0.006944444444444444,
72: 0.006944444444444444,
73: 0.006944444444444444,
74: 0.006944444444444444,
75: 0.006944444444444444,
76: 0.006944444444444444,
77: 0.006944444444444444,
78: 0.006944444444444444,
79: 0.006944444444444444,
80: 0.006944444444444444,
81: 0.006944444444444444,
82: 0.006944444444444444,
83: 0.006944444444444444,
84: 0.006944444444444444,
85: 0.006944444444444444,
86: 0.006944444444444444,
87: 0.006944444444444444,
88: 0.006944444444444444,
89: 0.006944444444444444,
90: 0.006944444444444444,
91: 0.006944444444444444,
92: 0.006944444444444444,
93: 0.006944444444444444,
94: 0.006944444444444444,
95: 0.006944444444444444,
96: 0.006944444444444444,
97: 0.006944444444444444,
98: 0.006944444444444444,
99: 0.006944444444444444,
100: 0.006944444444444444,
101: 0.006944444444444444,
102: 0.006944444444444444,
103: 0.006944444444444444,
104: 0.006944444444444444,
105: 0.006944444444444444,
106: 0.006944444444444444,
107: 0.006944444444444444,
108: 0.006944444444444444,
109: 0.006944444444444444,
110: 0.006944444444444444,
111: 0.006944444444444444,
112: 0.006944444444444444,
113: 0.006944444444444444,
114: 0.006944444444444444,
115: 0.006944444444444444,
116: 0.006944444444444444,
117: 0.006944444444444444,
118: 0.006944444444444444,
119: 0.006944444444444444,
120: 0.006944444444444444,
121: 0.006944444444444444,
122: 0.006944444444444444,
123: 0.006944444444444444,
124: 0.006944444444444444,
125: 0.006944444444444444,
126: 0.006944444444444444,
127: 0.006944444444444444,
128: 0.006944444444444444,
129: 0.006944444444444444,
130: 0.006944444444444444,
131: 0.006944444444444444,
132: 0.006944444444444444,
133: 0.006944444444444444,
134: 0.006944444444444444,
135: 0.006944444444444444,
136: 0.006944444444444444,
137: 0.006944444444444444,
138: 0.006944444444444444,
139: 0.006944444444444444,
140: 0.006944444444444444,
141: 0.006944444444444444,
142: 0.006944444444444444,
143: 0.006944444444444444,
144: 0.0,
145: 0.0,
146: 0.0,
147: 0.0,
148: 0.0,
149: 0.0,
150: 0.0,
151: 0.0,
152: 0.0,
153: 0.0,
154: 0.0,
155: 0.0,
156: 0.0,
157: 0.0,
158: 0.0,
159: 0.0,
160: 0.0,
161: 0.0,
162: 0.0,
163: 0.0,
164: 0.0,
165: 0.0,
166: 0.0,
167: 0.0,
}
)
More information on setting and getting the distribution can be found at
TruckArrivalDistributionManager
.
Container Dwell Time Distribution
The container dwell time distribution is used in two cases. First, the truck arrivals are made more likely whenever the container dwell time is close to its expected duration. Second, a vehicle that arrives at the terminal close to the expected container dwell time is more likely to pick up the container.
Note
- DEFAULT_MINIMUM_DWELL_TIME_OF_IMPORT_CONTAINERS_IN_HOURS = 3
The minimum dwell time of import containers is the earliest time after the discharging and loading process has started that a vehicle arrives from the hinterland and tries to pick up the container. In practice, this is often determined by the IT system of the terminal operators which releases a container for being picked up once the container is on the terminal (it has been successfully discharged). The actual earliest feasible point is determined in the subsequent model which consumes the generated data because here no sequence of discharge is determined, i.e., the container might be still on the vessel when the truck arrives. Thus, this value must be checked for when using the synthetic data in, e.g., a simulation model or mathematical model.
- DEFAULT_MINIMUM_DWELL_TIME_OF_EXPORT_CONTAINERS_IN_HOURS = 12
The minimum dwell time of export containers is the minimum time a container must reside on the terminal before the vessel discharging and loading process starts. This time is needed for, e.g., finalizing the stowage planning and avoiding that a container which is designated for a vessel arrives shortly before vessel departure. If the vehicle that delivers this container is waiting in a queue, actually the container might miss the vessel. This cut-off is typically defined by the shipping company. Here, as a simplification one cut-off period is used for all cases. Both the time interval and the logic are inspired by expert interviews.
- DEFAULT_MINIMUM_DWELL_TIME_OF_TRANSSHIPMENT_CONTAINERS_IN_HOURS = 3
The minimum dwell time for transshipment is the minimum time difference of arrival between two vessels. This means that one vessel can request a container from another vessel if and only if the previous vessel has arrived these k hours before the first one. For short transshipment dwell times, it might result in a direct transfer from one vessel to the other without any storage if the user decides to support such activities in their model (such as a simulation model or optimization model).
- DEFAULT_AVERAGE_CONTAINER_DWELL_TIMES
The container dwell time distribution is based on [A container terminal operator in the German Bight, 2021]. The average container dwell times are taken from a report and reflect the reality at a given time for a specific container terminal operator.
The default values can be overwritten with the help of ContainerDwellTimeDistributionManager.set_container_dwell_time_distribution()
.
[24]:
container_dwell_time_distribution_manager = (
conflowgen.ContainerDwellTimeDistributionManager()
)
distributions = (
container_dwell_time_distribution_manager.get_container_dwell_time_distribution()
)
number_days_in_hours = 30 * 24
x = np.linspace(0, number_days_in_hours, number_days_in_hours)
locator = matplotlib.ticker.MultipleLocator(24)
for storage_requirement in [
conflowgen.StorageRequirement.standard,
conflowgen.StorageRequirement.empty,
]:
display(Markdown(f"### {str(storage_requirement).capitalize()} container"))
for inbound_vehicle in conflowgen.ModeOfTransport:
display(
Markdown(
f"#### {str(inbound_vehicle).replace('_', ' ').capitalize()} delivering a "
f"{str(storage_requirement)} container"
)
)
for outbound_vehicle in conflowgen.ModeOfTransport:
distribution = distributions[inbound_vehicle][outbound_vehicle][
storage_requirement
]
plt.figure(figsize=(20, 5))
plt.axvline(distribution.minimum, color="dimgray")
if distribution.maximum < number_days_in_hours:
plt.axvline(distribution.maximum, color="dimgray")
x_in_range = x[
np.where((distribution.minimum < x) & (x < distribution.maximum))
]
plt.plot(
x_in_range,
distribution.get_probabilities(x_in_range),
color="gray",
lw=5,
alpha=0.6,
)
plt.xlabel("Container Dwell Time (h)")
plt.gca().xaxis.set_major_locator(locator)
title = (
f'Container dwell time from {str(inbound_vehicle).replace("_", " ")} to {str(outbound_vehicle).replace("_", " ")} '
f'for a(n) {str(storage_requirement).replace("_", " ")} container\n'
f"avg={distribution.average:.1f}h={distribution.average / 24:.1f}d, var={distribution.variance:.1f}h², "
f"{distribution.minimum:.1f}h ≤ x ≤ {distribution.maximum:.1f}h or in other terms "
f"{distribution.minimum / 24:.1f}d ≤ x ≤ {distribution.maximum / 24:.1f}d"
)
plt.title(title)
plt.show()
Standard container
Truck delivering a standard container
Train delivering a standard container
Feeder delivering a standard container
Deep sea vessel delivering a standard container
Barge delivering a standard container
Empty container
Truck delivering a empty container
Train delivering a empty container
Feeder delivering a empty container
Deep sea vessel delivering a empty container
Barge delivering a empty container
More information on setting and getting the distribution can be found at
ContainerDwellTimeDistributionManager
.
Default Values
In addition to the input distributions, also some default values are defined. All of them are currently some kind of minimum or maximum value. Thus, they directly influence other distributions.
Note
- DEFAULT_TRANSPORTATION_BUFFER = 0.2
The export buffer describes how much more a vehicle typically takes from the terminal compared to the amount of containers in TEU which it delivers. The value
.2
means that 20% more than the moved capacity (which determines the containers that are delivered to the terminal) is available for exporting containers as long as the maximum capacity of the vehicle is not exceeded. This concept has been first proposed by Hartmann [2004].
The default values can be overwritten with the help of ContainerFlowGenerationManager.set_properties()
.
All default values are optional. They are only overwritten if provided. The parameters start_date
and end_date
are obligatory though and no default values are provided.
[25]:
container_flow_generation_manager = conflowgen.ContainerFlowGenerationManager()
container_flow_generation_manager.set_properties(
start_date=datetime.date(2021, 1, 15),
end_date=datetime.date(2021, 1, 31),
transportation_buffer=0.4,
)
container_flow_generation_manager.get_properties()
Randomly set seed 1739739406060041509 for 'TruckForImportContainersManager'
Randomly set seed 1739739406061895621 for 'TruckForExportContainersManager'
Randomly set seed 1739739406063590689 for 'ContainerFactory'
Randomly set seed 1739739406065264097 for 'LargeScheduledVehicleForOnwardTransportationManager'
Randomly set seed 1739739406073796633 for 'AllocateSpaceForContainersDeliveredByTruckService'
Replace seed 1739739406063590689 with 1739739406075224329 for 'ContainerFactory' for the new round.
Randomly set seed 1739739406076955929 for 'AssignDestinationToContainerService'
Loading destination distribution...
[25]:
{'name': None,
'start_date': datetime.date(2021, 1, 15),
'end_date': datetime.date(2021, 1, 31),
'transportation_buffer': 0.4,
'ramp_up_period': 0.0,
'ramp_down_period': 0.0,
'conflowgen_version': '3.0.0'}
More information on setting and getting the values can be found at
ContainerFlowGenerationManager
.