Tomasz Wegrzanowski
Posted on December 6, 2021
GNU Octave is a language for numerical calculations, which I used a lot back at university. It's supposedly somehow related to some MATLAB thing, but I never touched that, and I don't plan to, as closed source programming languages are an abomination.
Hello, World!
Octave is often used interactively, but we can also use it with files:
#!/usr/bin/env octave
printf("Hello, World!\n")
Graphs
That's great, but what about graphs? As language for data science (from time before it was called that), Octave comes with builtin visualizations:
#!/usr/bin/env octave
x = -10:0.1:10
y = cos(x)
plot(x, y)
title("Function plot")
xlabel("x")
ylabel("cos(x)")
waitforbuttonpress()
Which pops us this:
The first line creates a vector from -10 to 10, with steps of 0.1.
The second one is probably the most interesting. cos()
is a function that takes a number and returns a number, so what does it even mean to pass a vector to it? In Octave a lot of such functions are just called for each element, and return a vector of results. So y = cos(x)
is like y = x.map{|u| cos(u)}
or y = [cos(u) for u in x]
or y = x.map(u => cos(u))
in some more popular languages. If you do a lot of calculations on vectors and matrices, this concise syntax will really make the difference.
Then we have a few commands to create the plot.
The last command waitforbuttonpress()
is only needed in scripts, and not in interactive use.
Scripts could also save the graphs to image files instead of displaying them in windows.
Fibonacci
As we're doing scientific computing, the normal recursive way to do Fibonacci will not do. As you probably know, the Fibonacci sequence can be defined in another way:
- start with pair of numbers, Current=1 and Previous=1
- at every step, set Current=Current+Previous, Previous=Current
But that's just a matrix multiplication by [1 1; 1 0]
.
#!/usr/bin/env octave
u = [1,1];
m = [1 1; 1 0];
for i = 1:40
result = dot(u * (m**(i-1)), [0 1]);
printf("fib(%d) = %d\n", i, result);
endfor
There's a bunch of things going on here.
- Octave by default prints every assignment, which makes sense for interactive use, but not so much for scripts. All those nasty
;
s prevent automated assignment printing. -
u * (m**(i-1))
raises the matrix to (i-1) exponent, then multiplies by the initial vectoru
. For big numbers this is logarithmic, unlike the usual linear algorithm, or the exponential recursive algorithm. -
dot()
is a dot product function, which we use as a very fancy way to get the second element of the vector - and obviously there are easier ways
It prints just what we expect:
$ ./fib.m
fib(1) = 1
fib(2) = 1
fib(3) = 2
fib(4) = 3
fib(5) = 5
fib(6) = 8
fib(7) = 13
fib(8) = 21
fib(9) = 34
fib(10) = 55
fib(11) = 89
fib(12) = 144
fib(13) = 233
fib(14) = 377
fib(15) = 610
fib(16) = 987
fib(17) = 1597
fib(18) = 2584
fib(19) = 4181
fib(20) = 6765
fib(21) = 10946
fib(22) = 17711
fib(23) = 28657
fib(24) = 46368
fib(25) = 75025
fib(26) = 121393
fib(27) = 196418
fib(28) = 317811
fib(29) = 514229
fib(30) = 832040
fib(31) = 1346269
fib(32) = 2178309
fib(33) = 3524578
fib(34) = 5702887
fib(35) = 9227465
fib(36) = 14930352
fib(37) = 24157817
fib(38) = 39088169
fib(39) = 63245986
fib(40) = 102334155
FizzBuzz
Octave can do a lot of numerical operations without looping, but sadly this doesn't extend to strings or I/O.
So here's an interesting way to do FizzBuzz:
#!/usr/bin/env octave
vector = 1:100
indexes = 1 + !mod(vector, 3) + 2*!mod(vector, 5)
for i=vector
o = {i, "Fizz", "Buzz", "FizzBuzz"};
ai = indexes(i);
disp(o{ai})
endfor
Let's look at it step by step. First, vector
is initialized to all numbers from 1 to 100. As we didn't put a ;
at the end of the line, Octave helpfully prints:
vector =
Columns 1 through 13:
1 2 3 4 5 6 7 8 9 10 11 12 13
Columns 14 through 26:
14 15 16 17 18 19 20 21 22 23 24 25 26
Columns 27 through 39:
27 28 29 30 31 32 33 34 35 36 37 38 39
Columns 40 through 52:
40 41 42 43 44 45 46 47 48 49 50 51 52
Columns 53 through 65:
53 54 55 56 57 58 59 60 61 62 63 64 65
Columns 66 through 78:
66 67 68 69 70 71 72 73 74 75 76 77 78
Columns 79 through 91:
79 80 81 82 83 84 85 86 87 88 89 90 91
Columns 92 through 100:
92 93 94 95 96 97 98 99 100
Next we use the power of Octave to calculate which of the FizzBuzz branches we are at, without any loops. Again, Octave is helpfully printing the result:
indexes =
Columns 1 through 20:
1 1 2 1 3 2 1 1 2 3 1 2 1 1 4 1 1 2 1 3
Columns 21 through 40:
2 1 1 2 3 1 2 1 1 4 1 1 2 1 3 2 1 1 2 3
Columns 41 through 60:
1 2 1 1 4 1 1 2 1 3 2 1 1 2 3 1 2 1 1 4
Columns 61 through 80:
1 1 2 1 3 2 1 1 2 3 1 2 1 1 4 1 1 2 1 3
Columns 81 through 100:
2 1 1 2 3 1 2 1 1 4 1 1 2 1 3 2 1 1 2 3
Unfortunately to turn that into strings and print them, we need to loop.
o = {i, "Fizz", "Buzz", "FizzBuzz"}
is an array with some numbers and strings in it. Arrays (like normal programming language arrays) and vectors (numbers only, but with all the fancy extra functionality) are separate concepts in Octave, and use different characters - []
for vectors, and {}
for arrays.
Then we lookup twice, ai = indexes(i)
takes i-th element of the vector, o{ai}
takes ai-th element of the array. Also notice different syntax - ()
for vectors, {}
for arrays.
Then we use disp()
which displays the result.
All this extra syntax for arrays is the price we pay for having all the convenient vector and matrix syntax.
Octave got halfway to where we wanted, and calculations part was great, but then it somewhat disappointed when it came to finding the string to print and printing the result.
Oh and all indexes start at 1, not 0.
Polynomials
Octave can solve polynomial equations numerically. If you treat a vector as vector of polynomial coefficients, so [2 3 4]
represents 2 * x**2 + 3 * x + 4
, then we can do some interesting things:
#!/usr/bin/env octave
a = [2 3 4]
# 2 * 10**2 + 3 * 10**1 + 4
polyval(a, 10)
roots(a)
polyval(a, -0.7500 + 1.1990i)
Which prints:
a =
2 3 4
ans = 234
ans =
-0.7500 + 1.1990i
-0.7500 - 1.1990i
ans = -2.0200e-04
ans = 1.7764e-15 + 6.6613e-16i
As you can see it can handle complex numbers without any extra work.
Also the solution it printed had a minor rounding error - during printing not calculations, actual value returned by roots
is accurate to 16 digits. Most languages print more significant digits than Octave, but I guess if you work with numbers a lot, it might be a reasonable default.
Audio
One unexpected feature of Octave is builtin support for audio.
You can use it for boring single tones:
#!/usr/bin/env octave
x = (0:44100) * (100 * 2 * pi / 44100);
y = sin(x);
player = audioplayer(y, 44100, 8);
play(player);
pause(1.1);
But you could also do some proper electronic music generation. You can also load audio files and use Octave to manipulate them.
There's nothing too special about it, as audio file is just a vector of numbers (44100 numbers for each second of audio or so), so any language can do it with some libraries, but it's interesting that it comes builtin.
pause
command is there to wait 1.1s before quitting the script. The audio sample is just 1s, but 0.1s given as an extra buffer, as timers tend to be not quite that exact.
Image processing
Octave supports data with more than 2 dimensions, and it also has builtin processing of images - third dimension in this case being R, G, B channels.
For example this code:
#!/usr/bin/env octave
img = imread("lenna.png");
img2 = img(:, columns(img):-1:1, :);
imwrite(img2, "lenna_mirror.png");
Loads the image, flips it horizontally, and saves it. Turning this:
Into this:
As for the code, :
means the whole range, columns(img):-1:1
is a range from columns(img)
down to 1
with step -1
.
Should you use Octave?
Octave was great in its time, but nowadays Python with Jupyter and all its numerical libraries do pretty much the same job, a lot better.
Octave still does matrix and vector computations very well, and has some interesting image and audio processing code, but connecting those calculations with the outside world requires a lot of glue code, and that's where Python is far superior.
Overall it's by no means a bad language, Python just has too many advantages over it to really recommend Octave.
Code
All code examples for the series will be in this repository.
Posted on December 6, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.