├── .gitignore ├── README.md ├── bootstrap.py ├── requirements.txt ├── roadSim.py ├── script.py ├── simpy ├── __init__.py ├── _compat.py ├── core.py ├── events.py ├── exceptions.py ├── resources │ ├── __init__.py │ ├── base.py │ ├── container.py │ ├── resource.py │ └── store.py ├── rt.py └── util.py └── simulation ├── __init__.py ├── distribution.py ├── roadnetwork.py ├── simulation.py ├── siouxfalls.py └── statistics.py /.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 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 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 | local_settings.py 56 | 57 | # Flask stuff: 58 | instance/ 59 | .webassets-cache 60 | 61 | # Scrapy stuff: 62 | .scrapy 63 | 64 | # Sphinx documentation 65 | docs/_build/ 66 | 67 | # PyBuilder 68 | target/ 69 | 70 | # Jupyter Notebook 71 | .ipynb_checkpoints 72 | 73 | # pyenv 74 | .python-version 75 | 76 | # celery beat schedule file 77 | celerybeat-schedule 78 | 79 | # SageMath parsed files 80 | *.sage.py 81 | 82 | # dotenv 83 | .env 84 | 85 | # virtualenv 86 | .venv 87 | venv/ 88 | ENV/ 89 | 90 | # Spyder project settings 91 | .spyderproject 92 | .spyproject 93 | 94 | # Rope project settings 95 | .ropeproject 96 | 97 | # mkdocs documentation 98 | /site 99 | 100 | # mypy 101 | .mypy_cache/ 102 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | simulation 2 | ========== 3 | 4 | 5 | Requriements 6 | ------------ 7 | 8 | Requirements can be installed with Conda and pip 9 | 10 | Python (3.5+) https://www.python.org/downloads/ 11 | 12 | NumPy (1.13+) https://scipy.org/install.html 13 | 14 | Matplotlib (2.02+) https://matplotlib.org/ 15 | 16 | SimPy (3.0.10+) https://simpy.readthedocs.io/ 17 | 18 | opencv (3.0+) http://www.opencv.org/ 19 | 20 | Conda setup 21 | ----------- 22 | 23 | Get miniconda from [here](https://docs.conda.io/en/latest/miniconda.html) 24 | 25 | Install git: 26 | 27 | > conda install git 28 | 29 | Clone this repository 30 | 31 | > git clone https://github.com/mwong009/simulation.git simulation 32 | 33 | cd into the directory 34 | 35 | > cd simulation 36 | 37 | Installing 38 | ---------- 39 | 40 | From the project directory: 41 | 42 | Windows: 43 | 44 | C:\...\simulation> pip install -r requirements.txt 45 | 46 | Linux: 47 | 48 | /.../simulation> pip3 install -r requirements.txt 49 | 50 | 51 | 52 | Usage and Documentation 53 | ----------------------- 54 | SimPy is a process-based discrete-event simulation framework based on standard Python. Processes in SimPy are defined by Python generator functions and may, for example, be used to model active components like customers, vehicles or agents. SimPy also provides various types of shared resources to model limited capacity congestion points (like servers, checkout counters and tunnels). 55 | 56 | From the project directory, run the command: 57 | 58 | > python script.py 59 | 60 | On linux: 61 | 62 | > python3 script.py 63 | 64 | **Hit spacebar to start** 65 | 66 | Sample output: 67 | 68 | car 1 arrived on link 1E at 0.03s (Q=0) 69 | car 2 arrived on link 2S at 0.63s (Q=0) 70 | car 3 arrived on link 6N at 0.73s (Q=0) 71 | ... 72 | car 1 departed link 4E at 1.29s (Q=0) 73 | car 6 arrived on link 6N at 1.71s (Q=0) 74 | car 6 departed link 6N at 1.83s (Q=0) 75 | ... 76 | car 96 arrived on link 7E at 37.20s (Q=0) 77 | car 96 departed link 7E at 37.20s (Q=0) 78 | car 99 departed link 6N at 38.10s (Q=0) 79 | car 99 arrived on link 7E at 39.06s (Q=0) 80 | car 99 departed link 7E at 39.06s (Q=0) 81 | 82 | Statistics can be generated 83 | 84 | [514 rows x 6 columns] 85 | totalTravelTime totalSegments meanWaitTime 86 | carID 87 | 1 3.759368 3 0.372207 88 | 2 7.340689 3 1.282778 89 | 3 2.007794 2 0.064265 90 | 4 1.956925 1 1.140182 91 | 5 5.664470 3 0.986793 92 | 6 1.404893 2 0.055849 93 | 7 4.237412 1 3.289740 94 | 8 3.490860 1 3.139994 95 | 9 2.345139 1 1.451333 96 | 10 4.175994 1 3.244251 97 | 11 7.098955 3 1.487988 98 | ... 99 | 100 | Plotting done on Matplotlib 101 | -------------------------------------------------------------------------------- /bootstrap.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import simpy 3 | import cv2 4 | import timeit 5 | import numpy as np 6 | import matplotlib.pyplot as plt 7 | import pandas as pd 8 | import simulation 9 | 10 | import simulation.statistics as stats 11 | from simulation.roadnetwork import RoadNetwork 12 | from simulation.simulation import Simulation 13 | from simulation.siouxfalls import SiouxFalls 14 | 15 | plt.style.use('ggplot') 16 | 17 | class Bootstrap(object): 18 | def __init__(self, env): 19 | self.env = env 20 | 21 | # create simulation enviromment 22 | img = np.zeros((900, 800, 3), dtype=np.uint8) 23 | self.sim = Simulation(self.env, img) 24 | 25 | def processSimulation(self): 26 | # initialize Sioux Falls network 27 | siouxfalls = SiouxFalls(0.0025) 28 | 29 | # create Sioux Fall network by enumerating across all links 30 | for linkid, t0 in enumerate(siouxfalls.t0): 31 | 32 | # calculate length of link with sqrt((x1 - x2)^2 + (y1 - y2)^2) 33 | length = np.sqrt( 34 | np.power(siouxfalls.x1[linkid] - siouxfalls.x2[linkid], 2) 35 | + np.power(siouxfalls.y1[linkid] - siouxfalls.y2[linkid], 2)) / 600. 36 | mu = siouxfalls.mu[linkid] 37 | 38 | # assign nodeID to each link if check pass in node list 39 | for i, node in enumerate(siouxfalls.nodes): 40 | if linkid+1 in node: 41 | nodeID = i 42 | 43 | # assign turn ratio to each link 44 | turns = {} 45 | for j, turn in enumerate(siouxfalls.turns[linkid]): 46 | turns[j + 1] = turn 47 | 48 | # assign exit probability from last item in turn list ([-1]) 49 | turns['exit'] = turns.pop(list(turns.keys())[-1]) 50 | 51 | # generate coordinates of each link (for visualization) 52 | pt1 = (np.float32(siouxfalls.x1[linkid] / 600.), 53 | np.float32(siouxfalls.y1[linkid] / 600.)) 54 | 55 | pt2 = (np.float32(siouxfalls.x2[linkid] / 600.), 56 | np.float32(siouxfalls.y2[linkid] / 600.)) 57 | 58 | c = (pt1, pt2) 59 | 60 | # draw link on map 61 | self.sim.networkLines.append(c) 62 | 63 | # add link to sim.network 64 | self.sim.network.addLink(linkID=linkid+1, turns=turns, 65 | type='link', length=length, 66 | t0=t0, MU=mu, nodeID=nodeID, 67 | coordinates=c) 68 | 69 | # initialize car generation 70 | self.env.process(self.sim.source( 71 | 10, LAMBDA=siouxfalls.flambda[linkid], linkid=linkid+1)) 72 | 73 | yield self.env.timeout(1) 74 | 75 | def main(): 76 | 77 | name = 'Sioux Falls Network' 78 | 79 | ######################## 80 | # bootstrap parameters # 81 | ######################## 82 | boot = 5 83 | 84 | ################################# 85 | # initialize discrete event env # 86 | ################################# 87 | env = simpy.Environment() # use instant simulation 88 | # env = simpy.rt.RealtimeEnvironment(factor=1.) # use real time simulation 89 | 90 | # setup simulation processes 91 | bsProcess = [] 92 | for b in range(boot): 93 | bs = Bootstrap(env) 94 | env.process(bs.processSimulation()) 95 | bsProcess.append(bs) 96 | 97 | start_time = timeit.default_timer() # start simulation timer 98 | 99 | env.run() 100 | 101 | end_time = timeit.default_timer() # end simulation timer 102 | 103 | # compile simulation statistics 104 | bsTable = None 105 | for n, bootstrap in enumerate(bsProcess): 106 | df = pd.DataFrame(sorted(bootstrap.sim.data, key=lambda x: x[3]), 107 | columns=['carID', 'link', 'event', 108 | 'time', 'queue', 't_queue']) 109 | 110 | meanQlength = df.loc[df['event'] == 'departure'][ 111 | ['link', 'queue']].groupby(['link']).mean() 112 | meanQlength.columns=['mean'] 113 | 114 | varQlength = df.loc[df['event'] == 'departure'][ 115 | ['link', 'queue']].groupby(['link']).var() 116 | varQlength.columns=['variance'] 117 | 118 | maxQlength = df.loc[df['event'] == 'departure'][ 119 | ['link', 'queue']].groupby(['link']).max() 120 | maxQlength.columns=['max'] 121 | 122 | if bsTable is None: 123 | bsTable = maxQlength 124 | bsTable.columns = [1] 125 | 126 | else: 127 | bsTable[n+1] = maxQlength 128 | 129 | mean = bsTable.mean(axis=1) 130 | mse = bsTable.var(axis=1, ddof=0) 131 | bsTable['mean'] = mean 132 | bsTable['MSE'] = mse 133 | 134 | print('Simulation runtime: %.3fs' % (end_time-start_time)) 135 | 136 | with pd.option_context('expand_frame_repr', False): 137 | print(bsTable) 138 | 139 | # Standard boilerplate to call the main() function to begin 140 | # the program. 141 | if __name__ == '__main__': 142 | main() 143 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | numpy 2 | scipy 3 | matplotlib 4 | opencv-python -------------------------------------------------------------------------------- /roadSim.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import matplotlib.pyplot as plt 3 | import pandas as pd 4 | import simpy, cv2, sys, os 5 | 6 | from Simulation import * 7 | from statistics import * 8 | 9 | plt.style.use('ggplot') 10 | 11 | def road(): 12 | env = simpy.Environment() 13 | # env = simpy.rt.RealtimeEnvironment(factor=0.8) 14 | img = np.zeros((400, 400, 3), dtype=np.uint8) 15 | sim = Simulation(env, img) 16 | c = ((np.float32(100.), np.float32(200.)), (np.float32(300.), np.float32(200.))) 17 | sim.network.addLink( 18 | linkID=1, 19 | turns={'exit': 1.}, 20 | type='link', 21 | length=20, 22 | t0=1, 23 | MU=1, 24 | coordinates=c 25 | ) 26 | 27 | # draw link on map 28 | sim.networkLines.append(c) 29 | 30 | # initialize car generation 31 | env.process(sim.source(10, LAMBDA=1, linkid=1)) 32 | 33 | # draw initial network 34 | for i in sim.networkLines: 35 | cv2.line(sim.img, i[0], i[1], (255,255,255), 3) 36 | name = 'Single Road' 37 | cv2.imshow(name, sim.img) 38 | 39 | # start visualization update process 40 | env.process(sim.visualization(frequency=0.2, name=name)) 41 | 42 | # wait for keypress to start simulation 43 | print('press space to start') 44 | k = cv2.waitKey(0) 45 | if k == 27: 46 | sys.exit() 47 | # run simulation 48 | env.run() 49 | 50 | print('Press any key to exit') 51 | cv2.waitKey(0) 52 | 53 | if __name__ == '__main__': 54 | road() 55 | -------------------------------------------------------------------------------- /script.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import simpy 3 | import cv2 4 | import numpy as np 5 | import matplotlib.pyplot as plt 6 | import pandas as pd 7 | import simulation 8 | 9 | import simulation.statistics as stats 10 | from simulation.roadnetwork import RoadNetwork 11 | from simulation.simulation import Simulation 12 | from simulation.siouxfalls import SiouxFalls 13 | 14 | plt.style.use('ggplot') 15 | 16 | 17 | def main(): 18 | ################################# 19 | # initialize discrete event env # 20 | ################################# 21 | env = simpy.Environment() # use instant simulation 22 | # env = simpy.rt.RealtimeEnvironment(factor=1.) # use real time simulation 23 | 24 | # initialize Sioux Falls network 25 | siouxfalls = SiouxFalls(0.0025) 26 | 27 | # create simulation enviromment 28 | img = np.zeros((900, 800, 3), dtype=np.uint8) 29 | sim = Simulation(env, img) 30 | 31 | # create Sioux Fall network by enumerating across all links 32 | for linkid, t0 in enumerate(siouxfalls.t0): 33 | 34 | # calculate length of link with sqrt((x1 - x2)^2 + (y1 - y2)^2) 35 | length = np.sqrt( 36 | np.power(siouxfalls.x1[linkid] - siouxfalls.x2[linkid], 2) 37 | + np.power(siouxfalls.y1[linkid] - siouxfalls.y2[linkid], 2)) / 600. 38 | mu = siouxfalls.mu[linkid] 39 | 40 | # assign nodeID to each link if check pass in node list 41 | for i, node in enumerate(siouxfalls.nodes): 42 | if linkid+1 in node: 43 | nodeID = i 44 | 45 | # assign turn ratio to each link 46 | turns = {} 47 | for j, turn in enumerate(siouxfalls.turns[linkid]): 48 | turns[j + 1] = turn 49 | 50 | # assign exit probability from last item in turn list ([-1]) 51 | turns['exit'] = turns.pop(list(turns.keys())[-1]) 52 | 53 | # generate coordinates of each link (for visualization) 54 | pt1 = (np.float32(siouxfalls.x1[linkid] / 600.), 55 | np.float32(siouxfalls.y1[linkid] / 600.)) 56 | 57 | pt2 = (np.float32(siouxfalls.x2[linkid] / 600.), 58 | np.float32(siouxfalls.y2[linkid] / 600.)) 59 | 60 | c = (pt1, pt2) 61 | 62 | # draw link on map 63 | sim.networkLines.append(c) 64 | 65 | # add link to sim.network 66 | sim.network.addLink(linkID=linkid+1, turns=turns, 67 | type='link', length=length, 68 | t0=t0, MU=mu, nodeID=nodeID, 69 | coordinates=c) 70 | 71 | # initialize car generation 72 | if linkid % 3 == 0: 73 | env.process(sim.source(10, 74 | LAMBDA=siouxfalls.flambda[linkid], 75 | linkid=linkid+1)) 76 | 77 | # draw initial network 78 | for i in sim.networkLines: 79 | cv2.line(sim.img, i[0], i[1], (255, 255, 255), 3) 80 | 81 | for linkid in sim.network.links: 82 | loc = (0.25 * np.asarray(sim.network.links[linkid]['coordinates'][1]) + 83 | 0.75 * np.asarray(sim.network.links[linkid]['coordinates'][0])) 84 | 85 | cv2.putText(sim.img, str(linkid), tuple(loc), 86 | cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255)) 87 | 88 | name = 'Sioux Falls Network' 89 | cv2.imshow(name, sim.img) 90 | 91 | # start visualization update process 92 | # frequency is the visualization poll rate, smaller = faster polling 93 | env.process(sim.visualization(frequency=0.2, name=name)) 94 | 95 | # wait for keypress to start simulation 96 | print('press space to start') 97 | k = cv2.waitKey(0) 98 | if k == 27: 99 | sys.exit() 100 | 101 | # run simulation 102 | env.run() 103 | 104 | ###################################### 105 | # simulation statistics and graphing # 106 | ###################################### 107 | df = pd.DataFrame(sorted(sim.data, key=lambda x: x[3]), 108 | columns=['carID', 'link', 'event', 'time', 'queue', 109 | 't_queue']) 110 | print(df) 111 | 112 | # cars statistics 113 | totalTravelTime = (df[['carID', 'time']].groupby(['carID']).max() 114 | - df[['carID', 'time']].groupby(['carID']).min()) 115 | totalTravelTime.columns = ['totalTravelTime'] 116 | 117 | totalSegments = df.loc[df['event'] == 'arrival'][['carID', 'link']] 118 | totalSegments = totalSegments.groupby(['carID']).count() 119 | totalSegments.columns = ['totalSegments'] 120 | 121 | meanWaitTime = df.loc[df['event'] == 'departure'][['carID', 't_queue']] 122 | meanWaitTime = meanWaitTime.groupby(['carID']).mean() 123 | meanWaitTime.columns = ['meanWaitTime'] 124 | 125 | carStatistics = pd.concat([totalTravelTime, totalSegments, meanWaitTime], 126 | axis=1) 127 | 128 | # links statistics 129 | stats.meanQueueLength(plt, df) 130 | 131 | plt.figure(2) 132 | 133 | for link in sim.network.links.keys(): 134 | df2 = df.loc[(df['link'] == link) & (df['event'] != 'entry')] 135 | 136 | if df2.empty is False and df2['t_queue'].sum() > 0.: 137 | plt.plot(df2[['time']], df2[['queue']], label='link %s' % link) 138 | 139 | plt.title('Queueing Simulation') 140 | plt.ylabel('queue length') 141 | plt.xlabel('time (s)') 142 | plt.legend() 143 | plt.show() 144 | 145 | print('Press any key to exit') 146 | cv2.waitKey(0) 147 | 148 | 149 | # Standard boilerplate to call the main() function to begin 150 | # the program. 151 | if __name__ == '__main__': 152 | main() 153 | -------------------------------------------------------------------------------- /simpy/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | The ``simpy`` module aggregates SimPy's most used components into a single 3 | namespace. This is purely for convenience. You can of course also access 4 | everything (and more!) via their actual submodules. 5 | 6 | The following tables list all of the available components in this module. 7 | 8 | {toc} 9 | 10 | """ 11 | from pkgutil import extend_path 12 | 13 | from simpy.core import Environment 14 | from simpy.rt import RealtimeEnvironment 15 | from simpy.exceptions import SimPyException, Interrupt, StopProcess 16 | from simpy.events import Event, Timeout, Process, AllOf, AnyOf 17 | from simpy.resources.resource import ( 18 | Resource, PriorityResource, PreemptiveResource) 19 | from simpy.resources.container import Container 20 | from simpy.resources.store import ( 21 | Store, PriorityItem, PriorityStore, FilterStore) 22 | 23 | 24 | def compile_toc(entries, section_marker='='): 25 | """Compiles a list of sections with objects into sphinx formatted 26 | autosummary directives.""" 27 | toc = '' 28 | for section, objs in entries: 29 | toc += '\n\n%s\n%s\n\n' % (section, section_marker * len(section)) 30 | toc += '.. autosummary::\n\n' 31 | for obj in objs: 32 | toc += ' ~%s.%s\n' % (obj.__module__, obj.__name__) 33 | return toc 34 | 35 | 36 | toc = ( 37 | ('Environments', ( 38 | Environment, RealtimeEnvironment, 39 | )), 40 | ('Events', ( 41 | Event, Timeout, Process, AllOf, AnyOf, Interrupt, 42 | )), 43 | ('Resources', ( 44 | Resource, PriorityResource, PreemptiveResource, Container, Store, 45 | PriorityItem, PriorityStore, FilterStore, 46 | )), 47 | ('Exceptions', ( 48 | SimPyException, Interrupt, StopProcess, 49 | )), 50 | ) 51 | 52 | # Use the toc to keep the documentation and the implementation in sync. 53 | if __doc__: 54 | __doc__ = __doc__.format(toc=compile_toc(toc)) 55 | __all__ = [obj.__name__ for section, objs in toc for obj in objs] 56 | 57 | __path__ = extend_path(__path__, __name__) 58 | __version__ = '3.0.10' 59 | -------------------------------------------------------------------------------- /simpy/_compat.py: -------------------------------------------------------------------------------- 1 | """ 2 | Compatibility helpers for older Python versions. 3 | 4 | """ 5 | import sys 6 | 7 | 8 | PY2 = sys.version_info[0] == 2 9 | 10 | 11 | if PY2: # NOQA 12 | # Python 2.x does not report exception chains. To emulate the behaviour of 13 | # Python 3 traceback.format_exception and traceback.print_exception are 14 | # overwritten with the custom functions. The original functions 15 | # are stored in _format_exception and _print_exception. 16 | import traceback 17 | from collections import deque 18 | 19 | _print_exception = traceback.print_exception 20 | _format_exception = traceback.format_exception 21 | 22 | def print_exception(etype, value, tb, limit=None, file=None): 23 | if file is None: 24 | file = sys.stderr 25 | 26 | # Build the exception chain. 27 | chain = deque() 28 | cause = value 29 | while True: 30 | cause = cause.__dict__.get('__cause__', None) 31 | if cause is None: 32 | break 33 | chain.appendleft(cause) 34 | 35 | # Print the exception chain. 36 | for cause in chain: 37 | _print_exception(type(cause), cause, 38 | cause.__dict__.get('__traceback__', None), 39 | limit, file) 40 | traceback._print(file, '\nThe above exception was the direct ' 41 | 'cause of the following exception:\n') 42 | 43 | _print_exception(etype, value, tb, limit, file) 44 | 45 | traceback.print_exception = print_exception 46 | 47 | def format_exception(etype, value, tb, limit=None): 48 | # Build the exception chain. 49 | chain = deque() 50 | cause = value 51 | while True: 52 | cause = cause.__dict__.get('__cause__', None) 53 | if cause is None: 54 | break 55 | chain.appendleft(cause) 56 | 57 | # Format the exception chain. 58 | lines = [] 59 | for cause in chain: 60 | lines.extend(_format_exception( 61 | type(cause), cause, cause.__dict__.get('__traceback__', None), 62 | limit)) 63 | lines.append('\nThe above exception was the direct ' 64 | 'cause of the following exception:\n\n') 65 | 66 | lines.extend(_format_exception(etype, value, tb, limit)) 67 | 68 | return lines 69 | 70 | traceback.format_exception = format_exception 71 | 72 | sys.excepthook = print_exception 73 | -------------------------------------------------------------------------------- /simpy/core.py: -------------------------------------------------------------------------------- 1 | """ 2 | Core components for event-discrete simulation environments. 3 | 4 | """ 5 | import types 6 | from heapq import heappush, heappop 7 | from itertools import count 8 | 9 | from simpy.exceptions import StopProcess 10 | from simpy.events import (AllOf, AnyOf, Event, Process, Timeout, URGENT, 11 | NORMAL) 12 | 13 | 14 | Infinity = float('inf') #: Convenience alias for infinity 15 | 16 | 17 | class BoundClass(object): 18 | """Allows classes to behave like methods. 19 | 20 | The ``__get__()`` descriptor is basically identical to 21 | ``function.__get__()`` and binds the first argument of the ``cls`` to the 22 | descriptor instance. 23 | 24 | """ 25 | def __init__(self, cls): 26 | self.cls = cls 27 | 28 | def __get__(self, obj, type=None): 29 | if obj is None: 30 | return self.cls 31 | return types.MethodType(self.cls, obj) 32 | 33 | @staticmethod 34 | def bind_early(instance): 35 | """Bind all :class:`BoundClass` attributes of the *instance's* class 36 | to the instance itself to increase performance.""" 37 | cls = type(instance) 38 | for name, obj in cls.__dict__.items(): 39 | if type(obj) is BoundClass: 40 | bound_class = getattr(instance, name) 41 | setattr(instance, name, bound_class) 42 | 43 | 44 | class EmptySchedule(Exception): 45 | """Thrown by an :class:`Environment` if there are no further events to be 46 | processed.""" 47 | pass 48 | 49 | 50 | class StopSimulation(Exception): 51 | """Indicates that the simulation should stop now.""" 52 | 53 | @classmethod 54 | def callback(cls, event): 55 | """Used as callback in :meth:`BaseEnvironment.run()` to stop the 56 | simulation when the *until* event occurred.""" 57 | if event.ok: 58 | raise cls(event.value) 59 | else: 60 | raise event.value 61 | 62 | 63 | class BaseEnvironment(object): 64 | """Base class for event processing environments. 65 | 66 | An implementation must at least provide the means to access the current 67 | time of the environment (see :attr:`now`) and to schedule (see 68 | :meth:`schedule()`) events as well as processing them (see :meth:`step()`. 69 | 70 | The class is meant to be subclassed for different execution environments. 71 | For example, SimPy defines a :class:`Environment` for simulations with 72 | a virtual time and and a :class:`~simpy.rt.RealtimeEnvironment` that 73 | schedules and executes events in real (e.g., wallclock) time. 74 | 75 | """ 76 | @property 77 | def now(self): 78 | """The current time of the environment.""" 79 | raise NotImplementedError(self) 80 | 81 | @property 82 | def active_process(self): 83 | """The currently active process of the environment.""" 84 | raise NotImplementedError(self) 85 | 86 | def schedule(self, event, priority=NORMAL, delay=0): 87 | """Schedule an *event* with a given *priority* and a *delay*. 88 | 89 | There are two default priority values, :data:`~simpy.events.URGENT` and 90 | :data:`~simpy.events.NORMAL`. 91 | 92 | """ 93 | raise NotImplementedError(self) 94 | 95 | def step(self): 96 | """Processes the next event.""" 97 | raise NotImplementedError(self) 98 | 99 | def run(self, until=None): 100 | """Executes :meth:`step()` until the given criterion *until* is met. 101 | 102 | - If it is ``None`` (which is the default), this method will return 103 | when there are no further events to be processed. 104 | 105 | - If it is an :class:`~simpy.events.Event`, the method will continue 106 | stepping until this event has been triggered and will return its 107 | value. Raises a :exc:`RuntimeError` if there are no further events 108 | to be processed and the *until* event was not triggered. 109 | 110 | - If it is a number, the method will continue stepping 111 | until the environment's time reaches *until*. 112 | 113 | """ 114 | if until is not None: 115 | if not isinstance(until, Event): 116 | # Assume that *until* is a number if it is not None and 117 | # not an event. Create a Timeout(until) in this case. 118 | at = float(until) 119 | 120 | if at <= self.now: 121 | raise ValueError('until(=%s) should be > the current ' 122 | 'simulation time.' % at) 123 | 124 | # Schedule the event before all regular timeouts. 125 | until = Event(self) 126 | until._ok = True 127 | until._value = None 128 | self.schedule(until, URGENT, at - self.now) 129 | 130 | elif until.callbacks is None: 131 | # Until event has already been processed. 132 | return until.value 133 | 134 | until.callbacks.append(StopSimulation.callback) 135 | 136 | try: 137 | while True: 138 | self.step() 139 | except StopSimulation as exc: 140 | return exc.args[0] # == until.value 141 | except EmptySchedule: 142 | if until is not None: 143 | assert not until.triggered 144 | raise RuntimeError('No scheduled events left but "until" ' 145 | 'event was not triggered: %s' % until) 146 | 147 | def exit(self, value=None): 148 | """Stop the current process, optionally providing a ``value``. 149 | 150 | This is a convenience function provided for Python versions prior to 151 | 3.3. From Python 3.3, you can instead use ``return value`` in 152 | a process. 153 | 154 | """ 155 | raise StopProcess(value) 156 | 157 | 158 | class Environment(BaseEnvironment): 159 | """Execution environment for an event-based simulation. The passing of time 160 | is simulated by stepping from event to event. 161 | 162 | You can provide an *initial_time* for the environment. By default, it 163 | starts at ``0``. 164 | 165 | This class also provides aliases for common event types, for example 166 | :attr:`process`, :attr:`timeout` and :attr:`event`. 167 | 168 | """ 169 | def __init__(self, initial_time=0): 170 | self._now = initial_time 171 | self._queue = [] # The list of all currently scheduled events. 172 | self._eid = count() # Counter for event IDs 173 | self._active_proc = None 174 | 175 | # Bind all BoundClass instances to "self" to improve performance. 176 | BoundClass.bind_early(self) 177 | 178 | @property 179 | def now(self): 180 | """The current simulation time.""" 181 | return self._now 182 | 183 | @property 184 | def active_process(self): 185 | """The currently active process of the environment.""" 186 | return self._active_proc 187 | 188 | process = BoundClass(Process) 189 | timeout = BoundClass(Timeout) 190 | event = BoundClass(Event) 191 | all_of = BoundClass(AllOf) 192 | any_of = BoundClass(AnyOf) 193 | 194 | def schedule(self, event, priority=NORMAL, delay=0): 195 | """Schedule an *event* with a given *priority* and a *delay*.""" 196 | heappush(self._queue, 197 | (self._now + delay, priority, next(self._eid), event)) 198 | 199 | def peek(self): 200 | """Get the time of the next scheduled event. Return 201 | :data:`~simpy.core.Infinity` if there is no further event.""" 202 | try: 203 | return self._queue[0][0] 204 | except IndexError: 205 | return Infinity 206 | 207 | def step(self): 208 | """Process the next event. 209 | 210 | Raise an :exc:`EmptySchedule` if no further events are available. 211 | 212 | """ 213 | try: 214 | self._now, _, _, event = heappop(self._queue) 215 | except IndexError: 216 | raise EmptySchedule() 217 | 218 | # Process callbacks of the event. Set the events callbacks to None 219 | # immediately to prevent concurrent modifications. 220 | callbacks, event.callbacks = event.callbacks, None 221 | for callback in callbacks: 222 | callback(event) 223 | 224 | if not event._ok and not hasattr(event, '_defused'): 225 | # The event has failed and has not been defused. Crash the 226 | # environment. 227 | # Create a copy of the failure exception with a new traceback. 228 | exc = type(event._value)(*event._value.args) 229 | exc.__cause__ = event._value 230 | raise exc 231 | -------------------------------------------------------------------------------- /simpy/events.py: -------------------------------------------------------------------------------- 1 | """ 2 | This module contains the basic event types used in SimPy. 3 | 4 | The base class for all events is :class:`Event`. Though it can be directly 5 | used, there are several specialized subclasses of it. 6 | 7 | .. autosummary:: 8 | 9 | ~simpy.events.Event 10 | ~simpy.events.Timeout 11 | ~simpy.events.Process 12 | ~simpy.events.AnyOf 13 | ~simpy.events.AllOf 14 | 15 | """ 16 | from simpy._compat import PY2 17 | from simpy.exceptions import Interrupt, StopProcess 18 | 19 | if PY2: 20 | import sys 21 | 22 | 23 | PENDING = object() 24 | """Unique object to identify pending values of events.""" 25 | 26 | URGENT = 0 27 | """Priority of interrupts and process initialization events.""" 28 | NORMAL = 1 29 | """Default priority used by events.""" 30 | 31 | 32 | class Event(object): 33 | """An event that may happen at some point in time. 34 | 35 | An event 36 | 37 | - may happen (:attr:`triggered` is ``False``), 38 | - is going to happen (:attr:`triggered` is ``True``) or 39 | - has happened (:attr:`processed` is ``True``). 40 | 41 | Every event is bound to an environment *env* and is initially not 42 | triggered. Events are scheduled for processing by the environment after 43 | they are triggered by either :meth:`succeed`, :meth:`fail` or 44 | :meth:`trigger`. These methods also set the *ok* flag and the *value* of 45 | the event. 46 | 47 | An event has a list of :attr:`callbacks`. A callback can be any callable. 48 | Once an event gets processed, all callbacks will be invoked with the event 49 | as the single argument. Callbacks can check if the event was successful by 50 | examining *ok* and do further processing with the *value* it has produced. 51 | 52 | Failed events are never silently ignored and will raise an exception upon 53 | being processed. If a callback handles an exception, it must set 54 | :attr:`defused` to ``True`` to prevent this. 55 | 56 | This class also implements ``__and__()`` (``&``) and ``__or__()`` (``|``). 57 | If you concatenate two events using one of these operators, 58 | a :class:`Condition` event is generated that lets you wait for both or one 59 | of them. 60 | 61 | """ 62 | def __init__(self, env): 63 | self.env = env 64 | """The :class:`~simpy.core.Environment` the event lives in.""" 65 | self.callbacks = [] 66 | """List of functions that are called when the event is processed.""" 67 | self._value = PENDING 68 | 69 | def __repr__(self): 70 | """Return the description of the event (see :meth:`_desc`) with the id 71 | of the event.""" 72 | return '<%s object at 0x%x>' % (self._desc(), id(self)) 73 | 74 | def _desc(self): 75 | """Return a string *Event()*.""" 76 | return '%s()' % self.__class__.__name__ 77 | 78 | @property 79 | def triggered(self): 80 | """Becomes ``True`` if the event has been triggered and its callbacks 81 | are about to be invoked.""" 82 | return self._value is not PENDING 83 | 84 | @property 85 | def processed(self): 86 | """Becomes ``True`` if the event has been processed (e.g., its 87 | callbacks have been invoked).""" 88 | return self.callbacks is None 89 | 90 | @property 91 | def ok(self): 92 | """Becomes ``True`` when the event has been triggered successfully. 93 | 94 | A "successful" event is one triggered with :meth:`succeed()`. 95 | 96 | :raises AttributeError: if accessed before the event is triggered. 97 | 98 | """ 99 | return self._ok 100 | 101 | @property 102 | def defused(self): 103 | """Becomes ``True`` when the failed event's exception is "defused". 104 | 105 | When an event fails (i.e. with :meth:`fail()`), the failed event's 106 | `value` is an exception that will be re-raised when the 107 | :class:`~simpy.core.Environment` processes the event (i.e. in 108 | :meth:`~simpy.core.Environment.step()`). 109 | 110 | It is also possible for the failed event's exception to be defused by 111 | setting :attr:`defused` to ``True`` from an event callback. Doing so 112 | prevents the event's exception from being re-raised when the event is 113 | processed by the :class:`~simpy.core.Environment`. 114 | 115 | """ 116 | return hasattr(self, '_defused') 117 | 118 | @defused.setter 119 | def defused(self, value): 120 | self._defused = True 121 | 122 | @property 123 | def value(self): 124 | """The value of the event if it is available. 125 | 126 | The value is available when the event has been triggered. 127 | 128 | Raises :exc:`AttributeError` if the value is not yet available. 129 | 130 | """ 131 | if self._value is PENDING: 132 | raise AttributeError('Value of %s is not yet available' % self) 133 | return self._value 134 | 135 | def trigger(self, event): 136 | """Trigger the event with the state and value of the provided *event*. 137 | Return *self* (this event instance). 138 | 139 | This method can be used directly as a callback function to trigger 140 | chain reactions. 141 | 142 | """ 143 | self._ok = event._ok 144 | self._value = event._value 145 | self.env.schedule(self) 146 | 147 | def succeed(self, value=None): 148 | """Set the event's value, mark it as successful and schedule it for 149 | processing by the environment. Returns the event instance. 150 | 151 | Raises :exc:`RuntimeError` if this event has already been triggerd. 152 | 153 | """ 154 | if self._value is not PENDING: 155 | raise RuntimeError('%s has already been triggered' % self) 156 | 157 | self._ok = True 158 | self._value = value 159 | self.env.schedule(self) 160 | return self 161 | 162 | def fail(self, exception): 163 | """Set *exception* as the events value, mark it as failed and schedule 164 | it for processing by the environment. Returns the event instance. 165 | 166 | Raises :exc:`ValueError` if *exception* is not an :exc:`Exception`. 167 | 168 | Raises :exc:`RuntimeError` if this event has already been triggered. 169 | 170 | """ 171 | if self._value is not PENDING: 172 | raise RuntimeError('%s has already been triggered' % self) 173 | if not isinstance(exception, BaseException): 174 | raise ValueError('%s is not an exception.' % exception) 175 | self._ok = False 176 | self._value = exception 177 | self.env.schedule(self) 178 | return self 179 | 180 | def __and__(self, other): 181 | """Return a :class:`~simpy.events.Condition` that will be triggered if 182 | both, this event and *other*, have been processed.""" 183 | return Condition(self.env, Condition.all_events, [self, other]) 184 | 185 | def __or__(self, other): 186 | """Return a :class:`~simpy.events.Condition` that will be triggered if 187 | either this event or *other* have been processed (or even both, if they 188 | happened concurrently).""" 189 | return Condition(self.env, Condition.any_events, [self, other]) 190 | 191 | 192 | class Timeout(Event): 193 | """A :class:`~simpy.events.Event` that gets triggered after a *delay* has 194 | passed. 195 | 196 | This event is automatically triggered when it is created. 197 | 198 | """ 199 | def __init__(self, env, delay, value=None): 200 | if delay < 0: 201 | raise ValueError('Negative delay %s' % delay) 202 | # NOTE: The following initialization code is inlined from 203 | # Event.__init__() for performance reasons. 204 | self.env = env 205 | self.callbacks = [] 206 | self._value = value 207 | self._delay = delay 208 | self._ok = True 209 | env.schedule(self, NORMAL, delay) 210 | 211 | def _desc(self): 212 | """Return a string *Timeout(delay[, value=value])*.""" 213 | return '%s(%s%s)' % (self.__class__.__name__, self._delay, 214 | '' if self._value is None else 215 | (', value=%s' % self._value)) 216 | 217 | 218 | class Initialize(Event): 219 | """Initializes a process. Only used internally by :class:`Process`. 220 | 221 | This event is automatically triggered when it is created. 222 | 223 | """ 224 | def __init__(self, env, process): 225 | # NOTE: The following initialization code is inlined from 226 | # Event.__init__() for performance reasons. 227 | self.env = env 228 | self.callbacks = [process._resume] 229 | self._value = None 230 | 231 | # The initialization events needs to be scheduled as urgent so that it 232 | # will be handled before interrupts. Otherwise a process whose 233 | # generator has not yet been started could be interrupted. 234 | self._ok = True 235 | env.schedule(self, URGENT) 236 | 237 | 238 | class Interruption(Event): 239 | """Immediately schedules an :class:`~simpy.exceptions.Interrupt` exception 240 | with the given *cause* to be thrown into *process*. 241 | 242 | This event is automatically triggered when it is created. 243 | 244 | """ 245 | def __init__(self, process, cause): 246 | # NOTE: The following initialization code is inlined from 247 | # Event.__init__() for performance reasons. 248 | self.env = process.env 249 | self.callbacks = [self._interrupt] 250 | self._value = Interrupt(cause) 251 | self._ok = False 252 | self._defused = True 253 | 254 | if process._value is not PENDING: 255 | raise RuntimeError('%s has terminated and cannot be interrupted.' % 256 | process) 257 | 258 | if process is self.env.active_process: 259 | raise RuntimeError('A process is not allowed to interrupt itself.') 260 | 261 | self.process = process 262 | self.env.schedule(self, URGENT) 263 | 264 | def _interrupt(self, event): 265 | # Ignore dead processes. Multiple concurrently scheduled interrupts 266 | # cause this situation. If the process dies while handling the first 267 | # one, the remaining interrupts must be ignored. 268 | if self.process._value is not PENDING: 269 | return 270 | 271 | # A process never expects an interrupt and is always waiting for a 272 | # target event. Remove the process from the callbacks of the target. 273 | self.process._target.callbacks.remove(self.process._resume) 274 | 275 | self.process._resume(self) 276 | 277 | 278 | class Process(Event): 279 | """Process an event yielding generator. 280 | 281 | A generator (also known as a coroutine) can suspend its execution by 282 | yielding an event. ``Process`` will take care of resuming the generator 283 | with the value of that event once it has happened. The exception of failed 284 | events is thrown into the generator. 285 | 286 | ``Process`` itself is an event, too. It is triggered, once the generator 287 | returns or raises an exception. The value of the process is the return 288 | value of the generator or the exception, respectively. 289 | 290 | .. note:: 291 | 292 | Python version prior to 3.3 do not support return statements in 293 | generators. You can use :meth:~simpy.core.Environment.exit() as 294 | a workaround. 295 | 296 | Processes can be interrupted during their execution by :meth:`interrupt`. 297 | 298 | """ 299 | def __init__(self, env, generator): 300 | if not hasattr(generator, 'throw'): 301 | # Implementation note: Python implementations differ in the 302 | # generator types they provide. Cython adds its own generator type 303 | # in addition to the CPython type, which renders a type check 304 | # impractical. To workaround this issue, we check for attribute 305 | # name instead of type and optimistically assume that all objects 306 | # with a ``throw`` attribute are generators (the more intuitive 307 | # name ``__next__`` cannot be used because it was renamed from 308 | # ``next`` in Python 2). 309 | # Remove this workaround if it causes issues in production! 310 | raise ValueError('%s is not a generator.' % generator) 311 | 312 | # NOTE: The following initialization code is inlined from 313 | # Event.__init__() for performance reasons. 314 | self.env = env 315 | self.callbacks = [] 316 | self._value = PENDING 317 | 318 | self._generator = generator 319 | 320 | # Schedule the start of the execution of the process. 321 | self._target = Initialize(env, self) 322 | 323 | def _desc(self): 324 | """Return a string *Process(process_func_name)*.""" 325 | return '%s(%s)' % (self.__class__.__name__, self._generator.__name__) 326 | 327 | @property 328 | def target(self): 329 | """The event that the process is currently waiting for. 330 | 331 | Returns ``None`` if the process is dead or it is currently being 332 | interrupted. 333 | 334 | """ 335 | return self._target 336 | 337 | @property 338 | def is_alive(self): 339 | """``True`` until the process generator exits.""" 340 | return self._value is PENDING 341 | 342 | def interrupt(self, cause=None): 343 | """Interupt this process optionally providing a *cause*. 344 | 345 | A process cannot be interrupted if it already terminated. A process can 346 | also not interrupt itself. Raise a :exc:`RuntimeError` in these 347 | cases. 348 | 349 | """ 350 | Interruption(self, cause) 351 | 352 | def _resume(self, event): 353 | """Resumes the execution of the process with the value of *event*. If 354 | the process generator exits, the process itself will get triggered with 355 | the return value or the exception of the generator.""" 356 | # Mark the current process as active. 357 | self.env._active_proc = self 358 | 359 | while True: 360 | # Get next event from process 361 | try: 362 | if event._ok: 363 | event = self._generator.send(event._value) 364 | else: 365 | # The process has no choice but to handle the failed event 366 | # (or fail itself). 367 | event._defused = True 368 | 369 | # Create an exclusive copy of the exception for this 370 | # process to prevent traceback modifications by other 371 | # processes. 372 | exc = type(event._value)(*event._value.args) 373 | exc.__cause__ = event._value 374 | if PY2: 375 | if hasattr(event._value, '__traceback__'): 376 | exc.__traceback__ = event._value.__traceback__ 377 | event = self._generator.throw(exc) 378 | except (StopIteration, StopProcess) as e: 379 | # Process has terminated. 380 | event = None 381 | self._ok = True 382 | self._value = e.args[0] if len(e.args) else None 383 | self.env.schedule(self) 384 | break 385 | except BaseException as e: 386 | # Process has failed. 387 | event = None 388 | self._ok = False 389 | tb = e.__traceback__ if not PY2 else sys.exc_info()[2] 390 | # Strip the frame of this function from the traceback as it 391 | # does not add any useful information. 392 | e.__traceback__ = tb.tb_next 393 | self._value = e 394 | self.env.schedule(self) 395 | break 396 | 397 | # Process returned another event to wait upon. 398 | try: 399 | # Be optimistic and blindly access the callbacks attribute. 400 | if event.callbacks is not None: 401 | # The event has not yet been triggered. Register callback 402 | # to resume the process if that happens. 403 | event.callbacks.append(self._resume) 404 | break 405 | except AttributeError: 406 | # Our optimism didn't work out, figure out what went wrong and 407 | # inform the user. 408 | if not hasattr(event, 'callbacks'): 409 | msg = 'Invalid yield value "%s"' % event 410 | 411 | descr = _describe_frame(self._generator.gi_frame) 412 | error = RuntimeError('\n%s%s' % (descr, msg)) 413 | # Drop the AttributeError as the cause for this exception. 414 | error.__cause__ = None 415 | raise error 416 | 417 | self._target = event 418 | self.env._active_proc = None 419 | 420 | 421 | class ConditionValue(object): 422 | """Result of a :class:`~simpy.events.Condition`. It supports convenient 423 | dict-like access to the triggered events and their values. The events are 424 | ordered by their occurences in the condition.""" 425 | 426 | def __init__(self): 427 | self.events = [] 428 | 429 | def __getitem__(self, key): 430 | if key not in self.events: 431 | raise KeyError(str(key)) 432 | 433 | return key._value 434 | 435 | def __contains__(self, key): 436 | return key in self.events 437 | 438 | def __eq__(self, other): 439 | if type(other) is ConditionValue: 440 | return self.events == other.events 441 | 442 | return self.todict() == other 443 | 444 | def __repr__(self): 445 | return '' % self.todict() 446 | 447 | def __iter__(self): 448 | return self.keys() 449 | 450 | def keys(self): 451 | return (event for event in self.events) 452 | 453 | def values(self): 454 | return (event._value for event in self.events) 455 | 456 | def items(self): 457 | return ((event, event._value) for event in self.events) 458 | 459 | def todict(self): 460 | return dict((event, event._value) for event in self.events) 461 | 462 | 463 | class Condition(Event): 464 | """An event that gets triggered once the condition function *evaluate* 465 | returns ``True`` on the given list of *events*. 466 | 467 | The value of the condition event is an instance of :class:`ConditionValue` 468 | which allows convenient access to the input events and their values. The 469 | :class:`ConditionValue` will only contain entries for those events that 470 | occurred before the condition is processed. 471 | 472 | If one of the events fails, the condition also fails and forwards the 473 | exception of the failing event. 474 | 475 | The *evaluate* function receives the list of target events and the number 476 | of processed events in this list: ``evaluate(events, processed_count)``. If 477 | it returns ``True``, the condition is triggered. The 478 | :func:`Condition.all_events()` and :func:`Condition.any_events()` functions 479 | are used to implement *and* (``&``) and *or* (``|``) for events. 480 | 481 | Condition events can be nested. 482 | 483 | """ 484 | def __init__(self, env, evaluate, events): 485 | super(Condition, self).__init__(env) 486 | self._evaluate = evaluate 487 | self._events = events if type(events) is tuple else tuple(events) 488 | self._count = 0 489 | 490 | if not self._events: 491 | # Immediately succeed if no events are provided. 492 | self.succeed(ConditionValue()) 493 | return 494 | 495 | # Check if events belong to the same environment. 496 | for event in self._events: 497 | if self.env != event.env: 498 | raise ValueError('It is not allowed to mix events from ' 499 | 'different environments') 500 | 501 | # Check if the condition is met for each processed event. Attach 502 | # _check() as a callback otherwise. 503 | for event in self._events: 504 | if event.callbacks is None: 505 | self._check(event) 506 | else: 507 | event.callbacks.append(self._check) 508 | 509 | # Register a callback which will build the value of this condition 510 | # after it has been triggered. 511 | self.callbacks.append(self._build_value) 512 | 513 | def _desc(self): 514 | """Return a string *Condition(evaluate, [events])*.""" 515 | return '%s(%s, %s)' % (self.__class__.__name__, 516 | self._evaluate.__name__, self._events) 517 | 518 | def _populate_value(self, value): 519 | """Populate the *value* by recursively visiting all nested 520 | conditions.""" 521 | 522 | for event in self._events: 523 | if isinstance(event, Condition): 524 | event._populate_value(value) 525 | elif event.callbacks is None: 526 | value.events.append(event) 527 | 528 | def _build_value(self, event): 529 | """Build the value of this condition.""" 530 | self._remove_check_callbacks() 531 | if event._ok: 532 | self._value = ConditionValue() 533 | self._populate_value(self._value) 534 | 535 | def _remove_check_callbacks(self): 536 | """Remove _check() callbacks from events recursively. 537 | 538 | Once the condition has triggered, the condition's events no longer need 539 | to have _check() callbacks. Removing the _check() callbacks is 540 | important to break circular references between the condition and 541 | untriggered events. 542 | 543 | """ 544 | for event in self._events: 545 | if event.callbacks and self._check in event.callbacks: 546 | event.callbacks.remove(self._check) 547 | if isinstance(event, Condition): 548 | event._remove_check_callbacks() 549 | 550 | def _check(self, event): 551 | """Check if the condition was already met and schedule the *event* if 552 | so.""" 553 | if self._value is not PENDING: 554 | return 555 | 556 | self._count += 1 557 | 558 | if not event._ok: 559 | # Abort if the event has failed. 560 | event._defused = True 561 | self.fail(event._value) 562 | elif self._evaluate(self._events, self._count): 563 | # The condition has been met. The _build_value() callback will 564 | # populate the ConditionValue once this condition is processed. 565 | self.succeed() 566 | 567 | @staticmethod 568 | def all_events(events, count): 569 | """An evaluation function that returns ``True`` if all *events* have 570 | been triggered.""" 571 | return len(events) == count 572 | 573 | @staticmethod 574 | def any_events(events, count): 575 | """An evaluation function that returns ``True`` if at least one of 576 | *events* has been triggered.""" 577 | return count > 0 or len(events) == 0 578 | 579 | 580 | class AllOf(Condition): 581 | """A :class:`~simpy.events.Condition` event that is triggered if all of 582 | a list of *events* have been successfully triggered. Fails immediately if 583 | any of *events* failed. 584 | 585 | """ 586 | def __init__(self, env, events): 587 | super(AllOf, self).__init__(env, Condition.all_events, events) 588 | 589 | 590 | class AnyOf(Condition): 591 | """A :class:`~simpy.events.Condition` event that is triggered if any of 592 | a list of *events* has been successfully triggered. Fails immediately if 593 | any of *events* failed. 594 | 595 | """ 596 | def __init__(self, env, events): 597 | super(AnyOf, self).__init__(env, Condition.any_events, events) 598 | 599 | 600 | def _describe_frame(frame): 601 | """Print filename, line number and function name of a stack frame.""" 602 | filename, name = frame.f_code.co_filename, frame.f_code.co_name 603 | lineno = frame.f_lineno 604 | 605 | with open(filename) as f: 606 | for no, line in enumerate(f): 607 | if no + 1 == lineno: 608 | break 609 | 610 | return ' File "%s", line %d, in %s\n %s\n' % (filename, lineno, name, 611 | line.strip()) 612 | -------------------------------------------------------------------------------- /simpy/exceptions.py: -------------------------------------------------------------------------------- 1 | """ 2 | SimPy specific exeptions. 3 | 4 | """ 5 | 6 | 7 | class SimPyException(Exception): 8 | """Base class for all SimPy specific exceptions.""" 9 | 10 | 11 | class Interrupt(SimPyException): 12 | """Exception thrown into a process if it is interrupted (see 13 | :func:`~simpy.events.Process.interrupt()`). 14 | 15 | :attr:`cause` provides the reason for the interrupt, if any. 16 | 17 | If a process is interrupted concurrently, all interrupts will be thrown 18 | into the process in the same order as they occurred. 19 | 20 | 21 | """ 22 | def __init__(self, cause): 23 | super(Interrupt, self).__init__(cause) 24 | 25 | def __str__(self): 26 | return '%s(%r)' % (self.__class__.__name__, self.cause) 27 | 28 | @property 29 | def cause(self): 30 | """The cause of the interrupt or ``None`` if no cause was provided.""" 31 | return self.args[0] 32 | 33 | 34 | class StopProcess(SimPyException): 35 | """Raised to stop a SimPy process (similar to :exc:`StopIteration`). 36 | 37 | In Python 2, a ``return value`` inside generator functions is not allowed. 38 | The fall-back was raising ``StopIteration(value)`` instead. However, this 39 | is deprecated_ now, so we need a custom exception type for this. 40 | 41 | .. _deprecated: https://www.python.org/dev/peps/pep-0479/ 42 | 43 | """ 44 | def __init__(self, value): 45 | super(StopProcess, self).__init__(value) 46 | 47 | def __str__(self): 48 | return '%s(%r)' % (self.__class__.__name__, self.value) 49 | 50 | @property 51 | def value(self): 52 | """The process' return value.""" 53 | return self.args[0] 54 | -------------------------------------------------------------------------------- /simpy/resources/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | SimPy implements three types of resources that can be used to synchronize 3 | processes or to model congestion points: 4 | 5 | .. currentmodule:: simpy.resources 6 | 7 | .. autosummary:: 8 | 9 | resource 10 | container 11 | store 12 | 13 | They are derived from the base classes defined in the 14 | :mod:`~simpy.resources.base` module. These classes are also meant to support 15 | the implementation of custom resource types. 16 | 17 | """ 18 | -------------------------------------------------------------------------------- /simpy/resources/base.py: -------------------------------------------------------------------------------- 1 | """ 2 | Base classes of for SimPy's shared resource types. 3 | 4 | :class:`BaseResource` defines the abstract base resource. It supports *get* and 5 | *put* requests, which return :class:`Put` and :class:`Get` events respectively. 6 | These events are triggered once the request has been completed. 7 | 8 | """ 9 | from simpy.core import BoundClass 10 | from simpy.events import Event 11 | 12 | 13 | class Put(Event): 14 | """Generic event for requesting to put something into the *resource*. 15 | 16 | This event (and all of its subclasses) can act as context manager and can 17 | be used with the :keyword:`with` statement to automatically cancel the 18 | request if an exception (like an :class:`simpy.exceptions.Interrupt` for 19 | example) occurs: 20 | 21 | .. code-block:: python 22 | 23 | with res.put(item) as request: 24 | yield request 25 | 26 | """ 27 | def __init__(self, resource): 28 | super(Put, self).__init__(resource._env) 29 | self.resource = resource 30 | self.proc = self.env.active_process 31 | 32 | resource.put_queue.append(self) 33 | self.callbacks.append(resource._trigger_get) 34 | resource._trigger_put(None) 35 | 36 | def __enter__(self): 37 | return self 38 | 39 | def __exit__(self, exc_type, exc_value, traceback): 40 | self.cancel() 41 | 42 | def cancel(self): 43 | """Cancel this put request. 44 | 45 | This method has to be called if the put request must be aborted, for 46 | example if a process needs to handle an exception like an 47 | :class:`~simpy.exceptions.Interrupt`. 48 | 49 | If the put request was created in a :keyword:`with` statement, this 50 | method is called automatically. 51 | 52 | """ 53 | if not self.triggered: 54 | self.resource.put_queue.remove(self) 55 | 56 | 57 | class Get(Event): 58 | """Generic event for requesting to get something from the *resource*. 59 | 60 | This event (and all of its subclasses) can act as context manager and can 61 | be used with the :keyword:`with` statement to automatically cancel the 62 | request if an exception (like an :class:`simpy.exceptions.Interrupt` for 63 | example) occurs: 64 | 65 | .. code-block:: python 66 | 67 | with res.get() as request: 68 | item = yield request 69 | 70 | """ 71 | def __init__(self, resource): 72 | super(Get, self).__init__(resource._env) 73 | self.resource = resource 74 | self.proc = self.env.active_process 75 | 76 | resource.get_queue.append(self) 77 | self.callbacks.append(resource._trigger_put) 78 | resource._trigger_get(None) 79 | 80 | def __enter__(self): 81 | return self 82 | 83 | def __exit__(self, exc_type, exc_value, traceback): 84 | self.cancel() 85 | 86 | def cancel(self): 87 | """Cancel this get request. 88 | 89 | This method has to be called if the get request must be aborted, for 90 | example if a process needs to handle an exception like an 91 | :class:`~simpy.exceptions.Interrupt`. 92 | 93 | If the get request was created in a :keyword:`with` statement, this 94 | method is called automatically. 95 | 96 | """ 97 | if not self.triggered: 98 | self.resource.get_queue.remove(self) 99 | 100 | 101 | class BaseResource(object): 102 | """Abstract base class for a shared resource. 103 | 104 | You can :meth:`put()` something into the resources or :meth:`get()` 105 | something out of it. Both methods return an event that is triggered once 106 | the operation is completed. If a :meth:`put()` request cannot complete 107 | immediately (for example if the resource has reached a capacity limit) it 108 | is enqueued in the :attr:`put_queue` for later processing. Likewise for 109 | :meth:`get()` requests. 110 | 111 | Subclasses can customize the resource by: 112 | 113 | - providing custom :attr:`PutQueue` and :attr:`GetQueue` types, 114 | - providing custom :class:`Put` respectively :class:`Get` events, 115 | - and implementing the request processing behaviour through the methods 116 | ``_do_get()`` and ``_do_put()``. 117 | 118 | """ 119 | PutQueue = list 120 | """The type to be used for the :attr:`put_queue`. It is a plain 121 | :class:`list` by default. The type must support index access (e.g. 122 | ``__getitem__()`` and ``__len__()``) as well as provide ``append()`` and 123 | ``pop()`` operations.""" 124 | 125 | GetQueue = list 126 | """The type to be used for the :attr:`get_queue`. It is a plain 127 | :class:`list` by default. The type must support index access (e.g. 128 | ``__getitem__()`` and ``__len__()``) as well as provide ``append()`` and 129 | ``pop()`` operations.""" 130 | 131 | def __init__(self, env, capacity): 132 | self._env = env 133 | self._capacity = capacity 134 | self.put_queue = self.PutQueue() 135 | """Queue of pending *put* requests.""" 136 | self.get_queue = self.GetQueue() 137 | """Queue of pending *get* requests.""" 138 | 139 | # Bind event constructors as methods 140 | BoundClass.bind_early(self) 141 | 142 | @property 143 | def capacity(self): 144 | """Maximum capacity of the resource.""" 145 | return self._capacity 146 | 147 | put = BoundClass(Put) 148 | """Request to put something into the resource and return a :class:`Put` 149 | event, which gets triggered once the request succeeds.""" 150 | 151 | get = BoundClass(Get) 152 | """Request to get something from the resource and return a :class:`Get` 153 | event, which gets triggered once the request succeeds.""" 154 | 155 | def _do_put(self, event): 156 | """Perform the *put* operation. 157 | 158 | This method needs to be implemented by subclasses. If the conditions 159 | for the put *event* are met, the method must trigger the event (e.g. 160 | call :meth:`Event.succeed()` with an apropriate value). 161 | 162 | This method is called by :meth:`_trigger_put` for every event in the 163 | :attr:`put_queue`, as long as the return value does not evaluate 164 | ``False``. 165 | """ 166 | raise NotImplementedError(self) 167 | 168 | def _trigger_put(self, get_event): 169 | """This method is called once a new put event has been created or a get 170 | event has been processed. 171 | 172 | The method iterates over all put events in the :attr:`put_queue` and 173 | calls :meth:`_do_put` to check if the conditions for the event are met. 174 | If :meth:`_do_put` returns ``False``, the iteration is stopped early. 175 | """ 176 | 177 | # Maintain queue invariant: All put requests must be untriggered. 178 | # This code is not very pythonic because the queue interface should be 179 | # simple (only append(), pop(), __getitem__() and __len__() are 180 | # required). 181 | idx = 0 182 | while idx < len(self.put_queue): 183 | put_event = self.put_queue[idx] 184 | proceed = self._do_put(put_event) 185 | if not put_event.triggered: 186 | idx += 1 187 | elif self.put_queue.pop(idx) != put_event: 188 | raise RuntimeError('Put queue invariant violated') 189 | 190 | if not proceed: 191 | break 192 | 193 | def _do_get(self, event): 194 | """Perform the *get* operation. 195 | 196 | This method needs to be implemented by subclasses. If the conditions 197 | for the get *event* are met, the method must trigger the event (e.g. 198 | call :meth:`Event.succeed()` with an apropriate value). 199 | 200 | This method is called by :meth:`_trigger_get` for every event in the 201 | :attr:`get_queue`, as long as the return value does not evaluate 202 | ``False``. 203 | """ 204 | raise NotImplementedError(self) 205 | 206 | def _trigger_get(self, put_event): 207 | """Trigger get events. 208 | 209 | This method is called once a new get event has been created or a put 210 | event has been processed. 211 | 212 | The method iterates over all get events in the :attr:`get_queue` and 213 | calls :meth:`_do_get` to check if the conditions for the event are met. 214 | If :meth:`_do_get` returns ``False``, the iteration is stopped early. 215 | """ 216 | 217 | # Maintain queue invariant: All get requests must be untriggered. 218 | # This code is not very pythonic because the queue interface should be 219 | # simple (only append(), pop(), __getitem__() and __len__() are 220 | # required). 221 | idx = 0 222 | while idx < len(self.get_queue): 223 | get_event = self.get_queue[idx] 224 | proceed = self._do_get(get_event) 225 | if not get_event.triggered: 226 | idx += 1 227 | elif self.get_queue.pop(idx) != get_event: 228 | raise RuntimeError('Get queue invariant violated') 229 | 230 | if not proceed: 231 | break 232 | -------------------------------------------------------------------------------- /simpy/resources/container.py: -------------------------------------------------------------------------------- 1 | """ 2 | Resource for sharing homogeneous matter between processes, either continuous 3 | (like water) or discrete (like apples). 4 | 5 | A :class:`Container` can be used to model the fuel tank of a gasoline station. 6 | Tankers increase and refuelled cars decrease the amount of gas in the station's 7 | fuel tanks. 8 | 9 | """ 10 | from simpy.core import BoundClass 11 | from simpy.resources import base 12 | 13 | 14 | class ContainerPut(base.Put): 15 | """Request to put *amount* of matter into the *container*. The request will 16 | be triggered once there is enough space in the *container* available. 17 | 18 | Raise a :exc:`ValueError` if ``amount <= 0``. 19 | 20 | """ 21 | def __init__(self, container, amount): 22 | if amount <= 0: 23 | raise ValueError('amount(=%s) must be > 0.' % amount) 24 | self.amount = amount 25 | """The amount of matter to be put into the container.""" 26 | 27 | super(ContainerPut, self).__init__(container) 28 | 29 | 30 | class ContainerGet(base.Get): 31 | """Request to get *amount* of matter from the *container*. The request will 32 | be triggered once there is enough matter available in the *container*. 33 | 34 | Raise a :exc:`ValueError` if ``amount <= 0``. 35 | 36 | """ 37 | def __init__(self, container, amount): 38 | if amount <= 0: 39 | raise ValueError('amount(=%s) must be > 0.' % amount) 40 | self.amount = amount 41 | """The amount of matter to be taken out of the container.""" 42 | 43 | super(ContainerGet, self).__init__(container) 44 | 45 | 46 | class Container(base.BaseResource): 47 | """Resource containing up to *capacity* of matter which may either be 48 | continuous (like water) or discrete (like apples). It supports requests to 49 | put or get matter into/from the container. 50 | 51 | The *env* parameter is the :class:`~simpy.core.Environment` instance the 52 | container is bound to. 53 | 54 | The *capacity* defines the size of the container. By default, a container 55 | is of unlimited size. The initial amount of matter is specified by *init* 56 | and defaults to ``0``. 57 | 58 | Raise a :exc:`ValueError` if ``capacity <= 0``, ``init < 0`` or 59 | ``init > capacity``. 60 | 61 | """ 62 | def __init__(self, env, capacity=float('inf'), init=0): 63 | if capacity <= 0: 64 | raise ValueError('"capacity" must be > 0.') 65 | if init < 0: 66 | raise ValueError('"init" must be >= 0.') 67 | if init > capacity: 68 | raise ValueError('"init" must be <= "capacity".') 69 | 70 | super(Container, self).__init__(env, capacity) 71 | 72 | self._level = init 73 | 74 | @property 75 | def level(self): 76 | """The current amount of the matter in the container.""" 77 | return self._level 78 | 79 | put = BoundClass(ContainerPut) 80 | """Request to put *amount* of matter into the container.""" 81 | 82 | get = BoundClass(ContainerGet) 83 | """Request to get *amount* of matter out of the container.""" 84 | 85 | def _do_put(self, event): 86 | if self._capacity - self._level >= event.amount: 87 | self._level += event.amount 88 | event.succeed() 89 | return True 90 | 91 | def _do_get(self, event): 92 | if self._level >= event.amount: 93 | self._level -= event.amount 94 | event.succeed() 95 | return True 96 | -------------------------------------------------------------------------------- /simpy/resources/resource.py: -------------------------------------------------------------------------------- 1 | """ 2 | Shared resources supporting priorities and preemption. 3 | 4 | These resources can be used to limit the number of processes using them 5 | concurrently. A process needs to *request* the usage right to a resource. Once 6 | the usage right is not needed anymore it has to be *released*. A gas station 7 | can be modelled as a resource with a limited amount of fuel-pumps. Vehicles 8 | arrive at the gas station and request to use a fuel-pump. If all fuel-pumps are 9 | in use, the vehicle needs to wait until one of the users has finished refueling 10 | and releases its fuel-pump. 11 | 12 | These resources can be used by a limited number of processes at a time. 13 | Processes *request* these resources to become a user and have to *release* them 14 | once they are done. For example, a gas station with a limited number of fuel 15 | pumps can be modeled with a `Resource`. Arriving vehicles request a fuel-pump. 16 | Once one is available they refuel. When they are done, the release the 17 | fuel-pump and leave the gas station. 18 | 19 | Requesting a resource is modelled as "putting a process' token into the 20 | resources" and releasing a resources correspondingly as "getting a process' 21 | token out of the resource". Thus, calling ``request()``/``release()`` is 22 | equivalent to calling ``put()``/``get()``. Note, that releasing a resource will 23 | always succeed immediately, no matter if a process is actually using a resource 24 | or not. 25 | 26 | Besides :class:`Resource`, there is a :class:`PriorityResource`, where 27 | processes can define a request priority, and a :class:`PreemptiveResource` 28 | whose resource users can be preempted by requests with a higher priority. 29 | 30 | """ 31 | from simpy.core import BoundClass 32 | from simpy.resources import base 33 | 34 | 35 | class Preempted(object): 36 | """Cause of an preemption :class:`~simpy.exceptions.Interrupt` containing 37 | information about the preemption. 38 | 39 | """ 40 | def __init__(self, by, usage_since, resource): 41 | self.by = by 42 | """The preempting :class:`simpy.events.Process`.""" 43 | self.usage_since = usage_since 44 | """The simulation time at which the preempted process started to use 45 | the resource.""" 46 | self.resource = resource 47 | """The resource which was lost, i.e., caused the preemption.""" 48 | 49 | 50 | class Request(base.Put): 51 | """Request usage of the *resource*. The event is triggered once access is 52 | granted. Subclass of :class:`simpy.resources.base.Put`. 53 | 54 | If the maximum capacity of users has not yet been reached, the request is 55 | triggered immediately. If the maximum capacity has been 56 | reached, the request is triggered once an earlier usage request on the 57 | resource is released. 58 | 59 | The request is automatically released when the request was created within 60 | a :keyword:`with` statement. 61 | 62 | """ 63 | def __exit__(self, exc_type, value, traceback): 64 | super(Request, self).__exit__(exc_type, value, traceback) 65 | # Don't release the resource on generator cleanups. This seems to 66 | # create unclaimable circular references otherwise. 67 | if exc_type is not GeneratorExit: 68 | self.resource.release(self) 69 | 70 | 71 | class Release(base.Get): 72 | """Releases the usage of *resource* granted by *request*. This event is 73 | triggered immediately. Subclass of :class:`simpy.resources.base.Get`. 74 | 75 | """ 76 | def __init__(self, resource, request): 77 | self.request = request 78 | """The request (:class:`Request`) that is to be released.""" 79 | super(Release, self).__init__(resource) 80 | 81 | 82 | class PriorityRequest(Request): 83 | """Request the usage of *resource* with a given *priority*. If the 84 | *resource* supports preemption and *preempt* is ``True`` other usage 85 | requests of the *resource* may be preempted (see 86 | :class:`PreemptiveResource` for details). 87 | 88 | This event type inherits :class:`Request` and adds some additional 89 | attributes needed by :class:`PriorityResource` and 90 | :class:`PreemptiveResource` 91 | 92 | """ 93 | def __init__(self, resource, priority=0, preempt=True): 94 | self.priority = priority 95 | """The priority of this request. A smaller number means higher 96 | priority.""" 97 | 98 | self.preempt = preempt 99 | """Indicates whether the request should preempt a resource user or not 100 | (:class:`PriorityResource` ignores this flag).""" 101 | 102 | self.time = resource._env.now 103 | """The time at which the request was made.""" 104 | 105 | self.usage_since = None 106 | """The time at which the request succeeded.""" 107 | 108 | self.key = (self.priority, self.time, not self.preempt) 109 | """Key for sorting events. Consists of the priority (lower value is 110 | more important), the time at which the request was made (earlier 111 | requests are more important) and finally the preemption flag (preempt 112 | requests are more important).""" 113 | 114 | super(PriorityRequest, self).__init__(resource) 115 | 116 | 117 | class SortedQueue(list): 118 | """Queue for sorting events by their :attr:`~PriorityRequest.key` 119 | attribute. 120 | 121 | """ 122 | def __init__(self, maxlen=None): 123 | super(SortedQueue, self).__init__() 124 | self.maxlen = maxlen 125 | """Maximum length of the queue.""" 126 | 127 | def append(self, item): 128 | """Sort *item* into the queue. 129 | 130 | Raise a :exc:`RuntimeError` if the queue is full. 131 | 132 | """ 133 | if self.maxlen is not None and len(self) >= self.maxlen: 134 | raise RuntimeError('Cannot append event. Queue is full.') 135 | 136 | super(SortedQueue, self).append(item) 137 | super(SortedQueue, self).sort(key=lambda e: e.key) 138 | 139 | 140 | class Resource(base.BaseResource): 141 | """Resource with *capacity* of usage slots that can be requested by 142 | processes. 143 | 144 | If all slots are taken, requests are enqueued. Once a usage request is 145 | released, a pending request will be triggered. 146 | 147 | The *env* parameter is the :class:`~simpy.core.Environment` instance the 148 | resource is bound to. 149 | 150 | """ 151 | def __init__(self, env, capacity=1): 152 | if capacity <= 0: 153 | raise ValueError('"capacity" must be > 0.') 154 | 155 | super(Resource, self).__init__(env, capacity) 156 | 157 | self.users = [] 158 | """List of :class:`Request` events for the processes that are currently 159 | using the resource.""" 160 | self.queue = self.put_queue 161 | """Queue of pending :class:`Request` events. Alias of 162 | :attr:`~simpy.resources.base.BaseResource.put_queue`. 163 | """ 164 | 165 | @property 166 | def count(self): 167 | """Number of users currently using the resource.""" 168 | return len(self.users) 169 | 170 | request = BoundClass(Request) 171 | """Request a usage slot.""" 172 | 173 | release = BoundClass(Release) 174 | """Release a usage slot.""" 175 | 176 | def _do_put(self, event): 177 | if len(self.users) < self.capacity: 178 | self.users.append(event) 179 | event.usage_since = self._env.now 180 | event.succeed() 181 | 182 | def _do_get(self, event): 183 | try: 184 | self.users.remove(event.request) 185 | except ValueError: 186 | pass 187 | event.succeed() 188 | 189 | 190 | class PriorityResource(Resource): 191 | """A :class:`~simpy.resources.resource.Resource` supporting prioritized 192 | requests. 193 | 194 | Pending requests in the :attr:`~Resource.queue` are sorted in ascending 195 | order by their *priority* (that means lower values are more important). 196 | 197 | """ 198 | PutQueue = SortedQueue 199 | """Type of the put queue. See 200 | :attr:`~simpy.resources.base.BaseResource.put_queue` for details.""" 201 | GetQueue = list 202 | """Type of the get queue. See 203 | :attr:`~simpy.resources.base.BaseResource.get_queue` for details.""" 204 | 205 | def __init__(self, env, capacity=1): 206 | super(PriorityResource, self).__init__(env, capacity) 207 | 208 | request = BoundClass(PriorityRequest) 209 | """Request a usage slot with the given *priority*.""" 210 | 211 | release = BoundClass(Release) 212 | """Release a usage slot.""" 213 | 214 | 215 | class PreemptiveResource(PriorityResource): 216 | """A :class:`~simpy.resources.resource.PriorityResource` with preemption. 217 | 218 | If a request is preempted, the process of that request will receive an 219 | :class:`~simpy.exceptions.Interrupt` with a :class:`Preempted` instance as 220 | cause. 221 | 222 | """ 223 | def _do_put(self, event): 224 | if len(self.users) >= self.capacity and event.preempt: 225 | # Check if we can preempt another process 226 | preempt = sorted(self.users, key=lambda e: e.key)[-1] 227 | if preempt.key > event.key: 228 | self.users.remove(preempt) 229 | preempt.proc.interrupt(Preempted( 230 | by=event.proc, usage_since=preempt.usage_since, 231 | resource=self)) 232 | 233 | return super(PreemptiveResource, self)._do_put(event) 234 | -------------------------------------------------------------------------------- /simpy/resources/store.py: -------------------------------------------------------------------------------- 1 | """ 2 | Shared resources for storing a possibly unlimited amount of objects supporting 3 | requests for specific objects. 4 | 5 | The :class:`Store` operates in a FIFO (first-in, first-out) order. Objects are 6 | retrieved from the store in the order they were put in. The *get* requests of a 7 | :class:`FilterStore` can be customized by a filter to only retrieve objects 8 | matching a given criterion. 9 | 10 | """ 11 | from heapq import heappush, heappop 12 | from collections import namedtuple 13 | 14 | from simpy.core import BoundClass 15 | from simpy.resources import base 16 | 17 | 18 | class StorePut(base.Put): 19 | """Request to put *item* into the *store*. The request is triggered once 20 | there is space for the item in the store. 21 | 22 | """ 23 | def __init__(self, store, item): 24 | self.item = item 25 | """The item to put into the store.""" 26 | super(StorePut, self).__init__(store) 27 | 28 | 29 | class StoreGet(base.Get): 30 | """Request to get an *item* from the *store*. The request is triggered 31 | once there is an item available in the store. 32 | 33 | """ 34 | pass 35 | 36 | 37 | class FilterStoreGet(StoreGet): 38 | """Request to get an *item* from the *store* matching the *filter*. The 39 | request is triggered once there is such an item available in the store. 40 | 41 | *filter* is a function receiving one item. It should return ``True`` for 42 | items matching the filter criterion. The default function returns ``True`` 43 | for all items, which makes the request to behave exactly like 44 | :class:`StoreGet`. 45 | 46 | """ 47 | def __init__(self, resource, filter=lambda item: True): 48 | self.filter = filter 49 | """The filter function to filter items in the store.""" 50 | super(FilterStoreGet, self).__init__(resource) 51 | 52 | 53 | class Store(base.BaseResource): 54 | """Resource with *capacity* slots for storing arbitrary objects. By 55 | default, the *capacity* is unlimited and objects are put and retrieved from 56 | the store in a first-in first-out order. 57 | 58 | The *env* parameter is the :class:`~simpy.core.Environment` instance the 59 | container is bound to. 60 | 61 | """ 62 | def __init__(self, env, capacity=float('inf')): 63 | if capacity <= 0: 64 | raise ValueError('"capacity" must be > 0.') 65 | 66 | super(Store, self).__init__(env, capacity) 67 | 68 | self.items = [] 69 | """List of the items available in the store.""" 70 | 71 | put = BoundClass(StorePut) 72 | """Request to put *item* into the store.""" 73 | 74 | get = BoundClass(StoreGet) 75 | """Request to get an *item* out of the store.""" 76 | 77 | def _do_put(self, event): 78 | if len(self.items) < self._capacity: 79 | self.items.append(event.item) 80 | event.succeed() 81 | 82 | def _do_get(self, event): 83 | if self.items: 84 | event.succeed(self.items.pop(0)) 85 | 86 | 87 | class PriorityItem(namedtuple('PriorityItem', 'priority item')): 88 | """Wrap an arbitrary *item* with an orderable *priority*. 89 | 90 | Pairs a *priority* with an arbitrary *item*. Comparisons of *PriorityItem* 91 | instances only consider the *priority* attribute, thus supporting use of 92 | unorderable items in a :class:`PriorityStore` instance. 93 | 94 | """ 95 | 96 | def __lt__(self, other): 97 | return self.priority < other.priority 98 | 99 | 100 | class PriorityStore(Store): 101 | """Resource with *capacity* slots for storing objects in priority order. 102 | 103 | Unlike :class:`Store` which provides first-in first-out discipline, 104 | :class:`PriorityStore` maintains items in sorted order such that 105 | the smallest items value are retreived first from the store. 106 | 107 | All items in a *PriorityStore* instance must be orderable; which is to say 108 | that items must implement :meth:`~object.__lt__()`. To use unorderable 109 | items with *PriorityStore*, use :class:`PriorityItem`. 110 | 111 | """ 112 | 113 | def _do_put(self, event): 114 | if len(self.items) < self._capacity: 115 | heappush(self.items, event.item) 116 | event.succeed() 117 | 118 | def _do_get(self, event): 119 | if self.items: 120 | event.succeed(heappop(self.items)) 121 | 122 | 123 | class FilterStore(Store): 124 | """Resource with *capacity* slots for storing arbitrary objects supporting 125 | filtered get requests. Like the :class:`Store`, the *capacity* is unlimited 126 | by default and objects are put and retrieved from the store in a first-in 127 | first-out order. 128 | 129 | Get requests can be customized with a filter function to only trigger for 130 | items for which said filter function returns ``True``. 131 | 132 | .. note:: 133 | 134 | In contrast to :class:`Store`, get requests of a :class:`FilterStore` 135 | won't necessarily be triggered in the same order they were issued. 136 | 137 | *Example:* The store is empty. *Process 1* tries to get an item of type 138 | *a*, *Process 2* an item of type *b*. Another process puts one item of 139 | type *b* into the store. Though *Process 2* made his request after 140 | *Process 1*, it will receive that new item because *Process 1* doesn't 141 | want it. 142 | 143 | """ 144 | 145 | put = BoundClass(StorePut) 146 | """Request a to put *item* into the store.""" 147 | 148 | get = BoundClass(FilterStoreGet) 149 | """Request a to get an *item*, for which *filter* returns ``True``, out of 150 | the store.""" 151 | 152 | def _do_get(self, event): 153 | for item in self.items: 154 | if event.filter(item): 155 | self.items.remove(item) 156 | event.succeed(item) 157 | break 158 | return True 159 | -------------------------------------------------------------------------------- /simpy/rt.py: -------------------------------------------------------------------------------- 1 | """Execution environment for events that synchronizes passing of time 2 | with the real-time (aka *wall-clock time*). 3 | 4 | """ 5 | try: 6 | # Python >= 3.3 7 | from time import monotonic as time, sleep 8 | except ImportError: 9 | # Python < 3.3 10 | from time import time, sleep 11 | 12 | from simpy.core import Environment, EmptySchedule, Infinity 13 | 14 | 15 | class RealtimeEnvironment(Environment): 16 | """Execution environment for an event-based simulation which is 17 | synchronized with the real-time (also known as wall-clock time). A time 18 | step will take *factor* seconds of real time (one second by default). 19 | A step from ``0`` to ``3`` with a ``factor=0.5`` will, for example, take at 20 | least 21 | 1.5 seconds. 22 | 23 | The :meth:`step()` method will raise a :exc:`RuntimeError` if a time step 24 | took too long to compute. This behaviour can be disabled by setting 25 | *strict* to ``False``. 26 | 27 | """ 28 | def __init__(self, initial_time=0, factor=1.0, strict=True): 29 | Environment.__init__(self, initial_time) 30 | 31 | self.env_start = initial_time 32 | self.real_start = time() 33 | self._factor = factor 34 | self._strict = strict 35 | 36 | @property 37 | def factor(self): 38 | """Scaling factor of the real-time.""" 39 | return self._factor 40 | 41 | @property 42 | def strict(self): 43 | """Running mode of the environment. :meth:`step()` will raise a 44 | :exc:`RuntimeError` if this is set to ``True`` and the processing of 45 | events takes too long.""" 46 | return self._strict 47 | 48 | def sync(self): 49 | """Synchronize the internal time with the current wall-clock time. 50 | 51 | This can be useful to prevent :meth:`step()` from raising an error if 52 | a lot of time passes between creating the RealtimeEnvironment and 53 | calling :meth:`run()` or :meth:`step()`. 54 | 55 | """ 56 | self.real_start = time() 57 | 58 | def step(self): 59 | """Process the next event after enough real-time has passed for the 60 | event to happen. 61 | 62 | The delay is scaled according to the real-time :attr:`factor`. With 63 | :attr:`strict` mode enabled, a :exc:`RuntimeError` will be raised, if 64 | the event is processed too slowly. 65 | 66 | """ 67 | evt_time = self.peek() 68 | 69 | if evt_time is Infinity: 70 | raise EmptySchedule() 71 | 72 | real_time = self.real_start + (evt_time - self.env_start) * self.factor 73 | 74 | if self.strict and time() - real_time > self.factor: 75 | # Events scheduled for time *t* may take just up to *t+1* 76 | # for their computation, before an error is raised. 77 | raise RuntimeError('Simulation too slow for real time (%.3fs).' % ( 78 | time() - real_time)) 79 | 80 | # Sleep in a loop to fix inaccuracies of windows (see 81 | # http://stackoverflow.com/a/15967564 for details) and to ignore 82 | # interrupts. 83 | while True: 84 | delta = real_time - time() 85 | if delta <= 0: 86 | break 87 | sleep(delta) 88 | 89 | return Environment.step(self) 90 | -------------------------------------------------------------------------------- /simpy/util.py: -------------------------------------------------------------------------------- 1 | """ 2 | A collection of utility functions: 3 | 4 | .. autosummary:: 5 | start_delayed 6 | 7 | """ 8 | 9 | 10 | def start_delayed(env, generator, delay): 11 | """Return a helper process that starts another process for *generator* 12 | after a certain *delay*. 13 | 14 | :meth:`~simpy.core.Environment.process()` starts a process at the current 15 | simulation time. This helper allows you to start a process after a delay of 16 | *delay* simulation time units:: 17 | 18 | >>> from simpy import Environment 19 | >>> from simpy.util import start_delayed 20 | >>> def my_process(env, x): 21 | ... print('%s, %s' % (env.now, x)) 22 | ... yield env.timeout(1) 23 | ... 24 | >>> env = Environment() 25 | >>> proc = start_delayed(env, my_process(env, 3), 5) 26 | >>> env.run() 27 | 5, 3 28 | 29 | Raise a :exc:`ValueError` if ``delay <= 0``. 30 | 31 | """ 32 | if delay <= 0: 33 | raise ValueError('delay(=%s) must be > 0.' % delay) 34 | 35 | def starter(): 36 | yield env.timeout(delay) 37 | proc = env.process(generator) 38 | env.exit(proc) 39 | 40 | return env.process(starter()) 41 | 42 | 43 | def subscribe_at(event): 44 | """Register at the *event* to receive an interrupt when it occurs. 45 | 46 | The most common use case for this is to pass 47 | a :class:`~simpy.events.Process` to get notified when it terminates. 48 | 49 | Raise a :exc:`RuntimeError` if ``event`` has already occurred. 50 | 51 | """ 52 | env = event.env 53 | subscriber = env.active_process 54 | 55 | def signaller(signaller, receiver): 56 | result = yield signaller 57 | if receiver.is_alive: 58 | receiver.interrupt((signaller, result)) 59 | 60 | if event.callbacks is not None: 61 | env.process(signaller(event, subscriber)) 62 | else: 63 | raise RuntimeError('%s has already terminated.' % event) 64 | -------------------------------------------------------------------------------- /simulation/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mwong009/simulation/8585c645fd9449ba08a272c93df854f2f783eacb/simulation/__init__.py -------------------------------------------------------------------------------- /simulation/distribution.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import matplotlib.pyplot as plt 3 | 4 | def plot(x): 5 | plt.style.use('ggplot') 6 | plt.hist(x, bins='auto', normed=True) 7 | plt.show() 8 | 9 | def uniform(low=0, high=1): 10 | return np.random.uniform(low, high) 11 | 12 | def bernoulli(p): 13 | U = uniform() 14 | if u <= p: 15 | X = 1 16 | else: 17 | X = 0 18 | return X 19 | 20 | def poisson(LAMBDA=1): 21 | U = uniform() 22 | i = 0 23 | p = np.exp(-LAMBDA) 24 | F = p 25 | while U >= F: 26 | p = (LAMBDA*p)/(i+1) 27 | F = F + p 28 | i += 1 29 | X = i 30 | return X 31 | 32 | def exponential(LAMBDA=1): 33 | U = uniform() 34 | X = -(LAMBDA) * np.log(U) 35 | return X 36 | 37 | def normal(mean=0, var=1, d_type='Box-Muller'): 38 | if d_type == 'Box-Muller': 39 | while True: 40 | U_1 = uniform() 41 | U_2 = uniform() 42 | V_1 = 2*U_1 -1 43 | V_2 = 2*U_2 -1 44 | S = V_1*V_1 + V_2*V_2 45 | if S <= 1: 46 | X = np.sqrt(-2*np.log(S)/S) * V_1 47 | return X 48 | else: 49 | while True: 50 | Y_1 = exponential(1) 51 | Y_2 = exponential(1) 52 | if Y_2 - (Y_1-1)*(Y_1-1) > 0: 53 | Y = Y_2 - (Y_1-1)*(Y_1-1)/2 54 | U = uniform() 55 | if U <= 0.5: 56 | Z = Y_1 57 | return mean + np.sqrt(var)*Z 58 | else: 59 | Z = -Y_1 60 | return mean + np.sqrt(var)*Z 61 | 62 | def lognormal(mean=0, var=1, LAMBDA=1): 63 | N = normal() 64 | X = -(LAMBDA) * np.log(N) 65 | -------------------------------------------------------------------------------- /simulation/roadnetwork.py: -------------------------------------------------------------------------------- 1 | import random 2 | import string 3 | import simpy 4 | import numpy as np 5 | 6 | 7 | class RoadNetwork(object): 8 | def __init__(self, env): 9 | self.env = env 10 | self.links = {} 11 | self.nodes = {} 12 | self.trafficLights = {} 13 | 14 | def addLink(self, linkID=None, turns={}, type='link', length=0, t0=1, MU=1, nodeID=None, coordinates=((0, 0), (0 ,0))): 15 | if linkID in self.links: 16 | print('Error: Link %d has already been defined!' % linkID) 17 | else: 18 | if 'exit' not in turns.keys(): 19 | turns['exit'] = np.min((1 - sum(turns.values()), 1)) 20 | 21 | self.links[linkID] = {'length': length, 'turns': turns, 't0': t0, 'MU': MU} 22 | if nodeID is None: 23 | chars = string.ascii_uppercase + string.digits 24 | nodeID = ''.join([random.choice(chars) for i in range(8)]) 25 | if nodeID not in self.nodes.keys(): 26 | self.addNode(nodeID, cap=1) 27 | 28 | self.links[linkID]['node'] = self.nodes[nodeID] 29 | self.links[linkID]['queue'] = simpy.Container(self.env) 30 | self.links[linkID]['coordinates'] = coordinates 31 | print('Created link %s at node %s' % (linkID, nodeID)) 32 | 33 | def addNode(self, nodeID, cap=1): 34 | if nodeID in self.nodes: 35 | print('Error: Node %d has already been defined!' % nodeID) 36 | else: 37 | node = simpy.Resource(self.env, capacity=cap) 38 | self.nodes[nodeID] = node 39 | 40 | def addTrafficLight(self, nodeID, duration=60, sync=None, t=[5, 1, 5]): 41 | if nodeID in self.nodes: 42 | t1 = TrafficLight(self.env, duration, t) 43 | if sync is not None and self.nodes[sync] in self.trafficLights: 44 | t2 = self.trafficLights[self.nodes[sync]] 45 | t1.setStatus(t2.status) 46 | t1.setTimings(list(t2.timings.values())) 47 | self.trafficLights[self.nodes[nodeID]]= t1 48 | # to interrupt the traffic light, call tl.process.interrupt() 49 | print('Created traffic light at node %s' % (nodeID)) 50 | else: 51 | print('Node %s not found!' % nodeID) 52 | exit() 53 | 54 | class TrafficLight(object): 55 | def __init__(self, env, t_max=50, t=[5, 1, 5]): 56 | self.env = env 57 | self.timings = {'t_red': t[0], 't_amber': t[1], 't_green': t[2]} 58 | self.status = random.choice(['GREEN', 'RED']) 59 | self.process = env.process(self.cycle(t_max)) 60 | self.stop = simpy.PriorityResource(env, capacity=1) 61 | 62 | def setStatus(self, color): 63 | self.status = color 64 | 65 | def setTimings(self, t=[5, 1, 5]): 66 | self.timings = {'t_red': t[0], 't_amber': t[1], 't_green': t[2]} 67 | 68 | def cycle(self, t_max): 69 | while self.env.now < t_max: 70 | if self.status == 'RED': 71 | with self.stop.request(priority=-1): # all cars must yield to 'RED' 72 | print('Light is %s at %.2f' % (self.status, self.env.now)) 73 | try: 74 | yield self.env.timeout(self.timings['t_red']) 75 | except simpy.Interrupt: 76 | pass 77 | self.status = 'GREEN' 78 | self.prev_status = 'RED' 79 | if self.status == 'GREEN': 80 | print('Light is %s at %.2f' % (self.status, self.env.now)) 81 | try: 82 | yield self.env.timeout(self.timings['t_green']) 83 | except simpy.Interrupt: 84 | pass 85 | self.status = 'AMBER' 86 | self.prev_status = 'GREEN' 87 | if self.status == 'AMBER': 88 | with self.stop.request(priority=0): # allow cars in intersection to continue 89 | print('Light is %s at %.2f' % (self.status, self.env.now)) 90 | yield self.env.timeout(self.timings['t_amber']) 91 | try: 92 | self.prev_status 93 | except AttributeError: 94 | self.prev_status = random.choice(['GREEN', 'RED']) 95 | self.status = 'RED' 96 | self.prev_status = 'AMBER' 97 | -------------------------------------------------------------------------------- /simulation/simulation.py: -------------------------------------------------------------------------------- 1 | import simpy 2 | import cv2 3 | import numpy as np 4 | from numpy.random import multinomial 5 | 6 | from simulation.distribution import uniform, exponential 7 | from simulation.roadnetwork import RoadNetwork 8 | 9 | 10 | class Simulation(object): 11 | def __init__(self, env, img): 12 | self.env = env 13 | self.data = [] 14 | self.network = RoadNetwork(env) 15 | self.carCounter = 0 16 | self.carsInSystem = 0 17 | self.t_max = 0 18 | self.img = img 19 | self.networkLines = [] 20 | self.cars = {} 21 | 22 | def visualization(self, frequency, name): 23 | """ visualization env process """ 24 | while self.env.now < self.t_max or self.carsInSystem > 0: 25 | # redraw entire network 26 | for i in self.networkLines: 27 | cv2.line(self.img, i[0], i[1], (255,255,255), 3) 28 | for _linkid in self.network.links: 29 | if 'queueLines' in self.network.links[_linkid]: 30 | line = self.network.links[_linkid]['queueLines'] 31 | cap = self.network.links[_linkid]['capacity'] 32 | # overlay = self.img.copy() 33 | cv2.line(self.img, line[0], line[1], (0,0, 255), 3) 34 | # opacity = 0.8 35 | # cv2.addWeighted(overlay, opacity, self.img, 1-opacity, 0, self.img) 36 | 37 | cv2.imshow(name, self.img) 38 | k = cv2.waitKey(1) 39 | yield self.env.timeout(frequency) 40 | 41 | def updateQueue(self, queue, linkid, carLength=3.): 42 | # draw queue lines 43 | length = self.network.links[linkid]['length'] 44 | pt1 = self.network.links[linkid]['coordinates'][0] 45 | pt2 = self.network.links[linkid]['coordinates'][1] 46 | 47 | # coordinate math 48 | dk = np.min((1, queue.level * carLength/length)) 49 | x2 = pt2[0] + dk * (pt1[0] - pt2[0]) 50 | y2 = pt2[1] + dk * (pt1[1] - pt2[1]) 51 | pt1 = (np.float32(x2), np.float32(y2)) 52 | line = (pt2, pt1) 53 | 54 | # add to data dictionary 55 | self.network.links[linkid]['queueLines'] = line 56 | self.network.links[linkid]['capacity'] = dk 57 | 58 | def car(self, carID, t_arrival, node, turn_ratio, linkid): 59 | """ car generator """ 60 | 61 | # prepare variables 62 | t_entry, t_travel = t_arrival 63 | queue = self.network.links[linkid]['queue'] 64 | 65 | # en-route 66 | yield self.env.timeout(t_travel) 67 | 68 | # put 1 car in link queue 69 | yield queue.put(1) 70 | 71 | # update queue length for visualization 72 | self.updateQueue(queue, linkid) 73 | 74 | # query queue length 75 | with node.request() as req: 76 | q_length = queue.level 77 | 78 | # data logging 79 | print('car %d arrived on link %s at %.2fs (Q=%d cars) ' % (carID, linkid, sum(t_arrival), q_length)) 80 | self.data.append((carID, linkid, 'arrival', sum(t_arrival), q_length)) 81 | 82 | # wait until queue is ready 83 | result = yield req 84 | t_service = exponential(self.network.links[linkid]['MU']) 85 | 86 | # query traffic lights if available 87 | if node in self.network.trafficLights: 88 | tg = self.network.trafficLights[node] 89 | 90 | # yield to traffic light 'stop' 91 | with tg.stop.request(priority=0) as stop: 92 | yield stop 93 | 94 | # services at junction 95 | yield self.env.timeout(t_service) 96 | 97 | # time spent in queue 98 | t_depart = self.env.now 99 | t_queue = t_depart - sum(t_arrival) 100 | 101 | # recursions (move 'car' into next link with probability prob) 102 | prob = list(turn_ratio.values()) 103 | egress = list(turn_ratio.keys())[np.argmax(multinomial(1, prob))] 104 | if egress is not 'exit' and egress in self.network.links.keys(): 105 | c = self.car( 106 | carID=carID, 107 | t_arrival=(t_depart, exponential(self.network.links[egress]['t0'])), 108 | node=self.network.links[egress]['node'], 109 | turn_ratio=self.network.links[egress]['turns'], 110 | linkid=egress 111 | ) 112 | self.cars[carID].append(egress) # keep track of history 113 | 114 | self.env.process(c) 115 | 116 | # keep track of the number of cars in the system 117 | if egress is 'exit': 118 | self.carsInSystem -= 1 119 | 120 | # release 1 car from queue 121 | yield queue.get(1) 122 | 123 | # update queue length for visualization 124 | self.updateQueue(queue, linkid) 125 | 126 | # update queue level 127 | q_length = queue.level 128 | 129 | # data logging 130 | print('car %d departed link %s at %.2fs (Q=%d cars)' % (carID, linkid, t_depart, q_length)) 131 | self.data.append((carID, linkid, 'departure', t_depart, q_length, t_queue)) 132 | 133 | # prints car travel history 134 | print('car %d history: %s' % (carID, self.cars[carID])) 135 | 136 | def source(self, demand_duration, LAMBDA, linkid): 137 | """ Event generator """ 138 | if self.t_max < demand_duration: 139 | self.t_max = demand_duration 140 | if linkid not in self.network.links.keys(): 141 | print('Link %s not defined, exiting simulation' % linkid) 142 | exit() 143 | while self.env.now < demand_duration: 144 | arrival_rate = exponential(LAMBDA) 145 | turn_ratio = self.network.links[linkid]['turns'] 146 | n = self.network.links[linkid]['node'] 147 | t_entry = self.env.now 148 | t_travel = uniform(0, self.network.links[linkid]['t0']) 149 | t_arrival = (t_entry, t_travel) 150 | 151 | self.carCounter +=1 152 | self.carsInSystem +=1 153 | self.data.append((self.carCounter, linkid, 'entry', t_entry, None, None)) 154 | c = self.car( 155 | carID=self.carCounter, 156 | t_arrival=t_arrival, 157 | node=n, 158 | turn_ratio=turn_ratio, 159 | linkid=linkid 160 | ) 161 | self.cars[self.carCounter] = [] 162 | self.cars[self.carCounter].append(linkid) 163 | 164 | self.env.process(c) 165 | yield self.env.timeout(arrival_rate) 166 | -------------------------------------------------------------------------------- /simulation/siouxfalls.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | class SiouxFalls(object): 4 | def __init__(self, scale): 5 | self.scale = scale 6 | 7 | self.t0 = [6.0, 4.0, 6.0, 5.0, 4.0, 4.0, 4.0, 4.0, 2.0, 6.0, 2.0, 4.0, 5.0, 5.0, 4.0, 2.0, 3.0, 2.0, 2.0, 3.0, 10.0, 5.0, 5.0, 10.0, 3.0, 3.0, 5.0, 6.0, 4.0, 8.0, 6.0, 5.0, 6.0, 4.0, 4.0, 6.0, 3.0, 3.0, 4.0, 4.0, 5.0, 4.0, 6.0, 5.0, 3.0, 3.0, 5.0, 4.0, 2.0, 3.0, 8.0, 2.0, 2.0, 2.0, 3.0, 4.0, 3.0, 2.0, 4.0, 4.0, 4.0, 6.0, 5.0, 6.0, 2.0, 3.0, 3.0, 5.0, 2.0, 4.0, 4.0, 4.0, 2.0, 4.0, 3.0, 2.0] 8 | 9 | self.flambda = self.scale*np.asarray([52.261785999165475, 94.40488066750119, 28.7297382296481, 37.936928437018565, 11.759380960474777, 20.347525477920634, 14.559760228271255, 72.84636433709505, 93.48868312706102, 26.998285869177256, 43.020975715289154, 20.992694758591195, 37.65299619278632, 27.8095716613072, 40.87363462274193, 57.98346038261754, 87.48620974356392, 114.18045692310275, 87.51184795271215, 84.12569522773738, 48.08677857257881, 58.609011580304994, 96.10991728224717, 41.59562062554975, 132.29446209220308, 200.86235296992203, 163.22541463737082, 212.94058200464232, 101.72079998059415, 74.58418374080406, 47.99130613344198, 159.40560015563162, 75.74735560529209, 88.52240477230097, 75.34588526920359, 63.49466787575582, 92.82611352170724, 128.17600977568438, 115.15732355764895, 84.63248206234452, 77.92561694884904, 72.44190098880644, 118.56769802293753, 46.41946533329545, 97.56097332252625, 94.11852998790742, 78.72293892593113, 103.69090638099117, 109.51543682347209, 143.07071786960563, 106.23179549993455, 153.23396531782967, 130.53423918223578, 25.284045861784264, 24.452842230686052, 30.26311190752968, 108.04143689105523, 56.18810409856249, 49.103792343715604, 142.81175685549732, 65.49859801941444, 47.38731124966673, 52.63566720875482, 45.4527467960133, 62.78568846851396, 75.09489806880607, 171.2758740017857, 65.20723868038404, 80.18057130698395, 90.00298267751299, 78.2579911602372, 89.73637051210814, 73.67230499432135, 48.78233294972327, 45.03831646814931, 34.51268391546075]) 10 | 11 | self.mu = self.scale*np.asarray([431.67001066666666, 390.0578865, 431.67001066666666, 82.6363488, 390.0578865, 285.17539533333337, 390.0578865, 285.17539533333337, 296.3799016666666, 81.81377883333333, 296.3799016666666, 82.46659115000001, 166.66666666666666, 82.6363488, 82.46659115000001, 81.64312743333333, 130.69685516666667, 390.0578865, 81.64312743333333, 130.69685516666667, 84.16988593333333, 84.09704305, 166.66666666666666, 84.16988593333333, 231.929807, 231.929807, 166.66666666666666, 225.20002583333334, 80.91529528333334, 83.22517823333332, 81.81377883333333, 166.66666666666666, 81.81377883333333, 81.27513811666667, 390.0578865, 81.81377883333333, 431.67001066666666, 431.67001066666666, 84.8542692, 81.27513811666667, 85.45876865, 82.07984341666666, 225.20002583333334, 85.45876865, 242.74588583333335, 159.98634275, 84.09704305, 80.91529528333334, 87.16516771666667, 327.9982785, 83.22517823333332, 87.16516771666667, 80.39918051666668, 390.0578865, 327.9982785, 390.0578865, 242.74588583333335, 80.39918051666668, 83.37679271666666, 390.0578865, 83.37679271666666, 84.33187233333334, 84.59495321666667, 84.33187233333334, 87.16516771666667, 81.42262606666667, 159.98634275, 84.59495321666667, 87.16516771666667, 83.33333333333333, 82.07984341666666, 83.33333333333333, 84.64180726666667, 84.8542692, 81.42262606666667, 84.64180726666667]) 12 | 13 | self.turns = [[0.0, 0.0, 0.2665634819859004, 0.3519906676206934, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.3814458503934061], 14 | [0.0, 0.0, 0.0, 0.0, 0.23002255941941643, 0.3980132886258769, 0.28480013731137466, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.08716401464333198], 15 | [0.10773575051209111, 0.19461219084399545, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.6976520586439134], 16 | [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.15840956552248933, 0.23282540201516994, 0.33028681198569626, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.27847822047664456], 17 | [0.10773575051209111, 0.19461219084399545, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.6976520586439134], 18 | [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.2587191436459299, 0.33203183520995827, 0.09588636939598975, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.313362651748122], 19 | [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.1772382001975898, 0.14936025525792637, 0.21835742234817387, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.45504412219631], 20 | [0.0, 0.0, 0.0, 0.0, 0.23002255941941643, 0.3980132886258769, 0.28480013731137466, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.08716401464333198], 21 | [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.362577881501388, 0.17692501543776593, 0.317336912163721, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.1431601908971251], 22 | [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.058828160057412385, 0.19540076975460333, 0.09285176667381238, 0.10851153294582093, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.544407770568351], 23 | [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.2587191436459299, 0.33203183520995827, 0.09588636939598975, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.313362651748122], 24 | [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.15840956552248933, 0.23282540201516994, 0.33028681198569626, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.27847822047664456], 25 | [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.2255104437985229, 0.09759915659687471, 0.3104131571655227, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.36647724243907975], 26 | [0.0, 0.0, 0.2665634819859004, 0.3519906676206934, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.3814458503934061], 27 | [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.362577881501388, 0.17692501543776593, 0.317336912163721, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.1431601908971251], 28 | [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.1826118426911072, 0.17554592415313633, 0.10034315866536012, 0.12230000683755689, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.4191990676528394], 29 | [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.1826118426911072, 0.17554592415313633, 0.10034315866536012, 0.12230000683755689, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.4191990676528394], 30 | [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.286380286455468, 0.276965640742595, 0.34277578455952984, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.09387828824240722], 31 | [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.15840956552248933, 0.23282540201516994, 0.33028681198569626, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.27847822047664456], 32 | [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.24564344709576422, 0.32059545283505386, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.43376110006918195], 33 | [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.2255104437985229, 0.09759915659687471, 0.3104131571655227, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.36647724243907975], 34 | [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.07929162714592665, 0.10443996120267422, 0.1103065675876293, 0.144104249302614, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.5618575947611558], 35 | [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.362577881501388, 0.17692501543776593, 0.317336912163721, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.1431601908971251], 36 | [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.1826118426911072, 0.17554592415313633, 0.10034315866536012, 0.12230000683755689, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.4191990676528394], 37 | [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.1194701473233669, 0.09708421735238976, 0.12665411077320005, 0.060502123866646634, 0.04436164013692121, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.5519277605474755], 38 | [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.2255104437985229, 0.09759915659687471, 0.3104131571655227, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.36647724243907975], 39 | [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.058828160057412385, 0.19540076975460333, 0.09285176667381238, 0.10851153294582093, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.544407770568351], 40 | [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.23079230130307557, 0.09035559775696295, 0.18990266257946492, 0.1832019385833862, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.3057474997771104], 41 | [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.07929162714592665, 0.10443996120267422, 0.1103065675876293, 0.144104249302614, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.5618575947611558], 42 | [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.05804554638843777, 0.08372775024917872, 0.07132451447397936, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.7869021888884041], 43 | [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.2587191436459299, 0.33203183520995827, 0.09588636939598975, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.313362651748122], 44 | [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.1194701473233669, 0.09708421735238976, 0.12665411077320005, 0.060502123866646634, 0.04436164013692121, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.5519277605474755], 45 | [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.1772382001975898, 0.14936025525792637, 0.21835742234817387, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.45504412219631], 46 | [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.17379721632274564, 0.16002431898388486, 0.14876322223077354, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.517415242462596], 47 | [0.0, 0.0, 0.0, 0.0, 0.23002255941941643, 0.3980132886258769, 0.28480013731137466, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.08716401464333198], 48 | [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.058828160057412385, 0.19540076975460333, 0.09285176667381238, 0.10851153294582093, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.544407770568351], 49 | [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.20034536125809463, 0.1799965190837857, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.6196581196581197], 50 | [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.1772382001975898, 0.14936025525792637, 0.21835742234817387, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.45504412219631], 51 | [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.27904560457094535, 0.25762901213980544, 0.19742009383768752, 0.2659052894515617], 52 | [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.058828160057412385, 0.19540076975460333, 0.09285176667381238, 0.10851153294582093, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.544407770568351], 53 | [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.23079230130307557, 0.09035559775696295, 0.18990266257946492, 0.1832019385833862, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.3057474997771104], 54 | [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.14270202324900153, 0.16363263918798832, 0.13434010794604853, 0.0, 0.0, 0.0, 0.5593252296169616], 55 | [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.1194701473233669, 0.09708421735238976, 0.12665411077320005, 0.060502123866646634, 0.04436164013692121, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.5519277605474755], 56 | [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.17379721632274564, 0.16002431898388486, 0.14876322223077354, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.517415242462596], 57 | [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.3347088624562881, 0.17406892158764284, 0.1521219538594968, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.3391002620965722], 58 | [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.18577008556403413, 0.0707253977225248, 0.08696584781179546, 0.09761948021263958, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.558919188689006], 59 | [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.1826118426911072, 0.17554592415313633, 0.10034315866536012, 0.12230000683755689, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.4191990676528394], 60 | [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.1194701473233669, 0.09708421735238976, 0.12665411077320005, 0.060502123866646634, 0.04436164013692121, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.5519277605474755], 61 | [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.05804554638843777, 0.08372775024917872, 0.07132451447397936, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.7869021888884041], 62 | [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.286380286455468, 0.276965640742595, 0.34277578455952984, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.09387828824240722], 63 | [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.1194701473233669, 0.09708421735238976, 0.12665411077320005, 0.060502123866646634, 0.04436164013692121, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.5519277605474755], 64 | [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.07929162714592665, 0.10443996120267422, 0.1103065675876293, 0.144104249302614, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.5618575947611558], 65 | [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.3347088624562881, 0.17406892158764284, 0.1521219538594968, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.3391002620965722], 66 | [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.24564344709576422, 0.32059545283505386, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.43376110006918195], 67 | [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.07929162714592665, 0.10443996120267422, 0.1103065675876293, 0.144104249302614, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.5618575947611558], 68 | [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.2548281435446044, 0.11687333385969191, 0.08455620755048562, 0.09392118446249816, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.4498211305827199], 69 | [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.23079230130307557, 0.09035559775696295, 0.18990266257946492, 0.1832019385833862, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.3057474997771104], 70 | [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.05804554638843777, 0.08372775024917872, 0.07132451447397936, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.7869021888884041], 71 | [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.2548281435446044, 0.11687333385969191, 0.08455620755048562, 0.09392118446249816, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.4498211305827199], 72 | [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.286380286455468, 0.276965640742595, 0.34277578455952984, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.09387828824240722], 73 | [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.3347088624562881, 0.17406892158764284, 0.1521219538594968, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.3391002620965722], 74 | [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.13956967737270776, 0.19279315114884188, 0.23059047988534176, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.43704669159310866], 75 | [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.18577008556403413, 0.0707253977225248, 0.08696584781179546, 0.09761948021263958, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.558919188689006], 76 | [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.2548281435446044, 0.11687333385969191, 0.08455620755048562, 0.09392118446249816, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.4498211305827199], 77 | [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.18577008556403413, 0.0707253977225248, 0.08696584781179546, 0.09761948021263958, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.558919188689006], 78 | [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.27904560457094535, 0.25762901213980544, 0.19742009383768752, 0.2659052894515617], 79 | [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.23079230130307557, 0.09035559775696295, 0.18990266257946492, 0.1832019385833862, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.3057474997771104], 80 | [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.2548281435446044, 0.11687333385969191, 0.08455620755048562, 0.09392118446249816, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.4498211305827199], 81 | [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.13956967737270776, 0.19279315114884188, 0.23059047988534176, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.43704669159310866], 82 | [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.14270202324900153, 0.16363263918798832, 0.13434010794604853, 0.0, 0.0, 0.0, 0.5593252296169616], 83 | [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.17379721632274564, 0.16002431898388486, 0.14876322223077354, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.517415242462596], 84 | [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.18577008556403413, 0.0707253977225248, 0.08696584781179546, 0.09761948021263958, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.558919188689006], 85 | [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.27904560457094535, 0.25762901213980544, 0.19742009383768752, 0.2659052894515617], 86 | [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.20034536125809463, 0.1799965190837857, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.6196581196581197], 87 | [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.13956967737270776, 0.19279315114884188, 0.23059047988534176, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.43704669159310866], 88 | [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.14270202324900153, 0.16363263918798832, 0.13434010794604853, 0.0, 0.0, 0.0, 0.5593252296169616]] 89 | 90 | self.x1 = [50000.0, 50000.0, 320000.0, 320000.0, 50000.0, 50000.0, 50000.0, 130000.0, 130000.0, 130000.0, 220000.0, 220000.0, 220000.0, 320000.0, 320000.0, 320000.0, 420000.0, 420000.0, 320000.0, 320000.0, 320000.0, 320000.0, 220000.0, 220000.0, 220000.0, 220000.0, 220000.0, 220000.0, 220000.0, 220000.0, 130000.0, 130000.0, 130000.0, 130000.0, 50000.0, 50000.0, 50000.0, 50000.0, 50000.0, 130000.0, 130000.0, 130000.0, 220000.0, 220000.0, 220000.0, 220000.0, 320000.0, 320000.0, 320000.0, 320000.0, 320000.0, 320000.0, 320000.0, 420000.0, 420000.0, 420000.0, 320000.0, 320000.0, 320000.0, 320000.0, 320000.0, 320000.0, 320000.0, 220000.0, 220000.0, 220000.0, 220000.0, 220000.0, 220000.0, 220000.0, 130000.0, 130000.0, 130000.0, 130000.0, 130000.0, 130000.0] 91 | 92 | self.y1 = [510000.0, 510000.0, 510000.0, 510000.0, 440000.0, 440000.0, 440000.0, 440000.0, 440000.0, 440000.0, 440000.0, 440000.0, 440000.0, 440000.0, 440000.0, 440000.0, 380000.0, 380000.0, 380000.0, 380000.0, 380000.0, 380000.0, 380000.0, 380000.0, 380000.0, 320000.0, 320000.0, 320000.0, 320000.0, 320000.0, 320000.0, 320000.0, 320000.0, 320000.0, 320000.0, 320000.0, 320000.0, 50000.0, 50000.0, 190000.0, 190000.0, 190000.0, 190000.0, 190000.0, 190000.0, 190000.0, 320000.0, 320000.0, 320000.0, 320000.0, 260000.0, 260000.0, 260000.0, 320000.0, 320000.0, 320000.0, 190000.0, 190000.0, 190000.0, 50000.0, 50000.0, 50000.0, 50000.0, 50000.0, 50000.0, 50000.0, 130000.0, 130000.0, 130000.0, 130000.0, 130000.0, 130000.0, 130000.0, 50000.0, 50000.0, 50000.0] 93 | 94 | self.x2 = [320000.0, 50000.0, 50000.0, 320000.0, 50000.0, 130000.0, 50000.0, 50000.0, 220000.0, 130000.0, 130000.0, 320000.0, 220000.0, 320000.0, 220000.0, 320000.0, 320000.0, 420000.0, 320000.0, 420000.0, 220000.0, 320000.0, 220000.0, 320000.0, 220000.0, 220000.0, 130000.0, 220000.0, 320000.0, 320000.0, 130000.0, 220000.0, 50000.0, 130000.0, 50000.0, 130000.0, 50000.0, 50000.0, 130000.0, 130000.0, 220000.0, 130000.0, 220000.0, 130000.0, 320000.0, 220000.0, 320000.0, 220000.0, 320000.0, 420000.0, 220000.0, 320000.0, 320000.0, 420000.0, 320000.0, 320000.0, 220000.0, 320000.0, 320000.0, 420000.0, 320000.0, 220000.0, 220000.0, 320000.0, 220000.0, 130000.0, 220000.0, 320000.0, 220000.0, 130000.0, 130000.0, 220000.0, 130000.0, 50000.0, 220000.0, 130000.0] 95 | 96 | self.y2 = [510000.0, 440000.0, 510000.0, 440000.0, 510000.0, 440000.0, 320000.0, 440000.0, 440000.0, 320000.0, 440000.0, 440000.0, 380000.0, 510000.0, 440000.0, 380000.0, 380000.0, 320000.0, 440000.0, 380000.0, 380000.0, 320000.0, 440000.0, 380000.0, 320000.0, 380000.0, 320000.0, 190000.0, 320000.0, 260000.0, 440000.0, 320000.0, 320000.0, 190000.0, 440000.0, 320000.0, 50000.0, 320000.0, 50000.0, 320000.0, 190000.0, 130000.0, 320000.0, 190000.0, 190000.0, 130000.0, 380000.0, 320000.0, 260000.0, 320000.0, 320000.0, 320000.0, 190000.0, 380000.0, 320000.0, 50000.0, 190000.0, 260000.0, 50000.0, 320000.0, 190000.0, 50000.0, 130000.0, 50000.0, 130000.0, 50000.0, 190000.0, 50000.0, 50000.0, 130000.0, 190000.0, 130000.0, 50000.0, 50000.0, 50000.0, 130000.0] 97 | 98 | self.intersection = {} 99 | for linkid, (x, y) in enumerate(zip(self.x2, self.y2)): 100 | if (x, y) in self.intersection: 101 | self.intersection[(x,y)].append(linkid+1) 102 | else: 103 | self.intersection[(x,y)] = [linkid+1] 104 | 105 | self.nodes = [] 106 | for values in self.intersection.values(): 107 | self.nodes.append(values) 108 | -------------------------------------------------------------------------------- /simulation/statistics.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | import numpy as np 3 | 4 | def meanQueueLength(plt, data): 5 | mql = data.loc[data['event'] == 'departure'][['link', 'queue']].groupby(['link']).mean() 6 | vql = data.loc[data['event'] == 'departure'][['link', 'queue']].groupby(['link']).var() 7 | mql.plot.bar(yerr=np.sqrt(vql)) 8 | 9 | mql.columns=['mean'] 10 | vql.columns=['variance'] 11 | print(pd.concat([mql, vql], axis=1)) 12 | 13 | plt.title('Mean Queue Length') 14 | plt.ylabel('length (cars)') 15 | plt.xlabel('Link ID') 16 | --------------------------------------------------------------------------------