Skip to content
Snippets Groups Projects
Commit eba87b27 authored by Mohcine Chraibi's avatar Mohcine Chraibi
Browse files

document new options + delete old ca file

parent 3b136e96
Branches
No related tags found
No related merge requests found
...@@ -41,6 +41,8 @@ python cellular_automaton.py <optional arguments> ...@@ -41,6 +41,8 @@ python cellular_automaton.py <optional arguments>
| `--parallel` | | use multithreading | | `--parallel` | | use multithreading |
| `--moore` | | use moore neighborhood (*default Von Neumann*) | | `--moore` | | use moore neighborhood (*default Von Neumann*) |
| `--box` | from_x to_x from_y to_y | Rectangular box defined by 4 numbers, where agents will be distributed. (*default the whole room*) | | `--box` | from_x to_x from_y to_y | Rectangular box defined by 4 numbers, where agents will be distributed. (*default the whole room*) |
|`--save` | | Save simulation to a movie|
|`--maxframe` | MAX_FRAMES | Max frames of simulation (*default 1000*)|
## Simulation results ## Simulation results
......
#!/usr/bin/env python3
from functools import lru_cache
from multiprocessing.pool import Pool
import itertools as it # for cartesian product
import time
import random
import os
import logging
import argparse
import numpy as np
import matplotlib.pyplot as plt
#######################################################
MAX_STEPS = 1000
steps = range(MAX_STEPS)
cellSize = 0.4 # m
vmax = 1.2
dt = cellSize / vmax # time step
from_x, to_x = 1, 63 # todo parse this too
from_y, to_y = 1, 63 # todo parse this too
DEFAULT_BOX = [from_x, to_x, from_y, to_y]
del from_x, to_x, from_y, to_y
# DFF = np.ones( (dim_x, dim_y) ) # dynamic floor field
#######################################################
logfile = 'log.dat'
logging.basicConfig(filename=logfile, level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
def get_parser_args():
parser = argparse.ArgumentParser(
description='Cellular Automaton. Floor Field Model [Burstedde2001] Simulation of pedestrian'
'dynamics using a two-dimensional cellular automaton Physica A, 295, 507-525, 2001')
parser.add_argument('-s', '--ks', type=float, default=2,
help='sensitivity parameter for the Static Floor Field (default 2)')
parser.add_argument('-d', '--kd', type=float, default=1,
help='sensitivity parameter for the Dynamic Floor Field (default 1)')
parser.add_argument('-n', '--numPeds', type=int, default=10, help='Number of agents (default 10)')
parser.add_argument('-p', '--plotS', action='store_const', const=True, default=False,
help='plot Static Floor Field')
parser.add_argument('--plotD', action='store_const', const=True, default=False,
help='plot Dynamic Floor Field')
parser.add_argument('--plotAvgD', action='store_const', const = True, default=False,
help='plot average Dynamic Floor Field')
parser.add_argument('-P', '--plotP', action='store_const', const=True, default=False,
help='plot Pedestrians')
parser.add_argument('-r', '--shuffle', action='store_const', const=True, default=True,
help='random shuffle')
parser.add_argument('-v', '--reverse', action='store_const', const=True, default=False,
help='reverse sequential update')
parser.add_argument('-l', '--log', type=argparse.FileType('w'), default='log.dat',
help='log file (default log.dat)')
parser.add_argument('--decay', type=float, default=0.3,
help='the decay probability of the Dynamic Floor Field (default 0.2')
parser.add_argument('--diffusion', type=float, default=0.1,
help='the diffusion probability of the Dynamic Floor Field (default 0.2)')
parser.add_argument('-W', '--width', type=float, default=4.0,
help='the width of the simulation area in meter, excluding walls')
parser.add_argument('-H', '--height', type=float, default=4.0,
help='the height of the simulation room in meter, excluding walls')
parser.add_argument('-c', '--clean', action='store_const', const=True, default=False,
help='remove files from directories dff/ sff/ and peds/')
parser.add_argument('-N', '--nruns', type=int, default=1,
help='repeat the simulation N times')
parser.add_argument('--parallel', action='store_const', const=True, default=False,
help='use multithreading')
parser.add_argument('--moore', action='store_const', const=True, default=False,
help='use moore neighborhood. Default= Von Neumann')
parser.add_argument('--box', type=int, nargs=4, default=DEFAULT_BOX,
help='Rectangular box, initially populated with agents: from_x, to_x, from_y, to_y. Default: The whole room')
_args = parser.parse_args()
return _args
def init_obstacles():
return np.ones((dim_x, dim_y), int) # obstacles/walls/boundaries
def init_walls(exit_cells, ):
"""
define where are the walls. Consider the exits
"""
OBST = init_obstacles()
OBST[0, :] = OBST[-1, :] = OBST[:, -1] = OBST[:, 0] = -1
for e in exit_cells:
OBST[e] = 1
return OBST
def check_N_pedestrians(_box, N_pedestrians):
"""
check if <N_pedestrian> is too big. if so change it to fit in <box>
"""
# holding box, where to distribute pedestrians
# ---------------------------------------------------
_from_x = _box[0]
_to_x = _box[1]
_from_y = _box[2]
_to_y = _box[3]
# ---------------------------------------------------
nx = _to_x - _from_x + 1
ny = _to_y - _from_y + 1
if N_pedestrians > nx * ny:
logging.warning("N_pedestrians (%d) is too large (max. %d). Set to max." % (N_pedestrians, nx * ny))
N_pedestrians = nx * ny
return N_pedestrians
def init_peds(N, box):
"""
distribute N pedestrians in box
"""
from_x, to_x = box[:2]
from_y, to_y = box[2:]
nx = to_x - from_x + 1
ny = to_y - from_y + 1
PEDS = np.ones(N, int) # pedestrians
EMPTY_CELLS_in_BOX = np.zeros(nx * ny - N, int) # the rest of cells in the box
PEDS = np.hstack((PEDS, EMPTY_CELLS_in_BOX)) # put 0s and 1s together
np.random.shuffle(PEDS) # shuffle them
PEDS = PEDS.reshape((nx, ny)) # reshape to a box
EMPTY_CELLS = np.zeros((dim_x, dim_y), int) # this is the simulation space
EMPTY_CELLS[from_x:to_x + 1, from_y:to_y + 1] = PEDS # put in the box
logging.info("Init peds finished. Box: x: [%.2f, %.2f]. y: [%.2f, %.2f]",
from_x, to_x, from_y, to_y)
return EMPTY_CELLS
def plot_sff2(SFF, walls, i):
"""
plots a numbered image. Useful for making movies
"""
print("plot_sff: %.6d"%i)
fig = plt.figure()
ax = fig.add_subplot(111)
ax.cla()
plt.set_cmap('jet')
cmap = plt.get_cmap()
cmap.set_bad(color='k', alpha=0.8)
vect = SFF * walls
vect[vect < 0] = np.Inf
# print (vect)
max_value = np.max(SFF)
min_value = np.min(SFF)
plt.imshow(vect, cmap=cmap, interpolation='nearest', vmin=min_value, vmax=max_value, extent=[0, dim_y, 0, dim_x]) # lanczos nearest
plt.colorbar()
# print(i)
plt.title("%.6d"%i)
figure_name = os.path.join('sff', '%.6d.png'%i)
plt.savefig(figure_name)
plt.close()
def plot_sff(SFF, walls):
fig = plt.figure()
ax = fig.add_subplot(111)
ax.cla()
plt.set_cmap('jet')
cmap = plt.get_cmap()
cmap.set_bad(color='k', alpha=0.8)
vect = SFF.copy()
vect[walls < 0] = np.Inf
max_value = np.max(SFF)
min_value = np.min(SFF)
plt.imshow(vect, cmap=cmap, interpolation='nearest', vmin=min_value, vmax=max_value, extent=[0, dim_y, 0, dim_x]) # lanczos nearest
plt.colorbar()
figure_name = os.path.join('sff', 'SFF.png')
plt.savefig(figure_name, dpi=600)
plt.close()
def plot_dff(dff, walls, name="DFF", max_value=None, title=""):
fig = plt.figure()
ax = fig.add_subplot(111)
ax.cla()
plt.set_cmap('jet')
cmap = plt.get_cmap()
cmap.set_bad(color='k', alpha=0.8)
vect = dff.copy()
vect[walls < 0] = np.Inf
im = ax.imshow(vect, cmap=cmap, interpolation='nearest', vmin=0, vmax=max_value, extent=[0, dim_y, 0, dim_x]) # lanczos nearest
plt.colorbar(im, format='%.1f')
#cbar = plt.colorbar()
if title:
plt.title(title)
figure_name = os.path.join('dff', name+'.png')
plt.savefig(figure_name, dpi=600)
plt.close()
logging.info("plot dff. figure: {}.png".format(name))
def plot_peds(peds, walls, i):
fig = plt.figure()
ax = fig.add_subplot(111)
ax.cla()
cmap = plt.get_cmap("gray")
cmap.set_bad(color='b', alpha=0.8)
N = np.sum(peds)
# print N, type(N)
# print peds+walls
#ax.axes.autoscale(False)
grid_x = np.arange(1, dim_x-1, cellSize)
grid_y = np.arange(1, dim_y-1, cellSize)
ax.imshow(peds + walls, cmap=cmap, interpolation='nearest', vmin=-1, vmax=2) # 1-peds because I want the peds to be black
plt.grid(True, color='k', alpha=0.3)
plt.yticks(np.arange(1.5, peds.shape[0], 1))
plt.xticks(np.arange(1.5, peds.shape[1], 1))
plt.setp(ax.get_xticklabels(), visible=False)
plt.setp(ax.get_yticklabels(), visible=False)
ax.tick_params(axis='both', which='both', length=0)
S = 't: %3.3d | N: %3.3d ' % (i, N)
plt.title("%8s" % S)
figure_name = os.path.join('peds', 'peds%.6d.png' % i)
plt.savefig(figure_name)
plt.close()
def init_DFF():
"""
"""
return np.zeros((dim_x, dim_y))
def update_DFF(dff, diff):
#for cell in diff:
# assert walls[cell] > -10
# dff[cell] += 1
dff += diff
for i, j in it.chain(it.product(range(1, dim_x - 1), range(1, dim_y - 1)), exit_cells):
for _ in range(int(dff[i, j])):
if np.random.rand() < delta: # decay
dff[i, j] -= 1
elif np.random.rand() < alpha: # diffusion
dff[i, j] -= 1
dff[random.choice(get_neighbors((i, j)))] += 1
assert walls[i, j] > -10 or dff[i, j] == 0, (dff, i, j)
# dff[:] = np.ones((dim_x, dim_y))
@lru_cache(1)
def init_SFF(_exit_cells, _dim_x, _dim_y, drawS):
# start with exit's cells
SFF = np.empty((_dim_x, _dim_y)) # static floor field
SFF[:] = np.sqrt(_dim_x ** 2 + _dim_y ** 2)
make_videos = 0
if make_videos and drawS:
plot_sff2(SFF, walls, 1)
cells_initialised = []
for e in _exit_cells:
cells_initialised.append(e)
SFF[e] = 0
if make_videos and drawS:
plot_sff2(SFF, walls, 2)
i = 3
while cells_initialised:
cell = cells_initialised.pop(0)
neighbor_cells = get_neighbors(cell)
for neighbor in neighbor_cells:
# print ("cell",cell, "neighbor",neighbor)
if SFF[cell] + 1 < SFF[neighbor]:
SFF[neighbor] = SFF[cell] + 1
cells_initialised.append(neighbor)
# print(SFF)
# print(cells_initialised)
if make_videos and drawS:
plot_sff2(SFF, walls, i)
i += 1
return SFF
@lru_cache(16*1024)
def get_neighbors(cell):
"""
von Neumann neighborhood
"""
neighbors = []
i, j = cell
if i < dim_x - 1 and walls[(i + 1, j)] >= 0:
neighbors.append((i + 1, j))
if i >= 1 and walls[(i - 1, j)] >= 0:
neighbors.append((i - 1, j))
if j < dim_y - 1 and walls[(i, j + 1)] >= 0:
neighbors.append((i, j + 1))
if j >= 1 and walls[(i, j - 1)] >= 0:
neighbors.append((i, j - 1))
# moore
if moore:
if i >= 1 and j >= 1 and walls[(i-1, j - 1)] >= 0:
neighbors.append((i-1, j - 1))
if i < dim_x - 1 and j < dim_y -1 and walls[(i+1, j+1)] >= 0:
neighbors.append((i+1, j + 1))
if i < dim_x - 1 and j >= 1 and walls[(i+1, j-1)] >= 0:
neighbors.append((i+1, j - 1))
if i >= 1 and j < dim_y -1 and walls[(i-1, j+1)] >= 0:
neighbors.append((i-1, j + 1))
# not shuffling singnificantly alters the simulation...
random.shuffle(neighbors)
return neighbors
def seq_update_cells(peds, sff, dff, kappaD, kappaS, shuffle, reverse):
"""
sequential update
input
- peds:
- sff:
- dff:
- prob_walls:
- kappaD:
- kappaS:
- rand: random shuffle
return
- new peds
"""
tmp_peds = np.empty_like(peds) # temporary cells
np.copyto(tmp_peds, peds)
dff_diff = np.zeros((dim_x, dim_y))
grid = list(it.product(range(1, dim_x - 1), range(1, dim_y - 1))) + list(exit_cells)
if shuffle: # sequential random update
random.shuffle(grid)
elif reverse: # reversed sequential update
grid.reverse()
for (i, j) in grid: # walk through all cells in geometry
if peds[i, j] == 0:
continue
if (i, j) in exit_cells:
tmp_peds[i, j] = 0
dff_diff[i, j] += 1
continue
p = 0
probs = {}
cell = (i, j)
for neighbor in get_neighbors(cell): # get the sum of probabilities
# original code:
# probability = np.exp(-kappaS * sff[neighbor]) * np.exp(kappaD * dff[neighbor]) * \
# (1 - tmp_peds[neighbor])
# the absolute value of the exponents can get very large yielding 0 or
# inifite probability.
# to prevent this we multiply every probability with exp(kappaS * sff[cell) and
# exp(-kappaD * dff[cell]).
# since the probabilities are normalized this doesn't have any effect on the model
probability = np.exp(kappaS * (sff[cell] - sff[neighbor])) * \
np.exp(kappaD * (dff[neighbor] - dff[cell])) * \
(1 - tmp_peds[neighbor])
p += probability
probs[neighbor] = probability
if p == 0: # pedestrian in cell can not move
continue
r = np.random.rand() * p
# print ("start update")
for neighbor in get_neighbors(cell): #TODO: shuffle?
r -= probs[neighbor]
if r <= 0: # move to neighbor cell
tmp_peds[neighbor] = 1
tmp_peds[i, j] = 0
dff_diff[i, j] += 1
break
return tmp_peds, dff_diff
def print_logs(N_pedestrians, width, height, t, dt, nruns, Dt):
"""
print some infos to the screen
"""
print ("Simulation of %d pedestrians" % N_pedestrians)
print ("Simulation space (%.2f x %.2f) m^2" % (width, height))
print ("SFF: %.2f | DFF: %.2f" % (kappaS, kappaD))
print ("Mean Evacuation time: %.2f s, runs: %d" % (t * dt / nruns, nruns))
print ("Total Run time: %.2f s" % Dt)
print ("Factor: x%.2f" % (dt * t / Dt))
def setup_dir(dir, clean):
print("make ", dir)
if os.path.exists(dir) and clean:
os.system('rm -rf %s' % dir)
os.makedirs(dir, exist_ok=True)
def simulate(args):
n, npeds, box, sff, shuffle, reverse, drawP, giveD = args
print("init %d agents in box=[%d, %d, %d, %d]"%(npeds, box[0], box[1], box[2], box[3]))
peds = init_peds(npeds, box)
dff = init_DFF()
old_dffs = []
for t in steps: # simulation loop
print('\tn: %3d ---- t: %3d | N: %3d' % (n, t, int(np.sum(peds))))
if drawP:
plot_peds(peds, walls, t)
peds, dff_diff = seq_update_cells(peds, sff, dff, kappaD, kappaS,
shuffle, reverse)
update_DFF(dff, dff_diff)
if giveD:
old_dffs.append((t, dff.copy()))
if not peds.any(): # is everybody out? TODO: check this. Some bug is lurking here
print("Quite simulation")
break
# else:
# raise TimeoutError("simulation taking too long")
if giveD:
return t, old_dffs
else:
return t
def check_box(box):
"""
exit if box is not well defined
"""
assert (box[0] < box[1]), "from_x smaller than to_x"
assert (box[2] < box[3]), "from_y smaller than to_y"
def main(args):
global kappaS, kappaD, dim_y, dim_x, exit_cells, SFF, alpha, delta, walls, parallel, box, moore
# init parameters
drawS = args.plotS # plot or not
drawP = args.plotP # plot or not
kappaS = args.ks
kappaD = args.kd
npeds = args.numPeds
shuffle = args.shuffle
reverse = args.reverse
drawD = args.plotD
drawD_avg = args.plotAvgD
clean_dirs = args.clean
width = args.width # in meters
height = args.height # in meters
parallel = args.parallel
box = args.box
check_box(box)
moore = args.moore
# check if no box is specified
if moore:
print("Neighborhood: Moore")
else:
print("Neighborhood: Von Neumann")
if parallel and drawP :
raise NotImplementedError("cannot plot pedestrians when multiprocessing")
# TODO check if width and hight are multiples of cellSize
dim_y = int(width / cellSize + 2 + 0.00000001) # number of columns, add ghost cells
dim_x = int(height / cellSize + 2 + 0.00000001) # number of rows, add ghost cells
print("cellsize: ", cellSize, " dim_x: ", dim_x, " dim_y: ", dim_y)
if box == DEFAULT_BOX:
print("box == room")
box = [1, dim_x - 2, 1, dim_y - 2]
nruns = args.nruns
exit_cells = frozenset(((dim_x // 2, dim_y - 1), (dim_x // 2 + 1, dim_y - 1),
(dim_x - 1, dim_y//2 + 1) , (dim_x - 1, dim_y//2),
(0, dim_y//2 + 1) , (1, dim_y//2),
(dim_x//2 + 1, 0) , (dim_x//2, 0)
))
delta = args.decay
alpha = args.diffusion
npeds = check_N_pedestrians(box, npeds)
walls = init_walls(exit_cells)
sff = init_SFF(exit_cells, dim_x, dim_y, drawS)
init_obstacles()
if drawS:
setup_dir('sff', clean_dirs)
plot_sff(sff, walls)
t1 = time.time()
tsim = 0
if drawP: setup_dir('peds', clean_dirs)
if drawD or drawD_avg: setup_dir('dff', clean_dirs)
times = []
old_dffs = []
if not parallel:
for n in range(nruns):
print("n= ", n, " nruns=", nruns)
if drawD_avg or drawD:
t, dffs = simulate((n, npeds, box, sff, shuffle, reverse,
drawP, drawD_avg or drawD))
old_dffs += dffs
else:
t = simulate((n, npeds, box, sff, shuffle, reverse, drawP,
drawD_avg or drawD))
tsim += t
print("time ", tsim)
times.append(t * dt)
if moore:
print("save moore.npy")
np.save("moore.npy",times)
else:
print("save neumann.npy")
np.save("neumann.npy",times)
else:
nproc = min(nruns, 8)
print('using {} processes'.format(nproc))
jobs = [(n, npeds, box, sff, shuffle, reverse, drawP, drawD_avg or drawD)
for n in range(nruns)]
with Pool(nproc) as pool:
results = pool.map(simulate, jobs)
if drawD_avg or drawD:
ts, chunked_dffs = zip(*results)
times = [t * dt for t in ts]
tsim = sum(ts)
old_dffs = sum(chunked_dffs, [])
else:
times = [t * dt for t in results]
tsim = sum(results)
t2 = time.time()
print_logs(npeds, width, height, tsim, dt, nruns, t2 - t1)
if drawD_avg:
print('plotting average DFF')
if moore:
title = "DFF-avg_Moore_runs_%d_N%d_S%.2f_D%.2f"%(nruns, npeds, kappaS, kappaD)
else:
title = "DFF-avg_Neumann_runs_%d_N%d_S%.2f_D%.2f"%(nruns, npeds, kappaS, kappaD)
plot_dff(sum(x[1] for x in old_dffs) / tsim, walls, title)
# title=r"$t = {:.2f}$ s, N={}, #runs = {}, $\kappa_S={}\;, \kappa_D={}$".format(sum(times), npeds, nruns, kappaS, kappaD)
if drawD:
print('plotting DFFs...')
max_dff = max(field.max() for _, field in old_dffs)
for tm, dff in old_dffs:
print("t: %3.4d" % tm)
plot_dff(dff, walls, "DFF-%3.4d"%tm, max_dff, "t: %3.4d" % tm)
return times
if __name__ == "__main__":
args = get_parser_args()
main(args)
...@@ -46,7 +46,7 @@ def get_parser_args(): ...@@ -46,7 +46,7 @@ def get_parser_args():
parser.add_argument('--diffusion', type=float, default=0.1, parser.add_argument('--diffusion', type=float, default=0.1,
help='the diffusion probability of the Dynamic Floor Field (default 0.2)') help='the diffusion probability of the Dynamic Floor Field (default 0.2)')
parser.add_argument('--maxframe', type=int, default=1000, parser.add_argument('--maxframe', type=int, default=1000,
help='Max simulation frame (default 1000') help='Max simulation frame (default 1000)')
parser.add_argument('-W', '--width', type=float, default=4.0, parser.add_argument('-W', '--width', type=float, default=4.0,
help='the width of the simulation area in meter, excluding walls') help='the width of the simulation area in meter, excluding walls')
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please to comment