Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add apply_layout to other operators #11824

Open
chriseclectic opened this issue Feb 16, 2024 · 2 comments
Open

Add apply_layout to other operators #11824

chriseclectic opened this issue Feb 16, 2024 · 2 comments
Labels
mod: primitives Related to the Primitives module mod: quantum info Related to the Quantum Info module (States & Operators) type: feature request New feature or request
Milestone

Comments

@chriseclectic
Copy link
Member

chriseclectic commented Feb 16, 2024

What should we add?

apply_layout was added to SparsePauliOp at some point, but not to other operators (eg Pauli).

This method could in theory be added to BaseOperator to apply to any operator (caveat, it requires ScalarOp which is a subclass of BaseOperator, and you could easily blow up your computers memory if you try and apply a large qubit layout to a channel op or Operator).

This function generalizes the current function to work for any n-qubit BaseOperator subclass:

from typing import List
from qiskit.quantum_info.operators.base_operator import BaseOperator
from qiskit.quantum_info.operators import ScalarOp
from qiskit.transpiler import TranspileLayout


def apply_layout(
    operator: BaseOperator, layout: TranspileLayout | List[int] | None, num_qubits: int | None = None
) -> BaseOperator:
    """Apply a transpiler layout to this operator.

    Args:
        layout: Either a :class:`~.TranspileLayout`, a list of integers or None.
                If both layout and num_qubits are none, a copy of the operator is
                returned.
        num_qubits: The number of subsystems to expand the operator to. If not
            provided then if ``layout`` is a :class:`~.TranspileLayout` the
            number of the transpiler output circuit qubits will be used by
            default. If ``layout`` is a list of integers the permutation
            specified will be applied without any expansion. If layout is
            None, the operator will be expanded to the given number of qubits.

    Returns:
        A new operator with the provided layout applied
    """
    from qiskit.transpiler.layout import TranspileLayout

    if layout is None and num_qargs is None:
        return self.copy()
    
    if operator.num_qubits is None:
        raise QiskitError("Cannot apply layout to an operator with non-qubit dimensions")

    if layout is None:
        n_qubits = operator.num_qubits
        layout = list(range(n_qubits))
    elif isinstance(layout, TranspileLayout):
        n_qubits = len(layout._output_qubit_list)
        layout = layout.final_index_layout()
        if layout is not None and any(x >= n_qubits for x in layout):
            raise QiskitError("Provided layout contains indicies outside the number of qubits.")
  
    if num_qubits is not None:
        if num_qubits < n_qubits:
            raise QiskitError(
                f"The input num_qubits is too small, a {num_qubits} qubit layout cannot be "
                f"applied to a {n_qubits} qubit operator"
            )
        n_qubits = num_qubits
    new_op = ScalarOp(n_qubits * (2,))
    return new_op.compose(operator, qargs=layout)
@jakelishman
Copy link
Member

For the current definition of the V2 primitives, the only Qiskit-defined class other than SparsePauliOp that can be given as an observable is Pauli, which we could add apply_layout to. That doesn't affect the free-form inputs that BaseEstimatorV2 is also required to accept - str and dict, but for those, we either have a loose function (which presumably would need to live in qiskit.primitives, since it's a primitives-specific interpretation of standard Python classes), or more naturally we'd put it on the primitives container classes that these get coerced to, except that those aren't public, and also I don't think are actually valid as user inputs to the primitives anyway.

Fwiw, I feel like apply_layout is a (quite possibly necessary) hack around a more preferential input format which would have specified the qubits the entire ObservableArray was supposed to act on separately to the observables themselves, like how Qiskit Instruction instances don't contain the specific qubits (and aren't full-width on the circuit), but their context object (the CircuitInstruction, in this analogy) contains them.

I'm totally fine to add Pauli.apply_layout to ease this in the short term, and it does feel weird that we have SparsePauliOp.apply_layout but not Pauli.apply_layout. I'm somewhat against adding BaseOperator.apply_layout, because we don't really have a use for that, and adding an extra way to make it super easy to request a universe-ending amount of memory is probably something we'd be better keeping away from users who don't necessarily understand what Operator is if it's not needed.

@jakelishman
Copy link
Member

Since #12066 merged, we've got apply_layout on everything that's a possible input to the EstimatorV2 primitive, I think, which should bridge the gap before we know what a complete solution to #11825 looks like end-to-end and the timescale of that.

For this issue, I guess the question is: is it worth putting method on the base class to fill it in for all the remaining classes, which at this point are mostly (maybe only?) things that can't possibly represent the expanded operator for any layout that they're likely to be given? It potentially feels safer to me not to offer the API opportunity to make the mistake, and to argue that apply_layout only really makes sense for things that have some degree of sparsity to them (which even Pauli and SparsePauliOp don't entirely).

@mtreinish mtreinish added the mod: primitives Related to the Primitives module label Apr 19, 2024
@ShellyGarion ShellyGarion added the mod: quantum info Related to the Quantum Info module (States & Operators) label May 3, 2024
@mtreinish mtreinish modified the milestones: 1.2.0, 1.3.0 Aug 1, 2024
@raynelfss raynelfss modified the milestones: 1.3.0, 2.0.0 Oct 29, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
mod: primitives Related to the Primitives module mod: quantum info Related to the Quantum Info module (States & Operators) type: feature request New feature or request
Projects
None yet
Development

No branches or pull requests

7 participants