Spaces:
Sleeping
Sleeping
wilenPxs commited on
Commit ·
87b004b
1
Parent(s): cce0786
Mode Declinaisons QCM/QAT: spec MCQ/FGQ (calibree sur 33 exemples valides), harnais etendu (collisions options multi-graines, arite FGQ, un seul bloc python), filets deterministes (mcqOption, None dernier, fusion blocs), UI mode+types, analyse partagee QCM/QAT, zero regression pythonisation (smoke 45/45)
Browse files- README_APP.md +116 -0
- app/config.py +6 -0
- app/knowledge/fewshots/declinaisons/{en}`Domain of`{fr}`Domaine de définition de` $_frac{a + bx}{x^2 - c}$.md +84 -0
- app/knowledge/fewshots/declinaisons/{en}`Domain of`{fr}`Domaine de définition de` $_sqrt{a - bx}$.md +73 -0
- app/knowledge/fewshots/declinaisons/{en}`Solution sets for homogeneous and nonhomogeneous systems in $_mathbb{R}^3$`{fr}`Ensembles des solutions pour des systèmes homogènes et inhomogènes de $_mathbb{R}^3$` - MCQ.md +373 -0
- app/knowledge/fewshots/declinaisons/{en}`The set of solutions of a homogeneous system`{fr}`Ensemble des solutions d'un système homogène` - MCQ.md +133 -0
- app/knowledge/fewshots/declinaisons/{en}`The set of solutions of an inhomogeneous system`{fr}`Ensemble des solutions d'un système inhomogène` - MCQ.md +131 -0
- app/knowledge/fewshots/declinaisons/{fr}`Amortissement dégressif d'un équipement`{en}`Declining Balance Depreciation of Equipment`.md +189 -0
- app/knowledge/fewshots/declinaisons/{fr}`Capitalisation annuelle contre capitalisation continue`{en}`Annual Compounding versus Continuous Compounding`.md +193 -0
- app/knowledge/fewshots/declinaisons/{fr}`Contrainte de budget publicitaire`{en}`Advertising budget constraint`.md +110 -0
- app/knowledge/fewshots/declinaisons/{fr}`Discontinuité d'une fraction rationnelle`{en}`Discontinuity of a Rational Fraction`.md +81 -0
- app/knowledge/fewshots/declinaisons/{fr}`Formule générale du seuil de rentabilité`{en}`General Break-Even Point Formula`.md +261 -0
- app/knowledge/fewshots/declinaisons/{fr}`Intérêts composés _ calcul d'un capital futur`{en}`Compound Interest_ Future Capital Calculation`.md +196 -0
- app/knowledge/fewshots/declinaisons/{fr}`Inéquation linéaire en deux étapes`{en}`Linear Inequality in Two Steps`.md +94 -0
- app/knowledge/fewshots/declinaisons/{fr}`Inéquation linéaire en une étape et notation intervalle`{en}`One-step linear inequality and interval notation`.md +150 -0
- app/knowledge/fewshots/declinaisons/{fr}`Lecture graphique du sens de variation et des extremums`{en}`Graphical reading of monotonicity and extrema`.md +218 -0
- app/knowledge/fewshots/declinaisons/{fr}`Monotonie selon la base et comparaison de valeurs`{en}`Monotonicity according to base and comparison of values`.md +157 -0
- app/knowledge/fewshots/declinaisons/{fr}`Objectif de revenu`{en}`Income Target`.md +163 -0
- app/knowledge/fewshots/declinaisons/{fr}`Plafond budgétaire`{en}`Budget cap`.md +154 -0
- app/knowledge/fewshots/declinaisons/{fr}`Prix admissible entre marge minimale et plafond réglementaire`{en}`Admissible price between minimum margin and regulatory ceiling`.md +213 -0
- app/knowledge/fewshots/declinaisons/{fr}`Prix plafond et demande décroissante`{en}`Price ceiling and decreasing demand`.md +127 -0
- app/knowledge/fewshots/declinaisons/{fr}`Règle des exposants _ simplification et calcul`{en}`Exponent Rules_ Simplification and Calculation`.md +162 -0
- app/knowledge/fewshots/declinaisons/{fr}`Résolution de`{en}`Resolution of` $_ax+b_ = k$ {fr}`avec`{en}`with` $k _ 0$.md +151 -0
- app/knowledge/fewshots/declinaisons/{fr}`Seuil de rentabilité d'une boutique`{en}`Break-Even Point of a Store`.md +198 -0
- app/knowledge/fewshots/declinaisons/{fr}`Simplifier des expressions avec exponentielle et logarithme`{en}`Simplify expressions with exponential and logarithm`.md +119 -0
- app/knowledge/fewshots/declinaisons/{fr}`Symétries d'une courbe de demande et restriction économique`{en}`Symmetries of a demand curve and economic restriction`.md +192 -0
- app/knowledge/fewshots/declinaisons/{fr}`Unités minimales pour couvrir les coûts fixes`{en}`Minimum units to cover fixed costs`.md +242 -0
- app/knowledge/fewshots/declinaisons/{fr}`Valeurs absolues élémentaires 2`{en}`Elementary Absolute Values 2`.md +215 -0
- app/knowledge/fewshots/declinaisons/{fr}`Valeurs absolues élémentaires 3`{en}`Elementary Absolute Values 3`.md +163 -0
- app/knowledge/fewshots/declinaisons/{fr}`Valeurs absolues élémentaires`{en}`Elementary Absolute Values`.md +283 -0
- app/knowledge/fewshots/declinaisons/{fr}`Zone de rentabilité symétrique autour de l'optimum`{en}`Symmetric profitability zone around the optimum`.md +236 -0
- app/knowledge/fewshots/declinaisons/{fr}`Équations exponentielles et logarithmiques`{en}`Exponential and Logarithmic Equations`.md +302 -0
- app/knowledge/fewshots/declinaisons/{fr}`Équilibre de marché _ l'offre rencontre la demande`{en}`Market Equilibrium_ Supply Meets Demand`.md +202 -0
- app/knowledge/fewshots/declinaisons/{fr}`Équilibre offre-demande sur un marché`{en}`Supply-demand equilibrium in a market`.md +237 -0
- app/knowledge/fewshots/declinaisons/{fr}`Évaluer un logarithme en reconnaissant une puissance de la base`{en}`Evaluate a logarithm by recognizing a power of the base`.md +131 -0
- app/knowledge/fewshots/qat.md +62 -0
- app/knowledge/fewshots/qcm.md +85 -0
- app/pipeline/fewshots.py +13 -0
- app/pipeline/generate.py +53 -15
- app/pipeline/orchestrator.py +106 -10
- app/pipeline/postprocess.py +110 -1
- app/pipeline/prompts.py +285 -0
- app/server.py +63 -21
- app/validation/harness.py +152 -1
- app/validation/sandbox.py +57 -18
- app/web/templates/index.html +45 -1
- tests/smoke.py +363 -0
README_APP.md
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Pythonise Exercice — v2
|
| 2 |
+
|
| 3 |
+
App Flask à deux modes :
|
| 4 |
+
- **Pythonisation** d'exercices PyxiScience MyST (valeurs fixes → randomisées) ;
|
| 5 |
+
- **Déclinaisons** (2026-07) : génération de versions **QCM** (MCQ) et/ou
|
| 6 |
+
**QAT** (FGQ, champs libres ordonnés) d'un exercice, randomisées elles aussi.
|
| 7 |
+
|
| 8 |
+
Pipeline async : analyse LLM + notions + RAG (parallèles, **partagés entre QCM
|
| 9 |
+
et QAT**) → génération par paires → audit LLM → filets déterministes → **porte
|
| 10 |
+
harnais** (100 graines + contrôles étendus déclinaisons : unicité des options
|
| 11 |
+
MCQ sur toutes les graines, 1 seule bonne réponse, « None » en dernier, arité
|
| 12 |
+
FGQ, `:solution:` JSON) ; verdict exposé dans l'UI.
|
| 13 |
+
|
| 14 |
+
## Architecture (refonte 2026-06)
|
| 15 |
+
|
| 16 |
+
```
|
| 17 |
+
run.py Lancement : python run.py (ou python -m app)
|
| 18 |
+
app/
|
| 19 |
+
├── __init__.py create_app() + logging + .env
|
| 20 |
+
├── config.py TOUTE la config : modèles, PYTHON_FENCE_BACKTICKS=4,
|
| 21 |
+
│ ANALYSIS_MODEL_IDX, RAG_TOP_K, USE_REASONING,
|
| 22 |
+
│ DEFAULT_LANG, JOB_TTL, chemins, prix
|
| 23 |
+
├── server.py routes Flask (/, /health, /api/models, /api/jobs)
|
| 24 |
+
├── keys.py chargement des clés API (.env)
|
| 25 |
+
├── pipeline/
|
| 26 |
+
│ ├── orchestrator.py chef d'orchestre (1 exercice)
|
| 27 |
+
│ ├── analyze.py analyse + notions + RAG en PARALLÈLE
|
| 28 |
+
│ ├── generate.py découpage + génération par paires
|
| 29 |
+
│ ├── audit.py audit LLM ≤2 itérations + filet de sécurité patches
|
| 30 |
+
│ ├── postprocess.py filets déterministes (auto-lift {{}}, ${}, fences 4,
|
| 31 |
+
│ │ renommage camelCase, décimales/langue, dédoublonnage)
|
| 32 |
+
│ ├── solutions.py substitution des solutions validées (règle 8.1)
|
| 33 |
+
│ ├── translate.py langue cible fr/en/both (prose seule, Python masqué)
|
| 34 |
+
│ ├── prompts.py tous les prompts (v1 archivée dans knowledge/)
|
| 35 |
+
│ └── fewshots.py sélection d'un exemple canonique par type détecté
|
| 36 |
+
├── rag/ functions.py (FAISS) · formatter.py · notions.py ·
|
| 37 |
+
│ catalogue.py (catalogue curé injecté par domaine)
|
| 38 |
+
├── llm/ client.py (OpenRouter + reasoning opt.) · cost.py
|
| 39 |
+
├── validation/ harness.py (porte qualité) · sandbox.py (exec sandboxée)
|
| 40 |
+
├── knowledge/ pythonisation_rules.md · rules_digest.py ·
|
| 41 |
+
│ fewshots/ (exemples VERTS) · prompts_v1_archive.md ·
|
| 42 |
+
│ functions_catalogue.md (catalogue curé des helpers)
|
| 43 |
+
├── corpus/ 5 fichiers de fonctions PyxiScience (RAG)
|
| 44 |
+
└── web/templates/index.html UI (batch, langue, verdict harnais, coût)
|
| 45 |
+
data/ notions.xlsx · faiss_cache/
|
| 46 |
+
tests/smoke.py smoke tests hors-ligne (LLM mocké)
|
| 47 |
+
```
|
| 48 |
+
|
| 49 |
+
## Installation (WSL Ubuntu)
|
| 50 |
+
|
| 51 |
+
```bash
|
| 52 |
+
python3 -m venv .venv && source .venv/bin/activate
|
| 53 |
+
pip install -r requirements.txt # faiss-cpu + sentence-transformers ≈ 1.5 Go
|
| 54 |
+
cp .env.example .env # renseigner OPENROUTER_API_KEY / OPENAI_API_KEY
|
| 55 |
+
python run.py # http://127.0.0.1:5000
|
| 56 |
+
```
|
| 57 |
+
|
| 58 |
+
Smoke tests (sans réseau) : `.venv/bin/python tests/smoke.py`
|
| 59 |
+
Validation d'une sortie : `python ../.claude/skills/validation-harness/harness.py <fichier>.md --seeds 300`
|
| 60 |
+
|
| 61 |
+
### Endpoints
|
| 62 |
+
|
| 63 |
+
| Méthode | URL | Description |
|
| 64 |
+
|---------|------------------|--------------------------------------------------------|
|
| 65 |
+
| GET | `/` | UI web |
|
| 66 |
+
| GET | `/health` | sanity check |
|
| 67 |
+
| GET | `/api/models` | roster des modèles + défauts |
|
| 68 |
+
| POST | `/api/jobs` | démarre un job (1 fichier OU batch) → `{job_id}` |
|
| 69 |
+
| GET | `/api/jobs/<id>` | suivi + résultats par fichier (harnais, coût, langue) |
|
| 70 |
+
| GET | `/api/jobs/<id>/download` | ZIP de toutes les sorties `.md` + `_recapitulatif.md` |
|
| 71 |
+
|
| 72 |
+
POST body :
|
| 73 |
+
```json
|
| 74 |
+
{
|
| 75 |
+
"files": [{"filename": "exo.md", "content": "<MyST>"}, ...],
|
| 76 |
+
"lang": "fr | en | both",
|
| 77 |
+
"level": "",
|
| 78 |
+
"model_idx": 1,
|
| 79 |
+
"mode": "pythonise | declinaisons",
|
| 80 |
+
"types": {"qcm": true, "qat": true}
|
| 81 |
+
}
|
| 82 |
+
```
|
| 83 |
+
(`mode` absent ⇒ `pythonise`, rétro-compatible ; `types` requis en mode
|
| 84 |
+
`declinaisons`, au moins un `true` — les deux cochés ⇒ 2 fichiers par source,
|
| 85 |
+
nommés `<source>_QCM.md` / `<source>_QAT.md`, avec UNE seule analyse partagée.
|
| 86 |
+
Rétro-compat : `{"content": "...", "filename": "..."}` accepté pour 1 fichier.)
|
| 87 |
+
|
| 88 |
+
Résultat par fichier : `exercise`, `analysis`, `notions`, `audit_patches`,
|
| 89 |
+
`warnings`, **`harness` {ok, seeds, summary}**, **`lang` {source, target, action}**,
|
| 90 |
+
**`cost` {usd, eur, requests}**, `duration_s`.
|
| 91 |
+
|
| 92 |
+
**Téléchargement** (UI, panneau Résultat) : bouton **« Télécharger .md »**
|
| 93 |
+
(sortie pythonisée du fichier affiché, suffixe `_pythonise.md` pour ne pas
|
| 94 |
+
écraser la source) ; en **batch**, bouton **« Tout (.zip) »** → ZIP de toutes
|
| 95 |
+
les sorties + un `_recapitulatif.md` (verdict harnais / warnings / coût par fichier).
|
| 96 |
+
|
| 97 |
+
## Sécurité
|
| 98 |
+
|
| 99 |
+
🚨 **Les clés API dans `.env` étaient en clair dans le zip d'origine** —
|
| 100 |
+
considère-les comme exposées et **rotate-les** :
|
| 101 |
+
- OpenRouter : https://openrouter.ai/keys
|
| 102 |
+
- OpenAI : https://platform.openai.com/api-keys
|
| 103 |
+
|
| 104 |
+
La sandbox (`app/validation/sandbox.py`) exécute le code généré par le LLM
|
| 105 |
+
avec builtins restreints, imports whitelistés et timeout (mono-poste : le
|
| 106 |
+
modèle de menace est l'accident LLM, pas un adversaire).
|
| 107 |
+
|
| 108 |
+
## Notes
|
| 109 |
+
|
| 110 |
+
- Premier lancement : reconstruit le cache FAISS si absent (≈ 30 s).
|
| 111 |
+
- Modèles (IDs vérifiés sur OpenRouter 2026-06-12) : Opus 4.8, **Sonnet 4.6
|
| 112 |
+
(défaut)**, Fable 5, Haiku 4.5, Gemini 2.5 Pro, GPT-5.2.
|
| 113 |
+
- L'étape d'analyse suit le modèle choisi par l'utilisateur
|
| 114 |
+
(`ANALYSIS_MODEL_IDX=None` dans config.py pour ce comportement).
|
| 115 |
+
- Mode batch : fichiers traités séquentiellement ; un échec n'arrête pas les
|
| 116 |
+
autres ; récapitulatif VERT/ROUGE/erreurs + coût total.
|
app/config.py
CHANGED
|
@@ -70,6 +70,12 @@ HARNESS_REPAIR_MAX = 2 # boucles de réparation LLM si la porte est ro
|
|
| 70 |
# ── Langue cible ─────────────────────────────────────────────────────────────
|
| 71 |
DEFAULT_LANG = "fr" # "fr" | "en" | "both"
|
| 72 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 73 |
# ── Serveur / jobs ───────────────────────────────────────────────────────────
|
| 74 |
JOB_TTL = 1800 # s avant purge d'un job terminé
|
| 75 |
# HOST/PORT pilotables par l'environnement (déploiement). En local : 127.0.0.1.
|
|
|
|
| 70 |
# ── Langue cible ─────────────────────────────────────────────────────────────
|
| 71 |
DEFAULT_LANG = "fr" # "fr" | "en" | "both"
|
| 72 |
|
| 73 |
+
# ── Modes (pythonisation / déclinaisons QCM-QAT) ─────────────────────────────
|
| 74 |
+
DEFAULT_MODE = "pythonise" # "pythonise" | "declinaisons"
|
| 75 |
+
DECLINAISON_TYPES = ("qcm", "qat")
|
| 76 |
+
MCQ_NUM_OPTIONS = 5 # 1 correcte + 3 distracteurs + « None » en dernier
|
| 77 |
+
QAT_FALLBACK_TO_MCQ = True # question non auto-corrigeable en champ libre → MCQ
|
| 78 |
+
|
| 79 |
# ── Serveur / jobs ───────────────────────────────────────────────────────────
|
| 80 |
JOB_TTL = 1800 # s avant purge d'un job terminé
|
| 81 |
# HOST/PORT pilotables par l'environnement (déploiement). En local : 127.0.0.1.
|
app/knowledge/fewshots/declinaisons/{en}`Domain of`{fr}`Domaine de définition de` $_frac{a + bx}{x^2 - c}$.md
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
`````{exercise}
|
| 2 |
+
:originalExerciseId: 22d960b8-d57e-46d2-8ebd-09d8bed0a1fb
|
| 3 |
+
:title: {en}`Domain of`{fr}`Domaine de définition de` $\frac{a + bx}{x^2 - c}$
|
| 4 |
+
:modules: Analyse_I_EFREI, Calc_1_Pyx
|
| 5 |
+
:recommendedExecutionTime: 0
|
| 6 |
+
:level: Elementary
|
| 7 |
+
:chap: chap_realFunctions_Graphs_functionsFundamentalDefinitions_ESCP, Second_chap_VIII, chap_I_fonction_reel_EFREI, Calculus_I_Functions_and_their_Representations_1_1
|
| 8 |
+
:involvedConcepts:
|
| 9 |
+
:originalSource: Selin
|
| 10 |
+
:visibility: All
|
| 11 |
+
:variations:
|
| 12 |
+
:comment:
|
| 13 |
+
:id: 91465036-709e-11f1-a8a1-0ed8d3b012a9
|
| 14 |
+
|
| 15 |
+
```{python}
|
| 16 |
+
|
| 17 |
+
import random as rd
|
| 18 |
+
from sympy import *
|
| 19 |
+
from pyxiscience.Mes_fctions_d_analyse import pxs_config
|
| 20 |
+
|
| 21 |
+
from pyxiscience.Classes_Extensions import pxs_Interval
|
| 22 |
+
|
| 23 |
+
config_standard = pxs_config()
|
| 24 |
+
|
| 25 |
+
import random as rd
|
| 26 |
+
|
| 27 |
+
x = symbols('x')
|
| 28 |
+
a, b, c = rd.sample(range(1, 10), 3)
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
f_num = (a*x+b)
|
| 32 |
+
f_den =(x**2 - c)
|
| 33 |
+
f = f_num/f_den
|
| 34 |
+
domaine1 = pxs_Interval(-oo, -sqrt(c), True, True)
|
| 35 |
+
domaine2 = pxs_Interval(sqrt(c), +oo, True, True)
|
| 36 |
+
|
| 37 |
+
# === Ajouts conversion FGQ ===
|
| 38 |
+
b_inf = latex(-sqrt(c))
|
| 39 |
+
b_sup = latex(sqrt(c))
|
| 40 |
+
```
|
| 41 |
+
|
| 42 |
+
:::::{question}
|
| 43 |
+
:questionType: FGQ
|
| 44 |
+
:solution: [["ord","${{ b_inf }}$","${{ b_sup }}$"],["0.000001","0.000001"]]
|
| 45 |
+
|
| 46 |
+
::::{questionStatement}
|
| 47 |
+
{en}`Find the domain of the function`{fr}`Donner le domaine de la fonction`
|
| 48 |
+
\begin{equation*}
|
| 49 |
+
\ds f(x) = {{ latex(f, **config_standard) }}.
|
| 50 |
+
\end{equation*}
|
| 51 |
+
{en}`The function is defined for every real number except the two values that cancel the denominator. Give these two excluded values`{fr}`La fonction est définie pour tout réel sauf aux deux valeurs qui annulent le dénominateur. Donner ces deux valeurs exclues` $x_1 < x_2$ :
|
| 52 |
+
|
| 53 |
+
$x_1 =$ {input}`||120` {en}`and`{fr}`et` $x_2 =$ {input}`||120`
|
| 54 |
+
::::
|
| 55 |
+
|
| 56 |
+
::::{questionHint}
|
| 57 |
+
{en}`A rational function is defined whenever its denominator is not zero. Start by solving`{fr}`Une fonction rationnelle est définie lorsque son dénominateur n’est pas nul. Commencez par résoudre` $x^2-c=0$.
|
| 58 |
+
::::
|
| 59 |
+
|
| 60 |
+
::::{displayedSolution}
|
| 61 |
+
$x_1 = {{ b_inf }}$ {en}`and`{fr}`et` $x_2 = {{ b_sup }}$
|
| 62 |
+
::::
|
| 63 |
+
|
| 64 |
+
::::{detailedSolution}
|
| 65 |
+
|
| 66 |
+
{en}`The denominator cannot be zero, therefore:`{fr}`Le dénominateur doit être différent de zéro :`
|
| 67 |
+
\begin{equation*}
|
| 68 |
+
{{ latex(f_den, **config_standard) }}\neq 0 \implies x \neq \pm {{ latex(sqrt(c)) }}.
|
| 69 |
+
\end{equation*}
|
| 70 |
+
{en}`Thus we have`{fr}`On a donc`
|
| 71 |
+
\begin{equation*}
|
| 72 |
+
\boxed{
|
| 73 |
+
\text{{en}`Domain:`{fr}`Domaine de définition :` }{{ domaine1.print() }} \cup {{ domaine2.print() }}}.
|
| 74 |
+
\end{equation*}
|
| 75 |
+
::::
|
| 76 |
+
|
| 77 |
+
::::{weightDistribution}
|
| 78 |
+
:reasoning: 20
|
| 79 |
+
:logic: 35
|
| 80 |
+
:abstraction: 30
|
| 81 |
+
:calculation: 15
|
| 82 |
+
::::
|
| 83 |
+
:::::
|
| 84 |
+
`````
|
app/knowledge/fewshots/declinaisons/{en}`Domain of`{fr}`Domaine de définition de` $_sqrt{a - bx}$.md
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
`````{exercise}
|
| 2 |
+
:originalExerciseId: e8559359-32c0-4394-94be-daf9a701cb6a
|
| 3 |
+
:title: {en}`Domain of`{fr}`Domaine de définition de` $\sqrt{a - bx}$
|
| 4 |
+
:modules: Analyse_I_EFREI, Calc_1_Pyx
|
| 5 |
+
:recommendedExecutionTime: 0
|
| 6 |
+
:level: Elementary
|
| 7 |
+
:chap: chap_realFunctions_Graphs_functionsFundamentalDefinitions_ESCP, Second_chap_VIII, chap_I_fonction_reel_EFREI, Calculus_I_Functions_and_their_Representations_1_1
|
| 8 |
+
:involvedConcepts:
|
| 9 |
+
:originalSource: Selin
|
| 10 |
+
:visibility: All
|
| 11 |
+
:variations:
|
| 12 |
+
:comment:
|
| 13 |
+
:id: 8f0a1b0c-709e-11f1-a8a1-0ed8d3b012a9
|
| 14 |
+
|
| 15 |
+
```{python}
|
| 16 |
+
|
| 17 |
+
import random as rd
|
| 18 |
+
from sympy import *
|
| 19 |
+
from pyxiscience.Mes_fctions_d_analyse import pxs_config
|
| 20 |
+
from pyxiscience.Classes_Extensions import pxs_Interval
|
| 21 |
+
|
| 22 |
+
config_standard = pxs_config()
|
| 23 |
+
|
| 24 |
+
import random as rd
|
| 25 |
+
|
| 26 |
+
x = symbols('x')
|
| 27 |
+
a, b = rd.sample(range(1, 10), 2)
|
| 28 |
+
|
| 29 |
+
arg = a - b*x
|
| 30 |
+
f = sqrt(arg)
|
| 31 |
+
|
| 32 |
+
domaine = pxs_Interval(-oo, Rational(a,b), True, False)
|
| 33 |
+
|
| 34 |
+
# === Ajouts conversion FGQ ===
|
| 35 |
+
borne = latex(Rational(a, b))
|
| 36 |
+
```
|
| 37 |
+
|
| 38 |
+
:::::{question}
|
| 39 |
+
:questionType: FGQ
|
| 40 |
+
:solution: [["ord","${{ borne }}$"],["0.000001"]]
|
| 41 |
+
|
| 42 |
+
::::{questionStatement}
|
| 43 |
+
{en}`Find the domain of the function`{fr}`Trouver le domaine de définition de la fonction` $f(x) = {{ latex(f) }}$. {en}`Its domain is an interval of the form`{fr}`Son domaine de définition est un intervalle de la forme` $(-\infty,\ \alpha]$. {en}`Give the value of`{fr}`Donner la valeur de` $\alpha$ :
|
| 44 |
+
|
| 45 |
+
$\alpha =$ {input}`||120`
|
| 46 |
+
::::
|
| 47 |
+
|
| 48 |
+
::::{questionHint}
|
| 49 |
+
{en}`A square root is defined only when its radicand is nonnegative. Start by solving`{fr}`Une racine carrée est définie seulement lorsque son radical est positif ou nul. Commencez par résoudre` ${{ latex(arg) }} \ge 0$.
|
| 50 |
+
::::
|
| 51 |
+
|
| 52 |
+
::::{displayedSolution}
|
| 53 |
+
$\alpha = {{ borne }}$
|
| 54 |
+
::::
|
| 55 |
+
|
| 56 |
+
::::{detailedSolution}
|
| 57 |
+
|
| 58 |
+
{en}`For the square root to be defined,`{fr}`Pour que la racine carrée soit définie, il faut que` ${{ latex(arg) }}\geq 0$. {en}`Solving for $x$ gives`{fr}`En résolvant l'inéquation, nous obtenons` ${{ a }} \geq {{ latex(b*x) }}$, {en}`or`{fr}`ou` $x \leq {{ latex(Rational(a,b)) }}$.
|
| 59 |
+
\begin{equation*}
|
| 60 |
+
\boxed{
|
| 61 |
+
\text{{en}`Domain:`{fr}`Domaine :` }{{ domaine.print() }}}.
|
| 62 |
+
\end{equation*}
|
| 63 |
+
|
| 64 |
+
::::
|
| 65 |
+
|
| 66 |
+
::::{weightDistribution}
|
| 67 |
+
:reasoning: 30
|
| 68 |
+
:logic: 25
|
| 69 |
+
:abstraction: 30
|
| 70 |
+
:calculation: 15
|
| 71 |
+
::::
|
| 72 |
+
:::::
|
| 73 |
+
`````
|
app/knowledge/fewshots/declinaisons/{en}`Solution sets for homogeneous and nonhomogeneous systems in $_mathbb{R}^3$`{fr}`Ensembles des solutions pour des systèmes homogènes et inhomogènes de $_mathbb{R}^3$` - MCQ.md
ADDED
|
@@ -0,0 +1,373 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
`````{exercise}
|
| 2 |
+
:id: 27512f8b-6fb2-11f1-a8a1-0ed8d3b012a9
|
| 3 |
+
:originalExerciseId: 9cad7853-dc83-4e8f-b06c-dcfc516b66fb
|
| 4 |
+
:title: {en}`Solution sets for homogeneous and nonhomogeneous systems in $\mathbb{R}^3$`{fr}`Ensembles des solutions pour des systèmes homogènes et inhomogènes de $\mathbb{R}^3$` - MCQ
|
| 5 |
+
:modules: Linear_Algebra_Pyx, Linear_Algebra_Trush
|
| 6 |
+
:recommendedExecutionTime: 30
|
| 7 |
+
:level: Elementary
|
| 8 |
+
:chap: section_2_4_linalg_Trush
|
| 9 |
+
:involvedConcepts:
|
| 10 |
+
:originalSource: 7.4 Ronan
|
| 11 |
+
:visibility: All
|
| 12 |
+
:variations:
|
| 13 |
+
:comment:
|
| 14 |
+
|
| 15 |
+
```{python}
|
| 16 |
+
|
| 17 |
+
# Code Python : Ecrivez ci-dessous votre code Python
|
| 18 |
+
from __future__ import division
|
| 19 |
+
from pyxiscience.Mes_fctions_d_alg_lineaire_bis import *
|
| 20 |
+
from sympy import *
|
| 21 |
+
import random as rd
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
#### Question 1
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
n = 3
|
| 30 |
+
p = 3
|
| 31 |
+
|
| 32 |
+
lam=rd.randint(-9,9)
|
| 33 |
+
nu=rd.randint(-9,9)
|
| 34 |
+
bnn=rd.randint(-9,9)
|
| 35 |
+
while lam==0 or nu==0 or bnn==0:
|
| 36 |
+
lam=rd.randint(-9,9)
|
| 37 |
+
nu=rd.randint(-9,9)
|
| 38 |
+
bnn=rd.randint(-9,9)
|
| 39 |
+
|
| 40 |
+
A= randmatrixrect(n,p,-9,9)
|
| 41 |
+
B= randmatrixrect(n,1,-9,9)
|
| 42 |
+
|
| 43 |
+
A[0,0]=1
|
| 44 |
+
A[0,1]=lam
|
| 45 |
+
A[0,2]=nu
|
| 46 |
+
|
| 47 |
+
for i in range(1,n):
|
| 48 |
+
for j in range(0,p):
|
| 49 |
+
A[i,j]=0
|
| 50 |
+
B[i]=0
|
| 51 |
+
|
| 52 |
+
B[0]=0
|
| 53 |
+
x='x'
|
| 54 |
+
vect_x=Matrix([Symbol(x+'_1')])
|
| 55 |
+
for i in range(p-1):
|
| 56 |
+
vect_x=vect_x.row_join(Matrix([Symbol(x+'_'+str(i+2))]))
|
| 57 |
+
expr=myst(r""" """,globals(),locals())
|
| 58 |
+
|
| 59 |
+
if A[0, :].is_zero_matrix:
|
| 60 |
+
expr += "0"
|
| 61 |
+
# Gère l'affichage du premier terme non nul sans le '+' devant
|
| 62 |
+
if A[0, 0] != 0:
|
| 63 |
+
expr+=pxsl_ax(A[0,0],vect_x[0], frac = frac)
|
| 64 |
+
sign="+"
|
| 65 |
+
else:
|
| 66 |
+
sign=""
|
| 67 |
+
for j in range(1,p):
|
| 68 |
+
if A[0,j]!=0:
|
| 69 |
+
expr+=pxsl_ax(A[0,j],vect_x[j],sign, frac = frac)
|
| 70 |
+
sign="+"
|
| 71 |
+
|
| 72 |
+
rhs = myst(r"""{{ B[0].p }}/{{ B[0].q }}""", globals(), locals()) if (isinstance(B[i], Rational) and B[0].q != 1 and not frac) else latex(B[0])
|
| 73 |
+
|
| 74 |
+
expr+=myst(r""" ={{ rhs }}""",globals(),locals())
|
| 75 |
+
|
| 76 |
+
sys=expr
|
| 77 |
+
|
| 78 |
+
|
| 79 |
+
Alat=pxsl_matrix(A)
|
| 80 |
+
Blat=pxsl_matrix(B)
|
| 81 |
+
augmentmat1=pxsl_double_matrix(A,B,opt='ext')
|
| 82 |
+
|
| 83 |
+
|
| 84 |
+
|
| 85 |
+
|
| 86 |
+
soluce1=pxs_gauss_jordan(A.copy(), B.copy(), method = "mat", view = "ext")
|
| 87 |
+
|
| 88 |
+
Ared1=pxsl_double_matrix(soluce1["A"],soluce1["B"],opt='ext')
|
| 89 |
+
|
| 90 |
+
|
| 91 |
+
|
| 92 |
+
#### Question 2
|
| 93 |
+
|
| 94 |
+
B[0]=bnn
|
| 95 |
+
|
| 96 |
+
x='x'
|
| 97 |
+
vect_x=Matrix([Symbol(x+'_1')])
|
| 98 |
+
for i in range(p-1):
|
| 99 |
+
vect_x=vect_x.row_join(Matrix([Symbol(x+'_'+str(i+2))]))
|
| 100 |
+
expr=myst(r""" """,globals(),locals())
|
| 101 |
+
|
| 102 |
+
if A[0, :].is_zero_matrix:
|
| 103 |
+
expr += "0"
|
| 104 |
+
# Gère l'affichage du premier terme non nul sans le '+' devant
|
| 105 |
+
if A[0, 0] != 0:
|
| 106 |
+
expr+=pxsl_ax(A[0,0],vect_x[0], frac = frac)
|
| 107 |
+
sign="+"
|
| 108 |
+
else:
|
| 109 |
+
sign=""
|
| 110 |
+
for j in range(1,p):
|
| 111 |
+
if A[0,j]!=0:
|
| 112 |
+
expr+=pxsl_ax(A[0,j],vect_x[j],sign, frac = frac)
|
| 113 |
+
sign="+"
|
| 114 |
+
|
| 115 |
+
rhs = myst(r"""{{ B[0].p }}/{{ B[0].q }}""", globals(), locals()) if (isinstance(B[i], Rational) and B[0].q != 1 and not frac) else latex(B[0])
|
| 116 |
+
|
| 117 |
+
expr+=myst(r""" ={{ rhs }}""",globals(),locals())
|
| 118 |
+
|
| 119 |
+
sys2=expr
|
| 120 |
+
|
| 121 |
+
|
| 122 |
+
Alat=pxsl_matrix(A)
|
| 123 |
+
Blat=pxsl_matrix(B)
|
| 124 |
+
augmentmat2=pxsl_double_matrix(A,B,opt='ext')
|
| 125 |
+
|
| 126 |
+
|
| 127 |
+
|
| 128 |
+
|
| 129 |
+
soluce2=pxs_gauss_jordan(A.copy(), B.copy(), method = "mat", view = "ext")
|
| 130 |
+
|
| 131 |
+
Ared2=pxsl_double_matrix(soluce2["A"],soluce2["B"],opt='ext')
|
| 132 |
+
|
| 133 |
+
|
| 134 |
+
|
| 135 |
+
```
|
| 136 |
+
|
| 137 |
+
|
| 138 |
+
|
| 139 |
+
|
| 140 |
+
|
| 141 |
+
:::::{question}
|
| 142 |
+
:questionType: MCQ
|
| 143 |
+
:questionId: 1
|
| 144 |
+
:questionIndex: 1
|
| 145 |
+
|
| 146 |
+
::::{questionStatement}
|
| 147 |
+
{en}`Which of the following best describes the solution set of the equation`{fr}`Laquelle des propositions suivantes décrit le mieux l'ensemble des solutions de l'équation`
|
| 148 |
+
|
| 149 |
+
\begin{equation}\label{hom}
|
| 150 |
+
{{ sys }}.
|
| 151 |
+
\end{equation}
|
| 152 |
+
::::
|
| 153 |
+
|
| 154 |
+
::::{questionHint}
|
| 155 |
+
|
| 156 |
+
::::
|
| 157 |
+
|
| 158 |
+
::::{mcqAnswer}
|
| 159 |
+
:isRightAnswer: true
|
| 160 |
+
{en}`A plane through the origin`{fr}`Un plan passant par l'origine`
|
| 161 |
+
::::
|
| 162 |
+
|
| 163 |
+
::::{mcqAnswer}
|
| 164 |
+
:isRightAnswer: false
|
| 165 |
+
{en}`A line through the origin`{fr}`Une droite passant par l'origine`
|
| 166 |
+
::::
|
| 167 |
+
|
| 168 |
+
::::{mcqAnswer}
|
| 169 |
+
:isRightAnswer: false
|
| 170 |
+
{en}`The whole space $\mathbb{R}^3$`{fr}`L'espace $\mathbb{R}^3$ tout entier`
|
| 171 |
+
::::
|
| 172 |
+
|
| 173 |
+
::::{mcqAnswer}
|
| 174 |
+
:isRightAnswer: false
|
| 175 |
+
{en}`Only the origin $\{0\}$`{fr}`Uniquement l'origine $\{0\}$`
|
| 176 |
+
::::
|
| 177 |
+
|
| 178 |
+
::::{mcqAnswer}
|
| 179 |
+
:isRightAnswer: false
|
| 180 |
+
{fr}`Aucune de ces réponses n'est correcte` {en}`None of these answers are correct`
|
| 181 |
+
::::
|
| 182 |
+
|
| 183 |
+
::::{detailedSolution}
|
| 184 |
+
|
| 185 |
+
{en}`The equation`{fr}`L'équation` ${{ sys }}$ {en}`may be written with an augmented matrix as`{fr}`peut s'écrire sous la forme de matrice augmentée comme`
|
| 186 |
+
\begin{equation*}
|
| 187 |
+
{{ Ared1 }},
|
| 188 |
+
\end{equation*}
|
| 189 |
+
|
| 190 |
+
|
| 191 |
+
{en}`which is equivalent to the system`{fr}`ce qui est équivalent au système`
|
| 192 |
+
\begin{equation*}
|
| 193 |
+
{{ soluce1["sys"] }}.
|
| 194 |
+
\end{equation*}
|
| 195 |
+
|
| 196 |
+
{en}`The set of solutions of the equation`{fr}`L'ensemble des solution de l'équation` ${{ sys }}$ {en}`is then`{fr}`est donc`
|
| 197 |
+
\begin{equation*}
|
| 198 |
+
\boxed{
|
| 199 |
+
{{ soluce1["span"] }}.
|
| 200 |
+
}
|
| 201 |
+
\end{equation*}
|
| 202 |
+
{en}`Geometrically, the solution set forms a plane in`{fr}`Géométriquement, l'ensemble des solutions forme un plan de` $\mathbb{R}^3.$
|
| 203 |
+
::::
|
| 204 |
+
|
| 205 |
+
::::{weightDistribution}
|
| 206 |
+
:
|
| 207 |
+
::::
|
| 208 |
+
:::::
|
| 209 |
+
|
| 210 |
+
|
| 211 |
+
|
| 212 |
+
:::::{question}
|
| 213 |
+
:questionType: MCQ
|
| 214 |
+
:questionId: 2
|
| 215 |
+
:questionIndex: 2
|
| 216 |
+
|
| 217 |
+
::::{questionStatement}
|
| 218 |
+
{en}`Which of the following best describes the solution set of the equation`{fr}`Laquelle des propositions suivantes décrit le mieux l'ensemble des solutions de l'équation`
|
| 219 |
+
\begin{equation}\label{inhom}
|
| 220 |
+
{{ sys2 }}.
|
| 221 |
+
\end{equation}
|
| 222 |
+
::::
|
| 223 |
+
|
| 224 |
+
::::{questionHint}
|
| 225 |
+
|
| 226 |
+
::::
|
| 227 |
+
|
| 228 |
+
::::{mcqAnswer}
|
| 229 |
+
:isRightAnswer: true
|
| 230 |
+
{en}`A plane that does not pass through the origin`{fr}`Un plan ne passant pas par l'origine`
|
| 231 |
+
::::
|
| 232 |
+
|
| 233 |
+
::::{mcqAnswer}
|
| 234 |
+
:isRightAnswer: false
|
| 235 |
+
{en}`A plane through the origin`{fr}`Un plan passant par l'origine`
|
| 236 |
+
::::
|
| 237 |
+
|
| 238 |
+
::::{mcqAnswer}
|
| 239 |
+
:isRightAnswer: false
|
| 240 |
+
{en}`A single point`{fr}`Un unique point`
|
| 241 |
+
::::
|
| 242 |
+
|
| 243 |
+
::::{mcqAnswer}
|
| 244 |
+
:isRightAnswer: false
|
| 245 |
+
{en}`The empty set (the equation has no solution)`{fr}`L'ensemble vide (l'équation n'a aucune solution)`
|
| 246 |
+
::::
|
| 247 |
+
|
| 248 |
+
::::{mcqAnswer}
|
| 249 |
+
:isRightAnswer: false
|
| 250 |
+
{fr}`Aucune de ces réponses n'est correcte` {en}`None of these answers are correct`
|
| 251 |
+
::::
|
| 252 |
+
|
| 253 |
+
::::{detailedSolution}
|
| 254 |
+
|
| 255 |
+
{en}`The equation`{fr}`L'équation` ${{ sys2 }}$ {en}`may be written with an augmented matrix as`{fr}`peut s'écrire sous la forme de matrice augmentée comme`
|
| 256 |
+
\begin{equation*}
|
| 257 |
+
{{ Ared2 }},
|
| 258 |
+
\end{equation*}
|
| 259 |
+
|
| 260 |
+
|
| 261 |
+
{en}`which is equivalent to the system`{fr}`ce qui est équivalent au système`
|
| 262 |
+
\begin{equation*}
|
| 263 |
+
{{ soluce2["sys"] }}.
|
| 264 |
+
\end{equation*}
|
| 265 |
+
|
| 266 |
+
{en}`The set of solutions of the equation`{fr}`L'ensemble des solution de l'équation` ${{ sys2 }}$ {en}`is then`{fr}`est donc`
|
| 267 |
+
\begin{equation*}
|
| 268 |
+
\boxed{
|
| 269 |
+
{{ soluce2["span"] }}.
|
| 270 |
+
}
|
| 271 |
+
\end{equation*}
|
| 272 |
+
{en}`Geometrically, the solution set forms a plane in`{fr}`Géométriquement, l'ensemble des solutions forme un plan de` $\mathbb{R}^3.$
|
| 273 |
+
::::
|
| 274 |
+
|
| 275 |
+
::::{weightDistribution}
|
| 276 |
+
:
|
| 277 |
+
::::
|
| 278 |
+
:::::
|
| 279 |
+
|
| 280 |
+
|
| 281 |
+
|
| 282 |
+
:::::{question}
|
| 283 |
+
:questionType: MCQ
|
| 284 |
+
:questionId: 3
|
| 285 |
+
:questionIndex: 3
|
| 286 |
+
|
| 287 |
+
::::{questionStatement}
|
| 288 |
+
{en}`How do the two solution sets compare geometrically?`{fr}`Comment peut-on comparer géométriquement les deux ensembles de solutions ?`
|
| 289 |
+
::::
|
| 290 |
+
|
| 291 |
+
::::{questionHint}
|
| 292 |
+
|
| 293 |
+
::::
|
| 294 |
+
|
| 295 |
+
::::{mcqAnswer}
|
| 296 |
+
:isRightAnswer: true
|
| 297 |
+
{en}`They are parallel planes: the nonhomogeneous one is a translate of the homogeneous one`{fr}`Ce sont des plans parallèles : celui du système inhomogène est un translaté de celui du système homogène`
|
| 298 |
+
::::
|
| 299 |
+
|
| 300 |
+
::::{mcqAnswer}
|
| 301 |
+
:isRightAnswer: false
|
| 302 |
+
{en}`They are the same plane`{fr}`Ce sont le même plan`
|
| 303 |
+
::::
|
| 304 |
+
|
| 305 |
+
::::{mcqAnswer}
|
| 306 |
+
:isRightAnswer: false
|
| 307 |
+
{en}`They are two perpendicular planes`{fr}`Ce sont deux plans perpendiculaires`
|
| 308 |
+
::::
|
| 309 |
+
|
| 310 |
+
::::{mcqAnswer}
|
| 311 |
+
:isRightAnswer: false
|
| 312 |
+
{en}`They intersect along a line`{fr}`Ils se coupent selon une droite`
|
| 313 |
+
::::
|
| 314 |
+
|
| 315 |
+
::::{mcqAnswer}
|
| 316 |
+
:isRightAnswer: false
|
| 317 |
+
{fr}`Aucune de ces réponses n'est correcte` {en}`None of these answers are correct`
|
| 318 |
+
::::
|
| 319 |
+
|
| 320 |
+
::::{detailedSolution}
|
| 321 |
+
|
| 322 |
+
{en}`Geometrically, the solution set of the nonhomogeneous equation` [](#inhom) {en}`is a translation by the vector` ${{ Blat }}$ {en}`of the solution set of the homogeneous equation` [](#hom).
|
| 323 |
+
|
| 324 |
+
|
| 325 |
+
```{python}
|
| 326 |
+
|
| 327 |
+
# Code Python : Ecrivez ci-dessous votre code Python
|
| 328 |
+
|
| 329 |
+
|
| 330 |
+
import numpy as np
|
| 331 |
+
import matplotlib.pyplot as plt
|
| 332 |
+
from mpl_toolkits.mplot3d import Axes3D
|
| 333 |
+
|
| 334 |
+
# Création de la figure et de l'axe 3D
|
| 335 |
+
fig = plt.figure(figsize=(10, 8))
|
| 336 |
+
ax = fig.add_subplot(111, projection='3d')
|
| 337 |
+
|
| 338 |
+
# Définition des limites pour x et y
|
| 339 |
+
x = np.linspace(-4, 4, 10)
|
| 340 |
+
y = np.linspace(-4, 4, 10)
|
| 341 |
+
X, Y = np.meshgrid(x, y)
|
| 342 |
+
|
| 343 |
+
# Équation du premier plan : z = a*x + b*y + c
|
| 344 |
+
a, b, c = -1/nu, -lam/nu, 0
|
| 345 |
+
Z1 = a*X + b*Y + c
|
| 346 |
+
|
| 347 |
+
# Équation du deuxième plan parallèle : z = a*x + b*y + d (d ≠ c)
|
| 348 |
+
d = -bnn/nu
|
| 349 |
+
Z2 = a*X + b*Y + d
|
| 350 |
+
|
| 351 |
+
# Tracé des deux plans
|
| 352 |
+
ax.plot_surface(X, Y, Z1, alpha=0.5, color='blue', label='Plane (1)')
|
| 353 |
+
ax.plot_surface(X, Y, Z2, alpha=0.5, color='red', label='Plane (2)')
|
| 354 |
+
|
| 355 |
+
# Ajout des labels et titre
|
| 356 |
+
ax.set_xlabel('$x_1$')
|
| 357 |
+
ax.set_ylabel('$x_2$')
|
| 358 |
+
ax.set_zlabel('$x_3$')
|
| 359 |
+
ax.set_title('The two planes')
|
| 360 |
+
|
| 361 |
+
# Affichage de la légende
|
| 362 |
+
ax.legend()
|
| 363 |
+
|
| 364 |
+
plt.show()
|
| 365 |
+
|
| 366 |
+
```
|
| 367 |
+
::::
|
| 368 |
+
|
| 369 |
+
::::{weightDistribution}
|
| 370 |
+
:
|
| 371 |
+
::::
|
| 372 |
+
:::::
|
| 373 |
+
`````
|
app/knowledge/fewshots/declinaisons/{en}`The set of solutions of a homogeneous system`{fr}`Ensemble des solutions d'un système homogène` - MCQ.md
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
`````{exercise}
|
| 2 |
+
:id: e2c3b203-6fb1-11f1-a8a1-0ed8d3b012a9
|
| 3 |
+
:originalExerciseId: e5717816-752a-4abf-9afc-55df5774e4e0
|
| 4 |
+
:title: {en}`The set of solutions of a homogeneous system`{fr}`Ensemble des solutions d'un système homogène` - MCQ
|
| 5 |
+
:modules:
|
| 6 |
+
:recommendedExecutionTime: 30
|
| 7 |
+
:level: Elementary
|
| 8 |
+
:chap: section_2_4_linalg_Trush
|
| 9 |
+
:involvedConcepts:
|
| 10 |
+
:originalSource: 7.1 Ronan
|
| 11 |
+
:visibility: All
|
| 12 |
+
:variations:
|
| 13 |
+
:comment:
|
| 14 |
+
|
| 15 |
+
````{python}
|
| 16 |
+
# Code Python : Ecrivez ci-dessous votre code Python
|
| 17 |
+
from __future__ import division
|
| 18 |
+
from pyxiscience.Mes_fctions_d_alg_lineaire_bis import *
|
| 19 |
+
from sympy import *
|
| 20 |
+
import random as rd
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
systems = []
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
n = rd.randint(2,3)
|
| 29 |
+
p = rd.randint(n+1,n+2)
|
| 30 |
+
|
| 31 |
+
nb_pivots = rd.randint(2, min(n, p))
|
| 32 |
+
pivots = tuple(rd.sample([i for i in range(1, min(n, p) + 1)], nb_pivots))
|
| 33 |
+
A, B = pxs_repeat_generate_sys(n = n, p = p, M = tuple(pivots), N = Matrix([rd.randint(1, 5) for _ in range(n-1)] + [0]), backup = zeros(3, 3))
|
| 34 |
+
if A == zeros(3, 3):
|
| 35 |
+
A = Matrix([[1, -5, -1, 0], [-2, 10, 1, -4], [0, 0, -1, -4]])
|
| 36 |
+
B = Matrix([[0, -3, -3]]).T
|
| 37 |
+
|
| 38 |
+
A, B = pxs_break_all_colinear_rows(A, B, max_iter=5)
|
| 39 |
+
systems.append((A, B))
|
| 40 |
+
|
| 41 |
+
B=0*B
|
| 42 |
+
Blat=pxsl_matrix(B)
|
| 43 |
+
Alat=pxsl_matrix(A)
|
| 44 |
+
|
| 45 |
+
augmentmat=pxsl_double_matrix(A,B,opt='ext')
|
| 46 |
+
|
| 47 |
+
sys = pxsl_system_lin(A, B)
|
| 48 |
+
|
| 49 |
+
soluce=pxs_gauss_jordan(A.copy(), B.copy(), method = "mat", view = "ext")
|
| 50 |
+
|
| 51 |
+
Ared=pxsl_double_matrix(soluce["A"],soluce["B"],opt='ext')
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
````
|
| 55 |
+
|
| 56 |
+
:::::{question}
|
| 57 |
+
:questionType: MCQ
|
| 58 |
+
:questionId: 1
|
| 59 |
+
:questionIndex: 1
|
| 60 |
+
|
| 61 |
+
::::{questionStatement}
|
| 62 |
+
{en}`Which of the following best describes the solution set of the equation`{fr}`Laquelle des propositions suivantes décrit le mieux l'ensemble des solutions de l'équation` $Ax=0$ {en}`where`{fr}`où`
|
| 63 |
+
\begin{equation*}
|
| 64 |
+
A={{Alat}}?
|
| 65 |
+
\end{equation*}
|
| 66 |
+
::::
|
| 67 |
+
|
| 68 |
+
::::{questionHint}
|
| 69 |
+
|
| 70 |
+
::::
|
| 71 |
+
|
| 72 |
+
::::{mcqAnswer}
|
| 73 |
+
:isRightAnswer: true
|
| 74 |
+
{en}`An infinite set of vectors forming a subspace through the origin (the system has infinitely many solutions)`{fr}`Un ensemble infini de vecteurs formant un sous-espace vectoriel passant par l'origine (le système a une infinité de solutions)`
|
| 75 |
+
::::
|
| 76 |
+
|
| 77 |
+
::::{mcqAnswer}
|
| 78 |
+
:isRightAnswer: false
|
| 79 |
+
{en}`Only the trivial solution $x=0$`{fr}`Uniquement la solution triviale $x=0$`
|
| 80 |
+
::::
|
| 81 |
+
|
| 82 |
+
::::{mcqAnswer}
|
| 83 |
+
:isRightAnswer: false
|
| 84 |
+
{en}`The empty set (the system has no solution)`{fr}`L'ensemble vide (le système n'a aucune solution)`
|
| 85 |
+
::::
|
| 86 |
+
|
| 87 |
+
::::{mcqAnswer}
|
| 88 |
+
:isRightAnswer: false
|
| 89 |
+
{en}`A single nonzero vector`{fr}`Un unique vecteur non nul`
|
| 90 |
+
::::
|
| 91 |
+
|
| 92 |
+
::::{mcqAnswer}
|
| 93 |
+
:isRightAnswer: false
|
| 94 |
+
{fr}`Aucune de ces réponses n'est correcte` {en}`None of these answers are correct`
|
| 95 |
+
::::
|
| 96 |
+
|
| 97 |
+
::::{detailedSolution}
|
| 98 |
+
{en}`The equation `{fr}`L'équation ` $Ax=0$ {en}`may be written with an augmented matrix as `{fr}`peut s'écrire sous la forme de matrice augmentée comme `
|
| 99 |
+
\begin{equation*}
|
| 100 |
+
{{augmentmat}}
|
| 101 |
+
\end{equation*}
|
| 102 |
+
{en}`The Row Reduction Echelon Form of this matrix is`{fr}`La forme échelonnée réduite de cette matrice est `
|
| 103 |
+
\begin{equation*}
|
| 104 |
+
{{Ared}}
|
| 105 |
+
\end{equation*}
|
| 106 |
+
|
| 107 |
+
:::{dropdown} {en}`See more details`{fr}`Voir plus de détails`
|
| 108 |
+
{en}`We may apply the Row Reduction Algorithm to reduce the augmented matrix: `{fr}`Nous pouvons appliquer l'algorithme de Gauss pour réduire la matrice augmentée :`
|
| 109 |
+
{{soluce["resol"]}}
|
| 110 |
+
:::
|
| 111 |
+
|
| 112 |
+
{en}`which is equivalent to the system `{fr}`ce qui est équivalent au système`
|
| 113 |
+
\begin{equation*}
|
| 114 |
+
{{soluce["sys"]}}.
|
| 115 |
+
\end{equation*}
|
| 116 |
+
|
| 117 |
+
{en}`The set of solutions of the equation `{fr}`L'ensemble des solution de l'équation ` $Ax=0$ {en}` is then `{fr}` est donc `
|
| 118 |
+
\begin{equation*}
|
| 119 |
+
\boxed{
|
| 120 |
+
{{soluce["span"]}}.
|
| 121 |
+
|
| 122 |
+
}
|
| 123 |
+
\end{equation*}
|
| 124 |
+
::::
|
| 125 |
+
|
| 126 |
+
::::{weightDistribution}
|
| 127 |
+
:reasoning: 25
|
| 128 |
+
:logic: 25
|
| 129 |
+
:abstraction: 25
|
| 130 |
+
:calculation: 25
|
| 131 |
+
::::
|
| 132 |
+
:::::
|
| 133 |
+
`````
|
app/knowledge/fewshots/declinaisons/{en}`The set of solutions of an inhomogeneous system`{fr}`Ensemble des solutions d'un système inhomogène` - MCQ.md
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
`````{exercise}
|
| 2 |
+
:id: f48b19b6-6fb1-11f1-a8a1-0ed8d3b012a9
|
| 3 |
+
:originalExerciseId: d8e6061b-52be-424b-8dc6-45548d0c00ce
|
| 4 |
+
:title: {en}`The set of solutions of an inhomogeneous system`{fr}`Ensemble des solutions d'un système inhomogène` - MCQ
|
| 5 |
+
:modules:
|
| 6 |
+
:recommendedExecutionTime: 30
|
| 7 |
+
:level: Elementary
|
| 8 |
+
:chap: section_2_4_linalg_Trush
|
| 9 |
+
:involvedConcepts:
|
| 10 |
+
:originalSource: 7.1 Ronan
|
| 11 |
+
:visibility: All
|
| 12 |
+
:variations:
|
| 13 |
+
:comment:
|
| 14 |
+
|
| 15 |
+
````{python}
|
| 16 |
+
# Code Python : Ecrivez ci-dessous votre code Python
|
| 17 |
+
from __future__ import division
|
| 18 |
+
from pyxiscience.Mes_fctions_d_alg_lineaire_bis import *
|
| 19 |
+
from sympy import *
|
| 20 |
+
import random as rd
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
systems = []
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
n = rd.randint(2,3)
|
| 29 |
+
p = rd.randint(n+1,n+2)
|
| 30 |
+
|
| 31 |
+
nb_pivots = rd.randint(2, min(n, p))
|
| 32 |
+
pivots = tuple(rd.sample([i for i in range(1, min(n, p) + 1)], nb_pivots))
|
| 33 |
+
A, B = pxs_repeat_generate_sys(n = n, p = p, M = tuple(pivots), N = Matrix([rd.randint(1, 5) for _ in range(n-1)] + [0]), backup = zeros(3, 3))
|
| 34 |
+
if A == zeros(3, 3):
|
| 35 |
+
A = Matrix([[1, -5, -1, 0], [-2, 10, 1, -4], [0, 0, -1, -4]])
|
| 36 |
+
B = Matrix([[0, -3, -3]]).T
|
| 37 |
+
|
| 38 |
+
A, B = pxs_break_all_colinear_rows(A, B, max_iter=5)
|
| 39 |
+
systems.append((A, B))
|
| 40 |
+
|
| 41 |
+
Blat=pxsl_matrix(B)
|
| 42 |
+
Alat=pxsl_matrix(A)
|
| 43 |
+
|
| 44 |
+
augmentmat=pxsl_double_matrix(A,B,opt='ext')
|
| 45 |
+
|
| 46 |
+
sys = pxsl_system_lin(A, B)
|
| 47 |
+
|
| 48 |
+
soluce=pxs_gauss_jordan(A.copy(), B.copy(), method = "mat", view = "ext")
|
| 49 |
+
|
| 50 |
+
Ared=pxsl_double_matrix(soluce["A"],soluce["B"],opt='ext')
|
| 51 |
+
|
| 52 |
+
|
| 53 |
+
````
|
| 54 |
+
|
| 55 |
+
:::::{question}
|
| 56 |
+
:questionType: MCQ
|
| 57 |
+
:questionId: 1
|
| 58 |
+
:questionIndex: 1
|
| 59 |
+
|
| 60 |
+
::::{questionStatement}
|
| 61 |
+
{en}`Which of the following best describes the solution set of the equation`{fr}`Laquelle des propositions suivantes décrit le mieux l'ensemble des solutions de l'équation` $Ax=b$ {en}`where`{fr}`où`
|
| 62 |
+
\begin{equation*}
|
| 63 |
+
A={{Alat}} \quad \textrm{{en}` and `{fr}` et `} \quad b={{Blat}}?
|
| 64 |
+
\end{equation*}
|
| 65 |
+
::::
|
| 66 |
+
|
| 67 |
+
::::{questionHint}
|
| 68 |
+
|
| 69 |
+
::::
|
| 70 |
+
|
| 71 |
+
::::{mcqAnswer}
|
| 72 |
+
:isRightAnswer: true
|
| 73 |
+
{en}`An infinite affine set: a particular solution plus all the solutions of $Ax=0$ (in general it does not pass through the origin)`{fr}`Un ensemble affine infini : une solution particulière à laquelle on ajoute toutes les solutions de $Ax=0$ (en général il ne passe pas par l'origine)`
|
| 74 |
+
::::
|
| 75 |
+
|
| 76 |
+
::::{mcqAnswer}
|
| 77 |
+
:isRightAnswer: false
|
| 78 |
+
{en}`A subspace through the origin`{fr}`Un sous-espace vectoriel passant par l'origine`
|
| 79 |
+
::::
|
| 80 |
+
|
| 81 |
+
::::{mcqAnswer}
|
| 82 |
+
:isRightAnswer: false
|
| 83 |
+
{en}`A single unique solution`{fr}`Une unique solution`
|
| 84 |
+
::::
|
| 85 |
+
|
| 86 |
+
::::{mcqAnswer}
|
| 87 |
+
:isRightAnswer: false
|
| 88 |
+
{en}`The empty set (the system has no solution)`{fr}`L'ensemble vide (le système n'a aucune solution)`
|
| 89 |
+
::::
|
| 90 |
+
|
| 91 |
+
::::{mcqAnswer}
|
| 92 |
+
:isRightAnswer: false
|
| 93 |
+
{fr}`Aucune de ces réponses n'est correcte` {en}`None of these answers are correct`
|
| 94 |
+
::::
|
| 95 |
+
|
| 96 |
+
::::{detailedSolution}
|
| 97 |
+
{en}`The equation `{fr}`L'équation ` $Ax=b$ {en}`may be written with an augmented matrix as `{fr}`peut s'écrire sous la forme de matrice augmentée comme `
|
| 98 |
+
\begin{equation*}
|
| 99 |
+
{{augmentmat}}
|
| 100 |
+
\end{equation*}
|
| 101 |
+
{en}`The Row Reduction Echelon Form of this matrix is`{fr}`La forme échelonnée réduite de cette matrice est `
|
| 102 |
+
\begin{equation*}
|
| 103 |
+
{{Ared}}
|
| 104 |
+
\end{equation*}
|
| 105 |
+
|
| 106 |
+
:::{dropdown} {en}`See more details`{fr}`Voir plus de détails`
|
| 107 |
+
{en}`We may apply the Row Reduction Algorithm to reduce the augmented matrix: `{fr}`Nous pouvons appliquer l'algorithme de Gauss pour réduire la matrice augmentée :`
|
| 108 |
+
{{soluce["resol"]}}
|
| 109 |
+
:::
|
| 110 |
+
|
| 111 |
+
{en}`which is equivalent to the system `{fr}`ce qui est équivalent au système`
|
| 112 |
+
\begin{equation*}
|
| 113 |
+
{{soluce["sys"]}}.
|
| 114 |
+
\end{equation*}
|
| 115 |
+
|
| 116 |
+
{en}`The set of solutions of the equation `{fr}`L'ensemble des solution de l'équation ` $Ax=b$ {en}` is then `{fr}` est donc `
|
| 117 |
+
\begin{equation*}
|
| 118 |
+
\boxed{
|
| 119 |
+
{{soluce["span"]}}.
|
| 120 |
+
}
|
| 121 |
+
\end{equation*}
|
| 122 |
+
::::
|
| 123 |
+
|
| 124 |
+
::::{weightDistribution}
|
| 125 |
+
:reasoning: 25
|
| 126 |
+
:logic: 25
|
| 127 |
+
:abstraction: 25
|
| 128 |
+
:calculation: 25
|
| 129 |
+
::::
|
| 130 |
+
:::::
|
| 131 |
+
`````
|
app/knowledge/fewshots/declinaisons/{fr}`Amortissement dégressif d'un équipement`{en}`Declining Balance Depreciation of Equipment`.md
ADDED
|
@@ -0,0 +1,189 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
`````{exercise}
|
| 2 |
+
:title: {fr}`Amortissement dégressif d'un équipement`{en}`Declining Balance Depreciation of Equipment`
|
| 3 |
+
:modules:
|
| 4 |
+
:recommendedExecutionTime: 10
|
| 5 |
+
:level: Elementary
|
| 6 |
+
:chap: chap_expLogFunctions_exponentialFunctions_ESCP
|
| 7 |
+
:involvedConcepts:
|
| 8 |
+
:originalSource:
|
| 9 |
+
:visibility: All
|
| 10 |
+
:variations:
|
| 11 |
+
:comment: Appliqué (économie & gestion) : valeur résiduelle V(n)=V0·(1−d)^n d'un actif, décroissance exponentielle (base 0,8∈(0,1)) et interprétation.
|
| 12 |
+
:id: c70b202b-74d9-11f1-a8a1-0ed8d3b012a9
|
| 13 |
+
:originalExerciseId: 773a7c51-6597-11f1-a8a1-0ed8d3b012a9
|
| 14 |
+
|
| 15 |
+
````{python}
|
| 16 |
+
import random as rd
|
| 17 |
+
from fractions import Fraction
|
| 18 |
+
from decimal import Decimal, ROUND_HALF_UP
|
| 19 |
+
|
| 20 |
+
def _grp(ip):
|
| 21 |
+
neg = ip.startswith('-'); ip = ip.lstrip('-'); g = []
|
| 22 |
+
while len(ip) > 3:
|
| 23 |
+
g.insert(0, ip[-3:]); ip = ip[:-3]
|
| 24 |
+
g.insert(0, ip)
|
| 25 |
+
return ('-' if neg else '') + '\\,'.join(g)
|
| 26 |
+
|
| 27 |
+
def money_fr(v, dec=2):
|
| 28 |
+
f = Fraction(v)
|
| 29 |
+
d = (Decimal(f.numerator) / Decimal(f.denominator)).quantize(Decimal(1).scaleb(-dec), rounding=ROUND_HALF_UP)
|
| 30 |
+
s = format(abs(d), 'f'); ip, _, fp = s.partition('.')
|
| 31 |
+
return ('-' if d < 0 else '') + _grp(ip) + (('{,}' + fp) if dec > 0 else '')
|
| 32 |
+
|
| 33 |
+
def num_fr(v, maxdec=5):
|
| 34 |
+
f = Fraction(v)
|
| 35 |
+
d = (Decimal(f.numerator) / Decimal(f.denominator)).quantize(Decimal(1).scaleb(-maxdec), rounding=ROUND_HALF_UP)
|
| 36 |
+
s = format(abs(d), 'f'); ip, _, fp = s.partition('.'); fp = fp.rstrip('0')
|
| 37 |
+
return ('-' if d < 0 else '') + _grp(ip) + (('{,}' + fp) if fp else '')
|
| 38 |
+
|
| 39 |
+
# Construction déterministe, arithmétique exacte
|
| 40 |
+
V0 = rd.choice([5000, 8000, 10000, 12000, 15000, 18000, 20000])
|
| 41 |
+
taux = rd.choice([Fraction(7, 10), Fraction(3, 4), Fraction(4, 5), Fraction(17, 20), Fraction(9, 10)])
|
| 42 |
+
|
| 43 |
+
V1val = V0 * taux
|
| 44 |
+
V2val = V0 * taux**2
|
| 45 |
+
V3val = V0 * taux**3
|
| 46 |
+
n5 = rd.randint(4, 7)
|
| 47 |
+
V5val = V0 * taux**n5
|
| 48 |
+
pourcentageRestant = taux**n5 * 100
|
| 49 |
+
pourcentagePerdu = 100 - pourcentageRestant
|
| 50 |
+
|
| 51 |
+
# Rendus
|
| 52 |
+
V0Aff = money_fr(V0, 0)
|
| 53 |
+
V1Aff = money_fr(V1val, 2)
|
| 54 |
+
V2Aff = money_fr(V2val, 2)
|
| 55 |
+
V3Aff = money_fr(V3val, 2)
|
| 56 |
+
V5valAff = money_fr(V5val, 2)
|
| 57 |
+
tauxAff = num_fr(taux, 2)
|
| 58 |
+
taux2Aff = num_fr(taux**2, 4)
|
| 59 |
+
taux3Aff = num_fr(taux**3, 5)
|
| 60 |
+
n5Aff = str(n5)
|
| 61 |
+
tauxPuissanceN5Aff = num_fr(taux**n5, 5)
|
| 62 |
+
pourcentageRestantAffQ3 = num_fr(pourcentageRestant, 2)
|
| 63 |
+
pourcentagePerduAffQ3 = num_fr(pourcentagePerdu, 2)
|
| 64 |
+
|
| 65 |
+
# === Ajouts conversion FGQ ===
|
| 66 |
+
def _solnum(s):
|
| 67 |
+
return s.replace("\\,", "").replace("{,}", ".")
|
| 68 |
+
V0Sol = _solnum(V0Aff)
|
| 69 |
+
V1Sol = _solnum(V1Aff)
|
| 70 |
+
V2Sol = _solnum(V2Aff)
|
| 71 |
+
V3Sol = _solnum(V3Aff)
|
| 72 |
+
V5Sol = _solnum(V5valAff)
|
| 73 |
+
globals()
|
| 74 |
+
````
|
| 75 |
+
|
| 76 |
+
:::::{question}
|
| 77 |
+
:questionType: FGQ
|
| 78 |
+
:questionId: 0
|
| 79 |
+
:questionIndex: 0
|
| 80 |
+
:solution: [["ord","${{ V0Sol }}$","${{ V1Sol }}$","${{ V2Sol }}$","${{ V3Sol }}$"],["0","0","0","0"]]
|
| 81 |
+
|
| 82 |
+
::::{questionStatement}
|
| 83 |
+
{fr}`Une entreprise acquiert un serveur pour`{en}`A company acquires a server for` ${{ V0Aff }}$ €. {fr}`Sa valeur résiduelle après`{en}`Its residual value after` $n$ {fr}`années est`{en}`years is` $V(n)={{ V0Aff }}\times({{ tauxAff }})^{n}$. {fr}`Calculer`{en}`Calculate` $V(0)$, $V(1)$, $V(2)$ {fr}`et`{en}`and` $V(3)$ {fr}`(valeurs exactes).`{en}`(exact values).`
|
| 84 |
+
|
| 85 |
+
$V(0) =$ {input}`||80` $\quad V(1) =$ {input}`||80` $\quad V(2) =$ {input}`||80` $\quad V(3) =$ {input}`||80`
|
| 86 |
+
::::
|
| 87 |
+
|
| 88 |
+
::::{questionHint}
|
| 89 |
+
{fr}`Appliquer la formule ; on a`{en}`Apply the formula; we have` $({{ tauxAff }})^{2}={{ taux2Aff }}$ {fr}`et`{en}`and` $({{ tauxAff }})^{3}={{ taux3Aff }}$.
|
| 90 |
+
::::
|
| 91 |
+
|
| 92 |
+
::::{displayedSolution}
|
| 93 |
+
$V(0) = {{ V0Aff }}$ € $\quad V(1) = {{ V1Aff }}$ € $\quad V(2) = {{ V2Aff }}$ € $\quad V(3) = {{ V3Aff }}$ €
|
| 94 |
+
::::
|
| 95 |
+
|
| 96 |
+
::::{detailedSolution}
|
| 97 |
+
{fr}`On applique la formule en augmentant`{en}`We apply the formula by increasing` $n$. {fr}`La base élevée à la puissance`{en}`The base raised to the power` $0$ {fr}`vaut`{en}`equals` $1$, {fr}`donc`{en}`so` $V(0)={{ V0Aff }}$. {fr}`Ensuite :`{en}`Then:`
|
| 98 |
+
|
| 99 |
+
$V(1)={{ V0Aff }}\times{{ tauxAff }}={{ V1Aff }}$, $\quad V(2)={{ V0Aff }}\times{{ taux2Aff }}={{ V2Aff }}$, $\quad V(3)={{ V0Aff }}\times{{ taux3Aff }}={{ V3Aff }}$.
|
| 100 |
+
::::
|
| 101 |
+
|
| 102 |
+
::::{weightDistribution}
|
| 103 |
+
:logic: 10
|
| 104 |
+
:abstraction: 20
|
| 105 |
+
:reasoning: 25
|
| 106 |
+
:calculation: 45
|
| 107 |
+
::::
|
| 108 |
+
:::::
|
| 109 |
+
|
| 110 |
+
:::::{question}
|
| 111 |
+
:questionType: MCQ
|
| 112 |
+
:questionId: 1
|
| 113 |
+
:questionIndex: 1
|
| 114 |
+
|
| 115 |
+
::::{questionStatement}
|
| 116 |
+
{fr}`Déterminer si`{en}`Determine whether` $V$ {fr}`est croissante ou décroissante, et justifier à partir de la base.`{en}`is increasing or decreasing, and justify from the base.`
|
| 117 |
+
::::
|
| 118 |
+
|
| 119 |
+
::::{questionHint}
|
| 120 |
+
{fr}`La base est`{en}`The base is` ${{ tauxAff }}\in(0,1)$.
|
| 121 |
+
::::
|
| 122 |
+
|
| 123 |
+
::::{mcqAnswer}
|
| 124 |
+
:isRightAnswer: true
|
| 125 |
+
{fr}`Strictement décroissante (base dans $(0,1)$)`{en}`Strictly decreasing (base in $(0,1)$)`
|
| 126 |
+
::::
|
| 127 |
+
|
| 128 |
+
::::{mcqAnswer}
|
| 129 |
+
:isRightAnswer: false
|
| 130 |
+
{fr}`Strictement croissante (base $> 1$)`{en}`Strictly increasing (base $> 1$)`
|
| 131 |
+
::::
|
| 132 |
+
|
| 133 |
+
::::{mcqAnswer}
|
| 134 |
+
:isRightAnswer: false
|
| 135 |
+
{fr}`Constante`{en}`Constant`
|
| 136 |
+
::::
|
| 137 |
+
|
| 138 |
+
::::{detailedSolution}
|
| 139 |
+
{fr}`La base est`{en}`The base is` $b={{ tauxAff }}\in(0,1)$, {fr}`donc`{en}`so` $V$ {fr}`est strictement décroissante : la valeur du serveur diminue chaque année. On vérifie :`{en}`is strictly decreasing: the server's value decreases each year. We verify:` ${{ V0Aff }}>{{ V1Aff }}>{{ V2Aff }}>{{ V3Aff }}$.
|
| 140 |
+
::::
|
| 141 |
+
|
| 142 |
+
::::{weightDistribution}
|
| 143 |
+
:logic: 25
|
| 144 |
+
:abstraction: 35
|
| 145 |
+
:reasoning: 30
|
| 146 |
+
:calculation: 10
|
| 147 |
+
::::
|
| 148 |
+
:::::
|
| 149 |
+
|
| 150 |
+
:::::{question}
|
| 151 |
+
:questionType: FGQ
|
| 152 |
+
:questionId: 2
|
| 153 |
+
:questionIndex: 2
|
| 154 |
+
:solution: [["ord","${{ V5Sol }}$"],["0"]]
|
| 155 |
+
|
| 156 |
+
::::{questionStatement}
|
| 157 |
+
{fr}`Calculer`{en}`Calculate` $V({{ n5Aff }})$ {fr}`(on donne`{en}`(given` $({{ tauxAff }})^{ {{ n5Aff }} }={{ tauxPuissanceN5Aff }}$), {fr}`puis interpréter le résultat dans le contexte de l'entreprise.`{en}`then interpret the result in the company's context.`
|
| 158 |
+
|
| 159 |
+
$V({{ n5Aff }}) =$ {input}`||80`
|
| 160 |
+
::::
|
| 161 |
+
|
| 162 |
+
::::{questionHint}
|
| 163 |
+
{fr}`Multiplier`{en}`Multiply` ${{ V0Aff }}$ {fr}`par la valeur fournie, puis comparer à la valeur d'achat.`{en}`by the given value, then compare to the purchase value.`
|
| 164 |
+
::::
|
| 165 |
+
|
| 166 |
+
::::{displayedSolution}
|
| 167 |
+
$V({{ n5Aff }}) = {{ V5valAff }}$ €
|
| 168 |
+
::::
|
| 169 |
+
|
| 170 |
+
::::{detailedSolution}
|
| 171 |
+
{fr}`On évalue`{en}`We evaluate` $V$ {fr}`en`{en}`at` $n={{ n5Aff }}$ :
|
| 172 |
+
|
| 173 |
+
\begin{equation*}
|
| 174 |
+
V({{ n5Aff }}) &= {{ V0Aff }} \times {{ tauxPuissanceN5Aff }} \\
|
| 175 |
+
&= {{ V5valAff }}.
|
| 176 |
+
\end{equation*}
|
| 177 |
+
|
| 178 |
+
{fr}`Après`{en}`After` ${{ n5Aff }}$ {fr}`ans, le serveur ne vaut plus que`{en}`years, the server is only worth` ${{ V5valAff }}$ €, {fr}`soit environ`{en}`which is approximately` ${{ pourcentageRestantAffQ3 }}\,\%$ {fr}`de sa valeur d'achat : l'entreprise a perdu environ`{en}`of its purchase value: the company has lost approximately` ${{ pourcentagePerduAffQ3 }}\,\%$ {fr}`de la valeur de cet actif en`{en}`of the value of this asset in` {{ n5Aff }} {fr}`ans.`{en}`years.`
|
| 179 |
+
::::
|
| 180 |
+
|
| 181 |
+
::::{weightDistribution}
|
| 182 |
+
:logic: 20
|
| 183 |
+
:abstraction: 25
|
| 184 |
+
:reasoning: 35
|
| 185 |
+
:calculation: 20
|
| 186 |
+
::::
|
| 187 |
+
:::::
|
| 188 |
+
|
| 189 |
+
`````
|
app/knowledge/fewshots/declinaisons/{fr}`Capitalisation annuelle contre capitalisation continue`{en}`Annual Compounding versus Continuous Compounding`.md
ADDED
|
@@ -0,0 +1,193 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
`````{exercise}
|
| 2 |
+
:title: {fr}`Capitalisation annuelle contre capitalisation continue`{en}`Annual Compounding versus Continuous Compounding`
|
| 3 |
+
:modules:
|
| 4 |
+
:recommendedExecutionTime: 12
|
| 5 |
+
:level: Elementary
|
| 6 |
+
:chap: chap_expLogFunctions_exponentialFunctions_ESCP
|
| 7 |
+
:involvedConcepts:
|
| 8 |
+
:originalSource:
|
| 9 |
+
:visibility: All
|
| 10 |
+
:variations:
|
| 11 |
+
:comment: Appliqué (économie & gestion) : comparaison d'un placement en capitalisation annuelle S=P(1+r)^n et en capitalisation continue S=P·e^(rn), évaluations numériques (e fourni) et interprétation de l'écart.
|
| 12 |
+
:id: daf335a0-74d9-11f1-a8a1-0ed8d3b012a9
|
| 13 |
+
:originalExerciseId: 5c4c10be-6598-11f1-a8a1-0ed8d3b012a9
|
| 14 |
+
|
| 15 |
+
````{python}
|
| 16 |
+
import random as rd
|
| 17 |
+
import math
|
| 18 |
+
from fractions import Fraction
|
| 19 |
+
from decimal import Decimal, ROUND_HALF_UP
|
| 20 |
+
|
| 21 |
+
def _grp(ip):
|
| 22 |
+
neg = ip.startswith('-'); ip = ip.lstrip('-'); g = []
|
| 23 |
+
while len(ip) > 3:
|
| 24 |
+
g.insert(0, ip[-3:]); ip = ip[:-3]
|
| 25 |
+
g.insert(0, ip)
|
| 26 |
+
return ('-' if neg else '') + '\\,'.join(g)
|
| 27 |
+
|
| 28 |
+
def money_fr(v, dec=2):
|
| 29 |
+
f = Fraction(v)
|
| 30 |
+
d = (Decimal(f.numerator) / Decimal(f.denominator)).quantize(Decimal(1).scaleb(-dec), rounding=ROUND_HALF_UP)
|
| 31 |
+
s = format(abs(d), 'f'); ip, _, fp = s.partition('.')
|
| 32 |
+
return ('-' if d < 0 else '') + _grp(ip) + (('{,}' + fp) if dec > 0 else '')
|
| 33 |
+
|
| 34 |
+
def num_fr(v, maxdec=4):
|
| 35 |
+
f = Fraction(v)
|
| 36 |
+
d = (Decimal(f.numerator) / Decimal(f.denominator)).quantize(Decimal(1).scaleb(-maxdec), rounding=ROUND_HALF_UP)
|
| 37 |
+
s = format(abs(d), 'f'); ip, _, fp = s.partition('.'); fp = fp.rstrip('0')
|
| 38 |
+
return ('-' if d < 0 else '') + _grp(ip) + (('{,}' + fp) if fp else '')
|
| 39 |
+
|
| 40 |
+
# Paramètres financiers (déterministe). Annuel exact ; continu approché (e).
|
| 41 |
+
P = rd.choice([5000, 10000, 15000, 20000, 25000, 30000, 40000, 50000])
|
| 42 |
+
n = rd.randint(3, 10)
|
| 43 |
+
rPct = rd.choice([Fraction(2), Fraction(5, 2), Fraction(3), Fraction(7, 2), Fraction(4),
|
| 44 |
+
Fraction(9, 2), Fraction(5), Fraction(11, 2), Fraction(6), Fraction(13, 2),
|
| 45 |
+
Fraction(7), Fraction(15, 2), Fraction(8)])
|
| 46 |
+
r = rPct / 100
|
| 47 |
+
|
| 48 |
+
# Placement A : capitalisation annuelle (exact)
|
| 49 |
+
baseComp = 1 + r
|
| 50 |
+
puissance = baseComp ** n
|
| 51 |
+
sA = P * puissance
|
| 52 |
+
|
| 53 |
+
# Placement B : capitalisation continue (e^(r n), approché)
|
| 54 |
+
exposant = r * n
|
| 55 |
+
expVal = Fraction(math.exp(float(exposant)))
|
| 56 |
+
sB = P * expVal
|
| 57 |
+
ecart = sB - sA
|
| 58 |
+
|
| 59 |
+
# Rendus
|
| 60 |
+
pAff = money_fr(P, 0)
|
| 61 |
+
nAff = str(n)
|
| 62 |
+
rPctStr = num_fr(rPct, 1)
|
| 63 |
+
baseCompStr = num_fr(baseComp, 4)
|
| 64 |
+
puissanceApproxStr = num_fr(puissance, 4)
|
| 65 |
+
saAff = money_fr(sA, 2)
|
| 66 |
+
exposantContStr = num_fr(exposant, 2)
|
| 67 |
+
expApproxStr = num_fr(expVal, 4)
|
| 68 |
+
sbAff = money_fr(sB, 2)
|
| 69 |
+
ecartAff = money_fr(ecart, 2)
|
| 70 |
+
|
| 71 |
+
# === Ajouts conversion FGQ ===
|
| 72 |
+
def _solnum(s):
|
| 73 |
+
return s.replace("\\,", "").replace("{,}", ".")
|
| 74 |
+
saSol = _solnum(saAff)
|
| 75 |
+
sbSol = _solnum(sbAff)
|
| 76 |
+
ecartSol = _solnum(ecartAff)
|
| 77 |
+
globals()
|
| 78 |
+
````
|
| 79 |
+
|
| 80 |
+
:::::{question}
|
| 81 |
+
:questionType: FGQ
|
| 82 |
+
:questionId: 0
|
| 83 |
+
:questionIndex: 0
|
| 84 |
+
:solution: [["ord","${{ saSol }}$"],["0"]]
|
| 85 |
+
|
| 86 |
+
::::{questionStatement}
|
| 87 |
+
{fr}`Un investisseur place`{en}`An investor invests` ${{ pAff }}$ € {fr}`sur`{en}`over` ${{ nAff }}$ {fr}`ans au taux de`{en}`years at a rate of` ${{ rPctStr }}\,\%$ {fr}`par an. Placement A (capitalisation annuelle) :`{en}`per year. Investment A (annual compounding):` $S_{A}={{ pAff }}\times({{ baseCompStr }})^{ {{ nAff }} }$. {fr}`Calculer`{en}`Calculate` $S_{A}$ {fr}`(arrondir au centime). On donne`{en}`(round to the nearest cent). We are given` $({{ baseCompStr }})^{ {{ nAff }} }\approx {{ puissanceApproxStr }}$.
|
| 88 |
+
|
| 89 |
+
$S_{A} =$ {input}`||80`
|
| 90 |
+
::::
|
| 91 |
+
|
| 92 |
+
::::{questionHint}
|
| 93 |
+
{fr}`Multiplier`{en}`Multiply` ${{ pAff }}$ {fr}`par la valeur fournie.`{en}`by the given value.`
|
| 94 |
+
::::
|
| 95 |
+
|
| 96 |
+
::::{displayedSolution}
|
| 97 |
+
$S_{A} = {{ saAff }}$ €
|
| 98 |
+
::::
|
| 99 |
+
|
| 100 |
+
::::{detailedSolution}
|
| 101 |
+
{fr}`On applique la formule de capitalisation annuelle :`{en}`We apply the annual compounding formula:`
|
| 102 |
+
|
| 103 |
+
\begin{equation*}
|
| 104 |
+
S_{A} &= {{ pAff }} \times ({{ baseCompStr }})^{ {{ nAff }} } \approx {{ pAff }} \times {{ puissanceApproxStr }} \\
|
| 105 |
+
&= {{ saAff }}.
|
| 106 |
+
\end{equation*}
|
| 107 |
+
::::
|
| 108 |
+
|
| 109 |
+
::::{weightDistribution}
|
| 110 |
+
:logic: 10
|
| 111 |
+
:abstraction: 15
|
| 112 |
+
:reasoning: 25
|
| 113 |
+
:calculation: 50
|
| 114 |
+
::::
|
| 115 |
+
:::::
|
| 116 |
+
|
| 117 |
+
:::::{question}
|
| 118 |
+
:questionType: FGQ
|
| 119 |
+
:questionId: 1
|
| 120 |
+
:questionIndex: 1
|
| 121 |
+
:solution: [["ord","${{ sbSol }}$"],["0"]]
|
| 122 |
+
|
| 123 |
+
::::{questionStatement}
|
| 124 |
+
{fr}`Placement B (capitalisation continue) :`{en}`Investment B (continuous compounding):` $S_{B}={{ pAff }}\times e^{r\times {{ nAff }}}$ {fr}`où`{en}`where` $r=\dfrac{ {{ rPctStr }} }{100}$, {fr}`avec`{en}`with` $e\approx 2{,}718$. {fr}`Calculer`{en}`Calculate` $S_{B}$ {fr}`(arrondir au centime). On donne`{en}`(round to the nearest cent). We are given` $e^{ {{ exposantContStr }} }\approx {{ expApproxStr }}$.
|
| 125 |
+
|
| 126 |
+
$S_{B} =$ {input}`||80`
|
| 127 |
+
::::
|
| 128 |
+
|
| 129 |
+
::::{questionHint}
|
| 130 |
+
{fr}`L'exposant vaut`{en}`The exponent equals` $r\times {{ nAff }}=\dfrac{ {{ rPctStr }} }{100}\times {{ nAff }}={{ exposantContStr }}$ ; {fr}`utiliser`{en}`use` $e^{ {{ exposantContStr }} }\approx {{ expApproxStr }}$.
|
| 131 |
+
::::
|
| 132 |
+
|
| 133 |
+
::::{displayedSolution}
|
| 134 |
+
$S_{B} = {{ sbAff }}$ €
|
| 135 |
+
::::
|
| 136 |
+
|
| 137 |
+
::::{detailedSolution}
|
| 138 |
+
{fr}`L'exposant vaut`{en}`The exponent equals` $r\times {{ nAff }}=\dfrac{ {{ rPctStr }} }{100}\times {{ nAff }}={{ exposantContStr }}$, {fr}`donc :`{en}`so:`
|
| 139 |
+
|
| 140 |
+
\begin{equation*}
|
| 141 |
+
S_{B} &= {{ pAff }} \times e^{ {{ exposantContStr }} } \approx {{ pAff }} \times {{ expApproxStr }} \\
|
| 142 |
+
&= {{ sbAff }}.
|
| 143 |
+
\end{equation*}
|
| 144 |
+
::::
|
| 145 |
+
|
| 146 |
+
::::{weightDistribution}
|
| 147 |
+
:logic: 10
|
| 148 |
+
:abstraction: 20
|
| 149 |
+
:reasoning: 25
|
| 150 |
+
:calculation: 45
|
| 151 |
+
::::
|
| 152 |
+
:::::
|
| 153 |
+
|
| 154 |
+
:::::{question}
|
| 155 |
+
:questionType: FGQ
|
| 156 |
+
:questionId: 2
|
| 157 |
+
:questionIndex: 2
|
| 158 |
+
:solution: [["ord","${{ ecartSol }}$"],["0"]]
|
| 159 |
+
|
| 160 |
+
::::{questionStatement}
|
| 161 |
+
{fr}`Comparer`{en}`Compare` $S_{A}$ {fr}`et`{en}`and` $S_{B}$, {fr}`et interpréter l'écart dans le contexte financier.`{en}`and interpret the difference in the financial context.`
|
| 162 |
+
|
| 163 |
+
$S_{B} - S_{A} =$ {input}`||80`
|
| 164 |
+
::::
|
| 165 |
+
|
| 166 |
+
::::{questionHint}
|
| 167 |
+
{fr}`Calculer`{en}`Calculate` $S_{B}-S_{A}$.
|
| 168 |
+
::::
|
| 169 |
+
|
| 170 |
+
::::{displayedSolution}
|
| 171 |
+
$S_{B} - S_{A} = {{ ecartAff }}$ €
|
| 172 |
+
::::
|
| 173 |
+
|
| 174 |
+
::::{detailedSolution}
|
| 175 |
+
{fr}`On compare les deux montants :`{en}`We compare the two amounts:`
|
| 176 |
+
|
| 177 |
+
\begin{equation*}
|
| 178 |
+
S_{B} - S_{A} &\approx {{ sbAff }} - {{ saAff }} \\
|
| 179 |
+
&= {{ ecartAff }}.
|
| 180 |
+
\end{equation*}
|
| 181 |
+
|
| 182 |
+
{fr}`À taux affiché identique (`{en}`At the same stated rate (` ${{ rPctStr }}\,\%$ ), {fr}`la capitalisation continue`{en}`continuous compounding` $S_{B}\approx {{ sbAff }}$ € {fr}`rapporte un peu plus que la capitalisation annuelle`{en}`yields slightly more than annual compounding` $S_{A}\approx {{ saAff }}$ €, {fr}`l'écart étant d'environ`{en}`the difference being approximately` ${{ ecartAff }}$ €. {fr}`La capitalisation continue réinvestit les intérêts à chaque instant, alors que la capitalisation annuelle ne les réinvestit qu'une fois par an.`{en}`Continuous compounding reinvests interest at every instant, whereas annual compounding reinvests it only once per year.`
|
| 183 |
+
::::
|
| 184 |
+
|
| 185 |
+
::::{weightDistribution}
|
| 186 |
+
:logic: 20
|
| 187 |
+
:abstraction: 25
|
| 188 |
+
:reasoning: 35
|
| 189 |
+
:calculation: 20
|
| 190 |
+
::::
|
| 191 |
+
:::::
|
| 192 |
+
|
| 193 |
+
`````
|
app/knowledge/fewshots/declinaisons/{fr}`Contrainte de budget publicitaire`{en}`Advertising budget constraint`.md
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
`````{exercise}
|
| 2 |
+
:originalExerciseId: df9c98f5-633f-11f1-a8a1-0ed8d3b012a9
|
| 3 |
+
:title: {fr}`Contrainte de budget publicitaire`{en}`Advertising budget constraint`
|
| 4 |
+
:modules:
|
| 5 |
+
:recommendedExecutionTime: 10
|
| 6 |
+
:level: Elementary
|
| 7 |
+
:chap:
|
| 8 |
+
:involvedConcepts: Solving_inequalities, Sets
|
| 9 |
+
:originalSource:
|
| 10 |
+
:visibility: All
|
| 11 |
+
:variations:
|
| 12 |
+
:comment: Version QCM — contrainte de budget, nombre maximal d'insertions et ensemble admissible.
|
| 13 |
+
:id: 0f2a4c67-6e48-11f1-a8a1-0ed8d3b012a9
|
| 14 |
+
|
| 15 |
+
````{python}
|
| 16 |
+
import random as rd
|
| 17 |
+
from pyxiscience.Mes_fctions_generalistes_bis import pxs_config, pxsl_format_number
|
| 18 |
+
from pyxiscience.Classes_Extensions import pxs_Interval
|
| 19 |
+
|
| 20 |
+
config_standard = pxs_config()
|
| 21 |
+
|
| 22 |
+
# Contrainte : budget multiple du coût d'insertion, nMax entier >= 5
|
| 23 |
+
for _ in range(2000):
|
| 24 |
+
cout_insertion = rd.choice([40, 50, 60, 80, 100, 120, 150])
|
| 25 |
+
budget = cout_insertion * rd.randint(10, 50)
|
| 26 |
+
nMax = budget // cout_insertion
|
| 27 |
+
if nMax >= 5 and 800 <= budget <= 3000:
|
| 28 |
+
break
|
| 29 |
+
|
| 30 |
+
verif_max = cout_insertion * nMax
|
| 31 |
+
verif_exclu = cout_insertion * (nMax + 1)
|
| 32 |
+
|
| 33 |
+
# Rendus
|
| 34 |
+
budgetAff = pxsl_format_number(budget)
|
| 35 |
+
coutInsertion = cout_insertion
|
| 36 |
+
nMaxPlusUn = nMax + 1
|
| 37 |
+
verifMaxAff = pxsl_format_number(verif_max)
|
| 38 |
+
verifExcluAff = pxsl_format_number(verif_exclu)
|
| 39 |
+
|
| 40 |
+
# === Distracteurs MCQ (ajouts ; le code ci-dessus est inchangé) ===
|
| 41 |
+
interCorrect = pxs_Interval(0, nMax, False, False).print()
|
| 42 |
+
interD1 = pxs_Interval(0, budget, True, False).print()
|
| 43 |
+
interD2 = pxs_Interval(0, nMax, True, False).print()
|
| 44 |
+
interD3 = pxs_Interval(0, budget, False, False).print()
|
| 45 |
+
|
| 46 |
+
globals()
|
| 47 |
+
````
|
| 48 |
+
|
| 49 |
+
:::::{question}
|
| 50 |
+
:questionType: MCQ
|
| 51 |
+
|
| 52 |
+
::::{questionStatement}
|
| 53 |
+
{fr}`Une start-up dispose d'un budget de communication de`{en}`A start-up has a communication budget of` ${{ budgetAff }}$ €. {fr}`Chaque insertion publicitaire coûte`{en}`Each advertising insertion costs` ${{ coutInsertion }}$ €. {fr}`On note`{en}`Let` $n$ {fr}`le nombre d'insertions. Quel est l'ensemble des valeurs admissibles de`{en}`be the number of insertions. What is the set of admissible values of` $n$ {fr}`en notation intervalle ?`{en}`in interval notation?`
|
| 54 |
+
::::
|
| 55 |
+
|
| 56 |
+
::::{questionHint}
|
| 57 |
+
{fr}`Traduire la contrainte de budget par`{en}`Translate the budget constraint as` ${{ coutInsertion }}n\leq {{ budgetAff }}$, {fr}`puis diviser par`{en}`then divide by` ${{ coutInsertion }}>0$ {fr}`(sens conservé). Ne pas oublier`{en}`(inequality sign preserved). Do not forget` $n\geq 0$.
|
| 58 |
+
::::
|
| 59 |
+
|
| 60 |
+
::::{mcqAnswer}
|
| 61 |
+
:isRightAnswer: true
|
| 62 |
+
$S = {{ interCorrect }}$
|
| 63 |
+
::::
|
| 64 |
+
|
| 65 |
+
::::{mcqAnswer}
|
| 66 |
+
:isRightAnswer: false
|
| 67 |
+
$S = {{ interD1 }}$
|
| 68 |
+
::::
|
| 69 |
+
|
| 70 |
+
::::{mcqAnswer}
|
| 71 |
+
:isRightAnswer: false
|
| 72 |
+
$S = {{ interD2 }}$
|
| 73 |
+
::::
|
| 74 |
+
|
| 75 |
+
::::{mcqAnswer}
|
| 76 |
+
:isRightAnswer: false
|
| 77 |
+
$S = {{ interD3 }}$
|
| 78 |
+
::::
|
| 79 |
+
|
| 80 |
+
::::{mcqAnswer}
|
| 81 |
+
:isRightAnswer: false
|
| 82 |
+
{fr}`Aucune de ces réponses n'est correcte`{en}`None of these answers are correct`
|
| 83 |
+
::::
|
| 84 |
+
|
| 85 |
+
::::{detailedSolution}
|
| 86 |
+
{fr}`La dépense totale`{en}`The total expenditure` ${{ coutInsertion }}n$ {fr}`ne doit pas dépasser le budget, d'où une inéquation résolue par division par un réel positif.`{en}`must not exceed the budget, hence an inequality solved by division by a positive real number.`
|
| 87 |
+
|
| 88 |
+
\begin{equation*}
|
| 89 |
+
{{ coutInsertion }}n \leq {{ budgetAff }}&\implies \frac{ {{ coutInsertion }}n }{ {{ coutInsertion }} } \leq \frac{ {{ budgetAff }} }{ {{ coutInsertion }} } \\[10pt]
|
| 90 |
+
&\implies n \leq {{ nMax }}.
|
| 91 |
+
\end{equation*}
|
| 92 |
+
|
| 93 |
+
{fr}`Comme`{en}`Since` $n$ {fr}`est un nombre d'insertions,`{en}`is a number of insertions,` $n\geq 0$. {fr}`L'ensemble admissible est donc :`{en}`The admissible set is therefore:`
|
| 94 |
+
|
| 95 |
+
\begin{equation*}
|
| 96 |
+
S = {{pxs_Interval(0, nMax, False, False).print()}}.
|
| 97 |
+
\end{equation*}
|
| 98 |
+
|
| 99 |
+
{fr}`La start-up peut financer au maximum`{en}`The start-up can finance at most` ${{ nMax }}$ {fr}`insertions.`{en}`insertions.`
|
| 100 |
+
::::
|
| 101 |
+
|
| 102 |
+
::::{weightDistribution}
|
| 103 |
+
:logic: 15
|
| 104 |
+
:abstraction: 20
|
| 105 |
+
:reasoning: 30
|
| 106 |
+
:calculation: 35
|
| 107 |
+
::::
|
| 108 |
+
:::::
|
| 109 |
+
|
| 110 |
+
`````
|
app/knowledge/fewshots/declinaisons/{fr}`Discontinuité d'une fraction rationnelle`{en}`Discontinuity of a Rational Fraction`.md
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
`````{exercise}
|
| 2 |
+
:id: ff0cf9ac-7067-11f1-a8a1-0ed8d3b012a9
|
| 3 |
+
:originalExerciseId: dd834157-3f2f-11f1-a8a1-0ed8d3b012a9
|
| 4 |
+
:title: {fr}`Discontinuité d'une fraction rationnelle`{en}`Discontinuity of a Rational Fraction`
|
| 5 |
+
:modules: CalcI, Calc_1_Pyx
|
| 6 |
+
:recommendedExecutionTime: 5
|
| 7 |
+
:level: Intermediate
|
| 8 |
+
:chap: chap_limitsContinuity_continuity_ESCP, Calculus_I_Continuity_1_5
|
| 9 |
+
:involvedConcepts: Continuity_at_a_point, Domain_of_function, Rational_Function
|
| 10 |
+
:originalSource: Selin
|
| 11 |
+
:visibility: All
|
| 12 |
+
:variations:
|
| 13 |
+
:comment:
|
| 14 |
+
|
| 15 |
+
```{python}
|
| 16 |
+
import random as rd
|
| 17 |
+
from sympy import *
|
| 18 |
+
from pyxiscience.Mes_fctions_generalistes_bis import pxs_config
|
| 19 |
+
from pyxiscience.Classes_Extensions import pxs_Interval
|
| 20 |
+
from pyxiscience.Mes_fctions_d_analyse import pxsl_pow
|
| 21 |
+
from pyxiscience.Mes_fctions_generalistes_bis import pxsl_latex_avec_formatage
|
| 22 |
+
from pyxiscience.Mes_fctions_probabilistes_bis import pxsl_res_num
|
| 23 |
+
|
| 24 |
+
from pyxiscience.Mes_fctions_generalistes_bis import pxs_config
|
| 25 |
+
|
| 26 |
+
config_standard = pxs_config()
|
| 27 |
+
|
| 28 |
+
x = symbols('x')
|
| 29 |
+
a,b,c,d = rd.sample(range(1, 11), 4)
|
| 30 |
+
p = rd.randint(2, 6)
|
| 31 |
+
q = Rational(1,rd.randint(2, 6))
|
| 32 |
+
f = 1/(a*x+b)
|
| 33 |
+
|
| 34 |
+
x0 = Rational(-b,a)
|
| 35 |
+
|
| 36 |
+
l = f.subs(x,x0)
|
| 37 |
+
```
|
| 38 |
+
|
| 39 |
+
:::::{question}
|
| 40 |
+
:questionType: MCQ
|
| 41 |
+
|
| 42 |
+
::::{questionStatement}
|
| 43 |
+
{fr}`Pour quelle raison la fonction`{en}`Why is the function` $\ds f(x) = {{latex(f)}}$ {fr}`est-elle discontinue en`{en}`discontinuous at` $\ds a = {{latex(x0)}}$ ?
|
| 44 |
+
::::
|
| 45 |
+
|
| 46 |
+
::::{questionHint}
|
| 47 |
+
{fr}`Évaluer le dénominateur de la fonction au point indiqué.`{en}`Evaluate the denominator of the function at the given point.`
|
| 48 |
+
::::
|
| 49 |
+
|
| 50 |
+
::::{mcqAnswer}
|
| 51 |
+
:isRightAnswer: true
|
| 52 |
+
{fr}`Car $\ds f\left({{latex(x0)}}\right)$ n'est pas définie : le dénominateur s'annule en $\ds {{latex(x0)}}$.`{en}`Because $\ds f\left({{latex(x0)}}\right)$ is undefined: the denominator vanishes at $\ds {{latex(x0)}}$.`
|
| 53 |
+
::::
|
| 54 |
+
|
| 55 |
+
::::{mcqAnswer}
|
| 56 |
+
:isRightAnswer: false
|
| 57 |
+
{fr}`Car le numérateur s'annule en $\ds {{latex(x0)}}$.`{en}`Because the numerator vanishes at $\ds {{latex(x0)}}$.`
|
| 58 |
+
::::
|
| 59 |
+
|
| 60 |
+
::::{mcqAnswer}
|
| 61 |
+
:isRightAnswer: false
|
| 62 |
+
{fr}`Car $\ds \lim_{x \to {{latex(x0)}}} f(x) = 0$.`{en}`Because $\ds \lim_{x \to {{latex(x0)}}} f(x) = 0$.`
|
| 63 |
+
::::
|
| 64 |
+
|
| 65 |
+
::::{mcqAnswer}
|
| 66 |
+
:isRightAnswer: false
|
| 67 |
+
{fr}`Car $f$ n'est pas dérivable en $\ds {{latex(x0)}}$, bien qu'elle y soit continue.`{en}`Because $f$ is not differentiable at $\ds {{latex(x0)}}$, although it is continuous there.`
|
| 68 |
+
::::
|
| 69 |
+
|
| 70 |
+
::::{detailedSolution}
|
| 71 |
+
{fr}`La fonction est discontinue en`{en}`The function is discontinuous at` $\ds a = {{latex(x0)}}$ {fr}`car`{en}`because` $\ds f\left({{latex(x0)}}\right)$ {fr}`n'est pas définie (le dénominateur devient zéro).`{en}`is undefined (the denominator becomes zero).`
|
| 72 |
+
::::
|
| 73 |
+
|
| 74 |
+
::::{weightDistribution}
|
| 75 |
+
:reasoning: 35
|
| 76 |
+
:logic: 25
|
| 77 |
+
:abstraction: 20
|
| 78 |
+
:calculation: 20
|
| 79 |
+
::::
|
| 80 |
+
:::::
|
| 81 |
+
`````
|
app/knowledge/fewshots/declinaisons/{fr}`Formule générale du seuil de rentabilité`{en}`General Break-Even Point Formula`.md
ADDED
|
@@ -0,0 +1,261 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
`````{exercise}
|
| 2 |
+
:title: {fr}`Formule générale du seuil de rentabilité`{en}`General Break-Even Point Formula`
|
| 3 |
+
:modules:
|
| 4 |
+
:recommendedExecutionTime: 15
|
| 5 |
+
:level: Elementary
|
| 6 |
+
:chap: chap_lines_Systems_economicEquilibriumBreakeven_ESCP
|
| 7 |
+
:involvedConcepts:
|
| 8 |
+
:originalSource: Session 4 — Erwan Lamy, ESCP Business School
|
| 9 |
+
:visibility: All
|
| 10 |
+
:variations:
|
| 11 |
+
:comment: Synthèse (gestion) : établissement de la formule q* = yFC/(p - c), condition p > c, application numérique et analyse de sensibilité aux coûts fixes.
|
| 12 |
+
:id: 60088df7-74c0-11f1-a8a1-0ed8d3b012a9
|
| 13 |
+
:originalExerciseId: 27f935cf-64e3-11f1-a8a1-0ed8d3b012a9
|
| 14 |
+
|
| 15 |
+
````{python}
|
| 16 |
+
import random as rd
|
| 17 |
+
# --- Helpers PyxiScience (definis inline, autonomes : aucun import externe) ---
|
| 18 |
+
def pxs_config():
|
| 19 |
+
return {}
|
| 20 |
+
def pxsl_format_number(n):
|
| 21 |
+
f = float(n)
|
| 22 |
+
if abs(f - round(f)) < 1e-9:
|
| 23 |
+
return "%d" % round(f)
|
| 24 |
+
s = ("%.10f" % f).rstrip("0").rstrip(".")
|
| 25 |
+
return s.replace(".", "{,}")
|
| 26 |
+
def pxsl_latex_coefficient(coef, ones=False, sign=False):
|
| 27 |
+
coef = int(coef)
|
| 28 |
+
if coef == 0:
|
| 29 |
+
return "+0" if sign else "0"
|
| 30 |
+
if not ones:
|
| 31 |
+
if coef == 1:
|
| 32 |
+
return "+" if sign else ""
|
| 33 |
+
if coef == -1:
|
| 34 |
+
return "-"
|
| 35 |
+
if sign:
|
| 36 |
+
return ("+%d" % coef) if coef > 0 else ("%d" % coef)
|
| 37 |
+
return "%d" % coef
|
| 38 |
+
lc = pxsl_latex_coefficient
|
| 39 |
+
config_standard = pxs_config()
|
| 40 |
+
|
| 41 |
+
# Contraintes : p > c (marge strictement positive, et p - c >= 2 pour eviter un
|
| 42 |
+
# denominateur d'affichage egal a 1) ; yFc et yFcNouv multiples de (p - c) pour
|
| 43 |
+
# des seuils entiers ; yFcNouv > yFc.
|
| 44 |
+
for _ in range(2000):
|
| 45 |
+
p = rd.randint(15, 25)
|
| 46 |
+
c = rd.randint(4, 10)
|
| 47 |
+
diff = p - c
|
| 48 |
+
if diff < 2:
|
| 49 |
+
continue
|
| 50 |
+
multFc = [k * diff for k in range(1, 200) if 240 <= k * diff <= 480]
|
| 51 |
+
multFcNouv = [k * diff for k in range(1, 200) if 480 <= k * diff <= 720]
|
| 52 |
+
if not (multFc and multFcNouv):
|
| 53 |
+
continue
|
| 54 |
+
yFc = rd.choice(multFc)
|
| 55 |
+
candNouv = [x for x in multFcNouv if x > yFc]
|
| 56 |
+
if not candNouv:
|
| 57 |
+
continue
|
| 58 |
+
yFcNouv = rd.choice(candNouv)
|
| 59 |
+
break
|
| 60 |
+
|
| 61 |
+
qStarNum = yFc // diff
|
| 62 |
+
qStarNouv = yFcNouv // diff
|
| 63 |
+
augmentation = qStarNouv - qStarNum
|
| 64 |
+
|
| 65 |
+
# --- Affichage precalcule ---
|
| 66 |
+
qStarAff = r"\dfrac{y_{FC}}{p - c}"
|
| 67 |
+
yFcAff = pxsl_format_number(yFc)
|
| 68 |
+
yFcNouvAff = pxsl_format_number(yFcNouv)
|
| 69 |
+
pMinusCAff = str(diff)
|
| 70 |
+
qStarNumAff = str(qStarNum)
|
| 71 |
+
qStarNouvAff = str(qStarNouv)
|
| 72 |
+
augmentationAff = str(augmentation)
|
| 73 |
+
|
| 74 |
+
globals()
|
| 75 |
+
````
|
| 76 |
+
|
| 77 |
+
:::::{question}
|
| 78 |
+
:questionType: MCQ
|
| 79 |
+
|
| 80 |
+
::::{questionStatement}
|
| 81 |
+
{fr}`Une entreprise vend un produit au prix unitaire`{en}`A company sells a product at unit price` $p${fr}`, avec un coût variable unitaire`{en}`, with a unit variable cost` $c$ {fr}`(avec`{en}`(with` $c < p${fr}`) et des coûts fixes`{en}`) and fixed costs` $y_{FC}${fr}`. Écrire le revenu total`{en}`. Write the total revenue` $y_{TR}(q)$ {fr}`et le coût total`{en}`and the total cost` $y_{TC}(q)${fr}`.`{en}`.`
|
| 82 |
+
::::
|
| 83 |
+
|
| 84 |
+
::::{questionHint}
|
| 85 |
+
{fr}`Reprendre les définitions générales du revenu et du coût total.`{en}`Use the general definitions of revenue and total cost.`
|
| 86 |
+
::::
|
| 87 |
+
|
| 88 |
+
::::{mcqAnswer}
|
| 89 |
+
:isRightAnswer: true
|
| 90 |
+
$y_{TR}(q) = p\,q$ {fr}`et`{en}`and` $y_{TC}(q) = c\,q + y_{FC}$
|
| 91 |
+
::::
|
| 92 |
+
|
| 93 |
+
::::{mcqAnswer}
|
| 94 |
+
:isRightAnswer: false
|
| 95 |
+
$y_{TR}(q) = p\,q + y_{FC}$ {fr}`et`{en}`and` $y_{TC}(q) = c\,q$
|
| 96 |
+
::::
|
| 97 |
+
|
| 98 |
+
::::{mcqAnswer}
|
| 99 |
+
:isRightAnswer: false
|
| 100 |
+
$y_{TR}(q) = (p-c)\,q$ {fr}`et`{en}`and` $y_{TC}(q) = c\,q + y_{FC}$
|
| 101 |
+
::::
|
| 102 |
+
|
| 103 |
+
::::{mcqAnswer}
|
| 104 |
+
:isRightAnswer: false
|
| 105 |
+
$y_{TR}(q) = p\,q$ {fr}`et`{en}`and` $y_{TC}(q) = c\,q$
|
| 106 |
+
::::
|
| 107 |
+
|
| 108 |
+
::::{mcqAnswer}
|
| 109 |
+
:isRightAnswer: false
|
| 110 |
+
{fr}`Aucune de ces réponses n'est correcte`{en}`None of these answers are correct`
|
| 111 |
+
::::
|
| 112 |
+
|
| 113 |
+
::::{detailedSolution}
|
| 114 |
+
{fr}`Par définition :`{en}`By definition:`
|
| 115 |
+
|
| 116 |
+
\begin{equation*}
|
| 117 |
+
y_{TR}(q) = p\,q, \qquad y_{TC}(q) = c\,q + y_{FC}.
|
| 118 |
+
\end{equation*}
|
| 119 |
+
::::
|
| 120 |
+
|
| 121 |
+
::::{weightDistribution}
|
| 122 |
+
:logic: 20
|
| 123 |
+
:abstraction: 30
|
| 124 |
+
:reasoning: 30
|
| 125 |
+
:calculation: 20
|
| 126 |
+
::::
|
| 127 |
+
:::::
|
| 128 |
+
|
| 129 |
+
:::::{question}
|
| 130 |
+
:questionType: MCQ
|
| 131 |
+
|
| 132 |
+
::::{questionStatement}
|
| 133 |
+
{fr}`En résolvant`{en}`By solving` $y_{TR}(q) = y_{TC}(q)${fr}`, établir la formule générale du seuil`{en}`, establish the general break-even formula` $q^{*} = \dfrac{y_{FC}}{p - c}${fr}`. Justifier pourquoi la condition`{en}`. Justify why the condition` $p > c$ {fr}`est nécessaire.`{en}`is necessary.`
|
| 134 |
+
::::
|
| 135 |
+
|
| 136 |
+
::::{questionHint}
|
| 137 |
+
{fr}`Regrouper les termes en`{en}`Group the terms in` $q${fr}`, factoriser par`{en}`, factor by` $q${fr}`, puis diviser par`{en}`, then divide by` $p - c${fr}`.`{en}`.`
|
| 138 |
+
::::
|
| 139 |
+
|
| 140 |
+
::::{mcqAnswer}
|
| 141 |
+
:isRightAnswer: true
|
| 142 |
+
$q^{*} = \dfrac{y_{FC}}{p-c}$, {fr}`valable si`{en}`valid if` $p > c$
|
| 143 |
+
::::
|
| 144 |
+
|
| 145 |
+
::::{mcqAnswer}
|
| 146 |
+
:isRightAnswer: false
|
| 147 |
+
$q^{*} = \dfrac{y_{FC}}{p+c}$, {fr}`valable si`{en}`valid if` $p > c$
|
| 148 |
+
::::
|
| 149 |
+
|
| 150 |
+
::::{mcqAnswer}
|
| 151 |
+
:isRightAnswer: false
|
| 152 |
+
$q^{*} = \dfrac{y_{FC}}{c-p}$, {fr}`valable si`{en}`valid if` $c > p$
|
| 153 |
+
::::
|
| 154 |
+
|
| 155 |
+
::::{mcqAnswer}
|
| 156 |
+
:isRightAnswer: false
|
| 157 |
+
$q^{*} = \dfrac{p-c}{y_{FC}}$, {fr}`valable si`{en}`valid if` $p > c$
|
| 158 |
+
::::
|
| 159 |
+
|
| 160 |
+
::::{mcqAnswer}
|
| 161 |
+
:isRightAnswer: false
|
| 162 |
+
{fr}`Aucune de ces réponses n'est correcte`{en}`None of these answers are correct`
|
| 163 |
+
::::
|
| 164 |
+
|
| 165 |
+
::::{detailedSolution}
|
| 166 |
+
{fr}`On résout`{en}`We solve` $y_{TR}(q) = y_{TC}(q)$ {fr}`:`{en}`:`
|
| 167 |
+
|
| 168 |
+
\begin{equation*}
|
| 169 |
+
p\,q = c\,q + y_{FC} \implies (p - c)\,q = y_{FC}.
|
| 170 |
+
\end{equation*}
|
| 171 |
+
|
| 172 |
+
{fr}`Comme`{en}`Since` $p > c${fr}`, on a`{en}`, we have` $p - c > 0${fr}`, et la division est licite :`{en}`, and the division is valid:`
|
| 173 |
+
|
| 174 |
+
\begin{equation*}
|
| 175 |
+
q^{*} = {{qStarAff}}.
|
| 176 |
+
\end{equation*}
|
| 177 |
+
|
| 178 |
+
{fr}`La condition`{en}`The condition` $p > c$ {fr}`est nécessaire : si`{en}`is necessary: if` $p \leq c${fr}`, alors`{en}`, then` $p - c \leq 0$ {fr}`et l'équation`{en}`and the equation` $(p - c)\,q = y_{FC}$ {fr}`n'a pas de solution positive (car`{en}`has no positive solution (since` $y_{FC} > 0${fr}`). L'entreprise ne pourrait jamais couvrir ses coûts fixes.`{en}`). The company could never cover its fixed costs.`
|
| 179 |
+
::::
|
| 180 |
+
|
| 181 |
+
::::{weightDistribution}
|
| 182 |
+
:logic: 25
|
| 183 |
+
:abstraction: 30
|
| 184 |
+
:reasoning: 35
|
| 185 |
+
:calculation: 10
|
| 186 |
+
::::
|
| 187 |
+
:::::
|
| 188 |
+
|
| 189 |
+
:::::{question}
|
| 190 |
+
:questionType: FGQ
|
| 191 |
+
:solution: [["ord","${{ qStarNumAff }}$"],["0"]]
|
| 192 |
+
|
| 193 |
+
::::{questionStatement}
|
| 194 |
+
{fr}`Une boutique d'accessoires vend des coques de téléphone à`{en}`An accessories shop sells phone cases at` $p = {{p}}$ {fr}`€, avec`{en}`€, with` $c = {{c}}$ {fr}`€ et`{en}`€ and` $y_{FC} = {{yFcAff}}$ {fr}`€/mois. Appliquer la formule pour trouver`{en}`€/month. Apply the formula to find` $q^{*}${fr}`.`{en}`.`\
|
| 195 |
+
\
|
| 196 |
+
$q^{*}$ = {input}`||80`
|
| 197 |
+
::::
|
| 198 |
+
|
| 199 |
+
::::{questionHint}
|
| 200 |
+
{fr}`Remplacer directement dans`{en}`Substitute directly into` $q^{*} = \dfrac{y_{FC}}{p - c}${fr}`.`{en}`.`
|
| 201 |
+
::::
|
| 202 |
+
|
| 203 |
+
::::{displayedSolution}
|
| 204 |
+
$q^{*} = {{ qStarNumAff }}$
|
| 205 |
+
::::
|
| 206 |
+
|
| 207 |
+
::::{detailedSolution}
|
| 208 |
+
{fr}`On applique la formule :`{en}`We apply the formula:`
|
| 209 |
+
|
| 210 |
+
\begin{equation*}
|
| 211 |
+
q^{*} = \dfrac{ {{yFcAff}} }{ {{p}} - {{c}} } = \dfrac{ {{yFcAff}} }{ {{pMinusCAff}} } = {{qStarNumAff}} \text{ {fr}`unités/mois`{en}`units/month`}.
|
| 212 |
+
\end{equation*}
|
| 213 |
+
::::
|
| 214 |
+
|
| 215 |
+
::::{weightDistribution}
|
| 216 |
+
:logic: 15
|
| 217 |
+
:abstraction: 20
|
| 218 |
+
:reasoning: 25
|
| 219 |
+
:calculation: 40
|
| 220 |
+
::::
|
| 221 |
+
:::::
|
| 222 |
+
|
| 223 |
+
:::::{question}
|
| 224 |
+
:questionType: FGQ
|
| 225 |
+
:questionId: 3
|
| 226 |
+
:questionIndex: 3
|
| 227 |
+
:solution: [["ord","${{ augmentationAff }}$"],["0"]]
|
| 228 |
+
|
| 229 |
+
::::{questionStatement}
|
| 230 |
+
{fr}`Si les coûts fixes passent à`{en}`If the fixed costs increase to` $y_{FC} = {{yFcNouvAff}}$ {fr}`€ (nouveau bail), de combien d'unités le seuil augmente-t-il ? Interpréter le résultat.`{en}`€ (new lease), by how many units does the break-even point increase? Interpret the result.`\
|
| 231 |
+
\
|
| 232 |
+
{fr}`Augmentation du seuil (unités/mois) :`{en}`Increase in break-even (units/month):` {input}`||80`.
|
| 233 |
+
::::
|
| 234 |
+
|
| 235 |
+
::::{questionHint}
|
| 236 |
+
{fr}`Recalculer`{en}`Recalculate` $q^{*}$ {fr}`avec le nouveau`{en}`with the new` $y_{FC}${fr}`, puis faire la différence.`{en}`, then compute the difference.`
|
| 237 |
+
::::
|
| 238 |
+
|
| 239 |
+
::::{displayedSolution}
|
| 240 |
+
{fr}`Augmentation :`{en}`Increase:` ${{ augmentationAff }}$
|
| 241 |
+
::::
|
| 242 |
+
|
| 243 |
+
::::{detailedSolution}
|
| 244 |
+
{fr}`Avec`{en}`With` $y_{FC} = {{yFcNouvAff}}$ {fr}`€ :`{en}`€:`
|
| 245 |
+
|
| 246 |
+
\begin{equation*}
|
| 247 |
+
q^{*} = \dfrac{ {{yFcNouvAff}} }{ {{pMinusCAff}} } = {{qStarNouvAff}} \text{ {fr}`unités/mois`{en}`units/month`}.
|
| 248 |
+
\end{equation*}
|
| 249 |
+
|
| 250 |
+
{fr}`Le seuil augmente de`{en}`The break-even point increases by` ${{qStarNouvAff}} - {{qStarNumAff}} = {{augmentationAff}}$ {fr}`unités : la boutique doit vendre`{en}`units: the shop must sell` {{augmentationAff}} {fr}`coques de plus chaque mois uniquement pour absorber le loyer plus élevé, sans gagner de profit supplémentaire.`{en}`more cases each month just to absorb the higher rent, without earning any additional profit.`
|
| 251 |
+
::::
|
| 252 |
+
|
| 253 |
+
::::{weightDistribution}
|
| 254 |
+
:logic: 20
|
| 255 |
+
:abstraction: 25
|
| 256 |
+
:reasoning: 30
|
| 257 |
+
:calculation: 25
|
| 258 |
+
::::
|
| 259 |
+
:::::
|
| 260 |
+
|
| 261 |
+
`````
|
app/knowledge/fewshots/declinaisons/{fr}`Intérêts composés _ calcul d'un capital futur`{en}`Compound Interest_ Future Capital Calculation`.md
ADDED
|
@@ -0,0 +1,196 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
`````{exercise}
|
| 2 |
+
:title: {fr}`Intérêts composés : calcul d'un capital futur`{en}`Compound Interest: Future Capital Calculation`
|
| 3 |
+
:modules:
|
| 4 |
+
:recommendedExecutionTime: 10
|
| 5 |
+
:level: Elementary
|
| 6 |
+
:chap: chap_expLogFunctions_exponentialFunctions_ESCP
|
| 7 |
+
:involvedConcepts:
|
| 8 |
+
:originalSource:
|
| 9 |
+
:visibility: All
|
| 10 |
+
:variations:
|
| 11 |
+
:comment: Appliqué (économie & gestion) : capitalisation annuelle S(n)=P(1+r)^n, évaluation à plusieurs dates et monotonie (base 1,03>1).
|
| 12 |
+
:id: 9378268f-74d9-11f1-a8a1-0ed8d3b012a9
|
| 13 |
+
:originalExerciseId: 7265a5b6-6597-11f1-a8a1-0ed8d3b012a9
|
| 14 |
+
|
| 15 |
+
````{python}
|
| 16 |
+
import random as rd
|
| 17 |
+
from fractions import Fraction
|
| 18 |
+
from decimal import Decimal, ROUND_HALF_UP
|
| 19 |
+
|
| 20 |
+
def _grp(ip):
|
| 21 |
+
neg = ip.startswith('-'); ip = ip.lstrip('-'); g = []
|
| 22 |
+
while len(ip) > 3:
|
| 23 |
+
g.insert(0, ip[-3:]); ip = ip[:-3]
|
| 24 |
+
g.insert(0, ip)
|
| 25 |
+
return ('-' if neg else '') + '\\,'.join(g)
|
| 26 |
+
|
| 27 |
+
def money_fr(v, dec=2):
|
| 28 |
+
f = Fraction(v)
|
| 29 |
+
d = (Decimal(f.numerator) / Decimal(f.denominator)).quantize(Decimal(1).scaleb(-dec), rounding=ROUND_HALF_UP)
|
| 30 |
+
s = format(abs(d), 'f'); ip, _, fp = s.partition('.')
|
| 31 |
+
return ('-' if d < 0 else '') + _grp(ip) + (('{,}' + fp) if dec > 0 else '')
|
| 32 |
+
|
| 33 |
+
def num_fr(v, maxdec=4):
|
| 34 |
+
f = Fraction(v)
|
| 35 |
+
d = (Decimal(f.numerator) / Decimal(f.denominator)).quantize(Decimal(1).scaleb(-maxdec), rounding=ROUND_HALF_UP)
|
| 36 |
+
s = format(abs(d), 'f'); ip, _, fp = s.partition('.'); fp = fp.rstrip('0')
|
| 37 |
+
return ('-' if d < 0 else '') + _grp(ip) + (('{,}' + fp) if fp else '')
|
| 38 |
+
|
| 39 |
+
# Construction déterministe, arithmétique exacte (Fraction)
|
| 40 |
+
P = rd.choice([3000, 4000, 5000, 6000, 7000, 8000, 10000])
|
| 41 |
+
rPourcent = rd.choice([Fraction(2), Fraction(5, 2), Fraction(3), Fraction(7, 2),
|
| 42 |
+
Fraction(4), Fraction(9, 2), Fraction(5)])
|
| 43 |
+
base = 1 + rPourcent / 100
|
| 44 |
+
nLong = rd.randint(8, 15)
|
| 45 |
+
|
| 46 |
+
S0 = Fraction(P)
|
| 47 |
+
S1 = P * base
|
| 48 |
+
S2 = P * base**2
|
| 49 |
+
SNLong = P * base**nLong
|
| 50 |
+
|
| 51 |
+
# Rendus
|
| 52 |
+
PAff = money_fr(P, 0)
|
| 53 |
+
rPourcentAff = num_fr(rPourcent, 2)
|
| 54 |
+
baseAff = num_fr(base, 4)
|
| 55 |
+
baseMonoAff = num_fr(base, 2)
|
| 56 |
+
nLongAff = str(nLong)
|
| 57 |
+
baseCarreAff = num_fr(base**2, 4)
|
| 58 |
+
basePuissNLongAff = num_fr(base**nLong, 4)
|
| 59 |
+
s0Aff = money_fr(S0, 2)
|
| 60 |
+
s1Aff = money_fr(S1, 2)
|
| 61 |
+
s2Aff = money_fr(S2, 2)
|
| 62 |
+
sNLongAff = money_fr(SNLong, 2)
|
| 63 |
+
|
| 64 |
+
# === Ajouts conversion FGQ ===
|
| 65 |
+
def _solnum(s):
|
| 66 |
+
return s.replace("\\,", "").replace("{,}", ".")
|
| 67 |
+
PSol = _solnum(PAff)
|
| 68 |
+
s1Sol = _solnum(s1Aff)
|
| 69 |
+
s2Sol = _solnum(s2Aff)
|
| 70 |
+
sNLongSol = _solnum(sNLongAff)
|
| 71 |
+
globals()
|
| 72 |
+
````
|
| 73 |
+
|
| 74 |
+
:::::{question}
|
| 75 |
+
:questionType: FGQ
|
| 76 |
+
:questionId: 0
|
| 77 |
+
:questionIndex: 0
|
| 78 |
+
:solution: [["ord","${{ PSol }}$","${{ s1Sol }}$","${{ s2Sol }}$"],["0","0","0"]]
|
| 79 |
+
|
| 80 |
+
::::{questionStatement}
|
| 81 |
+
{fr}`Un épargnant place`{en}`A saver invests` ${{ PAff }}$ € {fr}`à un taux annuel de`{en}`at an annual rate of` ${{ rPourcentAff }}\,\%$, {fr}`capitalisation annuelle :`{en}`annual compounding:` $S(n)={{ PAff }}\times({{ baseAff }})^{n}$. {fr}`Calculer`{en}`Calculate` $S(0)$, $S(1)$ {fr}`et`{en}`and` $S(2)$, {fr}`puis interpréter`{en}`then interpret` $S(0)$.
|
| 82 |
+
|
| 83 |
+
$S(0) =$ {input}`||80` $\qquad S(1) =$ {input}`||80` $\qquad S(2) =$ {input}`||80`
|
| 84 |
+
::::
|
| 85 |
+
|
| 86 |
+
::::{questionHint}
|
| 87 |
+
{fr}`Appliquer la formule avec`{en}`Apply the formula with` $n=0,1,2$ ; {fr}`rappeler que toute base élevée à la puissance`{en}`recall that any base raised to the power` $0$ {fr}`vaut`{en}`equals` $1$.
|
| 88 |
+
::::
|
| 89 |
+
|
| 90 |
+
::::{displayedSolution}
|
| 91 |
+
$S(0) = {{ PAff }}$ € $\qquad S(1) = {{ s1Aff }}$ € $\qquad S(2) = {{ s2Aff }}$ €
|
| 92 |
+
::::
|
| 93 |
+
|
| 94 |
+
::::{detailedSolution}
|
| 95 |
+
{fr}`On applique la formule. Pour`{en}`We apply the formula. For` $n=0$, {fr}`la base élevée à la puissance`{en}`the base raised to the power` $0$ {fr}`vaut`{en}`equals` $1$, {fr}`donc`{en}`so` $S(0)={{ PAff }}$.
|
| 96 |
+
|
| 97 |
+
\begin{equation*}
|
| 98 |
+
S(1) &= {{ PAff }} \times {{ baseAff }} \\
|
| 99 |
+
&= {{ s1Aff }}.
|
| 100 |
+
\end{equation*}
|
| 101 |
+
|
| 102 |
+
\begin{equation*}
|
| 103 |
+
S(2) &= {{ PAff }} \times ({{ baseAff }})^{2} \\
|
| 104 |
+
&= {{ PAff }} \times {{ baseCarreAff }} \\
|
| 105 |
+
&= {{ s2Aff }}.
|
| 106 |
+
\end{equation*}
|
| 107 |
+
|
| 108 |
+
$S(0)={{ PAff }}$ € {fr}`est le capital initial : aucun intérêt n'a encore été versé.`{en}`is the initial capital: no interest has been paid yet.`
|
| 109 |
+
::::
|
| 110 |
+
|
| 111 |
+
::::{weightDistribution}
|
| 112 |
+
:logic: 10
|
| 113 |
+
:abstraction: 20
|
| 114 |
+
:reasoning: 25
|
| 115 |
+
:calculation: 45
|
| 116 |
+
::::
|
| 117 |
+
:::::
|
| 118 |
+
|
| 119 |
+
:::::{question}
|
| 120 |
+
:questionType: FGQ
|
| 121 |
+
:questionId: 1
|
| 122 |
+
:questionIndex: 1
|
| 123 |
+
:solution: [["ord","${{ sNLongSol }}$"],["0"]]
|
| 124 |
+
|
| 125 |
+
::::{questionStatement}
|
| 126 |
+
{fr}`Calculer`{en}`Calculate` $S({{ nLongAff }})$. {fr}`On donne`{en}`We are given` $({{ baseAff }})^{ {{ nLongAff }} }\approx {{ basePuissNLongAff }}$.
|
| 127 |
+
|
| 128 |
+
$S({{ nLongAff }}) =$ {input}`||80`
|
| 129 |
+
::::
|
| 130 |
+
|
| 131 |
+
::::{questionHint}
|
| 132 |
+
{fr}`Multiplier`{en}`Multiply` ${{ PAff }}$ {fr}`par la valeur fournie.`{en}`by the provided value.`
|
| 133 |
+
::::
|
| 134 |
+
|
| 135 |
+
::::{displayedSolution}
|
| 136 |
+
$S({{ nLongAff }}) = {{ sNLongAff }}$ €
|
| 137 |
+
::::
|
| 138 |
+
|
| 139 |
+
::::{detailedSolution}
|
| 140 |
+
{fr}`En utilisant la valeur fournie :`{en}`Using the provided value:`
|
| 141 |
+
|
| 142 |
+
\begin{equation*}
|
| 143 |
+
S({{ nLongAff }}) &= {{ PAff }} \times ({{ baseAff }})^{ {{ nLongAff }} } \approx {{ PAff }} \times {{ basePuissNLongAff }} \\
|
| 144 |
+
&= {{ sNLongAff }}.
|
| 145 |
+
\end{equation*}
|
| 146 |
+
::::
|
| 147 |
+
|
| 148 |
+
::::{weightDistribution}
|
| 149 |
+
:logic: 10
|
| 150 |
+
:abstraction: 15
|
| 151 |
+
:reasoning: 25
|
| 152 |
+
:calculation: 50
|
| 153 |
+
::::
|
| 154 |
+
:::::
|
| 155 |
+
|
| 156 |
+
:::::{question}
|
| 157 |
+
:questionType: MCQ
|
| 158 |
+
:questionId: 2
|
| 159 |
+
:questionIndex: 2
|
| 160 |
+
|
| 161 |
+
::::{questionStatement}
|
| 162 |
+
{fr}`Déterminer si`{en}`Determine whether` $S$ {fr}`est croissante ou décroissante, et justifier à partir de la base.`{en}`is increasing or decreasing, and justify from the base.`
|
| 163 |
+
::::
|
| 164 |
+
|
| 165 |
+
::::{questionHint}
|
| 166 |
+
{fr}`La base est`{en}`The base is` ${{ baseMonoAff }}>1$.
|
| 167 |
+
::::
|
| 168 |
+
|
| 169 |
+
::::{mcqAnswer}
|
| 170 |
+
:isRightAnswer: true
|
| 171 |
+
{fr}`Strictement croissante (base $> 1$)`{en}`Strictly increasing (base $> 1$)`
|
| 172 |
+
::::
|
| 173 |
+
|
| 174 |
+
::::{mcqAnswer}
|
| 175 |
+
:isRightAnswer: false
|
| 176 |
+
{fr}`Strictement décroissante (base $< 1$)`{en}`Strictly decreasing (base $< 1$)`
|
| 177 |
+
::::
|
| 178 |
+
|
| 179 |
+
::::{mcqAnswer}
|
| 180 |
+
:isRightAnswer: false
|
| 181 |
+
{fr}`Constante`{en}`Constant`
|
| 182 |
+
::::
|
| 183 |
+
|
| 184 |
+
::::{detailedSolution}
|
| 185 |
+
{fr}`La base est`{en}`The base is` $b={{ baseMonoAff }}>1$, {fr}`donc`{en}`so` $S$ {fr}`est strictement croissante : le capital augmente chaque année. C'est cohérent avec`{en}`is strictly increasing: the capital increases each year. This is consistent with` ${{ s0Aff }}<{{ s1Aff }}<{{ s2Aff }}<\dots<{{ sNLongAff }}$.
|
| 186 |
+
::::
|
| 187 |
+
|
| 188 |
+
::::{weightDistribution}
|
| 189 |
+
:logic: 25
|
| 190 |
+
:abstraction: 35
|
| 191 |
+
:reasoning: 30
|
| 192 |
+
:calculation: 10
|
| 193 |
+
::::
|
| 194 |
+
:::::
|
| 195 |
+
|
| 196 |
+
`````
|
app/knowledge/fewshots/declinaisons/{fr}`Inéquation linéaire en deux étapes`{en}`Linear Inequality in Two Steps`.md
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
`````{exercise}
|
| 2 |
+
:title: {fr}`Inéquation linéaire en deux étapes`{en}`Linear Inequality in Two Steps`
|
| 3 |
+
:modules:
|
| 4 |
+
:recommendedExecutionTime: 6
|
| 5 |
+
:level: Elementary
|
| 6 |
+
:chap: chap_equations_Inequalities_linearInequalitiesIntervals_ESCP
|
| 7 |
+
:involvedConcepts:
|
| 8 |
+
:originalSource:
|
| 9 |
+
:visibility: All
|
| 10 |
+
:variations:
|
| 11 |
+
:comment: Échauffement : résolution en deux étapes (addition puis division par un réel positif), ensemble solution en intervalle, borne exclue.
|
| 12 |
+
:originalExerciseId: d40ec55f-633f-11f1-a8a1-0ed8d3b012a9
|
| 13 |
+
:id: e8a23848-7098-11f1-a8a1-0ed8d3b012a9
|
| 14 |
+
|
| 15 |
+
````{python}
|
| 16 |
+
import random as rd
|
| 17 |
+
from sympy import oo
|
| 18 |
+
from pyxiscience.Mes_fctions_generalistes_bis import pxs_config, pxsl_latex_coefficient as lc
|
| 19 |
+
from pyxiscience.Classes_Extensions import pxs_Interval
|
| 20 |
+
|
| 21 |
+
config_standard = pxs_config()
|
| 22 |
+
|
| 23 |
+
# Contrainte : (c - b) divisible par a pour une solution entière
|
| 24 |
+
a = rd.randint(2, 9)
|
| 25 |
+
k = rd.randint(2, 9)
|
| 26 |
+
somme = k * a
|
| 27 |
+
b = rd.randint(-15, -1)
|
| 28 |
+
c = somme + b
|
| 29 |
+
|
| 30 |
+
x_sol = somme // a
|
| 31 |
+
x_test_exclu = x_sol
|
| 32 |
+
x_test_inclus = x_sol + 1
|
| 33 |
+
verif_exclu = a * x_test_exclu + b
|
| 34 |
+
verif_inclus = a * x_test_inclus + b
|
| 35 |
+
intervalAff = pxs_Interval(x_sol, oo, True, True).print()
|
| 36 |
+
|
| 37 |
+
# Rendus
|
| 38 |
+
aAff = lc(a)
|
| 39 |
+
bSignAff = lc(b, ones=True, sign=True)
|
| 40 |
+
negBAff = str(-b)
|
| 41 |
+
xSol = x_sol
|
| 42 |
+
xTestExclu = x_test_exclu
|
| 43 |
+
xTestInclus = x_test_inclus
|
| 44 |
+
verifExclu = verif_exclu
|
| 45 |
+
verifInclus = verif_inclus
|
| 46 |
+
|
| 47 |
+
globals()
|
| 48 |
+
````
|
| 49 |
+
|
| 50 |
+
:::::{question}
|
| 51 |
+
:questionType: FGQ
|
| 52 |
+
:solution: [["ord","${{ xSol }}$","$+\infty$"],["0","0"]]
|
| 53 |
+
|
| 54 |
+
::::{questionStatement}
|
| 55 |
+
{fr}`Résoudre l'inéquation`{en}`Solve the inequality` ${{ aAff }}x {{ bSignAff }} > {{ c }}$ {fr}`et soit`{en}`and let` $S$ {fr}`son ensemble solution. Écrire`{en}`be its solution set. Write` $S$ {fr}`sous forme d'intervalle (choisir le crochet adapté au caractère strict ou large de l'inégalité) :`{en}`as an interval (choose the bracket matching the strict or non-strict nature of the inequality):`
|
| 56 |
+
|
| 57 |
+
$S =$ {select}`[|]||]||45` {input}`||120` $;$ {input}`||120` {select}`]|[||[||45`
|
| 58 |
+
::::
|
| 59 |
+
|
| 60 |
+
::::{questionHint}
|
| 61 |
+
{fr}`Isoler`{en}`Isolate` $x$ {fr}`en deux étapes : ajouter`{en}`in two steps: add` ${{ negBAff }}${fr}`, puis diviser par`{en}`, then divide by` ${{ a }} > 0$ {fr}`(le sens est conservé).`{en}`(the inequality sign is preserved).`
|
| 62 |
+
::::
|
| 63 |
+
|
| 64 |
+
::::{displayedSolution}
|
| 65 |
+
$S = \left] {{ xSol }} ;\ +\infty \right[$
|
| 66 |
+
::::
|
| 67 |
+
|
| 68 |
+
::::{detailedSolution}
|
| 69 |
+
{fr}`L'addition puis la division par un réel strictement positif conserve le sens de l'inégalité.`{en}`Addition followed by division by a strictly positive real number preserves the inequality sign.`
|
| 70 |
+
|
| 71 |
+
\begin{equation*}
|
| 72 |
+
{{ aAff }}x {{ bSignAff }} + {{ negBAff }} > {{ c }} + {{ negBAff }} &\implies {{ aAff }}x > {{ somme }}\\[10pt]
|
| 73 |
+
&\implies \frac{ {{ aAff }}x }{ {{ a }} } > \frac{ {{ somme }} }{ {{ a }} } \\[10pt]
|
| 74 |
+
&\implies x > {{ xSol }}.
|
| 75 |
+
\end{equation*}
|
| 76 |
+
|
| 77 |
+
{fr}`L'ensemble des solutions est donc`{en}`The solution set is therefore`
|
| 78 |
+
|
| 79 |
+
\begin{equation*}
|
| 80 |
+
S = {{ intervalAff }}.
|
| 81 |
+
\end{equation*}
|
| 82 |
+
|
| 83 |
+
{fr}`La borne`{en}`The bound` ${{ xSol }}$ {fr}`est exclue (inégalité stricte`{en}`is excluded (strict inequality` $>${fr}`), d'où la parenthèse ouverte.`{en}`), hence the open parenthesis.`
|
| 84 |
+
::::
|
| 85 |
+
|
| 86 |
+
::::{weightDistribution}
|
| 87 |
+
:logic: 15
|
| 88 |
+
:abstraction: 20
|
| 89 |
+
:reasoning: 30
|
| 90 |
+
:calculation: 35
|
| 91 |
+
::::
|
| 92 |
+
:::::
|
| 93 |
+
|
| 94 |
+
`````
|
app/knowledge/fewshots/declinaisons/{fr}`Inéquation linéaire en une étape et notation intervalle`{en}`One-step linear inequality and interval notation`.md
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
`````{exercise}
|
| 2 |
+
:title: {fr}`Inéquation linéaire en une étape et notation intervalle`{en}`One-step linear inequality and interval notation`
|
| 3 |
+
:modules:
|
| 4 |
+
:recommendedExecutionTime: 5
|
| 5 |
+
:level: Elementary
|
| 6 |
+
:chap:
|
| 7 |
+
:involvedConcepts: Interval_Notation, Solving_inequalities
|
| 8 |
+
:originalSource:
|
| 9 |
+
:visibility: All
|
| 10 |
+
:variations:
|
| 11 |
+
:comment: Échauffement : résolution d'une inéquation en une étape (règle d'addition), écriture de l'ensemble solution en notation intervalle et statut de la borne.
|
| 12 |
+
:originalExerciseId: cdbe79e9-633f-11f1-a8a1-0ed8d3b012a9
|
| 13 |
+
:id: 6fef6add-6e40-11f1-a8a1-0ed8d3b012a9
|
| 14 |
+
|
| 15 |
+
````{python}
|
| 16 |
+
import random as rd
|
| 17 |
+
from sympy import oo
|
| 18 |
+
from pyxiscience.Mes_fctions_generalistes_bis import pxs_config, pxsl_latex_coefficient as lc
|
| 19 |
+
from pyxiscience.Classes_Extensions import pxs_Interval
|
| 20 |
+
|
| 21 |
+
config_standard = pxs_config()
|
| 22 |
+
|
| 23 |
+
# Tirage des paramètres
|
| 24 |
+
a = rd.choice([i for i in range(-10, 11) if i != 0])
|
| 25 |
+
b = rd.randint(-20, 20)
|
| 26 |
+
signe = rd.choice(['\\leq', '\\geq', '<', '>'])
|
| 27 |
+
|
| 28 |
+
# Solution de x + a (signe) b -> x (signe) b - a
|
| 29 |
+
sol = b - a
|
| 30 |
+
stricte = signe in ['<', '>']
|
| 31 |
+
|
| 32 |
+
if signe == '\\leq':
|
| 33 |
+
intervalAff = pxs_Interval(-oo, sol, True, False).print()
|
| 34 |
+
valVraie, valFausse = sol, sol + 1
|
| 35 |
+
elif signe == '\\geq':
|
| 36 |
+
intervalAff = pxs_Interval(sol, oo, False, True).print()
|
| 37 |
+
valVraie, valFausse = sol, sol - 1
|
| 38 |
+
elif signe == '<':
|
| 39 |
+
intervalAff = pxs_Interval(-oo, sol, True, True).print()
|
| 40 |
+
valVraie, valFausse = sol - 1, sol
|
| 41 |
+
else: # '>'
|
| 42 |
+
intervalAff = pxs_Interval(sol, oo, True, True).print()
|
| 43 |
+
valVraie, valFausse = sol + 1, sol
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
if pxs_lang == "fr":
|
| 47 |
+
borneStatutAff = "exclue" if stricte else "incluse"
|
| 48 |
+
inegaliteTypeAff = "stricte" if stricte else "large"
|
| 49 |
+
crochetAff = "ouvert" if stricte else "fermé"
|
| 50 |
+
else:
|
| 51 |
+
borneStatutAff = "excluded" if stricte else "included"
|
| 52 |
+
inegaliteTypeAff = "strict" if stricte else "non-strict"
|
| 53 |
+
crochetAff = "open" if stricte else "closed"
|
| 54 |
+
|
| 55 |
+
infiniAff = "-\\infty" if signe in ['\\leq', '<'] else "+\\infty"
|
| 56 |
+
|
| 57 |
+
# Rendus
|
| 58 |
+
aSignAff = lc(a, ones=True, sign=True)
|
| 59 |
+
negASignAff = lc(-a, ones=True, sign=True)
|
| 60 |
+
verifVraieGauche = valVraie + a
|
| 61 |
+
verifFausseGauche = valFausse + a
|
| 62 |
+
solLineAff = f"x {aSignAff} {negASignAff} {signe} {b} {negASignAff} \\implies x {signe} {sol}"
|
| 63 |
+
|
| 64 |
+
globals()
|
| 65 |
+
|
| 66 |
+
|
| 67 |
+
# ═══════════════ Ajouts conversion QCM (distracteurs) ═══════════════
|
| 68 |
+
from sympy import oo as _oo
|
| 69 |
+
_sol2 = b + a # erreur arithmétique : b + a au lieu de b - a
|
| 70 |
+
if signe == '\\leq':
|
| 71 |
+
distInt1 = pxs_Interval(-_oo, sol, True, True).print()
|
| 72 |
+
distInt2 = pxs_Interval(sol, _oo, False, True).print()
|
| 73 |
+
distInt3 = pxs_Interval(-_oo, _sol2, True, False).print()
|
| 74 |
+
elif signe == '\\geq':
|
| 75 |
+
distInt1 = pxs_Interval(sol, _oo, True, True).print()
|
| 76 |
+
distInt2 = pxs_Interval(-_oo, sol, True, False).print()
|
| 77 |
+
distInt3 = pxs_Interval(_sol2, _oo, False, True).print()
|
| 78 |
+
elif signe == '<':
|
| 79 |
+
distInt1 = pxs_Interval(-_oo, sol, True, False).print()
|
| 80 |
+
distInt2 = pxs_Interval(sol, _oo, True, True).print()
|
| 81 |
+
distInt3 = pxs_Interval(-_oo, _sol2, True, True).print()
|
| 82 |
+
else:
|
| 83 |
+
distInt1 = pxs_Interval(sol, _oo, False, True).print()
|
| 84 |
+
distInt2 = pxs_Interval(-_oo, sol, True, True).print()
|
| 85 |
+
distInt3 = pxs_Interval(_sol2, _oo, True, True).print()
|
| 86 |
+
````
|
| 87 |
+
|
| 88 |
+
:::::{question}
|
| 89 |
+
:questionType: MCQ
|
| 90 |
+
:questionId: 0
|
| 91 |
+
:questionIndex: 0
|
| 92 |
+
|
| 93 |
+
::::{questionStatement}
|
| 94 |
+
{fr}`Résoudre l'inéquation`{en}`Solve the inequality` $x{{ aSignAff }} {{ signe }} {{ b }}$, {fr}`écrire l'ensemble solution en notation intervalle et préciser si la borne obtenue est incluse ou exclue.`{en}`write the solution set in interval notation and specify whether the obtained bound is included or excluded.`
|
| 95 |
+
::::
|
| 96 |
+
|
| 97 |
+
::::{questionHint}
|
| 98 |
+
{fr}`Soustraire`{en}`Subtract` ${{ a }}$ {fr}`des deux membres : ajouter ou soustraire un même réel conserve le sens de l'inégalité (règle d'addition).`{en}`from both sides: adding or subtracting the same real number preserves the direction of the inequality (addition rule).`
|
| 99 |
+
::::
|
| 100 |
+
|
| 101 |
+
::::{mcqAnswer}
|
| 102 |
+
:isRightAnswer: true
|
| 103 |
+
$S = {{ intervalAff }}$
|
| 104 |
+
::::
|
| 105 |
+
|
| 106 |
+
::::{mcqAnswer}
|
| 107 |
+
:isRightAnswer: false
|
| 108 |
+
$S = {{ distInt1 }}$
|
| 109 |
+
::::
|
| 110 |
+
|
| 111 |
+
::::{mcqAnswer}
|
| 112 |
+
:isRightAnswer: false
|
| 113 |
+
$S = {{ distInt2 }}$
|
| 114 |
+
::::
|
| 115 |
+
|
| 116 |
+
::::{mcqAnswer}
|
| 117 |
+
:isRightAnswer: false
|
| 118 |
+
$S = {{ distInt3 }}$
|
| 119 |
+
::::
|
| 120 |
+
|
| 121 |
+
::::{mcqAnswer}
|
| 122 |
+
:isRightAnswer: false
|
| 123 |
+
{fr}`Aucune de ces réponses n'est correcte`{en}`None of these answers are correct`
|
| 124 |
+
::::
|
| 125 |
+
|
| 126 |
+
::::{detailedSolution}
|
| 127 |
+
{fr}`Additionner ou soustraire ne change pas le sens de l'inégalité.`{en}`Adding or subtracting does not change the direction of the inequality.`
|
| 128 |
+
|
| 129 |
+
\begin{equation*}
|
| 130 |
+
{{ solLineAff }}.
|
| 131 |
+
\end{equation*}
|
| 132 |
+
|
| 133 |
+
{fr}`L'ensemble des solutions est`{en}`The solution set is`
|
| 134 |
+
|
| 135 |
+
\begin{equation*}
|
| 136 |
+
S = {{ intervalAff }}.
|
| 137 |
+
\end{equation*}
|
| 138 |
+
|
| 139 |
+
{fr}`La borne`{en}`The bound` ${{ sol }}$ {fr}`est`{en}`is` {{ borneStatutAff }} ({fr}`inégalité`{en}`inequality` {{ inegaliteTypeAff }} ${{ signe }}$), {fr}`d'où le crochet`{en}`hence the bracket is` {{ crochetAff }}. {fr}`Le symbole`{en}`The symbol` ${{ infiniAff }}$ {fr}`n'est pas un réel donc la parenthèse ouverte est obligatoire.`{en}`is not a real number so the open parenthesis is mandatory.`
|
| 140 |
+
::::
|
| 141 |
+
|
| 142 |
+
::::{weightDistribution}
|
| 143 |
+
:logic: 15
|
| 144 |
+
:abstraction: 20
|
| 145 |
+
:reasoning: 30
|
| 146 |
+
:calculation: 35
|
| 147 |
+
::::
|
| 148 |
+
:::::
|
| 149 |
+
|
| 150 |
+
`````
|
app/knowledge/fewshots/declinaisons/{fr}`Lecture graphique du sens de variation et des extremums`{en}`Graphical reading of monotonicity and extrema`.md
ADDED
|
@@ -0,0 +1,218 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
`````{exercise}
|
| 2 |
+
:originalExerciseId: a6a3c85d-689a-11f1-a8a1-0ed8d3b012a9
|
| 3 |
+
:title: {fr}`Lecture graphique du sens de variation et des extremums`{en}`Graphical reading of monotonicity and extrema`
|
| 4 |
+
:modules: fund_of_math_I_ESCP
|
| 5 |
+
:recommendedExecutionTime: 10
|
| 6 |
+
:level: Elementary
|
| 7 |
+
:chap: chap_extremaCurveSketching_monotonicityRelativeExtrema_ESCP
|
| 8 |
+
:involvedConcepts:
|
| 9 |
+
:originalSource:
|
| 10 |
+
:visibility: All
|
| 11 |
+
:variations:
|
| 12 |
+
:comment: Échauffement (thème pur) : lecture sur une courbe des intervalles de croissance/décroissance, du minimum et du maximum, et comparaison de deux images par la monotonie. Aucune dérivée.
|
| 13 |
+
:id: e2835bb5-74b2-11f1-a8a1-0ed8d3b012a9
|
| 14 |
+
|
| 15 |
+
````{python}
|
| 16 |
+
import random as rd
|
| 17 |
+
# ──────────────────────────────────────────────────────────────────────────
|
| 18 |
+
# MÉTHODE CONSTRUCTIVE — partir de la réponse (les extremums et la structure)
|
| 19 |
+
# ──────────────────────────────────────────────────────────────────────────
|
| 20 |
+
# On veut une fonction continue sur [xMin, xMax] avec :
|
| 21 |
+
# - un minimum global en (x2, y2)
|
| 22 |
+
# - un maximum global en (x3, y3)
|
| 23 |
+
# - décroissante sur [xMin, x2], croissante sur [x2, x3], décroissante sur [x3, xMax]
|
| 24 |
+
#
|
| 25 |
+
# On tire d'abord les extremums (la réponse), puis on place les points de départ
|
| 26 |
+
# et d'arrivée de manière cohérente (y1 et y4 entre le min et le max).
|
| 27 |
+
|
| 28 |
+
xMin = rd.randint(-6, -3)
|
| 29 |
+
xMax = rd.randint(4, 7)
|
| 30 |
+
|
| 31 |
+
# Abscisses des extremums : x2 < x3
|
| 32 |
+
x2 = rd.randint(xMin + 2, xMin + 4)
|
| 33 |
+
x3 = rd.randint(x2 + 2, xMax - 2) if xMax - 2 >= x2 + 2 else x2 + 2
|
| 34 |
+
|
| 35 |
+
# Valeurs des extremums (la réponse)
|
| 36 |
+
y2 = rd.randint(-3, -1) # minimum
|
| 37 |
+
y3 = rd.randint(3, 5) # maximum
|
| 38 |
+
|
| 39 |
+
minVal = y2
|
| 40 |
+
maxVal = y3
|
| 41 |
+
|
| 42 |
+
# Points de départ et d'arrivée : ordonnées entre min et max
|
| 43 |
+
y1 = rd.randint(2, 4) # au-dessus du min, en dessous du max
|
| 44 |
+
y4 = rd.randint(0, 2) # idem
|
| 45 |
+
|
| 46 |
+
# Points remarquables pour le tracé
|
| 47 |
+
x1 = xMin
|
| 48 |
+
x4 = xMax
|
| 49 |
+
|
| 50 |
+
# Question 3 : comparaison par monotonie sur l'intervalle de croissance [x2, x3]
|
| 51 |
+
# On tire xComp1 < xComp2 dans cet intervalle -> f(xComp1) < f(xComp2)
|
| 52 |
+
xComp1 = rd.randint(x2 + 1, x3 - 2) if x3 - x2 > 2 else x2 + 1
|
| 53 |
+
xComp2 = x3
|
| 54 |
+
# ──────────────────────────────────────────────────────────────────────────
|
| 55 |
+
# MÉTHODE CONSTRUCTIVE — partir de la réponse (la comparaison par monotonie)
|
| 56 |
+
# ──────────────────────────────────────────────────────────────────────────
|
| 57 |
+
# La réponse attendue est : f(xComp1) < f(xComp2) car f est croissante sur [x2, x3]
|
| 58 |
+
# et xComp1 < xComp2 appartiennent à cet intervalle.
|
| 59 |
+
#
|
| 60 |
+
# Les variables xMin, xMax, x2, x3, y2, y3, minVal, maxVal, xComp1, xComp2
|
| 61 |
+
# sont déjà définies dans le bloc précédent. On les réutilise pour construire
|
| 62 |
+
# l'énoncé de cette question.
|
| 63 |
+
|
| 64 |
+
# === Ajouts conversion FGQ ===
|
| 65 |
+
minValSolAff = str(minVal)
|
| 66 |
+
x2SolAff = str(x2)
|
| 67 |
+
maxValSolAff = str(maxVal)
|
| 68 |
+
x3SolAff = str(x3)
|
| 69 |
+
globals()
|
| 70 |
+
````
|
| 71 |
+
:::::{question}
|
| 72 |
+
:questionType: MCQ
|
| 73 |
+
|
| 74 |
+
::::{questionStatement}
|
| 75 |
+
{fr}`La figure ci-dessous représente la courbe d'une fonction`{en}`The figure below represents the curve of a function` $f$ {fr}`définie sur`{en}`defined on` $[{{ xMin }}\,;\,{{ xMax }}]$. {fr}`Lire les intervalles sur lesquels`{en}`Read the intervals on which` $f$ {fr}`est croissante, puis ceux sur lesquels`{en}`is increasing, then those on which` $f$ {fr}`est décroissante.`{en}`is decreasing.`
|
| 76 |
+
|
| 77 |
+
```{python}
|
| 78 |
+
import matplotlib.pyplot as plt
|
| 79 |
+
import numpy as np
|
| 80 |
+
|
| 81 |
+
def bez(p0, p1, p2, p3, t):
|
| 82 |
+
return (1-t)**3*p0 + 3*(1-t)**2*t*p1 + 3*(1-t)*t**2*p2 + t**3*p3
|
| 83 |
+
|
| 84 |
+
t = np.linspace(0, 1, 60)
|
| 85 |
+
fig, ax = plt.subplots(figsize=(6, 4.5))
|
| 86 |
+
segments = [((x1, y1), (x1+1, (y1+y2)/2), (x2-1, y2+0.5), (x2, y2)),
|
| 87 |
+
((x2, y2), ((x2+x3)/2, (y2+y3)/2), (x3-1, y3-0.5), (x3, y3)),
|
| 88 |
+
((x3, y3), (x3+1, (y3+y4)/2), (x4-1, y4+0.5), (x4, y4))]
|
| 89 |
+
for P0, C1, C2, P1 in segments:
|
| 90 |
+
ax.plot(bez(P0[0], C1[0], C2[0], P1[0], t), bez(P0[1], C1[1], C2[1], P1[1], t),
|
| 91 |
+
color="#1f77b4", linewidth=2)
|
| 92 |
+
pts = [(x1, y1, "right", "bottom"), (x2, y2, "left", "top"),
|
| 93 |
+
(x3, y3, "left", "bottom"), (x4, y4, "left", "top")]
|
| 94 |
+
for px, py, ha, va in pts:
|
| 95 |
+
ax.plot(px, py, "o", color="red", markersize=5)
|
| 96 |
+
ax.annotate(f"$({px}\\,;\\,{py})$", (px, py), textcoords="offset points",
|
| 97 |
+
xytext=(6 if ha == "left" else -6, 6 if va == "bottom" else -10),
|
| 98 |
+
ha=ha, fontsize=9)
|
| 99 |
+
ax.axhline(0, color="k", linewidth=0.8)
|
| 100 |
+
ax.axvline(0, color="k", linewidth=0.8)
|
| 101 |
+
ax.set_xticks(range(xMin, xMax+1))
|
| 102 |
+
ax.set_yticks(range(y2, y3+1))
|
| 103 |
+
ax.set_xlabel("$x$")
|
| 104 |
+
ax.set_ylabel("$y$")
|
| 105 |
+
ax.grid(True, alpha=0.3)
|
| 106 |
+
plt.show()
|
| 107 |
+
```
|
| 108 |
+
::::
|
| 109 |
+
|
| 110 |
+
::::{questionHint}
|
| 111 |
+
{fr}`Lire les intervalles de croissance (courbe montante) et de décroissance (courbe descendante) directement sur la courbe.`{en}`Read the intervals of increase (rising curve) and decrease (falling curve) directly from the curve.`
|
| 112 |
+
::::
|
| 113 |
+
|
| 114 |
+
::::{mcqAnswer}
|
| 115 |
+
:isRightAnswer: true
|
| 116 |
+
{fr}`Croissante sur`{en}`Increasing on` $[{{ x2 }}\,;\,{{ x3 }}]$ {fr}`; décroissante sur`{en}`; decreasing on` $[{{ xMin }}\,;\,{{ x2 }}]$ {fr}`et`{en}`and` $[{{ x3 }}\,;\,{{ xMax }}]$
|
| 117 |
+
::::
|
| 118 |
+
|
| 119 |
+
::::{mcqAnswer}
|
| 120 |
+
:isRightAnswer: false
|
| 121 |
+
{fr}`Décroissante sur`{en}`Decreasing on` $[{{ x2 }}\,;\,{{ x3 }}]$ {fr}`; croissante sur`{en}`; increasing on` $[{{ xMin }}\,;\,{{ x2 }}]$ {fr}`et`{en}`and` $[{{ x3 }}\,;\,{{ xMax }}]$
|
| 122 |
+
::::
|
| 123 |
+
|
| 124 |
+
::::{mcqAnswer}
|
| 125 |
+
:isRightAnswer: false
|
| 126 |
+
{fr}`Croissante sur tout`{en}`Increasing on the whole` $[{{ xMin }}\,;\,{{ xMax }}]$
|
| 127 |
+
::::
|
| 128 |
+
|
| 129 |
+
::::{mcqAnswer}
|
| 130 |
+
:isRightAnswer: false
|
| 131 |
+
{fr}`Aucune de ces réponses n'est correcte`{en}`None of these answers are correct`
|
| 132 |
+
::::
|
| 133 |
+
|
| 134 |
+
::::{detailedSolution}
|
| 135 |
+
{fr}`La courbe descend de`{en}`The curve falls from` $x={{ xMin }}$ {fr}`à`{en}`to` $x={{ x2 }}$ : $f$ {fr}`est décroissante sur`{en}`is decreasing on` $[{{ xMin }}\,;\,{{ x2 }}]$. {fr}`Elle monte de`{en}`It rises from` $x={{ x2 }}$ {fr}`à`{en}`to` $x={{ x3 }}$ : $f$ {fr}`est croissante sur`{en}`is increasing on` $[{{ x2 }}\,;\,{{ x3 }}]$. {fr}`Elle redescend de`{en}`It falls again from` $x={{ x3 }}$ {fr}`à`{en}`to` $x={{ xMax }}$ : $f$ {fr}`est décroissante sur`{en}`is decreasing on` $[{{ x3 }}\,;\,{{ xMax }}]$.
|
| 136 |
+
::::
|
| 137 |
+
|
| 138 |
+
::::{weightDistribution}
|
| 139 |
+
:logic: 15
|
| 140 |
+
:abstraction: 25
|
| 141 |
+
:reasoning: 35
|
| 142 |
+
:calculation: 25
|
| 143 |
+
::::
|
| 144 |
+
:::::
|
| 145 |
+
|
| 146 |
+
:::::{question}
|
| 147 |
+
:questionType: FGQ
|
| 148 |
+
:solution: [["ord","${{ minValSolAff }}$","${{ x2SolAff }}$","${{ maxValSolAff }}$","${{ x3SolAff }}$"],["0","0","0","0"]]
|
| 149 |
+
|
| 150 |
+
::::{questionStatement}
|
| 151 |
+
{fr}`Lire le minimum et le maximum de`{en}`Read the minimum and maximum of` $f$ {fr}`sur`{en}`on` $[{{ xMin }}\,;\,{{ xMax }}]$, {fr}`et préciser les abscisses où ils sont atteints.`{en}`and specify the abscissas where they are attained.`
|
| 152 |
+
|
| 153 |
+
{fr}`minimum`{en}`minimum` $=$ {input}`||60` {fr}`atteint en`{en}`attained at` $x =$ {input}`||60` {fr}`; maximum`{en}`; maximum` $=$ {input}`||60` {fr}`atteint en`{en}`attained at` $x =$ {input}`||60`
|
| 154 |
+
::::
|
| 155 |
+
|
| 156 |
+
::::{questionHint}
|
| 157 |
+
{fr}`Repérer le point le plus bas et le point le plus haut de la courbe.`{en}`Identify the lowest point and the highest point of the curve.`
|
| 158 |
+
::::
|
| 159 |
+
|
| 160 |
+
::::{displayedSolution}
|
| 161 |
+
{fr}`minimum`{en}`minimum` $= {{ minVal }}$ {fr}`atteint en`{en}`attained at` $x = {{ x2 }}$ ; {fr}`maximum`{en}`maximum` $= {{ maxVal }}$ {fr}`atteint en`{en}`attained at` $x = {{ x3 }}$
|
| 162 |
+
::::
|
| 163 |
+
|
| 164 |
+
::::{detailedSolution}
|
| 165 |
+
{fr}`Le point le plus bas est`{en}`The lowest point is` $({{ x2 }}\,;\,{{ minVal }})$ : {fr}`le minimum de`{en}`the minimum of` $f$ {fr}`sur`{en}`on` $[{{ xMin }}\,;\,{{ xMax }}]$ {fr}`est`{en}`is` ${{ minVal }}$, {fr}`atteint en`{en}`attained at` $x={{ x2 }}$. {fr}`Le point le plus haut est`{en}`The highest point is` $({{ x3 }}\,;\,{{ maxVal }})$ : {fr}`le maximum est`{en}`the maximum is` ${{ maxVal }}$, {fr}`atteint en`{en}`attained at` $x={{ x3 }}$.
|
| 166 |
+
::::
|
| 167 |
+
|
| 168 |
+
::::{weightDistribution}
|
| 169 |
+
:logic: 15
|
| 170 |
+
:abstraction: 25
|
| 171 |
+
:reasoning: 35
|
| 172 |
+
:calculation: 25
|
| 173 |
+
::::
|
| 174 |
+
:::::
|
| 175 |
+
|
| 176 |
+
:::::{question}
|
| 177 |
+
:questionType: MCQ
|
| 178 |
+
|
| 179 |
+
::::{questionStatement}
|
| 180 |
+
{fr}`Justifier, à partir du sens de variation, que`{en}`Justify, from the monotonicity, that` $f({{ xComp1 }}) < f({{ xComp2 }})$.
|
| 181 |
+
::::
|
| 182 |
+
|
| 183 |
+
::::{questionHint}
|
| 184 |
+
{fr}`Observer que`{en}`Observe that` ${{ xComp1 }}$ {fr}`et`{en}`and` ${{ xComp2 }}$ {fr}`appartiennent au même intervalle de croissance.`{en}`belong to the same interval of increase.`
|
| 185 |
+
::::
|
| 186 |
+
|
| 187 |
+
::::{mcqAnswer}
|
| 188 |
+
:isRightAnswer: true
|
| 189 |
+
{fr}`$f$ est croissante sur`{en}`$f$ is increasing on` $[{{ x2 }}\,;\,{{ x3 }}]$ {fr}`qui contient`{en}`which contains` ${{ xComp1 }}$ {fr}`et`{en}`and` ${{ xComp2 }}$, {fr}`et`{en}`and` ${{ xComp1 }}<{{ xComp2 }}$
|
| 190 |
+
::::
|
| 191 |
+
|
| 192 |
+
::::{mcqAnswer}
|
| 193 |
+
:isRightAnswer: false
|
| 194 |
+
{fr}`$f$ est décroissante sur cet intervalle`{en}`$f$ is decreasing on this interval`
|
| 195 |
+
::::
|
| 196 |
+
|
| 197 |
+
::::{mcqAnswer}
|
| 198 |
+
:isRightAnswer: false
|
| 199 |
+
{fr}`$f$ est constante sur cet intervalle`{en}`$f$ is constant on this interval`
|
| 200 |
+
::::
|
| 201 |
+
|
| 202 |
+
::::{mcqAnswer}
|
| 203 |
+
:isRightAnswer: false
|
| 204 |
+
{fr}`Aucune de ces réponses n'est correcte`{en}`None of these answers are correct`
|
| 205 |
+
::::
|
| 206 |
+
|
| 207 |
+
::::{detailedSolution}
|
| 208 |
+
{fr}`On a`{en}`We have` ${{ xComp1 }}<{{ xComp2 }}$, {fr}`et`{en}`and` $f$ {fr}`est croissante sur`{en}`is increasing on` $[{{ x2 }}\,;\,{{ x3 }}]$, {fr}`intervalle qui contient`{en}`an interval that contains` ${{ xComp1 }}$ {fr}`et`{en}`and` ${{ xComp2 }}$. {fr}`Par définition de la croissance,`{en}`By definition of increasing function,` ${{ xComp1 }}<{{ xComp2 }}$ {fr}`entraîne`{en}`implies` $f({{ xComp1 }})<f({{ xComp2 }})$.
|
| 209 |
+
::::
|
| 210 |
+
|
| 211 |
+
::::{weightDistribution}
|
| 212 |
+
:logic: 25
|
| 213 |
+
:abstraction: 30
|
| 214 |
+
:reasoning: 35
|
| 215 |
+
:calculation: 10
|
| 216 |
+
::::
|
| 217 |
+
:::::
|
| 218 |
+
`````
|
app/knowledge/fewshots/declinaisons/{fr}`Monotonie selon la base et comparaison de valeurs`{en}`Monotonicity according to base and comparison of values`.md
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
`````{exercise}
|
| 2 |
+
:title: {fr}`Monotonie selon la base et comparaison de valeurs`{en}`Monotonicity according to base and comparison of values`
|
| 3 |
+
:modules:
|
| 4 |
+
:recommendedExecutionTime: 10
|
| 5 |
+
:level: Elementary
|
| 6 |
+
:chap: chap_expLogFunctions_exponentialFunctions_ESCP
|
| 7 |
+
:involvedConcepts:
|
| 8 |
+
:originalSource:
|
| 9 |
+
:visibility: All
|
| 10 |
+
:variations:
|
| 11 |
+
:comment: Échauffement (thème pur) : reconnaissance de la monotonie de quatre fonctions b^x d'après la base, et comparaison de valeurs par la monotonie (sans calcul exact).
|
| 12 |
+
:id: 73274236-74d8-11f1-a8a1-0ed8d3b012a9
|
| 13 |
+
:originalExerciseId: 699e40c5-6597-11f1-a8a1-0ed8d3b012a9
|
| 14 |
+
|
| 15 |
+
````{python}
|
| 16 |
+
import random as rd
|
| 17 |
+
from fractions import Fraction
|
| 18 |
+
|
| 19 |
+
def frac_latex(fr):
|
| 20 |
+
fr = Fraction(fr)
|
| 21 |
+
if fr.denominator == 1:
|
| 22 |
+
return str(fr.numerator)
|
| 23 |
+
sign = '-' if fr.numerator < 0 else ''
|
| 24 |
+
return sign + r'\dfrac{%d}{%d}' % (abs(fr.numerator), fr.denominator)
|
| 25 |
+
|
| 26 |
+
# Construction déterministe : monotonies garanties par les intervalles des bases.
|
| 27 |
+
b1 = rd.randint(2, 5) # base entière > 1 -> croissante
|
| 28 |
+
b2Den = rd.randint(2, 5)
|
| 29 |
+
b2 = Fraction(1, b2Den) # base dans (0,1) -> décroissante
|
| 30 |
+
b3Den = rd.randint(2, 5)
|
| 31 |
+
b3Num = rd.randint(b3Den + 1, 9)
|
| 32 |
+
b3 = Fraction(b3Num, b3Den) # base > 1 -> croissante
|
| 33 |
+
b4Dec = rd.randint(6, 9)
|
| 34 |
+
b4 = Fraction(b4Dec, 10) # base décimale dans (0,1) -> décroissante
|
| 35 |
+
|
| 36 |
+
# Exposants pour Q2 (>= 2 pour éviter les exposants 0 et 1)
|
| 37 |
+
x1 = rd.randint(2, 4)
|
| 38 |
+
x2 = rd.randint(x1 + 2, x1 + 4)
|
| 39 |
+
|
| 40 |
+
# Rendus
|
| 41 |
+
b1Aff = str(b1)
|
| 42 |
+
b2Aff = frac_latex(b2)
|
| 43 |
+
b3Aff = frac_latex(b3)
|
| 44 |
+
b4Aff = '0{,}%d' % b4Dec
|
| 45 |
+
x1Aff = str(x1)
|
| 46 |
+
x2Aff = str(x2)
|
| 47 |
+
f1x1Aff = frac_latex(b1**x1)
|
| 48 |
+
f1x2Aff = frac_latex(b1**x2)
|
| 49 |
+
f2x1Aff = frac_latex(b2**x1)
|
| 50 |
+
f2x2Aff = frac_latex(b2**x2)
|
| 51 |
+
|
| 52 |
+
globals()
|
| 53 |
+
````
|
| 54 |
+
|
| 55 |
+
:::::{question}
|
| 56 |
+
:questionType: MCQ
|
| 57 |
+
:questionId: 0
|
| 58 |
+
:questionIndex: 0
|
| 59 |
+
|
| 60 |
+
::::{questionStatement}
|
| 61 |
+
{fr}`On considère`{en}`Consider` $f_{1}(x)={{ b1Aff }}^{x}$, $f_{2}(x)=\left({{ b2Aff }}\right)^{x}$, $f_{3}(x)=\left({{ b3Aff }}\right)^{x}$ {fr}`et`{en}`and` $f_{4}(x)=({{ b4Aff }})^{x}$. {fr}`Reconnaître lesquelles sont strictement croissantes et lesquelles sont strictement décroissantes, en justifiant à partir de la base.`{en}`Identify which are strictly increasing and which are strictly decreasing, justifying from the base.`
|
| 62 |
+
::::
|
| 63 |
+
|
| 64 |
+
::::{questionHint}
|
| 65 |
+
{fr}`Si`{en}`If` $b>1$ {fr}`: croissante ; si`{en}`: increasing; if` $0<b<1$ {fr}`: décroissante.`{en}`: decreasing.`
|
| 66 |
+
::::
|
| 67 |
+
|
| 68 |
+
::::{mcqAnswer}
|
| 69 |
+
:isRightAnswer: true
|
| 70 |
+
{fr}`$f_{1}$ et $f_{3}$ strictement croissantes ; $f_{2}$ et $f_{4}$ strictement décroissantes`{en}`$f_{1}$ and $f_{3}$ strictly increasing; $f_{2}$ and $f_{4}$ strictly decreasing`
|
| 71 |
+
::::
|
| 72 |
+
|
| 73 |
+
::::{mcqAnswer}
|
| 74 |
+
:isRightAnswer: false
|
| 75 |
+
{fr}`$f_{1}$ et $f_{2}$ strictement croissantes ; $f_{3}$ et $f_{4}$ strictement décroissantes`{en}`$f_{1}$ and $f_{2}$ strictly increasing; $f_{3}$ and $f_{4}$ strictly decreasing`
|
| 76 |
+
::::
|
| 77 |
+
|
| 78 |
+
::::{mcqAnswer}
|
| 79 |
+
:isRightAnswer: false
|
| 80 |
+
{fr}`$f_{2}$ et $f_{4}$ strictement croissantes ; $f_{1}$ et $f_{3}$ strictement décroissantes`{en}`$f_{2}$ and $f_{4}$ strictly increasing; $f_{1}$ and $f_{3}$ strictly decreasing`
|
| 81 |
+
::::
|
| 82 |
+
|
| 83 |
+
::::{mcqAnswer}
|
| 84 |
+
:isRightAnswer: false
|
| 85 |
+
{fr}`Les quatre fonctions sont strictement croissantes`{en}`All four functions are strictly increasing`
|
| 86 |
+
::::
|
| 87 |
+
|
| 88 |
+
::::{mcqAnswer}
|
| 89 |
+
:isRightAnswer: false
|
| 90 |
+
{fr}`Aucune de ces réponses n'est correcte`{en}`None of these answers are correct`
|
| 91 |
+
::::
|
| 92 |
+
|
| 93 |
+
::::{detailedSolution}
|
| 94 |
+
$f_{1}$ {fr}`: base`{en}`: base` ${{ b1Aff }}>1$, {fr}`strictement croissante.`{en}`strictly increasing.` $f_{2}$ {fr}`: base`{en}`: base` ${{ b2Aff }}\in(0,1)$, {fr}`strictement décroissante.`{en}`strictly decreasing.` $f_{3}$ {fr}`: base`{en}`: base` ${{ b3Aff }}>1$, {fr}`strictement croissante.`{en}`strictly increasing.` $f_{4}$ {fr}`: base`{en}`: base` ${{ b4Aff }}\in(0,1)$, {fr}`strictement décroissante.`{en}`strictly decreasing.`
|
| 95 |
+
::::
|
| 96 |
+
|
| 97 |
+
::::{weightDistribution}
|
| 98 |
+
:logic: 25
|
| 99 |
+
:abstraction: 35
|
| 100 |
+
:reasoning: 30
|
| 101 |
+
:calculation: 10
|
| 102 |
+
::::
|
| 103 |
+
:::::
|
| 104 |
+
|
| 105 |
+
:::::{question}
|
| 106 |
+
:questionType: MCQ
|
| 107 |
+
:questionId: 1
|
| 108 |
+
:questionIndex: 1
|
| 109 |
+
|
| 110 |
+
::::{questionStatement}
|
| 111 |
+
{fr}`Comparer`{en}`Compare` $f_{1}({{ x1Aff }})$ {fr}`et`{en}`and` $f_{1}({{ x2Aff }})$, {fr}`puis`{en}`then` $f_{2}({{ x1Aff }})$ {fr}`et`{en}`and` $f_{2}({{ x2Aff }})$, {fr}`sans calculer les valeurs exactes.`{en}`without calculating the exact values.`
|
| 112 |
+
::::
|
| 113 |
+
|
| 114 |
+
::::{questionHint}
|
| 115 |
+
{fr}`Utiliser la monotonie :`{en}`Use monotonicity:` ${{ x1Aff }}<{{ x2Aff }}$.
|
| 116 |
+
::::
|
| 117 |
+
|
| 118 |
+
::::{mcqAnswer}
|
| 119 |
+
:isRightAnswer: true
|
| 120 |
+
$f_{1}({{ x1Aff }}) < f_{1}({{ x2Aff }})$ {fr}`et`{en}`and` $f_{2}({{ x1Aff }}) > f_{2}({{ x2Aff }})$
|
| 121 |
+
::::
|
| 122 |
+
|
| 123 |
+
::::{mcqAnswer}
|
| 124 |
+
:isRightAnswer: false
|
| 125 |
+
$f_{1}({{ x1Aff }}) > f_{1}({{ x2Aff }})$ {fr}`et`{en}`and` $f_{2}({{ x1Aff }}) < f_{2}({{ x2Aff }})$
|
| 126 |
+
::::
|
| 127 |
+
|
| 128 |
+
::::{mcqAnswer}
|
| 129 |
+
:isRightAnswer: false
|
| 130 |
+
$f_{1}({{ x1Aff }}) < f_{1}({{ x2Aff }})$ {fr}`et`{en}`and` $f_{2}({{ x1Aff }}) < f_{2}({{ x2Aff }})$
|
| 131 |
+
::::
|
| 132 |
+
|
| 133 |
+
::::{mcqAnswer}
|
| 134 |
+
:isRightAnswer: false
|
| 135 |
+
$f_{1}({{ x1Aff }}) > f_{1}({{ x2Aff }})$ {fr}`et`{en}`and` $f_{2}({{ x1Aff }}) > f_{2}({{ x2Aff }})$
|
| 136 |
+
::::
|
| 137 |
+
|
| 138 |
+
::::{mcqAnswer}
|
| 139 |
+
:isRightAnswer: false
|
| 140 |
+
{fr}`Aucune de ces réponses n'est correcte`{en}`None of these answers are correct`
|
| 141 |
+
::::
|
| 142 |
+
|
| 143 |
+
::::{detailedSolution}
|
| 144 |
+
$f_{1}$ {fr}`est strictement croissante et`{en}`is strictly increasing and` ${{ x1Aff }}<{{ x2Aff }}$, {fr}`donc`{en}`so` $f_{1}({{ x1Aff }})<f_{1}({{ x2Aff }})$ ({fr}`soit`{en}`i.e.` ${{ b1Aff }}^{ {{ x1Aff }} }<{{ b1Aff }}^{ {{ x2Aff }} }$, {fr}`c'est-à-dire`{en}`that is` ${{ f1x1Aff }}<{{ f1x2Aff }}$).\
|
| 145 |
+
\
|
| 146 |
+
$f_{2}$ {fr}`est strictement décroissante et`{en}`is strictly decreasing and` ${{ x1Aff }}<{{ x2Aff }}$, {fr}`donc`{en}`so` $f_{2}({{ x1Aff }})>f_{2}({{ x2Aff }})$ ({fr}`soit`{en}`i.e.` $\left({{ b2Aff }}\right)^{ {{ x1Aff }} }>\left({{ b2Aff }}\right)^{ {{ x2Aff }} }$, {fr}`c'est-à-dire`{en}`that is` ${{ f2x1Aff }}>{{ f2x2Aff }}$).
|
| 147 |
+
::::
|
| 148 |
+
|
| 149 |
+
::::{weightDistribution}
|
| 150 |
+
:logic: 25
|
| 151 |
+
:abstraction: 30
|
| 152 |
+
:reasoning: 35
|
| 153 |
+
:calculation: 10
|
| 154 |
+
::::
|
| 155 |
+
:::::
|
| 156 |
+
|
| 157 |
+
`````
|
app/knowledge/fewshots/declinaisons/{fr}`Objectif de revenu`{en}`Income Target`.md
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
`````{exercise}
|
| 2 |
+
:originalExerciseId: 3fb67117-634b-11f1-a8a1-0ed8d3b012a9
|
| 3 |
+
:title: {fr}`Objectif de revenu`{en}`Income Target`
|
| 4 |
+
:modules:
|
| 5 |
+
:recommendedExecutionTime: 10
|
| 6 |
+
:level: Elementary
|
| 7 |
+
:chap:
|
| 8 |
+
:involvedConcepts: Solving_inequalities, Modeling_with_Functions
|
| 9 |
+
:originalSource: Session 2: Equations and Inequalities - Applications of Linear Inequalities (Erwan Lamy, ESCP Business School)
|
| 10 |
+
:visibility: All
|
| 11 |
+
:variations:
|
| 12 |
+
:comment: Version QCM — inéquation « au moins », résolution et intervalle.
|
| 13 |
+
:id: b3ceb29c-6e47-11f1-a8a1-0ed8d3b012a9
|
| 14 |
+
|
| 15 |
+
````{python}
|
| 16 |
+
import random as rd
|
| 17 |
+
from pyxiscience.Mes_fctions_generalistes_bis import pxs_config, pxsl_format_number
|
| 18 |
+
from pyxiscience.Classes_Extensions import pxs_Interval
|
| 19 |
+
|
| 20 |
+
config_standard = pxs_config()
|
| 21 |
+
|
| 22 |
+
# Tarif horaire (multiple de 5) et heures cibles ; revenu = rate * hMin (entier)
|
| 23 |
+
rate = rd.choice([50, 55, 60, 65, 70, 75, 80, 85, 90, 95, 100, 110, 120, 125, 150])
|
| 24 |
+
hMin = rd.randint(15, 50)
|
| 25 |
+
target = rate * hMin
|
| 26 |
+
|
| 27 |
+
# Précalcul de l'affichage
|
| 28 |
+
rateAff = pxsl_format_number(rate)
|
| 29 |
+
hMinAff = pxsl_format_number(hMin)
|
| 30 |
+
targetAff = pxsl_format_number(target)
|
| 31 |
+
|
| 32 |
+
inter = pxs_Interval(hMin, 168, False, False)
|
| 33 |
+
|
| 34 |
+
# === Distracteurs MCQ (ajouts ; le code ci-dessus est inchangé) ===
|
| 35 |
+
from sympy import oo
|
| 36 |
+
interCorrect = inter.print()
|
| 37 |
+
interD1 = pxs_Interval(hMin, 168, True, True).print()
|
| 38 |
+
interD2 = pxs_Interval(0, hMin, False, False).print()
|
| 39 |
+
interD3 = pxs_Interval(hMin, oo, False, True).print()
|
| 40 |
+
|
| 41 |
+
globals()
|
| 42 |
+
````
|
| 43 |
+
|
| 44 |
+
{fr}`Une consultante indépendante facture`{en}`An independent consultant charges` ${{rateAff}}$ € {fr}`de l'heure. Elle souhaite que son revenu hebdomadaire soit d'au moins`{en}`per hour. She wants her weekly income to be at least` ${{targetAff}}$ €. {fr}`On note`{en}`Let` $h$ {fr}`le nombre d'heures travaillées par semaine.`{en}`be the number of hours worked per week.`
|
| 45 |
+
|
| 46 |
+
:::::{question}
|
| 47 |
+
:questionType: MCQ
|
| 48 |
+
|
| 49 |
+
::::{questionStatement}
|
| 50 |
+
{fr}`Quelle inéquation traduit son objectif de revenu ?`{en}`Which inequality represents her income target?`
|
| 51 |
+
::::
|
| 52 |
+
|
| 53 |
+
::::{questionHint}
|
| 54 |
+
{fr}`« Au moins » se traduit par`{en}`"At least" translates to` $\geq$. {fr}`Son revenu hebdomadaire est`{en}`Her weekly income is` ${{rateAff}}\,h$.
|
| 55 |
+
::::
|
| 56 |
+
|
| 57 |
+
::::{mcqAnswer}
|
| 58 |
+
:isRightAnswer: true
|
| 59 |
+
${{rateAff}}\,h \geq {{targetAff}}$
|
| 60 |
+
::::
|
| 61 |
+
|
| 62 |
+
::::{mcqAnswer}
|
| 63 |
+
:isRightAnswer: false
|
| 64 |
+
${{rateAff}}\,h \leq {{targetAff}}$
|
| 65 |
+
::::
|
| 66 |
+
|
| 67 |
+
::::{mcqAnswer}
|
| 68 |
+
:isRightAnswer: false
|
| 69 |
+
${{rateAff}}\,h = {{targetAff}}$
|
| 70 |
+
::::
|
| 71 |
+
|
| 72 |
+
::::{mcqAnswer}
|
| 73 |
+
:isRightAnswer: false
|
| 74 |
+
${{targetAff}}\,h \geq {{rateAff}}$
|
| 75 |
+
::::
|
| 76 |
+
|
| 77 |
+
::::{mcqAnswer}
|
| 78 |
+
:isRightAnswer: false
|
| 79 |
+
${{targetAff}}\,h \leq {{rateAff}}$
|
| 80 |
+
::::
|
| 81 |
+
|
| 82 |
+
::::{mcqAnswer}
|
| 83 |
+
:isRightAnswer: false
|
| 84 |
+
{fr}`Aucune de ces réponses n'est correcte`{en}`None of these answers are correct`
|
| 85 |
+
::::
|
| 86 |
+
|
| 87 |
+
::::{detailedSolution}
|
| 88 |
+
{fr}`Le revenu hebdomadaire est`{en}`The weekly income is` ${{rateAff}}\,h$ {fr}`euros. L'objectif « revenu`{en}`euros. The target "income` $\geq {{targetAff}}$ {fr}`» donne :`{en}`" gives:`
|
| 89 |
+
|
| 90 |
+
\begin{equation*}
|
| 91 |
+
{{rateAff}}\,h \geq {{targetAff}}.
|
| 92 |
+
\end{equation*}
|
| 93 |
+
::::
|
| 94 |
+
|
| 95 |
+
::::{weightDistribution}
|
| 96 |
+
:logic: 15
|
| 97 |
+
:abstraction: 30
|
| 98 |
+
:reasoning: 35
|
| 99 |
+
:calculation: 20
|
| 100 |
+
::::
|
| 101 |
+
:::::
|
| 102 |
+
|
| 103 |
+
:::::{question}
|
| 104 |
+
:questionType: MCQ
|
| 105 |
+
|
| 106 |
+
::::{questionStatement}
|
| 107 |
+
{fr}`Résoudre l'inéquation`{en}`Solve the inequality` ${{rateAff}}\,h \geq {{targetAff}}$ {fr}`(sachant qu'une semaine compte 168 heures). Quel est l'ensemble des solutions sous forme d'intervalle ?`{en}`(knowing a week has 168 hours). What is the solution set in interval form?`
|
| 108 |
+
::::
|
| 109 |
+
|
| 110 |
+
::::{questionHint}
|
| 111 |
+
{fr}`Diviser les deux membres par`{en}`Divide both sides by` ${{rateAff}}$, {fr}`qui est positif : le sens de l'inégalité est conservé.`{en}`which is positive: the inequality sign is preserved.`
|
| 112 |
+
::::
|
| 113 |
+
|
| 114 |
+
::::{mcqAnswer}
|
| 115 |
+
:isRightAnswer: true
|
| 116 |
+
${{interCorrect}}$
|
| 117 |
+
::::
|
| 118 |
+
|
| 119 |
+
::::{mcqAnswer}
|
| 120 |
+
:isRightAnswer: false
|
| 121 |
+
${{interD1}}$
|
| 122 |
+
::::
|
| 123 |
+
|
| 124 |
+
::::{mcqAnswer}
|
| 125 |
+
:isRightAnswer: false
|
| 126 |
+
${{interD2}}$
|
| 127 |
+
::::
|
| 128 |
+
|
| 129 |
+
::::{mcqAnswer}
|
| 130 |
+
:isRightAnswer: false
|
| 131 |
+
${{interD3}}$
|
| 132 |
+
::::
|
| 133 |
+
|
| 134 |
+
::::{mcqAnswer}
|
| 135 |
+
:isRightAnswer: false
|
| 136 |
+
{fr}`Aucune de ces réponses n'est correcte`{en}`None of these answers are correct`
|
| 137 |
+
::::
|
| 138 |
+
|
| 139 |
+
::::{detailedSolution}
|
| 140 |
+
{fr}`On divise les deux membres par`{en}`We divide both sides by` ${{rateAff}} > 0$ {fr}`(sens conservé) :`{en}`(sign preserved):`
|
| 141 |
+
|
| 142 |
+
\begin{equation*}
|
| 143 |
+
h \geq \dfrac{ {{targetAff}} }{ {{rateAff}} } = {{hMinAff}}.
|
| 144 |
+
\end{equation*}
|
| 145 |
+
|
| 146 |
+
{fr}`L'ensemble des solutions est :`{en}`The solution set is:`
|
| 147 |
+
|
| 148 |
+
\begin{equation*}
|
| 149 |
+
h \in {{inter.print()}}.
|
| 150 |
+
\end{equation*}
|
| 151 |
+
|
| 152 |
+
{fr}`**Conclusion.** La consultante doit travailler au moins`{en}`**Conclusion.** The consultant must work at least` ${{hMinAff}}$ {fr}`heures par semaine pour atteindre son objectif de revenu. Elle ne peut pas travailler plus de 168 heures car cela correspond au nombre d'heures dans une semaine.`{en}`hours per week to reach her income target. She cannot work more than 168 hours as this corresponds to the number of hours in a week.`
|
| 153 |
+
::::
|
| 154 |
+
|
| 155 |
+
::::{weightDistribution}
|
| 156 |
+
:logic: 10
|
| 157 |
+
:abstraction: 15
|
| 158 |
+
:reasoning: 25
|
| 159 |
+
:calculation: 50
|
| 160 |
+
::::
|
| 161 |
+
:::::
|
| 162 |
+
|
| 163 |
+
`````
|
app/knowledge/fewshots/declinaisons/{fr}`Plafond budgétaire`{en}`Budget cap`.md
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
`````{exercise}
|
| 2 |
+
:originalExerciseId: ccace217-633f-11f1-a8a1-0ed8d3b012a9
|
| 3 |
+
:title: {fr}`Plafond budgétaire`{en}`Budget cap`
|
| 4 |
+
:modules:
|
| 5 |
+
:recommendedExecutionTime: 10
|
| 6 |
+
:level: Elementary
|
| 7 |
+
:chap:
|
| 8 |
+
:involvedConcepts: Solving_inequalities
|
| 9 |
+
:originalSource: Session 2: Equations and Inequalities - Applications of Linear Inequalities (Erwan Lamy, ESCP Business School)
|
| 10 |
+
:visibility: All
|
| 11 |
+
:variations:
|
| 12 |
+
:comment: Version QCM — contrainte « ne pas dépasser », résolution et intervalle.
|
| 13 |
+
:id: c8218be2-6e47-11f1-a8a1-0ed8d3b012a9
|
| 14 |
+
|
| 15 |
+
````{python}
|
| 16 |
+
import random as rd
|
| 17 |
+
from pyxiscience.Mes_fctions_generalistes_bis import pxs_config
|
| 18 |
+
from pyxiscience.Classes_Extensions import pxs_Interval
|
| 19 |
+
|
| 20 |
+
config_standard = pxs_config()
|
| 21 |
+
|
| 22 |
+
# Tirage aléatoire des paramètres
|
| 23 |
+
for _ in range(200):
|
| 24 |
+
budget = rd.randint(80, 200)
|
| 25 |
+
spent = rd.randint(20, budget - 10)
|
| 26 |
+
if spent < budget:
|
| 27 |
+
break
|
| 28 |
+
|
| 29 |
+
# Calcul de la solution
|
| 30 |
+
xMax = budget - spent
|
| 31 |
+
|
| 32 |
+
# === Distracteurs MCQ (ajouts ; le code ci-dessus est inchangé) ===
|
| 33 |
+
from sympy import oo
|
| 34 |
+
interCorrect = pxs_Interval(0, xMax, False, False).print()
|
| 35 |
+
interD1 = pxs_Interval(0, xMax, True, True).print()
|
| 36 |
+
interD2 = pxs_Interval(0, budget, False, False).print()
|
| 37 |
+
interD3 = pxs_Interval(xMax, oo, False, True).print()
|
| 38 |
+
|
| 39 |
+
globals()
|
| 40 |
+
````
|
| 41 |
+
|
| 42 |
+
:::::{question}
|
| 43 |
+
:questionType: MCQ
|
| 44 |
+
|
| 45 |
+
::::{questionStatement}
|
| 46 |
+
{fr}`Une étudiante dispose d'un budget hebdomadaire de {{budget}} € pour son alimentation. Elle a déjà dépensé {{spent}} € cette semaine. On note $x$ le montant supplémentaire (en euros) qu'elle peut encore dépenser. Quelle inéquation traduit que sa dépense totale ne doit pas dépasser {{budget}} € ?`{en}`A student has a weekly budget of €{{budget}} for food. She has already spent €{{spent}} this week. Let $x$ denote the additional amount (in euros) she may still spend. Which inequality expresses that her total spending must not exceed €{{budget}}?`
|
| 47 |
+
::::
|
| 48 |
+
|
| 49 |
+
::::{questionHint}
|
| 50 |
+
{fr}`« Ne doit pas dépasser » se traduit par $\leq$. La dépense totale est ${{spent}} + x$.`{en}`"Must not exceed" translates to $\leq$. The total spending is ${{spent}} + x$.`
|
| 51 |
+
::::
|
| 52 |
+
|
| 53 |
+
::::{mcqAnswer}
|
| 54 |
+
:isRightAnswer: true
|
| 55 |
+
${{spent}} + x \leq {{budget}}$
|
| 56 |
+
::::
|
| 57 |
+
|
| 58 |
+
::::{mcqAnswer}
|
| 59 |
+
:isRightAnswer: false
|
| 60 |
+
${{spent}} + x \geq {{budget}}$
|
| 61 |
+
::::
|
| 62 |
+
|
| 63 |
+
::::{mcqAnswer}
|
| 64 |
+
:isRightAnswer: false
|
| 65 |
+
${{spent}} + x = {{budget}}$
|
| 66 |
+
::::
|
| 67 |
+
|
| 68 |
+
::::{mcqAnswer}
|
| 69 |
+
:isRightAnswer: false
|
| 70 |
+
${{budget}} + x \leq {{spent}}$
|
| 71 |
+
::::
|
| 72 |
+
|
| 73 |
+
::::{mcqAnswer}
|
| 74 |
+
:isRightAnswer: false
|
| 75 |
+
{fr}`Aucune de ces réponses n'est correcte`{en}`None of these answers are correct`
|
| 76 |
+
::::
|
| 77 |
+
|
| 78 |
+
::::{detailedSolution}
|
| 79 |
+
{fr}`La dépense totale est ${{spent}} + x$ euros. La contrainte « dépense totale $\leq {{budget}}$ » donne :`{en}`The total spending is ${{spent}} + x$ euros. The constraint "total spending $\leq {{budget}}$" gives:`
|
| 80 |
+
|
| 81 |
+
\begin{equation*}
|
| 82 |
+
{{spent}} + x \leq {{budget}}.
|
| 83 |
+
\end{equation*}
|
| 84 |
+
::::
|
| 85 |
+
|
| 86 |
+
::::{weightDistribution}
|
| 87 |
+
:logic: 15
|
| 88 |
+
:abstraction: 30
|
| 89 |
+
:reasoning: 35
|
| 90 |
+
:calculation: 20
|
| 91 |
+
::::
|
| 92 |
+
:::::
|
| 93 |
+
|
| 94 |
+
:::::{question}
|
| 95 |
+
:questionType: MCQ
|
| 96 |
+
|
| 97 |
+
::::{questionStatement}
|
| 98 |
+
{fr}`Résoudre l'inéquation ${{spent}} + x \leq {{budget}}$ (on rappelle que $x \geq 0$ puisque $x$ est un montant). Quel est l'ensemble des solutions sous forme d'intervalle ?`{en}`Solve the inequality ${{spent}} + x \leq {{budget}}$ (recall that $x \geq 0$ since $x$ is an amount of money). What is the solution set as an interval?`
|
| 99 |
+
::::
|
| 100 |
+
|
| 101 |
+
::::{questionHint}
|
| 102 |
+
{fr}`Soustraire {{spent}} aux deux membres : ajouter ou retrancher une constante ne change pas le sens de l'inégalité.`{en}`Subtract {{spent}} from both sides: adding or subtracting a constant does not change the direction of the inequality.`
|
| 103 |
+
::::
|
| 104 |
+
|
| 105 |
+
::::{mcqAnswer}
|
| 106 |
+
:isRightAnswer: true
|
| 107 |
+
${{interCorrect}}$
|
| 108 |
+
::::
|
| 109 |
+
|
| 110 |
+
::::{mcqAnswer}
|
| 111 |
+
:isRightAnswer: false
|
| 112 |
+
${{interD1}}$
|
| 113 |
+
::::
|
| 114 |
+
|
| 115 |
+
::::{mcqAnswer}
|
| 116 |
+
:isRightAnswer: false
|
| 117 |
+
${{interD2}}$
|
| 118 |
+
::::
|
| 119 |
+
|
| 120 |
+
::::{mcqAnswer}
|
| 121 |
+
:isRightAnswer: false
|
| 122 |
+
${{interD3}}$
|
| 123 |
+
::::
|
| 124 |
+
|
| 125 |
+
::::{mcqAnswer}
|
| 126 |
+
:isRightAnswer: false
|
| 127 |
+
{fr}`Aucune de ces réponses n'est correcte`{en}`None of these answers are correct`
|
| 128 |
+
::::
|
| 129 |
+
|
| 130 |
+
::::{detailedSolution}
|
| 131 |
+
{fr}`On soustrait {{spent}} aux deux membres (le sens est conservé) :`{en}`Subtract {{spent}} from both sides (the direction is preserved):`
|
| 132 |
+
|
| 133 |
+
\begin{equation*}
|
| 134 |
+
x \leq {{budget}} - {{spent}} = {{xMax}}.
|
| 135 |
+
\end{equation*}
|
| 136 |
+
|
| 137 |
+
{fr}`Comme $x \geq 0$, l'ensemble des solutions est :`{en}`Since $x \geq 0$, the solution set is:`
|
| 138 |
+
|
| 139 |
+
\begin{equation*}
|
| 140 |
+
x \in {{pxs_Interval(0, xMax, False, False).print()}}.
|
| 141 |
+
\end{equation*}
|
| 142 |
+
|
| 143 |
+
{fr}`**Conclusion.** L'étudiante peut encore dépenser au plus {{xMax}} € cette semaine.`{en}`**Conclusion.** The student may spend at most €{{xMax}} more this week.`
|
| 144 |
+
::::
|
| 145 |
+
|
| 146 |
+
::::{weightDistribution}
|
| 147 |
+
:logic: 10
|
| 148 |
+
:abstraction: 15
|
| 149 |
+
:reasoning: 25
|
| 150 |
+
:calculation: 50
|
| 151 |
+
::::
|
| 152 |
+
:::::
|
| 153 |
+
|
| 154 |
+
`````
|
app/knowledge/fewshots/declinaisons/{fr}`Prix admissible entre marge minimale et plafond réglementaire`{en}`Admissible price between minimum margin and regulatory ceiling`.md
ADDED
|
@@ -0,0 +1,213 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
`````{exercise}
|
| 2 |
+
:originalExerciseId: 95586bb6-6340-11f1-a8a1-0ed8d3b012a9
|
| 3 |
+
:title: {fr}`Prix admissible entre marge minimale et plafond réglementaire`{en}`Admissible price between minimum margin and regulatory ceiling`
|
| 4 |
+
:modules:
|
| 5 |
+
:recommendedExecutionTime: 20
|
| 6 |
+
:level: Elementary
|
| 7 |
+
:chap:
|
| 8 |
+
:involvedConcepts: upp_and_low, Interval_Notation, Solving_inequalities
|
| 9 |
+
:originalSource:
|
| 10 |
+
:visibility: All
|
| 11 |
+
:variations:
|
| 12 |
+
:comment: Version QCM — borne inférieure (marge) puis encadrement/intervalle semi-ouvert.
|
| 13 |
+
:id: e5eb824f-6e47-11f1-a8a1-0ed8d3b012a9
|
| 14 |
+
|
| 15 |
+
````{python}
|
| 16 |
+
import random as rd
|
| 17 |
+
import math
|
| 18 |
+
from sympy import Rational
|
| 19 |
+
from pyxiscience.Mes_fctions_generalistes_bis import pxs_config, pxs_nvirgzero
|
| 20 |
+
from pyxiscience.Classes_Extensions import pxs_Interval
|
| 21 |
+
|
| 22 |
+
config_standard = pxs_config()
|
| 23 |
+
|
| 24 |
+
def rat_fr(r):
|
| 25 |
+
r = Rational(r)
|
| 26 |
+
if r.q == 1:
|
| 27 |
+
return str(r.p)
|
| 28 |
+
d, twos, fives = r.q, 0, 0
|
| 29 |
+
while d % 2 == 0:
|
| 30 |
+
d //= 2; twos += 1
|
| 31 |
+
while d % 5 == 0:
|
| 32 |
+
d //= 5; fives += 1
|
| 33 |
+
assert d == 1, f"décimale non finie : {r}"
|
| 34 |
+
k = max(twos, fives)
|
| 35 |
+
scaled = (r.p * 10**k) // r.q
|
| 36 |
+
sign = '-' if scaled < 0 else ''
|
| 37 |
+
s = str(abs(scaled)).rjust(k + 1, '0')
|
| 38 |
+
intpart, frac = s[:-k], s[-k:].rstrip('0')
|
| 39 |
+
return sign + intpart + ('{,}' + frac if frac else '')
|
| 40 |
+
|
| 41 |
+
# Coût d'achat et taux de marge (rationnels exacts)
|
| 42 |
+
for _ in range(2000):
|
| 43 |
+
c = rd.randint(20, 100)
|
| 44 |
+
pct = rd.choice([15, 20, 25, 30, 35, 40, 45, 50])
|
| 45 |
+
taux_marge = Rational(pct, 100)
|
| 46 |
+
coeff_marge = 1 + taux_marge
|
| 47 |
+
borneInf = c * coeff_marge
|
| 48 |
+
lo = math.ceil(borneInf)
|
| 49 |
+
if lo + 5 <= 150:
|
| 50 |
+
plafond = rd.randint(lo + 5, 150)
|
| 51 |
+
break
|
| 52 |
+
|
| 53 |
+
p_exemple = (lo + plafond) // 2
|
| 54 |
+
|
| 55 |
+
# Rendus
|
| 56 |
+
tauxPct = pct
|
| 57 |
+
tauxMargeAff = rat_fr(taux_marge)
|
| 58 |
+
coeffMargeAff = rat_fr(coeff_marge)
|
| 59 |
+
borneInfAff = rat_fr(borneInf)
|
| 60 |
+
pExemple = p_exemple
|
| 61 |
+
encadrAff = f"{borneInfAff} \\leq p < {plafond}"
|
| 62 |
+
if pxs_lang == "fr":
|
| 63 |
+
SAff = pxs_Interval(pxs_nvirgzero(float(borneInf)), plafond, False, True).print().replace(".",",")
|
| 64 |
+
else:
|
| 65 |
+
SAff = pxs_Interval(pxs_nvirgzero(float(borneInf)), plafond, False, True).print()
|
| 66 |
+
|
| 67 |
+
# === Distracteurs MCQ (ajouts ; le code ci-dessus est inchangé) ===
|
| 68 |
+
def _dedl(correct_l, cands, n=3):
|
| 69 |
+
seen = {correct_l}; out = []
|
| 70 |
+
for s in cands:
|
| 71 |
+
if s not in seen:
|
| 72 |
+
seen.add(s); out.append(s)
|
| 73 |
+
if len(out) == n:
|
| 74 |
+
break
|
| 75 |
+
return out
|
| 76 |
+
|
| 77 |
+
# Q0 : borne inférieure
|
| 78 |
+
repBI = borneInfAff
|
| 79 |
+
_cand = [Rational(c), c * taux_marge, c + coeff_marge, 2 * c * coeff_marge, c * (1 - taux_marge)]
|
| 80 |
+
_cs = [rat_fr(v) for v in _cand]
|
| 81 |
+
_d = _dedl(repBI, _cs)
|
| 82 |
+
bi1, bi2, bi3 = _d[0], _d[1], _d[2]
|
| 83 |
+
|
| 84 |
+
# Q1 : intervalle semi-ouvert (ouverture/fermeture des bornes)
|
| 85 |
+
def _iv(ol, oh):
|
| 86 |
+
s = pxs_Interval(pxs_nvirgzero(float(borneInf)), plafond, ol, oh).print()
|
| 87 |
+
if pxs_lang == "fr":
|
| 88 |
+
s = s.replace(".", ",")
|
| 89 |
+
return s
|
| 90 |
+
|
| 91 |
+
SCorrect = SAff
|
| 92 |
+
SD1 = _iv(True, False)
|
| 93 |
+
SD2 = _iv(False, False)
|
| 94 |
+
SD3 = _iv(True, True)
|
| 95 |
+
|
| 96 |
+
globals()
|
| 97 |
+
````
|
| 98 |
+
|
| 99 |
+
:::::{question}
|
| 100 |
+
:questionType: MCQ
|
| 101 |
+
|
| 102 |
+
::::{questionStatement}
|
| 103 |
+
{fr}`Une entreprise de distribution fixe le prix de vente unitaire`{en}`A distribution company sets the unit selling price` $p$ {fr}`(en euros) de sorte que la marge soit au moins`{en}`(in euros) so that the margin is at least` ${{ tauxPct }}\,\%$ {fr}`du coût d'achat`{en}`of the purchase cost` $c={{ c }}$ €, {fr}`et que le prix reste inférieur au plafond réglementaire de`{en}`and that the price remains below the regulatory ceiling of` ${{ plafond }}$ €. {fr}`Quelle est la borne inférieure issue de la contrainte de marge,`{en}`What is the lower bound from the margin constraint,` $p\geq {{ coeffMargeAff }}\times {{ c }}$ ?
|
| 104 |
+
::::
|
| 105 |
+
|
| 106 |
+
::::{questionHint}
|
| 107 |
+
{fr}`Une marge d'au moins`{en}`A margin of at least` ${{ tauxPct }}\,\%$ {fr}`sur le coût d'achat signifie`{en}`on the purchase cost means` $p\geq c+{{ tauxMargeAff }}\,c={{ coeffMargeAff }}\,c$.
|
| 108 |
+
::::
|
| 109 |
+
|
| 110 |
+
::::{mcqAnswer}
|
| 111 |
+
:isRightAnswer: true
|
| 112 |
+
${{ repBI }}$
|
| 113 |
+
::::
|
| 114 |
+
|
| 115 |
+
::::{mcqAnswer}
|
| 116 |
+
:isRightAnswer: false
|
| 117 |
+
${{ bi1 }}$
|
| 118 |
+
::::
|
| 119 |
+
|
| 120 |
+
::::{mcqAnswer}
|
| 121 |
+
:isRightAnswer: false
|
| 122 |
+
${{ bi2 }}$
|
| 123 |
+
::::
|
| 124 |
+
|
| 125 |
+
::::{mcqAnswer}
|
| 126 |
+
:isRightAnswer: false
|
| 127 |
+
${{ bi3 }}$
|
| 128 |
+
::::
|
| 129 |
+
|
| 130 |
+
::::{mcqAnswer}
|
| 131 |
+
:isRightAnswer: false
|
| 132 |
+
{fr}`Aucune de ces réponses n'est correcte`{en}`None of these answers are correct`
|
| 133 |
+
::::
|
| 134 |
+
|
| 135 |
+
::::{detailedSolution}
|
| 136 |
+
{fr}`On traduit la contrainte de marge en borne inférieure sur le prix.`{en}`We translate the margin constraint into a lower bound on the price.`
|
| 137 |
+
|
| 138 |
+
\begin{equation*}
|
| 139 |
+
p \geq {{ coeffMargeAff }} \times {{ c }} = {{ borneInfAff }}.
|
| 140 |
+
\end{equation*}
|
| 141 |
+
|
| 142 |
+
{fr}`La borne inférieure est`{en}`The lower bound is` ${{ borneInfAff }}$ €.
|
| 143 |
+
::::
|
| 144 |
+
|
| 145 |
+
::::{weightDistribution}
|
| 146 |
+
:logic: 15
|
| 147 |
+
:abstraction: 20
|
| 148 |
+
:reasoning: 30
|
| 149 |
+
:calculation: 35
|
| 150 |
+
::::
|
| 151 |
+
:::::
|
| 152 |
+
|
| 153 |
+
:::::{question}
|
| 154 |
+
:questionType: MCQ
|
| 155 |
+
|
| 156 |
+
::::{questionStatement}
|
| 157 |
+
{fr}`On combine la double contrainte (marge et plafond). Quel est l'ensemble des prix admissibles en notation intervalle (en tenant compte du type de chaque borne) ?`{en}`We combine the double constraint (margin and ceiling). What is the set of admissible prices in interval notation (taking into account the type of each bound)?`
|
| 158 |
+
::::
|
| 159 |
+
|
| 160 |
+
::::{questionHint}
|
| 161 |
+
{fr}`Combiner`{en}`Combine` $p\geq {{ borneInfAff }}$ {fr}`(marge, borne incluse) et`{en}`(margin, included bound) and` $p<{{ plafond }}$ {fr}`(plafond, borne exclue).`{en}`(ceiling, excluded bound).`
|
| 162 |
+
::::
|
| 163 |
+
|
| 164 |
+
::::{mcqAnswer}
|
| 165 |
+
:isRightAnswer: true
|
| 166 |
+
$S = {{ SCorrect }}$
|
| 167 |
+
::::
|
| 168 |
+
|
| 169 |
+
::::{mcqAnswer}
|
| 170 |
+
:isRightAnswer: false
|
| 171 |
+
$S = {{ SD1 }}$
|
| 172 |
+
::::
|
| 173 |
+
|
| 174 |
+
::::{mcqAnswer}
|
| 175 |
+
:isRightAnswer: false
|
| 176 |
+
$S = {{ SD2 }}$
|
| 177 |
+
::::
|
| 178 |
+
|
| 179 |
+
::::{mcqAnswer}
|
| 180 |
+
:isRightAnswer: false
|
| 181 |
+
$S = {{ SD3 }}$
|
| 182 |
+
::::
|
| 183 |
+
|
| 184 |
+
::::{mcqAnswer}
|
| 185 |
+
:isRightAnswer: false
|
| 186 |
+
{fr}`Aucune de ces réponses n'est correcte`{en}`None of these answers are correct`
|
| 187 |
+
::::
|
| 188 |
+
|
| 189 |
+
::::{detailedSolution}
|
| 190 |
+
{fr}`On rassemble les deux conditions en un encadrement ; aucune opération supplémentaire n'est nécessaire car`{en}`We combine the two conditions into an inequality; no additional operation is necessary since` $p$ {fr}`est déjà isolée.`{en}`is already isolated.`
|
| 191 |
+
|
| 192 |
+
\begin{equation*}
|
| 193 |
+
{{ encadrAff }}.
|
| 194 |
+
\end{equation*}
|
| 195 |
+
|
| 196 |
+
{fr}`L'ensemble des solutions est`{en}`The solution set is`
|
| 197 |
+
|
| 198 |
+
\begin{equation*}
|
| 199 |
+
S = {{ SAff }}.
|
| 200 |
+
\end{equation*}
|
| 201 |
+
|
| 202 |
+
{fr}`La borne`{en}`The bound` ${{ borneInfAff }}$ {fr}`est incluse (un prix de`{en}`is included (a price of` ${{ borneInfAff }}$ € {fr}`satisfait exactement la marge). La borne`{en}`exactly satisfies the margin). The bound` ${{ plafond }}$ {fr}`est exclue (un prix de`{en}`is excluded (a price of` ${{ plafond }}$ € {fr}`violerait le plafond).`{en}`would violate the ceiling).`
|
| 203 |
+
::::
|
| 204 |
+
|
| 205 |
+
::::{weightDistribution}
|
| 206 |
+
:logic: 20
|
| 207 |
+
:abstraction: 30
|
| 208 |
+
:reasoning: 30
|
| 209 |
+
:calculation: 20
|
| 210 |
+
::::
|
| 211 |
+
:::::
|
| 212 |
+
|
| 213 |
+
`````
|
app/knowledge/fewshots/declinaisons/{fr}`Prix plafond et demande décroissante`{en}`Price ceiling and decreasing demand`.md
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
`````{exercise}
|
| 2 |
+
:originalExerciseId: 84168f40-6340-11f1-a8a1-0ed8d3b012a9
|
| 3 |
+
:title: {fr}`Prix plafond et demande décroissante`{en}`Price ceiling and decreasing demand`
|
| 4 |
+
:modules:
|
| 5 |
+
:recommendedExecutionTime: 10
|
| 6 |
+
:level: Elementary
|
| 7 |
+
:chap:
|
| 8 |
+
:involvedConcepts: Solving_inequalities
|
| 9 |
+
:originalSource:
|
| 10 |
+
:visibility: All
|
| 11 |
+
:variations:
|
| 12 |
+
:comment: Version QCM — division par un coefficient négatif (inversion du sens), ensemble en intervalle.
|
| 13 |
+
:id: f2cc923d-6e47-11f1-a8a1-0ed8d3b012a9
|
| 14 |
+
|
| 15 |
+
````{python}
|
| 16 |
+
import random as rd
|
| 17 |
+
from sympy import oo
|
| 18 |
+
from pyxiscience.Mes_fctions_generalistes_bis import pxs_config, pxsl_latex_coefficient as lc, pxsl_format_number
|
| 19 |
+
from pyxiscience.Classes_Extensions import pxs_Interval
|
| 20 |
+
|
| 21 |
+
config_standard = pxs_config()
|
| 22 |
+
|
| 23 |
+
# Tirage contraint : a divise (p_max - p_plafond)
|
| 24 |
+
for _ in range(2000):
|
| 25 |
+
p_max = rd.randint(80, 200)
|
| 26 |
+
p_plafond = rd.randint(max(40, p_max // 3), p_max - 20)
|
| 27 |
+
diviseurs = [d for d in range(2, 11) if (p_max - p_plafond) % d == 0]
|
| 28 |
+
if diviseurs:
|
| 29 |
+
a = rd.choice(diviseurs)
|
| 30 |
+
break
|
| 31 |
+
|
| 32 |
+
q_min = (p_max - p_plafond) // a
|
| 33 |
+
intermediaire = p_plafond - p_max
|
| 34 |
+
|
| 35 |
+
q_verif_ok = q_min
|
| 36 |
+
q_verif_ko = q_min - 1
|
| 37 |
+
p_verif_ko = p_max - a * q_verif_ko
|
| 38 |
+
intervalAff = pxs_Interval(q_min, oo, False, True).print()
|
| 39 |
+
|
| 40 |
+
# Rendus
|
| 41 |
+
negASignAff = lc(-a, sign=True)
|
| 42 |
+
pMax = p_max
|
| 43 |
+
pPlafond = p_plafond
|
| 44 |
+
negAAff = lc(-a)
|
| 45 |
+
qMinAff = pxsl_format_number(q_min)
|
| 46 |
+
qVerifOk = q_verif_ok
|
| 47 |
+
qVerifKo = q_verif_ko
|
| 48 |
+
pVerifKo = p_verif_ko
|
| 49 |
+
|
| 50 |
+
# === Distracteurs MCQ (ajouts ; le code ci-dessus est inchangé) ===
|
| 51 |
+
intervalCorrect = intervalAff
|
| 52 |
+
intD1 = pxs_Interval(q_min, oo, True, True).print()
|
| 53 |
+
intD2 = pxs_Interval(-oo, q_min, True, False).print()
|
| 54 |
+
intD3 = pxs_Interval(0, q_min, False, False).print()
|
| 55 |
+
|
| 56 |
+
globals()
|
| 57 |
+
````
|
| 58 |
+
|
| 59 |
+
:::::{question}
|
| 60 |
+
:questionType: MCQ
|
| 61 |
+
|
| 62 |
+
::::{questionStatement}
|
| 63 |
+
{fr}`Sur un marché, la fonction de demande inverse est`{en}`In a market, the inverse demand function is`
|
| 64 |
+
\begin{equation*}
|
| 65 |
+
p(q) = {{ pMax }} {{ negASignAff }}q
|
| 66 |
+
\end{equation*}
|
| 67 |
+
{fr}`Le prix est en euros,`{en}`The price is in euros,` $q$ {fr}`en milliers d'unités. Un régulateur impose un prix plafond`{en}`in thousands of units. A regulator imposes a price ceiling` $p \leq {{ pPlafond }}$ €.\
|
| 68 |
+
\
|
| 69 |
+
{fr}`En traduisant cette contrainte par une inéquation sur`{en}`Translating this constraint into an inequality involving` $q$ {fr}`et en la résolvant, quel est l'ensemble des quantités compatibles ?`{en}`and solving it, what is the set of compatible quantities?`
|
| 70 |
+
::::
|
| 71 |
+
|
| 72 |
+
::::{questionHint}
|
| 73 |
+
{fr}`Substituer`{en}`Substitute` $p(q)$ {fr}`dans`{en}`into` $p \leq {{ pPlafond }}${fr}`, puis isoler`{en}`, then isolate` $q$ {fr}`en divisant par`{en}`by dividing by` ${{ negAAff }} < 0$ {fr}`: le sens s'inverse.`{en}`: the direction reverses.`
|
| 74 |
+
::::
|
| 75 |
+
|
| 76 |
+
::::{mcqAnswer}
|
| 77 |
+
:isRightAnswer: true
|
| 78 |
+
$S = {{ intervalCorrect }}$
|
| 79 |
+
::::
|
| 80 |
+
|
| 81 |
+
::::{mcqAnswer}
|
| 82 |
+
:isRightAnswer: false
|
| 83 |
+
$S = {{ intD1 }}$
|
| 84 |
+
::::
|
| 85 |
+
|
| 86 |
+
::::{mcqAnswer}
|
| 87 |
+
:isRightAnswer: false
|
| 88 |
+
$S = {{ intD2 }}$
|
| 89 |
+
::::
|
| 90 |
+
|
| 91 |
+
::::{mcqAnswer}
|
| 92 |
+
:isRightAnswer: false
|
| 93 |
+
$S = {{ intD3 }}$
|
| 94 |
+
::::
|
| 95 |
+
|
| 96 |
+
::::{mcqAnswer}
|
| 97 |
+
:isRightAnswer: false
|
| 98 |
+
{fr}`Aucune de ces réponses n'est correcte`{en}`None of these answers are correct`
|
| 99 |
+
::::
|
| 100 |
+
|
| 101 |
+
::::{detailedSolution}
|
| 102 |
+
{fr}`On soustrait d'abord une quantité, le sens de l'inégalité est alors conservé, puis on divise par un réel strictement négatif, qui inverse le sens de l'inégalité.`{en}`We first subtract a quantity, the direction of the inequality is then preserved, then we divide by a strictly negative real number, which reverses the direction of the inequality.`
|
| 103 |
+
|
| 104 |
+
\begin{equation*}
|
| 105 |
+
{{ pMax }} {{ negASignAff }}q \leq {{ pPlafond }} &\implies {{ negAAff }}q \leq {{ intermediaire }}\\[10pt]
|
| 106 |
+
&\implies \frac{ {{ negAAff }}q }{ {{ negAAff }} } \geq \frac{ {{ intermediaire }} }{ {{ negAAff }} }\\[10pt]
|
| 107 |
+
&\implies q \geq {{ qMinAff }}.
|
| 108 |
+
\end{equation*}
|
| 109 |
+
|
| 110 |
+
{fr}`L'ensemble des solutions est donc`{en}`The solution set is therefore`
|
| 111 |
+
|
| 112 |
+
\begin{equation*}
|
| 113 |
+
S = {{ intervalAff }}.
|
| 114 |
+
\end{equation*}
|
| 115 |
+
|
| 116 |
+
{fr}`Pour que le prix soit au plus`{en}`For the price to be at most` ${{ pPlafond }}$ €{fr}`, la quantité offerte doit être d'au moins`{en}`, the quantity supplied must be at least` ${{ qMinAff }}$ {fr}`milliers d'unités. Ceci est cohérent car la demande étant décroissante, un prix plus bas correspond à une quantité plus grande.`{en}`thousands of units. This is consistent because since demand is decreasing, a lower price corresponds to a larger quantity.`
|
| 117 |
+
::::
|
| 118 |
+
|
| 119 |
+
::::{weightDistribution}
|
| 120 |
+
:logic: 20
|
| 121 |
+
:abstraction: 25
|
| 122 |
+
:reasoning: 35
|
| 123 |
+
:calculation: 20
|
| 124 |
+
::::
|
| 125 |
+
:::::
|
| 126 |
+
|
| 127 |
+
`````
|
app/knowledge/fewshots/declinaisons/{fr}`Règle des exposants _ simplification et calcul`{en}`Exponent Rules_ Simplification and Calculation`.md
ADDED
|
@@ -0,0 +1,162 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
`````{exercise}
|
| 2 |
+
:title: {fr}`Règle des exposants : simplification et calcul`{en}`Exponent Rules: Simplification and Calculation`
|
| 3 |
+
:modules:
|
| 4 |
+
:recommendedExecutionTime: 8
|
| 5 |
+
:level: Elementary
|
| 6 |
+
:chap: chap_expLogFunctions_exponentialFunctions_ESCP
|
| 7 |
+
:involvedConcepts:
|
| 8 |
+
:originalSource:
|
| 9 |
+
:visibility: All
|
| 10 |
+
:variations:
|
| 11 |
+
:comment: Échauffement (thème pur) : application de la règle b^(x+y)=b^x·b^y et b^(x−y)=b^x/b^y, en simplification littérale et en vérification numérique.
|
| 12 |
+
:id: 7dff868d-74d9-11f1-a8a1-0ed8d3b012a9
|
| 13 |
+
:originalExerciseId: 6da31517-6597-11f1-a8a1-0ed8d3b012a9
|
| 14 |
+
|
| 15 |
+
````{python}
|
| 16 |
+
import random as rd
|
| 17 |
+
from fractions import Fraction
|
| 18 |
+
from decimal import Decimal, ROUND_HALF_UP
|
| 19 |
+
|
| 20 |
+
def _grp(ip):
|
| 21 |
+
neg = ip.startswith('-'); ip = ip.lstrip('-'); g = []
|
| 22 |
+
while len(ip) > 3:
|
| 23 |
+
g.insert(0, ip[-3:]); ip = ip[:-3]
|
| 24 |
+
g.insert(0, ip)
|
| 25 |
+
return ('-' if neg else '') + '\\,'.join(g)
|
| 26 |
+
|
| 27 |
+
def num_fr(v, maxdec=5):
|
| 28 |
+
f = Fraction(v)
|
| 29 |
+
d = (Decimal(f.numerator) / Decimal(f.denominator)).quantize(Decimal(1).scaleb(-maxdec), rounding=ROUND_HALF_UP)
|
| 30 |
+
s = format(abs(d), 'f'); ip, _, fp = s.partition('.'); fp = fp.rstrip('0')
|
| 31 |
+
return ('-' if d < 0 else '') + _grp(ip) + (('{,}' + fp) if fp else '')
|
| 32 |
+
|
| 33 |
+
# Q1 : simplification symbolique (exposants >= 2 partout)
|
| 34 |
+
sommeA = rd.randint(5, 10)
|
| 35 |
+
diffB = rd.randint(3, 6)
|
| 36 |
+
exp1 = rd.randint(2, sommeA - 2)
|
| 37 |
+
exp2 = sommeA - exp1
|
| 38 |
+
exp4 = rd.randint(2, 5)
|
| 39 |
+
exp3 = exp4 + diffB
|
| 40 |
+
aAff = 'b^{%d}' % sommeA
|
| 41 |
+
bAff = 'b^{%d}' % diffB
|
| 42 |
+
|
| 43 |
+
# Q2 : vérification numérique
|
| 44 |
+
baseC = rd.choice([2, 3])
|
| 45 |
+
sommeC = rd.randint(5, 8)
|
| 46 |
+
exp5 = rd.randint(2, sommeC - 2)
|
| 47 |
+
exp6 = sommeC - exp5
|
| 48 |
+
baseDNum = rd.choice([3, 5]) # base_D = 3/2 ou 5/2
|
| 49 |
+
baseD = Fraction(baseDNum, 2)
|
| 50 |
+
diffD = rd.randint(2, 4)
|
| 51 |
+
exp8 = rd.randint(2, 4)
|
| 52 |
+
exp7 = exp8 + diffD
|
| 53 |
+
|
| 54 |
+
cVal = baseC ** sommeC
|
| 55 |
+
dVal = baseD ** diffD
|
| 56 |
+
|
| 57 |
+
# Rendus
|
| 58 |
+
baseDAff = '%d{,}5' % (baseDNum // 2) # 3/2 -> 1{,}5 ; 5/2 -> 2{,}5
|
| 59 |
+
dValAff = num_fr(dVal, 5)
|
| 60 |
+
|
| 61 |
+
# === Ajouts conversion FGQ ===
|
| 62 |
+
def _solnum(x):
|
| 63 |
+
return x.replace("\\,", "").replace("{,}", ".")
|
| 64 |
+
dValSol = _solnum(dValAff)
|
| 65 |
+
globals()
|
| 66 |
+
````
|
| 67 |
+
|
| 68 |
+
:::::{question}
|
| 69 |
+
:questionType: FGQ
|
| 70 |
+
:questionId: 0
|
| 71 |
+
:questionIndex: 0
|
| 72 |
+
:solution: [["ord","$b^{ {{ sommeA }} }$","$b^{ {{ diffB }} }$"],["0","0"]]
|
| 73 |
+
|
| 74 |
+
::::{questionStatement}
|
| 75 |
+
{fr}`Soit`{en}`Let` $b>0$, $b\neq 1$. {fr}`Simplifier`{en}`Simplify` $A=b^{ {{exp1}} }\cdot b^{ {{exp2}} }$ {fr}`et`{en}`and` $B=\dfrac{b^{ {{exp3}} }}{b^{ {{exp4}} }}$ {fr}`à l'aide de la règle des exposants.`{en}`using the exponent rules.`
|
| 76 |
+
|
| 77 |
+
{fr}`$A$ se simplifie en`{en}`$A$ simplifies to` {input}`||110` {fr}`et $B$ en`{en}`and $B$ to` {input}`||110`
|
| 78 |
+
::::
|
| 79 |
+
|
| 80 |
+
::::{questionHint}
|
| 81 |
+
$b^{x}\cdot b^{y}=b^{x+y}$ {fr}`et`{en}`and` $\dfrac{b^{x}}{b^{y}}=b^{x-y}$.
|
| 82 |
+
::::
|
| 83 |
+
|
| 84 |
+
::::{displayedSolution}
|
| 85 |
+
$A = b^{ {{ sommeA }} }$ $\qquad B = b^{ {{ diffB }} }$
|
| 86 |
+
::::
|
| 87 |
+
|
| 88 |
+
::::{detailedSolution}
|
| 89 |
+
{fr}`Par addition des exposants pour le produit, et soustraction pour le quotient :`{en}`By adding exponents for the product, and subtracting for the quotient:`
|
| 90 |
+
|
| 91 |
+
\begin{equation*}
|
| 92 |
+
A &= b^{ {{exp1}} }\cdot b^{ {{exp2}} } \\
|
| 93 |
+
&= b^{ {{exp1}} + {{exp2}} } \\
|
| 94 |
+
&= {{ aAff }}.
|
| 95 |
+
\end{equation*}
|
| 96 |
+
|
| 97 |
+
\begin{equation*}
|
| 98 |
+
B &= \frac{b^{ {{exp3}} }}{b^{ {{exp4}} }} \\
|
| 99 |
+
&= b^{ {{exp3}} - {{exp4}} } \\
|
| 100 |
+
&= {{ bAff }}.
|
| 101 |
+
\end{equation*}
|
| 102 |
+
::::
|
| 103 |
+
|
| 104 |
+
::::{weightDistribution}
|
| 105 |
+
:logic: 20
|
| 106 |
+
:abstraction: 30
|
| 107 |
+
:reasoning: 25
|
| 108 |
+
:calculation: 25
|
| 109 |
+
::::
|
| 110 |
+
:::::
|
| 111 |
+
|
| 112 |
+
:::::{question}
|
| 113 |
+
:questionType: FGQ
|
| 114 |
+
:questionId: 1
|
| 115 |
+
:questionIndex: 1
|
| 116 |
+
:solution: [["ord","${{ cVal }}$","${{ dValSol }}$"],["0","0"]]
|
| 117 |
+
|
| 118 |
+
::::{questionStatement}
|
| 119 |
+
{fr}`Calculer numériquement`{en}`Compute numerically` $C={{ baseC }}^{ {{exp5}} }\cdot {{ baseC }}^{ {{exp6}} }$ {fr}`et vérifier que`{en}`and verify that` $C={{ baseC }}^{ {{sommeC}} }$, {fr}`puis`{en}`then` $D=\dfrac{({{ baseDAff }})^{ {{exp7}} }}{({{ baseDAff }})^{ {{exp8}} }}$ {fr}`et vérifier que`{en}`and verify that` $D=({{ baseDAff }})^{ {{diffD}} }$.
|
| 120 |
+
|
| 121 |
+
$C =$ {input}`||80` $\qquad D =$ {input}`||80`
|
| 122 |
+
::::
|
| 123 |
+
|
| 124 |
+
::::{questionHint}
|
| 125 |
+
{fr}`Calculer chaque puissance, puis comparer au résultat attendu par la règle des exposants.`{en}`Compute each power, then compare to the expected result using the exponent rules.`
|
| 126 |
+
::::
|
| 127 |
+
|
| 128 |
+
::::{displayedSolution}
|
| 129 |
+
$C = {{ cVal }}$ $\qquad D = {{ dValAff }}$
|
| 130 |
+
::::
|
| 131 |
+
|
| 132 |
+
::::{detailedSolution}
|
| 133 |
+
{fr}`Pour`{en}`For` $C$ :
|
| 134 |
+
|
| 135 |
+
\begin{equation*}
|
| 136 |
+
C &= {{ baseC }}^{ {{exp5}} }\cdot {{ baseC }}^{ {{exp6}} } \\
|
| 137 |
+
&= {{ baseC }}^{ {{exp5}} + {{exp6}} } \\
|
| 138 |
+
&= {{ baseC }}^{ {{sommeC}} } \\
|
| 139 |
+
&= {{ cVal }}.
|
| 140 |
+
\end{equation*}
|
| 141 |
+
|
| 142 |
+
{fr}`Pour`{en}`For` $D$ :
|
| 143 |
+
|
| 144 |
+
\begin{equation*}
|
| 145 |
+
D &= \frac{({{ baseDAff }})^{ {{exp7}} }}{({{ baseDAff }})^{ {{exp8}} }} \\
|
| 146 |
+
&= ({{ baseDAff }})^{ {{exp7}} - {{exp8}} } \\
|
| 147 |
+
&= ({{ baseDAff }})^{ {{diffD}} } \\
|
| 148 |
+
&= {{ dValAff }}.
|
| 149 |
+
\end{equation*}
|
| 150 |
+
|
| 151 |
+
{fr}`Les deux vérifications sont cohérentes avec la règle des exposants.`{en}`Both verifications are consistent with the exponent rules.`
|
| 152 |
+
::::
|
| 153 |
+
|
| 154 |
+
::::{weightDistribution}
|
| 155 |
+
:logic: 15
|
| 156 |
+
:abstraction: 25
|
| 157 |
+
:reasoning: 25
|
| 158 |
+
:calculation: 35
|
| 159 |
+
::::
|
| 160 |
+
:::::
|
| 161 |
+
|
| 162 |
+
`````
|
app/knowledge/fewshots/declinaisons/{fr}`Résolution de`{en}`Resolution of` $_ax+b_ = k$ {fr}`avec`{en}`with` $k _ 0$.md
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
`````{exercise}
|
| 2 |
+
:title: {fr}`Résolution de`{en}`Resolution of` $|ax+b| = k$ {fr}`avec`{en}`with` $k > 0$
|
| 3 |
+
:modules:
|
| 4 |
+
:recommendedExecutionTime: 5
|
| 5 |
+
:level: Elementary
|
| 6 |
+
:chap: chap_equations_Inequalities_absoluteValue_ESCP
|
| 7 |
+
:involvedConcepts: Abs_Val, Simplifying_Algebraic_Expressions, Solving_equalities
|
| 8 |
+
:originalSource: Erwan Lamy, ESCP Business School
|
| 9 |
+
:visibility: All
|
| 10 |
+
:variations:
|
| 11 |
+
:comment: Échauffement 2/10 — résolution d'une équation à valeur absolue, cas k > 0 (deux cas).
|
| 12 |
+
:id: dafee084-70aa-11f1-a8a1-0ed8d3b012a9
|
| 13 |
+
:originalExerciseId: 004ee5bb-634d-11f1-a8a1-0ed8d3b012a9
|
| 14 |
+
|
| 15 |
+
````{python}
|
| 16 |
+
import random as rd
|
| 17 |
+
from math import gcd
|
| 18 |
+
from sympy import Rational, latex
|
| 19 |
+
from pyxiscience.Mes_fctions_generalistes_bis import pxs_config, pxsl_latex_coefficient as lc
|
| 20 |
+
config_standard = pxs_config()
|
| 21 |
+
|
| 22 |
+
# Équation |b - a x| = k, k > 0.
|
| 23 |
+
# Cas 1 : b - a x = k -> x = (b - k)/a (fraction irréductible, dénominateur >= 2)
|
| 24 |
+
# Cas 2 : b - a x = -k -> x = (b + k)/a (entier par construction)
|
| 25 |
+
for _ in range(200):
|
| 26 |
+
a = rd.randint(2, 9)
|
| 27 |
+
b = rd.randint(5, 15)
|
| 28 |
+
k = rd.randint(2, min(10, b - 1))
|
| 29 |
+
if (b + k) % a == 0 and (b - k) != 0 and gcd(b - k, a) == 1:
|
| 30 |
+
break
|
| 31 |
+
|
| 32 |
+
sol1 = Rational(b - k, a) # exact, irréductible, q >= 2
|
| 33 |
+
sol2 = (b + k) // a # entier
|
| 34 |
+
|
| 35 |
+
aAff = str(a)
|
| 36 |
+
bAff = str(b)
|
| 37 |
+
kAff = str(k)
|
| 38 |
+
negKAff = str(-k)
|
| 39 |
+
coefAff = lc(-a, sign=True) # coefficient signé du terme en x : "- a"
|
| 40 |
+
sol1Aff = latex(sol1) # fraction irréductible
|
| 41 |
+
sol1NumAff = str(b - k)
|
| 42 |
+
sol1DenAff = str(a)
|
| 43 |
+
sol2Aff = str(sol2)
|
| 44 |
+
bPlusKAff = str(b + k) # = a * sol2
|
| 45 |
+
aTimesSol1Aff = str(b - k) # a * sol1
|
| 46 |
+
bMinusASol2Aff = str(b - a * sol2) # = -k
|
| 47 |
+
|
| 48 |
+
globals()
|
| 49 |
+
````
|
| 50 |
+
|
| 51 |
+
:::::{question}
|
| 52 |
+
:questionType: FGQ
|
| 53 |
+
:questionId: 0
|
| 54 |
+
:questionIndex: 0
|
| 55 |
+
:solution: [["notord","${{ sol1Aff }}$","${{ sol2Aff }}$"],["0","0"]]
|
| 56 |
+
|
| 57 |
+
::::{questionStatement}
|
| 58 |
+
{fr}`**Résolution d'une équation $|ax + b| = k$ avec $k > 0$.**` {en}`**Solving an equation $|ax + b| = k$ with $k > 0$.**`
|
| 59 |
+
|
| 60 |
+
{fr}`Résoudre l'équation suivante et donner ses deux solutions :` {en}`Solve the following equation and give its two solutions:`
|
| 61 |
+
|
| 62 |
+
\begin{equation*}
|
| 63 |
+
|{{ bAff }} {{ coefAff }}x| = {{ kAff }}.
|
| 64 |
+
\end{equation*}
|
| 65 |
+
|
| 66 |
+
$x =$ {input}`||120` {fr}`ou`{en}`or` $x =$ {input}`||120`
|
| 67 |
+
::::
|
| 68 |
+
|
| 69 |
+
::::{questionHint}
|
| 70 |
+
{fr}`Lorsque $k > 0$, l'équation $|f(x)| = k$ se scinde en deux cas : $f(x) = k$ ou $f(x) = -k$.`
|
| 71 |
+
|
| 72 |
+
{en}`When $k > 0$, the equation $|f(x)| = k$ splits into two cases: $f(x) = k$ or $f(x) = -k$.`
|
| 73 |
+
::::
|
| 74 |
+
|
| 75 |
+
::::{displayedSolution}
|
| 76 |
+
$\ds x = {{ sol1Aff }}$ {fr}`ou`{en}`or` $\ds x = {{ sol2Aff }}$
|
| 77 |
+
::::
|
| 78 |
+
|
| 79 |
+
::::{detailedSolution}
|
| 80 |
+
{fr}`L'équation proposée est $|{{bAff}} {{coefAff}}x| = {{kAff}}$ avec ${{kAff}} > 0$.`
|
| 81 |
+
|
| 82 |
+
{en}`The given equation is $|{{bAff}} {{coefAff}}x| = {{kAff}}$ with ${{kAff}} > 0$.`
|
| 83 |
+
|
| 84 |
+
{fr}`Par définition de la valeur absolue, cette équation se décompose en deux situations distinctes :`{en}`By definition of absolute value, this equation splits into two distinct cases:`
|
| 85 |
+
|
| 86 |
+
$\phantom{-}$
|
| 87 |
+
|
| 88 |
+
{fr}`$\bullet$ **Premier cas :** ${{bAff}} {{coefAff}}x = {{kAff}}$`
|
| 89 |
+
|
| 90 |
+
{en}`$\bullet$ **First case:** ${{bAff}} {{coefAff}}x = {{kAff}}$`
|
| 91 |
+
|
| 92 |
+
{fr}`On isole $x$ en regroupant les termes constants :`
|
| 93 |
+
{en}`We isolate $x$ by grouping the constant terms:`
|
| 94 |
+
|
| 95 |
+
\begin{equation*}
|
| 96 |
+
{{bAff}} {{coefAff}}x = {{kAff}} \quad &\Longleftrightarrow \quad {{aAff}}x = {{sol1NumAff}} \quad \\[6pt]
|
| 97 |
+
&\Longleftrightarrow \quad x = \frac{{{sol1NumAff}}}{{{sol1DenAff}}}.
|
| 98 |
+
\end{equation*}
|
| 99 |
+
|
| 100 |
+
{fr}`$\bullet$ **Deuxième cas :** ${{bAff}} {{coefAff}}x = {{negKAff}}$`
|
| 101 |
+
|
| 102 |
+
{en}`$\bullet$ **Second case:** ${{bAff}} {{coefAff}}x = {{negKAff}}$`
|
| 103 |
+
|
| 104 |
+
{fr}`On isole $x$ de la même façon :`
|
| 105 |
+
{en}`We isolate $x$ in the same way:`
|
| 106 |
+
|
| 107 |
+
\begin{equation*}
|
| 108 |
+
{{bAff}} {{coefAff}}x = {{negKAff}} \quad &\Longleftrightarrow \quad {{aAff}}x = {{bPlusKAff}} \quad \\[6pt]
|
| 109 |
+
&\Longleftrightarrow \quad x = {{sol2Aff}}.
|
| 110 |
+
\end{equation*}
|
| 111 |
+
|
| 112 |
+
{fr}`**Vérification des deux solutions.**`
|
| 113 |
+
|
| 114 |
+
{en}`**Verification of both solutions.**`
|
| 115 |
+
|
| 116 |
+
{fr}`Pour $x = \dfrac{{{sol1NumAff}}}{{{sol1DenAff}}}$ :`
|
| 117 |
+
{en}`For $x = \dfrac{{{sol1NumAff}}}{{{sol1DenAff}}}$:`
|
| 118 |
+
|
| 119 |
+
\begin{equation*}
|
| 120 |
+
\left|{{bAff}} {{coefAff}}\times\frac{{{sol1NumAff}}}{{{sol1DenAff}}}\right| &= \left|{{bAff}} - {{aAff}}\times\frac{{{sol1NumAff}}}{{{sol1DenAff}}}\right| \\[10pt]
|
| 121 |
+
&= \left|{{bAff}} - {{aTimesSol1Aff}}\right| \\[10pt]
|
| 122 |
+
&= {{kAff}}.
|
| 123 |
+
\end{equation*}
|
| 124 |
+
|
| 125 |
+
{fr}`Pour $x = {{sol2Aff}}$ :`
|
| 126 |
+
{en}`For $x = {{sol2Aff}}$:`
|
| 127 |
+
|
| 128 |
+
\begin{equation*}
|
| 129 |
+
\left|{{bAff}} {{coefAff}}\times{{sol2Aff}}\right| &= \left|{{bAff}} - {{aAff}}\times{{sol2Aff}}\right| \\[10pt]
|
| 130 |
+
&= \left|{{bMinusASol2Aff}}\right| \\[10pt]
|
| 131 |
+
&= {{kAff}}.
|
| 132 |
+
\end{equation*}
|
| 133 |
+
|
| 134 |
+
{fr}`Les deux valeurs vérifient l'équation initiale.`
|
| 135 |
+
|
| 136 |
+
{en}`Both values satisfy the original equation.`
|
| 137 |
+
|
| 138 |
+
{fr}`**Conclusion :** L'ensemble des solutions est $\boxed{\left\{\dfrac{{{sol1NumAff}}}{{{sol1DenAff}}},\, {{sol2Aff}}\right\}}$.`
|
| 139 |
+
|
| 140 |
+
{en}`**Conclusion:** The solution set is $\boxed{\left\{\dfrac{{{sol1NumAff}}}{{{sol1DenAff}}},\, {{sol2Aff}}\right\}}$.`
|
| 141 |
+
::::
|
| 142 |
+
|
| 143 |
+
::::{weightDistribution}
|
| 144 |
+
:logic: 10
|
| 145 |
+
:abstraction: 15
|
| 146 |
+
:reasoning: 25
|
| 147 |
+
:calculation: 50
|
| 148 |
+
::::
|
| 149 |
+
:::::
|
| 150 |
+
|
| 151 |
+
`````
|
app/knowledge/fewshots/declinaisons/{fr}`Seuil de rentabilité d'une boutique`{en}`Break-Even Point of a Store`.md
ADDED
|
@@ -0,0 +1,198 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
`````{exercise}
|
| 2 |
+
:title: {fr}`Seuil de rentabilité d'une boutique`{en}`Break-Even Point of a Store`
|
| 3 |
+
:modules:
|
| 4 |
+
:recommendedExecutionTime: 15
|
| 5 |
+
:level: Elementary
|
| 6 |
+
:chap: chap_lines_Systems_economicEquilibriumBreakeven_ESCP
|
| 7 |
+
:involvedConcepts:
|
| 8 |
+
:originalSource: Session 4 — Erwan Lamy, ESCP Business School
|
| 9 |
+
:visibility: All
|
| 10 |
+
:variations:
|
| 11 |
+
:comment: Appliqué (gestion) : revenu total, coût total, profit, seuil de rentabilité, marge sur coût variable.
|
| 12 |
+
:id: 33724a3b-74c0-11f1-a8a1-0ed8d3b012a9
|
| 13 |
+
:originalExerciseId: 10e7b53f-64e3-11f1-a8a1-0ed8d3b012a9
|
| 14 |
+
|
| 15 |
+
````{python}
|
| 16 |
+
import random as rd
|
| 17 |
+
from pyxiscience.Mes_fctions_generalistes_bis import pxs_config, pxsl_latex_coefficient as lc, pxsl_format_number
|
| 18 |
+
# --- Helpers PyxiScience (definis inline, autonomes : aucun import externe) ---
|
| 19 |
+
|
| 20 |
+
config_standard = pxs_config()
|
| 21 |
+
|
| 22 |
+
# p > c (marge positive) ; y_FC multiple de 50 divisible par la marge (seuil entier)
|
| 23 |
+
for _ in range(300):
|
| 24 |
+
p = rd.randint(20, 50)
|
| 25 |
+
c = rd.randint(5, 15)
|
| 26 |
+
if p <= c:
|
| 27 |
+
continue
|
| 28 |
+
marge = p - c
|
| 29 |
+
yFc = rd.randint(6, 16) * 50
|
| 30 |
+
if yFc % marge == 0:
|
| 31 |
+
break
|
| 32 |
+
|
| 33 |
+
qStar = yFc // marge
|
| 34 |
+
qTest = qStar + rd.randint(5, 20)
|
| 35 |
+
profitQtest = marge * qTest - yFc
|
| 36 |
+
|
| 37 |
+
# Affichage
|
| 38 |
+
yFcAff = pxsl_format_number(yFc)
|
| 39 |
+
profitQtestAff = pxsl_format_number(profitQtest)
|
| 40 |
+
|
| 41 |
+
globals()
|
| 42 |
+
````
|
| 43 |
+
|
| 44 |
+
{fr}`Une boutique vend des écharpes faites main au prix unitaire `{en}`A shop sells handmade scarves at the unit price `$p = {{p}}${fr}` €. Le coût variable unitaire est `{en}` €. The unit variable cost is `$c = {{c}}${fr}` € et le coût fixe mensuel `{en}` € and the monthly fixed cost `$y_{FC} = {{yFcAff}}$ €.
|
| 45 |
+
|
| 46 |
+
:::::{question}
|
| 47 |
+
:questionType: FGQ
|
| 48 |
+
:questionId: 0
|
| 49 |
+
:questionIndex: 0
|
| 50 |
+
:solution: [["ord","${{ p }}$","${{ c }}$","${{ yFcAff }}$"],["0","0","0"]]
|
| 51 |
+
|
| 52 |
+
::::{questionStatement}
|
| 53 |
+
{fr}`Écrire le revenu total `{en}`Write the total revenue `$y_{TR}(q)${fr}` et le coût total `{en}` and the total cost `$y_{TC}(q)$.
|
| 54 |
+
- $y_{TR}(q) =$ {input}`||80` $q$
|
| 55 |
+
- $y_{TC}(q) =$ {input}`||80` $q +$ {input}`||80`
|
| 56 |
+
::::
|
| 57 |
+
|
| 58 |
+
::::{questionHint}
|
| 59 |
+
$y_{TR} = p\,q${fr}` et `{en}` and `$y_{TC} = c\,q + y_{FC}$.
|
| 60 |
+
::::
|
| 61 |
+
|
| 62 |
+
::::{displayedSolution}
|
| 63 |
+
$y_{TR}(q) = {{ p }}q$ — $y_{TC}(q) = {{ c }}q + {{ yFcAff }}$
|
| 64 |
+
::::
|
| 65 |
+
|
| 66 |
+
::::{detailedSolution}
|
| 67 |
+
{fr}`On applique les définitions :`{en}`We apply the definitions:`
|
| 68 |
+
|
| 69 |
+
\begin{equation*}
|
| 70 |
+
y_{TR}(q) = {{p}}\,q, \qquad y_{TC}(q) = {{c}}\,q + {{yFcAff}}.
|
| 71 |
+
\end{equation*}
|
| 72 |
+
::::
|
| 73 |
+
|
| 74 |
+
::::{weightDistribution}
|
| 75 |
+
:logic: 10
|
| 76 |
+
:abstraction: 25
|
| 77 |
+
:reasoning: 30
|
| 78 |
+
:calculation: 35
|
| 79 |
+
::::
|
| 80 |
+
:::::
|
| 81 |
+
|
| 82 |
+
:::::{question}
|
| 83 |
+
:questionType: FGQ
|
| 84 |
+
:questionId: 1
|
| 85 |
+
:questionIndex: 1
|
| 86 |
+
:solution: [["ord","${{ marge }}$","${{ yFcAff }}$"],["0","0"]]
|
| 87 |
+
|
| 88 |
+
::::{questionStatement}
|
| 89 |
+
{fr}`Calculer le profit `{en}`Compute the profit `$\Pi(q) = y_{TR}(q) - y_{TC}(q)${fr}` et simplifier.`{en}` and simplify.`\
|
| 90 |
+
\
|
| 91 |
+
$\Pi(q) =$ {input}`||80` $q -$ {input}`||80`
|
| 92 |
+
::::
|
| 93 |
+
|
| 94 |
+
::::{questionHint}
|
| 95 |
+
{fr}`Soustraire le coût total du revenu total, puis regrouper les termes en `{en}`Subtract the total cost from the total revenue, then group the terms in `$q$.
|
| 96 |
+
::::
|
| 97 |
+
|
| 98 |
+
::::{displayedSolution}
|
| 99 |
+
$\Pi(q) = {{ marge }}q - {{ yFcAff }}$
|
| 100 |
+
::::
|
| 101 |
+
|
| 102 |
+
::::{detailedSolution}
|
| 103 |
+
{fr}`On développe :`{en}`We expand:`
|
| 104 |
+
|
| 105 |
+
\begin{equation*}
|
| 106 |
+
\Pi(q) = {{p}}\,q - ({{c}}\,q + {{yFcAff}}) = {{marge}}\,q - {{yFcAff}}.
|
| 107 |
+
\end{equation*}
|
| 108 |
+
|
| 109 |
+
{fr}`Le profit est affine, de coefficient directeur `{en}`The profit is affine, with slope `${{marge}}${fr}` : c'est la marge sur coût variable `{en}`: it is the contribution margin `$p - c = {{p}} - {{c}} = {{marge}}${fr}` € par écharpe.`{en}` € per scarf.`
|
| 110 |
+
::::
|
| 111 |
+
|
| 112 |
+
::::{weightDistribution}
|
| 113 |
+
:logic: 15
|
| 114 |
+
:abstraction: 25
|
| 115 |
+
:reasoning: 25
|
| 116 |
+
:calculation: 35
|
| 117 |
+
::::
|
| 118 |
+
:::::
|
| 119 |
+
|
| 120 |
+
:::::{question}
|
| 121 |
+
:questionType: FGQ
|
| 122 |
+
:questionId: 2
|
| 123 |
+
:questionIndex: 2
|
| 124 |
+
:solution: [["ord","${{ qStar }}$"],["0"]]
|
| 125 |
+
|
| 126 |
+
::::{questionStatement}
|
| 127 |
+
{fr}`Déterminer la quantité de seuil de rentabilité `{en}`Determine the break-even quantity `$q^{*}${fr}` en résolvant `{en}` by solving `$y_{TR}(q) = y_{TC}(q)$.\
|
| 128 |
+
\
|
| 129 |
+
$q^*$ = {input}`||80`
|
| 130 |
+
::::
|
| 131 |
+
|
| 132 |
+
::::{questionHint}
|
| 133 |
+
{fr}`Au seuil de rentabilité, le profit est nul : `{en}`At the break-even point, the profit is zero: `$\Pi(q) = 0$.
|
| 134 |
+
::::
|
| 135 |
+
|
| 136 |
+
::::{displayedSolution}
|
| 137 |
+
$q^* = {{ qStar }}$
|
| 138 |
+
::::
|
| 139 |
+
|
| 140 |
+
::::{detailedSolution}
|
| 141 |
+
{fr}`On résout `{en}`We solve `$\Pi(q) = 0$ :
|
| 142 |
+
|
| 143 |
+
\begin{equation*}
|
| 144 |
+
{{marge}}\,q - {{yFcAff}} = 0 \implies {{marge}}\,q = {{yFcAff}} \implies q^{*} = {{qStar}}.
|
| 145 |
+
\end{equation*}
|
| 146 |
+
|
| 147 |
+
{fr}`La boutique doit vendre au moins `{en}`The shop must sell at least `${{qStar}}${fr}` écharpes par mois pour couvrir ses coûts.`{en}` scarves per month to cover its costs.`
|
| 148 |
+
::::
|
| 149 |
+
|
| 150 |
+
::::{weightDistribution}
|
| 151 |
+
:logic: 15
|
| 152 |
+
:abstraction: 20
|
| 153 |
+
:reasoning: 30
|
| 154 |
+
:calculation: 35
|
| 155 |
+
::::
|
| 156 |
+
:::::
|
| 157 |
+
|
| 158 |
+
:::::{question}
|
| 159 |
+
:questionType: FGQ
|
| 160 |
+
:questionId: 3
|
| 161 |
+
:questionIndex: 3
|
| 162 |
+
:solution: [["ord","${{ profitQtestAff }}$"],["0"]]
|
| 163 |
+
|
| 164 |
+
::::{questionStatement}
|
| 165 |
+
{fr}`Calculer le profit pour `{en}`Compute the profit for `$q = {{qTest}}${fr}` écharpes vendues. La boutique est-elle rentable à ce niveau ?`{en}` scarves sold. Is the shop profitable at this level?`\
|
| 166 |
+
\
|
| 167 |
+
$\Pi({{ qTest }})$ = {input}`||80`
|
| 168 |
+
::::
|
| 169 |
+
|
| 170 |
+
::::{questionHint}
|
| 171 |
+
{fr}`Évaluer `{en}`Evaluate `$\Pi({{qTest}})${fr}` et comparer `{en}` and compare `${{qTest}}${fr}` au seuil `{en}` with the break-even `$q^{*}$.
|
| 172 |
+
::::
|
| 173 |
+
|
| 174 |
+
::::{displayedSolution}
|
| 175 |
+
$\Pi({{ qTest }}) = {{ profitQtestAff }}$
|
| 176 |
+
::::
|
| 177 |
+
|
| 178 |
+
::::{detailedSolution}
|
| 179 |
+
{fr}`On évalue le profit en `{en}`We evaluate the profit at `$q = {{qTest}}$ :
|
| 180 |
+
|
| 181 |
+
\begin{equation*}
|
| 182 |
+
\Pi({{qTest}}) = {{marge}} \times {{qTest}} - {{yFcAff}} = {{profitQtestAff}}.
|
| 183 |
+
\end{equation*}
|
| 184 |
+
|
| 185 |
+
{fr}`soit`{en}`i.e.` ${{ profitQtestAff }}$ {fr}`€.`{en}`€.`
|
| 186 |
+
|
| 187 |
+
{fr}`Comme `{en}`Since `${{qTest}} > q^{*} = {{qStar}}${fr}`, la boutique est rentable et dégage un profit mensuel de `{en}`, the shop is profitable and earns a monthly profit of `${{profitQtestAff}}$ €.
|
| 188 |
+
::::
|
| 189 |
+
|
| 190 |
+
::::{weightDistribution}
|
| 191 |
+
:logic: 15
|
| 192 |
+
:abstraction: 20
|
| 193 |
+
:reasoning: 30
|
| 194 |
+
:calculation: 35
|
| 195 |
+
::::
|
| 196 |
+
:::::
|
| 197 |
+
|
| 198 |
+
`````
|
app/knowledge/fewshots/declinaisons/{fr}`Simplifier des expressions avec exponentielle et logarithme`{en}`Simplify expressions with exponential and logarithm`.md
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
`````{exercise}
|
| 2 |
+
:title: {fr}`Simplifier des expressions avec exponentielle et logarithme`{en}`Simplify expressions with exponential and logarithm`
|
| 3 |
+
:modules:
|
| 4 |
+
:recommendedExecutionTime: 10
|
| 5 |
+
:level: Elementary
|
| 6 |
+
:chap: chap_expLogFunctions_logarithmicFunctions_ESCP
|
| 7 |
+
:involvedConcepts:
|
| 8 |
+
:originalSource:
|
| 9 |
+
:visibility: All
|
| 10 |
+
:variations:
|
| 11 |
+
:comment: Échauffement (thème pur) : simplification via les relations réciproques e^(ln y)=y, ln(e^x)=x, b^(log_b y)=y, log_b(b^x)=x, y compris en composition. Aucune loi algébrique du logarithme.
|
| 12 |
+
:originalExerciseId: 2f1a6101-6599-11f1-a8a1-0ed8d3b012a9
|
| 13 |
+
:id: 07721958-74bf-11f1-a8a1-0ed8d3b012a9
|
| 14 |
+
|
| 15 |
+
````{python}
|
| 16 |
+
import random as rd
|
| 17 |
+
|
| 18 |
+
# Q1 : e^(ln y1)=y1, ln(e^x1)=x1, b1^(log_b1 y2)=y2
|
| 19 |
+
y1 = rd.randint(2, 20)
|
| 20 |
+
x1 = rd.randint(2, 15)
|
| 21 |
+
b1 = rd.choice([2, 3, 4, 5, 6, 7, 8, 9])
|
| 22 |
+
y2 = rd.randint(2, 20)
|
| 23 |
+
r1, r2, r3 = y1, x1, y2
|
| 24 |
+
|
| 25 |
+
# Q2 : log_b2(b2^x2)=x2 (x2 != 0, 1 pour éviter ^1), e^(ln(e^x3))=e^x3
|
| 26 |
+
b2 = rd.choice([2, 3, 4, 5, 6, 7, 8, 9])
|
| 27 |
+
x2 = rd.choice(list(range(-10, 0)) + list(range(2, 11)))
|
| 28 |
+
x3 = rd.randint(2, 10)
|
| 29 |
+
r4 = x2
|
| 30 |
+
r5Aff = "e^{%d}" % x3
|
| 31 |
+
|
| 32 |
+
globals()
|
| 33 |
+
````
|
| 34 |
+
|
| 35 |
+
:::::{question}
|
| 36 |
+
:questionType: FGQ
|
| 37 |
+
:solution: [["ord","{{ r1 }}","{{ r2 }}","{{ r3 }}"],["0","0","0"]]
|
| 38 |
+
|
| 39 |
+
::::{questionStatement}
|
| 40 |
+
{fr}`Simplifier`{en}`Simplify` $e^{\ln {{ y1 }}}$, $\ln(e^{ {{ x1 }} })$ {fr}`et`{en}`and` ${{ b1 }}^{\log_{ {{ b1 }} }({{ y2 }})}$.
|
| 41 |
+
|
| 42 |
+
$e^{\ln {{ y1 }}} =$ {input}`||70`
|
| 43 |
+
|
| 44 |
+
$\ln(e^{ {{ x1 }} }) =$ {input}`||70`
|
| 45 |
+
|
| 46 |
+
${{ b1 }}^{\log_{ {{ b1 }} }({{ y2 }})} =$ {input}`||70`
|
| 47 |
+
::::
|
| 48 |
+
|
| 49 |
+
::::{questionHint}
|
| 50 |
+
$e^{\ln y}=y$, $\ln(e^{x})=x$, $b^{\log_{b} y}=y$.
|
| 51 |
+
::::
|
| 52 |
+
|
| 53 |
+
::::{displayedSolution}
|
| 54 |
+
$e^{\ln {{ y1 }}} = {{ r1 }}$
|
| 55 |
+
|
| 56 |
+
$\ln(e^{ {{ x1 }} }) = {{ r2 }}$
|
| 57 |
+
|
| 58 |
+
${{ b1 }}^{\log_{ {{ b1 }} }({{ y2 }})} = {{ r3 }}$
|
| 59 |
+
::::
|
| 60 |
+
|
| 61 |
+
::::{detailedSolution}
|
| 62 |
+
{fr}`Par les relations réciproques :`{en}`By the reciprocal relations:` $e^{\ln {{ y1 }}}={{ r1 }}$, $\quad \ln(e^{ {{ x1 }} })={{ r2 }}$, $\quad {{ b1 }}^{\log_{ {{ b1 }} }({{ y2 }})}={{ r3 }}$.
|
| 63 |
+
|
| 64 |
+
{fr}`(respectivement`{en}`(respectively` $e^{\ln y}=y$ {fr}`avec`{en}`with` $y={{ y1 }}$ ; $\ln(e^{x})=x$ {fr}`avec`{en}`with` $x={{ x1 }}$ ; $b^{\log_{b} y}=y$ {fr}`avec`{en}`with` $b={{ b1 }}$, $y={{ y2 }}$.)
|
| 65 |
+
::::
|
| 66 |
+
|
| 67 |
+
::::{weightDistribution}
|
| 68 |
+
:logic: 20
|
| 69 |
+
:abstraction: 30
|
| 70 |
+
:reasoning: 30
|
| 71 |
+
:calculation: 20
|
| 72 |
+
::::
|
| 73 |
+
:::::
|
| 74 |
+
|
| 75 |
+
:::::{question}
|
| 76 |
+
:questionType: FGQ
|
| 77 |
+
:solution: [["ord","{{ r4 }}","${{ r5Aff }}$"],["0","0"]]
|
| 78 |
+
|
| 79 |
+
::::{questionStatement}
|
| 80 |
+
{fr}`Simplifier`{en}`Simplify` $\log_{ {{ b2 }} }({{ b2 }}^{ {{ x2 }} })$ {fr}`et`{en}`and` $e^{\ln(e^{ {{ x3 }} })}$.
|
| 81 |
+
|
| 82 |
+
$\log_{ {{ b2 }} }({{ b2 }}^{ {{ x2 }} }) =$ {input}`||70`
|
| 83 |
+
|
| 84 |
+
$e^{\ln(e^{ {{ x3 }} })} =$ {input}`||90`
|
| 85 |
+
::::
|
| 86 |
+
|
| 87 |
+
::::{questionHint}
|
| 88 |
+
{fr}`Pour la seconde, simplifier de l'intérieur vers l'extérieur : d'abord`{en}`For the second, simplify from the inside out: first` $\ln(e^{ {{ x3 }} })$.
|
| 89 |
+
::::
|
| 90 |
+
|
| 91 |
+
::::{displayedSolution}
|
| 92 |
+
$\log_{ {{ b2 }} }({{ b2 }}^{ {{ x2 }} }) = {{ r4 }}$
|
| 93 |
+
|
| 94 |
+
$e^{\ln(e^{ {{ x3 }} })} = {{ r5Aff }}$
|
| 95 |
+
::::
|
| 96 |
+
|
| 97 |
+
::::{detailedSolution}
|
| 98 |
+
{fr}`Par`{en}`By` $\log_{b}(b^{x})=x$ :
|
| 99 |
+
|
| 100 |
+
\begin{equation*}
|
| 101 |
+
\log_{ {{ b2 }} }({{ b2 }}^{ {{ x2 }} }) = {{ r4 }}.
|
| 102 |
+
\end{equation*}
|
| 103 |
+
|
| 104 |
+
{fr}`Pour la seconde,`{en}`For the second,` $\ln(e^{ {{ x3 }} })={{ x3 }}$, {fr}`puis`{en}`then` $e^{\ln(e^{ {{ x3 }} })}=e^{ {{ x3 }} }$ {fr}`(relation`{en}`(relation` $e^{\ln y}=y$ {fr}`avec`{en}`with` $y=e^{ {{ x3 }} }>0$) :
|
| 105 |
+
|
| 106 |
+
\begin{equation*}
|
| 107 |
+
e^{\ln(e^{ {{ x3 }} })} = {{ r5Aff }}.
|
| 108 |
+
\end{equation*}
|
| 109 |
+
::::
|
| 110 |
+
|
| 111 |
+
::::{weightDistribution}
|
| 112 |
+
:logic: 20
|
| 113 |
+
:abstraction: 35
|
| 114 |
+
:reasoning: 30
|
| 115 |
+
:calculation: 15
|
| 116 |
+
::::
|
| 117 |
+
:::::
|
| 118 |
+
|
| 119 |
+
`````
|
app/knowledge/fewshots/declinaisons/{fr}`Symétries d'une courbe de demande et restriction économique`{en}`Symmetries of a demand curve and economic restriction`.md
ADDED
|
@@ -0,0 +1,192 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
`````{exercise}
|
| 2 |
+
:title: {fr}`Symétries d'une courbe de demande et restriction économique`{en}`Symmetries of a demand curve and economic restriction`
|
| 3 |
+
:modules:
|
| 4 |
+
:recommendedExecutionTime: 18
|
| 5 |
+
:level: Elementary
|
| 6 |
+
:chap: chap_realFunctions_Graphs_graphSymmetries_ESCP
|
| 7 |
+
:involvedConcepts:
|
| 8 |
+
:originalSource:
|
| 9 |
+
:visibility: All
|
| 10 |
+
:variations:
|
| 11 |
+
:comment: Appliqué (économie & gestion) : quatre tests de symétrie sur une courbe de demande implicite (cercle), puis restriction au premier quadrant et identification de la seule symétrie pertinente (y=x), en reconnaissance uniquement.
|
| 12 |
+
:id: 64470bcd-74b4-11f1-a8a1-0ed8d3b012a9
|
| 13 |
+
:originalExerciseId: e6ac5c47-6409-11f1-a8a1-0ed8d3b012a9
|
| 14 |
+
|
| 15 |
+
````{python}
|
| 16 |
+
import random as rd
|
| 17 |
+
from pyxiscience.Mes_fctions_generalistes_bis import pxs_config
|
| 18 |
+
|
| 19 |
+
config_standard = pxs_config()
|
| 20 |
+
|
| 21 |
+
rayon = rd.randint(3, 10)
|
| 22 |
+
rayon_carre = rayon**2
|
| 23 |
+
|
| 24 |
+
if pxs_lang == "fr":
|
| 25 |
+
unite_demande = rd.choice(["centaines d'unités", "milliers d'unités", "dizaines d'unités"])
|
| 26 |
+
unite_prix = rd.choice(["dizaines d'euros", "centaines d'euros", "euros"])
|
| 27 |
+
type_produit = rd.choice(["produit de luxe", "produit technologique", "bien de consommation", "service premium"])
|
| 28 |
+
else:
|
| 29 |
+
unite_demande = rd.choice(["hundreds of units", "thousands of units", "tens of units"])
|
| 30 |
+
unite_prix = rd.choice(["tens of dollars", "hundreds of dollars", "dollars"])
|
| 31 |
+
type_produit = rd.choice(["luxury product", "technological product", "consumer good", "premium service"])
|
| 32 |
+
|
| 33 |
+
# Rendus
|
| 34 |
+
rayonCarre = rayon_carre
|
| 35 |
+
uniteDemande = unite_demande
|
| 36 |
+
unitePrix = unite_prix
|
| 37 |
+
typeProduit = type_produit
|
| 38 |
+
|
| 39 |
+
globals()
|
| 40 |
+
````
|
| 41 |
+
|
| 42 |
+
:::::{question}
|
| 43 |
+
:questionType: MCQ
|
| 44 |
+
:questionId: 0
|
| 45 |
+
:questionIndex: 0
|
| 46 |
+
|
| 47 |
+
::::{questionStatement}
|
| 48 |
+
{fr}`Un directeur commercial étudie la courbe de demande d'un`{en}`A sales director studies the demand curve of a` {{ typeProduit }} {fr}`: la demande`{en}`: the demand` $D>0$ {fr}`(en`{en}`(in` {{ uniteDemande }}) {fr}`et le prix`{en}`and the price` $p>0$ {fr}`(en`{en}`(in` {{ unitePrix }}) {fr}`vérifient`{en}`satisfy` $p^2+D^2={{ rayonCarre }}$. {fr}`Appliquer les quatre tests algébriques de symétrie (par rapport à l'axe`{en}`Apply the four algebraic symmetry tests (with respect to the axis` $Op$, {fr}`à l'axe`{en}`to the axis` $OD$, {fr}`à l'origine, à la droite`{en}`to the origin, to the line` $D=p$) {fr}`à la courbe complète et conclure pour chacun.`{en}`to the complete curve and conclude for each.`
|
| 49 |
+
::::
|
| 50 |
+
|
| 51 |
+
::::{questionHint}
|
| 52 |
+
{fr}`Substituer dans l'équation : les carrés rendent`{en}`Substitute in the equation: the squares make` $p^2$ {fr}`et`{en}`and` $D^2$ {fr}`inchangés par changement de signe ; échanger`{en}`unchanged by sign change; exchanging` $p$ {fr}`et`{en}`and` $D$ {fr}`ne change pas une somme.`{en}`does not change a sum.`
|
| 53 |
+
::::
|
| 54 |
+
|
| 55 |
+
::::{mcqAnswer}
|
| 56 |
+
:isRightAnswer: true
|
| 57 |
+
{fr}`Les quatre :`{en}`All four:` $Op$, $OD$, {fr}`l'origine et la droite`{en}`the origin, and the line` $D=p$
|
| 58 |
+
::::
|
| 59 |
+
|
| 60 |
+
::::{mcqAnswer}
|
| 61 |
+
:isRightAnswer: false
|
| 62 |
+
{fr}`Seulement les axes`{en}`Only the axes` $Op$ {fr}`et`{en}`and` $OD$
|
| 63 |
+
::::
|
| 64 |
+
|
| 65 |
+
::::{mcqAnswer}
|
| 66 |
+
:isRightAnswer: false
|
| 67 |
+
{fr}`Seulement la droite`{en}`Only the line` $D=p$
|
| 68 |
+
::::
|
| 69 |
+
|
| 70 |
+
::::{mcqAnswer}
|
| 71 |
+
:isRightAnswer: false
|
| 72 |
+
{fr}`Seulement l'origine`{en}`Only the origin`
|
| 73 |
+
::::
|
| 74 |
+
|
| 75 |
+
::::{mcqAnswer}
|
| 76 |
+
:isRightAnswer: false
|
| 77 |
+
{fr}`Aucune de ces réponses n'est correcte`{en}`None of these answers are correct`
|
| 78 |
+
::::
|
| 79 |
+
|
| 80 |
+
::::{detailedSolution}
|
| 81 |
+
{fr}`La courbe complète`{en}`The complete curve` $p^2+D^2={{ rayonCarre }}$ {fr}`est un cercle de centre`{en}`is a circle with center` $O$ {fr}`et de rayon`{en}`and radius` ${{ rayon }}$.
|
| 82 |
+
|
| 83 |
+
\begin{equation*}
|
| 84 |
+
p^2 + D^2 = {{ rayonCarre }}.
|
| 85 |
+
\end{equation*}
|
| 86 |
+
|
| 87 |
+
{fr}`**Test axe**`{en}`**Axis test**` $Op$ ($D \to -D$) : {fr}`on remplace`{en}`we replace` $D$ {fr}`par`{en}`by` $-D$ {fr}`dans l'équation :`{en}`in the equation:`
|
| 88 |
+
|
| 89 |
+
\begin{equation*}
|
| 90 |
+
p^2 + (-D)^2 = p^2 + D^2 = {{ rayonCarre }}.
|
| 91 |
+
\end{equation*}
|
| 92 |
+
|
| 93 |
+
{fr}`L'équation est inchangée : la courbe est symétrique par rapport à l'axe`{en}`The equation is unchanged: the curve is symmetric with respect to the axis` $Op$.
|
| 94 |
+
|
| 95 |
+
{fr}`**Test axe**`{en}`**Axis test**` $OD$ ($p \to -p$) : {fr}`on remplace`{en}`we replace` $p$ {fr}`par`{en}`by` $-p$ {fr}`dans l'équation :`{en}`in the equation:`
|
| 96 |
+
|
| 97 |
+
\begin{equation*}
|
| 98 |
+
(-p)^2 + D^2 = p^2 + D^2 = {{ rayonCarre }}.
|
| 99 |
+
\end{equation*}
|
| 100 |
+
|
| 101 |
+
{fr}`L'équation est inchangée : la courbe est symétrique par rapport à l'axe`{en}`The equation is unchanged: the curve is symmetric with respect to the axis` $OD$.
|
| 102 |
+
|
| 103 |
+
{fr}`**Test origine** (`{en}`**Origin test** (` $p \to -p$, $D \to -D$) : {fr}`on remplace`{en}`we replace` $p$ {fr}`par`{en}`by` $-p$ {fr}`et`{en}`and` $D$ {fr}`par`{en}`by` $-D$ :
|
| 104 |
+
|
| 105 |
+
\begin{equation*}
|
| 106 |
+
(-p)^2 + (-D)^2 = p^2 + D^2 = {{ rayonCarre }}.
|
| 107 |
+
\end{equation*}
|
| 108 |
+
|
| 109 |
+
{fr}`L'équation est inchangée : la courbe est symétrique par rapport à l'origine.`{en}`The equation is unchanged: the curve is symmetric with respect to the origin.`
|
| 110 |
+
|
| 111 |
+
{fr}`**Test droite**`{en}`**Line test**` $D=p$ {fr}`(échanger`{en}`(exchange` $p$ {fr}`et`{en}`and` $D$) : {fr}`on échange`{en}`we exchange` $p$ {fr}`et`{en}`and` $D$ {fr}`dans l'équation :`{en}`in the equation:`
|
| 112 |
+
|
| 113 |
+
\begin{equation*}
|
| 114 |
+
D^2 + p^2 = {{ rayonCarre }}.
|
| 115 |
+
\end{equation*}
|
| 116 |
+
|
| 117 |
+
{fr}`Cette équation est identique à l'équation initiale : la courbe est symétrique par rapport à la droite`{en}`This equation is identical to the initial equation: the curve is symmetric with respect to the line` $D=p$.
|
| 118 |
+
|
| 119 |
+
{fr}`**Conclusion** : la courbe complète possède les quatre symétries.`{en}`**Conclusion**: the complete curve has all four symmetries.`
|
| 120 |
+
::::
|
| 121 |
+
|
| 122 |
+
::::{weightDistribution}
|
| 123 |
+
:logic: 25
|
| 124 |
+
:abstraction: 35
|
| 125 |
+
:reasoning: 30
|
| 126 |
+
:calculation: 10
|
| 127 |
+
::::
|
| 128 |
+
:::::
|
| 129 |
+
|
| 130 |
+
:::::{question}
|
| 131 |
+
:questionType: MCQ
|
| 132 |
+
:questionId: 1
|
| 133 |
+
:questionIndex: 1
|
| 134 |
+
|
| 135 |
+
::::{questionStatement}
|
| 136 |
+
{fr}`Justifier pourquoi, dans le contexte économique (`{en}`Justify why, in the economic context (` $p>0$ {fr}`et`{en}`and` $D>0$), {fr}`la courbe de demande n'est définie que sur un quart de cercle. En déduire la seule symétrie pertinente pour ce modèle, et l'interpréter.`{en}`the demand curve is only defined on a quarter circle. Deduce the only relevant symmetry for this model, and interpret it.`
|
| 137 |
+
::::
|
| 138 |
+
|
| 139 |
+
::::{questionHint}
|
| 140 |
+
{fr}`Une symétrie n'est pertinente que si elle renvoie un point du premier quadrant dans le premier quadrant.`{en}`A symmetry is relevant only if it maps a point from the first quadrant to the first quadrant.`
|
| 141 |
+
::::
|
| 142 |
+
|
| 143 |
+
::::{mcqAnswer}
|
| 144 |
+
:isRightAnswer: true
|
| 145 |
+
{fr}`La droite`{en}`The line` $D=p$
|
| 146 |
+
::::
|
| 147 |
+
|
| 148 |
+
::::{mcqAnswer}
|
| 149 |
+
:isRightAnswer: false
|
| 150 |
+
{fr}`L'axe`{en}`The axis` $Op$
|
| 151 |
+
::::
|
| 152 |
+
|
| 153 |
+
::::{mcqAnswer}
|
| 154 |
+
:isRightAnswer: false
|
| 155 |
+
{fr}`L'axe`{en}`The axis` $OD$
|
| 156 |
+
::::
|
| 157 |
+
|
| 158 |
+
::::{mcqAnswer}
|
| 159 |
+
:isRightAnswer: false
|
| 160 |
+
{fr}`L'origine`{en}`The origin`
|
| 161 |
+
::::
|
| 162 |
+
|
| 163 |
+
::::{mcqAnswer}
|
| 164 |
+
:isRightAnswer: false
|
| 165 |
+
{fr}`Aucune de ces réponses n'est correcte`{en}`None of these answers are correct`
|
| 166 |
+
::::
|
| 167 |
+
|
| 168 |
+
::::{detailedSolution}
|
| 169 |
+
{fr}`**Restriction au premier quadrant** : dans le contexte économique, le prix`{en}`**Restriction to the first quadrant**: in the economic context, the price` $p$ {fr}`et la demande`{en}`and the demand` $D$ {fr}`sont des grandeurs strictement positives. On a donc`{en}`are strictly positive quantities. We therefore have` $p>0$ {fr}`et`{en}`and` $D>0$, {fr}`ce qui signifie que seuls les points du premier quadrant sont économiquement pertinents. La courbe de demande se réduit donc à un quart de cercle de rayon`{en}`which means that only points in the first quadrant are economically relevant. The demand curve is therefore reduced to a quarter circle of radius` ${{ rayon }}$ {fr}`situé dans le premier quadrant.`{en}`located in the first quadrant.`
|
| 170 |
+
|
| 171 |
+
{fr}`**Analyse des symétries** :`{en}`**Analysis of symmetries**:`
|
| 172 |
+
|
| 173 |
+
- {fr}`**Symétrie par rapport à l'axe**`{en}`**Symmetry with respect to the axis**` $Op$ ($D \to -D$) : {fr}`cette transformation envoie un point`{en}`this transformation maps a point` $(p_0, D_0)$ {fr}`avec`{en}`with` $p_0>0$ {fr}`et`{en}`and` $D_0>0$ {fr}`sur le point`{en}`to the point` $(p_0, -D_0)$ {fr}`avec`{en}`with` $-D_0<0$. {fr}`Ce point sort du premier quadrant : cette symétrie n'est pas pertinente dans le contexte économique.`{en}`This point leaves the first quadrant: this symmetry is not relevant in the economic context.`
|
| 174 |
+
|
| 175 |
+
- {fr}`**Symétrie par rapport à l'axe**`{en}`**Symmetry with respect to the axis**` $OD$ ($p \to -p$) : {fr}`cette transformation envoie un point`{en}`this transformation maps a point` $(p_0, D_0)$ {fr}`avec`{en}`with` $p_0>0$ {fr}`et`{en}`and` $D_0>0$ {fr}`sur le point`{en}`to the point` $(-p_0, D_0)$ {fr}`avec`{en}`with` $-p_0<0$. {fr}`Ce point sort du premier quadrant : cette symétrie n'est pas pertinente dans le contexte économique.`{en}`This point leaves the first quadrant: this symmetry is not relevant in the economic context.`
|
| 176 |
+
|
| 177 |
+
- {fr}`**Symétrie par rapport à l'origine** (`{en}`**Symmetry with respect to the origin** (` $p \to -p$, $D \to -D$) : {fr}`cette transformation envoie un point`{en}`this transformation maps a point` $(p_0, D_0)$ {fr}`avec`{en}`with` $p_0>0$ {fr}`et`{en}`and` $D_0>0$ {fr}`sur le point`{en}`to the point` $(-p_0, -D_0)$ {fr}`avec`{en}`with` $-p_0<0$ {fr}`et`{en}`and` $-D_0<0$. {fr}`Ce point sort du premier quadrant : cette symétrie n'est pas pertinente dans le contexte économique.`{en}`This point leaves the first quadrant: this symmetry is not relevant in the economic context.`
|
| 178 |
+
|
| 179 |
+
- {fr}`**Symétrie par rapport à la droite**`{en}`**Symmetry with respect to the line**` $D=p$ {fr}`(échanger`{en}`(exchange` $p$ {fr}`et`{en}`and` $D$) : {fr}`cette transformation envoie un point`{en}`this transformation maps a point` $(p_0, D_0)$ {fr}`avec`{en}`with` $p_0>0$ {fr}`et`{en}`and` $D_0>0$ {fr}`sur le point`{en}`to the point` $(D_0, p_0)$ {fr}`avec`{en}`with` $D_0>0$ {fr}`et`{en}`and` $p_0>0$. {fr}`Ce point reste dans le premier quadrant : c'est la seule symétrie pertinente dans le contexte économique.`{en}`This point remains in the first quadrant: this is the only relevant symmetry in the economic context.`
|
| 180 |
+
|
| 181 |
+
{fr}`**Interprétation** : la symétrie par rapport à la droite`{en}`**Interpretation**: the symmetry with respect to the line` $D=p$ {fr}`signifie que si à un prix`{en}`means that if at a price` $p_0$ {fr}`correspond une demande`{en}`corresponds a demand` $D_0$ {fr}`(le point`{en}`(the point` $(p_0, D_0)$ {fr}`est sur la courbe), alors le point`{en}`is on the curve), then the point` $(D_0, p_0)$ {fr}`est également sur la courbe. Autrement dit, si la demande vaut`{en}`is also on the curve. In other words, if the demand equals` $D_0$ {fr}`unités au prix`{en}`units at price` $p_0$, {fr}`alors elle vaudra`{en}`then it will equal` $p_0$ {fr}`unités au prix`{en}`units at price` $D_0$. {fr}`Le modèle est donc réversible vis-à-vis de l'échange entre prix et demande : la relation entre`{en}`The model is therefore reversible with respect to the exchange between price and demand: the relationship between` $p$ {fr}`et`{en}`and` $D$ {fr}`est symétrique.`{en}`is symmetric.`
|
| 182 |
+
::::
|
| 183 |
+
|
| 184 |
+
::::{weightDistribution}
|
| 185 |
+
:logic: 30
|
| 186 |
+
:abstraction: 35
|
| 187 |
+
:reasoning: 30
|
| 188 |
+
:calculation: 5
|
| 189 |
+
::::
|
| 190 |
+
:::::
|
| 191 |
+
|
| 192 |
+
`````
|
app/knowledge/fewshots/declinaisons/{fr}`Unités minimales pour couvrir les coûts fixes`{en}`Minimum units to cover fixed costs`.md
ADDED
|
@@ -0,0 +1,242 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
`````{exercise}
|
| 2 |
+
:originalExerciseId: 7629a255-634b-11f1-a8a1-0ed8d3b012a9
|
| 3 |
+
:title: {fr}`Unités minimales pour couvrir les coûts fixes`{en}`Minimum units to cover fixed costs`
|
| 4 |
+
:modules:
|
| 5 |
+
:recommendedExecutionTime: 10
|
| 6 |
+
:level: Elementary
|
| 7 |
+
:chap:
|
| 8 |
+
:involvedConcepts: Solving_inequalities, Modeling_with_Functions
|
| 9 |
+
:originalSource: Session 2: Equations and Inequalities - Applications of Linear Inequalities (Erwan Lamy, ESCP Business School)
|
| 10 |
+
:visibility: All
|
| 11 |
+
:variations:
|
| 12 |
+
:comment: Version QCM — condition de non-perte, résolution et arrondi entier.
|
| 13 |
+
:id: a3e299b2-6e47-11f1-a8a1-0ed8d3b012a9
|
| 14 |
+
|
| 15 |
+
````{python}
|
| 16 |
+
import random as rd
|
| 17 |
+
from sympy import Rational, ceiling, oo, Integer
|
| 18 |
+
from pyxiscience.Mes_fctions_generalistes_bis import pxs_config, pxsl_format_number, pxsl_num
|
| 19 |
+
from pyxiscience.Classes_Extensions import pxs_Interval
|
| 20 |
+
|
| 21 |
+
config_standard = pxs_config()
|
| 22 |
+
|
| 23 |
+
# Coûts fixes entiers, marge unitaire décimale (k/10), quotient non entier -> arrondi utile
|
| 24 |
+
for _ in range(300):
|
| 25 |
+
fixedCost = rd.randint(400, 1000)
|
| 26 |
+
k = rd.randint(5, 30)
|
| 27 |
+
if (10 * fixedCost) % k != 0:
|
| 28 |
+
break
|
| 29 |
+
|
| 30 |
+
margin = Rational(k, 10)
|
| 31 |
+
qExact = Rational(fixedCost * 10, k)
|
| 32 |
+
qMin = int(ceiling(qExact))
|
| 33 |
+
|
| 34 |
+
# Précalcul de l'affichage
|
| 35 |
+
marginAff = pxsl_num(margin, dec=2)
|
| 36 |
+
fixedCostAff = pxsl_format_number(fixedCost)
|
| 37 |
+
qApproxAff = pxsl_num(qExact, dec=2)
|
| 38 |
+
qMinAff = pxsl_format_number(qMin)
|
| 39 |
+
margeFoisQminAff = pxsl_num(margin * qMin, dec=2)
|
| 40 |
+
|
| 41 |
+
# === Distracteurs MCQ (ajouts ; le code ci-dessus est inchangé) ===
|
| 42 |
+
def _dedl(correct_l, cands, n=3):
|
| 43 |
+
seen = {correct_l}; out = []
|
| 44 |
+
for s in cands:
|
| 45 |
+
if s not in seen:
|
| 46 |
+
seen.add(s); out.append(s)
|
| 47 |
+
if len(out) == n:
|
| 48 |
+
break
|
| 49 |
+
return out
|
| 50 |
+
|
| 51 |
+
if isinstance(qExact, Integer):
|
| 52 |
+
borne = qExact
|
| 53 |
+
else:
|
| 54 |
+
borne = int(qExact) + 1
|
| 55 |
+
|
| 56 |
+
# Q2 : entier minimal
|
| 57 |
+
repBorne = pxsl_format_number(int(borne))
|
| 58 |
+
_cB = [pxsl_format_number(int(borne) - 1),
|
| 59 |
+
pxsl_format_number(int(ceiling(Rational(fixedCost, k)))),
|
| 60 |
+
pxsl_format_number(int(borne) + 1),
|
| 61 |
+
pxsl_format_number(int(2 * borne))]
|
| 62 |
+
_d = _dedl(repBorne, _cB)
|
| 63 |
+
b1, b2, b3 = _d[0], _d[1], _d[2]
|
| 64 |
+
|
| 65 |
+
# Q1 : intervalles
|
| 66 |
+
def _ival(lo, hi, ol, oh):
|
| 67 |
+
s = pxs_Interval(lo, hi, ol, oh).print()
|
| 68 |
+
if pxs_lang == "fr":
|
| 69 |
+
s = s.replace(".", ",")
|
| 70 |
+
return s
|
| 71 |
+
|
| 72 |
+
_qa = round(float(qExact), 2)
|
| 73 |
+
interCorrect = _ival(_qa, oo, False, True)
|
| 74 |
+
interD1 = _ival(_qa, oo, True, True)
|
| 75 |
+
interD2 = _ival(-oo, _qa, True, False)
|
| 76 |
+
interD3 = _ival(0, _qa, False, False)
|
| 77 |
+
|
| 78 |
+
globals()
|
| 79 |
+
````
|
| 80 |
+
|
| 81 |
+
:::::{question}
|
| 82 |
+
:questionType: MCQ
|
| 83 |
+
|
| 84 |
+
::::{questionStatement}
|
| 85 |
+
{fr}`Une petite boulangerie a des coûts fixes hebdomadaires de`{en}`A small bakery has weekly fixed costs of` ${{fixedCostAff}}$ {fr}`€ (loyer, salaires, etc.). Chaque pain vendu génère une marge de contribution de`{en}`€ (rent, salaries, etc.). Each loaf of bread sold generates a contribution margin of` ${{marginAff}}$ {fr}`€ (prix de vente moins coût variable unitaire). On note`{en}`€ (selling price minus unit variable cost). Let` $q$ {fr}`le nombre de pains vendus par semaine. Quelle inéquation traduit que la marge de contribution totale couvre les coûts fixes (la boulangerie ne fait pas de perte) ?`{en}`be the number of loaves sold per week. Which inequality expresses that the total contribution margin covers the fixed costs (the bakery does not make a loss)?`
|
| 86 |
+
::::
|
| 87 |
+
|
| 88 |
+
::::{questionHint}
|
| 89 |
+
{fr}`La marge de contribution totale est égale à`{en}`The total contribution margin is equal to` ${{marginAff}}\,q$. {fr}`La condition de non-perte correspond à une marge au moins égale aux coûts fixes.`{en}`The break-even condition corresponds to a margin at least equal to the fixed costs.`
|
| 90 |
+
::::
|
| 91 |
+
|
| 92 |
+
::::{mcqAnswer}
|
| 93 |
+
:isRightAnswer: true
|
| 94 |
+
${{marginAff}}\,q \geq {{fixedCostAff}}$
|
| 95 |
+
::::
|
| 96 |
+
|
| 97 |
+
::::{mcqAnswer}
|
| 98 |
+
:isRightAnswer: false
|
| 99 |
+
${{marginAff}}\,q \leq {{fixedCostAff}}$
|
| 100 |
+
::::
|
| 101 |
+
|
| 102 |
+
::::{mcqAnswer}
|
| 103 |
+
:isRightAnswer: false
|
| 104 |
+
${{marginAff}}\,q = {{fixedCostAff}}$
|
| 105 |
+
::::
|
| 106 |
+
|
| 107 |
+
::::{mcqAnswer}
|
| 108 |
+
:isRightAnswer: false
|
| 109 |
+
${{fixedCostAff}}\,q \geq {{marginAff}}$
|
| 110 |
+
::::
|
| 111 |
+
|
| 112 |
+
::::{mcqAnswer}
|
| 113 |
+
:isRightAnswer: false
|
| 114 |
+
${{fixedCostAff}}\,q \leq {{marginAff}}$
|
| 115 |
+
::::
|
| 116 |
+
|
| 117 |
+
::::{mcqAnswer}
|
| 118 |
+
:isRightAnswer: false
|
| 119 |
+
{fr}`Aucune de ces réponses n'est correcte`{en}`None of these answers are correct`
|
| 120 |
+
::::
|
| 121 |
+
|
| 122 |
+
::::{detailedSolution}
|
| 123 |
+
{fr}`La marge de contribution totale est`{en}`The total contribution margin is` ${{marginAff}}\,q$. {fr}`La condition de non-perte s'écrit :`{en}`The break-even condition is written as:`
|
| 124 |
+
|
| 125 |
+
\begin{equation*}
|
| 126 |
+
{{marginAff}}\,q \geq {{fixedCostAff}}.
|
| 127 |
+
\end{equation*}
|
| 128 |
+
::::
|
| 129 |
+
|
| 130 |
+
::::{weightDistribution}
|
| 131 |
+
:logic: 15
|
| 132 |
+
:abstraction: 30
|
| 133 |
+
:reasoning: 35
|
| 134 |
+
:calculation: 20
|
| 135 |
+
::::
|
| 136 |
+
:::::
|
| 137 |
+
|
| 138 |
+
:::::{question}
|
| 139 |
+
:questionType: MCQ
|
| 140 |
+
|
| 141 |
+
::::{questionStatement}
|
| 142 |
+
{fr}`Résoudre l'inéquation`{en}`Solve the inequality` ${{marginAff}}\,q \geq {{fixedCostAff}}$ {fr}`(pour`{en}`(for` $q \geq 0${fr}`). Quel est l'ensemble des solutions sous forme d'intervalle ?`{en}`). What is the solution set in interval form?`
|
| 143 |
+
::::
|
| 144 |
+
|
| 145 |
+
::::{questionHint}
|
| 146 |
+
{fr}`Diviser les deux membres par la marge unitaire, qui est positive : le sens est conservé.`{en}`Divide both sides by the unit margin, which is positive: the direction is preserved.`
|
| 147 |
+
::::
|
| 148 |
+
|
| 149 |
+
::::{mcqAnswer}
|
| 150 |
+
:isRightAnswer: true
|
| 151 |
+
${{interCorrect}}$
|
| 152 |
+
::::
|
| 153 |
+
|
| 154 |
+
::::{mcqAnswer}
|
| 155 |
+
:isRightAnswer: false
|
| 156 |
+
${{interD1}}$
|
| 157 |
+
::::
|
| 158 |
+
|
| 159 |
+
::::{mcqAnswer}
|
| 160 |
+
:isRightAnswer: false
|
| 161 |
+
${{interD2}}$
|
| 162 |
+
::::
|
| 163 |
+
|
| 164 |
+
::::{mcqAnswer}
|
| 165 |
+
:isRightAnswer: false
|
| 166 |
+
${{interD3}}$
|
| 167 |
+
::::
|
| 168 |
+
|
| 169 |
+
::::{mcqAnswer}
|
| 170 |
+
:isRightAnswer: false
|
| 171 |
+
{fr}`Aucune de ces réponses n'est correcte`{en}`None of these answers are correct`
|
| 172 |
+
::::
|
| 173 |
+
|
| 174 |
+
::::{detailedSolution}
|
| 175 |
+
{fr}`On divise les deux membres par`{en}`We divide both sides by` ${{marginAff}} > 0$, {fr}`le sens est conservé :`{en}`the direction is preserved:`
|
| 176 |
+
|
| 177 |
+
\begin{equation*}
|
| 178 |
+
q \geq \dfrac{ {{fixedCostAff}} }{ {{marginAff}} } \approx {{qApproxAff}}.
|
| 179 |
+
\end{equation*}
|
| 180 |
+
|
| 181 |
+
{fr}`L'ensemble des solutions (pour`{en}`The solution set (for` $q \geq 0${fr}`) est`{en}`) is` ${{interCorrect}}$.
|
| 182 |
+
::::
|
| 183 |
+
|
| 184 |
+
::::{weightDistribution}
|
| 185 |
+
:logic: 10
|
| 186 |
+
:abstraction: 15
|
| 187 |
+
:reasoning: 25
|
| 188 |
+
:calculation: 50
|
| 189 |
+
::::
|
| 190 |
+
:::::
|
| 191 |
+
|
| 192 |
+
:::::{question}
|
| 193 |
+
:questionType: MCQ
|
| 194 |
+
|
| 195 |
+
::::{questionStatement}
|
| 196 |
+
{fr}`Quel est le nombre minimal de pains entiers que la boulangerie doit vendre pour atteindre le seuil de rentabilité ?`{en}`What is the minimum number of whole loaves that the bakery must sell to reach the break-even point?`
|
| 197 |
+
::::
|
| 198 |
+
|
| 199 |
+
::::{questionHint}
|
| 200 |
+
{fr}`Le nombre de pains est un entier. Arrondir la solution à l'entier supérieur.`{en}`The number of loaves is an integer. Round the solution up to the next integer.`
|
| 201 |
+
::::
|
| 202 |
+
|
| 203 |
+
::::{mcqAnswer}
|
| 204 |
+
:isRightAnswer: true
|
| 205 |
+
${{repBorne}}$
|
| 206 |
+
::::
|
| 207 |
+
|
| 208 |
+
::::{mcqAnswer}
|
| 209 |
+
:isRightAnswer: false
|
| 210 |
+
${{b1}}$
|
| 211 |
+
::::
|
| 212 |
+
|
| 213 |
+
::::{mcqAnswer}
|
| 214 |
+
:isRightAnswer: false
|
| 215 |
+
${{b2}}$
|
| 216 |
+
::::
|
| 217 |
+
|
| 218 |
+
::::{mcqAnswer}
|
| 219 |
+
:isRightAnswer: false
|
| 220 |
+
${{b3}}$
|
| 221 |
+
::::
|
| 222 |
+
|
| 223 |
+
::::{mcqAnswer}
|
| 224 |
+
:isRightAnswer: false
|
| 225 |
+
{fr}`Aucune de ces réponses n'est correcte`{en}`None of these answers are correct`
|
| 226 |
+
::::
|
| 227 |
+
|
| 228 |
+
::::{detailedSolution}
|
| 229 |
+
{fr}`La quantité`{en}`The quantity` $q$ {fr}`doit être un entier. Comme`{en}`must be an integer. Since` $\dfrac{ {{fixedCostAff}} }{ {{marginAff}} } \approx {{qApproxAff}}$, {fr}`on arrondit à l'entier supérieur :`{en}`we round up to the next integer:` $q = {{borne}}$.
|
| 230 |
+
|
| 231 |
+
{fr}`La boulangerie doit vendre au moins`{en}`The bakery must sell at least` ${{borne}}$ {fr}`pains par semaine pour ne pas faire de perte.`{en}`loaves per week to avoid making a loss.`
|
| 232 |
+
::::
|
| 233 |
+
|
| 234 |
+
::::{weightDistribution}
|
| 235 |
+
:logic: 20
|
| 236 |
+
:abstraction: 20
|
| 237 |
+
:reasoning: 35
|
| 238 |
+
:calculation: 25
|
| 239 |
+
::::
|
| 240 |
+
:::::
|
| 241 |
+
|
| 242 |
+
`````
|
app/knowledge/fewshots/declinaisons/{fr}`Valeurs absolues élémentaires 2`{en}`Elementary Absolute Values 2`.md
ADDED
|
@@ -0,0 +1,215 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
`````{exercise}
|
| 2 |
+
:id: 73dd633c-70a7-11f1-a8a1-0ed8d3b012a9
|
| 3 |
+
:originalExerciseId: d140e232-68cb-11f1-a8a1-0ed8d3b012a9
|
| 4 |
+
:title: {fr}`Valeurs absolues élémentaires 2`{en}`Elementary Absolute Values 2`
|
| 5 |
+
:modules: fund_of_math_I_ESCP
|
| 6 |
+
:recommendedExecutionTime: 20
|
| 7 |
+
:level: Elementary
|
| 8 |
+
:chap: chap_equations_Inequalities_absoluteValue_ESCP
|
| 9 |
+
:involvedConcepts: Abs_Val
|
| 10 |
+
:originalSource: ESCP
|
| 11 |
+
:visibility: All
|
| 12 |
+
:variations:
|
| 13 |
+
:comment:
|
| 14 |
+
|
| 15 |
+
```{python}
|
| 16 |
+
|
| 17 |
+
import random as rd
|
| 18 |
+
from sympy import *
|
| 19 |
+
from pyxiscience.Mes_fctions_generalistes_bis import pxs_config
|
| 20 |
+
from pyxiscience.Classes_Extensions import pxs_Interval
|
| 21 |
+
from pyxiscience.Mes_fctions_d_analyse import pxsl_pow
|
| 22 |
+
from pyxiscience.Mes_fctions_generalistes_bis import pxsl_latex_avec_formatage
|
| 23 |
+
from pyxiscience.Mes_fctions_probabilistes_bis import pxsl_res_num
|
| 24 |
+
|
| 25 |
+
from pyxiscience.Mes_fctions_generalistes_bis import pxs_config
|
| 26 |
+
|
| 27 |
+
config_standard = pxs_config()
|
| 28 |
+
|
| 29 |
+
n, x, u = symbols('n x u')
|
| 30 |
+
|
| 31 |
+
e1 = rd.randint(-10,-1)
|
| 32 |
+
|
| 33 |
+
e2 = rd.randint(1,11)
|
| 34 |
+
|
| 35 |
+
e31, e32 = rd.randint(1,11), rd.randint(-10,-1)
|
| 36 |
+
e3 = Add(e31, e32, evaluate = False)
|
| 37 |
+
e3_eval = Add(e31, e32, evaluate = True)
|
| 38 |
+
|
| 39 |
+
e41 = rd.randint(1,11)
|
| 40 |
+
e42 = rd.randint(-10,-1)
|
| 41 |
+
e43 = rd.randint(2, 11)
|
| 42 |
+
e4 = Mul(Rational(1,e43), Add(e41, e42, evaluate = False), evaluate = False)
|
| 43 |
+
e4_eval = Mul(Rational(1,e43), Add(e41, e42, evaluate = True), evaluate = True)
|
| 44 |
+
|
| 45 |
+
e51 = rd.choice([3, 5, 7])
|
| 46 |
+
e52 = rd.choice([-2, -4, -8])
|
| 47 |
+
e5 = Mul(e51, Rational(e52, e51), evaluate = False)
|
| 48 |
+
e5_eval = Mul(e51, Rational(e52, e51), evaluate = True)
|
| 49 |
+
|
| 50 |
+
e611, e612 = rd.randint(1,11), rd.randint(-10,-1)
|
| 51 |
+
e61 = Add(e611, e612, evaluate = False)
|
| 52 |
+
e621, e622 = rd.randint(1,11), rd.randint(-10,-1)
|
| 53 |
+
e62 = Add(e621, e622, evaluate = False)
|
| 54 |
+
e61_eval = Add(e611, e612, evaluate = True)
|
| 55 |
+
e62_eval = Add(e621, e622, evaluate = True)
|
| 56 |
+
|
| 57 |
+
e7 = rd.randint(1,11)
|
| 58 |
+
|
| 59 |
+
e8 = rd.randint(1,11)
|
| 60 |
+
|
| 61 |
+
a9 = rd.choice([2, 3, 5])
|
| 62 |
+
if a9 == 2:
|
| 63 |
+
b9 = rd.choice([5, 7, 10])
|
| 64 |
+
elif a9 == 3:
|
| 65 |
+
b9 = rd.choice([10, 11, 13])
|
| 66 |
+
else:
|
| 67 |
+
b9 = rd.choice([26, 29, 31])
|
| 68 |
+
|
| 69 |
+
a9, b9 = Min(a9, b9), Max(a9, b9)
|
| 70 |
+
e9 = a9 - sqrt(b9)
|
| 71 |
+
|
| 72 |
+
a10, b10 = Max(a9, b9), Min(a9, b9)
|
| 73 |
+
e10 = sqrt(b10) - a10
|
| 74 |
+
|
| 75 |
+
```
|
| 76 |
+
|
| 77 |
+
{en}`Evaluate the absolute value expressions:`{fr}`Evaluer les valeurs abolues suivantes :`
|
| 78 |
+
|
| 79 |
+
|
| 80 |
+
|
| 81 |
+
:::::{question}
|
| 82 |
+
:questionType: FGQ
|
| 83 |
+
:questionId: 5
|
| 84 |
+
:questionIndex: 5
|
| 85 |
+
:solution: [["ord","${{ latex(Abs(e61_eval) - Abs(e62_eval)) }}$"],["0"]]
|
| 86 |
+
|
| 87 |
+
::::{questionStatement}
|
| 88 |
+
\begin{equation*}
|
| 89 |
+
\Big|{{ latex(e61) }}\Big| - \Big|{{ latex(e62) }}\Big|
|
| 90 |
+
\end{equation*}
|
| 91 |
+
|
| 92 |
+
{fr}`Cette expression vaut :` {en}`This expression equals:` {input}`||120`
|
| 93 |
+
::::
|
| 94 |
+
|
| 95 |
+
::::{questionHint}
|
| 96 |
+
{fr}`Calculez chaque valeur absolue séparément avant de soustraire.`{en}`Compute each absolute value separately before subtracting.`
|
| 97 |
+
::::
|
| 98 |
+
|
| 99 |
+
::::{displayedSolution}
|
| 100 |
+
${{ latex(Abs(e61_eval) - Abs(e62_eval)) }}$
|
| 101 |
+
::::
|
| 102 |
+
|
| 103 |
+
::::{detailedSolution}
|
| 104 |
+
{fr}`On calcule chaque valeur absolue séparément :`{en}`Compute each absolute value separately:`
|
| 105 |
+
|
| 106 |
+
\begin{equation*}
|
| 107 |
+
|{{ latex(e61) }}| &= |{{ e61_eval }}| \\[6pt]
|
| 108 |
+
&= {{ Abs(e61_eval) }}
|
| 109 |
+
\end{equation*}
|
| 110 |
+
|
| 111 |
+
{fr}`et`{en}`and`
|
| 112 |
+
|
| 113 |
+
\begin{equation*}
|
| 114 |
+
|{{ latex(e62) }}| &= |{{ e62_eval }}| \\[6pt]
|
| 115 |
+
&= {{ Abs(e62_eval) }}.
|
| 116 |
+
\end{equation*}
|
| 117 |
+
|
| 118 |
+
{fr}`La soustraction donne`{en}`Subtracting gives`
|
| 119 |
+
|
| 120 |
+
\begin{equation*}
|
| 121 |
+
|{{ latex(e61) }}| - |{{ latex(e62) }}| &= {{ Abs(e61_eval) }} - {{ Abs(e62_eval) }} \\[6pt]
|
| 122 |
+
&={{ latex(Abs(e61_eval) - Abs(e62_eval)) }}.
|
| 123 |
+
\end{equation*}
|
| 124 |
+
::::
|
| 125 |
+
|
| 126 |
+
::::{weightDistribution}
|
| 127 |
+
:reasoning: 25
|
| 128 |
+
:logic: 25
|
| 129 |
+
:abstraction: 15
|
| 130 |
+
:calculation: 35
|
| 131 |
+
::::
|
| 132 |
+
:::::
|
| 133 |
+
|
| 134 |
+
:::::{question}
|
| 135 |
+
:questionType: FGQ
|
| 136 |
+
:questionId: 8
|
| 137 |
+
:questionIndex: 8
|
| 138 |
+
:solution: [["ord","${{ latex(Abs(e9)) }}$"],["0"]]
|
| 139 |
+
|
| 140 |
+
::::{questionStatement}
|
| 141 |
+
\begin{equation*}
|
| 142 |
+
|{{ latex(e9) }}|
|
| 143 |
+
\end{equation*}
|
| 144 |
+
|
| 145 |
+
{fr}`Cette expression vaut :` {en}`This expression equals:` {input}`||120`
|
| 146 |
+
::::
|
| 147 |
+
|
| 148 |
+
::::{questionHint}
|
| 149 |
+
{fr}`Comparez l'entier avec $\sqrt{b}$ pour déterminer le signe de l'expression à l'intérieur de la valeur absolue.`{en}`Compare the integer with $\sqrt{b}$ to decide the sign of the expression inside the absolute value.`
|
| 150 |
+
::::
|
| 151 |
+
|
| 152 |
+
::::{displayedSolution}
|
| 153 |
+
${{ latex(Abs(e9)) }}$
|
| 154 |
+
::::
|
| 155 |
+
|
| 156 |
+
::::{detailedSolution}
|
| 157 |
+
{fr}`L'expression à l'intérieur de la valeur absolue est`{en}`The expression inside the absolute value is` ${{ latex(e9) }}$.\
|
| 158 |
+
{fr}`Puisque`{en}`Since` ${{ a9 }} < \sqrt{{{ b9 }}}$, {fr}`la quantité`{en}`the quantity` ${{ latex(e9) }}$ {fr}`est négative.`{en}`is negative.`\
|
| 159 |
+
{fr}`Par conséquent`{en}`Therefore`
|
| 160 |
+
|
| 161 |
+
\begin{equation*}
|
| 162 |
+
|{{ latex(e9) }}| &= -\big({{ latex(e9) }}\big) \\[6pt]
|
| 163 |
+
&= {{ latex(Abs(e9)) }}.
|
| 164 |
+
\end{equation*}
|
| 165 |
+
::::
|
| 166 |
+
|
| 167 |
+
::::{weightDistribution}
|
| 168 |
+
:reasoning: 25
|
| 169 |
+
:logic: 25
|
| 170 |
+
:abstraction: 25
|
| 171 |
+
:calculation: 25
|
| 172 |
+
::::
|
| 173 |
+
:::::
|
| 174 |
+
|
| 175 |
+
:::::{question}
|
| 176 |
+
:questionType: FGQ
|
| 177 |
+
:questionId: 9
|
| 178 |
+
:questionIndex: 9
|
| 179 |
+
:solution: [["ord","${{ latex(Abs(e10)) }}$"],["0"]]
|
| 180 |
+
|
| 181 |
+
::::{questionStatement}
|
| 182 |
+
\begin{equation*}
|
| 183 |
+
|{{ latex(e10) }}|
|
| 184 |
+
\end{equation*}
|
| 185 |
+
|
| 186 |
+
{fr}`Cette expression vaut :` {en}`This expression equals:` {input}`||120`
|
| 187 |
+
::::
|
| 188 |
+
|
| 189 |
+
::::{questionHint}
|
| 190 |
+
{fr}`Déterminez si $\sqrt{b}$ est plus petit ou plus grand que l'entier devant lui.`{en}`Determine whether $\sqrt{b}$ is smaller or larger than the integer in front of it.`
|
| 191 |
+
::::
|
| 192 |
+
|
| 193 |
+
::::{displayedSolution}
|
| 194 |
+
${{ latex(Abs(e10)) }}$
|
| 195 |
+
::::
|
| 196 |
+
|
| 197 |
+
::::{detailedSolution}
|
| 198 |
+
{fr}`À l'intérieur de la valeur absolue on a`{en}`Inside the absolute value one has` ${{ latex(e10) }}$.\
|
| 199 |
+
{fr}`Puisque`{en}`Because` ${{ a10 }} > \sqrt{{{ b10 }}}$, {fr}`l'expression`{en}`the expression` ${{ latex(e10) }}$ {fr}`est négative.`{en}`is negative.`\
|
| 200 |
+
{fr}`En conséquence`{en}`Consequently`
|
| 201 |
+
|
| 202 |
+
\begin{equation*}
|
| 203 |
+
|{{ latex(e10) }}| &= -\big({{ latex(e10) }}\big) \\[6pt]
|
| 204 |
+
&= {{ latex(Abs(e10)) }}.
|
| 205 |
+
\end{equation*}
|
| 206 |
+
::::
|
| 207 |
+
|
| 208 |
+
::::{weightDistribution}
|
| 209 |
+
:reasoning: 25
|
| 210 |
+
:logic: 25
|
| 211 |
+
:abstraction: 25
|
| 212 |
+
:calculation: 25
|
| 213 |
+
::::
|
| 214 |
+
:::::
|
| 215 |
+
`````
|
app/knowledge/fewshots/declinaisons/{fr}`Valeurs absolues élémentaires 3`{en}`Elementary Absolute Values 3`.md
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
`````{exercise}
|
| 2 |
+
:id: 9af8828d-70a7-11f1-a8a1-0ed8d3b012a9
|
| 3 |
+
:originalExerciseId: 2ce83c2b-68cc-11f1-a8a1-0ed8d3b012a9
|
| 4 |
+
:title: {fr}`Valeurs absolues élémentaires 3`{en}`Elementary Absolute Values 3`
|
| 5 |
+
:modules: fund_of_math_I_ESCP
|
| 6 |
+
:recommendedExecutionTime: 5
|
| 7 |
+
:level: Elementary
|
| 8 |
+
:chap: chap_equations_Inequalities_absoluteValue_ESCP
|
| 9 |
+
:involvedConcepts: Abs_Val
|
| 10 |
+
:originalSource: ESCP
|
| 11 |
+
:visibility: All
|
| 12 |
+
:variations:
|
| 13 |
+
:comment:
|
| 14 |
+
|
| 15 |
+
```{python}
|
| 16 |
+
|
| 17 |
+
import random as rd
|
| 18 |
+
from sympy import *
|
| 19 |
+
from pyxiscience.Mes_fctions_generalistes_bis import pxs_config
|
| 20 |
+
from pyxiscience.Classes_Extensions import pxs_Interval
|
| 21 |
+
from pyxiscience.Mes_fctions_d_analyse import pxsl_pow
|
| 22 |
+
from pyxiscience.Mes_fctions_generalistes_bis import pxsl_latex_avec_formatage
|
| 23 |
+
from pyxiscience.Mes_fctions_probabilistes_bis import pxsl_res_num
|
| 24 |
+
|
| 25 |
+
from pyxiscience.Mes_fctions_generalistes_bis import pxs_config
|
| 26 |
+
|
| 27 |
+
config_standard = pxs_config()
|
| 28 |
+
|
| 29 |
+
n, x, u = symbols('n x u')
|
| 30 |
+
|
| 31 |
+
e1 = rd.randint(-10,-1)
|
| 32 |
+
|
| 33 |
+
e2 = rd.randint(1,11)
|
| 34 |
+
|
| 35 |
+
e31, e32 = rd.randint(1,11), rd.randint(-10,-1)
|
| 36 |
+
e3 = Add(e31, e32, evaluate = False)
|
| 37 |
+
e3_eval = Add(e31, e32, evaluate = True)
|
| 38 |
+
|
| 39 |
+
e41 = rd.randint(1,11)
|
| 40 |
+
e42 = rd.randint(-10,-1)
|
| 41 |
+
e43 = rd.randint(2, 11)
|
| 42 |
+
e4 = Mul(Rational(1,e43), Add(e41, e42, evaluate = False), evaluate = False)
|
| 43 |
+
e4_eval = Mul(Rational(1,e43), Add(e41, e42, evaluate = True), evaluate = True)
|
| 44 |
+
|
| 45 |
+
e51 = rd.choice([3, 5, 7])
|
| 46 |
+
e52 = rd.choice([-2, -4, -8])
|
| 47 |
+
e5 = Mul(e51, Rational(e52, e51), evaluate = False)
|
| 48 |
+
e5_eval = Mul(e51, Rational(e52, e51), evaluate = True)
|
| 49 |
+
|
| 50 |
+
e611, e612 = rd.randint(1,11), rd.randint(-10,-1)
|
| 51 |
+
e61 = Add(e611, e612, evaluate = False)
|
| 52 |
+
e621, e622 = rd.randint(1,11), rd.randint(-10,-1)
|
| 53 |
+
e62 = Add(e621, e622, evaluate = False)
|
| 54 |
+
e61_eval = Add(e611, e612, evaluate = True)
|
| 55 |
+
e62_eval = Add(e621, e622, evaluate = True)
|
| 56 |
+
|
| 57 |
+
e7 = rd.randint(1,11)
|
| 58 |
+
|
| 59 |
+
e8 = rd.randint(1,11)
|
| 60 |
+
|
| 61 |
+
a9 = rd.choice([2, 3, 5])
|
| 62 |
+
if a9 == 2:
|
| 63 |
+
b9 = rd.choice([5, 7, 10])
|
| 64 |
+
elif a9 == 3:
|
| 65 |
+
b9 = rd.choice([10, 11, 13])
|
| 66 |
+
else:
|
| 67 |
+
b9 = rd.choice([26, 29, 31])
|
| 68 |
+
|
| 69 |
+
a9, b9 = Min(a9, b9), Max(a9, b9)
|
| 70 |
+
e9 = a9 - sqrt(b9)
|
| 71 |
+
|
| 72 |
+
a10, b10 = Max(a9, b9), Min(a9, b9)
|
| 73 |
+
e10 = sqrt(b10) - a10
|
| 74 |
+
|
| 75 |
+
```
|
| 76 |
+
|
| 77 |
+
{en}`Evaluate the absolute value expressions:`{fr}`Evaluer les valeurs abolues suivantes :`
|
| 78 |
+
|
| 79 |
+
|
| 80 |
+
|
| 81 |
+
:::::{question}
|
| 82 |
+
:questionType: FGQ
|
| 83 |
+
:questionId: 6
|
| 84 |
+
:questionIndex: 6
|
| 85 |
+
:solution: [["ord","${{ -e7 }}$","${{ e7 }}$"],["0","0"]]
|
| 86 |
+
|
| 87 |
+
::::{questionStatement}
|
| 88 |
+
\begin{equation*}
|
| 89 |
+
|x| < {{ e7 }}
|
| 90 |
+
\end{equation*}
|
| 91 |
+
|
| 92 |
+
{fr}`Compléter l'encadrement donnant l'ensemble des solutions :` {en}`Complete the bounds giving the solution set:`\
|
| 93 |
+
\
|
| 94 |
+
{input}`||80` $< x <$ {input}`||80`
|
| 95 |
+
::::
|
| 96 |
+
|
| 97 |
+
::::{questionHint}
|
| 98 |
+
{fr}`$|x|<a$ signifie que $x$ se trouve strictement entre $-a$ et $a$.`{en}`$|x|<a$ means $x$ lies strictly between $-a$ and $a$.`
|
| 99 |
+
::::
|
| 100 |
+
|
| 101 |
+
::::{displayedSolution}
|
| 102 |
+
$-{{ e7 }} < x < {{ e7 }}$
|
| 103 |
+
::::
|
| 104 |
+
|
| 105 |
+
::::{detailedSolution}
|
| 106 |
+
{fr}`L'inéquation`{en}`The inequality` $|x| < {{ e7 }}$ {fr}`signifie que`{en}`states that` $x$ {fr}`se trouve strictement entre`{en}`lies strictly between` $-{{ e7 }}$ {fr}`et`{en}`and` ${{ e7 }}$.\
|
| 107 |
+
{fr}`Ainsi`{en}`Thus`
|
| 108 |
+
|
| 109 |
+
\begin{equation*}
|
| 110 |
+
-{{ e7 }} < x < {{ e7 }}.
|
| 111 |
+
\end{equation*}
|
| 112 |
+
::::
|
| 113 |
+
|
| 114 |
+
::::{weightDistribution}
|
| 115 |
+
:reasoning: 30
|
| 116 |
+
:logic: 30
|
| 117 |
+
:abstraction: 30
|
| 118 |
+
:calculation: 10
|
| 119 |
+
::::
|
| 120 |
+
:::::
|
| 121 |
+
|
| 122 |
+
:::::{question}
|
| 123 |
+
:questionType: FGQ
|
| 124 |
+
:questionId: 7
|
| 125 |
+
:questionIndex: 7
|
| 126 |
+
:solution: [["ord","${{ -e8 }}$","${{ e8 }}$"],["0","0"]]
|
| 127 |
+
|
| 128 |
+
::::{questionStatement}
|
| 129 |
+
\begin{equation*}
|
| 130 |
+
|x| \leq {{ e8 }}
|
| 131 |
+
\end{equation*}
|
| 132 |
+
|
| 133 |
+
{fr}`Compléter l'encadrement donnant l'ensemble des solutions :` {en}`Complete the bounds giving the solution set:`\
|
| 134 |
+
\
|
| 135 |
+
{input}`||80` $\leq x \leq$ {input}`||80`
|
| 136 |
+
::::
|
| 137 |
+
|
| 138 |
+
::::{questionHint}
|
| 139 |
+
{fr}`$|x|\le a$ permet à $x$ d'être égal à $-a$ ou $a$.`{en}`$|x|\le a$ allows $x$ to equal $-a$ or $a$.`
|
| 140 |
+
::::
|
| 141 |
+
|
| 142 |
+
::::{displayedSolution}
|
| 143 |
+
$-{{ e8 }} \leq x \leq {{ e8 }}$
|
| 144 |
+
::::
|
| 145 |
+
|
| 146 |
+
::::{detailedSolution}
|
| 147 |
+
{fr}`L'inéquation large`{en}`The non-strict inequality` $|x| \leq {{ e8 }}$ {fr}`inclut les bornes, donc`{en}`includes the endpoints, hence`
|
| 148 |
+
|
| 149 |
+
\begin{equation*}
|
| 150 |
+
-{{ e8 }} \leq x \leq {{ e8 }}.
|
| 151 |
+
\end{equation*}
|
| 152 |
+
::::
|
| 153 |
+
|
| 154 |
+
::::{weightDistribution}
|
| 155 |
+
:reasoning: 30
|
| 156 |
+
:logic: 30
|
| 157 |
+
:abstraction: 30
|
| 158 |
+
:calculation: 10
|
| 159 |
+
::::
|
| 160 |
+
:::::
|
| 161 |
+
|
| 162 |
+
|
| 163 |
+
`````
|
app/knowledge/fewshots/declinaisons/{fr}`Valeurs absolues élémentaires`{en}`Elementary Absolute Values`.md
ADDED
|
@@ -0,0 +1,283 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
`````{exercise}
|
| 2 |
+
:id: c84654fa-70a6-11f1-a8a1-0ed8d3b012a9
|
| 3 |
+
:originalExerciseId: 834b79ee-fc00-47e7-921b-83e4524d24b0
|
| 4 |
+
:title: {fr}`Valeurs absolues élémentaires`{en}`Elementary Absolute Values`
|
| 5 |
+
:modules: fund_of_math_I_ESCP
|
| 6 |
+
:recommendedExecutionTime: 20
|
| 7 |
+
:level: Elementary
|
| 8 |
+
:chap: chap_equations_Inequalities_absoluteValue_ESCP
|
| 9 |
+
:involvedConcepts: Solving_inequalities, Abs_Val
|
| 10 |
+
:originalSource: ESCP
|
| 11 |
+
:visibility: All
|
| 12 |
+
:variations:
|
| 13 |
+
:comment:
|
| 14 |
+
|
| 15 |
+
```{python}
|
| 16 |
+
|
| 17 |
+
import random as rd
|
| 18 |
+
from sympy import *
|
| 19 |
+
from pyxiscience.Mes_fctions_generalistes_bis import pxs_config
|
| 20 |
+
from pyxiscience.Classes_Extensions import pxs_Interval
|
| 21 |
+
from pyxiscience.Mes_fctions_d_analyse import pxsl_pow
|
| 22 |
+
from pyxiscience.Mes_fctions_generalistes_bis import pxsl_latex_avec_formatage
|
| 23 |
+
from pyxiscience.Mes_fctions_probabilistes_bis import pxsl_res_num
|
| 24 |
+
|
| 25 |
+
from pyxiscience.Mes_fctions_generalistes_bis import pxs_config
|
| 26 |
+
|
| 27 |
+
config_standard = pxs_config()
|
| 28 |
+
|
| 29 |
+
n, x, u = symbols('n x u')
|
| 30 |
+
|
| 31 |
+
e1 = rd.randint(-10,-1)
|
| 32 |
+
|
| 33 |
+
e2 = rd.randint(1,11)
|
| 34 |
+
|
| 35 |
+
e31, e32 = rd.randint(1,11), rd.randint(-10,-1)
|
| 36 |
+
e3 = Add(e31, e32, evaluate = False)
|
| 37 |
+
e3_eval = Add(e31, e32, evaluate = True)
|
| 38 |
+
|
| 39 |
+
e41 = rd.randint(1,11)
|
| 40 |
+
e42 = rd.randint(-10,-1)
|
| 41 |
+
e43 = rd.randint(2, 11)
|
| 42 |
+
e4 = Mul(Rational(1,e43), Add(e41, e42, evaluate = False), evaluate = False)
|
| 43 |
+
e4_eval = Mul(Rational(1,e43), Add(e41, e42, evaluate = True), evaluate = True)
|
| 44 |
+
|
| 45 |
+
e51 = rd.choice([3, 5, 7])
|
| 46 |
+
e52 = rd.choice([-2, -4, -8])
|
| 47 |
+
e5 = Mul(e51, Rational(e52, e51), evaluate = False)
|
| 48 |
+
e5_eval = Mul(e51, Rational(e52, e51), evaluate = True)
|
| 49 |
+
|
| 50 |
+
e611, e612 = rd.randint(1,11), rd.randint(-10,-1)
|
| 51 |
+
e61 = Add(e611, e612, evaluate = False)
|
| 52 |
+
e621, e622 = rd.randint(1,11), rd.randint(-10,-1)
|
| 53 |
+
e62 = Add(e621, e622, evaluate = False)
|
| 54 |
+
e61_eval = Add(e611, e612, evaluate = True)
|
| 55 |
+
e62_eval = Add(e621, e622, evaluate = True)
|
| 56 |
+
|
| 57 |
+
e7 = rd.randint(1,11)
|
| 58 |
+
|
| 59 |
+
e8 = rd.randint(1,11)
|
| 60 |
+
|
| 61 |
+
a9 = rd.choice([2, 3, 5])
|
| 62 |
+
if a9 == 2:
|
| 63 |
+
b9 = rd.choice([5, 7, 10])
|
| 64 |
+
elif a9 == 3:
|
| 65 |
+
b9 = rd.choice([10, 11, 13])
|
| 66 |
+
else:
|
| 67 |
+
b9 = rd.choice([26, 29, 31])
|
| 68 |
+
|
| 69 |
+
a9, b9 = Min(a9, b9), Max(a9, b9)
|
| 70 |
+
e9 = a9 - sqrt(b9)
|
| 71 |
+
|
| 72 |
+
a10, b10 = Max(a9, b9), Min(a9, b9)
|
| 73 |
+
e10 = sqrt(b10) - a10
|
| 74 |
+
|
| 75 |
+
```
|
| 76 |
+
|
| 77 |
+
{en}`Evaluate the absolute value expressions:`{fr}`Evaluer les valeurs abolues suivantes :`
|
| 78 |
+
|
| 79 |
+
:::::{question}
|
| 80 |
+
:questionType: FGQ
|
| 81 |
+
:solution: [["ord","${{ Abs(e1) }}$"],["0"]]
|
| 82 |
+
|
| 83 |
+
::::{questionStatement}
|
| 84 |
+
\begin{equation*}
|
| 85 |
+
|{{ e1 }}|
|
| 86 |
+
\end{equation*}
|
| 87 |
+
|
| 88 |
+
{fr}`Cette expression vaut :` {en}`This expression equals:` {input}`||120`
|
| 89 |
+
::::
|
| 90 |
+
|
| 91 |
+
::::{questionHint}
|
| 92 |
+
{fr}`Rappel : la valeur absolue d'un nombre négatif est son opposé.`{en}`Remember: the absolute value of a negative number is its opposite.`
|
| 93 |
+
::::
|
| 94 |
+
|
| 95 |
+
::::{displayedSolution}
|
| 96 |
+
${{ Abs(e1) }}$
|
| 97 |
+
::::
|
| 98 |
+
|
| 99 |
+
::::{detailedSolution}
|
| 100 |
+
{fr}`La valeur absolue d'un nombre réel est sa distance à zéro sur la droite réelle, elle est donc toujours positive ou nulle.`{en}`The absolute value of a real number is its distance from zero on the real line, hence it is always non–negative.`\
|
| 101 |
+
{fr}`Puisque`{en}`Since` ${{e1}}$ {fr}`est négatif, on change son signe :`{en}`is negative, one changes its sign:`
|
| 102 |
+
|
| 103 |
+
\begin{equation*}
|
| 104 |
+
|{{e1}}| &= -({{e1}}) \\[6pt]
|
| 105 |
+
&= {{ Abs(e1) }}.
|
| 106 |
+
\end{equation*}
|
| 107 |
+
::::
|
| 108 |
+
|
| 109 |
+
::::{weightDistribution}
|
| 110 |
+
:reasoning: 10
|
| 111 |
+
:logic: 20
|
| 112 |
+
:abstraction: 10
|
| 113 |
+
:calculation: 60
|
| 114 |
+
::::
|
| 115 |
+
:::::
|
| 116 |
+
|
| 117 |
+
:::::{question}
|
| 118 |
+
:questionType: FGQ
|
| 119 |
+
:solution: [["ord","${{ Abs(e2) }}$"],["0"]]
|
| 120 |
+
|
| 121 |
+
::::{questionStatement}
|
| 122 |
+
\begin{equation*}
|
| 123 |
+
|{{ e2 }}|
|
| 124 |
+
\end{equation*}
|
| 125 |
+
|
| 126 |
+
{fr}`Cette expression vaut :` {en}`This expression equals:` {input}`||120`
|
| 127 |
+
::::
|
| 128 |
+
|
| 129 |
+
::::{questionHint}
|
| 130 |
+
{fr}`La valeur absolue d'un nombre positif est le nombre lui-même.`{en}`The absolute value of a positive number is the number itself.`
|
| 131 |
+
::::
|
| 132 |
+
|
| 133 |
+
::::{displayedSolution}
|
| 134 |
+
${{ Abs(e2) }}$
|
| 135 |
+
::::
|
| 136 |
+
|
| 137 |
+
::::{detailedSolution}
|
| 138 |
+
{fr}`Le nombre`{en}`The number` ${{e2}}$ {fr}`est positif, donc sa valeur absolue est égale à lui-même :`{en}`is positive, therefore its absolute value equals itself:`
|
| 139 |
+
|
| 140 |
+
\begin{equation*}
|
| 141 |
+
|{{e2}}| = {{ Abs(e2) }}.
|
| 142 |
+
\end{equation*}
|
| 143 |
+
::::
|
| 144 |
+
|
| 145 |
+
::::{weightDistribution}
|
| 146 |
+
:reasoning: 10
|
| 147 |
+
:logic: 20
|
| 148 |
+
:abstraction: 10
|
| 149 |
+
:calculation: 60
|
| 150 |
+
::::
|
| 151 |
+
:::::
|
| 152 |
+
|
| 153 |
+
:::::{question}
|
| 154 |
+
:questionType: FGQ
|
| 155 |
+
:solution: [["ord","${{ Abs(e3) }}$"],["0"]]
|
| 156 |
+
|
| 157 |
+
::::{questionStatement}
|
| 158 |
+
\begin{equation*}
|
| 159 |
+
|{{ e3 }}|
|
| 160 |
+
\end{equation*}
|
| 161 |
+
|
| 162 |
+
{fr}`Cette expression vaut :` {en}`This expression equals:` {input}`||120`
|
| 163 |
+
::::
|
| 164 |
+
|
| 165 |
+
::::{questionHint}
|
| 166 |
+
{fr}`Additionnez d'abord les deux entiers, puis prenez la valeur absolue du résultat.`{en}`First add the two integers, then take the absolute value of the result.`
|
| 167 |
+
::::
|
| 168 |
+
|
| 169 |
+
::::{displayedSolution}
|
| 170 |
+
$\ds {{ Abs(e3) }}$
|
| 171 |
+
::::
|
| 172 |
+
|
| 173 |
+
::::{detailedSolution}
|
| 174 |
+
{fr}`On calcule d'abord la somme à l'intérieur de la valeur absolue :`{en}`First one computes the sum inside the absolute value:`
|
| 175 |
+
|
| 176 |
+
\begin{equation*}
|
| 177 |
+
{{ e3 }} = {{ e3_eval }}.
|
| 178 |
+
\end{equation*}
|
| 179 |
+
|
| 180 |
+
{fr}`La valeur absolue de ce résultat est donc`{en}`The absolute value of this result is therefore`
|
| 181 |
+
|
| 182 |
+
\begin{equation*}
|
| 183 |
+
\left|{{ e3_eval }}\right| = {{ Abs(e3) }}.
|
| 184 |
+
\end{equation*}
|
| 185 |
+
::::
|
| 186 |
+
|
| 187 |
+
::::{weightDistribution}
|
| 188 |
+
:reasoning: 15
|
| 189 |
+
:logic: 15
|
| 190 |
+
:abstraction: 15
|
| 191 |
+
:calculation: 55
|
| 192 |
+
::::
|
| 193 |
+
:::::
|
| 194 |
+
|
| 195 |
+
:::::{question}
|
| 196 |
+
:questionType: FGQ
|
| 197 |
+
:solution: [["ord","${{ latex(Abs(e4)) }}$"],["0"]]
|
| 198 |
+
|
| 199 |
+
::::{questionStatement}
|
| 200 |
+
\begin{equation*}
|
| 201 |
+
\left|{{ latex(e4) }}\right|
|
| 202 |
+
\end{equation*}
|
| 203 |
+
|
| 204 |
+
{fr}`Cette expression vaut :` {en}`This expression equals:` {input}`||120`
|
| 205 |
+
::::
|
| 206 |
+
|
| 207 |
+
::::{questionHint}
|
| 208 |
+
{fr}`Calculez la somme entre parenthèses avant de diviser par le dénominateur, puis appliquez la valeur absolue.`{en}`Compute the sum in parentheses before dividing by the denominator, then apply the absolute value.`
|
| 209 |
+
::::
|
| 210 |
+
|
| 211 |
+
::::{displayedSolution}
|
| 212 |
+
$\ds {{ latex(Abs(e4)) }}$
|
| 213 |
+
::::
|
| 214 |
+
|
| 215 |
+
::::{detailedSolution}
|
| 216 |
+
{fr}`On commence par évaluer l'expression à l'intérieur de la valeur absolue :`{en}`Begin by evaluating the expression inside the absolute value:`
|
| 217 |
+
|
| 218 |
+
\begin{equation*}
|
| 219 |
+
\frac{1}{{{ e43 }}}\big({{ e42 }}+{{ e41 }}\big) &= \frac{1}{{{ e43 }}}\cdot {{ pxsl_pow(e41+e42) }} \\[10pt]
|
| 220 |
+
&= {{ latex(e4_eval) }}.
|
| 221 |
+
\end{equation*}
|
| 222 |
+
|
| 223 |
+
{fr}`La valeur absolue est alors`{en}`The absolute value is then`
|
| 224 |
+
|
| 225 |
+
\begin{equation*}
|
| 226 |
+
\Big|\frac{1}{{{ e43 }}}\big({{ e42 }}+{{ e41 }}\big)\Big| &= \left|{{ latex(e4_eval) }}\right| \\[10pt]
|
| 227 |
+
&= {{ latex(Abs(e4)) }}.
|
| 228 |
+
\end{equation*}
|
| 229 |
+
::::
|
| 230 |
+
|
| 231 |
+
::::{weightDistribution}
|
| 232 |
+
:reasoning: 20
|
| 233 |
+
:logic: 20
|
| 234 |
+
:abstraction: 20
|
| 235 |
+
:calculation: 40
|
| 236 |
+
::::
|
| 237 |
+
:::::
|
| 238 |
+
|
| 239 |
+
:::::{question}
|
| 240 |
+
:questionType: FGQ
|
| 241 |
+
:solution: [["ord","${{ latex(Abs(e5)) }}$"],["0"]]
|
| 242 |
+
|
| 243 |
+
::::{questionStatement}
|
| 244 |
+
\begin{equation*}
|
| 245 |
+
\Big|{{ latex(e5) }}\Big|
|
| 246 |
+
\end{equation*}
|
| 247 |
+
|
| 248 |
+
{fr}`Cette expression vaut :` {en}`This expression equals:` {input}`||120`
|
| 249 |
+
::::
|
| 250 |
+
|
| 251 |
+
::::{questionHint}
|
| 252 |
+
{fr}`Le facteur commun au numérateur et au dénominateur se simplifie complètement.`{en}`The common factor in numerator and denominator cancels out completely.`
|
| 253 |
+
::::
|
| 254 |
+
|
| 255 |
+
::::{displayedSolution}
|
| 256 |
+
${{ latex(Abs(e5)) }}$
|
| 257 |
+
::::
|
| 258 |
+
|
| 259 |
+
::::{detailedSolution}
|
| 260 |
+
{fr}`La fraction se simplifie d'abord :`{en}`The fraction simplifies first:`
|
| 261 |
+
|
| 262 |
+
\begin{equation*}
|
| 263 |
+
\frac{{{ e51 }}\cdot {{ pxsl_pow(e52) }}}{{{ e51 }}} = {{ e5_eval }}.
|
| 264 |
+
\end{equation*}
|
| 265 |
+
|
| 266 |
+
{fr}`La valeur absolue devient donc`{en}`Hence the absolute value becomes`
|
| 267 |
+
|
| 268 |
+
\begin{equation*}
|
| 269 |
+
\Big|\frac{{{ e51 }}\cdot {{ pxsl_pow(e52) }}}{{{ e51 }}}\Big| &= \left|{{ e5_eval }}\right| \\[10pt]
|
| 270 |
+
&= {{ latex(Abs(e5)) }}.
|
| 271 |
+
\end{equation*}
|
| 272 |
+
::::
|
| 273 |
+
|
| 274 |
+
::::{weightDistribution}
|
| 275 |
+
:reasoning: 20
|
| 276 |
+
:logic: 20
|
| 277 |
+
:abstraction: 20
|
| 278 |
+
:calculation: 40
|
| 279 |
+
::::
|
| 280 |
+
:::::
|
| 281 |
+
|
| 282 |
+
|
| 283 |
+
`````
|
app/knowledge/fewshots/declinaisons/{fr}`Zone de rentabilité symétrique autour de l'optimum`{en}`Symmetric profitability zone around the optimum`.md
ADDED
|
@@ -0,0 +1,236 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
`````{exercise}
|
| 2 |
+
:title: {fr}`Zone de rentabilité symétrique autour de l'optimum`{en}`Symmetric profitability zone around the optimum`
|
| 3 |
+
:modules:
|
| 4 |
+
:recommendedExecutionTime: 15
|
| 5 |
+
:level: Elementary
|
| 6 |
+
:chap: chap_realFunctions_Graphs_graphSymmetries_ESCP
|
| 7 |
+
:involvedConcepts:
|
| 8 |
+
:originalSource:
|
| 9 |
+
:visibility: All
|
| 10 |
+
:variations:
|
| 11 |
+
:comment: Appliqué (économie & gestion) : axe de symétrie et maximum d'une parabole de profit, seuils de rentabilité par factorisation, et symétrie des seuils par rapport à l'optimum (sans discriminant ni Viète).
|
| 12 |
+
:id: df8f8e47-7480-11f1-a8a1-0ed8d3b012a9
|
| 13 |
+
:originalExerciseId: e1c9656e-6409-11f1-a8a1-0ed8d3b012a9
|
| 14 |
+
|
| 15 |
+
````{python}
|
| 16 |
+
import random as rd
|
| 17 |
+
from sympy import symbols, latex
|
| 18 |
+
from pyxiscience.Mes_fctions_generalistes_bis import pxs_config, pxsl_latex_coefficient as lc
|
| 19 |
+
|
| 20 |
+
config_standard = pxs_config()
|
| 21 |
+
q = symbols('q')
|
| 22 |
+
|
| 23 |
+
# qStar entier, q1 < qStar < q2 symétriques, a<0, racines q1,q2
|
| 24 |
+
# coefficient négatif
|
| 25 |
+
a = rd.choice([-5, -4, -3, -2, -1])
|
| 26 |
+
|
| 27 |
+
# choisir q_star directement compatible avec 20 <= b <= 60
|
| 28 |
+
# b = -2 a q_star
|
| 29 |
+
# donc q_star = b / (-2a)
|
| 30 |
+
|
| 31 |
+
possible_qstars = []
|
| 32 |
+
|
| 33 |
+
for q in range(3, 16):
|
| 34 |
+
b = -2 * a * q
|
| 35 |
+
if 20 <= b <= 60:
|
| 36 |
+
possible_qstars.append(q)
|
| 37 |
+
|
| 38 |
+
qStar = rd.choice(possible_qstars)
|
| 39 |
+
|
| 40 |
+
# construction des racines symétriques
|
| 41 |
+
q1 = rd.randint(1, qStar - 2)
|
| 42 |
+
q2 = 2 * qStar - q1
|
| 43 |
+
|
| 44 |
+
# coefficients
|
| 45 |
+
b = -2 * a * qStar
|
| 46 |
+
c = a * q1 * q2
|
| 47 |
+
|
| 48 |
+
Pi = a*q**2 + b*q + c
|
| 49 |
+
profitMax = a * qStar**2 + b * qStar + c
|
| 50 |
+
somme_racines = q1 + q2
|
| 51 |
+
produit_racines = q1 * q2
|
| 52 |
+
factAff = f"(q-{q1})(q-{q2})"
|
| 53 |
+
distance = qStar - q1
|
| 54 |
+
|
| 55 |
+
# Rendus
|
| 56 |
+
aLcAff = lc(a)
|
| 57 |
+
aMulAff = lc(a, ones=True)
|
| 58 |
+
bSignAff = lc(b, sign=True)
|
| 59 |
+
cSignAff = lc(c, ones=True, sign=True)
|
| 60 |
+
twoAAff = latex(2*a)
|
| 61 |
+
aQStarSqAff = latex(a*qStar**2)
|
| 62 |
+
bQStarSignAff = lc(b*qStar, ones=True, sign=True)
|
| 63 |
+
sommeRacines = somme_racines
|
| 64 |
+
produitRacines = produit_racines
|
| 65 |
+
negSommeSignAff = lc(-somme_racines, sign=True)
|
| 66 |
+
produitSignAff = lc(produit_racines, ones=True, sign=True)
|
| 67 |
+
q1PlusQ2 = q1 + q2
|
| 68 |
+
|
| 69 |
+
globals()
|
| 70 |
+
````
|
| 71 |
+
|
| 72 |
+
{fr}`Une entreprise modélise son profit (en milliers d'euros) par`{en}`A company models its profit (in thousands of euros) by` $\Pi(q)={{ aLcAff }}q^2{{ bSignAff }}q{{ cSignAff }}$, $q\geq 0$ {fr}`(quantité en centaines d'unités).`{en}`(quantity in hundreds of units).`
|
| 73 |
+
|
| 74 |
+
:::::{question}
|
| 75 |
+
:questionType: FGQ
|
| 76 |
+
:solution: [["ord","${{ qStar }}$"],["0"]]
|
| 77 |
+
:questionId: 0
|
| 78 |
+
:questionIndex: 0
|
| 79 |
+
|
| 80 |
+
::::{questionStatement}
|
| 81 |
+
{fr}`Déterminer l'axe de symétrie`{en}`Determine the axis of symmetry` $q^*$ {fr}`de la parabole.`{en}`of the parabola.`
|
| 82 |
+
|
| 83 |
+
$q^* =$ {input}`||90`
|
| 84 |
+
::::
|
| 85 |
+
|
| 86 |
+
::::{questionHint}
|
| 87 |
+
{fr}`Appliquer`{en}`Apply` $q^*=-\dfrac{b}{2a}$ {fr}`avec`{en}`with` $a={{ a }}$, $b={{ b }}$.
|
| 88 |
+
::::
|
| 89 |
+
|
| 90 |
+
::::{displayedSolution}
|
| 91 |
+
$q^* = {{ qStar }}$
|
| 92 |
+
::::
|
| 93 |
+
|
| 94 |
+
::::{detailedSolution}
|
| 95 |
+
{fr}`Règle utilisée : axe de symétrie`{en}`Rule used: axis of symmetry` $q^*=-\dfrac{b}{2a}$.
|
| 96 |
+
|
| 97 |
+
\begin{equation*}
|
| 98 |
+
q^* &= -\frac{ {{ b }} }{2\times({{ a }})} \\
|
| 99 |
+
&= -\frac{ {{ b }} }{ {{ twoAAff }} } \\
|
| 100 |
+
&= {{ qStar }}.
|
| 101 |
+
\end{equation*}
|
| 102 |
+
|
| 103 |
+
{fr}`L'axe de symétrie est`{en}`The axis of symmetry is` $q={{ qStar }}$.
|
| 104 |
+
::::
|
| 105 |
+
|
| 106 |
+
::::{weightDistribution}
|
| 107 |
+
:logic: 15
|
| 108 |
+
:abstraction: 25
|
| 109 |
+
:reasoning: 30
|
| 110 |
+
:calculation: 30
|
| 111 |
+
::::
|
| 112 |
+
:::::
|
| 113 |
+
|
| 114 |
+
:::::{question}
|
| 115 |
+
:questionType: FGQ
|
| 116 |
+
:solution: [["ord","${{ profitMax }}$"],["0"]]
|
| 117 |
+
:questionId: 1
|
| 118 |
+
:questionIndex: 1
|
| 119 |
+
|
| 120 |
+
::::{questionStatement}
|
| 121 |
+
{fr}`Calculer le profit maximal`{en}`Calculate the maximum profit` $\Pi(q^*)$.
|
| 122 |
+
|
| 123 |
+
$\Pi(q^*) =$ {input}`||90`
|
| 124 |
+
::::
|
| 125 |
+
|
| 126 |
+
::::{questionHint}
|
| 127 |
+
{fr}`Substituer`{en}`Substitute` $q={{ qStar }}$ {fr}`dans`{en}`in` $\Pi(q)$ ; {fr}`comme`{en}`since` $a<0$, {fr}`c'est un maximum.`{en}`it is a maximum.`
|
| 128 |
+
::::
|
| 129 |
+
|
| 130 |
+
::::{displayedSolution}
|
| 131 |
+
$\Pi(q^*) = {{ profitMax }}$
|
| 132 |
+
::::
|
| 133 |
+
|
| 134 |
+
::::{detailedSolution}
|
| 135 |
+
{fr}`On évalue`{en}`We evaluate` $\Pi$ {fr}`au sommet.`{en}`at the vertex.`
|
| 136 |
+
|
| 137 |
+
\begin{equation*}
|
| 138 |
+
\Pi({{ qStar }}) &= {{ aMulAff }}\times {{ qStar }}^2 {{ bSignAff }}\times {{ qStar }} {{ cSignAff }} \\
|
| 139 |
+
&= {{ aQStarSqAff }} {{ bQStarSignAff }} {{ cSignAff }} \\
|
| 140 |
+
&= {{ profitMax }}.
|
| 141 |
+
\end{equation*}
|
| 142 |
+
|
| 143 |
+
{fr}`Le profit maximal est de`{en}`The maximum profit is` ${{ profitMax }}$ {fr}`milliers d'euros.`{en}`thousands of euros.`
|
| 144 |
+
::::
|
| 145 |
+
|
| 146 |
+
::::{weightDistribution}
|
| 147 |
+
:logic: 15
|
| 148 |
+
:abstraction: 25
|
| 149 |
+
:reasoning: 30
|
| 150 |
+
:calculation: 30
|
| 151 |
+
::::
|
| 152 |
+
:::::
|
| 153 |
+
|
| 154 |
+
:::::{question}
|
| 155 |
+
:questionType: FGQ
|
| 156 |
+
:solution: [["notord","${{ q1 }}$","${{ q2 }}$"],["0","0"]]
|
| 157 |
+
:questionId: 2
|
| 158 |
+
:questionIndex: 2
|
| 159 |
+
|
| 160 |
+
::::{questionStatement}
|
| 161 |
+
{fr}`Déterminer les deux seuils de rentabilité`{en}`Determine the two break-even thresholds` $q_1$ {fr}`et`{en}`and` $q_2$ ($\Pi(q)=0$) {fr}`par factorisation.`{en}`by factorization.`
|
| 162 |
+
|
| 163 |
+
$q_1 =$ {input}`||90` {fr}`et`{en}`and` $q_2 =$ {input}`||90`
|
| 164 |
+
::::
|
| 165 |
+
|
| 166 |
+
::::{questionHint}
|
| 167 |
+
{fr}`Diviser par`{en}`Divide by` ${{ a }}$ {fr}`pour obtenir`{en}`to obtain` $q^2{{ negSommeSignAff }}q{{ produitSignAff }}=0$, {fr}`puis factoriser (deux nombres de produit`{en}`then factor (two numbers with product` ${{ produitRacines }}$ {fr}`et de somme`{en}`and sum` ${{ sommeRacines }}$).
|
| 168 |
+
::::
|
| 169 |
+
|
| 170 |
+
::::{displayedSolution}
|
| 171 |
+
$q_1 = {{ q1 }}$ {fr}`et`{en}`and` $q_2 = {{ q2 }}$
|
| 172 |
+
::::
|
| 173 |
+
|
| 174 |
+
::::{detailedSolution}
|
| 175 |
+
{fr}`On résout`{en}`We solve` $\Pi(q)=0$ {fr}`par factorisation (sans discriminant).`{en}`by factorization (without discriminant).`
|
| 176 |
+
|
| 177 |
+
\begin{equation*}
|
| 178 |
+
{{ aLcAff }}q^2 {{ bSignAff }}q {{ cSignAff }} &= 0 \\
|
| 179 |
+
q^2 {{ negSommeSignAff }}q {{ produitSignAff }} &= 0 \\
|
| 180 |
+
{{ factAff }} &= 0.
|
| 181 |
+
\end{equation*}
|
| 182 |
+
|
| 183 |
+
\begin{equation*}
|
| 184 |
+
q_1 &= {{ q1 }} \quad\text{{fr}`et`{en}`and`}\quad q_2 = {{ q2 }}.
|
| 185 |
+
\end{equation*}
|
| 186 |
+
::::
|
| 187 |
+
|
| 188 |
+
::::{weightDistribution}
|
| 189 |
+
:logic: 20
|
| 190 |
+
:abstraction: 25
|
| 191 |
+
:reasoning: 30
|
| 192 |
+
:calculation: 25
|
| 193 |
+
::::
|
| 194 |
+
:::::
|
| 195 |
+
|
| 196 |
+
:::::{question}
|
| 197 |
+
:questionType: FGQ
|
| 198 |
+
:solution: [["ord","${{ qStar }}$"],["0"]]
|
| 199 |
+
:questionId: 3
|
| 200 |
+
:questionIndex: 3
|
| 201 |
+
|
| 202 |
+
::::{questionStatement}
|
| 203 |
+
{fr}`Montrer que`{en}`Show that` $q_1$ {fr}`et`{en}`and` $q_2$ {fr}`sont symétriques par rapport à`{en}`are symmetric with respect to` $q^*$, {fr}`puis interpréter : que peut-on dire de la zone de rentabilité de l'entreprise ?`{en}`then interpret: what can be said about the company's profitability zone?`
|
| 204 |
+
|
| 205 |
+
$\dfrac{q_1+q_2}{2} =$ {input}`||90`
|
| 206 |
+
::::
|
| 207 |
+
|
| 208 |
+
::::{questionHint}
|
| 209 |
+
{fr}`Calculer la moyenne`{en}`Calculate the average` $\dfrac{q_1+q_2}{2}$ {fr}`et la comparer à`{en}`and compare it to` $q^*$.
|
| 210 |
+
::::
|
| 211 |
+
|
| 212 |
+
::::{displayedSolution}
|
| 213 |
+
$\dfrac{q_1+q_2}{2} = {{ qStar }}$
|
| 214 |
+
::::
|
| 215 |
+
|
| 216 |
+
::::{detailedSolution}
|
| 217 |
+
{fr}`Règle utilisée : l'axe de symétrie d'une parabole passe par le milieu de ses deux zéros.`{en}`Rule used: the axis of symmetry of a parabola passes through the midpoint of its two zeros.`
|
| 218 |
+
|
| 219 |
+
\begin{equation*}
|
| 220 |
+
\frac{q_1 + q_2}{2} &= \frac{ {{ q1 }} + {{ q2 }} }{2} \\
|
| 221 |
+
&= \frac{ {{ q1PlusQ2 }} }{2} \\
|
| 222 |
+
&= {{ qStar }}.
|
| 223 |
+
\end{equation*}
|
| 224 |
+
|
| 225 |
+
{fr}`Les deux seuils sont symétriques par rapport à`{en}`The two thresholds are symmetric with respect to` $q^*={{ qStar }}$. {fr}`L'entreprise est rentable`{en}`The company is profitable` ($\Pi>0$) {fr}`pour`{en}`for` $q$ {fr}`entre`{en}`between` ${{ q1 }}$ {fr}`et`{en}`and` ${{ q2 }}$. {fr}`La zone de rentabilité est symétrique autour de l'optimum : les deux seuils sont à égale distance`{en}`The profitability zone is symmetric around the optimum: the two thresholds are at equal distance` (${{ distance }}$ {fr}`centaines d'unités) de la quantité optimale, donc l'entreprise dispose de la même marge de manœuvre en sous-production ou en surproduction.`{en}`hundreds of units) from the optimal quantity, so the company has the same margin of maneuver in underproduction or overproduction.`
|
| 226 |
+
::::
|
| 227 |
+
|
| 228 |
+
::::{weightDistribution}
|
| 229 |
+
:logic: 30
|
| 230 |
+
:abstraction: 30
|
| 231 |
+
:reasoning: 35
|
| 232 |
+
:calculation: 5
|
| 233 |
+
::::
|
| 234 |
+
:::::
|
| 235 |
+
|
| 236 |
+
`````
|
app/knowledge/fewshots/declinaisons/{fr}`Équations exponentielles et logarithmiques`{en}`Exponential and Logarithmic Equations`.md
ADDED
|
@@ -0,0 +1,302 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
`````{exercise}
|
| 2 |
+
:title: {fr}`Équations exponentielles et logarithmiques`{en}`Exponential and Logarithmic Equations`
|
| 3 |
+
:modules:
|
| 4 |
+
:recommendedExecutionTime: 15
|
| 5 |
+
:level: Elementary
|
| 6 |
+
:chap: chap_expLogFunctions_solvingExpLogEquations_ESCP
|
| 7 |
+
:involvedConcepts: Domain_of_function, solving_equ_with_ln, propers_of_log_and_exp
|
| 8 |
+
:originalSource: % Exercise source
|
| 9 |
+
:visibility: All
|
| 10 |
+
:comment: % Exercise comment (optional)
|
| 11 |
+
:originalExerciseId: b41f6b56-65a4-11f1-a8a1-0ed8d3b012a9
|
| 12 |
+
:id: 91f28cdc-74bf-11f1-a8a1-0ed8d3b012a9
|
| 13 |
+
|
| 14 |
+
```{python}
|
| 15 |
+
import random as rd
|
| 16 |
+
from sympy import Symbol, Rational, ln, exp, latex, simplify
|
| 17 |
+
from pyxiscience.Mes_fctions_generalistes_bis import pxs_config, pxsl_latex_coefficient as lc
|
| 18 |
+
config_standard = pxs_config()
|
| 19 |
+
|
| 20 |
+
# ═══════════════════════════════════════════════════════════════════════════
|
| 21 |
+
# MÉTHODE CONSTRUCTIVE — partir de la réponse
|
| 22 |
+
# ═══════════════════════════════════════════════════════════════════════════
|
| 23 |
+
|
| 24 |
+
# Question 1 : e^x = a1
|
| 25 |
+
# Réponse : x = ln(a1) avec a1 > 0, a1 ≠ 1
|
| 26 |
+
# On tire d'abord la réponse (ln(a1)), puis on fabrique l'énoncé (e^x = a1)
|
| 27 |
+
a1 = rd.choice([i for i in range(2, 11) if i != 1])
|
| 28 |
+
sol1 = ln(a1)
|
| 29 |
+
sol1Aff = latex(sol1, **config_standard)
|
| 30 |
+
a1Aff = latex(a1, **config_standard)
|
| 31 |
+
|
| 32 |
+
# Question 2 : e^(coef_exp·x) = a2
|
| 33 |
+
# Réponse : x = ln(a2)/coef_exp avec a2 > 0, a2 ≠ 1, coef_exp ≥ 2
|
| 34 |
+
# On tire d'abord coef_exp et a2, puis on calcule la réponse
|
| 35 |
+
coefExp = rd.randint(2, 5)
|
| 36 |
+
a2 = rd.choice([i for i in range(2, 11) if i != 1])
|
| 37 |
+
sol2 = ln(a2) / coefExp
|
| 38 |
+
sol2Aff = latex(sol2, **config_standard)
|
| 39 |
+
a2Aff = latex(a2, **config_standard)
|
| 40 |
+
coefExpAff = latex(coefExp, **config_standard)
|
| 41 |
+
|
| 42 |
+
# Affichage de l'exposant dans l'énoncé (2x, 3x, etc.)
|
| 43 |
+
x = Symbol('x')
|
| 44 |
+
expExprAff = latex(coefExp * x, **config_standard)
|
| 45 |
+
|
| 46 |
+
globals()
|
| 47 |
+
```
|
| 48 |
+
|
| 49 |
+
{fr}`Résoudre dans`{en}`Solve in` $\mathbb{R}$ {fr}`les équations suivantes :`{en}`the following equations:`
|
| 50 |
+
|
| 51 |
+
:::::{question}
|
| 52 |
+
:questionType: FGQ
|
| 53 |
+
:solution: [["ord","${{ sol1Aff }}$"],["0"]]
|
| 54 |
+
|
| 55 |
+
::::{questionStatement}
|
| 56 |
+
$e^x = {{ a1Aff }}$
|
| 57 |
+
|
| 58 |
+
$x =$ {input}`||150`
|
| 59 |
+
::::
|
| 60 |
+
|
| 61 |
+
::::{questionHint}
|
| 62 |
+
{fr}`Appliquer l'équivalence fondamentale entre exponentielle et logarithme.`{en}`Apply the fundamental equivalence between exponential and logarithm.`
|
| 63 |
+
::::
|
| 64 |
+
|
| 65 |
+
::::{displayedSolution}
|
| 66 |
+
$x = {{ sol1Aff }}$
|
| 67 |
+
::::
|
| 68 |
+
|
| 69 |
+
::::{detailedSolution}
|
| 70 |
+
{fr}`On rappelle l'équivalence fondamentale : pour tout`{en}`Recall the fundamental equivalence: for all` $x \in \mathbb{R}$ {fr}`et tout`{en}`and all` $y > 0$,
|
| 71 |
+
|
| 72 |
+
\begin{equation*}
|
| 73 |
+
e^x = y \iff x = \ln y.
|
| 74 |
+
\end{equation*}
|
| 75 |
+
|
| 76 |
+
{fr}`De même, pour`{en}`Similarly, for` $u > 0$ : $\ln u = k \iff u = e^k$.
|
| 77 |
+
|
| 78 |
+
{fr}`L'équation`{en}`The equation` $e^x = {{ a1Aff }}$ {fr}`est définie pour tout`{en}`is defined for all` $x \in \mathbb{R}$. {fr}`Par l'équivalence fondamentale :`{en}`By the fundamental equivalence:`
|
| 79 |
+
|
| 80 |
+
\begin{equation*}
|
| 81 |
+
e^x = {{ a1Aff }} \iff x = \ln {{ a1Aff }}.
|
| 82 |
+
\end{equation*}
|
| 83 |
+
|
| 84 |
+
{fr}`La solution est`{en}`The solution is` $x = {{ sol1Aff }}$.
|
| 85 |
+
::::
|
| 86 |
+
|
| 87 |
+
::::{weightDistribution}
|
| 88 |
+
:reasoning: 20
|
| 89 |
+
:logic: 30
|
| 90 |
+
:abstraction: 20
|
| 91 |
+
:calculation: 30
|
| 92 |
+
::::
|
| 93 |
+
:::::
|
| 94 |
+
|
| 95 |
+
:::::{question}
|
| 96 |
+
:questionType: FGQ
|
| 97 |
+
:solution: [["ord","${{ sol2Aff }}$"],["0"]]
|
| 98 |
+
|
| 99 |
+
::::{questionStatement}
|
| 100 |
+
$e^{ {{ expExprAff }} } = {{ a2Aff }}$
|
| 101 |
+
|
| 102 |
+
$x =$ {input}`||150`
|
| 103 |
+
::::
|
| 104 |
+
|
| 105 |
+
::::{questionHint}
|
| 106 |
+
{fr}`Isoler l'exposant en appliquant le logarithme népérien des deux côtés, puis diviser par le coefficient de`{en}`Isolate the exponent by applying the natural logarithm to both sides, then divide by the coefficient of` $x$.
|
| 107 |
+
::::
|
| 108 |
+
|
| 109 |
+
::::{displayedSolution}
|
| 110 |
+
$x = {{ sol2Aff }}$
|
| 111 |
+
::::
|
| 112 |
+
|
| 113 |
+
::::{detailedSolution}
|
| 114 |
+
{fr}`L'équation`{en}`The equation` $e^{ {{ expExprAff }} } = {{ a2Aff }}$ {fr}`est définie pour tout`{en}`is defined for all` $x \in \mathbb{R}$. {fr}`On applique l'équivalence fondamentale :`{en}`We apply the fundamental equivalence:`
|
| 115 |
+
|
| 116 |
+
\begin{equation*}
|
| 117 |
+
e^{ {{ expExprAff }} } = {{ a2Aff }} &\iff {{ expExprAff }} = \ln {{ a2Aff }} \\
|
| 118 |
+
&\iff x = \dfrac{\ln {{ a2Aff }} }{ {{ coefExpAff }} }.
|
| 119 |
+
\end{equation*}
|
| 120 |
+
|
| 121 |
+
{fr}`La solution est`{en}`The solution is` $x = {{ sol2Aff }}$.
|
| 122 |
+
::::
|
| 123 |
+
|
| 124 |
+
::::{weightDistribution}
|
| 125 |
+
:reasoning: 25
|
| 126 |
+
:logic: 25
|
| 127 |
+
:abstraction: 20
|
| 128 |
+
:calculation: 30
|
| 129 |
+
::::
|
| 130 |
+
:::::
|
| 131 |
+
|
| 132 |
+
```{python}
|
| 133 |
+
import random as rd
|
| 134 |
+
from sympy import Symbol, Rational, ln, exp, latex, simplify
|
| 135 |
+
from pyxiscience.Mes_fctions_generalistes_bis import pxs_config, pxsl_latex_coefficient as lc
|
| 136 |
+
config_standard = pxs_config()
|
| 137 |
+
|
| 138 |
+
# ═══════════════════════════════════════════════════════════════════════════
|
| 139 |
+
# MÉTHODE CONSTRUCTIVE — partir de la réponse
|
| 140 |
+
# ═══════════════════════════════════════════════════════════════════════════
|
| 141 |
+
|
| 142 |
+
# Question 3 : ln(x) = k
|
| 143 |
+
# Réponse : x = e^k avec k entier ≥ 2
|
| 144 |
+
# On tire d'abord k (qui détermine la réponse), puis on fabrique l'énoncé
|
| 145 |
+
k = rd.randint(2, 6)
|
| 146 |
+
sol3 = exp(k)
|
| 147 |
+
sol3Aff = latex(sol3, **config_standard)
|
| 148 |
+
kAff = latex(k, **config_standard)
|
| 149 |
+
|
| 150 |
+
# Question 4 : ln(coefLn·x - constLn) = valLn
|
| 151 |
+
# Réponse : x = (e^valLn + constLn) / coefLn
|
| 152 |
+
# On tire d'abord coefLn, valLn, puis constLn tel que la solution soit > constLn/coefLn
|
| 153 |
+
# (pour garantir le domaine de définition)
|
| 154 |
+
coefLn = rd.randint(2, 5)
|
| 155 |
+
valLn = rd.choice([0, 1, -1])
|
| 156 |
+
# Pour que x > constLn/coefLn, on tire constLn puis on vérifie que la solution construite
|
| 157 |
+
# satisfait automatiquement cette condition (construction directe)
|
| 158 |
+
# Solution : x = (e^valLn + constLn) / coefLn
|
| 159 |
+
# Domaine : coefLn·x - constLn > 0 ⟺ x > constLn/coefLn
|
| 160 |
+
# On choisit constLn < coefLn·e^valLn pour garantir que la solution est dans le domaine
|
| 161 |
+
constLn = rd.randint(1, min(4, coefLn - 1))
|
| 162 |
+
|
| 163 |
+
# Calcul de la solution
|
| 164 |
+
sol4Num = exp(valLn) + constLn
|
| 165 |
+
sol4Den = coefLn
|
| 166 |
+
# Simplification si possible
|
| 167 |
+
from math import gcd
|
| 168 |
+
g = gcd(int(sol4Num) if sol4Num == int(sol4Num) else 1, sol4Den)
|
| 169 |
+
sol4NumSimp = sol4Num / g
|
| 170 |
+
sol4DenSimp = sol4Den / g
|
| 171 |
+
|
| 172 |
+
# Calcul de la borne du domaine
|
| 173 |
+
borneNum = constLn
|
| 174 |
+
borneDen = coefLn
|
| 175 |
+
gBorne = gcd(borneNum, borneDen)
|
| 176 |
+
borneNumSimp = borneNum // gBorne
|
| 177 |
+
borneDenSimp = borneDen // gBorne
|
| 178 |
+
|
| 179 |
+
# Affichage de la borne du domaine
|
| 180 |
+
if borneDenSimp == 1:
|
| 181 |
+
borneAff = latex(borneNumSimp, **config_standard)
|
| 182 |
+
else:
|
| 183 |
+
borneAff = latex(Rational(borneNumSimp, borneDenSimp), **config_standard)
|
| 184 |
+
|
| 185 |
+
# Affichage de la solution
|
| 186 |
+
if valLn == 0:
|
| 187 |
+
# e^0 = 1, donc solution = (1 + constLn) / coefLn
|
| 188 |
+
solNum = 1 + constLn
|
| 189 |
+
gSol = gcd(solNum, coefLn)
|
| 190 |
+
solNumFinal = solNum // gSol
|
| 191 |
+
solDenFinal = coefLn // gSol
|
| 192 |
+
if solDenFinal == 1:
|
| 193 |
+
sol4Aff = latex(solNumFinal, **config_standard)
|
| 194 |
+
else:
|
| 195 |
+
sol4Aff = latex(Rational(solNumFinal, solDenFinal), **config_standard)
|
| 196 |
+
else:
|
| 197 |
+
# Solution générale avec e^valLn
|
| 198 |
+
sol4Aff = r"\dfrac{e^{" + latex(valLn, **config_standard) + r"} + " + latex(constLn, **config_standard) + r"}{" + latex(coefLn, **config_standard) + r"}"
|
| 199 |
+
|
| 200 |
+
# Affichage de l'argument du logarithme
|
| 201 |
+
x = Symbol('x')
|
| 202 |
+
argLn = coefLn * x - constLn
|
| 203 |
+
argLnAff = latex(argLn, **config_standard)
|
| 204 |
+
|
| 205 |
+
# Affichage de valLn
|
| 206 |
+
valLnAff = latex(valLn, **config_standard)
|
| 207 |
+
|
| 208 |
+
# Affichage des étapes intermédiaires pour la solution 4
|
| 209 |
+
# Étape 1 : coefLn·x - constLn = e^valLn
|
| 210 |
+
if valLn == 0:
|
| 211 |
+
etape1Aff = latex(coefLn, **config_standard) + r" x - " + latex(constLn, **config_standard) + r" = 1"
|
| 212 |
+
else:
|
| 213 |
+
etape1Aff = latex(coefLn, **config_standard) + r" x - " + latex(constLn, **config_standard) + r" = e^{" + latex(valLn, **config_standard) + r"}"
|
| 214 |
+
|
| 215 |
+
# Étape 2 : coefLn·x = e^valLn + constLn
|
| 216 |
+
if valLn == 0:
|
| 217 |
+
etape2Aff = latex(coefLn, **config_standard) + r" x = " + latex(1 + constLn, **config_standard)
|
| 218 |
+
else:
|
| 219 |
+
etape2Aff = latex(coefLn, **config_standard) + r" x = e^{" + latex(valLn, **config_standard) + r"} + " + latex(constLn, **config_standard)
|
| 220 |
+
|
| 221 |
+
# Affichage de coefLn pour l'inégalité du domaine
|
| 222 |
+
coefLnAff = latex(coefLn, **config_standard)
|
| 223 |
+
constLnAff = latex(constLn, **config_standard)
|
| 224 |
+
|
| 225 |
+
globals()
|
| 226 |
+
```
|
| 227 |
+
|
| 228 |
+
:::::{question}
|
| 229 |
+
:questionType: FGQ
|
| 230 |
+
:solution: [["ord","${{ sol3Aff }}$"],["0"]]
|
| 231 |
+
|
| 232 |
+
::::{questionStatement}
|
| 233 |
+
$\ln x = {{ kAff }}$ {fr}`(on précisera le domaine de définition)`{en}`(specify the domain of definition)`
|
| 234 |
+
|
| 235 |
+
$x =$ {input}`||150`
|
| 236 |
+
::::
|
| 237 |
+
|
| 238 |
+
::::{questionHint}
|
| 239 |
+
{fr}`Utiliser la définition du logarithme népérien comme réciproque de la fonction exponentielle.`{en}`Use the definition of the natural logarithm as the inverse of the exponential function.`
|
| 240 |
+
::::
|
| 241 |
+
|
| 242 |
+
::::{displayedSolution}
|
| 243 |
+
$x = {{ sol3Aff }}$
|
| 244 |
+
::::
|
| 245 |
+
|
| 246 |
+
::::{detailedSolution}
|
| 247 |
+
{fr}`La fonction`{en}`The function` $\ln$ {fr}`est définie sur`{en}`is defined on` $\mathbb{R}_{+}^{*}$, {fr}`donc l'équation`{en}`so the equation` $\ln x = {{ kAff }}$ {fr}`impose`{en}`requires` $x > 0$. {fr}`Par définition du logarithme comme réciproque de l'exponentielle :`{en}`By definition of the logarithm as the inverse of the exponential:`
|
| 248 |
+
|
| 249 |
+
\begin{equation*}
|
| 250 |
+
\ln x = {{ kAff }} \iff x = {{ sol3Aff }}.
|
| 251 |
+
\end{equation*}
|
| 252 |
+
|
| 253 |
+
{fr}`La solution est`{en}`The solution is` $x = {{ sol3Aff }}$.
|
| 254 |
+
::::
|
| 255 |
+
|
| 256 |
+
::::{weightDistribution}
|
| 257 |
+
:reasoning: 20
|
| 258 |
+
:logic: 30
|
| 259 |
+
:abstraction: 25
|
| 260 |
+
:calculation: 25
|
| 261 |
+
::::
|
| 262 |
+
:::::
|
| 263 |
+
|
| 264 |
+
:::::{question}
|
| 265 |
+
:questionType: FGQ
|
| 266 |
+
:solution: [["ord","${{ sol4Aff }}$"],["0"]]
|
| 267 |
+
|
| 268 |
+
::::{questionStatement}
|
| 269 |
+
$\ln({{ argLnAff }}) = {{ valLnAff }}$ {fr}`(on précisera le domaine de définition)`{en}`(specify the domain of definition)`
|
| 270 |
+
|
| 271 |
+
$x =$ {input}`||200`
|
| 272 |
+
::::
|
| 273 |
+
|
| 274 |
+
::::{questionHint}
|
| 275 |
+
{fr}`Déterminer le domaine de définition en résolvant une inégalité, puis appliquer l'exponentielle aux deux membres pour éliminer le logarithme.`{en}`Determine the domain of definition by solving an inequality, then apply the exponential to both sides to eliminate the logarithm.`
|
| 276 |
+
::::
|
| 277 |
+
|
| 278 |
+
::::{displayedSolution}
|
| 279 |
+
$x = {{ sol4Aff }}$
|
| 280 |
+
::::
|
| 281 |
+
|
| 282 |
+
::::{detailedSolution}
|
| 283 |
+
{fr}`La fonction`{en}`The function` $\ln({{ argLnAff }})$ {fr}`est définie si et seulement si`{en}`is defined if and only if` ${{ argLnAff }} > 0$, {fr}`c'est-à-dire`{en}`that is` ${{ coefLnAff }} x > {{ constLnAff }}$, {fr}`soit`{en}`i.e.` $x > {{ borneAff }}$. {fr}`Sur ce domaine :`{en}`On this domain:`
|
| 284 |
+
|
| 285 |
+
\begin{equation*}
|
| 286 |
+
\ln({{ argLnAff }}) = {{ valLnAff }} &\iff {{ etape1Aff }} \\
|
| 287 |
+
&\iff {{ etape2Aff }} \\
|
| 288 |
+
&\iff x = {{ sol4Aff }}.
|
| 289 |
+
\end{equation*}
|
| 290 |
+
|
| 291 |
+
{fr}`On vérifie que`{en}`We verify that` $\ds {{ sol4Aff }} > {{ borneAff }}$ : {fr}`la condition de domaine est bien satisfaite. La solution est`{en}`the domain condition is indeed satisfied. The solution is` $x = {{ sol4Aff }}$.
|
| 292 |
+
::::
|
| 293 |
+
|
| 294 |
+
::::{weightDistribution}
|
| 295 |
+
:reasoning: 30
|
| 296 |
+
:logic: 25
|
| 297 |
+
:abstraction: 25
|
| 298 |
+
:calculation: 20
|
| 299 |
+
::::
|
| 300 |
+
:::::
|
| 301 |
+
|
| 302 |
+
`````
|
app/knowledge/fewshots/declinaisons/{fr}`Équilibre de marché _ l'offre rencontre la demande`{en}`Market Equilibrium_ Supply Meets Demand`.md
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
`````{exercise}
|
| 2 |
+
:title: {fr}`Équilibre de marché : l'offre rencontre la demande`{en}`Market Equilibrium: Supply Meets Demand`
|
| 3 |
+
:modules:
|
| 4 |
+
:recommendedExecutionTime: 10
|
| 5 |
+
:level: Elementary
|
| 6 |
+
:chap: chap_lines_Systems_economicEquilibriumBreakeven_ESCP
|
| 7 |
+
:involvedConcepts:
|
| 8 |
+
:originalSource: Session 4 — Erwan Lamy, ESCP Business School
|
| 9 |
+
:visibility: All
|
| 10 |
+
:variations:
|
| 11 |
+
:comment: Appliqué (micro-économie) : sens de variation de l'offre et de la demande, point d'équilibre par résolution de S(q) = D(q), vérification.
|
| 12 |
+
:id: 0924e9e6-74c0-11f1-a8a1-0ed8d3b012a9
|
| 13 |
+
:originalExerciseId: 0177637c-64e3-11f1-a8a1-0ed8d3b012a9
|
| 14 |
+
|
| 15 |
+
````{python}
|
| 16 |
+
import random as rd
|
| 17 |
+
from pyxiscience.Mes_fctions_generalistes_bis import pxs_config, pxsl_latex_coefficient as lc, pxsl_format_number
|
| 18 |
+
# --- Helpers PyxiScience (definis inline, autonomes : aucun import externe) ---
|
| 19 |
+
|
| 20 |
+
config_standard = pxs_config()
|
| 21 |
+
|
| 22 |
+
# S croissante (penteS>0), D decroissante (penteD<0), ordD>ordS,
|
| 23 |
+
# equilibre q* entier positif
|
| 24 |
+
for _ in range(300):
|
| 25 |
+
penteS = rd.randint(1, 5)
|
| 26 |
+
ordS = rd.randint(2, 10)
|
| 27 |
+
penteD = rd.randint(-5, -1)
|
| 28 |
+
ordD = rd.randint(15, 30)
|
| 29 |
+
if ordD > ordS and (ordD - ordS) % (penteS - penteD) == 0:
|
| 30 |
+
qStar = (ordD - ordS) // (penteS - penteD)
|
| 31 |
+
if qStar > 0:
|
| 32 |
+
break
|
| 33 |
+
|
| 34 |
+
pStar = penteS * qStar + ordS
|
| 35 |
+
pStarVerif = penteD * qStar + ordD # = pStar
|
| 36 |
+
pots = qStar * 100
|
| 37 |
+
|
| 38 |
+
# Affichage
|
| 39 |
+
penteSCoefAff = lc(penteS); ordSConstAff = lc(ordS, ones=True, sign=True)
|
| 40 |
+
penteDCoefAff = lc(penteD); ordDConstAff = lc(ordD, ones=True, sign=True)
|
| 41 |
+
penteSAff = str(penteS); penteDAff = str(penteD)
|
| 42 |
+
penteDiffAff = lc(penteS - penteD); rhsEq = ordD - ordS
|
| 43 |
+
|
| 44 |
+
globals()
|
| 45 |
+
````
|
| 46 |
+
|
| 47 |
+
{fr}`Sur le marché du miel bio (prix `{en}`On the organic honey market (price `$p${fr}` en €/pot, quantité `{en}` in €/jar, quantity `$q${fr}` en centaines de pots par semaine), l'offre et la demande sont `{en}` in hundreds of jars per week), supply and demand are `$S(q) = {{penteSCoefAff}}q {{ordSConstAff}}${fr}` et `{en}` and `$D(q) = {{penteDCoefAff}}q {{ordDConstAff}}$.
|
| 48 |
+
|
| 49 |
+
:::::{question}
|
| 50 |
+
:questionType: FGQ
|
| 51 |
+
:questionId: 0
|
| 52 |
+
:questionIndex: 0
|
| 53 |
+
:solution: [["ord","${{ penteS }}$","${{ ordS }}$","${{ penteD }}$","${{ ordD }}$"],["0","0","0","0"]]
|
| 54 |
+
|
| 55 |
+
::::{questionStatement}
|
| 56 |
+
{fr}`Vérifier que `{en}`Check that `$S${fr}` est croissante et `{en}` is increasing and `$D${fr}` décroissante, et donner leurs coefficients directeurs et ordonnées à l'origine.`{en}` decreasing, and give their slopes and y-intercepts.`
|
| 57 |
+
- $S$ {fr}`: coefficient directeur`{en}`: slope` {input}`||80` {fr}`, ordonnée à l'origine`{en}`, y-intercept` {input}`||80`
|
| 58 |
+
- $D$ {fr}`: coefficient directeur`{en}`: slope` {input}`||80` {fr}`, ordonnée à l'origine`{en}`, y-intercept` {input}`||80`
|
| 59 |
+
::::
|
| 60 |
+
|
| 61 |
+
::::{questionHint}
|
| 62 |
+
{fr}`Le signe du coefficient directeur donne le sens de variation.`{en}`The sign of the slope gives the direction of variation.`
|
| 63 |
+
::::
|
| 64 |
+
|
| 65 |
+
::::{displayedSolution}
|
| 66 |
+
- $S$ {fr}`: pente`{en}`: slope` ${{ penteS }}$ {fr}`, ordonnée`{en}`, intercept` ${{ ordS }}$
|
| 67 |
+
- $D$ {fr}`: pente`{en}`: slope` ${{ penteD }}$ {fr}`, ordonnée`{en}`, intercept` ${{ ordD }}$
|
| 68 |
+
::::
|
| 69 |
+
|
| 70 |
+
::::{detailedSolution}
|
| 71 |
+
{fr}`Pour `{en}`For `$S(q) = {{penteSCoefAff}}q {{ordSConstAff}}${fr}` : coefficient directeur `{en}`: slope `${{penteS}} > 0${fr}`, donc `{en}`, so `$S${fr}` est croissante (les producteurs offrent davantage quand le prix monte) ; ordonnée à l'origine `{en}` is increasing (producers supply more as the price rises); y-intercept `${{ordS}}${fr}` (prix d'offre minimal).`{en}` (minimum supply price).`
|
| 72 |
+
|
| 73 |
+
{fr}`Pour `{en}`For `$D(q) = {{penteDCoefAff}}q {{ordDConstAff}}${fr}` : coefficient directeur `{en}`: slope `${{penteD}} < 0${fr}`, donc `{en}`, so `$D${fr}` est décroissante (les consommateurs demandent moins quand le prix monte) ; ordonnée à l'origine `{en}` is decreasing (consumers demand less as the price rises); y-intercept `${{ordD}}$.
|
| 74 |
+
::::
|
| 75 |
+
|
| 76 |
+
::::{weightDistribution}
|
| 77 |
+
:logic: 15
|
| 78 |
+
:abstraction: 25
|
| 79 |
+
:reasoning: 30
|
| 80 |
+
:calculation: 30
|
| 81 |
+
::::
|
| 82 |
+
:::::
|
| 83 |
+
|
| 84 |
+
:::::{question}
|
| 85 |
+
:questionType: FGQ
|
| 86 |
+
:questionId: 1
|
| 87 |
+
:questionIndex: 1
|
| 88 |
+
:solution: [["ord","${{ qStar }}$"],["0"]]
|
| 89 |
+
|
| 90 |
+
::::{questionStatement}
|
| 91 |
+
{fr}`Déterminer la quantité d'équilibre `{en}`Determine the equilibrium quantity `$q^{*}${fr}` en résolvant `{en}` by solving `$S(q) = D(q)$.\
|
| 92 |
+
\
|
| 93 |
+
$q^*$ = {input}`||80`
|
| 94 |
+
::::
|
| 95 |
+
|
| 96 |
+
::::{questionHint}
|
| 97 |
+
{fr}`À l'équilibre, le prix d'offre égale le prix de demande : `{en}`At equilibrium, the supply price equals the demand price: `${{penteSCoefAff}}q {{ordSConstAff}} = {{penteDCoefAff}}q {{ordDConstAff}}$.
|
| 98 |
+
::::
|
| 99 |
+
|
| 100 |
+
::::{displayedSolution}
|
| 101 |
+
$q^* = {{ qStar }}$
|
| 102 |
+
::::
|
| 103 |
+
|
| 104 |
+
::::{detailedSolution}
|
| 105 |
+
{fr}`On résout `{en}`We solve `$S(q) = D(q)$ :
|
| 106 |
+
|
| 107 |
+
\begin{equation*}
|
| 108 |
+
{{penteSCoefAff}}q {{ordSConstAff}} &= {{penteDCoefAff}}q {{ordDConstAff}} \\
|
| 109 |
+
{{penteDiffAff}}q &= {{rhsEq}} \\
|
| 110 |
+
q^{*} &= {{qStar}}.
|
| 111 |
+
\end{equation*}
|
| 112 |
+
|
| 113 |
+
{fr}`La quantité d'équilibre est `{en}`The equilibrium quantity is `$q^{*} = {{qStar}}${fr}` (soit `{en}` (i.e. `${{pots}}${fr}` pots par semaine).`{en}` jars per week).`
|
| 114 |
+
::::
|
| 115 |
+
|
| 116 |
+
::::{weightDistribution}
|
| 117 |
+
:logic: 15
|
| 118 |
+
:abstraction: 25
|
| 119 |
+
:reasoning: 30
|
| 120 |
+
:calculation: 30
|
| 121 |
+
::::
|
| 122 |
+
:::::
|
| 123 |
+
|
| 124 |
+
:::::{question}
|
| 125 |
+
:questionType: FGQ
|
| 126 |
+
:questionId: 2
|
| 127 |
+
:questionIndex: 2
|
| 128 |
+
:solution: [["ord","${{ pStar }}$"],["0"]]
|
| 129 |
+
|
| 130 |
+
::::{questionStatement}
|
| 131 |
+
{fr}`En déduire le prix d'équilibre `{en}`Deduce the equilibrium price `$p^{*} = S(q^{*})$.\
|
| 132 |
+
\
|
| 133 |
+
$p^*$ = {input}`||80`
|
| 134 |
+
::::
|
| 135 |
+
|
| 136 |
+
::::{questionHint}
|
| 137 |
+
{fr}`Remplacer `{en}`Replace `$q${fr}` par `{en}` by `$q^{*}${fr}` dans la fonction d'offre.`{en}` in the supply function.`
|
| 138 |
+
::::
|
| 139 |
+
|
| 140 |
+
::::{displayedSolution}
|
| 141 |
+
$p^* = {{ pStar }}$
|
| 142 |
+
::::
|
| 143 |
+
|
| 144 |
+
::::{detailedSolution}
|
| 145 |
+
{fr}`On utilise la fonction d'offre :`{en}`We use the supply function:`
|
| 146 |
+
|
| 147 |
+
\begin{equation*}
|
| 148 |
+
p^{*} = S({{qStar}}) = {{penteSAff}} \times {{qStar}} {{ordSConstAff}} = {{pStar}}.
|
| 149 |
+
\end{equation*}
|
| 150 |
+
|
| 151 |
+
{fr}`soit`{en}`i.e.` ${{ pStar }}$ {fr}`€/pot.`{en}`€/jar.`
|
| 152 |
+
::::
|
| 153 |
+
|
| 154 |
+
::::{weightDistribution}
|
| 155 |
+
:logic: 10
|
| 156 |
+
:abstraction: 20
|
| 157 |
+
:reasoning: 25
|
| 158 |
+
:calculation: 45
|
| 159 |
+
::::
|
| 160 |
+
:::::
|
| 161 |
+
|
| 162 |
+
:::::{question}
|
| 163 |
+
:questionType: FGQ
|
| 164 |
+
:questionId: 3
|
| 165 |
+
:questionIndex: 3
|
| 166 |
+
:solution: [["ord","${{ pStar }}$"],["0"]]
|
| 167 |
+
|
| 168 |
+
::::{questionStatement}
|
| 169 |
+
{fr}`Vérifier la réponse à l'aide de `{en}`Check the answer using `$D(q^{*})$.\
|
| 170 |
+
\
|
| 171 |
+
$D(q^*)$ = {input}`||80`
|
| 172 |
+
::::
|
| 173 |
+
|
| 174 |
+
::::{questionHint}
|
| 175 |
+
{fr}`À l'équilibre, l'offre et la demande doivent donner le même prix.`{en}`At equilibrium, supply and demand must give the same price.`
|
| 176 |
+
::::
|
| 177 |
+
|
| 178 |
+
::::{displayedSolution}
|
| 179 |
+
$D(q^*) = {{ pStar }}$
|
| 180 |
+
::::
|
| 181 |
+
|
| 182 |
+
::::{detailedSolution}
|
| 183 |
+
{fr}`On calcule la demande en `{en}`We compute demand at `$q^{*} = {{qStar}}$ :
|
| 184 |
+
|
| 185 |
+
\begin{equation*}
|
| 186 |
+
D({{qStar}}) = {{penteDAff}} \times {{qStar}} {{ordDConstAff}} = {{pStar}}.
|
| 187 |
+
\end{equation*}
|
| 188 |
+
|
| 189 |
+
{fr}`soit`{en}`i.e.` ${{ pStar }}$ {fr}`€/pot.`{en}`€/jar.`
|
| 190 |
+
|
| 191 |
+
{fr}`On a `{en}`We have `$S({{qStar}}) = D({{qStar}}) = {{pStar}}${fr}` : le point d'équilibre est bien `{en}`: the equilibrium point is indeed `$(q^{*},\, p^{*}) = ({{qStar}},\, {{pStar}})$.
|
| 192 |
+
::::
|
| 193 |
+
|
| 194 |
+
::::{weightDistribution}
|
| 195 |
+
:logic: 15
|
| 196 |
+
:abstraction: 20
|
| 197 |
+
:reasoning: 30
|
| 198 |
+
:calculation: 35
|
| 199 |
+
::::
|
| 200 |
+
:::::
|
| 201 |
+
|
| 202 |
+
`````
|
app/knowledge/fewshots/declinaisons/{fr}`Équilibre offre-demande sur un marché`{en}`Supply-demand equilibrium in a market`.md
ADDED
|
@@ -0,0 +1,237 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
`````{exercise}
|
| 2 |
+
:title: {fr}`Équilibre offre-demande sur un marché`{en}`Supply-demand equilibrium in a market`
|
| 3 |
+
:modules:
|
| 4 |
+
:recommendedExecutionTime: 15
|
| 5 |
+
:level: Elementary
|
| 6 |
+
:chap: chap_lines_Systems_economicEquilibriumBreakeven_ESCP
|
| 7 |
+
:involvedConcepts:
|
| 8 |
+
:originalSource:
|
| 9 |
+
:visibility: All
|
| 10 |
+
:variations:
|
| 11 |
+
:comment: Appliqué éco/gestion — prix et quantité d'équilibre comme intersection de la droite d'offre (croissante) et de la droite de demande (décroissante).
|
| 12 |
+
:id: 730a9473-74c0-11f1-a8a1-0ed8d3b012a9
|
| 13 |
+
:originalExerciseId: 0582ee6e-64e4-11f1-a8a1-0ed8d3b012a9
|
| 14 |
+
|
| 15 |
+
````{python}
|
| 16 |
+
import random as rd
|
| 17 |
+
from pyxiscience.Mes_fctions_generalistes_bis import pxs_config, pxsl_latex_coefficient as lc
|
| 18 |
+
config_standard = pxs_config()
|
| 19 |
+
|
| 20 |
+
# Offre croissante, demande décroissante, équilibre ENTIER dans le quadrant positif
|
| 21 |
+
for _ in range(300):
|
| 22 |
+
a_offre = rd.randint(1, 5)
|
| 23 |
+
b_offre = rd.randint(2, 10)
|
| 24 |
+
a_demande = rd.randint(-5, -1)
|
| 25 |
+
b_demande = rd.randint(max(15, b_offre + 10), 40)
|
| 26 |
+
denom = a_offre - a_demande
|
| 27 |
+
num = b_demande - b_offre
|
| 28 |
+
if denom > 0 and num > 0 and num % denom == 0:
|
| 29 |
+
repQ = num // denom
|
| 30 |
+
repP = a_offre * repQ + b_offre
|
| 31 |
+
if 2 <= repQ <= 12 and repP > 0:
|
| 32 |
+
break
|
| 33 |
+
|
| 34 |
+
# Limites pour le graphique
|
| 35 |
+
q_max_graph = max(6.5, repQ * 1.3)
|
| 36 |
+
p_max_graph = max(32, b_demande + 3)
|
| 37 |
+
|
| 38 |
+
# Précalcul de l'affichage
|
| 39 |
+
slopeOffreAff = lc(a_offre)
|
| 40 |
+
interOffreAff = lc(b_offre, ones=True, sign=True)
|
| 41 |
+
slopeDemAff = lc(a_demande)
|
| 42 |
+
interDemAff = lc(b_demande, ones=True, sign=True)
|
| 43 |
+
slopeOffreSignAff = lc(a_offre, ones=True, sign=True)
|
| 44 |
+
slopeDemPlainAff = lc(a_demande, ones=True)
|
| 45 |
+
coefDiffAff = lc(a_offre - a_demande)
|
| 46 |
+
rhsAff = str(b_demande - b_offre)
|
| 47 |
+
aOffreAff = str(a_offre)
|
| 48 |
+
aDemandeAff = str(a_demande)
|
| 49 |
+
repQAff = str(repQ)
|
| 50 |
+
repPAff = str(repP)
|
| 51 |
+
|
| 52 |
+
# Libellés multilingues pour matplotlib
|
| 53 |
+
if pxs_lang == "fr":
|
| 54 |
+
label_offre_prefix = "Offre : "
|
| 55 |
+
label_demande_prefix = "Demande : "
|
| 56 |
+
label_q_axis = r"$q$ (milliers)"
|
| 57 |
+
label_p_axis = r"$p$ (euros)"
|
| 58 |
+
else:
|
| 59 |
+
label_offre_prefix = "Supply: "
|
| 60 |
+
label_demande_prefix = "Demand: "
|
| 61 |
+
label_q_axis = r"$q$ (thousands)"
|
| 62 |
+
label_p_axis = r"$p$ (euros)"
|
| 63 |
+
|
| 64 |
+
globals()
|
| 65 |
+
````
|
| 66 |
+
|
| 67 |
+
:::::{question}
|
| 68 |
+
:questionType: FGQ
|
| 69 |
+
:questionId: 0
|
| 70 |
+
:questionIndex: 0
|
| 71 |
+
:solution: [["ord","${{ a_offre }}$","${{ a_demande }}$"],["0","0"]]
|
| 72 |
+
|
| 73 |
+
::::{questionStatement}
|
| 74 |
+
{fr}`Sur le marché d'un bien de consommation, l'offre et la demande (prix`{en}`In the market for a consumer good, supply and demand (price` $p$ {fr}`en euros par unité, en fonction de la quantité`{en}`in euros per unit, as a function of quantity` $q$ {fr}`en milliers d'unités) sont`{en}`in thousands of units) are` $\ds \text{{fr}`Offre`{en}`Supply`} : p = {{slopeOffreAff}}q {{interOffreAff}}$ {fr}`et`{en}`and` $\ds \text{{fr}`Demande`{en}`Demand`} : p = {{slopeDemAff}}q {{interDemAff}}$. {fr}`Vérifier que la courbe d'offre est croissante et celle de demande décroissante, et interpréter économiquement.`{en}`Verify that the supply curve is increasing and the demand curve is decreasing, and interpret economically.`
|
| 75 |
+
- {fr}`Pente de l'offre :`{en}`Slope of supply:` {input}`||80`
|
| 76 |
+
- {fr}`Pente de la demande :`{en}`Slope of demand:` {input}`||80`
|
| 77 |
+
::::
|
| 78 |
+
|
| 79 |
+
::::{questionHint}
|
| 80 |
+
{fr}`Le signe de la pente donne le sens de variation.`{en}`The sign of the slope gives the direction of variation.`
|
| 81 |
+
::::
|
| 82 |
+
|
| 83 |
+
::::{displayedSolution}
|
| 84 |
+
- {fr}`Pente offre`{en}`Supply slope` ${{ a_offre }}$
|
| 85 |
+
- {fr}`Pente demande`{en}`Demand slope` ${{ a_demande }}$
|
| 86 |
+
::::
|
| 87 |
+
|
| 88 |
+
::::{detailedSolution}
|
| 89 |
+
{fr}`La droite d'offre`{en}`The supply line` $p = {{slopeOffreAff}}q {{interOffreAff}}$ {fr}`a une pente`{en}`has a slope` ${{slopeOffreSignAff}} > 0$ : {fr}`elle est croissante. Économiquement, les producteurs offrent davantage quand le prix monte. La droite de demande`{en}`it is increasing. Economically, producers supply more when the price rises. The demand line` $p = {{slopeDemAff}}q {{interDemAff}}$ {fr}`a une pente`{en}`has a slope` ${{slopeDemPlainAff}} < 0$ : {fr}`elle est décroissante. Les consommateurs demandent moins quand le prix monte.`{en}`it is decreasing. Consumers demand less when the price rises.`
|
| 90 |
+
::::
|
| 91 |
+
|
| 92 |
+
::::{weightDistribution}
|
| 93 |
+
:logic: 18
|
| 94 |
+
:abstraction: 22
|
| 95 |
+
:reasoning: 35
|
| 96 |
+
:calculation: 25
|
| 97 |
+
::::
|
| 98 |
+
:::::
|
| 99 |
+
|
| 100 |
+
:::::{question}
|
| 101 |
+
:questionType: FGQ
|
| 102 |
+
:questionId: 1
|
| 103 |
+
:questionIndex: 1
|
| 104 |
+
:solution: [["ord","${{ repQAff }}$","${{ repPAff }}$"],["0","0"]]
|
| 105 |
+
|
| 106 |
+
::::{questionStatement}
|
| 107 |
+
{fr}`Déterminer la quantité d'équilibre`{en}`Determine the equilibrium quantity` $q^*$ {fr}`et le prix d'équilibre`{en}`and the equilibrium price` $p^*$ {fr}`en résolvant le système formé par les deux équations.`{en}`by solving the system formed by the two equations.`
|
| 108 |
+
- $q^*$ = {input}`||80`
|
| 109 |
+
- $p^*$ = {input}`||80`
|
| 110 |
+
::::
|
| 111 |
+
|
| 112 |
+
::::{questionHint}
|
| 113 |
+
{fr}`À l'équilibre, offre = demande : égale les deux expressions de`{en}`At equilibrium, supply = demand: equate the two expressions of` $p$.
|
| 114 |
+
::::
|
| 115 |
+
|
| 116 |
+
::::{displayedSolution}
|
| 117 |
+
- $q^* = {{ repQAff }}$
|
| 118 |
+
- $p^* = {{ repPAff }}$
|
| 119 |
+
::::
|
| 120 |
+
|
| 121 |
+
::::{detailedSolution}
|
| 122 |
+
{fr}`On égale offre et demande :`{en}`We equate supply and demand:`
|
| 123 |
+
|
| 124 |
+
\begin{equation*}
|
| 125 |
+
{{slopeOffreAff}}q^* {{interOffreAff}} &= {{slopeDemAff}}q^* {{interDemAff}} \\
|
| 126 |
+
{{coefDiffAff}}q^* &= {{rhsAff}} \\
|
| 127 |
+
q^* &= {{repQAff}}.
|
| 128 |
+
\end{equation*}
|
| 129 |
+
|
| 130 |
+
{fr}`En reportant dans l'offre :`{en}`Substituting into the supply:`
|
| 131 |
+
|
| 132 |
+
\begin{equation*}
|
| 133 |
+
p^* &= {{aOffreAff}} \times {{repQAff}} {{interOffreAff}} \\
|
| 134 |
+
&= {{repPAff}}.
|
| 135 |
+
\end{equation*}
|
| 136 |
+
|
| 137 |
+
{fr}`Vérification par la demande :`{en}`Verification by demand:` ${{aDemandeAff}} \times {{repQAff}} {{interDemAff}} = {{repPAff}}$. {fr}`L'équilibre est`{en}`The equilibrium is` $q^* = {{repQAff}}$ {fr}`milliers d'unités au prix`{en}`thousands of units at price` $p^* = {{repPAff}}$ {fr}`€ par unité.`{en}`€ per unit.`
|
| 138 |
+
::::
|
| 139 |
+
|
| 140 |
+
::::{weightDistribution}
|
| 141 |
+
:logic: 15
|
| 142 |
+
:abstraction: 20
|
| 143 |
+
:reasoning: 30
|
| 144 |
+
:calculation: 35
|
| 145 |
+
::::
|
| 146 |
+
:::::
|
| 147 |
+
|
| 148 |
+
:::::{question}
|
| 149 |
+
:questionType: FGQ
|
| 150 |
+
:questionId: 2
|
| 151 |
+
:questionIndex: 2
|
| 152 |
+
:solution: [["ord","${{ repQAff }}$","${{ repPAff }}$"],["0","0"]]
|
| 153 |
+
|
| 154 |
+
::::{questionStatement}
|
| 155 |
+
{fr}`Représenter les deux droites sur un graphique clairement légendé et repérer le point d'équilibre.`{en}`Represent the two lines on a clearly labeled graph and identify the equilibrium point.`\
|
| 156 |
+
\
|
| 157 |
+
{fr}`Point d'équilibre`{en}`Equilibrium point` $E${fr}` `:
|
| 158 |
+
- {fr}`abscisse`{en}`x-coordinate`{fr}` `: {input}`||80`
|
| 159 |
+
- {fr}`ordonnée`{en}`y-coordinate`{fr}` `: {input}`||80`
|
| 160 |
+
::::
|
| 161 |
+
|
| 162 |
+
::::{questionHint}
|
| 163 |
+
{fr}`Trace les deux droites ; leur intersection est le point d'équilibre`{en}`Draw the two lines; their intersection is the equilibrium point` $E$.
|
| 164 |
+
::::
|
| 165 |
+
|
| 166 |
+
::::{displayedSolution}
|
| 167 |
+
{fr}`Point d'équilibre`{en}`Equilibrium point` $E${fr}` `:
|
| 168 |
+
- {fr}`abscisse`{en}`x-coordinate`{fr}` `: ${{ repQAff }}$
|
| 169 |
+
- {fr}`ordonnée`{en}`y-coordinate`{fr}` `: ${{ repPAff }}$
|
| 170 |
+
::::
|
| 171 |
+
|
| 172 |
+
::::{detailedSolution}
|
| 173 |
+
{fr}`Les deux droites se coupent au point d'équilibre`{en}`The two lines intersect at the equilibrium point` $E({{repQAff}}\,;\,{{repPAff}})$ :
|
| 174 |
+
|
| 175 |
+
```{python}
|
| 176 |
+
import numpy as np
|
| 177 |
+
import matplotlib.pyplot as plt
|
| 178 |
+
|
| 179 |
+
# Libellés pour matplotlib : moteur mathtext, donc PAS de \displaystyle ni \; (non supportés).
|
| 180 |
+
def slopeTerm(a):
|
| 181 |
+
if a == 1: return "q"
|
| 182 |
+
if a == -1: return "-q"
|
| 183 |
+
return f"{a}q"
|
| 184 |
+
|
| 185 |
+
def interTerm(b):
|
| 186 |
+
if b == 0: return ""
|
| 187 |
+
return f" + {b}" if b > 0 else f" - {abs(b)}"
|
| 188 |
+
|
| 189 |
+
q = np.linspace(0, q_max_graph, 200)
|
| 190 |
+
offre = a_offre * q + b_offre
|
| 191 |
+
demande = a_demande * q + b_demande
|
| 192 |
+
|
| 193 |
+
fig, ax = plt.subplots(figsize=(6, 6))
|
| 194 |
+
ax.plot(q, offre, color="blue", lw=2, label=f"{label_offre_prefix}$p = {slopeTerm(a_offre)}{interTerm(b_offre)}$")
|
| 195 |
+
ax.plot(q, demande, color="red", lw=2, label=f"{label_demande_prefix}$p = {slopeTerm(a_demande)}{interTerm(b_demande)}$")
|
| 196 |
+
|
| 197 |
+
ax.plot(repQ, repP, "ko", markersize=6)
|
| 198 |
+
ax.annotate(f"$E({repQ}\\,;\\,{repP})$", xy=(repQ, repP), xytext=(repQ + 0.15, repP + 1.2), fontsize=11)
|
| 199 |
+
ax.plot([repQ, repQ], [0, repP], ls="--", color="0.5", lw=1)
|
| 200 |
+
ax.plot([0, repQ], [repP, repP], ls="--", color="0.5", lw=1)
|
| 201 |
+
|
| 202 |
+
ax.annotate("", xy=(q_max_graph + 0.5, 0), xytext=(0, 0), arrowprops=dict(arrowstyle="->", color="black", lw=1.2))
|
| 203 |
+
ax.annotate("", xy=(0, p_max_graph), xytext=(0, 0), arrowprops=dict(arrowstyle="->", color="black", lw=1.2))
|
| 204 |
+
ax.text(q_max_graph + 0.6, -0.5, label_q_axis, ha="center", fontsize=11)
|
| 205 |
+
ax.text(-0.4, p_max_graph, label_p_axis, ha="center", fontsize=11)
|
| 206 |
+
|
| 207 |
+
q_step = max(1, int(q_max_graph / 6))
|
| 208 |
+
for x in range(q_step, int(q_max_graph) + 1, q_step):
|
| 209 |
+
ax.plot([x, x], [-0.3, 0.3], color="black", lw=1)
|
| 210 |
+
ax.text(x, -1.0, str(x), ha="center", fontsize=9)
|
| 211 |
+
|
| 212 |
+
p_step = max(5, int(p_max_graph / 6))
|
| 213 |
+
for y in range(p_step, int(p_max_graph) + 1, p_step):
|
| 214 |
+
ax.plot([-0.1, 0.1], [y, y], color="black", lw=1)
|
| 215 |
+
ax.text(-0.35, y, str(y), ha="right", va="center", fontsize=9)
|
| 216 |
+
ax.text(-0.35, 0, "0", ha="right", va="center", fontsize=9)
|
| 217 |
+
|
| 218 |
+
ax.legend(loc="upper right", fontsize=10)
|
| 219 |
+
ax.set_xlim(-0.5, q_max_graph + 1)
|
| 220 |
+
ax.set_ylim(-2, p_max_graph + 1)
|
| 221 |
+
ax.axis("off")
|
| 222 |
+
plt.tight_layout()
|
| 223 |
+
plt.show()
|
| 224 |
+
```
|
| 225 |
+
|
| 226 |
+
{fr}`L'offre (croissante) et la demande (décroissante) se croisent en`{en}`Supply (increasing) and demand (decreasing) intersect at` $E({{repQAff}}\,;\,{{repPAff}})$, {fr}`qui matérialise le prix où le marché s'équilibre.`{en}`which represents the price where the market reaches equilibrium.`
|
| 227 |
+
::::
|
| 228 |
+
|
| 229 |
+
::::{weightDistribution}
|
| 230 |
+
:logic: 20
|
| 231 |
+
:abstraction: 25
|
| 232 |
+
:reasoning: 35
|
| 233 |
+
:calculation: 20
|
| 234 |
+
::::
|
| 235 |
+
:::::
|
| 236 |
+
|
| 237 |
+
`````
|
app/knowledge/fewshots/declinaisons/{fr}`Évaluer un logarithme en reconnaissant une puissance de la base`{en}`Evaluate a logarithm by recognizing a power of the base`.md
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
`````{exercise}
|
| 2 |
+
:title: {fr}`Évaluer un logarithme en reconnaissant une puissance de la base`{en}`Evaluate a logarithm by recognizing a power of the base`
|
| 3 |
+
:modules:
|
| 4 |
+
:recommendedExecutionTime: 10
|
| 5 |
+
:level: Elementary
|
| 6 |
+
:chap: chap_expLogFunctions_logarithmicFunctions_ESCP
|
| 7 |
+
:involvedConcepts:
|
| 8 |
+
:originalSource:
|
| 9 |
+
:visibility: All
|
| 10 |
+
:variations:
|
| 11 |
+
:comment: Échauffement (thème pur) : évaluation de log_b(x) en reconnaissant x comme une puissance de b, via log_b(b^k)=k. Aucune loi algébrique du logarithme.
|
| 12 |
+
:originalExerciseId: 1331c2a5-6599-11f1-a8a1-0ed8d3b012a9
|
| 13 |
+
:id: fee348e8-74be-11f1-a8a1-0ed8d3b012a9
|
| 14 |
+
|
| 15 |
+
````{python}
|
| 16 |
+
import random as rd
|
| 17 |
+
|
| 18 |
+
# Construction déterministe : on part des exposants (réponses) puis on bâtit les arguments.
|
| 19 |
+
base1 = rd.choice([2, 3, 5, 7, 10])
|
| 20 |
+
exp1 = rd.randint(2, 5)
|
| 21 |
+
arg1 = base1 ** exp1
|
| 22 |
+
base2 = rd.choice([b for b in [2, 3, 5, 7, 10] if b != base1])
|
| 23 |
+
exp2 = rd.randint(2, 4)
|
| 24 |
+
arg2 = base2 ** exp2
|
| 25 |
+
base3 = rd.choice([b for b in [2, 3, 5, 7] if b not in [base1, base2]])
|
| 26 |
+
exp3 = rd.randint(2, 5)
|
| 27 |
+
arg3 = base3 ** exp3
|
| 28 |
+
l1, l2, l3 = exp1, exp2, exp3
|
| 29 |
+
|
| 30 |
+
base4 = rd.choice([2, 3, 5, 7, 10])
|
| 31 |
+
base5 = rd.choice([b for b in [2, 3, 4, 5, 6, 7, 10] if b != base4])
|
| 32 |
+
l4, l5 = 1, 0
|
| 33 |
+
|
| 34 |
+
globals()
|
| 35 |
+
````
|
| 36 |
+
|
| 37 |
+
:::::{question}
|
| 38 |
+
:questionType: FGQ
|
| 39 |
+
:questionId: 0
|
| 40 |
+
:questionIndex: 0
|
| 41 |
+
:solution: [["ord","{{ l1 }}","{{ l2 }}","{{ l3 }}"],["0","0","0"]]
|
| 42 |
+
|
| 43 |
+
::::{questionStatement}
|
| 44 |
+
{fr}`Évaluer`{en}`Evaluate` $\log_{ {{ base1 }} }({{ arg1 }})$, $\log_{ {{ base2 }} }({{ arg2 }})$ {fr}`et`{en}`and` $\log_{ {{ base3 }} }({{ arg3 }})$ {fr}`en reconnaissant chaque argument comme une puissance de la base.`{en}`by recognizing each argument as a power of the base.`
|
| 45 |
+
|
| 46 |
+
$\log_{ {{ base1 }} }({{ arg1 }}) =$ {input}`||70`
|
| 47 |
+
|
| 48 |
+
$\log_{ {{ base2 }} }({{ arg2 }}) =$ {input}`||70`
|
| 49 |
+
|
| 50 |
+
$\log_{ {{ base3 }} }({{ arg3 }}) =$ {input}`||70`
|
| 51 |
+
::::
|
| 52 |
+
|
| 53 |
+
::::{questionHint}
|
| 54 |
+
${{ arg1 }}={{ base1 }}^{ {{ exp1 }} }$, ${{ arg2 }}={{ base2 }}^{ {{ exp2 }} }$, ${{ arg3 }}={{ base3 }}^{ {{ exp3 }} }$ ; {fr}`utiliser`{en}`use` $\log_{b}(b^{k})=k$.
|
| 55 |
+
::::
|
| 56 |
+
|
| 57 |
+
::::{displayedSolution}
|
| 58 |
+
$\log_{ {{ base1 }} }({{ arg1 }}) = {{ l1 }}$
|
| 59 |
+
|
| 60 |
+
$\log_{ {{ base2 }} }({{ arg2 }}) = {{ l2 }}$
|
| 61 |
+
|
| 62 |
+
$\log_{ {{ base3 }} }({{ arg3 }}) = {{ l3 }}$
|
| 63 |
+
::::
|
| 64 |
+
|
| 65 |
+
::::{detailedSolution}
|
| 66 |
+
{fr}`Par la relation réciproque`{en}`By the reciprocal relation` $\log_{b}(b^{k})=k$ :
|
| 67 |
+
|
| 68 |
+
\begin{equation*}
|
| 69 |
+
\log_{ {{ base1 }} }({{ arg1 }}) &= \log_{ {{ base1 }} }({{ base1 }}^{ {{ exp1 }} }) \\
|
| 70 |
+
&= {{ l1 }}, \\
|
| 71 |
+
\log_{ {{ base2 }} }({{ arg2 }}) &= \log_{ {{ base2 }} }({{ base2 }}^{ {{ exp2 }} }) \\
|
| 72 |
+
&= {{ l2 }}, \\
|
| 73 |
+
\log_{ {{ base3 }} }({{ arg3 }}) &= \log_{ {{ base3 }} }({{ base3 }}^{ {{ exp3 }} }) \\
|
| 74 |
+
&= {{ l3 }}.
|
| 75 |
+
\end{equation*}
|
| 76 |
+
::::
|
| 77 |
+
|
| 78 |
+
::::{weightDistribution}
|
| 79 |
+
:logic: 15
|
| 80 |
+
:abstraction: 25
|
| 81 |
+
:reasoning: 30
|
| 82 |
+
:calculation: 30
|
| 83 |
+
::::
|
| 84 |
+
:::::
|
| 85 |
+
|
| 86 |
+
:::::{question}
|
| 87 |
+
:questionType: FGQ
|
| 88 |
+
:questionId: 1
|
| 89 |
+
:questionIndex: 1
|
| 90 |
+
:solution: [["ord","{{ l4 }}","{{ l5 }}"],["0","0"]]
|
| 91 |
+
|
| 92 |
+
::::{questionStatement}
|
| 93 |
+
{fr}`Évaluer`{en}`Evaluate` $\log_{ {{ base4 }} }({{ base4 }})$ {fr}`et`{en}`and` $\log_{ {{ base5 }} }(1)$, {fr}`et justifier à partir des cas`{en}`and justify from the cases` $b^1=b$ {fr}`et`{en}`and` $b^0=1$.
|
| 94 |
+
|
| 95 |
+
$\log_{ {{ base4 }} }({{ base4 }}) =$ {input}`||70`
|
| 96 |
+
|
| 97 |
+
$\log_{ {{ base5 }} }(1) =$ {input}`||70`
|
| 98 |
+
::::
|
| 99 |
+
|
| 100 |
+
::::{questionHint}
|
| 101 |
+
${{ base4 }}={{ base4 }}^1$ {fr}`et`{en}`and` $1={{ base5 }}^0$.
|
| 102 |
+
::::
|
| 103 |
+
|
| 104 |
+
::::{displayedSolution}
|
| 105 |
+
$\log_{ {{ base4 }} }({{ base4 }}) = {{ l4 }}$
|
| 106 |
+
|
| 107 |
+
$\log_{ {{ base5 }} }(1) = {{ l5 }}$
|
| 108 |
+
::::
|
| 109 |
+
|
| 110 |
+
::::{detailedSolution}
|
| 111 |
+
{fr}`On a`{en}`We have` ${{ base4 }}={{ base4 }}^1$ {fr}`et`{en}`and` $1={{ base5 }}^0$, {fr}`donc :`{en}`so:`
|
| 112 |
+
|
| 113 |
+
\begin{equation*}
|
| 114 |
+
\log_{ {{ base4 }} }({{ base4 }}) &= \log_{ {{ base4 }} }({{ base4 }}^1) \\
|
| 115 |
+
&= {{ l4 }}, \\
|
| 116 |
+
\log_{ {{ base5 }} }(1) &= \log_{ {{ base5 }} }({{ base5 }}^0) \\
|
| 117 |
+
&= {{ l5 }}.
|
| 118 |
+
\end{equation*}
|
| 119 |
+
|
| 120 |
+
{fr}`En particulier`{en}`In particular` $\log_{b}(1)=0$ {fr}`pour toute base : le graphe d'un logarithme passe toujours par`{en}`for any base: the graph of a logarithm always passes through` $(1,0)$.
|
| 121 |
+
::::
|
| 122 |
+
|
| 123 |
+
::::{weightDistribution}
|
| 124 |
+
:logic: 20
|
| 125 |
+
:abstraction: 30
|
| 126 |
+
:reasoning: 30
|
| 127 |
+
:calculation: 20
|
| 128 |
+
::::
|
| 129 |
+
:::::
|
| 130 |
+
|
| 131 |
+
`````
|
app/knowledge/fewshots/qat.md
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
`````{exercise}
|
| 2 |
+
:id:
|
| 3 |
+
:title: {fr}`Exemple QAT — racines d'un trinôme`{en}`QAT example — roots of a quadratic`
|
| 4 |
+
:modules:
|
| 5 |
+
:recommendedExecutionTime: 4
|
| 6 |
+
:level: Elementary
|
| 7 |
+
:chap:
|
| 8 |
+
:involvedConcepts:
|
| 9 |
+
:originalSource:
|
| 10 |
+
:visibility: All
|
| 11 |
+
|
| 12 |
+
````{python}
|
| 13 |
+
import random as rd
|
| 14 |
+
from sympy import symbols, latex, Poly
|
| 15 |
+
x = symbols('x')
|
| 16 |
+
|
| 17 |
+
r1 = rd.randint(-6, -1) # plus petite racine (entière)
|
| 18 |
+
r2 = rd.randint(1, 6) # plus grande racine (entière)
|
| 19 |
+
p = -(r1 + r2)
|
| 20 |
+
q = r1*r2
|
| 21 |
+
polyAff = latex(Poly(x**2 + p*x + q, x).as_expr())
|
| 22 |
+
r1Aff, r2Aff = str(r1), str(r2)
|
| 23 |
+
|
| 24 |
+
globals()
|
| 25 |
+
````
|
| 26 |
+
|
| 27 |
+
:::::{question}
|
| 28 |
+
:questionType: FGQ
|
| 29 |
+
:questionId: 0
|
| 30 |
+
:questionIndex: 0
|
| 31 |
+
:solution: [["ord","${{r1Aff}}$","${{r2Aff}}$"],["0","0"]]
|
| 32 |
+
|
| 33 |
+
::::{questionStatement}
|
| 34 |
+
{fr}`Résoudre l'équation suivante, en donnant les racines de la plus petite à la plus grande :`{en}`Solve the following equation, giving the roots from smallest to largest:`
|
| 35 |
+
\begin{equation*}
|
| 36 |
+
{{polyAff}} = 0.
|
| 37 |
+
\end{equation*}
|
| 38 |
+
|
| 39 |
+
{fr}`Plus petite racine :`{en}`Smallest root:` {input}`||90` $\qquad$ {fr}`plus grande racine :`{en}`largest root:` {input}`||90`
|
| 40 |
+
::::
|
| 41 |
+
|
| 42 |
+
::::{questionHint}
|
| 43 |
+
{fr}`Cherchez deux entiers dont la somme et le produit correspondent aux coefficients.`{en}`Look for two integers whose sum and product match the coefficients.`
|
| 44 |
+
::::
|
| 45 |
+
|
| 46 |
+
::::{displayedSolution}
|
| 47 |
+
$x_1 = {{r1Aff}}$, $\quad x_2 = {{r2Aff}}$
|
| 48 |
+
::::
|
| 49 |
+
|
| 50 |
+
::::{detailedSolution}
|
| 51 |
+
{fr}`On factorise`{en}`Factoring` ${{polyAff}} = (x - ({{r1Aff}}))(x - ({{r2Aff}}))$, {fr}`d'où les racines`{en}`hence the roots` ${}{{r1Aff}}$ {fr}`et`{en}`and` ${}{{r2Aff}}$.
|
| 52 |
+
::::
|
| 53 |
+
|
| 54 |
+
::::{weightDistribution}
|
| 55 |
+
:logic: 15
|
| 56 |
+
:abstraction: 15
|
| 57 |
+
:reasoning: 20
|
| 58 |
+
:calculation: 50
|
| 59 |
+
::::
|
| 60 |
+
:::::
|
| 61 |
+
|
| 62 |
+
`````
|
app/knowledge/fewshots/qcm.md
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
`````{exercise}
|
| 2 |
+
:id:
|
| 3 |
+
:title: {fr}`Exemple QCM — dérivée d'un monôme`{en}`MCQ example — derivative of a monomial`
|
| 4 |
+
:modules:
|
| 5 |
+
:recommendedExecutionTime: 3
|
| 6 |
+
:level: Elementary
|
| 7 |
+
:chap:
|
| 8 |
+
:involvedConcepts:
|
| 9 |
+
:originalSource:
|
| 10 |
+
:visibility: All
|
| 11 |
+
|
| 12 |
+
````{python}
|
| 13 |
+
import random as rd
|
| 14 |
+
from sympy import symbols, diff, latex
|
| 15 |
+
x = symbols('x')
|
| 16 |
+
|
| 17 |
+
a = rd.randint(2, 9) # coefficient (>= 2, jamais 1)
|
| 18 |
+
n = rd.choice([k for k in range(2, 7) if k != a]) # exposant >= 2, ET n != a
|
| 19 |
+
f = a*x**n
|
| 20 |
+
fp = diff(f, x) # bonne réponse : n a x^(n-1)
|
| 21 |
+
|
| 22 |
+
fAff = latex(f)
|
| 23 |
+
correctAff = latex(fp)
|
| 24 |
+
d1Aff = latex(a*x**(n-1)) # oubli du facteur n (dérivée)
|
| 25 |
+
d2Aff = latex(a*n*x**n) # exposant non décrémenté
|
| 26 |
+
d3Aff = latex(n*x**(n-1)) # oubli du coefficient a
|
| 27 |
+
# distincts par construction : a != n garantit d1 != d3 (sinon a x^(n-1) == n x^(n-1)),
|
| 28 |
+
# a,n >= 2 garantit correct != d1/d2/d3 — collision impossible sur toute graine.
|
| 29 |
+
|
| 30 |
+
globals()
|
| 31 |
+
````
|
| 32 |
+
|
| 33 |
+
{fr}`Soit la fonction`{en}`Let the function` $f(x) = {{fAff}}$.
|
| 34 |
+
|
| 35 |
+
:::::{question}
|
| 36 |
+
:questionType: MCQ
|
| 37 |
+
:questionId: 0
|
| 38 |
+
:questionIndex: 0
|
| 39 |
+
|
| 40 |
+
::::{questionStatement}
|
| 41 |
+
{fr}`Quelle est la dérivée`{en}`What is the derivative` $f'(x)$ ?
|
| 42 |
+
::::
|
| 43 |
+
|
| 44 |
+
::::{questionHint}
|
| 45 |
+
{fr}`Règle de la puissance :`{en}`Power rule:` $\dfrac{d}{dx}\left(x^{p}\right) = p\,x^{p-1}$.
|
| 46 |
+
::::
|
| 47 |
+
|
| 48 |
+
::::{mcqAnswer}
|
| 49 |
+
:isRightAnswer: true
|
| 50 |
+
$f'(x) = {{correctAff}}$
|
| 51 |
+
::::
|
| 52 |
+
|
| 53 |
+
::::{mcqAnswer}
|
| 54 |
+
:isRightAnswer: false
|
| 55 |
+
$f'(x) = {{d1Aff}}$
|
| 56 |
+
::::
|
| 57 |
+
|
| 58 |
+
::::{mcqAnswer}
|
| 59 |
+
:isRightAnswer: false
|
| 60 |
+
$f'(x) = {{d2Aff}}$
|
| 61 |
+
::::
|
| 62 |
+
|
| 63 |
+
::::{mcqAnswer}
|
| 64 |
+
:isRightAnswer: false
|
| 65 |
+
$f'(x) = {{d3Aff}}$
|
| 66 |
+
::::
|
| 67 |
+
|
| 68 |
+
::::{mcqAnswer}
|
| 69 |
+
:isRightAnswer: false
|
| 70 |
+
{fr}`Aucune de ces réponses n'est correcte`{en}`None of these answers are correct`
|
| 71 |
+
::::
|
| 72 |
+
|
| 73 |
+
::::{detailedSolution}
|
| 74 |
+
{fr}`Par la règle de la puissance,`{en}`By the power rule,` $\dfrac{d}{dx}\left(a x^{n}\right) = n\,a\,x^{n-1}$, {fr}`donc`{en}`so` $f'(x) = {{correctAff}}$.
|
| 75 |
+
::::
|
| 76 |
+
|
| 77 |
+
::::{weightDistribution}
|
| 78 |
+
:logic: 20
|
| 79 |
+
:abstraction: 20
|
| 80 |
+
:reasoning: 20
|
| 81 |
+
:calculation: 40
|
| 82 |
+
::::
|
| 83 |
+
:::::
|
| 84 |
+
|
| 85 |
+
`````
|
app/pipeline/fewshots.py
CHANGED
|
@@ -79,3 +79,16 @@ def fewshot_for(analysis: dict) -> str:
|
|
| 79 |
shot = load_fewshot(key)
|
| 80 |
logger.info("Few-shot sélectionné : %s (%d caractères)", key, len(shot))
|
| 81 |
return shot or "(aucun exemple canonique disponible pour ce type)"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 79 |
shot = load_fewshot(key)
|
| 80 |
logger.info("Few-shot sélectionné : %s (%d caractères)", key, len(shot))
|
| 81 |
return shot or "(aucun exemple canonique disponible pour ce type)"
|
| 82 |
+
|
| 83 |
+
|
| 84 |
+
@lru_cache(maxsize=None)
|
| 85 |
+
def fewshot_for_declinaison(decl_type: str) -> str:
|
| 86 |
+
"""Exemple canonique COMPLET pour une déclinaison (qcm|qat) — fichiers
|
| 87 |
+
stricts (conventions corpus 222) calibrés sur les 33 exemples validés de
|
| 88 |
+
fewshots/declinaisons/. Complet (pas élagué) : la structure mcqAnswer /
|
| 89 |
+
:solution:/displayedSolution est le cœur de ce qu'il faut imiter."""
|
| 90 |
+
path = FEWSHOTS_DIR / f"{decl_type}.md"
|
| 91 |
+
if not path.exists():
|
| 92 |
+
logger.warning("Few-shot déclinaison absent : %s", path)
|
| 93 |
+
return "(aucun exemple canonique disponible)"
|
| 94 |
+
return path.read_text(encoding="utf-8")
|
app/pipeline/generate.py
CHANGED
|
@@ -21,7 +21,13 @@ from app.pipeline.postprocess import (
|
|
| 21 |
normalize_python_fences,
|
| 22 |
strip_fences,
|
| 23 |
)
|
| 24 |
-
from app.pipeline.prompts import
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 25 |
|
| 26 |
logger = logging.getLogger(__name__)
|
| 27 |
|
|
@@ -124,6 +130,7 @@ def build_exercise_metadata(
|
|
| 124 |
lists_of_notions: str,
|
| 125 |
analysis: dict | None = None,
|
| 126 |
level: str = "",
|
|
|
|
| 127 |
) -> str:
|
| 128 |
"""Construit TOUJOURS un en-tête `{exercise}` complet et bien formé
|
| 129 |
(5 backticks englobant tout l'exercice — le bloc Python à 4 backticks vient
|
|
@@ -142,6 +149,11 @@ def build_exercise_metadata(
|
|
| 142 |
src = _parse_source_options(metadata)
|
| 143 |
|
| 144 |
title = src.get("title") or analysis.get("exercise_title") or "Exercice"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 145 |
|
| 146 |
nb_q = analysis.get("nb_questions") or 0
|
| 147 |
try:
|
|
@@ -170,7 +182,12 @@ def build_exercise_metadata(
|
|
| 170 |
|
| 171 |
fence = "`" * EXERCISE_FENCE_BACKTICKS
|
| 172 |
lines = [f"{fence}{{exercise}}"]
|
| 173 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 174 |
return "\n".join(lines)
|
| 175 |
|
| 176 |
|
|
@@ -208,6 +225,10 @@ _LANG_DIRECTIVES = {
|
|
| 208 |
}
|
| 209 |
|
| 210 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 211 |
def generate_pair_blocks(
|
| 212 |
content: str,
|
| 213 |
exercise_header: str,
|
|
@@ -222,20 +243,37 @@ def generate_pair_blocks(
|
|
| 222 |
model_idx: int,
|
| 223 |
lang: str = "auto",
|
| 224 |
set_step: Optional[Callable[[str], None]] = None,
|
|
|
|
| 225 |
) -> list[str]:
|
| 226 |
-
"""Boucle de génération par paires (séquentielle). Retourne les blocs.
|
|
|
|
|
|
|
| 227 |
nb_questions = analysis.get("nb_questions", max(1, len(question_segments)))
|
| 228 |
lang_directive = _LANG_DIRECTIVES.get(lang, _LANG_DIRECTIVES["auto"])
|
| 229 |
-
|
| 230 |
-
|
| 231 |
-
|
| 232 |
-
|
| 233 |
-
|
| 234 |
-
|
| 235 |
-
|
| 236 |
-
|
| 237 |
-
|
| 238 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 239 |
|
| 240 |
generated: list[str] = []
|
| 241 |
|
|
@@ -244,7 +282,7 @@ def generate_pair_blocks(
|
|
| 244 |
if set_step:
|
| 245 |
set_step("Génération (bloc Python + toutes les questions)…")
|
| 246 |
raw = process_with_openrouter(
|
| 247 |
-
prompt=
|
| 248 |
content=content,
|
| 249 |
previous_blocks="(aucun — première génération)",
|
| 250 |
nb_current=nb_questions,
|
|
@@ -272,7 +310,7 @@ def generate_pair_blocks(
|
|
| 272 |
current_segment = enonce + "\n\n" + current_segment
|
| 273 |
|
| 274 |
raw_pair = process_with_openrouter(
|
| 275 |
-
prompt=
|
| 276 |
content=exercise_header,
|
| 277 |
previous_blocks=("\n\n".join(generated) if generated
|
| 278 |
else "(aucun — première paire)"),
|
|
|
|
| 21 |
normalize_python_fences,
|
| 22 |
strip_fences,
|
| 23 |
)
|
| 24 |
+
from app.pipeline.prompts import (
|
| 25 |
+
FGQ_SPEC,
|
| 26 |
+
MCQ_SPEC,
|
| 27 |
+
STEP_DECLINAISON_PROMPT,
|
| 28 |
+
STEP_PAIR_PROMPT,
|
| 29 |
+
SYSTEM_PROMPT,
|
| 30 |
+
)
|
| 31 |
|
| 32 |
logger = logging.getLogger(__name__)
|
| 33 |
|
|
|
|
| 130 |
lists_of_notions: str,
|
| 131 |
analysis: dict | None = None,
|
| 132 |
level: str = "",
|
| 133 |
+
decl_type: str | None = None,
|
| 134 |
) -> str:
|
| 135 |
"""Construit TOUJOURS un en-tête `{exercise}` complet et bien formé
|
| 136 |
(5 backticks englobant tout l'exercice — le bloc Python à 4 backticks vient
|
|
|
|
| 149 |
src = _parse_source_options(metadata)
|
| 150 |
|
| 151 |
title = src.get("title") or analysis.get("exercise_title") or "Exercice"
|
| 152 |
+
if decl_type:
|
| 153 |
+
# Suffixe de traçabilité (convention des exemples validés : « - MCQ »).
|
| 154 |
+
suffix = " - MCQ" if decl_type == "qcm" else " - QAT"
|
| 155 |
+
if not title.rstrip().endswith(suffix.strip()):
|
| 156 |
+
title = title.rstrip() + suffix
|
| 157 |
|
| 158 |
nb_q = analysis.get("nb_questions") or 0
|
| 159 |
try:
|
|
|
|
| 182 |
|
| 183 |
fence = "`" * EXERCISE_FENCE_BACKTICKS
|
| 184 |
lines = [f"{fence}{{exercise}}"]
|
| 185 |
+
for k in _HEADER_FIELDS:
|
| 186 |
+
lines.append(f":{k}: {values[k]}".rstrip())
|
| 187 |
+
# Déclinaison d'un exercice existant : tracer l'id source juste après :id:
|
| 188 |
+
# (convention des exemples validés : :originalExerciseId:).
|
| 189 |
+
if k == "id" and decl_type and src.get("id"):
|
| 190 |
+
lines.append(f":originalExerciseId: {src['id']}")
|
| 191 |
return "\n".join(lines)
|
| 192 |
|
| 193 |
|
|
|
|
| 225 |
}
|
| 226 |
|
| 227 |
|
| 228 |
+
_DECL_LABELS = {"qcm": "QCM (MCQ)", "qat": "QAT (FGQ)"}
|
| 229 |
+
_DECL_SPECS = {"qcm": MCQ_SPEC, "qat": FGQ_SPEC}
|
| 230 |
+
|
| 231 |
+
|
| 232 |
def generate_pair_blocks(
|
| 233 |
content: str,
|
| 234 |
exercise_header: str,
|
|
|
|
| 243 |
model_idx: int,
|
| 244 |
lang: str = "auto",
|
| 245 |
set_step: Optional[Callable[[str], None]] = None,
|
| 246 |
+
decl_type: Optional[str] = None,
|
| 247 |
) -> list[str]:
|
| 248 |
+
"""Boucle de génération par paires (séquentielle). Retourne les blocs.
|
| 249 |
+
`decl_type` (qcm|qat) bascule sur le prompt Déclinaisons — même mécanique
|
| 250 |
+
par paires, 1 question source → 1 question déclinée."""
|
| 251 |
nb_questions = analysis.get("nb_questions", max(1, len(question_segments)))
|
| 252 |
lang_directive = _LANG_DIRECTIVES.get(lang, _LANG_DIRECTIVES["auto"])
|
| 253 |
+
if decl_type:
|
| 254 |
+
prompt_tmpl = STEP_DECLINAISON_PROMPT
|
| 255 |
+
common = dict(
|
| 256 |
+
analysis=json.dumps(analysis, ensure_ascii=False, indent=2),
|
| 257 |
+
functions=functions_ctx or "Aucune fonction spécifique détectée.",
|
| 258 |
+
niveau=level or "non précisé",
|
| 259 |
+
fewshot=fewshot,
|
| 260 |
+
lang_directive=lang_directive,
|
| 261 |
+
nb_total=nb_questions,
|
| 262 |
+
decl_label=_DECL_LABELS[decl_type],
|
| 263 |
+
decl_spec=_DECL_SPECS[decl_type],
|
| 264 |
+
)
|
| 265 |
+
else:
|
| 266 |
+
prompt_tmpl = STEP_PAIR_PROMPT
|
| 267 |
+
common = dict(
|
| 268 |
+
analysis=json.dumps(analysis, ensure_ascii=False, indent=2),
|
| 269 |
+
functions=functions_ctx or "Aucune fonction spécifique détectée.",
|
| 270 |
+
niveau=level or "non précisé",
|
| 271 |
+
targeted_rules=targeted_rules_digest,
|
| 272 |
+
property_constraints=property_constraints_text,
|
| 273 |
+
fewshot=fewshot,
|
| 274 |
+
lang_directive=lang_directive,
|
| 275 |
+
nb_total=nb_questions,
|
| 276 |
+
)
|
| 277 |
|
| 278 |
generated: list[str] = []
|
| 279 |
|
|
|
|
| 282 |
if set_step:
|
| 283 |
set_step("Génération (bloc Python + toutes les questions)…")
|
| 284 |
raw = process_with_openrouter(
|
| 285 |
+
prompt=prompt_tmpl.format(
|
| 286 |
content=content,
|
| 287 |
previous_blocks="(aucun — première génération)",
|
| 288 |
nb_current=nb_questions,
|
|
|
|
| 310 |
current_segment = enonce + "\n\n" + current_segment
|
| 311 |
|
| 312 |
raw_pair = process_with_openrouter(
|
| 313 |
+
prompt=prompt_tmpl.format(
|
| 314 |
content=exercise_header,
|
| 315 |
previous_blocks=("\n\n".join(generated) if generated
|
| 316 |
else "(aucun — première paire)"),
|
app/pipeline/orchestrator.py
CHANGED
|
@@ -39,7 +39,7 @@ from app.rag.catalogue import catalogue_for
|
|
| 39 |
from app.pipeline import postprocess as pp
|
| 40 |
from app.pipeline.analyze import run_analysis_phase
|
| 41 |
from app.pipeline.audit import run_audit
|
| 42 |
-
from app.pipeline.fewshots import fewshot_for
|
| 43 |
from app.pipeline.generate import (
|
| 44 |
assemble_exercise,
|
| 45 |
build_exercise_metadata,
|
|
@@ -105,21 +105,31 @@ def run_exercise(
|
|
| 105 |
model_idx: int = 1,
|
| 106 |
lang: str = "fr",
|
| 107 |
set_step: Optional[Callable[[str], None]] = None,
|
|
|
|
|
|
|
| 108 |
) -> dict:
|
| 109 |
"""
|
| 110 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 111 |
exercise, pair_blocks, analysis, functions, notions, audit_patches,
|
| 112 |
warnings, harness {ok, summary, seeds}, lang {source, target, action},
|
| 113 |
-
cost {usd, eur, requests}, duration_s
|
| 114 |
"""
|
| 115 |
t0 = time.time()
|
| 116 |
cost_before = cost_snapshot()
|
| 117 |
_step = set_step or (lambda label: None)
|
| 118 |
|
| 119 |
-
# ── 1. Analyse + notions + RAG (parallèle) ────
|
| 120 |
-
|
| 121 |
-
|
| 122 |
-
|
|
|
|
|
|
|
|
|
|
| 123 |
|
| 124 |
step1_targets = [r for r in (analysis.get("target_rules") or []) if isinstance(r, str)]
|
| 125 |
target_rules = list(dict.fromkeys(TRUNK_RULES + step1_targets))
|
|
@@ -133,7 +143,8 @@ def run_exercise(
|
|
| 133 |
|
| 134 |
# ── 2. Génération par paires ─────────────────────────────────────────────
|
| 135 |
metadata, enonce, question_segments = split_original_questions(content)
|
| 136 |
-
exercise_header = build_exercise_metadata(metadata, lists_of_notions, analysis,
|
|
|
|
| 137 |
|
| 138 |
# Contexte fonctions = catalogue CURÉ (domaine détecté) + hits RAG FAISS.
|
| 139 |
# Le catalogue curé donne « quel helper pour quel besoin » + couvre les
|
|
@@ -145,6 +156,8 @@ def run_exercise(
|
|
| 145 |
if functions_ctx else "",
|
| 146 |
])) or "Aucune fonction spécifique détectée."
|
| 147 |
|
|
|
|
|
|
|
| 148 |
pair_blocks = generate_pair_blocks(
|
| 149 |
content=content,
|
| 150 |
exercise_header=exercise_header,
|
|
@@ -152,13 +165,14 @@ def run_exercise(
|
|
| 152 |
question_segments=question_segments,
|
| 153 |
analysis=analysis,
|
| 154 |
functions_ctx=functions_combined,
|
| 155 |
-
fewshot=
|
| 156 |
targeted_rules_digest=targeted_rules_digest,
|
| 157 |
property_constraints_text=property_constraints_text,
|
| 158 |
level=level,
|
| 159 |
model_idx=model_idx,
|
| 160 |
lang=lang,
|
| 161 |
set_step=_step,
|
|
|
|
| 162 |
)
|
| 163 |
|
| 164 |
# ── 3. Post-traitements déterministes ────────────────────────────────────
|
|
@@ -247,6 +261,39 @@ def run_exercise(
|
|
| 247 |
myst_exercise, dollar_patches = pp.fix_dollar_digit(myst_exercise)
|
| 248 |
audit_patches.extend(dollar_patches)
|
| 249 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 250 |
# Les warnings 6.1 du LLM deviennent du bruit une fois l'auto-lift passé.
|
| 251 |
if not pp.INJECTION_RE.search(myst_exercise) or not any(
|
| 252 |
"(" in tok or "**" in tok for tok in pp.INJECTION_RE.findall(myst_exercise)
|
|
@@ -363,12 +410,18 @@ def run_exercise(
|
|
| 363 |
candidate, _ = pp.auto_lift_injections(candidate)
|
| 364 |
candidate, _ = pp.rename_underscore_injections(candidate)
|
| 365 |
candidate, _ = pp.fix_dollar_digit(candidate)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 366 |
candidate, _ = pp.renumber_question_ids(candidate)
|
| 367 |
candidate_report = harness.validate_text(candidate, seeds=HARNESS_GATE_SEEDS)
|
| 368 |
|
| 369 |
def _badness(r: dict) -> int:
|
| 370 |
return (len(r["static_errors"]) + r["n_exec_errors"]
|
| 371 |
-
+ r["n_unresolved"] + r["n_forbidden"]
|
|
|
|
| 372 |
|
| 373 |
if candidate_report["ok"] or _badness(candidate_report) < _badness(report):
|
| 374 |
myst_exercise, report = candidate, candidate_report
|
|
@@ -401,6 +454,49 @@ def run_exercise(
|
|
| 401 |
"summary": harness.format_report(report),
|
| 402 |
},
|
| 403 |
"lang": lang_info,
|
|
|
|
| 404 |
"cost": cost_delta(cost_before),
|
| 405 |
"duration_s": round(time.time() - t0, 1),
|
| 406 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 39 |
from app.pipeline import postprocess as pp
|
| 40 |
from app.pipeline.analyze import run_analysis_phase
|
| 41 |
from app.pipeline.audit import run_audit
|
| 42 |
+
from app.pipeline.fewshots import fewshot_for, fewshot_for_declinaison
|
| 43 |
from app.pipeline.generate import (
|
| 44 |
assemble_exercise,
|
| 45 |
build_exercise_metadata,
|
|
|
|
| 105 |
model_idx: int = 1,
|
| 106 |
lang: str = "fr",
|
| 107 |
set_step: Optional[Callable[[str], None]] = None,
|
| 108 |
+
decl_type: Optional[str] = None,
|
| 109 |
+
shared_phase: Optional[tuple] = None,
|
| 110 |
) -> dict:
|
| 111 |
"""
|
| 112 |
+
Traite UN exercice. `decl_type=None` = pythonisation (flux historique) ;
|
| 113 |
+
`decl_type ∈ {"qcm","qat"}` = mode déclinaisons (même pipeline, prompt et
|
| 114 |
+
harnais étendus). `shared_phase` = résultat de run_analysis_phase à
|
| 115 |
+
RÉUTILISER (déclinaisons QCM+QAT d'une même source : une seule analyse).
|
| 116 |
+
|
| 117 |
+
Retourne le dict résultat (contrat UI) :
|
| 118 |
exercise, pair_blocks, analysis, functions, notions, audit_patches,
|
| 119 |
warnings, harness {ok, summary, seeds}, lang {source, target, action},
|
| 120 |
+
cost {usd, eur, requests}, duration_s [, decl_type]
|
| 121 |
"""
|
| 122 |
t0 = time.time()
|
| 123 |
cost_before = cost_snapshot()
|
| 124 |
_step = set_step or (lambda label: None)
|
| 125 |
|
| 126 |
+
# ── 1. Analyse + notions + RAG (parallèle ; partagée en mode QCM+QAT) ────
|
| 127 |
+
if shared_phase is not None:
|
| 128 |
+
analysis, notions_ctx, lists_of_notions, functions_ctx = shared_phase
|
| 129 |
+
else:
|
| 130 |
+
_step("Analyse + notions + catalogue RAG (en parallèle)…")
|
| 131 |
+
analysis, notions_ctx, lists_of_notions, functions_ctx = run_analysis_phase(
|
| 132 |
+
content, model_idx)
|
| 133 |
|
| 134 |
step1_targets = [r for r in (analysis.get("target_rules") or []) if isinstance(r, str)]
|
| 135 |
target_rules = list(dict.fromkeys(TRUNK_RULES + step1_targets))
|
|
|
|
| 143 |
|
| 144 |
# ── 2. Génération par paires ─────────────────────────────────────────────
|
| 145 |
metadata, enonce, question_segments = split_original_questions(content)
|
| 146 |
+
exercise_header = build_exercise_metadata(metadata, lists_of_notions, analysis,
|
| 147 |
+
level, decl_type=decl_type)
|
| 148 |
|
| 149 |
# Contexte fonctions = catalogue CURÉ (domaine détecté) + hits RAG FAISS.
|
| 150 |
# Le catalogue curé donne « quel helper pour quel besoin » + couvre les
|
|
|
|
| 156 |
if functions_ctx else "",
|
| 157 |
])) or "Aucune fonction spécifique détectée."
|
| 158 |
|
| 159 |
+
fewshot = (fewshot_for_declinaison(decl_type) if decl_type
|
| 160 |
+
else fewshot_for(analysis))
|
| 161 |
pair_blocks = generate_pair_blocks(
|
| 162 |
content=content,
|
| 163 |
exercise_header=exercise_header,
|
|
|
|
| 165 |
question_segments=question_segments,
|
| 166 |
analysis=analysis,
|
| 167 |
functions_ctx=functions_combined,
|
| 168 |
+
fewshot=fewshot,
|
| 169 |
targeted_rules_digest=targeted_rules_digest,
|
| 170 |
property_constraints_text=property_constraints_text,
|
| 171 |
level=level,
|
| 172 |
model_idx=model_idx,
|
| 173 |
lang=lang,
|
| 174 |
set_step=_step,
|
| 175 |
+
decl_type=decl_type,
|
| 176 |
)
|
| 177 |
|
| 178 |
# ── 3. Post-traitements déterministes ────────────────────────────────────
|
|
|
|
| 261 |
myst_exercise, dollar_patches = pp.fix_dollar_digit(myst_exercise)
|
| 262 |
audit_patches.extend(dollar_patches)
|
| 263 |
|
| 264 |
+
if decl_type:
|
| 265 |
+
# Filet : alias d'option MCQ mal nommés / :isRightAnswer: manquant
|
| 266 |
+
# (le repli MCQ en QAT est concerné aussi).
|
| 267 |
+
myst_exercise, alias_fixed = pp.fix_mcq_answer_aliases(myst_exercise)
|
| 268 |
+
if alias_fixed:
|
| 269 |
+
audit_patches.append({
|
| 270 |
+
"rule": "MCQ", "location": "(mcqOption / :isRightAnswer:)",
|
| 271 |
+
"fix": f"{alias_fixed} bloc(s) d'option normalisé(s)",
|
| 272 |
+
"message": "Blocs d'options MCQ normalisés (mcqOption→mcqAnswer, :isRightAnswer: false par défaut).",
|
| 273 |
+
"iteration": 0,
|
| 274 |
+
})
|
| 275 |
+
# Déclinaisons : UN SEUL bloc {python} — fusion des blocs additionnels
|
| 276 |
+
# sans re-tirage (re-tirage → laissé au harnais + réparation LLM).
|
| 277 |
+
myst_exercise, merged = pp.merge_decl_python_blocks(myst_exercise)
|
| 278 |
+
if merged:
|
| 279 |
+
audit_patches.append({
|
| 280 |
+
"rule": "3.1", "location": "(blocs python additionnels)",
|
| 281 |
+
"fix": f"{merged} bloc(s) fusionné(s) dans le bloc principal",
|
| 282 |
+
"message": "Déclinaison : blocs {python} additionnels fusionnés (un seul bloc, spec).",
|
| 283 |
+
"iteration": 0,
|
| 284 |
+
})
|
| 285 |
+
|
| 286 |
+
if decl_type == "qcm":
|
| 287 |
+
# Filet MCQ : l'option « None/Aucune » doit être le dernier mcqAnswer.
|
| 288 |
+
myst_exercise, none_moved = pp.fix_none_option_last(myst_exercise)
|
| 289 |
+
if none_moved:
|
| 290 |
+
audit_patches.append({
|
| 291 |
+
"rule": "MCQ", "location": "(option None)",
|
| 292 |
+
"fix": f"{none_moved} option(s) « None » déplacée(s) en dernier",
|
| 293 |
+
"message": "Option « Aucune de ces réponses / None » repositionnée en dernière position.",
|
| 294 |
+
"iteration": 0,
|
| 295 |
+
})
|
| 296 |
+
|
| 297 |
# Les warnings 6.1 du LLM deviennent du bruit une fois l'auto-lift passé.
|
| 298 |
if not pp.INJECTION_RE.search(myst_exercise) or not any(
|
| 299 |
"(" in tok or "**" in tok for tok in pp.INJECTION_RE.findall(myst_exercise)
|
|
|
|
| 410 |
candidate, _ = pp.auto_lift_injections(candidate)
|
| 411 |
candidate, _ = pp.rename_underscore_injections(candidate)
|
| 412 |
candidate, _ = pp.fix_dollar_digit(candidate)
|
| 413 |
+
if decl_type:
|
| 414 |
+
candidate, _ = pp.fix_mcq_answer_aliases(candidate)
|
| 415 |
+
candidate, _ = pp.merge_decl_python_blocks(candidate)
|
| 416 |
+
if decl_type == "qcm":
|
| 417 |
+
candidate, _ = pp.fix_none_option_last(candidate)
|
| 418 |
candidate, _ = pp.renumber_question_ids(candidate)
|
| 419 |
candidate_report = harness.validate_text(candidate, seeds=HARNESS_GATE_SEEDS)
|
| 420 |
|
| 421 |
def _badness(r: dict) -> int:
|
| 422 |
return (len(r["static_errors"]) + r["n_exec_errors"]
|
| 423 |
+
+ r["n_unresolved"] + r["n_forbidden"]
|
| 424 |
+
+ r.get("n_mcq_collisions", 0))
|
| 425 |
|
| 426 |
if candidate_report["ok"] or _badness(candidate_report) < _badness(report):
|
| 427 |
myst_exercise, report = candidate, candidate_report
|
|
|
|
| 454 |
"summary": harness.format_report(report),
|
| 455 |
},
|
| 456 |
"lang": lang_info,
|
| 457 |
+
"decl_type": decl_type,
|
| 458 |
"cost": cost_delta(cost_before),
|
| 459 |
"duration_s": round(time.time() - t0, 1),
|
| 460 |
}
|
| 461 |
+
|
| 462 |
+
|
| 463 |
+
def run_declinaisons(
|
| 464 |
+
content: str,
|
| 465 |
+
filename: str = "exercise.md",
|
| 466 |
+
level: str = "",
|
| 467 |
+
model_idx: int = 1,
|
| 468 |
+
lang: str = "fr",
|
| 469 |
+
types: Optional[list] = None,
|
| 470 |
+
set_step: Optional[Callable[[str], None]] = None,
|
| 471 |
+
) -> list[tuple[str, dict]]:
|
| 472 |
+
"""
|
| 473 |
+
Mode `declinaisons` : produit une déclinaison par type coché (qcm/qat).
|
| 474 |
+
L'analyse + notions + RAG sont calculées UNE SEULE fois et partagées entre
|
| 475 |
+
les types (aucun appel LLM redondant). Retourne [(decl_type, result), …].
|
| 476 |
+
Un échec sur un type n'empêche pas l'autre (l'appelant gère l'erreur).
|
| 477 |
+
"""
|
| 478 |
+
_step = set_step or (lambda label: None)
|
| 479 |
+
types = [t for t in (types or []) if t in ("qcm", "qat")] or ["qcm"]
|
| 480 |
+
|
| 481 |
+
_step("Analyse + notions + catalogue RAG (partagés QCM/QAT)…")
|
| 482 |
+
shared = run_analysis_phase(content, model_idx)
|
| 483 |
+
|
| 484 |
+
out: list[tuple[str, dict]] = []
|
| 485 |
+
for decl_type in types:
|
| 486 |
+
label = "QCM" if decl_type == "qcm" else "QAT"
|
| 487 |
+
|
| 488 |
+
def step_with_type(msg: str, _label=label):
|
| 489 |
+
_step(f"[{_label}] {msg}")
|
| 490 |
+
|
| 491 |
+
result = run_exercise(
|
| 492 |
+
content=content,
|
| 493 |
+
filename=filename,
|
| 494 |
+
level=level,
|
| 495 |
+
model_idx=model_idx,
|
| 496 |
+
lang=lang,
|
| 497 |
+
set_step=step_with_type,
|
| 498 |
+
decl_type=decl_type,
|
| 499 |
+
shared_phase=shared,
|
| 500 |
+
)
|
| 501 |
+
out.append((decl_type, result))
|
| 502 |
+
return out
|
app/pipeline/postprocess.py
CHANGED
|
@@ -324,8 +324,19 @@ def fix_dollar_digit(exercise: str) -> tuple[str, list[dict]]:
|
|
| 324 |
• `$` non échappé immédiatement suivi d'un chiffre → `${}` + chiffre
|
| 325 |
• `$` non échappé immédiatement suivi de `{{` → `${}{{`
|
| 326 |
Le groupe vide {} est invisible au rendu mais empêche la lecture
|
| 327 |
-
« montant en devise » qui désynchronise tout le `$…$`.
|
|
|
|
|
|
|
|
|
|
| 328 |
masked, blocks = mask_python_blocks(exercise)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 329 |
patches: list[dict] = []
|
| 330 |
|
| 331 |
def _digit(m: re.Match) -> str:
|
|
@@ -350,6 +361,8 @@ def fix_dollar_digit(exercise: str) -> tuple[str, list[dict]]:
|
|
| 350 |
|
| 351 |
masked = re.sub(r"(?<!\\)\$(?!\{)(\d)", _digit, masked)
|
| 352 |
masked = re.sub(r"(?<!\\)\$\{\{", _inj, masked)
|
|
|
|
|
|
|
| 353 |
return unmask_python_blocks(masked, blocks), patches
|
| 354 |
|
| 355 |
|
|
@@ -605,6 +618,102 @@ def check_hardcoded_decimals_in_solutions(exercise: str) -> list[dict]:
|
|
| 605 |
return warnings
|
| 606 |
|
| 607 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 608 |
# ─────────────────────────────────────────────────────────────────────────────
|
| 609 |
# Langues : détection, réduction bilingue → mono, contrôle des décimales
|
| 610 |
# ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
| 324 |
• `$` non échappé immédiatement suivi d'un chiffre → `${}` + chiffre
|
| 325 |
• `$` non échappé immédiatement suivi de `{{` → `${}{{`
|
| 326 |
Le groupe vide {} est invisible au rendu mais empêche la lecture
|
| 327 |
+
« montant en devise » qui désynchronise tout le `$…$`.
|
| 328 |
+
EXCLUSION : les lignes `:solution:` (FGQ) — c'est un motif de
|
| 329 |
+
correspondance de réponse, pas du texte affiché ; un `${}` y casserait
|
| 330 |
+
la correction automatique."""
|
| 331 |
masked, blocks = mask_python_blocks(exercise)
|
| 332 |
+
# Masque les lignes :solution: (elles ne doivent pas être réécrites).
|
| 333 |
+
sol_lines: list[str] = []
|
| 334 |
+
|
| 335 |
+
def _mask_sol(m: re.Match) -> str:
|
| 336 |
+
sol_lines.append(m.group(0))
|
| 337 |
+
return f"\x00SOLLINE{len(sol_lines) - 1}\x00"
|
| 338 |
+
|
| 339 |
+
masked = re.sub(r"(?m)^:solution:.*$", _mask_sol, masked)
|
| 340 |
patches: list[dict] = []
|
| 341 |
|
| 342 |
def _digit(m: re.Match) -> str:
|
|
|
|
| 361 |
|
| 362 |
masked = re.sub(r"(?<!\\)\$(?!\{)(\d)", _digit, masked)
|
| 363 |
masked = re.sub(r"(?<!\\)\$\{\{", _inj, masked)
|
| 364 |
+
for i, line in enumerate(sol_lines):
|
| 365 |
+
masked = masked.replace(f"\x00SOLLINE{i}\x00", line)
|
| 366 |
return unmask_python_blocks(masked, blocks), patches
|
| 367 |
|
| 368 |
|
|
|
|
| 618 |
return warnings
|
| 619 |
|
| 620 |
|
| 621 |
+
# ─────────────────────────────────────────────────────────────────────────────
|
| 622 |
+
# Déclinaisons QCM/QAT — filets déterministes (mode `declinaisons`)
|
| 623 |
+
# ─────────────────────────────────────────────────────────────────────────────
|
| 624 |
+
|
| 625 |
+
QUESTION_BLOCK_RE = _QUESTION_BLOCK_RE # réutilisé par le harnais étendu
|
| 626 |
+
MCQ_ANSWER_RE = re.compile(
|
| 627 |
+
r"::::\{mcqAnswer\}\s*\n:isRightAnswer:\s*(true|false)\s*\n(.*?)\n::::",
|
| 628 |
+
re.DOTALL,
|
| 629 |
+
)
|
| 630 |
+
_NONE_OPTION_RE = re.compile(r"Aucune de ces réponses|None of these answers", re.IGNORECASE)
|
| 631 |
+
|
| 632 |
+
|
| 633 |
+
def fix_none_option_last(exercise: str) -> tuple[str, int]:
|
| 634 |
+
"""MCQ : l'option « Aucune de ces réponses / None » doit être le DERNIER
|
| 635 |
+
mcqAnswer de sa question — on la déplace si besoin (filet déterministe)."""
|
| 636 |
+
moved = {"n": 0}
|
| 637 |
+
|
| 638 |
+
def _fix_question(qm: re.Match) -> str:
|
| 639 |
+
block = qm.group(0)
|
| 640 |
+
answers = list(MCQ_ANSWER_RE.finditer(block))
|
| 641 |
+
if len(answers) < 2:
|
| 642 |
+
return block
|
| 643 |
+
none_idx = [i for i, a in enumerate(answers) if _NONE_OPTION_RE.search(a.group(2))]
|
| 644 |
+
if not none_idx or none_idx[-1] == len(answers) - 1:
|
| 645 |
+
return block
|
| 646 |
+
i = none_idx[-1]
|
| 647 |
+
none_text = answers[i].group(0)
|
| 648 |
+
# retire l'option None puis la réinsère après le dernier mcqAnswer
|
| 649 |
+
block2 = block.replace(none_text + "\n\n", "", 1).replace(none_text, "", 1)
|
| 650 |
+
last = list(MCQ_ANSWER_RE.finditer(block2))[-1]
|
| 651 |
+
block2 = block2[:last.end()] + "\n\n" + none_text + block2[last.end():]
|
| 652 |
+
moved["n"] += 1
|
| 653 |
+
return block2
|
| 654 |
+
|
| 655 |
+
new = QUESTION_BLOCK_RE.sub(_fix_question, exercise)
|
| 656 |
+
return new, moved["n"]
|
| 657 |
+
|
| 658 |
+
|
| 659 |
+
def fix_mcq_answer_aliases(exercise: str) -> tuple[str, int]:
|
| 660 |
+
"""Normalise les blocs d'options MCQ mal nommés/incomplets (vus en prod) :
|
| 661 |
+
• `::::{mcqOption}` / `::::{mcqChoice}` → `::::{mcqAnswer}` ;
|
| 662 |
+
• bloc mcqAnswer SANS ligne `:isRightAnswer:` → insère `false` (défaut)."""
|
| 663 |
+
fixed = {"n": 0}
|
| 664 |
+
out, n_alias = re.subn(r"(?m)^::::\{mcq(?:Option|Choice)\}", "::::{mcqAnswer}", exercise)
|
| 665 |
+
fixed["n"] += n_alias
|
| 666 |
+
|
| 667 |
+
def _ensure_flag(m: re.Match) -> str:
|
| 668 |
+
head, rest = m.group(1), m.group(2)
|
| 669 |
+
if rest.lstrip().startswith(":isRightAnswer:"):
|
| 670 |
+
return m.group(0)
|
| 671 |
+
fixed["n"] += 1
|
| 672 |
+
return f"{head}:isRightAnswer: false\n{rest}"
|
| 673 |
+
|
| 674 |
+
out = re.sub(r"(?ms)^(::::\{mcqAnswer\}\n)(.*?)(?=^::::$)",
|
| 675 |
+
lambda m: _ensure_flag(m), out)
|
| 676 |
+
return out, fixed["n"]
|
| 677 |
+
|
| 678 |
+
|
| 679 |
+
def merge_decl_python_blocks(exercise: str) -> tuple[str, int]:
|
| 680 |
+
"""DÉCLINAISONS : la spec impose UN SEUL bloc {python}. Fusionne les blocs
|
| 681 |
+
additionnels dans le bloc principal (ordre préservé, un seul `globals()`
|
| 682 |
+
final) — SAUF si un bloc ultérieur RE-TIRE de l'aléatoire (`rd.`/`random.`) :
|
| 683 |
+
re-tirage = bug sémantique (valeurs incohérentes entre questions), on le
|
| 684 |
+
laisse en place pour que le harnais le signale et que la réparation LLM
|
| 685 |
+
corrige à la source."""
|
| 686 |
+
blocks = list(PYTHON_FENCE_RE.finditer(exercise))
|
| 687 |
+
if len(blocks) < 2:
|
| 688 |
+
return exercise, 0
|
| 689 |
+
extras = blocks[1:]
|
| 690 |
+
if any(re.search(r"\brd\.|(?<!_)\brandom\.", m.group("code")) for m in extras):
|
| 691 |
+
return exercise, 0 # re-tirage suspect → laisser le harnais trancher
|
| 692 |
+
|
| 693 |
+
main = blocks[0]
|
| 694 |
+
parts = [re.sub(r"(?m)^\s*globals\(\)\s*$", "", main.group("code")).rstrip()]
|
| 695 |
+
for m in extras:
|
| 696 |
+
code = re.sub(r"(?m)^\s*globals\(\)\s*$", "", m.group("code")).strip()
|
| 697 |
+
if code:
|
| 698 |
+
parts.append(code)
|
| 699 |
+
merged_code = "\n\n".join(parts) + "\n\nglobals()"
|
| 700 |
+
|
| 701 |
+
# Supprime les blocs additionnels (indices inversés), puis remplace le principal.
|
| 702 |
+
out = exercise
|
| 703 |
+
for m in reversed(extras):
|
| 704 |
+
out = out[:m.start()] + out[m.end():]
|
| 705 |
+
m0 = PYTHON_FENCE_RE.search(out)
|
| 706 |
+
out = (out[:m0.start()]
|
| 707 |
+
+ f"{m0.group('open')}{{python}}\n{merged_code}\n{m0.group('open')}"
|
| 708 |
+
+ out[m0.end():])
|
| 709 |
+
return re.sub(r"\n{3,}", "\n\n", out), len(extras)
|
| 710 |
+
|
| 711 |
+
|
| 712 |
+
# (Les contrôles statiques MCQ/FGQ — {{}} dans un rôle, :solution:, résidus
|
| 713 |
+
# légacy — vivent dans validation/harness.py::check_declinaison_static :
|
| 714 |
+
# une seule source de vérité pour le verdict.)
|
| 715 |
+
|
| 716 |
+
|
| 717 |
# ─────────────────────────────────────────────────────────────────────────────
|
| 718 |
# Langues : détection, réduction bilingue → mono, contrôle des décimales
|
| 719 |
# ─────────────────────────────────────────────────────────────────────────────
|
app/pipeline/prompts.py
CHANGED
|
@@ -523,6 +523,279 @@ TRANSLATE_FORMAT_BOTH = (
|
|
| 523 |
)
|
| 524 |
|
| 525 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 526 |
# ─────────────────────────────────────────────────────────────────────────────
|
| 527 |
# Réparation post-harnais (NOUVEAU — 1 itération max)
|
| 528 |
# ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -554,9 +827,21 @@ RÈGLES DE CORRECTION :
|
|
| 554 |
TIRAGE DÉGÉNÉRÉ — exclure la valeur fautive à la source
|
| 555 |
(ex. `b = rd.randint(2, 5)` au lieu de `randint(1, 5)`, ou boucle de rejet
|
| 556 |
`if b == 1: continue`). Ne PAS rafistoler le texte : corriger le tirage.
|
|
|
|
|
|
|
|
|
|
|
|
|
| 557 |
• Injection non nue → pré-calculer en variable camelCase `…Aff`.
|
| 558 |
• Le bloc {{python}} reste à 4 backticks et se termine par `globals()`.
|
| 559 |
• Ne touche NI à `\\inftys`/`\\ds`/`\\dfrac`, NI à la prose des solutions.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 560 |
|
| 561 |
Réponds UNIQUEMENT avec l'exercice complet corrigé (de `````{{exercise}} à `````),
|
| 562 |
sans préambule ni wrapper markdown.
|
|
|
|
| 523 |
)
|
| 524 |
|
| 525 |
|
| 526 |
+
# ─────────────────────────────────────────────────────────────────────────────
|
| 527 |
+
# DÉCLINAISONS QCM / QAT (NOUVEAU 2026-07 — mode `declinaisons`)
|
| 528 |
+
# Spécification normative fournie par l'équipe (PROMPT_declinaisons_QCM_QAT §4-§6),
|
| 529 |
+
# calibrée sur les 33 exemples validés de knowledge/fewshots/declinaisons/.
|
| 530 |
+
# Divergences wx ↔ conventions strictes (corpus 222) tranchées : format
|
| 531 |
+
# plateforme des wx (mcqAnswer/:solution:/{input}) + conventions strictes de
|
| 532 |
+
# l'app (fences 4, globals(), injections nues camelCase Aff, IDs contigus).
|
| 533 |
+
# ─────────────────────────────────────────────────────────────────────────────
|
| 534 |
+
|
| 535 |
+
MCQ_SPEC = """\
|
| 536 |
+
FORMAT QCM (questionType MCQ) — NORMATIF :
|
| 537 |
+
|
| 538 |
+
:::::{question}
|
| 539 |
+
:questionType: MCQ
|
| 540 |
+
:questionId: N
|
| 541 |
+
:questionIndex: N
|
| 542 |
+
|
| 543 |
+
::::{questionStatement}
|
| 544 |
+
<énoncé auto-suffisant ; maths et {{ }} HORS des rôles bilingues>
|
| 545 |
+
::::
|
| 546 |
+
|
| 547 |
+
::::{questionHint}
|
| 548 |
+
<indice — peut rester vide ; VERBATIM si repris de la source>
|
| 549 |
+
::::
|
| 550 |
+
|
| 551 |
+
::::{mcqAnswer}
|
| 552 |
+
:isRightAnswer: true
|
| 553 |
+
<BONNE réponse — TOUJOURS en slot 1>
|
| 554 |
+
::::
|
| 555 |
+
|
| 556 |
+
::::{mcqAnswer}
|
| 557 |
+
:isRightAnswer: false
|
| 558 |
+
<distracteur 1 — erreur type>
|
| 559 |
+
::::
|
| 560 |
+
|
| 561 |
+
::::{mcqAnswer}
|
| 562 |
+
:isRightAnswer: false
|
| 563 |
+
<distracteur 2 — erreur type>
|
| 564 |
+
::::
|
| 565 |
+
|
| 566 |
+
::::{mcqAnswer}
|
| 567 |
+
:isRightAnswer: false
|
| 568 |
+
<distracteur 3 — erreur type>
|
| 569 |
+
::::
|
| 570 |
+
|
| 571 |
+
::::{mcqAnswer}
|
| 572 |
+
:isRightAnswer: false
|
| 573 |
+
{fr}`Aucune de ces réponses n'est correcte`{en}`None of these answers are correct`
|
| 574 |
+
::::
|
| 575 |
+
|
| 576 |
+
::::{detailedSolution}
|
| 577 |
+
<solution détaillée ; VERBATIM de la source si elle existe, sinon rédigée>
|
| 578 |
+
::::
|
| 579 |
+
|
| 580 |
+
::::{weightDistribution}
|
| 581 |
+
:logic: 25
|
| 582 |
+
:abstraction: 25
|
| 583 |
+
:reasoning: 25
|
| 584 |
+
:calculation: 25
|
| 585 |
+
::::
|
| 586 |
+
:::::
|
| 587 |
+
|
| 588 |
+
RÈGLES MCQ DURES :
|
| 589 |
+
• EXACTEMENT UNE option `:isRightAnswer: true`, EN SLOT 1 (l'affichage est
|
| 590 |
+
mélangé côté plateforme — ne PAS randomiser l'ordre dans le fichier).
|
| 591 |
+
• 5 options par défaut (1 correcte + 3 distracteurs + « None » EN DERNIER).
|
| 592 |
+
Ensembles fermés (vrai/faux, intervalles exhaustifs) : « None » omis
|
| 593 |
+
autorisé (⇒ 4 options ; jamais moins de 3).
|
| 594 |
+
• PAS de `:solution:`, PAS de `{input}`, PAS de displayedSolution en MCQ.
|
| 595 |
+
• Ordre des blocs : questionStatement → questionHint → mcqAnswer×N →
|
| 596 |
+
detailedSolution → weightDistribution.
|
| 597 |
+
• L'option « None » et toute option textuelle sont bilingues si la cible l'est.
|
| 598 |
+
|
| 599 |
+
DISTRACTEURS — ERREURS TYPES UNIQUEMENT (aucune valeur au hasard) :
|
| 600 |
+
• Algèbre : erreur de signe ; coefficient inversé ; terme oublié ; distribution partielle.
|
| 601 |
+
• Dérivées : oubli du facteur de la règle de chaîne ; exposant non décrémenté ;
|
| 602 |
+
primitive au lieu de dérivée ; quotient sans v² ; produit sans la 2e moitié.
|
| 603 |
+
• Intégrales : oubli du +1 sur l'exposant ; oubli du 1/a ; dérivée au lieu de
|
| 604 |
+
primitive ; oubli de |x| dans ln.
|
| 605 |
+
• Matrices : transposée ; colonnes/lignes échangées ; entrée non dérivée ;
|
| 606 |
+
matrice opposée ; oubli du terme +b.
|
| 607 |
+
• Compositions affines : oubli de b_f ; ordre inversé (g∘f vs f∘g) ; signe opposé.
|
| 608 |
+
• Limites : mauvaise forme indéterminée ; mauvais signe d'infini.
|
| 609 |
+
|
| 610 |
+
ANTI-COLLISION (le piège n°1 des QCM randomisés — un distracteur peut devenir
|
| 611 |
+
ÉGAL à la bonne réponse sur certaines graines) — dans CET ordre :
|
| 612 |
+
1. DISTINCT PAR CONSTRUCTION (préféré) : distracteurs de type différent,
|
| 613 |
+
tirages qui garantissent la non-nullité/non-égalité (coefficients >= 2,
|
| 614 |
+
exposants >= 2, entrées non nulles…). Documente-le en commentaire Python.
|
| 615 |
+
2. TIRAGE AVEC REJET dans le bloc Python : reboucler tant que les chaînes
|
| 616 |
+
RENDUES (latex) de toutes les options ne sont pas toutes distinctes.
|
| 617 |
+
3. Le harnais vérifie l'unicité sur 100 graines — un doublon = REJET.
|
| 618 |
+
⚠️ La collision est aussi SÉMANTIQUE : deux options formulées différemment
|
| 619 |
+
mais mathématiquement ÉQUIVALENTES (« divise x par 2 » ≡ « multiplie x par
|
| 620 |
+
1/2 » ; « T_{1/a,1/b} » ≡ « division par a et b ») comptent comme un doublon
|
| 621 |
+
pour l'élève. Vérifie l'équivalence MATHÉMATIQUE de chaque paire d'options
|
| 622 |
+
sur TOUT l'espace des tirages (ex. b == 1/a possible ? → l'exclure au tirage).
|
| 623 |
+
|
| 624 |
+
UN SEUL bloc {python} au total : JAMAIS de re-tirage (`rd.`/`random`) hors du
|
| 625 |
+
bloc principal — un second tirage rendrait les valeurs incohérentes entre les
|
| 626 |
+
questions. Les variables des paires suivantes s'ajoutent SANS aléa nouveau.
|
| 627 |
+
|
| 628 |
+
FORMATAGE : STRICTEMENT IDENTIQUE entre bonne réponse et distracteurs (même
|
| 629 |
+
style LaTeX, mêmes helpers, même nombre de décimales, même notation
|
| 630 |
+
matricielle) — sinon la bonne réponse se devine.
|
| 631 |
+
|
| 632 |
+
L'ÉNONCÉ NE DONNE JAMAIS LA RÉPONSE (l'énoncé définit, la question interroge).
|
| 633 |
+
""" # noqa: E501 — texte normatif verbatim (valeur injectée telle quelle, accolades SIMPLES)
|
| 634 |
+
|
| 635 |
+
FGQ_SPEC = """\
|
| 636 |
+
FORMAT QAT (questionType FGQ — question à champ(s) libre(s) ordonné(s)) — NORMATIF :
|
| 637 |
+
|
| 638 |
+
:::::{question}
|
| 639 |
+
:questionType: FGQ
|
| 640 |
+
:questionId: N
|
| 641 |
+
:questionIndex: N
|
| 642 |
+
:solution: [["ord","${{v1Aff}}$","${{v2Aff}}$"],["0","0"]]
|
| 643 |
+
|
| 644 |
+
::::{questionStatement}
|
| 645 |
+
<énoncé auto-suffisant>
|
| 646 |
+
|
| 647 |
+
<LIGNE VIDE obligatoire avant le premier {input}>
|
| 648 |
+
$x_1 =$ {input}`||110` $\\qquad x_2 =$ {input}`||110`
|
| 649 |
+
::::
|
| 650 |
+
|
| 651 |
+
::::{questionHint}
|
| 652 |
+
<indice — vide ou verbatim source>
|
| 653 |
+
::::
|
| 654 |
+
|
| 655 |
+
::::{displayedSolution}
|
| 656 |
+
$x_1 = {{v1Aff}}$, $\\quad x_2 = {{v2Aff}}$
|
| 657 |
+
::::
|
| 658 |
+
|
| 659 |
+
::::{detailedSolution}
|
| 660 |
+
<solution détaillée>
|
| 661 |
+
::::
|
| 662 |
+
|
| 663 |
+
::::{weightDistribution}
|
| 664 |
+
:logic: 15
|
| 665 |
+
:abstraction: 20
|
| 666 |
+
:reasoning: 20
|
| 667 |
+
:calculation: 45
|
| 668 |
+
::::
|
| 669 |
+
:::::
|
| 670 |
+
|
| 671 |
+
RÈGLES FGQ DURES :
|
| 672 |
+
• `:solution:` DIRECTEMENT dans le champ (jamais construite dans une variable
|
| 673 |
+
Python), juste après `:questionIndex:`. Format [["ord","<v1>",…],["0",…]].
|
| 674 |
+
• ARITÉ STRICTE : nb de {input} == nb de valeurs dans "ord" == nb de
|
| 675 |
+
tolérances. Tolérance TOUJOURS "0" (exacte).
|
| 676 |
+
• Un `{{varAff}}` par valeur dynamique dans `:solution:` (variable NUE).
|
| 677 |
+
• ORDRE : les valeurs de "ord" suivent l'ordre d'apparition des {input}
|
| 678 |
+
dans l'énoncé. Plusieurs solutions (racines…) → ÉNONCER l'ordre (« de la
|
| 679 |
+
plus petite à la plus grande ») et le respecter dans "ord" ET dans
|
| 680 |
+
displayedSolution.
|
| 681 |
+
• Chaque {input} est INTRODUIT PAR UN LABEL ($x =$ {input}`||110`),
|
| 682 |
+
jamais nu, jamais collé à la prose ; LIGNE VIDE avant le premier {input}.
|
| 683 |
+
• Ordre des blocs : questionStatement → questionHint → displayedSolution →
|
| 684 |
+
detailedSolution → weightDistribution.
|
| 685 |
+
• Valeurs EXACTES (fractions, \\ln, \\sqrt, +\\infty…) — JAMAIS de décimales
|
| 686 |
+
approchées dans `:solution:`.
|
| 687 |
+
• REPLI MCQ : si une question n'est PAS auto-corrigeable en champ libre
|
| 688 |
+
(réponse avec fonction abstraite, vrai/faux, matrice à dimension VARIABLE),
|
| 689 |
+
produis cette question en MCQ (format ci-contre) — une sortie QAT peut être
|
| 690 |
+
mixte FGQ + MCQ. Ne force JAMAIS un champ libre ingérable.
|
| 691 |
+
|
| 692 |
+
MATRICES EN QAT (PIÈGE plateforme) :
|
| 693 |
+
• `pxsl_matrix` est INTERDIT dans un champ `:solution:` (le rendu
|
| 694 |
+
\\left[\\begin{array}… ne matche pas le widget). Si champ unique matrice :
|
| 695 |
+
variable calculée avec latex(M, mat_delim='', mat_str='pmatrix') (FR)
|
| 696 |
+
ou mat_str='bmatrix' (EN), injectée nue.
|
| 697 |
+
• Dimension variable ⇒ repli MCQ pour cette question.
|
| 698 |
+
|
| 699 |
+
L'ÉNONCÉ NE DONNE JAMAIS LA RÉPONSE (l'énoncé définit, la question interroge).
|
| 700 |
+
""" # noqa: E501 — texte normatif verbatim (valeur injectée telle quelle, accolades SIMPLES)
|
| 701 |
+
|
| 702 |
+
# Prompt de génération d'une déclinaison (QCM ou QAT) — même mécanique par
|
| 703 |
+
# paires que la pythonisation ; le champ {decl_spec} reçoit MCQ_SPEC ou FGQ_SPEC.
|
| 704 |
+
STEP_DECLINAISON_PROMPT = """\
|
| 705 |
+
Tu déclines un exercice PyxiScience MyST en version {decl_label}, niveau {niveau}.
|
| 706 |
+
L'exercice source (statique OU déjà pythonisé) est fourni ; tu produis la
|
| 707 |
+
déclinaison RANDOMISÉE (bloc Python terminé par `globals()` + injections),
|
| 708 |
+
au format plateforme EXACT ci-dessous.
|
| 709 |
+
|
| 710 |
+
═══════════════════════════════════════════════════════════════════════════
|
| 711 |
+
CONVENTIONS PLATEFORME (identiques à la pythonisation — NON NÉGOCIABLES)
|
| 712 |
+
═══════════════════════════════════════════════════════════════════════════
|
| 713 |
+
• Bloc Python : 4 backticks ````{{python}} … ```` terminé par `globals()`.
|
| 714 |
+
• Injections `{{{{ }}}}` : UNIQUEMENT des noms de variables NUS camelCase
|
| 715 |
+
suffixe `Aff` — JAMAIS d'appel de fonction, de calcul, d'underscore, ni
|
| 716 |
+
d'accès dict. Tout se pré-calcule dans le bloc Python.
|
| 717 |
+
• `{{{{ }}}}` et les maths TOUJOURS HORS des rôles {{fr}}`…`{{en}}`…` (un
|
| 718 |
+
placeholder dans un rôle ne s'évalue PAS — texte cassé).
|
| 719 |
+
• Règle du `$` collé à un chiffre : préfixe `${{}}`. Décimales localisées
|
| 720 |
+
(virgule FR / point EN). `latex(expr, **config_standard)`.
|
| 721 |
+
• `:questionId:`/`:questionIndex:` contigus dès 0.
|
| 722 |
+
• Interdits : \\py{{}}, \\qcm, \\qat, \\qcl, \\right/\\wrong (légacy),
|
| 723 |
+
\\begin{{align*}}, \\displaystyle, \\[ \\], $$.
|
| 724 |
+
|
| 725 |
+
FIDÉLITÉ À LA SOURCE :
|
| 726 |
+
• 1 question source → 1 question déclinée. NE JAMAIS inventer de
|
| 727 |
+
sous-questions ni enrichir l'énoncé.
|
| 728 |
+
• L'énoncé ne doit JAMAIS donner la réponse (l'énoncé définit, la question
|
| 729 |
+
interroge).
|
| 730 |
+
• detailedSolution : VERBATIM de la source si elle existe (seules les valeurs
|
| 731 |
+
littérales deviennent des {{{{varAff}}}}), sinon rédigée sobrement.
|
| 732 |
+
• Source DÉJÀ PYTHONISÉE : recopie son bloc Python À L'IDENTIQUE (octet pour
|
| 733 |
+
octet), puis ajoute `# === Ajouts déclinaison {decl_label} ===` suivi des
|
| 734 |
+
NOUVELLES variables (distracteurs/solutions), AVANT le `globals()` final.
|
| 735 |
+
• weightDistribution : repris de la question source si présent, sinon le
|
| 736 |
+
barème par défaut du format ci-dessous (somme = 100 TOUJOURS).
|
| 737 |
+
|
| 738 |
+
═══════════════════════════════════════════════════════════════════════════
|
| 739 |
+
SPÉCIFICATION DU FORMAT {decl_label}
|
| 740 |
+
═══════════════════════════════════════════════════════════════════════════
|
| 741 |
+
{decl_spec}
|
| 742 |
+
|
| 743 |
+
═══════════════════════════════════════════════════════════════════════════
|
| 744 |
+
EXEMPLE CANONIQUE COMPLET (structure et conventions à imiter)
|
| 745 |
+
═══════════════════════════════════════════════════════════════════════════
|
| 746 |
+
{fewshot}
|
| 747 |
+
|
| 748 |
+
═══════════════════════════════════════════════════════════════════════════
|
| 749 |
+
CATALOGUE PyxiScience (helpers à utiliser DANS le bloc Python)
|
| 750 |
+
═══════════════════════════════════════════════════════════════════════════
|
| 751 |
+
{functions}
|
| 752 |
+
|
| 753 |
+
═══════════════════════════════════════════════════════════════════════════
|
| 754 |
+
CONTEXTE
|
| 755 |
+
═══════════════════════════════════════════════════════════════════════════
|
| 756 |
+
|
| 757 |
+
EN-TÊTE déjà finalisé (NE PAS reproduire) :
|
| 758 |
+
{content}
|
| 759 |
+
|
| 760 |
+
VARIABLES DÉTECTÉES :
|
| 761 |
+
{analysis}
|
| 762 |
+
|
| 763 |
+
BLOCS PRÉCÉDENTS (ne pas redéfinir leurs variables, ne pas les répéter) :
|
| 764 |
+
{previous_blocks}
|
| 765 |
+
|
| 766 |
+
SECTION À DÉCLINER ({range_label} / {nb_total}) :
|
| 767 |
+
{current_segment}
|
| 768 |
+
|
| 769 |
+
{lang_directive}
|
| 770 |
+
|
| 771 |
+
═══════════════════════════════════════════════════════════════════════════
|
| 772 |
+
RÈGLES D'ASSEMBLAGE PAR PAIRE
|
| 773 |
+
═══════════════════════════════════════════════════════════════════════════
|
| 774 |
+
⚠️ TU PRODUIS UNIQUEMENT LE CONTENU DE CETTE PAIRE (les paires précédentes
|
| 775 |
+
sont concaténées mécaniquement avant ta sortie).
|
| 776 |
+
⚠️ EXACTEMENT {nb_current} bloc(s) `:::::{{question}}` — questionId/questionIndex
|
| 777 |
+
CONTINUS depuis la paire précédente.
|
| 778 |
+
⚠️ PAIRE 1 UNIQUEMENT : le bloc ````{{python}}```` (source recopiée si déjà
|
| 779 |
+
pythonisée + ajouts déclinaison + `globals()`) puis l'énoncé général VERBATIM,
|
| 780 |
+
AVANT la première question.
|
| 781 |
+
⚠️ PAIRES SUIVANTES : ni énoncé, ni ré-imports ; petit bloc ````{{python}}````
|
| 782 |
+
additionnel possible pour les nouvelles variables seulement.
|
| 783 |
+
|
| 784 |
+
═══════════════════════════════════════════════════════════════════════════
|
| 785 |
+
CHECKLIST FINALE
|
| 786 |
+
═══════════════════════════════════════════════════════════════════════════
|
| 787 |
+
□ Bloc ````{{python}}```` (4 backticks) terminé par `globals()`
|
| 788 |
+
□ CHAQUE `{{{{ }}}}` = variable NUE camelCase (Aff) — aucun appel/underscore
|
| 789 |
+
□ Aucun `{{{{ }}}}` NI maths à l'intérieur d'un rôle {{fr}}`…`/{{en}}`…`
|
| 790 |
+
□ MCQ : 1 seule bonne réponse (slot 1), « None » en dernier, options toutes
|
| 791 |
+
distinctes SUR TOUTES LES GRAINES, formatage identique
|
| 792 |
+
□ FGQ : arité #input == #valeurs == #tolérances ("0"), :solution: littérale,
|
| 793 |
+
ordre énoncé/"ord"/displayedSolution cohérents, labels devant chaque {{input}}
|
| 794 |
+
□ 1 question source → 1 question ; solutions source VERBATIM ; poids repris
|
| 795 |
+
□ IDs contigus dès 0 ; aucun motif interdit ; `\\%` pour les pourcentages
|
| 796 |
+
"""
|
| 797 |
+
|
| 798 |
+
|
| 799 |
# ─────────────────────────────────────────────────────────────────────────────
|
| 800 |
# Réparation post-harnais (NOUVEAU — 1 itération max)
|
| 801 |
# ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
| 827 |
TIRAGE DÉGÉNÉRÉ — exclure la valeur fautive à la source
|
| 828 |
(ex. `b = rd.randint(2, 5)` au lieu de `randint(1, 5)`, ou boucle de rejet
|
| 829 |
`if b == 1: continue`). Ne PAS rafistoler le texte : corriger le tirage.
|
| 830 |
+
**EXCEPTION — texte pédagogique FIXE** qui enseigne précisément la règle de
|
| 831 |
+
l'exposant (`$b^{{0}} = 1$`, `$b^{{1}} = b$`) : réécrire SANS accolades
|
| 832 |
+
(`$b^0 = 1$`, `$b^1 = b$`) — rendu LaTeX identique pour un exposant à un
|
| 833 |
+
seul caractère, et conforme au corpus validé.
|
| 834 |
• Injection non nue → pré-calculer en variable camelCase `…Aff`.
|
| 835 |
• Le bloc {{python}} reste à 4 backticks et se termine par `globals()`.
|
| 836 |
• Ne touche NI à `\\inftys`/`\\ds`/`\\dfrac`, NI à la prose des solutions.
|
| 837 |
+
• **QCM — collision d'options** (deux options rendues identiques sur une
|
| 838 |
+
graine) : contraindre le TIRAGE (rejet `while` sur les chaînes rendues) ou
|
| 839 |
+
changer la CONSTRUCTION du distracteur — jamais rafistoler le texte.
|
| 840 |
+
• **QCM — plusieurs/zéro `:isRightAnswer: true`** : exactement une, en slot 1.
|
| 841 |
+
• **FGQ — arité** : le nb de {{{{input}}}} doit égaler le nb de valeurs de
|
| 842 |
+
`"ord"` et le nb de tolérances ("0") dans `:solution:`.
|
| 843 |
+
• **`{{{{ }}}}` dans un rôle {{fr}}`…`/{{en}}`…`** : sortir l'injection du
|
| 844 |
+
rôle (elle ne s'évalue pas dedans) — découper le rôle autour.
|
| 845 |
|
| 846 |
Réponds UNIQUEMENT avec l'exercice complet corrigé (de `````{{exercise}} à `````),
|
| 847 |
sans préambule ni wrapper markdown.
|
app/server.py
CHANGED
|
@@ -30,10 +30,11 @@ from flask import Response, jsonify, render_template, request, send_file
|
|
| 30 |
from app.config import (
|
| 31 |
AVAILABLE_MODELS,
|
| 32 |
DEFAULT_LANG,
|
|
|
|
| 33 |
DEFAULT_MODEL_IDX,
|
| 34 |
JOB_TTL,
|
| 35 |
)
|
| 36 |
-
from app.pipeline.orchestrator import run_exercise
|
| 37 |
|
| 38 |
logger = logging.getLogger(__name__)
|
| 39 |
|
|
@@ -42,6 +43,7 @@ _JOBS_LOCK = threading.Lock()
|
|
| 42 |
|
| 43 |
VALID_LANGS = ("fr", "en", "both")
|
| 44 |
VALID_LEVELS = ("", "Elementary", "Intermediate", "Advanced")
|
|
|
|
| 45 |
|
| 46 |
|
| 47 |
def _set_job(job_id: str, **kwargs):
|
|
@@ -57,14 +59,16 @@ def _get_job(job_id: str):
|
|
| 57 |
|
| 58 |
|
| 59 |
def _safe_md_name(filename: str, used: set) -> str:
|
| 60 |
-
"""Nom de fichier .md sûr et unique pour
|
| 61 |
-
|
|
|
|
| 62 |
base = re.sub(r"\.(md|txt)$", "", filename or "", flags=re.IGNORECASE)
|
| 63 |
base = re.sub(r"[^\w.\-() ]+", "_", base).strip() or "exercice"
|
| 64 |
-
|
|
|
|
| 65 |
i = 2
|
| 66 |
while name in used:
|
| 67 |
-
name = f"{base}
|
| 68 |
i += 1
|
| 69 |
used.add(name)
|
| 70 |
return name
|
|
@@ -78,8 +82,17 @@ def _purge_old_jobs():
|
|
| 78 |
del _JOBS[jid]
|
| 79 |
|
| 80 |
|
| 81 |
-
def
|
| 82 |
-
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 83 |
results: list[dict] = []
|
| 84 |
for i, f in enumerate(files):
|
| 85 |
name = f.get("filename") or f"fichier_{i + 1}.md"
|
|
@@ -90,19 +103,36 @@ def _run_job(job_id: str, files: list[dict], level: str, model_idx: int, lang: s
|
|
| 90 |
_set_job(job_id, step_label=f"[{_i + 1}/{len(files)}] {_name} — {label}")
|
| 91 |
|
| 92 |
try:
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
|
| 97 |
-
|
| 98 |
-
|
| 99 |
-
|
| 100 |
-
|
| 101 |
-
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 106 |
except Exception as exc:
|
| 107 |
logger.exception("Échec du pipeline sur %s", name)
|
| 108 |
results.append({"filename": name, "status": "error", "error": str(exc)})
|
|
@@ -212,6 +242,18 @@ def register_routes(app):
|
|
| 212 |
"error": f"'model_idx' inconnu : {model_idx}. Valides : {sorted(AVAILABLE_MODELS)}."
|
| 213 |
}), 400
|
| 214 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 215 |
job_id = uuid.uuid4().hex
|
| 216 |
with _JOBS_LOCK:
|
| 217 |
_JOBS[job_id] = {
|
|
@@ -229,7 +271,7 @@ def register_routes(app):
|
|
| 229 |
|
| 230 |
threading.Thread(
|
| 231 |
target=_run_job,
|
| 232 |
-
args=(job_id, clean_files, level, model_idx, lang),
|
| 233 |
daemon=True,
|
| 234 |
).start()
|
| 235 |
return jsonify({"job_id": job_id}), 202
|
|
|
|
| 30 |
from app.config import (
|
| 31 |
AVAILABLE_MODELS,
|
| 32 |
DEFAULT_LANG,
|
| 33 |
+
DEFAULT_MODE,
|
| 34 |
DEFAULT_MODEL_IDX,
|
| 35 |
JOB_TTL,
|
| 36 |
)
|
| 37 |
+
from app.pipeline.orchestrator import run_declinaisons, run_exercise
|
| 38 |
|
| 39 |
logger = logging.getLogger(__name__)
|
| 40 |
|
|
|
|
| 43 |
|
| 44 |
VALID_LANGS = ("fr", "en", "both")
|
| 45 |
VALID_LEVELS = ("", "Elementary", "Intermediate", "Advanced")
|
| 46 |
+
VALID_MODES = ("pythonise", "declinaisons")
|
| 47 |
|
| 48 |
|
| 49 |
def _set_job(job_id: str, **kwargs):
|
|
|
|
| 59 |
|
| 60 |
|
| 61 |
def _safe_md_name(filename: str, used: set) -> str:
|
| 62 |
+
"""Nom de fichier .md sûr et unique pour le ZIP. Les déclinaisons portent
|
| 63 |
+
déjà leur suffixe (_QCM/_QAT) ; la pythonisation reçoit _pythonise pour
|
| 64 |
+
ne pas écraser la source."""
|
| 65 |
base = re.sub(r"\.(md|txt)$", "", filename or "", flags=re.IGNORECASE)
|
| 66 |
base = re.sub(r"[^\w.\-() ]+", "_", base).strip() or "exercice"
|
| 67 |
+
suffix = "" if re.search(r"_(QCM|QAT)$", base) else "_pythonise"
|
| 68 |
+
name = f"{base}{suffix}.md"
|
| 69 |
i = 2
|
| 70 |
while name in used:
|
| 71 |
+
name = f"{base}{suffix}_{i}.md"
|
| 72 |
i += 1
|
| 73 |
used.add(name)
|
| 74 |
return name
|
|
|
|
| 82 |
del _JOBS[jid]
|
| 83 |
|
| 84 |
|
| 85 |
+
def _decl_output_name(source_name: str, decl_type: str) -> str:
|
| 86 |
+
"""foo.md + qcm → foo_QCM.md (nommage de sortie des déclinaisons)."""
|
| 87 |
+
base = re.sub(r"\.(md|txt)$", "", source_name or "exercice", flags=re.IGNORECASE)
|
| 88 |
+
return f"{base}_{'QCM' if decl_type == 'qcm' else 'QAT'}.md"
|
| 89 |
+
|
| 90 |
+
|
| 91 |
+
def _run_job(job_id: str, files: list[dict], level: str, model_idx: int,
|
| 92 |
+
lang: str, mode: str = "pythonise", decl_types: list | None = None):
|
| 93 |
+
"""Worker de job : boucle séquentielle sur les fichiers, robuste.
|
| 94 |
+
En mode `declinaisons`, chaque source produit 1 résultat PAR type coché
|
| 95 |
+
(analyse partagée entre types — aucun appel LLM redondant)."""
|
| 96 |
results: list[dict] = []
|
| 97 |
for i, f in enumerate(files):
|
| 98 |
name = f.get("filename") or f"fichier_{i + 1}.md"
|
|
|
|
| 103 |
_set_job(job_id, step_label=f"[{_i + 1}/{len(files)}] {_name} — {label}")
|
| 104 |
|
| 105 |
try:
|
| 106 |
+
if mode == "declinaisons":
|
| 107 |
+
for decl_type, result in run_declinaisons(
|
| 108 |
+
content=f["content"],
|
| 109 |
+
filename=name,
|
| 110 |
+
level=level,
|
| 111 |
+
model_idx=model_idx,
|
| 112 |
+
lang=lang,
|
| 113 |
+
types=decl_types,
|
| 114 |
+
set_step=set_step,
|
| 115 |
+
):
|
| 116 |
+
out_name = _decl_output_name(name, decl_type)
|
| 117 |
+
results.append({"filename": out_name, "status": "done", "result": result})
|
| 118 |
+
logger.info("Déclinaison %s : harnais %s, %d warnings, %.1fs, %.4f$",
|
| 119 |
+
out_name, "VERT" if result["harness"]["ok"] else "ROUGE",
|
| 120 |
+
len(result["warnings"]), result["duration_s"],
|
| 121 |
+
result["cost"]["usd"])
|
| 122 |
+
else:
|
| 123 |
+
result = run_exercise(
|
| 124 |
+
content=f["content"],
|
| 125 |
+
filename=name,
|
| 126 |
+
level=level,
|
| 127 |
+
model_idx=model_idx,
|
| 128 |
+
lang=lang,
|
| 129 |
+
set_step=set_step,
|
| 130 |
+
)
|
| 131 |
+
results.append({"filename": name, "status": "done", "result": result})
|
| 132 |
+
logger.info("Fichier %s : harnais %s, %d warnings, %.1fs, %.4f$",
|
| 133 |
+
name, "VERT" if result["harness"]["ok"] else "ROUGE",
|
| 134 |
+
len(result["warnings"]), result["duration_s"],
|
| 135 |
+
result["cost"]["usd"])
|
| 136 |
except Exception as exc:
|
| 137 |
logger.exception("Échec du pipeline sur %s", name)
|
| 138 |
results.append({"filename": name, "status": "error", "error": str(exc)})
|
|
|
|
| 242 |
"error": f"'model_idx' inconnu : {model_idx}. Valides : {sorted(AVAILABLE_MODELS)}."
|
| 243 |
}), 400
|
| 244 |
|
| 245 |
+
mode = (data.get("mode") or DEFAULT_MODE).strip()
|
| 246 |
+
if mode not in VALID_MODES:
|
| 247 |
+
return jsonify({"error": f"'mode' invalide : {mode!r} (pythonise|declinaisons)."}), 400
|
| 248 |
+
decl_types: list = []
|
| 249 |
+
if mode == "declinaisons":
|
| 250 |
+
types_obj = data.get("types") or {}
|
| 251 |
+
if not isinstance(types_obj, dict):
|
| 252 |
+
return jsonify({"error": "'types' doit être un objet {qcm: bool, qat: bool}."}), 400
|
| 253 |
+
decl_types = [t for t in ("qcm", "qat") if types_obj.get(t)]
|
| 254 |
+
if not decl_types:
|
| 255 |
+
return jsonify({"error": "En mode 'declinaisons', cocher au moins un type (qcm/qat)."}), 400
|
| 256 |
+
|
| 257 |
job_id = uuid.uuid4().hex
|
| 258 |
with _JOBS_LOCK:
|
| 259 |
_JOBS[job_id] = {
|
|
|
|
| 271 |
|
| 272 |
threading.Thread(
|
| 273 |
target=_run_job,
|
| 274 |
+
args=(job_id, clean_files, level, model_idx, lang, mode, decl_types),
|
| 275 |
daemon=True,
|
| 276 |
).start()
|
| 277 |
return jsonify({"job_id": job_id}), 202
|
app/validation/harness.py
CHANGED
|
@@ -65,18 +65,25 @@ def install_pyxiscience_stubs() -> None:
|
|
| 65 |
# sympy.latex (qui rejette toute clé inconnue).
|
| 66 |
return {}
|
| 67 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 68 |
def _make_module(name: str) -> types.ModuleType:
|
| 69 |
mod = types.ModuleType(name)
|
| 70 |
|
| 71 |
def __getattr__(attr): # PEP 562
|
| 72 |
if attr == "pxs_config":
|
| 73 |
return _pxs_config
|
|
|
|
|
|
|
| 74 |
# Classe si CamelCase OU motif pxs_Xxx (pxs_Interval, pxs_Plotable…)
|
| 75 |
if attr and (attr[0].isupper() or re.match(r"pxs_[A-Z]", attr)):
|
| 76 |
return _Universal
|
| 77 |
return _passthrough
|
| 78 |
|
| 79 |
mod.__getattr__ = __getattr__
|
|
|
|
| 80 |
return mod
|
| 81 |
|
| 82 |
root = _make_module("pyxiscience")
|
|
@@ -184,6 +191,132 @@ def scan_forbidden(body: str) -> list[str]:
|
|
| 184 |
return errs
|
| 185 |
|
| 186 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 187 |
# ── Rendu (substitution des injections) ──────────────────────────────────────
|
| 188 |
|
| 189 |
def render_body(body_tmpl: str, env: dict) -> tuple[str, list[str]]:
|
|
@@ -218,6 +351,9 @@ def validate_text(text: str, seeds: int = 100) -> dict:
|
|
| 218 |
}
|
| 219 |
"""
|
| 220 |
static_errors = check_injection_tokens(text) + check_question_ids(text)
|
|
|
|
|
|
|
|
|
|
| 221 |
code = extract_python_code(text)
|
| 222 |
if code is None:
|
| 223 |
static_errors.append("aucun bloc `{python}` trouvé")
|
|
@@ -229,6 +365,7 @@ def validate_text(text: str, seeds: int = 100) -> dict:
|
|
| 229 |
"n_exec_errors": 0,
|
| 230 |
"n_unresolved": 0,
|
| 231 |
"n_forbidden": 0,
|
|
|
|
| 232 |
"first_failures": [],
|
| 233 |
}
|
| 234 |
if code is None:
|
|
@@ -261,11 +398,22 @@ def validate_text(text: str, seeds: int = 100) -> dict:
|
|
| 261 |
report["n_unresolved"] += 1
|
| 262 |
if len(failures) < 8:
|
| 263 |
failures.append(f"seed {s} : variable(s) non résolue(s) : {sorted(set(unresolved))}")
|
| 264 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 265 |
if fb:
|
| 266 |
report["n_forbidden"] += 1
|
| 267 |
if len(failures) < 8:
|
| 268 |
failures.append(f"seed {s} : motif interdit : {fb[0]}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 269 |
|
| 270 |
report["first_failures"] = failures
|
| 271 |
report["ok"] = (
|
|
@@ -273,6 +421,7 @@ def validate_text(text: str, seeds: int = 100) -> dict:
|
|
| 273 |
and report["n_exec_errors"] == 0
|
| 274 |
and report["n_unresolved"] == 0
|
| 275 |
and report["n_forbidden"] == 0
|
|
|
|
| 276 |
)
|
| 277 |
return report
|
| 278 |
|
|
@@ -289,5 +438,7 @@ def format_report(report: dict) -> str:
|
|
| 289 |
lines.append(f"[INJECTION] {report['n_unresolved']}/{n} graines avec variables non résolues")
|
| 290 |
if report["n_forbidden"]:
|
| 291 |
lines.append(f"[INTERDITS] {report['n_forbidden']}/{n} graines avec motif interdit")
|
|
|
|
|
|
|
| 292 |
lines.extend(f" • {f}" for f in report["first_failures"])
|
| 293 |
return "\n".join(lines) if lines else "VERT"
|
|
|
|
| 65 |
# sympy.latex (qui rejette toute clé inconnue).
|
| 66 |
return {}
|
| 67 |
|
| 68 |
+
# Liste canonique partagée avec la sandbox (les deux systèmes de stubs
|
| 69 |
+
# partagent sys.modules — le premier installé sert aux deux).
|
| 70 |
+
from app.validation.sandbox import KNOWN_PXS_HELPERS
|
| 71 |
+
|
| 72 |
def _make_module(name: str) -> types.ModuleType:
|
| 73 |
mod = types.ModuleType(name)
|
| 74 |
|
| 75 |
def __getattr__(attr): # PEP 562
|
| 76 |
if attr == "pxs_config":
|
| 77 |
return _pxs_config
|
| 78 |
+
if attr == "pxs_variation_number":
|
| 79 |
+
return 1
|
| 80 |
# Classe si CamelCase OU motif pxs_Xxx (pxs_Interval, pxs_Plotable…)
|
| 81 |
if attr and (attr[0].isupper() or re.match(r"pxs_[A-Z]", attr)):
|
| 82 |
return _Universal
|
| 83 |
return _passthrough
|
| 84 |
|
| 85 |
mod.__getattr__ = __getattr__
|
| 86 |
+
mod.__all__ = list(KNOWN_PXS_HELPERS)
|
| 87 |
return mod
|
| 88 |
|
| 89 |
root = _make_module("pyxiscience")
|
|
|
|
| 191 |
return errs
|
| 192 |
|
| 193 |
|
| 194 |
+
# ── Contrôles déclinaisons QCM/QAT (mode `declinaisons`) ─────────────────────
|
| 195 |
+
|
| 196 |
+
_QUESTION_RE = re.compile(r":::::\{question\}.*?:::::", re.DOTALL)
|
| 197 |
+
_MCQ_ANSWER_RE = re.compile(
|
| 198 |
+
r"::::\{mcqAnswer\}\s*\n:isRightAnswer:\s*(true|false)\s*\n(.*?)\n::::",
|
| 199 |
+
re.DOTALL,
|
| 200 |
+
)
|
| 201 |
+
_NONE_OPTION_RE = re.compile(r"Aucune de ces réponses|None of these answers", re.IGNORECASE)
|
| 202 |
+
_SOLUTION_FIELD_RE = re.compile(r"(?m)^:solution:[ \t]*(.+)$")
|
| 203 |
+
_INPUT_RE = re.compile(r"\{input\}`")
|
| 204 |
+
_QTYPE_RE = re.compile(r":questionType:\s*(\w+)")
|
| 205 |
+
|
| 206 |
+
|
| 207 |
+
def has_declinaison_questions(text: str) -> bool:
|
| 208 |
+
return bool(re.search(r":questionType:\s*(MCQ|FGQ)\b", text))
|
| 209 |
+
|
| 210 |
+
|
| 211 |
+
_LEGACY_PATTERNS = [
|
| 212 |
+
(re.compile(r"\\py\{"), r"résidu légacy `\py{`"),
|
| 213 |
+
(re.compile(r"\\qcm\b|\\qat\b|\\qcl\b"), r"résidu légacy `\qcm`/`\qat`/`\qcl`"),
|
| 214 |
+
(re.compile(r"\\input\{null\}"), r"résidu légacy `\input{null}`"),
|
| 215 |
+
(re.compile(r"\\begin\{align\*?\}"), r"`\begin{align*}` interdit (utiliser equation*)"),
|
| 216 |
+
(re.compile(r"(?<!\\)\$\$"), r"`$$` interdit (utiliser equation*)"),
|
| 217 |
+
]
|
| 218 |
+
|
| 219 |
+
|
| 220 |
+
def check_declinaison_static(text: str) -> list[str]:
|
| 221 |
+
"""Contrôles statiques MCQ/FGQ (indépendants des graines)."""
|
| 222 |
+
import json as _json
|
| 223 |
+
errs: list[str] = []
|
| 224 |
+
body = strip_python_blocks(text)
|
| 225 |
+
|
| 226 |
+
# {{ }} dans un rôle bilingue → ne s'évalue pas.
|
| 227 |
+
for m in re.finditer(r"\{(?:fr|en)\}`([^`]*)`", body):
|
| 228 |
+
if "{{" in m.group(1):
|
| 229 |
+
errs.append(f"injection `{{{{…}}}}` dans un rôle bilingue : …{m.group(0)[:80]}…")
|
| 230 |
+
break
|
| 231 |
+
|
| 232 |
+
# Résidus de l'ancienne syntaxe plateforme.
|
| 233 |
+
errs += [msg for rx, msg in _LEGACY_PATTERNS if rx.search(body)]
|
| 234 |
+
|
| 235 |
+
# UN SEUL bloc {python} en déclinaison (un bloc additionnel qui RE-TIRE de
|
| 236 |
+
# l'aléatoire rendrait les valeurs incohérentes entre questions ; les blocs
|
| 237 |
+
# sans re-tirage sont fusionnés en amont par merge_decl_python_blocks).
|
| 238 |
+
n_blocks = len(PY_FENCE_ANY_RE.findall(text))
|
| 239 |
+
if n_blocks > 1:
|
| 240 |
+
errs.append(f"{n_blocks} blocs {{python}} (un seul attendu en déclinaison — "
|
| 241 |
+
"bloc additionnel avec re-tirage aléatoire probable)")
|
| 242 |
+
|
| 243 |
+
# Approximations dans :solution: (valeurs exactes uniquement).
|
| 244 |
+
for m in _SOLUTION_FIELD_RE.finditer(text):
|
| 245 |
+
if "\\approx" in m.group(1) or "≈" in m.group(1):
|
| 246 |
+
errs.append("approximation (≈) dans un champ `:solution:` — valeurs exactes uniquement")
|
| 247 |
+
break
|
| 248 |
+
|
| 249 |
+
for qi, qm in enumerate(_QUESTION_RE.finditer(text)):
|
| 250 |
+
q = qm.group(0)
|
| 251 |
+
tm = _QTYPE_RE.search(q)
|
| 252 |
+
qtype = tm.group(1) if tm else "?"
|
| 253 |
+
|
| 254 |
+
if qtype == "MCQ":
|
| 255 |
+
answers = list(_MCQ_ANSWER_RE.finditer(q))
|
| 256 |
+
n_true = sum(1 for a in answers if a.group(1) == "true")
|
| 257 |
+
if n_true != 1:
|
| 258 |
+
errs.append(f"question {qi} (MCQ) : {n_true} option(s) `:isRightAnswer: true` (attendu : exactement 1)")
|
| 259 |
+
if not (3 <= len(answers) <= 6):
|
| 260 |
+
errs.append(f"question {qi} (MCQ) : {len(answers)} options (attendu : 3 à 5)")
|
| 261 |
+
none_idx = [i for i, a in enumerate(answers) if _NONE_OPTION_RE.search(a.group(2))]
|
| 262 |
+
if none_idx and none_idx[-1] != len(answers) - 1:
|
| 263 |
+
errs.append(f"question {qi} (MCQ) : option « None/Aucune » pas en dernière position")
|
| 264 |
+
if _SOLUTION_FIELD_RE.search(q) or _INPUT_RE.search(q):
|
| 265 |
+
errs.append(f"question {qi} (MCQ) : `:solution:`/`{{input}}` interdits en MCQ")
|
| 266 |
+
|
| 267 |
+
elif qtype == "FGQ":
|
| 268 |
+
sol = _SOLUTION_FIELD_RE.search(q)
|
| 269 |
+
n_inputs = len(_INPUT_RE.findall(q))
|
| 270 |
+
if not sol:
|
| 271 |
+
errs.append(f"question {qi} (FGQ) : champ `:solution:` manquant")
|
| 272 |
+
continue
|
| 273 |
+
if n_inputs == 0:
|
| 274 |
+
errs.append(f"question {qi} (FGQ) : aucun `{{input}}` dans l'énoncé")
|
| 275 |
+
raw = sol.group(1).strip()
|
| 276 |
+
if "pxsl_matrix" in raw:
|
| 277 |
+
errs.append(f"question {qi} (FGQ) : `pxsl_matrix` dans `:solution:` (interdit)")
|
| 278 |
+
# Parse JSON structurel : on remplace les {{var}} par un littéral neutre.
|
| 279 |
+
probe = INJECTION_RE.sub("V", raw)
|
| 280 |
+
try:
|
| 281 |
+
data = _json.loads(probe)
|
| 282 |
+
assert (isinstance(data, list) and len(data) == 2
|
| 283 |
+
and isinstance(data[0], list) and isinstance(data[1], list)
|
| 284 |
+
and data[0] and data[0][0] in ("ord", "CL"))
|
| 285 |
+
n_vals = len(data[0]) - 1
|
| 286 |
+
n_tols = len(data[1])
|
| 287 |
+
if not (n_vals == n_tols == n_inputs):
|
| 288 |
+
errs.append(f"question {qi} (FGQ) : arité incohérente — "
|
| 289 |
+
f"{n_inputs} input(s), {n_vals} valeur(s), {n_tols} tolérance(s)")
|
| 290 |
+
if any(str(t).strip() != "0" for t in data[1]):
|
| 291 |
+
errs.append(f"question {qi} (FGQ) : tolérance ≠ \"0\"")
|
| 292 |
+
except Exception:
|
| 293 |
+
errs.append(f"question {qi} (FGQ) : `:solution:` non parseable en JSON : {raw[:80]}")
|
| 294 |
+
|
| 295 |
+
return errs
|
| 296 |
+
|
| 297 |
+
|
| 298 |
+
def check_mcq_collisions(text: str, env: dict) -> list[str]:
|
| 299 |
+
"""Pour UNE graine : deux options d'un même MCQ rendues identiques = échec.
|
| 300 |
+
(Le piège n°1 des QCM randomisés — un distracteur « erreur type » peut
|
| 301 |
+
devenir égal à la bonne réponse sur certaines graines.)"""
|
| 302 |
+
errs: list[str] = []
|
| 303 |
+
for qi, qm in enumerate(_QUESTION_RE.finditer(text)):
|
| 304 |
+
q = qm.group(0)
|
| 305 |
+
if not re.search(r":questionType:\s*MCQ\b", q):
|
| 306 |
+
continue
|
| 307 |
+
rendered: list[str] = []
|
| 308 |
+
for a in _MCQ_ANSWER_RE.finditer(q):
|
| 309 |
+
r, _unres = render_body(a.group(2), env)
|
| 310 |
+
rendered.append(re.sub(r"\s+", " ", r).strip())
|
| 311 |
+
seen: dict[str, int] = {}
|
| 312 |
+
for i, r in enumerate(rendered):
|
| 313 |
+
if r in seen:
|
| 314 |
+
errs.append(f"question {qi} (MCQ) : options {seen[r]} et {i} identiques après rendu : « {r[:70]} »")
|
| 315 |
+
else:
|
| 316 |
+
seen[r] = i
|
| 317 |
+
return errs
|
| 318 |
+
|
| 319 |
+
|
| 320 |
# ── Rendu (substitution des injections) ──────────────────────────────────────
|
| 321 |
|
| 322 |
def render_body(body_tmpl: str, env: dict) -> tuple[str, list[str]]:
|
|
|
|
| 351 |
}
|
| 352 |
"""
|
| 353 |
static_errors = check_injection_tokens(text) + check_question_ids(text)
|
| 354 |
+
declinaison = has_declinaison_questions(text)
|
| 355 |
+
if declinaison:
|
| 356 |
+
static_errors += check_declinaison_static(text)
|
| 357 |
code = extract_python_code(text)
|
| 358 |
if code is None:
|
| 359 |
static_errors.append("aucun bloc `{python}` trouvé")
|
|
|
|
| 365 |
"n_exec_errors": 0,
|
| 366 |
"n_unresolved": 0,
|
| 367 |
"n_forbidden": 0,
|
| 368 |
+
"n_mcq_collisions": 0,
|
| 369 |
"first_failures": [],
|
| 370 |
}
|
| 371 |
if code is None:
|
|
|
|
| 398 |
report["n_unresolved"] += 1
|
| 399 |
if len(failures) < 8:
|
| 400 |
failures.append(f"seed {s} : variable(s) non résolue(s) : {sorted(set(unresolved))}")
|
| 401 |
+
# Les lignes `:solution:` (FGQ) sont des motifs de correspondance de
|
| 402 |
+
# réponse, pas du texte affiché : exclues du scan des motifs interdits
|
| 403 |
+
# (`$5$` y est légitime — confirmé par les exemples validés). Leur
|
| 404 |
+
# format est contrôlé séparément (JSON, arité, exactitude).
|
| 405 |
+
scan_target = re.sub(r"(?m)^:solution:.*$", "", rendered)
|
| 406 |
+
fb = scan_forbidden(scan_target)
|
| 407 |
if fb:
|
| 408 |
report["n_forbidden"] += 1
|
| 409 |
if len(failures) < 8:
|
| 410 |
failures.append(f"seed {s} : motif interdit : {fb[0]}")
|
| 411 |
+
if declinaison:
|
| 412 |
+
mc = check_mcq_collisions(text, env)
|
| 413 |
+
if mc:
|
| 414 |
+
report["n_mcq_collisions"] += 1
|
| 415 |
+
if len(failures) < 8:
|
| 416 |
+
failures.append(f"seed {s} : collision d'options MCQ : {mc[0]}")
|
| 417 |
|
| 418 |
report["first_failures"] = failures
|
| 419 |
report["ok"] = (
|
|
|
|
| 421 |
and report["n_exec_errors"] == 0
|
| 422 |
and report["n_unresolved"] == 0
|
| 423 |
and report["n_forbidden"] == 0
|
| 424 |
+
and report["n_mcq_collisions"] == 0
|
| 425 |
)
|
| 426 |
return report
|
| 427 |
|
|
|
|
| 438 |
lines.append(f"[INJECTION] {report['n_unresolved']}/{n} graines avec variables non résolues")
|
| 439 |
if report["n_forbidden"]:
|
| 440 |
lines.append(f"[INTERDITS] {report['n_forbidden']}/{n} graines avec motif interdit")
|
| 441 |
+
if report.get("n_mcq_collisions"):
|
| 442 |
+
lines.append(f"[MCQ] {report['n_mcq_collisions']}/{n} graines avec collision d'options")
|
| 443 |
lines.extend(f" • {f}" for f in report["first_failures"])
|
| 444 |
return "\n".join(lines) if lines else "VERT"
|
app/validation/sandbox.py
CHANGED
|
@@ -78,10 +78,62 @@ class _StubObject:
|
|
| 78 |
return f"_StubObject({self._args!r})"
|
| 79 |
|
| 80 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 81 |
def install_pyxiscience_stubs() -> None:
|
| 82 |
"""
|
| 83 |
Register stub modules for `pyxiscience.*` in sys.modules so that generated
|
| 84 |
Python blocks can `import` PyxiScience helpers without crashing. Idempotent.
|
|
|
|
|
|
|
| 85 |
"""
|
| 86 |
global _STUBS_INSTALLED
|
| 87 |
if _STUBS_INSTALLED:
|
|
@@ -91,7 +143,7 @@ def install_pyxiscience_stubs() -> None:
|
|
| 91 |
_STUBS_INSTALLED = True
|
| 92 |
return
|
| 93 |
|
| 94 |
-
pyxiscience =
|
| 95 |
sys.modules["pyxiscience"] = pyxiscience
|
| 96 |
|
| 97 |
submodules = [
|
|
@@ -101,25 +153,12 @@ def install_pyxiscience_stubs() -> None:
|
|
| 101 |
"Mes_fctions_d_analyse", # alias without _bis (cf. Exo 2 Am. Sud)
|
| 102 |
"Mes_fctions_d_alg_lineaire_bis",
|
| 103 |
"Mes_fctions_probabilistes_bis",
|
|
|
|
|
|
|
|
|
|
| 104 |
]
|
| 105 |
-
helper_names = [
|
| 106 |
-
"pxs_config", "pxsl_latex_coefficient", "pxsl_format_number",
|
| 107 |
-
"pxsl_res_num", "pxsl_matrix", "pxsl_pow", "pxsl_par", "pxsl_mult",
|
| 108 |
-
"pxs_explain_IBP", "pxs_nvirgzero", "_pxsl_choose_udv",
|
| 109 |
-
"pxs_variation_number", "myst",
|
| 110 |
-
"pxsl_latex_avec_formatage", # cf. Exo 2 Am Sud
|
| 111 |
-
"pxsl_choose_udv",
|
| 112 |
-
]
|
| 113 |
-
class_names = ["pxs_Interval", "pxs_Plotable"]
|
| 114 |
-
|
| 115 |
for sub in submodules:
|
| 116 |
-
m =
|
| 117 |
-
for name in helper_names:
|
| 118 |
-
setattr(m, name, _passthrough)
|
| 119 |
-
# Special-case: pxs_config must return a dict (used as **kwargs).
|
| 120 |
-
setattr(m, "pxs_config", _config_stub)
|
| 121 |
-
for name in class_names:
|
| 122 |
-
setattr(m, name, _StubObject)
|
| 123 |
sys.modules[f"pyxiscience.{sub}"] = m
|
| 124 |
setattr(pyxiscience, sub, m)
|
| 125 |
|
|
|
|
| 78 |
return f"_StubObject({self._args!r})"
|
| 79 |
|
| 80 |
|
| 81 |
+
# Liste CANONIQUE des helpers connus (catalogue curé + corpus 222 + 33 exemples
|
| 82 |
+
# déclinaisons). Sert au `__all__` des modules stub : `from X import *` ne
|
| 83 |
+
# passe PAS par __getattr__ (PEP 562), il lit __all__. Partagée avec
|
| 84 |
+
# validation/harness.py — NE PAS dupliquer ailleurs.
|
| 85 |
+
KNOWN_PXS_HELPERS = [
|
| 86 |
+
"pxs_config", "pxsl_latex", "pxsl_sign", "pxsl_format_number",
|
| 87 |
+
"pxsl_latex_with_formatting", "pxsl_latex_avec_formatage",
|
| 88 |
+
"pxsl_latex_coefficient", "pxsl_to_rational_or_symbol",
|
| 89 |
+
"pxsl_solve_general_inequality", "pxsl_Rational",
|
| 90 |
+
"pxs_is_reductible_sqrt", "pxs_separate_factors",
|
| 91 |
+
"pxs_explain_IBP", "pxsl_par", "pxsl_final_sentence",
|
| 92 |
+
"pxsl_pow", "pxsl_matrix", "pxsl_mat", "pxsl_sum_matrix",
|
| 93 |
+
"pxsl_prod_scalar_matrix", "pxsl_prod_matrix", "pxsl_ax",
|
| 94 |
+
"pxsl_system_lin", "pxsl_double_matrix", "pxsl_lines_op",
|
| 95 |
+
"pxsl_resol_system", "pxs_steps_invert_matrix", "pxs_compute_ech",
|
| 96 |
+
"pxs_compute_ech_reduite", "pxs_system_simpl", "pxs_commute_matrix",
|
| 97 |
+
"pxsl_pow_matrix", "pxs_invertible_matrix", "pxs_diag_matrix",
|
| 98 |
+
"randmatrixrect", "pxs_finiterv", "pxsl_law", "pxsl_moment",
|
| 99 |
+
"pxsl_scalar_product", "pxs_simul_law", "pxs_fct_finiterv",
|
| 100 |
+
"pxsl_res_num", "pxsl_sum_vector", "pxs_nvirgzero", "pxsl_num",
|
| 101 |
+
"pxs_gauss_jordan", "pxs_construct_RREF",
|
| 102 |
+
"pxs_repeat_generate_sys", "pxs_break_all_colinear_rows",
|
| 103 |
+
"pxsl_mult", "pxsl_choose_udv", "pxs_lang", "myst",
|
| 104 |
+
"pxs_variation_number",
|
| 105 |
+
"pxs_Interval", "pxs_Plotable",
|
| 106 |
+
]
|
| 107 |
+
_STUB_CLASS_NAMES = ("pxs_Interval", "pxs_Plotable")
|
| 108 |
+
|
| 109 |
+
|
| 110 |
+
def _make_stub_module(name: str) -> types.ModuleType:
|
| 111 |
+
"""Module stub PEP 562 : tout attribut inconnu est fourni (passthrough /
|
| 112 |
+
classe universelle), et `__all__` couvre les helpers connus pour que
|
| 113 |
+
`from X import *` fonctionne."""
|
| 114 |
+
import re as _re
|
| 115 |
+
mod = types.ModuleType(name)
|
| 116 |
+
|
| 117 |
+
def __getattr__(attr):
|
| 118 |
+
if attr == "pxs_config":
|
| 119 |
+
return _config_stub
|
| 120 |
+
if attr == "pxs_variation_number":
|
| 121 |
+
return 1 # règle 13.2 : vaut toujours 1
|
| 122 |
+
if attr and (attr[0].isupper() or _re.match(r"pxs_[A-Z]", attr)):
|
| 123 |
+
return _StubObject # ressemble à une classe
|
| 124 |
+
return _passthrough
|
| 125 |
+
|
| 126 |
+
mod.__getattr__ = __getattr__
|
| 127 |
+
mod.__all__ = list(KNOWN_PXS_HELPERS)
|
| 128 |
+
return mod
|
| 129 |
+
|
| 130 |
+
|
| 131 |
def install_pyxiscience_stubs() -> None:
|
| 132 |
"""
|
| 133 |
Register stub modules for `pyxiscience.*` in sys.modules so that generated
|
| 134 |
Python blocks can `import` PyxiScience helpers without crashing. Idempotent.
|
| 135 |
+
UNIFIÉ avec validation/harness.py (même factory PEP 562 + __all__) : les
|
| 136 |
+
deux systèmes partagent sys.modules, le premier installé sert aux deux.
|
| 137 |
"""
|
| 138 |
global _STUBS_INSTALLED
|
| 139 |
if _STUBS_INSTALLED:
|
|
|
|
| 143 |
_STUBS_INSTALLED = True
|
| 144 |
return
|
| 145 |
|
| 146 |
+
pyxiscience = _make_stub_module("pyxiscience")
|
| 147 |
sys.modules["pyxiscience"] = pyxiscience
|
| 148 |
|
| 149 |
submodules = [
|
|
|
|
| 153 |
"Mes_fctions_d_analyse", # alias without _bis (cf. Exo 2 Am. Sud)
|
| 154 |
"Mes_fctions_d_alg_lineaire_bis",
|
| 155 |
"Mes_fctions_probabilistes_bis",
|
| 156 |
+
"Mes_fctions_generalistes", # alias historiques
|
| 157 |
+
"Mes_fctions_probabilistes",
|
| 158 |
+
"Mes_fctions_d_alg_lineaire",
|
| 159 |
]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 160 |
for sub in submodules:
|
| 161 |
+
m = _make_stub_module(f"pyxiscience.{sub}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 162 |
sys.modules[f"pyxiscience.{sub}"] = m
|
| 163 |
setattr(pyxiscience, sub, m)
|
| 164 |
|
app/web/templates/index.html
CHANGED
|
@@ -343,6 +343,24 @@
|
|
| 343 |
<ul class="filelist" id="filelist" hidden></ul>
|
| 344 |
|
| 345 |
<div class="meta">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 346 |
<div>
|
| 347 |
<label class="field-label" for="model-select">Modèle</label>
|
| 348 |
<select id="model-select" aria-label="Modèle LLM"></select>
|
|
@@ -478,6 +496,12 @@ function prettyModelName(slug) {
|
|
| 478 |
.replace(/\b\w/g, c => c.toUpperCase()).replace(/Gpt/g, "GPT");
|
| 479 |
}
|
| 480 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 481 |
/* ─── Tabs ─── */
|
| 482 |
function bindTabs() {
|
| 483 |
document.querySelectorAll("#tabs .tab").forEach(btn => {
|
|
@@ -678,7 +702,15 @@ function setMetaResult(entry) {
|
|
| 678 |
const cost = r.cost ? `<span class="pill pill--info">${r.cost.usd.toFixed(4)} $ · ${r.cost.requests} appels</span>` : "";
|
| 679 |
const lang = r.lang ? `<span class="pill pill--info">langue : ${escapeHtml(r.lang.source)} → ${escapeHtml(r.lang.target)} (${escapeHtml(r.lang.action)})</span>` : "";
|
| 680 |
const dur = r.duration_s ? `<span class="pill pill--info">${r.duration_s}s</span>` : "";
|
| 681 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 682 |
}
|
| 683 |
|
| 684 |
/* Sélecteur de fichier (batch) */
|
|
@@ -721,12 +753,24 @@ async function run() {
|
|
| 721 |
}
|
| 722 |
if (!files.length) { setStatus("error", "Saisissez un énoncé ou chargez des fichiers .md."); return; }
|
| 723 |
|
|
|
|
| 724 |
const payload = {
|
| 725 |
files,
|
| 726 |
level: document.getElementById("level").value,
|
| 727 |
lang: document.getElementById("lang-select").value,
|
| 728 |
model_idx: Number(document.getElementById("model-select").value || 0),
|
|
|
|
| 729 |
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 730 |
|
| 731 |
if (pollTimer) { clearTimeout(pollTimer); pollTimer = null; }
|
| 732 |
currentJobId = null;
|
|
|
|
| 343 |
<ul class="filelist" id="filelist" hidden></ul>
|
| 344 |
|
| 345 |
<div class="meta">
|
| 346 |
+
<div>
|
| 347 |
+
<label class="field-label" for="mode-select">Mode</label>
|
| 348 |
+
<select id="mode-select" onchange="onModeChange()">
|
| 349 |
+
<option value="pythonise">Pythonisation</option>
|
| 350 |
+
<option value="declinaisons">Déclinaisons (QCM/QAT)</option>
|
| 351 |
+
</select>
|
| 352 |
+
</div>
|
| 353 |
+
<div id="decl-types" hidden>
|
| 354 |
+
<label class="field-label">Types de déclinaison</label>
|
| 355 |
+
<div style="display:flex;gap:14px;padding:9px 2px;font-size:13px;">
|
| 356 |
+
<label style="display:flex;align-items:center;gap:6px;cursor:pointer;">
|
| 357 |
+
<input type="checkbox" id="type-qcm" checked> QCM
|
| 358 |
+
</label>
|
| 359 |
+
<label style="display:flex;align-items:center;gap:6px;cursor:pointer;">
|
| 360 |
+
<input type="checkbox" id="type-qat"> QAT
|
| 361 |
+
</label>
|
| 362 |
+
</div>
|
| 363 |
+
</div>
|
| 364 |
<div>
|
| 365 |
<label class="field-label" for="model-select">Modèle</label>
|
| 366 |
<select id="model-select" aria-label="Modèle LLM"></select>
|
|
|
|
| 496 |
.replace(/\b\w/g, c => c.toUpperCase()).replace(/Gpt/g, "GPT");
|
| 497 |
}
|
| 498 |
|
| 499 |
+
/* ─── Mode (pythonisation / déclinaisons) ─── */
|
| 500 |
+
function onModeChange() {
|
| 501 |
+
const decl = document.getElementById("mode-select").value === "declinaisons";
|
| 502 |
+
document.getElementById("decl-types").hidden = !decl;
|
| 503 |
+
}
|
| 504 |
+
|
| 505 |
/* ─── Tabs ─── */
|
| 506 |
function bindTabs() {
|
| 507 |
document.querySelectorAll("#tabs .tab").forEach(btn => {
|
|
|
|
| 702 |
const cost = r.cost ? `<span class="pill pill--info">${r.cost.usd.toFixed(4)} $ · ${r.cost.requests} appels</span>` : "";
|
| 703 |
const lang = r.lang ? `<span class="pill pill--info">langue : ${escapeHtml(r.lang.source)} → ${escapeHtml(r.lang.target)} (${escapeHtml(r.lang.action)})</span>` : "";
|
| 704 |
const dur = r.duration_s ? `<span class="pill pill--info">${r.duration_s}s</span>` : "";
|
| 705 |
+
let decl = "";
|
| 706 |
+
if (r.decl_type) {
|
| 707 |
+
const label = r.decl_type === "qcm" ? "QCM" : "QAT";
|
| 708 |
+
decl = `<span class="pill pill--info">déclinaison : ${label}</span>`;
|
| 709 |
+
if (r.decl_type === "qat" && (r.exercise || "").includes(":questionType: MCQ")) {
|
| 710 |
+
decl += `<span class="pill pill--info">repli MCQ partiel</span>`;
|
| 711 |
+
}
|
| 712 |
+
}
|
| 713 |
+
el.innerHTML = verdict + decl + cost + lang + dur;
|
| 714 |
}
|
| 715 |
|
| 716 |
/* Sélecteur de fichier (batch) */
|
|
|
|
| 753 |
}
|
| 754 |
if (!files.length) { setStatus("error", "Saisissez un énoncé ou chargez des fichiers .md."); return; }
|
| 755 |
|
| 756 |
+
const mode = document.getElementById("mode-select").value;
|
| 757 |
const payload = {
|
| 758 |
files,
|
| 759 |
level: document.getElementById("level").value,
|
| 760 |
lang: document.getElementById("lang-select").value,
|
| 761 |
model_idx: Number(document.getElementById("model-select").value || 0),
|
| 762 |
+
mode,
|
| 763 |
};
|
| 764 |
+
if (mode === "declinaisons") {
|
| 765 |
+
payload.types = {
|
| 766 |
+
qcm: document.getElementById("type-qcm").checked,
|
| 767 |
+
qat: document.getElementById("type-qat").checked,
|
| 768 |
+
};
|
| 769 |
+
if (!payload.types.qcm && !payload.types.qat) {
|
| 770 |
+
setStatus("error", "Mode Déclinaisons : coche au moins un type (QCM ou QAT).");
|
| 771 |
+
return;
|
| 772 |
+
}
|
| 773 |
+
}
|
| 774 |
|
| 775 |
if (pollTimer) { clearTimeout(pollTimer); pollTimer = null; }
|
| 776 |
currentJobId = null;
|
tests/smoke.py
ADDED
|
@@ -0,0 +1,363 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Smoke tests minimaux — AUCUN appel réseau (LLM mocké), exécution :
|
| 3 |
+
|
| 4 |
+
.venv/bin/python tests/smoke.py
|
| 5 |
+
|
| 6 |
+
Couvre : import/create_app, /health, 1 run de pipeline complet (mock LLM)
|
| 7 |
+
avec porte harnais VERTE, et les filets déterministes clés.
|
| 8 |
+
"""
|
| 9 |
+
|
| 10 |
+
import json
|
| 11 |
+
import sys
|
| 12 |
+
from pathlib import Path
|
| 13 |
+
|
| 14 |
+
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
|
| 15 |
+
|
| 16 |
+
PASS = []
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
def check(name, cond):
|
| 20 |
+
PASS.append((name, bool(cond)))
|
| 21 |
+
print(("✓" if cond else "✗"), name)
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
# ── 1. Import + /health ──────────────────────────────────────────────────────
|
| 25 |
+
from app import create_app # noqa: E402
|
| 26 |
+
|
| 27 |
+
flask_app = create_app()
|
| 28 |
+
client = flask_app.test_client()
|
| 29 |
+
health = client.get("/health").get_json()
|
| 30 |
+
check("create_app + /health", health and health["status"] == "ok")
|
| 31 |
+
models = client.get("/api/models").get_json()
|
| 32 |
+
check("/api/models expose le roster", len(models["models"]) >= 5)
|
| 33 |
+
|
| 34 |
+
# ── 2. Filets déterministes ──────────────────────────────────────────────────
|
| 35 |
+
from app.pipeline import postprocess as pp # noqa: E402
|
| 36 |
+
|
| 37 |
+
t, _ = pp.fix_dollar_digit("Prix : $3$ et ${{nAff}}$.")
|
| 38 |
+
check("fix_dollar_digit", "${}3$" in t and "${}{{nAff}}$" in t)
|
| 39 |
+
check("detect_languages both", pp.detect_languages("{fr}`Calculer`{en}`Compute` $x$") == "both")
|
| 40 |
+
check("strip_language fr", pp.strip_language("{fr}`Bonjour `{en}`Hello `", "fr").strip() == "Bonjour")
|
| 41 |
+
|
| 42 |
+
# ── 3. Pipeline complet avec LLM mocké ───────────────────────────────────────
|
| 43 |
+
SMOKE_SOURCE = """`````{exercise}
|
| 44 |
+
:title: Somme de deux entiers
|
| 45 |
+
:level: Elementary
|
| 46 |
+
|
| 47 |
+
On additionne deux entiers.
|
| 48 |
+
|
| 49 |
+
:::::{question}
|
| 50 |
+
:questionType: STQ
|
| 51 |
+
:questionId: 0
|
| 52 |
+
:questionIndex: 0
|
| 53 |
+
|
| 54 |
+
::::{questionStatement}
|
| 55 |
+
Calculer $3 + 4$.
|
| 56 |
+
::::
|
| 57 |
+
|
| 58 |
+
::::{questionHint}
|
| 59 |
+
Poser l'addition.
|
| 60 |
+
::::
|
| 61 |
+
|
| 62 |
+
::::{detailedSolution}
|
| 63 |
+
On trouve $7$.
|
| 64 |
+
::::
|
| 65 |
+
|
| 66 |
+
::::{weightDistribution}
|
| 67 |
+
:logic: 25
|
| 68 |
+
:abstraction: 25
|
| 69 |
+
:reasoning: 25
|
| 70 |
+
:calculation: 25
|
| 71 |
+
::::
|
| 72 |
+
:::::
|
| 73 |
+
`````"""
|
| 74 |
+
|
| 75 |
+
MOCK_ANALYSIS = json.dumps({
|
| 76 |
+
"exercise_type": "équation linéaire",
|
| 77 |
+
"exercise_title": "Somme de deux entiers",
|
| 78 |
+
"nb_questions": 1,
|
| 79 |
+
"variables": [{"nom": "a", "type_python": "int", "description": "1er terme",
|
| 80 |
+
"contraintes": "2..9", "plage_python": "rd.randint(2, 9)",
|
| 81 |
+
"location": "énoncé", "valeur_exemple": "3"}],
|
| 82 |
+
"needs_fraction": False, "needs_sympy": False, "needs_numpy": False,
|
| 83 |
+
"needs_matplolib": False, # typo v1 volontaire : doit être normalisée
|
| 84 |
+
"target_rules": [], "property_constraints": [],
|
| 85 |
+
"has_validated_solution_in_input": False,
|
| 86 |
+
})
|
| 87 |
+
|
| 88 |
+
MOCK_PAIR = """````{python}
|
| 89 |
+
import random as rd
|
| 90 |
+
a = rd.randint(2, 9)
|
| 91 |
+
b = rd.randint(2, 9)
|
| 92 |
+
sumAff = str(a + b)
|
| 93 |
+
globals()
|
| 94 |
+
````
|
| 95 |
+
|
| 96 |
+
On additionne deux entiers.
|
| 97 |
+
|
| 98 |
+
:::::{question}
|
| 99 |
+
:questionType: STQ
|
| 100 |
+
:questionId: 0
|
| 101 |
+
:questionIndex: 0
|
| 102 |
+
|
| 103 |
+
::::{questionStatement}
|
| 104 |
+
Calculer ${}{{a}} + {{b}}$.
|
| 105 |
+
::::
|
| 106 |
+
|
| 107 |
+
::::{questionHint}
|
| 108 |
+
Poser l'addition.
|
| 109 |
+
::::
|
| 110 |
+
|
| 111 |
+
::::{detailedSolution}
|
| 112 |
+
On trouve ${}{{a}} + {{b}} = {{sumAff}}$.
|
| 113 |
+
::::
|
| 114 |
+
|
| 115 |
+
::::{weightDistribution}
|
| 116 |
+
:logic: 25
|
| 117 |
+
:abstraction: 25
|
| 118 |
+
:reasoning: 25
|
| 119 |
+
:calculation: 25
|
| 120 |
+
::::
|
| 121 |
+
:::::"""
|
| 122 |
+
|
| 123 |
+
|
| 124 |
+
MOCK_MCQ_PAIR = """````{python}
|
| 125 |
+
import random as rd
|
| 126 |
+
a = rd.randint(2, 9)
|
| 127 |
+
b = rd.randint(2, 9)
|
| 128 |
+
sumAff = str(a + b)
|
| 129 |
+
d1Aff = str(a + b + 1) # erreur type : +1
|
| 130 |
+
d2Aff = str(a + b - 1) # erreur type : -1
|
| 131 |
+
d3Aff = str(a * b + 100) # produit décalé — toujours > somme+1 (distinct)
|
| 132 |
+
globals()
|
| 133 |
+
````
|
| 134 |
+
|
| 135 |
+
On additionne deux entiers.
|
| 136 |
+
|
| 137 |
+
:::::{question}
|
| 138 |
+
:questionType: MCQ
|
| 139 |
+
:questionId: 0
|
| 140 |
+
:questionIndex: 0
|
| 141 |
+
|
| 142 |
+
::::{questionStatement}
|
| 143 |
+
Combien vaut ${}{{a}} + {{b}}$ ?
|
| 144 |
+
::::
|
| 145 |
+
|
| 146 |
+
::::{questionHint}
|
| 147 |
+
Poser l'addition.
|
| 148 |
+
::::
|
| 149 |
+
|
| 150 |
+
::::{mcqAnswer}
|
| 151 |
+
:isRightAnswer: true
|
| 152 |
+
${}{{sumAff}}$
|
| 153 |
+
::::
|
| 154 |
+
|
| 155 |
+
::::{mcqAnswer}
|
| 156 |
+
:isRightAnswer: false
|
| 157 |
+
${}{{d1Aff}}$
|
| 158 |
+
::::
|
| 159 |
+
|
| 160 |
+
::::{mcqAnswer}
|
| 161 |
+
:isRightAnswer: false
|
| 162 |
+
${}{{d2Aff}}$
|
| 163 |
+
::::
|
| 164 |
+
|
| 165 |
+
::::{mcqAnswer}
|
| 166 |
+
:isRightAnswer: false
|
| 167 |
+
${}{{d3Aff}}$
|
| 168 |
+
::::
|
| 169 |
+
|
| 170 |
+
::::{mcqAnswer}
|
| 171 |
+
:isRightAnswer: false
|
| 172 |
+
{fr}`Aucune de ces réponses n'est correcte`{en}`None of these answers are correct`
|
| 173 |
+
::::
|
| 174 |
+
|
| 175 |
+
::::{detailedSolution}
|
| 176 |
+
On trouve ${}{{a}} + {{b}} = {{sumAff}}$.
|
| 177 |
+
::::
|
| 178 |
+
|
| 179 |
+
::::{weightDistribution}
|
| 180 |
+
:logic: 25
|
| 181 |
+
:abstraction: 25
|
| 182 |
+
:reasoning: 25
|
| 183 |
+
:calculation: 25
|
| 184 |
+
::::
|
| 185 |
+
:::::"""
|
| 186 |
+
|
| 187 |
+
MOCK_FGQ_PAIR = """````{python}
|
| 188 |
+
import random as rd
|
| 189 |
+
a = rd.randint(2, 9)
|
| 190 |
+
b = rd.randint(2, 9)
|
| 191 |
+
sumAff = str(a + b)
|
| 192 |
+
globals()
|
| 193 |
+
````
|
| 194 |
+
|
| 195 |
+
On additionne deux entiers.
|
| 196 |
+
|
| 197 |
+
:::::{question}
|
| 198 |
+
:questionType: FGQ
|
| 199 |
+
:questionId: 0
|
| 200 |
+
:questionIndex: 0
|
| 201 |
+
:solution: [["ord","${{sumAff}}$"],["0"]]
|
| 202 |
+
|
| 203 |
+
::::{questionStatement}
|
| 204 |
+
Calculer ${}{{a}} + {{b}}$.
|
| 205 |
+
|
| 206 |
+
$s =$ {input}`||110`
|
| 207 |
+
::::
|
| 208 |
+
|
| 209 |
+
::::{questionHint}
|
| 210 |
+
Poser l'addition.
|
| 211 |
+
::::
|
| 212 |
+
|
| 213 |
+
::::{displayedSolution}
|
| 214 |
+
$s = {{sumAff}}$
|
| 215 |
+
::::
|
| 216 |
+
|
| 217 |
+
::::{detailedSolution}
|
| 218 |
+
On trouve ${}{{a}} + {{b}} = {{sumAff}}$.
|
| 219 |
+
::::
|
| 220 |
+
|
| 221 |
+
::::{weightDistribution}
|
| 222 |
+
:logic: 15
|
| 223 |
+
:abstraction: 20
|
| 224 |
+
:reasoning: 20
|
| 225 |
+
:calculation: 45
|
| 226 |
+
::::
|
| 227 |
+
:::::"""
|
| 228 |
+
|
| 229 |
+
ANALYSIS_CALLS = {"n": 0}
|
| 230 |
+
|
| 231 |
+
|
| 232 |
+
def mock_llm(prompt, model_idx=0, temperature=0.0, max_tokens=4096,
|
| 233 |
+
image_b64=None, system_prompt="", reasoning=False):
|
| 234 |
+
if "expert en analyse d'exercices" in prompt:
|
| 235 |
+
ANALYSIS_CALLS["n"] += 1
|
| 236 |
+
return MOCK_ANALYSIS
|
| 237 |
+
if "auditeur PyxiScience" in prompt:
|
| 238 |
+
return json.dumps({"verdict": "OK", "issues": []})
|
| 239 |
+
if "Tu déclines un exercice" in prompt:
|
| 240 |
+
return MOCK_MCQ_PAIR if "QCM (MCQ)" in prompt else MOCK_FGQ_PAIR
|
| 241 |
+
if "RÈGLES D'ASSEMBLAGE PAR PAIRE" in prompt:
|
| 242 |
+
return MOCK_PAIR
|
| 243 |
+
return "{}"
|
| 244 |
+
|
| 245 |
+
|
| 246 |
+
import app.pipeline.analyze as analyze # noqa: E402
|
| 247 |
+
import app.pipeline.audit as audit # noqa: E402
|
| 248 |
+
import app.pipeline.generate as generate # noqa: E402
|
| 249 |
+
import app.pipeline.orchestrator as orchestrator # noqa: E402
|
| 250 |
+
|
| 251 |
+
analyze.process_with_openrouter = mock_llm
|
| 252 |
+
audit.process_with_openrouter = mock_llm
|
| 253 |
+
generate.process_with_openrouter = mock_llm
|
| 254 |
+
orchestrator.process_with_openrouter = mock_llm
|
| 255 |
+
analyze.enrich_exercise_with_notions = lambda *a, **k: ("(notions mockées)", "NOTION_TEST")
|
| 256 |
+
analyze.retrieve_functions_context = lambda **k: {"catalogue": "(catalogue mocké)"}
|
| 257 |
+
|
| 258 |
+
result = orchestrator.run_exercise(
|
| 259 |
+
content=SMOKE_SOURCE, filename="smoke.md", level="", model_idx=0, lang="fr")
|
| 260 |
+
|
| 261 |
+
check("pipeline : exercice produit", bool(result["exercise"].strip()))
|
| 262 |
+
check("pipeline : fence 4 backticks", "````{python}" in result["exercise"])
|
| 263 |
+
check("pipeline : se termine par `````", result["exercise"].rstrip().endswith("`````"))
|
| 264 |
+
check("pipeline : globals() présent", "globals()" in result["exercise"])
|
| 265 |
+
check("pipeline : typo needs_matplolib normalisée",
|
| 266 |
+
"needs_matplolib" not in result["analysis"] and result["analysis"]["needs_matplotlib"] is False)
|
| 267 |
+
check("pipeline : harnais VERT", result["harness"]["ok"])
|
| 268 |
+
check("pipeline : coût exposé", "usd" in result["cost"])
|
| 269 |
+
check("pipeline : langue exposée", result["lang"]["target"] == "fr")
|
| 270 |
+
|
| 271 |
+
# En-tête {exercise} complet et bien formé
|
| 272 |
+
ex = result["exercise"]
|
| 273 |
+
check("header : enveloppe `````{exercise} en tête", ex.lstrip().startswith("`````{exercise}"))
|
| 274 |
+
for field in (":id:", ":title:", ":modules:", ":recommendedExecutionTime:",
|
| 275 |
+
":level:", ":chap:", ":involvedConcepts:", ":originalSource:", ":visibility:"):
|
| 276 |
+
check(f"header : champ {field} présent", field in ex.split("````{python}")[0])
|
| 277 |
+
check("header : level mappé (level='' → Elementary)", ":level: Elementary" in ex)
|
| 278 |
+
check("header : visibility All", ":visibility: All" in ex)
|
| 279 |
+
check("header : concepts = notions", "NOTION_TEST" in ex)
|
| 280 |
+
check("header : une seule enveloppe {exercise}", ex.count("`````{exercise}") == 1)
|
| 281 |
+
|
| 282 |
+
# ── 3bis. Mode déclinaisons (QCM + QAT, LLM mocké, analyse partagée) ─────────
|
| 283 |
+
ANALYSIS_CALLS["n"] = 0
|
| 284 |
+
decl_results = orchestrator.run_declinaisons(
|
| 285 |
+
content=SMOKE_SOURCE, filename="smoke.md", level="", model_idx=0,
|
| 286 |
+
lang="fr", types=["qcm", "qat"])
|
| 287 |
+
check("déclinaisons : 2 sorties (QCM + QAT)", len(decl_results) == 2)
|
| 288 |
+
check("déclinaisons : analyse partagée (1 seul appel)", ANALYSIS_CALLS["n"] == 1)
|
| 289 |
+
|
| 290 |
+
qcm = dict(decl_results)["qcm"]
|
| 291 |
+
qat = dict(decl_results)["qat"]
|
| 292 |
+
check("QCM : harnais VERT", qcm["harness"]["ok"])
|
| 293 |
+
check("QCM : 5 options, 1 seule bonne", qcm["exercise"].count("{mcqAnswer}") == 5
|
| 294 |
+
and qcm["exercise"].count(":isRightAnswer: true") == 1)
|
| 295 |
+
check("QCM : « None » en dernière option",
|
| 296 |
+
"Aucune de ces réponses" in qcm["exercise"].split(":isRightAnswer: false")[-1])
|
| 297 |
+
check("QCM : titre suffixé - MCQ", " - MCQ" in qcm["exercise"].split("````{python}")[0])
|
| 298 |
+
check("QAT : harnais VERT", qat["harness"]["ok"])
|
| 299 |
+
check("QAT : :solution: + {input} présents",
|
| 300 |
+
':solution: [["ord"' in qat["exercise"] and "{input}`" in qat["exercise"])
|
| 301 |
+
check("QAT : displayedSolution présent", "{displayedSolution}" in qat["exercise"])
|
| 302 |
+
check("déclinaisons : decl_type exposé", qcm["decl_type"] == "qcm" and qat["decl_type"] == "qat")
|
| 303 |
+
|
| 304 |
+
# Harnais étendu : un MCQ avec collision d'options doit être ROUGE.
|
| 305 |
+
from app.validation import harness as _harness # noqa: E402
|
| 306 |
+
|
| 307 |
+
_collision = qcm["exercise"].replace("{{d1Aff}}", "{{sumAff}}") # distracteur == bonne réponse
|
| 308 |
+
_rep = _harness.validate_text(_collision, seeds=10)
|
| 309 |
+
check("harnais étendu : collision d'options MCQ détectée (ROUGE)",
|
| 310 |
+
not _rep["ok"] and _rep["n_mcq_collisions"] > 0)
|
| 311 |
+
_two_true = qcm["exercise"].replace(":isRightAnswer: false", ":isRightAnswer: true", 1)
|
| 312 |
+
_rep2 = _harness.validate_text(_two_true, seeds=5)
|
| 313 |
+
check("harnais étendu : 2 bonnes réponses détectées (statique)",
|
| 314 |
+
not _rep2["ok"] and any("isRightAnswer" in e for e in _rep2["static_errors"]))
|
| 315 |
+
_bad_arity = qat["exercise"].replace('[["ord","${{sumAff}}$"],["0"]]',
|
| 316 |
+
'[["ord","${{sumAff}}$","$2$"],["0","0"]]')
|
| 317 |
+
_rep3 = _harness.validate_text(_bad_arity, seeds=5)
|
| 318 |
+
check("harnais étendu : arité FGQ incohérente détectée",
|
| 319 |
+
not _rep3["ok"] and any("arité" in e for e in _rep3["static_errors"]))
|
| 320 |
+
|
| 321 |
+
# Validation API du mode (sans lancer de job).
|
| 322 |
+
r_bad_mode = client.post("/api/jobs", json={"content": "x", "mode": "zzz"})
|
| 323 |
+
check("API : mode invalide → 400", r_bad_mode.status_code == 400)
|
| 324 |
+
r_no_types = client.post("/api/jobs", json={"content": "x", "mode": "declinaisons", "types": {}})
|
| 325 |
+
check("API : declinaisons sans type → 400", r_no_types.status_code == 400)
|
| 326 |
+
|
| 327 |
+
# ── 4. Téléchargement ZIP (endpoint, sans LLM) ───────────────────────────────
|
| 328 |
+
import io as _io # noqa: E402
|
| 329 |
+
import zipfile as _zipfile # noqa: E402
|
| 330 |
+
|
| 331 |
+
import app.server as _server # noqa: E402
|
| 332 |
+
|
| 333 |
+
_fake = {
|
| 334 |
+
"status": "done", "step_label": "Terminé", "current_file": "b.md",
|
| 335 |
+
"files_total": 2, "files_done": 2, "error": None, "summary": {},
|
| 336 |
+
"results": [
|
| 337 |
+
{"filename": "a.md", "status": "done",
|
| 338 |
+
"result": {"exercise": result["exercise"], "warnings": [],
|
| 339 |
+
"harness": {"ok": True, "seeds": 100}, "cost": {"usd": 0.01}}},
|
| 340 |
+
{"filename": "a.md", "status": "done", # collision de nom volontaire
|
| 341 |
+
"result": {"exercise": "````{python}\nglobals()\n````\n`````", "warnings": [{}],
|
| 342 |
+
"harness": {"ok": False, "seeds": 100}, "cost": {"usd": 0.02}}},
|
| 343 |
+
{"filename": "c.md", "status": "error", "error": "boom"},
|
| 344 |
+
],
|
| 345 |
+
}
|
| 346 |
+
with _server._JOBS_LOCK:
|
| 347 |
+
_server._JOBS["smoketest"] = _fake
|
| 348 |
+
resp = client.get("/api/jobs/smoketest/download")
|
| 349 |
+
check("ZIP : 200 + mimetype zip", resp.status_code == 200 and "zip" in resp.mimetype)
|
| 350 |
+
zf = _zipfile.ZipFile(_io.BytesIO(resp.data))
|
| 351 |
+
names = zf.namelist()
|
| 352 |
+
check("ZIP : 2 .md (collision dédupliquée) + récap",
|
| 353 |
+
"a_pythonise.md" in names and "a_pythonise_2.md" in names
|
| 354 |
+
and "_recapitulatif.md" in names)
|
| 355 |
+
check("ZIP : 404 si job inconnu", client.get("/api/jobs/zzz/download").status_code == 404)
|
| 356 |
+
|
| 357 |
+
# ── Bilan ────────────────────────────────────────────────────────────────────
|
| 358 |
+
failed = [n for n, ok in PASS if not ok]
|
| 359 |
+
print(f"\n{len(PASS) - len(failed)}/{len(PASS)} smoke tests verts")
|
| 360 |
+
if failed:
|
| 361 |
+
print("ÉCHECS :", failed)
|
| 362 |
+
sys.exit(1)
|
| 363 |
+
print("✅ SMOKE OK")
|