Archive for the tag 'take'

Simple Example - Calculator Agent

Now, we have enough knowledge to create a simple example: a calculator.

The example shows how to:

  • Create an agent.
  • Locate the blackboard.
  • Write a tuple (a calculator operation).
  • Take a tuple (a calculator operation result).
  • Differentiate between different operations (plus and minus).
  • Handle a simple ID.

The calculator agent will be able to handle two kinds of operations: plus and minus. It will receive an operation request tuple, where one item will be an operation identifier (to handle two operations) and two items with numerical values (to transmit values).
The agent will produce a result tuple, where one item will be a numerical value (to transmit back a result value).
To track a result, each operation request tuple will have an ID, that will be inserted back into a result tuple.

To sum up, there will be two tuple types:

  • operation request [:calculator, id, operation, a, b], e.g.
    [:calculator, id, :plus, 1, 2] or [:calculator, id, :minus, 6, 2].

  • result [:result, id, value], e.g.
    [:result, id, 4].

To run this example, we will need three files: rinda.rb to start the environment, calculator.rb the calculator agent and client.rb to send requests and display results.


The calculator agent source code, calculator.rb:

require 'rinda/ring'
require 'rinda/tuplespace'

# A simple calculator example.
#
class SimpleCalculator

  def plus(a, b)
    a + b
  end

  def minus(a, b)
    a - b
  end

  def main
    DRb.start_service
    ts = Rinda::RingFinger.primary

    loop do
      t = ts.take [:calculator, nil, nil, nil, nil] # take an operation request

      # assign values
      id = t[1]
      operation = t[2]
      arg1 = t[3]
      arg2 = t[4]

      puts “Calculator recived operation ##{id} #{operation} with arguments #{arg1} and #{arg2}”

      res = operation == ‘plus’ ? plus(arg1, arg2) : minus(arg1, arg2) # perform the received operation
      #sleep 1 # to pretend that an operation takes longer

      ts.write [:result, id, res] # send a result back
    end
  end
end

SimpleCalculator.new.main


And the client source code, client.rb:

require 'rinda/ring'

DRb.start_service

ts = Rinda::RingFinger.primary

t1 = Time.now #the start time

# sending operation requests

id = 1 # the ID
for i in (1..5) # plus operations
  puts "Sending operation ##{id} plus with arguments #{i} and 1"
  ts.write [:calculator, id, :plus, i, 1]
  id += 1 # an unique ID value
end
for i in (5..9) # minus operations
  puts “Sending operation ##{id} minus with arguments #{i} and 1″
  ts.write [:calculator, id, :minus, i, 1]
  id += 1 # an unique ID value
end

puts “Waiting for results…”

# receiving results

i = 1
loop do
  t = ts.take [:result, nil, nil], 2
  puts “Result ##{t[1]} = #{t[2]}”
  i += 1
  break if i > 10 # 10 request were sent
end

t2 = Time.now # the end time
puts “The operation took #{t2 - t1} seconds.”

See a screencast Simple calculator agent screencast [.ogg]

To start the example, firstly start the environment ruby rinda.rb and then start the calculator agent ruby calculator.rb. Each execution of the client, ruby client.rb, will produce 10 operation requests that will be processed by the agent. The agent will produce 10 results, that will be read by the client.

There is a lot of log messages, so you can easily see when an agent instance received a request, when it send a result and when a client send a request and received a result. With the #id part of a log message you can track particular communication parts.

Execution time should be very short, approx. 0.06 seconds.


Let’s imagine that an operation processing would take longer, e.g. 1 second. Uncomment the 32nd line in the agent source code calculator.rb and restart all parts of the example (rinda.rb and calculator.rb).
Execute the client and the execution time will be, as expected, 10.06 seconds.


And now, to show power of a parallel processing in a multi-agent system, start another calculator agent…

The logs of the started calculator agent instances show that the processing load was nicely shared between the agents.

The result? A half of time (of course), approx. 5.04 seconds.

I like it :)


Files to download: simple_calculator.zip.

Message and Tuple

In the last posts a term message was used. In the Rinda documentation you would not probably find this term; it is called more accurately: tuple.


What is a tuple?

  • [1, 2] is a tuple
  • [:message, 'Hi there!'] is a tuple
  • ['Hi there!', :message] is also a tuple
  • [:result, 42, 'John', 'Susanne', 'London'] is a tuple
  • [:clock, Time.new] is a tuple too
  • [:service, CalculatorService.new(1, true) is a tuple

Tuple is an ordered list of elements [wikipedia].


A blackboard is a place to share tuples; agents are writing, reading and taking tuples.

If you have a simple system, where agents are using only one kind of messages, it is enough to use a single, e.g. [1], ['Hello']. But what to do, if your system requires to differentiate more types of messages? Well, there is no problem with writing different types of messages, but there is a problem how to find the correct one to read or take it.

Basically there are two possibilities:

  • Every type of a message has different number of elements. The number of elements is the distinguishing sign.

    Example: [1] is similar to [2] or ['Hello'], but it is different then [3, 'Hello'].

  • One of the parameters is an identifier. The number of elements and an identifier are the distinguishing signs.

    Example: [:request, 1, 2] is similar to [:request, 24, 'John'], but it is different to [:result, 42, 'London'].


How are the blackboard operations using tuples?

  • The write operation writes a tuple (specified as a parameter).
    ts.write [:message, 'Hi there!']
    
  • Reading operations read and take use tuples in a slightly different way. It is possible to use the nil value as a wild card for the position, where it is used.
    • To read any single:
      ts.take [nil], 0
      
    • To read any pair:
      ts.take [nil, nil], 0
      
    • To read any :message type:
      ts.take [:message, nil], 0
      
    • To read any pair, where the second element has value 4 (not very practical example :):
      ts.take [nil, 4], 0
      
    • To read exactly the tuple [:result, 4]:
      ts.take [:result, 4], 0
      

    The second parameter says, how long (in seconds) should a read operation wait until a tuple appears on the blackboard. Zero means to not to wait at all.

Take Message From Blackboard

The Rinda multi-agent system environment is already running, on the blackboard is written a message, so the last step is to read it :)

The following functionalities will be shown:

  • Creation of an agent.
  • Locating the blackboard.
  • Taking a message.

Although a logical opposite operation to the write operation should be the read operation, it is not so in the blackboard system: the operation read only checks if a message is there (this will be shown in a post that will follow); the operation take takes a message from the blackboard.


The agent source code is stored in the take.rb file:

require 'rinda/ring'

DRb.start_service

ts = Rinda::RingFinger.primary

result = ts.take [:message, nil], 0

puts “[0]: #{result[0]}”
puts “[1]: #{result[1]}”


Firstly, start the rinda.rb code (for more details see the post about starting multi-agent system environment), then, in a separate console, start the write.rb (for more details see the post about writing a message to the blackboard) and finally, in a separate console or in the same console where you started the write.rb file, type:

ruby take.rb

The agent connects to the blackboard, takes a message and shows two lines:

[0]: message
[1]: Hi there!


Files to download: rinda.rb, write.rb and take.rb.

Blackboard Operations

A blackboard system should support three operations: write, take, read.

  • write — adds a message to the blackboard.
  • take — returns a content of a message from the blackboard, but it also removes it from the blackboard. An agent, that performed the take operation, is the only one in the system, that has a content of this message.

    This operations is usually used for messages that start a task. There is no need to let other agents to start the same task.

  • read — returns a content of a message from the blackboard. The message stays there, so other agents can also read a content of this message.

    This is used for messages that describe a state of the whole system — a flag. For example, a flag stop processing should be visible to all agents, so all of them will stop processing.