lean-interact 0.9.2__tar.gz → 0.10.0__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.
- {lean_interact-0.9.2 → lean_interact-0.10.0}/PKG-INFO +2 -2
- {lean_interact-0.9.2 → lean_interact-0.10.0}/README.md +1 -1
- {lean_interact-0.9.2 → lean_interact-0.10.0}/docs/api/sessioncache.md +4 -0
- {lean_interact-0.9.2 → lean_interact-0.10.0}/docs/index.md +1 -1
- {lean_interact-0.9.2 → lean_interact-0.10.0}/docs/user-guide/examples.md +2 -2
- lean_interact-0.10.0/docs/user-guide/performance.md +224 -0
- {lean_interact-0.9.2 → lean_interact-0.10.0}/examples/beq_plus.py +24 -8
- lean_interact-0.10.0/examples/parallelization.py +125 -0
- {lean_interact-0.9.2 → lean_interact-0.10.0}/pyproject.toml +1 -1
- {lean_interact-0.9.2 → lean_interact-0.10.0}/src/lean_interact/__init__.py +3 -0
- {lean_interact-0.9.2 → lean_interact-0.10.0}/src/lean_interact/server.py +7 -7
- lean_interact-0.10.0/src/lean_interact/sessioncache.py +447 -0
- {lean_interact-0.9.2 → lean_interact-0.10.0}/src/lean_interact/utils.py +1 -1
- {lean_interact-0.9.2 → lean_interact-0.10.0}/tests/test_server.py +195 -17
- lean_interact-0.9.2/docs/user-guide/performance.md +0 -199
- lean_interact-0.9.2/examples/multi_processing.py +0 -60
- lean_interact-0.9.2/src/lean_interact/sessioncache.py +0 -235
- {lean_interact-0.9.2 → lean_interact-0.10.0}/.devcontainer/devcontainer.json +0 -0
- {lean_interact-0.9.2 → lean_interact-0.10.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {lean_interact-0.9.2 → lean_interact-0.10.0}/.github/copilot-instructions.md +0 -0
- {lean_interact-0.9.2 → lean_interact-0.10.0}/.github/workflows/ci.yml +0 -0
- {lean_interact-0.9.2 → lean_interact-0.10.0}/.github/workflows/docs.yml +0 -0
- {lean_interact-0.9.2 → lean_interact-0.10.0}/.github/workflows/publish-to-pypi.yml +0 -0
- {lean_interact-0.9.2 → lean_interact-0.10.0}/.gitignore +0 -0
- {lean_interact-0.9.2 → lean_interact-0.10.0}/LICENSE +0 -0
- {lean_interact-0.9.2 → lean_interact-0.10.0}/docs/.nojekyll +0 -0
- {lean_interact-0.9.2 → lean_interact-0.10.0}/docs/api/config.md +0 -0
- {lean_interact-0.9.2 → lean_interact-0.10.0}/docs/api/interface.md +0 -0
- {lean_interact-0.9.2 → lean_interact-0.10.0}/docs/api/project.md +0 -0
- {lean_interact-0.9.2 → lean_interact-0.10.0}/docs/api/server.md +0 -0
- {lean_interact-0.9.2 → lean_interact-0.10.0}/docs/api/utils.md +0 -0
- {lean_interact-0.9.2 → lean_interact-0.10.0}/docs/contributing.md +0 -0
- {lean_interact-0.9.2 → lean_interact-0.10.0}/docs/generate_changelog.py +0 -0
- {lean_interact-0.9.2 → lean_interact-0.10.0}/docs/theme/main.html +0 -0
- {lean_interact-0.9.2 → lean_interact-0.10.0}/docs/user-guide/basic-usage.md +0 -0
- {lean_interact-0.9.2 → lean_interact-0.10.0}/docs/user-guide/custom-lean-configuration.md +0 -0
- {lean_interact-0.9.2 → lean_interact-0.10.0}/docs/user-guide/data-extraction.md +0 -0
- {lean_interact-0.9.2 → lean_interact-0.10.0}/docs/user-guide/getting-started.md +0 -0
- {lean_interact-0.9.2 → lean_interact-0.10.0}/docs/user-guide/installation.md +0 -0
- {lean_interact-0.9.2 → lean_interact-0.10.0}/docs/user-guide/set-options.md +0 -0
- {lean_interact-0.9.2 → lean_interact-0.10.0}/docs/user-guide/tactic-mode.md +0 -0
- {lean_interact-0.9.2 → lean_interact-0.10.0}/docs/user-guide/troubleshooting.md +0 -0
- {lean_interact-0.9.2 → lean_interact-0.10.0}/examples/extract_mathlib_decls.py +0 -0
- {lean_interact-0.9.2 → lean_interact-0.10.0}/examples/proof_generation_and_autoformalization.py +0 -0
- {lean_interact-0.9.2 → lean_interact-0.10.0}/examples/type_check.py +0 -0
- {lean_interact-0.9.2 → lean_interact-0.10.0}/mkdocs.yml +0 -0
- {lean_interact-0.9.2 → lean_interact-0.10.0}/src/lean_interact/config.py +0 -0
- {lean_interact-0.9.2 → lean_interact-0.10.0}/src/lean_interact/interface.py +0 -0
- {lean_interact-0.9.2 → lean_interact-0.10.0}/src/lean_interact/project.py +0 -0
- {lean_interact-0.9.2 → lean_interact-0.10.0}/src/lean_interact/py.typed +0 -0
- {lean_interact-0.9.2 → lean_interact-0.10.0}/tests/test_concurrency.py +0 -0
- {lean_interact-0.9.2 → lean_interact-0.10.0}/tests/test_git_functionality.py +0 -0
- {lean_interact-0.9.2 → lean_interact-0.10.0}/tests/test_utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: lean-interact
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.10.0
|
|
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.
|
|
56
|
+
- **🔧 Compatibility**: Supports all Lean versions between `v4.8.0-rc1` and `v4.25.1`.
|
|
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.
|
|
18
|
+
- **🔧 Compatibility**: Supports all Lean versions between `v4.8.0-rc1` and `v4.25.1`.
|
|
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.
|
|
20
|
+
- **🔧 Compatibility**: Supports all Lean versions between `v4.8.0-rc1` and `v4.25.1`
|
|
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. [**
|
|
77
|
-
Shows how to
|
|
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
|
-
@
|
|
16
|
-
title=
|
|
17
|
-
author=
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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
|
"""
|
|
@@ -250,6 +256,12 @@ def beq_plus(
|
|
|
250
256
|
console.print(Syntax(proof_exact, "lean4"))
|
|
251
257
|
continue
|
|
252
258
|
|
|
259
|
+
# If trivially provable by assumption, we skip
|
|
260
|
+
if check_proof_sub(server, formal_code, context_env, formal_2_start_line, "assumption", timeout_per_proof):
|
|
261
|
+
if verbose:
|
|
262
|
+
console.print("Skipping as provable by assumption")
|
|
263
|
+
continue
|
|
264
|
+
|
|
253
265
|
# 2. try to apply the base theorem directly
|
|
254
266
|
proof_apply = check_proof_sub(
|
|
255
267
|
server,
|
|
@@ -340,6 +352,10 @@ open Set Real Ideal Polynomial
|
|
|
340
352
|
open scoped BigOperators"""
|
|
341
353
|
|
|
342
354
|
formalization_pairs = [
|
|
355
|
+
( # negative example -- not semantically equivalent
|
|
356
|
+
"theorem prediction (a b : ℤ) (ha : a ∣ b) : a ∣ (b : ℤ) :=",
|
|
357
|
+
"theorem ground_truth (a b : ℤ) : (Zsqrtd.ofInt a : GaussianInt) ∣ Zsqrtd.ofInt b → a ∣ b :=",
|
|
358
|
+
),
|
|
343
359
|
(
|
|
344
360
|
"theorem random_name_1 {G : Type*} [Group G] [Fintype G] (h : Fintype.card G % 2 = 0) :\n ∃ a : G, a ≠ 1 ∧ a = a⁻¹ :=",
|
|
345
361
|
"theorem random_name_2 {G : Type*} [Group G] [Fintype G] (hG2 : Even (card G)) :\n ∃ (a : G), a ≠ 1 ∧ a = a⁻¹ :=",
|
|
@@ -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)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "lean-interact"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.10.0"
|
|
4
4
|
description = "LeanInteract is a Python package that allows you to interact with the Lean theorem prover."
|
|
5
5
|
keywords = ["Lean", "theorem proving", "autoformalization", "REPL"]
|
|
6
6
|
license = { file = "LICENSE" }
|
|
@@ -16,11 +16,14 @@ from lean_interact.project import (
|
|
|
16
16
|
TempRequireProject,
|
|
17
17
|
)
|
|
18
18
|
from lean_interact.server import AutoLeanServer, LeanServer
|
|
19
|
+
from lean_interact.sessioncache import PickleSessionCache, ReplaySessionCache
|
|
19
20
|
|
|
20
21
|
__all__ = [
|
|
21
22
|
"LeanREPLConfig",
|
|
22
23
|
"LeanServer",
|
|
23
24
|
"AutoLeanServer",
|
|
25
|
+
"PickleSessionCache",
|
|
26
|
+
"ReplaySessionCache",
|
|
24
27
|
"LeanRequire",
|
|
25
28
|
"GitProject",
|
|
26
29
|
"LocalProject",
|
|
@@ -33,7 +33,7 @@ from lean_interact.interface import (
|
|
|
33
33
|
UnpickleEnvironment,
|
|
34
34
|
UnpickleProofState,
|
|
35
35
|
)
|
|
36
|
-
from lean_interact.sessioncache import BaseSessionCache,
|
|
36
|
+
from lean_interact.sessioncache import BaseSessionCache, ReplaySessionCache
|
|
37
37
|
from lean_interact.utils import _limit_memory, get_total_memory_usage, logger
|
|
38
38
|
|
|
39
39
|
DEFAULT_TIMEOUT: int | None = None
|
|
@@ -287,7 +287,7 @@ class LeanServer:
|
|
|
287
287
|
raise ConnectionAbortedError(
|
|
288
288
|
f"The Lean server closed unexpectedly."
|
|
289
289
|
f"\n{'-' * 50}\nstdout:\n{stdout}\n{'-' * 50}\nstderr:\n{stderr}\n{'-' * 50}"
|
|
290
|
-
f"
|
|
290
|
+
f"\nIf stdout and stderr are empty or obscure, here is a list of possible reasons (not exhaustive):\n"
|
|
291
291
|
"- Not enough memory and/or compute available\n"
|
|
292
292
|
"- The cached Lean REPL is corrupted. In this case, clear the cache using the `clear-lean-cache` command.\n"
|
|
293
293
|
"- An uncaught exception in the Lean REPL"
|
|
@@ -438,11 +438,11 @@ class AutoLeanServer(LeanServer):
|
|
|
438
438
|
max_total_memory: The maximum proportion of system-wide memory usage (across all processes) before triggering a Lean server restart. This is a soft limit ranging from 0.0 to 1.0, with default 0.8 (80%). When system memory exceeds this threshold, the server restarts to free memory. Particularly useful in multiprocessing environments to prevent simultaneous crashes.
|
|
439
439
|
max_process_memory: The maximum proportion of the memory hard limit (set in `LeanREPLConfig.memory_hard_limit_mb`) that the Lean server process can use before restarting. This soft limit ranges from 0.0 to 1.0, with default 0.8 (80%). Only applied if a hard limit is configured in `LeanREPLConfig`.
|
|
440
440
|
max_restart_attempts: The maximum number of consecutive restart attempts allowed before raising a `MemoryError` exception. Default is 5. The server uses exponential backoff between restart attempts.
|
|
441
|
-
session_cache:
|
|
441
|
+
session_cache: Optional session cache implementation. Defaults to `ReplaySessionCache()`.
|
|
442
|
+
Use `PickleSessionCache` if you explicitly need on-disk artifacts.
|
|
442
443
|
"""
|
|
443
|
-
session_cache
|
|
444
|
-
session_cache
|
|
445
|
-
)
|
|
444
|
+
if session_cache is None:
|
|
445
|
+
session_cache = ReplaySessionCache()
|
|
446
446
|
self._session_cache: BaseSessionCache = session_cache
|
|
447
447
|
self._max_total_memory = max_total_memory
|
|
448
448
|
self._max_process_memory = max_process_memory
|
|
@@ -453,7 +453,7 @@ class AutoLeanServer(LeanServer):
|
|
|
453
453
|
if state_id is None:
|
|
454
454
|
return None
|
|
455
455
|
if state_id in self._session_cache:
|
|
456
|
-
return self._session_cache
|
|
456
|
+
return self._session_cache.get_repl_id(state_id, self)
|
|
457
457
|
return state_id
|
|
458
458
|
|
|
459
459
|
def restart(self, verbose: bool = False) -> None:
|