bfcl module

Python library for working with circuit definitions represented using the Bristol Fashion.

class bfcl.bfcl.operation(iterable=(), /)

Bases: logical.logical.logical

Data structure for an individual gate operation. This class is derived from the logical class exported by the logical library. This module indirectly imports the logical class via the op synonym defined in the circuit library. See the documentation for the logical class for more information on this data structure and how logical operations are represented as tuples of integers.

token_op_pairs = [('LID', (0, 1)), ('INV', (1, 0)), ('FLS', (0, 0, 0, 0)), ('AND', (0, 0, 0, 1)), ('NIM', (0, 0, 1, 0)), ('FST', (0, 0, 1, 1)), ('NIF', (0, 1, 0, 0)), ('SND', (0, 1, 0, 1)), ('XOR', (0, 1, 1, 0)), ('LOR', (0, 1, 1, 1)), ('NOR', (1, 0, 0, 0)), ('XNR', (1, 0, 0, 1)), ('NSD', (1, 0, 1, 0)), ('LIF', (1, 0, 1, 1)), ('NFT', (1, 1, 0, 0)), ('IMP', (1, 1, 0, 1)), ('NND', (1, 1, 1, 0)), ('TRU', (1, 1, 1, 1))]

List of pairs of string representations and corresponding unary/binary operations.

static parse(token: str) bfcl.bfcl.operation

Parse a Bristol Fashion circuit gate operator token.

>>> operation.parse('AND')
(0, 0, 0, 1)
emit() str

Emit a Bristol Fashion operation token.

>>> operation((0, 1, 1, 0)).emit()
'XOR'
bfcl.bfcl.op

alias of bfcl.bfcl.operation

class bfcl.bfcl.gate(wire_in_count: Optional[int] = None, wire_out_count: Optional[int] = None, wire_in_index: Optional[Sequence[int]] = None, wire_out_index: Optional[Sequence[int]] = None, operation: Optional[bfcl.bfcl.operation] = None)

Bases: object

Data structure for an individual circuit logic gate.

>>> gate.parse('2 1 0 1 15 AND').emit()
'2 1 0 1 15 AND'
>>> gate.parse('1 1 100 200 INV').emit()
'1 1 100 200 INV'
static parse(tokens) bfcl.bfcl.gate

Parse a Bristol Fashion gate string or token list.

emit() str

Emit a Bristol Fashion string for this gate.

class bfcl.bfcl.bfc(raw=None)

Bases: object

Data structure for circuits represented using the Bristol Fashion. A string representing a circuit that conforms to the Bristol Fashion syntax can be parsed into an instance of this class.

>>> circuit_string = ['7 36', '2 4 4', '1 1']
>>> circuit_string.extend(['2 1 0 1 15 AND', '2 1 2 3 16 AND'])
>>> circuit_string.extend(['2 1 15 16 8 AND', '2 1 4 5 22 AND'])
>>> circuit_string.extend(['2 1 6 7 23 AND', '2 1 22 23 9 AND'])
>>> circuit_string.extend(['2 1 8 9 35 AND'])
>>> circuit_string = "\n".join(circuit_string)
>>> c = bfc(circuit_string)

The string representation can be recovered from an instance of this class, as well.

>>> c.emit() == circuit_string
True
>>> for line in c.emit().split("\n"):
...     print(line)
7 36
2 4 4
1 1
2 1 0 1 15 AND
2 1 2 3 16 AND
2 1 15 16 8 AND
2 1 4 5 22 AND
2 1 6 7 23 AND
2 1 22 23 9 AND
2 1 8 9 35 AND

We could just add a ‘1 1 35 36 LID’ line, and increment ‘8 16’, but the force_id_outputs is perhaps not as lazy as it could be and performs a full bfc->`circuit_`->`bfc` conversion to get the identity gates, hence the wire renumbering. >>> for line in c.emit(force_id_outputs=True).split(”n”): … print(line) 8 16 2 4 4 1 1 2 1 0 1 8 AND 2 1 2 3 9 AND 2 1 8 9 10 AND 2 1 4 5 11 AND 2 1 6 7 12 AND 2 1 11 12 13 AND 2 1 10 13 14 AND 1 1 14 15 LID

A circuit can also be consructed using an instance of the circuit class defined in the circuit library (see the documentation for the circuit.circuit method defined as part of this class).

Common properties of the circuit can be found in the attributes of an instance.

>>> c.gate_count
7
>>> c.wire_count
36
>>> c.value_in_count
2
>>> c.value_in_length
[4, 4]
>>> c.value_out_count
1
>>> c.wire_in_count
8
>>> c.wire_in_index
[0, 1, 2, 3, 4, 5, 6, 7]
>>> c.wire_out_count
1
>>> c.wire_out_index
[35]

The individual gates are stored within a list consisting of zero or more instances of the gate class.

>>> (c.gate[0].wire_in_index, c.gate[0].wire_out_index)
([0, 1], [15])
>>> (c.gate[1].wire_in_index, c.gate[1].wire_out_index)
([2, 3], [16])
>>> (c.gate[2].wire_in_index, c.gate[2].wire_out_index)
([15, 16], [8])
>>> (c.gate[3].wire_in_index, c.gate[3].wire_out_index)
([4, 5], [22])
>>> (c.gate[4].wire_in_index, c.gate[4].wire_out_index)
([6, 7], [23])
>>> (c.gate[5].wire_in_index, c.gate[5].wire_out_index)
([22, 23], [9])
>>> (c.gate[6].wire_in_index, c.gate[6].wire_out_index)
([8, 9], [35])
>>> {c.gate[i].operation for i in range(7)} == {op.and_}
True

A circuit can also be evaluated an on a sequence of input bit vectors using the bfcl.evaluate method.

>>> from itertools import product
>>> inputs = list(product(*([[0, 1]]*4)))
>>> pairs = product(inputs, inputs)
>>> outputs = ([0]*255) + [1]
>>> [c.evaluate(p)[0][0] for p in pairs] == outputs
True
circuit(c: Optional[circuit.circuit.circuit] = None) Union[Type[None], circuit.circuit.circuit]

Populate this Bristol Fashion circuit instance using an instance of the circuit class defined in the circuit library.

>>> c_ = circuit_.circuit()
>>> c_.count()
0
>>> g0 = c_.gate(op.id_, is_input=True)
>>> g1 = c_.gate(op.id_, is_input=True)
>>> g2 = c_.gate(op.and_, [g0, g1])
>>> g3 = c_.gate(op.id_, [g2], is_output=True)
>>> c_.count()
4
>>> c = bfc(c_)
>>> c.emit().split("\n")
['2 4', '1 2', '1 1', '2 1 0 1 2 AND', '1 1 2 3 LID']
>>> c_reparsed = bfc(bfc(c_).circuit())
>>> c_reparsed.emit().split("\n")
['2 4', '1 2', '1 1', '2 1 0 1 2 AND', '1 1 2 3 LID']
parse(raw: str)

Parse a string representation of a circuit that conforms to the Bristol Fashion syntax.

>>> s = ['7 36', '2 4 4', '1 1']
>>> s.extend(['2 1 0 1 15 AND', '2 1 2 3 16 AND'])
>>> s.extend(['2 1 15 16 8 AND', '2 1 4 5 22 AND'])
>>> s.extend(['2 1 6 7 23 AND', '2 1 22 23 9 AND'])
>>> s.extend(['2 1 8 9 35 AND'])
>>> s = "\n".join(s)
>>> c = bfc()
>>> c.parse(s)
>>> for line in c.emit().split("\n"):
...     print(line)
7 36
2 4 4
1 1
2 1 0 1 15 AND
2 1 2 3 16 AND
2 1 15 16 8 AND
2 1 4 5 22 AND
2 1 6 7 23 AND
2 1 22 23 9 AND
2 1 8 9 35 AND
emit(force_id_outputs=False, progress=lambda _: ...) str

Emit a string representation of a Bristol Fashion circuit definition.

In the example below, a circuit object is first constructed using the circuit library.

>>> c_ = circuit_.circuit()
>>> c_.count()
0
>>> g0 = c_.gate(op.id_, is_input=True)
>>> g1 = c_.gate(op.id_, is_input=True)
>>> g2 = c_.gate(op.and_, [g0, g1])
>>> g3 = c_.gate(op.id_, [g2], is_output=True)

The c_ object above can be converted into an instance of the class circuit.

>>> c = bfc(c_)

This method can be used to emit a string representation of an object, where the string conforms to the Bristol Fashion syntax.

>>> c.emit().split("\n")
['2 4', '1 2', '1 1', '2 1 0 1 2 AND', '1 1 2 3 LID']
>>> c.emit(True).split("\n")
['2 4', '1 2', '1 1', '2 1 0 1 2 AND', '1 1 2 3 LID']
evaluate(inputs: Sequence[Sequence[int]]) Sequence[Sequence[int]]

Evaluate a circuit on a sequence of input bit vectors.

>>> s = ['7 36', '2 4 4', '1 1']
>>> s.extend(['2 1 0 1 15 AND', '2 1 2 3 16 AND'])
>>> s.extend(['2 1 15 16 8 AND', '2 1 4 5 22 AND'])
>>> s.extend(['2 1 6 7 23 AND', '2 1 22 23 9 AND'])
>>> s.extend(['2 1 8 9 35 AND'])
>>> c = bfc("\n".join(s))
>>> c.evaluate([[1, 0, 1, 1], [1, 1, 1, 0]])
[[0]]
>>> c.evaluate([[1, 1, 1, 1], [1, 1, 1, 1]])
[[1]]

The example below confirms that the circuit c defined above has correct behavior when evaluated on all compatible inputs (i.e., inputs consisting of a pair of 4-bit vectors).

>>> from itertools import product
>>> inputs = list(product(*([[0, 1]]*4)))
>>> pairs = product(inputs, inputs)
>>> outputs = ([0]*255) + [1]
>>> [c.evaluate(p)[0][0] for p in pairs] == outputs
True