Skip to content

Commit b3710ee

Browse files
author
Martin Vo
committed
Initial commit
1 parent aa304e3 commit b3710ee

12 files changed

+1425
-1
lines changed

.gitignore

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
.simulation
2+
__pycache__
3+
*pyc
4+
.DS_Store
5+
.ipynb_checkpoints

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
# transtycoon
1+
# Transtycoon
22
Optimization for transports logistic -warehouses location, transport types, waiting policy etc.

notebooks/optimal_warehouse_position.ipynb

+164
Large diffs are not rendered by default.

notebooks/transport_logistic-intro.ipynb

+705
Large diffs are not rendered by default.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
{
2+
"cells": [
3+
{
4+
"cell_type": "code",
5+
"execution_count": 1,
6+
"metadata": {},
7+
"outputs": [],
8+
"source": [
9+
"import sys\n",
10+
"\n",
11+
"sys.path.insert(0, \"..\")"
12+
]
13+
},
14+
{
15+
"cell_type": "code",
16+
"execution_count": 2,
17+
"metadata": {},
18+
"outputs": [],
19+
"source": [
20+
"from transtycoon.entities import Field, Warehouse, Position, Transporter\n",
21+
"from transtycoon.simulation import OneWayGathering, Simulation\n",
22+
"from transtycoon.report import plot_objects"
23+
]
24+
},
25+
{
26+
"cell_type": "code",
27+
"execution_count": 3,
28+
"metadata": {},
29+
"outputs": [],
30+
"source": [
31+
"log = False\n",
32+
"\n",
33+
"field1 = Field(Position(10, -10), 70, name=\"Field1\")\n",
34+
"field2 = Field(Position(-5, 20), 70, name=\"Field2\")\n",
35+
"inter_warehouse = Warehouse(Position(-5, 10))\n",
36+
"final_warehouse = Warehouse(Position(-20, 0))\n",
37+
"transporter1 = Transporter(Position(0, 0), speed=1, max_capacity=30, name=\"Field truck 1\", log=log)\n",
38+
"transporter2 = Transporter(Position(0, 0), speed=1, max_capacity=30, name=\"Field truck 2\", log=log)\n",
39+
"transporter3 = Transporter(Position(0, 0), speed=1, max_capacity=50, name=\"Warehouse truck 1\", log=log)"
40+
]
41+
},
42+
{
43+
"cell_type": "code",
44+
"execution_count": 4,
45+
"metadata": {},
46+
"outputs": [],
47+
"source": [
48+
"field1_to_warehouse_task = OneWayGathering(gather_from=field1, deliver_to=inter_warehouse)\n",
49+
"field2_to_warehouse_task = OneWayGathering(gather_from=field2, deliver_to=inter_warehouse)\n",
50+
"warehouse_to_warehouse = OneWayGathering(gather_from=inter_warehouse,\n",
51+
" deliver_to=final_warehouse,\n",
52+
" wait_for=[transporter1, transporter2],\n",
53+
" min_amount=40)\n",
54+
"\n",
55+
"transporter1.assign_task_queue([field1_to_warehouse_task, field2_to_warehouse_task])\n",
56+
"transporter2.assign_task_queue([field2_to_warehouse_task, field1_to_warehouse_task])\n",
57+
"transporter3.assign_task_queue([warehouse_to_warehouse])"
58+
]
59+
},
60+
{
61+
"cell_type": "code",
62+
"execution_count": 5,
63+
"metadata": {},
64+
"outputs": [],
65+
"source": [
66+
"animate_objects = {\"warehouses\": [inter_warehouse, final_warehouse],\n",
67+
" \"transports\": [transporter1, transporter2, transporter3],\n",
68+
" \"fields\": [field1, field2]\n",
69+
" }\n",
70+
"simulation = Simulation(transports=[transporter1, transporter2, transporter3], captured_objects=animate_objects)"
71+
]
72+
},
73+
{
74+
"cell_type": "code",
75+
"execution_count": 6,
76+
"metadata": {},
77+
"outputs": [
78+
{
79+
"name": "stderr",
80+
"output_type": "stream",
81+
"text": [
82+
"/usr/local/lib/python3.6/site-packages/matplotlib/pyplot.py:524: RuntimeWarning: More than 20 figures have been opened. Figures created through the pyplot interface (`matplotlib.pyplot.figure`) are retained until explicitly closed and may consume too much memory. (To control this warning, see the rcParam `figure.max_open_warning`).\n",
83+
" max_open_warning, RuntimeWarning)\n"
84+
]
85+
},
86+
{
87+
"name": "stdout",
88+
"output_type": "stream",
89+
"text": [
90+
"Video created at .simulation/12c3e61e21784240ba46d6b303bce04d/animation.avi\n"
91+
]
92+
},
93+
{
94+
"data": {
95+
"text/plain": [
96+
"{'simulation_steps': 149,\n",
97+
" 'total_work_steps': 395,\n",
98+
" 'transport_steps': [119, 128, 148]}"
99+
]
100+
},
101+
"execution_count": 6,
102+
"metadata": {},
103+
"output_type": "execute_result"
104+
}
105+
],
106+
"source": [
107+
"simulation.make_turns()"
108+
]
109+
}
110+
],
111+
"metadata": {
112+
"kernelspec": {
113+
"display_name": "Python 3",
114+
"language": "python",
115+
"name": "python3"
116+
},
117+
"language_info": {
118+
"codemirror_mode": {
119+
"name": "ipython",
120+
"version": 3
121+
},
122+
"file_extension": ".py",
123+
"mimetype": "text/x-python",
124+
"name": "python",
125+
"nbconvert_exporter": "python",
126+
"pygments_lexer": "ipython3",
127+
"version": "3.6.5"
128+
}
129+
},
130+
"nbformat": 4,
131+
"nbformat_minor": 2
132+
}

samples/animation_sample1.avi

295 KB
Binary file not shown.
Loading

transtycoon/__init__.py

Whitespace-only changes.

transtycoon/entities.py

+178
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
from mock import Mock
2+
3+
from transtycoon import exceptions
4+
from transtycoon.report import get_logger
5+
6+
7+
class Field:
8+
9+
def __init__(self, position, resources, name=None):
10+
self.position = position
11+
self.resources = resources
12+
self.name = name or "Field"
13+
14+
def __repr__(self):
15+
return "{} (resources = {} position = {}".format(self.name, self.resources, self.position)
16+
17+
18+
class Warehouse:
19+
20+
# Capacity = None means unlimited capacity
21+
def __init__(self, position, capacity=None, name=None):
22+
self.position = position
23+
self.capacity = capacity
24+
self.name = name or "Warehouse"
25+
26+
self.resources = 0
27+
28+
def __repr__(self):
29+
return "{} (resources = {} position = {}".format(self.name, self.resources, self.position)
30+
31+
def has_free_space(self):
32+
if self.capacity is None:
33+
return True
34+
35+
if self.get_free_capacity() > 0:
36+
return True
37+
38+
return False
39+
40+
def get_free_capacity(self):
41+
return self.capacity - self.resources if self.capacity is not None else None
42+
43+
44+
class Position:
45+
EPS = 1e-3
46+
47+
def __init__(self, x, y):
48+
self.x = x
49+
self.y = y
50+
51+
def get_distance(self, other):
52+
# Doesnt have to euclidian distance, but rather distance dependent on the real routes
53+
return ((self.x - other.x) ** 2 + (self.y - other.y) ** 2) ** 0.5
54+
55+
def __eq__(self, other):
56+
if isinstance(other, Position):
57+
return abs(self.x - other.x) < self.EPS and abs(self.y - other.y) < self.EPS
58+
return False
59+
60+
def __repr__(self):
61+
return "[{0:.1f}, {0:.1f}]".format(self.x, self.y)
62+
63+
64+
class Transporter:
65+
66+
def __init__(self, position, max_capacity=10, speed=1, name=None, log=True):
67+
self.position = position
68+
self.speed = speed
69+
self.max_capacity = max_capacity
70+
self.name = name or "Truck"
71+
72+
self.loaded = 0
73+
self.distance_travelled = 0
74+
self.tasks = []
75+
self.historical_tasks = []
76+
self.work_steps = 0
77+
self.transported = 0
78+
79+
self.log = get_logger().bind(name=self.name, position=self.position) if log else Mock()
80+
81+
def __repr__(self):
82+
return "[{}] position: {} loaded: {}/{}".format(self.name or "Unknown", self.position, self.loaded,
83+
self.max_capacity)
84+
85+
def can_load(self, field, min_amount=0):
86+
if self.position != field.position:
87+
return exceptions.NotInPlaceException()
88+
89+
elif not field.resources > 0:
90+
return exceptions.NothingToLoadException()
91+
92+
elif self.max_capacity - self.loaded < min_amount:
93+
return exceptions.TruckIsAlredyFullException()
94+
95+
elif not field.resources > min_amount:
96+
return exceptions.NotEnoughResources()
97+
98+
return True
99+
100+
def load(self, field):
101+
if self.can_load(field) is not True:
102+
raise self.can_load(field)
103+
104+
to_load = min(field.resources, self.max_capacity - self.loaded)
105+
106+
field.resources -= to_load
107+
self.loaded += to_load
108+
return to_load
109+
110+
def can_unload(self, warehouse):
111+
if self.position != warehouse.position:
112+
return exceptions.NotInPlaceException()
113+
114+
elif not warehouse.has_free_space():
115+
return exceptions.StorageIsFullException()
116+
return True
117+
118+
def unload(self, warehouse):
119+
if self.can_unload(warehouse) is not True:
120+
raise self.can_unload(warehouse)
121+
122+
cap = warehouse.get_free_capacity()
123+
to_unload = min(self.loaded, cap) if cap is not None else self.loaded
124+
125+
self.loaded -= to_unload
126+
warehouse.resources += to_unload
127+
128+
self.transported += to_unload
129+
return to_unload
130+
131+
def is_on_position(self, position):
132+
return self.position == position
133+
134+
def get_steps_to_postion(self, position):
135+
return self.position.get_distance(position) / self.speed
136+
137+
def go_to_position(self, position):
138+
assert not self.is_on_position(position)
139+
140+
steps_needed = self.get_steps_to_postion(position)
141+
142+
if steps_needed < 1:
143+
self.distance_travelled += self.speed * steps_needed
144+
145+
self.position.x = position.x
146+
self.position.y = position.y
147+
return 0
148+
149+
else:
150+
self.distance_travelled += self.speed
151+
152+
self.position.x += (position.x - self.position.x) / steps_needed
153+
self.position.y += (position.y - self.position.y) / steps_needed
154+
return steps_needed - 1
155+
156+
def assign_task_queue(self, tasks):
157+
self.tasks = tasks
158+
159+
def work(self):
160+
if self.is_working():
161+
162+
self.work_steps += 1
163+
if not self.tasks[0].work(self):
164+
while len(self.tasks):
165+
if self.tasks[0].completed:
166+
self.historical_tasks.append(self.tasks[0])
167+
self.tasks.pop(0)
168+
self.log.info("Task is finished", finished=len(self.historical_tasks), remaining=len(self.tasks))
169+
else:
170+
break
171+
172+
if len(self.tasks):
173+
return True
174+
self.log.info("All tasks is finished")
175+
return False
176+
177+
def is_working(self):
178+
return len(self.tasks) > 0 or self.loaded > 0

transtycoon/exceptions.py

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
2+
3+
class NotInPlaceException(Exception):
4+
pass
5+
6+
7+
class NothingToLoadException(Exception):
8+
pass
9+
10+
11+
class TruckIsAlredyFullException(Exception):
12+
pass
13+
14+
15+
class StorageIsFullException(Exception):
16+
pass
17+
18+
19+
class RoutineFinished(StopIteration):
20+
pass
21+
22+
23+
class NotEnoughResources(Exception):
24+
pass

transtycoon/report.py

+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import structlog
2+
from matplotlib import pyplot as plt
3+
4+
5+
def renderer(logger, name, event_dict, **kwargs):
6+
7+
t = "Time {}".format(event_dict.pop("time")) if "time" in event_dict else ""
8+
9+
event = event_dict.pop("event") if "event" in event_dict else ""
10+
name = ", {}".format(event_dict.pop("name")) if "name" in event_dict else ""
11+
12+
return "[{}{}] {} : {}".format(t, name, event,
13+
" ".join(["{}={}".format(key, value) for key, value in event_dict.items()]))
14+
15+
16+
def get_logger():
17+
structlog.configure(processors=[renderer])
18+
return structlog.get_logger()
19+
20+
21+
def plot_objects(warehouses=None, fields=None, transports=None, ax=None, scale=20, min_size=20, shift=None):
22+
fig = None
23+
if not ax:
24+
fig, ax = plt.subplots(figsize=(13, 8))
25+
26+
if shift is None:
27+
shift = [0, 0]
28+
29+
for field in fields or []:
30+
ax.scatter(field.position.x + shift[0], field.position.y + shift[1], s=field.resources * scale + min_size,
31+
marker=r'$\clubsuit$')
32+
ax.annotate(field.name, (field.position.x + shift[0], field.position.y + shift[1]))
33+
34+
for warehouse in warehouses or []:
35+
ax.scatter(warehouse.position.x + shift[0], warehouse.position.y + shift[1],
36+
s=warehouse.resources * scale + min_size)
37+
ax.annotate(warehouse.name, (warehouse.position.x + shift[0], warehouse.position.y + shift[1]))
38+
39+
for transport in transports or []:
40+
ax.scatter(transport.position.x + shift[0], transport.position.y + shift[1],
41+
s=transport.loaded * scale + min_size, marker='^')
42+
ax.scatter(transport.position.x + shift[0], transport.position.y + shift[1],
43+
s=transport.max_capacity * scale + min_size, marker='^',
44+
alpha=0.5)
45+
46+
ax.annotate(transport.name, (transport.position.x, transport.position.y))
47+
48+
return fig, ax

0 commit comments

Comments
 (0)