lean-interact 0.9.3__tar.gz → 0.10.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. {lean_interact-0.9.3 → lean_interact-0.10.1}/PKG-INFO +2 -2
  2. {lean_interact-0.9.3 → lean_interact-0.10.1}/README.md +1 -1
  3. {lean_interact-0.9.3 → lean_interact-0.10.1}/docs/api/sessioncache.md +4 -0
  4. {lean_interact-0.9.3 → lean_interact-0.10.1}/docs/index.md +1 -1
  5. {lean_interact-0.9.3 → lean_interact-0.10.1}/docs/user-guide/examples.md +2 -2
  6. lean_interact-0.10.1/docs/user-guide/performance.md +224 -0
  7. {lean_interact-0.9.3 → lean_interact-0.10.1}/examples/beq_plus.py +41 -40
  8. {lean_interact-0.9.3 → lean_interact-0.10.1}/examples/extract_mathlib_decls.py +7 -0
  9. lean_interact-0.10.1/examples/parallelization.py +125 -0
  10. lean_interact-0.10.1/examples/type_check.py +69 -0
  11. {lean_interact-0.9.3 → lean_interact-0.10.1}/mkdocs.yml +1 -1
  12. {lean_interact-0.9.3 → lean_interact-0.10.1}/pyproject.toml +1 -1
  13. {lean_interact-0.9.3 → lean_interact-0.10.1}/src/lean_interact/__init__.py +3 -0
  14. {lean_interact-0.9.3 → lean_interact-0.10.1}/src/lean_interact/server.py +7 -7
  15. lean_interact-0.10.1/src/lean_interact/sessioncache.py +447 -0
  16. {lean_interact-0.9.3 → lean_interact-0.10.1}/src/lean_interact/utils.py +1 -1
  17. {lean_interact-0.9.3 → lean_interact-0.10.1}/tests/test_server.py +184 -30
  18. lean_interact-0.9.3/docs/user-guide/performance.md +0 -199
  19. lean_interact-0.9.3/examples/multi_processing.py +0 -60
  20. lean_interact-0.9.3/examples/type_check.py +0 -118
  21. lean_interact-0.9.3/src/lean_interact/sessioncache.py +0 -235
  22. {lean_interact-0.9.3 → lean_interact-0.10.1}/.devcontainer/devcontainer.json +0 -0
  23. {lean_interact-0.9.3 → lean_interact-0.10.1}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  24. {lean_interact-0.9.3 → lean_interact-0.10.1}/.github/copilot-instructions.md +0 -0
  25. {lean_interact-0.9.3 → lean_interact-0.10.1}/.github/workflows/ci.yml +0 -0
  26. {lean_interact-0.9.3 → lean_interact-0.10.1}/.github/workflows/docs.yml +0 -0
  27. {lean_interact-0.9.3 → lean_interact-0.10.1}/.github/workflows/publish-to-pypi.yml +0 -0
  28. {lean_interact-0.9.3 → lean_interact-0.10.1}/.gitignore +0 -0
  29. {lean_interact-0.9.3 → lean_interact-0.10.1}/LICENSE +0 -0
  30. {lean_interact-0.9.3 → lean_interact-0.10.1}/docs/.nojekyll +0 -0
  31. {lean_interact-0.9.3 → lean_interact-0.10.1}/docs/api/config.md +0 -0
  32. {lean_interact-0.9.3 → lean_interact-0.10.1}/docs/api/interface.md +0 -0
  33. {lean_interact-0.9.3 → lean_interact-0.10.1}/docs/api/project.md +0 -0
  34. {lean_interact-0.9.3 → lean_interact-0.10.1}/docs/api/server.md +0 -0
  35. {lean_interact-0.9.3 → lean_interact-0.10.1}/docs/api/utils.md +0 -0
  36. {lean_interact-0.9.3 → lean_interact-0.10.1}/docs/contributing.md +0 -0
  37. {lean_interact-0.9.3 → lean_interact-0.10.1}/docs/generate_changelog.py +0 -0
  38. {lean_interact-0.9.3 → lean_interact-0.10.1}/docs/theme/main.html +0 -0
  39. {lean_interact-0.9.3 → lean_interact-0.10.1}/docs/user-guide/basic-usage.md +0 -0
  40. {lean_interact-0.9.3 → lean_interact-0.10.1}/docs/user-guide/custom-lean-configuration.md +0 -0
  41. {lean_interact-0.9.3 → lean_interact-0.10.1}/docs/user-guide/data-extraction.md +0 -0
  42. {lean_interact-0.9.3 → lean_interact-0.10.1}/docs/user-guide/getting-started.md +0 -0
  43. {lean_interact-0.9.3 → lean_interact-0.10.1}/docs/user-guide/installation.md +0 -0
  44. {lean_interact-0.9.3 → lean_interact-0.10.1}/docs/user-guide/set-options.md +0 -0
  45. {lean_interact-0.9.3 → lean_interact-0.10.1}/docs/user-guide/tactic-mode.md +0 -0
  46. {lean_interact-0.9.3 → lean_interact-0.10.1}/docs/user-guide/troubleshooting.md +0 -0
  47. {lean_interact-0.9.3 → lean_interact-0.10.1}/examples/proof_generation_and_autoformalization.py +0 -0
  48. {lean_interact-0.9.3 → lean_interact-0.10.1}/src/lean_interact/config.py +0 -0
  49. {lean_interact-0.9.3 → lean_interact-0.10.1}/src/lean_interact/interface.py +0 -0
  50. {lean_interact-0.9.3 → lean_interact-0.10.1}/src/lean_interact/project.py +0 -0
  51. {lean_interact-0.9.3 → lean_interact-0.10.1}/src/lean_interact/py.typed +0 -0
  52. {lean_interact-0.9.3 → lean_interact-0.10.1}/tests/test_concurrency.py +0 -0
  53. {lean_interact-0.9.3 → lean_interact-0.10.1}/tests/test_git_functionality.py +0 -0
  54. {lean_interact-0.9.3 → lean_interact-0.10.1}/tests/test_utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lean-interact
3
- Version: 0.9.3
3
+ Version: 0.10.1
4
4
  Summary: LeanInteract is a Python package that allows you to interact with the Lean theorem prover.
5
5
  Author-email: Auguste Poiroux <auguste.poiroux@epfl.ch>
6
6
  License: MIT License
@@ -53,7 +53,7 @@ Check the [documentation](https://augustepoiroux.github.io/LeanInteract/) for de
53
53
  - **🔗 Interactivity**: Execute Lean code and files directly from Python.
54
54
  - **🚀 Ease of Use**: LeanInteract abstracts the complexities of Lean setup and interaction.
55
55
  - **💻 Cross-platform**: Works on Windows, macOS, and Linux operating systems.
56
- - **🔧 Compatibility**: Supports all Lean versions between `v4.8.0-rc1` and `v4.25.0`.
56
+ - **🔧 Compatibility**: Supports all Lean versions between `v4.8.0-rc1` and `v4.26.0-rc1`.
57
57
  - We backport the latest features of Lean REPL to older versions of Lean (see [fork](https://github.com/augustepoiroux/repl)).
58
58
  - **📦 Temporary Projects**: Easily instantiate temporary Lean environments.
59
59
  - Useful for experimenting with benchmarks depending on [Mathlib](https://github.com/leanprover-community/mathlib4) like [ProofNet#](https://huggingface.co/datasets/PAug/ProofNetSharp) and [MiniF2F](https://github.com/yangky11/miniF2F-lean4).
@@ -15,7 +15,7 @@ Check the [documentation](https://augustepoiroux.github.io/LeanInteract/) for de
15
15
  - **🔗 Interactivity**: Execute Lean code and files directly from Python.
16
16
  - **🚀 Ease of Use**: LeanInteract abstracts the complexities of Lean setup and interaction.
17
17
  - **💻 Cross-platform**: Works on Windows, macOS, and Linux operating systems.
18
- - **🔧 Compatibility**: Supports all Lean versions between `v4.8.0-rc1` and `v4.25.0`.
18
+ - **🔧 Compatibility**: Supports all Lean versions between `v4.8.0-rc1` and `v4.26.0-rc1`.
19
19
  - We backport the latest features of Lean REPL to older versions of Lean (see [fork](https://github.com/augustepoiroux/repl)).
20
20
  - **📦 Temporary Projects**: Easily instantiate temporary Lean environments.
21
21
  - Useful for experimenting with benchmarks depending on [Mathlib](https://github.com/leanprover-community/mathlib4) like [ProofNet#](https://huggingface.co/datasets/PAug/ProofNetSharp) and [MiniF2F](https://github.com/yangky11/miniF2F-lean4).
@@ -1,9 +1,13 @@
1
+ # Session Cache API
2
+
1
3
  ::: lean_interact.sessioncache
2
4
  options:
3
5
  heading_level: 1
4
6
  heading: "Session Cache"
5
7
  show_symbol_type_heading: false
6
8
  members:
9
+ - ReplaySessionCache
7
10
  - PickleSessionCache
8
11
  - SessionState
12
+ - ReplaySessionState
9
13
  - PickleSessionState
@@ -17,7 +17,7 @@ hide:
17
17
  - **🔗 Interactivity**: Execute Lean code and files directly from Python
18
18
  - **🚀 Ease of Use**: LeanInteract abstracts the complexities of Lean setup and interaction
19
19
  - **💻 Cross-platform**: Works on Windows, macOS, and Linux operating systems
20
- - **🔧 Compatibility**: Supports all Lean versions between `v4.8.0-rc1` and `v4.25.0`
20
+ - **🔧 Compatibility**: Supports all Lean versions between `v4.8.0-rc1` and `v4.26.0-rc1`
21
21
  - We backport the latest features of Lean REPL to older versions of Lean (see [fork](https://github.com/augustepoiroux/repl)).
22
22
  - **📦 Temporary Projects**: Easily instantiate temporary Lean environments
23
23
  - Useful for experimenting with benchmarks depending on [Mathlib](https://github.com/leanprover-community/mathlib4) like [ProofNet#](https://huggingface.co/datasets/PAug/ProofNetSharp) and [MiniF2F](https://github.com/yangky11/miniF2F-lean4)
@@ -73,8 +73,8 @@ print(result) # Example raw output from the REPL
73
73
 
74
74
  For more comprehensive examples, check out the following scripts in the examples directory:
75
75
 
76
- 1. [**multi_processing.py**](https://github.com/augustepoiroux/LeanInteract/blob/main/examples/multi_processing.py)
77
- Shows how to use multiprocessing with LeanInteract for parallel proof checking.
76
+ 1. [**parallelization.py**](https://github.com/augustepoiroux/LeanInteract/blob/main/examples/parallelization.py)
77
+ Shows how to parallelize calls to LeanInteract for faster processing.
78
78
 
79
79
  2. [**proof_generation_and_autoformalization.py**](https://github.com/augustepoiroux/LeanInteract/blob/main/examples/proof_generation_and_autoformalization.py)
80
80
  Shows how to use models like DeepSeek-Prover-V1.5 and Goedel-Prover on MiniF2F and ProofNet# benchmarks.
@@ -0,0 +1,224 @@
1
+ # Performance & Parallelization
2
+
3
+ LeanInteract provides **three complementary performance layers** that can be combined:
4
+
5
+ 1. **Within-command speedups (inside a single Lean request)**
6
+ - **Incremental elaboration**: reuse previous elaboration results instead of starting from scratch.
7
+ - **Parallel elaboration**: use `Elab.async` so Lean elaborates independent parts in parallel.
8
+ 2. **Across-command speedups (multiple commands / tasks)**
9
+ - **External parallelization**: run several Lean servers in parallel, typically via `AutoLeanServer`.
10
+
11
+ All three can be enabled together for maximum throughput: fast single commands (incremental + parallel elaboration) and high aggregate throughput across many commands (external parallelization).
12
+ By default, **incremental elaboration** and **parallel elaboration** are enabled in `LeanREPLConfig`. **External parallelization** can be implemented in various ways, thus no default is provided.
13
+
14
+ ## Incremental Elaboration
15
+
16
+ Incremental elaboration is a free performance boost that reduces latency and memory by automatically reusing elaboration results from prior commands executed on the same `LeanServer`.
17
+
18
+ ### How it works
19
+
20
+ In Lean, incremental elaboration allows reusing the elaboration state of a prior command when elaborating a new command that shares a common prefix.
21
+ For example, if you have already elaborated:
22
+
23
+ ```lean
24
+ import Mathlib
25
+ def foo : Nat := 42
26
+ ```
27
+
28
+ and now want to elaborate:
29
+
30
+ ```lean
31
+ import Mathlib
32
+ def bar : Nat := 56
33
+ ```
34
+
35
+ then Lean can reuse the elaboration state after `import Mathlib` from the first command instead of starting from scratch, effectively loading Mathlib only once.
36
+
37
+ Incremental elaboration therefore **automatically finds the best incremental state from the previous command** to reuse for the new command. For VS Code users, this corresponds to how Lean4's built-in language server reuses elaboration states when editing files (vertical orange loading bar in the gutter).
38
+
39
+ LeanInteract generalizes this mechanism further by extending the search for the best incremental state to reuse to **all prior commands** in the history, not just the most recent one. The optimal incremental state is found using a Trie-based data structure, which in practice has no noticeable overhead in both memory and CPU usage.
40
+
41
+ ### Properties
42
+
43
+ - Write commands in any order without manually worrying about managing command states.
44
+ - The Trie-based history lookup ensures that the best reuse point is found efficiently, independent of the number of prior commands.
45
+ - In the worst case, memory and CPU usage will be similar to non-incremental elaboration (if no reuse is possible).
46
+ - Particularly useful when checking batches of file edits or multiple similar commands.
47
+
48
+ ### How to use it
49
+
50
+ - Create your `LeanREPLConfig` with default settings (or ensure `enable_incremental_optimization=True`).
51
+ - Simply send commands to the same `LeanServer` instance as usual, incremental elaboration will be automatically applied.
52
+ - **Recommendation:** Instead of splitting your code into small chunks like in a REPL, you should send full commands or file contents. LeanInteract will find the best reuse points automatically.
53
+
54
+ ### Example
55
+
56
+ Below is a small script that measures the elapsed time of two "heavy" commands, but the second command benefits from incremental reuse:
57
+
58
+ ```python exec="on" source="above" session="perf" result="python"
59
+ import time
60
+ from lean_interact import LeanREPLConfig, LeanServer, Command
61
+
62
+ server = LeanServer(LeanREPLConfig())
63
+
64
+ t1 = time.perf_counter()
65
+ print(server.run(Command(cmd="""
66
+ def fib : Nat → Nat
67
+ | 0 => 0
68
+ | 1 => 1
69
+ | n + 2 => fib (n + 1) + fib n
70
+ #eval fib 35
71
+
72
+ theorem foo : n = n := by rfl
73
+ #check foo
74
+ """)))
75
+ print(f"First run: {time.perf_counter() - t1:.3f}s")
76
+
77
+ t2 = time.perf_counter()
78
+ print(server.run(Command(cmd="""
79
+ def fib : Nat → Nat
80
+ | 0 => 0
81
+ | 1 => 1
82
+ | n + 2 => fib (n + 1) + fib n
83
+ #eval fib 35
84
+
85
+ theorem foo2 : n = n+0 := by rfl
86
+ #check foo2
87
+ """)))
88
+ print(f"Second run: {time.perf_counter() - t2:.3f}s")
89
+ ```
90
+
91
+ !!! warning Imports are cached
92
+ Imports are cached in incremental mode, meaning that if the content of one of your imported file has changed, it will not be taken into account unless you restart the server.
93
+
94
+ You can disable this feature by setting `enable_incremental_optimization=False` in `LeanREPLConfig`.
95
+
96
+ ## Parallel Elaboration
97
+
98
+ When supported (Lean >= v4.19.0), Lean can elaborate different parts of a command/file in parallel. LeanInteract auto-enables this by adding `set_option Elab.async true` to each request.
99
+ You can disable it if needed by setting `enable_parallel_elaboration=False` in `LeanREPLConfig`.
100
+
101
+ !!! note
102
+ Only available for Lean >= v4.19.0
103
+
104
+ ---
105
+
106
+ ## Parallelization Guide (multiple commands)
107
+
108
+ LeanInteract is designed with parallelization in mind, allowing you to leverage multiple CPU cores for parallel theorem proving and verification tasks.
109
+
110
+ This section focuses on **external parallelization**: running multiple Lean servers (usually via `AutoLeanServer`) in parallel threads, processes or workers. This is complementary to the within-command optimizations above and can be combined with them.
111
+
112
+ We recommend using `AutoLeanServer`. It is specifically designed for parallel environments with automated restart on fatal Lean errors, timeouts, and when memory limits are reached. On automated restarts, only commands run with `add_to_session_cache=True` (attribute of the `AutoLeanServer.run` method) will be preserved.
113
+
114
+ `AutoLeanServer` is still experimental, feedback and issues are welcome.
115
+
116
+ ### Best Practices Summary
117
+
118
+ 1. **Always pre-instantiate** `LeanREPLConfig` before parallelization
119
+ 2. **One lean server per process/thread**
120
+ 3. **Use `AutoLeanServer`**
121
+ 4. **Configure memory limits** to prevent system overload
122
+ 5. **Set appropriate timeouts** for long-running operations
123
+ 6. **Use session caching** to keep context between requests
124
+ 7. **Consider using `maxtasksperchild`** to limit memory accumulation
125
+
126
+ ### Quick Start
127
+
128
+ ```python
129
+ from multiprocessing import Pool
130
+ from lean_interact import AutoLeanServer, Command, LeanREPLConfig
131
+ from lean_interact.interface import LeanError
132
+
133
+ def worker(config: LeanREPLConfig, task_id: int):
134
+ """Worker function that runs in each process"""
135
+ server = AutoLeanServer(config)
136
+ result = server.run(Command(cmd=f"#eval {task_id} * {task_id}"))
137
+ return f"Task {task_id}: {result.messages[0].data if not isinstance(result, LeanError) else 'Error'}"
138
+
139
+ # Pre-instantiate config before parallelization (downloads/initializes resources)
140
+ config = LeanREPLConfig(verbose=True)
141
+ with Pool() as p:
142
+ print(p.starmap(worker, [(config, i) for i in range(5)]))
143
+ ```
144
+
145
+ For more examples, check the [examples directory](https://github.com/augustepoiroux/LeanInteract/tree/main/examples).
146
+
147
+ ### Core Principles
148
+
149
+ #### 1. Pre-instantiate Configuration
150
+
151
+ Always create your `LeanREPLConfig` instance **before** starting parallelization:
152
+
153
+ ```python
154
+ from lean_interact import LeanREPLConfig, AutoLeanServer
155
+ import multiprocessing as mp
156
+
157
+ # ✅ CORRECT: Config created in main process
158
+ config = LeanREPLConfig() # Pre-setup in main process
159
+
160
+ def worker(cfg):
161
+ server = AutoLeanServer(cfg) # Use pre-configured config
162
+ # ... your work here
163
+ pass
164
+
165
+ ctx = mp.get_context("spawn")
166
+ with ctx.Pool() as pool:
167
+ pool.map(worker, [config] * 4)
168
+
169
+ # ❌ INCORRECT: Config created in each process
170
+ def worker():
171
+ config = LeanREPLConfig()
172
+ server = AutoLeanServer(config)
173
+ # ... your work here
174
+ pass
175
+
176
+ ctx = mp.get_context("spawn")
177
+ with ctx.Pool() as pool:
178
+ pool.map(worker, range(4))
179
+ ```
180
+
181
+ #### 2. One Server Per Process/Thread
182
+
183
+ Each process or thread should have its own `LeanServer` or `AutoLeanServer` instance.
184
+
185
+ ```python
186
+ def worker(config, task_data):
187
+ # Each process gets its own server
188
+ server = AutoLeanServer(config)
189
+
190
+ for task in task_data:
191
+ result = server.run(task)
192
+ # Handle result
193
+
194
+ return results
195
+ ```
196
+
197
+ ### Thread Safety
198
+
199
+ Within a single process, `LeanServer` and `AutoLeanServer` are thread-safe thanks to internal locking. All concurrent requests are processed sequentially. Across processes, servers are not shareable: each process must create its own instance.
200
+
201
+ Similarly, `ReplaySessionCache` is thread-safe within a process, meaning multiple `AutoLeanServer` in different threads can safely share the same cache instance. However, across processes, each must have its own cache instance.
202
+
203
+ ### Memory Management
204
+
205
+ ```python
206
+ from lean_interact import AutoLeanServer, LeanREPLConfig
207
+
208
+ # Configure memory limits for multi-process safety
209
+ config = LeanREPLConfig(memory_hard_limit_mb=8192) # 8GB per server, works on Linux only
210
+
211
+ server = AutoLeanServer(
212
+ config,
213
+ max_total_memory=0.8, # Restart when system uses >80% memory
214
+ max_process_memory=0.8, # Restart when process uses >80% of memory limit
215
+ max_restart_attempts=5 # Allow up to 5 restart attempts per command
216
+ )
217
+ ```
218
+
219
+ #### Memory Configuration Options
220
+
221
+ - `max_total_memory`: System-wide memory threshold (0.0-1.0)
222
+ - `max_process_memory`: Per-process memory threshold (0.0-1.0)
223
+ - `memory_hard_limit_mb`: Hard memory limit in MB (Linux only)
224
+ - `max_restart_attempts`: Maximum consecutive restart attempts
@@ -12,14 +12,20 @@
12
12
 
13
13
  Citation:
14
14
  ```bibtex
15
- @misc{poiroux2024improvingautoformalizationusingtype,
16
- title={Improving Autoformalization using Type Checking},
17
- author={Auguste Poiroux and Gail Weiss and Viktor Kunčak and Antoine Bosselut},
18
- year={2024},
19
- eprint={2406.07222},
20
- archivePrefix={arXiv},
21
- primaryClass={cs.CL},
22
- url={https://arxiv.org/abs/2406.07222},
15
+ @inproceedings{poiroux-etal-2025-reliable,
16
+ title = "Reliable Evaluation and Benchmarks for Statement Autoformalization",
17
+ author = "Poiroux, Auguste and
18
+ Weiss, Gail and
19
+ Kun{\v{c}}ak, Viktor and
20
+ Bosselut, Antoine",
21
+ booktitle = "Proceedings of the 2025 Conference on Empirical Methods in Natural Language Processing",
22
+ month = nov,
23
+ year = "2025",
24
+ publisher = "Association for Computational Linguistics",
25
+ url = "https://aclanthology.org/2025.emnlp-main.907/",
26
+ doi = "10.18653/v1/2025.emnlp-main.907",
27
+ pages = "17958--17980",
28
+ ISBN = "979-8-89176-332-6",
23
29
  }
24
30
  ```
25
31
  """
@@ -66,7 +72,6 @@ def extract_exact_proof(lean_output: CommandResponse, proof_start_line: int | No
66
72
  def check_proof_sub(
67
73
  server: AutoLeanServer,
68
74
  formal_code: str,
69
- context_env: int,
70
75
  formal_2_start_line: int,
71
76
  proof: str,
72
77
  timeout: int,
@@ -92,7 +97,6 @@ def check_proof_sub(
92
97
  lean_output = server.run(
93
98
  Command(
94
99
  cmd=formal_code + indent_code(prepended + proof, indent_level),
95
- env=context_env,
96
100
  ),
97
101
  timeout=timeout,
98
102
  )
@@ -118,7 +122,7 @@ def beql(
118
122
  formalization_1: str,
119
123
  formalization_2: str,
120
124
  src_header: str,
121
- repl_config: LeanREPLConfig,
125
+ server: AutoLeanServer,
122
126
  timeout_per_proof: int,
123
127
  verbose: bool = False,
124
128
  ) -> bool:
@@ -135,11 +139,6 @@ def beql(
135
139
  Returns:
136
140
  True if both directions of the equivalence hold; False otherwise.
137
141
  """
138
- server = AutoLeanServer(config=repl_config)
139
- context_run = server.run(Command(cmd=src_header), add_to_session_cache=True)
140
- assert isinstance(context_run, CommandResponse)
141
- context_env = context_run.env
142
-
143
142
  base_thm_name = "base_theorem"
144
143
  reformulated_thm_name = "reformulated_theorem"
145
144
 
@@ -150,7 +149,9 @@ def beql(
150
149
  if verbose:
151
150
  console.print(f"=====\nChecking {'1 -> 2' if i == 0 else '2 -> 1'}")
152
151
  try:
153
- formal_1_code = clean_last_theorem_string(base_thm, base_thm_name, add_sorry=True) + "\n\n"
152
+ formal_1_code = (
153
+ src_header + "\n\n" + clean_last_theorem_string(base_thm, base_thm_name, add_sorry=True) + "\n\n"
154
+ )
154
155
  formal_2_start_line = formal_1_code.count("\n") + 1
155
156
  formal_2_code = f"{clean_last_theorem_string(reform_thm, reformulated_thm_name, add_sorry=False)} := by"
156
157
  except ValueError:
@@ -160,14 +161,12 @@ def beql(
160
161
 
161
162
  formal_code = formal_1_code + formal_2_code
162
163
  # Preliminary check to ensure the formalization is well-typed.
163
- if check_proof_sub(server, formal_code, context_env, formal_2_start_line, "sorry", timeout_per_proof) is None:
164
+ if check_proof_sub(server, formal_code, formal_2_start_line, "sorry", timeout_per_proof) is None:
164
165
  if verbose:
165
166
  console.print("Ill-typed formalization encountered, skipping this pair.")
166
167
  break
167
168
 
168
- proof_exact = check_proof_sub(
169
- server, formal_code, context_env, formal_2_start_line, "exact?", timeout_per_proof
170
- )
169
+ proof_exact = check_proof_sub(server, formal_code, formal_2_start_line, "exact?", timeout_per_proof)
171
170
  if proof_exact and base_thm_name in proof_exact:
172
171
  res[i] = True
173
172
  if verbose:
@@ -183,7 +182,7 @@ def beq_plus(
183
182
  formalization_1: str,
184
183
  formalization_2: str,
185
184
  src_header: str,
186
- repl_config: LeanREPLConfig,
185
+ server: AutoLeanServer,
187
186
  timeout_per_proof: int,
188
187
  verbose: bool = False,
189
188
  ) -> bool:
@@ -200,11 +199,6 @@ def beq_plus(
200
199
  Returns:
201
200
  True if both directions of the equivalence hold; False otherwise.
202
201
  """
203
- server = AutoLeanServer(config=repl_config)
204
- context_run = server.run(Command(cmd=src_header), add_to_session_cache=True)
205
- assert isinstance(context_run, CommandResponse)
206
- context_env = context_run.env
207
-
208
202
  base_thm_name = "base_theorem"
209
203
  reformulated_thm_name = "reformulated_theorem"
210
204
 
@@ -225,7 +219,9 @@ def beq_plus(
225
219
  if verbose:
226
220
  console.print(f"=====\nChecking {'1 -> 2' if i == 0 else '2 -> 1'}")
227
221
  try:
228
- formal_1_code = clean_last_theorem_string(base_thm, base_thm_name, add_sorry=True) + "\n\n"
222
+ formal_1_code = (
223
+ src_header + "\n\n" + clean_last_theorem_string(base_thm, base_thm_name, add_sorry=True) + "\n\n"
224
+ )
229
225
  formal_2_start_line = formal_1_code.count("\n") + 1
230
226
  formal_2_code = f"{clean_last_theorem_string(reform_thm, reformulated_thm_name, add_sorry=False)} := by"
231
227
  except ValueError:
@@ -234,15 +230,13 @@ def beq_plus(
234
230
  break
235
231
 
236
232
  formal_code = formal_1_code + formal_2_code
237
- if check_proof_sub(server, formal_code, context_env, formal_2_start_line, "sorry", timeout_per_proof) is None:
233
+ if check_proof_sub(server, formal_code, formal_2_start_line, "sorry", timeout_per_proof) is None:
238
234
  if verbose:
239
235
  console.print("Ill-typed formalization encountered, skipping this pair.")
240
236
  break
241
237
 
242
238
  # 1. Use BEqL
243
- proof_exact = check_proof_sub(
244
- server, formal_code, context_env, formal_2_start_line, "exact?", timeout_per_proof
245
- )
239
+ proof_exact = check_proof_sub(server, formal_code, formal_2_start_line, "exact?", timeout_per_proof)
246
240
  if proof_exact and base_thm_name in proof_exact:
247
241
  res[i] = True
248
242
  if verbose:
@@ -250,11 +244,16 @@ def beq_plus(
250
244
  console.print(Syntax(proof_exact, "lean4"))
251
245
  continue
252
246
 
247
+ # If trivially provable by assumption, we skip
248
+ if check_proof_sub(server, formal_code, formal_2_start_line, "assumption", timeout_per_proof):
249
+ if verbose:
250
+ console.print("Skipping as provable by assumption")
251
+ continue
252
+
253
253
  # 2. try to apply the base theorem directly
254
254
  proof_apply = check_proof_sub(
255
255
  server,
256
256
  formal_code,
257
- context_env,
258
257
  formal_2_start_line,
259
258
  f"apply {base_thm_name}\n" + proof_all_apply,
260
259
  timeout_per_proof,
@@ -272,9 +271,7 @@ def beq_plus(
272
271
  # drawback of `have` strategy: variable names/types must match exactly
273
272
  provable_without_have = False
274
273
  try:
275
- res_without_have = server.run(
276
- Command(cmd=formal_code + proof_all_have, env=context_env), timeout=timeout_per_proof
277
- )
274
+ res_without_have = server.run(Command(cmd=formal_2_code + proof_all_have), timeout=timeout_per_proof)
278
275
  if isinstance(res_without_have, CommandResponse):
279
276
  provable_without_have = res_without_have.lean_code_is_valid(allow_sorry=False)
280
277
  except TimeoutError:
@@ -295,7 +292,6 @@ def beq_plus(
295
292
  proof_have = check_proof_sub(
296
293
  server,
297
294
  formal_code,
298
- context_env,
299
295
  formal_2_start_line,
300
296
  have_stmt_proof + proof_all_have,
301
297
  timeout_per_proof,
@@ -312,7 +308,6 @@ def beq_plus(
312
308
  proof_convert = check_proof_sub(
313
309
  server,
314
310
  formal_code,
315
- context_env,
316
311
  formal_2_start_line,
317
312
  f"convert (config := .unfoldSameFun) {base_thm_name} using {max_step}\n" + proof_all_apply,
318
313
  timeout_per_proof,
@@ -332,6 +327,7 @@ def beq_plus(
332
327
 
333
328
  def examples_limitations(metric):
334
329
  repl_config = LeanREPLConfig(project=TempRequireProject(lean_version="v4.8.0", require="mathlib"), verbose=True)
330
+ server = AutoLeanServer(config=repl_config)
335
331
 
336
332
  src_header = """import Mathlib
337
333
 
@@ -340,6 +336,10 @@ open Set Real Ideal Polynomial
340
336
  open scoped BigOperators"""
341
337
 
342
338
  formalization_pairs = [
339
+ ( # negative example -- not semantically equivalent
340
+ "theorem prediction (a b : ℤ) (ha : a ∣ b) : a ∣ (b : ℤ) :=",
341
+ "theorem ground_truth (a b : ℤ) : (Zsqrtd.ofInt a : GaussianInt) ∣ Zsqrtd.ofInt b → a ∣ b :=",
342
+ ),
343
343
  (
344
344
  "theorem random_name_1 {G : Type*} [Group G] [Fintype G] (h : Fintype.card G % 2 = 0) :\n ∃ a : G, a ≠ 1 ∧ a = a⁻¹ :=",
345
345
  "theorem random_name_2 {G : Type*} [Group G] [Fintype G] (hG2 : Even (card G)) :\n ∃ (a : G), a ≠ 1 ∧ a = a⁻¹ :=",
@@ -398,7 +398,7 @@ open scoped BigOperators"""
398
398
  formalization_1,
399
399
  formalization_2,
400
400
  src_header,
401
- repl_config,
401
+ server,
402
402
  timeout_per_proof=DEFAULT_TIMEOUT,
403
403
  verbose=True,
404
404
  )
@@ -413,6 +413,7 @@ open scoped BigOperators"""
413
413
 
414
414
  def proofnetverif(metric, n_samples=100):
415
415
  repl_config = LeanREPLConfig(project=TempRequireProject(lean_version="v4.8.0", require="mathlib"), verbose=True)
416
+ server = AutoLeanServer(config=repl_config)
416
417
 
417
418
  dataset = load_dataset("PAug/ProofNetVerif", split="valid")
418
419
  dataset = dataset.shuffle(seed=42).select(range(n_samples))
@@ -424,7 +425,7 @@ def proofnetverif(metric, n_samples=100):
424
425
  example["lean4_formalization"],
425
426
  example["lean4_prediction"],
426
427
  example["lean4_src_header"],
427
- repl_config,
428
+ server,
428
429
  timeout_per_proof=DEFAULT_TIMEOUT,
429
430
  verbose=False,
430
431
  )
@@ -1,3 +1,10 @@
1
+ # /// script
2
+ # requires-python = ">=3.10"
3
+ # dependencies = [
4
+ # "lean-interact",
5
+ # "tqdm",
6
+ # ]
7
+ # ///
1
8
  """Clone mathlib4 and extract all Lean declarations in parallel (per-file tasks).
2
9
 
3
10
  Output: mathlib_declarations.jsonl (JSONL, one declaration per line).
@@ -0,0 +1,125 @@
1
+ # /// script
2
+ # requires-python = ">=3.10"
3
+ # dependencies = ["lean-interact", "joblib"]
4
+ # ///
5
+ """
6
+ Example demonstrating multi-processing and multi-threading with LeanInteract.
7
+
8
+ This example shows the correct pattern for using LeanInteract with multiple processes:
9
+ 1. Pre-instantiate the config before starting multiprocessing
10
+ 2. Use spawn context for cross-platform compatibility
11
+ 3. Each process gets its own server instance
12
+
13
+ Run this example with: python examples/parallelization.py
14
+ """
15
+
16
+ import concurrent.futures
17
+ import multiprocessing as mp
18
+ import time
19
+ from contextlib import contextmanager
20
+
21
+ from joblib import Parallel, delayed # type: ignore
22
+
23
+ from lean_interact import AutoLeanServer, LeanREPLConfig
24
+ from lean_interact.interface import Command, LeanError
25
+
26
+
27
+ def worker(config: LeanREPLConfig, task_id: int) -> str:
28
+ """Worker function that runs in each process"""
29
+ try:
30
+ # Each process gets its own server instance
31
+ server = AutoLeanServer(config)
32
+ result = server.run(
33
+ Command(
34
+ cmd="""
35
+ def fib : Nat → Nat
36
+ | 0 => 0
37
+ | 1 => 1
38
+ | n + 2 => fib (n + 1) + fib n
39
+ #eval fib 32
40
+ """
41
+ )
42
+ )
43
+ if isinstance(result, LeanError):
44
+ return f"Task {task_id}: LeanError - {result}"
45
+ return f"Task {task_id}: {result.messages[0].data}"
46
+ except Exception as e:
47
+ return f"Task {task_id}: Exception - {e}"
48
+
49
+
50
+ def setup() -> tuple[LeanREPLConfig, list[int]]:
51
+ print("Setting up LeanREPLConfig (may take a few minutes the first time)...")
52
+ config = LeanREPLConfig(verbose=True)
53
+ print("Config setup complete.")
54
+
55
+ # Dummy tasks
56
+ tasks = list(range(8))
57
+
58
+ return config, tasks
59
+
60
+
61
+ @contextmanager
62
+ def timed_section(title: str, *, summary_prefix: str | None = None):
63
+ print("\n\n" + "=" * 40)
64
+ print(title)
65
+ start_time = time.time()
66
+ try:
67
+ yield
68
+ finally:
69
+ elapsed = time.time() - start_time
70
+ label = summary_prefix or title
71
+ print(f"{label} took {elapsed:.2f} seconds.")
72
+
73
+
74
+ def print_results(results: list[str]) -> None:
75
+ """Utility function to print results"""
76
+ print("\nResults:")
77
+ print("-" * 40)
78
+ for result in results:
79
+ print(result)
80
+
81
+
82
+ def sequential_baseline(config: LeanREPLConfig, tasks: list[int]) -> None:
83
+ """Run tasks sequentially for baseline comparison"""
84
+ with timed_section("Sequential Baseline", summary_prefix="Sequential processing"):
85
+ results = []
86
+ for task_id in tasks:
87
+ result = worker(config, task_id)
88
+ results.append(result)
89
+ print_results(results)
90
+
91
+
92
+ def multiprocessing_example(config: LeanREPLConfig, tasks: list[int]) -> None:
93
+ """Demonstrate multiprocessing with LeanInteract"""
94
+ with timed_section("Multi-processing Example", summary_prefix="Multi-processing"):
95
+ ctx = mp.get_context("spawn")
96
+ with ctx.Pool(processes=min(4, len(tasks))) as pool:
97
+ results = pool.starmap(worker, [(config, task_id) for task_id in tasks])
98
+ print_results(results)
99
+
100
+
101
+ def multithreading_example(config: LeanREPLConfig, tasks: list[int]) -> None:
102
+ """Demonstrate multithreading with LeanInteract"""
103
+ with timed_section("Multi-threading Example", summary_prefix="Multi-threading"):
104
+ results = []
105
+ with concurrent.futures.ThreadPoolExecutor(max_workers=min(4, len(tasks))) as executor:
106
+ future_to_task = {executor.submit(worker, config, task_id): task_id for task_id in tasks}
107
+ for future in concurrent.futures.as_completed(future_to_task):
108
+ results.append(future.result())
109
+ print_results(results)
110
+
111
+
112
+ def joblib_parallel_example(config: LeanREPLConfig, tasks: list[int]) -> None:
113
+ """Demonstrate joblib-based parallel execution with LeanInteract"""
114
+ with timed_section("Joblib Parallel Example", summary_prefix="Joblib parallel processing"):
115
+ results = Parallel(n_jobs=min(4, len(tasks)))(delayed(worker)(config, task_id) for task_id in tasks)
116
+ print_results(results)
117
+
118
+
119
+ if __name__ == "__main__":
120
+ config, tasks = setup()
121
+
122
+ multiprocessing_example(config, tasks)
123
+ joblib_parallel_example(config, tasks)
124
+ multithreading_example(config, tasks)
125
+ sequential_baseline(config, tasks)