Mr.CAS 0.2.6

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,232 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ module CAS
4
+ # This is an attempt to build some sort of node in the graph that
5
+ # has arbitrary number of childs node. It should help implement more easily
6
+ # some sort of better simplifications engine
7
+ #
8
+ # This is an incredibly experimental feature.
9
+ class NaryOp < CAS::Op
10
+ # List of arguments of the operation
11
+ attr_reader :x
12
+
13
+ # Initialize a new empty N-elements operation container. This is
14
+ # a virtual class, and other must inherit from this basical container
15
+ #
16
+ # * **argument**: `Numeric` to be converted in `CAS::Constant` or `CAS::Op` child operations
17
+ # * **returns**: `CAS::NaryOp` instance
18
+ def initialize(*xs)
19
+ @x = []
20
+ xs.flatten.each do |x|
21
+ if x.is_a? Numeric
22
+ x = Op.numeric_to_const x
23
+ end
24
+ CAS::Help.assert(x, CAS::Op)
25
+ @x << x
26
+ end
27
+ end
28
+
29
+ # Returns the dependencies of the operation. Require a `CAS::Variable`
30
+ # and it is one of the recursive method (implicit tree resolution)
31
+ #
32
+ # * **argument**: `CAS::Variable` instance
33
+ # * **returns**: `TrueClass` or `FalseClass`
34
+ def depend?(v)
35
+ CAS::Help.assert(v, CAS::Op)
36
+ @x.include? v
37
+ end
38
+
39
+ # Return a list of derivative using the chain rule. The input is a
40
+ # operation:
41
+ #
42
+ # ```
43
+ # f(x) = g(x) + h(x) + l(x) + m(x)
44
+ #
45
+ # d f(x)
46
+ # ------ = g'(x) + h'(x) + l'(x) + m'(x)
47
+ # dx
48
+ #
49
+ # d f(x)
50
+ # ------ = 1
51
+ # d g(x)
52
+ # ```
53
+ # * **argument**: `CAS::Op` object of the derivative
54
+ # * **returns**: `CAS::NaryOp` of derivative
55
+ def diff(v)
56
+ CAS::Help.assert(v, CAS::Op)
57
+ if self.depend?(v)
58
+ return @x.map { |x| x.diff(v) }
59
+ end
60
+ return CAS::Zero
61
+ end
62
+
63
+ # Call resolves the operation tree in a `Numeric` (if `Fixnum`)
64
+ # or `Float` depends upon promotions).
65
+ # As input, it requires an hash with `CAS::Variable` or `CAS::Variable#name`
66
+ # as keys, and a `Numeric` as a value
67
+ #
68
+ # ``` ruby
69
+ # x, y = CAS::vars :x, :y
70
+ # f = (x ** 2) + (y ** 2)
71
+ # f.call({x => 1, y => 2})
72
+ # # => 2
73
+ # ```
74
+ # * **argument**: `Hash` with feed dictionary
75
+ # * **returns**: `Array` of `Numeric`
76
+ def call(fd)
77
+ CAS::Help.assert(fd, Hash)
78
+ return @x.map { |x| x.call(fd) }
79
+ end
80
+
81
+ # Perform substitution of a part of the graph using a data table:
82
+ #
83
+ # ``` ruby
84
+ # x, y = CAS::vars :x, :y
85
+ # f = (x ** 2) + (y ** 2)
86
+ # puts f
87
+ # # => (x^2) + (y^2)
88
+ # puts f.subs({x => CAS::ln(y)})
89
+ # # => (ln(y)^2) + (y^2)
90
+ # ```
91
+ #
92
+ # * **argument**: `Hash` with substitution table
93
+ # * **returns**: `CAS::NaryOp` (`self`) with substitution performed
94
+ def subs(dt)
95
+ CAS::Help.assert(dt, Hash)
96
+ @x = @x.map { |z| z.subs(dt) || z }
97
+ @x.each_with_index do |x, k|
98
+ sub = dt.keys.select { |e| e == x }[0]
99
+ if sub
100
+ if dt[sub].is_a? CAS::Op
101
+ @x[k] = dt[sub]
102
+ elsif dt[sub].is_a? Numeric
103
+ @x[k] = CAS::const dt[sub]
104
+ else
105
+ raise CAS::CASError, "Impossible subs. Received a #{dt[sub].class} = #{dt[sub]}"
106
+ end
107
+ end
108
+ end
109
+ return self
110
+ end
111
+
112
+ # Convert expression to string
113
+ #
114
+ # * **returns**: `String` to print on screen
115
+ def to_s
116
+ return "(#{@x.map(&:to_s).join(", ")})"
117
+ end
118
+
119
+ # Convert expression to code (internal, for `CAS::Op#to_proc` method)
120
+ #
121
+ # * **returns**: `String` that represent Ruby code to be parsed in `CAS::Op#to_proc`
122
+ def to_code
123
+ return "(#{@x.map(&:to_code).join(", ")})"
124
+ end
125
+
126
+ # Simplification callback. It simplify the subgraph of each node
127
+ # until all possible simplification are performed (thus the execution
128
+ # time is not deterministic).
129
+ #
130
+ # * **returns**: `CAS::Op` simplified
131
+ def simplify
132
+ hash = self.to_s
133
+ @x = @x.map { |x| x.simplify }
134
+ while self.to_s != hash
135
+ hash = self.to_s
136
+ @x = @x.map { |x| x.simplify }
137
+ end
138
+ end
139
+
140
+ # Inspector for the current object
141
+ #
142
+ # * **returns**: `String`
143
+ def inspect
144
+ "#{self.class}(#{@x.map(&:inspect).join(", ")})"
145
+ end
146
+
147
+ # Equality operator, the standard operator is overloaded
148
+ # :warning: this operates on the graph, not on the math
149
+ # See `CAS::equal`, etc.
150
+ #
151
+ # * **argument**: `CAS::Op` to be tested against
152
+ # * **returns**: `TrueClass` if equal, `FalseClass` if differs
153
+ def ==(op)
154
+ # CAS::Help.assert(op, CAS::Op)
155
+ if op.is_a? CAS::NaryOp
156
+ return false if @x.size != op.x.size
157
+ 0.upto(@x.size - 1) do |i|
158
+ return false if @x[i] != op.x[i]
159
+ end
160
+ return true
161
+ end
162
+ false
163
+ end
164
+
165
+ # Returns a list of all `CAS::Variable`s of the current tree
166
+ #
167
+ # * **returns**: `Array` of `CAS::Variable`s
168
+ def args
169
+ r = []
170
+ @x.each { |x| r += x.args }
171
+ return r.uniq
172
+ end
173
+
174
+ # Reduce multeplicity will scan for elements that are equal in the definition
175
+ # of the sum and will reduce their multeplicity. A block can be used to do something
176
+ # different. For example in nary-product we use it like this:
177
+ #
178
+ # ``` ruby
179
+ # @x = self.__reduce_multeplicity(@x) do |count, op|
180
+ # count > 1 ? (op ** count) : op
181
+ # end
182
+ # ```
183
+ #
184
+ # In general it works like that:
185
+ #
186
+ # ```
187
+ # a + a + b + c => 2 * a + b + c
188
+ # a * a * b * a => (a ** b) * b
189
+ # ```
190
+ # But operates only on Array level! This is an internal function
191
+ # and should never be used
192
+ #
193
+ # * **requires**: An `Array`
194
+ # * **returns**: An `Array` with multeplicity reduced
195
+ # * **block**: yields the count and the op. Get the value to insert in a new
196
+ # `Array` that is the returned `Array`
197
+ def __reduce_multeplicity(xs)
198
+ count = Hash.new(0)
199
+ xs.each do |x|
200
+ e = x
201
+ count.keys.each { |d| e = d if x == d }
202
+ count[e] += 1
203
+ end
204
+ count.map do |k, v|
205
+ if block_given?
206
+ yield(k, v)
207
+ else
208
+ v > 1 ? CAS.const(v) * k : k
209
+ end
210
+ end
211
+ end
212
+
213
+ # Collects all the constants and tries to reduce them to a single constant.
214
+ # Requires a block to understand what it should do with the constants
215
+ #
216
+ # * **requires**: input `Array` of `CAS::Op`
217
+ # * **returns**: new `Array` of `CAS::Op`
218
+ # * **block**: yields an `Array` of `CAS::Constant` and an `Array` of others `CAS::Op`,
219
+ # requires an `Array` back
220
+ def __reduce_constants(xs)
221
+ const = []
222
+ xs.each { |x| const << x if x.is_a? CAS::Constant }
223
+ if const.size > 0
224
+ yield const, (xs - const)
225
+ else
226
+ xs
227
+ end
228
+ end
229
+
230
+ end # NaryOp
231
+ CAS::NaryOp.init_simplify_dict
232
+ end
@@ -0,0 +1,285 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ module CAS
4
+ class CASError < RuntimeError; end
5
+
6
+ # ___
7
+ # / _ \ _ __
8
+ # | (_) | '_ \
9
+ # \___/| .__/
10
+ # |_|
11
+ class Op
12
+ # Argument of the operation
13
+ attr_reader :x
14
+
15
+ # Initialize a new empty operation container. This is a virtual
16
+ # class and the other must inherit from this basic container.
17
+ # Some methods raise a `CAS::CASError` if called.
18
+ # The input element is a Numric, to create a constant.
19
+ # `CAS::Op` specifies operations with a single variable
20
+ #
21
+ # * **argument**: `Numeric` to be converted in `CAS::Constant` or `CAS::Op` child operation
22
+ # * **returns**: `CAS::Op` instance
23
+ def initialize(x)
24
+ if x.is_a? Numeric
25
+ x = Op.numeric_to_const x
26
+ end
27
+ CAS::Help.assert(x, CAS::Op)
28
+
29
+ @x = x
30
+ end
31
+
32
+ def self.numeric_to_const(x)
33
+ if CAS::NumericToConst[x]
34
+ return CAS::NumericToConst[x]
35
+ else
36
+ return CAS::const x
37
+ end
38
+ end
39
+
40
+ # Return the dependencies of the operation. Requires a `CAS::Variable`
41
+ # and it is one of the recursve method (implicit tree resolution)
42
+ #
43
+ # * **argument**: `CAS::Variable` instance
44
+ # * **returns**: `TrueClass` if depends, `FalseClass` if not
45
+ def depend?(v)
46
+ CAS::Help.assert(v, CAS::Op)
47
+
48
+ @x.depend? v
49
+ end
50
+
51
+ # Return the derivative of the operation using the chain rule
52
+ # The input is a `CAS::Op` because it can handle derivatives
53
+ # with respect to functions. E.g.:
54
+ #
55
+ # ```
56
+ # f(x) = (ln(x))**2
57
+ # g(x) = ln(x)
58
+ #
59
+ # d f(x)
60
+ # ------ = 2 ln(x)
61
+ # d g(x)
62
+ # ```
63
+ #
64
+ # * **argument**: `CAS::Op` object of the derivative
65
+ # * **returns**: `CAS::Op` a derivated object, or `CAS::Zero` for constants
66
+ def diff(v)
67
+ CAS::Help.assert(v, CAS::Op)
68
+
69
+ if @x.depend? v
70
+ return @x.diff(v)
71
+ end
72
+ CAS::Zero
73
+ end
74
+
75
+ # Call resolves the operation tree in a `Numeric` (if `Fixnum`)
76
+ # or `Float` (depends upon promotions).
77
+ # As input, it requires an hash with `CAS::Variable` or `CAS::Variable#name`
78
+ # as keys, and a `Numeric` as a value
79
+ #
80
+ # ``` ruby
81
+ # x, y = CAS::vars :x, :y
82
+ # f = (x ** 2) + (y ** 2)
83
+ # f.call({x => 1, y => 2})
84
+ # # => 2
85
+ # ```
86
+ #
87
+ # * **argument**: `Hash` with feed dictionary
88
+ # * **returns**: `Numeric`
89
+ def call(f)
90
+ CAS::Help.assert(f, Hash)
91
+
92
+ @x.call(f)
93
+ end
94
+
95
+ # Perform substitution of a part of the graph using a data table:
96
+ #
97
+ # ``` ruby
98
+ # x, y = CAS::vars :x, :y
99
+ # f = (x ** 2) + (y ** 2)
100
+ # puts f
101
+ # # => (x^2) + (y^2)
102
+ # puts f.subs({x => CAS::ln(y)})
103
+ # # => (ln(y)^2) + (y^2)
104
+ # ```
105
+ #
106
+ # * **argument**: `Hash` with substitution table
107
+ # * **returns**: `CAS::Op` (`self`) with substitution performed
108
+ def subs(dt)
109
+ CAS::Help.assert(dt, Hash)
110
+ sub = dt.keys.select { |e| e == @x }[0]
111
+ if sub
112
+ if dt[sub].is_a? CAS::Op
113
+ @x = dt[sub]
114
+ elsif dt[sub].is_a? Numeric
115
+ @x = CAS::const dt[sub]
116
+ else
117
+ raise CAS::CASError, "Impossible subs. Received a #{dt[@x].class} = #{dt[@x]}"
118
+ end
119
+ else
120
+ @x.subs(dt)
121
+ end
122
+ return self
123
+ end
124
+
125
+ # Convert expression to string
126
+ #
127
+ # * **returns**: `String` to print on screen
128
+ def to_s
129
+ "#{@x}"
130
+ end
131
+
132
+ # Convert expression to code (internal, for `CAS::Op#to_proc` method)
133
+ #
134
+ # * **returns**: `String` that represent Ruby code to be parsed in `CAS::Op#to_proc`
135
+ def to_code
136
+ "#{@x}"
137
+ end
138
+
139
+ # Returns a sum of two `CAS::Op`s
140
+ #
141
+ # * **argument**: `CAS::Op` tree
142
+ # * **returns**: `CAS::Op` new object
143
+ def +(op)
144
+ CAS::Sum.new [self, op]
145
+ end
146
+
147
+ # Returns a difference of two `CAS::Op`s
148
+ #
149
+ # * **argument**: `CAS::Op` tree
150
+ # * **returns**: `CAS::Op` new object
151
+ def -(op)
152
+ CAS::Diff.new self, op
153
+ end
154
+
155
+ # Returns a product of two `CAS::Op`s
156
+ #
157
+ # * **argument**: `CAS::Op` tree
158
+ # * **returns**: `CAS::Op` new object
159
+ def *(op)
160
+ CAS::Prod.new [self, op]
161
+ end
162
+
163
+ # Returns a division of two `CAS::Op`s
164
+ #
165
+ # * **argument**: `CAS::Op` tree
166
+ # * **returns**: `CAS::Op` new object
167
+ def /(op)
168
+ CAS::Div.new self, op
169
+ end
170
+
171
+ # Returns the power of two `CAS::Op`s
172
+ #
173
+ # * **argument**: `CAS::Op` tree
174
+ # * **returns**: `CAS::Op` new object
175
+ def **(op)
176
+ CAS.pow(self, op)
177
+ end
178
+
179
+ # Unary operator for inversion of a `CAS::Op`
180
+ #
181
+ # * **returns**: `CAS::Op` new object
182
+ def -@
183
+ CAS.invert(self)
184
+ end
185
+
186
+ # Simplification callback. It simplify the subgraph of each node
187
+ # until all possible simplification are performed (thus the execution
188
+ # time is not deterministic).
189
+ #
190
+ # * **returns**: `CAS::Op` simplified version
191
+ def simplify
192
+ hash = @x.to_s
193
+ @x = @x.simplify
194
+ while @x.to_s != hash
195
+ hash = @x.to_s
196
+ @x = @x.simplify
197
+ end
198
+ end
199
+
200
+ # Simplify dictionary performs a dictionary simplification
201
+ # that is the class variable `@@simplify_dict`
202
+ #
203
+ # * **returns**: `CAS::Op` self
204
+ def simplify_dictionary
205
+ self.class.simplify_dict(@x) || self
206
+ end
207
+
208
+ # Initializes the simplification dictionary (one for each class)
209
+ #
210
+ # * **returns**: `Hash` with simplification dictionary
211
+ def self.init_simplify_dict
212
+ @simplify_dict = { }
213
+ end
214
+
215
+ # Returns an element of a
216
+ def self.simplify_dict(k)
217
+ @simplify_dict.keys.each do |op|
218
+ return @simplify_dict[op] if op.simplify == k.simplify
219
+ end
220
+ return nil
221
+ end
222
+
223
+ # Inspector for the current object
224
+ #
225
+ # * **returns**: `String`
226
+ def inspect
227
+ "#{self.class}(#{@x.inspect})"
228
+ end
229
+
230
+ # Equality operator, the standard operator is overloaded
231
+ # :warning: this operates on the graph, not on the math
232
+ # See `CAS::equal`, etc.
233
+ #
234
+ # * **argument**: `CAS::Op` to be tested against
235
+ # * **returns**: `TrueClass` if equal, `FalseClass` if differs
236
+ def ==(op)
237
+ # CAS::Help.assert(op, CAS::Op)
238
+ if op.is_a? CAS::Op
239
+ return false if op.is_a? CAS::BinaryOp
240
+ return (self.class == op.class and @x == op.x)
241
+ end
242
+ false
243
+ end
244
+
245
+ # Disequality operator, the standard operator is overloaded
246
+ # :warning: this operates on the graph, not on the math
247
+ # See `CAS::equal`, etc.
248
+ #
249
+ # * **argument**: `CAS::Op` to be tested against
250
+ # * **returns**: `FalseClass` if equal, `TrueClass` if differs
251
+ def !=(op)
252
+ not self.==(op)
253
+ end
254
+
255
+ # Evaluates the proc against a given context. It is like having a
256
+ # snapshot of the tree transformed in a callable object.
257
+ # Obviously **if the tree changes, the generated proc does notchanges**.
258
+ # The proc takes as input a feed dictionary in which each variable
259
+ # is identified through the `CAS::Variable#name` key.
260
+ #
261
+ # The proc is evaluated in the context devined by the input `Binding` object
262
+ # If `nil` is passed, the `eval` will run in this local context
263
+ #
264
+ # * **argument**: `Binding` or `NilClass` that is the context of the Ruby VM
265
+ # * **returns**: `Proc` object with a single argument as an `Hash`
266
+ def as_proc(bind=nil)
267
+ args_ext = self.args.map { |e| "#{e} = fd[\"#{e}\"];" }
268
+ code = "Proc.new do |fd|; #{args_ext.join " "} #{self.to_code}; end"
269
+ if bind # All objects have eval value, we bind when not nil
270
+ # CAS::Help.assert(bind, Binding)
271
+ bind.eval(code)
272
+ else
273
+ eval(code)
274
+ end
275
+ end
276
+
277
+ # Returns a list of all `CAS::Variable`s of the current tree
278
+ #
279
+ # * **returns**: `Array` of `CAS::Variable`s
280
+ def args
281
+ @x.args.uniq
282
+ end
283
+ end # Op
284
+ CAS::Op.init_simplify_dict
285
+ end