Skip to content

Instantly share code, notes, and snippets.

@hejmsdz
Last active July 1, 2017 11:19
Show Gist options
  • Save hejmsdz/274da4aebdd3711a2d91c172e59a077e to your computer and use it in GitHub Desktop.
Save hejmsdz/274da4aebdd3711a2d91c172e59a077e to your computer and use it in GitHub Desktop.
Matrix inversion helper
require "matrix"
require "readline"
class Rational
# convert to integer if is actually an integer
def try_to_i
to_i == self ? to_i : self
end
end
class Integer
# for consistency with Rational#try_to_i; does nothing
def try_to_i
self
end
end
class Matrix
# output the matrix in a user-readable way; return self for chaining
def output(line=-1)
row_vectors.each do |row|
row.to_a.each_with_index do |x, i|
print " | " if line == i
printf("%5s", x)
end
puts
end
self
end
# create an augmented matrix ie. append another matrix (identity by default) on the right
def aug(other=nil)
if other.nil?
other = Matrix.unit(row_count)
end
Matrix.build(row_count, column_count+other.column_count) do |i, j| # row, column
if j < column_count then element(i, j)
else other[i, j-column_count]
end
end
end
# swap rows of a matrix
def swap_rows(a, b)
row_a = row(a)
row_b = row(b)
Matrix.build(row_count, column_count) do |i, j| # row, column
if i == a then row_b[j]
elsif i == b then row_a[j]
else element(i, j)
end
end
end
# multiply a row by a constant
def multiply_row(a, times)
Matrix.build(row_count, column_count) do |i, j| # row, column
(element(i, j) * (i == a ? times : 1)).try_to_i
end
end
# add a row to another row, optionally multiplied by a constant
def add_row(row1, row2, times=1)
Matrix.build(row_count, column_count) do |i, j| # row, column
x = element(i, j)
if i == row1 then (x + element(row2, j) * times).try_to_i
else x
end
end
end
end
class MatrixInverter
def initialize(matrix)
raise "Matrix is not square" unless matrix.square?
raise "Matrix is singular" if matrix.singular?
@matrix = matrix
@size = matrix.row_count
@aug = matrix.aug
@inv = matrix.inv
@stack = []
end
# check if the inversion process has been completed
def inverted?
@aug.minor(0...@size, @size...2*@size) == @inv
end
# return result of a transformation, according to the command
def exec_command(command, *args)
case command
when "undo"
if @stack.length >= 1
puts "Undoing last operation"
@stack.pop
else
puts "Error: Nothing to undo!"
end
when "swap"
@aug.swap_rows(args[0]-1, args[1]-1)
when "mult"
@aug.multiply_row(args[0]-1, args[1])
when "addr"
@aug.add_row(args[0]-1, args[1]-1, args[2] || 1)
end
end
def print_help
puts "Perform elementary operations on the augmented matrix"
puts "to transform the left side to an identity matrix."
puts "The right side will then become the inverse of the one you entered."
puts
puts "Commands:"
puts "swap a b swap a-th and b-th row"
puts "mult a x multiply the a-th row by x"
puts "addr a b [x] add the b-th row [multiplied by x] to the a-th row"
puts "undo revert the last operation"
puts "exit quit the program"
end
def main_loop
while !inverted?
@aug.output(@size)
buf = Readline.readline("> ", true)
break unless buf
words = buf.split
command = words.shift.downcase
args = words.map(&:to_r).map(&:try_to_i)
aug_prime = exec_command(command, *args)
unless aug_prime.nil?
@stack << @aug unless command == "undo"
@aug = aug_prime
end
end
puts "Done."
@aug.output(@size)
end
end
puts "Enter size of the matrix:"
n = gets.to_i
puts "Input the matrix:"
matrix = Matrix[*Array.new(n) { gets.split.map(&:to_i) }]
mi = MatrixInverter.new(matrix)
mi.print_help
mi.main_loop
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment