micro_agent 0.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
+
|