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.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/lib/functions/fnc-base.rb +660 -0
- data/lib/functions/fnc-box-conditions.rb +316 -0
- data/lib/functions/fnc-conditions.rb +365 -0
- data/lib/functions/fnc-piecewise.rb +186 -0
- data/lib/functions/fnc-trig.rb +487 -0
- data/lib/functions/fnc-trsc.rb +196 -0
- data/lib/numbers/constants.rb +358 -0
- data/lib/numbers/variables.rb +211 -0
- data/lib/operators/bary-op.rb +185 -0
- data/lib/operators/nary-op.rb +175 -0
- data/lib/operators/op.rb +285 -0
- data/lib/overloading/fixnum.rb +61 -0
- data/lib/overloading/float.rb +61 -0
- data/lib/ragni-cas.rb +23 -187
- data/lib/version.rb +7 -1
- data.tar.gz.sig +0 -0
- metadata +15 -8
- metadata.gz.sig +0 -0
- data/lib/fnc-base.rb +0 -560
- data/lib/fnc-branch.rb +0 -388
- data/lib/fnc-trig.rb +0 -336
- data/lib/fnc-trsc.rb +0 -126
- data/lib/numbers.rb +0 -390
- data/lib/op.rb +0 -454
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
|