├── notes.md ├── README.md ├── reports ├── media │ ├── agent_forces.png │ ├── sampleOutput.png │ ├── room_with_barrier.png │ ├── rooms_side_by_side.png │ ├── Graph1_doorwidth_time.png │ ├── room_without_barrier.png │ ├── Graph2_doorwidth_ratio.png │ ├── sample_plot_evacuation_vs_desired_velocity.png │ └── agent_forces.svg ├── data │ ├── experimentdataNoBarrier1212.csv │ ├── experimentdata2.csv │ └── experimentdataNoBarrier.csv ├── draft_review_adam.md ├── draft_review_changjun.md ├── project_proposal.md ├── draft_final_report.md └── final_report.md ├── code ├── exportcsv.py ├── test.py ├── Point.py ├── simulation.py ├── Agent.py ├── Environment.py └── thinkplot.py └── .gitignore /notes.md: -------------------------------------------------------------------------------- 1 | # Libraries 2 | Using pygame for visualization 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Evacuation 2 | Agent based modeling of panicked crowds 3 | -------------------------------------------------------------------------------- /reports/media/agent_forces.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/labseven/Evacuation/HEAD/reports/media/agent_forces.png -------------------------------------------------------------------------------- /reports/media/sampleOutput.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/labseven/Evacuation/HEAD/reports/media/sampleOutput.png -------------------------------------------------------------------------------- /reports/media/room_with_barrier.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/labseven/Evacuation/HEAD/reports/media/room_with_barrier.png -------------------------------------------------------------------------------- /reports/media/rooms_side_by_side.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/labseven/Evacuation/HEAD/reports/media/rooms_side_by_side.png -------------------------------------------------------------------------------- /reports/media/Graph1_doorwidth_time.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/labseven/Evacuation/HEAD/reports/media/Graph1_doorwidth_time.png -------------------------------------------------------------------------------- /reports/media/room_without_barrier.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/labseven/Evacuation/HEAD/reports/media/room_without_barrier.png -------------------------------------------------------------------------------- /reports/media/Graph2_doorwidth_ratio.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/labseven/Evacuation/HEAD/reports/media/Graph2_doorwidth_ratio.png -------------------------------------------------------------------------------- /reports/media/sample_plot_evacuation_vs_desired_velocity.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/labseven/Evacuation/HEAD/reports/media/sample_plot_evacuation_vs_desired_velocity.png -------------------------------------------------------------------------------- /code/exportcsv.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | import pickle 3 | import numpy as np 4 | 5 | with open("experimentdataNoBarrier.pk", "rb") as infile: 6 | infile.seek(0) 7 | results = pickle.load(infile) 8 | 9 | print(results) 10 | 11 | results.to_csv("experimentdataNoBarrier.csv") 12 | 13 | print("done") 14 | -------------------------------------------------------------------------------- /reports/data/experimentdataNoBarrier1212.csv: -------------------------------------------------------------------------------- 1 | ,roomHeight,roomWidth,barrier,barrierRadius,barrierPos,doorWidth,numAgents,agentMass,desiredSpeed,escapeTime 2 | 0,20.0,10.0,,,,1.714,49.0,80.0,3.0,1661.0 3 | 1,20.0,10.0,,,,1.96,49.0,80.0,3.0,1524.0 4 | 2,20.0,10.0,,,,2.24,49.0,80.0,3.0,1016.0 5 | 3,20.0,10.0,,,,2.92,49.0,80.0,3.0,761.0 6 | 4,20.0,10.0,,,,3.347,49.0,80.0,3.0,745.0 7 | -------------------------------------------------------------------------------- /reports/draft_review_adam.md: -------------------------------------------------------------------------------- 1 | # Draft Review: Power Laws and Wars 2 | 3 | ### Q: 4 | 5 | Nick and Seungin are studying why war severity follows a power law. There is no good model explaining why this phenomenon happens, so this paper tries to make a model that explains it. 6 | 7 | 8 | ### M: 9 | They simulate wars with a model that models "transitions between equilibria." 10 | I do not know what an equilibrium based model is. 11 | 12 | In the past, a forest fire model was used, but it has too many mechanism differences to be useful. Iin what ways is the equilibrium model different? 13 | 14 | There is also some tax and technological shift being modeled, that I do not understand how or why. 15 | 16 | I can see a spacial model with states controlling land. 17 | 18 | 19 | ### R: 20 | Graph placeholders look okay, hopefully the simulation has similar output :) 21 | 22 | ### I: 23 | In process... 24 | 25 | 26 | ### Overall: 27 | The outline looks fine, hopefully there will be more content by next week. 28 | -------------------------------------------------------------------------------- /code/test.py: -------------------------------------------------------------------------------- 1 | from Environment import * 2 | from Agent import * 3 | import random 4 | import timeit 5 | 6 | walls = [] 7 | walls.append(Wall('circle', **{ 'center': Point(600,600), 'radius': 50 })) 8 | walls.append(Wall('line', **{ 'p1': Point(900,400), 'p2': Point(950, 500) })) 9 | 10 | goals = [] 11 | goals.append(Goal('line', **{ 'p1': Point(300,100), 'p2': Point(300,300) })) 12 | 13 | instruments = [] 14 | instruments.append(ReachedGoal()) 15 | 16 | agents = [] 17 | for _ in range(10): 18 | # Agent(size, mass, pos, goal, desiredSpeed = 4)) 19 | size = random.randint(10,20) 20 | mass = 50 21 | pos = Point(random.randint(10,990), random.randint(10,990)) 22 | goal = goals[0] 23 | 24 | agents.append(Agent(size, mass, pos, goal)) 25 | 26 | env = Environment(100, walls, goals, agents, {}, instruments) 27 | viewer = EnvironmentViewer(env) 28 | 29 | viewer.draw() 30 | env.step() 31 | 32 | for _ in range(50): 33 | env.step() 34 | 35 | env.plot(0) 36 | 37 | while True: 38 | pass 39 | -------------------------------------------------------------------------------- /code/Point.py: -------------------------------------------------------------------------------- 1 | from math import sqrt 2 | 3 | 4 | class Point: 5 | def __init__(self, x, y): 6 | self.x = x 7 | self.y = y 8 | 9 | def __str__(self): 10 | return "Point({}, {})".format(self.x, self.y) 11 | 12 | def __sub__(self, other): 13 | return Point(self.x - other.x, self.y - other.y) 14 | 15 | def __add__(self, other): 16 | return Point(self.x + other.x, self.y + other.y) 17 | 18 | def __mul__(self, scalar): 19 | return Point(self.x * scalar, self.y * scalar) 20 | 21 | def __rmul__(self, scalar): 22 | return Point(self.x * scalar, self.y * scalar) 23 | 24 | def __neg__(self): 25 | return Point(-self.x, -self.y) 26 | 27 | def __truediv__(self, scalar): 28 | return Point(self.x / scalar, self.y / scalar) 29 | 30 | @property 31 | def tuple(self): 32 | return (self.x, self.y) 33 | 34 | @property 35 | def pygame(self): 36 | return (int(self.x*50), int(self.y*50)) 37 | 38 | def mag(self): 39 | return sqrt((self.x ** 2) + (self.y ** 2)) 40 | 41 | def norm(self): 42 | return self / self.mag() 43 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | MANIFEST 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | 49 | # Translations 50 | *.mo 51 | *.pot 52 | 53 | # Django stuff: 54 | *.log 55 | .static_storage/ 56 | .media/ 57 | local_settings.py 58 | 59 | # Flask stuff: 60 | instance/ 61 | .webassets-cache 62 | 63 | # Scrapy stuff: 64 | .scrapy 65 | 66 | # Sphinx documentation 67 | docs/_build/ 68 | 69 | # PyBuilder 70 | target/ 71 | 72 | # Jupyter Notebook 73 | .ipynb_checkpoints 74 | 75 | # pyenv 76 | .python-version 77 | 78 | # celery beat schedule file 79 | celerybeat-schedule 80 | 81 | # SageMath parsed files 82 | *.sage.py 83 | 84 | # Environments 85 | .env 86 | .venv 87 | env/ 88 | venv/ 89 | ENV/ 90 | env.bak/ 91 | venv.bak/ 92 | 93 | # Spyder project settings 94 | .spyderproject 95 | .spyproject 96 | 97 | # Rope project settings 98 | .ropeproject 99 | 100 | # mkdocs documentation 101 | /site 102 | 103 | # mypy 104 | .mypy_cache/ 105 | -------------------------------------------------------------------------------- /reports/data/experimentdata2.csv: -------------------------------------------------------------------------------- 1 | barrierRadius,barrierPos,doorWidth,numAgents,agentMass,desiredSpeed,escapeTime 2 | ,,1.5,49,80,3,2639 3 | 0.5,"Point(-1.5, 0.015)",1.5,49,80,3,2661 4 | 0.5,"Point(-2.0, 0.015)",1.5,49,80,3,2492 5 | 1.077217345,"Point(-3.2316520350478255, 0.015)",1.5,49,80,3,2910 6 | 1.077217345,"Point(-4.308869380063768, 0.015)",1.5,49,80,3,2661 7 | 2.320794417,"Point(-6.962383250419167, 0.015)",1.5,49,80,3,3281 8 | 2.320794417,"Point(-9.283177667225557, 0.015)",1.5,49,80,3,3177 9 | 5,"Point(-15.000000000000004, 0.015)",1.5,49,80,3,3305 10 | 5,"Point(-20.000000000000004, 0.015)",1.5,49,80,3,2191 11 | ,,,,,, 12 | ,,,,,, 13 | 0.5,"Point(-1.5, 0.017147029594748057)",1.714702959,49,80,3,1692 14 | 0.5,"Point(-2.0, 0.017147029594748057)",1.714702959,49,80,3,2054 15 | 1.077217345,"Point(-3.2316520350478255, 0.017147029594748057)",1.714702959,49,80,3,2031 16 | 1.077217345,"Point(-4.308869380063768, 0.017147029594748057)",1.714702959,49,80,3,2259 17 | 2.320794417,"Point(-6.962383250419167, 0.017147029594748057)",1.714702959,49,80,3,2108 18 | 2.320794417,"Point(-9.283177667225557, 0.017147029594748057)",1.714702959,49,80,3,2353 19 | 5,"Point(-15.000000000000004, 0.017147029594748057)",1.714702959,49,80,3,1694 20 | 5,"Point(-20.000000000000004, 0.017147029594748057)",1.714702959,49,80,3,1689 21 | ,,,,,, 22 | ,,,,,, 23 | 0.5,"Point(-1.5, 0.019601374928211047)",1.960137493,49,80,3,1497 24 | 0.5,"Point(-2.0, 0.019601374928211047)",1.960137493,49,80,3,1393 25 | 1.077217345,"Point(-3.2316520350478255, 0.019601374928211047)",1.960137493,49,80,3,1684 26 | 1.077217345,"Point(-4.308869380063768, 0.019601374928211047)",1.960137493,49,80,3,1548 27 | 2.320794417,"Point(-6.962383250419167, 0.019601374928211047)",1.960137493,49,80,3,1610 28 | 2.320794417,"Point(-9.283177667225557, 0.019601374928211047)",1.960137493,49,80,3,1551 29 | 5,"Point(-15.000000000000004, 0.019601374928211047)",1.960137493,49,80,3,1353 30 | 5,"Point(-20.000000000000004, 0.019601374928211047)",1.960137493,49,80,3,1354 31 | ,,,,,, 32 | 0,,3,49,80,3,717 33 | 0.5,"Point(-1.5, 0.0292805648807772)",2.928056488,49,80,3,994 34 | 0.5,"Point(-2.0, 0.0292805648807772)",2.928056488,49,80,3,848 35 | 1.077217345,"Point(-3.2316520350478255, 0.0292805648807772)",2.928056488,49,80,3,837 36 | 1.077217345,"Point(-4.308869380063768, 0.0292805648807772)",2.928056488,49,80,3,828 37 | 2.320794417,"Point(-6.962383250419167, 0.0292805648807772)",2.928056488,49,80,3,747 38 | 2.320794417,"Point(-9.283177667225557, 0.0292805648807772)",2.928056488,49,80,3,756 39 | 5,"Point(-15.000000000000004, 0.0292805648807772)",2.928056488,49,80,3,683 40 | 5,"Point(-20.000000000000004, 0.0292805648807772)",2.928056488,49,80,3,797 41 | -------------------------------------------------------------------------------- /reports/draft_review_changjun.md: -------------------------------------------------------------------------------- 1 | ## Review of *Agent-Based Simulation of Wars and State Formation* 2 | 3 | **Changjun Lim** 4 | 5 | 6 | ### Question 7 | What is your understanding of the experiment the team is replicating? What question does it answer? How clear is the team's explanation? 8 | 9 | The purpose of Richardson's paper was to analyze the relation between the frequency and severity of wars and find the power law distribution. But there is still no principals to explain it well. Cederman attempts to make the agent based model to produce the power-law graph. 10 | Nick and Seungin implement the repllication of Cederman's model and check whether it follows power-law graph. The question they try to answer and the explanation about the background of the project is sufficiently clear to understand it. 11 | 12 | 13 | ### Methodology 14 | Do you understand the methodology? Does it make sense for the question? Are there limitations you see that the team did not address? 15 | 16 | They built the agent based model similar to GioSim, Cederman's model. I think it is a good approach to explain how wars start and spread. But it is slightly unclear how the war starts in the model and what is the function of tax and technology. 17 | 18 | 1) I do not fully understand what is contextual activation. How does it affect the war decision? 19 | 20 | 2) Why are tax and advancements in technology needed in the model? How do they influence wars? 21 | 22 | 3) What is the meaning of tax threshold? Is it the property of the state or the cell? 23 | 24 | 4) How is the severity of wars determined? 25 | 26 | 27 | ### Results 28 | Do you understand what the results are (not yet considering their interpretation)? If they are presented graphically, are the visualizations effective? Do all figures have labels on the axes and captions? 29 | 30 | The cited graphical result makes sense. It is easily understand the meaning of the graph(power-law). But it is uncertain which situation happens in maps. And I do not catch the severity of wars.(Is it the number of dead in a quarrel?) 31 | 32 | ### Interpretation 33 | Does the draft report interpret the results as an answer to the motivating question? Does the argument hold water? 34 | 35 | The replication is in progress, so there is no interpretation yet. 36 | 37 | ### Replication 38 | Are the results in the report consistent with the results from the original paper? 39 | 40 | The replication is not finished yet. 41 | 42 | ### Extension 43 | Does the report explain an extension to the original experiment clearly? Is it a sensible extension in the sense that it has the potential to answer an interesting question that the original experiment did not answer? 44 | 45 | I am not sure you mention about the extension from the replication of the original paper. 46 | 47 | ### Progress 48 | Is the team roughly where they should be at this point, with a replication that is substantially complete and an extension that is clearly defined and either complete or nearly so? 49 | 50 | The replication is not done yet, but I think the direction of the project is good. 51 | 52 | ### Presentation 53 | Is the report written in clear, concise, correct language? Is it consistent with the audience and goals of the report? Does it violate any of the recommendations in my style guide? 54 | 55 | Everything looks good to me. The goals and content of the report are consistent. 56 | 57 | ### Mechanics 58 | Is the report in the right directory with the right file name? Is it formatted professionally in Markdown? Does it include a meaningful title and the full names of the authors? Is the bibliography in an acceptable style? 59 | 60 | The format of bibliographies is not consistent. It is better to use the same format when you cite papers. The rest looks good. -------------------------------------------------------------------------------- /code/simulation.py: -------------------------------------------------------------------------------- 1 | from Environment import * 2 | from Agent import * 3 | from Point import Point 4 | 5 | # import pygame 6 | # from pygame.locals import * 7 | # from pygame.color import * 8 | import thinkplot 9 | import random 10 | import numpy as np 11 | 12 | from datetime import datetime 13 | import pickle 14 | import pandas as pd 15 | 16 | 17 | """ 18 | def runSimulation(roomHeight=10, 19 | roomWidth=8, 20 | barrier={ 'radius': .3, 'pos': Point(-1,0)}, # pos is relative to door center 21 | doorWidth=1.5, 22 | numAgents=50, 23 | agentMass=80, 24 | desiredSpeed=4, 25 | view=False): 26 | """ 27 | def importData(): 28 | with open("experimentData.pk", "rb") as infile: 29 | results = pickle.load(infile) 30 | return results 31 | 32 | def saveData(): 33 | with open("experimentData.pk", "wb") as outfile: 34 | pickle.dump(results, outfile) 35 | 36 | def logscalelinspace(start, stop, num=20, dtype=np.float): 37 | logStart = np.log10(start) 38 | logStop = np.log10(stop) 39 | return np.logspace(logStart, logStop, num=num, dtype=dtype) 40 | 41 | def testDBExists(simulation, df): 42 | truth = df['roomHeight'] == simulation['roomHeight'] 43 | 44 | for x in simulation: 45 | truth = truth & df[x] == simulation[x] 46 | 47 | return truth.any() 48 | 49 | 50 | try: 51 | results = importData() 52 | except FileNotFoundError: 53 | columns = ["roomHeight", "roomWidth", "barrier", "barrierRadius", "barrierPos", "doorWidth", "numAgents", "agentMass", "desiredSpeed", "escapeTime"] 54 | results = pd.DataFrame(columns=columns) 55 | 56 | 57 | settings = { 58 | "roomHeight": 20, 59 | "roomWidth": 10, 60 | "barrier": { 61 | 'radius': logscalelinspace(0.5, 5, num=4), 62 | 'xOffset': np.array([3,4]), 63 | 'yOffset': logscalelinspace(0.01,1,num=3) 64 | }, 65 | "doorWidth": logscalelinspace(1.5, 5, num=10), 66 | "numAgents": logscalelinspace(50, 500, num=10, dtype=np.int), 67 | "agentMass": 80, 68 | "desiredSpeed": logscalelinspace(3, 11, num=10) 69 | } 70 | 71 | 72 | roomHeight = settings['roomHeight'] 73 | roomWidth = settings['roomWidth'] 74 | agentMass = settings['agentMass'] 75 | 76 | for numAgents in settings['numAgents']: 77 | for doorWidth in settings['doorWidth']: 78 | for radius in settings['barrier']['radius']: 79 | for xOffset in settings['barrier']['xOffset']: 80 | for yOffset in settings['barrier']['yOffset']: 81 | for desiredSpeed in settings['desiredSpeed']: 82 | barrier = { 83 | 'radius': radius, 84 | 'pos': Point(-(xOffset * radius), yOffset*doorWidth) 85 | } 86 | 87 | 88 | simulation_data = { 89 | "roomHeight": roomHeight, 90 | "roomWidth": roomWidth, 91 | "barrier": barrier, 92 | "doorWidth": doorWidth, 93 | "numAgents": numAgents, 94 | "agentMass": agentMass, 95 | "desiredSpeed": desiredSpeed 96 | } 97 | print("running", simulation_data) 98 | 99 | escapeTime = len(runSimulation( 100 | roomHeight=roomHeight, 101 | roomWidth=roomWidth, 102 | barrier=barrier, 103 | doorWidth=doorWidth, 104 | numAgents=numAgents, 105 | agentMass=agentMass, 106 | desiredSpeed=desiredSpeed, 107 | view=False 108 | )) 109 | 110 | simulation_data["escapeTime"] = escapeTime 111 | simulation_data["barrierRadius"] = barrier['radius'] 112 | simulation_data["barrierPos"] = str(barrier['pos']) 113 | 114 | results = results.append(simulation_data, ignore_index=True) 115 | saveData() 116 | 117 | print(simulation_data) 118 | -------------------------------------------------------------------------------- /reports/data/experimentdataNoBarrier.csv: -------------------------------------------------------------------------------- 1 | ,roomHeight,roomWidth,barrier,barrierRadius,barrierPos,doorWidth,numAgents,agentMass,desiredSpeed,escapeTime 2 | 0,20.0,10.0,,,,1.5,49.0,80.0,3.0,2639.0 3 | 1,20.0,10.0,,,,1.5,49.0,80.0,3.465916378175619,2674.0 4 | 2,20.0,10.0,,,,1.5,49.0,80.0,4.004192113501999,2435.0 5 | 3,20.0,10.0,,,,1.5,49.0,80.0,4.626065009182741,3985.0 6 | 4,20.0,10.0,,,,1.5,49.0,80.0,5.344518160610536,1737.0 7 | 5,20.0,10.0,,,,1.5,49.0,80.0,6.174551008772363,1651.0 8 | 6,20.0,10.0,,,,1.5,49.0,80.0,7.133492489728305,1486.0 9 | 7,20.0,10.0,,,,1.5,49.0,80.0,8.241362817914036,1331.0 10 | 8,20.0,10.0,,,,1.5,49.0,80.0,9.521291456365278,1356.0 11 | 9,20.0,10.0,,,,1.5,49.0,80.0,3.0,2927.0 12 | 10,20.0,10.0,,,,1.5,49.0,80.0,3.707792751067341,2934.0 13 | 11,20.0,10.0,,,,1.5,49.0,80.0,4.58257569495584,2064.0 14 | 12,20.0,10.0,,,,1.5,49.0,80.0,5.663746980991548,2166.0 15 | 13,20.0,10.0,,,,1.5,49.0,80.0,7.0,1641.0 16 | 14,20.0,10.0,,,,2.121320343559643,49.0,80.0,3.0,1612.0 17 | 15,20.0,10.0,,,,2.121320343559643,49.0,80.0,3.707792751067341,1211.0 18 | 16,20.0,10.0,,,,2.121320343559643,49.0,80.0,4.58257569495584,906.0 19 | 17,20.0,10.0,,,,2.121320343559643,49.0,80.0,5.663746980991548,861.0 20 | 18,20.0,10.0,,,,2.121320343559643,49.0,80.0,7.0,732.0 21 | 19,20.0,10.0,,,,3.0,49.0,80.0,3.0,717.0 22 | 20,20.0,10.0,,,,3.0,49.0,80.0,3.707792751067341,717.0 23 | 21,20.0,10.0,,,,3.0,49.0,80.0,4.58257569495584,525.0 24 | 22,20.0,10.0,,,,3.0,49.0,80.0,5.663746980991548,506.0 25 | 23,20.0,10.0,,,,3.0,49.0,80.0,7.0,489.0 26 | 24,20.0,10.0,,,,1.5,84.0,80.0,3.0,4218.0 27 | 25,20.0,10.0,,,,1.5,84.0,80.0,3.707792751067341,4507.0 28 | 26,20.0,10.0,,,,1.5,84.0,80.0,4.58257569495584,3239.0 29 | 27,20.0,10.0,,,,1.5,84.0,80.0,5.663746980991548,3147.0 30 | 28,20.0,10.0,,,,1.5,84.0,80.0,7.0,3284.0 31 | 29,20.0,10.0,,,,2.121320343559643,84.0,80.0,3.0,2058.0 32 | 30,20.0,10.0,,,,2.121320343559643,84.0,80.0,3.707792751067341,2084.0 33 | 31,20.0,10.0,,,,2.121320343559643,84.0,80.0,4.58257569495584,1385.0 34 | 32,20.0,10.0,,,,2.121320343559643,84.0,80.0,5.663746980991548,1158.0 35 | 33,20.0,10.0,,,,2.121320343559643,84.0,80.0,7.0,1202.0 36 | 34,20.0,10.0,,,,3.0,84.0,80.0,3.0,1091.0 37 | 35,20.0,10.0,,,,3.0,84.0,80.0,3.707792751067341,927.0 38 | 36,20.0,10.0,,,,3.0,84.0,80.0,4.58257569495584,784.0 39 | 37,20.0,10.0,,,,3.0,84.0,80.0,5.663746980991548,726.0 40 | 38,20.0,10.0,,,,3.0,84.0,80.0,7.0,599.0 41 | 39,20.0,10.0,,,,1.5,141.0,80.0,3.0,6000.0 42 | 40,20.0,10.0,,,,1.5,141.0,80.0,3.707792751067341,6000.0 43 | 41,20.0,10.0,,,,1.5,141.0,80.0,4.58257569495584,5716.0 44 | 42,20.0,10.0,,,,1.5,141.0,80.0,5.663746980991548,4916.0 45 | 43,20.0,10.0,,,,1.5,141.0,80.0,7.0,4789.0 46 | 44,20.0,10.0,,,,2.121320343559643,141.0,80.0,3.0,3557.0 47 | 45,20.0,10.0,,,,2.121320343559643,141.0,80.0,3.707792751067341,3034.0 48 | 46,20.0,10.0,,,,2.121320343559643,141.0,80.0,4.58257569495584,2530.0 49 | 47,20.0,10.0,,,,2.121320343559643,141.0,80.0,5.663746980991548,1849.0 50 | 48,20.0,10.0,,,,2.121320343559643,141.0,80.0,7.0,1819.0 51 | 49,20.0,10.0,,,,3.0,141.0,80.0,3.0,1785.0 52 | 50,20.0,10.0,,,,3.0,141.0,80.0,3.707792751067341,1479.0 53 | 51,20.0,10.0,,,,3.0,141.0,80.0,4.58257569495584,1327.0 54 | 52,20.0,10.0,,,,3.0,141.0,80.0,5.663746980991548,1093.0 55 | 53,20.0,10.0,,,,3.0,141.0,80.0,7.0,823.0 56 | 54,20.0,10.0,,,,1.5,237.0,80.0,3.0,6000.0 57 | 55,20.0,10.0,,,,1.5,237.0,80.0,3.707792751067341,6000.0 58 | 56,20.0,10.0,,,,1.5,237.0,80.0,4.58257569495584,6000.0 59 | 57,20.0,10.0,,,,1.5,237.0,80.0,5.663746980991548,6000.0 60 | 58,20.0,10.0,,,,1.5,237.0,80.0,7.0,5649.0 61 | 59,20.0,10.0,,,,2.121320343559643,237.0,80.0,3.0,4934.0 62 | 60,20.0,10.0,,,,2.121320343559643,237.0,80.0,3.707792751067341,3952.0 63 | 61,20.0,10.0,,,,2.121320343559643,237.0,80.0,4.58257569495584,3538.0 64 | 62,20.0,10.0,,,,2.121320343559643,237.0,80.0,5.663746980991548,2865.0 65 | 63,20.0,10.0,,,,2.121320343559643,237.0,80.0,7.0,2157.0 66 | 64,20.0,10.0,,,,3.0,237.0,80.0,3.0,2449.0 67 | 65,20.0,10.0,,,,3.0,237.0,80.0,3.707792751067341,2049.0 68 | 66,20.0,10.0,,,,3.0,237.0,80.0,4.58257569495584,1674.0 69 | 67,20.0,10.0,,,,3.0,237.0,80.0,5.663746980991548,1441.0 70 | 68,20.0,10.0,,,,3.0,237.0,80.0,7.0,1631.0 71 | 69,20.0,10.0,,,,1.5,400.0,80.0,3.0,6000.0 72 | 70,20.0,10.0,,,,1.5,400.0,80.0,3.707792751067341,6000.0 73 | 71,20.0,10.0,,,,1.5,400.0,80.0,4.58257569495584,6000.0 74 | 72,20.0,10.0,,,,1.5,400.0,80.0,5.663746980991548,6000.0 75 | 73,20.0,10.0,,,,1.5,400.0,80.0,7.0,6000.0 76 | 74,20.0,10.0,,,,2.121320343559643,400.0,80.0,3.0,6000.0 77 | 75,20.0,10.0,,,,2.121320343559643,400.0,80.0,3.707792751067341,6000.0 78 | 76,20.0,10.0,,,,2.121320343559643,400.0,80.0,4.58257569495584,4675.0 79 | 77,20.0,10.0,,,,2.121320343559643,400.0,80.0,5.663746980991548,3792.0 80 | 78,20.0,10.0,,,,2.121320343559643,400.0,80.0,7.0,3329.0 81 | 79,20.0,10.0,,,,3.0,400.0,80.0,3.0,3377.0 82 | 80,20.0,10.0,,,,3.0,400.0,80.0,3.707792751067341,2911.0 83 | 81,20.0,10.0,,,,3.0,400.0,80.0,4.58257569495584,2352.0 84 | 82,20.0,10.0,,,,3.0,400.0,80.0,5.663746980991548,2046.0 85 | 83,20.0,10.0,,,,3.0,400.0,80.0,7.0,1746.0 86 | -------------------------------------------------------------------------------- /reports/project_proposal.md: -------------------------------------------------------------------------------- 1 | # A Multiple-Agent Based Model of Panicked Evacuation 2 | 3 | Adam Novotny, Changjun Lim 4 | 5 | ## Abstract 6 | 7 | There have been various approaches to describe the evacuation process of pedestrians, from an agent-based model [1] and a physical model [1, 2] to a cellular automaton model [3] and a game theory [4]. We will recreate the agent-based model proposed by Helbing, Farkas and Vicsek [1], which describes pedestrian behaviors, including panic and jamming, with a generalized force model. We will replicate the model for the situation that there is a single exit with a large crowd and visualize it. We then try to extend the model by inserting some barriers in front of a door and observe the changes in the evacuation time. 8 | 9 | ## Experiment Direction 10 | The original research paper ran multiple experiments that we would like to replicate. 11 | First, a single exit with a large crowd. This will let us validate that our model matches the original paper. 12 | 13 | We want to extend our software to allow arbitrary map input (from vector graphics), so that we can experiment with different environments, including a widened corridor, multiple doors, etc. We want to try to replicate the findings of a different paper, in which placing a column in front of the exit decreased evacuation times. 14 | 15 | The original paper implemented herding behavior and low visibility, but these are probably out of scope. 16 | 17 | Our biggest technical risk is 1. making a usable physics simulation, 2. making agents have goals. After discussing the problems with peers and reading on the Internet, we concluded that making a physics simulation is feasible and that the agents can have simple goals while still creating emergent phenomena. 18 | 19 | The original paper has very effective data visualization that we want to replicate and learn from. 20 | 21 | ![Sample Output](media/sampleOutput.png) 22 | 23 | Possible output graphs. 24 | 25 | http://angel.elte.hu/panic/ 26 | 27 | http://angel.elte.hu/panic/pdf/panicLetter.pdf 28 | 29 | ### Annotated Bibliography 30 | 31 | [1] [**Simulating dynamical features of escape panic**](https://www.nature.com/nature/journal/v407/n6803/abs/407487a0.html) 32 | 33 | Helbing, D., Farkas, I., & Vicsek, T. (2000). Simulating dynamical features of escape panic. Nature, 407(6803), 487-490. 34 | 35 | Helbing, Farkas and Vicsek propose the model describing pedestrian behaviors like panic and jamming in the evacuation process. The crowd dynamics of pedestrians are based on a generalized force model. They simulate the situation in which pedestrians escapes through a narrow exit and a wider area. They observe the evacuation process with respect to paramters such as pedestrians' velocity, panic and angle. 36 | 37 | [2] [**Social force model for pedestrian dynamics**](https://arxiv.org/pdf/cond-mat/9805244) 38 | 39 | Helbing, D., & Molnar, P. (1995). Social force model for pedestrian dynamics. Physical review E, 51(5), 4282. 40 | 41 | Dirk Helbing and Péter Molnár model pedestrian traffic using a physics model with social forces. Each pedestrian has a goal, repels from walls and other individuals, and has 'distractions' that attract it (friends, posters, etc). Using this model, they show the emergent phenomena describe real-world pedestrian movements, like lane formation and crowd-door dynamics. 42 | 43 | [3] [**Cellular automaton model for evacuation process with obstacles**](http://www.sciencedirect.com/science/article/pii/S0378437107003676) 44 | 45 | _Varas, A., Cornejo, M. D., Mainemer, D., Toledo, B., Rogan, J., Munoz, V., & Valdivia, J. A. (2007). Cellular automaton model for evacuation process with obstacles. Physica A: Statistical Mechanics and its Applications, 382(2), 631-642._ 46 | 47 | Varas, Cornejo, Mainemer, Toldeo, Rogan, Munoz and Valdivia simulate the behavior of pedestrian evacuating a room with a fixed obstacles so that they can find the effect of obstacles in an evacuation. They use the 2D cellular automation model in which pedestrian movement is determined by a static floor field, interaction with others and 'panic'. They experiment the evacuation process by changing the width and position of exit doors. They find that increasing exit width beyond the critical value does not reduce evacuation time and corners of the room are the worst position for an evacuation. 48 | 49 | [4] [**A game theory based exit selection model for evacuation**](http://www.sciencedirect.com/science/article/pii/S037971120600021X) 50 | 51 | Lo, S. M., Huang, H. C., Wang, P., & Yuen, K. K. (2006). A game theory based exit selection model for evacuation. Fire Safety Journal, 41(5), 364-369. 52 | 53 | Lo, Huang, Wang and Yuen integrate non-cooperative game theory with evacuation model to study the behavioral reation of the evacuees. Their non-cooperative game theory model has been established to test how how the evacuation pattern will be affected by rational interaction between evacuees. In their model, evacuees perceive the actions of others and the environmental condition and decide their escape route. They fuind the mixed-strategry _Nash Equilibrium_ for the game which describes the congestion states of exits. They suggest to examine the effect of familiarity and 'grouping' effect on further studies. 54 | -------------------------------------------------------------------------------- /code/Agent.py: -------------------------------------------------------------------------------- 1 | from Point import Point 2 | import math 3 | 4 | 5 | class Agent: 6 | # constant 7 | k = 12000 8 | kappa = 24000 9 | v_max = 5 10 | time = .01 11 | _index = 0 12 | 13 | def __init__(self, size, mass, pos, goal, desiredSpeed=4): 14 | # the constants 15 | self.A = 180 16 | self.B = 0.08 17 | self.tau = 0.5 18 | # instance variables 19 | self.size = size # radius 20 | self.mass = mass 21 | self.pos = pos # current position: Point object 22 | self.velocity = Point(0, 0) # current velocity: Point object 23 | self.desiredSpeed = desiredSpeed # preferred speed: float 24 | self.goal = goal # exit: Goal object 25 | 26 | self.index = Agent._index 27 | Agent._index += 1 28 | 29 | @property 30 | def desiredDirection(self): 31 | """ 32 | Calculates the vector pointing towards the goal. 33 | """ 34 | p1 = self.goal.parameters['p1'] 35 | p2 = self.goal.parameters['p2'] 36 | 37 | # If past the goal move right 38 | if self.pos.x >= p1.x: 39 | return Point(1, 0) 40 | 41 | # If above the goal, move to top point 42 | elif self.pos.y - self.size < p1.y: 43 | return self.vectorTo(p1 + Point(0, .5)).norm() 44 | 45 | # If below the goal, move to bottom point 46 | elif self.pos.y + self.size > p2.y: 47 | return self.vectorTo(p2 - Point(0, .5)).norm() 48 | 49 | # If directly in front of the goal, move right 50 | else: 51 | return Point(1, 0) 52 | 53 | def vectorTo(self, point): 54 | return point - self.pos 55 | 56 | def move(self, force): 57 | """ update step - move toward goal during unit time """ 58 | self.pos = self.pos + self.velocity * Agent.time 59 | self.velocity = self.velocity + force / self.mass * Agent.time 60 | 61 | # apply speed limit 62 | speed = self.velocity.mag() 63 | if speed > Agent.v_max: 64 | self.velocity = self.velocity.norm() * Agent.v_max 65 | 66 | # the force generated by the agent self 67 | def selfDriveForce(self): 68 | desiredVelocity = self.desiredDirection * self.desiredSpeed 69 | return (desiredVelocity - self.velocity) * (self.mass / self.tau) 70 | 71 | # the force between the agent and an part of wall 72 | def wallForce(self, wall): 73 | if wall.wallType == 'line': 74 | p1 = wall.parameters["p1"] 75 | p2 = wall.parameters["p2"] 76 | r = self.vectorTo(p1) 77 | wallLine = (p2 - p1).norm() 78 | normalUnitVector = Point(-wallLine.y, wallLine.x) # rotate 90 degrees counterclockwise 79 | temp = dotProduct(r, normalUnitVector) 80 | if temp > 0: # perpendicular 81 | normalUnitVector.x *= -1 82 | normalUnitVector.y *= -1 83 | elif temp == 0: 84 | normalUnitVector = -r.norm() 85 | 86 | if dotProduct(self.velocity, wallLine) >= 0: 87 | tangentUnitVector = wallLine 88 | else: 89 | tangentUnitVector = -wallLine 90 | distance = -dotProduct(r, normalUnitVector) 91 | 92 | # if wall cannot apply force on agent 93 | # (in the case of that intersection between wall and normal vector is not on wall.) 94 | perpendicularPoint = self.pos + normalUnitVector * distance 95 | if dotProduct(p1 - perpendicularPoint, p2 - perpendicularPoint) > 0: 96 | nearDistance = min(self.vectorTo(p1).mag(), self.vectorTo(p2).mag()) 97 | if self.size < nearDistance: 98 | return Point(0, 0) 99 | 100 | else: # wallType : circle 101 | normalUnitVector = (self.pos - wall.parameters["center"]).norm() 102 | tangentLine = Point(-normalUnitVector.y, normalUnitVector.x) # rotate 90 degrees counterclockwise 103 | if dotProduct(self.velocity, tangentLine) >= 0: 104 | tangentUnitVector = tangentLine 105 | else: 106 | tangentUnitVector = -tangentLine 107 | distance = (self.pos - wall.parameters["center"]).mag() - wall.parameters["radius"] 108 | 109 | overlap = self.size - distance 110 | 111 | return 3*self.calculateForce(overlap, self.velocity, tangentUnitVector, normalUnitVector) 112 | 113 | # the force between the agent and other agent 114 | def pairForce(self, other): 115 | displacement = self.pos - other.pos 116 | overlap = self.size + other.size - displacement.mag() 117 | normalUnitVector = (self.pos - other.pos).norm() 118 | tangentUnitVector = Point(-normalUnitVector.y, normalUnitVector.x) 119 | 120 | return self.calculateForce(overlap, self.velocity - other.velocity, tangentUnitVector, normalUnitVector) 121 | 122 | # calculating each force(Psychological force and contacting force) of wallForce() and pairForce() 123 | def calculateForce(self, overlap, velocityDifference, tangentUnitVector, normalUnitVector): 124 | # psychological force 125 | psyForce = self.psychologicalForce(overlap) 126 | # touching force(young and tangential force) 127 | if overlap > 0: # if distance is shorter than size 128 | youngForce = self.youngForce(overlap) 129 | tangentForce = self.tangentialForce(overlap, velocityDifference, tangentUnitVector) 130 | else: 131 | youngForce = 0 132 | tangentForce = 0 133 | return ((psyForce + youngForce) * normalUnitVector) - (tangentForce * tangentUnitVector) 134 | 135 | # scalar function for the magnitude of force 136 | def psychologicalForce(self, overlap): 137 | return self.A * math.exp(overlap / self.B) 138 | 139 | @classmethod 140 | def youngForce(cls, overlap): # overlap > 0 141 | return cls.k * overlap 142 | 143 | @classmethod 144 | def tangentialForce(cls, overlap, tangVeloDiff, tangentDirection): # overlap > 0 145 | return cls.kappa * overlap * dotProduct(tangVeloDiff, tangentDirection) 146 | 147 | 148 | def dotProduct(vec1, vec2): 149 | return vec1.x * vec2.x + vec1.y * vec2.y 150 | 151 | -------------------------------------------------------------------------------- /reports/draft_final_report.md: -------------------------------------------------------------------------------- 1 | # A Multiple-Agent Based Model of Evacuation 2 | 3 | Adam Novotny, Changjun Lim 4 | 5 | ## Abstract 6 | 7 | There have been various approaches to model the evacuation process of pedestrians, from an agent-based model [1] and a physical model [1, 2] to a cellular automaton model [3] and a game theory [4]. We replicate and extend the agent-based model proposed by Helbing, Farkas, and Vicsek [1], which describes pedestrian behaviors, including panic and jamming, with a generalized force model. 8 | 9 | Yanagisawa et al. [5] find that placing a barrier in front of a narrow exit reduces pressure on the choke point and (surprisingly) reduces evacuation time. We recreate this behavior, and investigate how properties of the barrier affect egress time. 10 | 11 | ## Reproduction 12 | We create an agent-based physical simulation of people attempting to escape a room through a narrow doorway. We based our model on the code from Helbing et al. [1]. In their model, agents want to move at the desired velocity while keeping a distance from other agents and walls. This is modeled as a 'psychological force' which acts on each agent, in addition to the physical forces (friction and normal force from walls and other agents). 13 | 14 | ![visualization of model](media/room_without_barrier.png) 15 | > A visualization of the state of the model 16 | 17 | We add a circular barrier in front of the doorway with r={} and analyze how evacuation time is affected. 18 | 19 | ![Room with Barrier](media/room_with_barrier.png) 20 | > With a barrier 21 | 22 | We validate our model by comparing our plot for escape time vs desired speed to a plot from Helbing et al. [1]. 23 | 24 | ![validation plot](media/sample_plot_evacuation_vs_desired_velocity.png) 25 | > Note: our plot is not visible because it doesn't exist 26 | 27 | ## Different Barriers 28 | 29 | We then run the simulation multiple times to see how different parameters affect evacuation time. 30 | ![sample plot](media/sample_plot_evacuation_vs_desired_velocity.png) 31 | > Sample plot output (note: we are not modeling injury) 32 | 33 | For n barrier sizes and positions (including no barrier): 34 | ``` 35 | {plot: escape time vs number of agents} 36 | {plot: escape time vs desired velocity} 37 | {plot: escape time vs door size} 38 | {plot: escape time vs barrier size (for a few door sizes)} 39 | {plot: escape time vs barrier placement (for a few sizes)} 40 | ``` 41 | 42 | We can see in the above plots that escape time {increases | decreases} when {door size | barrier size} changes. 43 | 44 | The optimal escape time for n agents is a barrier at {size and position}. 45 | 46 | ## Adding Panic 47 | Crowd dynamics also include panic. Panicked people want to go faster, and therefore push more against agents in their way. 48 | 49 | We {will} model panic when agents become impatient (eg. in a burning building). 50 | 51 | 52 | ## Interpretation 53 | We found that <>. 54 | 55 | But please do not put barriers in front of fire exits; human tendencies like assessing the worthiness of an exit were not modeled, and empirical tests should be run before rewriting the fire code. 56 | 57 | 58 | ## Annotated Bibliography 59 | 60 | [1] [**Simulating dynamical features of escape panic**](https://www.nature.com/nature/journal/v407/n6803/abs/407487a0.html) 61 | 62 | Helbing, D., Farkas, I., & Vicsek, T. (2000). Simulating dynamical features of escape panic. Nature, 407(6803), 487-490. 63 | 64 | Helbing, Farkas and Vicsek propose the model describing pedestrian behaviors like panic and jamming in the evacuation process. The crowd dynamics of pedestrians are based on a generalized force model. They simulate the situation in which pedestrians escapes through a narrow exit and a wider area. They observe the evacuation process with respect to parameters such as pedestrians' velocity, panic, and angle. 65 | 66 | [2] [**Social force model for pedestrian dynamics**](https://arxiv.org/pdf/cond-mat/9805244) 67 | 68 | Helbing, D., & Molnar, P. (1995). Social force model for pedestrian dynamics. Physical review E, 51(5), 4282. 69 | 70 | Dirk Helbing and Péter Molnár model pedestrian traffic using a physics model with social forces. Each pedestrian has a goal, repels from walls and other individuals, and has 'distractions' that attract it (friends, posters, etc). Using this model, they show the emergent phenomena describe real-world pedestrian movements, like lane formation and crowd-door dynamics. 71 | 72 | [3] [**Cellular automaton model for evacuation process with obstacles**](http://www.sciencedirect.com/science/article/pii/S0378437107003676) 73 | 74 | _Varas, A., Cornejo, M. D., Mainemer, D., Toledo, B., Rogan, J., Munoz, V., & Valdivia, J. A. (2007). Cellular automaton model for evacuation process with obstacles. Physica A: Statistical Mechanics and its Applications, 382(2), 631-642._ 75 | 76 | Varas, Cornejo, Mainemer, Toldeo, Rogan, Munoz and Valdivia simulate the behavior of pedestrian evacuating a room with a fixed obstacles so that they can find the effect of obstacles in an evacuation. They use the 2D cellular automation model in which pedestrian movement is determined by a static floor field, interaction with others and 'panic'. They experiment the evacuation process by changing the width and position of exit doors. They find that increasing exit width beyond the critical value does not reduce evacuation time and corners of the room are the worst position for an evacuation. 77 | 78 | [4] [**A game theory based exit selection model for evacuation**](http://www.sciencedirect.com/science/article/pii/S037971120600021X) 79 | 80 | Lo, S. M., Huang, H. C., Wang, P., & Yuen, K. K. (2006). A game theory based exit selection model for evacuation. Fire Safety Journal, 41(5), 364-369. 81 | 82 | Lo, Huang, Wang and Yuen integrate non-cooperative game theory with evacuation model to study the behavioral reaction of the evacuees. Their non-cooperative game theory model has been established to test how how the evacuation pattern will be affected by rational interaction between evacuees. In their model, evacuees perceive the actions of others and the environmental condition and decide their escape route. They fuind the mixed-strategry _Nash Equilibrium_ for the game which describes the congestion states of exits. They suggest to examine the effect of familiarity and 'grouping' effect on further studies. 83 | 84 | [5] [Introduction of frictional and turning function for pedestrian outflow with an obstacle.](https://arxiv.org/pdf/0906.0224) 85 | 86 | Yanagisawa, D., Kimura, A., Tomoeda, A., Nishi, R., Suma, Y., Ohtsuka, K., & Nishinari, K. (2009). Introduction of frictional and turning function for pedestrian outflow with an obstacle. Physical Review E, 80(3), 036110. 87 | 88 | Yanagisawa et al. explore how a barrier affects evacuation time in a simulation and emperical experiment. 89 | -------------------------------------------------------------------------------- /code/Environment.py: -------------------------------------------------------------------------------- 1 | from Agent import * 2 | from Point import Point 3 | 4 | import pygame 5 | from pygame.locals import * 6 | from pygame.color import * 7 | import thinkplot 8 | import random 9 | 10 | from datetime import datetime 11 | import pickle 12 | import pandas 13 | 14 | import sys 15 | 16 | DEBUG = False 17 | 18 | 19 | class Wall: 20 | def __init__(self, wallType, **parameters): 21 | # type : "circle" | "line", points 22 | # Circle : type='circle' { "center": Point(x,y), "radius": r } 23 | # Line : type='line'{ "p1": Point(x1,y1), "p2": Point(x2,y2) } 24 | self.wallType = wallType 25 | self.parameters = parameters 26 | self.checkValid() 27 | 28 | def checkValid(self): 29 | if self.wallType == 'circle': 30 | assert isinstance(self.parameters["center"], Point), "Circles need a center" 31 | # assert isinstance(self.parameters["radius"], int), "Radius needs to be an int" 32 | 33 | if self.wallType == 'line': 34 | assert isinstance(self.parameters['p1'], Point) 35 | assert isinstance(self.parameters['p2'], Point) 36 | 37 | 38 | class Goal(Wall): 39 | """ Defines a goal. Currently, only horizontal and vertical lines are supported. """ 40 | 41 | def checkValid(self): 42 | assert self.wallType == 'line' 43 | assert isinstance(self.parameters['p1'], Point) 44 | assert isinstance(self.parameters['p2'], Point) 45 | assert (self.parameters['p1'].x == self.parameters['p2'].x or self.parameters['p1'].y == self.parameters[ 46 | 'p2'].y) 47 | 48 | # p1 should always be smaller than p2 49 | if (self.parameters['p1'].x == self.parameters['p2'].x): 50 | if self.parameters['p1'].y > self.parameters['p2'].y: 51 | p1Temp = self.parameters['p1'] 52 | self.parameters['p1'] = self.paramters['p2'] 53 | self.parameters['p2'] = p1Temp 54 | elif (self.parameters['p1'].y == self.parameters['p2'].y): 55 | if self.parameters['p1'].x > self.parameters['p2'].x: 56 | p1Temp = self.parameters['p1'] 57 | self.parameters['p1'] = self.paramters['p2'] 58 | self.parameters['p2'] = p1Temp 59 | 60 | 61 | class Environment(): 62 | conditions = {'k': 1.2 * 10 ** 5, 'ka': 2.4 * 10 ** 5} 63 | 64 | def __init__(self, N, walls, goals, agents, conditions, instruments): 65 | self.N = N 66 | self.walls = walls 67 | self.goals = goals 68 | self.agents = agents 69 | self.instruments = instruments 70 | # Conditions: Agent force, Agent repulsive distance, acceleration time, step length, 71 | self.conditions.update(conditions) 72 | 73 | def step(self): 74 | for agent in self.agents: 75 | # print(agent.desiredDirection) 76 | selfDriveForce = agent.selfDriveForce() 77 | pairForce = Point(0, 0) 78 | wallForce = Point(0, 0) 79 | for wall in self.walls: 80 | wallForce += agent.wallForce(wall) 81 | for agent2 in self.agents: 82 | if agent.index == agent2.index: 83 | continue 84 | pairForce += agent.pairForce(agent2) 85 | netForce = selfDriveForce + pairForce + wallForce 86 | agent.move(netForce) 87 | 88 | self.updateInstruments() 89 | 90 | def updateInstruments(self): 91 | for instrument in self.instruments: 92 | instrument.update(self) 93 | 94 | def plot(self, num): 95 | self.instruments[num].plot() 96 | 97 | 98 | class EnvironmentViewer(): 99 | BG_COLOR = Color(0, 0, 0) 100 | 101 | BLACK = Color(0, 0, 0) 102 | WHITE = Color(255, 255, 255) 103 | YELLOW = Color(255, 233, 0) 104 | RED = Color(203, 20, 16) 105 | GOAL = Color(252, 148, 37) 106 | 107 | pygameScale = 50 108 | 109 | def __init__(self, environment): 110 | self.env = environment 111 | self.screen = pygame.display.set_mode((1000,1000)) 112 | 113 | def draw(self): 114 | self.screen.fill(self.BG_COLOR) 115 | 116 | for agent in self.env.agents: 117 | self.drawAgent(agent) 118 | 119 | for wall in self.env.walls: 120 | self.drawWall(wall) 121 | 122 | for goal in self.env.goals: 123 | self.drawGoal(goal) 124 | 125 | pygame.display.update() 126 | 127 | def drawAgent(self, agent): 128 | # Draw agent 129 | pygame.draw.circle(self.screen, self.YELLOW, agent.pos.pygame, int(agent.size * self.pygameScale)) 130 | # Draw desired vector 131 | pygame.draw.line(self.screen, self.YELLOW, agent.pos.pygame, (agent.pos + (agent.desiredDirection)).pygame) 132 | if(DEBUG): print("drew agent at ", agent.pos) 133 | 134 | def drawWall(self, wall, color=WHITE): 135 | if wall.wallType == 'circle': 136 | pygame.draw.circle(self.screen, color, wall.parameters['center'].pygame, int(wall.parameters['radius'] * self.pygameScale)) 137 | if(DEBUG): print("drew wall at {}".format(wall.parameters['center'])) 138 | 139 | if wall.wallType == 'line': 140 | pygame.draw.line(self.screen, color, wall.parameters['p1'].pygame, wall.parameters['p2'].pygame, 10) 141 | if(DEBUG): print("drew wall between {} and {}".format(wall.parameters['p1'], wall.parameters['p2'])) 142 | 143 | def drawGoal(self, goal): 144 | self.drawWall(goal, color=self.GOAL) 145 | 146 | 147 | class Instrument(): 148 | """ Instrument that logs the state of the environment""" 149 | 150 | def __init__(self): 151 | self.metric = [] 152 | 153 | def plot(self, **options): 154 | thinkplot.plot(self.metric, **options) 155 | thinkplot.show() 156 | 157 | 158 | class ReachedGoal(Instrument): 159 | """ Logs the number of agents that have escaped """ 160 | 161 | def update(self, env): 162 | self.metric.append(self.countReachedGoal(env)) 163 | 164 | def countReachedGoal(self, env): 165 | num_escaped = 0 166 | 167 | for agent in env.agents: 168 | if agent.pos.x > agent.goal.parameters['p1'].x: 169 | num_escaped += 1 170 | return num_escaped 171 | 172 | 173 | 174 | def randFloat(minVal, maxVal): 175 | return random.random() * (maxVal - minVal) + minVal 176 | 177 | def runSimulation(roomHeight=10, 178 | roomWidth=8, 179 | barrier={ 'radius': .3, 'pos': Point(-1,0)}, # pos is relative to door center 180 | doorWidth=1.5, 181 | numAgents=50, 182 | agentMass=80, 183 | desiredSpeed=4, 184 | view=False): 185 | 186 | walls = [] 187 | # Only add barrier if its radius is above 0 188 | if barrier: 189 | walls.append(Wall('circle', **{ 'center': Point(roomWidth + barrier['pos'].x, roomHeight//2 + barrier['pos'].y), 'radius': barrier['radius'] })) 190 | 191 | walls.append(Wall('line', **{ 'p1': Point(0,0), 'p2': Point(roomWidth, 0) })) # Top 192 | # walls.append(Wall('line', **{ 'p1': Point(0,0), 'p2': Point(0, roomHeight) })) # Left 193 | walls.append(Wall('line', **{ 'p1': Point(0,roomHeight), 'p2': Point(roomWidth, roomHeight) })) # Bottom 194 | 195 | walls.append(Wall('line', **{ 'p1': Point(roomWidth,0), 'p2': Point(roomWidth, roomHeight/2 - doorWidth/2) })) # Top Doorway 196 | walls.append(Wall('line', **{ 'p1': Point(roomWidth, roomHeight/2 + doorWidth/2), 'p2': Point(roomWidth, roomHeight) })) # Bottom Doorway 197 | 198 | 199 | 200 | goals = [] 201 | goals.append(Goal('line', **{ 'p1': Point(roomWidth, roomHeight/2 - doorWidth/2), 'p2': Point(roomWidth, roomHeight/2 + doorWidth/2) })) 202 | 203 | instruments = [] 204 | instruments.append(ReachedGoal()) 205 | 206 | 207 | agents = [] 208 | for _ in range(numAgents): 209 | # Agent(size, mass, pos, goal, desiredSpeed = 4)) 210 | size = randFloat(.25, .35) 211 | mass = agentMass 212 | pos = Point(randFloat(.5, 2*roomWidth/3 - .5), randFloat(.5,roomHeight-.5)) 213 | goal = goals[0] 214 | 215 | agents.append(Agent(size, mass, pos, goal, desiredSpeed=desiredSpeed)) 216 | 217 | env = Environment(100, walls, goals, agents, {}, instruments) 218 | 219 | if view: 220 | viewer = EnvironmentViewer(env) 221 | viewer.draw() 222 | 223 | env.step() 224 | 225 | # print(env.instruments[0].metric) 226 | # Run until all agents have escaped 227 | while env.instruments[0].metric[-1] < len(env.agents): 228 | env.step() 229 | if view: 230 | viewer.draw() 231 | # pygame.event.wait() 232 | if (len(env.instruments[0].metric) % 100 == 0): 233 | message = "num escaped: {}, step: {}".format(env.instruments[0].metric[-1], len(env.instruments[0].metric)) 234 | sys.stdout.write('\r' + str(message) + ' ' * 20) 235 | sys.stdout.flush() # important 236 | 237 | if len(env.instruments[0].metric) == 6000: 238 | break 239 | 240 | print() 241 | return env.instruments[0].metric 242 | 243 | def runExperiment(): 244 | x = [] 245 | time_to_escape = [] 246 | for num_agents in range(50, 500, 50): 247 | statistics = runSimulation() 248 | 249 | x.append(num_agents) 250 | time_to_escape.append(len(statistics)) 251 | 252 | export = [x, time_to_escape] 253 | # with open("{}.pd".format(datetime.time()), "r") as outfile: 254 | # pickle.dump(export, outfile) 255 | 256 | 257 | if __name__ == '__main__': 258 | simResult = runSimulation(barrier=None, view=True, desiredSpeed=2, numAgents=50, roomHeight=20, roomWidth=10) 259 | print(simResult) 260 | 261 | # thinkplot.plot(defaultExperiment) 262 | # thinkplot.show() 263 | -------------------------------------------------------------------------------- /reports/final_report.md: -------------------------------------------------------------------------------- 1 | # A Multiple-Agent Based Model of Evacuation 2 | 3 | Adam Novotny, Changjun Lim 4 | 5 | ## Abstract 6 | 7 | There have been various approaches to model human walking dynamics, specifically in evacuation scenarios. Researchers have used tools ranging from agent-based models [1] and physical simulations, [1, 2] to cellular automaton models [3] and even game theory [4]. These models are useful for exploring the emergent behavior of pedestrians. Experiments modeling evacuations can help identify which features are the most important for reducing evacuation time. 8 | 9 | Yanagisawa et al. [5] ran an experiment with people to analyze human behavior during an evacuation. Counter-intuitively, they found that a small barrier near the exit reduces evacuation time. They suggest that the biggest bottleneck in an unorganized evacuation is resolving who goes through the doorway (described as a 'conflict'). By reducing the available area in front of the door, the barrier reduces conflicts and speeds up evacuation time. 10 | 11 | Then, they modeled people as agents on a grid world to extrapolate findings about barrier placement. We adopt the agent-based physics model proposed by Helbing, Farkas, and Vicsek [1] to explore evacuation in a continuous world. This model describes pedestrian behaviors, including panic and jamming, with a generalized force model. We want to find whether a barrier actually reduces evacuation time, and what other parameters (door width, number of agents, etc.) are the most important for fast evacuations. 12 | 13 | ## Reproduction 14 | 15 | We create an agent-based physical simulation of people attempting to escape a room through a narrow doorway. We based our model on the generalized force model of Helbing et al. [1]. In their model, agents want to move at the desired velocity while keeping a distance from other agents and walls. This is modeled as a self-driving force and a 'psychological force' which acts on each agent, in addition to the physical forces (friction and normal force from walls and other agents). Figure 1 shows a visualization of all of the forces that we model. 16 | A self-driving force is proportional to the difference between the desired velocity and current velocity. The magnitude of the psychological force is an exponential function of the distance between surfaces of an agent and walls or other agents. So, even if an agent is not colliding with other objects, there is a psychological force repelling it from them. The magnitude of friction and normal force is proportional to the overlapped length between an agent and walls or other agents. There is no friction nor normal force when an agent is not in contact with other objects. We choose the coefficients of forces from the original paper [1]. 17 | 18 | We had to tune some constants slightly to get a more realistic looking simulation of people. 19 | 20 | 21 | ![visualization of forces on agents](media/agent_forces.png) 22 | > Figure 1: A visualization of the forces acting on an agent when near another agent, and when in contact with a wall 23 | 24 | ![visualization of model](media/rooms_side_by_side.png) 25 | > Figure 2: A visualization of the model with and without a barrier. The vectors from each agent are each agent's desired direction 26 | 27 | Figure 2 is a visualization of the simulated room and doorway. Agents crowd around the exit, trying to push their way out. On the right, we show a room with a barrier. The entrance has visibly less pressure on it, as the barrier blocks agents from approaching it straight on. 28 | 29 | 30 | ![video](https://i.imgur.com/3LthHPN.gif) 31 | > A video of the agents evacuating. Notice minor resonance between neighbors 32 | 33 | In our model, we noticed some spring-like behavior and resonance between agents. The crowd would run up to the wall and compress against it, then expand backward before settling into a densely packed configuration. Further tuning the agent's psychological force constants would decrease this springiness, but overall the physical model is realistic. 34 | 35 | 36 | ## Different Barriers 37 | 38 | We then run the simulation with a parameter sweep to see how the parameters affect evacuation time. We plot the time it takes for all agents to exit the room. We assume the case where 49 people, having a diameter uniformly distributed in the interval [0.5m, 0.7m], are leaving through a doorway. All results are the average of two simulations. This is probably not enough averaging, but it is better than a single run and running physics on multiple bodies is extremely slow in python. 39 | 40 | ![barrier vs no barrier](media/Graph2_doorwidth_ratio.png) 41 | > Figure 3: The ratio of time between the same room with and without a barrier 42 | 43 | ![door size to evacuation time](media/Graph1_doorwidth_time.png) 44 | > Figure 4: The door width vs escape time 45 | 46 | 47 | Figure 3 shows the ratio of evacuation times between a room with a barrier and without. A value below 1 means the barrier sped up evacuation time. We can see that the barrier has little benefit, and for wider doors can slow down evacuation. The bars show the minimum and maximum ratios for barrier sizes between 0.5 and 3 meters. 48 | 49 | We find that when the door is small (a strong bottleneck), a barrier in front of the door makes the evacuation quicker. The explanation that others have suggested is that, by blocking part of the pressure on the doorway, conflicts near the door are reduced. 50 | 51 | Another explanation is that the barrier parallelizes the bottlenecks. If the barrier creates two gaps approximately the size of a doorway, this is similar to doubling the number of bottlenecks. Now, twice as many people should be able to go through. Once an agent gets through the gap, it can easily walk out the door as there is no crowd in the doorway. 52 | 53 | Generally though, the barrier had little effect. We have found a few specific configurations of the barrier that result in much faster evacuations. This indicates that either our model is not completely accurate and we are overfitting the solution (quite possible) or that the effect is very sensitive to placement and size of the barrier. We did not have the computational power to study these effects on a granular level. Further research should be done to analyze the relationship between precise barrier parameters and the evacuation time. 54 | 55 | 56 | The most important parameter is the width of the door. In Figure 4 we can see that the slope is very steep downwards until it saturates at 2.5 meters wide (for 49 people with an average diameter of 0.6m). This means it is very easy to optimize a door for evacuation, that is, by making it wider. 57 | 58 | 59 | ## Interpretation 60 | We have found that a barrier has the ability to speed up evacuation through a narrow doorway. The effect is sensitive to doorway size, and further research should be done to discover the principals for designing a doorway barrier. We have shown that the barrier is almost unnecessary though, because widening the door has a much bigger effect on evacuation time. 61 | 62 | So please do not start putting barriers in front of fire exits; human tendencies like assessing the worthiness of an exit were not modeled, and more empirical tests should be run before rewriting the fire code. 63 | 64 | 65 | ## Review 66 | 67 | Generally, this project went all right. In the future, I would not write physics models in python, as running a single simulation with 100 agents took multiple minutes to complete. This was still okay to find data, but if it was 10-100x faster, we might have been able to find more meaningful conclusions. 68 | 69 | ## Annotated Bibliography 70 | 71 | [1] [**Simulating dynamical features of escape panic**](https://www.nature.com/nature/journal/v407/n6803/abs/407487a0.html) 72 | 73 | Helbing, D., Farkas, I., & Vicsek, T. (2000). Simulating dynamical features of escape panic. Nature, 407(6803), 487-490. 74 | 75 | Helbing, Farkas and Vicsek propose the model describing pedestrian behaviors like panic and jamming in the evacuation process. The crowd dynamics of pedestrians are based on a generalized force model. They simulate the situation in which pedestrians escapes through a narrow exit and a wider area. They observe the evacuation process with respect to parameters such as pedestrians' velocity, panic, and angle. 76 | 77 | [2] [**Social force model for pedestrian dynamics**](https://arxiv.org/pdf/cond-mat/9805244) 78 | 79 | Helbing, D., & Molnar, P. (1995). Social force model for pedestrian dynamics. Physical review E, 51(5), 4282. 80 | 81 | Dirk Helbing and Péter Molnár model pedestrian traffic using a physics model with social forces. Each pedestrian has a goal, repels from walls and other individuals, and has 'distractions' that attract it (friends, posters, etc). Using this model, they show the emergent phenomena describe real-world pedestrian movements, like lane formation and crowd-door dynamics. 82 | 83 | [3] [**Cellular automaton model for evacuation process with obstacles**](http://www.sciencedirect.com/science/article/pii/S0378437107003676) 84 | 85 | _Varas, A., Cornejo, M. D., Mainemer, D., Toledo, B., Rogan, J., Munoz, V., & Valdivia, J. A. (2007). Cellular automaton model for evacuation process with obstacles. Physica A: Statistical Mechanics and its Applications, 382(2), 631-642._ 86 | 87 | Varas, Cornejo, Mainemer, Toldeo, Rogan, Munoz and Valdivia simulate the behavior of pedestrian evacuating a room with a fixed obstacles so that they can find the effect of obstacles in an evacuation. They use the 2D cellular automation model in which pedestrian movement is determined by a static floor field, interaction with others and 'panic'. They experiment the evacuation process by changing the width and position of exit doors. They find that increasing exit width beyond the critical value does not reduce evacuation time and corners of the room are the worst position for an evacuation. 88 | 89 | [4] [**A game theory based exit selection model for evacuation**](http://www.sciencedirect.com/science/article/pii/S037971120600021X) 90 | 91 | Lo, S. M., Huang, H. C., Wang, P., & Yuen, K. K. (2006). A game theory based exit selection model for evacuation. Fire Safety Journal, 41(5), 364-369. 92 | 93 | Lo, Huang, Wang and Yuen integrate non-cooperative game theory with evacuation model to study the behavioral reaction of the evacuees. Their non-cooperative game theory model has been established to test how the evacuation pattern will be affected by a rational interaction between evacuees. In their model, evacuees perceive the actions of others and the environmental condition and decide their escape route. They found the mixed-strategy _Nash Equilibrium_ for the game which describes the congestion states of exits. They suggest examining the effect of familiarity and 'grouping' effect on further studies. 94 | 95 | [5] [Introduction of frictional and turning function for pedestrian outflow with an obstacle.](https://arxiv.org/pdf/0906.0224) 96 | 97 | Yanagisawa, D., Kimura, A., Tomoeda, A., Nishi, R., Suma, Y., Ohtsuka, K., & Nishinari, K. (2009). Introduction of frictional and turning function for pedestrian outflow with an obstacle. Physical Review E, 80(3), 036110. 98 | 99 | Yanagisawa et al. explore how a barrier affects evacuation time in a simulation and empirical experiment. 100 | -------------------------------------------------------------------------------- /reports/media/agent_forces.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 40 | 42 | 43 | 45 | image/svg+xml 46 | 48 | 49 | 50 | 51 | 52 | 57 | Agent near agent 73 | Agenttouching a wall 89 | 94 | 99 | DesiredDirection 115 | 120 | DesiredDirection 136 | 142 | 147 | RepulsiveForce 163 | 168 | Net Force 179 | 184 | NormalForce 200 | 205 | Friction 216 | RepulsiveForce 232 | 237 | 242 | Net Force 253 | 254 | 258 | 264 | 271 | 279 | 280 | 281 | -------------------------------------------------------------------------------- /code/thinkplot.py: -------------------------------------------------------------------------------- 1 | """This file contains code for use with "Think Stats", 2 | by Allen B. Downey, available from greenteapress.com 3 | 4 | Copyright 2014 Allen B. Downey 5 | License: GNU GPLv3 http://www.gnu.org/licenses/gpl.html 6 | """ 7 | 8 | from __future__ import print_function 9 | 10 | import math 11 | import matplotlib 12 | import matplotlib.pyplot as plt 13 | import numpy as np 14 | import pandas 15 | 16 | import warnings 17 | 18 | # customize some matplotlib attributes 19 | #matplotlib.rc('figure', figsize=(4, 3)) 20 | 21 | #matplotlib.rc('font', size=14.0) 22 | #matplotlib.rc('axes', labelsize=22.0, titlesize=22.0) 23 | #matplotlib.rc('legend', fontsize=20.0) 24 | 25 | #matplotlib.rc('xtick.major', size=6.0) 26 | #matplotlib.rc('xtick.minor', size=3.0) 27 | 28 | #matplotlib.rc('ytick.major', size=6.0) 29 | #matplotlib.rc('ytick.minor', size=3.0) 30 | 31 | 32 | class _Brewer(object): 33 | """Encapsulates a nice sequence of colors. 34 | 35 | Shades of blue that look good in color and can be distinguished 36 | in grayscale (up to a point). 37 | 38 | Borrowed from http://colorbrewer2.org/ 39 | """ 40 | color_iter = None 41 | 42 | colors = ['#f7fbff', '#deebf7', '#c6dbef', 43 | '#9ecae1', '#6baed6', '#4292c6', 44 | '#2171b5','#08519c','#08306b'][::-1] 45 | 46 | # lists that indicate which colors to use depending on how many are used 47 | which_colors = [[], 48 | [1], 49 | [1, 3], 50 | [0, 2, 4], 51 | [0, 2, 4, 6], 52 | [0, 2, 3, 5, 6], 53 | [0, 2, 3, 4, 5, 6], 54 | [0, 1, 2, 3, 4, 5, 6], 55 | [0, 1, 2, 3, 4, 5, 6, 7], 56 | [0, 1, 2, 3, 4, 5, 6, 7, 8], 57 | ] 58 | 59 | current_figure = None 60 | 61 | @classmethod 62 | def Colors(cls): 63 | """Returns the list of colors. 64 | """ 65 | return cls.colors 66 | 67 | @classmethod 68 | def ColorGenerator(cls, num): 69 | """Returns an iterator of color strings. 70 | 71 | n: how many colors will be used 72 | """ 73 | for i in cls.which_colors[num]: 74 | yield cls.colors[i] 75 | raise StopIteration('Ran out of colors in _Brewer.') 76 | 77 | @classmethod 78 | def InitIter(cls, num): 79 | """Initializes the color iterator with the given number of colors.""" 80 | cls.color_iter = cls.ColorGenerator(num) 81 | fig = plt.gcf() 82 | cls.current_figure = fig 83 | 84 | @classmethod 85 | def ClearIter(cls): 86 | """Sets the color iterator to None.""" 87 | cls.color_iter = None 88 | cls.current_figure = None 89 | 90 | @classmethod 91 | def GetIter(cls, num): 92 | """Gets the color iterator.""" 93 | fig = plt.gcf() 94 | if fig != cls.current_figure: 95 | cls.InitIter(num) 96 | cls.current_figure = fig 97 | 98 | if cls.color_iter is None: 99 | cls.InitIter(num) 100 | 101 | return cls.color_iter 102 | 103 | 104 | def _UnderrideColor(options): 105 | """If color is not in the options, chooses a color. 106 | """ 107 | if 'color' in options: 108 | return options 109 | 110 | # get the current color iterator; if there is none, init one 111 | color_iter = _Brewer.GetIter(5) 112 | 113 | try: 114 | options['color'] = next(color_iter) 115 | except StopIteration: 116 | # if you run out of colors, initialize the color iterator 117 | # and try again 118 | warnings.warn('Ran out of colors. Starting over.') 119 | _Brewer.ClearIter() 120 | _UnderrideColor(options) 121 | 122 | return options 123 | 124 | 125 | def PrePlot(num=None, rows=None, cols=None): 126 | """Takes hints about what's coming. 127 | 128 | num: number of lines that will be plotted 129 | rows: number of rows of subplots 130 | cols: number of columns of subplots 131 | """ 132 | if num: 133 | _Brewer.InitIter(num) 134 | 135 | if rows is None and cols is None: 136 | return 137 | 138 | if rows is not None and cols is None: 139 | cols = 1 140 | 141 | if cols is not None and rows is None: 142 | rows = 1 143 | 144 | # resize the image, depending on the number of rows and cols 145 | size_map = {(1, 1): (8, 6), 146 | (1, 2): (12, 6), 147 | (1, 3): (12, 6), 148 | (1, 4): (12, 5), 149 | (1, 5): (12, 4), 150 | (2, 2): (10, 10), 151 | (2, 3): (16, 10), 152 | (3, 1): (8, 10), 153 | (4, 1): (8, 12), 154 | } 155 | 156 | if (rows, cols) in size_map: 157 | fig = plt.gcf() 158 | fig.set_size_inches(*size_map[rows, cols]) 159 | 160 | # create the first subplot 161 | if rows > 1 or cols > 1: 162 | ax = plt.subplot(rows, cols, 1) 163 | global SUBPLOT_ROWS, SUBPLOT_COLS 164 | SUBPLOT_ROWS = rows 165 | SUBPLOT_COLS = cols 166 | else: 167 | ax = plt.gca() 168 | 169 | return ax 170 | 171 | def SubPlot(plot_number, rows=None, cols=None, **options): 172 | """Configures the number of subplots and changes the current plot. 173 | 174 | rows: int 175 | cols: int 176 | plot_number: int 177 | options: passed to subplot 178 | """ 179 | rows = rows or SUBPLOT_ROWS 180 | cols = cols or SUBPLOT_COLS 181 | return plt.subplot(rows, cols, plot_number, **options) 182 | 183 | 184 | def _Underride(d, **options): 185 | """Add key-value pairs to d only if key is not in d. 186 | 187 | If d is None, create a new dictionary. 188 | 189 | d: dictionary 190 | options: keyword args to add to d 191 | """ 192 | if d is None: 193 | d = {} 194 | 195 | for key, val in options.items(): 196 | d.setdefault(key, val) 197 | 198 | return d 199 | 200 | 201 | def Clf(): 202 | """Clears the figure and any hints that have been set.""" 203 | global LOC 204 | LOC = None 205 | _Brewer.ClearIter() 206 | plt.clf() 207 | fig = plt.gcf() 208 | fig.set_size_inches(8, 6) 209 | 210 | 211 | def Figure(**options): 212 | """Sets options for the current figure.""" 213 | _Underride(options, figsize=(6, 8)) 214 | plt.figure(**options) 215 | 216 | 217 | def Plot(obj, ys=None, style='', **options): 218 | """Plots a line. 219 | 220 | Args: 221 | obj: sequence of x values, or Series, or anything with Render() 222 | ys: sequence of y values 223 | style: style string passed along to plt.plot 224 | options: keyword args passed to plt.plot 225 | """ 226 | options = _UnderrideColor(options) 227 | label = getattr(obj, 'label', '_nolegend_') 228 | options = _Underride(options, linewidth=3, alpha=0.7, label=label) 229 | 230 | xs = obj 231 | if ys is None: 232 | if hasattr(obj, 'Render'): 233 | xs, ys = obj.Render() 234 | if isinstance(obj, pandas.Series): 235 | ys = obj.values 236 | xs = obj.index 237 | 238 | if ys is None: 239 | plt.plot(xs, style, **options) 240 | else: 241 | plt.plot(xs, ys, style, **options) 242 | 243 | 244 | def Vlines(xs, y1, y2, **options): 245 | """Plots a set of vertical lines. 246 | 247 | Args: 248 | xs: sequence of x values 249 | y1: sequence of y values 250 | y2: sequence of y values 251 | options: keyword args passed to plt.vlines 252 | """ 253 | options = _UnderrideColor(options) 254 | options = _Underride(options, linewidth=1, alpha=0.5) 255 | plt.vlines(xs, y1, y2, **options) 256 | 257 | 258 | def Hlines(ys, x1, x2, **options): 259 | """Plots a set of horizontal lines. 260 | 261 | Args: 262 | ys: sequence of y values 263 | x1: sequence of x values 264 | x2: sequence of x values 265 | options: keyword args passed to plt.vlines 266 | """ 267 | options = _UnderrideColor(options) 268 | options = _Underride(options, linewidth=1, alpha=0.5) 269 | plt.hlines(ys, x1, x2, **options) 270 | 271 | 272 | def FillBetween(xs, y1, y2=None, where=None, **options): 273 | """Fills the space between two lines. 274 | 275 | Args: 276 | xs: sequence of x values 277 | y1: sequence of y values 278 | y2: sequence of y values 279 | where: sequence of boolean 280 | options: keyword args passed to plt.fill_between 281 | """ 282 | options = _UnderrideColor(options) 283 | options = _Underride(options, linewidth=0, alpha=0.5) 284 | plt.fill_between(xs, y1, y2, where, **options) 285 | 286 | 287 | def Bar(xs, ys, **options): 288 | """Plots a line. 289 | 290 | Args: 291 | xs: sequence of x values 292 | ys: sequence of y values 293 | options: keyword args passed to plt.bar 294 | """ 295 | options = _UnderrideColor(options) 296 | options = _Underride(options, linewidth=0, alpha=0.6) 297 | plt.bar(xs, ys, **options) 298 | 299 | 300 | def Scatter(xs, ys=None, **options): 301 | """Makes a scatter plot. 302 | 303 | xs: x values 304 | ys: y values 305 | options: options passed to plt.scatter 306 | """ 307 | options = _Underride(options, color='blue', alpha=0.2, 308 | s=30, edgecolors='none') 309 | 310 | if ys is None and isinstance(xs, pandas.Series): 311 | ys = xs.values 312 | xs = xs.index 313 | 314 | plt.scatter(xs, ys, **options) 315 | 316 | 317 | def HexBin(xs, ys, **options): 318 | """Makes a scatter plot. 319 | 320 | xs: x values 321 | ys: y values 322 | options: options passed to plt.scatter 323 | """ 324 | options = _Underride(options, cmap=matplotlib.cm.Blues) 325 | plt.hexbin(xs, ys, **options) 326 | 327 | 328 | def Pdf(pdf, **options): 329 | """Plots a Pdf, Pmf, or Hist as a line. 330 | 331 | Args: 332 | pdf: Pdf, Pmf, or Hist object 333 | options: keyword args passed to plt.plot 334 | """ 335 | low, high = options.pop('low', None), options.pop('high', None) 336 | n = options.pop('n', 101) 337 | xs, ps = pdf.Render(low=low, high=high, n=n) 338 | options = _Underride(options, label=pdf.label) 339 | Plot(xs, ps, **options) 340 | 341 | 342 | def Pdfs(pdfs, **options): 343 | """Plots a sequence of PDFs. 344 | 345 | Options are passed along for all PDFs. If you want different 346 | options for each pdf, make multiple calls to Pdf. 347 | 348 | Args: 349 | pdfs: sequence of PDF objects 350 | options: keyword args passed to plt.plot 351 | """ 352 | for pdf in pdfs: 353 | Pdf(pdf, **options) 354 | 355 | 356 | def Hist(hist, **options): 357 | """Plots a Pmf or Hist with a bar plot. 358 | 359 | The default width of the bars is based on the minimum difference 360 | between values in the Hist. If that's too small, you can override 361 | it by providing a width keyword argument, in the same units 362 | as the values. 363 | 364 | Args: 365 | hist: Hist or Pmf object 366 | options: keyword args passed to plt.bar 367 | """ 368 | # find the minimum distance between adjacent values 369 | xs, ys = hist.Render() 370 | 371 | # see if the values support arithmetic 372 | try: 373 | xs[0] - xs[0] 374 | except TypeError: 375 | # if not, replace values with numbers 376 | labels = [str(x) for x in xs] 377 | xs = np.arange(len(xs)) 378 | plt.xticks(xs+0.5, labels) 379 | 380 | if 'width' not in options: 381 | try: 382 | options['width'] = 0.9 * np.diff(xs).min() 383 | except TypeError: 384 | warnings.warn("Hist: Can't compute bar width automatically." 385 | "Check for non-numeric types in Hist." 386 | "Or try providing width option." 387 | ) 388 | 389 | options = _Underride(options, label=hist.label) 390 | options = _Underride(options, align='center') 391 | if options['align'] == 'left': 392 | options['align'] = 'edge' 393 | elif options['align'] == 'right': 394 | options['align'] = 'edge' 395 | options['width'] *= -1 396 | 397 | Bar(xs, ys, **options) 398 | 399 | 400 | def Hists(hists, **options): 401 | """Plots two histograms as interleaved bar plots. 402 | 403 | Options are passed along for all PMFs. If you want different 404 | options for each pmf, make multiple calls to Pmf. 405 | 406 | Args: 407 | hists: list of two Hist or Pmf objects 408 | options: keyword args passed to plt.plot 409 | """ 410 | for hist in hists: 411 | Hist(hist, **options) 412 | 413 | 414 | def Pmf(pmf, **options): 415 | """Plots a Pmf or Hist as a line. 416 | 417 | Args: 418 | pmf: Hist or Pmf object 419 | options: keyword args passed to plt.plot 420 | """ 421 | xs, ys = pmf.Render() 422 | low, high = min(xs), max(xs) 423 | 424 | width = options.pop('width', None) 425 | if width is None: 426 | try: 427 | width = np.diff(xs).min() 428 | except TypeError: 429 | warnings.warn("Pmf: Can't compute bar width automatically." 430 | "Check for non-numeric types in Pmf." 431 | "Or try providing width option.") 432 | points = [] 433 | 434 | lastx = np.nan 435 | lasty = 0 436 | for x, y in zip(xs, ys): 437 | if (x - lastx) > 1e-5: 438 | points.append((lastx, 0)) 439 | points.append((x, 0)) 440 | 441 | points.append((x, lasty)) 442 | points.append((x, y)) 443 | points.append((x+width, y)) 444 | 445 | lastx = x + width 446 | lasty = y 447 | points.append((lastx, 0)) 448 | pxs, pys = zip(*points) 449 | 450 | align = options.pop('align', 'center') 451 | if align == 'center': 452 | pxs = np.array(pxs) - width/2.0 453 | if align == 'right': 454 | pxs = np.array(pxs) - width 455 | 456 | options = _Underride(options, label=pmf.label) 457 | Plot(pxs, pys, **options) 458 | 459 | 460 | def Pmfs(pmfs, **options): 461 | """Plots a sequence of PMFs. 462 | 463 | Options are passed along for all PMFs. If you want different 464 | options for each pmf, make multiple calls to Pmf. 465 | 466 | Args: 467 | pmfs: sequence of PMF objects 468 | options: keyword args passed to plt.plot 469 | """ 470 | for pmf in pmfs: 471 | Pmf(pmf, **options) 472 | 473 | 474 | def Diff(t): 475 | """Compute the differences between adjacent elements in a sequence. 476 | 477 | Args: 478 | t: sequence of number 479 | 480 | Returns: 481 | sequence of differences (length one less than t) 482 | """ 483 | diffs = [t[i+1] - t[i] for i in range(len(t)-1)] 484 | return diffs 485 | 486 | 487 | def Cdf(cdf, complement=False, transform=None, **options): 488 | """Plots a CDF as a line. 489 | 490 | Args: 491 | cdf: Cdf object 492 | complement: boolean, whether to plot the complementary CDF 493 | transform: string, one of 'exponential', 'pareto', 'weibull', 'gumbel' 494 | options: keyword args passed to plt.plot 495 | 496 | Returns: 497 | dictionary with the scale options that should be passed to 498 | Config, Show or Save. 499 | """ 500 | xs, ps = cdf.Render() 501 | xs = np.asarray(xs) 502 | ps = np.asarray(ps) 503 | 504 | scale = dict(xscale='linear', yscale='linear') 505 | 506 | for s in ['xscale', 'yscale']: 507 | if s in options: 508 | scale[s] = options.pop(s) 509 | 510 | if transform == 'exponential': 511 | complement = True 512 | scale['yscale'] = 'log' 513 | 514 | if transform == 'pareto': 515 | complement = True 516 | scale['yscale'] = 'log' 517 | scale['xscale'] = 'log' 518 | 519 | if complement: 520 | ps = [1.0-p for p in ps] 521 | 522 | if transform == 'weibull': 523 | xs = np.delete(xs, -1) 524 | ps = np.delete(ps, -1) 525 | ps = [-math.log(1.0-p) for p in ps] 526 | scale['xscale'] = 'log' 527 | scale['yscale'] = 'log' 528 | 529 | if transform == 'gumbel': 530 | xs = xp.delete(xs, 0) 531 | ps = np.delete(ps, 0) 532 | ps = [-math.log(p) for p in ps] 533 | scale['yscale'] = 'log' 534 | 535 | options = _Underride(options, label=cdf.label) 536 | Plot(xs, ps, **options) 537 | return scale 538 | 539 | 540 | def Cdfs(cdfs, complement=False, transform=None, **options): 541 | """Plots a sequence of CDFs. 542 | 543 | cdfs: sequence of CDF objects 544 | complement: boolean, whether to plot the complementary CDF 545 | transform: string, one of 'exponential', 'pareto', 'weibull', 'gumbel' 546 | options: keyword args passed to plt.plot 547 | """ 548 | for cdf in cdfs: 549 | Cdf(cdf, complement, transform, **options) 550 | 551 | 552 | def Contour(obj, pcolor=False, contour=True, imshow=False, **options): 553 | """Makes a contour plot. 554 | 555 | d: map from (x, y) to z, or object that provides GetDict 556 | pcolor: boolean, whether to make a pseudocolor plot 557 | contour: boolean, whether to make a contour plot 558 | imshow: boolean, whether to use plt.imshow 559 | options: keyword args passed to plt.pcolor and/or plt.contour 560 | """ 561 | try: 562 | d = obj.GetDict() 563 | except AttributeError: 564 | d = obj 565 | 566 | _Underride(options, linewidth=3, cmap=matplotlib.cm.Blues) 567 | 568 | xs, ys = zip(*d.keys()) 569 | xs = sorted(set(xs)) 570 | ys = sorted(set(ys)) 571 | 572 | X, Y = np.meshgrid(xs, ys) 573 | func = lambda x, y: d.get((x, y), 0) 574 | func = np.vectorize(func) 575 | Z = func(X, Y) 576 | 577 | x_formatter = matplotlib.ticker.ScalarFormatter(useOffset=False) 578 | axes = plt.gca() 579 | axes.xaxis.set_major_formatter(x_formatter) 580 | 581 | if pcolor: 582 | plt.pcolormesh(X, Y, Z, **options) 583 | if contour: 584 | cs = plt.contour(X, Y, Z, **options) 585 | plt.clabel(cs, inline=1, fontsize=10) 586 | if imshow: 587 | extent = xs[0], xs[-1], ys[0], ys[-1] 588 | plt.imshow(Z, extent=extent, **options) 589 | 590 | 591 | def Pcolor(xs, ys, zs, pcolor=True, contour=False, **options): 592 | """Makes a pseudocolor plot. 593 | 594 | xs: 595 | ys: 596 | zs: 597 | pcolor: boolean, whether to make a pseudocolor plot 598 | contour: boolean, whether to make a contour plot 599 | options: keyword args passed to plt.pcolor and/or plt.contour 600 | """ 601 | _Underride(options, linewidth=3, cmap=matplotlib.cm.Blues) 602 | 603 | X, Y = np.meshgrid(xs, ys) 604 | Z = zs 605 | 606 | x_formatter = matplotlib.ticker.ScalarFormatter(useOffset=False) 607 | axes = plt.gca() 608 | axes.xaxis.set_major_formatter(x_formatter) 609 | 610 | if pcolor: 611 | plt.pcolormesh(X, Y, Z, **options) 612 | 613 | if contour: 614 | cs = plt.contour(X, Y, Z, **options) 615 | plt.clabel(cs, inline=1, fontsize=10) 616 | 617 | 618 | def Text(x, y, s, **options): 619 | """Puts text in a figure. 620 | 621 | x: number 622 | y: number 623 | s: string 624 | options: keyword args passed to plt.text 625 | """ 626 | options = _Underride(options, 627 | fontsize=16, 628 | verticalalignment='top', 629 | horizontalalignment='left') 630 | plt.text(x, y, s, **options) 631 | 632 | 633 | LEGEND = True 634 | LOC = None 635 | 636 | def Config(**options): 637 | """Configures the plot. 638 | 639 | Pulls options out of the option dictionary and passes them to 640 | the corresponding plt functions. 641 | """ 642 | names = ['title', 'xlabel', 'ylabel', 'xscale', 'yscale', 643 | 'xticks', 'yticks', 'axis', 'xlim', 'ylim'] 644 | 645 | for name in names: 646 | if name in options: 647 | getattr(plt, name)(options[name]) 648 | 649 | global LEGEND 650 | LEGEND = options.get('legend', LEGEND) 651 | 652 | if LEGEND: 653 | global LOC 654 | LOC = options.get('loc', LOC) 655 | frameon = options.get('frameon', True) 656 | 657 | warnings.filterwarnings('error', category=UserWarning) 658 | try: 659 | plt.legend(loc=LOC, frameon=frameon) 660 | except UserWarning: 661 | pass 662 | warnings.filterwarnings('default', category=UserWarning) 663 | 664 | # x and y ticklabels can be made invisible 665 | val = options.get('xticklabels', None) 666 | if val is not None: 667 | if val == 'invisible': 668 | ax = plt.gca() 669 | labels = ax.get_xticklabels() 670 | plt.setp(labels, visible=False) 671 | 672 | val = options.get('yticklabels', None) 673 | if val is not None: 674 | if val == 'invisible': 675 | ax = plt.gca() 676 | labels = ax.get_yticklabels() 677 | plt.setp(labels, visible=False) 678 | 679 | 680 | def Show(**options): 681 | """Shows the plot. 682 | 683 | For options, see Config. 684 | 685 | options: keyword args used to invoke various plt functions 686 | """ 687 | clf = options.pop('clf', True) 688 | Config(**options) 689 | plt.show() 690 | if clf: 691 | Clf() 692 | 693 | 694 | def Plotly(**options): 695 | """Shows the plot. 696 | 697 | For options, see Config. 698 | 699 | options: keyword args used to invoke various plt functions 700 | """ 701 | clf = options.pop('clf', True) 702 | Config(**options) 703 | import plotly.plotly as plotly 704 | url = plotly.plot_mpl(plt.gcf()) 705 | if clf: 706 | Clf() 707 | return url 708 | 709 | 710 | def Save(root=None, formats=None, **options): 711 | """Saves the plot in the given formats and clears the figure. 712 | 713 | For options, see Config. 714 | 715 | Args: 716 | root: string filename root 717 | formats: list of string formats 718 | options: keyword args used to invoke various plt functions 719 | """ 720 | clf = options.pop('clf', True) 721 | 722 | save_options = {} 723 | for option in ['bbox_inches', 'pad_inches']: 724 | if option in options: 725 | save_options[option] = options.pop(option) 726 | 727 | Config(**options) 728 | 729 | if formats is None: 730 | formats = ['pdf', 'png'] 731 | 732 | try: 733 | formats.remove('plotly') 734 | Plotly(clf=False) 735 | except ValueError: 736 | pass 737 | 738 | if root: 739 | for fmt in formats: 740 | SaveFormat(root, fmt, **save_options) 741 | if clf: 742 | Clf() 743 | 744 | 745 | def SaveFormat(root, fmt='eps', **options): 746 | """Writes the current figure to a file in the given format. 747 | 748 | Args: 749 | root: string filename root 750 | fmt: string format 751 | """ 752 | _Underride(options, dpi=300) 753 | filename = '%s.%s' % (root, fmt) 754 | print('Writing', filename) 755 | plt.savefig(filename, format=fmt, **options) 756 | 757 | 758 | # provide aliases for calling functions with lower-case names 759 | preplot = PrePlot 760 | subplot = SubPlot 761 | clf = Clf 762 | figure = Figure 763 | plot = Plot 764 | vlines = Vlines 765 | hlines = Hlines 766 | fill_between = FillBetween 767 | text = Text 768 | scatter = Scatter 769 | pmf = Pmf 770 | pmfs = Pmfs 771 | hist = Hist 772 | hists = Hists 773 | diff = Diff 774 | cdf = Cdf 775 | cdfs = Cdfs 776 | contour = Contour 777 | pcolor = Pcolor 778 | config = Config 779 | show = Show 780 | save = Save 781 | 782 | 783 | def main(): 784 | color_iter = _Brewer.ColorGenerator(7) 785 | for color in color_iter: 786 | print(color) 787 | 788 | 789 | if __name__ == '__main__': 790 | main() 791 | --------------------------------------------------------------------------------