Structured state evolution#
Module for circuit simulation by state evolution, where the state is
represented by a tensor network with a predefined structure.
Approximate tensor network contraction is supported. Both MPS
and TTN
methods are provided.
For an example of its use, see the examples/
folder at
CQCL/pytket-cutensornet.
Simulation#
- pytket.extensions.cutensornet.structured_state.simulate(libhandle: CuTensorNetHandle, circuit: Circuit, algorithm: SimulationAlgorithm, config: Config) StructuredState #
Simulates the circuit and returns the
StructuredState
of the final state.Note
A
libhandle
is created via awith CuTensorNetHandle() as libhandle:
statement. The device where theStructuredState
is stored will match the one specified by the library handle.The input
circuit
must be composed of one-qubit and two-qubit gates only. Any gateset supported bypytket
can be used.- Parameters:
libhandle – The cuTensorNet library handle that will be used to carry out tensor operations.
circuit – The pytket circuit to be simulated.
algorithm – Choose between the values of the
SimulationAlgorithm
enum.config – The configuration object for simulation.
- Returns:
An instance of
StructuredState
for (an approximation of) the final state of the circuit. The instance be of the class matchingalgorithm
.
- enum pytket.extensions.cutensornet.structured_state.SimulationAlgorithm(value)#
An enum to refer to the StructuredState contraction algorithm.
Each enum value corresponds to the class with the same name; see its docs for information about the algorithm.
Valid values are as follows:
- TTNxGate = <SimulationAlgorithm.TTNxGate: 0>#
- MPSxGate = <SimulationAlgorithm.MPSxGate: 1>#
- MPSxMPO = <SimulationAlgorithm.MPSxMPO: 2>#
- class pytket.extensions.cutensornet.structured_state.Config#
Configuration class for simulation using
StructuredState
.- __init__(chi: int | None = None, truncation_fidelity: float | None = None, seed: int | None = None, float_precision: ~typing.Type[~typing.Any] = <class 'numpy.float64'>, value_of_zero: float = 1e-16, leaf_size: int = 8, use_kahypar: bool = False, k: int = 4, optim_delta: float = 1e-05, loglevel: int = 30)#
Instantiate a configuration object for
StructuredState
simulation.Note
Providing both a custom
chi
andtruncation_fidelity
will raise an exception. Choose one or the other (or neither, for exact simulation).- Parameters:
chi – The maximum value allowed for the dimension of the virtual bonds. Higher implies better approximation but more computational resources. If not provided,
chi
will be unbounded.truncation_fidelity – Every time a two-qubit gate is applied, the virtual bond will be truncated to the minimum dimension that satisfies
|<psi|phi>|^2 >= trucantion_fidelity
, where|psi>
and|phi>
are the states before and after truncation (both normalised). If not provided, it will default to its maximum value 1.seed – Seed for the random number generator. Setting a seed provides reproducibility across simulations using
StructuredState
, in the sense that they will produce the same sequence of measurement outcomes. Crucially, consecutive samples taken from the sameStructuredState
can still be different from each other.float_precision – The floating point precision used in tensor calculations; choose from
numpy
types:np.float64
ornp.float32
. Complex numbers are represented using two of suchfloat
numbers. Default isnp.float64
.value_of_zero – Any number below this value will be considered equal to zero. Even when no
chi
ortruncation_fidelity
is provided, singular values below this number will be truncated. We suggest to use a value slightly below what your chosenfloat_precision
can reasonably achieve. For instance,1e-16
fornp.float64
precision (default) and1e-7
fornp.float32
.leaf_size – For
TTN
simulation only. Sets the maximum number of qubits in a leaf node when usingTTN
. Default is 8.use_kahypar – Use KaHyPar for graph partitioning (used in
TTN
) if this is True. Otherwise, use NetworkX (worse, but easy to setup). Defaults to False.k – For
MPSxMPO
simulation only. Sets the maximum number of layers the MPO is allowed to have before being contracted. Increasing this might increase fidelity, but it will also increase resource requirements exponentially. Default value is 4.optim_delta – For
MPSxMPO
simulation only. Sets the stopping criteria for the optimisation when contracting thek
layers of MPO. Stops when the increase of fidelity between iterations is smaller than this value. Default value is1e-5
.loglevel – Internal logger output level. Use 30 for warnings only, 20 for verbose and 10 for debug mode.
- Raises:
ValueError – If both
chi
andtruncation_fidelity
are fixed.ValueError – If the value of
chi
is set below 2.ValueError – If the value of
truncation_fidelity
is not in [0,1].
Classes#
- class pytket.extensions.cutensornet.structured_state.StructuredState#
Class representing a Tensor Network state.
- abstract is_valid() bool #
Verify that the tensor network state is valid.
- Returns:
False if a violation was detected or True otherwise.
- abstract apply_gate(gate: Command) StructuredState #
Applies the gate to the StructuredState.
- Parameters:
gate – The gate to be applied.
- Returns:
self
, to allow for method chaining.- Raises:
RuntimeError – If the
CuTensorNetHandle
is out of scope.ValueError – If the command introduced is not a unitary gate.
ValueError – If gate acts on more than 2 qubits.
- abstract apply_unitary(unitary: cp.ndarray, qubits: list[Qubit]) StructuredState #
Applies the unitary to the specified qubits of the StructuredState.
Note
It is assumed that the matrix provided by the user is unitary. If this is not the case, the program will still run, but its behaviour is undefined.
- Parameters:
unitary – The matrix to be applied as a CuPy ndarray. It should either be a 2x2 matrix if acting on one qubit or a 4x4 matrix if acting on two.
qubits – The qubits the unitary acts on. Only one qubit and two qubit unitaries are supported.
- Returns:
self
, to allow for method chaining.- Raises:
RuntimeError – If the
CuTensorNetHandle
is out of scope.ValueError – If the number of qubits provided is not one or two.
ValueError – If the size of the matrix does not match with the number of qubits provided.
- abstract apply_scalar(scalar: complex) StructuredState #
Multiplies the state by a complex number.
- Parameters:
scalar – The complex number to be multiplied.
- Returns:
self
, to allow for method chaining.
- abstract vdot(other: StructuredState) complex #
Obtain the inner product of the two states:
<self|other>
.It can be used to compute the squared norm of a state
state
asstate.vdot(state)
. The tensors within the state are not modified.Note
The state that is conjugated is
self
.- Parameters:
other – The other
StructuredState
.- Returns:
The resulting complex number.
- Raises:
RuntimeError – If the two states do not have the same qubits.
RuntimeError – If the
CuTensorNetHandle
is out of scope.
- abstract sample() dict[pytket.unit_id.Qubit, int] #
Returns a sample from a Z measurement applied on every qubit.
Notes
The contents of
self
are not updated. This is equivalent to applyingstate = self.copy()
thenstate.measure(state.get_qubits())
.- Returns:
A dictionary mapping each qubit in the state to its 0 or 1 outcome.
- abstract measure(qubits: set[pytket.unit_id.Qubit], destructive: bool = True) dict[pytket.unit_id.Qubit, int] #
Applies a Z measurement on each of the
qubits
.Notes
After applying this function,
self
will contain the normalised projected state.- Parameters:
qubits – The subset of qubits to be measured.
destructive – If
True
, the resulting state will not contain the measured qubits. IfFalse
, these qubits will appear on the state corresponding to the measurement outcome. Defaults toTrue
.
- Returns:
A dictionary mapping the given
qubits
to their measurement outcome, i.e. either0
or1
.- Raises:
ValueError – If an element in
qubits
is not a qubit in the state.
- abstract postselect(qubit_outcomes: dict[pytket.unit_id.Qubit, int]) float #
Applies a postselection, updates the states and returns its probability.
Notes
After applying this function,
self
will contain the projected state over the non-postselected qubits.The resulting state has been normalised.
- Parameters:
qubit_outcomes – A dictionary mapping a subset of qubits to their desired outcome value (either
0
or1
).- Returns:
The probability of this postselection to occur in a measurement.
- Raises:
ValueError – If a key in
qubit_outcomes
is not a qubit in the state.ValueError – If a value in
qubit_outcomes
is other than0
or1
.ValueError – If all of the qubits in the state are being postselected. Instead, you may wish to use
get_amplitude()
.
- abstract expectation_value(pauli_string: QubitPauliString) float #
Obtains the expectation value of the Pauli string observable.
- Parameters:
pauli_string – A pytket object representing a tensor product of Paulis.
- Returns:
The expectation value.
- Raises:
ValueError – If a key in
pauli_string
is not a qubit in the state.
- abstract get_statevector() ndarray #
Returns the statevector with qubits in Increasing Lexicographic Order (ILO).
- Raises:
ValueError – If there are no qubits left in the state.
- abstract get_amplitude(state: int) complex #
Returns the amplitude of the chosen computational state.
Notes
The result is equivalent to
state.get_statevector[b]
, but this method is faster when querying a single amplitude (or just a few).- Parameters:
state – The integer whose bitstring describes the computational state. The qubits in the bitstring are in increasing lexicographic order.
- Returns:
The amplitude of the computational state in
self
.
- abstract get_qubits() set[pytket.unit_id.Qubit] #
Returns the set of qubits that
self
is defined on.
- abstract get_device_id() int #
Returns the identifier of the device (GPU) where the tensors are stored.
- abstract update_libhandle(libhandle: CuTensorNetHandle) None #
Update the
CuTensorNetHandle
used byself
. Multiple objects may use the same handle.- Parameters:
libhandle – The new cuTensorNet library handle.
- Raises:
RuntimeError – If the device (GPU) where
libhandle
was initialised does not match the one where the tensors ofself
are stored.
- abstract copy() StructuredState #
Returns a deep copy of
self
on the same device.
- class pytket.extensions.cutensornet.structured_state.TTNxGate#
Implements a gate-by-gate contraction algorithm to calculate the output state of a circuit as a
TTN
.- __init__(libhandle: CuTensorNetHandle, qubit_partition: dict[int, list[pytket.unit_id.Qubit]], config: Config)#
Initialise a TTN on the computational state
|0>
.Note
A
libhandle
should be created via awith CuTensorNet() as libhandle:
statement. The device where the TTN is stored will match the one specified by the library handle.The current implementation requires the keys of
qubit_partition
to be integers from0
to2^l - 1
for somel
.- Parameters:
libhandle – The cuTensorNet library handle that will be used to carry out tensor operations on the TTN.
qubit_partition – A partition of the qubits in the circuit into disjoint groups, describing the hierarchical structure of the TTN. Each key identifies a leaf of the TTN, with its corresponding value indicating the list of qubits represented by the leaf. The leaves are numbered from left to right on a planar representation of the tree. Hence, the smaller half of the keys correspond to leaves in the left subtree and the rest are in the right subtree; providing recursive bipartitions.
config – The object describing the configuration for simulation.
- Raises:
ValueError – If the keys of
qubit_partition
do not range from0
to2^l - 1
for somel
.ValueError – If a
Qubit
is repeated inqubit_partition
.ValueError – If there is only one entry in
qubit_partition
.
- class pytket.extensions.cutensornet.structured_state.MPSxGate#
Implements a gate-by-gate contraction algorithm to calculate the output state of a circuit as an
MPS
. The algorithm is described in: https://arxiv.org/abs/2002.07730- __init__(libhandle: CuTensorNetHandle, qubits: list[pytket.unit_id.Qubit], config: Config)#
Initialise an MPS on the computational state
|0>
Note
A
libhandle
should be created via awith CuTensorNet() as libhandle:
statement. The device where the MPS is stored will match the one specified by the library handle.- Parameters:
libhandle – The cuTensorNet library handle that will be used to carry out tensor operations on the MPS.
qubits – The list of qubits in the circuit to be simulated.
config – The object describing the configuration for simulation.
- Raises:
ValueError – If less than two qubits are provided.
- add_qubit(new_qubit: Qubit, position: int, state: int = 0) MPS #
Adds a qubit at the specified position.
- Parameters:
new_qubit – The identifier of the qubit to be added to the state.
position – The location the new qubit should be inserted at in the MPS. Qubits on this and later indexed have their position shifted by 1.
state – Choose either
0
or1
for the new qubit’s state. Defaults to0
.
- Returns:
self
, to allow for method chaining.- Raises:
ValueError – If
new_qubit
already exists in the state.ValueError – If
position
is negative or larger thanlen(self)
.ValueError – If
state
is not0
or1
.
- class pytket.extensions.cutensornet.structured_state.MPSxMPO#
Implements a batched–gate contraction algorithm (DMRG-like) to calculate the output state of a circuit as an
MPS
. The algorithm is described in: https://arxiv.org/abs/2207.05612.- __init__(libhandle: CuTensorNetHandle, qubits: list[pytket.unit_id.Qubit], config: Config)#
Initialise an MPS on the computational state
|0>
.Note
A
libhandle
should be created via awith CuTensorNet() as libhandle:
statement. The device where the MPS is stored will match the one specified by the library handle.- Parameters:
libhandle – The cuTensorNet library handle that will be used to carry out tensor operations on the MPS.
qubits – The list of qubits in the circuit to be simulated.
config – The object describing the configuration for simulation.
- add_qubit(new_qubit: Qubit, position: int, state: int = 0) MPS #
Adds a qubit at the specified position.
- Parameters:
new_qubit – The identifier of the qubit to be added to the state.
position – The location the new qubit should be inserted at in the MPS. Qubits on this and later indexed have their position shifted by 1.
state – Choose either
0
or1
for the new qubit’s state. Defaults to0
.
- Returns:
self
, to allow for method chaining.- Raises:
ValueError – If
new_qubit
already exists in the state.ValueError – If
position
is negative or larger thanlen(self)
.ValueError – If
state
is not0
or1
.
Miscellaneous#
- pytket.extensions.cutensornet.structured_state.prepare_circuit_mps(circuit: Circuit) tuple[pytket.circuit.Circuit, dict[pytket.unit_id.Qubit, pytket.unit_id.Qubit]] #
Adds SWAP gates to the circuit so that all gates act on adjacent qubits.
The qubits in the output circuit will be renamed. Implicit SWAPs may be added to the circuit, meaning that the logical qubit held at the
node[i]
qubit at the beginning of the circuit may differ from the one it holds at the end. Consider applyingapply_qubit_relabelling
on the MPS after simulation.Note
This preprocessing is not required by the MPS algorithms we provide. Shallow circuits tend to run faster if this preprocessing is not used. In occassions, it has been shown to improve runtime for deep circuits.
- Parameters:
circuit – The circuit to be simulated.
- Returns:
A tuple with an equivalent circuit with the appropriate structure and a map of qubit names at the end of the circuit to their corresponding original names.