1use std::cmp::Ordering;
2use std::collections::{HashMap, HashSet, VecDeque};
3use std::hash::Hash;
4use std::sync::{Arc, RwLock};
5
6use num_bigint::BigUint;
7use num_complex::Complex64;
8use smallvec::SmallVec;
9
10use crate::operator::{LinearOperator, MatrixFormat, Operator, check_apply_shape};
11use crate::{QmbedError, Result};
12
13pub type LocalTransitions<State> = SmallVec<[(State, Complex64); 2]>;
19
20pub trait BinaryState: Copy + Eq + Hash {
27 fn bit(&self, index: usize) -> Result<bool>;
29}
30
31impl BinaryState for u128 {
32 fn bit(&self, index: usize) -> Result<bool> {
33 if index >= u128::BITS as usize {
34 return Err(QmbedError::InvalidSite {
35 site: index,
36 sites: u128::BITS as usize,
37 });
38 }
39 Ok(*self & (1_u128 << index) != 0)
40 }
41}
42
43#[derive(Clone, Copy, Debug, PartialEq)]
50pub struct ReductionImage<State> {
51 representative: State,
52 phase: Complex64,
53 orbit_size: usize,
54}
55
56impl<State> ReductionImage<State> {
57 pub fn new(representative: State, phase: Complex64, orbit_size: usize) -> Result<Self> {
63 if orbit_size == 0 || !phase.re.is_finite() || !phase.im.is_finite() {
64 return Err(QmbedError::InternalState(
65 "a reduction image requires a positive orbit and finite phase".into(),
66 ));
67 }
68 if (phase.norm() - 1.0).abs() > 1.0e-10 {
69 return Err(QmbedError::InternalState(
70 "a reduction-image phase must have unit magnitude".into(),
71 ));
72 }
73 Ok(Self {
74 representative,
75 phase,
76 orbit_size,
77 })
78 }
79
80 pub const fn representative(&self) -> &State {
82 &self.representative
83 }
84
85 pub const fn phase(&self) -> Complex64 {
87 self.phase
88 }
89
90 pub const fn orbit_size(&self) -> usize {
92 self.orbit_size
93 }
94
95 pub fn amplitude(&self) -> Complex64 {
97 self.phase / (self.orbit_size as f64).sqrt()
98 }
99}
100
101pub trait Basis: Send + Sync {
103 type State: Copy + Eq + Send + Sync;
105
106 fn len(&self) -> usize;
108 fn state(&self, index: usize) -> Result<Self::State>;
113 fn index(&self, state: Self::State) -> Result<usize>;
115 fn apply_local(
120 &self,
121 state: Self::State,
122 operator: &str,
123 sites: &[usize],
124 ) -> Result<Option<(Self::State, Complex64)>>;
125
126 fn apply_local_transitions(
133 &self,
134 state: Self::State,
135 operator: &str,
136 sites: &[usize],
137 ) -> Result<LocalTransitions<Self::State>> {
138 Ok(self
139 .apply_local(state, operator, sites)?
140 .into_iter()
141 .collect())
142 }
143
144 fn apply_local_unreduced_transitions(
147 &self,
148 state: Self::State,
149 operator: &str,
150 sites: &[usize],
151 ) -> Result<LocalTransitions<Self::State>> {
152 self.apply_local_transitions(state, operator, sites)
153 }
154
155 fn visit_local_unreduced_transitions<F>(
162 &self,
163 state: Self::State,
164 operator: &str,
165 sites: &[usize],
166 mut visit: F,
167 ) -> Result<()>
168 where
169 Self: Sized,
170 F: FnMut(Self::State, Complex64) -> Result<()>,
171 {
172 if let Some((target, amplitude)) = self.apply_local(state, operator, sites)? {
173 visit(target, amplitude)?;
174 }
175 Ok(())
176 }
177
178 #[doc(hidden)]
185 fn visit_preparsed_local_unreduced_transitions<F>(
186 &self,
187 state: Self::State,
188 operator: &str,
189 symbols: &[char],
190 split: Option<usize>,
191 sites: &[usize],
192 visit: F,
193 ) -> Result<()>
194 where
195 Self: Sized,
196 F: FnMut(Self::State, Complex64) -> Result<()>,
197 {
198 let _ = (symbols, split);
199 self.visit_local_unreduced_transitions(state, operator, sites, visit)
200 }
201
202 fn transition_orbit_size(&self, _state: Self::State) -> Result<usize> {
204 Ok(1)
205 }
206
207 fn reduction_image(&self, state: Self::State) -> Result<Option<ReductionImage<Self::State>>> {
212 match self.index(state) {
213 Ok(_) => Ok(Some(ReductionImage::new(
214 state,
215 Complex64::new(1.0, 0.0),
216 1,
217 )?)),
218 Err(QmbedError::StateNotInBasis) => Ok(None),
219 Err(error) => Err(error),
220 }
221 }
222
223 fn reduce_transition(
225 &self,
226 state: Self::State,
227 source_orbit_size: usize,
228 ) -> Result<Option<(Self::State, Complex64)>> {
229 Ok(self.reduction_image(state)?.map(|image| {
230 (
231 image.representative,
232 (source_orbit_size as f64 / image.orbit_size as f64).sqrt() * image.phase.conj(),
233 )
234 }))
235 }
236
237 fn index_transition(
242 &self,
243 state: Self::State,
244 source_orbit_size: usize,
245 ) -> Result<Option<(usize, Complex64)>> {
246 let Some((representative, amplitude)) = self.reduce_transition(state, source_orbit_size)?
247 else {
248 return Ok(None);
249 };
250 Ok(Some((self.index(representative)?, amplitude)))
251 }
252
253 fn operator_preserves_particle_sector(&self, _operator: &str) -> Result<bool> {
257 Ok(true)
258 }
259
260 fn operator_preserves_particle_sector_on_sites(
267 &self,
268 operator: &str,
269 _sites: &[usize],
270 ) -> Result<bool> {
271 self.operator_preserves_particle_sector(operator)
272 }
273
274 fn is_empty(&self) -> bool {
276 self.len() == 0
277 }
278}
279
280fn operator_number_change(operator: &str) -> Result<Option<i32>> {
281 let mut change = 0_i32;
282 for character in operator.chars().filter(|character| *character != '|') {
283 match character {
284 '+' => change += 1,
285 '-' => change -= 1,
286 'x' | 'y' => return Ok(None),
287 'I' | 'n' | 'z' => {}
288 _ => return Err(QmbedError::InvalidOperator(operator.into())),
289 }
290 }
291 Ok(Some(change))
292}
293
294fn operator_number_changes(operator: &str) -> Result<Vec<i32>> {
295 let mut changes = vec![0_i32];
296 for character in operator.chars().filter(|character| *character != '|') {
297 match character {
298 '+' => {
299 for change in &mut changes {
300 *change += 1;
301 }
302 }
303 '-' => {
304 for change in &mut changes {
305 *change -= 1;
306 }
307 }
308 'x' | 'y' => {
309 let mut lowered = changes.clone();
310 for change in &mut changes {
311 *change += 1;
312 }
313 for change in &mut lowered {
314 *change -= 1;
315 }
316 changes.extend(lowered);
317 changes.sort_unstable();
318 changes.dedup();
319 }
320 'I' | 'n' | 'z' => {}
321 _ => return Err(QmbedError::InvalidOperator(operator.into())),
322 }
323 }
324 Ok(changes)
325}
326
327fn canonical_particle_sectors(
328 sectors: impl IntoIterator<Item = usize>,
329 maximum: usize,
330 family: &str,
331) -> Result<Vec<usize>> {
332 let mut sectors = sectors.into_iter().collect::<Vec<_>>();
333 if sectors.is_empty() {
334 return Err(QmbedError::InvalidSector(format!(
335 "{family} particle-sector union must be nonempty"
336 )));
337 }
338 if sectors.iter().any(|§or| sector > maximum) {
339 return Err(QmbedError::InvalidSector(format!(
340 "{family} particle sector exceeds the local capacity"
341 )));
342 }
343 sectors.sort_unstable();
344 sectors.dedup();
345 Ok(sectors)
346}
347
348fn selected_sectors_preserve_changes(
349 fixed: Option<usize>,
350 sectors: Option<&[usize]>,
351 maximum: usize,
352 changes: &[i32],
353) -> bool {
354 let contains = |sector: usize| {
355 sectors.map_or_else(
356 || fixed == Some(sector),
357 |selected| selected.binary_search(§or).is_ok(),
358 )
359 };
360 let preserves_source = |source: usize| {
361 changes.iter().copied().all(|change| {
362 let target = source as i128 + i128::from(change);
363 target < 0 || target > maximum as i128 || contains(target as usize)
364 })
365 };
366 match (fixed, sectors) {
367 (_, Some(sectors)) => sectors.iter().copied().all(preserves_source),
368 (Some(fixed), None) => preserves_source(fixed),
369 (None, None) => true,
370 }
371}
372
373fn fixed_weight_states(sites: usize, particles: Option<usize>) -> Result<Vec<u128>> {
374 if sites > 128 {
375 return Err(QmbedError::UnsupportedBackend(
376 "the initial u128 state backend supports at most 128 orbitals".into(),
377 ));
378 }
379 if particles.is_some_and(|count| count > sites) {
380 return Err(QmbedError::InvalidSector(
381 "particle count exceeds site count".into(),
382 ));
383 }
384 let Some(particles) = particles else {
385 let limit = 1_u128
386 .checked_shl(u32::try_from(sites).unwrap_or(u32::MAX))
387 .ok_or_else(|| {
388 QmbedError::UnsupportedBackend(
389 "enumerating the unconstrained 128-site Hilbert space is infeasible".into(),
390 )
391 })?;
392 return Ok((0..limit).collect());
393 };
394 if particles == 0 {
395 return Ok(vec![0]);
396 }
397 if particles == sites {
398 let state = if sites == 128 {
399 u128::MAX
400 } else {
401 (1_u128 << sites) - 1
402 };
403 return Ok(vec![state]);
404 }
405
406 let mut state = (1_u128 << particles) - 1;
409 let limit = (sites < 128).then(|| 1_u128 << sites);
410 let mut states = Vec::new();
411 loop {
412 states.push(state);
413 let low_bit = state & state.wrapping_neg();
414 let Some(ripple) = state.checked_add(low_bit) else {
415 break;
416 };
417 let next = (((ripple ^ state) >> 2) / low_bit) | ripple;
418 if limit.is_some_and(|upper| next >= upper) {
419 break;
420 }
421 state = next;
422 }
423 Ok(states)
424}
425
426fn fixed_weight_sector_states(
427 sites: usize,
428 particles: Option<usize>,
429 particle_sectors: Option<&[usize]>,
430) -> Result<Vec<u128>> {
431 let Some(sectors) = particle_sectors else {
432 return fixed_weight_states(sites, particles);
433 };
434 let mut states = Vec::new();
435 for §or in sectors {
436 states.extend(fixed_weight_states(sites, Some(sector))?);
437 }
438 states.sort_unstable();
439 Ok(states)
440}
441
442fn fixed_digit_sum_states(
443 sites: usize,
444 states_per_site: usize,
445 total: Option<usize>,
446) -> Result<Vec<u128>> {
447 if sites == 0 || states_per_site == 0 {
448 return Err(QmbedError::InvalidSector(
449 "sites and local state count must be positive".into(),
450 ));
451 }
452 if total.is_some_and(|value| value > sites.saturating_mul(states_per_site - 1)) {
453 return Err(QmbedError::InvalidSector(
454 "requested occupation exceeds the local spin capacity".into(),
455 ));
456 }
457 let base = states_per_site as u128;
458 let exponent = u32::try_from(sites)
459 .map_err(|_| QmbedError::UnsupportedBackend("site count is too large".into()))?;
460 let limit = base.checked_pow(exponent).ok_or_else(|| {
461 QmbedError::UnsupportedBackend("mixed-radix state encoding overflow".into())
462 })?;
463 if total.is_none() {
464 return Ok((0..limit).collect());
465 }
466
467 fn enumerate(
468 site: usize,
469 sites: usize,
470 states_per_site: usize,
471 remaining: usize,
472 place: u128,
473 encoded: u128,
474 output: &mut Vec<u128>,
475 ) {
476 if site == sites {
477 if remaining == 0 {
478 output.push(encoded);
479 }
480 return;
481 }
482 let remaining_sites = sites - site - 1;
483 let maximum_tail = remaining_sites.saturating_mul(states_per_site - 1);
484 for digit in 0..states_per_site {
485 if digit > remaining || remaining - digit > maximum_tail {
486 continue;
487 }
488 enumerate(
489 site + 1,
490 sites,
491 states_per_site,
492 remaining - digit,
493 place * states_per_site as u128,
494 encoded + digit as u128 * place,
495 output,
496 );
497 }
498 }
499
500 let mut states = Vec::new();
501 enumerate(
502 0,
503 sites,
504 states_per_site,
505 total.unwrap_or_default(),
506 1,
507 0,
508 &mut states,
509 );
510 states.sort_unstable();
511 Ok(states)
512}
513
514fn fixed_digit_sum_sector_states(
515 sites: usize,
516 states_per_site: usize,
517 total: Option<usize>,
518 particle_sectors: Option<&[usize]>,
519) -> Result<Vec<u128>> {
520 let Some(sectors) = particle_sectors else {
521 return fixed_digit_sum_states(sites, states_per_site, total);
522 };
523 let mut states = Vec::new();
524 for §or in sectors {
525 states.extend(fixed_digit_sum_states(
526 sites,
527 states_per_site,
528 Some(sector),
529 )?);
530 }
531 states.sort_unstable();
532 Ok(states)
533}
534
535#[derive(Clone, Debug, Eq, PartialEq)]
542pub struct LocalOccupationConstraint {
543 species: usize,
544 allowed_local_states: Vec<usize>,
545}
546
547impl LocalOccupationConstraint {
548 pub fn new(
549 species: usize,
550 allowed_local_states: impl IntoIterator<Item = usize>,
551 ) -> Result<Self> {
552 let local_dimension = 1_usize
553 .checked_shl(u32::try_from(species).unwrap_or(u32::MAX))
554 .ok_or_else(|| {
555 QmbedError::UnsupportedBackend(
556 "the local binary-species dimension exceeds usize".into(),
557 )
558 })?;
559 if species == 0 {
560 return Err(QmbedError::InvalidSector(
561 "a local occupation constraint needs at least one species".into(),
562 ));
563 }
564 let mut allowed_local_states = allowed_local_states.into_iter().collect::<Vec<_>>();
565 if allowed_local_states.is_empty() {
566 return Err(QmbedError::InvalidSector(
567 "a local occupation constraint must allow at least one state".into(),
568 ));
569 }
570 if allowed_local_states
571 .iter()
572 .any(|&state| state >= local_dimension)
573 {
574 return Err(QmbedError::InvalidSector(
575 "an allowed local occupation is outside the binary-species space".into(),
576 ));
577 }
578 allowed_local_states.sort_unstable();
579 allowed_local_states.dedup();
580 Ok(Self {
581 species,
582 allowed_local_states,
583 })
584 }
585
586 pub const fn species(&self) -> usize {
587 self.species
588 }
589
590 pub fn allowed_local_states(&self) -> &[usize] {
591 &self.allowed_local_states
592 }
593
594 pub fn accepts_packed_state(&self, state: u128, sites: usize) -> Result<bool> {
595 let orbitals = self.species.checked_mul(sites).ok_or_else(|| {
596 QmbedError::UnsupportedBackend("binary-species orbital count overflow".into())
597 })?;
598 if orbitals > 128 {
599 return Err(QmbedError::UnsupportedBackend(
600 "the packed local-constraint backend supports at most 128 orbitals".into(),
601 ));
602 }
603 for site in 0..sites {
604 let mut local_state = 0_usize;
605 for species in 0..self.species {
606 let orbital = species * sites + site;
607 if state & (1_u128 << orbital) != 0 {
608 local_state |= 1_usize << species;
609 }
610 }
611 if self
612 .allowed_local_states
613 .binary_search(&local_state)
614 .is_err()
615 {
616 return Ok(false);
617 }
618 }
619 Ok(true)
620 }
621}
622
623fn state_index(states: &[u128], state: u128) -> Result<usize> {
624 states
625 .binary_search(&state)
626 .map_err(|_| QmbedError::StateNotInBasis)
627}
628
629fn direct_state_index(states: &[u128], state: u128) -> Result<usize> {
630 let index = usize::try_from(state).map_err(|_| QmbedError::StateNotInBasis)?;
631 if index < states.len() {
632 Ok(index)
633 } else {
634 Err(QmbedError::StateNotInBasis)
635 }
636}
637
638fn fixed_weight_state_index(state: u128, sites: usize, particles: usize) -> Result<usize> {
641 if particles > sites
642 || state.count_ones() as usize != particles
643 || (sites < 128 && state >= (1_u128 << sites))
644 {
645 return Err(QmbedError::StateNotInBasis);
646 }
647 let mut rank = 0_usize;
648 let mut ordinal = 1_usize;
649 let mut remaining = state;
650 while remaining != 0 {
651 let position = remaining.trailing_zeros() as usize;
652 if position >= ordinal {
653 rank = rank.saturating_add(binomial(position, ordinal));
654 }
655 ordinal += 1;
656 remaining &= remaining - 1;
657 }
658 Ok(rank)
659}
660
661fn rotate_lattice_state(state: u128, shift: usize, sites: usize, base: u128) -> u128 {
662 if sites == 0 {
663 return state;
664 }
665 let shift = shift % sites;
666 if shift == 0 {
667 return state;
668 }
669 if base == 2 {
670 let mask = if sites == 128 {
671 u128::MAX
672 } else {
673 (1_u128 << sites) - 1
674 };
675 return ((state << shift) & mask) | (state >> (sites - shift));
676 }
677 let mut translated = 0_u128;
678 let mut source_place = 1_u128;
679 for site in 0..sites {
680 let digit = (state / source_place) % base;
681 let target_site = (site + shift) % sites;
682 translated += digit * base.pow(u32::try_from(target_site).unwrap_or(u32::MAX));
683 source_place *= base;
684 }
685 translated
686}
687
688fn reflect_lattice_state(state: u128, sites: usize, base: u128) -> u128 {
689 let mut reflected = 0_u128;
690 let mut source_place = 1_u128;
691 for site in 0..sites {
692 let digit = (state / source_place) % base;
693 let target_site = sites - site - 1;
694 reflected += digit * base.pow(u32::try_from(target_site).unwrap_or(u32::MAX));
695 source_place *= base;
696 }
697 reflected
698}
699
700#[derive(Clone, Copy, Debug)]
701struct SymmetryImage {
702 representative: u128,
703 phase: Complex64,
704 orbit_size: usize,
705}
706
707type SymmetrySectorData = (
708 Vec<u128>,
709 Vec<usize>,
710 HashMap<u128, SymmetryImage>,
711 Option<usize>,
712 Option<i8>,
713);
714
715fn spin_symmetry_sector(
716 parent_states: Vec<u128>,
717 sites: usize,
718 base: u128,
719 momentum: Option<i32>,
720 parity: Option<i8>,
721) -> Result<SymmetrySectorData> {
722 if sites == 0 {
723 return Err(QmbedError::InvalidSector(
724 "symmetry sectors require at least one site".into(),
725 ));
726 }
727 if parity.is_some_and(|value| value != -1 && value != 1) {
728 return Err(QmbedError::InvalidSector(
729 "parity must be either -1 or +1".into(),
730 ));
731 }
732 let sites_i64 = i64::try_from(sites)
733 .map_err(|_| QmbedError::UnsupportedBackend("site count is too large".into()))?;
734 let normalized_momentum = momentum.map(|value| i64::from(value).rem_euclid(sites_i64) as usize);
735 if parity.is_some() && normalized_momentum.is_some_and(|value| value != 0 && 2 * value != sites)
736 {
737 return Err(QmbedError::IncompatibleSymmetry(
738 "parity can share a one-dimensional sector with momentum only at k=0 or k=pi".into(),
739 ));
740 }
741
742 if momentum.is_none() && parity.is_none() {
743 let orbit_sizes = vec![1; parent_states.len()];
744 let lookup = parent_states
745 .iter()
746 .copied()
747 .map(|state| {
748 (
749 state,
750 SymmetryImage {
751 representative: state,
752 phase: Complex64::new(1.0, 0.0),
753 orbit_size: 1,
754 },
755 )
756 })
757 .collect();
758 return Ok((parent_states, orbit_sizes, lookup, None, None));
759 }
760
761 let parent_lookup: HashSet<_> = parent_states.iter().copied().collect();
762 let translations = if momentum.is_some() { sites } else { 1 };
763 let mut visited = HashSet::with_capacity(parent_states.len());
764 let mut sectors = Vec::<(u128, usize)>::new();
765 let mut lookup = HashMap::with_capacity(parent_states.len());
766
767 for seed in parent_states {
768 if visited.contains(&seed) {
769 continue;
770 }
771 let mut orbit = HashSet::new();
772 for shift in 0..translations {
773 let translated = rotate_lattice_state(seed, shift, sites, base);
774 orbit.insert(translated);
775 if parity.is_some() {
776 orbit.insert(reflect_lattice_state(translated, sites, base));
777 }
778 }
779 if orbit.iter().any(|state| !parent_lookup.contains(state)) {
780 return Err(QmbedError::IncompatibleSymmetry(
781 "symmetry map leaves the selected magnetization sector".into(),
782 ));
783 }
784 visited.extend(orbit.iter().copied());
785 let representative = *orbit
786 .iter()
787 .min()
788 .ok_or_else(|| QmbedError::InvalidSector("symmetry generated an empty orbit".into()))?;
789
790 let mut coefficients = HashMap::<u128, Complex64>::new();
791 for shift in 0..translations {
792 let angle = normalized_momentum.map_or(0.0, |value| {
793 -std::f64::consts::TAU * (value * shift) as f64 / sites as f64
794 });
795 let character = Complex64::from_polar(1.0, angle);
796 let translated = rotate_lattice_state(representative, shift, sites, base);
797 *coefficients
798 .entry(translated)
799 .or_insert(Complex64::new(0.0, 0.0)) += character;
800 if let Some(parity_value) = parity {
801 let reflected = reflect_lattice_state(translated, sites, base);
802 *coefficients
803 .entry(reflected)
804 .or_insert(Complex64::new(0.0, 0.0)) += f64::from(parity_value) * character;
805 }
806 }
807 coefficients.retain(|_, coefficient| coefficient.norm() > 1.0e-12);
808 if coefficients.is_empty() {
809 continue;
810 }
811 let representative_coefficient =
812 coefficients
813 .get(&representative)
814 .copied()
815 .ok_or(QmbedError::IncompatibleSymmetry(
816 "symmetry projection removed its orbit representative".into(),
817 ))?;
818 let gauge = representative_coefficient / representative_coefficient.norm();
819 let norm = coefficients
820 .values()
821 .map(Complex64::norm_sqr)
822 .sum::<f64>()
823 .sqrt();
824 let orbit_size = coefficients.len();
825 let expected_magnitude = 1.0 / (orbit_size as f64).sqrt();
826 for (&state, coefficient) in &coefficients {
827 let normalized = *coefficient / (gauge * norm);
828 if (normalized.norm() - expected_magnitude).abs() > 1.0e-10 {
829 return Err(QmbedError::IncompatibleSymmetry(
830 "symmetry projection does not define a one-dimensional orbit sector".into(),
831 ));
832 }
833 lookup.insert(
834 state,
835 SymmetryImage {
836 representative,
837 phase: normalized / expected_magnitude,
838 orbit_size,
839 },
840 );
841 }
842 sectors.push((representative, orbit_size));
843 }
844
845 sectors.sort_by_key(|(representative, _)| *representative);
846 let (states, orbit_sizes) = sectors.into_iter().unzip();
847 Ok((states, orbit_sizes, lookup, normalized_momentum, parity))
848}
849
850fn translate_fermion_state(state: u128, shift: usize, sites: usize) -> (u128, f64) {
851 let normalized = shift % sites;
852 if normalized == 0 {
853 return (state, 1.0);
854 }
855 let translated = rotate_lattice_state(state, normalized, sites, 2);
856 let wrapped_mask = ((1_u128 << normalized) - 1) << (sites - normalized);
857 let wrapped = (state & wrapped_mask).count_ones() as usize;
858 let retained = state.count_ones() as usize - wrapped;
859 let sign = if wrapped * retained % 2 == 0 {
860 1.0
861 } else {
862 -1.0
863 };
864 (translated, sign)
865}
866
867type FermionTranslationSector = (
868 Vec<u128>,
869 Vec<usize>,
870 HashMap<u128, SymmetryImage>,
871 Option<usize>,
872);
873
874fn fermion_translation_sector(
875 parent_states: Vec<u128>,
876 sites: usize,
877 momentum: Option<i32>,
878) -> Result<FermionTranslationSector> {
879 if momentum.is_none() {
880 let orbit_sizes = vec![1; parent_states.len()];
881 let lookup = parent_states
882 .iter()
883 .copied()
884 .map(|state| {
885 (
886 state,
887 SymmetryImage {
888 representative: state,
889 phase: Complex64::new(1.0, 0.0),
890 orbit_size: 1,
891 },
892 )
893 })
894 .collect();
895 return Ok((parent_states, orbit_sizes, lookup, None));
896 }
897 if sites == 0 {
898 return Err(QmbedError::InvalidSector(
899 "translation sectors require at least one site".into(),
900 ));
901 }
902 let sites_i64 = i64::try_from(sites)
903 .map_err(|_| QmbedError::UnsupportedBackend("site count is too large".into()))?;
904 let normalized = i64::from(momentum.unwrap_or_default()).rem_euclid(sites_i64) as usize;
905 let mut visited = HashSet::with_capacity(parent_states.len());
906 let mut sectors = Vec::<(u128, usize)>::new();
907 let mut lookup = HashMap::with_capacity(parent_states.len());
908
909 for seed in parent_states {
910 if visited.contains(&seed) {
911 continue;
912 }
913 let orbit: HashSet<_> = (0..sites)
914 .map(|shift| translate_fermion_state(seed, shift, sites).0)
915 .collect();
916 visited.extend(orbit.iter().copied());
917 let representative = *orbit.iter().min().ok_or_else(|| {
918 QmbedError::InvalidSector("translation generated an empty fermion orbit".into())
919 })?;
920 let mut coefficients = HashMap::<u128, Complex64>::new();
921 for shift in 0..sites {
922 let (translated, sign) = translate_fermion_state(representative, shift, sites);
923 let angle = -std::f64::consts::TAU * (normalized * shift) as f64 / sites as f64;
924 *coefficients
925 .entry(translated)
926 .or_insert(Complex64::new(0.0, 0.0)) += sign * Complex64::from_polar(1.0, angle);
927 }
928 coefficients.retain(|_, coefficient| coefficient.norm() > 1.0e-12);
929 if coefficients.is_empty() {
930 continue;
931 }
932 let representative_coefficient =
933 coefficients
934 .get(&representative)
935 .copied()
936 .ok_or(QmbedError::IncompatibleSymmetry(
937 "translation projection removed its fermion representative".into(),
938 ))?;
939 let gauge = representative_coefficient / representative_coefficient.norm();
940 let norm = coefficients
941 .values()
942 .map(Complex64::norm_sqr)
943 .sum::<f64>()
944 .sqrt();
945 let orbit_size = coefficients.len();
946 let expected_magnitude = 1.0 / (orbit_size as f64).sqrt();
947 for (&state, coefficient) in &coefficients {
948 let projected = *coefficient / (gauge * norm);
949 if (projected.norm() - expected_magnitude).abs() > 1.0e-10 {
950 return Err(QmbedError::IncompatibleSymmetry(
951 "fermion translation does not define a one-dimensional orbit sector".into(),
952 ));
953 }
954 lookup.insert(
955 state,
956 SymmetryImage {
957 representative,
958 phase: projected / expected_magnitude,
959 orbit_size,
960 },
961 );
962 }
963 sectors.push((representative, orbit_size));
964 }
965 sectors.sort_by_key(|(representative, _)| *representative);
966 if sectors.is_empty() {
967 return Err(QmbedError::InvalidSector(
968 "the requested fermion momentum sector is empty".into(),
969 ));
970 }
971 let (states, orbit_sizes) = sectors.into_iter().unzip();
972 Ok((states, orbit_sizes, lookup, Some(normalized)))
973}
974
975fn checked_site(site: usize, sites: usize) -> Result<()> {
976 if site >= sites {
977 Err(QmbedError::InvalidSite { site, sites })
978 } else {
979 Ok(())
980 }
981}
982
983fn operator_chars(operator: &str, sites: &[usize]) -> Result<SmallVec<[char; 8]>> {
984 let chars: SmallVec<[char; 8]> = operator
985 .chars()
986 .filter(|character| *character != '|')
987 .collect();
988 if chars.len() != sites.len() {
989 return Err(QmbedError::InvalidCoupling(format!(
990 "operator arity {} does not match {} sites",
991 chars.len(),
992 sites.len()
993 )));
994 }
995 Ok(chars)
996}
997
998#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
1006pub enum SpinNormalization {
1007 #[default]
1009 AngularMomentum,
1010 Pauli,
1012 PauliCartesian,
1014}
1015
1016#[derive(Clone, Debug)]
1018pub struct SpinBasis1D {
1019 sites: usize,
1020 spin_twice: u16,
1021 states_per_site: u128,
1022 radix_bits: Option<u32>,
1023 up: Option<usize>,
1024 particle_sectors: Option<Vec<usize>>,
1025 normalization: SpinNormalization,
1026 place_values: Vec<u128>,
1027 z_factors: Vec<f64>,
1028 raise_factors: Vec<f64>,
1029 lower_factors: Vec<f64>,
1030 momentum: Option<usize>,
1031 parity: Option<i8>,
1032 orbit_lengths: Vec<usize>,
1033 symmetry_lookup: HashMap<u128, SymmetryImage>,
1034 states: Vec<u128>,
1035}
1036
1037impl SpinBasis1D {
1038 pub fn builder(sites: usize) -> SpinBasisBuilder {
1043 SpinBasisBuilder {
1044 sites,
1045 spin_twice: 1,
1046 up: None,
1047 particle_sectors: None,
1048 momentum: None,
1049 parity: None,
1050 normalization: SpinNormalization::AngularMomentum,
1051 }
1052 }
1053
1054 pub const fn sites(&self) -> usize {
1056 self.sites
1057 }
1058
1059 pub const fn spin_twice(&self) -> u16 {
1061 self.spin_twice
1062 }
1063
1064 pub const fn up(&self) -> Option<usize> {
1066 self.up
1067 }
1068
1069 pub fn particle_sectors(&self) -> Option<&[usize]> {
1071 self.particle_sectors.as_deref()
1072 }
1073
1074 pub const fn pauli(&self) -> bool {
1076 !matches!(self.normalization, SpinNormalization::AngularMomentum)
1077 }
1078
1079 pub const fn normalization(&self) -> SpinNormalization {
1081 self.normalization
1082 }
1083
1084 pub const fn momentum(&self) -> Option<usize> {
1086 self.momentum
1087 }
1088
1089 pub const fn parity(&self) -> Option<i8> {
1091 self.parity
1092 }
1093
1094 fn unreduced_local_transitions(
1095 &self,
1096 state: u128,
1097 operator: &str,
1098 sites: &[usize],
1099 ) -> Result<LocalTransitions<u128>> {
1100 let mut transitions = LocalTransitions::new();
1101 self.visit_unreduced_local_transitions(state, operator, sites, |target, amplitude| {
1102 transitions.push((target, amplitude));
1103 Ok(())
1104 })?;
1105 Ok(transitions)
1106 }
1107
1108 fn visit_unreduced_local_transitions<F>(
1109 &self,
1110 state: u128,
1111 operator: &str,
1112 sites: &[usize],
1113 visit: F,
1114 ) -> Result<()>
1115 where
1116 F: FnMut(u128, Complex64) -> Result<()>,
1117 {
1118 let symbols = operator_chars(operator, sites)?;
1119 self.visit_unreduced_local_transitions_with_symbols(state, &symbols, sites, visit)
1120 }
1121
1122 fn visit_unreduced_local_transitions_with_symbols<F>(
1123 &self,
1124 state: u128,
1125 symbols: &[char],
1126 sites: &[usize],
1127 mut visit: F,
1128 ) -> Result<()>
1129 where
1130 F: FnMut(u128, Complex64) -> Result<()>,
1131 {
1132 if symbols.len() != sites.len() {
1133 return Err(QmbedError::InvalidCoupling(format!(
1134 "operator arity {} does not match {} sites",
1135 symbols.len(),
1136 sites.len()
1137 )));
1138 }
1139 let mut pending = SmallVec::<[(u128, Complex64, usize); 2]>::new();
1140 pending.push((state, Complex64::new(1.0, 0.0), symbols.len()));
1141 while let Some((mut encoded, mut amplitude, mut remaining)) = pending.pop() {
1142 loop {
1143 if remaining == 0 {
1144 visit(encoded, amplitude)?;
1145 break;
1146 }
1147 let position = remaining - 1;
1148 let site = sites[position];
1149 let op = symbols[position];
1150 checked_site(site, self.sites)?;
1151 let place = self.place_values[site];
1152 let encoded_digit = self.radix_bits.map_or_else(
1153 || (encoded / place) % self.states_per_site,
1154 |bits| {
1155 (encoded >> (bits * u32::try_from(site).unwrap_or(u32::MAX)))
1156 & (self.states_per_site - 1)
1157 },
1158 );
1159 let digit =
1160 usize::try_from(encoded_digit).map_err(|_| QmbedError::StateNotInBasis)?;
1161 let raise_factor = self.raise_factors[digit];
1162 let lower_factor = self.lower_factors[digit];
1163 match op {
1164 'I' => {}
1165 'z' => {
1166 let factor = self.z_factors[digit];
1167 if factor == 0.0 {
1168 break;
1169 }
1170 amplitude *= factor;
1171 }
1172 '+' => {
1173 if raise_factor == 0.0 {
1174 break;
1175 }
1176 encoded += place;
1177 if raise_factor != 1.0 {
1178 amplitude *= raise_factor;
1179 }
1180 }
1181 '-' => {
1182 if lower_factor == 0.0 {
1183 break;
1184 }
1185 encoded -= place;
1186 if lower_factor != 1.0 {
1187 amplitude *= lower_factor;
1188 }
1189 }
1190 'x' | 'y' => {
1191 let scale = match self.normalization {
1192 SpinNormalization::AngularMomentum | SpinNormalization::Pauli => 0.5,
1193 SpinNormalization::PauliCartesian => 1.0,
1194 };
1195 let raise_phase = if op == 'x' {
1196 Complex64::new(scale, 0.0)
1197 } else {
1198 Complex64::new(0.0, -scale)
1199 };
1200 let lower_phase = if op == 'x' {
1201 Complex64::new(scale, 0.0)
1202 } else {
1203 Complex64::new(0.0, scale)
1204 };
1205 match (raise_factor != 0.0, lower_factor != 0.0) {
1206 (true, true) => {
1207 pending.push((
1208 encoded - place,
1209 amplitude * lower_phase * lower_factor,
1210 position,
1211 ));
1212 encoded += place;
1213 amplitude *= raise_phase * raise_factor;
1214 }
1215 (true, false) => {
1216 encoded += place;
1217 amplitude *= raise_phase * raise_factor;
1218 }
1219 (false, true) => {
1220 encoded -= place;
1221 amplitude *= lower_phase * lower_factor;
1222 }
1223 (false, false) => break,
1224 }
1225 }
1226 _ => return Err(QmbedError::InvalidOperator(op.to_string())),
1227 }
1228 remaining = position;
1229 }
1230 }
1231 Ok(())
1232 }
1233}
1234
1235#[derive(Clone, Debug)]
1237pub struct SpinBasisBuilder {
1238 sites: usize,
1239 spin_twice: u16,
1240 up: Option<usize>,
1241 particle_sectors: Option<Vec<usize>>,
1242 momentum: Option<i32>,
1243 parity: Option<i8>,
1244 normalization: SpinNormalization,
1245}
1246
1247impl SpinBasisBuilder {
1248 pub const fn spin_twice(mut self, spin_twice: u16) -> Self {
1252 self.spin_twice = spin_twice;
1253 self
1254 }
1255
1256 pub fn up(mut self, up: usize) -> Self {
1258 self.up = Some(up);
1259 self.particle_sectors = None;
1260 self
1261 }
1262
1263 pub fn magnetization(mut self, up: usize) -> Self {
1265 self.up = Some(up);
1266 self.particle_sectors = None;
1267 self
1268 }
1269
1270 pub fn particle_sectors(mut self, sectors: impl IntoIterator<Item = usize>) -> Self {
1272 self.particle_sectors = Some(sectors.into_iter().collect());
1273 self.up = None;
1274 self
1275 }
1276
1277 pub const fn momentum(mut self, momentum: i32) -> Self {
1282 self.momentum = Some(momentum);
1283 self
1284 }
1285
1286 pub const fn parity(mut self, parity: i8) -> Self {
1288 self.parity = Some(parity);
1289 self
1290 }
1291
1292 pub const fn pauli(mut self, pauli: bool) -> Self {
1294 self.normalization = if pauli {
1295 SpinNormalization::PauliCartesian
1296 } else {
1297 SpinNormalization::AngularMomentum
1298 };
1299 self
1300 }
1301
1302 pub const fn normalization(mut self, normalization: SpinNormalization) -> Self {
1304 self.normalization = normalization;
1305 self
1306 }
1307
1308 pub fn build(self) -> Result<SpinBasis1D> {
1313 if self.spin_twice == 0 {
1314 return Err(QmbedError::InvalidSector(
1315 "spin_twice must be positive".into(),
1316 ));
1317 }
1318 if self.normalization != SpinNormalization::AngularMomentum && self.spin_twice != 1 {
1319 return Err(QmbedError::InvalidOptions(
1320 "the Pauli convention is defined only for spin one-half".into(),
1321 ));
1322 }
1323 let states_per_site = usize::from(self.spin_twice) + 1;
1324 let maximum_particles = self.sites.saturating_mul(usize::from(self.spin_twice));
1325 let particle_sectors = self
1326 .particle_sectors
1327 .map(|sectors| canonical_particle_sectors(sectors, maximum_particles, "spin"))
1328 .transpose()?;
1329 let states_per_site_u128 = states_per_site as u128;
1330 let radix_bits = states_per_site_u128
1331 .is_power_of_two()
1332 .then_some(states_per_site_u128.trailing_zeros());
1333 let place_values = (0..self.sites)
1334 .map(|site| {
1335 states_per_site_u128
1336 .checked_pow(u32::try_from(site).unwrap_or(u32::MAX))
1337 .ok_or_else(|| {
1338 QmbedError::UnsupportedBackend(
1339 "spin-state place value exceeds the u128 backend".into(),
1340 )
1341 })
1342 })
1343 .collect::<Result<Vec<_>>>()?;
1344 let spin = f64::from(self.spin_twice) * 0.5;
1345 let mut z_factors = Vec::with_capacity(states_per_site);
1346 let mut raise_factors = Vec::with_capacity(states_per_site);
1347 let mut lower_factors = Vec::with_capacity(states_per_site);
1348 for digit in 0..states_per_site {
1349 let magnetic = digit as f64 - spin;
1350 z_factors.push(
1351 if self.normalization == SpinNormalization::AngularMomentum {
1352 magnetic
1353 } else {
1354 2.0 * magnetic
1355 },
1356 );
1357 let ladder_scale = if self.normalization == SpinNormalization::Pauli {
1358 2.0
1359 } else {
1360 1.0
1361 };
1362 raise_factors.push(
1363 if digit + 1 < states_per_site {
1364 (spin * (spin + 1.0) - magnetic * (magnetic + 1.0)).sqrt()
1365 } else {
1366 0.0
1367 } * ladder_scale,
1368 );
1369 lower_factors.push(
1370 if digit > 0 {
1371 (spin * (spin + 1.0) - magnetic * (magnetic - 1.0)).sqrt()
1372 } else {
1373 0.0
1374 } * ladder_scale,
1375 );
1376 }
1377 let parent_states = if self.spin_twice == 1 {
1378 fixed_weight_sector_states(self.sites, self.up, particle_sectors.as_deref())?
1379 } else {
1380 fixed_digit_sum_sector_states(
1381 self.sites,
1382 states_per_site,
1383 self.up,
1384 particle_sectors.as_deref(),
1385 )?
1386 };
1387 let (states, orbit_lengths, symmetry_lookup, momentum, parity) = spin_symmetry_sector(
1388 parent_states,
1389 self.sites,
1390 states_per_site_u128,
1391 self.momentum,
1392 self.parity,
1393 )?;
1394 Ok(SpinBasis1D {
1395 sites: self.sites,
1396 spin_twice: self.spin_twice,
1397 states_per_site: states_per_site_u128,
1398 radix_bits,
1399 up: self.up,
1400 particle_sectors,
1401 normalization: self.normalization,
1402 place_values,
1403 z_factors,
1404 raise_factors,
1405 lower_factors,
1406 momentum,
1407 parity,
1408 orbit_lengths,
1409 symmetry_lookup,
1410 states,
1411 })
1412 }
1413}
1414
1415impl Basis for SpinBasis1D {
1416 type State = u128;
1417
1418 fn len(&self) -> usize {
1419 self.states.len()
1420 }
1421
1422 fn state(&self, index: usize) -> Result<Self::State> {
1423 self.states
1424 .get(index)
1425 .copied()
1426 .ok_or(QmbedError::StateNotInBasis)
1427 }
1428
1429 fn index(&self, state: Self::State) -> Result<usize> {
1430 if self.momentum.is_none() && self.parity.is_none() {
1431 if self.spin_twice == 1 && self.particle_sectors.is_none() {
1432 return self.up.map_or_else(
1433 || direct_state_index(&self.states, state),
1434 |up| fixed_weight_state_index(state, self.sites, up),
1435 );
1436 }
1437 if self.up.is_none() && self.particle_sectors.is_none() {
1438 return direct_state_index(&self.states, state);
1439 }
1440 }
1441 state_index(&self.states, state)
1442 }
1443
1444 fn apply_local(
1445 &self,
1446 state: Self::State,
1447 operator: &str,
1448 sites: &[usize],
1449 ) -> Result<Option<(Self::State, Complex64)>> {
1450 let transitions = self.apply_local_transitions(state, operator, sites)?;
1451 match transitions.as_slice() {
1452 [] => Ok(None),
1453 [transition] => Ok(Some(*transition)),
1454 _ => Err(QmbedError::UnsupportedBackend(
1455 "this higher-spin local action branches; use apply_local_transitions".into(),
1456 )),
1457 }
1458 }
1459
1460 fn apply_local_transitions(
1461 &self,
1462 state: Self::State,
1463 operator: &str,
1464 sites: &[usize],
1465 ) -> Result<LocalTransitions<Self::State>> {
1466 let source_state = state;
1467 let branches = self.unreduced_local_transitions(state, operator, sites)?;
1468 if self.momentum.is_some() || self.parity.is_some() {
1469 let source_index = self.index(source_state)?;
1470 let source_orbit = self.orbit_lengths[source_index];
1471 let mut reduced = HashMap::<u128, Complex64>::new();
1472 for (encoded, mut amplitude) in branches {
1473 let Some(image) = self.symmetry_lookup.get(&encoded) else {
1474 continue;
1475 };
1476 amplitude *=
1477 (source_orbit as f64 / image.orbit_size as f64).sqrt() * image.phase.conj();
1478 *reduced
1479 .entry(image.representative)
1480 .or_insert(Complex64::new(0.0, 0.0)) += amplitude;
1481 }
1482 let mut transitions: LocalTransitions<_> = reduced
1483 .into_iter()
1484 .filter(|(_, amplitude)| amplitude.norm() > f64::EPSILON)
1485 .collect();
1486 transitions.sort_by_key(|(encoded, _)| *encoded);
1487 return Ok(transitions);
1488 }
1489 Ok(branches)
1490 }
1491
1492 fn apply_local_unreduced_transitions(
1493 &self,
1494 state: Self::State,
1495 operator: &str,
1496 sites: &[usize],
1497 ) -> Result<LocalTransitions<Self::State>> {
1498 self.unreduced_local_transitions(state, operator, sites)
1499 }
1500
1501 fn visit_local_unreduced_transitions<F>(
1502 &self,
1503 state: Self::State,
1504 operator: &str,
1505 sites: &[usize],
1506 visit: F,
1507 ) -> Result<()>
1508 where
1509 F: FnMut(Self::State, Complex64) -> Result<()>,
1510 {
1511 self.visit_unreduced_local_transitions(state, operator, sites, visit)
1512 }
1513
1514 fn visit_preparsed_local_unreduced_transitions<F>(
1515 &self,
1516 state: Self::State,
1517 _operator: &str,
1518 symbols: &[char],
1519 _split: Option<usize>,
1520 sites: &[usize],
1521 visit: F,
1522 ) -> Result<()>
1523 where
1524 F: FnMut(Self::State, Complex64) -> Result<()>,
1525 {
1526 self.visit_unreduced_local_transitions_with_symbols(state, symbols, sites, visit)
1527 }
1528
1529 fn transition_orbit_size(&self, state: Self::State) -> Result<usize> {
1530 if self.momentum.is_none() && self.parity.is_none() {
1531 return Ok(1);
1532 }
1533 Ok(self.orbit_lengths[self.index(state)?])
1534 }
1535
1536 fn reduction_image(&self, state: Self::State) -> Result<Option<ReductionImage<Self::State>>> {
1537 let Some(image) = self.symmetry_lookup.get(&state) else {
1538 return Ok(None);
1539 };
1540 Ok(Some(ReductionImage::new(
1541 image.representative,
1542 image.phase,
1543 image.orbit_size,
1544 )?))
1545 }
1546
1547 fn reduce_transition(
1548 &self,
1549 state: Self::State,
1550 source_orbit_size: usize,
1551 ) -> Result<Option<(Self::State, Complex64)>> {
1552 let Some(image) = self.symmetry_lookup.get(&state) else {
1553 return Ok(None);
1554 };
1555 Ok(Some((
1556 image.representative,
1557 (source_orbit_size as f64 / image.orbit_size as f64).sqrt() * image.phase.conj(),
1558 )))
1559 }
1560
1561 fn index_transition(
1562 &self,
1563 state: Self::State,
1564 source_orbit_size: usize,
1565 ) -> Result<Option<(usize, Complex64)>> {
1566 if self.momentum.is_none() && self.parity.is_none() {
1567 return match self.index(state) {
1568 Ok(index) => Ok(Some((index, Complex64::new(1.0, 0.0)))),
1569 Err(QmbedError::StateNotInBasis) => Ok(None),
1570 Err(error) => Err(error),
1571 };
1572 }
1573 let Some(image) = self.symmetry_lookup.get(&state) else {
1574 return Ok(None);
1575 };
1576 Ok(Some((
1577 self.index(image.representative)?,
1578 (source_orbit_size as f64 / image.orbit_size as f64).sqrt() * image.phase.conj(),
1579 )))
1580 }
1581
1582 fn operator_preserves_particle_sector(&self, operator: &str) -> Result<bool> {
1583 Ok(selected_sectors_preserve_changes(
1584 self.up,
1585 self.particle_sectors.as_deref(),
1586 self.sites.saturating_mul(usize::from(self.spin_twice)),
1587 &operator_number_changes(operator)?,
1588 ))
1589 }
1590}
1591
1592#[derive(Clone, Debug)]
1594pub struct BosonBasis1D {
1595 sites: usize,
1596 particles: Option<usize>,
1597 particle_sectors: Option<Vec<usize>>,
1598 states_per_site: usize,
1599 states: Vec<u128>,
1600}
1601
1602impl BosonBasis1D {
1603 pub fn builder(sites: usize, states_per_site: usize) -> BosonBasisBuilder {
1608 BosonBasisBuilder {
1609 sites,
1610 particles: None,
1611 particle_sectors: None,
1612 states_per_site,
1613 }
1614 }
1615
1616 pub const fn sites(&self) -> usize {
1618 self.sites
1619 }
1620
1621 pub const fn particles(&self) -> Option<usize> {
1623 self.particles
1624 }
1625
1626 pub fn particle_sectors(&self) -> Option<&[usize]> {
1628 self.particle_sectors.as_deref()
1629 }
1630
1631 pub const fn states_per_site(&self) -> usize {
1633 self.states_per_site
1634 }
1635
1636 fn apply_local_symbols(
1637 &self,
1638 mut state: u128,
1639 symbols: &[char],
1640 sites: &[usize],
1641 ) -> Result<Option<(u128, Complex64)>> {
1642 if symbols.len() != sites.len() {
1643 return Err(QmbedError::InvalidCoupling(format!(
1644 "operator arity {} does not match {} sites",
1645 symbols.len(),
1646 sites.len()
1647 )));
1648 }
1649 let base = self.states_per_site as u128;
1650 let mut amplitude = Complex64::new(1.0, 0.0);
1651 for (&site, &op) in sites.iter().zip(symbols).rev() {
1652 checked_site(site, self.sites)?;
1653 let place = base.pow(u32::try_from(site).unwrap_or(u32::MAX));
1654 let occupation = (state / place) % base;
1655 match op {
1656 'I' => {}
1657 'n' => amplitude *= occupation as f64,
1658 'z' => {
1659 amplitude *=
1660 occupation as f64 - 0.5 * (self.states_per_site.saturating_sub(1)) as f64;
1661 }
1662 '+' if occupation + 1 < base => {
1663 state += place;
1664 amplitude *= ((occupation + 1) as f64).sqrt();
1665 }
1666 '-' if occupation > 0 => {
1667 state -= place;
1668 amplitude *= (occupation as f64).sqrt();
1669 }
1670 '+' | '-' => return Ok(None),
1671 _ => return Err(QmbedError::InvalidOperator(op.to_string())),
1672 }
1673 }
1674 Ok(Some((state, amplitude)))
1675 }
1676}
1677
1678#[derive(Clone, Debug)]
1680pub struct BosonBasisBuilder {
1681 sites: usize,
1682 particles: Option<usize>,
1683 particle_sectors: Option<Vec<usize>>,
1684 states_per_site: usize,
1685}
1686
1687impl BosonBasisBuilder {
1688 pub fn particles(mut self, particles: usize) -> Self {
1690 self.particles = Some(particles);
1691 self.particle_sectors = None;
1692 self
1693 }
1694
1695 pub fn particle_sectors(mut self, sectors: impl IntoIterator<Item = usize>) -> Self {
1697 self.particle_sectors = Some(sectors.into_iter().collect());
1698 self.particles = None;
1699 self
1700 }
1701
1702 pub fn build(self) -> Result<BosonBasis1D> {
1704 if self.sites == 0 || self.states_per_site == 0 {
1705 return Err(QmbedError::InvalidSector(
1706 "boson sites and states_per_site must be positive".into(),
1707 ));
1708 }
1709 let maximum_particles = self.sites * (self.states_per_site - 1);
1710 if self
1711 .particles
1712 .is_some_and(|count| count > maximum_particles)
1713 {
1714 return Err(QmbedError::InvalidSector(
1715 "particle count exceeds the local cutoff".into(),
1716 ));
1717 }
1718 let particle_sectors = self
1719 .particle_sectors
1720 .map(|sectors| canonical_particle_sectors(sectors, maximum_particles, "boson"))
1721 .transpose()?;
1722 let states = fixed_digit_sum_sector_states(
1723 self.sites,
1724 self.states_per_site,
1725 self.particles,
1726 particle_sectors.as_deref(),
1727 )?;
1728 if states.is_empty() {
1729 return Err(QmbedError::InvalidSector("empty boson sector".into()));
1730 }
1731 Ok(BosonBasis1D {
1732 sites: self.sites,
1733 particles: self.particles,
1734 particle_sectors,
1735 states_per_site: self.states_per_site,
1736 states,
1737 })
1738 }
1739}
1740
1741impl Basis for BosonBasis1D {
1742 type State = u128;
1743
1744 fn len(&self) -> usize {
1745 self.states.len()
1746 }
1747
1748 fn state(&self, index: usize) -> Result<Self::State> {
1749 self.states
1750 .get(index)
1751 .copied()
1752 .ok_or(QmbedError::StateNotInBasis)
1753 }
1754
1755 fn index(&self, state: Self::State) -> Result<usize> {
1756 if self.particles.is_none() && self.particle_sectors.is_none() {
1757 return direct_state_index(&self.states, state);
1758 }
1759 state_index(&self.states, state)
1760 }
1761
1762 fn apply_local(
1763 &self,
1764 state: Self::State,
1765 operator: &str,
1766 sites: &[usize],
1767 ) -> Result<Option<(Self::State, Complex64)>> {
1768 let symbols = operator_chars(operator, sites)?;
1769 self.apply_local_symbols(state, &symbols, sites)
1770 }
1771
1772 fn visit_preparsed_local_unreduced_transitions<F>(
1773 &self,
1774 state: Self::State,
1775 _operator: &str,
1776 symbols: &[char],
1777 _split: Option<usize>,
1778 sites: &[usize],
1779 mut visit: F,
1780 ) -> Result<()>
1781 where
1782 F: FnMut(Self::State, Complex64) -> Result<()>,
1783 {
1784 if let Some((target, amplitude)) = self.apply_local_symbols(state, symbols, sites)? {
1785 visit(target, amplitude)?;
1786 }
1787 Ok(())
1788 }
1789
1790 fn operator_preserves_particle_sector(&self, operator: &str) -> Result<bool> {
1791 Ok(selected_sectors_preserve_changes(
1792 self.particles,
1793 self.particle_sectors.as_deref(),
1794 self.sites * (self.states_per_site - 1),
1795 &operator_number_changes(operator)?,
1796 ))
1797 }
1798}
1799
1800#[derive(Clone, Debug)]
1802pub struct SpinlessFermionBasis1D {
1803 sites: usize,
1804 particles: Option<usize>,
1805 particle_sectors: Option<Vec<usize>>,
1806 momentum: Option<usize>,
1807 orbit_lengths: Vec<usize>,
1808 symmetry_lookup: HashMap<u128, SymmetryImage>,
1809 states: Vec<u128>,
1810}
1811
1812impl SpinlessFermionBasis1D {
1813 pub fn builder(sites: usize) -> SpinlessFermionBasisBuilder {
1815 SpinlessFermionBasisBuilder {
1816 sites,
1817 particles: None,
1818 particle_sectors: None,
1819 momentum: None,
1820 }
1821 }
1822
1823 pub const fn sites(&self) -> usize {
1825 self.sites
1826 }
1827
1828 pub const fn particles(&self) -> Option<usize> {
1830 self.particles
1831 }
1832
1833 pub fn particle_sectors(&self) -> Option<&[usize]> {
1835 self.particle_sectors.as_deref()
1836 }
1837
1838 pub const fn momentum(&self) -> Option<usize> {
1840 self.momentum
1841 }
1842
1843 fn unreduced_local_transition(
1844 &self,
1845 state: u128,
1846 operator: &str,
1847 sites: &[usize],
1848 ) -> Result<Option<(u128, Complex64)>> {
1849 let symbols = operator_chars(operator, sites)?;
1850 self.unreduced_local_transition_with_symbols(state, &symbols, sites)
1851 }
1852
1853 fn unreduced_local_transition_with_symbols(
1854 &self,
1855 mut state: u128,
1856 symbols: &[char],
1857 sites: &[usize],
1858 ) -> Result<Option<(u128, Complex64)>> {
1859 if symbols.len() != sites.len() {
1860 return Err(QmbedError::InvalidCoupling(format!(
1861 "operator arity {} does not match {} sites",
1862 symbols.len(),
1863 sites.len()
1864 )));
1865 }
1866 let mut amplitude = Complex64::new(1.0, 0.0);
1867 for (&site, &op) in sites.iter().zip(symbols).rev() {
1868 checked_site(site, self.sites)?;
1869 let Some((next, local)) = apply_fermion(state, site, op)? else {
1870 return Ok(None);
1871 };
1872 state = next;
1873 amplitude *= local;
1874 }
1875 Ok(Some((state, amplitude)))
1876 }
1877}
1878
1879#[derive(Clone, Debug)]
1881pub struct SpinlessFermionBasisBuilder {
1882 sites: usize,
1883 particles: Option<usize>,
1884 particle_sectors: Option<Vec<usize>>,
1885 momentum: Option<i32>,
1886}
1887
1888impl SpinlessFermionBasisBuilder {
1889 pub fn particles(mut self, particles: usize) -> Self {
1891 self.particles = Some(particles);
1892 self.particle_sectors = None;
1893 self
1894 }
1895
1896 pub fn particle_sectors(mut self, sectors: impl IntoIterator<Item = usize>) -> Self {
1898 self.particle_sectors = Some(sectors.into_iter().collect());
1899 self.particles = None;
1900 self
1901 }
1902
1903 pub const fn momentum(mut self, momentum: i32) -> Self {
1905 self.momentum = Some(momentum);
1906 self
1907 }
1908
1909 pub fn build(self) -> Result<SpinlessFermionBasis1D> {
1911 let particle_sectors = self
1912 .particle_sectors
1913 .map(|sectors| canonical_particle_sectors(sectors, self.sites, "fermion"))
1914 .transpose()?;
1915 let parent_states =
1916 fixed_weight_sector_states(self.sites, self.particles, particle_sectors.as_deref())?;
1917 let (states, orbit_lengths, symmetry_lookup, momentum) =
1918 fermion_translation_sector(parent_states, self.sites, self.momentum)?;
1919 Ok(SpinlessFermionBasis1D {
1920 sites: self.sites,
1921 particles: self.particles,
1922 particle_sectors,
1923 momentum,
1924 orbit_lengths,
1925 symmetry_lookup,
1926 states,
1927 })
1928 }
1929}
1930
1931fn apply_fermion(mut state: u128, orbital: usize, op: char) -> Result<Option<(u128, Complex64)>> {
1932 let mask = 1_u128 << orbital;
1933 let occupied = state & mask != 0;
1934 let prior_mask = mask - 1;
1935 let sign = if (state & prior_mask).count_ones() % 2 == 0 {
1936 1.0
1937 } else {
1938 -1.0
1939 };
1940 let amplitude = match op {
1941 'I' => 1.0,
1942 'n' => return Ok(occupied.then_some((state, Complex64::new(1.0, 0.0)))),
1943 'z' => {
1944 return Ok(Some((
1945 state,
1946 Complex64::new(if occupied { 0.5 } else { -0.5 }, 0.0),
1947 )));
1948 }
1949 '+' if !occupied => {
1950 state |= mask;
1951 sign
1952 }
1953 '-' if occupied => {
1954 state &= !mask;
1955 sign
1956 }
1957 'x' => {
1958 state ^= mask;
1959 sign
1960 }
1961 'y' => {
1962 state ^= mask;
1963 return Ok(Some((
1964 state,
1965 Complex64::new(0.0, if occupied { -sign } else { sign }),
1966 )));
1967 }
1968 '+' | '-' => return Ok(None),
1969 _ => return Err(QmbedError::InvalidOperator(op.to_string())),
1970 };
1971 Ok(Some((state, Complex64::new(amplitude, 0.0))))
1972}
1973
1974impl Basis for SpinlessFermionBasis1D {
1975 type State = u128;
1976
1977 fn len(&self) -> usize {
1978 self.states.len()
1979 }
1980
1981 fn state(&self, index: usize) -> Result<Self::State> {
1982 self.states
1983 .get(index)
1984 .copied()
1985 .ok_or(QmbedError::StateNotInBasis)
1986 }
1987
1988 fn index(&self, state: Self::State) -> Result<usize> {
1989 if self.momentum.is_none() {
1990 if self.particle_sectors.is_some() {
1991 return state_index(&self.states, state);
1992 }
1993 return self.particles.map_or_else(
1994 || direct_state_index(&self.states, state),
1995 |particles| fixed_weight_state_index(state, self.sites, particles),
1996 );
1997 }
1998 state_index(&self.states, state)
1999 }
2000
2001 fn apply_local(
2002 &self,
2003 mut state: Self::State,
2004 operator: &str,
2005 sites: &[usize],
2006 ) -> Result<Option<(Self::State, Complex64)>> {
2007 let source_state = state;
2008 let chars = operator_chars(operator, sites)?;
2009 let mut amplitude = Complex64::new(1.0, 0.0);
2010 for (&site, op) in sites.iter().zip(chars).rev() {
2011 checked_site(site, self.sites)?;
2012 let Some((next, local)) = apply_fermion(state, site, op)? else {
2013 return Ok(None);
2014 };
2015 state = next;
2016 amplitude *= local;
2017 }
2018 if self.momentum.is_some() {
2019 let source_index = self.index(source_state)?;
2020 let source_orbit = self.orbit_lengths[source_index];
2021 let Some(image) = self.symmetry_lookup.get(&state) else {
2022 return Ok(None);
2023 };
2024 amplitude *=
2025 (source_orbit as f64 / image.orbit_size as f64).sqrt() * image.phase.conj();
2026 state = image.representative;
2027 }
2028 Ok(Some((state, amplitude)))
2029 }
2030
2031 fn apply_local_unreduced_transitions(
2032 &self,
2033 state: Self::State,
2034 operator: &str,
2035 sites: &[usize],
2036 ) -> Result<LocalTransitions<Self::State>> {
2037 Ok(self
2038 .unreduced_local_transition(state, operator, sites)?
2039 .into_iter()
2040 .collect())
2041 }
2042
2043 fn visit_local_unreduced_transitions<F>(
2044 &self,
2045 state: Self::State,
2046 operator: &str,
2047 sites: &[usize],
2048 mut visit: F,
2049 ) -> Result<()>
2050 where
2051 F: FnMut(Self::State, Complex64) -> Result<()>,
2052 {
2053 if let Some((target, amplitude)) =
2054 self.unreduced_local_transition(state, operator, sites)?
2055 {
2056 visit(target, amplitude)?;
2057 }
2058 Ok(())
2059 }
2060
2061 fn visit_preparsed_local_unreduced_transitions<F>(
2062 &self,
2063 state: Self::State,
2064 _operator: &str,
2065 symbols: &[char],
2066 _split: Option<usize>,
2067 sites: &[usize],
2068 mut visit: F,
2069 ) -> Result<()>
2070 where
2071 F: FnMut(Self::State, Complex64) -> Result<()>,
2072 {
2073 if let Some((target, amplitude)) =
2074 self.unreduced_local_transition_with_symbols(state, symbols, sites)?
2075 {
2076 visit(target, amplitude)?;
2077 }
2078 Ok(())
2079 }
2080
2081 fn transition_orbit_size(&self, state: Self::State) -> Result<usize> {
2082 if self.momentum.is_none() {
2083 return Ok(1);
2084 }
2085 Ok(self.orbit_lengths[self.index(state)?])
2086 }
2087
2088 fn reduction_image(&self, state: Self::State) -> Result<Option<ReductionImage<Self::State>>> {
2089 let Some(image) = self.symmetry_lookup.get(&state) else {
2090 return Ok(None);
2091 };
2092 Ok(Some(ReductionImage::new(
2093 image.representative,
2094 image.phase,
2095 image.orbit_size,
2096 )?))
2097 }
2098
2099 fn reduce_transition(
2100 &self,
2101 state: Self::State,
2102 source_orbit_size: usize,
2103 ) -> Result<Option<(Self::State, Complex64)>> {
2104 let Some(image) = self.symmetry_lookup.get(&state) else {
2105 return Ok(None);
2106 };
2107 Ok(Some((
2108 image.representative,
2109 (source_orbit_size as f64 / image.orbit_size as f64).sqrt() * image.phase.conj(),
2110 )))
2111 }
2112
2113 fn index_transition(
2114 &self,
2115 state: Self::State,
2116 source_orbit_size: usize,
2117 ) -> Result<Option<(usize, Complex64)>> {
2118 if self.momentum.is_none() {
2119 return match self.index(state) {
2120 Ok(index) => Ok(Some((index, Complex64::new(1.0, 0.0)))),
2121 Err(QmbedError::StateNotInBasis) => Ok(None),
2122 Err(error) => Err(error),
2123 };
2124 }
2125 let Some(image) = self.symmetry_lookup.get(&state) else {
2126 return Ok(None);
2127 };
2128 Ok(Some((
2129 self.index(image.representative)?,
2130 (source_orbit_size as f64 / image.orbit_size as f64).sqrt() * image.phase.conj(),
2131 )))
2132 }
2133
2134 fn operator_preserves_particle_sector(&self, operator: &str) -> Result<bool> {
2135 Ok(selected_sectors_preserve_changes(
2136 self.particles,
2137 self.particle_sectors.as_deref(),
2138 self.sites,
2139 &operator_number_changes(operator)?,
2140 ))
2141 }
2142}
2143
2144#[derive(Clone, Debug)]
2149pub struct SpinfulFermionBasis1D {
2150 sites: usize,
2151 particles_up: Option<usize>,
2152 particles_down: Option<usize>,
2153 particle_sectors: Option<Vec<(usize, usize)>>,
2154 local_occupation_constraint: Option<LocalOccupationConstraint>,
2155 states: Vec<u128>,
2156 indices: Option<HashMap<u128, usize>>,
2157}
2158
2159impl SpinfulFermionBasis1D {
2160 pub fn builder(sites: usize) -> SpinfulFermionBasisBuilder {
2162 SpinfulFermionBasisBuilder {
2163 sites,
2164 particles_up: None,
2165 particles_down: None,
2166 particle_sectors: None,
2167 local_occupation_constraint: None,
2168 }
2169 }
2170
2171 pub const fn sites(&self) -> usize {
2173 self.sites
2174 }
2175
2176 pub const fn particles_up(&self) -> Option<usize> {
2178 self.particles_up
2179 }
2180
2181 pub const fn particles_down(&self) -> Option<usize> {
2183 self.particles_down
2184 }
2185
2186 pub fn particle_sectors(&self) -> Option<&[(usize, usize)]> {
2188 self.particle_sectors.as_deref()
2189 }
2190
2191 pub const fn local_occupation_constraint(&self) -> Option<&LocalOccupationConstraint> {
2193 self.local_occupation_constraint.as_ref()
2194 }
2195
2196 fn apply_local_symbols(
2197 &self,
2198 mut state: u128,
2199 symbols: &[char],
2200 split: Option<usize>,
2201 sites: &[usize],
2202 ) -> Result<Option<(u128, Complex64)>> {
2203 if symbols.len() != sites.len() || split.is_some_and(|value| value > symbols.len()) {
2204 return Err(QmbedError::InvalidCoupling(format!(
2205 "operator arity {} does not match {} sites",
2206 symbols.len(),
2207 sites.len()
2208 )));
2209 }
2210 let mut amplitude = Complex64::new(1.0, 0.0);
2211 for (position, (&site, &op)) in sites.iter().zip(symbols).enumerate().rev() {
2212 let orbital = match split {
2213 Some(boundary) => {
2214 checked_site(site, self.sites)?;
2215 if position < boundary {
2216 site
2217 } else {
2218 self.sites + site
2219 }
2220 }
2221 None => {
2222 let orbitals = self.sites.checked_mul(2).ok_or_else(|| {
2223 QmbedError::UnsupportedBackend("spinful orbital count is too large".into())
2224 })?;
2225 checked_site(site, orbitals)?;
2226 site
2227 }
2228 };
2229 let Some((next, local)) = apply_fermion(state, orbital, op)? else {
2230 return Ok(None);
2231 };
2232 state = next;
2233 amplitude *= local;
2234 }
2235 Ok(Some((state, amplitude)))
2236 }
2237}
2238
2239#[derive(Clone, Debug)]
2241pub struct SpinfulFermionBasisBuilder {
2242 sites: usize,
2243 particles_up: Option<usize>,
2244 particles_down: Option<usize>,
2245 particle_sectors: Option<Vec<(usize, usize)>>,
2246 local_occupation_constraint: Option<LocalOccupationConstraint>,
2247}
2248
2249impl SpinfulFermionBasisBuilder {
2250 pub const fn particles_up(mut self, particles: usize) -> Self {
2252 self.particles_up = Some(particles);
2253 self
2254 }
2255
2256 pub const fn particles_down(mut self, particles: usize) -> Self {
2258 self.particles_down = Some(particles);
2259 self
2260 }
2261
2262 pub fn particles(mut self, up: usize, down: usize) -> Self {
2264 self.particles_up = Some(up);
2265 self.particles_down = Some(down);
2266 self.particle_sectors = None;
2267 self
2268 }
2269
2270 pub fn particle_sectors(mut self, sectors: impl IntoIterator<Item = (usize, usize)>) -> Self {
2272 self.particle_sectors = Some(sectors.into_iter().collect());
2273 self.particles_up = None;
2274 self.particles_down = None;
2275 self
2276 }
2277
2278 pub fn local_occupation_constraint(mut self, constraint: LocalOccupationConstraint) -> Self {
2281 self.local_occupation_constraint = Some(constraint);
2282 self
2283 }
2284
2285 pub fn build(self) -> Result<SpinfulFermionBasis1D> {
2287 if self.sites > 64 {
2288 return Err(QmbedError::UnsupportedBackend(
2289 "the packed spinful backend supports at most 64 sites".into(),
2290 ));
2291 }
2292 if self
2293 .local_occupation_constraint
2294 .as_ref()
2295 .is_some_and(|constraint| constraint.species() != 2)
2296 {
2297 return Err(QmbedError::InvalidSector(
2298 "spinful fermions require a two-species local occupation constraint".into(),
2299 ));
2300 }
2301 let sectors = match &self.particle_sectors {
2302 Some(sectors) if sectors.is_empty() => {
2303 return Err(QmbedError::InvalidSector(
2304 "spinful particle-sector union must be nonempty".into(),
2305 ));
2306 }
2307 Some(sectors) => sectors.clone(),
2308 None => vec![(
2309 self.particles_up.unwrap_or(usize::MAX),
2310 self.particles_down.unwrap_or(usize::MAX),
2311 )],
2312 };
2313 let mut states = Vec::new();
2314 for (up_count, down_count) in sectors {
2315 let up_states =
2316 fixed_weight_states(self.sites, (up_count != usize::MAX).then_some(up_count))?;
2317 let down_states =
2318 fixed_weight_states(self.sites, (down_count != usize::MAX).then_some(down_count))?;
2319 states.reserve(up_states.len().saturating_mul(down_states.len()));
2320 for down in down_states {
2321 for &up in &up_states {
2322 states.push(up | (down << self.sites));
2323 }
2324 }
2325 }
2326 let mask = if self.sites == 128 {
2332 u128::MAX
2333 } else {
2334 (1_u128 << self.sites) - 1
2335 };
2336 states.sort_unstable_by_key(|state| ((*state & mask), (*state >> self.sites)));
2337 states.dedup();
2338 if let Some(constraint) = &self.local_occupation_constraint {
2339 let mut filtered = Vec::with_capacity(states.len());
2340 for state in states {
2341 if constraint.accepts_packed_state(state, self.sites)? {
2342 filtered.push(state);
2343 }
2344 }
2345 states = filtered;
2346 }
2347 let indices = (self.particle_sectors.is_some()
2348 || self.local_occupation_constraint.is_some())
2349 .then(|| {
2350 states
2351 .iter()
2352 .copied()
2353 .enumerate()
2354 .map(|(index, state)| (state, index))
2355 .collect()
2356 });
2357 Ok(SpinfulFermionBasis1D {
2358 sites: self.sites,
2359 particles_up: self.particles_up,
2360 particles_down: self.particles_down,
2361 particle_sectors: self.particle_sectors,
2362 local_occupation_constraint: self.local_occupation_constraint,
2363 states,
2364 indices,
2365 })
2366 }
2367}
2368
2369impl Basis for SpinfulFermionBasis1D {
2370 type State = u128;
2371
2372 fn len(&self) -> usize {
2373 self.states.len()
2374 }
2375
2376 fn state(&self, index: usize) -> Result<Self::State> {
2377 self.states
2378 .get(index)
2379 .copied()
2380 .ok_or(QmbedError::StateNotInBasis)
2381 }
2382
2383 fn index(&self, state: Self::State) -> Result<usize> {
2384 if self.particle_sectors.is_none() && self.local_occupation_constraint.is_none() {
2385 let mask = if self.sites == 128 {
2386 u128::MAX
2387 } else {
2388 (1_u128 << self.sites) - 1
2389 };
2390 let up_state = state & mask;
2391 let down_state = state >> self.sites;
2392 let up_index = self.particles_up.map_or_else(
2393 || usize::try_from(up_state).map_err(|_| QmbedError::StateNotInBasis),
2394 |particles| fixed_weight_state_index(up_state, self.sites, particles),
2395 )?;
2396 let down_index = self.particles_down.map_or_else(
2397 || usize::try_from(down_state).map_err(|_| QmbedError::StateNotInBasis),
2398 |particles| fixed_weight_state_index(down_state, self.sites, particles),
2399 )?;
2400 let down_dimension = match self.particles_down {
2401 Some(particles) => binomial(self.sites, particles),
2402 None => 1_usize
2403 .checked_shl(u32::try_from(self.sites).unwrap_or(u32::MAX))
2404 .ok_or(QmbedError::StateNotInBasis)?,
2405 };
2406 let index = up_index
2407 .checked_mul(down_dimension)
2408 .and_then(|offset| offset.checked_add(down_index))
2409 .ok_or(QmbedError::StateNotInBasis)?;
2410 if index < self.states.len() {
2411 return Ok(index);
2412 }
2413 return Err(QmbedError::StateNotInBasis);
2414 }
2415 self.indices
2416 .as_ref()
2417 .and_then(|indices| indices.get(&state).copied())
2418 .ok_or(QmbedError::StateNotInBasis)
2419 }
2420
2421 fn apply_local(
2422 &self,
2423 state: Self::State,
2424 operator: &str,
2425 sites: &[usize],
2426 ) -> Result<Option<(Self::State, Complex64)>> {
2427 let symbols = operator_chars(operator, sites)?;
2428 let split = operator
2429 .find('|')
2430 .map(|position| operator[..position].chars().count());
2431 self.apply_local_symbols(state, &symbols, split, sites)
2432 }
2433
2434 fn visit_preparsed_local_unreduced_transitions<F>(
2435 &self,
2436 state: Self::State,
2437 _operator: &str,
2438 symbols: &[char],
2439 split: Option<usize>,
2440 sites: &[usize],
2441 mut visit: F,
2442 ) -> Result<()>
2443 where
2444 F: FnMut(Self::State, Complex64) -> Result<()>,
2445 {
2446 if let Some((target, amplitude)) = self.apply_local_symbols(state, symbols, split, sites)? {
2447 visit(target, amplitude)?;
2448 }
2449 Ok(())
2450 }
2451
2452 fn operator_preserves_particle_sector(&self, operator: &str) -> Result<bool> {
2453 let (up_operator, down_operator) = operator.split_once('|').unwrap_or((operator, ""));
2454 if down_operator.contains('|') {
2455 return Err(QmbedError::InvalidOperator(operator.into()));
2456 }
2457 let Some(up_change) = operator_number_change(up_operator)? else {
2458 return Ok(self.particles_up.is_none()
2459 && self.particle_sectors.is_none()
2460 && self.particles_down.is_none());
2461 };
2462 let Some(down_change) = operator_number_change(down_operator)? else {
2463 return Ok(self.particles_up.is_none()
2464 && self.particle_sectors.is_none()
2465 && self.particles_down.is_none());
2466 };
2467 if let Some(sectors) = &self.particle_sectors {
2468 let sectors: HashSet<_> = sectors.iter().copied().collect();
2469 return Ok(sectors.iter().all(|&(up, down)| {
2470 let target_up = up as i32 + up_change;
2471 let target_down = down as i32 + down_change;
2472 target_up >= 0
2473 && target_down >= 0
2474 && target_up <= self.sites as i32
2475 && target_down <= self.sites as i32
2476 && sectors.contains(&(target_up as usize, target_down as usize))
2477 }));
2478 }
2479 Ok(self.particles_up.is_none_or(|_| up_change == 0)
2480 && self.particles_down.is_none_or(|_| down_change == 0))
2481 }
2482
2483 fn operator_preserves_particle_sector_on_sites(
2484 &self,
2485 operator: &str,
2486 sites: &[usize],
2487 ) -> Result<bool> {
2488 if operator.contains('|') {
2489 return self.operator_preserves_particle_sector(operator);
2490 }
2491 let symbols = operator_chars(operator, sites)?;
2492 let mut changes = [Some(0_i32), Some(0_i32)];
2493 for (&symbol, &orbital) in symbols.iter().zip(sites) {
2494 let orbitals = self.sites.checked_mul(2).ok_or_else(|| {
2495 QmbedError::UnsupportedBackend("spinful orbital count is too large".into())
2496 })?;
2497 checked_site(orbital, orbitals)?;
2498 let species = usize::from(orbital >= self.sites);
2499 match symbol {
2500 '+' => {
2501 if let Some(change) = &mut changes[species] {
2502 *change += 1;
2503 }
2504 }
2505 '-' => {
2506 if let Some(change) = &mut changes[species] {
2507 *change -= 1;
2508 }
2509 }
2510 'x' | 'y' => changes[species] = None,
2511 'I' | 'n' | 'z' => {}
2512 _ => return Err(QmbedError::InvalidOperator(operator.into())),
2513 }
2514 }
2515 if let Some(sectors) = &self.particle_sectors {
2516 let sectors: HashSet<_> = sectors.iter().copied().collect();
2517 return Ok(sectors.iter().all(|&(up, down)| {
2518 let Some(target_up) = changes[0].and_then(|change| (up as i32).checked_add(change))
2519 else {
2520 return false;
2521 };
2522 let Some(target_down) =
2523 changes[1].and_then(|change| (down as i32).checked_add(change))
2524 else {
2525 return false;
2526 };
2527 target_up >= 0
2528 && target_down >= 0
2529 && target_up <= self.sites as i32
2530 && target_down <= self.sites as i32
2531 && sectors.contains(&(target_up as usize, target_down as usize))
2532 }));
2533 }
2534 Ok(self
2535 .particles_up
2536 .is_none_or(|_| changes[0].is_some_and(|change| change == 0))
2537 && self
2538 .particles_down
2539 .is_none_or(|_| changes[1].is_some_and(|change| change == 0)))
2540 }
2541}
2542
2543type UserAction<State> = Arc<
2544 dyn Fn(State, usize, &mut dyn FnMut(State, Complex64) -> Result<()>) -> Result<()>
2545 + Send
2546 + Sync,
2547>;
2548type UserStateFactory<State> = Arc<dyn Fn() -> Result<Vec<State>> + Send + Sync>;
2549
2550#[derive(Clone)]
2552pub struct UserBasis<State>
2553where
2554 State: Copy + Eq + Hash + Send + Sync,
2555{
2556 sites: usize,
2557 states: Vec<State>,
2558 indices: HashMap<State, usize>,
2559 operators: HashMap<char, UserAction<State>>,
2560}
2561
2562impl<State> std::fmt::Debug for UserBasis<State>
2563where
2564 State: Copy + Eq + Hash + Send + Sync,
2565{
2566 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2567 let mut operators: Vec<_> = self.operators.keys().copied().collect();
2568 operators.sort_unstable();
2569 formatter
2570 .debug_struct("UserBasis")
2571 .field("sites", &self.sites)
2572 .field("states", &self.states.len())
2573 .field("operators", &operators)
2574 .finish()
2575 }
2576}
2577
2578impl<State> UserBasis<State>
2579where
2580 State: Copy + Eq + Hash + Send + Sync + 'static,
2581{
2582 pub fn builder(sites: usize) -> UserBasisBuilder<State> {
2583 UserBasisBuilder {
2584 sites,
2585 states: Vec::new(),
2586 state_factory: None,
2587 operators: HashMap::new(),
2588 }
2589 }
2590
2591 pub const fn sites(&self) -> usize {
2592 self.sites
2593 }
2594
2595 pub fn with_states(&self, states: impl IntoIterator<Item = State>) -> Result<UserBasis<State>> {
2603 let mut indices = HashMap::new();
2604 let states: Vec<_> = states.into_iter().collect();
2605 if states.is_empty() {
2606 return Err(QmbedError::InvalidSector(
2607 "UserBasis requires at least one accepted state".into(),
2608 ));
2609 }
2610 indices.reserve(states.len());
2611 for (index, state) in states.iter().copied().enumerate() {
2612 if indices.insert(state, index).is_some() {
2613 return Err(QmbedError::InvalidSector(
2614 "UserBasis states must be unique".into(),
2615 ));
2616 }
2617 }
2618 Ok(UserBasis {
2619 sites: self.sites,
2620 states,
2621 indices,
2622 operators: self.operators.clone(),
2623 })
2624 }
2625}
2626
2627pub struct UserBasisBuilder<State>
2628where
2629 State: Copy + Eq + Hash + Send + Sync,
2630{
2631 sites: usize,
2632 states: Vec<State>,
2633 state_factory: Option<UserStateFactory<State>>,
2634 operators: HashMap<char, UserAction<State>>,
2635}
2636
2637impl<State> UserBasisBuilder<State>
2638where
2639 State: Copy + Eq + Hash + Send + Sync + 'static,
2640{
2641 pub fn states(mut self, states: impl IntoIterator<Item = State>) -> Self {
2642 self.states = states.into_iter().collect();
2643 self.state_factory = None;
2644 self
2645 }
2646
2647 pub fn deferred_states<F>(mut self, factory: F) -> Self
2650 where
2651 F: Fn() -> Result<Vec<State>> + Send + Sync + 'static,
2652 {
2653 self.states.clear();
2654 self.state_factory = Some(Arc::new(factory));
2655 self
2656 }
2657
2658 pub fn operator<F>(mut self, name: char, action: F) -> Self
2659 where
2660 F: Fn(State, usize) -> Result<Option<(State, Complex64)>> + Send + Sync + 'static,
2661 {
2662 self.operators.insert(
2663 name,
2664 Arc::new(move |state, site, visit| {
2665 if let Some((target, amplitude)) = action(state, site)? {
2666 visit(target, amplitude)?;
2667 }
2668 Ok(())
2669 }),
2670 );
2671 self
2672 }
2673
2674 pub fn branching_operator<F>(mut self, name: char, action: F) -> Self
2676 where
2677 F: Fn(State, usize) -> Result<Vec<(State, Complex64)>> + Send + Sync + 'static,
2678 {
2679 self.operators.insert(
2680 name,
2681 Arc::new(move |state, site, visit| {
2682 for (target, amplitude) in action(state, site)? {
2683 visit(target, amplitude)?;
2684 }
2685 Ok(())
2686 }),
2687 );
2688 self
2689 }
2690
2691 pub fn build(mut self) -> Result<UserBasis<State>> {
2692 if self.states.is_empty() {
2693 if let Some(factory) = self.state_factory.take() {
2694 self.states = factory()?;
2695 }
2696 }
2697 if self.states.is_empty() {
2698 return Err(QmbedError::InvalidSector(
2699 "UserBasis requires at least one accepted state".into(),
2700 ));
2701 }
2702 let mut indices = HashMap::with_capacity(self.states.len());
2703 for (index, state) in self.states.iter().copied().enumerate() {
2704 if indices.insert(state, index).is_some() {
2705 return Err(QmbedError::InvalidSector(
2706 "UserBasis states must be unique".into(),
2707 ));
2708 }
2709 }
2710 Ok(UserBasis {
2711 sites: self.sites,
2712 states: self.states,
2713 indices,
2714 operators: self.operators,
2715 })
2716 }
2717
2718 pub fn materialize(self) -> Result<UserBasis<State>> {
2719 self.build()
2720 }
2721}
2722
2723impl UserBasisBuilder<u128> {
2724 pub fn state_filter<F>(mut self, keep: F) -> Result<Self>
2725 where
2726 F: Fn(u128) -> bool,
2727 {
2728 if self.sites > 127 {
2729 return Err(QmbedError::UnsupportedBackend(
2730 "u128 UserBasis filters support at most 127 sites".into(),
2731 ));
2732 }
2733 let limit = 1_u128 << self.sites;
2734 self.states = (0..limit).filter(|state| keep(*state)).collect();
2735 Ok(self)
2736 }
2737
2738 pub fn state_filter_parallel<F>(mut self, keep: F) -> Result<Self>
2740 where
2741 F: Fn(u128) -> bool + Sync,
2742 {
2743 if self.sites > 127 {
2744 return Err(QmbedError::UnsupportedBackend(
2745 "u128 UserBasis filters support at most 127 sites".into(),
2746 ));
2747 }
2748 let limit = 1_u128 << self.sites;
2749 let workers = std::thread::available_parallelism()
2750 .map_or(1, std::num::NonZeroUsize::get)
2751 .min(usize::try_from(limit).unwrap_or(usize::MAX).max(1));
2752 let stride = limit.div_ceil(workers as u128);
2753 let mut chunks = std::thread::scope(|scope| {
2754 let mut handles = Vec::with_capacity(workers);
2755 let keep = &keep;
2756 for worker in 0..workers {
2757 let start = worker as u128 * stride;
2758 let end = (start + stride).min(limit);
2759 handles.push(scope.spawn(move || {
2760 (start..end)
2761 .filter(|state| keep(*state))
2762 .collect::<Vec<_>>()
2763 }));
2764 }
2765 handles
2766 .into_iter()
2767 .map(|handle| handle.join().unwrap_or_default())
2768 .collect::<Vec<_>>()
2769 });
2770 self.states.clear();
2771 for chunk in &mut chunks {
2772 self.states.append(chunk);
2773 }
2774 Ok(self)
2775 }
2776}
2777
2778impl<State> UserBasis<State>
2779where
2780 State: Copy + Eq + Hash + Send + Sync + 'static,
2781{
2782 fn visit_user_transitions<F>(
2783 &self,
2784 state: State,
2785 operator: &str,
2786 sites: &[usize],
2787 visit: F,
2788 ) -> Result<()>
2789 where
2790 F: FnMut(State, Complex64) -> Result<()>,
2791 {
2792 let symbols = operator_chars(operator, sites)?;
2793 self.visit_user_transitions_with_symbols(state, &symbols, sites, visit)
2794 }
2795
2796 fn visit_user_transitions_with_symbols<F>(
2797 &self,
2798 state: State,
2799 symbols: &[char],
2800 sites: &[usize],
2801 mut visit: F,
2802 ) -> Result<()>
2803 where
2804 F: FnMut(State, Complex64) -> Result<()>,
2805 {
2806 if symbols.len() != sites.len() {
2807 return Err(QmbedError::InvalidCoupling(format!(
2808 "operator arity {} does not match {} sites",
2809 symbols.len(),
2810 sites.len()
2811 )));
2812 }
2813 self.visit_user_branch(
2814 state,
2815 Complex64::new(1.0, 0.0),
2816 symbols,
2817 sites,
2818 symbols.len(),
2819 &mut visit,
2820 )
2821 }
2822
2823 fn visit_user_branch<F>(
2824 &self,
2825 state: State,
2826 amplitude: Complex64,
2827 chars: &[char],
2828 sites: &[usize],
2829 remaining: usize,
2830 visit: &mut F,
2831 ) -> Result<()>
2832 where
2833 F: FnMut(State, Complex64) -> Result<()>,
2834 {
2835 if remaining == 0 {
2836 return visit(state, amplitude);
2837 }
2838 let position = remaining - 1;
2839 let site = sites[position];
2840 let op = chars[position];
2841 checked_site(site, self.sites)?;
2842 let action = self
2843 .operators
2844 .get(&op)
2845 .ok_or_else(|| QmbedError::InvalidOperator(op.to_string()))?;
2846 action(state, site, &mut |target, local| {
2847 if local.norm() <= f64::EPSILON {
2848 return Ok(());
2849 }
2850 self.visit_user_branch(target, amplitude * local, chars, sites, position, visit)
2851 })
2852 }
2853}
2854
2855impl<State> Basis for UserBasis<State>
2856where
2857 State: Copy + Eq + Hash + Send + Sync + 'static,
2858{
2859 type State = State;
2860
2861 fn len(&self) -> usize {
2862 self.states.len()
2863 }
2864
2865 fn state(&self, index: usize) -> Result<Self::State> {
2866 self.states
2867 .get(index)
2868 .copied()
2869 .ok_or(QmbedError::StateNotInBasis)
2870 }
2871
2872 fn index(&self, state: Self::State) -> Result<usize> {
2873 self.indices
2874 .get(&state)
2875 .copied()
2876 .ok_or(QmbedError::StateNotInBasis)
2877 }
2878
2879 fn apply_local(
2880 &self,
2881 state: Self::State,
2882 operator: &str,
2883 sites: &[usize],
2884 ) -> Result<Option<(Self::State, Complex64)>> {
2885 let transitions = self.apply_local_transitions(state, operator, sites)?;
2886 match transitions.as_slice() {
2887 [] => Ok(None),
2888 [transition] => Ok(Some(*transition)),
2889 _ => Err(QmbedError::UnsupportedBackend(
2890 "this user local action branches; use apply_local_transitions".into(),
2891 )),
2892 }
2893 }
2894
2895 fn apply_local_transitions(
2896 &self,
2897 state: Self::State,
2898 operator: &str,
2899 sites: &[usize],
2900 ) -> Result<LocalTransitions<Self::State>> {
2901 let mut accumulated = HashMap::<State, Complex64>::new();
2902 self.visit_user_transitions(state, operator, sites, |target, amplitude| {
2903 *accumulated
2904 .entry(target)
2905 .or_insert(Complex64::new(0.0, 0.0)) += amplitude;
2906 Ok(())
2907 })?;
2908 Ok(accumulated
2909 .into_iter()
2910 .filter(|(_, amplitude)| amplitude.norm() > f64::EPSILON)
2911 .collect())
2912 }
2913
2914 fn visit_local_unreduced_transitions<F>(
2915 &self,
2916 state: Self::State,
2917 operator: &str,
2918 sites: &[usize],
2919 visit: F,
2920 ) -> Result<()>
2921 where
2922 F: FnMut(Self::State, Complex64) -> Result<()>,
2923 {
2924 self.visit_user_transitions(state, operator, sites, visit)
2925 }
2926
2927 fn visit_preparsed_local_unreduced_transitions<F>(
2928 &self,
2929 state: Self::State,
2930 _operator: &str,
2931 symbols: &[char],
2932 _split: Option<usize>,
2933 sites: &[usize],
2934 visit: F,
2935 ) -> Result<()>
2936 where
2937 F: FnMut(Self::State, Complex64) -> Result<()>,
2938 {
2939 self.visit_user_transitions_with_symbols(state, symbols, sites, visit)
2940 }
2941}
2942
2943pub trait SymmetryMap<State>: Send + Sync {
2945 fn period(&self) -> usize;
2946 fn apply(&self, state: State) -> Result<(State, Complex64)>;
2947}
2948
2949#[derive(Clone, Copy, Debug, Eq, PartialEq)]
2951pub enum ExchangeStatistics {
2952 Distinguishable,
2953 Fermionic,
2954}
2955
2956#[derive(Clone, Debug)]
2967pub struct LatticeSymmetryMap {
2968 states_per_site: usize,
2969 destinations: Vec<usize>,
2970 local_permutations: Vec<Vec<usize>>,
2971 statistics: ExchangeStatistics,
2972 place_values: Option<Vec<u128>>,
2973 state_limit: Option<u128>,
2974 period: usize,
2975}
2976
2977impl LatticeSymmetryMap {
2978 pub fn new(
2979 states_per_site: usize,
2980 destinations: impl Into<Vec<usize>>,
2981 local_permutations: Option<Vec<Vec<usize>>>,
2982 statistics: ExchangeStatistics,
2983 ) -> Result<Self> {
2984 let destinations = destinations.into();
2985 if destinations.is_empty() || states_per_site == 0 {
2986 return Err(QmbedError::InvalidSector(
2987 "a lattice symmetry requires sites and local states".into(),
2988 ));
2989 }
2990 let unique_destinations: HashSet<_> = destinations.iter().copied().collect();
2991 if unique_destinations.len() != destinations.len()
2992 || destinations
2993 .iter()
2994 .any(|&destination| destination >= destinations.len())
2995 {
2996 return Err(QmbedError::IncompatibleSymmetry(
2997 "symmetry site destinations must be a bijection".into(),
2998 ));
2999 }
3000
3001 let identity = (0..states_per_site).collect::<Vec<_>>();
3002 let local_permutations =
3003 local_permutations.unwrap_or_else(|| vec![identity.clone(); destinations.len()]);
3004 if local_permutations.len() != destinations.len()
3005 || local_permutations.iter().any(|permutation| {
3006 permutation.len() != states_per_site
3007 || permutation.iter().copied().collect::<HashSet<_>>().len() != states_per_site
3008 || permutation.iter().any(|&digit| digit >= states_per_site)
3009 })
3010 {
3011 return Err(QmbedError::IncompatibleSymmetry(
3012 "every local-state map must be a permutation".into(),
3013 ));
3014 }
3015 if statistics == ExchangeStatistics::Fermionic && states_per_site != 2 {
3016 return Err(QmbedError::InvalidOptions(
3017 "fermionic lattice maps require binary occupation".into(),
3018 ));
3019 }
3020
3021 let base = states_per_site as u128;
3022 let mut packed_place_values = Vec::with_capacity(destinations.len());
3023 let mut place = Some(1_u128);
3024 for site in 0..destinations.len() {
3025 if let Some(value) = place {
3026 packed_place_values.push(value);
3027 }
3028 if site + 1 < destinations.len() {
3029 place = place.and_then(|value| value.checked_mul(base));
3030 }
3031 }
3032 let place_values =
3033 (packed_place_values.len() == destinations.len()).then_some(packed_place_values);
3034 let state_limit = place.and_then(|value| value.checked_mul(base));
3035 let exact_full_range = base.is_power_of_two()
3036 && (base.trailing_zeros() as usize).checked_mul(destinations.len()) == Some(128);
3037 let wide_binary = states_per_site == 2 && destinations.len() <= 16_384;
3038 if place_values.is_none() && !wide_binary {
3039 return Err(QmbedError::UnsupportedBackend(
3040 "lattice-symmetry state encoding exceeds the supported fixed-width backends".into(),
3041 ));
3042 }
3043 if place_values.is_some() && state_limit.is_none() && !exact_full_range {
3044 return Err(QmbedError::UnsupportedBackend(
3045 "lattice-symmetry state encoding exceeds u128".into(),
3046 ));
3047 }
3048 let period =
3049 combined_permutation_period(&destinations, &local_permutations, states_per_site)?;
3050 Ok(Self {
3051 states_per_site,
3052 destinations,
3053 local_permutations,
3054 statistics,
3055 place_values,
3056 state_limit,
3057 period,
3058 })
3059 }
3060
3061 pub fn site_permutation(
3062 states_per_site: usize,
3063 destinations: impl Into<Vec<usize>>,
3064 ) -> Result<Self> {
3065 Self::new(
3066 states_per_site,
3067 destinations,
3068 None,
3069 ExchangeStatistics::Distinguishable,
3070 )
3071 }
3072
3073 pub fn fermionic_orbital_permutation(destinations: impl Into<Vec<usize>>) -> Result<Self> {
3074 Self::new(2, destinations, None, ExchangeStatistics::Fermionic)
3075 }
3076
3077 pub fn sites(&self) -> usize {
3078 self.destinations.len()
3079 }
3080
3081 pub const fn states_per_site(&self) -> usize {
3082 self.states_per_site
3083 }
3084
3085 pub fn destinations(&self) -> &[usize] {
3086 &self.destinations
3087 }
3088
3089 pub fn local_permutations(&self) -> &[Vec<usize>] {
3090 &self.local_permutations
3091 }
3092
3093 pub const fn statistics(&self) -> ExchangeStatistics {
3094 self.statistics
3095 }
3096
3097 pub const fn period(&self) -> usize {
3098 self.period
3099 }
3100
3101 fn mapped_state(&self, state: u128) -> Result<(u128, Complex64)> {
3102 let place_values = self.place_values.as_ref().ok_or_else(|| {
3103 QmbedError::UnsupportedBackend(
3104 "this lattice symmetry requires a wide fixed-width state".into(),
3105 )
3106 })?;
3107 if self.state_limit.is_some_and(|limit| state >= limit) {
3108 return Err(QmbedError::StateNotInBasis);
3109 }
3110 let base = self.states_per_site as u128;
3111 let mut mapped = 0_u128;
3112 let mut occupied_destinations = 0_u128;
3113 let mut odd_fermion_permutation = false;
3114 for source in 0..self.destinations.len() {
3115 let digit = usize::try_from((state / place_values[source]) % base)
3116 .map_err(|_| QmbedError::StateNotInBasis)?;
3117 let mapped_digit = self.local_permutations[source][digit];
3118 let destination = self.destinations[source];
3119 mapped += mapped_digit as u128 * place_values[destination];
3120 if self.statistics == ExchangeStatistics::Fermionic && digit == 1 {
3121 let occupied_after = if destination == 127 {
3122 0
3123 } else {
3124 (occupied_destinations >> (destination + 1)).count_ones()
3125 };
3126 odd_fermion_permutation ^= occupied_after % 2 == 1;
3127 occupied_destinations |= 1_u128 << destination;
3128 }
3129 }
3130 Ok((
3131 mapped,
3132 Complex64::new(if odd_fermion_permutation { -1.0 } else { 1.0 }, 0.0),
3133 ))
3134 }
3135}
3136
3137impl SymmetryMap<u128> for LatticeSymmetryMap {
3138 fn period(&self) -> usize {
3139 self.period
3140 }
3141
3142 fn apply(&self, state: u128) -> Result<(u128, Complex64)> {
3143 self.mapped_state(state)
3144 }
3145}
3146
3147impl<const WORDS: usize> SymmetryMap<WideState<WORDS>> for LatticeSymmetryMap {
3148 fn period(&self) -> usize {
3149 self.period
3150 }
3151
3152 fn apply(&self, state: WideState<WORDS>) -> Result<(WideState<WORDS>, Complex64)> {
3153 if self.states_per_site != 2
3154 || self.destinations.len() > WideState::<WORDS>::capacity_bits()
3155 {
3156 return Err(QmbedError::UnsupportedBackend(
3157 "wide lattice symmetries currently require a binary fixed-width state".into(),
3158 ));
3159 }
3160 if state.has_bits_at_or_above(self.destinations.len()) {
3161 return Err(QmbedError::StateNotInBasis);
3162 }
3163 let mut mapped = WideState::<WORDS>::zero();
3164 let mut occupied_destinations = WideState::<WORDS>::zero();
3165 let mut odd_fermion_permutation = false;
3166 for source in 0..self.destinations.len() {
3167 let occupied = state.bit(source)?;
3168 let digit = usize::from(occupied);
3169 let mapped_digit = self.local_permutations[source][digit];
3170 let destination = self.destinations[source];
3171 mapped = mapped.with_bit(destination, mapped_digit != 0)?;
3172 if self.statistics == ExchangeStatistics::Fermionic && occupied {
3173 odd_fermion_permutation ^=
3174 occupied_destinations.count_ones_after(destination) % 2 == 1;
3175 occupied_destinations = occupied_destinations.with_bit(destination, true)?;
3176 }
3177 }
3178 Ok((
3179 mapped,
3180 Complex64::new(if odd_fermion_permutation { -1.0 } else { 1.0 }, 0.0),
3181 ))
3182 }
3183}
3184
3185impl SymmetryMap<ErasedState> for LatticeSymmetryMap {
3186 fn period(&self) -> usize {
3187 self.period
3188 }
3189
3190 fn apply(&self, state: ErasedState) -> Result<(ErasedState, Complex64)> {
3191 if state.width_bits != self.destinations.len() {
3192 return Err(QmbedError::StateNotInBasis);
3193 }
3194 let (value, phase) = match state.value {
3195 ErasedStateValue::U128(value) => {
3196 let (value, phase) = self.mapped_state(value)?;
3197 (ErasedStateValue::U128(value), phase)
3198 }
3199 ErasedStateValue::U256(value) => {
3200 let (value, phase) = <Self as SymmetryMap<U256>>::apply(self, value)?;
3201 (ErasedStateValue::U256(value), phase)
3202 }
3203 ErasedStateValue::U1024(value) => {
3204 let (value, phase) = <Self as SymmetryMap<U1024>>::apply(self, value)?;
3205 (ErasedStateValue::U1024(value), phase)
3206 }
3207 ErasedStateValue::U4096(value) => {
3208 let (value, phase) = <Self as SymmetryMap<U4096>>::apply(self, value)?;
3209 (ErasedStateValue::U4096(value), phase)
3210 }
3211 ErasedStateValue::U16384(value) => {
3212 let (value, phase) = <Self as SymmetryMap<U16384>>::apply(self, value)?;
3213 (ErasedStateValue::U16384(value), phase)
3214 }
3215 };
3216 Ok((
3217 ErasedState {
3218 width_bits: state.width_bits,
3219 value,
3220 },
3221 phase,
3222 ))
3223 }
3224}
3225
3226fn combined_permutation_period(
3227 destinations: &[usize],
3228 local_permutations: &[Vec<usize>],
3229 states_per_site: usize,
3230) -> Result<usize> {
3231 let elements = destinations
3232 .len()
3233 .checked_mul(states_per_site)
3234 .ok_or_else(|| {
3235 QmbedError::UnsupportedBackend("lattice-symmetry permutation is too large".into())
3236 })?;
3237 let mut visited = vec![false; elements];
3238 let mut period = 1_usize;
3239 for seed in 0..elements {
3240 if visited[seed] {
3241 continue;
3242 }
3243 let mut current = seed;
3244 let mut cycle = 0_usize;
3245 loop {
3246 if visited[current] {
3247 if current != seed {
3248 return Err(QmbedError::IncompatibleSymmetry(
3249 "lattice symmetry does not decompose into closed cycles".into(),
3250 ));
3251 }
3252 break;
3253 }
3254 visited[current] = true;
3255 cycle += 1;
3256 let source = current / states_per_site;
3257 let digit = current % states_per_site;
3258 current = destinations[source] * states_per_site + local_permutations[source][digit];
3259 }
3260 period = checked_lcm(period, cycle)?;
3261 }
3262 Ok(period)
3263}
3264
3265fn checked_lcm(left: usize, right: usize) -> Result<usize> {
3266 fn gcd(mut left: usize, mut right: usize) -> usize {
3267 while right != 0 {
3268 (left, right) = (right, left % right);
3269 }
3270 left
3271 }
3272
3273 left.checked_div(gcd(left, right))
3274 .and_then(|reduced| reduced.checked_mul(right))
3275 .ok_or_else(|| QmbedError::UnsupportedBackend("symmetry period is too large".into()))
3276}
3277
3278type SymmetryAction<State> = Arc<dyn Fn(State) -> Result<(State, Complex64)> + Send + Sync>;
3279
3280pub struct ClosureSymmetryMap<State> {
3282 period: usize,
3283 action: SymmetryAction<State>,
3284}
3285
3286impl<State> ClosureSymmetryMap<State> {
3287 pub fn new<F>(period: usize, action: F) -> Result<Self>
3293 where
3294 F: Fn(State) -> Result<(State, Complex64)> + Send + Sync + 'static,
3295 {
3296 if period == 0 {
3297 return Err(QmbedError::InvalidSector(
3298 "a symmetry-map period must be positive".into(),
3299 ));
3300 }
3301 Ok(Self {
3302 period,
3303 action: Arc::new(action),
3304 })
3305 }
3306}
3307
3308impl<State> SymmetryMap<State> for ClosureSymmetryMap<State>
3309where
3310 State: Copy,
3311{
3312 fn period(&self) -> usize {
3313 self.period
3314 }
3315
3316 fn apply(&self, state: State) -> Result<(State, Complex64)> {
3317 (self.action)(state)
3318 }
3319}
3320
3321#[derive(Clone)]
3322struct SymmetryGenerator<State> {
3323 map: Arc<dyn SymmetryMap<State>>,
3324 sector: i32,
3325}
3326
3327#[derive(Clone)]
3330pub struct SymmetryReducer<State> {
3331 generators: Vec<SymmetryGenerator<State>>,
3332 orbit_cache: Arc<RwLock<HashMap<State, Arc<SymmetryTrace<State>>>>>,
3333}
3334
3335impl<State> std::fmt::Debug for SymmetryReducer<State> {
3336 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3337 formatter
3338 .debug_struct("SymmetryReducer")
3339 .field("generators", &self.generators.len())
3340 .field("cached_orbits", &self.cached_orbits())
3341 .finish()
3342 }
3343}
3344
3345impl<State> SymmetryReducer<State> {
3346 pub fn new() -> Self {
3348 Self {
3349 generators: Vec::new(),
3350 orbit_cache: Arc::new(RwLock::new(HashMap::new())),
3351 }
3352 }
3353
3354 pub fn with_map<M>(mut self, map: M, sector: i32) -> Self
3360 where
3361 M: SymmetryMap<State> + 'static,
3362 {
3363 self.orbit_cache = Arc::new(RwLock::new(HashMap::new()));
3366 self.generators.push(SymmetryGenerator {
3367 map: Arc::new(map),
3368 sector,
3369 });
3370 self
3371 }
3372
3373 pub fn generators(&self) -> usize {
3375 self.generators.len()
3376 }
3377
3378 pub fn cached_orbits(&self) -> usize {
3383 self.orbit_cache.read().map_or(0, |cache| cache.len())
3384 }
3385
3386 pub fn clear_cache(&self) {
3388 if let Ok(mut cache) = self.orbit_cache.write() {
3389 cache.clear();
3390 }
3391 }
3392
3393 pub fn period_product(&self) -> Result<usize> {
3399 self.generators
3400 .iter()
3401 .try_fold(1_usize, |product, generator| {
3402 product.checked_mul(generator.map.period()).ok_or_else(|| {
3403 QmbedError::UnsupportedBackend("symmetry period product is too large".into())
3404 })
3405 })
3406 }
3407}
3408
3409impl<State> Default for SymmetryReducer<State> {
3410 fn default() -> Self {
3411 Self::new()
3412 }
3413}
3414
3415#[derive(Clone, Debug, PartialEq)]
3417pub struct SymmetryOrbit<State> {
3418 representative: State,
3419 orbit_size: usize,
3420 compatible: bool,
3421 phase: Option<Complex64>,
3422 physical_phase_to_representative: Complex64,
3423 generator_word: Vec<usize>,
3424}
3425
3426impl<State> SymmetryOrbit<State> {
3427 pub const fn representative(&self) -> &State {
3428 &self.representative
3429 }
3430
3431 pub const fn orbit_size(&self) -> usize {
3432 self.orbit_size
3433 }
3434
3435 pub const fn is_compatible(&self) -> bool {
3436 self.compatible
3437 }
3438
3439 pub const fn phase(&self) -> Option<Complex64> {
3441 self.phase
3442 }
3443
3444 pub const fn physical_phase_to_representative(&self) -> Complex64 {
3446 self.physical_phase_to_representative
3447 }
3448
3449 pub fn generator_word(&self) -> &[usize] {
3451 &self.generator_word
3452 }
3453}
3454
3455#[derive(Clone, Copy, Debug)]
3456struct GeneralSymmetryImage<State> {
3457 representative: State,
3458 phase: Complex64,
3459 orbit_size: usize,
3460}
3461
3462#[derive(Clone)]
3463struct SymmetryTrace<State> {
3464 coefficients: HashMap<State, Complex64>,
3465 physical_phases: HashMap<State, Complex64>,
3466 predecessors: HashMap<State, (State, usize)>,
3467 compatible: bool,
3468}
3469
3470fn trace_symmetry_orbit<State>(
3471 state: State,
3472 generators: &[SymmetryGenerator<State>],
3473) -> Result<SymmetryTrace<State>>
3474where
3475 State: Copy + Eq + Hash,
3476{
3477 let mut sector_steps = Vec::with_capacity(generators.len());
3478 for generator in generators {
3479 let period = generator.map.period();
3480 if period == 0 {
3481 return Err(QmbedError::InvalidSector(
3482 "a symmetry-map period must be positive".into(),
3483 ));
3484 }
3485 let normalized_sector = i64::from(generator.sector).rem_euclid(period as i64) as usize;
3486 let angle = -std::f64::consts::TAU * normalized_sector as f64 / period as f64;
3487 sector_steps.push(Complex64::from_polar(1.0, angle));
3488
3489 let mut closure_state = state;
3490 let mut closure_phase = Complex64::new(1.0, 0.0);
3491 for _ in 0..period {
3492 let (next, phase) = generator.map.apply(closure_state)?;
3493 if !phase.re.is_finite() || !phase.im.is_finite() {
3494 return Err(QmbedError::IncompatibleSymmetry(
3495 "a symmetry map returned a non-finite phase".into(),
3496 ));
3497 }
3498 closure_state = next;
3499 closure_phase *= phase;
3500 }
3501 if closure_state != state || (closure_phase - Complex64::new(1.0, 0.0)).norm() > 1.0e-10 {
3502 return Err(QmbedError::IncompatibleSymmetry(
3503 "a symmetry map does not close at its declared period".into(),
3504 ));
3505 }
3506 }
3507
3508 let mut coefficients = HashMap::new();
3509 coefficients.insert(state, Complex64::new(1.0, 0.0));
3510 let mut physical_phases = HashMap::new();
3511 physical_phases.insert(state, Complex64::new(1.0, 0.0));
3512 let mut predecessors = HashMap::new();
3513 let mut queue = VecDeque::from([state]);
3514 let mut compatible = true;
3515 while let Some(source) = queue.pop_front() {
3516 let source_coefficient = coefficients[&source];
3517 let source_physical_phase = physical_phases[&source];
3518 for (generator_index, (generator, §or_step)) in
3519 generators.iter().zip(§or_steps).enumerate()
3520 {
3521 let (target, map_phase) = generator.map.apply(source)?;
3522 if !map_phase.re.is_finite() || !map_phase.im.is_finite() {
3523 return Err(QmbedError::IncompatibleSymmetry(
3524 "a symmetry map returned a non-finite phase".into(),
3525 ));
3526 }
3527 let target_coefficient = source_coefficient * map_phase * sector_step;
3528 if let Some(previous) = coefficients.get(&target) {
3529 if (*previous - target_coefficient).norm() > 1.0e-10 {
3530 compatible = false;
3531 }
3532 } else {
3533 coefficients.insert(target, target_coefficient);
3534 physical_phases.insert(target, source_physical_phase * map_phase);
3535 predecessors.insert(target, (source, generator_index));
3536 queue.push_back(target);
3537 }
3538 }
3539 }
3540 Ok(SymmetryTrace {
3541 coefficients,
3542 physical_phases,
3543 predecessors,
3544 compatible,
3545 })
3546}
3547
3548impl<State> SymmetryReducer<State>
3549where
3550 State: Copy + Eq + Hash + Ord,
3551{
3552 fn trace(&self, state: State) -> Result<Arc<SymmetryTrace<State>>> {
3553 if let Some(trace) = self
3554 .orbit_cache
3555 .read()
3556 .map_err(|_| QmbedError::InternalState("symmetry orbit cache was poisoned".into()))?
3557 .get(&state)
3558 .cloned()
3559 {
3560 return Ok(trace);
3561 }
3562 let trace = Arc::new(trace_symmetry_orbit(state, &self.generators)?);
3563 self.orbit_cache
3564 .write()
3565 .map_err(|_| QmbedError::InternalState("symmetry orbit cache was poisoned".into()))?
3566 .entry(state)
3567 .or_insert_with(|| trace.clone());
3568 Ok(trace)
3569 }
3570
3571 pub fn orbit_states(&self, state: State) -> Result<Vec<State>> {
3578 Ok(self.orbit_with_states(state)?.1)
3579 }
3580
3581 pub fn orbit_with_states_and_ordering(
3587 &self,
3588 state: State,
3589 ordering: RepresentativeOrdering,
3590 ) -> Result<(SymmetryOrbit<State>, Vec<State>)> {
3591 let trace = self.trace(state)?;
3592 let representative = *match ordering {
3593 RepresentativeOrdering::Minimum => trace.coefficients.keys().min(),
3594 RepresentativeOrdering::Maximum => trace.coefficients.keys().max(),
3595 }
3596 .ok_or_else(|| QmbedError::InternalState("a symmetry orbit contains no states".into()))?;
3597 let representative_coefficient = trace.coefficients[&representative];
3598 let phase = trace.compatible.then(|| {
3599 let gauge = representative_coefficient / representative_coefficient.norm();
3600 gauge.conj()
3601 });
3602 let mut generator_word = Vec::new();
3603 let mut cursor = representative;
3604 while cursor != state {
3605 let &(previous, generator) = trace.predecessors.get(&cursor).ok_or_else(|| {
3606 QmbedError::InternalState(
3607 "a symmetry-orbit representative has no predecessor path".into(),
3608 )
3609 })?;
3610 generator_word.push(generator);
3611 cursor = previous;
3612 }
3613 generator_word.reverse();
3614 let mut states: Vec<_> = trace.coefficients.keys().copied().collect();
3615 states.sort_unstable();
3616 Ok((
3617 SymmetryOrbit {
3618 representative,
3619 orbit_size: trace.coefficients.len(),
3620 compatible: trace.compatible,
3621 phase,
3622 physical_phase_to_representative: trace.physical_phases[&representative],
3623 generator_word,
3624 },
3625 states,
3626 ))
3627 }
3628
3629 pub fn orbit_with_states(&self, state: State) -> Result<(SymmetryOrbit<State>, Vec<State>)> {
3630 self.orbit_with_states_and_ordering(state, RepresentativeOrdering::Minimum)
3631 }
3632
3633 pub fn orbit(&self, state: State) -> Result<SymmetryOrbit<State>> {
3639 Ok(self.orbit_with_states(state)?.0)
3640 }
3641
3642 pub fn orbit_with_ordering(
3644 &self,
3645 state: State,
3646 ordering: RepresentativeOrdering,
3647 ) -> Result<SymmetryOrbit<State>> {
3648 Ok(self.orbit_with_states_and_ordering(state, ordering)?.0)
3649 }
3650}
3651
3652#[derive(Clone)]
3653struct MatrixSymmetryGenerator<State> {
3654 map: Arc<dyn SymmetryMap<State>>,
3655 representation: Vec<Complex64>,
3656}
3657
3658#[derive(Clone)]
3666pub struct MatrixSymmetryReducer<State> {
3667 dimension: usize,
3668 selected_row: usize,
3669 generators: Vec<MatrixSymmetryGenerator<State>>,
3670}
3671
3672impl<State> std::fmt::Debug for MatrixSymmetryReducer<State> {
3673 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3674 formatter
3675 .debug_struct("MatrixSymmetryReducer")
3676 .field("dimension", &self.dimension)
3677 .field("selected_row", &self.selected_row)
3678 .field("generators", &self.generators.len())
3679 .finish()
3680 }
3681}
3682
3683impl<State> MatrixSymmetryReducer<State> {
3684 pub fn new(dimension: usize, selected_row: usize) -> Result<Self> {
3685 if dimension == 0 || selected_row >= dimension {
3686 return Err(QmbedError::InvalidSector(
3687 "a matrix symmetry representation needs a positive dimension and valid row".into(),
3688 ));
3689 }
3690 Ok(Self {
3691 dimension,
3692 selected_row,
3693 generators: Vec::new(),
3694 })
3695 }
3696
3697 pub const fn dimension(&self) -> usize {
3698 self.dimension
3699 }
3700
3701 pub const fn selected_row(&self) -> usize {
3702 self.selected_row
3703 }
3704
3705 pub fn with_map<M>(mut self, map: M, representation: impl Into<Vec<Complex64>>) -> Result<Self>
3706 where
3707 M: SymmetryMap<State> + 'static,
3708 {
3709 let representation = representation.into();
3710 let entries = self.dimension.checked_mul(self.dimension).ok_or_else(|| {
3711 QmbedError::UnsupportedBackend("matrix symmetry representation is too large".into())
3712 })?;
3713 if representation.len() != entries {
3714 return Err(QmbedError::InvalidSector(format!(
3715 "matrix symmetry generator has {} entries, expected {entries}",
3716 representation.len()
3717 )));
3718 }
3719 validate_unitary_matrix(&representation, self.dimension)?;
3720 let mut power = identity_matrix(self.dimension);
3721 for _ in 0..map.period() {
3722 power = multiply_square_matrices(&representation, &power, self.dimension);
3723 }
3724 if !matrices_close(
3725 &power,
3726 &identity_matrix(self.dimension),
3727 MATRIX_SYMMETRY_TOLERANCE,
3728 ) {
3729 return Err(QmbedError::IncompatibleSymmetry(
3730 "a representation generator does not close at the physical map period".into(),
3731 ));
3732 }
3733 self.generators.push(MatrixSymmetryGenerator {
3734 map: Arc::new(map),
3735 representation,
3736 });
3737 Ok(self)
3738 }
3739}
3740
3741const MATRIX_SYMMETRY_TOLERANCE: f64 = 1.0e-10;
3742const MAX_MATRIX_SYMMETRY_GROUP_ORDER: usize = 65_536;
3743
3744fn identity_matrix(dimension: usize) -> Vec<Complex64> {
3745 let mut matrix = vec![Complex64::new(0.0, 0.0); dimension * dimension];
3746 for index in 0..dimension {
3747 matrix[index * dimension + index] = Complex64::new(1.0, 0.0);
3748 }
3749 matrix
3750}
3751
3752fn multiply_square_matrices(
3753 left: &[Complex64],
3754 right: &[Complex64],
3755 dimension: usize,
3756) -> Vec<Complex64> {
3757 let mut product = vec![Complex64::new(0.0, 0.0); dimension * dimension];
3758 for row in 0..dimension {
3759 for middle in 0..dimension {
3760 let coefficient = left[row * dimension + middle];
3761 if coefficient.norm() <= f64::EPSILON {
3762 continue;
3763 }
3764 for column in 0..dimension {
3765 product[row * dimension + column] +=
3766 coefficient * right[middle * dimension + column];
3767 }
3768 }
3769 }
3770 product
3771}
3772
3773fn matrices_close(left: &[Complex64], right: &[Complex64], tolerance: f64) -> bool {
3774 left.len() == right.len()
3775 && left
3776 .iter()
3777 .zip(right)
3778 .all(|(left, right)| (*left - *right).norm() <= tolerance)
3779}
3780
3781fn validate_unitary_matrix(matrix: &[Complex64], dimension: usize) -> Result<()> {
3782 if matrix
3783 .iter()
3784 .any(|value| !value.re.is_finite() || !value.im.is_finite())
3785 {
3786 return Err(QmbedError::InvalidSector(
3787 "matrix symmetry generators must contain finite values".into(),
3788 ));
3789 }
3790 for left in 0..dimension {
3791 for right in 0..dimension {
3792 let overlap = (0..dimension).fold(Complex64::new(0.0, 0.0), |sum, row| {
3793 sum + matrix[row * dimension + left].conj() * matrix[row * dimension + right]
3794 });
3795 let expected = if left == right {
3796 Complex64::new(1.0, 0.0)
3797 } else {
3798 Complex64::new(0.0, 0.0)
3799 };
3800 if (overlap - expected).norm() > MATRIX_SYMMETRY_TOLERANCE {
3801 return Err(QmbedError::InvalidSector(
3802 "matrix symmetry generators must be unitary".into(),
3803 ));
3804 }
3805 }
3806 }
3807 Ok(())
3808}
3809
3810#[derive(Clone)]
3811struct OrbitGroupElement {
3812 destinations: Vec<usize>,
3813 phases: Vec<Complex64>,
3814 representation: Vec<Complex64>,
3815}
3816
3817type OrbitColumn<State> = (State, Vec<(State, Complex64)>);
3818
3819fn orbit_group_elements_close(left: &OrbitGroupElement, right: &OrbitGroupElement) -> bool {
3820 left.destinations == right.destinations
3821 && matrices_close(&left.phases, &right.phases, MATRIX_SYMMETRY_TOLERANCE)
3822 && matrices_close(
3823 &left.representation,
3824 &right.representation,
3825 MATRIX_SYMMETRY_TOLERANCE,
3826 )
3827}
3828
3829fn compose_orbit_group_elements(
3830 left: &OrbitGroupElement,
3831 right: &OrbitGroupElement,
3832 representation_dimension: usize,
3833) -> OrbitGroupElement {
3834 let mut destinations = Vec::with_capacity(right.destinations.len());
3835 let mut phases = Vec::with_capacity(right.phases.len());
3836 for source in 0..right.destinations.len() {
3837 let middle = right.destinations[source];
3838 destinations.push(left.destinations[middle]);
3839 phases.push(right.phases[source] * left.phases[middle]);
3840 }
3841 OrbitGroupElement {
3842 destinations,
3843 phases,
3844 representation: multiply_square_matrices(
3845 &left.representation,
3846 &right.representation,
3847 representation_dimension,
3848 ),
3849 }
3850}
3851
3852#[derive(Clone, Debug)]
3855pub struct MatrixSymmetrySubspace<State> {
3856 physical_states: Vec<State>,
3857 labels: Vec<State>,
3858 columns: Vec<Vec<(State, Complex64)>>,
3859}
3860
3861impl<State> MatrixSymmetrySubspace<State>
3862where
3863 State: Copy + Eq + Hash + Ord,
3864{
3865 pub fn dimension(&self) -> usize {
3866 self.columns.len()
3867 }
3868
3869 pub fn physical_dimension(&self) -> usize {
3870 self.physical_states.len()
3871 }
3872
3873 pub fn physical_states(&self) -> &[State] {
3874 &self.physical_states
3875 }
3876
3877 pub fn labels(&self) -> &[State] {
3879 &self.labels
3880 }
3881
3882 pub fn columns(&self) -> &[Vec<(State, Complex64)>] {
3883 &self.columns
3884 }
3885
3886 pub fn projector<Parent>(&self, parent: &Parent, format: MatrixFormat) -> Result<Operator>
3888 where
3889 Parent: Basis<State = State>,
3890 {
3891 Operator::from_triplets(
3892 parent.len(),
3893 self.columns.len(),
3894 self.columns
3895 .iter()
3896 .enumerate()
3897 .flat_map(|(column, entries)| {
3898 entries
3899 .iter()
3900 .map(move |&(state, value)| (state, column, value))
3901 })
3902 .map(|(state, column, value)| Ok((parent.index(state)?, column, value)))
3903 .collect::<Result<Vec<_>>>()?,
3904 format,
3905 )
3906 }
3907}
3908
3909impl<State> MatrixSymmetryReducer<State>
3910where
3911 State: Copy + Eq + Hash + Ord,
3912{
3913 fn physical_orbit(&self, seed: State) -> Result<Vec<State>> {
3914 let mut visited = HashSet::from([seed]);
3915 let mut queue = VecDeque::from([seed]);
3916 while let Some(source) = queue.pop_front() {
3917 for generator in &self.generators {
3918 let (target, phase) = generator.map.apply(source)?;
3919 if !phase.re.is_finite() || !phase.im.is_finite() {
3920 return Err(QmbedError::IncompatibleSymmetry(
3921 "a symmetry map returned a non-finite phase".into(),
3922 ));
3923 }
3924 if visited.insert(target) {
3925 queue.push_back(target);
3926 }
3927 }
3928 }
3929 let mut orbit: Vec<_> = visited.into_iter().collect();
3930 orbit.sort_unstable();
3931 Ok(orbit)
3932 }
3933
3934 fn orbit_group(&self, orbit: &[State]) -> Result<Vec<OrbitGroupElement>> {
3935 let indices: HashMap<_, _> = orbit
3936 .iter()
3937 .copied()
3938 .enumerate()
3939 .map(|(index, state)| (state, index))
3940 .collect();
3941 let mut generators = Vec::with_capacity(self.generators.len());
3942 for generator in &self.generators {
3943 let mut destinations = Vec::with_capacity(orbit.len());
3944 let mut phases = Vec::with_capacity(orbit.len());
3945 for &state in orbit {
3946 let (target, phase) = generator.map.apply(state)?;
3947 let target = indices.get(&target).copied().ok_or_else(|| {
3948 QmbedError::IncompatibleSymmetry(
3949 "a matrix symmetry generator leaves its physical orbit".into(),
3950 )
3951 })?;
3952 destinations.push(target);
3953 phases.push(phase);
3954 }
3955 generators.push(OrbitGroupElement {
3956 destinations,
3957 phases,
3958 representation: generator.representation.clone(),
3959 });
3960 }
3961
3962 let identity = OrbitGroupElement {
3963 destinations: (0..orbit.len()).collect(),
3964 phases: vec![Complex64::new(1.0, 0.0); orbit.len()],
3965 representation: identity_matrix(self.dimension),
3966 };
3967 let mut group = vec![identity];
3968 let mut cursor = 0;
3969 while cursor < group.len() {
3970 let current = group[cursor].clone();
3971 cursor += 1;
3972 for generator in &generators {
3973 let candidate = compose_orbit_group_elements(generator, ¤t, self.dimension);
3974 if group
3975 .iter()
3976 .any(|element| orbit_group_elements_close(element, &candidate))
3977 {
3978 continue;
3979 }
3980 if group.len() >= MAX_MATRIX_SYMMETRY_GROUP_ORDER {
3981 return Err(QmbedError::UnsupportedBackend(
3982 "matrix symmetry group exceeds the finite closure limit".into(),
3983 ));
3984 }
3985 group.push(candidate);
3986 }
3987 }
3988 Ok(group)
3989 }
3990
3991 fn selected_orbit_columns(&self, orbit: &[State]) -> Result<Vec<OrbitColumn<State>>> {
3992 let group = self.orbit_group(orbit)?;
3993 let scale = self.dimension as f64 / group.len() as f64;
3994 let mut projector = vec![vec![Complex64::new(0.0, 0.0); orbit.len()]; orbit.len()];
3995 let diagonal = self.selected_row * self.dimension + self.selected_row;
3996 for element in &group {
3997 let weight = scale * element.representation[diagonal].conj();
3998 for source in 0..orbit.len() {
3999 projector[element.destinations[source]][source] += weight * element.phases[source];
4000 }
4001 }
4002
4003 let mut orthonormal = Vec::<(State, Vec<Complex64>)>::new();
4004 for source in 0..orbit.len() {
4005 let mut vector: Vec<_> = projector.iter().map(|row| row[source]).collect();
4006 for _ in 0..2 {
4009 for (_, previous) in &orthonormal {
4010 let overlap = previous
4011 .iter()
4012 .zip(&vector)
4013 .fold(Complex64::new(0.0, 0.0), |sum, (left, right)| {
4014 sum + left.conj() * right
4015 });
4016 for (value, basis_value) in vector.iter_mut().zip(previous) {
4017 *value -= overlap * basis_value;
4018 }
4019 }
4020 }
4021 let norm = vector.iter().map(Complex64::norm_sqr).sum::<f64>().sqrt();
4022 if norm <= MATRIX_SYMMETRY_TOLERANCE {
4023 continue;
4024 }
4025 vector.iter_mut().for_each(|value| *value /= norm);
4026 if let Some(pivot) = vector
4027 .iter()
4028 .copied()
4029 .find(|value| value.norm() > MATRIX_SYMMETRY_TOLERANCE)
4030 {
4031 let gauge = pivot / pivot.norm();
4032 vector.iter_mut().for_each(|value| *value *= gauge.conj());
4033 }
4034 orthonormal.push((orbit[source], vector));
4035 }
4036
4037 Ok(orthonormal
4038 .into_iter()
4039 .map(|(label, vector)| {
4040 (
4041 label,
4042 orbit
4043 .iter()
4044 .copied()
4045 .zip(vector)
4046 .filter(|(_, value)| value.norm() > MATRIX_SYMMETRY_TOLERANCE)
4047 .collect(),
4048 )
4049 })
4050 .collect())
4051 }
4052
4053 pub fn subspace<Seed>(&self, seeds: &Seed) -> Result<MatrixSymmetrySubspace<State>>
4059 where
4060 Seed: Basis<State = State>,
4061 {
4062 if self.generators.is_empty() {
4063 return Err(QmbedError::InvalidSector(
4064 "a matrix symmetry representation requires at least one generator".into(),
4065 ));
4066 }
4067 let mut visited = HashSet::new();
4068 let mut physical_states = Vec::new();
4069 let mut labels = Vec::new();
4070 let mut columns = Vec::new();
4071 for index in 0..seeds.len() {
4072 let seed = seeds.state(index)?;
4073 if visited.contains(&seed) {
4074 continue;
4075 }
4076 let orbit = self.physical_orbit(seed)?;
4077 visited.extend(orbit.iter().copied());
4078 physical_states.extend(orbit.iter().copied());
4079 for (label, column) in self.selected_orbit_columns(&orbit)? {
4080 labels.push(label);
4081 columns.push(column);
4082 }
4083 }
4084 physical_states.sort_unstable();
4085 physical_states.dedup();
4086 Ok(MatrixSymmetrySubspace {
4087 physical_states,
4088 labels,
4089 columns,
4090 })
4091 }
4092}
4093
4094#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
4096pub enum RepresentativeOrdering {
4097 #[default]
4098 Minimum,
4099 Maximum,
4100}
4101
4102#[derive(Clone, Debug)]
4103pub struct GeneralBasis<Parent>
4104where
4105 Parent: Basis,
4106 Parent::State: Hash + Ord,
4107{
4108 parent: Parent,
4109 reducer: SymmetryReducer<Parent::State>,
4110 states: Vec<Parent::State>,
4111 indices: HashMap<Parent::State, usize>,
4112 orbit_lengths: Vec<usize>,
4113 lookup: HashMap<Parent::State, GeneralSymmetryImage<Parent::State>>,
4114}
4115
4116impl<Parent> GeneralBasis<Parent>
4117where
4118 Parent: Basis,
4119 Parent::State: Hash + Ord,
4120{
4121 pub fn new(parent: Parent, sector: SymmetryReducer<Parent::State>) -> Result<Self> {
4122 Self::from_reducer(parent, sector)
4123 }
4124
4125 pub fn from_reducer(parent: Parent, reducer: SymmetryReducer<Parent::State>) -> Result<Self> {
4127 Self::from_reducer_with_ordering(parent, reducer, RepresentativeOrdering::Minimum)
4128 }
4129
4130 pub fn from_reducer_with_ordering(
4135 parent: Parent,
4136 reducer: SymmetryReducer<Parent::State>,
4137 ordering: RepresentativeOrdering,
4138 ) -> Result<Self> {
4139 Self::from_reducer_impl(parent, reducer, true, ordering)
4140 }
4141
4142 pub fn from_orbit_seeds(
4157 parent: Parent,
4158 reducer: SymmetryReducer<Parent::State>,
4159 ) -> Result<Self> {
4160 Self::from_orbit_seeds_with_ordering(parent, reducer, RepresentativeOrdering::Minimum)
4161 }
4162
4163 pub fn from_orbit_seeds_with_ordering(
4164 parent: Parent,
4165 reducer: SymmetryReducer<Parent::State>,
4166 ordering: RepresentativeOrdering,
4167 ) -> Result<Self> {
4168 Self::from_reducer_impl(parent, reducer, false, ordering)
4169 }
4170
4171 fn from_reducer_impl(
4172 parent: Parent,
4173 reducer: SymmetryReducer<Parent::State>,
4174 require_parent_membership: bool,
4175 ordering: RepresentativeOrdering,
4176 ) -> Result<Self> {
4177 if reducer.generators.is_empty() {
4178 let mut states = Vec::with_capacity(parent.len());
4179 let mut lookup = HashMap::with_capacity(parent.len());
4180 for index in 0..parent.len() {
4181 let state = parent.state(index)?;
4182 states.push(state);
4183 lookup.insert(
4184 state,
4185 GeneralSymmetryImage {
4186 representative: state,
4187 phase: Complex64::new(1.0, 0.0),
4188 orbit_size: 1,
4189 },
4190 );
4191 }
4192 let indices = states
4193 .iter()
4194 .copied()
4195 .enumerate()
4196 .map(|(index, state)| (state, index))
4197 .collect();
4198 return Ok(Self {
4199 parent,
4200 reducer,
4201 orbit_lengths: vec![1; states.len()],
4202 states,
4203 indices,
4204 lookup,
4205 });
4206 }
4207
4208 let mut visited = HashSet::with_capacity(parent.len());
4209 let mut representatives = Vec::new();
4210 let mut lookup = HashMap::with_capacity(parent.len());
4211 for index in 0..parent.len() {
4212 let seed = parent.state(index)?;
4213 if visited.contains(&seed) {
4214 continue;
4215 }
4216 let trace = reducer.trace(seed)?;
4217 for state in trace.coefficients.keys() {
4218 if require_parent_membership {
4219 parent.index(*state).map_err(|_| {
4220 QmbedError::IncompatibleSymmetry(
4221 "a symmetry map leaves the parent basis".into(),
4222 )
4223 })?;
4224 }
4225 visited.insert(*state);
4226 }
4227 if !trace.compatible {
4228 continue;
4229 }
4230 let representative = *match ordering {
4231 RepresentativeOrdering::Minimum => trace.coefficients.keys().min(),
4232 RepresentativeOrdering::Maximum => trace.coefficients.keys().max(),
4233 }
4234 .ok_or_else(|| {
4235 QmbedError::InvalidSector("symmetry projection generated no state".into())
4236 })?;
4237 let representative_coefficient = trace.coefficients[&representative];
4238 let gauge = representative_coefficient / representative_coefficient.norm();
4239 let norm = trace
4240 .coefficients
4241 .values()
4242 .map(Complex64::norm_sqr)
4243 .sum::<f64>()
4244 .sqrt();
4245 let orbit_size = trace.coefficients.len();
4246 let expected_magnitude = 1.0 / (orbit_size as f64).sqrt();
4247 for (&state, &coefficient) in &trace.coefficients {
4248 let normalized = coefficient / (gauge * norm);
4249 if (normalized.norm() - expected_magnitude).abs() > 1.0e-10 {
4250 return Err(QmbedError::IncompatibleSymmetry(
4251 "symmetry maps do not define a one-dimensional orbit sector".into(),
4252 ));
4253 }
4254 lookup.insert(
4255 state,
4256 GeneralSymmetryImage {
4257 representative,
4258 phase: normalized / expected_magnitude,
4259 orbit_size,
4260 },
4261 );
4262 }
4263 representatives.push((representative, orbit_size));
4264 }
4265 let parent_rows = representatives
4271 .iter()
4272 .map(|(state, _)| parent.index(*state).ok())
4273 .collect::<Vec<_>>();
4274 if parent_rows.iter().all(Option::is_some) {
4275 let mut with_rows = representatives
4276 .into_iter()
4277 .zip(parent_rows)
4278 .map(|((state, orbit_size), row)| (row.unwrap(), state, orbit_size))
4279 .collect::<Vec<_>>();
4280 with_rows.sort_by_key(|(row, _, _)| *row);
4281 representatives = with_rows
4282 .into_iter()
4283 .map(|(_, state, orbit_size)| (state, orbit_size))
4284 .collect();
4285 } else {
4286 representatives.sort_by_key(|(state, _)| *state);
4290 }
4291 let (states, orbit_lengths): (Vec<Parent::State>, Vec<usize>) =
4292 representatives.into_iter().unzip();
4293 let indices = states
4294 .iter()
4295 .copied()
4296 .enumerate()
4297 .map(|(index, state)| (state, index))
4298 .collect();
4299 Ok(Self {
4300 parent,
4301 reducer,
4302 states,
4303 indices,
4304 orbit_lengths,
4305 lookup,
4306 })
4307 }
4308
4309 pub fn parent(&self) -> &Parent {
4310 &self.parent
4311 }
4312
4313 pub const fn reducer(&self) -> &SymmetryReducer<Parent::State> {
4314 &self.reducer
4315 }
4316
4317 pub fn representative(&self, state: Parent::State) -> Result<Parent::State> {
4318 self.lookup
4319 .get(&state)
4320 .map(|image| image.representative)
4321 .ok_or(QmbedError::StateNotInBasis)
4322 }
4323
4324 pub fn orbit_size(&self, state: Parent::State) -> Result<usize> {
4325 self.lookup
4326 .get(&state)
4327 .map(|image| image.orbit_size)
4328 .ok_or(QmbedError::StateNotInBasis)
4329 }
4330
4331 pub fn symmetry_amplitude(&self, state: Parent::State) -> Result<Complex64> {
4333 self.lookup
4334 .get(&state)
4335 .map(|image| image.phase / (image.orbit_size as f64).sqrt())
4336 .ok_or(QmbedError::StateNotInBasis)
4337 }
4338}
4339
4340impl<Parent> Basis for GeneralBasis<Parent>
4341where
4342 Parent: Basis,
4343 Parent::State: Hash + Ord + 'static,
4344{
4345 type State = Parent::State;
4346
4347 fn len(&self) -> usize {
4348 self.states.len()
4349 }
4350
4351 fn state(&self, index: usize) -> Result<Self::State> {
4352 self.states
4353 .get(index)
4354 .copied()
4355 .ok_or(QmbedError::StateNotInBasis)
4356 }
4357
4358 fn index(&self, state: Self::State) -> Result<usize> {
4359 self.indices
4360 .get(&state)
4361 .copied()
4362 .ok_or(QmbedError::StateNotInBasis)
4363 }
4364
4365 fn apply_local(
4366 &self,
4367 state: Self::State,
4368 operator: &str,
4369 sites: &[usize],
4370 ) -> Result<Option<(Self::State, Complex64)>> {
4371 let transitions = self.apply_local_transitions(state, operator, sites)?;
4372 match transitions.as_slice() {
4373 [] => Ok(None),
4374 [transition] => Ok(Some(*transition)),
4375 _ => Err(QmbedError::UnsupportedBackend(
4376 "this reduced local action branches; use apply_local_transitions".into(),
4377 )),
4378 }
4379 }
4380
4381 fn apply_local_transitions(
4382 &self,
4383 state: Self::State,
4384 operator: &str,
4385 sites: &[usize],
4386 ) -> Result<LocalTransitions<Self::State>> {
4387 let source_index = self.index(state)?;
4388 let source_orbit = self.orbit_lengths[source_index];
4389 let mut reduced = HashMap::<Self::State, Complex64>::new();
4390 for (target, mut amplitude) in self
4391 .parent
4392 .apply_local_transitions(state, operator, sites)?
4393 {
4394 let Some(image) = self.lookup.get(&target) else {
4395 continue;
4396 };
4397 amplitude *=
4398 (source_orbit as f64 / image.orbit_size as f64).sqrt() * image.phase.conj();
4399 *reduced
4400 .entry(image.representative)
4401 .or_insert(Complex64::new(0.0, 0.0)) += amplitude;
4402 }
4403 let mut transitions: LocalTransitions<_> = reduced
4404 .into_iter()
4405 .filter(|(_, amplitude)| amplitude.norm() > f64::EPSILON)
4406 .collect();
4407 transitions.sort_by_key(|(state, _)| *state);
4408 Ok(transitions)
4409 }
4410
4411 fn apply_local_unreduced_transitions(
4412 &self,
4413 state: Self::State,
4414 operator: &str,
4415 sites: &[usize],
4416 ) -> Result<LocalTransitions<Self::State>> {
4417 self.parent
4418 .apply_local_unreduced_transitions(state, operator, sites)
4419 }
4420
4421 fn visit_local_unreduced_transitions<F>(
4422 &self,
4423 state: Self::State,
4424 operator: &str,
4425 sites: &[usize],
4426 visit: F,
4427 ) -> Result<()>
4428 where
4429 F: FnMut(Self::State, Complex64) -> Result<()>,
4430 {
4431 self.parent
4432 .visit_local_unreduced_transitions(state, operator, sites, visit)
4433 }
4434
4435 fn visit_preparsed_local_unreduced_transitions<F>(
4436 &self,
4437 state: Self::State,
4438 operator: &str,
4439 symbols: &[char],
4440 split: Option<usize>,
4441 sites: &[usize],
4442 visit: F,
4443 ) -> Result<()>
4444 where
4445 F: FnMut(Self::State, Complex64) -> Result<()>,
4446 {
4447 self.parent.visit_preparsed_local_unreduced_transitions(
4448 state, operator, symbols, split, sites, visit,
4449 )
4450 }
4451
4452 fn transition_orbit_size(&self, state: Self::State) -> Result<usize> {
4453 Ok(self.orbit_lengths[self.index(state)?])
4454 }
4455
4456 fn reduction_image(&self, state: Self::State) -> Result<Option<ReductionImage<Self::State>>> {
4457 let Some(image) = self.lookup.get(&state) else {
4458 return Ok(None);
4459 };
4460 Ok(Some(ReductionImage::new(
4461 image.representative,
4462 image.phase,
4463 image.orbit_size,
4464 )?))
4465 }
4466
4467 fn reduce_transition(
4468 &self,
4469 state: Self::State,
4470 source_orbit_size: usize,
4471 ) -> Result<Option<(Self::State, Complex64)>> {
4472 let Some(image) = self.lookup.get(&state) else {
4473 return Ok(None);
4474 };
4475 Ok(Some((
4476 image.representative,
4477 (source_orbit_size as f64 / image.orbit_size as f64).sqrt() * image.phase.conj(),
4478 )))
4479 }
4480
4481 fn index_transition(
4482 &self,
4483 state: Self::State,
4484 source_orbit_size: usize,
4485 ) -> Result<Option<(usize, Complex64)>> {
4486 let Some(image) = self.lookup.get(&state) else {
4487 return Ok(None);
4488 };
4489 Ok(Some((
4490 self.index(image.representative)?,
4491 (source_orbit_size as f64 / image.orbit_size as f64).sqrt() * image.phase.conj(),
4492 )))
4493 }
4494
4495 fn operator_preserves_particle_sector(&self, operator: &str) -> Result<bool> {
4496 self.parent.operator_preserves_particle_sector(operator)
4497 }
4498
4499 fn operator_preserves_particle_sector_on_sites(
4500 &self,
4501 operator: &str,
4502 sites: &[usize],
4503 ) -> Result<bool> {
4504 self.parent
4505 .operator_preserves_particle_sector_on_sites(operator, sites)
4506 }
4507}
4508
4509pub type SpinBasisGeneral = GeneralBasis<SpinBasis1D>;
4510pub type BosonBasisGeneral = GeneralBasis<BosonBasis1D>;
4511pub type SpinlessFermionBasisGeneral = GeneralBasis<SpinlessFermionBasis1D>;
4512pub type SpinfulFermionBasisGeneral = GeneralBasis<SpinfulFermionBasis1D>;
4513pub type UserBasisGeneral = GeneralBasis<UserBasis<u128>>;
4514
4515#[derive(Clone, Debug)]
4517pub struct TensorBasis<Left, Right> {
4518 left: Left,
4519 right: Right,
4520}
4521
4522impl<Left, Right> TensorBasis<Left, Right>
4523where
4524 Left: Basis,
4525 Right: Basis,
4526{
4527 pub fn new(left: Left, right: Right) -> Result<Self> {
4528 left.len()
4529 .checked_mul(right.len())
4530 .ok_or_else(|| QmbedError::UnsupportedBackend("tensor-basis size overflow".into()))?;
4531 Ok(Self { left, right })
4532 }
4533
4534 pub fn left(&self) -> &Left {
4535 &self.left
4536 }
4537
4538 pub fn right(&self) -> &Right {
4539 &self.right
4540 }
4541}
4542
4543impl<Left, Right> Basis for TensorBasis<Left, Right>
4544where
4545 Left: Basis,
4546 Right: Basis,
4547 Left::State: 'static,
4548 Right::State: 'static,
4549{
4550 type State = (Left::State, Right::State);
4551
4552 fn len(&self) -> usize {
4553 self.left.len() * self.right.len()
4554 }
4555
4556 fn state(&self, index: usize) -> Result<Self::State> {
4557 if index >= self.len() {
4558 return Err(QmbedError::StateNotInBasis);
4559 }
4560 Ok((
4561 self.left.state(index / self.right.len())?,
4562 self.right.state(index % self.right.len())?,
4563 ))
4564 }
4565
4566 fn index(&self, state: Self::State) -> Result<usize> {
4567 Ok(self.left.index(state.0)? * self.right.len() + self.right.index(state.1)?)
4568 }
4569
4570 fn apply_local(
4571 &self,
4572 state: Self::State,
4573 operator: &str,
4574 sites: &[usize],
4575 ) -> Result<Option<(Self::State, Complex64)>> {
4576 let transitions = self.apply_local_transitions(state, operator, sites)?;
4577 match transitions.as_slice() {
4578 [] => Ok(None),
4579 [transition] => Ok(Some(*transition)),
4580 _ => Err(QmbedError::UnsupportedBackend(
4581 "this tensor local action branches; use apply_local_transitions".into(),
4582 )),
4583 }
4584 }
4585
4586 fn apply_local_transitions(
4587 &self,
4588 state: Self::State,
4589 operator: &str,
4590 sites: &[usize],
4591 ) -> Result<LocalTransitions<Self::State>> {
4592 let (left_operator, right_operator) = operator.split_once('|').ok_or_else(|| {
4593 QmbedError::InvalidOperator(
4594 "tensor-basis operator strings must contain one `|` separator".into(),
4595 )
4596 })?;
4597 if right_operator.contains('|') {
4598 return Err(QmbedError::InvalidOperator(
4599 "a two-factor tensor operator contains too many separators".into(),
4600 ));
4601 }
4602 let left_arity = left_operator.chars().count();
4603 let right_arity = right_operator.chars().count();
4604 if sites.len() != left_arity + right_arity {
4605 return Err(QmbedError::InvalidCoupling(
4606 "tensor operator arity does not match its sites".into(),
4607 ));
4608 }
4609 let left_transitions = if left_operator.is_empty() {
4610 LocalTransitions::from_iter([(state.0, Complex64::new(1.0, 0.0))])
4611 } else {
4612 self.left
4613 .apply_local_transitions(state.0, left_operator, &sites[..left_arity])?
4614 };
4615 let right_transitions = if right_operator.is_empty() {
4616 LocalTransitions::from_iter([(state.1, Complex64::new(1.0, 0.0))])
4617 } else {
4618 self.right
4619 .apply_local_transitions(state.1, right_operator, &sites[left_arity..])?
4620 };
4621 let mut transitions = LocalTransitions::new();
4622 for &(left_state, left_amplitude) in &left_transitions {
4623 for &(right_state, right_amplitude) in &right_transitions {
4624 transitions.push(((left_state, right_state), left_amplitude * right_amplitude));
4625 }
4626 }
4627 Ok(transitions)
4628 }
4629
4630 fn visit_local_unreduced_transitions<F>(
4631 &self,
4632 state: Self::State,
4633 operator: &str,
4634 sites: &[usize],
4635 mut visit: F,
4636 ) -> Result<()>
4637 where
4638 F: FnMut(Self::State, Complex64) -> Result<()>,
4639 {
4640 for (target, amplitude) in self.apply_local_transitions(state, operator, sites)? {
4641 visit(target, amplitude)?;
4642 }
4643 Ok(())
4644 }
4645
4646 fn operator_preserves_particle_sector(&self, operator: &str) -> Result<bool> {
4647 let (left_operator, right_operator) = operator
4648 .split_once('|')
4649 .ok_or_else(|| QmbedError::InvalidOperator(operator.into()))?;
4650 if right_operator.contains('|') {
4651 return Err(QmbedError::InvalidOperator(operator.into()));
4652 }
4653 Ok(self
4654 .left
4655 .operator_preserves_particle_sector(left_operator)?
4656 && self
4657 .right
4658 .operator_preserves_particle_sector(right_operator)?)
4659 }
4660}
4661
4662pub struct PhotonBasis<Matter>
4665where
4666 Matter: Basis,
4667 Matter::State: Hash,
4668{
4669 tensor: TensorBasis<Matter, BosonBasis1D>,
4670 states: Vec<(Matter::State, u128)>,
4671 indices: HashMap<(Matter::State, u128), usize>,
4672 total_excitations: Option<usize>,
4673}
4674
4675impl<Matter> PhotonBasis<Matter>
4676where
4677 Matter: Basis,
4678 Matter::State: Hash + 'static,
4679{
4680 pub fn new(matter: Matter, photon: BosonBasis1D) -> Result<Self> {
4681 Self::build(matter, photon, None, |_| 0)
4682 }
4683
4684 pub fn fixed_total_excitations<F>(
4685 matter: Matter,
4686 photon: BosonBasis1D,
4687 total: usize,
4688 matter_excitations: F,
4689 ) -> Result<Self>
4690 where
4691 F: Fn(Matter::State) -> usize,
4692 {
4693 Self::build(matter, photon, Some(total), matter_excitations)
4694 }
4695
4696 fn build<F>(
4697 matter: Matter,
4698 photon: BosonBasis1D,
4699 total_excitations: Option<usize>,
4700 matter_excitations: F,
4701 ) -> Result<Self>
4702 where
4703 F: Fn(Matter::State) -> usize,
4704 {
4705 if photon.sites() != 1 {
4706 return Err(QmbedError::InvalidSector(
4707 "PhotonBasis requires a one-mode boson basis".into(),
4708 ));
4709 }
4710 let tensor = TensorBasis::new(matter, photon)?;
4711 let mut states = Vec::new();
4712 for index in 0..tensor.len() {
4713 let state = tensor.state(index)?;
4714 if total_excitations
4715 .is_none_or(|total| matter_excitations(state.0) + state.1 as usize == total)
4716 {
4717 states.push(state);
4718 }
4719 }
4720 if states.is_empty() {
4721 return Err(QmbedError::InvalidSector(
4722 "the requested photon sector is empty".into(),
4723 ));
4724 }
4725 let indices = states
4726 .iter()
4727 .copied()
4728 .enumerate()
4729 .map(|(index, state)| (state, index))
4730 .collect();
4731 Ok(Self {
4732 tensor,
4733 states,
4734 indices,
4735 total_excitations,
4736 })
4737 }
4738
4739 pub const fn total_excitations(&self) -> Option<usize> {
4740 self.total_excitations
4741 }
4742
4743 pub fn matter(&self) -> &Matter {
4744 self.tensor.left()
4745 }
4746
4747 pub fn photon(&self) -> &BosonBasis1D {
4748 self.tensor.right()
4749 }
4750}
4751
4752impl<Matter> Basis for PhotonBasis<Matter>
4753where
4754 Matter: Basis,
4755 Matter::State: Hash + 'static,
4756{
4757 type State = (Matter::State, u128);
4758
4759 fn len(&self) -> usize {
4760 self.states.len()
4761 }
4762
4763 fn state(&self, index: usize) -> Result<Self::State> {
4764 self.states
4765 .get(index)
4766 .copied()
4767 .ok_or(QmbedError::StateNotInBasis)
4768 }
4769
4770 fn index(&self, state: Self::State) -> Result<usize> {
4771 self.indices
4772 .get(&state)
4773 .copied()
4774 .ok_or(QmbedError::StateNotInBasis)
4775 }
4776
4777 fn apply_local(
4778 &self,
4779 state: Self::State,
4780 operator: &str,
4781 sites: &[usize],
4782 ) -> Result<Option<(Self::State, Complex64)>> {
4783 let transitions = self.apply_local_transitions(state, operator, sites)?;
4784 match transitions.as_slice() {
4785 [] => Ok(None),
4786 [transition] => Ok(Some(*transition)),
4787 _ => Err(QmbedError::UnsupportedBackend(
4788 "this photon-basis action branches; use apply_local_transitions".into(),
4789 )),
4790 }
4791 }
4792
4793 fn apply_local_transitions(
4794 &self,
4795 state: Self::State,
4796 operator: &str,
4797 sites: &[usize],
4798 ) -> Result<LocalTransitions<Self::State>> {
4799 Ok(self
4800 .tensor
4801 .apply_local_transitions(state, operator, sites)?
4802 .into_iter()
4803 .filter(|(target, _)| self.indices.contains_key(target))
4804 .collect())
4805 }
4806
4807 fn visit_local_unreduced_transitions<F>(
4808 &self,
4809 state: Self::State,
4810 operator: &str,
4811 sites: &[usize],
4812 mut visit: F,
4813 ) -> Result<()>
4814 where
4815 F: FnMut(Self::State, Complex64) -> Result<()>,
4816 {
4817 for (target, amplitude) in self.apply_local_transitions(state, operator, sites)? {
4818 visit(target, amplitude)?;
4819 }
4820 Ok(())
4821 }
4822
4823 fn operator_preserves_particle_sector(&self, operator: &str) -> Result<bool> {
4824 Ok(self.total_excitations.is_none() || operator_number_change(operator)? == Some(0))
4825 }
4826}
4827
4828#[derive(Clone, Copy, Debug, Eq, PartialEq)]
4829pub enum StateStorage {
4830 U128,
4831 U256,
4832 U1024,
4833 U4096,
4834 U16384,
4835}
4836
4837impl StateStorage {
4838 pub fn for_bits(bits: usize) -> Result<Self> {
4839 match bits {
4840 1..=128 => Ok(Self::U128),
4841 129..=256 => Ok(Self::U256),
4842 257..=1024 => Ok(Self::U1024),
4843 1025..=4096 => Ok(Self::U4096),
4844 4097..=16384 => Ok(Self::U16384),
4845 0 => Err(QmbedError::InvalidSector(
4846 "a basis state needs at least one bit".into(),
4847 )),
4848 _ => Err(QmbedError::UnsupportedBackend(
4849 "basis state requires more than 16384 bits".into(),
4850 )),
4851 }
4852 }
4853
4854 pub const fn capacity_bits(self) -> usize {
4855 match self {
4856 Self::U128 => 128,
4857 Self::U256 => 256,
4858 Self::U1024 => 1024,
4859 Self::U4096 => 4096,
4860 Self::U16384 => 16384,
4861 }
4862 }
4863}
4864
4865#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
4867pub struct WideState<const WORDS: usize> {
4868 words: [u64; WORDS],
4869}
4870
4871impl<const WORDS: usize> Ord for WideState<WORDS> {
4872 fn cmp(&self, other: &Self) -> Ordering {
4873 self.words.iter().rev().cmp(other.words.iter().rev())
4874 }
4875}
4876
4877impl<const WORDS: usize> PartialOrd for WideState<WORDS> {
4878 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
4879 Some(self.cmp(other))
4880 }
4881}
4882
4883impl<const WORDS: usize> WideState<WORDS> {
4884 pub const fn zero() -> Self {
4885 Self { words: [0; WORDS] }
4886 }
4887
4888 pub const fn capacity_bits() -> usize {
4889 WORDS * 64
4890 }
4891
4892 pub fn from_words(words: [u64; WORDS]) -> Self {
4893 Self { words }
4894 }
4895
4896 pub fn words(&self) -> &[u64; WORDS] {
4897 &self.words
4898 }
4899
4900 pub fn bit(&self, index: usize) -> Result<bool> {
4901 if index >= Self::capacity_bits() {
4902 return Err(QmbedError::InvalidSite {
4903 site: index,
4904 sites: Self::capacity_bits(),
4905 });
4906 }
4907 Ok(self.words[index / 64] & (1_u64 << (index % 64)) != 0)
4908 }
4909
4910 pub fn with_bit(mut self, index: usize, occupied: bool) -> Result<Self> {
4911 if index >= Self::capacity_bits() {
4912 return Err(QmbedError::InvalidSite {
4913 site: index,
4914 sites: Self::capacity_bits(),
4915 });
4916 }
4917 let mask = 1_u64 << (index % 64);
4918 if occupied {
4919 self.words[index / 64] |= mask;
4920 } else {
4921 self.words[index / 64] &= !mask;
4922 }
4923 Ok(self)
4924 }
4925
4926 pub fn count_ones(&self) -> usize {
4927 self.words
4928 .iter()
4929 .map(|word| word.count_ones() as usize)
4930 .sum()
4931 }
4932
4933 pub fn count_ones_after(&self, index: usize) -> usize {
4934 if index + 1 >= Self::capacity_bits() {
4935 return 0;
4936 }
4937 let first = (index + 1) / 64;
4938 let offset = (index + 1) % 64;
4939 let mut count = 0_usize;
4940 if offset != 0 {
4941 count += (self.words[first] & (!0_u64 << offset)).count_ones() as usize;
4942 } else {
4943 count += self.words[first].count_ones() as usize;
4944 }
4945 count
4946 + self.words[first + 1..]
4947 .iter()
4948 .map(|word| word.count_ones() as usize)
4949 .sum::<usize>()
4950 }
4951
4952 pub fn has_bits_at_or_above(&self, index: usize) -> bool {
4953 if index >= Self::capacity_bits() {
4954 return false;
4955 }
4956 let first = index / 64;
4957 let offset = index % 64;
4958 let first_mask = !0_u64 << offset;
4959 self.words[first] & first_mask != 0 || self.words[first + 1..].iter().any(|word| *word != 0)
4960 }
4961
4962 pub fn bitwise_and(self, right: Self) -> Self {
4963 Self::from_words(std::array::from_fn(|index| {
4964 self.words[index] & right.words[index]
4965 }))
4966 }
4967
4968 pub fn bitwise_or(self, right: Self) -> Self {
4969 Self::from_words(std::array::from_fn(|index| {
4970 self.words[index] | right.words[index]
4971 }))
4972 }
4973
4974 pub fn bitwise_xor(self, right: Self) -> Self {
4975 Self::from_words(std::array::from_fn(|index| {
4976 self.words[index] ^ right.words[index]
4977 }))
4978 }
4979
4980 pub fn bitwise_not(self) -> Self {
4981 Self::from_words(std::array::from_fn(|index| !self.words[index]))
4982 }
4983
4984 pub fn left_shift(self, shift: usize) -> Self {
4985 if shift >= Self::capacity_bits() {
4986 return Self::zero();
4987 }
4988 let word_shift = shift / 64;
4989 let bit_shift = shift % 64;
4990 let mut words = [0_u64; WORDS];
4991 for target in (word_shift..WORDS).rev() {
4992 let source = target - word_shift;
4993 words[target] |= self.words[source] << bit_shift;
4994 if bit_shift > 0 && source > 0 {
4995 words[target] |= self.words[source - 1] >> (64 - bit_shift);
4996 }
4997 }
4998 Self::from_words(words)
4999 }
5000
5001 pub fn right_shift(self, shift: usize) -> Self {
5002 if shift >= Self::capacity_bits() {
5003 return Self::zero();
5004 }
5005 let word_shift = shift / 64;
5006 let bit_shift = shift % 64;
5007 let mut words = [0_u64; WORDS];
5008 for (target, word) in words.iter_mut().enumerate().take(WORDS - word_shift) {
5009 let source = target + word_shift;
5010 *word |= self.words[source] >> bit_shift;
5011 if bit_shift > 0 && source + 1 < WORDS {
5012 *word |= self.words[source + 1] << (64 - bit_shift);
5013 }
5014 }
5015 Self::from_words(words)
5016 }
5017}
5018
5019impl<const WORDS: usize> BinaryState for WideState<WORDS> {
5020 fn bit(&self, index: usize) -> Result<bool> {
5021 WideState::bit(self, index)
5022 }
5023}
5024
5025pub type U256 = WideState<4>;
5026pub type U1024 = WideState<16>;
5027pub type U4096 = WideState<64>;
5028pub type U16384 = WideState<256>;
5029
5030#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
5036pub struct ErasedState {
5037 width_bits: usize,
5038 value: ErasedStateValue,
5039}
5040
5041#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
5042#[allow(clippy::large_enum_variant)] enum ErasedStateValue {
5044 U128(u128),
5045 U256(U256),
5046 U1024(U1024),
5047 U4096(U4096),
5048 U16384(U16384),
5049}
5050
5051impl Ord for ErasedState {
5052 fn cmp(&self, other: &Self) -> Ordering {
5053 self.width_bits
5054 .cmp(&other.width_bits)
5055 .then_with(|| match (&self.value, &other.value) {
5056 (ErasedStateValue::U128(left), ErasedStateValue::U128(right)) => left.cmp(right),
5057 (ErasedStateValue::U256(left), ErasedStateValue::U256(right)) => left.cmp(right),
5058 (ErasedStateValue::U1024(left), ErasedStateValue::U1024(right)) => left.cmp(right),
5059 (ErasedStateValue::U4096(left), ErasedStateValue::U4096(right)) => left.cmp(right),
5060 (ErasedStateValue::U16384(left), ErasedStateValue::U16384(right)) => {
5061 left.cmp(right)
5062 }
5063 (left, right) => erased_state_value_rank(left).cmp(&erased_state_value_rank(right)),
5064 })
5065 }
5066}
5067
5068impl PartialOrd for ErasedState {
5069 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
5070 Some(self.cmp(other))
5071 }
5072}
5073
5074impl std::fmt::Display for ErasedState {
5075 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
5076 formatter.write_str(&self.to_decimal())
5077 }
5078}
5079
5080const fn erased_state_value_rank(value: &ErasedStateValue) -> u8 {
5081 match value {
5082 ErasedStateValue::U128(_) => 0,
5083 ErasedStateValue::U256(_) => 1,
5084 ErasedStateValue::U1024(_) => 2,
5085 ErasedStateValue::U4096(_) => 3,
5086 ErasedStateValue::U16384(_) => 4,
5087 }
5088}
5089
5090trait FixedWidthState: Copy {
5091 const STORAGE: StateStorage;
5092
5093 fn into_erased_value(self) -> ErasedStateValue;
5094 fn from_erased_value(value: ErasedStateValue) -> Option<Self>;
5095}
5096
5097macro_rules! impl_fixed_width_state {
5098 ($state:ty, $storage:ident, $variant:ident) => {
5099 impl FixedWidthState for $state {
5100 const STORAGE: StateStorage = StateStorage::$storage;
5101
5102 fn into_erased_value(self) -> ErasedStateValue {
5103 ErasedStateValue::$variant(self)
5104 }
5105
5106 fn from_erased_value(value: ErasedStateValue) -> Option<Self> {
5107 match value {
5108 ErasedStateValue::$variant(value) => Some(value),
5109 _ => None,
5110 }
5111 }
5112 }
5113 };
5114}
5115
5116impl_fixed_width_state!(U256, U256, U256);
5117impl_fixed_width_state!(U1024, U1024, U1024);
5118impl_fixed_width_state!(U4096, U4096, U4096);
5119impl_fixed_width_state!(U16384, U16384, U16384);
5120
5121fn erase_fixed_width_state<State: FixedWidthState>(width_bits: usize, state: State) -> ErasedState {
5122 ErasedState {
5123 width_bits,
5124 value: state.into_erased_value(),
5125 }
5126}
5127
5128fn restore_fixed_width_state<State: FixedWidthState>(
5129 state: ErasedState,
5130 width_bits: usize,
5131) -> Result<State> {
5132 if state.width_bits != width_bits || state.storage() != State::STORAGE {
5133 return Err(QmbedError::StateNotInBasis);
5134 }
5135 State::from_erased_value(state.value).ok_or(QmbedError::StateNotInBasis)
5136}
5137
5138impl ErasedState {
5139 pub fn from_decimal(width_bits: usize, value: &str) -> Result<Self> {
5140 let value = BigUint::parse_bytes(value.as_bytes(), 10).ok_or_else(|| {
5141 QmbedError::InvalidOptions(format!(
5142 "basis state {value:?} is not a nonnegative decimal integer"
5143 ))
5144 })?;
5145 Self::from_biguint(width_bits, &value)
5146 }
5147
5148 pub fn from_biguint(width_bits: usize, value: &BigUint) -> Result<Self> {
5149 if value.bits() > u64::try_from(width_bits).unwrap_or(u64::MAX) {
5150 return Err(QmbedError::UnsupportedBackend(format!(
5151 "integer needs {} bits but the requested state width is {width_bits}",
5152 value.bits()
5153 )));
5154 }
5155 let storage = StateStorage::for_bits(width_bits)?;
5156 let value = match storage {
5157 StateStorage::U128 => {
5158 let digits = value.to_u64_digits();
5159 let low = u128::from(digits.first().copied().unwrap_or_default());
5160 let high = u128::from(digits.get(1).copied().unwrap_or_default()) << 64;
5161 ErasedStateValue::U128(low | high)
5162 }
5163 StateStorage::U256 => ErasedStateValue::U256(state_from_biguint(value)?),
5164 StateStorage::U1024 => ErasedStateValue::U1024(state_from_biguint(value)?),
5165 StateStorage::U4096 => ErasedStateValue::U4096(state_from_biguint(value)?),
5166 StateStorage::U16384 => ErasedStateValue::U16384(state_from_biguint(value)?),
5167 };
5168 Ok(Self { width_bits, value })
5169 }
5170
5171 pub const fn width_bits(&self) -> usize {
5172 self.width_bits
5173 }
5174
5175 pub const fn storage(&self) -> StateStorage {
5176 match self.value {
5177 ErasedStateValue::U128(_) => StateStorage::U128,
5178 ErasedStateValue::U256(_) => StateStorage::U256,
5179 ErasedStateValue::U1024(_) => StateStorage::U1024,
5180 ErasedStateValue::U4096(_) => StateStorage::U4096,
5181 ErasedStateValue::U16384(_) => StateStorage::U16384,
5182 }
5183 }
5184
5185 pub fn to_biguint(&self) -> BigUint {
5186 match &self.value {
5187 ErasedStateValue::U128(value) => BigUint::from(*value),
5188 ErasedStateValue::U256(value) => state_to_biguint(*value),
5189 ErasedStateValue::U1024(value) => state_to_biguint(*value),
5190 ErasedStateValue::U4096(value) => state_to_biguint(*value),
5191 ErasedStateValue::U16384(value) => state_to_biguint(*value),
5192 }
5193 }
5194
5195 pub fn to_decimal(&self) -> String {
5196 self.to_biguint().to_str_radix(10)
5197 }
5198
5199 pub fn bit(&self, index: usize) -> Result<bool> {
5201 if index >= self.width_bits {
5202 return Err(QmbedError::InvalidSite {
5203 site: index,
5204 sites: self.width_bits,
5205 });
5206 }
5207 match &self.value {
5208 ErasedStateValue::U128(value) => BinaryState::bit(value, index),
5209 ErasedStateValue::U256(value) => value.bit(index),
5210 ErasedStateValue::U1024(value) => value.bit(index),
5211 ErasedStateValue::U4096(value) => value.bit(index),
5212 ErasedStateValue::U16384(value) => value.bit(index),
5213 }
5214 }
5215
5216 fn ensure_compatible(&self, right: &Self) -> Result<()> {
5217 if self.width_bits != right.width_bits || self.storage() != right.storage() {
5218 return Err(QmbedError::DimensionMismatch(
5219 "bitwise basis states must have the same logical width".into(),
5220 ));
5221 }
5222 Ok(())
5223 }
5224
5225 fn remask(self) -> Result<Self> {
5226 let mask = (BigUint::from(1_u8) << self.width_bits) - BigUint::from(1_u8);
5227 Self::from_biguint(self.width_bits, &(self.to_biguint() & mask))
5228 }
5229
5230 pub fn bitwise_and(&self, right: &Self) -> Result<Self> {
5231 self.ensure_compatible(right)?;
5232 let value = match (&self.value, &right.value) {
5233 (ErasedStateValue::U128(left), ErasedStateValue::U128(right)) => {
5234 ErasedStateValue::U128(left & right)
5235 }
5236 (ErasedStateValue::U256(left), ErasedStateValue::U256(right)) => {
5237 ErasedStateValue::U256(left.bitwise_and(*right))
5238 }
5239 (ErasedStateValue::U1024(left), ErasedStateValue::U1024(right)) => {
5240 ErasedStateValue::U1024(left.bitwise_and(*right))
5241 }
5242 (ErasedStateValue::U4096(left), ErasedStateValue::U4096(right)) => {
5243 ErasedStateValue::U4096(left.bitwise_and(*right))
5244 }
5245 (ErasedStateValue::U16384(left), ErasedStateValue::U16384(right)) => {
5246 ErasedStateValue::U16384(left.bitwise_and(*right))
5247 }
5248 _ => unreachable!("compatible erased states have matching storage"),
5249 };
5250 Ok(Self {
5251 width_bits: self.width_bits,
5252 value,
5253 })
5254 }
5255
5256 pub fn bitwise_or(&self, right: &Self) -> Result<Self> {
5257 self.ensure_compatible(right)?;
5258 let value = match (&self.value, &right.value) {
5259 (ErasedStateValue::U128(left), ErasedStateValue::U128(right)) => {
5260 ErasedStateValue::U128(left | right)
5261 }
5262 (ErasedStateValue::U256(left), ErasedStateValue::U256(right)) => {
5263 ErasedStateValue::U256(left.bitwise_or(*right))
5264 }
5265 (ErasedStateValue::U1024(left), ErasedStateValue::U1024(right)) => {
5266 ErasedStateValue::U1024(left.bitwise_or(*right))
5267 }
5268 (ErasedStateValue::U4096(left), ErasedStateValue::U4096(right)) => {
5269 ErasedStateValue::U4096(left.bitwise_or(*right))
5270 }
5271 (ErasedStateValue::U16384(left), ErasedStateValue::U16384(right)) => {
5272 ErasedStateValue::U16384(left.bitwise_or(*right))
5273 }
5274 _ => unreachable!("compatible erased states have matching storage"),
5275 };
5276 Ok(Self {
5277 width_bits: self.width_bits,
5278 value,
5279 })
5280 }
5281
5282 pub fn bitwise_xor(&self, right: &Self) -> Result<Self> {
5283 self.ensure_compatible(right)?;
5284 let value = match (&self.value, &right.value) {
5285 (ErasedStateValue::U128(left), ErasedStateValue::U128(right)) => {
5286 ErasedStateValue::U128(left ^ right)
5287 }
5288 (ErasedStateValue::U256(left), ErasedStateValue::U256(right)) => {
5289 ErasedStateValue::U256(left.bitwise_xor(*right))
5290 }
5291 (ErasedStateValue::U1024(left), ErasedStateValue::U1024(right)) => {
5292 ErasedStateValue::U1024(left.bitwise_xor(*right))
5293 }
5294 (ErasedStateValue::U4096(left), ErasedStateValue::U4096(right)) => {
5295 ErasedStateValue::U4096(left.bitwise_xor(*right))
5296 }
5297 (ErasedStateValue::U16384(left), ErasedStateValue::U16384(right)) => {
5298 ErasedStateValue::U16384(left.bitwise_xor(*right))
5299 }
5300 _ => unreachable!("compatible erased states have matching storage"),
5301 };
5302 Ok(Self {
5303 width_bits: self.width_bits,
5304 value,
5305 })
5306 }
5307
5308 pub fn bitwise_not(&self) -> Result<Self> {
5309 let value = match &self.value {
5310 ErasedStateValue::U128(value) => ErasedStateValue::U128(!value),
5311 ErasedStateValue::U256(value) => ErasedStateValue::U256(value.bitwise_not()),
5312 ErasedStateValue::U1024(value) => ErasedStateValue::U1024(value.bitwise_not()),
5313 ErasedStateValue::U4096(value) => ErasedStateValue::U4096(value.bitwise_not()),
5314 ErasedStateValue::U16384(value) => ErasedStateValue::U16384(value.bitwise_not()),
5315 };
5316 Self {
5317 width_bits: self.width_bits,
5318 value,
5319 }
5320 .remask()
5321 }
5322
5323 pub fn left_shift(&self, shift: usize) -> Result<Self> {
5324 let value = match &self.value {
5325 ErasedStateValue::U128(value) => ErasedStateValue::U128(
5326 u32::try_from(shift)
5327 .ok()
5328 .and_then(|shift| value.checked_shl(shift))
5329 .unwrap_or_default(),
5330 ),
5331 ErasedStateValue::U256(value) => ErasedStateValue::U256(value.left_shift(shift)),
5332 ErasedStateValue::U1024(value) => ErasedStateValue::U1024(value.left_shift(shift)),
5333 ErasedStateValue::U4096(value) => ErasedStateValue::U4096(value.left_shift(shift)),
5334 ErasedStateValue::U16384(value) => ErasedStateValue::U16384(value.left_shift(shift)),
5335 };
5336 Self {
5337 width_bits: self.width_bits,
5338 value,
5339 }
5340 .remask()
5341 }
5342
5343 pub fn right_shift(&self, shift: usize) -> Self {
5344 let value = match &self.value {
5345 ErasedStateValue::U128(value) => ErasedStateValue::U128(
5346 u32::try_from(shift)
5347 .ok()
5348 .and_then(|shift| value.checked_shr(shift))
5349 .unwrap_or_default(),
5350 ),
5351 ErasedStateValue::U256(value) => ErasedStateValue::U256(value.right_shift(shift)),
5352 ErasedStateValue::U1024(value) => ErasedStateValue::U1024(value.right_shift(shift)),
5353 ErasedStateValue::U4096(value) => ErasedStateValue::U4096(value.right_shift(shift)),
5354 ErasedStateValue::U16384(value) => ErasedStateValue::U16384(value.right_shift(shift)),
5355 };
5356 Self {
5357 width_bits: self.width_bits,
5358 value,
5359 }
5360 }
5361}
5362
5363impl BinaryState for ErasedState {
5364 fn bit(&self, index: usize) -> Result<bool> {
5365 ErasedState::bit(self, index)
5366 }
5367}
5368
5369#[derive(Clone, Debug)]
5371pub struct WideSpinBasis<const WORDS: usize> {
5372 sites: usize,
5373 particle_sectors: Option<Vec<usize>>,
5374 normalization: SpinNormalization,
5375 states: Vec<WideState<WORDS>>,
5376}
5377
5378impl<const WORDS: usize> WideSpinBasis<WORDS> {
5379 pub fn new(sites: usize, particles: Option<usize>, pauli: bool) -> Result<Self> {
5380 let normalization = if pauli {
5381 SpinNormalization::PauliCartesian
5382 } else {
5383 SpinNormalization::AngularMomentum
5384 };
5385 match particles {
5386 Some(particles) => {
5387 Self::from_particle_sectors_with_normalization(sites, [particles], normalization)
5388 }
5389 None => Self::from_optional_particle_sectors(sites, None, normalization),
5390 }
5391 }
5392
5393 pub fn from_particle_sectors(
5395 sites: usize,
5396 sectors: impl IntoIterator<Item = usize>,
5397 pauli: bool,
5398 ) -> Result<Self> {
5399 let normalization = if pauli {
5400 SpinNormalization::PauliCartesian
5401 } else {
5402 SpinNormalization::AngularMomentum
5403 };
5404 Self::from_particle_sectors_with_normalization(sites, sectors, normalization)
5405 }
5406
5407 pub fn with_normalization(
5408 sites: usize,
5409 particles: Option<usize>,
5410 normalization: SpinNormalization,
5411 ) -> Result<Self> {
5412 match particles {
5413 Some(particles) => {
5414 Self::from_particle_sectors_with_normalization(sites, [particles], normalization)
5415 }
5416 None => Self::from_optional_particle_sectors(sites, None, normalization),
5417 }
5418 }
5419
5420 pub fn from_particle_sectors_with_normalization(
5421 sites: usize,
5422 sectors: impl IntoIterator<Item = usize>,
5423 normalization: SpinNormalization,
5424 ) -> Result<Self> {
5425 let mut sectors: Vec<_> = sectors.into_iter().collect();
5426 sectors.sort_unstable();
5427 sectors.dedup();
5428 if sectors.is_empty() {
5429 return Err(QmbedError::InvalidSector(
5430 "wide spin sector union must be nonempty".into(),
5431 ));
5432 }
5433 Self::from_optional_particle_sectors(sites, Some(sectors), normalization)
5434 }
5435
5436 pub fn from_explicit_states_with_normalization(
5444 sites: usize,
5445 states: impl IntoIterator<Item = WideState<WORDS>>,
5446 normalization: SpinNormalization,
5447 ) -> Result<Self> {
5448 if sites == 0 || sites > WideState::<WORDS>::capacity_bits() {
5449 return Err(QmbedError::UnsupportedBackend(format!(
5450 "wide spin basis needs 1..={} sites",
5451 WideState::<WORDS>::capacity_bits()
5452 )));
5453 }
5454 let mut states: Vec<_> = states.into_iter().collect();
5455 if states.iter().any(|state| state.has_bits_at_or_above(sites)) {
5456 return Err(QmbedError::InvalidSector(
5457 "an explicit wide spin state exceeds the requested site width".into(),
5458 ));
5459 }
5460 states.sort_unstable();
5461 states.dedup();
5462 let mut particle_sectors: Vec<_> = states.iter().map(WideState::count_ones).collect();
5463 particle_sectors.sort_unstable();
5464 particle_sectors.dedup();
5465 Ok(Self {
5466 sites,
5467 particle_sectors: Some(particle_sectors),
5468 normalization,
5469 states,
5470 })
5471 }
5472
5473 fn from_optional_particle_sectors(
5474 sites: usize,
5475 particle_sectors: Option<Vec<usize>>,
5476 normalization: SpinNormalization,
5477 ) -> Result<Self> {
5478 if sites == 0 || sites > WideState::<WORDS>::capacity_bits() {
5479 return Err(QmbedError::UnsupportedBackend(format!(
5480 "wide spin basis needs 1..={} sites",
5481 WideState::<WORDS>::capacity_bits()
5482 )));
5483 }
5484 if particle_sectors
5485 .as_ref()
5486 .is_some_and(|sectors| sectors.iter().any(|&count| count > sites))
5487 {
5488 return Err(QmbedError::InvalidSector(
5489 "particle count exceeds the wide spin site count".into(),
5490 ));
5491 }
5492 let mut states = Vec::new();
5493 if let Some(sectors) = &particle_sectors {
5494 fn enumerate<const WORDS: usize>(
5495 next_site: usize,
5496 sites: usize,
5497 remaining: usize,
5498 state: WideState<WORDS>,
5499 output: &mut Vec<WideState<WORDS>>,
5500 ) -> Result<()> {
5501 if remaining == 0 {
5502 output.push(state);
5503 return Ok(());
5504 }
5505 if sites.saturating_sub(next_site) < remaining {
5506 return Ok(());
5507 }
5508 for site in next_site..=sites - remaining {
5509 enumerate(
5510 site + 1,
5511 sites,
5512 remaining - 1,
5513 state.with_bit(site, true)?,
5514 output,
5515 )?;
5516 }
5517 Ok(())
5518 }
5519 for &count in sectors {
5520 enumerate(0, sites, count, WideState::zero(), &mut states)?;
5521 }
5522 } else {
5523 if sites > 24 {
5524 return Err(QmbedError::InvalidOptions(
5525 "an unrestricted wide spin basis above 24 sites is not enumerable; select a particle sector"
5526 .into(),
5527 ));
5528 }
5529 let limit = 1_u128 << sites;
5530 states.extend((0..limit).map(python_int_to_basis_int));
5531 }
5532 states.sort_unstable();
5533 Ok(Self {
5534 sites,
5535 particle_sectors,
5536 normalization,
5537 states,
5538 })
5539 }
5540
5541 pub const fn sites(&self) -> usize {
5542 self.sites
5543 }
5544
5545 pub fn particles(&self) -> Option<usize> {
5546 match self.particle_sectors.as_deref() {
5547 Some([particles]) => Some(*particles),
5548 _ => None,
5549 }
5550 }
5551
5552 pub fn particle_sectors(&self) -> Option<&[usize]> {
5553 self.particle_sectors.as_deref()
5554 }
5555
5556 pub const fn normalization(&self) -> SpinNormalization {
5557 self.normalization
5558 }
5559}
5560
5561impl<const WORDS: usize> Basis for WideSpinBasis<WORDS> {
5562 type State = WideState<WORDS>;
5563
5564 fn len(&self) -> usize {
5565 self.states.len()
5566 }
5567
5568 fn state(&self, index: usize) -> Result<Self::State> {
5569 self.states
5570 .get(index)
5571 .copied()
5572 .ok_or(QmbedError::StateNotInBasis)
5573 }
5574
5575 fn index(&self, state: Self::State) -> Result<usize> {
5576 self.states
5577 .binary_search(&state)
5578 .map_err(|_| QmbedError::StateNotInBasis)
5579 }
5580
5581 fn apply_local(
5582 &self,
5583 mut state: Self::State,
5584 operator: &str,
5585 sites: &[usize],
5586 ) -> Result<Option<(Self::State, Complex64)>> {
5587 let chars = operator_chars(operator, sites)?;
5588 let mut amplitude = Complex64::new(1.0, 0.0);
5589 for (&site, op) in sites.iter().zip(chars).rev() {
5590 checked_site(site, self.sites)?;
5591 let occupied = state.bit(site)?;
5592 let cartesian_scale = match self.normalization {
5593 SpinNormalization::AngularMomentum | SpinNormalization::Pauli => 0.5,
5594 SpinNormalization::PauliCartesian => 1.0,
5595 };
5596 let z_scale = if self.normalization == SpinNormalization::AngularMomentum {
5597 0.5
5598 } else {
5599 1.0
5600 };
5601 let ladder_scale = if self.normalization == SpinNormalization::Pauli {
5602 2.0
5603 } else {
5604 1.0
5605 };
5606 match op {
5607 'I' => {}
5608 'n' => {
5609 if !occupied {
5610 return Ok(None);
5611 }
5612 }
5613 'z' => amplitude *= if occupied { z_scale } else { -z_scale },
5614 '+' if !occupied => {
5615 state = state.with_bit(site, true)?;
5616 amplitude *= ladder_scale;
5617 }
5618 '-' if occupied => {
5619 state = state.with_bit(site, false)?;
5620 amplitude *= ladder_scale;
5621 }
5622 'x' => {
5623 state = state.with_bit(site, !occupied)?;
5624 amplitude *= cartesian_scale;
5625 }
5626 'y' => {
5627 state = state.with_bit(site, !occupied)?;
5628 amplitude *= Complex64::new(
5629 0.0,
5630 if occupied {
5631 cartesian_scale
5632 } else {
5633 -cartesian_scale
5634 },
5635 );
5636 }
5637 '+' | '-' => return Ok(None),
5638 _ => return Err(QmbedError::InvalidOperator(op.to_string())),
5639 }
5640 }
5641 Ok(Some((state, amplitude)))
5642 }
5643
5644 fn operator_preserves_particle_sector(&self, operator: &str) -> Result<bool> {
5645 Ok(self.particle_sectors.is_none() || operator_number_change(operator)? == Some(0))
5646 }
5647}
5648
5649pub type WideSpinBasis256 = WideSpinBasis<4>;
5650pub type WideSpinBasis1024 = WideSpinBasis<16>;
5651pub type WideSpinBasis4096 = WideSpinBasis<64>;
5652pub type WideSpinBasis16384 = WideSpinBasis<256>;
5653pub type WideSpinBasisGeneral256 = GeneralBasis<WideSpinBasis256>;
5654pub type WideSpinBasisGeneral1024 = GeneralBasis<WideSpinBasis1024>;
5655pub type WideSpinBasisGeneral4096 = GeneralBasis<WideSpinBasis4096>;
5656pub type WideSpinBasisGeneral16384 = GeneralBasis<WideSpinBasis16384>;
5657
5658#[derive(Clone, Debug)]
5665pub enum WidePackedBasis {
5666 Spin256(WideSpinBasis256),
5667 Spin1024(WideSpinBasis1024),
5668 Spin4096(WideSpinBasis4096),
5669 Spin16384(WideSpinBasis16384),
5670 GeneralSpin256(WideSpinBasisGeneral256),
5671 GeneralSpin1024(WideSpinBasisGeneral1024),
5672 GeneralSpin4096(WideSpinBasisGeneral4096),
5673 GeneralSpin16384(WideSpinBasisGeneral16384),
5674 Reversed(Box<WidePackedBasis>),
5675}
5676
5677impl WidePackedBasis {
5678 pub fn reversed(self) -> Self {
5679 match self {
5680 Self::Reversed(inner) => *inner,
5681 basis => Self::Reversed(Box::new(basis)),
5682 }
5683 }
5684
5685 pub fn explicit_spin_subspace(&self, states: &[ErasedState]) -> Result<Self> {
5688 macro_rules! explicit {
5689 ($basis:expr, $state:ty, $variant:ident) => {{
5690 let basis = $basis;
5691 let typed = states
5692 .iter()
5693 .copied()
5694 .map(|state| restore_fixed_width_state::<$state>(state, basis.sites()))
5695 .collect::<Result<Vec<_>>>()?;
5696 Ok(Self::$variant(
5697 WideSpinBasis::from_explicit_states_with_normalization(
5698 basis.sites(),
5699 typed,
5700 basis.normalization(),
5701 )?,
5702 ))
5703 }};
5704 }
5705 match self {
5706 Self::Spin256(basis) => explicit!(basis, U256, Spin256),
5707 Self::Spin1024(basis) => explicit!(basis, U1024, Spin1024),
5708 Self::Spin4096(basis) => explicit!(basis, U4096, Spin4096),
5709 Self::Spin16384(basis) => explicit!(basis, U16384, Spin16384),
5710 Self::Reversed(inner) => Ok(inner.explicit_spin_subspace(states)?.reversed()),
5711 Self::GeneralSpin256(_)
5712 | Self::GeneralSpin1024(_)
5713 | Self::GeneralSpin4096(_)
5714 | Self::GeneralSpin16384(_) => Err(QmbedError::InvalidOptions(
5715 "an explicit wide spin subspace must start from an unreduced spin algebra".into(),
5716 )),
5717 }
5718 }
5719
5720 pub fn width_bits(&self) -> usize {
5721 match self {
5722 Self::Spin256(basis) => basis.sites(),
5723 Self::Spin1024(basis) => basis.sites(),
5724 Self::Spin4096(basis) => basis.sites(),
5725 Self::Spin16384(basis) => basis.sites(),
5726 Self::GeneralSpin256(basis) => basis.parent().sites(),
5727 Self::GeneralSpin1024(basis) => basis.parent().sites(),
5728 Self::GeneralSpin4096(basis) => basis.parent().sites(),
5729 Self::GeneralSpin16384(basis) => basis.parent().sites(),
5730 Self::Reversed(basis) => basis.width_bits(),
5731 }
5732 }
5733
5734 pub fn storage(&self) -> StateStorage {
5735 match self {
5736 Self::Spin256(_) | Self::GeneralSpin256(_) => StateStorage::U256,
5737 Self::Spin1024(_) | Self::GeneralSpin1024(_) => StateStorage::U1024,
5738 Self::Spin4096(_) | Self::GeneralSpin4096(_) => StateStorage::U4096,
5739 Self::Spin16384(_) | Self::GeneralSpin16384(_) => StateStorage::U16384,
5740 Self::Reversed(basis) => basis.storage(),
5741 }
5742 }
5743}
5744
5745macro_rules! dispatch_wide_basis {
5746 ($basis:expr, $inner:ident => $body:expr) => {
5747 match $basis {
5748 WidePackedBasis::Spin256($inner) => $body,
5749 WidePackedBasis::Spin1024($inner) => $body,
5750 WidePackedBasis::Spin4096($inner) => $body,
5751 WidePackedBasis::Spin16384($inner) => $body,
5752 WidePackedBasis::GeneralSpin256($inner) => $body,
5753 WidePackedBasis::GeneralSpin1024($inner) => $body,
5754 WidePackedBasis::GeneralSpin4096($inner) => $body,
5755 WidePackedBasis::GeneralSpin16384($inner) => $body,
5756 WidePackedBasis::Reversed(_) => {
5757 unreachable!("reversed wide bases are handled before typed dispatch")
5758 }
5759 }
5760 };
5761}
5762
5763impl Basis for WidePackedBasis {
5764 type State = ErasedState;
5765
5766 fn len(&self) -> usize {
5767 if let Self::Reversed(basis) = self {
5768 return basis.len();
5769 }
5770 dispatch_wide_basis!(self, basis => basis.len())
5771 }
5772
5773 fn state(&self, index: usize) -> Result<Self::State> {
5774 if let Self::Reversed(basis) = self {
5775 let reversed = basis
5776 .len()
5777 .checked_sub(index + 1)
5778 .ok_or(QmbedError::StateNotInBasis)?;
5779 return basis.state(reversed);
5780 }
5781 let width_bits = self.width_bits();
5782 dispatch_wide_basis!(
5783 self,
5784 basis => basis
5785 .state(index)
5786 .map(|state| erase_fixed_width_state(width_bits, state))
5787 )
5788 }
5789
5790 fn index(&self, state: Self::State) -> Result<usize> {
5791 if let Self::Reversed(basis) = self {
5792 return basis.index(state).map(|index| basis.len() - index - 1);
5793 }
5794 let width_bits = self.width_bits();
5795 dispatch_wide_basis!(
5796 self,
5797 basis => basis.index(restore_fixed_width_state(state, width_bits)?)
5798 )
5799 }
5800
5801 fn apply_local(
5802 &self,
5803 state: Self::State,
5804 operator: &str,
5805 sites: &[usize],
5806 ) -> Result<Option<(Self::State, Complex64)>> {
5807 if let Self::Reversed(basis) = self {
5808 return basis.apply_local(state, operator, sites);
5809 }
5810 let width_bits = self.width_bits();
5811 dispatch_wide_basis!(
5812 self,
5813 basis => basis
5814 .apply_local(
5815 restore_fixed_width_state(state, width_bits)?,
5816 operator,
5817 sites,
5818 )
5819 .map(|transition| {
5820 transition.map(|(target, amplitude)| {
5821 (
5822 erase_fixed_width_state(width_bits, target),
5823 amplitude,
5824 )
5825 })
5826 })
5827 )
5828 }
5829
5830 fn apply_local_transitions(
5831 &self,
5832 state: Self::State,
5833 operator: &str,
5834 sites: &[usize],
5835 ) -> Result<LocalTransitions<Self::State>> {
5836 if let Self::Reversed(basis) = self {
5837 return basis.apply_local_transitions(state, operator, sites);
5838 }
5839 let width_bits = self.width_bits();
5840 dispatch_wide_basis!(
5841 self,
5842 basis => basis
5843 .apply_local_transitions(
5844 restore_fixed_width_state(state, width_bits)?,
5845 operator,
5846 sites,
5847 )
5848 .map(|transitions| {
5849 transitions
5850 .into_iter()
5851 .map(|(target, amplitude)| {
5852 (
5853 erase_fixed_width_state(width_bits, target),
5854 amplitude,
5855 )
5856 })
5857 .collect()
5858 })
5859 )
5860 }
5861
5862 fn apply_local_unreduced_transitions(
5863 &self,
5864 state: Self::State,
5865 operator: &str,
5866 sites: &[usize],
5867 ) -> Result<LocalTransitions<Self::State>> {
5868 if let Self::Reversed(basis) = self {
5869 return basis.apply_local_unreduced_transitions(state, operator, sites);
5870 }
5871 let width_bits = self.width_bits();
5872 dispatch_wide_basis!(
5873 self,
5874 basis => basis
5875 .apply_local_unreduced_transitions(
5876 restore_fixed_width_state(state, width_bits)?,
5877 operator,
5878 sites,
5879 )
5880 .map(|transitions| {
5881 transitions
5882 .into_iter()
5883 .map(|(target, amplitude)| {
5884 (
5885 erase_fixed_width_state(width_bits, target),
5886 amplitude,
5887 )
5888 })
5889 .collect()
5890 })
5891 )
5892 }
5893
5894 fn visit_local_unreduced_transitions<F>(
5895 &self,
5896 state: Self::State,
5897 operator: &str,
5898 sites: &[usize],
5899 mut visit: F,
5900 ) -> Result<()>
5901 where
5902 F: FnMut(Self::State, Complex64) -> Result<()>,
5903 {
5904 if let Self::Reversed(basis) = self {
5905 return basis.visit_local_unreduced_transitions(state, operator, sites, visit);
5906 }
5907 let width_bits = self.width_bits();
5908 dispatch_wide_basis!(
5909 self,
5910 basis => basis.visit_local_unreduced_transitions(
5911 restore_fixed_width_state(state, width_bits)?,
5912 operator,
5913 sites,
5914 |target, amplitude| {
5915 visit(erase_fixed_width_state(width_bits, target), amplitude)
5916 },
5917 )
5918 )
5919 }
5920
5921 fn visit_preparsed_local_unreduced_transitions<F>(
5922 &self,
5923 state: Self::State,
5924 operator: &str,
5925 symbols: &[char],
5926 split: Option<usize>,
5927 sites: &[usize],
5928 mut visit: F,
5929 ) -> Result<()>
5930 where
5931 F: FnMut(Self::State, Complex64) -> Result<()>,
5932 {
5933 if let Self::Reversed(basis) = self {
5934 return basis.visit_preparsed_local_unreduced_transitions(
5935 state, operator, symbols, split, sites, visit,
5936 );
5937 }
5938 let width_bits = self.width_bits();
5939 dispatch_wide_basis!(
5940 self,
5941 basis => basis.visit_preparsed_local_unreduced_transitions(
5942 restore_fixed_width_state(state, width_bits)?,
5943 operator,
5944 symbols,
5945 split,
5946 sites,
5947 |target, amplitude| {
5948 visit(erase_fixed_width_state(width_bits, target), amplitude)
5949 },
5950 )
5951 )
5952 }
5953
5954 fn reduction_image(&self, state: Self::State) -> Result<Option<ReductionImage<Self::State>>> {
5955 if let Self::Reversed(basis) = self {
5956 return basis.reduction_image(state);
5957 }
5958 let width_bits = self.width_bits();
5959 dispatch_wide_basis!(
5960 self,
5961 basis => basis
5962 .reduction_image(restore_fixed_width_state(state, width_bits)?)?
5963 .map(|image| {
5964 ReductionImage::new(
5965 erase_fixed_width_state(width_bits, *image.representative()),
5966 image.phase(),
5967 image.orbit_size(),
5968 )
5969 })
5970 .transpose()
5971 )
5972 }
5973
5974 fn transition_orbit_size(&self, state: Self::State) -> Result<usize> {
5975 if let Self::Reversed(basis) = self {
5976 return basis.transition_orbit_size(state);
5977 }
5978 let width_bits = self.width_bits();
5979 dispatch_wide_basis!(
5980 self,
5981 basis => basis.transition_orbit_size(restore_fixed_width_state(state, width_bits)?)
5982 )
5983 }
5984
5985 fn reduce_transition(
5986 &self,
5987 state: Self::State,
5988 source_orbit_size: usize,
5989 ) -> Result<Option<(Self::State, Complex64)>> {
5990 if let Self::Reversed(basis) = self {
5991 return basis.reduce_transition(state, source_orbit_size);
5992 }
5993 let width_bits = self.width_bits();
5994 dispatch_wide_basis!(
5995 self,
5996 basis => Ok(basis
5997 .reduce_transition(
5998 restore_fixed_width_state(state, width_bits)?,
5999 source_orbit_size,
6000 )?
6001 .map(|(representative, amplitude)| {
6002 (
6003 erase_fixed_width_state(width_bits, representative),
6004 amplitude,
6005 )
6006 }))
6007 )
6008 }
6009
6010 fn index_transition(
6011 &self,
6012 state: Self::State,
6013 source_orbit_size: usize,
6014 ) -> Result<Option<(usize, Complex64)>> {
6015 if let Self::Reversed(basis) = self {
6016 return Ok(basis
6017 .index_transition(state, source_orbit_size)?
6018 .map(|(index, amplitude)| (basis.len() - index - 1, amplitude)));
6019 }
6020 let width_bits = self.width_bits();
6021 dispatch_wide_basis!(
6022 self,
6023 basis => basis.index_transition(
6024 restore_fixed_width_state(state, width_bits)?,
6025 source_orbit_size,
6026 )
6027 )
6028 }
6029
6030 fn operator_preserves_particle_sector(&self, operator: &str) -> Result<bool> {
6031 if let Self::Reversed(basis) = self {
6032 return basis.operator_preserves_particle_sector(operator);
6033 }
6034 dispatch_wide_basis!(
6035 self,
6036 basis => basis.operator_preserves_particle_sector(operator)
6037 )
6038 }
6039}
6040
6041impl From<WideSpinBasis256> for WidePackedBasis {
6042 fn from(basis: WideSpinBasis256) -> Self {
6043 Self::Spin256(basis)
6044 }
6045}
6046
6047impl From<WideSpinBasis1024> for WidePackedBasis {
6048 fn from(basis: WideSpinBasis1024) -> Self {
6049 Self::Spin1024(basis)
6050 }
6051}
6052
6053impl From<WideSpinBasis4096> for WidePackedBasis {
6054 fn from(basis: WideSpinBasis4096) -> Self {
6055 Self::Spin4096(basis)
6056 }
6057}
6058
6059impl From<WideSpinBasis16384> for WidePackedBasis {
6060 fn from(basis: WideSpinBasis16384) -> Self {
6061 Self::Spin16384(basis)
6062 }
6063}
6064
6065impl From<WideSpinBasisGeneral256> for WidePackedBasis {
6066 fn from(basis: WideSpinBasisGeneral256) -> Self {
6067 Self::GeneralSpin256(basis)
6068 }
6069}
6070
6071impl From<WideSpinBasisGeneral1024> for WidePackedBasis {
6072 fn from(basis: WideSpinBasisGeneral1024) -> Self {
6073 Self::GeneralSpin1024(basis)
6074 }
6075}
6076
6077impl From<WideSpinBasisGeneral4096> for WidePackedBasis {
6078 fn from(basis: WideSpinBasisGeneral4096) -> Self {
6079 Self::GeneralSpin4096(basis)
6080 }
6081}
6082
6083impl From<WideSpinBasisGeneral16384> for WidePackedBasis {
6084 fn from(basis: WideSpinBasisGeneral16384) -> Self {
6085 Self::GeneralSpin16384(basis)
6086 }
6087}
6088
6089pub fn basis_zeros<const WORDS: usize>(length: usize) -> Vec<WideState<WORDS>> {
6090 vec![WideState::zero(); length]
6091}
6092
6093pub fn basis_ones<const WORDS: usize>(length: usize) -> Vec<WideState<WORDS>> {
6094 vec![WideState::zero().bitwise_not(); length]
6095}
6096
6097pub fn bitwise_and<const WORDS: usize>(
6098 left: WideState<WORDS>,
6099 right: WideState<WORDS>,
6100) -> WideState<WORDS> {
6101 left.bitwise_and(right)
6102}
6103
6104pub fn bitwise_or<const WORDS: usize>(
6105 left: WideState<WORDS>,
6106 right: WideState<WORDS>,
6107) -> WideState<WORDS> {
6108 left.bitwise_or(right)
6109}
6110
6111pub fn bitwise_xor<const WORDS: usize>(
6112 left: WideState<WORDS>,
6113 right: WideState<WORDS>,
6114) -> WideState<WORDS> {
6115 left.bitwise_xor(right)
6116}
6117
6118pub fn bitwise_not<const WORDS: usize>(value: WideState<WORDS>) -> WideState<WORDS> {
6119 value.bitwise_not()
6120}
6121
6122pub fn bitwise_leftshift<const WORDS: usize>(
6123 value: WideState<WORDS>,
6124 shift: usize,
6125) -> WideState<WORDS> {
6126 value.left_shift(shift)
6127}
6128
6129pub fn bitwise_rightshift<const WORDS: usize>(
6130 value: WideState<WORDS>,
6131 shift: usize,
6132) -> WideState<WORDS> {
6133 value.right_shift(shift)
6134}
6135
6136pub fn python_int_to_basis_int<const WORDS: usize>(value: u128) -> WideState<WORDS> {
6137 let mut words = [0_u64; WORDS];
6138 if WORDS > 0 {
6139 words[0] = value as u64;
6140 }
6141 if WORDS > 1 {
6142 words[1] = (value >> 64) as u64;
6143 }
6144 WideState::from_words(words)
6145}
6146
6147pub fn basis_int_to_python_int<const WORDS: usize>(value: WideState<WORDS>) -> Result<u128> {
6148 if value.words.iter().skip(2).any(|word| *word != 0) {
6149 return Err(QmbedError::UnsupportedBackend(
6150 "wide basis integer does not fit into a Python-compatible u128".into(),
6151 ));
6152 }
6153 Ok(u128::from(value.words.first().copied().unwrap_or_default())
6154 | (u128::from(value.words.get(1).copied().unwrap_or_default()) << 64))
6155}
6156
6157pub fn state_from_biguint<const WORDS: usize>(value: &BigUint) -> Result<WideState<WORDS>> {
6160 let digits = value.to_u64_digits();
6161 if digits.len() > WORDS {
6162 return Err(QmbedError::UnsupportedBackend(format!(
6163 "integer needs {} bits but this state stores {} bits",
6164 value.bits(),
6165 WideState::<WORDS>::capacity_bits()
6166 )));
6167 }
6168 let mut words = [0_u64; WORDS];
6169 words[..digits.len()].copy_from_slice(&digits);
6170 Ok(WideState::from_words(words))
6171}
6172
6173pub fn state_to_biguint<const WORDS: usize>(value: WideState<WORDS>) -> BigUint {
6175 let bytes: Vec<_> = value
6176 .words()
6177 .iter()
6178 .flat_map(|word| word.to_le_bytes())
6179 .collect();
6180 BigUint::from_bytes_le(&bytes)
6181}
6182
6183pub fn get_basis_type(
6184 sites: usize,
6185 _particles: Option<usize>,
6186 states_per_site: usize,
6187) -> Result<StateStorage> {
6188 if states_per_site < 2 {
6189 return Err(QmbedError::InvalidSector(
6190 "states_per_site must be at least two".into(),
6191 ));
6192 }
6193 let bits_per_site =
6194 usize::try_from(usize::BITS - (states_per_site - 1).leading_zeros()).unwrap_or(usize::MAX);
6195 let bits = sites
6196 .checked_mul(bits_per_site)
6197 .ok_or_else(|| QmbedError::UnsupportedBackend("basis bit width overflow".into()))?;
6198 StateStorage::for_bits(bits.max(1))
6199}
6200
6201pub fn coherent_state(amplitude: Complex64, states: usize) -> Result<Vec<Complex64>> {
6202 if states == 0 || !amplitude.re.is_finite() || !amplitude.im.is_finite() {
6203 return Err(QmbedError::InvalidOptions(
6204 "coherent-state amplitude must be finite and the cutoff positive".into(),
6205 ));
6206 }
6207 let mut coefficients = Vec::with_capacity(states);
6208 let mut coefficient = Complex64::new((-0.5 * amplitude.norm_sqr()).exp(), 0.0);
6209 coefficients.push(coefficient);
6210 for occupation in 1..states {
6211 coefficient *= amplitude / (occupation as f64).sqrt();
6212 coefficients.push(coefficient);
6213 }
6214 Ok(coefficients)
6215}
6216
6217fn binomial(n: usize, k: usize) -> usize {
6218 let k = k.min(n.saturating_sub(k));
6219 (0..k).fold(1_usize, |value, index| {
6220 value.saturating_mul(n - index) / (index + 1)
6221 })
6222}
6223
6224pub fn photon_hspace_dim(
6226 sites: usize,
6227 total_excitations: Option<usize>,
6228 photon_cutoff: Option<usize>,
6229) -> Result<usize> {
6230 match (total_excitations, photon_cutoff) {
6231 (None, Some(cutoff)) => 1_usize
6232 .checked_shl(u32::try_from(sites).unwrap_or(u32::MAX))
6233 .and_then(|matter| matter.checked_mul(cutoff.saturating_add(1)))
6234 .ok_or_else(|| QmbedError::UnsupportedBackend("photon dimension overflow".into())),
6235 (Some(total), cutoff) => {
6236 let minimum_matter =
6237 cutoff.map_or(0, |maximum_photons| total.saturating_sub(maximum_photons));
6238 let maximum_matter = sites.min(total);
6239 Ok((minimum_matter..=maximum_matter)
6240 .map(|matter| binomial(sites, matter))
6241 .sum())
6242 }
6243 (None, None) => Err(QmbedError::InvalidSector(
6244 "either total excitation or photon cutoff must be finite".into(),
6245 )),
6246 }
6247}
6248
6249#[derive(Clone, Debug)]
6251pub struct BasisProjector {
6252 source_dimension: usize,
6253 reduced_dimension: usize,
6254 column_offsets: Vec<usize>,
6255 row_indices: Vec<usize>,
6256 values: Vec<Complex64>,
6257}
6258
6259impl BasisProjector {
6260 fn from_columns(
6261 source_dimension: usize,
6262 mut columns: Vec<Vec<(usize, Complex64)>>,
6263 ) -> Result<Self> {
6264 if columns.iter().flatten().any(|(row, value)| {
6265 *row >= source_dimension || !value.re.is_finite() || !value.im.is_finite()
6266 }) {
6267 return Err(QmbedError::DimensionMismatch(
6268 "projector contains an invalid parent-space row or coefficient".into(),
6269 ));
6270 }
6271 let reduced_dimension = columns.len();
6272 let mut column_offsets = Vec::with_capacity(reduced_dimension + 1);
6273 let mut row_indices = Vec::new();
6274 let mut values = Vec::new();
6275 column_offsets.push(0);
6276 for column in &mut columns {
6277 column.sort_by_key(|(row, _)| *row);
6278 for &(row, value) in column.iter() {
6279 row_indices.push(row);
6280 values.push(value);
6281 }
6282 column_offsets.push(row_indices.len());
6283 }
6284 Ok(Self {
6285 source_dimension,
6286 reduced_dimension,
6287 column_offsets,
6288 row_indices,
6289 values,
6290 })
6291 }
6292
6293 pub fn from_operator(
6300 projector: &(impl LinearOperator + ?Sized),
6301 tolerance: f64,
6302 ) -> Result<Self> {
6303 if !tolerance.is_finite() || tolerance <= 0.0 {
6304 return Err(QmbedError::InvalidOptions(
6305 "projector-isometry tolerance must be positive and finite".into(),
6306 ));
6307 }
6308 let (source_dimension, reduced_dimension) = projector.shape();
6309 let mut columns = vec![Vec::new(); reduced_dimension];
6310 if let Some(entries) = projector.stored_triplets()? {
6311 for (row, column, value) in entries {
6312 if value.norm() > f64::EPSILON {
6313 columns[column].push((row, value));
6314 }
6315 }
6316 } else {
6317 let mut input = vec![Complex64::new(0.0, 0.0); reduced_dimension];
6318 let mut output = vec![Complex64::new(0.0, 0.0); source_dimension];
6319 for column in 0..reduced_dimension {
6320 input.fill(Complex64::new(0.0, 0.0));
6321 input[column] = Complex64::new(1.0, 0.0);
6322 projector.apply(&input, &mut output)?;
6323 columns[column].extend(
6324 output
6325 .iter()
6326 .copied()
6327 .enumerate()
6328 .filter(|(_, value)| value.norm() > f64::EPSILON),
6329 );
6330 }
6331 }
6332 let projector = Self::from_columns(source_dimension, columns)?;
6333 let mut coordinate = vec![Complex64::new(0.0, 0.0); reduced_dimension];
6334 let mut parent = vec![Complex64::new(0.0, 0.0); source_dimension];
6335 let mut recovered = vec![Complex64::new(0.0, 0.0); reduced_dimension];
6336 for column in 0..reduced_dimension {
6337 coordinate.fill(Complex64::new(0.0, 0.0));
6338 coordinate[column] = Complex64::new(1.0, 0.0);
6339 projector.apply(&coordinate, &mut parent)?;
6340 projector.project(&parent, &mut recovered)?;
6341 for (row, value) in recovered.iter().copied().enumerate() {
6342 let expected = if row == column {
6343 Complex64::new(1.0, 0.0)
6344 } else {
6345 Complex64::new(0.0, 0.0)
6346 };
6347 if (value - expected).norm() > tolerance {
6348 return Err(QmbedError::InvalidOptions(format!(
6349 "operator columns are not isometric within tolerance {tolerance}"
6350 )));
6351 }
6352 }
6353 }
6354 Ok(projector)
6355 }
6356
6357 pub fn from_embedding<Reduced, Parent>(reduced: &Reduced, parent: &Parent) -> Result<Self>
6359 where
6360 Reduced: Basis,
6361 Parent: Basis<State = Reduced::State>,
6362 {
6363 let columns = (0..reduced.len())
6364 .map(|column| {
6365 let state = reduced.state(column)?;
6366 Ok(vec![(parent.index(state)?, Complex64::new(1.0, 0.0))])
6367 })
6368 .collect::<Result<Vec<_>>>()?;
6369 Self::from_columns(parent.len(), columns)
6370 }
6371
6372 pub fn between<Reduced, Parent>(reduced: &Reduced, parent: &Parent) -> Result<Self>
6379 where
6380 Reduced: Basis,
6381 Parent: Basis<State = Reduced::State>,
6382 {
6383 let mut columns = vec![Vec::<(usize, Complex64)>::new(); reduced.len()];
6384 for row in 0..parent.len() {
6385 let state = parent.state(row)?;
6386 let Some(image) = reduced.reduction_image(state)? else {
6387 continue;
6388 };
6389 let column = reduced.index(*image.representative())?;
6390 columns[column].push((row, image.amplitude()));
6391 }
6392 Self::from_columns(parent.len(), columns)
6393 }
6394
6395 pub fn from_general<Parent>(basis: &GeneralBasis<Parent>) -> Result<Self>
6396 where
6397 Parent: Basis,
6398 Parent::State: Hash + Ord + 'static,
6399 {
6400 Self::between(basis, &basis.parent)
6401 }
6402
6403 pub const fn source_dimension(&self) -> usize {
6404 self.source_dimension
6405 }
6406
6407 pub const fn reduced_dimension(&self) -> usize {
6408 self.reduced_dimension
6409 }
6410
6411 pub fn project(&self, parent: &[Complex64], reduced: &mut [Complex64]) -> Result<()> {
6413 if parent.len() != self.source_dimension || reduced.len() != self.reduced_dimension {
6414 return Err(QmbedError::DimensionMismatch(
6415 "projector input or output length does not match".into(),
6416 ));
6417 }
6418 reduced.fill(Complex64::new(0.0, 0.0));
6419 for (column, reduced_value) in reduced.iter_mut().enumerate() {
6420 for position in self.column_offsets[column]..self.column_offsets[column + 1] {
6421 *reduced_value += self.values[position].conj() * parent[self.row_indices[position]];
6422 }
6423 }
6424 Ok(())
6425 }
6426
6427 pub fn lifted(&self, reduced: &[Complex64]) -> Result<Vec<Complex64>> {
6428 let mut parent = vec![Complex64::new(0.0, 0.0); self.source_dimension];
6429 self.apply(reduced, &mut parent)?;
6430 Ok(parent)
6431 }
6432
6433 pub fn projected(&self, parent: &[Complex64]) -> Result<Vec<Complex64>> {
6434 let mut reduced = vec![Complex64::new(0.0, 0.0); self.reduced_dimension];
6435 self.project(parent, &mut reduced)?;
6436 Ok(reduced)
6437 }
6438
6439 pub fn lift_batch(&self, reduced: &[Vec<Complex64>]) -> Result<Vec<Vec<Complex64>>> {
6440 reduced.iter().map(|state| self.lifted(state)).collect()
6441 }
6442
6443 pub fn project_batch(&self, parent: &[Vec<Complex64>]) -> Result<Vec<Vec<Complex64>>> {
6444 parent.iter().map(|state| self.projected(state)).collect()
6445 }
6446
6447 pub fn lift_density(&self, reduced: &[Complex64]) -> Result<Vec<Complex64>> {
6449 if reduced.len()
6450 != self
6451 .reduced_dimension
6452 .saturating_mul(self.reduced_dimension)
6453 {
6454 return Err(QmbedError::DimensionMismatch(
6455 "reduced density matrix does not match the projector domain".into(),
6456 ));
6457 }
6458 let mut parent =
6459 vec![Complex64::new(0.0, 0.0); self.source_dimension * self.source_dimension];
6460 for reduced_row in 0..self.reduced_dimension {
6461 for reduced_column in 0..self.reduced_dimension {
6462 let density = reduced[reduced_row * self.reduced_dimension + reduced_column];
6463 if density.norm_sqr() <= f64::EPSILON {
6464 continue;
6465 }
6466 for left in self.column_offsets[reduced_row]..self.column_offsets[reduced_row + 1] {
6467 for right in
6468 self.column_offsets[reduced_column]..self.column_offsets[reduced_column + 1]
6469 {
6470 let row = self.row_indices[left];
6471 let column = self.row_indices[right];
6472 parent[row * self.source_dimension + column] +=
6473 self.values[left] * density * self.values[right].conj();
6474 }
6475 }
6476 }
6477 }
6478 Ok(parent)
6479 }
6480
6481 pub fn symmetry_leakage_norm(&self, operator: &(impl LinearOperator + ?Sized)) -> Result<f64> {
6485 if operator.shape() != (self.source_dimension, self.source_dimension) {
6486 return Err(QmbedError::DimensionMismatch(
6487 "symmetry check requires a square parent-space operator".into(),
6488 ));
6489 }
6490 let mut total = 0.0;
6491 let mut reduced_basis = vec![Complex64::new(0.0, 0.0); self.reduced_dimension];
6492 let mut applied = vec![Complex64::new(0.0, 0.0); self.source_dimension];
6493 for column in 0..self.reduced_dimension {
6494 reduced_basis.fill(Complex64::new(0.0, 0.0));
6495 reduced_basis[column] = Complex64::new(1.0, 0.0);
6496 let lifted = self.lifted(&reduced_basis)?;
6497 operator.apply(&lifted, &mut applied)?;
6498 let projected = self.projected(&applied)?;
6499 let invariant_component = self.lifted(&projected)?;
6500 total += applied
6501 .iter()
6502 .zip(invariant_component)
6503 .map(|(value, invariant)| (*value - invariant).norm_sqr())
6504 .sum::<f64>();
6505 }
6506 Ok(total.sqrt())
6507 }
6508
6509 pub fn preserves_operator_symmetry(
6510 &self,
6511 operator: &(impl LinearOperator + ?Sized),
6512 tolerance: f64,
6513 ) -> Result<bool> {
6514 if !tolerance.is_finite() || tolerance < 0.0 {
6515 return Err(QmbedError::InvalidOptions(
6516 "symmetry-check tolerance must be finite and nonnegative".into(),
6517 ));
6518 }
6519 Ok(self.symmetry_leakage_norm(operator)? <= tolerance)
6520 }
6521}
6522
6523impl LinearOperator for BasisProjector {
6524 fn shape(&self) -> (usize, usize) {
6525 (self.source_dimension, self.reduced_dimension)
6526 }
6527
6528 fn format(&self) -> MatrixFormat {
6529 MatrixFormat::Csc
6530 }
6531
6532 fn apply(&self, input: &[Complex64], output: &mut [Complex64]) -> Result<()> {
6533 check_apply_shape(self.shape(), input, output)?;
6534 output.fill(Complex64::new(0.0, 0.0));
6535 for (column, &input_value) in input.iter().enumerate() {
6536 for position in self.column_offsets[column]..self.column_offsets[column + 1] {
6537 output[self.row_indices[position]] += self.values[position] * input_value;
6538 }
6539 }
6540 Ok(())
6541 }
6542
6543 fn stored_triplets(&self) -> Result<Option<Vec<(usize, usize, Complex64)>>> {
6544 let mut entries = Vec::with_capacity(self.values.len());
6545 for column in 0..self.reduced_dimension {
6546 for position in self.column_offsets[column]..self.column_offsets[column + 1] {
6547 entries.push((self.row_indices[position], column, self.values[position]));
6548 }
6549 }
6550 Ok(Some(entries))
6551 }
6552}
6553
6554#[derive(Clone, Debug)]
6560pub struct PackedTensorBasis {
6561 factors: Vec<PackedBasis>,
6562 dimensions: Vec<usize>,
6563 strides: Vec<usize>,
6564 dimension: usize,
6565}
6566
6567impl PackedTensorBasis {
6568 pub fn new(factors: impl IntoIterator<Item = PackedBasis>) -> Result<Self> {
6569 let mut flattened = Vec::new();
6570 for factor in factors {
6571 match factor {
6572 PackedBasis::Tensor(tensor) => flattened.extend(tensor.factors),
6573 other => flattened.push(other),
6574 }
6575 }
6576 if flattened.len() < 2 {
6577 return Err(QmbedError::InvalidSector(
6578 "a packed tensor basis requires at least two factors".into(),
6579 ));
6580 }
6581 let dimensions: Vec<_> = flattened.iter().map(Basis::len).collect();
6582 if dimensions.contains(&0) {
6583 return Err(QmbedError::InvalidSector(
6584 "tensor-basis factors must be nonempty".into(),
6585 ));
6586 }
6587 let mut strides = vec![1; dimensions.len()];
6588 let mut dimension = 1_usize;
6589 for index in (0..dimensions.len()).rev() {
6590 strides[index] = dimension;
6591 dimension = dimension.checked_mul(dimensions[index]).ok_or_else(|| {
6592 QmbedError::UnsupportedBackend("tensor-basis size overflow".into())
6593 })?;
6594 }
6595 Ok(Self {
6596 factors: flattened,
6597 dimensions,
6598 strides,
6599 dimension,
6600 })
6601 }
6602
6603 pub fn factors(&self) -> &[PackedBasis] {
6604 &self.factors
6605 }
6606
6607 pub fn dimensions(&self) -> &[usize] {
6608 &self.dimensions
6609 }
6610
6611 fn row(&self, state: u128) -> Result<usize> {
6612 let row = usize::try_from(state).map_err(|_| QmbedError::StateNotInBasis)?;
6613 (row < self.dimension)
6614 .then_some(row)
6615 .ok_or(QmbedError::StateNotInBasis)
6616 }
6617
6618 fn factor_rows(&self, state: u128) -> Result<Vec<usize>> {
6619 let row = self.row(state)?;
6620 Ok(self
6621 .dimensions
6622 .iter()
6623 .zip(&self.strides)
6624 .map(|(&dimension, &stride)| (row / stride) % dimension)
6625 .collect())
6626 }
6627
6628 fn operator_factors<'a>(&self, operator: &'a str) -> Result<Vec<&'a str>> {
6629 let factors: Vec<_> = operator.split('|').collect();
6630 if factors.len() != self.factors.len() {
6631 return Err(QmbedError::InvalidOperator(format!(
6632 "tensor operator has {} factors, expected {}",
6633 factors.len(),
6634 self.factors.len()
6635 )));
6636 }
6637 Ok(factors)
6638 }
6639}
6640
6641impl Basis for PackedTensorBasis {
6642 type State = u128;
6643
6644 fn len(&self) -> usize {
6645 self.dimension
6646 }
6647
6648 fn state(&self, index: usize) -> Result<Self::State> {
6649 if index >= self.dimension {
6650 return Err(QmbedError::StateNotInBasis);
6651 }
6652 Ok(index as u128)
6653 }
6654
6655 fn index(&self, state: Self::State) -> Result<usize> {
6656 self.row(state)
6657 }
6658
6659 fn apply_local(
6660 &self,
6661 state: Self::State,
6662 operator: &str,
6663 sites: &[usize],
6664 ) -> Result<Option<(Self::State, Complex64)>> {
6665 let transitions = self.apply_local_transitions(state, operator, sites)?;
6666 match transitions.as_slice() {
6667 [] => Ok(None),
6668 [transition] => Ok(Some(*transition)),
6669 _ => Err(QmbedError::UnsupportedBackend(
6670 "this tensor local action branches; use apply_local_transitions".into(),
6671 )),
6672 }
6673 }
6674
6675 fn apply_local_transitions(
6676 &self,
6677 state: Self::State,
6678 operator: &str,
6679 sites: &[usize],
6680 ) -> Result<LocalTransitions<Self::State>> {
6681 let operators = self.operator_factors(operator)?;
6682 let source_rows = self.factor_rows(state)?;
6683 let expected_arity: usize = operators.iter().map(|part| part.chars().count()).sum();
6684 if sites.len() != expected_arity {
6685 return Err(QmbedError::InvalidCoupling(format!(
6686 "tensor operator arity {expected_arity} does not match {} sites",
6687 sites.len()
6688 )));
6689 }
6690
6691 let mut partial = vec![(0_usize, Complex64::new(1.0, 0.0))];
6692 let mut site_offset = 0;
6693 for (factor_index, ((factor, part), &source_row)) in self
6694 .factors
6695 .iter()
6696 .zip(&operators)
6697 .zip(&source_rows)
6698 .enumerate()
6699 {
6700 let arity = part.chars().count();
6701 let source_state = factor.state(source_row)?;
6702 let transitions = if part.is_empty() {
6703 LocalTransitions::from_iter([(source_state, Complex64::new(1.0, 0.0))])
6704 } else {
6705 factor.apply_local_transitions(
6706 source_state,
6707 part,
6708 &sites[site_offset..site_offset + arity],
6709 )?
6710 };
6711 site_offset += arity;
6712 let mut next = Vec::with_capacity(partial.len().saturating_mul(transitions.len()));
6713 for &(row, amplitude) in &partial {
6714 for &(target_state, local) in &transitions {
6715 let target_row = factor.index(target_state)?;
6716 next.push((
6717 row + target_row * self.strides[factor_index],
6718 amplitude * local,
6719 ));
6720 }
6721 }
6722 partial = next;
6723 }
6724
6725 let mut accumulated = HashMap::<usize, Complex64>::new();
6726 for (row, amplitude) in partial {
6727 *accumulated.entry(row).or_insert(Complex64::new(0.0, 0.0)) += amplitude;
6728 }
6729 let mut transitions: Vec<_> = accumulated
6730 .into_iter()
6731 .filter(|(_, amplitude)| amplitude.norm() > f64::EPSILON)
6732 .collect();
6733 transitions.sort_unstable_by_key(|(row, _)| *row);
6734 Ok(transitions
6735 .into_iter()
6736 .map(|(row, amplitude)| (row as u128, amplitude))
6737 .collect())
6738 }
6739
6740 fn visit_local_unreduced_transitions<F>(
6741 &self,
6742 state: Self::State,
6743 operator: &str,
6744 sites: &[usize],
6745 mut visit: F,
6746 ) -> Result<()>
6747 where
6748 F: FnMut(Self::State, Complex64) -> Result<()>,
6749 {
6750 for (target, amplitude) in self.apply_local_transitions(state, operator, sites)? {
6751 visit(target, amplitude)?;
6752 }
6753 Ok(())
6754 }
6755
6756 fn operator_preserves_particle_sector(&self, operator: &str) -> Result<bool> {
6757 self.operator_factors(operator)?
6758 .into_iter()
6759 .zip(&self.factors)
6760 .try_fold(true, |preserves, (part, factor)| {
6761 Ok(preserves && factor.operator_preserves_particle_sector(part)?)
6762 })
6763 }
6764
6765 fn operator_preserves_particle_sector_on_sites(
6766 &self,
6767 operator: &str,
6768 sites: &[usize],
6769 ) -> Result<bool> {
6770 let operators = self.operator_factors(operator)?;
6771 let expected_arity: usize = operators.iter().map(|part| part.chars().count()).sum();
6772 if sites.len() != expected_arity {
6773 return Err(QmbedError::InvalidCoupling(
6774 "tensor operator arity does not match its sites".into(),
6775 ));
6776 }
6777 let mut offset = 0;
6778 for (part, factor) in operators.into_iter().zip(&self.factors) {
6779 let arity = part.chars().count();
6780 if !factor
6781 .operator_preserves_particle_sector_on_sites(part, &sites[offset..offset + arity])?
6782 {
6783 return Ok(false);
6784 }
6785 offset += arity;
6786 }
6787 Ok(true)
6788 }
6789}
6790
6791#[derive(Clone, Debug)]
6799pub struct PackedPhotonBasis {
6800 matter: Box<PackedBasis>,
6801 photon: BosonBasis1D,
6802 photon_dimension: usize,
6803 full_dimension: usize,
6804 states: Vec<u128>,
6805 indices: HashMap<u128, usize>,
6806 total_excitations: Option<usize>,
6807}
6808
6809impl PackedPhotonBasis {
6810 pub fn new(
6814 matter: PackedBasis,
6815 photon_cutoff: usize,
6816 total_excitations: Option<usize>,
6817 ) -> Result<Self> {
6818 let photon_dimension = photon_cutoff
6819 .checked_add(1)
6820 .ok_or_else(|| QmbedError::UnsupportedBackend("photon cutoff is too large".into()))?;
6821 let photon = BosonBasis1D::builder(1, photon_dimension).build()?;
6822 let full_dimension = matter.len().checked_mul(photon_dimension).ok_or_else(|| {
6823 QmbedError::UnsupportedBackend("matter-photon basis size overflow".into())
6824 })?;
6825
6826 let mut states = Vec::with_capacity(full_dimension);
6827 for matter_row in 0..matter.len() {
6828 let matter_state = matter.state(matter_row)?;
6829 let matter_excitations = matter.additive_quantum_number(matter_state)?;
6830 for photon_occupation in 0..photon_dimension {
6831 if total_excitations.is_none_or(|total| {
6832 matter_excitations
6833 .checked_add(photon_occupation)
6834 .is_some_and(|value| value == total)
6835 }) {
6836 states.push(Self::encode_product_state(
6837 matter_state,
6838 photon_occupation,
6839 photon_dimension,
6840 )?);
6841 }
6842 }
6843 }
6844 if states.is_empty() {
6845 return Err(QmbedError::InvalidSector(
6846 "the requested total-excitation photon sector is empty".into(),
6847 ));
6848 }
6849 let indices = states
6850 .iter()
6851 .copied()
6852 .enumerate()
6853 .map(|(index, state)| (state, index))
6854 .collect();
6855 Ok(Self {
6856 matter: Box::new(matter),
6857 photon,
6858 photon_dimension,
6859 full_dimension,
6860 states,
6861 indices,
6862 total_excitations,
6863 })
6864 }
6865
6866 pub fn matter(&self) -> &PackedBasis {
6867 &self.matter
6868 }
6869
6870 pub const fn photon(&self) -> &BosonBasis1D {
6871 &self.photon
6872 }
6873
6874 pub const fn photon_dimension(&self) -> usize {
6875 self.photon_dimension
6876 }
6877
6878 pub const fn full_dimension(&self) -> usize {
6879 self.full_dimension
6880 }
6881
6882 pub const fn total_excitations(&self) -> Option<usize> {
6883 self.total_excitations
6884 }
6885
6886 fn encode_product_state(
6887 matter_state: u128,
6888 photon_occupation: usize,
6889 photon_dimension: usize,
6890 ) -> Result<u128> {
6891 matter_state
6892 .checked_mul(photon_dimension as u128)
6893 .and_then(|value| value.checked_add(photon_occupation as u128))
6894 .ok_or_else(|| {
6895 QmbedError::UnsupportedBackend("matter-photon state encoding exceeds u128".into())
6896 })
6897 }
6898
6899 fn product_state(&self, state: u128) -> Result<(u128, usize)> {
6900 let photon_occupation = usize::try_from(state % self.photon_dimension as u128)
6901 .map_err(|_| QmbedError::StateNotInBasis)?;
6902 Ok((state / self.photon_dimension as u128, photon_occupation))
6903 }
6904
6905 fn product_rows(&self, state: u128) -> Result<(usize, usize)> {
6906 let (matter_state, photon_occupation) = self.product_state(state)?;
6907 Ok((self.matter.index(matter_state)?, photon_occupation))
6908 }
6909
6910 fn operator_parts<'a>(&self, operator: &'a str) -> Result<(&'a str, &'a str)> {
6911 let (matter, photon) = operator
6912 .split_once('|')
6913 .ok_or_else(|| QmbedError::InvalidOperator(operator.into()))?;
6914 if photon.contains('|') {
6915 return Err(QmbedError::InvalidOperator(operator.into()));
6916 }
6917 Ok((matter, photon))
6918 }
6919
6920 fn raw_local_transitions(
6921 &self,
6922 state: u128,
6923 operator: &str,
6924 sites: &[usize],
6925 ) -> Result<LocalTransitions<u128>> {
6926 self.index(state)?;
6927 let (matter_operator, photon_operator) = self.operator_parts(operator)?;
6928 let matter_arity = matter_operator.chars().count();
6929 let photon_arity = photon_operator.chars().count();
6930 if sites.len() != matter_arity + photon_arity {
6931 return Err(QmbedError::InvalidCoupling(format!(
6932 "matter-photon operator arity {} does not match {} sites",
6933 matter_arity + photon_arity,
6934 sites.len()
6935 )));
6936 }
6937 let (matter_row, photon_row) = self.product_rows(state)?;
6938 let matter_state = self.matter.state(matter_row)?;
6939 let photon_state = self.photon.state(photon_row)?;
6940 let matter_transitions = if matter_operator.is_empty() {
6941 LocalTransitions::from_iter([(matter_state, Complex64::new(1.0, 0.0))])
6942 } else {
6943 self.matter.apply_local_transitions(
6944 matter_state,
6945 matter_operator,
6946 &sites[..matter_arity],
6947 )?
6948 };
6949 let photon_transitions = if photon_operator.is_empty() {
6950 LocalTransitions::from_iter([(photon_state, Complex64::new(1.0, 0.0))])
6951 } else {
6952 self.photon.apply_local_transitions(
6953 photon_state,
6954 photon_operator,
6955 &sites[matter_arity..],
6956 )?
6957 };
6958
6959 let mut accumulated = HashMap::<u128, Complex64>::new();
6960 for &(target_matter, matter_amplitude) in &matter_transitions {
6961 for &(target_photon, photon_amplitude) in &photon_transitions {
6962 let target_photon_row = self.photon.index(target_photon)?;
6963 let target = Self::encode_product_state(
6964 target_matter,
6965 target_photon_row,
6966 self.photon_dimension,
6967 )?;
6968 *accumulated
6969 .entry(target)
6970 .or_insert(Complex64::new(0.0, 0.0)) += matter_amplitude * photon_amplitude;
6971 }
6972 }
6973 let mut transitions: Vec<_> = accumulated
6974 .into_iter()
6975 .filter(|(_, amplitude)| amplitude.norm() > f64::EPSILON)
6976 .collect();
6977 transitions.sort_unstable_by_key(|(row, _)| *row);
6978 Ok(transitions.into())
6979 }
6980}
6981
6982impl Basis for PackedPhotonBasis {
6983 type State = u128;
6984
6985 fn len(&self) -> usize {
6986 self.states.len()
6987 }
6988
6989 fn state(&self, index: usize) -> Result<Self::State> {
6990 self.states
6991 .get(index)
6992 .copied()
6993 .ok_or(QmbedError::StateNotInBasis)
6994 }
6995
6996 fn index(&self, state: Self::State) -> Result<usize> {
6997 self.indices
6998 .get(&state)
6999 .copied()
7000 .ok_or(QmbedError::StateNotInBasis)
7001 }
7002
7003 fn apply_local(
7004 &self,
7005 state: Self::State,
7006 operator: &str,
7007 sites: &[usize],
7008 ) -> Result<Option<(Self::State, Complex64)>> {
7009 let transitions = self.apply_local_transitions(state, operator, sites)?;
7010 match transitions.as_slice() {
7011 [] => Ok(None),
7012 [transition] => Ok(Some(*transition)),
7013 _ => Err(QmbedError::UnsupportedBackend(
7014 "this matter-photon action branches; use apply_local_transitions".into(),
7015 )),
7016 }
7017 }
7018
7019 fn apply_local_transitions(
7020 &self,
7021 state: Self::State,
7022 operator: &str,
7023 sites: &[usize],
7024 ) -> Result<LocalTransitions<Self::State>> {
7025 Ok(self
7026 .raw_local_transitions(state, operator, sites)?
7027 .into_iter()
7028 .filter(|(target, _)| self.indices.contains_key(target))
7029 .collect())
7030 }
7031
7032 fn apply_local_unreduced_transitions(
7033 &self,
7034 state: Self::State,
7035 operator: &str,
7036 sites: &[usize],
7037 ) -> Result<LocalTransitions<Self::State>> {
7038 self.raw_local_transitions(state, operator, sites)
7039 }
7040
7041 fn visit_local_unreduced_transitions<F>(
7042 &self,
7043 state: Self::State,
7044 operator: &str,
7045 sites: &[usize],
7046 mut visit: F,
7047 ) -> Result<()>
7048 where
7049 F: FnMut(Self::State, Complex64) -> Result<()>,
7050 {
7051 for (target, amplitude) in self.raw_local_transitions(state, operator, sites)? {
7052 visit(target, amplitude)?;
7053 }
7054 Ok(())
7055 }
7056
7057 fn transition_orbit_size(&self, state: Self::State) -> Result<usize> {
7058 let (matter_state, _) = self.product_state(state)?;
7059 self.matter.transition_orbit_size(matter_state)
7060 }
7061
7062 fn reduction_image(&self, state: Self::State) -> Result<Option<ReductionImage<Self::State>>> {
7063 let (matter_state, photon_occupation) = self.product_state(state)?;
7064 let matter_excitations = match self.matter.additive_quantum_number(matter_state) {
7065 Ok(value) => value,
7066 Err(QmbedError::StateNotInBasis) => return Ok(None),
7067 Err(error) => return Err(error),
7068 };
7069 if self.total_excitations.is_some_and(|total| {
7070 matter_excitations
7071 .checked_add(photon_occupation)
7072 .is_none_or(|value| value != total)
7073 }) {
7074 return Ok(None);
7075 }
7076 let Some(matter_image) = self.matter.reduction_image(matter_state)? else {
7077 return Ok(None);
7078 };
7079 let representative = Self::encode_product_state(
7080 *matter_image.representative(),
7081 photon_occupation,
7082 self.photon_dimension,
7083 )?;
7084 if !self.indices.contains_key(&representative) {
7085 return Ok(None);
7086 }
7087 Ok(Some(ReductionImage::new(
7088 representative,
7089 matter_image.phase(),
7090 matter_image.orbit_size(),
7091 )?))
7092 }
7093
7094 fn operator_preserves_particle_sector(&self, operator: &str) -> Result<bool> {
7095 self.operator_parts(operator)?;
7096 Ok(self.total_excitations.is_none() || operator_number_change(operator)? == Some(0))
7097 }
7098}
7099
7100#[derive(Clone, Debug)]
7101pub enum PackedBasis {
7102 Spin(SpinBasis1D),
7103 Boson(BosonBasis1D),
7104 SpinlessFermion(SpinlessFermionBasis1D),
7105 SpinfulFermion(SpinfulFermionBasis1D),
7106 GeneralSpin(SpinBasisGeneral),
7107 GeneralBoson(BosonBasisGeneral),
7108 GeneralSpinlessFermion(SpinlessFermionBasisGeneral),
7109 GeneralSpinfulFermion(SpinfulFermionBasisGeneral),
7110 Tensor(Box<PackedTensorBasis>),
7111 Photon(Box<PackedPhotonBasis>),
7112 User(Box<UserBasisGeneral>),
7113 Reversed(Box<PackedBasis>),
7114}
7115
7116impl PackedBasis {
7117 pub fn reversed(self) -> Self {
7122 match self {
7123 Self::Reversed(inner) => *inner,
7124 basis => Self::Reversed(Box::new(basis)),
7125 }
7126 }
7127
7128 pub fn additive_quantum_number(&self, state: u128) -> Result<usize> {
7135 fn digit_sum(mut state: u128, sites: usize, base: usize) -> Result<usize> {
7136 let base = base as u128;
7137 let mut total = 0_usize;
7138 for _ in 0..sites {
7139 total = total.checked_add((state % base) as usize).ok_or_else(|| {
7140 QmbedError::UnsupportedBackend("additive quantum number overflow".into())
7141 })?;
7142 state /= base;
7143 }
7144 Ok(total)
7145 }
7146
7147 match self {
7148 Self::Spin(basis) => {
7149 basis.index(state)?;
7150 digit_sum(state, basis.sites(), usize::from(basis.spin_twice()) + 1)
7151 }
7152 Self::Boson(basis) => {
7153 basis.index(state)?;
7154 digit_sum(state, basis.sites(), basis.states_per_site())
7155 }
7156 Self::SpinlessFermion(basis) => {
7157 basis.index(state)?;
7158 Ok(state.count_ones() as usize)
7159 }
7160 Self::SpinfulFermion(basis) => {
7161 basis.index(state)?;
7162 Ok(state.count_ones() as usize)
7163 }
7164 Self::GeneralSpin(basis) => {
7165 basis.parent().index(state)?;
7166 digit_sum(
7167 state,
7168 basis.parent().sites(),
7169 usize::from(basis.parent().spin_twice()) + 1,
7170 )
7171 }
7172 Self::GeneralBoson(basis) => {
7173 basis.parent().index(state)?;
7174 digit_sum(
7175 state,
7176 basis.parent().sites(),
7177 basis.parent().states_per_site(),
7178 )
7179 }
7180 Self::GeneralSpinlessFermion(basis) => {
7181 basis.parent().index(state)?;
7182 Ok(state.count_ones() as usize)
7183 }
7184 Self::GeneralSpinfulFermion(basis) => {
7185 basis.parent().index(state)?;
7186 Ok(state.count_ones() as usize)
7187 }
7188 Self::Tensor(basis) => {
7189 basis.index(state)?;
7190 let rows = basis.factor_rows(state)?;
7191 basis
7192 .factors()
7193 .iter()
7194 .zip(rows)
7195 .try_fold(0_usize, |total, (factor, row)| {
7196 let factor_state = factor.state(row)?;
7197 total
7198 .checked_add(factor.additive_quantum_number(factor_state)?)
7199 .ok_or_else(|| {
7200 QmbedError::UnsupportedBackend(
7201 "additive quantum number overflow".into(),
7202 )
7203 })
7204 })
7205 }
7206 Self::Photon(basis) => {
7207 let (matter_state, photon_row) = basis.product_state(state)?;
7208 basis
7209 .matter
7210 .additive_quantum_number(matter_state)?
7211 .checked_add(photon_row)
7212 .ok_or_else(|| {
7213 QmbedError::UnsupportedBackend("additive quantum number overflow".into())
7214 })
7215 }
7216 Self::User(basis) => {
7217 basis.parent().index(state)?;
7218 Err(QmbedError::UnsupportedBackend(
7219 "a callback-defined basis must provide an additive quantum-number callback"
7220 .into(),
7221 ))
7222 }
7223 Self::Reversed(basis) => basis.additive_quantum_number(state),
7224 }
7225 }
7226}
7227
7228impl From<SpinBasis1D> for PackedBasis {
7229 fn from(basis: SpinBasis1D) -> Self {
7230 Self::Spin(basis)
7231 }
7232}
7233
7234impl From<BosonBasis1D> for PackedBasis {
7235 fn from(basis: BosonBasis1D) -> Self {
7236 Self::Boson(basis)
7237 }
7238}
7239
7240impl From<SpinlessFermionBasis1D> for PackedBasis {
7241 fn from(basis: SpinlessFermionBasis1D) -> Self {
7242 Self::SpinlessFermion(basis)
7243 }
7244}
7245
7246impl From<SpinfulFermionBasis1D> for PackedBasis {
7247 fn from(basis: SpinfulFermionBasis1D) -> Self {
7248 Self::SpinfulFermion(basis)
7249 }
7250}
7251
7252impl From<SpinBasisGeneral> for PackedBasis {
7253 fn from(basis: SpinBasisGeneral) -> Self {
7254 Self::GeneralSpin(basis)
7255 }
7256}
7257
7258impl From<BosonBasisGeneral> for PackedBasis {
7259 fn from(basis: BosonBasisGeneral) -> Self {
7260 Self::GeneralBoson(basis)
7261 }
7262}
7263
7264impl From<SpinlessFermionBasisGeneral> for PackedBasis {
7265 fn from(basis: SpinlessFermionBasisGeneral) -> Self {
7266 Self::GeneralSpinlessFermion(basis)
7267 }
7268}
7269
7270impl From<SpinfulFermionBasisGeneral> for PackedBasis {
7271 fn from(basis: SpinfulFermionBasisGeneral) -> Self {
7272 Self::GeneralSpinfulFermion(basis)
7273 }
7274}
7275
7276impl From<PackedTensorBasis> for PackedBasis {
7277 fn from(basis: PackedTensorBasis) -> Self {
7278 Self::Tensor(Box::new(basis))
7279 }
7280}
7281
7282impl From<PackedPhotonBasis> for PackedBasis {
7283 fn from(basis: PackedPhotonBasis) -> Self {
7284 Self::Photon(Box::new(basis))
7285 }
7286}
7287
7288impl From<UserBasisGeneral> for PackedBasis {
7289 fn from(basis: UserBasisGeneral) -> Self {
7290 Self::User(Box::new(basis))
7291 }
7292}
7293
7294impl Basis for PackedBasis {
7295 type State = u128;
7296
7297 fn len(&self) -> usize {
7298 match self {
7299 Self::Spin(basis) => basis.len(),
7300 Self::Boson(basis) => basis.len(),
7301 Self::SpinlessFermion(basis) => basis.len(),
7302 Self::SpinfulFermion(basis) => basis.len(),
7303 Self::GeneralSpin(basis) => basis.len(),
7304 Self::GeneralBoson(basis) => basis.len(),
7305 Self::GeneralSpinlessFermion(basis) => basis.len(),
7306 Self::GeneralSpinfulFermion(basis) => basis.len(),
7307 Self::Tensor(basis) => basis.len(),
7308 Self::Photon(basis) => basis.len(),
7309 Self::User(basis) => basis.len(),
7310 Self::Reversed(basis) => basis.len(),
7311 }
7312 }
7313
7314 fn state(&self, index: usize) -> Result<Self::State> {
7315 match self {
7316 Self::Spin(basis) => basis.state(index),
7317 Self::Boson(basis) => basis.state(index),
7318 Self::SpinlessFermion(basis) => basis.state(index),
7319 Self::SpinfulFermion(basis) => basis.state(index),
7320 Self::GeneralSpin(basis) => basis.state(index),
7321 Self::GeneralBoson(basis) => basis.state(index),
7322 Self::GeneralSpinlessFermion(basis) => basis.state(index),
7323 Self::GeneralSpinfulFermion(basis) => basis.state(index),
7324 Self::Tensor(basis) => basis.state(index),
7325 Self::Photon(basis) => basis.state(index),
7326 Self::User(basis) => basis.state(index),
7327 Self::Reversed(basis) => {
7328 let reversed = basis
7329 .len()
7330 .checked_sub(index + 1)
7331 .ok_or(QmbedError::StateNotInBasis)?;
7332 basis.state(reversed)
7333 }
7334 }
7335 }
7336
7337 fn index(&self, state: Self::State) -> Result<usize> {
7338 match self {
7339 Self::Spin(basis) => basis.index(state),
7340 Self::Boson(basis) => basis.index(state),
7341 Self::SpinlessFermion(basis) => basis.index(state),
7342 Self::SpinfulFermion(basis) => basis.index(state),
7343 Self::GeneralSpin(basis) => basis.index(state),
7344 Self::GeneralBoson(basis) => basis.index(state),
7345 Self::GeneralSpinlessFermion(basis) => basis.index(state),
7346 Self::GeneralSpinfulFermion(basis) => basis.index(state),
7347 Self::Tensor(basis) => basis.index(state),
7348 Self::Photon(basis) => basis.index(state),
7349 Self::User(basis) => basis.index(state),
7350 Self::Reversed(basis) => basis.index(state).map(|index| basis.len() - index - 1),
7351 }
7352 }
7353
7354 fn apply_local(
7355 &self,
7356 state: Self::State,
7357 operator: &str,
7358 sites: &[usize],
7359 ) -> Result<Option<(Self::State, Complex64)>> {
7360 match self {
7361 Self::Spin(basis) => basis.apply_local(state, operator, sites),
7362 Self::Boson(basis) => basis.apply_local(state, operator, sites),
7363 Self::SpinlessFermion(basis) => basis.apply_local(state, operator, sites),
7364 Self::SpinfulFermion(basis) => basis.apply_local(state, operator, sites),
7365 Self::GeneralSpin(basis) => basis.apply_local(state, operator, sites),
7366 Self::GeneralBoson(basis) => basis.apply_local(state, operator, sites),
7367 Self::GeneralSpinlessFermion(basis) => basis.apply_local(state, operator, sites),
7368 Self::GeneralSpinfulFermion(basis) => basis.apply_local(state, operator, sites),
7369 Self::Tensor(basis) => basis.apply_local(state, operator, sites),
7370 Self::Photon(basis) => basis.apply_local(state, operator, sites),
7371 Self::User(basis) => basis.apply_local(state, operator, sites),
7372 Self::Reversed(basis) => basis.apply_local(state, operator, sites),
7373 }
7374 }
7375
7376 fn apply_local_transitions(
7377 &self,
7378 state: Self::State,
7379 operator: &str,
7380 sites: &[usize],
7381 ) -> Result<LocalTransitions<Self::State>> {
7382 match self {
7383 Self::Spin(basis) => basis.apply_local_transitions(state, operator, sites),
7384 Self::Boson(basis) => basis.apply_local_transitions(state, operator, sites),
7385 Self::SpinlessFermion(basis) => basis.apply_local_transitions(state, operator, sites),
7386 Self::SpinfulFermion(basis) => basis.apply_local_transitions(state, operator, sites),
7387 Self::GeneralSpin(basis) => basis.apply_local_transitions(state, operator, sites),
7388 Self::GeneralBoson(basis) => basis.apply_local_transitions(state, operator, sites),
7389 Self::GeneralSpinlessFermion(basis) => {
7390 basis.apply_local_transitions(state, operator, sites)
7391 }
7392 Self::GeneralSpinfulFermion(basis) => {
7393 basis.apply_local_transitions(state, operator, sites)
7394 }
7395 Self::Tensor(basis) => basis.apply_local_transitions(state, operator, sites),
7396 Self::Photon(basis) => basis.apply_local_transitions(state, operator, sites),
7397 Self::User(basis) => basis.apply_local_transitions(state, operator, sites),
7398 Self::Reversed(basis) => basis.apply_local_transitions(state, operator, sites),
7399 }
7400 }
7401
7402 fn apply_local_unreduced_transitions(
7403 &self,
7404 state: Self::State,
7405 operator: &str,
7406 sites: &[usize],
7407 ) -> Result<LocalTransitions<Self::State>> {
7408 match self {
7409 Self::Spin(basis) => basis.apply_local_unreduced_transitions(state, operator, sites),
7410 Self::Boson(basis) => basis.apply_local_unreduced_transitions(state, operator, sites),
7411 Self::SpinlessFermion(basis) => {
7412 basis.apply_local_unreduced_transitions(state, operator, sites)
7413 }
7414 Self::SpinfulFermion(basis) => {
7415 basis.apply_local_unreduced_transitions(state, operator, sites)
7416 }
7417 Self::GeneralSpin(basis) => {
7418 basis.apply_local_unreduced_transitions(state, operator, sites)
7419 }
7420 Self::GeneralBoson(basis) => {
7421 basis.apply_local_unreduced_transitions(state, operator, sites)
7422 }
7423 Self::GeneralSpinlessFermion(basis) => {
7424 basis.apply_local_unreduced_transitions(state, operator, sites)
7425 }
7426 Self::GeneralSpinfulFermion(basis) => {
7427 basis.apply_local_unreduced_transitions(state, operator, sites)
7428 }
7429 Self::Tensor(basis) => basis.apply_local_unreduced_transitions(state, operator, sites),
7430 Self::Photon(basis) => basis.apply_local_unreduced_transitions(state, operator, sites),
7431 Self::User(basis) => basis.apply_local_unreduced_transitions(state, operator, sites),
7432 Self::Reversed(basis) => {
7433 basis.apply_local_unreduced_transitions(state, operator, sites)
7434 }
7435 }
7436 }
7437
7438 fn visit_local_unreduced_transitions<F>(
7439 &self,
7440 state: Self::State,
7441 operator: &str,
7442 sites: &[usize],
7443 visit: F,
7444 ) -> Result<()>
7445 where
7446 Self: Sized,
7447 F: FnMut(Self::State, Complex64) -> Result<()>,
7448 {
7449 match self {
7450 Self::Spin(basis) => {
7451 basis.visit_local_unreduced_transitions(state, operator, sites, visit)
7452 }
7453 Self::Boson(basis) => {
7454 basis.visit_local_unreduced_transitions(state, operator, sites, visit)
7455 }
7456 Self::SpinlessFermion(basis) => {
7457 basis.visit_local_unreduced_transitions(state, operator, sites, visit)
7458 }
7459 Self::SpinfulFermion(basis) => {
7460 basis.visit_local_unreduced_transitions(state, operator, sites, visit)
7461 }
7462 Self::GeneralSpin(basis) => {
7463 basis.visit_local_unreduced_transitions(state, operator, sites, visit)
7464 }
7465 Self::GeneralBoson(basis) => {
7466 basis.visit_local_unreduced_transitions(state, operator, sites, visit)
7467 }
7468 Self::GeneralSpinlessFermion(basis) => {
7469 basis.visit_local_unreduced_transitions(state, operator, sites, visit)
7470 }
7471 Self::GeneralSpinfulFermion(basis) => {
7472 basis.visit_local_unreduced_transitions(state, operator, sites, visit)
7473 }
7474 Self::Tensor(basis) => {
7475 basis.visit_local_unreduced_transitions(state, operator, sites, visit)
7476 }
7477 Self::Photon(basis) => {
7478 basis.visit_local_unreduced_transitions(state, operator, sites, visit)
7479 }
7480 Self::User(basis) => {
7481 basis.visit_local_unreduced_transitions(state, operator, sites, visit)
7482 }
7483 Self::Reversed(basis) => {
7484 basis.visit_local_unreduced_transitions(state, operator, sites, visit)
7485 }
7486 }
7487 }
7488
7489 fn visit_preparsed_local_unreduced_transitions<F>(
7490 &self,
7491 state: Self::State,
7492 operator: &str,
7493 symbols: &[char],
7494 split: Option<usize>,
7495 sites: &[usize],
7496 visit: F,
7497 ) -> Result<()>
7498 where
7499 Self: Sized,
7500 F: FnMut(Self::State, Complex64) -> Result<()>,
7501 {
7502 match self {
7503 Self::Spin(basis) => basis.visit_preparsed_local_unreduced_transitions(
7504 state, operator, symbols, split, sites, visit,
7505 ),
7506 Self::Boson(basis) => basis.visit_preparsed_local_unreduced_transitions(
7507 state, operator, symbols, split, sites, visit,
7508 ),
7509 Self::SpinlessFermion(basis) => basis.visit_preparsed_local_unreduced_transitions(
7510 state, operator, symbols, split, sites, visit,
7511 ),
7512 Self::SpinfulFermion(basis) => basis.visit_preparsed_local_unreduced_transitions(
7513 state, operator, symbols, split, sites, visit,
7514 ),
7515 Self::GeneralSpin(basis) => basis.visit_preparsed_local_unreduced_transitions(
7516 state, operator, symbols, split, sites, visit,
7517 ),
7518 Self::GeneralBoson(basis) => basis.visit_preparsed_local_unreduced_transitions(
7519 state, operator, symbols, split, sites, visit,
7520 ),
7521 Self::GeneralSpinlessFermion(basis) => basis
7522 .visit_preparsed_local_unreduced_transitions(
7523 state, operator, symbols, split, sites, visit,
7524 ),
7525 Self::GeneralSpinfulFermion(basis) => basis
7526 .visit_preparsed_local_unreduced_transitions(
7527 state, operator, symbols, split, sites, visit,
7528 ),
7529 Self::Tensor(basis) => {
7530 basis.visit_local_unreduced_transitions(state, operator, sites, visit)
7531 }
7532 Self::Photon(basis) => {
7533 basis.visit_local_unreduced_transitions(state, operator, sites, visit)
7534 }
7535 Self::User(basis) => basis.visit_preparsed_local_unreduced_transitions(
7536 state, operator, symbols, split, sites, visit,
7537 ),
7538 Self::Reversed(basis) => basis.visit_preparsed_local_unreduced_transitions(
7539 state, operator, symbols, split, sites, visit,
7540 ),
7541 }
7542 }
7543
7544 fn transition_orbit_size(&self, state: Self::State) -> Result<usize> {
7545 match self {
7546 Self::Spin(basis) => basis.transition_orbit_size(state),
7547 Self::Boson(basis) => basis.transition_orbit_size(state),
7548 Self::SpinlessFermion(basis) => basis.transition_orbit_size(state),
7549 Self::SpinfulFermion(basis) => basis.transition_orbit_size(state),
7550 Self::GeneralSpin(basis) => basis.transition_orbit_size(state),
7551 Self::GeneralBoson(basis) => basis.transition_orbit_size(state),
7552 Self::GeneralSpinlessFermion(basis) => basis.transition_orbit_size(state),
7553 Self::GeneralSpinfulFermion(basis) => basis.transition_orbit_size(state),
7554 Self::Tensor(basis) => basis.transition_orbit_size(state),
7555 Self::Photon(basis) => basis.transition_orbit_size(state),
7556 Self::User(basis) => basis.transition_orbit_size(state),
7557 Self::Reversed(basis) => basis.transition_orbit_size(state),
7558 }
7559 }
7560
7561 fn reduction_image(&self, state: Self::State) -> Result<Option<ReductionImage<Self::State>>> {
7562 match self {
7563 Self::Spin(basis) => basis.reduction_image(state),
7564 Self::Boson(basis) => basis.reduction_image(state),
7565 Self::SpinlessFermion(basis) => basis.reduction_image(state),
7566 Self::SpinfulFermion(basis) => basis.reduction_image(state),
7567 Self::GeneralSpin(basis) => basis.reduction_image(state),
7568 Self::GeneralBoson(basis) => basis.reduction_image(state),
7569 Self::GeneralSpinlessFermion(basis) => basis.reduction_image(state),
7570 Self::GeneralSpinfulFermion(basis) => basis.reduction_image(state),
7571 Self::Tensor(basis) => basis.reduction_image(state),
7572 Self::Photon(basis) => basis.reduction_image(state),
7573 Self::User(basis) => basis.reduction_image(state),
7574 Self::Reversed(basis) => basis.reduction_image(state),
7575 }
7576 }
7577
7578 fn reduce_transition(
7579 &self,
7580 state: Self::State,
7581 source_orbit_size: usize,
7582 ) -> Result<Option<(Self::State, Complex64)>> {
7583 match self {
7584 Self::Spin(basis) => basis.reduce_transition(state, source_orbit_size),
7585 Self::Boson(basis) => basis.reduce_transition(state, source_orbit_size),
7586 Self::SpinlessFermion(basis) => basis.reduce_transition(state, source_orbit_size),
7587 Self::SpinfulFermion(basis) => basis.reduce_transition(state, source_orbit_size),
7588 Self::GeneralSpin(basis) => basis.reduce_transition(state, source_orbit_size),
7589 Self::GeneralBoson(basis) => basis.reduce_transition(state, source_orbit_size),
7590 Self::GeneralSpinlessFermion(basis) => {
7591 basis.reduce_transition(state, source_orbit_size)
7592 }
7593 Self::GeneralSpinfulFermion(basis) => basis.reduce_transition(state, source_orbit_size),
7594 Self::Tensor(basis) => basis.reduce_transition(state, source_orbit_size),
7595 Self::Photon(basis) => basis.reduce_transition(state, source_orbit_size),
7596 Self::User(basis) => basis.reduce_transition(state, source_orbit_size),
7597 Self::Reversed(basis) => basis.reduce_transition(state, source_orbit_size),
7598 }
7599 }
7600
7601 fn index_transition(
7602 &self,
7603 state: Self::State,
7604 source_orbit_size: usize,
7605 ) -> Result<Option<(usize, Complex64)>> {
7606 match self {
7607 Self::Spin(basis) => basis.index_transition(state, source_orbit_size),
7608 Self::Boson(basis) => basis.index_transition(state, source_orbit_size),
7609 Self::SpinlessFermion(basis) => basis.index_transition(state, source_orbit_size),
7610 Self::SpinfulFermion(basis) => basis.index_transition(state, source_orbit_size),
7611 Self::GeneralSpin(basis) => basis.index_transition(state, source_orbit_size),
7612 Self::GeneralBoson(basis) => basis.index_transition(state, source_orbit_size),
7613 Self::GeneralSpinlessFermion(basis) => basis.index_transition(state, source_orbit_size),
7614 Self::GeneralSpinfulFermion(basis) => basis.index_transition(state, source_orbit_size),
7615 Self::Tensor(basis) => basis.index_transition(state, source_orbit_size),
7616 Self::Photon(basis) => basis.index_transition(state, source_orbit_size),
7617 Self::User(basis) => basis.index_transition(state, source_orbit_size),
7618 Self::Reversed(basis) => Ok(basis
7619 .index_transition(state, source_orbit_size)?
7620 .map(|(index, amplitude)| (basis.len() - index - 1, amplitude))),
7621 }
7622 }
7623
7624 fn operator_preserves_particle_sector(&self, operator: &str) -> Result<bool> {
7625 match self {
7626 Self::Spin(basis) => basis.operator_preserves_particle_sector(operator),
7627 Self::Boson(basis) => basis.operator_preserves_particle_sector(operator),
7628 Self::SpinlessFermion(basis) => basis.operator_preserves_particle_sector(operator),
7629 Self::SpinfulFermion(basis) => basis.operator_preserves_particle_sector(operator),
7630 Self::GeneralSpin(basis) => basis.operator_preserves_particle_sector(operator),
7631 Self::GeneralBoson(basis) => basis.operator_preserves_particle_sector(operator),
7632 Self::GeneralSpinlessFermion(basis) => {
7633 basis.operator_preserves_particle_sector(operator)
7634 }
7635 Self::GeneralSpinfulFermion(basis) => {
7636 basis.operator_preserves_particle_sector(operator)
7637 }
7638 Self::Tensor(basis) => basis.operator_preserves_particle_sector(operator),
7639 Self::Photon(basis) => basis.operator_preserves_particle_sector(operator),
7640 Self::User(basis) => basis.operator_preserves_particle_sector(operator),
7641 Self::Reversed(basis) => basis.operator_preserves_particle_sector(operator),
7642 }
7643 }
7644
7645 fn operator_preserves_particle_sector_on_sites(
7646 &self,
7647 operator: &str,
7648 sites: &[usize],
7649 ) -> Result<bool> {
7650 match self {
7651 Self::Spin(basis) => basis.operator_preserves_particle_sector_on_sites(operator, sites),
7652 Self::Boson(basis) => {
7653 basis.operator_preserves_particle_sector_on_sites(operator, sites)
7654 }
7655 Self::SpinlessFermion(basis) => {
7656 basis.operator_preserves_particle_sector_on_sites(operator, sites)
7657 }
7658 Self::SpinfulFermion(basis) => {
7659 basis.operator_preserves_particle_sector_on_sites(operator, sites)
7660 }
7661 Self::GeneralSpin(basis) => {
7662 basis.operator_preserves_particle_sector_on_sites(operator, sites)
7663 }
7664 Self::GeneralBoson(basis) => {
7665 basis.operator_preserves_particle_sector_on_sites(operator, sites)
7666 }
7667 Self::GeneralSpinlessFermion(basis) => {
7668 basis.operator_preserves_particle_sector_on_sites(operator, sites)
7669 }
7670 Self::GeneralSpinfulFermion(basis) => {
7671 basis.operator_preserves_particle_sector_on_sites(operator, sites)
7672 }
7673 Self::Tensor(basis) => {
7674 basis.operator_preserves_particle_sector_on_sites(operator, sites)
7675 }
7676 Self::Photon(basis) => {
7677 basis.operator_preserves_particle_sector_on_sites(operator, sites)
7678 }
7679 Self::User(basis) => basis.operator_preserves_particle_sector_on_sites(operator, sites),
7680 Self::Reversed(basis) => {
7681 basis.operator_preserves_particle_sector_on_sites(operator, sites)
7682 }
7683 }
7684 }
7685}