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.

These have great consequences for supporting 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 is ultimately for you to decide. You should aim to have not too much nor too little code in files, but this is no golden rule and there are many instances where grouping or separating elements is more desirable. Using a combination of function (e.g., “setup”, “data collection”), object (e.g., “patches”, a given breed of agent) and topic (e.g., vegetation growth, movement, communication) can make good intermediate solutions. Remember the reasons for going modular and think in terms of what helps you pursuing them.

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 may also consult and modify the source code directly in NetLogo since, once loaded, they will appear 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                 ; stricly, 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 connect 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, common utility functions can be stored in a single shared file that can be referenced in the main __includes list of different models or .nlogo files.

For example, we can store commonProcedure.nls in a utils subdirectory insider 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 a better modularity

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

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

We can see that assign-path-cost has a somewhat ambiguous position: it is clearly related to route calculation, but it is actually 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 a “spaghetti code”. The refactoring measure that can solve this is straightforward: we move the call for assign-path-cost from create-map to setup. Since it was already positioned at the end of create-map, moving it immediately following the call for 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 is a process that never finishes. Do you see any other improvements to our implementation of the model 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 offers a good case, but feel free to try another model.

Here is 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 all these modules into one program when the model is loaded.

22.5.3 ✂️ Step 3 — Split the Code

Now open modelCopy.nlogo and move procedures, one at a time, to new .nls files in modules/ (create these as txt files, then change the file extension; opening the entire folder in an IDE will make this easier). With the .nlogo file always open, save and check for error every time a module is separated.

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.