This document describes the basics of the creation and design of holons in SARL. This tutorial is applied on a simple English auction application. Before reading this document, it is recommended reading the General Syntax Reference. The architecture presented in this tutorial may be used for designing a system in which a decision must be taken after arbitration among components.
According to the vocabulary used in the SARL project, all the agents are holons. In the rest of this tutorial, the terms “agent” and “holon” are synonymous.
The elements that are explained in this tutorial are:
Note The communication between the sub-agents is out of the scope of this tutorial. For interested readers, the Agent Reference may be read.
The source code related to this tutorial may be found in the Github of the SARL demos.
This type of auction is arguably the most common form of auction in use today. Participants bid openly against one another, with each subsequent bid required to be higher than the previous bid. An auctioneer may announce prices, bidders may call out their bids themselves. The auction ends when no participant is willing to bid further, at which point the highest bidder pays their bid. Alternatively, if the seller has set a minimum sale price in advance (the ‘reserve’ price) and the final bid does not reach that price the item remains unsold. Sometimes the auctioneer sets a minimum amount by which the next bid must exceed the current highest bid. The most significant distinguishing factor of this auction type is that the current highest bid is always available to potential bidders.
Source: Wikipedia
The principle of the application is the following:
By a design choice, the bidders are sub-agents of the auctioneer agent.
According to the Built-in Capacity Reference, a sub-agent is living in the inner context of a super-agent. Each agent defines its own context, called the inner context, where other agents can live. Therefore, every agent can be seen as a part of a larger holon and at the same time be composed by other agents that exist in its inner context.
Note According to the SARL specifications, all the agents in a context belong to the default space of this context. This property is important for designing the communication links between two adjacent levels in the hierarchy of agents. The default space of the inner context becomes the natural place where the super-agent and its sub-agents are interacting.
In the application, two events are needed: the event from the auctioneer for notifying the bidders of the new price; and the event that is sent by a bidder to the auctioneer with a bid inside.
The Price
event is the event sent by the auctioneer for notifying a bidder that
the price has changed. This event contains the new price.
package io.sarl.docs.tutorials.holonicauction
event Price {
val price : float
new(price : float) {
this.price = price
}
}
The Bid
event is the event sent by a bidder to the auctioneer.
This event contains the value of the bid.
event Bid {
val value : float
new(value : float) {
this.value = value
}
}
The bidder agent is reacting to new price notifications, and could offer a new bid if the new price is not exceeding its maximal price.
The initial definition of the bidder is below. The random
attribute contains an instance of
a random number generator (from the Java library). The maxPrice
attribute is the maximum value
of the price that the bidder will consider for bidding.
The bidder selects the maximum price between 100 and 1000 randomly.
agent Bidder {
val random = new Random
var maxPrice : float
on Initialize {
maxPrice = random.nextFloat() * 900f + 100f
}
}
The definition of the bidder agent is extended by the bidding behavior.
The bidding must occur when the auctioneer is notifying a new price, i.e. when the
Price
event is received.
The bidder computes the new price. If this last is not exceeding the maximal
price, then the bidder is sending its bid in a Bid
event.
Interaction Principle For sending data to its super-agent, a sub-agent must fire an event in the default space of the inner context of the super-agent. The emit
function is supporting this interaction.
Caution The Bid
event is sent in the default space. But there is no restriction on the event’s recipient. It means that the super-agent and the other sub-agents will receive this event.
agent Bidder {
val random = new Random
var maxPrice : float
var myLastBid : float
on Initialize {
maxPrice = random.nextFloat() * 900f + 100f
}
uses DefaultContextInteractions, Logging
on Price {
if(occurrence.price == myLastBid) {
println("I do not bet, I am the winner with :" + myLastBid)
} else {
if(occurrence.price < maxPrice) {
var priceIncrease = random.nextFloat() * 50f
if (priceIncrease > 0) {
var newPrice = occurrence.price + priceIncrease
if (newPrice <= maxPrice) {
emit(new Bid(newPrice))
myLastBid = newPrice
} else {
println(" I give up, this is beyond my resources : " + myLastBid)
}
}
} else {
println("I dropped to " + myLastBid)
}
}
}
}
For restricting the recipients of the Bid
event to the auctioneer, it is mandatory to specify a
scope for the event.
For supporting the holonic communication from the sub-agent to the super-agent, the scope
of the event corresponds to the address of the super-agent in the default space.
Note The ID of the super-agent, and the ID of the inner context of this super-agent are always the same.
Below, we update the bidding behavior by creating a scope, and providing it to the emit
function.
agent Bidder {
val random = new Random
var maxPrice : float
var myLastBid : float
on Initialize {
maxPrice = random.nextFloat() * 900f + 100f
}
uses DefaultContextInteractions, Logging
on Price {
if(occurrence.price == myLastBid) {
println("I do not bet, I am the winner with :" + myLastBid)
} else {
if(occurrence.price < maxPrice) {
var priceIncrease = random.nextFloat() * 50f
if (priceIncrease > 0) {
var newPrice = occurrence.price + priceIncrease
if (newPrice <= maxPrice) {
emit(new Bid(newPrice)) [ it.ID == defaultContext.ID]
myLastBid = newPrice
} else {
println(" I give up, this is beyond my resources : " + myLastBid)
}
}
} else {
println("I dropped to " + myLastBid)
}
}
}
}
The third step of this tutorial is the definition of the auctioneer that is initiating the auction, waiting for bids, and selecting the winner.
The initial definition of the auctioneer is defined below. The auctioneer is starting the
auction with a price of 50. It is notifying the bidders with a Price
event.
Because the bidders are sub-agents, they are living in the inner context of the auctioneer.
For sending the Price
event to the bidders, the auctioneer must put it in the
default space of its inner context.
This particular type of emit is supported by the wake
function, which is provided by
the Behaviors
capacity. This function does the same as:
innerContext.defaultSpace.emit(new Price(50))
Interaction Principle For sending data to its sub-agents, a super-agent must fire an event in the default space of its inner context. The wake
function is supporting this interaction.
agent Auctioneer {
uses Behaviors
var maxBid = 0f
var winner : Address
var hasBid = false
var isAuctionOpened = true
on Initialize {
wake(new Price(50))
}
}
The creation of the sub-agents in the auctioneer needs to spawn agents in the inner context.
The Lifecycle
capacity gives the spawnInContext
function.
This function permits creating an agent in a particular context.
For obtaining the inner context, we need to use the InnerContextAccess
capacity,
which provides the [:getinner:s] function. Below, we create the three bidders.
agent Auctioneer {
uses Behaviors, Lifecycle, InnerContextAccess
var maxBid = 0f
var winner : Address
var hasBid = false
var isAuctionOpened = true
on Initialize {
for(i : 1..3) {
spawnInContext(typeof(Bidder), getInnerContext)
}
wake(new Price(50))
}
}
The auctioneer is waiting for bids. This behavior is coded inside the behavior
unit dedicated to the Bid
event. We add a guard on the isAuctionOpened
attribute
to execute the behavior only if the auction is still opened. We will see later when
the auction is closed. If the value of the received bid is greater than the current
price, the source of the Bid
event becomes the new potential winner.
agent Auctioneer {
uses Behaviors, Lifecycle, InnerContextAccess
var maxBid = 0f
var winner : Address
var hasBid = false
var isAuctionOpened = true
on Initialize {
for(i : 1..3) {
spawnInContext(Bidder, innerContext)
}
wake(new Price(50))
}
on Bid [ isAuctionOpened ] {
if (occurrence.value > maxBid) {
maxBid = occurrence.value
winner = occurrence.source
}
}
}
The auctioneer must wait some time before it is closing the auction due to lake of bid. To reproduce this behavior, we introduce a periodic task, which is executed every 10 seconds for checking if a bid was provided during the last 10 seconds. This periodic task is started after the first 10 seconds.
Coding the periodic task in SARL is done with the Schedules
capacity.
It provides the every
function that is executing at a fixed delay its second argument,
given by the first argument.
In the task’s code, we test if a bid was received. If not, the auctioneer closes the auction,
and outputs the appropriate message. To delay the task executor about the first ten seconds,
we use the in
function provided by the capacity.
agent Auctioneer {
uses Behaviors, Lifecycle, InnerContextAccess, Schedules, Logging
var maxBid = 0f
var winner : Address
var hasBid = false
var isAuctionOpened = true
on Initialize {
for(i : 1..3) {
spawnInContext(Bidder, innerContext)
}
wake(new Price(50))
in(10000) [
val waitTask = task("wait-task")
waitTask.every(10000) [
if (!hasBid) {
isAuctionOpened = false
if (winner === null) {
println("No winner")
} else {
println("The winner is " + winner
+ " with the bid of " + maxBid)
}
}
hasBid = false
]
]
}
on Bid [ isAuctionOpened ] {
hasBid = true
if (occurrence.value > maxBid) {
maxBid = occurrence.value
winner = occurrence.source
}
}
}
Because the periodic task and the event handlers may be executed in parallel, we are facing a classical problem in concurrent programming: how to ensure that two blocks of code are not executed at the same time for avoiding any conflicting access on the same data.
It is recommended to “synchronize” the blocks of code. We use the synchronized
operator
(which has the same meaning as in the Java language). This operator ensures
that two blocks of code, which are synchronized on the
same Object (the argument of the operator) cannot be
executed in parallel by different threads.
agent Auctioneer {
uses Behaviors, Lifecycle, InnerContextAccess, Schedules, Logging
var maxBid = 0f
var winner : Address
var hasBid = false
var isAuctionOpened = true
on Initialize {
for(i : 1..3) {
spawnInContext(Bidder, innerContext)
}
wake(new Price(50))
in(10000) [
val waitTask = task("wait-task")
waitTask.every(10000) [
synchronized(this) {
if (!hasBid) {
isAuctionOpened = false
if (winner === null) {
println("No winner")
} else {
println("The winner is " + winner
+ " with the bid of " + maxBid)
}
}
hasBid = false
}
]
]
}
on Bid [ isAuctionOpened ] {
synchronized(this) {
hasBid = true
if (occurrence.value > maxBid) {
maxBid = occurrence.value
winner = occurrence.source
}
}
}
}
The previous code works well at one exception. When the auction is closed, the system does not stop.
Indeed, the auctioneer does not send any more the Price
event. This
cause all the agents waiting something that will never append.
We need to stop the agents.
Important Note In the specification of SARL, a super-agent cannot be killed if there is some other agent belonging to its inner context. Consequently, for stopping the agents, we need to stop the sub-agents before the super-agent.
Because the determination of the end of the agent’s life is made by the auctioneer,
this last must notify its sub-agents that it is time to commit a suicide.
We introduce the StopAuction
event that is used for this particular notification task.
event StopAuction
The code of the bidder must be updated for reacting on the receiving of the StopAuction
event.
When it is received, the bidder agent is killing itself by calling the killMe
function.
This function is provided by the Lifecycle
capacity.
agent Bidder {
val random = new Random()
var maxPrice : float
on Initialize {
maxPrice = random.nextFloat() * 900f + 100f
}
uses DefaultContextInteractions
on Price {
var priceIncrease = random.nextFloat() * 50f
if (priceIncrease > 0) {
var newPrice = occurrence.price + priceIncrease
if (newPrice <= maxPrice) {
emit(new Bid(newPrice)) [ it.ID == defaultContext.ID]
}
}
}
uses Lifecycle
on StopAuction {
killMe
}
}
The code of the auctioneer must be updated for firing the StopAuction
event, and for killing
itself when there is no more sub-agent.
Firstly, the periodic task is updated with a wake
call
that permits notifying the sub-agents of the end of the auction.
Secondly, in this periodic task, if the auction is closed, then the auctioneer is killing itself if the
hasMemberAgent
function replies false.
This function is provided by the InnerContextAccess
capacity.
The periodic task must also be stopped. The cancel
function is invoked on the periodic task
to stop its execution.
agent Auctioneer {
uses Behaviors, Lifecycle, InnerContextAccess, Schedules, Logging
var maxBid = 0f
var winner : Address
var hasBid = false
var isAuctionOpened = true
on Initialize {
for(i : 1..3) {
spawnInContext(Bidder, innerContext)
}
wake(new Price(50))
in(10000) [
val waitTask = task("wait-task")
waitTask.every(10000) [
synchronized(this) {
if (!isAuctionOpened) {
if (!hasMemberAgent) {
waitTask.cancel
killMe
}
} else {
if (!hasBid) {
isAuctionOpened = false
if (winner === null) {
println("No winner")
} else {
println("The winner is " + winner
+ " with the bid of " + maxBid)
}
wake(new StopAuction)
}
hasBid = false
}
}
]
]
}
on Bid [ isAuctionOpened ] {
synchronized(this) {
hasBid = true
if (occurrence.value > maxBid) {
maxBid = occurrence.value
winner = occurrence.source
}
}
}
}
Copyright © 2014-2024 SARL.io, the Original Authors and Main Authors.
Documentation text and medias are licensed under the Creative Common CC-BY-SA-4.0; you may not use this file except in compliance with CC-BY-SA-4.0. You may obtain a copy of CC-BY-4.0.
Examples of SARL code are licensed under the Apache License, Version 2.0; you may not use this file except in compliance with the Apache License. You may obtain a copy of the Apache License.
You are free to reproduce the content of this page on copyleft websites such as Wikipedia.
Generated with the translator docs.generator 0.14.0.