redshift 1.3.15 → 1.3.16
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/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,
|