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
@@ -0,0 +1,211 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
module CAS
|
4
|
+
# __ __ _ _ _
|
5
|
+
# \ \ / /_ _ _ _(_)__ _| |__| |___
|
6
|
+
# \ V / _` | '_| / _` | '_ \ / -_)
|
7
|
+
# \_/\__,_|_| |_\__,_|_.__/_\___|
|
8
|
+
|
9
|
+
##
|
10
|
+
# Container for a variable. It can be resolved in a numerical value.
|
11
|
+
# It can also be used for derivatives.
|
12
|
+
class Variable < CAS::Op
|
13
|
+
# Contains all define variable, in an hash. Variables are
|
14
|
+
# accessible through variable name.
|
15
|
+
@@vars = {}
|
16
|
+
|
17
|
+
# Returns the `Hash` that contains all the variable
|
18
|
+
#
|
19
|
+
# * **returns**: `Hash`
|
20
|
+
def self.list
|
21
|
+
@@vars
|
22
|
+
end
|
23
|
+
|
24
|
+
# Return the number of variable defined
|
25
|
+
#
|
26
|
+
# * **returns**: `Fixnum`
|
27
|
+
def self.size
|
28
|
+
@@vars.keys.size
|
29
|
+
end
|
30
|
+
|
31
|
+
# Returns a variable given its name
|
32
|
+
#
|
33
|
+
# * **argument**: `Object` name of the variable
|
34
|
+
# * **returns**: `CAS::Variable` instance if exists, creates a new variable if does not
|
35
|
+
def self.[](s)
|
36
|
+
@@vars[s] || CAS::vars(s)
|
37
|
+
end
|
38
|
+
|
39
|
+
# Returns `true` if a variable already exists
|
40
|
+
#
|
41
|
+
# * **argument**: `Object` that represent the variable
|
42
|
+
# * **returns**: `TrueClass` if variable exists, `FalseClass` if not
|
43
|
+
def self.exist?(name)
|
44
|
+
@@vars.keys.include? name
|
45
|
+
end
|
46
|
+
|
47
|
+
attr_reader :name
|
48
|
+
# Variable is a container for an atomic simbol that becomes a number
|
49
|
+
# when `CAS::Op#call` method is used.
|
50
|
+
#
|
51
|
+
# * **argument**: `Object` that is a identifier for the variable
|
52
|
+
# * **returns**: `CAS::Variable` instance
|
53
|
+
def initialize(name)
|
54
|
+
raise CASError, "Variable #{name} already exists" if CAS::Variable.exist? name
|
55
|
+
@name = name
|
56
|
+
@@vars[@name] = self
|
57
|
+
end
|
58
|
+
|
59
|
+
# Overrides new method. This will return an existing variable if in variable container
|
60
|
+
#
|
61
|
+
# * **requires**: `Object` that is an identifier for the variable
|
62
|
+
# * **returns**: new variable instance o
|
63
|
+
def Variable.new(name)
|
64
|
+
@@vars[name] || super
|
65
|
+
end
|
66
|
+
# Returns the derivative of a variable
|
67
|
+
#
|
68
|
+
# ```
|
69
|
+
# dx dx
|
70
|
+
# -- = 1; -- = 0
|
71
|
+
# dx dy
|
72
|
+
# ```
|
73
|
+
#
|
74
|
+
# * **argument**: `CAS::Op` for the derivative denominator
|
75
|
+
# * **returns**: `CAS::Constant`, 0 if not depended, 1 if dependent
|
76
|
+
def diff(v)
|
77
|
+
(self == v ? CAS::One : CAS::Zero)
|
78
|
+
end
|
79
|
+
|
80
|
+
# Returns `TrueClass` if argument of the function is equal
|
81
|
+
# to `self`
|
82
|
+
#
|
83
|
+
# * **argument**: `CAS::Op`
|
84
|
+
# * **returns**: `TrueClass` or `FalseClass`
|
85
|
+
def depend?(v)
|
86
|
+
self == v
|
87
|
+
end
|
88
|
+
|
89
|
+
# Equality operator, the standard operator is overloaded
|
90
|
+
# :warning: this operates on the graph, not on the math
|
91
|
+
# See `CAS::equal`, etc.
|
92
|
+
#
|
93
|
+
# * **argument**: `CAS::Op` to be tested against
|
94
|
+
# * **returns**: `TrueClass` if equal, `FalseClass` if differs
|
95
|
+
def ==(op)
|
96
|
+
# CAS::Help.assert(op, CAS::Op)
|
97
|
+
if op.is_a? CAS::Variable
|
98
|
+
return self.inspect == op.inspect
|
99
|
+
else
|
100
|
+
false
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
# Call resolves the operation tree in a `Numeric` (if `Fixnum`)
|
105
|
+
# or `Float` (depends upon promotions).
|
106
|
+
# As input, it requires an hash with `CAS::Variable` or `CAS::Variable#name`
|
107
|
+
# as keys, and a `Numeric` as a value
|
108
|
+
#
|
109
|
+
# ``` ruby
|
110
|
+
# x, y = CAS::vars :x, :y
|
111
|
+
# f = (x ** 2) + (y ** 2)
|
112
|
+
# f.call({x => 1, y => 2})
|
113
|
+
# # => 2
|
114
|
+
# ```
|
115
|
+
#
|
116
|
+
# * **argument**: `Hash` with feed dictionary
|
117
|
+
# * **returns**: `Numeric`
|
118
|
+
def call(f)
|
119
|
+
CAS::Help.assert(f, Hash)
|
120
|
+
|
121
|
+
return f[self] if f[self]
|
122
|
+
return f[@name] if f[@name]
|
123
|
+
end
|
124
|
+
|
125
|
+
# Convert expression to string
|
126
|
+
#
|
127
|
+
# * **returns**: `String` to print on screen
|
128
|
+
def to_s
|
129
|
+
"#{@name}"
|
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
|
+
"#{@name}"
|
137
|
+
end
|
138
|
+
|
139
|
+
# Returns an array containing `self`
|
140
|
+
#
|
141
|
+
# * **returns**: `Array` containing `self`
|
142
|
+
def args
|
143
|
+
[self]
|
144
|
+
end
|
145
|
+
|
146
|
+
# Terminal substitutions for variables. If input datatable
|
147
|
+
# contains the variable will perform the substitution with
|
148
|
+
# the value.
|
149
|
+
#
|
150
|
+
# * **argument**: `Hash` of substitutions
|
151
|
+
# * **returns**: `CAS::Op` of substitutions
|
152
|
+
def subs(dt)
|
153
|
+
CAS::Help.assert(dt, Hash)
|
154
|
+
if dt.keys.include? self
|
155
|
+
if dt[self].is_a? CAS::Op
|
156
|
+
return dt[self]
|
157
|
+
elsif dt[self].is_a? Numeric
|
158
|
+
return CAS::const(dt[self])
|
159
|
+
else
|
160
|
+
raise CASError, "Impossible subs. Received a #{dt[self].class} = #{dt[self]}"
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
# Inspector for the current object
|
166
|
+
#
|
167
|
+
# * **returns**: `String`
|
168
|
+
def inspect
|
169
|
+
"Var(#{@name})"
|
170
|
+
end
|
171
|
+
|
172
|
+
# Simplification callback. The only possible simplification
|
173
|
+
# is returning `self`
|
174
|
+
#
|
175
|
+
# * **returns**: `CAS::Variable` as `self`
|
176
|
+
def simplify
|
177
|
+
self
|
178
|
+
end
|
179
|
+
|
180
|
+
# Return the local Graphviz node of the tree
|
181
|
+
#
|
182
|
+
# * **returns**: `String` of local Graphiz node
|
183
|
+
def dot_graph
|
184
|
+
"#{@name};"
|
185
|
+
end
|
186
|
+
|
187
|
+
# Returns the latex representation of the current Op.
|
188
|
+
#
|
189
|
+
# * **returns**: `String`
|
190
|
+
def to_latex
|
191
|
+
self.to_s
|
192
|
+
end
|
193
|
+
end # Number
|
194
|
+
|
195
|
+
# Allows to define a series of new variables.
|
196
|
+
#
|
197
|
+
# ``` ruby
|
198
|
+
# x, y = CAS::vars :x, :y
|
199
|
+
# ```
|
200
|
+
#
|
201
|
+
# * **argument**: `Array` of Numeric
|
202
|
+
# * **returns**: `Array` of `CAS::Variable`
|
203
|
+
def self.vars(*name)
|
204
|
+
(return CAS::Variable.new(name[0])) if name.size == 1
|
205
|
+
ret = []
|
206
|
+
name.each do |n|
|
207
|
+
ret << CAS::Variable.new(n)
|
208
|
+
end
|
209
|
+
return ret
|
210
|
+
end
|
211
|
+
end
|
@@ -0,0 +1,185 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
module CAS
|
4
|
+
# ___ _ ___
|
5
|
+
# | _ |_)_ _ __ _ _ _ _ _ / _ \ _ __
|
6
|
+
# | _ \ | ' \/ _` | '_| || | (_) | '_ \
|
7
|
+
# |___/_|_||_\__,_|_| \_, |\___/| .__/
|
8
|
+
# |__/ |_|
|
9
|
+
|
10
|
+
##
|
11
|
+
# Binary operator
|
12
|
+
class BinaryOp < CAS::Op
|
13
|
+
# First element of the operation
|
14
|
+
attr_reader :x
|
15
|
+
# Second element of the operation
|
16
|
+
attr_reader :y
|
17
|
+
|
18
|
+
# The binary operator inherits from the `CAS::Op`, even
|
19
|
+
# if it is defined as a node with two possible branches. This
|
20
|
+
# is particular of the basic operations. The two basic nodes
|
21
|
+
# shares the **same** interface, so all the operations do not
|
22
|
+
# need to know which kind of node they are handling.
|
23
|
+
#
|
24
|
+
# * **argument**: `CAS::Op` left argument of the node or `Numeric` to be converted in `CAS::Constant`
|
25
|
+
# * **argument**: `CAS::Op` right argument of the node or `Numeric` to be converted in `CAS::Constant`
|
26
|
+
# * **returns**: `CAS::BinaryOp` instance
|
27
|
+
def initialize(x, y)
|
28
|
+
if x.is_a? Numeric
|
29
|
+
x = BinaryOp.numeric_to_const x
|
30
|
+
end
|
31
|
+
if y.is_a? Numeric
|
32
|
+
y = BinaryOp.numeric_to_const y
|
33
|
+
end
|
34
|
+
CAS::Help.assert(x, CAS::Op)
|
35
|
+
CAS::Help.assert(y, CAS::Op)
|
36
|
+
|
37
|
+
@x = x
|
38
|
+
@y = y
|
39
|
+
end
|
40
|
+
|
41
|
+
# Return the dependencies of the operation. Requires a `CAS::Variable`
|
42
|
+
# and it is one of the recursve method (implicit tree resolution)
|
43
|
+
#
|
44
|
+
# * **argument**: `CAS::Variable` instance
|
45
|
+
# * **returns**: `TrueClass` if depends, `FalseClass` if not
|
46
|
+
def depend?(v)
|
47
|
+
CAS::Help.assert(v, CAS::Op)
|
48
|
+
|
49
|
+
@x.depend? v or @y.depend? v
|
50
|
+
end
|
51
|
+
|
52
|
+
# This method returns an array with the derivatives of the two branches
|
53
|
+
# of the node. This method is usually called by child classes, and it is not
|
54
|
+
# intended to be used directly.
|
55
|
+
#
|
56
|
+
# * **argument**: `CAS::Op` operation to differentiate against
|
57
|
+
# * **returns**: `Array` of differentiated branches ([0] for left, [1] for right)
|
58
|
+
def diff(v)
|
59
|
+
CAS::Help.assert(v, CAS::Op)
|
60
|
+
left, right = CAS::Zero, CAS::Zero
|
61
|
+
|
62
|
+
left = @x.diff(v) if @x.depend? v
|
63
|
+
right = @y.diff(v) if @y.depend? v
|
64
|
+
|
65
|
+
return left, right
|
66
|
+
end
|
67
|
+
|
68
|
+
# Substituitions for both branches of the graph, same as `CAS::Op#subs`
|
69
|
+
#
|
70
|
+
# * **argument**: `Hash` of substitutions
|
71
|
+
# * **returns**: `CAS::BinaryOp`, in practice `self`
|
72
|
+
def subs(dt)
|
73
|
+
return self.subs_lhs(dt).subs_rhs(dt)
|
74
|
+
end
|
75
|
+
|
76
|
+
# Substituitions for left branch of the graph, same as `CAS::Op#subs`
|
77
|
+
#
|
78
|
+
# * **argument**: `Hash` of substitutions
|
79
|
+
# * **returns**: `CAS::BinaryOp`, in practice `self`
|
80
|
+
def subs_lhs(dt)
|
81
|
+
CAS::Help.assert(dt, Hash)
|
82
|
+
|
83
|
+
if dt.keys.include? @x
|
84
|
+
if dt[@x].is_a? CAS::Op
|
85
|
+
@x = dt[@x]
|
86
|
+
elsif dt[@x].is_a? Numeric
|
87
|
+
@x = CAS::const dt[@x]
|
88
|
+
else
|
89
|
+
raise CASError, "Impossible subs. Received a #{dt[@x].class} = #{dt[@x]}"
|
90
|
+
end
|
91
|
+
else
|
92
|
+
@x.subs(dt)
|
93
|
+
end
|
94
|
+
return self
|
95
|
+
end
|
96
|
+
|
97
|
+
# Substituitions for left branch of the graph, same as `CAS::Op#subs`
|
98
|
+
#
|
99
|
+
# * **argument**: `Hash` of substitutions
|
100
|
+
# * **returns**: `CAS::BinaryOp`, in practice `self`
|
101
|
+
def subs_rhs(dt)
|
102
|
+
CAS::Help.assert(dt, Hash)
|
103
|
+
if dt.keys.include? @y
|
104
|
+
if dt[@y].is_a? CAS::Op
|
105
|
+
@y = dt[@y]
|
106
|
+
elsif dt[@y].is_a? Numeric
|
107
|
+
@y = CAS::const dt[@y]
|
108
|
+
else
|
109
|
+
raise CASError, "Impossible subs. Received a #{dt[@y].class} = #{dt[@y]}"
|
110
|
+
end
|
111
|
+
else
|
112
|
+
@y.subs(dt)
|
113
|
+
end
|
114
|
+
return self
|
115
|
+
end
|
116
|
+
|
117
|
+
# Same `CAS::Op#call`
|
118
|
+
#
|
119
|
+
# * **argument**: `Hash` of values
|
120
|
+
# * **returns**: `Numeric` for result
|
121
|
+
def call(_fd)
|
122
|
+
raise CAS::CASError, "Not Implemented. This is a virtual method"
|
123
|
+
end
|
124
|
+
|
125
|
+
# String representation of the tree
|
126
|
+
#
|
127
|
+
# * **returns**: `String`
|
128
|
+
def to_s
|
129
|
+
raise CAS::CASError, "Not Implemented. This is a virtual method"
|
130
|
+
end
|
131
|
+
|
132
|
+
# Code to be used in `CAS::BinaryOp#to_proc`
|
133
|
+
#
|
134
|
+
# * **returns**: `String`
|
135
|
+
def to_code
|
136
|
+
raise CAS::CASError, "Not implemented. This is a virtual method"
|
137
|
+
end
|
138
|
+
|
139
|
+
# Returns an array of all the variables that are in the graph
|
140
|
+
#
|
141
|
+
# * **returns**: `Array` of `CAS::Variable`s
|
142
|
+
def args
|
143
|
+
(@x.args + @y.args).uniq
|
144
|
+
end
|
145
|
+
|
146
|
+
# Inspector
|
147
|
+
#
|
148
|
+
# * **returns**: `String`
|
149
|
+
def inspect
|
150
|
+
"#{self.class}(#{@x.inspect}, #{@y.inspect})"
|
151
|
+
end
|
152
|
+
|
153
|
+
# Comparison with other `CAS::Op`. This is **not** a math operation.
|
154
|
+
#
|
155
|
+
# * **argument**: `CAS::Op` to be compared against
|
156
|
+
# * **returns**: `TrueClass` if equal, `FalseClass` if different
|
157
|
+
def ==(op)
|
158
|
+
CAS::Help.assert(op, CAS::Op)
|
159
|
+
if op.is_a? CAS::BinaryOp
|
160
|
+
return (self.class == op.class and @x == op.x and @y == op.y)
|
161
|
+
else
|
162
|
+
return false
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
# Executes simplifications of the two branches of the graph
|
167
|
+
#
|
168
|
+
# * **returns**: `CAS::BinaryOp` as `self`
|
169
|
+
def simplify
|
170
|
+
hash = @x.to_s
|
171
|
+
@x = @x.simplify
|
172
|
+
while @x.to_s != hash
|
173
|
+
hash = @x.to_s
|
174
|
+
@x = @x.simplify
|
175
|
+
end
|
176
|
+
hash = @y.to_s
|
177
|
+
@y = @y.simplify
|
178
|
+
while @y.to_s != hash
|
179
|
+
hash = @y.to_s
|
180
|
+
@y = @y.simplify
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end # BinaryOp
|
184
|
+
CAS::BinaryOp.init_simplify_dict
|
185
|
+
end
|
@@ -0,0 +1,175 @@
|
|
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.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
|
+
dep = false
|
37
|
+
@x.each { |x| dep = (x.depend?(v) or dep) }
|
38
|
+
return dep
|
39
|
+
end
|
40
|
+
|
41
|
+
# Return a list of derivative using the chain rule. The input is a
|
42
|
+
# operation:
|
43
|
+
#
|
44
|
+
# ```
|
45
|
+
# f(x) = g(x) + h(x) + l(x) + m(x)
|
46
|
+
#
|
47
|
+
# d f(x)
|
48
|
+
# ------ = g'(x) + h'(x) + l'(x) + m'(x)
|
49
|
+
# dx
|
50
|
+
#
|
51
|
+
# d f(x)
|
52
|
+
# ------ = 1
|
53
|
+
# d g(x)
|
54
|
+
# ```
|
55
|
+
# * **argument**: `CAS::Op` object of the derivative
|
56
|
+
# * **returns**: `CAS::NaryOp` of derivative
|
57
|
+
def diff(v)
|
58
|
+
CAS::Help.assert(v, CAS::Op)
|
59
|
+
if self.depend?(v)
|
60
|
+
return @x.map { |x| x.diff(v) }
|
61
|
+
end
|
62
|
+
return CAS::Zero
|
63
|
+
end
|
64
|
+
|
65
|
+
# Call resolves the operation tree in a `Numeric` (if `Fixnum`)
|
66
|
+
# or `Float` depends upon promotions).
|
67
|
+
# As input, it requires an hash with `CAS::Variable` or `CAS::Variable#name`
|
68
|
+
# as keys, and a `Numeric` as a value
|
69
|
+
#
|
70
|
+
# ``` ruby
|
71
|
+
# x, y = CAS::vars :x, :y
|
72
|
+
# f = (x ** 2) + (y ** 2)
|
73
|
+
# f.call({x => 1, y => 2})
|
74
|
+
# # => 2
|
75
|
+
# ```
|
76
|
+
# * **argument**: `Hash` with feed dictionary
|
77
|
+
# * **returns**: `Array` of `Numeric`
|
78
|
+
def call(fd)
|
79
|
+
CAS::Help.assert(fd, Hash)
|
80
|
+
return @x.map { |x| x.call(fd) }
|
81
|
+
end
|
82
|
+
|
83
|
+
# Perform substitution of a part of the graph using a data table:
|
84
|
+
#
|
85
|
+
# ``` ruby
|
86
|
+
# x, y = CAS::vars :x, :y
|
87
|
+
# f = (x ** 2) + (y ** 2)
|
88
|
+
# puts f
|
89
|
+
# # => (x^2) + (y^2)
|
90
|
+
# puts f.subs({x => CAS::ln(y)})
|
91
|
+
# # => (ln(y)^2) + (y^2)
|
92
|
+
# ```
|
93
|
+
#
|
94
|
+
# * **argument**: `Hash` with substitution table
|
95
|
+
# * **returns**: `CAS::NaryOp` (`self`) with substitution performed
|
96
|
+
def subs(dt)
|
97
|
+
CAS::Help.assert(dt, Hash)
|
98
|
+
@x.each_with_index do |x, k|
|
99
|
+
if dt.keys.include? x
|
100
|
+
if dt[x].is_a? CAS::Op
|
101
|
+
@x[k] = dt[x]
|
102
|
+
elsif dt[x].is_a? Numeric
|
103
|
+
@x[k] = CAS::const dt[x]
|
104
|
+
else
|
105
|
+
raise CAS::CASError, "Impossible subs. Received a #{dt[x].class} = #{dt[x]}"
|
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
|
+
end # NaryOp
|
174
|
+
CAS::NaryOp.init_simplify_dict
|
175
|
+
end
|