"""Mes_fctions_generalistes_bis - Gestion de certaines fonctions de formatage Latex.
Pour voir les tests unitaires s'afficher dans l'éditeur
----------------------------------------------------------------------
>>> \\begin{python}
>>> # Code Python : Ecrivez ci-dessous votre code Python
>>> from src.scripts.tests.test_Mes_fctions_generalistes_bis import print_tests_Mes_fctions_generalistes_bis
>>> print_tests_Mes_fctions_generalistes_bis()
>>> \\end{python}
"""
from sympy import *
from fractions import Fraction
from sympy import latex as sympy_latex
from sympy.printing.latex import LatexPrinter
from src.scripts.pxs_runtime import get_pxs_lang, myst
import random as rd
from re import sub, match
import math as m
## ============= IMPORTÉ DE Classes_Extensions
[docs]
class pxs_Interval(Interval):
"""
Classe personnalisée héritant de Interval de SymPy
Permet un affichage formaté des intervalles avec :
- Séparateurs de milliers pour les grands nombres
- Notation française pour l'infini et les intervalles
"""
[docs]
@classmethod
def from_Interval(cls, interval):
"""Convertit un Interval standard en pxs_Interval."""
if not isinstance(interval, Interval):
raise TypeError("L'objet fourni n'est pas un Interval")
return cls(interval.start, interval.end,
left_open=interval.left_open,
right_open=interval.right_open)
[docs]
def print(self, res_num = False, dec = 4):
"""
Méthode pour générer une représentation LaTeX formatée de l'intervalle
Returns:
str: Chaîne LaTeX formatée selon les conventions françaises
"""
# Formatage de la borne gauche si elle n'est pas -∞
if self.left != -oo:
# Exemple: 1000 devient "1\ 000"
if isinstance(self.left, (int, Integer)):
left_formate = f"{int(self.left):,}".replace(",", r"\ ")
elif isinstance(self.left, Symbol):
left_formate = latex(self.left)
else:
if res_num:
#from src.scripts.Mes_fctions.Mes_fctions_generalistes_bis import pxsl_num
left_formate = pxsl_num(self.left, dec = dec)
else:
left_formate = latex(self.left)
else:
left_formate = myst(r""" -\infty """)
# Formatage de la borne gauche si elle n'est pas -∞
if self.right != oo:
# Exemple: 1000 devient "1\ 000"
if isinstance(self.right, (int, Integer)):
right_formate = f"{int(self.right):,}".replace(",", r"\ ")
elif isinstance(self.right, Symbol):
right_formate = latex(self.right)
else:
if res_num:
#from src.scripts.Mes_fctions.Mes_fctions_generalistes_bis import pxsl_num
right_formate = pxsl_num(self.right, dec = dec)
else:
right_formate = latex(self.right)
else:
right_formate = myst(r""" \inftys """)
# Application des conventions françaises si la langue est française
# Note: pxs_lang doit être définie ailleurs dans le programme
pxs_lang = get_pxs_lang()
if pxs_lang == "fr":
l_delim = r'\left[' if not self.left_open else r'\left]'
r_delim = r'\right]' if not self.right_open else r'\right['
if pxs_lang == "en":
l_delim = r'\left[' if not self.left_open else r'\left('
r_delim = r'\right]' if not self.right_open else r'\right)'
# Conversion des crochets selon la notation française
# En français: ]a,b[ au lieu de (a,b) pour les intervalles ouverts
if pxs_lang == "fr":
Inter_latex = myst(r""" {{l_delim}}{{left_formate}};{{right_formate}}{{r_delim}} """, globals(), locals())
else:
Inter_latex = myst(r""" {{l_delim}}{{left_formate}},{{right_formate}}{{r_delim}} """, globals(), locals())
# Retour de la chaîne LaTeX formatée
return Inter_latex
## =======================================================
# Les fonctions à tester
[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",
}
def pxsl_latex(expr, **kwargs):
"""
Wrapper around SymPy's `latex` function that applies custom configuration
options based on the current pyxisciences language settings.
This function retrieves the LaTeX configuration using `pxs_config()` and
passes it to SymPy's `latex` function to ensure consistent formatting in
LaTeX output.
Parameters
----------
expr : sympy.Expr
The SymPy expression to convert to LaTeX.
**kwargs
Additional keyword arguments to pass to SymPy's `latex` function.
Returns
-------
str
The LaTeX string representation of the expression, formatted according
to the current language settings.
Examples
--------
>>> from sympy import symbols, sin
>>> x = symbols('x')
>>> pxsl_latex(sin(x))
'\\sin{\\left(x \\right)}'
"""
config = pxs_config()
return sympy_latex(expr, **config, **kwargs)
[docs]
def pxsl_mult(val, mult=myst(r"""\cdot""")):
"""
Return a multiplication symbol or a blank space depending on the coefficient
value.
This function is used to avoid displaying an explicit multiplication symbol
when the coefficient is 0, 1, or -1, following standard mathematical
conventions.
Parameters
----------
val : Any
Coefficient value to be tested (typically a numeric or SymPy object).
mult : Any, optional
LaTeX representation (or compatible object) of the multiplication symbol.
Default is ``myst(r"\\cdot")``.
Returns
-------
Any
The multiplication symbol if `val` is not equal to 0, 1, or -1;
otherwise, a blank space.
Examples
--------
>>> pxsl_mult(3)
\\cdot
>>> pxsl_mult(1)
"""
if val != 1 and val != -1 and val != 0:
return mult
else:
return myst(r""" """)
[docs]
def pxsl_symb(val, symb=myst(r"""+"""), ones=True):
"""
Return a symbol (e.g. ``+``, ``-``, ``\\cdot``) or a blank space depending
on the coefficient value and formatting options.
This function controls whether an explicit symbol should be displayed in
LaTeX output, depending on the value of a coefficient and common
mathematical conventions.
By default (``ones=True``), the symbol is displayed whenever the coefficient
is nonzero (including when it is 1 or -1). Setting ``ones=False`` restores
the usual convention where the symbol is omitted for coefficients 0, 1, or -1.
Parameters
----------
val : Any
Coefficient value to be tested (typically a numeric or SymPy object).
symb : Any, optional
LaTeX representation (or compatible object) of the symbol to display
(e.g. ``myst(r"+")``, ``myst(r"\\cdot")``).
Default is ``myst(r"+")``.
ones : bool, optional
If ``True`` (default), the symbol is displayed whenever ``val`` is
nonzero (including when ``val`` is 1 or -1).
If ``False``, the symbol is omitted when ``val`` is 0, 1, or -1.
Returns
-------
Any
The symbol if the conditions are met; otherwise, a blank space
(``myst(r" ")``).
Examples
--------
>>> pxsl_symb(3)
+
>>> pxsl_symb(1)
+
>>> pxsl_symb(1, ones=False)
>>> pxsl_symb(0)
>>> pxsl_symb(0, ones=True)
"""
if ones:
if val != 0:
return symb
else:
return myst(r""" """)
else:
if val != 1 and val != -1 and val != 0:
return symb
else:
return myst(r""" """)
[docs]
def pxsl_sign(expr: str):
''' {py:function} Returns the sign of an expression in LaTeX format.
This function takes an expression and returns its sign in LaTeX format:
'+' if the expression is positive, '-' if it is negative,
and '' (empty string) if it is zero.
:param sympy.Expr: The expression whose sign we want to determine.
:return: The sign of the expression in LaTeX format.
:rtype: str
Examples
--------
>>> pxsl_sign(5)
'+'
>>> pxsl_sign(-3)
'-'
>>> pxsl_sign(0)
''
'''
if expr > 0:
return myst(r"""+""", globals(), locals())
elif expr < 0:
return myst(r"""-""", globals(), locals())
else:
return myst(r"""""" , globals(), locals())
[docs]
def latex_coefficient(coeff, variable = None, sign = False, zeros = True, ones = False, display = True):
"""
Formats a coefficient for LaTeX display.
This function formats a coefficient for display in a LaTeX polynomial expression.
It handles special cases where the coefficient is 1 or -1 and provides options for
displaying signs, omitting zeros, or showing numerical ones.
Parameters
----------
coeff : int, float, or sympy.Expr
The coefficient to format.
sign : None or '+'
If '+', a '+' sign is displayed before the expression when it is positive.
variable : str or Symbol, optional
Expression or variable attached to the coefficient (can be omitted if zeros=False).
zeros : bool, default True
If False, the coefficient and its variable are not written when the coefficient is zero.
ones : bool, default False
If False, -1 is written as '-' and 1 as an empty string.
If True, both -1 and 1 are kept as numeric values.
display : bool, optional
Whether to produce display-mode LaTeX (used in the examples below).
Returns
-------
str
The coefficient formatted for LaTeX. Returns an empty string for coeff=1,
'-' for coeff=-1, and the formatted representation otherwise.
Examples
--------
>>> pxsl_latex_coefficient(1)
''
>>> pxsl_latex_coefficient(-1, ones = True)
'-1'
>>> pxsl_latex_coefficient(-1)
'-'
>>> pxsl_latex_coefficient(5)
'5'
>>> pxsl_latex_coefficient(5, sign = True)
'+5'
>>> pxsl_latex_coefficient(1500)
'1\\ 500'
>>> pxsl_latex_coefficient(0, variable = Symbol('L_1'), zeros = True)
'0L_1'
>>> pxsl_latex_coefficient(0, variable = Symbol('L_1'), zeros = False)
''
>>> pxsl_latex_coefficient(Rational(-5, 2), sign = '+', display = False)
'-\\frac{5}{2}'
>>> pxsl_latex_coefficient(Rational(-5, 2), sign='+')
'-\\displaystyle \\frac{5}{2}'
"""
config_standard = pxs_config()
if zeros == False and coeff == 0:
return myst(r""" """)
elif variable is not None:
return myst(
r"""\py{pxsl_latex_coefficient(coeff, sign=sign, zeros=zeros, ones=ones, display=display)}\py{latex(variable, **config_standard)}""",
globals(), locals()
)
if coeff == 1:
if sign:
return myst(r""" +\; """) if ones == False else myst(r"""+ \;\py{latex(coeff, **config_standard)} """, globals(), locals())
else:
return myst(r""" """) if ones == False else myst(r"""\py{latex(coeff, **config_standard)} """, globals(), locals())
elif coeff == -1:
if sign:
return myst(r""" -\; """) if ones == False else myst(r"""- \;\py{latex(abs(coeff), **config_standard)} """, globals(), locals())
else:
return myst(r""" - """) if ones == False else myst(r"""\py{latex(coeff, **config_standard)} """, globals(), locals())
else:
return pxsl_latex_with_formatting(coeff, sign=sign, display=display)
[docs]
def pxsl_latex_coefficient(coeff, variable=None, sign=False, zeros=True, ones=False, display=True):
"""
Formats a coefficient for LaTeX display.
This function formats a coefficient for display in a LaTeX polynomial expression.
It handles special cases where the coefficient is 1 or -1 and provides options for
displaying signs, omitting zeros, or showing numerical ones.
Parameters
----------
coeff : int, float, or sympy.Expr
The coefficient to format.
sign : None or '+'
If '+', a '+' sign is displayed before the expression when it is positive.
variable : str or Symbol, optional
Expression or variable attached to the coefficient (can be omitted if zeros=False).
zeros : bool, default True
If False, the coefficient and its variable are not written when the coefficient is zero.
ones : bool, default False
If False, -1 is written as '-' and 1 as an empty string.
If True, both -1 and 1 are kept as numeric values.
display : bool, optional
Whether to produce display-mode LaTeX (used in the examples below).
Returns
-------
str
The coefficient formatted for LaTeX. Returns an empty string for coeff=1,
'-' for coeff=-1, and the formatted representation otherwise.
Examples
--------
>>> pxsl_latex_coefficient(1)
''
>>> pxsl_latex_coefficient(-1, ones = True)
'-1'
>>> pxsl_latex_coefficient(-1)
'-'
>>> pxsl_latex_coefficient(5)
'5'
>>> pxsl_latex_coefficient(5, sign = True)
'+5'
>>> pxsl_latex_coefficient(1500)
'1\\ 500'
>>> pxsl_latex_coefficient(0, variable = Symbol('L_1'), zeros = True)
'0L_1'
>>> pxsl_latex_coefficient(0, variable = Symbol('L_1'), zeros = False)
''
>>> pxsl_latex_coefficient(Rational(-5, 2), sign = '+', display = False)
'-\\frac{5}{2}'
>>> pxsl_latex_coefficient(Rational(-5, 2), sign='+')
'-\\displaystyle \\frac{5}{2}'
"""
config_standard = pxs_config()
if zeros == False and coeff == 0:
return myst(r""" """)
elif variable is not None:
return myst(
r"""\py{pxsl_latex_coefficient(coeff, sign=sign, zeros=zeros, ones=ones, display=display)}\py{latex(variable, **config_standard)}""",
globals(), locals()
)
if coeff == 1:
if sign:
return myst(r""" +\; """) if ones == False else myst(r"""+ \;\py{latex(coeff, **config_standard)} """, globals(), locals())
else:
return myst(r""" """) if ones == False else myst(r"""\py{latex(coeff, **config_standard)} """, globals(), locals())
elif coeff == -1:
if sign:
return myst(r""" -\; """) if ones == False else myst(r"""- \;\py{latex(abs(coeff), **config_standard)} """, globals(), locals())
else:
return myst(r""" - """) if ones == False else myst(r"""\py{latex(coeff, **config_standard)} """, globals(), locals())
else:
return pxsl_latex_with_formatting(coeff, sign=sign, display=display)
[docs]
def to_rational_or_symbol(value):
"""
Convertit un nombre en Rational SymPy ou garde un symbole SymPy.
Cette fonction prend une valeur et la convertit en objet Rational de SymPy
si c'est un nombre, ou la garde telle quelle si c'est déjà un symbole SymPy.
Pour les flottants, elle utilise Fraction pour une conversion précise.
Parameters
----------
value : int, float, sympy.Symbol, or sympy.Rational
La valeur à convertir.
Returns
-------
sympy.Rational or sympy.Symbol or any
La valeur convertie en Rational si c'est un nombre, ou la valeur
originale si c'est un symbole ou autre type.
Examples
--------
>>> from sympy import Symbol
>>> x = Symbol('x')
>>> to_rational_or_symbol(5)
Rational(5, 1)
>>> to_rational_or_symbol(0.5)
Rational(1, 2)
>>> to_rational_or_symbol(0.5)
Rational(1, 2)
>>> to_rational_or_symbol(x)
Symbol('x')
"""
if isinstance(value, Symbol):
return value
elif isinstance(value, (int, Rational)):
return Rational(value)
elif isinstance(value, float):
frac = Fraction.from_float(value).limit_denominator()
return Rational(frac.numerator, frac.denominator)
else:
return value
[docs]
def pxsl_to_rational_or_symbol(value):
r"""
\en{Converts a number to a SymPy Rational or keeps a SymPy symbol as is.}
\fr{Convertit un nombre en Rational SymPy ou garde un symbole SymPy tel quel.}
\en{This function takes a value and converts it into a SymPy `Rational`
object if it is numeric, or keeps it unchanged if it is already a SymPy `Symbol`.
For floats, it uses Python's `Fraction` class to ensure a precise rational conversion.}
\fr{Cette fonction prend une valeur et la convertit en objet `Rational` de SymPy
si c’est un nombre, ou la garde telle quelle si c’est déjà un symbole SymPy.
Pour les flottants, elle utilise la classe `Fraction` de Python pour une conversion rationnelle précise.}
Parameters
----------
value : int, float, sympy.Symbol, or sympy.Rational
\en{The value to convert.}
\fr{La valeur à convertir.}
Returns
-------
sympy.Rational or sympy.Symbol or any
\en{The value converted to `Rational` if numeric, or the original value
if it is a symbol or another type.}
\fr{La valeur convertie en `Rational` si c’est un nombre, ou la valeur
originale si c’est un symbole ou un autre type.}
Examples
--------
>>> from sympy import Symbol
>>> x = Symbol('x')
>>> pxsl_to_rational_or_symbol(5)
Rational(5, 1)
>>> pxsl_to_rational_or_symbol(0.5)
Rational(1, 2)
>>> pxsl_to_rational_or_symbol(x)
Symbol('x')
"""
if isinstance(value, Symbol):
return value
elif isinstance(value, (int, Rational)):
return Rational(value)
elif isinstance(value, float):
frac = Fraction.from_float(value).limit_denominator()
return Rational(frac.numerator, frac.denominator)
else:
return value
[docs]
def resoudre_inequation_generale(a = 1, b = 0, c = 0, variable = "x", inegalite = ">=", domaine = "R", puissance = 1, signe_a = None, detail_signe_a = False):
"""
Solves an inequality of the form ax^p + b ≥ c with detailed step-by-step reasoning in LaTeX.
This function solves linear (p=1) or quadratic (p=2) inequalities and generates
a complete LaTeX-formatted solution showing all intermediate steps. It handles
symbolic coefficients, different domains (real, integer, natural numbers), and
cases where the sign of coefficient 'a' needs to be analyzed separately.
Parameters
----------
a : int, float, or sympy expression, optional
Coefficient of the variable term. Default is 1.
b : int, float, or sympy expression, optional
Constant term on the left side. Default is 0.
c : int, float, or sympy expression, optional
Constant term on the right side. Default is 0.
variable : str, optional
Name of the variable. Default is "x".
inegalite : str, optional
Inequality symbol: ">=", ">", "<=", or "<". Default is ">=".
domaine : str, optional
Solution domain: "R" (reals), "Z" (integers), or "N" (natural numbers).
Default is "R".
puissance : int, optional
Exponent of the variable: 1 (linear) or 2 (quadratic). Default is 1.
signe_a : str or None, optional
Sign of coefficient 'a' when symbolic: ">" for positive, "<" for negative,
or None to determine automatically. Default is None.
detail_signe_a : bool, optional
Whether to explicitly mention the sign of 'a' when dividing. Default is False.
Returns
-------
tuple
A tuple (solution_set, latex_reasoning) where:
- solution_set : sympy set or dict
The solution set. Returns a dict with cases if the sign of 'a'
or the right-hand side is undetermined.
- latex_reasoning : str
Complete LaTeX-formatted step-by-step solution.
Raises
------
ValueError
If inegalite is not in [">=", ">", "<=", "<"].
If domaine is not in ["R", "Z", "N"].
If puissance is not 1 or 2.
If signe_a is not None, ">", or "<".
Examples
--------
>>> # Simple linear inequality
>>> sol, latex = resoudre_inequation_generale(2, 3, 7, variable="x", inegalite=">=")
>>> print(sol)
[2, oo)
>>> # Quadratic inequality
>>> sol, latex = resoudre_inequation_generale(1, 0, 4, puissance=2, inegalite="<=")
>>> print(sol)
[-2, 2]
>>> # Integer domain
>>> sol, latex = resoudre_inequation_generale(3, -1, 5, domaine="Z", inegalite=">")
>>> print(sol)
{3, 4, 5, ...}
>>> # Symbolic coefficient with sign analysis
>>> from sympy import Symbol
>>> a = Symbol('a')
>>> sol, latex = resoudre_inequation_generale(a, 0, 5, inegalite=">=")
>>> # Returns dict with cases for a>0 and a<0
"""
# ======= FONCTIONS AUXILIAIRES =================
def __ceil(x):
return (x if x.is_integer else floor(x) + 1)
def __simplifier_signes(chaine):
return chaine.replace("+ + ", "+ ").replace("+ -", "-").replace("- + ", "- ").replace("- -", "+")
def __test_a_frac(a):
if isinstance(a, Rational) and a.q != 1:
return True
if isinstance(a, Mul):
for fac in a.args:
if isinstance(fac, Rational) or (isinstance(fac, Pow) and fac.args[1].is_integer and fac.args[1].is_negative):
return True
return False
def __solutions_inequation_generale(a=1, b=0, c=0, inegalite=">=", domaine="R", puissance=1, signe_a=None, signe_quotient = None):
# import retiré car risque d'importation circulaire
# from src.scripts.Mes_fctions.Classes_Extensions import pxs_Interval
a = sympify(a)
b = sympify(b)
c = sympify(c)
dico_ensembles = {"R" : Reals, "N" : Naturals, "Z" : Integers}
ensemble = dico_ensembles[domaine]
quotient = (c - b) / a
if a == 0:
valeur = b - c
dico_cas_existence = {"<=" : valeur.is_nonpositive, "<" : valeur.is_negative,
">=" : valeur.is_nonnegative, ">" : valeur.is_positive}
cas_existence = dico_cas_existence[inegalite]
sol = ensemble if cas_existence else EmptySet
elif a.is_negative or signe_a == "<":
dico_inverse = {"<=" : ">=", "<" : ">", ">=" : "<=", ">" : "<"}
symb_inverse = dico_inverse[inegalite]
return __solutions_inequation_generale(-a, -b, -c, inegalite = symb_inverse, domaine = domaine, puissance = puissance, signe_a = ">", signe_quotient = signe_quotient)
elif a.is_nonnegative or signe_a == ">":
if puissance == 1:
if inegalite == ">=":
sol = pxs_Interval(quotient, oo)
elif inegalite == ">":
sol = pxs_Interval.open(quotient, oo)
elif inegalite == "<=":
sol = pxs_Interval(-oo, quotient)
else:
sol = pxs_Interval.open(-oo, quotient)
elif puissance == 2:
if quotient.is_negative or signe_quotient == "<":
if inegalite in [">=", ">"]:
sol = ensemble
else:
sol = EmptySet
elif quotient.is_nonnegative or signe_quotient == ">":
racine = simplify(sqrt(quotient))
if inegalite == ">=":
sol = pxs_Interval(-oo, -racine).union(pxs_Interval(racine, oo))
elif inegalite == ">":
sol = pxs_Interval.open(-oo, -racine).union(pxs_Interval.open(racine, oo))
elif inegalite == "<=":
sol = pxs_Interval(-racine, racine)
else:
sol = pxs_Interval.open(-racine, racine)
else: # cas où le signe de quotient est indéterminé
sol_rhs_pos = __solutions_inequation_generale(a, b, c, inegalite = inegalite, domaine = domaine, puissance = puissance, signe_a = signe_a, signe_quotient = ">")
sol_rhs_neg = __solutions_inequation_generale(a, b, c, inegalite = inegalite, domaine = domaine, puissance = puissance, signe_a = signe_a, signe_quotient = "<")
return {"rhs>0" : sol_rhs_pos, "rhs<0" : sol_rhs_neg}
else: # cas où le signe de a est indéterminé
sol_pos = __solutions_inequation_generale(a, b, c, inegalite = inegalite, domaine = domaine, puissance = puissance, signe_a = ">", signe_quotient = signe_quotient)
sol_neg = __solutions_inequation_generale(a, b, c, inegalite = inegalite, domaine = domaine, puissance = puissance, signe_a = "<", signe_quotient = signe_quotient)
return {"a>0" : sol_pos, "a<0" : sol_neg}
if domaine in ["Z", "N"]:
try:
sol = sol.intersect(Integers) if domaine == "Z" else sol.intersect(Naturals0)
except:
sol = Intersection(sol, Integers, evaluate = False) if domaine == "Z" else Intersection(sol, Naturals0, evaluate = False)
return sol
# ===================================================================
if inegalite not in [">=", ">", "<=", "<"]:
raise ValueError("Inégalité non valide")
if domaine not in ["R", "Z", "N"]:
raise ValueError("Domaine non valide")
if puissance not in [1, 2]:
raise ValueError("Puissance non valide (1 ou 2 seulement)")
if signe_a not in [None, ">", "<"]:
raise ValueError("Signe de a non valide. Utilisez None, '>' pour positif, ou '<' pour négatif.")
# Convertir les paramètres
a = to_rational_or_symbol(a)
b = to_rational_or_symbol(b)
c = to_rational_or_symbol(c)
# Symbole associé à la variable
var_symb = Symbol(variable)
ensemble_solution = __solutions_inequation_generale(a, b, c, inegalite, domaine, puissance, signe_a)
# Si le signe de a n'est pas déterminé, on distingue deux cas :
if not (signe_a in [">", "<"] or sympify(a).is_nonpositive or sympify(a).is_nonnegative):
latex_a = latex_avec_formatage(a)
raisonnement_latex = myst(r"""On distingue deux cas :
1. Si \py{latex_a} > 0 :
""", locals(), globals())
raisonnement_latex += resoudre_inequation_generale(a, b, c, variable = variable, inegalite = inegalite, domaine = domaine, puissance = puissance, signe_a = ">")[-1]
raisonnement_latex += myst(r"""
2. Si \py{latex_a} < 0 :
""", locals(), globals())
raisonnement_latex += resoudre_inequation_generale(a, b, c, variable = variable, inegalite = inegalite, domaine = domaine, puissance = puissance, signe_a = "<")[-1]
raisonnement_latex = __simplifier_signes(raisonnement_latex)
return ensemble_solution, raisonnement_latex # L'ensemble des solutions est indéterminé
raisonnement_latex = myst(r"""\begin{equation*}\begin{align*}""", locals(), globals())
# symbole d'inégalité initial (et son affichage latex)
symb = inegalite
dico_symboles_latex = {">=": myst(r"""\geq""", locals(), globals()), ">": myst(r""">""", locals(), globals()), "<=": myst(r"""\leq""", locals(), globals()), "<": myst(r"""<""", locals(), globals())}
symb_inegalite = dico_symboles_latex[inegalite]
# Construire l'inéquation d'origine
# si a est une somme, on met des parenthèses autour
coeff_str = latex_coefficient(a)
coeff_str = myst(r"""\left(\py{coeff_str}\right)""", locals(), globals()) if a.is_Add else coeff_str
latex_c = latex_avec_formatage(c)
# Expression du côté gauche
if sympify(b).is_zero:
expression = sympy_latex(a * var_symb ** puissance)
expr_gauche = myst(r"""\py{expression}""", locals(), globals())
elif isinstance(b, (int, Rational)) and b >= 0:
latex_b = latex_avec_formatage(b)
if puissance == 1:
expr_gauche = myst(r"""\py{coeff_str}\py{variable} + \py{latex_b}""", locals(), globals()) if coeff_str else myst(r"""\py{variable} + \py{latex_b}""", locals(), globals())
else:
expr_gauche = myst(r"""\py{coeff_str}\py{variable}^2 + \py{latex_b}""", locals(), globals()) if coeff_str else myst(r"""\py{variable}^2 + \py{latex_b}""", locals(), globals())
elif isinstance(b, (int, Rational)) and b < 0:
latex_abs_b = latex_avec_formatage(abs(b))
if puissance == 1:
expr_gauche = myst(r"""\py{coeff_str}\py{variable} - \py{latex_abs_b}""", locals(), globals()) if coeff_str else myst(r"""\py{variable} - \py{latex_abs_b}""", locals(), globals())
else:
expr_gauche = myst(r"""\py{coeff_str}\py{variable}^2 - \py{latex_abs_b}""", locals(), globals()) if coeff_str else myst(r"""\py{variable}^2 - \py{latex_abs_b}""", locals(), globals())
else:
latex_b = latex_avec_formatage(b)
if puissance == 1:
expr_gauche = myst(r"""\py{coeff_str}\py{variable} + \py{latex_b}""", locals(), globals()) if coeff_str else myst(r"""\py{variable} + \py{latex_b}""", locals(), globals())
else:
expr_gauche = myst(r"""\py{coeff_str}\py{variable}^2 + \py{latex_b}""", locals(), globals()) if coeff_str else myst(r"""\py{variable}^2 + \py{latex_b}""", locals(), globals())
# CAS DÉGÉNÉRÉ: a = 0
if a == 0:
latex_b_simple = latex_avec_formatage(b)
raisonnement_latex += myst(r"""\py{latex_b_simple} \py{symb_inegalite} \py{latex_c}""", locals(), globals())
if ensemble_solution == EmptySet:
raisonnement_latex += myst(r"""\text{ impossible.}""", locals(), globals())
else:
raisonnement_latex += myst(r"""&\text{ toujours vrai.}""", locals(), globals())
raisonnement_latex += myst(r"""\\ \end{align*}\end{equation*}""", locals(), globals())
raisonnement_latex = __simplifier_signes(raisonnement_latex)
return ensemble_solution, raisonnement_latex
# Première ligne: l'inéquation d'origine
raisonnement_latex += myst(r"""
\py{expr_gauche} \py{symb_inegalite} \py{latex_c}""", locals(), globals())
valeur_droite = c - b
# Étape 1: Isoler le terme en x - construire c - b avec gestion des signes
if not sympify(b).is_zero: # si b est nul il n'y a rien à faire à cette étape
latex_b = latex_avec_formatage(b)
latex_c = latex_avec_formatage(c)
if c == 0:
latex_droite_etape1 = latex_avec_formatage(-b)
elif b.is_Add:
latex_droite_etape1 = latex_c + " - " + myst(r"""\left(\py{latex_b}\right)""", locals(), globals())
else:
latex_droite_etape1 = __simplifier_signes(latex_c + " - " + latex_b)
if puissance == 1:
terme_variable = myst(r"""\py{coeff_str}\py{variable}""", locals(), globals()) if coeff_str else variable
else:
terme_variable = myst(r"""\py{coeff_str}\py{variable}^2""", locals(), globals()) if coeff_str else myst(r"""\py{variable}^2""", locals(), globals())
raisonnement_latex += myst(r""" &\iff \py{terme_variable} \py{symb_inegalite}
\py{latex_droite_etape1} \\""", locals(), globals())
# Etape de simplification de c-b
latex_mb = latex_avec_formatage(-b)
expr_equiv = __simplifier_signes(myst(r"""\py{latex_mb} + \py{latex_c}""", locals(), globals())) # si la réecriture est équivalente, on ne la détaille pas
if latex_droite_etape1 != latex_avec_formatage(c - b) and latex_avec_formatage(c - b) != expr_equiv:
latex_droite_etape1 = latex_avec_formatage(c - b)
raisonnement_latex += myst(r""" &\iff \py{terme_variable} \py{symb_inegalite}
\py{latex_droite_etape1}\\""", locals(), globals())
else: # si b=0
latex_droite_etape1 = latex_avec_formatage(c)
# Pour la fraction, utiliser la même logique de gestion des signes
latex_fraction_num = latex_droite_etape1
latex_a = latex_avec_formatage(a)
# A partir de là on va diviser par a : le sens des inégalités peut changer
if sympify(a).is_negative or signe_a == "<":
symb = {"<=" : ">=", "<" : ">", ">=" : "<=", ">" : "<"}[inegalite]
symb_inegalite = dico_symboles_latex[symb]
# Étape 2: Solution selon le signe de a (avec option signe_a)
# Désormais le membre de gauche vaudra juste variable ou variable^2 :
if puissance == 1:
terme_variable = myst(r"""\py{variable}""", locals(), globals())
else:
terme_variable = myst(r"""\py{variable}^2""", locals(), globals())
if signe_a is None:
signe_a = ">" if sympify(a).is_positive else "<"
if detail_signe_a or a.free_symbols:
car_a = myst(r""", \quad \text{car } \py{latex_a} \py{signe_a} 0""", locals(), globals())
else:
car_a = " "
if a == 1:
pass
elif a == -1:
symb_inverse = {">=": myst(r"""\leq""", locals(), globals()), ">": myst(r"""<""", locals(), globals()), "<=": myst(r"""\geq""", locals(), globals()), "<": myst(r""">""", locals(), globals())}[inegalite]
latex_membre_droite = latex_avec_formatage(b - c)
raisonnement_latex += myst(r"""&\iff \py{terme_variable} \py{symb_inegalite} \py{latex_membre_droite}\\""", locals(), globals())
elif c - b == 0:
raisonnement_latex += myst(r"""&\iff \py{terme_variable} \py{symb_inegalite} 0 \py{car_a}\\""", locals(), globals())
elif __test_a_frac(a): # S'il est plus pertinent de multiplier par 1/a que de diviser par a
fraction = Mul(c - b, 1 / a, evaluate = False)
latex_fraction = latex_avec_formatage(fraction)
raisonnement_latex += myst(r"""&\iff \py{terme_variable} \py{symb_inegalite} \py{latex_fraction} \py{car_a}\\""", locals(), globals())
fraction1 = Mul(c - b, 1 / a)
if fraction1 != fraction:
latex_fraction = latex_avec_formatage(fraction1)
raisonnement_latex += myst(r"""&\iff \py{terme_variable} \py{symb_inegalite} \py{latex_fraction} \\""", locals(), globals())
else:
latex_fraction = myst(r"""\frac{\py{latex_fraction_num}}{\py{latex_a}}""", locals(), globals())
latex_rhs = latex_avec_formatage((c - b) / a)
if gcd(c - b, a) != 1 or a.is_noninteger:
raisonnement_latex += myst(r"""&\iff \py{terme_variable} \py{symb_inegalite} \py{latex_fraction} \py{car_a}\\""", locals(), globals())
raisonnement_latex += myst(r"""&\iff \py{terme_variable} \py{symb_inegalite} \py{latex_rhs} \\""", locals(), globals())
else:
raisonnement_latex += myst(r"""&\iff \py{terme_variable} \py{symb_inegalite} \py{latex_fraction} \py{car_a} \\""", locals(), globals())
# Gestion domaine entier pour puissance = 1
valeur_critique = (c - b) / a
if puissance == 1 and domaine in ["Z", "N"]:
if symb in [">=", ">"]:
if symb == ">":
valeurs_entieres_limites = valeur_critique + 1 if valeur_critique.is_integer else __ceil(valeur_critique)
else:
valeurs_entieres_limites = valeur_critique if valeur_critique.is_integer else __ceil(valeur_critique)
else:
if symb == "<":
valeurs_entieres_limites = valeur_critique - 1 if valeur_critique.is_integer else floor(valeur_critique)
else:
valeurs_entieres_limites = floor(valeur_critique)
if domaine == "Z" or (symb in [">=", ">"] and (not valeurs_entieres_limites.is_negative)): # on inclut le cas n >= C Naturel
# N'ajouter la phrase explicative que si on change vraiment l'inégalité pour les entiers
if valeur_critique.is_noninteger or symb in ['<', '>'] or not (valeur_critique.is_nonpositive or valeur_critique.is_nonnegative):
# Ne pas répéter la ligne qui est déjà dans le raisonnement principal
latex_limite = latex_avec_formatage(valeurs_entieres_limites)
if symb == ">":
resultat_final = myst(r"""\py{variable} \geq \py{latex_limite}""", locals(), globals())
elif symb == "<":
resultat_final = myst(r"""\py{variable} \leq \py{latex_limite}""", locals(), globals())
else:
symb_latex_final = dico_symboles_latex[symb]
resultat_final = myst(r"""\py{variable} \py{symb_latex_final} \py{latex_limite}""", locals(), globals())
raisonnement_latex += myst(r"""&\iff \py{resultat_final} \quad \quad \text{car } \py{variable} \text{ doit appartenir à } \mathbb{\py{domaine}} \\""", locals(), globals())
# autres cas où domaine = "N" et valeurs_entieres_limites a un signe déterminé :
else:
latex_limite = latex_avec_formatage(valeurs_entieres_limites)
if valeurs_entieres_limites.is_positive and symb in ["<=", "<"]: # cas n <= C (C positif)
resultat_final = myst(r"""0 \leq \py{variable} \leq \py{latex_limite}""", locals(), globals())
raisonnement_latex += myst(r"""&\iff \py{resultat_final} \quad \quad \text{car } \py{variable} \text{ doit appartenir à } \mathbb{N} \\""", locals(), globals())
elif valeurs_entieres_limites == 0 and symb in ["<=", "<"]:
raisonnement_latex += myst(r"""&\iff \py{variable} = 0 \quad \quad \text{car } \py{variable} \text{ doit appartenir à } \mathbb{N} \\""", locals(), globals())
elif valeurs_entieres_limites.is_negative and symb in [">=", ">"]: # cas n >= -C (C positif)
raisonnement_latex += myst(r"""&\iff \py{variable} \text{ est un entier naturel quelconque} \\""", locals(), globals())
elif valeurs_entieres_limites.is_negative and symb in ["<=", "<"]: # cas n <= -C (C positif)
raisonnement_latex += myst(r"""\quad \quad \text{ impossible car } \py{variable}\in\N \\""", locals(), globals())
else:
raisonnement_latex += myst(r"""& \text{(le résultat dépend du signe de } \py{latex_limite}) \\""", locals(), globals())
if puissance == 2:
quotient = sympify(valeur_droite / a)
latex_quotient = latex_avec_formatage(quotient)
racine = simplify(sqrt(quotient))
latex_racine = latex_avec_formatage(racine)
# Remplacer brutalement et impoliment ces maudits "-b+c" par "c-b" :
latex_mb = latex_avec_formatage(-b)
expr_a_remplacer = __simplifier_signes(myst(r"""\py{latex_mb} + \py{latex_c}""", locals(), globals()))
expr_nouvelle = __simplifier_signes(myst(r"""\py{latex_c} - \py{latex_b}""", locals(), globals()))
latex_racine = latex_racine.replace(expr_a_remplacer, expr_nouvelle)
# Étape 3: Résoudre x² symb quotient
phrase_cas_entier_a_ajouter = False
bool_q_neg = quotient.is_nonpositive or (signe_a == "<" and (c-b).is_positive) or (signe_a == ">" and (c-b).is_negative) # quotient négatif
bool_q_pos = quotient.is_nonnegative or (signe_a == ">" and (c-b).is_positive) or (signe_a == "<" and (c-b).is_negative)
if quotient.is_zero:
if symb == ">=":
raisonnement_latex += myst(r"""&\iff \py{variable} \text{ quelconque} """, locals(), globals())
raisonnement_latex += myst(r"""\text{ (car on a toujours } \py{variable}^2 \geq 0) \\""", locals(), globals())
elif symb == ">":
if domaine == "N":
raisonnement_latex += myst(r"""&\iff \py{variable} \in \mathbb{N} \setminus \{0\} \\""", locals(), globals())
else:
raisonnement_latex += myst(r"""&\iff \py{variable} \neq 0 \\""", locals(), globals())
elif symb == "<=":
if domaine == "N":
raisonnement_latex += myst(r"""&\iff \py{variable} = 0 \\""", locals(), globals())
else:
raisonnement_latex += myst(r"""&\iff \py{variable} = 0 \\""", locals(), globals())
else: # symb == "<"
raisonnement_latex += myst(r"""& \text{ impossible} \\""", locals(), globals())
# raisonnement_latex += myst(r"""\text{ (car } \py{variable}^2 \geq 0 {et non} < 0)""", locals(), globals())
elif bool_q_neg:
if symb in [">=", ">"]:
raisonnement_latex += myst(r"""&\iff \py{variable} \text{ quelconque } """, locals(), globals())
raisonnement_latex += myst(r"""\text{ (car on a toujours } \py{variable}^2 \geq 0) \\""", locals(), globals())
else:
raisonnement_latex += myst(r"""& \text{ impossible car } \py{variable}^2 \geq 0 > \py{latex_quotient} \\""", locals(), globals())
elif bool_q_pos: # quotient > 0
racine0 = sqrt(quotient, evaluate = False)
latex_racine0 = latex_avec_formatage(racine0)
racine1 = sqrt(quotient)
latex_racine1 = latex_avec_formatage(racine1)
valeurs_critiques = [-racine, racine]
if symb in [">=", ">"]:
pg, pp = (r"\geq", r"\leq") if symb == ">=" else (">", "<")
# Si nécessaire, on détaille le calcul de sqrt(quotient) :
if racine1 != racine0:
raisonnement_latex += myst(r"""&\iff \py{variable} \py{pp} -\py{latex_racine0} \text{ ou } \py{variable} \py{pg} \py{latex_racine0} \\""", locals(), globals())
if racine != racine1:
raisonnement_latex += myst(r"""&\iff \py{variable} \py{pp} -\py{latex_racine1} \text{ ou } \py{variable} \py{pg} \py{latex_racine1} \\""", locals(), globals())
raisonnement_latex += myst(r"""&\iff \py{variable} \py{pp} -\py{latex_racine} \text{ ou } \py{variable} \py{pg} \py{latex_racine} \\""", locals(), globals())
else: # symb in ["<=", "<"]
pp = r"\leq" if symb == "<=" else "<"
# si nécessaire, on détaille le calcul de sqrt(racine1)
if racine1 != racine0:
raisonnement_latex += myst(r"""&\iff -\py{latex_racine0} \py{pp} \py{variable} \py{pp} \py{latex_racine0} \\""", locals(), globals())
if racine != racine1:
raisonnement_latex += myst(r"""&\iff -\py{latex_racine1} \py{pp} \py{variable} \py{pp} \py{latex_racine1} \\""", locals(), globals())
raisonnement_latex += myst(r"""&\iff -\py{latex_racine} \py{pp} \py{variable} \py{pp} \py{latex_racine} \\""", locals(), globals())
else: # le signe de quotient est indéterminé
if (c-b).is_positive or (c-b).is_negative:
sens_pos, sens_neg = (r"\geq", "<") if (c-b).is_positive else (r"\leq", ">")
cas_pos = myst(r""" \text{ si } \py{latex_a} \py{sens_pos} 0""", locals(), globals())
cas_neg = myst(r""" \text{ si } \py{latex_a} \py{sens_neg} 0""", locals(), globals())
elif a.is_positive or a.is_negative or signe_a:
sens_pos, sens_neg = (r"\geq", "<") if (a.is_positive or signe_a == ">") else (r"\leq", ">")
cas_pos = myst(r""" \text{ si } \py{latex_fraction_num} \py{sens_pos} 0""", locals(), globals())
cas_neg = myst(r""" \text{ si } \py{latex_fraction_num} \py{sens_neg} 0""", locals(), globals())
else:
cas_pos = myst(r""" \text{ si } \py{latex_quotient} > 0""", locals(), globals())
cas_neg = myst(r""" \text{ si } \py{latex_quotient} < 0""", locals(), globals())
if symb in [">=", ">"]:
pg, pp = (r"\geq", r"\leq") if symb == ">=" else (">", "<")
raisonnement_latex += myst(r"""&\iff \begin{cases} \py{variable} \py{pp} -\py{latex_racine} \text{ ou } \py{variable} \py{pg} \py{latex_racine}, & \py{cas_pos} \\
\py{variable} \text{ quelconque}, & \py{cas_neg}
\end{cases} \\""", locals(), globals())
else: # symb in ["<=", "<"]
pp = r"\leq" if symb == "<=" else "<"
raisonnement_latex += myst(r"""&\iff \begin{cases} -\py{latex_racine} \py{pp} \py{variable} \py{pp} \py{latex_racine}, & \py{cas_pos} \\
\text{ impossible}, & \py{cas_neg}
\end{cases} \\""", locals(), globals())
# Si des détails doivent être donnés dans le cas entier :
# (quel que soit le sens de l'inégalité)
if not bool_q_neg: # à moins qu'on ne soit sûr que q <= 0
if domaine in ["Z", "N"] and (not racine.is_integer or symb in ["<", ">"]):
phrase_cas_entier_a_ajouter = True
# cas particulier où 0 est la seule solution
if (symb == "<=" and (racine - 1).is_negative) or (symb == "<" and (racine - 1).is_nonpositive):
raisonnement_latex += myst(r"""&\iff \py{variable} = 0""", locals(), globals())
# tous les autres cas :
else:
# calcul des valeurs extrêmes :
if symb == "<=":
limite_pos = floor(racine)
limite_neg = __ceil(-racine) if domaine == "Z" else 0
elif symb == ">=":
limite_pos = __ceil(racine)
limite_neg = floor(-racine)
elif symb == "<":
if racine.is_integer:
limite_pos = racine - 1
limite_neg = - racine + 1 if domaine == "Z" else 0
else:
limite_pos = floor(racine)
limite_neg = __ceil(-racine) if domaine == "Z" else 0
else: #symb = ">"
limite_pos = racine + 1 if racine.is_integer else __ceil(racine)
limite_neg = - racine - 1 if racine.is_integer else floor(- racine)
# affichage de la dernière étape :
latex_limite_pos = latex_avec_formatage(limite_pos)
latex_limite_neg = latex_avec_formatage(limite_neg)
if symb in ["<=", "<"]:
raisonnement_latex += myst(r"""&\iff \py{latex_limite_neg} \leq \py{variable} \leq \py{latex_limite_pos}""", locals(), globals())
else: # >= ou >
if domaine == "Z":
raisonnement_latex += myst(r"""&\iff \py{variable} \leq \py{latex_limite_neg} \text{ ou } \py{variable} \geq \py{latex_limite_pos}""", locals(), globals())
else:
raisonnement_latex += myst(r"""&\iff \py{variable} \geq \py{latex_limite_pos}""", locals(), globals())
naturel_ou_pas = "naturel" if domaine == "N" else " "
raisonnement_latex += myst(r"""& \text{car } \py{variable} \text{ est entier \py{naturel_ou_pas}}\\""", locals(), globals())
# ==============================================================================================================
# Terminer le raisonnement LaTeX
raisonnement_latex = raisonnement_latex[:-2] # on enlève le saut de ligne final
raisonnement_latex += myst(r"""\,.
\end{align*}\end{equation*}""", locals(), globals())
raisonnement_latex = __simplifier_signes(raisonnement_latex)
return ensemble_solution, raisonnement_latex
[docs]
def pxsl_solve_general_inequality(a=1, b=0, c=0, variable="x", inequality=">=", domain="R", power=1, sign_a=None, detail_sign_a=False):
r"""
\en{Solves an inequality of the form \(a x^p + b \,\square\, c\) with detailed step-by-step reasoning in LaTeX.}
\fr{Résout une inéquation de la forme \(a x^p + b \,\square\, c\) avec un raisonnement détaillé pas à pas en LaTeX.}
\en{This function solves linear (\(p=1\)) or quadratic (\(p=2\)) inequalities and generates
a complete LaTeX-formatted solution showing all intermediate steps. It handles
symbolic coefficients, different domains (reals, integers, natural numbers), and
cases where the sign of the coefficient \(a\) must be analyzed.}
\fr{Cette fonction résout des inéquations linéaires (\(p=1\)) ou quadratiques (\(p=2\)) et génère
une solution complète au format LaTeX en détaillant toutes les étapes. Elle gère
des coefficients symboliques, différents domaines (réels, entiers, naturels) et
les cas où le signe du coefficient \(a\) doit être analysé.}
Parameters
----------
a : int, float, or sympy expression, optional
\en{Coefficient of the variable term. Default: 1.}
\fr{Coefficient du terme en variable. Par défaut : 1.}
b : int, float, or sympy expression, optional
\en{Constant term on the left-hand side. Default: 0.}
\fr{Terme constant au membre de gauche. Par défaut : 0.}
c : int, float, or sympy expression, optional
\en{Constant term on the right-hand side. Default: 0.}
\fr{Terme constant au membre de droite. Par défaut : 0.}
variable : str, optional
\en{Name of the variable. Default: "x".}
\fr{Nom de la variable. Par défaut : "x".}
inequality : str, optional
\en{Inequality symbol: ">=", ">", "<=", "<". Default: ">=".}
\fr{Symbole d'inégalité : ">=", ">", "<=", "<". Par défaut : ">=".}
domain : str, optional
\en{Solution domain: "R" (reals), "Z" (integers), "N" (natural numbers). Default: "R".}
\fr{Domaine des solutions : "R" (réels), "Z" (entiers), "N" (naturels). Par défaut : "R".}
power : int, optional
\en{Exponent of the variable: 1 (linear) or 2 (quadratic). Default: 1.}
\fr{Exposant de la variable : 1 (linéaire) ou 2 (quadratique). Par défaut : 1.}
sign_a : str or None, optional
\en{Sign of the coefficient \(a\) when symbolic: ">" for positive, "<" for negative,
or None to determine automatically.}
\fr{Signe du coefficient \(a\) quand il est symbolique : ">" pour positif, "<" pour négatif,
ou None pour déterminer automatiquement.}
detail_sign_a : bool, optional
\en{Whether to explicitly mention the sign of \(a\) when dividing. Default: False.}
\fr{Indique s'il faut expliciter le signe de \(a\) lors d'une division. Par défaut : False.}
Returns
-------
tuple
\en{A tuple \((\text{solution\_set}, \text{latex\_reasoning})\) where:}
\fr{Un tuple \((\text{solution\_set}, \text{latex\_reasoning})\) où :}
- solution_set : sympy set or dict
\en{The solution set. Returns a dict with cases if the sign of \(a\) or the RHS is undetermined.}
\fr{L'ensemble des solutions. Retourne un dictionnaire par cas si le signe de \(a\) ou le membre droit est indéterminé.}
- latex_reasoning : str
\en{Complete LaTeX-formatted step-by-step solution.}
\fr{Solution détaillée pas à pas au format LaTeX.}
Raises
------
ValueError
\en{If `inequality` not in [">=", ">", "<=", "<"].}
\fr{Si `inequality` n'est pas dans [">=", ">", "<=", "<"].}
\en{If `domain` not in ["R", "Z", "N"].}
\fr{Si `domain` n'est pas dans ["R", "Z", "N"].}
\en{If `power` not in [1, 2].}
\fr{Si `power` n'est pas dans [1, 2].}
\en{If `sign_a` not in {None, ">", "<"}.}
\fr{Si `sign_a` n'est pas dans {None, ">", "<"}.}
Examples
--------
>>> # Simple linear inequality
>>> sol, latex = solve_general_inequality(2, 3, 7, variable="x", inequality=">=")
>>> print(sol)
[2, oo)
>>> # Quadratic inequality
>>> sol, latex = solve_general_inequality(1, 0, 4, power=2, inequality="<=")
>>> print(sol)
[-2, 2]
>>> # Integer domain
>>> sol, latex = solve_general_inequality(3, -1, 5, domain="Z", inequality=">")
>>> print(sol)
{3, 4, 5, ...}
>>> # Symbolic coefficient with sign analysis
>>> from sympy import Symbol
>>> a = Symbol('a')
>>> sol, latex = solve_general_inequality(a, 0, 5, inequality=">=")
>>> # Returns dict with cases for a>0 and a<0
"""
# ======= AUXILIARY FUNCTIONS =================
def __ceil(x):
return (x if x.is_integer else floor(x) + 1)
def __simplify_signs(s):
return s.replace("+ + ", "+ ").replace("+ -", "-").replace("- + ", "- ").replace("- -", "+")
def __is_a_fraction_like(a_):
if isinstance(a_, Rational) and a_.q != 1:
return True
if isinstance(a_, Mul):
for fac in a_.args:
if isinstance(fac, Rational) or (isinstance(fac, Pow) and fac.args[1].is_integer and fac.args[1].is_negative):
return True
return False
def __solutions_general_inequality(a=1, b=0, c=0, inequality=">=", domain="R", power=1, sign_a=None, sign_rhs=None):
# import retiré pour cause d'import circulaire
# from src.scripts.Mes_fctions.Classes_Extensions import pxs_Interval
a = sympify(a)
b = sympify(b)
c = sympify(c)
set_map = {"R": Reals, "N": Naturals, "Z": Integers}
the_set = set_map[domain]
quotient = (c - b) / a
if a == 0:
value = b - c
existence_cases = {"<=": value.is_nonpositive, "<": value.is_negative,
">=": value.is_nonnegative, ">": value.is_positive,
"=": value.is_nonzero}
ok = existence_cases[inequality]
sol = the_set if ok else EmptySet
elif a.is_negative or sign_a == "<":
inv = {"<=": ">=", "<": ">", ">=": "<=", ">": "<", "=" : "="}[inequality]
return __solutions_general_inequality(-a, -b, -c, inequality=inv, domain=domain, power=power, sign_a=">", sign_rhs=sign_rhs)
elif a.is_nonnegative or sign_a == ">":
if power == 1:
if inequality == ">=":
sol = pxs_Interval(quotient, oo)
elif inequality == ">":
sol = pxs_Interval.open(quotient, oo)
elif inequality == "<=":
sol = pxs_Interval(-oo, quotient)
elif inequality == "<":
sol = pxs_Interval.open(-oo, quotient)
else:
sol = sympify({quotient})
elif power == 2:
if quotient.is_negative or sign_rhs == "<":
if inequality in [">=", ">"]:
sol = the_set
else:
sol = EmptySet
elif quotient.is_nonnegative or sign_rhs == ">":
root = simplify(sqrt(quotient))
if inequality == ">=":
sol = pxs_Interval(-oo, -root).union(pxs_Interval(root, oo))
elif inequality == ">":
sol = pxs_Interval.open(-oo, -root).union(pxs_Interval.open(root, oo))
elif inequality == "<=":
sol = pxs_Interval(-root, root)
elif inequality == "<":
sol = pxs_Interval.open(-root, root)
else:
sol = sympify({-root, root})
else: # sign of RHS undetermined
sol_rhs_pos = __solutions_general_inequality(a, b, c, inequality=inequality, domain=domain, power=power, sign_a=sign_a, sign_rhs=">")
sol_rhs_neg = __solutions_general_inequality(a, b, c, inequality=inequality, domain=domain, power=power, sign_a=sign_a, sign_rhs="<")
return {"rhs>0": sol_rhs_pos, "rhs<0": sol_rhs_neg}
else: # sign of a undetermined
sol_pos = __solutions_general_inequality(a, b, c, inequality=inequality, domain=domain, power=power, sign_a=">", sign_rhs=sign_rhs)
sol_neg = __solutions_general_inequality(a, b, c, inequality=inequality, domain=domain, power=power, sign_a="<", sign_rhs=sign_rhs)
return {"a>0": sol_pos, "a<0": sol_neg}
if domain in ["Z", "N"]:
try:
sol = sol.intersect(Integers) if domain == "Z" else sol.intersect(Naturals0)
except:
sol = Intersection(sol, Integers, evaluate=False) if domain == "Z" else Intersection(sol, Naturals0, evaluate=False)
return sol
# ===================================================================
if inequality not in [">=", ">", "<=", "<", "="]:
raise ValueError("Invalid inequality symbol")
if domain not in ["R", "Z", "N"]:
raise ValueError("Invalid domain")
if power not in [1, 2]:
raise ValueError("Invalid power (must be 1 or 2)")
if sign_a not in [None, ">", "<"]:
raise ValueError("Invalid sign_a. Use None, '>' for positive, or '<' for negative.")
# Convert parameters
a = pxsl_to_rational_or_symbol(a)
b = pxsl_to_rational_or_symbol(b)
c = pxsl_to_rational_or_symbol(c)
# Variable symbol
var_symb = Symbol(variable)
solution_set = __solutions_general_inequality(a, b, c, inequality, domain, power, sign_a)
# If the sign of a is not determined, split into cases
if inequality != "=" and not (sign_a in [">", "<"] or sympify(a).is_nonpositive or sympify(a).is_nonnegative):
latex_a = pxsl_latex_with_formatting(a)
latex_reasoning = myst(r"""\en{We distinguish two cases:}\fr{On distingue deux cas :}
1. \en{If}\fr{Si} \py{latex_a} > 0:
""", locals(), globals())
latex_reasoning += pxsl_solve_general_inequality(a, b, c, variable=variable, inequality=inequality, domain=domain, power=power, sign_a=">")[-1]
latex_reasoning += myst(r"""
2. \en{If}\fr{Si} \py{latex_a} < 0:
""", locals(), globals())
latex_reasoning += pxsl_solve_general_inequality(a, b, c, variable=variable, inequality=inequality, domain=domain, power=power, sign_a="<")[-1]
latex_reasoning = __simplify_signs(latex_reasoning)
return solution_set, latex_reasoning # the solution set is case-dependent
latex_reasoning = myst(r"""\begin{equation*}\begin{align*}""", locals(), globals())
# Initial inequality symbol (and LaTeX display)
symb = inequality
symb_latex_map = {">=": myst(r"""\geq""", locals(), globals()), ">": myst(r""">""", locals(), globals()),
"<=": myst(r"""\leq""", locals(), globals()), "<": myst(r"""<""", locals(), globals()),
"=" : myst(r"""=""", locals(), globals())}
latex_ineq_symbol = symb_latex_map[inequality]
# Build the original inequality
coeff_str = pxsl_latex_coefficient(a)
coeff_str = myst(r"""\left(\py{coeff_str}\right)""", locals(), globals()) if a.is_Add else coeff_str
latex_c = pxsl_latex_with_formatting(c)
# Left-hand expression
if sympify(b).is_zero:
expression = sympy_latex(a * var_symb ** power)
lhs_expr = myst(r"""\py{expression}""", locals(), globals())
elif isinstance(b, (int, Rational)) and b >= 0:
latex_b = pxsl_latex_with_formatting(b)
if power == 1:
lhs_expr = myst(r"""\py{coeff_str}\py{variable} + \py{latex_b}""", locals(), globals()) if coeff_str else myst(r"""\py{variable} + \py{latex_b}""", locals(), globals())
else:
lhs_expr = myst(r"""\py{coeff_str}\py{variable}^2 + \py{latex_b}""", locals(), globals()) if coeff_str else myst(r"""\py{variable}^2 + \py{latex_b}""", locals(), globals())
elif isinstance(b, (int, Rational)) and b < 0:
latex_abs_b = pxsl_latex_with_formatting(abs(b))
if power == 1:
lhs_expr = myst(r"""\py{coeff_str}\py{variable} - \py{latex_abs_b}""", locals(), globals()) if coeff_str else myst(r"""\py{variable} - \py{latex_abs_b}""", locals(), globals())
else:
lhs_expr = myst(r"""\py{coeff_str}\py{variable}^2 - \py{latex_abs_b}""", locals(), globals()) if coeff_str else myst(r"""\py{variable}^2 - \py{latex_abs_b}""", locals(), globals())
else:
latex_b = pxsl_latex_with_formatting(b)
if power == 1:
lhs_expr = myst(r"""\py{coeff_str}\py{variable} + \py{latex_b}""", locals(), globals()) if coeff_str else myst(r"""\py{variable} + \py{latex_b}""", locals(), globals())
else:
lhs_expr = myst(r"""\py{coeff_str}\py{variable}^2 + \py{latex_b}""", locals(), globals()) if coeff_str else myst(r"""\py{variable}^2 + \py{latex_b}""", locals(), globals())
# Degenerate case: a = 0
if a == 0:
latex_b_simple = pxsl_latex_with_formatting(b)
latex_reasoning += myst(r"""\py{latex_b_simple} \py{latex_ineq_symbol} \py{latex_c}""", locals(), globals())
if solution_set == EmptySet:
latex_reasoning += myst(r"""\text{ impossible.}""", locals(), globals())
else:
latex_reasoning += myst(r"""&\text{ \en{always true}\fr{toujours vrai}.}""", locals(), globals())
latex_reasoning += myst(r"""\\ \end{align*}\end{equation*}""", locals(), globals())
latex_reasoning = __simplify_signs(latex_reasoning)
return solution_set, latex_reasoning
# First line: original inequality
latex_reasoning += myst(r"""
\py{lhs_expr} \py{latex_ineq_symbol} \py{latex_c}""", locals(), globals())
# Step 1: isolate the x-term — construct c - b with sign handling
if not sympify(b).is_zero:
latex_b = pxsl_latex_with_formatting(b)
latex_c = pxsl_latex_with_formatting(c)
if c == 0:
latex_rhs_step1 = pxsl_latex_with_formatting(-b)
elif b.is_Add:
latex_rhs_step1 = latex_c + " - " + myst(r"""\left(\py{latex_b}\right)""", locals(), globals())
else:
latex_rhs_step1 = __simplify_signs(latex_c + " - " + latex_b)
if power == 1:
var_term = myst(r"""\py{coeff_str}\py{variable}""", locals(), globals()) if coeff_str else variable
else:
var_term = myst(r"""\py{coeff_str}\py{variable}^2""", locals(), globals()) if coeff_str else myst(r"""\py{variable}^2""", locals(), globals())
latex_reasoning += myst(r""" &\iff \py{var_term} \py{latex_ineq_symbol}
\py{latex_rhs_step1} \\""", locals(), globals())
# Simplify c-b
latex_mb = pxsl_latex_with_formatting(-b)
expr_equiv = __simplify_signs(myst(r"""\py{latex_mb} + \py{latex_c}""", locals(), globals()))
if latex_rhs_step1 != pxsl_latex_with_formatting(c - b) and pxsl_latex_with_formatting(c - b) != expr_equiv:
latex_rhs_step1 = pxsl_latex_with_formatting(c - b)
latex_reasoning += myst(r""" &\iff \py{var_term} \py{latex_ineq_symbol}
\py{latex_rhs_step1}\\""", locals(), globals())
else:
latex_rhs_step1 = pxsl_latex_with_formatting(c)
# Fraction handling and sign of a
latex_fraction_num = latex_rhs_step1
latex_a = pxsl_latex_with_formatting(a)
# From here we divide by a: inequality direction may change
if sympify(a).is_negative or sign_a == "<":
symb = {"<=": ">=", "<": ">", ">=": "<=", ">": "<", "=" : "="}[inequality]
latex_ineq_symbol = symb_latex_map[symb]
# Step 2: solution depending on the sign of a (with option sign_a)
# Left side becomes variable or variable^2:
var_term_simple = myst(r"""\py{variable}""", locals(), globals()) if power == 1 else myst(r"""\py{variable}^2""", locals(), globals())
if inequality != "=" and sign_a is None:
sign_a = ">" if sympify(a).is_positive else "<"
if inequality != "=" and (detail_sign_a or a.free_symbols):
because_a = myst(r""", \quad \text{since } \py{latex_a} \py{sign_a} 0""", locals(), globals())
else:
because_a = " "
if a == 1:
pass
elif a == -1:
inv = {">=": myst(r"""\leq""", locals(), globals()), ">": myst(r"""<""", locals(), globals()),
"<=": myst(r"""\geq""", locals(), globals()), "<": myst(r""">""", locals(), globals()),
"=" : myst(r"""=""", locals(), globals())}[inequality]
latex_rhs_only = pxsl_latex_with_formatting(b - c)
latex_reasoning += myst(r"""&\iff \py{var_term_simple} \py{latex_ineq_symbol} \py{latex_rhs_only}\\""", locals(), globals())
elif c - b == 0:
latex_reasoning += myst(r"""&\iff \py{var_term_simple} \py{latex_ineq_symbol} 0 \py{because_a}\\""", locals(), globals())
elif __is_a_fraction_like(a): # multiplying by 1/a may be clearer than dividing by a
fraction = Mul(c - b, 1 / a, evaluate=False)
latex_fraction = pxsl_latex_with_formatting(fraction)
latex_reasoning += myst(r"""&\iff \py{var_term_simple} \py{latex_ineq_symbol} \py{latex_fraction} \py{because_a}\\""", locals(), globals())
fraction1 = Mul(c - b, 1 / a)
if fraction1 != fraction:
latex_fraction = pxsl_latex_with_formatting(fraction1)
latex_reasoning += myst(r"""&\iff \py{var_term_simple} \py{latex_ineq_symbol} \py{latex_fraction} \\""", locals(), globals())
else:
latex_fraction = myst(r"""\frac{\py{latex_fraction_num}}{\py{latex_a}}""", locals(), globals())
if gcd(c - b, a) != 1 or a.is_noninteger:
latex_reasoning += myst(r"""&\iff \py{var_term_simple} \py{latex_ineq_symbol} \py{latex_fraction} \py{because_a}\\""", locals(), globals())
latex_fraction = pxsl_latex_with_formatting((c - b) / a)
latex_reasoning += myst(r"""&\iff \py{var_term_simple} \py{latex_ineq_symbol} \py{latex_fraction} \\""", locals(), globals())
elif a.could_extract_minus_sign() or match(r"\s*-", latex_a): # to avoid minus sign in denominator
latex_minus_a = pxsl_latex_with_formatting(-a)
if match(r"\s*-", latex_fraction_num):
latex_fraction_num = pxsl_latex_with_formatting(b - c)
latex_a = latex_minus_a
a, b, c = -a, c, b
if sign_a:
sign_a = "<" if sign_a == ">" else ">"
latex_fraction = myst(r"""\frac{\py{latex_fraction_num}}{\py{latex_a}}""", locals(), globals())
else:
latex_fraction = myst(r"""-\frac{\py{latex_fraction_num}}{\py{latex_minus_a}}""", locals(), globals())
latex_reasoning += myst(r"""&\iff \py{var_term_simple} \py{latex_ineq_symbol} \py{latex_fraction} \py{because_a} \\""", locals(), globals())
else:
latex_reasoning += myst(r"""&\iff \py{var_term_simple} \py{latex_ineq_symbol} \py{latex_fraction} \py{because_a} \\""", locals(), globals())
# Integer/Natural domain when power = 1
critical_value = (c - b) / a
if power == 1 and domain in ["Z", "N"]:
if inequality == "=": # equation case
if critical_value.is_noninteger or (domain == "N" and critical_value.is_negative): # on sait que la solution n'est pas dans le domaine
latex_reasoning = latex_reasoning[:-2] # on enlève le saut de ligne final
latex_reasoning += myst(r"""
\end{align*}\end{equation*}""", locals(), globals())
latex_reasoning += myst(r"""
Or $\py{variable}$ doit appartenir à l'ensemble $\mathbb{\py{domain}}\,$, donc l'équation ne possède aucune solution.""", locals(), globals())
latex_reasoning = __simplify_signs(latex_reasoning)
return solution_set, latex_reasoning
elif (not critical_value.is_integer) or (domain == "N" and not critical_value.is_nonnegative):# solution might not be in domain
positive_or_not = " positif" if domain == "N" else ""
latex_reasoning = latex_reasoning[:-2]
latex_reasoning += myst(r"""
\end{align*}\end{equation*}""", locals(), globals())
latex_reasoning += myst(r"""
Ainsi l'équation admet pour unique solution $\ds \py{latex_fraction}$ si $\ds \py{latex_fraction}$ est un entier\py{positive_or_not}, et n'admet aucune solution sinon.""", locals(), globals())
latex_reasoning = __simplify_signs(latex_reasoning)
return solution_set, latex_reasoning
else: # inequation case
if symb in [">=", ">"]:
if symb == ">":
int_bounds = critical_value + 1 if critical_value.is_integer else __ceil(critical_value)
else:
int_bounds = critical_value if critical_value.is_integer else __ceil(critical_value)
else:
if symb == "<":
int_bounds = critical_value - 1 if critical_value.is_integer else floor(critical_value)
else:
int_bounds = floor(critical_value)
if domain == "Z" or (symb in [">=", ">"] and (not int_bounds.is_negative)):
if critical_value.is_noninteger or symb in ['<', '>'] or not (critical_value.is_nonpositive or critical_value.is_nonnegative):
latex_bound = pxsl_latex_with_formatting(int_bounds)
if symb == ">":
final_result = myst(r"""\py{variable} \geq \py{latex_bound}""", locals(), globals())
elif symb == "<":
final_result = myst(r"""\py{variable} \leq \py{latex_bound}""", locals(), globals())
else:
latex_final_symbol = symb_latex_map[symb]
final_result = myst(r"""\py{variable} \py{latex_final_symbol} \py{latex_bound}""", locals(), globals())
latex_reasoning += myst(r"""&\iff \py{final_result} \quad \quad \text{\en{since}\fr{car} } \py{variable} \text{ \en{must lie in}\fr{doit appartenir à} } \mathbb{\py{domain}} \\""", locals(), globals())
else:
latex_bound = pxsl_latex_with_formatting(int_bounds)
if int_bounds.is_positive and symb in ["<=", "<"]:
final_result = myst(r"""0 \leq \py{variable} \leq \py{latex_bound}""", locals(), globals())
latex_reasoning += myst(r"""&\iff \py{final_result} \quad \quad \text{\en{since}\fr{car} } \py{variable} \text{ \en{must lie in}\fr{doit appartenir à} } \mathbb{N} \\""", locals(), globals())
elif int_bounds == 0 and symb in ["<=", "<"]:
latex_reasoning += myst(r"""&\iff \py{variable} = 0 \quad \quad \text{\en{since}\fr{car} } \py{variable} \text{ \en{must lie in}\fr{doit appartenir à} } \mathbb{N} \\""", locals(), globals())
elif int_bounds.is_negative and symb in [">=", ">"]:
latex_reasoning += myst(r"""&\iff \py{variable} \text{ \en{is any natural integer}\fr{est un entier naturel quelconque}} \\""", locals(), globals())
elif int_bounds.is_negative and symb in ["<=", "<"]:
latex_reasoning += myst(r"""\quad \quad \text{ impossible \en{since}\fr{car} } \py{variable}\in\mathbb{N} \\""", locals(), globals())
else:
latex_reasoning += myst(r"""& \text{(\en{the result depends on the sign of}\fr{le résultat dépend du signe de} } \py{latex_bound}) \\""", locals(), globals())
rhs_value = c - b
# Quadratic case
if power == 2:
quotient = sympify(rhs_value / a)
latex_quotient = pxsl_latex_with_formatting(quotient)
root = simplify(sqrt(quotient))
latex_root = pxsl_latex_with_formatting(root)
# Replace "-b + c" by "c - b" inside latex_root when needed:
latex_mb = pxsl_latex_with_formatting(-b)
expr_to_replace = __simplify_signs(myst(r"""\py{latex_mb} + \py{latex_c}""", locals(), globals()))
new_expr = __simplify_signs(myst(r"""\py{latex_c} - \py{latex_b}""", locals(), globals()))
latex_root = latex_root.replace(expr_to_replace, new_expr)
# Step 3: solve x^2 (symb) quotient
phrase_integer_case = False
q_neg = quotient.is_nonpositive or (sign_a == "<" and (c - b).is_positive) or (sign_a == ">" and (c - b).is_negative)
q_pos = quotient.is_nonnegative or (sign_a == ">" and (c - b).is_positive) or (sign_a == "<" and (c - b).is_negative)
if quotient.is_zero:
if symb == ">=":
latex_reasoning += myst(r"""&\iff \py{variable} \text{ \en{arbitraryø\fr{arbitraire}} """, locals(), globals())
latex_reasoning += myst(r"""\text{ (\en{since we always have}\fr{car on a toujours} } \py{variable}^2 \ge 0) \\""", locals(), globals())
elif symb == ">":
if domain == "N":
latex_reasoning += myst(r"""&\iff \py{variable} \in \mathbb{N} \setminus \{0\} \\""", locals(), globals())
else:
latex_reasoning += myst(r"""&\iff \py{variable} \neq 0 \\""", locals(), globals())
elif symb in ["<=", "="]:
latex_reasoning += myst(r"""&\iff \py{variable} = 0 \\""", locals(), globals())
else: # symb == "<"
latex_reasoning += myst(r"""& \text{ impossible} \\""", locals(), globals())
elif q_neg:
if symb in [">=", ">"]:
latex_reasoning += myst(r"""&\iff \py{variable} \text{ \en{arbitrary}\fr{arbitraire} } """, locals(), globals())
latex_reasoning += myst(r"""\text{ (\en{since we always have}\fr{car on a toujours} } \py{variable}^2 \ge 0) \\""", locals(), globals())
else:
latex_reasoning += myst(r"""& \text{ impossible \en{since}\fr{car} } \py{variable}^2 \ge 0 > \py{latex_quotient} \\""", locals(), globals())
elif q_pos:
root0 = sqrt(quotient, evaluate=False)
latex_root0 = pxsl_latex_with_formatting(root0)
root1 = sqrt(quotient)
latex_root1 = pxsl_latex_with_formatting(root1)
if symb in [">=", ">", "="]:
if symb == ">=":
ge, le = r"\geq", r"\leq"
elif symb == ">":
ge, le = ">", "<"
else: # symb = "="
ge, le = "=", "="
if root1 != root0:
latex_reasoning += myst(r"""&\iff \py{variable} \py{le} -\py{latex_root0} \text{ or } \py{variable} \py{ge} \py{latex_root0} \\""", locals(), globals())
if root != root1:
latex_reasoning += myst(r"""&\iff \py{variable} \py{le} -\py{latex_root1} \text{ or } \py{variable} \py{ge} \py{latex_root1} \\""", locals(), globals())
latex_reasoning += myst(r"""&\iff \py{variable} \py{le} -\py{latex_root} \text{ or } \py{variable} \py{ge} \py{latex_root} \\""", locals(), globals())
else: # symb in ["<=", "<"]
le = r"\leq" if symb == "<=" else "<"
if root1 != root0:
latex_reasoning += myst(r"""&\iff -\py{latex_root0} \py{le} \py{variable} \py{le} \py{latex_root0} \\""", locals(), globals())
if root != root1:
latex_reasoning += myst(r"""&\iff -\py{latex_root1} \py{le} \py{variable} \py{le} \py{latex_root1} \\""", locals(), globals())
latex_reasoning += myst(r"""&\iff -\py{latex_root} \py{le} \py{variable} \py{le} \py{latex_root} \\""", locals(), globals())
else: # sign of quotient undetermined
if (c - b).is_positive or (c - b).is_negative:
ge0, lt0 = (r"\geq", "<") if (c - b).is_positive else (r"\leq", ">")
case_pos = myst(r""" \text{ \en{if}\fr{si} } \py{latex_a} \py{ge0} 0""", locals(), globals())
case_neg = myst(r""" \text{ \en{if}\fr{si} } \py{latex_a} \py{lt0} 0""", locals(), globals())
elif a.is_positive or a.is_negative or sign_a:
ge0, lt0 = (r"\geq", "<") if (a.is_positive or sign_a == ">") else (r"\leq", ">")
case_pos = myst(r""" \text{ \en{if}\fr{si} } \py{latex_fraction_num} \py{ge0} 0""", locals(), globals())
case_neg = myst(r""" \text{ \en{if}\fr{si} } \py{latex_fraction_num} \py{lt0} 0""", locals(), globals())
else:
case_pos = myst(r""" \text{ \en{if}\fr{si} } \py{latex_quotient} > 0""", locals(), globals())
case_neg = myst(r""" \text{ \en{if}\fr{si} } \py{latex_quotient} < 0""", locals(), globals())
if symb == "=":
latex_reasoning += myst(r"""&\iff \begin{cases} \py{variable} = -\py{latex_root} \text{ \en{or}\fr{ou} } \py{variable} = \py{latex_root}, & \py{case_pos} \\
\text{ impossible}, & \py{case_neg}
\end{cases} \\""", locals(), globals())
elif symb in [">=", ">"]:
if symb == ">=":
ge, le = r"\geq", r"\leq"
elif symb == ">":
ge, le = ">", "<"
latex_reasoning += myst(r"""&\iff \begin{cases} \py{variable} \py{le} -\py{latex_root} \text{ \en{or}\fr{ou} } \py{variable} \py{ge} \py{latex_root}, & \py{case_pos} \\
\py{variable} \text{ \en{arbitrary}\fr{arbitraire}}, & \py{case_neg}
\end{cases} \\""", locals(), globals())
else: # "<=", "<"
le = r"\leq" if symb == "<=" else "<"
latex_reasoning += myst(r"""&\iff \begin{cases} -\py{latex_root} \py{le} \py{variable} \py{le} \py{latex_root}, & \py{case_pos} \\
\text{ impossible}, & \py{case_neg}
\end{cases} \\""", locals(), globals())
# Integer-domain details (any inequality direction)
if not q_neg:
if domain in ["Z", "N"] and (not root.is_integer or symb in ["<", ">"] or domain == "N"):
phrase_integer_case = True
if (symb == "<=" and (root - 1).is_negative) or (symb == "<" and (root - 1).is_nonpositive):
latex_reasoning += myst(r"""&\iff \py{variable} = 0""", locals(), globals())
elif symb == "=":
if root.is_noninteger or (not root.free_symbols and not root.is_integer): # root is not an integer
final_sentence = myst(r"""
Or $\py{variable}$ doit être un entier, donc l'équation n'admet aucune solution.""", locals(), globals())
elif not root.is_integer: # don't know whether root is integer or not
if domain == "N":
final_sentence = myst(r"""
Or $\py{variable}$ doit être un entier positif, donc l'équation admet pour unique solution $\py{latex_root}$ si $\py{latex_root}$ est un entier, et n'admet aucune solution sinon.""", locals(), globals())
if domain == "Z":
final_sentence = myst(r"""
Or \py{variable} doit être un entier, donc l'équation admet deux solutions -\py{latex_root} et \py{latex_root} si \py{latex_root} est un entier, et n'admet aucune solution sinon.""", locals(), globals())
elif domain == "N": # on sait que racine est entier et domaine = N
latex_reasoning += myst(r"""&\iff \py{variable} = \py{latex_root} \\""", locals(), globals())
final_sentence = myst(r"""
En effet, $\py{variable}$ doit être un entier positif.""", locals(), globals())
latex_reasoning = latex_reasoning[:-2] # on enlève le saut de ligne final
latex_reasoning += myst(r"""\,.
\end{align*}\end{equation*}""", locals(), globals())
latex_reasoning += final_sentence
latex_reasoning = __simplify_signs(latex_reasoning)
return solution_set, latex_reasoning
else:
if symb == "<=":
lim_pos = floor(root)
lim_neg = __ceil(-root) if domain == "Z" else 0
elif symb == ">=":
lim_pos = __ceil(root)
lim_neg = floor(-root)
elif symb == "<":
if root.is_integer:
lim_pos = root - 1
lim_neg = -root + 1 if domain == "Z" else 0
else:
lim_pos = floor(root)
lim_neg = __ceil(-root) if domain == "Z" else 0
else: # ">"
lim_pos = root + 1 if root.is_integer else __ceil(root)
lim_neg = -root - 1 if root.is_integer else floor(-root)
latex_lim_pos = pxsl_latex_with_formatting(lim_pos)
latex_lim_neg = pxsl_latex_with_formatting(lim_neg)
if symb in ["<=", "<"]:
latex_reasoning += myst(r"""&\iff \py{latex_lim_neg} \leq \py{variable} \leq \py{latex_lim_pos}""", locals(), globals())
else:
if domain == "Z":
latex_reasoning += myst(r"""&\iff \py{variable} \leq \py{latex_lim_neg} \text{ \en{or}\fr{ou} } \py{variable} \geq \py{latex_lim_pos}""", locals(), globals())
else:
latex_reasoning += myst(r"""&\iff \py{variable} \geq \py{latex_lim_pos}""", locals(), globals())
nat_flag = myst(r"""\en{natural}\fr{naturel}""", locals(), globals()) if domain == "N" else " "
latex_reasoning += myst(r"""& \text{\fr{since}\en{car} } \py{variable} \text{ \en{is an integer}\fr{est un entier} \py{nat_flag}}\\""", locals(), globals())
# Finish LaTeX reasoning
latex_reasoning = latex_reasoning[:-2] # remove last line break
latex_reasoning += myst(r"""\,.
\end{align*}\end{equation*}""", locals(), globals())
latex_reasoning = __simplify_signs(latex_reasoning)
return solution_set, latex_reasoning
## ==================== Tests resoudre_inequation_generale() =================
[docs]
def run_tests_resoudre_inequation_generale():
from sympy import Symbol, sqrt, Rational, oo, latex
# Définition des variables symboliques
x = Symbol('x')
a_sym = Symbol('a', positive=True)
b_sym = Symbol('b', real=True)
k = Symbol('k', real=True)
print("="*80)
print("TESTS DE LA FONCTION resoudre_inequation_generale")
print("="*80 + "\n")
# ============================================================================
# TESTS LINÉAIRES (puissance=1) - CAS NUMÉRIQUES
# ============================================================================
print("Test 1a: 2x + 3 ≥ 10 (linéaire, a>0, >=, domaine R)")
result = resoudre_inequation_generale(2, 3, 10, variable="x", inegalite=">=", domaine="R", puissance=1)
print("RAISONNEMENT DÉTAILLÉ:")
print(result[1])
print(f"\nSolutions : ${latex(result[0])}$")
print("\n" + "="*80 + "\n")
print("Test 1b: 2x + 3 ≤ 10 (linéaire, a>0, <=, domaine R)")
result = resoudre_inequation_generale(2, 3, 10, variable="x", inegalite="<=", domaine="R", puissance=1)
print("RAISONNEMENT DÉTAILLÉ:")
print(result[1])
print(f"\nSolutions : ${latex(result[0])}$")
print("\n" + "="*80 + "\n")
print("Test 1c: 2x + 3 > 10 (linéaire, a>0, >, domaine R)")
result = resoudre_inequation_generale(2, 3, 10, variable="x", inegalite=">", domaine="R", puissance=1)
print("RAISONNEMENT DÉTAILLÉ:")
print(result[1])
print(f"\nSolutions : ${latex(result[0])}$")
print("\n" + "="*80 + "\n")
print("Test 1d: 2x + 3 < 10 (linéaire, a>0, <, domaine R)")
result = resoudre_inequation_generale(2, 3, 10, variable="x", inegalite="<", domaine="R", puissance=1)
print("RAISONNEMENT DÉTAILLÉ:")
print(result[1])
print(f"\nSolutions : ${latex(result[0])}$")
print("\n" + "="*80 + "\n")
print("Test 2a: -3x + 5 ≥ 2 (linéaire, a<0, >=, domaine R)")
result = resoudre_inequation_generale(-3, 5, 2, variable="x", inegalite=">=", domaine="R", puissance=1)
print("RAISONNEMENT DÉTAILLÉ:")
print(result[1])
print(f"\nSolutions : ${latex(result[0])}$")
print("\n" + "="*80 + "\n")
print("Test 2b: -3x + 5 ≤ 2 (linéaire, a<0, <=, domaine R)")
result = resoudre_inequation_generale(-3, 5, 2, variable="x", inegalite="<=", domaine="R", puissance=1)
print("RAISONNEMENT DÉTAILLÉ:")
print(result[1])
print(f"\nSolutions : ${latex(result[0])}$")
print("\n" + "="*80 + "\n")
print("Test 3a: 4x - 7 ≥ 5 (linéaire, b<0, domaine Z)")
result = resoudre_inequation_generale(4, -7, 5, variable="x", inegalite=">=", domaine="Z", puissance=1)
print("RAISONNEMENT DÉTAILLÉ:")
print(result[1])
print(f"\nSolutions : ${latex(result[0])}$")
print("\n" + "="*80 + "\n")
print("Test 3b: 4x - 7 ≥ 5 (linéaire, b<0, domaine N)")
result = resoudre_inequation_generale(4, -7, 5, variable="x", inegalite=">=", domaine="N", puissance=1)
print("RAISONNEMENT DÉTAILLÉ:")
print(result[1])
print(f"\nSolutions : ${latex(result[0])}$")
print("\n" + "="*80 + "\n")
print("Test 4: 5x + 12 < -3 (linéaire, c<0, domaine Z)")
result = resoudre_inequation_generale(5, 12, -3, variable="x", inegalite="<", domaine="Z", puissance=1)
print("RAISONNEMENT DÉTAILLÉ:")
print(result[1])
print(f"\nSolutions : ${latex(result[0])}$")
print("\n" + "="*80 + "\n")
print("Test 5: -2x - 8 > -4 (linéaire, a<0, b<0, c<0, domaine R)")
result = resoudre_inequation_generale(-2, -8, -4, variable="x", inegalite=">", domaine="R", puissance=1)
print("RAISONNEMENT DÉTAILLÉ:")
print(result[1])
print(f"\nSolutions : ${latex(result[0])}$")
print("\n" + "="*80 + "\n")
# ============================================================================
# TESTS LINÉAIRES - CAS AVEC FRACTIONS
# ============================================================================
print("Test 6: (1/2)x + 3 ≥ 7 (linéaire, coefficients rationnels)")
result = resoudre_inequation_generale(Rational(1, 2), 3, 7, variable="x", inegalite=">=", domaine="R", puissance=1)
print("RAISONNEMENT DÉTAILLÉ:")
print(result[1])
print(f"\nSolutions : ${latex(result[0])}$")
print("\n" + "="*80 + "\n")
print("Test 7: (-3/4)x + 2 < 5 (linéaire, a rationnel négatif)")
result = resoudre_inequation_generale(Rational(-3, 4), 2, 5, variable="x", inegalite="<", domaine="R", puissance=1)
print("RAISONNEMENT DÉTAILLÉ:")
print(result[1])
print(f"\nSolutions : ${latex(result[0])}$")
print("\n" + "="*80 + "\n")
print("Test 7b: autre cas a fractionnaire : (1/sqrt(2))x + 2 < 5 (linéaire, a rationnel négatif)")
result = resoudre_inequation_generale(1 / sqrt(2), 2, 5, variable="x", inegalite="<", domaine="R", puissance=1)
print("RAISONNEMENT DÉTAILLÉ:")
print(result[1])
print(f"\nSolutions : ${latex(result[0])}$")
print("\n" + "="*80 + "\n")
# ============================================================================
# TESTS LINÉAIRES - CAS AVEC RACINES ET EXPRESSIONS
# ============================================================================
print("Test 8: x + √2 ≥ 5 (linéaire, b irrationnel)")
result = resoudre_inequation_generale(1, sqrt(2), 5, variable="x", inegalite=">=", domaine="R", puissance=1)
print("RAISONNEMENT DÉTAILLÉ:")
print(result[1])
print(f"\nSolutions : ${latex(result[0])}$")
print("\n" + "="*80 + "\n")
print("Test 9: 2x + (1 - √3) ≤ 4 (linéaire, b expression avec racine)")
result = resoudre_inequation_generale(2, 1 - sqrt(3), 4, variable="x", inegalite="<=", domaine="R", puissance=1)
print("RAISONNEMENT DÉTAILLÉ:")
print(result[1])
print(f"\nSolutions : ${latex(result[0])}$")
print("\n" + "="*80 + "\n")
print("Test 10: √5·x + 2 > √7 + 1 (linéaire, a et c irrationnels)")
result = resoudre_inequation_generale(sqrt(5), 2, sqrt(7) + 1, variable="x", inegalite=">", domaine="R", puissance=1)
print("RAISONNEMENT DÉTAILLÉ:")
print(result[1])
print(f"\nSolutions : ${latex(result[0])}$")
print("\n" + "="*80 + "\n")
print("Test 10bis: 2x - 3 + 2√3 > 1 (linéaire, a et c irrationnels, doit se simplifier)")
result = resoudre_inequation_generale(2, -3 + 2 * sqrt(3), 1, variable="x", inegalite=">", domaine="R", puissance=1)
print("RAISONNEMENT DÉTAILLÉ:")
print(result[1])
print(f"\nSolutions : ${latex(result[0])}$")
print("\n" + "="*80 + "\n")
# ============================================================================
# TESTS LINÉAIRES - CAS SYMBOLIQUES
# ============================================================================
print("Test 11: ax + 3 ≥ 10 (linéaire symbolique, a>0 par hypothèse)")
result = resoudre_inequation_generale(a_sym, 3, 10, variable="x", inegalite=">=", domaine="R", puissance=1)
print("RAISONNEMENT DÉTAILLÉ:")
print(result[1])
print(f"\nSolutions : ${latex(result[0])}$")
print("\n" + "="*80 + "\n")
print("Test 12: 2x + b ≤ 5 (linéaire symbolique, b réel)")
result = resoudre_inequation_generale(2, b_sym, 5, variable="x", inegalite="<=", domaine="R", puissance=1)
print("RAISONNEMENT DÉTAILLÉ:")
print(result[1])
print(f"\nSolutions : ${latex(result[0])}$")
print("\n" + "="*80 + "\n")
print("Test 13: ax + b > k (linéaire entièrement symbolique)")
result = resoudre_inequation_generale(a_sym, b_sym, k, variable="x", inegalite=">", domaine="R", puissance=1)
print("RAISONNEMENT DÉTAILLÉ:")
print(result[1])
print(f"\nSolutions : ${latex(result[0])}$")
print("\n" + "="*80 + "\n")
# ============================================================================
# TESTS QUADRATIQUES (puissance=2) - CAS NUMÉRIQUES
# ============================================================================
print("Test 14a: x² + 0 ≥ 9 (quadratique, a>0, b=0, >=)")
result = resoudre_inequation_generale(1, 0, 9, variable="x", inegalite=">=", domaine="R", puissance=2)
print("RAISONNEMENT DÉTAILLÉ:")
print(result[1])
print(f"\nSolutions : ${latex(result[0])}$")
print("\n" + "="*80 + "\n")
print("Test 14b: x² + 0 ≤ 9 (quadratique, a>0, b=0, <=)")
result = resoudre_inequation_generale(1, 0, 9, variable="x", inegalite="<=", domaine="R", puissance=2)
print("RAISONNEMENT DÉTAILLÉ:")
print(result[1])
print(f"\nSolutions : ${latex(result[0])}$")
print("\n" + "="*80 + "\n")
print("Test 15: 2x² + 5 > 15 (quadratique, a>0, b>0, >)")
result = resoudre_inequation_generale(2, 5, 15, variable="x", inegalite=">", domaine="R", puissance=2)
print("RAISONNEMENT DÉTAILLÉ:")
print(result[1])
print(f"\nSolutions : ${latex(result[0])}$")
print("\n" + "="*80 + "\n")
print("Test 16a: 3x² - 7 < 20 (quadratique, a>0, b<0, <)")
result = resoudre_inequation_generale(3, -7, 20, variable="x", inegalite="<", domaine="R", puissance=2)
print("RAISONNEMENT DÉTAILLÉ:")
print(result[1])
print(f"\nSolutions : ${latex(result[0])}$")
print("\n" + "="*80 + "\n")
print("Test 16b: 3x² - 7 < 20 (quadratique, a>0, b<0, < et détail signe a)")
result = resoudre_inequation_generale(3, -7, 20, variable="x", inegalite="<", domaine="R", puissance=2, detail_signe_a = True)
print("RAISONNEMENT DÉTAILLÉ:")
print(result[1])
print(f"\nSolutions : ${latex(result[0])}$")
print("\n" + "="*80 + "\n")
print("Test 17: -x² + 10 ≥ 6 (quadratique, a<0, >=)")
result = resoudre_inequation_generale(-1, 10, 6, variable="x", inegalite=">=", domaine="R", puissance=2)
print("RAISONNEMENT DÉTAILLÉ:")
print(result[1])
print(f"\nSolutions : ${latex(result[0])}$")
print("\n" + "="*80 + "\n")
print("Test 18: -2x² + 8 ≤ 0 (quadratique, a<0, c=0, <=)")
result = resoudre_inequation_generale(-2, 8, 0, variable="x", inegalite="<=", domaine="R", puissance=2)
print("RAISONNEMENT DÉTAILLÉ:")
print(result[1])
print(f"\nSolutions : ${latex(result[0])}$")
print("\n" + "="*80 + "\n")
print("Test 19: x² + 3 ≥ 7 (quadratique, domaine Z)")
result = resoudre_inequation_generale(1, 3, 7, variable="x", inegalite=">=", domaine="Z", puissance=2)
print("RAISONNEMENT DÉTAILLÉ:")
print(result[1])
print(f"\nSolutions : ${latex(result[0])}$")
print("\n" + "="*80 + "\n")
print("Test 20: x² - 5 < 4 (quadratique, domaine N)")
result = resoudre_inequation_generale(1, -5, 4, variable="x", inegalite="<", domaine="N", puissance=2)
print("RAISONNEMENT DÉTAILLÉ:")
print(result[1])
print(f"\nSolutions : ${latex(result[0])}$")
print("\n" + "="*80 + "\n")
# ============================================================================
# TESTS QUADRATIQUES - CAS SANS SOLUTION OU SOLUTION VIDE
# ============================================================================
print("Test 21: x² + 5 < 2 (quadratique, pas de solution réelle)")
result = resoudre_inequation_generale(1, 5, 2, variable="x", inegalite="<", domaine="R", puissance=2)
print("RAISONNEMENT DÉTAILLÉ:")
print(result[1])
print(f"\nSolutions : ${latex(result[0])}$")
print("\n" + "="*80 + "\n")
print("Test 22: -x² - 3 > 0 (quadratique, a<0, pas de solution)")
result = resoudre_inequation_generale(-1, -3, 0, variable="x", inegalite=">", domaine="R", puissance=2)
print("RAISONNEMENT DÉTAILLÉ:")
print(result[1])
print(f"\nSolutions : ${latex(result[0])}$")
print("\n" + "="*80 + "\n")
# ============================================================================
# TESTS QUADRATIQUES - CAS AVEC FRACTIONS ET RACINES
# ============================================================================
print("Test 23: (1/2)x² + 1 ≥ 3 (quadratique, a rationnel)")
result = resoudre_inequation_generale(Rational(1, 2), 1, 3, variable="x", inegalite=">=", domaine="R", puissance=2)
print("RAISONNEMENT DÉTAILLÉ:")
print(result[1])
print(f"\nSolutions : ${latex(result[0])}$")
print("\n" + "="*80 + "\n")
print("Test 24: x² + √2 ≤ 5 (quadratique, b irrationnel)")
result = resoudre_inequation_generale(1, sqrt(2), 5, variable="x", inegalite="<=", domaine="R", puissance=2)
print("RAISONNEMENT DÉTAILLÉ:")
print(result[1])
print(f"\nSolutions : ${latex(result[0])}$")
print("\n" + "="*80 + "\n")
print("Test 25: 2x² + (√3 - 1) > √5 (quadratique, expressions irrationnelles)")
result = resoudre_inequation_generale(2, sqrt(3) - 1, sqrt(5), variable="x", inegalite=">", domaine="R", puissance=2)
print("RAISONNEMENT DÉTAILLÉ:")
print(result[1])
print(f"\nSolutions : ${latex(result[0])}$")
print("\n" + "="*80 + "\n")
# ============================================================================
# TESTS QUADRATIQUES - CAS SYMBOLIQUES
# ============================================================================
print("Test 26: ax² + 2 ≥ 10 (quadratique symbolique, a>0)")
result = resoudre_inequation_generale(a_sym, 2, 10, variable="x", inegalite=">=", domaine="R", puissance=2)
print("RAISONNEMENT DÉTAILLÉ:")
print(result[1])
print(f"\nSolutions : ${latex(result[0])}$")
print("\n" + "="*80 + "\n")
print("Test 27: x² + b ≤ k (quadratique symbolique)")
result = resoudre_inequation_generale(1, b_sym, k, variable="x", inegalite="<=", domaine="R", puissance=2)
print("RAISONNEMENT DÉTAILLÉ:")
print(result[1])
print(f"\nSolutions : ${latex(result[0])}$")
print("\n" + "="*80 + "\n")
# ============================================================================
# TESTS DE CAS LIMITES
# ============================================================================
print("Test 28: 0x + 5 ≥ 3 (linéaire dégénéré, a=0, toujours vrai)")
result = resoudre_inequation_generale(0, 5, 3, variable="x", inegalite=">=", domaine="R", puissance=1)
print("RAISONNEMENT DÉTAILLÉ:")
print(result[1])
print(f"\nSolutions : ${latex(result[0])}$")
print("\n" + "="*80 + "\n")
print("Test 29: 0x + 2 > 5 (linéaire dégénéré, a=0, jamais vrai)")
result = resoudre_inequation_generale(0, 2, 5, variable="x", inegalite=">", domaine="R", puissance=1)
print("RAISONNEMENT DÉTAILLÉ:")
print(result[1])
print(f"\nSolutions : ${latex(result[0])}$")
print("\n" + "="*80 + "\n")
print("Test 30: x² + 0 ≥ 0 (quadratique, toujours vrai)")
result = resoudre_inequation_generale(1, 0, 0, variable="x", inegalite=">=", domaine="R", puissance=2)
print("RAISONNEMENT DÉTAILLÉ:")
print(result[1])
print(f"\nSolutions : ${latex(result[0])}$")
print("\n" + "="*80 + "\n")
print("Test 31a: -3x + 7 ≤ 1 (linéaire, a<0, domaine N avec solution)")
result = resoudre_inequation_generale(-3, 7, 1, variable="x", inegalite="<=", domaine="N", puissance=2)
print("RAISONNEMENT DÉTAILLÉ:")
print(result[1])
print(f"\nSolutions : ${latex(result[0])}$")
print("\n" + "="*80 + "\n")
print("Test 31b: -3x + 7 ≤ 1 (linéaire, a<0, domaine N avec solution + détail signe a)")
result = resoudre_inequation_generale(-3, 7, 1, variable="x", inegalite="<=", domaine="N", puissance=2, detail_signe_a = True)
print("RAISONNEMENT DÉTAILLÉ:")
print(result[1])
print(f"\nSolutions : ${latex(result[0])}$")
print("\n" + "="*80 + "\n")
print("Test 32: (1/3)x² - 2 > 1 (quadratique rationnel, domaine Z)")
result = resoudre_inequation_generale(Rational(1, 3), -2, 1, variable="x", inegalite=">", domaine="Z", puissance=2)
print("RAISONNEMENT DÉTAILLÉ:")
print(result[1])
print(f"\nSolutions : ${latex(result[0])}$")
print("\n" + "="*80 + "\n")
print("Test 33: Cx² + sqrt(2) + 1 > sqrt(3)")
C = Symbol("C")
result = resoudre_inequation_generale(C, sqrt(2) + 1, sqrt(3), variable="n", inegalite=">", domaine="R", puissance=2)
print("RAISONNEMENT DÉTAILLÉ:")
print(result[1])
print(f"\nSolutions : ${latex(result[0])}$")
print("\n" + "="*80 + "\n")
print("Test 34: Cx² + sqrt(2) + 1 > sqrt(3) dans Z")
C = Symbol("C")
result = resoudre_inequation_generale(C, sqrt(2) + 1, sqrt(3), variable="n", inegalite=">", domaine="Z", puissance=2)
print("RAISONNEMENT DÉTAILLÉ:")
print(result[1])
print(f"\nSolutions : ${latex(result[0])}$")
print("\n" + "="*80 + "\n")
print("Test 35: totalement indéterminé:")
a, b, c = symbols("a,b,c")
result = resoudre_inequation_generale(a, b, c, variable="n", inegalite=">", domaine="R", puissance=2)
print("RAISONNEMENT DÉTAILLÉ:")
print(result[1])
print(f"\nSolutions : ${latex(result[0])}$")
print("\n" + "="*80 + "\n")
print("="*80)
print("FIN DES TESTS")
print("="*80)
[docs]
def pxsl_latex(expr, reverse = False):
"""
Convertit une expression symbolique en représentation LaTeX.
Args:
expr: Expression symbolique (probablement SymPy) à convertir
reverse (bool): Si True, inverse l'ordre des termes dans l'expression
Returns:
str: Représentation LaTeX de l'expression
Examples:
>>> from sympy import symbols
>>> x, y = symbols('x y')
# Conversion standard
>>> expr = x**2 + 3*x - 5
>>> pxsl_latex(expr)
'x^{2} + 3 x - 5'
# Conversion avec ordre inversé
>>> pxsl_latex(expr, reverse=True)
'- 5 + 3 x + x^{2}'
# Expression avec termes négatifs
>>> expr2 = -2*x**2 + x - 7
>>> pxsl_latex(expr2, reverse=True)
'- 7 + x - 2 x^{2}'
# Expression plus complexe
>>> expr3 = x**3 - 2*x**2 + 3*x - 4
>>> pxsl_latex(expr3, reverse=True)
'- 4 + 3 x - 2 x^{2} + x^{3}'
Note:
La fonction utilise myst() pour l'interpolation des variables,
ce qui suggère une intégration avec MyST (Markedly Structured Text).
"""
# Si l'option reverse est activée, traiter les termes dans l'ordre inverse
if reverse == True:
# Initialiser la chaîne LaTeX vide
expr_latex = ""
# Obtenir les termes de l'expression dans l'ordre inverse
# as_ordered_terms() retourne une liste des termes, [::-1] inverse la liste
terms = expr.as_ordered_terms()[::-1]
# Parcourir chaque terme avec son index
for i, term in enumerate(terms):
# Convertir le terme en chaîne de caractères
term_str = str(term)
# Pour tous les termes sauf le premier, ajouter un signe +
# seulement si le terme ne commence pas déjà par un signe négatif
if i != 0 and not term_str.startswith('-'):
sign = myst(r"""+""")
else:
# Pas de signe pour le premier terme ou les termes négatifs
sign = ""
# Construire l'expression LaTeX en ajoutant le signe et le terme converti
# myst() semble être une fonction de template qui interpole les variables
expr_latex += myst(r"""\py{sign}\py{latex(term)} """, globals(), locals())
# Retourner l'expression LaTeX complète
return expr_latex
# Cas par défaut : retourner simplement la conversion LaTeX standard
return latex(expr)
[docs]
def pxs_is_reductible_sqrt(x):
"""
fr : détermine si un nombre ou une expression est reductible en racine carrée
en : determines if a number or an expression is reducible for square root
Args:
x : nombre ou expression symbolique
Returns:
bool: True si x est simplifiable dans une racine carrée
Examples:
>>> pxs_is_reductible_sqrt(16)
'True'
>>> pxs_is_reductible_sqrt(24)
'True'
>>> pxs_is_reductible_sqrt(13)
'False'
>>> pxs_is_reductible_sqrt(13/24)
'True'
>>> x = Symbol('x')
>>> pxs_is_reductible_sqrt(4*x)
'True'
>>> pxs_is_reductible_sqrt(x/4)
'True'
>>> y = Symbol('y')
>>> pxs_is_reductible_sqrt(x/(4*y))
'True'
>>> pxs_is_reductible_sqrt(3*x/(2*y))
'False'
"""
def _is_factor(x, is_reducible):
factors = factorint(x)
for prime, power in factors.items():
if power >= 2:
is_reducible = True
return is_reducible
def _is_reducible_int(x, is_reducible):
if isinstance(x, (int, Integer)):
is_reducible = _is_factor(x, is_reducible)
return is_reducible
def _is_reducible_rational(x, is_reducible):
try:
x_ratio = Rational(x)
is_reducible = _is_factor(x_ratio.p, is_reducible)
is_reducible = _is_factor(x_ratio.q, is_reducible)
except:
pass
return is_reducible
is_reducible = False
is_reducible = _is_reducible_int(x, is_reducible)
is_reducible = _is_reducible_rational(x, is_reducible)
if isinstance(x, Mul):
for arg in x.args:
is_reducible = _is_reducible_int(arg, is_reducible)
is_reducible = _is_reducible_rational(arg, is_reducible)
return is_reducible
[docs]
def pxsl_Rational(num, den, orientation="v", display=True):
"""
Builds a SymPy expression representing the rational fraction `num/den`,
simplifying only the numeric parts while keeping the symbolic or irrational
factors in place.
Parameters
----------
num : sympy.Expr, int, float
The numerator of the fraction. Can be numeric or symbolic
(e.g., `3*pi`, `2*x`, etc.).
den : sympy.Expr, int, float
The denominator of the fraction. Must not be zero.
orientation : str, optional
Display orientation: `'v'` for vertical (LaTeX-style fraction),
or any other value for horizontal rendering. Default is `'v'`.
display : bool, optional
If `True`, returns a formatted LaTeX string via `myst()` for
visual display. If `False`, returns a raw LaTeX string.
Default is `True`.
Returns
-------
sympy.Expr or str
A SymPy expression representing the simplified fraction, or a LaTeX
string depending on the `orientation` and `display` parameters.
Raises
------
ZeroDivisionError
If `den` equals zero.
Examples
--------
>>> pxsl_Rational(3*pi, 6)
\displaystyle{\frac{\pi}{2}}
>>> x = Symbol('x')
>>> pxsl_Rational(4*x, 8)
\displaystyle{\frac{x}{2}}
>>> pxsl_Rational(3*pi, 6, orientation='h')
\pi / 2
"""
# Convert inputs into SymPy expressions
num = sympify(num)
den = sympify(den)
# Denominator must not be zero
if den == 0:
raise ZeroDivisionError("Denominator is zero.")
# Separate the numeric coefficient from the symbolic/irrational remainder
# Example: 3*pi → (3, pi), 6 → (6, 1)
ncoef, nrest = num.as_coeff_Mul()
dcoef, drest = den.as_coeff_Mul()
# Extract internal numerator/denominator for rational arithmetic
# Example: Rational(3,2) → (3, 2)
a, b = ncoef.as_numer_denom()
c, d = dcoef.as_numer_denom()
# Preliminary GCD reduction between a and c to avoid large integers
g1 = gcd(a, c)
if g1 != 0:
a //= g1
c //= g1
# Compute the numeric coefficient (a*d)/(b*c)
num_int = a * d
den_int = b * c
# Normalize sign: denominator must always be positive
if den_int < 0:
den_int = -den_int
num_int = -num_int
# Final GCD reduction for the numeric ratio
g2 = gcd(num_int, den_int)
if g2 != 0:
num_int //= g2
den_int //= g2
# Build the final SymPy expression, keeping symbolic structure intact
expr = (Integer(num_int) * nrest) / (Integer(den_int) * drest)
# Display handling according to user parameters
if orientation == 'v' and display:
# Special case: denominator equals ±1
if Integer(den_int) * drest == 1:
return myst(r""" \displaystyle{\py{latex(Integer(num_int) * nrest)}} """, locals(), globals())
elif Integer(den_int) * drest == -1:
return myst(r""" \displaystyle{\py{latex(-Integer(num_int) * nrest)}} """, locals(), globals())
# General case
return myst(r"""\displaystyle{\py{latex(expr)}}""", locals(), globals())
elif orientation == 'v':
# Non-displayed version (raw LaTeX)
return myst(r"""\py{latex(expr)}""", locals(), globals())
else:
# Horizontal display mode
if Integer(den_int) * drest == 1:
return myst(r"""\py{latex(Integer(num_int) * nrest)}""", locals(), globals())
elif Integer(den_int) * drest == -1:
return myst(r"""\py{latex(-Integer(num_int) * nrest)}""", locals(), globals())
else:
return myst(r"""\py{latex(Integer(num_int) * nrest)} / \py{latex(Integer(den_int) * drest)}""", locals(), globals())
[docs]
def pxs_separate_factors(expr, var):
if isinstance(expr, Mul):
return expr.as_independent(var)[0], expr.as_independent(var)[1]
if isinstance(expr, Add):
expr = factor(expr)
if isinstance(expr, Add):
return 1, expr
else:
return pxs_separate_factors(expr, var)
return 1, expr
[docs]
def pxs_ln(arg):
"""
Reduce the natural logarithm ln(arg) when the argument is a perfect power.
The function rewrites ln(m**k) as k*ln(m) whenever possible.
If the argument cannot be reduced, the expression ln(arg) is returned unchanged.
Parameters
----------
arg : sympy expression or int
Argument of the natural logarithm.
Returns
-------
sympy expression
A reduced logarithmic expression of the form k*ln(m) if applicable,
otherwise ln(arg).
Examples
--------
>>> pxs_ln(9)
2*ln(3)
>>> pxs_ln(12)
ln(12)
>>> pxs_ln(1)
ln(1)
>>> pxs_ln(72)
2*ln(6)
"""
# Convert input to a SymPy object
arg = sympify(arg)
# Domain and type checks
if not arg.is_Integer or arg <= 0:
return ln(arg)
# Special case: ln(1)
if arg == 1:
return ln(1)
# Prime factorization of the argument
factors = factorint(arg)
# Safety check: empty factorization (e.g. arg == 1)
if not factors:
return ln(arg)
# Extract the maximal common exponent
k = min(factors.values())
if k <= 1:
return ln(arg)
# Reconstruct the base m such that arg = m**k
m = Integer(1)
for p, e in factors.items():
m *= p**(e // k)
return k * ln(m)
[docs]
def pxs_is_factorable(expr) -> bool:
"""
Determine whether a SymPy expression is factorable.
An expression is considered factorable if its factorized form
is not equivalent to its expanded form.
Parameters
----------
expr : sympy expression or str
The expression to test.
Returns
-------
bool
True if the expression is factorable, False otherwise.
Examples
--------
>>> pxsl_is_factorable("x^2 - 1")
True
>>> pxsl_is_factorable("x^2 + 1")
False
>>> pxsl_is_factorable("2*x*(x+1)")
False
"""
if isinstance(expr, Poly):
expr = expr.as_expr()
expr = sympify(expr)
expanded = expand(expr)
factored = factor(expr)
# If factor() does not change the structure, it's not factorable
return not simplify(expanded - factored) == 0
[docs]
def pxsl_quotient(num: "sympy.Expr", den: "sympy.Expr", sign: bool = True) -> str:
"""
Formats a quotient in LaTeX using myst, with special handling depending
on whether the numerator is equal to 1.
Parameters
----------
num : sympy.Expr
The numerator of the quotient.
den : sympy.Expr
The denominator of the quotient.
sign : bool, optional
If True, the sign of the expression is explicitly handled.
Default is True.
Returns
-------
str
A LaTeX-formatted string generated via `myst`, representing
either the simplified quotient `num/den` or the product
`num * 1/den` depending on the value of `num.q`.
Examples
--------
Case 1 — numerator behaves like 1 (num.q == 1)
The function returns a single `lc(num/den, sign=sign)` block.
>>> # Example setup (illustrative)
>>> # num = 1, den = x + 1
>>> pxsl_quotient(num, den)
'\\n \\\\py{lc(num/den, sign = sign)}\\n '
>>> # Same case, but without forcing sign handling
>>> pxsl_quotient(num, den, sign=False)
'\\n \\\\py{lc(num/den, sign = sign)}\\n '
Case 2 — general numerator (num.q != 1)
The function returns `lc(num, sign=sign)` multiplied by `1/den`.
>>> # Example setup (illustrative)
>>> # num = 3*x, den = x + 1
>>> pxsl_quotient(num, den)
'\\n \\\\py{lc(num, sign = sign)}\\\\py{mult_A}\\\\frac{1}{\\\\py{latex(den)}}\\n '
>>> # Same case, but without forcing sign handling
>>> pxsl_quotient(num, den, sign=False)
'\\n \\\\py{lc(num, sign = sign)}\\\\py{mult_A}\\\\frac{1}{\\\\py{latex(den)}}\\n '
"""
if num.q == 1:
return myst(r"""
\py{lc(num/den, sign = sign)}
""", globals(), locals())
else:
return myst(r"""
\py{lc(num, sign = sign)}\py{mult_A}\frac{1}{\py{latex(den)}}
""", globals(), locals())
[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 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 pxsl_mul(*args, ones = True, mult = None):
pxs_lang = get_pxs_lang()
config_standard = pxs_config()
mul_symbol = config_standard["mul_symbol"]
if mult is None:
mult = r"\cdot " if pxs_lang == "en" else r"\times "
if mul_symbol: # mult sert à mettre quelque chose entre les facteurs qui le nécessitent si mul_symbol est vide
mult = mul_symbol
factors = [sympify(fac) for fac in args if ones or fac != 1]
if factors == []: factors = [sympify(1)]
factors_tex = [latex(fac, **config_standard) for fac in factors]
tests = [fac.could_extract_minus_sign() or match(r"\s*-", fac_tex) or isinstance(fac, Add) for fac, fac_tex in zip(factors, factors_tex)]
lparentheses = [myst(r"""\left( """) if test else myst(r""" """) for test in tests]
rparentheses = [myst(r"""\right) """) if test else myst(r""" """) for test in tests]
tests_no_mult = tests[1:] + [True] # no mult if parenthesis on the right
return myst(r""" """).join([myst(r"""\py{lpar} \py{fac_tex} \py{rpar} \py{"" if test_not else mult}""", globals(), locals()) for lpar, rpar, fac_tex, test_not in zip(lparentheses, rparentheses, factors_tex, tests_no_mult)])
[docs]
def pxs_nvirgzero(x):
"""
Fr : Fonction qui supprime .0 si le nombre a une valeur entière en le convertissant en int.
En : Function that removes .0 if the number has an integer value by converting it to int.
Version 2
---------
13/03/25
Vérification
------------
Auteur : Ronan
Vérificateurs : Delphine
Paramètres
----------
x : nombre
Retour
------
int ou float :
si le nombre a une valeur entière avec une précision de E-10, il est transformé en int, sinon il n'est pas modifié.
Fonction utilisée par
---------------------
pxsl_res_num, pxs_simul_law, pxsl_sum_vector
"""
if m.isclose(x, int(x), abs_tol=1e-10)==True:
x=int(x)
return x
[docs]
def pxsl_res_num(x, dec=4, pourc=False, text=False, egal=True, dot = True):
"""
Fr : Formate un nombre pour l'affichage avec LaTeX, avec gestion d'approximation.
En : Formats a number for display with LaTeX, with approximation handling.
Version 2
---------
13/03/25
Vérification
------------
Auteur : Ronan
Vérificateurs : Delphine
Arguments:
x (float/str): Nombre à formater
dec (int): Nombre de décimales pour l'arrondi (défaut: 4)
pourc (bool): Si True, affiche également le résultat en pourcentage (défaut: False)
text (bool): Si True, utilise un format texte plus descriptif (défaut: False)
egal (bool): Si False, affichera simplement le nombre sans = ou approx devant
Returns:
str: Formule LaTeX formatée
Fonction utilisée par
---------------------
Aucune fonction pyxiscience
"""
# Conversion et arrondi du nombre
valeur_precise = round(float(x), 10) # Conversion en float et arrondi à 10 décimales pour précision interne
valeur_arrondie = round(valeur_precise, dec) # Arrondi au nombre de décimales demandé
# Vérification si l'arrondi modifie la valeur (pour décider d'utiliser ≈ ou =)
valeur_precise_int = int(valeur_precise * (10**10)) # Conversion en entier pour comparaison précise
valeur_arrondie_int = int(valeur_arrondie * (10**10)) # Conversion de la valeur arrondie
# Définition du symbole et format selon que la valeur est exacte ou approximative
est_exact = (valeur_precise_int == valeur_arrondie_int)
symbole = "" if egal == False else (" = " if est_exact else " \\approx ")
# Construction de la formule LaTeX selon les paramètres
if text:
# Version texte descriptive
prefixe = "" if est_exact else " \\fr{ environ }\\en{ approximately } "
if pourc:
# Format pourcentage avec texte explicatif
texte_pourcentage = ", \\fr{ soit " + ("" if est_exact else "environ ") + "}\\en{that is " + ("" if est_exact else "approximately ") + "} "
if dot:
resultat = myst(r"""{0}$\py{{latex(pxs_nvirgzero(round(valeur_precise,dec)))}}${1}$\py{{latex(pxs_nvirgzero(round(100*valeur_precise,dec-2)))}}$ $\%$.""".format(
prefixe, texte_pourcentage), globals(), locals())
else:
resultat = myst(r"""{0}$\py{{latex(pxs_nvirgzero(round(valeur_precise,dec)))}}${1}$\py{{latex(pxs_nvirgzero(round(100*valeur_precise,dec-2)))}}$ $\%$""".format(
prefixe, texte_pourcentage), globals(), locals())
else:
# Format décimal simple
if dot:
resultat = myst(r"""{0}$\py{{latex(pxs_nvirgzero(round(valeur_precise,dec)))}}$.""".format(prefixe), globals(), locals())
else:
resultat = myst(r"""{0}$\py{{latex(pxs_nvirgzero(round(valeur_precise,dec)))}}$""".format(prefixe), globals(), locals())
else:
# Version concise avec symbole mathématique
if pourc:
# Format pourcentage
resultat = myst(r"""{0}\py{{latex(pxs_nvirgzero(round(100*valeur_precise,dec-2)))}} \%""".format(symbole), globals(), locals())
else:
# Format décimal simple
resultat = myst(r"""{0}\py{{latex(pxs_nvirgzero(round(valeur_precise,dec)))}}""".format(symbole), globals(), locals())
return resultat
[docs]
def pxsl_num(val, dec=4, pourc=False, text=False, egal=False, dot = True):
pxs_lang = get_pxs_lang()
if pxs_lang == "fr":
return pxsl_res_num(val, dec, pourc, text, egal, dot).replace(".",",")
else:
return pxsl_res_num(val, dec, pourc, text, egal, dot)