#Corrigé du TP d'algèbre

import numpy as np
import numpy.linalg as alg
import numpy.random as rd
import matplotlib.pyplot as plt

#Exercice 1

A=1/3*np.array([[1,2,2],[2,1,-2],[-2,2,-1]])

#1. Produit A^T*A, pour vérifier que c'est  bien I_3 :
print(np.dot(np.transpose(A),A))
#Ou bien :
print(np.dot(A.T,A))
#Ou bien :
print(A.T.dot(A))

#Déterminant de A, ce qui permet d'affirmer que A appartient à SO3(R)
print(alg.det(A))

#La transformation associée à la matrice A est une isométrie vectorielle positive de l'espace, à savoir une rotation autour d'un certain axe.

#2. Puissance 23ème de A
print(alg.matrix_power(A,23))
#On obtient une matrice proche de l'identité, mais pas tout à fait égale : l'angle de rotation est tel que l'effectuer 23 fois est porche de faire un certain nombre de tours complets.

#3. Spectre de A
print(alg.eigvals(A))

#Valeurs propres et vecteurs propres
print(alg.eig(A))
#On repère que 1 est la première valeur propre, on récupère le vecteur propre correspondant
P=alg.eig(A)[1]
print(P[:,0])

#On comprend que l'espace propre est orienté par (1,1,0) (mais Python donne un vecteur normé

#Exercice 2

def comp(n,p):
    M=rd.randint(-2,3,(n,p))
    G=np.dot(M.T,M)
    H=np.dot(M,M.T)
    return(alg.eigvals(G),alg.eigvals(H))

print(comp(4,4))
print(comp(5,3))
#On constate que lorsque n=p, les matrices G et H ont le même spectre (mais pas forcément donné dans le même ordre). Lorsque n>p, G est de taille p et H est de taille n. Le spectre de H coïncide avec le spectre de G auquel on a rajouté n-p zéros.

#Exercice 3

z=3+4j
print(abs(z))

def puissance(k):
    w=z**k
    return(w.real,w.imag)

for k in range(1,30):
    a,b=puissance(k)
    print(a%5,b%5)

#On conjecture que le reste de a_k dans la division par 5 vaut toujours 3, celui de b_k vaut toujours 4.
#Attention, pour de grandes valeurs de k (à partir de k=23 ici), le résultat n'est pas vérifié car le nombre z**k devient extrêmement grand et des erreurs apparaissent.

#On constet
en particulier que z^k ne peut jamais être un nombre réel, et donc (z/|z|)^k non plus. En particulier, le nombre z/|z|, qui est de module 1, n'est pas une racine n-ième de l'unité, quel que soit n.

#Exercice 4

def normecolonne(A,k):
    Ck=A[:,k]
    return(sum(Ck*Ck)**0.5)

def diff(A):
    P=1
    for k in range(len(A)):
        P*=normecolonne(A,k)
    return(abs(alg.det(A))-P)

n=4
L=[]
for j in range(100):
    A=rd.random((n,n))
    L.append(diff(A))
print(L)

#La valeur donnée par diff est toujours négative, on conjecture que |det(A)| est toujours majoré par le produit des normes des colonnes.

#Exercice 5

def A(n):
    A=np.zeros((n,n))
    for i in range(n):
        for j in range(n):
            if i!=j:
                A[i,j]=j+1
    return(A)

def vp(n):
    return(alg.eigvals(A(n)))

for k in range(2,11):
    print(A(k),vp(k))

#On conjecture qu'il y a une unique valeur propre positive, et toutes les autres sont négatives (et leur somme vaut l'opposé de la vp positive puisque par argument de trace, la somme de toutes les valeurs prorpes est nulle)

def test(n):
    for x in vp(n):
        print(sum([k/(x+k) for k in range(1,n+1)]))
print(test(7))

#Exercice 6

def D(n,a,b):
    M=a*np.eye(n)
    for i in range(n-1):
        M[i,i+1]=2*b
        M[i+1,i]=1
    return(alg.det(M))

def graphe(n,b):
    L=np.linspace(-2*b**0.5,2*b**0.5,100)
    M=[D(n,a,b) for a in L]
    plt.plot(L,M)
    plt.show()

def affichage(n):
    for b in range(1,n+1):
        graphe(n,b)

affichage(4)






