Source code for qarp.factories._pauli_block_factory

from typing import Dict, List, Optional, Union

from qarp.operators import FermionOperator, QubitOperator

from ..blocks import PauliBlock
from ..operators import JordanWigner, Mapping
from ._block_factory import BlockFactory


[docs] class PauliBlockFactory(BlockFactory): """A factory class for creating PauliBlock objects from various Hamiltonian representations. This factory supports creation from QubitOperator and FermionOperator objects, with configurable options for coefficients, basis changes, and measurements. """
[docs] def create_blocks( # type: ignore[override] self, hamiltonian: Union[QubitOperator, FermionOperator], n_qubits: Optional[int] = None, include_coefficients: bool = True, change_basis: bool = False, measure: bool = False, mapping: Optional[Mapping] = None, ) -> List[PauliBlock]: """Create blocks from a Hamiltonian operator. Automatically dispatches to the appropriate method based on input type. Args: hamiltonian: OpenFermion QubitOperator or FermionOperator representing the Hamiltonian n_qubits: Total number of qubits/spin orbitals. If None, inferred from the operator include_coefficients: Whether to include the coefficients in the blocks change_basis: Whether to change basis for the Pauli blocks measure: Whether to measure the Pauli blocks mapping: Fermion-to-qubit mapping (only used for FermionOperator) Returns: List of PauliBlock objects Raises: TypeError: If hamiltonian is not a QubitOperator or FermionOperator """ if isinstance(hamiltonian, QubitOperator): return self.from_qubit_operator( hamiltonian, n_qubits, include_coefficients, change_basis, measure ) elif isinstance(hamiltonian, FermionOperator): return self.from_fermion_operator( hamiltonian, n_qubits, mapping or JordanWigner(), include_coefficients, change_basis, measure, ) else: raise TypeError( f"Unsupported hamiltonian type: {type(hamiltonian)}. " "Expected QubitOperator or FermionOperator." )
[docs] @staticmethod def from_qubit_operator( operator: QubitOperator, n_qubits: Optional[int] = None, include_coefficients: bool = True, change_basis: bool = False, measure: bool = False, ) -> List[PauliBlock]: """Create PauliBlock objects from a QubitOperator. Args: operator: OpenFermion QubitOperator representing the Hamiltonian n_qubits: Total number of qubits. If None, inferred from the operator include_coefficients: Whether to include the coefficients in the blocks change_basis: Whether to change basis for the Pauli blocks measure: Whether to measure the Pauli blocks Returns: List of PauliBlock objects, one for each term in the Hamiltonian """ # Infer number of qubits if not provided if n_qubits is None: n_qubits = PauliBlockFactory._infer_n_qubits(operator) blocks = [] for term, coefficient in operator.terms.items(): block = PauliBlockFactory._create_pauli_block( term, coefficient, n_qubits, include_coefficients, change_basis, measure ) blocks.append(block) return blocks
[docs] @staticmethod def from_fermion_operator( operator: FermionOperator, spin_orbitals: Optional[int] = None, mapping: Optional[Mapping] = None, include_coefficients: bool = True, change_basis: bool = False, measure: bool = False, ) -> List[PauliBlock]: """Create PauliBlock objects from a FermionOperator. Requires a fermion-to-qubit mapping (e.g., Jordan-Wigner transform). Args: operator: OpenFermion FermionOperator spin_orbitals: Number of spin orbitals. If None, inferred from the operator mapping: Fermion-to-qubit mapping. Defaults to Jordan-Wigner if None include_coefficients: Whether to include the coefficients in the blocks change_basis: Whether to change basis for the Pauli blocks measure: Whether to measure the Pauli blocks Returns: List of PauliBlock objects """ # Use Jordan-Wigner mapping as default if mapping is None: mapping = JordanWigner() # Transform to qubit operator qubit_op = mapping.encode_operator(operator) return PauliBlockFactory.from_qubit_operator( qubit_op, # type: ignore[arg-type] spin_orbitals, include_coefficients, change_basis, measure, )
[docs] @staticmethod def get_coefficients(blocks: List[PauliBlock]) -> List[complex]: """Extract coefficients from a list of PauliBlocks. Args: blocks: List of PauliBlock objects Returns: List of complex coefficients """ return [block.coefficient for block in blocks] # type: ignore[misc]
[docs] @staticmethod def get_pauli_strings(blocks: List[PauliBlock]) -> List[Dict[int, str]]: """Extract Pauli strings from a list of PauliBlocks. Args: blocks: List of PauliBlock objects Returns: List of dictionaries mapping qubit indices to Pauli operators """ return [block.pauli_string for block in blocks] # type: ignore[misc]
@staticmethod def _infer_n_qubits(hamiltonian: QubitOperator) -> int: """Infer the number of qubits from a QubitOperator. Args: hamiltonian: OpenFermion QubitOperator Returns: Number of qubits required """ max_qubit = -1 for term in hamiltonian.terms: if term: # Skip identity terms term_max = max(qubit for qubit, _ in term) max_qubit = max(max_qubit, term_max) return max_qubit + 1 if max_qubit >= 0 else 1 @staticmethod def _create_pauli_block( term: tuple, coefficient: complex, n_qubits: int, include_coefficients: bool, change_basis: bool, measure: bool, ) -> PauliBlock: """Create a single PauliBlock from a term. Args: term: Tuple representing a Pauli term coefficient: Complex coefficient for the term n_qubits: Total number of qubits include_coefficients: True if coefficients are included. False otherwise change_basis: True if basis is changed. False otherwise measure: True if measured. False otherwise Returns: PauliBlock object """ # Convert OpenFermion term to our format pauli_string = {} if term: # Non-identity term for qubit_idx, pauli_op in term: pauli_string[qubit_idx] = pauli_op coeff = coefficient if include_coefficients else 1.0 name = PauliBlockFactory._generate_block_name(term) return PauliBlock( pauli_string=pauli_string, coefficient=coeff, n_qubits=n_qubits, name=name, change_basis=change_basis, measure=measure, ) @staticmethod def _generate_block_name(term: tuple) -> str: """Generate a descriptive name for a Pauli block. Args: term: Tuple representing a Pauli term Returns: String name for the block """ if term: term_str = " ".join([f"{pauli}{qubit}" for qubit, pauli in term]) else: term_str = "I" return f"Pauli_{term_str}"