18  Programming agent AI

18.1 Modelling behaviour

We now implement the behaviour rules, i.e. the Artificial Intelligence, for each agent type, particularly traders. Artificial Intelligence (AI) is the simulation of human intelligence in machines that are programmed to think and act like humans (or anything we consider intelligent). In the context of ABM, AI refers to the rules and decision-making processes that govern the behaviour of individual agents within the model.

For our model, based on the conceptual model, we will implement simple behaviour for two agent types:

  • settlements:
    • `produce goods according to their size and a productivity term.
    • create or destroy traders according to their current size.
  • traders:
    • take some goods from their base and deliver them to another settlement,
    • which is chosen based on the size of other settlements and the distance between these and the base settlement.
    • Traders will then return with goods from that settlement back to the base and restart the cycle.

Implementing this will take several development steps (up to step 9).

18.2 Implementing pathfinding 🤔

Given the importance and potential complexity of these behaviours, let us start by implementing the most fundamental aspect: the traders’ criterion for choosing a destination settlement. We have mentioned “distance”, yet how should traders measure such distance in a heterogeneous terrain?

In the original repository of the Pond Trade model, you will notice an initial attempt to approach this aspect through a simple network implementation, using NetLogo’s links (PondTrade_step07_agent AI v1_links.nlogo). This approach is similar to other ABM and network-based models applied to topics of trade and settlement interactions.

However, as you may know, there can be a significant difference between a straight-line measurement (Euclidean distance) and a more complex path cost calculation that uses terrain data. As the Pond Trade model eventually did, we will “complicate” our design and make a proper “least-cost route” calculation, using this as a perfect excuse for learning.

But how should we implement a least-cost path algorithm in NetLogo? We follow our modular philosophy and look for an implementation of the A* (A-star) algorithm in NetLogo User Community Models.

We get the main fragment of the code from this and adapt it to our purposes, making sure we keep a clear reference to the source as a commentary header:

Note: Try to walk through this code and understand it, but don’t panic if you don’t!

Once the algorithm is clear, we need to assign path costs to patches. How should we do that?

Before looking at the solution, try to write the code yourself:

  • Each patch has a pathCost variable (already defined)
  • Land patches have a high pathCost (arbitrary,e.g. 1000)
  • Water patches have a low pathCost (the baseline, i.e. 1)

A few questions to guide your solution: - When and where should this be done? - Do we need to do it every time we run the search? - What value should be a parameter (slider) in the interface?

Solution:

Because our model assigns different path costs for each patch (i.e., land or water), we must create a new procedure that assigns these costs and add a call to it just after the other steps in create-map:

We are then forced to introduce another parameter, relativePathCostInLand, which specifies how much movement on land costs relative to movement in water. Add a slider in the interface (from 0 to 100, in steps of 0.01, default value 50).

EXTRA CHALLENGE: further differentiate the path cost in settlements, i.e. cost to “port”.

Tip: follow the same rationale from above, except consider how to distinguish a patch as a “port” and what could be the more efficient way of assigning their path cost.

Solution:

Given the special circumstance of traders arriving and leaving settlements, let us introduce a differential value for pathCost in patches with settlements. For this, we should modify create-coastal-settlements and introduce the parameter relativePathCostInPort (from 0 to 100, in increments of 0.01, default value 10):

Notice that relativePathCostInPort will generally not affect A* results since routes will always have two patches with this pathCost. However, it does matter because routes will more likely avoid going through a third settlement if a path through water is available.

18.3 Testing the A* path-finding

You can now experiment with the A* algorithm by running setupas it is, and then find-a-path between two patches of your choosing.

Follow these steps: - Uncomment the lines involving set pcolor ... in the find-a-path procedure to visualise the search process - Reduce the simulation speed to see the search process - Run setup (which now sets pathCost) - In the Command Center, run:

Try various coordinates to see how the search process works with different contexts.

Notice how the large difference in path cost between land and water (x50) makes A* avoid drawing a route through land, practically until all adjacent water patches are explored. This will make routes go around peninsulas, for example.

18.4 Finding all routes

Now that we have confirmed the code we just introduced, let us implement everything we will need to call A* and keep track of all routes between settlements. Given that A* takes time to compute, we should make everything possible to avoid repeating the calculation of routes.

Our plan is to implement a new procedure, set-routes, that will:

  • be called during setup after creating the settlements
  • ask each settlement to find the best routes to every other settlement
  • store the path in a new global variable routes

This involves:

  1. creating a new global variable routes
  2. creating a new procedure set-routes
  3. calling set-routes in setup
  4. asking each settlement to find the best routes to every other settlement
  5. storing the path in a new global variable routes

Running the code as-is generates the maximum number of routes given the settlements present (e.g., if numberOfSettlements = 10, it will be 45, or 9 + 8 + 7 + 6 + 5 + 4 + 3 + 2 + 1). We store each route as a list of patches, and all routes as a list of routes (routes). These will be globally accessible by all traders in our model.

Notice that calculating all possible routes takes considerable time, and the number of settlements increases this time exponentially. At this point, we should know how to stop NetLogo while it’s running a long command. Go to Tools > Halt. This might not work if the NetLogo interface is unresponsive. Then the only options are to wait, quit, or force-quit NetLogo.

18.5 Decision making 🤔

Now that we’ve solved the route calculations, we must use them to inform traders’ decisions. How would you do that?

Before looking at the solution, try to write the code yourself (this is a hard one!):

  • Create a new procedure choose-destination for the trader breed
  • The procedure should:
    • calculate an “attractiveness” score for each settlement (excluding the base settlement)
    • choose the destination settlement with the best attractiveness score
  • The attractiveness score should consider:
    • the size of the settlement (bigger is more attractive)
    • the distance (total path cost) from the base settlement (closer/less costly is more attractive)

Solution:

Let us create a procedure to be called by traders named choose-destination, for which we will need to implement a few “helper” procedures:

Notice that get-route is only helpful for debugging and querying the information in routes.

To inform traders’ decisions, we define a simple calculation of a route’s potential gain (the sum of the sizeLevel of the origin and destination) and calculate a benefit-to-cost ratio for each route. Traders will then compare these ratios for all possible routes from their base settlement and choose the one with the highest value.

18.6 Movement 🤔

Once a destination is decided, traders must move towards it and then, once there, start the return trip. Are you up to this challenge?

Before looking at the solution, try to write the code yourself (this is VERY hard!): - Create a new procedure move-to-destination for the trader breed - The procedure should: - move the trader along the selected route towards the destination settlement (or back) - check if the trader is at the destination or base settlement and choose the right direction - check if in a patch centre (round coordinates) to move to the next patch in the route - move progressively towards the next patch in the route (not teleporting) - speed should be dependent on the pathCost of the patch-here

Things to consider: - How to keep track of the direction of movement? - How to keep track of the progress of movement? - How to check if the trader is at the destination or base settlement? - How to check if the trader is in a patch centre? - How to move progressively from one patch centre to another? - How to make the speed dependent on the pathCost of the patch-here?

Solution:

We implement this behaviour assuming movement must go through each patch centre in the route, and that traders move at a maximum speed of 1 / [pathCost] of patch-here, meaning they are delayed in proportion to the local value of pathCost.

We wrap up this development step by finally implementing our preliminary model cycle, ordering the behaviour of traders within the conventional go procedure:

As specified in our conceptual model, traders choose a destination whenever they are at their base and move toward their current destination.

18.7 Enhancing visualisation

To help visualise routes, implement the following version of update-display, call it at the end of setup, and add two switches, showRoutes and showActiveRoutes, to the interface:

18.8 Testing agent behaviour

As a temporary “hack”, replace the code create-traders-per-settlement with the following:

Reduce numberOfSettlements (e.g. 10) and run setup again. Run go repeatedly to observe the behaviour of the loner trader.

18.9 Checking the milestone File (step 7)

Pond Trade step 7
Pond Trade step 7