From 1b00608ed855bca2444a00454a412d5fdcc34867 Mon Sep 17 00:00:00 2001 From: Zhixiang Feng Date: Tue, 22 Sep 2026 23:19:40 +0100 Subject: [PATCH 1/2] docs(guide) #115: teach agents to generate visual NumPy explanations --- README.md | 8 +- docs/guide/llm-prompts.md | 470 +++++++++++++++++++++----------------- docs/index.md | 7 +- 3 files changed, 268 insertions(+), 217 deletions(-) diff --git a/README.md b/README.md index 5c5799b..59cbed1 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ [Documentation](https://rainbow-tensor.zhixiangfeng.com/) · [Notebook examples](https://github.com/Niox1337/rainbow-tensor/tree/main/examples) · -[LLM visualization guide](https://rainbow-tensor.zhixiangfeng.com/guide/llm-prompts.html) · +[Agent instructions](https://rainbow-tensor.zhixiangfeng.com/guide/llm-prompts.html) · [Releases](https://github.com/Niox1337/rainbow-tensor/releases) rainbow-tensor turns tensor shapes and operations into SVG figures for Jupyter @@ -215,9 +215,13 @@ and [translations](https://rainbow-tensor.zhixiangfeng.com/guide/translations.ht | [Learning path](https://rainbow-tensor.zhixiangfeng.com/guide/learning-path.html) | Small exercises that connect shapes, output coordinates and source values | | [Notebook collection](https://github.com/Niox1337/rainbow-tensor/tree/main/examples) | Runnable examples from basic shapes through cross-operation tracing | | [API reference](https://rainbow-tensor.zhixiangfeng.com/api.html) | Function signatures, parameters and result objects | -| [LLM visualization guide](https://rainbow-tensor.zhixiangfeng.com/guide/llm-prompts.html) | A reusable prompt and verified patterns for generating beginner explanations | +| [Agent instructions](https://rainbow-tensor.zhixiangfeng.com/guide/llm-prompts.html) | Instructions and runnable patterns for agents generating visual NumPy explanations | | [Architecture](https://rainbow-tensor.zhixiangfeng.com/guide/architecture.html) | Coordinate mappings, rendering boundaries and performance decisions | +Give the [agent instructions URL](https://rainbow-tensor.zhixiangfeng.com/guide/llm-prompts.html) +to your agent with a NumPy question. The page teaches the agent how to generate +runnable Rainbow Tensor code and explain the result visually. + ## Contributing Bug reports, clearer teaching examples and translation catalogs are welcome. diff --git a/docs/guide/llm-prompts.md b/docs/guide/llm-prompts.md index f374826..07b8706 100644 --- a/docs/guide/llm-prompts.md +++ b/docs/guide/llm-prompts.md @@ -1,189 +1,239 @@ -# LLM prompt for beginner tensor lessons +# Agent instructions: teach NumPy with Rainbow Tensor + +This page is written for an LLM agent. When a user gives you this URL, use it +as the API and teaching reference for generating Rainbow Tensor code that helps +them understand NumPy visually. Read the instructions directly. The user does +not need to copy a prompt, fill in a template, or learn this reference first. + +The examples target **rainbow-tensor 1.3.0**. Your output should answer the +user's NumPy question with runnable code, a useful visualization, and a short +explanation of how particular output elements were produced. + +## Your task + +Use the user's expression, data, and learning goal when supplied. Otherwise, +choose a small deterministic NumPy array with distinct values. Default to a +static notebook example. Add interaction only when it helps and the environment +supports it. If the user only supplies this URL without a topic, ask which +NumPy operation or expression they want to understand. + +Follow this sequence when composing your answer: + +1. Identify the NumPy concept and one concrete question to explain. For a + reduction, this might be which input elements produce `result[1]`. +2. Write the real NumPy computation. Keep it separate from the visualization. + Show the input values, input shape, and expected result shape. +3. Choose the matching Rainbow Tensor call from the reference below. Focus + on one valid output coordinate so its source elements can be followed. +4. Explain the coordinate mapping or local arithmetic in words. Name the + coordinates as well as their colours. Preserve repeated contributions. +5. Check the shape and at least one value or source mapping with assertions. + Compare with the real NumPy result. Say which checks you actually ran. +6. Offer one small variation or prediction question, such as changing the + focus, axis, or slice direction. Include enough information to check it. + +Return complete, runnable cells with imports and brief explanations between +cells. Use `display(...)` when a cell contains multiple figures or other output. +For scripts, save SVG files and print their `.text` explanation. If you cannot +execute code, state that its checks are unverified. + +Write the explanation in the user's language. Keep Python identifiers and +comments in English. Leave the theme automatic unless requested otherwise. +Use `rt.available_languages()` before selecting a figure language with +`rt.set_language(...)`. Do not invent locale codes or translation keys. + +## Runtime and return types + +Install into the environment used by the notebook kernel or Python script: -Use this page as context when asking an LLM to build a lesson with -rainbow-tensor 1.3.0. It includes a reusable prompt, the API choices that matter, -and examples with checked results. Give the model the whole page when possible, -then state the operation and the learner's experience. +```bash +python -m pip install "rainbow-tensor==1.3.0" +``` -## Copy this prompt +The distribution name is `rainbow-tensor`. Import it as +`import rainbow_tensor as rt`. NumPy and IPython are dependencies. The optional +`rainbow-tensor[interactive]` extra provides notebook controls. -Replace the bracketed fields before sending it to an LLM. The instructions -work for a short notebook lesson or a single worked example. +Keep these three kinds of objects distinct: -```text -Write a beginner tensor lesson using rainbow-tensor 1.3.0. - -Topic: [for example, repeated indexing followed by transpose and sum] -Learner: [for example, knows Python lists but is new to tensor axes] -Environment: [Jupyter with widgets, Jupyter without widgets, or a Python script] -Explanation language: [language] -Lesson length: [one worked example, or a short sequence of notebook cells] - -Use these package facts: -- Install name: rainbow-tensor. Import: import rainbow_tensor as rt. -- Start with small, deterministic NumPy arrays, usually np.arange or np.array. - A shape tuple such as (2, 3) creates generated row-major example values - starting at zero. It does not supply the learner's actual data. -- rt.shape(x) shows structure. rt.index(x, selection, show_result=True) - compares input and result. Add focus=(...) to follow one output coordinate. -- Static operation calls return TensorVisual, a display object, not an array. - Do not index it, chain it into another tensor operation, or claim it holds - an executable backend tensor. Use .result_shape for an operation's output - shape. .shape describes its source. Read .svg and .text or call .save(path). -- Supported static operation names are index, reshape, transpose, swapaxes, - moveaxis, squeeze, expand_dims, repeat, take, concatenate, stack, broadcast, - sum, mean, matmul, and einsum. Use their documented arguments. rt.shape and - rt.memory are structure and storage views, not arithmetic operations. -- repeat and take default to axis=0 and require an integer axis. They do not - accept axis=None. Flatten a NumPy input with x.reshape(-1), or a tracked - input with flow.reshape(node, (-1,)), before repeating or taking positions. -- For a chain, create flow = rt.Flow(), register each array with - flow.input(array, name="X"), and call the corresponding flow methods. - They return TrackedTensor objects with the logical output .shape. - Keep operands in the same Flow. Each explicit name must be unique there. - Use node.visualize(focus=(...)), node.trace((...)), or node.value((...)). - These recipes do not intercept NumPy operations, recover earlier history, - execute native backend kernels, or materialise intermediate arrays. -- focus is a tuple of coordinates in the output, not an input selection. - Use () for a scalar. Empty outputs have no valid focus coordinate. -- rt.explore(rt.sum, x, axis=1) explores one operation. - rt.explore(node) explores a recorded chain. Both need the interactive extra, - a live notebook kernel, and widget support. Always supply a static fallback. - Do not pass an already rendered TensorVisual to rt.explore. -- A static broadcast view takes two inputs and focus_operand=0 or 1 chooses - its focused output. flow.broadcast(a, b) returns two tracked outputs, - not their sum or product. Unpack them before recording another operation. -- Coordinate traces preserve repeated contributions. A source cell appearing - twice can contribute twice even though the picture highlights it once. - Root occurrence counts describe participation, not derivatives or general - arithmetic coefficients. Keep products and each mean's divisor local to - the operation that produced them. -- Values in arithmetic previews use Python scalar arithmetic. Native backend - dtype accumulation, overflow, and rounding can differ. Compute native - results separately when the lesson needs that numerical behaviour. -- Keep default budgets for beginner examples. A question mark means a value - was not evaluated, not zero. Inspect trace completeness before calling a - source list exhaustive. Large previews can omit cells. -- Python code, identifiers, and comments should be English. Write the teaching - prose in the requested language. Use rt.available_languages() to inspect - installed catalogs, then rt.set_language(...) for supported figure text. - Never invent a locale code or translation key. Leave theme selection - automatic unless the learner requests a specific appearance. - -Teach in this order: -1. State one question the learner should be able to answer. -2. Show the input values and shapes. Explain which dimension each axis names. -3. Ask the learner to predict one output's shape or source coordinate. -4. Render a focused example. Name the output coordinate and its input - coordinates in words as well as colour. Explain each axis change. -5. Write the local arithmetic or coordinate mapping for that output. Preserve - duplicate terms and operation order. Check it with executable assertions. -6. Change one coordinate or parameter and ask a short follow-up question. - -Return runnable cells with imports, expected results, and brief explanations -between cells. Use explicit display(...) calls if a cell must show several -figures. Otherwise put the figure last in its own notebook cell. For a Python -script, save an SVG and print its text explanation. Keep the first example -small enough that all relevant cells are visible. State which assertions you -actually ran. If execution is unavailable, label them as unverified. - -Stay within the documented API. For an unsupported operation, compute its -native result separately and show it with rt.shape, stating that its internal -operation and earlier history are not traced. Do not invent a Flow method. -``` +| Object | Purpose | How to use it | +| --- | --- | --- | +| NumPy array | Execute the computation and check NumPy semantics | Index it, call NumPy operations, inspect `.shape` | +| `TensorVisual` | Display one operation and its metadata | Use `display(visual)`, `.svg`, `.text`, `.save(path)`, `.result_shape` | +| `TrackedTensor` | Record an explicit operation chain within a `Flow` | Use `.shape`, `.visualize(focus=...)`, `.trace(coordinate)`, `.value(coordinate)` | + +Static calls such as `rt.sum(x, axis=1)` return `TensorVisual`, not arrays. +Do not index that object or pass it as the input to another tensor operation. +For a single-input operation, `visual.shape` describes the source and +`visual.result_shape` describes the result. A shape tuple such as `(2, 3)` +creates generated row-major values starting at zero. Pass an actual array when +explaining the user's values. -## Choose the right entry point +`focus` is a tuple of **output** coordinates. It is not the input selection. +Use `focus=()` for a scalar output. Empty outputs have no valid coordinate, +so omit focus. Keep the first example small enough to show all relevant cells. -The [API reference](../api) lists full signatures. These are the choices -that most often change the explanation: +## Translate NumPy expressions into visualizations -| Question | Entry point | What to inspect | +Use this table to select an entry point. The rows are API patterns, with +`x`, `a`, `b`, `selection`, and `focus` supplied by your example. + +| NumPy expression or concept | Rainbow Tensor call | What to explain | | --- | --- | --- | -| What does each axis contain? | `rt.shape(x)` | Shape, axis legend, nested frames | -| Which input position becomes this output? | `rt.index(x, selection, focus=(0, 0))` | `visual.index_mapping` and `visual.trace` | -| How does one transformation move elements? | `rt.reshape(x, (3, 2), focus=(2, 1))` | Source coordinate and `visual.result_shape` | -| Which values form this sum or product? | `rt.sum(x, axis=1, focus=(0,))` or `rt.matmul(a, b, focus=(0, 0))` | Ordered `visual.trace.terms` | -| How did several steps produce this value? | `node.visualize(focus=(0,))` | `visual.provenance`, plus `node.trace((0,))` | -| Can the learner choose another result? | `rt.explore(operation, *args, **kwargs)` or `rt.explore(node)` | Clickable result cells and coordinate fields | -| Is the physical array contiguous or a view? | `rt.memory(x)` | Backend-reported storage information | - -The reshape example assumes a six-element input. Focus coordinates in the -other examples must match their own output shapes. Physical storage and logical -origins answer different questions. A traced transpose does not establish -whether a backend made a copy. - -Static views and Flow share these operation families: - -| Family | Operation names | -| --- | --- | -| Selection | `index`, `take`, `repeat` | -| Shape changes | `reshape`, `transpose`, `swapaxes`, `moveaxis`, `squeeze`, `expand_dims` | -| Combining inputs | `concatenate`, `stack`, `broadcast` | -| Arithmetic | `sum`, `mean`, `matmul`, `einsum` | - -For `concatenate` and `stack`, pass a sequence of operands. For `einsum`, put -the subscript string first, for example `rt.einsum("ij,jk->ik", a, b)`. -`repeat` and `take` default to `axis=0` and require an integer axis. They do -not accept `axis=None`. To flatten first, use `x.reshape(-1)` on a NumPy array, -or record `flow.reshape(node, (-1,))` before calling a Flow method. Static -`broadcast` takes two operands. -`Flow.broadcast` also supports more than two and returns one tracked output -per input. - -## Template: explain one index - -Install the package and NumPy in the Python environment used by the notebook -kernel or script: +| `x.shape` | `rt.shape(x)` | Axis numbers, lengths, and nested structure | +| `x[selection]` | `rt.index(x, selection, show_result=True, focus=focus)` | Which source coordinate supplies the chosen output | +| `x.reshape((3, 2))` | `rt.reshape(x, (3, 2), focus=focus)` | Row-major positions before and after a shape change | +| `x.transpose((1, 0))` | `rt.transpose(x, (1, 0), focus=focus)` | How swapping the two axes moves coordinates | +| `np.take(x, [1, 0, 1], axis=0)` | `rt.take(x, [1, 0, 1], axis=0, focus=focus)` | Reordered and repeated selections | +| `np.repeat(x, 2, axis=0)` | `rt.repeat(x, 2, axis=0, focus=focus)` | Separate copies that share a source | +| `np.concatenate([a, b], axis=0)` | `rt.concatenate([a, b], axis=0, focus=focus)` | Joining an existing axis | +| `np.stack([a, b], axis=0)` | `rt.stack([a, b], axis=0, focus=focus)` | Introducing a new axis | +| `np.broadcast_arrays(a, b)` | `rt.broadcast(a, b, focus=focus, focus_operand=0)` | Expansion of one operand to the common shape | +| `x.sum(axis=1, keepdims=True)` | `rt.sum(x, axis=1, keepdims=True, focus=focus)` | Which axis is reduced and why a length-one axis remains | +| `x.mean(axis=1)` | `rt.mean(x, axis=1, focus=focus)` | The selected group's sum and its divisor | +| `a @ b` | `rt.matmul(a, b, focus=focus)` | The ordered products forming a chosen output | +| `np.einsum("ij,jk->ik", a, b)` | `rt.einsum("ij,jk->ik", a, b, focus=focus)` | Retained and contracted indices | +| `x.strides` and storage layout | `rt.memory(x)` | Backend-reported storage, separately from logical origins | + +The reshape row requires six elements, the transpose and reduction rows assume +suitable two-dimensional arrays, and every focus must match its output shape. +Other supported shape operations are `swapaxes`, `moveaxis`, `squeeze`, and +`expand_dims`. Consult the [API reference](../api) for their exact signatures. + +Do not assume that every NumPy keyword is supported: + +- `repeat` and `take` default to `axis=0` and require an integer axis. NumPy's + default flattening behaviour is different. To reproduce it, flatten the + array first with `x.reshape(-1)` and visualize that flattened input. +- `reshape` has no `order` argument. Do not claim that a view explains a + Fortran-order reshape or proves whether NumPy copied the array. +- `sum` and `mean` accept `axis=None`, an integer, or a tuple, plus `keepdims`. + They do not accept NumPy's `dtype`, `out`, or `where` keywords. +- Indexing supports integer positions, slices, ellipsis, new axes, boolean + masks, and advanced integer arrays. Boolean scalar indices are unsupported. +- `broadcast` explains operand expansion. It does not add or multiply the + operands. Choose `focus_operand=0` or `1` to inspect the corresponding input. +- There is no general `rt.add`, `rt.multiply`, or automatic NumPy interception. + For an unsupported operation, compute its result with NumPy and display + that array with `rt.shape`. State that its internal operation is not traced. + +## Pattern 1: generate a focused reduction explanation + +Use this self-contained pattern when the user asks about reduction axes. Keep +the NumPy result visible in the generated answer and connect it to the figure. -```bash -python -m pip install rainbow-tensor numpy +```python +import numpy as np +import rainbow_tensor as rt +from IPython.display import display + +x = np.arange(1, 7).reshape(2, 3) +result = x.sum(axis=1) +assert result.tolist() == [6, 15] + +visual = rt.sum(x, axis=1, focus=(1,)) +assert visual.result_shape == result.shape == (2,) +assert result[1] == x[1, 0] + x[1, 1] + x[1, 2] == 15 + +display(rt.shape(x)) +display(visual) +print(result) ``` -The question is: *which input value becomes output `(0, 0)`?* Row selection -`[1, 0, 1]` repeats the second input row. The slice reverses each selected row. +Explain that axis 0 selects rows and axis 1 moves through entries within a +row. Reducing axis 1 produces one sum per row. Output `(1,)` comes from +`X[1, 0]`, `X[1, 1]`, and `X[1, 2]`, giving `4 + 5 + 6 = 15`. + +For a follow-up, switch to `axis=0`. The NumPy result is `[5, 7, 9]` with +shape `(3,)`. If you instead add `keepdims=True` to the original reduction, +the result shape is `(2, 1)` and the matching focus becomes `(1, 0)`. + +## Pattern 2: generate an indexing explanation + +Use a repeated gather with a reverse slice to show why distinct output +positions can read the same source. Keep the original selection unchanged +between the NumPy expression and the visual call. ```python import numpy as np import rainbow_tensor as rt +from IPython.display import display x = np.arange(1, 7).reshape(2, 3) selection = ([1, 0, 1], slice(None, None, -1)) -expected = x[selection] -assert expected.tolist() == [[6, 5, 4], [3, 2, 1], [6, 5, 4]] +result = x[selection] +assert result.tolist() == [[6, 5, 4], [3, 2, 1], [6, 5, 4]] -visual = rt.index(x, selection, focus=(0, 0)) -assert visual.shape == (2, 3) -assert visual.result_shape == (3, 3) +visual = rt.index(x, selection, show_result=True, focus=(0, 0)) +assert visual.shape == x.shape == (2, 3) +assert visual.result_shape == result.shape == (3, 3) assert visual.index_mapping.source_coord((0, 0)) == (1, 2) assert visual.index_mapping.source_coord((2, 0)) == (1, 2) assert visual.trace.output_coord == (0, 0) -visual +display(visual) ``` -Output `(0, 0)` reads `X[1, 2]`, whose value is 6. Output `(2, 0)` reads the -same input position. Ask the learner what changes when the focus moves to -`(1, 0)`. The answer is input `(0, 2)`, whose value is 3. +Explain that outputs `(0, 0)` and `(2, 0)` both read `X[1, 2]`, whose value +is 6. Changing the focus to `(1, 0)` selects `X[0, 2]`, whose value is 3. +Do not collapse the two occurrences into one output position. -## Template: explain a chain +## Pattern 3: explain broadcasting before arithmetic -This example is self-contained. It records selection, transpose, and sum, -then asks why `Y[0]` is 15. +For a question about `a + b`, visualize how both operands expand, then compute +the addition in NumPy. The broadcast figure only explains the expansion. ```python import numpy as np import rainbow_tensor as rt +from IPython.display import display + +a = np.array([[10], [20]]) +b = np.array([[1, 2, 3]]) +expanded_a, expanded_b = np.broadcast_arrays(a, b) +result = a + b +assert result.tolist() == [[11, 12, 13], [21, 22, 23]] +assert expanded_a[1, 2] == a[1, 0] == 20 +assert expanded_b[1, 2] == b[0, 2] == 3 +assert result[1, 2] == expanded_a[1, 2] + expanded_b[1, 2] == 23 + +display(rt.broadcast(a, b, focus=(1, 2), focus_operand=0)) +display(rt.broadcast(a, b, focus=(1, 2), focus_operand=1)) +display(rt.shape(result)) +``` + +Explain right-aligned dimension matching: `(2, 1)` and `(1, 3)` both expand +to `(2, 3)`. At output `(1, 2)`, the values come from `a[1, 0]` and `b[0, 2]`. +The final shape view shows the addition's values without tracing the addition. + +## Pattern 4: trace a NumPy expression across operations + +Use `Flow` when the user wants to know how several steps produced a value. +Record each operation explicitly. Keep all tracked operands in the same Flow, +and use unique names there. NumPy operations on the original arrays do not +record history. Flow does not run native backend kernels or materialise +intermediate arrays. + +This pattern explains `x[selection].T.sum(axis=1)` and checks it against NumPy: + +```python +import numpy as np +import rainbow_tensor as rt +from IPython.display import display x = np.arange(1, 7).reshape(2, 3) +selection = ([1, 0, 1], slice(None, None, -1)) +result = x[selection].T.sum(axis=1) + flow = rt.Flow() source = flow.input(x, name="X") -selected = flow.index(source, ([1, 0, 1], slice(None, None, -1)), name="S") +selected = flow.index(source, selection, name="S") transposed = flow.transpose(selected, name="T") y = flow.sum(transposed, axis=1, name="Y") assert selected.shape == (3, 3) assert transposed.shape == (3, 3) -assert y.shape == (3,) -assert [y.value((i,)) for i in range(3)] == [15, 12, 9] +assert y.shape == result.shape == (3,) +assert [y.value((i,)) for i in range(3)] == result.tolist() == [15, 12, 9] trace = y.trace((0,)) assert trace.complete @@ -191,13 +241,12 @@ counts = {root.reference.coordinate: root.count for root in trace.roots} assert counts == {(1, 2): 2, (0, 2): 1} visual = y.visualize(focus=(0,)) -assert visual.result_shape == (3,) +assert visual.result_shape == result.shape assert visual.provenance.complete -visual +display(visual) ``` -Transpose turns the first column of `S` into the first row of `T`. Summing -axis 1 combines the entries along each row: +Explain the local operations in order, retaining the repeated term: ```text Y[0] = T[0, 0] + T[0, 1] + T[0, 2] @@ -207,20 +256,26 @@ Y[0] = T[0, 0] + T[0, 1] + T[0, 2] = 15 ``` -Three terms reach two distinct original cells. The count of two for `(1, 2)` -must survive the explanation. A dictionary keyed only by coordinates is safe -here because the chain has one original input. With several inputs, retain -the full `root.reference` so equal coordinates in different inputs remain -distinct. +Three contributions reach two distinct original cells. Root occurrence counts +are participation counts, not derivatives or general arithmetic coefficients. +Keep products and each mean's divisor local to the operation that produced +them. With several original inputs, retain the full `root.reference` when +collecting counts so equal coordinates in different arrays stay distinct. + +Flow provides the same operation families listed above, excluding the +standalone `shape` and `memory` views. `flow.broadcast(a, b)` returns a tuple +of tracked outputs, one per operand. Unpack it before recording another +operation. To flatten before `repeat` or `take`, record +`flow.reshape(node, (-1,))` first. -For the follow-up, focus on `(1,)` and predict `5 + 2 + 5 = 12`. Then change -the reduction to `flow.mean(transposed, axis=1, name="M")` and explain the -division by three at that step. +## Deliver notebook controls or portable output -## Add interaction or export a static lesson +Use static figures by default. When live interaction is appropriate, +`rt.explore(rt.sum, x, axis=1)` explores one operation and `rt.explore(node)` +explores a recorded chain. Do not pass an already rendered `TensorVisual`. -For live controls, install `rainbow-tensor[interactive]` in the kernel's -environment. The following cell reuses `y` from the chain: +The following cell reuses `y` from Pattern 4 and falls back when optional +widget dependencies are missing: ```python try: @@ -229,17 +284,17 @@ except ImportError: lesson_view = y.visualize(focus=(0,)) else: lesson_view = explorer -lesson_view +display(lesson_view) ``` -This fallback handles missing optional dependencies. A notebook host without -widget support may still need the explicit static call -`y.visualize(focus=(0,))`. Click a visible result cell or use its coordinate -fields to select another output. When finished with a live explorer, call -`explorer.close()` to release its widget connections. +A live kernel and a notebook host with widget support are also required. +An unsupported host may need the explicit static call even if the import +succeeds. Tell the learner that clicking a result cell or changing coordinate +fields selects another output. Call `explorer.close()` when the live explorer +is no longer needed, not immediately after displaying it. -For a script or an exported lesson, save the figure and its explanation -separately: +For a script or portable output, reuse `y` from Pattern 4 and save the figure +and explanation separately: ```python from pathlib import Path @@ -250,50 +305,41 @@ Path("tensor-origins.txt").write_text(visual.text, encoding="utf-8") print(visual.text) ``` -`save` writes the figure, while `.text` contains the notebook's accompanying -explanation. The default renderer produces SVG. It does not save a PNG -screenshot, and a saved SVG has no live Python controls. A screenshot can be -captured separately by the notebook host or browser when required. +`save` writes the figure only. It does not save a PNG or embed the accompanying +text explanation. A saved SVG has no live Python controls. -## Handle boundaries honestly +## Guardrails for generated explanations -- **Scalar and empty results.** A scalar has shape `()` and one element at - `focus=()`. An empty output has no element to select. Leave `focus=None`. - Distinguish an empty output from an empty reduction group. The latter can - produce a real output cell with sum 0 or mean NaN. +- **Numerical semantics.** Arithmetic previews use Python scalar arithmetic. + NumPy dtype accumulation, overflow, and rounding can differ. Use the real + NumPy result as the reference when those details are the lesson's subject. +- **Scalars and empties.** A scalar has one coordinate, `()`. An empty output + has none. Distinguish it from an empty reduction group, which can produce a + real output cell with sum 0 or mean NaN. - **Partial traces.** A single-operation `OutputTrace` stores at most eight terms. Check `.complete`. Flow tracing defaults to `max_depth=6`, - `max_nodes=80`, and `max_edges=120`. Check its `.complete` and - `.truncated_reasons` before claiming to list every contribution. Root counts - from an incomplete trace cover only paths actually reached. + `max_nodes=80`, and `max_edges=120`. Check `.complete` and + `.truncated_reasons` before calling a list of sources exhaustive. - **Bounded evaluation.** Arithmetic previews and Flow value queries default - to `max_terms=10_000` and `max_total_terms=100_000`. Their scopes differ, as - described in the guides below. A budget-limited figure shows `?`. - `TrackedTensor.value` raises `rt.ValueBudgetExceeded`. Do not silently - remove limits to make a large example render. -- **Live values.** Flow records operation parameters, including copied index - arrays. Input array values remain live and are read again on refresh. - Keep input shapes fixed. Editing an input value can change the next answer. -- **Language and colour.** Use installed translation catalogs for figure - text. Do not translate Python API names. Keep repository examples and code - comments in English. Name coordinates and axes explicitly so the lesson - remains understandable without relying on colour alone. - -## Check the generated lesson - -Before sharing it, verify that: - -- Every cell runs in order with declared imports and dependencies. -- The expected output shape, focused coordinate, and source coordinates agree. -- At least one asserted value or mapping checks the lesson's main claim. -- Repeated contributions and local products or divisors remain visible. -- Truncation, generated values, and skipped arithmetic are labelled accurately. -- The static version is useful without widgets, and saved text accompanies SVG - when the explanation is needed outside the notebook. -- The learner gets one concrete prediction question and a checked answer. - -Use the [indexing guide](indexing.md) for coordinate rules, -[reductions and math](reductions-and-math.md) for numerical semantics, -[cross-operation origins](provenance.md) for Flow and its budgets, -[interactive focus](interactive.md) for widget behaviour, and -[translations](translations.md) for language configuration. + to `max_terms=10_000` and `max_total_terms=100_000`. Their scopes differ. + A budget-limited figure shows `?`, meaning unevaluated, not zero. + `TrackedTensor.value` raises `rt.ValueBudgetExceeded`. Simplify a teaching + example rather than silently removing its limits. +- **Live inputs.** Flow copies operation parameters such as index arrays. + Input values remain live and are read again on refresh. Keep their shapes + fixed. An edited input value can change the next answer. +- **Unsupported behaviour.** Do not invent methods, keywords, or tracing + support. Displaying a NumPy result with `rt.shape` does not recover its + earlier operations. Logical origins do not establish physical memory use. + +Before returning generated code, check that it runs in order, compares the +NumPy result with the visualized operation, uses valid focus coordinates, and +explains at least one actual source mapping or calculation. Keep duplicate +contributions visible. Label generated values, incomplete traces, and +unverified checks accurately. The static version should remain useful when +widgets are unavailable. + +For less common cases, consult the [API reference](../api), +[indexing rules](indexing.md), [numerical semantics](reductions-and-math.md), +[Flow and its budgets](provenance.md), [widget behaviour](interactive.md), and +[language configuration](translations.md). diff --git a/docs/index.md b/docs/index.md index cf78f47..b17a7b8 100644 --- a/docs/index.md +++ b/docs/index.md @@ -71,8 +71,9 @@ notebooks in the `examples` folder. click and keyboard controls across shape, combining and math operations - [Cross-operation origins](guide/provenance) records a chain and follows a final element back to its original inputs, keeping repeated contributions -- [LLM lesson prompt](guide/llm-prompts) provides a reusable teaching prompt, - verified examples, and a checklist for beginner tensor visualisations +- [Agent instructions](guide/llm-prompts) teach agents to generate runnable + Rainbow Tensor code for visual NumPy explanations. Give its URL directly to + an agent with the NumPy topic you want to learn ```{toctree} :maxdepth: 2 @@ -91,7 +92,6 @@ guide/group-colours guide/memory guide/interactive guide/provenance -guide/llm-prompts ``` ```{toctree} @@ -100,5 +100,6 @@ guide/llm-prompts :hidden: api +guide/llm-prompts guide/architecture ``` From 5382867e309c39460f1e973efeb8c73f39f50ff1 Mon Sep 17 00:00:00 2001 From: Zhixiang Feng Date: Tue, 22 Sep 2026 23:19:40 +0100 Subject: [PATCH 2/2] docs(releases) #115: remove the repository changelog --- CHANGELOG.md | 272 --------------------------------------------------- 1 file changed, 272 deletions(-) delete mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index baae00e..0000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,272 +0,0 @@ -# Changelog - -## 1.3.0 (2026-09-22) - -### Added - -- Automatic themes follow the SVG viewer's light or dark preference, including saved SVG files and operand colours -- Automatic language selection follows the Python process environment and system locale, with explicit overrides -- English and Simplified Chinese catalogs cover visual labels, hover text, explanations, accessibility descriptions, and notebook controls -- New languages are discovered from UTF-8 JSON catalogs without adding a Python registry entry, and external catalogs can be loaded from a file or directory -- Regional language fallback, English fallback for missing messages, and validation of translation keys and placeholders -- A settings notebook and translation contribution guide -- A group colour notebook and architecture guide with reproducible index-query benchmarks -- Index result exploration with `explore(index, ...)`, preserving distinct repeated positions and reverse-slice order while highlighting the corresponding source element -- Static `index(..., focus=...)` comparisons with a one-source coordinate trace, bounded previews, and translated provenance text -- An index explorer notebook covering repeated gathers, reversed slices, scalar outputs, and empty results -- Explicit `Flow` recipes trace output elements across indexing, shape changes, combining, reductions, matmul, and einsum without materialising intermediate arrays -- Bounded provenance trees preserve repeated contribution paths, output ports, and each operation's arithmetic grouping -- Clickable result cells, Enter and Space activation, and coordinate controls explore individual operations or complete recorded flows -- Optional `focus` explains source coordinates for all shape-changing and combining views -- A worked operation-origins notebook and a reusable LLM prompt guide for beginner visualizations - -### Changed - -- The default theme and language are now `auto`, while explicit settings remain available -- Panel widths account for long captions and full-width characters -- Reduction groups and combining operands use colours generated from their logical IDs, with stable focus behaviour and paired light and dark paints -- SVG text and paint primitives, tensor drawing, and panel composition have separate modules while existing renderer imports remain available -- Equally shaped advanced-index arrays use direct candidate-set intersection, improving duplicate-gather previews without adding a native dependency -- Repeat uses a constant-size mapping for uniform counts and prefix boundaries for variable counts instead of allocating one source position per output copy -- Flow value requests plan recursive work before array reads, preserve live input values, and report skipped computation separately from structural truncation -- The interactive extra includes `anywidget` for result-cell activation while static rendering remains independent of widget imports -- The README now provides a complete entry point for installation, operation tracing, notebook interaction, supported APIs, and contributions -- Documentation builds use the package version directly and fail on import errors instead of showing an outdated fallback version - -## 1.2.0 (2026-09-22) - -### Added - -- Scalar sources with shape `()` and empty sources with zero-length dimensions work across shape views, indexing, transforms, arithmetic previews, and memory inspection -- Empty sums and contractions produce zero, empty means show NaN, and empty outputs have no trace or focus controls -- Scalar `take` indices remove an axis, and scalar inputs support `repeat`, `take`, stacking, and broadcasting -- A scalar and empty tensor notebook compares one value, zero values, empty contribution groups, and empty results -- `sum` and `mean` accept an omitted axis or `axis=None` for all axes, a tuple of axes, negative axes, and an empty tuple for an unchanged shape -- Keyword-only `keepdims=True` retains reduced axes at length one for broadcasting, with those result dimensions marked in the highlight colour -- Multi-axis focus identifies the full source group, reports the product of reduced dimensions as the mean divisor, and traces terms in source row-major order regardless of axis tuple order -- Reduction metadata records normalized axes, `keepdims`, and the number of source terms per output -- A beginner notebook connects reduction shapes to row normalisation, broadcasting, output traces, and optional interactive controls - -### Changed - -- Custom renderers receive scalar panel shapes and coordinates as `()` instead of the former `(1,)` display surrogate -- Empty previews avoid element reads and large coordinate or repeat allocations -- Multi-axis source selections and traces stay compact, including when the contributing group is too large to evaluate within the preview budgets -- CPU backend, interactive, and installed-distribution checks cover all-axis, multi-axis, and empty-axis reductions with and without retained dimensions - -### Fixed - -- Scalar sum and mean captions show the logical result shape `()` instead of the single-cell display shape `(1)` - -## 1.1.1 (2026-09-21) - -### Fixed - -- Axis arguments, reshape dimensions, repeat counts, and take indices consistently accept the integer index protocol and reject booleans or floating-point values before reading array data -- Iterable axis and count arguments are consumed once, and transpose accepts negative axes -- Advanced-index highlight queries use shared-coordinate lookups instead of repeatedly scanning duplicate candidates, with disconnected broadcast groups handled separately -- Source distributions include their SVG test fixtures and distribution checker - -### Added - -- `max_total_terms=100_000` limits the combined calculation cost of visible math outputs alongside the existing per-output `max_terms` limit -- CI verifies fresh wheel and source-distribution installations, including optional notebook controls, and release artifacts must pass these checks before publication - -### Changed - -- Math previews plan visible output work before reading values and show `?` for every output if either calculation limit is exceeded. Set both limits to `None` to remove them -- Evaluation metadata reports the planned output count, total terms, and exceeded limit, with `scope="per_output_cell_and_preview"` -- Custom renderers can choose different output coordinates within the planned cell count. Repeated computed values are cached, while excess or skipped requests return `?` without retaining their coordinates - -## 1.1.0 (2026-09-21) - -### Added - -- `index(..., show_result=True)` compares the source with the ordered result, including repeated picks, reverse slices, scalar results, and empty results -- `visual.index_mapping` resolves result positions back to the original source without constructing the indexed tensor -- `focus` and bounded coordinate traces explain one output of sum, mean, matmul, and einsum -- Optional `explore` notebook controls update a focused output and retain the latest static visual for SVG export -- `memory` reports available byte strides, contiguity, and ownership metadata -- CPU backend contract jobs validate PyTorch, JAX, and TensorFlow independently - -### Changed - -- Basic and advanced source selections are compact iterables. Explicit list conversion materializes coordinates. `index_mapping.result_count` counts output positions including repeats, while the lower-level `advanced_index` helper continues to return a list -- Reduction values are evaluated only when requested. `max_terms` defaults to 10,000 per output cell and skipped values appear as question marks -- Numerical previews explicitly describe Python scalar arithmetic and distinguish array values from generated placeholders. Backend accumulation dtype, rounding, and overflow can differ -- Shapes and basic indices accept the integer index protocol. Omitted trailing axes become full slices, and advanced indexing supports inserted `None` axes - -### Fixed - -- Ragged index lists are rejected instead of losing values, and empty boolean masks retain their type and axis consumption -- Advanced-index axis placement follows scalar, slice, and ellipsis ordering -- Einsum accepts singleton broadcasting across operands while keeping diagonal dimensions strict -- Valid zero-byte dtype metadata remains visible in memory explanations - -## 1.0.1 - -### Changed - -- Internal restructure, with no change to the public API. The shape maths and the display layer are each split by operation family into the `rainbow_tensor.ops` and `rainbow_tensor.views` packages (reshaping, reductions, combining, einsum, and shapes for the views), so `einsum` is no longer the only separated operation. Shared helpers moved into `shape.py` and `visual.py`, and the tests follow the same family layout. Every public import keeps working unchanged - -## 0.15.0 - -### Added - -- `moveaxis` draws moving one or more axes to new positions, the named axis movement operation between `transpose` and `swapaxes`. `source` and `destination` are an axis or a sequence of axes of equal length, the moved axes go to their destinations while the rest keep their order, and the result shape matches `numpy.moveaxis`. Each result axis keeps the colour of the source axis it came from, so the move stays traceable, and `transpose` and `swapaxes` are unchanged - -## 0.14.0 - -### Added - -- `repeat` draws repeating elements along one axis. A single count repeats every element, or one count per element repeats each by its own amount, matching `numpy.repeat`. Each source element and the adjacent run of copies it produces share one tint, so the result reads as materialised copies. This is the contrast with `broadcast`, which stretches a size one axis virtually without copying any values - -## 0.13.0 - -### Added - -- `take` draws a gather along one axis. A 1-D list of indices selects positions along the chosen axis, the result replaces that axis with the gathered positions, and the result shape matches `numpy.take`. Negative axes and negative indices both work. Each gathered source slice and the result slice it feeds share one tint, so repeated indices repeat a tint and reordered indices reorder them, making the gather visible. The existing advanced indexing behaviour is unchanged - -## 0.12.0 - -### Added - -- `matmul` draws a matrix multiplication `a @ b` as two operands and the output side by side. Vector, matrix, and batched matrix multiplication are all supported, with the result shape matching `numpy.matmul`. The shared inner axis is marked in the accent colour, and the row of the first operand and the column of the second operand that combine into the first output element are highlighted, so the contraction reads directly off the figure. The batch axes reuse the broadcast shape logic - -## 0.11.0 - -### Added - -- `squeeze` draws a tensor with its size one axes removed. With no axis it removes every size one axis, and an explicit axis or tuple removes only those, each of which must have size one, matching `numpy.squeeze`. The source panel marks the removed size one axes in the accent colour, every surviving axis keeps its source colour in the result, and the result shape matches NumPy -- `expand_dims` draws a tensor with a size one axis inserted. The axis is a position or tuple of positions in the result, with negative positions counting from the end, matching `numpy.expand_dims`. Each existing axis keeps its colour, the inserted size one axes are marked in the accent colour, and the result shape matches NumPy - -## 0.10.8 - -### Added - -- Einsum now parses ellipsis (`...`) notation. An ellipsis stands for the broadcast axes a subscript leaves unnamed, is expanded against the operand shapes and aligned from the right like NumPy, and supports both implicit and explicit output. The derived output shape matches NumPy, and an invalid ellipsis raises a clear error - -## 0.10.7 - -### Fixed - -- Einsum label colours now match between the figure and the caption. A leaf axis label used to be coloured only in the caption, and the contraction highlight dimmed unrelated frames, so the colours disagreed. A leaf axis now shows its label colour as the cell border and operand frames keep their colour - -### Changed - -- Einsum now colours every label by its role. Free, shared, and contracted labels are drawn from three distinct colour families, and each label keeps one colour across every operand caption, operand figure, and the output panel - -## 0.10.6 - -### Changed - -- A sum or mean result element now shares the same background as the source values that fold into it, so a result cell and its source group read as one unit. The first group stays highlighted on both panels, and the result shape and numeric result are unchanged - -## 0.10.5 - -### Changed - -- Sum and mean now colour the source values that fold into the same result element with one shared background and highlight the first group, so a reduction reads with the same clarity as the concatenate and stack views. The reduced axis, the result shape, and the numeric result are unchanged - -## 0.10.4 - -### Added - -- Advanced indexing now accepts per-axis boolean arrays. A boolean array on one or more consecutive axes acts like its nonzero integer arrays, mixes with slices and integer indices, and matches the NumPy selected coordinates and result shape. The full-shape boolean mask keeps working as before - -## 0.10.3 - -### Added - -- Advanced indexing now accepts multi-dimensional integer index arrays. Index arrays of any shape broadcast together, the gathered block takes the broadcast shape, and the selected coordinates and result shape match NumPy, including when a slice moves the block to the front - -## 0.10.2 - -### Added - -- A global axis colour scheme. `set_default_axis_colors` sets an axis colour ramp once for every later render, `get_default_axis_colors` reads it back, and `None` clears it. A per call `theme` still overrides the global scheme - -### Changed - -- The default axis colour ramp now spreads its hues so adjacent axes are easy to tell apart. After red and orange it jumps to lime and teal, then walks through blue, violet, and pink, in both the light and the dark theme - -## 0.10.1 - -### Fixed - -- Skipped tensor positions in a large preview now always draw the intended ellipsis glyphs. The horizontal, vertical, and midline ellipsis are defined with ASCII unicode escapes so a non-UTF-8 re-save of the source can no longer corrupt them into broken text - -## 0.10.0 - -### Added - -- Big tensor previews now respect a total visible cell budget, and selected positions stay visible when the preview budget allows it -- Explanation text now prints as standard notebook output and is available as `TensorVisual.text` - -## 0.9.0 - -### Added - -- Backend value adapters for NumPy style arrays plus Torch, JAX, and TensorFlow style scalar values -- A renderer registry with SVG as the default renderer and a public hook for later output backends -- A backend arrays notebook that runs even when optional backend libraries are not installed - -## 0.8.0 - -### Added - -- Einsum views parse subscripts, colour shared labels across operands, highlight labels that contract away, and show the derived output shape -- Swapaxes views draw the source and result side by side with the moved axes keeping their colours - -## 0.7.0 - -### Added - -- Shape changing views. Reshape draws the same values flowing from the old layout into the new one, transpose reorders the axes with each axis keeping its colour, and sum and mean collapse a chosen axis and mark the elements that fold into each result. The source and the result sit side by side in one figure through a new multi panel renderer -- Combining views. Concatenate joins operands along an existing axis and tints each operand so the seam is clear, and stack places operands onto a brand new axis. The result colours every cell by the operand it came from, and a mismatch in the operand shapes raises a clear error -- Broadcasting views. Broadcast stretches a smaller operand to match a larger one, drawing each operand in its own shape and again stretched to the common shape so the repeated values show, and marking every stretched axis in the accent colour. Incompatible shapes raise a clear error -- Render tensors of any rank. Frames nest to arbitrary depth, alternating across and down so a 4D or 5D tensor stays compact, with per axis truncation at every level -- Advanced indexing. A full-shape boolean mask highlights every True position, and integer index arrays highlight the gathered coordinates. The result shape matches NumPy for mixed basic and advanced indexing, including moving the gathered axis to the front when a slice separates the advanced axes - -### Changed - -- Renamed `show_shape` to `shape` and `show_index` to `index`. The first argument is an array-like object whose own values are rendered, or a shape tuple as before - -## 0.2.1 - -### Fixed - -- show_shape and show_index no longer render twice in a notebook cell. The result renders once through _repr_svg_ and is still returned for inspection -- The SVG width now grows to fit the label and explanation lines, so longer text is no longer clipped at the edge of the frame - -## 0.2.0 - -### Changed - -- Redesigned the visualisation so each axis has its own colour, with axis 0 frames red, axis 1 frames orange, and selected leaf values green -- The shape label numbers and the index label tokens are now coloured to match the frames and the selected values -- In an index view the unselected frames and values are drawn in a neutral dark tone instead of being faded - -## 0.1.0 - -First version. - -### Added - -- Static SVG visualisation of tensor shape for 1D, 2D, and 3D tensors through `show_shape` -- Static SVG visualisation of basic indexing through `show_index`, with highlighted selections and de-emphasised context -- A plain text explanation of the result shape and of which axes are kept or removed -- Support for shape tuples and array-like objects with a `.shape` attribute, including NumPy arrays -- Integer indexing and basic slicing with `slice(None)`, `slice(start, stop)`, and `slice(start, stop, step)` - -### Known limitations - -- Interactive widgets are not included in this version -- Advanced indexing, boolean masks, `None`, `newaxis`, and ellipsis are not supported -- Tensors of 4D or higher are not supported - -### Planned next steps - -- Interactive controls in a notebook widget -- Higher dimensional layouts -- Optional support for more tensor libraries through shape duck typing