Skip to content

Commit 295cfbd

Browse files
Improve new user install and scheduling docs
1 parent 590864a commit 295cfbd

8 files changed

Lines changed: 149 additions & 42 deletions

File tree

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

README.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,27 @@ Please see the [documentation](https://egglog-python.readthedocs.io/) for more i
99

1010
Come say hello [on the e-graphs Zulip](https://egraphs.zulipchat.com/#narrow/stream/375765-egglog/) or [open an issue](https://github.com/egraphs-good/egglog-python/issues/new/choose)!
1111

12+
## Install
13+
14+
With pip:
15+
16+
```shell
17+
python -m pip install egglog
18+
```
19+
20+
With uv in a project:
21+
22+
```shell
23+
uv add egglog
24+
```
25+
26+
For array examples, install the optional array dependencies:
27+
28+
```shell
29+
python -m pip install "egglog[array]"
30+
uv add "egglog[array]"
31+
```
32+
1233
## How to cite
1334

1435
If you use **egglog-python** in academic work, please cite the paper:

docs/environment.yml

Lines changed: 0 additions & 12 deletions
This file was deleted.

docs/reference/egglog-translation.md

Lines changed: 44 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -395,12 +395,38 @@ After a run, you get a run report, with some timing information as well as wheth
395395

396396
### Schedules
397397

398-
The `egraph.run` function also takes a `schedule` argument, which corresponds to the `(run-schedule ...)` command in egglog. A schedule can be either:
398+
`EGraph.run` can run either a bounded number of iterations or a full schedule.
399+
The bounded form:
399400

400-
- A run configuration, created with `run(limit=..., ruleset=..., *until)`, corresponding to `(run ...)` in egglog
401-
- Saturating an existing schedule, by calling the `schedule.saturate()` method, corresponding to `(saturate ...)` in egglog
402-
- A sequence of sequences run one after the other, created with `seq(*schedules)`, corresponding to `(seq ...)` in egglog
403-
- Repeating a schedule some number of times, created with `schedule * n`, corresponding to `(repeat ...)` in egglog
401+
```python
402+
egraph.run(5)
403+
```
404+
405+
is shorthand for running the default ruleset five times. You can also pass a
406+
ruleset and optional stop facts:
407+
408+
```python
409+
egraph.run(10, ruleset=path_ruleset)
410+
egraph.run(10000, fib(7))
411+
```
412+
413+
For more control, pass a `Schedule` object. Schedules correspond to egglog's
414+
`(run-schedule ...)` command and are composed from these Python forms:
415+
416+
| Python | egglog | Meaning |
417+
| --- | --- | --- |
418+
| `run()` | `(run)` | Run the default ruleset once. |
419+
| `run(ruleset)` | `(run ruleset)` | Run one named ruleset once. |
420+
| `run(ruleset, fact)` | `(run ruleset :until fact)` | Run until the fact is reached. |
421+
| `schedule.saturate()` | `(saturate schedule)` | Repeat until the schedule stops changing the e-graph. |
422+
| `schedule * n` | `(repeat n schedule)` | Repeat a schedule exactly `n` times. |
423+
| `left + right` | `(seq left right)` | Run two schedules in order. |
424+
| `seq(a, b, c)` | `(seq a b c)` | Run any number of schedules in order. |
425+
426+
Rulesets are schedules, so `egraph.run(path_ruleset)` runs `path_ruleset` once.
427+
For readability, prefer `run(path_ruleset)` when you are composing a larger
428+
schedule and `egraph.run(10, ruleset=path_ruleset)` when all you need is a
429+
bounded run.
404430

405431
We can show an example of this by translating the `schedule-demo.egg` to Python:
406432

@@ -470,15 +496,19 @@ step_egraph.check(left(i64(10)), right(i64(9)))
470496
step_egraph.check_fail(left(i64(11)), right(i64(10)))
471497
```
472498

473-
#### Custom Schedulers
499+
#### Backoff Scheduler
474500

475-
Custom backoff scheduler from egglog-experimental is supported. Create a custom backoff scheduler with `bo = BackOff(match_limit: None | int=None, ban_length: None | int=None)`, then run using `run(ruleset, *facts, scheduler=bo)`:
501+
The custom backoff scheduler can delay rules that produce too many matches in a
502+
single scheduler iteration. Create one with
503+
`bo = back_off(match_limit=None, ban_length=None)`, then pass it to
504+
`run(ruleset, *facts, scheduler=bo)`.
476505

477506
- `match_limit`: per-rule threshold of matches allowed in a single scheduler iteration. If a rule produces more matches than the threshold, that rule is temporarily banned.
478507
- `ban_length`: initial ban duration (in scheduler iterations). While banned, that rule is skipped.
479-
- Exponential backoff: each time a rule is banned, both the threshold and ban length double for that rule (threshold = match_limit << times_banned; ban = ban_length << times_banned).
508+
- Exponential backoff: each time a rule is banned, both the threshold and ban length double for that rule. After `times_banned` bans, the effective threshold is `match_limit << times_banned` and the ban duration is `ban_length << times_banned`.
480509
- Fast-forwarding: when any rule is banned, the scheduler fast-forwards by the minimum remaining ban to unban at least one rule before checking for termination again.
481510
- Defaults: match_limit defaults to 1000; ban_length defaults to 5.
511+
- `:until` support: custom scheduler runs can use at most one non-equality fact as the stop condition. Equality stop facts and multiple stop facts raise `ValueError`.
482512

483513
For example, this egglog code:
484514

@@ -497,7 +527,7 @@ step_egraph.run(
497527
```
498528

499529
By default the scheduler will be created before any other schedules are run.
500-
To control where is instantiated explicitly, use `bo.scope(<schedule>)`, where it will be created before everything in `<schedule>`.
530+
To control where it is instantiated explicitly, use `bo.scope(<schedule>)`, where it will be created before everything in `<schedule>`.
501531

502532
So the previous is equivalent to:
503533

@@ -526,6 +556,11 @@ This would be equivalent to this egglog:
526556
(run-with bo step_right)))
527557
```
528558

559+
That distinction matters because a scheduler carries state. Hoisting one
560+
scheduler outside `* 10` lets its `times_banned` counters accumulate across all
561+
ten runs. Placing `bo.scope(...)` inside `* 10` creates a fresh scheduler each
562+
time, so every iteration starts with the initial `match_limit` and `ban_length`.
563+
529564
## Check
530565

531566
The `(check ...)` command to verify that some facts are true, can be translated to Python with the `egraph.check` function:

docs/reference/usage.md

Lines changed: 39 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,55 @@
22

33
## Installation
44

5-
You can install this package with `pip`:
5+
`egglog` supports Python 3.11 and newer. The examples below create an isolated
6+
environment first so that new installs do not depend on packages already present
7+
on your machine.
8+
9+
With `pip`:
10+
11+
```shell
12+
python3.13 -m venv .venv # or any supported Python 3.11+
13+
source .venv/bin/activate
14+
python -m pip install --upgrade pip
15+
python -m pip install egglog
16+
python -c "from egglog import EGraph; EGraph(); print('egglog ok')"
17+
```
18+
19+
With `uv` in a project:
20+
21+
```shell
22+
uv init egglog-demo
23+
cd egglog-demo
24+
uv add egglog
25+
uv run python -c "from egglog import EGraph; EGraph(); print('egglog ok')"
26+
```
27+
28+
With `uv` in a standalone virtual environment:
29+
30+
```shell
31+
uv venv --python 3.13 .venv # or any supported Python 3.11+
32+
uv pip install --python .venv/bin/python egglog
33+
.venv/bin/python -c "from egglog import EGraph; EGraph(); print('egglog ok')"
34+
```
35+
36+
If you already have an active environment, the install command is simply:
637

738
```shell
8-
pip install egglog
39+
python -m pip install egglog
940
```
1041

11-
To be able to run the array demos:
42+
To run the array demos, install the optional array dependencies:
1243

1344
```shell
14-
pip install egglog[array]
45+
python -m pip install "egglog[array]"
46+
uv add "egglog[array]"
1547
```
1648

17-
To see interactive widgets:
49+
From a source checkout for development, use the repo's uv workflow:
1850

1951
```shell
20-
pip install anywidget
52+
uv sync --all-extras
53+
uv run python -c "from egglog import EGraph; EGraph(); print('egglog ok')"
2154
```
2255

2356
It follows [SPEC 0](https://scientific-python.org/specs/spec-0000/) in terms of what Python versions are supported.

docs/tutorials/getting-started.ipynb

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -18,30 +18,47 @@
1818
"\n",
1919
"## Install egglog Python\n",
2020
"\n",
21-
"First, you will need to have a working Python interpreter. In this tutorial, we will [use `miniconda`](https://docs.conda.io/en/latest/miniconda.html) to create a new Python environment and activate it:\n",
21+
"First, you will need a working Python 3.11 or newer interpreter. The safest setup is to create a new environment before installing `egglog`.\n",
22+
"\n",
23+
"With `pip`:\n",
2224
"\n",
2325
"```bash\n",
24-
"$ brew install miniconda\n",
25-
"$ conda create -n egglog-python python=3.11\n",
26-
"$ conda activate egglog-python\n",
26+
"$ python3.13 -m venv .venv # or any supported Python 3.11+\n",
27+
"$ source .venv/bin/activate\n",
28+
"$ python -m pip install --upgrade pip\n",
29+
"$ python -m pip install egglog\n",
2730
"```\n",
2831
"\n",
29-
"Then we want to install `egglog` Python. `egglog` Python can run on any recent Python version, and is tested on 3.8 - 3.11. To install it, run:\n",
32+
"With `uv`:\n",
3033
"\n",
3134
"```bash\n",
32-
"$ pip install egglog\n",
35+
"$ uv init egglog-python-start\n",
36+
"$ cd egglog-python-start\n",
37+
"$ uv add egglog\n",
3338
"```\n",
3439
"\n",
3540
"To test you have installed it correctly, run:\n",
3641
"\n",
3742
"```bash\n",
38-
"$ python -m 'import egglog'\n",
43+
"$ python -c \"from egglog import EGraph; EGraph(); print('egglog ok')\"\n",
44+
"```\n",
45+
"\n",
46+
"If you used `uv`, run the same check through uv:\n",
47+
"\n",
48+
"```bash\n",
49+
"$ uv run python -c \"from egglog import EGraph; EGraph(); print('egglog ok')\"\n",
50+
"```\n",
51+
"\n",
52+
"We also want to install `mypy` for static type checking. This is not required, but it will help us write correct representations. With pip, run:\n",
53+
"\n",
54+
"```bash\n",
55+
"$ python -m pip install mypy\n",
3956
"```\n",
4057
"\n",
41-
"We also want to install `mypy` for static type checking. This is not required, but it will help us write correct representations. To install it, run:\n",
58+
"With uv, run:\n",
4259
"\n",
4360
"```bash\n",
44-
"$ pip install mypy\n",
61+
"$ uv add --dev mypy\n",
4562
"```\n",
4663
"\n",
4764
"## Creating an E-Graph\n",
@@ -1246,4 +1263,4 @@
12461263
},
12471264
"nbformat": 4,
12481265
"nbformat_minor": 5
1249-
}
1266+
}

docs/tutorials/tut_4_scheduling.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,7 @@ def _(x: Num) -> Iterable[RewriteOrRule]:
228228
egraph.function_size(Num.__mul__)
229229

230230

231-
# Note that any scheudler which doesn't have an explicit scope is bound to the outer loop like:
231+
# Note that any scheduler which doesn't have an explicit scope is bound to the outer loop like:
232232
#
233233
# ```python
234234
# bo = back_off()

python/egglog/egraph.py

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1560,7 +1560,11 @@ def ruleset(
15601560
@dataclass
15611561
class Schedule(DelayedDeclarations):
15621562
"""
1563-
A composition of some rulesets, either composing them sequentially, running them repeatedly, running them till saturation, or running until some facts are met
1563+
A composable e-graph schedule.
1564+
1565+
Use ``left + right`` to run schedules in sequence, ``schedule * n`` to
1566+
repeat a schedule a fixed number of times, and ``schedule.saturate()`` to
1567+
repeat until the schedule stops changing the e-graph.
15641568
"""
15651569

15661570
# Defer declerations so that we can have rule generators that used not yet defined yet
@@ -2065,7 +2069,11 @@ def to_runtime_expr(expr: BaseExpr) -> RuntimeExpr:
20652069

20662070
def run(ruleset: Ruleset | None = None, *until: FactLike, scheduler: BackOff | None = None) -> Schedule:
20672071
"""
2068-
Create a run configuration.
2072+
Create a one-step run schedule.
2073+
2074+
``run()`` runs the default ruleset once. ``run(ruleset)`` runs one named
2075+
ruleset once. Additional facts become ``:until`` stop conditions. A custom
2076+
``scheduler`` currently supports at most one non-equality stop fact.
20692077
"""
20702078
facts = _fact_likes(until)
20712079
return Schedule(
@@ -2086,6 +2094,9 @@ def back_off(match_limit: None | int = None, ban_length: None | int = None) -> B
20862094
schedule = run(analysis_ruleset).saturate() + run(ruleset, scheduler=back_off(match_limit=1000, ban_length=5)) * 10
20872095
```
20882096
This will run the `analysis_ruleset` until saturation, then run `ruleset` 10 times, using a backoff scheduler.
2097+
2098+
The backend defaults are ``match_limit=1000`` and ``ban_length=5``. Each time
2099+
a rule is banned, both effective values double for that rule.
20892100
"""
20902101
return BackOff(BackOffDecl(id=uuid4(), match_limit=match_limit, ban_length=ban_length))
20912102

@@ -2110,7 +2121,9 @@ def __repr__(self) -> str:
21102121

21112122
def seq(*schedules: Schedule) -> Schedule:
21122123
"""
2113-
Run a sequence of schedules.
2124+
Run any number of schedules in order.
2125+
2126+
For two schedules, ``left + right`` is equivalent to ``seq(left, right)``.
21142127
"""
21152128
return Schedule(partial(Declarations.create, *schedules), SequenceDecl(tuple(s.schedule for s in schedules)))
21162129

0 commit comments

Comments
 (0)