Home > Uncategorized > Easy-peasy symbolic computation with Ruby

Easy-peasy symbolic computation with Ruby

January 3rd, 2009 — Mark Przepiora

Ruby is a wonderful language, largely deserving of the fanaticism surrounding it. There are a number of ways you can exploit its syntax to write concise, beautiful code. For example, to shuffle an array…

deck.sort_by{ rand }

…or to pick out certain elements of one…

deck.find_all{ |card| card.suit == Clubs }

…or to seamlessly cache computations.

def average_earnings
  @average_earnings ||= some_lengthy_computation
end

(Above, the ||= operator acts analogously to the familiar += operator. So if the instance variable @average_earnings already has a non-nil value, it is returned without any further computation. If on the other hand it is nil, then some_lengthy_computation is performed, @average_earnings is set to it, and returned.)

In addition, there are also a number of ridiculously short applications written in it, including a web server in 70 lines of code, a message board application in 500 lines, and its slightly more verbose successor.

In addition to these, I present a proof of concept of my own: Mathematica and Maple-like symbolic differentiation in about a hundred lines of code.

The idea is to have a Function class, which is subclassed by actual functions (Cos, Exp, Constant, etc.) as well as by operators (Addition, Multiplication, etc.)

Ruby allows us to overload operators, so we can add, subtract, multiply, and divide functions naturally, and define “f + g” as an instantiation of the Add class (which we will define later!)

Each subclass of Function must implement two methods: (1) diff, which returns another Function object (its derivative), and (2) the indexing method [], so given a Function object f and number x in its domain, we can evaluate f(x) via “f[x]“.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
class Function
  # Skeleton. Returns: another Function
  def diff
  end
 
  # Skeleton. Returns: a number
  def [](index)
  end
 
  # Begin algebraic operations
  def +(a)
    Add.new(self,a)
  end
 
  def *(a)
    a = Constant.new(a) if a.kind_of? Numeric
    Multiply.new(self,a)
  end
 
  def /(a)
    a = Constant.new(a) if a.kind_of? Numeric
    Divide.new(self,a)
  end
 
  def -(a)
    self + a * -1
  end
 
  def -@
    self * -1
  end
  # End algebraic operations
end

All binary operations are initialized the same way and have the same basic structure: they act in some way on two functions specified upon creation of the object.

1
2
3
4
5
6
class BinaryOperation < Function
  def initialize(f, g)
    @f = f
    @g = g
  end
end

Multiply subclasses it as follows:

1
2
3
4
5
6
7
8
9
class Multiply < BinaryOperation
  def diff
    @f * @g.diff + @f.diff + @g
  end
 
  def [](x)
    @f[x] * @g[x] 
  end
end

We can similarly define Add and Divide. For any operation, we simply need to know how to differentiate the operation, and how to evaluate it.

We can also define some actual functions. For example,

1
2
3
4
5
6
7
8
9
class Sin < Function
  def diff
    Cos.new
  end
 
  def [](x)
    Math.sin(x)
  end
end
1
2
3
4
5
6
7
8
9
class Cos < Function
  def diff
    -Sin.new
  end
 
  def [](x)
    Math.cos(x)
  end
end
1
2
3
4
5
6
7
8
9
class Exp < Function
  def diff
    self
  end
 
  def [](x)
    Math.exp(x)
  end
end

Once we have all of our operations and functions defined, we can start computing and plotting functions and their derivatives,

1
2
3
4
5
6
7
8
9
10
11
12
f = Cos.new / Exp.new
g = f.diff
 
(0..10).each do |a|
  x = a.to_f/10
  puts "ft#{x}t#{f[x]}"
end
 
(0..10).each do |a|
  x = a.to_f/10
  puts "f't#{x}t#{g[x]}"
end

With a little additional code, we could also print out functions symbolically.

Not bad for less than a couple hundred lines of code.

, , ,

  1. No comments yet.
  1. No trackbacks yet.