Fast Fibonacci numbers using Monoids
haskellforall.com
haskellforall.com
X^n Y = (X^2)^(n/2) Y if n is even
X^n Y = X^(n-1) (XY) if n is odd
So you don't really need to take a full matrix-matrix product; you only need to square a matrix, X^2, and take a matrix-vector product, XY.In the case of Fibonacci matrices, they can be represented with their first column only since they have the form
A B
B A+B
so these are just square (A, B) = (AA+BB, 2AB+BB)
mulVec (A, B) (u, v) = (Au+Bv, Av+B(u+v))Fun trick, if you ever find yourself in a situation like this - instead of calculating the matrix by hand (a tedious chore), you can use an algorithm called Berlekamp Massey on the terms of your sequence and then find the k-th term in O(N log N log K) time instead of O(N^3 log K) time (like matrix exponentiation would).
It definitely makes sense, recurrence relation does have its own polynomial. Even the naive polynomial multiplication is very fast.
Now, what I'm wondering is if this is possible to do with adjacency matrices of graphs.
Recently I was calculating the number of paths of a knight going from one position to another in K moves. Ended up doing O(N^6 log K) (N is the chessboard dimension) which was fast enough for a small chessboard but maybe there's a way to turn that adjacency matrix to a polynomial and do the same.
Cayley Hamilton theorem gives a linear relation between A^N and the terms less than that.
So for your problem, you can calculate the number of paths from A->B of length k in k*N^2 time, you need O(N^3) to compute enough terms for Berlekamp Massey, O(N^2) to compute Berlekamp Massey, and then O(n log n log k) to compute the k-th linear recurrence term with FFT.
I've actually considered writing a blog post about this :) It's a cool example of how much further you can go with a problem where many people only know the O(NK)DP solution.
Is this actually this fast or did the author get fooled by lazy evaluation or something along that line? For the fun of it I tried to come up with an algorithm in C# that works more or less like the matrix exponentiation one but without explicitly using matrix exponentiation. When finished it failed to terminate for large numbers and I eventually estimated that it would take about six hours to find the 100 millionth Fibonacci number with this code on my laptop. I don't see why this would be so much slower than a straight forward implementation of the matrix exponentiation algorithm, where is the big difference if any? Or is the .NET big integer implementation that slow? Or did the author get fooled after all?
private static BigInteger Fibonacci(UInt32 n)
{
if (n == 0)
{
return BigInteger.Zero;
}
else
{
var (a, b) = (BigInteger.Zero, BigInteger.One);
var mask = 1u << (Int32)Math.Floor(Math.Log(n, 2));
while ((mask >>= 1) != 0)
{
var s = a + b;
var (a2, b2, s2) = (a * a, b * b, s * s);
(a, b) = ((n & mask) == 0) ? (a2 + b2, s2 - a2) : (s2 - a2, s2 + b2);
}
return b;
}
}(The mechanism for the speedup is described in passing, in a little note about the Haskell library function he's using to calculate a power of the matrix. For whatever it's worth this trick is excellent, but it dates from before computers were invented.)
Nice idea to use s.
EDIT: I found an issue [1] concerning .NET big integer performance, it seems it is only using pretty basic and relatively slow algorithms, especially for multiplication which is the relevant operation here.
The closed form solution is not quite a dead end. The floating point errors can be avoided if one works in the field of number of the form a + b √5 where a and b are rational.
All one needs to is to maintain the numbers a and b separately. The same doubling trick applies for exponentiation.
However the field you'd be working over is Q[√5], although really we're interested in the subring Z[φ] here. And since φ is (trivially) an eigenvalue of the matrix, let's call it M, this is equivalent to calculating in Z[M] by the Cayley-Hamilton theorem.
Of course some of these constructions might be easier to work with, and certainly something like Z[x]/(x^2-x-1) allows for fairly straightforward calculation, but you're still doing the same multiplications essentially.
And if you're working in a convenient ring you don't really need the ψ^n term, since you can just use that φ^n = F_{n-1} + F_{n} φ.
Since you're defining your own custom Matrix2x2 class and <> operation anyway, you can save a little space and computation by relying on the fact that every Fibonacci matrix is of the form: [ a+b, a; a, b ]. Thus only two member variables are needed, and <> can be implemented with fewer multiplications.
The elegance of the Haskell solution is that you only need to define a Monoid, and then you get exponentiation by squaring for free. This is a great example of abstraction done right.
I am not good with Haskell but I meant in general mathematical / algebraic introduction, explanation or a lecture.
I've had some luck with the first chapter of this (free) [2] book, although they explain groups and not monoids but for me it was then rather quick to gain intuition about monoids.
[1] https://en.wikipedia.org/wiki/Monoid#Relation_to_category_th...
https://bartoszmilewski.com/2014/10/28/category-theory-for-p...
(It's not focused on Monoids. It also covers groups, rings, and fields from a mathematical point of view. It's written for an undergraduate math major with no prior experience in these areas.)
Julie Moronuki's blog has helped me greatly to understand it all: https://argumatronic.com/posts/2019-06-21-algebra-cheatsheet...
In any case, doesn't adding more properties make a theory simpler? For example the classification of finite simple groups is much harder than the classification of finite simple commutative groups.
Think for example about learning about the "iterator" abstraction. It's just something with "hasNext" and "next" operations, nothing more to understand. But when you know it you start seeing iterators everywhere, and you get frustrated when something that should be an iterator isn't so you cannot write a simple foreach loop. And you'll start implementing iterators in your own code, because you know that provides a lot of convenience for free to consumers.
Same thing for monoids. They're everywhere, they offer some convenience for free, and you'll be wondering why things that should have monoid instances don't.
The closed form as sums of exponents also easily generalizes. You can find the roots of an associated polynomial, and the those roots are the numbers you take powers of. This is explained on Wikipedia here: https://en.wikipedia.org/wiki/Recurrence_relation#Solving_ho....
One of the more enjoyable maths texts I've read.
Sadly it's not as functional as it once was (sequences are supposed to continue generating as you scroll down the page). If anyone's interested in lending a hand to get it working properly again, I'd love to hear from you! My email is in my profile.
https://aperiodical.com/2014/06/discovering-integer-sequence...