MSA-A Complete Vector Class-Ruby

__NOTITLE__

Table of Contents = A Complete Vector Class (Ruby Version) =

Vector Multiplication
Carol: Which means it is time to move on to multiplication. But here we have another problem: there is multiplication and then there is multiplication.

Dan: You mean?

Carol: We can multiply a vector with a scalar number; for a vector

v = [1, 2, 3]

we would like to see multiplication by two giving us:

2 * v = [2, 4, 6]

But in addition (no pun intended) we want to form an inner product of two vectors. In particular, we would like to get the square of the length of a vector by forming the inner product with itself:

v * v = 2*2 + 4*4 + 6*6 = 56

Or more generally, for

w = [10, 10, 10]

we want to be able to take the inner product of  v and  w to give us:

v * w = 2*10 + 4*10 + 6*10 = 120

We could of course define different method names for these two operations, like multiply_scalar and inner_product, but something tells me that we will be happier using * for both.

Dan: Certainly I will be happier that way!

Carol: Well, how about this, in vector_try.rb:

def *(a) if a.class == Vector             # inner product product = 0 self.each_index{|k| product += self[k]*a[k]} else product = Vector.new           # scalar product self.each_index{|k| product[k] = self[k]*a} end product end

Time for a workout:

|gravity> irb require "vector_try.rb" true v1 = Vector[1, 2, 3] [1, 2, 3] v2 = Vector[5, 6, 7] [5, 6, 7] v1 * 3 [3, 6, 9] v1 * v1 14 v1 * v2 38 v1 * v2 * 3 114 v1 * 3 * v2 114 quit

An Unnatural Asymmetry
Dan: So far so good, but why do you put the number 3 only at the end and in the middle, why not in front? That would seem more natural! Let me try:

|gravity> irb require "vector_try.rb" true v1 = Vector[1, 2, 3] [1, 2, 3] v1 * 3 [3, 6, 9] 3 * v1 TypeError: Vector can't be coerced into Fixnum from (irb):4:in `*' from (irb):4 from :0 quit

Hmmm, I guess not.

Carol: Yes, it would be more natural, but it doesn't work. Do you see why? Remember, when we write v2 * 3 we invoke the *</tt> method of the vector object v2</tt>. I understand that you would like to write 3 * v2</tt>, but in that case you have a problem: you would be trying to invoke the *</tt> method of the number object 3. . ..

Erica: That's all and well, as a formal explanation, but if you can only write [1, 2, 3] * 3</tt> and never 3 * [1, 2, 3]</tt>, I'm not very happy with it. The whole point was to make our life easier, and to make the software notation more natural, more close to what you would write with pen and paper. ..

Dan: I agree. Half a solution is often worse than no solution at all. Perhaps we should just return to our earlier component notation.

Carol: I can't argue with that. But I'm not yet willing to give up. I wonder whether there is not some sort of way out. Let me see whether I can find something.

 Vector</tt> Division
Erica: For now at least, let's finish the job we started. ..

Carol:. . . which means that we have to add a division method as well. In the case of division, we only have to deal with scalar division.

Erica: Ah, yes, of course, you have an inner product, but not an inner quotient.

Carol: Well, not completely `of course', there is something called `geometric algebra'. If you're curious, you can search for it on the internet.

Dan: I'm not curious, let's move on.

Carol: Okay, so I will punish attempts to divide two vectors by letting an error message appear. In Ruby you can use the command  raise</tt> to halt execution and display an error message:

def /(a) if a.class == Vector raise else quotient = Vector.new          # scalar quotient self.each_index{|k| quotient[k] = self[k]/a} end quotient end

A quick check:

|gravity> irb require "vector_try.rb" true v1 = Vector[1, 2, 3] [1, 2, 3] v2 = Vector[5, 6, 7] [5, 6, 7] v1 / 3.0 [0.333333333333333, 0.666666666666667, 1.0] v2 / 0.5 [10.0, 12.0, 14.0] v1 / v2 RuntimeError: from ./vector_try.rb:41:in `/' from (irb):6 from :0 quit

You see, we got a run time error, because the  raise</tt> statement had the effect of raising an error, as it is called in Ruby.

The Score: Six to One
Erica: Let me see whether I can sum up what we've learned.We've successfully implemented seven legal operations, unary/binary addition/multiplication, scalar/vector multiplication and scalar division.

Of these seven, six are okay. It is only with scalar multiplication that we encounter a problem, namely a lack of symmetry.

Dan: Can you define "okay" for the other six?

Erica: I will be explicit. I will use a notation where v1</tt> and v2</tt> are vectors, and  s</tt> is a scalar number, which could be either an integer or a floating point number. I will call something "well defined" if it gives a specific answer, and not an error message.

Here is the whole list of the six operations that I am now considering "okay":

1) unary plus: +v1</tt> is okay, because it is well defined.

2) unary minus: -v1</tt> is okay, because it is well defined.

3) binary plus: v1 + v2</tt> is okay, because both v1 + v2</tt> and <tt>v2 + v1</tt> are well defined, and both give the exact same answer, as they should.

4) binary minus: <tt>v1 - v2</tt> is okay, because both <tt>v1 - v2</tt> and <tt>v2 - v1</tt> are well defined, and both give the exact opposite answer, as they should.

5) vector times: <tt>v1 * v2</tt> is okay, because both <tt>v1 * v2</tt> and <tt>v2 * v1</tt> are well defined, and both give the exact same answer, as they should.

6) scalar slash: <tt>v1 / s</tt> is okay, because it is well defined. The alternative, <tt>s / v1</tt> is not defined, and will give an error message.  However, that's perfectly okay too, because we have decided that we don't allow division by vectors.

So far, the score is six to zero, and we seem to be winning. The problem is that we are losing in the case of number 7:

7) scalar times: <tt>v1 * s</tt> is okay, because it is well defined. The alternative, <tt>s * v1</tt> should give the same answer too,  but unfortunately, in our implementation it is not defined, and in fact, as we have seen, it gives an error message.

Dan: Thanks, Erica, that's a very clear summary. So all we have to do, to save the day, is to repair <tt>s * v1</tt>, so that it gives a well defined result, and in fact the same result as <tt>v1 * s</tt>.

A Solution
Carol: Ah, of course! How stupid, we should have thought about it right away!

Dan: About what?

Carol: Class augmentation! We can just augment the number classes, telling them how to multiply with vectors! Just as we have already augmented the array class, telling it how to convert an array into a vector.

Dan: I don't see that. What exactly are you trying to repair?

Carol: Let's go back to square one. We wanted to make our scalar-vector multiplication symmetric. The problem was, when you have a vector <tt> v</tt> and you want to write

<tt>3 * v</tt>

you are effectively asking the number 3 to provide a method that knows how to handle multiplication of 3 with a vector. But the poor number 3 has never heard of vectors! No wonder we got an error message. Let me show again explicitly what the error message says:

|gravity> irb require "vector_try.rb" true v = Vector[1, 2, 3] [1, 2, 3] v * 3 [3, 6, 9] 3 * v TypeError: Vector can't be coerced into Fixnum from (irb):4:in `*' from (irb):4 from :0 quit

You see: it tells us that the number 3 expects to make a multiplication with something like another number. It tries to convert a vector into such an other number, but doesn't succeed.

Erica: So, what we really would like to do is to augment the rules for the class <tt> Fixnum</tt>, which is the class of integers, to explain how to multiply with an object of class <tt> Vector</tt>. We can then do the same for the class <tt> Float</tt>, the class of floating point numbers.

Augmenting the <tt> Fixnum</tt> Class
Carol: Indeed. Well, there is no stopping us now to add to the behavior of the <tt> Fixnum</tt> class! Here we go, in <tt>vector.rb</tt:

class Fixnum alias :original_mult :* def *(a) if a.class == Vector a*self else original_mult(a) end end end

Dan: Whoa! That's quite a bit more complicated than I had expected. You must have been reading Ruby manuals lately!

Carol: I admit, I did. Here is what happens. If we start with the number 3, and take a vector <tt> v</tt>, and write

3 * v

then the <tt>*</tt> method of the fixed point number 3 first checks whether <tt> v</tt> is a <tt> Vector</tt>. In our case, it is, so then it returns the result

v * self

which in the case of 3 is simply

v * 3

and that is something we have already defined, in the <tt> Vector</tt> class.

If, on the other hand we write anything else, such as

3 * 8.5

then the <tt>*</tt> method of the fixed point number 3 finds that <tt>8.5</tt> is not a <tt> Vector</tt>, so it applies the original definition of multiplication, to the number <tt>8.5</tt>, as it should.

Dan: So the <tt> alias</tt> notion means that whatever the original <tt>*</tt> did is now assigned to <tt>original_mult</tt> instead?

Carol: Exactly. Writing <tt>alias :x :y</tt> means that <tt>x</tt> becomes an alias for <tt>y</tt>. Or so the theory goes. Let's see what the practice tells us:

|gravity> irb require "vector.rb" true v = [1, 2, 3].to_v [1, 2, 3] v * 3 [3, 6, 9] (v * 3).class Vector 3 * v [3, 6, 9] (3 * v).class Vector quit

Augmenting the <tt> Float</tt> Class
Dan: Not bad! Great! <tt>3 * v</tt> now produces the exact same thing as <tt>v * 3</tt>. Congratulations! Another hope fulfilled. And I guess you should do the same thing for floating point numbers, right?

Carol: Right. All very similar:

class Float alias :original_mult :* def *(a) if a.class == Vector a*self else original_mult(a) end end end

This time my hope for success will be quite justified:

|gravity> irb require "vector.rb" true v = [1, 2, 3].to_v [1, 2, 3] v * 3.14 [3.14, 6.28, 9.42] (v * 3.14).class Vector 3.14 * v [3.14, 6.28, 9.42] (3.14 * v).class Vector quit

And indeed, it all comes out the way it should. So I declare victory: I'm very pleased with our vector class.

<tt> Vector</tt> Class Listing
Erica: Let's get the whole listing on the screen. Here it is. In fact, it is shorter than I thought, for all the things it does:

class Vector &lt; Array def +(a) sum = Vector.new self.each_index{|k| sum[k] = self[k]+a[k]} sum end def -(a) diff = Vector.new self.each_index{|k| diff[k] = self[k]-a[k]} diff end def +@ self end def -@ self.map{|x| -x}.to_v end def *(a) if a.class == Vector             # inner product product = 0 self.each_index{|k| product += self[k]*a[k]} else product = Vector.new           # scalar product self.each_index{|k| product[k] = self[k]*a} end product end def /(a) if a.class == Vector raise else quotient = Vector.new          # scalar quotient self.each_index{|k| quotient[k] = self[k]/a} end quotient end end class Array def to_v Vector[*self] end end class Fixnum alias :original_mult :* def *(a) if a.class == Vector a*self else original_mult(a) end end end class Float alias :original_mult :* def *(a) if a.class == Vector a*self else original_mult(a) end end end

Forward Euler in Vector Form
Dan: The whole point of this long exercise was to make our codes more readable. Here is one of the array versions we made of our first code, the forward Euler version, in <tt>euler_array_each_def.rb</tt>:

include Math def print_pos_vel(r,v) r.each{|x| print(x, " ")} v.each{|x| print(x, " ")} print "\n" end r = [1, 0, 0] v = [0, 0.5, 0] dt = 0.01 print_pos_vel(r,v) 1000.times{ r2 = 0 r.each{|x| r2 += x*x} r3 = r2 * sqrt(r2) a = r.map{|x| -x/r3} r.each_index{|k| r[k] += v[k]*dt} v.each_index{|k| v[k] += a[k]*dt} print_pos_vel(r,v) }

What would that look like in vector form?

Carol: To start with, we'll have to <tt> require</tt> the file <tt>vector.rb</tt>. We also have to convert the arrays into vectors, using <tt>.to_v</tt>. Then, in the loop, we can use our vector versions of addition, unary minus,multiplication and division. Let me write it all in file <tt>euler_vector.rb</tt>:

require "vector.rb" include Math def print_pos_vel(r,v) r.each{|x| print(x, " ")} v.each{|x| print(x, " ")} print "\n" end r = [1, 0, 0].to_v v = [0, 0.5, 0].to_v dt = 0.01 print_pos_vel(r,v) 1000.times{ r2 = r*r r3 = r2 * sqrt(r2) a = -r/r3 r += v*dt v += a*dt print_pos_vel(r,v) }

Dan: Wait a minute, we haven't define <tt>+=</tt> yet!

Carol: Ah, you see, that's a nice feature of operator overloading in Ruby: once you redefine <tt>+</tt> the same redefinition applies to derivative expressions such as <tt>+=</tt>.

Dan: That's nice, if it really works. Let's compare the run with the old results:

|gravity> ruby euler_array_each_def.rb | tail -1 7.6937453936572 -6.27772005661599  0.0  0.812206830641815  -0.574200201239989  0.0

|gravity> ruby euler_vector.rb | tail -1 7.6937453936572 -6.27772005661599  0.0  0.812206830641815  -0.574200201239989  0.0

Wonderful. And I must say, the code does look a lot cleaner now.

Erica: Definitely. This is a lot more pretty. What a difference! None of all those ugly <tt>[k]</tt> occurrences anymore. I think it was worth all the work we put in, defining a <tt>Vector</tt> class.