Forum: Ruby Need a ruby math genius - potential ruby bug.

Announcement (2017-05-07): www.ruby-forum.com is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see rubyonrails.org/community and ruby-lang.org/en/community for other Rails- und Ruby-related community platforms.
Todd S. (Guest)
on 2006-01-26 23:04
Please forgive the longish post, I've tried to boil down a bug to its
minimum and my hair is now nearly entirely pulled out.

The below code should be executable and requires only the "matrix.rb"
library that comes with a standard ruby install (At least I think it
does).

Under certain conditions, if I add a veriable to the script and never
use it the result of the procedure is changed.  I cannot determin why.

[code]
#!/usr/bin/env ruby
#=Synopsis
#Executable Script for debugging potential ruby floating point and/or
matrix error
require "matrix"

#Expand the Vector class from Matrix.rb to include:
#  normalize, sum, delta, dot, cross, effectively_equal
#Defaults to a triplet but can contain an n-deminsional vector
#  Currently most of these functions only operate on a 3-deminsional
vector.

class Vector
  attr :elements, true

  def normalize()
    divisor = 0
    @elements.each do |elem|
      divisor += elem*elem
    end

    if divisor != 0
      divisor = Math.sqrt(divisor);
      i = 0
      @elements.each do |elem|
        @elements[i] /= divisor
        i += 1
      end
    end
  end

  def Vector.normalize(v)
    divisor = 0
    v.elements.each do |elem|
      divisor += elem*elem
    end

    if divisor != 0
      divisor = Math.sqrt(divisor);
      i = 0
      v.elements.each do |elem|
        v.elements[i] /= divisor
        i += 1
      end
    end
    v
  end

  def Vector.sum(u, v)
    newVector = Vector[0,0,0]
    i=0
    u.elements.each do |uElem|
      newVector.elements[i] = uElem.to_f + v.elements[i].to_f
      i += 1
    end
    newVector
  end

  def Vector.delta(u, v)
    newVector = Vector[0,0,0]
    i=0
    # puts "U: #{u} V: #{v}"
    u.elements.each do |uElem|
      newVector.elements[i] = uElem.to_f - v.elements[i].to_f
      i += 1
    end
    newVector
  end

  def Vector.dot(u, v)
    value = 0
    i=0
    u.elements.each do |uElem|
      value += uElem.to_f * v.elements[i].to_f
      i += 1
    end
    value
  end

  def Vector.cross(u, v)
    n = Vector[0,0,0]
    n.elements[0] = u.elements[1].to_f * v.elements[2].to_f -
u.elements[2].to_f * v.elements[1].to_f
    n.elements[1] = u.elements[2].to_f * v.elements[0].to_f -
u.elements[0].to_f * v.elements[2].to_f
    n.elements[2] = u.elements[0].to_f * v.elements[1].to_f -
u.elements[1].to_f * v.elements[0].to_f
    n
  end

  def Vector.effectively_equal(u, v, epsilon = 0.000001)
    i = 0
    eq = TRUE
    u.elements.each do |uElem|
      if uElem.to_f - v.elements[i].to_f > epsilon
        eq = FALSE
      end
      break if not eq
      i += 1
    end
    eq
  end

end # Vector class expansion

# Note, This error occurs when delta of curVert and prevVert have an
axis of zero.
#       and have a resonably high percision.
curVert = Vector[0.015525, -0.284744, 0.532219]
prevVert = Vector[-0.007794, -0.283622, 0.532219]
nextVert = Vector[0.017021, -0.256189, 0.52561]

puts "-----------"
puts "prevVert \t#{prevVert}"
puts "curVert \t#{curVert}"
puts "nextVert \t#{nextVert}"
a = Vector.delta(prevVert,curVert)
  puts "a: \t\t#{a}"
b0 = Vector.delta(nextVert,curVert)
  puts "b0: \t\t#{b0}"
l = Vector.cross(a,b0)
  puts "l: \t\t#{l}"
l = Vector.normalize(l)
  puts "l normalized: \t#{l}"
a = Vector.normalize(a)
  puts "a normalized: \t#{a}"
b1 = Vector.cross(l,a)
  puts "b1: \t\t#{b1}"

# With b2 uncommented, even though it is not used in any function,
# it changes the output of mInverse by a factor of 2
# - Comment out the two lines below and rerun to see the change in
mInverse
#--------------v------------
b2 = Vector.normalize(b1)
  puts "b2: \t\t#{b2}"
#--------------^------------

m = Matrix[l.elements,b1.elements,a.elements]
mInverse = m.inverse
  puts "matrix:   \t#{m}"
  puts "mInverse: \t#{mInverse}\n\n"

[/code]
Todd S. (Guest)
on 2006-01-26 23:17
I should have included the results...

with the script run as is here is the output:

-----------
prevVert         Vector[-0.007794, -0.283622, 0.532219]
curVert           Vector[0.015525, -0.284744, 0.532219]
nextVert         Vector[0.017021, -0.256189, 0.52561]
a:                    Vector[-0.023319, 0.00112200000000001, 0.0]
b0:                  Vector[0.001496, 0.028555, -0.00660899999999998]
l:                     Vector[-7.41529800000005e-06,
-0.000154115270999999, -0.000667552557]
l normalized:   Vector[-0.0108228549457685, -0.224935966560048,
-0.97431343866259]
a normalized:  Vector[-0.99884446633489, 0.0480596719939859, 0.0]
b1:                  Vector[0.0468251842814566, 0.973187586683846,
-0.225196188336926]
b2:                  Vector[0.0468251842814566, 0.973187586683846,
-0.225196188336926]
matrix:            Matrix[[-0.0108228549457685, -0.224935966560048,
-0.97431343866259], [0.0468251842814566, 0.973187586683846,
-0.225196188336926], [-0.99884446633489, 0.0480596719939859, 0.0]]
mInverse:        Matrix[[-128.0, -32.0, -0.99884446633489], [0.0, 2.0,
0.0480596719939859], [-0.97431343866259, -0.225196188336926,
-1.20157827529705e-18]]


With the variable "b2" commented out this is the ouput.  Note the change
in mInverse...

-----------
prevVert          Vector[-0.007794, -0.283622, 0.532219]
curVert            Vector[0.015525, -0.284744, 0.532219]
nextVert          Vector[0.017021, -0.256189, 0.52561]
a:                    Vector[-0.023319, 0.00112200000000001, 0.0]
b0:                  Vector[0.001496, 0.028555, -0.00660899999999998]
l:                     Vector[-7.41529800000005e-06,
-0.000154115270999999, -0.000667552557]
l normalized:   Vector[-0.0108228549457685, -0.224935966560048,
-0.97431343866259]
a normalized:  Vector[-0.99884446633489, 0.0480596719939859, 0.0]
b1:                  Vector[0.0468251842814566, 0.973187586683846,
-0.225196188336926]
matrix:            Matrix[[-0.0108228549457685, -0.224935966560048,
-0.97431343866259], [0.0468251842814566, 0.973187586683846,
-0.225196188336926], [-0.99884446633489, 0.0480596719939859, 0.0]]
mInverse:        Matrix[[-64.0, -16.0, -0.99884446633489], [0.0, 1.0,
0.0480596719939859], [-0.97431343866259, -0.225196188336926,
-2.40315655059409e-18]]


incidentally, I believe both of these results are incorrect.
Andrew D. (Guest)
on 2006-01-26 23:38
(Received via mailing list)
For starters, Vector#normalize(v) both changes v and returns it--you
should
make a copy.  Certainly doesn't explain the behavior, at least not in my
mind, but you should try it.

I'll give a shot at a more detailed analysis, but at the moment I can't
get
the code to compile.  It would help if you could post a .rb file.
Matthew M. (Guest)
on 2006-01-26 23:41
(Received via mailing list)
I think (offhand, didn't actually run your code) your problem is not
math but pass-by-reference vs. pass-by-value issues.  Look at your
Vector.normalize function:

>         v.elements[i] /= divisor
>         i += 1
>       end
>     end
>     v
>   end

v is not a copy of the parameter passed in but a reference to it.  And
you are changing the elements of v directly.

> l = Vector.normalize(l)
> a = Vector.normalize(a)

In these two cases here, you don't notice the destructive behaviour,
since you assign the result to the parameter.  But here:

> b2 = Vector.normalize(b1)

You are assigning the result to a different variable, but you've
actually changed b1 as well because it's not pass-by-value.


I would write a Vector normalize like such:

class Vector
   def normalize
      div = inner_product(self)
      if div.zero?
         self
      else
         self * (1 / Math.sqrt(div))
      end
   end

   def Vector.normalize(v)
     v.normalize
   end
end
Todd S. (Guest)
on 2006-01-27 00:01
Brilliant!  Thankyou Very much.  I completely forgot about the variable
refrence operations with ruby.  I'm still very new with my ruby code.
Not quite through the whole book yet.
This topic is locked and can not be replied to.