use crate::qis::QisError;
use crate::qis::error::validate_coefficient;
use num_complex::Complex64;
use std::collections::{BTreeMap, BTreeSet};
use std::ops::{Add, Mul, Neg, Sub};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum FermionicLadder {
Create(usize),
Annihilate(usize),
}
impl FermionicLadder {
pub fn mode(self) -> usize {
match self {
Self::Create(mode) | Self::Annihilate(mode) => mode,
}
}
pub fn adjoint(self) -> Self {
match self {
Self::Create(mode) => Self::Annihilate(mode),
Self::Annihilate(mode) => Self::Create(mode),
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct FermionicOp {
num_modes: usize,
terms: BTreeMap<Vec<FermionicLadder>, Complex64>,
}
pub(crate) fn validate_tolerance(atol: f64) -> Result<(), QisError> {
if !atol.is_finite() || atol < 0.0 {
return Err(QisError::InvalidParameterValue(
"tolerance must be finite and nonnegative".into(),
));
}
Ok(())
}
impl FermionicOp {
pub fn new(num_modes: usize) -> Self {
Self {
num_modes,
terms: BTreeMap::new(),
}
}
pub fn identity(num_modes: usize) -> Self {
let mut result = Self::new(num_modes);
result.terms.insert(Vec::new(), Complex64::new(1.0, 0.0));
result
}
pub fn from_term(
num_modes: usize,
term: impl IntoIterator<Item = FermionicLadder>,
coefficient: Complex64,
) -> Result<Self, QisError> {
let mut result = Self::new(num_modes);
result.add_term(term, coefficient)?;
Ok(result)
}
pub fn from_terms(
num_modes: usize,
terms: impl IntoIterator<Item = (Vec<FermionicLadder>, Complex64)>,
) -> Result<Self, QisError> {
let mut result = Self::new(num_modes);
for (term, coefficient) in terms {
result.add_term(term, coefficient)?;
}
Ok(result)
}
pub fn add_term(
&mut self,
term: impl IntoIterator<Item = FermionicLadder>,
coefficient: Complex64,
) -> Result<(), QisError> {
validate_coefficient(coefficient)?;
let term: Vec<_> = term.into_iter().collect();
for ladder in &term {
let mode = ladder.mode();
if mode >= self.num_modes {
return Err(QisError::ModeOutOfBounds {
mode,
num_modes: self.num_modes,
});
}
}
let merged = self.terms.get(&term).copied().unwrap_or_default() + coefficient;
validate_coefficient(merged)?;
if merged == Complex64::new(0.0, 0.0) {
self.terms.remove(&term);
} else {
self.terms.insert(term, merged);
}
Ok(())
}
pub fn creation(num_modes: usize, mode: usize) -> Result<Self, QisError> {
Self::from_term(num_modes, [FermionicLadder::Create(mode)], 1.0.into())
}
pub fn annihilation(num_modes: usize, mode: usize) -> Result<Self, QisError> {
Self::from_term(num_modes, [FermionicLadder::Annihilate(mode)], 1.0.into())
}
pub fn number(num_modes: usize, mode: usize) -> Result<Self, QisError> {
Self::from_term(
num_modes,
[
FermionicLadder::Create(mode),
FermionicLadder::Annihilate(mode),
],
1.0.into(),
)
}
pub fn total_number(num_modes: usize) -> Self {
let terms = (0..num_modes)
.map(|mode| {
(
vec![
FermionicLadder::Create(mode),
FermionicLadder::Annihilate(mode),
],
1.0.into(),
)
})
.collect();
Self { num_modes, terms }
}
pub fn excitation_generator(
num_modes: usize,
sources: &[usize],
targets: &[usize],
) -> Result<Self, QisError> {
if sources.len() != targets.len() {
return Err(QisError::InvalidParameterValue(
"excitation sources and targets must have equal lengths".into(),
));
}
if sources.is_empty() {
return Err(QisError::InvalidParameterValue(
"excitation sources and targets must be nonempty".into(),
));
}
let mut source_modes = BTreeSet::new();
let mut target_modes = BTreeSet::new();
for (label, modes, seen) in [
("sources", sources, &mut source_modes),
("targets", targets, &mut target_modes),
] {
for &mode in modes {
if mode >= num_modes {
return Err(QisError::ModeOutOfBounds { mode, num_modes });
}
if !seen.insert(mode) {
return Err(QisError::InvalidParameterValue(format!(
"excitation {label} contain repeated mode {mode}"
)));
}
}
}
if let Some(mode) = source_modes.intersection(&target_modes).next() {
return Err(QisError::InvalidParameterValue(format!(
"excitation sources and targets overlap at mode {mode}"
)));
}
let term = targets.iter().copied().map(FermionicLadder::Create).chain(
sources
.iter()
.rev()
.copied()
.map(FermionicLadder::Annihilate),
);
let excitation = Self::from_term(num_modes, term, 1.0.into())?;
excitation
.try_sub(&excitation.adjoint())?
.scaled(Complex64::new(0.0, 1.0))
}
pub fn num_modes(&self) -> usize {
self.num_modes
}
pub fn num_terms(&self) -> usize {
self.terms.len()
}
pub fn terms(&self) -> impl ExactSizeIterator<Item = (&[FermionicLadder], Complex64)> + '_ {
self.terms
.iter()
.map(|(term, coefficient)| (term.as_slice(), *coefficient))
}
pub fn is_zero(&self) -> bool {
self.terms.is_empty()
}
fn check_modes(&self, other: &Self) -> Result<(), QisError> {
if self.num_modes != other.num_modes {
return Err(QisError::ModeMismatch {
expected: self.num_modes,
actual: other.num_modes,
});
}
Ok(())
}
pub fn try_add(&self, other: &Self) -> Result<Self, QisError> {
self.check_modes(other)?;
let mut result = self.clone();
for (term, coefficient) in other.terms() {
result.add_term(term.iter().copied(), coefficient)?;
}
Ok(result)
}
pub fn try_sub(&self, other: &Self) -> Result<Self, QisError> {
self.check_modes(other)?;
let mut result = self.clone();
for (term, coefficient) in other.terms() {
result.add_term(term.iter().copied(), -coefficient)?;
}
Ok(result)
}
pub fn try_mul(&self, other: &Self) -> Result<Self, QisError> {
self.check_modes(other)?;
let mut result = Self::new(self.num_modes);
for (left, a) in self.terms() {
for (right, b) in other.terms() {
result.add_term(left.iter().chain(right).copied(), a * b)?;
}
}
Ok(result)
}
pub fn scaled(&self, factor: Complex64) -> Result<Self, QisError> {
validate_coefficient(factor)?;
Self::from_terms(
self.num_modes,
self.terms
.iter()
.map(|(term, c)| (term.clone(), *c * factor)),
)
}
pub fn adjoint(&self) -> Self {
Self {
num_modes: self.num_modes,
terms: self
.terms
.iter()
.map(|(term, coefficient)| {
(
term.iter().rev().map(|ladder| ladder.adjoint()).collect(),
coefficient.conj(),
)
})
.collect(),
}
}
pub fn normal_order(&self) -> Result<Self, QisError> {
let mut result = Self::new(self.num_modes);
let mut pending: Vec<_> = self
.terms
.iter()
.map(|(term, c)| (term.clone(), *c, 0usize))
.collect();
while let Some((mut term, mut coefficient, mut index)) = pending.pop() {
let mut complete = true;
while index + 1 < term.len() {
let left = term[index];
let right = term[index + 1];
if left == right {
complete = false;
break;
}
if left > right {
if let (FermionicLadder::Annihilate(p), FermionicLadder::Create(q)) =
(left, right)
&& p == q
{
let mut contracted = term.clone();
contracted.drain(index..index + 2);
pending.push((contracted, coefficient, index.saturating_sub(1)));
}
term.swap(index, index + 1);
coefficient = -coefficient;
index = index.saturating_sub(1);
} else {
index += 1;
}
}
if complete {
result.add_term(term, coefficient)?;
}
}
Ok(result)
}
pub fn chop(&mut self, atol: f64) -> Result<(), QisError> {
validate_tolerance(atol)?;
self.terms
.retain(|_, coefficient| coefficient.norm() > atol);
Ok(())
}
pub fn equiv(&self, other: &Self, atol: f64) -> Result<bool, QisError> {
validate_tolerance(atol)?;
let difference = self.try_sub(other)?.normal_order()?;
Ok(difference
.terms
.values()
.all(|coefficient| coefficient.norm() <= atol))
}
pub fn is_hermitian(&self, atol: f64) -> Result<bool, QisError> {
self.equiv(&self.adjoint(), atol)
}
}
macro_rules! binary_operator {
($trait:ident, $method:ident, $checked:ident) => {
impl $trait<&FermionicOp> for &FermionicOp {
type Output = Result<FermionicOp, QisError>;
fn $method(self, rhs: &FermionicOp) -> Self::Output {
self.$checked(rhs)
}
}
impl $trait<FermionicOp> for FermionicOp {
type Output = Result<FermionicOp, QisError>;
fn $method(self, rhs: FermionicOp) -> Self::Output {
self.$checked(&rhs)
}
}
impl $trait<&FermionicOp> for FermionicOp {
type Output = Result<FermionicOp, QisError>;
fn $method(self, rhs: &FermionicOp) -> Self::Output {
self.$checked(rhs)
}
}
impl $trait<FermionicOp> for &FermionicOp {
type Output = Result<FermionicOp, QisError>;
fn $method(self, rhs: FermionicOp) -> Self::Output {
self.$checked(&rhs)
}
}
};
}
binary_operator!(Add, add, try_add);
binary_operator!(Sub, sub, try_sub);
binary_operator!(Mul, mul, try_mul);
macro_rules! scalar_operator {
($scalar:ty) => {
impl Mul<$scalar> for &FermionicOp {
type Output = Result<FermionicOp, QisError>;
fn mul(self, rhs: $scalar) -> Self::Output {
self.scaled(rhs.into())
}
}
impl Mul<$scalar> for FermionicOp {
type Output = Result<FermionicOp, QisError>;
fn mul(self, rhs: $scalar) -> Self::Output {
self.scaled(rhs.into())
}
}
impl Mul<&FermionicOp> for $scalar {
type Output = Result<FermionicOp, QisError>;
fn mul(self, rhs: &FermionicOp) -> Self::Output {
rhs.scaled(self.into())
}
}
impl Mul<FermionicOp> for $scalar {
type Output = Result<FermionicOp, QisError>;
fn mul(self, rhs: FermionicOp) -> Self::Output {
rhs.scaled(self.into())
}
}
};
}
scalar_operator!(f64);
scalar_operator!(Complex64);
impl Neg for &FermionicOp {
type Output = FermionicOp;
fn neg(self) -> Self::Output {
let mut result = self.clone();
for coefficient in result.terms.values_mut() {
*coefficient = -*coefficient;
}
result
}
}
impl Neg for FermionicOp {
type Output = Self;
fn neg(mut self) -> Self::Output {
for coefficient in self.terms.values_mut() {
*coefficient = -*coefficient;
}
self
}
}