Previous | ToC | Up | Next |

** 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 = 56Or more generally, for

w = [10, 10, 10]we want to be able to take the inner product of

v * w = 2*10 + 4*10 + 6*10 = 120We could of course define different method names for these two operations, like

** 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

** 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 quitHmmm, 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 `*`
method of the vector object `v2`. I understand that you would
like to write `3 * v2`, but in that case you have a problem:
you would be trying to invoke the `*` 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` and never `3 * [1, 2, 3]`,
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.

** 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` 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 quitYou see, we got a run time error, because the

** 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`
and `v2` are vectors, and ` s` 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` is okay, because it is well defined.

2) unary minus: `-v1` is okay, because it is well defined.

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

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

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

6) scalar slash: `v1 / s` is okay, because it is well defined.
The alternative, `s / v1` 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: `v1 * s` is okay, because it is well defined.
The alternative, `s * v1` * 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 `s * v1`, so that it gives a well
defined result, and in fact the same result as `v1 * s`.

** 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 ` v` and you want to write

`3 * v`

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 quitYou 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 ` Fixnum`, which is the class of integers, to explain how to
multiply with an object of class ` Vector`. We can then do the same
for the class ` Float`, the class of floating point numbers.

** Carol**: Indeed. Well, there is no stopping us now to add to the
behavior of the ` Fixnum` class! Here we go, in vector.rb:

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 ` v`, and write

3 * vthen the

v * selfwhich in the case of 3 is simply

v * 3and that is something we have already defined, in the

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

3 * 8.5then the

** Dan**: So the ` alias` notion means that whatever the original `*`
did is now assigned to `original_mult` instead?

** Carol**: Exactly. Writing `alias :x :y` means that `x`
becomes an alias for `y`. 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

** Dan**: Not bad! Great! `3 * v` now produces the
exact same thing as `v * 3`. 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 quitAnd indeed, it all comes out the way it should. So I declare victory: I'm very pleased with our vector class.

** 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 < 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

** 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 euler_array_each_def.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] 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 ` require` the file vector.rb.
We also have to convert the arrays into vectors, using `.to_v`.
Then, in the loop, we can use our vector versions of addition, unary minus,
multiplication and division. Let me write it all in file
euler_vector.rb:

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 `+=` yet!

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

** 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.0Wonderful. 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 `[k]` occurrences anymore.
I think it was worth all the work we put in,
defining a `Vector` class.

Previous | ToC | Up | Next |