-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathSimulate.py
More file actions
253 lines (206 loc) · 11.1 KB
/
Copy pathSimulate.py
File metadata and controls
253 lines (206 loc) · 11.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
from Transportation import Ferry, Speedboat
from Functions import truncated_normal_rvs
from Agent import Commuter
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from mesa.datacollection import DataCollector
class Simulation:
"""
Class representing the simulation of commuters choosing transportation modes over a number of days.
Attributes:
num_commuters (int): Number of commuters in the simulation.
num_days (int): Number of days the simulation will run.
islands (list): List of island names.
transport_modes (list): List of available transport modes.
transport_restrictions (dict): Restrictions on transportation between islands.
commuters (list): List of commuter agents.
datacollector (DataCollector): Object to collect data during the simulation.
initial_ferry_score (float): Initial score representing ferry conditions.
"""
def __init__(self, num_commuters=1000, num_days=80, islands=['A', 'B'], capacity=1000, ferry_base_price=6, ferry_base_time=40,
speedboat_base_price=6, speedboat_base_time=10, transport_restrictions=None):
"""
Initialises the Simulation instance with the given parameters.
Args:
num_commuters (int): Number of commuters in the simulation.
num_days (int): Number of days the simulation will run.
islands (list): List of island names.
capacity (int): Capacity of the transportation modes.
ferry_base_price (float): Base price for ferry transportation.
ferry_base_time (float): Base time for ferry transportation.
speedboat_base_price (float): Base price for speedboat transportation.
speedboat_base_time (float): Base time for speedboat transportation.
transport_restrictions (dict, optional): Restrictions on transportation between islands.
"""
self.num_commuters = num_commuters
self.num_days = num_days
self.islands = islands
self.transport_modes = [] # Initialise transport_modes as an empty list
self.transport_restrictions = transport_restrictions if transport_restrictions else {}
# Create transportation modes based on provided parameters
self.create_transport_modes(capacity, ferry_base_price, ferry_base_time, speedboat_base_price, speedboat_base_time)
# Initialise commuters with random start locations
self.commuters = [Commuter(i, np.random.choice(self.islands), self.transport_modes) for i in range(num_commuters)]
# Initialise DataCollector to gather data during the simulation
self.datacollector = DataCollector(
model_reporters=self.create_data_collectors()
)
# Calculate the initial ferry score that is used to normalise the time points in Transportation
self.initial_ferry_score = 1+9*((ferry_base_time - speedboat_base_time) / (ferry_base_time + speedboat_base_time))
def create_transport_modes(self, capacity, ferry_base_price, ferry_base_time, speedboat_base_price, speedboat_base_time):
"""
Create transportation modes (ferry and speedboat) between islands.
Args:
islands (list): List of island names.
capacity (int): Capacity of the transportation modes.
ferry_base_price (float): Base price for ferry transportation.
ferry_base_time (float): Base time for ferry transportation.
speedboat_base_price (float): Base price for speedboat transportation.
speedboat_base_time (float): Base time for speedboat transportation.
"""
for i, island_start in enumerate(self.islands):
for island_end in self.islands[i+1:]:
# Get allowed modes between the pair of islands
allowed_modes = self.transport_restrictions.get((island_start, island_end), {'Ferry', 'Speedboat'})
# Create ferry transport modes if allowed
if 'Ferry' in allowed_modes:
self.transport_modes.append(Ferry(island_start, island_end, capacity, ferry_base_price, ferry_base_time))
self.transport_modes.append(Ferry(island_end, island_start, capacity, ferry_base_price, ferry_base_time))
# Create speedboat transport modes if allowed
if 'Speedboat' in allowed_modes:
self.transport_modes.append(Speedboat(island_start, island_end, capacity, speedboat_base_price, speedboat_base_time))
self.transport_modes.append(Speedboat(island_end, island_start, capacity, speedboat_base_price, speedboat_base_time))
def create_data_collectors(self):
"""
Create a dictionary of data collectors for each transport mode.
Returns:
dict: A dictionary with data collectors for number of users, time, density, and price for each transport mode.
"""
data_collectors = {}
for mode in self.transport_modes:
# Create a unique key for each mode based on its type and route
mode_key = f'{type(mode).__name__}_{mode.start_location}{mode.end_location}'
# Define data collection functions for users, time, density, and price
data_collectors[f'{mode_key}_users'] = (lambda m, mode=mode: mode.number_of_mode_users)
data_collectors[f'{mode_key}_time'] = (lambda m, mode=mode: mode.time)
data_collectors[f'{mode_key}_density'] = (lambda m, mode=mode: mode.density)
data_collectors[f'{mode_key}_price'] = (lambda m, mode=mode: mode.price)
return data_collectors
def run(self):
"""
Run the simulation for the specified number of days.
"""
for day in range(self.num_days):
daily_choices = {mode: 0 for mode in self.transport_modes}
commuter_choices = {}
# Each commuter chooses a transportation mode
for commuter in self.commuters:
chosen_mode = commuter.choose_transportation()
daily_choices[chosen_mode] += 1
commuter_choices[commuter] = chosen_mode
# Update the conditions for each mode based on the number of users
for mode, num_users in daily_choices.items():
mode.update_conditions(num_users, self.num_commuters, self.initial_ferry_score)
# Update the memory of each commuter with the chosen mode
for commuter, chosen_mode in commuter_choices.items():
commuter.update_memory(chosen_mode)
# Collect data at the end of each day
self.datacollector.collect(self)
def return_percentage_ferry_users(self):
"""
Return the percentage of commuters using the ferry.
"""
data = self.datacollector.get_model_vars_dataframe()
num_Ferry_users = 0
# Calculate the total number of ferry users
for metric in data:
if metric.startswith('Ferry'):
num_Ferry_users += data[metric]
# Calculate the percentage of ferry users
percentage_Ferry_users = num_Ferry_users / self.num_commuters
return percentage_Ferry_users
def return_percentage_speedboat_users(self):
"""
Return the percentage of commuters using the speedboat.
"""
data = self.datacollector.get_model_vars_dataframe()
num_Speedboat_users = 0
# Calculate the total number of ferry users
for metric in data:
if metric.startswith('Speedboat'):
num_Speedboat_users += data[metric]
# Calculate the percentage of ferry users
percentage_Speedboat_users = num_Speedboat_users / self.num_commuters
return percentage_Speedboat_users
def return_equilibrium_value(self):
results = self.return_percentage_ferry_users()
equilibrium_value = np.mean(results[-10:])
return equilibrium_value
def plot_specific_results(self, attribute='users', metrics=None):
"""
Plot the results for specific attributes and metrics.
Args:
metrics (list): List of metrics to plot (e.g. Ferry_AB).
attributes (str): Attributes to plot (e.g. density, time, price, users)
"""
data = self.datacollector.get_model_vars_dataframe()
plt.figure(figsize=(15, 8))
# Plot each metric over the simulation days
if metrics:
for metric in metrics:
plt.plot(data.index, data[f'{metric}_{attribute}'], label=metric.replace('_', ' '))
else:
for metric in data:
if metric.endswith(attribute):
plt.plot(data.index, data[metric], label=metric.replace('_', ' ').replace('users', ''))
plt.xlabel('Time [a.u.]')
plt.ylabel(f'{attribute}'.title())
plt.legend()
plt.title(f'{attribute} per Transportation Mode'.title())
plt.tight_layout()
plt.xticks(np.arange(min(data.index), max(data.index) + 1, 5))
plt.grid(True)
plt.show()
def collect_specific_results(self, attribute='users'):
"""
Collect the results for specific attributes and metrics separately for ferries and speedboats.
Args:
attribute (str): Attribute to collect (e.g. density, time, price, users).
Returns:
tuple: Two lists containing the collected data for specified attributes and metrics for ferries and speedboats.
"""
data = self.datacollector.get_model_vars_dataframe()
collected_ferry_results = []
collected_speedboat_results = []
# Collect each ferry metric over the simulation days
for metric in data:
if metric.startswith('Ferry') and metric.endswith(attribute):
collected_ferry_results.append(data[metric].tolist())
# Collect each speedboat metric over the simulation days
for metric in data:
if metric.startswith('Speedboat') and metric.endswith(attribute):
collected_speedboat_results.append(data[metric].tolist())
return collected_ferry_results, collected_speedboat_results
def plot_percentage_ferry_users(self):
"""
Plot the percentage of commuters using the ferry.
"""
data = self.datacollector.get_model_vars_dataframe()
num_Ferry_users = 0
# Calculate the total number of ferry users
for metric in data:
if metric.startswith('Ferry'):
num_Ferry_users += data[metric]
# Calculate the percentage of ferry users
percentage_Ferry_users = num_Ferry_users / self.num_commuters
plt.figure(figsize=(20, 8))
plt.plot(data.index, percentage_Ferry_users)
plt.xlabel('Day')
plt.ylabel('Percentage of Commuters Using the Ferry')
plt.legend()
plt.title('Percentage of Ferry Users')
plt.tight_layout()
plt.xticks(np.arange(min(data.index), max(data.index) + 1, 5))
plt.grid(True)
plt.show()