"""
TP info : Thème 2 - Résolution approchée de f(x)=0
23 sept. 2025
"""

## importations
import numpy as np
import matplotlib.pyplot as plt

## Q1

def f(x):
    return np.cos(x)-x


# Tracé de la courbe de la fonction
a,b = -5,5              #bornes de l'interavlle de tracé
X = np.linspace(a,b)    # X = tableau numpy 1-dimension (= "liste")
Y = f(X)                # !!! Normalement, f prend un flottant
                        # en entrée, mais les fonctions numpy
                        # sont vectorisées : elles acceptent
                        # les tableaux numpy en entrée :
                        # f(tableau T) = Tableau des f(items de T)
plt.plot(X,Y)
plt.grid('on')
plt.show()


# Graphiquement, la racine de f semble se situer autour de 0.72
# On propose [a,b]=[0,1]


## Q2

# Description de l'algorithme :(étape ULTRA IMPORTANTE)
# 1. Considérer le réseau de points x0=a, x1= a +eps,...xk = a+k*eps...
# 2. Répéter (= boucle) le calcul suivant :
#               2.a calcul de xk et xk+1
#               2.b comparer les signes de f(xk) et f(xk+1)
#                     2.b.1) Test ( = branchement if)
#                          Si de même signe : poursuivre la  boucle
#                          Sinon : sortir et renvoyer xk et k
# rem : on peut renvoyer xk+1 (qui est une valeur approchée par excès)



def balayage(f,a,b,eps):
    """
    entrées : f (function) la fonction dont on cherche une racine r
              a,b (float) : les bornes de l'intervalle [a,b] contenant r
              eps : (float) la précision du calcul de r
    sorties : x (float) valeur approchée de r à eps près
              n (int)   nombre de calculs nécessaires pour obtenir x
    """
    x0 = a         #x0,x1 : les deux extrémités du 1er  I intervalle balayé
    x1 = a+eps

    N = int((b-a)/eps) # nb de points du réseau

    for k in range(N):
        y0,y1 =f(x0),f(x1)     # je calcule les valeurs de f aux bornes de I
        if y0*y1<=0:           # si f change de signe sur I : j'ai trouvé r
            return x0,k+1      # je renvoie une valeur approchée de  r par défaut
        else:
            x0,x1=x0+eps,x1+eps  # sinon, je passe au I suivant


def balayage2(f,a,b,eps):
    # version avec boucle while de balayage
    x0,x1 = a,a+eps
    y0,y1 = f(x0),f(x1)
    k = 1  # nombre de calculs
    while y0*y1>0:
        x0,x1 = x0+eps,x1+eps
        y0,y1 = f(x0),f(x1)
        k+=1
    return x0,k
# rem : dans cette version, pas besoin de b


##Q2b)

# Utilisation du programme :
eps = 1E-3
a,b = 0,1
x,n = balayage2(f,a,b,eps)
print(f"valeur approchée de x*  à {eps} près par défaut : {x}")
print(f"obtenue en {n} itérations ")



## Q3a) dichotomie. Tombe à l'oral sans aide parfois

def  dicho(f,a,b,eps):
    an,bn = a,b
    n = 0                   # compteur du nombre d'itérations
    while bn-an >eps:
        n+=1
        cn = (an+bn)/2      # milieu du segment
        if f(an)*f(cn)<0:   # si la fonction change de signe sur [a,c]
            bn = cn         # à l'étape suivante on prend [a,b] = [a,c]
        else:               # sinon
            an = cn         # c'est qu'elle s'annule sur [b,c]
    return cn,n

## 3b
# Utilisation du programme :
eps = 1E-7
a,b = 0,1
x,n =dicho(f,a,b,eps)
print(f"valeur approchée de x*  à {eps} près  : {x}")
print(f"obtenue en {n} itérations ")



##3c
# la méthode de Dichotomie semble plus efficace que le balayage


##Q4 Méthode de Newton

def fprime(x):
    """
    dérivée pour Newton
    """
    return -np.sin(x)-1

def Newton(f,fprime,a,b,eps):
    n = 1                        # compteur du nombre d'itérations
    x0 = (a+b)/2                 # choix du point de départ (arbitraire dans [a,b])
    # 1. pour le critère d'arrêt, il faut calculer deux termes
    #    consécutifs
    # 2. A priori, on ne sait pas quand est-ce que deux termes
    #    consécutifs seront distants de moins de eps :
    #    on programme donc une  boucle while
    x1 = x0 - f(x0)/fprime(x0)
    while (x1-x0)**2>eps**2:     # évite les valeurs absolues
    # while abs(x1-x0)>eps       # les deux while
                                 # sont mathématiquement équivalents
        x0,x1 = x1, x1-f(x1)/fprime(x1)
        n+=1
    return x1,n

a,b,eps = 0,1,1E-14
x,n =Newton(f,fprime,a,b,eps)
print(f"valeur approchée de x* par Newton  à {eps} près  : {x}")
print(f"obtenue en {n} itérations ")









