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 0x150c352644f0>], [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.
- 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
- 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
orAbstractPlugin
) – a plugin or a qpuspecs (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
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.
ExampleBy 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:
qat.core.printer.plot_in_notebook()
if inside a notebookqat.core.console.display()
if in a terminal
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
- 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. 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
- 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 (viaget_default_qpu()
)kwargs (keyword arguments) – these are passed to the .to_job method of the circuit
- Returns
a result
- Return type
- 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
) – seeObservable
. 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 theto_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()
andload()
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
Iterating over the instructions using the
iterate_simple()
method
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.