ragni-cas 0.2.2 → 0.2.3

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/lib/op.rb DELETED
@@ -1,454 +0,0 @@
1
- #!/usr/bin/env ruby
2
-
3
- module CAS
4
- class CASError < RuntimeError; end
5
-
6
- # ___
7
- # / _ \ _ __
8
- # | (_) | '_ \
9
- # \___/| .__/
10
- # |_|
11
- class Op
12
- attr_reader :x
13
-
14
- # Initialize a new empty operation container. This is a virtual
15
- # class and the other must inherit from this basic container.
16
- # Some methods raise a `CAS::CASError` if called.
17
- # The input element is a Numric, to create a constant.
18
- # `CAS::Op` specifies operations with a single variable
19
- #
20
- # <- `Numeric` to be converted in `CAS::Constant` or `CAS::Op` child operation
21
- # -> `CAS::Op` instance
22
- def initialize(x)
23
- if x.is_a? Numeric
24
- case x.to_f
25
- when 0.0
26
- x = CAS::Zero
27
- when 1.0
28
- x = CAS::One
29
- else
30
- x = CAS.const x
31
- end
32
- end
33
- CAS::Help.assert(x, CAS::Op)
34
-
35
- @x = x
36
- end
37
-
38
- # Return the dependencies of the operation. Requires a `CAS::Variable`
39
- # and it is one of the recursve method (implicit tree resolution)
40
- #
41
- # <- `CAS::Variable` instance
42
- # -> `TrueClass` if depends, `FalseClass` if not
43
- def depend?(v)
44
- CAS::Help.assert(v, CAS::Op)
45
-
46
- @x.depend? v
47
- end
48
-
49
- # Return the derivative of the operation using the chain rule
50
- # The input is a `CAS::Op` because it can handle derivatives
51
- # with respect to functions. E.g.:
52
- #
53
- # ```
54
- # f(x) = (ln(x))**2
55
- # g(x) = ln(x)
56
- #
57
- # d f(x)
58
- # ------ = 2 ln(x)
59
- # d g(x)
60
- # ```
61
- #
62
- # <- `CAS::Op` object of the derivative
63
- # -> `CAS::Op` a derivated object, or `CAS::Zero` for constants
64
- def diff(v)
65
- CAS::Help.assert(v, CAS::Op)
66
-
67
- if @x.depend? v
68
- return @x.diff(v)
69
- end
70
- CAS::Zero
71
- end
72
-
73
- # Call resolves the operation tree in a `Numeric` (if `Fixnum`)
74
- # or `Float` depends upon promotions).
75
- # As input, it requires an hash with `CAS::Variable` or `CAS::Variable#name`
76
- # as keys, and a `Numeric` as a value
77
- #
78
- # ``` ruby
79
- # x, y = CAS::vars :x, :y
80
- # f = (x ** 2) + (y ** 2)
81
- # f.call({x => 1, y => 2})
82
- # # => 2
83
- # ```
84
- # <- `Hash` with feed dictionary
85
- # -> `Numeric`
86
- def call(f)
87
- CAS::Help.assert(f, Hash)
88
-
89
- @x.call(f)
90
- end
91
-
92
- # Perform substitution of a part of the graph using a data table:
93
- #
94
- # ``` ruby
95
- # x, y = CAS::vars :x, :y
96
- # f = (x ** 2) + (y ** 2)
97
- # puts f
98
- # # => (x^2) + (y^2)
99
- # puts f.subs({x => CAS::ln(y)})
100
- # # => (ln(y)^2) + (y^2)
101
- # ```
102
- #
103
- # <- `Hash` with substitution table
104
- # -> `CAS::Op` (`self`) with substitution performed
105
- def subs(dt)
106
- CAS::Help.assert(dt, Hash)
107
- if dt.keys.include? @x
108
- if dt[@x].is_a? CAS::Op
109
- @x = dt[@x]
110
- elsif dt[@x].is_a? Numeric
111
- @x = CAS::const dt[@x]
112
- else
113
- raise CAS::CASError, "Impossible subs. Received a #{dt[@x].class} = #{dt[@x]}"
114
- end
115
- else
116
- @x.subs(dt)
117
- end
118
- return self
119
- end
120
-
121
- # Convert expression to string
122
- #
123
- # -> `String` to print on screen
124
- def to_s
125
- "#{@x}"
126
- end
127
-
128
- # Convert expression to code (internal, for `CAS::Op#to_proc` method)
129
- #
130
- # -> `String` that represent Ruby code to be parsed in `CAS::Op#to_proc`
131
- def to_code
132
- "#{@x}"
133
- end
134
-
135
- # Returns a sum of two `CAS::Op`s
136
- #
137
- # <- `CAS::Op` tree
138
- # -> `CAS::Op` new object
139
- def +(op)
140
- CAS::Sum.new self, op
141
- end
142
-
143
- # Returns a difference of two `CAS::Op`s
144
- #
145
- # <- `CAS::Op` tree
146
- # -> `CAS::Op` new object
147
- def -(op)
148
- CAS::Diff.new self, op
149
- end
150
-
151
- # Returns a product of two `CAS::Op`s
152
- #
153
- # <- `CAS::Op` tree
154
- # -> `CAS::Op` new object
155
- def *(op)
156
- CAS::Prod.new self, op
157
- end
158
-
159
- # Returns a division of two `CAS::Op`s
160
- #
161
- # <- `CAS::Op` tree
162
- # -> `CAS::Op` new object
163
- def /(op)
164
- CAS::Div.new self, op
165
- end
166
-
167
- # Returns the power of two `CAS::Op`s
168
- #
169
- # <- `CAS::Op` tree
170
- # -> `CAS::Op` new object
171
- def **(op)
172
- CAS.pow(self, op)
173
- end
174
-
175
- # Unary operator for inversion of a `CAS::Op`
176
- #
177
- # -> `CAS::Op` new object
178
- def -@
179
- CAS.invert(self)
180
- end
181
-
182
- # Simplification callback. It simplify the subgraph of each node
183
- # until all possible simplification are performed (thus the execution
184
- # time is not deterministic).
185
- def simplify # TODO: improve this
186
- hash = @x.to_s
187
- @x = @x.simplify
188
- while @x.to_s != hash
189
- hash = @x.to_s
190
- @x = @x.simplify
191
- end
192
- end
193
-
194
- # Inspector for the current object
195
- #
196
- # -> `String`
197
- def inspect
198
- "#{self.class}(#{@x.inspect})"
199
- end
200
-
201
- # Equality operator, the standard operator is overloaded
202
- # :warning: this operates on the graph, not on the math
203
- # See `CAS::equal`, etc.
204
- #
205
- # <- `CAS::Op` to be tested against
206
- # -> `TrueClass` if equal, `FalseClass` if differs
207
- def ==(op)
208
- # CAS::Help.assert(op, CAS::Op)
209
- if op.is_a? CAS::Op
210
- return false if op.is_a? CAS::BinaryOp
211
- return (self.class == op.class and @x == op.x)
212
- end
213
- false
214
- end
215
-
216
- # Disequality operator, the standard operator is overloaded
217
- # :warning: this operates on the graph, not on the math
218
- # See `CAS::equal`, etc.
219
- #
220
- # <- `CAS::Op` to be tested against
221
- # -> `FalseClass` if equal, `TrueClass` if differs
222
- def !=(op)
223
- not self.==(op)
224
- end
225
-
226
- # Evaluates the proc against a given context. It is like having a
227
- # snapshot of the tree transformed in a callable object.
228
- # Obviously **if the tree changes, the generated proc does notchanges**.
229
- # The proc takes as input a feed dictionary in which each variable
230
- # is identified through the `CAS::Variable#name` key.
231
- #
232
- # The proc is evaluated in the context devined by the input `Binding` object
233
- # If `nil` is passed, the `eval` will run in this local context
234
- #
235
- # <- `Binding` or `NilClass` that is the context of the Ruby VM
236
- # -> `Proc` object with a single argument as an `Hash`
237
- def as_proc(bind=nil)
238
- args_ext = self.args.map { |e| "#{e} = fd[\"#{e}\"];" }
239
- code = "Proc.new do |fd|; #{args_ext.join " "} #{self.to_code}; end"
240
- if bind # All objects have eval value, we bind when not nil
241
- # CAS::Help.assert(bind, Binding)
242
- bind.eval(code)
243
- else
244
- eval(code)
245
- end
246
- end
247
-
248
- # Returns a list of all `CAS::Variable`s of the current tree
249
- #
250
- # -> `Array` of `CAS::Variable`s
251
- def args
252
- @x.args.uniq
253
- end
254
-
255
- # Return the local Graphviz node of the tree
256
- #
257
- # <- `?` unused variable (TODO: to be removed)
258
- # -> `String` of local Graphiz node
259
- def dot_graph(node)
260
- cls = "#{self.class.to_s.gsub("CAS::", "")}_#{self.object_id}"
261
- "#{cls} -> #{@x.dot_graph node}\n"
262
- end
263
-
264
- # Returns the latex representation of the current Op.
265
- #
266
- # -> `String`
267
- def to_latex
268
- "#{self.class.gsub("CAS::", "")}\\left(#{@x.to_latex}\\right)"
269
- end
270
- end # Op
271
-
272
- # ___ _ ___
273
- # | _ |_)_ _ __ _ _ _ _ _ / _ \ _ __
274
- # | _ \ | ' \/ _` | '_| || | (_) | '_ \
275
- # |___/_|_||_\__,_|_| \_, |\___/| .__/
276
- # |__/ |_|
277
- class BinaryOp < CAS::Op
278
- attr_reader :x, :y
279
-
280
- # The binary operator inherits from the `CAS::Op`, even
281
- # if it is defined as a node with two possible branches. This
282
- # is particular of the basic operations. The two basic nodes
283
- # shares the **same** interface, so all the operations do not
284
- # need to know which kind of node they are handling.
285
- #
286
- # <- `CAS::Op` left argument of the node or `Numeric` to be converted in `CAS::Constant`
287
- # <- `CAS::Op` right argument of the node or `Numeric` to be converted in `CAS::Constant`
288
- # -> `CAS::BinaryOp` instance
289
- def initialize(x, y)
290
- if x.is_a? Numeric
291
- case x
292
- when 0
293
- x = CAS::Zero
294
- when 1
295
- x = CAS::One
296
- else
297
- x = CAS.const x
298
- end
299
- end
300
- if y.is_a? Numeric
301
- case y
302
- when 0
303
- y = CAS::Zero
304
- when 1
305
- y = CAS::One
306
- else
307
- y = CAS.const y
308
- end
309
- end
310
- CAS::Help.assert(x, CAS::Op)
311
- CAS::Help.assert(y, CAS::Op)
312
-
313
- @x = x
314
- @y = y
315
- end
316
-
317
- # Same as `CAS::Op#depend?`
318
- def depend?(v)
319
- CAS::Help.assert(v, CAS::Op)
320
-
321
- @x.depend? v or @y.depend? v
322
- end
323
-
324
- # This method returns an array with the derivatives of the two branches
325
- # of the node. This method is usually called by child classes, and it is not
326
- # intended to be used directly.
327
- #
328
- # <- `CAS::Op` operation to differentiate against
329
- # -> `Array` of differentiated branches ([0] for left, [1] for right)
330
- def diff(v)
331
- CAS::Help.assert(v, CAS::Op)
332
- left, right = CAS::Zero, CAS::Zero
333
-
334
- left = @x.diff(v) if @x.depend? v
335
- right = @y.diff(v) if @y.depend? v
336
-
337
- return left, right
338
- end
339
-
340
- # Substituitions for both branches of the graph, same as `CAS::Op#subs`
341
- #
342
- # <- `Hash` of substitutions
343
- # -> `CAS::BinaryOp`, in practice `self`
344
- def subs(dt)
345
- CAS::Help.assert(dt, Hash)
346
- if dt.keys.include? @x
347
- if dt[@x].is_a? CAS::Op
348
- @x = dt[@x]
349
- elsif dt[@x].is_a? Numeric
350
- @x = CAS::const dt[@x]
351
- else
352
- raise CASError, "Impossible subs. Received a #{dt[@x].class} = #{dt[@x]}"
353
- end
354
- else
355
- @x.subs(dt)
356
- end
357
- if dt.keys.include? @y
358
- if dt[@y].is_a? CAS::Op
359
- @y = dt[@y]
360
- elsif dt[@y].is_a? Numeric
361
- @y = CAS::const dt[@y]
362
- else
363
- raise CASError, "Impossible subs. Received a #{dt[@y].class} = #{dt[@y]}"
364
- end
365
- else
366
- @y.subs(dt)
367
- end
368
- return self
369
- end
370
-
371
- # Same `CAS::Op#call`
372
- #
373
- # <- `Hash` of values
374
- # -> `Numeric` for result
375
- def call(fd)
376
- raise CASError, "Not Implemented. This is a virtual method"
377
- end
378
-
379
- # String representation of the tree
380
- #
381
- # -> `String`
382
- def to_s
383
- raise CASError, "Not Implemented. This is a virtual method"
384
- end
385
-
386
- # Code to be used in `CAS::BinaryOp#to_proc`
387
- #
388
- # -> `String`
389
- def to_code
390
- raise CASError, "Not implemented. This is a virtual method"
391
- end
392
-
393
- # Returns an array of all the variables that are in the graph
394
- #
395
- # -> `Array` of `CAS::Variable`s
396
- def args
397
- (@x.args + @y.args).uniq
398
- end
399
-
400
- # Inspector
401
- #
402
- # -> `String`
403
- def inspect
404
- "#{self.class}(#{@x.inspect}, #{@y.inspect})"
405
- end
406
-
407
- # Comparison with other `CAS::Op`. This is **not** a math operation.
408
- #
409
- # <- `CAS::Op` to be compared against
410
- # -> `TrueClass` if equal, `FalseClass` if different
411
- def ==(op)
412
- CAS::Help.assert(op, CAS::Op)
413
- if op.is_a? CAS::BinaryOp
414
- return (self.class == op.class and @x == op.x and @y == op.y)
415
- else
416
- return false
417
- end
418
- end
419
-
420
- # Executes simplifications of the two branches of the graph
421
- #
422
- # -> `CAS::BinaryOp` as `self`
423
- def simplify # TODO: improve this
424
- hash = @x.to_s
425
- @x = @x.simplify
426
- while @x.to_s != hash
427
- hash = @x.to_s
428
- @x = @x.simplify
429
- end
430
- hash = @y.to_s
431
- @y = @y.simplify
432
- while @y.to_s != hash
433
- hash = @y.to_s
434
- @y = @y.simplify
435
- end
436
- end
437
-
438
- # Returns the graphviz representation of the current node
439
- #
440
- # <- `?` to be removed
441
- # -> `String`
442
- def dot_graph(node)
443
- cls = "#{self.class.to_s.gsub("CAS::", "")}_#{self.object_id}"
444
- "#{cls} -> #{@x.dot_graph node}\n #{cls} -> #{@y.dot_graph node}"
445
- end
446
-
447
- # Returns the latex representation of the current Op.
448
- #
449
- # -> `String`
450
- def to_latex
451
- "#{self.class.gsub("CAS::", "")}\\left(#{@x.to_latex},\\,#{@y.to_latex}\\right)"
452
- end
453
- end # BinaryOp
454
- end