| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| use crate::{AlgorithmError, AlgorithmResult}; |
| use num_complex::Complex64; |
| use std::collections::{HashMap, BTreeSet}; |
| use std::f64::consts::PI; |
|
|
| |
| #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] |
| pub enum PauliOp { |
| I = 0, |
| X = 1, |
| Y = 2, |
| Z = 3, |
| } |
|
|
| impl PauliOp { |
| |
| pub fn as_char(&self) -> char { |
| match self { |
| PauliOp::I => 'I', |
| PauliOp::X => 'X', |
| PauliOp::Y => 'Y', |
| PauliOp::Z => 'Z', |
| } |
| } |
|
|
| |
| pub fn from_char(c: char) -> AlgorithmResult<Self> { |
| match c { |
| 'I' => Ok(PauliOp::I), |
| 'X' => Ok(PauliOp::X), |
| 'Y' => Ok(PauliOp::Y), |
| 'Z' => Ok(PauliOp::Z), |
| _ => Err(AlgorithmError::InvalidParameters(format!( |
| "Invalid Pauli operator: {}", |
| c |
| ))), |
| } |
| } |
| } |
|
|
| |
| #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] |
| pub enum Phase { |
| Plus, |
| PlusI, |
| Minus, |
| MinusI, |
| } |
|
|
| impl Phase { |
| |
| pub fn mul(self, other: Phase) -> Phase { |
| match (self, other) { |
| (Phase::Plus, other) => other, |
| (Phase::Minus, Phase::Plus) => Phase::Minus, |
| (Phase::Minus, Phase::Minus) => Phase::Plus, |
| (Phase::Minus, Phase::PlusI) => Phase::MinusI, |
| (Phase::Minus, Phase::MinusI) => Phase::PlusI, |
| (Phase::PlusI, Phase::Plus) => Phase::PlusI, |
| (Phase::PlusI, Phase::Minus) => Phase::MinusI, |
| (Phase::PlusI, Phase::PlusI) => Phase::Minus, |
| (Phase::PlusI, Phase::MinusI) => Phase::Plus, |
| (Phase::MinusI, Phase::Plus) => Phase::MinusI, |
| (Phase::MinusI, Phase::Minus) => Phase::PlusI, |
| (Phase::MinusI, Phase::PlusI) => Phase::Plus, |
| (Phase::MinusI, Phase::MinusI) => Phase::Minus, |
| } |
| } |
|
|
| |
| pub fn as_complex(&self) -> Complex64 { |
| match self { |
| Phase::Plus => Complex64::new(1.0, 0.0), |
| Phase::PlusI => Complex64::new(0.0, 1.0), |
| Phase::Minus => Complex64::new(-1.0, 0.0), |
| Phase::MinusI => Complex64::new(0.0, -1.0), |
| } |
| } |
|
|
| |
| pub fn negate(&self) -> Phase { |
| match self { |
| Phase::Plus => Phase::Minus, |
| Phase::Minus => Phase::Plus, |
| Phase::PlusI => Phase::MinusI, |
| Phase::MinusI => Phase::PlusI, |
| } |
| } |
| } |
|
|
| |
| |
| #[derive(Debug, Clone, PartialEq, Eq, Hash)] |
| pub struct PauliString { |
| pub ops: Vec<PauliOp>, |
| pub phase: Phase, |
| } |
|
|
| impl PauliString { |
| |
| pub fn new(ops: Vec<PauliOp>) -> Self { |
| PauliString { |
| ops, |
| phase: Phase::Plus, |
| } |
| } |
|
|
| |
| pub fn with_phase(ops: Vec<PauliOp>, phase: Phase) -> Self { |
| PauliString { ops, phase } |
| } |
|
|
| |
| pub fn n_qubits(&self) -> usize { |
| self.ops.len() |
| } |
|
|
| |
| pub fn weight(&self) -> usize { |
| self.ops.iter().filter(|op| **op != PauliOp::I).count() |
| } |
|
|
| |
| |
| pub fn multiply(&self, other: &PauliString) -> AlgorithmResult<Self> { |
| if self.n_qubits() != other.n_qubits() { |
| return Err(AlgorithmError::InvalidParameters( |
| "Pauli strings must have same number of qubits".to_string(), |
| )); |
| } |
|
|
| let mut result_ops = Vec::new(); |
| let mut phase = self.phase; |
| phase = phase.mul(other.phase); |
|
|
| for i in 0..self.n_qubits() { |
| let p1 = self.ops[i]; |
| let p2 = other.ops[i]; |
| let (new_op, extra_phase) = pauli_multiply_single(p1, p2); |
| result_ops.push(new_op); |
| phase = phase.mul(extra_phase); |
| } |
|
|
| Ok(PauliString::with_phase(result_ops, phase)) |
| } |
|
|
| |
| pub fn commutes_with(&self, other: &PauliString) -> AlgorithmResult<bool> { |
| if self.n_qubits() != other.n_qubits() { |
| return Err(AlgorithmError::InvalidParameters( |
| "Pauli strings must have same number of qubits".to_string(), |
| )); |
| } |
|
|
| let mut anticommutation_count = 0; |
| for i in 0..self.n_qubits() { |
| let p1 = self.ops[i]; |
| let p2 = self.ops[i]; |
| if pauli_anticommute(p1, p2) { |
| anticommutation_count += 1; |
| } |
| } |
|
|
| |
| Ok(anticommutation_count % 2 == 0) |
| } |
|
|
| |
| pub fn to_string_rep(&self) -> String { |
| let phase_str = match self.phase { |
| Phase::Plus => String::new(), |
| Phase::Minus => "-".to_string(), |
| Phase::PlusI => "i".to_string(), |
| Phase::MinusI => "-i".to_string(), |
| }; |
|
|
| let ops_str: String = self.ops.iter().map(|op| op.as_char()).collect(); |
| format!("{}{}", phase_str, ops_str) |
| } |
| } |
|
|
| |
| fn pauli_multiply_single(p1: PauliOp, p2: PauliOp) -> (PauliOp, Phase) { |
| match (p1, p2) { |
| |
| (PauliOp::I, p) => (p, Phase::Plus), |
| (p, PauliOp::I) => (p, Phase::Plus), |
|
|
| |
| (PauliOp::X, PauliOp::X) => (PauliOp::I, Phase::Plus), |
| (PauliOp::Y, PauliOp::Y) => (PauliOp::I, Phase::Plus), |
| (PauliOp::Z, PauliOp::Z) => (PauliOp::I, Phase::Plus), |
|
|
| |
| (PauliOp::X, PauliOp::Y) => (PauliOp::Z, Phase::PlusI), |
| (PauliOp::X, PauliOp::Z) => (PauliOp::Y, Phase::MinusI), |
| (PauliOp::Y, PauliOp::X) => (PauliOp::Z, Phase::MinusI), |
| (PauliOp::Z, PauliOp::X) => (PauliOp::Y, Phase::PlusI), |
|
|
| |
| (PauliOp::Y, PauliOp::Z) => (PauliOp::X, Phase::PlusI), |
| (PauliOp::Z, PauliOp::Y) => (PauliOp::X, Phase::MinusI), |
| } |
| } |
|
|
| |
| fn pauli_anticommute(p1: PauliOp, p2: PauliOp) -> bool { |
| if p1 == PauliOp::I || p2 == PauliOp::I { |
| return false; |
| } |
| p1 != p2 |
| } |
|
|
| |
| #[derive(Debug, Clone)] |
| pub struct PauliHamiltonian { |
| |
| pub terms: Vec<(f64, PauliString)>, |
| |
| pub n_qubits: usize, |
| } |
|
|
| impl PauliHamiltonian { |
| |
| pub fn new(n_qubits: usize) -> Self { |
| PauliHamiltonian { |
| terms: Vec::new(), |
| n_qubits, |
| } |
| } |
|
|
| |
| pub fn add_term(&mut self, coeff: f64, pauli: PauliString) -> AlgorithmResult<()> { |
| if pauli.n_qubits() != self.n_qubits { |
| return Err(AlgorithmError::InvalidParameters( |
| format!("Pauli string has {} qubits, expected {}", pauli.n_qubits(), self.n_qubits), |
| )); |
| } |
| self.terms.push((coeff, pauli)); |
| Ok(()) |
| } |
|
|
| |
| |
| pub fn eigenvalue_bounds(&self) -> (f64, f64) { |
| let sum_abs: f64 = self.terms.iter().map(|(c, _)| c.abs()).sum(); |
| (-sum_abs, sum_abs) |
| } |
|
|
| |
| |
| pub fn commuting_groups(&self) -> AlgorithmResult<Vec<Vec<usize>>> { |
| let mut groups: Vec<Vec<usize>> = Vec::new(); |
|
|
| for (idx, (_, pauli)) in self.terms.iter().enumerate() { |
| let mut added = false; |
| for group in &mut groups { |
| |
| if pauli |
| .commutes_with(&self.terms[group[0]].1)? |
| { |
| group.push(idx); |
| added = true; |
| break; |
| } |
| } |
| if !added { |
| groups.push(vec![idx]); |
| } |
| } |
|
|
| Ok(groups) |
| } |
|
|
| |
| |
| pub fn energy_expectation(&self, _state: &[Complex64]) -> AlgorithmResult<f64> { |
| |
| |
| Ok(0.0) |
| } |
|
|
| |
| pub fn n_terms(&self) -> usize { |
| self.terms.len() |
| } |
|
|
| |
| pub fn total_weight(&self) -> usize { |
| self.terms.iter().map(|(_, p)| p.weight()).sum() |
| } |
| } |
|
|
| |
| pub fn h2_hamiltonian() -> PauliHamiltonian { |
| let mut h = PauliHamiltonian::new(2); |
|
|
| |
| |
| h.add_term(-1.0523732, PauliString::new(vec![PauliOp::I, PauliOp::I])) |
| .unwrap(); |
| h.add_term(-0.39793742, PauliString::new(vec![PauliOp::Z, PauliOp::I])) |
| .unwrap(); |
| h.add_term(-0.39793742, PauliString::new(vec![PauliOp::I, PauliOp::Z])) |
| .unwrap(); |
| h.add_term(-0.01128010, PauliString::new(vec![PauliOp::Z, PauliOp::Z])) |
| .unwrap(); |
|
|
| h |
| } |
|
|
| |
| pub fn ising_hamiltonian(n: usize, j: f64, h: &[f64]) -> AlgorithmResult<PauliHamiltonian> { |
| let mut ham = PauliHamiltonian::new(n); |
|
|
| |
| for i in 0..n { |
| let mut ops = vec![PauliOp::I; n]; |
| ops[i] = PauliOp::Z; |
| ham.add_term(-h[i], PauliString::new(ops))?; |
| } |
|
|
| |
| for i in 0..n - 1 { |
| let mut ops = vec![PauliOp::I; n]; |
| ops[i] = PauliOp::Z; |
| ops[i + 1] = PauliOp::Z; |
| ham.add_term(-j, PauliString::new(ops))?; |
| } |
|
|
| Ok(ham) |
| } |
|
|
| #[cfg(test)] |
| mod tests { |
| use super::*; |
|
|
| #[test] |
| fn test_pauli_multiply_single() { |
| let (result, phase) = pauli_multiply_single(PauliOp::X, PauliOp::Y); |
| assert_eq!(result, PauliOp::Z); |
| assert_eq!(phase, Phase::PlusI); |
|
|
| let (result, phase) = pauli_multiply_single(PauliOp::Y, PauliOp::X); |
| assert_eq!(result, PauliOp::Z); |
| assert_eq!(phase, Phase::MinusI); |
| } |
|
|
| #[test] |
| fn test_pauli_string_multiply() { |
| let p1 = PauliString::new(vec![PauliOp::X, PauliOp::X]); |
| let p2 = PauliString::new(vec![PauliOp::Y, PauliOp::Y]); |
|
|
| let result = p1.multiply(&p2).unwrap(); |
| assert_eq!(result.ops[0], PauliOp::Z); |
| assert_eq!(result.ops[1], PauliOp::Z); |
| } |
|
|
| #[test] |
| fn test_pauli_anticommute() { |
| assert!(pauli_anticommute(PauliOp::X, PauliOp::Y)); |
| assert!(pauli_anticommute(PauliOp::Y, PauliOp::Z)); |
| assert!(!pauli_anticommute(PauliOp::X, PauliOp::X)); |
| assert!(!pauli_anticommute(PauliOp::I, PauliOp::X)); |
| } |
|
|
| #[test] |
| fn test_h2_hamiltonian() { |
| let h = h2_hamiltonian(); |
| assert_eq!(h.n_qubits, 2); |
| assert_eq!(h.n_terms(), 4); |
| } |
|
|
| #[test] |
| fn test_eigenvalue_bounds() { |
| let mut h = PauliHamiltonian::new(1); |
| h.add_term(2.0, PauliString::new(vec![PauliOp::Z])) |
| .unwrap(); |
| h.add_term(-1.0, PauliString::new(vec![PauliOp::X])) |
| .unwrap(); |
|
|
| let (min, max) = h.eigenvalue_bounds(); |
| assert_eq!(min, -3.0); |
| assert_eq!(max, 3.0); |
| } |
|
|
| #[test] |
| fn test_commuting_groups() { |
| let mut h = PauliHamiltonian::new(1); |
| h.add_term(1.0, PauliString::new(vec![PauliOp::Z])) |
| .unwrap(); |
| h.add_term(1.0, PauliString::new(vec![PauliOp::Z])) |
| .unwrap(); |
| h.add_term(1.0, PauliString::new(vec![PauliOp::X])) |
| .unwrap(); |
|
|
| let groups = h.commuting_groups().unwrap(); |
| assert!(groups.len() >= 1); |
| } |
| } |
|
|
| |
|
|