Class: CAS::NaryOp

Inherits:
Op
  • Object
show all
Defined in:
lib/operators/nary-op.rb,
lib/Mr.CAS/graphviz.rb

Overview

This is an attempt to build some sort of node in the graph that has arbitrary number of childs node. It should help implement more easily some sort of better simplifications engine

This is an incredibly experimental feature.

Direct Known Subclasses

Function, Prod, Sum

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods inherited from Op

#!=, #*, #**, #+, #-, #[email protected], #/, #as_proc, #equal, #greater, #greater_equal, init_simplify_dict, #limit, numeric_to_const, simplify_dict, #simplify_dictionary, #smaller, #smaller_equal, #to_c_lib

Constructor Details

#initialize(*xs) ⇒ NaryOp

Initialize a new empty N-elements operation container. This is a virtual class, and other must inherit from this basical container

* **argument**: `Numeric` to be converted in `CAS::Constant` or `CAS::Op` child operations
* **returns**: `CAS::NaryOp` instance

41
42
43
44
45
46
47
48
49
50
# File 'lib/operators/nary-op.rb', line 41

def initialize(*xs)
  @x = []
  xs.flatten.each do |x|
    if x.is_a? Numeric
      x = Op.numeric_to_const x
    end
    CAS::Help.assert(x, CAS::Op)
    @x << x
  end
end

Instance Attribute Details

#xObject (readonly)

List of arguments of the operation


34
35
36
# File 'lib/operators/nary-op.rb', line 34

def x
  @x
end

Instance Method Details

#==(op) ⇒ Object

Equality operator, the standard operator is overloaded :warning: this operates on the graph, not on the math See `CAS::equal`, etc.

* **argument**: `CAS::Op` to be tested against
* **returns**: `TrueClass` if equal, `FalseClass` if differs

180
181
182
183
184
185
186
187
188
189
190
# File 'lib/operators/nary-op.rb', line 180

def ==(op)
  # CAS::Help.assert(op, CAS::Op)
  if op.is_a? CAS::NaryOp
    return false if @x.size != op.x.size
    0.upto(@x.size - 1) do |i|
      return false if @x[i] != op.x[i]
    end
    return true
  end
  false
end

#__reduce_constants(xs) ⇒ Object

Collects all the constants and tries to reduce them to a single constant. Requires a block to understand what it should do with the constants

  • *requires*: input `Array` of `CAS::Op`

  • *returns*: new `Array` of `CAS::Op`

  • *block*: yields an `Array` of `CAS::Constant` and an `Array` of others `CAS::Op`, requires an `Array` back


247
248
249
250
251
252
253
254
255
# File 'lib/operators/nary-op.rb', line 247

def __reduce_constants(xs)
  const = []
  xs.each { |x| const << x if x.is_a? CAS::Constant }
  if const.size > 0
    yield const, (xs - const)
  else
    xs
  end
end

#__reduce_multeplicity(xs) ⇒ Object

Reduce multeplicity will scan for elements that are equal in the definition of the sum and will reduce their multeplicity. A block can be used to do something different. For example in nary-product we use it like this:

“` ruby end “`

In general it works like that:

“`

a + a + b + c => 2 * a + b + c
a * a * b * a => (a ** b) * b

“` But operates only on Array level! This is an internal function and should never be used

* **requires**: An `Array`
* **returns**: An `Array` with multeplicity reduced
* **block**: yields the count and the op. Get the value to insert in a new
  `Array` that is the returned `Array`

224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
# File 'lib/operators/nary-op.rb', line 224

def __reduce_multeplicity(xs)
  count = Hash.new(0)
  xs.each do |x|
    e = x
    count.keys.each { |d| e = d if x == d  }
    count[e] += 1
  end
  count.map do |k, v|
    if block_given?
      yield(k, v)
    else
      v > 1 ? CAS.const(v) * k : k
    end
  end
end

#argsObject

Returns a list of all `CAS::Variable`s of the current tree

* **returns**: `Array` of `CAS::Variable`s

195
196
197
198
199
# File 'lib/operators/nary-op.rb', line 195

def args
  r = []
  @x.each { |x| r += x.args }
  return r.uniq
end

#call(fd) ⇒ Object

Call resolves the operation tree in a `Numeric` (if `Fixnum`) or `Float` depends upon promotions). As input, it requires an hash with `CAS::Variable` or `CAS::Variable#name` as keys, and a `Numeric` as a value

“` ruby x, y = CAS::vars :x, :y f = (x ** 2) + (y ** 2) f.call(=> 1, y => 2) # => 2 “`

* **argument**: `Hash` with feed dictionary
* **returns**: `Array` of `Numeric`

103
104
105
106
# File 'lib/operators/nary-op.rb', line 103

def call(fd)
  CAS::Help.assert(fd, Hash)
  return @x.map { |x| x.call(fd) }
end

#depend?(v) ⇒ Boolean

Returns the dependencies of the operation. Require a `CAS::Variable` and it is one of the recursive method (implicit tree resolution)

* **argument**: `CAS::Variable` instance
* **returns**: `TrueClass` or `FalseClass`

Returns:

  • (Boolean)

57
58
59
60
61
62
63
64
# File 'lib/operators/nary-op.rb', line 57

def depend?(v)
  CAS::Help.assert(v, CAS::Op)
  ret = false
  @x.each do |y|
    ret |= y.depend?(v)
  end
  return ret
end

#diff(v) ⇒ Object

Return a list of derivative using the chain rule. The input is a operation:

“`

f(x) = g(x) + h(x) + l(x) + m(x)

d f(x)
------ = g'(x) + h'(x) + l'(x) + m'(x)
  dx

d f(x)
------ = 1
d g(x)

“`

* **argument**: `CAS::Op` object of the derivative
* **returns**: `CAS::NaryOp` of derivative

82
83
84
85
86
87
88
# File 'lib/operators/nary-op.rb', line 82

def diff(v)
  CAS::Help.assert(v, CAS::Op)
  if self.depend?(v)
    return @x.map { |x| x.diff(v) }
  end
  return CAS::Zero
end

#dot_graphObject

Return the local Graphviz node of the tree

* **returns**: `String` of local Graphiz node

62
63
64
65
66
67
68
69
# File 'lib/Mr.CAS/graphviz.rb', line 62

def dot_graph
  cls = "#{self.class.to_s.gsub("CAS::", "")}_#{self.object_id}"
  ret = ""
  @x.each do |x|
    ret += "#{cls} -> #{x.dot_graph}\n"
  end
  return ret
end

#inspectObject

Inspector for the current object

* **returns**: `String`

170
171
172
# File 'lib/operators/nary-op.rb', line 170

def inspect
  "#{self.class}(#{@x.map(&:inspect).join(", ")})"
end

#simplifyObject

Simplification callback. It simplify the subgraph of each node until all possible simplification are performed (thus the execution time is not deterministic).

* **returns**: `CAS::Op` simplified

158
159
160
161
162
163
164
165
# File 'lib/operators/nary-op.rb', line 158

def simplify
  hash = self.to_s
  @x = @x.map { |x| x.simplify }
  while self.to_s != hash
    hash = self.to_s
    @x = @x.map { |x| x.simplify }
  end
end

#subs(dt) ⇒ Object

Perform substitution of a part of the graph using a data table:

“` ruby x, y = CAS::vars :x, :y f = (x ** 2) + (y ** 2) puts f # => (x^2) + (y^2) puts f.subs(=> CAS::ln(y)) # => (ln(y)^2) + (y^2) “`

* **argument**: `Hash` with substitution table
* **returns**: `CAS::NaryOp` (`self`) with substitution performed

121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
# File 'lib/operators/nary-op.rb', line 121

def subs(dt)
  CAS::Help.assert(dt, Hash)
  @x = @x.map { |z| z.subs(dt) || z }
  @x.each_with_index do |x, k|
    sub = dt.keys.select { |e| e == x }[0]
    if sub
      if dt[sub].is_a? CAS::Op
        @x[k] = dt[sub]
      elsif dt[sub].is_a? Numeric
        @x[k] = CAS::const dt[sub]
      else
        raise CAS::CASError, "Impossible subs. Received a #{dt[sub].class} = #{dt[sub]}"
      end
    end
  end
  return self
end

#to_codeObject

Convert expression to code (internal, for `CAS::Op#to_proc` method)

* **returns**: `String` that represent Ruby code to be parsed in `CAS::Op#to_proc`

149
150
151
# File 'lib/operators/nary-op.rb', line 149

def to_code
  return "(#{@x.map(&:to_code).join(", ")})"
end

#to_sObject

Convert expression to string

* **returns**: `String` to print on screen

142
143
144
# File 'lib/operators/nary-op.rb', line 142

def to_s
  return "(#{@x.map(&:to_s).join(", ")})"
end