10  Getting started with NetLogo

10.0.1 Test-drive: creating a “pond” terrain

Now, let us make a minimum working example of all elements explained above while advancing towards the Pond Trade model prototype. Let us start with implementing a minimum base for the PondTrade model: the pond-like terrain.

For this section, I recommend starting your own NetLogo file (placed at the root directory of this repository) and writing the following steps progressively. Feel free to copy and paste whenever the code fragments become too long. If you run into any problems at the end of each step, you may check the corresponding complete script at the repository’s root.

10.0.1.1 Step 0: Drawing a blue circle

Inside a single procedure, called create-map, we order (ask) all patches to do something using the structure:

Next, we use the syntax for setting variables (set <VARIABLE> <VALUE>) inside the procedure to change patches color to blue:

Notice that both pcolor and blue are ‘primitives’, so they are automatically understood by NetLogo. Remember to consult NetLogo’s Dictionary, if in doubt about such primitives. Color primitives such as blue or their numeric equivalent are shown in ‘Color Swatches’ inside the Tools menu tab.

Since we don’t want all patches to have the same color, we need to build some structure that give different colors to patches depending on their position, so that a blue circle is draw.

Considering how to draw a circle, we need two bits of information: a center and a radius (respectively, O and R in the figure).

Circle-withsegments

First, we must define a center. Because we won’t use this information for anything else, we can declare and set a local variable (i.e., accessable only from its own context), using the syntax let <VARIABLE> <VALUE>. We define the center patch coordinates as (0,0):

We can set a radius for our circle using a single numeric value, e.g. 5, expressed in patch widths:

However, if we are passing a single absolute numeric value to be compared to a distance, the result will be sensitive to how large is our world grid. This is not wise, given that we might want to use this procedure in grids of different dimensions. Imagine that you are interested in changing the dimensions of the grid but want the circle to cover a similar proportion of it (e.g., adjusting the resolution of our map).

We can easily circunvent this code fragility by stepping-up its complexity. One alternative solution is to work with proportions of one of the grid dimensions, e.g. width (world-width). For the time being, we will set it to be half of half (a quater) of the width, since we have the circle positioned in the middle of the grid.

Running this code will still not produce a blue circle. Rather, we are asking for all patches to paint themselve blue, no matter what the values of centralPatch or minDistOfLandToCenter.

To differenciate patches to be painted blue, we now use minDistOfLandToCenter for evaluating a criterium for a ifelse structure, finding out if a patch is inside or outside our circle. With this, we are ordering patches to paint themselves green or blue depending on if their distance to the center is less than a given value, i.e. minDistOfLandToCenter.

Now, the entire code for the create-map procedure is finally doing what we expected, drawing a blue circle over a green background:

Pond Trade step 0
Pond Trade step 0

10.0.1.2 Step 1a: De-composing “magic numbers”

The code we just created has several fixed arbitrary values (the coordinates of the centralPatch, the values used to calculate minDistOfLandToCenter). It is good enough for us to draw a particular blue circle, but it is insufficient to draw other types of blue circle. Of course, the code will never be able to draw anything, if we are not programming it to do it. For instance, the colors blue and green are also magic numbers, but we are hardly interest in having them as parameters. We must generalize but also compromise, accepting that there will be possibilities that are not covered by our model.

First, is there any case where the patch 0 0 is not the one at the center of the grid? Imagine that you don’t like to have negative coordinates in your model. Go to “Settings” and modify the “location of origin” to be at the corner. Now, test the create-map procedure:

Not at all what we are looking for! To correct this behavior, we must calculate the center coordinates, depending on the ranges or sizes of the grid width and height, whatever its configuration. Therefore, we must replace 0 with the calculation minimum + range / 2 for both x and y coordinates:

We use the floor function to obtain the round lower grid position when the range is an odd number. Because this calculation uses only NetLogo primitives, you can test this by printing it in the console in any NetLogo model. It will return the central patch given your grid settings:

Now, regarding minDistOfLandToCenter, we could simply bring it as a parameter to be set in the interface instead of hard-coded (e.g. as a slider). This would be a better design, but we still would have a potential problem. Maybe you do not want the grid to be squared (e.g., 33x33), but rectangular (e.g., 100x20) instead. This is what you would get:

Again, not what we are looking for. No matter how strange our map is shaped, we want our pond to be always fully within the grid. To do this, we must modify our calculation of minDistOfLandToCenter to account for both width and height. One way of doing it is to calculate the radius for both dimensions and then choose the lowest value.

To test our solution, we can run the procedure with different extreme grid settings:

100x20 20x100

Success!

10.0.1.3 Step 1b: Parameterizing

Once you defined a procedure in raw terms and tested that it does what you expect, you probably want to generalize it further. As the aim of modeling is to represent a type of phenomenon, it is a good practice to program all non-dynamic conditions as parameters. In NetLogo, parameters are often those variables that can be set through user input in the interface tab (e.g., slides, selectors, numeric fields).

After the later changes, we still have two magic numbers in our code. Regarding the calculation of centralPatch and minDistOfLandToCenter, we used 2 to divide world-width and world-height, so that the circle is always draw in the center of the grid. Although it is possible, we will not replace this value with a parameter. As an exercise aside, you can test the outcome of having different numbers instead of 2.

The other “magic number” is 0.5, used to represent the relative size of the pond radius, i.e., the half of the half of the smaller dimension. Here we have a good candidate for a parameter. It is reasonable to believe that the size of the pond will be relevant to our model’s results. Mainly, we expect that larger ponds will make trade slower, assuming a fixed number of settlements, evenly distributed around the pond.

In NetLogo, we create a parameter by adding an input element to the interface tab (e.g., slider) and naming it. In this case, we create a parameter called pondSize that represents the pond radius as the percentage of the smallest dimension, i.e. varing between 0 and 100. We can use it in the code to replace the two instances of 0.5 with (pondSize / 100):

Note that we use percentage, instead of a proportion (from 0 to 1), for no other reason than because it can improve the inteligibility of our model. I recommend using percentages to format this kind of parameters because they are more intuitive for humans and will be more easily understood by colleagues and the general public with no background in computer science or mathematics.

Once you close a version of any piece of code, it is good practice to increase the spacing between the lines or even break down single lines that are particularly complicated. NetLogo language allows much flexibility in this sense: you can add spaces, tabs, line breaks, and commentary between most elements of your code. Also, enclosing parenthesis are not required but may improve readability.

10.0.1.4 Step 1c: Optimasing

We increased significantly the create-map procedure, but we now have a process that is both flexible and controllable by user input. Yet, we still have a pending issue to solve.

The fact that the centralPatch and minDistOfLandToCenter are local variables (let) and placed inside ask patches [ <ACTIONS> ] means that we are creating and destroying a different variable once for every patch. We cannot use these variables (plural intended) outside their enclosing brackets and patches hold no memory of their values before or after this particular action. Does anything feel wrong about this?

Besides taking unnecessary computational resources, this design does not generete any errors and, for now, is quite inofensive. However, while it can be easily solved in a short piece of code, it might become harder to find and have odd repercussions over other parts of our model later on.

The solution is to extract the declaration of the local variables, centralPatch and minDistOfLandToCenter, from the patches commands. They are now calculated only once at the start of the procedure and then used by every patch:

Step 1 interface in NetLogo
Pond Trade step 1

10.0.1.5 Step 2a: Pacing comments and line breaks

Since our code for now is rather simple, we can downgrade the extensive commentary and line-breaking on the calculation of centralPatch.

Commenting and spacing your code is generally a good practice, but do not get carried away! You must assume that your reader has to know something about the programming language and the context of the model. Sometimes comments and line breaks might be just too many and end up defeating the purpuse of improving the code readabiity.

At every step in this tutorial, we will be downgrading most commentaries added in the previous step.

10.0.1.6 Step 2b: Exploring alternative designs

During refactoring, we should always keep in mind that there are always alternative designs that could generate the outcome we seek, some of which might be more readable or optimal.

In this case, we can simplify the calculation of minDistOfLandToCenter. This new version initializes a local variable halfSmallerDimension assuming the smaller dimension is the width. Then, it checks that this is the case, and re-write this value if height is actually smaller. Finally, we calculate minDistOfLandToCenter as a proportion of halfSmallerDimension.

This version is less redundant, uses two instead of three local variables, and expresses more clearly that the condition is the comparison between the grid width and height.

10.0.1.7 Step 2c: Colors and shades

Last, we replace the text reference for colors with NetLogo’s numerical codes. Using this numeric system allow us to use many shades of any given color. In this case, we are selecting slightly different shades of blue (106) and green (54). You can consult the color codes in “Tools” > “Color Swatches” or in http://ccl.northwestern.edu/netlogo/docs/programming.html#colors:

Step 2 interface in NetLogo
Pond Trade step 2

Great, now we can draw a blue circle with a bulletproof piece of NetLogo code. Now let us go a few steps faster and aim at a more interesting outcome.

10.0.1.8 Step 3: Adding noise (stochasticity)

Stochasticity is intrinsic in NetLogo. We were already dealing with random processes since step 0, when asking patches to paint themselves. You probably did not realize, but the command ask patches demands that patches are ordered somehow. Think as if you were told to ask all your friends to have a look at your new model. Well, but who exactly are you going to ask first? NetLogo solves this dilemma automaticaly by randomizing the order of “asking”. As an exercise, you can reduce the velocity of simulation (top of the interface tab), and execute the create-map procedure. You will observe each patch changing color, one at a time. This is also a nice example of using stochasticity to assure that an aggregated outcome (i.e. blue circle) is not a mere artefact of any particular schedulle of processes (i.e. the order in which patches change colors). Remember, sequences will be different every time we run our simulation, unless we preset the RNG using a specific ‘seed’ (see http://ccl.northwestern.edu/netlogo/docs/dict/random-seed.html).

But is it so important to have a random order? In our script so far, it is completely irrelevant. Our goal was to draw a blue circle; it does not matter which patch assumes the role of land or water first. However, this will became increasingly relevant as we advance in creating a proper agent-based model, because agents’ states DO normally depend on other agents’ states. Following the scenario where you want to show your model to your friends, imagine that your friends would talk to each other after one of them have seen your model and that some of them are more talkative and some more respected. Can you assume that the order in which you present your model would have no effect on the level of publicity and prestige of your model? ‘I don’t think so’, ‘Who knows?!’, ‘I don’t really care about it’? If these thoughts cross your mind while addressing a process in your model, you are probably better off using stochasticity.

A perfect blue circle is great but it is a poor representation of a real water body. Once we implement the dynamics of the model, it will be quite difficult to explore the effect of geography solely by varying the size of the pond. The first step to creating more interesting set-ups is to add noise to the condition used to determine whether a patch is land or water. NetLogo has a family of primitive functions, random and alike, that can be used to generate random discrete (integer) and continuos (float) values, following different probability distributions (e.g., uniform, normal, exponential).

For each patch, we sample a random continuous number, add it to the minDistOfLandToCenter, and use as the threshold distance from the center:

The function random-float <number> returns a random “float” number greater or equal to 0.0 and lower than <number>. To strech your learning skills, we are jumping a few minor steps in refactoring by defining a noise that is a portion of halfSmallerDimension and controlable through the parameter coastalNoiseLevel, exposed in the interface.

We now can generate strange “spray ponds”. More importantly, we made the generation process controllable through two parameters that are easily understandable. Play with the parameters and meditate on their effects on the shape of the pond.

Pond Trade step 3
Pond Trade step 3

10.0.1.9 Step 4b: design alternatives

Because a “spray pond” is not exactly what we want, let us implement another alternative to add stochasticity. NetLogo offers the random-normal primitive, which samples a random “float-point number” given the mean and standard deviation of a normal distribution, using the structure random-normal <MEAN> <STD.DEV> (https://ccl.northwestern.edu/netlogo/docs/dictionary.html#random-reporters). In this context, we can re-use the paramater coastalNoiseLevel for the latter and consider minDistOfLandToCenter as the mean.

Comment-out the line with random-float and add the following:

We can now experiment running create-map in three different ways: without noise, with uniform-distributed noise, and with normal-distributed noise. We can run each by commenting-out the line corresponding to the other two. For example, to come back to the no noise option:

no noise
uniform
normal

But at this point, can we decide which option we want going forward?

10.0.1.10 Step 4c: keeping design alternatives

As mentioned, stochasticity may be the only honest way of defining values that are unknown or undefined in our conceptual model, but still need specification in the model implementation. Another useful, still honest approach is to consolidate alternative solutions and expose the criterium for deciding among them as a special type of parameter. Often, these will be Booleans (true or false), which can be set with “switchers” in NetLogo interface, or String (declarative text, e.g. "option A", "option B", "option C"), which can be selected through “choosers” or dropdown menus.

Create a chooser by clicking at “Add” and then “chooser” in the NetLogo Interface tab. Give it the name noiseType and write down a name for each alternative as String values:

The configuration of a “chooser” for noiseType
The configuration of a “chooser” for noiseType

Then, we use this new parameter in if structures containing the code specific for each alternative:

With this, we can now run create-map with any of the alternative designs without having to comment-out/in the code every time.

10.0.1.11 Step 4d: patch neighborhood

Despite having all the alternative modes of stochasticity, our ponds are still not quite what we need. This kind of terrain might be valid to represent something like a swamp, but not a distinctive water body. There is no coastline, which makes PondTrade more interesting.

One generic, but very useful technique in distributed computation is “smoothing”. More precisily, smoothing refers to the approximation of each point in a variable to the average of a sample of points, often taken from within the neighbourhood of the point.

In a two-dimensional grid such as in NetLogo, we can use the values in the immediate patch neighbourhood of each patch to adjust its value. In our present case, we want to switch the color of a patch, depending on the color of the adjacent patches. We can antecipate, however, that we will need to expose another paramater, one that can express how many neighbors (out of eight) of a type are enough to activate the change. We will call it coastLineSmoothThreshold. Notice that this step moves data in the opposite direction of our last steps, reducing stochasticity. Yet, it does not bringing us back to the original circle. Therefore, we want to smooth after adding noise.

Remembering our mandate of modularity, we want to avoid adding the code for this operation directly in create-map. Rather, we can implement it in a cleaner way by enclosing it in a new procedure:

We can then used it by calling smooth-coast-line after create-map, either through the console or, better, by adding a button in the interface. Testing it for each noiseType:

no noise
uniform
normal

Notice that there is no effect visible on the no noise option, but we significantly modify the outcome of the others.

10.0.1.12 Step 4e: iterative structures

Try pressing the new smooth-coast-line button more than once after create-map. The coastline will become smoother and smoother, until it becomes stable. This behaviour indicates that we have a good opportunity for implementing and iterative structure. NetLogo has a easy option for this using the follow structure: repeat <NUMBER OF ITERATIONS> [ <COMMANDS> ], requiring that we expose another parameter specifying the number of iterations, which we will call smoothIterations.

10.0.1.13 Step 4f: printing event messages

Since we are expanding considerably the complexity of our code, this is a good moment to consider printing messages for us to know what is happening in the background after pressing the buttons in the interface. This will be valuable specially when the code is encountering an error or outputing an unexpected result (i.e. debugging).

We just consolidated a version of our script that is able to create interesting (and more realistic) waterbody shapes with stochasticity, while being able to vary the output with a set of five parameters clearly displayed and no grave magic number unexposed.

Pond Trade step 4
Pond Trade step 4

10.0.1.14 Step 5: refactoring (again)

We reached our initial objective, to have a realistic procedurally-generated terrain upon which we can build PondTrade. As before, we should now stop ourselves and think about refactoring, again.

Here is a summary of the improvements we can make:

  • Control the RNG seed and expose it in the interface
  • Move the initialisation of coastThreshold and noiseRange before ask patches
  • Declare a new patch Boolean variable called isLand and use it to replace color in the procedures for creating terrains
  • Define a new procedure paint-patches dedicated only to set patch colors based on isLand
  • Call smooth-coast-line and paint-patches at the end of create-map
  • In the evaluation of coastLineSmoothThreshold used in smooth-coast-line, consider it in relation to the actual number of neighbors instead of as an absolute number (to avoid having isolated water bodies adjacent to the world edges, where there are less than 8 neighbors)
  • Rearrange the interface elements to set apart the parameters we will be using for terrain generation

Pond Trade step 5
Pond Trade step 5