1use std::collections::HashMap;
2use std::marker::PhantomData;
3use std::sync::Arc;
4
5use num_complex::Complex64;
6use smallvec::SmallVec;
7
8pub use crate::backend::ShiftedLinearSolver;
9use crate::backend::factor_shifted_csc;
10use crate::basis::Basis;
11use crate::{QmbedError, Result};
12
13#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
18pub enum LocalOperator {
19 Identity,
20 Number,
21 Z,
22 Raising,
23 Lowering,
24 X,
25 Y,
26 Custom(char),
27}
28
29impl LocalOperator {
30 pub const fn symbol(self) -> char {
32 match self {
33 Self::Identity => 'I',
34 Self::Number => 'n',
35 Self::Z => 'z',
36 Self::Raising => '+',
37 Self::Lowering => '-',
38 Self::X => 'x',
39 Self::Y => 'y',
40 Self::Custom(symbol) => symbol,
41 }
42 }
43}
44
45#[derive(Clone, Debug, Eq, PartialEq)]
51pub struct OpProduct {
52 local: SmallVec<[LocalOperator; 8]>,
53 symbols: SmallVec<[char; 8]>,
54 split: Option<usize>,
55 splits: SmallVec<[usize; 4]>,
56 label: String,
57}
58
59impl OpProduct {
60 pub fn parse(label: impl AsRef<str>) -> Result<Self> {
65 let mut splits = Vec::new();
66 let mut local = Vec::with_capacity(label.as_ref().chars().count());
67 for symbol in label.as_ref().chars() {
68 if symbol == '|' {
69 splits.push(local.len());
70 continue;
71 }
72 local.push(match symbol {
73 'I' => LocalOperator::Identity,
74 'n' => LocalOperator::Number,
75 'z' => LocalOperator::Z,
76 '+' => LocalOperator::Raising,
77 '-' => LocalOperator::Lowering,
78 'x' => LocalOperator::X,
79 'y' => LocalOperator::Y,
80 custom => LocalOperator::Custom(custom),
81 });
82 }
83 Self::with_splits(local, splits)
84 }
85
86 pub fn new(local: impl IntoIterator<Item = LocalOperator>) -> Result<Self> {
91 Self::with_split(local, None)
92 }
93
94 pub fn spinful(
96 up: impl IntoIterator<Item = LocalOperator>,
97 down: impl IntoIterator<Item = LocalOperator>,
98 ) -> Result<Self> {
99 let mut local: SmallVec<[LocalOperator; 8]> = up.into_iter().collect();
100 let split = local.len();
101 local.extend(down);
102 Self::with_split(local, Some(split))
103 }
104
105 pub fn with_split(
107 local: impl IntoIterator<Item = LocalOperator>,
108 split: Option<usize>,
109 ) -> Result<Self> {
110 Self::with_splits(local, split)
111 }
112
113 pub fn with_splits(
117 local: impl IntoIterator<Item = LocalOperator>,
118 splits: impl IntoIterator<Item = usize>,
119 ) -> Result<Self> {
120 let local: SmallVec<[LocalOperator; 8]> = local.into_iter().collect();
121 if local.is_empty() {
122 return Err(QmbedError::InvalidOperator(
123 "operator products cannot be empty".into(),
124 ));
125 }
126 if local.contains(&LocalOperator::Custom('|')) {
127 return Err(QmbedError::InvalidOperator(
128 "the species separator is not a local operator".into(),
129 ));
130 }
131 let splits: SmallVec<[usize; 4]> = splits.into_iter().collect();
132 if splits.iter().any(|&boundary| boundary > local.len())
133 || splits.windows(2).any(|pair| pair[0] > pair[1])
134 {
135 return Err(QmbedError::InvalidOperator(
136 "operator factor boundaries must be ordered within the product arity".into(),
137 ));
138 }
139 let symbols: SmallVec<[char; 8]> = local.iter().map(|operator| operator.symbol()).collect();
140 let mut label = String::with_capacity(symbols.len() + splits.len());
141 let mut boundary = 0;
142 for position in 0..=symbols.len() {
143 while splits.get(boundary) == Some(&position) {
144 label.push('|');
145 boundary += 1;
146 }
147 if let Some(symbol) = symbols.get(position) {
148 label.push(*symbol);
149 }
150 }
151 let split = (splits.len() == 1).then(|| splits[0]);
152 Ok(Self {
153 local,
154 symbols,
155 split,
156 splits,
157 label,
158 })
159 }
160
161 pub fn local_operators(&self) -> &[LocalOperator] {
163 &self.local
164 }
165
166 pub const fn split(&self) -> Option<usize> {
168 self.split
169 }
170
171 pub fn splits(&self) -> &[usize] {
173 &self.splits
174 }
175
176 pub fn label(&self) -> &str {
178 &self.label
179 }
180
181 pub(crate) fn symbols(&self) -> &[char] {
182 &self.symbols
183 }
184}
185
186#[derive(Clone, Debug, PartialEq)]
188pub struct Coupling {
189 pub coefficient: Complex64,
190 pub sites: Vec<usize>,
191}
192
193impl Coupling {
194 pub fn new(coefficient: impl Into<Complex64>, sites: impl Into<Vec<usize>>) -> Self {
199 Self {
200 coefficient: coefficient.into(),
201 sites: sites.into(),
202 }
203 }
204}
205
206#[derive(Clone, Debug, PartialEq)]
208pub struct OperatorSpec {
209 product: OpProduct,
210 couplings: Vec<Coupling>,
211}
212
213impl OperatorSpec {
214 pub fn new(
216 label: impl AsRef<str>,
217 couplings: impl IntoIterator<Item = Coupling>,
218 ) -> Result<Self> {
219 Self::from_product(OpProduct::parse(label)?, couplings)
220 }
221
222 pub fn from_product(
227 product: OpProduct,
228 couplings: impl IntoIterator<Item = Coupling>,
229 ) -> Result<Self> {
230 let arity = product.local_operators().len();
231 let couplings: Vec<_> = couplings.into_iter().collect();
232 for coupling in &couplings {
233 if coupling.sites.len() != arity {
234 return Err(QmbedError::InvalidCoupling(format!(
235 "operator {:?} has arity {arity}, but a coupling has {} sites",
236 product.label(),
237 coupling.sites.len()
238 )));
239 }
240 if !coupling.coefficient.re.is_finite() || !coupling.coefficient.im.is_finite() {
241 return Err(QmbedError::InvalidCoupling(
242 "coupling coefficients must be finite".into(),
243 ));
244 }
245 }
246 Ok(Self { product, couplings })
247 }
248
249 pub fn operator(&self) -> &str {
251 self.product.label()
252 }
253
254 pub const fn product(&self) -> &OpProduct {
256 &self.product
257 }
258
259 pub fn couplings(&self) -> &[Coupling] {
261 &self.couplings
262 }
263
264 pub fn with_site_permutation(&self, permutation: &[usize]) -> Result<Self> {
270 if permutation.iter().any(|&site| site >= permutation.len()) {
271 return Err(QmbedError::InvalidSite {
272 site: permutation
273 .iter()
274 .copied()
275 .find(|&site| site >= permutation.len())
276 .unwrap_or(permutation.len()),
277 sites: permutation.len(),
278 });
279 }
280 let unique = permutation
281 .iter()
282 .copied()
283 .collect::<std::collections::HashSet<_>>();
284 if unique.len() != permutation.len() {
285 return Err(QmbedError::InvalidOptions(
286 "site permutation must be bijective".into(),
287 ));
288 }
289 let couplings = self
290 .couplings
291 .iter()
292 .map(|coupling| {
293 let sites = coupling
294 .sites
295 .iter()
296 .map(|&site| {
297 permutation
298 .get(site)
299 .copied()
300 .ok_or(QmbedError::InvalidSite {
301 site,
302 sites: permutation.len(),
303 })
304 })
305 .collect::<Result<Vec<_>>>()?;
306 Ok(Coupling::new(coupling.coefficient, sites))
307 })
308 .collect::<Result<Vec<_>>>()?;
309 Self::from_product(self.product.clone(), couplings)
310 }
311
312 pub(crate) fn symbols(&self) -> &[char] {
313 self.product.symbols()
314 }
315
316 pub(crate) const fn split(&self) -> Option<usize> {
317 self.product.split()
318 }
319}
320
321#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
323#[non_exhaustive]
324pub enum MatrixFormat {
325 Dense,
327 Csc,
329 Csr,
331 Dia,
333 MatrixFree,
335}
336
337pub trait LinearOperator: Send + Sync {
339 fn shape(&self) -> (usize, usize);
341 fn format(&self) -> MatrixFormat;
343 fn apply(&self, input: &[Complex64], output: &mut [Complex64]) -> Result<()>;
348
349 fn is_real(&self) -> bool {
354 false
355 }
356
357 fn apply_real(&self, input: &[f64], output: &mut [f64]) -> Result<()> {
364 let shape = self.shape();
365 if input.len() != shape.1 || output.len() != shape.0 {
366 return Err(QmbedError::DimensionMismatch(format!(
367 "shape {shape:?} requires real input length {} and output length {}, got {} and {}",
368 shape.1,
369 shape.0,
370 input.len(),
371 output.len()
372 )));
373 }
374 let complex_input: Vec<_> = input
375 .iter()
376 .map(|&value| Complex64::new(value, 0.0))
377 .collect();
378 let mut complex_output = vec![Complex64::new(0.0, 0.0); shape.0];
379 self.apply(&complex_input, &mut complex_output)?;
380 for (real, complex) in output.iter_mut().zip(complex_output) {
381 if complex.im.abs() > 1.0e-12 {
382 return Err(QmbedError::UnsupportedBackend(
383 "operator declared a real action but produced an imaginary component".into(),
384 ));
385 }
386 *real = complex.re;
387 }
388 Ok(())
389 }
390
391 fn apply_transpose(&self, input: &[Complex64], output: &mut [Complex64]) -> Result<()> {
397 let (rows, columns) = self.shape();
398 if input.len() != rows || output.len() != columns {
399 return Err(QmbedError::DimensionMismatch(format!(
400 "transpose of shape ({rows}, {columns}) requires input length {rows} and output length {columns}"
401 )));
402 }
403 output.fill(Complex64::new(0.0, 0.0));
404 if let Some(entries) = self.stored_triplets()? {
405 for (row, column, value) in entries {
406 output[column] += value * input[row];
407 }
408 return Ok(());
409 }
410
411 let mut basis_vector = vec![Complex64::new(0.0, 0.0); columns];
412 let mut column_values = vec![Complex64::new(0.0, 0.0); rows];
413 for column in 0..columns {
414 basis_vector.fill(Complex64::new(0.0, 0.0));
415 basis_vector[column] = Complex64::new(1.0, 0.0);
416 self.apply(&basis_vector, &mut column_values)?;
417 output[column] = column_values
418 .iter()
419 .zip(input.iter())
420 .map(|(value, input_value)| *value * *input_value)
421 .sum();
422 }
423 Ok(())
424 }
425
426 fn apply_adjoint(&self, input: &[Complex64], output: &mut [Complex64]) -> Result<()> {
428 let conjugated_input: Vec<_> = input.iter().map(|value| value.conj()).collect();
429 self.apply_transpose(&conjugated_input, output)?;
430 for value in output {
431 *value = value.conj();
432 }
433 Ok(())
434 }
435
436 fn stored_triplets(&self) -> Result<Option<Vec<(usize, usize, Complex64)>>> {
438 Ok(None)
439 }
440
441 fn shifted_solver(&self, _shift: f64) -> Result<Option<Box<dyn ShiftedLinearSolver>>> {
443 Ok(None)
444 }
445}
446
447pub trait TimeDependentOperator: Send + Sync {
449 fn shape(&self) -> (usize, usize);
450 fn apply_at(&self, time: f64, input: &[Complex64], output: &mut [Complex64]) -> Result<()>;
451}
452
453type TimedApply = Arc<dyn Fn(f64, &[Complex64], &mut [Complex64]) -> Result<()> + Send + Sync>;
454
455#[derive(Clone)]
457pub struct TimeOperator {
458 shape: (usize, usize),
459 action: TimedApply,
460}
461
462impl std::fmt::Debug for TimeOperator {
463 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
464 formatter
465 .debug_struct("TimeOperator")
466 .field("shape", &self.shape)
467 .finish_non_exhaustive()
468 }
469}
470
471impl TimeOperator {
472 pub fn new<F>(shape: (usize, usize), action: F) -> Result<Self>
473 where
474 F: Fn(f64, &[Complex64], &mut [Complex64]) -> Result<()> + Send + Sync + 'static,
475 {
476 if shape.0 == 0 || shape.1 == 0 {
477 return Err(QmbedError::DimensionMismatch(
478 "time-dependent operator dimensions must be positive".into(),
479 ));
480 }
481 Ok(Self {
482 shape,
483 action: Arc::new(action),
484 })
485 }
486
487 pub fn from_operator<O>(operator: Arc<O>) -> Self
488 where
489 O: TimeDependentOperator + 'static,
490 {
491 let shape = operator.shape();
492 Self {
493 shape,
494 action: Arc::new(move |time, input, output| operator.apply_at(time, input, output)),
495 }
496 }
497
498 pub fn evaluate(&self, time: f64, format: MatrixFormat) -> Result<Operator> {
499 if !time.is_finite() {
500 return Err(QmbedError::InvalidOptions(
501 "evaluation time must be finite".into(),
502 ));
503 }
504 let mut input = vec![Complex64::new(0.0, 0.0); self.shape.1];
505 let mut output = vec![Complex64::new(0.0, 0.0); self.shape.0];
506 let mut triplets = Vec::new();
507 for column in 0..self.shape.1 {
508 input.fill(Complex64::new(0.0, 0.0));
509 input[column] = Complex64::new(1.0, 0.0);
510 self.apply_at(time, &input, &mut output)?;
511 for (row, value) in output.iter().copied().enumerate() {
512 if value.norm() > f64::EPSILON {
513 triplets.push((row, column, value));
514 }
515 }
516 }
517 Operator::from_triplets(self.shape.0, self.shape.1, triplets, format)
518 }
519
520 pub fn scaled(&self, coefficient: impl Into<Complex64>) -> Result<Self> {
521 let coefficient = coefficient.into();
522 if !coefficient.re.is_finite() || !coefficient.im.is_finite() {
523 return Err(QmbedError::InvalidOptions(
524 "time-operator scale must be finite".into(),
525 ));
526 }
527 let operator = self.clone();
528 Self::new(self.shape, move |time, input, output| {
529 operator.apply_at(time, input, output)?;
530 for value in output {
531 *value *= coefficient;
532 }
533 Ok(())
534 })
535 }
536
537 pub fn add(&self, right: &Self) -> Result<Self> {
538 if self.shape != right.shape {
539 return Err(QmbedError::DimensionMismatch(
540 "time-dependent sums require equal shapes".into(),
541 ));
542 }
543 let left = self.clone();
544 let right = right.clone();
545 Self::new(self.shape, move |time, input, output| {
546 left.apply_at(time, input, output)?;
547 let mut contribution = vec![Complex64::new(0.0, 0.0); output.len()];
548 right.apply_at(time, input, &mut contribution)?;
549 for (value, addition) in output.iter_mut().zip(contribution) {
550 *value += addition;
551 }
552 Ok(())
553 })
554 }
555
556 pub fn subtract(&self, right: &Self) -> Result<Self> {
557 self.add(&right.scaled(-1.0)?)
558 }
559
560 pub fn product(&self, right: &Self) -> Result<Self> {
561 if self.shape.1 != right.shape.0 {
562 return Err(QmbedError::DimensionMismatch(
563 "time-dependent product inner dimensions do not match".into(),
564 ));
565 }
566 let left = self.clone();
567 let right = right.clone();
568 Self::new((self.shape.0, right.shape.1), move |time, input, output| {
569 let mut intermediate = vec![Complex64::new(0.0, 0.0); right.shape.0];
570 right.apply_at(time, input, &mut intermediate)?;
571 left.apply_at(time, &intermediate, output)
572 })
573 }
574
575 pub fn commutator(&self, right: &Self) -> Result<Self> {
576 self.product(right)?.subtract(&right.product(self)?)
577 }
578
579 pub fn anticommutator(&self, right: &Self) -> Result<Self> {
580 self.product(right)?.add(&right.product(self)?)
581 }
582
583 pub fn pow(&self, exponent: u32) -> Result<Self> {
584 if self.shape.0 != self.shape.1 {
585 return Err(QmbedError::DimensionMismatch(
586 "time-dependent powers require a square operator".into(),
587 ));
588 }
589 if exponent == 0 {
590 let dimension = self.shape.0;
591 return Self::new(self.shape, move |_time, input, output| {
592 check_apply_shape((dimension, dimension), input, output)?;
593 output.copy_from_slice(input);
594 Ok(())
595 });
596 }
597 let mut result = self.clone();
598 for _ in 1..exponent {
599 result = result.product(self)?;
600 }
601 Ok(result)
602 }
603
604 pub fn rotated(&self, unitary: &Operator, tolerance: f64) -> Result<Self> {
605 if self.shape.0 != self.shape.1 || unitary.shape() != self.shape {
606 return Err(QmbedError::DimensionMismatch(
607 "time-dependent rotation needs equal square shapes".into(),
608 ));
609 }
610 let adjoint = unitary.adjoint()?;
611 let identity = adjoint.product(unitary)?;
612 for row in 0..self.shape.0 {
613 for column in 0..self.shape.1 {
614 let expected = if row == column { 1.0 } else { 0.0 };
615 if (identity.value_at(row, column) - expected).norm() > tolerance {
616 return Err(QmbedError::InvalidOptions(
617 "rotation matrix must be unitary".into(),
618 ));
619 }
620 }
621 }
622 let operator = self.clone();
623 let unitary = unitary.clone();
624 Self::new(self.shape, move |time, input, output| {
625 let mut rotated_input = vec![Complex64::new(0.0, 0.0); input.len()];
626 let mut applied = vec![Complex64::new(0.0, 0.0); output.len()];
627 unitary.apply(input, &mut rotated_input)?;
628 operator.apply_at(time, &rotated_input, &mut applied)?;
629 adjoint.apply(&applied, output)
630 })
631 }
632}
633
634impl TimeDependentOperator for TimeOperator {
635 fn shape(&self) -> (usize, usize) {
636 self.shape
637 }
638
639 fn apply_at(&self, time: f64, input: &[Complex64], output: &mut [Complex64]) -> Result<()> {
640 if !time.is_finite() {
641 return Err(QmbedError::InvalidOptions(
642 "time-dependent operator time must be finite".into(),
643 ));
644 }
645 check_apply_shape(self.shape, input, output)?;
646 (self.action)(time, input, output)
647 }
648}
649
650#[derive(Clone, Copy, Debug, Eq, PartialEq)]
651pub struct AssemblyChecks {
652 pub hermiticity: bool,
653 pub particle_conservation: bool,
654 pub symmetry_compatibility: bool,
655}
656
657impl AssemblyChecks {
658 pub const fn all() -> Self {
659 Self {
660 hermiticity: true,
661 particle_conservation: true,
662 symmetry_compatibility: true,
663 }
664 }
665
666 pub const fn rectangular() -> Self {
667 Self {
668 hermiticity: false,
669 particle_conservation: false,
670 symmetry_compatibility: true,
671 }
672 }
673
674 pub const fn none() -> Self {
679 Self {
680 hermiticity: false,
681 particle_conservation: false,
682 symmetry_compatibility: false,
683 }
684 }
685}
686
687impl Default for AssemblyChecks {
688 fn default() -> Self {
689 Self::all()
690 }
691}
692
693#[derive(Clone, Debug)]
694struct Triplet {
695 row: usize,
696 column: usize,
697 value: Complex64,
698}
699
700#[derive(Clone, Debug)]
701enum Storage {
702 Dense(Vec<Complex64>),
703 Csc {
704 column_offsets: Vec<usize>,
705 row_indices: Vec<usize>,
706 values: Vec<Complex64>,
707 },
708 Csr {
709 row_offsets: Vec<usize>,
710 column_indices: Vec<usize>,
711 values: Vec<Complex64>,
712 },
713 Dia {
714 offsets: Vec<isize>,
715 values_by_row: Vec<Complex64>,
716 },
717 MatrixFree(Vec<Triplet>),
718}
719
720#[derive(Clone, Debug)]
722pub struct Operator {
723 shape: (usize, usize),
724 format: MatrixFormat,
725 storage: Storage,
726 real: bool,
727}
728
729impl Operator {
730 pub fn from_dense(
732 rows: usize,
733 columns: usize,
734 values_row_major: Vec<Complex64>,
735 ) -> Result<Self> {
736 if values_row_major.len() != rows.saturating_mul(columns) {
737 return Err(QmbedError::DimensionMismatch(format!(
738 "dense storage has {} entries for shape ({rows}, {columns})",
739 values_row_major.len()
740 )));
741 }
742 let storage = Storage::Dense(values_row_major);
743 Ok(Self {
744 shape: (rows, columns),
745 format: MatrixFormat::Dense,
746 real: storage_is_real(&storage),
747 storage,
748 })
749 }
750
751 pub fn from_triplets(
756 rows: usize,
757 columns: usize,
758 triplets: impl IntoIterator<Item = (usize, usize, Complex64)>,
759 format: MatrixFormat,
760 ) -> Result<Self> {
761 if format == MatrixFormat::Dense {
762 let mut values = vec![Complex64::new(0.0, 0.0); rows.saturating_mul(columns)];
763 for (row, column, value) in triplets {
764 if row >= rows
765 || column >= columns
766 || !value.re.is_finite()
767 || !value.im.is_finite()
768 {
769 return Err(QmbedError::InvalidCoupling(
770 "triplet index is out of bounds or its value is non-finite".into(),
771 ));
772 }
773 values[row * columns + column] += value;
774 }
775 return Self::from_dense(rows, columns, values);
776 }
777 let mut accumulated = HashMap::new();
778 for (row, column, value) in triplets {
779 if row >= rows || column >= columns || !value.re.is_finite() || !value.im.is_finite() {
780 return Err(QmbedError::InvalidCoupling(
781 "triplet index is out of bounds or its value is non-finite".into(),
782 ));
783 }
784 *accumulated
785 .entry((row, column))
786 .or_insert(Complex64::new(0.0, 0.0)) += value;
787 }
788 let mut entries: Vec<_> = accumulated
789 .into_iter()
790 .filter_map(|((row, column), value)| {
791 (value.norm() > f64::EPSILON).then_some(Triplet { row, column, value })
792 })
793 .collect();
794 match format {
795 MatrixFormat::Csc => entries.sort_by_key(|entry| (entry.column, entry.row)),
796 MatrixFormat::Csr => entries.sort_by_key(|entry| (entry.row, entry.column)),
797 MatrixFormat::Dia | MatrixFormat::MatrixFree => {
798 entries.sort_by_key(|entry| (entry.row, entry.column));
799 }
800 MatrixFormat::Dense => unreachable!(),
801 }
802 let shape = (rows, columns);
803 let storage = match format {
804 MatrixFormat::Csc => csc_storage(&entries, columns),
805 MatrixFormat::Csr => csr_storage(&entries, rows),
806 MatrixFormat::Dia => dia_storage(&entries, rows),
807 MatrixFormat::MatrixFree => Storage::MatrixFree(entries),
808 MatrixFormat::Dense => unreachable!(),
809 };
810 Ok(Self {
811 shape,
812 format,
813 real: storage_is_real(&storage),
814 storage,
815 })
816 }
817
818 pub fn to_dense(&self) -> Vec<Complex64> {
820 if let Storage::Dense(values) = &self.storage {
821 return values.clone();
822 }
823 let mut dense = vec![Complex64::new(0.0, 0.0); self.shape.0 * self.shape.1];
824 match &self.storage {
825 Storage::Dense(_) => unreachable!(),
826 Storage::Csc {
827 column_offsets,
828 row_indices,
829 values,
830 } => {
831 for column in 0..self.shape.1 {
832 for position in column_offsets[column]..column_offsets[column + 1] {
833 dense[row_indices[position] * self.shape.1 + column] = values[position];
834 }
835 }
836 }
837 Storage::Csr {
838 row_offsets,
839 column_indices,
840 values,
841 } => {
842 for row in 0..self.shape.0 {
843 for position in row_offsets[row]..row_offsets[row + 1] {
844 dense[row * self.shape.1 + column_indices[position]] = values[position];
845 }
846 }
847 }
848 Storage::Dia {
849 offsets,
850 values_by_row,
851 } => {
852 for (diagonal, &offset) in offsets.iter().enumerate() {
853 for row in 0..self.shape.0 {
854 let Some(column) = row.checked_add_signed(-offset) else {
855 continue;
856 };
857 if column < self.shape.1 {
858 dense[row * self.shape.1 + column] =
859 values_by_row[diagonal * self.shape.0 + row];
860 }
861 }
862 }
863 }
864 Storage::MatrixFree(entries) => {
865 for entry in entries {
866 dense[entry.row * self.shape.1 + entry.column] = entry.value;
867 }
868 }
869 }
870 dense
871 }
872
873 pub fn conjugated(&self) -> Result<Self> {
875 Self::from_triplets(
876 self.shape.0,
877 self.shape.1,
878 self.triplets()
879 .into_iter()
880 .map(|(row, column, value)| (row, column, value.conj())),
881 self.format,
882 )
883 }
884
885 pub fn right_apply(&self, input: &[Complex64]) -> Result<Vec<Complex64>> {
887 if input.len() != self.shape.0 {
888 return Err(QmbedError::DimensionMismatch(
889 "right-apply input must match the operator row count".into(),
890 ));
891 }
892 let mut output = vec![Complex64::new(0.0, 0.0); self.shape.1];
893 self.apply_transpose(input, &mut output)?;
894 Ok(output)
895 }
896
897 pub fn memory_bytes(&self) -> usize {
899 let complex = std::mem::size_of::<Complex64>();
900 let index = std::mem::size_of::<usize>();
901 match &self.storage {
902 Storage::Dense(values) => values.len() * complex,
903 Storage::Csc {
904 column_offsets,
905 row_indices,
906 values,
907 } => column_offsets.len() * index + row_indices.len() * index + values.len() * complex,
908 Storage::Csr {
909 row_offsets,
910 column_indices,
911 values,
912 } => row_offsets.len() * index + column_indices.len() * index + values.len() * complex,
913 Storage::Dia {
914 offsets,
915 values_by_row,
916 } => offsets.len() * std::mem::size_of::<isize>() + values_by_row.len() * complex,
917 Storage::MatrixFree(entries) => entries.len() * std::mem::size_of::<Triplet>(),
918 }
919 }
920
921 pub fn nnz(&self) -> usize {
923 match &self.storage {
924 Storage::Dense(values) => values
925 .iter()
926 .filter(|value| value.norm() > f64::EPSILON)
927 .count(),
928 Storage::Csc { values, .. } | Storage::Csr { values, .. } => values.len(),
929 Storage::Dia { values_by_row, .. } => values_by_row
930 .iter()
931 .filter(|value| value.norm() > f64::EPSILON)
932 .count(),
933 Storage::MatrixFree(entries) => entries.len(),
934 }
935 }
936
937 pub fn triplets(&self) -> Vec<(usize, usize, Complex64)> {
939 let mut entries = match &self.storage {
940 Storage::Dense(values) => values
941 .iter()
942 .enumerate()
943 .filter_map(|(index, value)| {
944 (value.norm() > f64::EPSILON).then_some((
945 index / self.shape.1,
946 index % self.shape.1,
947 *value,
948 ))
949 })
950 .collect(),
951 Storage::Csc {
952 column_offsets,
953 row_indices,
954 values,
955 } => {
956 let mut entries = Vec::with_capacity(values.len());
957 for column in 0..self.shape.1 {
958 for position in column_offsets[column]..column_offsets[column + 1] {
959 entries.push((row_indices[position], column, values[position]));
960 }
961 }
962 entries
963 }
964 Storage::Csr {
965 row_offsets,
966 column_indices,
967 values,
968 } => {
969 let mut entries = Vec::with_capacity(values.len());
970 for row in 0..self.shape.0 {
971 for position in row_offsets[row]..row_offsets[row + 1] {
972 entries.push((row, column_indices[position], values[position]));
973 }
974 }
975 entries
976 }
977 Storage::Dia {
978 offsets,
979 values_by_row,
980 } => {
981 let mut entries = Vec::new();
982 for (diagonal, &offset) in offsets.iter().enumerate() {
983 for row in 0..self.shape.0 {
984 let Some(column) = row.checked_add_signed(-offset) else {
985 continue;
986 };
987 if column < self.shape.1 {
988 let value = values_by_row[diagonal * self.shape.0 + row];
989 if value.norm() > f64::EPSILON {
990 entries.push((row, column, value));
991 }
992 }
993 }
994 }
995 entries
996 }
997 Storage::MatrixFree(entries) => entries
998 .iter()
999 .map(|entry| (entry.row, entry.column, entry.value))
1000 .collect(),
1001 };
1002 entries.sort_by_key(|(row, column, _)| (*row, *column));
1003 entries
1004 }
1005
1006 pub fn converted(&self, format: MatrixFormat) -> Result<Self> {
1008 if format == self.format {
1009 return Ok(self.clone());
1010 }
1011 Self::from_triplets(self.shape.0, self.shape.1, self.triplets(), format)
1012 }
1013
1014 pub fn diagonal(&self) -> Vec<Complex64> {
1015 let dimension = self.shape.0.min(self.shape.1);
1016 (0..dimension)
1017 .map(|index| self.value_at(index, index))
1018 .collect()
1019 }
1020
1021 pub fn trace(&self) -> Result<Complex64> {
1022 if self.shape.0 != self.shape.1 {
1023 return Err(QmbedError::DimensionMismatch(
1024 "operator trace requires a square operator".into(),
1025 ));
1026 }
1027 Ok(self.diagonal().into_iter().sum())
1028 }
1029
1030 pub fn scaled(&self, coefficient: impl Into<Complex64>) -> Result<Self> {
1031 let coefficient = coefficient.into();
1032 if !coefficient.re.is_finite() || !coefficient.im.is_finite() {
1033 return Err(QmbedError::InvalidOptions(
1034 "operator scale must be finite".into(),
1035 ));
1036 }
1037 Self::from_triplets(
1038 self.shape.0,
1039 self.shape.1,
1040 self.triplets()
1041 .into_iter()
1042 .map(|(row, column, value)| (row, column, coefficient * value)),
1043 self.format,
1044 )
1045 }
1046
1047 pub fn add(&self, right: &Self) -> Result<Self> {
1048 self.combine(right, Complex64::new(1.0, 0.0))
1049 }
1050
1051 pub fn subtract(&self, right: &Self) -> Result<Self> {
1052 self.combine(right, Complex64::new(-1.0, 0.0))
1053 }
1054
1055 fn combine(&self, right: &Self, right_scale: Complex64) -> Result<Self> {
1056 if self.shape != right.shape {
1057 return Err(QmbedError::DimensionMismatch(
1058 "operator addition requires equal shapes".into(),
1059 ));
1060 }
1061 let entries = self.triplets().into_iter().chain(
1062 right
1063 .triplets()
1064 .into_iter()
1065 .map(|(row, column, value)| (row, column, right_scale * value)),
1066 );
1067 Self::from_triplets(self.shape.0, self.shape.1, entries, self.format)
1068 }
1069
1070 pub fn product(&self, right: &Self) -> Result<Self> {
1072 if self.shape.1 != right.shape.0 {
1073 return Err(QmbedError::DimensionMismatch(
1074 "operator product has incompatible inner dimensions".into(),
1075 ));
1076 }
1077 let mut right_by_row = vec![Vec::new(); right.shape.0];
1078 for (row, column, value) in right.triplets() {
1079 right_by_row[row].push((column, value));
1080 }
1081 let mut accumulated = HashMap::new();
1082 for (row, middle, left_value) in self.triplets() {
1083 for &(column, right_value) in &right_by_row[middle] {
1084 *accumulated
1085 .entry((row, column))
1086 .or_insert(Complex64::new(0.0, 0.0)) += left_value * right_value;
1087 }
1088 }
1089 Self::from_triplets(
1090 self.shape.0,
1091 right.shape.1,
1092 accumulated
1093 .into_iter()
1094 .map(|((row, column), value)| (row, column, value)),
1095 self.format,
1096 )
1097 }
1098
1099 pub fn projected_by(&self, projector: &Self) -> Result<Self> {
1106 if self.shape.0 != self.shape.1 || projector.shape.0 != self.shape.0 {
1107 return Err(QmbedError::DimensionMismatch(
1108 "operator projection requires a square source and matching projector rows".into(),
1109 ));
1110 }
1111 let applied = self.product(projector)?;
1112 projector.adjoint()?.product(&applied)
1113 }
1114
1115 pub fn pow(&self, exponent: u32) -> Result<Self> {
1116 if self.shape.0 != self.shape.1 {
1117 return Err(QmbedError::DimensionMismatch(
1118 "operator powers require a square operator".into(),
1119 ));
1120 }
1121 let dimension = self.shape.0;
1122 let mut result = Self::from_triplets(
1123 dimension,
1124 dimension,
1125 (0..dimension).map(|index| (index, index, Complex64::new(1.0, 0.0))),
1126 self.format,
1127 )?;
1128 let mut base = self.clone();
1129 let mut remaining = exponent;
1130 while remaining > 0 {
1131 if remaining & 1 == 1 {
1132 result = result.product(&base)?;
1133 }
1134 remaining >>= 1;
1135 if remaining > 0 {
1136 base = base.product(&base)?;
1137 }
1138 }
1139 result.converted(self.format)
1140 }
1141
1142 pub fn adjoint(&self) -> Result<Self> {
1143 Self::from_triplets(
1144 self.shape.1,
1145 self.shape.0,
1146 self.triplets()
1147 .into_iter()
1148 .map(|(row, column, value)| (column, row, value.conj())),
1149 self.format,
1150 )
1151 }
1152
1153 pub fn transpose(&self) -> Result<Self> {
1154 Self::from_triplets(
1155 self.shape.1,
1156 self.shape.0,
1157 self.triplets()
1158 .into_iter()
1159 .map(|(row, column, value)| (column, row, value)),
1160 self.format,
1161 )
1162 }
1163
1164 pub fn rotated(&self, unitary: &Self, tolerance: f64) -> Result<Self> {
1166 if self.shape.0 != self.shape.1 || unitary.shape != self.shape {
1167 return Err(QmbedError::DimensionMismatch(
1168 "operator rotation requires equal square shapes".into(),
1169 ));
1170 }
1171 if !tolerance.is_finite() || tolerance <= 0.0 {
1172 return Err(QmbedError::InvalidOptions(
1173 "unitarity tolerance must be positive".into(),
1174 ));
1175 }
1176 let identity = unitary.adjoint()?.product(unitary)?;
1177 for row in 0..self.shape.0 {
1178 for column in 0..self.shape.1 {
1179 let expected = if row == column { 1.0 } else { 0.0 };
1180 if (identity.value_at(row, column) - expected).norm() > tolerance {
1181 return Err(QmbedError::InvalidOptions(
1182 "rotation matrix must be unitary".into(),
1183 ));
1184 }
1185 }
1186 }
1187 unitary.adjoint()?.product(self)?.product(unitary)
1188 }
1189
1190 fn value_at(&self, row: usize, column: usize) -> Complex64 {
1191 match &self.storage {
1192 Storage::Dense(values) => values[row * self.shape.1 + column],
1193 Storage::Csc {
1194 column_offsets,
1195 row_indices,
1196 values,
1197 } => {
1198 let range = column_offsets[column]..column_offsets[column + 1];
1199 row_indices[range.clone()]
1200 .binary_search(&row)
1201 .map_or(Complex64::new(0.0, 0.0), |position| {
1202 values[range.start + position]
1203 })
1204 }
1205 Storage::Csr {
1206 row_offsets,
1207 column_indices,
1208 values,
1209 } => {
1210 let range = row_offsets[row]..row_offsets[row + 1];
1211 column_indices[range.clone()]
1212 .binary_search(&column)
1213 .map_or(Complex64::new(0.0, 0.0), |position| {
1214 values[range.start + position]
1215 })
1216 }
1217 Storage::Dia {
1218 offsets,
1219 values_by_row,
1220 } => {
1221 let offset = row as isize - column as isize;
1222 offsets
1223 .binary_search(&offset)
1224 .map_or(Complex64::new(0.0, 0.0), |diagonal| {
1225 values_by_row[diagonal * self.shape.0 + row]
1226 })
1227 }
1228 Storage::MatrixFree(entries) => entries
1229 .binary_search_by_key(&(row, column), |entry| (entry.row, entry.column))
1230 .map_or(Complex64::new(0.0, 0.0), |position| entries[position].value),
1231 }
1232 }
1233
1234 fn entry_is_hermitian(
1235 &self,
1236 row: usize,
1237 column: usize,
1238 value: Complex64,
1239 tolerance: f64,
1240 ) -> bool {
1241 (value - self.value_at(column, row).conj()).norm() <= tolerance
1242 }
1243
1244 fn apply_real_coefficients(&self, input: &[Complex64], output: &mut [Complex64]) {
1245 match &self.storage {
1246 Storage::Dense(values) => {
1247 for row in 0..self.shape.0 {
1248 let target = &mut output[row];
1249 for column in 0..self.shape.1 {
1250 let coefficient = values[row * self.shape.1 + column].re;
1251 target.re += coefficient * input[column].re;
1252 target.im += coefficient * input[column].im;
1253 }
1254 }
1255 }
1256 Storage::Csc {
1257 column_offsets,
1258 row_indices,
1259 values,
1260 } => {
1261 for column in 0..self.shape.1 {
1262 let input_value = input[column];
1263 for position in column_offsets[column]..column_offsets[column + 1] {
1264 let target = &mut output[row_indices[position]];
1265 let coefficient = values[position].re;
1266 target.re += coefficient * input_value.re;
1267 target.im += coefficient * input_value.im;
1268 }
1269 }
1270 }
1271 Storage::Csr {
1272 row_offsets,
1273 column_indices,
1274 values,
1275 } => {
1276 for row in 0..self.shape.0 {
1277 let target = &mut output[row];
1278 for position in row_offsets[row]..row_offsets[row + 1] {
1279 let input_value = input[column_indices[position]];
1280 let coefficient = values[position].re;
1281 target.re += coefficient * input_value.re;
1282 target.im += coefficient * input_value.im;
1283 }
1284 }
1285 }
1286 Storage::Dia {
1287 offsets,
1288 values_by_row,
1289 } => {
1290 for (diagonal, &offset) in offsets.iter().enumerate() {
1291 for row in 0..self.shape.0 {
1292 let Some(column) = row.checked_add_signed(-offset) else {
1293 continue;
1294 };
1295 if column < self.shape.1 {
1296 let input_value = input[column];
1297 let coefficient = values_by_row[diagonal * self.shape.0 + row].re;
1298 output[row].re += coefficient * input_value.re;
1299 output[row].im += coefficient * input_value.im;
1300 }
1301 }
1302 }
1303 }
1304 Storage::MatrixFree(entries) => {
1305 for entry in entries {
1306 output[entry.row].re += entry.value.re * input[entry.column].re;
1307 output[entry.row].im += entry.value.re * input[entry.column].im;
1308 }
1309 }
1310 }
1311 }
1312
1313 pub fn is_hermitian(&self, tolerance: f64) -> bool {
1314 if self.shape.0 != self.shape.1 {
1315 return false;
1316 }
1317 match &self.storage {
1318 Storage::Dense(values) => {
1319 for row in 0..self.shape.0 {
1320 for column in 0..self.shape.1 {
1321 if !self.entry_is_hermitian(
1322 row,
1323 column,
1324 values[row * self.shape.1 + column],
1325 tolerance,
1326 ) {
1327 return false;
1328 }
1329 }
1330 }
1331 }
1332 Storage::Csc {
1333 column_offsets,
1334 row_indices,
1335 values,
1336 } => {
1337 for column in 0..self.shape.1 {
1338 for position in column_offsets[column]..column_offsets[column + 1] {
1339 if !self.entry_is_hermitian(
1340 row_indices[position],
1341 column,
1342 values[position],
1343 tolerance,
1344 ) {
1345 return false;
1346 }
1347 }
1348 }
1349 }
1350 Storage::Csr {
1351 row_offsets,
1352 column_indices,
1353 values,
1354 } => {
1355 for row in 0..self.shape.0 {
1356 for position in row_offsets[row]..row_offsets[row + 1] {
1357 if !self.entry_is_hermitian(
1358 row,
1359 column_indices[position],
1360 values[position],
1361 tolerance,
1362 ) {
1363 return false;
1364 }
1365 }
1366 }
1367 }
1368 Storage::Dia {
1369 offsets,
1370 values_by_row,
1371 } => {
1372 for (diagonal, &offset) in offsets.iter().enumerate() {
1373 for row in 0..self.shape.0 {
1374 let value = values_by_row[diagonal * self.shape.0 + row];
1375 if value.norm() <= f64::EPSILON {
1376 continue;
1377 }
1378 let Some(column) = row.checked_add_signed(-offset) else {
1379 continue;
1380 };
1381 if column < self.shape.1
1382 && !self.entry_is_hermitian(row, column, value, tolerance)
1383 {
1384 return false;
1385 }
1386 }
1387 }
1388 }
1389 Storage::MatrixFree(entries) => {
1390 for entry in entries {
1391 if !self.entry_is_hermitian(entry.row, entry.column, entry.value, tolerance) {
1392 return false;
1393 }
1394 }
1395 }
1396 }
1397 true
1398 }
1399}
1400
1401impl LinearOperator for Operator {
1402 fn shape(&self) -> (usize, usize) {
1403 self.shape
1404 }
1405
1406 fn format(&self) -> MatrixFormat {
1407 self.format
1408 }
1409
1410 fn is_real(&self) -> bool {
1411 self.real
1412 }
1413
1414 fn apply_real(&self, input: &[f64], output: &mut [f64]) -> Result<()> {
1415 if input.len() != self.shape.1 || output.len() != self.shape.0 {
1416 return Err(QmbedError::DimensionMismatch(format!(
1417 "shape {:?} requires real input length {} and output length {}, got {} and {}",
1418 self.shape,
1419 self.shape.1,
1420 self.shape.0,
1421 input.len(),
1422 output.len()
1423 )));
1424 }
1425 if !self.real {
1426 return Err(QmbedError::UnsupportedBackend(
1427 "real action requires an operator with real-valued storage".into(),
1428 ));
1429 }
1430 output.fill(0.0);
1431 match &self.storage {
1432 Storage::Dense(values) => {
1433 for row in 0..self.shape.0 {
1434 for column in 0..self.shape.1 {
1435 output[row] += values[row * self.shape.1 + column].re * input[column];
1436 }
1437 }
1438 }
1439 Storage::Csc {
1440 column_offsets,
1441 row_indices,
1442 values,
1443 } => {
1444 for column in 0..self.shape.1 {
1445 let input_value = input[column];
1446 for position in column_offsets[column]..column_offsets[column + 1] {
1447 output[row_indices[position]] += values[position].re * input_value;
1448 }
1449 }
1450 }
1451 Storage::Csr {
1452 row_offsets,
1453 column_indices,
1454 values,
1455 } => {
1456 for row in 0..self.shape.0 {
1457 for position in row_offsets[row]..row_offsets[row + 1] {
1458 output[row] += values[position].re * input[column_indices[position]];
1459 }
1460 }
1461 }
1462 Storage::Dia {
1463 offsets,
1464 values_by_row,
1465 } => {
1466 for (diagonal, &offset) in offsets.iter().enumerate() {
1467 for row in 0..self.shape.0 {
1468 let Some(column) = row.checked_add_signed(-offset) else {
1469 continue;
1470 };
1471 if column < self.shape.1 {
1472 output[row] +=
1473 values_by_row[diagonal * self.shape.0 + row].re * input[column];
1474 }
1475 }
1476 }
1477 }
1478 Storage::MatrixFree(entries) => {
1479 for entry in entries {
1480 output[entry.row] += entry.value.re * input[entry.column];
1481 }
1482 }
1483 }
1484 Ok(())
1485 }
1486
1487 fn apply(&self, input: &[Complex64], output: &mut [Complex64]) -> Result<()> {
1488 check_apply_shape(self.shape, input, output)?;
1489 output.fill(Complex64::new(0.0, 0.0));
1490 if self.real {
1491 self.apply_real_coefficients(input, output);
1492 return Ok(());
1493 }
1494 match &self.storage {
1495 Storage::Dense(values) => {
1496 for row in 0..self.shape.0 {
1497 for column in 0..self.shape.1 {
1498 output[row] += values[row * self.shape.1 + column] * input[column];
1499 }
1500 }
1501 }
1502 Storage::Csc {
1503 column_offsets,
1504 row_indices,
1505 values,
1506 } => {
1507 for column in 0..self.shape.1 {
1508 let input_value = input[column];
1509 for position in column_offsets[column]..column_offsets[column + 1] {
1510 output[row_indices[position]] += values[position] * input_value;
1511 }
1512 }
1513 }
1514 Storage::Csr {
1515 row_offsets,
1516 column_indices,
1517 values,
1518 } => {
1519 for row in 0..self.shape.0 {
1520 for position in row_offsets[row]..row_offsets[row + 1] {
1521 output[row] += values[position] * input[column_indices[position]];
1522 }
1523 }
1524 }
1525 Storage::Dia {
1526 offsets,
1527 values_by_row,
1528 } => {
1529 for (diagonal, &offset) in offsets.iter().enumerate() {
1530 for row in 0..self.shape.0 {
1531 let Some(column) = row.checked_add_signed(-offset) else {
1532 continue;
1533 };
1534 if column < self.shape.1 {
1535 output[row] +=
1536 values_by_row[diagonal * self.shape.0 + row] * input[column];
1537 }
1538 }
1539 }
1540 }
1541 Storage::MatrixFree(entries) => {
1542 for entry in entries {
1543 output[entry.row] += entry.value * input[entry.column];
1544 }
1545 }
1546 }
1547 Ok(())
1548 }
1549
1550 fn stored_triplets(&self) -> Result<Option<Vec<(usize, usize, Complex64)>>> {
1551 Ok(Some(self.triplets()))
1552 }
1553
1554 fn shifted_solver(&self, shift: f64) -> Result<Option<Box<dyn ShiftedLinearSolver>>> {
1555 if self.shape.0 != self.shape.1 || !shift.is_finite() {
1556 return Err(QmbedError::InvalidOptions(
1557 "shifted factorization requires a square operator and finite shift".into(),
1558 ));
1559 }
1560 let Storage::Csc {
1561 column_offsets,
1562 row_indices,
1563 values,
1564 } = &self.storage
1565 else {
1566 return Ok(None);
1567 };
1568 factor_shifted_csc(
1569 self.shape.0,
1570 column_offsets,
1571 row_indices,
1572 values,
1573 shift,
1574 self.real,
1575 )
1576 .map(Some)
1577 }
1578}
1579
1580fn csc_storage(entries: &[Triplet], columns: usize) -> Storage {
1581 let mut column_offsets = vec![0_usize; columns + 1];
1582 for entry in entries {
1583 column_offsets[entry.column + 1] += 1;
1584 }
1585 for column in 0..columns {
1586 column_offsets[column + 1] += column_offsets[column];
1587 }
1588 Storage::Csc {
1589 column_offsets,
1590 row_indices: entries.iter().map(|entry| entry.row).collect(),
1591 values: entries.iter().map(|entry| entry.value).collect(),
1592 }
1593}
1594
1595fn storage_is_real(storage: &Storage) -> bool {
1596 match storage {
1597 Storage::Dense(values)
1598 | Storage::Csc { values, .. }
1599 | Storage::Csr { values, .. }
1600 | Storage::Dia {
1601 values_by_row: values,
1602 ..
1603 } => values.iter().all(|value| value.im == 0.0),
1604 Storage::MatrixFree(entries) => entries.iter().all(|entry| entry.value.im == 0.0),
1605 }
1606}
1607
1608fn csr_storage(entries: &[Triplet], rows: usize) -> Storage {
1609 let mut row_offsets = vec![0_usize; rows + 1];
1610 for entry in entries {
1611 row_offsets[entry.row + 1] += 1;
1612 }
1613 for row in 0..rows {
1614 row_offsets[row + 1] += row_offsets[row];
1615 }
1616 Storage::Csr {
1617 row_offsets,
1618 column_indices: entries.iter().map(|entry| entry.column).collect(),
1619 values: entries.iter().map(|entry| entry.value).collect(),
1620 }
1621}
1622
1623fn dia_storage(entries: &[Triplet], rows: usize) -> Storage {
1624 let mut offsets: Vec<_> = entries
1625 .iter()
1626 .map(|entry| entry.row as isize - entry.column as isize)
1627 .collect();
1628 offsets.sort_unstable();
1629 offsets.dedup();
1630 let diagonal_index: HashMap<_, _> = offsets
1631 .iter()
1632 .copied()
1633 .enumerate()
1634 .map(|(index, offset)| (offset, index))
1635 .collect();
1636 let mut values_by_row = vec![Complex64::new(0.0, 0.0); offsets.len() * rows];
1637 for entry in entries {
1638 let offset = entry.row as isize - entry.column as isize;
1639 let diagonal = diagonal_index[&offset];
1640 values_by_row[diagonal * rows + entry.row] = entry.value;
1641 }
1642 Storage::Dia {
1643 offsets,
1644 values_by_row,
1645 }
1646}
1647
1648pub(crate) fn check_apply_shape(
1649 shape: (usize, usize),
1650 input: &[Complex64],
1651 output: &[Complex64],
1652) -> Result<()> {
1653 if input.len() != shape.1 || output.len() != shape.0 {
1654 return Err(QmbedError::DimensionMismatch(format!(
1655 "shape {shape:?} requires input length {} and output length {}, got {} and {}",
1656 shape.1,
1657 shape.0,
1658 input.len(),
1659 output.len()
1660 )));
1661 }
1662 Ok(())
1663}
1664
1665pub(crate) fn materialize_dense(
1666 operator: &(impl LinearOperator + ?Sized),
1667) -> Result<Vec<Complex64>> {
1668 let (rows, columns) = operator.shape();
1669 let mut dense = vec![Complex64::new(0.0, 0.0); rows * columns];
1670 let mut input = vec![Complex64::new(0.0, 0.0); columns];
1671 let mut output = vec![Complex64::new(0.0, 0.0); rows];
1672 for column in 0..columns {
1673 input.fill(Complex64::new(0.0, 0.0));
1674 input[column] = Complex64::new(1.0, 0.0);
1675 operator.apply(&input, &mut output)?;
1676 for row in 0..rows {
1677 dense[row * columns + column] = output[row];
1678 }
1679 }
1680 Ok(dense)
1681}
1682
1683pub fn matvec(
1687 operator: &(impl LinearOperator + ?Sized),
1688 input: &[Complex64],
1689 output: &mut [Complex64],
1690 coefficient: Complex64,
1691 overwrite: bool,
1692) -> Result<()> {
1693 if !coefficient.re.is_finite() || !coefficient.im.is_finite() {
1694 return Err(QmbedError::InvalidOptions(
1695 "matvec coefficient must be finite".into(),
1696 ));
1697 }
1698 if overwrite {
1699 operator.apply(input, output)?;
1700 for value in output {
1701 *value *= coefficient;
1702 }
1703 return Ok(());
1704 }
1705
1706 let mut contribution = vec![Complex64::new(0.0, 0.0); output.len()];
1707 operator.apply(input, &mut contribution)?;
1708 for (value, addition) in output.iter_mut().zip(contribution) {
1709 *value += coefficient * addition;
1710 }
1711 Ok(())
1712}
1713
1714pub fn matmat(
1716 operator: &(impl LinearOperator + ?Sized),
1717 columns: &[Vec<Complex64>],
1718) -> Result<Vec<Vec<Complex64>>> {
1719 let (rows, input_dimension) = operator.shape();
1720 columns
1721 .iter()
1722 .map(|column| {
1723 if column.len() != input_dimension {
1724 return Err(QmbedError::DimensionMismatch(
1725 "matrix column does not match the operator input dimension".into(),
1726 ));
1727 }
1728 let mut output = vec![Complex64::new(0.0, 0.0); rows];
1729 operator.apply(column, &mut output)?;
1730 Ok(output)
1731 })
1732 .collect()
1733}
1734
1735pub fn rmatvec(
1737 operator: &(impl LinearOperator + ?Sized),
1738 input: &[Complex64],
1739) -> Result<Vec<Complex64>> {
1740 let mut output = vec![Complex64::new(0.0, 0.0); operator.shape().1];
1741 operator.apply_transpose(input, &mut output)?;
1742 Ok(output)
1743}
1744
1745pub fn rmatmat(
1746 operator: &(impl LinearOperator + ?Sized),
1747 rows: &[Vec<Complex64>],
1748) -> Result<Vec<Vec<Complex64>>> {
1749 rows.iter().map(|row| rmatvec(operator, row)).collect()
1750}
1751
1752#[derive(Clone)]
1754pub struct MatVecPlan {
1755 operator: Arc<dyn LinearOperator>,
1756}
1757
1758impl std::fmt::Debug for MatVecPlan {
1759 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1760 formatter
1761 .debug_struct("MatVecPlan")
1762 .field("shape", &self.operator.shape())
1763 .finish()
1764 }
1765}
1766
1767impl MatVecPlan {
1768 pub fn new(operator: Arc<dyn LinearOperator>) -> Self {
1769 Self { operator }
1770 }
1771
1772 pub fn apply(
1773 &self,
1774 input: &[Complex64],
1775 output: &mut [Complex64],
1776 coefficient: Complex64,
1777 overwrite: bool,
1778 ) -> Result<()> {
1779 matvec(
1780 self.operator.as_ref(),
1781 input,
1782 output,
1783 coefficient,
1784 overwrite,
1785 )
1786 }
1787
1788 pub fn operator(&self) -> &Arc<dyn LinearOperator> {
1789 &self.operator
1790 }
1791}
1792
1793pub fn get_matvec_function(operator: Arc<dyn LinearOperator>) -> MatVecPlan {
1794 MatVecPlan::new(operator)
1795}
1796
1797#[derive(Clone)]
1799pub struct TransposedLinearOperator {
1800 operator: Arc<dyn LinearOperator>,
1801}
1802
1803impl TransposedLinearOperator {
1804 pub fn new(operator: Arc<dyn LinearOperator>) -> Self {
1805 Self { operator }
1806 }
1807}
1808
1809impl std::fmt::Debug for TransposedLinearOperator {
1810 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1811 formatter
1812 .debug_struct("TransposedLinearOperator")
1813 .field("shape", &self.shape())
1814 .finish()
1815 }
1816}
1817
1818impl LinearOperator for TransposedLinearOperator {
1819 fn shape(&self) -> (usize, usize) {
1820 let (rows, columns) = self.operator.shape();
1821 (columns, rows)
1822 }
1823
1824 fn format(&self) -> MatrixFormat {
1825 MatrixFormat::MatrixFree
1826 }
1827
1828 fn apply(&self, input: &[Complex64], output: &mut [Complex64]) -> Result<()> {
1829 self.operator.apply_transpose(input, output)
1830 }
1831
1832 fn apply_transpose(&self, input: &[Complex64], output: &mut [Complex64]) -> Result<()> {
1833 self.operator.apply(input, output)
1834 }
1835
1836 fn stored_triplets(&self) -> Result<Option<Vec<(usize, usize, Complex64)>>> {
1837 Ok(self.operator.stored_triplets()?.map(|entries| {
1838 entries
1839 .into_iter()
1840 .map(|(row, column, value)| (column, row, value))
1841 .collect()
1842 }))
1843 }
1844}
1845
1846#[derive(Clone)]
1848pub struct ConjugatedLinearOperator {
1849 operator: Arc<dyn LinearOperator>,
1850}
1851
1852impl ConjugatedLinearOperator {
1853 pub fn new(operator: Arc<dyn LinearOperator>) -> Self {
1854 Self { operator }
1855 }
1856}
1857
1858impl std::fmt::Debug for ConjugatedLinearOperator {
1859 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1860 formatter
1861 .debug_struct("ConjugatedLinearOperator")
1862 .field("shape", &self.shape())
1863 .finish()
1864 }
1865}
1866
1867impl LinearOperator for ConjugatedLinearOperator {
1868 fn shape(&self) -> (usize, usize) {
1869 self.operator.shape()
1870 }
1871
1872 fn format(&self) -> MatrixFormat {
1873 MatrixFormat::MatrixFree
1874 }
1875
1876 fn apply(&self, input: &[Complex64], output: &mut [Complex64]) -> Result<()> {
1877 let conjugated_input: Vec<_> = input.iter().map(|value| value.conj()).collect();
1878 self.operator.apply(&conjugated_input, output)?;
1879 for value in output {
1880 *value = value.conj();
1881 }
1882 Ok(())
1883 }
1884
1885 fn stored_triplets(&self) -> Result<Option<Vec<(usize, usize, Complex64)>>> {
1886 Ok(self.operator.stored_triplets()?.map(|entries| {
1887 entries
1888 .into_iter()
1889 .map(|(row, column, value)| (row, column, value.conj()))
1890 .collect()
1891 }))
1892 }
1893}
1894
1895#[derive(Clone)]
1897pub struct AdjointLinearOperator {
1898 operator: Arc<dyn LinearOperator>,
1899}
1900
1901impl AdjointLinearOperator {
1902 pub fn new(operator: Arc<dyn LinearOperator>) -> Self {
1903 Self { operator }
1904 }
1905}
1906
1907impl std::fmt::Debug for AdjointLinearOperator {
1908 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1909 formatter
1910 .debug_struct("AdjointLinearOperator")
1911 .field("shape", &self.shape())
1912 .finish()
1913 }
1914}
1915
1916impl LinearOperator for AdjointLinearOperator {
1917 fn shape(&self) -> (usize, usize) {
1918 let (rows, columns) = self.operator.shape();
1919 (columns, rows)
1920 }
1921
1922 fn format(&self) -> MatrixFormat {
1923 MatrixFormat::MatrixFree
1924 }
1925
1926 fn apply(&self, input: &[Complex64], output: &mut [Complex64]) -> Result<()> {
1927 self.operator.apply_adjoint(input, output)
1928 }
1929
1930 fn stored_triplets(&self) -> Result<Option<Vec<(usize, usize, Complex64)>>> {
1931 Ok(self.operator.stored_triplets()?.map(|entries| {
1932 entries
1933 .into_iter()
1934 .map(|(row, column, value)| (column, row, value.conj()))
1935 .collect()
1936 }))
1937 }
1938}
1939
1940pub struct OperatorBuilder<'a, Source, Target>
1942where
1943 Source: Basis,
1944 Target: Basis<State = Source::State>,
1945{
1946 source: &'a Source,
1947 target: &'a Target,
1948 terms: Vec<OperatorSpec>,
1949 checks: AssemblyChecks,
1950}
1951
1952#[derive(Clone, Debug, Eq, Hash, PartialEq)]
1953struct ParticleConservationComponent {
1954 cartesian_degree: usize,
1959 operator: String,
1960 sites: Vec<usize>,
1961}
1962
1963fn definite_number_change_components(operator: &str) -> Result<Vec<(usize, String, Complex64)>> {
1964 let mut components = vec![(0_usize, String::new(), Complex64::new(1.0, 0.0))];
1965 for symbol in operator.chars() {
1966 match symbol {
1967 'x' | 'y' => {
1968 let mut expanded = Vec::with_capacity(components.len().saturating_mul(2));
1969 for (degree, prefix, coefficient) in components {
1970 let mut raised = prefix.clone();
1971 raised.push('+');
1972 let mut lowered = prefix;
1973 lowered.push('-');
1974 let (raise_phase, lower_phase) = if symbol == 'x' {
1975 (Complex64::new(1.0, 0.0), Complex64::new(1.0, 0.0))
1976 } else {
1977 (Complex64::new(0.0, -1.0), Complex64::new(0.0, 1.0))
1978 };
1979 expanded.push((degree + 1, raised, coefficient * raise_phase));
1980 expanded.push((degree + 1, lowered, coefficient * lower_phase));
1981 }
1982 components = expanded;
1983 }
1984 'I' | 'n' | 'z' | '+' | '-' | '|' => {
1985 for (_, prefix, _) in &mut components {
1986 prefix.push(symbol);
1987 }
1988 }
1989 _ => return Err(QmbedError::InvalidOperator(operator.into())),
1990 }
1991 }
1992 Ok(components)
1993}
1994
1995pub fn operator_sum_preserves_particle_sector<B>(basis: &B, terms: &[OperatorSpec]) -> Result<bool>
2003where
2004 B: Basis,
2005{
2006 let mut components = HashMap::<ParticleConservationComponent, Complex64>::new();
2007 for term in terms {
2008 for coupling in term.couplings() {
2009 for (cartesian_degree, operator, coefficient) in
2010 definite_number_change_components(term.operator())?
2011 {
2012 *components
2013 .entry(ParticleConservationComponent {
2014 cartesian_degree,
2015 operator,
2016 sites: coupling.sites.clone(),
2017 })
2018 .or_insert(Complex64::new(0.0, 0.0)) += coupling.coefficient * coefficient;
2019 }
2020 }
2021 }
2022 for (component, coefficient) in components {
2023 if coefficient.norm() > f64::EPSILON
2024 && !basis.operator_preserves_particle_sector_on_sites(
2025 &component.operator,
2026 &component.sites,
2027 )?
2028 {
2029 return Ok(false);
2030 }
2031 }
2032 Ok(true)
2033}
2034
2035impl<'a, BasisType> OperatorBuilder<'a, BasisType, BasisType>
2036where
2037 BasisType: Basis,
2038{
2039 pub fn on(basis: &'a BasisType) -> Self {
2041 Self {
2042 source: basis,
2043 target: basis,
2044 terms: Vec::new(),
2045 checks: AssemblyChecks::all(),
2046 }
2047 }
2048}
2049
2050impl<'a, Source, Target> OperatorBuilder<'a, Source, Target>
2051where
2052 Source: Basis,
2053 Target: Basis<State = Source::State>,
2054{
2055 pub fn between(source: &'a Source, target: &'a Target) -> Self {
2060 Self {
2061 source,
2062 target,
2063 terms: Vec::new(),
2064 checks: AssemblyChecks::rectangular(),
2065 }
2066 }
2067
2068 pub fn terms(mut self, terms: impl IntoIterator<Item = OperatorSpec>) -> Self {
2070 self.terms.extend(terms);
2071 self
2072 }
2073
2074 pub fn term(mut self, term: OperatorSpec) -> Self {
2076 self.terms.push(term);
2077 self
2078 }
2079
2080 pub const fn checks(mut self, checks: AssemblyChecks) -> Self {
2082 self.checks = checks;
2083 self
2084 }
2085
2086 pub fn build(self, format: MatrixFormat) -> Result<Operator> {
2092 let shape = (self.target.len(), self.source.len());
2093 if self.checks.particle_conservation
2094 && (!operator_sum_preserves_particle_sector(self.source, &self.terms)?
2095 || !operator_sum_preserves_particle_sector(self.target, &self.terms)?)
2096 {
2097 return Err(QmbedError::InvalidSector(
2098 "operator sum does not preserve the selected particle sector".into(),
2099 ));
2100 }
2101 let local_capacity = self.terms.iter().map(|term| term.couplings().len()).sum();
2110 let mut column_entries = Vec::<(usize, Complex64)>::with_capacity(local_capacity);
2111 let mut entries = Vec::<Triplet>::new();
2112 let mut csc_column_offsets = Vec::new();
2113 let mut csc_row_indices = Vec::new();
2114 let mut csc_values = Vec::new();
2115 if format == MatrixFormat::Csc {
2116 csc_column_offsets.reserve(shape.1 + 1);
2117 csc_column_offsets.push(0);
2118 }
2119 for column in 0..self.source.len() {
2120 column_entries.clear();
2121 let source_state = self.source.state(column)?;
2122 let source_orbit_size = self.source.transition_orbit_size(source_state)?;
2123 for term in &self.terms {
2124 for coupling in term.couplings() {
2125 self.source.visit_preparsed_local_unreduced_transitions(
2126 source_state,
2127 term.operator(),
2128 term.symbols(),
2129 term.split(),
2130 &coupling.sites,
2131 |unreduced_target, local_amplitude| {
2132 let Some((row, reduction_amplitude)) = self
2133 .target
2134 .index_transition(unreduced_target, source_orbit_size)?
2135 else {
2136 return Ok(());
2137 };
2138 column_entries.push((
2139 row,
2140 coupling.coefficient * local_amplitude * reduction_amplitude,
2141 ));
2142 Ok(())
2143 },
2144 )?;
2145 }
2146 }
2147
2148 column_entries.sort_unstable_by_key(|(row, _)| *row);
2149 let mut position = 0;
2150 while position < column_entries.len() {
2151 let row = column_entries[position].0;
2152 let mut value = column_entries[position].1;
2153 position += 1;
2154 while position < column_entries.len() && column_entries[position].0 == row {
2155 value += column_entries[position].1;
2156 position += 1;
2157 }
2158 if value.norm() <= f64::EPSILON {
2159 continue;
2160 }
2161 if format == MatrixFormat::Csc {
2162 csc_row_indices.push(row);
2163 csc_values.push(value);
2164 } else {
2165 entries.push(Triplet { row, column, value });
2166 }
2167 }
2168 if format == MatrixFormat::Csc {
2169 csc_column_offsets.push(csc_row_indices.len());
2170 }
2171 }
2172 match format {
2173 MatrixFormat::Csc => {}
2174 MatrixFormat::Csr => entries.sort_by_key(|entry| (entry.row, entry.column)),
2175 _ => entries.sort_by_key(|entry| (entry.row, entry.column)),
2176 }
2177 if format == MatrixFormat::Dia
2178 && entries
2179 .iter()
2180 .map(|entry| entry.row as isize - entry.column as isize)
2181 .collect::<std::collections::HashSet<_>>()
2182 .len()
2183 > shape.0.min(shape.1).saturating_mul(2).max(1)
2184 {
2185 return Err(QmbedError::UnsupportedBackend(
2186 "the requested operator is not usefully diagonal-banded".into(),
2187 ));
2188 }
2189 let storage = match format {
2190 MatrixFormat::Dense => {
2191 let mut dense = vec![Complex64::new(0.0, 0.0); shape.0 * shape.1];
2192 for entry in &entries {
2193 dense[entry.row * shape.1 + entry.column] = entry.value;
2194 }
2195 Storage::Dense(dense)
2196 }
2197 MatrixFormat::Csc => Storage::Csc {
2198 column_offsets: csc_column_offsets,
2199 row_indices: csc_row_indices,
2200 values: csc_values,
2201 },
2202 MatrixFormat::Csr => csr_storage(&entries, shape.0),
2203 MatrixFormat::Dia => dia_storage(&entries, shape.0),
2204 MatrixFormat::MatrixFree => Storage::MatrixFree(entries),
2205 };
2206 let operator = Operator {
2207 shape,
2208 format,
2209 real: storage_is_real(&storage),
2210 storage,
2211 };
2212 if self.checks.hermiticity && !operator.is_hermitian(1.0e-12) {
2213 return Err(QmbedError::NonHermitian);
2214 }
2215 Ok(operator)
2216 }
2217
2218 pub fn build_dynamic(
2220 self,
2221 dynamic_terms: impl IntoIterator<Item = DynamicTerm>,
2222 format: MatrixFormat,
2223 ) -> Result<Hamiltonian<Dynamic>> {
2224 let Self {
2225 source,
2226 target,
2227 terms,
2228 checks,
2229 } = self;
2230 if source.len() != target.len() {
2231 return Err(QmbedError::DimensionMismatch(
2232 "a Hamiltonian must be square".into(),
2233 ));
2234 }
2235 let static_part = Self {
2236 source,
2237 target,
2238 terms,
2239 checks,
2240 }
2241 .build(format)?;
2242 let component_checks = AssemblyChecks {
2243 hermiticity: false,
2244 particle_conservation: checks.particle_conservation,
2245 symmetry_compatibility: checks.symmetry_compatibility,
2246 };
2247 let mut components = Vec::new();
2248 for dynamic_term in dynamic_terms {
2249 let (term, drive) = dynamic_term.into_parts();
2250 let operator = Self {
2251 source,
2252 target,
2253 terms: vec![term],
2254 checks: component_checks,
2255 }
2256 .build(format)?;
2257 components.push(DynamicComponent { operator, drive });
2258 }
2259 Hamiltonian::<Dynamic>::new(static_part, components)
2260 }
2261}
2262
2263#[derive(Clone, Debug, PartialEq)]
2264pub struct BraKetTransition<State> {
2265 pub bra: State,
2266 pub ket: State,
2267 pub matrix_element: Complex64,
2268}
2269
2270pub fn bra_ket_transitions<B>(
2272 basis: &B,
2273 operator: &str,
2274 sites: &[usize],
2275 coefficient: impl Into<Complex64>,
2276 kets: impl IntoIterator<Item = B::State>,
2277) -> Result<Vec<BraKetTransition<B::State>>>
2278where
2279 B: Basis,
2280{
2281 let coefficient = coefficient.into();
2282 if !coefficient.re.is_finite() || !coefficient.im.is_finite() {
2283 return Err(QmbedError::InvalidCoupling(
2284 "transition-table coefficient must be finite".into(),
2285 ));
2286 }
2287 let mut transitions = Vec::new();
2288 for ket in kets {
2289 basis.visit_local_unreduced_transitions(ket, operator, sites, |bra, amplitude| {
2290 let matrix_element = coefficient * amplitude;
2291 if matrix_element.norm() > f64::EPSILON {
2292 transitions.push(BraKetTransition {
2293 bra,
2294 ket,
2295 matrix_element,
2296 });
2297 }
2298 Ok(())
2299 })?;
2300 }
2301 Ok(transitions)
2302}
2303
2304pub fn apply_sector_shift<Source, Target>(
2306 source: &Source,
2307 target: &Target,
2308 terms: &[OperatorSpec],
2309 input: &[Complex64],
2310 output: &mut [Complex64],
2311) -> Result<()>
2312where
2313 Source: Basis,
2314 Target: Basis<State = Source::State>,
2315{
2316 if input.len() != source.len() || output.len() != target.len() {
2317 return Err(QmbedError::DimensionMismatch(
2318 "sector-shift state dimensions do not match source and target bases".into(),
2319 ));
2320 }
2321 output.fill(Complex64::new(0.0, 0.0));
2322 for (column, input_value) in input.iter().copied().enumerate() {
2323 if input_value.norm() <= f64::EPSILON {
2324 continue;
2325 }
2326 let source_state = source.state(column)?;
2327 let source_orbit_size = source.transition_orbit_size(source_state)?;
2328 for term in terms {
2329 for coupling in term.couplings() {
2330 source.visit_local_unreduced_transitions(
2331 source_state,
2332 term.operator(),
2333 &coupling.sites,
2334 |unreduced_target, local_amplitude| {
2335 let Some((row, reduction_amplitude)) =
2336 target.index_transition(unreduced_target, source_orbit_size)?
2337 else {
2338 return Ok(());
2339 };
2340 output[row] += input_value
2341 * coupling.coefficient
2342 * local_amplitude
2343 * reduction_amplitude;
2344 Ok(())
2345 },
2346 )?;
2347 }
2348 }
2349 }
2350 Ok(())
2351}
2352
2353type DriveFunction = Arc<dyn Fn(f64) -> Complex64 + Send + Sync>;
2354
2355#[derive(Clone)]
2357pub struct DynamicTerm {
2358 term: OperatorSpec,
2359 drive: DriveFunction,
2360}
2361
2362impl std::fmt::Debug for DynamicTerm {
2363 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2364 formatter
2365 .debug_struct("DynamicTerm")
2366 .field("term", &self.term)
2367 .field("drive", &"<callable>")
2368 .finish()
2369 }
2370}
2371
2372impl DynamicTerm {
2373 pub fn new<F>(term: OperatorSpec, drive: F) -> Self
2374 where
2375 F: Fn(f64) -> Complex64 + Send + Sync + 'static,
2376 {
2377 Self {
2378 term,
2379 drive: Arc::new(drive),
2380 }
2381 }
2382
2383 pub fn coefficient_at(&self, time: f64) -> Result<Complex64> {
2384 finite_drive_value(time, (self.drive)(time))
2385 }
2386
2387 fn into_parts(self) -> (OperatorSpec, DriveFunction) {
2388 (self.term, self.drive)
2389 }
2390}
2391
2392#[derive(Clone)]
2393pub struct DynamicComponent {
2394 operator: Operator,
2395 drive: DriveFunction,
2396}
2397
2398impl std::fmt::Debug for DynamicComponent {
2399 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2400 formatter
2401 .debug_struct("DynamicComponent")
2402 .field("operator", &self.operator)
2403 .field("drive", &"<callable>")
2404 .finish()
2405 }
2406}
2407
2408impl DynamicComponent {
2409 pub fn new<F>(operator: Operator, drive: F) -> Self
2410 where
2411 F: Fn(f64) -> Complex64 + Send + Sync + 'static,
2412 {
2413 Self {
2414 operator,
2415 drive: Arc::new(drive),
2416 }
2417 }
2418}
2419
2420#[derive(Clone, Copy, Debug)]
2421pub struct Static;
2422
2423#[derive(Clone, Copy, Debug)]
2424pub struct Dynamic;
2425
2426#[derive(Clone, Debug)]
2428pub struct Hamiltonian<Kind = Static> {
2429 static_part: Operator,
2430 dynamic_parts: Vec<DynamicComponent>,
2431 marker: PhantomData<Kind>,
2432}
2433
2434impl Hamiltonian<Static> {
2435 pub fn new(operator: Operator) -> Result<Self> {
2436 let shape = operator.shape();
2437 if shape.0 != shape.1 {
2438 return Err(QmbedError::DimensionMismatch(
2439 "a Hamiltonian must be square".into(),
2440 ));
2441 }
2442 Ok(Self {
2443 static_part: operator,
2444 dynamic_parts: Vec::new(),
2445 marker: PhantomData,
2446 })
2447 }
2448
2449 pub fn operator(&self) -> &Operator {
2450 &self.static_part
2451 }
2452
2453 pub fn transpose(&self) -> Result<Self> {
2454 Self::new(self.static_part.transpose()?)
2455 }
2456
2457 pub fn conjugated(&self) -> Result<Self> {
2458 Self::new(self.static_part.conjugated()?)
2459 }
2460
2461 pub fn adjoint(&self) -> Result<Self> {
2462 Self::new(self.static_part.adjoint()?)
2463 }
2464
2465 pub fn scaled(&self, coefficient: impl Into<Complex64>) -> Result<Self> {
2466 Self::new(self.static_part.scaled(coefficient)?)
2467 }
2468}
2469
2470impl Hamiltonian<Dynamic> {
2471 pub fn new(static_part: Operator, dynamic_parts: Vec<DynamicComponent>) -> Result<Self> {
2472 let shape = static_part.shape();
2473 if shape.0 != shape.1
2474 || dynamic_parts
2475 .iter()
2476 .any(|component| component.operator.shape() != shape)
2477 {
2478 return Err(QmbedError::DimensionMismatch(
2479 "all Hamiltonian components must share one square shape".into(),
2480 ));
2481 }
2482 Ok(Self {
2483 static_part,
2484 dynamic_parts,
2485 marker: PhantomData,
2486 })
2487 }
2488
2489 pub fn evaluate(&self, time: f64, format: MatrixFormat) -> Result<Operator> {
2490 if !time.is_finite() {
2491 return Err(QmbedError::InvalidOptions(
2492 "evaluation time must be finite".into(),
2493 ));
2494 }
2495 let shape = self.static_part.shape();
2496 let mut entries = self.static_part.triplets();
2497 for component in &self.dynamic_parts {
2498 let coefficient = finite_drive_value(time, (component.drive)(time))?;
2499 entries.extend(
2500 component
2501 .operator
2502 .triplets()
2503 .into_iter()
2504 .map(|(row, column, value)| (row, column, coefficient * value)),
2505 );
2506 }
2507 Operator::from_triplets(shape.0, shape.1, entries, format)
2508 }
2509
2510 pub fn dynamic_components(&self) -> usize {
2511 self.dynamic_parts.len()
2512 }
2513
2514 pub fn static_part(&self) -> &Operator {
2515 &self.static_part
2516 }
2517
2518 pub fn transpose(&self) -> Result<Self> {
2519 Self::new(
2520 self.static_part.transpose()?,
2521 self.dynamic_parts
2522 .iter()
2523 .map(|component| {
2524 Ok(DynamicComponent {
2525 operator: component.operator.transpose()?,
2526 drive: component.drive.clone(),
2527 })
2528 })
2529 .collect::<Result<_>>()?,
2530 )
2531 }
2532
2533 pub fn conjugated(&self) -> Result<Self> {
2534 Self::new(
2535 self.static_part.conjugated()?,
2536 self.dynamic_parts
2537 .iter()
2538 .map(|component| {
2539 let drive = component.drive.clone();
2540 Ok(DynamicComponent {
2541 operator: component.operator.conjugated()?,
2542 drive: Arc::new(move |time| drive(time).conj()),
2543 })
2544 })
2545 .collect::<Result<_>>()?,
2546 )
2547 }
2548
2549 pub fn adjoint(&self) -> Result<Self> {
2550 Self::new(
2551 self.static_part.adjoint()?,
2552 self.dynamic_parts
2553 .iter()
2554 .map(|component| {
2555 let drive = component.drive.clone();
2556 Ok(DynamicComponent {
2557 operator: component.operator.adjoint()?,
2558 drive: Arc::new(move |time| drive(time).conj()),
2559 })
2560 })
2561 .collect::<Result<_>>()?,
2562 )
2563 }
2564
2565 pub fn scaled(&self, coefficient: impl Into<Complex64>) -> Result<Self> {
2566 let coefficient = coefficient.into();
2567 if !coefficient.re.is_finite() || !coefficient.im.is_finite() {
2568 return Err(QmbedError::InvalidOptions(
2569 "Hamiltonian scale must be finite".into(),
2570 ));
2571 }
2572 Self::new(
2573 self.static_part.scaled(coefficient)?,
2574 self.dynamic_parts
2575 .iter()
2576 .map(|component| DynamicComponent {
2577 operator: component.operator.clone(),
2578 drive: {
2579 let drive = component.drive.clone();
2580 Arc::new(move |time| coefficient * drive(time))
2581 },
2582 })
2583 .collect(),
2584 )
2585 }
2586}
2587
2588fn finite_drive_value(time: f64, value: Complex64) -> Result<Complex64> {
2589 if !time.is_finite() || !value.re.is_finite() || !value.im.is_finite() {
2590 return Err(QmbedError::InvalidOptions(
2591 "drive time and coefficient must be finite".into(),
2592 ));
2593 }
2594 Ok(value)
2595}
2596
2597impl LinearOperator for Hamiltonian<Static> {
2598 fn shape(&self) -> (usize, usize) {
2599 self.static_part.shape()
2600 }
2601
2602 fn format(&self) -> MatrixFormat {
2603 self.static_part.format()
2604 }
2605
2606 fn apply(&self, input: &[Complex64], output: &mut [Complex64]) -> Result<()> {
2607 self.static_part.apply(input, output)
2608 }
2609
2610 fn apply_transpose(&self, input: &[Complex64], output: &mut [Complex64]) -> Result<()> {
2611 self.static_part.apply_transpose(input, output)
2612 }
2613
2614 fn apply_adjoint(&self, input: &[Complex64], output: &mut [Complex64]) -> Result<()> {
2615 self.static_part.apply_adjoint(input, output)
2616 }
2617
2618 fn stored_triplets(&self) -> Result<Option<Vec<(usize, usize, Complex64)>>> {
2619 self.static_part.stored_triplets()
2620 }
2621
2622 fn shifted_solver(&self, shift: f64) -> Result<Option<Box<dyn ShiftedLinearSolver>>> {
2623 self.static_part.shifted_solver(shift)
2624 }
2625}
2626
2627impl TimeDependentOperator for Hamiltonian<Static> {
2628 fn shape(&self) -> (usize, usize) {
2629 self.static_part.shape()
2630 }
2631
2632 fn apply_at(&self, time: f64, input: &[Complex64], output: &mut [Complex64]) -> Result<()> {
2633 if !time.is_finite() {
2634 return Err(QmbedError::InvalidOptions(
2635 "evaluation time must be finite".into(),
2636 ));
2637 }
2638 self.static_part.apply(input, output)
2639 }
2640}
2641
2642impl TimeDependentOperator for Hamiltonian<Dynamic> {
2643 fn shape(&self) -> (usize, usize) {
2644 self.static_part.shape()
2645 }
2646
2647 fn apply_at(&self, time: f64, input: &[Complex64], output: &mut [Complex64]) -> Result<()> {
2648 check_apply_shape(self.static_part.shape(), input, output)?;
2649 self.static_part.apply(input, output)?;
2650 let mut driven = vec![Complex64::new(0.0, 0.0); output.len()];
2651 for component in &self.dynamic_parts {
2652 let coefficient = finite_drive_value(time, (component.drive)(time))?;
2653 component.operator.apply(input, &mut driven)?;
2654 for (value, contribution) in output.iter_mut().zip(&driven) {
2655 *value += coefficient * *contribution;
2656 }
2657 }
2658 Ok(())
2659 }
2660}
2661
2662#[derive(Clone, Debug)]
2663pub struct QuantumComponent {
2664 name: String,
2665 operator: Operator,
2666 default: Option<Complex64>,
2667}
2668
2669impl QuantumComponent {
2670 pub fn required(name: impl Into<String>, operator: Operator) -> Self {
2671 Self {
2672 name: name.into(),
2673 operator,
2674 default: None,
2675 }
2676 }
2677
2678 pub fn with_default(
2679 name: impl Into<String>,
2680 operator: Operator,
2681 default: impl Into<Complex64>,
2682 ) -> Self {
2683 Self {
2684 name: name.into(),
2685 operator,
2686 default: Some(default.into()),
2687 }
2688 }
2689
2690 pub fn parameter(name: impl Into<String>, operator: Operator) -> Self {
2692 Self::with_default(name, operator, Complex64::new(1.0, 0.0))
2693 }
2694
2695 pub fn name(&self) -> &str {
2696 &self.name
2697 }
2698
2699 pub fn operator(&self) -> &Operator {
2700 &self.operator
2701 }
2702
2703 pub const fn default(&self) -> Option<Complex64> {
2704 self.default
2705 }
2706}
2707
2708#[derive(Clone, Debug)]
2710pub struct QuantumOperator {
2711 components: Vec<QuantumComponent>,
2712 shape: (usize, usize),
2713}
2714
2715impl QuantumOperator {
2716 pub fn new(components: impl IntoIterator<Item = QuantumComponent>) -> Result<Self> {
2718 let components: Vec<_> = components.into_iter().collect();
2719 let first = components.first().ok_or_else(|| {
2720 QmbedError::InvalidOptions("QuantumOperator requires at least one component".into())
2721 })?;
2722 let shape = first.operator.shape();
2723 let mut names = std::collections::HashSet::new();
2724 for component in &components {
2725 if component.name.is_empty() || !names.insert(component.name.clone()) {
2726 return Err(QmbedError::InvalidOptions(
2727 "component names must be nonempty and unique".into(),
2728 ));
2729 }
2730 if component.operator.shape() != shape {
2731 return Err(QmbedError::DimensionMismatch(
2732 "all parameterized components must have equal shapes".into(),
2733 ));
2734 }
2735 if component
2736 .default
2737 .is_some_and(|value| !value.re.is_finite() || !value.im.is_finite())
2738 {
2739 return Err(QmbedError::InvalidOptions(
2740 "component defaults must be finite".into(),
2741 ));
2742 }
2743 }
2744 Ok(Self { components, shape })
2745 }
2746
2747 pub const fn shape(&self) -> (usize, usize) {
2749 self.shape
2750 }
2751
2752 pub fn component_names(&self) -> impl Iterator<Item = &str> {
2754 self.components
2755 .iter()
2756 .map(|component| component.name.as_str())
2757 }
2758
2759 pub fn components(&self) -> &[QuantumComponent] {
2761 &self.components
2762 }
2763
2764 pub fn component(&self, name: &str) -> Result<&Operator> {
2766 self.components
2767 .iter()
2768 .find(|component| component.name == name)
2769 .map(|component| &component.operator)
2770 .ok_or_else(|| {
2771 QmbedError::InvalidOptions(format!("unknown operator component {name:?}"))
2772 })
2773 }
2774
2775 pub fn resolve_coefficients(
2782 &self,
2783 parameters: &HashMap<String, Complex64>,
2784 ) -> Result<Vec<Complex64>> {
2785 if let Some(name) = parameters.keys().find(|name| {
2786 !self
2787 .components
2788 .iter()
2789 .any(|component| &component.name == *name)
2790 }) {
2791 return Err(QmbedError::InvalidOptions(format!(
2792 "unknown operator parameter {name:?}"
2793 )));
2794 }
2795 self.components
2796 .iter()
2797 .map(|component| {
2798 let coefficient = parameters
2799 .get(&component.name)
2800 .copied()
2801 .or(component.default)
2802 .ok_or_else(|| {
2803 QmbedError::InvalidOptions(format!(
2804 "missing required operator parameter {:?}",
2805 component.name
2806 ))
2807 })?;
2808 if !coefficient.re.is_finite() || !coefficient.im.is_finite() {
2809 return Err(QmbedError::InvalidOptions(format!(
2810 "operator parameter {:?} must be finite",
2811 component.name
2812 )));
2813 }
2814 Ok(coefficient)
2815 })
2816 .collect()
2817 }
2818
2819 pub fn apply_coefficients(
2822 &self,
2823 coefficients: &[Complex64],
2824 input: &[Complex64],
2825 output: &mut [Complex64],
2826 ) -> Result<()> {
2827 if coefficients.len() != self.components.len() {
2828 return Err(QmbedError::DimensionMismatch(format!(
2829 "received {} operator coefficients for {} components",
2830 coefficients.len(),
2831 self.components.len()
2832 )));
2833 }
2834 check_apply_shape(self.shape, input, output)?;
2835 output.fill(Complex64::new(0.0, 0.0));
2836 let mut contribution = vec![Complex64::new(0.0, 0.0); output.len()];
2837 for (component, coefficient) in self.components.iter().zip(coefficients) {
2838 if !coefficient.re.is_finite() || !coefficient.im.is_finite() {
2839 return Err(QmbedError::InvalidOptions(format!(
2840 "operator parameter {:?} must be finite",
2841 component.name
2842 )));
2843 }
2844 component.operator.apply(input, &mut contribution)?;
2845 for (value, addition) in output.iter_mut().zip(&contribution) {
2846 *value += *coefficient * *addition;
2847 }
2848 }
2849 Ok(())
2850 }
2851
2852 pub fn apply(
2855 &self,
2856 parameters: &HashMap<String, Complex64>,
2857 input: &[Complex64],
2858 output: &mut [Complex64],
2859 ) -> Result<()> {
2860 let coefficients = self.resolve_coefficients(parameters)?;
2861 self.apply_coefficients(&coefficients, input, output)
2862 }
2863
2864 pub fn evaluate(
2869 &self,
2870 parameters: &HashMap<String, Complex64>,
2871 format: MatrixFormat,
2872 ) -> Result<Operator> {
2873 let coefficients = self.resolve_coefficients(parameters)?;
2874 let mut entries = Vec::new();
2875 for (component, coefficient) in self.components.iter().zip(coefficients) {
2876 entries.extend(
2877 component
2878 .operator
2879 .triplets()
2880 .into_iter()
2881 .map(|(row, column, value)| (row, column, coefficient * value)),
2882 );
2883 }
2884 Operator::from_triplets(self.shape.0, self.shape.1, entries, format)
2885 }
2886
2887 pub fn plan(&self, format: MatrixFormat) -> Result<QuantumOperatorPlan> {
2894 QuantumOperatorPlan::new(self, format)
2895 }
2896
2897 pub fn scaled(&self, coefficient: impl Into<Complex64>) -> Result<Self> {
2898 let coefficient = coefficient.into();
2899 let mut components = Vec::with_capacity(self.components.len());
2900 for component in &self.components {
2901 components.push(QuantumComponent {
2902 name: component.name.clone(),
2903 operator: component.operator.scaled(coefficient)?,
2904 default: component.default,
2905 });
2906 }
2907 Self::new(components)
2908 }
2909
2910 pub fn conjugated(&self) -> Result<Self> {
2911 let mut components = Vec::with_capacity(self.components.len());
2912 for component in &self.components {
2913 components.push(QuantumComponent {
2914 name: component.name.clone(),
2915 operator: component.operator.conjugated()?,
2916 default: component.default.map(|value| value.conj()),
2917 });
2918 }
2919 Self::new(components)
2920 }
2921
2922 pub fn add(&self, right: &Self) -> Result<Self> {
2923 if self.shape != right.shape {
2924 return Err(QmbedError::DimensionMismatch(
2925 "parameterized operators must have equal shapes".into(),
2926 ));
2927 }
2928 let mut components = self.components.clone();
2929 for right_component in &right.components {
2930 if let Some(left_component) = components
2931 .iter_mut()
2932 .find(|component| component.name == right_component.name)
2933 {
2934 left_component.operator = left_component.operator.add(&right_component.operator)?;
2935 if left_component.default != right_component.default {
2936 left_component.default = None;
2937 }
2938 } else {
2939 components.push(right_component.clone());
2940 }
2941 }
2942 Self::new(components)
2943 }
2944
2945 pub fn adjoint(&self) -> Result<Self> {
2946 let mut components = Vec::with_capacity(self.components.len());
2947 for component in &self.components {
2948 components.push(QuantumComponent {
2949 name: component.name.clone(),
2950 operator: component.operator.adjoint()?,
2951 default: component.default.map(|value| value.conj()),
2952 });
2953 }
2954 Self::new(components)
2955 }
2956
2957 pub fn transpose(&self) -> Result<Self> {
2958 let mut components = Vec::with_capacity(self.components.len());
2959 for component in &self.components {
2960 components.push(QuantumComponent {
2961 name: component.name.clone(),
2962 operator: component.operator.transpose()?,
2963 default: component.default,
2964 });
2965 }
2966 Self::new(components)
2967 }
2968}
2969
2970#[derive(Clone, Debug)]
2976pub struct QuantumOperatorPlan {
2977 component_names: Vec<String>,
2978 defaults: Vec<Option<Complex64>>,
2979 component_values: Vec<Vec<Complex64>>,
2980 combined_values: Vec<Complex64>,
2981 operator: Operator,
2982}
2983
2984impl QuantumOperatorPlan {
2985 fn new(family: &QuantumOperator, format: MatrixFormat) -> Result<Self> {
2986 let mut entries = HashMap::<(usize, usize), Vec<Complex64>>::new();
2987 for (component_index, component) in family.components.iter().enumerate() {
2988 for (row, column, value) in component.operator.triplets() {
2989 entries
2990 .entry((row, column))
2991 .or_insert_with(|| vec![Complex64::new(0.0, 0.0); family.components.len()])
2992 [component_index] += value;
2993 }
2994 }
2995 let mut coordinates: Vec<_> = entries.keys().copied().collect();
2996 match format {
2997 MatrixFormat::Csc => coordinates.sort_by_key(|&(row, column)| (column, row)),
2998 MatrixFormat::Csr | MatrixFormat::MatrixFree | MatrixFormat::Dia => {
2999 coordinates.sort_unstable()
3000 }
3001 MatrixFormat::Dense => {}
3002 }
3003
3004 let components = family.components.len();
3005 let zero = Complex64::new(0.0, 0.0);
3006 let (storage, component_values) = match format {
3007 MatrixFormat::Dense => {
3008 let slots = family.shape.0.saturating_mul(family.shape.1);
3009 let mut values = vec![vec![zero; slots]; components];
3010 for (&(row, column), aligned) in &entries {
3011 let slot = row * family.shape.1 + column;
3012 for (component, value) in aligned.iter().enumerate() {
3013 values[component][slot] = *value;
3014 }
3015 }
3016 (Storage::Dense(vec![zero; slots]), values)
3017 }
3018 MatrixFormat::Csc => {
3019 let mut column_offsets = vec![0usize; family.shape.1 + 1];
3020 let mut row_indices = Vec::with_capacity(coordinates.len());
3021 let mut values = vec![vec![zero; coordinates.len()]; components];
3022 for (slot, &(row, column)) in coordinates.iter().enumerate() {
3023 column_offsets[column + 1] += 1;
3024 row_indices.push(row);
3025 for (component, value) in entries[&(row, column)].iter().enumerate() {
3026 values[component][slot] = *value;
3027 }
3028 }
3029 for column in 0..family.shape.1 {
3030 column_offsets[column + 1] += column_offsets[column];
3031 }
3032 (
3033 Storage::Csc {
3034 column_offsets,
3035 row_indices,
3036 values: vec![zero; coordinates.len()],
3037 },
3038 values,
3039 )
3040 }
3041 MatrixFormat::Csr => {
3042 let mut row_offsets = vec![0usize; family.shape.0 + 1];
3043 let mut column_indices = Vec::with_capacity(coordinates.len());
3044 let mut values = vec![vec![zero; coordinates.len()]; components];
3045 for (slot, &(row, column)) in coordinates.iter().enumerate() {
3046 row_offsets[row + 1] += 1;
3047 column_indices.push(column);
3048 for (component, value) in entries[&(row, column)].iter().enumerate() {
3049 values[component][slot] = *value;
3050 }
3051 }
3052 for row in 0..family.shape.0 {
3053 row_offsets[row + 1] += row_offsets[row];
3054 }
3055 (
3056 Storage::Csr {
3057 row_offsets,
3058 column_indices,
3059 values: vec![zero; coordinates.len()],
3060 },
3061 values,
3062 )
3063 }
3064 MatrixFormat::MatrixFree => {
3065 let mut values = vec![vec![zero; coordinates.len()]; components];
3066 let pattern = coordinates
3067 .iter()
3068 .enumerate()
3069 .map(|(slot, &(row, column))| {
3070 for (component, value) in entries[&(row, column)].iter().enumerate() {
3071 values[component][slot] = *value;
3072 }
3073 Triplet {
3074 row,
3075 column,
3076 value: zero,
3077 }
3078 })
3079 .collect();
3080 (Storage::MatrixFree(pattern), values)
3081 }
3082 MatrixFormat::Dia => {
3083 let mut offsets: Vec<_> = coordinates
3084 .iter()
3085 .map(|&(row, column)| row as isize - column as isize)
3086 .collect();
3087 offsets.sort_unstable();
3088 offsets.dedup();
3089 let slots = offsets.len().saturating_mul(family.shape.0);
3090 let mut values = vec![vec![zero; slots]; components];
3091 for (&(row, column), aligned) in &entries {
3092 let offset = row as isize - column as isize;
3093 let diagonal = offsets.binary_search(&offset).map_err(|_| {
3094 QmbedError::InternalState(
3095 "parameterized diagonal pattern lost an offset".into(),
3096 )
3097 })?;
3098 let slot = diagonal * family.shape.0 + row;
3099 for (component, value) in aligned.iter().enumerate() {
3100 values[component][slot] = *value;
3101 }
3102 }
3103 (
3104 Storage::Dia {
3105 offsets,
3106 values_by_row: vec![zero; slots],
3107 },
3108 values,
3109 )
3110 }
3111 };
3112 let combined_values =
3113 vec![Complex64::new(0.0, 0.0); component_values.first().map_or(0, Vec::len)];
3114 Ok(Self {
3115 component_names: family
3116 .components
3117 .iter()
3118 .map(|component| component.name.clone())
3119 .collect(),
3120 defaults: family
3121 .components
3122 .iter()
3123 .map(|component| component.default)
3124 .collect(),
3125 component_values,
3126 combined_values,
3127 operator: Operator {
3128 shape: family.shape,
3129 format,
3130 real: true,
3131 storage,
3132 },
3133 })
3134 }
3135
3136 pub fn component_names(&self) -> impl Iterator<Item = &str> {
3138 self.component_names.iter().map(String::as_str)
3139 }
3140
3141 pub fn operator(&self) -> &Operator {
3143 &self.operator
3144 }
3145
3146 pub fn resolve_coefficients(
3148 &self,
3149 parameters: &HashMap<String, Complex64>,
3150 ) -> Result<Vec<Complex64>> {
3151 if let Some(name) = parameters
3152 .keys()
3153 .find(|name| !self.component_names.contains(name))
3154 {
3155 return Err(QmbedError::InvalidOptions(format!(
3156 "unknown operator parameter {name:?}"
3157 )));
3158 }
3159 self.component_names
3160 .iter()
3161 .zip(&self.defaults)
3162 .map(|(name, default)| {
3163 let coefficient = parameters.get(name).copied().or(*default).ok_or_else(|| {
3164 QmbedError::InvalidOptions(format!(
3165 "missing required operator parameter {name:?}"
3166 ))
3167 })?;
3168 if !coefficient.re.is_finite() || !coefficient.im.is_finite() {
3169 return Err(QmbedError::InvalidOptions(format!(
3170 "operator parameter {name:?} must be finite"
3171 )));
3172 }
3173 Ok(coefficient)
3174 })
3175 .collect()
3176 }
3177
3178 pub fn evaluate(&mut self, parameters: &HashMap<String, Complex64>) -> Result<&Operator> {
3180 let coefficients = self.resolve_coefficients(parameters)?;
3181 self.evaluate_coefficients(&coefficients)
3182 }
3183
3184 pub fn evaluate_coefficients(&mut self, coefficients: &[Complex64]) -> Result<&Operator> {
3189 if coefficients.len() != self.component_values.len() {
3190 return Err(QmbedError::DimensionMismatch(
3191 "parameter coefficients have the wrong length".into(),
3192 ));
3193 }
3194 if coefficients
3195 .iter()
3196 .any(|value| !value.re.is_finite() || !value.im.is_finite())
3197 {
3198 return Err(QmbedError::InvalidOptions(
3199 "parameter coefficients must be finite".into(),
3200 ));
3201 }
3202 self.combined_values.fill(Complex64::new(0.0, 0.0));
3203 for (coefficient, values) in coefficients.iter().zip(&self.component_values) {
3204 for (target, value) in self.combined_values.iter_mut().zip(values) {
3205 *target += *coefficient * *value;
3206 }
3207 }
3208 match &mut self.operator.storage {
3209 Storage::Dense(values) | Storage::Csc { values, .. } | Storage::Csr { values, .. } => {
3210 values.copy_from_slice(&self.combined_values)
3211 }
3212 Storage::Dia { values_by_row, .. } => {
3213 values_by_row.copy_from_slice(&self.combined_values)
3214 }
3215 Storage::MatrixFree(entries) => {
3216 for (entry, value) in entries.iter_mut().zip(&self.combined_values) {
3217 entry.value = *value;
3218 }
3219 }
3220 }
3221 self.operator.real = storage_is_real(&self.operator.storage);
3222 Ok(&self.operator)
3223 }
3224}
3225
3226#[derive(Clone, Debug)]
3228pub struct QuantumLinearOperator {
3229 operator: Operator,
3230 diagonal: Vec<Complex64>,
3231}
3232
3233impl QuantumLinearOperator {
3234 pub fn new(operator: Operator, diagonal: Vec<Complex64>) -> Result<Self> {
3235 let shape = operator.shape();
3236 if shape.0 != shape.1 || diagonal.len() != shape.0 {
3237 return Err(QmbedError::DimensionMismatch(
3238 "QuantumLinearOperator needs a square operator and one diagonal value per row"
3239 .into(),
3240 ));
3241 }
3242 if diagonal
3243 .iter()
3244 .any(|value| !value.re.is_finite() || !value.im.is_finite())
3245 {
3246 return Err(QmbedError::InvalidOptions(
3247 "QuantumLinearOperator diagonal values must be finite".into(),
3248 ));
3249 }
3250 Ok(Self { operator, diagonal })
3251 }
3252
3253 pub fn from_operator(operator: Operator) -> Result<Self> {
3254 let dimension = operator.shape().0;
3255 Self::new(operator, vec![Complex64::new(0.0, 0.0); dimension])
3256 }
3257
3258 pub fn operator(&self) -> &Operator {
3259 &self.operator
3260 }
3261
3262 pub fn diagonal_correction(&self) -> &[Complex64] {
3263 &self.diagonal
3264 }
3265
3266 pub fn set_diagonal(&mut self, diagonal: Vec<Complex64>) -> Result<()> {
3267 if diagonal.len() != self.diagonal.len()
3268 || diagonal
3269 .iter()
3270 .any(|value| !value.re.is_finite() || !value.im.is_finite())
3271 {
3272 return Err(QmbedError::DimensionMismatch(
3273 "replacement diagonal has the wrong length or non-finite values".into(),
3274 ));
3275 }
3276 self.diagonal = diagonal;
3277 Ok(())
3278 }
3279
3280 pub fn materialize(&self, format: MatrixFormat) -> Result<Operator> {
3281 let dimension = self.diagonal.len();
3282 let entries = self.operator.triplets().into_iter().chain(
3283 self.diagonal
3284 .iter()
3285 .copied()
3286 .enumerate()
3287 .filter_map(|(index, value)| {
3288 (value.norm() > f64::EPSILON).then_some((index, index, value))
3289 }),
3290 );
3291 Operator::from_triplets(dimension, dimension, entries, format)
3292 }
3293
3294 pub fn adjoint(&self) -> Result<Self> {
3295 Self::new(
3296 self.operator.adjoint()?,
3297 self.diagonal.iter().map(|value| value.conj()).collect(),
3298 )
3299 }
3300
3301 pub fn transpose(&self) -> Result<Self> {
3302 Self::new(self.operator.transpose()?, self.diagonal.clone())
3303 }
3304
3305 pub fn conjugated(&self) -> Result<Self> {
3306 Self::new(
3307 self.operator.conjugated()?,
3308 self.diagonal.iter().map(|value| value.conj()).collect(),
3309 )
3310 }
3311
3312 pub fn right_apply(&self, input: &[Complex64]) -> Result<Vec<Complex64>> {
3313 let mut output = vec![Complex64::new(0.0, 0.0); self.shape().1];
3314 self.apply_transpose(input, &mut output)?;
3315 Ok(output)
3316 }
3317}
3318
3319impl LinearOperator for QuantumLinearOperator {
3320 fn shape(&self) -> (usize, usize) {
3321 self.operator.shape()
3322 }
3323
3324 fn format(&self) -> MatrixFormat {
3325 MatrixFormat::MatrixFree
3326 }
3327
3328 fn apply(&self, input: &[Complex64], output: &mut [Complex64]) -> Result<()> {
3329 self.operator.apply(input, output)?;
3330 for ((value, diagonal), input_value) in output.iter_mut().zip(&self.diagonal).zip(input) {
3331 *value += *diagonal * *input_value;
3332 }
3333 Ok(())
3334 }
3335
3336 fn apply_transpose(&self, input: &[Complex64], output: &mut [Complex64]) -> Result<()> {
3337 self.operator.apply_transpose(input, output)?;
3338 for ((value, diagonal), input_value) in output.iter_mut().zip(&self.diagonal).zip(input) {
3339 *value += *diagonal * *input_value;
3340 }
3341 Ok(())
3342 }
3343
3344 fn stored_triplets(&self) -> Result<Option<Vec<(usize, usize, Complex64)>>> {
3345 Ok(Some(self.materialize(MatrixFormat::Csc)?.triplets()))
3346 }
3347
3348 fn shifted_solver(&self, shift: f64) -> Result<Option<Box<dyn ShiftedLinearSolver>>> {
3349 self.materialize(MatrixFormat::Csc)?.shifted_solver(shift)
3350 }
3351}
3352
3353pub fn commutator(left: &Operator, right: &Operator) -> Result<Operator> {
3354 left.product(right)?.subtract(&right.product(left)?)
3355}
3356
3357pub fn anticommutator(left: &Operator, right: &Operator) -> Result<Operator> {
3358 left.product(right)?.add(&right.product(left)?)
3359}
3360
3361#[derive(Clone, Copy, Debug, PartialEq)]
3363pub struct ExpGrid {
3364 pub start: f64,
3365 pub stop: f64,
3366 pub points: usize,
3367 pub endpoint: bool,
3368}
3369
3370impl ExpGrid {
3371 pub fn new(start: f64, stop: f64, points: usize, endpoint: bool) -> Result<Self> {
3372 if !start.is_finite() || !stop.is_finite() || points == 0 {
3373 return Err(QmbedError::InvalidOptions(
3374 "exponential grid endpoints must be finite and points must be positive".into(),
3375 ));
3376 }
3377 Ok(Self {
3378 start,
3379 stop,
3380 points,
3381 endpoint,
3382 })
3383 }
3384
3385 pub fn values(&self) -> Vec<f64> {
3386 if self.points == 1 {
3387 return vec![self.start];
3388 }
3389 let intervals = if self.endpoint {
3390 self.points - 1
3391 } else {
3392 self.points
3393 };
3394 let step = (self.stop - self.start) / intervals as f64;
3395 (0..self.points)
3396 .map(|index| self.start + index as f64 * step)
3397 .collect()
3398 }
3399}
3400
3401pub struct ExpOpGridIter {
3403 operator: ExpOp,
3404 input: Vec<Complex64>,
3405 scales: std::vec::IntoIter<f64>,
3406}
3407
3408impl Iterator for ExpOpGridIter {
3409 type Item = Result<Vec<Complex64>>;
3410
3411 fn next(&mut self) -> Option<Self::Item> {
3412 self.scales.next().map(|scale| {
3413 let mut operator = self.operator.clone();
3414 operator.set_exponent(scale * self.operator.exponent)?;
3415 let mut output = vec![Complex64::new(0.0, 0.0); self.input.len()];
3416 operator.apply(&self.input, &mut output)?;
3417 Ok(output)
3418 })
3419 }
3420}
3421
3422#[derive(Clone)]
3424pub struct ExpOp {
3425 operator: Arc<dyn LinearOperator>,
3426 exponent: Complex64,
3427 krylov_dimension: usize,
3428 tolerance: f64,
3429 max_substeps: usize,
3430 plan: crate::solve::ExpmActionPlan,
3431}
3432
3433impl std::fmt::Debug for ExpOp {
3434 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3435 formatter
3436 .debug_struct("ExpOp")
3437 .field("shape", &self.operator.shape())
3438 .field("exponent", &self.exponent)
3439 .field("krylov_dimension", &self.krylov_dimension)
3440 .field("tolerance", &self.tolerance)
3441 .field("max_substeps", &self.max_substeps)
3442 .finish()
3443 }
3444}
3445
3446impl ExpOp {
3447 pub fn new(
3448 operator: Arc<dyn LinearOperator>,
3449 exponent: Complex64,
3450 krylov_dimension: usize,
3451 tolerance: f64,
3452 max_substeps: usize,
3453 ) -> Result<Self> {
3454 let shape = operator.shape();
3455 if shape.0 != shape.1 {
3456 return Err(QmbedError::DimensionMismatch(
3457 "ExpOp requires a square operator".into(),
3458 ));
3459 }
3460 if !exponent.re.is_finite()
3461 || !exponent.im.is_finite()
3462 || krylov_dimension == 0
3463 || !tolerance.is_finite()
3464 || tolerance <= 0.0
3465 || max_substeps == 0
3466 {
3467 return Err(QmbedError::InvalidOptions(
3468 "invalid ExpOp coefficient or numerical controls".into(),
3469 ));
3470 }
3471 let plan = crate::solve::ExpmActionPlan::new(
3472 operator.as_ref(),
3473 exponent,
3474 krylov_dimension,
3475 tolerance,
3476 max_substeps,
3477 )?;
3478 Ok(Self {
3479 operator,
3480 exponent,
3481 krylov_dimension,
3482 tolerance,
3483 max_substeps,
3484 plan,
3485 })
3486 }
3487
3488 pub const fn exponent(&self) -> Complex64 {
3489 self.exponent
3490 }
3491
3492 pub fn generator(&self) -> &Arc<dyn LinearOperator> {
3493 &self.operator
3494 }
3495
3496 pub fn set_exponent(&mut self, exponent: Complex64) -> Result<()> {
3497 if !exponent.re.is_finite() || !exponent.im.is_finite() {
3498 return Err(QmbedError::InvalidOptions(
3499 "ExpOp coefficient must be finite".into(),
3500 ));
3501 }
3502 self.plan = crate::solve::ExpmActionPlan::new(
3503 self.operator.as_ref(),
3504 exponent,
3505 self.krylov_dimension,
3506 self.tolerance,
3507 self.max_substeps,
3508 )?;
3509 self.exponent = exponent;
3510 Ok(())
3511 }
3512
3513 pub fn sandwich(&self, state: &[Complex64]) -> Result<Complex64> {
3514 let mut output = vec![Complex64::new(0.0, 0.0); state.len()];
3515 self.apply(state, &mut output)?;
3516 Ok(state
3517 .iter()
3518 .zip(output)
3519 .map(|(left, right)| left.conj() * right)
3520 .sum())
3521 }
3522
3523 pub fn matrix(&self, format: MatrixFormat) -> Result<Operator> {
3524 let shape = self.shape();
3525 let mut input = vec![Complex64::new(0.0, 0.0); shape.1];
3526 let mut output = vec![Complex64::new(0.0, 0.0); shape.0];
3527 let mut triplets = Vec::new();
3528 for column in 0..shape.1 {
3529 input.fill(Complex64::new(0.0, 0.0));
3530 input[column] = Complex64::new(1.0, 0.0);
3531 self.apply(&input, &mut output)?;
3532 for (row, value) in output.iter().copied().enumerate() {
3533 if value.norm() > f64::EPSILON {
3534 triplets.push((row, column, value));
3535 }
3536 }
3537 }
3538 Operator::from_triplets(shape.0, shape.1, triplets, format)
3539 }
3540
3541 pub fn iter_grid(&self, input: &[Complex64], grid: ExpGrid) -> Result<ExpOpGridIter> {
3542 if input.len() != self.shape().1 {
3543 return Err(QmbedError::DimensionMismatch(
3544 "ExpOp grid input must match the operator dimension".into(),
3545 ));
3546 }
3547 Ok(ExpOpGridIter {
3548 operator: self.clone(),
3549 input: input.to_vec(),
3550 scales: grid.values().into_iter(),
3551 })
3552 }
3553
3554 pub fn apply_grid(&self, input: &[Complex64], grid: ExpGrid) -> Result<Vec<Vec<Complex64>>> {
3555 self.iter_grid(input, grid)?.collect()
3556 }
3557
3558 pub fn transpose(&self) -> Result<Self> {
3559 Self::new(
3560 Arc::new(TransposedLinearOperator::new(self.operator.clone())),
3561 self.exponent,
3562 self.krylov_dimension,
3563 self.tolerance,
3564 self.max_substeps,
3565 )
3566 }
3567
3568 pub fn conjugated(&self) -> Result<Self> {
3569 Self::new(
3570 Arc::new(ConjugatedLinearOperator::new(self.operator.clone())),
3571 self.exponent.conj(),
3572 self.krylov_dimension,
3573 self.tolerance,
3574 self.max_substeps,
3575 )
3576 }
3577
3578 pub fn adjoint(&self) -> Result<Self> {
3579 Self::new(
3580 Arc::new(AdjointLinearOperator::new(self.operator.clone())),
3581 self.exponent.conj(),
3582 self.krylov_dimension,
3583 self.tolerance,
3584 self.max_substeps,
3585 )
3586 }
3587
3588 pub fn right_apply(&self, input: &[Complex64]) -> Result<Vec<Complex64>> {
3589 if input.len() != self.shape().0 {
3590 return Err(QmbedError::DimensionMismatch(
3591 "ExpOp right-apply input must match the operator dimension".into(),
3592 ));
3593 }
3594 let mut output = vec![Complex64::new(0.0, 0.0); self.shape().1];
3595 self.apply_transpose(input, &mut output)?;
3596 Ok(output)
3597 }
3598}
3599
3600impl LinearOperator for ExpOp {
3601 fn shape(&self) -> (usize, usize) {
3602 self.operator.shape()
3603 }
3604
3605 fn format(&self) -> MatrixFormat {
3606 MatrixFormat::MatrixFree
3607 }
3608
3609 fn apply(&self, input: &[Complex64], output: &mut [Complex64]) -> Result<()> {
3610 check_apply_shape(self.shape(), input, output)?;
3611 let result = self.plan.apply(self.operator.as_ref(), input)?;
3612 output.copy_from_slice(&result);
3613 Ok(())
3614 }
3615
3616 fn apply_transpose(&self, input: &[Complex64], output: &mut [Complex64]) -> Result<()> {
3617 check_apply_shape((self.shape().1, self.shape().0), input, output)?;
3618 let transpose = TransposedLinearOperator::new(self.operator.clone());
3619 let result = crate::solve::expm_action_complex(
3620 &transpose,
3621 input,
3622 self.exponent,
3623 self.krylov_dimension,
3624 self.tolerance,
3625 self.max_substeps,
3626 )?;
3627 output.copy_from_slice(&result);
3628 Ok(())
3629 }
3630}
3631
3632pub fn is_exp_op(value: &dyn std::any::Any) -> bool {
3633 value.is::<ExpOp>()
3634}
3635
3636pub fn is_hamiltonian(value: &dyn std::any::Any) -> bool {
3637 value.is::<Hamiltonian<Static>>() || value.is::<Hamiltonian<Dynamic>>()
3638}
3639
3640pub fn is_quantum_operator(value: &dyn std::any::Any) -> bool {
3641 value.is::<QuantumOperator>()
3642}
3643
3644pub fn is_quantum_linear_operator(value: &dyn std::any::Any) -> bool {
3645 value.is::<QuantumLinearOperator>()
3646}