lib/functions/fnc-base.rb
#!/usr/vin/env ruby
# Copyright (c) 2016 Matteo Ragni
#
# Permission is hereby granted, free of charge, to any person
# obtaining a copy of this software and associated documentation
# files (the "Software"), to deal in the Software without
# restriction, including without limitation the rights to use,
# copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the
# Software is furnished to do so, subject to the following
# conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
# OTHER DEALINGS IN THE SOFTWARE.
module CAS
# ___ _ __ __
# | \(_)/ _|/ _|
# | |) | | _| _/
# |___/|_|_| |_|
##
# **Difference basic operation**. It's a binary operation. This cannot
# be implemented as a n-ary op, thus will not be changed
class Diff < CAS::BinaryOp
# Performs the difference between two `CAS::Op`s
#
# ```
# d
# ---- (f(x) - g(x)) = f'(x) - g'(x)
# dx
# ```
#
# * **argument**: `CAS::Op` argument of derivative
# * **returns**: `CAS::Op` derivative
def diff(v)
left, right = super v
return left if right == CAS::Zero
return CAS::Invert.new(right) if left == CAS::Zero
left - right
end
# Same as `CAS::Op`
def call(f)
CAS::Help.assert(f, Hash)
return @x.call(f).overloaded_minus(@y.call(f))
end
# Same as `CAS::Op`
def to_s
"(#{@x} - #{@y})"
end
# Same as `CAS::Op`
#
# Simplifcation engine supports:
#
# * 0 - y = -y
# * x - 0 = x
# * a - b = c (constants reduction)
# * x - x = 0
# * x - (-y) = x + y
#
# * **returns**: `CAS::Op` simplified version
def simplify
super
return CAS.invert(@y) if @x == CAS::Zero
return @x if @y == CAS::Zero
return CAS::Zero if @x == @y
return CAS.const(self.call({})) if (@x.is_a? CAS::Constant and @y.is_a? CAS::Constant)
return @x + @y.x if @y.is_a? CAS::Invert
return -(@x.x + @y) if @x.is_a? CAS::Invert
return self
end
# 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`
def to_code
"(#{@x.to_code} - #{@y.to_code})"
end
end # Difference
CAS::Diff.init_simplify_dict
# ___
# | _ \_____ __ __
# | _/ _ \ V V /
# |_| \___/\_/\_/
##
# Power function.
class Pow < CAS::BinaryOp
# Performs the power between two `CAS::Op`
#
# ```
# d
# ---- (f(x)^a) = f(x)^(a - 1) * a * f'(x)
# dx
#
# d
# ---- (a^f(x)) = a^f(x) * f'(x) * ln a
# dx
#
# d
# ---- (f(x)^g(x)) = (f(x)^g(x)) * (g'(x) * ln f(x) + g(x) * f'(x) / f(x))
# dx
# ```
#
# * **argument**: `CAS::Op` argument of derivative
# * **returns**: `CAS::Op` derivative
def diff(v)
diff_x, diff_y = super v
if diff_y == CAS::Zero
return ((@x ** (@y - 1.0)) * @y * diff_x)
elsif diff_x == CAS::Zero
return (@x ** @y) * diff_y * CAS.ln(@x)
else
return (@x ** @y) * ((diff_y * CAS.ln(@x)) + (@y * diff_x / @x))
end
end
# 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. In this case it will call
# the `Fixnum#overloaded_plus`, that is the old plus function.
#
# * **argument**: `Hash` with feed dictionary
# * **returns**: `Numeric`
def call(f)
CAS::Help.assert(f, Hash)
@x.call(f).overloaded_pow(@y.call(f))
end
# Convert expression to string
#
# * **returns**: `String` to print on screen
def to_s
"(#{@x})^(#{@y})"
end
# Same as `CAS::Op`
#
# Simplifcation engine supports:
#
# * 0 ^ y = 0
# * x ^ 0 = 1
# * a ^ b = c (constants reduction)
# * x ^ 1 = x
# * 1 ^ y = 1
#
# * **returns**: `CAS::Op` simplified version
def simplify
super
return self if (@x == CAS::Zero and @y == CAS::Zero)
return self if (@x == CAS::Infinity and @y == CAS::Infinity)
return self if (@x == CAS::Infinity and @y == CAS::Zero)
return self if (@x == CAS::Zero and @y == CAS::Infinity)
return CAS::Zero if @x == CAS::Zero
return CAS::One if @x == CAS::One
return @x if @y == CAS::One
return CAS::One if @y == CAS::Zero
return CAS.const(self.call({})) if (@x.is_a? CAS::Constant and @y.is_a? CAS::Constant)
return self
end
# 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`
def to_code
"(#{@x.to_code} ** #{@y.to_code})"
end
end # Pow
CAS::Pow.init_simplify_dict
# Shortcut for `CAS::Pow` initializer
#
# * **argument**: `CAS::Op` base
# * **argument**: `CAS::Op` exponent
# * **returns**: `CAS::Pow` new instance
def self.pow(x, y)
CAS::Pow.new x, y
end
# ___ _
# | \(_)_ __
# | |) | \ V /
# |___/|_|\_/
##
# Division between two functions. A function divided by zero it is considered
# as an Infinity.
class Div < CAS::BinaryOp
# Performs the division between two `CAS::Op`
#
# ```
# d
# ---- (f(x) / g(x)) = (f'(x) * g(x) - f(x) * g'(x))/(g(x)^2)
# dx
# ```
#
# * **argument**: `CAS::Op` argument of derivative
# * **returns**: `CAS::Op` derivative
def diff(v)
diff_x, diff_y = super v
if diff_y == CAS::Zero
return (diff_x/@y)
elsif diff_x == CAS::Zero
return CAS.invert(@x * diff_y / CAS.pow(@y, CAS.const(2.0)))
else
return ((diff_x * @y) - (diff_y * @x))/CAS.pow(@y, CAS.const(2.0))
end
end
# 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. In this case it will call
# the `Fixnum#overloaded_plus`, that is the old plus function.
#
# * **argument**: `Hash` with feed dictionary
# * **returns**: `Numeric`
def call(f)
CAS::Help.assert(f, Hash)
@x.call(f).overloaded_div(@y.call(f))
end
# Convert expression to string
#
# * **returns**: `String` to print on screen
def to_s
"(#{@x}) / (#{@y})"
end
# Same as `CAS::Op`
#
# Simplifcation engine supports:
#
# * 0 / y = 0
# * x / 0 = Inf
# * x / 1 = x
# * x / Inf = 0
# * a / b = c (constants reduction)
#
# * **returns**: `CAS::Op` simplified version
def simplify
super
return self if (@x == CAS::Zero and @y == CAS::Zero)
return self if (@x == CAS::Infinity and @y == CAS::Infinity)
return self if (@x == CAS::Infinity and @y == CAS::Zero)
return self if (@x == CAS::Zero and @y == CAS::Infinity)
return CAS::Zero if @x == CAS::Zero
return CAS::Infinity if @y == CAS::Zero
return @x if @y == CAS::One
return CAS::Zero if @y == CAS::Infinity
return CAS::One if @x == @y
return CAS.const(self.call({})) if (@x.is_a? CAS::Constant and @y.is_a? CAS::Constant)
return self
end
# 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`
def to_code
"(#{@x.to_code} / #{@y.to_code})"
end
end # Div
CAS::Div.init_simplify_dict
# ___ _
# / __| __ _ _ _| |_
# \__ \/ _` | '_| _|
# |___/\__, |_| \__|
# |_|
##
# Square Root of a function. Even if it can be implemented as a power function,
# it is a separated class.
class Sqrt < CAS::Op
# Performs the square root between two `CAS::Op`
#
# ```
# d
# ---- √f(x) = 1/2 * f'(x) * √f(x)
# dx
# ```
#
# * **argument**: `CAS::Op` argument of derivative
# * **returns**: `CAS::Op` derivative
def diff(v)
if @x.depend? v
return (@x.diff(v) / (CAS.const(2.0) * CAS.sqrt(@x)))
else
return CAS::Zero
end
end
# 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. In this case it will call
# the `Fixnum#overloaded_plus`, that is the old plus function.
#
# * **argument**: `Hash` with feed dictionary
# * **returns**: `Numeric`
def call(f)
CAS::Help.assert(f, Hash)
Math::sqrt @x.call(f)
end
# Convert expression to string
#
# * **returns**: `String` to print on screen
def to_s
"√(#{@x})"
end
# Same as `CAS::Op`
#
# Simplifcation engine supports:
#
# * √(x^z) = x^(z - 1/2)
# * √x = 0
# * √x = 1
# * √a = b (constants reduction)
#
# * **returns**: `CAS::Op` simplified version
def simplify
super
return (CAS.pow(@x.x, @x.y - 0.5)).simplify if @x.is_a? CAS::Pow
return CAS::Zero if @x == CAS::Zero
return CAS::One if @x == CAS::One
return CAS.const(self.call({})) if (@x.is_a? CAS::Constant and @y.is_a? CAS::Constant)
return self
end
# 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`
def to_code
"Math::sqrt(#{@x.to_code})"
end
end # Sqrt
CAS::Sqrt.init_simplify_dict
# Shortcut for `CAS::Sqrt` initializer
#
# * **argument**: `CAS::Op` argument of square root
# * **returns**: `CAS::Sqrt` new instance
def self.sqrt(x)
CAS::Sqrt.new x
end
# ___ _
# |_ _|_ ___ _____ _ _| |_
# | || ' \ V / -_) '_| _|
# |___|_||_\_/\___|_| \__|
##
# Invert is the same as multiply by `-1` a function.
# `Invert(x)` is equal to `-x`
class Invert < CAS::Op
# Performs the inversion of a `CAS::Op`
#
# ```
# d
# ---- (-f(x)) = -f'(x)
# dx
# ```
#
# * **argument**: `CAS::Op` argument of derivative
# * **returns**: `CAS::Op` derivative
def diff(v)
if @x.depend? v
-@x.diff(v)
else
CAS::Zero
end
end
# 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. In this case it will call
# the `Fixnum#overloaded_plus`, that is the old plus function.
#
# * **argument**: `Hash` with feed dictionary
# * **returns**: `Numeric`
def call(f)
CAS::Help.assert(f, Hash)
-1.0 * @x.call(f)
end
# Convert expression to string
#
# * **returns**: `String` to print on screen
def to_s
"-#{@x}"
end
# Same as `CAS::Op`
#
# Simplifcation engine supports:
#
# * -(-x) = x
# * -0 = 0
#
# * **returns**: `CAS::Op` simplified version
def simplify
super
return @x.x if @x.is_a? CAS::Invert
return self.simplify_dictionary
end
def self.init_simplify_dict
@simplify_dict = {
CAS::Zero => CAS::Zero
}
end
# 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`
def to_code
"(-#{@x.to_code})"
end
end # Invert
CAS::Invert.init_simplify_dict
# Shortcut for `CAs::Invert` initializer
#
# * **argument**: `CAs::Op` argument of the inversion
# * **returns**: `CAS::Invert` new instance
def self.invert(x)
CAS::Invert.new x
end
# _ _
# /_\ | |__ ___
# / _ \| '_ (_-<
# /_/ \_\_.__/__/
##
# Absolute value of a function. It can be also implemented as a Piecewise function.
class Abs < CAS::Op
# Performs the absolute value of a `CAS::Op`
#
# ```
# d
# ---- |f(x)| = f'(x) * (f(x) / |f(x)|)
# dx
# ```
#
# * **argument**: `CAS::Op` argument of derivative
# * **returns**: `CAS::Op` derivative
def diff(v)
if @x.depend? v
return @x.diff(v) * (@x/CAS.abs(@x))
else
return CAS::Zero
end
end
# 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. In this case it will call
# the `Fixnum#overloaded_plus`, that is the old plus function.
#
# * **argument**: `Hash` with feed dictionary
# * **returns**: `Numeric`
def call(f)
CAS::Help.assert(f, Hash)
s = (@x.call(f) >= 0 ? 1 : -1)
return s * @x.call(f)
end
# Convert expression to string
#
# * **returns**: `String` to print on screen
def to_s
"|#{@x}|"
end
# Same as `CAS::Op`
#
# Simplifcation engine supports:
#
# * |-x| = x
# * |0| = 0
#
# * **returns**: `CAS::Op` simplified version
def simplify
super
return CAS.abs(@x.x) if @x.is_a? CAS::Invert
return self.simplify_dictionary
end
def self.init_simplify_dict
@simplify_dict = {
CAS::Zero => CAS::Zero
}
end
# 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`
def to_code
"(#{@x.to_code}).abs"
end
end # Abs
CAS::Abs.init_simplify_dict
# Shortcut for `CAs::Abs` initializer
#
# * **argument**: `CAs::Op` argument of absolute value
# * **returns**: `CAS::Abs` new instance
def self.abs(x)
CAS::Abs.new x
end
end