Make a microscopic simulation

In the two following models, individuals are seen as spherical particles. They move at a desired velocity to reach a goal (typically a door). Doing nothing more would lead to many overlaps between individuals. Hence the two families of models below can prevent these overlaps through two different approaches. For the Social Force models, and, for some forces are added to act against overlaps, and for the Granular model the velocities are projected into a permissible velocity space which ensures the absence of overlaps.

Social force model

The Social Force model has been introduced in the 90’s. Pedestrians are identified with inertial particles submitted to a forcing term which implements the individual tendencies and extra forces which account for interactions with other pedestrians (typically the tendency to preserve a certain distance with neighbors).

Reference : [MF2018] Chapter 3.

Some examples can be found in the directory

cromosim/examples/micro/social

and can be launched with

python micro_social.py --json input_room.json
python micro_social.py --json input_event.json
python micro_social.py --json input_stadium.json
python micro_social.py --json input_shibuya_crossing.json
python micro_social.py --json input_stairs.json
Evacuation of a room, visualization of trajectories

Circular track around a stadium

Evacuation of an exhibition hall: two groups of people with the same destination
Evacuation of an exhibition hall: sensors (green lines) results
Evacuation of an exhibition hall : Sensors (green lines) results

Shibuya crossing (Japan) : five groups of people and five different destinations

Two floors of a building: a group of people goes up and another goes down
Floor 1 on the left and 0 on the right
micro_social.py
  1# Authors:
  2#     Sylvain Faure <sylvain.faure@universite-paris-saclay.fr>
  3#     Bertrand Maury <bertrand.maury@universite-paris-saclay.fr>
  4#
  5#      cromosim/examples/micro/social/micro_social.py
  6#      python micro_social.py --json input.json
  7#
  8# License: GPL
  9
 10import sys
 11import os
 12import json
 13from optparse import OptionParser
 14import numpy as np
 15import matplotlib.pyplot as plt
 16from matplotlib.patches import Circle, Ellipse, Rectangle, Polygon
 17from matplotlib.lines import Line2D
 18
 19from cromosim.domain import Domain
 20from cromosim.domain import Destination
 21from cromosim.micro import people_initialization, plot_people, plot_sensors
 22from cromosim.micro import find_duplicate_people, compute_contacts
 23from cromosim.micro import compute_forces, move_people, people_update_destination
 24
 25plt.ion()
 26
 27"""
 28    python micro_social.py --json input.json
 29"""
 30parser = OptionParser(usage="usage: %prog [options] filename", version="%prog 1.0")
 31parser.add_option('--json', dest="jsonfilename", default="input.json",
 32                  type="string",
 33                  action="store", help="Input json filename")
 34opt, remainder = parser.parse_args()
 35print("===> JSON filename = ", opt.jsonfilename)
 36with open(opt.jsonfilename) as json_file:
 37    try:
 38        input = json.load(json_file)
 39    except json.JSONDecodeError as msg:
 40        print(msg)
 41        print("Failed to load json file ", opt.jsonfilename)
 42        print("Check its content \
 43            (https://fr.wikipedia.org/wiki/JavaScript_Object_Notation)")
 44        sys.exit()
 45
 46"""
 47    Get parameters from json file :
 48    prefix: string
 49        Folder name to store the results
 50    with_graphes: bool
 51        true if all the graphes are shown and saved in png files,
 52        false otherwise
 53    seed: integer
 54        Random seed which can be used to reproduce a random selection
 55        if >0
 56    For each domain :
 57    |    name: string
 58    |        Domain name
 59    |    background: string
 60    |        Image file used as background
 61    |    px: float
 62    |        Pixel size in meters (also called space step)
 63    |    width: integer
 64    |        Domain width (equal to the width of the background image)
 65    |    height: integer
 66    |        Domain height (equal to the height of the background image)
 67    |    wall_colors: list
 68    |        rgb colors for walls
 69    |        [ [r,g,b],[r,g,b],... ]
 70    |    shape_lines: list
 71    |        Used to define the Matplotlib Polyline shapes,
 72    |        [
 73    |          {
 74    |             "xx": [x0,x1,x2,...],
 75    |             "yy": [y0,y1,y2,...],
 76    |             "linewidth": float,
 77    |             "outline_color": [r,g,b],
 78    |             "fill_color": [r,g,b]
 79    |          },...
 80    |        ]
 81    |    shape_circles: list
 82    |        Used to define the Matplotlib Circle shapes,
 83    |        [
 84    |           {
 85    |             "center_x": float,
 86    |             "center_y": float,
 87    |             "radius": float,
 88    |             "outline_color": [r,g,b],
 89    |             "fill_color": [r,g,b]
 90    |            },...
 91    |        ]
 92    |    shape_ellipses: list
 93    |        Used to define the Matplotlib Ellipse shapes,
 94    |        [
 95    |           {
 96    |             "center_x": float,
 97    |             "center_y": float,
 98    |             "width": float,
 99    |             "height": float,
100    |             "angle_in_degrees_anti-clockwise": float (degre),
101    |             "outline_color": [r,g,b],
102    |             "fill_color": [r,g,b]
103    |            },...
104    |        ]
105    |    shape_rectangles: list
106    |        Used to define the Matplotlib Rectangle shapes,
107    |        [
108    |           {
109    |             "bottom_left_x": float,
110    |             "bottom_left_y": float,
111    |             "width": float,
112    |             "height": float,
113    |             "angle_in_degrees_anti-clockwise": float (degre),
114    |             "outline_color": [r,g,b],
115    |             "fill_color": [r,g,b]
116    |            },...
117    |        ]
118    |    shape_polygons: list
119    |        Used to define the Matplotlib Polygon shapes,
120    |        [
121    |           {
122    |             "xy": float,
123    |             "outline_color": [r,g,b],
124    |             "fill_color": [r,g,b]
125    |            },...
126    |        ]
127    |    destinations: list
128    |        Used to define the Destination objects,
129    |        [
130    |           {
131    |             "name": string,
132    |             "colors": [[r,g,b],...],
133    |             "excluded_colors": [[r,g,b],...],
134    |             "desired_velocity_from_color": [] or
135    |             [
136    |                {
137    |                   "color": [r,g,b],
138    |                   "desired_velocity": [ex,ey]
139    |                },...
140    |             ],
141    |             "velocity_scale": float,
142    |             "next_destination": null or string,
143    |             "next_domain": null or string,
144    |             "next_transit_box": null or [[x0,y0],...,[x3,y3]]
145    |            },...
146    |        ]
147    |--------------------
148    For each group of persons, required for the initialization process:
149    |    nb:
150    |        Number of people in the group
151    |    domain:
152    |        Name of the domain where people are located
153    |    radius_distribution:
154    |        Radius distribution used to create people
155    |        ["uniform",min,max] or ["normal",mean,sigma]
156    |    velocity_distribution:
157    |        Velocity distribution used to create people
158    |        ["uniform",min,max] or ["normal",mean,sigma]
159    |    box:
160    |        Boxe to randomly position people at initialization
161    |        [ [x0,y0],[x1,y1],...]
162    |    destination:
163    |        Initial destination for the group
164    |--------------------
165    For each sensor:
166    |    domain:
167    |        Name of the domain where the sensor is located
168    |    line:
169    |        Segment through which incoming and outgoing flows are measured
170    |        [x0,y0,x1,y1]
171    |--------------------
172    Tf: float
173        Final time
174    dt: float
175        Time step
176    drawper: integer
177        The results will be displayed every "drawper" iterations
178    mass: float
179        Mass of one person (typically 80 kg)
180    tau: float
181        (typically 0.5 s)
182    F: float
183        Coefficient for the repulsion force between individuals
184        (typically 2000 N)
185    kappa: float
186        Stiffness constant to handle overlapping (typically
187        120000 kg s^-2)
188    delta: float
189        To maintain a certain distance from neighbors (typically 0.08 m)
190    Fwall: float
191        Coefficient for the repulsion force between individual and
192        walls (typically 2000 N, like for F)
193    lambda: float
194        Directional dependence (between 0 and 1 = fully isotropic case)
195    eta: float
196        Friction coefficient (typically 240000 kg m^-1 s^-1)
197    projection_method: string
198        Name of the projection method : cvxopt(default),
199        mosek(a licence is needed) or uzawa
200    dmax: float
201        Maximum distance used to detect neighbors
202    dmin_people: float
203        Minimum distance allowed between individuals
204    dmin_walls: float
205        Minimum distance allowed between an individual and a wall
206    plot_people: boolean
207        If true, people are drawn
208    plot_contacts: boolean
209        If true, active contacts between people are drawn
210    plot_desired_velocities: boolean
211        If true, people desired velocities are drawn
212    plot_velocities: boolean
213        If true, people velocities are drawn
214    plot_sensors: boolean
215        If true, plot sensor lines on people graph and sensor data graph
216    plot_paths: boolean
217        If true, people paths are drawn
218"""
219
220prefix = input["prefix"]
221if not os.path.exists(prefix):
222    os.makedirs(prefix)
223seed = input["seed"]
224with_graphes = input["with_graphes"]
225json_domains = input["domains"]
226# print("===> JSON data used to build the domains : ",json_domains)
227json_people_init = input["people_init"]
228# print("===> JSON data used to create the groups : ",json_people_init)
229json_sensors = input["sensors"]
230# print("===> JSON data used to create sensors : ",json_sensors)
231Tf = input["Tf"]
232dt = input["dt"]
233drawper = input["drawper"]
234mass = input["mass"]
235tau = input["tau"]
236F = input["F"]
237kappa = input["kappa"]
238delta = input["delta"]
239Fwall = input["Fwall"]
240lambda_ = input["lambda"]
241eta = input["eta"]
242projection_method = input["projection_method"]
243dmax = input["dmax"]
244dmin_people = input["dmin_people"]
245dmin_walls = input["dmin_walls"]
246plot_p = input["plot_people"]
247plot_c = input["plot_contacts"]
248plot_v = input["plot_velocities"]
249plot_vd = input["plot_desired_velocities"]
250plot_pa = input["plot_paths"]
251plot_s = input["plot_sensors"]
252plot_pa = input["plot_paths"]
253print("===> Final time, Tf = ", Tf)
254print("===> Time step, dt = ", dt)
255print("===> To draw the results each drawper iterations, \
256      drawper = ", drawper)
257print("===> Maximal distance to find neighbors, dmax = ",
258      dmax, ", example : 2*dt")
259print("===> ONLY used during initialization ! Minimal distance between \
260      persons, dmin_people = ", dmin_people)
261print("===> ONLY used during initialization ! Minimal distance between a \
262      person and a wall, dmin_walls = ", dmin_walls)
263
264"""
265    Build the Domain objects
266"""
267domains = {}
268for i, jdom in enumerate(json_domains):
269    jname = jdom["name"]
270    print("===> Build domain number ", i, " : ", jname)
271    jbg = jdom["background"]
272    jpx = jdom["px"]
273    jwidth = jdom["width"]
274    jheight = jdom["height"]
275    jwall_colors = jdom["wall_colors"]
276    if (jbg == ""):
277        dom = Domain(name=jname, pixel_size=jpx, width=jwidth,
278                     height=jheight, wall_colors=jwall_colors)
279    else:
280        dom = Domain(name=jname, background=jbg, pixel_size=jpx,
281                     wall_colors=jwall_colors)
282    # To add lines : Line2D(xdata, ydata, linewidth)
283    for sl in jdom["shape_lines"]:
284        line = Line2D(sl["xx"], sl["yy"], linewidth=sl["linewidth"])
285        dom.add_shape(line, outline_color=sl["outline_color"],
286                      fill_color=sl["fill_color"])
287    # To add circles : Circle( (center_x,center_y), radius )
288    for sc in jdom["shape_circles"]:
289        circle = Circle((sc["center_x"], sc["center_y"]), sc["radius"])
290        dom.add_shape(circle, outline_color=sc["outline_color"],
291                      fill_color=sc["fill_color"])
292    # To add ellipses : Ellipse( (center_x,center_y), width, height,
293    #                            angle_in_degrees_anti-clockwise )
294    for se in jdom["shape_ellipses"]:
295        ellipse = Ellipse((se["center_x"], se["center_y"]),
296                          se["width"], se["height"],
297                          se["angle_in_degrees_anti-clockwise"])
298        dom.add_shape(ellipse, outline_color=se["outline_color"],
299                      fill_color=se["fill_color"])
300    # To add rectangles : Rectangle( (bottom_left_x,bottom_left_y),
301    #                       width, height, angle_in_degrees_anti-clockwise )
302    for sr in jdom["shape_rectangles"]:
303        rectangle = Rectangle((sr["bottom_left_x"], sr["bottom_left_y"]),
304                              sr["width"], sr["height"],
305                              sr["angle_in_degrees_anti-clockwise"])
306        dom.add_shape(rectangle, outline_color=sr["outline_color"],
307                      fill_color=sr["fill_color"])
308    # To add polygons : Polygon( [[x0,y0],[x1,y1],...] )
309    for spo in jdom["shape_polygons"]:
310        polygon = Polygon(spo["xy"])
311        dom.add_shape(polygon, outline_color=spo["outline_color"],
312                      fill_color=spo["fill_color"])
313    # To build the domain : background + shapes
314    dom.build_domain()
315    # To add all the available destinations
316    for j, dd in enumerate(jdom["destinations"]):
317        desired_velocity_from_color = []
318        for gg in dd["desired_velocity_from_color"]:
319            desired_velocity_from_color.append(
320                np.concatenate((gg["color"], gg["desired_velocity"])))
321        dest = Destination(
322            name=dd["name"], colors=dd["colors"],
323            excluded_colors=dd["excluded_colors"],
324            desired_velocity_from_color=desired_velocity_from_color,
325            velocity_scale=dd["velocity_scale"],
326            next_destination=dd["next_destination"],
327            next_domain=dd["next_domain"],
328            next_transit_box=dd["next_transit_box"])
329        print("===> Destination : ", dest)
330        dom.add_destination(dest)
331        if (with_graphes):
332            dom.plot_desired_velocity(dd["name"], id=100*i+10+j, step=20)
333
334    print("===> Domain : ", dom)
335    if (with_graphes):
336        dom.plot(id=100*i)
337        dom.plot_wall_dist(id=100*i+1, step=20)
338
339    domains[dom.name] = dom
340
341print("===> All domains = ", domains)
342
343"""
344    To create the sensors to measure the pedestrian flows
345"""
346
347all_sensors = {}
348for domain_name in domains:
349    all_sensors[domain_name] = []
350for s in json_sensors:
351    s["id"] = []
352    s["times"] = []
353    s["xy"] = []
354    s["dir"] = []
355    all_sensors[s["domain"]].append(s)
356    # print("===> All sensors = ",all_sensors)
357
358"""
359    Initialization
360"""
361
362# Current time
363t = 0.0
364counter = 0
365
366# Initialize people
367all_people = {}
368for i, peopledom in enumerate(json_people_init):
369    dom = domains[peopledom["domain"]]
370    groups = peopledom["groups"]
371    print("===> Group number ", i, ", domain = ", peopledom["domain"])
372    people = people_initialization(
373        dom, groups, dt,
374        dmin_people=dmin_people, dmin_walls=dmin_walls, seed=seed,
375        itermax=10, projection_method=projection_method, verbose=True)
376    I, J, Vd = dom.people_desired_velocity(
377        people["xyrv"],
378        people["destinations"])
379    people["Vd"] = Vd
380    for ip, pid in enumerate(people["id"]):
381        people["paths"][pid] = people["xyrv"][ip, :2]
382    contacts = None
383    if (with_graphes):
384        colors = people["xyrv"][:, 2]
385        plot_people(100*i+20, dom, people, contacts, colors, time=t,
386                    plot_people=plot_p, plot_contacts=plot_c,
387                    plot_velocities=plot_v, plot_desired_velocities=plot_vd,
388                    plot_sensors=plot_s, sensors=all_sensors[dom.name],
389                    savefig=True, filename=prefix+dom.name+'_fig_' +
390                    str(counter).zfill(6)+'.png')
391    all_people[peopledom["domain"]] = people
392# print("===> All people = ",all_people)
393
394"""
395    Main loop
396"""
397
398cc = 0
399draw = True
400
401while (t < Tf):
402
403    print("\n===> Time = "+str(t))
404
405    # Compute people desired velocity
406    for idom, name in enumerate(domains):
407        print("===> Compute desired velocity for domain ", name)
408        dom = domains[name]
409        people = all_people[name]
410        I, J, Vd = dom.people_desired_velocity(
411            people["xyrv"],
412            people["destinations"])
413        people["Vd"] = Vd
414        people["I"] = I
415        people["J"] = J
416
417    # Look at if there are people in the transit boxes
418    print("===> Find people who have to be duplicated")
419    virtual_people = find_duplicate_people(all_people, domains)
420    # print("     virtual_people : ",virtual_people)
421
422    # Social forces
423    for idom, name in enumerate(domains):
424        print("===> Compute social forces for domain ", name)
425        dom = domains[name]
426        people = all_people[name]
427
428        try:
429            xyrv = np.concatenate(
430                (people["xyrv"], virtual_people[name]["xyrv"]))
431            Vd = np.concatenate(
432                (people["Vd"], virtual_people[name]["Vd"]))
433            Uold = np.concatenate(
434                (people["Uold"], virtual_people[name]["Uold"]))
435        except:
436            xyrv = people["xyrv"]
437            Vd = people["Vd"]
438            Uold = people["Uold"]
439
440        if (xyrv.shape[0] > 0):
441
442            if (np.unique(xyrv, axis=0).shape[0] != xyrv.shape[0]):
443                print("===> ERROR : There are two identical lines in the")
444                print("             array xyrv used to determine the \
445                    contacts between")
446                print("             individuals and this is not normal.")
447                sys.exit()
448
449            contacts = compute_contacts(dom, xyrv, dmax)
450            print("     Number of contacts: ", contacts.shape[0])
451            Forces = compute_forces(F, Fwall, xyrv, contacts, Uold, Vd,
452                                    lambda_, delta, kappa, eta)
453            nn = people["xyrv"].shape[0]
454            all_people[name]["U"] = dt*(Vd[:nn, :]-Uold[:nn, :])/tau +\
455                Uold[:nn, :] + dt*Forces[:nn, :]/mass
456            # only for the plot of virtual people :
457            virtual_people[name]["U"] = dt*(Vd[nn:, :]-Uold[nn:, :])/tau +\
458                Uold[nn:, :] + dt*Forces[nn:, :]/mass
459
460            all_people[name], all_sensors[name] = move_people(
461                t, dt,
462                all_people[name],
463                all_sensors[name])
464
465        if (draw and with_graphes):
466            # coloring people according to their radius
467            colors = all_people[name]["xyrv"][:, 2]
468            # coloring people according to their destinations
469            # colors = np.zeros(all_people[name]["xyrv"].shape[0])
470            # for i,dest_name in enumerate(all_people[name]["destinations"]):
471            #     ind = np.where(all_people[name]["destinations"]==dest_name)[0]
472            #     colors[ind]=i
473            plot_people(100*idom+20, dom, all_people[name], contacts,
474                        colors, virtual_people=virtual_people[name], time=t,
475                        plot_people=plot_p, plot_contacts=plot_c,
476                        plot_paths=plot_pa, plot_velocities=plot_v,
477                        plot_desired_velocities=plot_vd, plot_sensors=plot_s,
478                        sensors=all_sensors[dom.name], savefig=True,
479                        filename=prefix+dom.name+'_fig_'
480                        + str(counter).zfill(6)+'.png')
481            plt.pause(0.05)
482
483    # Update people destinations
484    all_people = people_update_destination(all_people, domains, dom.pixel_size)
485
486    # Update previous velocities
487    for idom, name in enumerate(domains):
488        all_people[name]["Uold"] = all_people[name]["U"]
489
490    # Print the number of persons for each domain
491    for idom, name in enumerate(domains):
492        print("===> Domain ", name, " nb of persons = ",
493              all_people[name]["xyrv"].shape[0])
494
495    t += dt
496    cc += 1
497    counter += 1
498    if (cc >= drawper):
499        draw = True
500        cc = 0
501    else:
502        draw = False
503
504
505for idom, domain_name in enumerate(all_sensors):
506    print("===> Plot sensors of domain ", domain_name)
507    plot_sensors(100*idom+40, all_sensors[domain_name], t, savefig=True,
508                 filename=prefix+'sensor_'+str(i)+'_'+str(counter)+'.png')
509    plt.pause(0.05)
510
511plt.ioff()
512plt.show()
513sys.exit()

Granular model

The Granular model comes from crowd motion models of the granular type : each individual is identified to a hard disk of a prescribed size, subject to a non-overlapping constraint with their neighbors. The approach relies on a desired velocity for each individual (the velocity they would take if they were alone), and the global velocity field shall be defined as the closest to the desired one among all those feasible fields (fields which do not lead to overlapping of disks).

Reference : [MF2018] Chapter 4.

An example can be find in the directory

cromosim/examples/micro/granular

and can be launched with

python micro_granular.py --json input_room.json
python micro_granular.py --json input_event.json
python micro_granular.py --json input_stadium.json
python micro_granular.py --json input_shibuya_crossing.json
python micro_granular.py --json input_stairs.json
Evacuation of a room, visualization of trajectories

Circular track around a stadium

Evacuation of an exhibition hall: two groups of people with the same destination
Evacuation of an exhibition hall: sensors (green lines) results
Evacuation of an exhibition hall : Sensors (green lines) results

Shibuya crossing (Japan) : five groups of people and five different destinations

Two floors of a building: a group of people goes up and another goes down
Floor 1 on the left and 0 on the right
micro_granular.py
  1# Authors:
  2#     Sylvain Faure <sylvain.faure@universite-paris-saclay.fr>
  3#     Bertrand Maury <bertrand.maury@universite-paris-saclay.fr>
  4#
  5#     cromosim/examples/micro/granular/micro_granular.py
  6#     python micro_granular.py --json input.json
  7#
  8# License: GPL
  9
 10
 11import sys
 12import os
 13import json
 14from optparse import OptionParser
 15import numpy as np
 16import matplotlib.pyplot as plt
 17from matplotlib.patches import Circle, Ellipse, Rectangle, Polygon
 18from matplotlib.lines import Line2D
 19
 20from cromosim.domain import Domain
 21from cromosim.domain import Destination
 22from cromosim.micro import people_initialization, plot_people, plot_sensors
 23from cromosim.micro import find_duplicate_people, compute_contacts
 24from cromosim.micro import projection, move_people, people_update_destination
 25
 26plt.ion()
 27
 28"""
 29    python3 micro_granular.py --json input.json
 30"""
 31parser = OptionParser(usage="usage: %prog [options] filename",
 32                      version="%prog 1.0")
 33parser.add_option('--json', dest="jsonfilename", default="input.json",
 34                  type="string", action="store", help="Input json filename")
 35opt, remainder = parser.parse_args()
 36print("===> JSON filename = ", opt.jsonfilename)
 37with open(opt.jsonfilename) as json_file:
 38    try:
 39        input = json.load(json_file)
 40    except json.JSONDecodeError as msg:
 41        print(msg)
 42        print("Failed to load json file ", opt.jsonfilename)
 43        print("Check its content \
 44            (https://fr.wikipedia.org/wiki/JavaScript_Object_Notation)")
 45        sys.exit()
 46
 47"""
 48    Get parameters from json file :
 49    prefix: string
 50        Folder name to store the results
 51    with_graphes: bool
 52        true if all the graphes are shown and saved in png files,
 53        false otherwise
 54    seed: integer
 55        Random seed which can be used to reproduce a random selection
 56        if >0
 57    For each domain :
 58    |    name: string
 59    |        Domain name
 60    |    background: string
 61    |        Image file used as background
 62    |    px: float
 63    |        Pixel size in meters (also called space step)
 64    |    width: integer
 65    |        Domain width (equal to the width of the background image)
 66    |    height: integer
 67    |        Domain height (equal to the height of the background image)
 68    |    wall_colors: list
 69    |        rgb colors for walls
 70    |        [ [r,g,b],[r,g,b],... ]
 71    |    shape_lines: list
 72    |        Used to define the Matplotlib Polyline shapes,
 73    |        [
 74    |          {
 75    |             "xx": [x0,x1,x2,...],
 76    |             "yy": [y0,y1,y2,...],
 77    |             "linewidth": float,
 78    |             "outline_color": [r,g,b],
 79    |             "fill_color": [r,g,b]
 80    |          },...
 81    |        ]
 82    |    shape_circles: list
 83    |        Used to define the Matplotlib Circle shapes,
 84    |        [
 85    |           {
 86    |             "center_x": float,
 87    |             "center_y": float,
 88    |             "radius": float,
 89    |             "outline_color": [r,g,b],
 90    |             "fill_color": [r,g,b]
 91    |            },...
 92    |        ]
 93    |    shape_ellipses: list
 94    |        Used to define the Matplotlib Ellipse shapes,
 95    |        [
 96    |           {
 97    |             "center_x": float,
 98    |             "center_y": float,
 99    |             "width": float,
100    |             "height": float,
101    |             "angle_in_degrees_anti-clockwise": float (degre),
102    |             "outline_color": [r,g,b],
103    |             "fill_color": [r,g,b]
104    |            },...
105    |        ]
106    |    shape_rectangles: list
107    |        Used to define the Matplotlib Rectangle shapes,
108    |        [
109    |           {
110    |             "bottom_left_x": float,
111    |             "bottom_left_y": float,
112    |             "width": float,
113    |             "height": float,
114    |             "angle_in_degrees_anti-clockwise": float (degre),
115    |             "outline_color": [r,g,b],
116    |             "fill_color": [r,g,b]
117    |            },...
118    |        ]
119    |    shape_polygons: list
120    |        Used to define the Matplotlib Polygon shapes,
121    |        [
122    |           {
123    |             "xy": float,
124    |             "outline_color": [r,g,b],
125    |             "fill_color": [r,g,b]
126    |            },...
127    |        ]
128    |    destinations: list
129    |        Used to define the Destination objects,
130    |        [
131    |           {
132    |             "name": string,
133    |             "colors": [[r,g,b],...],
134    |             "excluded_colors": [[r,g,b],...],
135    |             "desired_velocity_from_color": [] or
136    |             [
137    |                {
138    |                   "color": [r,g,b],
139    |                   "desired_velocity": [ex,ey]
140    |                },...
141    |             ],
142    |             "velocity_scale": float,
143    |             "next_destination": null or string,
144    |             "next_domain": null or string,
145    |             "next_transit_box": null or [[x0,y0],...,[x3,y3]]
146    |            },...
147    |        ]
148    |--------------------
149    For each group of persons, required for the initialization process:
150    |    nb:
151    |        Number of people in the group
152    |    domain:
153    |        Name of the domain where people are located
154    |    radius_distribution:
155    |        Radius distribution used to create people
156    |        ["uniform",min,max] or ["normal",mean,sigma]
157    |    velocity_distribution:
158    |        Velocity distribution used to create people
159    |        ["uniform",min,max] or ["normal",mean,sigma]
160    |    box:
161    |        Boxe to randomly position people at initialization
162    |        [ [x0,y0],[x1,y1],...]
163    |    destination:
164    |        Initial destination for the group
165    |--------------------
166    For each sensor:
167    |    domain:
168    |        Name of the domain where the sensor is located
169    |    line:
170    |        Segment through which incoming and outgoing flows are measured
171    |        [x0,y0,x1,y1]
172    |--------------------
173    Tf: float
174        Final time
175    dt: float
176        Time step
177    drawper: integer
178        The results will be displayed every "drawper" iterations
179    projection_method: string
180        Name of the projection method : cvxopt(default),
181        mosek(a licence is needed) or uzawa
182    dmax: float
183        Maximum distance used to detect neighbors
184    dmin_people: float
185        Minimum distance allowed between individuals
186    dmin_walls: float
187        Minimum distance allowed between an individual and a wall
188    plot_people: boolean
189        If true, people are drawn
190    plot_contacts: boolean
191        If true, active contacts between people are drawn
192    plot_desired_velocities: boolean
193        If true, people desired velocities are drawn
194    plot_velocities: boolean
195        If true, people velocities are drawn
196    plot_sensors: boolean
197        If true, plot sensor lines on people graph and sensor data graph
198    plot_paths: boolean
199        If true, plot people paths
200"""
201
202prefix = input["prefix"]
203if not os.path.exists(prefix):
204    os.makedirs(prefix)
205seed = input["seed"]
206with_graphes = input["with_graphes"]
207json_domains = input["domains"]
208# print("===> JSON data used to build the domains : ",json_domains)
209json_people_init = input["people_init"]
210# print("===> JSON data used to create groups : ",json_people_init)
211json_sensors = input["sensors"]
212# print("===> JSON data used to create sensors : ",json_sensors)
213Tf = input["Tf"]
214dt = input["dt"]
215drawper = input["drawper"]
216projection_method = input["projection_method"]
217dmax = input["dmax"]
218dmin_people = input["dmin_people"]
219dmin_walls = input["dmin_walls"]
220plot_p = input["plot_people"]
221plot_c = input["plot_contacts"]
222plot_v = input["plot_velocities"]
223plot_vd = input["plot_desired_velocities"]
224plot_s = input["plot_sensors"]
225plot_pa = input["plot_paths"]
226print("===> Final time, Tf = ", Tf)
227print("===> Time step, dt = ", dt)
228print("===> To draw the results each drawper iterations, drawper = ", drawper)
229print("===> Maximal distance to find neighbors, dmax = ", dmax, ", example : 2*dt")
230print("===> Minimal distance between persons, dmin_people = ", dmin_people)
231print("===> Minimal distance between a person and a wall, dmin_walls = ", dmin_walls)
232
233"""
234    Build the Domain objects
235"""
236domains = {}
237
238for i, jdom in enumerate(json_domains):
239    jname = jdom["name"]
240    print("===> Build domain number ", i, " : ", jname)
241    jbg = jdom["background"]
242    jpx = jdom["px"]
243    jwidth = jdom["width"]
244    jheight = jdom["height"]
245    jwall_colors = jdom["wall_colors"]
246    if (jbg == ""):
247        dom = Domain(name=jname, pixel_size=jpx, width=jwidth,
248                     height=jheight, wall_colors=jwall_colors)
249    else:
250        dom = Domain(name=jname, background=jbg, pixel_size=jpx,
251                     wall_colors=jwall_colors)
252    # To add lines : Line2D(xdata, ydata, linewidth)
253    for sl in jdom["shape_lines"]:
254        line = Line2D(sl["xx"], sl["yy"], linewidth=sl["linewidth"])
255        dom.add_shape(line, outline_color=sl["outline_color"],
256                      fill_color=sl["fill_color"])
257    # To add circles : Circle( (center_x,center_y), radius )
258    for sc in jdom["shape_circles"]:
259        circle = Circle((sc["center_x"], sc["center_y"]), sc["radius"])
260        dom.add_shape(circle, outline_color=sc["outline_color"],
261                      fill_color=sc["fill_color"])
262    # To add ellipses : Ellipse( (center_x,center_y), width, height,
263    #                            angle_in_degrees_anti-clockwise )
264    for se in jdom["shape_ellipses"]:
265        ellipse = Ellipse((se["center_x"], se["center_y"]),
266                          se["width"], se["height"],
267                          se["angle_in_degrees_anti-clockwise"])
268        dom.add_shape(ellipse, outline_color=se["outline_color"],
269                      fill_color=se["fill_color"])
270    # To add rectangles : Rectangle( (bottom_left_x,bottom_left_y),
271    #                   width, height, angle_in_degrees_anti-clockwise )
272    for sr in jdom["shape_rectangles"]:
273        rectangle = Rectangle((sr["bottom_left_x"], sr["bottom_left_y"]),
274                              sr["width"], sr["height"],
275                              sr["angle_in_degrees_anti-clockwise"])
276        dom.add_shape(rectangle, outline_color=sr["outline_color"],
277                      fill_color=sr["fill_color"])
278    # To add polygons : Polygon( [[x0,y0],[x1,y1],...] )
279    for spo in jdom["shape_polygons"]:
280        polygon = Polygon(spo["xy"])
281        dom.add_shape(polygon, outline_color=spo["outline_color"],
282                      fill_color=spo["fill_color"])
283    # To build the domain : background + shapes
284    dom.build_domain()
285    # To add all the available destinations
286    for j, dd in enumerate(jdom["destinations"]):
287        desired_velocity_from_color = []
288        for gg in dd["desired_velocity_from_color"]:
289            desired_velocity_from_color.append(
290                np.concatenate((gg["color"], gg["desired_velocity"])))
291        dest = Destination(name=dd["name"], colors=dd["colors"],
292                           excluded_colors=dd["excluded_colors"],
293                           desired_velocity_from_color=desired_velocity_from_color,
294                           velocity_scale=dd["velocity_scale"],
295                           next_destination=dd["next_destination"],
296                           next_domain=dd["next_domain"],
297                           next_transit_box=dd["next_transit_box"])
298        print("===> Destination : ", dest)
299        dom.add_destination(dest)
300        if (with_graphes):
301            dom.plot_desired_velocity(dd["name"], id=100*i+10+j, step=20)
302
303    print("===> Domain : ", dom)
304    if (with_graphes):
305        dom.plot(id=100*i)
306        dom.plot_wall_dist(id=100*i+1, step=20)
307
308    domains[dom.name] = dom
309
310print("===> All domains = ", domains)
311
312
313"""
314    To create the sensors to measure the pedestrian flows
315"""
316
317all_sensors = {}
318for domain_name in domains:
319    all_sensors[domain_name] = []
320for s in json_sensors:
321    s["id"] = []
322    s["times"] = []
323    s["xy"] = []
324    s["dir"] = []
325    all_sensors[s["domain"]].append(s)
326    # print("===> All sensors = ",all_sensors)
327
328"""
329    Initialization
330"""
331
332# Current time
333t = 0.0
334counter = 0
335
336# Initialize people
337all_people = {}
338for i, peopledom in enumerate(json_people_init):
339    dom = domains[peopledom["domain"]]
340    groups = peopledom["groups"]
341    print("===> Group number ", i, ", domain = ", peopledom["domain"])
342    people = people_initialization(dom, groups, dt,
343                                   dmin_people=dmin_people, dmin_walls=dmin_walls, seed=seed,
344                                   itermax=10, projection_method=projection_method, verbose=True)
345    II, JJ, Vd = dom.people_desired_velocity(people["xyrv"], people["destinations"])
346    people["Vd"] = Vd
347    for ip, pid in enumerate(people["id"]):
348        people["paths"][pid] = people["xyrv"][ip, :2]
349    contacts = None
350    if (with_graphes):
351        colors = people["xyrv"][:, 2]
352        plot_people(100*i+20, dom, people, contacts, colors, time=t,
353                    plot_people=plot_p, plot_contacts=plot_c,
354                    plot_velocities=plot_v, plot_desired_velocities=plot_vd,
355                    plot_sensors=plot_s, sensors=all_sensors[dom.name],
356                    savefig=True, filename=prefix+dom.name+'_fig_' +
357                    str(counter).zfill(6)+'.png')
358    all_people[peopledom["domain"]] = people
359# print("===> All people = ",all_people)
360
361"""
362    Main loop
363"""
364
365cc = 0
366draw = True
367
368while (t < Tf):
369
370    print("\n===> Time = "+str(t))
371
372    # Compute people desired velocity
373    for idom, name in enumerate(domains):
374        print("===> Compute desired velocity for domain ", name)
375        dom = domains[name]
376        people = all_people[name]
377        II, JJ, Vd = dom.people_desired_velocity(
378            people["xyrv"],
379            people["destinations"])
380        people["Vd"] = Vd
381        people["I"] = II
382        people["J"] = JJ
383
384    # Look at if there are people in the transit boxes
385    print("===> Find people who have to be duplicated")
386    virtual_people = find_duplicate_people(all_people, domains)
387    # print("     virtual_people : ",virtual_people)
388
389    # Projection
390    for idom, name in enumerate(domains):
391        print("===> Projection step for domain ", name)
392        dom = domains[name]
393        people = all_people[name]
394
395        try:
396            xyrv = np.concatenate(
397                (people["xyrv"], virtual_people[name]["xyrv"]))
398            Vd = np.concatenate(
399                (people["Vd"], virtual_people[name]["Vd"]))
400        except:
401            xyrv = people["xyrv"]
402            Vd = people["Vd"]
403
404        if (xyrv.shape[0] > 0):
405
406            if (np.unique(xyrv, axis=0).shape[0] != xyrv.shape[0]):
407                print("===> ERROR : There are two identical lines \
408                    in the")
409                print("             array xyrv used to determine the \
410                    contacts between")
411                print("             individuals and this is not normal.")
412                sys.exit()
413
414            contacts = compute_contacts(dom, xyrv, dmax)
415            print("     Number of contacts: ", contacts.shape[0])
416            info, B, U, L, P = projection(
417                dt, xyrv,
418                contacts, Vd,
419                dmin_people=dmin_people,
420                dmin_walls=dmin_walls,
421                method=projection_method,
422                log=True)
423            nn = people["xyrv"].shape[0]
424            all_people[name]["U"] = U[:nn, :]
425            virtual_people[name]["U"] = U[nn:, :]
426            all_people[name], all_sensors[name] = move_people(
427                t, dt,
428                all_people[name],
429                all_sensors[name])
430
431        if (draw and with_graphes):
432            # coloring people according to their radius
433            colors = all_people[name]["xyrv"][:, 2]
434            # coloring people according to their destinations
435            # colors = np.zeros(all_people[name]["xyrv"].shape[0])
436            # for i,dest_name in enumerate(list(dom.destinations.keys())):
437            #    ind = np.where(all_people[name]["destinations"]==dest_name)[0]
438            #    if (ind.shape[0]>0):
439            #        colors[ind]=i
440            plot_people(100*idom+20, dom, all_people[name], contacts,
441                        colors, virtual_people=virtual_people[name], time=t,
442                        plot_people=plot_p, plot_contacts=plot_c,
443                        plot_paths=plot_pa, plot_velocities=plot_v,
444                        plot_desired_velocities=plot_vd, plot_sensors=plot_s,
445                        sensors=all_sensors[dom.name], savefig=True,
446                        filename=prefix+dom.name+'_fig_'
447                        + str(counter).zfill(6)+'.png')
448            plt.pause(0.05)
449
450    # Update people destinations
451    all_people = people_update_destination(all_people, domains, dom.pixel_size)
452
453    # Print the number of persons for each domain
454    for idom, name in enumerate(domains):
455        print("===> Domain ", name, " nb of persons = ",
456              all_people[name]["xyrv"].shape[0])
457
458    t += dt
459    cc += 1
460    counter += 1
461    if (cc >= drawper):
462        draw = True
463        cc = 0
464    else:
465        draw = False
466
467for idom, domain_name in enumerate(all_sensors):
468    print("===> Plot sensors of domain ", domain_name)
469    plot_sensors(100*idom+40, all_sensors[domain_name], t, savefig=True,
470                 filename=prefix+'sensor_'+str(i)+'_'+str(counter)+'.png')
471    plt.pause(0.05)
472
473plt.ioff()
474plt.show()
475sys.exit()