recursionlens 0.1.2__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.
- recursionlens-0.1.2/LICENSE +21 -0
- recursionlens-0.1.2/PKG-INFO +172 -0
- recursionlens-0.1.2/README.md +146 -0
- recursionlens-0.1.2/pyproject.toml +45 -0
- recursionlens-0.1.2/setup.cfg +4 -0
- recursionlens-0.1.2/src/recursionlens/__init__.py +16 -0
- recursionlens-0.1.2/src/recursionlens/_model.py +93 -0
- recursionlens-0.1.2/src/recursionlens/_render.py +365 -0
- recursionlens-0.1.2/src/recursionlens/_report.py +193 -0
- recursionlens-0.1.2/src/recursionlens/_trace.py +283 -0
- recursionlens-0.1.2/src/recursionlens.egg-info/PKG-INFO +172 -0
- recursionlens-0.1.2/src/recursionlens.egg-info/SOURCES.txt +17 -0
- recursionlens-0.1.2/src/recursionlens.egg-info/dependency_links.txt +1 -0
- recursionlens-0.1.2/src/recursionlens.egg-info/requires.txt +4 -0
- recursionlens-0.1.2/src/recursionlens.egg-info/top_level.txt +1 -0
- recursionlens-0.1.2/tests/test_regressions.py +136 -0
- recursionlens-0.1.2/tests/test_render.py +109 -0
- recursionlens-0.1.2/tests/test_smoke.py +5 -0
- recursionlens-0.1.2/tests/test_trace.py +130 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Ziyu Jiang and contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: recursionlens
|
|
3
|
+
Version: 0.1.2
|
|
4
|
+
Summary: Interactive recursion tracing, subproblem DAGs, and memoization comparisons for JupyterLab.
|
|
5
|
+
Author: Ziyu Jiang, Xinyue Cai, Kehan Cao, Ruoxi Lyu, Haobo Ma
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/ZiyuJiang07/recursionlens
|
|
8
|
+
Project-URL: Repository, https://github.com/ZiyuJiang07/recursionlens
|
|
9
|
+
Project-URL: Documentation, https://github.com/ZiyuJiang07/recursionlens/blob/main/docs/USER.md
|
|
10
|
+
Project-URL: Tutorial, https://github.com/ZiyuJiang07/recursionlens/blob/main/docs/RecursionLens_Tutorial.ipynb
|
|
11
|
+
Project-URL: Changelog, https://github.com/ZiyuJiang07/recursionlens/blob/main/CHANGELOG.md
|
|
12
|
+
Project-URL: Issues, https://github.com/ZiyuJiang07/recursionlens/issues
|
|
13
|
+
Keywords: recursion,jupyterlab,visualization,memoization,education
|
|
14
|
+
Classifier: Development Status :: 3 - Alpha
|
|
15
|
+
Classifier: Intended Audience :: Education
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
18
|
+
Classifier: Framework :: Jupyter
|
|
19
|
+
Requires-Python: >=3.9
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
License-File: LICENSE
|
|
22
|
+
Provides-Extra: dev
|
|
23
|
+
Requires-Dist: pytest>=8; extra == "dev"
|
|
24
|
+
Requires-Dist: coverage>=7; extra == "dev"
|
|
25
|
+
Dynamic: license-file
|
|
26
|
+
|
|
27
|
+
# RecursionLens
|
|
28
|
+
|
|
29
|
+
**See not just what recursion returns, but where the repeated work happens.**
|
|
30
|
+
|
|
31
|
+
RecursionLens is a zero-runtime-dependency Python package for exploring recursive execution in JupyterLab. It turns a traced run into an expandable call tree, a compressed subproblem DAG, and a quantitative naive-versus-memoized comparison.
|
|
32
|
+
|
|
33
|
+
> **JupyterLab tutorial, already executed:** [Open RecursionLens_Tutorial.ipynb](https://github.com/ZiyuJiang07/recursionlens/blob/main/docs/RecursionLens_Tutorial.ipynb). All eight code cells have saved output. If Jupyter hides HTML/SVG from a downloaded notebook, use its **Trust** action or click **Run All Cells** once. For a zero-setup preview, download and open the [static HTML companion](https://github.com/ZiyuJiang07/recursionlens/raw/main/docs/RecursionLens_Tutorial.html).
|
|
34
|
+
|
|
35
|
+
## Project team
|
|
36
|
+
|
|
37
|
+
CS1302A Group 50:
|
|
38
|
+
|
|
39
|
+
- Ziyu Jiang
|
|
40
|
+
- Xinyue CAI
|
|
41
|
+
- Kehan CAO
|
|
42
|
+
- Ruoxi LYU
|
|
43
|
+
- Haobo MA
|
|
44
|
+
|
|
45
|
+
## Why RecursionLens?
|
|
46
|
+
|
|
47
|
+
A recursive function normally exposes only its final return value or traceback. Students still have to reconstruct the execution in their heads. RecursionLens answers three different questions:
|
|
48
|
+
|
|
49
|
+
1. **Call tree:** what actually happened, invocation by invocation?
|
|
50
|
+
2. **Subproblem DAG:** how many distinct problems and dependencies exist after identical arguments are merged?
|
|
51
|
+
3. **Execution comparison:** what changes when memoization is enabled?
|
|
52
|
+
|
|
53
|
+
For `fib(5)`, that story is **15 observed calls → 6 unique subproblems → 6 executed bodies with memoization**.
|
|
54
|
+
|
|
55
|
+
## Innovation beyond a call graph
|
|
56
|
+
|
|
57
|
+
The existing `rcviz` package documents decorator-based recursion trees with arguments, return values, and Jupyter rendering. RecursionLens deliberately focuses on a different workflow:
|
|
58
|
+
|
|
59
|
+
- transform one trace between an invocation tree and a compressed subproblem DAG;
|
|
60
|
+
- show the complete call tree at once in an SVG view or keep a collapsible tree for larger runs;
|
|
61
|
+
- quantify repeated calls, executed bodies, cache hits, and maximum depth;
|
|
62
|
+
- compare naive and memoized runs in one report with side-by-side DAGs;
|
|
63
|
+
- render self-contained HTML/SVG in JupyterLab without Graphviz, native libraries, external JavaScript, or a CDN;
|
|
64
|
+
- export versioned JSON for reproducible analysis and testing.
|
|
65
|
+
|
|
66
|
+
## Installation
|
|
67
|
+
|
|
68
|
+
RecursionLens requires Python 3.9 or later.
|
|
69
|
+
|
|
70
|
+
Install from PyPI:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
python -m pip install recursionlens
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
For local development:
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
git clone https://github.com/ZiyuJiang07/recursionlens.git
|
|
80
|
+
cd recursionlens
|
|
81
|
+
python -m pip install -e ".[dev]"
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
The package has no runtime dependencies.
|
|
85
|
+
|
|
86
|
+
## Quickstart in JupyterLab
|
|
87
|
+
|
|
88
|
+
```python
|
|
89
|
+
from recursionlens import trace
|
|
90
|
+
|
|
91
|
+
@trace
|
|
92
|
+
def fib(n):
|
|
93
|
+
if n < 2:
|
|
94
|
+
return n
|
|
95
|
+
return fib(n - 1) + fib(n - 2)
|
|
96
|
+
|
|
97
|
+
naive = fib.run(5)
|
|
98
|
+
naive # expandable call tree in the notebook output area
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Switch the same trace to the compressed subproblem DAG:
|
|
102
|
+
|
|
103
|
+
```python
|
|
104
|
+
naive.show(mode="dag")
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Or show every call at once as one compact SVG tree, with no nodes collapsed:
|
|
108
|
+
|
|
109
|
+
```python
|
|
110
|
+
naive.show(mode="expanded")
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Run the function again with memoization and compare the evidence:
|
|
114
|
+
|
|
115
|
+
```python
|
|
116
|
+
memoized = fib.with_options(memoize=True).run(5)
|
|
117
|
+
comparison = naive.compare(memoized)
|
|
118
|
+
comparison
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
The comparison confirms the returned values match and reports the measured changes, including **15 → 6 executed function bodies**.
|
|
122
|
+
|
|
123
|
+
Normal function behavior is preserved:
|
|
124
|
+
|
|
125
|
+
```python
|
|
126
|
+
assert fib(5) == 5
|
|
127
|
+
assert fib.last_report.total_calls == 15
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## Export a report
|
|
131
|
+
|
|
132
|
+
```python
|
|
133
|
+
naive.export_html("fib-tree.html")
|
|
134
|
+
naive.export_html("fib-dag.html", mode="dag")
|
|
135
|
+
naive.export_json("fib-trace.json")
|
|
136
|
+
comparison.export_html("fib-comparison.html")
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
HTML reports are standalone files that can be opened without a notebook server. JSON contains the tree, DAG nodes and edges, metrics, warnings, and schema version.
|
|
140
|
+
|
|
141
|
+
## Supported scope
|
|
142
|
+
|
|
143
|
+
Version 0.1 targets synchronous, deterministic teaching examples such as factorial, Fibonacci, and pure mathematical recursion.
|
|
144
|
+
|
|
145
|
+
- Memoization requires hashable bound arguments; unhashable arguments produce a warning instead of a crash.
|
|
146
|
+
- Identical arguments do not necessarily imply redundant work for functions with side effects or external state.
|
|
147
|
+
- `max_calls` bounds the stored tree without changing the function's result. A truncated report explicitly marks partial tree/DAG metrics.
|
|
148
|
+
- RecursionLens executes your function; it is **not a sandbox for untrusted code**.
|
|
149
|
+
- Async functions, generators, automatic Big-O inference, and automatic code rewriting are non-goals for 0.1.
|
|
150
|
+
|
|
151
|
+
## Documentation and demo
|
|
152
|
+
|
|
153
|
+
- [Executed JupyterLab tutorial](https://github.com/ZiyuJiang07/recursionlens/blob/main/docs/RecursionLens_Tutorial.ipynb) - setup, three views, comparison, export, troubleshooting, and a one-minute demo recipe
|
|
154
|
+
- [Static HTML tutorial preview](https://github.com/ZiyuJiang07/recursionlens/raw/main/docs/RecursionLens_Tutorial.html) - the same tutorial with all visual reports rendered, no Jupyter required
|
|
155
|
+
- [Runnable example notebook](https://github.com/ZiyuJiang07/recursionlens/blob/main/examples/demo.ipynb)
|
|
156
|
+
- [User guide](https://github.com/ZiyuJiang07/recursionlens/blob/main/docs/USER.md)
|
|
157
|
+
- [Developer guide](https://github.com/ZiyuJiang07/recursionlens/blob/main/docs/DEVELOPER.md)
|
|
158
|
+
- [Changelog](https://github.com/ZiyuJiang07/recursionlens/blob/main/CHANGELOG.md)
|
|
159
|
+
|
|
160
|
+
## Development and testing
|
|
161
|
+
|
|
162
|
+
```bash
|
|
163
|
+
python -m pytest tests -v
|
|
164
|
+
python -m build
|
|
165
|
+
python -m twine check dist/*
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
The test suite covers trace invariants, cache behavior, signatures, exceptions, truncation, JSON, HTML escaping, DAG rendering, and comparison reports. Contributions should keep the public API, tests, and user documentation synchronized.
|
|
169
|
+
|
|
170
|
+
## License
|
|
171
|
+
|
|
172
|
+
MIT © 2026 Ziyu Jiang, Xinyue Cai, Kehan Cao, Ruoxi Lyu, Haobo Ma.
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
# RecursionLens
|
|
2
|
+
|
|
3
|
+
**See not just what recursion returns, but where the repeated work happens.**
|
|
4
|
+
|
|
5
|
+
RecursionLens is a zero-runtime-dependency Python package for exploring recursive execution in JupyterLab. It turns a traced run into an expandable call tree, a compressed subproblem DAG, and a quantitative naive-versus-memoized comparison.
|
|
6
|
+
|
|
7
|
+
> **JupyterLab tutorial, already executed:** [Open RecursionLens_Tutorial.ipynb](https://github.com/ZiyuJiang07/recursionlens/blob/main/docs/RecursionLens_Tutorial.ipynb). All eight code cells have saved output. If Jupyter hides HTML/SVG from a downloaded notebook, use its **Trust** action or click **Run All Cells** once. For a zero-setup preview, download and open the [static HTML companion](https://github.com/ZiyuJiang07/recursionlens/raw/main/docs/RecursionLens_Tutorial.html).
|
|
8
|
+
|
|
9
|
+
## Project team
|
|
10
|
+
|
|
11
|
+
CS1302A Group 50:
|
|
12
|
+
|
|
13
|
+
- Ziyu Jiang
|
|
14
|
+
- Xinyue CAI
|
|
15
|
+
- Kehan CAO
|
|
16
|
+
- Ruoxi LYU
|
|
17
|
+
- Haobo MA
|
|
18
|
+
|
|
19
|
+
## Why RecursionLens?
|
|
20
|
+
|
|
21
|
+
A recursive function normally exposes only its final return value or traceback. Students still have to reconstruct the execution in their heads. RecursionLens answers three different questions:
|
|
22
|
+
|
|
23
|
+
1. **Call tree:** what actually happened, invocation by invocation?
|
|
24
|
+
2. **Subproblem DAG:** how many distinct problems and dependencies exist after identical arguments are merged?
|
|
25
|
+
3. **Execution comparison:** what changes when memoization is enabled?
|
|
26
|
+
|
|
27
|
+
For `fib(5)`, that story is **15 observed calls → 6 unique subproblems → 6 executed bodies with memoization**.
|
|
28
|
+
|
|
29
|
+
## Innovation beyond a call graph
|
|
30
|
+
|
|
31
|
+
The existing `rcviz` package documents decorator-based recursion trees with arguments, return values, and Jupyter rendering. RecursionLens deliberately focuses on a different workflow:
|
|
32
|
+
|
|
33
|
+
- transform one trace between an invocation tree and a compressed subproblem DAG;
|
|
34
|
+
- show the complete call tree at once in an SVG view or keep a collapsible tree for larger runs;
|
|
35
|
+
- quantify repeated calls, executed bodies, cache hits, and maximum depth;
|
|
36
|
+
- compare naive and memoized runs in one report with side-by-side DAGs;
|
|
37
|
+
- render self-contained HTML/SVG in JupyterLab without Graphviz, native libraries, external JavaScript, or a CDN;
|
|
38
|
+
- export versioned JSON for reproducible analysis and testing.
|
|
39
|
+
|
|
40
|
+
## Installation
|
|
41
|
+
|
|
42
|
+
RecursionLens requires Python 3.9 or later.
|
|
43
|
+
|
|
44
|
+
Install from PyPI:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
python -m pip install recursionlens
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
For local development:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
git clone https://github.com/ZiyuJiang07/recursionlens.git
|
|
54
|
+
cd recursionlens
|
|
55
|
+
python -m pip install -e ".[dev]"
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
The package has no runtime dependencies.
|
|
59
|
+
|
|
60
|
+
## Quickstart in JupyterLab
|
|
61
|
+
|
|
62
|
+
```python
|
|
63
|
+
from recursionlens import trace
|
|
64
|
+
|
|
65
|
+
@trace
|
|
66
|
+
def fib(n):
|
|
67
|
+
if n < 2:
|
|
68
|
+
return n
|
|
69
|
+
return fib(n - 1) + fib(n - 2)
|
|
70
|
+
|
|
71
|
+
naive = fib.run(5)
|
|
72
|
+
naive # expandable call tree in the notebook output area
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Switch the same trace to the compressed subproblem DAG:
|
|
76
|
+
|
|
77
|
+
```python
|
|
78
|
+
naive.show(mode="dag")
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Or show every call at once as one compact SVG tree, with no nodes collapsed:
|
|
82
|
+
|
|
83
|
+
```python
|
|
84
|
+
naive.show(mode="expanded")
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Run the function again with memoization and compare the evidence:
|
|
88
|
+
|
|
89
|
+
```python
|
|
90
|
+
memoized = fib.with_options(memoize=True).run(5)
|
|
91
|
+
comparison = naive.compare(memoized)
|
|
92
|
+
comparison
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
The comparison confirms the returned values match and reports the measured changes, including **15 → 6 executed function bodies**.
|
|
96
|
+
|
|
97
|
+
Normal function behavior is preserved:
|
|
98
|
+
|
|
99
|
+
```python
|
|
100
|
+
assert fib(5) == 5
|
|
101
|
+
assert fib.last_report.total_calls == 15
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## Export a report
|
|
105
|
+
|
|
106
|
+
```python
|
|
107
|
+
naive.export_html("fib-tree.html")
|
|
108
|
+
naive.export_html("fib-dag.html", mode="dag")
|
|
109
|
+
naive.export_json("fib-trace.json")
|
|
110
|
+
comparison.export_html("fib-comparison.html")
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
HTML reports are standalone files that can be opened without a notebook server. JSON contains the tree, DAG nodes and edges, metrics, warnings, and schema version.
|
|
114
|
+
|
|
115
|
+
## Supported scope
|
|
116
|
+
|
|
117
|
+
Version 0.1 targets synchronous, deterministic teaching examples such as factorial, Fibonacci, and pure mathematical recursion.
|
|
118
|
+
|
|
119
|
+
- Memoization requires hashable bound arguments; unhashable arguments produce a warning instead of a crash.
|
|
120
|
+
- Identical arguments do not necessarily imply redundant work for functions with side effects or external state.
|
|
121
|
+
- `max_calls` bounds the stored tree without changing the function's result. A truncated report explicitly marks partial tree/DAG metrics.
|
|
122
|
+
- RecursionLens executes your function; it is **not a sandbox for untrusted code**.
|
|
123
|
+
- Async functions, generators, automatic Big-O inference, and automatic code rewriting are non-goals for 0.1.
|
|
124
|
+
|
|
125
|
+
## Documentation and demo
|
|
126
|
+
|
|
127
|
+
- [Executed JupyterLab tutorial](https://github.com/ZiyuJiang07/recursionlens/blob/main/docs/RecursionLens_Tutorial.ipynb) - setup, three views, comparison, export, troubleshooting, and a one-minute demo recipe
|
|
128
|
+
- [Static HTML tutorial preview](https://github.com/ZiyuJiang07/recursionlens/raw/main/docs/RecursionLens_Tutorial.html) - the same tutorial with all visual reports rendered, no Jupyter required
|
|
129
|
+
- [Runnable example notebook](https://github.com/ZiyuJiang07/recursionlens/blob/main/examples/demo.ipynb)
|
|
130
|
+
- [User guide](https://github.com/ZiyuJiang07/recursionlens/blob/main/docs/USER.md)
|
|
131
|
+
- [Developer guide](https://github.com/ZiyuJiang07/recursionlens/blob/main/docs/DEVELOPER.md)
|
|
132
|
+
- [Changelog](https://github.com/ZiyuJiang07/recursionlens/blob/main/CHANGELOG.md)
|
|
133
|
+
|
|
134
|
+
## Development and testing
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
python -m pytest tests -v
|
|
138
|
+
python -m build
|
|
139
|
+
python -m twine check dist/*
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
The test suite covers trace invariants, cache behavior, signatures, exceptions, truncation, JSON, HTML escaping, DAG rendering, and comparison reports. Contributions should keep the public API, tests, and user documentation synchronized.
|
|
143
|
+
|
|
144
|
+
## License
|
|
145
|
+
|
|
146
|
+
MIT © 2026 Ziyu Jiang, Xinyue Cai, Kehan Cao, Ruoxi Lyu, Haobo Ma.
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=77"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "recursionlens"
|
|
7
|
+
version = "0.1.2"
|
|
8
|
+
description = "Interactive recursion tracing, subproblem DAGs, and memoization comparisons for JupyterLab."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.9"
|
|
11
|
+
license = "MIT"
|
|
12
|
+
license-files = ["LICENSE"]
|
|
13
|
+
authors = [
|
|
14
|
+
{ name = "Ziyu Jiang" },
|
|
15
|
+
{ name = "Xinyue Cai" },
|
|
16
|
+
{ name = "Kehan Cao" },
|
|
17
|
+
{ name = "Ruoxi Lyu" },
|
|
18
|
+
{ name = "Haobo Ma" },
|
|
19
|
+
]
|
|
20
|
+
keywords = ["recursion", "jupyterlab", "visualization", "memoization", "education"]
|
|
21
|
+
classifiers = [
|
|
22
|
+
"Development Status :: 3 - Alpha",
|
|
23
|
+
"Intended Audience :: Education",
|
|
24
|
+
"Programming Language :: Python :: 3",
|
|
25
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
26
|
+
"Framework :: Jupyter",
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
[project.urls]
|
|
30
|
+
Homepage = "https://github.com/ZiyuJiang07/recursionlens"
|
|
31
|
+
Repository = "https://github.com/ZiyuJiang07/recursionlens"
|
|
32
|
+
Documentation = "https://github.com/ZiyuJiang07/recursionlens/blob/main/docs/USER.md"
|
|
33
|
+
Tutorial = "https://github.com/ZiyuJiang07/recursionlens/blob/main/docs/RecursionLens_Tutorial.ipynb"
|
|
34
|
+
Changelog = "https://github.com/ZiyuJiang07/recursionlens/blob/main/CHANGELOG.md"
|
|
35
|
+
Issues = "https://github.com/ZiyuJiang07/recursionlens/issues"
|
|
36
|
+
|
|
37
|
+
[project.optional-dependencies]
|
|
38
|
+
dev = ["pytest>=8", "coverage>=7"]
|
|
39
|
+
|
|
40
|
+
[tool.setuptools.packages.find]
|
|
41
|
+
where = ["src"]
|
|
42
|
+
|
|
43
|
+
[tool.pytest.ini_options]
|
|
44
|
+
testpaths = ["tests"]
|
|
45
|
+
addopts = "-q"
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""RecursionLens: recursive execution analysis for JupyterLab."""
|
|
2
|
+
|
|
3
|
+
from ._model import CallNode, DAGNode
|
|
4
|
+
from ._report import ComparisonReport, TraceReport
|
|
5
|
+
from ._trace import trace
|
|
6
|
+
|
|
7
|
+
__version__ = "0.1.2"
|
|
8
|
+
|
|
9
|
+
__all__ = [
|
|
10
|
+
"CallNode",
|
|
11
|
+
"ComparisonReport",
|
|
12
|
+
"DAGNode",
|
|
13
|
+
"TraceReport",
|
|
14
|
+
"trace",
|
|
15
|
+
"__version__",
|
|
16
|
+
]
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
from typing import Any, Dict, Iterable, List, Optional, Tuple
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@dataclass
|
|
8
|
+
class CallNode:
|
|
9
|
+
"""One observed function invocation, including cache-only invocations."""
|
|
10
|
+
|
|
11
|
+
call_id: int
|
|
12
|
+
identity: str
|
|
13
|
+
signature: str
|
|
14
|
+
depth: int
|
|
15
|
+
status: str = "running"
|
|
16
|
+
result: Optional[str] = None
|
|
17
|
+
error: Optional[str] = None
|
|
18
|
+
repeated: bool = False
|
|
19
|
+
children: List["CallNode"] = field(default_factory=list)
|
|
20
|
+
|
|
21
|
+
@property
|
|
22
|
+
def cached(self) -> bool:
|
|
23
|
+
return self.status == "cached"
|
|
24
|
+
|
|
25
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
26
|
+
return {
|
|
27
|
+
"call_id": self.call_id,
|
|
28
|
+
"identity": self.identity,
|
|
29
|
+
"signature": self.signature,
|
|
30
|
+
"depth": self.depth,
|
|
31
|
+
"status": self.status,
|
|
32
|
+
"result": self.result,
|
|
33
|
+
"error": self.error,
|
|
34
|
+
"repeated": self.repeated,
|
|
35
|
+
"children": [child.to_dict() for child in self.children],
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@dataclass
|
|
40
|
+
class DAGNode:
|
|
41
|
+
"""All invocations with one internal argument identity, merged together."""
|
|
42
|
+
|
|
43
|
+
identity: str
|
|
44
|
+
signature: str
|
|
45
|
+
calls: int = 0
|
|
46
|
+
executed: int = 0
|
|
47
|
+
cached: int = 0
|
|
48
|
+
result: Optional[str] = None
|
|
49
|
+
children: List[str] = field(default_factory=list)
|
|
50
|
+
|
|
51
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
52
|
+
return {
|
|
53
|
+
"identity": self.identity,
|
|
54
|
+
"signature": self.signature,
|
|
55
|
+
"calls": self.calls,
|
|
56
|
+
"executed": self.executed,
|
|
57
|
+
"cached": self.cached,
|
|
58
|
+
"result": self.result,
|
|
59
|
+
"children": list(self.children),
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def build_dag(nodes: Iterable[CallNode]) -> Tuple[List[DAGNode], List[Dict[str, str]]]:
|
|
64
|
+
"""Compress a call forest into a deterministic subproblem DAG."""
|
|
65
|
+
|
|
66
|
+
merged: Dict[str, DAGNode] = {}
|
|
67
|
+
edges = set()
|
|
68
|
+
|
|
69
|
+
def visit(node: CallNode) -> None:
|
|
70
|
+
dag_node = merged.get(node.identity)
|
|
71
|
+
if dag_node is None:
|
|
72
|
+
dag_node = DAGNode(identity=node.identity, signature=node.signature)
|
|
73
|
+
merged[node.identity] = dag_node
|
|
74
|
+
|
|
75
|
+
dag_node.calls += 1
|
|
76
|
+
if node.cached:
|
|
77
|
+
dag_node.cached += 1
|
|
78
|
+
else:
|
|
79
|
+
dag_node.executed += 1
|
|
80
|
+
if dag_node.result is None and node.result is not None:
|
|
81
|
+
dag_node.result = node.result
|
|
82
|
+
|
|
83
|
+
for child in node.children:
|
|
84
|
+
if child.identity not in dag_node.children:
|
|
85
|
+
dag_node.children.append(child.identity)
|
|
86
|
+
edges.add((node.identity, child.identity))
|
|
87
|
+
visit(child)
|
|
88
|
+
|
|
89
|
+
for root in nodes:
|
|
90
|
+
visit(root)
|
|
91
|
+
|
|
92
|
+
edge_dicts = [{"source": source, "target": target} for source, target in sorted(edges)]
|
|
93
|
+
return list(merged.values()), edge_dicts
|