Folds three previously-separate pieces into one preliminary-example repo for the HAHACS thesis: - thesis/ (submodule) → gitea Thesis.git — the PhD proposal - fret-pipeline/ — FRET requirements to AIGER controller (was ~/Documents/fret_processing/; prior single-commit history abandoned per user decision) - plant-model/ — 10-state PKE + lumped T/H PWR model (was ~/Documents/PKE_Playground/; never version-controlled before) - presentations/2026DICE/ (submodule) → gitea 2026DICE.git - reachability/, hardware/ — empty placeholders for Thrust 3 and HIL - docs/architecture.md — how the discrete and continuous layers compose - claude_memory/ — session notes and scratch knowledge pattern Plant model refactored to thesis naming (x, plant, u, ref); pke_th_rhs now takes u as an explicit arg instead of reading rho_ext from the params struct. First two controllers built to the contract u = ctrl_<mode>(t, x, plant, ref): ctrl_null (baseline) and ctrl_operation (stabilizing, proportional on T_avg). Validated under a 100% -> 80% Q_sg step: ctrl_operation reduces steady-state T_avg drift ~47% vs. the unforced plant. Root CLAUDE.md emphasizes that CLAUDE.md files are living documents and that any knowledge not captured before a session ends is lost forever; claude_memory/ holds the session-level notes that haven't stabilized enough to graduate into a CLAUDE.md. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
168 lines
4.9 KiB
Python
168 lines
4.9 KiB
Python
#!/usr/bin/env python3
|
|
"""Convert an ASCII AIGER (.aag) file to Graphviz DOT format.
|
|
|
|
Usage: python3 scripts/aag2dot.py circuits/spec.aag | dot -Tsvg -o diagrams/spec.svg
|
|
"""
|
|
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
|
|
def parse_aag(text: str) -> dict:
|
|
lines = text.strip().split('\n')
|
|
header = lines[0].split()
|
|
assert header[0] == 'aag'
|
|
max_var = int(header[1])
|
|
n_inputs = int(header[2])
|
|
n_latches = int(header[3])
|
|
n_outputs = int(header[4])
|
|
n_ands = int(header[5])
|
|
|
|
idx = 1
|
|
inputs = []
|
|
for i in range(n_inputs):
|
|
inputs.append(int(lines[idx]))
|
|
idx += 1
|
|
|
|
latches = []
|
|
for i in range(n_latches):
|
|
parts = lines[idx].split()
|
|
latches.append((int(parts[0]), int(parts[1])))
|
|
idx += 1
|
|
|
|
outputs = []
|
|
for i in range(n_outputs):
|
|
outputs.append(int(lines[idx]))
|
|
idx += 1
|
|
|
|
ands = []
|
|
for i in range(n_ands):
|
|
parts = lines[idx].split()
|
|
ands.append((int(parts[0]), int(parts[1]), int(parts[2])))
|
|
idx += 1
|
|
|
|
# Symbol table
|
|
input_names = {}
|
|
output_names = {}
|
|
latch_names = {}
|
|
while idx < len(lines):
|
|
line = lines[idx]
|
|
if line.startswith('i'):
|
|
parts = line.split(' ', 1)
|
|
input_names[int(parts[0][1:])] = parts[1]
|
|
elif line.startswith('o'):
|
|
parts = line.split(' ', 1)
|
|
output_names[int(parts[0][1:])] = parts[1]
|
|
elif line.startswith('l'):
|
|
parts = line.split(' ', 1)
|
|
latch_names[int(parts[0][1:])] = parts[1]
|
|
elif line.startswith('c'):
|
|
break
|
|
idx += 1
|
|
|
|
return {
|
|
'inputs': inputs,
|
|
'latches': latches,
|
|
'outputs': outputs,
|
|
'ands': ands,
|
|
'input_names': input_names,
|
|
'output_names': output_names,
|
|
'latch_names': latch_names,
|
|
}
|
|
|
|
|
|
def lit_node(lit: int) -> tuple[str, bool]:
|
|
"""Return (node_id, is_negated) for a literal."""
|
|
var = lit >> 1
|
|
negated = lit & 1
|
|
return f'n{var}', bool(negated)
|
|
|
|
|
|
def to_dot(aag: dict) -> str:
|
|
lines = ['digraph aiger {']
|
|
lines.append(' rankdir=LR;')
|
|
lines.append(' node [shape=record];')
|
|
lines.append('')
|
|
|
|
# Constant 0
|
|
lines.append(' n0 [label="0", shape=circle, style=filled, fillcolor=gray90];')
|
|
|
|
# Inputs
|
|
lines.append(' { rank=source;')
|
|
for i, inp in enumerate(aag['inputs']):
|
|
var = inp >> 1
|
|
name = aag['input_names'].get(i, f'i{i}')
|
|
lines.append(f' n{var} [label="{name}", shape=box, style=filled, fillcolor=lightblue];')
|
|
lines.append(' }')
|
|
|
|
# Latches
|
|
for i, (latch_lit, next_lit) in enumerate(aag['latches']):
|
|
var = latch_lit >> 1
|
|
name = aag['latch_names'].get(i, f'L{i}')
|
|
lines.append(f' n{var} [label="{name}", shape=box, style="filled,rounded", fillcolor=lightyellow];')
|
|
|
|
# AND gates
|
|
for lhs, rhs0, rhs1 in aag['ands']:
|
|
var = lhs >> 1
|
|
lines.append(f' n{var} [label="&", shape=circle, style=filled, fillcolor=white];')
|
|
|
|
# Outputs
|
|
lines.append(' { rank=sink;')
|
|
for i, out_lit in enumerate(aag['outputs']):
|
|
name = aag['output_names'].get(i, f'o{i}')
|
|
lines.append(f' out_{i} [label="{name}", shape=box, style=filled, fillcolor=lightgreen];')
|
|
lines.append(' }')
|
|
|
|
lines.append('')
|
|
|
|
# AND gate edges
|
|
for lhs, rhs0, rhs1 in aag['ands']:
|
|
lhs_var = lhs >> 1
|
|
for rhs in [rhs0, rhs1]:
|
|
src_node, negated = lit_node(rhs)
|
|
style = 'style=dashed, color=red' if negated else ''
|
|
label = 'label="~"' if negated else ''
|
|
attrs = ', '.join(filter(None, [style, label]))
|
|
if attrs:
|
|
lines.append(f' {src_node} -> n{lhs_var} [{attrs}];')
|
|
else:
|
|
lines.append(f' {src_node} -> n{lhs_var};')
|
|
|
|
# Latch feedback edges
|
|
for i, (latch_lit, next_lit) in enumerate(aag['latches']):
|
|
latch_var = latch_lit >> 1
|
|
src_node, negated = lit_node(next_lit)
|
|
style = 'style=dashed, color=red' if negated else ''
|
|
label = 'label="~"' if negated else ''
|
|
extra = 'constraint=false, '
|
|
attrs = ', '.join(filter(None, [extra + style, label]))
|
|
lines.append(f' {src_node} -> n{latch_var} [{attrs}];')
|
|
|
|
# Output edges
|
|
for i, out_lit in enumerate(aag['outputs']):
|
|
src_node, negated = lit_node(out_lit)
|
|
style = 'style=dashed, color=red' if negated else ''
|
|
label = 'label="~"' if negated else ''
|
|
attrs = ', '.join(filter(None, [style, label]))
|
|
if attrs:
|
|
lines.append(f' {src_node} -> out_{i} [{attrs}];')
|
|
else:
|
|
lines.append(f' {src_node} -> out_{i};')
|
|
|
|
lines.append('}')
|
|
return '\n'.join(lines)
|
|
|
|
|
|
def main():
|
|
if len(sys.argv) < 2:
|
|
print(f'Usage: {sys.argv[0]} <file.aag>', file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
text = Path(sys.argv[1]).read_text()
|
|
aag = parse_aag(text)
|
|
print(to_dot(aag))
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|