Need a ruby math genius - potential ruby bug


#1

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.

#!/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"

#2

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.


#3

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.


#4

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


#5

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.