micro_agent 0.1.6
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +79 -0
- data/examples/drivers.rb +114 -0
- data/lib/micro_agent.rb +168 -0
- data/spec/micro_agent_spec.rb +94 -0
- metadata +65 -0
data/README.md
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
# Micro Agent
|
2
|
+
by VisFleet
|
3
|
+
|
4
|
+
http://www.vworkapp.com
|
5
|
+
|
6
|
+
## Overview:
|
7
|
+
|
8
|
+
A simple simulator. This simulates changes to a collection of agents over a period
|
9
|
+
of time.
|
10
|
+
|
11
|
+
Setup the world. Each world is populated with a number of agents. Each agent has a collection
|
12
|
+
of properties that change over time. Properties change based on the rules you provide. Properties
|
13
|
+
can also depend on each other (as long as you don't create a cycle!).
|
14
|
+
|
15
|
+
update = lambda { |agent| self.update(agent) }
|
16
|
+
@world = Micro::World.new(1, 1, 1.0) do |i|
|
17
|
+
Micro::MarkovAgent.new(
|
18
|
+
:speed => Agent::Parameter.new do |p|
|
19
|
+
p.start_value = (0..100).to_a.rand
|
20
|
+
p.probability = 0.3
|
21
|
+
p.max = 100
|
22
|
+
p.min = 0
|
23
|
+
p.change_func = lambda { |value| (value + (-10..10).to_a.rand) }
|
24
|
+
end,
|
25
|
+
|
26
|
+
:distance => Agent::Parameter.new do |p|
|
27
|
+
p.start_value = 0
|
28
|
+
p.depends_on :speed
|
29
|
+
p.change_func = lambda do |distance, speed|
|
30
|
+
distance + (speed / 1.hour)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
)
|
34
|
+
end
|
35
|
+
|
36
|
+
Set the world in motion.
|
37
|
+
|
38
|
+
@world.start
|
39
|
+
|
40
|
+
Get a callback on each change
|
41
|
+
|
42
|
+
def change
|
43
|
+
@world.agents.each do |agent|
|
44
|
+
pp agent
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
## Install:
|
49
|
+
|
50
|
+
sudo gem install visfleet-micro_agent
|
51
|
+
|
52
|
+
### Dependancies
|
53
|
+
|
54
|
+
- eventmachine (http://rubyeventmachine.com/)
|
55
|
+
|
56
|
+
## LICENSE:
|
57
|
+
|
58
|
+
(The MIT License)
|
59
|
+
|
60
|
+
Copyright (c) 2008 FIX
|
61
|
+
|
62
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
63
|
+
a copy of this software and associated documentation files (the
|
64
|
+
'Software'), to deal in the Software without restriction, including
|
65
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
66
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
67
|
+
permit persons to whom the Software is furnished to do so, subject to
|
68
|
+
the following conditions:
|
69
|
+
|
70
|
+
The above copyright notice and this permission notice shall be
|
71
|
+
included in all copies or substantial portions of the Software.
|
72
|
+
|
73
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
74
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
75
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
76
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
77
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
78
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
79
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/examples/drivers.rb
ADDED
@@ -0,0 +1,114 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
|
3
|
+
$:.unshift(File.dirname(__FILE__) + '/../lib')
|
4
|
+
|
5
|
+
require "micro_agent"
|
6
|
+
require "active_support"
|
7
|
+
require "pp"
|
8
|
+
|
9
|
+
class Drivers
|
10
|
+
|
11
|
+
def initialize
|
12
|
+
setup_world
|
13
|
+
@inner_kml = ""
|
14
|
+
end
|
15
|
+
|
16
|
+
def setup_world
|
17
|
+
update = lambda { |agent| self.populate_inner_kml(agent) }
|
18
|
+
@world = Micro::World.new(nil, 1, 1.0, update) do |i|
|
19
|
+
Micro::MarkovAgent.new(
|
20
|
+
:name => Micro::Parameter.new do |p|
|
21
|
+
p.start_value = "sim_#{i.to_s}"
|
22
|
+
end,
|
23
|
+
|
24
|
+
:heading => Micro::Parameter.new do |p|
|
25
|
+
p.start_value = (0..359).to_a.rand
|
26
|
+
p.probability = 0.1
|
27
|
+
p.change_func = lambda { |value| (value + (0..120).to_a.rand) % 360 }
|
28
|
+
end,
|
29
|
+
|
30
|
+
:ignition => Micro::Parameter.new do |p|
|
31
|
+
p.start_value = true
|
32
|
+
p.probability = 0.001
|
33
|
+
p.change_func = lambda { |value| !value}
|
34
|
+
end,
|
35
|
+
|
36
|
+
:speed => Micro::Parameter.new do |p|
|
37
|
+
p.start_value = (1..130).to_a.rand
|
38
|
+
p.depends_on = :ignition
|
39
|
+
p.probability = 0.2
|
40
|
+
p.change_func = lambda { |value, ignition| ignition ? value + (-30..30).to_a.rand : 0 }
|
41
|
+
p.max = 135
|
42
|
+
p.min = 0
|
43
|
+
end,
|
44
|
+
|
45
|
+
:seconds_delta => Micro::Parameter.new do |p|
|
46
|
+
p.start_value = 0
|
47
|
+
p.change_func = lambda { |value| (1..5).to_a.rand }
|
48
|
+
end,
|
49
|
+
|
50
|
+
:distance_delta => Micro::Parameter.new do |p|
|
51
|
+
p.start_value = 0
|
52
|
+
p.depends_on = :speed, :seconds_delta
|
53
|
+
p.change_func = lambda do |distance_delta, speed, seconds_delta|
|
54
|
+
# assume speed in kmph
|
55
|
+
distance_delta = (speed.to_f / 1.hour) * seconds_delta
|
56
|
+
end
|
57
|
+
end,
|
58
|
+
|
59
|
+
:y => Micro::Parameter.new do |p|
|
60
|
+
p.start_value = -39.0
|
61
|
+
p.depends_on = :heading, :distance_delta
|
62
|
+
p.change_func = lambda do |y, heading, distance_delta|
|
63
|
+
# assume heading in degress
|
64
|
+
y_km = Math.cos(heading / (180/Math::PI)) * distance_delta
|
65
|
+
# assume 111 km per 1 degree
|
66
|
+
y + (y_km / 111.0)
|
67
|
+
end
|
68
|
+
end,
|
69
|
+
|
70
|
+
:x => Micro::Parameter.new do |p|
|
71
|
+
p.start_value = 176.0
|
72
|
+
p.depends_on = :heading, :distance_delta, :y
|
73
|
+
p.change_func = lambda do |x, heading, distance_delta, y|
|
74
|
+
x_km = Math.sin(heading / (180/Math::PI)) * distance_delta
|
75
|
+
# assume spheriod earth
|
76
|
+
x + x_km / (Math.cos(y / (180/Math::PI)) * 111.0)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def step(number)
|
84
|
+
number.times { @world.step_agents }
|
85
|
+
end
|
86
|
+
|
87
|
+
def populate_inner_kml(agent)
|
88
|
+
@inner_kml << <<-EOS
|
89
|
+
<Placemark>
|
90
|
+
<name>#{agent[:name]}</name>
|
91
|
+
<Point>
|
92
|
+
<coordinates>#{agent[:x]}, #{agent[:y]}</coordinates>
|
93
|
+
</Point>
|
94
|
+
</Placemark>
|
95
|
+
EOS
|
96
|
+
end
|
97
|
+
|
98
|
+
def to_kml
|
99
|
+
<<-EOS
|
100
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
101
|
+
<kml xmlns="http://www.opengis.net/kml/2.2">
|
102
|
+
<Document>
|
103
|
+
#{@inner_kml}
|
104
|
+
</Document>
|
105
|
+
</kml>
|
106
|
+
EOS
|
107
|
+
end
|
108
|
+
|
109
|
+
end
|
110
|
+
|
111
|
+
d = Drivers.new
|
112
|
+
d.step(100)
|
113
|
+
puts d.to_kml
|
114
|
+
|
data/lib/micro_agent.rb
ADDED
@@ -0,0 +1,168 @@
|
|
1
|
+
require "rubygems"
|
2
|
+
require 'eventmachine'
|
3
|
+
|
4
|
+
module Micro
|
5
|
+
|
6
|
+
# The world populates
|
7
|
+
#
|
8
|
+
class World
|
9
|
+
attr_accessor :agents, :step_proc, :begin_proc, :end_proc
|
10
|
+
|
11
|
+
def initialize(cycle_delay_seconds, number_of_agents, percent_per_cycle = 1.0, step_proc = nil, begin_proc = nil, end_proc = nil, &block)
|
12
|
+
@cycle_delay_seconds = cycle_delay_seconds
|
13
|
+
@step_proc = step_proc
|
14
|
+
@begin_proc = begin_proc
|
15
|
+
@end_proc = end_proc
|
16
|
+
@number_of_agents = number_of_agents
|
17
|
+
@create_agent_proc = block
|
18
|
+
@percent_per_cycle = percent_per_cycle
|
19
|
+
create_agents
|
20
|
+
end
|
21
|
+
|
22
|
+
def create_agents
|
23
|
+
@agents = []
|
24
|
+
@number_of_agents.times do |i|
|
25
|
+
@agents << @create_agent_proc.call(i)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def start
|
30
|
+
EM.run do
|
31
|
+
EventMachine::add_periodic_timer( @cycle_delay_seconds ) { step_agents }
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def stop
|
36
|
+
EM.stop
|
37
|
+
end
|
38
|
+
|
39
|
+
def step_agents
|
40
|
+
@begin_proc.call unless @begin_proc.nil?
|
41
|
+
@agents.each do |agent|
|
42
|
+
next unless rand <= @percent_per_cycle
|
43
|
+
agent.step
|
44
|
+
@step_proc.call(agent) unless @step_proc.nil?
|
45
|
+
end
|
46
|
+
@end_proc.call unless @end_proc.nil?
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
|
51
|
+
# An agent is a autonomous entity that interacts with the Micro::World. Each agent has a collection
|
52
|
+
# of properties that are updated over time (i.e. with each call to #step_agents in the #World).
|
53
|
+
#
|
54
|
+
# Dependancy relationships can be setup between an agent's parameters. So that properties:
|
55
|
+
# a.depends_on b.depends_on c
|
56
|
+
# d.depends_on b.depends_on c
|
57
|
+
# b.depends_on c
|
58
|
+
# This is useful where one parameter is required for calculation of another. For example, calculating a
|
59
|
+
# _speed_ parameter might require you to know the values of the _distance_ and _time_ parameters. #step_agents
|
60
|
+
# calculates dependancies in the correct order, making that a parameter's dependants are calculated first .
|
61
|
+
# Note however that cyclic depends_on relationships aren't supported.
|
62
|
+
#
|
63
|
+
# The runtime complexity of processing dependacies is still O(n) (where n is the number of parameters) even with
|
64
|
+
# complex dependency trees thanks to the niftyness of dynamic programming algorithms.
|
65
|
+
#
|
66
|
+
class MarkovAgent
|
67
|
+
attr_accessor :parameters
|
68
|
+
|
69
|
+
def initialize(parameters)
|
70
|
+
@parameters = parameters
|
71
|
+
@already_done = Hash.new
|
72
|
+
end
|
73
|
+
|
74
|
+
# Updates the parameter value using the parameter's change function. Each parameter is updated with
|
75
|
+
# probability equal to its probability value.
|
76
|
+
#
|
77
|
+
def step(number = 1)
|
78
|
+
number.times do
|
79
|
+
@already_done.clear
|
80
|
+
@parameters.each_value do |parameter|
|
81
|
+
process(parameter)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def [](parameter_name)
|
87
|
+
@parameters[parameter_name].value
|
88
|
+
end
|
89
|
+
|
90
|
+
def values
|
91
|
+
value_hash = {}
|
92
|
+
@parameters.each_pair { |key, parameter| value_hash[key.to_s] = parameter.value }
|
93
|
+
value_hash
|
94
|
+
end
|
95
|
+
|
96
|
+
private
|
97
|
+
|
98
|
+
def process(parameter)
|
99
|
+
return parameter.value if @already_done[parameter] || parameter.change_func.nil?
|
100
|
+
|
101
|
+
values = [parameter.value]
|
102
|
+
parameter.depends_on.each do |p_name|
|
103
|
+
raise Exception.new("Can't 'depend_on' parameter #{p_name}. It doesn't exist") unless @parameters.has_key?(p_name)
|
104
|
+
values << process(@parameters[p_name])
|
105
|
+
end
|
106
|
+
|
107
|
+
if rand() <= parameter.probability
|
108
|
+
parameter.value = parameter.change_func.call(*values)
|
109
|
+
parameter.value = limit_between(parameter.value, parameter.min, parameter.max)
|
110
|
+
end
|
111
|
+
@already_done[parameter] = true
|
112
|
+
parameter.value
|
113
|
+
end
|
114
|
+
|
115
|
+
def limit_between(value, min, max)
|
116
|
+
raise Exception.new("Max can't be less than Min") if min && max && (max < min)
|
117
|
+
value = [value, min].max if min
|
118
|
+
value = [value, max].min if max
|
119
|
+
value
|
120
|
+
end
|
121
|
+
|
122
|
+
end
|
123
|
+
|
124
|
+
# Represents an individual parameter of an agent. Parameters are updated with each call to #step_world.
|
125
|
+
# See the attributes below for a description of the properties that are possible on each parameter.
|
126
|
+
#
|
127
|
+
class Parameter
|
128
|
+
# The probility that this parameter is updated. Should be between 0.0 and 1.0.
|
129
|
+
attr_accessor :probability
|
130
|
+
|
131
|
+
# The starting value of this parameter
|
132
|
+
attr_accessor :start_value
|
133
|
+
|
134
|
+
# Takes a passed in block. The block is used to update this parameter's value. The block is passed the current
|
135
|
+
# value of this parameter and then the value of any dependant parameter's values (in the order they are specified
|
136
|
+
# in the depends_on property). This looks like:
|
137
|
+
#
|
138
|
+
# :speed => Micro::Parameter.new do |p|
|
139
|
+
# p.start_value = 0
|
140
|
+
# p.depends_on = :distance, :time
|
141
|
+
# p.change_func = lambda { |value, distance, time| distance / time }
|
142
|
+
# end
|
143
|
+
#
|
144
|
+
attr_accessor :change_func
|
145
|
+
|
146
|
+
# Specifies which parameter's this parameter is dependent on for it's calculations. See #change_func
|
147
|
+
attr_accessor :depends_on
|
148
|
+
|
149
|
+
# Specifies an upper and lower bound on this parameter's value.
|
150
|
+
attr_accessor :max, :min
|
151
|
+
|
152
|
+
attr_accessor :value
|
153
|
+
|
154
|
+
def initialize
|
155
|
+
@depends_on = []
|
156
|
+
@probability = 1.0
|
157
|
+
yield self
|
158
|
+
@value = @start_value
|
159
|
+
end
|
160
|
+
|
161
|
+
def depends_on=(p_names)
|
162
|
+
p_names = [p_names] unless p_names.instance_of? Array
|
163
|
+
@depends_on = p_names
|
164
|
+
end
|
165
|
+
|
166
|
+
end
|
167
|
+
|
168
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
$:.unshift(File.dirname(__FILE__) + '/../lib')
|
2
|
+
require 'micro_agent'
|
3
|
+
require 'active_support'
|
4
|
+
|
5
|
+
describe Micro::World, "with a single agent" do
|
6
|
+
|
7
|
+
before(:each) do
|
8
|
+
@world = Micro::World.new(1, 1) do |i|
|
9
|
+
Micro::MarkovAgent.new(
|
10
|
+
:speed => Micro::Parameter.new do |p|
|
11
|
+
p.start_value = (0..100).to_a.rand
|
12
|
+
p.probability = 0.3
|
13
|
+
p.max = 100
|
14
|
+
p.min = 0
|
15
|
+
p.change_func = lambda { |value| (value + (-10..10).to_a.rand) }
|
16
|
+
end,
|
17
|
+
:distance => Micro::Parameter.new do |p|
|
18
|
+
p.start_value = 0
|
19
|
+
p.depends_on = :speed
|
20
|
+
p.change_func = lambda do |distance, speed|
|
21
|
+
distance + (speed / 1.hour)
|
22
|
+
end
|
23
|
+
end,
|
24
|
+
:name => Micro::Parameter.new do |p|
|
25
|
+
p.start_value = "name"
|
26
|
+
end
|
27
|
+
)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should use the start_value if no change function is given" do
|
32
|
+
@world.step_proc = lambda do |agent|
|
33
|
+
agent[:name].should == "name"
|
34
|
+
end
|
35
|
+
|
36
|
+
@world.step_agents
|
37
|
+
@world.step_agents
|
38
|
+
@world.step_agents
|
39
|
+
end
|
40
|
+
|
41
|
+
it "should stop when asked to" do
|
42
|
+
@world.step_proc = lambda { |agent| @world.stop }
|
43
|
+
@world.start
|
44
|
+
end
|
45
|
+
|
46
|
+
it "should update it's parameters"
|
47
|
+
|
48
|
+
it "should handle dependant parameters"
|
49
|
+
|
50
|
+
it "should call its callbacks" do
|
51
|
+
obj = mock("obj")
|
52
|
+
@world.begin_proc = lambda { obj.start }
|
53
|
+
@world.step_proc = lambda { |agent| obj.during }
|
54
|
+
@world.end_proc = lambda { obj.end }
|
55
|
+
|
56
|
+
obj.should_receive(:start).once
|
57
|
+
obj.should_receive(:during).once
|
58
|
+
obj.should_receive(:end).once
|
59
|
+
@world.step_agents
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
63
|
+
|
64
|
+
describe Micro::World, "with many agents" do
|
65
|
+
|
66
|
+
before(:each) do
|
67
|
+
@world = Micro::World.new(1, 10) do |i|
|
68
|
+
Micro::MarkovAgent.new(
|
69
|
+
:speed => Micro::Parameter.new do |p|
|
70
|
+
p.start_value = (0..100).to_a.rand
|
71
|
+
p.probability = 0.3
|
72
|
+
p.max = 100
|
73
|
+
p.min = 0
|
74
|
+
p.change_func = lambda { |value| (value + (-10..10).to_a.rand) }
|
75
|
+
end
|
76
|
+
)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
it "should call its callback for each agent update" do
|
81
|
+
obj = mock("obj")
|
82
|
+
@world.begin_proc = lambda { obj.start }
|
83
|
+
@world.step_proc = lambda { |agent| obj.callback }
|
84
|
+
@world.end_proc = lambda { obj.end }
|
85
|
+
|
86
|
+
obj.should_receive(:start).once
|
87
|
+
obj.should_receive(:callback).exactly(10)
|
88
|
+
obj.should_receive(:end).once
|
89
|
+
@world.step_agents
|
90
|
+
end
|
91
|
+
|
92
|
+
it "should update the passed in percentage of the population each step"
|
93
|
+
|
94
|
+
end
|
metadata
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: micro_agent
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 1
|
8
|
+
- 6
|
9
|
+
version: 0.1.6
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- VisFleet
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
|
17
|
+
date: 2009-03-01 00:00:00 +13:00
|
18
|
+
default_executable:
|
19
|
+
dependencies: []
|
20
|
+
|
21
|
+
description: A simple simulator
|
22
|
+
email: aisha.fenton@visfleet.com
|
23
|
+
executables: []
|
24
|
+
|
25
|
+
extensions: []
|
26
|
+
|
27
|
+
extra_rdoc_files:
|
28
|
+
- README.md
|
29
|
+
files:
|
30
|
+
- examples/drivers.rb
|
31
|
+
- lib/micro_agent.rb
|
32
|
+
- README.md
|
33
|
+
- spec/micro_agent_spec.rb
|
34
|
+
has_rdoc: true
|
35
|
+
homepage: http://www.vworkapp.com
|
36
|
+
licenses: []
|
37
|
+
|
38
|
+
post_install_message:
|
39
|
+
rdoc_options: []
|
40
|
+
|
41
|
+
require_paths:
|
42
|
+
- lib
|
43
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
segments:
|
48
|
+
- 0
|
49
|
+
version: "0"
|
50
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
segments:
|
55
|
+
- 0
|
56
|
+
version: "0"
|
57
|
+
requirements: []
|
58
|
+
|
59
|
+
rubyforge_project:
|
60
|
+
rubygems_version: 1.3.6
|
61
|
+
signing_key:
|
62
|
+
specification_version: 3
|
63
|
+
summary: A simple simulator. This gem simulates changes to a collection of agents over a period of time.
|
64
|
+
test_files: []
|
65
|
+
|