Newn 1.0.1__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.
- newn-1.0.1/LICENSE +21 -0
- newn-1.0.1/MANIFEST.in +4 -0
- newn-1.0.1/NEWN_README.md +554 -0
- newn-1.0.1/Newn.egg-info/PKG-INFO +586 -0
- newn-1.0.1/Newn.egg-info/SOURCES.txt +21 -0
- newn-1.0.1/Newn.egg-info/dependency_links.txt +1 -0
- newn-1.0.1/Newn.egg-info/entry_points.txt +2 -0
- newn-1.0.1/Newn.egg-info/top_level.txt +1 -0
- newn-1.0.1/PKG-INFO +586 -0
- newn-1.0.1/newn/__init__.py +87 -0
- newn-1.0.1/newn/__main__.py +46 -0
- newn-1.0.1/newn/backchain.py +323 -0
- newn-1.0.1/newn/core.py +2164 -0
- newn-1.0.1/newn/errors.py +23 -0
- newn-1.0.1/newn/export.py +772 -0
- newn-1.0.1/newn/patterns.py +680 -0
- newn-1.0.1/newn/preprocessor.py +2566 -0
- newn-1.0.1/newn/safety.py +257 -0
- newn-1.0.1/newn/safety_export.py +164 -0
- newn-1.0.1/newn/safety_serve.py +136 -0
- newn-1.0.1/pyproject.toml +55 -0
- newn-1.0.1/setup.cfg +4 -0
- newn-1.0.1/setup.py +26 -0
newn-1.0.1/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Absence Calculus Project
|
|
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.
|
newn-1.0.1/MANIFEST.in
ADDED
|
@@ -0,0 +1,554 @@
|
|
|
1
|
+
# Newn
|
|
2
|
+
|
|
3
|
+
**Define new mathematical systems in plain text. Reason over them bidirectionally.**
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
pip install newn
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
Newn is a Python library for building custom symbolic rule systems — algebras, type systems, abstract number domains, game logic, formal grammars — using a clean declarative file format called `.nn`. You state what is true. Newn enforces it, inverts it, and exports it.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Why Newn?
|
|
14
|
+
|
|
15
|
+
Most languages let you *compute*. Newn lets you *declare what a system is* — and then query that system from any direction.
|
|
16
|
+
|
|
17
|
+
- **No imperative code.** Every op is defined by axioms, not procedures.
|
|
18
|
+
- **Bidirectional by default.** Every rule system is automatically reversible — find what inputs produce a given output without writing inverse functions.
|
|
19
|
+
- **Multi-result by design.** When multiple axioms match, you get all results. Ambiguity is data, not a bug.
|
|
20
|
+
- **Semantically typed values.** Props wrap values with meaning, not just types — `absent(5)` and `star(5)` are different objects in your system's algebra even though both contain `5`.
|
|
21
|
+
- **Export to four formats.** The same system runs in Python, exports to Prolog, Z3, and PDDL with no rewriting.
|
|
22
|
+
|
|
23
|
+
## Installation
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
pip install newn
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Requires Python 3.8 or later. No mandatory dependencies — pure Python.
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## Main Concepts
|
|
34
|
+
|
|
35
|
+
### Props — semantic wrappers
|
|
36
|
+
|
|
37
|
+
A **prop** wraps a value and gives it a named identity within your system. Two props with the same inner value are different objects — the prop name is part of the identity, not just metadata.
|
|
38
|
+
|
|
39
|
+
```
|
|
40
|
+
prop absent: None
|
|
41
|
+
prop star: None
|
|
42
|
+
prop quantum: None
|
|
43
|
+
|
|
44
|
+
a = absent(5) # PropValue('absent', 5)
|
|
45
|
+
b = star(5) # PropValue('star', 5)
|
|
46
|
+
c = quantum(5) # PropValue('quantum', 5)
|
|
47
|
+
|
|
48
|
+
# a, b, and c are three different values in your system
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Props can nest:
|
|
52
|
+
|
|
53
|
+
```
|
|
54
|
+
deep = absent(star(3)) # PropValue('absent', PropValue('star', 3))
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
### Ops — operations defined entirely by axioms
|
|
60
|
+
|
|
61
|
+
An **op** has no built-in meaning. It only does what its axioms say. With no matching axiom, it raises `NewnError`.
|
|
62
|
+
|
|
63
|
+
```
|
|
64
|
+
op combine: None
|
|
65
|
+
op scale: None
|
|
66
|
+
op project: None
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Binary ops use infix notation.
|
|
70
|
+
|
|
71
|
+
```
|
|
72
|
+
result = absent(3) combine star(4) # infix
|
|
73
|
+
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
### Axioms — the rules of your system
|
|
79
|
+
|
|
80
|
+
Axioms define what ops do. Each axiom fires when its pattern matches the call's arguments.
|
|
81
|
+
|
|
82
|
+
```
|
|
83
|
+
axiom plain_add: [for all x, for all y], x add y = x + y
|
|
84
|
+
axiom absent_add: [for all x, for all absent(y)], x add absent(y) = absent(x + y)
|
|
85
|
+
axiom star_add: [for all x, for all star(y)], x add star(y) = star(x + y)
|
|
86
|
+
```
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
### Categories — named domains
|
|
90
|
+
|
|
91
|
+
A **category** is a named set of values defined by a condition. Use it to scope axioms to a subset of inputs.
|
|
92
|
+
|
|
93
|
+
```
|
|
94
|
+
category small: [for all x in range(0, 10)], absent(x)
|
|
95
|
+
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Then constrain axiom variables to categories:
|
|
99
|
+
|
|
100
|
+
```
|
|
101
|
+
axiom discount: [for all x in small, for all y], x add y = absent(y)
|
|
102
|
+
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
**Prop-union categories** combine multiple props:
|
|
106
|
+
|
|
107
|
+
```
|
|
108
|
+
category typed_val: [for all x], absent(x) and star(x) # values that are absent OR star wrapped
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
**Unresolved categories** — categories defined by the output of an op, enabling higher-order dispatch:
|
|
112
|
+
|
|
113
|
+
```
|
|
114
|
+
axiom double: [for all x, for all y], double(x) add y = y add double(x)
|
|
115
|
+
|
|
116
|
+
category result_of_double: [for all x], double(x) add y
|
|
117
|
+
|
|
118
|
+
#Arguments of the unresolved can be accessed through x.1 (1st arg) - x.n
|
|
119
|
+
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
### Multiple matching axioms — MultiResult
|
|
125
|
+
|
|
126
|
+
When two or more axioms all match the same call, Newn returns a `MultiResult` — a list of all results in axiom-definition order. A single match returns the value directly, no wrapping.
|
|
127
|
+
|
|
128
|
+
```
|
|
129
|
+
op classify: None
|
|
130
|
+
axiom zero: [for all x], classify(0) = "zero" # matches literal 0
|
|
131
|
+
axiom any: [for all x], classify(x) = "other" # matches everything
|
|
132
|
+
|
|
133
|
+
r = classify(0) # MultiResult(['zero', 'other']) — both axioms fired
|
|
134
|
+
s = classify(99) # 'other' — only one axiom fired, plain value returned
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Use `first()` inside `.nn` to extract only the first matching result (explicit first-match-wins):
|
|
138
|
+
|
|
139
|
+
```
|
|
140
|
+
r = first(classify(0)) # 'zero'
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
Use `op.all()` from Python to get all results with their axiom names:
|
|
144
|
+
|
|
145
|
+
```python
|
|
146
|
+
results = ns['classify'].all(0)
|
|
147
|
+
# [('zero', 'zero'), ('any', 'other')]
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
---
|
|
151
|
+
|
|
152
|
+
### Ground axioms — constant facts
|
|
153
|
+
|
|
154
|
+
A ground axiom fires only for exact inputs. No variables. Auto-recorded at definition time.
|
|
155
|
+
|
|
156
|
+
```
|
|
157
|
+
axiom base_case: [None], fact(0) = 1
|
|
158
|
+
axiom next_case: [None], fact(1) = 1
|
|
159
|
+
axiom two: [None], fact(2) = 2
|
|
160
|
+
axiom six: [None], fact(6) = 720
|
|
161
|
+
```
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
### Prop-family axioms
|
|
165
|
+
|
|
166
|
+
An axiom can fire for an entire *family* of props at once using a numeric index variable:
|
|
167
|
+
|
|
168
|
+
```
|
|
169
|
+
op lift: None
|
|
170
|
+
axiom gen: [for all n, for all pn(x)], lift(pn(x)) = pn(x + 1)
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
This fires for `p0(3)` → `p0(4)`, `p7(1)` → `p7(2)`, and any `pN(x)` — one axiom covers the whole family. The `n` variable must be consistent across both sides.
|
|
174
|
+
|
|
175
|
+
---
|
|
176
|
+
|
|
177
|
+
### Recursion guard — symbolic expressions
|
|
178
|
+
|
|
179
|
+
When an axiom's RHS would recursively call itself with the same pattern, the engine records the call as an `UnresolvedExpr` instead of looping. This is how Newn produces symbolic unevaluated expressions you can inspect and pattern-match later.
|
|
180
|
+
|
|
181
|
+
```
|
|
182
|
+
axiom hold: [for all absent(x), for all y], absent(x) add y = absent(x) add y
|
|
183
|
+
|
|
184
|
+
result = absent(5) add 7
|
|
185
|
+
print(result) # absent(5) add 7
|
|
186
|
+
print(type(result)) # UnresolvedExpr
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
---
|
|
190
|
+
|
|
191
|
+
### DotAccessPat — deconstructing unresolved categories
|
|
192
|
+
|
|
193
|
+
Access the inner components of an UnresolvedExpr using `.N` notation:
|
|
194
|
+
|
|
195
|
+
```
|
|
196
|
+
axiom new2: [for all x, for all y], x add absent(y) = absent(y) add x
|
|
197
|
+
category nop: [for all x, for all y], absent(y) add x
|
|
198
|
+
axiom futnew: [for all x in nop, for all y], x cube y = (x.2 + y) add x.1
|
|
199
|
+
coq = 6 add absent(7)
|
|
200
|
+
print(coq cube 7)
|
|
201
|
+
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
---
|
|
205
|
+
|
|
206
|
+
### Prop-family ops via Python-backed definitions
|
|
207
|
+
|
|
208
|
+
Import any Python callable as a Newn op:
|
|
209
|
+
|
|
210
|
+
```
|
|
211
|
+
from_python: math.sqrt as sqrt_op
|
|
212
|
+
from_python: operator.add as py_add
|
|
213
|
+
|
|
214
|
+
result = sqrt_op(16) # 4.0
|
|
215
|
+
result2 = 3 py_add 4 # 7
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
---
|
|
219
|
+
|
|
220
|
+
### Multi-file support
|
|
221
|
+
|
|
222
|
+
```
|
|
223
|
+
include "base_domain.nn"
|
|
224
|
+
include "extensions/extra.nn"
|
|
225
|
+
|
|
226
|
+
axiom my_extension: ...
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
Paths resolve relative to the file containing the `include` directive.
|
|
230
|
+
|
|
231
|
+
---
|
|
232
|
+
|
|
233
|
+
### Bind — language translation boundaries
|
|
234
|
+
|
|
235
|
+
Map Newn values to and from external representations:
|
|
236
|
+
|
|
237
|
+
```
|
|
238
|
+
prop fib: None
|
|
239
|
+
bind f5: fib(5) == [21] # fib(5) displays and exports as 21
|
|
240
|
+
|
|
241
|
+
result = translate_out(fib(5)) # 21
|
|
242
|
+
back = translate_in(21) # fib(5)
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
Bind tables are included in all export formats.
|
|
246
|
+
|
|
247
|
+
---
|
|
248
|
+
|
|
249
|
+
### VisualDict — readable prop representations
|
|
250
|
+
|
|
251
|
+
Control how PropValues print for human-readable output:
|
|
252
|
+
|
|
253
|
+
```
|
|
254
|
+
prop degree: None
|
|
255
|
+
vd = VisualDict({0: "none", 1: "low", 2: "medium", 3: "high"})
|
|
256
|
+
prop degree: vd
|
|
257
|
+
|
|
258
|
+
d = degree(2)
|
|
259
|
+
print(d) # degree(medium)
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
---
|
|
263
|
+
|
|
264
|
+
## Backtracking
|
|
265
|
+
|
|
266
|
+
### Structural backtracking — the default
|
|
267
|
+
|
|
268
|
+
Given a pattern, find all axioms whose LHS or RHS structurally matches it — then return the opposite side. No execution needed.
|
|
269
|
+
|
|
270
|
+
```
|
|
271
|
+
op add: None
|
|
272
|
+
axiom plain: for all x, for all y, x add y = x + y
|
|
273
|
+
|
|
274
|
+
hits = {[for all x, for all y], x add y}
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
`hits` is a `BacktrackResult` containing the RHS pattern `x + y` — found by matching the LHS structurally.
|
|
278
|
+
|
|
279
|
+
Going the other direction:
|
|
280
|
+
|
|
281
|
+
```
|
|
282
|
+
reverse = {[for all x, for all y], x + y} # finds: x add y
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
Results display as `matched_side → opposite_side`.
|
|
286
|
+
|
|
287
|
+
Use `.values()`, `.bindings()`, and `.sources()` to inspect:
|
|
288
|
+
|
|
289
|
+
```python
|
|
290
|
+
bt = ns['hits']
|
|
291
|
+
print(bt.values()) # ['x + y']
|
|
292
|
+
print(bt.sources()) # ['plain']
|
|
293
|
+
print(bt.items()) # [(bindings, value, axiom_name), ...]
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
### Structural backtracking in Python
|
|
297
|
+
|
|
298
|
+
```python
|
|
299
|
+
from newn.core import _ENGINE
|
|
300
|
+
|
|
301
|
+
results = _ENGINE.backtrack_structural(['x', 'y'], 'x add y')
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
### Explicit structural mode
|
|
305
|
+
|
|
306
|
+
```
|
|
307
|
+
hits = {[for all x, for all y], x add y, structural}
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
Identical to the default — `, structural` is explicit confirmation of the mode.
|
|
311
|
+
|
|
312
|
+
---
|
|
313
|
+
|
|
314
|
+
## Debug tools
|
|
315
|
+
|
|
316
|
+
All available inside `.nn` scripts and `nn_exec` code blocks:
|
|
317
|
+
|
|
318
|
+
| Tool | Description |
|
|
319
|
+
|---|---|
|
|
320
|
+
| `trace_on()` | Print every axiom that fires as it fires |
|
|
321
|
+
| `trace_off()` | Stop tracing |
|
|
322
|
+
| `show(value)` | Pretty-print a value with its type information |
|
|
323
|
+
| `which_axioms("op_name")` | List all axioms registered for an op |
|
|
324
|
+
| `explain("op", arg1, arg2)` | Which axiom would fire for these exact arguments? |
|
|
325
|
+
| `coverage("op_name")` | Static coverage analysis — which input classes are handled? |
|
|
326
|
+
| `why_not("op", arg1, arg2)` | Explain why no axiom fired |
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
Example:
|
|
330
|
+
|
|
331
|
+
```
|
|
332
|
+
trace_on()
|
|
333
|
+
r = 3 add absent(4)
|
|
334
|
+
trace_off()
|
|
335
|
+
|
|
336
|
+
explain("add", 3, absent(4))
|
|
337
|
+
coverage("add")
|
|
338
|
+
why_not("add", absent(3), absent(4))
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
---
|
|
342
|
+
|
|
343
|
+
## Export
|
|
344
|
+
|
|
345
|
+
### Prolog
|
|
346
|
+
|
|
347
|
+
```python
|
|
348
|
+
from newn.export import export_prolog
|
|
349
|
+
|
|
350
|
+
nn_exec('...')
|
|
351
|
+
export_prolog('domain.pl')
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
Generates a loadable SWI-Prolog file:
|
|
355
|
+
|
|
356
|
+
```prolog
|
|
357
|
+
?- consult('domain.pl').
|
|
358
|
+
?- add(3, 4, R). % R = 7
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
### Z3
|
|
362
|
+
|
|
363
|
+
```python
|
|
364
|
+
from newn.export import export_z3
|
|
365
|
+
export_z3('domain_z3.py')
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
Generates a Z3 Python script with `ForAll` constraints for every axiom.
|
|
369
|
+
|
|
370
|
+
### PDDL
|
|
371
|
+
|
|
372
|
+
```python
|
|
373
|
+
from newn.export import export_pddl
|
|
374
|
+
export_pddl('domain.pddl')
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
Generates a PDDL domain file for use with planners like Fast Downward.
|
|
378
|
+
|
|
379
|
+
### Python module
|
|
380
|
+
|
|
381
|
+
```python
|
|
382
|
+
from newn.export import export_python
|
|
383
|
+
export_python('my_domain_api.py')
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
Generates a self-contained Python module that re-initialises the engine on import and exposes every op as a regular Python function.
|
|
387
|
+
|
|
388
|
+
---
|
|
389
|
+
|
|
390
|
+
## Command-line interface
|
|
391
|
+
|
|
392
|
+
Run a `.nn` file:
|
|
393
|
+
|
|
394
|
+
```bash
|
|
395
|
+
python -m newn myfile.nn
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
Show the generated Python before running it:
|
|
399
|
+
|
|
400
|
+
```bash
|
|
401
|
+
python -m newn --transform myfile.nn
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
---
|
|
405
|
+
|
|
406
|
+
## Full example — custom number system with categories using a .nn file
|
|
407
|
+
|
|
408
|
+
```
|
|
409
|
+
category unresolved: [for all x, for all y], absent(x) add y
|
|
410
|
+
axiom truth: [for all absent(x), for all y], absent(x) add y = absent(x) add y
|
|
411
|
+
axiom newtruth: [for all x in unresolved, for all y], x newad y = x.1 add (x.2 + y)
|
|
412
|
+
news = absent(5) add 7
|
|
413
|
+
fire = news newad 3
|
|
414
|
+
axiom forever: [for all x, for all y, for all z], absnet(z(x)) forevez z(y) = z(x(y))
|
|
415
|
+
axiom new: [for all x], x nop x = double(x)
|
|
416
|
+
axiom new2: [for all x, for all y], x op y = x(y)
|
|
417
|
+
axiom fifthen: [for all x, for all y], x(x) ching y = y ching x(x)
|
|
418
|
+
category unresolved: [for all x, for all y], x(x) ching y
|
|
419
|
+
axiom furyfire: [for all x in unresolved], x five x.2 = fuel(x.1)
|
|
420
|
+
newfine = {[for all x, for all y, for all z], z(x(y))}
|
|
421
|
+
supback = {[for all x, for all y], y ching x(x)}
|
|
422
|
+
axiom newcan: [for all x, for all y, for all z], z(x) supadd y = z
|
|
423
|
+
axiom newcan: [for all x, for all y, for all z], z(x) supadd y = y
|
|
424
|
+
print(5(7) supadd 8)
|
|
425
|
+
print(supback)
|
|
426
|
+
print(newfine)
|
|
427
|
+
dict = {1: "happy", 2: "news", 3: "double"}
|
|
428
|
+
print(dict[1 op 3])
|
|
429
|
+
print(dict[1 nop 1])
|
|
430
|
+
```
|
|
431
|
+
---
|
|
432
|
+
|
|
433
|
+
## Still in Construction
|
|
434
|
+
|
|
435
|
+
|
|
436
|
+
> **⚠ Warning — Probe-based backtracking is not yet fully operational.**
|
|
437
|
+
>
|
|
438
|
+
> The probe mode (`{[for all x], pattern, probe}`) executes axioms against test values to find matches. This feature is under active development and may produce incomplete or incorrect results in complex axiom systems. Use structural backtracking (the default) for reliable results.
|
|
439
|
+
|
|
440
|
+
> **⚠ Warning — Safety Net is not yet fully operational.**
|
|
441
|
+
>
|
|
442
|
+
> The `SafetyNet` class (`from newn import SafetyNet`) provides an API for validating external (e.g., neural network) outputs against your axiom rules, exporting rules as JSON or LLM prompts, and serving a local HTTP endpoint. The API surface is in place but the validation engine is under active development. Do not use `SafetyNet` in production workflows yet.
|
|
443
|
+
|
|
444
|
+
---
|
|
445
|
+
|
|
446
|
+
## API Reference
|
|
447
|
+
|
|
448
|
+
### Execution
|
|
449
|
+
|
|
450
|
+
| Name | Description |
|
|
451
|
+
|---|---|
|
|
452
|
+
| `nn_exec(code, *, base_dir=None)` | Execute `.nn` code string; return namespace dict |
|
|
453
|
+
| `nn_exec_file(path)` | Execute a `.nn` file; return namespace dict |
|
|
454
|
+
| `nn_transform(code)` | Return the generated Python source without running it |
|
|
455
|
+
|
|
456
|
+
### Backward chaining
|
|
457
|
+
|
|
458
|
+
| Name | Description |
|
|
459
|
+
|---|---|
|
|
460
|
+
| `backchain(engine, op, target)` | Symbolic inverse — find inputs that produce `target` |
|
|
461
|
+
| `query(engine, op, target, domain)` | Search over `domain` for inputs that produce `target` |
|
|
462
|
+
| `prove(engine, op, *args)` | Returns `True` if the call resolves to a concrete value |
|
|
463
|
+
|
|
464
|
+
### Types
|
|
465
|
+
|
|
466
|
+
| Name | Description |
|
|
467
|
+
|---|---|
|
|
468
|
+
| `PropValue(name, value)` | A value wrapped with a named property |
|
|
469
|
+
| `UnresolvedExpr(op, args)` | A symbolic, unevaluated expression |
|
|
470
|
+
| `BacktrackResult` | Result of a `{}` backtrack query; iterable, has `.values()`, `.bindings()`, `.sources()`, `.items()` |
|
|
471
|
+
| `MultiResult` | List subclass returned when 2+ axioms match; supports indexing, `len()`, iteration |
|
|
472
|
+
| `Category` | Named category object; call `.contains(value)` to test membership |
|
|
473
|
+
| `UnresolvedCategory` | Category defined by op output membership |
|
|
474
|
+
| `VisualDict` | Mapping from inner values to display strings for prop printing |
|
|
475
|
+
| `AxiomEngine` | The runtime engine; access via `ns['_nn_engine']` |
|
|
476
|
+
|
|
477
|
+
### Errors
|
|
478
|
+
|
|
479
|
+
| Name | Raised when |
|
|
480
|
+
|---|---|
|
|
481
|
+
| `NewnError` | Generic Error |
|
|
482
|
+
| `NewnSyntaxError` | `.nn` code has a syntax problem |
|
|
483
|
+
| `AxiomNotFoundError` | Specific axiom name not found |
|
|
484
|
+
| `CategoryError` | Category constraint violated |
|
|
485
|
+
| `PatternError` | Pattern matching failure |
|
|
486
|
+
|
|
487
|
+
### Export
|
|
488
|
+
|
|
489
|
+
| Name | Description |
|
|
490
|
+
|---|---|
|
|
491
|
+
| `export_prolog(path)` | Write a SWI-Prolog `.pl` file |
|
|
492
|
+
| `export_z3(path)` | Write a Z3 Python script |
|
|
493
|
+
| `export_pddl(path)` | Write a PDDL domain file |
|
|
494
|
+
| `export_python(path)` | Write a self-contained Python API module |
|
|
495
|
+
|
|
496
|
+
### Multi-result
|
|
497
|
+
|
|
498
|
+
| Expression | Returns |
|
|
499
|
+
|---|---|
|
|
500
|
+
| `op(args)` | Single value if one axiom matches; `MultiResult` if 2+ match |
|
|
501
|
+
| `first(op(args))` | Always a single value — the first matching axiom's result |
|
|
502
|
+
| `op.all(args)` | `[(axiom_name, value), ...]` for every matching axiom |
|
|
503
|
+
|
|
504
|
+
---
|
|
505
|
+
|
|
506
|
+
## `.nn` Language Quick Reference
|
|
507
|
+
|
|
508
|
+
```
|
|
509
|
+
# Comments
|
|
510
|
+
prop name: None # declare a prop
|
|
511
|
+
op name: None # declare an op
|
|
512
|
+
|
|
513
|
+
# Simple axiom
|
|
514
|
+
axiom label: for all x, for all y, x op y = x + y
|
|
515
|
+
|
|
516
|
+
# Bracket form (per-variable constraints)
|
|
517
|
+
axiom label: [for all x, for all absent(y)], x op absent(y) = absent(x + y)
|
|
518
|
+
|
|
519
|
+
# Range-constrained variable
|
|
520
|
+
axiom label: [for all x in range(0, 10)], process(x) = x * 10
|
|
521
|
+
|
|
522
|
+
# Category-constrained variable
|
|
523
|
+
axiom label: [for all x in small], process(x) = small(x)
|
|
524
|
+
|
|
525
|
+
# Ground axiom (no variables)
|
|
526
|
+
axiom exact: [None], 3 op 4 = 99
|
|
527
|
+
|
|
528
|
+
# Template op (prop family)
|
|
529
|
+
axiom gen: [for all n, for all pn(x)], lift(pn(x)) = pn(x + 1)
|
|
530
|
+
|
|
531
|
+
# Ground axiom (structural) backtracking
|
|
532
|
+
hits = {[for all x, for all y], x op y}
|
|
533
|
+
|
|
534
|
+
# Explicit structural
|
|
535
|
+
hits = {[for all x, for all y], x op y, structural}
|
|
536
|
+
|
|
537
|
+
# first() — first matching axiom's result
|
|
538
|
+
r = first(x op y)
|
|
539
|
+
|
|
540
|
+
# Include another file
|
|
541
|
+
include "other.nn"
|
|
542
|
+
|
|
543
|
+
# Python-backed op
|
|
544
|
+
from_python: math.sqrt as sqrt_op
|
|
545
|
+
|
|
546
|
+
# Bind (translation table)
|
|
547
|
+
bind label: prop(value) == [external_value]
|
|
548
|
+
```
|
|
549
|
+
|
|
550
|
+
---
|
|
551
|
+
|
|
552
|
+
## License
|
|
553
|
+
|
|
554
|
+
MIT
|