The Fibonacci sequence is defined as: 0,1,1,2,3,5,8,13.... we can write it has Fn=Fn1+Fn2,withF0=0,F1=1

Lets make a recursive implementation:

import timeit

# Recursive Fibonacci function
def fibonacci(n):
    if n <= 1:
        return n
    return fibonacci(n-1) + fibonacci(n-2)

# Timing the Fibonacci function
n = 10  # Example Fibonacci number
execution_time = timeit.timeit(lambda: fibonacci(n), number=1)
print(f"Fibonacci({n}) = {fibonacci(n)}")
print(f"Execution time: {execution_time:.6f} seconds")
Output:
Fibonacci(10) = 55
Execution time: 0.000023 seconds

which is not much but if we choose n like 36 it takes

Output:
Fibonacci(36) = 14930352
Execution time: 6.045001 seconds
it is very inefficient it has Time Complexity O(2n)

In this post I will show how we can do better using Linear Algebra

Matrix Representation of the Fibonacci Sequence

We can represent the Fibonacci recurrence relation using matrices. Consider the vector x(n) :

[FnFn1]

The recurrence relation can be rewritten as:

[FnFn1]=[Fn1+Fn2Fn1]

we can express it by matrix multiplication:

[FnFn1]=[1110][Fn1Fn2]

Let's denote the matrix: A=[1110] hence [FnFn1]=A[Fn1Fn2] or x(n)=A[Fn1Fn2] we can rewrite it has:

x(n)=A×x(n1)

if we start with x(1)=[10]

then x(2)=A×x(1) so on x(3)=A×x(2)=A2×x(1)

...

x(n)=An1×x(1)

we can Diagonalize Matrix A in form of:

A=M×D×M1

where D is a Diagonal Matrix containing eigenvalues of A

now

A2=M×D×M1×M×D×M1

we see we can cancel out M and M1

so

x(n)=M×Dn1×M1×x(1)

Lets do Diagonalization of A

import numpy as np

A = np.array([[1, 1],
              [1, 0]])
eigenvalues, eigenvectors = np.linalg.eig(A)
D = np.diag(eigenvalues)
M = eigenvectors
M_inv = np.linalg.inv(M)

D,M,M_inv
Output:
(array([[ 1.61803399, 0. ], [ 0. , -0.61803399]]),
array([[ 0.85065081, -0.52573111], [ 0.52573111, 0.85065081]]),
array([[ 0.85065081, 0.52573111], [-0.52573111, 0.85065081]]))

New Fibonacci Implementation


import timeit
import numpy as np

def fibonacci(n):

  D = np.array(np.array([[1.61803399,0],[0,-0.61803399]]))

  M = np.array([[ 0.85065081, -0.52573111],
        [ 0.52573111,  0.85065081]])

  M_inv = np.array([[ 0.85065081,  0.52573111],
        [-0.52573111,  0.85065081]])

  x1 = np.array([1,0])

  res = [email protected]_power(D,n-1)@M_inv@x1

  return int(res[0])

# Timing the Fibonacci function
n = 36  # Example Fibonacci number
execution_time = timeit.timeit(lambda: fibonacci(n), number=1)
print(f"Fibonacci({n}) = {fibonacci(n)}")
print(f"Execution time: {execution_time:.6f} seconds")
Output:
Fibonacci(36) = 14930352
Execution time: 0.000199 seconds
we can go even higher like n= 100
Output:
Fibonacci(100) = 354224876645738086400
Execution time: 0.000243 seconds
Our new implementation have Time Complexity O(log(n))