%matplotlib inline
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as snspynetlogo
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/activatepip 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 sobolYou 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
sns.set_style("white")
sns.set_context("talk")import pynetlogo
netlogo = pynetlogo.NetLogoLink(
gui = True,
thd = False,
netlogo_home = "/opt/netlogo-7-0-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
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'
netlogo.load_model("./nlogox/Wolf Sheep Predation_v6.nlogox")
netlogo.command("setup")agent_xy = pd.read_excel("./data/xy_DataFrame.xlsx")
agent_xy[["who", "xcor", "ycor"]].head(5)| 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 |
netlogo.write_NetLogo_attriblist(agent_xy[["who", "xcor", "ycor"]], "a-sheep")x = netlogo.report("map [s -> [xcor] of s] sort sheep")
y = netlogo.report("map [s -> [ycor] of s] sort sheep")fig, ax = plt.subplots(1)
ax.scatter(x, y, s=4)
ax.set_xlabel("xcor")
ax.set_ylabel("ycor")
ax.set_aspect("equal")
fig.set_size_inches(5, 5)
plt.show()# We can use either of the following commands to run for 100 ticks:
netlogo.command("repeat 100 [go]")
# netlogo.repeat_command('go', 100)
# Return sorted arrays so that the x, y and energy properties of each agent are in the same order
x = netlogo.report("map [s -> [xcor] of s] sort sheep")
y = netlogo.report("map [s -> [ycor] of s] sort sheep")
energy_sheep = netlogo.report("map [s -> [energy] of s] sort sheep")
energy_wolves = netlogo.report("[energy] of wolves") # NetLogo returns these in random orderfrom mpl_toolkits.axes_grid1 import make_axes_locatable
fig, ax = plt.subplots(1, 2)
sc = ax[0].scatter(x, y, s=50, c=energy_sheep, cmap=plt.cm.coolwarm)
ax[0].set_xlabel("xcor")
ax[0].set_ylabel("ycor")
ax[0].set_aspect("equal")
divider = make_axes_locatable(ax[0])
cax = divider.append_axes("right", size="5%", pad=0.1)
cbar = plt.colorbar(sc, cax=cax, orientation="vertical")
cbar.set_label("Energy of sheep")
sns.histplot(energy_sheep, kde=False, bins=10, ax=ax[1], label="Sheep")
sns.histplot(energy_wolves, kde=False, bins=10, ax=ax[1], label="Wolves")
ax[1].set_xlabel("Energy")
ax[1].set_ylabel("Counts")
ax[1].legend()
fig.set_size_inches(14, 5)
plt.show()counts = netlogo.repeat_report(["count wolves", "count sheep"], 200, go="go")counts = pd.DataFrame(counts)fig, (ax1, ax2) = plt.subplots(1, 2)
counts.plot(ax=ax1, use_index=True, legend=True)
ax1.set_xlabel("Ticks")
ax1.set_ylabel("Counts")
ax2.plot(counts["count wolves"], counts["count sheep"])
ax2.set_xlabel("Wolves")
ax2.set_ylabel("Sheep")
for ax in [ax1, ax2]:
ax.set_aspect(1 / ax.get_data_ratio())
fig.set_size_inches(12, 5)
plt.tight_layout()
plt.show()results = netlogo.repeat_report(
[
"[energy] of wolves",
"[energy] of sheep",
"[sheep_str] of sheep",
"count sheep",
"glob_str",
],
5,
)
fig, ax = plt.subplots(1)
sns.histplot(results["[energy] of wolves"][-1], kde=False, bins=20, ax=ax)
ax.set_xlabel("Energy")
ax.set_ylabel("Counts")
fig.set_size_inches(4, 4)
plt.show()list(results.keys())['[energy] of wolves',
'[energy] of sheep',
'[sheep_str] of sheep',
'count sheep',
'glob_str']
countdown_df = netlogo.patch_report("countdown")
fig, ax = plt.subplots(1)
patches = sns.heatmap(
countdown_df, xticklabels=5, yticklabels=5, cbar_kws={"label": "countdown"}, ax=ax
)
ax.set_xlabel("pxcor")
ax.set_ylabel("pycor")
ax.set_aspect("equal")
fig.set_size_inches(8, 4)
plt.show()countdown_df.to_excel("countdown.xlsx")
netlogo.patch_set("countdown", countdown_df.max() - countdown_df)countdown_update_df = netlogo.patch_report("countdown")
fig, ax = plt.subplots(1)
patches = sns.heatmap(
countdown_update_df,
xticklabels=5,
yticklabels=5,
cbar_kws={"label": "countdown"},
ax=ax,
)
ax.set_xlabel("pxcor")
ax.set_ylabel("pycor")
ax.set_aspect("equal")
fig.set_size_inches(8, 4)
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
sns.set_style("white")
sns.set_context("talk")
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 sobolproblem = {
"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],
],
}n = 2 ** 5 # Changed
param_values = sobolsample.sample(problem, n, calc_second_order=True)param_values.shape(448, 6)
Running the Experiments in Parallel Using ipyparallel
import ipyparallel as ipp
cluster = ipp.Cluster(n=4)
cluster.start_cluster_sync();Starting 4 engines with <class 'ipyparallel.cluster.launcher.LocalEngineSetLauncher'>
rc = cluster.connect_client_sync()
rc.wait_for_engines(n=4) # Added
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]
direct_view = rc[:]import os
# Push the current working directory of the notebook to a "cwd" variable on the engines that can be accessed later
direct_view.push(dict(cwd=os.getcwd()), block=True)[None, None, None, None]
# Push the "problem" variable from the notebook to a corresponding variable on the engines
direct_view.push(dict(problem=problem), block=True)[None, None, None, None]
%%px
import os
os.chdir(cwd)
import pynetlogo
import numpy as np
import pandas as pd
netlogo = pynetlogo.NetLogoLink(
gui = False,
thd = False,
netlogo_home = "/opt/netlogo-7-0-0"
)
netlogo.load_model("./nlogox/Wolf Sheep Predation_v6.nlogox")[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
netlogo.command("random-seed {}".format(experiment[i]))
else:
# Otherwise, assume the input parameters are global variables
netlogo.command("set {0} {1}".format(name, experiment[i]))
netlogo.command("setup")
# Run for 100 ticks and return the number of sheep and wolf agents at each time step
counts = netlogo.repeat_report(["count sheep", "count wolves"], 100)
results = pd.Series( # Added
[np.mean(counts["count sheep"]), np.mean(counts["count wolves"])],
index=["Avg. sheep", "Avg. wolves"],
)
# Original code:
#
# results = pd.Series(
# [counts["count sheep"].values.mean(), counts["count wolves"].values.mean()],
# index=["Avg. sheep", "Avg. wolves"],
# )
return resultslview = rc.load_balanced_view()results = pd.DataFrame(lview.map_sync(simulation, param_values))results.to_csv("./data/Sobol_parallel.csv")results.head(5)| 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
fig, ax = plt.subplots(1, len(results.columns), sharey=True)
for i, n in enumerate(results.columns):
ax[i].hist(results[n], 20)
ax[i].set_xlabel(n)
ax[0].set_ylabel("Counts")
fig.set_size_inches(10, 4)
fig.subplots_adjust(wspace=0.1)
plt.show()import scipy
nrow = 2
ncol = 3
fig, ax = plt.subplots(nrow, ncol, sharey=True)
y = results["Avg. sheep"]
for i, a in enumerate(ax.flatten()):
x = param_values[:, i]
sns.regplot(
x=x,
y=y,
ax=a,
ci=None,
color="k",
scatter_kws={"alpha": 0.2, "s": 4, "color": "gray"},
)
pearson = scipy.stats.pearsonr(x, y)
a.annotate(
"r: {:6.3f}".format(pearson[0]),
xy=(0.15, 0.85),
xycoords="axes fraction",
fontsize=13,
)
if divmod(i, ncol)[1] > 0:
a.get_yaxis().set_visible(False)
a.set_xlabel(problem["names"][i])
a.set_ylim([0, 1.1 * np.max(y)])
fig.set_size_inches(9, 9, forward=True)
fig.subplots_adjust(wspace=0.2, hspace=0.3)
plt.show()Si = sobol.analyze(
problem,
results["Avg. sheep"].values,
calc_second_order=True,
print_to_console=False,
)/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))
Si_filter = {k: Si[k] for k in ["ST", "ST_conf", "S1", "S1_conf"]}
Si_df = pd.DataFrame(Si_filter, index=problem["names"])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 |
fig, ax = plt.subplots(1)
indices = Si_df[["S1", "ST"]]
err = Si_df[["S1_conf", "ST_conf"]]
indices.plot.bar(yerr=err.values.T, ax=ax)
fig.set_size_inches(8, 4)
plt.show()%matplotlib inline
import itertools
from math import pi
from matplotlib.legend_handler import HandlerPatchdef normalize(x, xmin, xmax):
return (x - xmin) / (xmax - xmin)def plot_circles(ax, locs, names, max_s, stats, smax, smin, fc, ec, lw, zorder):
s = np.asarray([stats[name] for name in names])
s = 0.01 + max_s * np.sqrt(normalize(s, smin, smax))
fill = True
for loc, name, si in zip(locs, names, s):
if fc == "w":
fill = False
else:
ec = "none"
x = np.cos(loc)
y = np.sin(loc)
circle = plt.Circle(
(x, y),
radius=si,
ec=ec,
fc=fc,
transform=ax.transData._b,
zorder=zorder,
lw=lw,
fill=True,
)
ax.add_artist(circle)def filter(sobol_indices, names, locs, criterion, threshold):
if criterion in ["ST", "S1", "S2"]:
data = sobol_indices[criterion]
data = np.abs(data)
data = data.flatten() # flatten in case of S2
# TODO:: remove nans
filtered = [(name, locs[i]) for i, name in enumerate(names) if data[i] > threshold]
filtered_names, filtered_locs = zip(*filtered)
elif criterion in ["ST_conf", "S1_conf", "S2_conf"]:
raise NotImplementedError
else:
raise ValueError("unknown value for criterion")
return filtered_names, filtered_locsdef 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
"""
max_linewidth_s2 = 15 # 25*1.8
max_s_radius = 0.3
# 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
sobol_stats = {key: sobol_indices[key] for key in ["ST", "S1"]}
sobol_stats = pd.DataFrame(sobol_stats, index=problem["names"])
smax = sobol_stats.max().max()
smin = sobol_stats.min().min()
# dataframe with s2
s2 = pd.DataFrame(sobol_indices["S2"], index=problem["names"], columns=problem["names"])
s2[s2 < 0.0] = 0.0 # Set negative values to 0 (artifact from small sample sizes)
s2max = s2.max().max()
s2min = s2.min().min()
names = problem["names"]
n = len(names)
ticklocs = np.linspace(0, 2 * pi, n + 1)
locs = ticklocs[0:-1]
filtered_names, filtered_locs = filter(sobol_indices, names, locs, criterion, threshold)
# setup figure
fig = plt.figure()
ax = fig.add_subplot(111, polar=True)
ax.grid(False)
ax.spines["polar"].set_visible(False)
ax.set_xticks(locs)
ax.set_xticklabels(names)
ax.set_yticklabels([])
ax.set_ylim(top=1.4)
legend(ax)
# plot ST
plot_circles(
ax,
filtered_locs,
filtered_names,
max_s_radius,
sobol_stats["ST"],
smax,
smin,
"w",
"k",
1,
9,
)
# plot S1
plot_circles(
ax,
filtered_locs,
filtered_names,
max_s_radius,
sobol_stats["S1"],
smax,
smin,
"k",
"k",
1,
10,
)
# plot S2
for name1, name2 in itertools.combinations(zip(filtered_names, filtered_locs), 2):
name1, loc1 = name1
name2, loc2 = name2
weight = s2.loc[name1, name2]
lw = 0.5 + max_linewidth_s2 * normalize(weight, s2min, s2max)
ax.plot([loc1, loc2], [1, 1], c="darkgray", lw=lw, zorder=1)
return figclass HandlerCircle(HandlerPatch):
def create_artists(
self, legend, orig_handle, xdescent, ydescent, width, height, fontsize, trans
):
center = 0.5 * width - 0.5 * xdescent, 0.5 * height - 0.5 * ydescent
p = plt.Circle(xy=center, radius=orig_handle.radius)
self.update_prop(p, orig_handle, legend)
p.set_transform(trans)
return [p]def legend(ax):
some_identifiers = [
plt.Circle((0, 0), radius=5, color="k", fill=False, lw=1),
plt.Circle((0, 0), radius=5, color="k", fill=True),
plt.Line2D([0, 0.5], [0, 0.5], lw=8, color="darkgray"),
]
ax.legend(
some_identifiers,
["ST", "S1", "S2"],
loc=(1, 0.75),
borderaxespad=0.1,
mode="expand",
handler_map={plt.Circle: HandlerCircle()},
)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 sobolsampledef 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
netlogo = pynetlogo.NetLogoLink(
gui = False,
thd = False,
netlogo_home = "/opt/netlogo-7-0-0"
)
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
netlogo.command("random-seed {}".format(value))
else:
# Otherwise, assume the input parameters are global variables
netlogo.command("set {0} {1}".format(key, value))
netlogo.command("setup")
# Run for 100 ticks and return the number of sheep and
# wolf agents at each time step
counts = netlogo.repeat_report(["count sheep", "count wolves"], 100)
results = pd.Series( # Added
[np.mean(counts["count sheep"]), np.mean(counts["count wolves"])],
index=["Avg. sheep", "Avg. wolves"],
)
# Original code:
#
# results = pd.Series(
# [counts["count sheep"].values.mean(), counts["count wolves"].values.mean()],
# index=["Avg. sheep", "Avg. wolves"],
# )
return resultsif __name__ == "__main__":
modelfile = os.path.abspath("./nlogox/Wolf Sheep Predation_v6.nlogox")
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]],
}
n = 2 ** 1 # Changed
param_values = sobolsample.sample(problem, n, calc_second_order=True)
# cast the param_values to a dataframe to
# include the column labels
experiments = pd.DataFrame(param_values, columns=problem["names"])
with Pool(4, initializer=initializer, initargs=(modelfile,)) as executor:
results = []
for entry in executor.map(run_simulation, experiments.to_dict("records")):
results.append(entry)
results = pd.DataFrame(results)results.head(5)| 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
session_info.show(cpu = True, jupyter = True, dependencies = True)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.









