CharlesCNorton commited on
Commit
e669ef2
·
1 Parent(s): 5ad204f

Make inputs coverage strict and document full verbose runs

Browse files
Files changed (2) hide show
  1. README.md +2 -1
  2. eval.py +100 -42
README.md CHANGED
@@ -115,6 +115,7 @@ python eval.py
115
  ```
116
 
117
  Tests all circuits exhaustively. 8-bit operations test all 256 or 65,536 input combinations. Float16 tests cover special cases (NaN, Inf, ±0, subnormals) plus normal arithmetic.
 
118
 
119
  For coverage and input-routing validation:
120
 
@@ -122,7 +123,7 @@ For coverage and input-routing validation:
122
  python eval.py --coverage --inputs-coverage
123
  ```
124
 
125
- `--inputs-coverage` evaluates gates via their `.inputs` tensors (using seeded/randomized external inputs to complete missing dependencies). This is for coverage and routing sanity, not a correctness proof.
126
 
127
  ## Development History
128
 
 
115
  ```
116
 
117
  Tests all circuits exhaustively. 8-bit operations test all 256 or 65,536 input combinations. Float16 tests cover special cases (NaN, Inf, ±0, subnormals) plus normal arithmetic.
118
+ Eval runs full + verbose by default; there is no quick/verbose mode. Use --circuit to filter reported circuits.
119
 
120
  For coverage and input-routing validation:
121
 
 
123
  python eval.py --coverage --inputs-coverage
124
  ```
125
 
126
+ `--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.
127
 
128
  ## Development History
129
 
eval.py CHANGED
@@ -3,11 +3,8 @@
3
  Unified evaluator for threshold-calculus circuits.
4
 
5
  Usage:
6
- python eval.py # Run all tests
7
- python eval.py --category float16 # Run only float16 tests
8
  python eval.py --circuit float16.add # Run specific circuit
9
- python eval.py --quick # Quick mode (fewer test cases)
10
- python eval.py --verbose # Show all test details
11
  python eval.py --json # Output JSON for CI
12
  python eval.py --coverage # Show detailed coverage report
13
  python eval.py --inputs-coverage # Sweep all gates using .inputs tensors
@@ -160,7 +157,8 @@ def evaluate_circuit(ctx: EvalContext, prefix: str, input_bits: torch.Tensor,
160
  return torch.stack(outputs, dim=-1) if outputs else torch.tensor([])
161
 
162
 
163
- def seed_external_signals(ctx: EvalContext, rng: random.Random) -> Dict[int, float]:
 
164
  """Seed external input signals and constants with random 0/1 values."""
165
  signals: Dict[int, float] = {}
166
 
@@ -176,20 +174,68 @@ def seed_external_signals(ctx: EvalContext, rng: random.Random) -> Dict[int, flo
176
  if sid not in signals:
177
  signals[sid] = float(rng.getrandbits(1))
178
 
 
 
 
 
 
 
179
  return signals
180
 
181
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
182
  def evaluate_gates_from_inputs(ctx: EvalContext, signals: Dict[int, float],
183
- gate_list: Optional[List[str]] = None,
184
- rng: Optional[random.Random] = None,
185
- fill_missing: bool = False) -> Tuple[int, List[str], List[str], int]:
186
- """Evaluate gates using explicit .inputs tensors. Returns (evaluated, missing_inputs, unresolved, filled_ids)."""
187
  gates = gate_list if gate_list is not None else ctx.gates
188
  remaining = set(gates)
189
  missing_inputs: List[str] = []
190
  unresolved: List[str] = []
191
  evaluated = 0
192
- filled_ids = 0
193
 
194
  progress = True
195
  while progress and remaining:
@@ -205,7 +251,17 @@ def evaluate_gates_from_inputs(ctx: EvalContext, signals: Dict[int, float],
205
  continue
206
 
207
  input_ids = [int(x) for x in ctx.tensors[inputs_key].tolist()]
208
- if not all(sid in signals for sid in input_ids):
 
 
 
 
 
 
 
 
 
 
209
  continue
210
 
211
  weight = ctx.tensors[weight_key].tolist()
@@ -216,6 +272,8 @@ def evaluate_gates_from_inputs(ctx: EvalContext, signals: Dict[int, float],
216
  gate_id = ctx.name_to_id.get(gate)
217
  if gate_id is not None:
218
  signals[gate_id] = out
 
 
219
 
220
  if inputs_key in ctx.tensors:
221
  ctx.tested_tensors.add(inputs_key)
@@ -228,26 +286,10 @@ def evaluate_gates_from_inputs(ctx: EvalContext, signals: Dict[int, float],
228
  remaining.remove(gate)
229
  progress = True
230
 
231
- if not progress and remaining and fill_missing and rng is not None:
232
- missing_ids = set()
233
- for gate in remaining:
234
- inputs_key = f"{gate}.inputs"
235
- if inputs_key not in ctx.tensors:
236
- continue
237
- input_ids = [int(x) for x in ctx.tensors[inputs_key].tolist()]
238
- for sid in input_ids:
239
- if sid not in signals:
240
- missing_ids.add(sid)
241
- if missing_ids:
242
- for sid in missing_ids:
243
- signals[sid] = float(rng.getrandbits(1))
244
- filled_ids += len(missing_ids)
245
- progress = True
246
-
247
  if remaining:
248
  unresolved = sorted(remaining)
249
 
250
- return evaluated, missing_inputs, unresolved, filled_ids
251
 
252
 
253
  # =============================================================================
@@ -341,11 +383,12 @@ def inputs_coverage_sweep(ctx: EvalContext, seed: int = 0, verbose: bool = False
341
  quiet: bool = False) -> None:
342
  """Evaluate all gates via .inputs to improve coverage."""
343
  rng = random.Random(seed)
344
- signals = seed_external_signals(ctx, rng)
 
 
 
345
 
346
- evaluated, missing_inputs, unresolved, filled_ids = evaluate_gates_from_inputs(
347
- ctx, signals, rng=rng, fill_missing=True
348
- )
349
  total = len(ctx.gates)
350
 
351
  orphan_tensors = 0
@@ -357,12 +400,17 @@ def inputs_coverage_sweep(ctx: EvalContext, seed: int = 0, verbose: bool = False
357
  ctx.tested_tensors.add(name)
358
  orphan_tensors += 1
359
 
 
 
 
 
 
 
 
360
  if quiet:
361
  return
362
 
363
  print(f"\nInput-coverage sweep: evaluated {evaluated}/{total} gates")
364
- if filled_ids:
365
- print(f" Filled missing signal IDs: {filled_ids}")
366
  if orphan_tensors:
367
  print(f" Orphan tensors touched: {orphan_tensors}")
368
  if missing_inputs:
@@ -444,6 +492,19 @@ def bits_to_int_msb(bits: List[float]) -> int:
444
  return val
445
 
446
 
 
 
 
 
 
 
 
 
 
 
 
 
 
447
  def int_to_bits(val: int, n: int, signed: bool = False) -> List[float]:
448
  """Convert integer to n bits."""
449
  if signed and val < 0:
@@ -2151,10 +2212,7 @@ def print_summary(results: List[TestResult], ctx: EvalContext,
2151
  def main():
2152
  parser = argparse.ArgumentParser(description="Unified evaluator for threshold-calculus circuits")
2153
  parser.add_argument("--model", default="./arithmetic.safetensors", help="Path to model file")
2154
- parser.add_argument("--category", "-c", action="append", help="Test specific category (can repeat)")
2155
  parser.add_argument("--circuit", action="append", help="Test specific circuit (can repeat)")
2156
- parser.add_argument("--quick", "-q", action="store_true", help="Quick mode (fewer test cases)")
2157
- parser.add_argument("--verbose", "-v", action="store_true", help="Verbose output")
2158
  parser.add_argument("--json", "-j", action="store_true", help="Output JSON for CI")
2159
  parser.add_argument("--coverage", action="store_true", help="Show detailed coverage")
2160
  parser.add_argument("--inputs-coverage", action="store_true", help="Sweep all gates using .inputs tensors")
@@ -2179,15 +2237,15 @@ def main():
2179
  signals=signals,
2180
  name_to_id=name_to_id,
2181
  id_to_name=id_to_name,
2182
- verbose=args.verbose,
2183
- quick=args.quick,
2184
  )
2185
 
2186
  start = time.time()
2187
- results = run_tests(ctx, categories=args.category, circuits=args.circuit)
2188
 
2189
  if args.coverage or args.inputs_coverage:
2190
- inputs_coverage_sweep(ctx, seed=0, verbose=args.verbose, quiet=args.json)
2191
  elapsed = time.time() - start
2192
 
2193
  if args.json:
@@ -2200,7 +2258,7 @@ def main():
2200
  }
2201
  print(json.dumps(output, indent=2))
2202
  else:
2203
- print_summary(results, ctx, elapsed, verbose=args.verbose or args.coverage)
2204
 
2205
  # Return exit code based on failures
2206
  failed = [r for r in results if not r.success]
 
3
  Unified evaluator for threshold-calculus circuits.
4
 
5
  Usage:
6
+ python eval.py # Run all tests (always full + verbose)
 
7
  python eval.py --circuit float16.add # Run specific circuit
 
 
8
  python eval.py --json # Output JSON for CI
9
  python eval.py --coverage # Show detailed coverage report
10
  python eval.py --inputs-coverage # Sweep all gates using .inputs tensors
 
157
  return torch.stack(outputs, dim=-1) if outputs else torch.tensor([])
158
 
159
 
160
+ def seed_external_signals(ctx: EvalContext, rng: random.Random,
161
+ extra_names: Optional[List[str]] = None) -> Dict[int, float]:
162
  """Seed external input signals and constants with random 0/1 values."""
163
  signals: Dict[int, float] = {}
164
 
 
174
  if sid not in signals:
175
  signals[sid] = float(rng.getrandbits(1))
176
 
177
+ if extra_names:
178
+ for name in extra_names:
179
+ sid = ctx.name_to_id.get(name)
180
+ if sid is not None and sid not in signals:
181
+ signals[sid] = float(rng.getrandbits(1))
182
+
183
  return signals
184
 
185
 
186
+ def resolve_alias_target(name: str, gates: set) -> Optional[str]:
187
+ """Resolve common alias signal names to actual gate names."""
188
+ if name in gates:
189
+ return name
190
+ cand = name + ".layer2"
191
+ if cand in gates:
192
+ return cand
193
+ if name.endswith(".sum"):
194
+ cand = name[:-4] + ".xor2.layer2"
195
+ if cand in gates:
196
+ return cand
197
+ if name.endswith(".cout"):
198
+ for suffix in [".or_carry", ".carry_or"]:
199
+ cand = name[:-5] + suffix
200
+ if cand in gates:
201
+ return cand
202
+ return None
203
+
204
+
205
+ def build_alias_maps(ctx: EvalContext) -> Tuple[Dict[int, int], Dict[int, List[int]]]:
206
+ """Build alias maps from orphan signals to actual gate outputs."""
207
+ gates = set(ctx.gates)
208
+ alias_to_gate: Dict[int, int] = {}
209
+ gate_to_alias: Dict[int, List[int]] = {}
210
+
211
+ for name, sid in ctx.name_to_id.items():
212
+ if name in ("#0", "#1"):
213
+ continue
214
+ if name.startswith("$") or ".$" in name:
215
+ continue
216
+ if name in gates:
217
+ continue
218
+ target = resolve_alias_target(name, gates)
219
+ if not target:
220
+ continue
221
+ target_id = ctx.name_to_id.get(target)
222
+ if target_id is None:
223
+ continue
224
+ alias_to_gate[sid] = target_id
225
+ gate_to_alias.setdefault(target_id, []).append(sid)
226
+
227
+ return alias_to_gate, gate_to_alias
228
+
229
+
230
  def evaluate_gates_from_inputs(ctx: EvalContext, signals: Dict[int, float],
231
+ gate_list: Optional[List[str]] = None) -> Tuple[int, List[str], List[str]]:
232
+ """Evaluate gates using explicit .inputs tensors. Returns (evaluated, missing_inputs, unresolved)."""
 
 
233
  gates = gate_list if gate_list is not None else ctx.gates
234
  remaining = set(gates)
235
  missing_inputs: List[str] = []
236
  unresolved: List[str] = []
237
  evaluated = 0
238
+ alias_to_gate, gate_to_alias = build_alias_maps(ctx)
239
 
240
  progress = True
241
  while progress and remaining:
 
251
  continue
252
 
253
  input_ids = [int(x) for x in ctx.tensors[inputs_key].tolist()]
254
+ ready = True
255
+ for sid in input_ids:
256
+ if sid in signals:
257
+ continue
258
+ alias_gate = alias_to_gate.get(sid)
259
+ if alias_gate is not None and alias_gate in signals:
260
+ signals[sid] = signals[alias_gate]
261
+ continue
262
+ ready = False
263
+ break
264
+ if not ready:
265
  continue
266
 
267
  weight = ctx.tensors[weight_key].tolist()
 
272
  gate_id = ctx.name_to_id.get(gate)
273
  if gate_id is not None:
274
  signals[gate_id] = out
275
+ for alias_id in gate_to_alias.get(gate_id, []):
276
+ signals[alias_id] = out
277
 
278
  if inputs_key in ctx.tensors:
279
  ctx.tested_tensors.add(inputs_key)
 
286
  remaining.remove(gate)
287
  progress = True
288
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
289
  if remaining:
290
  unresolved = sorted(remaining)
291
 
292
+ return evaluated, missing_inputs, unresolved
293
 
294
 
295
  # =============================================================================
 
383
  quiet: bool = False) -> None:
384
  """Evaluate all gates via .inputs to improve coverage."""
385
  rng = random.Random(seed)
386
+ extra_names = []
387
+ for names in EXTERNAL_INPUT_OVERRIDES.values():
388
+ extra_names.extend(names)
389
+ signals = seed_external_signals(ctx, rng, extra_names=extra_names)
390
 
391
+ evaluated, missing_inputs, unresolved = evaluate_gates_from_inputs(ctx, signals)
 
 
392
  total = len(ctx.gates)
393
 
394
  orphan_tensors = 0
 
400
  ctx.tested_tensors.add(name)
401
  orphan_tensors += 1
402
 
403
+ # Hard failure on unresolved inputs
404
+ if missing_inputs or unresolved:
405
+ raise RuntimeError(
406
+ f"Unresolved inputs in input-coverage sweep: "
407
+ f"missing_inputs={len(missing_inputs)} unresolved={len(unresolved)}"
408
+ )
409
+
410
  if quiet:
411
  return
412
 
413
  print(f"\nInput-coverage sweep: evaluated {evaluated}/{total} gates")
 
 
414
  if orphan_tensors:
415
  print(f" Orphan tensors touched: {orphan_tensors}")
416
  if missing_inputs:
 
492
  return val
493
 
494
 
495
+ # Explicit external signals needed to resolve orphan wiring (per circuit)
496
+ EXTERNAL_INPUT_OVERRIDES = {
497
+ "arithmetic.multiplier8x8": [
498
+ "arithmetic.multiplier8x8.stage0.bit9.ha2.sum.layer2",
499
+ "arithmetic.multiplier8x8.stage1.bit10.ha2.sum.layer2",
500
+ "arithmetic.multiplier8x8.stage2.bit11.ha2.sum.layer2",
501
+ "arithmetic.multiplier8x8.stage3.bit12.ha2.sum.layer2",
502
+ "arithmetic.multiplier8x8.stage4.bit13.ha2.sum.layer2",
503
+ "arithmetic.multiplier8x8.stage5.bit14.ha2.sum.layer2",
504
+ ],
505
+ }
506
+
507
+
508
  def int_to_bits(val: int, n: int, signed: bool = False) -> List[float]:
509
  """Convert integer to n bits."""
510
  if signed and val < 0:
 
2212
  def main():
2213
  parser = argparse.ArgumentParser(description="Unified evaluator for threshold-calculus circuits")
2214
  parser.add_argument("--model", default="./arithmetic.safetensors", help="Path to model file")
 
2215
  parser.add_argument("--circuit", action="append", help="Test specific circuit (can repeat)")
 
 
2216
  parser.add_argument("--json", "-j", action="store_true", help="Output JSON for CI")
2217
  parser.add_argument("--coverage", action="store_true", help="Show detailed coverage")
2218
  parser.add_argument("--inputs-coverage", action="store_true", help="Sweep all gates using .inputs tensors")
 
2237
  signals=signals,
2238
  name_to_id=name_to_id,
2239
  id_to_name=id_to_name,
2240
+ verbose=True,
2241
+ quick=False,
2242
  )
2243
 
2244
  start = time.time()
2245
+ results = run_tests(ctx, categories=None, circuits=args.circuit)
2246
 
2247
  if args.coverage or args.inputs_coverage:
2248
+ inputs_coverage_sweep(ctx, seed=0, verbose=True, quiet=args.json)
2249
  elapsed = time.time() - start
2250
 
2251
  if args.json:
 
2258
  }
2259
  print(json.dumps(output, indent=2))
2260
  else:
2261
+ print_summary(results, ctx, elapsed, verbose=True)
2262
 
2263
  # Return exit code based on failures
2264
  failed = [r for r in results if not r.success]