use crate::qis::pauli::PauliString;
use crate::qis::{Phase, QisError};
use ndarray::Array2;
use num_complex::Complex64;
use std::fmt;
use std::ops::Add;
#[derive(Debug, Clone, PartialEq)]
pub struct Hamiltonian {
pub num_qubits: usize,
pub terms: Vec<(PauliString, Complex64)>,
}
impl fmt::Display for Hamiltonian {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for (i, (op, coeff)) in self.terms.iter().enumerate() {
if i > 0 {
write!(f, " + ")?;
}
write!(f, "({}) * {}", coeff, op)?;
}
Ok(())
}
}
impl Hamiltonian {
pub fn new(num_qubits: usize) -> Self {
Self {
num_qubits,
terms: Vec::new(),
}
}
pub fn from_pauli(pauli: PauliString) -> Self {
pauli.into()
}
pub fn to_matrix(&self) -> Result<Array2<Complex64>, QisError> {
for (pauli, _) in &self.terms {
if pauli.num_qubits != self.num_qubits {
return Err(QisError::QubitMismatch {
expected: self.num_qubits,
actual: pauli.num_qubits,
});
}
}
let dim = 1usize << self.num_qubits;
let mut matrix = Array2::from_elem((dim, dim), Complex64::new(0.0, 0.0));
for (pauli, coefficient) in &self.terms {
let term = pauli.to_matrix().mapv(|value| value * *coefficient);
matrix += &term;
}
Ok(matrix)
}
pub fn from_list(
ops: Vec<(PauliString, Complex64)>,
) -> Result<Self, crate::qis::error::QisError> {
Self::try_from(ops)
}
}
impl From<PauliString> for Hamiltonian {
fn from(pauli: PauliString) -> Self {
let num_qubits = pauli.num_qubits;
Self {
num_qubits,
terms: vec![(pauli, Complex64::new(1.0, 0.0))],
}
}
}
impl TryFrom<Vec<(PauliString, Complex64)>> for Hamiltonian {
type Error = QisError;
fn try_from(ops: Vec<(PauliString, Complex64)>) -> Result<Self, Self::Error> {
if ops.is_empty() {
return Ok(Self::new(0));
}
let n = ops[0].0.num_qubits;
for (op, _) in &ops {
if op.num_qubits != n {
return Err(crate::qis::error::QisError::QubitMismatch {
expected: n,
actual: op.num_qubits,
});
}
}
Ok(Self {
num_qubits: n,
terms: ops,
})
}
}
impl Hamiltonian {
pub fn add_term(
&mut self,
op: PauliString,
coeff: Complex64,
) -> Result<(), crate::qis::error::QisError> {
if op.num_qubits != self.num_qubits {
return Err(crate::qis::error::QisError::QubitMismatch {
expected: self.num_qubits,
actual: op.num_qubits,
});
}
self.terms.push((op, coeff));
Ok(())
}
pub fn simplify(&mut self) {
if self.terms.is_empty() {
return;
}
let mut simplified_terms: Vec<(PauliString, Complex64)> =
Vec::with_capacity(self.terms.len());
for (op, coeff) in self.terms.drain(..) {
let mut pure_op = op.clone();
let phase_val = pure_op.phase.to_complex();
pure_op.phase = Phase::Plus;
let new_coeff = coeff * phase_val;
simplified_terms.push((pure_op, new_coeff));
}
simplified_terms.sort_by(|a, b| {
a.0.z.cmp(&b.0.z).then_with(|| a.0.x.cmp(&b.0.x))
});
let mut merged: Vec<(PauliString, Complex64)> = Vec::new();
if let Some(first) = simplified_terms.first() {
let mut current_op = first.0.clone();
let mut current_coeff = first.1;
for (op, coeff) in simplified_terms.into_iter().skip(1) {
if op.x == current_op.x && op.z == current_op.z {
current_coeff += coeff;
} else {
if current_coeff.norm() > 1e-10 {
merged.push((current_op, current_coeff));
}
current_op = op;
current_coeff = coeff;
}
}
if current_coeff.norm() > 1e-10 {
merged.push((current_op, current_coeff));
}
}
self.terms = merged;
}
pub fn scale(&mut self, factor: Complex64) {
for (_, coeff) in &mut self.terms {
*coeff *= factor;
}
}
pub fn all_terms_commute(&self) -> bool {
for i in 0..self.terms.len() {
for j in (i + 1)..self.terms.len() {
if !self.terms[i].0.commutes_with(&self.terms[j].0) {
return false;
}
}
}
true
}
}
impl Add for Hamiltonian {
type Output = Self;
fn add(mut self, rhs: Self) -> Self {
assert_eq!(self.num_qubits, rhs.num_qubits);
self.terms.extend(rhs.terms);
self
}
}
#[cfg(test)]
#[path = "hamiltonian_test.rs"]
mod tests;