CharlesCNorton commited on
Commit ·
58a9bad
1
Parent(s): ca99a3e
Add gate-level calculator UI and CLI expr mode
Browse files- Add calculator.py expression evaluator with JSON/strict output for float16 gate-only eval
- Add Gradio Space app.py + requirements.txt for proof-of-concept UI
- Update README with Space notes and expanded TODO/caveats
- README.md +48 -2
- app.py +59 -0
- calculator.py +568 -0
- requirements.txt +3 -0
README.md
CHANGED
|
@@ -183,6 +183,33 @@ python eval.py --coverage --inputs-coverage
|
|
| 183 |
|
| 184 |
`--inputs-coverage` evaluates gates via their `.inputs` tensors using seeded external inputs and explicit overrides, and fails if inputs cannot be resolved. This is for coverage and routing sanity, not a correctness proof.
|
| 185 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 186 |
## Development History
|
| 187 |
|
| 188 |
Started as an 8-bit CPU project. Built boolean gates, then arithmetic (adders -> multipliers -> dividers), then CPU control logic. The CPU worked but the arithmetic core turned out to be the useful part, so it was extracted.
|
|
@@ -205,14 +232,33 @@ This began as an attempt to build a complete threshold-logic CPU. The CPU is in
|
|
| 205 |
- Boolean, threshold, modular, pattern recognition, combinational
|
| 206 |
|
| 207 |
**Next:**
|
| 208 |
-
-
|
|
|
|
|
|
|
| 209 |
|
| 210 |
**Cleanup:**
|
| 211 |
- None (8-bit arithmetic scaffolding removed)
|
| 212 |
|
| 213 |
## TODO (Unified)
|
| 214 |
|
| 215 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 216 |
|
| 217 |
## License
|
| 218 |
|
|
|
|
| 183 |
|
| 184 |
`--inputs-coverage` evaluates gates via their `.inputs` tensors using seeded external inputs and explicit overrides, and fails if inputs cannot be resolved. This is for coverage and routing sanity, not a correctness proof.
|
| 185 |
|
| 186 |
+
## Python Calculator Interface (Gate-Level)
|
| 187 |
+
|
| 188 |
+
`calculator.py` provides a pure gate-level evaluator that uses only `.inputs` + `.weight`/`.bias` (no arithmetic shortcuts). This is intended as a rigorous proof-of-concept for using the weights as a calculator.
|
| 189 |
+
Expression mode routes even constants through a circuit (via `float16.add` with +0) to ensure gate-level evaluation.
|
| 190 |
+
|
| 191 |
+
Examples:
|
| 192 |
+
|
| 193 |
+
```bash
|
| 194 |
+
python calculator.py float16.add 1.0 2.0
|
| 195 |
+
python calculator.py float16.sqrt 2.0
|
| 196 |
+
python calculator.py float16.add --inputs a=0x3c00 b=0x4000 --hex
|
| 197 |
+
python calculator.py --expr "1 + 1"
|
| 198 |
+
python calculator.py "sin(pi / 2)"
|
| 199 |
+
python calculator.py --expr "1 + 1" --json
|
| 200 |
+
python calculator.py "pi" --strict
|
| 201 |
+
```
|
| 202 |
+
|
| 203 |
+
Programmatic use:
|
| 204 |
+
|
| 205 |
+
```python
|
| 206 |
+
from calculator import ThresholdCalculator
|
| 207 |
+
|
| 208 |
+
calc = ThresholdCalculator("arithmetic.safetensors")
|
| 209 |
+
out, _ = calc.float16_binop("add", 1.0, 2.0)
|
| 210 |
+
print(out)
|
| 211 |
+
```
|
| 212 |
+
|
| 213 |
## Development History
|
| 214 |
|
| 215 |
Started as an 8-bit CPU project. Built boolean gates, then arithmetic (adders -> multipliers -> dividers), then CPU control logic. The CPU worked but the arithmetic core turned out to be the useful part, so it was extracted.
|
|
|
|
| 232 |
- Boolean, threshold, modular, pattern recognition, combinational
|
| 233 |
|
| 234 |
**Next:**
|
| 235 |
+
- Add a full scientific calculator function set (log10, asin/acos/atan, sinh/cosh, etc.) as gate-level circuits.
|
| 236 |
+
- Add 32-bit integer arithmetic circuits (add/sub/shift/compare, then mul/div).
|
| 237 |
+
- Add higher precision modes (float32 or fixed-point) for typical calculator accuracy.
|
| 238 |
|
| 239 |
**Cleanup:**
|
| 240 |
- None (8-bit arithmetic scaffolding removed)
|
| 241 |
|
| 242 |
## TODO (Unified)
|
| 243 |
|
| 244 |
+
- Scientific functions missing from current weights (log10, asin/acos/atan, sinh/cosh, floor/ceil/round, etc.).
|
| 245 |
+
- 32-bit integer circuits and an int32 calculator mode.
|
| 246 |
+
- Degree-mode trig and implicit multiplication parsing.
|
| 247 |
+
- Higher-precision arithmetic (float32 or fixed-point).
|
| 248 |
+
- Complex-number support (or explicit domain errors).
|
| 249 |
+
|
| 250 |
+
## Hugging Face Space (Proof of Concept)
|
| 251 |
+
|
| 252 |
+
A minimal Space UI is included in `app.py`. It uses the gate-level evaluator only (no arithmetic shortcuts) and exposes a normal calculator-style expression input.
|
| 253 |
+
|
| 254 |
+
Example expressions:
|
| 255 |
+
- `1 + 1`
|
| 256 |
+
- `sin(pi / 2)`
|
| 257 |
+
- `exp(ln(2))`
|
| 258 |
+
|
| 259 |
+
Notes:
|
| 260 |
+
- All results are **float16** (IEEE-754 half) and may be rounded.
|
| 261 |
+
- `pow` uses the `exp(b*ln(a))` definition; negative bases yield NaN.
|
| 262 |
|
| 263 |
## License
|
| 264 |
|
app.py
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
Hugging Face Space: gate-level calculator for threshold-calculus.
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
import gradio as gr
|
| 7 |
+
|
| 8 |
+
from calculator import ThresholdCalculator, bits_to_int, float16_bits_to_float
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
calc = ThresholdCalculator("arithmetic.safetensors")
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
def eval_expression(expr: str):
|
| 15 |
+
expr = (expr or "").strip()
|
| 16 |
+
if not expr:
|
| 17 |
+
return "", "", "", ""
|
| 18 |
+
try:
|
| 19 |
+
result = calc.evaluate_expr(expr)
|
| 20 |
+
out_int = bits_to_int(result.bits)
|
| 21 |
+
out_float = float16_bits_to_float(out_int)
|
| 22 |
+
return (
|
| 23 |
+
f"{out_float}",
|
| 24 |
+
f"0x{out_int:04x}",
|
| 25 |
+
f"{result.gates_evaluated}",
|
| 26 |
+
f"{result.elapsed_s:.4f}s",
|
| 27 |
+
)
|
| 28 |
+
except Exception as exc:
|
| 29 |
+
return f"ERROR: {exc}", "", "", ""
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
with gr.Blocks(title="Threshold Calculus Calculator") as demo:
|
| 33 |
+
gr.Markdown(
|
| 34 |
+
"# Threshold Calculus Calculator\n"
|
| 35 |
+
"Pure gate-level evaluation of float16 arithmetic (IEEE-754 half). "
|
| 36 |
+
"All results are float16 and may be rounded."
|
| 37 |
+
)
|
| 38 |
+
expr = gr.Textbox(
|
| 39 |
+
label="Expression",
|
| 40 |
+
placeholder="e.g., 1 + 1, sin(pi / 2), exp(ln(2))",
|
| 41 |
+
)
|
| 42 |
+
run = gr.Button("Evaluate")
|
| 43 |
+
out_val = gr.Textbox(label="Float16 Result", interactive=False)
|
| 44 |
+
out_bits = gr.Textbox(label="Result Bits (hex)", interactive=False)
|
| 45 |
+
out_gates = gr.Textbox(label="Gates Evaluated", interactive=False)
|
| 46 |
+
out_time = gr.Textbox(label="Elapsed", interactive=False)
|
| 47 |
+
|
| 48 |
+
run.click(eval_expression, inputs=[expr], outputs=[out_val, out_bits, out_gates, out_time])
|
| 49 |
+
expr.submit(eval_expression, inputs=[expr], outputs=[out_val, out_bits, out_gates, out_time])
|
| 50 |
+
|
| 51 |
+
gr.Markdown(
|
| 52 |
+
"Notes:\n"
|
| 53 |
+
"- Functions: sin, cos, tan, tanh, sqrt, rsqrt, exp, ln, log2, abs, neg\n"
|
| 54 |
+
"- Constants: pi, e, inf, nan\n"
|
| 55 |
+
"- Pow uses exp(b*ln(a)); negative bases yield NaN."
|
| 56 |
+
)
|
| 57 |
+
|
| 58 |
+
if __name__ == "__main__":
|
| 59 |
+
demo.launch()
|
calculator.py
ADDED
|
@@ -0,0 +1,568 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
Threshold-calculus gate-level calculator.
|
| 4 |
+
|
| 5 |
+
Pure evaluation via .inputs + .weight/.bias only. No arithmetic shortcuts.
|
| 6 |
+
"""
|
| 7 |
+
|
| 8 |
+
from __future__ import annotations
|
| 9 |
+
|
| 10 |
+
import argparse
|
| 11 |
+
import ast
|
| 12 |
+
import json
|
| 13 |
+
import math
|
| 14 |
+
import struct
|
| 15 |
+
import time
|
| 16 |
+
from dataclasses import dataclass, field
|
| 17 |
+
from typing import Dict, Iterable, List, Optional, Sequence, Tuple
|
| 18 |
+
|
| 19 |
+
import torch
|
| 20 |
+
from safetensors import safe_open
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
def int_to_bits(val: int, width: int) -> List[float]:
|
| 24 |
+
"""Convert integer to LSB-first bit list of length width."""
|
| 25 |
+
return [float((val >> i) & 1) for i in range(width)]
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
def bits_to_int(bits: Sequence[float]) -> int:
|
| 29 |
+
"""Convert LSB-first bit list to integer."""
|
| 30 |
+
return sum((1 << i) for i, b in enumerate(bits) if b >= 0.5)
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
def float_to_float16_bits(val: float) -> int:
|
| 34 |
+
"""Convert float to IEEE-754 float16 bits (with canonical NaN)."""
|
| 35 |
+
try:
|
| 36 |
+
packed = struct.pack(">e", float(val))
|
| 37 |
+
return struct.unpack(">H", packed)[0]
|
| 38 |
+
except (OverflowError, struct.error):
|
| 39 |
+
if val == float("inf"):
|
| 40 |
+
return 0x7C00
|
| 41 |
+
if val == float("-inf"):
|
| 42 |
+
return 0xFC00
|
| 43 |
+
if val != val:
|
| 44 |
+
return 0x7E00
|
| 45 |
+
return 0x7BFF if val > 0 else 0xFBFF
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
def float16_bits_to_float(bits: int) -> float:
|
| 49 |
+
"""Interpret 16-bit int as IEEE-754 float16."""
|
| 50 |
+
packed = struct.pack(">H", bits & 0xFFFF)
|
| 51 |
+
return struct.unpack(">e", packed)[0]
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
def parse_external_name(name: str) -> Tuple[Optional[str], Optional[int], Optional[str]]:
|
| 55 |
+
"""
|
| 56 |
+
Parse an external signal name into (base, index, full_key).
|
| 57 |
+
Examples:
|
| 58 |
+
"$a" -> ("a", None, "$a")
|
| 59 |
+
"float16.add.$a[3]" -> ("a", 3, "float16.add.$a")
|
| 60 |
+
"""
|
| 61 |
+
if "$" not in name:
|
| 62 |
+
return None, None, None
|
| 63 |
+
full_key = name.split("[", 1)[0]
|
| 64 |
+
base_part = name.split("$", 1)[1]
|
| 65 |
+
base = base_part.split("[", 1)[0]
|
| 66 |
+
idx = None
|
| 67 |
+
if "[" in base_part and "]" in base_part:
|
| 68 |
+
try:
|
| 69 |
+
idx = int(base_part.split("[", 1)[1].split("]", 1)[0])
|
| 70 |
+
except ValueError:
|
| 71 |
+
idx = None
|
| 72 |
+
return base, idx, full_key
|
| 73 |
+
|
| 74 |
+
|
| 75 |
+
def resolve_alias_target(name: str, gates: set) -> Optional[str]:
|
| 76 |
+
"""Resolve common alias signal names to actual gate names."""
|
| 77 |
+
if name in gates:
|
| 78 |
+
return name
|
| 79 |
+
cand = name + ".layer2"
|
| 80 |
+
if cand in gates:
|
| 81 |
+
return cand
|
| 82 |
+
if name.endswith(".sum"):
|
| 83 |
+
cand = name[:-4] + ".xor2.layer2"
|
| 84 |
+
if cand in gates:
|
| 85 |
+
return cand
|
| 86 |
+
if name.endswith(".cout"):
|
| 87 |
+
for suffix in [".or_carry", ".carry_or"]:
|
| 88 |
+
cand = name[:-5] + suffix
|
| 89 |
+
if cand in gates:
|
| 90 |
+
return cand
|
| 91 |
+
return None
|
| 92 |
+
|
| 93 |
+
|
| 94 |
+
def normalize_expr(expr: str) -> str:
|
| 95 |
+
"""Normalize user-facing calculator syntax to Python AST syntax."""
|
| 96 |
+
expr = expr.replace("π", "pi")
|
| 97 |
+
expr = expr.replace("×", "*").replace("÷", "/").replace("−", "-")
|
| 98 |
+
if "^" in expr:
|
| 99 |
+
expr = expr.replace("^", "**")
|
| 100 |
+
return expr
|
| 101 |
+
|
| 102 |
+
|
| 103 |
+
def looks_like_expression(text: str) -> bool:
|
| 104 |
+
tokens = ["+", "-", "*", "/", "(", ")", "^", "pi", "π"]
|
| 105 |
+
return any(tok in text for tok in tokens)
|
| 106 |
+
|
| 107 |
+
|
| 108 |
+
@dataclass
|
| 109 |
+
class EvalResult:
|
| 110 |
+
bits: List[float]
|
| 111 |
+
elapsed_s: float
|
| 112 |
+
gates_evaluated: int
|
| 113 |
+
non_gate_events: List[str] = field(default_factory=list)
|
| 114 |
+
|
| 115 |
+
|
| 116 |
+
class ThresholdCalculator:
|
| 117 |
+
def __init__(self, model_path: str = "./arithmetic.safetensors") -> None:
|
| 118 |
+
self.model_path = model_path
|
| 119 |
+
self.tensors: Dict[str, torch.Tensor] = {}
|
| 120 |
+
self.gates: List[str] = []
|
| 121 |
+
self.name_to_id: Dict[str, int] = {}
|
| 122 |
+
self.id_to_name: Dict[int, str] = {}
|
| 123 |
+
self._gate_inputs: Dict[str, torch.Tensor] = {}
|
| 124 |
+
self._gate_set: set = set()
|
| 125 |
+
self._alias_to_gate: Dict[int, int] = {}
|
| 126 |
+
self._gate_to_alias: Dict[int, List[int]] = {}
|
| 127 |
+
self._id_to_gate: Dict[int, str] = {}
|
| 128 |
+
self._topo_cache: Dict[Tuple[str, Tuple[str, ...]], List[str]] = {}
|
| 129 |
+
self._load()
|
| 130 |
+
|
| 131 |
+
def _load(self) -> None:
|
| 132 |
+
with safe_open(self.model_path, framework="pt") as f:
|
| 133 |
+
for name in f.keys():
|
| 134 |
+
self.tensors[name] = f.get_tensor(name)
|
| 135 |
+
metadata = f.metadata()
|
| 136 |
+
if metadata and "signal_registry" in metadata:
|
| 137 |
+
registry_raw = json.loads(metadata["signal_registry"])
|
| 138 |
+
self.id_to_name = {int(k): v for k, v in registry_raw.items()}
|
| 139 |
+
self.name_to_id = {v: int(k) for k, v in registry_raw.items()}
|
| 140 |
+
self.gates = sorted({k.rsplit(".", 1)[0] for k in self.tensors.keys() if k.endswith(".weight")})
|
| 141 |
+
self._gate_set = set(self.gates)
|
| 142 |
+
for gate in self.gates:
|
| 143 |
+
inputs_key = f"{gate}.inputs"
|
| 144 |
+
if inputs_key in self.tensors:
|
| 145 |
+
self._gate_inputs[gate] = self.tensors[inputs_key].to(dtype=torch.long)
|
| 146 |
+
self._build_alias_maps()
|
| 147 |
+
for gate in self.gates:
|
| 148 |
+
gid = self.name_to_id.get(gate)
|
| 149 |
+
if gid is not None:
|
| 150 |
+
self._id_to_gate[gid] = gate
|
| 151 |
+
|
| 152 |
+
def _build_alias_maps(self) -> None:
|
| 153 |
+
gates = set(self.gates)
|
| 154 |
+
alias_to_gate: Dict[int, int] = {}
|
| 155 |
+
gate_to_alias: Dict[int, List[int]] = {}
|
| 156 |
+
for name, sid in self.name_to_id.items():
|
| 157 |
+
if name in ("#0", "#1"):
|
| 158 |
+
continue
|
| 159 |
+
if name.startswith("$") or ".$" in name:
|
| 160 |
+
continue
|
| 161 |
+
if name in gates:
|
| 162 |
+
continue
|
| 163 |
+
target = resolve_alias_target(name, gates)
|
| 164 |
+
if not target:
|
| 165 |
+
continue
|
| 166 |
+
target_id = self.name_to_id.get(target)
|
| 167 |
+
if target_id is None:
|
| 168 |
+
continue
|
| 169 |
+
alias_to_gate[sid] = target_id
|
| 170 |
+
gate_to_alias.setdefault(target_id, []).append(sid)
|
| 171 |
+
self._alias_to_gate = alias_to_gate
|
| 172 |
+
self._gate_to_alias = gate_to_alias
|
| 173 |
+
|
| 174 |
+
def _signal_to_gate(self, sid: int) -> Optional[str]:
|
| 175 |
+
if sid in self._id_to_gate:
|
| 176 |
+
return self._id_to_gate[sid]
|
| 177 |
+
alias_target = self._alias_to_gate.get(sid)
|
| 178 |
+
if alias_target is not None:
|
| 179 |
+
return self._id_to_gate.get(alias_target)
|
| 180 |
+
return None
|
| 181 |
+
|
| 182 |
+
def _default_outputs(self, prefix: str, out_bits: int) -> List[str]:
|
| 183 |
+
if f"{prefix}.out0.weight" in self.tensors:
|
| 184 |
+
return [f"{prefix}.out{i}" for i in range(out_bits)]
|
| 185 |
+
if prefix in self._gate_set:
|
| 186 |
+
return [prefix]
|
| 187 |
+
raise RuntimeError(f"{prefix}: no outputs found")
|
| 188 |
+
|
| 189 |
+
def _collect_required_gates(self, output_gates: Sequence[str]) -> List[str]:
|
| 190 |
+
required: set = set()
|
| 191 |
+
stack = list(output_gates)
|
| 192 |
+
while stack:
|
| 193 |
+
gate = stack.pop()
|
| 194 |
+
if gate in required:
|
| 195 |
+
continue
|
| 196 |
+
if gate not in self._gate_inputs:
|
| 197 |
+
raise RuntimeError(f"{gate}: missing .inputs tensor")
|
| 198 |
+
required.add(gate)
|
| 199 |
+
input_ids = self._gate_inputs[gate]
|
| 200 |
+
for sid in input_ids.tolist():
|
| 201 |
+
dep_gate = self._signal_to_gate(int(sid))
|
| 202 |
+
if dep_gate is not None and dep_gate not in required:
|
| 203 |
+
stack.append(dep_gate)
|
| 204 |
+
return sorted(required)
|
| 205 |
+
|
| 206 |
+
def _topo_sort(self, gates: Sequence[str]) -> List[str]:
|
| 207 |
+
key = tuple(gates)
|
| 208 |
+
cache_key = ("__set__", key)
|
| 209 |
+
if cache_key in self._topo_cache:
|
| 210 |
+
return self._topo_cache[cache_key]
|
| 211 |
+
gate_set = set(gates)
|
| 212 |
+
deps: Dict[str, set] = {g: set() for g in gates}
|
| 213 |
+
rev: Dict[str, List[str]] = {g: [] for g in gates}
|
| 214 |
+
for gate in gates:
|
| 215 |
+
input_ids = self._gate_inputs[gate].tolist()
|
| 216 |
+
for sid in input_ids:
|
| 217 |
+
dep_gate = self._signal_to_gate(int(sid))
|
| 218 |
+
if dep_gate is not None and dep_gate in gate_set:
|
| 219 |
+
deps[gate].add(dep_gate)
|
| 220 |
+
rev[dep_gate].append(gate)
|
| 221 |
+
queue = sorted([g for g in gates if not deps[g]])
|
| 222 |
+
order: List[str] = []
|
| 223 |
+
while queue:
|
| 224 |
+
g = queue.pop(0)
|
| 225 |
+
order.append(g)
|
| 226 |
+
for child in rev[g]:
|
| 227 |
+
deps[child].remove(g)
|
| 228 |
+
if not deps[child]:
|
| 229 |
+
queue.append(child)
|
| 230 |
+
queue.sort()
|
| 231 |
+
if len(order) != len(gates):
|
| 232 |
+
raise RuntimeError("Dependency cycle or unresolved inputs in gate graph")
|
| 233 |
+
self._topo_cache[cache_key] = order
|
| 234 |
+
return order
|
| 235 |
+
|
| 236 |
+
def _required_externals(self, gates: Iterable[str]) -> List[int]:
|
| 237 |
+
externals: set = set()
|
| 238 |
+
for gate in gates:
|
| 239 |
+
for sid in self._gate_inputs[gate].tolist():
|
| 240 |
+
sid = int(sid)
|
| 241 |
+
name = self.id_to_name.get(sid, "")
|
| 242 |
+
if name.startswith("$") or ".$" in name:
|
| 243 |
+
externals.add(sid)
|
| 244 |
+
return sorted(externals)
|
| 245 |
+
|
| 246 |
+
def _normalize_inputs(self, required_externals: List[int], inputs: Dict[str, object]) -> Dict[int, float]:
|
| 247 |
+
exact: Dict[str, object] = {}
|
| 248 |
+
base_inputs: Dict[str, object] = {}
|
| 249 |
+
for key, val in inputs.items():
|
| 250 |
+
if "$" in key:
|
| 251 |
+
exact[key] = val
|
| 252 |
+
else:
|
| 253 |
+
base_inputs[key] = val
|
| 254 |
+
|
| 255 |
+
width_full: Dict[str, int] = {}
|
| 256 |
+
width_base: Dict[str, int] = {}
|
| 257 |
+
for sid in required_externals:
|
| 258 |
+
name = self.id_to_name.get(sid, "")
|
| 259 |
+
base, idx, full_key = parse_external_name(name)
|
| 260 |
+
if base is None or full_key is None:
|
| 261 |
+
continue
|
| 262 |
+
w = (idx + 1) if idx is not None else 1
|
| 263 |
+
width_full[full_key] = max(width_full.get(full_key, 1), w)
|
| 264 |
+
width_base[base] = max(width_base.get(base, 1), w)
|
| 265 |
+
|
| 266 |
+
def ensure_bit_list(val: object, width: int) -> List[float]:
|
| 267 |
+
if isinstance(val, (list, tuple)):
|
| 268 |
+
bits = [float(b) for b in val]
|
| 269 |
+
if len(bits) < width:
|
| 270 |
+
raise RuntimeError(f"input width {len(bits)} < required {width}")
|
| 271 |
+
return bits
|
| 272 |
+
if isinstance(val, int):
|
| 273 |
+
return int_to_bits(val, width)
|
| 274 |
+
if isinstance(val, float):
|
| 275 |
+
if width != 16:
|
| 276 |
+
raise RuntimeError("float inputs only supported for 16-bit values")
|
| 277 |
+
bits_int = float_to_float16_bits(val)
|
| 278 |
+
return int_to_bits(bits_int, width)
|
| 279 |
+
raise RuntimeError("inputs must be list/tuple, int, or float16-compatible float")
|
| 280 |
+
|
| 281 |
+
normalized: Dict[int, float] = {}
|
| 282 |
+
for sid in required_externals:
|
| 283 |
+
name = self.id_to_name.get(sid, "")
|
| 284 |
+
base, idx, full_key = parse_external_name(name)
|
| 285 |
+
if base is None or full_key is None:
|
| 286 |
+
continue
|
| 287 |
+
if full_key in exact:
|
| 288 |
+
bits = ensure_bit_list(exact[full_key], width_full[full_key])
|
| 289 |
+
elif base in base_inputs:
|
| 290 |
+
bits = ensure_bit_list(base_inputs[base], width_base[base])
|
| 291 |
+
else:
|
| 292 |
+
raise RuntimeError(f"missing external input for {name}")
|
| 293 |
+
use_idx = idx if idx is not None else 0
|
| 294 |
+
normalized[sid] = float(bits[use_idx])
|
| 295 |
+
return normalized
|
| 296 |
+
|
| 297 |
+
def evaluate_prefix(
|
| 298 |
+
self,
|
| 299 |
+
prefix: str,
|
| 300 |
+
inputs: Dict[str, object],
|
| 301 |
+
out_bits: int = 16,
|
| 302 |
+
outputs: Optional[List[str]] = None,
|
| 303 |
+
) -> EvalResult:
|
| 304 |
+
output_gates = outputs if outputs is not None else self._default_outputs(prefix, out_bits)
|
| 305 |
+
required_gates = self._collect_required_gates(output_gates)
|
| 306 |
+
gate_order = self._topo_sort(required_gates)
|
| 307 |
+
required_externals = self._required_externals(required_gates)
|
| 308 |
+
|
| 309 |
+
num_signals = len(self.id_to_name)
|
| 310 |
+
signals = torch.full((num_signals,), float("nan"))
|
| 311 |
+
if "#0" in self.name_to_id:
|
| 312 |
+
signals[self.name_to_id["#0"]] = 0.0
|
| 313 |
+
if "#1" in self.name_to_id:
|
| 314 |
+
signals[self.name_to_id["#1"]] = 1.0
|
| 315 |
+
|
| 316 |
+
seeded = self._normalize_inputs(required_externals, inputs)
|
| 317 |
+
for sid, val in seeded.items():
|
| 318 |
+
signals[sid] = val
|
| 319 |
+
|
| 320 |
+
start = time.time()
|
| 321 |
+
evaluated = 0
|
| 322 |
+
for gate in gate_order:
|
| 323 |
+
input_ids = self._gate_inputs[gate]
|
| 324 |
+
input_vals = signals[input_ids]
|
| 325 |
+
if torch.isnan(input_vals).any():
|
| 326 |
+
raise RuntimeError(f"{gate}: unresolved inputs")
|
| 327 |
+
weight = self.tensors[f"{gate}.weight"].float()
|
| 328 |
+
bias = self.tensors.get(f"{gate}.bias", torch.tensor([0.0])).float().item()
|
| 329 |
+
total = torch.dot(weight, input_vals.float()).item() + bias
|
| 330 |
+
out = 1.0 if total >= 0 else 0.0
|
| 331 |
+
gate_id = self.name_to_id.get(gate)
|
| 332 |
+
if gate_id is not None:
|
| 333 |
+
signals[gate_id] = out
|
| 334 |
+
for alias_id in self._gate_to_alias.get(gate_id, []):
|
| 335 |
+
signals[alias_id] = out
|
| 336 |
+
evaluated += 1
|
| 337 |
+
elapsed = time.time() - start
|
| 338 |
+
|
| 339 |
+
bits: List[float] = []
|
| 340 |
+
for gate in output_gates:
|
| 341 |
+
gid = self.name_to_id.get(gate)
|
| 342 |
+
if gid is None or torch.isnan(signals[gid]):
|
| 343 |
+
raise RuntimeError(f"{prefix}: missing output {gate}")
|
| 344 |
+
bits.append(float(signals[gid]))
|
| 345 |
+
return EvalResult(bits=bits, elapsed_s=elapsed, gates_evaluated=evaluated)
|
| 346 |
+
|
| 347 |
+
# Float16 convenience wrappers (pure gate evaluation)
|
| 348 |
+
def float16_binop(self, op: str, a: float, b: float) -> Tuple[float, EvalResult]:
|
| 349 |
+
prefix = f"float16.{op}"
|
| 350 |
+
a_bits = int_to_bits(float_to_float16_bits(a), 16)
|
| 351 |
+
b_bits = int_to_bits(float_to_float16_bits(b), 16)
|
| 352 |
+
if op == "sub":
|
| 353 |
+
# float16.sub is defined as add with flipped sign bit on b
|
| 354 |
+
b_bits[15] = 1.0 - b_bits[15]
|
| 355 |
+
result = self.evaluate_prefix(prefix, {"a": a_bits, "b": b_bits}, out_bits=16)
|
| 356 |
+
out_int = bits_to_int(result.bits)
|
| 357 |
+
return float16_bits_to_float(out_int), result
|
| 358 |
+
|
| 359 |
+
def float16_unary(self, op: str, x: float) -> Tuple[float, EvalResult]:
|
| 360 |
+
prefix = f"float16.{op}"
|
| 361 |
+
x_bits = int_to_bits(float_to_float16_bits(x), 16)
|
| 362 |
+
# Unary LUT ops are wired through float16.lut.$x
|
| 363 |
+
result = self.evaluate_prefix(prefix, {"x": x_bits}, out_bits=16)
|
| 364 |
+
out_int = bits_to_int(result.bits)
|
| 365 |
+
return float16_bits_to_float(out_int), result
|
| 366 |
+
|
| 367 |
+
def float16_pow(self, a: float, b: float) -> Tuple[float, EvalResult]:
|
| 368 |
+
prefix = "float16.pow"
|
| 369 |
+
a_bits = int_to_bits(float_to_float16_bits(a), 16)
|
| 370 |
+
b_bits = int_to_bits(float_to_float16_bits(b), 16)
|
| 371 |
+
result = self.evaluate_prefix(prefix, {"a": a_bits, "b": b_bits}, out_bits=16)
|
| 372 |
+
out_int = bits_to_int(result.bits)
|
| 373 |
+
return float16_bits_to_float(out_int), result
|
| 374 |
+
|
| 375 |
+
def evaluate_expr(self, expr: str, force_gate_eval: bool = True) -> EvalResult:
|
| 376 |
+
"""Evaluate a calculator expression using float16 circuits."""
|
| 377 |
+
expr = normalize_expr(expr)
|
| 378 |
+
tree = ast.parse(expr, mode="eval")
|
| 379 |
+
|
| 380 |
+
total_elapsed = 0.0
|
| 381 |
+
total_gates = 0
|
| 382 |
+
non_gate_events: List[str] = []
|
| 383 |
+
|
| 384 |
+
def run_prefix(prefix: str, inputs: Dict[str, object]) -> int:
|
| 385 |
+
nonlocal total_elapsed, total_gates
|
| 386 |
+
res = self.evaluate_prefix(prefix, inputs, out_bits=16)
|
| 387 |
+
total_elapsed += res.elapsed_s
|
| 388 |
+
total_gates += res.gates_evaluated
|
| 389 |
+
return bits_to_int(res.bits)
|
| 390 |
+
|
| 391 |
+
def eval_node(node: ast.AST) -> int:
|
| 392 |
+
if isinstance(node, ast.Expression):
|
| 393 |
+
return eval_node(node.body)
|
| 394 |
+
if isinstance(node, ast.Constant):
|
| 395 |
+
if isinstance(node.value, (int, float)):
|
| 396 |
+
return float_to_float16_bits(float(node.value))
|
| 397 |
+
raise RuntimeError("unsupported literal")
|
| 398 |
+
if isinstance(node, ast.Name):
|
| 399 |
+
name = node.id
|
| 400 |
+
if name == "pi":
|
| 401 |
+
return float_to_float16_bits(math.pi)
|
| 402 |
+
if name == "e":
|
| 403 |
+
return float_to_float16_bits(math.e)
|
| 404 |
+
if name == "inf":
|
| 405 |
+
return float_to_float16_bits(float("inf"))
|
| 406 |
+
if name == "nan":
|
| 407 |
+
return float_to_float16_bits(float("nan"))
|
| 408 |
+
raise RuntimeError(f"unknown identifier: {name}")
|
| 409 |
+
if isinstance(node, ast.UnaryOp):
|
| 410 |
+
if isinstance(node.op, ast.UAdd):
|
| 411 |
+
return eval_node(node.operand)
|
| 412 |
+
if isinstance(node.op, ast.USub):
|
| 413 |
+
x = eval_node(node.operand)
|
| 414 |
+
return run_prefix("float16.neg", {"x": x})
|
| 415 |
+
raise RuntimeError("unsupported unary operator")
|
| 416 |
+
if isinstance(node, ast.BinOp):
|
| 417 |
+
a = eval_node(node.left)
|
| 418 |
+
b = eval_node(node.right)
|
| 419 |
+
if isinstance(node.op, ast.Add):
|
| 420 |
+
return run_prefix("float16.add", {"a": a, "b": b})
|
| 421 |
+
if isinstance(node.op, ast.Sub):
|
| 422 |
+
b_flip = b ^ 0x8000
|
| 423 |
+
return run_prefix("float16.sub", {"a": a, "b": b_flip})
|
| 424 |
+
if isinstance(node.op, ast.Mult):
|
| 425 |
+
return run_prefix("float16.mul", {"a": a, "b": b})
|
| 426 |
+
if isinstance(node.op, ast.Div):
|
| 427 |
+
return run_prefix("float16.div", {"a": a, "b": b})
|
| 428 |
+
if isinstance(node.op, ast.Pow):
|
| 429 |
+
return run_prefix("float16.pow", {"a": a, "b": b})
|
| 430 |
+
raise RuntimeError("unsupported binary operator")
|
| 431 |
+
if isinstance(node, ast.Call):
|
| 432 |
+
if not isinstance(node.func, ast.Name):
|
| 433 |
+
raise RuntimeError("unsupported function")
|
| 434 |
+
fname = node.func.id
|
| 435 |
+
if len(node.args) != 1:
|
| 436 |
+
raise RuntimeError(f"{fname} expects one argument")
|
| 437 |
+
x = eval_node(node.args[0])
|
| 438 |
+
if fname == "sqrt":
|
| 439 |
+
return run_prefix("float16.sqrt", {"x": x})
|
| 440 |
+
if fname == "rsqrt":
|
| 441 |
+
return run_prefix("float16.rsqrt", {"x": x})
|
| 442 |
+
if fname == "exp":
|
| 443 |
+
return run_prefix("float16.exp", {"x": x})
|
| 444 |
+
if fname in ("ln", "log"):
|
| 445 |
+
return run_prefix("float16.ln", {"x": x})
|
| 446 |
+
if fname == "log2":
|
| 447 |
+
return run_prefix("float16.log2", {"x": x})
|
| 448 |
+
if fname == "sin":
|
| 449 |
+
return run_prefix("float16.sin", {"x": x})
|
| 450 |
+
if fname == "cos":
|
| 451 |
+
return run_prefix("float16.cos", {"x": x})
|
| 452 |
+
if fname == "tan":
|
| 453 |
+
return run_prefix("float16.tan", {"x": x})
|
| 454 |
+
if fname == "tanh":
|
| 455 |
+
return run_prefix("float16.tanh", {"x": x})
|
| 456 |
+
if fname == "abs":
|
| 457 |
+
return run_prefix("float16.abs", {"x": x})
|
| 458 |
+
if fname == "neg":
|
| 459 |
+
return run_prefix("float16.neg", {"x": x})
|
| 460 |
+
raise RuntimeError(f"unsupported function: {fname}")
|
| 461 |
+
raise RuntimeError("unsupported expression")
|
| 462 |
+
|
| 463 |
+
out_bits = eval_node(tree)
|
| 464 |
+
if total_gates == 0:
|
| 465 |
+
if force_gate_eval:
|
| 466 |
+
# Route constants through float16.add with +0 to ensure gate-level evaluation.
|
| 467 |
+
out_bits = run_prefix("float16.add", {"a": out_bits, "b": 0})
|
| 468 |
+
else:
|
| 469 |
+
non_gate_events.append("constant_expression_no_gates")
|
| 470 |
+
return EvalResult(
|
| 471 |
+
bits=int_to_bits(out_bits, 16),
|
| 472 |
+
elapsed_s=total_elapsed,
|
| 473 |
+
gates_evaluated=total_gates,
|
| 474 |
+
non_gate_events=non_gate_events,
|
| 475 |
+
)
|
| 476 |
+
|
| 477 |
+
|
| 478 |
+
def main() -> int:
|
| 479 |
+
parser = argparse.ArgumentParser(description="Gate-level calculator for threshold-calculus")
|
| 480 |
+
parser.add_argument("prefix", nargs="?", default="", help="Circuit prefix (e.g., float16.add) or expression")
|
| 481 |
+
parser.add_argument("values", nargs="*", help="Input values (float for float16, int otherwise)")
|
| 482 |
+
parser.add_argument("--model", default="./arithmetic.safetensors", help="Path to safetensors model")
|
| 483 |
+
parser.add_argument("--out-bits", type=int, default=16, help="Number of output bits")
|
| 484 |
+
parser.add_argument("--inputs", nargs="*", help="Explicit inputs as name=value (e.g., a=0x3c00)")
|
| 485 |
+
parser.add_argument("--hex", action="store_true", help="Parse numeric inputs as hex")
|
| 486 |
+
parser.add_argument("--expr", help="Evaluate expression using float16 circuits")
|
| 487 |
+
parser.add_argument("--json", action="store_true", help="Output JSON result")
|
| 488 |
+
parser.add_argument("--strict", action="store_true", help="Warn if any non-gate path is used")
|
| 489 |
+
args = parser.parse_args()
|
| 490 |
+
|
| 491 |
+
calc = ThresholdCalculator(args.model)
|
| 492 |
+
|
| 493 |
+
def emit_result(prefix: str, out_int: int, result: EvalResult, expr: Optional[str] = None) -> int:
|
| 494 |
+
out_float = float16_bits_to_float(out_int) if len(result.bits) == 16 else None
|
| 495 |
+
if args.strict and result.non_gate_events:
|
| 496 |
+
print(f"STRICT WARNING: non-gate path used: {result.non_gate_events}")
|
| 497 |
+
if args.json:
|
| 498 |
+
payload = {
|
| 499 |
+
"prefix": prefix,
|
| 500 |
+
"expr": expr,
|
| 501 |
+
"bits": f"0x{out_int:04x}",
|
| 502 |
+
"float16": out_float,
|
| 503 |
+
"gates": result.gates_evaluated,
|
| 504 |
+
"elapsed_s": result.elapsed_s,
|
| 505 |
+
"non_gate_events": result.non_gate_events,
|
| 506 |
+
}
|
| 507 |
+
print(json.dumps(payload))
|
| 508 |
+
else:
|
| 509 |
+
if expr:
|
| 510 |
+
print(f"expr={expr}")
|
| 511 |
+
print(f"bits=0x{out_int:04x} float16={out_float}")
|
| 512 |
+
print(f"gates={result.gates_evaluated} elapsed_s={result.elapsed_s:.4f}")
|
| 513 |
+
return 0
|
| 514 |
+
|
| 515 |
+
if args.expr or (args.prefix and not args.values and not args.inputs and looks_like_expression(args.prefix)):
|
| 516 |
+
expr = args.expr if args.expr else args.prefix
|
| 517 |
+
result = calc.evaluate_expr(expr)
|
| 518 |
+
out_int = bits_to_int(result.bits)
|
| 519 |
+
return emit_result("expr", out_int, result, expr=expr)
|
| 520 |
+
|
| 521 |
+
if not args.prefix:
|
| 522 |
+
raise RuntimeError("Provide a circuit prefix or use --expr")
|
| 523 |
+
|
| 524 |
+
if args.inputs:
|
| 525 |
+
inputs: Dict[str, object] = {}
|
| 526 |
+
for item in args.inputs:
|
| 527 |
+
if "=" not in item:
|
| 528 |
+
raise RuntimeError("inputs must be name=value")
|
| 529 |
+
key, val = item.split("=", 1)
|
| 530 |
+
if args.hex or val.startswith("0x"):
|
| 531 |
+
inputs[key] = int(val, 16)
|
| 532 |
+
else:
|
| 533 |
+
try:
|
| 534 |
+
inputs[key] = int(val)
|
| 535 |
+
except ValueError:
|
| 536 |
+
inputs[key] = float(val)
|
| 537 |
+
result = calc.evaluate_prefix(args.prefix, inputs, out_bits=args.out_bits)
|
| 538 |
+
out_int = bits_to_int(result.bits)
|
| 539 |
+
print(f"bits={result.bits}")
|
| 540 |
+
print(f"int=0x{out_int:0{(args.out_bits + 3) // 4}x}")
|
| 541 |
+
if args.out_bits == 16:
|
| 542 |
+
pass
|
| 543 |
+
return emit_result(args.prefix, out_int, result)
|
| 544 |
+
|
| 545 |
+
# Convenience mode for float16 binary/unary
|
| 546 |
+
prefix = args.prefix
|
| 547 |
+
if prefix.startswith("float16."):
|
| 548 |
+
op = prefix.split(".", 1)[1]
|
| 549 |
+
if op == "pow":
|
| 550 |
+
if len(args.values) != 2:
|
| 551 |
+
raise RuntimeError("float16.pow requires two values")
|
| 552 |
+
out, result = calc.float16_pow(float(args.values[0]), float(args.values[1]))
|
| 553 |
+
elif op in ("add", "sub", "mul", "div"):
|
| 554 |
+
if len(args.values) != 2:
|
| 555 |
+
raise RuntimeError(f"{prefix} requires two values")
|
| 556 |
+
out, result = calc.float16_binop(op, float(args.values[0]), float(args.values[1]))
|
| 557 |
+
else:
|
| 558 |
+
if len(args.values) != 1:
|
| 559 |
+
raise RuntimeError(f"{prefix} requires one value")
|
| 560 |
+
out, result = calc.float16_unary(op, float(args.values[0]))
|
| 561 |
+
out_bits = bits_to_int(result.bits)
|
| 562 |
+
return emit_result(prefix, out_bits, result)
|
| 563 |
+
|
| 564 |
+
raise RuntimeError("Provide --inputs for non-float16 circuits")
|
| 565 |
+
|
| 566 |
+
|
| 567 |
+
if __name__ == "__main__":
|
| 568 |
+
raise SystemExit(main())
|
requirements.txt
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
gradio
|
| 2 |
+
torch
|
| 3 |
+
safetensors
|