%matplotlib inline
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
pynetlogo
Overview
This document reproduces the examples of the pynetlogo
Python package, which provides an interface to control NetLogo from Python. These examples are available in the package documentation.
Installation
Install pynetlogo
and other dependencies in a virtual environment:
python -m venv .venv
source .venv/bin/activate
pip install pynetlogo
pip install ipyparallel
pip install multiprocessing
pip install nbclient
pip install nbformat
pip install openpyxl
pip install pyyaml
pip install SALib
pip install session_info
pip install sobol
You will also need to ensure that the path to Java Virtual Machine (JVM) is properly configured in the JAVA_HOME
environment variable. You can set this variable in your shell configuration file (e.g., .bashrc
):
For Arch Linux, use:
export JAVA_HOME="/usr/lib/jvm/default"
Example 1
See the this example at: https://pynetlogo.readthedocs.io/en/latest/_docs/introduction.html
"white")
sns.set_style("talk") sns.set_context(
import pynetlogo
= pynetlogo.NetLogoLink(
netlogo = True,
gui = False,
thd = "/opt/netlogo-7-0-0"
netlogo_home )
WARNING: A restricted method in java.lang.System has been called
WARNING: java.lang.System::load has been called by org.jpype.JPypeContext in an unnamed module (file:/home/danielvartan/Git/pynetlogo/.venv/lib/python3.13/site-packages/org.jpype.jar)
WARNING: Use --enable-native-access=ALL-UNNAMED to avoid a warning for callers in this module
WARNING: Restricted methods will be blocked in a future release unless native access is enabled
WARNING: A terminally deprecated method in sun.misc.Unsafe has been called
WARNING: sun.misc.Unsafe::objectFieldOffset has been called by scala.runtime.LazyVals$ (file:/opt/netlogo-7-0-0/lib/app/scala3-library_3-3.7.0.jar)
WARNING: Please consider reporting this to the maintainers of class scala.runtime.LazyVals$
WARNING: sun.misc.Unsafe::objectFieldOffset will be removed in a future release
SLF4J(W): No SLF4J providers were found.
SLF4J(W): Defaulting to no-operation (NOP) logger implementation
SLF4J(W): See https://www.slf4j.org/codes.html#noProviders for further details.
Sep 27, 2025 12:22:06 AM com.sun.javafx.application.PlatformImpl startup
WARNING: Unsupported JavaFX configuration: classes were loaded from 'unnamed module @5d5d9e5'
"./nlogox/Wolf Sheep Predation_v6.nlogox")
netlogo.load_model("setup") netlogo.command(
= pd.read_excel("./data/xy_DataFrame.xlsx")
agent_xy "who", "xcor", "ycor"]].head(5) agent_xy[[
who | xcor | ycor | |
---|---|---|---|
0 | 0 | -24.000000 | -24.000000 |
1 | 1 | -23.666667 | -23.666667 |
2 | 2 | -23.333333 | -23.333333 |
3 | 3 | -23.000000 | -23.000000 |
4 | 4 | -22.666667 | -22.666667 |
"who", "xcor", "ycor"]], "a-sheep") netlogo.write_NetLogo_attriblist(agent_xy[[
= netlogo.report("map [s -> [xcor] of s] sort sheep")
x = netlogo.report("map [s -> [ycor] of s] sort sheep") y
= plt.subplots(1)
fig, ax
=4)
ax.scatter(x, y, s"xcor")
ax.set_xlabel("ycor")
ax.set_ylabel("equal")
ax.set_aspect(5, 5)
fig.set_size_inches(
plt.show()
# We can use either of the following commands to run for 100 ticks:
"repeat 100 [go]")
netlogo.command(# netlogo.repeat_command('go', 100)
# Return sorted arrays so that the x, y and energy properties of each agent are in the same order
= netlogo.report("map [s -> [xcor] of s] sort sheep")
x = netlogo.report("map [s -> [ycor] of s] sort sheep")
y = netlogo.report("map [s -> [energy] of s] sort sheep")
energy_sheep
= netlogo.report("[energy] of wolves") # NetLogo returns these in random order energy_wolves
from mpl_toolkits.axes_grid1 import make_axes_locatable
= plt.subplots(1, 2)
fig, ax
= ax[0].scatter(x, y, s=50, c=energy_sheep, cmap=plt.cm.coolwarm)
sc 0].set_xlabel("xcor")
ax[0].set_ylabel("ycor")
ax[0].set_aspect("equal")
ax[= make_axes_locatable(ax[0])
divider = divider.append_axes("right", size="5%", pad=0.1)
cax = plt.colorbar(sc, cax=cax, orientation="vertical")
cbar "Energy of sheep")
cbar.set_label(
=False, bins=10, ax=ax[1], label="Sheep")
sns.histplot(energy_sheep, kde=False, bins=10, ax=ax[1], label="Wolves")
sns.histplot(energy_wolves, kde1].set_xlabel("Energy")
ax[1].set_ylabel("Counts")
ax[1].legend()
ax[14, 5)
fig.set_size_inches(
plt.show()
= netlogo.repeat_report(["count wolves", "count sheep"], 200, go="go") counts
= pd.DataFrame(counts) counts
= plt.subplots(1, 2)
fig, (ax1, ax2)
=ax1, use_index=True, legend=True)
counts.plot(ax"Ticks")
ax1.set_xlabel("Counts")
ax1.set_ylabel(
"count wolves"], counts["count sheep"])
ax2.plot(counts["Wolves")
ax2.set_xlabel("Sheep")
ax2.set_ylabel(
for ax in [ax1, ax2]:
1 / ax.get_data_ratio())
ax.set_aspect(
12, 5)
fig.set_size_inches(
plt.tight_layout() plt.show()
= netlogo.repeat_report(
results
["[energy] of wolves",
"[energy] of sheep",
"[sheep_str] of sheep",
"count sheep",
"glob_str",
],5,
)
= plt.subplots(1)
fig, ax
"[energy] of wolves"][-1], kde=False, bins=20, ax=ax)
sns.histplot(results["Energy")
ax.set_xlabel("Counts")
ax.set_ylabel(4, 4)
fig.set_size_inches(
plt.show()
list(results.keys())
['[energy] of wolves',
'[energy] of sheep',
'[sheep_str] of sheep',
'count sheep',
'glob_str']
= netlogo.patch_report("countdown")
countdown_df
= plt.subplots(1)
fig, ax
= sns.heatmap(
patches =5, yticklabels=5, cbar_kws={"label": "countdown"}, ax=ax
countdown_df, xticklabels
)"pxcor")
ax.set_xlabel("pycor")
ax.set_ylabel("equal")
ax.set_aspect(8, 4)
fig.set_size_inches(
plt.show()
"countdown.xlsx")
countdown_df.to_excel("countdown", countdown_df.max() - countdown_df) netlogo.patch_set(
= netlogo.patch_report("countdown")
countdown_update_df
= plt.subplots(1)
fig, ax
= sns.heatmap(
patches
countdown_update_df,=5,
xticklabels=5,
yticklabels={"label": "countdown"},
cbar_kws=ax,
ax
)"pxcor")
ax.set_xlabel("pycor")
ax.set_ylabel("equal")
ax.set_aspect(8, 4)
fig.set_size_inches(
plt.show()
netlogo.kill_workspace()
Example 2
See the this example at: https://pynetlogo.readthedocs.io/en/latest/_docs/SALib_ipyparallel.html
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
"white")
sns.set_style("talk")
sns.set_context(
import pynetlogo
# Import the sampling and analysis modules for a Sobol variance-based
# sensitivity analysis
from SALib.sample import sobol as sobolsample
from SALib.analyze import sobol
= {
problem "num_vars": 6,
"names": [
"random-seed",
"grass-regrowth-time",
"sheep-gain-from-food",
"wolf-gain-from-food",
"sheep-reproduce",
"wolf-reproduce",
],"bounds": [
1, 100000],
[20.0, 40.0],
[2.0, 8.0],
[16.0, 32.0],
[2.0, 8.0],
[2.0, 8.0],
[
], }
= 2 ** 5 # Changed
n = sobolsample.sample(problem, n, calc_second_order=True) param_values
param_values.shape
(448, 6)
Running the Experiments in Parallel Using ipyparallel
import ipyparallel as ipp
= ipp.Cluster(n=4)
cluster ; cluster.start_cluster_sync()
Starting 4 engines with <class 'ipyparallel.cluster.launcher.LocalEngineSetLauncher'>
= cluster.connect_client_sync()
rc =4) # Added
rc.wait_for_engines(n rc.ids
0%| | 0/4 [00:00<?, ?engine/s] 25%|██▌ | 1/4 [00:05<00:16, 5.40s/engine]100%|██████████| 4/4 [00:05<00:00, 1.35s/engine]
[0, 1, 2, 3]
= rc[:] direct_view
import os
# Push the current working directory of the notebook to a "cwd" variable on the engines that can be accessed later
dict(cwd=os.getcwd()), block=True) direct_view.push(
[None, None, None, None]
# Push the "problem" variable from the notebook to a corresponding variable on the engines
dict(problem=problem), block=True) direct_view.push(
[None, None, None, None]
%%px
import os
os.chdir(cwd)
import pynetlogo
import numpy as np
import pandas as pd
= pynetlogo.NetLogoLink(
netlogo = False,
gui = False,
thd = "/opt/netlogo-7-0-0"
netlogo_home
)
"./nlogox/Wolf Sheep Predation_v6.nlogox") netlogo.load_model(
[stderr:0] WARNING: A restricted method in java.lang.System has been called
WARNING: java.lang.System::load has been called by org.jpype.JPypeContext in an unnamed module (file:/home/danielvartan/Git/pynetlogo/.venv/lib/python3.13/site-packages/org.jpype.jar)
WARNING: Use --enable-native-access=ALL-UNNAMED to avoid a warning for callers in this module
WARNING: Restricted methods will be blocked in a future release unless native access is enabled
WARNING: A terminally deprecated method in sun.misc.Unsafe has been called
WARNING: sun.misc.Unsafe::objectFieldOffset has been called by scala.runtime.LazyVals$ (file:/opt/netlogo-7-0-0/lib/app/scala3-library_3-3.7.0.jar)
WARNING: Please consider reporting this to the maintainers of class scala.runtime.LazyVals$
WARNING: sun.misc.Unsafe::objectFieldOffset will be removed in a future release
[stderr:1] WARNING: A restricted method in java.lang.System has been called
WARNING: java.lang.System::load has been called by org.jpype.JPypeContext in an unnamed module (file:/home/danielvartan/Git/pynetlogo/.venv/lib/python3.13/site-packages/org.jpype.jar)
WARNING: Use --enable-native-access=ALL-UNNAMED to avoid a warning for callers in this module
WARNING: Restricted methods will be blocked in a future release unless native access is enabled
WARNING: A terminally deprecated method in sun.misc.Unsafe has been called
WARNING: sun.misc.Unsafe::objectFieldOffset has been called by scala.runtime.LazyVals$ (file:/opt/netlogo-7-0-0/lib/app/scala3-library_3-3.7.0.jar)
WARNING: Please consider reporting this to the maintainers of class scala.runtime.LazyVals$
WARNING: sun.misc.Unsafe::objectFieldOffset will be removed in a future release
[stderr:3] WARNING: A restricted method in java.lang.System has been called
WARNING: java.lang.System::load has been called by org.jpype.JPypeContext in an unnamed module (file:/home/danielvartan/Git/pynetlogo/.venv/lib/python3.13/site-packages/org.jpype.jar)
WARNING: Use --enable-native-access=ALL-UNNAMED to avoid a warning for callers in this module
WARNING: Restricted methods will be blocked in a future release unless native access is enabled
WARNING: A terminally deprecated method in sun.misc.Unsafe has been called
WARNING: sun.misc.Unsafe::objectFieldOffset has been called by scala.runtime.LazyVals$ (file:/opt/netlogo-7-0-0/lib/app/scala3-library_3-3.7.0.jar)
WARNING: Please consider reporting this to the maintainers of class scala.runtime.LazyVals$
WARNING: sun.misc.Unsafe::objectFieldOffset will be removed in a future release
[stderr:2] WARNING: A restricted method in java.lang.System has been called
WARNING: java.lang.System::load has been called by org.jpype.JPypeContext in an unnamed module (file:/home/danielvartan/Git/pynetlogo/.venv/lib/python3.13/site-packages/org.jpype.jar)
WARNING: Use --enable-native-access=ALL-UNNAMED to avoid a warning for callers in this module
WARNING: Restricted methods will be blocked in a future release unless native access is enabled
WARNING: A terminally deprecated method in sun.misc.Unsafe has been called
WARNING: sun.misc.Unsafe::objectFieldOffset has been called by scala.runtime.LazyVals$ (file:/opt/netlogo-7-0-0/lib/app/scala3-library_3-3.7.0.jar)
WARNING: Please consider reporting this to the maintainers of class scala.runtime.LazyVals$
WARNING: sun.misc.Unsafe::objectFieldOffset will be removed in a future release
%px: 0%| | 0/4 [00:00<?, ?tasks/s]%px: 0%| | 0/4 [00:00<?, ?tasks/s]%px: 0%| | 0/4 [00:00<?, ?tasks/s]%px: 0%| | 0/4 [00:00<?, ?tasks/s]%px: 0%| | 0/4 [00:00<?, ?tasks/s]%px: 0%| | 0/4 [00:00<?, ?tasks/s]%px: 0%| | 0/4 [00:00<?, ?tasks/s]%px: 0%| | 0/4 [00:00<?, ?tasks/s]%px: 0%| | 0/4 [00:00<?, ?tasks/s]%px: 0%| | 0/4 [00:00<?, ?tasks/s]%px: 0%| | 0/4 [00:01<?, ?tasks/s]%px: 0%| | 0/4 [00:01<?, ?tasks/s]%px: 0%| | 0/4 [00:01<?, ?tasks/s]%px: 0%| | 0/4 [00:01<?, ?tasks/s]%px: 0%| | 0/4 [00:01<?, ?tasks/s]%px: 0%| | 0/4 [00:01<?, ?tasks/s]%px: 0%| | 0/4 [00:01<?, ?tasks/s]%px: 0%| | 0/4 [00:01<?, ?tasks/s]%px: 0%| | 0/4 [00:01<?, ?tasks/s]%px: 0%| | 0/4 [00:01<?, ?tasks/s]%px: 0%| | 0/4 [00:02<?, ?tasks/s]%px: 0%| | 0/4 [00:02<?, ?tasks/s]%px: 0%| | 0/4 [00:02<?, ?tasks/s]%px: 0%| | 0/4 [00:02<?, ?tasks/s]%px: 0%| | 0/4 [00:02<?, ?tasks/s]%px: 0%| | 0/4 [00:02<?, ?tasks/s]%px: 0%| | 0/4 [00:02<?, ?tasks/s]%px: 0%| | 0/4 [00:02<?, ?tasks/s]%px: 0%| | 0/4 [00:02<?, ?tasks/s]%px: 0%| | 0/4 [00:02<?, ?tasks/s]%px: 0%| | 0/4 [00:03<?, ?tasks/s]%px: 0%| | 0/4 [00:03<?, ?tasks/s]%px: 0%| | 0/4 [00:03<?, ?tasks/s]%px: 0%| | 0/4 [00:03<?, ?tasks/s]%px: 0%| | 0/4 [00:03<?, ?tasks/s]%px: 0%| | 0/4 [00:03<?, ?tasks/s]%px: 0%| | 0/4 [00:03<?, ?tasks/s]%px: 0%| | 0/4 [00:03<?, ?tasks/s]%px: 50%|█████ | 2/4 [00:03<00:00, 19.53tasks/s]%px: 75%|███████▌ | 3/4 [00:04<00:00, 8.64tasks/s]%px: 100%|██████████| 4/4 [00:04<00:00, 1.04s/tasks]
def simulation(experiment):
# Set the input parameters
for i, name in enumerate(problem["names"]):
if name == "random-seed":
# The NetLogo random seed requires a different syntax
"random-seed {}".format(experiment[i]))
netlogo.command(else:
# Otherwise, assume the input parameters are global variables
"set {0} {1}".format(name, experiment[i]))
netlogo.command(
"setup")
netlogo.command(# Run for 100 ticks and return the number of sheep and wolf agents at each time step
= netlogo.repeat_report(["count sheep", "count wolves"], 100)
counts
= pd.Series( # Added
results "count sheep"]), np.mean(counts["count wolves"])],
[np.mean(counts[=["Avg. sheep", "Avg. wolves"],
index
)
# Original code:
#
# results = pd.Series(
# [counts["count sheep"].values.mean(), counts["count wolves"].values.mean()],
# index=["Avg. sheep", "Avg. wolves"],
# )
return results
= rc.load_balanced_view() lview
= pd.DataFrame(lview.map_sync(simulation, param_values)) results
"./data/Sobol_parallel.csv") results.to_csv(
5) results.head(
Avg. sheep | Avg. wolves | |
---|---|---|
0 | 101.594059 | 28.465347 |
1 | 100.504950 | 33.247525 |
2 | 85.574257 | 20.376238 |
3 | 179.871287 | 108.752475 |
4 | 104.237624 | 33.504950 |
Using SALib
for Sensitivity Analysis
= plt.subplots(1, len(results.columns), sharey=True)
fig, ax
for i, n in enumerate(results.columns):
20)
ax[i].hist(results[n],
ax[i].set_xlabel(n)0].set_ylabel("Counts")
ax[
10, 4)
fig.set_size_inches(=0.1)
fig.subplots_adjust(wspace
plt.show()
import scipy
= 2
nrow = 3
ncol
= plt.subplots(nrow, ncol, sharey=True)
fig, ax
= results["Avg. sheep"]
y
for i, a in enumerate(ax.flatten()):
= param_values[:, i]
x
sns.regplot(=x,
x=y,
y=a,
ax=None,
ci="k",
color={"alpha": 0.2, "s": 4, "color": "gray"},
scatter_kws
)= scipy.stats.pearsonr(x, y)
pearson
a.annotate("r: {:6.3f}".format(pearson[0]),
=(0.15, 0.85),
xy="axes fraction",
xycoords=13,
fontsize
)if divmod(i, ncol)[1] > 0:
False)
a.get_yaxis().set_visible("names"][i])
a.set_xlabel(problem[0, 1.1 * np.max(y)])
a.set_ylim([
9, 9, forward=True)
fig.set_size_inches(=0.2, hspace=0.3)
fig.subplots_adjust(wspace
plt.show()
= sobol.analyze(
Si
problem,"Avg. sheep"].values,
results[=True,
calc_second_order=False,
print_to_console )
/home/danielvartan/Git/pynetlogo/.venv/lib/python3.13/site-packages/SALib/util/__init__.py:274: FutureWarning: unique with argument that is not not a Series, Index, ExtensionArray, or np.ndarray is deprecated and will raise in a future version.
names = list(pd.unique(groups))
= {k: Si[k] for k in ["ST", "ST_conf", "S1", "S1_conf"]}
Si_filter = pd.DataFrame(Si_filter, index=problem["names"]) Si_df
Si_df
ST | ST_conf | S1 | S1_conf | |
---|---|---|---|---|
random-seed | 0.056705 | 0.039673 | 0.036567 | 0.092495 |
grass-regrowth-time | 0.246658 | 0.215558 | 0.162614 | 0.188970 |
sheep-gain-from-food | 0.779248 | 0.565153 | 0.252965 | 0.371308 |
wolf-gain-from-food | 0.539663 | 0.301630 | 0.384790 | 0.279285 |
sheep-reproduce | 0.147828 | 0.099676 | -0.049013 | 0.253315 |
wolf-reproduce | 0.402822 | 0.229209 | 0.266902 | 0.352499 |
= plt.subplots(1)
fig, ax
= Si_df[["S1", "ST"]]
indices = Si_df[["S1_conf", "ST_conf"]]
err
=err.values.T, ax=ax)
indices.plot.bar(yerr8, 4)
fig.set_size_inches(
plt.show()
%matplotlib inline
import itertools
from math import pi
from matplotlib.legend_handler import HandlerPatch
def normalize(x, xmin, xmax):
return (x - xmin) / (xmax - xmin)
def plot_circles(ax, locs, names, max_s, stats, smax, smin, fc, ec, lw, zorder):
= np.asarray([stats[name] for name in names])
s = 0.01 + max_s * np.sqrt(normalize(s, smin, smax))
s
= True
fill for loc, name, si in zip(locs, names, s):
if fc == "w":
= False
fill else:
= "none"
ec
= np.cos(loc)
x = np.sin(loc)
y
= plt.Circle(
circle
(x, y),=si,
radius=ec,
ec=fc,
fc=ax.transData._b,
transform=zorder,
zorder=lw,
lw=True,
fill
) ax.add_artist(circle)
def filter(sobol_indices, names, locs, criterion, threshold):
if criterion in ["ST", "S1", "S2"]:
= sobol_indices[criterion]
data = np.abs(data)
data = data.flatten() # flatten in case of S2
data # TODO:: remove nans
= [(name, locs[i]) for i, name in enumerate(names) if data[i] > threshold]
filtered = zip(*filtered)
filtered_names, filtered_locs elif criterion in ["ST_conf", "S1_conf", "S2_conf"]:
raise NotImplementedError
else:
raise ValueError("unknown value for criterion")
return filtered_names, filtered_locs
def plot_sobol_indices(sobol_indices, criterion="ST", threshold=0.01):
"""plot sobol indices on a radial plot
Parameters
----------
sobol_indices : dict
the return from SAlib
criterion : {'ST', 'S1', 'S2', 'ST_conf', 'S1_conf', 'S2_conf'}, optional
threshold : float
only visualize variables with criterion larger than cutoff
"""
= 15 # 25*1.8
max_linewidth_s2 = 0.3
max_s_radius
# prepare data
# use the absolute values of all the indices
# sobol_indices = {key:np.abs(stats) for key, stats in sobol_indices.items()}
# dataframe with ST and S1
= {key: sobol_indices[key] for key in ["ST", "S1"]}
sobol_stats = pd.DataFrame(sobol_stats, index=problem["names"])
sobol_stats
= sobol_stats.max().max()
smax = sobol_stats.min().min()
smin
# dataframe with s2
= pd.DataFrame(sobol_indices["S2"], index=problem["names"], columns=problem["names"])
s2 < 0.0] = 0.0 # Set negative values to 0 (artifact from small sample sizes)
s2[s2 = s2.max().max()
s2max = s2.min().min()
s2min
= problem["names"]
names = len(names)
n = np.linspace(0, 2 * pi, n + 1)
ticklocs = ticklocs[0:-1]
locs
= filter(sobol_indices, names, locs, criterion, threshold)
filtered_names, filtered_locs
# setup figure
= plt.figure()
fig = fig.add_subplot(111, polar=True)
ax False)
ax.grid("polar"].set_visible(False)
ax.spines[
ax.set_xticks(locs)
ax.set_xticklabels(names)
ax.set_yticklabels([])=1.4)
ax.set_ylim(top
legend(ax)
# plot ST
plot_circles(
ax,
filtered_locs,
filtered_names,
max_s_radius,"ST"],
sobol_stats[
smax,
smin,"w",
"k",
1,
9,
)
# plot S1
plot_circles(
ax,
filtered_locs,
filtered_names,
max_s_radius,"S1"],
sobol_stats[
smax,
smin,"k",
"k",
1,
10,
)
# plot S2
for name1, name2 in itertools.combinations(zip(filtered_names, filtered_locs), 2):
= name1
name1, loc1 = name2
name2, loc2
= s2.loc[name1, name2]
weight = 0.5 + max_linewidth_s2 * normalize(weight, s2min, s2max)
lw 1, 1], c="darkgray", lw=lw, zorder=1)
ax.plot([loc1, loc2], [
return fig
class HandlerCircle(HandlerPatch):
def create_artists(
self, legend, orig_handle, xdescent, ydescent, width, height, fontsize, trans
):= 0.5 * width - 0.5 * xdescent, 0.5 * height - 0.5 * ydescent
center = plt.Circle(xy=center, radius=orig_handle.radius)
p self.update_prop(p, orig_handle, legend)
p.set_transform(trans)return [p]
def legend(ax):
= [
some_identifiers 0, 0), radius=5, color="k", fill=False, lw=1),
plt.Circle((0, 0), radius=5, color="k", fill=True),
plt.Circle((0, 0.5], [0, 0.5], lw=8, color="darkgray"),
plt.Line2D([
]
ax.legend(
some_identifiers,"ST", "S1", "S2"],
[=(1, 0.75),
loc=0.1,
borderaxespad="expand",
mode={plt.Circle: HandlerCircle()},
handler_map )
Example 3
See the this example at: https://pynetlogo.readthedocs.io/en/latest/_docs/SALib_multiprocessing.html
Running the Experiments in Parallel Using a Process Pool
from multiprocessing import Pool
import os
import pandas as pd
import numpy as np
import pynetlogo
from SALib.sample import sobol as sobolsample
def initializer(modelfile):
"""initialize a subprocess
Parameters
----------
modelfile : str
"""
# we need to set the instantiated netlogo
# link as a global so run_simulation can
# use it
global netlogo
= pynetlogo.NetLogoLink(
netlogo = False,
gui = False,
thd = "/opt/netlogo-7-0-0"
netlogo_home
)
netlogo.load_model(modelfile)
def run_simulation(experiment):
"""run a netlogo model
Parameters
----------
experiments : dict
"""
# Set the input parameters
for key, value in experiment.items():
if key == "random-seed":
# The NetLogo random seed requires a different syntax
"random-seed {}".format(value))
netlogo.command(else:
# Otherwise, assume the input parameters are global variables
"set {0} {1}".format(key, value))
netlogo.command(
"setup")
netlogo.command(# Run for 100 ticks and return the number of sheep and
# wolf agents at each time step
= netlogo.repeat_report(["count sheep", "count wolves"], 100)
counts
= pd.Series( # Added
results "count sheep"]), np.mean(counts["count wolves"])],
[np.mean(counts[=["Avg. sheep", "Avg. wolves"],
index
)
# Original code:
#
# results = pd.Series(
# [counts["count sheep"].values.mean(), counts["count wolves"].values.mean()],
# index=["Avg. sheep", "Avg. wolves"],
# )
return results
if __name__ == "__main__":
= os.path.abspath("./nlogox/Wolf Sheep Predation_v6.nlogox")
modelfile
= {
problem "num_vars": 6,
"names": [
"random-seed",
"grass-regrowth-time",
"sheep-gain-from-food",
"wolf-gain-from-food",
"sheep-reproduce",
"wolf-reproduce",
],"bounds": [[1, 100000], [20.0, 40.0], [2.0, 8.0], [16.0, 32.0], [2.0, 8.0], [2.0, 8.0]],
}
= 2 ** 1 # Changed
n = sobolsample.sample(problem, n, calc_second_order=True)
param_values
# cast the param_values to a dataframe to
# include the column labels
= pd.DataFrame(param_values, columns=problem["names"])
experiments
with Pool(4, initializer=initializer, initargs=(modelfile,)) as executor:
= []
results for entry in executor.map(run_simulation, experiments.to_dict("records")):
results.append(entry)= pd.DataFrame(results) results
5) results.head(
Avg. sheep | Avg. wolves | |
---|---|---|
0 | 180.455446 | 62.386139 |
1 | 208.336634 | 50.861386 |
2 | 208.237624 | 59.564356 |
3 | 167.475248 | 40.495050 |
4 | 132.247525 | 81.089109 |
Session Info
import session_info
= True, jupyter = True, dependencies = True) session_info.show(cpu
Click to view session information
----- SALib NA ipyparallel 9.0.1 matplotlib 3.10.6 mpl_toolkits NA numpy 2.3.3 pandas 2.3.2 pynetlogo 0.5.2 scipy 1.16.2 seaborn 0.13.2 session_info v1.0.1 -----
Click to view modules imported as dependencies
PIL 11.3.0 asttokens NA comm 0.2.3 cycler 0.12.1 cython_runtime NA dateutil 2.9.0.post0 debugpy 1.8.17 decorator 5.2.1 dill 0.4.0 et_xmlfile 2.0.0 executing 2.2.1 ipykernel 6.30.1 jedi 0.19.2 jpype 1.6.0 kiwisolver 1.4.9 matplotlib_inline 0.1.7 multiprocess 0.70.18 netLogoLink NA openpyxl 3.1.5 packaging 25.0 parso 0.8.5 platformdirs 4.4.0 prompt_toolkit 3.0.52 psutil 7.1.0 pure_eval 0.2.3 pydev_ipython NA pydevconsole NA pydevd 3.2.3 pydevd_file_utils NA pydevd_plugins NA pydevd_tracing NA pygments 2.19.2 pyparsing 3.2.5 pytz 2025.2 six 1.17.0 stack_data 0.6.3 tornado 6.5.2 tqdm 4.67.1 traitlets 5.14.3 wcwidth 0.2.14 zmq 27.1.0
----- IPython 9.5.0 jupyter_client 8.6.3 jupyter_core 5.8.1 ----- Python 3.13.7 (main, Aug 15 2025, 12:34:02) [GCC 15.2.1 20250813] Linux-6.16.8-arch1-1-x86_64-with-glibc2.42 12 logical CPU cores ----- Session information updated at 2025-09-27 00:23
License
This content is licensed under CC0 1.0 Universal, placing these materials in the public domain. You may freely copy, modify, distribute, and use this work, even for commercial purposes, without permission or attribution.