Navigation Menu

Skip to content

Commit

Permalink
Merge branch 'complex'
Browse files Browse the repository at this point in the history
  • Loading branch information
jgoizueta committed Jun 22, 2010
2 parents bf00482 + ac1d405 commit ffdab5d
Show file tree
Hide file tree
Showing 4 changed files with 337 additions and 15 deletions.
4 changes: 2 additions & 2 deletions README.txt
Expand Up @@ -536,7 +536,8 @@ Its functions can be accessed in a number of ways:
* Floating Point Tolerance: see the flt/tolerance.rb[link:files/lib/flt/tolerance_rb.html] file
and the Flt::Tolerance class
* Constructors: see Flt.DecNum(), Flt.BinNum() and Flt.Tolerance().
* Trigonometry functions: see Flt::Trigonometry..
* Trigonometry functions: see Flt::Trigonometry.
* Complex number support: see the flt/complex.rb[link:files/lib/flt/complex.rb.html] file

= DecNum vs BigDecimal

Expand Down Expand Up @@ -598,7 +599,6 @@ EXPAND+
= Roadmap

* Trigonometry optimizations
* Complex support.
* Implement the missing GDA functions:
rotate, shift, trim, and, or, xor, invert,
max, min, maxmag, minmag, comparetotal, comparetotmag
312 changes: 312 additions & 0 deletions lib/flt/complex.rb
@@ -0,0 +1,312 @@
# Complex number support for Flt::Num types.
#
# Complex is extended to handle properly components of Num types.
#
# Examples:
# require 'flt/complex'
# include Flt
# DecNum.context(:precision=>4) do
# puts (Complex(1,2)*DecNum(2)).abs.inspect # => DecNum('4.472')
# end
#
# Complex functions are provided through Num.ccontext object, Context#.cmath blocks and CMath modules.
#
# Examples:
# require 'flt/complex'
# include Flt
# DecNum.context.precision = 10
# puts DecNum.ccontext.sqrt(-2) # => 0+1.414213562i
# puts DecNum.ccontext(:precision=>5).sqrt(-2) # => 0+1.4142i
# puts DecNum.context(:precision=>6).cmath{sqrt(-2)} # => 0+1.41421i
#
# CMath Examples:
# DecNum.context(:precision=>5) do
# puts DecNum::CMath.sqrt(-2) # => 0+1.4142i
# end
# DecNum.context.precision = 10
# include DecNum::CMath
# puts sqrt(-2) # => 0+1.414213562i
#

require 'flt/math'
require 'complex'

class Complex

alias abs! abs
def abs
num_class.nil? ? abs! : num_class.context.hypot(real, imag)
end

alias polar! polar
def polar
num_class.nil? ? polar! : [num_class.context.hypot(real, imag), num_class.context.atan2(imag, real)]
end

# alias power! **
# def **(other)
# if classnum_class_num.nil? && other.class_num.nil?
# self.power!(other)
# else
# num_class.ccontext.power(self, other)
# end
# end

private

def num_class
real.kind_of?(Flt::Num) ? real.class : imag.kind_of?(Flt::Num) ? imag.class : nil
end

end # Complex

module Flt

class ComplexContext # consider: < Num::ContextBase

def initialize(context)
@context = context
end

def num_class
@context.num_class
end

def math
num_class.context
end

def Num(z)
if z.kind_of?(Complex)
Complex.rectangular(*z.rectangular.map{|v| @context.num_class[v]})
else
Complex.rectangular(@context.num_class[z])
end
end

def abs(z)
z.abs
end

def self.math_function(mth, negative_arg_to_complex=false, extra_prec=3, &blk)
class_eval do
define_method mth do |*args|
is_complex = args.detect{|z| z.kind_of?(Complex)}
if is_complex || (negative_arg_to_complex && args.first<0)
num_class.context(:extra_precision=>extra_prec) do |mth|
#Complex.rectangular *blk[mth, *args].map{|v| @context.plus(v)}
Complex.rectangular *instance_exec(mth,*args,&blk).map{|v| @context.plus(v)}
end
else
@context.send(mth, *args) # Num(@context.send(mth, *args)) ?
end
end
end
end

math_function :exp do |mth, z|
re_exp = mth.exp(z.real)
[re_exp * mth.cos(z.imag), re_exp * mth.sin(z.imag)]
end

math_function :log, true do |mth, x, *args| # we can do |mth, x, b=nil| in Ruby 1.9 but not in 1.8
b = args.first
r, theta = Num(x).polar
[mth.log(r, b), theta]
end

math_function :ln, true do |mth, x|
r, theta = Num(x).polar
[mth.ln(r), theta]
end

math_function :log2, true do |mth, x|
r, theta = Num(x).polar
[mth.log2(r), theta]
end

math_function :log10, true do |mth, x|
r, theta = Num(x).polar
[mth.log10(r), theta]
end

math_function :power, true, 4 do |mth, x, y|
if (y.respond_to?(:zero?) && y.zero?) || y==0
[1,0]
else
r, theta = Num(x).polar
ore, oim = y.kind_of?(Complex) ? y.rectangular : [y, 0]
log_r = mth.ln(r)
nr = mth.exp(ore*log_r - oim*theta)
ntheta = theta*ore + oim*log_r
[nr*mth.cos(ntheta), nr*mth.sin(ntheta)]
end
end

def sqrt(z)
z_is_complex = z.kind_of?(Complex)
if z_is_complex || z<0
if z_is_complex
re = im = nil
num_class.context(:extra_precision=>3) do |mth|
z = Num(z)
i_sign = z.imag.sign
r = abs(z)
x = z.real
re = ((r+x)/2).sqrt
im = ((r-x)/2).sqrt.copy_sign(i_sign)
end
fix_rect(re, im)
else
Complex(0, @context.sqrt(-z))
end
else
@context.sqrt(z)
end
end

math_function :sin do |mth, z|
[mth.sin(z.real)*mth.cosh(z.imag), mth.cos(z.real)*mth.sinh(z.imag)]
end

math_function :cos do |mth, z|
[mth.cos(z.real)*mth.cosh(z.imag), -mth.sin(z.real)*mth.sinh(z.imag)]
end

math_function :tan do |mth, z|
mth.cmath{sin(z)/cos(z)}.rectangular
end

math_function :atan do |mth, z|
i = Complex(0,mth.num_class[1])
mth.cmath{i*ln((i+z)/(i-z))*num_class.one_half}.rectangular
end

math_function :sinh do |mth, z|
[mth.sinh(z.real)*mth.cos(z.imag), mth.cosh(z.real)*mth.sin(z.imag)]
end

math_function :cosh do |mth, z|
[mth.cosh(z.real)*mth.cos(z.imag), mth.sinh(z.real)*mth.sin(z.imag)]
end

math_function :tanh do |mth, z|
mth.cmath{sinh(z)/cosh(z)}.rectangular
end

math_function :asinh do |mth, z|
mth.cmath{ln(z+sqrt(z*z+1))}.rectangular
end

def asin(z)
z_is_complex = z.kind_of?(Complex)
if z_is_complex || z.abs>1
# z = Complex(1) unless z_is_complex
i = Complex(0,@context.num_class[1])
fix num_class.context(:extra_precision=>3).cmath {
-i*ln(i*z + sqrt(1-z*z))
}
else
@context.asin(z)
end
end

def acos(z)
z_is_complex = z.kind_of?(Complex)
if z_is_complex || z.abs>1
# z = Complex(1) unless z_is_complex
i = Complex(0,@context.num_class[1])
fix num_class.context(:extra_precision=>3).cmath {
-i*ln(z + i*sqrt(1-z*z))
}
else
@context.acos(z)
end
end

def acosh(z)
z_is_complex = z.kind_of?(Complex)
if z_is_complex || z<=1
# z = Complex(1) unless z_is_complex
fix num_class.context(:extra_precision=>3).cmath{ ln(z + sqrt(z*z-1)) }
else
@context.acosh(z)
end
end

def atanh(z)
z_is_complex = z.kind_of?(Complex)
if z_is_complex || z.abs>1
# z = Complex(1) unless z_is_complex
i = Complex(0,@context.num_class[1])
fix num_class.context(:extra_precision=>3).cmath{ num_class.one_half*ln((1+z)/(1-z)) }
else
@context.atanh(z)
end
end

extend Forwardable
def_delegators :@context, :pi


private

def fix_rect(re, im)
Complex(@context.plus(re), @context.plus(im))
end

def fix_polar(r, theta)
num_class.context(:extra_precision=>3) do |mth|
re = r*mth.cos(theta)
im = r*mth.sin(theta)
end
fix_rect(re, im)
end

def fix(z)
fix_rect *z.rectangular
end

end # ComplexContext

class Num

def self.ccontext(*args)
ComplexContext(self.context(*args))
end

class ContextBase
def cmath(*parameters, &blk)
# if ComplexContext is derived from ContextBase: return ComplexContext(self).math(*parameters, &blk)
num_class.context(self) do
if parameters.empty?
ComplexContext(num_class.context).instance_eval &blk
else
ComplexContext(num_class.context).instance_exec *parameters, &blk
end
end
end
end

end # Num

module_function
def ComplexContext(context)
ComplexContext.new(context)
end

module DecNum::CMath
include MathBase
num_class(DecNum){num_class.ccontext}
math_function *Trigonometry.public_instance_methods
math_function :exp, :log, :log2, :log10, :sqrt
end

module BinNum::CMath
include MathBase
num_class(BinNum){num_class.ccontext}
math_function *Trigonometry.public_instance_methods
math_function :exp, :log, :log2, :log10, :sqrt
end

end # Flt
32 changes: 21 additions & 11 deletions lib/flt/math.rb
Expand Up @@ -11,21 +11,35 @@ module Flt
#
# The math functions provided by Math modules are trigonometric (sin, cos, tan, asin, acos, atan, hypot),
# exp, log, log2, and log10.
#
# Example:
# DecNum.context(:precision=>5) do
# puts DecNum::Math.sqrt(2) # => 1.4142
# end
# DecNum.context.precision = 10
# include DecNum::Math
# puts sqrt(2) # => 1.414213562
#
module MathBase

def self.included(base)
base.extend ClassMethods
end

def context
self.class_num.context
end

module ClassMethods
def num_class(cls, &blk)
define_method(:num_class){cls}
if blk
define_method(:context, &blk)
else
define_method(:context){num_class.context}
end
module_function :num_class, :context
end
def math_function(*fs)
fs.each do |f|
define_method f do |*args|
self.num_class.context.send f, *args
context.send f, *args
end
module_function f
end
Expand All @@ -37,19 +51,15 @@ def math_function(*fs)
# Math module for DecNum; uses the current DecNum Context. See Flt::MathBase.
module DecNum::Math
include MathBase
def self.num_class
DecNum
end
num_class DecNum
math_function *Trigonometry.public_instance_methods
math_function :exp, :log, :log2, :log10, :sqrt
end

# Math module for DecNum; uses the current DecNum Context. See Flt::MathBase.
module BinNum::Math
include MathBase
def self.num_class
BinNum
end
num_class BinNum
math_function *Trigonometry.public_instance_methods
math_function :exp, :log, :log2, :log10, :sqrt
end
Expand Down

0 comments on commit ffdab5d

Please sign in to comment.