[ANN] StringMatrix

G

Gavin Kistner

(This isn't a library I plan on maintaining, but ANN felt like the
right prefix for offering up code for mass enjoyment.)

At work yesterday I needed to do some algebra on some matrix math to
reverse-derive a value. (To be specific, I needed to figure out how
to draw the three euler rotation values out of a ZXY rotation
matrix.) After spending 2 minutes writing out equations and fearing I
was making a mistake, I turned to Excel and some stupid text
manipulation to produce my formulae. After 10 minutes of messing
around, I got the answer wrong anyhow.

I decided to write a little Ruby library to do the task for me. I
wrote a matrix class which knows how to do matrix math on strings,
producing formulae from them. After the initial pass produced a lot
of values like "0*sinX" and "0 + 0 + 1*(sinX)" I made it a bit
smarter, so that 0s and 1s properly simplify the equations during
calculation.

It doesn't produce perfectly-reduced equations by any means, but it
did the trick. In the end, I got my result :)

Sample output follows, followed by the code itself. Enjoy!

M1:
---------------------------------------------------------------------
0 | 1 | 2
3 | 4 | 5

M2:
---------------------------------------------------------------------
x | y | z
3q | 4r | 5s

M1 scaled by 2:
---------------------------------------------------------------------
0 | 2 | 4
6 | 8 | 10

M2 scaled by 2:
---------------------------------------------------------------------
x * 2 | y * 2 | z * 2
3q * 2 | 4r * 2 | 5s * 2

M1 + M2:
---------------------------------------------------------------------
x | 1 + y | 2 + z
3 + 3q | 4 + 4r | 5 + 5s

X:
------------------------------------------------------------------------
-----
1 | 0 | 0
0 | cosX | sinX
0 | -sinX | cosX

Y:
------------------------------------------------------------------------
-----
cosY | 0 | -sinY
0 | 1 | 0
sinY | 0 | cosY

Z:
------------------------------------------------------------------------
-----
cosZ | sinZ | 0
-sinZ | cosZ | 0
0 | 0 | 1

X - Y:
------------------------------------------------------------------------
-----
1 - cosY | 0 | sinY
0 | cosX - 1 | sinX
-sinY | -sinX | cosX - cosY

X * Y * Z:
------------------------------------------------------------------------
-----
cosY * cosZ | cosY *
sinZ | -sinY
((sinX * sinY) * cosZ) + (cosX * -sinZ) | ((sinX * sinY) * sinZ) +
(cosX * cosZ) | sinX * cosY
((cosX * sinY) * cosZ) + (-sinX * -sinZ) | ((cosX * sinY) * sinZ) + (-
sinX * cosZ) | cosX * cosY

Z * X * Y:
------------------------------------------------------------------------
-----
(cosZ * cosY) + ((sinZ * sinX) * sinY) | sinZ * cosX | (cosZ * -
sinY) + ((sinZ * sinX) * cosY)
(-sinZ * cosY) + ((cosZ * sinX) * sinY) | cosZ * cosX | (-sinZ * -
sinY) + ((cosZ * sinX) * cosY)
cosX * sinY | -sinX
| cosX * cosY




stringmatrix.rb
------------------------------------------------------------------------
-----
# A two-dimensional matrix of arbitrary size, with common matrix
# math methods. When entries in the matrix are strings, the math
# methods produce strings representing the equation.
#
# Strings and numbers mix nicely so that multiplying by 0 wipes
# out the string properly, and multiplying by 1 or adding 0
# leaves the original unchanged.
#
# For example:
# x = StringMatrix.parse <<END
# 1 0 0
# 0 cosX sinX
# 0 -sinX cosX
# END
#
# y = StringMatrix.parse <<END
# cosY | 0 | -sinY
# 0 | 1 | 0
# sinY | 0 | cosY
# END
#
# puts x, ' ', y, ' ', x - y, ' ', x * y
#
# #=> 1 | 0 | 0
# #=> 0 | cosX | sinX
# #=> 0 | -sinX | cosX
# #=>
# #=> cosY | 0 | -sinY
# #=> 0 | 1 | 0
# #=> sinY | 0 | cosY
# #=>
# #=> 1 - cosY | 0 | sinY
# #=> 0 | cosX - 1 | sinX
# #=> -sinY | -sinX | cosX - cosY
# #=>
# #=> cosY | 0 | -sinY
# #=> sinX * sinY | cosX | sinX * cosY
# #=> cosX * sinY | -sinX | cosX * cosY
class StringMatrix
# Add two values intelligently
def self.add( v1, v2 )
if v1==0
v2
elsif v2==0
v1
elsif Numeric===v1 && Numeric===v2
v1+v2
else
self.operator_join( v1, v2, '+' )
end
end

# Subtract two values intelligently
def self.subtract( v1, v2 )
if v2==0
v1
elsif v1==0
if v2 =~ /^-\((.+)\)$/ || v2 =~ /^-(.+)$/
$1
elsif v2 =~ /\s/
"-(#{v2})"
else
"-#{v2}"
end
elsif Numeric===v1 && Numeric===v2
v1-v2
else
self.operator_join( v1, v2, '-' )
end
end

# Multiply two values intelligently
def self.multiply( v1, v2 )
if v1==1
v2
elsif v2==1
v1
elsif v1==0 || v2==0
0
elsif Numeric===v1 && Numeric===v2
v1*v2
else
self.operator_join( v1, v2, '*' )
end
end

# Join two values semi-intelligently
def self.operator_join( v1, v2, op_str )
out = ( Numeric === v1 || v1 =~ /^\S+$/ ) ? "#{v1}" : "(#{v1})"
out << " #{op_str} "
out << ( ( Numeric === v2 || v2 =~ /^\S+$/ ) ? "#{v2}" : "(#
{v2})" )
end

attr_reader :width, :height

# Creates a new matrix of the specified _width_ and _height_,
optionally
# specifying a _default_value_ to fill each cell.
def initialize( width, height, default_value='' )
@width = width
@height = height
@values = Array.new( width ){ Array.new(height)
{ default_value } }
end

# Reads the value from column _x_, row _y_.
#
# StringMatrices are 1-based, not zero-based.
# (The first item in the matrix is 1,1 and the last is
_width_,_height_)
def []( x, y )
if !y
@values[ x-1 ].dup
elsif !x
a = []
1.upto(@width){ |x|
a << @values[ x-1 ][ y-1 ]
}
a
else
@values[ x-1 ][ y-1 ]
end
end

# Sets the value in column _x_, row _y_ to _val_.
#
# StringMatrices are 1-based, not zero-based.
# (The first item in the matrix is 1,1 and the last is
_width_,_height_)
def []=( x, y, val )
@values[ x-1 ][ y-1 ] = val
end

# Adds the supplied _right_matrix_ from the current matrix
# and returns the result. (The original matrix is not modified.)
def +( right_matrix )
raise "Size mismatch" if width != right_matrix.width ||
height != right_matrix.height
out = self.class.new( width, height )
1.upto( @height ){ |y|
1.upto( @width ){ |x|
out[ x, y ] = self.class.add( self[ x, y ],
right_matrix[ x, y ] )
}
}
out
end

# Subtracts the supplied _right_matrix_ from the current matrix
# and returns the result. (The original matrix is not modified.)
def -( right_matrix )
raise "Size mismatch" if width != right_matrix.width ||
height != right_matrix.height
out = self.class.new( width, height )
1.upto( @height ){ |y|
1.upto( @width ){ |x|
out[ x, y ] = self.class.subtract( self[ x, y ],
right_matrix[ x, y ] )
}
}
out
end

# Performs matrix multiplication between the two matrices.
# (The original matrix is not modified.)
def *( right_matrix )
raise "Size mismatch" if width != right_matrix.height ||
height != right_matrix.width
out = self.class.new( width, height )
1.upto( @height ){ |y|
1.upto( @width ){ |x|
row = self[ nil, y ]
col = right_matrix[ x, nil ]
val = row.zip( col ).inject( 0 ){ |sum, pair|
self.class.add( sum, self.class.multiply( *pair ) )
}
out[ x, y ] = val
}
}
out
end

# Scales the matrix, multiplying each value by _scale_value_ and
returning
# the resulting matrix.
# (The original matrix is not modified.)
def scale( scale_value )
out = self.class.new( width, height )
1.upto( @height ){ |y|
1.upto( @width ){ |x|
out[ x, y ] = self.class.multiply( self[ x, y ],
scale_value )
}
}
out
end

# Parses a multi-line string for use as a StringMatrix
#
# Lines in the string may be delimited by tabs, vertical bars,
or commas.
# The most common item is used as the separator; if none of the
above are
# present in the string, spaces are used.
def self.parse( raw_str )
best_count = 1
split_char = [ "\t", '|', ',' ].inject( ' ' ){ |split_char,
char|
count = raw_str.scan( char ).length
if count > best_count
best_count = count
char
else
split_char
end
}

values = []
y = 0
raw_str.each_line{ |line|
line.split( split_char ).each_with_index{ |val, x|
val.strip!
( values[ x ] ||= [] )[ y ] = case val
when /^\d+$/ then val.to_i
when /^\d+\.\d+$/ then val.to_f
else val
end
}
y += 1
}

width = values.length
height = y
out = self.new( width, height, '' )
1.upto( height ){ |y|
1.upto( width ){ |x|
out[ x, y ] = values[ x-1 ][ y-1 ]
}
}
out
end

def to_s( no_padding=false )
out = ''
column_widths = @values.collect{ |col|
no_padding ? 0 : col.inject(0){ |max_len,val|
len = val.to_s.length
max_len > len ? max_len : len
}
}
1.upto(@height){ |y|
1.upto(@width){ |x|
out << self[ x, y ].to_s.centered_in( column_widths
[ x-1 ] )
out << " | " unless x == @width
}
out << "\n" unless y == @height
}
out
end

end

class String
# Returns a copy of the string, centered (by padding both sides
with spaces)
# within the specified width.
#
# If width is smaller than the length of the string, the string
itself is returned.
def centered_in( width )
out = self.dup
remains = width - out.length
if remains > 0
back = remains / 2
front = remains - back
out = " "*front + out + " "*back
end
out
end
end

if $0 == __FILE__
m1 = StringMatrix.parse( "0,1,2\n3,4,5" )
m2 = StringMatrix.parse( "x,y,z\n3q,4r,5s" )
puts <<-ENDOUTPUT
M1:
---------------------------------------------------------------------
#{ m1 }

M2:
---------------------------------------------------------------------
#{ m2 }

M1 scaled by 2:
---------------------------------------------------------------------
#{ m1.scale( 2 ) }

M2 scaled by 2:
---------------------------------------------------------------------
#{ m2.scale( 2 ) }

M1 + M2:
---------------------------------------------------------------------
#{ m1 + m2 }

ENDOUTPUT

x = StringMatrix.parse <<-ENDMATRIX
1 0 0
0 cosX sinX
0 -sinX cosX
ENDMATRIX

y = StringMatrix.parse <<-ENDMATRIX
cosY | 0 | -sinY
0 | 1 | 0
sinY | 0 | cosY
ENDMATRIX


z = StringMatrix.parse <<-ENDMATRIX
cosZ sinZ 0
-sinZ cosZ 0
0 0 1
ENDMATRIX

puts <<-ENDOUT
X:
------------------------------------------------------------------------
-----
#{x}

Y:
------------------------------------------------------------------------
-----
#{y}

Z:
------------------------------------------------------------------------
 
J

Jim Freeze

* Gavin Kistner said:
(This isn't a library I plan on maintaining, but ANN felt like the
right prefix for offering up code for mass enjoyment.)

At work yesterday I needed to do some algebra on some matrix math to
reverse-derive a value. (To be specific, I needed to figure out how
to draw the three euler rotation values out of a ZXY rotation
matrix.) After spending 2 minutes writing out equations and fearing I
was making a mistake, I turned to Excel and some stupid text
manipulation to produce my formulae. After 10 minutes of messing
around, I got the answer wrong anyhow.

I decided to write a little Ruby library to do the task for me. I
wrote a matrix class which knows how to do matrix math on strings,
producing formulae from them. After the initial pass produced a lot
of values like "0*sinX" and "0 + 0 + 1*(sinX)" I made it a bit
smarter, so that 0s and 1s properly simplify the equations during
calculation.

It doesn't produce perfectly-reduced equations by any means, but it
did the trick. In the end, I got my result :)

Ahh, a symbolic math library. Gavin, you're a young Wolfram at heart. ;)

I remember seeing a symbolic library in the RAA long ago. Don't know if
it did matrices, but it did reduction. Possibly you could send the
result of your code through its reduction engine as a final step.

Nice work.
 
A

Ara.T.Howard

(This isn't a library I plan on maintaining, but ANN felt like the right
prefix for offering up code for mass enjoyment.)

At work yesterday I needed to do some algebra on some matrix math to
reverse-derive a value. (To be specific, I needed to figure out how to draw
the three euler rotation values out of a ZXY rotation matrix.) After spending
2 minutes writing out equations and fearing I was making a mistake, I turned
to Excel and some stupid text manipulation to produce my formulae. After 10
minutes of messing around, I got the answer wrong anyhow.

I decided to write a little Ruby library to do the task for me. I wrote a
matrix class which knows how to do matrix math on strings, producing formulae
from them. After the initial pass produced a lot of values like "0*sinX" and
"0 + 0 + 1*(sinX)" I made it a bit smarter, so that 0s and 1s properly
simplify the equations during calculation.

It doesn't produce perfectly-reduced equations by any means, but it did the
trick. In the end, I got my result :)


on a related note

http://blade.nagaokaut.ac.jp/~sinara/ruby/math
http://blade.nagaokaut.ac.jp/~sinara/ruby/math/algebra/

cheers.

-a
--
===============================================================================
| email :: ara [dot] t [dot] howard [at] noaa [dot] gov
| phone :: 303.497.6469
| My religion is very simple. My religion is kindness.
| --Tenzin Gyatso
===============================================================================
 
G

Guillaume Marcais

Do you (or anyone else) use this library? I _barely_ understand how
to set up a single polynomial function using it (what is a 'ring'
anyhow?),

http://en.wikipedia.org/wiki/Ring_(mathematics)

As an example, the set of integers Z (positive and negative) is a Ring.
(but not the positive integers only). You have a the common addition and
multiplication, but not division. The set of polynomials on a field is
also a Ring.

That said, I can't help you with the math library as I've never used
it :(.

Guillaume.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,769
Messages
2,569,580
Members
45,055
Latest member
SlimSparkKetoACVReview

Latest Threads

Top