circuitry module

Embedded domain-specific combinator library for assembling abstract definitions of logical circuits and for automatically synthesizing circuits from those definitions.

This library makes it possible both to construct circuits programmatically (see the documentation for the bit class) and to synthesize circuits in an automated manner from Python function definitions (see the documentation for the synthesize function).

class circuitry.circuitry.bit(value: int, gate: Optional[gate] = None)[source]

Bases: object

Class for representing an abstract bit. Such a bit can be interpreted concretely as a value, but it is also used to keep track of relationships between operators and to represent the individual wires within a circuit that is programmatically built up from gates that correspond to those operators.

The documentation for this class describes how circuits can be synthesized programmatically using the methods made available by this class. Consult the documentation for the synthesize function to learn how circuits can be synthesized automatically from Python function definitions.

When constructing a circuit programmatically, it is first necessary to introduce a new circuit object and to designate this object as the circuit that is being constructed.

>>> c = circuit()
>>> bit.circuit(c)

Subsequently, it is possible to instantiate subclasses of bit that serve specific roles (such as input, which represents an input value into the overall circuit being constructed). An operator method such as bit.and_ can be used to operate on bit instances (and, at the same time to introduce a gate into the circuit).

>>> b = output(input(1).and_(input(1)))

At this point, constructed circuit can be retrieved and evaluated on a vector of bit values (wherein each value is represented using the integer 0 or the integer 1).

>>> b.value == bit.circuit().evaluate([1, 1])[0]
True

It is possible to add custom hook functions that are applied whenever an operator is introduced using the bit.hook_operation method.

>>> def make_hook_that_prints_created_gates(bit_):
...     def hook(o, v, *args):
...         print('created gate with operation "' + o.name() + '"')
...         return bit_.constructor(*args)(v, bit_.gate(o, [a.gate for a in args]))
...     return hook
>>> bit.hook_operation(make_hook_that_prints_created_gates(bit))
>>> bit.circuit(circuit())
>>> b = output(input(0).and_(input(1)).not_())
created gate with operation "and"
created gate with operation "not"

Note that a hook must be removed if its effects are not desired when subsequent circuits are constructed.

>>> bit.hook_operation()
static circuit(circuit: Optional[circuit.circuit.circuit] = None) Optional[circuit.circuit.circuit][source]

Designate the circuit object that is under construction. Any invocation of the bit constructor adds a gate to the circuit designated using this method.

>>> c = circuit()
>>> bit.circuit(c)
>>> b = output(input(1).and_(input(1)))
>>> b.value == bit.circuit().evaluate([1, 1])[0]
True

Invoking this method with no argument retrieves and removes the circuit object (leaving no designated circuit object). Note that because of the invocation of this method at the end of the above example, the invocation below returns None.

>>> bit.circuit() is None
True
static hook_operation(hook: Optional[Callable] = None)[source]

Assign a function that is invoked whenever the bit.operation method is used to create a new instance of bit.

>>> def make_hook_that_prints_created_gates(bit_):
...     def hook(o, v, *args):
...         print('created gate with operation "' + o.name() + '"')
...         return bit_.constructor(*args)(v, bit_.gate(o, [a.gate for a in args]))
...     return hook
>>> bit.hook_operation(make_hook_that_prints_created_gates(bit))
>>> bit.circuit(circuit())
>>> b = output(input(0).and_(input(1)).not_())
created gate with operation "and"
created gate with operation "not"
>>> b.value == bit.circuit().evaluate([0, 0])[0]
True

If a hook function returns None, then the default instance of bit is returned when an operation is applied.

>>> bit.hook_operation(lambda o, v, *args: None)
>>> bit.circuit(circuit())
>>> b = output(input(1).and_(input(1)))
>>> b.value == bit.circuit().evaluate([1, 1])[0]
True

It is only possible to assign one hook function at a time. A hook must be removed if its effects are not desired when subsequent circuits are constructed.

>>> bit.hook_operation()
static operation(o: Callable, *args) circuitry.circuitry.bit[source]

Apply the supplied operation method to zero or more bit arguments. Gate operations are represented using instances of the logical class exported by the logical library. This module indirectly imports the logical class via the op synonym defined in the circuit library, enabling the more concise syntax used in the example below.

>>> bit.circuit(circuit())
>>> b = output(bit.operation(op.and_, input(1), input(1)))
>>> b.value == bit.circuit().evaluate([1, 1])[0]
True

Arguments that are instances of output are not permitted.

>>> bit.circuit(circuit())
>>> b0 = output(input(0).not_())
>>> b1 = b0.not_()
Traceback (most recent call last):
  ...
TypeError: cannot supply an output as an argument to an operation
>>> _ = bit.circuit() # Remove designated circuit.
static constructor(b1: Optional[circuitry.circuitry.bit] = None, b2: Optional[circuitry.circuitry.bit] = None) type[source]

Return the constructor to use for instantiating a bit object. This method is used by the bit.operation and bits.from_byte methods. It is provided primarily to aid in the implementation of custom hooks that can be set using the bit.hook_operation method.

static gate(operation: op, igs: Sequence[gate]) Optional[gate][source]

Add a gate to the designated circuit object that is under construction. This method is primarily provided to aid in the implementation of custom hooks that can be set using the bit.hook_operation method. Gate operations are represented using instances of the logical class exported by the logical library (which is indirectly imported into this module as the op synonym from the circuit library).

>>> def make_hook(bit_):
...     def hook(o, v, *args):
...         return bit_.constructor(*args)(v, bit_.gate(o, [a.gate for a in args]))
...     return hook
>>> bit.hook_operation(make_hook(bit))
>>> bit.circuit(circuit())
>>> b = output(input(0).and_(input(0)))
>>> b.value == bit.circuit().evaluate([0, 0])[0]
True
__int__() int[source]

Convert this bit instance into the integer representation of its value.

>>> int(bit(1))
1
id_() circuitry.circuitry.bit[source]

Logical operation for an individual bit instance.

>>> results = []
>>> for x in [0, 1]:
...     bit.circuit(circuit())
...     b = output(input(x).id_())
...     results.append(int(b) == bit.circuit().evaluate([x])[0])
>>> all(results)
True
not_() circuitry.circuitry.bit[source]

Logical operation for an individual bit instance.

>>> results = []
>>> for x in [0, 1]:
...     bit.circuit(circuit())
...     b = output(input(x).not_())
...     results.append(int(b) == bit.circuit().evaluate([x])[0])
>>> all(results)
True
__invert__() circuitry.circuitry.bit[source]

Logical operation for an individual bit instance.

>>> results = []
>>> for x in [0, 1]:
...     bit.circuit(circuit())
...     b = output(~input(x))
...     results.append(int(b) == bit.circuit().evaluate([x])[0])
>>> all(results)
True
__rsub__(other: circuitry.circuitry.bit) circuitry.circuitry.bit[source]

Arithmetic operation involving an individual bit instance that is often used to represent the logical negation operation. This method is provided primarily as a convenience enabling use of the syntax 1 - b to represent logical negation of a bit value b; it does not enable subtraction of one bit instance from another.

>>> results = []
>>> for x in [0, 1]:
...     bit.circuit(circuit())
...     b = output(1 - input(x))
...     results.append(int(b) == bit.circuit().evaluate([x])[0])
>>> all(results)
True
>>> 2 - input(0)
Traceback (most recent call last):
  ...
ValueError: can only subtract a bit from the integer 1
and_(other: circuitry.circuitry.bit) circuitry.circuitry.bit[source]

Logical operation for individual bit instances.

>>> results = []
>>> for (x, y) in [(0, 0), (0, 1), (1, 0), (1, 1)]:
...     bit.circuit(circuit())
...     b = output(input(x).and_(input(y)))
...     results.append(int(b) == bit.circuit().evaluate([x, y])[0])
>>> all(results)
True
__and__(other: circuitry.circuitry.bit) circuitry.circuitry.bit[source]

Logical operation for individual bit instances.

>>> results = []
>>> for (x, y) in [(0, 0), (0, 1), (1, 0), (1, 1)]:
...     bit.circuit(circuit())
...     b = output(input(x) & input(y))
...     results.append(int(b) == bit.circuit().evaluate([x, y])[0])
>>> all(results)
True
__rand__(other: circuitry.circuitry.bit) circuitry.circuitry.bit[source]

Logical operation for individual bit instances.

>>> b = 0 & constant(1)
>>> b.value
0
nimp(other: circuitry.circuitry.bit) circuitry.circuitry.bit[source]

Logical operation for individual bit instances.

>>> results = []
>>> for (x, y) in [(0, 0), (0, 1), (1, 0), (1, 1)]:
...     bit.circuit(circuit())
...     b = output(input(x).nimp(input(y)))
...     results.append(int(b) == bit.circuit().evaluate([x, y])[0])
>>> all(results)
True
nimp_(other: circuitry.circuitry.bit) circuitry.circuitry.bit[source]

Logical operation for individual bit instances.

>>> results = []
>>> for (x, y) in [(0, 0), (0, 1), (1, 0), (1, 1)]:
...     bit.circuit(circuit())
...     b = output(input(x).nimp_(input(y)))
...     results.append(int(b) == bit.circuit().evaluate([x, y])[0])
>>> all(results)
True
__gt__(other: circuitry.circuitry.bit) circuitry.circuitry.bit[source]

Logical operation for individual bit instances.

>>> results = []
>>> for (x, y) in [(0, 0), (0, 1), (1, 0), (1, 1)]:
...     bit.circuit(circuit())
...     b = output(input(x) > input(y))
...     results.append(int(b) == bit.circuit().evaluate([x, y])[0])
>>> all(results)
True
nif(other: circuitry.circuitry.bit) circuitry.circuitry.bit[source]

Logical operation for individual bit instances.

>>> results = []
>>> for (x, y) in [(0, 0), (0, 1), (1, 0), (1, 1)]:
...     bit.circuit(circuit())
...     b = output(input(x).nif(input(y)))
...     results.append(int(b) == bit.circuit().evaluate([x, y])[0])
>>> all(results)
True
nif_(other: circuitry.circuitry.bit) circuitry.circuitry.bit[source]

Logical operation for individual bit instances.

>>> results = []
>>> for (x, y) in [(0, 0), (0, 1), (1, 0), (1, 1)]:
...     bit.circuit(circuit())
...     b = output(input(x).nif_(input(y)))
...     results.append(int(b) == bit.circuit().evaluate([x, y])[0])
>>> all(results)
True
__lt__(other: circuitry.circuitry.bit) circuitry.circuitry.bit[source]

Logical operation for individual bit instances.

>>> results = []
>>> for (x, y) in [(0, 0), (0, 1), (1, 0), (1, 1)]:
...     bit.circuit(circuit())
...     b = output(input(x) < input(y))
...     results.append(int(b) == bit.circuit().evaluate([x, y])[0])
>>> all(results)
True
xor(other: circuitry.circuitry.bit) circuitry.circuitry.bit[source]

Logical operation for individual bit instances.

>>> results = []
>>> for (x, y) in [(0, 0), (0, 1), (1, 0), (1, 1)]:
...     bit.circuit(circuit())
...     b = output(input(x).xor(input(y)))
...     results.append(int(b) == bit.circuit().evaluate([x, y])[0])
>>> all(results)
True
xor_(other: circuitry.circuitry.bit) circuitry.circuitry.bit[source]

Logical operation for individual bit instances.

>>> results = []
>>> for (x, y) in [(0, 0), (0, 1), (1, 0), (1, 1)]:
...     bit.circuit(circuit())
...     b = output(input(x).xor_(input(y)))
...     results.append(int(b) == bit.circuit().evaluate([x, y])[0])
>>> all(results)
True
__xor__(other: circuitry.circuitry.bit) circuitry.circuitry.bit[source]

Logical operation for individual bit instances.

>>> results = []
>>> for (x, y) in [(0, 0), (0, 1), (1, 0), (1, 1)]:
...     bit.circuit(circuit())
...     b = output(input(x) ^ input(y))
...     results.append(int(b) == bit.circuit().evaluate([x, y])[0])
>>> all(results)
True
__rxor__(other: circuitry.circuitry.bit) circuitry.circuitry.bit[source]

Logical operation for individual bit instances.

>>> b =  1 ^ constant(0)
>>> b.value
1
or_(other: circuitry.circuitry.bit) circuitry.circuitry.bit[source]

Logical operation for individual bit instances.

>>> results = []
>>> for (x, y) in [(0, 0), (0, 1), (1, 0), (1, 1)]:
...     bit.circuit(circuit())
...     b = output(input(x).or_(input(y)))
...     results.append(int(b) == bit.circuit().evaluate([x, y])[0])
>>> all(results)
True
__or__(other: circuitry.circuitry.bit) circuitry.circuitry.bit[source]

Logical operation for individual bit instances.

>>> results = []
>>> for (x, y) in [(0, 0), (0, 1), (1, 0), (1, 1)]:
...     bit.circuit(circuit())
...     b = output(input(x) | input(y))
...     results.append(int(b) == bit.circuit().evaluate([x, y])[0])
>>> all(results)
True
__ror__(other: circuitry.circuitry.bit) circuitry.circuitry.bit[source]

Logical operation for individual bit instances.

>>> b = 1 | constant(0)
>>> b.value
1
nor(other: circuitry.circuitry.bit) circuitry.circuitry.bit[source]

Logical operation for individual bit instances.

>>> results = []
>>> for (x, y) in [(0, 0), (0, 1), (1, 0), (1, 1)]:
...     bit.circuit(circuit())
...     b = output(input(x).nor(input(y)))
...     results.append(int(b) == bit.circuit().evaluate([x, y])[0])
>>> all(results)
True
nor_(other: circuitry.circuitry.bit) circuitry.circuitry.bit[source]

Logical operation for individual bit instances.

>>> results = []
>>> for (x, y) in [(0, 0), (0, 1), (1, 0), (1, 1)]:
...     bit.circuit(circuit())
...     b = output(input(x).nor_(input(y)))
...     results.append(int(b) == bit.circuit().evaluate([x, y])[0])
>>> all(results)
True
__mod__(other: circuitry.circuitry.bit) circuitry.circuitry.bit[source]

Logical operation for individual bit instances.

>>> results = []
>>> for (x, y) in [(0, 0), (0, 1), (1, 0), (1, 1)]:
...     bit.circuit(circuit())
...     b = output(input(x) % input(y))
...     results.append(int(b) == bit.circuit().evaluate([x, y])[0])
>>> all(results)
True
xnor(other: circuitry.circuitry.bit) circuitry.circuitry.bit[source]

Logical operation for individual bit instances.

>>> results = []
>>> for (x, y) in [(0, 0), (0, 1), (1, 0), (1, 1)]:
...     bit.circuit(circuit())
...     b = output(input(x).xnor(input(y)))
...     results.append(int(b) == bit.circuit().evaluate([x, y])[0])
>>> all(results)
True
xnor_(other: circuitry.circuitry.bit) circuitry.circuitry.bit[source]

Logical operation for individual bit instances.

>>> results = []
>>> for (x, y) in [(0, 0), (0, 1), (1, 0), (1, 1)]:
...     bit.circuit(circuit())
...     b = output(input(x).xnor_(input(y)))
...     results.append(int(b) == bit.circuit().evaluate([x, y])[0])
>>> all(results)
True
__eq__(other: circuitry.circuitry.bit) circuitry.circuitry.bit[source]

Logical operation for individual bit instances.

>>> results = []
>>> for (x, y) in [(0, 0), (0, 1), (1, 0), (1, 1)]:
...     bit.circuit(circuit())
...     b = output(input(x) == input(y))
...     results.append(int(b) == bit.circuit().evaluate([x, y])[0])
>>> all(results)
True
if_(other: circuitry.circuitry.bit) circuitry.circuitry.bit[source]

Logical operation for individual bit instances.

>>> results = []
>>> for (x, y) in [(0, 0), (0, 1), (1, 0), (1, 1)]:
...     bit.circuit(circuit())
...     b = output(input(x).if_(input(y)))
...     results.append(int(b) == bit.circuit().evaluate([x, y])[0])
>>> all(results)
True
__ge__(other: circuitry.circuitry.bit) circuitry.circuitry.bit[source]

Logical operation for individual bit instances.

>>> results = []
>>> for (x, y) in [(0, 0), (0, 1), (1, 0), (1, 1)]:
...     bit.circuit(circuit())
...     b = output(input(x) >= input(y))
...     results.append(int(b) == bit.circuit().evaluate([x, y])[0])
>>> all(results)
True
imp(other: circuitry.circuitry.bit) circuitry.circuitry.bit[source]

Logical operation for individual bit instances.

>>> results = []
>>> for (x, y) in [(0, 0), (0, 1), (1, 0), (1, 1)]:
...     bit.circuit(circuit())
...     b = output(input(x).imp(input(y)))
...     results.append(int(b) == bit.circuit().evaluate([x, y])[0])
>>> all(results)
True
imp_(other: circuitry.circuitry.bit) circuitry.circuitry.bit[source]

Logical operation for individual bit instances.

>>> results = []
>>> for (x, y) in [(0, 0), (0, 1), (1, 0), (1, 1)]:
...     bit.circuit(circuit())
...     b = output(input(x).imp_(input(y)))
...     results.append(int(b) == bit.circuit().evaluate([x, y])[0])
>>> all(results)
True
__le__(other: circuitry.circuitry.bit) circuitry.circuitry.bit[source]

Logical operation for individual bit instances.

>>> results = []
>>> for (x, y) in [(0, 0), (0, 1), (1, 0), (1, 1)]:
...     bit.circuit(circuit())
...     b = output(input(x) <= input(y))
...     results.append(int(b) == bit.circuit().evaluate([x, y])[0])
>>> all(results)
True
nand(other: circuitry.circuitry.bit) circuitry.circuitry.bit[source]

Logical operation for individual bit instances.

>>> results = []
>>> for (x, y) in [(0, 0), (0, 1), (1, 0), (1, 1)]:
...     bit.circuit(circuit())
...     b = output(input(x).nand(input(y)))
...     results.append(int(b) == bit.circuit().evaluate([x, y])[0])
>>> all(results)
True
nand_(other: circuitry.circuitry.bit) circuitry.circuitry.bit[source]

Logical operation for individual bit instances.

>>> results = []
>>> for (x, y) in [(0, 0), (0, 1), (1, 0), (1, 1)]:
...     bit.circuit(circuit())
...     b = output(input(x).nand_(input(y)))
...     results.append(int(b) == bit.circuit().evaluate([x, y])[0])
>>> all(results)
True
__matmul__(other: circuitry.circuitry.bit) circuitry.circuitry.bit[source]

Logical operation for individual bit instances.

>>> results = []
>>> for (x, y) in [(0, 0), (0, 1), (1, 0), (1, 1)]:
...     bit.circuit(circuit())
...     b = output(input(x) @ input(y))
...     results.append(int(b) == bit.circuit().evaluate([x, y])[0])
>>> all(results)
True
class circuitry.circuitry.constant(value: int)[source]

Bases: circuitry.circuitry.bit

Instance of a bit that is designated as a constant value.

>>> constant(1).value
1

When a constant instance is introduced during circuit construction, a gate with a nullary operator (i.e., one of the two appearing in nullary) is added to the circuit.

>>> bit.circuit(circuit())
>>> _ = output(constant(0))
>>> c = bit.circuit()
>>> c.count()
2
>>> c.evaluate([])
[0]
class circuitry.circuitry.input(value: int)[source]

Bases: circuitry.circuitry.bit

Instance of a bit that is designated as a variable input. When an input instance is introduced during circuit construction, a gate is added to the circuit that is explicitly marked as an input gate (as defined in the circuit library).

>>> b0 = output(input(1).not_())
>>> b0.value
0
class circuitry.circuitry.input_one(value: int)[source]

Bases: circuitry.circuitry.input

Instance of a bit that is designated as a variable input from one source.

class circuitry.circuitry.input_two(value: int)[source]

Bases: circuitry.circuitry.input

Instance of a bit that is designated as a variable input from a second source

class circuitry.circuitry.output(b: circuitry.circuitry.bit)[source]

Bases: circuitry.circuitry.bit

Instance of a bit that is designated as an output. When an output instance is introduced during circuit construction, a gate is added to the circuit that is explicitly marked as an output gate (as defined in the circuit library).

>>> bit.circuit(circuit())
>>> b0 = input(0).not_()
>>> b1 = output(b0.not_())
>>> [b0.value, b1.value]
[1, 0]
>>> bit.circuit().evaluate([1])
[1]

It is not possible to apply an operation to an output instance. Any attempt to do so raises an exception (see implementation of bit.operation for more details).

>>> bit.circuit(circuit())
>>> b0 = output(input(0).not_())
>>> b1 = b0.not_()
Traceback (most recent call last):
  ...
TypeError: cannot supply an output as an argument to an operation

If the value represented by a bit instance must be both an argument into another operation and an output, an bit.id_ operation can be introduced.

>>> bit.circuit(circuit())
>>> b0 = input(1).not_()
>>> b1 = output(b0.id_())
>>> b2 = output(b0.not_())
>>> [b0.value, b1.value, b2.value]
[0, 0, 1]
>>> bit.circuit().evaluate([0])
[1, 0]
class circuitry.circuitry.bits(argument=None)[source]

Bases: list

Class for representing a bit vector (i.e., a list of abstract bit instances).

>>> bs = bits([constant(1)] * 3)
>>> [b.value for b in bs]
[1, 1, 1]
static from_byte(b: int, constructor=bit) circuitry.circuitry.bits[source]

Convert an integer representing a single byte into a corresponding bit vector.

>>> [b.value for b in bits.from_byte(255)]
[1, 1, 1, 1, 1, 1, 1, 1]
static from_bytes(bs: Union[bytes, bytearray], constructor=bit) circuitry.circuitry.bits[source]

Convert a vector of bytes into a corresponding bit vector.

>>> [b.value for b in bits.from_bytes(bytes([255]))]
[1, 1, 1, 1, 1, 1, 1, 1]
>>> [b.value for b in bits.from_bytes(bytes([11, 0]))]
[0, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0]
static zeros(n: int) circuitry.circuitry.bits[source]

Create a vector (of the specified length) of constant zero bits.

>>> bit.circuit(circuit())
>>> xs = bits.zeros(3)
>>> ys = outputs(xs.not_())
>>> [y.value for y in ys]
[1, 1, 1]
>>> _ = bit.circuit() # Remove designated circuit.
__int__() int[source]

Convert a bit vector into an integer.

>>> bit.circuit(circuit())
>>> xs = constants([0, 0, 0])
>>> ys = outputs(xs.not_())
>>> int(ys)
7
>>> _ = bit.circuit() # Remove designated circuit.
not_() circuitry.circuitry.bits[source]

Logical operation for bit vectors.

>>> results = []
>>> for x in [0, 1]:
...     bit.circuit(circuit())
...     xs = inputs([x, x, x])
...     ys = outputs(xs.not_())
...     ns = [int(y) for y in ys]
...     c = bit.circuit()
...     results.append(ns == c.evaluate([x, x, x]))
>>> all(results)
True
__invert__() circuitry.circuitry.bits[source]

Logical operation for bit vectors.

>>> results = []
>>> for x in [0, 1]:
...     bit.circuit(circuit())
...     xs = inputs([x, x, x])
...     ys = outputs(~xs)
...     ns = [int(y) for y in ys]
...     c = bit.circuit()
...     results.append(ns == c.evaluate([x, x, x]))
>>> all(results)
True
and_(other: circuitry.circuitry.bits) circuitry.circuitry.bits[source]

Logical operation for bit vectors.

>>> results = []
>>> for (x, y) in [(0, 0), (0, 1), (1, 0), (1, 1)]:
...     bit.circuit(circuit())
...     (xs, ys) = (inputs([x, x, x]), inputs([y, y, y]))
...     zs = outputs(xs.and_(ys))
...     ns = [int(z) for z in zs]
...     c = bit.circuit()
...     results.append(ns == c.evaluate([x, x, x, y, y, y]))
>>> all(results)
True
__and__(other: circuitry.circuitry.bits) circuitry.circuitry.bits[source]

Logical operation for bit vectors.

>>> results = []
>>> for (x, y) in [(0, 0), (0, 1), (1, 0), (1, 1)]:
...     bit.circuit(circuit())
...     (xs, ys) = (inputs([x, x, x]), inputs([y, y, y]))
...     zs = outputs(xs & ys)
...     ns = [int(z) for z in zs]
...     c = bit.circuit()
...     results.append(ns == c.evaluate([x, x, x, y, y, y]))
>>> all(results)
True
nimp(other: circuitry.circuitry.bits) circuitry.circuitry.bits[source]

Logical operation for bit vectors.

>>> results = []
>>> for (x, y) in [(0, 0), (0, 1), (1, 0), (1, 1)]:
...     bit.circuit(circuit())
...     (xs, ys) = (inputs([x, x, x]), inputs([y, y, y]))
...     zs = outputs(xs.nimp(ys))
...     ns = [int(z) for z in zs]
...     c = bit.circuit()
...     results.append(ns == c.evaluate([x, x, x, y, y, y]))
>>> all(results)
True
nimp_(other: circuitry.circuitry.bits) circuitry.circuitry.bits[source]

Logical operation for bit vectors.

>>> results = []
>>> for (x, y) in [(0, 0), (0, 1), (1, 0), (1, 1)]:
...     bit.circuit(circuit())
...     (xs, ys) = (inputs([x, x, x]), inputs([y, y, y]))
...     zs = outputs(xs.nimp_(ys))
...     ns = [int(z) for z in zs]
...     c = bit.circuit()
...     results.append(ns == c.evaluate([x, x, x, y, y, y]))
>>> all(results)
True
__gt__(other: circuitry.circuitry.bits) circuitry.circuitry.bits[source]

Logical operation for bit vectors.

>>> results = []
>>> for (x, y) in [(0, 0), (0, 1), (1, 0), (1, 1)]:
...     bit.circuit(circuit())
...     (xs, ys) = (inputs([x, x, x]), inputs([y, y, y]))
...     zs = outputs(xs > ys)
...     ns = [int(z) for z in zs]
...     c = bit.circuit()
...     results.append(ns == c.evaluate([x, x, x, y, y, y]))
>>> all(results)
True
nif(other: circuitry.circuitry.bits) circuitry.circuitry.bits[source]

Logical operation for bit vectors.

>>> results = []
>>> for (x, y) in [(0, 0), (0, 1), (1, 0), (1, 1)]:
...     bit.circuit(circuit())
...     (xs, ys) = (inputs([x, x, x]), inputs([y, y, y]))
...     zs = outputs(xs.nif(ys))
...     ns = [int(z) for z in zs]
...     c = bit.circuit()
...     results.append(ns == c.evaluate([x, x, x, y, y, y]))
>>> all(results)
True
nif_(other: circuitry.circuitry.bits) circuitry.circuitry.bits[source]

Logical operation for bit vectors.

>>> results = []
>>> for (x, y) in [(0, 0), (0, 1), (1, 0), (1, 1)]:
...     bit.circuit(circuit())
...     (xs, ys) = (inputs([x, x, x]), inputs([y, y, y]))
...     zs = outputs(xs.nif_(ys))
...     ns = [int(z) for z in zs]
...     c = bit.circuit()
...     results.append(ns == c.evaluate([x, x, x, y, y, y]))
>>> all(results)
True
__lt__(other: circuitry.circuitry.bits) circuitry.circuitry.bits[source]

Logical operation for bit vectors.

>>> results = []
>>> for (x, y) in [(0, 0), (0, 1), (1, 0), (1, 1)]:
...     bit.circuit(circuit())
...     (xs, ys) = (inputs([x, x, x]), inputs([y, y, y]))
...     zs = outputs(xs < ys)
...     ns = [int(z) for z in zs]
...     c = bit.circuit()
...     results.append(ns == c.evaluate([x, x, x, y, y, y]))
>>> all(results)
True
xor(other: circuitry.circuitry.bits) circuitry.circuitry.bits[source]

Logical operation for bit vectors.

>>> results = []
>>> for (x, y) in [(0, 0), (0, 1), (1, 0), (1, 1)]:
...     bit.circuit(circuit())
...     (xs, ys) = (inputs([x, x, x]), inputs([y, y, y]))
...     zs = outputs(xs.xor(ys))
...     ns = [int(z) for z in zs]
...     c = bit.circuit()
...     results.append(ns == c.evaluate([x, x, x, y, y, y]))
>>> all(results)
True
xor_(other: circuitry.circuitry.bits) circuitry.circuitry.bits[source]

Logical operation for bit vectors.

>>> results = []
>>> for (x, y) in [(0, 0), (0, 1), (1, 0), (1, 1)]:
...     bit.circuit(circuit())
...     (xs, ys) = (inputs([x, x, x]), inputs([y, y, y]))
...     zs = outputs(xs.xor_(ys))
...     ns = [int(z) for z in zs]
...     c = bit.circuit()
...     results.append(ns == c.evaluate([x, x, x, y, y, y]))
>>> all(results)
True
__xor__(other: circuitry.circuitry.bits) circuitry.circuitry.bits[source]

Logical operation for bit vectors.

>>> results = []
>>> for (x, y) in [(0, 0), (0, 1), (1, 0), (1, 1)]:
...     bit.circuit(circuit())
...     (xs, ys) = (inputs([x, x, x]), inputs([y, y, y]))
...     zs = outputs(xs ^ ys)
...     ns = [int(z) for z in zs]
...     c = bit.circuit()
...     results.append(ns == c.evaluate([x, x, x, y, y, y]))
>>> all(results)
True
or_(other: circuitry.circuitry.bits) circuitry.circuitry.bits[source]

Logical operation for bit vectors.

>>> results = []
>>> for (x, y) in [(0, 0), (0, 1), (1, 0), (1, 1)]:
...     bit.circuit(circuit())
...     (xs, ys) = (inputs([x, x, x]), inputs([y, y, y]))
...     zs = outputs(xs.or_(ys))
...     ns = [int(z) for z in zs]
...     c = bit.circuit()
...     results.append(ns == c.evaluate([x, x, x, y, y, y]))
>>> all(results)
True
__or__(other: circuitry.circuitry.bits) circuitry.circuitry.bits[source]

Logical operation for bit vectors.

>>> results = []
>>> for (x, y) in [(0, 0), (0, 1), (1, 0), (1, 1)]:
...     bit.circuit(circuit())
...     (xs, ys) = (inputs([x, x, x]), inputs([y, y, y]))
...     zs = outputs(xs | ys)
...     ns = [int(z) for z in zs]
...     c = bit.circuit()
...     results.append(ns == c.evaluate([x, x, x, y, y, y]))
>>> all(results)
True
nor(other: circuitry.circuitry.bits) circuitry.circuitry.bits[source]

Logical operation for bit vectors.

>>> results = []
>>> for (x, y) in [(0, 0), (0, 1), (1, 0), (1, 1)]:
...     bit.circuit(circuit())
...     (xs, ys) = (inputs([x, x, x]), inputs([y, y, y]))
...     zs = outputs(xs.nor(ys))
...     ns = [int(z) for z in zs]
...     c = bit.circuit()
...     results.append(ns == c.evaluate([x, x, x, y, y, y]))
>>> all(results)
True
nor_(other: circuitry.circuitry.bits) circuitry.circuitry.bits[source]

Logical operation for bit vectors.

>>> results = []
>>> for (x, y) in [(0, 0), (0, 1), (1, 0), (1, 1)]:
...     bit.circuit(circuit())
...     (xs, ys) = (inputs([x, x, x]), inputs([y, y, y]))
...     zs = outputs(xs.nor_(ys))
...     ns = [int(z) for z in zs]
...     c = bit.circuit()
...     results.append(ns == c.evaluate([x, x, x, y, y, y]))
>>> all(results)
True
__mod__(other: circuitry.circuitry.bits) circuitry.circuitry.bits[source]

Logical operation for bit vectors.

>>> results = []
>>> for (x, y) in [(0, 0), (0, 1), (1, 0), (1, 1)]:
...     bit.circuit(circuit())
...     (xs, ys) = (inputs([x, x, x]), inputs([y, y, y]))
...     zs = outputs(xs % ys)
...     ns = [int(z) for z in zs]
...     c = bit.circuit()
...     results.append(ns == c.evaluate([x, x, x, y, y, y]))
>>> all(results)
True
xnor(other: circuitry.circuitry.bits) circuitry.circuitry.bits[source]

Logical operation for bit vectors.

>>> results = []
>>> for (x, y) in [(0, 0), (0, 1), (1, 0), (1, 1)]:
...     bit.circuit(circuit())
...     (xs, ys) = (inputs([x, x, x]), inputs([y, y, y]))
...     zs = outputs(xs.xnor(ys))
...     ns = [int(z) for z in zs]
...     c = bit.circuit()
...     results.append(ns == c.evaluate([x, x, x, y, y, y]))
>>> all(results)
True
xnor_(other: circuitry.circuitry.bits) circuitry.circuitry.bits[source]

Logical operation for bit vectors.

>>> results = []
>>> for (x, y) in [(0, 0), (0, 1), (1, 0), (1, 1)]:
...     bit.circuit(circuit())
...     (xs, ys) = (inputs([x, x, x]), inputs([y, y, y]))
...     zs = outputs(xs.xnor_(ys))
...     ns = [int(z) for z in zs]
...     c = bit.circuit()
...     results.append(ns == c.evaluate([x, x, x, y, y, y]))
>>> all(results)
True
__eq__(other: circuitry.circuitry.bits) circuitry.circuitry.bits[source]

Logical operation for bit vectors.

>>> results = []
>>> for (x, y) in [(0, 0), (0, 1), (1, 0), (1, 1)]:
...     bit.circuit(circuit())
...     (xs, ys) = (inputs([x, x, x]), inputs([y, y, y]))
...     zs = outputs(xs == ys)
...     ns = [int(z) for z in zs]
...     c = bit.circuit()
...     results.append(ns == c.evaluate([x, x, x, y, y, y]))
>>> all(results)
True
if_(other: circuitry.circuitry.bits) circuitry.circuitry.bits[source]

Logical operation for bit vectors.

>>> results = []
>>> for (x, y) in [(0, 0), (0, 1), (1, 0), (1, 1)]:
...     bit.circuit(circuit())
...     (xs, ys) = (inputs([x, x, x]), inputs([y, y, y]))
...     zs = outputs(xs.if_(ys))
...     ns = [int(z) for z in zs]
...     c = bit.circuit()
...     results.append(ns == c.evaluate([x, x, x, y, y, y]))
>>> all(results)
True
__ge__(other: circuitry.circuitry.bits) circuitry.circuitry.bits[source]

Logical operation for bit vectors.

>>> results = []
>>> for (x, y) in [(0, 0), (0, 1), (1, 0), (1, 1)]:
...     bit.circuit(circuit())
...     (xs, ys) = (inputs([x, x, x]), inputs([y, y, y]))
...     zs = outputs(xs >= ys)
...     ns = [int(z) for z in zs]
...     c = bit.circuit()
...     results.append(ns == c.evaluate([x, x, x, y, y, y]))
>>> all(results)
True
imp(other: circuitry.circuitry.bits) circuitry.circuitry.bits[source]

Logical operation for bit vectors.

>>> results = []
>>> for (x, y) in [(0, 0), (0, 1), (1, 0), (1, 1)]:
...     bit.circuit(circuit())
...     (xs, ys) = (inputs([x, x, x]), inputs([y, y, y]))
...     zs = outputs(xs.imp(ys))
...     ns = [int(z) for z in zs]
...     c = bit.circuit()
...     results.append(ns == c.evaluate([x, x, x, y, y, y]))
>>> all(results)
True
imp_(other: circuitry.circuitry.bits) circuitry.circuitry.bits[source]

Logical operation for bit vectors.

>>> results = []
>>> for (x, y) in [(0, 0), (0, 1), (1, 0), (1, 1)]:
...     bit.circuit(circuit())
...     (xs, ys) = (inputs([x, x, x]), inputs([y, y, y]))
...     zs = outputs(xs.imp_(ys))
...     ns = [int(z) for z in zs]
...     c = bit.circuit()
...     results.append(ns == c.evaluate([x, x, x, y, y, y]))
>>> all(results)
True
__le__(other: circuitry.circuitry.bits) circuitry.circuitry.bits[source]

Logical operation for bit vectors.

>>> results = []
>>> for (x, y) in [(0, 0), (0, 1), (1, 0), (1, 1)]:
...     bit.circuit(circuit())
...     (xs, ys) = (inputs([x, x, x]), inputs([y, y, y]))
...     zs = outputs(xs <= ys)
...     ns = [int(z) for z in zs]
...     c = bit.circuit()
...     results.append(ns == c.evaluate([x, x, x, y, y, y]))
>>> all(results)
True
nand(other: circuitry.circuitry.bits) circuitry.circuitry.bits[source]

Logical operation for bit vectors.

>>> results = []
>>> for (x, y) in [(0, 0), (0, 1), (1, 0), (1, 1)]:
...     bit.circuit(circuit())
...     (xs, ys) = (inputs([x, x, x]), inputs([y, y, y]))
...     zs = outputs(xs.nand(ys))
...     ns = [int(z) for z in zs]
...     c = bit.circuit()
...     results.append(ns == c.evaluate([x, x, x, y, y, y]))
>>> all(results)
True
nand_(other: circuitry.circuitry.bits) circuitry.circuitry.bits[source]

Logical operation for bit vectors.

>>> results = []
>>> for (x, y) in [(0, 0), (0, 1), (1, 0), (1, 1)]:
...     bit.circuit(circuit())
...     (xs, ys) = (inputs([x, x, x]), inputs([y, y, y]))
...     zs = outputs(xs.nand_(ys))
...     ns = [int(z) for z in zs]
...     c = bit.circuit()
...     results.append(ns == c.evaluate([x, x, x, y, y, y]))
>>> all(results)
True
__rshift__(other: Union[int, set]) circuitry.circuitry.bits[source]

Overloaded operator for performing rotation and shift operations on bit vectors. When the second parameter is an integer, a shift operation is performed. When the second parameter is a set containing a single integer, a rotation operation is performed.

>>> bs = bits(map(bit, [1, 1, 1, 1, 0, 0, 0, 0]))
>>> bs = bs >> 3
>>> [b.value for b in bs]
[0, 0, 0, 1, 1, 1, 1, 0]
>>> bs = bits(map(bit, [0, 0, 0, 0, 1, 1, 1, 1]))
>>> bs = bs >> {3}
>>> [b.value for b in bs]
[1, 1, 1, 0, 0, 0, 0, 1]
__lshift__(other: int) circuitry.circuitry.bits[source]

Overloaded operator for performing shift operations on bit vectors.

>>> bs = bits(map(bit, [1, 1, 1, 1, 0, 0, 0, 0]))
>>> bs = bs << 3
>>> [b.value for b in bs]
[1, 0, 0, 0, 0, 0, 0, 0]
__truediv__(other: [Union, set, list]) Sequence[bits][source]

Overloaded operator for splitting a bit vector into a collection of smaller bit vectors. Three operations are possible depending on the type of the second parameter.

When the second parameter is an integer, this instance is split into that number of bit vectors. If the size of the vector is not an exact multiple of the integer, the parts in the result may not all be the same length.

>>> bs = bits(map(bit, [1, 1, 1, 1, 0, 0, 0, 0]))
>>> bss = list(bs / 2)
>>> ([b.value for b in bss[0]], [b.value for b in bss[1]])
([1, 1, 1, 1], [0, 0, 0, 0])
>>> bs = bits(map(bit, [1, 1, 1, 1, 0, 0, 0, 0]))
>>> [len(bs) for bs in list(bs / 3)]
[2, 3, 3]

When the second parameter is a set containing a single integer, this instance is split into parts of the size specified by that integer.

>>> bs = bits(map(bit, [1, 1, 1, 1, 0, 0, 0, 0]))
>>> bss = list(bs / {2})
>>> [[b.value for b in bs] for bs in bss]
[[1, 1], [1, 1], [0, 0], [0, 0]]
>>> bs = bits(map(bit, [1, 1, 1, 1, 0, 0, 0, 0]))
>>> bss = list(bs / {1})
>>> [[b.value for b in bs] for bs in bss]
[[1], [1], [1], [1], [0], [0], [0], [0]]

When the second parameter is a list of integers, this instance is split into parts the sizes of which correspond to the entries (in order) of the list of integers.

>>> bs = bits(map(bit, [1, 1, 1, 1, 0, 0, 0, 0]))
>>> bss = list(bs / [1, 3, 4])
>>> [[b.value for b in bs] for bs in bss]
[[1], [1, 1, 1], [0, 0, 0, 0]]
__add__(other: Union[circuitry.circuitry.bits, Sequence[int]]) circuitry.circuitry.bits[source]

Overloaded operator for concatenating bit vectors.

>>> bs = bits(map(bit, [1, 1])) + bits(map(bit, [0, 0]))
>>> [b.value for b in bs]
[1, 1, 0, 0]
__pow__(other: Union[circuitry.circuitry.bits, Sequence[int]]) circuitry.circuitry.bits[source]

Overloaded operator for concatenating bit vectors.

>>> bs = bits(map(bit, [1, 1])) ** bits(map(bit, [0, 0]))
>>> [b.value for b in bs]
[1, 1, 0, 0]
circuitry.circuitry.constants(l: Sequence[int]) circuitry.circuitry.bits[source]

Synonym for concisely constructing a vector of bit objects that are designated as constants.

>>> bit.circuit(circuit())
>>> xs = constants([0, 0, 0])
>>> ys = outputs(xs.not_())
>>> int(ys)
7
>>> _ = bit.circuit() # Remove designated circuit.
circuitry.circuitry.inputs(l: Sequence[int]) circuitry.circuitry.bits[source]

Synonym for concisely constructing a vector of bit objects that are designated as inputs.

>>> results = []
>>> bit.circuit(circuit())
>>> xs = inputs([1, 1, 1])
>>> ys = outputs(xs.not_())
>>> ns = [int(y) for y in ys]
>>> c = bit.circuit()
>>> ns == c.evaluate([1, 1, 1])
True
circuitry.circuitry.outputs(l: Sequence[int]) circuitry.circuitry.bits[source]

Synonym for concisely constructing a vector of bit objects that are designated as outputs.

>>> bit.circuit(circuit())
>>> xs = bits.zeros(3)
>>> ys = outputs(xs.not_())
>>> [y.value for y in ys]
[1, 1, 1]
>>> _ = bit.circuit() # Remove designated circuit.
circuitry.circuitry.synthesize(function: Callable, in_type=None, out_type=None) Callable[source]

Decorator for automatically synthesizing a circuit from a function that takes only bit and/or bits objects as its arguments and returns an output of type bit or bits (or a tuple or list thereof).

In the example below, a function equal is defined that determines whether two bits are equivalent. The use of this decorator causes a circuit that implements this function to be constructed.

>>> @synthesize
... def equal(x: bit, y: bit) -> bit:
...     return (x & y) | ((1 - x) & (1 - y))

The synthesized circuit object is introduced as an attribute of the function and can be evaluated on two bit values (as indicated by the function’s type annotation).

>>> [equal.circuit.evaluate([[x], [y]]) for x in (0, 1) for y in (0, 1)]
[[[1]], [[0]], [[0]], [[1]]]

Note that the function itself can still be invoked on its own in the usual manner if the supplied inputs are integers or bit instances. When the function is invoked in this way, the output of the function corresponds to its output type annotation.

>>> equal(0, 1)
0
>>> b = equal(bit(0), bit(1))
>>> isinstance(b, bit)
True
>>> int(b)
0

This decorator can also be applied to functions that are defined explicitly as operating on bit vectors (in the form of bits objects).

>>> @synthesize
... def conjunction(xy: bits(2)) -> bits(2):
...     return (xy[0], xy[0] & xy[1])
>>> [conjunction.circuit.evaluate([[x, y]]) for x in (0, 1) for y in (0, 1)]
[[[0, 0]], [[0, 0]], [[1, 0]], [[1, 1]]]

If the decorated function returns multiple outputs, the output type annotation should indicate this.

>>> @synthesize
... def swap(x: bit, y: bit) -> (bit, bit):
...     return (y, x)
>>> [swap.circuit.evaluate([[x], [y]]) for x in (0, 1) for y in (0, 1)]
[[[0], [0]], [[1], [0]], [[0], [1]], [[1], [1]]]

Functions to which this decorator is applied must have valid type annotations that specify the lengths of the input and output bit vectors.

>>> @synthesize
... def equal(x, y):
...     return x & y
Traceback (most recent call last):
  ...
ValueError: function must have a type annotation for every argument
>>> @synthesize
... def equal(x: bit, y: bit):
...     return x & y
Traceback (most recent call last):
  ...
ValueError: function must have an output type annotation
>>> @synthesize
... def equal(x: 'ABC', y: 'ABC') -> bit:
...     return x & y
Traceback (most recent call last):
  ...
TypeError: input type annotations must be specified using bit or bits
>>> @synthesize
... def swap(x: bit, y: bit) -> ('ABC', 'ABC'):
...     return (y, x)
Traceback (most recent call last):
  ...
TypeError: output type annotation components must be specified using bit or bits
>>> @synthesize
... def swap(x: bit, y: bit) -> 'ABC':
...     return (y, x)
Traceback (most recent call last):
  ...
TypeError: output type must be specified using bit/bits, or a list/tuple thereof

If an exception occurs during the execution (for the purpose of circuit synthesis) of the decorated function, then synthesis will fail.

>>> @synthesize
... def equal(x: bit, y: bit) -> bit:
...     return 1 / 0 # Run-time error.
Traceback (most recent call last):
  ...
RuntimeError: automated circuit synthesis failed

To support programmatic synthesis (e.g., of circuit variants of many different sizes from the same function definition), this function can accept input and output type information via two optional parameters.

>>> def conjunction(xy):
...     return (xy[0], xy[0] & xy[1])
>>> c = synthesize(conjunction, {'xy': bits(2)}, bits(2))
>>> [c.evaluate([[x, y]]) for x in (0, 1) for y in (0, 1)]
[[[0, 0]], [[0, 0]], [[1, 0]], [[1, 1]]]
>>> def swap(x: bit, y: bit) -> (bit, bit):
...     return (y, x)
>>> c = synthesize(swap, (bit, bit), (bit, bit))
>>> [c.evaluate([[x], [y]]) for x in (0, 1) for y in (0, 1)]
[[[0], [0]], [[1], [0]], [[0], [1]], [[1], [1]]]

When synthesizing programmatically, the input type information must be supplied either (1) as a dictionary that maps input type parameter names to types or (2) as a tuple or list (the length of which matches the number of parameters). The output type information must follow the same conventions as an output type annotation.

>>> def equal(x, y):
...     return (x & y) | ((1 - x) & (1 - y))
>>> c = synthesize(equal, (bit, bit), bit)
>>> [c.evaluate([[x], [y]]) for x in (0, 1) for y in (0, 1)]
[[[1]], [[0]], [[0]], [[1]]]
>>> def swap(xy):
...     return [xy[1], xy[0]]
>>> c = synthesize(swap, bits(2), bits(2))
>>> [c.evaluate([[x, y]]) for x in (0, 1) for y in (0, 1)]
[[[0, 0]], [[1, 0]], [[0, 1]], [[1, 1]]]

If the supplied type information does not have the correct type or is incomplete, an exception is raised.

>>> c = synthesize(equal, (bit, bit))
Traceback (most recent call last):
  ...
ValueError: must include input and output types when supplying type information via parameters
>>> c = synthesize(equal, bits(2), bit)
Traceback (most recent call last):
  ...
ValueError: number of input type components does not match number of function arguments
>>> c = synthesize(equal, ('ABC', 'ABC'), bit)
Traceback (most recent call last):
  ...
TypeError: input type components must be specified using bit or bits
>>> c = synthesize(equal, 'bits(2)', bit)
Traceback (most recent call last):
  ...
TypeError: input type must be specified using bit/bits, or a dict/list/tuple thereof
>>> c = synthesize(equal, {'x': 'ABC', 'y': 'ABC'}, bit)
Traceback (most recent call last):
  ...
TypeError: input type components must be specified using bit or bits
>>> c = synthesize(equal, (bit, bit), 'ABC')
Traceback (most recent call last):
  ...
TypeError: output type must be specified using bit/bits, or a list/tuple thereof
>>> c = synthesize(equal, (bit, bit), ('ABC', bit))
Traceback (most recent call last):
  ...
TypeError: output type components must be specified using bit or bits