Mr.CAS 0.2.6
Sign up to get free protection for your applications and to get access to all the features.
- 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
|