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