Agent based models are very diverse in architecture, behavior types, number of agents, space, and so on. There is no special or standard language for agent based modeling. Agents in an agent based model may represent very diverse things: vehicles, units of equipment, projects, products, ideas, organizations, pieces of land, people in different roles.
Agent based models are very diverse in architecture, behavior types, number of agents, space, and so on. There is no special or standard language for agent based modeling. Agents in an agent based model may represent very diverse things: vehicles, units of equipment, projects, products, ideas, organizations, pieces of land, people in different roles.
The Big Book of Simulation Modeling Featuring AnyLogic 1
The AnyLogic Company www.anylogic.com
Agent based modeling. Technology overview In this chapter, we will present an overview of the technologies and techniques used in agent based modeling. It is important to understand that there is no special or standard language for agent based modeling. Agent based models are very diverse in architecture, behavior types, number of agents, space, and so on. Other modeling methods (discrete event and system dynamics) are often used inside and outside agents. There are, however, design patterns that are common to many agent based models, which we will consider: Object-based architecture Time model: asynchronous or synchronous (steps or clock ticks) Space (continuous, discrete, geographical) and mobility Networks and links between agents Communication between agents, and between agents and environment Dynamic creation and destruction of agents Statistics collection on agent populations Before we dive into the technical stuff, we will discuss what kind of real world objects the agents actually represent. Who are the agents? Agents in an agent based model may represent very diverse things: vehicles, units of equipment, projects, products, ideas, organizations, investments, pieces of land, people in different roles, etc.
Agents may be People in different roles: consumers, citizens, employees, patients, doctors, clients, soldiers,
Non-material things: projects, products, innovations, ideas, investments Organizations: companies, political parties, countries, The Big Book of Simulation Modeling Featuring AnyLogic 2 The AnyLogic Company www.anylogic.com Given a particular problem we are solving with the help of agent based modeling, how do we choose who the agents are? This may not be as trivial as it seems. Consider the following example. Who are the agents in an American automotive market model? You are going to model the American automotive market (for example, to forecast its reaction to a certain move by one of the players) and you have decided to use agent based modeling. In automotive market, people buy and sell cars. So, the first thing that comes to mind is that agents are people because they make decisions.
Agents are people What about cars? Cars, at first glance, are passive objects that have different parameters and may vary in appeal to different people. But cars do have some dynamics because with age they lose appeal and value. So, should we model cars as agents or just leave them as plain objects, or entities, being handled by agents/people?
Agents are people and cars What if a person shares a car with a spouse? Who makes the decision then? Should we model collaboration between family members? ? ? The Big Book of Simulation Modeling Featuring AnyLogic 3 The AnyLogic Company www.anylogic.com
A car shared by two family members. Collaborative decision making The situation gets even more complicated if we consider families with children, families with multiple cars shared by multiple people, and so on.
Multiple cars shared by multiple people There is no universal answer to the question. Depending on the details of the problem, different model architectures may make sense. In the Vehicle Market Model created by Mark Paich (Decisio Consulting and Lexidyne), the agents were not individual people, but households. Households had garage slots with cars, and cars were not agents, they were objects with properties like year, make, and model. Decisions about selling or buying a car were made at the household level, taking into account the number of family members, their ages, income, and other factors.
Agents are households ? ? The Big Book of Simulation Modeling Featuring AnyLogic 4 The AnyLogic Company www.anylogic.com Agent based modeling and object-oriented design Agent based simulation modeling and object-oriented software design have a lot in common. When software engineers think about how to choose a set of classes, where to draw the line between interface and implementation, and which set of constructors a class should have, they are doing a type of agent based, or individual based, modeling for the problem that the software is addressing. We can extend this analogy to the procedural programming paradigm that historically preceded the object-oriented paradigm. In procedural programming, the software developer was thinking of the program as a (possibly hierarchical) sequence of steps that should be taken to reach a goal. Similarly, in discrete event (process-centric) modeling, the modeler represents the system as a (possibly hierarchical) sequence of operations that are performed over entities. However, unlike procedural programming, which was almost totally replaced by object-oriented programming, discrete event modeling successfully coexists with agent based modeling. Lets also discuss classes and objects. Class is, in a way, a design-time term; it is a description. Class describes a set of similar objects, but these objects (called instances of the class) will not be created until the program is run. Objects exist at runtime. So, speaking rigorously, agents exist only in a running agent based model. When you are developing the model, you only construct agent classes. Agents (objects) of the same class have common structure and behavior, but may differ in details, such as parameter values and state information (memory) that includes variable values, statechart and event states, etc. For example, two agents of the class Patient have the same set of parameters and the same behavior pattern, but may differ in age, location, disease state, or contact lists. Interface of the agent (object) is the set of things other agents and external parts of the model can see and use to interact with the agent. These can be variables (in OO terminology these would be public fields), functions (public methods), ports, or messages. As opposed to interface, implementation of the agent (object) is a set of things internal to the agent and hidden from the external world (variables, functions, statecharts, events, embedded agents, etc). Separation of interface and implementation in OO programming allows for modular development: one can change (optimize, extend, or improve) a class by changing its implementation and other classes will not need to know about the changes as long as the interface stays the same. In OO programming, this separation is strongly supported by the restrictive language constructs that prevent the programmer from The Big Book of Simulation Modeling Featuring AnyLogic 5 The AnyLogic Company www.anylogic.com accessing the implementation from outside the class (for example, the Java keywords private or protected). In agent based modeling, those formal access restrictions are often omitted for the purpose of simplicity and more rapid development. For example, the statechart states and transitions in AnyLogic are visible from outside the agent so that one can make inquiries and collect statistics on the agent state by simply writing <agent name>.<state name>. The modeler, however, should not abuse the transparency of the agent border (which he should define himself and keep in mind) and refrain from interacting with the agent in an illegal manner. Examples of this would be assigning a new value to the agent variable or resetting the agent internal event from outside. Important differences between agent based modeling and OO programming are that agents are typically dynamic, have internal time delays, can initiate events, and can have continuously changing variables inside, whereas objects in an OO program typically act upon request; they do something only when their methods are called. Agents in a running agent based model, in this sense, are more like threads or concurrent processes in a running program. This makes the techniques applied in the design of concurrent and parallel systems (such as message sequence diagrams explained in the Field Service example) useful in agent based modeling. Similar to objects, agents can be created and destroyed dynamically. There are things that are fundamentally important in OO design, but are not widely used in agent based modeling. These are, for example, inheritance and polymorphism. Agent based modelers, in general, are not really keen on creating class hierarchies and interfaces or overriding methods in Java or C++ style. Instead, they often use conditional functions and behavior components. Consider a population model where males and females have differences in behavior. An OO purist would create a class hierarchy like the one shown in the Figure on the left. Properties and behavior common for all people would be located in the base class Person, whereas behavior specific to males and females would be placed in the two corresponding subclasses Male and Female. A non OO-minded modeler would create just one class Person, with the parameter Sex, and one behavior component with two branches as shown in the same figure on the right. Both versions may make sense, but which one is better depends on how these constructs are used throughout the model. The Big Book of Simulation Modeling Featuring AnyLogic 6 The AnyLogic Company www.anylogic.com
OO purist and non OO-minded modeler versions of agent classes in a population model OO modeling in AnyLogic Any AnyLogic model, not just an agent based one, consists of classes that inherit from ActiveObject. ActiveObject is the base class for all model components (called active objects) defined in the AnyLogic engine. In the simplest case, there is just one active object class Main in the model. When you create a new model, this class is added automatically and its editor opens. Then you can start to fill the class with process flowcharts, stocks, flows, statecharts, events, and graphics. Person Education Birth date Male Male lifephase Female Female lifephase Base class Subclasses Agent Coordinates Connections
AnyLogic engine classes Classes in the user model Subclass of Person Education Birth date Sex Male lifephases Female lifephases Lifephase Sex == MALE Sex == FEMALE Subclass of ActiveObject The base class for all model components A subclass of ActiveObject that extends it with networking, moblity, message passing and other useful functionality OO purist version Non OO-minded modeler version Subclass of The Big Book of Simulation Modeling Featuring AnyLogic 7 The AnyLogic Company www.anylogic.com In a hierarchical model there is more than one class, and when the model runs objects of different classes are embedded one into another. When we say an active object is embedded into another active object (called container object), it means: The embedded object does not exist without the container; it is created when the container is created, and deleted when the container is deleted. If the embedded object is replicated, i.e., has multiple instances, the container object controls dynamic creation and deletion of the instances. The animation of the embedded object becomes a part of the animation of the container object (embedded animation). From a Java viewpoint, the embedded object is a member, or field, of the container object and exists in its namespace at the same level as variables, parameters, statecharts, etc. Any agent based model is hierarchical and has at least two classes: the top-level class (like Main) that contains the agents, and the agent class (like Person). Agents typically exist as a replicated object (a collection of multiple objects of the same type) embedded into Main, see the Figure. User agent classes do not inherit directly from the ActiveObject class; they are subclasses of the class Agent, which extends ActiveObject with features specific for agent based modeling.
UML diagram of a typical agent based model In AnyLogic you do not need to write code to create subclasses and embed objects into one another; it is done by using drag and drop and wizards. The simplest way of setting up the architecture of an agent based model is to use the Agent population wizard. Main Agent AnyLogic engine classes Classes in the user model Subclass of Person Subclass of ActiveObject people[] Contains multiple instances of Subclass of The Big Book of Simulation Modeling Featuring AnyLogic 8 The AnyLogic Company www.anylogic.com To create a population of agents: 1. Drag the Agent population item from the General palette to the editor of Main (or another container object). 2. In the wizard, specify the name of the agent class (e.g., Person), the name of the population (e.g. people), the initial number of agents, and, optionally, the space and network settings.
The population of agents, the environment, and the animation created by the wizard There is also a general way of creating a new active object class, making it an agent class, and embedding it into the container class. It includes the following steps. To create a new active object class: 3. Right-click in the Projects tree and choose New | Active object class from the context menu. 4. Enter the class name (by convention, the first letter of class name should be capitalized) and click Finish. 5. The new class appears in the Projects tree and its editor opens. Note that the class created this way is not embedded anywhere yet. To make an active object class an agent class: 1. On the General page of your active object class properties, check the Agent checkbox. This changes the base class from ActiveObject to Agent. The Agent page of the properties becomes available and the icon of the class changes. To embed one active object class into another: 1. Drag the class icon from the Projects tree to the editor of the container object. An embedded object is created inside the container object. So far, there is a single instance. To replicate (to create multiple instances of) the embedded object: 1. Select the embedded object in the editor of the container object and select the Replicated checkbox in its properties. Specify the initial number of objects (by default, the initial number is 0). Drag The Big Book of Simulation Modeling Featuring AnyLogic 9 The AnyLogic Company www.anylogic.com
The general way of creating a replicated embedded object Choose this menu item to create a new active object or agent class The new class appears in the Projects tree Select the Agent checkbox to make the class an agent class Drag the class icon to the editor of the another class to create an embedded object Drag In the properties of the embedded object select the Replicated checkbox to create multple instances The Big Book of Simulation Modeling Featuring AnyLogic 10 The AnyLogic Company www.anylogic.com Time in agent based models When talking about agent based models, we need to distinguish between asynchronous and synchronous time models. Asynchronous time means there is no grid on the time axis and events may occur at arbitrary moments, exactly when they are to occur. Synchronous time assumes things can only happen during discrete time steps (they are snapped to the time grid), and nothing happens in between, see the Figure. In synchronous models, on every time step, every agent checks whether it needs to do something. Do not confuse these time steps with the time steps of the numeric solver that solves continuous-time algebraic and differential equations. The latter are typically smaller and are out of the user control. The user cannot associate an arbitrary action with a numeric step or even know how many times the equations are evaluated.
Synchronous and asynchronous time in agent based models Some people think that agent based modeling assumes synchronous time (time steps). This is not true. On the contrary, the most elegant and practically useful agent based time Agent 3 Agent 2 Agent 1 time Agent 3 Agent 2 Agent 1 Step 1 Step 2 Step 3 Step 4 Step 5 Step 6 Preparation for the step, no public state change occur The actual step state change occurs here Synchronous time model Asynchronous time model The Big Book of Simulation Modeling Featuring AnyLogic 11 The AnyLogic Company www.anylogic.com models are either asynchronous or have a mixture of asynchronous time and time steps. Synchronous time only makes sense in these cases: When modeling an artificial world, which is synchronous by definition, such as in The Game of Life or in The Shelling Segregation Model. When the real world system is synchronous. These cases are rare. An example would be a supply chain where inventory decisions are made, for example, on a monthly basis. When the agent needs to be updated on what happens around it to recalculate internal variables and check conditions. For example, in the Hispanic population acculturation model, a person periodically updates his contact rate with other Hispanics, which depends on how many there are in his region. This is, in a way, emulation of continuous time dynamics, typically done at time steps larger than those of numerical solver. In any case, we recommend using asynchronous time whenever possible, i.e., letting things happen when they really are going to happen. Individual decisions like purchases, job changes, moves, contacts between people, and stochastic events (like breakdowns, recovery, etc.) can just occur at their exact times on the continuous time axis. If there is still a need for time steps, such as in the latter case above, you can mix time steps with continuous time. The model Air Defense System presented later in this chapter is a good example of such a mixture. Synchronous time inevitably introduces a certain degree of inaccuracy and raises a lot of questions (and thus provides for numerous discussions at academic conferences on agent based modeling). Do the results depend on the size of the time step? Do they depend on the order the agents are processed within a time step (deterministic, random)? Can we do a single loop across all agents or do we need to do two loops within a step: the first one to prepare the action and the second one to execute it? In The Game of Life, for example, you cannot do a single loop: all cells must see the previous state of other cells before changing their own state. If a message is sent on a certain time step, on what time step should it be delivered? In addition, synchronous time models are typically computationally less efficient than asynchronous models as every agent is visited on every time step, even if it is not doing anything. Statistics collection in agent based models can also be synchronous and asynchronous. For example, to find out the number of infected people in an epidemic model, you can loop throughout the population and calculate the number at the time you need it, or you can have an always up-to-date counter, which is incremented or decremented by The Big Book of Simulation Modeling Featuring AnyLogic 12 The AnyLogic Company www.anylogic.com the agents when they change their individual states. The synchronous statistics collection is, however, more standard and common. Space in agent based models Space is widely used in agent based models. Even in the models where location and movement of agents are not important from the model logic viewpoint, space is often used to visualize the agents.
Space types in AnyLogic AnyLogic supports four types of space: Continuous two-dimensional space Airplane Agent AnyLogic engine classes User model AgentContinuousGIS AgentContinuous2D AgentContinuous3D AgentContinuous AgentDiscrete2D Combine Bomber Cell moveTo( longitude, latitude ) moveTo( x,y ) moveTo( x,y, z ) jumpToCell( row, column ) X Y X Y Z 41844N 35942W [4,3] Subclass of The Big Book of Simulation Modeling Featuring AnyLogic 13 The AnyLogic Company www.anylogic.com Continuous three-dimensional space Discrete space (grid of cells) GIS (Geographic Information System) space Correspondingly, there are four base classes for agents: AgentContinuous2D, AgentContinuous3D, AgentDiscrete2D, and AgentContinuousGIS, see the Figure. Depending on the space type, agents will have different space-related functions and properties, like moveTo(), distanceTo(), jumpToCell(), velocity, column, row, On arrival action, etc. Do not confuse the type of space and the type of animation: 2D continuous or discrete space applies to the model logic only and does not prevent you from creating 3D animation of the model. The space settings are defined in two places: in the agent itself and in the Environment object, see the Figure. The Environment object is required for the discrete and GIS space types and is optional for continuous spaces. If the Environment object is used, its space type should match the space type of all agents in the environment. If space is irrelevant to the model logic and animation, you can choose arbitrary space settings. Agents of different types, populations as well as individual agents, can belong to the same environment object and share the same space. Environment can be used to apply layouts to the agents; it also provides some space-related functions, such as getAgentAtCell().
Space type settings The space type of the environment and the agent must match This puts the agent population into the environment Airplane agent class Main class The Big Book of Simulation Modeling Featuring AnyLogic 14 The AnyLogic Company www.anylogic.com Discrete space Thinking of agents as cellular automata living in discrete space is another common misconception (the first one is assumption of discrete time). Indeed, there are a lot academic models that use discrete space (for example, The Game of Life, Schelling Segregation, Heat Bugs, etc.), but in most industrial-level models, space-aware agents live and move in continuous 2D or 3D space.
Examples of discrete spaces With that said, we will, nevertheless, spend some time on discrete space because there are cases when grid cells are natural and useful. For example, they can be used to The Game of Life The Schelling Segregation Model Wildfire Wandering Elephants The Big Book of Simulation Modeling Featuring AnyLogic 15 The AnyLogic Company www.anylogic.com model dynamics of vegetation, water resources, wildfire, or population density in a geographical space. In such models, land is partitioned in square cells with each cell being an agent with its own variables and behavior. Cells may interact with each other and with other types of agents, for example, with agents freely moving in 2D space that overlaps the discrete space, see the example model Wildfire described later in this section. Discrete space is a rectangular grid of cells, see the Figure. In AnyLogic, it is created under the control of the Environment object. To create an agent population living in discrete space: 2. Drag the Agent population object from the General palette to the graphical editor. 3. In the Configure new environment page of the wizard choose Space type: Discrete 2D and set up the space size. The options are shown in the Figure. You can choose the visual size of the entire space and the number of rows and columns. A cell is, in general, a rectangle whose width equals the space width divided by the number of columns, and height is the space height divided by the number of rows. AnyLogic does not draw any grid lines or rectangles for cells so it is up to you to decorate the space.
Discrete space options (Advanced page of the Environment properties) The position of the discrete space rectangle on the canvas of the container object, namely its top left corner, is defined by the position of the presentation of the discrete Discrete space is chosen The visual size of the (rectangular) space The number of grid cells in this case is 200 * 200 = 40000 Moore (8 neighbors) or Euclidean (4 neighbors) Arranged, random, or custom Also: random, ring lattice, small world, and scale free The Big Book of Simulation Modeling Featuring AnyLogic 16 The AnyLogic Company www.anylogic.com space agent, see the Figure. Therefore, if there are multiple populations of agents in a single discrete space environment, their presentation shapes should be located at the same point. A grid cell may be either empty or occupied by, at most, one agent. In many models there is an agent in every cell. In other words, a cell is itself an agent. In such models, agents typically do not move. In other models (for example, in the Schelling Segregation Model), there are fewer agents than cells and agents can move from one cell to another. In the latter case you can apply different layout types to the agents, such as random or arranged.
Dimensions and neighborhood types of the discrete space In discrete space there is a notion of neighborhood. There are two types of neighborhoods: Moore and Euclidean (also known as Von Neumann). In the Moore neighborhood a cell has eight neighbors, and the Euclidean neighborhood a cell has four neighbors, see the Figure. The neighborhood type will affect the result of the Moore neighborhood (8 neighbor cells) N NE E W S SE SW
NW
N E W S Euclidean neighborhood (4 neighbor cells) Position of the presentation shape of the discrete space agent defines the top left corner of the space Width Height Columns Rows The Big Book of Simulation Modeling Featuring AnyLogic 17 The AnyLogic Company www.anylogic.com getNeighbors() function. The constants NORTH, NORTHEAST, EAST, etc. defined in the Agent class are used to address directions in the discrete space. Although networks are not frequently used in conjunction with discrete space, a choice of social networks is available, as you can see in the Figure. Example: Schelling segregation In the 1970s, Thomas Schelling, a Nobel prize winning economist, showed in his articles dealing with racial dynamics that a preference that one's neighbors be of the same color, or even a preference for a mixture "up to some limit", could lead to total segregation, thus arguing that motives, malicious or not, were indistinguishable as to explaining the phenomenon of complete local separation of distinct groups. He used coins on graph paper to demonstrate his theory by placing pennies and nickels in different patterns on the "board" and then moving them one by one if they were in an "unhappy" situation. [Wikipedia]. We will implement Schellings model as a discrete space/discrete time agent based model. The space represents a city, and each cell represents a house. Agents are people and are two colors: yellow and red. Initially, people are randomly distributed across the city. There are fewer people than houses, so there is always the possibility that a person could move. The agent behavior in this model is very simple: If the percentage of people of the same color among ones neighbors is lower than a certain threshold value, the person feels unhappy and moves to a randomly chosen empty house; otherwise the person is happy and does nothing. The threshold value (the preference for the same color) will be a parameter of the model. Discrete time is originally assumed in this model: the evaluation of happiness and moves are performed on discrete time steps. It is, however, possible to implement an asynchronous version of the model. Create the population of agents and discrete space: 1. Create a new model and drag the Agent population object from the General palette to the editor of Main. 2. In the first page of the wizard, set the Population name to people, the Initial population size to 8000, and choose rectangle as Animation. 3. In the next page of the wizard, choose the Discrete 2D space type and click Finish. 4. Open the editor of Person, and add a new parameter. Set the parameter name to color and choose Color as the type of the parameter. The Big Book of Simulation Modeling Featuring AnyLogic 18 The AnyLogic Company www.anylogic.com 5. Type the following expression in the default value field of the parameter: randomTrue( 0.5 ) ? red : yellow. This will ensure there is approximately the same number of red and yellow people in the model. 6. Select the rectangle shape at the coordinate origin of Person and type color in the Fill color field of its Dynamic property page. 7. Run the model. See the red and yellow people randomly distributed across the discrete 100 by 100 space, see the Figure on the left.
The Schelling Segregation Model. Preference is set to 60% Enable the discrete time in the model and implement agent behavior: 8. In Main, create a new parameter Preference of type double and default value 0.6. This will be the minimum threshold value for the percentage of same color neighbors. 9. Open the editor of Main, select the environment object, and select Enable steps in its properties. This enables time steps and now we can type behavior rules in the On before step and On step action fields of the agents. 10. Return to the editor of Person and add a Boolean variable happy with initial values true. 11. Open the Agent page of Person properties and type the following code in the On before step field: Agent[] neighbors = getNeighbors(); //default neighborhood model is Moore if( neighbors == null ) { happy = true; //no neighbors are good neighbors } else { //count same color neighbors int nsamecolor = 0; Initially the population is evenly mixed ...and just after a few steps it is strongly segregated The Big Book of Simulation Modeling Featuring AnyLogic 19 The AnyLogic Company www.anylogic.com for( Agent a : neighbors ) if( ((Person)a).color.equals( color ) ) //need to cast generis Agent to Person nsamecolor++; //compare with minimum number happy = nsamecolor >= neighbors.length * get_Main().Preference; } 12. And in the field On step type: if( ! happy ) jumpToRandomEmptyCell(); 13. Run the model. Watch how the initially mixed population becomes almost fully segregated in just a few steps; see the Figure on the right. Even a slight preference to have at least 60% of neighbors of the same color leads to strong segregation. You can link a slider control to the Preference parameter and experiment with different values. Another implicit parameter of the model is the occupancy of the space. In our case it is 80% because we have 8,000 people and 10,000 cells (houses). Occupancy affects the ability of the system to reach equilibrium at high values of preference. Some comments on the model implementation. Notice that in this model each step has two phases: preparation and action (this scheme is shown in the Figure). An agent evaluates its neighborhood in the preparation phase (On before step) and moves in the action phase (On step). The agents internal variable happy is only needed to keep the neighborhood evaluation results between the two phases. The two-phase steps are necessary in this case because we must ensure people do not move before everyone has evaluated their current neighborhood. If agents were doing both things at once, some people would be evaluating their current neighborhood and some would be evaluating intermediate neighborhoods in the middle of moving, which does not make any sense. ? Convert this model into an asynchronous model. Instead of global steps you can use a cyclic event with random recurrence time inside each agent. In the asynchronous model you will not need to separate neighborhood evaluation and moving. Compare the simulation results. Example: Conways Game of Life And of course we will show how to program, in AnyLogic, The Game of Life the famous invention of John Conway that opened up a whole new field of mathematical research, the field of cellular automata [Martin Gardner, Scientific American]. The universe of The Game of Life is an infinite two-dimensional orthogonal grid of square The Big Book of Simulation Modeling Featuring AnyLogic 20 The AnyLogic Company www.anylogic.com cells, each of which is in one of two possible states: dead or alive. Every cell interacts with its eight neighbors (Moore neighborhood model is assumed). At each step: Any live cell with fewer than two live neighbors dies, as if caused by under- population. Any live cell with two or three live neighbors lives on to the next generation. Any live cell with more than three live neighbors dies, as if by overcrowding. Any dead cell with exactly three live neighbors becomes a live cell, as if by reproduction. The initial pattern constitutes the seed of the system, and the rules are applied repeatedly to create further generations [Wikipedia]. The following is interesting in the context of the agent based modeling technique. One may try to model a dead cell as a cell with no agent in it, and a live cell as a cell with an agent. This however will lead to a very awkward algorithm of reproduction. A more elegant implementation is having, in every cell, an agent with two possible states: dead or alive. Create discrete space and cells: 1. Create a new model and drag the Agent population object from the General palette to the editor of Main. 2. In the first page of the wizard, set the Agent class name to Cell, Population name to cells, the Initial population size to 10000, and choose rectangle as Animation. 3. In the next page of the wizard, choose the Discrete 2D space type and click Finish. 4. Open the editor of Cell and add a Boolean variable alive with initial value randomTrue( 0.2 ). 20% of cells will be randomly chosen to be initially alive. 5. Select the rectangle (the cell animation shape created by the wizard) and type this code in the Fill color field of its Dynamic property page: alive ? mediumBlue : lavender. 6. Run the model. See the initial random pattern. Program the cell behavior: 7. Open the editor of Main, select the environment object, and select Enable steps in its properties. This enables time steps and now we can type behavior rules in the On before step and On step action fields of the agents. 8. Return to the editor of Cell and add an integer variable naliveneighbors. This variable will keep a count of live neighbor cells between the evaluation and action phases of the step, similar to the happy variable in the Schelling Segregation Model. The Big Book of Simulation Modeling Featuring AnyLogic 21 The AnyLogic Company www.anylogic.com 9. Open the Agent page of Cell properties and type the following code in the On before step field: naliveneighbrs = 0; //reset counter for( Agent a : getNeighbors() ) //count all alive neighbors if( ((Cell)a).alive ) //need to cast generic Agent to Cell naliveneighbrs++; 10. And in the field On step type: if( alive && naliveneighbrs < 2 ) alive = false; //die because of loneliness else if( !alive && naliveneighbrs == 3 ) alive = true; //new cell is born else if( alive && naliveneighbrs > 3 ) alive = false; //die because of overcrowding 11. Run the model and watch the evolution of this exciting artificial world.
The Game of Life Although The Game of Life is a zero-player game, i.e., its evolution is entirely determined by its initial state [Wikipedia], we can bring in some instructiveness. For example, we can program the cell to toggle its state on mouse click. Then we will be able to initiate additional waves of evolution in static or oscillating configurations. Add cells reaction on mouse click: 12. In the editor of Cell, select the rectangle animation shape and type this code in the On click field of its Dynamic property page: alive = ! alive; 13. Play with the model. Initial configuraion (randomly generated) Step 5149 Pulsar The Big Book of Simulation Modeling Featuring AnyLogic 22 The AnyLogic Company www.anylogic.com Example: Wildfire We will build a model of a wildfire. In this example we will show how the two types of space, continuous and discrete, can be linked. We will model vegetation as grid cells agents in discrete space. The burning time of a cell is proportional to the amount of fuel in the cell, which will be randomly generated at the model startup. While burning, the cell may cause ignition in the adjacent cells. The ignition may also be caused by a bomb dropped by an aircraft an agent moving in continuous 2D space that overlaps the discrete space. As an additional exercise, we can introduce the wind into the model and make the probability of ignition depend on the wind direction. Create the grid cells: 1. Create a new model and drag the Agent population object to the editor of Main. On the first page of the wizard type: Agent class name: GridCell Population name: gridcells Initial population size: 40000 Animation: Rectangle Press Next. 2. On the second page of the wizard, choose the Discrete 2D Space type for the environment and make the following settings: Width: 600 Height: 600 Columns: 200 Rows: 200 Initial location: Arranged Press Finish. The wizard creates a new environment object, a population of grid cells, and places the animation of a cell (a small blue rectangle) nearby. 3. Run the model. You should see a square space filled with 40,000 cells. You should also see that the model consumes 60-70% of the default available memory (64K). This is because of the number of agents. 4. Open the editor of GridCell. Select the blue rectangle and change both its width and height to 3 pixels (these settings are on the Advanced property page). Now the cell animations will not overlap. 5. Open the editor of the Simulation experiment and extend the frame (the default model window border) to 800 by 650 pixels. The entire area now will be visible. The Big Book of Simulation Modeling Featuring AnyLogic 23 The AnyLogic Company www.anylogic.com 6. Open the Advanced page of the Simulation experiment and set Maximum available memory to 512M. This will allow us to further add internal memory to our agents. The next step is to create the initial distribution of fuel in the cells. We could use real geographical data, but in this simple model we will generate a random landscape with forests and deserts. Model the initial vegetation distribution: 7. Open the editor of GridCell and add a parameter Fuel of type double. This will be the amount of fuel in the cell. 8. Go to the editor of Main and add a function makeUpInitialFuel(). Type this code in the function body: int N = environment.getColumns(); //generate forest "seeds" for( int n = 0; n < 200; n++ ) { //200 seeds int x = uniform_discr( 0, N - 1 ); int y = uniform_discr( 0, N - 1 ); for( int k = 0; k < 1000; k++ ) { int i = ( x + (int)triangular(-50,0,50) + N ) % N; //%N to stay within the space int j = ( y + (int)triangular(-50,0,50) + N ) % N; ((GridCell)( environment.getAgentAtCell( i, j ) )).Fuel += 0.25; } } //smooth for( int n = 0; n < 10; n++ ) { for( GridCell gc : gridcells ) { double sum = gc.Fuel; for( AgentDiscrete2D ad : gc.getNeighbors() ) sum += ((GridCell)ad).Fuel; //getNeighbors() returns generic agent class gc.Fuel = sum / (gc.getNeighbors().length + 1); } } //find min/max fuel values double min = +infinity; double max = -infinity; for( GridCell gc : gridcells ) { double f = gc.Fuel; if( f > max ) max = f; if( f < min ) min = f; } //normalize into [-0.2,1.3] for( GridCell gc : gridcells ) { gc.Fuel -= min; //to [0..max-min] gc.Fuel *= 1.5 / ( max - min ); //to [0..1.5] gc.Fuel -= 0.2; //to [-0.2..1.3] The Big Book of Simulation Modeling Featuring AnyLogic 24 The AnyLogic Company www.anylogic.com } //update cells for( GridCell gc : gridcells ) gc.square.setFillColor( lerpColor( gc.Fuel, paleGoldenRod, new Color(65, 100, 0) ) ); 9. In the Startup code of Main type: makeUpInitialFuel(); This will call the function at the model startup to make up the initial landscape. 10. Run the model. The area covered with the cells should look like the one shown in the Figure.
A randomly generated landscape The landscape generation algorithm we used is just one of many possibilities. It starts with several forest seeds randomly distributed all over the area. Then it creates forests around those seeds by increasing the amount of fuel (the closer to the seed, the larger the increase). We then normalize the values of the cell fuel to the interval [- 0.2,1.3] and modify the initial color of the cell animation to reflect the value of the Fuel. Now we will program the cell behavior. In the first version of our model the fire diffusion will not depend on the wind. Clearing Dense forest The Big Book of Simulation Modeling Featuring AnyLogic 25 The AnyLogic Company www.anylogic.com Program the cell behavior (the fire diffusion): 11. In the editor of GridCell create the statechart, as shown in the Figure. 12. Select the cell animation (the rectangle) and type this code in the On click field of its Dynamic property page: statechart.receiveMessage( "Ignition" ); This will allow us to manually initiate the wildfire. 13. Run the model and try clicking in the areas of various fuel densities. See how the wildfire spreads. The behavior of a grid cell needs detailed explanation. The cell can be in one of three states: Normal, Burning, and BurnedOut. The transition from Normal to Burning is triggered by the message Ignition, which may come from an adjacent cell, or from a mouse click (later on, when we add an aircraft, it will be coming from the aircraft as well). The burning time of a cell is set to the value of the Fuel parameter (see the transition OutOfFuel): the more wood that is in the cell, the longer the cell will burn.
A vegetation grid cell behavior and a wildfire caused by a mouse click On click: statechart.receiveMessage( "Ignition" ); Triggered by: Message: Ignition Guard: Fuel > 0 Entry action: rectangle.setFillColor( red ); Triggered by: Rate: 5 Action: for( int i=NORTH; i<=SOUTHWEST; i++ ) { GridCell gc = (GridCell)( getAgentNextToMe( i ) ); if( gc != null ) //null may be at the space border if( randomTrue( 0.2 ) ) send( "Ignition", gc ); } Action: rectangle.setFillColor( lerpColor( Fuel, feldspar, darkGray ) ); Triggered by: Timeout: Fuel The fire stopped here because of lack of fuel The Big Book of Simulation Modeling Featuring AnyLogic 26 The AnyLogic Company www.anylogic.com The most interesting part of the statechart, however, is the transition SpreadFire. First of all, this transition is internal to the state Burning, which means its execution will not reset the timeout of the transition OutOfFuel. SpreadFire will be executed at the rate of 5, i.e., on average 5 times per time unit. Therefore, the longer the cell is burning, the more occurrences of SpreadFire there will be. Upon each occurrence, the cell iterates across all neighbor cells. (In the for loop we use the knowledge about the values of the direction constants: NORTH is 0, SOUTH is 1, , SOUTHWEST is 7. This knowledge can be obtained from AnyLogic API reference.) The message Ignition is sent to each of the neighbor cells with 20% probability. Try to increase the real time scale and see how fast the model is. This model is computationally very efficient because it is completely asynchronous; there are no time steps. There are quite a few cells (40,000), but inactive cells do not consume any CPU time so there is no global loop across all cells. Our next step is to model the aircraft that drops bombs as it flies over the area. The aircraft will be an agent living in continuous 2D space that overlaps with the discrete space. This time we will not be using the Agent population wizard because there will be only one aircraft. It will live in 2D space and will not need an environment object. Create the Aircraft agent class: 14. Right-click the model (top-level) item in the Projects tree and choose New | Active object class from the context menu. Name the class Aircraft and press Finish. 15. Select the Agent checkbox in the Aircraft class properties. 16. Drag the Plane picture from the Pictures palette to (0,0) of the Aircraft editor. Rotate the picture so that the aircraft looks towards the right (this side of the agent moves forward), and reduce the size by 50%. 17. Open the Agent page of the Aircraft properties and make sure the space type is Continuous 2D. On the same page set the velocity of the aircraft to 30. 18. Return to the editor of Main and drag the Aircraft class icon from the Projects tree there. The animation of the airplane appears at (0,0) of Main. Move the aircraft animation to the same point where the cell animation is located. This will synchronize the animation locations. Let the aircraft fly over the area: 19. Type this code in the Startup code field of the aircraft: setXY( 0, uniform( 400, 600 ) ); //set the initial location moveTo( 600, uniform( 0, 200 ) ); //start moving towards the right The Big Book of Simulation Modeling Featuring AnyLogic 27 The AnyLogic Company www.anylogic.com The initial position is chosen randomly at the west edge of the area, closer to the south, and the final position is on the east side, closer to the north. 20. Run the model and see the aircraft flying. Now we will model dropping the bombs. This is exactly where the two types of space will be linked in our model.
Wildfire caused by bombing Model bombing: 21. Return to the editor of the Aircraft class and add an Event object. Name the event bomb. Set the event Mode to cyclic and set Recurrence time to 5. 22. Type this code in the Action field of the event: EnvironmentDiscrete2D env = get_Main().environment; double cellw = env.getWidth() / env.getColumns(); //cell width in pixels double cellh = env.getHeight() / env.getRows(); //cell height in pixels //transalate (X,Y) into (row,column) int c = (int)( getX() / cellw ); int r = (int)( getY() / cellh ); //send the message to the cell at that position env.getAgentAtCell( r, c ).receive( "Ignition" ); Dense forest The Big Book of Simulation Modeling Featuring AnyLogic 28 The AnyLogic Company www.anylogic.com 23. Open the Agent page of the Aircraft properties and type this code in the On arrival field: bomb.reset(); This will turn off the cyclic bomb event when the aircraft has reached the edge of the area. 24. Run the model. While the aircraft is flying, it drops a bomb every 5 time units. The bomb ignites the cell right below the aircraft. The translation of the continuous coordinates (X,Y) into the discrete coordinates (row, column) is done using the grid cell width and height. ? Modify the model to take wind into consideration. Make the probability of passing the fire from cell to cell depend on the wind direction, as shown in the Figure. Assume the wind direction can take eight values {NORTH, NORTHEAST,} and the strength of the wind is always the same.
The probability of passing the fire depending on the wind direction Tip: Program the function that converts the direction constant to angle, and then another one that compares two angles and returns the probability. Use the probability in the action of the SpreadFire transition instead of 0.2. Discrete space API The base class for agents that live in discrete space is AgentDiscrete2D, which is a subclass of Agent, which, in turn, is a subclass of ActiveObject. In addition to the functionality common to all kinds of agents, AgentDiscrete2D offers the following API: int getC() returns the column of the agent's cell. int getR() returns the row of the agent's cell. jumpToCell( int r, int c ) moves the agent into a cell with the given row and column. setCell( int r, int c ) puts the agent into a given cell (initialization use only). 0.5 0.3 0.3 0.1 0.1 0.05 0.05 0.03 NE The wind direction The probability of passing fire from cell to cell The Big Book of Simulation Modeling Featuring AnyLogic 29 The AnyLogic Company www.anylogic.com boolean jumpToRandomEmptyCell() finds a random empty cell and places the agent there. moveToNextCell( int dir ) moves the agent to an adjacent cell in a given direction. swapWithAgent( AgentDiscrete2D anotherAgent ) swaps the cell location of this agent with another agent. Swap is the only way for an agent to move in a fully occupied space. swapWithCell( int r, int c ) swaps this agent with an agent at the cell with the given row and column. swapWithNextCell( int dir ) swaps the agent with an agent at the adjacent cell in a given direction. int[] findRandomEmptyCell() tries to find a randomly located empty cell and returns its row and column in the array with two elements. Agent getAgentAtCell( int r, int c ) returns the agent located in the cell with a given row and column, or null. Agent getAgentNextToMe( int dir ) returns the agent next to this agent in a given direction, if any. AgentDiscrete2D[] getNeighbors() returns the array of neighbor agents, subject to the current neighborhood type (Euclidean {N, S, E, W}, Moore - also {..,NW, NW, SE, SW}). The elements in the returned array are of the base class AgentDiscrete2D, and not of the same class as this agent because, in general, there can be more than one type of agent in the same space. In addition, the environment object supporting 2D continuous space (base class EnvironmentDiscrete2D) offers these functions: int[] findRandomEmptyCell() tries to find a randomly located empty cell and return its row and column in the array with two elements. AgentDiscrete2D getAgentAtCell(int r, int c) returns the agent located in the cell with a given row and column, or null. int getColumns() Returns the number of columns in the space. int getRows() Returns the number of rows in the space. Continuous 2D and 3D space Continuous space (two- or three-dimensional) in AnyLogic is infinite space with real number coordinates (Java type for coordinate is double). Continuous 2D space is assumed by default when a new agent class is created. If you want to apply standard layouts or networks to the agents, you should use the Environment object. Otherwise, the Environment object can be omitted in the model. The Big Book of Simulation Modeling Featuring AnyLogic 30 The AnyLogic Company www.anylogic.com To create an agent population living in continuous 2D or 3D space: 25. Drag the Agent population object from the General palette to the graphical editor. 26. In the Configure new environment page of the wizard choose Space type: Continuous 2D or Continuous 3D and choose the width and height, and the initial layout. The options of the Environment object related to the continuous space are shown in the Figure. The Width and Height are not the bounds of the entire space; the space is always infinite and unbounded. These are the bounds of the layout, and they apply each time the layout is applied (in most cases, the layout is applied once, when the model initializes). The agents can freely cross these bounds when they move.
Continuous 2D space options (Advanced page of the Environment properties) When you create an agent population, or when you drop an individual agent on the canvas of the container object, its animation is also placed there. The design-time position of the animation defines the point where the agent with coordinates (0,0) will be displayed at runtime (see the Figure), but does not affect the actual coordinates of the agent. If there is more than one agent population or individual agent and, correspondingly, more than one embedded agent animation, it makes sense to place all the animations into the same point to make sure that at runtime the positions of the agent animations are consistent with their coordinates. Continuus 2D space is chosen The bounding rectangle of the layout Random, arranged, ring, spring mass, or custom Also: random, ring lattice, small world, and scale free The Big Book of Simulation Modeling Featuring AnyLogic 31 The AnyLogic Company www.anylogic.com To see the agent animation in the 3D scene, make sure its Show in 3D scene checkbox is selected.
The meaning of the width, height, and the layout properties Space type: Continuous 2D Width: 200 Height: 200 Layout type: Random Editors coordinate origin The animation of agents from the population houses. The agent with coordinates (0,0) will be displayed here The rectangle 200 x 200. When the layout is applied, these bounds are assumed The Big Book of Simulation Modeling Featuring AnyLogic 32 The AnyLogic Company www.anylogic.com Movement in continuous space The agent movement is always piecewise linear: the agent moves along straight line segments at constant velocity. To initiate a straight-line movement from the current position to the point with coordinates (Xb,Yb), you should call the function moveTo( Xb, Yb ), in 3D space this is moveTo( Xb, Yb, Zb ). Optionally, you can suggest a polyline-based trajectory for the agent movement by providing the polyline as the last parameter of the moveTo() function, e.g. moveTo( x, y, z, polyline ), see the Figure. The agent will first move from its current location to the closest point on the polyline along the shortest straight line, then move along the polyline to the point on the polyline closest to the destination, and then straight to the destination point. You can model movement along curved trajectories by approximating them by polylines. The agent stops when it reaches the destination point, or when stop() or jumpTo() is called. You can also call moveTo() another time while the agent is moving, and it will then change its direction. The velocity of the agent is set at the bottom of its Agent property page and can be changed at runtime by calling the function setVelocity(). If this function is called when the agent is moving, it continues to move with the new velocity. To model acceleration/deceleration you can break down the movement into small segments and increase or decrease the agent velocity at the beginning of a segment.
Movement in continuous 2D space (Xa,Ya) (Xb,Yb) moveTo( Xb, Yb, polyline ) (Xa,Ya) (Xb,Yb) moveTo( Xb, Yb ) On arrival is called here This is the front side of the agent animation The Big Book of Simulation Modeling Featuring AnyLogic 33 The AnyLogic Company www.anylogic.com When an agent moves, the right-hand side of its animation is the front side. To change the front side, you need to rotate the animation. When the agent successfully reaches the destination point, its On arrival code (specified in the Agent property page) is executed, and active statecharts transitions waiting on arrival are triggered, if there are any. The call of moveTo() followed by an arrival- triggered transition is a frequently used pattern. To calculate the distance to another agent or to a point you can use the functions distanceTo( x, y ), distanceTo( x, y, z ), and distanceTo( agent ). To detect the moment when a geometric condition over the moving agents becomes true (for example, when two agents get close to each other), you can use two approaches: Analytical. You can obtain the time analytically using your high school knowledge of physics and geometry, and then schedule the required action at the exact moment of time with the help of event or transition. The solution will be 100% accurate and very efficient computationally. Test the condition periodically (polling). You can introduce time steps ("ticks") and test the condition on each tick. Such a model will consume a lot more CPU power, and the detection will occur with an error (the larger the time step, the larger the error), or can be even missed. However, this approach is simpler and also more general. The example Air defense system presented below demonstrates most of continuous space modeling techniques. Example: Air defense system We will build a simple model of a radar-based air defense system. All types of agents in this model (bomber aircrafts, radars, missiles, bombs, and buildings) live and interact in continuous 3D space. We will use various kinds of movement and space sensing techniques. Bombers are sent to destroy ground assets (buildings) compactly located in a certain area. One aircraft is launched per building. Aircrafts carry bombs. To complete its mission successfully, an aircraft needs to drop a bomb within 500 meters ground distance from the target building while flying, at most, at 2 km altitude. Having completed the mission, the bomber returns to the base using a higher altitude route. The bomber speed is 600 km/h. The buildings are protected by the air defense system, which consists of two radars equipped with guided surface-to-air missiles. A radar can simultaneously guide up to The Big Book of Simulation Modeling Featuring AnyLogic 34 The AnyLogic Company www.anylogic.com two missiles. A missile is launched once the bomber enters the radars coverage area, which is a hemisphere of 6.5 kilometer radius around the radar. The missile speed is 900 km/h. The missile explodes once it gets as close as 300 meters to the aircraft. If the missile exits the radar coverage area before it hits the aircraft, it destroys itself. Create the scene: 1. Create a new model. Drag a Rectangle shape into the editor of Main, place its top left corner at (0,0), and set its size to 800 x 600. We will assume 10 pixels per kilometer scale. 2. Set the Line color of the rectangle to No color and use Earth texture for the Fill color. 3. Check the Show in 3D scene checkbox on the General page of the rectangle properties. On the Advanced property page set the rectangles Z to -1 and Z- height to 1. 4. Finally, on the General page check the Lock checkbox. This prevents the rectangle from being selected and we will be able to freely draw on top of it. 5. Open the 3D palette and drag the 3D window to the editor of Main. Place the window below the ground rectangle, for example, at (0,850) and extend it to the size of 800 by 550 pixels. 6. From the Presentation palette drag the View area and place it at (0,800). Set its Name to view3D and Title to 3D. 7. Create another view area at (0,0) with the name view2D and title 2D. 8. Run the model. Switch between the 2D and 3D views. Create the buildings: 9. Draw a closed polyline on top of the ground rectangle, as shown in the Figure. Name it protectedArea. Our buildings will be located within its bounds. 10. Drag the Agent population object from the General palette to Main (it makes sense to place it to the left of the ground rectangle so it does not interfere with the animation). 11. On the first page of the wizard, type: Agent class name: Building Population name: buildings Initial population size: 10 Press Next. 12. On the second page of the wizard, choose the Continuous3D Space type for the environment. Press Finish. The wizard creates a new environment object, a population of buildings, and places the animation of the building nearby, see the Figure. The Big Book of Simulation Modeling Featuring AnyLogic 35 The AnyLogic Company www.anylogic.com 13. Drag the animation of asset to (0,0) to synchronize the coordinate systems of the agents (buildings) and the Main object. 14. Run the model. See 10 animation shapes of the buildings (so far a person shape is used, but we will change it later) randomly distributed in a cubicle 400 by 400 by 400 pixel space. (This was the default space size setting in the environment object. We do not need to change it as we will customize the asset locations.)
The scene of the Air Defense System model In the next phase, we will change the animation of building to 3D House object and place assets on the ground within the protectedArea. The protectedArea polyline Move the building animation to (0,0) The Big Book of Simulation Modeling Featuring AnyLogic 36 The AnyLogic Company www.anylogic.com Change the animation of building and set the buildings locations: 15. Open the editor of Building, delete the animation created by the wizard, and drag the House object from the 3D Objects palette to (0,0). Set the Scale of the object to 25%. 16. In the General page of the Building properties write the following Startup code: Point pt = get_Main().protectedArea.randomPointInside(); setXYZ( pt.x, pt.y, 0 ); This will place the house on the ground and within the polyline bounds. 17. Run the model and see where the buildings are located. Note that the polyline protectedArea is visible in the 2D view, but not in the 3D view. This is because we have not selected its Show in 3D scene property. Now we will create the bomber aircrafts and let them fly to the buildings and return to the base. Create the Bomber agent class: 18. Return to the editor of Main and create another Agent population (place it nearby the environment object to the left of animation). In the wizard, make these settings: Agent class name: Bomber Population name: bombers Initial population size: 0 Environment: Use existing (environment) Press Finish. 19. In the editor of Main, select the default animation of the bomber (it should be a blue 2D person), drag it to (0,0), and check the Show in 3D scene checkbox in its properties. The animations of building and bomber are now at the same location, one on top of the other. 20. Open the editor of Bomber, delete the default animation and replace it with the Airliner 3D object. Rotate the airliner so it is oriented rightwards and set its scale to 50%. 21. On the Agent page of the Bomber properties select the Continuous 3D space type, deselect the Environment defines initial location checkbox, and set the initial coordinates to (0,0,50). The bomber will therefore fly into the model space at 5km altitude. Set the model time units and specify the velocity of the aircraft: 22. Select the model (topmost) item in the Projects tree and choose minutes as the model Time units in the Properties view. The Big Book of Simulation Modeling Featuring AnyLogic 37 The AnyLogic Company www.anylogic.com 23. Return to the editor of Bomber. On the Agent page of its properties set the Velocity of the bomber to 100. With the 10 pixels per kilometer spatial scale we assumed earlier, the bomber will cover 100 pixels = 10 km in one minute (time unit). This gives us 600 km per hour. Create the initial version of the bomber behavior: 24. In the editor of Bomber, create a parameter target of type Building (select Other and type Building). This will be the target building in the bomber mission. 25. Create the bomber statechart, as shown in the Figure. The bombers will be parameterized with the target buildings at creation time, fly directly there, gradually lowering the altitude from 5 to 1.8 kilometers, and then return to the initial point where they will delete themselves from the model. In the On enter field of both states we call the moveTo() function of the agent to initiate movement, and then the transition from the state is triggered by the agent arrival. This pattern is very common in agent based models.
The statechart of the Bomber Program mission assignment: 26. Open the editor of Main. Add an Event object (from the General palette). Set the event properties, as shown in the Figure. 27. Run the model. The cyclic event periodically looks for a building without a bomber already flying to it. We use iteration across the agent population twice: in the outer loop we iterate across buildings, and in the inner loop we iterate across bombers. If such a building is found, we create a new bomber agent and assign the building to the bomber as the target (as Arliner 3D object at 25% scale Parameter of type Building Entry action: moveTo( target.getX(), target.getY(), 18 ); Triggered by: Agent arrival Entry action: moveTo( 0, 0, 50 ); get_Main().remove_bombers( this ); Triggered by: Agent arrival The Big Book of Simulation Modeling Featuring AnyLogic 38 The AnyLogic Company www.anylogic.com long as the Bomber agent has one parameter target of type Building, AnyLogic generates a constructor with that parameter).
Mission assignment implemented using cyclic event Upon creation, a bomber takes direction to the target building, makes an instant u- turn, and heads back to (0,0). So far, the bombers use straight line trajectories this is assumed by the moveTo( x, y, z ) method. We will now draw the 3D escape trajectory for the return route and set the bombers to follow that trajectory on the way back. We will use another version of the method: moveTo( x, y, z, polyline ). Draw the 3D escape route and let the bombers follow in on the way back: 28. Open the editor of Main and draw a polyline, as shown in the Figure. This time you can use the polyline object from the 3D palette and not from the The building is passed into the bombers constructor The Big Book of Simulation Modeling Featuring AnyLogic 39 The AnyLogic Company www.anylogic.com Presentation palette, because it has the Show in 3D scene property already selected. 29. Set the name of the polyline to escapeRoute, and the Line width to 2 pixels. On the Advanced page set Z to 20 (this will be the base Z-coordinate of the polyline) and Z-height to 2 pixels.
Bombers return to base using a route defined by a polyline 30. Open the Points page of the polyline and modify the individual Z coordinates of the points approximately, as shown in the Figure. The idea is to have the These points are above the base Z level The Big Book of Simulation Modeling Featuring AnyLogic 40 The AnyLogic Company www.anylogic.com initial section of the polyline at about the same altitude as the bomber attack altitude (20 pixels = 2 km is the base Z), and then gradually raise it to 9 km (base 20 + 70, which is Z of the last point). 31. Open the editor of Bomber and change the Entry action of the state Away to: moveTo( 0, 0, 90, get_Main().escapeRoute ); (We need to put the prefix get_Main() before the escapeRoute because this graphical object is located not inside the Bomber agent, but one level up, in Main.) 32. Run the model. See how the bombers return to the base. (The escapeRoute polyline is visible at runtime. If you would like to hide it, you can deselect the checkbox Show at runtime in its properties.) Note that when you ask an agent to move from the point A (its current position) to the point B using a polyline, both points A and B do not need to be located on the polyline. The agent will calculate and use the shortest straight route to and from the polyline. The next step is to model the interaction between the bomber and the target building. We will use yet another agent type, Bomb. When approaching the attack distance, the bomber will drop a bomb onto the building. When the bomb reaches the building, the building will change its state to Destroyed. Although aircrafts carry bombs, in the model the Bomb agents will not be located inside the Bomber agent, but at the same level as the bombers and the buildings directly inside the Main object. With this model architecture it will be easier to place bombs in the same space. Create the Bomb agent class and program its interaction with the building: 33. Return to the editor of Main and create another Agent population. In the wizard, make these settings: Agent class name: Bomb Population name: bombs Initial population size: 0 Environment: Use existing (environment) Press Finish. 34. In the editor of Main, select the default animation of the bomb, drag it to (0,0) where all other agent animations are already located, and check the Show in 3D scene checkbox in its properties. 35. Open the editor of Bomb, delete the default animation and replace it with the Oval dragged from the 3D palette (this will actually be a cylinder). Place the oval at exactly (0,0), set its Fill color to black, Line color to No line, radius to 2 pixels, and Z-height to 5. The Big Book of Simulation Modeling Featuring AnyLogic 41 The AnyLogic Company www.anylogic.com
The interaction between the bomber, the bomb, and the target building setXYZ( bomber.getX(), bomber.getY(), bomber.getZ() ); Entry action: moveTo( target.getX(), target.getY(), target.getZ() ); Triggered by: Agent arrival Action: deliver( "You are destroyed", target ); get_Main().remove_bomb( this ); Bomb agent Building agent a semi-transparent cube (Z-height: 10 ) Dynamic Visible: destroyed Agent property page: Bomber agent Agent property page: The Big Book of Simulation Modeling Featuring AnyLogic 42 The AnyLogic Company www.anylogic.com 36. In the Agent page of the Bomb properties, select the Continuous 3D space type. 37. Create two parameters of the Bomb agent: bomber of type Bomber and target of type Building. 38. Draw the statechart of the Bomb as shown in the Figure. As you can see, the bomb sends the message "You are destroyed" to the building when it reaches the building, and then immediately destroys itself. We are using the method deliver(), which delivers the message instantly (within the same event) instead of the method send(), which does it in a separate event (still at the model time though). This is because the message sent by the method send() will be discarded once the sender agent ceases to exist. Now we will implement the building's reaction on the bomb explosion. We will add a Boolean flag destroyed and add some animation. 39. Open the editor of Building and add a Boolean variable destroyed with initial value false. 40. Open the Agent page of the Building properties and type this code in the On message received field: destroyed = true; We are not analyzing the content of the message because, for now, the only message the building may receive is the message from a bomb. 41. Create a cube around the house animation of semi-transparent red color (use the Rectangle shape from the 3D palette). On the Dynamic page of the cube properties type destroyed in the field Visible. This way the red cube will appear around the house once the house is bombed. The missing piece is the bomber's decision to drop a bomb. We could do it exactly upon arrival to the point directly above the target building (in the action of the transition from ToTarget and Away states). However, according to our problem definition, the bomber can do it once it gets within 500 m ground distance from the target and, at most, at 2 km altitude. To detect the moment when these conditions hold, we will introduce time steps in our model and let the bomber evaluate these conditions on each time step. The time will now be a mixture of synchronous ticks and asynchronous events. Add time steps and make the bomber sense the attack conditions: 42. Open the editor of Main, select the environment object, and check the Enable steps checkbox in its properties. In the Step duration field, type second(). The default step size is one time unit (one minute in our case). However, given the velocity of the aircraft is as high as 600 km/h, under the default time step setting, the The Big Book of Simulation Modeling Featuring AnyLogic 43 The AnyLogic Company www.anylogic.com bomber will be checking the attack conditions every 10 km, which is unacceptable if we want to detect reaching 500 m ground distance from the building. Therefore, we set the time step to one second so that the bomber will perform the checks every 167 m (10 km divided by 60). Alternatively, to using the function second(), we could write 1./60. 43. Open the editor of Bomber and add a condition-triggered event as shown in the Figure. Notice that, when creating a new bomb, the bomber passes itself (this) and the target building to the bomb constructor parameters. Another thing to notice is the calculation of the ground distance, i.e., the distance of the projections of the two objects on the ground. The bomber uses the function distanceTo(), but provides its own Z-coordinate instead of the building Z-coordinate. 44. In the Agent page of the Bomber properties, find the On step field and type onChange(); 45. Run the model. Use the 3D view and zoom in to watch how the bombs are dropped and destroy the buildings. You probably have noticed that, although all buildings are destroyed by the very first bomber attack, the new bombers are, nevertheless, sent to the target area. This happens because the mission assignment is done regardless of the state of the target assets. We will now fix it. 46. Open the editor of Main and modify the Action code of the assignMission event: for( Building bldg : buildings ) { //destroyed buldings are ignored if( bldg.destroyed ) continue; //look up if a bomber is handling it already
Now the bombers are not sent to bomb the already destroyed buildings. The last phase of the model development is to add radars and radar-guided missiles. Both will be agents in our model. Similar to the bomber aircraft, the radar will scan the air within its coverage zone on each time step (every second). Once it detects a bomber and is able to guide yet another missile, it will launch one. The missile, similar to the bomb, will engage with the bomber (the radar will pass the bomber to the missile constructor), and explode once it gets as close as 300 m to the bomber. As you can see, we are extensively using discrete time (clock ticks) in this model to detect when certain geometric conditions defined over moving objects become true. The Big Book of Simulation Modeling Featuring AnyLogic 44 The AnyLogic Company www.anylogic.com Alternative approach would be to analytically calculate the exact moments of time when a bomber gets close enough to the target to drop a bomb, enters the radar coverage zone, or when a missile catches up with the bomber. Given linear or piecewise-linear trajectories and constant velocities of bombers, this would be a task of medium (high school level) mathematical complexity and you can do it as an exercise. The resulting model will be more accurate and the simulation will be much more efficient. However, the approach we chose (recalculation of the geometric conditions on each time step) is simpler, no analytical skills are required at all, and it is also more general. It will work regardless of the type of movement. Create the Radar agent class: 47. In the Projects tree right-click the model (topmost) item and choose New active object class from the context menu. Give the new class the name Radar. 48. The editor of Radar opens and its properties are displayed. In the properties window check the Agent checkbox to enable the agent functionality. 49. Open the Agent page of the Radar properties. Choose Continuous 3D space type. Uncheck the Environment defines initial location checkbox. 50. Choose the animation shape for the radar. Among the standard 3D objects available in AnyLogic 6.9, you can use, for example, the Truck object (let it be the mobile air defense system). Drag it to the coordinate origin of the Radar editor and reduce the scale to 25%. 51. Add two parameters to the Radar class: x and y, both of type double. These will be the coordinates of the radar. 52. Open again the Agent page of the radar properties and specify the initial coordinates of the radar: (x,y,0). Notice that this time we did not use the Agent population wizard to create the Radar class but used the New active object class command instead. This is because we do not plan to have a population of radars. We will create two individual radars in the model and specify individual parameters to each of them. Create two radars in the Main class: 53. Open the editor of Main and drag the Radar item from the Projects tree there. An instance of the Radar agent class is created in Main and its presentation is placed at the (0,0) point. Name this radar radar1. 54. In the properties of radar1 type environment in the Environment field. This adds the radar1 agent into the same environment where bombers, bombs, and buildings already are. The Big Book of Simulation Modeling Featuring AnyLogic 45 The AnyLogic Company www.anylogic.com 55. Ctrl+drag the radar1 agent to create its copy. The name of the copy will be radar2, which is OK for us. In the properties of radar2 press the Create presentation button. 56. Specify the coordinates of the radars by providing the values for their x and y parameters. Place radar1 at, for example, (300, 350) and radar2 at (350,200). 57. Run the model and make sure the radars are located on the bombers route. Now we will create the Missile class. The missile will be very similar to the bomb. It will have a limited lifetime from launch to explosion and will be parameterized by the target aircraft and the guiding radar. Unlike the bomb, however, the missile will periodically be adjusting its trajectory to catch up the moving target. Create the Missile agent class: 58. In the editor of Main, create yet another Agent population. In the wizard, make these settings: Agent class name: Missile Population name: missiles Initial population size: 0 Environment: Use existing (environment) Press Finish. 59. In the editor of Main, select the default animation of the missile, drag it to (0,0) where all other agent animations are already located, and check the Show in 3D scene checkbox in its properties. 60. Open the editor of Missile, delete the default animation, and replace it with the Oval dragged from the 3D palette (just like in the bomb, this will be a cylinder). Place the oval at exactly (0,0), set its Fill color to royalBlue, Line color to No line, radius to 1 pixel, and Z-height to 20. 61. In the editor of Missile, right-click the oval (it is now a small object at the coordinate origin) and choose Grouping | Create a group from the context menu. This creates a new group and adds the oval to it. In the Dynamic property page of the group, type PI/2 in the Rotation Y field. The reason for placing the cylinder in the group is that, by default, the cylinder stands on the (X,Y) plane and grows upwards along the Z axis. When the agent moves, however, its animation moves to the right along the X axis. Therefore, we need to rotate the missile body around the Y axis so that it is oriented towards the movement direction. The group (unlike individual 3D shapes) can be rotated around all three axes, and therefore is the universal method of changing the orientation of 3D shapes at runtime. 62. On the Agent page of the Missile properties, select the Continuous 3D space type. The Big Book of Simulation Modeling Featuring AnyLogic 46 The AnyLogic Company www.anylogic.com 63. On the same page, set the Velocity of the missile to 150. Remember that the model time units are minutes and the chosen space scale is 10 pixels per kilometer. The missile therefore will cover 150/10 = 15 kilometers in one minute, which gives us the desired 900 km/h. 64. Create two parameters of the Missile agent: radar of type Radar and target of type Bomber. 65. Draw the statechart of the Missile, as shown in the Figure.
The internals of the Missile object It makes sense to discuss the missile trajectory adjustment. The transition Adjust executes periodically every 0.01 minute. It has no action, but it makes the missile statechart re-enter the Flying state. Therefore, the entry action of the Flying state also executes every 0.01 minutes and makes the missile head the current position of the bomber. This way of navigation is, of course, far from ideal, but it will give us a nice curved trajectory of the missile. The condition of the AtTarget transition will also be re- evaluated each 0.01 minutes, or every 150 meters, which is good enough to detect the explosion moment. The same is true for the transition OutOfRange, which is responsible for checking if the missile leaves the radar coverage area and can no longer be guided. Program the missile launch and guiding: 66. Open the editor of Radar and add a parameter Range of type double and default value 65. This is the radius of the radar coverage zone (65 pixels equals 6.5 km). Here is the group containg the cylinder (the missile body animation) Action: setXYZ( radar.getX(), radar.getY(), radar.getZ() ); Entry action: moveTo( target.getX(), target.getY(), target.getZ() ); Timeout: 0.01 Triggered by: Condition: distanceTo( target ) < 3 Action: deliver( "You are destroyed", target ); get_Main().remove_missiles( this ); Triggered by: Condition: distanceTo( radar ) > radar.range The Big Book of Simulation Modeling Featuring AnyLogic 47 The AnyLogic Company www.anylogic.com 67. Create a Collection inside the Radar. Name it guidedmissiles and set the element type to Missile. This collection will contain the missiles currently guided by the radar. 68. Open the Agent page of the Radar properties and type this code in the On step field: for( Bomber b : get_Main().bombers ) { //for all bombers in the air if( guidedmissiles.size() >= 2 ) //if can't have more engagements, do nothing break; if( distanceTo( b ) < range ) { //if within engagement range //already engaged by another missile? boolean engaged = false; for( Missile m : get_Main().missiles ) { if( m.target == b ) { engaged = true; break; } } if( engaged ) continue; //proceed to the next bomber //engage (create a new missile) Missile m = get_Main().add_missiles( this, b ); guidedmissiles.add( m ); //register guided missile } } Scanning of the zone is done on every time step, i.e., every second (remember that the step size is defined in the environment object). The radar iterates across all bombers in the model and then across all missiles in the air. If a bomber without a missile is found, a new missile is launched. 69. Open the editor of Missile and add one more line to the Action of the Exploded final state: radar.guidedmissiles.remove( this ); //unregister with the radar get_Main().remove_missiles( this ); //delete self We need to let the radar know that the missile has reached the aircraft and exploded so that the radar can launch and guide another missile. 70. Run the model. In the 3D view see how the missiles are launched, catch up with the bombers, and disappear. So far the missiles do no harm to the bombers because we have not programmed the bombers reaction on the missile explosion. This will be the last bit of our model. The Big Book of Simulation Modeling Featuring AnyLogic 48 The AnyLogic Company www.anylogic.com Model the bomber destruction: 71. Modify the statechart of the Bomber agent, as shown in the Figure. Draw a composite state around the ToTarget and Away states, and then add a transition triggered by the message You are destroyed leading to another final state. 72. Run the model.
The modified statechart of the Bomber Depending on the layout drawn, some bombers may be intercepted, and some may be able to complete their mission. You can perform various interesting experiments with this model. For example, you can optimize the location of radars, determine the time interval between the bombers, (which secures at least some of them), and so on. On the animation side, you can visualize the radar coverage area. For that, you will need a 3D sphere object of semitransparent color, which you can add to the animation or radar and scale dynamically according to the current value of the parameter range. Triggered by: message You are destroyed Action: get_Main().remove_bombers( this );
The Big Book of Simulation Modeling Featuring AnyLogic 49 The AnyLogic Company www.anylogic.com Example: Agent leaving a movement trail In the models where agents represent moving physical objects, we sometimes need to see the trajectory of the agent movement. In this example, we will create a very simple model with moving agents using the standard agent based template and then add a movement trail to the agents. Create an agent based model with agents moving randomly: 1. Press the New button on the toolbar. In the New model wizard, enter the model name, and on the next page choose Use template to create model option and Agent Based model template. Press Next. 2. In the next page of the wizard (Setup agent properties), set the Initial number of agents to 3 and press Next. 3. In the next page of the wizard (Configure space and animation properties), leave the default settings and press Next. 4. In the next page of the wizard (Configure network properties), check the checkbox Add random movement and press Finish. A new agent based model is created and the editor of its Main object opens. 5. Run the model. Three agents are moving randomly within the space 500x500 pixels. In the model created by the wizard, each agent chooses a random point in the 500x500 space and starts moving there see the Startup code of Person. Upon arrival, an agent chooses another random point and continues to the new target, this is defined in the On arrival field, in the Agent page of the Person properties. Create a polyline that will be used to show the agent trail: 6. In the Projects tree double-click the agent class Person to open its editor. 7. Open the Presentation palette, drag the Polyline object and drop it anywhere in the editor of Person. 8. In the General page of the polyline properties set the name to trail. 9. In the Dynamic page of the polyline properties set: X: -getX() Y: -getY() 10. Right-click the polyline and choose Grouping | Create a group from the context menu. A new group is created; it contains the polyline trail. 11. In the Dynamic page of the group properties set Rotation to: getRotation() and enter the following code in the On draw field in the same page: trail.setPoint( trail.getNPoints()-1, getX(), getY() ); 12. Move the group to the coordinate origin in the editor of Person. The Big Book of Simulation Modeling Featuring AnyLogic 50 The AnyLogic Company www.anylogic.com
Trail of the agent is implemented as a polyline in a group in the agent animation We need the trail of the agent to be fixed in the space where the agents move. The polyline, however, belongs to the presentation of the agent, therefore it will move with the agent. To compensate the movement, we set the coordinates of the polyline opposite to the coordinates of the agent. Similarly, we need to compensate the rotation of the agent. This is done by adding the polyline to a group and rotating the group in the direction opposite to the agent. Finally, we need to continuously extend the last segment of the trail to the current position of the agent. Therefore, whenever the group with the polyline is drawn, we set the coordinates of the last point of trail to the current position of the agent. Set the initial shape of the trail and add a new segment on each arrival 13. In the Startup code field of the General page of the Person properties, enter the following code before the auto-generated call of moveTo: //leave two points in the polyline trail.setNPoints( 2 ); //place both at the current agent position trail.setPoint( 0, getX(), getY() ); trail.setPoint( 1, getX(), getY() ); //the call of moveTo generated by the wizard follows 14. In the Agent page of the Person properties, enter the following code in the On arrival field before the auto-generated call of moveTo: int n = trail.getNPoints(); trail.setPoint( n-1, getX(), getY() ); //set the last point to the current position trail.setNPoints( n+1 ); //add a new point trail.setPoint( n, getX(), getY() ); //place it at the same position for now //the call of moveTo generated by the wizard follows 15. Run the model. The three agents are leaving their trails as they move. You can apply the virtual time mode for a short time to let the agents move long distances. Group: On draw: trail.setPoint( trail.getNPoints()-1, getX(), getY() ); Rotation: -getRotation() Polyline: Name: trail X: -getX() Y: -getY() The Big Book of Simulation Modeling Featuring AnyLogic 51 The AnyLogic Company www.anylogic.com
The three agents leave their trails When the model starts, the trail should have two points (one segment): the start point at the initial position of the agent and the end point that will move as the agent moves, initially at the same place. Upon arrival, we need to close the current segment of the trail and start a new one. This is done in the On arrival code. Finally, we will add a text displaying the distance traveled by the agent. Add text displaying the distance traveled 16. Open the editor of Person. Open the Presentation palette, drag the Text object, and drop it nearby the person shape. 17. In the Dynamic page of the text properties type trail.length() in the Text field. 18. Right-click the text shape and choose Grouping | Add to existing group from the context menu. Click on the highlighted group, which you have previously placed at the coordinate origin. 19. Run the model. The length of the trail equals the distance traveled by the agent, so we can simply use the method length() of the polyline. By adding the text shape to the group, we prevent the text from rotating with the agent. The Big Book of Simulation Modeling Featuring AnyLogic 52 The AnyLogic Company www.anylogic.com ? The length of the trail implemented that way is unlimited, and after a long period of simulation the picture may become less informative and may slow down the animation. How would you modify the model so that the trail will contain the maximum 50 most recent segments? How would you keep the information on the distance traveled correct? Continuous space API The following methods, defined in the class AgentContinuous, are common for agents living in 2D, 3D, and GIS continuous spaces: double distanceTo( Agent other ) calculates the distance from this agent to another agent. AgentContinuous getNearestAgent( java.lang.Iterable<? extends AgentContinuous> agents ) returns the nearest agent from the given collection. double getRotation() returns the current rotation angle (in radians) of the agent in XY plane. double getTargetX() returns the X coordinate of the target location when moving, otherwise the current X coordinate of the agent. double getTargetY() returns the Y coordinate of the target location when moving, otherwise the current Y coordinate of the agent.. double getVelocity() returns the velocity of the agent regardless of whether the agent is moving or not. double getX() returns the current (up-to-date) X coordinate of the agent. double getY() returns the current (up-to-date) Y coordinate of the agent. boolean isMoving() tests whether the agent is currently moving. void moveToNearestAgent( java.lang.Iterable<? extends AgentContinuous> agents ) starts movement towards the nearest agent from the given collection. void setVelocity( double v ) changes the velocity of the agent (in 2D and 3D space the velocity is measured in pixels per model time unit, and in GIS-based environments in m/s). void stop() stops movement. double timeToArrival() returns the time to arrival to the target location if the agent is moving or 0. The following functions are specific to 2D space; they are defined in the class AgentContinuous2D: double distanceTo( double x, double y ) calculates the distance from this agent to a given point. void jumpTo( double x, double y ) instantly places the agent at a given location. The Big Book of Simulation Modeling Featuring AnyLogic 53 The AnyLogic Company www.anylogic.com void moveTo( double x, double y ) starts movement towards the given target location. void moveTo( double x, double y, Path2D path ) starts movement towards the given target location along a given path (polyline or line). void setXY( double x, double y ) sets the coordinates of the agent location; should be used for initial setup only; assumes the agent is not moving. These are the functions of AgentContinuous3D specific to 3D space: double distanceTo( double x, double y, double z ) calculates the distance from this agent to a given point. double getTargetZ() returns the Z coordinate of the target location when moving, otherwise the current Z coordinate. double getVerticalRotation() returns the current vertical rotation angle of the agent. double getZ() returns the current (up-to-date) Z coordinate of the agent. void jumpTo( double x, double y, double z ) instantly places the agent into a given location. void moveTo( double x, double y, double z ) starts movement towards the given target location. void moveTo(double x, double y, double z, Path3D path ) starts movement towards the given target location along a given path. void setXYZ( double x, double y, double z ) sets the coordinates of the agent location. Networks and links In many (but not all) agent based models, agents have relationships with each other. If agents are people, the relationships may be friendship, kinship, sexual relationships, relationships at work, or physical contacts. If agents are IT infrastructure objects, they may be physical or wireless links. In a supply chain model, they may be producer- consumer relationships, and so on. AnyLogic offers several ways to model agent relationships. They are: Standard and user-defined networks with uniform bidirectional links (friendship, common interest, information exchange) Custom unidirectional links with possibly different meanings (child-parent, boss-subordinate, client-salesman, workstation-server, producer-consumer) Hierarchical model architectures (company-employee, town-habitant, group- member) The Big Book of Simulation Modeling Featuring AnyLogic 54 The AnyLogic Company www.anylogic.com Using ports to connect agents Do not think of links and networks as things that are set up once and are not able to change. As the model is running, you can modify the relationships between the existing objects, add new objects and establish their connections, delete objects, restore the standard network types, and so on. Standard networks AnyLogic supports several standard types of networks and you are able to modify them and create your own networks. The standard networks are based on uniform bi- directional connections between agents. The standard network types are (see the Figure): Random. An agent is connected (on average) to a given number of randomly selected other agents. Distance-based. Two agents are connected if the distance between them is within a given range. This network type applies only to agents in continuous space. Ring lattice. Regardless of the space and layout type, the agents are considered to be evenly distributed on a virtual ring. An agent is connected to a given number of agents closest to it on the ring. Small world. Same virtual ring is assumed. An agent is connected to a given number of agents, most of which are its closest neighbors, but a certain percentage of connections are long-distant. This network is constructed from a pure ring lattice by re-wiring some connections to long-distant. Examples of small world networks are social networks or Internet connectivity network. An interesting property of a small world network is that the number of hops between two randomly chosen agents is proportional to the logarithm of the number of agents, which means even strangers are linked via mutual acquaintances [Wikipedia]. Scale free. Formally, this is a network where the distribution of number of agents connections follows a power law, i.e., the fraction of agents with k connections is k - , being the network parameter. In such networks, some agents are hubs with a lot of connections, and some are hermits with few connections. Some real world networks are claimed to be scale-free, such as professional collaboration networks, computer networks, or airline networks [Wikipedia]. A good visual demonstration of the standard network types is available in the example model Agent Network and Layout Demo included in the AnyLogic How-to example set. The Big Book of Simulation Modeling Featuring AnyLogic 55 The AnyLogic Company www.anylogic.com
Standard and custom network types Ring lattice with 4 links per agent. 10 agents on a ring layout Random network with 3 links per agent. 50 agents on a ring layout Distance-based network, range 50. 100 agents on a random layout Small world network with 3 links per agent, 75% of neighborhood links. 50 agents on a ring layout Scale free network with m = 4. 50 agents on a ring layout Custom network (50 initial agents small world, spring mass layout, 200 more agents linked to them) These guys are hubs The Big Book of Simulation Modeling Featuring AnyLogic 56 The AnyLogic Company www.anylogic.com In AnyLogic, networks are constructed with the help of the Environment object and can connect agents of different types (different classes), given they are members of the same environment. To create a network: 1. Select the Environment object and open the Advanced page of its properties. Select the network type and specify the parameters of the network. This has the following effect. The agents that are created at the model startup get connected according to the given network parameters. If you add more agents later on, or delete existing agents, the network will not automatically reconfigure itself unless you call the function applyNetwork() of the Environment object. Example: Periodic repair of a standard network In this example, we will create a population of agents in a rectangular 2D space and connect them based on inter-agent distances. We will let the agents die, be born, and move, and will set up a periodical repair of the network. We will also show how to visualize links between the agents. Create agents connected with a distance-based network: 1. Create a new model. Drag the Agent population object from the General palette to the editor of Main. 2. On the first page of the wizard, leave the default settings. On the second page, set the space size to 500 by 400 and set the Network type to Distance based with parameter 50. 3. Run the model. The agents are randomly distributed across the area and they are connected, but we do not see the links yet. Visualize the links between agents: 4. Open the editor of Person (this is the default agent class name given by the wizard). Drag a Line object from the Presentation palette and place the beginning of the line to the coordinate origin. 5. Open the Dynamic page of the line properties and type: Replication: getConnectionsNumber() Rotation: -getRotation() dX: getConnectedAgent( index ).getX() - getX() dY: getConnectedAgent( index ).getY() - getY() 6. Run the model. Now you can see the links and make sure the connections are distance-based. This works as follows. The number of line copies is set to the number of connections the agent has. The beginning of each line is at the coordinate origin (at the center of The Big Book of Simulation Modeling Featuring AnyLogic 57 The AnyLogic Company www.anylogic.com the agent). The end of the line is placed at the center of the connected agent by setting dX and dY to the differences of coordinates of this and the other agent. The rotation of the line is set to compensate the rotation of the agent, which moves with the right- hand side in front. As the standard connections are bidirectional, for each connection, there will be not one, but two lines displayed one on top of the other, which is fine for our simple example. Should you need to display only one line, you can set the color of the line to null if getIndex() or hashCode() of the other agent is greater than getIndex() of this agent. Make the agents move: 7. Open the editor of Person and write this statement in the Startup code: moveTo( uniform(500), uniform(400) ); 8. Open the Agent page of the Person properties and write exactly the same code in the On arrival field. This will make the agents constantly move in the space. 9. Run the model. See how the agents move and how the links, after a while, cease to reflect the distances between the agents. Make the agents die and be born: 10. Open the editor of Person and add an Event object from the General palette. Name the event death, leave the Timeout trigger type and set the Mode to User control. Set the timeout value to uniform( 60, 100 ). In the event Action write: get_Main().remove_persons( this );. This event will not be scheduled unless we explicitly do it. 11. In the Startup code of Person, add the line: death.restart();. This will schedule the death of the agent within 60-100 time units from its birth. We could not use a Timeout event with the mode Occurs once because then the occurrence time is absolute (counted from the model startup), and the agents born later than time 100 will never die. 12. Run the model. Wait for all agents to die. 13. Open the editor of Main and add an Event object with the name birth. Set the Trigger type to Rate with rate value 1. The action of the event should be add_persons();. Now, on average, every time unit a new agent will be born. 14. Run the model. After a while the number of agents stabilized at about 80 (do you know why, by the way?), but there are no links between them because no one is taking care of connecting the new born agents. The last bit of the model is the periodic repair of the The Big Book of Simulation Modeling Featuring AnyLogic 58 The AnyLogic Company www.anylogic.com distance-based network. We can implement this as yet another periodic event at the Main level. Setup the periodic repair of the network: 15. Add another event in Main object with the name repairNetwork. Make it a Cyclic Timeout with Recurrence time 5. The Action of the event will be: environment.applyNetwork();. This will rewire the agent connections according to the network type specified in the environment object (the network type can also be changed dynamically if needed). 16. Run the model. See how the network is repaired every 5 time units.
Distance-based network with periodic repair The initial layout and network at time = 0 The network at time = 4.9 just before repair The network at time = 5.1 just after repair The network at time = 4620.08 just after repair
Too long link Missing link Link deleted Link created The Big Book of Simulation Modeling Featuring AnyLogic 59 The AnyLogic Company www.anylogic.com Example: Custom network built using standard connections We will first create a standard network of 20 agents connected in a ring, and then add 60 more agents and connect 3 new agents to each initial agent. We will then apply a spring-mass layout to better visualize our network. Create 20 agents connected in a ring: 1. Create a new model. Drag the Agent population object from the General palette to the editor of Main. 2. On the first page of the wizard, set the Initial population size to 20. On the second page, set the Network type to Ring lattice. Click Finish. 3. Select the environment object in the editor of Main and set the Layout type to Ring on the Advanced page of its properties. 4. Repeat steps 4-6 of the previous model, Periodic Repair of a standard network, to visualize the links. 5. Run the model. The agents are evenly distributed on a ring and linked (by default, there are two connections per agent; you can change this in the properties of the environment).
A simple ring lattice the backbone of the future custom network The Big Book of Simulation Modeling Featuring AnyLogic 60 The AnyLogic Company www.anylogic.com Dynamically create 100 additional agents and link them to the existing agents: 6. Add this Startup code in the Main object: for( int i=0; i<20; i++ ) { //iterate across the initial 20 agents Person hub = persons.get(i); //for each initial agent (a "hub") for( int j=0; j<3; j++ ) { //create 3 more agents Person sub = add_persons(); sub.connectTo( hub ); //and link to the hub } } 7. Run the model. The new agents are randomly spread across the area and linked to the initial 20 agents. 8. Add two more lines at the end of the Startup code of Main (note that after you have set the new layout type you need to call the function applyLayout() to actually relocate the agents): //change the layout type environment.setLayoutType( Environment.LAYOUT_SPRING_MASS ); //and apply the new layout environment.applyLayout(); 9. Run the model and see the new spring mass layout. The key function used in this example is the agents function connectTo(), which creates a new bidirectional connection between this and another agent. The standard networks are built of connections of the same type.
The custom network before and after applying the spring mass layout 60 randomly located agents were added and linked to the initial 20 The Spring Mass layout was applied to the new network The Big Book of Simulation Modeling Featuring AnyLogic 61 The AnyLogic Company www.anylogic.com Fully connected networks It does not make a lot of sense to use connections to establish a fully connected network (one where everybody is connected to everybody else). Building and maintaining such a network may require significant computer resources, whereas you can easily use AnyLogic agent, environment, or replicated object API to iterate through the collection of agents, or to access a particular or a random agent. For example, if all of the agents of the class Person are elements of a single replicated object (like people embedded in Main class), to access the set of agents from inside one of them, you can use the function: ActiveObjectList< Person> getReplicatedList() returns the list you can iterate through, access a particular agent by index by calling its get(i) function, query statistics, or pick a random agent by calling random(). If the set of agents consists of several subsets (for instance, you have two types of agents or two replicated objects), but all of them live in the same environment, you can use the API of the Environment object. From inside an agent you can call: getEnvironment().getAgentCollection() returns the collection of all agents in the environment, again, with possibilities of iteration and access by index. getEnvironment().getRandomAgent() returns a randomly chosen agent in the environment. When other agents are accessed via the environment, the returned class will be one of the generic classes, like AgentContinuous2D, so you may need to cast (convert) it to the actual class to access its specific properties. Please also note that all those functions may return the calling agent, so this may need to be checked. Network and layout-related API The following methods are defined in the class Agent, and thus are common for all types of agents: LinkedList<Agent> getConnections() returns the list of all connected agents, or null if there have been no connections established. int getConnectionsNumber() returns the number of connected agents. Agent getConnectedAgent( int index ) returns the connected agent with the given index. Note that the method returns the generic type Agent and you may need to cast it to a specific type. connectTo( Agent a ) adds a given agent to the connections of this agent and vice versa. The Big Book of Simulation Modeling Featuring AnyLogic 62 The AnyLogic Company www.anylogic.com boolean isConnectedTo( Agent a ) tests if this agent is connected to a given agent. boolean disconnectFrom( Agent a ) disconnects this agent from a given other agent, returns false if the agents were not connected. disconnectFromAll() disconnects the agent from all other agents. In addition, the Environment class offers this API: applyNetwork() discards all existing connections and establishes new connection network according to the current network settings. getNetworkType() returns the network type, one of: NETWORK_ALL_IN_RANGE, NETWORK_RANDOM, NETWORK_RING_LATTICE, NETWORK_SCALE_FREE, NETWORK_SMALL_WORLD, NETWORK_USER_DEFINED; remember that, as long as the constants are defined in the class Environment, you should include the prefix Environment. when you use them. setNetworkRandom( double connectionsPerAgent ) sets the network type to NETWORK_RANDOM with a given average number of connections per agent. setNetworkRingLattice( int connectionsPerAgent ) sets the network type to NETWORK_RING_LATTICE lattice. setNetworkScaleFree( int m ) sets the network type to NETWORK_SCALE_FREE. setNetworkSmallWorld( int connectionsPerAgent, double neighborLinkProbability ) sets the network type to NETWORK_SMALL_WORLD. setNetworkUserDefined() sets the network type to NETWORK_USER_DEFINED. The 2D-specific class Environment2D additionally has these functions: int getLayoutType() returns the current layout type, one of LAYOUT_ARRANGED, LAYOUT_RANDOM, LAYOUT_RING, LAYOUT_SPRING_MASS, LAYOUT_USER_DEFINED. Again, the prefix Environment. is needed when you use these constants. setLayoutType( int type ) sets the layout type. applyLayout() relocates the agents according to the selected layout type. Unidirectional, temporary, and other custom types of links Uniform bidirectional connections are not always the desired way of modeling the relationships of real world objects. For example, I may listen to somebodys opinion, but that person does not necessarily know me, thus our relation is asymmetric. In many cases, connections must have specific names so that we can distinguish between them, like Husband, Wife, Kids, Colleagues, Friends, Boss, and so on. The links Wife and Husband are naturally unidirectional, while the link Spouse is bidirectional. In AnyLogic you can establish and maintain all kinds of connections using references to agents stored in plain variables or collections. This can be considered as an The Big Book of Simulation Modeling Featuring AnyLogic 63 The AnyLogic Company www.anylogic.com alternative or as an addition to the standard networking service provided by the Environment object with the functions getConnections(), connectTo(), etc. Consider the Figure. Here kinship between individuals is modeled by custom links. All kinds of kinship are implemented as pairs of unidirectional links, either symmetric (like Spouse) or asymmetric (like Father and Kid).
Custom links used to model kinship relations Spouse Kids Mother Father Spouse Kids Mother Father Spouse Kids Mother Father Spouse Kids Mother Father Spouse Kids Mother Father Spouse Kids Mother Father Spouse Kids Mother Father Spouse Kids Mother Father Spouse Kids Mother Father Spouse Kids Mother Father Spouse Kids Mother Father A reference (a unidirectional link) to a another agent, may be empty (null) A collection of references, may contain 0, 1, or multiple elements The Big Book of Simulation Modeling Featuring AnyLogic 64 The AnyLogic Company www.anylogic.com Custom links are a very flexible mechanism for creating specific networks, but the modeler who uses them becomes responsible for maintaining the network consistency. For example, when an agent dies or is otherwise deleted from the model, all references to that agent should be cleared throughout the model. Example: Kinship modeled using custom links We will create a fairly simple agent based population model where people (males and females) are born, grow up, marry, have kids, get old, and die. We will maintain the kinship relations, as shown in the Figure, by means of custom links (references) to other agents. We will also take care of the kinship network consistency. Create the initial population of 300 people and create custom links: 1. Create a new model and use the Agent population wizard to create 300 agents. Set the name of the population to people. 2. Open the editor of Person and replace the default animation with the circle of 2 pixel radius located at the coordinate origin. Name the circle circle. 3. In the Person class, create a Boolean parameter male with the initial value randomTrue( 0.5 ). The birth probability for males and females will be the same. 4. From the General palette, drag the Variable and name it mother. Set the type of the variable to Person. This will be the reference (link) to the persons mother. Agents in the initial population will not have live parents, and this variable will be null. 5. Copy the variable to create two more links: father and spouse. 6. Drag the Collection object and name it kids. Set the element type of the collection to person. This will be a collection of references to the persons children, initially empty. 7. Open the Agent page of the Person properties and deselect the checkbox Environment defines initial location (parents will take care of the initial location of their kids). 8. On the same page set the Velocity of the agent to 100. We will use movement to visualize the family formation. 9. Draw two lines with the start points at the coordinate origin of the Person and dynamic properties, as shown in the Figure. These lines will visualize the kinship relations of the person. Note that we are only visualizing the relation wife and kid, and we do not draw the lines for husband and father/mother to avoid duplication. 10. Run the model to make sure the 300 people are in place and there are no links between them so far. The Big Book of Simulation Modeling Featuring AnyLogic 65 The AnyLogic Company www.anylogic.com
Links to the agents in kinship and their visualization Add persons behavior: 11. Draw the statechart, as shown in the Figure. 12. Specify the triggers and actions of the transitions as explained below. 13. Run the model. As you can see, the person may reach the Adult state at any age between 16 and 25. Then, for another 15-25 years the person may create a family and have kids. Then he or she becomes a Senior and dies after yet another 15-25 years. Transition LookForWife and its branches Found and NoLuck. The transition has a Guard that is set to male, which means that only a male person can execute it. The transition is triggered at the Rate of 1, i.e., on average once a year. The Action of the transition is: //look for wife on average once a year for( Person p : get_Main().people ) { //search all people if( ! p.male && p.statechart.isStateActive( NoFamily ) ) { //if a person is female and not married, marry her spouse = p; Boolean parameter, initially: randomTrue( 0.5 ). Variables of type Person links to other agents Collection with element type Person Visualization of my wife relation Visualization of my kid relation The Big Book of Simulation Modeling Featuring AnyLogic 66 The AnyLogic Company www.anylogic.com send( this, p ); //and let her know break; //exit the loop } } If a single adult male finds a single adult female, he marries her, which is implemented by setting the males spouse link and sending the wife the message containing a reference to the male himself (this). The condition of the branch Found is spouse != null, otherwise the branch NoLuck takes the male back to the NoFamily state. The transition GetMarried has a Guard set to ! male so that it applies to females only. It is triggered by a message of type Person (remember that, when getting married, a male sends a reference to himself to the chosen wife). The Action of the transition is: spouse = msg; //remember husband and move to him moveTo( spouse.getX() + 10, spouse.getY() ); This means the wife sets up the reference to the husband and moves to his location.
The statechart of a person. Other transitions are explained in the main text The transition NewKid is an internal transition inside the state Family. It is guarded by the expression ! male && spouse != null, so that only females with live husbands can have Entry action: circle.setFillColor( yellowGreen ); Entry action: circle.setFillColor( male ? blue : red ); Entry action: circle.setFillColor(goldenRod ); Triggered by: Timeout: uniform( 16, 25 ) Triggered by: Timeout: uniform( 15, 25 ) Triggered by: Timeout: uniform( 15, 25 ) Males only Females only The Big Book of Simulation Modeling Featuring AnyLogic 67 The AnyLogic Company www.anylogic.com new kids. The transition is taken at the Rate of 0.1, i.e., on average once every 10 years. The action is: //a kid is born on average every 10 years Person kid = get_Main().add_people(); //new person kids.add( kid ); //add to my kids spouse.kids.add( kid ); //add to husband's kids kid.mother = this; //assign mother to the kid kid.father = spouse; //assign father //place the kid nearby the father kid.jumpTo( spouse.getX() + uniform( -10, 10 ), spouse.getY() + uniform( -10, 10 ) ); A newborn needs some wiring to be done. We establish the cross-links between the kid and his parents, and place the kid near his father. And finally, the state Dead. Here we delete the person from the model, but, before he/she disappears, we must clean all references to the person to maintain the network consistency. The action of the state Dead is: //clean up all links to myself //from kids for( Person kid : kids ) { if( male ) kid.father = null; else kid.mother = null; } //from spouse if( spouse != null) spouse.spouse = null; //from parents if( father != null ) father.kids.remove( this ); if( mother != null ) mother.kids.remove( this ); //and die get_Main().remove_people( this ); Note that, before addressing a spouse or parent, we need to check if the other person is alive. The model shows interesting behavior, see the Figure. ? What can be changed in the model to ensure the population growth (give options)? ? In the current model, brothers and sisters can possibly marry. How would you modify the model to prevent this? The Big Book of Simulation Modeling Featuring AnyLogic 68 The AnyLogic Company www.anylogic.com
Behavior of the Kinship model Time = 0. Initially all 300 people are newborn 30 years later. Kids have grown up and formed families. Some already have kids Year 55. Second generation is forming families at this time, moving out of their parents homes Year 75. The first generation is mostly gone Year 400. The population is again 300 people Year 500. This people are not going to survive A family The Big Book of Simulation Modeling Featuring AnyLogic 69 The AnyLogic Company www.anylogic.com A note on vertical links in hierarchical models Note that if the model is hierarchical, for example, the agent of type Department contains agents of type Employee, and is itself embedded into the agent of type Company, you do not have to establish special "vertical" links: AnyLogic active object hierarchy naturally provides them: an employee can always access his department by calling get_Department(), and the company can address any of its departments by index, e.g., departments.get(i). Also see the section Where am I and how do I get to ? Using ports to connect agents Sometimes you want to manually lay out the agents on the canvas and connect them at design time. This may make sense if the number of agents is not too large, and their relationships are stable and known in advance. In such cases, AnyLogic ports may serve as connection points. Consider a model of a supply chain. Each element in the chain (a producer, a distribution center, a retailer) is an agent with two ports, see the Figure. The incoming orders and outgoing shipments are received and sent by the right-hand side port. Correspondingly, the outgoing orders and incoming shipments are sent and received by the left-hand side port. Shipments and orders are AnyLogic messages, i.e., arbitrary Java objects.
A model of a supply chain. Fixed layout, agents are connected via ports Orders Shipments The Big Book of Simulation Modeling Featuring AnyLogic 70 The AnyLogic Company www.anylogic.com If a port is connected to multiple other ports, you may need to include the destination object into the message and perform filtering at the receiving end so that the message gets handled only by the intended recipient. AnyLogic ports can be connected and disconnected dynamically if needed. This leaves some flexibility to such structures. Communication between agents. Message passing You can consider an AnyLogic model as an open space where everybody can see and talk to everybody else. You can access any construct in any active object, from any other construct in any other active object (or agent), no matter how distant they are in the model hierarchy. The syntax of expressions allowing you to move up and down the model hierarchy and penetrate inside agents is explained in the section Where am I and how do I get to? in the chapter Java for AnyLogic users. Using those rules an agent can: Directly call functions of other agents Read and modify variables and parameters of other agents In addition, AnyLogic supports a communication mechanism specific to agent based modeling: message passing. An agent can send a message to an individual agent or a group of agents. A message can be an object of any type and complexity, for example, a text string, an integer, a reference to an object, or a structure with multiple fields. Synchronous and asynchronous communication The fundamental difference between message passing and inter-agent function calls is that the first one is asynchronous communication, whereas the second one is synchronous. Consider the Figure. The agent a send the message Message to the agent b by calling the function send() somewhere in the middle of event 1. The message is delivered to b, but execution of the reaction to the message is postponed until event 1 finishes and is performed in a new event 2, scheduled immediately after event 1. Compare this to the function call. When a calls the function() of b, the execution of function() starts immediately in the middle of the event 1, the execution of agent a code is suspended and resumes only when function() returns control there. The same scheme applies when you use special functions deliver() and receive() as shown at the bottom of the Figure. The Big Book of Simulation Modeling Featuring AnyLogic 71 The AnyLogic Company www.anylogic.com Unless you intentionally want to use synchronous communication, we recommend using asynchronous message passing because it leads to a cleaner ordering of events, which is better for understanding and easier for debugging. Direct function calls can cause complex chains and loops (for example, imagine what would happen if, in the middle of execution of the function(), the agent b calls another function of agent a), and this is not the kind of complexity a simulation modeler should spend time on.
Message passing and function calls between agents API for message passing AnyLogic offers the following API for sending messages. These functions are available in the Agent class: send( Object msg, Agent dest ) sends the message to a given agent. Agent b Agent a send( Message, b ); Event 1 Reaction to the message Event 2 b.function() Event 1 function() execution 0 time 0 time Event 1 0 time Reaction to the message deliver( Message, b ) Asynchronous message passing Synchronous function call Synchronous message passing (special case) or b.receive( Message ) The Big Book of Simulation Modeling Featuring AnyLogic 72 The AnyLogic Company www.anylogic.com send( Object msg, int mode ) sends the message to a single or a group of recipients, subject to the mode parameter. deliver( Object msg, Agent dest ) immediately delivers the message to a given agent and executes the recipients reaction. deliver( Object msg, int mode ) immediately delivers the message to a single or a group of recipients (subject to mode) and executes their reactions. receive( Object msg ) immediately delivers the message to this agent and executes its reaction (this function is typically called by other agents). The mode parameter is explained in the Figure. ALL means all agents in the environment (not just agents of same type). ALL_CONNECTED delivers message to all agents connected to this one via standard connections. RANDOM_... chooses a random agent from a given set; the sender itself may be chosen.
Different modes of message passing ALL ALL_CONNECTED RANDOM RANDOM_CONNECTED ALL_NEIGHBORS (Moore neighborhood) RANDOM_NEIGHBORS (Moore neighborhood) The Big Book of Simulation Modeling Featuring AnyLogic 73 The AnyLogic Company www.anylogic.com You can also ask the environment to do the message passing by calling these functions: deliverToAll( Object msg ) immediately delivers a message to all agents in the environment. deliverToRandom( Object msg ) immediately delivers a message to a randomly chosen agent. These functions are typically used when a message is sent not from one agent to another, but from some construct at the container level (e.g., from an event at Main) to an agent. Message handling An agent has a special code field On message received where you can define its reaction to the incoming messages, see the Figure. In addition, all messages received by the agent are optionally forwarded to the statecharts inside the agent and can trigger transitions. Statecharts allow you to visually specify how the agents reaction on a particular message depends on its internal state, and they are used more frequently than the On message received code.
Message routing and handling inside an agent Agent The incoming message is handled by whatever rules are written in the On message received field And then may optionally be forwarded to the agents statechart(s) The message (of type Object) The sender (or null) The Big Book of Simulation Modeling Featuring AnyLogic 74 The AnyLogic Company www.anylogic.com Other types of inter-agent communication As you know, agents can access variables and parameters of other agents. We, however, strongly encourage you limit this access to read-only type to keep interface between agents clean and thin. For example, it is absolutely fine to check if the other agent has certain properties: if( otheragent.male and otheragent.income > 100000 ) But be careful with modifying variables of other agents. If you choose to do so, and write something like this: otheragent.income += 20000; the income of otheragent will indeed grow, but it will not notice it immediately (and react) unless you explicitly call otheragent.onChange();. Dynamic creation and destruction of agents Agents and, in general, active objects in AnyLogic can be dynamically created and destroyed. The only object that has to be present in the beginning of the simulation, and cannot be deleted at runtime, is the root object, like Main. Creation and deletion of active objects is done using a replicated object construct a collection of active objects of the same type. At runtime you can add more objects to the replicated object or delete existing objects.
Using replicated object construct to dynamically create and destroy agents Main people[..] Person Person Person
add_people() remove_people( p ) + get_Main().remove_people( this )
get_Main().remove_people( p ) get_Main().add_people() The Big Book of Simulation Modeling Featuring AnyLogic 75 The AnyLogic Company www.anylogic.com If you need to create only one instance of an active object dynamically, you still need to use a replicated object with the initial number of objects set to 0. If you plan to add or delete objects type Person within the object Main, you need to: 1. Drag the icon of Person from the Projects tree to the editor of Main. This embeds an instance of Person into Main. 2. Select the Replicated checkbox in the properties of embedded object. Give the embedded replicated object a meaningful name, like people. 3. Specify the initial number of objects, possibly 0. After you do it, AnyLogic prepares several useful functions for you. They are: The function add_people() of Main. This function creates a new object of type Person, places it into the collection people, and returns the newly created active object. The function remove_people( Person object ), also in Main. This function deletes a given instance in the collection people. The function get_Main() in the class Person returning the container of this instance of Person if it is embedded into Main. This function is actually generated for all classes whose instances are embedded in Main, be they replicated or not. The replicated object provides API common for any Java collection, in particular: int people.size() returns the number of active objects (or agents) in the collection. Person people.get( int index ) returns the element of the collection (the active object or agent) with the given index. Person people.random() returns a randomly selected element. To delete itself from the model, a Person object needs to: 1. Call get_Main().remove_people( this );. The meaning of this code is: go one level up to access the container object of type Main, then call the function remove_people() available in Main and provide itself (this) as a parameter. Correspondingly, to create another person a Person object needs to: 1. Call get_Main().add_people() or Person newborn = get_Main().add_people() if you need to remember the newly created person to do, for example, its additional setup. The Big Book of Simulation Modeling Featuring AnyLogic 76 The AnyLogic Company www.anylogic.com There is a way to specify the parameters of the active object directly at the time of its creation. Assume the Person has two parameters: male of type Boolean and income of type double. Then an additional form of the function add_people() is generated: add_people( boolean male, double income ) This creates a new object with the given parameters. Statistics on agent populations The easiest way of collecting statistics in agent based models in AnyLogic is to use the standard statistics of the replicated object. You can count the number of agents satisfying a certain condition (for example, being in a particular state), calculate the average of a certain numeric property across the agent population (for example, average income), and calculate minimum, maximum, and total (sum). You can also create dynamic histograms displaying the distribution of a value throughout the population, such as age or income distribution. In addition, as long as every internal element of every agent is accessible at the global (Main) level, you can collect highly customized statistics of arbitrary complexity. In this section we give some examples. Example: Kinship model with standard statistics We will take the Kinship model introduced earlier in this chapter and add several statistics, namely: Number of juniors, male adults, female adults, and seniors, and Average number of kids in a family We will display the statistics using time stack charts and time plots. Create statistics items in the replicated object: 1. Open the Kinship model described earlier. Open the editor of Main and select the replicated object people (the population of agents). 2. Open the Statistics page of the people properties and click Add statistics. A new statistics item is created. 3. Name the new statistics nJunior (n will mean number of). Leave the default type of the statistics (Count) and specify the condition: item.statechart.isStateActive( item.Junior ) When this statistics is calculated, AnyLogic will iterate across the agent population, evaluate the condition for each agent, and return the number of The Big Book of Simulation Modeling Featuring AnyLogic 77 The AnyLogic Company www.anylogic.com times the condition evaluated to true. The item represents the current agent (person). Note that the name Junior was defined within the Person class, therefore we need to provide the prefix item. (or Person.) because we are now writing a piece of code on the territory of the Main object. 4. Create three more statistics items: nFemaleAdults that counts people with condition item.statechart.isStateActive( item.Adult ) && ! item.male (this is a condition on both the current statechart state and a parameter), similar item nMaleAdults with condition item.statechart.isStateActive( item.Adult ) && item.male and nSenior with condition item.statechart.isStateActive( item.Senior ) 5. Finally, create the fifth statistics item with name aveKids (average number of kids). Change the type of this statistic to Average and specify: Expression: item.kids.size() Condition: ! item.statechart.isStateActive( item.Junior ) && ! item.male This statistics will calculate the average value of the expression across those agents who satisfy the condition, namely for adult and senior females only. We have created five statistics items, and AnyLogic has generated five functions in the replicated object people. Now we can write (while in Main): people.nJunior() to obtain the current number of juniors (integer value). people.nFemaleAdults() to obtain the current number of female adults. people.nMaleAdults() to obtain the current number of male adults. people.nSenior() to obtain the current number of senior people. people.aveKids() to obtain the average number of kids per non-junior female (a real value). So far, nobody is calling these functions and statistics are not collected. We will now add a couple of charts and let them periodically update themselves by calling the statistics. Add charts displaying the statistics: 6. While still in the editor of Main, add the Time stack chart object from the Analysis palette. The Big Book of Simulation Modeling Featuring AnyLogic 78 The AnyLogic Company www.anylogic.com 7. In the properties of the chart, add a new data item with default type Value, title Junior, and value people.nJunior(). Set the color of this item to yellowGreen to match the color of junior agents. 8. Similarly, add three other items: Female adults, Male adults, and Senior. Look at the bottom part of the chart properties. The chart is set to update itself each 1 time unit (each year in our model), to keep 100 last data items, and to display the 100 years time window. This means the statistics functions will be called by the chart each year. 9. Add a Bar chart with the data item Ave no of kids and value people.aveKids(). This chart will also update itself each model year. 10. Run the model and watch the charts. The Big Book of Simulation Modeling Featuring AnyLogic 79 The AnyLogic Company www.anylogic.com
Standard statistics in the Kinship model Example: Kinship model with dynamic histograms We will now further develop the statistics collection in the Kinship model. The statistics we have collected and displayed so far are useful, but we want to know more things about the population, for example, the age distribution or the distribution of the number of kids per family. The best way of answering such questions is to maintain and display dynamic histograms reflecting the current distribution of a given value. To be able to display the age distribution, we will need to slightly modify the model of Person. In the initial implementation, the exact age of a person is actually lost: we only know the current age group (junior, adult, senior), and the time remaining before the person leaves the group (the remaining time of a timeout transition). We will now add a variable birthdate and the function age(). The Big Book of Simulation Modeling Featuring AnyLogic 80 The AnyLogic Company www.anylogic.com Add explicit age information to Person: 1. Open the Kinship model (you can take the version with the standard statistics), and open the editor of Person. Drag a Variable object from the General palette, name the variable birthdate, leave the default type (double), and set the initial value to time(). Now, when a new person is created, the birthdate is set to the current model time. 2. Add a function age(), returning type double, with the code return time() - birthdate;. This function will return the current age of the person. Create periodically updated distributions 3. Open the editor of Main and add a Histogram data object from the Analysis palette. Name the item ageDistribution, set the Number of intervals to 20, set the Values range to 0-75, and select the option Do not update data automatically. 4. Create a copy of the histogram with the name kidsDistribution and the same properties except for the Values range, which should be 0-10. 5. Create an event updateDistributions (the Event object can be found in the General palette) and set its mode to Cyclic Timeout with Recurrence time 1. In the Action of the event write: //clear previous data ageDistribution.reset(); kidsDistribution.reset(); //update histograms for( Person p : people ) { //for all agents in the population ageDistribution.add( p.age() ); //add persons age to age distribution if( !p.statechart.isStateActive( p.Junior ) && !p.male ) //female non-junior only kidsDistribution.add( p.kids.size() ); //add no of kids to kids distribution } Display the distributions using Histogram objects: 6. Add a Histogram chart from the Analysis palette, click Add histogram data in its properties, specify title Age distribution, and write ageDistribution in the Histogram field. 7. Add another histogram chart with the title No of kids distribution displaying the kidsDistribution correspondingly. 8. Run the model. Now the statistics are much more informative, see the Figure. The Big Book of Simulation Modeling Featuring AnyLogic 81 The AnyLogic Company www.anylogic.com
Dynamic histograms in the Kinship model Customized high performance statistics As you can see, the standard statistics collection involves iteration across the entire population of agents each time the value is updated. This may be computationally inefficient, especially when the population is large. If the simulation performance becomes an issue, you may consider replacing the standard statistics with customized high performance statistics. The general idea is to use the knowledge of the model to update the statistics only when relevant changes occur. For example, if you are interested in the number of infected people in an epidemic model, you can maintain an integer counted at the top (Main) level and let the agents- Year 14 Year 30 Year 65 No kids yet The 1 st
generation (all of the same age) 2 nd generation 3 rd generation The Big Book of Simulation Modeling Featuring AnyLogic 82 The AnyLogic Company www.anylogic.com patients increment it each time a patient gets infected and decrement when the patient recovers. In a population model, where you are interested in average income per capita, you can add a variable holding the total income of the entire population and change it each time an individual persons income grows or falls. Then, to obtain the average income, you only need to divide the total income by the population size. Example: Kinship model with customized statistics We will refactor the Kinship model with standard statistics by replacing the statistical items of the replicated object with plain variables directly updated by the agents. Remove all standard statistics items and create auxiliary variables: 1. Open the previously created model, select the people object in the editor of Main, and delete all items on the Statistics page. 2. In Main, add four variables of type int: nJunior, nFemaleAdults, nMaleAdults, nSenior. These will be the counters. 3. Add two more variables of type int: totalKids and nFemaleNonJunior. These two variables will help us to obtain the average number of kids per family. 4. In the time stack chart, change the expression people.nJunior() to simply nJunior, people.nFemaleAdults() to nFemaleAdults, etc. 5. In the bar chart, change the expression people.aveKids() to (double)totalKids / nFemaleNonJunior. (We have to explicitly convert one of the operands to the type double to avoid integer division.) Program updates of the statistical variables: 6. Open the editor of Person. Select the statechart state Junior and add this code line to the Entry action: get_Main().nJunior++; and write get_Main().nJunior--; in the Exit action. 7. Similarly, in the Entry action of the state Adult add: if( male ) { get_Main().nMaleAdults++; } else { get_Main().nFemaleAdults++; get_Main().nFemaleNonJunior++; } and in the Exit action, correspondingly: if( male ) get_Main().nMaleAdults--; else get_Main().nFemaleAdults--; 8. In the Entry action of the state Senior, add this line: get_Main.nSenior++. The Big Book of Simulation Modeling Featuring AnyLogic 83 The AnyLogic Company www.anylogic.com and in the Exit action type: get_Main().nSenior--; if( !male ) get_Main().nFemaleNonJunior--; 9. Select the transition NewKid and add this line to its Entry action: get_Main().totalKids++;. 10. And finally, modify the Action of the Dead state this way: //clean up all links to me //from kids for( Person kid : kids ) { if( male ) { kid.father = null; } else { kid.mother = null; //update stats (mother dies first) get_Main().totalKids--; } } //from spouse if( spouse != null) spouse.spouse = null; //from parents if( father != null ) father.kids.remove( this ); if( mother != null ) { mother.kids.remove( this ); //update stats (child dies first) get_Main().totalKids--; } //and die get_Main().remove_people( this ); 11. Run the model. Switch to the Virtual time mode and watch how fast the simulation is after the changes that we made. As you can see, the simulation performance comes at the cost of writing some code, which may not be trivial. In this particular example, the most challenging part is to maintain an up-to-date total number of kids. As long as we originally decided to count children of adult and senior females, we have to take care of decrementing the total number of kids when either the mother dies before her kids, or a child dies before his or her mother. Otherwise, the code is pretty straightforward. The Big Book of Simulation Modeling Featuring AnyLogic 84 The AnyLogic Company www.anylogic.com Condition-triggered events and transitions in agents AnyLogic allows you to specify events and transitions triggered by a condition Boolean expressions defined over the agents local and possible external variables. Such events or transitions should occur once the associated condition becomes true. It makes sense to discuss the AnyLogic implementation of these constructs. We distinguish between the two cases: The model contains dynamic variables and equations (for example, the model includes system dynamics components). Then the numeric solver is involved and the implicit (and typically small) time steps are present in the model. If this is the case, all conditions of events and transitions are tested on every numeric time step, and the moment when a condition becomes true will be discovered at least with the time step accuracy. The model does not contain dynamic variables and equations. Then the numeric solver is not working during the simulation, and there are no implicit time steps in the model. The conditions the agents events and transitions wait on are then tested only when something happens in the agent (e.g., an internal event or statechart state change), and also when the agents function onChange() is explicitly called. Therefore, it makes a lot of sense to explicitly notify the agent that is waiting on a condition each time an external change occurs that may affect the condition. For example, if the agent-consumer waits for the product price to fall below a certain threshold, the agent-retailer may call the function onChange() of all consumers in the beginning of a sale. The model constructed this way will be computationally very efficient. However, the model structure and behavior does not always allow it to be done in a simple way, and then the modeler can introduce artificial time steps by, for example, adding a cyclic event at the Main object and calling onChange() of all agents in the action of that event.