Mr.CAS 0.2.6

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