“Parking” is a Python library for simulating the parking of vehicles along a single block face. After checking out the repository you can run this code as
python parking.py
Simulation parameters are stored in json files. There are some samples in the json/
folder.
import sys, os
import json
import numpy as np
import matplotlib.pyplot as plt
from plotting import *
from tqdm import tqdm
Park a single car following a given strategy
, an existing set of parked_car_locs
, an index
which represents between which cars we should park, and the bounds of the parking space min_loc
and max_loc
. Returns the updated parked_car_locs
.
def park_the_car(strategy, parked_car_locs, test_loc, index, min_loc, max_loc):
if strategy == "test_loc":
parked_car_locs.insert(index, test_loc) # just for testing, we are going to put a car right at `test_loc`.
elif strategy == "random":
parked_car_locs.insert(index, np.random.rand() * (max_loc - min_loc) + min_loc) # put it somewhere random between `min_loc` and `max_loc`
elif strategy == "middle":
parked_car_locs.insert(index, (max_loc + min_loc) / 2.0) # put it right in the middle
elif strategy == "one_side":
parked_car_locs.insert(index, min_loc) # put it at `min_loc` always
elif strategy == "half_half":
if np.random.rand() < 0.5: # half of the time
parked_car_locs.insert(index, min_loc) # put it at `min_loc`
else:
parked_car_locs.insert(index, max_loc) # put it at `max_loc`
else:
sys.exit(f'Strategy "{strategy}" is undefined') # you made a typo
return parked_car_locs
Pull another random vehicle length out of the vehicle length distribution defined by mean_car_length
, sigma_car_length
and optionally if there are motorcycles, motorcycle_ratio
and motorcycle_length
def generate_new_car_length(params):
return np.random.rand()*(max_car_length - min_car_length) + min_car_length # HALF CAR LENGTH
if "motorcycles" in params: # if we are simulating motorcycles
if np.random.rand() < params["motorcycle_ratio"]: # if this vehicle is going to be a motorcycle (stochastic)
return params["motorcycle_length"] # this is a motorcycle
else:
return np.random.normal(loc=params["mean_car_length"], scale=params["sigma_car_length"]) # this is a car
else:
return np.random.normal(loc=params["mean_car_length"], scale=params["sigma_car_length"]) # this is a car
For a given set of vehicle centres parked_car_locs
, and their respective parked_car_lengths
, block face length L
and some min_clearance
between the vehicles, return a list of all of the spaces between the vehicles.
def get_spot_lengths(parked_car_locs, parked_car_lengths, L, min_clearance):
if len(parked_car_locs) == 0: # if no cars are parked yet
spots = [L - 2 * min_clearance] # return the full block face less the clearances at the ends
elif len(parked_car_locs) == 1: # if there is just one car
spots = [
parked_car_locs[0] - parked_car_lengths[0] - min_clearance,
L - parked_car_locs[0] - parked_car_lengths[0] - min_clearance,
]
else:
x = np.array(parked_car_locs) # convert to a numpy array
l = np.array(parked_car_lengths) # convert to a numpy array
spots = np.hstack(
[
x[0] - l[0] - min_clearance, # the very first spot
x[1:] - x[:-1] - l[1:] - l[:-1] - 2 * min_clearance, # the spaces between all of the parked cars
L - x[-1] - l[-1] - min_clearance, # the empty space at the end
]
)
return spots
For a given spot at position index
, and a set of cars located at parked_car_locs
with lengths parked_car_lengths
, and clearances between them min_clearance
, for a block face of length L, return the extremeties at which a new car could park in this spot if it has a length new_car_length
.
def get_spot_bounds(index, parked_car_locs, parked_car_lengths, min_clearance, new_car_length, L):
if len(parked_car_locs) == 0: # no cars yet
min_loc = new_car_length + min_clearance
max_loc = L - min_clearance - new_car_length
elif len(parked_car_locs) == 1: # just one car
if index == 0:
min_loc = new_car_length + min_clearance
max_loc = (
parked_car_locs[0] - parked_car_lengths[0] - 2 * min_clearance - new_car_length
)
else:
min_loc = (
parked_car_locs[0] + parked_car_lengths[0] + 2 * min_clearance + new_car_length
)
max_loc = L - min_clearance - new_car_length
else: # more than one car parked
if index == 0: # if this is the first spot
min_loc = new_car_length + min_clearance
max_loc = (
parked_car_locs[0] - parked_car_lengths[0] - 2 * min_clearance - new_car_length
)
elif index == len(parked_car_locs): # if this is the last spot
min_loc = (
parked_car_locs[index - 1]
+ parked_car_lengths[index - 1]
+ 2 * min_clearance
+ new_car_length
)
max_loc = L - min_clearance - new_car_length
else: # if this is one of the interior spots
min_loc = (
parked_car_locs[index - 1]
+ parked_car_lengths[index - 1]
+ 2 * min_clearance
+ new_car_length
)
max_loc = (
parked_car_locs[index]
- parked_car_lengths[index]
- 2 * min_clearance
- new_car_length
)
return min_loc, max_loc
This function is not used for anything.
def is_empty(parked_car_locs, parked_car_lengths, new_car_length, test_loc):
if len(parked_car_locs) == 0: # no cars yet
min_loc = new_car_length + min_clearance
max_loc = L - min_clearance - new_car_length
parked_car_locs = park_the_car(parked_car_locs, test_loc, 0, min_loc, max_loc)
return parked_car_locs, [new_car_length], True
elif len(parked_car_locs) == 1: # just one car
if (test_loc - new_car_length - min_clearance > 0) and (
test_loc + new_car_length + min_clearance < parked_car_locs[0] - parked_car_lengths[0]
):
parked_car_lengths.insert(0, new_car_length)
min_loc = new_car_length + min_clearance
max_loc = parked_car_locs[0] - parked_car_lengths[0] - min_clearance - new_car_length
parked_car_locs = park_the_car(parked_car_locs, test_loc, 0, min_loc, max_loc)
return parked_car_locs, parked_car_lengths, True
elif (
test_loc - new_car_length - min_clearance > parked_car_locs[0] + parked_car_lengths[0]
) and (test_loc + new_car_length + min_clearance < L):
parked_car_lengths.insert(1, new_car_length)
min_loc = parked_car_locs[0] + parked_car_lengths[0] + min_clearance - new_car_length
max_loc = L - min_clearance - new_car_length
parked_car_locs = park_the_car(parked_car_locs, test_loc, 1, min_loc, max_loc)
return parked_car_locs, parked_car_lengths, True
else:
for i in range(len(parked_car_locs) + 1):
if i == 0:
if (test_loc - new_car_length - min_clearance > 0) and (
test_loc + new_car_length + min_clearance
< parked_car_locs[i] - parked_car_lengths[i]
):
parked_car_lengths.insert(i, new_car_length)
min_loc = new_car_length + min_clearance
max_loc = (
parked_car_locs[0] - parked_car_lengths[0] - min_clearance - new_car_length
)
parked_car_locs = park_the_car(parked_car_locs, test_loc, i, min_loc, max_loc)
return parked_car_locs, parked_car_lengths, True
elif i == len(parked_car_locs):
if (
test_loc - new_car_length - min_clearance
> parked_car_locs[i - 1] + parked_car_lengths[i - 1]
) and (test_loc + new_car_length + min_clearance < L):
parked_car_lengths.insert(i, new_car_length)
min_loc = (
parked_car_locs[i - 1]
+ parked_car_lengths[i - 1]
+ min_clearance
+ new_car_length
)
max_loc = L - min_clearance - new_car_length
parked_car_locs = park_the_car(parked_car_locs, test_loc, i, min_loc, max_loc)
return parked_car_locs, parked_car_lengths, True
else:
if (
test_loc - new_car_length - min_clearance
> parked_car_locs[i - 1] + parked_car_lengths[i - 1]
) and (
test_loc + new_car_length + min_clearance
< parked_car_locs[i] - parked_car_lengths[i]
):
parked_car_lengths.insert(i, new_car_length)
min_loc = (
parked_car_locs[i - 1]
+ parked_car_lengths[i - 1]
+ min_clearance
+ new_car_length
)
max_loc = (
parked_car_locs[i] - parked_car_lengths[i] - min_clearance - new_car_length
)
parked_car_locs = park_the_car(parked_car_locs, test_loc, i, min_loc, max_loc)
return parked_car_locs, parked_car_lengths, True
if nothing else worked
return parked_car_locs, parked_car_lengths, False
def time_march(
params,
):
parked_car_locs = []
parked_car_lengths = []
linear_densities = []
car_ids = []
new_car_length = generate_new_car_length(params)
found_spot = False
if not os.path.exists(params["outfolder"]):
os.makedirs(params["outfolder"])
nt = int(np.ceil(params["departures_per_metre"] * params["L"]))
for t in range(nt): # remove one car every timestep
just remove one car
if len(parked_car_locs) > 0:
to_remove = np.random.randint(len(parked_car_locs))
if "spatiotemporal" in params:
for i in range(len(car_ids)): # omg this is going to be so slow
if not "t_f" in car_ids[i]:
if car_ids[i]["loc"] == parked_car_locs[to_remove]:
car_ids[i]["t_f"] = t
print(i)
break
parked_car_locs.pop(to_remove)
parked_car_lengths.pop(to_remove)
Brute force filling method for i in range(1000): # aggressively try to fill every spot test_loc = L*np.random.rand() parked_car_locs,parked_car_lengths,found_spot = is_empty(parked_car_locs, parked_car_lengths, new_car_length, test_loc) if found_spot: new_car_length = generate_new_car_length() # another car arrives looking to park found_spot = False
Criteria-based filling method
fits = True
while fits:
spot_lengths = get_spot_lengths(
parked_car_locs, parked_car_lengths, params["L"], params["min_clearance"]
)
avail_spots = np.array(spot_lengths) > 2 * new_car_length
num_avail_spots = np.sum(avail_spots)
if num_avail_spots == 0:
fits = False
else:
avail_spot_indices = np.where(avail_spots)[0]
i = np.random.choice(avail_spot_indices)
min_loc, max_loc = get_spot_bounds(
i,
parked_car_locs,
parked_car_lengths,
params["min_clearance"],
new_car_length,
params["L"],
)
print(max_loc-min_loc,spot_lengths[i],new_car_length)
parked_car_locs = park_the_car(
params["strategy"], parked_car_locs, 0, i, min_loc, max_loc
)
parked_car_lengths.insert(i, new_car_length)
if "spatiotemporal" in params:
car_ids.append({})
car_ids[-1] = {"loc": parked_car_locs[i], "len": new_car_length, "t_i": t}
new_car_length = generate_new_car_length(
params
) # another car arrives looking to park —-- cars never give up, if they dont make it in this round they will persist to next round
linear_densities.append(2.0 * np.sum(parked_car_lengths) / params["L"])
if params["verbose"]:
plt.ion()
fig = make_graph(
t,
nt,
parked_car_locs,
parked_car_lengths,
spot_lengths,
linear_densities,
params["L"],
)
plt.pause(1e-3)
fig = make_graph(
t, nt, parked_car_locs, parked_car_lengths, spot_lengths, linear_densities, params["L"]
)
fig.savefig(f'{params["outfolder"]}/summary.png')
if "spatiotemporal" in params:
spatiotemporal(nt, car_ids, L, params)
plt.savefig(f'{params["outfolder"]}/spatiotemporal.pdf', dpi=300)
plt.savefig(f'{params["outfolder"]}/spatiotemporal.png', dpi=300)
np.save(f'{params["outfolder"]}/linear_density_{params["ergodic"]}.npy', linear_densities)
np.save(f'{params["outfolder"]}/gaps_{params["ergodic"]}.npy', spot_lengths)
np.save(f'{params["outfolder"]}/cars_{params["ergodic"]}.npy', parked_car_lengths)
return parked_car_locs, parked_car_lengths, spot_lengths, linear_densities
if __name__ == "__main__":
verbose = True verbose = False L = 1e4 # length of road (m) nt = 1e5 mean_car_length = 2.5 # length of HALF OF the average car (m) sigma_car_length = 1./3. # width of distribution of HALF OF car size (m) min_clearance = 0. # gap between cars at EACH END (m)
strategy = ‘test_loc’ strategy = ‘random’ strategy = ‘middle’ strategy = ‘one_side’
json_file = sys.argv[1]
with open(json_file) as f:
params = json.load(f)
strategy = sys.argv[1] outfolder = sys.argv[2]
for i, p in enumerate(
tqdm(params["strategy"], desc="strategy", disable=(len(params["strategy"]) == 1))
):
for j, L in enumerate(
tqdm(params["L"], desc="L", leave=False, disable=(len(params["L"]) == 1))
):
for k, mean_car_length in enumerate(
tqdm(
params["mean_car_length"],
desc="mean",
leave=False,
disable=(len(params["mean_car_length"]) == 1),
)
):
for l, sigma_car_length in enumerate(
tqdm(
params["sigma_car_length"],
desc="sigma",
leave=False,
disable=(len(params["sigma_car_length"]) == 1),
)
):
if not "ergodic" in params:
params["ergodic"] = L
for t in tqdm(
range(int(params["ergodic"] / L)),
desc="ergodic",
leave=False,
disable=(int(params["ergodic"] / L) == 1),
): # rerun the same simulation this many times so that they all have the same equivalent length
temp_params = params.copy()
temp_params["strategy"] = p
temp_params["L"] = L
temp_params["mean_car_length"] = mean_car_length
temp_params["sigma_car_length"] = sigma_car_length
temp_params["ergodic"] = t
temp_params[
"outfolder"
] = f"output/strategy_{p}/L_{L}/mean_car_length_{mean_car_length}/sigma_car_length_{sigma_car_length}"
if "motorcycles" in params:
base_folder = temp_params['outfolder'][6:]
for m in tqdm(params['motorcycle_ratio'], desc="motorcycle_ratio", leave=False):
temp_params['motorcycle_ratio'] = m
temp_params[
"outfolder"
] = f"output/motorcycles/motorcycle_ratio_{m}/{base_folder}"
(
parked_car_locs,
parked_car_lengths,
spot_lengths,
linear_densities,
) = time_march(temp_params)
else:
(
parked_car_locs,
parked_car_lengths,
spot_lengths,
linear_densities,
) = time_march(temp_params)