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 https://github.com/CQCL/pytket-cutensornet.

Simulation

pytket.extensions.cutensornet.structured_state.simulate(libhandle, circuit, algorithm, config)[source]

Simulates the circuit and returns the StructuredState of the final state.

Note

A libhandle is created via a with CuTensorNetHandle() as libhandle: statement. The device where the StructuredState 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 by pytket can be used.

Parameters:
  • libhandle (CuTensorNetHandle) – The cuTensorNet library handle that will be used to carry out tensor operations.

  • circuit (Circuit) – The pytket circuit to be simulated.

  • algorithm (SimulationAlgorithm) – Choose between the values of the SimulationAlgorithm enum.

  • config (Config) – The configuration object for simulation.

Return type:

StructuredState

Returns:

An instance of StructuredState for (an approximation of) the final state of the circuit. The instance be of the class matching algorithm.

enum pytket.extensions.cutensornet.structured_state.SimulationAlgorithm(value)[source]

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(chi=None, truncation_fidelity=None, seed=None, float_precision=<class 'numpy.float64'>, value_of_zero=1e-16, leaf_size=8, use_kahypar=False, k=4, optim_delta=1e-05, loglevel=30)[source]

Configuration class for simulation using StructuredState.

__init__(chi=None, truncation_fidelity=None, seed=None, float_precision=<class 'numpy.float64'>, value_of_zero=1e-16, leaf_size=8, use_kahypar=False, k=4, optim_delta=1e-05, loglevel=30)[source]

Instantiate a configuration object for StructuredState simulation.

Note

Providing both a custom chi and truncation_fidelity will raise an exception. Choose one or the other (or neither, for exact simulation).

Parameters:
  • chi (Optional[int]) – 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 (Optional[float]) – 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 (Optional[int]) – 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 same StructuredState can still be different from each other.

  • float_precision (Type[Any]) – The floating point precision used in tensor calculations; choose from numpy types: np.float64 or np.float32. Complex numbers are represented using two of such float numbers. Default is np.float64.

  • value_of_zero (float) – Any number below this value will be considered equal to zero. Even when no chi or truncation_fidelity is provided, singular values below this number will be truncated. We suggest to use a value slightly below what your chosen float_precision can reasonably achieve. For instance, 1e-16 for np.float64 precision (default) and 1e-7 for np.float32.

  • leaf_size (int) – For TTN simulation only. Sets the maximum number of qubits in a leaf node when using TTN. Default is 8.

  • use_kahypar (bool) – Use KaHyPar for graph partitioning (used in TTN) if this is True. Otherwise, use NetworkX (worse, but easy to setup). Defaults to False.

  • k (int) – 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 (float) – For MPSxMPO simulation only. Sets the stopping criteria for the optimisation when contracting the k layers of MPO. Stops when the increase of fidelity between iterations is smaller than this value. Default value is 1e-5.

  • loglevel (int) – Internal logger output level. Use 30 for warnings only, 20 for verbose and 10 for debug mode.

Raises:
  • ValueError – If both chi and truncation_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[source]

Class representing a Tensor Network state.

abstract is_valid()[source]

Verify that the tensor network state is valid.

Return type:

bool

Returns:

False if a violation was detected or True otherwise.

abstract apply_gate(gate)[source]

Applies the gate to the StructuredState.

Parameters:

gate (Command) – The gate to be applied.

Return type:

StructuredState

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, qubits)[source]

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 (cp.ndarray) – 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 (list[Qubit]) – The qubits the unitary acts on. Only one qubit and two qubit unitaries are supported.

Return type:

StructuredState

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)[source]

Multiplies the state by a complex number.

Parameters:

scalar (complex) – The complex number to be multiplied.

Return type:

StructuredState

Returns:

self, to allow for method chaining.

abstract vdot(other)[source]

Obtain the inner product of the two states: <self|other>.

It can be used to compute the squared norm of a state state as state.vdot(state). The tensors within the state are not modified.

Note

The state that is conjugated is self.

Parameters:

other (StructuredState) – The other StructuredState.

Return type:

complex

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()[source]

Returns a sample from a Z measurement applied on every qubit.

Notes

The contents of self are not updated. This is equivalent to applying state = self.copy() then state.measure(state.get_qubits()).

Return type:

dict[Qubit, int]

Returns:

A dictionary mapping each qubit in the state to its 0 or 1 outcome.

abstract measure(qubits, destructive=True)[source]

Applies a Z measurement on each of the qubits.

Notes

After applying this function, self will contain the normalised projected state.

Parameters:
  • qubits (set[Qubit]) – The subset of qubits to be measured.

  • destructive (bool) – If True, the resulting state will not contain the measured qubits. If False, these qubits will appear on the state corresponding to the measurement outcome. Defaults to True.

Return type:

dict[Qubit, int]

Returns:

A dictionary mapping the given qubits to their measurement outcome, i.e. either 0 or 1.

Raises:

ValueError – If an element in qubits is not a qubit in the state.

abstract postselect(qubit_outcomes)[source]

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 (dict[Qubit, int]) – A dictionary mapping a subset of qubits to their desired outcome value (either 0 or 1).

Return type:

float

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 than 0 or 1.

  • 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)[source]

Obtains the expectation value of the Pauli string observable.

Parameters:

pauli_string (QubitPauliString) – A pytket object representing a tensor product of Paulis.

Return type:

float

Returns:

The expectation value.

Raises:

ValueError – If a key in pauli_string is not a qubit in the state.

abstract get_fidelity()[source]

Returns the current fidelity of the state.

Return type:

float

abstract get_statevector()[source]

Returns the statevector with qubits in Increasing Lexicographic Order (ILO).

Raises:

ValueError – If there are no qubits left in the state.

Return type:

ndarray

abstract get_amplitude(state)[source]

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 (int) – The integer whose bitstring describes the computational state. The qubits in the bitstring are in increasing lexicographic order.

Return type:

complex

Returns:

The amplitude of the computational state in self.

abstract get_qubits()[source]

Returns the set of qubits that self is defined on.

Return type:

set[Qubit]

abstract get_byte_size()[source]

Returns the number of bytes self currently occupies in GPU memory.

Return type:

int

abstract get_device_id()[source]

Returns the identifier of the device (GPU) where the tensors are stored.

Return type:

int

abstract update_libhandle(libhandle)[source]

Update the CuTensorNetHandle used by self. Multiple objects may use the same handle.

Parameters:

libhandle (CuTensorNetHandle) – The new cuTensorNet library handle.

Raises:

RuntimeError – If the device (GPU) where libhandle was initialised does not match the one where the tensors of self are stored.

Return type:

None

abstract copy()[source]

Returns a deep copy of self on the same device.

Return type:

StructuredState

class pytket.extensions.cutensornet.structured_state.TTNxGate(libhandle, qubit_partition, config)[source]

Implements a gate-by-gate contraction algorithm to calculate the output state of a circuit as a TTN.

__init__(libhandle, qubit_partition, config)

Initialise a TTN on the computational state |0>.

Note

A libhandle should be created via a with 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 from 0 to 2^l - 1 for some l.

Parameters:
  • libhandle (CuTensorNetHandle) – The cuTensorNet library handle that will be used to carry out tensor operations on the TTN.

  • qubit_partition (dict[int, list[Qubit]]) – 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 (Config) – The object describing the configuration for simulation.

Raises:
  • ValueError – If the keys of qubit_partition do not range from 0 to 2^l - 1 for some l.

  • ValueError – If a Qubit is repeated in qubit_partition.

  • ValueError – If there is only one entry in qubit_partition.

class pytket.extensions.cutensornet.structured_state.MPSxGate(libhandle, qubits, config)[source]

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, qubits, config)

Initialise an MPS on the computational state |0>

Note

A libhandle should be created via a with CuTensorNet() as libhandle: statement. The device where the MPS is stored will match the one specified by the library handle.

Parameters:
  • libhandle (CuTensorNetHandle) – The cuTensorNet library handle that will be used to carry out tensor operations on the MPS.

  • qubits (list[Qubit]) – The list of qubits in the circuit to be simulated.

  • config (Config) – The object describing the configuration for simulation.

Raises:

ValueError – If less than two qubits are provided.

add_qubit(new_qubit, position, state=0)

Adds a qubit at the specified position.

Parameters:
  • new_qubit (Qubit) – The identifier of the qubit to be added to the state.

  • position (int) – 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 (int) – Choose either 0 or 1 for the new qubit’s state. Defaults to 0.

Return type:

MPS

Returns:

self, to allow for method chaining.

Raises:
  • ValueError – If new_qubit already exists in the state.

  • ValueError – If position is negative or larger than len(self).

  • ValueError – If state is not 0 or 1.

class pytket.extensions.cutensornet.structured_state.MPSxMPO(libhandle, qubits, config)[source]

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, qubits, config)[source]

Initialise an MPS on the computational state |0>.

Note

A libhandle should be created via a with CuTensorNet() as libhandle: statement. The device where the MPS is stored will match the one specified by the library handle.

Parameters:
  • libhandle (CuTensorNetHandle) – The cuTensorNet library handle that will be used to carry out tensor operations on the MPS.

  • qubits (list[Qubit]) – The list of qubits in the circuit to be simulated.

  • config (Config) – The object describing the configuration for simulation.

add_qubit(new_qubit, position, state=0)

Adds a qubit at the specified position.

Parameters:
  • new_qubit (Qubit) – The identifier of the qubit to be added to the state.

  • position (int) – 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 (int) – Choose either 0 or 1 for the new qubit’s state. Defaults to 0.

Return type:

MPS

Returns:

self, to allow for method chaining.

Raises:
  • ValueError – If new_qubit already exists in the state.

  • ValueError – If position is negative or larger than len(self).

  • ValueError – If state is not 0 or 1.

Miscellaneous

pytket.extensions.cutensornet.structured_state.prepare_circuit_mps(circuit)[source]

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 applying apply_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 (Circuit) – The circuit to be simulated.

Return type:

tuple[Circuit, dict[Qubit, Qubit]]

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.