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 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.
@@ -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
+
@@ -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
+