fn_manifests
def fn_manifests()->dict:The fn manifests, by name.
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.
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.
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.
Each block folder has its code, its style, its manifest and one example.
The fns are rules, rrf, softmax, lookup, expr, and four small helpers.
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 whole demo spec.
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.
Predict, then reveal: blocks with after=id wait for this gate.
The derive builders name a fn and the key it writes (out).
A small demo: two numbers, a gate on their sum, and a readout that waits for the gate.
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'}])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 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 turns a math error into the value JavaScript gives, so both sides agree: nan or an infinity.
parse_expr returns a function of a dict of values, and the set of names that the formula reads.
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.
Names that reach into objects, calls of unknown functions, and a second statement are refused.
Each fn has a Python twin with the same result. 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 reads a path such as fused.0.id, and show_value writes a value as the runtime does.
Fill {path} and {path:2} from the state, as the runtime does.
A value as the runtime shows it: numbers with at most d (default 4) digits and a true minus sign.
Read a path such as fused.0.id in dicts and lists; own keys only, as the runtime does.
Now the fns. rules orders its alts with the same small hash as the runtime, so both sides give the same options.
rrf, softmax and lookup follow the runtime line by line.
expr gives a number, or a series of points with over. The small helpers finish the set.
run gives the opening state of a demo: the state, then each derive in order. Each gate starts open.
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.
softmax at a high temperature makes the shares more equal.
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:
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.context.First, the prop types. _reads gives the keys that a value of a type reads, so the think rule can follow them.
_props checks the props of one block or one derive entry against its manifest. It returns the keys that the entry reads.
_walk visits every block with its path and its parent, in reading order.
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:
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.
ASD-STE100 asks for sentences of at most 20 words. This cheap check counts the words in each sentence of each text prop.
The sentences of s that have more than MAX_WORDS words.
Now validate puts the checks together.
The errors of spec, each as path: sentence. An empty list means the spec is good.
_check_blocks does the block checks, the gate checks and the think rule.
_check_gate checks one gate: its answer, its options, its context, and the think rule for its answer.
A good demo has no errors.
A readout of the sum before the gate fails the think rule, with the path of the readout.
An unknown block, an unknown fn, an unknown prop and a missing prop each fail with a path.
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.
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.
A live control that changes the answer needs a context on the gate, so the verdict names its inputs.
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.
A block inside a stack that waits for the gate passes, because the stack waits.
A fixed answer names the keys that give it away in hides.
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.
Each sentence has at most 20 words.
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.
Each block’s example.json must pass validate. The browser tests draw each one.
Hand-made demos live in rupaka/demos/. The agent shows two of them to the model as examples, and abhyasa can import them.
Each gallery demo passes validate.