100 Languages Speedrun: Episode 16: Octave

taw

Tomasz Wegrzanowski

Posted on December 6, 2021

100 Languages Speedrun: Episode 16: Octave

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")
Enter fullscreen mode Exit fullscreen mode

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()
Enter fullscreen mode Exit fullscreen mode

Which pops us this:

Octave Graph

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
Enter fullscreen mode Exit fullscreen mode

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 vector u. 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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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");
Enter fullscreen mode Exit fullscreen mode

Loads the image, flips it horizontally, and saves it. Turning this:

Lenna

Into this:

Lenna mirror

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.

Code for the Octave episode is available here.

💖 💪 🙅 🚩
taw
Tomasz Wegrzanowski

Posted on December 6, 2021

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related