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}