Simulator Ruby Gem

28 Jan 2013

Intro

I’ve been working on this off and on for awhile. Simulator is a Ruby gem which provides functionality for creating discrete time models, and running those models. You can find the Homepage for the Simulator gem on Github.

Take the following two examples included with the gem.

Ball Drop

Let’s say we want to model a ball that is dropped. Assuming I’m not modeling any bounces, I just need to show the affect of acceleration due to gravity on the position of the ball in space. Position is a function of velocity, which is a function of acceleration. We can model this system using the gem thusly.

# We create a model that simulates a ball drop
model = Simulator::Model.new do
  name = "Ball drop model"
  # create a couple static variables to represent 
  # acceleration with default values
  var :ax, 0
  var :ay, - 9.8

  # create dynamic variables bound to some 
  # computation, with default values.

  # velocity is affected by acceleration
  eqtn(:vx, 20) { vx + ax }
  eqtn(:vy, 50) { vy + ay }

  # position is affected by velocity
  eqtn(:x, 10) { x + vx }
  eqtn(:y, 100) { y + vy }
end

There is a simple DSL provided that makes creating the models easier. Hopefully it’s easy enough to follow. We initialize two static variables, ax and ay, which are set to 0 and -9.8, respectively (acceleration due to gravity is equal to -9.8m/s\^2.)

Next, we create two “equations”. These are just dynamic variables, variables that depend on the values of other variables, or on some sort of computation. Note that we just pass regular ruby blocks to the eqtn method. These could really perform any ruby code. The result of the block gets stored in the variable given as a symbol. So, we state that the variable vy is dependent on a the addition of vy + ay. vy + ay will be the sum of the values vy and ay from the previous period. We do the same for vx.

Finally, the position of the ball, the coordinates x and y, depend on the velocity. We also set the default initial position of (10, 100).

Next, we create a “run” of the model, and step it 10 periods. This would be the equivalent of 10 seconds (because of what we set acceleration to).

model_run = model.new_run
model_run.step 10

Note that although we do not do so here, we could alter the values of run variables in each period, like so:

model_run.set ax: 5
model_run.step

This would give the ball an acceleration in the x direction, and this value would be propagated through subsequent periods (because the variable is static).

Once we have stepped the run, we can request the series data in case we want to plot it. We can retrieve and plot the data like so:

xs, ys = model_run.data.series :x, :y

require 'chunky_png'
image = ChunkyPNG::Image.new @width, @height,
  ChunkyPNG::Color::BLACK
pts.each do |pt|
  x, y = pt
  # flip y due to inverted coordinate system
  y = @height - y
  image.circle x, y, 3, ChunkyPNG::Color('red')
end
image.save filename

and we’d get something like this:

drop

Mortgages

A mortgage involves a balance, loan payment, and interest rate. Let’s create a model for that.

model = Model.new do
  name = "Mortgage model"

  # monthly steps
  var :base_rate, 0.08
  eqtn(:annual_rate) { base_rate }
  eqtn(:monthly_rate) { annual_rate / 12.0 }
  var :payment, 2000
  eqtn :balance, 250000 do
    balance * (1 + monthly_rate) - payment
  end
end

We want the period to be monthly instead of annually, so note the monthly_rate variable above. We also have various default values we will override in a bit. Let’s look at 3 types of mortgages, fixed, balloon, and variable. Fixed rate mortgages have a constant interest rate locked over a defined term (such as 30 years). Let’s see what happens to the balance at the end of 30 years for each of these loan types. A balloon loan has a lower monthly payment, but a large payment at the end of the term. A variable loan has a term where the interest rate is indexed to a published interest rate, and then a period where the rate is fixed. Below, we simplify things by assuming that we will just pay different amounts for each of the terms.

fixed = @model.new_run
fixed.set payment: 2100
fixed.step 30*12

# balloon
balloon = @model.new_run
balloon.set payment: 1850
balloon.step 30*12

variable = @model.new_run
# first 10 years, stick with low payment
variable.set payment: 1800
variable.step 10*12

# subsequent years, balloon to higher payment 
# until its paid
variable.set payment: 2100
variable.step 20*12

And then we can get the series data and plot it similarly to before. It results in an image like the one below.

  • Fixed = yellow
  • Variable = blue
  • Balloon = red

mortgage

You can find the homepage for the Simulator gem on Github and find the gem on RubyGems.