redshift 1.3.15 → 1.3.16
Sign up to get free protection for your applications and to get access to all the features.
- data/RELEASE-NOTES +4 -0
- data/bench/diff-bench +2 -2
- data/bench/run +1 -1
- data/bench/strictness.rb +2 -2
- data/examples/ball.rb +1 -1
- data/examples/collide.rb +1 -1
- data/examples/delay.rb +1 -1
- data/examples/derivative.rb +1 -1
- data/examples/euler.rb +1 -1
- data/examples/lotka-volterra.rb +1 -1
- data/examples/pid.rb +1 -1
- data/examples/subsystem.rb +1 -1
- data/examples/thermostat.rb +1 -1
- data/lib/redshift/component.rb +2 -2
- data/lib/redshift/redshift.rb +2 -2
- data/lib/{accessible-index.rb → redshift/util/accessible-index.rb} +0 -0
- data/lib/redshift/util/argos.rb +214 -0
- data/lib/redshift/util/histogram.rb +155 -0
- data/lib/redshift/util/object-diff.rb +83 -0
- data/lib/redshift/util/plot.rb +386 -0
- data/lib/redshift/util/random.rb +261 -0
- data/lib/redshift/util/superhash.rb +454 -0
- data/lib/redshift/util/tracer.rb +113 -0
- data/lib/redshift/util/tracer/trace.rb +145 -0
- data/lib/redshift/util/tracer/var.rb +112 -0
- data/rakefile +2 -2
- data/test/test_flow_trans.rb +28 -25
- metadata +45 -7
- data/examples/persist-ball.rb +0 -68
@@ -0,0 +1,113 @@
|
|
1
|
+
# Records values of variables over time.
|
2
|
+
#
|
3
|
+
# A Tracer manages a two-tier system typical of simulations: Object and
|
4
|
+
# Variable. The Object tier is really a hash on arbitrary keys. The keys can be
|
5
|
+
# simulation objects, or they can be strings, symbols, etc.
|
6
|
+
#
|
7
|
+
# The values of this hash are the the Variable tier. More precisely, each value
|
8
|
+
# is another hash mapping variable name to Tracer::Var instances.
|
9
|
+
#
|
10
|
+
# The Tracer::Var class provides the primary per-variable functionality, and it
|
11
|
+
# can be used without the Tracer class. For example, it could be attched
|
12
|
+
# directly to the simulation object itself.
|
13
|
+
#
|
14
|
+
# The two-tier structure permits add/remove operations at the Object tier, which
|
15
|
+
# is convenient when objects enter or leave a simulation. Keeping the Tracer
|
16
|
+
# tiers separate from simulation objects makes it easier to run those objects
|
17
|
+
# without tracing, or to change the tracing strategy.
|
18
|
+
#
|
19
|
+
class Tracer
|
20
|
+
require 'redshift/util/tracer/var'
|
21
|
+
|
22
|
+
attr_reader :var_map, :default_opts
|
23
|
+
|
24
|
+
# Construct a Tracer which passes the +default_opts+ on to each
|
25
|
+
# var, overridden by the opts passed to #add.
|
26
|
+
def initialize default_opts = {}
|
27
|
+
@var_map = {}
|
28
|
+
@default_opts = default_opts
|
29
|
+
end
|
30
|
+
|
31
|
+
# Adds an item to the list of things to be recorded during #run!.
|
32
|
+
#
|
33
|
+
# If no block is given, the +key+ must be an object which has an
|
34
|
+
# attribute +name+.
|
35
|
+
#
|
36
|
+
# If a block is given, then it is called during #run! to determine
|
37
|
+
# the value that is stored in the Tracer. The +key+ and +name+ can be
|
38
|
+
# anything and are significant only for looking up traces in the Tracer.
|
39
|
+
# If the block accepts an argument, the associated +Var+ object is passed.
|
40
|
+
#
|
41
|
+
def add key, name, opts={}, &block
|
42
|
+
@var_map[key] ||= {}
|
43
|
+
var = Var.new(key, name, default_opts.merge(opts), &block)
|
44
|
+
@var_map[key][name] = var
|
45
|
+
end
|
46
|
+
|
47
|
+
# If +name+ is given, remove the trace of that variable
|
48
|
+
# associated with +key+. If name is not given, remove all traces
|
49
|
+
# associated with +key+. The removed Tracer::Var or hash of such objects
|
50
|
+
# is returned.
|
51
|
+
def remove key, name = nil
|
52
|
+
if name
|
53
|
+
h = @var_map[key]
|
54
|
+
r = h.delete name
|
55
|
+
@var_map.delete key if h.empty?
|
56
|
+
r
|
57
|
+
else
|
58
|
+
@var_map.delete key
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
# Called during a sumulation to record traces. In RedShift, typically called
|
63
|
+
# in the evolve {..} block, or in action clauses of transitions, or in
|
64
|
+
# hook methods.
|
65
|
+
def run!
|
66
|
+
@var_map.each do |key, h|
|
67
|
+
h.each do |name, var|
|
68
|
+
var.run!
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
# Convenience method to get a Var or a hash of Vars. Returns +nil+ if not
|
74
|
+
# found.
|
75
|
+
def [](key, name = nil)
|
76
|
+
if name
|
77
|
+
h = @var_map[key]
|
78
|
+
h && h[name]
|
79
|
+
else
|
80
|
+
@var_map[key]
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
class MissingVariableError < StandardError; end
|
85
|
+
class MissingKeyError < StandardError; end
|
86
|
+
|
87
|
+
# Yield once for each var associated with the +key+, or just once if
|
88
|
+
# +name+ given.
|
89
|
+
def each_var(key, name = nil)
|
90
|
+
if name
|
91
|
+
var = self[key, name] or
|
92
|
+
raise MissingVariableError, "No such var, #{key}.#{name}"
|
93
|
+
yield var
|
94
|
+
else
|
95
|
+
h = @var_map[key] or
|
96
|
+
raise MissingKeyError, "No such key, #{key}"
|
97
|
+
h.each do |name, var|
|
98
|
+
yield var
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
# Make a variable (or all vars of a key) inactive. No data will be traced
|
104
|
+
# unti #start is called.
|
105
|
+
def stop(key, name = nil)
|
106
|
+
each_var(key, name) {|var| var.active = false}
|
107
|
+
end
|
108
|
+
|
109
|
+
# Make a variable (or all vars of a key) active.
|
110
|
+
def start(key, name = nil)
|
111
|
+
each_var(key, name) {|var| var.active = true}
|
112
|
+
end
|
113
|
+
end
|
@@ -0,0 +1,145 @@
|
|
1
|
+
class Tracer
|
2
|
+
begin
|
3
|
+
require 'narray'
|
4
|
+
|
5
|
+
rescue LoadError
|
6
|
+
warn "Can't find narray lib;" +
|
7
|
+
" using Array instead of NVector for trace storage."
|
8
|
+
class Trace < Array
|
9
|
+
def initialize(*); super(); end
|
10
|
+
end
|
11
|
+
|
12
|
+
else
|
13
|
+
# A Trace is a linear store. Usually, the type of the stored data is
|
14
|
+
# homogenous and numeric. It is optimized for appending new data at the
|
15
|
+
# end of the store. Insertions are not efficient.
|
16
|
+
#
|
17
|
+
# If we have NArray, the we can use it for more compact storage than an
|
18
|
+
# array of ruby objects. The Trace class manages a list of fixed-size
|
19
|
+
# NVectors. Otherwise, if NArray is not available, Trace is just a ruby
|
20
|
+
# array.
|
21
|
+
class Trace
|
22
|
+
include Enumerable
|
23
|
+
|
24
|
+
DEFAULT_TYPE = "float"
|
25
|
+
|
26
|
+
DEFAULT_CHUNK_SIZE = 128
|
27
|
+
|
28
|
+
# The +opts+ is a hash of the form:
|
29
|
+
#
|
30
|
+
# { :type => t, :dims => nil or [d1,d2,...], :chunk_size => s }
|
31
|
+
#
|
32
|
+
# Strings may be used instead of symbols as the keys.
|
33
|
+
#
|
34
|
+
# The +type+ is passed to NVector.new. The +chunk_size+ is the size of
|
35
|
+
# each NVector. The +dims+ is the dimensions of each entry; default is
|
36
|
+
# +nil+, which means scalar entries, as does an empty array.
|
37
|
+
def initialize opts = {}
|
38
|
+
@type = (opts[:type] || opts["type"] || DEFAULT_TYPE).to_s
|
39
|
+
@chunk_size = opts[:chunk_size] || opts["chunk_size"] ||
|
40
|
+
DEFAULT_CHUNK_SIZE
|
41
|
+
@dims = opts[:dims] || opts["dims"] || []
|
42
|
+
@index_template = @dims.map {|d| true}
|
43
|
+
@index_template << nil
|
44
|
+
@nvector_new_args = [@type, *@dims] << @chunk_size
|
45
|
+
clear
|
46
|
+
end
|
47
|
+
|
48
|
+
# Clear the trace. Does not affect any state of the Var, such as
|
49
|
+
# the period counter.
|
50
|
+
def clear
|
51
|
+
@chunk_list = []
|
52
|
+
@index = @chunk_size # index of next insertion
|
53
|
+
end
|
54
|
+
|
55
|
+
# Iterate over values. (Note that this iteration yields the complete
|
56
|
+
# data structure for a point in time, whereas NVector#each iterates over
|
57
|
+
# individual numbers.)
|
58
|
+
def each
|
59
|
+
last = @chunk_list.last
|
60
|
+
it = @index_template.dup
|
61
|
+
@chunk_list.each do |chunk|
|
62
|
+
if chunk == last and @index < @chunk_size
|
63
|
+
@chunk_size.times do |i|
|
64
|
+
break if i == @index
|
65
|
+
it[-1] = i
|
66
|
+
yield chunk[*it]
|
67
|
+
end
|
68
|
+
else
|
69
|
+
@chunk_size.times do |i|
|
70
|
+
it[-1] = i
|
71
|
+
yield chunk[*it]
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def size
|
78
|
+
(@chunk_list.size - 1) * @chunk_size + @index
|
79
|
+
end
|
80
|
+
|
81
|
+
alias length size
|
82
|
+
|
83
|
+
def << item
|
84
|
+
if @index == @chunk_size
|
85
|
+
@chunk_list << NVector.new(*@nvector_new_args)
|
86
|
+
@index = 0
|
87
|
+
end
|
88
|
+
@index_template[-1] = @index
|
89
|
+
@chunk_list.last[*@index_template] = item
|
90
|
+
@index += 1
|
91
|
+
self
|
92
|
+
end
|
93
|
+
|
94
|
+
alias push <<
|
95
|
+
|
96
|
+
# This is an inefficient, but sometimes convenient, implementation
|
97
|
+
# of #[]. For efficiency, use the Enumerable methods, if possible, or
|
98
|
+
# use #to_vector. This implementation does support negative indices.
|
99
|
+
def [](i)
|
100
|
+
if i.abs >= size
|
101
|
+
raise IndexError, "index out of range"
|
102
|
+
end
|
103
|
+
|
104
|
+
i %= size
|
105
|
+
it = @index_template.dup
|
106
|
+
it[-1] = i % @chunk_size
|
107
|
+
@chunk_list[i / @chunk_size][*it]
|
108
|
+
end
|
109
|
+
|
110
|
+
# This is the most efficient way to manipulate the data if Enumerable
|
111
|
+
# methods are not enough. It returns a copy of the trace data as a
|
112
|
+
# NVector, which supports a complete set of indexed collection methods and
|
113
|
+
# algebraic operations, including slicing, sorting, reshaping, statistics,
|
114
|
+
# and scalar and vector math. See NArray docs for details.
|
115
|
+
# Note that #[] works differently on NArray than the one defined above..
|
116
|
+
def to_vector
|
117
|
+
nva = @nvector_new_args.dup
|
118
|
+
nva[-1] = size
|
119
|
+
v = NVector.new(*nva)
|
120
|
+
last = @chunk_list.last
|
121
|
+
cs = @chunk_size
|
122
|
+
it = @index_template.dup
|
123
|
+
@chunk_list.each_with_index do |chunk, ci|
|
124
|
+
base = ci * @chunk_size
|
125
|
+
if chunk == last and @index < @chunk_size
|
126
|
+
it[-1] = base...base+@index
|
127
|
+
v[*it] = chunk[true, 0...@index]
|
128
|
+
else
|
129
|
+
it[-1] = base...base+cs
|
130
|
+
v[*it] = chunk
|
131
|
+
end
|
132
|
+
end
|
133
|
+
v
|
134
|
+
end
|
135
|
+
|
136
|
+
def inspect
|
137
|
+
to_vector.inspect
|
138
|
+
end
|
139
|
+
|
140
|
+
def to_s
|
141
|
+
entries.to_s
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
require 'sci/tracer/trace'
|
2
|
+
|
3
|
+
class Tracer
|
4
|
+
# Represents the various options and controls associated with a variable
|
5
|
+
# being traced. The #trace attr holds the actual data. The Var object
|
6
|
+
# references the original object, and will therefore keep it from being GC-ed.
|
7
|
+
# If you want to retain the data without the object, keep only the #trace.
|
8
|
+
class Var
|
9
|
+
# Type affects precision and storage size. Can be any of the following
|
10
|
+
# strings, which are the same as the NArray scalar types:
|
11
|
+
#
|
12
|
+
# "byte" :: 1 byte unsigned integer
|
13
|
+
# "sint" :: 2 byte signed integer
|
14
|
+
# "int" :: 4 byte signed integer
|
15
|
+
# "sfloat" :: single precision float
|
16
|
+
# "float" :: double precision float
|
17
|
+
#
|
18
|
+
attr_reader :type
|
19
|
+
|
20
|
+
# Chunk size is the size of the chunks (in values, not bytes) in which
|
21
|
+
# memory is allocated. Each chunk is a +Vector+; the list of chunks is
|
22
|
+
# a ruby array. This only needs to be adjusted for performance tuning.
|
23
|
+
attr_reader :chunk_size
|
24
|
+
|
25
|
+
# Dimensions of each entry. A vlaue of +nil+ means scalar entries, as
|
26
|
+
# does an empty array. A value of [d1, d2...] indicates multidimensional
|
27
|
+
# data.
|
28
|
+
attr_reader :dims
|
29
|
+
|
30
|
+
# Is this var currently recording? See Tracer#stop and Tracer#start.
|
31
|
+
attr_accessor :active
|
32
|
+
|
33
|
+
# How many calls to #run! before trace is updated? A period of 0, 1, or
|
34
|
+
# nil means trace is updated on each call.
|
35
|
+
attr_accessor :period
|
36
|
+
|
37
|
+
# Counter to check for period expiration.
|
38
|
+
attr_accessor :counter
|
39
|
+
|
40
|
+
# Optional code to compute value, rather than use +name+. During #run!, the
|
41
|
+
# code will be called; if it accepts an arg, it will be passed the Var, from
|
42
|
+
# which #key and #name can be read. If code returns nil/false, no data is
|
43
|
+
# added to the trace.
|
44
|
+
attr_accessor :value_getter
|
45
|
+
|
46
|
+
# The +key+ can be either an object that has a named variable (accessed by
|
47
|
+
# the #name attr) or some arbitrary object (in which case a value_getter
|
48
|
+
# must be provided to compute the value).
|
49
|
+
attr_reader :key
|
50
|
+
|
51
|
+
# The name of the variable to access in the object.
|
52
|
+
attr_reader :name
|
53
|
+
|
54
|
+
# Stores the trace as a list of Vectors
|
55
|
+
attr_reader :trace
|
56
|
+
|
57
|
+
DEFAULT_TYPE = "float"
|
58
|
+
|
59
|
+
DEFAULT_CHUNK_SIZE = 128
|
60
|
+
|
61
|
+
DEFAULT_PERIOD = nil
|
62
|
+
|
63
|
+
# The +opts+ is a hash of the form:
|
64
|
+
#
|
65
|
+
# { :type => t, :chunk_size => s,:dims => nil or [d1,d2,...],
|
66
|
+
# :period => p }
|
67
|
+
#
|
68
|
+
# Strings may be used instead of symbols as the keys.
|
69
|
+
#
|
70
|
+
def initialize key, name, opts={}, &value_getter
|
71
|
+
@key, @name, @value_getter = key, name, value_getter
|
72
|
+
|
73
|
+
@type = (opts[:type] || opts["type"] || DEFAULT_TYPE).to_s
|
74
|
+
@chunk_size = opts[:chunk_size] || opts["chunk_size"] ||
|
75
|
+
DEFAULT_CHUNK_SIZE
|
76
|
+
@dims = opts[:dims] || opts["dims"] || []
|
77
|
+
@period = opts[:period] || opts["period"] || DEFAULT_PERIOD
|
78
|
+
|
79
|
+
@period = nil unless @period.kind_of?(Integer) and @period > 1
|
80
|
+
@counter = 0
|
81
|
+
@active = true
|
82
|
+
@trace = Trace.new(
|
83
|
+
:type => type, :chunk_size => chunk_size, :dims => dims)
|
84
|
+
end
|
85
|
+
|
86
|
+
def run!
|
87
|
+
return unless @active
|
88
|
+
|
89
|
+
if @period
|
90
|
+
@counter += 1
|
91
|
+
return if @counter < @period
|
92
|
+
@counter = 0
|
93
|
+
end
|
94
|
+
|
95
|
+
result =
|
96
|
+
if (vg=@value_getter)
|
97
|
+
case vg.arity
|
98
|
+
when 0; vg.call
|
99
|
+
when 1,-1; vg.call(self)
|
100
|
+
else raise ArgumentError,
|
101
|
+
"value_getter for #{@key}.#{@name} must take 0 or 1 arguments"
|
102
|
+
end
|
103
|
+
else
|
104
|
+
@key.send @name
|
105
|
+
end
|
106
|
+
|
107
|
+
if result
|
108
|
+
@trace << result
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
data/rakefile
CHANGED
@@ -25,10 +25,10 @@ END
|
|
25
25
|
|
26
26
|
history_file 'RELEASE-NOTES'
|
27
27
|
changes File.read(history_file)[/^\w.*?(?=^\w)/m]
|
28
|
-
|
28
|
+
rdoc.dir "rdoc" ## Note: doc dir has original content files
|
29
29
|
|
30
30
|
spec.opts << '--color'
|
31
|
-
test.files = Dir["test/
|
31
|
+
test.files = Dir["test/test_*.rb"]
|
32
32
|
}
|
33
33
|
|
34
34
|
task :release => ["rubyforge:release", "rubyforge:doc_release"]
|
data/test/test_flow_trans.rb
CHANGED
@@ -1,24 +1,25 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
3
|
require 'redshift'
|
4
|
-
require '
|
5
|
-
|
4
|
+
require 'redshift/util/random'
|
5
|
+
### TODO: add pure ruby ISAAC to util
|
6
|
+
#require 'isaac'
|
6
7
|
|
7
|
-
# Adaptor class to use ISAAC with
|
8
|
-
class ISAACGenerator < ISAAC
|
9
|
-
def initialize(*seeds)
|
10
|
-
super()
|
11
|
-
if seeds.compact.empty?
|
12
|
-
seeds = [Random::Sequence.random_seed]
|
13
|
-
end
|
14
|
-
@seeds = seeds
|
15
|
-
srand(seeds)
|
16
|
-
end
|
17
|
-
|
18
|
-
attr_reader :seeds
|
19
|
-
|
20
|
-
alias next rand
|
21
|
-
end
|
8
|
+
# Adaptor class to use ISAAC with redshift/util/random distributions.
|
9
|
+
#class ISAACGenerator < ISAAC
|
10
|
+
# def initialize(*seeds)
|
11
|
+
# super()
|
12
|
+
# if seeds.compact.empty?
|
13
|
+
# seeds = [Random::Sequence.random_seed]
|
14
|
+
# end
|
15
|
+
# @seeds = seeds
|
16
|
+
# srand(seeds)
|
17
|
+
# end
|
18
|
+
#
|
19
|
+
# attr_reader :seeds
|
20
|
+
#
|
21
|
+
# alias next rand
|
22
|
+
#end
|
22
23
|
|
23
24
|
include RedShift
|
24
25
|
|
@@ -69,13 +70,14 @@ class Flow_Transition < FlowTestComponent
|
|
69
70
|
setup do
|
70
71
|
self.x = 0
|
71
72
|
@alarm_time = 0
|
72
|
-
@alarm_seq = Random::Exponential.new
|
73
|
-
|
74
|
-
:seed =>
|
73
|
+
@alarm_seq = Random::Exponential.new(
|
74
|
+
#:generator => ISAACGenerator,
|
75
|
+
:seed => 614822716,
|
75
76
|
:mean => 0.5
|
76
|
-
|
77
|
-
|
78
|
-
|
77
|
+
)
|
78
|
+
@state_seq = Random::Discrete.new(
|
79
|
+
#:generator => ISAACGenerator,
|
80
|
+
:seed => 3871653669, ## doesn't make sense to re-seed the same global gen
|
79
81
|
:distrib =>
|
80
82
|
{
|
81
83
|
Alg => 100,
|
@@ -83,9 +85,10 @@ class Flow_Transition < FlowTestComponent
|
|
83
85
|
Euler => 100,
|
84
86
|
Empty => 100
|
85
87
|
}
|
88
|
+
)
|
86
89
|
puts "\n\n Flow_Transition used the following seeds:"
|
87
|
-
puts " alarm seed = #{@alarm_seq.generator.seeds}"
|
88
|
-
puts " state seed = #{@state_seq.generator.seeds}"
|
90
|
+
puts " alarm seed = #{@alarm_seq.generator.seeds rescue @alarm_seq.generator.seed}"
|
91
|
+
puts " state seed = #{@state_seq.generator.seeds rescue @state_seq.generator.seed}"
|
89
92
|
end
|
90
93
|
|
91
94
|
transition Enter => Switch,
|