Simulation Workflow

In this workflow a simply simulation system will be generated and analyzed. After installing the package via PyPI

pip install simemobilecity

it can be imported into the python script

import simemobilecity as sec

Create Topology

First, the topology object is generated by loading the OSM map using an OSMnx wrapper function

name = "Munich, Bavaria , Germany"
topo = sec.Topology({"name": name})

Since this step takes a long time depending on the complexity of the desired map, it is advised to deposit the graph objects for later use

sec.utils.save(topo.get_G(), "data/munich_G.obj")
sec.utils.save(topo.get_Gp(), "data/munich_Gp.obj")

The save functionality creates pickle object files containing the graph \(G\) and the projected graph \(G_p\), necessary for running the simulation. Once saved, the object files can be loaded later

G = sec.utils.load("data/munich_G.obj")
Gp = sec.utils.load("data/munich_Gp.obj")
topo = sec.Topology({"name": name, "G": G, "Gp": Gp})

Define User and POI objects

The next step is defining the user and POI objects which contain the probability densities for charging sessions.

input_all = 0.2
input_daily = {0: 0.0, 1: 0.1, 2: 0.2, 3: 0.3, 4: 0.4, 5: 0.5, 6: 0.6}
input_hourly = { 0: 0.0,  1: 0.1,  2: 0.2,  3: 0.3,  4: 0.4,  5: 0.5,  6: 0.6,  7: 0.7,  8: 0.8,  9: 0.9, 10: 0.0, 11: 0.1,
                12: 0.2, 13: 0.3, 14: 0.4, 15: 0.5, 16: 0.6, 17: 0.7, 18: 0.8, 19: 0.9, 20: 0.0, 21: 0.1, 22: 0.2, 23: 0.3}
input_exact = {0: { 0: 0.0,  1: 0.1,  2: 0.2,  3: 0.3,  4: 0.4,  5: 0.5,  6: 0.6,  7: 0.7,  8: 0.8,  9: 0.9, 10: 0.0, 11: 0.1,
                   12: 0.2, 13: 0.3, 14: 0.4, 15: 0.5, 16: 0.6, 17: 0.7, 18: 0.8, 19: 0.9, 20: 0.0, 21: 0.1, 22: 0.2, 23: 0.3},
               1: { 0: 0.0,  1: 0.1,  2: 0.2,  3: 0.3,  4: 0.4,  5: 0.5,  6: 0.6,  7: 0.7,  8: 0.8,  9: 0.9, 10: 0.0, 11: 0.1,
                   12: 0.2, 13: 0.3, 14: 0.4, 15: 0.5, 16: 0.6, 17: 0.7, 18: 0.8, 19: 0.9, 20: 0.0, 21: 0.1, 22: 0.2, 23: 0.3},
               2: { 0: 0.0,  1: 0.1,  2: 0.2,  3: 0.3,  4: 0.4,  5: 0.5,  6: 0.6,  7: 0.7,  8: 0.8,  9: 0.9, 10: 0.0, 11: 0.1,
                   12: 0.2, 13: 0.3, 14: 0.4, 15: 0.5, 16: 0.6, 17: 0.7, 18: 0.8, 19: 0.9, 20: 0.0, 21: 0.1, 22: 0.2, 23: 0.3},
               3: { 0: 0.0,  1: 0.1,  2: 0.2,  3: 0.3,  4: 0.4,  5: 0.5,  6: 0.6,  7: 0.7,  8: 0.8,  9: 0.9, 10: 0.0, 11: 0.1,
                   12: 0.2, 13: 0.3, 14: 0.4, 15: 0.5, 16: 0.6, 17: 0.7, 18: 0.8, 19: 0.9, 20: 0.0, 21: 0.1, 22: 0.2, 23: 0.3},
               4: { 0: 0.0,  1: 0.1,  2: 0.2,  3: 0.3,  4: 0.4,  5: 0.5,  6: 0.6,  7: 0.7,  8: 0.8,  9: 0.9, 10: 0.0, 11: 0.1,
                   12: 0.2, 13: 0.3, 14: 0.4, 15: 0.5, 16: 0.6, 17: 0.7, 18: 0.8, 19: 0.9, 20: 0.0, 21: 0.1, 22: 0.2, 23: 0.3},
               5: { 0: 0.0,  1: 0.1,  2: 0.2,  3: 0.3,  4: 0.4,  5: 0.5,  6: 0.6,  7: 0.7,  8: 0.8,  9: 0.9, 10: 0.0, 11: 0.1,
                   12: 0.2, 13: 0.3, 14: 0.4, 15: 0.5, 16: 0.6, 17: 0.7, 18: 0.8, 19: 0.9, 20: 0.0, 21: 0.1, 22: 0.2, 23: 0.3},
               6: { 0: 0.0,  1: 0.1,  2: 0.2,  3: 0.3,  4: 0.4,  5: 0.5,  6: 0.6,  7: 0.7,  8: 0.8,  9: 0.9, 10: 0.0, 11: 0.1,
                   12: 0.2, 13: 0.3, 14: 0.4, 15: 0.5, 16: 0.6, 17: 0.7, 18: 0.8, 19: 0.9, 20: 0.0, 21: 0.1, 22: 0.2, 23: 0.3}}

Therefore multiple input possibilities exist

  • input_all - Sets the probability for all hours of all days to the same value
  • input_daily - Sets a different probability for all hours of each day
  • input_hourly - Sets the same probability for all days for each hour
  • input_exact - Sets a different probability for each hour for each day

These probability inputs are then passed to the object constructures of the User

user_a = sec.User(input_hourly)
user_b = sec.User(input_exact)

and POI classes

poi_a = sec.Poi(topo, {"amenity": ["cafe"]}, input_all)
poi_b = sec.Poi(topo, {"amenity": ["restaurant"]}, input_daily)

topo.plot(pois=[topo.poi({"amenity": ["cafe"]})])
_images/poi.png

In case of the POIs, a description tag is added for extracting the relevant POIs. A list of available tags is available on the OSM website.

Simulation

Finally, the simulation is run using the Monte Carlo class by first adding the user and POI objects, then defining the simulation parameters at the execution command

mc = sec.MC(topo)
mc.add_user(user_a, 50)
mc.add_user(user_b, 50)
mc.add_poi(poi_a)
mc.add_poi(poi_b)
mc.set_drivers(100)
traj = mc.run("data/traj.obj", weeks=4, weeks_equi=1)

Starting preparation...

Starting equilibration...

Finished day 7/7...

Starting production...

Finished day 28/28...

The definition of the number of drivers is analogous to the definitions of the probabilities shown earlier.

Optimization

Once the simulation is finished, the output trajectory can be analyzed using the Optimize module

opt = sec.Optimize(topo)
capacity_opt = opt.run("data/opt.obj", traj, crit={"dist": 0.85, "occ": 0.05})

Finished node 430/430...

This new optimized charging station list can be passed to the MC class for further iterations

traj_opt = mc.run("data/traj_opt.obj", weeks=4, weeks_equi=1, capacity=capacity_opt)

Analysis

The simulated data can be extracted using the trajectory functionalities

extract = traj["cs"].extract(days=list(range(7)), hours=list(range(24)), users=list(range(2)), is_norm=False)

This command aggregates the data of all days, all hours, and all users of all nodes into a single data structure. The necessary data is converted into a pandas Dataframe

import pandas as pd

data = [{"node": node, "x": G.nodes[node]["x"], "y": G.nodes[node]["y"], "Charging Sessions": x["success"], "Occupancy Fail": x["fail"]["occ"], "Distance Fail": x["fail"]["dist"]} for node, x in extract.items()]
df = pd.DataFrame(data)

and then plotted using the scatterplot function of seaborn

import seaborn as sns

sns.scatterplot(data=df, x="x", y="y", hue="Charging Sessions", size="Charging Sessions")
_images/analyze.png

Note

For further information, more thorough variable descriptions and functionalities, visit the API section of this documentation website.