1use crate::qis::QisError;
16use crate::qis::error::validate_coefficient;
17use num_complex::Complex64;
18use std::collections::{BTreeMap, BTreeSet};
19use std::ops::{Add, Mul, Neg, Sub};
20
21#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
25pub enum FermionicLadder {
26 Create(usize),
28 Annihilate(usize),
30}
31
32impl FermionicLadder {
33 pub fn mode(self) -> usize {
35 match self {
36 Self::Create(mode) | Self::Annihilate(mode) => mode,
37 }
38 }
39
40 pub fn adjoint(self) -> Self {
42 match self {
43 Self::Create(mode) => Self::Annihilate(mode),
44 Self::Annihilate(mode) => Self::Create(mode),
45 }
46 }
47}
48
49#[derive(Debug, Clone, PartialEq)]
59pub struct FermionicOp {
60 num_modes: usize,
61 terms: BTreeMap<Vec<FermionicLadder>, Complex64>,
62}
63
64pub(crate) fn validate_tolerance(atol: f64) -> Result<(), QisError> {
65 if !atol.is_finite() || atol < 0.0 {
66 return Err(QisError::InvalidParameterValue(
67 "tolerance must be finite and nonnegative".into(),
68 ));
69 }
70 Ok(())
71}
72
73impl FermionicOp {
74 pub fn new(num_modes: usize) -> Self {
76 Self {
77 num_modes,
78 terms: BTreeMap::new(),
79 }
80 }
81
82 pub fn identity(num_modes: usize) -> Self {
84 let mut result = Self::new(num_modes);
85 result.terms.insert(Vec::new(), Complex64::new(1.0, 0.0));
86 result
87 }
88
89 pub fn from_term(
91 num_modes: usize,
92 term: impl IntoIterator<Item = FermionicLadder>,
93 coefficient: Complex64,
94 ) -> Result<Self, QisError> {
95 let mut result = Self::new(num_modes);
96 result.add_term(term, coefficient)?;
97 Ok(result)
98 }
99
100 pub fn from_terms(
102 num_modes: usize,
103 terms: impl IntoIterator<Item = (Vec<FermionicLadder>, Complex64)>,
104 ) -> Result<Self, QisError> {
105 let mut result = Self::new(num_modes);
106 for (term, coefficient) in terms {
107 result.add_term(term, coefficient)?;
108 }
109 Ok(result)
110 }
111
112 pub fn add_term(
114 &mut self,
115 term: impl IntoIterator<Item = FermionicLadder>,
116 coefficient: Complex64,
117 ) -> Result<(), QisError> {
118 validate_coefficient(coefficient)?;
119 let term: Vec<_> = term.into_iter().collect();
120 for ladder in &term {
121 let mode = ladder.mode();
122 if mode >= self.num_modes {
123 return Err(QisError::ModeOutOfBounds {
124 mode,
125 num_modes: self.num_modes,
126 });
127 }
128 }
129 let merged = self.terms.get(&term).copied().unwrap_or_default() + coefficient;
130 validate_coefficient(merged)?;
131 if merged == Complex64::new(0.0, 0.0) {
132 self.terms.remove(&term);
133 } else {
134 self.terms.insert(term, merged);
135 }
136 Ok(())
137 }
138
139 pub fn creation(num_modes: usize, mode: usize) -> Result<Self, QisError> {
141 Self::from_term(num_modes, [FermionicLadder::Create(mode)], 1.0.into())
142 }
143
144 pub fn annihilation(num_modes: usize, mode: usize) -> Result<Self, QisError> {
146 Self::from_term(num_modes, [FermionicLadder::Annihilate(mode)], 1.0.into())
147 }
148
149 pub fn number(num_modes: usize, mode: usize) -> Result<Self, QisError> {
151 Self::from_term(
152 num_modes,
153 [
154 FermionicLadder::Create(mode),
155 FermionicLadder::Annihilate(mode),
156 ],
157 1.0.into(),
158 )
159 }
160
161 pub fn total_number(num_modes: usize) -> Self {
163 let terms = (0..num_modes)
164 .map(|mode| {
165 (
166 vec![
167 FermionicLadder::Create(mode),
168 FermionicLadder::Annihilate(mode),
169 ],
170 1.0.into(),
171 )
172 })
173 .collect();
174 Self { num_modes, terms }
175 }
176
177 pub fn excitation_generator(
211 num_modes: usize,
212 sources: &[usize],
213 targets: &[usize],
214 ) -> Result<Self, QisError> {
215 if sources.len() != targets.len() {
216 return Err(QisError::InvalidParameterValue(
217 "excitation sources and targets must have equal lengths".into(),
218 ));
219 }
220 if sources.is_empty() {
221 return Err(QisError::InvalidParameterValue(
222 "excitation sources and targets must be nonempty".into(),
223 ));
224 }
225
226 let mut source_modes = BTreeSet::new();
227 let mut target_modes = BTreeSet::new();
228 for (label, modes, seen) in [
229 ("sources", sources, &mut source_modes),
230 ("targets", targets, &mut target_modes),
231 ] {
232 for &mode in modes {
233 if mode >= num_modes {
234 return Err(QisError::ModeOutOfBounds { mode, num_modes });
235 }
236 if !seen.insert(mode) {
237 return Err(QisError::InvalidParameterValue(format!(
238 "excitation {label} contain repeated mode {mode}"
239 )));
240 }
241 }
242 }
243 if let Some(mode) = source_modes.intersection(&target_modes).next() {
244 return Err(QisError::InvalidParameterValue(format!(
245 "excitation sources and targets overlap at mode {mode}"
246 )));
247 }
248
249 let term = targets.iter().copied().map(FermionicLadder::Create).chain(
250 sources
251 .iter()
252 .rev()
253 .copied()
254 .map(FermionicLadder::Annihilate),
255 );
256 let excitation = Self::from_term(num_modes, term, 1.0.into())?;
257 excitation
258 .try_sub(&excitation.adjoint())?
259 .scaled(Complex64::new(0.0, 1.0))
260 }
261
262 pub fn num_modes(&self) -> usize {
264 self.num_modes
265 }
266
267 pub fn num_terms(&self) -> usize {
269 self.terms.len()
270 }
271
272 pub fn terms(&self) -> impl ExactSizeIterator<Item = (&[FermionicLadder], Complex64)> + '_ {
274 self.terms
275 .iter()
276 .map(|(term, coefficient)| (term.as_slice(), *coefficient))
277 }
278
279 pub fn is_zero(&self) -> bool {
281 self.terms.is_empty()
282 }
283
284 fn check_modes(&self, other: &Self) -> Result<(), QisError> {
285 if self.num_modes != other.num_modes {
286 return Err(QisError::ModeMismatch {
287 expected: self.num_modes,
288 actual: other.num_modes,
289 });
290 }
291 Ok(())
292 }
293
294 pub fn try_add(&self, other: &Self) -> Result<Self, QisError> {
296 self.check_modes(other)?;
297 let mut result = self.clone();
298 for (term, coefficient) in other.terms() {
299 result.add_term(term.iter().copied(), coefficient)?;
300 }
301 Ok(result)
302 }
303
304 pub fn try_sub(&self, other: &Self) -> Result<Self, QisError> {
306 self.check_modes(other)?;
307 let mut result = self.clone();
308 for (term, coefficient) in other.terms() {
309 result.add_term(term.iter().copied(), -coefficient)?;
310 }
311 Ok(result)
312 }
313
314 pub fn try_mul(&self, other: &Self) -> Result<Self, QisError> {
316 self.check_modes(other)?;
317 let mut result = Self::new(self.num_modes);
318 for (left, a) in self.terms() {
319 for (right, b) in other.terms() {
320 result.add_term(left.iter().chain(right).copied(), a * b)?;
321 }
322 }
323 Ok(result)
324 }
325
326 pub fn scaled(&self, factor: Complex64) -> Result<Self, QisError> {
328 validate_coefficient(factor)?;
329 Self::from_terms(
330 self.num_modes,
331 self.terms
332 .iter()
333 .map(|(term, c)| (term.clone(), *c * factor)),
334 )
335 }
336
337 pub fn adjoint(&self) -> Self {
339 Self {
340 num_modes: self.num_modes,
341 terms: self
342 .terms
343 .iter()
344 .map(|(term, coefficient)| {
345 (
346 term.iter().rev().map(|ladder| ladder.adjoint()).collect(),
347 coefficient.conj(),
348 )
349 })
350 .collect(),
351 }
352 }
353
354 pub fn normal_order(&self) -> Result<Self, QisError> {
363 let mut result = Self::new(self.num_modes);
364 let mut pending: Vec<_> = self
365 .terms
366 .iter()
367 .map(|(term, c)| (term.clone(), *c, 0usize))
368 .collect();
369 while let Some((mut term, mut coefficient, mut index)) = pending.pop() {
370 let mut complete = true;
371 while index + 1 < term.len() {
372 let left = term[index];
373 let right = term[index + 1];
374 if left == right {
375 complete = false;
376 break;
377 }
378 if left > right {
379 if let (FermionicLadder::Annihilate(p), FermionicLadder::Create(q)) =
382 (left, right)
383 && p == q
384 {
385 let mut contracted = term.clone();
386 contracted.drain(index..index + 2);
387 pending.push((contracted, coefficient, index.saturating_sub(1)));
390 }
391 term.swap(index, index + 1);
392 coefficient = -coefficient;
393 index = index.saturating_sub(1);
395 } else {
396 index += 1;
397 }
398 }
399 if complete {
400 result.add_term(term, coefficient)?;
401 }
402 }
403 Ok(result)
404 }
405
406 pub fn chop(&mut self, atol: f64) -> Result<(), QisError> {
409 validate_tolerance(atol)?;
410 self.terms
411 .retain(|_, coefficient| coefficient.norm() > atol);
412 Ok(())
413 }
414
415 pub fn equiv(&self, other: &Self, atol: f64) -> Result<bool, QisError> {
418 validate_tolerance(atol)?;
419 let difference = self.try_sub(other)?.normal_order()?;
420 Ok(difference
421 .terms
422 .values()
423 .all(|coefficient| coefficient.norm() <= atol))
424 }
425
426 pub fn is_hermitian(&self, atol: f64) -> Result<bool, QisError> {
428 self.equiv(&self.adjoint(), atol)
429 }
430}
431
432macro_rules! binary_operator {
433 ($trait:ident, $method:ident, $checked:ident) => {
434 impl $trait<&FermionicOp> for &FermionicOp {
435 type Output = Result<FermionicOp, QisError>;
436 fn $method(self, rhs: &FermionicOp) -> Self::Output {
437 self.$checked(rhs)
438 }
439 }
440 impl $trait<FermionicOp> for FermionicOp {
441 type Output = Result<FermionicOp, QisError>;
442 fn $method(self, rhs: FermionicOp) -> Self::Output {
443 self.$checked(&rhs)
444 }
445 }
446 impl $trait<&FermionicOp> for FermionicOp {
447 type Output = Result<FermionicOp, QisError>;
448 fn $method(self, rhs: &FermionicOp) -> Self::Output {
449 self.$checked(rhs)
450 }
451 }
452 impl $trait<FermionicOp> for &FermionicOp {
453 type Output = Result<FermionicOp, QisError>;
454 fn $method(self, rhs: FermionicOp) -> Self::Output {
455 self.$checked(&rhs)
456 }
457 }
458 };
459}
460
461binary_operator!(Add, add, try_add);
462binary_operator!(Sub, sub, try_sub);
463binary_operator!(Mul, mul, try_mul);
464
465macro_rules! scalar_operator {
466 ($scalar:ty) => {
467 impl Mul<$scalar> for &FermionicOp {
468 type Output = Result<FermionicOp, QisError>;
469 fn mul(self, rhs: $scalar) -> Self::Output {
470 self.scaled(rhs.into())
471 }
472 }
473 impl Mul<$scalar> for FermionicOp {
474 type Output = Result<FermionicOp, QisError>;
475 fn mul(self, rhs: $scalar) -> Self::Output {
476 self.scaled(rhs.into())
477 }
478 }
479 impl Mul<&FermionicOp> for $scalar {
480 type Output = Result<FermionicOp, QisError>;
481 fn mul(self, rhs: &FermionicOp) -> Self::Output {
482 rhs.scaled(self.into())
483 }
484 }
485 impl Mul<FermionicOp> for $scalar {
486 type Output = Result<FermionicOp, QisError>;
487 fn mul(self, rhs: FermionicOp) -> Self::Output {
488 rhs.scaled(self.into())
489 }
490 }
491 };
492}
493
494scalar_operator!(f64);
495scalar_operator!(Complex64);
496
497impl Neg for &FermionicOp {
498 type Output = FermionicOp;
499 fn neg(self) -> Self::Output {
500 let mut result = self.clone();
501 for coefficient in result.terms.values_mut() {
502 *coefficient = -*coefficient;
503 }
504 result
505 }
506}
507
508impl Neg for FermionicOp {
509 type Output = Self;
510 fn neg(mut self) -> Self::Output {
511 for coefficient in self.terms.values_mut() {
512 *coefficient = -*coefficient;
513 }
514 self
515 }
516}