Skip to content
Pedro Andrade edited this page Oct 26, 2017 · 35 revisions

Frequently Asked Questions

Pedro R. Andrade

Summary

General

Why can I choose different names for the arguments of second-order functions?

Second order functions, such as forEachElement or forEachNeighborhood, are the way to apply a piece of code over each object of a given set. For example, forEachNeighborhood is applied over each neighborhood of a given Cell. It is up to TerraME to call such functions internally and set its arguments. Such functions are always called with some arguments in the same order. As the applied function is user defined, the user is responsible to define the name of the arguments. For example, forEachOrderedElement calls a second-order function passing the arguments (1) name of the attribute, (2) value of the attribute, and (3) type of the attribute. In the code below, a will get the name of the attribute, b will get the value, and c will get the attribute's type.

mytable = {
    value1 = 2,
    value2 = 5,
    value5 = 7
}

forEachOrderedElement(mytable, function(a, b, c)
    print(a, b, c)
end)

It will produce the following output:

value1	2	number
value2	5	number
value5	7	number

If the code exchange the names of the first and second arguments, b will get the name of the attribute and a will get its value.

forEachOrderedElement(mytable, function(b, a, c)
    print(a, b, c)
end)

We will have a slightly different output:

2	value1	number
5	value2	number
7	value5	number

Of course, it is recommended to use names that better represent the values of the arguments instead of a, b, and c.

What does underscore (_) mean in the argument of second-order functions?

As TerraME always calls second order functions with the same arguments, sometimes some of the arguments are not necessary. If the unnecessary arguments are the last ones, it is possible to ignore them as arguments. For example, a call to forEachElement that uses the three available arguments is like:

forEachElement(mytable, function(name, value, type) ... end)

If one only needs only name and value, it is possible to declare the second order function with only two arguments:

forEachElement(mytable, function(name, value) ... end)

However, if the user needs only value, but not name, it is necessary to declare a name for the argument name beause the value is the second argument. To indicate that we do not need the first argument, we usually use _ as name for a given argument.

forEachElement(mytable, function(_, value) ... end)

Why doesn't forEachElement() execute in the same order when using named tables?

Lua does not guarantee that a named-table will be trasversed in the same way when executed twice. For example, the code below prints the names and values of a given table:

mytable = {
    value1 = 2,
    value2 = 5,
    value5 = 7
}

forEachElement(mytable, function(name, value)
    print(name, value)
end)

It might produce an output like:

value2	5
value5	7
value1	2

or:

value5	7
value2	5
value1	2

If you need to guarantee that the table will be traversed always in the same order, use forEachOrderedElement instead.

When to use . and when to use :?

Basically, operator . is used to access attributes from a table or from an object of a type defined by TerraME (Cell, CellularSpace, Agent, etc.). Operator : is used to call a function from such object.

agent = Agent{
    age = 10,
    execute = function(self)
        self.age = self.age + 1
    end
}

print(agent.age)
print(agent.execute) -- it does not execute the function
print(agent.age) -- still 10

It is possible to call execute using .. In this case, it would be necessary to set the agent itself as argument of the function

agent.execute(agent)
print(agent.age) -- 11

The code above can easily create errors in the code, for example when one forgets to set the argument (agent.execute()), or when the argument is not the agent itself (e.g. agent.execute(agnt)). Operator : is a syntax sugar to avoid such problems, because it adds the agent itself to the first argument of the function:

agent:execute()
print(agent.age) -- 12

When the function to be called has more than one argument, as the first is hidden, the second argument in the declaration of the function becomes the first one in the function call, and so on. For example:

agent = Agent{
    age = 10,
    getOld = function(self, years)
        self.age = self.age + years
    end
}

print(agent.age)
agent:getOld(5) -- 5 years
print(agent.age) --  15

Why some functions are called with () while others use {}?

Functions called with () get a set of arguments in a given order. Each argument has a specific meaning and they are passed as simple values. For example:

agent:move(cell)

On the other hand, functions called with {} get a single argument: a table with named attributes. The arguments can be described in any order and the names have a specific meaning to the function. For example:

agent:message{receiver = otheragent, content = "hello"}

Usually functions with a small number of arguments (up to three) that can be ordered from the most common (first argument) to the least common (last one) are called with (). Those with more (or a user-defined number of) arguments without a straight order usually use {}.

Why doesn't != work in Lua?

Unlike other languages, Lua does not have a != operator. The operator that verifies if two values are different is named ~=.

Cell, CellularSpace, Neighborhood, and Map

How to set (x, y) locations of Cells read from external sources?

When TerraME reads a CellularSpace, if it finds attributes col and row it uses them to represent the spacial location of the objects. Supposing that the data has attributes mycol and mylin with this information, it is possible to set argument xy to use them.

mycs = CellularSpace{
    file = "myfile.shp",
    xy = {"mylin", "mycol"}
}

Why it is not possible to remove an attribute of a Cell when I use an instance?

When one uses an instance to create a CellularSpace, every Cell will have the attributes of the instance as default. It means that those attributes do not need to be declared in the Cells explicitly. When a given Cell does not have a given attribute value or function, Lua automatically searches for the specific attribute or function in the instance. This way, when one sets a given attribute of a Cell to nil, it restarts the value to the one defined in the instance. For example, in the code below, the last line will show 2.

cell = Cell{value = 2}

cs = CellularSpace{
    xdim = 2,
    instance = cell
}

sample = cs:sample()
print(sample.value)
sample.value = nil
print(sample.value) -- still 2

My Map is upside down. How to fix it?

In TerraME, as default the (0, 0) point of a CellularSpace is located on the bottom left. If the Map is upside down, the (0, 0) point should be the top left. To invert a Map, it is necessary to indicate this when one reads the CellularSpace, using argument zero. The example below reads a map inverting the y axis.

amazonia = CellularSpace{
    file = filePath("amazonia.shp"),
    zero = "top"
}

See the documentation of CellularSpace for more information, specially when one uses argument xy.

How does transparency work?

Every value that does not belong to the interval [min, max] of a Map, or is not described in the values, is drawn as transparent. This will be changed in a future version of TerraME to give more flexibility to the modeler.

Can I create other types of Neighborhoods?

Neighborhoods are created using createNeighborhood. As default, when this function is called without any argument, TerraME creates a Moore (3x3) neighborhood. For other available options, see the documentation in the link above.

Is it possible to have more than one Neighborhood per CellularSpace?

Yes, one Cell can have more than one Neighborhod. CellularSpace:createNeighborhoodi() has an argument name, whose default value is "1". If you call CellularSpace:createNeighborhood() twice, with different names, both neighborhoods are stored in the Cell. For example:

cs = CellularSpace{
    xdim = 10
}

cs:createNeighborhood{
    name = "moore"
}

cs:createNeighborhood{
    strategy = "vonneumann",
    name = "vonneumann",
    self = true
}

To trasverse such neighborhoods, it is necessary to set their name in the second argument of forEachNeighborhood(). In this case, the second order function will be the third argument instead of second.

forEachNeighbor(cs:sample(), "moore", function(neigh)
    neigh.state = "alive"
end)

Random

Why does Random work differently outside an instance?

Objects of type Random can generate random numbers by calling sample(). When a Random object belongs to a Cell (Agent) that is used as instance to a CellularSpace (Society), it is not necessary to call sample() explicitly. TerraME calls it internally to initialize the attribute values of the Cells (Agents) of the CellularSpace (Society).

Agent, Society, and SocialNetwork

How to define a maximum number of Agents in each Cell?

When creating a placement using Environment:createPlacement(), it is possible to define a maximum number of Agents per Cell using argument max, whose default is one. After that, TerraME allows a Cell to have any amount of Agents. However, as the common strategy in the literature is to have at most one Agent per Cell, functions walkToEmpty(), walkIfEmpty(), and emptyNeighbor() will implement relocation considering a Cell full if there is a single Agent within it. If the maximum is greater than one, it is up to the modeller to control whether a Cell is full or not.

How to kill agents within the same Society?

There is no problem on calling Agent:die() to agents belonging to another Society. However, it is not recommended to kill another Agent that share the same Sociegy, because it is usually scheduled to execute in the same Event of the killer. In this case, it is better to have a third entity to work as a mediator. Code below shows one example where a mediator called master manages the Agents that need to be removed from the simulation. It gets messages with the target agents and stores them into a Group. After the Society executes in a given time step, master removes the agents from its list, ensuring that each Agent will have the opportunity to be executed in each time step.

master = Agent{
    to_kill = Group{build = false},
    on_message = function(self, message)
        self.to_kill:add(message.kill)
    end,
    execute = function(self)
        forEachAgent(self.to_kill, function(agent)
            agent:die()
        end)

        self.to_kill:clean()
    end
}

predator = Agent{
    energy = 40,
    execute = function(self)
        forEachAgent(self:getCell(), function(other)
            if other ~= self and math.random() < 0.1 then
                self.energy = self.energy + other.energy / 2
                self:message{target = master, kill = other}
            end
        end)

        -- ...
    end
}

predators = Society{
    instance = predator,
    quantity = 50
}

t = Timer{
    Event{action = predators},
    Event{action = master}
}

How to create a SocialNetwork from scratch?

It is possible to create a SocialNetwork from scratch in TerraME. Code below creates a SocialNetwork with two agents, john and mary. When the SocialNetwork is added to myself through Agent:addSocialNetwork(), it connects myself to john and mary. Note that this code does not establish any connection between john and mary.

soc = Society{...}

john   = soc:sample() -- get a random agent from the society
mary   = soc:sample()
myself = soc:sample()

friends = SocialNetwork()
friends:add(john)
friends:add(mary)

myself:addSocialNetwork(friends) -- adding two connections to myself

print(#myself:getSocialNetwork()) -- amount of agents in the social network

How to add and remove connections along the simulation?

It is possible to add() and remove() agents from a SocialNetwork after creating it. For example, given an agent, it can add a friend to its network and remove a nomorefriend one:

agent:getSocialNetwork():add(friend)
agent:getSocialNetwork():remove(nomorefriend)

If the Agents have more than one SocialNetwork, it is possible to indicate which one will be used:

agent:getSocialNetwork("friends"):add(friend)
agent:getSocialNetwork("friends"):remove(nomorefriend)
agent:getSocialNetwork("nomorefriends"):add(nomorefriend)

Can an Agent have multiple SocialNetworks?

Each Agent can have one or more social networks attached to it. They are indexed as strings, having an implicit value of "1" if not specified when calling Agent:addSocialNetwork(). Therefore, whenever the modeler wants to have more than one social network per agent, it is necessary to use explicit indexes. Every function of TerraME that uses social networks has an optional argument to describe which index will be used, such as the second parameter of Agent:addSocialNetwork(), as shown in code below. Note that forEachConnection() has a construction that takes three arguments instead of two, shifting the function to third argument in order to add the index before it. Using this strategy one can choose from a set of SocialNetworks, representing, for instance, family, friends, and colleagues.

family = SocialNetwork()
family:add(Jonh)   -- Jonh is an Agent
family:add(Mary)   -- Mary too
family:add(Anekee) -- Anekee too

myself:addSocialNetwork(family, "family") -- myself is also an Agent

friends = SocialNetwork()
friends:add(Mark)
friends:add(Richard)

myself:addSocialNetwork(friends, "friends")

forEachConnection(myself, "family", function(familyMember)
    -- ... do something with my family
end)

forEachConnection(myself, "friends", function(friend)
    -- ... do something with my friends
end)

How to recompute connections?

Connections can be recomputed by calling Society:createSocialNetwork() again, or by setting argument inmemory = false when calling such function for the first time. When this argument is used, every time the SocialNetwork is required, TerraME recomputes it.

Is it possible to have other functions to receive messages besides on_message()?

Messages can have an argument called subject. It is a string that indicates which function of the receiver will be called whenever the message arrives. It facilitates implementing a model where there are different behaviors for different subjects of messages. In TerraME, a message with subject "x" will be received by a function called on_x(). As the default way of receiving messages is through "on_message", the default subject of a message is "message". Code below presents one example of two subjects, "hello" and "goodbye", each one with a different behavior associated.

citizen = Agent{
    -- ...
    on_hello = function(self, message)
        self:addConnection(message.sender)
    end,
    on_goodbye = function(self, message)
        self:removeConnection(message.sender)
    end
}

-- jonh, mary, and joane are citizens
john:message{subject = "hello", receiver = mary}
john:message{subject = "goodbye", receiver = joann}

Is it possible to have different synchronization times for Societies?

Society:synchronize() has one optional argument to depict the temporal interval between the last synchronization and the current one. The default value is the last synchronization time plus one. Code below uses different synchronization points.

paul:message{receiver = anne, delay = 1, content = "greetings"}
paul:message{receiver = anne, delay = 2, content = "takethis", x = 3}
paul:message{receiver = anne, delay = 2.5, content = "farewell"}
paul:message{receiver = anne, delay = 5, content = "whereareyou"}

society:synchronize()    -- greetings (until time 1)
society:synchronize(1.6) -- takethis, farewell (until time 2.6)
society:synchronize()    -- nothing happens (until time 3.6)
society:synchronize(4)   -- whereareyou (until time 7.6)

Timer and Event

How does a Timer work?

Timer is a queue of Events ordered by the time each of them will be executed. At each simulation step, a Timer gets the next Event, updates its internal clock to the registered time for the Event, and then executes its action. After that, the Event will be scheduled to occur again in the current time plus its period. When there are two Events with the same time and priority, the one added first to such time will be executed first. When a Timer is created, Events are added to the Timer in the same order they are declared. See the script below:

timer = Timer{
    Event{start = 2, period = 2, priority = "high", action = function()
        print("first")
    end},
    Event{action = function()
        print("second")
    end},
    Event{start = 2, period = 3, priority = "low", action = function()
        print("third")
    end},
    Event{period = 2, action = function()
        print("fourth")
    end}
}

timer:run(4)

It will produce the following output:

second (end of time 1)
first
fourth
second
third (end of time 2)
second (end of time 3)
first
fourth
second (end of time 4).

Usually a Timer stops in the time passed as argument to run(). However, it might stop if it does not contain any Event. If the action of an Event returns false, it will not be added to the Timer again. For example, the code below should run until time ten, but as the only event returns false when the simulation time is greater than one, timer will stop in time two. Note that the action needs to get the Event itself as argument to access the current simulation time through getTime().

timer = Timer{
    Event{action = function(self)
        print("Rained")

        if self:getTime() > 1 then
            return false
        end
    end}
}

timer:run(10)

How to stop a simulation?

Just call Timer:clear(). As it is usually called from an action of an Event, to have access to the Timer itself use the second argument of action (usually ignored). Note that, if it is called within an action of an Event, it must return false, otherwise it will be inserted again to the Timer. For example:

mytimer = Timer{
    Event{action = function(_, timer)
        if #society == 0 then
            -- mytimer might not be available here as it was not created yet
            timer:clear()
            return false
        end
    end}
}

How to remove an Event from a Timer?

When an Event is executed, it is removed from the Timer and then its action is called. In the end of the action, the Event is usually added to the Timer to be executed in the future. If the the action should not be executed again, it can simply return false.

How to create an Event to represent a delayed behavior?

It is possible to create an Event within the action of another Event to add a delayed behavior. For example, the following code simulates the behavior of a person while using a gas shower. Each time step the person verifies whether it isCold(). If true, it will open the faucet a little bit more in order to getWarm(). However, the gas shower takes some time to getWarm(), which we represent by a delayed Event.

Timer{
    Event{action = function(event, timer)
        if isCold() then
            -- open the gas shower a little bit more, which
            -- increases the temperature of the shower in the future
            timer:add{start = event:getTime() + 5, action = function()
                getWarm()
                return false
            end}
        end
    end}
}

The person in the code above will open the faucet a couple of times before the water gets warm, which in turn will get warmer than the person desired after he stops opening the faucet.

Clone this wiki locally