Bloq#
qualtran.Bloq
View source on GitHub
|
Bloq is the primary abstract base class for all operations.
Bloqs let you represent high-level quantum programs and subroutines as a hierarchical collection of Python objects. The main interface is this abstract base class.
There are two important flavors of implementations of the Bloq interface. The first flavor
consists of bloqs implemented by you, the user-developer to express quantum operations of
interest. For example:
>>> class ShorsAlgorithm(Bloq):
>>> ...
The other important Bloq subclass is CompositeBloq, which is a container type for a
collection of sub-bloqs.
There is only one mandatory method you must implement to have a well-formed Bloq,
namely Bloq.registers. There are many other methods you can optionally implement to
encode more information about the bloq.
Attributes
signatureThe input and output names and types for this bloq.
This property can be thought of as analogous to the function signature in ordinary programming. For example, it is analogous to function declarations in a C header (
*.h) file.This is the only mandatory method (property) you must implement to inherit from
Bloq. You can optionally implement additional methods to encode more information about this bloq.
Methods#
build_composite_bloq
build_composite_bloq(
bb: 'BloqBuilder', **soqs
) -> Dict[str, 'SoquetT']
Override this method to define a Bloq in terms of its constituent parts.
Bloq authors should override this method. If you already have an instance of a Bloq,
consider calling decompose_bloq() which will set up the correct context for
calling this function.
Args
bbA
BloqBuilderto append sub-Bloq to.**soqsThe initial soquets corresponding to the inputs to the Bloq.
Returns
decompose_bloq
decompose_bloq() -> 'CompositeBloq'
Decompose this Bloq into its constituent parts contained in a CompositeBloq.
Bloq users can call this function to delve into the definition of a Bloq. If you’re
trying to define a bloq’s decomposition, consider overriding build_composite_bloq
which provides helpful arguments for implementers.
Returns
Raises
NotImplementedErrorIf there is no decomposition defined; namely: if
build_composite_bloqreturnsNotImplemented.
as_composite_bloq
as_composite_bloq() -> 'CompositeBloq'
Wrap this Bloq into a size-1 CompositeBloq.
This method is overriden so if this Bloq is already a CompositeBloq, it will be returned.
adjoint
adjoint() -> 'Bloq'
The adjoint of this bloq.
Bloq authors can override this method in certain circumstances. Otherwise, the default
fallback wraps this bloq in Adjoint.
Please see the documentation for Adjoint and the Adjoint.ipynb notebook for full
details.
on_classical_vals
on_classical_vals(
**vals
) -> Mapping[str, 'ClassicalValRetT']
How this bloq operates on classical data.
Override this method if your bloq represents classical, reversible logic. For example: quantum circuits composed of X and C^nNOT gates are classically simulable.
Bloq authors should override this method. If you already have an instance of a Bloq,
consider calling call_clasically(**vals) which will do input validation before
calling this function.
Args
**valsThe input classical values for each left (or thru) register. The data types are guaranteed to match
self.signature. Values for registers with a particular dtype will be the corresponding classical data type. Values for registers withshapewill be an ndarray of values of the expected type.
Returns
basis_state_phase
basis_state_phase(
**vals
) -> Union[complex, None]
How this bloq phases classical basis states.
Override this method if your bloq represents classical logic with basis-state dependent phase factors. This corresponds to bloqs whose matrix representation (in the standard basis) is a generalized permutation matrix: a permutation matrix where each entry can be +1, -1 or any complex number with unit absolute value. Alternatively, this corresponds to bloqs composed from classical operations (X, CNOT, Toffoli, …) and diagonal operations (T, CZ, CCZ, …).
Bloq authors should override this method. If you are using an instantiated bloq object, call
If this method is implemented, on_classical_vals must also be implemented.
If on_classical_vals is implemented but this method is not implemented, it is assumed
that the bloq does not alter the phase.
call_classically
call_classically(
**vals
) -> Tuple['ClassicalValT', ...]
Call this bloq on classical data.
Bloq users can call this function to apply bloqs to classical data. If you’re
trying to define a bloq’s action on classical values, consider overriding
on_classical_vals which promises type checking for arguments.
Args
**valsThe input classical values for each left (or thru) register. The data types must match
self.registers. Values for registers with bitsizenshould be integers of that bitsize or less. Values for registers withshapeshould be an ndarray of integers of the given bitsize. Note: integers can be either Numpy or Python integers, but should be positive and unsigned.
Returns
tensor_contract
tensor_contract(
superoperator: bool = False
) -> 'NDArray'
Return a contracted, dense ndarray encoding of this bloq.
This method decomposes and flattens this bloq into a factorized CompositeBloq, turns that composite bloq into a Quimb tensor network, and contracts it into a dense ndarray.
The returned array will be 0-, 1-, 2-, or 4-dimensional with indices arranged according to the
bloq’s signature and the type of simulation requested via the superoperator flag.
If superoperator is set to False (the default), a pure-state tensor network will be
constructed.
If
bloqhas all thru-registers, the dense tensor will be 2-dimensional with shape(n, n)wherenis the number of bits in the signature. We follow the linear algebra convention and order the indices as (right, left) so the matrix-vector product can be used to evolve a state vector.If
bloqhas all left- or all right-registers, the tensor will be 1-dimensional with shape(n,). Note that we do not distinguish between ‘row’ and ‘column’ vectors in this function.If
bloqhas no external registers, the contracted form is a 0-dimensional complex number.
If superoperator is set to True, an open-system tensor network will be constructed.
States result in a 2-dimensional density matrix with indices (right_forward, right_backward) or (left_forward, left_backward) depending on whether they’re input or output states.
Operations result in a 4-dimensional tensor with indices (right_forward, right_backward, left_forward, left_backward).
Args
superoperatorIf toggled to True, do an open-system simulation. This supports non-unitary operations like measurement, but is more costly and results in higher-dimension resultant tensors.
my_tensors
my_tensors(
incoming: Dict[str, 'ConnectionT'], outgoing: Dict[str, 'ConnectionT']
) -> List[Union['qtn.Tensor', 'DiscardInd']]
Override this method to support native quimb simulation of this Bloq.
This method is responsible for returning tensors corresponding to the unitary, state, or effect of the bloq. Often, this method will return one tensor for a given Bloq, but some bloqs can be represented in a factorized form using more than one tensor.
By default, calls to Bloq.tensor_contract() will first decompose and flatten the bloq
before initiating the conversion to a tensor network. This has two consequences:
Overriding this method is only necessary if this bloq does not define a decomposition or if the fully-decomposed form contains a bloq that does not define its tensors.
Even if you override this method to provide custom tensors, they may not be used (by default) because we prefer the flat-decomposed version. This is usually desirable for contraction performance; but for finer-grained control see
qualtran.simulation.tensor.cbloq_to_quimb.
Quimb defines a connection between two tensors by a shared index. The returned tensors from this method must use the Qualtran-Quimb index convention:
Each tensor index is a tuple
(cxn, j)The
cxn: qualtran.Connectionentry identifies the connection between bloq instances.The second integer
jis the bit index within high-bitsize registers, which is necessary due to technical restrictions.
Args
incomingA mapping from register name to Connection (or an array thereof) to use as left indices for the tensor network. The shape of the array matches the register’s shape.
outgoingA mapping from register name to Connection (or an array thereof) to use as right indices for the tensor network.
build_call_graph
build_call_graph(
ssa: 'SympySymbolAllocator'
) -> Union['BloqCountDictT', Set['BloqCountT']]
Override this method to build the bloq call graph.
This method must return a set of (bloq, n) tuples where bloq is called n times in
the decomposition. This method defines one level of the call graph, specifically the
edges from this bloq to its immediate children. To get the full graph,
call Bloq.call_graph().
By default, this method will use self.decompose_bloq() to count the bloqs called
in the decomposition. By overriding this method, you can provide explicit call counts.
This is appropriate if: 1) you can’t or won’t provide a complete decomposition, 2) you
know symbolic expressions for the counts, or 3) you need to “generalize” the subbloqs
by overwriting bloq attributes that do not affect its cost with generic sympy symbols using
the provided SympySymbolAllocator.
my_static_costs
my_static_costs(
cost_key: 'CostKey'
)
Override this method to provide static costs.
The system will query a particular cost by asking for a cost_key. This method
can optionally provide a value, which will be preferred over a computed cost.
Static costs can be provided if the particular cost cannot be easily computed or as a performance optimization.
This method must return NotImplemented if a value cannot be provided for the specified
CostKey.
call_graph
call_graph(
generalizer: Optional[Union['GeneralizerT', Sequence['GeneralizerT']]] = None,
keep: Optional[Callable[['Bloq'], bool]] = None,
max_depth: Optional[int] = None
) -> Tuple['nx.DiGraph', Dict['Bloq', Union[int, 'sympy.Expr']]]
Get the bloq call graph and call totals.
The call graph has edges from a parent bloq to each of the bloqs that it calls in
its decomposition. The number of times it is called is stored as an edge attribute.
To specify the bloq call counts for a specific node, override Bloq.build_call_graph().
Args
generalizerIf provided, run this function on each (sub)bloq to replace attributes that do not affect resource estimates with generic sympy symbols. If the function returns
None, the bloq is omitted from the counts graph. If a sequence of generalizers is provided, each generalizer will be run in order.keepIf this function evaluates to True for the current bloq, keep the bloq as a leaf node in the call graph instead of recursing into it.
max_depthIf provided, build a call graph with at most this many layers.
Returns
gA directed graph where nodes are (generalized) bloqs and edge attribute ‘n’ reports the number of times successor bloq is called via its predecessor.
sigmaCall totals for “leaf” bloqs. We keep a bloq as a leaf in the call graph according to
keepandmax_depth(if provided) or if a bloq cannot be decomposed.
bloq_counts
bloq_counts(
generalizer: Optional[Union['GeneralizerT', Sequence['GeneralizerT']]] = None
) -> Dict['Bloq', Union[int, 'sympy.Expr']]
The number of subbloqs directly called by this bloq.
This corresponds to one level of the call graph, see Bloq.call_graph().
To specify explicit values for a bloq, override Bloq.build_call_graph(…), not this
method.
Args
generalizerIf provided, run this function on each (sub)bloq to replace attributes that do not affect resource estimates with generic sympy symbols. If the function returns
None, the bloq is omitted from the counts graph. If a sequence of generalizers is provided, each generalizer will be run in order.
Returns
get_ctrl_system
get_ctrl_system(
ctrl_spec: 'CtrlSpec'
) -> Tuple['Bloq', 'AddControlledT']
Get a controlled version of this bloq and a function to wire it up correctly.
Users should likely call Bloq.controlled(…) which uses this method behind-the-scenes.
Intrepid bloq authors can override this method to provide a custom controlled version of
this bloq. By default, this will use the qualtran.Controlled meta-bloq to control any
bloq.
This method must return both a controlled version of this bloq and a callable that ‘wires up’ soquets correctly.
A controlled version of this bloq has all the registers from the original bloq plus
any additional control registers to support the activation function specified by
the ctrl_spec. In the simplest case, this could be one additional 1-qubit register
that activates the bloq if the input is in the |1> state, but additional logic is possible.
See the documentation for CtrlSpec for more information.
The second return value ensures we can accurately wire up soquets into the added registers. It must have the following signature:
def _my_add_controlled(
bb: 'BloqBuilder', ctrl_soqs: Sequence['SoquetT'], in_soqs: Dict[str, 'SoquetT']
) -> Tuple[Iterable['SoquetT'], Iterable['SoquetT']]:
Which takes a bloq builder (for adding the controlled bloq), the new control soquets, input soquets for the existing registers; and returns a sequence of the output control soquets and a sequence of the output soquets for the existing registers. This complexity is sadly unavoidable due to the variety of ways of wiring up custom controlled bloqs.
Returns
controlled_bloqA controlled version of this bloq
add_controlledA function with the signature documented above that the system can use to automatically wire up the new control registers.
controlled
controlled(
ctrl_spec: Optional['CtrlSpec'] = None
) -> 'Bloq'
Return a controlled version of this bloq.
By default, the system will use the qualtran.Controlled meta-bloq to wrap this
bloq. Bloqs authors can declare their own, custom controlled versions by overriding
Bloq.get_ctrl_system in the bloq.
Args
ctrl_specan optional
CtrlSpec, which specifies how to control the bloq. The default spec means the bloq will be active when one control qubit is in the |1> state. See the CtrlSpec documentation for more possibilities including negative controls, integer-equality control, and ndarrays of control values.
Returns
t_complexity
t_complexity() -> 'TComplexity'
The TComplexity for this bloq.
By default, this will recurse into this bloq’s decomposition but this method can be overriden with a known value.
as_cirq_op
as_cirq_op(
qubit_manager: 'cirq.QubitManager', **cirq_quregs
) -> Tuple[Union['cirq.Operation', None], Dict[str, 'CirqQuregT']]
Override this method to support conversion to a Cirq operation.
If this method is not overriden, the default implementation will wrap this bloq
in a BloqAsCirqGate shim.
Args
qubit_managerA
cirq.QubitManagerfor allocatingcirq.Qids.**cirq_quregskwargs mapping from this bloq’s left register names to an ndarray of
cirq.Qid. The final dimension of this array corresponds to the registersbitsizesize. Any additional dimensions come first and correspond to the registershapesizes.
Returns
opA cirq operation corresponding to this bloq acting on the provided cirq qubits or None. This method should return None if and only if the bloq instance truly should not be included in the Cirq circuit (e.g. for reshaping bloqs). A bloq with no cirq equivalent should raise an exception instead.
cirq_quregsA mapping from this bloq’s right register of the same format as the
cirq_quregsargument. The returned dictionary corresponds to the output qubits.
as_pl_op
as_pl_op(
wires: 'Wires'
) -> 'Operation'
Override this method to support conversion to a PennyLane operation.
If this method is not overriden, the default implementation will wrap this bloq
in a FromBloq shim.
Args
wiresthe wires that the op acts on
Returns
on
on(
*qubits
) -> 'cirq.Operation'
A cirq.Operation of this bloq operating on the given qubits.
This method supports an alternative decomposition backend that follows a ‘Cirq-style’
association of gates with qubits to form operations. Instead of wiring up Soquets,
each gate operates on qubit addresses (cirq.Qids), which are reused by multiple
gates. This method lets you operate this bloq on qubits and returns a cirq.Operation.
The primary, bloq-native way of writing decompositions is to override
build_composite_bloq. If this is what you’re doing, do not use this method.
To provide a Cirq-style decomposition for this bloq, implement a method (typically named
decompose_from_registers for historical reasons) that yields a list of cirq.Operations
using cirq.Gate.on(...), Bloq.on(…), GateWithRegisters.on_registers(…), or
Bloq.on_registers(…).
See Also
on_registers
on_registers(
**qubit_regs
) -> 'cirq.Operation'
A cirq.Operation of this bloq operating on the given qubit registers.
This method supports an alternative decomposition backend that follows a ‘Cirq-style’
association of gates with qubit registers to form operations. See Bloq.on() for
more details.
Args
**qubit_regsA mapping of register name to the qubits comprising that register.
See Also
wire_symbol
wire_symbol(
reg: Optional['Register'], idx: Tuple[int, ...] = tuple()
) -> 'WireSymbol'
On a musical score visualization, use this WireSymbol to represent soq.
By default, we use a “directional text box”, which is a text box that is either rectangular for thru-registers or facing to the left or right for non-thru-registers.
If reg is specified as None, this should return a Text label for the title of
the gate. If no title is needed (as the wire_symbols are self-explanatory),
this should return Text('').
Override this method to provide a more relevant WireSymbol for the provided soquet.
This method can access bloq attributes. For example: you may want to draw either
a filled or empty circle for a control register depending on a control value bloq
attribute.
View source on GitHub