%% nav-debut : généré par gen_navigation.py, ne pas modifier à la main %% > [!nav] > ↩ [[Analyse et Données/Mathématiques/Programmation Scientifique/Programmation Scientifique|Accueil · Programmation Scientifique]] > > | [[Analyse et Données/Mathématiques/Programmation Scientifique/Cours 1 - Programmation Scientifique\|← Cours 1 - Programmation Scientifique]] | | > |:---|---:| %% nav-fin %% # Exercices - Programmation scientifique **Consignes.** Travaillez dans un notebook ou un fichier Python. Pour chaque exercice, conservez le code, les résultats obtenus et une courte interprétation mathématique. Vous pouvez utiliser la documentation Python et les ressources du cours. N'importez que les bibliothèques nécessaires. ## Exercice 1 — Une suite définie par récurrence On considère la suite $(u_n)$ définie par $u_0=3$ et $u_{n+1}=(u_n+7/u_n)/2$. Cette relation permet d'approcher $\sqrt 7$. 1. Écrire une fonction `suite(u0, n)` renvoyant la liste $[u_0,\ldots,u_n]$ à l'aide d'une boucle `for`. 2. Afficher les six premiers termes arrondis à six décimales. Comparer le dernier à `math.sqrt(7)`. 3. À l'aide de `enumerate`, afficher les indices $n$ pour lesquels $|u_n^2-7|<10^{-5}$. 4. Écrire une fonction `premier_rang(u0, tol)` utilisant une boucle `while` pour trouver le premier rang vérifiant $|u_n^2-7|<\texttt{tol}$. Prévoir une limite de 100 itérations. 5. Expliquer la différence entre `return` et `print` dans vos fonctions. #### Correction de l’exercice 1 ```python import math def suite(u0, n): valeurs = [u0] u = u0 for _ in range(n): u = (u + 7/u)/2 valeurs.append(u) return valeurs termes = suite(3, 5) print([round(u, 6) for u in termes]) print(math.sqrt(7)) print([n for n, u in enumerate(termes) if abs(u*u - 7) < 1e-5]) def premier_rang(u0, tol): u = u0 for n in range(101): if abs(u*u - 7) < tol: return n if n < 100: u = (u + 7/u)/2 return None print(premier_rang(3, 1e-5)) ``` La suite commence par $u_0=3$, $u_1=8/3\approx2{,}666667$ et $u_2=127/48\approx2{,}645833$. Les six premiers termes arrondis sont approximativement `[3.0, 2.666667, 2.645833, 2.645751, 2.645751, 2.645751]`. Le premier rang satisfaisant la tolérance est **3** (les rangs 3, 4 et 5 satisfont le test dans la liste). La valeur limite est $\sqrt7\approx2{,}64575131$. `return` renvoie une valeur utilisable par un autre calcul ; `print` ne fait que l'afficher. ## Exercice 2 — Fonctions anonymes et traitement de données On considère $f(x)=x^2-3x+1$ et les entiers de $-3$ à $5$. 1. Définir `f` avec `lambda`, puis créer la liste des valeurs de $f$ à l'aide de `map`. 2. Avec `zip`, associer chaque entier à son image, sous forme de couples `(x, f(x))`. 3. À l'aide d'une compréhension de liste, conserver les couples pour lesquels $f(x)>0$. 4. Construire un dictionnaire ayant les entiers pour clés et leurs images pour valeurs. Vérifier le résultat obtenu pour $x=2$. 5. Écrire une fonction `somme_images(valeurs, fonction)` avec un argument `fonction` facultatif, dont la valeur par défaut est `lambda x: x*x`. #### Correction de l’exercice 2 ```python f = lambda x: x*x - 3*x + 1 xs = list(range(-3, 6)) ys = list(map(f, xs)) couples = list(zip(xs, ys)) positifs = [(x, y) for x, y in couples if y > 0] d = dict(couples) print(couples) print(positifs) print(d[2]) def somme_images(valeurs, fonction=lambda x: x*x): return sum(map(fonction, valeurs)) print(somme_images([1, 2, 3])) print(somme_images([1, 2, 3], f)) ``` Les images de $-3$ à $5$ sont `[19, 11, 5, 1, -1, -1, 1, 5, 11]`. En particulier $f(2)=4-6+1=-1$. Les couples retenus vérifient $f(x)>0$. Par défaut, `somme_images([1,2,3])=1+4+9=14`. Avec `f`, la somme est $f(1)+f(2)+f(3)=-1-1+1=-1$. ## Exercice 3 — Divisibilité, PGCD et PPCM **Rappel mathématique.** Le PGCD de deux entiers non tous deux nuls est leur plus grand diviseur commun positif. Leur PPCM est le plus petit entier strictement positif multiple des deux. Pour $a,b>0$, $\operatorname{PGCD}(a,b)\operatorname{PPCM}(a,b)=ab$. 1. Calculer à la main le PGCD et le PPCM de 84 et 126, puis vérifier avec `math.gcd` et `math.lcm`. 2. Écrire une fonction `euclide(a, b)` qui calcule le PGCD uniquement avec une boucle `while` et l'opérateur `%`. 3. Vérifier votre fonction sur `(84,126)`, `(35,64)` et `(0,18)`. 4. Pour $n$ allant de 2 à 20, créer la liste des entiers tels que `math.gcd(n, 20) == 1`. 5. À l'aide de `divmod`, calculer quotient et reste de 347 par 12 ; vérifier l'identité $347=12q+r$. 6. Expliquer pourquoi `math.floor(-7/3)` et `int(-7/3)` ne donnent pas la même valeur. #### Correction de l’exercice 3 $84=2^2\cdot3\cdot7$ et $126=2\cdot3^2\cdot7$, donc $\operatorname{PGCD}(84,126)=42$ et $\operatorname{PPCM}(84,126)=252$. Leur produit vaut $10584=84\times126$. ```python import math def euclide(a, b): a, b = abs(a), abs(b) while b != 0: a, b = b, a % b return a print(math.gcd(84, 126), math.lcm(84, 126)) print([euclide(a, b) for a, b in [(84,126), (35,64), (0,18)]]) print([n for n in range(2, 21) if math.gcd(n, 20) == 1]) q, r = divmod(347, 12) print(q, r, 12*q + r) print(math.floor(-7/3), int(-7/3)) ``` Résultats : `42 252` ; `[42, 1, 18]` ; `[3, 7, 9, 11, 13, 17, 19]` ; `28 11 347` ; `-3 -2`. `floor` arrondit vers $-\infty$, tandis que `int` tronque vers zéro. ## Exercice 4 — Combinatoire et fractions exactes **Rappel mathématique.** Le nombre de façons de choisir $k$ objets parmi $n$, sans ordre, est $\binom nk=\frac{n!}{k!(n-k)!}$. Une fraction rationnelle $p/q$ peut être simplifiée en divisant numérateur et dénominateur par leur PGCD. 1. Calculer $\binom{12}{4}$ à l'aide de `math.factorial`, puis avec `math.comb`. 2. Construire la liste des coefficients binomiaux $\binom{6}{k}$ pour $k=0,\ldots,6$. Vérifier que leur somme vaut $2^6$. 3. Avec `fractions.Fraction`, calculer exactement $\frac{7}{12}+\frac{5}{18}-\frac{1}{9}$. 4. Afficher le numérateur et le dénominateur de la fraction obtenue. 5. Comparer `Fraction(1, 10) + Fraction(2, 10)` avec `0.1 + 0.2`. Expliquer la différence de représentation. #### Correction de l’exercice 4 ```python import math from fractions import Fraction c = math.factorial(12) // (math.factorial(4)*math.factorial(8)) print(c, math.comb(12, 4)) coeffs = [math.comb(6, k) for k in range(7)] print(coeffs, sum(coeffs)) r = Fraction(7, 12) + Fraction(5, 18) - Fraction(1, 9) print(r, r.numerator, r.denominator) print(Fraction(1, 10) + Fraction(2, 10)) print(0.1 + 0.2) ``` Résultats : $\binom{12}{4}=495$ ; coefficients `[1, 6, 15, 20, 15, 6, 1]`, de somme $64=2^6$ ; fraction $3/4$, numérateur 3, dénominateur 4 ; `3/10` contre `0.30000000000000004` (représentation binaire approchée des flottants). ## Exercice 5 — Rotation et distance dans le plan On identifie un point $(x,y)$ au nombre complexe $z=x+iy$. Une rotation d'angle $\theta$ autour de l'origine correspond à $z'=ze^{i\theta}$ ; la distance entre deux points d'affixes $z_1,z_2$ est $|z_1-z_2|$. 1. Définir `rotation(z, theta)` avec `cmath.exp` et `1j`. 2. Appliquer une rotation d'angle $\pi/3$ au point d'affixe $2+1j$. 3. Afficher les parties réelle et imaginaire du résultat, arrondies à quatre décimales. 4. Vérifier numériquement qu'une rotation conserve le module : $|z'|=|z|$. 5. Appliquer successivement deux rotations d'angles $\pi/6$ et $\pi/3$ ; comparer avec une rotation d'angle $\pi/2$. 6. Écrire une compréhension de liste qui transforme les quatre sommets du carré d'affixes `0`, `1`, `1+1j`, `1j`. #### Correction de l’exercice 5 ```python import cmath import math def rotation(z, theta): return z * cmath.exp(1j*theta) z = 2 + 1j w = rotation(z, math.pi/3) print(round(w.real, 4), round(w.imag, 4)) print(math.isclose(abs(w), abs(z), rel_tol=1e-12)) w1 = rotation(rotation(z, math.pi/6), math.pi/3) w2 = rotation(z, math.pi/2) print(cmath.isclose(w1, w2)) sommets = [0, 1, 1+1j, 1j] images = [rotation(s, math.pi/3) for s in sommets] print(images) ``` Comme $e^{i\pi/3}=\tfrac12+i\tfrac{\sqrt3}{2}$, on obtient $w=1-\tfrac{\sqrt3}{2}+i(\sqrt3+\tfrac12)$, soit **`0.1340 2.2321`**. Une rotation conserve le module, car $|e^{i\theta}|=1$. Deux rotations successives s'additionnent : $\pi/6+\pi/3=\pi/2$. Les résultats numériques peuvent contenir de très petites erreurs d'arrondi. ## Exercice 6 — Explorer et transformer un tableau On dispose d'un tableau $A$ contenant les entiers de 1 à 20, rangés en 4 lignes et 5 colonnes. 1. Construire `A` avec `np.arange` et `reshape` ; afficher sa forme (`shape`). 2. Extraire les lignes d'indices 1 et 2, puis les colonnes d'indices 0, 2 et 4. 3. Construire `B` en inversant l'ordre des colonnes de `A` par slicing. 4. Former un masque booléen qui repère les éléments de `A` divisibles par 3 et strictement supérieurs à 6. Afficher les valeurs sélectionnées. 5. Calculer la somme de chaque ligne et le maximum de chaque colonne, en précisant le rôle de `axis`. 6. Ajouter le vecteur `[10,20,30,40,50]` à toutes les lignes de `A` par broadcasting ; expliquer pourquoi l'opération est possible. 7. Extraire une vue `V` sur une partie de `A`, modifier un élément de `V` et observer `A`. Refaire avec `copy()` et comparer. #### Correction de l’exercice 6 ```python import numpy as np A = np.arange(1, 21).reshape(4, 5) print(A.shape) # (4, 5) print(A[1:3, :]) # lignes 1 et 2 print(A[:, [0, 2, 4]]) # colonnes non consécutives B = A[:, ::-1] print(B) masque = (A % 3 == 0) & (A > 6) print(A[masque]) # [ 9 12 15 18] print(A.sum(axis=1)) # [15 40 65 90] print(A.max(axis=0)) # [16 17 18 19 20] print(A + np.array([10, 20, 30, 40, 50])) V = A[1:3, 1:4] # vue V[0, 0] = -100 print(A[1, 1]) # -100 C = A[1:3, 1:4].copy() # copie indépendante C[0, 0] = 999 print(A[1, 1]) # toujours -100 ``` `axis=1` réduit les colonnes à l'intérieur de chaque ligne ; `axis=0` réduit les lignes à l'intérieur de chaque colonne. Le vecteur de longueur 5 se diffuse sur les 4 lignes (*broadcasting*). Le slicing de base crée généralement une vue partageant les données, tandis que `copy()` produit un tableau indépendant. L'indexation avancée `A[:, [0,2,4]]` crée une copie. ## Exercice 7 — Une grille de valeurs et un calcul vectorisé On considère $g(x,y)=x^2+2xy-y^2$ sur une grille de points. 1. Construire `x = np.arange(-2, 3)` et `y = np.arange(0, 4)`. 2. À l'aide de `reshape` ou de `np.newaxis`, calculer sans boucle une matrice `G` dont l'élément $(i,j)$ vaut $g(x_i,y_j)$. 3. Vérifier manuellement deux coefficients de `G`. 4. Repérer les positions où $G>0$ avec `np.where`. 5. Calculer les sommes par ligne et par colonne. Comparer `G**2` et `G @ G.T` : ont-ils le même sens mathématique ? #### Correction de l’exercice 7 ```python import numpy as np x = np.arange(-2, 3) y = np.arange(0, 4) X = x[:, np.newaxis] # (5, 1) Y = y[np.newaxis, :] # (1, 4) G = X**2 + 2*X*Y - Y**2 print(G) print(G[0, 0], G[2, 1]) print(np.where(G > 0)) print(G.sum(axis=1)) print(G.sum(axis=0)) print(G**2) print(G @ G.T) ``` On a $G_{0,0}=g(-2,0)=4$ et $G_{2,1}=g(0,1)=-1$. La matrice $G$ est de taille $5\times4$. `G**2` élève **chaque coefficient** au carré (résultat $5\times4$), tandis que `G @ G.T` est un **produit matriciel** de taille $5\times5$, dont le coefficient $(i,j)$ est le produit scalaire des lignes $i$ et $j$ de $G$. ## Exercice 8 — Système linéaire et puissances de matrices On considère $A=\begin{pmatrix}3&1&0\\1&4&1\\0&1&2\end{pmatrix},\qquad b=\begin{pmatrix}5\\6\\3\end{pmatrix}.$ 1. Construire `A` et `b` avec NumPy, puis calculer la trace et le déterminant de `A`. 2. Résoudre $Ax=b$ avec `np.linalg.solve`. Vérifier le résultat par le produit `A @ x`. 3. Comparer `A*A`, `A @ A` et `np.linalg.matrix_power(A, 2)` ; expliquer la différence. 4. Calculer les valeurs propres de `A` avec `np.linalg.eigvals` et vérifier que leur somme est proche de la trace. 5. Former `A + np.eye(3)` et résoudre le nouveau système avec le même second membre. Comparer les solutions. #### Correction de l’exercice 8 ```python import numpy as np A = np.array([[3., 1., 0.], [1., 4., 1.], [0., 1., 2.]]) b = np.array([5., 6., 3.]) print(np.trace(A)) # 9.0 print(np.linalg.det(A)) # environ 19.0 x = np.linalg.solve(A, b) print(x) # environ [1.36842105 0.89473684 1.05263158] print(np.allclose(A @ x, b)) # True print(A*A) print(A @ A) print(np.linalg.matrix_power(A, 2)) vp = np.linalg.eigvals(A) print(vp, np.isclose(vp.sum(), np.trace(A))) x2 = np.linalg.solve(A + np.eye(3), b) print(x2) # environ [1.03773585 0.8490566 0.71698113] ``` Le déterminant est $19\ne0$, donc le système admet une unique solution : $x=(26/19,17/19,20/19)^T$. `A*A` est le produit **terme à terme**, tandis que `A @ A` et `matrix_power(A,2)` calculent $A^2$. Les valeurs propres sont environ $1{,}4679$, $2{,}6527$, $4{,}8794$ ; leur somme vaut la trace, soit 9. Pour $A+I$, la solution est $(55/53,45/53,38/53)^T$. ## Exercice 9 — Étude d'un polynôme avec SymPy On considère $P(x)=(x-1)^2(x+2)$. 1. Déclarer `x` comme symbole SymPy, construire `P`, puis afficher ses formes développée et factorisée. 2. Extraire les coefficients du polynôme avec `sp.Poly` et calculer $P(3)$ par substitution. 3. Calculer $P'(x)$ et $P''(x)$. Vérifier que $x=1$ est une racine de $P'$. 4. Calculer exactement $\int_0^2 P(x)\,dx$ et afficher le résultat sous forme rationnelle. 5. Résoudre $P(x)=0$ ; expliquer pourquoi la liste des solutions distinctes ne reflète pas forcément les multiplicités. 6. Construire la matrice symbolique $M=\begin{pmatrix}x&1\\1&x\end{pmatrix}$, puis calculer son déterminant et le factoriser. #### Correction de l’exercice 9 ```python import sympy as sp x = sp.symbols('x') P = (x - 1)**2 * (x + 2) print(sp.expand(P)) # x**3 - 3*x + 2 print(sp.factor(P)) # (x - 1)**2*(x + 2) print(sp.Poly(P, x).all_coeffs()) # [1, 0, -3, 2] print(P.subs(x, 3)) # 20 print(sp.expand(sp.diff(P, x))) # 3*x**2 - 3 print(sp.diff(P, x, 2)) # 6*x print(sp.diff(P, x).subs(x, 1)) # 0 print(sp.integrate(P, (x, 0, 2))) # 2 print(sp.solve(P, x)) # [-2, 1] M = sp.Matrix([[x, 1], [1, x]]) print(sp.factor(M.det())) # (x - 1)*(x + 1) ``` $P(x)=x^3-3x+2$, $P'(x)=3x^2-3$ et $P''(x)=6x$. L'intégrale vaut 2. Les racines distinctes sont $-2$ et $1$, mais $1$ est **double**, ce que la liste de `solve` ne montre pas nécessairement. Pour la matrice $M$, $\det M=x^2-1=(x-1)(x+1)$. ## Exercice 10 — Trouver une racine, minimiser et visualiser On considère $h(x)=x^3-4x+1$ et $q(x)=(x-1)^2+2$. 1. Vérifier que $h(0)$ et $h(1)$ sont de signes opposés. 2. Utiliser `scipy.optimize.brentq` pour trouver une racine de $h$ dans $[0,1]$. Afficher la valeur et le résidu $|h(x_*)|$. 3. Utiliser `minimize_scalar` pour minimiser $q$ sur $[-2,4]$. Comparer la valeur obtenue à celle prévue mathématiquement. 4. Tracer $h$ sur $[-3,3]$ avec Matplotlib, ajouter une ligne horizontale d'ordonnée zéro et marquer la racine calculée. 5. Écrire deux phrases expliquant la différence entre une racine et un minimum. --- #### Correction de l’exercice 10 ```python import numpy as np import matplotlib.pyplot as plt from scipy.optimize import brentq, minimize_scalar def h(x): return x**3 - 4*x + 1 def q(x): return (x - 1)**2 + 2 print(h(0), h(1)) # 1 -2 racine = brentq(h, 0, 1) print(racine, abs(h(racine))) res = minimize_scalar(q, bounds=(-2, 4), method='bounded') print(res.x, res.fun) # proche de 1 et 2 xs = np.linspace(-3, 3, 400) plt.plot(xs, h(xs), label='h(x)') plt.axhline(0, color='black', linewidth=0.8) plt.scatter([racine], [0], label='racine') plt.xlabel('x') plt.ylabel('h(x)') plt.legend() plt.grid(True) plt.show() ``` Comme $h(0)=1>0$ et $h(1)=-2<0$, le théorème des valeurs intermédiaires garantit une racine dans $]0,1[$. Numériquement, elle vaut environ **0,2541** et le résidu est proche de zéro. La fonction $q(x)=(x-1)^2+2$ atteint son minimum global **2 en $x=1$**. Une racine annule la fonction ; un minimum est une valeur la plus petite sur le domaine étudié et n'est pas nécessairement nulle. --- # 🗓️ Historique - Dernière MAJ : `27-octobre-2026` - Rédigé par : [[Antonia JABBOUR]] %% nav-debut : généré par gen_navigation.py, ne pas modifier à la main %% > [!nav] > ↩ [[Analyse et Données/Mathématiques/Programmation Scientifique/Programmation Scientifique|Accueil · Programmation Scientifique]] > > | [[Analyse et Données/Mathématiques/Programmation Scientifique/Cours 1 - Programmation Scientifique\|← Cours 1 - Programmation Scientifique]] | | > |:---|---:| %% nav-fin %%