22  Identifying and formatting submodels

In the early stages of model development, it is common to work within a single .nlogo file. This approach keeps all code—setup, agent logic, data collection, and plotting—in one place, which is convenient for learning and experimentation. However, as models grow in complexity, maintaining a long monolithic file becomes difficult. To make large projects easier to navigate, debug, and extend, NetLogo allows modularisation through the __includes directive.

22.1 Why Modularise?

Breaking a model into smaller source files improves:

  • Readability: Each file focuses on a specific function (e.g., setup, environment, agent behaviour, data collection).
  • Reusability: Procedures can be shared across projects.
  • Maintenance: Updating or replacing a subsystem (e.g., weather generation) doesn’t require scrolling through hundreds of lines.
  • Collaboration: Multiple researchers can work on separate components concurrently.

This has major benefits for transparent and replicable research. Instead of one opaque model file, a modular structure makes explicit where, for example, environmental data, agent rules, or calibration routines are defined—facilitating peer review, teaching, and long-term model reuse.

22.2 The __includes Directive

NetLogo allows a model to load additional source files (.nls, for NetLogo Source) at runtime. The syntax, placed at the top of your main .nlogo file, is:

NOTE: Storing these files in a subdirectory, like ‘modules’, is not a requirement, but it will be preferable if the model directory contains anything besides a single .nlogo file (e.g., input or output data files, documentation, etc).

Each included file may contain any NetLogo code (procedures, reporters, variable declarations). They could even correspond to a single element each (e.g., “setup.nls” containing only the setup procedure). The criteria for splitting those elements and naming the files are ultimately for you to decide. Aim for a reasonable amount of code per file, but this isn’t a hard rule; sometimes grouping or separating elements makes more sense. Using a combination of functions (e.g., “setup”, “data collection”), objects (e.g., “patches”, a given breed of agent), and topics (e.g., vegetation growth, movement, communication) can make good intermediate solutions. Remember why you’re going modular, and think in terms of what helps you pursue those goals.

When the model (.nlogo file) loads, all included .nls files are merged into a single shared namespace, so procedures defined in one file are visible to all others. You can also consult and modify the source code directly in NetLogo; once loaded, it appears in the drop-down list at the top of the “Code” tab.

22.3 Breaking Down The Pond Trade Model

To break down the Pond Trade model developed within one .nlogo file, we can refactor it into modules stored in a structured directory, for example:

PondTrade/
├── PondTrade.nlogo              ; main file (interface + globals + includes)
└── modules/
    ├── main.nls                 ; strictly, setup and go procedures
    ├── map.nls                  ; Map and terrain generation
    ├── output.nls               ; Plotting, displays, outputs, and computing model statistics
    ├── routes.nls               ; Route calculation, pathfinding, and connectivity logic.
    ├── settlements.nls          ; Creating and managing settlements
    └── traders.nls              ; trader creation, activation, movement, and trading logic

Inside the main model:

Each .nls file then contains code that is more closely related, even though they are inevitably connected to others. For example, map.nls stores create-map and smooth-coast-line, but while smooth-coast-line is only called by create-map, create-map in turn is called by setup.

This structure makes it easier to focus on specific model components, understand or modify them, without being overwhelmed by the entire codebase.

22.3.1 Shared Utilities

All included files share the same namespace. Therefore, you can store common utility functions in a single shared file and reference it in the main __includes list of different models or .nlogo files.

For example, we can store commonProcedure.nls in a utils subdirectory inside a larger directory containing subdirectories for two models:

my models/
├──PondTrade/
|   ├── PondTrade.nlogo
|   └── modules/
└──OtherModel/
|   ├── PondTrade.nlogo
|   └── modules/
└──utils/
    └──commonProcedure.nls

And then use it in

22.4 Refactoring for better modularity

Once we split our code into logical parts, we might gain useful insights into how to better respect modularity without changing the code behaviour. We have one such case in map.nls.

Here are the call paths involving the two procedures in this file:

We can see that assign-path-cost has an ambiguous position: it is clearly related to route calculation, but it’s called in map, inside create-map, not in another procedure in routes or directly in setup.

to create-map

  ...
  
  smooth-coast-line

  assign-path-cost

  ask patches [ paint-terrain ]

end

This makes our code “spaghetti code”. The refactoring that solves this is straightforward: move the call to assign-path-cost from create-map to setup. Since it was already at the end of create-map, moving it to immediately after the call to create-map in setup will not change the model behaviour. The cost is simply a slightly longer setup procedure.

to setup

  clear-all
  reset-ticks

  ; set the random seed so we can reproduce the same experiment
  random-seed seed
  
  set patchesCount count patches

  create-map
  
  assign-path-cost

  create-coastal-settlements

  set-routes

  create-traders-per-settlement

  update-output

  update-display

  update-plots

end

Refactoring never finishes. Do you see any other improvements to our model implementation so far? If so, please try them and if successful, create a pull request with your changes to our course-guide repository.


22.5 🧠 Activity

Refactor another model using __includes. The Artificial Anasazi model is a good example, but feel free to use another model.

Here is a short summary of the steps you should follow:

22.5.1 🧩 Step 1 — Create the Folder Structure

  1. Make a new directory.
  2. Inside it, create a modules subdirectory.
  3. Copy your original model .nlogo file.
newModelDirectory/
├── modelCopy.nlogo
└── modules/

22.5.2 ⚙️ Step 2 — Add the __includes Statement

At the very top of modelCopy.nlogo, add:

This tells NetLogo to merge these modules into one program when the model loads.

22.5.3 ✂️ Step 3 — Split the Code

Now open modelCopy.nlogo and move procedures one at a time into new .nls files in modules/ (create these as TXT files, then change the file extension; opening the entire folder in an IDE makes this easier). With the .nlogo file always open, save and check for errors each time you separate a module.

Here are a few examples of modules:

Type of Procedure Move it to file Example
World setup and initialization setup.nls to setup
Agent behaviour (movement, decisions) turtles.nls to go, to forage, to trade
Environment updates environment.nls to update-water, to grow-resources
Trade and interaction logic trade.nls to exchange-goods, to evaluate-partner
Data collection and plotting data.nls to record-stats, to export-csv
Helper and math functions utils.nls to-report clamp01 [x], to-report random-between [a b]

Remember — you do not need to re-declare globals or interface variables in every file. All modules share the same namespace once included.

To go fully modular, the final version of the new .nlogo file should contain only the __includes directive and all breed and variable declarations.

22.5.4 🧪 Step 4 — Test the Modular Model

  1. Check that it runs exactly like the original version.
  2. If an error appears such as “Nothing named CLAMP01”, ensure that the file containing that procedure (utils.nls) is listed in the __includes.