torchlogix.Circuit

class torchlogix.Circuit(n_inputs, input_shape, gates=<factory>, outputs=<factory>, output_shape=<factory>, sum_nodes=<factory>)[source]
__init__(n_inputs, input_shape, gates=<factory>, outputs=<factory>, output_shape=<factory>, sum_nodes=<factory>)

Methods

__init__(n_inputs, input_shape[, gates, ...])

bypass_wires()

compile([opt_level, pack_bits])

Write C code to a temp file, compile to a shared library, and load it.

constant_fold_gates()

Evaluate gates that have constant inputs and replace them with CONST_TRUE or CONST_FALSE gates as appropriate.

constant_fold_sum_reductions()

For each SumReduction, fold CONST_TRUE / CONST_FALSE inputs directly into beta, leaving only genuinely variable inputs in input_ids.

dedup()

Structural hashing / common-subexpression elimination.

eliminate_dead_gates()

Remove gates that do not contribute to the output (i.e. not on a path from any output ID back to an input ID).

from_dict(data)

Create a Circuit instance from a (JSON-deserialized) dictionary.

from_fx_graph(gm, input_shape)

Build a Circuit directly from a folded FX graph.

from_json_file(file_path)

Load a Circuit instance from a JSON file.

from_model(model, input_shape)

Build a Circuit from a PyTorch model by tracing and folding it.

fuse_not_inputs()

Absorb NOT gates into their single downstream consumer.

get_c_code([inline_single_use, pack_bits])

Generate a self-contained C function that evaluates the circuit.

get_verilog_code([inline_single_use])

Generate a Verilog module that implements the circuit.

simplify([n_max])

to_and_inverter_graph()

to_dict()

Convert the circuit representation to a JSON-serializable format.

write_c_code(path)

Write the generated C code to a file.

write_json(path)

Write the circuit representation to a JSON file.

write_to_aiger_file([path])

write_verilog_code(path)

Write the generated Verilog code to a file.

Attributes

n_inputs

input_shape

gates

outputs

output_shape

sum_nodes

n_inputs: int
input_shape: list[int]
gates: list[Gate]
outputs: list[int]
output_shape: list[int]
sum_nodes: list[SumReduction]
to_and_inverter_graph()[source]
write_to_aiger_file(path='circuit.aig')[source]
classmethod from_model(model, input_shape)[source]

Build a Circuit from a PyTorch model by tracing and folding it.

The model should be in export mode (if applicable) and should have been traced and folded with the appropriate utilities to ensure the FX graph is in the expected form.

Return type:

Circuit

classmethod from_fx_graph(gm, input_shape)[source]

Build a Circuit directly from a folded FX graph.

This is the core logic for walking the FX graph and constructing the flat gate list. Assumptions (satisfied after constant_fold_views): - Exactly one placeholder node (‘input’) - Wiring is done via aten.index.Tensor with folded constant index tensors - LUT dispatch is a cascade of aten.eq + aten.where nodes - Layers are connected by further aten.index.Tensor nodes

Return type:

Circuit

simplify(n_max=1000)[source]
Return type:

None

fuse_not_inputs()[source]

Absorb NOT gates into their single downstream consumer.

Return type:

None

Recognises patterns that arise when native-torch boolean ops are used instead of all 16 gates, and folds them into the equivalent single-gate form:

AND(x, NOT_1use(y)) -> AND_NOT_B(x, y) AND(NOT_1use(x), y) -> AND_NOT_A(x, y) OR(x, NOT_1use(y)) -> OR_NOT_B(x, y) OR(NOT_1use(x), y) -> OR_NOT_A(x, y) NOT(AND(x, y))_1use -> NAND(x, y) NOT(OR(x, y))_1use -> NOR(x, y) NOT(XOR(x, y))_1use -> XNOR(x, y)

After fusion the absorbed NOT gates become dead and are removed by the next eliminate_dead_gates() call (which simplify() already calls).

eliminate_dead_gates()[source]

Remove gates that do not contribute to the output (i.e. not on a path from any output ID back to an input ID).

Return type:

None

constant_fold_sum_reductions()[source]

For each SumReduction, fold CONST_TRUE / CONST_FALSE inputs directly into beta, leaving only genuinely variable inputs in input_ids.

After this pass, a fully-folded reduction has input_ids == [] and beta encodes the entire sum; codegen emits a constant rather than a loop. output_ids is rebuilt from the remaining live inputs so that eliminate_dead_gates can remove the constant gates that were folded away.

Return type:

None

constant_fold_gates()[source]

Evaluate gates that have constant inputs and replace them with CONST_TRUE or CONST_FALSE gates as appropriate. This can simplify the circuit and reduce the number of gates.

Return type:

None

bypass_wires()[source]
Return type:

None

Eliminate trivial aliases:

WIRE(x) -> x NOT(NOT(x)) -> x

Rewrites all fanins/output IDs transitively and removes dead alias gates.

This pass is intentionally conservative and cheap.

dedup()[source]

Structural hashing / common-subexpression elimination.

Return type:

None

Deduplicates gates with identical:

(op, in0, in1)

Commutative ops are canonicalized so:

AND(a,b) == AND(b,a)

After deduplication, all fanins/output IDs are rewritten to the canonical representative and duplicate gates are removed.

compile(opt_level=1, pack_bits=None)[source]

Write C code to a temp file, compile to a shared library, and load it. After calling this, circuit(x) will use the compiled implementation.

Return type:

None

__call__(input, use_compiled=False)[source]

Evaluate the circuit on a given input tensor (shape: batch x n_inputs).

Uses the compiled C library when available (after compile()), otherwise evaluates the gate list in Python.

Returns a tensor of shape (batch, len(outputs)). The dtype is determined by _c_output_dtype over the output sum nodes: uint{N}_t when all tau=1, float when tau≠1, bool when there are no sum nodes in outputs.

Attention: For a fair performance comparison, the compiled code does not do type conversions or looping over batches. Instead, it expects numpy inputs of the correct shape (batch dim must match number of packed bits)

Return type:

Tensor

get_c_code(inline_single_use=False, pack_bits=None)[source]

Generate a self-contained C function that evaluates the circuit.

inline_single_use=True (default): gates used by only one other gate are inlined into their parent’s expression rather than emitted as variables. This eliminates most temporaries and makes each output a single expression tree rooted at the inputs.

Return type:

str

to_dict()[source]

Convert the circuit representation to a JSON-serializable format.

Return type:

dict

classmethod from_dict(data)[source]

Create a Circuit instance from a (JSON-deserialized) dictionary.

Return type:

Circuit

classmethod from_json_file(file_path)[source]

Load a Circuit instance from a JSON file.

Return type:

Circuit

get_verilog_code(inline_single_use=False)[source]

Generate a Verilog module that implements the circuit.

Return type:

str

Each gate becomes a continuous assignment:

wire g<id> = <expr>;

Outputs are assigned to an output bus:

assign out[k] = <expr>;

inline_single_use=True: single-use gates are folded into their parent expression rather than named wires (same semantics as in emit_c).

write_c_code(path)[source]

Write the generated C code to a file.

Return type:

None

write_verilog_code(path)[source]

Write the generated Verilog code to a file.

Return type:

None

write_json(path)[source]

Write the circuit representation to a JSON file.

Return type:

None

__init__(n_inputs, input_shape, gates=<factory>, outputs=<factory>, output_shape=<factory>, sum_nodes=<factory>)