Easy-peasy symbolic computation with Ruby
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.