HierarQcal Core

This module contains the core classes for the hierarqcal package. Qmotif is the base class for all primitives, it’s a directed graph functioning as the building block for higher level motifs. Qhierarchy is the full compute graph / architecture of the quantum circuit, it manages the interaction between motifs, their execution and symbol distribuition.

Create a hierarchical circuit as follows:

from hierarqcal import Qinit, Qcycle, Qmask
hierq = Qinit(8) + (Qcycle(1) + Qmask("right")) * 3

The above creates a circuit that resembles a reverse binary tree architecture. There are 8 initial qubits and then a cycle-masking unit is repeated three times.

class hierarqcal.core.CircuitInstruction(gate_name, symbol_info, sub_bits)

Bases: tuple

gate_name

Alias for field number 0

sub_bits

Alias for field number 2

symbol_info

Alias for field number 1

class hierarqcal.core.Default_Mappings(value)

Bases: Enum

Enum for default mappings

BASE_MOTIF = None
CYCLE = <hierarqcal.core.Qunitary object>
MASK = None
PERMUTE = <hierarqcal.core.Qunitary object>
PIVOT = <hierarqcal.core.Qunitary object>
SPLIT = None
class hierarqcal.core.Primitive_Types(value)

Bases: Enum

Enum for primitive types.

BASE_MOTIF = 'base_motif'
CYCLE = 'cycle'
INIT = 'init'
MASK = 'mask'
PERMUTE = 'permute'
PIVOT = 'pivot'
SPLIT = 'split'
class hierarqcal.core.Qcycle(stride=1, step=1, offset=0, boundary='periodic', **kwargs)

Bases: Qmotif

A cycle motif, spreads unitaries in a ladder structure across the circuit TODO implement open boundary for dense and pooling also + tests

__call__(Qc_l, *args, **kwargs)

Call the motif, this generates the edges and qubits of the motif (directed graph) based on it’s available qubits. Each time a motif in the stack changes, a loop runs through the stack from the beginning and calls each motif to update the graph (the available qubits, the edges etc).

Parameters
  • Qc_l (list) – List of available qubits.

  • *args – Variable length argument list.

  • **kwargs – Arbitrary keyword arguments, such as:

    • mapping (tuple(function, int)): TODO update this docstring, it’s not a tuple anymore.

      Function mapping is specified as a tuple, where the first argument is a function and the second is the number of symbols it uses. A symbol here refers to an variational paramater for a quantum circuit, i.e. crz(theta, q0, q1) <- theta is a symbol for the gate.

Returns

Returns the updated version of itself, with correct nodes and edges.

Return type

Qconv

class hierarqcal.core.Qhierarchy(qubits, function_mappings={})

Bases: object

The main class that manages the “stack” of motifs, it handles the interaction between successive motifs and when a motifs are added to the stack, it updates all the others accordingly. An object of this class fully captures the architectural information of a hierarchical quantum circuit. It also handles function (unitary operation) mappings.

__add__(other)

Add a motif, motifs or hierarchy to the stack.

Parameters

other (Qmotif, Qhierarchy, Sequence(Qmotif)) – The motif, motifs or hierarchy to add to the stack.

Returns

A new Qhierarchy object with the motif(s) added to the stack.

Return type

Qhierarchy

__call__(symbols=None, backend=None, **kwargs)

Call self as a function.

__mul__(other)

Repeat the hierarchy a number of times. If a motif(s) is provided, it is added to the stack.

Parameters

other (int, Qmotif, Sequence(Qmotif)) – The number of times to repeat the hierarchy or the motif(s) to add to the stack.

Returns

A new Qhierarchy object with the motif(s) added to the stack.

Return type

Qhierarchy

append(motif)

Add a motif to the stack of motifs and update it (call to generate nodes and edges) according to the action of the previous motifs in the stack.

Parameters

motif (Qmotif) – The motif to add to the stack.

Returns

A new Qhierarchy object with the new motif added to the stack.

Return type

Qhierarchy

copy()
Returns

A copy of the current Qhierarchy object.

Return type

Qhierarchy

extend(motifs)

Add and update a list of motifs to the current stack of motifs call each to generate their nodes and edges according to the action of the previous motifs in the stack.

Parameters

motifs (Qmotifs) – A tuple of motifs to add to the stack.

Returns

A new Qhierarchy object with the motifs added to the stack.

Return type

Qhierarchy

extmerge(hierarchies)

Merge a list of Qhierarchy objects by adding the head of each one to the tail of the previous one. All are copied to ensure immutability.

Parameters

hierarchies (Qhierarchy) – A list of Qhierarchy objects to merge with the current one.

Returns

A new Qhierarchy object with the list merged.

Return type

Qhierarchy

get_symbols()
get_unitary_function(**kwargs)

Convert the Qhierarchy into a function that can be called.

merge(hierarchy)

Merge two Qhierarchy objects by adding the head of the second one to the tail of the first one. Both are copied to ensure immutability.

Parameters

hierarchy (Qhierarchy) – The Qhierarchy object to merge with the current one.

Returns

A new Qhierarchy object with the two merged.

Return type

Qhierarchy

set_symbols(symbols)
update_Q(Q, start_idx=0)

Update the number of available qubits for the hierarchy and update the rest of the stack accordingly.

Parameters

Q (list(int or string)) – The list of available qubits.

class hierarqcal.core.Qinit(Q, state=None, tensors=None, **kwargs)

Bases: Qmotif

Qinit motif, represents a freeing up qubit for the QCNN, that is making qubits available for future operations. All Qhierarchy objects start with a Qinit motif. It is a special motif that has no edges and is not an operation.

__add__(other)

Add a motif, motifs or hierarchy to the stack with self.Qinit available qubits.

__call__(Q, *args, **kwargs)

Calling TODO add explanation just returns the object. Kwargs and Args are ignored, it just ensures that Qinit can be called the same way operational motifs can.

class hierarqcal.core.Qmask(global_pattern='1*', merge_within='1*', merge_between=None, strides=[1, 1, 0], steps=[1, 1, 1], offsets=[0, 0, 0], boundaries=['open', 'open', 'periodic'], **kwargs)

Bases: Qsplit

A masking motif, it masks qubits based on some pattern TODO some controlled operation where the control is not used for the rest of the circuit). This motif changes the available qubits for the next motif in the stack.

__call__(Qp_l, *args, **kwargs)

Call the motif, this is used to generate the edges and qubits of the motif (directed graph) based on it’s available qubits. Each time a motif in the stack changes, a loop runs through the stack from the beginning and calls each motif to update the graph (the available qubits, the edges etc).

Parameters
  • Qp_l (list) – List of available qubits.

  • *args – Variable length argument list.

  • **kwargs – Arbitrary keyword arguments, such as:

    • mapping (tuple(function, int)):

      Function mapping is specified as a tuple, where the first argument is a function and the second is the number of symbols it uses. A symbol here refers to an variational paramater for a quantum circuit, i.e. crz(theta, q0, q1) <- theta is a symbol for the gate.

Returns

Returns the updated version of itself, with correct nodes and edges.

Return type

Qpool

class hierarqcal.core.Qmotif(Q=[], E=[], Q_avail=[], edge_order=[1], next=None, prev=None, mapping=None, symbol_fn=<function Qmotif.<lambda>>, is_default_mapping=True, is_operation=True, share_weights=True, type=Primitive_Types.BASE_MOTIF)

Bases: object

Hierarchical circuit architectures are created by stacking motifs, the lowest level motifs (primitives) are building blocks for higher level ones. Examples of primitives are cycles (Qcycle), masks (Qmask_Base), and permutations (Qpermute). Each motif is a directed graph with nodes Q representing qubits and edges E unitary operations applied between them. The direction of an edge is the order of interaction for the unitary. Each instance has pointers to its predecessor and successor.

Variables
  • Q (list, optional) – Qubit labels of the motif. Defaults to [].

  • E (list, optional) – Edges of the motif. Defaults to [].

  • Q_avail (list, optional) – Available qubits of the motif. This is calculated by the previous motif in the stack. Defaults to [].

  • edge_order (list, optional) – Order of unitaries applied. Defaults to [1], [-1] will reverse the order, [5,6] does the 5th and 6th edge first and then the rest in the normal order.

  • next (Qmotif, optional) – Next motif in the stack. Defaults to None.

  • prev (Qmotif, optional) – Previous motif in the stack. Defaults to None.

  • mapping (Qunitary or Qhierarchy, optional) – Either a Qunitary instance or a Qhierarchy that will be converted to an Qunitary object. Defaults to None.

  • symbol_fn (lambda) – TODO

  • is_default_mapping (bool, optional) – Flag to determine if default mapping is used. Defaults to True.

  • is_operation (bool, optional) – Flag to determine if the motif is an operation. Defaults to True.

  • share_weights (bool, optional) – Flag to determine if weights are shared within a motif. Defaults to True.

__add__(other)

Append an other motif to the current one: self + other = (self, other).

Parameters

other (Qmotif) – Motif to append to current motif (self).

Returns

A 2-tuple of motifs: (self, other) in the order they were added. The tuples contain copies of the original motifs.

Return type

Qmotifs

__call__(Q, E=[], remaining_q=None, is_operation=True, **kwargs)

Call self as a function.

__mul__(other)

Repeat motif “other” times: self * other = (self, self, …, self). Other is an int.

Parameters

other (int) – Number of times to repeat motif.

Returns

A tuple of motifs: (self, self, …, self), where each is a new object copied from the original.

Return type

Qmotifs

append(other)

Append an other motif to the current one: self + other = (self, other).

Parameters

other (Qmotif) – Motif to append to current motif (self).

Returns

A 2-tuple of motifs: (self, other) in the order they were added. The tuples contain copies of the original motifs.

Return type

Qmotifs

cycle(Q, stride=1, step=1, offset=0, boundary='periodic', arity=2)

The cycle pattern

get_symbols()

Get the symbols of the motif. If share_weights is True, then symbols are obtained from the first edge_mapping. Otherwise, symbols are obtained from each edge_mapping.

Yields

symbols (List) or None – List of symbols or None if no edge_mapping.

set_E(E)

Set the edges E of the motif.

Parameters

E (list(tuples)) – List of edges, where each edge is a tuple of qubit labels (self.Q).

set_Q(Q)

Set the qubit labels Q of the motif.

Parameters

Q (list(int or string)) – List of qubit labels.

set_Qavail(Q_avail)

Set the number of available qubits Q_avail for the motif. This gets calculated by the previous motif in the stack, for example the :py:class:Qmask motif would mask qubits and use this function to update the available qubits after it’s action. Example: if Q = [1,2,3,4] and Qmask(“right”) is applied then Q_avail = [1,2].

Parameters

Q_avail (list) – List of available qubits.

set_arity(arity)

Set the arity of the motif (qubits per unitary/ nodes per edge).

Parameters

arity (int) – Number of qubits per unitary

set_edge_mapping(unitary_function)

Maps each edge to a unitary function.

Parameters

edge_mapping (function) – function for each edge.

set_edge_order(edge_order)

Set the edge order of the motif (order of unitaries applied). [1] means first edge comes first [2,8] means second edge comes first, then 8th edge comes second. For example, if self.E = [(1,2),(7,3),(5,2)] then self.edge_order = [1,2,3] means the unitaries are applied in the order (1,2),(7,3),(5,2). If self.edge_order = [2,1,3] then the unitaries are applied in the order (7,3),(1,2),(5,2). If self.edge_order = [3] then the unitaries are applied in the order (5,2),(1,2),(7,3),.

Parameters

edge_order (list(int)) – List of edge orders.

set_is_operation(is_operation)

Set the is_operation flag.

Parameters

is_operation (bool) – Whether the motif is an operation.

set_mapping(mapping)

Specify the unitary operations applied according to the type of motif.

Parameters

mapping (Qhierarchy or Qunitary) – Unitary operation applied to the motif.

set_next(next)

Set the next motif in the stack.

Parameters

next (Qmotif) – Next motif in the stack.

set_prev(prev)

Set the previous motif in the stack.

Parameters

prev (Qmotif) – Previous motif in the stack.

set_share_weights(share_weights)

Set the share_weights flag.

Parameters

share_weights (bool) – Whether to share weights within a motif.

set_symbols(symbols=None, start_idx=0)

Set the symbol’s.

Parameters
  • symbols (list) – List of symbols to set.

  • start_idx (int) – Starting index of symbols, this is used when :py:class:Qhierarchy updates the stack, it loops through each motif counting symbols, at each motif it updates the symbols (inderectly calls this function) and send the current count as starting inde so that correct sympy symbol indices are used.

class hierarqcal.core.Qmotifs(iterable=(), /)

Bases: tuple

A tuple of motifs, this is the data structure for storing sequences motifs. It subclasses tuple, so all tuple methods are available.

__add__(other)

Add two tuples of motifs together.

Parameters

other (Qmotifs or Qmotif) – Multiple motifs or singe motif to add to current sequence of motifs.

Returns

A single tuple of the motifs that were added, These are copies of the original motifs, since tuples are immutable.

Return type

Qmotifs(tuple)

__mul__(other)

Repeat motifs “other” times.

Parameters

other (int) – Number of times to repeat motifs.

Returns

A tuple of motifs: (self, self, …, self), where each is a new object copied from the original.

Return type

Qmotifs(tuple)

Raises

ValueError – Only integers are allowed for multiplication.

class hierarqcal.core.Qpermute(combinations=True, **kwargs)

Bases: Qmotif

A dense motif, it connects unitaries to all possible combinations of qubits (all possible edges given Q) in the quantum circuit.

__call__(Qc_l, *args, **kwargs)

Call the motif, this is used to generate the edges and qubits of the motif (directed graph) based on it’s available qubits. Each time a motif in the stack changes, a loop runs through the stack from the beginning and calls each motif to update the graph (the available qubits, the edges etc).

Parameters
  • Qc_l (list) – List of available qubits.

  • *args – Variable length argument list.

  • **kwargs – Arbitrary keyword arguments, such as:

    • mapping (tuple(function, int)):

      Function mapping is specified as a tuple, where the first argument is a function and the second is the number of symbols it uses. A symbol here refers to an variational paramater for a quantum circuit, i.e. crz(theta, q0, q1) <- theta is a symbol for the gate.

Returns

Returns the updated version of itself, with correct nodes and edges.

Return type

Qdense

class hierarqcal.core.Qpivot(global_pattern='1*', merge_within='1*', merge_between=None, strides=[1, 1, 0], steps=[1, 1, 1], offsets=[0, 0, 0], boundaries=['open', 'open', 'periodic'], **kwargs)

Bases: Qsplit

The pivot connects the set of available qubits sequentially to a fixed set of qubits. The global pattern determine the pivot points while the merge pattern determines how the qubits are passed to the mapping.

__call__(Qp_l, *args, **kwargs)
Parameters
  • Qp_l (list) – List of available qubits.

  • *args – Variable length argument list.

  • **kwargs – Arbitrary keyword arguments, such as: * mapping (tuple(function, int)):

    Function mapping is specified as a tuple, where the first argument is a function and the second is the number of symbols it uses. A symbol here refers to an variational paramater for a quantum circuit, i.e. crz(theta, q0, q1) <- theta is a symbol for the gate.

Returns:

cycle_between_splits(E_a, E_b, stride=0, step=1, offset=0, boundary='open')
class hierarqcal.core.Qsplit(global_pattern='1*', merge_within='1*', merge_between=None, mask=False, strides=[1, 1, 0], steps=[1, 1, 1], offsets=[0, 0, 0], boundaries=['open', 'open', 'periodic'], type=Primitive_Types.SPLIT, **kwargs)

Bases: Qmotif

__call__(Q, E=[], remaining_q=None, is_operation=True, **kwargs)

Call self as a function.

cycle_between_splits(E_a, E_b, stride=0, step=1, offset=0, boundary='open')
get_pattern_fn(pattern, length)
get_predefined_pattern_fn(pattern)
merge_within_splits(E, merge_pattern)
wildcard_populate(pattern, length)
class hierarqcal.core.Qunitary(function=None, n_symbols=0, arity=2, symbols=None)

Bases: object

Base class for all unitary operations, the main purpose is to store the operation, its arity and parameters (symbols) for later use. # TODO add support for function as matrix # TODO add share_weights parameter

__call__(*args, **kwargs)

Call self as a function.

get_circ_info_from_string(input_str)

Takes a string that represents a circuit function, and breaks down the string into a set of circuit instructions.

Parameters

`input_str` (str)

Returns

a list of circuit instructions, where each entry represents a distinct gate operation. Each entry is a list of three components: [gate_name,symbol_info, sub_bits]

  1. gate_name (str) is the name of the Qiskit gate being implemented.

  2. symbol_info (list) keeps track of whether the gate is parametrized, and

    if so, whether it is the same parameter as another gate.

  3. sub_bits (list of ints) keeps track of the bits the gates are applied on.

unique_bits (list of ints): set of qubits unique_params (list of strs): set of gate parameters

Return type

substr_list (list)

Workflow:
Step 1: partition the string into lists of individual gate instructions

in the form {gate_string}(parameters)^{bits}

Step 2: split each substring into the gate string, the relevant

parameters, and the bits it acts on

Step 3: convert the bits, the gate string, and the relevant parameters

into integers/functions

get_symbols()

Get symbols for this unitary.

Returns: List of symbols

set_edge(edge)
set_symbols(symbols=None)

Set symbols for this unitary.

Parameters

symbols (list) – List of symbols

class hierarqcal.core.Qunmask(*args, **kwargs)

Bases: Qsplit

__call__(Qp_l, *args, **kwargs)

TODO