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.
- checksums.yaml +7 -0
- checksums.yaml.gz.sig +1 -0
- data.tar.gz.sig +2 -0
- data/lib/Mr.CAS.rb +73 -0
- data/lib/Mr.CAS/auto-diff.rb +129 -0
- data/lib/Mr.CAS/c-opt.rb +225 -0
- data/lib/Mr.CAS/c.rb +126 -0
- data/lib/Mr.CAS/graphviz.rb +132 -0
- data/lib/Mr.CAS/latex.rb +68 -0
- data/lib/Mr.CAS/matlab.rb +81 -0
- data/lib/functions/fnc-base.rb +515 -0
- data/lib/functions/fnc-box-conditions.rb +319 -0
- data/lib/functions/fnc-conditions.rb +365 -0
- data/lib/functions/fnc-piecewise.rb +186 -0
- data/lib/functions/fnc-prod.rb +102 -0
- data/lib/functions/fnc-sum.rb +151 -0
- data/lib/functions/fnc-trig.rb +489 -0
- data/lib/functions/fnc-trsc.rb +192 -0
- data/lib/numbers/constants.rb +350 -0
- data/lib/numbers/functions.rb +194 -0
- data/lib/numbers/variables.rb +202 -0
- data/lib/operators/bary-op.rb +186 -0
- data/lib/operators/nary-op.rb +232 -0
- data/lib/operators/op.rb +285 -0
- data/lib/overloading/fixnum.rb +61 -0
- data/lib/overloading/float.rb +61 -0
- data/lib/version.rb +12 -0
- metadata +88 -0
- metadata.gz.sig +2 -0
@@ -0,0 +1,194 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
module CAS
|
4
|
+
# ___ _ _
|
5
|
+
# | __| _ _ _ __| |_(_)___ _ _
|
6
|
+
# | _| || | ' \/ _| _| / _ \ ' \
|
7
|
+
# |_| \_,_|_||_\__|\__|_\___/_||_|
|
8
|
+
|
9
|
+
# Unknown function class. Will allow to make symbolic differentiation and
|
10
|
+
# so on.
|
11
|
+
class Function < CAS::NaryOp
|
12
|
+
# Contains all defined functions. Container is a `Hash` with name of the
|
13
|
+
# function as key and the function as the value
|
14
|
+
@@container = {}
|
15
|
+
|
16
|
+
# Return the `Hash` of the functions
|
17
|
+
#
|
18
|
+
# * **returns**: `Hash`
|
19
|
+
def self.list; @@container; end
|
20
|
+
|
21
|
+
# Return `true` if a function was already defined
|
22
|
+
#
|
23
|
+
# * **argument**: name of the function to be checked
|
24
|
+
def self.exist?(name)
|
25
|
+
CAS::Help.assert_name name
|
26
|
+
(@@container[name] ? true : false)
|
27
|
+
end
|
28
|
+
|
29
|
+
# Return the number of functions defined
|
30
|
+
#
|
31
|
+
# * **returns**: `Fixnum`
|
32
|
+
def self.size; @@container.keys.size; end
|
33
|
+
|
34
|
+
# Returns a function given its name
|
35
|
+
#
|
36
|
+
# * **argument**: `Object` name of the function
|
37
|
+
# * **returns**: `CAS::Function` instance if exists, raises a `CAS::CASError`
|
38
|
+
# if not
|
39
|
+
def self.[](s)
|
40
|
+
return @@container[s] if self.exist? s
|
41
|
+
raise CASError, "Function #{s} not found"
|
42
|
+
end
|
43
|
+
|
44
|
+
# Returns `true` if a function exists in container
|
45
|
+
#
|
46
|
+
# * **argument**: `String` or `Symbol` that represent the functions
|
47
|
+
# * **returns**: `TrueClass` if variable exists, `FalseClass` if not
|
48
|
+
def self.exist?(name); @@container.keys.include?(name); end
|
49
|
+
|
50
|
+
# The attribute `name` identifies the current function. A function with the
|
51
|
+
# same name of an existing function connot be defined
|
52
|
+
attr_reader :name
|
53
|
+
|
54
|
+
# Initializes a new function. It requires a name and a series of arguments
|
55
|
+
# that will be the functions on which it depends.
|
56
|
+
#
|
57
|
+
# * **argument**: `String` or `Symbol` name of the variable
|
58
|
+
# * **argument**: `Array` of `CAS::Variable` that are argument of the function
|
59
|
+
# * **returns**: `CAS::Function`
|
60
|
+
def initialize(name, *xs)
|
61
|
+
xs.flatten!
|
62
|
+
CAS::Help.assert_name name
|
63
|
+
xs.each do |x|
|
64
|
+
CAS::Help.assert x, CAS::Op
|
65
|
+
end
|
66
|
+
# raise CASError, "Function #{name} already exists" if CAS::Function.exist? name
|
67
|
+
|
68
|
+
@x = xs.uniq
|
69
|
+
@name = name
|
70
|
+
@@container[@name] = self
|
71
|
+
end
|
72
|
+
|
73
|
+
# Overrides new method. This will return an existing function if in the function container
|
74
|
+
#
|
75
|
+
# * **requires**: `String` or `Symbol` that is the name of the function
|
76
|
+
# * **requires**: `Array` of `CAS::Variable`
|
77
|
+
# * **returns**: a new `CAS::Function` or the old one
|
78
|
+
def Function.new(name, *xs)
|
79
|
+
xs.flatten!
|
80
|
+
if @@container[name]
|
81
|
+
# return @@container[name] if (@@container[name].x.uniq - xs.uniq == [] or xs.size == 0)
|
82
|
+
return @@container[name] if (@@container[name].x.uniq.map(&:to_s).sort == xs.uniq.map(&:to_s).sort or xs.size == 0)
|
83
|
+
raise CASError, "Function #{name} already defined with different arguments!"
|
84
|
+
end
|
85
|
+
super
|
86
|
+
end
|
87
|
+
|
88
|
+
# Returns an array containing `CAS::Variable`s argument of the function
|
89
|
+
# Plese note that sub Op will return their args.
|
90
|
+
#
|
91
|
+
# * **returns**: `Array` containing `CAs::Variable`
|
92
|
+
def args
|
93
|
+
ret = []
|
94
|
+
@x.each do |e|
|
95
|
+
ret << e.args
|
96
|
+
end
|
97
|
+
ret.flatten.uniq
|
98
|
+
end
|
99
|
+
|
100
|
+
# Get an element in a particular position of the argument
|
101
|
+
#
|
102
|
+
# * **requires**: an iterator
|
103
|
+
# * **returns**: element of the argument `CAS::Op` or `NilClass`
|
104
|
+
def [](i); @x[i]; end
|
105
|
+
|
106
|
+
# Simplifications cannot be performed on anonymous function, thus it will always return
|
107
|
+
# the `self` `CAS::Function` object
|
108
|
+
#
|
109
|
+
# * **returns**: `CAS::Function` self instance
|
110
|
+
def simplify; self; end
|
111
|
+
|
112
|
+
# Tries to convert an anonymous function into Ruby code will always raise a `CASError` because it
|
113
|
+
# is not possible to generate code for such a fuction
|
114
|
+
#
|
115
|
+
# * **raises**: `CAS::CASError`: Ruby code for CAs::Function cannot be generated
|
116
|
+
def to_code
|
117
|
+
raise CASError, "Ruby code for #{self.class} cannot be generated"
|
118
|
+
end
|
119
|
+
|
120
|
+
# Substitutions in which a function is involved directly generates a CAS::Error unless the substitution will
|
121
|
+
# involve another variable. Example:
|
122
|
+
#
|
123
|
+
# ``` ruby
|
124
|
+
# (CAS.declare :f [x, y, z]).subs { x => x ** 2 } # this raises CASError
|
125
|
+
# (CAS.declare :f [x, y, z]).subs { x => y } # this returns f(y, z)
|
126
|
+
# ```
|
127
|
+
#
|
128
|
+
# * **requires**: a substitution `Hash`
|
129
|
+
# * **returns**: a `CAS::Function` with modified argument list
|
130
|
+
# * **raises**: `CASError` if something different with resppect to a `CAS::Variable` is a active substitution
|
131
|
+
def subs(s)
|
132
|
+
@x.each { |e| e.subs(s) }
|
133
|
+
self
|
134
|
+
end
|
135
|
+
|
136
|
+
# Performs the derivative with respect to one of the variable. The new function
|
137
|
+
# has a name with respect to a schema that for now is fixed (TODO: make it variable and user defined).
|
138
|
+
#
|
139
|
+
# * **requires**: a `CAS::Variable` for derivative
|
140
|
+
# * **returns**: the `CAS::Variable` derivated function
|
141
|
+
def diff(v)
|
142
|
+
# return CAS.declare :"d#{@name}[#{v}]", @x
|
143
|
+
ret = []
|
144
|
+
@x.each_with_index do |x, k|
|
145
|
+
dx = (x.depend?(v) ? x.diff(v) : CAS::Zero)
|
146
|
+
dfx = CAS.declare :"D#{@name}[#{k}]", @x
|
147
|
+
ret << dx * dfx
|
148
|
+
end
|
149
|
+
return CAS::Zero if ret == []
|
150
|
+
return ret.inject { |d, e| d += e }
|
151
|
+
end
|
152
|
+
|
153
|
+
# Trying to call a `CAS::Function` will always return a `CAS::Error`
|
154
|
+
#
|
155
|
+
# * **raises**: `CAS::CASError`
|
156
|
+
def call(_v)
|
157
|
+
raise CASError, "Cannot call a #{self.class}"
|
158
|
+
end
|
159
|
+
|
160
|
+
# Returns the inspect string of the function, that is similar to `CAS::Function#to_s`
|
161
|
+
#
|
162
|
+
# * **returns**: inspection `String`
|
163
|
+
def inspect; self.to_s; end
|
164
|
+
|
165
|
+
# Returns a description `String` for the `CAS::Function`
|
166
|
+
#
|
167
|
+
# * **returns**: `String`
|
168
|
+
def to_s
|
169
|
+
"#{@name}(#{@x.map(&:to_s).join(", ")})"
|
170
|
+
end
|
171
|
+
|
172
|
+
# Checks if two functions can be considered equal (same name, same args)
|
173
|
+
#
|
174
|
+
# * **requires**: another op to be checked against
|
175
|
+
# * **returns**: `TrueClass` if functions are equal, `FalseClass` if not equal
|
176
|
+
def ==(op)
|
177
|
+
return false if not self.class == op.class
|
178
|
+
return false if not (@name == op.name and @x.uniq == op.x.uniq)
|
179
|
+
true
|
180
|
+
end
|
181
|
+
end # Function
|
182
|
+
|
183
|
+
class << self
|
184
|
+
# This shortcut allows to declare a new function
|
185
|
+
#
|
186
|
+
# * **requires**: `String` or `Symbol` that is the name of the function
|
187
|
+
# * **requires**: `Array` of `CAS::Variable`
|
188
|
+
# * **returns**: a new `CAS::Function` or the old one
|
189
|
+
def declare(name, *xs)
|
190
|
+
xs.flatten!
|
191
|
+
CAS::Function.new(name, xs)
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|
@@ -0,0 +1,202 @@
|
|
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
|
+
@@container = {}
|
16
|
+
|
17
|
+
# Returns the `Hash` that contains all the variable
|
18
|
+
#
|
19
|
+
# * **returns**: `Hash`
|
20
|
+
def self.list
|
21
|
+
@@container
|
22
|
+
end
|
23
|
+
|
24
|
+
# Return the number of variable defined
|
25
|
+
#
|
26
|
+
# * **returns**: `Fixnum`
|
27
|
+
def self.size
|
28
|
+
@@container.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
|
+
@@container[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
|
+
@@container.keys.include? name
|
45
|
+
end
|
46
|
+
|
47
|
+
# The attribute `name` identifies the current variable. A variable with the
|
48
|
+
# same name of an existing variable connot be defined
|
49
|
+
attr_reader :name
|
50
|
+
|
51
|
+
# Variable is a container for an atomic simbol that becomes a number
|
52
|
+
# when `CAS::Op#call` method is used.
|
53
|
+
#
|
54
|
+
# * **argument**: `Object` that is a identifier for the variable
|
55
|
+
# * **returns**: `CAS::Variable` instance
|
56
|
+
def initialize(name)
|
57
|
+
CAS::Help.assert_name name
|
58
|
+
raise CASError, "Variable #{name} already exists" if CAS::Variable.exist? name
|
59
|
+
@name = name
|
60
|
+
@@container[@name] = self
|
61
|
+
end
|
62
|
+
|
63
|
+
# Overrides new method. This will return an existing variable if in variable container
|
64
|
+
#
|
65
|
+
# * **requires**: `Object` that is an identifier for the variable
|
66
|
+
# * **returns**: new variable instance o
|
67
|
+
def Variable.new(name)
|
68
|
+
@@container[name] || super
|
69
|
+
end
|
70
|
+
|
71
|
+
# Returns the derivative of a variable
|
72
|
+
#
|
73
|
+
# ```
|
74
|
+
# dx dx
|
75
|
+
# -- = 1; -- = 0
|
76
|
+
# dx dy
|
77
|
+
# ```
|
78
|
+
#
|
79
|
+
# * **argument**: `CAS::Op` for the derivative denominator
|
80
|
+
# * **returns**: `CAS::Constant`, 0 if not depended, 1 if dependent
|
81
|
+
def diff(v)
|
82
|
+
(self == v ? CAS::One : CAS::Zero)
|
83
|
+
end
|
84
|
+
|
85
|
+
# Returns `TrueClass` if argument of the function is equal
|
86
|
+
# to `self`
|
87
|
+
#
|
88
|
+
# * **argument**: `CAS::Op`
|
89
|
+
# * **returns**: `TrueClass` or `FalseClass`
|
90
|
+
def depend?(v)
|
91
|
+
self == v
|
92
|
+
end
|
93
|
+
|
94
|
+
# Equality operator, the standard operator is overloaded
|
95
|
+
# :warning: this operates on the graph, not on the math
|
96
|
+
# See `CAS::equal`, etc.
|
97
|
+
#
|
98
|
+
# * **argument**: `CAS::Op` to be tested against
|
99
|
+
# * **returns**: `TrueClass` if equal, `FalseClass` if differs
|
100
|
+
def ==(op)
|
101
|
+
# CAS::Help.assert(op, CAS::Op)
|
102
|
+
if op.is_a? CAS::Variable
|
103
|
+
return self.inspect == op.inspect
|
104
|
+
else
|
105
|
+
false
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
# Call resolves the operation tree in a `Numeric` (if `Fixnum`)
|
110
|
+
# or `Float` (depends upon promotions).
|
111
|
+
# As input, it requires an hash with `CAS::Variable` or `CAS::Variable#name`
|
112
|
+
# as keys, and a `Numeric` as a value
|
113
|
+
#
|
114
|
+
# ``` ruby
|
115
|
+
# x, y = CAS::vars :x, :y
|
116
|
+
# f = (x ** 2) + (y ** 2)
|
117
|
+
# f.call({x => 1, y => 2})
|
118
|
+
# # => 2
|
119
|
+
# ```
|
120
|
+
#
|
121
|
+
# * **argument**: `Hash` with feed dictionary
|
122
|
+
# * **returns**: `Numeric`
|
123
|
+
def call(f)
|
124
|
+
CAS::Help.assert(f, Hash)
|
125
|
+
|
126
|
+
return f[self] if f[self]
|
127
|
+
return f[@name] if f[@name]
|
128
|
+
end
|
129
|
+
|
130
|
+
# Convert expression to string
|
131
|
+
#
|
132
|
+
# * **returns**: `String` to print on screen
|
133
|
+
def to_s
|
134
|
+
"#{@name}"
|
135
|
+
end
|
136
|
+
|
137
|
+
# Convert expression to code (internal, for `CAS::Op#to_proc` method)
|
138
|
+
#
|
139
|
+
# * **returns**: `String` that represent Ruby code to be parsed in `CAS::Op#to_proc`
|
140
|
+
def to_code
|
141
|
+
"#{@name}"
|
142
|
+
end
|
143
|
+
|
144
|
+
# Returns an array containing `self`
|
145
|
+
#
|
146
|
+
# * **returns**: `Array` containing `self`
|
147
|
+
def args
|
148
|
+
[self]
|
149
|
+
end
|
150
|
+
|
151
|
+
# Terminal substitutions for variables. If input datatable
|
152
|
+
# contains the variable will perform the substitution with
|
153
|
+
# the value.
|
154
|
+
#
|
155
|
+
# * **argument**: `Hash` of substitutions
|
156
|
+
# * **returns**: `CAS::Op` of substitutions
|
157
|
+
def subs(dt)
|
158
|
+
CAS::Help.assert(dt, Hash)
|
159
|
+
if dt.keys.include? self
|
160
|
+
if dt[self].is_a? CAS::Op
|
161
|
+
return dt[self]
|
162
|
+
elsif dt[self].is_a? Numeric
|
163
|
+
return CAS::const(dt[self])
|
164
|
+
else
|
165
|
+
raise CASError, "Impossible subs. Received a #{dt[self].class} = #{dt[self]}"
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
# Inspector for the current object
|
171
|
+
#
|
172
|
+
# * **returns**: `String`
|
173
|
+
def inspect
|
174
|
+
"Var(#{@name})"
|
175
|
+
end
|
176
|
+
|
177
|
+
# Simplification callback. The only possible simplification
|
178
|
+
# is returning `self`
|
179
|
+
#
|
180
|
+
# * **returns**: `CAS::Variable` as `self`
|
181
|
+
def simplify
|
182
|
+
self
|
183
|
+
end
|
184
|
+
end # Number
|
185
|
+
|
186
|
+
# Allows to define a series of new variables.
|
187
|
+
#
|
188
|
+
# ``` ruby
|
189
|
+
# x, y = CAS::vars :x, :y
|
190
|
+
# ```
|
191
|
+
#
|
192
|
+
# * **argument**: `Array` of Numeric
|
193
|
+
# * **returns**: `Array` of `CAS::Variable`
|
194
|
+
def self.vars(*name)
|
195
|
+
(return CAS::Variable.new(name[0])) if name.size == 1
|
196
|
+
ret = []
|
197
|
+
name.each do |n|
|
198
|
+
ret << CAS::Variable.new(n)
|
199
|
+
end
|
200
|
+
return ret
|
201
|
+
end
|
202
|
+
end
|
@@ -0,0 +1,186 @@
|
|
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
|
+
sub = dt.keys.select { |e| e == @x }[0]
|
83
|
+
if sub
|
84
|
+
if dt[sub].is_a? CAS::Op
|
85
|
+
@x = dt[sub]
|
86
|
+
elsif dt[sub].is_a? Numeric
|
87
|
+
@x = CAS::const dt[sub]
|
88
|
+
else
|
89
|
+
raise CASError, "Impossible subs. Received a #{dt[sub].class} = #{dt[sub]}"
|
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
|
+
sub = dt.keys.select { |e| e == @y }[0]
|
104
|
+
if sub
|
105
|
+
if dt[sub].is_a? CAS::Op
|
106
|
+
@y = dt[sub]
|
107
|
+
elsif dt[sub].is_a? Numeric
|
108
|
+
@y = CAS::const dt[sub]
|
109
|
+
else
|
110
|
+
raise CASError, "Impossible subs. Received a #{dt[sub].class} = #{dt[sub]}"
|
111
|
+
end
|
112
|
+
else
|
113
|
+
@y.subs(dt)
|
114
|
+
end
|
115
|
+
return self
|
116
|
+
end
|
117
|
+
|
118
|
+
# Same `CAS::Op#call`
|
119
|
+
#
|
120
|
+
# * **argument**: `Hash` of values
|
121
|
+
# * **returns**: `Numeric` for result
|
122
|
+
def call(_fd)
|
123
|
+
raise CAS::CASError, "Not Implemented. This is a virtual method"
|
124
|
+
end
|
125
|
+
|
126
|
+
# String representation of the tree
|
127
|
+
#
|
128
|
+
# * **returns**: `String`
|
129
|
+
def to_s
|
130
|
+
raise CAS::CASError, "Not Implemented. This is a virtual method"
|
131
|
+
end
|
132
|
+
|
133
|
+
# Code to be used in `CAS::BinaryOp#to_proc`
|
134
|
+
#
|
135
|
+
# * **returns**: `String`
|
136
|
+
def to_code
|
137
|
+
raise CAS::CASError, "Not implemented. This is a virtual method"
|
138
|
+
end
|
139
|
+
|
140
|
+
# Returns an array of all the variables that are in the graph
|
141
|
+
#
|
142
|
+
# * **returns**: `Array` of `CAS::Variable`s
|
143
|
+
def args
|
144
|
+
(@x.args + @y.args).uniq
|
145
|
+
end
|
146
|
+
|
147
|
+
# Inspector
|
148
|
+
#
|
149
|
+
# * **returns**: `String`
|
150
|
+
def inspect
|
151
|
+
"#{self.class}(#{@x.inspect}, #{@y.inspect})"
|
152
|
+
end
|
153
|
+
|
154
|
+
# Comparison with other `CAS::Op`. This is **not** a math operation.
|
155
|
+
#
|
156
|
+
# * **argument**: `CAS::Op` to be compared against
|
157
|
+
# * **returns**: `TrueClass` if equal, `FalseClass` if different
|
158
|
+
def ==(op)
|
159
|
+
CAS::Help.assert(op, CAS::Op)
|
160
|
+
if op.is_a? CAS::BinaryOp
|
161
|
+
return (self.class == op.class and @x == op.x and @y == op.y)
|
162
|
+
else
|
163
|
+
return false
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
# Executes simplifications of the two branches of the graph
|
168
|
+
#
|
169
|
+
# * **returns**: `CAS::BinaryOp` as `self`
|
170
|
+
def simplify
|
171
|
+
hash = @x.to_s
|
172
|
+
@x = @x.simplify
|
173
|
+
while @x.to_s != hash
|
174
|
+
hash = @x.to_s
|
175
|
+
@x = @x.simplify
|
176
|
+
end
|
177
|
+
hash = @y.to_s
|
178
|
+
@y = @y.simplify
|
179
|
+
while @y.to_s != hash
|
180
|
+
hash = @y.to_s
|
181
|
+
@y = @y.simplify
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end # BinaryOp
|
185
|
+
CAS::BinaryOp.init_simplify_dict
|
186
|
+
end
|