# spec


<!-- WARNING: THIS FILE WAS AUTOGENERATED! DO NOT EDIT! -->

A demo is one JSON object: the spec. It lists blocks, a small state,
derived values and data tables. This module reads the block and fn
manifests, gives a builder for each block, and checks a spec with
[`validate`](https://vedicreader.github.io/rupaka/spec.html#validate).

[`validate`](https://vedicreader.github.io/rupaka/spec.html#validate)
enforces the think rule: a block that shows a value which a gate’s
answer depends on must wait for that gate. So the learner always
predicts before the demo shows the result.

## Manifests

Each block is a folder in `rupaka/blocks/`, with a `block.json`
manifest. Each fn has a manifest in `rupaka/fns/`. A manifest gives the
purpose in one line, the props with their types, and the keys. The
validator and the agent prompt read the manifests, so they never drift
from the code.

------------------------------------------------------------------------

<a
href="https://github.com/vedicreader/rupaka/blob/main/rupaka/spec.py#L33"
target="_blank" style="float:right; font-size:smaller">source</a>

### fn_manifests

``` python
def fn_manifests()->dict:
```

*The fn manifests, by name.*

------------------------------------------------------------------------

<a
href="https://github.com/vedicreader/rupaka/blob/main/rupaka/spec.py#L28"
target="_blank" style="float:right; font-size:smaller">source</a>

### manifests

``` python
def manifests()->dict:
```

*The block manifests, by name.*

Each block folder has its code, its style, its manifest and one example.

``` python
test_eq(set(manifests()), {'axis', 'bars', 'caption', 'choice', 'gate', 'grid', 'plot', 'point', 'ranklist', 'readout', 'row', 'slider', 'stack', 'toggle', 'wheel'})
for n in manifests(): assert all((PKG/'blocks'/n/f).exists() for f in (f'{n}.js', f'{n}.css', 'example.json')), n
```

The fns are
[`rules`](https://vedicreader.github.io/rupaka/spec.html#rules),
[`rrf`](https://vedicreader.github.io/rupaka/spec.html#rrf),
[`softmax`](https://vedicreader.github.io/rupaka/spec.html#softmax),
[`lookup`](https://vedicreader.github.io/rupaka/spec.html#lookup),
[`expr`](https://vedicreader.github.io/rupaka/spec.html#expr), and four
small helpers.

``` python
test_eq(set(fn_manifests()), {'expr', 'fmt', 'ids', 'lookup', 'pick', 'rrf', 'rules', 'softmax', 'sort'})
```

## Builders

A builder makes the dict for one block, so a notebook author does not
type JSON. A prop with the value `None` is left out.

------------------------------------------------------------------------

<a
href="https://github.com/vedicreader/rupaka/blob/main/rupaka/spec.py#L48"
target="_blank" style="float:right; font-size:smaller">source</a>

### demo

``` python
def demo(
    id:str, title:str='', ask:str='', state:dict=None, derive:tuple=(), blocks:tuple=(), tables:dict=None,
    concept:str=None
)->dict:
```

*A whole demo spec.*

------------------------------------------------------------------------

<a
href="https://github.com/vedicreader/rupaka/blob/main/rupaka/spec.py#L44"
target="_blank" style="float:right; font-size:smaller">source</a>

### is\_

``` python
def is_(
    v
)->dict:
```

*A fixed gate answer.*

------------------------------------------------------------------------

<a
href="https://github.com/vedicreader/rupaka/blob/main/rupaka/spec.py#L40"
target="_blank" style="float:right; font-size:smaller">source</a>

### from\_

``` python
def from_(
    path:str
)->dict:
```

*Gate options read from a list in the state, for example
`from_('join.alts')`.*

These are the builders for the blocks. Each one takes the props of its
manifest.

------------------------------------------------------------------------

<a
href="https://github.com/vedicreader/rupaka/blob/main/rupaka/spec.py#L72"
target="_blank" style="float:right; font-size:smaller">source</a>

### grid

``` python
def grid(
    bind, label:NoneType=None, **kw
):
```

------------------------------------------------------------------------

<a
href="https://github.com/vedicreader/rupaka/blob/main/rupaka/spec.py#L71"
target="_blank" style="float:right; font-size:smaller">source</a>

### plot

``` python
def plot(
    series, **kw
):
```

------------------------------------------------------------------------

<a
href="https://github.com/vedicreader/rupaka/blob/main/rupaka/spec.py#L70"
target="_blank" style="float:right; font-size:smaller">source</a>

### point

``` python
def point(
    bind:NoneType=None, **kw
):
```

------------------------------------------------------------------------

<a
href="https://github.com/vedicreader/rupaka/blob/main/rupaka/spec.py#L69"
target="_blank" style="float:right; font-size:smaller">source</a>

### axis

``` python
def axis(
    x, *blocks, y:NoneType=None, **kw
):
```

------------------------------------------------------------------------

<a
href="https://github.com/vedicreader/rupaka/blob/main/rupaka/spec.py#L68"
target="_blank" style="float:right; font-size:smaller">source</a>

### bars

``` python
def bars(
    bind, label:NoneType=None, **kw
):
```

------------------------------------------------------------------------

<a
href="https://github.com/vedicreader/rupaka/blob/main/rupaka/spec.py#L67"
target="_blank" style="float:right; font-size:smaller">source</a>

### ranklist

``` python
def ranklist(
    bind, label:NoneType=None, **kw
):
```

------------------------------------------------------------------------

<a
href="https://github.com/vedicreader/rupaka/blob/main/rupaka/spec.py#L66"
target="_blank" style="float:right; font-size:smaller">source</a>

### wheel

``` python
def wheel(
    bind, items, label:NoneType=None, **kw
):
```

------------------------------------------------------------------------

<a
href="https://github.com/vedicreader/rupaka/blob/main/rupaka/spec.py#L65"
target="_blank" style="float:right; font-size:smaller">source</a>

### choice

``` python
def choice(
    bind, options, label:NoneType=None, **kw
):
```

------------------------------------------------------------------------

<a
href="https://github.com/vedicreader/rupaka/blob/main/rupaka/spec.py#L64"
target="_blank" style="float:right; font-size:smaller">source</a>

### toggle

``` python
def toggle(
    bind, label:NoneType=None, **kw
):
```

------------------------------------------------------------------------

<a
href="https://github.com/vedicreader/rupaka/blob/main/rupaka/spec.py#L63"
target="_blank" style="float:right; font-size:smaller">source</a>

### slider

``` python
def slider(
    bind, label:NoneType=None, min:NoneType=None, max:NoneType=None, step:NoneType=None, **kw
):
```

------------------------------------------------------------------------

<a
href="https://github.com/vedicreader/rupaka/blob/main/rupaka/spec.py#L62"
target="_blank" style="float:right; font-size:smaller">source</a>

### stack

``` python
def stack(
    *blocks, **kw
):
```

------------------------------------------------------------------------

<a
href="https://github.com/vedicreader/rupaka/blob/main/rupaka/spec.py#L61"
target="_blank" style="float:right; font-size:smaller">source</a>

### row

``` python
def row(
    *blocks, **kw
):
```

------------------------------------------------------------------------

<a
href="https://github.com/vedicreader/rupaka/blob/main/rupaka/spec.py#L60"
target="_blank" style="float:right; font-size:smaller">source</a>

### caption

``` python
def caption(
    text, label:NoneType=None, **kw
):
```

------------------------------------------------------------------------

<a
href="https://github.com/vedicreader/rupaka/blob/main/rupaka/spec.py#L59"
target="_blank" style="float:right; font-size:smaller">source</a>

### readout

``` python
def readout(
    text, label:NoneType=None, **kw
):
```

------------------------------------------------------------------------

<a
href="https://github.com/vedicreader/rupaka/blob/main/rupaka/spec.py#L55"
target="_blank" style="float:right; font-size:smaller">source</a>

### gate

``` python
def gate(
    id, prompt, options, answer, why:NoneType=None, context:NoneType=None, again:NoneType=None, hides:NoneType=None
):
```

*Predict, then reveal: blocks with `after=id` wait for this gate.*

The derive builders name a fn and the key it writes (`out`).

------------------------------------------------------------------------

<a
href="https://github.com/vedicreader/rupaka/blob/main/rupaka/spec.py#L80"
target="_blank" style="float:right; font-size:smaller">source</a>

### fmt

``` python
def fmt(
    text, out
):
```

------------------------------------------------------------------------

<a
href="https://github.com/vedicreader/rupaka/blob/main/rupaka/spec.py#L79"
target="_blank" style="float:right; font-size:smaller">source</a>

### expr

``` python
def expr(
    f, out, over:NoneType=None
):
```

------------------------------------------------------------------------

<a
href="https://github.com/vedicreader/rupaka/blob/main/rupaka/spec.py#L78"
target="_blank" style="float:right; font-size:smaller">source</a>

### lookup

``` python
def lookup(
    table, key, out, by:NoneType=None
):
```

------------------------------------------------------------------------

<a
href="https://github.com/vedicreader/rupaka/blob/main/rupaka/spec.py#L77"
target="_blank" style="float:right; font-size:smaller">source</a>

### softmax

``` python
def softmax(
    src, out, t:NoneType=None, labels:NoneType=None, sort:NoneType=None
):
```

------------------------------------------------------------------------

<a
href="https://github.com/vedicreader/rupaka/blob/main/rupaka/spec.py#L76"
target="_blank" style="float:right; font-size:smaller">source</a>

### rrf

``` python
def rrf(
    ins, k, out
):
```

------------------------------------------------------------------------

<a
href="https://github.com/vedicreader/rupaka/blob/main/rupaka/spec.py#L75"
target="_blank" style="float:right; font-size:smaller">source</a>

### rules

``` python
def rules(
    table, ins, out, alts:int=4
):
```

A small demo: two numbers, a gate on their sum, and a readout that waits
for the gate.

``` python
d = demo('sum', 'Sum', state=dict(a=2, b=3), derive=[expr('a + b', 'sum')],
         blocks=[gate('s', 'What is {a} + {b}?', [4, 5, 6], 'sum'), readout('{a} + {b} = {sum}', after='s')])
test_eq(d['blocks'][1], {'type': 'readout', 'text': '{a} + {b} = {sum}', 'after': 's'})
test_eq(d['derive'], [{'fn': 'expr', 'expr': 'a + b', 'out': 'sum'}])
```

## The expr grammar

[`expr`](https://vedicreader.github.io/rupaka/spec.html#expr) holds a
formula in a tiny safe grammar: numbers, names, `+ - * / ^`, parentheses
and a few math functions. The runtime parses it in JavaScript. This is
the same parser in Python, so
[`validate`](https://vedicreader.github.io/rupaka/spec.html#validate)
can check a formula and run it. There is no `eval` in either one. A name
must be a state key, a constant or one of the functions.

[`_safe`](https://vedicreader.github.io/rupaka/spec.html#_safe) turns a
math error into the value JavaScript gives, so both sides agree: `nan`
or an infinity.

[`parse_expr`](https://vedicreader.github.io/rupaka/spec.html#parse_expr)
returns a function of a dict of values, and the set of names that the
formula reads.

------------------------------------------------------------------------

<a
href="https://github.com/vedicreader/rupaka/blob/main/rupaka/spec.py#L113"
target="_blank" style="float:right; font-size:smaller">source</a>

### parse_expr

``` python
def parse_expr(
    src:str
):
```

*Parse a formula. Return `(f, names)`: `f(env)` gives a number, and
`names` are the values it reads.*

`2*x^2 - 1` at x = 3 is 17. The power binds before the minus sign, so
`-x^2` at x = 3 is −9.

``` python
f, ns = parse_expr('2*x^2 - 1')
test_eq(f(dict(x=3)), 17)
test_eq(ns, {'x'})
test_eq(parse_expr('-x^2')[0](dict(x=3)), -9)
test_eq(parse_expr('max(a, b) + floor(7/2)')[0](dict(a=1, b=4)), 7)
```

Names that reach into objects, calls of unknown functions, and a second
statement are refused.

``` python
for bad in ('constructor', '__proto__', 'alert(1)', 'x;y', 'x.y', '2 +'): test_fail(lambda: parse_expr(bad)[0](dict(x=1, y=2)), contains='expr')
```

## The fns in Python

Each fn has a Python twin with the same result.
[`validate`](https://vedicreader.github.io/rupaka/spec.html#validate)
runs the derive list on the opening state with these twins, so it can
check that each gate answer is one of the options. The browser tests
compare the two sides.

First, two helpers that copy the runtime:
[`get`](https://vedicreader.github.io/rupaka/spec.html#get) reads a path
such as `fused.0.id`, and
[`show_value`](https://vedicreader.github.io/rupaka/spec.html#show_value)
writes a value as the runtime does.

------------------------------------------------------------------------

<a
href="https://github.com/vedicreader/rupaka/blob/main/rupaka/spec.py#L194"
target="_blank" style="float:right; font-size:smaller">source</a>

### fmt_text

``` python
def fmt_text(
    s, st
)->str:
```

*Fill `{path}` and `{path:2}` from the state, as the runtime does.*

------------------------------------------------------------------------

<a
href="https://github.com/vedicreader/rupaka/blob/main/rupaka/spec.py#L185"
target="_blank" style="float:right; font-size:smaller">source</a>

### show_value

``` python
def show_value(
    v, d:NoneType=None
)->str:
```

*A value as the runtime shows it: numbers with at most `d` (default 4)
digits and a true minus sign.*

------------------------------------------------------------------------

<a
href="https://github.com/vedicreader/rupaka/blob/main/rupaka/spec.py#L173"
target="_blank" style="float:right; font-size:smaller">source</a>

### js_str

``` python
def js_str(
    v
)->str:
```

*`String(v)` in JavaScript, for the values a spec holds.*

------------------------------------------------------------------------

<a
href="https://github.com/vedicreader/rupaka/blob/main/rupaka/spec.py#L164"
target="_blank" style="float:right; font-size:smaller">source</a>

### get

``` python
def get(
    o, path
):
```

*Read a path such as `fused.0.id` in dicts and lists; own keys only, as
the runtime does.*

``` python
st = dict(fused=[dict(id='B', value=0.0328)], k=-60, xs=[1, 2])
test_eq(get(st, 'fused.0.id'), 'B')
test_eq(get(st, 'fused.0.__proto__'), None)
test_eq(fmt_text('{fused.0.id} {fused.0.value:2} {k} {xs}', st), 'B 0.03 −60 1, 2')
```

Now the fns.
[`rules`](https://vedicreader.github.io/rupaka/spec.html#rules) orders
its `alts` with the same small hash as the runtime, so both sides give
the same options.

[`rrf`](https://vedicreader.github.io/rupaka/spec.html#rrf),
[`softmax`](https://vedicreader.github.io/rupaka/spec.html#softmax) and
[`lookup`](https://vedicreader.github.io/rupaka/spec.html#lookup) follow
the runtime line by line.

[`expr`](https://vedicreader.github.io/rupaka/spec.html#expr) gives a
number, or a series of points with `over`. The small helpers finish the
set.

[`run`](https://vedicreader.github.io/rupaka/spec.html#run) gives the
opening state of a demo: the state, then each derive in order. Each gate
starts open.

------------------------------------------------------------------------

<a
href="https://github.com/vedicreader/rupaka/blob/main/rupaka/spec.py#L274"
target="_blank" style="float:right; font-size:smaller">source</a>

### run

``` python
def run(
    spec, state:dict=None
)->dict:
```

*The state of `spec` after the derive list, from its opening state or
from `state`.*

The fused scores of the two spike lists at k = 60: B is first, because
it is in both lists.

``` python
sp = dict(state=dict(bm25=list('ACEB'), vec=list('DFGB'), k=60), derive=[rrf(['bm25', 'vec'], 'k', 'fused')], blocks=[])
st = run(sp)
test_eq(st['fused'][0]['id'], 'B')
test_close(st['fused'][0]['value'], 1/64 + 1/64)
test_eq(run(sp, {**sp['state'], 'k': 1})['fused'][0]['id'], 'A')
```

[`softmax`](https://vedicreader.github.io/rupaka/spec.html#softmax) at a
high temperature makes the shares more equal.

``` python
sp = dict(state=dict(z=[2, 1, 0]), derive=[softmax('z', 'p', t=1), softmax('z', 'q', t=2)], blocks=[])
st = run(sp)
test_close(st['p'][0]['value'], 0.6652, eps=1e-4)
assert st['q'][0]['value'] < st['p'][0]['value']
```

## validate

`validate(spec)` returns a list of errors. Each error has a path and one
sentence, for example
`blocks[3].bind: "fused" is not in state or derive`. An empty list means
the spec is good. It checks, in order:

1.  The shape: the schema number, the top-level keys, and a list of
    blocks.
2.  Each derive: the fn exists, its props have the right types, and it
    reads only keys that come before it.
3.  Each block: the type exists, its props have the right types, the
    required props are there, and each `bind` names a key. Each `after`
    and each `lock` names a gate, and no gate waits for itself. A block
    above its gate can wait for it, for example a mark on a figure.
4.  Each gate: its answer resolves on the opening state and is one of
    its options. A gate whose answer a live control can change names its
    inputs with `context`.
5.  The think rule (below).
6.  Text: each sentence has at most 20 words.

First, the prop types.
[`_reads`](https://vedicreader.github.io/rupaka/spec.html#_reads) gives
the keys that a value of a type reads, so the think rule can follow
them.

[`_props`](https://vedicreader.github.io/rupaka/spec.html#_props) checks
the props of one block or one derive entry against its manifest. It
returns the keys that the entry reads.

[`_walk`](https://vedicreader.github.io/rupaka/spec.html#_walk) visits
every block with its path and its parent, in reading order.

### The think rule

The answer of a gate has a root key: for `fused.0.id`, it is `fused`. A
block must wait for the gate when it reads a key that gives the answer
away:

- the root key itself;
- each derived key that the root is computed from (raw inputs are the
  question, so they stay visible);
- each derived key that is computed from any of these.

A fixed answer (`{"is": ...}`) has no root key, so the gate names the
keys that give it away in `hides`. A block waits for the gate when it,
or a block that holds it, has `after` set to that gate, or to a gate
that itself waits for it.

### Text

ASD-STE100 asks for sentences of at most 20 words. This cheap check
counts the words in each sentence of each text prop.

------------------------------------------------------------------------

<a
href="https://github.com/vedicreader/rupaka/blob/main/rupaka/spec.py#L376"
target="_blank" style="float:right; font-size:smaller">source</a>

### long_sentences

``` python
def long_sentences(
    s:str
)->list:
```

*The sentences of `s` that have more than `MAX_WORDS` words.*

``` python
test_eq(long_sentences('Short one. ' + 'word ' * 21 + 'end.'), ['word ' * 21 + 'end.'])
```

### validate

Now
[`validate`](https://vedicreader.github.io/rupaka/spec.html#validate)
puts the checks together.

------------------------------------------------------------------------

<a
href="https://github.com/vedicreader/rupaka/blob/main/rupaka/spec.py#L384"
target="_blank" style="float:right; font-size:smaller">source</a>

### validate

``` python
def validate(
    spec
)->fastcore.foundation.L:
```

*The errors of `spec`, each as `path: sentence`. An empty list means the
spec is good.*

[`_check_blocks`](https://vedicreader.github.io/rupaka/spec.html#_check_blocks)
does the block checks, the gate checks and the think rule.

[`_check_gate`](https://vedicreader.github.io/rupaka/spec.html#_check_gate)
checks one gate: its answer, its options, its context, and the think
rule for its answer.

### Tests for validate

A good demo has no errors.

``` python
good = demo('sum', 'Sum', state=dict(a=2, b=3), derive=[expr('a + b', 'sum')],
            blocks=[gate('s', 'What is {a} + {b}?', [4, 5, 6], 'sum'), readout('{a} + {b} = {sum}', after='s')])
test_eq(validate(good), [])
```

A readout of the sum before the gate fails the think rule, with the path
of the readout.

``` python
bad = deepcopy(good); del bad['blocks'][1]['after']
test_eq(validate(bad), ['blocks[1]: this readout shows "sum", which gives away the answer of gate "s"; add "after": "s"'])
```

An unknown block, an unknown fn, an unknown prop and a missing prop each
fail with a path.

``` python
def errs_of(**kw): return validate({**deepcopy(good), **kw})
test_eq(errs_of(blocks=[*good['blocks'], dict(type='dial')])[0][:23], 'blocks[2].type: "dial" ')
assert errs_of(derive=[dict(fn='sum', out='sum')])[0].startswith('derive[0].fn: "sum" is not a fn')
assert errs_of(blocks=[good['blocks'][0], {**good['blocks'][1], 'colour': 'red'}])[0].startswith('blocks[1].colour: is not a prop of readout')
assert 'blocks[1]: needs "text"' in validate({**good, 'blocks': [good['blocks'][0], dict(type='readout', after='s')]})[0]
```

A bind to a key that is not in the state fails. So does an `after` that
names no gate.

``` python
assert 'blocks[2].bind: "k" is not in state or derive' in errs_of(blocks=[*good['blocks'], slider('k')])
test_eq(errs_of(blocks=[readout('{sum}', after='t'), good['blocks'][0]]), ['blocks[0].after: "t" is not a gate', 'blocks[0]: this readout shows "sum", which gives away the answer of gate "s"; add "after": "s"'])
```

A gate answer must be one of its options. A demo needs a gate.

``` python
test_eq(errs_of(blocks=[gate('s', 'Sum?', [1, 2], 'sum')]), ['blocks[0].answer: the answer "5" is not one of the options [\'1\', \'2\']'])
test_eq(errs_of(blocks=[readout('{a}')]), ['blocks: needs at least one gate: the learner predicts first'])
```

A live control that changes the answer needs a `context` on the gate, so
the verdict names its inputs.

``` python
live = errs_of(blocks=[slider('a', lock='s'), *good['blocks']])
test_eq(live, ['blocks[1].context: the slider at blocks[0] changes the answer, so name the inputs, for example "For {a}:"'])
ok = deepcopy(good); ok['blocks'] = [slider('a', lock='s'), {**good['blocks'][0], 'context': 'For {a} + {b}:'}, good['blocks'][1]]
test_eq(validate(ok), [])
```

The think rule follows derived keys both ways. Here `twice` is computed
from the answer `sum`, so a readout of `twice` must wait too.

``` python
t = deepcopy(good); t['derive'].append(expr('2 * sum', 'twice')); t['blocks'].append(readout('{twice}'))
test_eq(validate(t), ['blocks[2]: this readout shows "twice", which gives away the answer of gate "s"; add "after": "s"'])
```

A block inside a
[`stack`](https://vedicreader.github.io/rupaka/spec.html#stack) that
waits for the gate passes, because the stack waits.

``` python
t['blocks'][2] = stack(readout('{twice}'), after='s')
test_eq(validate(t), [])
```

A fixed answer names the keys that give it away in `hides`.

``` python
h = demo('h', state=dict(m=1), derive=[expr('m * x', 'line', over=dict(x=[-1, 1, 3]))],
         blocks=[gate('g', 'Steeper or flatter?', ['steeper', 'flatter'], is_('steeper'), hides=['line']),
                 axis([-1, 1], plot('line'), y=[-3, 3])])
test_eq(validate(h), ['blocks[1].blocks[0]: this plot shows "line", which gives away the answer of gate "g"; add "after": "g"'])
```

A plain-text prop, such as `ask` or a label, cannot hold `{path}`: the
runtime would show the braces.

``` python
assert errs_of(ask='The list has {a} items.')[0].startswith('ask: is plain text')
assert errs_of(blocks=[*good['blocks'], readout('{a}', label='a is {a}')])[0].startswith('blocks[2].label: is plain text')
```

Each sentence has at most 20 words.

``` python
assert 'more than 20 words' in errs_of(ask=' '.join(['word'] * 25) + '.')[0]
```

## upgrade

`upgrade(spec)` moves a spec from schema N−1 to N. Schema 1 is the
first, so today the step is the identity. The frame and the test are in
place for schema 2.

------------------------------------------------------------------------

<a
href="https://github.com/vedicreader/rupaka/blob/main/rupaka/spec.py#L487"
target="_blank" style="float:right; font-size:smaller">source</a>

### upgrade

``` python
def upgrade(
    spec:dict
)->dict:
```

*A copy of `spec` at the current schema.*

``` python
test_eq(upgrade({**good, 'rupaka': 0}), good)
test_fail(lambda: upgrade({**good, 'rupaka': 9}), contains='newer')
```

## The block examples

Each block’s `example.json` must pass
[`validate`](https://vedicreader.github.io/rupaka/spec.html#validate).
The browser tests draw each one.

------------------------------------------------------------------------

<a
href="https://github.com/vedicreader/rupaka/blob/main/rupaka/spec.py#L497"
target="_blank" style="float:right; font-size:smaller">source</a>

### examples

``` python
def examples()->dict:
```

*The example spec of each block, by block name.*

``` python
for n, ex in examples().items(): test_eq((n, validate(ex)), (n, []))
```

## The gallery

Hand-made demos live in `rupaka/demos/`. The agent shows two of them to
the model as examples, and abhyasa can import them.

------------------------------------------------------------------------

<a
href="https://github.com/vedicreader/rupaka/blob/main/rupaka/spec.py#L502"
target="_blank" style="float:right; font-size:smaller">source</a>

### gallery

``` python
def gallery()->dict:
```

*The gallery demos, by id.*

Each gallery demo passes
[`validate`](https://vedicreader.github.io/rupaka/spec.html#validate).

``` python
test_eq({k: list(validate(s)) for k, s in gallery().items()}, {k: [] for k in gallery()})
assert {'vowel-sandhi', 'reciprocal-rank-fusion', 'softmax-temperature', 'nakshatra-pada', 'parabola-vertex'} <= set(gallery())
```
