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] | None

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

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] | 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`.
        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.")