1
0
mirror of https://github.com/gsi-upm/soil synced 2025-08-24 03:52:20 +00:00

v1.0.0rc11

This commit is contained in:
J. Fernando Sánchez
2024-04-11 17:46:45 +02:00
parent f49be3af68
commit 25d042f16c
30 changed files with 5896 additions and 404 deletions

View File

@@ -0,0 +1,355 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": 1,
"id": "7641396c-a602-477e-bf03-09e1191ff549",
"metadata": {},
"outputs": [],
"source": [
"%load_ext autoreload"
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "4f12285c-78db-4ee8-b9c6-7799d34f10f5",
"metadata": {},
"outputs": [],
"source": [
"%autoreload 1"
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "7710bb03-0cb9-413a-a407-fe48855ff917",
"metadata": {},
"outputs": [],
"source": [
"%aimport markov_sim"
]
},
{
"cell_type": "code",
"execution_count": 10,
"id": "2dffca0f-da9e-4f69-ac43-7afe52ad2d32",
"metadata": {},
"outputs": [],
"source": [
"%aimport soil\n",
"%aimport soil.visualization"
]
},
{
"cell_type": "code",
"execution_count": 11,
"id": "12871006-70ca-4c6f-8a3e-0aae1d0bce31",
"metadata": {},
"outputs": [],
"source": [
"G = markov_sim.load_city_graph(\"Chamberi, Madrid\", network_type=\"drive\")"
]
},
{
"cell_type": "code",
"execution_count": 12,
"id": "31e96cc5-b703-4d2a-a006-7b9a2cedc365",
"metadata": {},
"outputs": [],
"source": [
"# env = markov_sim.CityEnv(G=G, n_assets=20, side=10, max_weight=1, seed=10)"
]
},
{
"cell_type": "code",
"execution_count": 13,
"id": "5e070b36-0ba6-4780-8fd4-3c72fa3bb240",
"metadata": {},
"outputs": [],
"source": [
"# for i in range(2):\n",
"# env.step()"
]
},
{
"cell_type": "code",
"execution_count": 35,
"id": "56f8b997-65b0-431d-9517-b93edb1cfcd8",
"metadata": {},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"/home/j/.cache/pypoetry/virtualenvs/soil-cCX5yKRx-py3.10/lib/python3.10/site-packages/osmnx/plot.py:955: UserWarning: FigureCanvasAgg is non-interactive, and thus cannot be shown\n",
" plt.show()\n",
"/home/j/.cache/pypoetry/virtualenvs/soil-cCX5yKRx-py3.10/lib/python3.10/site-packages/osmnx/plot.py:955: UserWarning: FigureCanvasAgg is non-interactive, and thus cannot be shown\n",
" plt.show()\n"
]
},
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "86e45bd44e434674b11805fd94e98414",
"version_major": 2,
"version_minor": 0
},
"text/html": [
"Cannot show widget. You probably want to rerun the code cell above (<i>Click in the code cell, and press Shift+Enter <kbd>⇧</kbd>+<kbd>↩</kbd></i>)."
],
"text/plain": [
"Cannot show ipywidgets in text"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"from soil.visualization import JupyterViz, GeoNetworkDrawer, Controller\n",
"from soil import visualization\n",
"from matplotlib import colors\n",
"from matplotlib import colormaps\n",
"plasma = colormaps.get_cmap('plasma')\n",
"model_params = {\n",
" \"n_assets\": {\n",
" \"type\": \"SliderInt\",\n",
" \"value\": 100,\n",
" \"label\": \"Number of assets:\",\n",
" \"min\": 1,\n",
" \"max\": 1000,\n",
" \"step\": 1,\n",
" },\n",
" \"max_weight\": {\n",
" \"type\": \"SliderInt\",\n",
" \"value\": 3,\n",
" \"label\": \"Maximum edge weight:\",\n",
" \"min\": 1,\n",
" \"max\": 20,\n",
" \"step\": 1,\n",
" },\n",
" \"ratio_lazy\": {\n",
" \"type\": \"SliderFloat\",\n",
" \"value\": 0,\n",
" \"label\": \"Ratio of lazy agents (they prefer shorter streets):\",\n",
" \"min\": 0,\n",
" \"max\": 1,\n",
" \"step\": 0.05,\n",
" },\n",
" \"side\": {\n",
" \"type\": \"SliderInt\",\n",
" \"value\": 10,\n",
" \"label\": \"Size of the side:\",\n",
" \"min\": 2,\n",
" \"max\": 20,\n",
" \"step\": 1,\n",
" },\n",
" \"gradual_move\": {\n",
" \"type\": \"Checkbox\",\n",
" \"value\": True,\n",
" \"label\": \"Use gradual movement\",\n",
" }, \n",
" \"lockstep\": {\n",
" \"type\": \"Checkbox\",\n",
" \"value\": True,\n",
" \"label\": \"Run in locksteps\",\n",
" },\n",
" \"G\": G,\n",
" # \"width\": 10,\n",
"}\n",
"\n",
"def colorize(d):\n",
" # print(d)\n",
" if any(a.waiting for a in d):\n",
" return 'red'\n",
" else:\n",
" return 'blue'\n",
"\n",
"def network_portrayal(graph, spring=True):\n",
" global pos, l\n",
" node_size = [10*(len(node[1][\"agent\"])) for node in graph.nodes(data=True)]\n",
" node_color = [colorize(d[\"agent\"]) for (k, d) in graph.nodes(data=True)]\n",
" # pos = {node: (d[\"x\"], d[\"y\"]) for node, d in graph.nodes(data=True)}\n",
" edge_width = [graph.edges[k]['travel_time']/100 for k in graph.edges]\n",
" # print(edge_width)\n",
" weights = [graph.edges[k]['occupation'] for k in graph.edges]\n",
" norm = colors.Normalize(vmin=0, vmax=max(weights))\n",
" color = plasma(norm(weights))\n",
" # print(color)\n",
" return dict(node_size=node_size, node_color=node_color, edge_linewidth=edge_width, edge_color=color)\n",
"\n",
"page = visualization.JupyterViz(\n",
" markov_sim.CityEnv,\n",
" model_params,\n",
" measures=[\"NodeGini\", \"EdgeGini\", \"EdgeOccupation\"],\n",
" name=\"City Environment\",\n",
" space_drawer=GeoNetworkDrawer,\n",
" agent_portrayal=network_portrayal,\n",
" columns=3,\n",
")\n",
"# This is required to render the visualization in the Jupyter notebook\n",
"page"
]
},
{
"cell_type": "code",
"execution_count": 39,
"id": "70da18d7-66bd-4710-89a6-aca14707c56e",
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"<div>\n",
"<style scoped>\n",
" .dataframe tbody tr th:only-of-type {\n",
" vertical-align: middle;\n",
" }\n",
"\n",
" .dataframe tbody tr th {\n",
" vertical-align: top;\n",
" }\n",
"\n",
" .dataframe thead th {\n",
" text-align: right;\n",
" }\n",
"</style>\n",
"<table border=\"1\" class=\"dataframe\">\n",
" <thead>\n",
" <tr style=\"text-align: right;\">\n",
" <th></th>\n",
" <th>NodeGini</th>\n",
" <th>EdgeGini</th>\n",
" <th>EdgeOccupation</th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <th>0</th>\n",
" <td>0.866567</td>\n",
" <td>0.927276</td>\n",
" <td>0.087624</td>\n",
" </tr>\n",
" <tr>\n",
" <th>1</th>\n",
" <td>0.866567</td>\n",
" <td>0.933494</td>\n",
" <td>0.081301</td>\n",
" </tr>\n",
" <tr>\n",
" <th>2</th>\n",
" <td>0.863867</td>\n",
" <td>0.933163</td>\n",
" <td>0.078591</td>\n",
" </tr>\n",
" <tr>\n",
" <th>3</th>\n",
" <td>0.866567</td>\n",
" <td>0.929943</td>\n",
" <td>0.084914</td>\n",
" </tr>\n",
" <tr>\n",
" <th>4</th>\n",
" <td>0.869433</td>\n",
" <td>0.934949</td>\n",
" <td>0.076784</td>\n",
" </tr>\n",
" <tr>\n",
" <th>...</th>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" </tr>\n",
" <tr>\n",
" <th>127</th>\n",
" <td>0.880367</td>\n",
" <td>0.934185</td>\n",
" <td>0.075881</td>\n",
" </tr>\n",
" <tr>\n",
" <th>128</th>\n",
" <td>0.881400</td>\n",
" <td>0.933038</td>\n",
" <td>0.078591</td>\n",
" </tr>\n",
" <tr>\n",
" <th>129</th>\n",
" <td>0.881400</td>\n",
" <td>0.936299</td>\n",
" <td>0.078591</td>\n",
" </tr>\n",
" <tr>\n",
" <th>130</th>\n",
" <td>0.881400</td>\n",
" <td>0.929784</td>\n",
" <td>0.086721</td>\n",
" </tr>\n",
" <tr>\n",
" <th>131</th>\n",
" <td>0.876733</td>\n",
" <td>0.932746</td>\n",
" <td>0.082204</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"<p>132 rows × 3 columns</p>\n",
"</div>"
],
"text/plain": [
" NodeGini EdgeGini EdgeOccupation\n",
"0 0.866567 0.927276 0.087624\n",
"1 0.866567 0.933494 0.081301\n",
"2 0.863867 0.933163 0.078591\n",
"3 0.866567 0.929943 0.084914\n",
"4 0.869433 0.934949 0.076784\n",
".. ... ... ...\n",
"127 0.880367 0.934185 0.075881\n",
"128 0.881400 0.933038 0.078591\n",
"129 0.881400 0.936299 0.078591\n",
"130 0.881400 0.929784 0.086721\n",
"131 0.876733 0.932746 0.082204\n",
"\n",
"[132 rows x 3 columns]"
]
},
"execution_count": 39,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"page.controller.model.datacollector.get_model_vars_dataframe()"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d9a7d3c8-2f87-47d5-8d27-a7387ea3457d",
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.12"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@@ -0,0 +1,11 @@
from flask import Flask
import solara.server.flask
app = Flask(__name__)
app.register_blueprint(solara.server.flask.blueprint, url_prefix="/solara/")
@app.route("/")
def hello_world():
return "<p>Hello, World!</p>"

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,159 @@
'''
This scenario has drivers driving around a city.
In this model, drivers can only be at intersections, which are treated as nodes in the City Graph (grid).
At the start of the simulation, drivers are randomly positioned in the city grid.
The following models for agent behavior are included:
* DummyDriver: In each simulation step, this type of driver can instantly move to any of the neighboring nodes in the grid, or stay in its place.
'''
import networkx as nx
from soil import Environment, BaseAgent, state, time
from mesa.space import NetworkGrid
import mesa
import statistics
class CityGrid(NetworkGrid):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
for (u, v, d) in self.G.edges(data=True):
d["occupation"] = 0
# self.dijkstras = dict(nx.all_pairs_dijkstra(self.G, weight="length"))
# def eta(self, pos1, pos2):
# return self.dijkstras[pos1][0][pos2]
def travel_time(self, pos1, pos2):
return float(min(d["travel_time"] for d in self.G.adj[pos1][pos2].values()))
def node_occupation(self):
return {k: len(v.get("agent", [])) for (k, v) in self.G.nodes(data=True)}
def edge_occupation(self):
return {(u,v): d.get('occupation', 1) for (u, v, d) in self.G.edges(data=True)}
class Roamer(BaseAgent):
waiting = False
def step(self):
'''
A simple driver that just moves to a neighboring cell in the city
'''
yield from self.move_to(None)
return self.delay(0)
def choose_next(self):
opts = self.model.grid.get_neighborhood(self.pos, include_center=False)
pos = self.random.choice(opts)
delay = self.model.grid.travel_time(self.pos, pos)
return pos, delay
def move_to(self, pos=None):
self.waiting = True
if pos is None:
pos, delay = self.choose_next()
if self.model.gradual_move:
# Calculate how long it will take, and wait for that long
if pos != self.pos:
self.model.grid.G.edges[self.pos,pos,0]["occupation"] += 1
yield delay
if self.model.gradual_move and pos != self.pos:
w1 = self.model.grid.G.edges[self.pos,pos,0]["occupation"]
oldpos = self.pos
self.model.grid.G.edges[self.pos,pos,0]["occupation"] = w1 - 1
assert self.model.grid.G.edges[self.pos,pos,0]["occupation"] == w1-1
self.model.grid.move_agent(self, pos)
self.waiting = False
class LazyRoamer(Roamer):
waiting = False
def choose_next(self):
opts = self.model.grid.get_neighborhood(self.pos, include_center=False)
times = [self.model.grid.travel_time(self.pos, other) for other in opts]
idx = self.random.choices(range(len(times)), k=1, weights=[1/time for time in times])[0]
return opts[idx], times[idx]
def gini(values):
s = sum(values)
N = len(values)
if s == 0:
return 0
x = sorted(values)
B = sum(xi * (N - i) for i, xi in enumerate(x)) / (N * s)
return 1 + (1 / N) - 2 * B
class CityEnv(Environment):
def __init__(self, *, G, side=20, n_assets=100, ratio_lazy=1, lockstep=True, gradual_move=True, max_weight=1, **kwargs):
super().__init__(**kwargs)
if lockstep:
self.schedule = time.Lockstepper(self.schedule)
self.n_assets = n_assets
self.side = side
self.max_weight = max_weight
self.gradual_move = gradual_move
self.grid = CityGrid(g=G)
n_lazy = round(self.n_assets * ratio_lazy)
n_other = self.n_assets - n_lazy
self.add_agents(Roamer, k=n_other)
self.add_agents(LazyRoamer, k=n_lazy)
positions = list(self.grid.G.nodes)
for agent in self.get_agents():
pos = self.random.choice(positions)
self.grid.place_agent(agent, pos)
self.datacollector = mesa.DataCollector(
model_reporters={
"NodeGini": lambda model: gini(model.grid.node_occupation().values()),
"EdgeGini": lambda model: gini(model.grid.edge_occupation().values()),
"EdgeOccupation": lambda model: statistics.mean(model.grid.edge_occupation().values()),
}#, agent_reporters={"Wealth": "wealth"}
)
class SquareCityEnv(CityEnv):
def __init__(self, *, side=20, **kwargs):
self.side = side
G = nx.grid_graph(dim=[side, side])
for (_, _, d) in G.edges(data=True):
d["travel_time"] = self.random.randint(1, self.max_weight)
for (k, d) in G.nodes(data=True):
d["pos"] = k
super().__init__(**kwargs, G=G)
import osmnx as ox
class NamedCityEnv(CityEnv):
def __init__(self, *, location="Chamberi, Madrid", **kwargs):
self.location = location
super().__init__(**kwargs, G=load_city_graph(location))
def load_city_graph(location='Chamberi, Madrid', **kwargs):
G = ox.graph.graph_from_place(location, **kwargs)
G = ox.add_edge_speeds(G)
G = ox.add_edge_travel_times(G)
largest = sorted(nx.strongly_connected_components(G), key=lambda x: len(x))[-1]
G = G.subgraph(largest)
return G
if __name__ == "__main__":
env = CityEnv()
for i in range(100):
env.step()

View File

@@ -0,0 +1,26 @@
import solara
@solara.component
def MainPage(clicks):
color = "green"
if clicks.value >= 5:
color = "red"
def increment():
clicks.value += 1
print("clicks", clicks) # noqa
solara.Button(label=f"Clicked: {clicks}", on_click=increment, color=color)
@solara.component
def Page():
v = Visualization()
v.viz()
class Visualization:
def __init__(self):
self.clicks = solara.reactive(0)
def viz(self):
from sol_lib import MainPage
return MainPage(self.clicks)

View File

@@ -0,0 +1,13 @@
import solara
@solara.component
def MainPage(clicks):
color = "green"
if clicks.value >= 5:
color = "red"
def increment():
clicks.value += 1
print("clicks", clicks) # noqa
solara.Button(label=f"Clicked: {clicks}", on_click=increment, color=color)