mathslate 0.1.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.
Files changed (89) hide show
  1. mathslate-0.1.0/.github/workflows/ci.yml +48 -0
  2. mathslate-0.1.0/.github/workflows/publish.yml +45 -0
  3. mathslate-0.1.0/.gitignore +12 -0
  4. mathslate-0.1.0/LICENSE +21 -0
  5. mathslate-0.1.0/PKG-INFO +338 -0
  6. mathslate-0.1.0/README.md +301 -0
  7. mathslate-0.1.0/README_ko.md +248 -0
  8. mathslate-0.1.0/docs/manual.md +2136 -0
  9. mathslate-0.1.0/docs/manual_ko.md +1734 -0
  10. mathslate-0.1.0/docs/tutorial.md +637 -0
  11. mathslate-0.1.0/docs/tutorial_ko.md +511 -0
  12. mathslate-0.1.0/examples/__init__.py +0 -0
  13. mathslate-0.1.0/examples/build_3d_tour.py +301 -0
  14. mathslate-0.1.0/examples/build_tour.py +804 -0
  15. mathslate-0.1.0/examples/marimo_notebook.py +113 -0
  16. mathslate-0.1.0/examples/mathslate_3d_tour.ipynb +494 -0
  17. mathslate-0.1.0/examples/mathslate_3d_tour.py +219 -0
  18. mathslate-0.1.0/examples/mathslate_tour.ipynb +1799 -0
  19. mathslate-0.1.0/examples/mathslate_tour.py +1079 -0
  20. mathslate-0.1.0/examples/quickstart.py +81 -0
  21. mathslate-0.1.0/mathslate/__init__.py +140 -0
  22. mathslate-0.1.0/mathslate/_text.py +64 -0
  23. mathslate-0.1.0/mathslate/ai/__init__.py +51 -0
  24. mathslate-0.1.0/mathslate/ai/providers.py +231 -0
  25. mathslate-0.1.0/mathslate/ai/suggest.py +453 -0
  26. mathslate-0.1.0/mathslate/api.py +626 -0
  27. mathslate-0.1.0/mathslate/classroom.py +229 -0
  28. mathslate-0.1.0/mathslate/codegen.py +843 -0
  29. mathslate-0.1.0/mathslate/core/__init__.py +24 -0
  30. mathslate-0.1.0/mathslate/core/_budget.py +334 -0
  31. mathslate-0.1.0/mathslate/core/_failure.py +85 -0
  32. mathslate-0.1.0/mathslate/core/_sets.py +170 -0
  33. mathslate-0.1.0/mathslate/core/_source.py +48 -0
  34. mathslate-0.1.0/mathslate/core/analysis.py +1277 -0
  35. mathslate-0.1.0/mathslate/core/binding.py +182 -0
  36. mathslate-0.1.0/mathslate/core/data.py +775 -0
  37. mathslate-0.1.0/mathslate/core/dispatch.py +1106 -0
  38. mathslate-0.1.0/mathslate/core/sampling.py +947 -0
  39. mathslate-0.1.0/mathslate/core/surfaces.py +305 -0
  40. mathslate-0.1.0/mathslate/core/tables.py +202 -0
  41. mathslate-0.1.0/mathslate/errors.py +43 -0
  42. mathslate-0.1.0/mathslate/render/__init__.py +7 -0
  43. mathslate-0.1.0/mathslate/render/axes.py +126 -0
  44. mathslate-0.1.0/mathslate/render/options.py +199 -0
  45. mathslate-0.1.0/mathslate/render/plotly_backend.py +580 -0
  46. mathslate-0.1.0/mathslate/result.py +842 -0
  47. mathslate-0.1.0/mathslate/ui/__init__.py +16 -0
  48. mathslate-0.1.0/mathslate/ui/adapters.py +104 -0
  49. mathslate-0.1.0/mathslate/ui/interact.py +302 -0
  50. mathslate-0.1.0/mathslate_prd_0.3.md +2193 -0
  51. mathslate-0.1.0/pyproject.toml +52 -0
  52. mathslate-0.1.0/tests/conftest.py +73 -0
  53. mathslate-0.1.0/tests/corpus.py +224 -0
  54. mathslate-0.1.0/tests/helpers.py +44 -0
  55. mathslate-0.1.0/tests/test_acceptance.py +222 -0
  56. mathslate-0.1.0/tests/test_ai_credentials.py +182 -0
  57. mathslate-0.1.0/tests/test_ai_safety.py +115 -0
  58. mathslate-0.1.0/tests/test_analysis.py +394 -0
  59. mathslate-0.1.0/tests/test_api_surface.py +220 -0
  60. mathslate-0.1.0/tests/test_binding.py +84 -0
  61. mathslate-0.1.0/tests/test_codegen.py +105 -0
  62. mathslate-0.1.0/tests/test_codegen_fidelity.py +514 -0
  63. mathslate-0.1.0/tests/test_corpus.py +95 -0
  64. mathslate-0.1.0/tests/test_discontinuity_review.py +117 -0
  65. mathslate-0.1.0/tests/test_dispatch.py +151 -0
  66. mathslate-0.1.0/tests/test_docs.py +108 -0
  67. mathslate-0.1.0/tests/test_environments.py +119 -0
  68. mathslate-0.1.0/tests/test_example_notebook.py +123 -0
  69. mathslate-0.1.0/tests/test_exclusions.py +168 -0
  70. mathslate-0.1.0/tests/test_extreme_values.py +35 -0
  71. mathslate-0.1.0/tests/test_failure_taxonomy.py +176 -0
  72. mathslate-0.1.0/tests/test_fitting_robustness.py +181 -0
  73. mathslate-0.1.0/tests/test_inequalities.py +258 -0
  74. mathslate-0.1.0/tests/test_input_validation.py +207 -0
  75. mathslate-0.1.0/tests/test_interaction.py +260 -0
  76. mathslate-0.1.0/tests/test_marimo_imports.py +130 -0
  77. mathslate-0.1.0/tests/test_notebook_display.py +165 -0
  78. mathslate-0.1.0/tests/test_parametric_sampling.py +156 -0
  79. mathslate-0.1.0/tests/test_sampling.py +149 -0
  80. mathslate-0.1.0/tests/test_session_state.py +180 -0
  81. mathslate-0.1.0/tests/test_symbolic_budget.py +434 -0
  82. mathslate-0.1.0/tests/test_tables_and_export.py +181 -0
  83. mathslate-0.1.0/tests/test_text.py +68 -0
  84. mathslate-0.1.0/tests/test_three_dimensions.py +310 -0
  85. mathslate-0.1.0/tests/test_tour_notebooks.py +255 -0
  86. mathslate-0.1.0/tests/test_v1_data_and_ai.py +615 -0
  87. mathslate-0.1.0/tests/test_view_and_mesh.py +926 -0
  88. mathslate-0.1.0/tests/test_warning_policy.py +89 -0
  89. mathslate-0.1.0/tests/test_worksheet_display.py +119 -0
@@ -0,0 +1,48 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ pull_request:
6
+
7
+ jobs:
8
+ test:
9
+ name: tests (py${{ matrix.python }}, ${{ matrix.os }})
10
+ runs-on: ${{ matrix.os }}
11
+ strategy:
12
+ fail-fast: false
13
+ matrix:
14
+ os: [ubuntu-latest, windows-latest]
15
+ python: ["3.10", "3.12"]
16
+ steps:
17
+ - uses: actions/checkout@v4
18
+ - uses: actions/setup-python@v5
19
+ with:
20
+ python-version: ${{ matrix.python }}
21
+ # All three notebook hosts, so acceptance criterion 5 really runs.
22
+ - run: pip install -e ".[dev,jupyter,marimo]" nbclient nbformat ipykernel
23
+ # The marimo tour is maintained separately by its author. Its expensive
24
+ # rendering and generator-synchronisation checks remain available for
25
+ # manual verification, but must not make the library CI fail.
26
+ - run: python -m pytest -q --ignore=tests/test_tour_notebooks.py
27
+
28
+ no_frontend_dependency:
29
+ name: acceptance criterion 6 - no frontend dependency
30
+ runs-on: ubuntu-latest
31
+ steps:
32
+ - uses: actions/checkout@v4
33
+ - uses: actions/setup-python@v5
34
+ with:
35
+ python-version: "3.12"
36
+ - name: install the package exactly as a user would
37
+ run: pip install .
38
+ - name: assert no frontend package came with it
39
+ run: |
40
+ python - <<'PY'
41
+ import importlib.util, sys
42
+ for frontend in ("marimo", "ipywidgets", "notebook", "jupyter_core"):
43
+ if importlib.util.find_spec(frontend) is not None:
44
+ sys.exit(f"{frontend} was installed by `pip install mathslate`")
45
+ import mathslate
46
+ mathslate.plot(mathslate.sin(mathslate.x), verbose=False)
47
+ print("clean install, plot works:", mathslate.frontend_report())
48
+ PY
@@ -0,0 +1,45 @@
1
+ name: Publish distribution to PyPI
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - "v*"
7
+ workflow_dispatch:
8
+
9
+ permissions:
10
+ contents: read
11
+
12
+ jobs:
13
+ build:
14
+ name: Build distribution
15
+ runs-on: ubuntu-latest
16
+ steps:
17
+ - uses: actions/checkout@v6
18
+ with:
19
+ persist-credentials: false
20
+ - uses: actions/setup-python@v6
21
+ with:
22
+ python-version: "3.12"
23
+ - name: Build wheel and source distribution
24
+ run: python -m pip install --upgrade build twine && python -m build && python -m twine check dist/*
25
+ - uses: actions/upload-artifact@v5
26
+ with:
27
+ name: python-package-distributions
28
+ path: dist/
29
+
30
+ publish:
31
+ name: Publish to PyPI
32
+ if: startsWith(github.ref, 'refs/tags/v')
33
+ needs: build
34
+ runs-on: ubuntu-latest
35
+ environment:
36
+ name: pypi
37
+ url: https://pypi.org/p/mathslate
38
+ permissions:
39
+ id-token: write
40
+ steps:
41
+ - uses: actions/download-artifact@v6
42
+ with:
43
+ name: python-package-distributions
44
+ path: dist/
45
+ - uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,12 @@
1
+ .venv/
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.egg-info/
5
+ build/
6
+ dist/
7
+ .pytest_cache/
8
+ .ruff_cache/
9
+ .mypy_cache/
10
+ .ipynb_checkpoints/
11
+ __marimo__/
12
+ old_doc/
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 MathSlate 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,338 @@
1
+ Metadata-Version: 2.4
2
+ Name: mathslate
3
+ Version: 0.1.0
4
+ Summary: A mathematical workspace that grows with you.
5
+ Project-URL: Homepage, https://github.com/berd2/mathslate
6
+ Project-URL: Documentation, https://github.com/berd2/mathslate/blob/master/docs/manual.md
7
+ Project-URL: Repository, https://github.com/berd2/mathslate
8
+ Project-URL: Issues, https://github.com/berd2/mathslate/issues
9
+ Author: MathSlate contributors
10
+ License-Expression: MIT
11
+ License-File: LICENSE
12
+ Keywords: education,mathematics,plotly,plotting,sympy
13
+ Classifier: Development Status :: 3 - Alpha
14
+ Classifier: Intended Audience :: Education
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Topic :: Scientific/Engineering :: Mathematics
18
+ Requires-Python: >=3.10
19
+ Requires-Dist: numpy>=1.24
20
+ Requires-Dist: plotly>=5.18
21
+ Requires-Dist: sympy>=1.12
22
+ Provides-Extra: ai
23
+ Requires-Dist: anthropic>=0.40; extra == 'ai'
24
+ Provides-Extra: ai-gemini
25
+ Requires-Dist: google-genai>=1.0; extra == 'ai-gemini'
26
+ Provides-Extra: ai-openai
27
+ Requires-Dist: openai>=1.50; extra == 'ai-openai'
28
+ Provides-Extra: dev
29
+ Requires-Dist: pytest>=8.0; extra == 'dev'
30
+ Requires-Dist: tomli>=2.0; (python_version < '3.11') and extra == 'dev'
31
+ Provides-Extra: jupyter
32
+ Requires-Dist: anywidget>=0.9; extra == 'jupyter'
33
+ Requires-Dist: ipywidgets>=8.0; extra == 'jupyter'
34
+ Provides-Extra: marimo
35
+ Requires-Dist: marimo>=0.9; extra == 'marimo'
36
+ Description-Content-Type: text/markdown
37
+
38
+ # MathSlate
39
+
40
+ **A mathematical workspace that grows with you.**
41
+
42
+ A mathematical workspace that orchestrates SymPy, NumPy and Plotly so that a
43
+ learner can go from their first graph to real scientific computing without ever
44
+ changing tools.
45
+
46
+ ```python
47
+ from mathslate import *
48
+
49
+ plot(sin(x)/x)
50
+ ```
51
+
52
+ ```
53
+ curve | x ∈ [-10, 10] | 411 samples | 1 discontinuity handled
54
+ · singularities at x = 0
55
+ ```
56
+
57
+ That is the whole first lesson. No `symbols`, no `lambdify`, no `linspace`, no
58
+ `figure`, no `show`. When you are ready for those, ask:
59
+
60
+ ```python
61
+ plot(sin(x)/x).show_python()
62
+ ```
63
+
64
+ and MathSlate prints the plain NumPy + SymPy + Plotly program that would have
65
+ produced the same picture — including the parts it did quietly on your behalf.
66
+
67
+ ---
68
+
69
+ ## Install
70
+
71
+ ```bash
72
+ pip install mathslate
73
+ ```
74
+
75
+ Nothing frontend-specific comes with it. Add what your notebook needs:
76
+
77
+ ```bash
78
+ pip install "mathslate[jupyter]"
79
+ ```
80
+
81
+ ```bash
82
+ pip install "mathslate[marimo]"
83
+ ```
84
+
85
+ > **marimo users:** marimo rejects `import *` at parse time — it needs to know
86
+ > statically which names each cell defines in order to build its reactive
87
+ > graph. Import explicitly instead; everything else is identical. See the
88
+ > [manual](https://github.com/berd2/mathslate/blob/master/docs/manual.md#marimo-forbids-import-).
89
+ >
90
+ > ```python
91
+ > from mathslate import plot, polar, sin, cos, tan, exp, sqrt, x, y, t
92
+ > ```
93
+
94
+ ## The one rule
95
+
96
+ `plot()` infers what you meant. There is exactly one thing to memorise:
97
+
98
+ - a **list** means *several things together*
99
+ - a **tuple** means *one vector-valued object*
100
+
101
+ ```python
102
+ plot([sin(x), cos(x)]) # two curves, overlaid
103
+ plot((sin(t), cos(t))) # one parametric curve
104
+ ```
105
+
106
+ Everything else follows the dispatch contract:
107
+
108
+ | Input | Free symbols | Result |
109
+ |---|---|---|
110
+ | `Expr` | 1 | 2D curve |
111
+ | `Expr` | 0 | horizontal line + message |
112
+ | `list[Expr]` | 1 shared | curves overlaid |
113
+ | `tuple[Expr, Expr]` | 1 shared | 2D parametric curve |
114
+ | `callable` | — | numeric sampling |
115
+ | array-like | — | data series |
116
+ | `(xdata, ydata)` | — | scatter |
117
+ | `Expr` | 2 | surface, `kind="contour"` to flatten |
118
+ | `Eq(lhs, rhs)` | 2 | implicit curve |
119
+ | `tuple[Expr × 3]` | 1 or 2 | space curve / parametric surface |
120
+
121
+ Two cases inference cannot decide in principle, so you say them out loud:
122
+
123
+ ```python
124
+ polar(1 + cos(t)) # r = f(θ) is indistinguishable from y = f(x)
125
+ plot(x*y, kind="contour") # surface vs. contour
126
+ ```
127
+
128
+ Every call reports what it inferred, in one line. That line is not logging: it
129
+ is where you first learn that parameters you never wrote exist.
130
+
131
+ ## Which symbol becomes the axis
132
+
133
+ 1. an explicit range wins — `plot(a*sin(x), (a, -1, 1))`
134
+ 2. bound parameters are never axes — `plot(a*sin(x), parameters={a: 3})`
135
+ 3. otherwise the conventional order: `x, y, z` → `t, u, v` → `r, θ` → alphabetical
136
+ 4. if it is still ambiguous, MathSlate asks instead of guessing
137
+
138
+ ## The hard part: discontinuities
139
+
140
+ `plot(tan(x))` drawing no spurious vertical lines is the feature that separates
141
+ MathSlate from a plotting wrapper. Everything else here is convenience.
142
+
143
+ ```python
144
+ plot(tan(x)) # broken at every pole, y-window from the 2nd–98th percentile
145
+ plot(1/x) # broken at 0
146
+ plot(floor(x)) # broken at every integer
147
+ plot(sqrt(x)) # never sampled outside its real domain
148
+ plot(x/abs(x)) # broken at 0
149
+ ```
150
+
151
+ How, in order:
152
+
153
+ 1. **Symbolic singularities** from `sympy.calculus.singularities` — never guessed.
154
+ 2. **Real domain** from `sympy.calculus.util.continuous_domain` — never sampled outside it.
155
+ 3. **Adaptive subdivision**: 200 uniform points, then midpoints wherever three
156
+ adjacent points bend, to depth 8 and at most 5000 points.
157
+ 4. **Line breaking**: NaN at every discontinuity. Jumps SymPy cannot see
158
+ (`floor`, `sign`, `Piecewise`) are found by bisection — a genuine jump keeps
159
+ its size as the interval shrinks, a steep slope does not.
160
+ 5. **Y-clipping**: the visible window comes from the 2nd–98th percentile with
161
+ near-pole samples excluded.
162
+ 6. **Vectorised evaluation** via `lambdify(modules="numpy")`, falling back to
163
+ element-wise evaluation loudly — never silently. The last element-wise tier
164
+ is SymPy's own `evalf`, which is what makes `zeta`, `Si` and `besselj`
165
+ plottable at all: no numeric backend carries them.
166
+
167
+ All six apply to parametric and polar curves as well. There the continuous
168
+ pieces are the intersection of both components' domains, the jump probe watches
169
+ `x(t)` as well as `y(t)`, and the window is clipped horizontally too — `x(t)`
170
+ can reach a pole in a way `x` never can when it is the axis.
171
+
172
+ ```python
173
+ plot((tan(t), t)) # broken at t = π/2 and 3π/2, not drawn out to 10¹⁶
174
+ polar(tan(t)) # the same, through the polar reduction
175
+ ```
176
+
177
+ ## What a function *is*: `analyze()`
178
+
179
+ Explicit, never automatic — property detection is too slow and too noisy to run
180
+ on every plot.
181
+
182
+ ```python
183
+ analyze(x**3 - 3*x) # roots -sqrt(3), 0, sqrt(3); max at -1; min at 1
184
+ analyze(1/x) # x = 0 vertical; y = 0 at both ends; odd
185
+ plot(tan(x)).analyze() # analyses the window you are looking at
186
+ ```
187
+
188
+ Roots, extrema, inflection points, symmetry, periodicity, asymptotes,
189
+ discontinuities and monotonic intervals. Exact where SymPy can solve it,
190
+ sampled where it cannot — and the sampled lines are **labelled approximate**,
191
+ because an approximation dressed as a proof is worse than no answer.
192
+
193
+ It reports what a function *is*. It never narrates how the answer was reached.
194
+
195
+ ## Interactive: `slider()`
196
+
197
+ Bind a parameter and it becomes something the reader can drag. **You never
198
+ write a callback.**
199
+
200
+ ```python
201
+ a = slider(-3, 3, default=1, name="a")
202
+ plot(a*sin(x)) # x is the axis, a is the parameter — inferred
203
+ animate(a*sin(x)) # the same, with a play button
204
+ ```
205
+
206
+ The control is Plotly's own, carried inside the figure, so it needs no frontend
207
+ package and survives export to one self-contained HTML file — which is the
208
+ point if you are handing it to a class. `Slider.widget()` gives you
209
+ `mo.ui.slider` or `ipywidgets` instead when you want live recomputation.
210
+
211
+ ## Your own numbers: `dataset()`
212
+
213
+ The bridge from symbolic to data. Write the model as you would on paper; get
214
+ **the same expression back with its parameters filled in**, still SymPy.
215
+
216
+ ```python
217
+ readings = dataset({"x": [0, 1, 2, 3], "y": [1.0, 3.1, 4.9, 7.2]})
218
+ found = readings.fit(a*x + b) # a = 2.05, b = 0.98, R² = 0.999
219
+ diff(found.expr, x) # still an expression — everything works on it
220
+ ```
221
+
222
+ Linear in the parameters is solved exactly; anything else is refined
223
+ iteratively. `.residuals` and `.r_squared` come back so the fit can be judged
224
+ rather than trusted.
225
+
226
+ ```python
227
+ plot(readings) # the columns
228
+ plot(readings, kind="hist") # their shape
229
+ plot(Matrix([[2, 1], [1, 3]])) # a matrix as what it does, with eigenvectors
230
+ ```
231
+
232
+ ## Numbers, and one file to hand out
233
+
234
+ ```python
235
+ table(sin(x), (x, 0, 1)) # the same function, read as values
236
+ plot(a*sin(x)).to_html("lesson.html")
237
+ ```
238
+
239
+ `to_html()` embeds Plotly itself, so the page opens with no network and nothing
240
+ installed — sliders included. That is why the slider is built from frames
241
+ carried inside the figure rather than a notebook widget: a widget needs a live
242
+ kernel, and a file handed to a class does not have one.
243
+
244
+ ## Escape hatches
245
+
246
+ Peeling the wrapper off costs nothing:
247
+
248
+ ```python
249
+ f = plot(sin(x)/x)
250
+ f.plotly # the Plotly Figure — yours to mutate
251
+ f.sympy # the expression
252
+ f.numpy # the sampled (x, y) arrays
253
+ f.python() # the equivalent code, as a string
254
+ ```
255
+
256
+ ## Optional: the assistant, and worksheets
257
+
258
+ ```python
259
+ from mathslate.ai import ask
260
+ print(ask("plot the tangent over one period").code) # you read it, then run it
261
+ ```
262
+
263
+ Claude, OpenAI or Gemini — install one and set its key, either as the provider's
264
+ environment variable or with `api_key=` on `ask()`/`configure()`. **The core never
265
+ imports any of it** and works fully offline, which is the point on a school
266
+ network; the test suite asserts that in a subprocess. `ask()` returns code, it
267
+ does not execute it. `Suggestion.run()` validates a restricted MathSlate subset
268
+ by default; unrestricted Python requires the explicit `unsafe=True` escape hatch.
269
+
270
+ ```python
271
+ from mathslate.classroom import worksheet
272
+ worksheet([
273
+ "Where does sin(x)/x go at zero?",
274
+ ("The graph", plot(sin(x)/x)),
275
+ ("The numbers", table(sin(x)/x, (x, -1, 1))),
276
+ ], title="Limits", path="handout.html")
277
+ ```
278
+
279
+ Plotly is embedded once however many figures the page holds.
280
+ Pass `standalone=False` to `worksheet(...)`, `.html()`, `.save()` or `.preview()`
281
+ to link the versioned Plotly CDN and keep networked outputs small.
282
+
283
+ ## What MathSlate is not
284
+
285
+ - Not a CAS. All symbolic computation is SymPy's.
286
+ - **Not a step-by-step derivation engine.** There is no `explain()`, no "show
287
+ steps", no generated worked solutions. SymPy provides no general
288
+ step-by-step engine, and a partial one would be worse than none.
289
+ - Not a notebook or editor. It runs inside marimo and Jupyter.
290
+ - Not Wolfram Language compatible.
291
+ - Not a grading or LMS tool.
292
+ - Not high-performance numerics — educational scale (≤10⁶ points).
293
+ - Not a GUI equation editor. Input is Python code.
294
+
295
+ ## Development
296
+
297
+ ```bash
298
+ python -m venv .venv && .venv/Scripts/pip install -e ".[dev,jupyter,marimo]"
299
+ ```
300
+
301
+ ```bash
302
+ python -m pytest -q
303
+ ```
304
+
305
+ The suite covers each PRD acceptance criterion in its own class, plus the
306
+ 200-function corpus, a 30-case discontinuity review, a three-environment parity
307
+ check, and every example in `docs/`.
308
+
309
+ ## Try it
310
+
311
+ A guided tour of everything in v1.0, as a notebook you run yourself — 95 cells
312
+ of curves, discontinuities, 3D, sliders, `analyze()`, tables, data fitting,
313
+ worksheets and refusals. Nothing in it needs a network.
314
+
315
+ ```bash
316
+ marimo edit examples/mathslate_tour.py
317
+ ```
318
+
319
+ ```bash
320
+ jupyter lab examples/mathslate_tour.ipynb
321
+ ```
322
+
323
+ The two are the same notebook: both are generated from
324
+ [`examples/build_tour.py`](https://github.com/berd2/mathslate/blob/master/examples/build_tour.py), and the test suite executes
325
+ the Jupyter one end to end on every run, so a tour cell that stops working
326
+ fails the build.
327
+
328
+ ## Documentation
329
+
330
+ - **[Tutorial](https://github.com/berd2/mathslate/blob/master/docs/tutorial.md)** — a one-sitting walkthrough, from your first
331
+ graph to reading real Python. Start here.
332
+ - **[Reference manual](https://github.com/berd2/mathslate/blob/master/docs/manual.md)** — every option, the full dispatch
333
+ contract, the sampling algorithm, and the exact guarantees.
334
+ - [`mathslate_prd_0.3.md`](https://github.com/berd2/mathslate/blob/master/mathslate_prd_0.3.md) — the product requirements
335
+ document, with an implementation-status section kept up to date.
336
+
337
+ Every `python` example in both documents is executed by the test suite in
338
+ document order, so the manuals cannot drift from the code.