Getting started#

BGLS is a Python package that implements the Bravyi, Gosset, and Liu Sampling algorithm presented in How to simulate quantum measurement without computing marginals (Phys. Rev. Lett.) (arXiv).

Quick start#

Install BGLS via pip install bgls. The following example shows how to use the package.

"""Setup."""
import cirq

import bgls
"""Define a circuit."""
nqubits = 2
qubits = cirq.LineQubit.range(nqubits)

circuit = cirq.Circuit(
    cirq.H.on(qubits[0]),
    cirq.CNOT.on(qubits[0], qubits[1]),
    cirq.measure(*qubits, key="z")
)
circuit
0: ───H───@───M('z')───
          │   │
1: ───────X───M────────
"""Use BGLS to simulate the circuit."""
simulator = bgls.Simulator(
    initial_state=cirq.StateVectorSimulationState(qubits=qubits, initial_state=0),
    apply_op=cirq.protocols.act_on,
    compute_probability=bgls.born.compute_probability_state_vector,
)
results = simulator.run(circuit, repetitions=10)

cirq.plot_state_histogram(results);
_images/d65be1c6d445954deebc7e313a03193c7f43f92fdd62a026ea8dd970576fecd3.png

More detail: How to create a bgls.Simulator#

Notice from the above example that there are three ingredients needed to create a bgls.Simulator:

  1. initial_state: The initial quantum state (wavefunction) of the circuit, including what type the wavefunction is (state vector, density matrix, tensor network, etc.).

  2. apply_op: A function for applying operations to the initial (and intermediate) states.

  3. compute_probability: A function for calculating the probability of sampling a bitstring from the input state type.

Note: A function to compute marginal distributions, which is used in the typical “qubit-by-qubit” sampling algorithm, is not needed. This is the primary purpose of the BGLS “gate-by-gate” sampling algorithm: when it is easier to compute probabilities than it is to compute marginal distributions, the BGLS algorithm is advantageous to use. See more in How BGLS works and When to use BGLS.

Any wavefunction type can be used with BGLS. In the example above, we used a cirq.StateVectorSimulationState which represents the wavefunction as a state vector.

"""Provide a representation of the (initial) wavefunction, here a cirq.StateVectorSimulationState."""
wavefunction = cirq.StateVectorSimulationState(qubits=qubits, initial_state=0)

# Visualize the wavefunction.
wavefunction.target_tensor.reshape(2 ** nqubits)
array([1.+0.j, 0.+0.j, 0.+0.j, 0.+0.j], dtype=complex64)

Once the state is provided, BGLS needs to know how to apply operations to the state. Here, we can do this with cirq.protocols.act_on.

"""Define a function for applying operations to the state."""
from typing import Any


def apply_op(operation: cirq.GateOperation, state: cirq.StateVectorSimulationState) -> None:
    cirq.protocols.act_on(operation, state)


# Example: Apply operation and visualize the updated wavefunction.
apply_op(cirq.H.on(qubits[0]), wavefunction)
wavefunction.target_tensor.reshape(2 ** nqubits).round(3)
array([ 0.707+0.j,  0.   +0.j,  0.707+0.j, -0.   +0.j], dtype=complex64)

Note: The apply_op function must modify the state in-place as above.

Last, BGLS needs to know how to compute the probability of sampling a bitstring z from the wavefunction \(|\psi\rangle\). Here this is given by \(| \langle z | \psi \rangle | ^ 2\) and can be computed via the following function.

"""Define a function for computing the probability of sampling a bitstring."""
import numpy as np


def probability_of_bitstring(
    wavefunction: cirq.StateVectorSimulationState,
    bitstring: str,
) -> float:
    return np.abs(wavefunction.target_tensor.reshape(2 ** nqubits)[int(bitstring, 2)]) ** 2


# Example: Calculating some p(z) for some bitstrings z.
for bitstring in {"00", "10"}:
    print(f"Probability of sampling {bitstring} is {probability_of_bitstring(wavefunction, bitstring):.2f}")
Probability of sampling 10 is 0.50
Probability of sampling 00 is 0.50

Note: This function is identical to bgls.born.compute_probability_state_vector used in the Quick start example.

With these three ingredients you can create a bgls.Simulator and execute circuits with bgls.Simulator.run.

simulator = bgls.Simulator(wavefunction, apply_op, probability_of_bitstring)

simulator.run(circuit)
z=0, 0

When to use the bgls.Simulator#

Other than introducing the bgls.Simulator, there’s (probably) no good reason to use BGLS with a statevector simulator as above: the samples can be drawn from the final state just as easily. The power of the bgls.Simulator comes in situations where it is easier to compute probabilities than compute marginal distributions - see When to use BGLS for more discussion and examples.