Skip to content

pyqtorch exposes three API endpoints called run, sample and expectation. Please note that all endpoints expect a QuantumCircuit object.

run

Sequentially apply each operation in circuit to an input state state given parameter values values, perform an optional embedding on values and return an output state.

Parameters:

Name Type Description Default
circuit QuantumCircuit

A pyqtorch.QuantumCircuit instance.

required
state Tensor

A torch.Tensor of shape [2, 2, ..., batch_size].

None
values dict[str, Tensor] | None

A dictionary containing <'parameter_name': torch.Tensor> pairs denoting the current parameter values for each parameter in circuit.

None
embedding Embedding | None

An optional instance of Embedding.

None

Returns:

Type Description
Tensor

A torch.Tensor of shape [2, 2, ..., batch_size].

Example:

from torch import rand
from pyqtorch import QuantumCircuit, RY, random_state, run

n_qubits = 2
circ = QuantumCircuit(n_qubits, [RY(0, 'theta')])
state = random_state(n_qubits)
run(circ, state, {'theta': rand(1)})
Source code in pyqtorch/api.py
def run(
    circuit: QuantumCircuit,
    state: Tensor = None,
    values: dict[str, Tensor] | None = None,
    embedding: Embedding | None = None,
) -> Tensor:
    """Sequentially apply each operation in `circuit` to an input state `state`
    given parameter values `values`, perform an optional `embedding` on `values`
    and return an output state.

    Arguments:
        circuit: A pyqtorch.QuantumCircuit instance.
        state: A torch.Tensor of shape [2, 2, ..., batch_size].
        values: A dictionary containing <'parameter_name': torch.Tensor> pairs denoting
                the current parameter values for each parameter in `circuit`.
        embedding: An optional instance of `Embedding`.

    Returns:
         A torch.Tensor of shape [2, 2, ..., batch_size].

    Example:

    ```python exec="on" source="material-block" html="1"
    from torch import rand
    from pyqtorch import QuantumCircuit, RY, random_state, run

    n_qubits = 2
    circ = QuantumCircuit(n_qubits, [RY(0, 'theta')])
    state = random_state(n_qubits)
    run(circ, state, {'theta': rand(1)})
    ```
    """
    values = values or dict()
    logger.debug(f"Running circuit {circuit} on state {state} and values {values}.")
    return circuit.run(state=state, values=values, embedding=embedding)

sample

Sample from circuit given an input state state given current parameter values values, perform an optional embedding on values and return a list Counter objects mapping from .

Parameters:

Name Type Description Default
circuit QuantumCircuit

A pyqtorch.QuantumCircuit instance.

required
state Tensor

A torch.Tensor of shape [2, 2, ..., batch_size].

None
values dict[str, Tensor] | None

A dictionary containing <'parameter_name': torch.Tensor> pairs denoting the current parameter values for each parameter in circuit.

None
n_shots int

A positive int denoting the number of requested samples.

1000
embedding Embedding | None

An optional instance of Embedding.

None

Returns:

Type Description
list[Counter]

A list of Counter objects containing pairs.

Example:

from torch import rand
from pyqtorch import random_state, sample, QuantumCircuit, RY

n_qubits = 2
circ = QuantumCircuit(n_qubits, [RY(0, 'theta')])
state = random_state(n_qubits)
sample(circ, state, {'theta': rand(1)}, n_shots=1000)[0]
Source code in pyqtorch/api.py
def sample(
    circuit: QuantumCircuit,
    state: Tensor = None,
    values: dict[str, Tensor] | None = None,
    n_shots: int = 1000,
    embedding: Embedding | None = None,
) -> list[Counter]:
    """Sample from `circuit` given an input state `state` given
    current parameter values `values`, perform an optional `embedding`
    on `values` and return a list Counter objects mapping from
    <bitstring: num_samples>.

    Arguments:
        circuit: A pyqtorch.QuantumCircuit instance.
        state: A torch.Tensor of shape [2, 2, ..., batch_size].
        values: A dictionary containing <'parameter_name': torch.Tensor> pairs
                denoting the current parameter values for each parameter in `circuit`.
        n_shots: A positive int denoting the number of requested samples.
        embedding: An optional instance of `Embedding`.

    Returns:
         A list of Counter objects containing <bitstring: num_samples> pairs.

    Example:

    ```python exec="on" source="material-block" html="1"
    from torch import rand
    from pyqtorch import random_state, sample, QuantumCircuit, RY

    n_qubits = 2
    circ = QuantumCircuit(n_qubits, [RY(0, 'theta')])
    state = random_state(n_qubits)
    sample(circ, state, {'theta': rand(1)}, n_shots=1000)[0]
    ```
    """
    values = values or dict()
    logger.debug(
        f"Sampling circuit {circuit} on state {state} and values {values} with n_shots {n_shots}."
    )
    return circuit.sample(
        state=state, values=values, n_shots=n_shots, embedding=embedding
    )

expectation

Compute the expectation value of circuit given a state, parameter values values and an observable and optionally compute gradients using diff_mode.

Parameters:

Name Type Description Default
circuit QuantumCircuit

A pyqtorch.QuantumCircuit instance.

required
state Tensor

A torch.Tensor of shape [2, 2, ..., batch_size].

None
values dict[str, Tensor] | dict[str, dict[str, Tensor]] | None

A dictionary containing <'parameter_name': torch.Tensor> pairs denoting the current parameter values for each parameter in circuit. Note it can include also values for the observables, but differentiation will not separate gradients. To do so, we should provide values as a dict of dict[str, Tensor] with two keys: circuit and observables.

None
observable Observable

A pyq.Observable instance.

None
diff_mode DiffMode

The differentiation mode.

AD
n_shots int | None

Number of shots for estimating expectation values. Only used with DiffMode.GPSR or DiffMode.AD.

None
embedding Embedding | None

An optional instance of Embedding.

None

Returns:

Type Description
Tensor

An expectation value.

Example:

from torch import pi, ones_like, tensor
from pyqtorch import random_state, RY, expectation, DiffMode, Observable, Add, Z, QuantumCircuit
from torch.autograd import grad

n_qubits = 2
circ = QuantumCircuit(n_qubits, [RY(0, 'theta')])
state = random_state(n_qubits)
theta = tensor(pi, requires_grad=True)
observable = Observable(Add([Z(i) for i in range(n_qubits)]))
expval = expectation(circ, state, {'theta': theta}, observable, diff_mode = DiffMode.ADJOINT)
dfdtheta= grad(expval, theta, ones_like(expval))[0]
Source code in pyqtorch/api.py
def expectation(
    circuit: QuantumCircuit,
    state: Tensor = None,
    values: dict[str, Tensor] | dict[str, dict[str, Tensor]] | None = None,
    observable: Observable = None,  # type: ignore[assignment]
    diff_mode: DiffMode = DiffMode.AD,
    n_shots: int | None = None,
    embedding: Embedding | None = None,
) -> Tensor:
    """Compute the expectation value of `circuit` given a `state`,
    parameter values `values` and an `observable`
    and optionally compute gradients using diff_mode.

    Arguments:
        circuit: A pyqtorch.QuantumCircuit instance.
        state: A torch.Tensor of shape [2, 2, ..., batch_size].
        values: A dictionary containing <'parameter_name': torch.Tensor> pairs
                denoting the current parameter values for each parameter in `circuit`.
                Note it can include also values for the observables, but differentiation will
                not separate gradients.
                To do so, we should provide values as a dict of dict[str, Tensor]
                with two keys: `circuit` and `observables`.
        observable: A pyq.Observable instance.
        diff_mode: The differentiation mode.
        n_shots: Number of shots for estimating expectation values.
                    Only used with DiffMode.GPSR or DiffMode.AD.
        embedding: An optional instance of `Embedding`.

    Returns:
        An expectation value.

    Example:

    ```python exec="on" source="material-block" html="1"
    from torch import pi, ones_like, tensor
    from pyqtorch import random_state, RY, expectation, DiffMode, Observable, Add, Z, QuantumCircuit
    from torch.autograd import grad

    n_qubits = 2
    circ = QuantumCircuit(n_qubits, [RY(0, 'theta')])
    state = random_state(n_qubits)
    theta = tensor(pi, requires_grad=True)
    observable = Observable(Add([Z(i) for i in range(n_qubits)]))
    expval = expectation(circ, state, {'theta': theta}, observable, diff_mode = DiffMode.ADJOINT)
    dfdtheta= grad(expval, theta, ones_like(expval))[0]
    ```
    """
    values = values or dict()

    if embedding is not None and diff_mode != DiffMode.AD:
        raise NotImplementedError("Only diff_mode AD supports embedding")
    logger.debug(
        f"Computing expectation of circuit {circuit} on state {state}, values {values},\
          given observable {observable} and diff_mode {diff_mode}."
    )
    if observable is None:
        logger.error("Please provide an observable to compute expectation.")

    if state is None:
        state = circuit.init_state(batch_size=1)

    expectation_fn = analytical_expectation
    if n_shots is not None:
        if isinstance(n_shots, int) and n_shots > 0:
            expectation_fn = partial(sampled_expectation, n_shots=n_shots)
        else:
            logger.error("Please provide a 'n_shots' in options of type 'int'.")

    if diff_mode == DiffMode.AD:
        return expectation_fn(
            circuit,
            state,
            observable,
            values,
            embedding,
        )
    elif diff_mode == DiffMode.ADJOINT:
        return AdjointExpectation.apply(
            circuit,
            state,
            observable,
            embedding,
            values.keys(),
            *values.values(),
        )
    elif diff_mode == DiffMode.GPSR:
        check_support_psr(circuit)
        return PSRExpectation.apply(
            circuit,
            state,
            observable,
            embedding,
            expectation_fn,
            values.keys(),
            *values.values(),
        )
    else:
        logger.error(f"Requested diff_mode '{diff_mode}' not supported.")