from sympy import *
import random as rd
from src.scripts.Mes_fctions.Classes_Extensions import *
from src.scripts.pxs_runtime import myst, get_pxs_lang
[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",
}
# --- Gestion automatique des noms : nouveau système de nommage ---
[docs]
class NameManager:
[docs]
def __init__(self, start='f', skip_letters=('i', 'o', 'x', 'y', 'z')):
"""
start : lettre de départ (nom de la fonction originale)
skip_letters : lettres à éviter dans la séquence
"""
self.skip_letters = set(skip_letters)
self.start_letter = start
# Séquence normale : u, v, g, h, j, k, l, m, n, p, q, r, s, t, w
# (on évite i, o, x, y, z)
all_letters = [chr(c) for c in range(ord('a'), ord('z') + 1)]
normal_sequence = []
# D'abord u et v
if 'u' not in self.skip_letters:
normal_sequence.append('u')
if 'v' not in self.skip_letters:
normal_sequence.append('v')
# Puis g jusqu'à t (en évitant i, o, et les lettres déjà utilisées)
for letter in all_letters[6:20]: # g jusqu'à t
if letter not in self.skip_letters and letter not in ['u', 'v']:
normal_sequence.append(letter)
# Ajouter w à la fin si pas dans skip_letters
if 'w' not in self.skip_letters and 'w' not in normal_sequence:
normal_sequence.append('w')
# Gérer les cas spéciaux
if start == 'u':
# Si la fonction originale est u, la séquence est : v, w, g, h, j, k, l, ...
self.sequence = ['v', 'w'] + [l for l in normal_sequence if l not in ['u', 'v', 'w']]
elif start == 'v':
# Si la fonction originale est v, la séquence est : u, w, g, h, j, k, l, ...
self.sequence = ['u', 'w'] + [l for l in normal_sequence if l not in ['u', 'v', 'w']]
else:
# Cas normal : u, v, g, h, j, k, l, ...
self.sequence = normal_sequence
self.index = -1 # On commence à -1 pour que le premier next() donne le premier élément
[docs]
def next(self):
"""Avance d'une lettre et la renvoie."""
self.index = (self.index + 1) % len(self.sequence)
return self.sequence[self.index]
[docs]
def current(self):
"""Renvoie la lettre courante (la fonction originale)."""
return self.start_letter
def _pxsl_print_latex(steps, print_equation = True):
"""Formate la liste d'étapes en LaTeX."""
config_standard = pxs_config()
if len(steps) == 1:
if print_equation:
return myst(r"""
\begin{equation*}
\py{latex(steps[0],**config_standard)}
\end{equation*}
""", globals(), locals())
else:
return myst(r"""
\py{latex(steps[0],**config_standard)}
""", globals(), locals())
if len(steps) == 2:
if print_equation:
return myst(r"""
\begin{equation*}
\py{latex(steps[0],**config_standard)} = \py{latex(steps[1],**config_standard)}
\end{equation*}
""", globals(), locals())
else:
return myst(r"""
\py{latex(steps[0],**config_standard)} = \py{latex(steps[1],**config_standard)}
""", globals(), locals())
lines = [myst(r"""
\py{latex(steps[0],**config_standard)} &= \py{latex(steps[1],**config_standard)}
""", globals(), locals())]
for s in steps[2:]:
lines.append(myst(r"""\\&= \py{latex(s,**config_standard)}""", globals(), locals()))
body = myst(r""" \py{" ".join(lines)}""", globals(), locals())
if print_equation:
return myst(r"""
\begin{equation*}
\begin{split}
\py{body}
\end{split}
\end{equation*}
""", globals(), locals())
else:
return myst(r"""
\py{body}
""", globals(), locals())
# --- Utilitaires ---
[docs]
def to_add_rest(args):
return pxs_Add(*args) if args else S.Zero
[docs]
def to_mul_rest(args):
return pxs_Mul(*args) if args else S.One
[docs]
def is_simple_linear(expr, variable):
"""Détecte une expression du type a*x + b ou a*x avec a,b constants (coeffs numériques)."""
poly = expr.as_poly(variable)
if poly is None:
return False
return poly.degree() <= 1 and all(c.is_number for c in poly.all_coeffs())
[docs]
def is_polynomial_in(expr, variable):
"""Retourne True si expr est un polynôme (coeffs numériques) en 'variable'."""
return expr.as_poly(variable) is not None
[docs]
def pxs_is_reference_function_diff(expr, variable):
"""
Retourne True si expr est une fonction de référence (éventuellement multipliée par une constante).
Fonctions de référence : constante, x, x^n, 1/x, sqrt(x), e^x, ln(x), sin(x), cos(x), polynômes simples
"""
# Cas 1 : constante
if expr.is_number:
return True
# Cas 2 : fonction simple
if expr == variable: # x
return True
if isinstance(expr, Pow) and expr.base == variable and expr.exp.is_number: # x^n
return True
if isinstance(expr, exp) and expr.args[0] == variable: # e^x
return True
if isinstance(expr, log) and expr.args[0] == variable: # ln(x)
return True
if isinstance(expr, sin) and expr.args[0] == variable: # sin(x)
return True
if isinstance(expr, cos) and expr.args[0] == variable: # cos(x)
return True
# Cas 3 : polynôme simple (comme 2x+1, x-3, etc.)
if expr.as_poly(variable) is not None:
poly = expr.as_poly(variable)
# C'est un polynôme avec coefficients numériques
if all(coeff.is_number for coeff in poly.all_coeffs()):
return True
# Cas 4 : fonction multipliée par une constante (k*f(x))
if isinstance(expr, Mul):
args = list(expr.args)
# Chercher un facteur constant
const_factors = [a for a in args if a.is_number]
if const_factors:
# Retirer les constantes et vérifier si le reste est une fonction de référence
other_factors = [a for a in args if not a.is_number]
if len(other_factors) == 1:
return pxs_is_reference_function_diff(other_factors[0], variable)
return False
[docs]
def pxs_is_simple_function_diff(expr, variable):
"""
Retourne True si expr est une fonction simple :
- fonction de référence
- k × fonction de référence
- somme de fonctions de référence (avec ou sans coefficients)
"""
# Cas 1 : fonction de référence directe
if pxs_is_reference_function_diff(expr, variable):
return True
# Cas 2 : k × fonction de référence
if isinstance(expr, Mul):
args = list(expr.args)
const_factors = [a for a in args if a.is_number]
other_factors = [a for a in args if not a.is_number]
if const_factors and len(other_factors) == 1:
return pxs_is_reference_function_diff(other_factors[0], variable)
# Cas 3 : somme de fonctions de référence (éventuellement avec coefficients)
if isinstance(expr, Add):
for term in expr.args:
# Vérifier si le terme est une fonction de référence
if pxs_is_reference_function_diff(term, variable):
continue
# Vérifier si c'est k × fonction de référence
elif isinstance(term, Mul):
has_const = any(a.is_number for a in term.args)
non_const = [a for a in term.args if not a.is_number]
if has_const and len(non_const) >= 1:
# Vérifier si le produit des facteurs non constants est une fonction de référence
if len(non_const) == 1:
if pxs_is_reference_function_diff(non_const[0], variable):
continue
else:
non_const_prod = Mul(*non_const)
if pxs_is_reference_function_diff(non_const_prod, variable):
continue
# Si on arrive ici, le terme n'est pas acceptable
return False
# Tous les termes sont acceptables
return True
return False
[docs]
def pxs_is_reference_function(expr, variable):
"""
Retourne True si expr est une fonction de référence (éventuellement multipliée par une constante).
"""
# Cas 1 : constante
if expr.is_number:
return True
# Cas 2 : fonction simple
if expr == variable: # x
return True
if isinstance(expr, Pow) and expr.base == variable and expr.exp.is_number: # x^n
return True
if isinstance(expr, exp): # e^x
return True
if isinstance(expr, log): # ln(x)
return True
if isinstance(expr, sin): # sin(x)
return True
if isinstance(expr, cos): # cos(x)
return True
# Cas 4 : fonction multipliée par une constante (k*f(x))
if isinstance(expr, Mul):
args = list(expr.args)
# Chercher un facteur constant
const_factors = [a for a in args if a.is_number]
if const_factors:
# Retirer les constantes et vérifier si le reste est une fonction de référence
other_factors = [a for a in args if not a.is_number]
if len(other_factors) == 1:
return pxs_is_reference_function(other_factors[0], variable)
return False
[docs]
def pxs_is_simple(expr, variable):
"""Détecte une expression du type a*x ou b avec a,b constants (coeffs numériques)."""
poly = expr.as_poly(variable)
if poly is None:
return False
return (poly.degree() <= 1 and all(c.is_number for c in poly.all_coeffs())) or isinstance(expr,exp) or isinstance(expr,log) or isinstance(expr,Pow) or isinstance(expr,sin) or isinstance(expr,cos) or isinstance(expr,tan)
[docs]
def pxsl_print(expr, variable = None, detail = None, print_equation = True):
"""
"""
simplified = None
if detail is None:
steps = [expr]
terms = []
if isinstance(expr,Add):
for term in expr.args:
terms.append(term)
simplified = Add(*terms)
if set(steps[0].args) == set(simplified.args):
simplified = expand(expr)
else:
simplified = expand(expr)
if all(set(step.args) != set(simplified.args) for step in steps):
steps.append(simplified)
return _pxsl_print_latex(steps, print_equation)
elif detail == "high":
steps = dispatch(expr, variable, steps=[expr])
simplified = expand(expr)
if all(set(step.args) != set(simplified.args) for step in steps):
steps.append(simplified)
return _pxsl_print_latex(steps, print_equation)
# Fonction utilitaire pour préserver l'ordre des termes
[docs]
def preserve_order_add(*terms):
"""Crée une addition en préservant l'ordre des termes."""
if len(terms) == 0:
return S.Zero
elif len(terms) == 1:
return terms[0]
else:
return SymPyAdd(*terms, evaluate=False)
# --- Découpe "logique" pour Add : avec les constantes à la fin---
[docs]
def split_add_terms(expr):
args = list(expr.args)
if args and args[0].is_number and len(args) > 1:
return args[1], to_add_rest([args[0]] + args[2:])
return args[0], to_add_rest(args[1:])
[docs]
def split_mul_terms(expr):
"""
Découpe intelligemment un produit en deux facteurs.
Si on a -x*f(x), on veut (-x, f(x)) et non (-1, x*f(x))
"""
args = list(expr.args)
# Cas spécial : si le premier argument est -1 et qu'il y a au moins 3 arguments
# (par exemple -1 * x * ln(x)), on regroupe -1 avec le suivant
if len(args) >= 3 and args[0] == S.NegativeOne:
# Regrouper -1 avec le deuxième terme
first_term = -args[1] # -1 * args[1] = -args[1]
remaining_terms = args[2:]
return first_term, to_mul_rest(remaining_terms)
# Cas spécial : si on a k*x*f(x) où k est un nombre autre que -1
# on veut (k*x, f(x)) si x est la variable
elif len(args) >= 3 and args[0].is_number and not args[0] == S.NegativeOne:
# Si le deuxième terme est simple (comme x), on le groupe avec k
first_term = args[0] * args[1]
remaining_terms = args[2:]
return first_term, to_mul_rest(remaining_terms)
# Cas général : découper après le premier terme
elif len(args) >= 2:
return args[0], to_mul_rest(args[1:])
else:
return expr, S.One
# --- Helpers pour l'étape "quotient" ---
def _decompose_v_bases_exps(v):
"""Renvoie [(base, exp)] pour v (qui est un produit de puissances positives)."""
if isinstance(v, Pow):
return [(v.base, v.exp)]
elif isinstance(v, Mul):
out = []
for a in v.args:
if isinstance(a, Pow):
out.append((a.base, a.exp))
else:
out.append((a, S.One))
return out
else:
return [(v, S.One)]
def _squarefree_common_factor(v):
"""
Pour v = ∏ w_i^{n_i} avec n_i ∈ ℕ, renvoie g = ∏ w_i^{max(n_i-1,0)}.
C'est le plus grand facteur commun de v et v' (utile pour "factoriser puis simplifier").
Si v contient des exposants non entiers (ex: 1/2), on ne prend rien pour ces facteurs.
"""
g = S.One
for base, exp in _decompose_v_bases_exps(v):
if getattr(exp, "is_integer", False) and exp.is_Integer and exp > 1:
g *= base**(exp - 1)
return g
def _fmt_paren(expr):
"""Latex avec parenthèses si somme."""
config_standard = pxs_config()
return f"\\left({latex(expr,**config_standard)}\\right)" if isinstance(expr, Add) else latex(expr,**config_standard)
def _expanded_minus_str(term1, term2):
"""
Construit 'latex(expand_mul(term1),**config_standard) - latex(expand_mul(term2),**config_standard)'
en distribuant le '-' terme à terme si term2 est une somme,
pour obtenir p.ex. '2x+6-4x-6'.
"""
config_standard = pxs_config()
t1 = expand_mul(term1)
t2 = expand_mul(term2)
s1 = latex(t1,**config_standard)
if isinstance(t2, Add):
parts = list(t2.as_ordered_terms())
s2 = " - " + " - ".join(latex(p,**config_standard) for p in parts)
else:
s2 = " - " + latex(t2,**config_standard)
return s1 + s2
# --- Narration utilitaires (même rendu final) ---
[docs]
def narr_conclude(name, variable, expr):
config_standard = pxs_config()
derivative = diff(expr, variable)
return myst(r"""
Donc
\begin{equation*}
\py{latex(Symbol(name),**config_standard)}'(\py{latex(variable,**config_standard)}) = \py{latex(derivative,**config_standard)}
\end{equation*}
""", globals(), locals())
[docs]
def should_remove_times(expr):
"""
Détermine si on doit retirer le signe × dans l'expression finale.
On retire × pour les cas finaux comme exp, sin, cos avec coefficients.
"""
# Si c'est une multiplication
if isinstance(expr, Mul):
args = list(expr.args)
# Chercher si on a un coefficient numérique avec une fonction transcendante
has_number = any(arg.is_number for arg in args)
has_exp = any(isinstance(arg, exp) for arg in args)
has_trig = any(isinstance(arg, (sin, cos)) for arg in args)
# Pour exp, sin, cos on retire toujours le × avec un coefficient
if has_number and (has_exp or has_trig):
return True
# Aussi pour les produits contenant ces fonctions
for arg in args:
if isinstance(arg, Mul) and any(isinstance(subarg, (exp, sin, cos)) for subarg in arg.args):
return True
return False
[docs]
def narr_conclude_with_details(name, variable, formula_latex, simplified=None, detail = None):
"""
Affiche la conclusion avec la formule détaillée puis la simplification.
formula_latex : la formule avec toutes les substitutions
simplified : la forme simplifiée (optionnel) - peut être une chaîne LaTeX ou une expression SymPy
"""
config_standard = pxs_config()
# Fonction pour nettoyer le × dans les cas appropriés
def clean_times_if_needed(latex_str, expr=None):
if expr and should_remove_times(expr):
return latex_str.replace(" \\times ", " ").replace(" \\cdot ", " ")
return latex_str
if detail is None:
if simplified is None:
return myst(r"""
Donc :
\begin{equation*}
\py{latex(Symbol(name),**config_standard)}'(\py{latex(variable,**config_standard)}) = \py{formula_latex.replace(" \\times ", " ").replace(" \\cdot ", " ")}
\end{equation*}
""", globals(), locals())
else:
return myst(r"""
Donc :
\begin{equation*}
\py{latex(Symbol(name),**config_standard)}'(\py{latex(variable,**config_standard)}) = \py{latex(simplified,**config_standard)}
\end{equation*}
""", globals(), locals())
else:
# Si simplified est une chaîne (notre formule LaTeX construite manuellement)
if simplified is not None and isinstance(simplified, str):
# Comparer les chaînes après nettoyage
formula_clean = formula_latex.replace(" ", "").replace("\\times", "").replace("\\cdot", "")
simplified_clean = simplified.replace(" ", "").replace("\\times", "").replace("\\cdot", "")
if formula_clean != simplified_clean:
return myst(r"""
Donc :
\begin{equation*}
\py{latex(Symbol(name),**config_standard)}'(\py{latex(variable,**config_standard)}) = \py{formula_latex} = \py{simplified}
\end{equation*}
""", globals(), locals())
else:
return myst(r"""
Donc :
\begin{equation*}
\py{latex(Symbol(name),**config_standard)}'(\py{latex(variable,**config_standard)}) = \py{formula_latex}
\end{equation*}
""", globals(), locals())
# Si simplified est une expression SymPy
elif simplified is not None:
# Obtenir la forme simplifiée finale
final_simplified = simplified.simplify()
final_latex = latex(final_simplified,**config_standard)
# CORRECTION: Si on peut retirer le × directement, on le fait sur formula_latex
if should_remove_times(final_simplified):
# Retirer le × directement dans la formule détaillée
formula_cleaned = formula_latex.replace(" \\times ", " ")
return myst(r"""
Donc :
\begin{equation*}
\py{latex(Symbol(name),**config_standard)}'(\py{latex(variable,**config_standard)}) = \py{formula_cleaned}
\end{equation*}
""", globals(), locals())
# Sinon, on continue avec la logique normale
# Comparer les représentations LaTeX
# Nettoyer les espaces pour une comparaison plus robuste
formula_clean = formula_latex.replace(" ", "")
final_clean = final_latex.replace(" ", "")
if formula_clean != final_clean:
# Les expressions sont différentes, on affiche les deux
return myst(r"""
Donc :
\begin{equation*}
\py{latex(Symbol(name),**config_standard)}'(\py{latex(variable,**config_standard)}) = \py{formula_latex} = \py{final_latex}
\end{equation*}
""", globals(), locals())
# Les expressions sont identiques ou simplified est None, on affiche seulement la formule
return myst(r"""
Donc :
\begin{equation*}
\py{latex(Symbol(name),**config_standard)}'(\py{latex(variable,**config_standard)}) = \py{formula_latex}
\end{equation*}
""", globals(), locals())
[docs]
def narr_deriv_or_recurse(expr, nm, variable, niveau, lhs=None, func_name=None, detail = None):
"""
Si expr est atomique ou linéaire simple: écrit une équation avec le lhs fourni,
sinon descend récursivement via _dispatch_diff.
func_name : nom de la fonction à utiliser (si None, on utilise nm.current())
"""
config_standard = pxs_config()
if expr.is_Atom or is_simple_linear(expr, variable):
if lhs is None:
lhs = "u'(x)"
return myst(r"""
\begin{equation*}
\py{lhs} = \py{latex(diff(expr, variable),**config_standard)}
\end{equation*}
""", globals(), locals())
else:
return _dispatch_diff(expr, nm, variable, niveau, override_name=func_name, detail= detail)
# --- Dispatcher ---
[docs]
def expliquer_derivation(expr, variable=Symbol('x'), start_name='f', niveau='terminale', detail = None):
"""
niveau: 'premiere' | 'terminale'
- 'premiere' : on détaille la somme terme à terme, et on explicite k*u
- 'terminale' : polynômes en bloc, pas de détail pour k*u
"""
nm = NameManager(start=start_name)
return _dispatch_diff(expr, nm, variable, niveau, detail = detail)
def _dispatch_diff(expr, nm, variable, niveau, override_name=None, detail = None):
"""
override_name : si fourni, utilise ce nom au lieu de nm.current()
"""
config_standard = pxs_config()
if isinstance(expr, Add):
return pxs_Add(*expr.args).pxsl_diff(nm, variable, niveau, override_name, detail)
elif isinstance(expr, Mul):
return pxs_Mul(*expr.args).pxsl_diff(nm, variable, niveau, override_name, detail)
elif isinstance(expr, Pow):
return pxs_Pow(expr.base, expr.exp).pxsl_diff(nm, variable, niveau, override_name, detail)
elif isinstance(expr, exp):
return pxs_exp(expr.args[0]).pxsl_diff(nm, variable, niveau, override_name, detail)
elif isinstance(expr, log):
return pxs_log(expr.args[0]).pxsl_diff(nm, variable, niveau, override_name, detail)
elif isinstance(expr, sin):
return pxs_sin(expr.args[0]).pxsl_diff(nm, variable, niveau, override_name, detail)
elif isinstance(expr, cos):
return pxs_cos(expr.args[0]).pxsl_diff(nm, variable, niveau, override_name, detail)
else:
name = override_name if override_name else nm.current()
return myst(r"""
\py{latex(Symbol(name),**config_standard)}'(\py{latex(variable,**config_standard)}) = \py{latex(diff(expr, variable),**config_standard)}
""", globals(), locals())
[docs]
def dispatch(expr, variable = None, steps=[]):
config_standard = pxs_config()
if isinstance(expr, Mul):
steps = pxs_Mul(*expr.args).pxsl_print(variable, steps)
elif isinstance(expr, Add):
steps = pxs_Add(*expr.args).pxsl_print(variable, steps)
else:
steps.append(expr)
return steps
[docs]
class pxs_Add(Add):
[docs]
def pxsl_print(self, variable=None, steps=[]):
config_standard = pxs_config()
simple = []
complex_terms = []
# Séparation des termes
for term in self.args:
if pxs_is_reference_function(term, variable):
simple.append(term)
else:
complex_terms.append(term)
# Étape 1 : Regroupement initial
if simple:
simple_sum = Add(*simple) if len(simple) > 1 else simple[0]
steps.append(Add(simple_sum, *complex_terms))
# Étape 2 : Développement des termes complexes
current_simple = Add(*simple) if simple else 0
for term in complex_terms:
developed_steps = dispatch(term, variable, steps=[])
# Ajouter chaque étape de développement avec les termes simples
for step in developed_steps[1:]: # Ignorer le terme original
if current_simple != 0:
steps.append(Add(current_simple, step))
else:
steps.append(step)
return steps
[docs]
def pxsl_diff(self, nm, variable, niveau, override_name=None, detail = None):
config_standard = pxs_config()
name = override_name if override_name else nm.current()
args = list(self.args)
# --- traiter bloc polynômial même s'il est partiel ---
# Cas 100% polynôme
if self.as_poly(variable) is not None:
phrase = narr_header(name, variable)
phrase += myst(r"""
On reconnaît un polynôme en $\py{latex(variable,**config_standard)}$.
On applique la règle de dérivation pour les polynômes :
\begin{equation*}
(a_n x^n + \dots + a_1 x + a_0)' = n a_n x^{n-1} + \dots + a_1
\end{equation*}
Ainsi :
\begin{equation*}
\py{latex(Symbol(name),**config_standard)}'(\py{latex(variable,**config_standard)}) =
\py{latex(diff(self, variable))}
\end{equation*}
""", globals(), locals())
return phrase
# Cas mixte : partition en trois catégories
poly_terms, ref_terms, other_terms = [], [], []
for t in args:
if is_polynomial_in(t, variable):
poly_terms.append(t)
elif pxs_is_reference_function_diff(t, variable):
ref_terms.append(t)
else:
other_terms.append(t)
# Si on a des termes polynomiaux et/ou de référence, on les groupe
if poly_terms or ref_terms:
# Combiner les termes "simples" (polynômes + fonctions de référence)
simple_terms = poly_terms + ref_terms
P = Add(*simple_terms)
dP = diff(P, variable)
name_P = nm.next()
if other_terms:
# Il y a aussi des termes complexes
name_h = nm.next()
H = Add(*other_terms)
dH = diff(H, variable)
phrase = narr_header(name, variable)
# Description adaptée selon ce qu'on a trouvé
if poly_terms and ref_terms:
desc = "un polynôme et des fonctions usuelles"
elif poly_terms:
desc = "un polynôme"
else:
desc = "des fonctions usuelles"
phrase += myst(r"""
On décompose la somme et on repère """ + desc + r""" :
$\py{latex(Symbol(name),**config_standard)}(\py{latex(variable,**config_standard)}) = \py{latex(Symbol(name_P),**config_standard)}(\py{latex(variable,**config_standard)}) + \py{latex(Symbol(name_h),**config_standard)}(\py{latex(variable,**config_standard)})$ avec
\begin{equation*}
\py{latex(Symbol(name_P),**config_standard)}(\py{latex(variable,**config_standard)}) = \py{latex(P,**config_standard)}
\end{equation*}
et
\begin{equation*}
\py{latex(Symbol(name_h),**config_standard)}(\py{latex(variable,**config_standard)}) = \py{latex(H,**config_standard)}
\end{equation*}
Par les règles de dérivation usuelles, on obtient directement :
\begin{equation*}
\py{latex(Symbol(name_P),**config_standard)}'(\py{latex(variable,**config_standard)}) = \py{latex(dP,**config_standard)}
\end{equation*}
""", globals(), locals())
phrase += _dispatch_diff(H, nm, variable, niveau, override_name=name_h, detail = detail)
# Correction pour le problème du +-ln(x)
# On vérifie si dH est négatif pour l'afficher correctement
if detail is None:
phrase += myst(r"""
Donc :
\begin{equation*}
\py{latex(Symbol(name),**config_standard)}'(\py{latex(variable,**config_standard)}) = \py{latex(dP + dH,**config_standard)}
\end{equation*}
""", globals(), locals())
elif dH.could_extract_minus_sign():
phrase += myst(r"""
Donc :
\begin{equation*}
\py{latex(Symbol(name),**config_standard)}'(\py{latex(variable,**config_standard)}) = \py{latex(dP,**config_standard)} - \py{latex(-dH,**config_standard)} = \py{latex(dP + dH,**config_standard)}
\end{equation*}
""", globals(), locals())
else:
phrase += myst(r"""
Donc :
\begin{equation*}
\py{latex(Symbol(name),**config_standard)}'(\py{latex(variable,**config_standard)}) = \py{latex(dP,**config_standard)} + \py{latex(dH,**config_standard)} = \py{latex(dP + dH,**config_standard)}
\end{equation*}
""", globals(), locals())
return phrase
else:
# Seulement des termes simples, pas besoin de décomposer
phrase = narr_header(name, variable)
if poly_terms and ref_terms:
desc = "une somme de fonctions usuelles"
elif poly_terms:
desc = "un polynôme"
else:
desc = "une somme de fonctions usuelles"
phrase += myst(r"""
On reconnaît """ + desc + r""" en $\py{latex(variable,**config_standard)}$.
En appliquant les règles de dérivation usuelles, on obtient directement :
\begin{equation*}
\py{latex(Symbol(name),**config_standard)}'(\py{latex(variable,**config_standard)}) = \py{latex(diff(self, variable),**config_standard)}
\end{equation*}
""", globals(), locals())
return phrase
# --- pas de bloc poly détectable : somme g+h comme avant ---
g, h = split_add_terms(self)
name_g = nm.next()
name_h = nm.next()
phrase = narr_header(name, variable)
phrase += myst(r"""
La fonction $\py{latex(Symbol(name),**config_standard)}$ est de la forme
$\py{latex(Symbol(name_g),**config_standard)} + \py{latex(Symbol(name_h),**config_standard)}$ avec
$\py{latex(Symbol(name_g),**config_standard)}(\py{latex(variable,**config_standard)}) = \py{latex(g,**config_standard)}$
et
$\py{latex(Symbol(name_h),**config_standard)}(\py{latex(variable,**config_standard)}) = \py{latex(h,**config_standard)}$.
On sait que
$\big(\py{latex(Symbol(name_g),**config_standard)} + \py{latex(Symbol(name_h),**config_standard)}\big)'(\py{latex(variable,**config_standard)}) =
\py{latex(Symbol(name_g),**config_standard)}'(\py{latex(variable,**config_standard)}) +
\py{latex(Symbol(name_h),**config_standard)}'(\py{latex(variable,**config_standard)})$.
""", globals(), locals())
phrase += narr_deriv_or_recurse(
g, nm, variable, niveau,
lhs=f"{latex(Symbol(name_g),**config_standard)}'({latex(variable,**config_standard)})",
func_name=name_g, detail = detail
)
phrase += narr_deriv_or_recurse(
h, nm, variable, niveau,
lhs=f"{latex(Symbol(name_h),**config_standard)}'({latex(variable,**config_standard)})",
func_name=name_h, detail = detail
)
dg, dh = diff(g, variable), diff(h, variable)
# Construire la formule détaillée
detailed_formula = f"{latex(dg,**config_standard)} + {latex(dh,**config_standard)}"
# Construire la forme avec l'ordre préservé
if dh.could_extract_minus_sign():
preserved_formula = f"{latex(dg,**config_standard)} - {latex(-dh,**config_standard)}"
else:
preserved_formula = f"{latex(dg,**config_standard)} + {latex(dh,**config_standard)}"
# Calculer la vraie simplification
final_simplified = dg + dh
final_simplified_str = latex(final_simplified,**config_standard)
# Vérifier si la simplification finale est différente
if preserved_formula.replace(" ", "") != final_simplified_str.replace(" ", ""):
# Il y a une simplification supplémentaire
if detail is None:
phrase += narr_conclude_with_details(name, variable, latex(diff(self,variable),**config_standard), simplified=None, detail = None)
else:
phrase += myst(r"""
Donc :
\begin{equation*}
\py{latex(Symbol(name),**config_standard)}'(\py{latex(variable,**config_standard)}) = \py{detailed_formula} = \py{final_simplified_str}
\end{equation*}
""", globals(), locals())
else:
phrase += narr_conclude_with_details(name, variable, detailed_formula, preserved_formula, detail)
return phrase
[docs]
class pxs_Mul(Mul):
[docs]
def pxsl_print(self, variable = None, steps=[]):
if all(isinstance(a, Add) for a in self.args):
A_terms = Add.make_args(self.args[0])
B_terms = Add.make_args(self.args[1])
rest = self.args[2:]
products = [Mul(a, b, evaluate=True) for a in A_terms for b in B_terms]
steps.append(Mul(Add(*products, evaluate=False),*rest))
if set(steps[-1].args) != set(Mul(Add(*products),*rest).args):
steps.append(Mul(Add(*products),*rest))
if rest != ():
steps = pxs_Mul(Add(*products),*rest).pxsl_print(variable, steps)
return steps
def _detect_quotient(self):
"""
Détecte des facteurs à puissance négative (numérique).
u = produit des autres facteurs
v = produit des base**(-exp) pour chaque exp < 0 (exposants rendus positifs).
"""
config_standard = pxs_config()
args = list(self.args)
neg_pow_factors, others = [], []
for f in args:
if isinstance(f, Pow) and getattr(f.exp, "is_number", False) and f.exp.is_negative:
neg_pow_factors.append(f)
else:
others.append(f)
if neg_pow_factors:
denom_parts = [p.base**(-p.exp) for p in neg_pow_factors]
v = to_mul_rest(denom_parts)
u = to_mul_rest(others) if others else S.One
return True, u, v
return False, None, None
[docs]
def pxsl_diff(self, nm, variable, niveau, override_name=None, detail = None):
config_standard = pxs_config()
name = override_name if override_name else nm.current()
# 1) Quotient direct si puissances négatives détectées
is_q, u_q, v_q = self._detect_quotient()
if is_q:
u, v = u_q, v_q
name_u = nm.next()
name_v = nm.next()
phrase = narr_header(name, variable)
phrase += myst(r"""
On reconnaît un quotient :
$\py{latex(Symbol(name),**config_standard)} = \dfrac{\py{latex(Symbol(name_u),**config_standard)}}{\py{latex(Symbol(name_v),**config_standard)}}$ avec
$\py{latex(Symbol(name_u),**config_standard)}(\py{latex(variable,**config_standard)}) = \py{latex(u,**config_standard)}$ et
$\py{latex(Symbol(name_v),**config_standard)}(\py{latex(variable,**config_standard)}) = \py{latex(v,**config_standard)}$.
On sait que
$\displaystyle{
\left(\frac{\py{latex(Symbol(name_u),**config_standard)}}{\py{latex(Symbol(name_v),**config_standard)}}\right)'(\py{latex(variable,**config_standard)}) = \frac{\py{latex(Symbol(name_u),**config_standard)}'(\py{latex(variable,**config_standard)}) \py{latex(Symbol(name_v),**config_standard)}(\py{latex(variable,**config_standard)}) - \py{latex(Symbol(name_u),**config_standard)}(\py{latex(variable,**config_standard)}) \py{latex(Symbol(name_v),**config_standard)}'(\py{latex(variable,**config_standard)})}{\py{latex(Symbol(name_v),**config_standard)}^2(\py{latex(variable,**config_standard)})}}
$""", globals(), locals())
# Calcul des dérivées
du = diff(u, variable)
dv = diff(v, variable)
# Étape 1 : formule brute u'v - uv' / v²
du_str = _fmt_paren(du)
dv_str = _fmt_paren(dv)
u_str = _fmt_paren(u)
v_str = _fmt_paren(v)
v2_str = _fmt_paren(v**2)
step1_formula = (
rf"\frac{{{du_str} \times {v_str} - {u_str} \times {dv_str}}}{{{v2_str}}}"
)
steps = [step1_formula]
# Étape 2-3 : factoriser par un facteur commun g de v et v'
g = _squarefree_common_factor(v)
if g != S.One:
v_over_g = simplify(v / g)
dv_over_g = simplify(dv / g)
g_str = _fmt_paren(g)
step2_num = (
rf"{g_str} \times \left({du_str} \times {_fmt_paren(v_over_g)}"
rf" - {u_str} \times {_fmt_paren(dv_over_g)}\right)"
)
step2_formula = rf"\frac{{{step2_num}}}{{{v2_str}}}"
steps.append(step2_formula)
# Simplification par g
den_after = simplify(v**2 / g)
den_after_str = _fmt_paren(den_after)
step3_num = (
rf"{du_str} \times {_fmt_paren(v_over_g)} - "
rf"{u_str} \times {_fmt_paren(dv_over_g)}"
)
step3_formula = rf"\frac{{{step3_num}}}{{{den_after_str}}}"
steps.append(step3_formula)
# Étape 4 : développement du numérateur uniquement
step4_num = _expanded_minus_str(du * v_over_g, u * dv_over_g)
step4_formula = rf"\frac{{{step4_num}}}{{{den_after_str}}}"
steps.append(step4_formula)
# Étape 5 : écriture finale simplifiée
final_expr = simplify((du * v_over_g - u * dv_over_g) / den_after)
step5_formula = latex(final_expr,**config_standard)
steps.append(step5_formula)
else:
# Pas de facteur commun : on développe puis on simplifie
den_after = v**2
den_after_str = _fmt_paren(den_after)
raw_num = du * v - u * dv
dev_num = expand_mul(raw_num)
step2_formula = rf"\frac{{{latex(dev_num,**config_standard)}}}{{{den_after_str}}}"
steps.append(step2_formula)
final_expr = simplify(raw_num / den_after)
step3_formula = latex(final_expr,**config_standard)
steps.append(step3_formula)
# Affichage final (on concatène toutes les étapes)
chain = " = ".join(steps)
if detail is None:
phrase += narr_conclude_with_details(name, variable, latex(diff(self,variable),**config_standard), simplified=None, detail = None)
else:
phrase += myst(r"""
Donc :
\begin{equation*}
\py{chain}
\end{equation*}
""", globals(), locals())
return phrase
# 2) Cas particulier k*u (k numérique, u fonction de x)
# On vérifie que c'est vraiment un coefficient numérique en facteur
args = list(self.args)
# Pour être un cas k*u, il faut :
# - Exactement 2 arguments
# - Le premier est un nombre
# - Le second n'est pas un produit (sinon on a k*u*v qu'on veut traiter comme produit classique)
if len(args) == 2 and args[0].is_number:
k = args[0]
core = args[1]
if niveau == 'premiere':
name_u = nm.next()
phrase = narr_header(name, variable)
# Vérifier si core est une fonction de référence
if pxs_is_reference_function_diff(core, variable):
# Fonction de référence, on fait court
dcore = diff(core, variable)
phrase += myst(r"""
On reconnaît un produit $k \times u$ avec
$k=\py{latex(k,**config_standard)}$ et $u(\py{latex(variable,**config_standard)})=\py{latex(core,**config_standard)}$.
On sait que $(k \times u)' = k \times u'$ avec $u'(\py{latex(variable,**config_standard)}) = \py{latex(dcore,**config_standard)}$.
""", globals(), locals())
else:
# Pas une fonction de référence, on détaille
phrase += myst(r"""
On reconnaît un produit $k \times u$ avec
$k=\py{latex(k,**config_standard)}$ et $u(\py{latex(variable,**config_standard)})=\py{latex(core,**config_standard)}$.
On sait que :
\begin{equation*}
(k \times u)' = k \times u'
\end{equation*}
""", globals(), locals())
phrase += narr_deriv_or_recurse(
core, nm, variable, niveau, lhs="u'(x)", func_name=name_u, detail = detail
)
dcore = diff(core, variable)
# Gérer les parenthèses pour dcore si c'est une somme
dcore_str = f"\\left({latex(dcore,**config_standard)}\\right)" if isinstance(dcore, Add) else latex(dcore,**config_standard)
# Formule détaillée : k × u'
detailed_formula = f"{latex(k,**config_standard)} \\times {dcore_str}"
simplified = k * dcore
phrase += narr_conclude_with_details(name, variable, detailed_formula, simplified, detail)
return phrase
else:
# En terminale, on donne quand même une explication minimale
phrase = narr_header(name, variable)
phrase += myst(r"""
La fonction est de la forme $k \times u$ avec $k = \py{latex(k,**config_standard)}$ et $u(\py{latex(variable,**config_standard)}) = \py{latex(core,**config_standard)}$.
Par la règle $(k \times u)' = k \times u'$, on obtient directement :
""", globals(), locals())
phrase += narr_conclude(name, variable, self)
return phrase
# 3) Produit « classique » (pas de quotient, pas de k*u avec k numérique en premier)
u, v = split_mul_terms(self)
name_u = nm.next()
name_v = nm.next()
phrase = narr_header(name, variable)
# Vérifier si u et v sont des fonctions simples
u_is_simple = pxs_is_simple_function_diff(u, variable)
v_is_simple = pxs_is_simple_function_diff(v, variable)
phrase += myst(r"""
La fonction $\py{latex(Symbol(name),**config_standard)}$ est de la forme
$\py{latex(Symbol(name_u),**config_standard)} \times \py{latex(Symbol(name_v),**config_standard)}$ avec
$\py{latex(Symbol(name_u),**config_standard)}(\py{latex(variable,**config_standard)}) = \py{latex(u,**config_standard)}$
et
$\py{latex(Symbol(name_v),**config_standard)}(\py{latex(variable,**config_standard)}) = \py{latex(v,**config_standard)}$.
On sait que
$\big(\py{latex(Symbol(name_u),**config_standard)} \times \py{latex(Symbol(name_v),**config_standard)}\big)'(\py{latex(variable,**config_standard)}) =
\py{latex(Symbol(name_u),**config_standard)}'(\py{latex(variable,**config_standard)}) \times \py{latex(Symbol(name_v),**config_standard)}(\py{latex(variable,**config_standard)}) +
\py{latex(Symbol(name_u),**config_standard)}(\py{latex(variable,**config_standard)}) \times \py{latex(Symbol(name_v),**config_standard)}'(\py{latex(variable,**config_standard)})$""", globals(), locals())
if u_is_simple and v_is_simple:
# Les deux sont simples, on donne directement les dérivées
du = diff(u, variable)
dv = diff(v, variable)
phrase += myst(r"""
avec $\py{latex(Symbol(name_u),**config_standard)}'(\py{latex(variable,**config_standard)}) = \py{latex(du,**config_standard)}$ et $\py{latex(Symbol(name_v),**config_standard)}'(\py{latex(variable,**config_standard)}) = \py{latex(dv,**config_standard)}$.
""", globals(), locals())
elif u_is_simple and not v_is_simple:
# u est simple, v ne l'est pas
du = diff(u, variable)
phrase += myst(r"""
avec $\py{latex(Symbol(name_u),**config_standard)}'(\py{latex(variable,**config_standard)}) = \py{latex(du,**config_standard)}$.
""", globals(), locals())
phrase += _dispatch_diff(v, nm, variable, niveau, override_name=name_v, detail = detail)
elif not u_is_simple and v_is_simple:
# v est simple, u ne l'est pas
dv = diff(v, variable)
phrase += myst(r"""
avec $\py{latex(Symbol(name_v),**config_standard)}'(\py{latex(variable,**config_standard)}) = \py{latex(dv,**config_standard)}$.
On calcule $\py{latex(Symbol(name_u),**config_standard)}'(\py{latex(variable,**config_standard)})$ :
""", globals(), locals())
phrase += _dispatch_diff(u, nm, variable, niveau, override_name=name_u, detail = detail)
else:
# Aucune n'est simple, on calcule les deux
phrase += myst(r""".
""", globals(), locals())
phrase += narr_deriv_or_recurse(
u, nm, variable, niveau,
lhs=f"{latex(Symbol(name_u),**config_standard)}'({latex(variable,**config_standard)})",
func_name=name_u, detail = detail
)
phrase += narr_deriv_or_recurse(
v, nm, variable, niveau,
lhs=f"{latex(Symbol(name_v),**config_standard)}'({latex(variable,**config_standard)})",
func_name=name_v, detail = detail
)
# Ajouter la conclusion détaillée
du = diff(u, variable)
dv = diff(v, variable)
# Gérer les parenthèses pour toutes les expressions si nécessaire
du_str = f"\\left({latex(du,**config_standard)}\\right)" if isinstance(du, Add) else latex(du,**config_standard)
dv_str = f"\\left({latex(dv,**config_standard)}\\right)" if isinstance(dv, Add) else latex(dv,**config_standard)
u_str = f"\\left({latex(u,**config_standard)}\\right)" if isinstance(u, Add) else latex(u,**config_standard)
v_str = f"\\left({latex(v,**config_standard)}\\right)" if isinstance(v, Add) else latex(v,**config_standard)
# Construire la formule détaillée : u'×v + u×v'
detailed_formula = f"{du_str} \\times {v_str} + {u_str} \\times {dv_str}"
# Calculer séparément chaque terme pour préserver l'ordre
term1 = du * v
term2 = u * dv
# Développer les termes pour la forme intermédiaire
term1_expanded = term1.expand()
term2_expanded = term2.expand()
# Construire la forme intermédiaire développée
term1_str = latex(term1_expanded,**config_standard)
term2_str = latex(term2_expanded,**config_standard)
# CORRECTION pour expr15: Préserver l'ordre sin(x) + x*cos(x)
# Si on a 1*sin(x), on veut juste sin(x)
if term1_expanded == sin(variable) and u == S.One:
term1_str = latex(sin(variable),**config_standard)
# Si term2 est négatif, on l'affiche correctement
if term2_expanded.could_extract_minus_sign():
simplified_formula = f"{term1_str} - {latex(-term2_expanded,**config_standard)}"
else:
simplified_formula = f"{term1_str} + {term2_str}"
# Calculer la vraie simplification finale
final_simplified = (term1_expanded + term2_expanded).simplify()
# Vérifier si c'est une vraie simplification
# On compare les chaînes LaTeX pour voir si elles sont différentes
final_simplified_str = latex(final_simplified,**config_standard)
# Nettoyer pour la comparaison
simplified_clean = simplified_formula.replace(" ", "").replace("\\left(", "").replace("\\right)", "")
final_clean = final_simplified_str.replace(" ", "").replace("\\left(", "").replace("\\right)", "")
# Si on a "1 \times sin(x) + x \times cos(x)" et que la simplification donne "sin(x) + x cos(x)"
# C'est une vraie simplification à montrer
if "1 \\times" in detailed_formula and "1 \\times" not in simplified_formula:
# On montre la simplification
if detail is None:
phrase += narr_conclude_with_details(name, variable, latex(diff(self,variable),**config_standard), simplified=None, detail = None)
else:
phrase += myst(r"""
Donc :
\begin{equation*}
\py{latex(Symbol(name),**config_standard)}'(\py{latex(variable,**config_standard)}) = \py{detailed_formula} = \py{simplified_formula}
\end{equation*}
""", globals(), locals())
elif simplified_clean != final_clean:
# Il y a une autre vraie simplification
if detail is None:
phrase += narr_conclude_with_details(name, variable, latex(diff(self,variable),**config_standard), simplified=None, detail = None)
else:
phrase += myst(r"""
Donc :
\begin{equation*}
\py{latex(Symbol(name),**config_standard)}'(\py{latex(variable,**config_standard)}) = \py{detailed_formula} = \py{simplified_formula} = \py{final_simplified_str}
\end{equation*}
""", globals(), locals())
else:
# Pas de simplification supplémentaire
phrase += narr_conclude_with_details(name, variable, detailed_formula, simplified_formula, detail)
return phrase
# --- pxs_Pow ---
[docs]
class pxs_Pow(Pow):
[docs]
def pxsl_diff(self, nm, variable, niveau, override_name=None, detail = None):
config_standard = pxs_config()
name = override_name if override_name else nm.current()
u = self.base
n = self.exp
name_u = nm.next()
# Préfixe
phrase = narr_header(name, variable)
# Vérifier si u est une fonction de référence
u_is_ref = pxs_is_reference_function_diff(u, variable)
if n.is_integer and n > 0:
# Calculer la chaîne pour u^(n-1)
if n-1 == 1:
# Si n-1 = 1, on n'affiche pas l'exposant
power_formula = r"\py{latex(Symbol(name_u),**config_standard)}(\py{latex(variable,**config_standard)})"
else:
power_formula = r"\py{latex(Symbol(name_u),**config_standard)}(\py{latex(variable,**config_standard)})^{\py{latex(n-1,**config_standard)}}"
if u_is_ref:
du = diff(u, variable)
phrase += myst(r"""
La fonction $\py{latex(Symbol(name),**config_standard)}$ est de la forme
$\py{latex(Symbol(name_u),**config_standard)}^{\py{latex(n,**config_standard)}}$ avec
$\py{latex(Symbol(name_u),**config_standard)}(\py{latex(variable,**config_standard)}) = \py{latex(u,**config_standard)}$.
On sait que
$\big(\py{latex(Symbol(name_u),**config_standard)}^{\py{latex(n,**config_standard)}}\big)'(\py{latex(variable,**config_standard)}) =
\py{latex(n,**config_standard)} \times \py{latex(Symbol(name_u),**config_standard)}'(\py{latex(variable,**config_standard)}) \times """ + power_formula + r"""$
avec $\py{latex(Symbol(name_u),**config_standard)}'(\py{latex(variable,**config_standard)}) = \py{latex(du,**config_standard)}$.
""", globals(), locals())
else:
phrase += myst(r"""
La fonction $\py{latex(Symbol(name),**config_standard)}$ est de la forme
$\py{latex(Symbol(name_u),**config_standard)}^{\py{latex(n,**config_standard)}}$ avec
$\py{latex(Symbol(name_u),**config_standard)}(\py{latex(variable,**config_standard)}) = \py{latex(u,**config_standard)}$.
On sait que
$\big(\py{latex(Symbol(name_u),**config_standard)}^{\py{latex(n,**config_standard)}}\big)'(\py{latex(variable,**config_standard)}) =
\py{latex(n,**config_standard)} \times \py{latex(Symbol(name_u),**config_standard)}'(\py{latex(variable,**config_standard)}) \times """ + power_formula + r"""$.
""", globals(), locals())
elif n.is_Rational and n.q == 2:
if u_is_ref:
du = diff(u, variable)
phrase += myst(r"""
La fonction $\py{latex(Symbol(name),**config_standard)}$ est de la forme
$\sqrt{\py{latex(Symbol(name_u),**config_standard)}}$ avec
$\py{latex(Symbol(name_u),**config_standard)}(\py{latex(variable,**config_standard)}) = \py{latex(u,**config_standard)}$.
On sait que
$\big(\sqrt{\py{latex(Symbol(name_u),**config_standard)}}\big)' = \dfrac{\py{latex(Symbol(name_u),**config_standard)}'(\py{latex(variable,**config_standard)})}{2\sqrt{\py{latex(Symbol(name_u),**config_standard)}(\py{latex(variable,**config_standard)})}}$
avec $\py{latex(Symbol(name_u),**config_standard)}'(\py{latex(variable,**config_standard)}) = \py{latex(du,**config_standard)}$.
""", globals(), locals())
else:
phrase += myst(r"""
La fonction $\py{latex(Symbol(name),**config_standard)}$ est de la forme
$\sqrt{\py{latex(Symbol(name_u),**config_standard)}}$ avec
$\py{latex(Symbol(name_u),**config_standard)}(\py{latex(variable,**config_standard)}) = \py{latex(u)}$.
On sait que
\begin{equation*}
\big(\sqrt{\py{latex(Symbol(name_u),**config_standard)}}\big)' = \frac{\py{latex(Symbol(name_u),**config_standard)}'(\py{latex(variable,**config_standard)})}{2\sqrt{\py{latex(Symbol(name_u),**config_standard)}(\py{latex(variable,**config_standard)})}}
\end{equation*}
""", globals(), locals())
elif n.is_number and n < 0:
# Gérer spécialement le cas n = -1
if n == -1:
phrase += myst(r"""
La fonction $\py{latex(Symbol(name),**config_standard)}$ est de la forme
$\dfrac{1}{\py{latex(Symbol(name_u),**config_standard)}}$ avec
$\py{latex(Symbol(name_u),**config_standard)}(\py{latex(variable,**config_standard)}) = \py{latex(u,**config_standard)}$.
On sait que
\begin{equation*}
\left(\frac{1}{\py{latex(Symbol(name_u),**config_standard)}}\right)' = -\frac{\py{latex(Symbol(name_u),**config_standard)}'(\py{latex(variable,**config_standard)})}{\py{latex(Symbol(name_u),**config_standard)}(\py{latex(variable,**config_standard)})^{2}}
\end{equation*}""", globals(), locals())
else:
phrase += myst(r"""
La fonction $\py{latex(Symbol(name),**config_standard)}$ est de la forme
$\dfrac{1}{\py{latex(Symbol(name_u),**config_standard)}^{\py{latex(-n,**config_standard)}}}$ avec
$\py{latex(Symbol(name_u),**config_standard)}(\py{latex(variable,**config_standard)}) = \py{latex(u,**config_standard)}$.
On sait que
\begin{equation*}
\left(\frac{1}{\py{latex(Symbol(name_u),**config_standard)}^{\py{latex(-n,**config_standard)}}}\right)' = \py{latex(n,**config_standard)} \times \frac{\py{latex(Symbol(name_u),**config_standard)}'(\py{latex(variable,**config_standard)})}{\py{latex(Symbol(name_u),**config_standard)}(\py{latex(variable,**config_standard)})^{\py{latex(-n+1,**config_standard)}}}
\end{equation*}""", globals(), locals())
if u_is_ref:
du = diff(u, variable)
phrase += myst(r"""avec $\py{latex(Symbol(name_u),**config_standard)}'(\py{latex(variable,**config_standard)}) = \py{latex(du,**config_standard)}$.
""", globals(), locals())
else:
phrase += myst(r"""
""", globals(), locals())
else:
phrase += myst(r"""
La fonction $\py{latex(Symbol(name),**config_standard)}$ est de la forme
$\py{latex(Symbol(name_u),**config_standard)}^{\py{latex(n,**config_standard)}}$ avec
$\py{latex(Symbol(name_u),**config_standard)}(\py{latex(variable,**config_standard)}) = \py{latex(u,**config_standard)}$.
On sait que
\begin{equation*}
\big(\py{latex(Symbol(name_u),**config_standard)}^{\alpha}\big)' = \alpha \times \py{latex(Symbol(name_u),**config_standard)}'(\py{latex(variable,**config_standard)}) \times \py{latex(Symbol(name_u),**config_standard)}(\py{latex(variable,**config_standard)})^{\alpha-1}
\end{equation*}""", globals(), locals())
if u_is_ref:
du = diff(u, variable)
phrase += myst(r"""avec $\py{latex(Symbol(name_u),**config_standard)}'(\py{latex(variable,**config_standard)}) = \py{latex(du,**config_standard)}$.
""", globals(), locals())
else:
phrase += myst(r"""
""", globals(), locals())
# Si u n'est pas une fonction de référence, on détaille son calcul
if not u_is_ref:
phrase += narr_deriv_or_recurse(
u, nm, variable, niveau,
lhs=f"{latex(Symbol(name_u),**config_standard)}'({latex(variable,**config_standard)})",
func_name=name_u, detail = detail
)
# Conclusion détaillée selon le type de puissance
du = diff(u, variable)
# Gérer les parenthèses pour du si c'est une somme
du_str = f"\\left({latex(du,**config_standard)}\\right)" if isinstance(du, Add) else latex(du,**config_standard)
# Pour u^n, on met des parenthèses si u est une Add ET qu'on écrit u^n explicitement
def format_power(base, exp):
if exp == 1:
# Si l'exposant est 1, on n'affiche pas l'exposant
if isinstance(base, Add):
return f"\\left({latex(base,**config_standard)}\\right)"
else:
return latex(base,**config_standard)
elif isinstance(base, Add):
return f"\\left({latex(base,**config_standard)}\\right)^{{{latex(exp,**config_standard)}}}"
else:
return latex(base**exp,**config_standard)
if n.is_integer and n > 0:
# n × u' × u^(n-1)
detailed_formula = f"{latex(n,**config_standard)} \\times {du_str} \\times {format_power(u, n-1)}"
elif n.is_Rational and n.q == 2:
# u' / (2√u) - pas de parenthèses supplémentaires dans la racine
detailed_formula = f"\\frac{{{du_str}}}{{2\\sqrt{{{latex(u,**config_standard)}}}}}"
elif n.is_number and n < 0:
# n × u' / u^(-n+1)
if -n+1 == 1:
# L'exposant vaut 1, on n'affiche pas l'exposant
detailed_formula = f"{latex(n,**config_standard)} \\times \\frac{{{du_str}}}{{{latex(u,**config_standard)}}}"
else:
detailed_formula = f"{latex(n,**config_standard)} \\times \\frac{{{du_str}}}{{{format_power(u, -n+1)}}}"
else:
# α × u' × u^(α-1)
detailed_formula = f"{latex(n,**config_standard)} \\times {du_str} \\times {format_power(u, n-1)}"
simplified = diff(self, variable)
phrase += narr_conclude_with_details(name, variable, detailed_formula, simplified, detail)
return phrase
# --- pxs_exp ---
# --- pxs_exp (remplacement complet) ---
[docs]
class pxs_exp(exp):
[docs]
def pxsl_diff(self, nm, variable, niveau, override_name=None, detail = None):
config_standard = pxs_config()
name = override_name if override_name else nm.current()
u = self.args[0] # exp a toujours un seul argument
phrase = narr_header(name, variable)
# Cas simple : e^x
if u == variable:
phrase += myst(r"""
La fonction $\py{latex(Symbol(name),**config_standard)}$ est l'exponentielle de base $e$.
On sait que la dérivée de $e^{\py{latex(variable,**config_standard)}}$ est $e^{\py{latex(variable,**config_standard)}}$.
Donc :
\begin{equation*}
\py{latex(Symbol(name),**config_standard)}'(\py{latex(variable,**config_standard)}) = e^{\py{latex(variable,**config_standard)}}
\end{equation*}
""", globals(), locals())
return phrase
# Cas composé : e^{u(x)}
name_u = nm.next()
# On annonce la règle — sans jamais réécrire l'intérieur de l'exponentielle
phrase += myst(r"""
La fonction $\py{latex(Symbol(name),**config_standard)}$ est de la forme
$e^{\py{latex(Symbol(name_u),**config_standard)}}$ avec
$\py{latex(Symbol(name_u),**config_standard)}(\py{latex(variable,**config_standard)}) = \py{latex(u,**config_standard)}$.
On sait que
$\big(e^{\py{latex(Symbol(name_u),**config_standard)}}\big)'(\py{latex(variable,**config_standard)}) =
\py{latex(Symbol(name_u),**config_standard)}'(\py{latex(variable,**config_standard)})\,e^{\py{latex(Symbol(name_u),**config_standard)}(\py{latex(variable,**config_standard)})}$.
""", globals(), locals())
# Si u n'est pas "simple", on détaille son calcul (mais on ne touche pas à e^{u})
u_is_polynomial = is_polynomial_in(u, variable)
if not (u_is_polynomial and niveau == 'terminale'):
phrase += narr_deriv_or_recurse(
u, nm, variable, niveau,
lhs=f"{latex(Symbol(name_u),**config_standard)}'({latex(variable,**config_standard)})",
func_name=name_u, detail = detail
)
# Conclusion : format strict (u') e^{u} SANS × et SANS manipulation de u
du = diff(u, variable)
# Parenthèses autour de u' si c'est une somme
du_latex = f"\\left({latex(du,**config_standard)}\\right)" if isinstance(du, Add) else latex(du,**config_standard)
exp_latex = f"e^{{{latex(u,**config_standard)}}}" # on n'altère jamais l'intérieur
# On imprime UNE SEULE forme finale, sans chaîne d'égalités
formula_latex = f"{du_latex}{' '}{exp_latex}"
phrase += myst(r"""
Donc :
\begin{equation*}
\py{latex(Symbol(name),**config_standard)}'(\py{latex(variable,**config_standard)}) = \py{formula_latex}
\end{equation*}
""", globals(), locals())
return phrase
# --- pxs_log ---
[docs]
class pxs_log(log):
[docs]
def pxsl_diff(self, nm, variable, niveau, override_name=None, detail = None):
config_standard = pxs_config()
name = override_name if override_name else nm.current()
u = self.args[0] # log a toujours un seul argument
phrase = narr_header(name, variable)
# Cas simple : ln(x)
if u == variable:
phrase += myst(r"""
La fonction $\py{latex(Symbol(name),**config_standard)}$ est le logarithme népérien.
On sait que la dérivée de $\ln(\py{latex(variable,**config_standard)})$ est $\dfrac{1}{\py{latex(variable,**config_standard)}}$.
Donc :
\begin{equation*}
\py{latex(Symbol(name),**config_standard)}'(\py{latex(variable,**config_standard)}) = \frac{1}{\py{latex(variable,**config_standard)}}
\end{equation*}
""", globals(), locals())
return phrase
# Cas composé : ln(u(x))
name_u = nm.next()
# Vérifier si u est un polynôme
u_is_polynomial = is_polynomial_in(u, variable)
phrase += myst(r"""
La fonction $\py{latex(Symbol(name),**config_standard)}$ est de la forme
$\ln(\py{latex(Symbol(name_u),**config_standard)})$ avec
$\py{latex(Symbol(name_u),**config_standard)}(\py{latex(variable,**config_standard)}) = \py{latex(u,**config_standard)}$.
On sait que
$\big(\ln(\py{latex(Symbol(name_u),**config_standard)})\big)'(\py{latex(variable,**config_standard)}) =
\dfrac{\py{latex(Symbol(name_u),**config_standard)}'(\py{latex(variable,**config_standard)})}{\py{latex(Symbol(name_u),**config_standard)}(\py{latex(variable,**config_standard)})}$""", globals(), locals())
if u_is_polynomial and niveau == 'terminale':
# En terminale, si u est un polynôme, on donne directement sa dérivée
du = diff(u, variable)
phrase += myst(r""" avec $\py{latex(Symbol(name_u),**config_standard)}'(\py{latex(variable,**config_standard)}) = \py{latex(du,**config_standard)}$.
""", globals(), locals())
else:
# Sinon on détaille le calcul
phrase += myst(r""".
""", globals(), locals())
phrase += narr_deriv_or_recurse(
u, nm, variable, niveau,
lhs=f"{latex(Symbol(name_u),**config_standard)}'({latex(variable,**config_standard)})",
func_name=name_u, detail = detail
)
# Conclusion avec gestion des parenthèses pour le numérateur
du = diff(u, variable)
# Si du est une somme (Add), on ajoute des parenthèses au numérateur
if isinstance(du, Add):
du_latex = f"\\left({latex(du,**config_standard)}\\right)"
else:
du_latex = latex(du,**config_standard)
# Pour le dénominateur, pas de parenthèses supplémentaires
u_latex = latex(u,**config_standard)
# Formule détaillée : u'/u
detailed_formula = f"\\frac{{{du_latex}}}{{{u_latex}}}"
# Forme simplifiée
simplified = diff(self, variable)
phrase += narr_conclude_with_details(name, variable, detailed_formula, simplified, detail)
return phrase
# --- pxs_sin ---
[docs]
class pxs_sin(sin):
[docs]
def pxsl_diff(self, nm, variable, niveau, override_name=None, detail = None):
config_standard = pxs_config()
name = override_name if override_name else nm.current()
u = self.args[0] # sin a toujours un seul argument
phrase = narr_header(name, variable)
# Cas simple : sin(x)
if u == variable:
phrase += myst(r"""
La fonction $\py{latex(Symbol(name),**config_standard)}$ est la fonction sinus.
On sait que la dérivée de $\sin(\py{latex(variable,**config_standard)})$ est $\cos(\py{latex(variable,**config_standard)})$.
Donc :
\begin{equation*}
\py{latex(Symbol(name),**config_standard)}'(\py{latex(variable,**config_standard)}) = \cos(\py{latex(variable,**config_standard)})
\end{equation*}
""", globals(), locals())
return phrase
# Cas composé : sin(u(x))
name_u = nm.next()
# Vérifier si u est un polynôme
u_is_polynomial = is_polynomial_in(u, variable)
phrase += myst(r"""
La fonction $\py{latex(Symbol(name),**config_standard)}$ est de la forme
$\sin(\py{latex(Symbol(name_u),**config_standard)})$ avec
$\py{latex(Symbol(name_u),**config_standard)}(\py{latex(variable,**config_standard)}) = \py{latex(u,**config_standard)}$.
On sait que
$\big(\sin(\py{latex(Symbol(name_u),**config_standard)})\big)'(\py{latex(variable,**config_standard)}) =
\py{latex(Symbol(name_u),**config_standard)}'(\py{latex(variable,**config_standard)}) \times \cos(\py{latex(Symbol(name_u),**config_standard)}(\py{latex(variable,**config_standard)}))$""", globals(), locals())
if u_is_polynomial and niveau == 'terminale':
# En terminale, si u est un polynôme, on donne directement sa dérivée
du = diff(u, variable)
phrase += myst(r""" avec $\py{latex(Symbol(name_u),**config_standard)}'(\py{latex(variable,**config_standard)}) = \py{latex(du,**config_standard)}$.
""", globals(), locals())
else:
# Sinon on détaille le calcul
phrase += myst(r""".
""", globals(), locals())
phrase += narr_deriv_or_recurse(
u, nm, variable, niveau,
lhs=f"{latex(Symbol(name_u),**config_standard)}'({latex(variable,**config_standard)})",
func_name=name_u, detail = detail
)
# Conclusion avec gestion des parenthèses
du = diff(u, variable)
# Si du est une somme (Add), on ajoute des parenthèses
if isinstance(du, Add):
du_latex = f"\\left({latex(du,**config_standard)}\\right)"
else:
du_latex = latex(du,**config_standard)
# Si u est une somme (Add), on ajoute des parenthèses dans cos(u)
if isinstance(u, Add):
cos_latex = f"\\cos\\left({latex(u,**config_standard)}\\right)"
else:
cos_latex = f"\\cos\\left({latex(u,**config_standard)}\\right)"
# Formule détaillée : u' × cos(u)
detailed_formula = f"{du_latex} \\times {cos_latex}"
# Forme simplifiée
simplified = diff(self, variable)
phrase += narr_conclude_with_details(name, variable, detailed_formula, simplified, detail)
return phrase
# --- pxs_cos ---
[docs]
class pxs_cos(cos):
[docs]
def pxsl_diff(self, nm, variable, niveau, override_name=None, detail = None):
config_standard = pxs_config()
name = override_name if override_name else nm.current()
u = self.args[0] # cos a toujours un seul argument
phrase = narr_header(name, variable)
# Cas simple : cos(x)
if u == variable:
phrase += myst(r"""
La fonction $\py{latex(Symbol(name),**config_standard)}$ est la fonction cosinus.
On sait que la dérivée de $\cos(\py{latex(variable,**config_standard)})$ est $-\sin(\py{latex(variable,**config_standard)})$.
Donc :
\begin{equation*}
\py{latex(Symbol(name),**config_standard)}'(\py{latex(variable,**config_standard)}) = -\sin(\py{latex(variable,**config_standard)})
\end{equation*}
""", globals(), locals())
return phrase
# Cas composé : cos(u(x))
name_u = nm.next()
# Vérifier si u est un polynôme
u_is_polynomial = is_polynomial_in(u, variable)
phrase += myst(r"""
La fonction $\py{latex(Symbol(name),**config_standard)}$ est de la forme
$\cos(\py{latex(Symbol(name_u),**config_standard)})$ avec
$\py{latex(Symbol(name_u),**config_standard)}(\py{latex(variable,**config_standard)}) = \py{latex(u,**config_standard)}$.
On sait que
$\big(\cos(\py{latex(Symbol(name_u),**config_standard)})\big)'(\py{latex(variable,**config_standard)}) =
-\py{latex(Symbol(name_u),**config_standard)}'(\py{latex(variable,**config_standard)}) \times \sin(\py{latex(Symbol(name_u),**config_standard)}(\py{latex(variable,**config_standard)}))$""", globals(), locals())
if u_is_polynomial and niveau == 'terminale':
# En terminale, si u est un polynôme, on donne directement sa dérivée
du = diff(u, variable)
phrase += myst(r""" avec $\py{latex(Symbol(name_u),**config_standard)}'(\py{latex(variable,**config_standard)}) = \py{latex(du,**config_standard)}$.
""", globals(), locals())
else:
# Sinon on détaille le calcul
phrase += myst(r""".
""", globals(), locals())
phrase += narr_deriv_or_recurse(
u, nm, variable, niveau,
lhs=f"{latex(Symbol(name_u),**config_standard)}'({latex(variable,**config_standard)})",
func_name=name_u, detail = detail
)
# Conclusion avec gestion des parenthèses et du signe négatif
du = diff(u, variable)
# Gérer les parenthèses pour du si c'est une somme
if isinstance(du, Add):
du_latex = f"\\left({latex(du,**config_standard)}\\right)"
else:
du_latex = latex(du,**config_standard)
# Si u est une somme (Add), on ajoute des parenthèses dans sin(u)
if isinstance(u, Add):
sin_latex = f"\\sin\\left({latex(u,**config_standard)}\\right)"
else:
sin_latex = f"\\sin\\left({latex(u,**config_standard)}\\right)"
# Formule détaillée : -u' × sin(u)
detailed_formula = f"-{du_latex} \\times {sin_latex}"
# Forme simplifiée
simplified = diff(self, variable)
phrase += narr_conclude_with_details(name, variable, detailed_formula, simplified, detail)
return phrase
""" x = Symbol('x')
# --- Add / Sommes ---
ex1 = 3*x + 2 # affine simple
ex2 = x**3 - 4*x + 7 # polynôme pur
ex3 = x**2 + sin(x) + exp(x) # mélange poly + usuelles
ex4 = -x + log(x) - 5 # signes + log simple
# --- Mul / Produits ---
ex5 = 4*sin(x) # k*u (k numérique)
ex6 = -x*log(x) # signe devant, produit classique
ex7 = (2*x+3)*cos(x) # produit poly * cos
ex8 = (x-1)*(x+2) # produit de deux polynômes
ex9 = (x+1)*exp(x) # produit * exp
# --- Quotients (via puissances négatives) ---
ex10 = (2*x+3)/(x+3)**2 # v=(x+3)^2 -> g=(x+3)
ex11 = (x**2+1)/((x+1)*(x+2)) # v=(x+1)(x+2) -> pas de g
ex12 = (3*x-1)/((x+1)**3*(x+2)**2) # v composite -> g=(x+1)^2*(x+2)
ex13 = (sin(x)+1)/x # 1/x = x**(-1)
ex14 = (x*cos(x))/(x+2)**4 # numérateur produit, v=(x+2)^4
# --- Puissances ---
ex15 = (x+1)**5 # n entier >0
ex16 = sqrt(x+2) # n = 1/2
ex17 = (x-3)**(-2) # n négatif (≠ -1)
ex18 = (2*x+1)**(-1) # n = -1 (cas spécial)
# --- Exponentielles (ne pas toucher l’intérieur) ---
ex19 = exp(2*x**2 + 3*x) # polynôme dans l’exposant
ex20 = exp((x+1)*(x+2)) # produit dans l’exposant
ex21 = exp(sin(x)) # non-polynôme dans l’exposant
# --- Logarithmes ---
ex22 = log(x) # simple
ex23 = log((x+1)*(x+2)) # produit dans l’argument
ex24 = log(x**2 + 3*x + 2) # polynôme dans l’argument
# --- Sin/Cos (chaînes) ---
ex25 = sin(2*x + 3) # affine dans sin
ex26 = cos(x**2) # poly dans cos
ex27 = sin((x+1)*(x+2)) # non-polynôme dans sin
# --- Cas « mélange » + robustesse ---
ex28 = x**4 + 3*x**2 - 2*x + 4 - x*log(x) + exp(x) # long mixte (ton expr6)
ex29 = (x+1)**2 * (x+2)**-3 # produit * quotient (Pow négatif)
ex30 = (2 - 3*x) * sin(x**2) # produit avec signe et chaîne
## Sans détails
###
$f(x) = \py{latex(ex6)}$
\py{expliquer_derivation(ex6)}
______________________________________________________
###
$f(x) = \py{latex(ex7)}$
\py{expliquer_derivation(ex7, variable=x, start_name='f')}
______________________________________________________
###
$f(x) = \py{latex(ex8)}$
\py{expliquer_derivation(ex8, variable=x, start_name='f')}
______________________________________________________
###
$f(x) = \py{latex(ex9)}$
\py{expliquer_derivation(ex9, variable=x, start_name='f')}
______________________________________________________
###
$f(x) = \py{latex(ex10)}$
\py{expliquer_derivation(ex10, variable=x, start_name='f')}
______________________________________________________
###
$f(x) = \py{latex(ex11)}$
\py{expliquer_derivation(ex11, variable=x, start_name='f')}
______________________________________________________
###
$f(x) = \py{latex(ex12)}$
\py{expliquer_derivation(ex12, variable=x, start_name='f')}
______________________________________________________
###
$f(x) = \py{latex(ex13)}$
\py{expliquer_derivation(ex13, variable=x, start_name='f')}
______________________________________________________
###
$f(x) = \py{latex(ex14)}$
\py{expliquer_derivation(ex14, variable=x, start_name='f')}
______________________________________________________
###
$f(x) = \py{latex(ex23)}$
\py{expliquer_derivation(ex23, variable=x, start_name='f')}
______________________________________________________
###
$f(x) = \py{latex(ex24)}$
\py{expliquer_derivation(ex24, variable=x, start_name='f')}
______________________________________________________
###
$f(x) = \py{latex(ex27)}$
\py{expliquer_derivation(ex27, variable=x, start_name='f')}
______________________________________________________
###
$f(x) = \py{latex(ex28)}$
\py{expliquer_derivation(ex28, variable=x, start_name='f')}
______________________________________________________
###
$f(x) = \py{latex(ex29)}$
\py{expliquer_derivation(ex29, variable=x, start_name='f')}
______________________________________________________
###
$f(x) = \py{latex(ex30)}$
\py{expliquer_derivation(ex30, variable=x, start_name='f')}
______________________________________________________
## Avec détails
###
$f(x) = \py{latex(ex1)}$
\py{expliquer_derivation(ex1, variable=x, start_name='f', niveau='premiere', detail = "high")}
______________________________________________________
###
$f(x) = \py{latex(ex2)}$
\py{expliquer_derivation(ex2, variable=x, start_name='f', detail = "high")}
______________________________________________________
###
$f(x) = \py{latex(ex3)}$
\py{expliquer_derivation(ex3, variable=x, start_name='f', detail = "high")}
______________________________________________________
###
$f(x) = \py{latex(ex4,**config_standard)}$
\py{expliquer_derivation(ex4, variable=x, start_name='f', detail = "high")}
______________________________________________________
###
$f(x) = \py{latex(ex5)}$
\py{expliquer_derivation(ex5, variable=x, start_name='f', detail = "high")}
______________________________________________________
###
$f(x) = \py{latex(ex10)}$
\py{expliquer_derivation(ex10, variable=x, start_name='f', detail = "high")}
______________________________________________________
$f(x) = \py{latex(ex15)}$
\py{expliquer_derivation(ex15, variable=x, start_name='f', detail = "high")}
______________________________________________________
###
$f(x) = \py{latex(ex16)}$
\py{expliquer_derivation(ex16, variable=x, start_name='f', detail = "high")}
______________________________________________________
###
$f(x) = \py{latex(ex17)}$
\py{expliquer_derivation(ex17, variable=x, start_name='f', detail = "high")}
______________________________________________________
###
$f(x) = \py{latex(ex18)}$
\py{expliquer_derivation(ex18, variable=x, start_name='f', detail = "high")}
______________________________________________________
###
$f(x) = \py{latex(ex19)}$
\py{expliquer_derivation(ex19, variable=x, start_name='f', detail = "high")}
______________________________________________________
###
$f(x) = \py{latex(ex20)}$
\py{expliquer_derivation(ex20, variable=x, start_name='f', detail = "high")}
______________________________________________________
###
$f(x) = \py{latex(ex21)}$
\py{expliquer_derivation(ex21, variable=x, start_name='f', detail = "high")}
______________________________________________________
###
$f(x) = \py{latex(ex22)}$
\py{expliquer_derivation(ex22, variable=x, start_name='f', detail = "high")}
______________________________________________________
###
$f(x) = \py{latex(ex25)}$
\py{expliquer_derivation(ex25, variable=x, start_name='f', detail = "high")}
______________________________________________________
###
$f(x) = \py{latex(ex26)}$
\py{expliquer_derivation(ex26, variable=x, start_name='f', detail = "high")}
______________________________________________________
###
$f(x) = \py{latex(ex29)}$
\py{expliquer_derivation(ex29, variable=x, start_name='f', detail = "high")}
______________________________________________________
"""