Last Updated: September 29, 2021
·
15.07K
· kmanzana

How to make homework more fun, MATLAB to Ruby

I wanted to liven up my ElectroMagnetism homework this past week which required us to do some MATLAB. Unfortunately, I don't have MATLAB because it's not OS and I didn't want to go to the school to use a computer, so I did some monkey patching to make my Ruby code look like MATLAB. Now I can just copy in the MATLAB code, make a few changes and run it. Only a few changes necessary at this point to convert the MATLAB to Ruby:

[1 2 3] -> [1, 2, 3]  # arrays
x^2     -> x**2       # exponents
%       -> #          # comments

Update: ^ has lesser precedence than ** which is why I stuck with **. Comment if you know how to change operator precedence. I couldn't find much.

Here is the file I require:

#matlab_helper.rb
$VERBOSE = nil # surpress constant redefinition warnings
include Math

def norm(vector)
  sqrt vector.map{ |x| x**2 }.inject(0, &:+)
end

class Array
  def +(op)
    self.zip(op).map { |a, b| a + b }
  end

  def -(op)
    self.zip(op).map { |a, b| a - b }
  end

  def /(op)
    self.map { |a| a / op }
  end
end

class Float
  alias_method :mult, :*

  def *(op)
    if op.is_a? Array
      op.map { |a| self * a }
    else
      mult op
    end
  end
end

class Fixnum
  alias_method :mult, :*

  def *(op)
    if op.is_a? Array
      op.map { |a| self * a }
    else
      mult op
    end
  end
end

def pi
  PI
end

And my converted MATLAB homework:

#hw.rb
require './matlab_helper'

# clc #clear the command line
# clear #remove all previous variables
Q1 = 8e-9 #charges on Q1
Q2 = 8e-9 #charges on Q2
pL = 2e-9 #charge density of the line
Epsilon_o = 8.8419e-12 #Permitivity of free space

P = [2, 3, 4] #coordinates of observation point
A = [0, 0, 1] #coordinates of Q1
B = [0, 0, -1] #coordinates of Q2
C = [2, 0, 0] #coordinates of the center of the line charge

Number_of_L_Steps = 100000 #the steps of L

##the following routine calculates the electric fields at the
##observation point generated by the point charges
R1 = P - A #the vector pointing from Q1 to the observation point
R2 = P - B #the vector pointing from Q2 to the observation point

R1Mag = norm(R1) #the magnitude of R1
R2Mag = norm(R2) #the magnitude of R1
E1 = Q1 / (4 * pi * Epsilon_o * R1Mag**3) * R1 #the electric field generated by Q1
E2 = Q2 / (4 * pi * Epsilon_o * R2Mag**3) * R2 #the electric field generated by Q2

##the following routine calculates the electric field at the
##observation point generated by the line charge
d = norm(P - C) #the distance from the observation point to the center of the line
length = 100 * d #the length of the line
dL_V = length / Number_of_L_Steps * [1, 0, 0] #vector of a segment
dL = norm(dL_V) #length of a segment

EL = [0, 0, 0] #initialize the electric field generated by EL
C_segment = C - (Number_of_L_Steps / 2 * dL_V - dL_V / 2) #the center of the first segment

for i in (1..Number_of_L_Steps)
  R = P - C_segment #the vector seen from the center of the first segment to the observation point
  RMag = norm(R) #the magnitude of the vector R
  EL = EL + dL * pL / (4 * pi * Epsilon_o * RMag**3) * R #get contibution from each segment
  C_segment = C_segment + dL_V #the center of the i-th segment
end

E = E1 + E2 + EL # the electric field at P

puts "E = #{E}"

# $ ruby hw.rb                                                 
# E = [2.010238790127288, 7.334514610377015, 9.388970095358529]

It runs in about 2 seconds with 100,000 steps, but it's Ruby!

Note: I only included the operators necessary to complete my homework, so there are quite a few combinations left out.

7 Responses
Add your response

Ruby has the ^ operator too, so you can cross that off the list of necessary changes.

over 1 year ago ·

Yeah, I wanted to add that as an alias that for **, however ^ has a lesser operator precedence than **. So if you want to use ^, you have to wrap it in parens just about everywhere it is used. I figured that it would just be better to use ** so that you wouldn't have that somewhat confusing problem.

That is, unless anyone knows how to change the precedence of an operator.

over 1 year ago ·

You can swap their definitions and their precedences might swap too :O

I just checked, they seem to. Try this:

>> 5 ** 3 ^ 5
#=> 120

class Fixnum
  alias_method :"__**", :"**"
  alias_method :"**", :"^"
  alias_method :"^", :"__**"
end

>> 5 ** 3 ^ 5
#=> 7776
over 1 year ago ·

Check out Octave: it's an open-source Matlab clone.

over 1 year ago ·

They keep the same precedence when you do that. You just traded the underlying method.

>> 2 * 2 ** 3 # ** has higher precedence
=> 16
>> 2 * 2 ^ 3
=> 7
>> 2 * (2 ^ 3) # ^ has lower precedence
=> 2

class Fixnum
  alias_method :pow, :**
  alias_method :**, :^
  alias_method :^, :pow
end

>> 2 * 2 ** 3 # ** still has higher precedence
=> 2
>> (2 * 2) ** 3
=> 7
>> 2 * 2 ^ 3 # ^ still have lower precedence
=> 64
>> 2 * (2 ^ 3)
=> 16

Still don't know how (or if) you can change precedence of an operator

over 1 year ago ·

Thanks. I was just fooling around with this, but that will actually help me out when I get sick of writing MATLAB functions in Ruby or when performance is intolerable.

over 1 year ago ·

Ruby doesn't have to be slow for scientific computation. It's possible to generate C code on the fly and keep ruby as the DSL and utility language. If you are working with continuous systems (ODEs) or hybrid systems (ODEs plus discrete state transitions and dynamic dataflow networks), then you might be interested in RedShift. For example, here's a simulation of a simple thermostat controller: https://github.com/vjoel/redshift/blob/master/examples/thermostat.rb. (I am the author of RedShift.)

over 1 year ago ·