Lean 4 - Installation et Configuration

Navigation : ← (debut) | Index | Lean-02-Dependent-Types-Lean →


Ce notebook configure l’environnement pour la serie de notebooks Lean qui explore le proof assistant Lean 4 pour la vérification formelle de preuves mathematiques.

IMPORTANT: Ce notebook Lean-01-Setup-Lean-Python configure uniquement l’environnement. Les exemples de preuves formelles se trouvent dans les notebooks suivants.

Contenu de ce notebook: * Introduction a Lean 4 * Diagnostic de l’environnement * Installation automatique des composants manquants * vérification et premiers tests

Serie complete Lean: 1. Lean-01-Setup-Lean-Python (ce notebook) - Installation et configuration 2. Lean-02-Dependent-Types-Lean - Système de types dependants 3. Lean-03-Propositions-Proofs-Lean - Logique propositionnelle et preuves 4. Lean-04-Quantifiers-Lean - Logique du premier ordre 5. Lean-05-Tactics-Lean - Construction de preuves par tactiques 6. Lean-06-Mathlib-Essentials-Lean - Bibliotheque mathematique Mathlib4 7. Lean-07-LLM-Integration-Lean-Python - Integration avec les LLMs 7b. Lean-07b-Examples-Python - Exemples grandeur nature (AlphaProof, LeanDojo) 8. Lean-08-Agentic-Proving-Python - Agents autonomes pour preuves 9. Lean-09-SK-Multi-Agents-Lean-Python - Semantic Kernel multi-agents 10. Lean-10-LeanDojo - LeanDojo pour ML/LLM theorem proving 11. Lean-11-TorchLean - PyTorch + Lean (reasoning vs learning) 12. Lean-12-Sensitivity-Theorem - théorème de sensibilite 13. Lean-15-Grothendieck-Tribute - Hommage a Alexandre Grothendieck 14b. Lean-16b-Conway-Game-of-Life-Lean - Hommage a John Conway (Game of Life) 15. Lean-13-Kochen-Specker - théorème de Kochen-Specker

Public vise: étudiants, chercheurs interesses par la vérification formelle et les assistants de preuves.

(Base sur Lean 4 - syntaxe moderne)


Plan de ce Notebook

  1. Introduction a Lean 4
  2. Diagnostic de l’environnement
  3. Installation automatique
  4. vérification de l’installation
  5. Vérifier les composants avant une première preuve
  6. Configuration VSCode (optionnel)
  7. Exercice : premier théorème
  8. Ressources et documentation

1. Introduction a Lean 4

Lean 4 est un proof assistant (assistant de preuves) et un langage de programmation fonctionnel moderne. Il est base sur le Calcul des Constructions (Calculus of Constructions), une théorie des types dependants très expressive.

Cas d’usage

  • vérification formelle de preuves mathematiques
  • vérification de programmes (preuve de correction)
  • Formalisation de mathematiques (Mathlib4 contient 4M+ lignes)
  • Enseignement de la logique et des mathematiques formelles

Lean vs autres proof assistants

Aspect Lean 4 Coq Isabelle Z3
Type Proof assistant Proof assistant Proof assistant SMT Solver
Fondation Calcul des Constructions CoC + Inductive HOL SAT/SMT
Tactiques Oui Oui Oui Automatique
Bibliotheque Mathlib4 (4M+ lignes) Standard Library AFP -
Performance Très rapide (Lean 4) Moyenne Rapide Très rapide
Courbe d’apprentissage Moyenne-Elevee Elevee Moyenne Faible

Avantages de Lean 4

  • Syntaxe moderne et expressive
  • Performances excellentes (compile)
  • Communaute active (Mathlib4, Zulip chat)
  • Integration LLM (LeanCopilot, AlphaProof)
  • vérification formelle garantie (impossible de prouver du faux)

Environnement de référence (kernel et plateforme)

Ce notebook est conçu pour tourner multiplateforme (Windows, macOS, Linux), mais les sorties committées ont été produites sous Windows 11 (kernel python3). Si vous utilisez un autre environnement :

  • Windows natif (kernel python3) : exécutez tel quel ; le diagnostic sortira les chemins elanin\lean.EXE visibles dans les sorties committées.
  • WSL Ubuntu (kernel python3-wsl) : passez le kernel à python3-wsl et ré-exécutez ; les chemins deviendront /home/<user>/.elan/bin/lean.
  • macOS / Linux natif (kernel python3) : passez simplement au kernel python3 standard ; exécutez tel quel.

Règle d’alignement (issue #18007) : kernelspec et outputs doivent refléter le même environnement à la lecture du notebook. Si vous régénérez les sorties sous un autre kernel, alignez metadata.kernelspec.name en conséquence.

2. Diagnostic de l’environnement

Cette section analyse votre environnement et identifie les composants manquants.

Composants requis

Composant Description Requis
Python 3.8+ Environnement d’exécution Oui
elan Gestionnaire de versions Lean Oui
Lean 4 Le proof assistant Oui
lean4_jupyter Kernel Jupyter pour Lean Oui
Kernel enregistre Lean dans Jupyter Oui

Executez la cellule suivante pour diagnostiquer votre environnement :

Plateformes supportées

Ce notebook supporte Windows, macOS et Linux. Les commandes s’adaptent automatiquement via platform.system() :

Plateforme Installation Kernel Jupyter
Windows elan-init.ps1 via PowerShell lean4_jupyter natif (avec patch SIGPIPE) ou kernel WSL (recommandé)
macOS elan-init.sh via curl lean4_jupyter natif
Linux elan-init.sh via curl lean4_jupyter natif

Les sections marquées (Windows uniquement) contiennent du code wsl.exe / powershell ; elles affichent un message d’information et s’arrêtent proprement sur Mac/Linux.

Exécutez la cellule de diagnostic ci-dessous pour identifier votre plateforme.

"""
Diagnostic complet de l'environnement Lean 4
Detecte les composants manquants et propose l'installation
"""

import subprocess
import shutil
import sys
import os
import platform
from pathlib import Path
from dataclasses import dataclass
from typing import Optional, List, Tuple

# Delai maximal accorde a une sonde de version. Le shim elan est la sonde la plus
# lente mesuree : 30 essais sous charge nominale donnent 0,58 s au maximum. Les
# echecs observes sous timeout=5 arrivent en salves de charge, donc bien au-dela
# du cout nominal. 30 s borne l'attente pathologique sans mordre le cas nominal.
PROBE_TIMEOUT_S = 30

@dataclass
class ComponentStatus:
    """Status d'un composant

    `measured=False` signale une sonde qui a echoue (delai depasse, erreur) :
    `installed` n'est alors pas une mesure, seulement un defaut de lecture.
    """
    name: str
    installed: bool
    version: Optional[str] = None
    path: Optional[str] = None
    error: Optional[str] = None
    can_auto_install: bool = False
    measured: bool = True

def check_python() -> ComponentStatus:
    """Verifie Python >= 3.8"""
    version = f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}"
    ok = sys.version_info >= (3, 8)
    return ComponentStatus(
        name="Python",
        installed=ok,
        version=version,
        path=sys.executable,
        error=None if ok else "Python 3.8+ requis"
    )

def _check_binary(name: str, binary: str, on_unix_source_path: str, missing_hint: str) -> ComponentStatus:
    """Sonde un binaire (elan, lean) par ses deux voies d'acces.

    Voie 1 -- Linux/WSL : `source ~/.elan/env`, ou elan n'exporte pas toujours son
    repertoire dans le PATH du noyau.
    Voie 2 -- methode standard : `shutil.which` puis appel direct.

    Regle de verdict : une sonde qui ECHOUE (delai depasse, erreur d'execution) ne
    devient jamais une absence. "Absent" n'est rendu que si aucune voie n'a repondu
    et qu'aucune sonde n'a echoue -- on ne peut pas declarer absent ce qu'on n'a pas
    reussi a mesurer. Le code 127 ("commande introuvable") est la seule reponse qui
    vaut absence reelle sur la voie source.
    """
    unmeasured_reason = None

    def first_line(out: str) -> str:
        return out.strip().split('\n')[0]

    if platform.system() in ("Linux", "Darwin"):
        try:
            result = subprocess.run(
                ["bash", "-c", f"source ~/.elan/env 2>/dev/null && {binary} --version"],
                capture_output=True, text=True, timeout=PROBE_TIMEOUT_S, encoding="utf-8"
            )
        except subprocess.TimeoutExpired:
            unmeasured_reason = f"aucune reponse en {PROBE_TIMEOUT_S} s"
        except OSError as exc:
            unmeasured_reason = f"{type(exc).__name__} : {exc}"
        else:
            if result.returncode == 0:
                return ComponentStatus(name=name, installed=True,
                                       version=first_line(result.stdout),
                                       path=on_unix_source_path)
            if result.returncode != 127:
                unmeasured_reason = f"{binary} --version a rendu le code {result.returncode}"

    binary_path = shutil.which(binary)
    if binary_path:
        try:
            result = subprocess.run([binary, "--version"], capture_output=True, text=True,
                                    timeout=PROBE_TIMEOUT_S, encoding="utf-8")
            version = first_line(result.stdout) if result.returncode == 0 else "unknown"
            return ComponentStatus(name=name, installed=True, version=version, path=binary_path)
        except Exception as e:
            # Le binaire est la, la sonde n'a pas repondu : non mesure, jamais absent.
            return ComponentStatus(name=name, installed=True, path=binary_path,
                                   error=str(e), measured=False)

    if unmeasured_reason is not None:
        return ComponentStatus(name=name, installed=False, error=unmeasured_reason,
                               can_auto_install=False, measured=False)

    return ComponentStatus(
        name=name,
        installed=False,
        error=missing_hint,
        can_auto_install=True
    )

def check_elan() -> ComponentStatus:
    """Verifie si elan est installe"""
    return _check_binary("elan", "elan", "~/.elan/bin/elan", "elan non trouve dans PATH")

def check_lean() -> ComponentStatus:
    """Verifie si Lean 4 est installe"""
    return _check_binary("Lean 4", "lean", "~/.elan/bin/lean",
                         "lean non trouve (installez via elan)")

def check_lean4_jupyter() -> ComponentStatus:
    """Verifie si lean4_jupyter est installe"""
    try:
        import importlib.metadata
        version = importlib.metadata.version("lean4-jupyter")
        return ComponentStatus(name="lean4_jupyter", installed=True, version=version)
    except importlib.metadata.PackageNotFoundError:
        pass
    except Exception:
        pass

    # Fallback: essayer l'import
    try:
        import lean4_jupyter
        return ComponentStatus(name="lean4_jupyter", installed=True, version="unknown")
    except ImportError:
        return ComponentStatus(
            name="lean4_jupyter",
            installed=False,
            error="Module non installe",
            can_auto_install=True
        )

def check_jupyter_kernel() -> ComponentStatus:
    """Verifie si le kernel Lean est enregistre dans Jupyter"""
    try:
        result = subprocess.run(
            [sys.executable, "-m", "jupyter", "kernelspec", "list", "--json"],
            capture_output=True, text=True, timeout=PROBE_TIMEOUT_S, encoding="utf-8"
        )
        if result.returncode == 0:
            import json
            kernels = json.loads(result.stdout)
            kernel_names = list(kernels.get("kernelspecs", {}).keys())
            # Chercher lean4, lean4-wsl ou lean
            lean_kernel = next((k for k in kernel_names if "lean" in k.lower()), None)
            if lean_kernel:
                return ComponentStatus(
                    name="Kernel Jupyter",
                    installed=True,
                    version=lean_kernel,
                    path=kernels["kernelspecs"][lean_kernel].get("resource_dir")
                )
        return ComponentStatus(
            name="Kernel Jupyter",
            installed=False,
            error="Kernel Lean non enregistre",
            can_auto_install=True
        )
    except Exception as e:
        # Sonde en echec : le kernel n'est pas mesure. On ne propose pas de
        # l'installer -- installer ne repond pas a une sonde qui n'a pas repondu.
        return ComponentStatus(
            name="Kernel Jupyter",
            installed=False,
            error=f"sonde en echec ({type(e).__name__}) : {e}",
            can_auto_install=False,
            measured=False
        )

def run_diagnostic() -> Tuple[List[ComponentStatus], bool, List[str]]:
    """Execute le diagnostic complet"""
    print("=" * 60)
    print("    DIAGNOSTIC DE L'ENVIRONNEMENT LEAN 4")
    print("=" * 60)
    print(f"\nSysteme: {platform.system()} {platform.release()}")
    print(f"Architecture: {platform.machine()}")
    print()

    components = [
        check_python(),
        check_elan(),
        check_lean(),
        check_lean4_jupyter(),
        check_jupyter_kernel()
    ]

    print("-" * 60)
    print("Composant              Status         Version/Info")
    print("-" * 60)

    all_ok = True
    missing_installable = []
    indeterminate = []

    for c in components:
        if not c.measured:
            status = "[INDETERMINE]"
        elif c.installed:
            status = "[OK]"
        else:
            status = "[MANQUANT]"
        info = c.version or c.error or ""
        if c.path and c.installed:
            info = f"{info}" if info else c.path
        print(f"{c.name:<20}  {status:<14} {info[:40]}")

        if not c.measured:
            # Sonde ratee : ni present ni absent, et aucune installation proposee.
            indeterminate.append(c.name)
        elif not c.installed:
            all_ok = False
            if c.can_auto_install:
                missing_installable.append(c.name)

    print("-" * 60)

    if indeterminate:
        print(f"\n[~] Sonde(s) sans reponse : {', '.join(indeterminate)}")
        print(f"    Aucune reponse en {PROBE_TIMEOUT_S} s : ce n'est PAS une absence, et le")
        print("    composant n'a pas pu etre mesure. Re-executez ce diagnostic avant")
        print("    d'envisager une installation.")

    if all_ok and not indeterminate:
        print("\n[OK] Tous les composants sont installes !")
        print("     Vous pouvez passer aux notebooks suivants.")
    elif all_ok:
        print("\n[~] Aucun composant n'est mesure manquant (voir les sondes sans reponse ci-dessus).")
    else:
        print(f"\n[!] {len(missing_installable)} composant(s) manquant(s) peuvent etre installes automatiquement:")
        for name in missing_installable:
            print(f"    - {name}")
        print("\n    Executez la cellule 'Installation automatique' ci-dessous.")

    print("=" * 60)

    return components, all_ok, indeterminate

# Stocker le resultat pour utilisation ulterieure
DIAGNOSTIC_RESULTS, ALL_COMPONENTS_OK, INDETERMINATE_COMPONENTS = run_diagnostic()
============================================================
    DIAGNOSTIC DE L'ENVIRONNEMENT LEAN 4
============================================================

Systeme: Windows 11
Architecture: AMD64

------------------------------------------------------------
Composant              Status         Version/Info
------------------------------------------------------------
Python                [OK]           3.13.13
elan                  [OK]           elan 4.1.2 (58e8d545e 2025-05-26)
Lean 4                [OK]           Lean (version 4.34.1, x86_64-w64-windows
lean4_jupyter         [OK]           0.0.2
Kernel Jupyter        [OK]           lean4
------------------------------------------------------------

[OK] Tous les composants sont installes !
     Vous pouvez passer aux notebooks suivants.
============================================================

3. Installation automatique

Cette section installe automatiquement les composants manquants detectes ci-dessus.

Processus d’installation

L’installation se fait dans l’ordre suivant : 1. elan - Telecharge et installe le gestionnaire de versions Lean 2. Lean 4 - Installe la version stable via elan 3. lean4_jupyter - Installe le module Python via pip 4. Kernel Jupyter - Enregistre le kernel Lean dans Jupyter

Note Windows: L’installation de elan necessite PowerShell et peut demander une confirmation.

Executez la cellule suivante pour installer les composants manquants :

"""
Installation automatique des composants manquants
"""

import subprocess
import sys
import os
import platform
import shutil
import tempfile
import urllib.request
from pathlib import Path
from typing import Optional

# Delai maximal accorde a une sonde de version. Le shim elan est la sonde la plus
# lente mesuree : 30 essais sous charge nominale donnent 0,58 s au maximum. Les
# echecs observes sous timeout=5 arrivent en salves de charge, donc bien au-dela
# du cout nominal. 30 s borne l'attente pathologique sans mordre le cas nominal.
PROBE_TIMEOUT_S = 30


class LeanInstaller:
    """Installateur automatique pour l'environnement Lean 4"""
    
    def __init__(self):
        self.system = platform.system()
        self.is_windows = self.system == "Windows"
        self.is_macos = self.system == "Darwin"
        self.is_linux = self.system == "Linux"
        self.results = []
        
    def log(self, message: str, level: str = "INFO"):
        """Log un message"""
        prefix = {"INFO": "[*]", "OK": "[+]", "ERROR": "[!]", "WARN": "[~]"}.get(level, "[*]")
        print(f"{prefix} {message}")
        self.results.append((level, message))
    
    def probe_present(self, binary: str, description: str) -> Optional[bool]:
        """Sonde la presence d'un binaire.

        Rend True (present), False (absent), ou None : la sonde a ECHOUE
        (delai depasse, erreur d'execution) et n'a rien etabli -- ni presence,
        ni absence.

        Sur Linux, elan n'exporte pas toujours son repertoire dans le PATH du
        noyau : la sonde passe par `source ~/.elan/env`. Le code 127
        ("commande introuvable") est la seule reponse qui vaut absence reelle.
        """
        if self.is_linux:
            try:
                result = subprocess.run(
                    ["bash", "-c", f"source ~/.elan/env 2>/dev/null && {binary} --version"],
                    capture_output=True, text=True, timeout=PROBE_TIMEOUT_S, encoding="utf-8"
                )
            except subprocess.TimeoutExpired:
                self.log(f"{description} : aucune reponse en {PROBE_TIMEOUT_S} s -- "
                         "sonde en echec, ce n'est PAS une absence", "WARN")
                return None
            except OSError as exc:
                self.log(f"{description} : sonde impossible ({type(exc).__name__}) -- "
                         "ce n'est PAS une absence", "WARN")
                return None
            if result.returncode == 0:
                return True
            if result.returncode != 127:
                self.log(f"{description} : sonde sans reponse exploitable "
                         f"(code {result.returncode}) -- ce n'est PAS une absence", "WARN")
                return None
            return False
        return bool(shutil.which(binary))

    def run_command(self, cmd: list, description: str, timeout: int = 300, shell: bool = False, check_elan_env: bool = False) -> bool:
        """Execute une commande et retourne True si succes"""
        self.log(f"{description}...")
        try:
            # Sur Linux, sourcer .elan/env si demande
            if self.is_linux and check_elan_env:
                bash_cmd = f"source ~/.elan/env 2>/dev/null && {' '.join(cmd)}"
                cmd = ["bash", "-c", bash_cmd]
            
            if shell and self.is_windows:
                result = subprocess.run(cmd, capture_output=True, text=True, timeout=timeout, shell=True, encoding="utf-8")
            else:
                result = subprocess.run(cmd, capture_output=True, text=True, timeout=timeout, encoding="utf-8")
            
            if result.returncode == 0:
                self.log(f"{description} - OK", "OK")
                return True
            else:
                stderr = result.stderr[:200] if result.stderr else ""
                if stderr:
                    self.log(f"{description} - Erreur: {stderr}", "ERROR")
                return False
        except subprocess.TimeoutExpired:
            self.log(f"{description} - Timeout apres {timeout}s", "ERROR")
            return False
        except Exception as e:
            self.log(f"{description} - Exception: {e}", "ERROR")
            return False
    
    def install_elan(self) -> bool:
        """Installe elan (gestionnaire de versions Lean)"""
        # Verifier si deja installe (avec .elan/env sur Linux)
        present = self.probe_present("elan", "elan")
        if present is True:
            self.log("elan deja installe", "OK")
            return True
        if present is None:
            self.log("elan : non mesure (sonde en echec) -- installation NON tentee, "
                     "re-executez cette cellule avant de conclure a une absence", "WARN")
            return None
        
        self.log("Installation de elan...")
        if self.is_windows:
            return self._install_elan_windows()
        else:
            return self._install_elan_unix()
    
    def _install_elan_windows(self) -> bool:
        """Installation elan sur Windows via PowerShell"""
        try:
            script_url = "https://raw.githubusercontent.com/leanprover/elan/master/elan-init.ps1"
            with tempfile.NamedTemporaryFile(mode='w', suffix='.ps1', delete=False) as f:
                script_path = f.name
            
            self.log(f"Telechargement du script elan depuis {script_url}")
            urllib.request.urlretrieve(script_url, script_path)
            
            self.log("Execution du script d'installation (cela peut prendre quelques minutes)...")
            escaped_path = script_path.replace("'", "''")
            ps_command = f"& '{escaped_path}' -NoPrompt:$true -DefaultToolchain none"
            cmd = ["powershell", "-ExecutionPolicy", "Bypass", "-Command", ps_command]
            
            result = subprocess.run(cmd, capture_output=True, text=True, timeout=600, encoding="utf-8")
            os.unlink(script_path)
            
            if result.returncode == 0:
                elan_bin = Path.home() / ".elan" / "bin"
                if elan_bin.exists():
                    os.environ["PATH"] = str(elan_bin) + os.pathsep + os.environ.get("PATH", "")
                    self.log("elan installe avec succes", "OK")
                    return True
            
            self.log(f"Erreur installation elan: {result.stderr[:300]}", "ERROR")
            return False
        except Exception as e:
            self.log(f"Exception lors de l'installation elan: {e}", "ERROR")
            return False
    
    def _install_elan_unix(self) -> bool:
        """Installation elan sur Linux/macOS"""
        try:
            cmd = ["sh", "-c", "curl -sSf https://raw.githubusercontent.com/leanprover/elan/master/elan-init.sh | sh -s -- -y --default-toolchain none"]
            result = subprocess.run(cmd, capture_output=True, text=True, timeout=600, encoding="utf-8")
            
            if result.returncode == 0:
                elan_bin = Path.home() / ".elan" / "bin"
                if elan_bin.exists():
                    os.environ["PATH"] = str(elan_bin) + os.pathsep + os.environ.get("PATH", "")
                    self.log("elan installe avec succes", "OK")
                    return True
            
            self.log(f"Erreur: {result.stderr[:200]}", "ERROR")
            return False
        except Exception as e:
            self.log(f"Exception: {e}", "ERROR")
            return False
    
    def install_lean4(self) -> bool:
        """Installe Lean 4 stable via elan"""
        # Verifier si deja installe (avec .elan/env sur Linux)
        present = self.probe_present("lean", "Lean 4")
        if present is True:
            self.log("Lean 4 deja installe", "OK")
            return True
        if present is None:
            self.log("Lean 4 : non mesure (sonde en echec) -- installation NON tentee, "
                     "re-executez cette cellule avant de conclure a une absence", "WARN")
            return None
        
        # Verifier que elan est disponible
        elan_path = shutil.which("elan")
        if not elan_path and self.is_linux:
            elan_bin = Path.home() / ".elan" / "bin" / "elan"
            if elan_bin.exists():
                elan_path = str(elan_bin)
        
        if not elan_path:
            self.log("elan non trouve - installez elan d'abord", "ERROR")
            return False
        
        self.log("Installation de Lean 4 (stable) via elan...")
        self.log("Cela peut prendre plusieurs minutes (telechargement ~200MB)...")
        
        return self.run_command(
            [elan_path, "default", "leanprover/lean4:stable"],
            "Installation Lean 4 stable",
            timeout=600,
            check_elan_env=True
        )
    
    def install_lean4_jupyter(self) -> bool:
        """Installe le module lean4_jupyter via pip"""
        try:
            import lean4_jupyter
            self.log("lean4_jupyter deja installe", "OK")
            return True
        except ImportError:
            pass
        
        return self.run_command(
            [sys.executable, "-m", "pip", "install", "lean4-jupyter", "--quiet"],
            "Installation lean4_jupyter via pip",
            timeout=120
        )
    
    def register_jupyter_kernel(self) -> bool:
        """Enregistre le kernel Lean dans Jupyter"""
        # Verifier si un kernel lean existe deja
        try:
            result = subprocess.run([sys.executable, "-m", "jupyter", "kernelspec", "list"], capture_output=True, text=True, timeout=PROBE_TIMEOUT_S, encoding="utf-8")
            if "lean" in result.stdout.lower():
                self.log("Un kernel Lean est deja enregistre", "OK")
                return True
        except subprocess.TimeoutExpired:
            self.log(f"Kernel Jupyter : aucune reponse en {PROBE_TIMEOUT_S} s -- "
                     "sonde en echec, enregistrement NON tente", "WARN")
            return None
        except OSError as exc:
            self.log(f"Kernel Jupyter : sonde impossible ({type(exc).__name__}) -- "
                     "enregistrement NON tente", "WARN")
            return None
        
        self.log("Enregistrement du kernel Jupyter...")
        try:
            result = subprocess.run(
                [sys.executable, "-m", "lean4_jupyter", "install"],
                capture_output=True, text=True, timeout=60, encoding="utf-8"
            )
            if result.returncode == 0:
                self.log("Kernel enregistre avec succes", "OK")
                return True
            else:
                self.log(f"Avertissement: kernel non enregistre ({result.stderr[:100]})", "WARN")
                self.log("Verifiez si un autre kernel lean existe deja", "WARN")
                return False
        except Exception as e:
            self.log(f"Avertissement: {e}", "WARN")
            return False
    
    def install_all(self) -> bool:
        """Installe tous les composants manquants"""
        print("=" * 60)
        print("    INSTALLATION AUTOMATIQUE DES COMPOSANTS LEAN 4")
        print("=" * 60)
        print()
        
        steps = [
            ("1/4", "elan", self.install_elan),
            ("2/4", "Lean 4", self.install_lean4),
            ("3/4", "lean4_jupyter", self.install_lean4_jupyter),
            ("4/4", "Kernel Jupyter", self.register_jupyter_kernel),
        ]
        
        critical_failures = []
        warnings = []
        indeterminate = []

        for step_num, name, func in steps:
            print(f"\n--- Etape {step_num}: {name} ---")
            success = func()
            if success is None:
                # Sonde en echec : ni installe, ni etabli manquant. Ne compte
                # ni comme succes ni comme echec -- installer ne repond pas a
                # une question qu'on n'a pas reussi a poser.
                indeterminate.append(name)
            elif not success:
                # Kernel Jupyter est optionnel si un autre kernel lean existe
                if name == "Kernel Jupyter":
                    warnings.append(name)
                else:
                    critical_failures.append(name)

        print("\n" + "=" * 60)
        if indeterminate:
            print(f"[~] Composant(s) non mesure(s) : {', '.join(indeterminate)}")
            print("    Sonde(s) en echec : ni present(s) ni absent(s). Rien n'a ete")
            print("    installe pour eux -- re-executez cette cellule.")
        if not critical_failures and not indeterminate:
            print("[OK] Installation terminee avec succes !")
            if warnings:
                print(f"\nAvertissements: {', '.join(warnings)}")
                print("(Un kernel Lean alternatif existe probablement)")
            print("\nIMPORTANT: Redemarrez le kernel Jupyter pour prendre en compte les changements.")
            print("           Kernel > Restart Kernel")
            return True
        elif critical_failures:
            print(f"[!] Installation incomplete: {', '.join(critical_failures)}")
            print("\nPour une installation manuelle, voir la section 'Installation manuelle' ci-dessous.")
            return False
        else:
            return False

# Verifier si l'installation est necessaire
try:
    if not ALL_COMPONENTS_OK:
        print("Des composants manquants ont ete detectes.")
        print("Lancement de l'installation automatique...\n")
        installer = LeanInstaller()
        INSTALLATION_SUCCESS = installer.install_all()
    elif INDETERMINATE_COMPONENTS:
        print("=" * 60)
        print("[~] AUCUN COMPOSANT N'EST ETABLI MANQUANT")
        print("=" * 60)
        print(f"\nSonde(s) sans reponse : {', '.join(INDETERMINATE_COMPONENTS)}")
        print("Un echec de sonde n'est PAS une absence : ces composants n'ont pas pu")
        print("etre mesures, et rien n'a ete installe pour eux.")
        print(f"\nRe-executez la cellule de diagnostic (section 2), puis cette cellule.")
        INSTALLATION_SUCCESS = True
    else:
        print("=" * 60)
        print("[OK] TOUS LES COMPOSANTS SONT DEJA INSTALLES")
        print("=" * 60)
        print("\nVotre environnement Lean 4 est pret a l'emploi.")
        print("Vous pouvez passer aux notebooks suivants.")
        print("\n    Notebooks Lean:")
        print("    - Lean-02-Dependent-Types-Lean.ipynb")
        print("    - Lean-03-Propositions-Proofs-Lean.ipynb")
        print("    - Lean-04-Quantifiers-Lean.ipynb")
        print("    - Lean-05-Tactics-Lean.ipynb")
        print("    - Lean-06-Mathlib-Essentials-Lean.ipynb")
        print("    - Lean-07-LLM-Integration-Lean-Python.ipynb")
        print("    - Lean-08-Agentic-Proving-Python.ipynb")
        print("=" * 60)
        INSTALLATION_SUCCESS = True
except NameError:
    print("[!] Executez d'abord la cellule de diagnostic (section 2) avant l'installation.")
============================================================
[OK] TOUS LES COMPOSANTS SONT DEJA INSTALLES
============================================================

Votre environnement Lean 4 est pret a l'emploi.
Vous pouvez passer aux notebooks suivants.

    Notebooks Lean:
    - Lean-02-Dependent-Types-Lean.ipynb
    - Lean-03-Propositions-Proofs-Lean.ipynb
    - Lean-04-Quantifiers-Lean.ipynb
    - Lean-05-Tactics-Lean.ipynb
    - Lean-06-Mathlib-Essentials-Lean.ipynb
    - Lean-07-LLM-Integration-Lean-Python.ipynb
    - Lean-08-Agentic-Proving-Python.ipynb
============================================================

Installation manuelle (si l’automatique echoue)

Si l’installation automatique echoue, suivez ces étapes manuellement :

Windows (PowerShell en administrateur)

# 1. Installer elan
Invoke-WebRequest -Uri https://raw.githubusercontent.com/leanprover/elan/master/elan-init.ps1 -OutFile elan-init.ps1
.\elan-init.ps1

# 2. Redemarrer PowerShell puis installer Lean 4
elan default leanprover/lean4:stable

# 3. Installer lean4_jupyter
pip install lean4-jupyter
python -m lean4_jupyter install

Linux / macOS

# 1. Installer elan
curl https://raw.githubusercontent.com/leanprover/elan/master/elan-init.sh -sSf | sh

# 2. Sourcer le profil et installer Lean 4
source ~/.elan/env
elan default leanprover/lean4:stable

# 3. Installer lean4_jupyter
pip install lean4-jupyter
python -m lean4_jupyter install

Après installation, redemarrez Jupyter pour que les changements prennent effet.

Aide-mémoire : commande equivalente selon votre plateforme

Cette cellule resume, par système, la commande d’installation equivalente. Utile quand vous voulez voir en un coup d’oeil ce qu’il faut taper depuis votre machine.

Plateforme Installation elan Activation Lean 4 stable
Windows iwr -useb https://raw.githubusercontent.com/leanprover/elan/master/elan-init.ps1 -OutFile elan-init.ps1; ./elan-init.ps1 -DefaultToolchain none Relancer PowerShell elan default leanprover/lean4:stable
macOS / Linux curl -sSf https://raw.githubusercontent.com/leanprover/elan/master/elan-init.sh \| sh -s -- -y --default-toolchain none source ~/.elan/env elan default leanprover/lean4:stable

Pour pip install lean4-jupyter, la commande est identique sur les trois plateformes.

Executez la cellule ci-dessous pour voir automatiquement la commande adaptee a votre système :

"""
Auto-detection de la plateforme et affichage de la commande d'installation equivalente.
Cette cellule sert d'aide-memoire immediat : pas de modification du systeme,
elle affiche simplement ce qu'il faudrait taper pour installer Lean 4
sur la machine sur laquelle le notebook s'execute.
"""

import platform
import shutil

system = platform.system()
print("=" * 64)
print("    AIDE-MEMOIRE : commande equivalente pour VOTRE machine")
print("=" * 64)
print()
print(f"OS detecte : {system} ({platform.release()}, {platform.machine()})")
print()

if system == "Windows":
    print("Plateforme : Windows")
    print()
    print("  # Installation elan (PowerShell)")
    ps_url = "https://raw.githubusercontent.com/leanprover/elan/master/elan-init.ps1"
    print(f"  iwr -useb {ps_url} -OutFile elan-init.ps1")
    print("  ./elan-init.ps1 -DefaultToolchain none")
    print()
    print("  # Relancer PowerShell, puis :")
    print("  elan default leanprover/lean4:stable")
    print()
    print("  # Kernel Jupyter")
    print("  pip install lean4-jupyter")
    print("  python -m lean4_jupyter install")
    print()
    print("  Note : si le kernel lean4 natif echoue avec")
    print("  'signal.SIGPIPE not found', utilisez le kernel lean4-wsl")
    print("  (voir section 5 ci-dessous).")
elif system == "Darwin":
    print("Plateforme : macOS")
    print()
    print("  # Installation elan (Terminal)")
    sh_url = "https://raw.githubusercontent.com/leanprover/elan/master/elan-init.sh"
    print(f"  curl -sSf {sh_url} | sh -s -- -y --default-toolchain none")
    print()
    print("  # Activer elan pour la session courante")
    print("  source ~/.elan/env")
    print()
    print("  # Installer Lean 4 stable")
    print("  elan default leanprover/lean4:stable")
    print()
    print("  # Kernel Jupyter")
    print("  pip install lean4-jupyter")
    print("  python -m lean4_jupyter install")
elif system == "Linux":
    print("Plateforme : Linux")
    print()
    print("  # Installation elan (Terminal)")
    sh_url = "https://raw.githubusercontent.com/leanprover/elan/master/elan-init.sh"
    print(f"  curl -sSf {sh_url} | sh -s -- -y --default-toolchain none")
    print()
    print("  # Activer elan pour la session courante")
    print("  source ~/.elan/env")
    print()
    print("  # Installer Lean 4 stable")
    print("  elan default leanprover/lean4:stable")
    print()
    print("  # Kernel Jupyter")
    print("  pip install lean4-jupyter")
    print("  python -m lean4_jupyter install")
else:
    print(f"Plateforme non reconnue : {system}")
    print("Consultez https://leanprover.github.io/lean4/doc/install.html")

print()
print("=" * 64)
print()
print("Etat actuel de l'installation :")
# Ne pas imprimer les chemins absolus (H.1 secrets-hygiene : chemins machine
# absolus comme C:\Users\<user>\.elan\bin\elan.EXE sont interdits dans outputs)
elan_path = shutil.which("elan")
if not elan_path:
    _alt = shutil.os.path.expanduser("~/.elan/bin/elan")
    if shutil.os.path.exists(_alt):
        elan_path = _alt
elan_label = shutil.os.path.basename(elan_path) if elan_path else None
lean_path = shutil.which("lean")
lean_label = shutil.os.path.basename(lean_path) if lean_path else None
non_installe = chr(39) + "non installe" + chr(39)
print(f"  elan : {elan_label or non_installe}")
print(f"  lean : {lean_label or non_installe}")
================================================================
    AIDE-MEMOIRE : commande equivalente pour VOTRE machine
================================================================

OS detecte : Windows (11, AMD64)

Plateforme : Windows

  # Installation elan (PowerShell)
  iwr -useb https://raw.githubusercontent.com/leanprover/elan/master/elan-init.ps1 -OutFile elan-init.ps1
  ./elan-init.ps1 -DefaultToolchain none

  # Relancer PowerShell, puis :
  elan default leanprover/lean4:stable

  # Kernel Jupyter
  pip install lean4-jupyter
  python -m lean4_jupyter install

  Note : si le kernel lean4 natif echoue avec
  'signal.SIGPIPE not found', utilisez le kernel lean4-wsl
  (voir section 5 ci-dessous).

================================================================

Etat actuel de l'installation :
  elan : elan.EXE
  lean : lean.EXE

4. vérification de l’installation

Après installation (ou redemarrage du kernel), executez cette cellule pour vérifier que tout fonctionne :

"""
Verification finale de l'installation
"""

import subprocess
import shutil
import sys
import platform

# Delai maximal accorde a une sonde de version. Le shim elan est la sonde la plus
# lente mesuree : 30 essais sous charge nominale donnent 0,58 s au maximum. Les
# echecs observes sous timeout=5 arrivent en salves de charge, donc bien au-dela
# du cout nominal. 30 s borne l'attente pathologique sans mordre le cas nominal.
PROBE_TIMEOUT_S = 30

print("=" * 60)
print("    VERIFICATION FINALE DE L'INSTALLATION")
print("=" * 60)
print()


def probe_component(name, probes, extract):
    """Sonde un composant par une ou plusieurs voies d'acces.

    `probes`  : liste de (description, commande)
    `extract` : fonction qui tire la version de la sortie standard

    Rend (etat, info). Etats possibles :
      "ok"            la commande a repondu (code retour 0)
      "erreur"        la commande a repondu par une erreur (code retour != 0), ou
                      n'a pas pu etre lancee (OSError)
      "delai_depasse" aucune reponse dans PROBE_TIMEOUT_S
      "introuvable"   le binaire n'est accessible par aucune voie

    Un delai depasse, un OSError ou un code retour d'erreur n'est PAS une absence :
    la sonde a echoue, elle n'a pas etabli que le composant manque. Seul
    "introuvable" se traduit par [MANQUANT], et il n'est rendu que lorsque aucune
    sonde n'a echoue ET qu'aucune voie n'a resolu le binaire -- code 127
    ("commande introuvable") compris.
    """
    failures = []
    for _label, cmd in probes:
        try:
            result = subprocess.run(cmd, capture_output=True, text=True,
                                    timeout=PROBE_TIMEOUT_S, encoding="utf-8")
        except subprocess.TimeoutExpired:
            failures.append(("delai_depasse", f"aucune reponse en {PROBE_TIMEOUT_S} s"))
            continue
        except OSError as exc:
            failures.append(("erreur", f"sonde non lancable ({type(exc).__name__})"))
            continue
        if result.returncode == 0:
            return "ok", extract(result.stdout)
        if result.returncode == 127:
            failures.append(("introuvable", f"{cmd[0]} : commande introuvable"))
            continue
        failures.append(("erreur", (result.stderr or "").strip().split("\n")[0][:60]
                         or f"code retour {result.returncode}"))

    # Un echec de sonde prime sur une absence : il n'a rien etabli, alors qu'une
    # absence se mesure. "introuvable" n'est rendu que si c'est la seule reponse.
    for state, info in failures:
        if state != "introuvable":
            return state, info
    if failures:
        return "introuvable", failures[0][1]
    return "introuvable", f"{name} : aucune voie d'acces disponible"


def probes_for(binary, unix_source_cmd=None):
    """Voies d'acces a un binaire : `source ~/.elan/env` sous Linux/WSL, puis PATH."""
    probes = []
    if unix_source_cmd and platform.system() in ("Linux", "Darwin"):
        probes.append(("source ~/.elan/env", unix_source_cmd))
    if shutil.which(binary):
        probes.append(("PATH", [binary, "--version"]))
    return probes


checks = []

# 1. Python
python_ok = sys.version_info >= (3, 8)
checks.append(("Python 3.8+", "ok" if python_ok else "manquant",
               f"{sys.version_info.major}.{sys.version_info.minor}"))

# 2. elan
elan_state, elan_info = probe_component(
    "elan",
    probes_for("elan", ["bash", "-c", "source ~/.elan/env 2>/dev/null && elan --version"]),
    lambda out: out.strip().split()[1],
)
checks.append(("elan", elan_state, elan_info))

# 3. lean
lean_state, lean_info = probe_component(
    "lean",
    probes_for("lean", ["bash", "-c", "source ~/.elan/env 2>/dev/null && lean --version"]),
    lambda out: out.strip().split("\n")[0],
)
checks.append(("Lean 4", lean_state, lean_info))

# 4. lean4_jupyter module
try:
    import lean4_jupyter
    jupyter_mod_ok = True
    jupyter_mod_version = getattr(lean4_jupyter, "__version__", "installed")
except ImportError:
    jupyter_mod_ok = False
    jupyter_mod_version = ""
checks.append(("lean4_jupyter", "ok" if jupyter_mod_ok else "manquant", jupyter_mod_version))

# 5. Jupyter kernel
kernel_name = ""
try:
    result = subprocess.run([sys.executable, "-m", "jupyter", "kernelspec", "list"],
                            capture_output=True, text=True, timeout=PROBE_TIMEOUT_S,
                            encoding="utf-8")
    for line in result.stdout.split('\n'):
        if 'lean' in line.lower():
            kernel_name = line.strip().split()[0]
            break
    kernel_state = "ok" if kernel_name else "manquant"
except subprocess.TimeoutExpired:
    kernel_state, kernel_name = "delai_depasse", f"aucune reponse en {PROBE_TIMEOUT_S} s"
except OSError as exc:
    kernel_state, kernel_name = "introuvable", type(exc).__name__
checks.append(("Kernel Jupyter", kernel_state, kernel_name))

# Afficher les resultats
STATUS_LABEL = {
    "ok": "[OK]",
    "manquant": "[MANQUANT]",
    "introuvable": "[MANQUANT]",
    "delai_depasse": "[DELAI DEPASSE]",
    "erreur": "[ERREUR]",
}

print(f"{'Composant':<20} {'Status':<16} {'Version/Info'}")
print("-" * 60)

missing = []
indeterminate = []
for name, state, info in checks:
    print(f"{name:<20} {STATUS_LABEL[state]:<16} {info}")
    if state == "ok":
        continue
    if state in ("manquant", "introuvable"):
        missing.append(name)
    else:
        indeterminate.append(name)

print("-" * 60)

if not missing and not indeterminate:
    print("\n" + "*" * 60)
    print("*  INSTALLATION COMPLETE ET FONCTIONNELLE !              *")
    print("*                                                        *")
    print("*  Vous pouvez maintenant :                              *")
    print("*  1. Creer un nouveau notebook avec le kernel Lean 4    *")
    print("*  2. Continuer avec Lean-02-Dependent-Types-Lean.ipynb  *")
    print("*" * 60)
else:
    if indeterminate:
        print(f"\n[~] Sonde(s) sans reponse : {', '.join(indeterminate)}")
        print(f"    Aucune reponse en {PROBE_TIMEOUT_S} s : ce n'est PAS une absence, et le")
        print("    composant n'a pas pu etre mesure. Re-executez cette cellule avant")
        print("    d'envisager une installation.")
    if missing:
        print(f"\n[!] Installation incomplete. Manquant : {', '.join(missing)}")
        print("    Executez la cellule d'installation automatique ci-dessus,")
        print("    puis redemarrez le kernel et re-executez cette verification.")
============================================================
    VERIFICATION FINALE DE L'INSTALLATION
============================================================

Composant            Status           Version/Info
------------------------------------------------------------
Python 3.8+          [OK]             3.13
elan                 [OK]             4.1.2
Lean 4               [OK]             Lean (version 4.34.1, x86_64-w64-windows-gnu, commit 5045d0056413266e57c625dcd7c365b10e377c52, Release)
lean4_jupyter        [OK]             0.0.2
Kernel Jupyter       [OK]             lean4
------------------------------------------------------------

************************************************************
*  INSTALLATION COMPLETE ET FONCTIONNELLE !              *
*                                                        *
*  Vous pouvez maintenant :                              *
*  1. Creer un nouveau notebook avec le kernel Lean 4    *
*  2. Continuer avec Lean-02-Dependent-Types-Lean.ipynb  *
************************************************************

5. Vérifier les composants avant une première preuve

Le diagnostic de la section 4 contrôle les versions de Lean, d’elan, du module Python et l’enregistrement d’un kernel. Il ne compile aucun théorème. La section 7 propose un exercice à réaliser dans un fichier Lean séparé pour vérifier cette dernière étape.

Sous Windows, le kernel lean4_jupyter natif peut rencontrer l’erreur signal.SIGPIPE not found. Les cellules suivantes préparent repl, puis proposent un patch pour le kernel natif et une configuration WSL. Sur macOS et Linux, le patch Windows et la configuration du kernel WSL ne sont pas appliqués ; la cellule de configuration Python peut toutefois installer des dépendances dans l’environnement courant.

Après le diagnostic

Les cellules précédentes affichent l’état des composants sur la machine où elles ont été exécutées. Si elles indiquent [OK], les commandes et le kernel sont disponibles dans cet environnement ; cela ne constitue pas encore une preuve Lean exécutée.

Prochaines étapes :

  1. Notebooks Lean natifs (kernels Lean 4 ou Lean 4 WSL) :
  2. Notebooks Python avec intégration Lean (kernel Python 3 WSL) :

Pour les notebooks 7 et 8 sous Windows, exécutez aussi la cellule « Configuration du kernel Python WSL » ci-dessous. Avant de poursuivre, réalisez le premier théorème proposé à la section 7.

Installation de REPL

Le kernel lean4_jupyter nécessite l’outil repl (serveur REPL pour Lean 4).

Exécutez la cellule suivante pour installer repl automatiquement si cet outil manque :

"""
Installation automatique de REPL pour lean4_jupyter

REPL est le serveur REPL pour Lean 4, nécessaire pour le kernel Jupyter.
"""

import subprocess
import shutil
import platform
import tempfile
from pathlib import Path

def install_repl():
    """Installe le serveur REPL pour Lean 4"""
    
    # Vérifier si repl est déjà installé
    if shutil.which("repl") or shutil.which("repl.exe"):
        print("[OK] repl est déjà installé")
        return True
    
    print("=" * 60)
    print("    INSTALLATION DE REPL")
    print("=" * 60)
    print()
    
    # Chemins
    is_windows = platform.system() == "Windows"
    home = Path.home()
    elan_bin = home / ".elan" / "bin"
    repl_dir = home / "repl"
    
    print(f"[*] Vérification de lake...")
    if not shutil.which("lake"):
        print("[!] Lake n'est pas installé - installez Lean 4 d'abord")
        return False
    
    # Cloner le dépôt repl si nécessaire
    if not repl_dir.exists():
        print(f"[*] Clonage de repl depuis GitHub...")
        result = subprocess.run(
            ["git", "clone", "https://github.com/leanprover-community/repl", str(repl_dir)],
            capture_output=True, text=True, timeout=60, encoding="utf-8"
        )
        if result.returncode != 0:
            print(f"[!] Erreur lors du clonage: {result.stderr[:200]}")
            return False
        print("[+] Clonage réussi")
    else:
        print(f"[OK] Dépôt repl existe: {repl_dir}")
    
    # Construire repl
    print(f"[*] Construction de repl (cela peut prendre 1-2 minutes)...")
    result = subprocess.run(
        ["lake", "build"],
        cwd=str(repl_dir),
        capture_output=True, text=True, timeout=300, encoding="utf-8"
    )
    if result.returncode != 0:
        print(f"[!] Erreur lors de la construction: {result.stderr[:200]}")
        return False
    print("[+] Construction réussie")
    
    # Copier l'exécutable dans le PATH
    repl_exe = repl_dir / ".lake" / "build" / "bin" / ("repl.exe" if is_windows else "repl")
    if not repl_exe.exists():
        print(f"[!] Exécutable non trouvé: {repl_exe}")
        return False
    
    # Créer le répertoire elan/bin si nécessaire
    elan_bin.mkdir(parents=True, exist_ok=True)
    
    # Copier repl
    dest = elan_bin / repl_exe.name
    shutil.copy(repl_exe, dest)
    print(f"[+] repl copié vers: {dest}")
    
    # Vérifier l'installation
    if shutil.which("repl") or shutil.which("repl.exe"):
        print("\n" + "=" * 60)
        print("[OK] REPL installé avec succès !")
        print("=" * 60)
        return True
    else:
        print("\n" + "=" * 60)
        print("[~] REPL installé mais pas dans le PATH")
        print(f"    Ajoutez {elan_bin} au PATH et redémarrez")
        print("=" * 60)
        return True

# Installer repl
try:
    install_repl()
except Exception as e:
    print(f"\n[!] Erreur: {e}")
    print("\nInstallation manuelle:")
    print("  cd ~")
    print("  git clone https://github.com/leanprover-community/repl")
    print("  cd repl")
    print("  lake build")
    print("  cp .lake/build/bin/repl* ~/.elan/bin/")
[OK] repl est déjà installé

Fix Windows : patch du kernel lean4

Sur Windows, le kernel lean4_jupyter peut échouer avec l’erreur signal.SIGPIPE not found.

Solution 1 : exécutez la cellule suivante pour patcher le kernel Windows natif en ignorant signal.SIGPIPE lorsqu’il n’existe pas.

Solution 2 : si le patch échoue, configurez le kernel WSL à la section suivante.

"""
Patch du kernel lean4_jupyter pour Windows (fix SIGPIPE)

Sur Windows, signal.SIGPIPE n'existe pas. Cette cellule patche
automatiquement le fichier kernel.py pour ignorer cette erreur.
"""

import sys
import platform
from pathlib import Path

def patch_lean4_jupyter_windows():
    """Patche lean4_jupyter pour Windows (SIGPIPE fix)"""
    
    if platform.system() != "Windows":
        print("[OK] Vous n'etes pas sur Windows - patch non necessaire")
        return True
    
    try:
        import lean4_jupyter
        kernel_file = Path(lean4_jupyter.__file__).parent / "kernel.py"
        # Chemin normalise pour l'affichage (home -> ~, env hors home -> ~/nom-env, review #18669)
        env_root = str(Path(sys.prefix).resolve())
        kernel_display = Path(str(kernel_file)
                              .replace(str(Path.home()), "~")
                              .replace(env_root, "~/" + Path(env_root).name)).as_posix()        
        if not kernel_file.exists():
            print(f"[!] Fichier kernel.py introuvable: {kernel_display}")
            return False
        
        # Lire le fichier
        with open(kernel_file, 'r', encoding='utf-8') as f:
            content = f.read()
        
        # Verifier si deja patche
        if "hasattr(signal, 'SIGPIPE')" in content:
            print(f"[OK] kernel.py deja patche: {kernel_display}")
            return True
        
        # Verifier si le bug existe
        if "signal.SIGPIPE" not in content:
            print(f"[OK] kernel.py ne contient pas de reference a SIGPIPE")
            return True
        
        # Appliquer le patch
        print(f"[*] Patch de {kernel_display}...")
        
        # Ligne 51: old_sigpipe_handler = signal.signal(signal.SIGPIPE, signal.SIG_DFL)
        old_line = "        old_sigpipe_handler = signal.signal(signal.SIGPIPE, signal.SIG_DFL)"
        new_line = "        # SIGPIPE doesn't exist on Windows, only set it if available\n        old_sigpipe_handler = signal.signal(signal.SIGPIPE, signal.SIG_DFL) if hasattr(signal, 'SIGPIPE') else None"
        
        content = content.replace(old_line, new_line)
        
        # Ligne 56: signal.signal(signal.SIGPIPE, old_sigpipe_handler)
        old_line2 = "            signal.signal(signal.SIGPIPE, old_sigpipe_handler)"
        new_line2 = "            if old_sigpipe_handler is not None:\n                signal.signal(signal.SIGPIPE, old_sigpipe_handler)"
        
        content = content.replace(old_line2, new_line2)
        
        # Ecrire le fichier patche
        with open(kernel_file, 'w', encoding='utf-8') as f:
            f.write(content)
        
        print(f"[+] Patch applique avec succes !")
        print(f"    Le kernel 'lean4' (Windows natif) est maintenant fonctionnel.")
        return True
        
    except ImportError:
        print("[!] lean4_jupyter non installe - installez-le d'abord")
        return False
    except Exception as e:
        print(f"[!] Erreur lors du patch: {e}")
        return False

# Appliquer le patch
print("=" * 60)
print("    PATCH DU KERNEL LEAN4 POUR WINDOWS")
print("=" * 60)
print()

if patch_lean4_jupyter_windows():
    print("\n" + "=" * 60)
    print("[OK] Kernel lean4 pret a l'emploi !")
    print("\n     Vous pouvez maintenant utiliser le kernel 'lean4'")
    print("     directement dans VSCode sans WSL.")
    print("\n     Rechargez VSCode: Ctrl+Shift+P > 'Reload Window'")
    print("=" * 60)
else:
    print("\n" + "=" * 60)
    print("[!] Patch echoue - utilisez le kernel WSL a la place")
    print("    Voir la cellule suivante pour WSL.")
    print("=" * 60)
============================================================
    PATCH DU KERNEL LEAN4 POUR WINDOWS
============================================================

[OK] kernel.py deja patche: ~/AppData/Local/Programs/Python/Python313/Lib/site-packages/lean4_jupyter/kernel.py

============================================================
[OK] Kernel lean4 pret a l'emploi !

     Vous pouvez maintenant utiliser le kernel 'lean4'
     directement dans VSCode sans WSL.

     Rechargez VSCode: Ctrl+Shift+P > 'Reload Window'
============================================================

Configuration du kernel Lean 4 (WSL) — Windows uniquement

Le kernel Lean 4 (WSL) est utilisé pour les notebooks Lean natifs (Lean-2 à Lean-6).

macOS / Linux : passez cette section. WSL est spécifique à Windows ; sur macOS/Linux, le kernel lean4 natif (configuré à la section précédente) fonctionne directement. La cellule ci-dessous se termine immédiatement ([OK] Vous n'etes pas sur Windows).

Exécutez la cellule suivante pour configurer automatiquement ce kernel :

"""
Detection et configuration du kernel Lean4 via WSL (Windows uniquement)

Sur Windows, le kernel lean4_jupyter natif peut avoir des problemes avec les chemins.
Ce script configure automatiquement un kernel WSL robuste pour Jupyter/VSCode.

Fonctionnalites du wrapper robuste:
- Conversion de chemins Windows vers WSL (y compris chemins "mangles")
- PATH propre sans pollution Windows
- Lancement direct via IPKernelApp (plus fiable que -m)
- Logging pour debugging (~/.lean4-wrapper.log)
"""

import subprocess
import platform
import json
import os
from pathlib import Path

def _detect_wsl_user() -> str:
    """Detect the WSL home directory dynamically (no hardcoded usernames)."""
    # Strategy 1: query $HOME from WSL
    try:
        result = subprocess.run(
            ["wsl.exe", "-d", "Ubuntu", "--", "bash", "-c", "echo -n $HOME"],
            capture_output=True, text=True, timeout=10, encoding="utf-8"
        )
        if result.returncode == 0 and result.stdout.strip().startswith("/"):
            return result.stdout.strip()
    except Exception:
        pass
    # Strategy 2: query whoami and construct /home/<user>
    try:
        result = subprocess.run(
            ["wsl.exe", "-d", "Ubuntu", "--", "whoami"],
            capture_output=True, text=True, timeout=10, encoding="utf-8"
        )
        if result.returncode == 0 and result.stdout.strip():
            return f"/home/{result.stdout.strip()}"
    except Exception:
        pass
    # Strategy 3: derive from Windows USERNAME env var (often matches WSL user)
    win_user = os.environ.get("USERNAME", os.environ.get("USER", ""))
    if win_user:
        return f"/home/{win_user.lower()}"
    # Last resort: use /root (WSL default when no user is set up)
    return "/root"

def _display_path(p) -> str:
    """Normalise un chemin pour l'affichage (home -> ~, separateurs /, pas de chemin machine)."""
    home = str(Path.home())
    s = str(p)
    s = "~" + s[len(home):] if s.startswith(home) else s
    return s.replace("\\", "/")

class WSLKernelManager:
    """Gestionnaire du kernel Lean4 via WSL avec wrapper robuste"""
    
    def __init__(self):
        self.is_windows = platform.system() == "Windows"
        self.kernel_name = "lean4-wsl"
        self.kernel_path = Path(os.environ.get("APPDATA", "")) / "jupyter" / "kernels" / self.kernel_name
        wsl_home = _detect_wsl_user() if self.is_windows else str(Path.home())
        self.wsl_home = wsl_home
        self.wrapper_path_wsl = f"{wsl_home}/.lean4-kernel-wrapper.py"
        self.venv_python = f"{wsl_home}/.lean4-venv/bin/python3"
    
    def _safe_decode(self, data: bytes) -> str:
        """Decode bytes safely, handling non-UTF8 characters from WSL"""
        return data.decode('utf-8', errors='replace')
    
    def _get_robust_wrapper_content(self) -> str:
        """Retourne le contenu du wrapper robuste en le chargeant depuis scripts/"""
        # Resolve script location dynamically from notebook directory
        notebook_dir = Path(globals().get("__vsc_ipynb_file__", str(Path.cwd()))).resolve().parent
        if not (notebook_dir / "scripts").exists():
            # Walk up to find the Lean directory
            current = Path.cwd().resolve()
            for _ in range(8):
                if (current / "scripts" / "lean4-kernel-wrapper.py").exists():
                    notebook_dir = current
                    break
                current = current.parent

        possible_paths = [
            notebook_dir / "scripts" / "lean4-kernel-wrapper.py",
            Path("scripts/lean4-kernel-wrapper.py"),  # Relatif au repertoire courant
        ]

        for wrapper_file in possible_paths:
            try:
                if wrapper_file.exists():
                    with open(wrapper_file, 'r', encoding='utf-8') as f:
                        return f.read()
            except Exception:
                continue

        raise FileNotFoundError(f"Wrapper script not found. Tried: {', '.join(_display_path(p) for p in possible_paths)}")

    def _create_wrapper_script(self) -> bool:
        """Deploie le script wrapper depuis scripts/ vers WSL via stdin"""
        try:
            # Charger le wrapper
            wrapper_content = self._get_robust_wrapper_content()

            # Ecrire via stdin WSL (evite les problemes d'echappement des heredoc)
            result = subprocess.run(
                ["wsl.exe", "-d", "Ubuntu", "bash", "-c", f"cat > {self.wrapper_path_wsl} && chmod +x {self.wrapper_path_wsl}"],
                input=wrapper_content,
                text=True,
                encoding="utf-8",
                capture_output=True,
                timeout=10
            )
            if result.returncode == 0:
                print(f"[+] Wrapper robuste deploye: {self.wrapper_path_wsl.replace(self.wsl_home, '~')}")
                return True
            print(f"[!] Erreur creation wrapper: {self._safe_decode(result.stderr)[:200]}")
            return False
        except FileNotFoundError as e:
            print(f"[!] {e}")
            print("    Assurez-vous que scripts/lean4-kernel-wrapper.py existe")
            return False
        except Exception as e:
            print(f"[!] Exception: {e}")
            return False

        
    def check_wsl_available(self) -> dict:
        """Verifie si WSL est disponible"""
        if not self.is_windows:
            return {"available": False, "reason": "Non Windows - WSL non necessaire"}
        
        try:
            result = subprocess.run(
                ["wsl.exe", "--status"],
                capture_output=True, timeout=10
            )
            if result.returncode == 0:
                return {"available": True, "reason": "WSL disponible"}
            return {"available": False, "reason": "WSL non configure"}
        except FileNotFoundError:
            return {"available": False, "reason": "WSL non installe"}
        except Exception as e:
            return {"available": False, "reason": str(e)}
    
    def check_wsl_lean_ready(self) -> dict:
        """Verifie si Lean est configure dans WSL"""
        try:
            result = subprocess.run(
                ["wsl.exe", "-d", "Ubuntu", "--", "bash", "-c",
                 "source ~/.elan/env 2>/dev/null && lean --version && which python3"],
                capture_output=True, timeout=30
            )
            stdout = self._safe_decode(result.stdout)
            
            if result.returncode == 0 and "Lean" in stdout:
                lines = stdout.strip().split('\n')
                lean_version = lines[0] if lines else "unknown"
                return {"ready": True, "lean_version": lean_version}
            return {"ready": False, "reason": "Lean non installe dans WSL"}
        except Exception as e:
            return {"ready": False, "reason": str(e)}
    
    def check_wsl_kernel_registered(self) -> dict:
        """Verifie si le kernel WSL est enregistre"""
        try:
            result = subprocess.run(
                [sys.executable, "-m", "jupyter", "kernelspec", "list", "--json"],
                capture_output=True, timeout=10
            )
            stdout = self._safe_decode(result.stdout)
            if result.returncode == 0:
                kernels = json.loads(stdout)
                if self.kernel_name in kernels.get("kernelspecs", {}):
                    return {
                        "registered": True,
                        "path": kernels["kernelspecs"][self.kernel_name].get("resource_dir")
                    }
            return {"registered": False}
        except Exception as e:
            return {"registered": False, "error": str(e)}
    
    def install_wsl_kernel(self, force: bool = False) -> bool:
        """Installe le kernel Lean4-WSL avec wrapper Python robuste."""
        print("[*] Installation du kernel Lean4-WSL...")
        
        # Deployer le wrapper robuste dans WSL
        if not self._create_wrapper_script():
            print("[!] Echec deploiement du wrapper")
            return False
        
        # Creer le dossier du kernel
        self.kernel_path.mkdir(parents=True, exist_ok=True)
        
        # Creer kernel.json qui utilise le venv Python et le wrapper
        kernel_json = {
            "argv": [
                "wsl.exe", "-d", "Ubuntu", "--",
                self.venv_python, self.wrapper_path_wsl,
                "-f", "{connection_file}"
            ],
            "display_name": "Lean 4 (WSL)",
            "language": "lean4"
        }
        
        kernel_file = self.kernel_path / "kernel.json"
        try:
            with open(kernel_file, 'w') as f:
                json.dump(kernel_json, f, indent=2)
        except OSError as e:
            print(f"[!] Erreur ecriture du kernelspec: {e}")
            return False
        
        print(f"[+] Kernel cree: {_display_path(kernel_file)}")
        return True
    
    def run_diagnostic(self):
        """Execute le diagnostic complet WSL et installe/met a jour le kernel"""
        print("=" * 60)
        print("    DIAGNOSTIC ET CONFIGURATION KERNEL LEAN4 VIA WSL")
        print("=" * 60)
        
        if not self.is_windows:
            print("\n[OK] Vous n'etes pas sur Windows.")
            print("     Utilisez le kernel lean4 standard.")
            return
        
        # 1. Verifier WSL
        print("\n[1/4] Verification de WSL...")
        wsl_status = self.check_wsl_available()
        if wsl_status["available"]:
            print(f"      [OK] {wsl_status['reason']}")
        else:
            print(f"      [!] {wsl_status['reason']}")
            print("\n      Pour installer WSL:")
            print("      wsl --install -d Ubuntu")
            return
        
        # 2. Verifier Lean dans WSL
        print("\n[2/4] Verification de Lean dans WSL...")
        lean_status = self.check_wsl_lean_ready()
        if lean_status.get("ready"):
            print(f"      [OK] {lean_status['lean_version']}")
        else:
            print(f"      [!] {lean_status.get('reason', 'Non configure')}")
            print("\n      Pour installer Lean dans WSL, executez dans WSL Ubuntu:")
            print("      curl -sSf https://raw.githubusercontent.com/leanprover/elan/master/elan-init.sh | sh")
            print("      source ~/.elan/env")
            print("      elan default leanprover/lean4:v4.11.0")
            print("      python3 -m venv ~/.lean4-venv")
            print("      source ~/.lean4-venv/bin/activate")
            print("      pip install lean4-jupyter ipykernel")
            return
        
        # 3. Installer/mettre a jour le kernel
        print("\n[3/4] Installation du kernel Jupyter...")
        kernel_status = self.check_wsl_kernel_registered()
        if kernel_status.get("registered"):
            print(f"      [OK] Kernel enregistre: {_display_path(kernel_status['path'])}")
            print("      [*] Mise a jour du wrapper robuste...")
        else:
            print("      [!] Kernel non enregistre")
            print("      [*] Installation automatique...")
        install_ok = self.install_wsl_kernel(force=True)

        if not install_ok:
            print("\n" + "=" * 60)
            print("[!] ECHEC a l'etape 3/4 : installation du kernel (deploiement du wrapper).")
            print("    Le kernel N'EST PAS pret a l'emploi.")
            print("    Cause frequente : scripts/lean4-kernel-wrapper.py introuvable")
            print("    depuis le repertoire courant -- reexecutez depuis le dossier du notebook.")
            print("=" * 60)
            return
        
        # 4. Tester le kernel
        print("\n[4/4] Test du kernel WSL...")
        test_ok = self._test_kernel()
        
        print("\n" + "=" * 60)
        if test_ok:
            print("[OK] Kernel Lean4-WSL pret a l'emploi !")
            print("\n     Dans VSCode:")
            print("     1. Rechargez la fenetre: Ctrl+Shift+P > 'Reload Window'")
            print("     2. Ouvrez un notebook .ipynb")
            print("     3. Cliquez sur 'Select Kernel' en haut a droite")
            print("     4. Choisissez 'Lean 4 (WSL)'")
            print("\n     Logs de debug: wsl cat ~/.lean4-wrapper.log")
        else:
            print("[~] Etape 4/4 echouee : kernel installe mais le test echoue.")
            print("    Redemarrez VSCode et reessayez.")
            print("    Verifiez les logs: wsl cat ~/.lean4-wrapper.log")
        print("=" * 60)
    
    def _test_kernel(self) -> bool:
        """Test rapide du kernel WSL"""
        try:
            result = subprocess.run(
                ["wsl.exe", "-d", "Ubuntu", "--", "bash", "-c",
                 f"source ~/.lean4-venv/bin/activate 2>/dev/null && "
                 f"source ~/.elan/env 2>/dev/null && "
                 f"python3 -c 'import lean4_jupyter; print(\"OK\")'"],
                capture_output=True, timeout=15
            )
            stdout = self._safe_decode(result.stdout)
            if "OK" in stdout:
                print("      [OK] lean4_jupyter fonctionne dans WSL")
                return True
            print(f"      [!] Erreur: {self._safe_decode(result.stderr)[:100]}")
            return False
        except Exception as e:
            print(f"      [!] Exception: {e}")
            return False

# Executer le diagnostic et configurer le kernel
wsl_manager = WSLKernelManager()
wsl_manager.run_diagnostic()
============================================================
    DIAGNOSTIC ET CONFIGURATION KERNEL LEAN4 VIA WSL
============================================================

[1/4] Verification de WSL...
      [OK] WSL disponible

[2/4] Verification de Lean dans WSL...
      [OK] Lean (version 4.33.0, x86_64-unknown-linux-gnu, commit d8b18978322de05a8f3dba51ef03cf5461676c17, Release)

[3/4] Installation du kernel Jupyter...
      [OK] Kernel enregistre: ~/AppData/Roaming/jupyter/kernels/lean4-wsl
      [*] Mise a jour du wrapper robuste...
[*] Installation du kernel Lean4-WSL...
[+] Wrapper robuste deploye: ~/.lean4-kernel-wrapper.py
[+] Kernel cree: ~/AppData/Roaming/jupyter/kernels/lean4-wsl/kernel.json

[4/4] Test du kernel WSL...
      [OK] lean4_jupyter fonctionne dans WSL

============================================================
[OK] Kernel Lean4-WSL pret a l'emploi !

     Dans VSCode:
     1. Rechargez la fenetre: Ctrl+Shift+P > 'Reload Window'
     2. Ouvrez un notebook .ipynb
     3. Cliquez sur 'Select Kernel' en haut a droite
     4. Choisissez 'Lean 4 (WSL)'

     Logs de debug: wsl cat ~/.lean4-wrapper.log
============================================================

Configuration du kernel Python WSL (pour notebooks 7-8) — Windows uniquement

Les notebooks Lean-07-LLM-Integration-Lean-Python et Lean-08-Agentic-Proving-Python utilisent un kernel Python qui s’execute dans WSL pour avoir acces a l’environnement Lean.

macOS / Linux : passez cette section. Sur macOS/Linux, la cellule ci-dessous détecte la plateforme et installe directement les packages via pip dans votre environnement Python courant (Lean étant déjà accessible via elan) — aucun kernel WSL nécessaire.

Packages installes automatiquement : - python-dotenv - Chargement des variables d’environnement - openai - Client OpenAI pour GPT-5.2 - anthropic - Client Anthropic pour Claude - matplotlib - Visualisations - semantic-kernel - Orchestration multi-agents Microsoft

Executez la cellule suivante pour configurer automatiquement ce kernel :

"""
Configuration automatique du kernel Python 3 (WSL)
Installe: python-dotenv, openai, anthropic, matplotlib, semantic-kernel
"""

import subprocess
import platform
import sys
from pathlib import Path

PACKAGES = ["python-dotenv", "openai", "anthropic", "matplotlib", "semantic-kernel"]

if platform.system() == "Windows":
    print("=== Configuration Python 3 (WSL) ===")
    print(f"Packages: {', '.join(PACKAGES)}")
    print()

    # Resolve script path dynamically from notebook location.
    # Three-strategy search for setup_wsl_python.sh:
    # 1. Notebook-relative (VS Code sets __vsc_ipynb_file__): the script
    #    lives next to the notebook at <notebook_dir>/scripts/.
    # 2. Walk UP from cwd (papermill cwd may be a worktree root): the
    #    script lives up to 8 levels above — find the FIRST ancestor whose
    #    scripts/ contains the script. Test the FILE, not just the dir.
    # 3. Walk DOWN from cwd (papermill wandered to a worktree root): the
    #    script lives below the cwd. Find it via rglob and use its
    #    parent.parent as notebook_dir (since the layout is
    #    <root>/MyIA.AI.Notebooks/SymbolicAI/Lean/scripts/setup_wsl_python.sh).
    notebook_dir = Path(globals().get("__vsc_ipynb_file__", str(Path.cwd()))).resolve().parent
    if not (notebook_dir / "scripts" / "setup_wsl_python.sh").exists():
        # strategy 2: walk UP
        notebook_dir = Path.cwd().resolve()
        for _ in range(8):
            if (notebook_dir / "scripts" / "setup_wsl_python.sh").exists():
                break
            notebook_dir = notebook_dir.parent
        if not (notebook_dir / "scripts" / "setup_wsl_python.sh").exists():
            # strategy 3: walk DOWN — find the script anywhere under cwd
            found = next(Path.cwd().resolve().rglob("setup_wsl_python.sh"), None)
            if found:
                notebook_dir = found.parent.parent

    # Convert to WSL path
    def _win_to_wsl(p: Path) -> str:
        drive = p.drive[0].lower()
        return f"/mnt/{drive}{p.as_posix()[2:]}"

    script_wsl = _win_to_wsl(notebook_dir / "scripts" / "setup_wsl_python.sh")

    result = subprocess.run(
        ["wsl", "-d", "Ubuntu", "bash", script_wsl],
        capture_output=True,
        text=True,
        timeout=180, encoding="utf-8"  # 3 min pour semantic-kernel (gros package)
    )

    if result.returncode == 0:
        print(result.stdout)
        print("[OK] Kernel Python 3 (WSL) configure avec succes")
        print("     Semantic Kernel disponible pour Lean-8")
    else:
        print(f"[ERREUR] {result.stderr}")
        print("Installation manuelle:")
        print("  wsl -d Ubuntu")
        print(f"  bash {script_wsl}")

else:
    # Linux/WSL - installer directement avec pip
    print("=== Installation des packages Python ===")
    print(f"Packages: {', '.join(PACKAGES)}")
    print()

    missing = []
    for pkg in PACKAGES:
        try:
            __import__(pkg.replace("-", "_"))
        except ImportError:
            missing.append(pkg)

    if missing:
        print(f"Installation de: {', '.join(missing)}")
        result = subprocess.run(
            [sys.executable, "-m", "pip", "install", "--quiet"] + missing,
            capture_output=True,
            text=True,
            timeout=180, encoding="utf-8"
        )
        if result.returncode == 0:
            print("[OK] Packages installes avec succes")
        else:
            print(f"[ERREUR] {result.stderr}")
    else:
        print("[OK] Tous les packages sont deja installes")

    # Verification finale
    print("\nVerification:")
    for pkg in PACKAGES:
        try:
            mod_name = pkg.replace("-", "_")
            mod = __import__(mod_name)
            version = getattr(mod, "__version__", "installed")
            print(f"  [OK] {pkg}: {version}")
        except ImportError as e:
            print(f"  [!] {pkg}: non trouve ({e})")
=== Configuration Python 3 (WSL) ===
Packages: python-dotenv, openai, anthropic, matplotlib, semantic-kernel

=== Configuration Python 3 (WSL) pour notebooks Lean ===
Installation des dependances Python...
Installation de Semantic Kernel...

=== Verification ===
- python-dotenv 1.2.2
- openai 2.38.0
- anthropic 0.105.2
- matplotlib 3.10.9
- semantic-kernel 1.42.0

=== Configuration terminee ===
Le kernel Python 3 (WSL) est pret pour les notebooks Lean 7-8

[OK] Kernel Python 3 (WSL) configure avec succes
     Semantic Kernel disponible pour Lean-8

6. Configuration VSCode (optionnel)

Pour une meilleure expérience de développement avec Lean 4, VSCode avec l’extension Lean 4 est recommande.

Installation de l’extension

  1. Ouvrez VSCode
  2. Allez dans Extensions (Ctrl+Shift+X)
  3. Recherchez “Lean 4”
  4. Installez l’extension officielle “lean4”

Fonctionnalites de l’extension

  • Infoview : Affichage en temps reel des buts et hypotheses
  • Go to Definition : Navigation dans le code
  • Autocompletion : Suggestions intelligentes
  • Diagnostics : Erreurs et warnings en direct
  • Hover : Information sur les types au survol

Creation d’un projet Lean

# Créer un nouveau projet
lake new my_project
cd my_project

# Ouvrir dans VSCode
code .

L’extension Lean 4 se chargera automatiquement et vous pourrez commencer a ecrire des preuves.

7. Exercice : premier théorème en Lean 4

Difficulté : débutant | Durée estimée : 15 minutes

Les cellules de diagnostic ont vérifié l’installation ; cet exercice reste à réaliser par vous-même. Créez un fichier Lean et remplacez chaque sorry par une preuve. La compilation seule ne suffit pas : un fichier contenant sorry peut compiler en admettant le théorème sans le prouver.

Objectifs pédagogiques : - Créer un fichier Lean 4 et l’éditer dans VSCode (avec l’extension Lean 4) - Comprendre que sorry est un marqueur provisoire, pas une preuve - Utiliser des tactiques de base (rfl, simp) ou des lemmes (Nat.add_zero) - Vérifier la compilation et l’absence de sorry dans votre solution

Étape 1 : créer un fichier Lean

Dans votre dossier de travail, créez un fichier MonPremierTheoreme.lean contenant :

-- Théorème : pour tout nombre naturel n, n + 0 = n
theorem add_zero_test (n : Nat) : n + 0 = n := by
  -- TODO étudiant : compléter la preuve
  sorry

-- Bonus : variation symétrique (notez la différence de difficulté)
theorem zero_add_test (n : Nat) : 0 + n = n := by
  -- TODO étudiant : remplacer sorry par une preuve valide
  -- Indice : la récursion sur n est nécessaire ici (induction)
  sorry

Étape 2 : prouver add_zero_test

Le premier théorème est simple car Nat.add est définie récursivement sur le second argument. Indices progressifs : - Essayez d’abord rfl (égalité par définition) - Si échec, tentez simp (simplificateur) - Sinon, citez le lemme Nat.add_zero : exact Nat.add_zero n

Étape 3 : prouver zero_add_test

Plus subtil : 0 + n n’est pas réductible par rfl lorsque n est une variable. Indices : - Tactique induction n : cas de base et cas inductif - Cas de base : simp ou rfl - Cas inductif : utilisez l’hypothèse de récurrence et Nat.zero_add ou simp - Alternative directe : exact Nat.zero_add n (lemme disponible dans Lean)

Étape 4 : vérifier la compilation

lean MonPremierTheoreme.lean

Critère de réussite : la commande se termine sans erreur et votre fichier ne contient plus de sorry. Vérifiez le fichier de votre exercice, pas seulement les sorties de diagnostic de ce notebook. Ne confondez pas le succès de la compilation d’un stub avec une preuve achevée.

Aller plus loin

Une fois les deux théorèmes prouvés, étendez votre fichier avec un théorème non trivial :

-- Défi : commutativité de l'addition
theorem add_comm_test (a b : Nat) : a + b = b + a := by
  -- TODO étudiant : preuve par induction sur a (ou b)
  sorry

Comparez ensuite votre preuve avec le lemme Nat.add_comm.



Conclusion

Les cellules exécutées ont diagnostiqué les composants de cet environnement et, si nécessaire, proposé leur installation. Elles n’ont ni écrit ni compilé le fichier MonPremierTheoreme.lean : le premier théorème est l’exercice de la section 7, à réaliser avant de poursuivre.

Ce que les cellules ont vérifié

Étape Composant Résultat à lire dans les sorties
1 elan (gestionnaire de toolchain) Version détectée ou diagnostic d’absence
2 Lean 4 (compilateur et lake) Version détectée ou diagnostic d’absence
3 lean4_jupyter et REPL Module et disponibilité de repl
4 Kernel Jupyter (lean4 ou lean4-wsl) Enregistrement et test du kernel WSL, sur Windows
5 Configuration WSL (Windows) Wrapper et environnement Python, si les cellules correspondantes ont réussi
6 Premier théorème À faire : remplacer les sorry, compiler et contrôler le fichier de l’exercice

Concepts clés à retenir

  • elan : gestionnaire de versions analogue à rustup pour Rust. Permet de basculer entre différentes toolchains Lean (stable, nightly ou version épinglée par projet dans lean-toolchain).
  • Lake : système de build officiel de Lean 4. Le fichier lakefile.lean (ou lakefile.toml) déclare les dépendances et la structure du projet ; lake build compile.
  • REPL lean4_jupyter : serveur externe qui permet l’interaction du kernel Jupyter avec Lean. Il rend les commandes #check, #eval et les déclarations de théorèmes utilisables dans un notebook.
  • WSL sous Windows : le kernel lean4-wsl exécute Lean dans Ubuntu via un wrapper Python (~/.lean4-kernel-wrapper.py) qui gère les chemins Windows et WSL. Sous macOS/Linux, le kernel natif suffit.
  • Hiérarchie d’installation : elan → lean/lake → projet Lake (lakefile) → Mathlib (optionnel, cache via lake exe cache get).

Vérifications conseillées avant d’avancer

Si la cellule de vérification finale (section 4) affiche [OK] sur les cinq composants, cet environnement dispose des prérequis mesurés. Ce résultat ne valide pas une preuve Lean : terminez l’exercice de la section 7 et vérifiez-le séparément. Si une sonde échoue, consultez les sections « Installation manuelle » et « Configuration du kernel Lean 4 (WSL) » avant de passer à Lean-2.

Pour relancer un diagnostic à tout moment :

python MyIA.AI.Notebooks/SymbolicAI/Lean/scripts/validate_lean_setup.py --wsl
# ou --windows selon votre configuration

Prochaine étape

Dans Lean-02-Dependent-Types-Lean, vous découvrirez le cœur du système de types de Lean 4 : le Calcul des Constructions. Vous manipulerez Nat, Bool, les fonctions et les produits, puis la hiérarchie des univers et les types dépendants qui distinguent Lean des langages de programmation traditionnels. Ces fondations préparent l’isomorphisme de Curry-Howard et la construction de preuves formelles dans Lean-3.



8. Ressources et documentation

Documentation officielle

Mathlib4

Communaute

Outils et integrations


Notebooks suivants

Après avoir complete ce setup, continuez avec :

Partie 1 : Fondations - Lean-02-Dependent-Types-Lean.ipynb - Système de types dependants - Lean-03-Propositions-Proofs-Lean.ipynb - Logique et preuves - Lean-04-Quantifiers-Lean.ipynb - Quantificateurs - Lean-05-Tactics-Lean.ipynb - Tactiques

Partie 2 : Etat de l’art - Lean-06-Mathlib-Essentials-Lean.ipynb - Mathlib4 - Lean-07-LLM-Integration-Lean-Python.ipynb - Integration LLM - Lean-08-Agentic-Proving-Python.ipynb - Agents autonomes


Navigation : ← (debut) | Index | Lean-02-Dependent-Types-Lean →

Retour au sommet