Skip to main content

ket/ir/
hamiltonian.rs

1// SPDX-FileCopyrightText: 2024 Evandro Chagas Ribeiro da Rosa <evandro@quantuloop.com>
2//
3// SPDX-License-Identifier: Apache-2.0
4
5//! Hamiltonian operator representation for expectation-value computations.
6//!
7//! A [`Hamiltonian`] is a sum of weighted Pauli strings. Each Pauli string is a
8//! tensor product of single-qubit Pauli operators ([`Pauli`]) indexed by a
9//! logical qubit. The expectation value `⟨ψ|H|ψ⟩` of the Hamiltonian with
10//! respect to a quantum state `|ψ⟩` is computed by the execution backend.
11
12use std::collections::BTreeMap;
13
14use itertools::Itertools;
15use serde::{Deserialize, Serialize};
16
17/// A single-qubit Pauli operator.
18#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
19pub enum Pauli {
20    /// The Pauli-X operator (bit-flip).
21    PauliX,
22    /// The Pauli-Y operator.
23    PauliY,
24    /// The Pauli-Z operator (phase-flip).
25    PauliZ,
26}
27
28/// A single-qubit Pauli operator applied to a specific logical qubit.
29#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
30pub struct PauliTerm {
31    /// The Pauli operator to apply.
32    pub pauli: Pauli,
33    /// The logical qubit index the operator acts on.
34    pub qubit: usize,
35}
36
37impl PauliTerm {
38    /// Returns a copy of this term with the qubit index remapped through `mapping`.
39    ///
40    /// `mapping` must contain an entry for `self.qubit`; the method panics
41    /// otherwise (this is always satisfied after qubit allocation).
42    #[must_use]
43    pub fn map(&self, mapping: &BTreeMap<usize, usize>) -> Self {
44        Self {
45            pauli: self.pauli,
46            qubit: mapping[&self.qubit],
47        }
48    }
49}
50
51/// An ordered list of [`PauliTerm`]s forming a tensor-product Pauli string.
52pub type PauliString = Vec<PauliTerm>;
53
54/// A Hamiltonian operator expressed as a weighted sum of Pauli strings.
55///
56/// ```text
57/// H = Σᵢ coefficients[i] · pauli_strings[i]
58/// ```
59///
60/// Hamiltonians are built incrementally with [`Hamiltonian::add_pauli_string`]
61/// and remapped to physical qubits with [`Hamiltonian::map`].
62#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
63pub struct Hamiltonian {
64    /// The list of Pauli strings that make up this Hamiltonian.
65    pub pauli_strings: Vec<PauliString>,
66    /// Real-valued coefficients, one per Pauli string.
67    pub coefficients: Vec<f64>,
68}
69
70impl Hamiltonian {
71    /// Creates a new, empty Hamiltonian.
72    #[must_use]
73    pub fn new() -> Self {
74        Self::default()
75    }
76
77    /// Appends a weighted Pauli string to this Hamiltonian.
78    pub fn add_pauli_string(&mut self, pauli_string: PauliString, coefficient: f64) {
79        self.pauli_strings.push(pauli_string);
80        self.coefficients.push(coefficient);
81    }
82
83    /// Returns a copy of this Hamiltonian with all qubit indices remapped
84    /// through `mapping` (logical → physical qubit translation).
85    #[must_use]
86    pub fn map(&self, mapping: &BTreeMap<usize, usize>) -> Self {
87        Self {
88            pauli_strings: self
89                .pauli_strings
90                .iter()
91                .map(|pauli_string| {
92                    pauli_string
93                        .iter()
94                        .map(|term| term.map(mapping))
95                        .collect_vec()
96                })
97                .collect_vec(),
98            coefficients: self.coefficients.clone(),
99        }
100    }
101}