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.
@@ -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