qat.core.Circuit

class qat.core.Circuit(ops=None, name=None, gateDic=None, nbqbits=None, nbcbits=0, _gate_set=None, has_matrices=None, var_dic=None, qregs=None, ancilla_map=None, _serialized_gate_set=None)

Simple higher level wrapper for the serializable Circuit class.

This wrapper provides high-level manipulation routines such as variable binding or iteration.

Circuits are usually built using the .to_circ method of the Program class, and are not designed to be built by hand.

The simplest way to explore the content of a circuit is to use the .iterate_simple method that provides a user friendly sequence of instructions:

from qat.lang.AQASM import *
prog = Program()
cbits = prog.calloc(4)
qbits = prog.qalloc(4)
for qb in qbits:
    H(qb)
RX(prog.new_var(float, "a")).ctrl(3)(qbits)
CNOT(qbits[0], qbits[1])
prog.measure(qbits[1])
prog.reset([qbits[3]], [cbits[2]])

circuit = prog.to_circ()

for op in circuit.iterate_simple():
    print(op)
('H', [], [0])
('H', [], [1])
('H', [], [2])
('H', [], [3])
('C-C-C-RX', [<qat.core.variables.Variable object at 0x148c3d24cc40>], [0, 1, 2, 3])
('CNOT', [], [0, 1])
('MEASURE', [1], [1])
('RESET', [3], [2])
bind_variable(v_name, v_value, gate_set=None)

Bind variable v_name with value v_value. Returns a fresh circuit. If binding the variable result in a gate becoming fully defined, its matrix will be regenerated using the default gate set of pyAQASM (or using a custom gate set).

Warning

For performances reasons, this method does not fully copy the circuit. In particular, modifying some portions of the resulting circuit will have a side effect of the initial circuit. To avoid this behavior, feel free to deepcopy the circuit beforehand.

Parameters
  • v_name (str) – the variable name

  • v_value (any) – the value

  • gate_set (optional, GateSet) – a gate set to use to generate the matrices for freshly bound gates

Returns

a quantum circuit

Return type

Circuit

bind_variables(v_dictionary, gate_set=None, **kwargs)

Iterates bind_variable() over a dictionary of (names, values).

Parameters
  • v_dictionary (map<str,any>) – the variable names/values dictionary

  • gate_set (optional, GateSet) – a gate set to use to generate the matrices for freshly bound gates. If not None, the current gate set will be updated and used to fill the matrices

Returns

a quantum circuit.

Return type

Circuit

compile(qpu, specs=None, **kwargs)

Compiles the circuit given some QPU or stack.

If the first argument is a QPU, then this call is equivalent to:

qpu.compile(circuit.to_job(**kwargs))

Otherwise, if the first argument is a Plugin, this is equivalent to:

plugin.compile(circuit.to_job(**kwargs), specs)
Parameters
  • qpu (QPUHandler or AbstractPlugin) – a plugin or a qpu

  • specs (optional,:class:~qat.core.HardwareSpecs) – optionally, some specs (only used when qpu is a Plugin) kwargs (keyword arguments): these are passed to the .to_job method of the circuit

Returns

a compiled job or a batch of jobs if the compilation produced

several jobs

Return type

Job or Batch

Note

Note that the return type is a Job and not a Circuit. This is due to the fact that some Plugins might change the sampling directive in the incoming job (e.g. shuffle the order of the measured qubits).

count(gate)

Return the number of occurences of a given gate.

Parameters

gate (str) – a gate name

Returns

a gate count

Return type

int

dag(dagger_from_params=None, **kwargs)

Computes the dagger of a circuit. This function:

  • crawls the gate dictionary and replace subgates by their dagger

  • clears the implementations of these subgates (i.e their matrices, subcircuits, etc)

  • reverts the list of instructions of the circuit

Raises
  • TypeError if the circuit contains non-unitary instructions – (i.e classical operations, measurements, etc)

  • ValueError if the circuit contains a gate with no proper – syntax, or if gate name is not in the dagger_from_params map.

Parameters
  • dagger_from_params (dict, optional) – a dictionary that maps gate names to callable that, given a set of parameters, returns the new parameters that implement the dagger of the gate (see example below)

  • kwargs – any additional keyword arguments are fed to a Linker in order to re-generate the various matrices and subcircuits.

Example

By default, the default gate set of pyAQASM is fully supported:

# Building a simple circuit
from qat.lang.AQASM import *

prog = Program()
qbits = prog.qalloc(2)
H(qbits[0])
PH(1.22)(qbits[1])
RY(3.9).ctrl()(qbits)
CNOT(qbits)
circuit = prog.to_circ()

print("Circuit dagger:")
for op in circuit.dag():
    print(op)
Circuit dagger:
Op(gate='CNOT', qbits=[0, 1], type=0, cbits=None, formula=None, remap=None)
Op(gate='_10', qbits=[0, 1], type=0, cbits=None, formula=None, remap=None)
Op(gate='_8', qbits=[1], type=0, cbits=None, formula=None, remap=None)
Op(gate='H', qbits=[0], type=0, cbits=None, formula=None, remap=None)

If the circuit contains custom asbtract gate, one can specify a way to change their parameters in order to invert them. The following examples shows how to define a \(ZZ\) rotation and specify that one needs to flip the sign of its angle in order to invert it.

# Building a simple circuit
from qat.lang.AQASM import *

ZZ = AbstractGate("ZZ", [float], arity=2)

prog = Program()
qbits = prog.qalloc(2)
H(qbits[0])
CNOT(qbits)
ZZ(5.334)(qbits)
circuit = prog.to_circ()

# Default dag function
print("=== Default dagger (default) ===")
dagger = circuit.dag()

for gate in dagger.iterate_simple():
    print(gate)

# Dag rule
dagger_from_params = {
    "ZZ": lambda params: [- params[0]],
}
print("=== Circuit dagger (smarter) ===")
smart_dagger = circuit.dag(dagger_from_params)

for gate in smart_dagger.iterate_simple():
    print(gate)
=== Default dagger (default) ===
('D-ZZ', [5.334], [0, 1])
('CNOT', [], [0, 1])
('H', [], [0])
=== Circuit dagger (smarter) ===
('ZZ', [-5.334], [0, 1])
('CNOT', [], [0, 1])
('H', [], [0])
depth(gate_times=None, gates=None, default=0)

Compute the depth of the quantum circuit.

The execution time of each gate can either be specified via a dictionary gate_times or a list of gates gates.

Via dictionary: each gate has an execution time as specified by the dictionary, or default if not in the dictionary.

Via a list: each gate in the list has execution time 1, all other gates have execution time default

Parameters
  • gate_times (optional, dict) – a dictionary mapping strings to floats

  • gates (optional, list) – a list of gate names

  • default (optional, float) – the default execution time of all unspecified gates

Returns

the overall execution time of the circuit

Return type

float

display(**kwargs)

Displays the circuit. This method adapts to the environment (i.e. notebook or terminal).

It will call one of the following methods:

All kwargs are passed to these methods.

dump(fname)

Dumps the circuit in a binary file (.circ)

Parameters

fname (str) – the file name

static empty(nbqbits)

Generates an empty circuit

Parameters

nbqbits (int) – the number of qbits of the circuit

Returns

an empty circuit

Return type

Circuit

eval(*args, **kwargs)

Turns the Circuit into a Job and calls the .eval method.

All arguments are passed to the .eval method of the resulting job.

These two lines are equivalent:

circuit.eval(a, b, c, d=e)
# and
circuit.to_job().eval(a, b, c, d=e)
fill_matrices(gate_set, submatrices_only=True, include_default_gate_set=False)

Attaches matrices to gates according to the matrix generators in gate_set.

Parameters
  • gate_set (GateSet) – a gate set specifying the matrix generators of the gates present in the circuit. Note that if some gate has no corresponding entry in the gate set or its signature has no matrix generator, no matrix will be attached to its definition.

  • submatrices_only (bool) –

    If set to True, only subgate will receive matrices. This is enough for some simulators (such as qat.qpus.LinAlg). Default to True.

  • include_default_gate_set (bool) – if set to True, the native gate set of pyAQASM will be included. Default to False.

classmethod from_bytes(data: bytes)

Builds a circuit from raw bytes object

classmethod from_thrift(tobject)

Builds a Circuit from a thrift circuit

property gate_set

The gate set used to generate the circuit

get_variables()

Returns the sorted list of variables appearing in the circuit

insert_gate(position, gate, qbits, **kwargs)

Inserts a pyAQASM gate inside the circuit.

Parameters
  • position (int) – index where to insert the gate

  • gate (Gate) – a pyAQASM gate (or routine)

  • qbits (list<int>) – a list of integers

  • kwargs (dict) – all the keyword arguments are fed to the linker

iterate_simple(depth=None)

Iterates over the instructions of the circuit in a user friendly way.

The iterator yields tuples with explicit content. Each tuples starts with a keyword or a gate name, followed by the arguments of the instruction.

E.g, a measure of qubits 0 and 1 stored in cbits 2 and 3 would yield:

("MEASURE", [0, 1], [2, 3])
classmethod load(fname)

Loads a circuit from a binary (.circ) file.

Parameters

fname (str) – the file name

Returns

a circuit

Return type

Circuit

remove_locks()

Remove all lock/release placeholders of the circuit.

run(qpu=None, **kwargs)

Runs the circuit on a qpu.

The two following lines of code are equivalent:

result = circuit.run(qpu, **kwargs)
# and
result = qpu.submit(circuit.to_job(**kwargs))
Parameters
  • qpu (optional, QPUHandler) – the target qpu. If None, import the default qpu (via get_default_qpu())

  • kwargs (keyword arguments) – these are passed to the .to_job method of the circuit

Returns

a result

Return type

Result

shift_qbits(offset)

Shift all the qbit indexes of the circuit by a given offset. Consequently increases the number of qbits by the same offset.

Acts in place.

Parameters

offset (int) – the offset

statistics()

Returns some statistics on the circuit.

The returned value is a dictionary with a very straightforward content.

Returns

a dictionary

Return type

dict

to_job(job_type='SAMPLE', qubits=None, nbshots=0, aggregate_data=True, amp_threshold=9.094947017729282e-13, **kwargs)

Generates a Job containing the circuit and some post processing information.

Parameters
  • job_type (str) – possible values are “SAMPLE” for computational basis sampling of some qubits, or “OBS” for observable evaluation (see qat.core.Observable for more information about this mode).

  • qubits (optional, list<int>, list<QRegister>) – the list of qubits to measure (in “SAMPLE” mode). If some quantum register is passed instead, all the qubits of the register will be measured. Moreover, if the register was “typed” (see qat.lang.Program.qalloc() for more information), the results will be cast into the register type. Defaults to None, meaning all qubits are to be measured.

  • nbshots (optional, int) – The number of shots to perform. Defaults to zero. If set to zero, the convention is that the QPU should do its best: a quantum processor will use the largest amount of shot authorized by its configuration, while a simulator will try to output all the possible states constained in the final distribution, together with their probabilities (and possible amplitudes).

  • aggregate_data (optional, bool) – if set to True, and nbshots is not zero, the samples will be aggregated and their probability field will be used to store their observed frequencies of apparition. Defaults to True.

  • amp_threshold (optional, double) – amplitude threshold under which states are not returned in the result structure. Useful to prune states that are unlikely to be measured out of the returned samples. Defaults to 1/2^40.

Keyword Arguments

observable (Observable) – see Observable. Used for the “OBS” mode only).

Most useful Circuit methods

One can easily concatenate two Circuit objects with the overloaded __add__ operator (as long as they have the same number of qubits):

cat_circuit = circuit1 + circuit2

Tensorial composition is implemented via an overloading of the __mult__ operator:

kron_circuit = circuit1 * circuit2

Among the other useful methods, we can find:

  • The direct generation of a Job object from a circuit using various parameters, with the to_job() method:

    job = circuit.to_job(job_type="SAMPLE", nbshots=1024, qubits=[0, 3, 7])
    
  • Serialization/deserialization of a circuit can be done using the dump() and load() methods:

    circuit.dump("my_circuit.circ")
    circuit = Circuit.load("my_circuit.circ")
    
  • Binding of abstract variables using the bind_variables() method:

    new_circuit = circuit(theta=0.34)
    # or, equivalently
    new_circuit = circuit.bind_variables({theta: 0.34})
    

Iterating of a Circuit

from qat.lang.AQASM import Program
from qat.lang.AQASM.qftarith import QFT

prog = Program()
qbits = prog.qalloc(4)
QFT(4)(qbits)
prog.measure([qbits[0], qbits[3]])
prog.reset(qbits[2])
circuit = prog.to_circ()

for instruction in circuit.iterate_simple():
    print(instruction)
('H', [], [0])
('C-PH', [1.5707963267948966], [1, 0])
('C-PH', [0.7853981633974483], [2, 0])
('C-PH', [0.39269908169872414], [3, 0])
('H', [], [1])
('C-PH', [1.5707963267948966], [2, 1])
('C-PH', [0.7853981633974483], [3, 1])
('H', [], [2])
('C-PH', [1.5707963267948966], [3, 2])
('H', [], [3])
('MEASURE', [0, 3], [0, 3])
('RESET', [2], [])
  • Iterating over raw instructions (for advanced usage):

from qat.lang.AQASM import Program
from qat.lang.AQASM.qftarith import QFT

prog = Program()
qbits = prog.qalloc(4)
QFT(4)(qbits)
prog.measure([qbits[0], qbits[3]])
prog.reset(qbits[2])
circuit = prog.to_circ()

for instruction in circuit:
    print(instruction)
Op(gate='H', qbits=[0], type=0, cbits=None, formula=None, remap=None)
Op(gate='_2', qbits=[1, 0], type=0, cbits=None, formula=None, remap=None)
Op(gate='_4', qbits=[2, 0], type=0, cbits=None, formula=None, remap=None)
Op(gate='_6', qbits=[3, 0], type=0, cbits=None, formula=None, remap=None)
Op(gate='H', qbits=[1], type=0, cbits=None, formula=None, remap=None)
Op(gate='_2', qbits=[2, 1], type=0, cbits=None, formula=None, remap=None)
Op(gate='_4', qbits=[3, 1], type=0, cbits=None, formula=None, remap=None)
Op(gate='H', qbits=[2], type=0, cbits=None, formula=None, remap=None)
Op(gate='_2', qbits=[3, 2], type=0, cbits=None, formula=None, remap=None)
Op(gate='H', qbits=[3], type=0, cbits=None, formula=None, remap=None)
Op(gate=None, qbits=[0, 3], type=1, cbits=[0, 3], formula=None, remap=None)
Op(gate=None, qbits=[2], type=2, cbits=[], formula=None, remap=None)

Qubits and cbits

The number of qubits and classical bits declared in the circuit can be accessed like so:

circuit.nbqbits
circuit.nbcbits

At circuit generation, the convention is to extend the number of classical bits to match the number of declared qubits. So it might be that your didn’t declare any cbits in pyAQASM, and still end up with a non-zero number of classical bits.

The field nbqbits might also be extended to match the total number of qbits used by the circuit (for instance if a sub-routine is using some ancillae that are dynamically allocated at inlining/emulation/execution).

This extension requires to emulate the flow of the circuit.

All quantum registers declared in pyAQASM can be found in the .qregs field. The type of these registers is also stored in the QReg structure:

for qreg in circuit.qregs:
    print("Register of length {} starting at {}".format(qreg.length, qreg.start))

Please refer this section of the user guide regarding quantum types for more information on registers.