LogoLink: An Interface for Running NetLogo Simulations from R 🐢

Hi everyone!

I’m excited to share my new NetLogo project: logolink.

logolink is an R package that simplifies setting up and running NetLogo simulations from R . It provides a modern, intuitive interface that follows tidyverse principles and integrates seamlessly with the tidyverse ecosystem.

The package is available on CRAN and GitHub.

How it Works?

logolink usage is very straightforward. The main functions are:

You can install the released version of logolink from CRAN with:

install.packages("logolink")

Along with the package, you will also need NetLogo 7.0.1 or higher installed on your computer. You can download it from the NetLogo website.

The package is feature-complete for BehaviorSpace functionality. The create_experiment() function supports even subexperiments, and run_experiment() supports all four BehaviorSpace output formats: Table, Spreadsheet, Lists, and Statistics.

With logolink you can easily perform simulation analyses and capture screenshots of the NetLogo world during simulation runs, all without ever leaving R.

results |> glimpse()
#> List of 2
#>  $ metadata:List of 6
#>   ..$ timestamp       : POSIXct[1:1], format: "2026-01-08 05:11:42"
#>   ..$ netlogo_version : chr "7.0.3"
#>   ..$ output_version  : chr "2.0"
#>   ..$ model_file      : chr "Wolf Sheep Simple 5.nlogox"
#>   ..$ experiment_name : chr "Wolf Sheep Simple Model Analysis"
#>   ..$ world_dimensions: Named int [1:4] -17 17 -17 17
#>   .. ..- attr(*, "names")= chr [1:4] "min-pxcor" "max-pxcor" "min-pycor" "max-pycor"
#>  $ table   : tibble [110,110 × 10] (S3: tbl_df/tbl/data.frame)
#>   ..$ run_number            : num [1:110110] 1 1 1 1 1 1 1 1 1 1 ...
#>   ..$ number_of_sheep       : num [1:110110] 500 500 500 500 500 500 500 500 500 500 ...
#>   ..$ number_of_wolves      : num [1:110110] 5 5 5 5 5 5 5 5 5 5 ...
#>   ..$ movement_cost         : num [1:110110] 0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5 ...
#>   ..$ grass_regrowth_rate   : num [1:110110] 0.3 0.3 0.3 0.3 0.3 0.3 0.3 0.3 0.3 0.3 ...
#>   ..$ energy_gain_from_grass: num [1:110110] 2 2 2 2 2 2 2 2 2 2 ...
#>   ..$ energy_gain_from_sheep: num [1:110110] 5 5 5 5 5 5 5 5 5 5 ...
#>   ..$ step                  : num [1:110110] 0 1 2 3 4 5 6 7 8 9 ...
#>   ..$ count_wolves          : num [1:110110] 5 5 5 5 5 5 5 5 5 5 ...
#>   ..$ count_sheep           : num [1:110110] 500 499 499 498 495 494 492 489 488 486 ...

vignette-wolf-sheep-model-animation-1

Learn more at: An Interface for Running NetLogo Simulations • logolink

GitHub Stars are always appreciated! :star:

Cheers,

Daniel Vartanian

2 Likes

this is astounding,thank u

1 Like

Hi Daniel,

I’m using logolink to automate NetLogo simulations and it’s working great.

I have a question regarding the sub_experiments argument in create_experiment(). I am performing a Sensitivity Analysis using the sensitivity R package, which generates a data frame (let’s call it SA) where each column is a parameter and each row is a specific simulation run.

Since logolink expects a list of lists for sub_experiments, what is the most efficient way to pass my sensitivity design to the function?
Thanks in advance!

Hi, Brayan,

I’m glad you like the package! :slight_smile:

If I understand your problem correctly, a combination of group_split(), from dplyr, and map(), from purrr, should help you get the lists for your subexperiments.

Example:

library(dplyr)
library(purrr)
SA <- tibble(
  par_1 = letters[1:3], 
  par_2 = sample(1:100, 3)
)
SA
#> # A tibble: 3 × 2
#>   par_1 par_2
#>   <chr> <int>
#> 1 a        48
#> 2 b        37
#> 3 c        62
SA <-
  SA |>
  mutate(run = row_number()) |>
  relocate(run)
SA
#> # A tibble: 3 × 3
#>     run par_1 par_2
#>   <int> <chr> <int>
#> 1     1 a        48
#> 2     2 b        37
#> 3     3 c        62
SA |>
  group_split(run) |>
  map(\(x) x[-1] |> as.list())
#> [[1]]
#> [[1]]$par_1
#> [1] "a"
#> 
#> [[1]]$par_2
#> [1] 48
#> 
#> 
#> [[2]]
#> [[2]]$par_1
#> [1] "b"
#> 
#> [[2]]$par_2
#> [1] 37
#> 
#> 
#> [[3]]
#> [[3]]$par_1
#> [1] "c"
#> 
#> [[3]]$par_2
#> [1] 62
test |> str()
#> List of 3
#>  $ :List of 2
#>   ..$ par_1: chr "a"
#>   ..$ par_2: int 48
#>  $ :List of 2
#>   ..$ par_1: chr "b"
#>   ..$ par_2: int 37
#>  $ :List of 2
#>   ..$ par_1: chr "c"
#>   ..$ par_2: int 62

Please tell me if that worked. :slight_smile:

"Hi Daniel! Thanks a lot for your help. Your suggestion was exactly what I needed to implement my Morris sensitivity analysis.
The key was definitely using the sub_experiments argument with a list of lists structure. This allowed me to pass my design matrix (from the sensitivity package) where each sublist represents one specific trajectory of the Morris method, avoiding the default factorial combination of parameters.
Following your logic, I used a direct approach to convert my design dataframe (SA) into the required format:

# Each row of the design matrix becomes an independent sub-experiment
SA_design <- lapply(1:nrow(SA), function(i) {
  as.list(SA[i, ])
})

# Then I just pass it to the experiment definition
SA_experiment <- create_experiment(
  sub_experiments = SA_design,
  ...
)

Thank you so much for the support!

1 Like