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