Mr.CAS 0.2.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.
@@ -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