diff --git "a/Chapitre 10 - Matrices orthogonales, matrices sym\303\251triques/Ch10_lib.py" "b/Chapitre 10 - Matrices orthogonales, matrices sym\303\251triques/Ch10_lib.py" index 5e29319..55f3b1f 100644 --- "a/Chapitre 10 - Matrices orthogonales, matrices sym\303\251triques/Ch10_lib.py" +++ "b/Chapitre 10 - Matrices orthogonales, matrices sym\303\251triques/Ch10_lib.py" @@ -1,1199 +1,1199 @@ import sys, os import numpy as np import sympy as sp from IPython.display import display, Latex, Markdown import plotly import plotly.graph_objects as go sys.path.append('../Chapitre 8 - Valeurs propres, vecteurs propres, diagonalisation') from Ch8_lib import * sys.path.append('../Librairie') import AL_Fct as al import ipywidgets as widgets from ipywidgets import interact_manual, Layout from sympy import sqrt import random def points_on_circle(n, center=np.array([0, 0])): theta = 2 * np.pi / n s_pts = np.zeros((n, 2)) e_pts = np.zeros((n, 2)) for i in range(n): s_pts[i] = [np.cos(i * theta) + center[0], np.sin(i * theta) + center[1]] e_pts[i] = [np.cos((i + 1) * theta) + center[0], np.sin((i + 1) * theta) + center[1]] return np.array(s_pts), np.array(e_pts) def plot_geom_2D(s_pts, e_pts, A): n = len(s_pts) if A.shape[0]!=2 or A.shape[1]!=2: raise ValueError("A should be a 2 by 2 numpy array") if n != len(e_pts): raise ValueError("start_points and end_points must have same length.") layout = go.Layout(yaxis=dict(scaleanchor="x", scaleratio=1)) display(Latex("On montre la transformation provoquée par la multiplication par la matrice $A = " + al.texMatrix(A) + "$ sur une figure géométrique.")) if np.allclose(A @ A.transpose(), np.eye(2)): display(Latex("La matrice $A = " + al.texMatrix(A) + "$ est une matrice orthogonale." )) else: display(Latex("La matrice $A = " + al.texMatrix(A) + "$ n'est pas une matrice orthogonale." )) fig = go.Figure(layout=layout) color = ['black', 'red', 'blue', 'green', 'yellow', 'brown', 'grey', 'cyan', 'orange', 'violet'] n_col = len(color) if n > n_col: color = ['blue'] n_col = 1 for i in range(n): a = np.array(s_pts[i]) b = np.array(e_pts[i]) a2 = A @ a b2 = A @ b if i == 0: show_legend = True else: show_legend = False fig.add_trace(go.Scatter(x=[a[0], b[0]], y=[a[1], b[1]], line=dict(color=color[i % n_col], width=2), mode='lines+markers', showlegend=show_legend, name='Original Figure')) fig.add_trace(go.Scatter(x=[a2[0], b2[0]], y=[a2[1], b2[1]], line=dict(color=color[i % n_col], width=2, dash='dot'), mode='lines+markers', showlegend=show_legend, name='Modified Figure')) fig.show() def is_orthogonal(A): A = sp.Matrix(A) n = A.shape[0] if n != A.shape[1]: raise ValueError("A should be a square matrix") display(Latex("On cherche à savoir si la matrice $A = " + latexp(A) + "$ est orthogonale en utilisant la " + "définition 1.")) if A.is_lower or A.is_upper: for i in range(n): if A[i, i] != 1 and A[i, i] != -1: display(Latex("Les valeurs propres de $A$ sont ses éléments diagonaux. Une des valeurs propres de " + "$A$ est différente de 1 ou -1. Il existe donc un vecteur propres $v$ de $A$ tel " + " que $A v = \lambda v$ avec $\lambda \\neq \pm 1$. Donc dans ce cas on a $\|A v\| " + "\\neq \|v\| $." )) return v = sp.zeros(n, 1) for i in range(n): symb_name = "x_" + str(i + 1) v[i] = sp.symbols(symb_name, real=True) b = A * v b_norm = b.norm() ** 2 v_norm = v.norm() ** 2 display(Latex("Il faut vérifier si $\|A v\| = \|v\|$ pour tout $v \in \mathbb{R}^" + str(n) + "$." + " On utilise le carré des normes pour s'affranchir des racines carrées.")) display(Latex("On calcule d'abord la norme au carré de $v$ et on obtient : $\|v\|^2 = " + sp.latex(v_norm) + "$.")) display(Latex("On calcule ensuite le produit $Av = " + latexp(b) + "$")) if sp.simplify(b_norm) != b_norm: display(Latex("On calcule la norme au carré de $Av$ et on obtient : $\|Av\|^2 = " + sp.latex(b_norm) + "=" + sp.latex(sp.simplify(b_norm)) + "$")) else: display(Latex("On calcule la norme au carré de $Av$ et on obtient : $\|Av\|^2 = " + sp.latex(b_norm) + "$")) if sp.simplify(b_norm - v_norm) == 0: display(Latex("Les normes sont toujours égales. La matrice est donc orthogonale.")) else: display(Latex("Les normes sont différente. La matrice n'est donc pas orthogonale.")) def interactive_is_orthogonal(A): A = sp.Matrix(A) n = A.shape[0] if n != A.shape[1]: raise ValueError("A should be a square matrix") display(Latex("La matrice suivante est-elle orthogonale ? $A = " + latexp(A) + "$")) answer = widgets.RadioButtons(options=["Oui", "Non"], description="Réponse : ", disabled=False) sol = A.transpose()*A - sp.eye(n) == sp.zeros(n) display(answer) def f(): if answer.value == "Oui": answer_bool = True else: answer_bool = False if answer_bool == sol: display(Latex("Correct !")) else: display(Latex("Incorrect")) interact_manual(f) display(Latex("Si vous n'arrivez pas à trouver la solution, vous pouvez afficher la solution détaillée en" " cliquant ici")) def f_sol(): is_orthogonal(A) im = interact_manual(f_sol) im.widget.children[0].description = 'Solution' def ortho_diag(A): A = sp.Matrix(A) n = A.shape[0] if A - A.transpose() != sp.zeros(n): display(Latex("La matrice $A = " + latexp(A) + "$ n'est pas orthogonalement diagonalisable car elle n'est pas " "symétrique.")) return eig = A.eigenvals() eig_list = list(eig.keys()) eig_list.sort() display(Latex("On cherche à diagonaliser orthogonalement la matrice $ A = " + latexp(A) + "$.")) default_value_P = str(np.ones(A.shape, dtype=np.int16).tolist()) default_value_P = default_value_P.replace("],", "],\n") default_value_D = str(np.ones(A.shape[0], dtype=np.int16).tolist()) answer_P = widgets.Text(value=default_value_P, description='P: ', disabled=False) answer_D = widgets.Text(value=default_value_D, description='D: ', disabled=False) display(Latex("Pour les réponses aux questions suivantes, ne faites pas d'approximations numériques, " "toutes les valeurs doivent être exactes. Pour les racines carrées " "(utiles pour normaliser les vecteurs), utilisez 'sqrt('votre valeur')', ex: sqrt(2).")) display(Latex("Donnez les éléments diagonaux de la matrice $D$")) display(answer_D) display(Latex("Donnez la matrice P permettant de diagonaliser orthogonalement la matrice $A$ de sorte que " "$ A = PDP^T = PDP^{-1} $")) display(answer_P) def f(): P_user = eval(answer_P.value) P_user = sp.Matrix(P_user) D_diag= eval(answer_D.value) D_diag_order = set(D_diag.copy()) D_diag_order = list(D_diag_order) D_diag_order.sort() D_user = sp.zeros(n) for i in range(n): D_user[i, i] = D_diag[i] D_user = sp.Matrix(D_user) if np.allclose(np.array(D_diag_order, dtype=np.float64), np.array(eig_list, dtype=np.float64)): if P_user*P_user.transpose()-sp.eye(n) == sp.zeros(n): if P_user * D_user * P_user.transpose() - A == sp.zeros(n): display(Latex("Correct !")) else: display(Latex("Incorrect. On a $PDP^{T}\\neq A$ avec $P = " + latexp(P_user) + "$ et $D =" + latexp(D_user) + "$.")) else: display(Latex("La matrice $P = " + latexp(P_user) + "$ n'est pas orthogonale, i.e. $P P^{T} \\neq I$.")) else: display(Latex("Incorrect. " "Les éléments diagonaux de $D$ ne correspondent pas aux valeurs propres de la matrice $A$.")) return interact_manual(f) def correction_ortho_diag(): display(Latex("Pour diagonaliser orthogonalement une matrice, on a applique la méthode donnée dans le cours et rappelée plus haut")) lamda = sp.symbols('lamda') poly_char_exp = sp.expand(A.charpoly(lamda).as_expr()) poly_char_fac = sp.factor(A.charpoly(lamda).as_expr()) poly_char_exp_str = sp.latex(poly_char_exp) poly_char_fac_str = sp.latex(poly_char_fac) display(Latex("Le polynôme caractéristique de $A$ s'exprime comme: $c_A (t) = " + poly_char_exp_str + "$.")) display(Latex("On trouve les racines du polynôme caractéristique et on factorise. On obtient $c_A (t) = " + poly_char_fac_str + "$.")) eig_list_ = list(eig.keys()) mult_list_ = list(eig.values()) display(Latex("On obtient donc les valeurs propres $\lambda$ et leur multiplicité respective $m$: " " $ \lambda = " + sp.latex(eig_list_) + " \hspace{10mm} m = " + sp.latex(mult_list_) + "$.")) display(Markdown("**On trouve une base orthonormée pour tous les espaces propres.**")) final_basis = [] P = sp.Matrix([]) k = 0 for l in eig_list_: basis_orthonorm = [] basis, basic_idx, free_idx = eigen_basis(A, l, prop_basis=None, disp=True, return_=True, dispA=False) for v in basis: if np.all(v == np.round(v)): v = v.astype(np.int16) basis_orthonorm.append(sp.Matrix(v)) basis_orthonorm = sp.GramSchmidt(basis_orthonorm, True) for v in basis_orthonorm: final_basis.append(v) P = P.col_insert(k, v) k+=1 display(Latex("On applique le procédé de Gram-Schmidt pour obtenir des vecteurs de norme 1 et orthogonaux. " "On obtient $" + sp.latex(basis_orthonorm) + "$")) display(Latex("Finalement, on construit la matrice $P$ en utilisant chaque vecteur de base " "trouvé précedemment comme colonne de cette matrice. Cette dernière est bien orthogonale car " "les vecteurs de base sont orthogonaux entre eux et tous de norme unitaire. " "On construit la matrice $D$ en plaçant les valeurs propres de $A$ sur la diagonale. " "Le placement des valeurs propres sur la diagonale de $D$ " "doit correspondre avec celui des vecteurs propres dans $P$.")) D = sp.zeros(n) i = 0 for idx in range(len(eig_list_)): for _ in range(mult_list_[idx]): D[i, i] = eig_list_[idx] i += 1 display(Latex("On obtient $P = " + latexp(P) + "\hspace{5mm} \mbox{et} \hspace{5mm} D = " + latexp(D) + "$")) display(Latex("On vérifie le résultat par un calcul :")) display(Latex("$P D P^T = "+ latexp(P) + latexp(D) + latexp(P.transpose()) + " = " + latexp(P*D*P.transpose()) + "=A$")) display(Latex("Si vous n'arrivez pas à résoudre l'exercice, vous pouvez afficher la solution étape " "par étape en cliquant ici.")) im = interact_manual(correction_ortho_diag) im.widget.children[0].description = 'Solution' def nl(): return "$$ $$" def sol_is_ortho(A): A = sp.Matrix(A) n = A.shape[0] if n != A.shape[1]: display(Latex("La matrice $A = " + latexp(A) + "$ n'est pas carrée !")) return display(Latex("Pour vérifier si la matrice $A$ est orthogonale, on effectue dans cette correction " "le produit de $A$ et de sa transposée $A^T$." " On vérifie ensuite si l'on obtient la matrice identité.")) if sp.simplify(A*A.transpose()-sp.eye(n)) == sp.zeros(n): display(Latex("On a que : $A A^T = " + latexp(A) + latexp(A.transpose()) + "= " + latexp(sp.eye(n)) + "= I_n $")) display(Latex("La matrice $A$ est donc orthogonale.")) else: display(Latex("On a que : $A A^T = " + latexp(A) + latexp(A.transpose()) + "\\neq I_n $")) display(Latex("Par consequent, la matrice $A$ n'est pas orthogonale.")) return def is_ortho_10_2(A): A = sp.Matrix(A) n = A.shape[0] display(Latex("La matrice $A =" + latexp(A) + "$ est-elle orthogonale ?")) answer = widgets.RadioButtons(options=["Oui", "Non"], description="Réponse : ", disabled=False) display(answer) sol = sp.simplify(A * A.transpose() - sp.eye(n)) == sp.zeros(n) if n != A.shape[1]: sol = False def f(): if answer.value == "Oui": answer_bool = True else: answer_bool = False if answer_bool == sol: display(Latex("Correct !")) else: display(Latex("Incorrect")) if sol: display(Latex("La matrice $A$ est orthogonale.")) else: display(Latex("La matrice $A$ n'est pas orthogonale.")) interact_manual(f) def f_sol(): sol_is_ortho(A) display(Latex("Pour afficher la solution, cliquez-ici.")) im = interact_manual(f_sol) im.widget.children[0].description = 'Solution' def vf_10_2_2(): display(Latex("Soit $A \in M_{n \\times n}(\mathbb{R})$ une matrice orthogonale. On construit la matrice" " $B \in M_{n \\times n}(\mathbb{R})$ en réarrangeant les colonnes de $A$ dans un ordre quelquonque. " "La matrice $B$ est aussi orthogonale.")) answer = widgets.RadioButtons(options=["Vrai", "Faux"], description="Réponse : ", disabled=False) display(answer) def f(): if answer.value=="Vrai": display(Latex("Correct ! En effet, si l'ordre des colonnes est modifié, l'ensemble de ces dernières reste " "toujours une base orthonormée de l'espace $V$. " "La proposition (5) est donc vérifiée pour la matrice $B$.")) else: display(Latex("Incorrect, changez votre réponse !")) interact_manual(f) def vf_10_2_3(): display(Latex("Soit $A \in M_{n \\times n}(\mathbb{R})$ une matrice orthogonale. On construit la matrice" " $B \in M_{n \\times n}(\mathbb{R})$ en réarrangeant les lignes de $A$ dans un ordre quelquonque. " "La matrice $B$ est aussi orthogonale.")) answer = widgets.RadioButtons(options=["Vrai", "Faux"], description="Réponse : ", disabled=False) display(answer) def f(): if answer.value=="Vrai": display(Latex("Correct ! En effet, si l'ordre des lignes est modifié, l'ensemble de ces dernières reste " "toujours une base orthonormée de l'espace $V$. " "La proposition (4) est donc vérifiée pour la matrice $B$.")) else: display(Latex("Incorrect, changez votre réponse !")) interact_manual(f) def vf_10_2_1(): display(Latex("Soit $A \in M_{n \\times n}(\mathbb{R})$ une matrice orthogonale. $A^T$ est aussi orthogonale.")) answer = widgets.RadioButtons(options=["Vrai", "Faux"], description="Réponse : ", disabled=False) display(answer) def f(): if answer.value == "Vrai": display(Latex("Correct ! En effet, comme $A$ est orthogonale, on sait que ses colonnes forment " "une base orthonormée de l'espace $V$. Comme les lignes de $A^T$ sont les colonnes de $A$, " "alors la proposition (4) est vérifiée pour $A^T$, i.e. les lignes de $A^T$ forment une base " "orthonormée de $V$. $A^T$ est donc une matrice orthogonale.")) else: display(Latex("Incorrect, changez votre réponse !")) interact_manual(f) def vf_10_2_4(): display(Latex("Soit $A \in M_{n \\times n}(\mathbb{R})$ une matrice ayant une valeur propre $\lambda = 2$. " "Il est possible que $A$ soit orthogonale.")) answer = widgets.RadioButtons(options=["Vrai", "Faux"], description="Réponse : ", disabled=False) display(answer) def f(): if answer.value == "Faux": display(Latex("Correct ! Etant donné que $A$ a pour valeur propre $\lambda=2$, cela signifique qu'il existe " "un vecteur $v$ tel que $A v = 2 v$. Donc la matrice $A$ ne conserve pas toujours les longeurs lors " "d'une multiplication avec un vecteur. La proposition (1) n'est pas vérifiée." " $A$ ne peut donc pas être orthogonale.")) else: display(Latex("Incorrect, changez votre réponse !")) interact_manual(f) def vf_10_4_1(): display(Markdown("**Dire si l'affirmation suivante est vraie ou fausse.**")) display(Latex("La matrice $A$ est orthogonalement diagonalisable.")) n_pos = [3, 4, 5] n = random.choice(n_pos) sym = bool(random.getrandbits(1)) A = sp.randMatrix(n, symmetric=sym, percent=70, min=-20, max=20) if sp.simplify(A-A.transpose())==sp.zeros(n): sol = True else: sol = False display(Latex("$A = " + latexp(A) + "$")) answer = widgets.RadioButtons(options=["Vrai", "Faux"], description="Réponse : ", disabled=False) display(answer) def f(): if answer.value == "Vrai": answer_value = True else: answer_value = False if answer_value == sol: display(Latex("Correct ! ")) if sol: display(Latex("La matrice $A$ est orthogonalement diagonalisable car elle est symétrique.")) else: display(Latex("La matrice $A$ n'est pas orthogonalement diagonalisable car elle n'est pas symétrique.")) else: display(Latex("Incorrect, changez votre réponse.")) interact_manual(f) def quest_10_4_2(): A = sp.Matrix([[2, -4], [1, -1]]) charpol = A.charpoly().as_expr() * -1 display(Latex("Soit $A$ une matrice carrée ayant pour polynome caractéristique $c_A(\lambda) = " + sp.latex(charpol) + "$")) display(Latex("La matrice $A$ est-elle symétrique ?")) answer = widgets.RadioButtons(options=["Oui", "Pas nécessairement", "Non"], description="Réponse : ", disabled=False) display(answer) sol = "Non" def f(): if answer.value == sol: display(Latex("Correct !")) else: display(Latex("Incorrect.")) interact_manual(f) def f_sol(): display(Latex("Le polynome caractéristique de $A$ admet une ou des racine(s) non réelle(s). " "Donc la matrice $A$ ne peut pas être symétrique.")) display(Latex("Pour afficher la solution détaillée, cliquez-ici.")) im = interact_manual(f_sol) im.widget.children[0].description = 'Solution' def quest_10_4_3(): # Non symmetric values A = sp.Matrix([[0, -1, -4], [0, 0, -2], [0, -2, 0]]) charpol = A.charpoly().as_expr()*-1 charpol = charpol.factor() display(Latex("Soit $A$ une matrice carrée ayant pour polynome caractéristique $c_A(\lambda) = " + sp.latex(charpol) + "$")) display(Latex("La matrice $A$ est-elle symétrique ?")) answer = widgets.RadioButtons(options=["Oui", "Pas nécessairement", "Non"], description="Réponse : ", disabled=False) display(answer) sol = "Pas nécessairement" def f(): if answer.value == sol: display(Latex("Correct !")) else: display(Latex("Incorrect.")) interact_manual(f) def f_sol(): A2 = sp.Matrix([[0, 0, 0], [0, 2, 0], [0, 0, -2]]) display(Latex("On sait que le polynome caractéristique d'une matrice symétrique admet uniquement des racines réelles. " "Or la réciproque est fausse, i.e., les polynomes caractéristiques à racines réelles peuvent être issus de matrices non symétriques. " + nl() + "On voit que le polynome caractéristique $c_A(\lambda)$ a uniquement des racines réelles mais cela ne suffit pas pour conclure que $A$ est symétrique. ")) display(Latex("La bonne réponse est donc: Pas nécessairement")) display(Latex("Dans ce cas précis, les matrices $A_1 =" + latexp(A) + "$ et $A_2 = " + latexp(A2) + "$ ont " "toutes les deux $c_A(\lambda)$ pour polynome caractéristique. On voit que une des matrices est symétrique et l'autre pas.")) display(Latex("Pour afficher la solution détaillée, cliquez-ici.")) im = interact_manual(f_sol) im.widget.children[0].description = 'Solution' def exo_2_1_10_4(): v1 = sp.Matrix([3, 4]) v2 = sp.Matrix([-1, 1/2]) display(Latex("La matrice $A \in M_{2 \\times 2}$ ( $\mathbb{R}$ ) a pour valeur propres $3$ et $-2$. " "$" + latexp(v1) + "\mbox{ et } " + latexp(v2) + "$ sont des vecteurs propres de $A$ respectivement " "associés aux valeurs propres $3$ et $-2$. ")) display(Latex("La matrice $A$ est-elle symétrique ?")) answer = widgets.RadioButtons(options=["Oui", "Non", "Manque d'informations"], description="Réponse : ", disabled=False) display(answer) sol = "Non" def f(): if answer.value == sol: display(Latex("Correct !")) else: display(Latex("Incorrect.")) interact_manual(f) def f_sol(): display(Latex("Les vecteurs propres $" + latexp(v1) + "$ et $" + latexp(v2) + "$ sont associés à des valeurs propres différentes mais ne sont pas orthogonaux. " "La matrice $A$ ne peut donc pas être symétrique.")) display(Latex("Pour afficher la solution détaillée, cliquez ici.")) im = interact_manual(f_sol) im.widget.children[0].description = 'Solution' def exo_2_2_10_4(): v1 = sp.Matrix([1, 0, 2]) v2 = sp.Matrix([-2, 0, -4]) v3 = sp.Matrix([2, 5, -1]) display(Latex("La matrice $A \in M_{3 \\times 3}$ ( $\mathbb{R}$ ) a pour valeur propres $4$ et $2$. " "$" + latexp(v1) + ", " + latexp(v2) + "\mbox{ et } " + latexp(v3) + "$ sont des vecteurs propres de $A$ respectivement " "associés aux valeurs propres $4$, $4$ et $-2$. ")) display(Latex("La matrice $A$ est-elle symétrique ?")) opt = ["Oui", "Non", "Manque d'informations"] answer = widgets.RadioButtons(options=opt, description="Réponse : ", disabled=False) display(answer) sol = opt[2] def f(): if answer.value == sol: display(Latex("Correct !")) else: display(Latex("Incorrect.")) interact_manual(f) def f_sol(): display(Latex("Les vecteurs propres associés à des valeurs propres différentes sont bien orthogonaux. " "Mais on ne connait pas la dimension de l'espace propre associé à $\lambda = 4$ car les deux " "vecteurs donnés dans l'énoncé, $ " + latexp(v1) + " \mbox{ et } " + latexp(v2) + "$, " "sont colinéaires." + nl() + "Pour connaitre la dimension de cette espace, il faudrait une base de ce dernier. " "Cela permettrait de vérifier si la dimension de l'espace est de 2 et si tous les vecteurs de " "cet espace sont orthogonaux aux vecteurs composant l'espace propre associé à $\lambda = 2$. " "Si ces deux conditions étaient vérifiées, la matrice $A$ serait symétrique.")) display(Latex("Il manque donc d'informations pour répondre.")) display(Latex("Pour afficher la solution détaillée, cliquez ici.")) im = interact_manual(f_sol) im.widget.children[0].description = 'Solution' def exo_2_3_10_4(): v1 = sp.Matrix([1, 0, 1]) v2 = sp.Matrix([-2, 0, 1]) v3 = sp.Matrix([0, 3, 0]) display(Latex("La matrice $A \in M_{3 \\times 3}$ ( $\mathbb{R}$ ) a pour valeur propres $-1$, $2$ et $4$. " "$" + latexp(v1) + ", " + latexp(v2) + "\mbox{ et } " + latexp(v3) + "$ sont des vecteurs propres de $A$ respectivement " "associés aux valeurs propres $-1$, $2$ et $4$. ")) display(Latex("La matrice $A$ est-elle symétrique ?")) opt = ["Oui", "Non", "Manque d'informations"] answer = widgets.RadioButtons(options=opt, description="Réponse : ", disabled=False) display(answer) sol = opt[1] def f(): if answer.value == sol: display(Latex("Correct !")) else: display(Latex("Incorrect.")) interact_manual(f) def f_sol(): display(Latex("Les vecteurs propres $" + latexp(v1) + "$ et $" + latexp(v2) + "$ sont associés à des valeurs propres différentes mais ne sont pas orthogonaux. " "La matrice $A$ ne peut donc pas être symétrique.")) display(Latex("Pour afficher la solution détaillée, cliquez ici.")) im = interact_manual(f_sol) im.widget.children[0].description = 'Solution' def exo_2_4_10_4(): v1 = sp.Matrix([1, 0, 1]) v2 = sp.Matrix([-2, 0, 1]) v3 = sp.Matrix([0, 3, 0]) display(Latex("La matrice $A \in M_{3 \\times 3}$ ( $\mathbb{R}$ ) a pour valeur propres $-1$, et $4$. " "$" + latexp(v1) + ", " + latexp(v2) + "\mbox{ et } " + latexp(v3) + "$ sont des vecteurs propres de $A$ respectivement " "associés aux valeurs propres $-1$, $-1$ et $4$. ")) display(Latex("La matrice $A$ est-elle symétrique ?")) opt = ["Oui", "Non", "Manque d'informations"] answer = widgets.RadioButtons(options=opt, description="Réponse : ", disabled=False) display(answer) sol = opt[0] def f(): if answer.value == sol: display(Latex("Correct !")) else: display(Latex("Incorrect.")) interact_manual(f) def f_sol(): base_1 = sp.GramSchmidt([v1, v2], orthonormal=True) base_4 = sp.GramSchmidt([v3], orthonormal=True) display(Latex("Les vecteurs propres $" + latexp(v1) + "$ et $" + latexp(v2) + "$ associés à $\lambda = -1$ sont linéairement indépendant " "et orthogonaux au vecteur propre $" + latexp(v3) + "$ associé à $\lambda = 4$. " "On peut utiliser ces deux ensembles de vecteurs propres pour former des bases des espaces propres " "associés à $\lambda = -1$ et $\lambda = 4$, respectivement. " "On peut othogonaliser et normaliser ces bases avec le schema de Gram-Schmidt.")) display(Latex("On obtient les bases des espaces propres associées respectivement à $\lambda = -1$ et $\lambda = 4$")) display(Latex("$ " + latexp(base_1[0]) + ", " + latexp(base_1[1]) + "\hspace{15mm} " + latexp(base_4[0]) + "$")) display(Latex("On voit que l'espace $V = \mathbb{R}^3$ possède une base othornormée composée de vecteurs propres de $A$. " "La transformation associée à la matrice $A$ est donc orthogonalement diagonalisable et $A$ est donc symétrique.")) display(Latex("Pour afficher la solution détaillée, cliquez ici.")) im = interact_manual(f_sol) im.widget.children[0].description = 'Solution' def ex_3_1_10_4(): l_1 = 2 l_2 = -1 v_1 = sp.Matrix([1, -1]) display(Latex("Trouver une matrice $A \in M_{2 \\times 2} (\mathbb{R})$ symétrique ayant pour valeurs propres 2 " "et -1 et $" + latexp(v_1) + "$ comme vecteur propres associé à $\lambda = 2$.")) default_value_A = str(np.ones((2, 2), dtype=np.int16).tolist()) answer = widgets.Text(value=default_value_A, description='A: ', disabled=False, display='flex') display(answer) def f(): A = eval(answer.value) A = sp.Matrix(A) if A.shape[0] != 2 or A.shape[1] != 2: display(Latex("Incorrect, la matrice doit être carrée et 2x2.")) return if sp.simplify(A - A.transpose()) != sp.zeros(2): display(Latex("Incorrect, la matrice n'est pas symétrique !")) return eig = A.eigenvals() eig_list = list(eig.keys()) if l_1 not in eig_list or l_2 not in eig_list: if l_1 not in eig_list: display(Latex("Incorrect, 2 n'est pas une valeur propre de la matrice rentrée.")) if l_2 not in eig_list: display(Latex("Incorrect, -1 n'est pas une valeur propre de la matrice rentrée.")) return A_v = A*v_1 if sp.simplify(A_v - l_1 * v_1) != sp.zeros(2, 1): display(Latex("Incorrect, le vecteur $" + latexp(v1) + "$ n'est pas un vecteur propre de $A$ associé à " "la valeur propre $\lambda = 2$.")) return display(Latex("Correct !")) interact_manual(f) def f_sol(): display(Latex("On utilise la formule $A=PDP^T$ pour constuire la matrice symetrique $A$. $P$ est une matrice " "orthogonale composée de vecteurs propres de $A$ et $D$ est une matrice diagonale avec les valeurs" " propres de $A$ sur la diagonale. ")) D = sp.Matrix([[2, 0],[0, -1]]) display(Latex("On construit simplement la matrice $D =" + latexp(D) + "$")) v_2 = sp.Matrix([1, 1]) display(Latex("On ne dispose pas d'informations sur les vecteurs propres associés à $\lambda = -1$. " "Mais pour obtenir une matrice symétrique on sait que $P$ est orthogonale, " "donc il faut trouver un vecteur propre orthogonal à $" + latexp(v_1) + "$. Simplement, on prend $" + latexp(v_2) + "$.")) display(Latex("On normalise les deux vecteurs propres orthogonaux et on forme les colonnes de $P$. " "Chaque vecteur est placé dans la même colonne que sa valeur propre correspondante dans $D$.")) P = sp.Matrix([[1, 1], [-1, 1]])/sp.sqrt(2) display(Latex("On obtient $P = " + latexp(P) + "$.")) A = P*D*P.transpose() display(Latex("$A = P D P^T = " + latexp(P) + latexp(D) + latexp(P.transpose()) + "=" + latexp(A) +"$")) display(Latex("Pour afficher la solution détaillée, cliquez ici.")) im = interact_manual(f_sol) im.widget.children[0].description = 'Solution' def ch10_7_ex_1_1(): x1, x2, x3, x4 = sp.symbols("x_1 x_2 x_3 x_4") Q = x1**2 - 5*x1*x2 + x1*x3 - x1*x4 + 4*x2**2 + 2*x2*x3 - 4*x2*x4 + x3**2 + x3*x4 - x4**2 sol = "Oui" display(Latex("Le polynôme $Q = " + sp.latex(Q) + "$ est-il une forme quadratique ?")) answer = widgets.RadioButtons(options=["Oui", "Non"], description="Réponse : ", disabled=False) display(answer) def f(): if answer.value == sol: display(Latex("Correct ! ")) display(Latex("Le polynôme $Q = "+ sp.latex(Q) + "$ est une forme quadratique car " "il contient uniquement des termes d'ordre 2.")) else: display(Latex("Incorrect, changez votre réponse.")) interact_manual(f) def ch10_7_ex_1_2(): x1, x2, x3 = sp.symbols("x_1 x_2 x_3") Q = (x1 + x2)**2 + (x3 - 1)**2 sol = "Non" display(Latex("Le polynôme $Q = " + sp.latex(Q) + "$ est-il une forme quadratique ?")) answer = widgets.RadioButtons(options=["Oui", "Non"], description="Réponse : ", disabled=False) display(answer) def f(): if answer.value == sol: display(Latex("Correct ! ")) display(Latex("Le polynôme $Q = " + sp.latex(Q) + "$ n'est pas une forme quadratique car " "il ne contient pas uniquement des termes d'ordre 2.")) display(Latex("On peut developper $Q$ pour expliciter l'ordre de chaque terme.")) display(Latex("$$ Q = " + sp.latex(Q.expand()) + "$$")) else: display(Latex("Incorrect, changez votre réponse.")) interact_manual(f) def ch10_7_ex_1_3(): x1, x2, x3 = sp.symbols("x_1 x_2 x_3") Q = 3 * x1 ** 2 + x1 * x2 + x2 ** 2 + 2 * x2 * x3 - x1 * x3 + x3 ** 3 sol = "Non" display(Latex("Le polynôme $Q = " + sp.latex(Q) + "$ est-il une forme quadratique ?")) answer = widgets.RadioButtons(options=["Oui", "Non"], description="Réponse : ", disabled=False) display(answer) def f(): if answer.value == sol: display(Latex("Correct ! ")) display(Latex("Le polynôme $Q = " + sp.latex(Q) + "$ n'est pas une forme quadratique car " "il ne contient pas uniquement des termes d'ordre 2.")) else: display(Latex("Incorrect, changez votre réponse.")) interact_manual(f) def ch10_7_ex_2(n): x1, x2, x3, x4, x5 = sp.symbols("x_1 x_2 x_3 x_4 x_5") x_list = [sp.Matrix([x1, x2]), sp.Matrix([x1, x2, x3]), sp.Matrix([x1, x2, x3, x4])] A1 = sp.Matrix([[-2, 3], [3, 4]]) A2 = sp.Matrix([[1, 0, 3], [0, -2, -1/4], [3, -1/4, 4]]) A3 = sp.Matrix([[-1, -1, 3, 0], [-1, -2, 0, 4], [3, 0, 0, -1], [0, 4, -1, 2]]) A_list = [A1, A2, A3] A = A_list[n-1] x = x_list[n-1] Q = x.dot(A * x).expand() display(Latex("Rentrez la matrice symétrique $A$ telle que $\mathbf{x}^T A \mathbf{x} = Q(\mathbf{x})$ " "avec $\mathbf{x} = " + latexp(x) + "$")) display(Latex("où $Q(\mathbf{x}) = " + sp.latex(Q) + "$")) default_value_A = str(np.ones(A.shape, dtype=np.int16).tolist()) answer = widgets.Text(value=default_value_A, description='A = ', disabled=False, layout=widgets.Layout(width='600px')) display(answer) def f(): A_user = eval(answer.value) A_user = sp.Matrix(A_user) display(Latex("Vous avez rentré la matrice $A_{user} = " + latexp(A_user) + "$")) if A_user.shape != A.shape: display(Latex("Incorrect, la matrice $A$ n'a pas les bonnes dimensions.")) return if sp.simplify(A_user - A_user.transpose()) != sp.zeros(A.shape[0]): display(Latex("Incorrect, la matrice rentrée n'est pas symétrique. ")) return Q_user = x.dot(A_user * x).expand() if sp.simplify(Q_user - Q) == 0: display(Latex("Correct !")) else: display(Latex("Incorrect, avec votre matrice on obtient $ Q_{user}(\mathbf{x}) = " + sp.latex(Q_user) + "\\neq Q(\mathbf{x}) $")) interact_manual(f) def f_sol(): display(Latex("La matrice $A = " + latexp(A) + "$ est telle que $Q(\mathbf{x}) = \mathbf{x}^T A \mathbf{x} $")) display(Latex("Pour afficher la solution, cliquez ici.")) im = interact_manual(f_sol) im.widget.children[0].description = 'Solution' def plot_quad(A, n_points=50j, lims=2): A_sp = sp.Matrix(A) eig = A_sp.eigenvects() # Create list with principal axis p_axis = [] for x in eig: v_list = sp.GramSchmidt(x[2], orthonormal=True) for v in v_list: p_axis.append(np.array(v).astype(np.float64).flatten()) A = np.array(A) if A.shape == (3, 3): X, Y, Z = np.mgrid[-lims:lims:n_points, -lims:lims:n_points, -lims:lims:n_points] # ellipsoid values = A[0, 0] * X * X + A[1, 1] * Y * Y + A[2, 2] * Z * Z + (A[0, 1]+A[1, 0])* X * Y + (A[0, 2] + A[2, 0]) * X * Z + (A[1, 2] + A[1, 2]) * Y * Z layout = go.Layout(scene=go.layout.Scene(aspectmode="cube")) fig = go.Figure(data=go.Isosurface( x=X.flatten(), y=Y.flatten(), z=Z.flatten(), value=values.flatten(), isomin=1, isomax=1, showscale=False, opacity=0.5, caps=dict(x_show=False, y_show=False), showlegend=True, name='Quadrique' ), layout=layout) fig.add_trace(go.Scatter3d(x=[0, p_axis[0][0]], y=[0, p_axis[0][1]], z=[0, p_axis[0][2]], line=dict(width=2, color='black'), mode='lines+markers', marker=dict(size=3), showlegend=True, name='Axes principaux', )) fig.add_trace(go.Scatter3d(x=[0, p_axis[1][0]], y=[0, p_axis[1][1]], z=[0, p_axis[1][2]], line=dict(width=2, color='black'), mode='lines+markers', marker=dict(size=3), showlegend=False, name='Principal axis 2', )) fig.add_trace(go.Scatter3d(x=[0, p_axis[2][0]], y=[0, p_axis[2][1]], z=[0, p_axis[2][2]], mode='lines+markers', line=dict(width=2, color='black'), marker=dict(size=3), showlegend=False, name='Principal axis 3', )) elif A.shape == (2, 2): X, Y = np.mgrid[-lims:lims:n_points, -lims:lims:n_points] Z = A[0, 0] * X * X + A[1, 1] * Y * Y + (A[1, 0] + A[0, 1]) * X * Y fig = go.Figure(data=go.Contour( x=X[:, 0], y=Y[0, :], z=Z, contours=dict(start=1, end=1), contours_coloring='lines', line_width=2, opacity=0.5, showscale=False, showlegend=True, name='Quadrique'), ) fig.update_layout(xaxis=dict(range=[-lims, lims], constrain="domain",), yaxis = dict(scaleanchor= "x", scaleratio = 1,)) fig.add_trace(go.Scatter(x=[0, p_axis[0][0]], - y=[0, p_axis[0][1]], - mode='lines+markers', - line=dict(width=2, color='black'), - marker=dict(size=3), - showlegend=True, - name='Axes principaux', - )) + y=[0, p_axis[0][1]], + mode='lines+markers', + line=dict(width=2, color='black'), + marker=dict(size=3), + showlegend=True, + name='Axes principaux', + )) fig.add_trace(go.Scatter(x=[0, p_axis[1][0]], y=[0, p_axis[1][1]], mode='lines+markers', line=dict(width=2, color='black'), marker=dict(size=3), showlegend=False, name = 'Principal axis 2', )) else: return fig.show() def ex1_10_8(n): A_list = [sp.Matrix([[5, 2], [2, 5]]), sp.Matrix([[3, 2, 0], [2, 0, 0], [0, 0, 2]])] x1, x2, x3 = sp.symbols("x_1 x_2 x_3") x_list = [sp.Matrix([x1, x2]), sp.Matrix([x1, x2, x3])] A = A_list[n-1] x = x_list[n-1] Q = x.dot(A * x).expand() display(Latex("$Q(x) = " + sp.latex(Q) + "$")) display(Latex("Donnez les coefficients $d_1, ..., d_n$ et la matrice $S$ de changement de variable tel " "que $x = Sy$. ")) default_value_d = str(np.ones(A.shape[0], dtype=np.int16).tolist()) default_value_S = str(np.ones(A.shape, dtype=np.int16).tolist()) answer_d = widgets.Text(value=default_value_d, description='d = ', disabled=False, layout=widgets.Layout(width='600px')) answer_S = widgets.Text(value=default_value_S, description='S = ', disabled=False, layout=widgets.Layout(width='600px')) display(answer_d) display(answer_S) def f(): d_user = eval(answer_d.value) d_user = np.array(d_user, dtype=np.int16) D_user = np.diag(d_user) D_user = sp.Matrix(D_user) S_user = eval(answer_S.value) S_user = sp.Matrix(S_user) display(Latex("Vous avez rentré la matrice suivante: $S_{user} = " + latexp(S_user) + "$")) A_user = S_user * D_user * S_user.transpose() if sp.simplify(A-A_user) == sp.zeros(A.shape[0]): display(Latex("Correct !")) else: display(Latex("Incorrect.")) interact_manual(f) def f_sol(): display(Latex("On commence par trouver la matrice symétrique $A$ telle que $Q(x) = x^T A x$ avec " "$x = " + latexp(x) + "$.")) display(Latex("Par identification, on trouve $A = " + latexp(A) + "$.")) display(Latex("Ensuite, l'exercice consiste simplement à diagonaliser orthogonalement la matrice symétrique " "$A$.")) display(Latex("En effet, si on a $D = S^T A S$, avec $D$ diagonale, en effectuant le changement de variable " "$x = Sy$, on obtient : " + nl() + " $Q(x) = x^T A x = (Sy)^T A (Sy) = y^T S^T A S y = y^T D y = " "d_1 y_1^2 + d_2 y_2^2 + ... + d_n y_n^2 $ ")) lamda = sp.symbols('lamda') poly_char_exp = sp.expand(A.charpoly(lamda).as_expr()) poly_char_fac = sp.factor(A.charpoly(lamda).as_expr()) poly_char_exp_str = sp.latex(poly_char_exp) poly_char_fac_str = sp.latex(poly_char_fac) display(Latex("Le polynôme caractéristique de $A$ s'exprime comme: $c_A (t) = " + poly_char_exp_str + "$.")) display(Latex("On trouve les racines du polynôme caractéristique et on factorise. On obtient $c_A (t) = " + poly_char_fac_str + "$.")) eig = A.eigenvals() eig_list_ = list(eig.keys()) mult_list_ = list(eig.values()) display(Latex("On obtient donc les valeurs propres $\lambda$ et leur multiplicité respective $m$: " " $ \lambda = " + sp.latex(eig_list_) + " \hspace{10mm} m = " + sp.latex(mult_list_) + "$.")) display(Markdown("**On trouve une base orthonormée pour tous les espaces propres.**")) final_basis = [] P = sp.Matrix([]) k = 0 for l in eig_list_: basis_orthonorm = [] basis, basic_idx, free_idx = eigen_basis(A, l, prop_basis=None, disp=True, return_=True, dispA=False) for v in basis: # if all ints if np.all(v == np.round(v)): v = v.astype(np.int16) # if there is some float else: for idx, el in enumerate(v): v = sp.Matrix(v) q = sp.Rational(el) # If rational are not too high and weird if abs(q.numerator()) < 100 and abs(q.denominator()) < 100: v[idx] = q basis_orthonorm.append(sp.Matrix(v)) basis_orthonorm = sp.GramSchmidt(basis_orthonorm, True) for v in basis_orthonorm: final_basis.append(v) P = P.col_insert(k, v) k += 1 display( Latex("On applique le procédé de Gram-Schmidt pour obtenir des vecteurs de norme 1 et orthogonaux. " "On obtient $" + sp.latex(basis_orthonorm) + "$")) display(Latex("Finalement, on construit la matrice $S$ en utilisant chaque vecteur de base " "trouvé précedemment comme colonne de cette matrice. Cette dernière est bien orthogonale car " "les vecteurs de base sont orthogonaux entre eux et tous de norme unitaire. " "On construit la matrice $D$ en plaçant les valeurs propres de $A$ sur la diagonale. " "Le placement des valeurs propres sur la diagonale de $D$ " "doit correspondre avec celui des vecteurs propres dans $S$.")) D = sp.zeros(A.shape[0]) i = 0 for idx in range(len(eig_list_)): for _ in range(mult_list_[idx]): D[i, i] = eig_list_[idx] i += 1 display( Latex("On obtient $S = " + latexp(P) + "\hspace{5mm} \mbox{et} \hspace{5mm} D = " + latexp(D) + "$")) display(Latex("On vérifie le résultat par un calcul :")) display(Latex( "$S D S^T = " + latexp(P) + latexp(D) + latexp(P.transpose()) + " = " + latexp(P * D * P.transpose()) + "=A$")) display(Latex("Pour afficher la solution, cliquez ici.")) im = interact_manual(f_sol) im.widget.children[0].description = 'Solution' def f_plot(): A_np = np.array(A, dtype=np.float64) plot_quad(A_np) if A.shape[0]==2 or A.shape[0]==3: display(Latex("Pour visualiser la quadrique associée et les axes principaux, cliquez ici")) im = interact_manual(f_plot) im.widget.children[0].description = 'Plot' \ No newline at end of file diff --git "a/Chapitre 2 - Algebre matricielle/2.10 D\303\251composition LU (applications aux syst\303\250mes lin\303\251aires).ipynb" "b/Chapitre 2 - Algebre matricielle/2.10 D\303\251composition LU (applications aux syst\303\250mes lin\303\251aires).ipynb" index 9fd0dd8..accab3a 100644 --- "a/Chapitre 2 - Algebre matricielle/2.10 D\303\251composition LU (applications aux syst\303\250mes lin\303\251aires).ipynb" +++ "b/Chapitre 2 - Algebre matricielle/2.10 D\303\251composition LU (applications aux syst\303\250mes lin\303\251aires).ipynb" @@ -1,372 +1,311 @@ { "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ - "###### Concept(s)-clé(s) et théorie\n", + "# Concept(s)-clé(s) et théorie\n", "\n", "## APPLICATION DE LA DÉCOMPOSITION AUX SYSTÈMES LINÉAIRES \n", "Soit un système d'équations linéaires aux inconnues $x_1, \\dots, x_n$, représenté sous forme matricielle $A\\vec{x}=\\vec{b}$. Supposons que $A=LU$ où $L$ est triangulaire inférieure et $U$ est un forme échelonnée. Alors on résout le système de la manière suivante.\n", "\n", "1. Poser $Y = (y_1, y_2, \\dots, y_n)^T$;\n", "2. Résoudre le système $LY=b$ avec la méthode de substitution en avant;\n", "3. Résoudre le sytème $Ux=y$ avec la méthode de substitution en arrière;" ] }, { "cell_type": "code", - "execution_count": 1, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - " \n", - " " - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - " \n", - " " - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ "import Librairie.AL_Fct as al\n", "import Corrections.corrections as corrections\n", "from ipywidgets import interact_manual\n", "import numpy as np\n", "import time\n", "from scipy.linalg import solve_triangular\n", "import matplotlib.pyplot as plt" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Exercise 1\n", "\n", "Considerez le système linéaire $Ax=b$, avec $A$ et $b$ donné par:\n", "\n", "\\begin{equation}\n", "A =\n", "\\begin{pmatrix}\n", "1 & -1 & 0 \\\\\n", "2 & 0 & 1 \\\\\n", "1 & 1 & 1 \n", "\\end{pmatrix}\n", "\\qquad b = \n", "\\begin{pmatrix}\n", "2 \\\\\n", "1 \\\\\n", "-1 \n", "\\end{pmatrix}\n", "\\end{equation}\n", "\n", "**Sans calculer aucune décomposition LU ni résoudre explicitement le système**, lesquelles des affirmations suivantes sont clairement correctes? [Exécutez la cellule suivante]" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "corrections.Ex1Chapitre2_10()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Exercise 2 \n", "\n", "Considerez le système linéaire $Ax=b$ avec $A \\in \\mathcal{M}_{4 \\times 4}(\\mathbb{R})$ et $b \\in \\mathcal{M}_{4 \\times 1}(\\mathbb{R})$ donnés par:\n", "\n", "\\begin{equation}\n", "A = \n", "\\begin{pmatrix}\n", "1 & 0 & -1 & -2 \\\\\n", "0 & -2 & -2 & 1 \\\\\n", "1 & 2 & 2 & 1 \\\\\n", "0 & 1 & 1 & -1\n", "\\end{pmatrix}\n", "\\qquad b = \n", "\\begin{pmatrix}\n", "1 \\\\\n", "-2 \\\\\n", "1 \\\\\n", "0\n", "\\end{pmatrix}.\n", "\\end{equation}\n", "\n", "En utilisation la décomposition LU de A, résolvez, si possible, le système linéaire." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#Entrez les coefficients de A et b\n", "A=[[1,0,-1,-2], [0,-2,-2,1], [1,2,2,1], [0,1,1,-1]]\n", "b = [[1], [-2], [1], [0]]" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "print('Vous allez échelonner la matrice A')\n", "al.printA(A)\n", "[i,j,r,alpha]= al.manualEch(A)\n", "LList = [np.eye(4)]\n", "UList=[np.array(A).astype(float)]\n", "print('\\033[1mExécutez la ligne suivante pour effectuer l\\'opération choisie \\033[0m')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "m=al.LU_interactive(i,j,r,alpha, LList, UList)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Entrez ci-dessous les coefficients de la variable temporaire y et de la solution x du système" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "y = [[1], [-2], [-2], [-1]] # variable temporaire\n", "x = [[-5], [12], [-10], [2]] # solution du système\n", "\n", "corrections.Ex2Chapitre2_10(LList[-1], UList[-1], b, x, y)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Exercise 3\n", "\n", "Considerez le système linéaire $Ax=b$ avec $A \\in \\mathcal{M}_{3 \\times 4}(\\mathbb{R})$ et $b \\in \\mathcal{M}_{3 \\times 1}(\\mathbb{R})$ donné par:\n", "\n", "\\begin{equation}\n", "A = \n", "\\begin{pmatrix}\n", "1 & 2 & 0 & -1 \\\\\n", "-2 & -2 & -1 & 0 \\\\\n", "0 & 2 & -2 & 1\n", "\\end{pmatrix}\n", "\\qquad b = \n", "\\begin{pmatrix}\n", "1 \\\\\n", "-1 \\\\\n", "2 \n", "\\end{pmatrix}\n", "\\end{equation}\n", "\n", "Profitant de la décomposition LU, résolvez, si possible, le système linéaire et marquez ceux des énoncés suivants qui sont corrects" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#Entrez les coefficients de A et b\n", "A = [[1,2,0,-1], [-2,-2,-1,0], [0,2,-2,1]]\n", "b = [[1], [-1], [2]]" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "print('Vous allez échelonner la matrice A')\n", "al.printA(A)\n", "[i,j,r,alpha]= al.manualEch(A)\n", "LList = [np.eye(3)]\n", "UList=[np.array(A).astype(float)]\n", "print('\\033[1mExécutez la ligne suivante pour effectuer l\\'opération choisie \\033[0m')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "m=al.LU_interactive(i,j,r,alpha, LList, UList)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "corrections.Ex3Chapitre2_10()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Exemple 1\n", "\n", "Il peut être difficile de comprendre pourquoi la décomposition LU est si importante. En effet il semble que ce ne soit rien de plus qu'une manière différente de mettre en œuvre la méthode d'élimination de Gauss, où au lieu d'impliquer le vecteur b directement dans la procédure de réduction, on va construire une matrice (L) qui contient toutes les opérations élémentaires effectuées sur les lignes de la matrice du système.\n", "\n", "En fin de compte, ce changement apparemment simple est la clé qui rend la décomposition LU (avec toutes ses variantes) très utile dans la pratique réelle; en effet, dans de nombreuses utilisations, il est nécessaire de résoudre plusieurs systèmes linéaires (qui peuvent êtres avec un très grand nombre de variables!). Tous ces systèmes ont la même matrice (L ou U), mais différents vecteurs du côté droit. \n", "Dans de telles situations, il est très utile d'utiliser la décomposition LU! D'abord, la décomposition LU de la matrice est calculée avant la résolution de tous les systèmes linéaires et on ne la calcule qu'une seule fois. En suite, chaque système est rapidement résolu via des schémas de substitution avant / arrière (sur les matrices L et U qui possèdent beaucoup de coefficients nuls). \n", "Si la décomposition LU n'est pas utilisée, alors à chaque étape un système linéaire complet devrait être résolu, conduisant à une augmentation significative en termes de nombre d'opérations et de temps de calcul.\n", "\n", "Afin de le montrer, nous présentons ci-dessous comment le nombre d'opérations et le temps d'exécution se comparent si plusieurs grands systèmes linéaires (partageant tous la même matrice) sont résolus en s'appuyant ou non sur la décomposition LU.\n", "\n", "**Exécutez la cellule suivante et évaluez les différences de performances ... cela peut prendre quelques secondes**" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "----------------------------Décomposition LU--------------------------------\n", - "Nombre d'opérations élémentaires (I, II, III): 499500\n", - "Coût de la décomposition LU (nombre total d'additions, soustractions, multiplications et divisions): 667166500\n", - "Temps d'exécution: 2.385104 s\n", - "\n", - "---------------------Résolution du système linéaire-------------------------\n" - ] - } - ], + "outputs": [], "source": [ "N = 1000 # dimension of the linear systems\n", "Nt = 5000 # number of linear systems to be solved\n", "A = np.random.rand(N, N);\n", - "start = time.time()\n", + "\n", "print(\"----------------------------Décomposition LU--------------------------------\")\n", + "start = time.time()\n", "L, U = al.LU_no_pivoting(A)\n", "time_lu = time.time() - start\n", "n_op_lu = 2/3*(N**3 - N)\n", "n_op_no_lu = 0\n", "print(\"Temps d'exécution: % f s\" %(time_lu))\n", "\n", "print(\"\\n---------------------Résolution du système linéaire-------------------------\")\n", "# solve without using LU \n", "start = time.time()\n", "for cnt in range(Nt):\n", " b = np.random.rand(N,1)\n", " x = np.linalg.solve(A, b)\n", " n_op_no_lu += N**3 # --> N^3 operations per cycle, according to Numpy/LAPACK documentation on benchmark cases\n", "time_no_lu = time.time() - start\n", "print(\"Sans décomposition LU: coût computationnelle: % e, temps d'exécution: % f s\" %(n_op_no_lu, time_no_lu))\n", "\n", "# solve using LU\n", "start = time.time()\n", "for cnt in range(Nt):\n", " b = np.random.rand(N,1)\n", " y = solve_triangular(L, b)\n", " n_op_lu += 2*N**2 - N # computational cost of forward substitution\n", " x = solve_triangular(U, y)\n", " n_op_lu += 2*N**2 - N # computational cost of backward substitution\n", "time_lu += time.time() - start\n", "\n", "print(\"Avec décomposition LU: coût computationnelle: % e, temps d'exécution: % f s\" %(n_op_lu, time_lu))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Vous pouvez comparer les temps d'exécution et le nombre d'opérations pour différentes tailles de matrice (c'est-à-dire changer le paramètre N) et pour un nombre différent de systèmes lineaires (c'est-à-dire changer le paramètre N_t)**" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "[Passez au notebook 2.11: Décomposition en blocs](2.11%20Décomposition%20en%20blocs.ipynb)" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" } }, "nbformat": 4, "nbformat_minor": 4 } diff --git "a/Chapitre 4 - Bases et dimension/4.1-2. D\303\251pendance et ind\303\251pendance lin\303\251aire.ipynb" "b/Chapitre 4 - Bases et dimension/4.1-2. D\303\251pendance et ind\303\251pendance lin\303\251aire.ipynb" index 52b7b45..b4242c0 100644 --- "a/Chapitre 4 - Bases et dimension/4.1-2. D\303\251pendance et ind\303\251pendance lin\303\251aire.ipynb" +++ "b/Chapitre 4 - Bases et dimension/4.1-2. D\303\251pendance et ind\303\251pendance lin\303\251aire.ipynb" @@ -1,207 +1,214 @@ { "cells": [ { "cell_type": "code", "execution_count": 1, "metadata": { "jupyter": { "outputs_hidden": false }, "pycharm": { "name": "#%%\n" } }, "outputs": [ { "data": { "text/html": [ " \n", " " ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "from Ch4Functions import *" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Concepts-clés et théorie\n", "\n", "### DÉFINITION 1\n", "\n", "Soient $V$ un $\\mathbb{R}$-espace vectoriel et $S\\subset V$ une collection de vecteurs dans $V.$ On dit que $S$ est linéairement dépendante (ou liée) s'il existe des vecteurs distincts $v_1,\\ldots,v_r\\in S$ et des scalaires $\\lambda_1,\\ldots,\\lambda_r\\in \\mathbb{R}$ non tous nuls tels que $\\lambda_1v_1+\\cdots+\\lambda_rv_r=0.$ (Autrement dit, s'il existe une combinaison linéaire (non triviale) de vecteurs de $S$ qui se réduit au vecteur nul.) S'il n'existe pas de tels vecteurs dans $S,$ alors on dit que $S$ est linéairement indépendante (ou libre).\n", "\n", "### PROPOSITION 1\n", "\n", "Soient $V$ un $\\mathbb{R}$-espace vectoriel et $v_1,\\ldots,v_r\\in V$ des vecteurs de $V.$ Alors ces derniers sont linéairement dépendants si et seulement s'il existe $1\\leq i\\leq r$ tels que $v_i\\in \\mbox{Vect}(\\{v_1,\\ldots,v_{i-1},v_{i+1},\\ldots,v_r\\}),$ c'est-à-dire si et seulement si l'on peut exprimer un des vecteurs de la liste comme une combinaison linéaire des autres.\n", "\n", "### PROPOSITION 2\n", "\n", "Soient $V$ un $\\mathbb{R}$-espace vectoriel et $S\\subset V$ une famille libre de vecteurs dans $V.$ Alors tout sous-ensemble $T\\subset S$ est aussi libre.\n", "\n", "### PROPOSITION 3\n", "\n", "Soient $V$ un $\\mathbb{R}$-espace vectoriel et $S\\subset V$ une famille liée de vecteurs dans $V.$ Alors toute collection de vecteurs $T$ contenant $S$ est également liée." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### EXERCICE 1\n", "\n", "Soient:\n", "\n", "$$v_1 = \\begin{pmatrix} 1 \\\\ 0 \\\\ 2 \\end{pmatrix} \\ \\ v_2 = \\begin{pmatrix} 0 \\\\ 1 \\\\ 0 \\end{pmatrix} \\ \\ v_3 = \\begin{pmatrix} 1 \\\\ 1 \\\\ 1 \\end{pmatrix}$$\n", "\n", "Trouver une combinaison linéaire telle que:\n", "\n", "$$\\lambda_1 v_1 + \\lambda_2 v_2 + \\lambda_3 v_3 = \\begin{pmatrix} 3 \\\\ 5 \\\\ 4 \\end{pmatrix}$$\n", "\n", "Entrez les coefficients $\\lambda_i$ dans le vecteur ci-dessous puis exécutez les deux cellules pour vérifier votre réponse." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "solution = [1, 0, 2] # Réponse à compléter\n", "\n", "ch4_1_2ex1(solution)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### EXERCICE 2\n", "\n", "D'après les mêmes données que l'exercice 1, la collection des vecteurs $v_1$, $v_2$, $v_3$ et $\\begin{pmatrix} 3 \\\\ 0 \\\\ 4 \\end{pmatrix}$ est-elle liée ?" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "ch4_1_2ex2()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### EXERCICE 3\n", "\n", "Les collections de vecteurs suivantes sont-elles liées ?" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### (a)\n", "\n", "$$v_1 = \\begin{pmatrix} 1 \\\\ 1 \\\\ 1 \\end{pmatrix} \\ \\ v_2 = \\begin{pmatrix} 1 \\\\ 2 \\\\ 1 \\end{pmatrix} \\ \\ v_3 = \\begin{pmatrix} 0 \\\\ 1 \\\\ 0 \\end{pmatrix}$$" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "ch4_1_2ex3a()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### (b)\n", "\n", "$$v_1 = \\begin{pmatrix} 1 \\\\ 0 \\\\ 4 \\end{pmatrix} \\ \\ v_2 = \\begin{pmatrix} 6 \\\\ 12 \\\\ 7 \\end{pmatrix} \\ \\ v_3 = \\begin{pmatrix} 0 \\\\ 9 \\\\ 7 \\end{pmatrix} \\ \\ v_4 = \\begin{pmatrix} 1 \\\\ 1 \\\\ 1 \\end{pmatrix}$$" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "ch4_1_2ex3b()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### (c)\n", "\n", "$$v_1 = \\begin{pmatrix} 1 \\\\ 0 \\\\ -1 \\end{pmatrix} \\ \\ v_2 = \\begin{pmatrix} 1 \\\\ 1 \\\\ 0 \\end{pmatrix} \\ \\ v_3 = \\begin{pmatrix} 0 \\\\ 1 \\\\ -1 \\end{pmatrix}$$" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "ch4_1_2ex3c()" ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "[Passez au notebook du chapitre 4.3. Bases et dimension](./4.3.%20Bases%20et%20dimension.ipynb)" + ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.2" }, "pycharm": { "stem_cell": { "cell_type": "raw", "metadata": { "collapsed": false }, "source": [] } } }, "nbformat": 4, "nbformat_minor": 4 } diff --git "a/Chapitre 4 - Bases et dimension/4.11. Coordonn\303\251es par rapport \303\240 une base.ipynb" "b/Chapitre 4 - Bases et dimension/4.11. Coordonn\303\251es par rapport \303\240 une base.ipynb" index df012d9..335d8a1 100644 --- "a/Chapitre 4 - Bases et dimension/4.11. Coordonn\303\251es par rapport \303\240 une base.ipynb" +++ "b/Chapitre 4 - Bases et dimension/4.11. Coordonn\303\251es par rapport \303\240 une base.ipynb" @@ -1,292 +1,305 @@ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from Ch4Functions import *" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Concept(s)-clé(s)\n", "\n", - "### Définition 1 :\n", - "Soient $V$ un $\\mathbb{R}$-espace vectoriel de dimension finie $n$, $\\mathscr{B} =\\{v_1,\\ldots,v_n\\}$ une base ordoné de $V$ et $v \\in V$. Comme $\\mathscr{B}$ est une base de $V$, il existe des uniques scalaires $\\alpha_1,\\ldots,\\alpha_n \\in \\mathbb{R}$ tels que $v = \\alpha_1v_1 + \\ldots + \\alpha_nv_n$. On appelle $\\alpha_1,\\ldots,\\alpha_n$ les coordonnées de $v$ par rapport à la base $\\mathscr{B}$ et on écrit\n", + "### DÉFINITION 1 :\n", + "Soient $V$ un $\\mathbb{R}$-espace vectoriel de dimension finie $n, \\mathscr{B}=\\left(v_{1}, \\ldots, v_{n}\\right)$ une base ordonnée de $V$ et $v \\in V$. Comme $\\mathscr{B}$ est une base de $V,$ il existe des uniques scalaires $\\alpha_{1}, \\ldots, \\alpha_{n} \\in \\mathbb{R}$ tels que $v=\\alpha_{1} v_{1}+\\cdots+\\alpha_{n} v_{n} .$ On appelle $\\alpha_{1}, \\ldots, \\alpha_{n}$ les coordonnées de $v$ par rapport $\\dot{a}$ la base $\\mathscr{B}$ et on écrit\n", + "$$\n", + "[v]_{\\mathscr{B}}=\\left(\\begin{array}{c}\n", + "\\alpha_{1} \\\\\n", + "\\alpha_{2} \\\\\n", + "\\vdots \\\\\n", + "\\alpha_{n}\n", + "\\end{array}\\right)\n", + "$$\n", "\n", - "\n", - "$$[v]_{\\mathscr{B}} = \\begin{pmatrix} \\alpha_1 \\\\ \\alpha_2 \\\\ \\vdots \\\\ \\alpha_n \\end{pmatrix}.$$\n", - "\n", - "### Proposition 2 :\n", - "Soient $V$ un $\\mathbb{R}$-espace vectoriel de dimension finie $n$ et $\\mathscr{B} = (v_1,\\ldots,v_n)$ une base ordonnée de $V$. Alors les deux affirmations suivantes sont vérifiées.\n", - "\n", - "1. Pour tous $v_1,v_2 \\in V$, on a $[v_1 + v_2 ]_{\\mathscr{B}} = [v_1]_{\\mathscr{B}}+[v_2]_{\\mathscr{B}}.$\n", - "\n", - "2. Pour tout $v \\in V$ et tout $\\lambda \\in \\mathbb{R}$, on a $[\\lambda v]_{\\mathscr{B}} = \\lambda[v]_{\\mathscr{B}}$" + "### PROPOSITION 2 :\n", + "Soient $V$ un $\\mathbb{R}$-espace vectoriel de dimension finie $n$ et $\\mathscr{B}=\\left(v_{1}, \\ldots, v_{n}\\right)$ une base ordonnée de $V$. Alors les deux affirmations suivantes sont vérifiées.\n", + "1. Pour tous $v_{1}, v_{2} \\in V,$ on a $\\left[v_{1}+v_{2}\\right]_{\\mathscr{B}}=\\left[v_{1}\\right]_{\\mathscr{B}}+\\left[v_{2}\\right]_{\\mathscr{B}}$\n", + "2. Pour tout $v \\in V$ et tout $\\lambda \\in \\mathbb{R},$ on a $[\\lambda v]_{\\mathscr{B}}=\\lambda[v]_{\\mathscr{B}}$." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Exercice 1:\n", "\n", - "Soit $\\mathbb{R}^2$ l'espace du plan. La base la plus évidnte de cet espace vectoriel est la base orthonormée $\\mathscr{B} = ((1,0),(0,1))$. Cette base est représentée dans le graphique suivant." + "Soit $\\mathbb{R}^2$ l'espace du plan. La base la plus évidente de cet espace vectoriel est la base canonique $\\mathscr{B} = \\left\\{\\left(\\begin{array}{l}1 \\\\ 0 \\end{array}\\right), \\left(\\begin{array}{l}0 \\\\ 1 \\end{array}\\right)\\right\\}$. Cette base est représentée dans le graphique suivant." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ - "#ici un graph pas distordu (graph 1)" + "ch4_11_plot(show_can_base=True)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Quelle base de $\\mathbb{R}^2$ est représentée ci-dessous?" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ - "#ici un graph avec grille distordue (graph 2)" + "ch4_11ex1_plot()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Entrez les vecteurs qui forment la base réprésentée ci-dessus\n", "# Par exemple : [[1, 2], [3, 4]]\n", - "base = [[-2,1],[4,1]]\n", + "base = [[0, 0],[0, 0]]\n", "\n", "ch4_11ex1(base)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Exercice 2\n", "\n", - "a) Soit le vecteur $\\textbf{v}$ représenté dans le graph suivant, $\\mathscr{B}_1$ la base orthonormée de $\\mathbb{R}^2$ et $\\mathscr{B}_2 = ((1,2),(3,2))$. Quelles sont les coordonnées de de $\\textbf{v}$ dans $\\mathscr{B}_1$? " + "a) Soit le vecteur $\\textbf{v}$ représenté dans le graph suivant, $\\mathscr{B}_1$ la base canonique de $\\mathbb{R}^2$ et $\\mathscr{B}_2 = \\left\\{\\left(\\begin{array}{l}1 \\\\ 2 \\end{array}\\right),\\left(\\begin{array}{l}3 \\\\ 2 \\end{array}\\right)\\right\\}$. Quelles sont les coordonnées de de $\\textbf{v}$ dans $\\mathscr{B}_1$? " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ - "#ici graph avec v desssus (graph 3)" + "#ici graph avec v desssus (graph 3)\n", + "ch4_11ex2a_plot()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Entrez les coordonnées du vecteur v dans la base B1\n", "# Par exemple : [1,2]\n", - "vB1 = [5,6]\n", + "vB1 = [0, 0]\n", "\n", "ch4_11ex2aB1(vB1)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Et dans $\\mathscr{B}_2$?" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Entrez les coordonnées du vecteur v dans la base B2\n", "# Par exemple : [1,2]\n", - "vB2 = [2,1]\n", + "vB2 = [0, 0]\n", "\n", "ch4_11ex2aB2(vB2)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "b) Avec les mêmes bases, quelles sont les coordonnées du vecteur $\\textbf{u}$ dans $\\mathscr{B}_1$?" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ - "#ici graph avec u desssus (graph 4)" + "#ici graph avec u desssus (graph 4)\n", + "ch4_11ex2b_plot()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Entrez les coordonnées du vecteur u dans la base B1\n", "# Par exemple : [1,2]\n", - "uB1 = [8,4]\n", + "uB1 = [0, 0]\n", "\n", "ch4_11ex2bB1(uB1)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Et dans $\\mathscr{B}_2$?" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Entrez les coordonnées du vecteur u dans la base B2\n", "# Par exemple : [1,2]\n", - "uB2 = [-1,3]\n", + "uB2 = [0, 0]\n", "\n", "ch4_11ex2bB2(uB2)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "c) Toujours avec les mêmes bases, quelles sont les coordonnées du vecteur $\\textbf{w}$ dans $\\mathscr{B}_1$?" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ - "#ici graph avec w desssus (graph 5)" + "#ici graph avec w desssus (graph 5)\n", + "ch4_11ex2c_plot()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Entrez les coordonnées du vecteur w dans la base B1\n", "# Par exemple : [1,2]\n", - "wB1 = [5,4]\n", + "wB1 = [0, 0]\n", "\n", "ch4_11ex2cB1(wB1)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Et dans $\\mathscr{B}_2$?" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Entrez les coordonnées du vecteur w dans la base B2\n", "# Par exemple : [1,2]\n", - "wB2 = [.5,1.5]\n", + "wB2 = [0, 0]\n", "\n", "ch4_11ex2cB2(wB2)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Exercice 3\n", "\n", - "Considérons maintenant le vecteur $\\textbf{s}$ tel que $\\textbf{s} = \\textbf{v}+\\textbf{u}$. A l'aide de la proposition 2 et de l'exerciceprécédent, déduire les coordonnées de $\\textbf{s}$ dans $\\mathscr{B}_1$." + "Considérons maintenant le vecteur $\\textbf{s}$ tel que $\\textbf{s} = \\textbf{v}+\\textbf{u}$. A l'aide de la proposition 2 et de l'exercice précédent, déduire les coordonnées de $\\textbf{s}$ dans $\\mathscr{B}_1$." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Entrez les coordonnées du vecteur s dans la base B1\n", "# Par exemple : [1,2]\n", - "sB1 = [13,10]\n", + "sB1 = [0, 0]\n", "\n", "ch4_11ex3B1(sB1)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Et dans $\\mathscr{B}_2$?" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Entrez les coordonnées du vecteur s dans la base B2\n", "# Par exemple : [1,2]\n", - "sB2 = [1,4]\n", + "sB2 = [0, 0]\n", "\n", "ch4_11ex3B2(sB2)" ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "[Passez au notebook du chapitre 4.12. Trouver une base à partir d'un système générateur](./4.12.%20Trouver%20une%20base%20%C3%A0%20partir%20d'un%20syst%C3%A8me%20g%C3%A9n%C3%A9rateur.ipynb)" + ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.6.8" + "version": "3.7.2" } }, "nbformat": 4, "nbformat_minor": 4 } diff --git "a/Chapitre 4 - Bases et dimension/4.12. Trouver une base \303\240 partir d'un syst\303\250me g\303\251n\303\251rateur.ipynb" "b/Chapitre 4 - Bases et dimension/4.12. Trouver une base \303\240 partir d'un syst\303\250me g\303\251n\303\251rateur.ipynb" index a078e1f..8e406ee 100644 --- "a/Chapitre 4 - Bases et dimension/4.12. Trouver une base \303\240 partir d'un syst\303\250me g\303\251n\303\251rateur.ipynb" +++ "b/Chapitre 4 - Bases et dimension/4.12. Trouver une base \303\240 partir d'un syst\303\250me g\303\251n\303\251rateur.ipynb" @@ -1,135 +1,135 @@ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from Ch4Functions import *" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Concept(s)-clé(s)\n", "\n", "\n", "\n", "### MÉTHODE POUR TROUVER UNE BASE À PARTIR D'UN SYSTÈME DE GÉNÉRATEURS :\n", "\n", "Soient $V$ un $\\mathbb{R}$-espace vectoriel de dimension finie $n,$ $\\mathscr{B}=(v_1,\\ldots,v_n)$ une base de $V,$ $S\\subset V$ une partie finie et $W=\\mbox{Vect}(S).$ Pour trouver une base de $W$ et la compléter en une base de $V,$ on procède comme suit.\n", "\n", "\n", "\n", "1. Pour chaque $v\\in S,$ on écrit $[v]_{\\mathscr{B}} = \\begin{pmatrix} \\alpha_1 \\ \\alpha_2 \\ \\cdots \\ \\alpha_n \\end{pmatrix}^T.$\n", "\n", "2. On définit la matrice $A$ dont les lignes sont les vecteurs $[v]_{\\mathscr{B}}^T$ ($v\\in S$).\n", "\n", "3. On échelonne la matrice $A$ : les lignes non-nulles ainsi obtenues forment une base de l'espace ligne de cette matrice. De plus, les vecteurs de $W$ correspondants forment une base de $W.$\n", "\n", "4. On remplace les lignes nulles de la matrice échelonnée par des lignes non-nulles de manière à ce que celle-ci contienne $n$ pivots. Les vecteurs de $V$ associés aux lignes de cette nouvelle matrice forment une base de $V.$" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Exercice 1\n", "\n", "Soit $S = (v_1,v_2,v_3,v_4,v_5) \\in \\mathbb{R}^6$ et $W=Vect(S)$. Selon la méthode vue en cours, quelles sont les dimensions de la matrice $A$?" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "ch4_12ex1()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Exercice 2\n", "\n", "Sachant que $v_2 = 2v_3+3v_5$, combien faut-il rajouter, au minimum, de lignes non-nulles à la matrice $A$, dans sa forme échelonnée, pour que les vecteurs associés aux lignes de celle-ci forment une base de $\\mathbb{R}^6$?" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "ch4_12ex2()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Exercice 3\n", "\n", "Voici la matrice $A$ échelonnée, nommée $B$:\n", "\n", "$$B = \\begin{pmatrix} 1 & 4 & 0 & 2 & 0 & 1 \\\\ 0 & 0 & 0 & 0 & 0 & 0 \\\\ 0 & 1 & 1 & 2 & 1 & 0 \\\\ 0 & 0 & 1 & 3 & 3 & 2 \\\\ 0 & 0 & 0 & 0 & 0 & 0\\end{pmatrix}$$\n", "\n", "a) Combien de vecteurs doivent être ajoutés aux lignes non-nulles de $B$ pour que celles-co soient une base de $\\mathbb{R}^6$ ?" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "ch4_12ex3a()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "b) Donner une collection de vecteurs qui complètent les lignes non-nulles de $B$ en une base de $\\mathbb{R}^6$." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Entrez les vecteurs qui complètent les lignes non-nulles de B en une base de R^6\n", "# Par exemple : [[1, 2], [3, 4]]\n", - "vecteurs_manquants = [[0,0,0,1,0,0],[0,0,0,0,1,0],[0,0,0,0,0,1]]\n", + "vecteurs_manquants = [[0,0,0,0,0,0],[0,0,0,0,0,0],[0,0,0,0,0,0]]\n", "\n", "ch4_12ex3b(vecteurs_manquants)" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.6.8" + "version": "3.7.2" } }, "nbformat": 4, "nbformat_minor": 4 } diff --git a/Chapitre 4 - Bases et dimension/4.3. Bases et dimension.ipynb b/Chapitre 4 - Bases et dimension/4.3. Bases et dimension.ipynb index 1c3d445..ee91912 100644 --- a/Chapitre 4 - Bases et dimension/4.3. Bases et dimension.ipynb +++ b/Chapitre 4 - Bases et dimension/4.3. Bases et dimension.ipynb @@ -1,183 +1,190 @@ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from Ch4Functions import *" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "# Concepts-clés et théorie\n", "\n", "### DÉFINITION 1 :\n", "\n", "Soient $V$ un $\\mathbb{R}$-espace vectoriel et $\\mathscr{B}\\subset V$ un ensemble de vecteurs de $V.$ On dit que $\\mathscr{B}$ est une *base* $V$ si les deux conditions suivantes sont vérifiées.\n", "\n", "1. Tout $v\\in V$ est une combinaison linaire de vecteurs de $\\mathscr{B},$ i.e. $\\mbox{Vect}\\mathscr{B}=V$ \n", "\n", "2. Le sous-ensemble $\\mathscr{B}$ est linéairement indépendant.\n", "\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### EXERCICE 1\n", "\n", "Cochez votre réponse puis cliquez sur \"Vérifier\"." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "1. L'ensemble $\\begin{Bmatrix}\\begin{pmatrix} 1 \\\\ 0 \\\\ 1 \\end{pmatrix}, \\begin{pmatrix} 0 \\\\ 2 \\\\ 0 \\end{pmatrix}, \\begin{pmatrix} 0 \\\\ 0 \\\\ 3 \\end{pmatrix}, \\begin{pmatrix} 1 \\\\ 1 \\\\ 1 \\end{pmatrix}\\end{Bmatrix}$ est une base de $\\mathbb{R}^3$ :" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "ch4_3ex1a()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "2. L'ensemble $\\begin{Bmatrix}\\begin{pmatrix} 1 \\\\ 0 \\\\ 1 \\end{pmatrix}, \\begin{pmatrix} 0 \\\\ 1 \\\\ 0 \\end{pmatrix}\\end{Bmatrix}$ est une base de $\\mathbb{R}^3$ :" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "ch4_3ex1b()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "3. L'ensemble $\\begin{Bmatrix}\\begin{pmatrix} 1 \\\\ -2 \\\\ 4 \\end{pmatrix}, \\begin{pmatrix} 7 \\\\ -1 \\\\ 2 \\end{pmatrix}, \\begin{pmatrix} 2 \\\\ 5 \\\\ -3 \\end{pmatrix}\\end{Bmatrix}$ est une base de $\\mathbb{R}^3$ :" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "ch4_3ex1c()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### DÉFINITION 2 :\n", "\n", "On dit d'un $\\mathbb{R}$-espace vectoriel $V$ qu'il est *de dimension finie* s'il possède une base finie. Sinon, on dit que $V$ est *de dimension infinie*.\n", "\n", "\n", "### THÉORÈME 3 :\n", "Soit $V$ un $\\mathbb{R}$-espace vectoriel de dimension finie. Alors toutes les bases de $V$ sont finies et possèdent le mème nombre d'éléments." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### EXERCICE 2\n", "\n", "Sélectionnez un ensemble de vecteurs parmi la liste suivante tel que cet ensemble forme une base de $\\mathbb{R}^3$ :\n", "\n", "$$v_1 = \\begin{pmatrix} 1 \\\\ 1 \\\\ 1 \\end{pmatrix},\\ v_2 = \\begin{pmatrix} 0 \\\\ 1 \\\\ 2 \\end{pmatrix},\\ v_3 = \\begin{pmatrix} 2 \\\\ 1 \\\\ 4 \\end{pmatrix},\\ v_4 = \\begin{pmatrix} 2 \\\\ 1 \\\\ 0 \\end{pmatrix},\\ v_5 = \\begin{pmatrix} 1 \\\\ 0 \\\\ -1 \\end{pmatrix}$$\n", "\n", "(maintenez CTRL pour sélectionner plusieurs cellules à la fois)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "ch4_3ex2()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### EXERCICE 3\n", "\n", "Soit $\\mathbb{P}^n(\\mathbb{R})$ l'ensemble des polynômes de degré $n$ à coefficients réels.\n", "\n", "- L'ensemble $\\{x, x^2, x^3, ..., x^{n}\\}$ est-il une base de $\\mathbb{P}^n(\\mathbb{R})$ ?" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "ch4_3ex3()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### EXERCICE 4\n", "\n", "Quelle est la dimension de l'espace engendré par l'ensemble de vecteurs suivant :\n", "\n", "$$\\begin{Bmatrix}\\begin{pmatrix} 1 & 1 \\\\ 0 & 0 \\end{pmatrix},\\ \\begin{pmatrix} 1 & 0 \\\\ 1 & 0 \\end{pmatrix},\\ \\begin{pmatrix} 0 & 0 \\\\ 1 & 1 \\end{pmatrix},\\ \\begin{pmatrix} 0 & 1 \\\\ 0 & 1 \\end{pmatrix},\\ \\begin{pmatrix} 1 & 0 \\\\ 0 & 1 \\end{pmatrix}\\end{Bmatrix}$$" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "ch4_3ex4()" ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "[Passez au notebook du chapitre 4.4-5. Dimension et base dans un espace de dimension connue](./4.4-5.%20Dimension%20et%20base%20dans%20un%20espace%20de%20dimension%20connue.ipynb)" + ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.2" } }, "nbformat": 4, "nbformat_minor": 4 } diff --git a/Chapitre 4 - Bases et dimension/4.4-5. Dimension et base dans un espace de dimension connue.ipynb b/Chapitre 4 - Bases et dimension/4.4-5. Dimension et base dans un espace de dimension connue.ipynb index ed0c9e0..2fbf53a 100644 --- a/Chapitre 4 - Bases et dimension/4.4-5. Dimension et base dans un espace de dimension connue.ipynb +++ b/Chapitre 4 - Bases et dimension/4.4-5. Dimension et base dans un espace de dimension connue.ipynb @@ -1,100 +1,107 @@ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from Ch4Functions import *" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Concepts-clés et théorie\n", "\n", "\n", "\n", "### DÉFINITION 1 :\n", "\n", "Soit $V$ un $\\mathbb{R}$-espace vectoriel de dimension finie. Le nombre d'éléments dans une base s'appelle la *dimension* de $V$ et on le désigne par $\\mbox{dim} V.$\n", "\n", "### PROPOSITION 2 :\n", "\n", "Soit $V$ un $\\mathbb{R}$-espace vectoriel de dimension finie. Alors les deux affirmations suivantes sont vérifiées.\n", "\n", "\n", "\n", "1. Si $\\{v_1,\\ldots,v_r\\}$ est un ensemble générateur de $V,$ alors il existe une base $\\mathscr{B}$ de $V$ telle que $\\mathscr{B}\\subset \\{v_1,\\ldots,v_r\\}.$ On parle d'*extraction de base*.\n", "\n", "2. Si $\\{v_1,\\ldots,v_r\\}$ est une partie libre de $V,$ alors il existe une base $\\mathscr{B}$ de $V$ telle que $\\{v_1,\\ldots,v_r\\}\\subset \\mathscr{B}.$ On parle de *complétion en une base*." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### EXERCICE 1\n", "\n", "Extraire une base $\\mathscr{B}$ de $\\mathbb{R}^3$ à partir des vecteurs suivants :\n", "\n", "$$v_1 = \\begin{pmatrix} 1 \\\\ 1 \\\\ 1 \\end{pmatrix},\\ v_2 = \\begin{pmatrix} 2 \\\\ 0 \\\\ 3 \\end{pmatrix},\\ v_3 = \\begin{pmatrix} 4 \\\\ 0 \\\\ 0 \\end{pmatrix},\\ v_4 = \\begin{pmatrix} 1 \\\\ 0 \\\\ 0 \\end{pmatrix}$$" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "ch4_4_5ex1()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### EXERCICE 2\n", "\n", "Ajouter un vecteur à la liste suivante afin de former une base $\\mathscr{B}$ de $M_{2 \\times 2}(\\mathbb{R})$ :\n", "\n", "$$\\begin{Bmatrix}\\begin{pmatrix} 1 & 4 \\\\ 3 & 0 \\end{pmatrix}, \\begin{pmatrix} 1 & 0 \\\\ 0 & 1 \\end{pmatrix}, \\begin{pmatrix} 0 & 1 \\\\ 1 & 0 \\end{pmatrix}\\end{Bmatrix}$$" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Complétez les valeurs puis exécutez la cellule\n", "v = [[0, 0], [0, 0]]\n", "\n", "ch4_4_5ex2(v)" ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "[Passez au notebook du chapitre 4.6. Systèmes homogènes et base de l'espace des solutions](./4.6.%20Syst%C3%A8mes%20homog%C3%A8nes%20et%20base%20de%20l'espace%20des%20solutions.ipynb)" + ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.2" } }, "nbformat": 4, "nbformat_minor": 4 } diff --git "a/Chapitre 4 - Bases et dimension/4.6. Syst\303\250mes homog\303\250nes et base de l'espace des solutions.ipynb" "b/Chapitre 4 - Bases et dimension/4.6. Syst\303\250mes homog\303\250nes et base de l'espace des solutions.ipynb" index 0830422..b9834e6 100644 --- "a/Chapitre 4 - Bases et dimension/4.6. Syst\303\250mes homog\303\250nes et base de l'espace des solutions.ipynb" +++ "b/Chapitre 4 - Bases et dimension/4.6. Syst\303\250mes homog\303\250nes et base de l'espace des solutions.ipynb" @@ -1,125 +1,133 @@ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from Ch4Functions import *" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Concepts-clés et théorie\n", "\n", "\n", "\n", "### RAPPEL 1 :\n", "\n", "Soient $A\\in M_{m \\times n}(\\mathbb{R})$ et $X = \\begin{pmatrix} x_1 \\; x_2 \\;\\cdots \\; x_n\\end{pmatrix}^T,$ où $x_1,\\ldots,x_n$ sont des inconnues réelles. Alors l'ensemble des solutions du système linéaire $AX=0$ est un sous-espace vectoriel de $\\mathbb{R}^n.$\n", "\n", "### PROPOSITION 1 :\n", "\n", "Soient  $A$ et $X$ comme ci-dessus. Alors la dimension de l'espace des solutions du système $AX=0$ est égale au nombre de variable(s) libre(s) dans une forme échelonnée de $A.$\n", "\n", "\n", "\n", "### PROPOSITION 2 :\n", "\n", "Soient  $A$ et $X$ comme ci-dessus. Pour trouver une base de l'espace des solutions du système $AX=b,$ on pose successivement une des variables libre égale à $1$ et toutes les autres égales à $0.$" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### EXERCICE 1\n", "\n", "_Dimension maximale de l'espace des solutions_\n", "\n", "Soit une matrice $A$ à $4$ lignes et $6$ colonnes non nulles. Quelle est la dimension maximale de l'espace des solutions du système $AX = 0$ ?" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "ch4_6ex1()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### EXERCICE 2\n", "\n", "Soit la matrice $A$ avec les coefficients suivants :\n", "\n", "$$A = \\begin{pmatrix} 1 & 3 & 5 & 1 & 1 \\\\ -3 & -8 & -13 & 1 & 0 \\\\ 0 & -1 & -1 & -1 & -1 \\\\ 2 & 6 & 12 & 8 & 6 \\end{pmatrix}$$\n", "\n", "A l'aide de sa forme échelonnée (donnée ci-dessous), indiquer la dimension de l'espace des solutions du système homogène $AX = 0$.\n", "\n", "$$A' = \\begin{pmatrix} 1 & 3 & 5 & 1 & 1 \\\\ 0 & 1 & 2 & 4 & 3 \\\\ 0 & 0 & 1 & 3 & 2 \\\\ 0 & 0 & 0 & 0 & 0 \\end{pmatrix}$$" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "ch4_6ex2()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### EXERCICE 3\n", "\n", "_Base de l'espace des solutions_\n", "\n", "A l'aide de de l'énoncé et de la réponse de l'exercice 2 et de la proposition 2, donnez une base de l'espace des solutions du système homogène $AX = 0$." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Entrez les vecteurs qui formeront une base de la solution\n", "# Par exemple : [[1, 2], [3, 4]]\n", "base = []\n", "\n", "ch4_6ex3(base)" ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "[Passez au notebook du chapitre 4.7-8. Dimension d'un sous-espace et d'une somme de sous-espaces\n", + "](./4.7-8.%20Dimension%20d'un%20sous-espace%20et%20d'une%20somme%20de%20sous-espaces.ipynb)" + ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.2" } }, "nbformat": 4, "nbformat_minor": 4 } diff --git a/Chapitre 4 - Bases et dimension/4.7-8. Dimension d'un sous-espace et d'une somme de sous-espaces.ipynb b/Chapitre 4 - Bases et dimension/4.7-8. Dimension d'un sous-espace et d'une somme de sous-espaces.ipynb index 0203365..6612950 100644 --- a/Chapitre 4 - Bases et dimension/4.7-8. Dimension d'un sous-espace et d'une somme de sous-espaces.ipynb +++ b/Chapitre 4 - Bases et dimension/4.7-8. Dimension d'un sous-espace et d'une somme de sous-espaces.ipynb @@ -1,226 +1,234 @@ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from Ch4Functions import *" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Concepts-clés et théorie\n", "\n", "\n", "\n", "### THÉORÈME 1 :\n", "\n", "Soient $V$ un $\\mathbb{R}$-espace vectoriel de dimension finie et $W$ un sous-espace vectoriel de $V.$ Alors les affirmations suivantes sont vérifiées.\n", "\n", "\n", "\n", "1. Le sous-espace vectoriel $W$ est de dimension finie.\n", "\n", "2. La dimension de $W$ satisfait $\\dim W\\leq \\dim V$.\n", "\n", "3. Si $\\dim W=\\dim V,$ alors $W=V$." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### EXERCICE 1\n", "\n", "Soit un système de $3$ équations à $5$ inconnues.\n", "\n", "Quelles sont les dimensions possibles pour l'espace des solutions ? Cochez toutes les réponses valides (maintenez CTRL pour en sélectionner plusieurs)." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "ch4_7_8ex1()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### EXERCICE 2\n", "\n", "Soit $S_1$ un système de $2$ équations à $3$ inconnues et dont l'espace de solution forme un plan.\n", "\n", "On définit $S_2$ comme étant le système $S_1$ privé d'une équation.\n", "\n", "Quelle est la dimension de l'espace des solutions de $S_2$ ?" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "ch4_7_8ex2()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### EXERCICE 3\n", "\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Remarquez la propriété suivante :\n", "\n", "- $\\dim(A + B) + \\dim(A \\cap B) = \\dim(A) + \\dim(B)$" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "ch4_7_8ex3_venn()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Donner dans chaque cas la dimension de la somme des deux espaces vectoriels ; appuyez sur le bouton \"Visualiser\" pour vous aider :" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "(a) $\\dim (W + V)$ avec $W$ un plan et $V$ une droite coplanaire à ce plan ; les deux passant par l'origine." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "ch4_7_8ex3a()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "(b) $\\dim (W + V)$ avec $W$ un plan et $V$ une droite non coplanaire à ce plan ; les deux passant par l'origine." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "ch4_7_8ex3b()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "(c) $\\dim (V + V)$ avec $V$ une droite passant par l'origine." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "ch4_7_8ex3c()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "(d) $\\dim (V_1 + V_2)$ avec $V_1$ et $V_2$ deux droites distinctes passant par l'origine." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "ch4_7_8ex3d()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "(e) $\\dim (W_1 + W_2)$ avec $W_1$ et $W_2$ deux plans distincts passant par l'origine." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "ch4_7_8ex3e()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "(f) $\\dim (U + X)$ avec $U$ l'origine et $X$ l'espace $\\mathbb{R}^3$." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "ch4_7_8ex3f()" ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "[Passez au notebook du chapitre 4.9. Rang-ligne et rang-colonne d'une matrice\n", + "](./4.9.%20Rang-ligne%20et%20rang-colonne%20d'une%20matrice.ipynb)" + ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.2" } }, "nbformat": 4, "nbformat_minor": 4 } diff --git a/Chapitre 4 - Bases et dimension/Ch4Functions.py b/Chapitre 4 - Bases et dimension/Ch4Functions.py index d6f65e2..789a587 100644 --- a/Chapitre 4 - Bases et dimension/Ch4Functions.py +++ b/Chapitre 4 - Bases et dimension/Ch4Functions.py @@ -1,1252 +1,1404 @@ import sys sys.path.insert(0,'..') import Librairie.AL_Fct as al sys.path.pop(0) import numpy as np from IPython.display import display, Markdown, Latex import plotly.graph_objs as go import plotly import ipywidgets as widgets from ipywidgets import interact, interactive, fixed, interact_manual, Layout, VBox, HBox import plotly.express as px import sympy as sp import matplotlib.pyplot as plt from itertools import permutations from IPython.display import display, Latex, Markdown def ch4_1_2ex1(solution): v = np.array([[1, 0, 2], [0, 1, 0], [1, 1, 1]]).transpose() e = np.array([3, 5, 4]) s = np.array(solution) r = v @ s if np.allclose(e, r): display(Markdown("**Correction:** C'est correct!")) else: display(Markdown("**Correction:** C'est incorrect car: $\lambda_1 v_1 + \lambda_2 v_2 + \lambda_3 v_3 = \\begin{pmatrix} %s \\\\ %s \\\\ %s \end{pmatrix} \\neq \\begin{pmatrix} %s \\\\ %s \\\\ %s \end{pmatrix}$" % (r[0], r[1], r[2], e[0], e[1], e[2]))) w = s * v cumsum = np.cumsum(np.insert(w, 0, 0, axis=1), axis=1).transpose() colors = px.colors.qualitative.Plotly global color_index color_index = 0 data = [] def addVector(start, v): global color_index color = colors[color_index] color_index = (color_index + 1) % len(colors) end = start + v trace = go.Scatter3d( x=[start[0], end[0], None], y=[start[1], end[1], None], z=[start[2], end[2], None], mode='lines', name=str(v), line=dict(color=color, width=4) ) norm = np.sqrt(np.sum(v * v)) n = v if norm == 0 else v / norm n = 1.5 * n c_end = end - 0.37 * n cone = go.Cone(x=[c_end[0]], y=[c_end[1]], z=[c_end[2]], u=[n[0]], v=[n[1]], w=[n[2]], name=str(v), colorscale=[[0, color], [1, color]], hoverinfo="none", showscale=False) data.append(trace) data.append(cone) addVector(np.zeros(3), e) for i in range(len(cumsum) - 1): start = cumsum[i] v = cumsum[i + 1] - start addVector(start, v) fig = go.Figure(data=data) fig.show() def ch4_1_2ex2(): radio = widgets.RadioButtons( options=['Oui, les vecteurs sont dépendants', 'Non, les vecteurs sont indépendants'], layout={'width': 'max-content'}, value=None, description='Réponse:', ) button = widgets.Button(description='Vérifier') out = widgets.Output() display(radio) display(button) display(out) def verification_2(e): if radio.value is not None: out.clear_output() with out: if radio.value.startswith('Non'): display(Markdown("C'est incorrect, il existe $\lambda_1$, $\lambda_2$ et $\lambda_3$ tels que $\lambda_1 v_1 + \lambda_2 v_2 + \lambda_3 v_3 - \\begin{pmatrix} 3 \\\\ 0 \\\\ 4 \end{pmatrix} = 0$.")) else: display(Markdown("C'est correct!")) button.on_click(verification_2) def ch4_1_2ex3(answer): radio = widgets.RadioButtons( options=['Oui, les vecteurs sont dépendants', 'Non, les vecteurs sont indépendants'], layout={'width': 'max-content'}, value=None, description='Réponse:', ) button = widgets.Button(description='Vérifier') out = widgets.Output() display(radio) display(button) display(out) def verification_3(e): if radio.value is not None: out.clear_output() with out: if radio.value.startswith('Oui') == answer: display(Markdown("C'est correct!")) else: display(Markdown("C'est incorrect!")) button.on_click(verification_3) def ch4_1_2ex3a(): ch4_1_2ex3(True) def ch4_1_2ex3b(): ch4_1_2ex3(True) def ch4_1_2ex3c(): ch4_1_2ex3(False) def ch4_3ex1(answer, reason, callback, options=['Oui, les vecteurs forment une base', 'Non, les vecteurs ne forment pas une base']): radio = widgets.RadioButtons( options=options, layout={'width': 'max-content'}, value=None, description='Réponse:', ) button = widgets.Button(description='Vérifier') out = widgets.Output() display(radio) display(button) display(out) def verification(e): if radio.value is not None: out.clear_output() with out: if options.index(radio.value) == answer: display(Markdown("C'est correct!
%s" % reason)) else: display(Markdown("C'est incorrect!
%s" % reason)) callback() button.on_click(verification) def plot_vectors(vectors, selected, solution): v = np.array(vectors).transpose() e = np.array(selected) s = np.array(solution) r = v @ s w = s * v cumsum = np.cumsum(np.insert(w, 0, 0, axis=1), axis=1).transpose() colors = px.colors.qualitative.Plotly global color_index color_index = 0 data = [] def addVector(start, v): global color_index color = colors[color_index] color_index = (color_index + 1) % len(colors) end = start + v trace = go.Scatter3d( x=[start[0], end[0], None], y=[start[1], end[1], None], z=[start[2], end[2], None], mode='lines', name=str(v), line=dict(color=color, width=4) ) norm = np.sqrt(np.sum(v * v)) n = v if norm == 0 else v / norm n = 0.5 * n c_end = end - 0.37 * n cone = go.Cone(x=[c_end[0]], y=[c_end[1]], z=[c_end[2]], u=[n[0]], v=[n[1]], w=[n[2]], name=str(v), colorscale=[[0, color], [1, color]], hoverinfo="none", showscale=False) data.append(trace) data.append(cone) addVector(np.zeros(3), e) for i in range(len(cumsum) - 1): start = cumsum[i] v = cumsum[i + 1] - start addVector(start, v) fig = go.Figure(data=data) fig.show() def ch4_3ex1a(): ch4_3ex1(1, """ En effet, les quatres vecteurs ne sont pas linéairement indépendants (Déf. 1-2) car il est possible d'exprimer l'un avec une combinaison linéaire des autres. Par exemple : $$\\begin{pmatrix} 1 \\\\ 1 \\\\ 1 \end{pmatrix} = \\begin{pmatrix} 1 \\\\ 0 \\\\ 1 \end{pmatrix} + \\frac{1}{2} \\begin{pmatrix} 0 \\\\ 2 \\\\ 0 \end{pmatrix} + 0 \\begin{pmatrix} 0 \\\\ 0 \\\\ 3 \end{pmatrix}$$ Comme tous les vecteurs sont issus de $\mathbb{R}^3$ on peut facilement représenter la combinaison dans l'espace : """, lambda: plot_vectors([[1, 0, 1], [0, 2, 0], [0, 0, 3]], [1, 1, 1], [1, 0.5, 0])) def ch4_3ex1b(): ch4_3ex1(1, """ On ne peut pas générer $\mathbb{R}^3$ à partir de cet ensemble. En effet, on ne peut par exemple pas obtenir le vecteur $\\begin{pmatrix} 1 \\\\ 0 \\\\ 0 \end{pmatrix} \\in \mathbb{R}^3$ avec une combinaison linéaire de $\\begin{pmatrix} 1 \\\\ 0 \\\\ 1 \end{pmatrix}$ et $\\begin{pmatrix} 0 \\\\ 1 \\\\ 0 \end{pmatrix}$. Graphiquement les deux vecteurs ne peuvent engendrer qu'un plan: """, lambda: plot_vectors([[1, 0, 0]], [0, 1, 0], [1])) def ch4_3ex1c(): ch4_3ex1(0, """ Ces trois vecteurs sont linéairement indépendants, ils engendrent donc $\mathbb{R}^3$ et forment une base de cet espace. """, lambda: None) def ch4_3ex2(): vecs = np.array([[1, 1, 1], [0, 1, 2], [2, 1, 4], [2, 1, 0], [1, 0, -1]]) select = widgets.SelectMultiple( options=['v1', 'v2', 'v3', 'v4', 'v5'], description='Sélection :', disabled=False ) button = widgets.Button(description='Vérifier') out = widgets.Output() def callback(e): answer = [int(v[1:])-1 for v in select.value] out.clear_output() with out: if len(answer) == 0: # Empty pass elif len(answer) < 3: display(Markdown("C'est incorrect!
La solution entrée ne permet pas d'engendrer $\mathbb{R}^3$.")) elif len(answer) > 3: display(Markdown("C'est incorrect!
La solution entrée contient des vecteurs qui sont dépendants.")) else: mat = np.array([vecs[answer[0]], vecs[answer[1]], vecs[answer[2]]]).transpose() det = np.linalg.det(mat) if det == 0: display(Markdown("C'est incorrect!
La solution entrée contient des vecteurs qui sont dépendants.")) else: # Correct display(Markdown("C'est correct!
Il s'agit d'_une_ base de $\mathbb{R}^3$.")) button.on_click(callback) display(select) display(button) display(out) def ch4_3ex3(): ch4_3ex1(1, """ Cet ensemble n'est pas une base de $\mathbb{P}^n(\mathbb{R})$ car il ne permet pas (par exemple) de générer les polynômes constants. $\mathbb{P}^n(\mathbb{R})$ est néanmoins engendré par $\{1, x, x^2, x^3, ..., x^{n-1}\}$. """, lambda: None) def ch4_3ex4(): ch4_3ex1(2, """ L'espace engendré par cet ensemble est $M_{2 \\times 2}(\mathbb{R})$ et donc sa dimension est $4$. Attention cet ensemble n'est pas une base de $M_{2 \\times 2}(\mathbb{R})$ car ses éléments sont linéairement dépendants ! """, lambda: None, ['2', '3', '4', '5']) def ch4_4_5ex1(): vs = np.array([[1, 1, 1], [2, 0, 3], [4, 0, 0], [1, 0, 0]]) select = widgets.SelectMultiple( options=['v1', 'v2', 'v3', 'v4'], description='Sélection :', disabled=False ) button = widgets.Button(description='Vérifier') out = widgets.Output() def callback(e): answer = [int(v[1:])-1 for v in select.value] out.clear_output() with out: if len(answer) == 0: # Empty pass elif len(answer) < 3: display(Markdown("C'est incorrect!
La solution entrée ne permet pas d'engendrer $\\mathbb{R}^3$, et n'est donc pas une base.")) elif len(answer) > 3: display(Markdown("C'est incorrect!
La solution entrée engendre $\\mathbb{R}^3$ mais n'est pas une base.")) else: mat = np.array([vs[answer[0]], vs[answer[1]], vs[answer[2]]]).transpose() if np.linalg.matrix_rank(mat) != len(mat): display(Markdown("C'est incorrect!
La solution entrée ne permet pas d'engendrer $\\mathbb{R}^3$, et n'est donc pas une base.")) else: # Correct display(Markdown("C'est correct!
Il s'agit d'_une_ base de $\\mathbb{R}^3$.")) button.on_click(callback) display(select) display(button) display(out) def ch4_4_5ex2(v): v = np.array(v) vs = np.array([[1, 4, 3, 0], [1, 0, 0, 1], [0, 1, 1, 0], v.flatten()]) is_base = np.linalg.matrix_rank(vs) == len(vs) out = widgets.Output() display(out) with out: if is_base: display(Markdown("C'est correct!")) else: display(Markdown("C'est incorrect, ce vecteur ne permet pas de former une base.")) def ch4_6ex1(): text = widgets.IntText( description='Réponse :', disabled=False ) button = widgets.Button(description='Vérifier') out = widgets.Output() def callback(e): out.clear_output() r = text.value feedback = "" is_correct = False if r == 6: feedback = "Comme la matrice n'est pas nulle, au moins une variable n'est pas libre." elif r >= 7: feedback = "La dimension de l'espace des solutions ne peut excéder la dimension de l'espace des variables." elif r == 5: is_correct = True feedback = "Le nombre maximal de variables libres dans ce système est $5$. Par la proposition 1 on en déduit la dimension maximale de l'espace des solutions du système homogène $AX = 0$." elif r >= 2 and r <= 4: feedback = "Ce n'est pas le nombre maximal de variables libres dans ce système." elif r <= 1: feedback = "Le nombre maximal de variables libres dans ce système ne peut être inférieur à $2$ ($\\text{nb. colonnes} - \\text{nb. lignes}$)." correct_text = "C'est correct!
" incorrect_text = "C'est incorrect.
" with out: display(Markdown((correct_text if is_correct else incorrect_text) + feedback)) button.on_click(callback) display(text) display(button) display(out) def ch4_6ex2(): text = widgets.IntText( description='Réponse :', disabled=False ) button = widgets.Button(description='Vérifier') out = widgets.Output() def callback(e): out.clear_output() r = text.value with out: if r == 2: display(Markdown("C'est correct!
Le nombre de variables libres est $2$, par la proposition 1 on trouve la dimension de l'espace des solutions.")) else: display(Markdown("C'est incorrect.")) button.on_click(callback) display(text) display(button) display(out) def ch4_7_8ex3_venn(): ax = plt.gca() r = 5 rd = r / 2 rc = rd * 2 f = 25 a = plt.Circle((-rd, 0), radius=r, color="#fc032c90") b = plt.Circle((rd, 0), radius=r, color="#0377fc90") ax.add_patch(a) ax.add_patch(b) ax.annotate("A", xy=(-rc, 0), fontsize=f, ha='center', va='center') ax.annotate("A ∩ B", xy=(0, 0), fontsize=f, ha='center', va='center') ax.annotate("B", xy=(rc, 0), fontsize=f, ha='center', va='center') plt.axis('scaled') plt.axis('off') plt.show() def ch4_6ex3(base): base = np.array(base) out = widgets.Output() display(out) with out: out.clear_output() feedback = "" is_correct = False s = base.shape if len(base) == 0: feedback = "L'ensemble ne peut pas être vide." elif len(s) != 2 or s[1] != 5: feedback = "Le format des vecteurs n'est pas bon." elif s[0] < 2: feedback = "L'ensemble entré ne contient pas assez de vecteurs pour engendrer toutes les solutions du système." elif s[0] > 2: feedback = "L'ensemble entré n'est pas une base." else: expected = np.array(sp.Matrix([[6, 1, -2, 0, 1], [8, 2, -3, 1, 0]]).rref()[0]) actual = np.array(sp.Matrix(base).rref()[0]) if not np.array_equal(actual, expected): feedback = "L'ensemble entré n'engendre pas l'espace solution du système." else: is_correct = True correct_text = "C'est correct!
" incorrect_text = "C'est incorrect.
" display(Markdown((correct_text if is_correct else incorrect_text) + feedback)) def ch4_7_8ex1(): select = widgets.SelectMultiple( options=['0', '1', '2', '3', '4', '5'], description='Réponse :', disabled=False ) button = widgets.Button(description='Vérifier') out = widgets.Output() display(select) display(button) display(out) def verification(e): if len(select.value) > 0: out.clear_output() with out: other = "
La dimension de l'espace des solutions dépend du nombre de variables libres : dans ce système celle-ci peut être au plus $5-3=2$." if select.value != ('0', '1', '2'): display(Markdown("C'est incorrect!" + other)) else: display(Markdown("C'est correct." + other)) button.on_click(verification) def ch4_7_8ex2(): text = widgets.IntText(description='Réponse :') button = widgets.Button(description='Vérifier') out = widgets.Output() def callback(e): out.clear_output() r = text.value with out: other = "
En effet, comme la solution de $S_1$ est un plan les deux équations sont dépendantes. La solution de $S_2$ est donc aussi un plan, donc sa dimension est $2$." if r == 2: display(Markdown("C'est correct!" + other)) else: display(Markdown("C'est incorrect." + other)) button.on_click(callback) display(text) display(button) display(out) def ch4_7_8ex3_cube(name, color): return go.Mesh3d( name=name, x=[-1, -1, 1, 1, -1, -1, 1, 1], y=[-1, 1, 1, -1, -1, 1, 1, -1], z=[-1, -1, -1, -1, 1, 1, 1, 1], i=[7, 0, 0, 0, 4, 4, 6, 6, 4, 0, 3, 2], j=[3, 4, 1, 2, 5, 6, 5, 2, 0, 1, 6, 3], k=[0, 7, 2, 3, 6, 7, 1, 1, 5, 5, 7, 6], color=color, opacity=0.25 ) def ch4_7_8ex3_plane(name, color, variant): ep = 1.25 p1 = ep * np.array([-1, 1, 1, -1]) p2 = ep * np.array([-1, -1, 1, 1]) pz = np.array([0, 0, 0, 0] if variant == 0 else [0, 0.01, 0.02, 0.03]) # Bug workaround ps = list(permutations([p1, p2, pz]))[variant] return go.Mesh3d( name=name, x=ps[0], y=ps[1], z=ps[2], color=color, opacity=0.25 ) def ch4_7_8ex3_line(name, color, variant): el = 1.5 l1 = [-el, el, None] lz = [0, 0, None] ps = list(permutations([l1, lz, lz]))[variant] return go.Scatter3d( name=name, x=ps[0], y=ps[1], z=ps[2], opacity=0.25, line=dict(color=color, width=4) ) def ch4_7_8ex3(answer, explanation, plot): text = widgets.IntText(description='Réponse :') button = widgets.Button(description='Vérifier') show = widgets.Button(description='Visualiser') box = widgets.HBox(children=[button, show]) out = widgets.Output() out2 = widgets.Output() def callback(e): out.clear_output() r = text.value with out: other = "
" + explanation if r == answer: display(Markdown("C'est correct!" + other)) else: display(Markdown("C'est incorrect." + other)) def callback2(e): with out2: plot() button.on_click(callback) show.on_click(callback2) display(text) display(box) display(out) display(out2) def ch4_7_8ex3a(): ch4_7_8ex3(2, "$V$ est un sous-ensemble de $W$, donc $\dim (W + V) = \dim W = 2$.", lambda: go.Figure(data=[ch4_7_8ex3_plane('A', 'red', 0), ch4_7_8ex3_line('B', 'blue', 0)]).show()) def ch4_7_8ex3b(): ch4_7_8ex3(3, "$W$ et $V$ sont deux espaces indépendants, donc $\dim (W + V) = \dim W + \dim V = 2 + 1 = 3$.", lambda: go.Figure(data=[ch4_7_8ex3_plane('A', 'red', 0), ch4_7_8ex3_line('B', 'blue', 3)]).show()) def ch4_7_8ex3c(): ch4_7_8ex3(1, "La somme d'un même espace n'a pas d'effet : $\dim (V + V) = \dim V = 1$.", lambda: go.Figure(data=[ch4_7_8ex3_line('A', 'red', 0), ch4_7_8ex3_line('B', 'blue', 0)]).show()) def ch4_7_8ex3d(): ch4_7_8ex3(2, "$V_1$ et $V_2$ sont deux espaces indépendants, donc $\dim (V_1 + V_2) = \dim V_1 + \dim V_2 = 1 + 1 = 2$.", lambda: go.Figure(data=[ch4_7_8ex3_line('A', 'red', 0), ch4_7_8ex3_line('B', 'blue', 2)]).show()) def ch4_7_8ex3e(): ch4_7_8ex3(3, "$\dim (W_1 + W_2) = 2 + 2 - 1 = 3$.", lambda: go.Figure(data=[ch4_7_8ex3_plane('A', 'red', 0), ch4_7_8ex3_plane('B', 'blue', 1)]).show()) def ch4_7_8ex3f(): ch4_7_8ex3(3, "$\dim (U + X) = 0 + 3 - 0 = 3$.", lambda: go.Figure(data=[ch4_7_8ex3_cube('B', 'blue')]).show()) def ch4_9ex1(): r = ['Le rang ligne de 𝐴 est plus petit ou égal à 2 car c\'est un sous-espace vectoriel de ℝ2.', 'Le rang ligne de 𝐴 est plus petit ou égal à 3 car c\'est un sous-espace vectoriel de ℝ3.', 'Le rang ligne de 𝐴 est plus petit ou égal à 3 car engendré par 3 vecteurs.', 'Le rang ligne de 𝐴 est plus petit ou égal à 2 car engendré par 2 vecteurs.', 'Le rang colonne de A est plus petit ou égal à 2.'] buttons = [] for i in range(5): b = widgets.ToggleButton( value=False, description=r[i], disabled=False, button_style='', # 'success', 'info', 'warning', 'danger' or '' tooltip='Description', layout=Layout(width='auto', height='auto') ) buttons.append(b) button = widgets.Button(description='Vérifier', layout=Layout(width='auto', height='auto'), button_style='info' ) def callback(e): out.clear_output() with out: if (buttons[0].value or buttons[2].value): print('Mauvaise réponse. \nAttention à ne pas confondre les espaces des lignes et colonnes') elif (not buttons[1].value) or (not buttons[3].value) or (not buttons[4].value): print('Il manque au moins une réponse.') elif (buttons[1].value and buttons[3].value and buttons[4].value): print('Correct !') buttons.append(button) buttons[5].on_click(callback) box = VBox(children = buttons) out = widgets.Output() display(box) display(out) def ch4_9ex2_1(): text = widgets.IntText( description='Réponse :', disabled=False ) button = widgets.Button(description='Vérifier') out = widgets.Output() def callback(e): out.clear_output() r = text.value with out: if r == 2: display(Markdown("Correct!")) elif r > 2: display(Markdown("Incorrect, le rang ligne est plus petit.")) else: display(Markdown("Incorrect, le rang ligne est plus grand.")) button.on_click(callback) display(text) display(button) display(out) def ch4_9ex2_2(): options = ['E1', 'E2', 'E3', 'E4'] buttons = [] for i in range(4): b = widgets.ToggleButton( value=False, description=options[i], disabled=False, button_style='', # 'success', 'info', 'warning', 'danger' or '' tooltip='Description' ) buttons.append(b) button = widgets.Button(description='Vérifier', # layout=Layout(width='auto', height='auto'), button_style='info' ) out = widgets.Output() def callback(e): out.clear_output() with out: if buttons[1].value or buttons[2].value or buttons[3].value: display(Markdown("Faux")) elif buttons[0].value: display(Markdown("Correct !")) buttons.append(button) buttons[4].on_click(callback) box = VBox(children=buttons) display(box) display(out) def ch4_9ex2_3(): text = widgets.IntText(description='Réponse :', disabled=False) button = widgets.Button(description='Vérifier') button2 = widgets.Button(description='Solution', disabled=True) box = widgets.HBox(children=[button, button2]) out = widgets.Output() def callback(e): out.clear_output() button2.disabled = False with out: if (text.value == 2): print('Correct !') else: print('Faux, essayez encore ou regardez la solution.') def solution(e): out.clear_output() with out: A = np.array([[1, 2, 3], [0, 1, 2]]) A_t = A.transpose() display(Markdown( 'Pour trouver le rang colonne de $A$, on utilise la remarque 1 et trouve le rang ligne de la transposée de $A$.')) display(Markdown( 'Par les propositions 1 et 2, on échelonne la matrice transposée et on trouve le nombre de lignes contenant des pivots')) M = al.echelonMat('E', A_t) display(Markdown('Ainsi le rang colonne de $A$ est 2.')) button.on_click(callback) button2.on_click(solution) display(text) display(box) display(out) def ch4_10ex1_1(A, b): A_sol = [[1, 4, 3, 4],[2, 6, 5, 8],[1, 0, 1, 4]] b_sol = [[1], [1], [1]] if A == [] or b == []: print("Attention, vous avez laissé au moins une des deux entrée vide") elif not (len(A) == len(b)): print("Les tailles de la matrice et du vecteur ne correspondent pas") else: b = np.reshape(np.array(b), (len(b), 1)).tolist() if A == A_sol: if b == b_sol: print("Correct !") else: print("Le vecteur b est faux, votre reponse correspond au système suivant:") al.printSyst(A, b) elif b == b_sol: print("La Matrice A est fausse, votre reponse correspond au système suivant:") al.printSyst(A, b) else: print("Faux, votre réponse correspond au système suivant:") al.printSyst(A, b) def ch4_10ex1_2_1_ech(): global m display(Latex('Échelonnez la matrice transposée de A')) A_sol = np.array([[1, 4, 3, 4], [2, 6, 5, 8], [1, 0, 1, 4]]) A_sol_t = A_sol.transpose() al.printA(A_sol_t) [i, j, r, alpha] = al.manualEch(A_sol_t) MatriceList = [np.array(A_sol_t)] m = A_sol_t button = widgets.Button(description='Appliquer') out = widgets.Output() def applique(e): global m out.clear_output() with out: m = al.echelonnage(i, j, r, alpha, A_sol_t, m, MatriceList) button.on_click(applique) display(button) display(out) def f_sol(): al.echelonMat('E', A_sol.transpose()) display(Latex("Pour afficher la solution, cliquez-ici.")) im = interact_manual(f_sol) im.widget.children[0].description = 'Solution' def ch4_10ex1_2_1(): text_rang = widgets.IntText() box = widgets.VBox([widgets.Label('Rang colonne de A:'), text_rang]) button = widgets.Button(description='Vérifier') out = widgets.Output() def callback(e): out.clear_output() with out: if(text_rang.value == 2): display(Latex('Correct !')) else: display(Latex('Faux, essayez encore ou regardez la solution.')) button.on_click(callback) display(box) display(button) display(out) def f_sol(): display(Latex("Le rang colonne de $A$ correspond au nombre de pivot(s) de la forme échelonnée de $A^T$.")) display(Latex("Pour afficher la solution, cliquez-ici.")) im = interact_manual(f_sol) im.widget.children[0].description = 'Solution' def ch4_10ex1_2_2(base): base_sol = [[1, 2, 1], [0, 1, 2]] correct = check_basis(base_sol, base) def f_sol(): display(Latex("Une base de l'espace ligne de $A$ est donnée par les lignes de la forme echelonnée contenant " "un pivots. Pour trouver une base de l'espace colonne $A$, on utilise l'espace ligne de $A^T$.")) display(Latex("Une base de l'espace colone de $A$ est donc:")) print(base_sol) if base_sol == []: display(Latex("L'entrée est vide")) display(Latex("Pour afficher la solution, cliquez-ici.")) im = interact_manual(f_sol) im.widget.children[0].description = 'Solution' else: if correct: display(Latex("Correct !")) if base_sol != base: display(Latex("Pour la suite de l'exercice, on utilise la base equivalente suivante:")) print(base_sol) else: display(Latex("Faux")) display(Latex("Pour afficher la solution, cliquez-ici.")) im = interact_manual(f_sol) im.widget.children[0].description = 'Solution' def check_basis(sol, prop): """ Checks if prop basis is equivalent to sol basis @param sol: verified basis, 2D numpy array, first dim: vector indexes, second dim: idx of element in a basis vect @param prop: proposed basis @return: boolean """ sol = np.array(sol, dtype=np.float64) prop = np.array(prop, dtype=np.float64) # number of vector in basis n = len(sol) # Check dimension if n != len(prop): return False else: # Check if the sol vector can be written as linear combination of prop vector # Do least squares to solve overdetermined system and check if sol is exact A = np.transpose(prop) lin_comb_ok = np.zeros(n, dtype=bool) for i in range(n): x, _, _, _ = np.linalg.lstsq(A, sol[i], rcond=None) res = np.sum((A @ x - sol[i]) ** 2) lin_comb_ok[i] = res < 10 ** -13 return np.all(lin_comb_ok) def ch4_10ex1_3_ech(): display(Latex("Echelonnez la matrice suivante.")) global m2 A = [[1, 2, 1], [0, 1, 2], [1, 1, 1]] al.printA(A) [i, j, r, alpha] = al.manualEch(A) MatriceList = [np.array(A)] m2 = A button = widgets.Button(description='Appliquer') out = widgets.Output() def callback(e): global m2 out.clear_output() with out: m2 = al.echelonnage(i, j, r, alpha, A, m2, MatriceList) button.on_click(callback) display(button) display(out) def ch4_10ex1_3(): display(Latex("Le système admet-il une solution ?")) radio = widgets.RadioButtons( options=['Oui', 'Non'], description='Réponse:', disabled=False ) button = widgets.Button(description='Vérifier') button2 = widgets.Button(description='Solution', disabled=True) box = widgets.HBox(children=[button, button2]) out = widgets.Output() def callback(e): out.clear_output() button2.disabled = False with out: if (radio.value == "Oui"): print('Mauvaise réponse.') else: print('Correct !') def solution(e): out.clear_output() with out: A = np.array([[1, 2, 1], [0, 1, 2], [0, 0, 1]]) display(Markdown('Après échelonnage, la matrice devient')) al.printA(A) display( Markdown("Ainsi le rang colonne de la matrice augmentée est 3, et le système n'admet pas de solution.")) button2.on_click(solution) button.on_click(callback) display(radio) display(box) display(out) def ch4_10ex1_3_2_ech(): display(Latex("Echelonnez la matrice suivante.")) global m3 A = [[1, 4, 3, 4], [2, 6, 5, 8], [1, 0, 1, 4]] b = [[1], [1], [1]] al.printA(A, b) [i, j, r, alpha] = al.manualEch(A, b) MatriceList = [np.array(A)] RHSList = [np.array(b)] m3 = np.concatenate((A, b), axis=1) button = widgets.Button(description='Appliquer') out = widgets.Output() def callback(e): global m3 out.clear_output() with out: m3 = al.echelonnage(i, j, r, alpha, A, m3, MatriceList, RHSList) button.on_click(callback) display(button) display(out) def ch4_10ex2_1_ech(): global m21 A = [[1, 2, 1], [0, 1, 2], [2, 5, 4]] al.printA(A) [i, j, r, alpha] = al.manualEch(A) MatriceList = [np.array(A)] m21 = A button = widgets.Button(description='Appliquer') out = widgets.Output() def callback(e): global m21 out.clear_output() with out: m21 = al.echelonnage(i, j, r, alpha, A, m21, MatriceList) button.on_click(callback) display(button) display(out) def ch4_10ex2_2(): radio = widgets.RadioButtons( options=['Oui', 'Non'], description='Réponse:', disabled=False ) button = widgets.Button(description='Vérifier') button2 = widgets.Button(description='Solution', disabled=True) box = HBox(children=[button, button2]) out = widgets.Output() def callback(e): out.clear_output() button2.disabled = False with out: if (radio.value == "Oui"): print('Correct !') else: print('Mauvaise réponse.') def solution(e): out.clear_output() with out: A = np.array([[1, 2, 1], [0, 1, 2], [0, 0, 0]]) display(Markdown('Après échelonnage, la matrice devient')) al.printA(A) display(Markdown( "Ainsi le rang colonne de la matrice augmentée est 2, et d'après le lemme, le système admet une solution.")) button2.on_click(solution) button.on_click(callback) display(radio) display(box) display(out) +def ch4_11_plot(base=None, show_grid_lines=False, show_can_base=False, vector_list=[], vector_name_list=[]): + + if base is not None: + x1 = np.array(base[0]) + x2 = np.array(base[1]) + n1 = 30 + n2 = 20 + + fig = go.Figure() + fig.update_layout(xaxis=dict(range=[-8.5, 8.5], constrain="domain", ), + yaxis=dict(range=[-8.5, 8.5], scaleanchor="x", scaleratio=1), showlegend=True) + fig.update_xaxes(tick0=0, dtick=2) + fig.update_yaxes(tick0=0, dtick=2) + + if show_grid_lines: + grid_x1_x = np.array([[-n1 * x1[0] + i * x2[0] for i in range(-n2, n2)], + [n1 * x1[0] + i * x2[0] for i in range(-n2, n2)]]) + grid_x1_y = np.array([[-n1 * x1[1] + i * x2[1] for i in range(-n2, n2)], + [n1 * x1[1] + i * x2[1] for i in range(-n2, n2)]]) + + grid_x2_x = np.array([[-n1 * x2[0] + i * x1[0] for i in range(-n2, n2)], + [n1 * x2[0] + i * x1[0] for i in range(-n2, n2)]]) + grid_x2_y = np.array([[-n1 * x2[1] + i * x1[1] for i in range(-n2, n2)], + [n1 * x2[1] + i * x1[1] for i in range(-n2, n2)]]) + + grid_lines = go.scatter.Line(color='grey', width=0.5) + + for i in range(2 * n2): + fig.add_trace(go.Scatter(x=grid_x1_x[:, i], y=grid_x1_y[:, i], mode='lines', showlegend=False, + line=grid_lines)) + + fig.add_trace(go.Scatter(x=grid_x2_x[:, i], y=grid_x2_y[:, i], mode='lines', showlegend=False, + line=grid_lines)) + + if show_can_base: + fig.add_annotation(x=1, # arrows' head + y=0, # arrows' head + ax=0, # arrows' tail + ay=0, # arrows' tail + xref='x', + yref='y', + axref='x', + ayref='y', + text='', # if you want only the arrow + showarrow=True, + arrowhead=3, + arrowsize=2, + arrowwidth=1, + arrowcolor='black', + ) + + fig.add_annotation(x=1.3, y=0, text="$x_1$", showarrow=False) + + fig.add_annotation(x=0, # arrows' head + y=1, # arrows' head + ax=0, # arrows' tail + ay=0, # arrows' tail + xref='x', + yref='y', + axref='x', + ayref='y', + text='', # if you want only the arrow + showarrow=True, + arrowhead=3, + arrowsize=2, + arrowwidth=1, + arrowcolor='black' + ) + fig.add_annotation(x=0, y=1.3, text="$y_1$", showarrow=False) + + if base is not None: + + fig.add_annotation(x=base[0][0], # arrows' head + y=base[0][1], # arrows' head + ax=0, # arrows' tail + ay=0, # arrows' tail + xref='x', + yref='y', + axref='x', + ayref='y', + text='', # if you want only the arrow + showarrow=True, + arrowhead=3, + arrowsize=2, + arrowwidth=1, + arrowcolor='blue' + ) + fig.add_annotation(x=base[0][0]+0.2, y=base[0][1]+0.2, text="$x_2$", showarrow=False) + + fig.add_annotation(x=base[1][0], # arrows' head + y=base[1][1], # arrows' head + ax=0, # arrows' tail + ay=0, # arrows' tail + xref='x', + yref='y', + axref='x', + ayref='y', + text='', # if you want only the arrow + showarrow=True, + arrowhead=3, + arrowsize=2, + arrowwidth=1, + arrowcolor='blue' + ) + fig.add_annotation(x=base[1][0]+0.2, y=base[1][1]+0.2, text="$y_2$", showarrow=False) + + color_list = ['red', 'orange', 'grey', 'brown'] + for idx, v in enumerate(vector_list): + fig.add_trace(go.Scatter(x=[0, v[0]], y=[0, v[1]], mode='lines + markers', showlegend=True, + name=vector_name_list[idx], + line=dict(color=color_list[idx]), + marker=dict(color=color_list[idx]), + )) + + fig.show() + + return + + +def ch4_11ex1_plot(): + ch4_11_plot(base=[[1, 2], [3, 2]], show_can_base=True, show_grid_lines=True, vector_list=[], vector_name_list=[]) + return + + def ch4_11ex1(base): base_solution = [[1, 2], [3, 2]] is_correct = all(item in base_solution for item in base) if is_correct: is_correct = all(item in base for item in base_solution) correct_text = "C'est correct!" incorrect_text = "C'est incorrect." display(correct_text if is_correct else incorrect_text) +def ch4_11ex2a_plot(): + ch4_11_plot(base=[[1, 2], [3, 2]], show_can_base=True, show_grid_lines=True, vector_list=[[5, 6]], + vector_name_list=['v']) + return + def ch4_11ex2aB1(vB1): v = [5, 6] if vB1 == v: is_correct = 1 else: is_correct = 0 correct_text = "C'est correct!" incorrect_text = "C'est incorrect." display(correct_text if is_correct else incorrect_text) def ch4_11ex2aB2(vB2): v = [2, 1] if vB2 == v: is_correct = 1 else: is_correct = 0 correct_text = "C'est correct!" incorrect_text = "C'est incorrect." display(correct_text if is_correct else incorrect_text) +def ch4_11ex2b_plot(): + ch4_11_plot(base=[[1, 2], [3, 2]], show_can_base=True, show_grid_lines=True, vector_list=[[8, 4]], + vector_name_list=['u']) + return + + def ch4_11ex2bB1(uB1): u = [8, 4] if uB1 == u: is_correct = 1 else: is_correct = 0 correct_text = "C'est correct!" incorrect_text = "C'est incorrect." display(correct_text if is_correct else incorrect_text) def ch4_11ex2bB2(uB2): u = [-1, 3] if uB2 == u: is_correct = 1 else: is_correct = 0 correct_text = "C'est correct!" incorrect_text = "C'est incorrect." display(correct_text if is_correct else incorrect_text) +def ch4_11ex2c_plot(): + ch4_11_plot(base=[[1, 2], [3, 2]], show_can_base=True, show_grid_lines=True, vector_list=[[5, 4]], + vector_name_list=['w']) + return + + def ch4_11ex2cB1(wB1): w = [5, 4] if wB1 == w: is_correct = 1 else: is_correct = 0 correct_text = "C'est correct!" incorrect_text = "C'est incorrect." display(correct_text if is_correct else incorrect_text) - + + def ch4_11ex2cB2(wB2): - w = [0.5,1.5] + w = [0.5, 1.5] if wB2==w: is_correct = 1 else: is_correct = 0 correct_text = "C'est correct!" incorrect_text = "C'est incorrect." display(correct_text if is_correct else incorrect_text) - + + def ch4_11ex3B1(sB1): s = [13,10] if sB1==s: is_correct = 1 else: is_correct = 0 correct_text = "C'est correct!" incorrect_text = "C'est incorrect." display(correct_text if is_correct else incorrect_text) + def ch4_11ex3B2(sB2): s = [1,4] if sB2==s: is_correct = 1 else: is_correct = 0 correct_text = "C'est correct!" incorrect_text = "C'est incorrect." display(correct_text if is_correct else incorrect_text) - + + def ch4_12ex1(): + style = {'description_width': 'initial'} nbr_ligne = widgets.IntText( description='Nombre de lignes : \n', - disabled=False + disabled=False, + style=style ) nbr_colonne = widgets.IntText( - description='Nombre de colonne : \n', - disabled=False + description='Nombre de colonnes : \n', + disabled=False, + style=style ) button = widgets.Button(description='Vérifier') out = widgets.Output() def callback(e): out.clear_output() r_l = nbr_ligne.value r_c = nbr_colonne.value with out: if r_l == 5 and r_c == 6: display(Markdown("C'est correct!")) else: display(Markdown("C'est incorrect.")) button.on_click(callback) display(nbr_ligne) display(nbr_colonne) display(button) display(out) + def ch4_12ex2(): text = widgets.IntText( description='Réponse :', disabled=False ) button = widgets.Button(description='Vérifier') out = widgets.Output() def callback(e): out.clear_output() r = text.value with out: if r == 2: display(Markdown("C'est correct!")) else: display(Markdown("C'est incorrect.")) button.on_click(callback) display(text) display(button) display(out) + def ch4_12ex3a(): text = widgets.IntText( description='Réponse :', disabled=False ) button = widgets.Button(description='Vérifier') out = widgets.Output() def callback(e): out.clear_output() r = text.value with out: if r == 3: display(Markdown("C'est correct!")) else: display(Markdown("C'est incorrect.")) button.on_click(callback) display(text) display(button) display(out) - + + def ch4_12ex3b(base): - base = base + [[1,4,0,2,0,1],[0,1,1,2,1,0],[0,0,1,3,3,2]] + base = base + [[1, 4, 0, 2, 0, 1], [0, 1, 1, 2, 1, 0], [0, 0, 1, 3, 3, 2]] base = np.array(base) out = widgets.Output() display(out) with out: out.clear_output() feedback = "" is_correct = False - + s = base.shape - if len(base) == 0: feedback = "Les vecteurs nuls ne peuvent pas ." elif len(s) != 2 or s[1] != 6: feedback = "Le format des vecteurs n'est pas bon." elif s[0] < 6: feedback = "L'ensemble entré ne contient pas assez de vecteurs pour engendrer toutes les solutions du système." elif s[0] > 6: feedback = "L'ensemble entré contient trop de vecteurs pour être une famille libre." else: - expected = np.array(sp.Matrix([[1,0,0,0,0,0],[0,1,0,0,0,0],[0,0,1,0,0,0],[0, 0, 0, 1, 0, 0], [0, 0, 0, 0, 1, 0],[0, 0, 0, 0, 0, 1]]).rref()[0]) + expected = np.array(sp.Matrix( + [[1, 0, 0, 0, 0, 0], [0, 1, 0, 0, 0, 0], [0, 0, 1, 0, 0, 0], [0, 0, 0, 1, 0, 0], [0, 0, 0, 0, 1, 0], + [0, 0, 0, 0, 0, 1]]).rref()[0]) actual = np.array(sp.Matrix(base).rref()[0]) - + if not np.array_equal(actual, expected): feedback = "L'ensemble entré n'engendre pas l'espace solution du système." else: is_correct = True - + correct_text = "C'est correct!
" incorrect_text = "C'est incorrect.
" - + display(Markdown((correct_text if is_correct else incorrect_text) + feedback)) diff --git a/Chapitre 9 - Produits scalaires et espaces euclidens/9.10-9.11 La meilleure approximation quadratique.ipynb b/Chapitre 9 - Produits scalaires et espaces euclidens/9.10-9.11 La meilleure approximation quadratique.ipynb index 006c6bc..ebe2504 100644 --- a/Chapitre 9 - Produits scalaires et espaces euclidens/9.10-9.11 La meilleure approximation quadratique.ipynb +++ b/Chapitre 9 - Produits scalaires et espaces euclidens/9.10-9.11 La meilleure approximation quadratique.ipynb @@ -1,296 +1,301 @@ { "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# **Concept(s)-clé(s) et théorie**\n", "\n", "## Proposition 1\n", "Soient $V$ un espace euclidien et $W \\subset V$ un sous-espace vectoriel de $V$. Alors pour tout $x \\in V$ et tout $y \\in W$, on a\n", "\n", "\\begin{equation}\n", "||x - proj_W x|| \\leq ||x-y||\n", "\\end{equation}\n", "\n", "## Définition 1\n", "Soient $V$ un espace euclidien, $W \\subset V$ un sous-espace vectoriel de $V$ et $x \\in V$; considérez aussi le produit scalaire usuel. Alors le vecteur $proj_Wx$ est appelé la **meilleure approximation quadratique** (ou la **meilleure approximation au sens des moindres carrées**) **de $\\boldsymbol{x}$ par un vecteur dans $\\boldsymbol{W}$**." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Exercises et Examples" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import Librairie.AL_Fct as al\n", "import Corrections.corrections as corrections\n", "import numpy as np\n", "import sympy as sp" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Exercice 1\n", "\n", "Soit $V = \\mathbb{R}^n$. Considérez les paires suivantes, faites par un ensemble de vecteurs $\\mathcal{S}$ générant un sous-espace vectoriel $W$ de $V$ et par un élément $v$ de $V$. Calculez la meilleure approximation au sens des moindres carrés de $v$ par un vecteur dans $W$.\n", "\n", "1. $V = \\mathbb{R}^2 \\qquad \\mathcal{S} = \\left\\{ \\begin{pmatrix}1 \\\\ -2\\end{pmatrix} \\right\\} \\qquad \\qquad \\quad \\ v = \\begin{pmatrix} -2 \\\\ 1 \\end{pmatrix}$\n", "2. $V = \\mathbb{R}^3 \\qquad \\mathcal{S} = \\left\\{ \\begin{pmatrix}0 \\\\ 1 \\\\ 0\\end{pmatrix}, \\begin{pmatrix} 1 \\\\ -1 \\\\ 0 \\end{pmatrix} \\right\\} \\qquad \\qquad v = \\begin{pmatrix} -3 \\\\ 2 \\\\ 1 \\end{pmatrix}$\n", "3. $V = \\mathbb{R}^4 \\qquad \\mathcal{S} = \\left\\{ \\begin{pmatrix}1 \\\\ 2 \\\\ -1 \\\\-2 \\end{pmatrix}, \\begin{pmatrix}0 \\\\ 1 \\\\ 0 \\\\-1 \\end{pmatrix} \\right\\} \\qquad \\quad \\ \\ \\ v = \\begin{pmatrix} 0 \\\\ -1 \\\\ 1 \\\\ -1\\end{pmatrix}$" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Aide\n", + "\n", + "Pour calculer la meiileure approximation quadratique de $v$ par un vecteur dans $W$,, il peut être utile de dériver une base ortogonale (ou orthonormée) pour ce dernier. Vous pouvez utiliser la cellule suivante pour exécuter l'algorithme interactif de Gram-Schmidt.\n", + "\n", + "#### Instructions\n", + "\n", + "Pour utiliser la méthode interactive de Gram-Schmidt, procédez comme suit:\n", + "\n", + "1. Insérez le numéro du cas souhaité dans la cellule suivante. Exécutez le cellules appelées \"SÉLECTION DU CAS\" et \"INITIALISATION DES VARIABLES\"\n", + "2. Exécutez la cellule appelée \"SÉLECTION DES PARAMÈTRES\" pour sélectionner le type d'opération et les coefficients nécessaires\n", + "3. Exécutez la cellule appelée \"EXÉCUTER L'ÉTAPE DE L'ALGORITHME GRAM-SCHMIDT\" pour exécuter l'étape de l'algorithme de Gram-Schmidt avec les paramètres précédemment sélectionnés\n", + "4. Répétez les étapes 2 et 3 jusqu'à ce que l'algorithme soit terminée\n", + "\n", + "En outre:\n", + "\n", + "1. Vous pouvez annuler une opération en sélectionnant le bouton \"Revert\".\n", + "\n", + "2. Si les coefficients insérés sont incorrects, vous pouvez essayer avec de nouvelles valeurs sans effectuer une opération \"Revert\".\n", + "\n", + "3. Les coefficients qui ne sont pas liés à l'opération sélectionnée peuvent être définis sur n'importe quelle valeur, car ils ne sont pas utilisés dans le code." + ] + }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ - "case_number = 1" + "# SÉLECTION DU CAS\n", + "case_number = 1 # CHOISISSEZ LE NUMÉRO DE CAS ICI ET EXECUTEZ LA CELLULE SUIVANTE!" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ + "# INITIALISATION DES VARIABLES\n", "if case_number == 1:\n", " S = [[1,-2]]\n", " v = [-2,1]\n", " dim=1\n", "elif case_number == 2:\n", " S = [[0,1,0], [1,-1,0]]\n", " v = [-3,2,1]\n", " dim=2\n", "elif case_number == 3:\n", " S = [[1,2,-1,-2], [0,1,0,-1]]\n", " v = [0,-1,1,-1]\n", " dim=2\n", "else:\n", " print(f\"{case_number} n'est pas un numéro de cas valide!\" \n", " f\"Numéros de cas disponibles: [1,2,3]\")\n", "\n", "step = 0\n", "VectorsList = [S]" ] }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Aide\n", - "\n", - "Pour calculer la meiileure approximation quadratique de $v$ par un vecteur dans $W$,, il peut être utile de dériver une base ortogonale (ou orthonormée) pour ce dernier. Vous pouvez utiliser la cellule suivante pour exécuter l'algorithme interactif de Gram-Schmidt.\n", - "\n", - "#### Instructions\n", - "\n", - "Pour utiliser la méthode interactive de Gram-Schmidt, procédez comme suit:\n", - "\n", - "1. Insérez le numéro de dossier souhaité dans la cellule suivante\n", - "2. Exécutez la cellule appelée \"SÉLECTION DES PARAMÈTRES\" pour sélectionner le type d'opération et les coefficients nécessaires\n", - "3. Exécutez la cellule appelée \"EXÉCUTER L'ÉTAPE DE L'ALGORITHME GRAM-SCHMIDT\" pour exécuter l'étape de l'algorithme de Gram-Schmidt avec les paramètres précédemment sélectionnés\n", - "\n", - "En outre:\n", - "\n", - "1. Vous pouvez annuler une opération en sélectionnant le bouton \"Revert\".\n", - "\n", - "2. Si les coefficients insérés sont incorrects, vous pouvez essayer avec de nouvelles valeurs sans effectuer une opération \"Revert\".\n", - "\n", - "3. Les coefficients qui ne sont pas liés à l'opération sélectionnée peuvent être définis sur n'importe quelle valeur, car ils ne sont pas utilisés dans le code." - ] - }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# SÉLECTION DES PARAMÈTRES\n", "norm_coeff, proj_coeffs, operation, step_number = al.manual_GS(dim=dim)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# EXÉCUTER L'ÉTAPE DE L'ALGORITHME GRAM-SCHMIDT\n", "S = al.interactive_gram_schmidt(norm_coeff, proj_coeffs,\n", " operation, step_number, \n", " S.copy(), VectorsList)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# INSÉREZ ICI LE VALEUR DE LA MEILLEURE APPROXIMATION DE v AU SENS DES MOINDRES CARRÉES DANS W\n", "best_appr = [0, 0] " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "corrections.Ex1Chapitre9_10_11(best_appr, \n", " case_nb=case_number)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Exercice 2\n", "\n", "Soit $V = \\mathcal{C}\\left(I, \\mathbb{R}\\right)$, ou $I$ est un interval dans $\\mathbb{R}$. Considérez les paires suivantes, faites par un ensemble de fonctions $\\mathcal{S}$ générant un sous-espace vectoriel $W$ de $V$ et par un élément $v$ de $V$. Calculez la meilleure approximation au ses des moindres carrés de $v$ par un vecteur dans $W$.\n", "\n", "1. $\\quad \\mathcal{S} = \\left\\{ 1, x \\right\\} = \\mathbb{P}^1(\\mathbb{R}) \\qquad \\qquad \\quad v = |x| \\qquad \\qquad \\ I = [-1,1]$\n", "2. $\\quad \\mathcal{S} = \\left\\{ 1, x, x^2 \\right\\} = \\mathbb{P}^2(\\mathbb{R}) \\qquad \\quad \\ \\ v = |x| \\qquad \\qquad \\ I = [-1,1]$\n", "3. $\\quad \\mathcal{S} = \\left\\{ 1, x, x^2 \\right\\} = \\mathbb{P}^2(\\mathbb{R}) \\qquad \\quad \\ \\ v = sin(x) \\qquad \\quad I = [-\\pi,\\pi]$\n", "4. $\\quad \\mathcal{S} = \\left\\{ 1, x, x^2, x^3 \\right\\} = \\mathbb{P}^3(\\mathbb{R}) \\qquad \\ v = e^x \\qquad \\qquad \\ I=[0,1]$" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ - "case_number=1" + "# SÉLECTION DU CAS\n", + "case_number = 1 # CHOISISSEZ LE NUMÉRO DE CAS ICI ET EXECUTEZ LA CELLULE SUIVANTE!" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ + "# INITIALISATION DES VARIABLES\n", "x = sp.Symbol('x')\n", "if case_number == 1:\n", " S = [1+0*x, x]\n", " v = sp.Abs(x)\n", " int_limits = [-1,1]\n", " dim=2\n", "elif case_number == 2:\n", " S = [1+0*x, x, x**2]\n", " v = sp.Abs(x)\n", " int_limits = [-1,1]\n", " dim=3\n", "elif case_number == 3:\n", " S = [1+0*x, x, x**2]\n", " v = sp.sin(x)\n", " int_limits = [-np.pi,np.pi]\n", " dim=3\n", "elif case_number == 4:\n", " S = [1, x, x**2, x**3]\n", " v = sp.exp(x)\n", " int_limits = [0,1]\n", " dim=4\n", "else:\n", " print(f\"{case_number} n'est pas un numéro de cas valide!\" \n", " f\"Numéros de cas disponibles: [1,2,3,4]\")\n", "\n", "step = 0\n", "VectorsList = [S]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Aide\n", "\n", "Pour calculer la meiileure approximation quadratique de $v$ par un vecteur dans $W$, il peut\n", "aider à dériver une base orthogonale (ou orthonormée) pour ce dernier. Vous pouvez utiliser la cellule suivante pour exécuter l'algorithme interactif de Gram-Schmidt pour fonctions." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# SÉLECTION DES PARAMÈTRES\n", "norm_coeff, proj_coeffs, operation, step_number = al.manual_GS(dim=dim)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# EXÉCUTER L'ÉTAPE DE L'ALGORITHME GRAM-SCHMIDT\n", "S = al.interactive_gram_schmidt_func(norm_coeff, proj_coeffs,\n", " operation, step_number, \n", " S.copy(), VectorsList,\n", " int_limits=int_limits,\n", " weight_function=None)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# INSÉREZ ICI LE VALEUR DE LA MEILLEURE APPROXIMATION DE v AU SENS DES MOINDRES CARRÉES DANS W\n", "best_appr = 1 + 0*x" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "corrections.Ex2Chapitre9_10_11(best_appr, \n", " int_limits=int_limits, \n", " case_nb=case_number)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "[Passez au notebook du chapitre 9.12: Solution au sens du moindres carrées](./9.12%20Solution%20au%20sens%20du%20moindres%20carrées.ipynb)" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" } }, "nbformat": 4, "nbformat_minor": 4 } diff --git "a/Chapitre 9 - Produits scalaires et espaces euclidens/9.12 Solution au sens du moindres carr\303\251es.ipynb" "b/Chapitre 9 - Produits scalaires et espaces euclidens/9.12 Solution au sens du moindres carr\303\251es.ipynb" index 6d1fe02..d8ca310 100644 --- "a/Chapitre 9 - Produits scalaires et espaces euclidens/9.12 Solution au sens du moindres carr\303\251es.ipynb" +++ "b/Chapitre 9 - Produits scalaires et espaces euclidens/9.12 Solution au sens du moindres carr\303\251es.ipynb" @@ -1,407 +1,412 @@ { "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# **Concept(s)-clé(s) et théorie**\n", "\n", "## Définition 1\n", "Soient $A \\in \\mathcal{M}_{m \\times n}(\\mathbb{R})$, $b \\in \\mathcal{M}_{m \\times 1}(\\mathbb{R})$ et $X = \\left(x_1, \\dots, x_n\\right)^T$. Aussi, désignons par $\\phi: \\mathbb{R}^n \\rightarrow \\mathbb{R}^m$ l'application linéaire associée à $A$. Une **solution du système $\\boldsymbol{AX=b}$ au sens du moindres carrées** est une solution du systeme\n", "\n", "\\begin{equation}\n", "AX = proj_{Im(\\phi)}b\n", "\\end{equation}\n", "\n", "## Théorème 1\n", "Soient $A \\in \\mathcal{M}_{m \\times n}(\\mathbb{R})$, $b \\in \\mathcal{M}_{m \\times 1}(\\mathbb{R})$ et $X = \\left(x_1, \\dots, x_n\\right)^T$. Alors une solution du système $AX=b$ au sens du moindres carrées est une solution du système $A^TAX = A^Tb$." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Exercises et Examples" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import Librairie.AL_Fct as al\n", "import Corrections.corrections as corrections\n", "import numpy as np\n", "import sympy as sp" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Exercice 1\n", "\n", "Considérez les systèmes linéaires suivants, avec forme $Ax=b$. Calculez leur solution au sens des moindres carrés en projetant $b$ sur l'espace image de $A$ et en résolvant $Ax = proj_{Im(A)}b$.\n", "\n", "1. $\\quad A = \\begin{pmatrix}1 & 0 \\\\ 1 & 0\\end{pmatrix} \\qquad \\qquad b = \\begin{pmatrix}1 \\\\ 3\\end{pmatrix}$\n", "2. $\\quad A = \\begin{pmatrix}1 & 1 \\\\ 1 & -1 \\\\ 2 & 0\\end{pmatrix} \\qquad \\qquad b = \\begin{pmatrix}1 \\\\ 2 \\\\ -2\\end{pmatrix}$\n", "3. $\\quad A = \\begin{pmatrix}1 & 0 & 0\\\\ 0 & 1 & 1 \\\\ 1 & 0 & 0\\end{pmatrix} \\qquad \\quad \\ \\ b = \\begin{pmatrix}-1 \\\\ 2 \\\\ 1\\end{pmatrix}$\n", "4. $\\quad A = \\begin{pmatrix}1 & 0 & 1\\\\ -1 & 1 & -1 \\\\ 0 & 1 & 1 \\\\ 1 & 1 & 0 \\\\ -1 & 0 & 1\\end{pmatrix} \\qquad \\ \\ b = \\begin{pmatrix}0 \\\\ 2 \\\\ 0 \\\\ 1 \\\\ 4\\end{pmatrix}$" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Aide\n", + "\n", + "Pour calculer la projection orthogonal de $b$ sur $Im(A)$, il peut être utile de dériver une base ortogonale (ou orthonormée) pour ce dernier. Vous pouvez utiliser la cellule suivante pour exécuter l'algorithme interactif de Gram-Schmidt.\n", + "\n", + "#### Instructions\n", + "\n", + "Pour utiliser la méthode interactive de Gram-Schmidt, procédez comme suit:\n", + "\n", + "1. Insérez le numéro du cas souhaité dans la cellule suivante. Exécutez le cellules appelées \"SÉLECTION DU CAS\" et \"INITIALISATION DES VARIABLES\"\n", + "2. Exécutez la cellule appelée \"SÉLECTION DES PARAMÈTRES\" pour sélectionner le type d'opération et les coefficients nécessaires\n", + "3. Exécutez la cellule appelée \"EXÉCUTER L'ÉTAPE DE L'ALGORITHME GRAM-SCHMIDT\" pour exécuter l'étape de l'algorithme de Gram-Schmidt avec les paramètres précédemment sélectionnés\n", + "4. Répétez les étapes 2 et 3 jusqu'à ce que l'algorithme soit terminée\n", + "\n", + "En outre:\n", + "\n", + "1. Vous pouvez annuler une opération en sélectionnant le bouton \"Revert\".\n", + "\n", + "2. Si les coefficients insérés sont incorrects, vous pouvez essayer avec de nouvelles valeurs sans effectuer une opération \"Revert\".\n", + "\n", + "3. Les coefficients qui ne sont pas liés à l'opération sélectionnée peuvent être définis sur n'importe quelle valeur, car ils ne sont pas utilisés dans le code." + ] + }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ - "case_number = 3 # CHOISISSEZ LE NUMÉRO DE CAS ICI ET EXECUTEZ LA CELLULE SUIVANTE!" + "# SÉLECTION DU CAS\n", + "case_number = 1 # CHOISISSEZ LE NUMÉRO DE CAS ICI ET EXECUTEZ LA CELLULE SUIVANTE!" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ + "# INITIALISATION DES VARIABLES\n", "if case_number == 1:\n", " A_cols = [[1,1], [0,0]]\n", " A = np.array(A_cols).T\n", " b = [1,3]\n", " dim=2\n", "elif case_number == 2:\n", " A_cols = [[1,1,2], [1,-1,0]]\n", " A = np.array(A_cols).T\n", " b = [1,2,-2]\n", " dim=2\n", "elif case_number == 3:\n", " A_cols = [[1,0,1], [0,1,0], [0,1,0]]\n", " A = np.array(A_cols).T\n", " b = [-1,2,1]\n", " dim=3\n", "elif case_number == 4:\n", " A_cols = [[1,-1,0,1,-1], [0,1,1,1,0], [1,-1,1,0,1]]\n", " A = np.array(A_cols).T\n", " b = [0,2,0,1,4]\n", " dim=3\n", "else:\n", " print(f\"{case_number} n'est pas un numéro de cas valide!\" \n", " f\"Numéros de cas disponibles: [1,2,3,4]\")\n", "\n", "step = 0\n", "VectorsList = [A_cols]" ] }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Aide\n", - "\n", - "Pour calculer la projection orthogonal de $b$ sur $Im(A)$, il peut être utile de dériver une base ortogonale (ou orthonormée) pour ce dernier. Vous pouvez utiliser la cellule suivante pour exécuter l'algorithme interactif de Gram-Schmidt.\n", - "\n", - "#### Instructions\n", - "\n", - "Pour utiliser la méthode interactive de Gram-Schmidt, procédez comme suit:\n", - "\n", - "1. Insérez le numéro de dossier souhaité dans la cellule suivante\n", - "2. Exécutez la cellule appelée \"SÉLECTION DES PARAMÈTRES\" pour sélectionner le type d'opération et les coefficients nécessaires\n", - "3. Exécutez la cellule appelée \"EXÉCUTER L'ÉTAPE DE L'ALGORITHME GRAM-SCHMIDT\" pour exécuter l'étape de l'algorithme de Gram-Schmidt avec les paramètres précédemment sélectionnés\n", - "\n", - "En outre:\n", - "\n", - "1. Vous pouvez annuler une opération en sélectionnant le bouton \"Revert\".\n", - "\n", - "2. Si les coefficients insérés sont incorrects, vous pouvez essayer avec de nouvelles valeurs sans effectuer une opération \"Revert\".\n", - "\n", - "3. Les coefficients qui ne sont pas liés à l'opération sélectionnée peuvent être définis sur n'importe quelle valeur, car ils ne sont pas utilisés dans le code." - ] - }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# SÉLECTION DES PARAMÈTRES\n", "print(f\"Current vectors: {A_cols}\")\n", "norm_coeff, proj_coeffs, operation, step_number = al.manual_GS(dim=dim)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# EXÉCUTER L'ÉTAPE DE L'ALGORITHME GRAM-SCHMIDT\n", "A_cols = al.interactive_gram_schmidt(norm_coeff, proj_coeffs,\n", " operation, step_number, \n", " A_cols.copy(), VectorsList)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Aide\n", "\n", "Pour résoudre le système linéaire, vous pouvez tirer parti des cellules interactives suivantes qui permettent d'appliquer la méthode d'élimitation de Gauss. Notez que vous devez **entrer la valeur trouvée pour la projection de $\\boldsymbol{b}$ sur l'espace image de $\\boldsymbol{A}$ dans la première ligne!**" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "proj_b = [[0], [2], [0]] # INSEREZ ICI LA VALEUR TROUVÉE POUR LA PROJECTION DE b SUR Im (A)\n", "al.printA(A, proj_b)\n", "[i,j,r,alpha]= al.manualEch(A,proj_b)\n", "m=np.concatenate((A,proj_b), axis=1)\n", "MatriceList=[A]\n", "RhSList=[proj_b]" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "m=al.echelonnage(i,j,r,alpha,A,m,MatriceList,RhSList)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# INSEREZ ICI LA SOLUTION\n", "x,y,z = sp.symbols('x, y, z')\n", "sol = sp.sets.FiniteSet((0,y,2-y))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# ÉVALUATION DE LA SOLUTION\n", "corrections.Ex1_Chapitre9_12(sol, case_nb=case_number)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Exercice 2\n", "\n", "Considérez les systèmes linéaires suivants, avec forme $Ax = b$. Calculez leur solution au sens des moindres carrés en résolvant le système linéaire $A^TAx=A^Tb$.\n", "\n", "1. $\\quad A = \\begin{pmatrix}0 & 1 \\\\ 0 & -1\\end{pmatrix} \\qquad \\qquad b = \\begin{pmatrix}0 \\\\ 2\\end{pmatrix}$\n", "2. $\\quad A = \\begin{pmatrix}0 & 1 \\\\ 1 & 1 \\\\ 1 & -2\\end{pmatrix} \\qquad \\qquad \\ \\ b = \\begin{pmatrix}1 \\\\ 1 \\\\ 0\\end{pmatrix}$\n", "3. $\\quad A = \\begin{pmatrix}0 & 1 & 0\\\\ 1 & 0 & 1 \\\\ -2 & 0 & 0\\end{pmatrix} \\qquad \\quad b = \\begin{pmatrix}0 \\\\ 2 \\\\ -1\\end{pmatrix}$\n", "4. $\\quad A = \\begin{pmatrix}1 & 0 & 0\\\\ 1 & -1 & -1 \\\\ 0 & 1 & 0 \\\\ 1 & 0 & 0 \\\\ 0 & 1 & -1\\end{pmatrix} \\qquad \\ \\ b = \\begin{pmatrix}2 \\\\ 0 \\\\ 0 \\\\ -1 \\\\ -1\\end{pmatrix}$" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ + "# SÉLECTION DU CAS\n", "case_number = 1 # CHOISISSEZ LE NUMÉRO DE CAS ICI ET EXECUTEZ LA CELLULE SUIVANTE!" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ + "# INITIALISATION DES VARIABLES\n", "if case_number == 1:\n", " A_cols = [[0,0], [1,-1]]\n", " A = np.array(A_cols).T\n", " b = [[0], [2]]\n", "elif case_number == 2:\n", " A_cols = [[0,1,1], [1,1,-2]]\n", " A = np.array(A_cols).T\n", " b = [[1], [1], [0]]\n", "elif case_number == 3:\n", " A_cols = [[0,1,-2], [1,0,0], [0,1,0]]\n", " A = np.array(A_cols).T\n", " b = [[0], [2], [-1]]\n", "elif case_number == 4:\n", " A_cols = [[1,1,0,1,0], [0,-1,1,0,1], [0,-1,0,0,-1]]\n", " A = np.array(A_cols).T\n", " b = [2,0,0,-1,-1]\n", "else:\n", " print(f\"{case_number} n'est pas un numéro de cas valide!\" \n", " f\"Numéros de cas disponibles: [1,2,3,4]\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Aide\n", "\n", "Pour résoudre le système linéaire, vous pouvez tirer parti des cellules interactives suivantes qui permettent d'appliquer la méthode d'élimitation de Gauss." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "al.printA(A.T@A, A.T@np.array(b))\n", "[i,j,r,alpha]= al.manualEch(A.T@A, A.T@np.array(b))\n", "m=np.concatenate((A.T@A,A.T@np.array(b)), axis=1)\n", "MatriceList=[A.T@A]\n", "RhSList=[A.T@np.array(b)]" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "m=al.echelonnage(i,j,r,alpha,A.T@A,m,MatriceList,RhSList)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# INSEREZ ICI LA SOLUTION\n", "x,y,z = sp.symbols('x, y, z')\n", "sol = sp.sets.FiniteSet((x,-1)) " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# ÉVALUATION DE LA SOLUTION\n", "corrections.Ex2_Chapitre9_12(sol, case_nb=case_number)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Exercice 3\n", "\n", "Un scénario important dans lequel le calcul des solutions de systèmes linéaires au sens des moindres carrés est utilisé est celui de la **régression linéaire**. Supposons que l'on donne $N$ mesures de certaines quantités $\\left\\{\\left\\{x_j^i\\right\\}_{j=1}^K, y^i\\right\\}_{i=1}^N$; les variables $x$ sont appelées variables explicatives et $K$ est leur nombre. Par exemple, si $K=1$, ils peuvent être le poids ($x_1$) et la taille ($y$) de $N$ personnes différentes; si $K=2$ ils peuvent être le poids ($x_1$), la longueur des pieds ($x_2$) et la taille ($y$) de $N$ personnes différentes.\n", "\n", "L'objectif est de déterminer la meilleure relation linéaire possible entre ces grandeurs, c'est-à-dire de déterminer les meilleures valeurs possibles pour les coefficients $\\left\\{c_j\\right\\}_{j=0}^K$. En termes mathématiques, cela revient à résoudre le problème de minimisation suivant $$ \\left\\{c_j\\right\\}_{j=0}^K = \\underset{\\tilde{c}_0, \\dots, \\tilde{c}_k}{argmin} \\sum\\limits_{i=1}^N \\left(y^i - \\tilde{c}_0 - \\sum\\limits_{j=1}^K \\tilde{c}_j x_j^i \\right)^2$$ \n", "\n", "S'il existe une relation linéaire entre les données, alors $$y^i = c_0 + \\sum\\limits_{j=1}^K c_j x_j^i \\quad \\forall \\ i \\in \\{1, \\dots, N\\}$$ Cela implique que les coefficients $\\left\\{c_j\\right\\}_{j=0}^K$ sont des solutions au système linéaire suivant:\n", "\n", "\\begin{equation}\n", "\\begin{pmatrix}\n", "1 & x_1^1 & x_2^1 & \\dots & x_K^1 \\\\\n", "1 & x_1^2 & x_2^2 & \\dots & x_K^2 \\\\ \n", "\\vdots & \\vdots & \\vdots & \\ddots & \\vdots \\\\\n", "1 & x_1^N & x_2^N & \\dots & x_K^N\n", "\\end{pmatrix}\n", "\\begin{pmatrix}\n", "c_0 \\\\ c_1 \\\\ c_2 \\\\ \\vdots \\\\ c_K\n", "\\end{pmatrix} = \n", "\\begin{pmatrix}\n", "y^1 \\\\ y^2 \\\\ \\vdots \\\\ y^N\n", "\\end{pmatrix}\n", "\\end{equation}\n", "\n", "Quoi qu'il en soit, comme $N$ dans les applications du monde réel est beaucoup plus grand que $K$, il est très probable que ce système n'admette aucune solution. Ainsi, c'est une approche commun de recourir au calcul d'une solution au sens des moindres carrés; on peut en fait prouver que la solution au sens des moindres carrés est égal à la solution au problème de minimisation quadratique susmentionné, dont dérive le nom de \"moindres carrés\".\n", "\n", "### Instructions\n", "e but de l'exercice est de montrer un scénario de cas réel où des solutions des moindres carrés aux systèmes linéaires sont employées; ainsi aucun calcul numérique n'est requis et peu de quantités doivent être correctement insérées.\n", "\n", "1. **GÉNÉRATION DE DONNÉES**: la cellule appelée \"GÉNÉRATION DE DONNÉES\" est responsable de la génération des données. En particulier, la méthode \"Ex3_Chapitre9_12_generate_data\" génère les données en superposant du bruit gaussien blanc à des données linéairement dépendantes. Les deux premiers arguments d'entrée régulent l'intensité du bruit. L'argument d'entrée appelée \"K\" définit le nombre de variables explicatives; les seules valeurs disponibles sont K = 1 et K = 2, de sorte que les données peuvent être visualisées via des nuages de points. Essayez les deux! Finalement, les variables X et Y stockent la matrice de gauche et le vecteur de droite du système linéaire précédemment introduit.\n", "\n", "2. **INSERTION DE SOLUTION**: la cellule appelée \"INSERTION DE SOLUTION\" vous permet de saisir les valeurs de la matrice de gauche M et du vecteur de droite f, définissant le système linéaire à résoudre afin de calculer la solution souhaitée. Dans ce but, nous rappelons que, étant donné deux matrices A, B:\n", " * A.T $\\rightarrow$ calcule la transposée de A\n", " * A @ B $\\rightarrow$ calcule le produit matriciel entre A et B\n", " * A * B $\\rightarrow$ calcule le produit élément par élément entre A et B\n", "\n", "\n", "3. **VISUALISATION DE LA SOLUTION**: la cellule appelée \"VISUALISATION DE LA SOLUTION\" vous permet de visualiser la solution au problème donnée." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# GÉNÉRATION DE DONNÉES\n", - "data, fig = corrections.Ex3_Chapitre9_12_generate_data(0.15, 0.075, case_nb=1)\n", + "data, fig = corrections.Ex3_Chapitre9_12_generate_data(0.15, 0.075, K=2)\n", "X = data[0]\n", "Y = data[1]" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# INSERTION DE SOLUTION\n", - "M = X # !! inserez ici la matrice !!\n", - "f = Y # !! inserez ici le vecteur de droit !!\n", + "M = X.T @ X # !! inserez ici la matrice !!\n", + "f = X.T @ Y # !! inserez ici le vecteur de droit !!\n", "sys = M, f" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "scrolled": false }, "outputs": [], "source": [ "# VISUALISATION DE LA SOLUTION\n", "corrections.Ex3_Chapitre9_12(sys, data, fig)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "[Passez au notebook du chapitre 9.13-9.14: La factorisation QR: application à la résolution d'un système au sens du moindres carrées](./9.13-9.14%20La%20factorisation%20QR%20-%20application%20à%20la%20résolution%20d'un%20système%20au%20sens%20des%20moindres%20carrées.ipynb)" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" } }, "nbformat": 4, "nbformat_minor": 4 } diff --git "a/Chapitre 9 - Produits scalaires et espaces euclidens/9.13-9.14 La factorisation QR - application \303\240 la r\303\251solution d'un syst\303\250me au sens des moindres carr\303\251es.ipynb" "b/Chapitre 9 - Produits scalaires et espaces euclidens/9.13-9.14 La factorisation QR - application \303\240 la r\303\251solution d'un syst\303\250me au sens des moindres carr\303\251es.ipynb" index b37cce5..992f09b 100644 --- "a/Chapitre 9 - Produits scalaires et espaces euclidens/9.13-9.14 La factorisation QR - application \303\240 la r\303\251solution d'un syst\303\250me au sens des moindres carr\303\251es.ipynb" +++ "b/Chapitre 9 - Produits scalaires et espaces euclidens/9.13-9.14 La factorisation QR - application \303\240 la r\303\251solution d'un syst\303\250me au sens des moindres carr\303\251es.ipynb" @@ -1,74 +1,371 @@ { "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# **Concept(s)-clé(s) et théorie**\n", "\n", "## Définition 1\n", "Soit $A \\in \\mathcal{M}_{m \\times n}(\\mathbb{R})$ une matrice dont les colonnes sont linéairement indépendantes (vues comme vecteurs de $\\mathbb{R}^m$). Alors il existe une factorisation du type $A = QR$, où $Q$ est une matrice $m \\times n$ dont les colonnes forment une base orthonormée de l'espace colonnes de $A$ et $R$ est une matrice $n \\times n$ triangulaire supériore, inversible, dont les coefficients diagonaux sont strictement positifs.\n", "\n", "## Algorithme 1\n", "Soit $A \\in \\mathcal{M}_{m \\times n}(\\mathbb{R})$ une matrice dont les colonnes sont linéairement indépendantes (vues comme vecteurs de $\\mathbb{R}^m$). Afin de déterminer une factorisation $QR$ de $A$, on procède comme suit:\n", "1. Poser $\\mathcal{C} = \\left( c_1, \\dots, c_n \\right)$, qui ici forme une base de l'espace colonnes $W$ de $A$\n", "2. A l'aide du procède de Gram-Schmidt, calculer une base orthoormée $\\mathcal{B} = \\left( w_1, \\dots, w_n \\right)$ de $W$\n", "3. Définir $Q \\in \\mathcal{M}_{m \\times n}(\\mathbb{R})$ comme étant la matrice dont la $i$-ème colonne est $w_i$\n", - "4. Pour tout $1 \\leq k \\leq n$, écrire $c_k = r_{1k}w_1 + r_{2k}w_2 + \\dots + r_{kk}w_k + 0w_{k+1} + \\dots + 0w_{n}$. On supposerà que $r_{ii} \\geq 0$, quitte à replacer $w_i$ par $-w_i$). Poser alors $r_k = \\begin{pmatrix} r_{1k} & r_{2k} & \\dots & r_{kk} & 0 & \\dots & 0 \\end{pmatrix}^T$\n", + "4. Pour tout $1 \\leq k \\leq n$, écrire $c_k = r_{1k}w_1 + r_{2k}w_2 + \\dots + r_{kk}w_k + 0w_{k+1} + \\dots + 0w_{n}$. On supposerà que $r_{ii} \\geq 0$, quitte à replacer $w_i$ par $-w_i$. Poser alors $r_k = \\begin{pmatrix} r_{1k} & r_{2k} & \\dots & r_{kk} & 0 & \\dots & 0 \\end{pmatrix}^T$\n", "5. Définir $R \\in \\mathcal{M}_{n \\times n}(\\mathbb{R})$ comme étant la matrice dont la $i$-ème colonne est $r_i$.\n", "\n", + "Donc:\n", + "\n", + "$$ Q = \\left( \\begin{array}{@{}c|c|c|c@{}} w_1 & w_2 & \\dots & w_n \\end{array}\\right) \\qquad \n", + "R = \\begin{pmatrix} \\langle c_1, w_1 \\rangle & \\langle c_2, w_1 \\rangle & \\langle c_3, w_1 \\rangle & \\dots & \\langle c_n, w_1 \\rangle \\\\\n", + "0 & \\langle c_2, w_2 \\rangle & \\langle c_3, w_2 \\rangle & \\dots & \\langle c_n, w_2 \\rangle \\\\ \n", + "0 & 0 & \\langle c_3, w_3 \\rangle & \\dots & \\langle c_n, w_2 \\rangle \\\\ \n", + "\\vdots & \\vdots & \\vdots & \\ddots & \\vdots \\\\ \n", + "0 & 0 & 0 & \\dots & \\langle c_n, w_n \\rangle \\end{pmatrix}$$\n", + "\n", "## Proposition 1\n", "Soit $Q \\in \\mathcal{M}_{m \\times n}(\\mathbb{R})$ une matrice dont les colonnes forment une base orthonormée de l'espace des colonnes $W$ de $Q$. Alors \n", "\n", "$$QQ^Tb = proj_Wb$$ \n", "\n", "pour tout $b \\in \\mathcal{M}_{m \\times 1}(\\mathbb{R})$.\n", "\n", "## Proposition 2\n", - "Soit $A \\in \\mathcal{M}_{m \\times n}(\\mathbb{R})$ une matrice dont les colonnes sont linéairement indépendantes et soit $A = QR$ une factorisation $QR$ de $A$. Alors pour tout $b \\in \\mathcal{M}_{m \\times 1}(\\mathbb{R})$, l'equation $AX=b$ admet une unique solution au sens du moindres carrées, sonnée par la formule\n", + "Soit $A \\in \\mathcal{M}_{m \\times n}(\\mathbb{R})$ une matrice dont les colonnes sont linéairement indépendantes et soit $A = QR$ une factorisation $QR$ de $A$. Alors pour tout $b \\in \\mathcal{M}_{m \\times 1}(\\mathbb{R})$, l'equation $Ax=b$ admet une unique solution au sens du moindres carrées, donnée par la formule\n", + "\n", + "$$ \\hat{x} = R^{-1} Q^T b$$\n", + "\n", + "Également, la solution du système au sens du moindres carrée peut être calculée en résolvant le système linéaire traingulaire supérieur suivant\n", + "\n", + "$$ R\\hat{x} = Q^T b$$\n", "\n", - "$$ \\hat{X} = R^{-1} Q^T b$$" + "\n", + "\n", + "En utilisant la méthode de substitution vers l'arrière, il n'est alors pas nécessaire de dériver explicitement l'espression de l'inverse de R." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Exercises et Examples" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import Librairie.AL_Fct as al\n", "import Corrections.corrections as corrections\n", "import numpy as np\n", - "import sympy as sp" + "import sympy as sp\n", + "import time\n", + "from scipy.linalg import solve_triangular, lu" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Exercice 1\n", + "Considérez les matrices suivantes. En tirant parti des cellules interactives suivantes, appliquez l'algorithme de Gram-Schmidt et dérivez leur factorisation QR.\n", + "\n", + "1. $\\qquad A_1 = \\begin{pmatrix} 1 & 2 & 0 \\\\ 0 & 1 & 1 \\\\ 1 & 0 & 1 \\end{pmatrix}$\n", + "2. $\\qquad A_2 = \\begin{pmatrix} 1 & 1 \\\\ 0 & 2 \\\\ 0 & -2 \\\\ 0 & -1 \\end{pmatrix}$\n", + "3. $\\qquad A_3 = \\begin{pmatrix} -3 & 1 & 0 \\\\ 1 & 1 & 1 \\\\ 5 & 0 & 2 \\\\ -1 & -1 & 0 \\end{pmatrix}$ \n", + "4. $\\qquad A_4 = \\begin{pmatrix} -4 & 20 & 35 & 5 \\\\ -4 & -30 & -15 & 55 \\\\ -8 & 40 & -80 & -65 \\\\ 23 & -15 & 30 & 15 \\end{pmatrix}$" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Instructions\n", + "\n", + "Pour utiliser la méthode interactive pour la factorisation QR, procédez comme suit:\n", + "\n", + "1. Insérez le numéro de cas souhaité dans la cellule appelèe \"SÉLECTION DU CAS\". Exécutez le cellules appelées \"SÉLECTION DU CAS\" et \"INITIALISATION DES VARIABLES\"\n", + "2. Exécutez la cellule appelée \"SÉLECTION DES PARAMÈTRES\" pour insérer les coefficients necessaires\n", + "3. Exécutez la cellule appelée \"EXÉCUTER L'ÉTAPE DE L'ALGORITHME\" pour exécuter l'étape de l'algorithme avec les paramètres précédemment sélectionnés\n", + "4. Répétez les étapes 2 et 3 jusqu'à ce que la factorisation soit terminée" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# SÉLECTION DU CAS\n", + "case_number = 1 # CHOISISSEZ LE NUMÉRO DE CAS ICI ET EXECUTEZ LA CELLULE SUIVANTE!" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# INITIALISATION DES VARIABLES\n", + "if case_number == 1: \n", + " A = np.array([[1,2,0], [0,1,1], [1,0,1]])\n", + "elif case_number == 2:\n", + " A = np.array([[1,1], [0,2], [0,-2], [0,-1]])\n", + "elif case_number == 3:\n", + " A = np.array([[-3,1,0], [1,1,1], [5,0,2], [-1,-1,0]])\n", + "elif case_number == 4:\n", + " A = np.array([[-4,20,35,5], [-4,-30,-15,55], [-8,40,-80,-65], [23,-15,30,15]])\n", + "else:\n", + " print(f\"{case_number} n'est pas un numéro de cas valide!\" \n", + " f\"Numéros de cas disponibles: [1,2,3,4]\")\n", + " \n", + "Q = np.full(A.shape, np.nan)\n", + "R = np.full((A.shape[1],A.shape[1]), np.nan)\n", + "QList = [Q]\n", + "RList = [R]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# SÉLECTION DES PARAMÈTRES\n", + "norm_coeff, proj_coeffs = al.manual_QR(QList[-1], RList[-1], A)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# EXÉCUTER L'ÉTAPE DE L'ALGORITHME\n", + "al.interactive_QR(norm_coeff, proj_coeffs, QList, RList, A)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "8/25" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Exercice 2\n", + "\n", + "Considérez les systèmes linéaires suivants, avec forme $Ax=b$. Calculez leur solution au sens des moindres carrés en tirant parti de la factorisation QR de A. \n", + "\n", + "1. $\\quad A = \\begin{pmatrix}1 & 1 \\\\ 1 & -1\\end{pmatrix} \\qquad \\qquad \\ \\ b = \\begin{pmatrix}1 \\\\ -1\\end{pmatrix}$\n", + "2. $\\quad A = \\begin{pmatrix}-2 & 1 \\\\ 1 & 3 \\\\ 2 & 0\\end{pmatrix} \\qquad \\qquad \\quad b = \\begin{pmatrix}1 \\\\ 2 \\\\ 0\\end{pmatrix}$\n", + "3. $\\quad A = \\begin{pmatrix}1 & 0 & 0\\\\ 2 & 0 & 1 \\\\ 0 & 4 & 0 \\\\ 0 & 3 & 2 \\\\ 2 & 0 & 2 \\end{pmatrix} \\qquad \\qquad \\ b = \\begin{pmatrix}0 \\\\ 1 \\\\ 0 \\\\ -1 \\\\ 1 \\end{pmatrix}$\n", + "4. $\\quad A = \\begin{pmatrix}5 & 0 & 0\\\\ -1 & -2 & -2 \\\\ 1 & 1 & 2 \\\\ 3 & 2 & 0 \\\\ 0 & 0 & 1\\end{pmatrix} \\qquad \\ \\ b = \\begin{pmatrix}0 \\\\ 2 \\\\ 0 \\\\ 1 \\\\ 4\\end{pmatrix}$\n", + "\n", + "### !! ATTENTION !!\n", + "Si $A$ ne correspond pas aux exigences de la *Définition 1*, insérez **None** pour les valeurs des $Q$, $R$ et de la solution $\\hat{x}$ au système linéaire donnée au sens des moindres carrés!" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# SÉLECTION DU CAS\n", + "case_number = 1 # CHOISISSEZ LE NUMÉRO DE CAS ICI ET EXECUTEZ LA CELLULE SUIVANTE!" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# INITIALISATION DES VARIABLES\n", + "if case_number == 1:\n", + " A_cols = [[1,1], [1,-1]]\n", + " A = np.array(A_cols).T\n", + " b = [1,-1]\n", + " dim=2\n", + "elif case_number == 2:\n", + " A_cols = [[-2,1,2], [1,3,0]]\n", + " A = np.array(A_cols).T\n", + " b = [1,2,0]\n", + " dim=2\n", + "elif case_number == 3:\n", + " A_cols = [[1,2,0,0,2], [0,0,4,3,0], [0,1,0,2,2]]\n", + " A = np.array(A_cols).T\n", + " b = [0,1,0, -1, 1]\n", + " dim=3\n", + "elif case_number == 4:\n", + " A_cols = [[5,-1,1,3,0], [0,-2,1,2,0], [0,-2,2,0,1]]\n", + " A = np.array(A_cols).T\n", + " b = [0,2,0,1,4]\n", + " dim=3\n", + "else:\n", + " print(f\"{case_number} n'est pas un numéro de cas valide!\" \n", + " f\"Numéros de cas disponibles: [1,2,3,4]\")\n", + "\n", + "step = 0\n", + "VectorsList = [A_cols]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Aide\n", + "\n", + "Pour calculer la factorisation QR de $A$, vous pouvez exécutez la cellule suivant. Si $A$ ne correspond pas aux exigences de la *Définition 1*, supprimez le résultat de cette cellule et insérez **None** comme réponse dans la suivante." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "Q1, R1 = al.unique_qr(A)\n", + "al.printA(Q1, name=\"Q\")\n", + "al.printA(R1, name=\"R\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Aide\n", + "\n", + "Pour calculer la solution du système linéaire $R\\hat{x}=Q^Tb$, plutôt que de dériver explicitement l'expression pour l'inverse de $R$, utilisez la **méthode de substitution vers l'arrière**!" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# INSÉREZ LES VALEURS DE Q,R ET DE LA SOLUTION AU SYSTÈME AU SENS DES MOINS CARREÉS\n", + "Q = Q1 # None\n", + "R = R1 # None\n", + "sol = sp.sets.FiniteSet((0, 0, 0)) # None" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "corrections.Ex2Chapitre9_13_14(Q, R, sol, case_nb=case_number)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Exemple 1\n", + "\n", + "Dans le chapitre 2, tout en considérant la décomposition LU, nous avons signalé qu'un cas d'utilisation très important est celui où plusieurs systèmes linéaires, présentant tous la même matrice de gauche mais différents vecteurs de droite, doivent être résolus.\n", + "\n", + "De la même manière, la factorisation QR est extrêmement utile lorsque plusieurs systèmes linéaires doivent être résolus au sens des moindres carrés. En effet, une fois la factorisation effectuée, il suffit de résoudre un seul système linéaire triangulaire supérieur (i.e. $R\\hat{x} = Q^Tb$) pour dériver la solution souhaitée.\n", + "\n", + "Pour des systèmes suffisamment grands, cette approche s'avère plus rapide à la fois que la solution directe des différents systèmes (c'est-à-dire résoudre à chaque étape $A^TAx = A^Tb$, avec $A^TA$ étant calculé une fois pour toutes au début) et que l'application de la décomposition LU (dans ce cas, utilisée avec pivotement partiel pour gagner en performances supplémentaires!) à la matrice $A^TA$.\n", + "\n", + "**Exécutez la cellule suivante et évaluez les différences de performances ... cela peut prendre quelques secondes**" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "M, N = 1000, 500 # dimension des systèmes linéaires à résoudre au sens des moindres carrés\n", + "Nt = 10000 # nombre de systèmes linéaires à résoudre au sens des moindres carrés\n", + "A = np.random.rand(M, N);\n", + "A_ls = A.T @ A\n", + "\n", + "print(\"----------------------------Factorisation QR----------------------------------\")\n", + "start = time.time()\n", + "Q, R = np.linalg.qr(A, mode=\"reduced\")\n", + "time_qr = time.time() - start\n", + "print(\"Temps d'exécution: % f s\" %(time_qr))\n", + "\n", + "print(\"----------------------------Factorisation LU--------------------------------\")\n", + "start = time.time()\n", + "P, L, U = lu(A_ls)\n", + "time_lu = time.time() - start\n", + "print(\"Temps d'exécution: % f s\" %(time_lu))\n", + "\n", + "print(\"\\n-------Résolution du systèmes linéaires au sens des moindres carreés--------\")\n", + "\n", + "# résoudre sans utiliser les factorisations QR et LU\n", + "start = time.time()\n", + "for cnt in range(Nt):\n", + " b = np.random.rand(M,1)\n", + " x = np.linalg.solve(A_ls, A.T@b)\n", + "time_no_fact = time.time() - start\n", + "print(\"Sans factorisation: temps d'exécution: % f s\" %time_no_fact)\n", + "\n", + "# résoudre en utilisant la factorisation LU\n", + "start = time.time()\n", + "for cnt in range(Nt):\n", + " b = np.random.rand(M,1)\n", + " y = solve_triangular(L, P@(A.T@b))\n", + " x = solve_triangular(U, y)\n", + "time_lu += time.time() - start\n", + "\n", + "print(\"Avec factorisation LU: temps d'exécution: % f s\" % time_lu)\n", + "\n", + "# résoudre en utilisant la factorisation QR\n", + "start = time.time()\n", + "for cnt in range(Nt):\n", + " b = np.random.rand(M,1)\n", + " x = solve_triangular(R, Q.T@b)\n", + "time_qr += time.time() - start\n", + "\n", + "print(\"Avec factorisation QR: temps d'exécution: % f s\" %time_qr)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Vous pouvez comparer les temps d'exécution pour différentes tailles de matrice (c'est-à-dire changer les paramètres M et N) et pour un nombre différent de systèmes lineaires (c'est-à-dire changer le paramètre N_t)**" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" } }, "nbformat": 4, "nbformat_minor": 4 } diff --git "a/Chapitre 9 - Produits scalaires et espaces euclidens/9.2 - Produit scalaires, d\303\251finitions, exemples.ipynb" "b/Chapitre 9 - Produits scalaires et espaces euclidens/9.2 - Produit scalaires, d\303\251finitions, exemples.ipynb" index a5faaea..4dfbaa5 100644 --- "a/Chapitre 9 - Produits scalaires et espaces euclidens/9.2 - Produit scalaires, d\303\251finitions, exemples.ipynb" +++ "b/Chapitre 9 - Produits scalaires et espaces euclidens/9.2 - Produit scalaires, d\303\251finitions, exemples.ipynb" @@ -1,253 +1,193 @@ { "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# **Concept(s)-clé(s) et théorie**\n", "\n", "## Définition 1 - Produit Scalaire\n", "Soit $V$ un $\\mathbb{R}$-espace vectoriel. Un **produit scalaire** sur $V$ est une application qui fait correspondre à chaque paire ordonnée $(u,v) \\in V \\times V$ un nombre réel, noté $\\langle u, v \\rangle \\in \\mathbb{R}$, telle que les conditions suivantes soient vérifiées, pour tous $u,v,w \\in V, \\alpha \\in \\mathbb{R}$:\n", "\n", "1. *Symmétrie*: $\\langle u,v \\rangle = \\langle v, u \\rangle$\n", "2. *Additivité*: $\\langle u+v, w \\rangle = \\langle u,w \\rangle + \\langle v,w \\rangle$\n", "3. *Bilinearité (combinè avec 2)*: $\\langle \\alpha u, v \\rangle = \\alpha \\langle u,v \\rangle = \\langle u, \\alpha v \\rangle$\n", "4. *Definié Positivité*: $\\langle u,u \\rangle \\geq 0 \\ \\forall u \\in V$ et si $\\langle u,u \\rangle = 0$ alors $u=0$.\n", "\n", "## Définition 2 - Espace Euclidien\n", "Un $\\mathbb{R}$-espace vectoriel *de dimension finie* muni d'un produit scalaire s'appelle un **espace euclidien**.\n", "\n", "## Définition 3 - Orthogonalité\n", "Soiet $V$ un $\\mathbb{R}$-espace vectoriel muni d'un produit scalaire $\\langle \\cdot,\\cdot \\rangle$ et $u,v \\in V$. On dit que $u$ et $v$ sont **orthogonaux** si $\\langle u,v \\rangle = 0$. \n", "\n", "## Example 1 \n", "Un example de produit scalaire dans $V = \\mathcal{M}_{n \\times n}(\\mathbb{R})$ est: $$ \\langle A,B \\rangle = Trace(A^TB)$$ ou la trace d'un matrice carée de dimension $n$ est definie comme suit: $$Trace(A) = \\sum\\limits_{i=1}^n a_{ii}$$\n", "\n", "## Example 2\n", "Un example de produit scalaire dans $V = \\mathcal{C}^0([a;b], \\mathbb{R}) =: \\{f: [a;b] \\rightarrow \\mathbb{R} : f \\ fonction \\ continue\\}$ (avec $[a;b]$ un intervalle de $\\mathbb{R}$) est: $$ \\langle f,g \\rangle = \\int_a^b f(x)g(x) \\ dx$$ où $\\int_a^b f(x) \\ dx$ désigne l'intégrale de Riemann de $f$ dans l'intervalle $[a;b]$." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Exercises et Exemples" ] }, { "cell_type": "code", - "execution_count": 1, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - " \n", - " " - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ "import Librairie.AL_Fct as al\n", "import Corrections.corrections as corrections\n", "import numpy as np\n", "import matplotlib.pyplot as plt" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Exercise 1\n", "Considérez les couples de $\\mathbb{R}$-espaces vectoriels et d'opérateurs suivants et marquez ceux des déclarations suivantes qui sont corrects.\n", "\n", "1. $\\big(\\mathbf{V}, \\langle a, b \\rangle\\big) = \\big(\\mathbb{R}^3, \\ a_1b_1 - a_1b_2 - a_2b_1 + a_2b_2 - a_2b_3 - a_3b_2 + 2a_3b_3\\big)$\n", "2. $\\big(\\mathbf{V}, \\langle a, b \\rangle\\big) = \\big(\\mathbb{R}^3, \\ a^TMb\\big) \\qquad$ with $M = \\begin{pmatrix} 2 & 0 & 1\\\\ 0 & 2 & -1 \\\\ -1 & -1 & 2 \\end{pmatrix}$\n", "3. $\\big(\\mathbf{V}, \\langle a, b\\rangle\\big) = \\big(\\mathbb{P}^2(\\mathbb{R}), \\ 2c^a_0c^b_0 - c^a_0c^b_2 + c^a_1c^b_1 - c^a_2c^b_0 + c^a_2c^b_2\\big) \\qquad$ with $a(x) =: c^a_2 x^2 + c^a_1 x + c^a_0 \\ $ and $ \\ b(x) =: c^b_2x^2 + c^b_1 x + c^b_0$\n", "4. $\\big(\\mathbf{V}, \\langle a, b\\rangle\\big) = \\big(\\mathbb{P}^3(\\mathbb{R}), \\ 2c^a_0c^b_0 - c^a_0c^b_2 + c^a_1c^b_1 - c^a_2c^b_0 + c^a_2c^b_2\\big) \\qquad$ with $a(x) =: c^a_3 x^3 + c^a_2 x^2 + c^a_1 x + c^a_0 \\ $ and $ \\ b(x) =: c^a_3 x^3 + c^b_2x^2 + c^b_1 x + c^b_0$\n", "5. $\\big(\\mathbf{V}, \\langle a, b\\rangle\\big) = \\big(\\mathcal{C}^1([x_0, x_1]; \\mathbb{R}), \\ a(x_0)b(x_0) + \\int_{x_0}^{x_1} a'(x)b'(x) \\ dx \\big)$\n", "6. $\\big(\\mathbf{V}, \\langle a, b\\rangle\\big) = \\big(\\mathcal{C}^2([x_0, x_1]; \\mathbb{R}), \\ a(x_0)b(x_0) + \\int_{x_0}^{x_1} a''(x)b''(x) \\ dx \\big)$\n", "\n", "### Remarques\n", "- $\\mathbb{P}^n(\\mathbb{R})$ désigne l'ensemble des polynômes a valeurs réelles de degré au plus $n$, qui peuvent alors être exprimés de manière unique en termes de $n+1$ coefficients scalaires\n", "- $\\mathcal{C}^n([x_0, x_1]; \\mathbb{R})$ désigne l'ensemble des fonctions à valeurs réelles qui sont $n$-fois différenciables, avec toutes les dérivées continues, sur l'intervalle $[x_0; x_1]$" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "corrections.Ex1Chapitre9_2()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Exercise 2\n", "Étant donné les couples de matrices suivants, déterminez celles qui sont orthogonales par rapport au produit scalaire défini via l'opérateur de trace\n", "\n", "1. $ \\qquad A = \\begin{pmatrix} 1 & 2 & 0 \\\\ 0 & -1 & -1 \\\\ 1 & 3 & 1 \\end{pmatrix} \\qquad \\quad \\ \\ \n", " B = \\begin{pmatrix} 1 & -1 & 4 \\\\ 3 & 1 & -2 \\\\ 1 & 0 & -1 \\end{pmatrix}$ \n", "2. $ \\qquad A = \\begin{pmatrix} 0 & 2 \\\\ -1 & 3 \\end{pmatrix} \\qquad \\qquad \\quad\n", " B = \\begin{pmatrix} 3 & 1 \\\\ 1 & -1 \\end{pmatrix}$\n", "3. $ \\qquad A = \\begin{pmatrix} 0 & 1 & 3 & 0 \\\\ 1 & 0 & 1 & 0 \\\\ -1 & -2 & 2 & 1 \\\\ 3 & 4 & 1 & 2 \\end{pmatrix} \\qquad\n", " B = \\begin{pmatrix} 3 & 1 & -1 & 2 \\\\ 2 & 2 & 0 & 1 \\\\ -1 & 1 & -1 & 3 \\\\ -1 & 1 & 1 & -1 \\end{pmatrix}$\n", "4. $ \\qquad A = \\begin{pmatrix} 1 & -3 \\\\ 2 & 1 \\end{pmatrix} \\qquad \\qquad \\quad\n", " B = \\begin{pmatrix} 1 & -2 \\\\ -2 & 1 \\end{pmatrix}$" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "corrections.Ex2Chapitre9_2()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Exercise 3\n", "Étant donné les couples de fonctions suivants, déterminez s'ils sont orthogonaux par rapport au produit scalaire donné\n", "\n", "1. $\\qquad f(x) = 1 - x^2; \\quad g(x) = -(x-1)^2 \\qquad \\quad with: \\quad \\langle f, g \\rangle = c^f_0c^g_0 + c^f_1c^g_1 + c^f_2c^g_2$\n", "2. $\\qquad f(x) = 1 - x^2; \\quad g(x) = -(x-1)^2 \\qquad \\quad with: \\quad \\langle f, g \\rangle = \\int_{-1}^{1} f(x)g(x) \\ dx$\n", "3. $\\qquad f(x) = x - \\dfrac{1}{2}; \\quad g(x) = x^2 - x - \\dfrac{1}{6} \\qquad \\ \\ with: \\quad \\langle f, g \\rangle = 2c^f_0c^g_0 - c^f_0c^g_0 + 2c^f_1c^g_1 - c^f_1c^g_2 - c^f_2c^g_0 - c^f_2c^g_1 + 2c^f_2c^g_2$\n", "4. $\\qquad f(x) = x - \\dfrac{1}{2}; \\quad g(x) = x^2 - x - \\dfrac{1}{6} \\qquad \\ \\ with: \\quad \\langle f, g \\rangle = \\int_0^1 f(x)g(x) \\ dx$\n", "5. $\\qquad f(x) = \\sin(x); \\quad \\ g(x) = \\cos(x) \\qquad \\qquad \\ \\ with: \\quad \\langle f,g \\rangle = \\int_{-\\pi}^{\\pi} f(x)g(x) \\ dx$\n", "6. $\\qquad f(x) = \\sin(x); \\quad \\ g(x) = \\cos(x) \\qquad \\qquad \\ \\ with: \\quad \\langle f,g \\rangle = \\int_{0}^{\\pi / 2} f(x)g(x) \\ dx$" ] }, { "cell_type": "code", - "execution_count": 2, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Cliquer sur CTRL pour sélectionner plusieurs réponses\n" - ] - }, - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "daffcdc93f9841568bb81cbcff6cbcad", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "interactive(children=(SelectMultiple(description='Les fonctions sont orthogonales dans les cas:', layout=Layou…" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ "corrections.Ex3Chapitre9_2()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Aide**: Vous pouvez vous aider en exécutant les cellules suivantes, qui permettent de tracer les 3 couples de fonctions considérés dans l'exercice 3" ] }, { "cell_type": "code", - "execution_count": 3, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "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\n", - "text/plain": [ - "
" - ] - }, - "metadata": { - "needs_background": "light" - }, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ "# CASES 1-2\n", "corrections.Ex3Chapitre9_2_plotter(case_nb=1)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# CASES 3-4\n", "corrections.Ex3Chapitre9_2_plotter(case_nb=3)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# CASES 5-6\n", "corrections.Ex3Chapitre9_2_plotter(case_nb=5)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "[Passez au notebook du chapitre 9.3-9.4: Norme, inegalité de Cauchy-Schwarz, orthogonalité, inegalité du triangle, Pythagore](./9.3-9.4%20Norme%2C%20inégalité%20de%20Cauchy-Schwarz%2C%20orthogonalité%2C%20inegalité%20du%20triangle%2C%20Pythagore.ipynb)" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" } }, "nbformat": 4, "nbformat_minor": 4 } diff --git "a/Chapitre 9 - Produits scalaires et espaces euclidens/9.6-9.7 Comment trouver une base orthogonale au orthonormale - Le proc\303\251d\303\251 de Gram-Schmidt.ipynb" "b/Chapitre 9 - Produits scalaires et espaces euclidens/9.6-9.7 Comment trouver une base orthogonale au orthonormale - Le proc\303\251d\303\251 de Gram-Schmidt.ipynb" index 280c74c..d325183 100644 --- "a/Chapitre 9 - Produits scalaires et espaces euclidens/9.6-9.7 Comment trouver une base orthogonale au orthonormale - Le proc\303\251d\303\251 de Gram-Schmidt.ipynb" +++ "b/Chapitre 9 - Produits scalaires et espaces euclidens/9.6-9.7 Comment trouver une base orthogonale au orthonormale - Le proc\303\251d\303\251 de Gram-Schmidt.ipynb" @@ -1,356 +1,361 @@ { "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# **Concept(s)-clé(s) et théorie**\n", "\n", "## Proposition 1\n", "Soient $V$ un $\\mathbb{R}$-espace vectoriel muni d'un produit scalaire $\\langle \\cdot,\\cdot\\rangle$ et $S = \\{v_1, \\dots, v_k\\} \\subset V$ une famille orthogonale de vecteurs non-nuls. Alors $S$ est une famille libre.\n", "\n", "## Définition 1\n", "Soit $V$ un $\\mathbb{R}$-espace vectoriel muni d'un produit scalaire $\\langle \\cdot,\\cdot\\rangle$. Pour $u,v \\in V$, on définit la **projection orthogonale de $u$ sur $v$** par $$ proj_v u = \\dfrac{\\langle u,v \\rangle}{\\langle v,v \\rangle} v = \\dfrac{\\langle u,v \\rangle}{||v||^2}v$$\n", "\n", "## Proposition 2\n", "Soit $V$ un $\\mathbb{R}$-espace vectoriel muni d'un produit scalaire $\\langle \\cdot,\\cdot\\rangle$. Alors les affirmations suivantes sont vérifiée:\n", "\n", "1. Pour tous $u,v \\in V$ le vecteur $proj_v u \\in V$ appartient à $Vect(\\{v\\})$\n", "2. Pour tous $u,v \\in V$, on a $\\langle u - proj_v u, v \\rangle = 0$\n", "\n", "## Théorème 1: Procédé de Gram-Schmidt\n", "Soient $V$ un $\\mathbb{R}$-espace vectoriel muni d'un produit scalaire $\\langle \\cdot,\\cdot \\rangle$ et $S = \\{x_1, \\dots, x_k\\}$ une famille de vecteurs dans $V$. Alors en posant successivement\n", "\\begin{equation}\n", "\\begin{aligned}\n", "v_1 &= x_1 \\\\\n", "v_2 &= x_2 - proj_{v_1}x_2 \\\\\n", "v_3 &= x_3 - proj_{v_1}x_3 - proj_{v_2}x_3 \\\\\n", "\\vdots & \\qquad \\\\\n", "v_k &= x_k - proj_{v_1}x_k - proj_{v_2}x_k - \\cdots - proj_{v_{k-1}}x_k\n", "\\end{aligned}\n", "\\end{equation}\n", "la famille $\\{v_1, \\dots, v_k\\}$ ainsi obtenue est une famille orthogonale.\n", "\n", "## Théorème 2: Proprietes du procédé de Gram-Schmidt\n", "Soient $V$ un $\\mathbb{R}$-espace vectoriel muni d'un produit scalaire $\\langle \\cdot,\\cdot \\rangle$ et $S = \\{x_1, \\dots, x_k\\}$ une famille de vecteurs *linéarment indépentants* dans $V$. La procédé de Gram-Schmidt applique à la famille $S$ définit une suite de vecteurs $v_1, \\dots v_k$ telle que $\\{v_1, \\dots, v_k\\}$ est une famille de vecteurs deux-à-deux ortogonaux, non-nuls et donc linéarment indédendants. De plus, on a $$ Vect(S) = Vect(v_1, \\dots, v_k)$$\n", "\n", "## Remarque 1\n", "1. Si $\\{x_1, \\dots, x_k\\}$ est une base de $V$, le procédé de Gram-Schmidt donne une base orthogonale $\\{v_1,\\dots, v_k\\}$ de $V$.\n", "2. Si l'on souhaite avoir une base orthonormale de $V$, il suffit de normaliser la base obtenue." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Exercises et Exemples" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import Librairie.AL_Fct as al\n", "import Corrections.corrections as corrections\n", "import numpy as np\n", "import sympy as sp" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Exercice 1\n", "\n", "Considérez les $\\mathbb{R}$-espaces vectoriels suivants, équipés avec le produit scalaire donnè. Considérez les éléments donnés $u$ et $v$ de ces espaces. Calculez la projection orthogonale de $u$ sur $v$.\n", "\n", "1. $\\quad$ $V = \\mathbb{R}^2$; $\\qquad \\qquad$ $\\langle u,v \\rangle_V = u^Tv$; $\\qquad \\qquad \\qquad \\qquad \\qquad \\qquad \\quad$ $u = \\begin{pmatrix} -1 \\\\ 2 \\end{pmatrix}$ $\\qquad \\ \\ $ $v = \\begin{pmatrix} 1 \\\\ 1 \\end{pmatrix}$\n", "\n", "2. $\\quad$ $V = \\mathbb{R}^3$; $\\qquad \\qquad$ $\\langle u,v \\rangle_V = u^TAv \\quad avec \\ \\ A=\\begin{bmatrix} 2 & 0 & -1\\\\ 0 & 1 & 0\\\\ -1 & 0 & 2 \\end{bmatrix}$ $\\qquad$ $u = \\begin{pmatrix} 1 \\\\ 2 \\\\ 1 \\end{pmatrix}$ $\\qquad \\quad \\ \\ $ $v = \\begin{pmatrix} 0 \\\\ 1 \\\\ -1 \\end{pmatrix}$\n", "\n", "3. $\\quad$ $V = \\mathbb{R}^5$; $\\qquad \\qquad$ $\\langle u,v \\rangle_V = u^Tv$; $\\qquad \\qquad \\qquad \\qquad \\qquad \\qquad \\quad \\ $ $u = \\begin{pmatrix} -1 \\\\ 0 \\\\ 1 \\\\ 0 \\\\ 1 \\end{pmatrix}$ $\\qquad \\quad $ $v = \\begin{pmatrix} 0 \\\\ 1 \\\\ -1 \\\\ 1 \\\\ 0 \\end{pmatrix}$\n", "\n", "4. $\\quad$ $V = \\mathbb{P}^2(\\mathbb{R})$; $\\qquad \\quad$ $\\langle f,g \\rangle_V = \\int_0^1 f(x)g(x) \\ dx$; $\\qquad \\qquad \\qquad \\qquad \\ \\ \\ $ $f(x) = x^2 - 1$ $\\qquad$ $g(x) = x$\n", "\n", "5. $\\quad$ $V = \\mathbb{P}^3(\\mathbb{R})$; $\\qquad \\quad$ $\\langle f,g \\rangle_V = \\int_{-1}^1 (1-x^2)f(x)g(x) \\ dx$; $\\qquad \\qquad \\quad \\ \\ \\ $ $f(x) = x^2 -x$ $\\qquad$ $g(x)=x+1$" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# CAS 1\n", "ans = [1/2,1/2] # inserez ici votre réponse\n", "corrections.Ex1Chapitre9_6_7(ans, case_nb=1)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# CAS 2\n", "ans = [0, 1/3, -1/3] # inserez ici votre réponse\n", "corrections.Ex1Chapitre9_6_7(ans, case_nb=2)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# CAS 3\n", "ans = [0, 0, 0, 0, 0] # inserez ici votre réponse\n", "corrections.Ex1Chapitre9_6_7(ans, case_nb=3)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# CAS 4\n", "x = sp.Symbol('x')\n", "ans = x**1 # inserez ici votre réponse\n", "corrections.Ex1Chapitre9_6_7(ans, case_nb=4)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# CAS 5\n", "x = sp.Symbol('x')\n", "ans = x**1 # inserez ici votre réponse\n", "corrections.Ex1Chapitre9_6_7(ans, case_nb=5)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Exercice 2\n", "\n", "Considérez les ensembles de vecteurs suivants dans $\\mathbb{R}^n, \\ n \\in \\{2,3,4\\}$. Tirez parti de la méthode interactive qui suit pour appliquer la méthode de Gram-Schmidt et dériver un ensemble orthonormé de vecteurs.\n", "Supposez que le produit scalair soit le produit standard $\\langle u,v \\rangle = u^Tv$ dans tous les cas.\n", "\n", "**Remarque**: pour exécuter correctement l'algorithme, gardez l'ordre donné des vecteurs!\n", "\n", "1. $\\mathcal{S}_1 = \\left\\{ \\begin{bmatrix} 2 \\\\ 1 \\end{bmatrix}, \\begin{bmatrix} -1 \\\\ -1 \\end{bmatrix} \\right\\}$\n", "\n", "2. $\\mathcal{S}_2 = \\left\\{ \\begin{bmatrix} 2 \\\\ 0 \\\\ 1 \\end{bmatrix}, \\begin{bmatrix} 1 \\\\ 1 \\\\ 0 \\end{bmatrix}, \\begin{bmatrix} 0 \\\\ 0 \\\\ 1 \\end{bmatrix} \\right\\}$\n", "\n", "3. $\\mathcal{S}_3 = \\left\\{ \\begin{bmatrix} 1 \\\\ 0 \\\\ -1 \\end{bmatrix}, \\begin{bmatrix} 0 \\\\ 1 \\\\ 1 \\end{bmatrix}, \\begin{bmatrix} 0 \\\\ 0 \\\\ 1 \\end{bmatrix}, \\begin{bmatrix} 1 \\\\ 1 \\\\ 1 \\end{bmatrix} \\right\\}$\n", "\n", "4. $\\mathcal{S}_4 = \\left\\{ \\begin{bmatrix} 1 \\\\ 0 \\\\ 0 \\\\ 1 \\end{bmatrix}, \\begin{bmatrix} 0 \\\\ 1 \\\\ 1 \\\\ -1\\end{bmatrix}, \\begin{bmatrix} 0 \\\\ 1 \\\\ 0 \\\\ 1 \\end{bmatrix}, \\begin{bmatrix} 0 \\\\ 1 \\\\ 1 \\\\ 0\\end{bmatrix} \\right\\}$" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Instructions\n", "\n", "Pour utiliser la méthode interactive de Gram-Schmidt, procédez comme suit:\n", "\n", - "1. Insérez le numéro de dossier souhaité dans la cellule suivante\n", + "1. Insérez le numéro du cas souhaité dans la cellule suivante. Exécutez le cellules appelées \"SÉLECTION DU CAS\" et \"INITIALISATION DES VARIABLES\"\n", "2. Exécutez la cellule appelée \"SÉLECTION DES PARAMÈTRES\" pour sélectionner le type d'opération et les coefficients nécessaires\n", "3. Exécutez la cellule appelée \"EXÉCUTER L'ÉTAPE DE L'ALGORITHME GRAM-SCHMIDT\" pour exécuter l'étape de l'algorithme de Gram-Schmidt avec les paramètres précédemment sélectionnés\n", + "4. Répétez les étapes 2 et 3 jusqu'à ce que l'algorithme soit terminée\n", "\n", "En outre:\n", "\n", "1. Vous pouvez annuler une opération en sélectionnant le bouton \"Revert\".\n", "\n", "2. Si les coefficients insérés sont incorrects, vous pouvez essayer avec de nouvelles valeurs sans effectuer une opération \"Revert\".\n", "\n", "3. Les coefficients qui ne sont pas liés à l'opération sélectionnée peuvent être définis sur n'importe quelle valeur, car ils ne sont pas utilisés dans le code." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ - "case_number = 3" + "# SÉLECTION DU CAS\n", + "case_number = 1 # CHOISISSEZ LE NUMÉRO DE CAS ICI ET EXECUTEZ LA CELLULE SUIVANTE!" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ + "# INITIALISATION DES VARIABLES\n", "if case_number == 1:\n", " vectors = [[2,1], [-1,-1]]\n", " dim=2\n", "elif case_number == 2:\n", " vectors = [[2,0,1], [1,1,0], [0,0,1]]\n", " dim=3\n", "elif case_number == 3:\n", " vectors = [[1,0,-1], [0,1,1], [0,0,1], [1,1,1]]\n", " dim=4\n", "elif case_number == 4:\n", " vectors = [[1,0,0,1], [0,1,1,-1], [0,1,0,1],[0,1,1,0]]\n", " dim=4\n", "else:\n", " print(f\"{case_number} n'est pas un numéro de cas valide!\" \n", " f\"Numéros de cas disponibles: [1,2,3,4]\")\n", "step = 0\n", "VectorsList = [vectors]" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# SÉLECTION DES PARAMÈTRES\n", "norm_coeff, proj_coeffs, operation, step_number = al.manual_GS(dim=dim)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# EXÉCUTER L'ÉTAPE DE L'ALGORITHME GRAM-SCHMIDT\n", "vectors = al.interactive_gram_schmidt(norm_coeff, proj_coeffs,\n", " operation, step_number, \n", " vectors.copy(), VectorsList)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Exercise 3\n", "\n", "Considérez les ensembles de fonctions suivants, dans les intervalles donnés et avec le produit scalir donné. Tirez parti de la méthode interactive qui suit pour appliquer la méthode de Gram-Schmidt et dériver un ensemble orthonormé de vecteurs. Vous pouvez vous aider à calculer les intégrales en exécutant la cellule appelée \"Cellule d'aide pour calculer les intégrales\"\n", "\n", "\n", "**Remarque**: pour exécuter correctement l'algorithme, gardez l'ordre donné des fonctions!\n", "\n", "1. $\\mathcal{F}_1 = \\left\\{1, x, x^2\\right\\}; \\qquad \\quad \\ \\ [a;b] = [-1;1] \\qquad \\langle f,g \\rangle = \\int_a^b f(x) g(x) \\ dx$\n", "2. $\\mathcal{F}_2 = \\left\\{1, x, x^2, x^3 \\right\\}; \\qquad \\ [a;b] = [0;1] \\qquad \\ \\ \\langle f,g \\rangle = \\int_a^b f(x) g(x) \\ dx$\n", "3. $\\mathcal{F}_3 = \\left\\{1, x^2, x^4 \\right\\}; \\qquad \\quad [a;b] = [-1;1] \\qquad \\langle f,g \\rangle = \\int_a^b (1-|x|) f(x) g(x) \\ dx$" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ - "case_number = 2" + "# SÉLECTION DU CAS\n", + "case_number = 1 # CHOISISSEZ LE NUMÉRO DE CAS ICI ET EXECUTEZ LA CELLULE SUIVANTE!" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ + "# INITIALISATION DES VARIABLES\n", "x = sp.Symbol('x')\n", "\n", "if case_number == 1:\n", " functions = [1 + 0*x, x, x**2]\n", " int_limits = [-1, 1]\n", " weight_function = 1.0 + 0*x\n", " dim=3\n", "elif case_number == 2:\n", " functions = [1.0 + 0*x, x, x**2, x**3]\n", " int_limits = [0, 1]\n", " weight_function = 1.0 + 0*x\n", " dim=4\n", "elif case_number == 3:\n", " functions = [1.0 + 0*x, x**2, x**4]\n", " int_limits = [-1, 1]\n", " weight_function = (1 - sp.abs(x))\n", " dim=3\n", "else:\n", " print(f\"{case_number} n'est pas un numéro de cas valide!\" \n", " f\"Numéros de cas disponibles: [1,2,3,4]\")\n", " \n", "step = 0\n", "FunctionsList = [functions]" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# SÉLECTION DES PARAMÈTRES\n", "norm_coeff, proj_coeffs, operation, step_number = al.manual_GS(dim=dim)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# EXÉCUTER L'ÉTAPE DE L'ALGORITHME GRAM-SCHMIDT\n", "functions = al.interactive_gram_schmidt_func(norm_coeff, proj_coeffs,\n", " operation, step_number, \n", " functions.copy(), \n", " FunctionsList,\n", " int_limits=int_limits,\n", " weight_function=weight_function)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Cellule d'aide pour calculer les intégrales\n", "x = sp.Symbol('x')\n", "func = weight_function * x # insert here the expression of your function, using 'x' as variable\n", "I = al.integrate_sp_function(func, x, int_limits)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "[Passez au notebook du chapitre 9.8-9.9: La projection orthgonale sur un sous-espace vectoriel - La projection orthogonale: examples et remarques supplémentaires](./9.8-9.9%20La%20projection%20orthogonale%20sur%20un%20sous-espace%20vectoriel%20-%20La%20projection%20orthogonale%20examples%20et%20remarques%20supplémentaires.ipynb)" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" } }, "nbformat": 4, "nbformat_minor": 4 } diff --git "a/Chapitre 9 - Produits scalaires et espaces euclidens/9.8-9.9 La projection orthogonale sur un sous-espace vectoriel - La projection orthogonale examples et remarques suppl\303\251mentaires.ipynb" "b/Chapitre 9 - Produits scalaires et espaces euclidens/9.8-9.9 La projection orthogonale sur un sous-espace vectoriel - La projection orthogonale examples et remarques suppl\303\251mentaires.ipynb" index a48e685..c6cb948 100644 --- "a/Chapitre 9 - Produits scalaires et espaces euclidens/9.8-9.9 La projection orthogonale sur un sous-espace vectoriel - La projection orthogonale examples et remarques suppl\303\251mentaires.ipynb" +++ "b/Chapitre 9 - Produits scalaires et espaces euclidens/9.8-9.9 La projection orthogonale sur un sous-espace vectoriel - La projection orthogonale examples et remarques suppl\303\251mentaires.ipynb" @@ -1,357 +1,362 @@ { "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# **Concept(s)-clé(s) et théorie**\n", "\n", "## Définition 1\n", "Soient $V$ un $\\mathbb{R}$-espace vectoriel muni d'un produit scalaire $\\langle \\cdot, \\cdot \\rangle$ et $W \\subset V$ un sous-espace vectoriel de $V$. L'**orthogonal à $W$ dans $V$** est le sous-ensemble de $V$ défini par $$W^\\perp = \\left\\{v \\in V: \\langle v,w \\rangle = 0 \\quad \\forall \\ w \\in W\\right\\}$$\n", "\n", "## Proposition 1\n", "Soient $V$ un $\\mathbb{R}$-espace vectoriel muni d'un produit scalaire $\\langle \\cdot, \\cdot \\rangle$ et $W \\subset V$ un sous-espace vectoriel de $V$. Alors le sous-ensamble $W^\\perp$ de $V$ est un sous-espace vectoriel de $V$.\n", "\n", "## Proposition 2\n", "Soient $V$ un $\\mathbb{R}$-espace vectoriel et $W \\subset V$ un sous-espace vectoriel de $V$. Alors pour tout $v \\in V$, il existe $w \\in W$ et $x \\in W^\\perp$ tels que $v = w + x$. De plus, $w$ et $x$ sont uniquement déterminés par $v$. Donc, cela revient à dire que $V = W \\oplus W^{\\perp}$.\n", "\n", "## Définition 2\n", "Soient $V$ un $\\mathbb{R}$-espace vectoriel et $W \\subset V$ un sous-espace vectoriel de $V$. Soient également $v \\in V$ et $w \\in W, x \\in W^\\perp$ tels que $v = w+x$, come ci-dessus. On appelle $w$ la **projection orthogonale de $v$ sur $W$** et on écrit $w = proj_Wv$.\n", "\n", "## Proposition 3\n", "Soient $V$ un $\\mathbb{R}$-espace euclidien de dimension $n$ et $W \\subset V$ un sous-espace vectoriel de $V$ de dimension $k$. Soit également $\\{v_1, \\dots, v_k, v_{k+1}, \\dots, v_n\\}$ un base orthonormée de $V$ tel que $\\{v_1, \\dots, v_k\\}$ est une base orthonormée de $W$ et $\\{v_{k+1}, \\dots, v_n\\}$ est une base orthonormée de $W^\\perp$. Alors nous pouvons calculer les projections orthogonales de $v \\in V$ sur $W$ et sur $W^\\perp$ respectivement comme suit:\n", "\\begin{align*}\n", "proj_Wv &= \\sum\\limits_{i=1}^k \\dfrac{\\langle v, v_i \\rangle}{\\langle v_i,v_i \\rangle} v_i\\\\\n", "proj_{W^\\perp}v &= \\sum\\limits_{j=k+1}^n \\dfrac{\\langle v, v_j \\rangle}{\\langle v_j,v_j \\rangle} v_j\n", "\\end{align*}\n", "\n", "## Corollaire 1 (de Gram-Schmidt)\n", "Soient $V$ un espace euclidien et $W \\subset V$ un sous-espace vectoriel de $V$. Alors $$dim W^\\perp = dim V - dim W$$\n", "\n", "## Corollaire 2\n", "Soient $V$ un espace euclidien et $W \\subset V$ un sous-espace vectoriel de $V$. Alors $$\\left(W^\\perp\\right)^\\perp = W$$" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Exercises et Examples" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import Librairie.AL_Fct as al\n", "import Corrections.corrections as corrections\n", "import numpy as np\n", "import sympy as sp" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Exercice 1\n", "\n", "Soit $V = \\mathbb{R}^n, \\ n=2,3$. Considérez les paires suivantes, faites par un ensemble de vecteurs $\\mathcal{S}$ générant un sous-espace vectoriel $W$ de $V$ et par un élément $v$ de $V$. Calculez les projections orthogonales de $v$ sur $W$ et $W^\\perp$. Tous les produits scalaires sont censés être les produits usuels.\n", "\n", "1. $V = \\mathbb{R}^2 \\qquad \\mathcal{S} = \\left\\{ \\begin{pmatrix}1 \\\\ 1\\end{pmatrix} \\right\\} \\qquad \\qquad \\quad \\ v = \\begin{pmatrix} -1 \\\\ 0 \\end{pmatrix}$\n", "2. $V = \\mathbb{R}^2 \\qquad \\mathcal{S} = \\left\\{ \\begin{pmatrix}0 \\\\ 1\\end{pmatrix}, \\begin{pmatrix} 1 \\\\ -1 \\end{pmatrix} \\right\\} \\qquad v = \\begin{pmatrix} -3 \\\\ 5 \\end{pmatrix}$\n", "3. $V = \\mathbb{R}^3 \\qquad \\mathcal{S} = \\left\\{ \\begin{pmatrix}1 \\\\ 2 \\\\ 1\\end{pmatrix} \\right\\} \\qquad \\qquad \\qquad \\ v = \\begin{pmatrix} 1 \\\\ 0 \\\\ 1\\end{pmatrix}$\n", "4. $V = \\mathbb{R}^3 \\qquad \\mathcal{S} = \\left\\{ \\begin{pmatrix}1 \\\\ -1\\\\ 1\\end{pmatrix}, \\begin{pmatrix} 0 \\\\ 1 \\\\ 1 \\end{pmatrix}\\right\\} \\qquad \\quad \\ \\ v = \\begin{pmatrix} -1 \\\\ 0 \\\\ 1 \\end{pmatrix}$" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Aide\n", + "\n", + "Pour calculer la projection orthogonal de $v$ sur $W$, il peut être utile de dériver une base ortogonale (ou orthonormée) pour ce dernier. Vous pouvez utiliser la cellule suivante pour exécuter l'algorithme interactif de Gram-Schmidt.\n", + "\n", + "#### Instructions\n", + "\n", + "Pour utiliser la méthode interactive de Gram-Schmidt, procédez comme suit:\n", + "\n", + "1. Insérez le numéro du cas souhaité dans la cellule suivante. Exécutez le cellules appelées \"SÉLECTION DU CAS\" et \"INITIALISATION DES VARIABLES\"\n", + "2. Exécutez la cellule appelée \"SÉLECTION DES PARAMÈTRES\" pour sélectionner le type d'opération et les coefficients nécessaires\n", + "3. Exécutez la cellule appelée \"EXÉCUTER L'ÉTAPE DE L'ALGORITHME GRAM-SCHMIDT\" pour exécuter l'étape de l'algorithme de Gram-Schmidt avec les paramètres précédemment sélectionnés\n", + "4. Répétez les étapes 2 et 3 jusqu'à ce que l'algorithme soit terminée\n", + "\n", + "En outre:\n", + "\n", + "1. Vous pouvez annuler une opération en sélectionnant le bouton \"Revert\".\n", + "\n", + "2. Si les coefficients insérés sont incorrects, vous pouvez essayer avec de nouvelles valeurs sans effectuer une opération \"Revert\".\n", + "\n", + "3. Les coefficients qui ne sont pas liés à l'opération sélectionnée peuvent être définis sur n'importe quelle valeur, car ils ne sont pas utilisés dans le code." + ] + }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ - "case_number = 4" + "# SÉLECTION DU CAS\n", + "case_number = 1 # CHOISISSEZ LE NUMÉRO DE CAS ICI ET EXECUTEZ LA CELLULE SUIVANTE!" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ + "# INITIALISATION DES VARIABLES\n", "if case_number == 1:\n", " S = [[1,1]]\n", " v = [-1,0]\n", " dim=1\n", "elif case_number == 2:\n", " S = [[0,1], [-1,1]]\n", " v = [-3,5]\n", " dim=2\n", "elif case_number == 3:\n", " S = [[1,2,1]]\n", " v = [1,0,1]\n", " dim=1\n", "elif case_number == 4:\n", " S = [[1,-1,1], [0,1,1]]\n", " v = [-1,0,1]\n", " dim=2\n", "else:\n", " print(f\"{case_number} n'est pas un numéro de cas valide!\" \n", " f\"Numéros de cas disponibles: [1,2,3,4]\")\n", "\n", "step = 0\n", "VectorsList = [S]" ] }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Aide\n", - "\n", - "Pour calculer la projection orthogonal de $v$ sur $W$, il peut être utile de dériver une base ortogonale (ou orthonormée) pour ce dernier. Vous pouvez utiliser la cellule suivante pour exécuter l'algorithme interactif de Gram-Schmidt.\n", - "\n", - "#### Instructions\n", - "\n", - "Pour utiliser la méthode interactive de Gram-Schmidt, procédez comme suit:\n", - "\n", - "1. Insérez le numéro de dossier souhaité dans la cellule suivante\n", - "2. Exécutez la cellule appelée \"SÉLECTION DES PARAMÈTRES\" pour sélectionner le type d'opération et les coefficients nécessaires\n", - "3. Exécutez la cellule appelée \"EXÉCUTER L'ÉTAPE DE L'ALGORITHME GRAM-SCHMIDT\" pour exécuter l'étape de l'algorithme de Gram-Schmidt avec les paramètres précédemment sélectionnés\n", - "\n", - "En outre:\n", - "\n", - "1. Vous pouvez annuler une opération en sélectionnant le bouton \"Revert\".\n", - "\n", - "2. Si les coefficients insérés sont incorrects, vous pouvez essayer avec de nouvelles valeurs sans effectuer une opération \"Revert\".\n", - "\n", - "3. Les coefficients qui ne sont pas liés à l'opération sélectionnée peuvent être définis sur n'importe quelle valeur, car ils ne sont pas utilisés dans le code." - ] - }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# SÉLECTION DES PARAMÈTRES\n", "norm_coeff, proj_coeffs, operation, step_number = al.manual_GS(dim=dim)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# EXÉCUTER L'ÉTAPE DE L'ALGORITHME GRAM-SCHMIDT\n", "S = al.interactive_gram_schmidt(norm_coeff, proj_coeffs,\n", " operation, step_number, \n", " S.copy(), VectorsList)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# IINSÉREZ ICI LES VALEURS DES DEUX PROJECTIONS\n", "proj1 = [1,1,1] # projection sur W\n", "proj2 = [0,0,0] # projection sur le complément orthogonal de W en V" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "scrolled": false }, "outputs": [], "source": [ "corrections.Ex1Chapitre9_8_9(proj1, proj2, case_nb=case_number)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Exercice 2\n", "\n", "Soit $V = \\mathbb{P}^n(\\mathbb{R}), \\ n=2,3$. Considérez les paires suivantes, faites par un ensemble de fonctions $\\mathcal{S}$ générant un sous-espace vectoriel $W$ de $V$ et par un élément $v$ de $V$. Calculez les projections orthogonales de $v$ sur $W$ e $W^\\perp$. Tous les produits scalaires sont censés être les produits usuels, dans l'intervalle spécifié $I$.\n", "\n", "1. $V = \\mathbb{P}^2(\\mathbb{R}) \\qquad \\mathcal{S} = \\left\\{ 1, x \\right\\} \\qquad \\qquad \\ \\ \\ v = x^2 \\qquad \\qquad \\qquad \\\n", "I = [-1,1]$\n", "2. $V = \\mathbb{P}^2(\\mathbb{R}) \\qquad \\mathcal{S} = \\left\\{ 1, x^2 \\right\\} \\qquad \\qquad v = x \\qquad \\qquad \\qquad \\ \\ \\ I = [0,1]$\n", "3. $V = \\mathbb{P}^3(\\mathbb{R}) \\qquad \\mathcal{S} = \\left\\{ 1, 1+x^2, x^3 \\right\\} \\quad \\ v = x^2 + x \\qquad \\qquad \\ \\ \\ I = [-1,1]$\n", "4. $V = \\mathbb{P}^3(\\mathbb{R}) \\qquad \\mathcal{S} = \\left\\{ x^2, x^3 \\right\\} \\qquad \\quad \\ \\ v = 1 + x + x^2 + x^3 \\quad \\ I=[-1,1]$" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ - "case_number = 1" + "# SÉLECTION DU CAS\n", + "case_number = 1 # CHOISISSEZ LE NUMÉRO DE CAS ICI ET EXECUTEZ LA CELLULE SUIVANTE!" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ + "# INITIALISATION DES VARIABLES\n", "x = sp.Symbol('x')\n", "if case_number == 1:\n", " S = [1+0*x, x]\n", " v = x**2\n", " int_limits = [-1,1]\n", " dim=2\n", "elif case_number == 2:\n", " S = [1+0*x, x**2]\n", " v = x\n", " int_limits = [0,1]\n", " dim=2\n", "elif case_number == 3:\n", " S = [1+0*x, x**2, x**3]\n", " v = x**2 + x\n", " int_limits = [-1,1]\n", " dim=3\n", "elif case_number == 4:\n", " S = [x**2, x**3]\n", " v = 1 + x + x**2 + x**3\n", " int_limits = [-1,1]\n", " dim=2\n", "else:\n", " print(f\"{case_number} n'est pas un numéro de cas valide!\" \n", " f\"Numéros de cas disponibles: [1,2,3,4]\")\n", "\n", "step = 0\n", "VectorsList = [S]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Aide\n", "\n", "Pour calculer la projection de $v$ sur $W$, il peut\n", "aider à dériver une base orthogonale (ou orthonormée) pour ce dernier. Vous pouvez utiliser la cellule suivante pour exécuter l'algorithme interactif de Gram-Schmidt pour fonctions." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# SÉLECTION DES PARAMÈTRES\n", "norm_coeff, proj_coeffs, operation, step_number = al.manual_GS(dim=dim)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# EXÉCUTER L'ÉTAPE DE L'ALGORITHME GRAM-SCHMIDT\n", "S = al.interactive_gram_schmidt_func(norm_coeff, proj_coeffs,\n", " operation, step_number, \n", " S.copy(), VectorsList,\n", " int_limits=int_limits,\n", " weight_function=None)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# INSÉREZ ICI LES VALEURS DES DEUX PROJECTIONS\n", "proj1 = 1 + 0*x # projection sur W\n", "proj2 = x # projection sur le complément orthogonal de W en V" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "corrections.Ex2Chapitre9_8_9(proj1, proj2, \n", " int_limits=int_limits, \n", " case_nb=case_number)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Exercice 3\n", "\n", "Soit $V = \\mathbb{R}^n, \\ n=2,3$. Étant donné les ensembles de vecteurs suivants $\\mathcal{S}$, générant un sous-espace $W$ de $V$, déterminez une *base orthogonale* pour $W^\\perp$.\n", "\n", "1. $ \\qquad V=\\mathbb{R}^2 \\qquad \\mathcal{S} = \\left\\{ \\begin{pmatrix} 1 \\\\ 2 \\end{pmatrix}, \\begin{pmatrix} 1 \\\\ 0 \\end{pmatrix} \\right\\}$\n", "2. $ \\qquad V=\\mathbb{R}^2 \\qquad \\mathcal{S} = \\left\\{ \\begin{pmatrix} 1 \\\\ -1 \\end{pmatrix}\\right\\}$\n", "3. $ \\qquad V=\\mathbb{R}^3 \\qquad \\mathcal{S} = \\left\\{ \\begin{pmatrix} 1 \\\\ 2 \\\\1 \\end{pmatrix}, \\begin{pmatrix} 0 \\\\ 1 \\\\ 0 \\end{pmatrix}, \\begin{pmatrix} -2 \\\\ 0 \\\\ -2 \\end{pmatrix} \\right\\}$\n", "4. $ \\qquad V=\\mathbb{R}^3 \\qquad \\mathcal{S} = \\left\\{ \\begin{pmatrix} -1 \\\\ 1 \\\\ 2 \\end{pmatrix}\\right\\}$\n", "\n", "### Instructions \n", "1. Si $W^\\perp$ ne contient que le vecteur nul (i.e. si $W \\equiv \\mathbb{R}^n$), entrez simplement une liste vide comme suit \"base = $[ \\ ]$\" (et non comme \"base = $[[ \\ ]]$\").\n", "2. Si $W^\\perp$ a une dimension supérieure à $0$, entrez les éléments de sa base sous forme de liste de listes. Ainsi, même si la base de $W^ \\perp$ contient un élément, entrez-le comme \"base = $[[a, b]]$\" et non comme \"base = $[a, b]$\"." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "case_number = 4\n", "basis = []" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "corrections.Ex3Chapitre9_8_9(basis, case_nb=case_number)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "[Passez au notebook du chapitre 9.10-9.11: La meilleure approximation quadratique](./9.10-9.11%20La%20meilleure%20approximation%20quadratique.ipynb)" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" } }, "nbformat": 4, "nbformat_minor": 4 } diff --git a/Chapitre 9 - Produits scalaires et espaces euclidens/Corrections/corrections.py b/Chapitre 9 - Produits scalaires et espaces euclidens/Corrections/corrections.py index 9c3cb54..43e0794 100644 --- a/Chapitre 9 - Produits scalaires et espaces euclidens/Corrections/corrections.py +++ b/Chapitre 9 - Produits scalaires et espaces euclidens/Corrections/corrections.py @@ -1,1701 +1,1773 @@ import sys sys.path.insert(0, './../') import numpy as np import sympy as sp import matplotlib.pyplot as plt from scipy.linalg import null_space from itertools import chain import ipywidgets as widgets import plotly import plotly.graph_objs as go from ipywidgets import interact_manual, Layout, widgets, HBox from IPython.display import display, Latex from sympy.solvers.solveset import linsolve from Librairie.AL_Fct import get_couple_vectors_info_2D, get_couple_matrices_info, get_couple_functions_info from Librairie.AL_Fct import couple_functions_plotter, compute_expansion_coefficients, vector_plot_2D, vector_plot_3D from Librairie.AL_Fct import project, project_on_subspace, isDiag, plot_line, plot_plane, scatterPlot -from Librairie.AL_Fct import Plot2DSys, Plot3DSys, compare_sympy_FiniteSets +from Librairie.AL_Fct import Plot2DSys, Plot3DSys, compare_sympy_FiniteSets, unique_qr def Ex1Chapitre9_1(): """Provides the correction of exercise 1 of notebook 9_1 """ a = widgets.Checkbox( value=False, description=r'$$\qquad u_1 \cdot u_2 \cdot [\dots] \cdot u_n = u_n \cdot [\dots] \cdot u_2 \cdot u_1$$', disabled=False, layout=Layout(width='90%', height='50px') ) b = widgets.Checkbox( value=False, description=r'$$\qquad (u_1 + u_2) \cdot (u_1 + u_2) = ||u_1|| + ||u_2|| + 2 (u_1 \cdot u_2)$$', disabled=False, layout=Layout(width='90%', height='50px') ) c = widgets.Checkbox( value=False, description=r"$$ \qquad \left(\sum\limits_{i=1}^nu_i\right) \cdot \left(\sum\limits_{i=1}^nu_i\right) = " r"\sum\limits_{i=1}^n ||u_i||^2 + 2\sum\limits_{i=1}^n\sum\limits_{j=1}^{i-1}u_i \cdot u_j$$", disabled=False, layout=Layout(width='100%', height='80px') ) d = widgets.Checkbox( value=False, description=r'\begin{align*}' r'\qquad (\lambda_1u_1 + \lambda_2u_2) \cdot (\lambda_3u_3 + \lambda_4u_4) =&' r'\lambda_1\lambda_3(u_1 \cdot u_3) + \lambda_1\lambda_4(u_1 \cdot u_4) \ + \\' r' \qquad \quad & \lambda_2\lambda_3(u_2 \cdot u_3) + \lambda_2\lambda_4(u_2 \cdot u_4)' r'\end{align*}', disabled=False, layout=Layout(width='90%', height='80px') ) e = widgets.Checkbox( value=False, description=r'$$\qquad u_1 \cdot u_2 = 0 \implies u_1 \perp u_2 \ \forall \ u_1, u_2 \in \mathbb{R}^2$$', disabled=False, layout=Layout(width='80%', height='50px') ) def correction(a, b, c, d, e): if a and not b and c and d and not e: display(Latex(r"C'est correct! En particulier: " "1. Vrai, en raison de la symétrie du produit scalaire. " "2. Faux, car les normes du côté gauche doivent être au carré. " "3. Vrai, en raison de l'additivité (la formule peut être prouvée par induction. Essaie! " "4. Vrai, en raison de la bilinéarité du produit scalaire. " "5. Faux, car il faut préciser que $u_1$ et $u_2$ ne peuvent pas être des vecteurs nuls.")) else: display(Latex("C'est faux.")) interact_manual(correction, a=a, b=b, c=c, d=d, e=e) return def Ex2Chapitre9_1(vec1, vec2): """Provides the correction of exercise 2 of notebook 9_1 """ display(Latex("Insérez les valeurs des quantités listées ci-dessous. " "Entrez les valeurs avec 4 chiffres après la virgule! Si l'angle n'est pas défini, entrez -999!")) norm_u = widgets.FloatText( value=0.0, step=0.0001, description='||u||:', disabled=False ) norm_v = widgets.FloatText( value=0.0, step=0.0001, description='||v||:', disabled=False ) u_dot_v = widgets.FloatText( value=0.0, step=0.0001, description=r'$u \cdot v$:', disabled=False ) theta_u_v = widgets.FloatText( value=0.0, step=0.0001, description=r'$\Delta\theta$', disabled=False ) solution_button = widgets.Button(description='Solution', disabled=True) box = HBox(children=[solution_button]) out = widgets.Output() @out.capture() def solution(e): out.clear_output() get_couple_vectors_info_2D(vec1, vec2, show=True, return_results=False) display(norm_u) display(norm_v) display(u_dot_v) display(theta_u_v) norm_u_true, norm_v_true, u_dot_v_true, theta_u_v_true = get_couple_vectors_info_2D(vec1, vec2, show=False, return_results=True) def f(): threshold = 1e-4 u_correct, v_correct, u_dot_v_correct, theta_correct = tuple(np.zeros((4, 1)).astype(bool)) if np.abs(norm_u.value - norm_u_true) < threshold: display(Latex(r"La norme de $u$ est correcte!")) u_correct = True else: display(Latex(f"La norme de $u$ n'est pas correcte! L'erreur est supérieure au seuil de {threshold}. " f"Entrez les valeurs avec 4 chiffres après la virgule!")) if np.abs(norm_v.value - norm_v_true) < threshold: display(Latex(r"La norme de $v$ est correcte!")) v_correct = True else: display(Latex(f"La norme de $v$ n'est pas correcte! L'erreur est supérieure au seuil de {threshold}. " f"Entrez les valeurs avec 4 chiffres après la virgule!")) if np.abs(u_dot_v.value - u_dot_v_true) < threshold: display(Latex(r"Le produit scalaire est correct!")) u_dot_v_correct = True else: display(Latex(f"Le produit scalaire n'est pas correct! L'erreur est supérieure au seuil de {threshold}. " f"Entrez les valeurs avec 4 chiffres après la virgule!")) if not np.isnan(theta_u_v_true) and np.abs(theta_u_v.value - theta_u_v_true) < threshold: display(Latex(r"L'angle est correct!")) theta_correct = True elif np.isnan(theta_u_v_true) and theta_u_v.value == -999: display(Latex(r"Correct! L'angle n'est pas defini!")) theta_correct = True else: display(Latex(f"L'angle n'est pas correcte! L'erreur est supérieure au seuil de {threshold}. " f"Entrez les valeurs avec 4 chiffres après la virgule!")) if u_correct and v_correct and u_dot_v_correct and theta_correct: display(Latex(r"RÉSULTATS")) get_couple_vectors_info_2D(vec1, vec2, show=True, return_results=False) else: solution_button.disabled = False interact_manual(f) solution_button.on_click(solution) display(box) display(out) return def Ex3Chapitre9_1(case=1): """Provides the correction of exercise 3 of notebook 9_1 :param case: number of the case. Available: {1,2,3}. Defaults to 1 :type case: int """ fig = plt.figure(figsize=(5, 5)) ax = fig.gca() origin = [0], [0] if case == 1: theta = np.hstack((np.linspace(0, np.pi / 4, 100), np.linspace(5 * np.pi / 4, 3 * np.pi / 2, 100))) x1 = 2 * np.cos(theta) x2 = 2 * np.sin(theta) limit = 2.5 for i in range(2): plt.plot(x1[i * 100: (i + 1) * 100], x2[i * 100: (i + 1) * 100], '-', color='r', linewidth=2) elif case == 2: theta_ref = np.linspace(0, 2 * np.pi, 13) theta = np.hstack( [np.linspace(theta_ref[i], theta_ref[i + 1], 50) if not i % 2 else np.array([]) for i in range(12)]) vals = [1, 3, 5, 7, 9, 11] x1 = np.hstack([vals[i] * np.cos(theta[i * 50:(i + 1) * 50]) for i in range(6)]) x2 = np.hstack([vals[i] * np.sin(theta[i * 50:(i + 1) * 50]) for i in range(6)]) limit = 12 for i in range(6): plt.plot(x1[i * 50:(i + 1) * 50], x2[i * 50:(i + 1) * 50], '-', color='r', linewidth=2) elif case == 3: theta = np.linspace(0, 8 * np.pi, 800) vals = np.logspace(-2, 0, 800) x1 = vals * np.cos(theta) x2 = vals * np.sin(theta) limit = 1.2 plt.plot(x1, x2, '-', color='r', linewidth=2) else: raise ValueError(f"Le cas {case} n'est pas défini. Cas disponibles: {1, 2, 3}") ax.plot(*origin, marker='o', color='b') ax.set_xlim(-limit, limit) ax.set_ylim(-limit, limit) ax.grid() # Move left y-axis and bottom x-axis to centre, passing through (0,0) ax.spines['left'].set_position('center') ax.spines['bottom'].set_position('center') # Eliminate upper and right axes ax.spines['right'].set_color('none') ax.spines['top'].set_color('none') # Show ticks in the left and lower axes only ax.xaxis.set_ticks_position('bottom') ax.yaxis.set_ticks_position('left') plt.show() if case == 1: a = widgets.Checkbox( value=False, description=r"L'ensemble peut être exprimé comme: " r" $$\qquad S=\Bigl\{v \in \mathbb{R}^2: ||v||=2 \land \theta \ \% \ \pi \in " r"\Bigl[0; \dfrac{\pi}{4}\Bigr]\Bigr\}$$", disabled=False, layout=Layout(width='80%', height='60px') ) b = widgets.Checkbox( value=False, description=r"L'ensemble peut être exprimé comme: " r" $$\qquad S=\Bigl\{v \in \mathbb{R}^2: ||v||=2 \land \Bigl\lfloor\dfrac{\theta}{\pi}\Bigr\rfloor " r"in [0; 1]\Bigr\}$$", disabled=False, layout=Layout(width='80%', height='60px') ) c = widgets.Checkbox( value=False, description=r"$v = \Bigl[\dfrac{\sqrt{2}}{2}; \dfrac{\sqrt{2}}{2}\Bigr]$ appartient à l'ensemble", disabled=False, layout=Layout(width='80%', height='50px') ) d = widgets.Checkbox( value=False, description=r"$v = -\dfrac{1}{2}\Bigl[\sqrt{2}; \sqrt{6}\Bigr]$ appartient à l'ensemble", disabled=False, layout=Layout(width='80%', height='50px') ) def correction(a, b, c, d): if a and not b and not c and d: display( (Latex(r"C'est correct! En effet l'ensemble S est fait d'éléments de norme égale à 2 $ et d'angles " r"dans $\Bigl[0; \dfrac{\pi}{4}\Bigr]$ ou dans " r"$\Bigl[\dfrac{5\pi}{4}; \dfrac{3\pi}{2}\Bigr]$, ce qui est indiqué par la réponse 1. " r"La réponse 2 est erronée car le calcul de $\Bigl\lceil\dfrac{\theta}{\pi}\Bigr\rceil$" r"n'a aucun sens dans ce contexte. Enfin, " r"$v_1 = \Bigl[\dfrac{\sqrt{2}}{2}; \dfrac{\sqrt{2}}{2}\Bigr]$ n'appartient pas à l'ensemble " r"puisque sa norme est égale à $1$ et non à $2$ (réponse 3); inversement " r"$v_2 = -\dfrac{1}{2}\Bigl[\sqrt{2}; \sqrt{6}\Bigr]$ fait partie à l'ensemble,appartenant " r" à l'arc dans le troisième quadrant (réponse 4)."))) else: display(Latex("C'est faux.")) elif case == 2: a = widgets.Checkbox( value=False, description=r"L'ensemble peut être exprimé comme: " r"$$\qquad S= \Bigl\{v \in \mathbb{R}^2: \Bigl\lceil \dfrac{\theta}{\pi / 6} \Bigr\rceil \ is \ odd \ \land ||v|| = \Bigl\lfloor \dfrac{\theta}{\pi / 6} \Bigr\rfloor \Bigr\}$$", disabled=False, layout=Layout(width='90%', height='60px') ) b = widgets.Checkbox( value=False, description=r"L'ensemble peut être exprimé comme: " r"$$\qquad S= \Bigl\{v \in \mathbb{R}^2: \Bigl\lfloor \dfrac{\theta}{\pi / 6} \Bigr\rfloor \ is \ even \ \land ||v|| = \Bigl\lceil \dfrac{\theta}{\pi / 6} \Bigr\rceil \Bigr\}$$", disabled=False, layout=Layout(width='90%', height='60px') ) c = widgets.Checkbox( value=False, description=r"$v = \dfrac{1}{2}\Bigl[\sqrt{3}; 3\Bigr]$ appartient à l'ensemble", disabled=False, layout=Layout(width='100%', height='60px') ) d = widgets.Checkbox( value=False, description=r"$v = \Bigl[-\sqrt{3}; \sqrt{3}\Bigr]$ appartient à l'ensemble", disabled=False, layout=Layout(width='100%', height='60px') ) def correction(a, b, c, d): if not a and b and c and not d: display((Latex( r"C'est correct! En effet l'ensemble est fait par tous les éléments tels que la partie entière de " r"a division de leur angle (supposée être en radians et en $[0;2\pi]$ par " r"$\dfrac{\pi}{6}$ est pair et avec norme égale à" r"$\Bigl\lceil\dfrac{\theta}{\pi / 6}\Bigr\rceil$ (réponse 2). La réponse 1 est erronée car" r"la norme des éléments de l'ensemble est égale à $\Bigl\lceil\dfrac{\theta}{\pi / 6}\Bigr\rceil$" r"et non à $\Bigl\lfloor\dfrac{\theta}{\pi / 6}\Bigr\rfloor$; la condition '" r"$\Bigl\lceil\dfrac{\theta}{\pi / 6}\Bigr\rceil \ is \ odd$' est plutôt vrai. Enfin, " r"$v_1 = \dfrac{1}{2}\bigl[\sqrt{3}; 3\bigr]$ appartient à S (c'est l'élément le plus à droite " r"du deuxième arc du premier quadrant) (réponse 3), tandis que" r" $v_2 = \bigl[-\sqrt{3}; \sqrt{3}\bigr]$ ne fait pas partie de S puisque sa norme est égale à $3$, " r"et il devrait être égal à $5$ pour faire partie de l'arc dans le deuxième quadrant (réponse 4)."))) else: display(Latex("C'est faux.")) elif case == 3: a = widgets.Checkbox( value=False, description=r"L'élément de l'ensemble $S$ avec la norme maximale a une norme égale à $1$", disabled=False, layout=Layout(width='90%', height='50px') ) b = widgets.Checkbox( value=False, description=r'Ce qui suit est valable: ' r'$$ \qquad \nexists \ v_1, v_2 \in S: v_1 \cdot v_2 = 0 \land ||v_1|| = ||v_2||$$', disabled=False, layout=Layout(width='90%', height='50px') ) c = widgets.Checkbox( value=False, description=r"Ce qui suit est valable: " r"$$ \qquad \forall \alpha \in [0;2\pi] \ \exists v \in S: \theta = \alpha$$", disabled=False, layout=Layout(width='100%', height='50px') ) d = widgets.Checkbox( value=False, description=r"Tous les couples d'élémets de S sont une base de $\mathbb{R}^2$", disabled=False, layout=Layout(width='100%', height='50px') ) def correction(a, b, c, d): if a and b and c and not d: display((Latex( r"C'est correct! En effet, cet ensemble est en fait constitué de vecteurs dont les angles s'étendent sur " r"$[0;8\pi]$ et dont les normes augmentent logarithmiquement de $10^{-2}$ (if $\theta = 0$) à " r"$10$ if $\theta = 8\pi$. Pour cette raison, l'élément avec la norme maximale a une norme égale à " r"$1$ (answer 1) et tous les angles dans $[0; 2\pi]$ sont représentés par un vecteur dans l'ensemble " r"(answer 3). (réponse 3). De plus, l'ensemble ne contient aucun couple d'éléments orthogonaux " r"et qui ont la même norme (en fait, il ne contient pas deux éléments avec le même " r"norme du tout!) (réponse 2). Au lieu de cela, tout couple d'éléments ayant le même angle est " r"dépend linéairement et ne peut donc pas être une base de $\mathbb{R}^2$ (réponse 4)."))) else: display(Latex("C'est faux.")) else: raise ValueError(f"Le cas {case} n'est pas défini. Cas disponibles: {1, 2, 3}") interact_manual(correction, a=a, b=b, c=c, d=d) return def Ex1Chapitre9_2(): """Provides the correction to exercise 1 of notebook 9_2 """ print("Cliquer sur CTRL pour sélectionner plusieurs réponses") style = {'description_width': 'initial'} ans1 = widgets.SelectMultiple( options=['1', '2', '3', '4', '5', '6'], description="L'opérateur est un produit scalaire dans les cas:", style=style, layout=Layout(width='70%', height='120px'), disabled=False, ) ans2 = widgets.SelectMultiple( options=['1', '2', '3', '4', '5', '6'], description=r"L' $\mathbb{R}$-espace vectoriel, équipé avec l'operateur donné, " r"est un espace Euclidien dans le cas", style=style, layout=Layout(width='70%', height='120px'), disabled=False, ) def correction(ans1, ans2): res_ans = np.zeros(2).astype(bool) res_ans[0] = '1' not in ans1 and '2' in ans1 and '3' in ans1 and \ '4' not in ans1 and '5' in ans1 and '6' not in ans1 res_ans[1] = '1' not in ans2 and '2' in ans2 and '3' in ans2 and \ '4' not in ans2 and '5' not in ans2 and '6' not in ans2 if res_ans.all(): display(Latex(r"C'est correct! En effet: CAS 1: l'opérateur n'est pas un produit scalaire " r"parce qu'il n'est pas défini positif; donc l'espace vectoriel ne peut pas être " r"un espace Euclidien. CAS 2: l'opérateur est en fait un produit scalaire (" r"il s'avère respecter toutes les propriétés requises); en plus l'espace vectoriel " r"à une dimension finie (3), et c'est donc un espace Euclidien une fois équipé " r"avec l'opérateur donné. CAS 3: l'opérateur est un produit scalaire; l'espace vectoriel " r"à une dimension finie (3, égal au nombre de coefficients caractérisants) " r"et c'est donc un espace Euclidien s'il est équipé de l'opérateur donné. CAS 4: l'opérateur " r"est le même que dans le cas 2, mais dans ce cas ce n'est PAS un produit scalaire car " r"il n'est pas défini positif (notez, en effet, que chaque polynôme de degré égal à 3 " r"à un produit scalaire nul avec lui-même, bien qu'il n'est pas le polynôme nul); " r"donc l'espace vectoriel ne peut alors pas être un espace Eucliden. " r"CAS 5: l'opérateur donné définit un produit scalaire. En particulier, definiè " r"positivitè vient du fait que, si $\langle a, a \rangle = 0$, alors $a(x_0) = 0$ " r"et $a'(x)^2=0 \ \forall x \in [x_0; x_1]$; en conséquence $a$ doit être constant dans $[x_0; x_1]$ " r"et égal à $0$ en $x_0$. En raison de la continuité, cela implique qu'en fait $a \equiv 0$, comme " r"souhaité. Cependant, la dimensionnalité de l'espace vectoriel est infinie; il ne peut donc pas " r"être un espace Euclidien. CAS 6: l'opérateur ne définit pas un produit scalaire car il n'est pas " r"défini positif; en effet, si $\langle a, a \rangle=0$ alors $a$ doit être $0$ à $x_0$ et il doit avoir " r"dérivée seconde nulle dans $[x_0; x_1]$. Ainsi, toute fonction linéaire égale à $0$ à $x_0$ " r"conduirait à avoir $ \langle a, a \rangle=0$, ce qui prouve que l'opérateur n'est pas " r"défini positif. Facilement, l'espace vectoriel ne peut pas être un espace Euclidien, aussi parce que " r"sa dimension est infinie.")) else: display(Latex("C'est faux.")) if not res_ans[0]: display((Latex("La réponse à la première question est fausse"))) if not res_ans[1]: display((Latex("La réponse à la deuxième question est fausse"))) interact_manual(correction, ans1=ans1, ans2=ans2) return def Ex2Chapitre9_2(): """Provides the correction to exercise 2 of notebook 9_2 """ print("Cliquer sur CTRL pour sélectionner plusieurs réponses") style = {'description_width': 'initial'} ans = widgets.SelectMultiple( options=['1', '2', '3', '4'], description='Les matrices sont orthogonales dans les cas:', style=style, layout=Layout(width='40%', height='80px'), disabled=False, ) def correction(ans): res_ans = '1' in ans and '2' not in ans and \ '3' in ans and '4' not in ans if res_ans: display(Latex(r"C'est correct!")) else: display(Latex("C'est faux.")) interact_manual(correction, ans=ans) return def Ex3Chapitre9_2(): """Provides the correction to exercise 3 of notebook 9_2 """ print("Cliquer sur CTRL pour sélectionner plusieurs réponses") style = {'description_width': 'initial'} ans = widgets.SelectMultiple( options=['1', '2', '3', '4', '5', '6'], description='Les fonctions sont orthogonales dans les cas:', style=style, layout=Layout(width='60%', height='110px'), disabled=False, ) def correction(ans): res_ans = '1' in ans and '2' not in ans and '3' not in ans and \ '4' in ans and '5' in ans and '6' not in ans if res_ans: display(Latex(r"C'est correct!")) else: display(Latex("C'est faux.")) interact_manual(correction, ans=ans) return def Ex3Chapitre9_2_plotter(case_nb=1): """Helper function that allows to plot the functions involved in Ex 3 of notebook 9_2 :param case_nb: test case number. Defaults to 1 :type case_nb: int """ if case_nb in {1, 2}: f = lambda x: 1 - x ** 2 g = lambda x: -(x - 1) ** 2 x0 = -1 x1 = 1 elif case_nb in {3, 4}: f = lambda x: x - 1 / 2 g = lambda x: x ** 2 - x - 1 / 6 x0 = 0 x1 = 1 elif case_nb in {5, 6}: f = lambda x: np.sin(x) g = lambda x: np.cos(x) x0 = -np.pi if case_nb == 5 else 0 x1 = np.pi if case_nb == 5 else np.pi / 2 else: raise ValueError(f"Cas test nombre {case_nb} n'est pas disponible. " f"Cas disponibles: [1,2,3,4,5,6]") couple_functions_plotter(f, g, [x0, x1], pi_formatting=case_nb in {5,6}) return def Ex1Chapitre9_3_4(): """Provides the correction to exercise 1 of notebook 9_3_4 """ print("Cliquer sur CTRL pour sélectionner plusieurs réponses") style = {'description_width': 'initial'} ans = widgets.SelectMultiple( options=['1', '2', '3', '4', '5', '6'], description='La déclaration est vraie dans les cas:', style=style, layout=Layout(width='40%', height='110px'), disabled=False, ) def correction(ans): res_ans = '1' in ans and '2' not in ans and '3' not in ans and \ '4' in ans and '5' not in ans and '6' in ans if res_ans: display(Latex(r"C'est correct! En effet, l'énoncé 1 correspond au lemme de Titu et c'est un " r"corollaire classique de l'inégalité Cauchy-Schwarz, qui peut être prouvé par l'exécution de les " r"substitutions $\tilde{u_i} = \dfrac{u_i}{\sqrt{u_i}}$ et $\tilde{v_i} = \sqrt{v_i}$, pour " r"$i \in \{1,\dots, n\}$. " r"L'énoncé 2 est faux, car l'inégalité traingulaire se maintient avec un " r"signe d'égalité si les vecteurs sont orthogonaux (théorème de Pythagore), mais l' inegalité de " r"Cauchy-Schwarz l'inégalité le fait si les vecteurs sont soit parallèles soit antiparallèles, " r"c'est-à-dire si le cosinus de l'angle entre les vecteurs est égal à 1 ou -1. " r"L'énoncé 3 est faux puisque, dans ce cas, il peut être prouvé par de simples calculs que le " r" cosinus de l'angle entre les deux vecteurs est egal a $0$, de sorte que l'angle est égal à " r"$\pi/2$ et les vecteurs sont orthogonaux." r"L'énoncé 4 est vrai, puisque toutes les fonctions appartenant à la famille $\mathcal{F}_1$ " r"sont mutuellement orthogonal par rapport au produit scalaire donné." r"L'énoncé 5, au contraire, est faux, puisque les monômes pairs et impairs sont toujours " r"orthogonaux sur un intervalle symétrique à 0$, mais deux monomiaux impairs et deux monomiaux " r"pairs ne le sont pas. " r"FEnfin, l'énoncé 6 est vraie, puisque on peut facilement vérifier que les quatre matrices " r"sont mutuellement orthogonales, par rapport au produit scalaire défini via l'opérateur de trace.")) else: display(Latex("C'est faux.")) interact_manual(correction, ans=ans) return def Ex2Chapitre9_3_4(case_nb=1): """Provides the correction to exercise 2 of notebook 9_3_4 :param case_nb: test case number. Defaults to 1 :type case_nb: int """ if case_nb == 1: A = np.array([[-1, 2, 1], [0, 1, -1], [-2, 3, 0]]) B = np.array([[0, -1, 2], [1, 3, -2], [1, 1, 1]]) elif case_nb == 2: A = np.array([[1, -1], [0, 3]]) B = np.array([[0, 2], [1, -1]]) elif case_nb == 3: A = np.array([[0, 1, 3, 0], [1, 0, 1, 0], [-1, -2, 2, 1], [3, 4, 1, 2]]) B = np.array([[3, 1, -1, 2], [2, 2, 0, 1], [-1, 1, -1, 3], [-1, 1, 1, -1]]) elif case_nb == 4: A = np.array([[1, 0], [-1, 3]]) B = np.array([[-1, -5], [2, -3]]) else: raise ValueError(f"Cas test nombre {case_nb} n'est pas disponible. " f"Cas disponibles: [1,2,3,4]") res = get_couple_matrices_info(A, B) display(Latex("Insérez les valeurs des quantités listées ci-dessous. " "Entrez les valeurs avec 4 chiffres après la virgule! Si l'angle n'est pas défini, entrez -999!")) style = {'description_width': 'initial'} angle = widgets.FloatText( value=0.0, step=0.0001, style=style, description=r'$\theta$:', disabled=False ) LHS = widgets.FloatText( value=0.0, step=0.0001, style=style, description='$||A+B||^2$', disabled=False ) RHS = widgets.FloatText( value=0.0, step=0.0001, style=style, description=r'$||A||^2 + ||B||^2$', disabled=False ) solution_button = widgets.Button(description='Solution', disabled=True) box = HBox(children=[solution_button]) out = widgets.Output() @out.capture() def solution(e): out.clear_output() get_couple_matrices_info(A, B, show=True) display(angle) display(LHS) display(RHS) def f(): threshold = 1e-4 if (np.isnan(res['angle']) and angle.value == -999.0) or \ (np.abs(angle.value - res['angle'] * 180.0 / np.pi) <= threshold): display(Latex(r"L'angle est correcte!")) theta_correct = True else: theta_correct = False display(Latex(f"La'angle n'est pas correcte! L'erreur est supérieure au seuil de {threshold}. " f"Entrez les valeurs avec 4 chiffres après la virgule!")) if np.abs(LHS.value - res['norm_sum'] ** 2) <= threshold: display(Latex(r"$||A+B||^2$ est correcte!")) LHS_correct = True else: LHS_correct = False display(Latex(f"$||A+B||^2$ n'est pas correcte! L'erreur est supérieure au seuil de {threshold}. " f"Entrez les valeurs avec 4 chiffres après la virgule!")) if np.abs(RHS.value - res['norm_1'] ** 2 - res['norm_2'] ** 2) <= threshold: display(Latex(r"$||A||^2 + ||B||^2$ est correcte!")) RHS_correct = True else: RHS_correct = False display(Latex(f"$||A||^2 + ||B||^2$ n'est pas correcte! L'erreur est supérieure au seuil de {threshold}. " f"Entrez les valeurs avec 4 chiffres après la virgule!")) if theta_correct and LHS_correct and RHS_correct: display(Latex("C'est tout correct!")) else: solution_button.disabled = False display(Latex("Il y a quelques erreurs. " "N'oubliez pas d'insérer tous les résultats avec 4 chiffres après la virgule")) return interact_manual(f) solution_button.on_click(solution) display(box) display(out) return def Ex3Chapitre9_3_4(case_nb=1): """Provides the correction to exercise 3 of notebook 9_3_4 :param case_nb: test case number. Defaults to 1 :type case_nb: int """ if case_nb == 1: f = lambda x: x g = lambda x: np.exp(-np.abs(x)) x0 = -1 x1 = 1 elif case_nb == 2: f = lambda x: x ** 3 + 0.5 g = lambda x: x ** 2 - x x0 = 0 x1 = 1 elif case_nb == 3: f = lambda x: np.sin(np.abs(2 * x)) g = lambda x: np.cos(np.abs(x + np.pi / 2)) x0 = -np.pi / 2 x1 = np.pi / 2 elif case_nb == 4: f = lambda x: np.sin(np.abs(2 * x)) g = lambda x: np.cos(np.abs(x + np.pi / 2)) x0 = 0 x1 = np.pi / 2 else: raise ValueError(f"Cas test nombre {case_nb} n'est pas disponible. " f"Cas disponibles: [1,2,3,4]") res = get_couple_functions_info(f, g, [x0, x1]) display(Latex("Insérez les valeurs des quantités listées ci-dessous. " "Entrez les valeurs avec 4 chiffres après la virgule! Si l'angle n'est pas défini, entrez -999!")) style = {'description_width': 'initial'} angle = widgets.FloatText( value=0.0, step=0.0001, style=style, description=r'$\theta$:', disabled=False ) LHS = widgets.FloatText( value=0.0, step=0.0001, style=style, description='$||f+g||^2$', disabled=False ) RHS = widgets.FloatText( value=0.0, step=0.0001, style=style, description=r'$||f||^2 + ||g||^2$', disabled=False ) solution_button = widgets.Button(description='Solution', disabled=True) box = HBox(children=[solution_button]) out = widgets.Output() @out.capture() def solution(e): out.clear_output() if case_nb == 1: f = lambda x: x g = lambda x: np.exp(-np.abs(x)) x0 = -1 x1 = 1 elif case_nb == 2: f = lambda x: x ** 3 + 0.5 g = lambda x: x ** 2 - x x0 = 0 x1 = 1 elif case_nb == 3: f = lambda x: np.sin(np.abs(2 * x)) g = lambda x: np.cos(np.abs(x + np.pi / 2)) x0 = -np.pi / 2 x1 = np.pi / 2 elif case_nb == 4: f = lambda x: np.sin(np.abs(2 * x)) g = lambda x: np.cos(np.abs(x + np.pi / 2)) x0 = 0 x1 = np.pi / 2 get_couple_functions_info(f, g, [x0, x1], show=True) couple_functions_plotter(f, g, [x0, x1], pi_formatting=case_nb in {3,4}) display(angle) display(LHS) display(RHS) def f(): threshold = 1e-4 if (np.isnan(res['angle']) and angle.value == -999.0) or \ (np.abs(angle.value - res['angle'] * 180.0 / np.pi) <= threshold): display(Latex(r"L'angle est correcte!")) theta_correct = True else: theta_correct = False display(Latex(f"La'angle n'est pas correcte! L'erreur est supérieure au seuil de {threshold}. " f"Entrez les valeurs avec 4 chiffres après la virgule!")) if np.abs(LHS.value - res['norm_sum'] ** 2) <= threshold: display(Latex(r"$||f+g||^2$ est correcte!")) LHS_correct = True else: LHS_correct = False display(Latex(f"$||f+g||^2$ n'est pas correcte! L'erreur est supérieure au seuil de {threshold}. " f"Entrez les valeurs avec 4 chiffres après la virgule!")) if np.abs(RHS.value - res['norm_1'] ** 2 - res['norm_2'] ** 2) <= threshold: display(Latex(r"$||f||^2 + ||g||^2$ est correcte!")) RHS_correct = True else: RHS_correct = False display(Latex(f"$||f||^2 + ||g||^2$ n'est pas correcte! L'erreur est supérieure au seuil de {threshold}. " f"Entrez les valeurs avec 4 chiffres après la virgule!")) if theta_correct and LHS_correct and RHS_correct: display(Latex("C'est tout correct!")) else: solution_button.disabled = False display(Latex("Il y a quelques erreurs. " "N'oubliez pas d'insérer tous les résultats avec 4 chiffres après la virgule")) return interact_manual(f) solution_button.on_click(solution) display(box) display(out) return def Ex1Chapitre9_5(): """Provides the correction to exercise 1 of notebook 9_5 """ print("Cliquer sur CTRL pour sélectionner plusieurs réponses") style = {'description_width': 'initial'} ans1 = widgets.SelectMultiple( options=['Famille Orthogonale', 'Famille Orthonormale', 'Base Orthogonale', 'Base Orthonormale'], description='Cas 1:', style=style, layout=Layout(width='60%', height='80px'), disabled=False, ) ans2 = widgets.SelectMultiple( options=['Famille Orthogonale', 'Famille Orthonormale', 'Base Orthogonale', 'Base Orthonormale'], description='Cas 2:', style=style, layout=Layout(width='60%', height='80px'), disabled=False, ) ans3 = widgets.SelectMultiple( options=['Famille Orthogonale', 'Famille Orthonormale', 'Base Orthogonale', 'Base Orthonormale'], description='Cas 3:', style=style, layout=Layout(width='60%', height='80px'), disabled=False, ) ans4 = widgets.SelectMultiple( options=['Famille Orthogonale', 'Famille Orthonormale', 'Base Orthogonale', 'Base Orthonormale'], description='Cas 4:', style=style, layout=Layout(width='60%', height='80px'), disabled=False, ) def correction(ans1, ans2, ans3, ans4): res_ans = np.zeros(4).astype(bool) res_ans[0] = 'Famille Orthogonale' not in ans1 and 'Famille Orthonormale' not in ans1 and \ 'Base Orthogonale' not in ans1 and 'Base Orthonormale' not in ans1 res_ans[1] = 'Famille Orthogonale' in ans2 and 'Famille Orthonormale' in ans2 and \ 'Base Orthogonale' in ans2 and 'Base Orthonormale' in ans2 res_ans[2] = 'Famille Orthogonale' in ans3 and 'Famille Orthonormale' not in ans3 and \ 'Base Orthogonale' in ans3 and 'Base Orthonormale' not in ans3 res_ans[3] = 'Famille Orthogonale' in ans4 and 'Famille Orthonormale' not in ans4 and \ 'Base Orthogonale' in ans4 and 'Base Orthonormale' not in ans4 if res_ans.all(): display(Latex(r"C'est correct! " r"CAS 1: une famille de trois vecteurs dans un espace de dimension $2$ ne peut pas être " r"orthonormal et donc elle ne peut pas être une base; toutes les options sont fausses. " r"CAS 2: par rapport au produit scalaire non-usuel défini via la matrice $A$ symétrique et " r"défini positive, cette famille de vecteurs est en fait une base orthonormée; " r"en effet tous les produits scalaires entre les différents éléments de la famille sont égaux " r"à $0$ et les normes de tous les vecteurs (toujours calculés par rapport au produit scalaire " r"non-usuel donnèe) sont égaux à $1$. " r"CAS 3: c'est une famille de $4$ vecteurs orthogonaux (et donc linéairement indépendants!) " r"dans $\mathbb{R}^4$, donc c'est à la fois une famille orthogonale et une base orthogonale; " r"de​toute façon aucune des vecteurs a la norme unitaire et il n'y a donc pas d'orthonormalité. " r"CAS 4: il s'agit d'une famille de $3$ vecteurs orthogonaux (et donc linéairement " r"indépendants!) dans un sous-espace de dimension $5$; donc, c'est une famille orthogonale " r"(et aussi orthonormal), mais elle ne peut pas être une base.")) else: display(Latex("C'est faux.")) wrong_indices = np.where(res_ans == False)[0] right_indices = np.setdiff1d(np.arange(4), wrong_indices) display(Latex(f"Cas correctes: {right_indices+1}")) display(Latex(f"Cas mauvais: {wrong_indices+1}")) interact_manual(correction, ans1=ans1, ans2=ans2, ans3=ans3, ans4=ans4) return def Ex2Chapitre9_5(): """Provides the correction to exercise 2 of notebook 9_5 """ print("Cliquer sur CTRL pour sélectionner plusieurs réponses") style = {'description_width': 'initial'} ans1 = widgets.SelectMultiple( options=['Famille Orthogonale', 'Famille Orthonormale', 'Base Orthogonale', 'Base Orthonormale'], description='Cas 1:', style=style, layout=Layout(width='60%', height='80px'), disabled=False, ) ans2 = widgets.SelectMultiple( options=['Famille Orthogonale', 'Famille Orthonormale', 'Base Orthogonale', 'Base Orthonormale'], description='Cas 2:', style=style, layout=Layout(width='60%', height='80px'), disabled=False, ) ans3 = widgets.SelectMultiple( options=['Famille Orthogonale', 'Famille Orthonormale', 'Base Orthogonale', 'Base Orthonormale'], description='Cas 3:', style=style, layout=Layout(width='60%', height='80px'), disabled=False, ) ans4 = widgets.SelectMultiple( options=['Famille Orthogonale', 'Famille Orthonormale', 'Base Orthogonale', 'Base Orthonormale'], description='Cas 4:', style=style, layout=Layout(width='60%', height='80px'), disabled=False, ) def correction(ans1, ans2, ans3, ans4): res_ans = np.zeros(4).astype(bool) res_ans[0] = 'Famille Orthogonale' not in ans1 and 'Famille Orthonormale' not in ans1 and \ 'Base Orthogonale' not in ans1 and 'Base Orthonormale' not in ans1 res_ans[1] = 'Famille Orthogonale' in ans2 and 'Famille Orthonormale' not in ans2 and \ 'Base Orthogonale' not in ans2 and 'Base Orthonormale' not in ans2 res_ans[2] = 'Famille Orthogonale' in ans3 and 'Famille Orthonormale' in ans3 and \ 'Base Orthogonale' in ans3 and 'Base Orthonormale' in ans3 res_ans[3] = 'Famille Orthogonale' in ans4 and 'Famille Orthonormale' not in ans4 and \ 'Base Orthogonale' in ans4 and 'Base Orthonormale' not in ans4 if res_ans.all(): display(Latex(r"C'est correct! CASE 1: CAS 1: c'est la base du monôme standard de $\mathbb{P}^4(\mathbb{R})$; " r"de toute façon, il n'est ni orthogonal ni orthonormé par rapport au produit scalaire usuel. " r"CAS 2: c'est une famille de $4$ vecteurs dans un sous-espace de dimension $5$, donc il ne " r"peut pas être une base; de toute façon ses éléments sont orthogonaux entre eux, donc c'est " r"une famille orthogonale (non orthonormé, car aucune des normes des vecteurs est ègale à $1$)" r"CAS 3: c'est une famille de $3$ vecteurs orthogonaux, tous avec norme unitaire, dans un " r"sous-espace de dimension $3$; il s'agit donc d'une famille et d'une base orthogonales et " r"orthonormées. Remarque que tous les calculs doivent être effectués par rapport au produit" r"scalaire non-usuel donnèe, caractérisé par la fonction de pondération $w(x)=1-x^2$." r"CAS 4: c'est la base dite de Fourier et elle est constituée d'un nombre infini de modes, " r"dont la fréquence augmente jusqu'à l'infini (étant infinie la dimension du espace vectoriel " r"$V = \mathcal{C}\left([-\pi;\pi]; \mathbb{R}\right)$); de plus, tous les éléments " r"sont orthogonales entre elles, mais leurs normes sont égales à $\sqrt{2\pi}$ (pour la " r"fonction constante) et à $\sqrt{\ pi}$ (pour toutes les autres fonctions trigonométriques), " r"donc la base est orthogonale mais non orthonormal.")) else: display(Latex("C'est faux.")) wrong_indices = np.where(res_ans == False)[0] right_indices = np.setdiff1d(np.arange(4), wrong_indices) display(Latex(f"Cas correctes: {right_indices+1}")) display(Latex(f"Cas mauvais: {wrong_indices+1}")) interact_manual(correction, ans1=ans1, ans2=ans2, ans3=ans3, ans4=ans4) return def Ex3Chapitre9_5(ans, case_nb=1): """Provides the correction to exercise 2 of notebook 9_5 :param ans: answer of the user :type ans: numpy.ndarray or list :param case_nb: number of the test case. It defaults to 1 :type case_nb: int """ int_limits = None weight_function = None W = None if case_nb == 1: B = [[2,0,1], [0,1,-1], [1,1,0]] v = [1,1,1] elif case_nb == 2: B = [[2,0,1,1], [0,1,-1,1], [1,0,-1,-1], [0,2,1,-1]] v = [-1,0,2,-2] elif case_nb == 3: B = [lambda x: 1.0, lambda x: x-1/2, lambda x: x**2-x+1/6, lambda x: x**3 - 3/2*x**2 + 3/5*x -1/20] v = lambda x: x**3 - x**2 + x - 1 int_limits = [0,1] elif case_nb == 4: B = [lambda x: np.sqrt(3)/2, lambda x: np.sqrt(15/2)*x, lambda x: np.sqrt(7)*5/7*x**2 - np.sqrt(7)/14] v = lambda x: x**2 + x + 1 int_limits = [-1,1] weight_function = lambda x: 1 - x**2 else: raise ValueError(f"Le cas numéro {case_nb} n'est pas disponible") v_B = compute_expansion_coefficients(B, v, int_limits=int_limits, weight_function=weight_function, W=W) solution_button = widgets.Button(description='Solution', disabled=True) box = HBox(children=[solution_button]) out = widgets.Output() @out.capture() def solution(e): out.clear_output() display(Latex(f"Solution: {v_B}")) if case_nb == 4: display(Latex(r"C'est le cas dans lequel la norme de $v$ peut être calculée rapidement. En effet, " r"base donnée est orthonormée, donc la norme de $v$ est égale à celle du vecteur exprimant " r"ses coordonnées par rapport à $\mathcal{B}$ et il peut être dérivé sans effectuer aucune " r"intégration numérique!")) threshold = 1e-4 if np.linalg.norm(ans-v_B) <= threshold: display(Latex("C'est correct!")) else: solution_button.disabled = False display(Latex("C'est faux! N'oubliez pas d'insérer tous les résultats avec 4 chiffres après la virgule")) solution_button.on_click(solution) display(box) display(out) return def Ex1Chapitre9_6_7(ans, case_nb=1): """Provides the correction to exercise 1 of notebook 9_6_7 :param ans: answer of the user :type ans: numpy.ndarray or list or function :param case_nb: number of the test case. It defaults to 1 :type case_nb: int """ W = None int_limits = None x = sp.Symbol('x') weight_function = 1 + 0*x if case_nb == 1: u = [-1, 2] v = [1, 1] W = np.eye(2) elif case_nb == 2: u = [1, 2, 1] v = [0, 1, -1] W = np.array([[2, 0, -1], [0, 1, 0], [-1, 0, 2]]) elif case_nb == 3: u = [-1, 0, 1, 0, 1] v = [0, 1, -1, 1, 0] W = np.eye(5) elif case_nb == 4: u = x**2 - 1 v = x**1 int_limits = [0, 1] elif case_nb == 5: u = x**2 - x v = x + 1 int_limits = [-1, 1] weight_function = 1 - x**2 else: raise ValueError(f"Le cas numéro {case_nb} n'est pas disponible") proj_u_v = project(u, v, W=W, int_limits=int_limits, weight_function=weight_function) if type(ans) is list: ans = np.array(ans) threshold = 1e-4 if case_nb in {1, 2, 3}: try: assert type(ans) is list or type(ans) is np.ndarray and len(ans) == len(proj_u_v) except AssertionError: raise ValueError(f"La réponse doit être une liste ou un numpy array 1D de longueur {len(proj_u_v)}") res = (np.abs(ans - proj_u_v) <= threshold).all() elif case_nb in {4, 5}: try: assert isinstance(ans, sp.Basic) except AssertionError: raise ValueError(f"La réponse doit être une fonction univariée!") xx = np.linspace(int_limits[0], int_limits[1], 1000) res = (np.abs(np.vectorize(sp.lambdify(x, ans - proj_u_v, "numpy"))(xx)) <= threshold).all() else: res = False if res: display(Latex("C'est correct!")) if case_nb == 1: vector_plot_2D([u, v, proj_u_v.tolist(), (np.array(u) - proj_u_v).tolist()], orig=[[0, 0], [0, 0], [0, 0], proj_u_v.tolist()], labels=['u', 'v', 'proj_v(u)', 'u-proj_v(u)']) elif case_nb == 2: vector_plot_3D([u, v, proj_u_v.tolist(), np.array(u - proj_u_v).tolist()], orig=[[0, 0, 0], [0, 0, 0], [0, 0, 0], proj_u_v.tolist()], labels=['u', 'v', 'proj_v(u)', 'u-proj_v(u)']) else: display(Latex(f"Aucune représentation graphique n'est disponible pour le cas de test numéro {case_nb}")) else: display(Latex("C'est faux! N'oubliez pas d'insérer tous les résultats numerique avec 4 chiffres " "après la virgule")) if case_nb == 1: vector_plot_2D([u, v, ans.tolist()], orig=[[0, 0], [0, 0], [0, 0]], labels=['u', 'v', 'ans', 'u-ans']) elif case_nb == 2: vector_plot_3D([u, v, ans.tolist()], orig=[[0, 0, 0], [0, 0, 0], [0, 0, 0]], labels=['u', 'v', 'ans', 'u-ans']) else: display(Latex(f"Aucune représentation graphique n'est disponible pour le cas de test numéro {case_nb}")) if case_nb in {1, 2, 3}: orth = np.inner(u - ans, np.dot(W, v)) elif case_nb in {4, 5}: orth = sp.integrate(weight_function * (u - ans) * v, (x, int_limits[0], int_limits[1])) display(Latex(f"Le produit scalaire entre 'v' et la différence entre 'u' et sa projection doit être égal à " f"zéro, mais il vaut {orth} dans ce cas!")) return def Ex1Chapitre9_8_9(proj1, proj2, case_nb=1): """Provides the correction to exercise 1 of Notebook 9.8-9.9 :param proj1: projection onto W :type proj1: list or numpy.ndarray :param proj2: projection onto the orthogonal complement of W in V :type proj2: list or numy.ndarray :param case_nb: number of the test case. Defaults to 1 :type case_nb: int """ if type(proj1) is list: proj1 = np.array(proj1) if type(proj2) is list: proj2= np.array(proj2) if case_nb == 1: S = [[1, 1]] v = [-1, 0] elif case_nb == 2: S = [[0, 1], [-1, 1]] v = [-3, 5] elif case_nb == 3: S = [[1, 2, 1]] v = [1, 0, 1] elif case_nb == 4: S = [[1, -1, 1], [0, 1, 1]] v = [-1, 0, 1] else: raise ValueError(f"{case_nb} n'est pas un numéro de cas valide!" f"Numéros de cas disponibles: [1,2,3,4]") true_proj_1 = project_on_subspace(v, S, W=None, plot=False, show=False) true_proj_2 = np.array(v) - true_proj_1 try: assert proj1.shape == true_proj_1.shape and proj2.shape == true_proj_2.shape except AssertionError: raise ValueError(f"Les projections doivent être des vecteurs 1D de longueur {true_proj_1.shape[0]}, " f"mais le premier a forme {proj1.shape[0]} " f"et la deuxième a forme {proj2.shape[0]}") if np.linalg.norm(proj1 - true_proj_1) <= 1e-4 and np.linalg.norm(proj2 - true_proj_2) <= 1e-4: display(Latex("C'est correct!")) _ = project_on_subspace(v, S, W=None, plot=True, show=True) else: display(Latex(f"C'est faux! En particulier, les normes $l^2$ des erreurs sur les deux projections sont " f"{np.linalg.norm(proj1 - true_proj_1)} and {np.linalg.norm(proj2 - true_proj_2)}.")) return def Ex2Chapitre9_8_9(proj1, proj2, int_limits, case_nb=1): """Provides the correction to exercise 2 of Notebook 9.8-9.9 :param proj1: projection onto W :type proj1: sympy.function :param proj2: projection onto the ortogonal complement of W in V :type proj2: sympy.function :param int_limits: integration limits :type int_limits: list[float] :param case_nb: number of the test case. Defaults to 1 :type case_nb: int """ x = sp.Symbol('x') if case_nb == 1: S = [1 + 0 * x, x] v = x**2 elif case_nb == 2: S = [1 + 0 * x, x] v = x ** 2 elif case_nb == 3: S = [1 + 0 * x, x ** 2, x ** 3] v = x ** 2 + x elif case_nb == 4: S = [x ** 2, x ** 3] v = 1 + x + x ** 2 + x ** 3 else: raise ValueError(f"{case_nb} n'est pas un numéro de cas valide!" f"Numéros de cas disponibles: [1,2,3,4]") true_proj_1 = project_on_subspace(v, S, int_limits=int_limits, plot=False, show=False) true_proj_2 = np.array(v) - true_proj_1 try: assert isinstance(proj1, sp.Basic) and isinstance(proj2, sp.Basic) except AssertionError: raise ValueError(f"Les réponses doivent être fonctions univariées!") xx = np.linspace(int_limits[0], int_limits[1], 1000) res1 = (np.abs(np.vectorize(sp.lambdify(x, proj1 - true_proj_1, "numpy"))(xx)) <= 1e-4).all() res2 = (np.abs(np.vectorize(sp.lambdify(x, proj2 - true_proj_2, "numpy"))(xx)) <= 1e-4).all() if res1 and res2: display(Latex("C'est correct!")) _ = project_on_subspace(v, S, W=None, plot=True, show=True) else: display(Latex(f"C'est faux!")) return def Ex3Chapitre9_8_9(basis, case_nb=1): """Provides the correction to exercice 3 of notebook 9.8-9.9 :param basis: orthonormal basis for the subspace, entered by the user :type basis: list[list] or numpy.ndarray :param case_nb: case number. Defaults to 1 :type case_nb: int """ is_correct = True if case_nb == 1: mat = [[1, 2], [0, 1]] elif case_nb == 2: mat = [[1, -1]] elif case_nb == 3: mat = [[1, 2, 1], [0, 1, 0], [-2, 0, -2]] elif case_nb == 4: mat = [[-1, 1, 2]] else: raise ValueError(f"{case_nb} n'est pas un numéro de cas valide!" f"Numéros de cas disponibles: [1,2,3,4]") dim = len(basis) elem_dims = [len(basis[i]) for i in range(dim)] true_elem_dim = len(mat[0]) true_basis = (np.array(null_space(mat))).T true_dim = len(true_basis) if not basis and not true_basis: display(Latex("C'est correct! Le sous-espace $W$ coincide avec $\mathbb{R}^n$, donc $W^\perp$ ne contien " "que le vecteur nul.")) return if type(basis) is list: basis = np.array(basis) try: assert type(basis) is np.ndarray and len(basis.shape) == 2 except AssertionError: raise TypeError("The entered basis must be either a 2D numpy array or a list of lists") if not all([elem_dims[i] == true_elem_dim for i in range(dim)]): display(Latex(f"C'est faux! Tous les éléments de la base doivent avoir dimension {true_elem_dim}!")) return if dim != true_dim: display(Latex("C'est faux! La dimension de la base donnée est fausse!")) return if not isDiag(basis.dot(basis.T)): display(Latex("C'est faux! La base doit être orthogonale!")) return i = 0 while i in range(dim) and is_correct: proj = project_on_subspace(basis[i], true_basis, plot=False, show=False) if np.linalg.norm(basis[i] - proj) >= 1e-4: is_correct = False i += 1 i = 0 while i in range(true_dim) and is_correct: true_proj = project_on_subspace(true_basis[i], basis, plot=False, show=False) if np.linalg.norm(true_basis[i] - true_proj) >= 1e-4: is_correct = False i += 1 if is_correct: display(Latex("C'est correct!")) else: display(Latex("C'est faux!")) if len(mat[0]) == 2 and len(basis) == 1: fig = vector_plot_2D(list(chain(*[mat, basis])), labels=list(chain(*[[f'$w_{i}$' for i in range(len(mat))], [f'$w^\perp_{i}$' for i in range(len(basis))]])), show=False) fig = plot_line([mat[0]], data=list(fig.data), layout=fig.layout, label=r'$W$') fig = plot_line(basis, data=list(fig.data), layout=fig.layout, label=r'$W^\perp$') plotly.offline.iplot(fig) elif len(mat[0]) == 3: fig = vector_plot_3D(list(chain(*[mat, basis])), labels=list(chain(*[[f'$w_{i}$' for i in range(len(mat))], [f'$w^\perp_{i}$' for i in range(len(basis))]])), show=False) if len(basis) == 1: _, inds = sp.Matrix(mat).T.rref() fig = plot_plane(np.array(mat)[inds, :], color='rgb(0,255,255)', data=list(fig.data), layout=fig.layout, label=r'$W$') fig = plot_line(basis, data=list(fig.data), layout=fig.layout, label=r'$W^\perp$') elif len(basis) == 2: fig = plot_line([mat[0]], data=list(fig.data), layout=fig.layout, label=r'$W$') fig = plot_plane(basis, color='rgb(0,255,255)', data=list(fig.data), layout=fig.layout, label=r'$W^\perp$') plotly.offline.iplot(fig) return def Ex1Chapitre9_10_11(proj, case_nb=1): """Provides the correction to exercise 1 of Notebook 9.10-9.11 :param proj: projection onto W :type proj: list or numpy.ndarray :param case_nb: number of the test case. Defaults to 1 :type case_nb: int """ if type(proj) is list: proj = np.array(proj) if case_nb == 1: S = [[1, -2]] v = [-2, 1] elif case_nb == 2: S = [[0, 1, 0], [1, -1, 0]] v = [-3, 2, 1] elif case_nb == 3: S = [[1, 2, -1, -2], [0, 1, 0, -1]] v = [0, -1, 1, -1] else: raise ValueError(f"{case_nb} n'est pas un numéro de cas valide!" f"Numéros de cas disponibles: [1,2,3]") true_proj = project_on_subspace(v, S, W=None, plot=False, show=False) try: assert proj.shape == true_proj.shape except AssertionError: raise ValueError(f"La meilleure approximation quadratique dois être un vecteur 1D de longueur " f"{true_proj.shape[0]}, mais il a forme {proj.shape[0]}") if np.linalg.norm(proj - true_proj) <= 1e-4: display(Latex("C'est correct!")) _ = project_on_subspace(v, S, W=None, plot=True, show=True) else: display(Latex(f"C'est faux! En particulier, la norme $l^2$ de l'erreur est " f"{np.linalg.norm(proj - true_proj)}")) return def Ex2Chapitre9_10_11(proj, int_limits, case_nb=1): """Provides the correction to exercise 2 of Notebook 9.10-9.11 :param proj: projection onto W :type proj: sympy.function :param int_limits: integration limits :type int_limits: list[float] :param case_nb: number of the test case. Defaults to 1 :type case_nb: int """ x = sp.Symbol('x') if case_nb == 1: S = [1 + 0 * x, x] v = sp.Abs(x) elif case_nb == 2: S = [1 + 0 * x, x, x**2] v = sp.Abs(x) elif case_nb == 3: S = [1 + 0 * x, x, x ** 2] v = sp.sin(x) elif case_nb == 4: S = [1 + 0 * x, x, x ** 2, x ** 3] v = sp.exp(x) else: raise ValueError(f"{case_nb} n'est pas un numéro de cas valide!" f"Numéros de cas disponibles: [1,2,3,4]") true_proj = project_on_subspace(v, S, int_limits=int_limits, plot=False, show=False) try: assert isinstance(proj, sp.Basic) except AssertionError: raise ValueError(f"Les réponses doivent être fonctions univariées!") xx = np.linspace(int_limits[0], int_limits[1], 1000) res = (np.abs(np.vectorize(sp.lambdify(x, proj - true_proj, "numpy"))(xx)) <= 1e-4).all() if res: display(Latex("C'est correct!")) _ = project_on_subspace(v, S, int_limits=int_limits, plot=True, show=True) else: display(Latex(f"C'est faux!")) return def Ex1_Chapitre9_12(sol, case_nb=1): """Provides the correction to exercise 1 of Notebook 9.12 :param sol: solution to the system, in LS sense :type sol: sp.FiniteSet :param case_nb: number of the test case. Defaults to 1 :type case_nb: int """ x, y, z = sp.symbols('x, y, z') if case_nb == 1: A = np.array([[1, 0], [1, 0]]) b = np.array([1, 3]) elif case_nb == 2: A = np.array([[1, 1], [1, -1], [2, 0]]) b = np.array([1, 2, -2]) elif case_nb == 3: A = np.array([[1, 0, 0], [0, 1, 1], [1, 0, 0]]) b = np.array([-1, 2, 1]) elif case_nb == 4: A = np.array([[1, 0, 1], [-1, 1, -1], [0, 1, 1], [1, 1, 0], [-1, 0, 1]]) b = np.array([0, 2, 0, 1, 4]) else: raise ValueError(f"{case_nb} n'est pas un numéro de cas valide!" f"Numéros de cas disponibles: [1,2,3,4]") sys = sp.Matrix(A.T@A), sp.Matrix(A.T@b) sol_true = linsolve(sys, x, y) if case_nb in {1,2} else linsolve(sys, x, y, z) is_correct = compare_sympy_FiniteSets(sol, sol_true) if is_correct: display(Latex("C'est correct!")) if case_nb in {1,2}: Plot2DSys(-7, 7, 15, A.tolist(), b.tolist(), with_sol=True, with_sol_lstsq=True) else: Plot3DSys(-7, 7, 15, A, b, with_sol=True, with_sol_lstsq=True) else: display(Latex("C'est faux!")) return def Ex2_Chapitre9_12(sol, case_nb=1): """Provides the correction to exercise 2 of Notebook 9.12 :param sol: solution to the system, in LS sense :type sol: sp.FiniteSet :param case_nb: number of the test case. Defaults to 1 :type case_nb: int """ x, y, z = sp.symbols('x, y, z') if case_nb == 1: A = np.array([[0, 1], [0, -1]]) b = np.array([0, 2]) elif case_nb == 2: A = np.array([[0, 1], [1, 1], [1, -2]]) b = np.array([1, 1, 0]) elif case_nb == 3: A = np.array([[0, 1, 0], [1, 0, 1], [-2, 0, 0]]) b = np.array([0, 2, -1]) elif case_nb == 4: A = np.array([[1, 0, 0], [1, -1, -1], [0, 1, 0], [1, 0, 0], [0, 1, -1]]) b = np.array([2, 0, 0, -1, -1]) else: raise ValueError(f"{case_nb} n'est pas un numéro de cas valide!" f"Numéros de cas disponibles: [1,2,3,4]") sys = sp.Matrix(A.T @ A), sp.Matrix(A.T @ b) sol_true = linsolve(sys, x, y) if case_nb in {1,2} else linsolve(sys, x, y, z) is_correct = compare_sympy_FiniteSets(sol, sol_true) if is_correct: display(Latex("C'est correct!")) if case_nb in {1,2}: Plot2DSys(-7, 7, 15, A.tolist(), b.tolist(), with_sol=True, with_sol_lstsq=True) else: Plot3DSys(-7, 7, 15, A.tolist(), b.tolist(), with_sol=True, with_sol_lstsq=True) else: display(Latex("C'est faux!")) return def Ex3_Chapitre9_12_generate_data(noise_scale_1, noise_scale_2, num_points=251, plot=True, K=1): """Generates the data used in exercise 3 of notebook 9.12 :param noise_scale_1: first noise scale parameter, representing the scale factor on the standard deviation of the first uncorrelated gaussian noise component :type noise_scale_1: float :param noise_scale_2: second noise component scale parameter, representing the scale factor on the standard deviation of the second correlated noise component :type noise_scale_2: float :param num_points: number of datapoints to be generated. Defaults to 251 :type num_points: int :param plot: if True, a scatter plot of the data (2D or 3D depending on the case number) is displayed. Defaults to True. :type plot: bool :param K: number of the case. Defaults to 1 :type K: int :return: generated data and figure, if 'plot' is set to True :rtype: Union(list[np.ndarray], plotly.Figure) or list[np.ndarray] """ try: assert K in {1, 2} except AssertionError: raise ValueError(f"{K} n'est pas un numéro valide!" f"Numéros disponibles: [1,2]") x1 = np.random.uniform(-1, 1, num_points) x2 = np.random.uniform(-1, 1, num_points) if K == 2 else None ones = np.ones_like(x1) X = np.hstack((np.expand_dims(ones, 1), np.expand_dims(x1, 1))) if K == 2: X = np.hstack((X, np.expand_dims(x2, 1))) coeffs = np.random.uniform(-2, 2, 2 if K == 1 else 3) y = coeffs[0] + coeffs[1] * x1 + (coeffs[2] * x2 if K == 2 else 0) Y = y + np.mean(y) * noise_scale_1 * np.random.normal(0, 1, num_points) + \ noise_scale_2 * np.random.normal(-1, 1, num_points) * np.abs(y) if plot: fig = scatterPlot(x1=x1, x2=x2, y=Y) data = [X, Y] if plot: return data, fig else: return data def Ex3_Chapitre9_12(sys, data, fig): """Provides the solution to exercise 3 of notebook 9.12 :param sys: matrix and right-hand side of the linear system to be solved :type sys: tuple(np.ndarray, np.ndarray) or tuple(list[list], list) or tuple(sp.Matrix, sp.Matrix) :param data: data on which doing linear regression via Least-Squares :type data: tuple(np.ndarray, np.ndarray) or list[np.ndarray, np.ndarray] :param fig: figure containing the data scatter plot, to be completed with the LS solution :type fig: plotly.Figure """ + if not isinstance(sys[0], sp.Matrix) or not isinstance(sys[1], sp.Matrix): sys = list(sys) if not isinstance(sys[0], sp.Matrix): sys[0] = sp.Matrix(sys[0]) if not isinstance(sys[1], sp.Matrix): sys[1] = sp.Matrix(sys[1]) sys = tuple(sys) X = data[0] Y = data[1] c0, c1, c2 = sp.symbols('c0, c1, c2') if X.shape[1] == 2: sol = linsolve(sys, c0, c1) elif X.shape[1] == 3: sol = linsolve(sys, c0, c1, c2) else: raise ValueError(f"La matrice de données X doit avoir 2 ou 3 colonnes, alors qu'ici elle en a {X.shape[1]}") print(f"Coefficients de la solution entrée: {sol.args[0]}") true_M = sp.Matrix(X.T@X) true_f = sp.Matrix(X.T@Y) true_sys = true_M, true_f if X.shape[1] == 2: true_sol = linsolve(true_sys, c0, c1) elif X.shape[1] == 3: true_sol = linsolve(true_sys, c0, c1, c2) else: raise ValueError(f"La matrice de données X doit avoir 2 ou 3 colonnes, alors qu'ici elle en a {X.shape[1]}") print(f"Coefficients de la solution: {true_sol.args[0]}") is_correct = compare_sympy_FiniteSets(sol, true_sol) x1 = np.linspace(-1, 1, 501) x2 = np.linspace(-1, 1, 501) if X.shape[1] == 3: x1Grid, x2Grid = np.meshgrid(x1, x2) fig_data = list(fig.data) fig_layout = fig.layout red = 'rgb(255, 0, 0)' green = 'rgb(0, 255, 0)' colorscale_red = [[i, red] for i in np.linspace(0, 1, 11)] colorscale_green = [[i, green] for i in np.linspace(0, 1, 11)] if is_correct: display(Latex("C'est correct!")) if X.shape[1] == 2: trace1 = go.Scatter(x=x1, y=float(sol.args[0][0]) + float(sol.args[0][1]) * x1, mode='lines', line=dict(color=red), name='Solution') elif X.shape[1] == 3: trace1 = go.Surface(x=x1Grid, y=x2Grid, z=float(sol.args[0][0]) + float(sol.args[0][1]) * x1Grid + float(sol.args[0][2]) * x2Grid, opacity=0.6, colorscale=colorscale_red, showscale=False, showlegend=True, name='Solution') else: display(Latex("C'est faux!")) do_second_plot = False if X.shape[1] == 2: trace1 = go.Scatter(x=x1, y=float(true_sol.args[0][0]) + float(true_sol.args[0][1]) * x1, mode='lines', line=dict(color=red), name='True Solution') if isinstance(sol, sp.sets.FiniteSet) and \ all([isinstance(sol.args[0][i], sp.Number) for i in range(len(sol.args[0]))]): trace2 = go.Scatter(x=x1, y=float(sol.args[0][0]) + float(sol.args[0][1]) * x1, mode='lines', line=dict(color=green), name='Your Solution') do_second_plot = True elif isinstance(sol, sp.sets.EmptySet): display(Latex("Le système entré n'admet aucune solution! " "Etes-vous sûr qu'il est écrit sous la forme des moindres carrés?")) else: display(Latex("Le système entré admet plusieurs solutions!")) elif X.shape[1] == 3: trace1 = go.Surface(x=x1Grid, y=x2Grid, z=float(true_sol.args[0][0]) + float(true_sol.args[0][1]) * x1Grid + float(true_sol.args[0][2]) * x2Grid, opacity=0.6, colorscale=colorscale_red, showscale=False, showlegend=True, name='True Solution') if isinstance(sol, sp.sets.FiniteSet) and \ all([isinstance(sol.args[0][i], sp.Number) for i in range(len(sol.args[0]))]): trace2 = go.Surface(x=x1Grid, y=x2Grid, z=float(sol.args[0][0]) + float(sol.args[0][1]) * x1Grid + float(sol.args[0][2]) * x2Grid, opacity=0.6, colorscale=colorscale_green, showscale=False, showlegend=True, name='Your Solution') do_second_plot = True elif isinstance(sol, sp.sets.EmptySet): display(Latex("Le système entré n'admet aucune solution! " "Etes-vous sûr qu'il est écrit sous la forme des moindres carrés?")) else: display(Latex("Le système entré admet plusieurs solutions!")) fig_data.append(trace1) if not is_correct and do_second_plot: fig_data.append(trace2) new_fig = go.Figure(data=fig_data, layout=fig_layout) plotly.offline.iplot(new_fig) - return \ No newline at end of file + return + + +def Ex2Chapitre9_13_14(Q, R, sol, case_nb=1): + """Provides the solution to exercise 2 of notebook 9.13-9.14 + + :param Q: orthogonal matrix of the factorisation + :type Q: list[list] or np.ndarray + :param R: superior triangular matrix of the factorisation + :type R: list[list] or np.ndarray + :param sol: LS solution to the linear system + :type sol: sp.sets.FiniteSet or sp.sets.EmptySet + :param case_nb: current case number. Defaults to 1 + :type case_nb: int + """ + + if not isinstance(Q, np.ndarray): + Q = np.array(Q) + if not isinstance(R, np.ndarray): + R = np.array(R) + + x, y, z = sp.symbols('x, y, z') + + if case_nb == 1: + A = np.array([[1, 1], [1, -1]]) + b = np.array([1, -1]) + elif case_nb == 2: + A = np.array([[-2, 1], [1, 3], [2, 0]]) + b = np.array([1, 2, 0]) + elif case_nb == 3: + A = np.array([[1, 0, 0], [2, 0, 1], [0, 4, 0], [0, 3, 2], [2, 0, 2]]) + b = np.array([0, 1, 0, -1, 1]) + elif case_nb == 4: + A = np.array([[5, 0, 0], [-1, -2, -2], [1, 1, 2], [3, 2, 0], [0, 0, 1]]) + b = np.array([0, 2, 0, 1, 4]) + else: + raise ValueError(f"{case_nb} n'est pas un numéro de cas valide!" + f"Numéros de cas disponibles: [1,2,3,4]") + + try: + Q_true, R_true = unique_qr(A, only_valid=True) + except ValueError: + if Q is None and R is None and x is None: + display(Latex("C'est correct! La matrice $A$ ne correspond pas aux exigences qui garantissent l'existence " + "d'une factorisation QR 'valide'!")) + else: + display(Latex("C'est faux! En effet, la matrice $A$ ne correspond pas aux exigences qui garantissent " + "l'existence d'une factorisation QR 'valide'; vous devez donc entrer 'None' pour les valeurs " + "des $Q$, $R$ et de la solution $\hat{x}$ au système linéaire au sens des moindres carrés.")) + return + + Q_correct = np.linalg.norm(Q - Q_true) < 1e-4 + R_correct = np.linalg.norm(R - R_true) < 1e-4 + + sys = sp.Matrix(R_true), sp.Matrix(Q_true.T @ b) + sol_true = linsolve(sys, x, y) if case_nb in {1, 2} else linsolve(sys, x, y, z) + sol_correct = compare_sympy_FiniteSets(sol, sol_true) + + if Q_correct and R_correct and sol_correct: + display(Latex("C'est correct!")) + if case_nb in {1,2}: + Plot2DSys(-7, 7, 15, A.tolist(), b.tolist(), with_sol=True, with_sol_lstsq=True) + else: + Plot3DSys(-7, 7, 15, A.tolist(), b.tolist(), with_sol=True, with_sol_lstsq=True) + elif not Q_correct or not R_correct: + display(Latex("C'est faux! La factoristation QR de A n'est pas correct!")) + elif not sol_correct: + display(Latex("C'est faux! La factorisation QR de $A$ est correcte, mais la solution du système linéaire au " + "le sens des moindres carrés n'est pas!")) + + return + diff --git a/Librairie/AL_Fct.py b/Librairie/AL_Fct.py new file mode 100644 index 0000000..1d73931 --- /dev/null +++ b/Librairie/AL_Fct.py @@ -0,0 +1,3686 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Created on Wed Mar 13 16:42:29 2019 + +@author: jecker +""" + +from __future__ import division +import numpy as np +import sympy as sp +from sympy.solvers.solveset import linsolve +from IPython.display import display, Latex +import plotly +import plotly.graph_objs as go + +plotly.offline.init_notebook_mode(connected=True) +from IPython.core.magic import register_cell_magic +from IPython.display import HTML +import ipywidgets as widgets +import random +from ipywidgets import interact_manual +import matplotlib.pyplot as plt +from scipy.integrate import quad, nquad +from operator import mul +from itertools import cycle, chain +from collections.abc import Iterable + + +@register_cell_magic +def bgc(color): + script = ( + "var cell = this.closest('.jp-CodeCell');" + "var editor = cell.querySelector('.jp-Editor');" + "editor.style.background='{}';" + "this.parentNode.removeChild(this)" + ).format(color) + + display(HTML(''.format(script))) + + +############################################################################### + +def printMonomial(coeff, index=None, include_zeros=False): + """Prints the monomial coeff*x_{index} in optimal way + + :param coeff: value of the coefficient + :type coeff: float + :param index: index of the monomial. If None, only the numerical value of the coefficient is displayed + :type index: int or NoneType + :param include_zeros: if True, monomials of type 0x_n are printed. Defaults to False + :type include_zeros: bool + :return: string representative of the monomial + :rtype: str + """ + + if index is not None: + coeff = abs(coeff) + + if coeff % 1: + return str(round(coeff, 3)) + ('x_' + str(index) if index is not None else "") + elif not coeff: + if index is None: + return str(0) + else: + return str(0) + 'x_' + str(index) if include_zeros else "" + elif coeff == 1: + return 'x_' + str(index) if index is not None else str(int(coeff)) + elif coeff == -1: + return 'x_' + str(index) if index is not None else str(int(coeff)) + else: + return str(int(coeff)) + ('x_' + str(index) if index is not None else "") + + +def printPlusMinus(coeff, include_zeros=False): + """Prints a plus or minus sign, depending on the sign of the coefficient + + :param coeff: value of the coefficient + :type coeff: float + :param include_zeros: if True, 0-coefficients are assigned a "+" sign + :type include_zeros: bool + :return: "+" if the coefficient is positive, "-" if it is negative, "" if it is 0 + :rtype: str + """ + if coeff > 0: + return "+" + elif coeff < 0: + return "-" + else: + return "+" if include_zeros else "" + + +def strEq(n, coeff): + """Method that provides the Latex string of a linear equation, given the number of unknowns and the values + of the coefficients. If no coefficient value is provided, then a symbolic equation with `n` unknowns is plotted. + In particular: + + * **SYMBOLIC EQUATION**: if the number of unknowns is either 1 or 2, then all the equation is + displayed while, if the number of unknowns is higher than 2, only the first and last term of the equation + are displayed + * **NUMERICAL EQUATION**: whichever the number of unknowns, the whole equation is plotted. Numerical values + of the coefficients are rounded to the third digit + + :param n: number of unknowns of the equation + :type n: int + :param coeff: coefficients of the linear equation. It must be [] if a symbolic equation is desired + :type: list[float] + :return: Latex string representing the equation + :rtype: str + """ + + Eq = '' + if not len(coeff): + if n is 1: + Eq = Eq + 'a_1x_1 = b' + elif n is 2: + Eq = Eq + 'a_1x_1 + a_2x_2 = b' + else: + Eq = Eq + 'a_1x_1 + \ldots + ' + 'a_' + str(n) + 'x_' + str(n) + '= b' + else: + all_zeros = len(set(coeff[:-1])) == 1 and not coeff[0] # check if all lhs coefficients are 0 + start_put_sign = all_zeros + if n is 1: + Eq += "-" if coeff[0] < 0 else "" + Eq += printMonomial(coeff[0], index=1, include_zeros=all_zeros) + "=" + printMonomial(coeff[-1]) + else: + Eq += "-" if coeff[0] < 0 else "" + Eq += printMonomial(coeff[0], index=1, include_zeros=all_zeros) + start_put_sign = start_put_sign or coeff[0] is not 0 + for i in range(1, n): + Eq += printPlusMinus(coeff[i], include_zeros=all_zeros) if start_put_sign \ + else "-" if coeff[i] < 0 else "" + Eq += printMonomial(coeff[i], index=i+1, include_zeros=all_zeros) + start_put_sign = start_put_sign or coeff[i] is not 0 + Eq += "=" + printMonomial(coeff[-1]) + return Eq + + +def printEq(coeff, b, *args): + """Method that prints the Latex string of a linear equation, given the values of the coefficients. If no coefficient + value is provided, then a symbolic equation with `n` unknowns is plotted. In particular: + + * **SYMBOLIC EQUATION**: if the number of unknowns is either 1 or 2, then all the equation is + displayed while, if the number of unknowns is higher than 2, only the first and last term of the equation + are displayed + * **NUMERICAL EQUATION**: whichever the number of unknowns, the whole equation is plotted. Numerical values + of the coefficients are rounded to the third digit + + :param coeff: coefficients of the left-hand side of the linear equation + :type: list[float] + :param b: right-hand side coefficient of the linear equation + :type b: float + :param *args: optional; if passed, it contains the number of unknowns to be considered. If not passed, all the + unknowns are considered, i.e. n equals the length of the coefficients list + :type: *args: list + """ + + if len(args) == 1: + n = args[0] + else: + n = len(coeff) + coeff = coeff + b + texEq = '$' + texEq = texEq + strEq(n, coeff) + texEq = texEq + '$' + display(Latex(texEq)) + return + + +def printSyst(A, b, *args): + """Method that prints a linear system of `n` unknowns and `m` equations. If `A` and `b` are empty, then a symbolic + system is printed; otherwise a system containing the values of the coefficients stored in `A` and `b`, approximated + up to their third digit is printed. + + :param A: left-hand side matrix. It must be [] if a symbolic system is desired + :type: list[list[float]] + :param b: right-hand side vector. It must be [] if a symbolic system is desired + :type b: list[float] + :param args: optional; if not empty, it is a list of two integers representing the number of equations of the + linear system (i.e. `m`) and the number of unknowns of the system (i.e. `n`) + :type: list + """ + + if (len(args) == 2) or (len(A) == len(b)): # ensures that MatCoeff has proper dimensions + if len(args) == 2: + m = args[0] + n = args[1] + else: + m = len(A) + n = len(A[0]) + + texSyst = '$\\begin{cases}' + Eq_list = [] + if len(A) and len(b): + if type(b[0]) is list: + b = np.array(b).astype(float) + A = np.concatenate((A, b), axis=1) + else: + A = [A[i] + [b[i]] for i in range(0, m)] # becomes augmented matrix + A = np.array(A) # just in case it's not + + for i in range(m): + if not len(A) or not len(b): + Eq_i = '' + if n is 1: + Eq_i = Eq_i + 'a_{' + str(i + 1) + '1}' + 'x_1 = b_' + str(i + 1) + elif n is 2: + Eq_i = Eq_i + 'a_{' + str(i + 1) + '1}' + 'x_1 + ' + 'a_{' + str(i + 1) + '2}' + 'x_2 = b_' + str( + i + 1) + else: + Eq_i = Eq_i + 'a_{' + str(i + 1) + '1}' + 'x_1 + \ldots +' + 'a_{' + str(i + 1) + str( + n) + '}' + 'x_' + str(n) + '= b_' + str(i + 1) + else: + Eq_i = strEq(n, A[i, :]) # attention A is (A|b) + Eq_list.append(Eq_i) + texSyst = texSyst + Eq_list[i] + '\\\\' + texSyst = texSyst + '\\end{cases}$' + display(Latex(texSyst)) + else: + print("La matrice des coefficients n'a pas les bonnes dimensions") + + return + + +def texMatrix(*args): + """Method which produces the Latex string corresponding to the input matrix. + + .. note:: if two inputs are passed, they represent A and b respectively; as a result the augmented matrix A|B is + plotted. Otherwise, if the input is unique, just the matrix A is plotted + + :param args: input arguments; they could be either a matrix and a vector or a single matrix + :type args: list[list] or list[numpy.ndarray] + :return: Latex string representing the input matrix or the input matrix augmented by the input vector + :rtype: str + """ + + if len(args) == 2: # matrice augmentée + if not type(args[0]) is np.ndarray: + A = np.array(args[0]).astype(float) + else: + A = args[0].astype(float) + if len(A.shape) <= 1: + raise ValueError("If two input arguments are passed, the first one must be either a matrix or a column " + "vector! Row vectors or empty vectors are not accepted.") + m = A.shape[1] + if not type(args[1]) is np.array: + b = np.array(args[1]).astype(float) + else: + b = args[1].astype(float) + if len(b.shape) <= 1: + raise ValueError("If two input arguments are passed, the second one must be either a matrix or a column " + "vector! Row vectors or empty vectors are not accepted.") + try: + assert A.shape[0] == b.shape[0] + except AssertionError: + raise ValueError(f"If two input arguments are passed, they must both be either matrices or column vectors, " + f"with the same number of rows. In this case, instead, the first input argument has " + f"{A.shape[0]} rows, while the second one has {b.shape[0]}") + + A = np.concatenate((A, b), axis=1) + texApre = '\\left(\\begin{array}{' + texA = '' + for i in np.asarray(A): + texALigne = '' + texALigne = texALigne + str(round(i[0], 4) if i[0] % 1 else int(i[0])) + if texA == '': + texApre = texApre + 'c' + for j in i[1:m]: + if texA == '': + texApre = texApre + 'c' + texALigne = texALigne + ' & ' + str(round(j, 4) if j % 1 else int(j)) + if texA == '': + texApre = texApre + '| c' + for j in i[m:]: + if texA == '': + texApre = texApre + 'c' + texALigne = texALigne + ' & ' + str(round(j, 4) if j % 1 else int(j)) + texALigne = texALigne + ' \\\\' + texA = texA + texALigne + texA = texApre + '} ' + texA[:-2] + ' \\end{array}\\right)' + elif len(args) == 1: # matrice des coefficients + if not type(args[0]) is np.ndarray: + A = np.array(args[0]).astype(float) + else: + A = args[0].astype(float) + + texApre = '\\left(\\begin{array}{' + texApost = ' \\end{array}\\right)' + texA = '' + if len(A.shape) == 0 or A.shape[0] == 0: + return texApre + '}' + texA + texApost + elif len(A.shape) == 1: + A = np.expand_dims(A, 0) + + for i in np.asarray(A): + texALigne = '' + texALigne = texALigne + str(round(i[0], 4) if i[0] % 1 else int(i[0])) + if texA == '': + texApre = texApre + 'c' + for j in i[1:]: + if texA == '': + texApre = texApre + 'c' + texALigne = texALigne + ' & ' + str(round(j, 4) if j % 1 else int(j)) + texALigne = texALigne + ' \\\\' + texA = texA + texALigne + texA = texApre + '} ' + texA[:-2] + texApost + else: + print("Ce n'est pas une matrice des coefficients ni une matrice augmentée") + texA = '' + + return texA + + +def printA(*args, name=None): + """Method which prints the input matrix. + + .. note:: if two inputs are passed, they represent A and b respectively; as a result the augmented matrix A|B is + plotted. Otherwise, if the input is unique, just the matrix A is plotted + + :param args: input arguments; they could be either a matrix and a vector or a single matrix + :type args: list[numpy.ndarray] or list[list] + :param name: if not None, it is the name of the matrix; what is printed is then {name} = {value}. If None, only the + matrix value is displayed. Defaults to None + :type name: str or NoneType + """ + + if name is not None and type(name) is str: + texA = '$' + name + ' = ' + texMatrix(*args) + '$' + else: + texA = '$' + texMatrix(*args) + '$' + display(Latex(texA)) + return + + +def printEquMatrices(*args): + """Method which prints the list of input matrices. + + .. note:: if two inputs are passed, they represent the list of coefficient matrices A and the list of rhs b + respectively; as a result the augmented matrices A|B are plotted. Otherwise, if the input is unique, just the + matrices A are plotted + + :param args: input arguments; they could be either a list of matrices and a list of vectors or + a single list of matrices + :type args: list + """ + + # list of matrices is M=[M1, M2, ..., Mn] where Mi=(Mi|b) + if len(args) == 2: + listOfMatrices = args[0] + listOfRhS = args[1] + texEqu = '$' + texMatrix(listOfMatrices[0], listOfRhS[0]) + for i in range(1, len(listOfMatrices)): + texEqu = texEqu + '\\quad \\sim \\quad' + texMatrix(listOfMatrices[i], listOfRhS[i]) + texEqu = texEqu + '$' + display(Latex(texEqu)) + else: + listOfMatrices = args[0] + texEqu = '$' + texMatrix(listOfMatrices[0]) + for i in range(1, len(listOfMatrices)): + texEqu = texEqu + '\\quad \\sim \\quad' + texMatrix(listOfMatrices[i]) + texEqu = texEqu + '$' + display(Latex(texEqu)) + return + + +def printEquMatricesAug(listOfMatrices, listOfRhS): # list of matrices is M=[M1, M2, ..., Mn] where Mi=(Mi|b) + texEqu = '$' + texMatrix(listOfMatrices[0], listOfRhS[0]) + for i in range(1, len(listOfMatrices)): + texEqu = texEqu + '\\quad \\sim \\quad' + texMatrix(listOfMatrices[i], listOfRhS[i]) + texEqu = texEqu + '$' + display(Latex(texEqu)) + + +def printLUMatrices(LList, UList): + """Method which prints the list of L and U matrices, constructed during an interactive LU decomposition routine + + :param LList: list of lower triangular matrices + :type LList: list[numpy.ndarray] + :param UList: list of upper triangular matrices + :type UList: list[numpy.ndarray] + """ + + try: + assert len(LList) == len(UList) + except AssertionError: + print("The lists of lower and upper traingular matrices must have the same length!") + raise ValueError + + texEqu = '\\begin{align*}' + for i in range(len(LList)): + texEqu += 'L &= ' + texMatrix(LList[i]) + '\\qquad & U &= ' + texMatrix(UList[i]) + if i < len(LList) - 1: + texEqu += ' \\\\ ' + texEqu += '\\end{align*}' + display(Latex(texEqu)) + + return + + +# %% Functions to enter something + +def EnterInt(n=None): + """Function to allow the user to enter a non-negative integer + + :param n: first integer, passed to the function. If null or negative or None, an integer is requested to the user. + Defaults to None + :type n: int or NoneType + :return: positive integer + :rtype: int + """ + + while type(n) is not int or (type(n) is int and n <= 0): + try: + n = int(n) + if n <= 0: + print("Le nombre ne peut pas être négatif o zero!") + print("Entrez à nouveau: ") + n = input() + except: + if n is not None: + print("Ce n'est pas un entier!") + print("Entrez à nouveau:") + n = input() + else: + print("Entrez un entier positif") + n = input() + return n + + +def EnterListReal(n): + """Function which allows the user to enter a list of `n` real numbers + + :param n: number of real numbers in the desired list + :type n: int + :return: list of `n` real numbers + :rtype: list[float] + """ + + if n < 0: + print(f"Impossible de générer une liste de {n} nombres réels") + elif n == 0: + return [] + else: + print(f"Entrez une liste de {n} nombres réels") + coeff = None + while type(coeff) is not list: + try: + coeff = input() + coeff = [float(eval(x)) for x in coeff.split(',')] + if len(coeff) != n: + print("Vous n'avez pas entré le bon nombre de réels!") + print("Entrez à nouveau : ") + coeff = input() + except: + print("Ce n'est pas le bon format!") + print("Entrez à nouveau") + coeff = input() + return coeff + + +def SolOfEq(sol, coeff, i): + """Method that verifies if `sol` is a solution to the linear equation `i`with coefficients `coeff` + + :param sol: candidate solution vector + :type sol: list + :param coeff: coefficients of the linear equation + :type coeff: list + :param i: index of the equation + :type i: int + :return: True if `sol` is a solution, False otherwise + :rtype: bool + """ + + try: + assert len(sol) == len(coeff)-1 + except AssertionError: + print(f"La suite entrée n'est pas une solution de l'équation {i}; Les dimensions ne correspondent pas") + return False + + A = np.array(coeff[:-1]) + isSol = abs(np.dot(A, sol) - coeff[-1]) < 1e-8 + if isSol: + print(f"La suite entrée est une solution de l'équation {i}") + else: + print(f"La suite entrée n'est pas une solution de l'équation {i}") + return isSol + + +def SolOfSyst(solution, A, b): + """Method that verifies if `solution` is a solution to the linear system with left-hand side matrix `A` and + right-hand side vector `b` + + :param solution: candidate solution vector + :type solution: list + :param A: left-hand side matrix of the linear system + :type A: list[list[float]] or numpy.ndarray + :param b: right-hand side vector of the linear system + :type b: list[float] or numpy.ndarray + :return: True if `sol` is a solution, False otherwise + :rtype: bool + """ + + try: + assert len(solution) == (len(A[0]) if type(A) is list else A.shape[1]) + except AssertionError: + print(f"La suite entrée n'est pas une solution du système; Les dimensions ne correspondent pas") + return False + + A = [A[i] + [b[i]] for i in range(0, len(A))] + A = np.array(A) + isSol = [SolOfEq(solution, A[i, :], i+1) for i in range(len(A))] + if all(isSol): + print("C'est une solution du système") + return True + else: + print("Ce n'est pas une solution du système") + return False + + +# PLOTS WITH PLOTLY # + +def scatterPlot(x1=None, x2=None, y=None, color=None): + """Method that realized an interactive scatter plot in 2D or 3D + + :param x1: x data. If None, no plot is realized + :type x1: list or np.ndarray or NoneType + :param x2: y data. If None, no pot is realized + :type x2: list or np.ndarray or NoneType + :param y: z data. If None, a 2D scatter plot is realized. Defaults to None + :type y: list or np.ndarray or NoneType + :param color: color of the points in the scatter plot. If None, defaults to cyan. + :type color: str or NoneType + :return: generated plot + :rtype: plotly.Figure + """ + + if x1 is None or y is None: + raise ValueError("x1 and y must be given as input to display the data in a scatter plot!") + + col = 'rgb(51, 214, 255)' if color is None else color + + data = [] + if x2 is None: + points = go.Scatter(x=x1, y=y, marker=dict(symbol='circle', size=8, color=col), + mode='markers', name='Data') + else: + points = go.Scatter3d(x=x1, y=x2, z=y, marker=dict(symbol='circle', size=5, color=col), + mode='markers', name='Data') + data.append(points) + + layout = go.Layout( + showlegend=True, + legend=dict(orientation="h"), + autosize=True, + width=600, + height=600, + scene=go.layout.Scene( + xaxis=dict( + gridcolor='rgb(255, 255, 255)', + zerolinecolor='rgb(255, 255, 255)', + showbackground=True, + backgroundcolor='rgb(230, 230, 230)' + ), + yaxis=dict( + gridcolor='rgb(255, 255, 255)', + zerolinecolor='rgb(255, 255, 255)', + showbackground=True, + backgroundcolor='rgb(230, 230, 230)' + ), + zaxis=None if x2 is None else dict( + gridcolor='rgb(255, 255, 255)', + zerolinecolor='rgb(255, 255, 255)', + showbackground=True, + backgroundcolor='rgb(230, 230, 230)' + ), + ) + ) + + fig = go.FigureWidget(data=data, layout=layout) + plotly.offline.iplot(fig) + + return fig + + +def drawLine(p, d): + """Method which allows to plot lines, points and arrows in the 2D-place or 3D-space, using plotly library + + :param p: point + :type p: list[list[float]] + :param d: direction vector. If made of all zeros, just the reference point is plotted; if different from 0 a line + passing through `p` and with direction `d` is plotted + :type d: list[list[float]] + :return: generated plot + :rtype: plotly.Figure + """ + + blue = 'rgb(51, 214, 255)' + colors = [blue] + colorscale = [[0.0, colors[0]], + [0.1, colors[0]], + [0.2, colors[0]], + [0.3, colors[0]], + [0.4, colors[0]], + [0.5, colors[0]], + [0.6, colors[0]], + [0.7, colors[0]], + [0.8, colors[0]], + [0.9, colors[0]], + [1.0, colors[0]]] + vec = 0.9 * np.array(d) + if len(p) == 2: + data = [] + t = np.linspace(-5, 5, 51) + s = np.linspace(0, 1, 10) + if all(dd == [0] for dd in d): + vector = go.Scatter(x=p[0] + s*0, y=p[1] + s*0, marker=dict(symbol=6, size=12, color=colors[0]), + name ='Point') + else: + trace = go.Scatter(x=p[0] + t * d[0], y=p[1] + t * d[1], name='Droite') + peak = go.Scatter(x=d[0], y=d[1], marker=dict(symbol=6, size=12, color=colors[0]), showlegend=False) + vector = go.Scatter(x=p[0] + s * d[0], y=p[1] + s * d[1], mode='lines', + line=dict(width=5, color=colors[0]), name='Vecteur directeur') + zero = go.Scatter(x=t*0, y=t*0, name='Origine', marker=dict(symbol=6, size=12, color=colors[0]), + showlegend=False) + + data.append(vector) + data.append(zero) + if not all(dd == [0] for dd in d): + data.append(trace) + data.append(peak) + + fig = go.FigureWidget(data=data) + plotly.offline.iplot(fig) + elif len(p) == 3: + data = [ + { + 'type': 'cone', + 'x': [1], 'y': vec[1], 'z': vec[2], + 'u': d[0], 'v': d[1], 'w': d[2], + "sizemode": "absolute", + 'colorscale': colorscale, + 'sizeref': 1, + "showscale": False, + 'hoverinfo': 'none' + } + ] + t = np.linspace(-5, 5, 51) + s = np.linspace(0, 1, 10) + zero = go.Scatter3d(x=t*0, y=t*0, z=t*0, name='Origine', marker=dict(size=3), showlegend=False) + if all(dd == [0] for dd in d): + vector = go.Scatter3d(x=p[0] + s*0, y=p[1] + s*0, z=p[2] + s*0, marker=dict(size=5), + name='Point') + else: + trace = go.Scatter3d(x=p[0] + t * d[0], y=p[1] + t * d[1], z=p[2] + t * d[2], mode='lines', name='Droite') + vector = go.Scatter3d(x=p[0] + s * d[0], y=p[1] + s * d[1], z=p[2] + s * d[2], mode='lines', + line=dict(width=5,color=colors[0], dash='solid'), name='Vecteur directeur', + hoverinfo='none') + data.append(zero) + data.append(vector) + if not all(dd == [0] for dd in d): + data.append(trace) + layout = { + 'scene': { + 'camera': { + 'eye': {'x': -0.76, 'y': 1.8, 'z': 0.92} + } + } + } + fig = go.FigureWidget(data=data, layout=layout) + plotly.offline.iplot(fig) + return fig + + +def Plot2DSys(xL, xR, p, A, b, with_sol=True, with_sol_lstsq=False): + """Function for the graphical visualization of a 2D system of equations, plotting the straight lines characterizing + the different equations appearing in the system + + :param xL: left limit of the plot in both coordinates + :type xL: int or float + :param xR: right limit of the plot in both coordinates + :type xR: int or float + :param p: number of points used to draw the straight lines + :type p: int + :param A: matrix of the linear system + :type A: list[list[float]] or numpy.ndarray + :param b: right-hand side vector of the linear system + :type b: list[float] or numpy.ndarray + :param with_sol: if True, also the solution is displayed. Defaults to True + :type with_sol: bool + :param with_sol_lstsq: if True, the Least-Squares solution is displayed, in case the system does not admit any + solution. Defaults to False. + :type with_sol_lstsq: bool + """ + + M = [A[i] + [b[i]] for i in range(len(A))] + A = np.array(A) + b = np.array(b) + M = np.array(M) + + t = np.linspace(xL, xR, p) + data = [] + for i in range(1, len(M)+1): + if abs(M[i-1, 1]) > abs(M[i-1, 0]): + trace = go.Scatter(x=t, y=(M[i-1, 2] - M[i-1, 0] * t) / M[i-1, 1], mode='lines', name='Droite %d' % i) + else: + trace = go.Scatter(x=(M[i-1, 2] - M[i-1, 1] * t) / M[i-1, 0], y=t, mode='lines', name='Droite %d' % i) + data.append(trace) + + has_solution = False + if with_sol: + A_sp = sp.Matrix(A) + b_sp = sp.Matrix(b) + x,y = sp.symbols('x, y') + sys = A_sp, b_sp + + x_sp = linsolve(sys, x, y) + + if isinstance(x_sp, sp.sets.EmptySet): + display(Latex(r"Le systéme n'admet pas des solutions!")) + elif isinstance(x_sp, sp.sets.FiniteSet): + has_solution = True + x_sp = x_sp.args[0] + used_symbols = x_sp.atoms(sp.Symbol) + + if len(used_symbols) == 0: + trace = go.Scatter(x=[float(x_sp[0])], y=[float(x_sp[1])], mode='markers', name='Solution') + elif len(used_symbols) == 1: + if x in used_symbols: + yy = sp.lambdify(x, x_sp[1])(t) + trace = go.Scatter(x=t, y=yy if isinstance(yy, Iterable) else yy*np.ones_like(t), name='Solution') + elif y in used_symbols: + xx = sp.lambdify(y, x_sp[0])(t) + trace = go.Scatter(x=xx if isinstance(xx, Iterable) else xx*np.ones_like(t), y=t, name='Solution') + else: + display(Latex(r"Chaque vecteur de $\mathbb{R}^2$ est une solution du systéme!")) + + data.append(trace) + + if with_sol_lstsq and not has_solution: + A_sp = sp.Matrix(A.T@A) + b_sp = sp.Matrix(A.T@b) + x, y = sp.symbols('x, y') + sys = A_sp, b_sp + + x_sp = linsolve(sys, x, y) + + if isinstance(x_sp, sp.sets.EmptySet): + display(Latex("Le systeme n'admet pas des solutions au sens du moidres carrées!")) + elif isinstance(x_sp, sp.sets.FiniteSet): + display(Latex("La solution du système peut être exprimée comme une solution au sens du moindres carrés.")) + x_sp = x_sp.args[0] + used_symbols = x_sp.atoms(sp.Symbol) + + if len(used_symbols) == 0: + trace = go.Scatter(x=[float(x_sp[0])], y=[float(x_sp[1])], mode='markers', name='Solution (MC)') + elif len(used_symbols) == 1: + if x in used_symbols: + yy = sp.lambdify(x, x_sp[1])(t) + trace = go.Scatter(x=t, y=yy if isinstance(yy, Iterable) else yy * np.ones_like(t), + name='Solution (MC)') + elif y in used_symbols: + xx = sp.lambdify(y, x_sp[0])(t) + trace = go.Scatter(x=xx if isinstance(xx, Iterable) else xx * np.ones_like(t), y=t, + name='Solution (MC)') + else: + display(Latex(r"Chaque vecteur de $\mathbb{R}^2$ est une solution du systéme!")) + + data.append(trace) + + layout = go.Layout(yaxis=dict(scaleanchor="x", scaleratio=1)) + + fig = go.Figure(data=data, layout=layout) + plotly.offline.iplot(fig) + + return + + +def Plot3DSys(xL, xR, p, A, b, with_sol=True, with_sol_lstsq=False): + """Function for the graphical visualization of a 3D system of equations, plotting the straight lines characterizing + the different equations appearing in the system + + :param xL: left limit of the plot in all coordinates + :type xL: int or float + :param xR: right limit of the plot in all coordinates + :type xR: int or float + :param p: number of points used to draw the straight lines + :type p: int + :param A: matrix of the linear system + :type A: list[list[float]] or numpy.ndarray + :param b: right-hand side vector of the linear system + :type b: list[float] or numpy.ndarray + :param with_sol: if True, also the solution is displayed. Defaults to True + :type with_sol: bool + :param with_sol_lstsq: if True, the Least-Squares solution is displayed, in case the system does not admit any + solution. Defaults to False. + :type with_sol_lstsq: bool + """ + + A = np.array(A) + b = np.array(b) + + gr = 'rgb(102,255,102)' + org = 'rgb(255,117,26)' + cyan = 'rgb(51, 214, 255)' + yellow = 'rgb(255, 255, 0)' + purple = 'rgb(255, 0, 255)' + blue = 'rgb(0, 0, 255)' + red = 'rgb(255, 0, 0)' + colors = cycle([cyan, gr, org, yellow, purple, blue]) + + s = np.linspace(xL, xR, p) + t = np.linspace(xL, xR, p) + tGrid, sGrid = np.meshgrid(s, t) + data = [] + for i in range(len(A)): + color = next(colors) + colorscale = [[0.0, color], + [0.1, color], + [0.2, color], + [0.3, color], + [0.4, color], + [0.5, color], + [0.6, color], + [0.7, color], + [0.8, color], + [0.9, color], + [1.0, color]] + j = i + 1 + + arg = np.argmax(np.abs(A[i,:])) + + # All the coefficient of equation are 0: b==0 -> every combinations are solution, b!=0 -> No solution + if A[i, arg] == 0: + if b[i] == 0: + print("No constraints on equation", j, "of the system.") + else: + print("No solution for equation", j, "of the system.") + # At least a coefficient is different from 0, plot the one with largest coeff in magnitude + else: + if arg == 2: # z en fonction de x,y + x = sGrid + y = tGrid + surface = go.Surface(x=x, y=y, z=(b[i] - A[i, 0] * x - A[i, 1] * y) / A[i, 2], + showscale=False, showlegend=True, colorscale=colorscale, opacity=1, + name='Plan %d' % j) + + elif arg == 1: # y en fonction de x,z + x = sGrid + z = tGrid + surface = go.Surface(x=x, y=(b[i]-A[i, 0]*x - A[i, 2]*z)/A[i, 1], z=z, + showscale=False, showlegend=True, colorscale=colorscale, opacity=1, + name='Plan %d' % j) + + elif arg == 0: # x en fonction de y,z + y = sGrid + z = tGrid + surface = go.Surface(x=(b[i] - A[i, 1] * y - A[i,2] * z)/A[i, 0], y=y, z=z, + showscale=False, showlegend=True, colorscale=colorscale, opacity=1, + name='Plan %d' % j) + + data.append(surface) + + layout = go.Layout( + showlegend=True, # not there WHY???? --> LEGEND NOT YET IMPLEMENTED FOR SURFACE OBJECTS!! + legend=dict(orientation="h"), + autosize=True, + width=800, + height=800, + scene=go.layout.Scene( + xaxis=dict( + gridcolor='rgb(255, 255, 255)', + zerolinecolor='rgb(255, 255, 255)', + showbackground=True, + backgroundcolor='rgb(230, 230,230)' + ), + yaxis=dict( + gridcolor='rgb(255, 255, 255)', + zerolinecolor='rgb(255, 255, 255)', + showbackground=True, + backgroundcolor='rgb(230, 230,230)' + ), + zaxis=dict( + gridcolor='rgb(255, 255, 255)', + zerolinecolor='rgb(255, 255, 255)', + showbackground=True, + backgroundcolor='rgb(230, 230,230)' + ) + ), + yaxis=dict(scaleanchor="x", scaleratio=1), + ) + + colorscale = [[0.0, red], + [0.1, red], + [0.2, red], + [0.3, red], + [0.4, red], + [0.5, red], + [0.6, red], + [0.7, red], + [0.8, red], + [0.9, red], + [1.0, red]] + + has_solution = False + if with_sol: + A_sp = sp.Matrix(A) + b_sp = sp.Matrix(b) + x, y, z = sp.symbols('x, y, z') + sys = A_sp, b_sp + + x_sp = linsolve(sys, x, y, z) + + if isinstance(x_sp, sp.sets.EmptySet): + display(Latex(r"Le systéme n'admet pas des solutions!")) + elif isinstance(x_sp, sp.sets.FiniteSet): + has_solution = True + x_sp = x_sp.args[0] + used_symbols = x_sp.atoms(sp.Symbol) + + if len(used_symbols) == 0: + trace = go.Scatter3d(x=[float(x_sp[0])], y=[float(x_sp[1])], z=[float(x_sp[2])], + mode='markers', marker=dict(color=red), name='Solution') + elif len(used_symbols) == 1: + if x in used_symbols: + yy = sp.lambdify([x], x_sp[1])(s) + zz = sp.lambdify([x], x_sp[2])(s) + trace = go.Scatter3d(x=s, + y=yy if isinstance(yy, Iterable) else yy * np.ones_like(s), + z=zz if isinstance(zz, Iterable) else zz * np.ones_like(s), + name='Solution', + marker=dict(color=red), + line=go.scatter3d.Line(color=red)) + elif y in used_symbols: + xx = sp.lambdify([y], x_sp[0])(s) + zz = sp.lambdify([y], x_sp[2])(s) + trace = go.Scatter3d(x=xx if isinstance(xx, Iterable) else xx * np.ones_like(s), + y=s, + z=zz if isinstance(zz, Iterable) else zz * np.ones_like(s), + name='Solution', + marker=dict(color=red), + line=go.scatter3d.Line(color=red)) + elif z in used_symbols: + xx = sp.lambdify([z], x_sp[0])(s) + yy = sp.lambdify([z], x_sp[1])(s) + trace = go.Scatter3d(x=xx if isinstance(xx, Iterable) else xx * np.ones_like(s), + y=yy if isinstance(yy, Iterable) else yy * np.ones_like(s), + z=s, + name='Solution', + marker=dict(color=red), + line=go.scatter3d.Line(color=red)) + elif len(used_symbols) == 2: + if x in used_symbols and y in used_symbols: + zz = sp.lambdify([x, y], x_sp[2])(sGrid, tGrid) + trace = go.Surface(x=sGrid, + y=tGrid, + z=zz if isinstance(zz, Iterable) else zz * np.ones_like(tGrid), + showscale=False, showlegend=True, colorscale=colorscale, opacity=1, + name='Solution') + elif x in used_symbols and z in used_symbols: + yy = sp.lambdify([x, z], x_sp[1])(sGrid, tGrid) + trace = go.Surface(x=sGrid, + y=yy if isinstance(yy, Iterable) else yy * np.ones_like(tGrid), + z=tGrid, + showscale=False, showlegend=True, colorscale=colorscale, opacity=1, + name='Solution') + elif y in used_symbols and z in used_symbols: + xx = sp.lambdify([y, z], x_sp[0])(sGrid, tGrid) + trace = go.Surface(x=xx if isinstance(xx, Iterable) else xx * np.ones_like(tGrid), + y=sGrid, + z=tGrid, + showscale=False, showlegend=True, colorscale=colorscale, opacity=1, + name='Solution') + else: + display(Latex(r"Chaque vecteur de $\mathbb{R}^3$ est une solution du systéme!")) + + data.append(trace) + + if with_sol_lstsq and not has_solution: + A_sp = sp.Matrix(A.T @ A) + b_sp = sp.Matrix(A.T @ b) + x, y, z = sp.symbols('x, y, z') + sys = A_sp, b_sp + + x_sp = linsolve(sys, x, y, z) + + if isinstance(x_sp, sp.sets.EmptySet): + display(Latex("Le systeme n'admet pas des solutions au sens du moidres carrées!")) + elif isinstance(x_sp, sp.sets.FiniteSet): + display(Latex("La solution du système peut être exprimée comme une solution au sens du moindres carrés.")) + x_sp = x_sp.args[0] + + used_symbols = x_sp.atoms(sp.Symbol) + + if len(used_symbols) == 0: + trace = go.Scatter3d(x=[float(x_sp[0])], y=[float(x_sp[1])], z=[float(x_sp[2])], + mode='markers', marker=dict(color=red), name='Solution (MC)') + elif len(used_symbols) == 1: + if x in used_symbols: + yy = sp.lambdify([x], x_sp[1])(s) + zz = sp.lambdify([x], x_sp[2])(s) + trace = go.Scatter3d(x=s, + y=yy if isinstance(yy, Iterable) else yy * np.ones_like(s), + z=zz if isinstance(zz, Iterable) else zz * np.ones_like(s), + name='Solution (MC)', + marker=dict(color=red), + line=go.scatter3d.Line(color=red)) + elif y in used_symbols: + xx = sp.lambdify([y], x_sp[0])(s) + zz = sp.lambdify([y], x_sp[2])(s) + trace = go.Scatter3d(x=xx if isinstance(xx, Iterable) else xx * np.ones_like(s), + y=s, + z=zz if isinstance(zz, Iterable) else zz * np.ones_like(s), + name='Solution (MC)', + marker=dict(color=red), + line=go.scatter3d.Line(color=red)) + elif z in used_symbols: + xx = sp.lambdify([z], x_sp[0])(s) + yy = sp.lambdify([z], x_sp[1])(s) + trace = go.Scatter3d(x=xx if isinstance(xx, Iterable) else xx * np.ones_like(s), + y=yy if isinstance(yy, Iterable) else yy * np.ones_like(s), + z=s, + name='Solution (MC)', + marker=dict(color=red), + line=go.scatter3d.Line(color=red)) + elif len(used_symbols) == 2: + if x in used_symbols and y in used_symbols: + zz = sp.lambdify([x, y], x_sp[2])(sGrid, tGrid) + trace = go.Surface(x=sGrid, + y=tGrid, + z=zz if isinstance(zz, Iterable) else zz * np.ones_like(tGrid), + showscale=False, showlegend=True, colorscale=colorscale, opacity=1, + name='Solution (MC)') + elif x in used_symbols and z in used_symbols: + yy = sp.lambdify([x, z], x_sp[1])(sGrid, tGrid) + trace = go.Surface(x=sGrid, + y=yy if isinstance(yy, Iterable) else yy * np.ones_like(tGrid), + z=tGrid, + showscale=False, showlegend=True, colorscale=colorscale, opacity=1, + name='Solution (MC)') + elif y in used_symbols and z in used_symbols: + xx = sp.lambdify([y, z], x_sp[0])(sGrid, tGrid) + trace = go.Surface(x=xx if isinstance(xx, Iterable) else xx * np.ones_like(tGrid), + y=sGrid, + z=tGrid, + showscale=False, showlegend=True, colorscale=colorscale, opacity=1, + name='Solution (MC)') + else: + display(Latex(r"Chaque vecteur de $\mathbb{R}^3$ est une solution du systéme " + r"au sens du moidres carrées!")) + + data.append(trace) + + fig = go.Figure(data=data, layout=layout) + plotly.offline.iplot(fig) + + return + + +def vector_plot_2D(vects, is_vect=True, orig=None, labels=None, show=True): + """Method to plot 2D vectors in plotly + + :param vects: vectors to plot + :type vects: list[list] or list[np.ndarray] + :param is_vect: + :type is_vect: bool + :param orig: location of the origin + :type orig: list + :param labels: labels of the vectors to plot. If None, the vectors are named as vector1, vector2 etc. + :type labels: list[str] or NoneType + :param show: if True, the plot is displayed. Defaults to True + :type show: bool + """ + + if orig is None: + orig = [0, 0] + + if is_vect: + if not hasattr(orig[0], "__iter__"): + coords = [[orig, np.sum([orig, v], axis=0)] for v in vects] + else: + coords = [[o, np.sum([o,v], axis=0)] for o,v in zip(orig, vects)] + else: + coords = vects + + data = [] + for i,c in enumerate(coords): + X1, Y1 = zip(c[0]) + X2, Y2 = zip(c[1]) + vector = go.Scatter(x=[X1[0], X2[0]], + y=[Y1[0], Y2[0]], + marker=dict(size=[0, 2], + color=['blue'], + line=dict(width=2, + color='DarkSlateGrey')), + name='Vector'+str(i+1) if labels is None else labels[i]) + data.append(vector) + + layout = go.Layout( + margin=dict(l=4, r=4, b=4, t=4), + width=600, + height=400, + yaxis=dict(scaleanchor="x", scaleratio=1), + scene=go.layout.Scene( + xaxis=dict( + gridcolor='rgb(255, 255, 255)', + zerolinecolor='rgb(255, 255, 255)', + showbackground=True, + backgroundcolor='rgb(230, 230,230)' + ), + yaxis=dict( + gridcolor='rgb(255, 255, 255)', + zerolinecolor='rgb(255, 255, 255)', + showbackground=True, + backgroundcolor='rgb(230, 230,230)' + ), + zaxis=dict( + gridcolor='rgb(255, 255, 255)', + zerolinecolor='rgb(255, 255, 255)', + showbackground=True, + backgroundcolor='rgb(230, 230,230)' + ) + ) + ) + fig = go.Figure(data=data, layout=layout) + + if show: + fig.show() + + return fig + + +def vector_plot_3D(vects, is_vect=True, orig=None, labels=None, show=True): + """Method to plot 3D vectors in plotly + + :param vects: vectors to plot + :type vects: list[list] or list[np.ndarray] + :param is_vect: + :type is_vect: bool + :param orig: location of the origin(s) + :type orig: list or list[list] + :param labels: labels of the vectors to plot. If None, the vectors are named as vector1, vector2 etc. + :type labels: list[str] or NoneType + :param show: if True, the plot is displayed. Defaults to True + :type show: bool + """ + + if orig is None: + orig = [0, 0, 0] + + if is_vect: + if not hasattr(orig[0], "__iter__"): + coords = [[orig, np.sum([orig, v], axis=0)] for v in vects] + else: + coords = [[o, np.sum([o,v], axis=0)] for o,v in zip(orig, vects)] + else: + coords = vects + + data = [] + for i,c in enumerate(coords): + X1, Y1, Z1 = zip(c[0]) + X2, Y2, Z2 = zip(c[1]) + vector = go.Scatter3d(x=[X1[0], X2[0]], + y=[Y1[0], Y2[0]], + z=[Z1[0], Z2[0]], + marker=dict(size=[0, 2], + color=['blue'], + line=dict(width=2, + color='DarkSlateGrey')), + name='Vector'+str(i+1) if labels is None else labels[i]) + data.append(vector) + + layout = go.Layout( + margin=dict(l=4, r=4, b=4, t=4), + yaxis=dict(scaleanchor="x", scaleratio=1), + scene=go.layout.Scene( + xaxis=dict( + gridcolor='rgb(255, 255, 255)', + zerolinecolor='rgb(255, 255, 255)', + showbackground=True, + backgroundcolor='rgb(230, 230,230)' + ), + yaxis=dict( + gridcolor='rgb(255, 255, 255)', + zerolinecolor='rgb(255, 255, 255)', + showbackground=True, + backgroundcolor='rgb(230, 230,230)' + ), + zaxis=dict( + gridcolor='rgb(255, 255, 255)', + zerolinecolor='rgb(255, 255, 255)', + showbackground=True, + backgroundcolor='rgb(230, 230,230)' + ) + ) + ) + fig = go.Figure(data=data, layout=layout) + + if show: + fig.show() + + return fig + + +def plot_line(B, data=None, layout=None, label=""): + """Function to plot a plane in the 2D-3D space + + :param B: list of 2D-3D vectors generating the line + :type B: list[list] + :param data: data of the figure. defaults to None + :type data: list or NoneType + :param layout: layout of the figure. Defaults to None + :type layout: plotly.layout or NoneType + :param label: name of the line in the legend. Defaults to "". + :type label: str + :return: figure + :rtype: plotly.figure + """ + + if data is None: + data = [] + + if layout is None: + layout = go.Layout( + margin=dict(l=4, r=4, b=4, t=4), + yaxis=dict(scaleanchor="x", scaleratio=1), + scene=go.layout.Scene( + xaxis=dict( + gridcolor='rgb(255, 255, 255)', + zerolinecolor='rgb(255, 255, 255)', + showbackground=True, + backgroundcolor='rgb(230, 230,230)' + ), + yaxis=dict( + gridcolor='rgb(255, 255, 255)', + zerolinecolor='rgb(255, 255, 255)', + showbackground=True, + backgroundcolor='rgb(230, 230,230)' + ), + zaxis=dict( + gridcolor='rgb(255, 255, 255)', + zerolinecolor='rgb(255, 255, 255)', + showbackground=True, + backgroundcolor='rgb(230, 230,230)' + ) + ) + ) + + try: + assert len(B) == 1 and \ + (all([len(B[i]) == 2 for i in range(len(B))]) or all([len(B[i]) == 3 for i in range(len(B))])) + except AssertionError: + raise TypeError("L'entrée doit être une liste d'un élément, " + "dont chacun est une liste de deux ou trois nombres.") + + x = np.linspace(-1.5 * abs(B[0][0]), + 1.5 * abs(B[0][0]), 2) + y = np.linspace(-1.5 * abs(B[0][1]), + 1.5 * abs(B[0][1]), 2) + if len(B[0]) == 3: + z = np.linspace(-1.5 * abs(B[0][2]), + 1.5 * abs(B[0][2]), 2) + + coeffs = [B[0][0], B[0][1]] + if len(B[0]) == 3: + coeffs.append(B[0][2]) + + if coeffs[0] != 0: + new_coeffs = [coeffs[1] / coeffs[0]] if len(coeffs) == 2 \ + else [coeffs[1] / coeffs[0], coeffs[2] / coeffs[0]] + ref_index = 0 + elif coeffs[1] != 0: + new_coeffs = [coeffs[0] / coeffs[1]] if len(coeffs) == 2 \ + else [coeffs[0] / coeffs[1], coeffs[2] / coeffs[1]] + ref_index = 1 + elif len(coeffs) == 3 and coeffs[2] != 0: + new_coeffs = [coeffs[0] / coeffs[2]] if len(coeffs) == 2 \ + else [coeffs[0] / coeffs[2], coeffs[1] / coeffs[2]] + ref_index = 2 + else: + return go.Figure(data=data, layout=layout) + + if len(B[0]) == 2: + line = go.Scatter(x=x if ref_index == 0 else np.zeros_like(x), + y=new_coeffs[0] * x if ref_index == 0 else y, + marker=dict(size=[0, 0], + color=['rgb(255,165,0)'], + line=dict(width=1, + color='rgb(255,165,0)')), + name=label) + elif len(B[0]) == 3: + line = go.Scatter3d(x=x if ref_index == 0 else np.zeros_like(x), + y=new_coeffs[0] * x if ref_index == 0 + else y if ref_index == 1 else np.zeros_like(x), + z=new_coeffs[1] * x if ref_index == 0 + else new_coeffs[1] * y if ref_index == 1 + else z, + marker=dict(size=[0, 0], + color=['rgb(255,165,0)'], + line=dict(width=1, + color='rgb(255,165,0)')), + name=label) + + data.append(line) + + return go.Figure(data=data, layout=layout) + + +def plot_plane(B, color='rgb(0,255,255)', data=None, layout=None, label=""): + """Function to plot a plane in the 3D space + + :param B: list of 3D vectors generating the plane + :type B: list[list] + :param data: data of the figure. defaults to None + :type data: list or NoneType + :param layout: layout of the figure. Defaults to None + :type layout: plotly.layout or NoneType + :param color: color of the plane to be drawn. Defaults to 'rgb(0,255,255)' (orange) + :type color: str + :param label: name of the plane in the legend + :type label: str + :return: figure + :rtype: plotly.figure + """ + + if data is None: + data = [] + + if layout is None: + layout = go.Layout( + margin=dict(l=4, r=4, b=4, t=4), + yaxis=dict(scaleanchor="x", scaleratio=1), + scene=go.layout.Scene( + xaxis=dict( + gridcolor='rgb(255, 255, 255)', + zerolinecolor='rgb(255, 255, 255)', + showbackground=True, + backgroundcolor='rgb(230, 230,230)' + ), + yaxis=dict( + gridcolor='rgb(255, 255, 255)', + zerolinecolor='rgb(255, 255, 255)', + showbackground=True, + backgroundcolor='rgb(230, 230,230)' + ), + zaxis=dict( + gridcolor='rgb(255, 255, 255)', + zerolinecolor='rgb(255, 255, 255)', + showbackground=True, + backgroundcolor='rgb(230, 230,230)' + ) + ) + ) + + colorscale = [[0.0, color], + [0.1, color], + [0.2, color], + [0.3, color], + [0.4, color], + [0.5, color], + [0.6, color], + [0.7, color], + [0.8, color], + [0.9, color], + [1.0, color]] + + try: + assert len(B) == 2 and all([len(B[i]) == 3 for i in range(len(B))]) + except AssertionError: + raise TypeError("L'entrée doit être une liste d'un élément, dont chacun est une liste de trois nombres.") + + coeffs = [(B[0][1] * B[1][2] - B[1][1] * B[0][2]), + (B[1][0] * B[0][2] - B[0][0] * B[1][2]), + (B[0][0] * B[1][1] - B[1][0] * B[0][1])] + length = np.linspace(-1.5 * max(abs(B[0][0]), abs(B[1][0])), + 1.5 * max(abs(B[0][0]), abs(B[1][0])), 100) + width = np.linspace(-1.5 * max(abs(B[0][1]), abs(B[1][1])), + 1.5 * max(abs(B[0][1]), abs(B[1][1])), 100) + height = np.linspace(-1.5 * max(abs(B[0][2]), abs(B[1][2])), + 1.5 * max(abs(B[0][2]), abs(B[1][2])), 100) + + if coeffs[2] != 0: + Grid1, Grid2 = np.meshgrid(length, width) + x = Grid1 + y = Grid2 + new_coeffs = [-coeffs[0] / coeffs[2], -coeffs[1] / coeffs[2]] + ref_index = 2 + elif coeffs[1] != 0: + Grid1, Grid2 = np.meshgrid(length, height) + x = Grid1 + z = Grid2 + new_coeffs = [-coeffs[0] / coeffs[1], -coeffs[2] / coeffs[1]] + ref_index = 1 + elif coeffs[0] != 0: + Grid1, Grid2 = np.meshgrid(width, height) + y = Grid1 + z = Grid2 + new_coeffs = [-coeffs[1] / coeffs[0], -coeffs[2] / coeffs[0]] + ref_index = 0 + else: + return go.Figure(data=data, layout=layout) + + surface = go.Surface(x=x if ref_index != 0 else new_coeffs[0] * y + new_coeffs[1] * z, + y=y if ref_index != 1 else new_coeffs[0] * x + new_coeffs[1] * z, + z=z if ref_index != 2 else new_coeffs[0] * x + new_coeffs[1] * y, + showscale=False, showlegend=True, opacity=0.25, colorscale=colorscale, name=label) + + data.append(surface) + + return go.Figure(data=data, layout=layout) + + +def isDiag(M): + """Method which checks if a matrix is diagonal + + :param M: input matrix + :type M: list[list[float]] or numpy.ndarray + :return: True if M is diagonal else False + :rtype: bool + """ + + if not type(M) is np.ndarray: + M = np.array(M) + + i, j = M.shape + try: + assert i == j + except AssertionError: + print("A non-squared matrix cannot be diagonal!") + return False + + test = M.reshape(-1)[:-1].reshape(i - 1, j + 1) + return ~np.any(test[:, 1:] >= 1e-10) + + +def isSym(M): + """Method which checks if a matrix is symmetric + + :param M: input matrix + :type M: list[list[float]] or numpy.ndarray + :return: True if M is symmetric else False + :rtype: bool + """ + + if not type(M) is np.ndarray: + M = np.array(M) + + i, j = M.shape + try: + assert i == j + except AssertionError: + print("A non-squared matrix cannot be symmetric!") + return False + + return ~np.any(M - np.transpose(M) >= 1e-10) + + +# ECHELONNAGE # + +def echZero(indice, M): + """Method which sets to zero the entries of matrix M that correspond to a True value in the boolean vector indice + + :param indice: vector of booleans; if an element is True, it means that the element with the corresponding index in + matrix M must be set to 0 + :type indice: list[bool] + :param M: matrix to be processed + :type: numpy.ndarray + :return: processed matrix M, where the given entries have been properly set to 0 + :rtype: numpy.ndarray + """ + + Mat = M[np.logical_not(indice), :].ravel() + Mat = np.concatenate([Mat, M[indice, :].ravel()]) + Mat = Mat.reshape(len(M), len(M[0, :])) + return Mat + + +def Eij(M, i, j, get_E_inv=False): + """Method to swap line `i` and line `j` of matrix `M` + + :param M: matrix to be processed + :type M: numpy.ndarray + :param i: first line index + :type i: int + :param j: second line index + :type j: int + :param get_E_inv: if True, the inverse matrix of the applied elementary operation is returned. Defaults to False + :type get_E_inv: bool + :return: processed matrix, with line `i` and `j` having been swapped + :rtype: numpy.ndarray + """ + + M = np.array(M) + M[[i, j], :] = M[[j, i], :] + + if get_E_inv: + L = np.eye(M.shape[0], M.shape[0]).astype(float) + L[[i, j]] = L[[j, i]] + + if get_E_inv: + return M, L + else: + return M + + +def Ealpha(M, i, alpha, get_E_inv=False): + """Method to multiply line `i` of matrix `M` by the scalar coefficient `alpha` + + :param M: matrix to be processed + :type M: numpy.ndarray + :param i: reference line index + :type i: int + :param alpha: scalar coefficient + :type alpha: float + :param get_E_inv: if True, the inverse matrix of the applied elementary operation is returned. Defaults to False + :type get_E_inv: bool + :return: processed matrix, with line `i` multiplied by the scalar `alpha` + :rtype: numpy.ndarray + """ + + M = np.array(M) + M[i, :] = alpha * M[i, :] + + if get_E_inv: + L = np.eye(M.shape[0], M.shape[0]).astype(float) + L[i ,i] = 1 / alpha + + if get_E_inv: + return M, L + else: + return M + + +def Eijalpha(M, i, j, alpha, get_E_inv=False): + """Method to add to line `i` of matrix `M` line `j` of the same matrix, multiplied by the scalar coefficient `alpha` + + :param M: matrix to be processed + :type M: numpy.ndarray + :param i: line to be modified + :type i: int + :param j: line whose multiple has tobe added to line `i` + :type j: int + :param alpha: scalar coefficient + :type alpha: float + :param get_E_inv: if True, the inverse matrix of the applied elementary operation is returned. Defaults to False + :type get_E_inv: bool + :return: processed matrix, with line `i` being summed up with line `j` multiplied by `alpha` + :rtype: numpy.ndarray + """ + + M = np.array(M) + M[i, :] = M[i, :] + alpha * M[j, :] + + if get_E_inv: + L = np.eye(M.shape[0], M.shape[0]).astype(float) + L[i, j] = -alpha + + if get_E_inv: + return M, L + else: + return M + + +def echelonMat(ech, *args): + """Method to perform Gauss elimination on either the matrix of the coefficients (if `len(args)==1`) or on the + augmented matrix (if `len(args)==2`); the elimination can be either in standard form (if `ech=='E` or in reduced + form (if `ech=='ER'`). + + :param ech: + :type ech: + :param args: + :type args: + :return: + :rtype: + """ + + if len(args) == 2: # matrice augmentée + A = np.array(args[0]).astype(float) + m = A.shape[0] + n = A.shape[1] + b = np.array(args[1]) + + b = np.reshape(b, (m, 1)) + A = np.concatenate((A, b), axis=1) + + else: # matrice coeff + A = np.array(args[0]).astype(float) + m = A.shape[0] + n = A.shape[1] + + if ech in {'E', 'ER'}: # Echelonnée + + Mat = np.array(A) + Mat = Mat.astype(float) # in case the array in int instead of float. + numPivot = 0 + for i in range(len(Mat)): + j = i + goOn = True + if all(abs(Mat[j:, i]) < 1e-15) and j != len(Mat[0, :]) - 1: # if column (or rest of) is 0, take next + goOn = False + if j == len(Mat[0, :]) - 1: + Mat[i+1:len(Mat), :] = 0 + break + if goOn: + if abs(Mat[i, j]) < 1e-15: + Mat[i, j] = 0 + zero = abs(Mat[i:, j]) < 1e-15 + M = echZero(zero, Mat[i:, :]) + Mat[i:, :] = M + Mat = Ealpha(Mat, i, 1 / Mat[i, j]) # usually Mat[i,j]!=0 + for k in range(i + 1, len(A)): + Mat = Eijalpha(Mat, k, i, -Mat[k, j]) + # Mat[k,:]=[0 if abs(Mat[k,l])<1e-15 else Mat[k,l] for l in range(len(MatCoeff[0,:]))] + numPivot += 1 + Mat[abs(Mat) < 1e-15] = 0 + + print("La forme échelonnée de la matrice est") + if len(args) == 2: + printEquMatrices([A[:, :n], Mat[:, :n]], [A[:, n:], Mat[:, n:]]) + else: + printEquMatrices([A, Mat]) + + if ech == 'ER': # Echelonnée réduite + + Mat = np.array(Mat) + i = len(Mat) - 1 + while i >= 1: + j = i # we can start at pos ij at least the pivot is there + goOn = True + if all(abs(Mat[i, :len(Mat[0]) - 1]) < 1e-15) and i != 0: # if row (or rest of) is zero, take next + goOn = False + if goOn: + # we have a row with one non-nul element + if abs(Mat[i, j]) < 1e-15: # if element Aij=0 take next one --> find pivot + j += 1 + # Aij!=0 and Aij==1 if echelonMat worked + for k in range(i): # put zeros above pivot (which is 1 now) + Mat = Eijalpha(Mat, k, i, -Mat[k, j]) + i -= 1 + + print("La forme échelonnée réduite de la matrice est") + if len(args) == 2: + printEquMatrices([A[:, :n], Mat[:, :n]], [A[:, n:], Mat[:, n:]]) + else: + printEquMatrices([A, Mat]) + + if (Mat[:min(m,n), :min(m,n)] == np.eye(min(m,n))).all(): + print("La matrice peut être réduite à la matrice d'identité") + else: + print("La matrice ne peut pas être réduite à la matrice d'identité") + + if ech != 'ER' and ech != 'E': + print(f"Méthode d'échelonnage non reconnue {ech}. Méthodes disponibles: 'E' (pour la forme échelonnée standard)" + f", 'ER' (pour la forme échelonnée réduite))") + + return np.asmatrix(Mat) + + +def randomA(): + """Method which generates a random matrix with rows and columns within 1 and 10 and integer entries between -100 + and 100 + + :return: generated random matrix + :rtype: numpy.ndarray + """ + n = random.randint(1, 10) + m = random.randint(1, 10) + A = [[random.randint(-100, 100) for i in range(n)] for j in range(m)] + printA(A) + return np.array(A) + + +def dimensionA(A): + """Method which allows the user to enter the matrix dimensions and verifies whether they are correct or not + + :param A: reference matrix + :type A: numpy.ndarray + """ + m = widgets.IntText( + value=1, + step=1, + description='m:', + disabled=False + ) + n = widgets.IntText( + value=1, + step=1, + description='n:', + disabled=False + ) + + display(m) + display(n) + + def f(): + if m.value == A.shape[0] and n.value == A.shape[1]: + print('Correct!') + else: + print('Incorrect, entrez de nouvelles valeurs') + + interact_manual(f) + return + + +def manualEch(*args): + """Method which allows the user to perform the Gauss elimination method on the given input matrix, eventually + extended by the right-hand side vector. + + :param args: + :type args: + :return: + :rtype: + """ + + if len(args) == 2: # matrice augmentée + A = np.array(args[0]).astype(float) + m = A.shape[0] + b = args[1] + if type(b[0]) is list: + b = np.array(b).astype(float) + A = np.concatenate((A, b), axis=1) + else: + b = [b[i] for i in range(m)] + A = [A[i] + [b[i]] for i in range(m)] + else: + A = np.array(args[0]).astype(float) + m = A.shape[0] + + j = widgets.BoundedIntText( + value=1, + min=1, + max=m, + step=1, + description='Ligne j:', + disabled=False + ) + i = widgets.BoundedIntText( + value=1, + min=1, + max=m, + step=1, + description='Ligne i:', + disabled=False + ) + + r = widgets.RadioButtons( + options=['Tij', 'Di(alpha)', 'Lij(alpha)', 'Revert'], + description='Opération:', + disabled=False + ) + + alpha = widgets.Text( + value='1', + description='Coeff. alpha:', + disabled=False + ) + print("Régler les paramètres et évaluer la cellule suivante") + print("Répéter cela jusqu'à obtenir une forme échelonnée réduite") + display(r) + display(i) + display(j) + display(alpha) + return i, j, r, alpha + + +def echelonnage(i, j, r, alpha, A, m=None, *args): + """Method which performs the Gauss elimination method step described by `r.value` with parameters `ì`, `j` and + `alpha` on matrix `A` + + :param i: first reference line + :type i: ipywidgets.Text + :param j: second reference line + :type j: ipywidgets.Text + :param r: RadioButton describing the elementary matricial operation to be performed + :type r: ipywidgets.radioButton + :param alpha: scalar coefficient + :type alpha: ipywidgets.Text + :param A: starting matrix + :type A: numpy.ndarray + :param m: starting augmented matrix. If None, it equals A. Defaults to None. + :type m: numpy.ndarray or NoneType + :param args: either the list of matrices or both the list of matrices and rhs having bee built during the + application of the methos + :type args: list[numpy.ndarray] or tuple(list[numpy.ndarray], list[numpy.ndarray]) + :return: processed matrix + :rtype: numpy.ndarray + """ + + if m is None: + m = A.copy() + m = np.array(m).astype(float) + if alpha.value == 0 and r.value in {'Di(alpha)', 'Lij(alpha)'}: + print('Le coefficient alpha doit être non-nul!') + + if r.value == 'Tij': + m = Eij(m, i.value-1, j.value-1) + if r.value == 'Di(alpha)': + m = Ealpha(m, i.value-1, eval(alpha.value)) + if r.value == 'Lij(alpha)': + m = Eijalpha(m, i.value-1, j.value-1, eval(alpha.value)) + + if len(args) == 2: + A = np.asmatrix(A) + MatriceList = args[0] + RhSList = args[1] + if r.value != 'Revert': + MatriceList.append(m[:, :A.shape[1]]) + RhSList.append(m[:, A.shape[1]:]) + else: + if len(MatriceList) > 1 and len(RhSList) > 1: + MatriceList.pop() + RhSList.pop() + mat = MatriceList[-1] + rhs = RhSList[-1] + m = np.concatenate((mat,rhs), axis=1) + else: + print("Impossible de revenir sur l'opération!") + printEquMatrices(MatriceList, RhSList) + elif len(args) == 1: + MatriceList = args[0] + if r.value != 'Revert': + MatriceList.append(m) + else: + if len(MatriceList) > 1: + MatriceList.pop() + m = MatriceList[-1] + else: + print("Impossible de revenir sur l'opération!") + printEquMatrices(MatriceList) + else: + print("La liste des matrices ou des matrices et des vecteurs connus doit être donnée en entrée de la fonction!") + raise ValueError + return m + + +def LU_interactive(i, j, r, alpha, *args): + """Method which performs the Gauss elimination method step described by `r.value` with parameters `ì`, `j` and + `alpha` on matrix `A` + + :param i: first reference line + :type i: ipywidgets.Text + :param j: second reference line + :type j: ipywidgets.Text + :param r: RadioButton describing the elementary matricial operation to be performed + :type r: ipywidgets.radioButton + :param alpha: scalar coefficient + :type alpha: ipywidgets.Text + :param A: starting matrix + :type A: numpy.ndarray + :param m: starting augmented matrix. If None, it equals A. Defaults to None. + :type m: numpy.ndarray or NoneType + :param args: either the list of matrices or both the list of matrices and rhs having bee built during the + application of the method + :type args: list[numpy.ndarray] or tuple(list[numpy.ndarray], list[numpy.ndarray]) + :return: processed matrix + :rtype: numpy.ndarray + """ + + if len(args) == 2: + U = np.array(args[1][-1]).astype(float) + else: + print("La liste des matrices diagonales inférieures et supérieures déjà calculées doit être donnée en entrée de" + " la fonction") + raise ValueError + + if alpha.value == 0 and r.value in {'Di(alpha)', 'Lij(alpha)'}: + print('Le coefficient alpha doit être non-nul!') + + is_valid_operation = True + if r.value == 'Tij': + print("Échanger deux lignes n'est pas une option si on veut une décomposition LU et non pas PLU (ou P est une matrice de permutation)") + is_valid_operation = False + if r.value == 'Di(alpha)': + U, L = Ealpha(U, i.value-1, eval(alpha.value), get_E_inv=True) + if r.value == 'Lij(alpha)': + U, L = Eijalpha(U, i.value-1, j.value-1, eval(alpha.value), get_E_inv=True) + + if is_valid_operation: + LList = args[0] + UList = args[1] + if r.value != 'Revert': + UList.append(U) + LList.append(np.dot(LList[-1], L)) + else: + if len(UList) > 1 and len(LList) > 1: + UList.pop() + U = UList[-1] + LList.pop() + L = LList[-1] + else: + print("Impossible de revenir sur l'opération!") + printLUMatrices(LList, UList) + else: + L = args[0][-1] + U = args[1][-1] + return L, U + + +def LU_no_pivoting(A, ptol=1e-5): + """Method that computes the LU decomposition of a matrix, without using pivoting. If the matrix cannot be + decomposed, the method raises a ValueError. + + :param A: matrix to be decomposed + :type A: list[list] or numpy.ndarray + :param ptol: tolerance on the pivot values; if a pivot with value smaller (in absolute value) than ptol is found, + a ValueError is raised. Defaults to 1e-5 + :type ptol: float + :return: lower triangular matrix L and upper triangular matrix U such that A = LU, if they exist + :rtype: tuple(numpy.ndarray, numpy.ndarray) or NoneType + """ + + A = np.array(A).astype(float) + m, n = A.shape + + n_ops = 0 + n_steps = 0 + + try: + assert m <= n + except AssertionError: + raise ValueError("La décomposition LU n'est pas implémentée pour les matrices rectangulaires " + "ayant plus de lignes que de colonnes") + for i in range(m): + if (A[i+1:, :] == 0).all(): + break + pivot = A[i, i] + if abs(pivot) <= ptol: + print("Pivot avec la valeur 0 rencontré. Cette matrice n'admet pas de décomposition LU (sans permutation)") + return None, None + for k in range(i+1, m): + lam = A[k, i] / pivot + n_ops += 1 + if lam: + A[k, i+1:n] = A[k, i+1:n] - lam * A[i, i+1:n] + n_ops += 2*(n-i-1) + A[k, i] = lam + n_steps += 1 + + L = np.eye(m) + np.tril(A, -1)[:m, :m] + U = np.triu(A) + + n_ops += m**2 + + print(f"Nombre d'opérations élémentaires (I, II, III): {n_steps}") + print(f"Coût de la décomposition LU (nombre total d'additions, soustractions, " + f"multiplications et divisions): {n_ops}") + + return L, U + + +def manualOp(*args): + """Method which allows the user to perform elementary operations on the given input matrix, eventually extended by + the right-hand side vector. + + :param args: + :type args: + :return: + :rtype: + """ + + if len(args) == 2: # matrice augmentée + A = np.array(args[0]).astype(float) + M = A.shape[0] + b = args[1] + if type(b[0]) is list: + b = np.array(b).astype(float) + A = np.concatenate((A, b), axis=1) + else: + b = [b[i] for i in range(M)] + A = [A[i] + [b[i]] for i in range(M)] + else: + A = np.array(args[0]).astype(float) + M = A.shape[0] + A = np.array(A) # just in case it's not + + i = widgets.BoundedIntText( + value=1, + min=1, + max=M, + step=1, + description='Ligne i:', + disabled=False + ) + + j = widgets.BoundedIntText( + value=1, + min=1, + max=M, + step=1, + description='Ligne j:', + disabled=False + ) + + r = widgets.RadioButtons( + options=['Tij', 'Di(alpha)', 'Lij(alpha)'], + description='Opération:', + disabled=False + ) + + alpha = widgets.Text( + value='1', + description='Coeff. alpha:', + disabled=False + ) + + print("Régler les paramètres et cliquer sur RUN INTERACT pour effectuer votre opération") + + def f(r, i, j, alpha): + m = A + MatriceList = [A[:, :len(A[0])-1]] + RhSList = [A[:, len(A[0])-1:]] + if alpha == 0 and r != 'Tij': + print('Le coefficient alpha doit être non-nul!') + if r == 'Tij': + m = Eij(m, i-1, j-1) + if r == 'Di(alpha)': + m = Ealpha(m, i-1, eval(alpha)) + if r == 'Lij(alpha)': + m = Eijalpha(m, i-1, j-1, eval(alpha)) + MatriceList.append(m[:, :len(A[0])-1]) + RhSList.append(m[:, len(A[0])-1:]) + printEquMatricesAug(MatriceList, RhSList) + return + + interact_manual(f, r=r, i=i, j=j, alpha=alpha) + return + + +def __check_vector_2D(vec): + """Method that, given a vector, checks if it is a valid 2D vector and returns it as a numpy column array + + :param vec: vector to be analyzed + :type vec: numpy.ndarray or list or lits[list] + :return: input vector, as a column numpy array, provided that it is a valid 2D array. Else it raises error + :rtype: numpy.ndarray or NoneType + """ + + if type(vec) is not np.ndarray: + vec = np.array(vec) + + try: + assert (len(vec.shape) == 1 and vec.shape[0] == 2) or \ + (len(vec.shape) == 2 and vec.shape[0] + vec.shape[1] == 3) + except AssertionError: + print("Erreur: le vecteur entré doit être un vecteur 2D, ligne ou colonne") + else: + if len(vec.shape) == 2: + vec = np.squeeze(vec) + + return vec + + +def __check_vector_3D(vec): + """Method that, given a vector, checks if it is a valid 3D vector and returns it as a numpy column array + + :param vec: vector to be analyzed + :type vec: numpy.ndarray or list or lits[list] + :return: input vector, as a column numpy array, provided that it is a valid 3D array. Else it raises error + :rtype: numpy.ndarray or NoneType + """ + + if type(vec) is not np.ndarray: + vec = np.array(vec) + + try: + assert (len(vec.shape) == 1 and vec.shape[0] == 3) or \ + (len(vec.shape) == 2 and (vec.shape[0] == 1 or vec.shape[1] == 1) and + (vec.shape[0] == 3 or vec.shape[1] == 3)) + except AssertionError: + print("Erreur: le vecteur entré doit être un vecteur 3D, ligne ou colonne") + else: + if len(vec.shape) == 2: + vec = np.squeeze(vec) + + return vec + + +def get_vector_info_2D(vec, show=False, return_results=False): + """Method that, given a 2D vector, computes and displays its norm and its angle with respect to the x-axis. + Basically it gives the representation of the vector in polar coordinates. If 'show' is passed as True, then it also + displays the vector in the plan + + :param vec: vector to be analyzed + :type vec: numpy.ndarray or list or list[list] + :param show: if True, a plot of the vector as an arrow in the 2D plane is performed. Defaults to False + :type show: bool + :param return_results: if True, the results are also returned and not only displayed. Defaults to False + :type return_results: bool + :return: norm and angle of the vector, if return_results is True, else None. + :rtype: tuple(float, float) or NoneType + """ + + vec = __check_vector_2D(vec) + + norm = np.linalg.norm(vec) + if norm > 0: + angle = (np.arccos(vec[0] / norm) / np.pi * 180.0) * (np.sign(vec[1]) if vec[1] != 0 else 1) + else: + if show: + print("Le vecteur donné est nul: impossible de définir son angle") + angle = np.nan + + if show: + display(Latex("$||v|| = % 10.4f; \\qquad \\theta = % 10.4f °$" % (norm, angle))) + + if show: + fig = plt.figure(figsize=(7, 7)) + ax = fig.gca() + origin = [0], [0] + limit = 1.25 * np.max(np.abs(vec)) + theta = np.linspace(0, angle / 180.0 * np.pi, 100) + r = 0.15 * np.max(np.abs(vec)) + x1 = r * np.cos(theta) + x2 = r * np.sin(theta) + + ax.quiver(*origin, vec[0], vec[1], color='r', angles='xy', scale_units='xy', scale=1) + ax.plot(*origin, marker='o', color='b') + ax.plot(x1, x2, '-', color='k', linewidth=1) + ax.text(x1[50] * 1.5, x2[50] * 1.5, r'$\Theta$') + ax.set_xlim(-limit, limit) + ax.set_ylim(-limit, limit) + ax.grid() + + # Move left y-axis and bottom x-axis to centre, passing through (0,0) + ax.spines['left'].set_position('center') + ax.spines['bottom'].set_position('center') + + # Eliminate upper and right axes + ax.spines['right'].set_color('none') + ax.spines['top'].set_color('none') + + # Show ticks in the left and lower axes only + ax.xaxis.set_ticks_position('bottom') + ax.yaxis.set_ticks_position('left') + + plt.show() + + if return_results: + return norm, angle + else: + return + + +def get_couple_vectors_info_2D(vec1, vec2, show=False, return_results=False): + """Method that, given a couple of 2D vectors, computes and displays their norms, their inner product and the angle + between them. If 'show' is passed as True, then it also displays the vectors in the plan. + + :param vec1: vector 1 to be analyzed + :type vec1: numpy.ndarray or list or list[list] + :param vec2: vector 1 to be analyzed + :type vec2: numpy.ndarray or list or list[list] + :param show: if True, a plot of the vectors as a arrows in the 2D plane is performed. Defaults to False + :type show: bool + :param return_results: if True, the results are also returned and not only displayed. Defaults to False + :type return_results: bool + :return: norms of the two vectors, inner product and angle, if return_results is True else None + :rtype: tuple(float,float,float,float) or NoneType + """ + + vec1 = __check_vector_2D(vec1) + vec2 = __check_vector_2D(vec2) + + norm1, angle1 = get_vector_info_2D(vec1, show=False, return_results=True) + if show: + display(Latex("$||v_1|| = % 10.4f; \\qquad \\theta_1 = % 10.4f °$" % (norm1, angle1))) + + norm2, angle2 = get_vector_info_2D(vec2, show=False, return_results=True) + if show: + display(Latex("$||v_2|| = % 10.4f; \\qquad \\theta_2 = % 10.4f °$" % (norm2, angle2))) + + if norm1 > 0 and norm2 > 0: + inner_product = np.dot(vec1, vec2) + diff_angle = np.arccos(inner_product / (norm1 * norm2)) * 180.0 / np.pi + else: + inner_product = 0 + diff_angle = np.nan + if show: + display(Latex("$v_1 \\cdot v_2 = % 10.4f; \\qquad \\Delta\\theta = % 10.4f °$" %(inner_product, diff_angle))) + + if show: + fig = plt.figure(figsize=(7, 7)) + ax = fig.gca() + origin = [0], [0] + + limit = 1.25 * np.max(np.hstack((np.abs(vec1), np.abs(vec2)))) + if not np.isnan(diff_angle): + if np.abs(angle2 - angle1) <= np.pi: + theta = np.linspace(angle1 / 180.0 * np.pi, angle2 / 180.0 * np.pi, 100) + else: + if angle1 < 0: + theta = np.linspace((angle1 + 360.0) / 180.0 * np.pi, angle2 / 180.0 * np.pi, 100) + elif angle2 < 0: + theta = np.linspace(angle1 / 180.0 * np.pi, (angle2 + 360.0) / 180.0 * np.pi, 100) + else: + theta = np.linspace(angle1 / 180.0 * np.pi, angle2 / 180.0 * np.pi, 100) + r = 0.15 * np.max(np.hstack((np.abs(vec1), np.abs(vec2)))) + x1 = r * np.cos(theta) + x2 = r * np.sin(theta) + + ax.plot(*origin, marker='o', color='b') + ax.quiver(*origin, vec1[0], vec1[1], color='r', angles='xy', scale_units='xy', scale=1, label="Vector 1") + ax.quiver(*origin, vec2[0], vec2[1], color='g', angles='xy', scale_units='xy', scale=1, label="Vector 2") + + if not np.isnan(diff_angle): + ax.plot(x1, x2, '-', color='k', linewidth=1) + ax.text(x1[50] * 1.75, x2[50] * 1.75, r'$\Delta\theta$') + + ax.set_xlim(-limit, limit) + ax.set_ylim(-limit, limit) + ax.legend(loc="best") + ax.grid() + + # Move left y-axis and bottom x-axis to centre, passing through (0,0) + ax.spines['left'].set_position('center') + ax.spines['bottom'].set_position('center') + + # Eliminate upper and right axes + ax.spines['right'].set_color('none') + ax.spines['top'].set_color('none') + + # Show ticks in the left and lower axes only + ax.xaxis.set_ticks_position('bottom') + ax.yaxis.set_ticks_position('left') + + plt.show() + + if return_results: + return norm1, norm2, inner_product, diff_angle + else: + return + + +def get_vector_info_3D(vec, show=False): + """Method that, given a 3D vector, computes, displays its norm and its angle with respect to the x-axis. + Basically it gives the representation of the vector in polar coordinates. If 'show' is passed as True, then it also + displays the vector in the plan + + :param vec: vector to be analyzed + :type vec: numpy.ndarray or list or list[list] + :param show: if True, a plot of the vector as an arrow in the 2D lan is performed. Defaults to False + :type show: bool + """ + + vec = __check_vector_3D(vec) + + norm = np.linalg.norm(vec) + if norm > 0: + polar_angle = np.arccos(vec[2] / norm) / np.pi * 180.0 + azimuthal_angle = np.arctan2(vec[1], vec[0]) /np.pi * 180.0 + else: + print("Le vecteur donné est nul: impossible de définir son angle") + polar_angle = np.nan + azimuthal_angle = np.nan + + display(Latex("$||v|| = % 10.4f; \\qquad \\theta = % 10.4f °$ (angle polaire); " + "$\\qquad \\phi = % 10.4f °$ (angle azimutal)" % + (norm, polar_angle, azimuthal_angle))) + + if show: + from mpl_toolkits.mplot3d import Axes3D + + fig = plt.figure() + ax = fig.gca(projection='3d') + + origin = [0], [0], [0] + limit = 1.25 * np.max(np.abs(vec)) + + ax.quiver(*origin, vec[0], vec[1], vec[2], color='r', normalize=False, linewidth=2) + ax.plot(*origin, marker='o', color='b') + ax.set_xlim(-limit, limit) + ax.set_ylim(-limit, limit) + ax.set_zlim(-limit, limit) + ax.grid() + + ax.quiver(0, 0, 0,limit, 0, 0, + color='k', linewidth=1) + ax.quiver(0, 0, 0, 0,limit, 0, + color='k', linewidth=1) + ax.quiver(0, 0, 0, 0, 0,limit, + color='k', linewidth=1) + ax.quiver(0, 0, 0, -limit, 0, 0, + arrow_length_ratio=0, color='k', linewidth=1) + ax.quiver(0, 0, 0, 0, -limit, 0, + arrow_length_ratio=0, color='k', linewidth=1) + ax.quiver(0, 0, 0, 0, 0, -limit, + arrow_length_ratio=0, color='k', linewidth=1) + + plt.show() + + return + + +def get_couple_matrices_info(A, B, show=False): + """Function that, given two matrices, returns their norms, the norm of their sum, the inner product and the angle + between those, all with respect to the trace inner product. Angle is expressed in radians. + + :param A: first matrix + :type A: list[list[int]] or numpy.ndarray + :param B: second matrix + :type B: list[list[int]] or numpy.ndarray + :param show: if True, results are displayed. Defaults to False + :type show: bool + :return: dictionary containing the norms of the matrices, the norm of their sum, the inner product and the angle + :rtype: dict{str:float} + """ + + if type(A) is not np.ndarray: + A = np.array(A) + + if type(B) is not np.ndarray: + B = np.array(B) + + result = dict() + result['norm_1'] = np.sqrt(np.trace(np.dot(A.T, A))) + result['norm_2'] = np.sqrt(np.trace(np.dot(B.T, B))) + result['norm_sum'] = np.sqrt(np.trace(np.dot((A+B).T, A+B))) + result['inner_product'] = np.trace(np.dot(A.T, B)) + result['angle'] = np.arccos(result['inner_product'] / (result['norm_1'] * result['norm_2'])) \ + if result['norm_1'] > 0 and result['norm_2'] > 0 else np.nan + + if show: + display(Latex(r"$||A||$ = % 10.4f; $||B||$ = % 10.4f" + %(result['norm_1'], result['norm_2']))) + display(Latex(r"$||A+B||$ = % 10.4f; $\langle A,B \rangle$ = % 10.4f" + %(result['norm_sum'], result['inner_product']))) + display(Latex(r"$\Delta\theta$ = % 10.4f °; $||A+B||^2$ = % 10.4f; $||A||^2 + ||B||^2$ = % 10.4f" + % (result['angle'] * 180.0 / np.pi, result['norm_sum']**2, result['norm_1']**2+result['norm_2']**2))) + + return result + + +def get_couple_functions_info(f, g, int_limits, weight_function=None, show=False): + """Function that, given two continuous functions, returns their norms, the norm of their sum, the inner product and + the angle between those, all with respect to the weighted L2 inner product. Angle is expressed in radians. + + :param f: first function + :type f: function + :param g: second function + :type g: function + :param int_limits: integration limits + :type int_limits: list[float, float] + :param weight_function: weighing function of the inner product. If None, it defaults to 1.0 + :type weight_function: function or NoneType + :param show: if True, results are displayed. Defaults to False + :type show: bool + :return: dictionary containing the norms of the functions, the norm of their sum, the inner product and the angle + :rtype: dict{str:float} + """ + + if weight_function is None: + weight_function = lambda x: 1.0 + 0*x + + try: + x = np.linspace(int_limits[0], int_limits[1], 1000) + w = weight_function(x) + assert (w >= 0).all() and (w > 0).any() + except AssertionError: + raise ValueError("The weight function must be non-negative and non-null within the integration limits!") + + result = dict() + result['norm_1'] = np.sqrt(quad(lambda x: weight_function(x)*f(x)**2, int_limits[0], int_limits[1])[0]) + result['norm_2'] = np.sqrt(quad(lambda x: weight_function(x)*g(x)**2, int_limits[0], int_limits[1])[0]) + result['norm_sum'] = np.sqrt(quad(lambda x: weight_function(x)*(f(x)+g(x))**2, int_limits[0], int_limits[1])[0]) + result['inner_product'] = quad(lambda x: weight_function(x)*f(x)*g(x), int_limits[0], int_limits[1])[0] + result['angle'] = np.arccos(result['inner_product'] / (result['norm_1'] * result['norm_2'])) \ + if result['norm_1'] > 0 and result['norm_2'] > 0 else np.nan + + if show: + display(Latex(r"$||f||$ = % 10.4f; $||g||$ = % 10.4f" + % (result['norm_1'], result['norm_2']))) + display(Latex(r"$||f+g||$ = % 10.4f; $\langle f,g \rangle$ = % 10.4f" + % (result['norm_sum'], result['inner_product']))) + display(Latex(r"$\Delta\theta$ = % 10.4f °; $||f+g||^2$ = % 10.4f; $||f||^2 + ||g||^2$ = % 10.4f" + % (result['angle'] * 180.0 / np.pi, result['norm_sum'] ** 2, + result['norm_1'] ** 2 + result['norm_2'] ** 2))) + + return result + + +def couple_functions_plotter(f, g, limits, pi_formatting=False): + """Function that, given two functions, generates a subplot with a plot of the functions and a plot of their + pointwise product + + :param f: first function + :type f: function + :param g: second function + :type g: function + :param limits: plotting limits + :type limits: list[float, float] + :param pi_formatting: if True, the x-axis is formatted with ticks relative to pi. Defaults to False + :type pi_formatting: bool + """ + + x = np.linspace(limits[0], limits[1], 1000) + + fg = lambda x: f(x) * g(x) + + fig, axs = plt.subplots(1, 2, figsize=(16, 8)) + + axs[0].plot(x, f(x), label='f', color='b') + axs[0].plot(x, g(x), label='g', color='r') + min_val = np.min(np.hstack([f(x), g(x)])) + max_val = np.max(np.hstack([f(x), g(x)])) + axs[0].set_ylim([min_val - 0.1*np.abs(min_val), max_val + 0.1*np.abs(max_val)]) + axs[0].set_xlim([limits[0], limits[1]]) + x_axis_pos = 0.0 if min_val >= 0 else 1.0 if max_val <= 0 else np.abs(min_val) / (max_val - min_val) + y_axis_pos = 0.0 if limits[0] >= 0 else 1.0 if limits[1] <= 0 else np.abs(limits[0]) / (limits[1] - limits[0]) + axs[0].grid(linestyle='--', linewidth=0.5) + axs[0].set_title('Fonctions f et g') + axs[0].legend(loc='best', fontsize=10) + axs[0].spines['left'].set_position(('axes', y_axis_pos)) + axs[0].spines['bottom'].set_position(('axes', x_axis_pos)) + axs[0].spines['right'].set_color('none') + axs[0].spines['top'].set_color('none') + axs[0].xaxis.set_ticks_position('bottom') + axs[0].yaxis.set_ticks_position('left') + + if pi_formatting: + axs[0].xaxis.set_major_locator(plt.MultipleLocator(np.pi/2)) + axs[0].xaxis.set_minor_locator(plt.MultipleLocator(np.pi/12)) + axs[0].xaxis.set_major_formatter(plt.FuncFormatter(multiple_formatter())) + axs[0].xaxis.set_tick_params(labelsize=13) + + axs[1].plot(x, fg(x), color='g') + axs[1].grid(linestyle='--', linewidth=0.5) + axs[1].set_title('Produit entre f et g') + min_val = np.min(fg(x)) + max_val = np.max(fg(x)) + axs[1].set_ylim([min_val - 0.1 * np.abs(min_val), max_val + 0.1 * np.abs(max_val)]) + axs[1].set_xlim([limits[0], limits[1]]) + x_axis_pos = 0.0 if min_val >= 0 else 1.0 if max_val <= 0 else np.abs(min_val) / (max_val - min_val) + y_axis_pos = 0.0 if limits[0] >= 0 else 1.0 if limits[1] <= 0 else np.abs(limits[0]) / (limits[1] - limits[0]) + axs[1].spines['left'].set_position(('axes', y_axis_pos)) + axs[1].spines['bottom'].set_position(('axes', x_axis_pos)) + axs[1].spines['right'].set_color('none') + axs[1].spines['top'].set_color('none') + axs[1].xaxis.set_ticks_position('bottom') + axs[1].yaxis.set_ticks_position('left') + + if pi_formatting: + axs[1].xaxis.set_major_locator(plt.MultipleLocator(np.pi/2)) + axs[1].xaxis.set_minor_locator(plt.MultipleLocator(np.pi/12)) + axs[1].xaxis.set_major_formatter(plt.FuncFormatter(multiple_formatter())) + axs[1].xaxis.set_tick_params(labelsize=13) + + fig.suptitle(f"Tracés des deux fonctions et de leur produit") + + return + + +def multiple_formatter(denominator=2, number=np.pi, latex='\pi'): + def gcd(a, b): + while b: + a, b = b, a%b + return a + + def _multiple_formatter(x, pos): + den = denominator + num = np.int(np.rint(den*x/number)) + com = gcd(num,den) + (num,den) = (int(num/com),int(den/com)) + if den == 1: + if num == 0: + return r'$0$' + if num == 1: + return r'$%s$'%latex + elif num == -1: + return r'$-%s$'%latex + else: + return r'$%s%s$'%(num,latex) + else: + if num == 1: + return r'$\frac{%s}{%s}$'%(latex,den) + elif num == -1: + return r'$-\frac{%s}{%s}$'%(latex,den) + else: + return r'$\frac{%s%s}{%s}$'%(num,latex,den) + + return _multiple_formatter + + +def visualize_Cauchy_Schwarz(u, v, limits=None): + """Method that allows to plot the parabola explaining the Cauchy-Schwarz inequality for the two 'vectors' u and v. + u and v can be n-dimensional vectors (in which case the standard inner product is adopted), MxN matrices (in which + case the trace-induced inner product is used) or continuous functions (in which case the integral-induced inner + product is used). Also, the LHS and the RHS terms appearing in the Cauchy-Schwarz inequality are returned + + :param u: first 'vector' + :type u: list[float] or list[list[float]] or numpy.ndarray or function + :param v: second 'vector' + :type v: list[float] or list[list[float]] or numpy.ndarray or function + :param limits: if u and v are functions, those are the integration limits; otherwise they are useless and + they are defaulted to None. They must be expressed as a either a list of 2 floats (for 1D integration) or as + a list of n lists of 2 floats (for n-dimensional integration) + :type limits: list[list[float, float]] or list[float, float] NoneType + :return: LHS and RHS terms of the Cauchy-Schwarz inequality, in a dictionary + :rtype: dict(str:float, str:float) + """ + + try: + assert type(u) is type(v) + except AssertionError: + raise ValueError(f"The two input vectors must be of the same type, while here the first input is of type " + f"{type(u)}, while he second one is of type {type(v)}") + + if not callable(u): + u = np.squeeze(np.array(u)) + v = np.squeeze(np.array(v)) + else: + try: + assert limits is not None and type(limits) is list and len(limits) + except AssertionError: + raise ValueError("If the given inputs are functions, then the extrema of integration must be passed as a " + "list made of lists of two floats via the keyword argument 'limits'. If the list" + "has n sublists, then the functions must have n arguments, otherwise an error is thrown." + "Also, 'limits' can be a list of 2 floats in 1D integration has to be performed") + + if type(u) is np.ndarray: + try: + assert u.shape == v.shape + except AssertionError: + raise ValueError(f"The two input elements must be of the same shape, while here the first input has" + f"shape {u.shape}, while the second one has shape {v.shape}") + + if callable(u): + if type(limits[0]) is not list: + limits = [limits] + + a = nquad(lambda *x: v(*x)**2, limits)[0] + b = 2 * nquad(lambda *x: u(*x)*v(*x), limits)[0] + c = nquad(lambda *x: u(*x)**2, limits)[0] + + elif len(u.shape) > 1: + a = np.trace(np.dot(v.T, v)) + b = 2 * np.trace(np.dot(u.T, v)) + c = np.trace(np.dot(u.T, u)) + + else: + a = np.linalg.norm(v)**2 + b = 2 * np.inner(u,v) + c = np.linalg.norm(u)**2 + + x_v = -b / (2*a) if a > 0 else 0.0 + t = np.linspace(x_v-5, x_v+5, 100) + plot_limits = [x_v-5, x_v+5] + + parab = lambda t: a*t**2 + b*t + c + + fig, axs = plt.subplots(1,1) + axs.plot(t, parab(t), color='r') + axs.grid(linestyle='--', linewidth=0.5) + axs.set_title('Parabole de Cauchy-Schwarz') + min_val = np.min(np.hstack([0, parab(t)])) + max_val = np.max(parab(t)) + axs.set_ylim([min_val - 0.1 * np.abs(min_val), max_val + 0.1 * np.abs(max_val)]) + axs.set_xlim([plot_limits[0], plot_limits[1]]) + x_axis_pos = 0.0 if min_val >= 0 else 1.0 if max_val <= 0 else np.abs(min_val) / (max_val - min_val) + y_axis_pos = 0.0 if plot_limits[0] >= 0 else 1.0 if plot_limits[1] <= 0 else \ + np.abs(plot_limits[0]) / (plot_limits[1] - plot_limits[0]) + axs.spines['left'].set_position(('axes', y_axis_pos)) + axs.spines['bottom'].set_position(('axes', x_axis_pos)) + axs.spines['right'].set_color('none') + axs.spines['top'].set_color('none') + axs.xaxis.set_ticks_position('bottom') + axs.yaxis.set_ticks_position('left') + + out = dict() + out['LHS'] = np.abs(b / 2) + out['RHS'] = np.sqrt(a) * np.sqrt(c) + + display(Latex(r"Cauchy-Schwarz Inequality: $|\langle u,v \rangle| = % 10.4f \leq ||u|| \ ||v|| = % 10.4f$" + % (out['LHS'], out['RHS']))) + + return out + + +def compute_expansion_coefficients(B, v, W=None, int_limits=None, weight_function=None): + """Method that, given a basis and an element, computes the expansion coefficients of the element with respect to + the basis + + :param B: basis of the vector space. Here B is assumed to be a basis!! + :type B: list[function] or list[list] or list[tuple] or list[numpy.ndarray] + :param v: element + :type v: function or list or tuple or numpy.ndarray + :param W: weighting matrix. If None, it defaults to the identity + :type W: numpy.ndarray or NoneType + :param int_limits: integration limits. If None, they are not used + :type int_limits: list[float, float] or NoneType + :param weight_function: weighting function. If None, it defaults to 1.0 + :type weight_function: function or NoneType + :return: coordinates of the element with respect to the basis + :rtype: list + """ + + try: + assert type(B) is list and len(B) + except AssertionError: + raise TypeError(f"The basis must be passed as a non-empty list of elements, while its type is {type(B)} " + f"in this case.") + + try: + assert type(B[0]) == type(v) + except AssertionError: + raise TypeError(f"The target vector and the basis elements must be of the same type, while they are of types" + f"{type(B[0])} and {type(v)} respectively in this case.") + + if callable(v): + + try: + assert int_limits is not None + except AssertionError: + raise ValueError("Impossible to compute the inner product between functions if no integration limits " + "are defined") + + if weight_function is None: + weight_function = lambda x: 1.0 + 0*x + + try: + x = np.linspace(int_limits[0], int_limits[1], 1000) + w = weight_function(x) + assert (w >= 0).all() and (w > 0).any() + except AssertionError: + raise ValueError("The weight function must be non-negative and non-null within the integration limits!") + + A = np.zeros((len(B), len(B))) + b = np.zeros(len(B)) + for i in range(len(B)): + b[i] = quad(lambda x: weight_function(x)*B[i](x)*v(x), int_limits[0], int_limits[1])[0] + for j in range(i+1): + A[i,j] = quad(lambda x: weight_function(x)*B[i](x)*B[j](x), int_limits[0], int_limits[1])[0] + + elif type(v) is list or type(v) is tuple or type(v) is np.ndarray: + + if W is None: + W = np.eye(len(v)) + else: + if type(W) is not np.ndarray: + W = np.array(W) + try: + assert W.shape[0] == W.shape[1] == len(v) == len(B[0]) + except AssertionError: + raise ValueError("The weighting matrix must be a square matrix with the same dimensionality of the " + "basis elements and the target element") + + try: + assert isSym(W) + except AssertionError: + raise ValueError("The weighting matrix must be symmetric to define an inner product") + + lam, _ = np.linalg.eig(W) + try: + assert (np.real(lam) > 0).all() + except AssertionError: + raise ValueError("The weighting matrix must be positive definite to define an inner product!") + + A = np.zeros((len(B), len(B))) + b = np.zeros(len(B)) + for i in range(len(B)): + b[i] = np.inner(v, np.dot(W, B[i])) + for j in range(i + 1): + A[i, j] = np.inner(B[i], np.dot(W, B[j])) + + else: + raise TypeError(f"The type of the target element is {type(v)} and it is not supported") + + A = A + A.T - np.diag(np.diag(A)) + v_B = np.linalg.solve(A, b) + + return v_B + + +def project(u, v, W=None, int_limits=None, weight_function=None): + """Method that computes the projection of 'u' over 'v', considering both the cases in which u and v are vectors or + functions. In the former case, it used the inner product defined by 'W' (the standard one if W is None); in the + latter case it uses the integral inner product, within the limits specified by 'int_limits' and considering + 'weight_function' as weight function + + :param u: element to project + :type u: list or numpy.ndarray or sympy.function + :param v: element on which projecting + :type v: list or numpy.ndarray or sympy.function + :param W: weighting matrix + :type W: list or numpy.ndarray or Nonetype + :param int_limits: integration limits + :type int_limits: list or NoneType + :param weight_function: weighting function + :type weight_function: sympy.function or NoneType + :return: projection of u onto v + :rtype: numpy.ndarray or sympy.function + """ + + try: + assert type(u) == type(v) or (isinstance(u, sp.Basic) and isinstance(v, sp.Basic)) + except AssertionError: + raise TypeError(f"The vector to project and the one on which projecting must be of the same type, while here " + f"u is of type {type(u)} and v is of type {type(v)}") + + if isinstance(u, sp.Basic): + + x = sp.Symbol('x') + + try: + assert int_limits is not None + except AssertionError: + raise ValueError("Impossible to compute the inner product between functions if no integration limits " + "are defined") + + if weight_function is None: + weight_function = 1.0 + 0*x + + try: + xx = np.linspace(int_limits[0], int_limits[1], 1000) + weight_function_lam = np.vectorize(sp.lambdify(x, weight_function, "numpy")) + w = weight_function_lam(xx) + assert (w >= 0).all() and (w > 0).any() + + except AssertionError: + raise ValueError("The weight function must be non-negative and non-null within the integration limits!") + + coeff = sp.integrate(weight_function * u * v, (x, int_limits[0], int_limits[1])) / \ + sp.integrate(weight_function * v * v, (x, int_limits[0], int_limits[1])) + + proj_u_v = coeff * v + + elif type(u) is list or type(u) is np.ndarray: + + if W is None: + W = np.eye(len(v)) + else: + if type(W) is not np.ndarray: + W = np.array(W) + try: + assert W.shape[0] == W.shape[1] == len(v) == len(u) + except AssertionError: + raise ValueError("The weighting matrix must be a square matrix with the same dimensionality of the " + "basis elements and the target element") + + try: + assert isSym(W) + except AssertionError: + raise ValueError("The weighting matrix must be symmetric to define an inner product") + + lam, _ = np.linalg.eig(W) + try: + assert (np.real(lam) > 0).all() + except AssertionError: + raise ValueError("The weighting matrix must be positive definite to define an inner product!") + + coeff = np.inner(u, np.dot(W, v)) / np.inner(v, np.dot(W, v)) + + proj_u_v = coeff * np.array(v) + + else: + raise TypeError(f"The type of the target element is {type(u)} and it is not supported") + + return proj_u_v + + +def gram_schmidt(vects, W=None, return_norms=False, show=False): + """Method that, given a list of vectors, applies the Gram-Schmidt process to it. If the list + of elements is a basis for some subspace, then this algorithm computes an orthonormal basis. + + :param vects: initial list of elements + :type vects: list[list] or list[numpy.ndarray] + :param W: weighting matrix + :type W: list or numpy.ndarray or NoneType + :param return_norms: if True, the norms of the generated orthogonal vectors are returned. Defaults to False + :type return_norms: bool + :param show: if True, the final vectors are plotted, in case thei are 2D or 3D. It defaults to False + :type show: bool + :return: list of elements after the application of the Gram-Schmidt algorithm + :rtype: list[numpy.ndarray] + """ + + if W is None: + W = np.eye(len(vects[0])) + + norms = [np.sqrt(np.inner(np.dot(W, vects[0]), vects[0]))] + result = [np.array(vects[0], dtype='float32') / norms[0]] + + for index in range(1, len(vects)): + tmp = np.array(vects[index], dtype='float32') + for item in range(index): + tmp -= project(tmp, result[item], W=W) + + norms.append(np.sqrt(np.inner(np.dot(W, tmp), tmp))) + if norms[-1] > 1e-4: + result.append(tmp / norms[-1]) + else: + result.append(tmp) + + if show: + if len(result[0]) == 2: + vector_plot_2D(result, + labels=[f'vector {i}' for i in range(len(result))]) + elif len(result[0] == 3): + vector_plot_3D(result, + labels=[f'old vector {i}' for i in range(len(result))]) + else: + display(Latex(r"La représentation graphique n'est pas possible dans $\mathbb{R}^n$, avec $n \geq 4$!")) + + if return_norms: + return result, norms + else: + return result + + +def gram_schmidt_func(vects, int_limits=None, weight_function=None, return_norms=False, show=False): + """Method that, given a list of uni-variate functions, applies the Gram-Schmidt process to it. If the list + of elements is a basis for some subspace, then this algorithm computes an orthonormal basis. + + :param vects: initial list of elements + :type vects: list[sympy.function] + :param int_limits: integration limits + :type int_limits: list[float float] or NoneType + :param weight_function: weighting function. If None, it defaults to 1 + :type weight_function: function or NoneType + :param return_norms: if True, the norms of the generated orthogonal vectors are returned. Defaults to False + :type return_norms: bool + :param show: if True, the computed functions are plotted. It defaults to False + :type show: bool. + :return: list of elements after the application of the Gram-Schmidt algorithm + :rtype: list[numpy.ndarray] + """ + + x = sp.Symbol('x') + + if int_limits is None: + int_limits = [-1, 1] + + if weight_function is None: + weight_function = 1 + 0*x + + norms = [sp.sqrt(sp.integrate(weight_function * vects[0] * vects[0], (x, int_limits[0], int_limits[1])))] + result = [vects[0] / norms[0]] + + for index in range(1, len(vects)): + for item in range(index): + tmp = vects[index] + vects[index] -= project(tmp, result[item], + weight_function=weight_function, + int_limits=int_limits) + + curr_norm = sp.sqrt(sp.integrate(weight_function * vects[index] * vects[index], + (x, int_limits[0], int_limits[1]))) + norms.append(curr_norm) + + if norms[-1] > 1e-4: + result.append(vects[index] / curr_norm) + else: + result.append(vects[index]) + + if show: + p = sp.plotting.plot(show=False, xlim=(int_limits[0], int_limits[1]), ylim=(-2, 2), + title="Fonctions", legend=True) + colors = cycle(["b", "g", "r", "c", "m", "y", "k"]) + for item in range(len(result)): + p.append(sp.plotting.plot(result[item], show=False, xlim=(int_limits[0], int_limits[1]), ylim=(-2, 2), + title="Fonctions", legend=True, line_color=next(colors))[0]) + p.show() + + if return_norms: + return result, norms + else: + return result + + +def project_on_subspace(u, B, W=None, int_limits=None, weight_function=None, plot=True, show=True): + """Method that allows to compute the projection of 'u' over a subspace, whose basis is 'B'. It works both on + vectors and functions. In the former case, it used the inner product defined by 'W' (the standard one if W is None); + in the latter case it uses the integral inner product, within the limits specified by 'int_limits' and considering + 'weight_function' as weight function + + :param u: element to project + :type u: list or numpy.ndarray or sympy.function + :param B: basis of the subspace + :type B: list[list] or list[numpy.ndarray] or list[sympy.function] + :param W: weighting matrix + :type W: list or numpy.ndarray or Nonetype + :param int_limits: integration limits + :type int_limits: list[float] or NoneType + :param weight_function: weighting function + :type weight_function: sympy.function or NoneType + :param plot: if True and if possible, the results are displayed in a plot. Defaults to True. + :type plot: bool + :param show: if True, the results are displayed. Defaults to True + :type show: bool + :return: projection of u onto the subspace having B as basis + :rtype: numpy.ndarray or sympy.function + """ + + try: + assert all([type(u) == type(B[i]) for i in range(len(B))]) or \ + all([isinstance(u, sp.Basic) and isinstance(B[i], sp.Basic) for i in range(len(B))]) + except AssertionError: + raise TypeError(f"Le vecteur à projeter et ceux générant le sous-espace vectoriel sur lequel la projection " + f"doit être calculée doivent être du même type!") + + if type(u) in {list, np.ndarray}: + B_norm = gram_schmidt(B, W=W, return_norms=False, show=False) + u = np.array(u, dtype='float64') + elif isinstance(u, sp.Basic): + if int_limits is None: + int_limits = [-1, 1] + x = sp.Symbol('x') + B_norm = gram_schmidt_func(B, int_limits=int_limits, weight_function=weight_function, return_norms=False, show=False) + else: + raise TypeError + + res = np.zeros_like(u, dtype='float64') if type(u) in {list, np.ndarray} \ + else 0 + 0*x if isinstance(u, sp.Basic) \ + else None + + for v in B_norm: + res += project(u, v, W=W, int_limits=int_limits, weight_function=weight_function) + + if show: + display(Latex(f'La projection de $u$ sur $W$ est: ' + f'{np.round(res, 4) if type(u) is np.ndarray else res}')) + display(Latex(f'La projection de $u$ sur $W^\perp$ est: ' + f'{np.round(u-res, 4) if type(u) is np.ndarray else u-res}')) + + if plot: + if type(res) is np.ndarray: + if len(res) >= 4 or len(res) <= 1: + display(Latex(r"La représentation graphique n'est pas possible dans $\mathbb{R}^n$, avec $n \geq 4$!")) + return np.round(res, 4) if type(u) is np.ndarray else res + + elif len(res) == 2: + fig = vector_plot_2D(list(chain(*[[u], B, [res], [u-res]])), + orig=list(chain(*[[[0,0]], [[0,0] for _ in range(len(B))], [[0,0]], [res]])), + labels=list(chain(*[['u'], [f'$B_{i}$' for i in range(len(B))], + [r'$proj_uW$'], [r'$proj_uW^\perp$']])), + show=False) + + elif len(res) == 3: + fig = vector_plot_3D(list(chain(*[[u], B, [res], [u-res]])), + orig=list(chain(*[[[0,0,0]], [[0,0,0] for _ in range(len(B))], [[0,0,0]], [res]])), + labels=list(chain(*[['u'], [f'$B_{i}$' for i in range(len(B))], + [r'$proj_uW$'], [r'$proj_uW^\perp$']])), + show=False) + + if len(B) == 2: + colorscale = [[0.0, 'rgb(0,255,255)'], + [0.1, 'rgb(0,255,255)'], + [0.2, 'rgb(0,255,255)'], + [0.3, 'rgb(0,255,255)'], + [0.4, 'rgb(0,255,255)'], + [0.5, 'rgb(0,255,255)'], + [0.6, 'rgb(0,255,255)'], + [0.7, 'rgb(0,255,255)'], + [0.8, 'rgb(0,255,255)'], + [0.9, 'rgb(0,255,255)'], + [1.0, 'rgb(0,255,255)']] + coeffs = [(B[0][1] * B[1][2] - B[1][1] * B[0][2]), + (B[1][0] * B[0][2] - B[0][0] * B[1][2]), + (B[0][0] * B[1][1] - B[1][0] * B[0][1])] + length = np.linspace(-1.5*max(abs(B[0][0]), abs(B[1][0]), abs(res[0])), + 1.5*max(abs(B[0][0]), abs(B[1][0]), abs(res[0])), 100) + width = np.linspace(-1.5*max(abs(B[0][1]), abs(B[1][1]), abs(u[1])), + 1.5*max(abs(B[0][1]), abs(B[1][1]), abs(res[1])), 100) + height = np.linspace(-1.5 * max(abs(B[0][2]), abs(B[1][2]), abs(u[2])), + 1.5 * max(abs(B[0][2]), abs(B[1][2]), abs(res[2])), 100) + + if coeffs[2] != 0: + Grid1, Grid2 = np.meshgrid(length, width) + x = Grid1 + y = Grid2 + new_coeffs = [-coeffs[0]/coeffs[2], -coeffs[1]/coeffs[2]] + ref_index = 2 + elif coeffs[1] != 0: + Grid1, Grid2 = np.meshgrid(length, height) + x = Grid1 + z = Grid2 + new_coeffs = [-coeffs[0] / coeffs[1], -coeffs[2] / coeffs[1]] + ref_index = 1 + elif coeffs[0] != 0: + Grid1, Grid2 = np.meshgrid(width, height) + y = Grid1 + z = Grid2 + new_coeffs = [-coeffs[1] / coeffs[0], -coeffs[2] / coeffs[0]] + ref_index = 0 + else: + plotly.offline.iplot(fig) + return np.round(res, 4) if type(u) is np.ndarray else res + + surface = go.Surface(x=x if ref_index != 0 else new_coeffs[0]*y + new_coeffs[1]*z, + y=y if ref_index != 1 else new_coeffs[0]*x + new_coeffs[1]*z, + z=z if ref_index != 2 else new_coeffs[0]*x + new_coeffs[1]*y, + showscale=False, showlegend=True, opacity=0.25, colorscale=colorscale, name='W') + data = list(fig.data) + layout = fig.layout + data.append(surface) + fig = go.Figure(data=data, layout=layout) + plotly.offline.iplot(fig) + + elif len(B) == 1: + x = np.linspace(-1.5*max(abs(B[0][0]), abs(res[0])), + 1.5*max(abs(B[0][0]), abs(res[0])), 2) + y = np.linspace(-1.5 * max(abs(B[0][1]), abs(res[1])), + 1.5 * max(abs(B[0][1]), abs(res[1])), 2) + if len(B[0]) == 3: + z = np.linspace(-1.5 * max(abs(B[0][2]), abs(res[2])), + 1.5 * max(abs(B[0][2]), abs(res[2])), 2) + + coeffs = [B[0][0], B[0][1]] + if len(B[0]) == 3: + coeffs.append(B[0][2]) + + if coeffs[0] != 0: + new_coeffs = [coeffs[1] / coeffs[0]] if len(coeffs) == 2 \ + else [coeffs[1] / coeffs[0], coeffs[2]/coeffs[0]] + ref_index = 0 + elif coeffs[1] != 0: + new_coeffs = [coeffs[0] / coeffs[1]] if len(coeffs) == 2 \ + else [coeffs[0] / coeffs[1], coeffs[2] / coeffs[1]] + ref_index = 1 + elif len(coeffs) == 3 and coeffs[2] != 0: + new_coeffs = [coeffs[0] / coeffs[2]] if len(coeffs) == 2 \ + else [coeffs[0] / coeffs[2], coeffs[1] / coeffs[2]] + ref_index = 2 + else: + plotly.offline.iplot(fig) + return np.round(res, 4) if type(u) is np.ndarray else res + + if len(B[0]) == 2: + line = go.Scatter(x=x if ref_index == 0 else np.zeros_like(x), + y=new_coeffs[0]*x if ref_index == 0 else y, + marker=dict(size=[0, 0], + color=['rgb(255,165,0)'], + line=dict(width=1, + color='rgb(255,165,0)')), + name='W') + elif len(B[0]) == 3: + line = go.Scatter3d(x=x if ref_index == 0 else np.zeros_like(x), + y=new_coeffs[0]*x if ref_index == 0 + else y if ref_index == 1 else np.zeros_like(x), + z=new_coeffs[1]*x if ref_index == 0 + else new_coeffs[1]*y if ref_index == 1 + else z, + marker=dict(size=[0, 0], + color=['rgb(255,165,0)'], + line=dict(width=1, + color='rgb(0255,165,0)')), + name='W') + + data = list(fig.data) + layout = fig.layout + data.append(line) + fig = go.Figure(data=data, layout=layout) + plotly.offline.iplot(fig) + + if isinstance(res, sp.Basic): + p = sp.plotting.plot(show=False, xlim=(int_limits[0], int_limits[1]), ylim=(-2, 2), + title="Fonctions", legend=True) + colors = cycle(chain(*[["g" for _ in range(len(B))], ["b"], ["c"], ["r"]])) + plt_vects = list(chain(*[B, [u], [res], [u-res]])) + labels = list(chain(*[[f'$B_{i}$' for i in range(len(B))], ['u'], [r'$proj_uW$'], [r'$proj_uW^\perp']])) + for cnt, item in enumerate(plt_vects): + p.append( + sp.plotting.plot(item, show=False, xlim=(int_limits[0], int_limits[1]), ylim=(-2, 2), + title="Fonctions", label=labels[cnt], legend=True, line_color=next(colors))[0]) + p.show() + + return np.round(res, 4) if type(u) is np.ndarray else res + + +def manual_GS(dim): + """Interactive code, that allows to perform the interactive GS process on vectors + + :type dim: dimensionality of the vectors' set + :type dim: int + :return: + :rtype: + """ + + assert dim > 0 and type(dim) is int + + style = {'description_width': 'initial'} + + step_number = widgets.BoundedIntText( + value=1, + step=1, + min=1, + max=dim, + description='Numéro du élément courant', + disabled=False, + style=style + ) + + norm_coeff = widgets.BoundedFloatText( + value=1.0, + step=0.1, + min=0.0, + max=1e10, + description='Coefficient de Normalisation', + disabled=False, + style=style + ) + + proj_coeffs = [None] * (dim-1) + for item in range(dim-1): + proj_coeffs[item] = widgets.FloatText( + value=0.0, + step=0.1, + description=f'Coefficient de Projection {item+1}', + disabled=False, + style=style + ) + + operation = widgets.RadioButtons( + options=['Projection', 'Normalization', 'Revert'], + description='Opération:', + disabled=False, + style=style + ) + + display(Latex("Régler les paramètres et évaluer la cellule suivante")) + display(step_number) + display(operation) + display(norm_coeff) + for item in range(dim-1): + display(proj_coeffs[item]) + + return norm_coeff, proj_coeffs, operation, step_number + + +def interactive_gram_schmidt(norm_coeff, proj_coeffs, operation, step_number, old_vects, VectorsList, W=None): + """Method that, given a list of vectors, allows to perform the Gram-Schmidt process interactively. + + :param norm_coeff: + :type norm_coeff: + :param proj_coeffs: + :type proj_coeffs: + :param operation: + :type operation: + :param step_number: + :type step_number: + :param VectorsList: + :type VectorsList: + :param old_vects: + :type old_vects: + :param W: + :type W: + :return: + :rtype: + """ + + if type(VectorsList[0][0]) is not np.ndarray or type(VectorsList[0][0][0]) is not float: + for elem in range(len(VectorsList[0])): + VectorsList[0][elem] = np.array(VectorsList[0][elem], dtype='float32') + + if type(old_vects[0]) is not np.ndarray or type(old_vects[0][0]) is not float: + for elem in range(len(old_vects)): + old_vects[elem] = np.array(old_vects[elem], dtype='float32') + + step = step_number.value + new_vects = old_vects.copy() + + if step <= 1 and operation.value == 'Projection': + display(Latex("Opération invalide. Le premier vecteur ne peut pas être projeté!")) + return new_vects + + display(Latex(f"Construction du vecteur numéro {step}")) + + if operation.value == 'Revert': + if len(VectorsList) > 1: + VectorsList.pop() + new_vects = VectorsList[-1].copy() + else: + display(Latex("Impossible de revenir sur l'opération!")) + + elif operation.value == 'Normalization': + _, true_val = gram_schmidt(new_vects[:step], W=W, return_norms=True) + if np.abs(true_val[-1] - norm_coeff.value) <= 1e-4 and norm_coeff.value > 0.0: + display(Latex("La valuer entrée est correct!")) + new_vects[step-1] /= norm_coeff.value + VectorsList.append(new_vects) + else: + display(Latex("La valeur entrée n'est pas correct! " + "Réessayez et n'oubliez pas d'insérer les résultats avec 4 chiffres après la virgule." + "Vous pouvez annuler votre opération en sélectionnant le bouton 'Revert'")) + + elif operation.value == 'Projection': + true_res, true_norms = gram_schmidt(new_vects[:step], W=W, return_norms=True) + true_res_2 = list(map(mul, true_res, true_norms)) + for item in range(step-1): + new_vects[step-1] -= proj_coeffs[item].value * new_vects[item] + if all([(np.linalg.norm(new_vects[item] - true_res_2[item]) <= 1e-4) for item in range(step)]): + display(Latex("Le valuers entrées sont correctes!")) + VectorsList.append(new_vects) + else: + display(Latex("Le valuer entrées ne sont pas correctes! " + "Réessayez et n'oubliez pas d'insérer les résultats avec 4 chiffres après la virgule." + "Vous pouvez annuler votre opération en sélectionnant le bouton 'Revert'")) + + else: + raise ValueError(f"Opération {operation.value} non reconnue!") + + new_vects_mat = np.array(new_vects) + orthonormality_mat = np.array([[np.dot(new_vects_mat[i], new_vects_mat[j]) + for i in range(len(new_vects_mat))] + for j in range(len(new_vects_mat))]) + + for dim in range(len(new_vects), 0, -1): + orthonormality_check = np.linalg.norm(orthonormality_mat[:dim, :dim] - np.eye(dim)) <= 1e-4 and \ + np.linalg.norm(orthonormality_mat[dim:, dim:]) <= 1e-4 + + if orthonormality_check: + display(Latex("OK! L'algorithme de Gram-Schmidt est terminé!")) + break + + display(Latex( + f"Vecteurs courantes: {[np.around(VectorsList[-1][item].tolist(), 4) for item in range(len(VectorsList[-1]))]}")) + + if len(new_vects[0]) == 2: + if len(VectorsList) >= 2: + vector_plot_2D(VectorsList[-2] + new_vects, + labels=[f'old vector {i}' for i in range(len(old_vects))] + + [f'new vector {i}' for i in range(len(new_vects))]) + else: + vector_plot_2D(new_vects, + labels=[f'old vector {i}' for i in range(len(new_vects))]) + elif len(new_vects[0]) == 3: + if len(VectorsList) >= 2: + vector_plot_3D(VectorsList[-2] + new_vects, + labels=[f'old vector {i}' for i in range(len(old_vects))] + + [f'new vector {i}' for i in range(len(new_vects))]) + else: + vector_plot_3D(new_vects, + labels=[f'old vector {i}' for i in range(len(new_vects))]) + else: + display(Latex(r"La représentation graphique n'est pas possible dans $\mathbb{R}^n$, avec $n \geq 4$!")) + + return new_vects + + +def interactive_gram_schmidt_func(norm_coeff, proj_coeffs, operation, step_number, old_vects, VectorsList, + int_limits=None, weight_function=None): + """Method that, given a list of vectors, allows to perform the Gram-Schmidt process interactively. + + :param norm_coeff: + :type norm_coeff: + :param proj_coeffs: + :type proj_coeffs: + :param operation: + :type operation: + :param step_number: + :type step_number: + :param VectorsList: + :type VectorsList: + :param old_vects: + :type old_vects: + :param int_limits: integration limits + :type int_limits: list[float,float] or NoneType + :param weight_function: weighting function + :type weight_function: sympy.function + :return: + :rtype: + """ + + x = sp.Symbol('x') + + if int_limits is None: + int_limits = [-1, 1] + + if weight_function is None: + weight_function = 1 + 0*x + + xx = np.linspace(int_limits[0], int_limits[1], 500) + + step = step_number.value + new_vects = old_vects.copy() + + if step <= 1 and operation.value == 'Projection': + display(Latex("Opération invalide. Le premier vecteur ne peut pas être projeté!")) + return new_vects + + display(Latex(f"Construction de la fonction numéro {step}")) + + if operation.value == 'Revert': + if len(VectorsList) > 1: + VectorsList.pop() + new_vects = VectorsList[-1].copy() + else: + display(Latex("Impossible de revenir sur l'opération!")) + + elif operation.value == 'Normalization': + _, true_val = gram_schmidt_func(new_vects[:step], int_limits=int_limits, weight_function=weight_function, + return_norms=True) + if np.abs(true_val[-1] - norm_coeff.value) <= 1e-4 and norm_coeff.value > 0.0: + display(Latex("La valuer entrée est correct!")) + new_vects[step-1] = new_vects[step-1] / norm_coeff.value + VectorsList.append(new_vects) + else: + display(Latex("La valeur entrée n'est pas correct! " + "Réessayez et n'oubliez pas d'insérer les résultats avec 4 chiffres après la virgule.")) + + elif operation.value == 'Projection': + true_res, true_norms = gram_schmidt_func(new_vects[:step], int_limits=int_limits, + weight_function=weight_function, + return_norms=True) + true_res_2 = [true_res[i] * true_norms[i] for i in range(len(true_norms))] + for item in range(step-1): + new_vects[step-1] = new_vects[step-1] - proj_coeffs[item].value * new_vects[item] + + if all([(np.linalg.norm(np.vectorize(sp.lambdify(x, new_vects[item] - true_res_2[item], "numpy"))(xx)) <= 1e-4) + for item in range(step)]): + display(Latex("Le valuers entrées sont correctes!")) + VectorsList.append(new_vects) + else: + display(Latex("Le valuer entrées ne sont pas correctes! " + "Réessayez et n'oubliez pas d'insérer les résultats avec 4 chiffres après la virgule." + "Vous pouvez annuler votre opération en sélectionnant le bouton 'Revert'")) + + else: + raise ValueError(f"Opération {operation.value} non reconnue!") + + display(Latex(f"Fonctions courantes: {new_vects}")) + + p = sp.plotting.plot(show=False, xlim=(int_limits[0], int_limits[1]), ylim=(-2, 2), + title="Fonctions") + colors = cycle(["b", "g", "r", "c", "m", "y", "k"]) + for item in range(len(new_vects)): + p.append(sp.plotting.plot(new_vects[item], show=False, xlim=(int_limits[0], int_limits[1]), ylim=(-2, 2), + title="Fonctions", line_color=next(colors))[0]) + p.show() + + orthonormality_mat = np.array([[float(sp.integrate(weight_function * new_vects[i] * new_vects[j], + (x, int_limits[0], int_limits[1]))) + for i in range(len(new_vects))] + for j in range(len(new_vects))]) + + for dim in range(len(new_vects), 1, -1): + orthonormality_check = np.linalg.norm(orthonormality_mat[:dim, :dim] - np.eye(dim)) <= 1e-4 and \ + np.linalg.norm(orthonormality_mat[dim:, dim:]) <= 1e-4 + + if orthonormality_check: + display(Latex("OK! L'algorithme de Gram-Schmidt est terminé!")) + break + + return new_vects + + +def integrate_sp_function(func, x, int_limits, show=True): + """Easy-to-use interface to the sympy integrate command, used to integrate uni-variate functions + + :param func: function to integrate + :type func: sympy.function + :param x: integration variable + :type x: sympy.Symbol + :param int_limits: integration limits + :type int_limits: list[float,float] + :param show: if True, the result of the integration is displayed. Defaults to True. + :type show: bool + :return: result of the integration + :rtype: float + """ + + I = sp.integrate(func, (x, int_limits[0], int_limits[1])) + + if show: + display(Latex(f"Résultat de l'intégration: {I}")) + + return I + + +def extract_vectors_from_sympy_FiniteSet(S): + """Method to extract characteristic vectors from a sympy FiniteSet. + NOTICE: here the FiniteSet is assumed to have a unique element, as the solutions to linear systems! + + :param S: sympy FiniteSet to be evaluated + :type S: sp.sets.FiniteSet + :return: characteristic vectors of the set, in the form (characteristic solution, basis vectors) + :rtype: list[np.ndarray] + """ + + try: + assert isinstance(S, sp.sets.FiniteSet) or isinstance(S, sp.sets.EmptySet) + except AssertionError: + raise ValueError(f"L'ensemble doit être de type sympy.sets.FiniteSet ou sympy.sets.EmptySet, " + f"alors qu'ici il est de type {type(S)}") + + if len(S) > 0 and isinstance(S.args[0], Iterable): + S = S.args[0] + + if isinstance(S, sp.sets.EmptySet) or len(S) == 0: + return [] + + used_symbols = list(S.atoms(sp.Symbol)) + num_symbols = len(used_symbols) + + sol = [] + S = list(S) + + base_sol = np.zeros(len(S)) + for elem in range(len(S)): + if isinstance(S[elem], sp.Number): + base_sol[elem] = S[elem] + S[elem] -= base_sol[elem] + else: + values = (0,) * num_symbols + base_sol[elem] = sp.lambdify(list(used_symbols), S[elem])(*values) + S[elem] -= base_sol[elem] + sol.append(base_sol) + + for count, symbol in enumerate(used_symbols): + expansion_sol = np.zeros(len(S)) + for elem in range(len(S)): + if isinstance(S[elem], sp.Number): + expansion_sol[elem] = 0 + else: + values = [0,] * num_symbols + values[count] = 1 + values = tuple(values) + expansion_sol[elem] = sp.lambdify(list(used_symbols), S[elem])(*values) + sol.append(expansion_sol) + + return sol + + +def compare_sympy_FiniteSets(set1, set2): + """Method that compares two sympy FiniteSet (or, more trivially, EmptySet) and determines whether they are + equivalent or not + + :param set1: first set + :type set1: sp.sets.FiniteSet or sp.sets.EmptySet + :param set2: second set + :type set2: sp.sets.FiniteSet or sp.sets.EmptySet + :return: True if the two sets are equivalent, False otherwise + :rtype: bool + """ + + vectors1 = extract_vectors_from_sympy_FiniteSet(set1) + vectors2 = extract_vectors_from_sympy_FiniteSet(set2) + + if len(vectors1) != len(vectors2) : + is_equal = False + elif all([np.linalg.norm(vectors1[i] - vectors2[i]) <= 1e-5 for i in range(len(vectors1))]): + is_equal = True + elif len(vectors1) > 1: + is_equal = True + + test_vectors = np.array([vectors1[i] - vectors1[0] + vectors2[0] + for i in range(1, len(vectors1))]).T + basis_vectors = np.array(vectors2[1:]).T + + rank1 = np.linalg.matrix_rank(basis_vectors) + rank2 = np.linalg.matrix_rank(np.hstack((basis_vectors, test_vectors))) + if rank1 != rank2: + is_equal = False + + if is_equal: + test_vectors = np.array([vectors2[i] - vectors2[0] + vectors1[0] + for i in range(1, len(vectors2))]).T + basis_vectors = np.array(vectors1[1:]).T + + rank1 = np.linalg.matrix_rank(basis_vectors) + rank2 = np.linalg.matrix_rank(np.hstack((basis_vectors, test_vectors))) + if rank1 != rank2: + is_equal = False + else: + is_equal = False + + return is_equal + + +def unique_qr(A, only_valid=True): + """Method to compute a unique QR factorization of a matrix A (if possible, else it raises a LinAlg error), + imposing the diagonal entries of R to be strictly positive. + + :param A: matrix to factorise + :type A: list[list[float]] or np.ndarray + :param only_valid: if True, the method raises a ValueError if A does not respect the criteria for the existance + of a QR decomposition. Defaults to True + :type only_valid: bool + :return: Q and R, resulting from QR factorization of A, with positivity constraint on the diagonal entries of R. + If A has more columns than rows or if its columns are linearly dependent, a ValueError is raised if 'only_valid' + is set to True + :rtype: tuple(np.ndarray, np.ndarray) ore NoneType + """ + + if only_valid: + if A.shape[1] > A.shape[0]: + raise ValueError("Cette méthode calcule uniquement la factorisation QR pour les matrices de taille MxN " + "avec M>=N, car cela garantit l'existence d'une factorisation QR.") + + if np.linalg.matrix_rank(A) < A.shape[1]: + raise ValueError("Cette méthode ne calcule la factorisation QR que pour les matrices ayant " + "colonnes linéairement indépendantes, car cela garantit l'existence de" + "une factorisation QR.") + + Q, R = np.linalg.qr(A, mode='reduced') + signs = 2 * (np.diag(R) >= 0) - 1 + Q = Q * signs[np.newaxis, :] + R = R * signs[:, np.newaxis] + + R[np.abs(R) < 1e-10] = 0 + + return Q, R + + +def check_QR_step(Q, R): + """Method to check the step number of the interactive QR algorithm, based on the expressions of Q and R + + :type Q: temporary orthogonal matrix + :type Q: list[list] or np.ndarray + :param R: temporary upper triangular matrix + :type R: list[list] or np.ndarray + :return: step number + :rtype: int + """ + + orthonormality_mat = np.array([[np.dot(Q[:, i], Q[:, j]) + for i in range(Q.shape[1])] + for j in range(Q.shape[1])]) + + step_check = False + step_number = 0 + for dim in range(Q.shape[1], 0, -1): + orthonormality_check = np.linalg.norm(orthonormality_mat[:dim, :dim] - np.eye(dim)) <= 1e-4 and \ + np.linalg.norm(orthonormality_mat[dim:, dim:]) <= 1e-4 + + if orthonormality_check: + step_number = Q.shape[1] + 1 + break + + if not orthonormality_check: + step_check = np.linalg.norm(orthonormality_mat[:dim, :dim] - np.eye(dim)) <= 1e-4 + + step_check_2 = False + if step_check: + for dim2 in range(1, Q.shape[1] - dim): + step_check_2 = np.linalg.norm(orthonormality_mat[dim:dim+dim2, dim:dim+dim2]) <= 1e-4 + + if step_check_2: + step_number = dim + dim2 + 1 + break + + if not step_check_2: + step_number = dim + 1 + break + + if not step_check: + step_number = 1 + + R_check = True + if step_number > 1: + R_check = np.allclose(R[:step_number-1, :step_number-1], np.triu(R[:step_number-1, :step_number-1])) and \ + np.all([R[i, i] >= 0 for i in range(step_number-1)]) + + if not R_check: + raise ValueError(f"Sur la base de la valeur de la matrice Q, l'algorithme doit être à l'étape {step_number+1}, " + f"mais la matrice R correspondante n'est pas triangulaire supérieure et avec des valeurs " + f"non-négatives le long de la diagonale principale!") + + return step_number + + +def manual_QR(Q, R, A): + """Interactive code, that allows to perform the interactive QR factorization of a matrix + + :type Q: temporary orthogonal matrix + :type Q: list[list] or np.ndarray + :param R: temporary upper triangular matrix + :type R: list[list] or np.ndarray + :param A: original matrix to be factorized + :type A: list[list] or np.ndarray + :return: + :rtype: + """ + + style = {'description_width': 'initial'} + + display(Latex(f"MATRICES COURANTES")) + printA(A, name="A") + printA(Q, name="Q") + printA(R, name="R") + + step_number = check_QR_step(Q, R) + + if step_number == Q.shape[1] + 1: + display(Latex("OK! La factorisation QR est terminé!")) + return + + display(Latex(f"En regardant les matrices Q et R, vous êtes à l'étape {step_number} de l'algorithme de " + f"factorisation QR!")) + if step_number == 1: + display(Latex(f"Il suffit de normaliser le premier vecteur colonne de la matrice $A$!")) + else: + display(Latex(f"Considérez la colonne nombre {step_number} de la matrice $A$!")) + display(Latex(r"Calculez son produit scalaire avec les vecteurs orthonormaux déjà dérivés " + r"$\{\langle c_i, w_j \rangle\}_{j=1}^{i-1}$ et insérez les valeurs dans les espaces données.")) + display(Latex(r"Enfin, dérivez sa projection sur l'espace généré par les vecteurs déjà considérés " + r"(via l'algorithme de Gram-Schmidt: " + r"$\tilde{w}_i = c_i - \sum\limits_{j=1}^{i-1} \langle c_i, w_j \rangle w_j $" + r"calculez sa norme $||\tilde{w}_i||$ et insérez-la dans le dernier espace!")) + + proj_coeffs = [None] * (step_number-1) + for item in range(step_number-1): + proj_coeffs[item] = widgets.FloatText( + value=0.0, + step=0.1, + description=f'Coefficient de Projection {item+1}', + disabled=False, + style=style + ) + + norm_coeff = widgets.BoundedFloatText( + value=1.0, + step=0.1, + min=0.0, + max=1e10, + description='Coefficient de Normalisation', + disabled=False, + style=style + ) + + + display(Latex("Régler les paramètres et évaluer la cellule suivante")) + for item in range(step_number-1): + display(proj_coeffs[item]) + display(norm_coeff) + + return norm_coeff, proj_coeffs + + +def interactive_QR(norm_coeff, proj_coeffs, QList, RList, A): + """Method that allows to perform the QR factorization algorithm interactively + + :param norm_coeff: + :type norm_coeff: + :param proj_coeffs: + :type proj_coeffs: + :param QList: + :type QList: + :param RList: + :type RList: + :param A: + :type A: + :return: + :rtype: + """ + + try: + assert len(QList) > 0 and len(RList) > 0 + except AssertionError: + raise ValueError("Les listes de matrices Q et R ne doivent pas être vides!") + + step_number = len(proj_coeffs) + 1 + + Q_new = QList[-1].copy() + Q_new[:, step_number-1] = A[:, step_number-1] + for index in range(step_number-1): + Q_new[:, step_number-1] -= proj_coeffs[index].value * Q_new[:, index] + Q_new[:, step_number-1] /= norm_coeff.value + + R_new = RList[-1].copy() + R_new[:step_number-1, step_number-1] = np.array([proj_coeffs[index].value for index in range(step_number-1)]) + R_new[step_number:, step_number-1] = 0 + R_new[step_number-1, step_number-1] = norm_coeff.value + + Q_true, R_true = unique_qr(A, only_valid=False) + + Q_check = np.linalg.norm(Q_new[:, :step_number] - Q_true[:, :step_number]) <= 1e-4 * np.max(A) + R_check = np.linalg.norm(R_new[:, :step_number] - R_true[:, :step_number]) <= 1e-4 * np.max(A) + + # printA(Q_new, name="Q_new") + # printA(Q_true, name="Q_true") + # printA(R_new, name="R_new") + # printA(R_true, name="R_true") + + if Q_check and R_check: + display(Latex("C'est correct!")) + QList.append(Q_new) + RList.append(R_new) + display(Latex(f"MATRICES COURANTES")) + printA(A, name="A") + printA(Q_new, name="Q") + printA(R_new, name="R") + if step_number == A.shape[1]: + display(Latex("OK! La factorisation QR est terminé!")) + else: + display(Latex("C'est faux!")) + + return +