#########################################
######## TP informatique bio-spé ########
########        2025-2026        ########
#########################################


#########################################
###### Thème 11 : Diagonalisation #######
#########################################

## Importations

import numpy as np
import numpy.linalg as la
import random as rd

## Q1 : dimension du noyau d'une matrice carrée

# on utilise la formule du rang : si M est une matrice
# carrée de taille n*n, alors : dim(Ker M) + rg(M) = n

def dimKer(M) :
    n,p = np.shape(M)
    if n!= p :
        return 'matrice non carrée'
    return n-la.matrix_rank(M)

## Q2 : dimension de Ker(A-mu.I)

def dimE(A,mu) :
    n,p = np.shape(A)
    if n != p : return 'matrice non carrée'
    B = A - mu*np.eye(n)
    return dimKer(B)

## Q3 : matrice diagonalisable

def est_diago(A,L) :
    n,p = np.shape(A)
    if n != p : return 'matrice non carrée'
    S = 0
    for mu in L :
        S += dimE(A,mu)
    return S >= n   # >= pour réduire quelques erreurs
                    # liées aux arrondis


## Q4 : la fonction 'eig' du sous-module 'linalg'

# abréviation de 'eigen value' [valeur propre]
# Les matrices ne sont bien sûr pas toutes
# diagonalisables. Dans le cas d'une matrice non diagonalisable,
# la fonction 'eig' renvoie une matrice P contenant
# une ou des colonnes nulles (elle n'est donc pas inversible).

## Q5 : spectre d'une matrice

def spectre(M) :
    n,p = np.shape(M)
    if n != p : return 'matrice non carrée'
    L,_ = la.eig(M)
    Sp = []
    for elt in L :
        if elt not in Sp :
            Sp.append(elt)
    return Sp

def diago(A) :
    Sp = spectre(A)
    return est_diago(A,Sp)

## Q6 : une matrice non diagonalisable

J = np.array([[0,1,0],[0,0,1],[0,0,0]])
print(diago(J))

# J est triangulaire, donc on peut lire son spectre
# sur sa diagonale : Sp(J) = {0}
# J ne possède qu'une seule valeur propre donc elle
# est diagonalisable si et seulement si elle est diagonale.
# Ce n'est pas le cas.


## Q7 : applications

# pour chaque matrice donnée, on utilise la fonction 'eig'

A1 = np.array([[0,1,0],[0,0,1],[0,0,0]])
A2 = np.array([[-5,6,4],[-4,5,4],[2,-2,-3]])
A3 = np.array([[1,3,2],[0,-2,-2],[-3,9,8]])
A4 = np.array([[3,5,-3],[0,2,0],[6,10,-6]])
A5 = np.array([[4,-2,0],[4,-2,0],[-2,2,2]])
A6 = np.array([[0,1,0],[-2,3,0],[0,0,2]])
A7 = np.array([[1,4,2],[2,3,2],[-4,-8,-5]])
A8 = np.array([[4,1,2],[-1,1,-1],[-2,-1,0]])
A9 = np.array([[-1,5,-5],[1,-7,6],[1,-11,10]])
A10 = np.array([[1,-1,0],[1,3,0],[-1,-3,1]])

# choix de la matrice :
A = A10
Sp = spectre(A)
print('Le spectre de A est :')
print(Sp)
print('La matrice A est diagonalisable ?')
print(diago(A))
if diago(A) :
    print('Une matrice de passage est :')
    print(la.eig(A)[1])
else :
    for vap in Sp :
        print("L'espace propre associé à la valeur propre ",vap," est de dimension :")
        print(dimE(A,vap))

