# pyquil.simulation.tools module¶

Miscellaneous tools that are helpful for simulation.

pyquil.simulation.tools.all_bitstrings(n_bits: int) ndarray[source]

All bitstrings in lexicographical order as a 2d np.ndarray.

This should be the same as `np.array(list(itertools.product([0,1], repeat=n_bits)))` but faster.

pyquil.simulation.tools.lifted_gate(gate: Gate, n_qubits: int) ndarray[source]

Lift a pyquil `Gate` in a full `n_qubits`-qubit Hilbert space.

This function looks up the matrix form of the gate and then dispatches to `lifted_gate_matrix()` with the target qubits.

Parameters:
• gate – A gate

• n_qubits – The total number of qubits.

Returns:

A 2^n by 2^n lifted version of the gate acting on its specified qubits.

pyquil.simulation.tools.lifted_gate_matrix(matrix: ndarray, qubit_inds: Sequence[int], n_qubits: int) ndarray[source]

Lift a unitary matrix to act on the specified qubits in a full `n_qubits`-qubit Hilbert space.

For 1-qubit gates, this is easy and can be achieved with appropriate kronning of identity matrices. For 2-qubit gates acting on adjacent qubit indices, it is also easy. However, for a multiqubit gate acting on non-adjactent qubit indices, we must first apply a permutation matrix to make the qubits adjacent and then apply the inverse permutation.

Parameters:
• matrix – A 2^k by 2^k matrix encoding an n-qubit operation, where `k == len(qubit_inds)`

• qubit_inds – The qubit indices we wish the matrix to act on.

• n_qubits – The total number of qubits.

Returns:

A 2^n by 2^n lifted version of the unitary matrix acting on the specified qubits.

pyquil.simulation.tools.lifted_pauli(pauli_sum: , qubits: list[int]) ndarray[source]

Return a matrix corresponding to the tensor representation of the given PauliSum and qubits.

Useful for generating the full Hamiltonian after a particular fermion to pauli transformation. For example:

Converting a PauliSum X0Y1 + Y1X0 into the matrix

```[
[0.0 + 0.0j, 0.0 + 0.0j, 0.0 + 0.0j, 0.0 - 2.0j],
[0.0 + 0.0j, 0.0 + 0.0j, 0.0 + 0.0j, 0.0 + 0.0j],
[0.0 + 0.0j, 0.0 + 0.0j, 0.0 + 0.0j, 0.0 + 0.0j],
[0.0 + 2.0j, 0.0 + 0.0j, 0.0 + 0.0j, 0.0 + 0.0j],
]
```

Developer note: Quil and the QVM like qubits to be ordered such that qubit 0 is on the right. Therefore, in `qubit_adjacent_lifted_gate`, `lifted_pauli`, and `lifted_state_operator`, we build up the lifted matrix by performing the kronecker product from right to left.

Parameters:
• pauli_sum – Pauli representation of an operator

• qubits – list of qubits in the order they will be represented in the resultant matrix.

Returns:

matrix representation of the pauli_sum operator

pyquil.simulation.tools.lifted_state_operator(state: TensorProductState, qubits: list[int]) ndarray[source]

Return a matrix corresponding to the tensored-up representation of the given state and qubits.

Developer note: Quil and the QVM like qubits to be ordered such that qubit 0 is on the right. Therefore, in `qubit_adjacent_lifted_gate`, `lifted_pauli`, and `lifted_state_operator`, we build up the lifted matrix by using the left kronecker product.

Parameters:
• state – The state

• qubits – list of qubits in the order they will be represented in the resultant matrix.

pyquil.simulation.tools.permutation_arbitrary(qubit_inds: Sequence[int], n_qubits: int) tuple[ndarray, ndarray, int][source]

Generate the permutation matrix that permutes an arbitrary number of single-particle Hilbert spaces into adjacent positions.

Transposes the qubit indices in the order they are passed to a contiguous region in the complete Hilbert space, in increasing qubit index order (preserving the order they are passed in).

Gates are usually defined as GATE 0 1 2, with such an argument ordering dictating the layout of the matrix corresponding to GATE. If such an instruction is given, actual qubits (0, 1, 2) need to be swapped into the positions (2, 1, 0), because the lifting operation taking the 8 x 8 matrix of GATE is done in the little-endian (reverse) addressed qubit space.

For example, suppose I have a Quil command CCNOT 20 15 10. The median of the qubit indices is 15 - hence, we permute qubits [20, 15, 10] into the final map [16, 15, 14] to minimize the number of swaps needed, and so we can directly operate with the final CCNOT, when lifted from indices [16, 15, 14] to the complete Hilbert space.

Notes: assumes qubit indices are unique (assured in parent call).

See documentation for further details and explanation.

Done in preparation for arbitrary gate application on adjacent qubits.

Parameters:
• qubit_inds – Qubit indices in the order the gate is applied to.

• n_qubits – Number of qubits in system

Returns:

perm - permutation matrix providing the desired qubit reordering qubit_arr - new indexing of qubits presented in left to right decreasing index order. start_i - starting index to lift gate from

pyquil.simulation.tools.program_unitary(program: Program, n_qubits: int) ndarray[source]

Return the unitary of a pyQuil program.

Parameters:

program – A program consisting only of `Gate`.:

Returns:

a unitary corresponding to the composition of the program’s gates.

pyquil.simulation.tools.qubit_adjacent_lifted_gate(i: int, matrix: ndarray, n_qubits: int) ndarray[source]

Lift k-qubit gate on adjacent qubits from qubit i to complete Hilbert space of dimension 2 ** num_qubits.

Ex: 1-qubit gate, lifts from qubit i Ex: 2-qubit gate, lifts from qubits (i+1, i) Ex: 3-qubit gate, lifts from qubits (i+2, i+1, i), operating in that order

In general, this takes a k-qubit gate (2D matrix 2^k x 2^k) and lifts it to the complete Hilbert space of dim 2^num_qubits, as defined by the right-to-left tensor product (1) in arXiv:1608.03355.

Developer note: Quil and the QVM like qubits to be ordered such that qubit 0 is on the right. Therefore, in `qubit_adjacent_lifted_gate`, `lifted_pauli`, and `lifted_state_operator`, we build up the lifted matrix by performing the kronecker product from right to left.

Note that while the qubits are addressed in decreasing order, starting with num_qubit - 1 on the left and ending with qubit 0 on the right (in a little-endian fashion), gates are still lifted to apply on qubits in increasing index (right-to-left) order.

Parameters:
• i – starting qubit to lift matrix from (incr. index order)

• matrix – the matrix to be lifted

• n_qubits – number of overall qubits present in space

Returns:

matrix representation of operator acting on the complete Hilbert space of all num_qubits.

pyquil.simulation.tools.scale_out_phase(unitary1: ndarray, unitary2: ndarray) ndarray[source]

Return a matrix m equal to unitary1/θ where ɑ satisfies unitary2 = e^(iθ)·unitary1.

Parameters:
• unitary1 – The unitary matrix from which the constant of proportionality should be scaled-out.

• unitary2 – The reference matrix.

Returns:

A matrix (same shape as the input matrices) with the constant of proportionality scaled-out.

pyquil.simulation.tools.tensor_up(pauli_sum: , qubits: list[int]) ndarray[source]

Return a matrix corresponding to the tensor representation of the given PauliSum and qubits.

This is the same as `lifted_pauli()`. Nick R originally wrote this functionality and really likes the name `tensor_up`. Who can blame him?

Parameters:
• pauli_sum – Pauli representation of an operator

• qubits – list of qubits in the order they will be represented in the resultant matrix.

Returns:

matrix representation of the pauli_sum operator

pyquil.simulation.tools.two_swap_helper(j: int, k: int, num_qubits: int, qubit_map: ndarray) tuple[ndarray, ndarray][source]

Generate the permutation matrix that permutes two single-particle Hilbert spaces into adjacent positions.

ALWAYS swaps j TO k. Recall that Hilbert spaces are ordered in decreasing qubit index order. Hence, j > k implies that j is to the left of k.

End results:

j == k: nothing happens j > k: Swap j right to k, until j at ind (k) and k at ind (k+1). j < k: Swap j left to k, until j at ind (k) and k at ind (k-1).

Done in preparation for arbitrary 2-qubit gate application on ADJACENT qubits.

Parameters:
• j – starting qubit index

• k – ending qubit index

• num_qubits – number of qubits in Hilbert space

• qubit_map – current index mapping of qubits

Returns:

tuple of swap matrix for the specified permutation, and the new qubit_map, after permutation is made

pyquil.simulation.tools.unitary_equal(A: ndarray, B: ndarray) bool[source]

Check if two matrices are unitarily equal.