#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Thu Mar 17 19:11:52 2022
@author: jlebovits
"""
from __future__ import division
import sys
from copy import deepcopy
from src.scripts.pxs_runtime import myst, get_pxs_lang
from sympy.printing.latex import LatexPrinter
from re import sub
# import src.scripts.Mes_fctions.Mes_fctions_deterministes
# from src.scripts.Mes_fctions.Mes_fctions_deterministes import *
# import src.scripts.Mes_fctions.Mes_fctions_generalistes
# from src.scripts.Mes_fctions.Mes_fctions_generalistes import *
# import src.scripts.Mes_fctions.Mes_fctions_probabilistes
# from src.scripts.Mes_fctions.Mes_fctions_probabilistes import *
# import src.scripts.Mes_fctions.Mes_fctions_d_ecriture_Latex
# from src.scripts.Mes_fctions.Mes_fctions_d_ecriture_Latex import *
# import src.scripts.Mes_fctions.Mes_fctions_d_alg_generale
# from src.scripts.Mes_fctions.Mes_fctions_d_alg_generale import *
from sympy import *
import functools as fct
import math as m
import random as rd
import numpy as np
from re import sub, match
from src.scripts.Mes_fctions.Mes_fctions_d_alg_lineaire import randmatrixdiagonale, zeros
from src.scripts.Mes_fctions.Classes_Extensions import pxs_Poly
# from src.scripts.Mes_fctions.Mes_fctions_probabilistes_bis import pxsl_res_num
from sympy.stats import P, E, variance, Die, Normal, DiscreteUniform, Bernoulli, sample, Binomial, density, Normal, sample_iter, given
## ============== Copiées depuis Mes_fctions_generalistes_bis ==============
[docs]
def pxs_config(mul_symbol: str = "") -> dict:
"""
Build a configuration dictionary for LaTeX rendering, depending on the
current pyxisciences language settings.
The language is retrieved using `get_pxs_lang()` and affects some formatting
options, such as the decimal separator.
Parameters
----------
mul_symbol : str, optional
Multiplication symbol to be used in LaTeX output (default is "").
Returns
-------
dict
A dictionary containing LaTeX configuration options, including:
- ln_notation : bool
- mul_symbol : str
- order : str
- decimal_separator : str
- inv_trig_style : str
Examples
--------
>>> pxs_config()
{'ln_notation': True, 'mul_symbol': '', 'order': 'lex', ...}
"""
pxs_lang = get_pxs_lang()
if pxs_lang == 'fr':
return {
"ln_notation": True,
"mul_symbol": mul_symbol,
"order": "lex",
"decimal_separator": "comma",
"inv_trig_style": "full",
}
else:
return {
"ln_notation": True,
"mul_symbol": mul_symbol,
"order": "lex",
"decimal_separator": "dot",
"inv_trig_style": "full",
}
[docs]
def simplify_plus_minus(txt):
separators = r'(?:\s|\\;|\\displaystyle)*'
return sub(r'\+' + separators + r'-', '-', txt)
[docs]
def pxsl_add(*args, zeros = False):
config_standard = pxs_config()
ln_notation = config_standard["ln_notation"]
mul_symbol = config_standard["mul_symbol"]
dec_sep = config_standard["decimal_separator"]
inv_trig = config_standard["inv_trig_style"]
terms = [sympify(x) for x in args if (zeros or x)]
if terms == []: terms = [sympify(0)]
expr = Add(*terms, evaluate = False)
return LatexPrinter(dict(order = "none", ln_notation = ln_notation, mul_symbol = mul_symbol, decimal_separator = dec_sep, inv_trig_style = inv_trig))._print_Add(expr)
[docs]
def pxs_randint(mini, maxi, exclude = []):
"""
Returns a random integer between mini and maxi avoiding the element(s) in exclude.
Exclude can be an integer or a collection of integers.
"""
if isinstance(exclude, int):
exclude = exclude,
st = set(range(mini, maxi + 1)) - set(exclude)
return rd.choice(list(st))
## ==============
[docs]
def pxsl_pow(x, n=1, opt=0, displaystyle=True):
"""
Fr : Fonction permettant d'écrire le nombre x entouré de parenthèses
lorsqu'il est négatif ou irrationnel avec deux termes (par ex : 1+sqrt(2) ou 3sqrt(2))
Ne fonctionne pas pour des valeurs numériques non simplifiées (par ex : 1+3 ou 3*3/2)
En : Function that writes the number x surrounded by parentheses
when it is negative or irrational with two terms (e.g.: 1+sqrt(2) or 3sqrt(2))
Does not work for unsimplified numerical values (e.g.: 1+3 or 3*3/2)
Version 5
---------
13/02/25
Vérification
------
Auteur : Delphine
Vérificateurs : ??
Paramètres
----------
x : nombre ou expression
La base à élever à la puissance n
n : int, optional
L'exposant (défaut: 1)
opt : int, optional
Option de formatage (défaut: 0)
0: formatage standard
1: simplifie l'affichage pour x=1, x=0 ou n=1
2: simplifie davantage et renvoie une chaîne vide pour x=0
displaystyle : bool, optional
Si True, utilise \displaystyle pour les fractions (défaut: False)
Retour
------
str
retourne l'expression en latex
Fonction utilisée par
---------------------
pxsl_sum_matrix, pxsl_prod_scalar_matrix, pxsl_prod_matrix, pxsl_pow_matrix
"""
# Préparation de l'expression LaTeX selon le mode displaystyle
if displaystyle:
latex_x = r"\displaystyle " + latex(x)
else:
latex_x = latex(x)
# Cas où x est une expression (Add ou Mul) ou nombre négatif:
if isinstance(x, Add) or isinstance(x, Mul) :
if n == 1 :
return myst(r"""\left(\py{latex_x}\right)""", globals(), locals())
else:
return myst(r"""\left(\py{latex_x}\right)^{\py{n}}""", globals(), locals())
# Cas où x est un Rational
elif isinstance(x,Rational) and x.q!=1:
if n == 1 : # Pas de parenthèses quand n=1
if x < 0: # si la fraction est négative il faut des parenthèses
return myst(r"""\left(\py{latex_x}\right)""", globals(), locals())
else:
return myst(r"""\py{latex_x}""", globals(), locals())
else: # Parenthèses quand n différent de 1
return myst(r"""\left(\py{latex_x}\right)^{\py{n}}""", globals(), locals())
# Cas où x est un Symbol:
elif isinstance(x,Symbol):
if n == 1:
return myst(r"""\py{latex_x}""", globals(), locals())
else:
return myst(r"""\py{latex_x}^{\py{n}}""", globals(), locals())
# Cas où x est strictement négatif
elif x<0:
if n == 1:
return myst(r"""\left(\py{latex_x}\right)""", globals(), locals())
else:
return myst(r"""\left(\py{latex_x}\right)^{\py{n}}""", globals(), locals())
# Cas où x est un nombre positif ou nul
else:
# Option 0: formatage standard
if opt == 0:
if n == 1:
return myst(r"""\py{latex_x}""", globals(), locals())
else:
return myst(r"""\py{latex_x}^{\py{n}}""", globals(), locals())
# Option 1: simplifie pour x=0, x=1 ou n=1
elif opt == 1:
if x == 1 or x == 0 or n == 1:
return myst(r"""\py{latex_x}""", globals(), locals())
else:
return myst(r"""\py{latex_x}^{\py{n}}""", globals(), locals())
# Option 2: simplifie davantage, chaîne vide pour x=0
else: # opt == 2 ou autres valeurs
if x == 0:
return myst(r""" """, globals(), locals())
elif x == 1 or n == 1:
return myst(r"""\py{latex_x}""", globals(), locals())
else:
return myst(r"""\py{latex_x}^{\py{n}}""", globals(), locals())
################ EXEMPLES ##################
# pxsl_pow(3,2) retourne l'écriture latex de 3^{2}
# pxsl_pow(-3,2) retourne l'écriture latex de \left(-3\right)^{2}
# pxsl_pow(-3+sqrt(2),3) retourne l'écriture latex de \left(-3+\sqrt{2}\right)^{3}
# pxsl_pow(-3*sqrt(2),3) retourne l'écriture latex de \left(-3\sqrt{2}\right)^{3}
# pxsl_pow(3,Symbol('n')) retourne l'écriture latex de 3^{n}
# pxsl_pow(1,'n') retourne l'écriture latex de 1^n
# pxsl_pow(0,Symbol('n')) retourne l'écriture latex de 0^n
#
# x=Symbol('x')
# y=Symbol('y')
# pxsl_pow(x+y,Symbol('n')) retourne l'écriture latex de \left(x+y\right)^{n}
#
# x=Symbol('x')
# y=Symbol('y')
# pxsl_pow(x*y,Symbol('n')) retourne l'écriture latex de \left(x y\right)^{n}
[docs]
def pxsl_matrix(A, sepG="(", sepD=")", display=False, res_num=False):
"""
Return a LaTeX representation of a matrix with right-aligned entries.
This function converts a matrix into a nicely formatted LaTeX matrix.
By default, entries are displayed as raw values. Optional display modes
allow symbolic LaTeX rendering or numerical result formatting.
Parameters
----------
A : Matrix
The matrix to be displayed.
sepG : str, optional
Left delimiter of the matrix (default "(").
In English mode, it is automatically replaced by "[".
sepD : str, optional
Right delimiter of the matrix (default ")").
In English mode, it is automatically replaced by "]".
display : bool, optional
If True, matrix entries are rendered in LaTeX format.
res_num : bool, optional
If True (and `display=True`), entries are displayed as numerical results.
Returns
-------
str
A LaTeX string representing the formatted matrix.
Examples
--------
Basic usage with raw values:
>>> from sympy import Matrix
>>> A = Matrix([[1, 2], [3, 4]])
>>> pxsl_matrix(A)
'\\\\left(\\begin{array}{rr}1 & 2\\\\[0.3em]3 & 4\\end{array}\\\\right)'
Using custom delimiters:
>>> pxsl_matrix(A, sepG='[', sepD=']')
'\\\\left[\\begin{array}{rr}1 & 2\\\\[0.3em]3 & 4\\end{array}\\\\right]'
Displaying symbolic expressions in LaTeX:
>>> from sympy import symbols
>>> x = symbols('x')
>>> B = Matrix([[x, x**2], [1/x, 2]])
>>> pxsl_matrix(B, display=True)
'\\\\left(\\begin{array}{rr}x & x^{2}\\\\[0.3em]\\frac{1}{x} & 2\\end{array}\\\\right)'
Displaying numerical results (after evaluation):
>>> from sympy import Rational
>>> C = Matrix([[Rational(1, 2), Rational(3, 4)], [1, 2]])
>>> pxsl_matrix(C, display=True, res_num=True)
'\\\\left(\\begin{array}{rr}0.5 & 0.75\\\\[0.3em]1 & 2\\end{array}\\\\right)'
"""
[n,p]=A.shape
pxs_lang = get_pxs_lang()
config_standard = pxs_config()
if pxs_lang == "en" and sepG=='(':
sepG='['
if pxs_lang == "en" and sepD==')':
sepD=']'
expr=myst(r"""\left{{sepG}}\begin{array}{{{'r'*p}}}""",globals(),locals())
# lazy import to avoid circular import
if display and res_num:
from src.scripts.Mes_fctions.Mes_fctions_generalistes_bis import pxsl_num
for i in range(n):
if display:
if res_num:
expr+=myst(r"""{{pxsl_num(A[i,0])}}""",globals(),locals())
else:
expr+=myst(r"""{{latex(A[i,0], **config_standard)}}""",globals(),locals())
else:
expr+=myst(r""" {{A[i,0]}}""",globals(),locals())
for j in range(p-1):
if display:
if res_num:
expr+=myst(r""" &{{pxsl_num(A[i,1+j])}}""",globals(),locals())
else:
expr+=myst(r""" &{{latex(A[i,1+j], **config_standard)}}""",globals(),locals())
else:
expr+=myst(r""" &{{A[i,1+j]}}""",globals(),locals())
expr+=myst(r"""\\[0.3em]""")
expr+=myst(r"""\end{array}\right{{sepD}}""",globals(),locals())
return expr
################ EXEMPLES ##################
# pxsl_matrix(Matrix([[1,2,3],[2,-3,4]])) retourne
# l'expression latex : \left(\begin{array}{rrr}1&2&3\2&-3&4\\end{array}\right)
# soit la matrice (entourée de parenthèses) :
# 1 2 3
# 2 -3 4
# pxsl_matrix(Matrix([[1,2,3],[2,-3,4]]),'|','|') retourne
# l'expression latex : \left|\begin{array}{rrr}1&2&3\2&-3&4\\end{array}\right|
# soit le déterminant (matrice entourée de |) :
# 1 2 3
# 2 -3 4
# on peut aussi utiliser une forme de séparateur d'un côté et un autre de l'autre côté
[docs]
def pxsl_mat(A, sepG="(", sepD=")", display=False, frac = False, res_num=False, color = "blue", row = "", col = "", union = True, order = True, dec = 4):
"""
Return a LaTeX representation of a SymPy matrix.
The function converts the entries of a matrix into a LaTeX array enclosed
between customizable delimiters. It can display entries in normal mode,
display mode, fractional LaTeX form, numerical approximation, and can
highlight a row, a column, or their intersection.
Parameters
----------
A : sympy.Matrix
Matrix to be converted into LaTeX.
sepG : str, optional
Left delimiter. Default is ``"("``.
In English mode, the default parenthesis is automatically replaced by
``"["``.
sepD : str, optional
Right delimiter. Default is ``")"``.
In English mode, the default parenthesis is automatically replaced by
``"]"``.
display : bool, optional
If ``True``, each entry is written in display style using ``\ds``.
This is useful for entries containing fractions, sums, powers, or
other expressions that are more readable in display mode.
Default is ``False``.
frac : bool, optional
If ``True``, entries are rendered using SymPy's ``latex`` function.
This is useful when entries are rational numbers or symbolic
expressions.
Default is ``False``.
res_num : bool, optional
If ``True``, entries are rendered as numerical approximations using
``pxsl_num``.
This option has priority over ``display`` and ``frac``.
Default is ``False``.
color : str, optional
LaTeX color used to highlight selected entries.
Default is ``"blue"``.
row : int or str, optional
Index of the row to highlight. Row indices start at ``0``.
If no row should be highlighted, keep the default value ``""``.
col : int or str, optional
Index of the column to highlight. Column indices start at ``0``.
If no column should be highlighted, keep the default value ``""``.
order : bool, optional
If True, the order of the terms is preserved when coefficients are
litteral expressions.
Default is True
union : bool, optional
Determines how ``row`` and ``col`` are combined when both are given.
- If ``True``, all entries in the selected row or in the selected
column are highlighted.
- If ``False``, only the entry at the intersection of the selected
row and selected column is highlighted.
Default is ``True``.
Returns
-------
str
A LaTeX string representing the matrix.
Examples
--------
Basic matrix with default delimiters:
>>> from sympy import Matrix
>>> A = Matrix([[1, 2], [3, 4]])
>>> pxsl_matrix(A)
'\\left(\\begin{array}{rr} 1 & 2\\\\[6pt] 3 & 4\\\\[6pt]\\end{array}\\right)'
Use custom delimiters:
>>> pxsl_matrix(A, sepG="[", sepD="]")
'\\left[\\begin{array}{rr} 1 & 2\\\\[6pt] 3 & 4\\\\[6pt]\\end{array}\\right]'
Display fractions using LaTeX formatting:
>>> from sympy import Rational
>>> B = Matrix([[Rational(1, 2), Rational(2, 3)], [Rational(3, 4), 1]])
>>> pxsl_matrix(B, frac=True)
'\\left(\\begin{array}{rr}\\frac{1}{2} & \\frac{2}{3}\\\\[6pt]\\frac{3}{4} & 1\\\\[6pt]\\end{array}\\right)'
Use display style for larger entries:
>>> pxsl_matrix(B, display=True)
'\\left(\\begin{array}{rr}\\ds \\frac{1}{2} &\\ds \\frac{2}{3}\\\\[10pt]\\ds \\frac{3}{4} &\\ds 1\\\\[10pt]\\end{array}\\right)'
Display numerical approximations:
>>> pxsl_matrix(B, res_num=True)
'\\left(\\begin{array}{rr}0.5 & 0.67\\\\[6pt]0.75 & 1\\\\[6pt]\\end{array}\\right)'
Highlight a row:
>>> pxsl_matrix(A, row=1)
'\\left(\\begin{array}{rr} 1 & 2\\\\[6pt] \\textcolor{blue}{3} &\\textcolor{blue}{4}\\\\[6pt]\\end{array}\\right)'
Highlight a column:
>>> pxsl_matrix(A, col=0, color="red")
'\\left(\\begin{array}{rr} \\textcolor{red}{1} & 2\\\\[6pt] \\textcolor{red}{3} & 4\\\\[6pt]\\end{array}\\right)'
Highlight the union of a row and a column:
>>> pxsl_matrix(A, row=0, col=1, color="purple", union=True)
'\\left(\\begin{array}{rr}\\textcolor{purple}{1} &\\textcolor{purple}{2}\\\\[6pt] 3 &\\textcolor{purple}{4}\\\\[6pt]\\end{array}\\right)'
Highlight only the intersection of a row and a column:
>>> pxsl_matrix(A, row=0, col=1, color="purple", union=False)
'\\left(\\begin{array}{rr} 1 &\\textcolor{purple}{2}\\\\[6pt] 3 & 4\\\\[6pt]\\end{array}\\right)'
Notes
-----
The priority order for rendering entries is:
1. ``res_num=True``: numerical approximation with ``pxsl_num``;
2. ``display=True``: LaTeX display style with ``\ds``;
3. ``frac=True``: LaTeX formatting with SymPy's ``latex``;
4. otherwise: raw entry display.
Row and column indices start at ``0``.
"""
[n,p]=A.shape
pxs_lang = get_pxs_lang()
config_standard = pxs_config()
if order:
config_standard["order"] = "none"
if pxs_lang == "en" and sepG=='(':
sepG='['
if pxs_lang == "en" and sepD==')':
sepD=']'
expr=myst(r"""\left\py{sepG}\begin{array}{\py{'r'*p}}""",globals(),locals())
# lazy import to avoid circular import
if res_num:
from src.scripts.Mes_fctions.Mes_fctions_generalistes_bis import pxsl_num
for i in range(n):
res = False
if res_num and union and (row == i or col == 0):
expr+=myst(r"""\textcolor{{{color}}}{{{pxsl_num(A[i,0], dec = dec)}}}""",globals(),locals())
res = True
elif res_num and not union and (row == i and col == 0):
expr+=myst(r"""\textcolor{{{color}}}{{{pxsl_num(A[i,0], dec = dec)}}}""",globals(),locals())
res = True
elif res_num:
expr+=myst(r"""{{pxsl_num(A[i,0], dec = dec)}}""",globals(),locals())
res = True
if display and union and (row == i or col == 0):
expr+=myst(r"""\ds \textcolor{{{color}}}{{{latex(A[i,0], **config_standard)}}}""",globals(),locals())
res = True
elif display and not union and (row == i and col == 0):
expr+=myst(r"""\ds \textcolor{{{color}}}{{{latex(A[i,0], **config_standard)}}}""",globals(),locals())
res = True
elif display:
expr+=myst(r"""\ds {{latex(A[i,0], **config_standard)}}""",globals(),locals())
res = True
if frac and not res:
if union and (row == i or col == 0):
expr+=myst(r"""\textcolor{{{color}}}{{{latex(A[i,0], **config_standard)}}}""",globals(),locals())
elif not union and (row == i and col == 0):
expr+=myst(r"""\textcolor{{{color}}}{{{latex(A[i,0], **config_standard)}}}""",globals(),locals())
else:
expr+=myst(r"""{{latex(A[i,0], **config_standard)}}""",globals(),locals())
elif isinstance(A[i, 0], Add) and not res:
config_standard["fold_short_frac"] = True
if union and(row == i or col == 0):
expr+=myst(r""" \textcolor{{{color}}}{{{latex(A[i,0], **config_standard)}}}""",globals(),locals())
elif not union and(row == i and col == 0):
expr+=myst(r""" \textcolor{{{color}}}{{{latex(A[i,0], **config_standard)}}}""",globals(),locals())
else:
expr+=myst(r""" {{latex(A[i,0], **config_standard)}}""",globals(),locals())
elif not res:
if union and(row == i or col == 0):
expr+=myst(r""" \textcolor{{{color}}}{{{A[i,0]}}}""",globals(),locals())
elif not union and(row == i and col == 0):
expr+=myst(r""" \textcolor{{{color}}}{{{A[i,0]}}}""",globals(),locals())
else:
expr+=myst(r""" {{A[i,0]}}""",globals(),locals())
for j in range(p-1):
res = False
if res_num and union and (row == i or col == j+1):
expr+=myst(r""" &\textcolor{{{color}}}{{{pxsl_num(A[i,1+j], dec = dec)}}}""",globals(),locals())
res = True
elif res_num and not union and (row == i and col == j+1):
expr+=myst(r""" &\textcolor{{{color}}}{{{pxsl_num(A[i,1+j], dec = dec)}}}""",globals(),locals())
res = True
elif res_num:
expr+=myst(r""" &{{pxsl_num(A[i,1+j], dec = dec)}}""",globals(),locals())
res = True
if display and union and (row == i or col == j+1):
expr+=myst(r""" &\ds \textcolor{{{color}}}{{{latex(A[i,1+j], **config_standard)}}}""",globals(),locals())
res = True
elif display and not union and (row == i and col == j+1):
expr+=myst(r""" &\ds \textcolor{{{color}}}{{{latex(A[i,1+j], **config_standard)}}}""",globals(),locals())
res = True
elif display:
expr+=myst(r""" &\ds {{latex(A[i,1+j], **config_standard)}}""",globals(),locals())
res = True
if frac and not res:
if union and (row == i or col == j+1):
expr+=myst(r""" &\textcolor{{{color}}}{{{latex(A[i,1+j], **config_standard)}}}""",globals(),locals())
elif not union and (row == i and col == j+1):
expr+=myst(r""" &\textcolor{{{color}}}{{{latex(A[i,1+j], **config_standard)}}}""",globals(),locals())
else:
expr+=myst(r""" &{{latex(A[i,1+j], **config_standard)}}""",globals(),locals())
elif isinstance(A[i, 1 + j], Add) and not res:
config_standard["fold_short_frac"] = True
if union and(row == i or col == j + 1):
expr+=myst(r""" &\textcolor{{{color}}}{{{latex(A[i,1+j], **config_standard)}}}""",globals(),locals())
elif not union and(row == i and col == j + 1):
expr+=myst(r""" &\textcolor{{{color}}}{{{latex(A[i,1+j], **config_standard)}}}""",globals(),locals())
else:
expr+=myst(r""" &{{latex(A[i,1+j], **config_standard)}}""",globals(),locals())
elif not res:
if union and (row == i or col == j+1):
expr+=myst(r""" &\textcolor{{{color}}}{{{A[i,1+j]}}}""",globals(),locals())
elif not union and (row == i and col == j+1):
expr+=myst(r""" &\textcolor{{{color}}}{{{A[i,1+j]}}}""",globals(),locals())
else:
expr+=myst(r""" &{{A[i,1+j]}}""",globals(),locals())
if display:
if i == n-1:
expr+=myst(r""" """)
else:
expr+=myst(r"""\\[10pt]""")
else:
if i == n-1:
expr+=myst(r""" """)
else:
expr+=myst(r"""\\[6pt]""")
expr+=myst(r"""\end{array}\right\py{sepD}""",globals(),locals())
return expr
[docs]
def pxsl_sum_matrix(A,B,s="+",sepG='(',sepD=')'):
"""
Fonction permettant d'afficher le détail de la somme (ou la différence) de deux matrices
Version
-------
13/02/25
Vérification
------------
Auteur : Ronan - Delphine
Vérificateurs :
Paramètres
----------
A : Matrix
Première matrice de la somme
B : Matrix
Deuxième matrice de la somme
s : str
"+" par défaut pour réaliser une somme
"-" pour réaliser une différence
sepG : str
délimiteur gauche de la matrice
sepD : str
délimiteur droit de la matrice
Retour
------
str
retourne l'expression en latex
Fonction utilisée par
---------------------
aucune fonction pyxiscience
"""
[n,p]=A.shape
pxs_lang = get_pxs_lang()
if pxs_lang == "en" and sepG=='(':
sepG='['
if pxs_lang == "en" and sepD==')':
sepD=']'
expr=myst(r"""\left\py{sepG}\begin{array}{\py{'c'*p}}""",globals(),locals())
for i in range(n):
expr+=myst(r""" \py{A[i,0]}\py{s}""",globals(),locals())+pxsl_pow(B[i,0])
for j in range(p-1):
expr+=myst(r""" &\py{A[i,1+j]}\py{s}""",globals(),locals())+pxsl_pow(B[i,1+j])
expr+=myst(r"""\\""")
expr+=myst(r"""\end{array}\right\py{sepD}""",globals(),locals())
return expr
################ EXEMPLES ##################
# pxsl_sum_matrix(Matrix([[-1,0,3],[2,3,4]]),Matrix([[2,3,5],[2,-3,4]])) retourne
# l'expression latex : \left(\begin{array}{ccc} -1+2&0+3&3+5\2+2&3+\left(-3\right)&4+4\\end{array}\right)
# soit la matrice :
# -1+2 0+3 3+5
# 2+2 3+(-3) 4+4
# la commande pxsl_sum_matrix(Matrix([[-1,0,3],[2,3,4]]),Matrix([[2,3,5],[2,-3,4]]),"+") renvoie la même chose
# pxsl_sum_matrix(Matrix([[-1,0,3],[2,3,4]]),Matrix([[2,3,5],[2,-3,4]]),"-") retourne
# l'expression latex : \left(\begin{array}{-1-2&0-3&3-5\2-2&3-\left(-3\right)&4-4\\end{array}\right)
# soit la matrice :
# -1-2 0-3 3-5
# 2-2 3-(-3) 4-4
[docs]
def pxsl_prod_scalar_matrix(lamb,A,mult="times",sepG='(',sepD=')'):
"""
Fonction permettant d'afficher le détail du produit entre un scalaire et une matrice
Version
-------
13/02/25
Vérification
------------
Auteur : Ronan - Delphine
Vérificateurs :
Paramètres
----------
lamb : float
coefficient multiplicateur
A : Matrix
Matrice
mult : str
"times" par défaut, peut-être remplacé par "cdot" pour modifier le symbole multiplicatif
sepG : str
délimiteur gauche de la matrice
sepD : str
délimiteur droit de la matrice
Retour
------
str
retourne l'expression en latex
Fonction utilisée par
---------------------
aucune fonction pyxiscience
"""
[n,p]=A.shape
pxs_lang = get_pxs_lang()
if pxs_lang == "en" and sepG=='(':
sepG='['
if pxs_lang == "en" and sepD==')':
sepD=']'
expr=myst(r"""\left\py{sepG}\begin{array}{\py{'c'*p}}""",globals(),locals())
for i in range(n):
expr+=myst(r"""\py{lamb}\\py{mult}""",globals(),locals())+pxsl_pow(A[i,0])
for j in range(p-1):
expr+=myst(r"""&""")+myst(r"""\py{lamb}\\py{mult}""",globals(),locals())+pxsl_pow(A[i,1+j])
expr+=myst(r"""\\""")
expr+=myst(r"""\end{array}\right\py{sepD}""",globals(),locals())
return expr
################ EXEMPLES ##################
# pxsl_prod_scalar_matrix(2,Matrix([[1,2,-3],[2,3,4]])) retourne
# l'expression latex : \left(\begin{array}{ccc} 2\times1&2\times2&2\times\left(-3\right)\2\times2&2\times3&2\times4\\end{array}\right)
# donc la matrice :
# 2 x 1 2 x 2 2 x (-3)
# 2 x 2 2 x 3 2 x 4
# la commande pxsl_prod_scalar_matrix(2,Matrix([[1,2,-3],[2,3,4]]),"times") renvoie la même chose
# pxsl_prod_scalar_matrix(-2,Matrix([[1,2,-3],[2,3,4]])) retourne
# l'expression latex : \left(\begin{array}{ccc} -2\times1&-2\times2&-2\times\left(-3\right)\-2\times2&-2\times3&-2\times4\\end{array}\right)
# soit la matrice :
# -2 x 1 -2 x 2 -2 x (-3)
# -2 x 2 -2 x 3 -2 x 4
# pxsl_prod_scalar_matrix(2,Matrix([[1,2,-3],[2,3,4]]),"cdot") retourne
# l'expression latex : \left(\begin{array}{ccc} 2\cdot1&2\cdot2&2\cdot\left(-3\right)\2\cdot2&2\cdot3&2\cdot4\\end{array}\right)
# soit la matrice :
# -2.1 -2.2 -2.(-3)
# -2.2 -2.3 -2.4 avec le . correspondant à la commande latex cdot
# pxsl_prod_scalar_matrix('a',Matrix([[1,2,-3],[2,3,4]])) retourne
# l'expression latex : \left(\begin{array}{ccc} a\times1&a\times2&a\times\left(-3\right)\a\times2&a\times3&a\times4\\end{array}\right)
# soit la matrice :
# a x 1 a x 2 a x (-3)
# a x 2 a x 3 a x 4
# pxsl_prod_scalar_matrix(Symbol('a'),Matrix([[1,2,-3],[2,3,4]])) renvoie la même chose
[docs]
def pxsl_prod_matrix(A,B,mult="times",sepG='(',sepD=')'):
"""
Fonction permettant d'afficher le détail du produit entre deux matrices
Version
-------
13/02/25
Vérification
------------
Auteur : Ronan - Delphine
Vérificateurs :
Paramètres
----------
A : Matrix
Première matrice du produit
B : Matrix
Deuxième matrice du produit
mult : str
"times" par défaut, peut-être remplacé par "cdot" pour modifier le symbole multiplicatif
sepG : str
délimiteur gauche de la matrice
sepD : str
délimiteur droit de la matrice
Retour
------
str
retourne l'expression en latex
Fonction utilisée par
---------------------
aucune fonction pyxiscience
"""
[nA,pA]=A.shape
[nB,pB]=B.shape
pxs_lang = get_pxs_lang()
if pxs_lang == "en" and sepG=='(':
sepG='['
if pxs_lang == "en" and sepD==')':
sepD=']'
expr=myst(r"""\left\py{sepG}\begin{array}{\py{'c'*pB}}""",globals(),locals())
for i in range(nA):
expr+=myst(r"""\py{pxsl_pow(A[i,0])}\\py{mult}""",globals(),locals())+pxsl_pow(B[0,0])
for k in range(pA-1):
expr+=myst(r"""+""")+pxsl_pow(A[i,1+k])+myst(r"""\\py{mult}""",globals(),locals())+pxsl_pow(B[1+k,0])
for j in range(pB-1):
expr+=myst(r"""&""")+myst(r"""\py{pxsl_pow(A[i,0])}\\py{mult}""",globals(),locals())+pxsl_pow(B[0,1+j])
for k in range(pA-1):
expr+=myst(r"""+""")+pxsl_pow(A[i,1+k])+myst(r"""\\py{mult}""",globals(),locals())+pxsl_pow(B[1+k,1+j])
expr+=myst(r"""\\[10pt]""")
expr+=myst(r"""\end{array}\right\py{sepD}""",globals(),locals())
return expr
################ EXEMPLES ##################
# pxsl_prod_matrix(Matrix([[-1,-2,3],[2,0,4]]),Matrix([[2,3,5],[2,-3,4],[2,3,4]])) retourne
# l'expression latex : \left(\begin{array}{ccc} -1\times2+\left(-2\right)\times2+3\times2&-1\times3+\left(-2\right)\times\left(-3\right)+3\times3&-1\times5+\left(-2\right)\times4+3\times4\2\times2+0\times2+4\times2&2\times3+0\times\left(-3\right)+4\times3&2\times5+0\times4+4\times4\\end{array}\right)
# donc la matrice :
# -1x2+(-2)x2+3x2 -1x3+(-2)x(-3)+3x3 -1x5+(-2)x4+3x4
# 2x2+0x2+4x2 2x3+0x(-3)+4x3 2x5+0x4+4x4
# la commande pxsl_prod_matrix(Matrix([[-1,-2,3],[2,0,4]]),Matrix([[2,3,5],[2,-3,4],[2,3,4]]),"times") renvoie la même chose
# pxsl_prod_matrix(Matrix([[-1,-2,3],[2,0,4]]),Matrix([[2,3,5],[2,-3,4],[2,3,4]]),"cdot") retourne la matrice :
# -1.2+(-2).2+3.2 -1.3+(-2).(-3)+3.3 -1.5+(-2).4+3.4
# 2.2+0.2+4.2 2.3+0.(-3)+4.3 2.5+0.4+4.4 avec le . correspondant à la commande latex cdot
[docs]
def pxs_system_simpl(n=3,N="",opt="sys",max_coef=2,limit_sum=15):
"""
Fonction permettant de créer les matrices A et B d'un système linéaire en s'assurant de la simplicité de la solution
Par défaut, la matrice est de taille 3x3 et B un vecteur aléatoire, de composant entre 1 et 3, de dimension 3
La fonction est également utilisable pour générer A dans le cadre de l'inversion de matrice
Version
-------
25/03/25
Vérification
------------
Auteur : Delphine
Vérificateurs :
Paramètres
----------
n : int
Dimension de la matrice A
N : Matrix
Deuxième matrice du produit, solution du système
opt : char
"sys" : c'est un système, on renvoie A et B pour Ax=B
sinon : on renvoie seulement A
max_coef : int
on tire les opérations à réaliser sur les coefficients entre 1 et max_coef
limit_sum : int
si les coefficients de A et B dépassent la valeur de limit_sum la simulation est relancée
Retour
------
A,B
retourne les deux matrices du système AX=B
Fonction utilisée par
---------------------
pxs_commute_matrix
"""
# La matrice est N est copiée pour ne pas modifier la matrice originale
if N=="":
N=Matrix([rd.randint(-3,3) for i in range(n)])
A,B=eye(n),N.copy()
# La variable compte permet de compter le nombre d'éléments >15 en valeur absolue dans la matrice A
# Si un élément est supérieur à 15 en valeur absolue, on recommence. La variable compte est initialisée à 1 par défaut
compte=1
while compte!=0:
A,B=eye(n),N.copy()
# Tant que le nombre de 0 dans la matrice A est supérieur à la dimension -1, on continue
# on autorise donc pas de 0 pour une matrice 2x2, on autorise un 0 pour une matrice 3x3 etc...
while sum(1 for element in A if element == 0)>=n-1:
# on tire aléatoirement les deux lignes impliquées dans la relation
index=rd.sample([i for i in range(n)],2)
# on tire les coefficients multiplicateurs
lamb=[rd.choice([-1,1])*rd.randint(1,max_coef),rd.choice([-1,1])*rd.randint(1,max_coef)]
# On aura par exemple L1=2*L1+3*L2
A[index[0],:]=lamb[0]*A[index[0],:]+lamb[1]*A[index[1],:]
B[index[0],:]=lamb[0]*B[index[0],:]+lamb[1]*B[index[1],:]
compte=sum(1 for element in A if abs(element) >= limit_sum)+sum(1 for element in B if abs(element) >= limit_sum)
if opt=="sys":
return A,B
else:
return A
################ EXEMPLES ##################
# pxs_system_simpl() retournera par exemple les matrices :
# 2 -4 -2 -4
# A= 2 -5 -2 B= -6
# 2 -4 -4 0
# pour la solution
# 0
# x= 2
# -2
[docs]
def pxsl_ax(a,x=Symbol('x'),sign=" ",frac=True):
"""
Fonction permettant d'afficher l'expression ax en fonction des valeurs de a
Version
-------
23/09/25
Vérification
------------
Auteur : Delphine
Vérificateurs :
Paramètres
----------
a : numerique
x : Symbol ('x' par défaut)
si x=Symbol("val"), la valeur a est affichée dans tous les cas
sign : str
"" ou "+", "" par défaut, le symbole "+" indique qu'il faut écrire le signe '+'
devant l'expression.
frac : boolean
True : fraction écrite en mode math
False : fraction écrite a/b en ligne
Retour
------
str
retourne l'expression en latex
Fonction utilisée par
---------------------
pxsl_system_lin, pxsl_lines_op
"""
config_standard = pxs_config()
# on règle le cas a nul en premier
if a==0:
return myst(r""" """)
# on considère ensuite le cas a entier (qui est aussi considéré comme un Rational donc
# il ne faut pas inverser les if)
if isinstance(a,Integer):
if a==1:
return myst(r""" \py{sign} 1""",globals(),locals()) if x is None else myst(r""" \py{sign} \py{x}""",globals(),locals())
elif a==-1:
return myst(r""" - 1""",globals(),locals()) if x is None else myst(r""" - \py{x}""",globals(),locals())
elif a<0:
return myst(r""" \py{a}""",globals(),locals()) if x is None else myst(r""" \py{a}\py{x}""",globals(),locals())
else :
return myst(r"""\py{sign} \py{a}""",globals(),locals()) if x is None else myst(r"""\py{sign} \py{a}\py{x}""",globals(),locals())
# on considère ensuite le cas a Rational
if isinstance(a,Rational) and frac == True:
if a<0:
return myst(r""" -\frac{\py{abs(a.p)}}{\py{a.q}}""",globals(),locals()) if x is None else myst(r""" -\frac{\py{abs(a.p)}}{\py{a.q}}\py{x}""",globals(),locals())
else:
return myst(r""" \py{sign}\frac{\py{a.p}}{\py{a.q}}""",globals(),locals()) if x is None else myst(r""" \py{sign}\frac{\py{a.p}}{\py{a.q}}\py{x}""",globals(),locals())
if isinstance(a,Rational) and frac == False:
if a.p==-1:
return myst(r""" - 1/\py{a.q}""",globals(),locals()) if x is None else myst(r""" - \py{x}/\py{a.q}""",globals(),locals())
elif a.p==1:
return myst(r""" \py{sign}1/\py{a.q}""",globals(),locals()) if x is None else myst(r""" \py{sign}\py{x}/\py{a.q}""",globals(),locals())
elif a<0:
return myst(r""" -\py{abs(a.p)}/\py{a.q}""",globals(),locals()) if x is None else myst(r""" -\py{abs(a.p)}\py{x}/\py{a.q}""",globals(),locals())
else:
return myst(r""" \py{sign}\py{a.p}/\py{a.q}""",globals(),locals()) if x is None else myst(r""" \py{sign}\py{a.p}\py{x}/\py{a.q}""",globals(),locals())
# on ferme avec le traitement pour tout nombre car certains calculs envoient un int
# non reconnu dans les conditionnels précédants
if a==1:
return myst(r""" \py{sign} 1""",globals(),locals()) if x is None else myst(r""" \py{sign} \py{x}""",globals(),locals())
elif a==-1:
return myst(r""" - 1""",globals(),locals()) if x is None else myst(r""" - \py{x}""",globals(),locals())
if isinstance(a, Add):
latex_a = latex(a, **config_standard)
if x:
return myst(r"""\py{sign} \left(\py{latex_a}\right)\py{x}""", globals(), locals())
elif a.could_extract_minus_sign() or match(r"\s*-", latex_a):
txt = myst(r"""\py{sign} \py{latex_a}""", globals(), locals())
txt = sub(r'\+(?:\s|\\;|\\displaystyle)*-', '-', txt)
return txt
else:
return myst(r"""\py{sign} \py{latex_a}""", globals(), locals())
try:
if a<0:
return myst(r""" \py{a}""",globals(),locals()) if x is None else myst(r""" \py{a}\py{x}""",globals(),locals())
else:
return myst(r"""\py{sign} \py{a}""",globals(),locals()) if x is None else myst(r"""\py{sign} \py{a}\py{x}""",globals(),locals())
except:
txt = myst(r"""\py{sign} \py{latex(a, **config_standard)}""",globals(),locals()) if x is None else myst(r"""\py{sign} \py{latex(a, **config_standard)}\py{x}""",globals(),locals())
txt = sub(r'\+(?:\s|\\;|\\displaystyle)*-', '-', txt)
return txt
################ EXEMPLES ##################
# pxsl_ax(2) retourne
# l'expression latex : 2x
#
# pxsl_ax(2,Symbol('y')) retourne
# l'expression latex : 2y
#
# pxsl_ax(2,Symbol('L_{'+str(1)+'}')) retourne
# l'expression latex : 2L_{1}
#
# pxsl_ax(1,Symbol('L_{'+str(1)+'}')) retourne
# l'expression latex : L_{1}
#
# pxsl_ax(0,Symbol('L_{'+str(1)+'}')) retourne ""
[docs]
def pxsl_double_matrix(A,B,listeMat=[],opt='sep', display = False):
"""
Fonction permettant d'afficher un système linéaire Ax=B
Version
-------
13/02/25
Vérification
------------
Auteur : Delphine
Vérificateurs :
Paramètres
----------
A : Matrix
B : Matrix
listeMat : liste
permet d'envisager l'ajout de matrices supplémentaires
opt : str ('sep' par défaut)
permet de préciser la présentation des matrices
'sep' : les deux matrices sont présentées côte à côte entourées de parenthèses
'ext' : les deux matrices sont présentées en matrice étendue séparée par
Retour
------
str
retourne l'expression en latex
Fonction utilisée par
---------------------
pxsl_resol_system
"""
if opt=="sep":
expr=myst(r"""\begin{array}{cc}""")
expr+=pxsl_matrix(A, display = display)+pxsl_matrix(B, display = display)+myst(r"""\end{array}""")
return expr
else:
expr=myst(r"""\begin{array}{c:c}""")
expr+=pxsl_matrix(A,'(','.', display = display)+myst(r"""&""",globals(),locals())+pxsl_matrix(B,".",')', display = display)+myst(r"""\end{array}""")
return expr
################ EXEMPLES ##################
# pxsl_double_matrix(Matrix([[1,2,3],[2,-3,4]]),Matrix([[1,2,3],[2,-3,4]])) retourne
# l'expression latex : \begin{array}{ccc}\left(\begin{array}{rrr}1&2&3\2&-3&4\\end{array}\right)&&\left(\begin{array}{rrr}1&2&3\2&-3&4\\end{array}\right)\end{array}
# c'est-à-dire deux matrices entourées de parenthèses et placées l'une à côté de l'autre
# pxsl_double_matrix(Matrix([[1,2,3],[2,-3,4]]),Matrix([[1,2,3],[2,-3,4]]),opt="sep") retourne la même chose
# ATTENTION : ne pas oublier opt= car il y a un autre paramêtre au milieu (la liste)
#
# pxsl_double_matrix(Matrix([[1,2,3],[2,-3,4]]),Matrix([[1,2,3],[2,-3,4]]),opt="ext") retourne
# l'expression latex : \begin{array}{c:c}\left(\begin{array}{rrr}1&2&3\2&-3&4\\end{array}\right.&\left.\begin{array}{rrr}1&2&3\2&-3&4\\end{array}\right)\end{array}
# c'est-à-dire deux matrices réécrite en matrice étendue séparées par des pointillés
#
# EXTENSION A VENIR : écrire une opération entre les deux matrices, possibilités d'écrire plus de 2 matrices
[docs]
def pxsl_system_lin(A, B, x = 'x', frac = True):
"""
Construct a LaTeX representation of a linear system.
The function formats a linear system of equations defined by a coefficient
matrix `A` and a right-hand side vector `B` into a LaTeX `array` environment.
Each equation is written as a linear combination of symbolic variables
followed by its corresponding constant term.
Parameters
----------
A : Matrix
Coefficient matrix of the linear system.
B : Matrix
Right-hand side vector of the system.
x : str, optional
Base name of the unknown variables (default is `"x"`), producing
variables of the form `x_1, x_2, ..., x_n`.
frac : bool, optional
If True, coefficients are displayed as fractions when appropriate.
If False, coefficients are displayed in a simplified inline form.
Returns
-------
Any
A symbolic object representing the LaTeX code of the linear system
formatted as an `array`.
Examples
--------
>>> pxsl_system_lin(A, B)
'\\\\left\\{ \\\\begin{array}{rcl} ... \\\\end{array}\\\\right.'
"""
[n,p]=A.shape
# Permet de créer le vecteur des x_i en fonction de la dimension de A
vect_x=Matrix([Symbol(x+'_1')])
for i in range(p-1):
vect_x=vect_x.row_join(Matrix([Symbol(x+'_'+str(i+2))]))
expr=myst(r"""\left\lbrace \begin{array}{rcl} """)
for i in range(n):
if A[i, :].is_zero_matrix:
expr += "0"
# Gère l'affichage du premier terme non nul sans le '+' devant
if A[i, 0] != 0:
expr+=pxsl_ax(A[i,0],vect_x[0], frac = frac)
sign="+"
else:
sign=""
for j in range(1,p):
if A[i,j]!=0:
expr+=pxsl_ax(A[i,j],vect_x[j],sign, frac = frac)
sign="+"
rhs = myst(r"""\py{B[i].p}/\py{B[i].q}""", globals(), locals()) if (isinstance(B[i], Rational) and B[i].q != 1 and not frac) else latex(B[i])
expr+=myst(r""" &=&\py{rhs}""",globals(),locals())+myst(r"""\\[0.3em]""")
expr+=myst(r"""\end{array}\right.""")
return expr
################ EXEMPLES ##################
# pxsl_system_lin(Matrix([[2,3],[1,4]]),Matrix([1,1])) renvoie
# l'expression latex \left{ \begin{array}{rcr} 2x_1+ 3x_2&=&1\\ x_1+ 4x_2&=&1\\end{array}\right.
#
# pxsl_system_lin(Matrix([[2,3,0],[1,4,-1],[-2,3,5]]),Matrix([1,1,0])) renvoie
# l'expression latex \left{ \begin{array}{rcr} 2x_1+ 3x_2&=&1\\ x_1+ 4x_2-x_3&=&1\\-2x_1+ 3x_2+ 5x_3&=&0\\end{array}\right.
[docs]
def pxsl_lines_op(n, listOp, opt="sys", frac = True):
"""
Construct a LaTeX array describing elementary row (line) operations.
The function generates a symbolic LaTeX representation of a sequence of
elementary row operations applied to a system or a matrix. Each operation
is displayed line by line using an `array` environment, with arrows and
linear combinations formatted according to the current language settings
(French or English).
Parameters
----------
n : int
Number of rows of the system or matrix.
listOp : list
List of elementary row operations. Each element of the list is expected
to be a tuple of the form `(a, i, b, j)` representing an operation applied
to row `i` using row `j`:
- if `a == 0`, rows `i` and `j` are swapped;
- otherwise, the operation corresponds to
`row_i ← a * row_i + b * row_j`.
Row indices are assumed to be 1-based.
opt : str, optional
Output option (currently kept for interface consistency; default is
`"sys"`).
frac : bool, optional
If True, coefficients are displayed as fractions when appropriate.
If False, coefficients are displayed in a simplified inline form.
Returns
-------
Any
A symbolic object representing the LaTeX code of an `array` environment
describing the row operations.
Examples
--------
>>> pxsl_lines_op(
... n=3,
... listOp=[(1, 1, -2, 2), (0, 2, 1, 3)]
... )
'\\\\begin{array}{ccc} ... \\\\end{array}'
"""
espace = ""
expr = myst(r""" \begin{array}{ccc}""")
pxs_lang = get_pxs_lang()
for j in range(n):
for i in range(len(listOp)):
if j==listOp[i][1]-1:
a,b=listOp[i][0],listOp[i][2]
ind1,ind2=listOp[i][1],listOp[i][3]
var1 = Symbol('L_{'+str(ind1)+'}') if pxs_lang == "fr" else Symbol('R_{'+str(ind1)+'}')
var2 = Symbol('L_{'+str(ind2)+'}') if pxs_lang == "fr" else Symbol('R_{'+str(ind2)+'}')
if a==0:
expr+=myst(r""" \fr{L}\en{R}_{\py{ind1}}& \leftrightarrow &""",globals(),locals())+pxsl_ax(a,var1,"",frac = frac)+pxsl_ax(b,var2,"")+myst(r"""\py{espace}""",globals(),locals())
else:
expr+=myst(r""" \fr{L}\en{R}_{\py{ind1}}& \leftarrow &""",globals(),locals())+pxsl_ax(a,var1,"",frac = frac)+pxsl_ax(b,var2,"+",frac = frac)+myst(r"""\py{espace}""",globals(),locals())
if i==len(listOp)-1:
expr+=myst(r""" \\[0.3em]""")
expr+=myst(r"""\end{array}""")
return expr
################ EXEMPLES ##################
# pxsl_lines_op(2,[2,1,3,2]) retourne
# l'expression latex : \begin{array}{c}L_{1} \leftarrow 2L_{1}+ 3L_{2}\\\end{array}\
#
# pxsl_lines_op(2,[0,1,3,2]) retourne
# l'expression latex : \begin{array}{c}L_{1} \leftarrow 3L_{2}\\\end{array}\
#
# pxsl_lines_op(2,[0,1,-1,2]) retourne
# l'expression latex : \begin{array}{c}L_{1} \leftarrow -3L_{2}\\\end{array}\
#
[docs]
def pxsl_resol_system(listA,listB=[],listOp=[],x='x',method="sys",view="sep", detail = "on"):
"""
Fonction qui permet d'écrire chaque étape de la résolution d'un problème impliquant des manipulations de lignes
Version
-------
23/09/25
Vérification
------------
Auteur : Ronan - Delphine
Vérificateurs :
Paramètres
----------
listA : list
contient la liste des matrices A successives utilisées lors de la résolution
listB : list
contient la liste des vecteurs (système) ou matrice (inversion) B successives utilisées lors de la résolution
listOp : liste de liste
chaque liste de la liste contient 4 éléments [a, ind1,b,ind2] permettant de réaliser le calcul sur la ligne d'indice ind1 a*L_ind1+b*L_ind2
x : s.Symbol ('x' par défaut)
utilisé comme variable dans le cas de la résolution d'un système
method : str ('sys' par défaut)
"sys" : résolution d'un système
"mat" : inversion d'une matrice
"ech" : échelonnage d'une matrice
view : str ("sep" par défaut)
"sep" : les deux matrices sont représentées côte à côté
"ext" : représente la matrice étendue A|B
Retour
------
str
retourne l'expression en latex
Fonction utilisée par
---------------------
Aucune fonction pyxiscience
"""
listA, listB, listOp = pxs_regroupe_ligne(listA, listB, listOp)
expr=""
for i in range(len(listA)):
if i==0:
if method=="sys":
expr=myst(r"""\begin{array}{cl} """)+myst(r"""&""")+ pxsl_system_lin(listA[i],listB[i],x)+myst(r"""\\ \\""")
elif method=="ech":
expr=myst(r"""\begin{array}{cc} """)+myst(r"""&""")+ pxsl_matrix(listA[i])+myst(r"""\\ \\""")
else:
expr=myst(r"""\begin{array}{cc} """)+myst(r"""&""")+ pxsl_double_matrix(listA[i],listB[i],opt=view)+myst(r""" \\ \\""")
if i!=0:
if detail == "on":
if method=="sys":
expr+=pxsl_lines_op(listA[0].shape[0],listOp[i])+myst(r""" & """)+pxsl_system_lin(listA[i],listB[i],x)+myst(r"""\\ \\""")
elif method=="ech":
expr+=pxsl_lines_op(listA[0].shape[0],listOp[i],opt="ech")+myst(r""" & """)+pxsl_matrix(listA[i])+myst(r"""\\ \\""")
else:
expr+=pxsl_lines_op(listA[0].shape[0],listOp[i])+myst(r""" & """)+pxsl_double_matrix(listA[i],listB[i],opt=view)+myst(r"""\\ \\""")
else:
if method=="sys":
expr+=myst(r""" & """)+pxsl_system_lin(listA[i],listB[i],x)+myst(r"""\\ \\""")
elif method=="ech":
expr+=myst(r""" & """)+pxsl_matrix(listA[i])+myst(r"""\\ \\""")
else:
expr+=myst(r""" & """)+pxsl_double_matrix(listA[i],listB[i],opt=view)+myst(r"""\\ \\""")
expr+=myst(r"""\end{array}""")
return expr
################ EXEMPLES ##################
# Soit les variables de départ :
# listA=[Matrix([ [-4, 2], [ 1, -1]]), Matrix([ [-2, 1], [ 1, -1]]), Matrix([ [-2, 1], [ 0, -1]]), Matrix([ [-2, 0], [ 0, -1]]), Matrix([ [-1, 0], [ 0, -1]]), Matrix([ [1, 0], [0, -1]]), Matrix([ [1, 0], [0, 1]])]
# listB=[Matrix([ [-6], [ 0]]), Matrix([ [-3], [ 0]]), Matrix([ [-3], [-3]]), Matrix([ [-6], [-3]]), Matrix([ [-3], [-3]]), Matrix([ [ 3], [-3]]), Matrix([ [3], [3]])]
# listOp=[[0, 0, 0, 0], [Rational(1,2), 1, 0, 0], [2, 2, 1, 1], [1, 1, 1, 2], [Rational(1,2), 1, 0, 0], [-1, 1, 0, 0], [-1, 2, 0, 0]]
#
# pxsl_resol_system(listA,listB,listOp,method='sys') retourne l'expression latex pour représenter
# la résolution du système.
#
[docs]
def pxs_reduce_pgcd(A, B, listA, listB, listOp):
"""
Fonction permettant de diviser lignes des matrices A et B lorsque leur pgcd est différent de 1
Version
-------
23/09/25 (modifié 14/10/25)
Vérification
------------
Auteur : Delphine
Vérificateurs :
Paramètres
----------
A : Matrix
B : Matrix
listA : list
liste de matrices, permet de retrouver les différentes transformations de la matrice A
listB : list
liste de matrices, permet de retrouver les différentes transformations de la matrice B
listOp : list
Chaque élément de la liste est une liste de 4 éléments :
[facteur multiplicatif de la ligne i, indice de la ligne i, facteur multiplicatif de la ligne j, indice de la ligne j]
Retour
------
liste, liste, liste
retourne les listes actualisées de l'opération de permutation
Fonction utilisée par
---------------------
pxs_steps_invert_matrix
"""
[n, p] = A.shape
for i in range(n):
# Extraction des numérateurs pour la ligne i de A
A_row_nums = [elem.numerator if isinstance(elem, Rational) else elem for elem in A[i, :]]
# Extraction des numérateurs pour la ligne i de B
B_row_nums = [elem.numerator if isinstance(elem, Rational) else elem for elem in B[i, :]]
# Calcul du PGCD pour les numérateurs
if len(A_row_nums) > 0 and len(B_row_nums) > 0:
# Utiliser sympy_gcd pour gérer les objets sympy
pg_A = A_row_nums[0]
for val in A_row_nums[1:]:
pg_A = gcd(pg_A, val)
pg_B = B_row_nums[0]
for val in B_row_nums[1:]:
pg_B = gcd(pg_B, val)
pg = gcd(pg_A, pg_B)
# Vérifier si pg est différent de 1
if pg != 1 and pg != 0:
# Division de la ligne par le PGCD
A[i, :] = A[i, :] / pg
B[i, :] = B[i, :] / pg
# Mise à jour des listes
listA.append(A.copy())
listB.append(B.copy())
for j in range(n):
if j == i:
# L'opération est L_{i+1} -> 1/pg * L_{i+1} d'où la liste [1/pg, i+1, 0, 0]
try:
listOp.append([Rational(1, pg), i+1, 0, 0])
except:
listOp.append([1/ pg, i+1, 0, 0])
return listA, listB, listOp
################ EXEMPLES ##################
# Exemple pour une matrice avec PGCD = 2
# pxs_reduce_pgcd(Matrix([[2,4,6],[1,2,3]]),Matrix([2,3,4]),[Matrix([[2,4,6],[1,2,3]])],[Matrix([2,3,4])],[[0,0,0,0]]) retourne
# listA = [Matrix([ [2, 4, 6], [1, 2, 3]]), Matrix([ [1, 2, 3], [1, 2, 3]])]
# listB = [Matrix([ [2], [3], [4]]), Matrix([ [1], [3], [4]])]
# listOp = [[0, 0, 0, 0], [1/2, 1, 0, 0]]
#
[docs]
def pxs_steps_invert_matrix(A,B,x='x',method="sys",view="sep", detail = "on"):
"""
Fonction permettant de stocker toutes les étapes de la résolution d'un système ou l'inversion d'une matrice
Version
-------
23/09/25
Vérification
------------
Auteur : Ronan - Delphine
Vérificateurs :
Paramètres
----------
A : Matrix
B : Matrix
x : Symbol ('x' par défaut)
liste de matrices, permet de retrouver les différentes transformations de la matrice A
method : str
"sys" : pour afficher la résolution d'un système
listOp : list
Chaque élément de la liste est une liste de 4 éléments :
[facteur multiplicatif de la ligne i, indice de la ligne i, facteur multiplicatif de la ligne j, indice de la ligne j]
Retour
------
liste, liste, liste
retourne les listes actualisées de l'opération de permutation
Fonction utilisée par
---------------------
Aucune fonction pyxiscience
"""
[n,p]=A.shape
nmin=min(n,p)
listA,listB=[A.copy()],[B.copy()]
listOp=[[0,0,0,0]]
# On vérifie le pgcd de chaque ligne pour simplifier
listA,listB,listOp=pxs_reduce_pgcd(A,B,listA,listB,listOp)
for k in range(nmin):
# On cherche la ligne pivot
r=-1
for i in range(k,n):
if r<0 and A[i,k]!=0:
r=i
if r>k:
# On met la ligne pivot en haut si elle ne l'est pas
A.row_swap(k, r)
B.row_swap(k,r)
listA.append(A.copy())
listB.append(B.copy())
for j in range(n):
if j==r:
listOp.append([0,r+1,1,1+k])
if r>=k:
# On élimine les lignes differentes de k
for j in range(n):
if A[j,k]!=0 and j!=k:
pg=gcd(A[k,k],A[j,k])
cpAjk=A[j,k]
cpAkk=A[k,k]
try:
A[j,:]=abs(cpAkk)*A[j,:]/pg- abs(cpAjk)*A[k,:]/pg*np.sign( cpAjk* cpAkk)
except:
A[j,:]=cpAkk*A[j,:]/pg- cpAjk*A[k,:]/pg
try:
B[j,:]=abs(cpAkk)*B[j,:]/pg- abs(cpAjk)*B[k,:]/pg*np.sign( cpAjk* cpAkk)
except:
B[j,:]=cpAkk*B[j,:]/pg- cpAjk*B[k,:]/pg
listA.append(A.copy())
listB.append(B.copy())
for l in range(n):
if l==j:
try:
listOp.append([abs(cpAkk)/pg,j+1,-abs(cpAjk)/pg*np.sign( cpAjk* cpAkk),k+1])
except:
listOp.append([cpAkk/pg,j+1,-cpAjk/pg,k+1])
# On vérifie le pgcd de chaque ligne pour simplifier
listA,listB,listOp=pxs_reduce_pgcd(A,B,listA,listB,listOp)
# On exprime les solutions sous la forme finale
for i in range(nmin):
if A[i,i]!=0 and A[i,i]!=1:
for j in range(B.shape[1]):
try:
B[i,j]=Rational(B[i,j],A[i,i])
except:
B[i,j]=B[i,j]/A[i,i]
cpAi=A[i,i]
A[i,:]=A[i,:]/cpAi
listA.append(A.copy())
listB.append(B.copy())
# Stockage de l'opération
for j in range(nmin):
if j==i and cpAi!=0:
try:
listOp.append([Rational(1,cpAi),i+1,0,0])
except:
listOp.append([1/cpAi,i+1,0,0])
elif j==i and cpAi == -1:
listOp.append([-1,i+1,0,0])
expr = pxsl_print_operations([listA, listB], listOp, method, x, view, detail)
return expr
################ EXEMPLES ##################
# pxs_steps_invert_matrix(Matrix([[2,3,0],[1,4,-1],[-2,3,5]]),Matrix([1,1,0])) renvoie
# l'expression latex qui permet de décrire toute la résolution du système.
#
# pxs_steps_invert_matrix(Matrix([[2,3,0],[1,4,-1],[-2,3,5]]),Matrix([[1,0,0],[0,1,0],[0,0,1]]),method="mat") renvoie
# l'expression latex qui permet de décrire l'inversion de la matrice avec les matrices mises côte à côte.
#
# pxs_steps_invert_matrix(Matrix([[2,3,0],[1,4,-1],[-2,3,5]]),Matrix([[1,0,0],[0,1,0],[0,0,1]]),method="mat",view="ext") renvoie
# l'expression latex qui permet de décrire l'inversion de la matrice avec la matrice étendue.
[docs]
def pxs_LU_decomposition(A, view = "sep", detail = "on", name_matrix = " ", PLU = False):
"""
Details the steps of LU factorization for a square matrix A.
Version
-------
26/12/25
Authors
------------
Author: Raphaël
Checked by:
Arguments
----------
A: Matrix, the matrix to factorize
method: str, display option
view: str, display option
detail: str, "on" to get additional details
name_matrix: str, name the matrix is referred as
Returns
------
text (str), L (Matrix), U (Matrix) (if PLU = False)
text (str), P (Matrix), L (Matrix), U (Matrix) (if PLU = True)
text: steps of the computation
(P,) L, U: Matrixes such that (P)A = LU if they exist. None, None otherwise
Function used by
---------------------
No pyxiscience function
Examples
--------
>>> A = Matrix([[1, 2, 1], [3, 10, 3], [-2, -8, 5]]) # LU factorization exists
>>> resol, L, U = pxs_LU_decomposition(A.copy())
>>> B = Matrix([[1, 2, 1], [3, 6, -1], [1, 1, 1]]) # LU factorization does not exist
>>> resol, P, L, U = pxs_LU_decomposition(A4.copy(), name_matrix = "B", PLU = True) # L, U = None, None
"""
pxs_lang = get_pxs_lang()
[n,p] = A.shape
# Check if square
if n != p:
err = "La matrice fournie doit être carrée" if pxs_lang == "fr" else "Input matrix must be square"
raise ValueError(err)
B = Matrix(np.eye(n).astype(np.int64))
listA, listB=[A.copy()], [B.copy()]
if PLU:
P = Matrix(np.eye(n).astype(np.int64))
listP = [P.copy()]
listOp = [[0,0,0,0]]
k = 0
ok = True
while k < n - 1 and ok: # for each column except the last one and while it is possible
if A[k, k] != 0:
# Handle subdiagonal coefficients on the (k+1)-th column
for j in range(k + 1, n):
if A[j,k]!=0:
coeff = A[j, k] / A[k, k]
A[j, :] -= coeff * A[k, :]
B[j, k] = coeff
listA.append(A.copy())
listB.append(B.copy())
if PLU:
listP.append(P.copy())
listOp.append([1, j + 1, -coeff, k + 1])
k += 1
# below : cases where the k-th pivot is 0
elif np.any(A[k+1:, k]): # else A[k:, k] == 0, all coeff under the pivot are 0, nothing to do
if not PLU:
# looking above for another line to use as a "pivot"
r = k - 1
while r >= 0 and not (A[r, k] != 0 and not np.any(A[r, :k])):
r -= 1
if r == -1: # nothing found
ok = False
else: # using r-th row to eliminate the subdiagonal coefficients on the (k+1)-th column
for j in range(k + 1, n):
if A[j,k]!=0:
coeff = A[j, k] / A[r, k]
A[j, :] -= coeff * A[r, :]
B[j, r] = coeff
listA.append(A.copy())
listB.append(B.copy())
listOp.append([1, j + 1, -coeff, r + 1])
k += 1
else: # PLU case, looking for a non-zero coeff below in order to swap lines
r = np.where(np.ravel(A[k + 1:, k]) != 0)[0][0] + k + 1 # row of first non-zero coeff. on column k
A.row_swap(k, r)
B[k, :k], B[r, :k] = B[r, :k], B[k, :k]
P.row_swap(k, r)
listA.append(A.copy())
listB.append(B.copy())
listP.append(P.copy())
listOp.append([0, k + 1, 1, r + 1])
else:
k += 1
list_mat = [listA, listB]
if PLU: list_mat.append(listP)
text = myst(r"""\begin{equation*}""", locals(), globals())
operations = pxsl_print_operations(list_mat, listOp, method = "mat", view = view, detail = detail)
text += operations
text += myst(r"""\end{equation*}""", locals(), globals())
if PLU:
return {"all": text, "resol": operations, "P": listP[-1], "L": listB[-1], "U": listA[-1]}
elif ok: # no permutation, and LU factorization exists
return {"all": text, "resol": operations, "L": listB[-1], "U": listA[-1]}
else: # no permutation, and LU factorization does not exist
if pxs_lang == "fr":
negative_conclusion = myst(r"""
On ne peut pas poursuivre la réduction sans permutation de lignes, la matrice ${{name_matrix}}$ ne possède donc pas de décomposition $LU$.""", locals(), globals())
if pxs_lang == "en":
negative_conclusion = myst(r"""
A line permutation would be required at this stage, hence the matrix ${{name_matrix}}$ does not admit an $LU$ factorization.""", locals(), globals())
text += negative_conclusion
return {"all": text, "resol": operations, "L": listB[-1], "U": listA[-1]}
[docs]
def pxs_matrix_no_LU(p, no_first = True):
"""
Constructs a random sympy (p x p) matrix with integer coefficients,
for which the LU decomposition without pivoting does not exist.
Strategy: a random index k is chosen in {1, ..., p-1}, then the
k-th leading principal submatrix A_k is made singular by replacing
its last row with a linear combination of the k-1 previous rows.
Coefficients of the full matrix are drawn from [-5, 5], except for
the constructed row which may exceed these bounds.
Parameters
----------
p : int
Matrix size (must be >= 2).
no_first : True for avoiding a 0 as first coefficient
Returns
-------
Matrix
A sympy (p x p) matrix with no LU decomposition.
"""
if p < 2:
raise ValueError("p must be an integer >= 2.")
no_first = no_first and p > 2
# Randomly choose which leading principal minor to annihilate (1-based)
k = rd.randint(1 + no_first, p - 1)
coeffs = [[rd.randint(-5, 5) for _ in range(p)] for _ in range(p)]
if k == 1:
# Trivial case: force a[0][0] = 0
coeffs[0][0] = 0
else:
# Make A_k singular by replacing its last row with a linear
# combination of the k-1 previous rows.
# Coefficients lambda are chosen in {-2, -1, 1, 2} to stay discrete.
lambdas = [rd.choice([-2, -1, 1, 2]) for _ in range(k - 1)]
for j in range(k):
val = sum(lambdas[i] * coeffs[i][j] for i in range(k - 1))
coeffs[k - 1][j] = val
return Matrix(coeffs)
[docs]
def pxsl_print_operations(list_mat, listOp=[], method = "sys", x = "x", view="sep", detail = "on", frac = True):
"""
Displays each step of the resolution for a problem involving line operations.
This function is meant to replace pxsl_resol_system
Version
-------
06/01/26
Authors
------------
Auteur : Ronan - Delphine - Raphaël
Vérificateurs :
Arguments
----------
list_mat : list of lists
each element is a list of successive matrices appearing in the resolution
listOp : liste of lists
each sublist contains 4 elements [a, ind1, b, ind2] describing the following operation:
L(ind1) <- a * L_ind1 + b * L_ind2
x : s.Symbol ('x' par défaut)
determines the name of the variables in the system
method : str ('sys' by default)
"sys" : system form
"mat" : matrix form
view : str ("sep" by default)
"sep" : matrices are displayed side-by-side
"ext" : extended matrix A1|A2|...|An
frac : bool, optional
If True, coefficients are displayed as fractions when appropriate.
If False, coefficients are displayed in a simplified inline form.
Returns
------
str
Latex expression
Function used by
---------------------
pxs_steps_invert_matrix, pxs_LU_decomposition, pxs_compute_ech, pxs_compute_ech_reduite
"""
def __pxsl_multiple_matrix(list_mat, view, display = frac):
n = len(list_mat)
cols = "c" * n if view == "sep" else ":".join("c" * n)
seps = [["(", ")"] if view == "sep" else [".", "."] for _ in range(n)]
seps[0][0], seps[-1][1] = "(", ")"
expr = myst(r"""\begin{array}{\py{cols}}""", locals(), globals())
expr += "&".join([pxsl_matrix(mat, sep[0], sep[1], display = display) for mat, sep in zip(list_mat, seps)])
expr += myst(r"""\end{array}""", locals(), globals())
return expr
n = list_mat[0][0].shape[0]
list_mat, listOp = pxs_regroupe_ligne(list_mat, listOp)
if method == "sys":
try:
listA, listB = list_mat
except:
raise ValueError("list_mat must be of length 2 exactly when method is 'sys'")
# First line
if method=="sys":
expr = myst(r"""\begin{array}{cl} """)+myst(r"""&""")+ pxsl_system_lin(listA[0],listB[0],x, frac = frac)+myst(r"""\\ \\""")
else:
expr = myst(r"""\begin{array}{cc} """)+myst(r"""&""")+ __pxsl_multiple_matrix([listX[0] for listX in list_mat], view = view) + myst(r""" \\ \\""")
# other lines
for i in range(1, len(list_mat[0])):
printed_ops = pxsl_lines_op(n, listOp[i], frac = frac) if detail == "on" else " "
if method=="sys":
expr += myst(r"""\py{printed_ops} & """, locals(), globals()) + pxsl_system_lin(listA[i],listB[i],x, frac = frac)+myst(r"""\\ \\""")
else:
expr += myst(r"""\py{printed_ops} & """, locals(), globals()) + __pxsl_multiple_matrix([listX[i] for listX in list_mat], view = view) + myst(r"""\\ \\""")
expr+=myst(r"""\end{array}""")
return expr
##
[docs]
def pxs_commute_matrix(n,opt=""):
"""
Fonction permettant de créer les matrices A, B et C de dimension n avec A et B commutantes et A et C non commutantes
Version
-------
13/02/25
Vérification
------------
Auteur : Ronan - Delphine
Vérificateurs :
Paramètres
----------
n : int
Dimension de la matrice A
opt: str
"commut" : Envoie deux matrices qui commutent
"noncommut" : Envoie deux matrices qui ne commutent pas
autre : renvoie les trois matrices
Retour
------
A,B
retourne les deux matrices du système AX=B
Fonction utilisée par
---------------------
aucune fonction pyxiscience
"""
Nmax=3
A=ones(n)*10*n
C=A.copy()
while A*C==C*A or sum(1 for element in A if abs(element) >= 10*n)>=1 or sum(1 for element in C if abs(element) >= 10*n)>=1:
# On construit les matrices diagonales de la décomposition P*A*Pinv
P=pxs_system_simpl(n,eye(n),"mat")
Pinv=P.inv()
DiagA=randmatrixdiagonale(n,-Nmax,Nmax)
DiagC=DiagA.copy()
DiagC[0,n-1]=1
C=P*DiagC*Pinv*P.det()
C=C/fct.reduce(m.gcd,C[:,:])
# on s'assure que A ne puisse pas commuter avec n'importe quelle matrice (ce qui arrive si les éléments de la diagonale sont tous égaux)
# on s'assure également qu'on n'obtient pas la matrice nulle
while DiagA==zeros(n) or DiagA[0,0]==DiagA[n-1,n-1]:
DiagA=randmatrixdiagonale(n,-Nmax,Nmax)
# on génère les matrices A et B commutantes
A=P*DiagA*Pinv*P.det()
A=A/fct.reduce(m.gcd,A[:,:])
B=C.copy()*10*n
while A*B!=B*A or sum(1 for element in B if abs(element) >= 10*n)>=1:
DiagB=randmatrixdiagonale(n,-Nmax,Nmax)
while DiagB==zeros(n):
DiagB=randmatrixdiagonale(n,-Nmax,Nmax)
if DiagA[0,0]==DiagB[0,0]:
DiagB[0,0]=DiagB[0,0]+1
B=P*DiagB*Pinv*P.det()
B=B/fct.reduce(m.gcd,B[:,:])
if opt=="commute":
return A,B
if opt=="noncommute":
return A,C
else:
return A,B,C
################ EXEMPLES ##################
# pxs_commute_matrix(2) renvoie A, B et C de dimension 2x2 telles que A et B commutent et
# A et C ne commutent pas.
# Par exemple :
# 3 4 5 8 5 2
# A = -2 -3 B = -4 -7 C = -2 1
#
# pxs_commute_matrix(3) renvoie A, B et C de dimension 3x3 telles que A et B commutent et
# A et C ne commutent pas.
# Par exemple :
# 0 1 1 4 -5 -5 0 3 3
# A = 4 3 -1 B = -8 1 5 C = 4 1 -3
# -4 -1 3 8 -7 -11 -4 1 5
#
# pxs_commute_matrix(2,"commute") renvoie A, B dimension 2x2 telles que A et B commutent
# Par exemple :
# 3 4 5 8
# A = -2 -3 B = -4 -7
#
# pxs_commute_matrix(3,"noncommute") renvoie A et C de dimension 3x3 telles que A et C ne commutent pas.
# Par exemple :
# 0 1 1 0 3 3
# A = 4 3 -1 C = 4 1 -3
# -4 -1 3 -4 1 5
[docs]
def pxsl_pow_matrix(A,k,opt=0,sepG='(',sepD=')'):
"""
Fonction permettant d'écrire en latex une matrice dont tous les coefficients sont élevés à la même puissance
Les puissances de 0 et 1 sont simplifiées, les valeurs sont centrées par défaut
Version
-------
13/02/25
Vérification
------------
Auteur : Ronan - Delphine
Vérificateurs :
Paramètres
----------
A : Matrix
k : float ou Symbol
valeur de la puissance
sepG : str
délimiteur gauche de la matrice
sepD : str
délimiteur droit de la matrice
Retour
------
str
retourne l'expression en latex
Fonction utilisée par
---------------------
aucune fonction pyxiscience
"""
[n,q]=A.shape
pxs_lang = get_pxs_lang()
if pxs_lang == "en" and sepG=='(':
sepG='['
if pxs_lang == "en" and sepD==')':
sepD=']'
expr=myst(r"""\left\py{sepG}\begin{array}{\py{'c'*q}}""",globals(),locals())
for i in range(n):
expr=expr+pxsl_pow(A[i,0],k,opt)
for j in range(q-1):
expr=expr+myst(r""" &""")+pxsl_pow(A[i,1+j],k,opt)
expr=expr+myst(r"""\\""")
expr=expr+myst(r"""\end{array}\right\py{sepD}""",globals(),locals())
return expr
################ EXEMPLES ##################
# pxsl_pow_matrix(Matrix([[2,0,1],[4,8,-4],[2,3,0]]),2)
# renvoie l'expression latex \left(\begin{array}{ccc}2^{2}&0^2&1^2\4^{2}&8^{2}&\left(-4\right)^{2}\2^{2}&3^{2}&0^2\\end{array}\right)
# c'est-à-dire
#
# 2^2 0^2 1^2
# 4^2 8^2 (-4)^2
# 2^2 3^2 0^2
# pxsl_pow_matrix(Matrix([[2,0,1],[4,8,-4],[2,3,0]]),2,1)
# renvoie l'expression latex \left(\begin{array}{ccc}2^{2}&0&1\4^{2}&8^{2}&\left(-4\right)^{2}\2^{2}&3^{2}&0\\end{array}\right)
# c'est-à-dire
#
# 2^2 0 1
# 4^2 8^2 (-4)^2
# 2^2 3^2 0
[docs]
def pxs_regroupe_ligne(list_mat, listOp=[]):
"""
Fonction permettant de regrouper les lignes qui peuvent être écrites en une seule étape
Version
-------
21/03/25 -> 06/01/26 (Raphaël)
Vérification
------------
Auteur : Delphine
Vérificateurs :
Paramètres
----------
listA : list
liste des étapes pour la matrice/système de départ
listB : list
liste des étapes pour la matrice miroir (inversion) ou membre droit (système)
listOp : liste
liste des opérations sur lignes
Retour
------
listA, listB, listOp
retourne les listes actualisées
Fonction utilisée par
---------------------
pxsl_resol_system, pxsl_print_operations
"""
if len(listOp) <= 1:
return list_mat, listOp
elif len(listOp) == 2:
return list_mat, [listOp[0], [listOp[1]]]
n, nb_etape = len(list_mat[0]), 1
listOp_bis = [listOp[0]]
listOp_bis.append([listOp[1]])
list_mat_bis = [[listX[0]] for listX in list_mat]
for i in range(1, n-1):
if listOp[i][0] != 0 and all(sous_liste[1] !=listOp[i+1][1] for sous_liste in listOp_bis[nb_etape]) and all(sous_liste[1] !=listOp[i+1][3] for sous_liste in listOp_bis[nb_etape]):
listOp_bis[nb_etape].append(listOp[i+1])
else:
for j in range(len(list_mat)):
list_mat_bis[j].append(list_mat[j][i])
nb_etape += 1
listOp_bis.append([listOp[i+1]])
if i == n-2:
for j in range(len(list_mat)):
list_mat_bis[j].append(list_mat[j][i+1])
if listOp_bis:
return list_mat_bis, listOp_bis
else:
return list_mat, listOp
[docs]
def pxs_compute_ech(A):
"""
Fonction permettant de stocker toutes les étapes de la construction d'une matrice échelonnée
Paramètres
----------
A : Matrix
Retour
------
liste, liste, liste
retourne les listes actualisées de l'opération de permutation
Fonction utilisée par
---------------------
Aucune fonction pyxiscience
"""
[n,p]=A.shape
nmin=min(n,p)
listA=[A.copy()]
listOp=[[0,0,0,0]]
for k in range(nmin):
# On cherche la ligne pivot
r=-1
for i in range(k,n):
if r<0 and A[i,k]!=0:
r=i
if r>k:
# On met la ligne pivot en haut si elle ne l'est pas
A.row_swap(k, r)
listA.append(A.copy())
for j in range(n):
if j==r:
listOp.append([0,r+1,1,1+k])
if r>=k:
# On élimine les lignes après k
for j in range(k,n):
if A[j,k]!=0 and j!=k:
pg=m.gcd(A[k,k],A[j,k])
cpAjk=A[j,k]
cpAkk=A[k,k]
A[j,:]=abs(cpAkk)*A[j,:]/pg- abs(cpAjk)*A[k,:]/pg*np.sign( cpAjk* cpAkk)
listA.append(A.copy())
for l in range(n):
if l==j:
listOp.append([abs(cpAkk)/pg,j+1,-abs(cpAjk)/pg*np.sign( cpAjk* cpAkk),k+1])
expr = pxsl_print_operations([listA], listOp = listOp, method = "mat")
return expr
[docs]
def pxs_compute_ech_reduite(A):
"""
Fonction transformant une matrice en forme échelonnée réduite en stockant chaque étape.
Paramètres
----------
A : numpy.ndarray
Matrice d'entrée
Retour
------
listA : liste des matrices à chaque étape
listOp : liste des opérations sous forme [a, i, b, j] avec :
- a : coefficient multiplicatif pour L_i
- i : numéro de la ligne affectée (1-based index)
- b : coefficient multiplicatif pour L_j
- j : numéro de la ligne utilisée (1-based index)
"""
[n, p] = A.shape
listA = [A.copy()]
listOp = [[0,0,0,0]]
for k in range(min(n, p)):
# Trouver la ligne pivot
r = -1
for i in range(k, n):
if A[i, k] != 0:
r = i
break
if r == -1:
continue # Si toute la colonne est nulle, on passe à la suivante
# Échanger L_k et L_r si nécessaire
if r != k:
A.row_swap(k, r) # Échange des lignes
listA.append(A.copy())
listOp.append([0, k + 1, 1, r + 1]) # Format imposé
# Normaliser le pivot (L_k = L_k / pivot pour avoir 1)
pivot = A[k, k]
if pivot != 1:
A[k, :] /= pivot
listA.append(A.copy())
listOp.append([1 / pivot, k + 1, 0, 0]) # Division de la ligne par le pivot
# Élimination en dessous et au-dessus
for j in range(n):
if j != k and A[j, k] != 0:
facteur = A[j, k]
A[j, :] -= facteur * A[k, :]
listA.append(A.copy())
listOp.append([1, j + 1, -facteur, k + 1]) # Format imposé
expr = pxsl_print_operations([listA], listOp = listOp, method = "mat")
return expr
[docs]
def randmatrixrect(p,q,a,b):
"""Returns a rectangular matrix with p rows and q columns such that every coefficient is a realization of
of a discrete random variable on range(a, b)
:returns: LaTeX bmatrix as a string
"""
M= eye(p,q)
for i in range(p):
for j in range(q):
#print('(i,j) =', i,j,'\n')
#M[i,j] = next(sample(DiscreteUniform('h', range(a, b))))
M[i,j] = sample(DiscreteUniform('h', range(a, b)))
# M[i,j] = next(sample(DiscreteUniform('h', range(a, b)))) for i in range(p) for j in range(p)]
return M
[docs]
def pxs_invertible_matrix(n):
for _ in range(100):
entries = [[random.randint(-2, 2) for _ in range(n)] for _ in range(n)]
M = Matrix(entries)
if M.det() != 0:
return M
return Matrix.eye(n)
[docs]
def pxs_diag_matrix(p,a,b):
"""
Returns a square diagonal matrix of size p such that every coefficient is a
realization of a uniform discrete random variable, the range of which is range(a, b)
"""
D= eye(p)
for i in range(p):
for j in range(p):
if j==i:
D[i,j] = sample(DiscreteUniform('h', range(a, b)))
else:
D[i,j] = 0
return D
[docs]
def pxs_triangular(p, a, b, diag = None, lower = False, inv = False):
"""
Returns a square triangular matrix of size p such that every coefficient is a
realization of a uniform discrete random variable, the range of which is [[a, b]]
The diagonal coefficients can be specified as a list with the optional <diag> argument.
"""
T = zeros(p)
for i in range(p):
for j in range(p):
if j == i and diag:
T[i, i] = diag[i]
elif j == i and inv:
T[i, i] = pxs_randint(a, b, 0)
elif j >= i:
T[i,j] = sample(DiscreteUniform('h', range(a, b + 1)))
return T.T if lower else T
[docs]
def pxs_construct_RREF(n = 3, p = 3, M = (1, 2, 3), min = -9, max = 9):
"""
Construct a matrix with a partial Row Reduced Echelon Form (RREF) structure
based on a given pivot pattern.
The function creates a matrix of shape `(n, p)` whose pivot positions are
specified by the tuple `M`. Each element `m` of `M` indicates that the
corresponding row has a pivot equal to 1 in column `m-1`.
The remaining coefficients located to the right of the pivot and outside
the pivot columns are filled with random integers between `min` and `max`.
Parameters
----------
n : int, optional
Number of rows of the matrix (default is 3).
p : int, optional
Number of columns of the matrix (default is 3).
M : tuple or Matrix, optional
- If `M` is a tuple, it represents the pivot positions (1-based indexing).
- If `M` is a SymPy Matrix, it is copied and used as the initial matrix.
min : int, optional
Minimum value for the random coefficients (default is -9).
max : int, optional
Maximum value for the random coefficients (default is 9).
Returns
-------
Matrix
A SymPy matrix of shape `(n, p)` that follows the structure imposed
by `M`.
Examples
--------
>>> pxs_construct_RREF(n=3, p=4, M=(1, 3))
Matrix([
[1, 0, 0, a],
[0, 0, 1, b],
[0, 0, 0, 0]
])
>>> pxs_construct_RREF(n=2, p=3, M=(2,))
Matrix([
[0, 1, c],
[0, 0, 0]
])
"""
if isinstance(M, Matrix):
A = M. copy()
elif isinstance(M, tuple):
A = zeros(n, p)
for i, m in enumerate(M):
A[i, m-1] = 1
for i in range(len(M)):
for k in range(p):
if k+1 > M[i] and k+1 not in M:
A[i, k] = rd.randint(min, max)
return A
[docs]
def pxs_generate_sys(M = (1, 2, 3), n = 3, p = 3, N = "", opt = "sys", min = -9, max = 9):
"""
Generate a linear system associated with a matrix in (partial) RREF form.
The function first constructs a matrix with a Row Reduced Echelon Form–like
structure using `pxs_construct_RREF`, based on the pivot pattern `M`.
A right-hand side vector is generated (or copied) and a simplifying
transformation matrix is then applied to produce either the full linear
system or only the transformed coefficient matrix.
Parameters
----------
M : tuple or Matrix, optional
- If `M` is a tuple, it specifies the pivot positions (1-based indexing)
used to construct the RREF-like matrix.
- If `M` is a SymPy Matrix, its shape defines the values of `n` and `p`.
n : int, optional
Number of rows of the system (default is 3).
p : int, optional
Number of columns of the coefficient matrix (default is 3).
N : Matrix or str, optional
Right-hand side vector of the system.
If an empty string is provided, a random vector of length `n` with
integer entries between `-3` and `3` is generated.
opt : str, optional
Output option:
- `"sys"` returns both the transformed coefficient matrix and the
transformed right-hand side vector.
- Any other value returns only the transformed coefficient matrix.
min : int, optional
Minimum value for the random coefficients in the generated matrix
(default is 9).
max : int, optional
Maximum value for the random coefficients in the generated matrix
(default is 9).
Returns
-------
Matrix or tuple of Matrix
- If `opt == "sys"`, returns a tuple `(A, B)` where `A` is the transformed
coefficient matrix and `B` is the transformed right-hand side vector.
- Otherwise, returns only the transformed coefficient matrix.
Examples
--------
>>> A, B = pxs_generate_sys(M=(1, 3), n=3, p=4)
>>> A.shape
(3, 4)
>>> A = pxs_generate_sys(M=(2,), n=2, p=3, opt="mat")
>>> A.shape
(2, 3)
"""
# La matrice est N est copiée pour ne pas modifier la matrice originale
if isinstance(M, Matrix):
n, p = M.shape
if N=="":
N = Matrix([rd.randint(-3,3) for i in range(n)])
A, B = pxs_construct_RREF(n, p, M, min, max), N.copy()
A1 = pxs_system_simpl(n = n, opt = "")
if opt=="sys":
return A1 * A, A1 * B
else:
return A1 * A
[docs]
def pxs_repeat_generate_sys(M = (1, 2, 3), n = 3, p = 3, N = "", opt = "sys", min = -9, max = 9, backup = Matrix([[1, 1, 1], [1, 2, 3], [2, 3, 4]]), nb_iter = 10):
for _ in range(nb_iter):
res = pxs_generate_sys(M, n, p, N, opt, min, max)
A = res[0] if opt == "sys" else res
if not pxs_zero_column(A):
return res
return (backup, zeros(backup.rows, 1)) if opt == "sys" else backup
[docs]
def pxs_gauss_jordan(
A,
B=None,
x: str = "x",
method: str = "sys",
view: str = "sep",
detail: str = "on",
strict: bool = False,
frac: bool = True,
vectors: str = "col",
short: bool = True
):
"""
Perform a Gauss–Jordan elimination and generate a formatted (LaTeX) output
of all intermediate steps.
The function applies the Gauss–Jordan algorithm to the linear system
``A * X = B`` (or to the reduction of ``A`` alone if ``B is None``).
Each elementary row operation is recorded so that a detailed, step-by-step
symbolic representation of the reduction process can be produced, typically
for inclusion in a LaTeX document via the ``myst`` / ``pxsl_*`` utilities.
Parameters
----------
A : Matrix
Coefficient matrix of the linear system (SymPy ``Matrix``).
B : Matrix or None, optional
Right-hand side vector or matrix. If ``None``, the function only reduces
``A`` (the right-hand side is taken as a zero matrix of compatible size).
x : str, optional
Base name of the unknown variables used in the symbolic display
(e.g. ``"x"`` produces ``x_1, x_2, ...``). Default is ``"x"``.
method : str, optional
Display method passed to the printing routine (typically ``"sys"`` to
format the output as a linear system). Default is ``"sys"``.
view : str, optional
Visualization mode for intermediate steps (for example, separate or
combined views of matrices and operations). Default is ``"sep"``.
detail : str, optional
Level of detail in the output:
- ``"on"`` displays all elementary operations,
- other values may reduce verbosity (depending on
``pxsl_print_operations``).
Default is ``"on"``.
strict : bool, optional
Pivot selection strategy:
- if ``True``, applies a strict Gauss–Jordan strategy by choosing, below
the current row, the pivot with the largest absolute value in the
column (partial pivoting);
- if ``False``, chooses the first non-zero coefficient below the current
row (simplified strategy).
Default is ``False``.
frac : bool, optional
Controls the rendering of rational coefficients:
- if ``True``, coefficients are displayed as fractions when appropriate,
- if ``False``, coefficients may be displayed in a simplified inline form.
Default is ``True``.
vectors : str, optional
Orientation of solution vectors in the display:
- ``"col"`` for column vectors,
- any other value for row vectors.
Default is ``"col"``.
Returns
-------
Any
A symbolic object representing the formatted output (typically a LaTeX
string or structure produced via ``myst`` and ``pxsl_*`` utilities).
Examples
--------
Basic example (solving a square linear system):
>>> from sympy import Matrix
>>> A = Matrix([[1, 2], [3, 4]])
>>> B = Matrix([[5], [6]])
>>> out = pxs_gauss_jordan(A, B)
>>> isinstance(out, str) or out is not None
True
Changing the variable base name (``x="u"`` produces ``u_1, u_2, ...``):
>>> A = Matrix([[1, 1], [0, 1]])
>>> B = Matrix([[2], [3]])
>>> out = pxs_gauss_jordan(A, B, x="u")
>>> isinstance(out, str) or out is not None
True
Pivot strategy: simplified vs strict (partial pivoting):
>>> A = Matrix([[0, 1], [2, 3]])
>>> B = Matrix([[1], [1]])
>>> out1 = pxs_gauss_jordan(A, B, strict=False) # first non-zero pivot
>>> out2 = pxs_gauss_jordan(A, B, strict=True) # largest |value| pivot
>>> (out1 is not None) and (out2 is not None)
True
Reducing verbosity (if supported by the display routine):
>>> A = Matrix([[1, 2], [3, 4]])
>>> B = Matrix([[5], [6]])
>>> out = pxs_gauss_jordan(A, B, detail="off")
>>> out is not None
True
Skipping the final solution display (only reduction steps):
>>> A = Matrix([[1, 2], [3, 4]])
>>> B = Matrix([[5], [6]])
>>> out = pxs_gauss_jordan(A, B, solve=False)
>>> out is not None
True
Reducing ``A`` alone (``B=None``):
>>> A = Matrix([[1, 2, 3], [2, 4, 6]])
>>> out = pxs_gauss_jordan(A)
>>> out is not None
True
Solution set representation (vector orientation and span form):
>>> A = Matrix([[1, 1, 0], [0, 0, 1]])
>>> B = Matrix([[2], [3]])
>>> out_col = pxs_gauss_jordan(A, B, vectors="col", span=True)
>>> out_row = pxs_gauss_jordan(A, B, vectors="row", span=False)
>>> (out_col is not None) and (out_row is not None)
True
"""
def __check_incompatible(A, B):
npA = np.array(A)
npB = np.ravel(B)
ind_zero_lines = np.where([not npA[i].any() for i in range(len(npA))])[0]
return npB[ind_zero_lines].any()
#def __print_solved(A, B, col_pivots, free_indices, x = "x", frac = True):
#n,p = A.shape
#r = len(col_pivots)
#vect_x = Matrix([Symbol(x + "_" + str(j + 1)) for j in range(p)])
#expr = myst(r"""\left\{ \begin{array}{rcl} """) if r > 1 else myst(r"""\left. \begin{array}{rcl} """)
#for i in range(r):
#j = col_pivots[i] - 1
#expr += myst(r"""\py{vect_x[j]} & =& """, globals(), locals())
#sign = " "
#if B[i] == 0 and A[i, j+1:].is_zero_matrix:
#expr += "0"
#if B[i]:
#rhs = myst(r"""\py{B[i].p}/\py{B[i].q}""", globals(), locals()) if (isinstance(B[i], Rational) and B[i].q != 1 and not frac) else latex(B[i])
#expr += myst(r"""\py{rhs}""",globals(),locals())
#sign = "+"
#for k in range(j+1, p):
#if A[i, k] != 0:
#expr += pxsl_ax(-A[i, k], vect_x[k], sign, frac = frac)
#sign = "+"
#expr += myst(r"""\\[0.3em]""")
# displaying the list of free variables
#if free_indices:
#expr += latex(tuple(vect_x[j-1] for j in free_indices)) if len(free_indices) > 1 else myst(r"""\py{vect_x[list(free_indices)[0] - 1]}""", globals(), locals())
#set_r = myst(r"""\R^{\py{p-r}}""", globals(), locals()) if p - r > 1 else myst(r"""\R""")
#expr += myst(r""" &\in &\py{set_r}\\""", globals(), locals())
#expr += myst(r"""\end{array}\right.""")
#return expr
def __print_solved(A, B, col_pivots, free_indices, x = "x", frac = True):
n,p = A.shape
r = len(col_pivots)
vect_x = Matrix([Symbol(x + "_" + str(j + 1)) for j in range(p)])
expr = myst(r"""\left\{ \begin{array}{rcl} """)
k = -1
for i in range(p):
if i+1 in free_indices:
expr += myst(r"""\py{latex(vect_x[i])} &=&\py{latex(vect_x[i])}\\""", globals(), locals())
continue
k += 1
j = i
sign = " "
expr += myst(r"""\py{latex(vect_x[i])} &=& """, globals(), locals())
if B[k] == 0 and A[k, j+1:].is_zero_matrix:
expr += myst(r"""0""", globals(), locals())
if B[k]:
rhs = myst(r"""\py{B[k].p}/\py{B[k].q},""", globals(), locals()) if (isinstance(B[k], Rational) and B[k].q != 1 and not frac) else latex(B[k])
expr += myst(r"""\py{rhs}""",globals(),locals())
sign = "+"
for l in range(j+1, p):
if A[k, l] != 0:
expr += myst(r"""\py{pxsl_ax(-A[k, l], vect_x[l], sign, frac = frac)}""", globals(), locals())
sign = "+"
if i != p-1:
expr += myst(r""" \\ """)
# displaying the list of free variables
#if free_indices:
#for j in free_indices:
#expr += myst(r"""\py{latex(vect_x[j-1])}""", globals(), locals())
expr += myst(r"""\end{array}\right.""")
return expr
def __print_param(A, B, col_pivots, free_indices, x = "x", frac = True):
n,p = A.shape
r = len(col_pivots)
vect_x = Matrix([Symbol(x + "_" + str(j + 1)) for j in range(p)])
expr1 = myst(r"""\left( """)
for i, v in enumerate(vect_x):
if i != len(vect_x) -1:
expr1 += myst(r"""\py{latex(v)},""", globals(), locals())
else:
expr1 += myst(r"""\py{latex(v)}""", globals(), locals())
expr1 += myst(r""" \right) = ( """)
k = -1
for i in range(p):
if i+1 in free_indices:
if i != p-1:
expr1 += myst(r"""\py{latex(vect_x[i])},""", globals(), locals())
else:
expr1 += myst(r"""\py{latex(vect_x[i])}""", globals(), locals())
continue
k += 1
j = i
sign = " "
if B[k] == 0 and A[k, j+1:].is_zero_matrix:
expr1 += myst(r"""0""", globals(), locals())
if B[k]:
rhs = myst(r"""\py{B[k].p}/\py{B[k].q},""", globals(), locals()) if (isinstance(B[k], Rational) and B[k].q != 1 and not frac) else latex(B[k])
if k != p-1:
expr1 += myst(r"""\py{rhs}""",globals(),locals())
else:
expr1 += myst(r"""\py{rhs}""",globals(),locals())
sign = "+"
for l in range(j+1, p):
if A[k, l] != 0:
expr1 += myst(r"""\py{pxsl_ax(-A[k, l], vect_x[l], sign, frac = frac)}""", globals(), locals())
sign = "+"
if i != p-1:
expr1 += myst(r""" , """)
# displaying the list of free variables
#if free_indices:
#for j in free_indices:
#expr += myst(r"""\py{latex(vect_x[j-1])}""", globals(), locals()) -->
expr1 += myst(r""") """)
expr2 = myst(r""" """)
if free_indices:
expr2 = latex(tuple(vect_x[j-1] for j in free_indices)) if len(free_indices) > 1 else myst(r"""\py{vect_x[list(free_indices)[0] - 1]}""", globals(), locals())
set_r = myst(r"""\R^{\py{p-r}}""", globals(), locals()) if p - r > 1 else myst(r"""\R""")
expr2 += myst(r""" \in \py{set_r}""", globals(), locals())
return expr1, expr2
def __get_basis(A, B, col_pivots, free_indices, frac = True):
n, p = A.shape
canonical = eye(p)
basis = []
for j in free_indices:
lesser_pivots = [p-1 for p in col_pivots if p < j]
nb = len(lesser_pivots)
u = canonical[j-1, :] - sum([A[i, j-1] * canonical[pivot, :] for i, pivot in enumerate(lesser_pivots)], start = zeros(1, p))
basis.append(u)
# particular solution:
x0 = sum([B[i] * canonical[p - 1, :] for i, p in enumerate(col_pivots)], start = zeros(1, p))
return basis, x0
pxs_lang = get_pxs_lang()
sol = {"sys": myst(r""" """), "param": myst(r""" """), "free_var": myst(r""" """)}
[n, p] = A.shape
no_rhs = B is None
if no_rhs:
B = zeros(n, 1)
listA = [A.copy()]
listB = [B.copy()]
listOp = [[0,0,0,0]]
r = -1
col_pivots = []
sol["resol"] = myst(r"""\begin{equation*}""", locals(), globals()) if solve else " "
j = 0
go_on = True
while j < p and r < n - 1 and go_on:
# looking for the line to swap with:
if strict:
k = max(range(r+1, A.rows), key = lambda i: Abs(A[i, j]))
# if strict = False, take the row of the first non zero coefficient if any
elif np.any(A[r+1:, j]):
k = np.where(np.ravel(A[r + 1:, j]) != 0)[0][0] + r + 1
else: # all coeffs under row r+1 are zero, nothing will be done anyway
k = r + 1
if A[k, j] != 0:
r += 1
if A[k, j] != 1:
listOp.append([1 / A[k, j], k + 1, 0, 0])
B[k, :] = B[k, :] / A[k, j]
A[k, :] = A[k, :] / A[k, j]
A, B = Matrix(simplify(A)), Matrix(simplify(B))
listA.append(A.copy())
listB.append(B.copy())
col_pivots.append(j + 1)
if k != r:
A.row_swap(k, r)
B.row_swap(k, r)
A, B = Matrix(simplify(A)), Matrix(simplify(B))
listA.append(A.copy())
listB.append(B.copy())
listOp.append([0, r + 1, 1, k + 1])
for i in range(n):
if i != r and A[i, j]:
listOp.append([1, i + 1, -A[i, j], r + 1])
B[i, :] = B[i, :] - A[i, j] * B[r, :]
A[i, :] = A[i, :] - A[i, j] * A[r, :]
A, B = Matrix(simplify(A)), Matrix(simplify(B))
listA.append(A.copy())
listB.append(B.copy())
go_on = not (short and __check_incompatible(A, B))
j += 1
free_indices = set(range(1, p + 1)) - set(col_pivots) # will be useful for basis of solutions
list_mat = [listA] if no_rhs else [listA, listB]
sol["resol"] += pxsl_print_operations(list_mat, listOp = listOp, method = method, x = x, view = view, detail = detail, frac = frac)
sol["resol"] += myst(r"""\end{equation*}
""")
#if len(col_pivots) > 1:
#sol["resol"] += myst(r"""
#On obtient donc le système équivalent suivant :""") if pxs_lang == "fr" else myst(r"""Hence we get the following equivalent system:""")
#else:
#sol["resol"] += myst(r"""
#Le système est donc équivalent à :""") if pxs_lang == "fr" else myst(r"""Hence the system is equivalent to:""") -->
# sol["sys"] = myst(r"""
# \begin{equation*}""")
consistent = not __check_incompatible(A, B)
sol["sys"] = __print_solved(A, B, col_pivots, free_indices, x = x, frac = frac) if consistent else pxsl_system_lin(A, B, x = x, frac = frac)
sol["param"], sol["free_var"] = __print_param(A, B, col_pivots, free_indices, x = x, frac = frac)
# sol["sys"] += myst(r"""
# \end{equation*}""")
sol["A"] = A
sol["B"] = B
#expr += myst(r"""\\ \\""")
#expr += __print_solved(A, B, col_pivots, free_indices, x = x, frac = frac)
#expr += myst(r"""\end{equation*}""", locals(), globals())
# displaying the list of free variables
vect_x_free = Matrix([Symbol(x + "_" + str(j)) for j in free_indices])
basis, x0 = __get_basis(A, B, col_pivots, free_indices, frac = frac)
if vectors == "col": basis, x0 = [v.T for v in basis], x0.T
# displaying the solutions as linear combinations of the basis vectors:
mat_delim = "[" if pxs_lang == "en" else "("
if consistent:
# sol["set"] = myst(r"""\begin{equation*}
# \begin{align*}
# \mathcal{S} &= \left\{""")
sol["set"] = myst(r"""\left\{""")
if not x0.is_zero_matrix or not free_indices:
sol["set"] += latex(x0, mat_delim = mat_delim, fold_short_frac = not frac)
if free_indices: sol["set"]+= " + "
for l in range(len(basis)):
vector_tex = latex(basis[l], mat_delim = mat_delim, fold_short_frac = not frac)
sol["set"] += myst(r""" \py{vect_x_free[l]} . \py{vector_tex} \py{" + " if (l < len(basis) - 1) else " "}""", globals(), locals())
if free_indices: sol["set"] += myst(r""" ~ : ~ """)
# displaying the list of free variables
if free_indices:
sol["set"] += latex(tuple(vect_x_free)) if len(vect_x_free) > 1 else myst(r"""\py{vect_x_free[0]}""", globals(), locals())
set_r = myst(r"""\R^{\py{p-r - 1}}""", globals(), locals()) if p - r -1 > 1 else myst(r"""\R""")
sol["set"] += myst(r""" \in \py{set_r}""", globals(), locals())
# sol["set"] += myst(r"""\right\}
# \end{align*}
# \end{equation*}""")
sol["set"] += myst(r"""\right\}""")
sol["span"] = myst(r""" """)
if free_indices:
vepan = "Vect" if pxs_lang == "fr" else "Span"
# sol["span"] = myst(r"""
# \begin{equation*}
# \begin{align*}""")
if not x0.is_zero_matrix:
sol["span"] += latex(x0, mat_delim = mat_delim, fold_short_frac = not frac) + " + "
sol["span"] += myst(r"""\text{\py{vepan}}\left( """, globals(), locals())
# for l in range(len(basis)):
# vector_tex = latex(basis[l], mat_delim = mat_delim, fold_short_frac = not frac)
# expr += myst(r"""\py{vector_tex} \py{" , " if (l < len(basis) - 1) else " "}""", globals(), locals())
sol["span"] += " , ".join([latex(vector, mat_delim = mat_delim, fold_short_frac = not frac) for vector in basis])
sol["span"] += myst(r"""\right)""")
# sol["span"] += myst(r"""
# \end{align*}
# \end{equation*}""")
else:
sol["set"], sol["span"] = myst(r"""\emptyset"""), myst(r"""\emptyset""")
return sol
[docs]
def pxs_colinear_rows(M, i, j):
"""
Test whether two rows of a matrix are colinear.
Two rows are said to be colinear if one is a scalar multiple of the other.
The test is performed by computing the rank of the matrix formed by the
two rows.
By convention, if at least one of the two rows is a zero row, the function
returns ``False`` (zero rows are ignored).
Parameters
----------
M : Matrix
A SymPy matrix.
i : int
Index of the first row to test (0-based).
j : int
Index of the second row to test (0-based).
Returns
-------
bool
``True`` if rows ``i`` and ``j`` are colinear, ``False`` otherwise.
Examples
--------
Two proportional rows:
>>> from sympy import Matrix
>>> M = Matrix([[1, 2, 3],
... [2, 4, 6],
... [1, 0, 1]])
>>> pxs_colinear_rows(M, 0, 1)
True
Rows that are not colinear:
>>> pxs_colinear_rows(M, 0, 2)
False
A zero row is ignored:
>>> M = Matrix([[1, 2, 3],
... [0, 0, 0],
... [2, 4, 6]])
>>> pxs_colinear_rows(M, 0, 1)
False
Colinearity still detected with non-adjacent rows:
>>> pxs_colinear_rows(M, 0, 2)
True
"""
if M.row(i).is_zero or M.row(j).is_zero:
return False # on ignore les lignes nulles ici
return Matrix([M.row(i), M.row(j)]).rank() == 1
[docs]
def pxs_break_colinearity(M, N, i, j, *, coef_range=(-3, 3)):
"""
Break the colinearity between two rows of a linear system using
an elementary row operation.
If rows ``i`` and ``j`` of the matrix ``M`` are colinear, the function
replaces row ``i`` by a linear combination
row_i ← a * row_i + b * row_k
where ``k`` is a row index different from ``i`` and ``j``, and
``a`` and ``b`` are nonzero integers chosen randomly in ``coef_range``.
The same operation is applied consistently to the right-hand side
vector ``N`` so that the linear system remains equivalent.
If rows ``i`` and ``j`` are not colinear, or if no suitable third row
is available, the matrices are returned unchanged.
Parameters
----------
M : Matrix
Coefficient matrix of the linear system.
N : Matrix
Right-hand side column vector of the system.
i : int
Index of the first row (0-based).
j : int
Index of the second row (0-based).
coef_range : tuple of int, optional
Range ``(min, max)`` from which the integer coefficients ``a`` and
``b`` are drawn (default is ``(-3, 3)``). Zero is excluded.
Returns
-------
Matrix
The modified coefficient matrix.
Matrix
The modified right-hand side vector.
Examples
--------
Breaking colinearity between two proportional rows:
>>> from sympy import Matrix
>>> M = Matrix([[1, 2, 3],
... [2, 4, 6],
... [1, 0, 1]])
>>> N = Matrix([1, 2, 0])
>>> M2, N2 = pxs_break_colinearity(M, N, 0, 1)
The resulting system is equivalent, but rows 0 and 1 are no longer colinear:
>>> from sympy import Matrix
>>> Matrix([M2.row(0), M2.row(1)]).rank() == 1
False
If the rows are not colinear, nothing is changed:
>>> M = Matrix([[1, 2],
... [3, 4]])
>>> N = Matrix([1, 1])
>>> M2, N2 = pxs_break_colinearity(M, N, 0, 1)
>>> M2 == M and N2 == N
True
If no suitable third row exists, the matrices are returned unchanged:
>>> M = Matrix([[1, 2],
... [2, 4]])
>>> N = Matrix([1, 2])
>>> M2, N2 = pxs_break_colinearity(M, N, 0, 1)
>>> M2 == M and N2 == N
True
"""
M = M.copy()
N = N.copy()
n = M.rows
if not pxs_colinear_rows(M, i, j):
return M, N # rien à faire
# choisir une ligne k différente de i et j
candidates = [k for k in range(n) if k not in (i, j) and not M.row(k).is_zero]
if not candidates:
return M, N # pas de ligne exploitable
k = rd.choice(candidates)
# coefficients non nuls
a = rd.choice([c for c in range(*coef_range) if c != 0])
b = rd.choice([c for c in range(*coef_range) if c != 0])
# opération élémentaire
M.row_op(i, lambda v, col: a * v + b * M[k, col])
N.row_op(i, lambda v, col: a * v + b * N[k])
return M, N
[docs]
def pxs_break_all_colinear_rows(A, B, max_iter=5):
"""
Remove colinearity between all pairs of rows of a linear system.
The function repeatedly scans the coefficient matrix ``A`` for pairs of
colinear rows. Whenever such a pair is found, an elementary row operation
is applied (via :func:`pxs_break_colinearity`) to break the colinearity
while preserving the solution set of the system.
The process is repeated until no colinear row pairs remain, or until the
maximum number of iterations is reached.
Parameters
----------
A : Matrix
Coefficient matrix of the linear system.
B : Matrix
Right-hand side column vector.
max_iter : int, optional
Maximum number of iterations allowed to remove colinearities
(default is ``10``).
Returns
-------
Matrix
The modified coefficient matrix with reduced row colinearity.
Matrix
The modified right-hand side vector.
Examples
--------
Removing colinearity between multiple rows:
>>> from sympy import Matrix
>>> A = Matrix([[1, 2, 3],
... [2, 4, 6],
... [3, 6, 9]])
>>> B = Matrix([1, 2, 3])
>>> A2, B2 = pxs_break_all_colinear_rows(A, B)
After processing, no two nonzero rows are colinear:
>>> any(
... Matrix([A2.row(i), A2.row(j)]).rank() == 1
... for i in range(A2.rows)
... for j in range(i + 1, A2.rows)
... if not A2.row(i).is_zero and not A2.row(j).is_zero
... )
False
If the matrix contains no colinear rows, it is returned unchanged:
>>> A = Matrix([[1, 0],
... [0, 1]])
>>> B = Matrix([1, 1])
>>> A2, B2 = pxs_break_all_colinear_rows(A, B)
>>> A2 == A and B2 == B
True
"""
M = A.copy()
N = B.copy()
for _ in range(max_iter):
changed = False
for i in range(M.rows):
for j in range(i + 1, M.rows):
if pxs_colinear_rows(M, i, j):
M, N = pxs_break_colinearity(M, N, i, j)
changed = True
break
if changed:
break
if not changed:
return M, N
return M, N
[docs]
def pxs_zero_column(A):
"""
Checks whether Matrix A has at least one zero column.
Parameters
----------
A : Matrix
Returns
-------
bool : True if A has at least one zero column, False otherwise
Examples
--------
>>> A = Matrix(
... [[1, 0, 2],
... [2, 0, -3],
... [1, 0, 1]])
>>> pxs_zero_column(A)
True
>>> M = Matrix(
... [[1, 0, 2],
... [2, 1, -3],
... [1, 0, 1]])
>>> pxs_zero_column(M)
False
"""
return np.any([A[:, j].is_zero_matrix for j in range(A.cols)])
## CALCULS DE DÉTERMINANTS
[docs]
def pxs_determinant(A, detail = "on", **kwargs):
"""
Compute the determinant of a matrix using row reduction, with optional
step-by-step output.
The function reduces the matrix to upper-triangular form via row swaps and
row scaling, tracking all intermediate matrices and operations. The
determinant is then recovered from the diagonal of the final matrix,
the number of row swaps performed, and the scaling factors applied.
Parameters
----------
A : Matrix
A square SymPy matrix.
detail : str, optional
If ``"on"`` (default), row operations are displayed next to each
intermediate matrix. Any other value suppresses the operation labels.
**kwargs
Additional keyword arguments forwarded to SymPy's ``latex()`` function
(e.g. ``mul_symbol``).
Returns
-------
dict
A dictionary with the following keys:
- ``"last"`` : Matrix
The final row-reduced matrix.
- ``"oper"`` : str
A LaTeX string displaying all intermediate matrices and operations.
- ``"swaps"`` : int
The number of row swaps performed.
- ``"exp"`` : str
A LaTeX string for the unsimplified determinant expression
(product of sign, scaling factors, and diagonal coefficients),
or ``"0"`` if a zero pivot was encountered.
- ``"sc_fact"`` : list
The list of scaling factors extracted from pivot rows
(as their inverses, i.e. the actual pivot values before scaling).
- ``"val"`` : str
A LaTeX string for the fully evaluated determinant value,
or ``"0"`` if a zero pivot was encountered.
- ``"all"`` : str
A complete LaTeX string combining the reduction table and the
final determinant computation.
Examples
--------
Full determinant computation with details:
>>> A = Matrix([[0, 1], [3, 4]])
>>> result = pxs_determinant(A)
>>> result["swaps"]
1
>>> result["val"]
'-3'
Singular matrix (zero pivot encountered):
>>> A = Matrix([[1, 2], [2, 4]])
>>> result = pxs_determinant(A)
>>> result["val"]
'0'
>>> result["exp"]
'0'
3x3 matrix with scaling factors:
>>> A = Matrix([[2, 4, 0], [1, 3, 1], [0, 1, 2]])
>>> result = pxs_determinant(A)
>>> result["sc_fact"]
[2]
"""
def __print_ops(listA, listOp, factors, **kwargs):
n = listA[0].shape[0]
[listA], listOp = pxs_regroupe_ligne([listA], listOp)
swaps_txt = myst(r"""\text{swaps:}\quad """) if pxs_lang == "en" else myst(r"""échanges : """)
factors_inv = [1 / x for x in factors]
factors_txt = myst(r"""\text{factors:}\quad """) if pxs_lang == "en" else myst(r"""facteurs : """)
r = 0 # nb of swaps
f = 0 # index of factor
# First line
expr = myst(r"""\begin{array}{ccc} """)+myst(r"""&""")+ pxsl_matrix(listA[0]) + myst(r""" \\ \\""")
# other lines
for i in range(1, len(listA)):
printed_ops = pxsl_lines_op(n, listOp[i], frac = frac) if detail == "on" else " "
expr += myst(r"""{{printed_ops}} & """, locals(), globals()) + pxsl_matrix(listA[i]) + myst(r"""&""")
do_swap = not all([op[0] for op in listOp[i]])
#do_scale = not all([op[2] for op in listOp[i]])
nb_scale = len([op for op in listOp[i] if op[2] == 0])
if do_swap and nb_scale: # both swap and scale
r += 1
f += nb_scale
current_factors = myst(r""" , """).join([latex(fac, **kwargs) for fac in factors_inv[:f]])
expr += myst(r"""\begin{array}{l}
{{swaps_txt}} {{r}} \\
{{factors_txt}} \displaystyle {{current_factors}}
\end{array}""", globals(), locals())
elif do_swap: # swap only
r += 1
expr += myst(r"""{{swaps_txt}} {{r}}""", globals(), locals())
elif nb_scale: # scaling only
f += nb_scale
current_factors = myst(r""" , """).join([latex(fac, **kwargs) for fac in factors_inv[:f]])
expr += myst(r"""{{factors_txt}} \displaystyle {{current_factors}}""", globals(), locals())
expr += myst(r"""\\ \\""")
expr+=myst(r"""\end{array}""")
return expr
def __writing(operations, expression, value, nb_swaps):
text = myst(r"""\begin{equation*}
{{operations}}
\end{equation*}
""", globals(), locals())
if expression:
if nb_swaps:
neg = " "
nb_swaps_tex = myst(r"""${{latex(nb_swaps)}}$""", globals(), locals())
else:
neg = "not" if pxs_lang == "en" else "n'"
nb_swaps_tex = "aucun" if pxs_lang == "fr" else "any"
if pxs_lang == "en":
text += myst(r"""We have {{neg}} performed {{nb_swaps_tex}} row swap, hence:
\begin{equation*}
\begin{align*}
\det {{pxsl_matrix(A)}} &= {{expression}} \\
&= {{value}} \,.
\end{align*}
\end{equation*}
""", globals(), locals())
else:
text += myst(r"""Nous {{neg}}avons effectué {{nb_swaps_tex}} échange de lignes, donc :
\begin{equation*}
\begin{align*}
\det {{pxsl_matrix(A)}} &= {{expression}} \\
&= {{value}} \,.
\end{align*}
\end{equation*}
""", globals(), locals())
else:
if pxs_lang == "en":
text += myst(r"""The last matrix above has a zero diagonal coefficient, hence:
\begin{equation*}
\det {{pxsl_matrix(A)}} = 0\,.
\end{equation*}""")
else:
text +=myst(r"""La dernière matrice obtenue ci-dessus possède un coefficient diagonal nul, hence:
\begin{equation*}
\det {{pxsl_matrix(A)}} = 0\,.
\end{equation*}""")
return text
pxs_lang = get_pxs_lang()
n = A.rows
listA=[A.copy()]
listOp=[[0,0,0,0]]
nb_swaps = 0
factors = []
for k in range(n - 1):
if np.any(A[k:, k]):
# swapping
r = np.where(np.ravel(A[k:, k]) != 0)[0][0] + k
if r != k:
nb_swaps += 1
A.row_swap(k, r)
listA.append(A.copy())
listOp.append([0, r + 1, 1, k + 1])
# setting 1 as pivot
if A[k, k] != 1:
factors.append(A[k, k])
listOp.append([1 / A[k, k], k + 1, 0, 0])
A[k, :] /= A[k, k]
listA.append(A.copy())
# getting zeros under the pivot
for j in range(k+1, n):
if A[j, k] != 0:
listOp.append([1, j+1, -A[j, k], k + 1])
A[j, :] = A[j, :] - A[j, k] * A[k, :]
listA.append(A.copy())
else: # zero pivot, computation is over
operations = pxsl_print_operations([listA], listOp = listOp, method = "mat")
all_details = __writing(operations, None, None, nb_swaps)
return {"last" : A, "oper" : operations, "swaps" : nb_swaps, "exp" : "0", "sc_fact" : factors, "val" : "0", "all" : all_details}
np_diag = np.array(A)[range(n), range(n)]
diag_coeffs = list(np_diag[np_diag != 1]) # non-ones diagonal coefficients of the last matrix
if not diag_coeffs: diag_coeffs = [1]
sign = [Pow(-1, nb_swaps, evaluate = False)] if nb_swaps else []
all_det_factors = sign + factors + diag_coeffs
# operations = pxsl_print_operations([listA], listOp = listOp, method = "mat")
operations = __print_ops(listA, listOp, factors, **kwargs)
expression = latex(Mul(*all_det_factors, evaluate = False), **kwargs)
value = latex(Mul(*[(-1) ** nb_swaps] + factors + diag_coeffs), **kwargs)
all_details = __writing(operations, expression, value, nb_swaps)
result = {
"last" : A,
"oper" : operations,
"swaps" : nb_swaps,
"exp" : expression,
"sc_fact" : [1 / x for x in factors],
"val" : value,
"all" : all_details,
}
return result
[docs]
def pxs_compute_determinant(A, smart = True, **kwargs):
"""
Compute the determinant of a matrix by cofactor expansion along a
strategically chosen row or column, with full LaTeX output.
At each recursive step, the function optionally selects the row or column
with the fewest non-zero entries (``smart=True``) to minimise the number
of non-zero terms in the expansion. The result is a complete LaTeX
``align*`` environment showing all intermediate steps down to 2x2
determinants.
Parameters
----------
A : Matrix
A square SymPy matrix.
smart : bool, optional
If ``True`` (default), at each step the row or column with the fewest
non-zero entries is selected for elimination, and either row or column
operations are applied accordingly. If ``False``, the first column is
always used, with row operations only.
**kwargs
Additional keyword arguments forwarded to SymPy's ``latex()`` function.
Returns
-------
str
A LaTeX string showing the full step-by-step
determinant computation, from the original matrix down to the final
scalar value.
Examples
--------
3x3 matrix with smart expansion:
>>> A = Matrix([[1, 0, 0], [2, 3, 1], [0, 4, 2]])
>>> print(pxs_compute_determinant(A))
# LaTeX output exploiting the zeros in the first row
3x3 matrix without smart expansion:
>>> A = Matrix([[2, 1, 3], [0, 4, 1], [1, 2, 0]])
>>> print(pxs_compute_determinant(A, smart=False))
# LaTeX output always expanding along the first column
"""
def __rec_compute_determinant(A, smart, expr, values, **kwargs):
def __latex_without_ones(list_terms, **kwargs):
list_no_ones = [x for x in list_terms if x != 1]
if list_no_ones:
return latex(Mul(*list_no_ones, evaluate = False), **kwargs) if list_no_ones != [-1] else myst(r""" - """)
else:
return " "
def __get_coeffs(vector):
flat = np.array(vector).ravel() # flatten to get a 1d-array
ones_ind = np.where(flat == 1)[0] # indices of the ones, if any
indices = np.where(flat)[0]
if len(ones_ind) > 0: # if any
return ones_ind[0], indices
else:
return indices[0], indices
def __det2x2(A, **kwargs):
a, b, c, d = A
ad = Mul(a, d, evaluate = not (a * d))
bc = Mul(b, c, evaluate = not (b * c))
return myst(r"""{{latex(ad, **kwargs)}} - {{latex(bc, **kwargs)}}""", globals(), locals()).replace("- -", "+")
pxs_lang = get_pxs_lang()
n = A.rows
if n == 2:
try:
mult = mul_symbol
except:
mult = myst(r""" """)
lpar = myst(r"""\left(""") if len(values) != values.count(1) else myst(r""" """)
rpar = myst(r"""\right)""") if len(values) != values.count(1) else myst(r""" """)
expr += myst(r"""
&= {{__latex_without_ones(values, **kwargs)}} {{mult}} {{lpar}} {{__det2x2(A, **kwargs)}} {{rpar}} & \\
""", globals(), locals())
values.append(A.det())
final_value = Mul(*values)
expr += myst(r"""
&= {{latex(final_value, **kwargs)}} \,.
""", globals(), locals())
return expr
if n <= 1:
values.append(A[0, 0])
final_value = Mul(*values)
expr += myst(r"""\\
&= {{__latex_without_ones(values, **kwargs)}} \\
&= {{latex(final_value, **kwargs)}} \,.
""", globals(), locals())
return expr
if A[:, 0].is_zero_matrix:
expr += myst(r"""\\
&= 0 \, .
""")
return expr
operations = []
sign = 1
column_operations = False # replace row operations by column operations if needed
if smart:
nonzero_row = np.count_nonzero(A, axis = 1)
nonzero_col = np.count_nonzero(A, axis = 0)
row_min, col_min = min(nonzero_row), min(nonzero_col)
if row_min < col_min: # The best is a row
column_operations = True # in this case we perform column operations
k = np.argmin(nonzero_row)
j0, indices = __get_coeffs(A[k, :])
coeff = A[k, j0]
# scaling if need be
if coeff != 1:
operations.append([1 / coeff, j0 + 1, 0, 0])
A[:, j0] /= coeff
# getting zeros on the row
for j in indices:
if j != j0:
operations.append([1, j + 1, -A[k, j], j0 + 1])
A[:, j] = A[:, j] - A[k, j] * A[:, j0]
sign = (-1) ** (k + j0)
else: # The best is a column
k = np.argmin(nonzero_col)
i0, indices = __get_coeffs(A[:, k])
coeff = A[i0, k]
# scaling if need be
if coeff != 1:
operations.append([1 / coeff, i0 + 1, 0, 0])
A[i0, :] /= coeff
# getting zeros on the row
for i in indices:
if i != i0:
operations.append([1, i + 1, -A[i, k], i0 + 1])
A[i, :] = A[i, :] - A[i, k] * A[i0, :]
sign = (-1) ** (i0 + k)
else:
# swapping
if A[0, 0].is_zero:
r = np.where(np.ravel(A[:, 0]) != 0)[0][0]
sign = -1
A.row_swap(0, r)
operations.append([0, r + 1, 1, 1])
coeff = A[0, 0]
# scaling if need be
if coeff != 1:
operations.append([1 / coeff, 1, 0, 0])
A[0, :] /= coeff
# getting zeros under the pivot
for j in range(1, n):
if A[j, 0] != 0:
operations.append([1, j+1, -A[j, 0], 1])
A[j, :] = A[j, :] - A[j, 0] * A[0, :]
minor = A.copy()
if column_operations: # smart and column operations
i, j = k, j0
elif smart: # smart and row operations
i, j = i0, k
else: # unsmart
i, j = 0, 0
minor.row_del(i)
minor.col_del(j)
row_symb = "L" if pxs_lang == "fr" else "R"
print_ops = pxsl_lines_op(n, operations).replace(row_symb, "C") if column_operations else pxsl_lines_op(n, operations)
expr += myst(r"""
&= {{__latex_without_ones(values + [1 if smart else sign, coeff], **kwargs)}} \det {{pxsl_matrix(A)}} & {{print_ops}} \\
""", globals(), locals())
values.append(sign * coeff)
expr += myst(r"""
&= {{__latex_without_ones(values, **kwargs)}} \det {{pxsl_matrix(minor)}} & \\
""", globals(), locals())
return __rec_compute_determinant(minor, smart, expr, values, **kwargs)
begin = myst(r"""
\begin{equation*}
\begin{align*}
\det {{pxsl_matrix(A)}}
""", globals(), locals())
end = myst(r"""
\end{align*}
\end{equation*}
""")
return begin + __rec_compute_determinant(A, smart, " ", [], **kwargs) + end
[docs]
def pxs_best_line(A):
"""
Identify the row or column of a matrix with the fewest non-zero entries.
This is used to select the most efficient line for cofactor expansion.
In case of a tie between the best row and the best column, the column is
preferred.
Parameters
----------
A : Matrix
A square SymPy matrix.
Returns
-------
rc : str
``"r"`` if the best line is a row, ``"c"`` if it is a column.
k : int
The 1-based index of that row or column.
Examples
--------
A matrix whose first row has the fewest non-zero entries:
>>> from sympy import Matrix
>>> A = Matrix([[1, 0, 0],
... [2, 3, 1],
... [0, 4, 2]])
>>> pxs_best_line(A)
('r', 1)
A matrix whose second column has the fewest non-zero entries:
>>> A = Matrix([[1, 0, 3],
... [2, 0, 1],
... [0, 5, 2]])
>>> pxs_best_line(A)
('c', 2)
Tie between a row and a column — the column is preferred:
>>> A = Matrix([[1, 0, 3],
... [0, 2, 1],
... [4, 5, 6]])
>>> pxs_best_line(A)
('c', 1)
"""
nonzero_row = np.count_nonzero(A, axis = 1)
nonzero_col = np.count_nonzero(A, axis = 0)
row_min, col_min = min(nonzero_row), min(nonzero_col)
if row_min < col_min: # The best is a row
rc = "r" # in this case we perform column operations
k = np.argmin(nonzero_row) + 1
else:
rc = "c"
k = np.argmin(nonzero_col) + 1
return rc, k
[docs]
def pxs_expand_determinant(A, rc = "s", k = 1, color = "red", rc_min = None, k_min = None, **kwargs):
"""
Perform one step of cofactor expansion of a determinant along a chosen
row or column, and return the resulting LaTeX expression and sub-problems.
The expansion row or column can be specified explicitly, or selected
automatically as the one with the fewest non-zero entries. The resulting
minors can optionally have one of their own rows or columns highlighted
in colour, indicating the line that will be used at the next expansion
step.
Parameters
----------
A : Matrix
A square SymPy matrix.
rc : str, optional
``"r"`` to expand along a row, ``"c"`` to expand along a column,
or ``"s"`` (default) to select automatically the best line.
k : int, optional
1-based index of the row or column to expand along. Used only when
``rc`` is ``"r"`` or ``"c"``. Defaults to ``1``.
color : str, optional
Color name to highlight the selected line inside each minor matrix.
Defaults to ``"red"``. Pass ``None`` or ``""`` to disable highlighting.
rc_min : str or None, optional
``"r"`` or ``"c"``, pre-specified choice of expansion direction for
the minors. If ``None`` (default), the best line is selected
automatically for each minor.
k_min : int or None, optional
1-based index of the row or column to highlight inside each minor.
If ``None`` (default), it is determined automatically.
**kwargs
Additional keyword arguments forwarded to SymPy's ``latex()`` function.
Returns
-------
dict
A dictionary with the following keys:
- ``"factors"`` : list of Expr
The signed cofactor scalars ``(-1)^(i+j) * a_{ij}`` for each
non-zero entry in the expansion line.
- ``"minors"`` : list of Matrix
The corresponding ``(n-1) x (n-1)`` minor matrices.
- ``"expr"`` : str
A LaTeX string of the full cofactor expansion
(sum of factors times determinants of minors).
- ``"rc"`` : str
The direction actually used for expansion (``"r"`` or ``"c"``).
- ``"index"`` : int
The 1-based index of the row or column actually used.
Examples
--------
Automatic selection of the best line:
>>> from sympy import Matrix
>>> A = Matrix([[1, 0, 0],
... [2, 3, 4],
... [5, 6, 7]])
>>> result = pxs_expand_determinant(A)
>>> result["rc"], result["index"]
('r', 1)
>>> result["factors"]
[1]
Explicit expansion along the second column:
>>> A = Matrix([[1, 2, 3],
... [0, 4, 0],
... [5, 6, 7]])
>>> result = pxs_expand_determinant(A, rc="c", k=2)
>>> result["rc"], result["index"]
('c', 2)
Disabling minor highlighting:
>>> result = pxs_expand_determinant(A, color=None)
>>> result["expr"] # LaTeX string, no color commands
"""
n = A.rows
if rc == "s":
rc, k = pxs_best_line(A)
A = A.copy() if rc == "r" else A.T
couples = []
M = A.copy()
M.row_del(k - 1)
list_factors, list_minors = [], []
get_rc_minor = rc_min is None or k_min is None
for j in range(n):
if A[k - 1, j]:
factor = (-1) ** (j + k - 1) * A[k - 1, j]
minor = M.copy()
minor.col_del(j)
if rc == "c":
minor = minor.T
if get_rc_minor:
rc_min, k_min = pxs_best_line(minor)
row_color = k_min - 1 if rc_min == "r" and color and n > 3 else "" # if n = 3 then minors are of order 2 hence colouring makes no sense
col_color = k_min - 1 if rc_min == "c" and color and n > 3 else ""
symb = Symbol(myst(r"""\det {{pxsl_mat(minor, color = color, row = row_color, col = col_color)}}""", globals(), locals()))
if factor != 1:
couples.append((factor, symb))
else:
couples.append((symb,))
list_factors.append(factor)
list_minors.append(minor)
addition = Add(*[Mul(*prod, evaluate = False) for prod in couples], evaluate = False)
kwargs_copy = kwargs.copy()
kwargs_copy["order"] = "none"
#expression = LatexPrinter(dict(order = "none"))._print_Add(addition) if len(couples) > 1 else latex(addition, **kwargs)
expression = LatexPrinter(kwargs_copy)._print_Add(addition) if len(couples) > 1 else latex(addition, **kwargs_copy)
result = {
"factors" : list_factors,
"minors" : list_minors,
"expr" : expression,
"rc" : rc,
"index" : k
}
return result
# def pxs_expand_determinant_rec(A, info, depth):
#
# def __det2x2(A):
# a, b, c, d = A
# ad = Mul(a, d, evaluate = not (a * d))
# bc = Mul(b, c, evaluate = not (b * c))
# return myst(r"""\py{latex(ad)} - \py{latex(bc)}""", globals(), locals()).replace("- -", "+")
#
# def __convert_factor(factor):
# if factor == 1:
# return myst(r""" """)
# elif factor == - 1:
# return myst(r""" - """)
# elif isinstance(factor, Add):
# return myst(r""" \left( {{latex(factor)}} \right) """, globals(), locals())
# else:
# return latex(factor)
#
# def __write_lin_comb(l_factors, r_factors):
#
# l_factors = [__convert_factor(fact) for fact in l_factors]
# expression = " + ".join([myst(r"""{{l}} \left( {{r}} \right) """, globals(), locals()) for l, r in zip(l_factors, r_factors)])
# return simplify_plus_minus(expression)
#
# n = A.rows
# if n == 1:
# return latex(A[0]), info
# if n == 2:
# return __det2x2(A), info
#
# expd = pxs_expand_determinant(A)
# if depth == 0:
# info.append((expd["rc"], expd["index"]))
# return expd["expr"], info
#
# l_factors = expd["factors"]
# r_factors, sub_info = zip(*[pxs_expand_determinant_rec(minor, info, depth - 1) for minor in expd["minors"]])
# info.append(list(sub_info))
# return __write_lin_comb(l_factors, r_factors), info
[docs]
def pxs_det_full_expand(A, nb_rep = None, rc = "s", k = 1, name = "A", color = "red", end = True, **kwargs):
# name = False to avoid displaying initial matrix ; name = True to display the explicit matrix
"""
Produce a complete step-by-step cofactor expansion of a determinant,
down to 2x2 determinants, as a LaTeX block.
Starting from the full matrix, the function repeatedly applies
``pxs_expand_determinant`` to each minor, building a sequence of aligned
LaTeX lines that shows the full expansion tree flattened into a single
chain of equalities. At each level, a highlighted row or column inside the
minor matrices indicates where the next expansion will be performed.
Parameters
----------
A : Matrix
A square SymPy matrix.
nb_rep : int or None, optional
Number of expansion steps to perform. Must be at most ``n - 2`` where
``n`` is the matrix size. Defaults to ``None``, meaning all steps are
performed down to 2x2 minors.
rc : str, optional
``"r"`` to always expand along a row, ``"c"`` along a column, or
``"s"`` (default) to select automatically at each step.
k : int, optional
1-based index of the row or column to use when ``rc`` is ``"r"`` or
``"c"``. Defaults to ``1``.
name : str, bool, or None, optional
Controls how the left-hand side of the first equality is displayed.
- A string (e.g. ``"A"``) : displayed as ``\\det A``.
- ``True`` : the explicit matrix is rendered, with the expansion line
highlighted.
- ``False`` or ``None`` : no left-hand side is prepended.
Defaults to ``"A"``.
color : str, optional
Color used to highlight the expansion row or column inside minor
matrices. Defaults to ``"red"``. Pass ``None`` to disable.
end : bool, optional
If ``True`` (default), the expansion is completed all the way to
the final numerical value. If ``False``, the last step stops at
the 2x2 determinant expressions without evaluating them.
**kwargs
Additional keyword arguments forwarded to SymPy's ``latex()`` function.
Returns
-------
all_details : str
A LaTeX string (without the surrounding
``equation*`` environment) showing the full expansion chain.
list_lines : list of str
The individual LaTeX lines of the expansion, including the
left-hand side label if ``name`` is set.
Examples
--------
Full expansion of a 3x3 matrix:
>>> from sympy import Matrix
>>> A = Matrix([[1, 2, 3],
... [0, 4, 5],
... [1, 0, 6]])
>>> details, lines = pxs_det_full_expand(A)
Expanding along the second row explicitly:
>>> details, lines = pxs_det_full_expand(A, rc = "r", k = 2)
Limiting the number of expansion steps to 1:
>>> details, lines = pxs_det_full_expand(A, nb_rep = 1)
Disabling the final numerical evaluation:
>>> details, lines = pxs_det_full_expand(A, end = False)
"""
def __det2x2(A, **kwargs):
a, b, c, d = A
ad = Mul(a, d, evaluate = not (a * d))
bc = Mul(b, c, evaluate = not (b * c))
return myst(r"""{{latex(ad, **kwargs)}} - {{latex(bc, **kwargs)}}""", globals(), locals()).replace("- -", "+")
def __convert_factor(factor, **kwargs):
kwargs_copy = kwargs.copy()
kwargs_copy["order"] = "none"
if factor == 1:
return myst(r""" """)
elif factor == - 1:
return myst(r""" - """)
elif isinstance(factor, Add):
return myst(r""" \left( {{latex(factor, **kwargs_copy)}} \right) """, globals(), locals())
else:
return latex(factor, **kwargs_copy)
def __write_lin_comb(l_factors, r_factors, r_par = None, show_mul = None, **kwargs):
if r_par is None:
r_par = [False] * len(r_factors)
if show_mul is None:
show_mul = [False] * len(r_factors)
mul_symbol = kwargs.get("mul_symbol", "")
if mul_symbol:
muls = [mul_symbol if abs(l) != 1 else myst(r""" """) for l in l_factors]
else:
muls = [myst(r""" \cdot """) if boo else myst(r""" """) for boo in show_mul]
l_factors = [__convert_factor(fact, **kwargs) for fact in l_factors]
r_factors = [myst(r"""\left( {{r}} \right) """, globals(), locals()) if boo else r for r, boo in zip(r_factors, r_par)]
expression = " + ".join([myst(r"""{{l}} {{x}} {{r}} """, globals(), locals()) for l, r, x in zip(l_factors, r_factors, muls)])
return simplify_plus_minus(expression)
rc_min, k_min = (None, None) if rc == "s" else (rc, k)
n = A.rows
nb_rep = min(nb_rep, n - 2) if nb_rep else n - 2
list_lines = []
if n == 2:
list_lines.append(__det2x2(A, **kwargs))
list_lines.append(latex(A.det(), **kwargs))
else:
first_expd = pxs_expand_determinant(A, rc = rc, k = k, color = color, rc_min = rc_min, k_min = k_min, **kwargs)
list_lines.append(first_expd["expr"])
factors, minors = first_expd["factors"], first_expd["minors"]
for _ in range(1, nb_rep):
sub_expds = [pxs_expand_determinant(minor, rc = rc, k = k, color = None, **kwargs) for minor in minors]
sub_expr = [dico["expr"] for dico in sub_expds]
sub_factors = [dico["factors"] for dico in sub_expds]
sub_minors = [dico["minors"] for dico in sub_expds]
r_par = [len(fact) > 0 and (len(fact) > 1 or fact[0].could_extract_minus_sign()) for fact in sub_factors]
show_mul = [len(r_fact) == 0 or (len(r_fact) == 1 and (not rp) and abs(l_fact) != 1 and (not isinstance(l_fact, Add))) for l_fact, r_fact, rp in zip(factors, sub_factors, r_par)] # put a mul sign between factor and subexpr if needed
old_factors = factors
factors = [factors[i] * y for i in range(len(factors)) for y in sub_factors[i]]
minors = sum(sub_minors, start = [])
i0 = [i for i in range(len(sub_minors)) if sub_minors[i]][0] if len(minors) > 0 else None # index of first non-empty sublist in sub_minors (and sub_factors)
if len(minors) > 1 or (len(minors) == 1 and old_factors[i0] != 1 and sub_factors[i0][0] != 1): # intermediary step needed
# old_factors_nn = [x if y else myst(r""" """) for x, y in zip(old_factors, sub_factors)] # for showing 0 instead of 4 . 0 e.g
list_lines.append(__write_lin_comb(old_factors, sub_expr, r_par, show_mul, **kwargs))
list_rck = [pxs_best_line(minor) for minor in minors] if rc == "s" else [(rc, k)] * len(minors)
if color and len(minors) > 0 and minors[0].rows > 2:
l_row_color = [kk - 1 if rrcc == "r" else "" for rrcc, kk in list_rck]
l_col_color = [kk - 1 if rrcc == "c" else "" for rrcc, kk in list_rck]
else: # if order 2 reached, no colouring
l_row_color, l_col_color = [""] * len(minors), [""] * len(minors)
# LA LIGNE CI-DESSOUS POSE UN PROBLÈME NON IDENTIFIÉ...
#minors_tex = [myst(r"""\det {{pxsl_mat(minor, color = color, row = r, col = c)}} """, globals(), locals()) for minor, r, c in zip(minors, l_row_color, l_col_color)]
# en remplacement de la ligne foireuse :
minors_tex = []
for minor, r, c in zip(minors, l_row_color, l_col_color):
mtext = myst(r"""\det {{pxsl_mat(minor, color = color, row = r, col = c)}} """, globals(), locals())
minors_tex.append(mtext)
list_lines.append(__write_lin_comb(factors, minors_tex, **kwargs))
if end and nb_rep == n - 2:
list_lines.append(__write_lin_comb(factors, [__det2x2(minor, **kwargs) for minor in minors], [True] * len(minors), **kwargs))
list_det = [minor.det() for minor in minors]
#last_sum = " + ".join([latex(left * right) for left, right in zip(factors, list_det)])
last_sum = pxsl_add(*[left * right for left, right in zip(factors, list_det)], zeros = True)
if len(factors) > 1:
list_lines.append(last_sum)
list_lines.append(latex(A.det(), **kwargs))
# all_details = myst(r"""
# \begin{equation*}
# \begin{align*}
if isinstance(name, str):
list_lines = [myst(r""" \det {{name}} """, globals(), locals())] + list_lines
elif name:
if rc == "s":
rc, k = pxs_best_line(A)
row_color = k - 1 if rc == "r" and color and n > 2 else ""
col_color = k - 1 if rc == "c" and color and n > 2 else ""
list_lines = [myst(r""" \det {{pxsl_mat(A, color = color, row = row_color, col = col_color)}} """, globals(), locals())] + list_lines
# remove empty lines:
list_lines = [line for line in list_lines if sub(r" *", "", line)]
all_details = myst(r"""
{{list_lines[0]}} &= """, globals(), locals())
all_details += myst(r""" \\
&= """, globals(), locals()).join(list_lines[1:])
# all_details += myst(r"""
# \end{align*}
# \end{equation*}
# """, globals(), locals())
return all_details, list_lines
# === REMOVE WHEN pxs_full_expand NO LONGER USED ===
pxs_full_expand = pxs_det_full_expand
# ==================================================
[docs]
def pxs_char_poly(A, var = r" \lambda ", unit = False, rc = "s", k = 1, name = "A", color = "red", **kwargs):
"""
Produce a complete step-by-step cofactor expansion of a characteristic
polynomial, down to 2x2 determinants, as a LaTeX block.
Starting from the matrix ``A - var*I_n`` (or ``var*I_n - A`` if
``unit=True``), the function calls ``pxs_det_full_expand`` to build a
sequence of aligned LaTeX lines showing the full expansion chain. The last
line is replaced by the canonical factored form of the characteristic
polynomial, computed via ``pxs_Poly``.
Parameters
----------
A : Matrix
A square SymPy matrix.
var : str, optional
LaTeX string for the characteristic variable. Defaults to
``r" \\lambda "``.
unit : bool, optional
If ``False`` (default), the characteristic matrix is ``A - var*I_n``.
If ``True``, it is ``var*I_n - A``.
rc : str, optional
``"r"`` to always expand along a row, ``"c"`` along a column, or
``"s"`` (default) to select automatically at each step.
k : int, optional
1-based index of the row or column to use when ``rc`` is ``"r"`` or
``"c"``. Defaults to ``1``.
name : str, bool, or None, optional
Controls how the left-hand side of the first equality is displayed.
- A string (e.g. ``"A"``): displayed as ``\\det(A - \\lambda I_n)``
or ``\\det(\\lambda I_n - A)`` depending on ``unit``.
- ``True`` : the explicit characteristic matrix is rendered, with the
expansion line highlighted.
- ``False`` or ``None`` : no left-hand side is prepended.
Defaults to ``"A"``.
color : str, optional
Color used to highlight the expansion row or column inside minor
matrices. Defaults to ``"red"``. Pass ``None`` to disable.
**kwargs
Additional keyword arguments forwarded to SymPy's ``latex()`` function.
Returns
-------
all_details : str
A LaTeX string (without the surrounding ``equation*`` environment)
showing the full expansion chain, ending with the canonical form of
the characteristic polynomial.
cpoly : Poly
The characteristic polynomial as a ``pxs_Poly`` object.
Examples
--------
Full expansion of the characteristic polynomial of a 3x3 matrix:
>>> from sympy import Matrix
>>> A = Matrix([[1, 2, 3],
... [0, 4, 5],
... [1, 0, 6]])
>>> details, cpoly = pxs_char_poly(A)
Using the convention ``var*I_n - A`` instead:
>>> details, cpoly = pxs_char_poly(A, unit=True)
Expanding along the second row with a custom variable name:
>>> details, cpoly = pxs_char_poly(A, var=r" \\mu ", rc="r", k=2)
Disabling color highlighting:
>>> details, cpoly = pxs_char_poly(A, color=None)
"""
n = A.rows
assert n == A.cols, "The matrix must be square"
var_sym = Symbol(var)
M = var_sym * eye(n) - A if unit else A - var_sym * eye(n)
_, list_lines = pxs_det_full_expand(M, nb_rep = None, rc = rc, k = k, name = name, color = color, end = True, **kwargs)
cpoly = pxs_Poly(M.det(), var_sym)
cpoly_tex = cpoly.pxsl_print()
list_lines[-1] = cpoly_tex
if isinstance(name, str):
if unit:
list_lines[0] = myst(r""" \det( {{var}} I_{{n}} - {{name}} ) """, globals(), locals())
else:
list_lines[0] = myst(r""" \det( {{name}} - {{var}} I_{{n}} ) """, globals(), locals())
all_details = myst(r"""
{{list_lines[0]}} &= """, globals(), locals()) + myst(r""" \\
&= """, globals(), locals()).join(list_lines[1:])
return all_details, cpoly
[docs]
def pxs_randmatrixrect(p,q,a,b):
"""Returns a rectangular matrix with p rows and q columns such that every coefficient is a realization of
of a discrete random variable on range(a, b)
:returns: LaTeX bmatrix as a string
"""
M= eye(p,q)
for i in range(p):
for j in range(q):
M[i,j] = sample(DiscreteUniform('h', range(a, b)))
return M
[docs]
def pxs_cptrzeros(M):
"""En: Counts the number of zero entries in the given matrix M.
cpte le nombre de coefficients nuls dans une matrice donnée"""
cptr = 0
p = M.cols
q = M.rows
for i in range(p):
for j in range(q):
if M[i,j] == 0:
cptr = cptr + 1
return cptr
[docs]
def pxs_matelement(p,q,i,j):
""" En: Renvoie la matrice élémentaire de p lignes et q colonnes avec un 1 à la place i,j.
Returns the elementary matrix with p rows and q columns with a 1 at position (i, j).
Fr: Renvoie la matrice élémentaire de p lignes et q colonnes avec un 1 à la place i,j."""
if i > p or j>q:
raise "l'indice de ligne ou de colonne est trop grand"
else:
M = Matrix(zeros(p, q))
M[i-1,j-1]=1
return M
[docs]
def pxs_randmatrixInv(p,a,b,r):
"""En: Returns an invertible square matrix of size p, with r percent of non-zero entries
(thus [r.p]+1 non-zero elements), and all entries are between a and b-1.
Retourne une matrice carrée de taille p, inversible et dont le nbre de coefficients non nuls est de r
pourcent (donc en fait [r.p]+1 éléments non nuls) et dont tous les coefficients sont compris
entre a et b-1"""
alphaprimeo = r*p*p/100
alphaprime = int(r*p*p/100)
alphaprimeseconde = alphaprimeo -alphaprime
H = eye(p)
S = eye(p)
if alphaprimeseconde < 1/2:
alpha = alphaprime
#print('alpha = ', alpha)
else :
alpha= alphaprime +1
#print('alpha = ', alpha)
# if alphaprime == p**2:
# alpha= p**2 #Nbre de coefficients qui devront être non nuls dans P
# else :
# alpha= int(r*p*p/100)+1 #Nbre de coefficients qui devront être non nuls dans P
beta=p**2 - alpha #Nbre de coefficients qui devront être nuls dans P
#print('beta = ', beta)
if alpha < p:
print('r is too small! Choose a greater value for r.')
else :
P = pxs_randmatrixrect(p,p,a,b)
q= P.det()
#print('(P,det(P)) = ', P, P.det())
#q= P.det()-q
#print('q = ', q)
rho = 0
while q == 0:
P = pxs_randmatrixrect(p,p,a,b)
q = P.det()
#print('q = ', q)
rho = rho+1
# A ce stade la matrice P est inversible
# On veut que P ait exactement alpha coef non nuls et p^2-alpha nulles.
#print('rho = ', rho) #Donc si rho = 0 c'est qu'on n'est pas rentré dans la boucle et que la matrice est inversible depuis le début
#print('P = ', P, ' et det(P) = ', P.det())
cptrzeros = pxs_cptrzeros(P)
# A ce stade cpt rzeros est égal au nombre de coefs nuls contenus dans P
if cptrzeros == beta:
H = P
else :
if cptrzeros < beta: #Donc ici la matrice P a trop peu de zéros, il faut en rajouter.
while cptrzeros < beta:
for i in range(p):
if cptrzeros == beta:
break
else :
for j in range(p):
if cptrzeros == beta:
break
else :
if P[i,j] != 0:
S=P-P[i,j]*pxs_matelement(p,p,i+1,j+1)
if S.det() != 0:
P=S
cptrzeros = pxs_cptrzeros(P)
#print('P = ', P)
#cptrzeros = cptrzeros +1
#print('cptrzeros = ', cptrzeros)
H = P
else : #Donc ici la matrice P a trop de zéros, il faut en retirer.
while cptrzeros > beta:
for i in range(p):
if cptrzeros == beta:
break
else :
for j in range(p):
if cptrzeros == beta:
break
else :
if P[i,j] == 0:
S=P+pxs_matelement(p,p,i+1,j+1)
if S.det() != 0:
P=S
cptrzeros = cptrzeros -1
H = P
perm_rows = rd.sample([i for i in range(p)], p)
perm_cols = rd.sample([i for i in range(p)], p)
H = H[perm_rows, perm_cols]
return H