Skip to content

PyQTorch

Fast differentiable statevector emulator based on PyTorch. The code is open source, hosted on Github and maintained by Pasqal.

Backend dataclass

Bases: Backend

PyQTorch backend.

convert(circuit, observable=None)

Convert an abstract circuit (and optionally and observable) to their native representation. Additionally this function constructs an embedding function which maps from user-facing parameters to device parameters (read more on parameter embedding here).

Source code in qadence/backend.py
def convert(
    self, circuit: QuantumCircuit, observable: list[AbstractBlock] | AbstractBlock | None = None
) -> Converted:
    """Convert an abstract circuit (and optionally and observable) to their native
    representation. Additionally this function constructs an embedding function which maps from
    user-facing parameters to device parameters (read more on parameter embedding
    [here][qadence.blocks.embedding.embedding]).
    """

    def check_observable(obs_obj: Any) -> AbstractBlock:
        if isinstance(obs_obj, QubitOperator):
            from qadence.blocks.manipulate import from_openfermion

            assert len(obs_obj.terms) > 0, "Make sure to give a non-empty qubit hamiltonian"

            return from_openfermion(obs_obj)

        elif isinstance(obs_obj, (CompositeBlock, PrimitiveBlock, ScaleBlock)):
            from qadence.blocks.utils import block_is_qubit_hamiltonian

            assert block_is_qubit_hamiltonian(
                obs_obj
            ), "Make sure the QubitHamiltonian consists only of Pauli operators X, Y, Z, I"
            return obs_obj
        raise TypeError(
            "qubit_hamiltonian should be a Pauli-like AbstractBlock or a QubitOperator"
        )

    conv_circ = self.circuit(circuit)
    circ_params, circ_embedding_fn = embedding(
        conv_circ.abstract.block, self.config._use_gate_params
    )
    params = circ_params
    if observable is not None:
        observable = observable if isinstance(observable, list) else [observable]
        conv_obs = []
        obs_embedding_fn_list = []

        for obs in observable:
            obs = check_observable(obs)
            c_obs = self.observable(obs, max(circuit.n_qubits, obs.n_qubits))
            obs_params, obs_embedding_fn = embedding(
                c_obs.abstract, self.config._use_gate_params
            )
            params.update(obs_params)
            obs_embedding_fn_list.append(obs_embedding_fn)
            conv_obs.append(c_obs)

        def embedding_fn_dict(a: dict, b: dict) -> dict:
            embedding_dict = circ_embedding_fn(a, b)
            for o in obs_embedding_fn_list:
                embedding_dict.update(o(a, b))
            return embedding_dict

        return Converted(conv_circ, conv_obs, embedding_fn_dict, params)

    def embedding_fn(a: dict, b: dict) -> dict:
        return circ_embedding_fn(a, b)

    return Converted(conv_circ, None, embedding_fn, params)

Configuration dataclass

Bases: BackendConfiguration

interaction: Callable | Interaction | str = Interaction.NN class-attribute instance-attribute

Digital-analog emulation interaction that is used for AnalogBlocks.

loop_expectation: bool = False class-attribute instance-attribute

When computing batches of expectation values, only allocate one wavefunction and loop over the batch of parameters to only allocate a single wavefunction at any given time.

use_gradient_checkpointing: bool = False class-attribute instance-attribute

Use gradient checkpointing. Recommended for higher-order optimization tasks.

use_single_qubit_composition: bool = False class-attribute instance-attribute

Composes chains of single qubit gates into a single matmul if possible.

supported_gates = list(set(OpName.list()) - set([OpName.TDAGGER])) module-attribute

The set of supported gates. Tdagger is currently not supported.

PyQComposedBlock(ops, qubits, n_qubits, config=None)

Bases: Module

Compose a chain of single qubit operations on the same qubit into a single call to _apply_batch_gate.

Source code in qadence/backends/pyqtorch/convert_ops.py
def __init__(
    self,
    ops: list[Module],
    qubits: list[int] | tuple,
    n_qubits: int,
    config: Configuration = None,
):
    """Compose a chain of single qubit operations on the same qubit into a single
    call to _apply_batch_gate."""
    super().__init__()
    self.operations = ops
    self.qubits = qubits
    self.n_qubits = n_qubits

ScalePyQOperation(n_qubits, block, config)

Bases: Module

Computes:

M = matrix(op, theta)
scale * matmul(M, state)
Source code in qadence/backends/pyqtorch/convert_ops.py
def __init__(self, n_qubits: int, block: ScaleBlock, config: Configuration):
    super().__init__()
    (self.param_name,) = config.get_param_name(block)
    if not isinstance(block.block, PrimitiveBlock):
        raise NotImplementedError(
            "The pyqtorch backend can currently only scale `PrimitiveBlock` types.\
            Please use the following transpile function on your circuit first:\
            from qadence.transpile import scale_primitive_blocks_only"
        )
    self.operation = convert_block(block.block, n_qubits, config)[0]

    def _fwd(state: torch.Tensor, values: dict[str, torch.Tensor]) -> torch.Tensor:
        return values[self.param_name] * self.operation(state, values)

    if config.use_gradient_checkpointing:

        def _forward(state: torch.Tensor, values: dict[str, torch.Tensor]) -> torch.Tensor:
            return checkpoint(_fwd, state, values, use_reentrant=False)

    else:

        def _forward(state: torch.Tensor, values: dict[str, torch.Tensor]) -> torch.Tensor:
            return _fwd(state, values)

    self._forward = _forward