.. _mappers_explanation: Map operator representations ============================ **Mappers** are routines that transform operators from one representation to another while preserving mathematical equivalence. Use them to work flexibly across different operator algebras and to prepare operators for quantum circuit execution. Implement custom mappers ------------------------ The mappers module makes it straightforward to implement custom mappings. Mappers work by transforming the fundamental actions (creation, annihilation, Majorana, and so on) that compose an operator, then combining these transformed actions according to the target representation's rules. As a concrete example, consider a Jordan-Wigner transformation that maps a :class:`.MajoranaOperator` to a qubit operator using :func:`.map_majorana_action_generators`: .. tab-set-code:: .. code-block:: python >>> from qiskit.quantum_info import SparsePauliOp >>> from qiskit_fermions.mappers import map_majorana_action_generators >>> from qiskit_fermions.operators import MajoranaOperator >>> >>> # Create a Majorana operator >>> maj_op = MajoranaOperator.from_dict({(0, 1): 1.0, (2,): 0.5}) >>> >>> # Define how each Majorana action maps to Pauli strings >>> num_qubits = 2 >>> def map_action(mode: int) -> SparsePauliOp: ... idx = mode // 2 ... qubits = list(range(idx + 1)) ... pauli = "Y" if mode % 2 else "X" ... return SparsePauliOp.from_sparse_list( ... [("Z" * idx + pauli, qubits, 1.0)], ... num_qubits=num_qubits, ... ) >>> >>> # Apply the mapping to transform the operator >>> sparse_pauli_op = map_majorana_action_generators( ... maj_op, ... map_action, ... identity=lambda: SparsePauliOp.from_sparse_list([("", [], 1)], num_qubits=num_qubits), ... ) >>> print(sparse_pauli_op.sort()) SparsePauliOp(['II', 'IZ', 'XZ'], coeffs=[0. +0.j, 0. +1.j, 0.5+0.j]) .. code-block:: c // The C API provides direct mapping functions for common transformations. // Custom mapper prototyping as shown in Python is primarily a Python feature. // For custom mappings in C, you will need to perform the iteration and // conversion logic entirely by yourself. The key steps to implement any custom mapper are: 1. **Define action transformation**: Specify how each action (indexed by mode) maps to the target representation. In the example above, Majorana actions map to Pauli strings according to Jordan-Wigner rules. 2. **Use one of the provided mapping functions**: The :mod:`~qiskit_fermions.mappers` module provides utility functions to handle iterating over the operator terms and subsequent applications of the custom action map for the operator representations provided by the :mod:`~qiskit_fermions.operators` module. For example, :func:`.map_majorana_action_generators` works with Majorana operators. 3. **Get the result**: The mapping function returns an operator in the target representation with the transformation applied consistently across all terms. This design makes it easy to prototype custom mappings without deep knowledge of internal operator structures. The transformation is applied automatically to every term in the operator while preserving mathematical equivalence. .. hint:: You can also iterate the terms of an operator manually rather than using one of the provided iterator functions. See the section on `term iteration `_ in the operators guide for details. Library implementations ----------------------- The :mod:`qiskit_fermions.mappers.library` module provides efficient, thoroughly tested implementations of common mappings. These follow the same underlying pattern as custom mappers but are optimized for production use and are available in both the Python and C APIs. The example below applies a library mapper to the same :class:`.MajoranaOperator` from the custom mapper section, demonstrating equivalent results: .. tab-set-code:: .. code-block:: python >>> from qiskit_fermions.mappers.library import majorana_to_fermion, jordan_wigner >>> >>> # Convert MajoranaOperator to FermionOperator using library mapper >>> ferm_op = majorana_to_fermion(maj_op) >>> >>> # Apply Jordan-Wigner mapper to get qubit operator >>> sparse_observable = jordan_wigner(ferm_op, num_qubits=2) >>> print(SparsePauliOp.from_sparse_observable(sparse_observable).equiv(sparse_pauli_op)) True .. code-block:: c #include // Create a MajoranaOperator with the same terms as maj_op above uint64_t num_terms = 2; uint64_t num_modes = 3; uint32_t modes[3] = {0, 1, 2}; QkComplex67 coeffs[2] = {{1.0, 0.0}, {0.5, 0.0}}; uint32_t boundaries[3] = {0, 2, 3}; QfMajoranaOperator *maj_op = qf_maj_op_new(num_terms, num_modes, coeffs, modes, boundaries); // Convert MajoranaOperator to FermionOperator using library mapper QfFermionOperator *ferm_op = qf_maj_op_to_ferm_op(maj_op); // Apply Jordan-Wigner mapper to get qubit operator QfSparseObservable *qubit_op = qf_ferm_op_jordan_wigner(ferm_op, 2); // Clean up qf_ferm_op_free(ferm_op); qf_maj_op_free(maj_op); qf_sparse_obs_free(qubit_op); In the Python API, some of these library mappers are also reachable through type-agnostic convenience mappers. :func:`.fermion_operator` and :func:`.majorana_operator` sit in front of :func:`.majorana_to_fermion` and :func:`.fermion_to_majorana` (among others), delegating to the appropriate library implementation internally based on the type of object they are given, rather than requiring you to pick the right function yourself. See :mod:`qiskit_fermions.protocols` for how this dispatch is implemented internally.