Matrix Product State (MPS)#

Module for circuit simulation by state evolution, with states represented as Matrix Product States (MPS). Approximate tensor network contraction is supported. For an example of its use, see examples/mps_tutorial.ipynb in CQCL/pytket-cutensornet.

Simulation#

pytket.extensions.cutensornet.mps.simulate(libhandle: CuTensorNetHandle, circuit: Circuit, algorithm: ContractionAlg, config: ConfigMPS) MPS#

Simulate the given circuit and return the MPS representing the final state.

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.

The input circuit must be composed of one-qubit and two-qubit gates only. Any gateset supported by pytket can be used.

Two-qubit gates must act between adjacent qubits, i.e. on circuit.qubits[i] and circuit.qubits[i+1] for any i. If this is not satisfied by your circuit, consider using prepare_circuit() on it.

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

  • circuit – The pytket circuit to be simulated.

  • algorithm – Choose between the values of the ContractionAlg enum.

  • config – The configuration object for simulation.

Returns:

An instance of MPS containing (an approximation of) the final state of the circuit.

enum pytket.extensions.cutensornet.mps.ContractionAlg(value)#

An enum to refer to the MPS contraction algorithm.

Each enum value corresponds to the class with the same name; see its docs for information of the algorithm.

Valid values are as follows:

MPSxGate = <ContractionAlg.MPSxGate: 0>#
MPSxMPO = <ContractionAlg.MPSxMPO: 1>#
class pytket.extensions.cutensornet.mps.ConfigMPS#

Configuration class for simulation using MPS.

__init__(chi: int | None = None, truncation_fidelity: float | None = None, k: int = 4, optim_delta: float = 1e-05, float_precision: ~numpy.float32 | ~numpy.float64 = <class 'numpy.float64'>, value_of_zero: float = 1e-16, loglevel: int = 30)#

Instantiate a configuration object for MPS 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 – 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.

  • k – If using MPSxMPO, 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. Ignored if not using MPSxMPO. Default value is 4.

  • optim_delta – If using MPSxMPO, stopping criteria for the optimisation when contracting the k layers of MPO. Stops when the increase of fidelity between iterations is smaller than optim_delta. Ignored if not using MPSxMPO. Default value is 1e-5.

  • float_precision – 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 – 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.

  • loglevel – 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].

class pytket.extensions.cutensornet.mps.CuTensorNetHandle(device_id: int | None = None)#

Initialise the cuTensorNet library with automatic workspace memory management.

Note

Always use as with CuTensorNetHandle() as libhandle: so that cuTensorNet handles are automatically destroyed at the end of execution.

handle#

The cuTensorNet library handle created by this initialisation.

Type:

int

device_id#

The ID of the device (GPU) where cuTensorNet is initialised. If not provided, defaults to cp.cuda.Device().

Type:

int

Classes#

class pytket.extensions.cutensornet.mps.MPS#

Represents a state as a Matrix Product State.

tensors#

A list of tensors in the MPS; tensors[0] is the leftmost and tensors[len(self)-1] is the rightmost; tensors[i] and tensors[i+1] are connected in the MPS via a bond. All of the tensors are rank three, with the dimensions listed in .shape matching the left, right and physical bonds, in that order.

Type:

list[Tensor]

canonical_form#

A dictionary mapping positions to the canonical form direction of the corresponding tensor, or None if it the tensor is not canonicalised.

Type:

dict[int, Optional[DirectionMPS]]

qubit_position#

A dictionary mapping circuit qubits to the position its tensor is at in the MPS.

Type:

dict[pytket.circuit.Qubit, int]

fidelity#

A lower bound of the fidelity, obtained by multiplying the fidelities after each contraction. The fidelity of a contraction corresponds to |<psi|phi>|^2 where |psi> and |phi> are the states before and after truncation (assuming both are normalised).

Type:

float

__init__(libhandle: CuTensorNetHandle, qubits: list[pytket.unit_id.Qubit], config: ConfigMPS)#

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 – 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.

apply_gate(gate: Command) MPS#

Apply the gate to the MPS.

Note

Only one-qubit gates and two-qubit gates are supported. Two-qubit gates must act on adjacent qubits.

Parameters:

gate – The gate to be applied.

Returns:

self, to allow for method chaining.

Raises:
  • RuntimeError – If the CuTensorNetHandle is out of scope.

  • RuntimeError – If gate acts on more than 2 qubits or acts on non-adjacent qubits.

  • RuntimeError – If physical bond dimension where gate is applied is not 2.

vdot(other: MPS) complex#

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

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

Note

The state that is conjugated is self.

Parameters:

other – The other MPS to compare against.

Returns:

The resulting complex number.

Raises:
  • RuntimeError – If number of tensors, dimensions or positions do not match.

  • RuntimeError – If there are no tensors in the MPS.

  • RuntimeError – If the CuTensorNetHandle is out of scope.

canonicalise(l_pos: int, r_pos: int) None#

Canonicalises the MPS object.

Applies the necessary gauge transformations so that all MPS tensors to the left of position l_pos are in left orthogonal form and all MPS tensors to the right of r_pos in right orthogonal form.

Parameters:
  • l_pos – The position of the leftmost tensor that is not to be canonicalised.

  • r_pos – The position of the rightmost tensor that is not to be canonicalised.

sample() dict[pytket.unit_id.Qubit, int]#

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

Notes

The MPS self is not updated. This is equivalent to applying mps = self.copy() then mps.measure(mps.get_qubits()).

Returns:

A dictionary mapping each of the qubits in the MPS to their 0 or 1 outcome.

measure(qubits: set[pytket.unit_id.Qubit]) dict[pytket.unit_id.Qubit, int]#

Applies a Z measurement on qubits, updates the MPS and returns outcome.

Notes

After applying this function, self will contain the MPS of the projected state over the non-measured qubits.

The resulting state has been normalised.

Parameters:

qubits – The subset of qubits to be measured.

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 MPS.

postselect(qubit_outcomes: dict[pytket.unit_id.Qubit, int]) float#

Applies a postselection, updates the MPS and returns its probability.

Notes

After applying this function, self will contain the MPS of the projected state over the non-postselected qubits.

The resulting state has been normalised.

Parameters:

qubit_outcomes – A dictionary mapping a subset of qubits in the MPS to their desired outcome value (either 0 or 1).

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 MPS.

  • ValueError – If a value in qubit_outcomes is other than 0 or 1.

  • ValueError – If all of the qubits in the MPS are being postselected. Instead, you may wish to use get_amplitude().

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 MPS.

get_statevector() ndarray#

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

Raises:

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

get_amplitude(state: int) complex#

Returns the amplitude of the chosen computational state.

Notes

The result is equivalent to mps.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 the MPS.

get_qubits() set[pytket.unit_id.Qubit]#

Returns the set of qubits that this MPS is defined on.

get_virtual_dimensions(position: int) tuple[int, int]#

Returns the virtual bonds dimension of the tensor tensors[position].

Parameters:

position – A position in the MPS.

Returns:

A tuple where the first element is the dimensions of the left virtual bond and the second elements is that of the right virtual bond.

Raises:

RuntimeError – If position is out of bounds.

get_physical_dimension(position: int) int#

Returns the physical bond dimension of the tensor tensors[position].

Parameters:

position – A position in the MPS.

Returns:

The dimension of the physical bond.

Raises:

RuntimeError – If position is out of bounds.

get_device_id() int#
Returns:

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

is_valid() bool#

Verify that the MPS object is valid.

Specifically, verify that the MPS does not exceed the dimension limit chi of the virtual bonds, that physical bonds have dimension 2, that all tensors are rank three and that the data structure sizes are consistent.

Returns:

False if a violation was detected or True otherwise.

update_libhandle(libhandle: CuTensorNetHandle) None#

Update the CuTensorNetHandle used by this MPS object. 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 of the MPS are stored.

copy() MPS#
Returns:

A deep copy of the MPS on the same device.

__len__() int#
Returns:

The number of tensors in the MPS.

class pytket.extensions.cutensornet.mps.MPSxGate#

Bases: MPS

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: ConfigMPS)#

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 – 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.

class pytket.extensions.cutensornet.mps.MPSxMPO#

Bases: MPS

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: ConfigMPS)#

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 – 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.

Miscellaneous#

enum pytket.extensions.cutensornet.mps.DirectionMPS(value)#

An enum to refer to relative directions within the MPS.

Valid values are as follows:

LEFT = <DirectionMPS.LEFT: 0>#
RIGHT = <DirectionMPS.RIGHT: 1>#
pytket.extensions.cutensornet.mps.prepare_circuit(circuit: Circuit) tuple[pytket.circuit.Circuit, dict[pytket.unit_id.Qubit, pytket.unit_id.Qubit]]#

Prepares a circuit in a specific, MPS-friendly, manner.

Returns an equivalent circuit with the appropriate structure to be simulated by an MPS algorithm.

Note

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.

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.