pairinteraction
A Rydberg Interaction Calculator
Basis.cpp
Go to the documentation of this file.
1// SPDX-FileCopyrightText: 2024 Pairinteraction Developers
2// SPDX-License-Identifier: LGPL-3.0-or-later
3
5
15
16#include <cassert>
17#include <numeric>
18#include <set>
19
20namespace pairinteraction {
21
22template <typename Scalar>
23class BasisAtom;
24
25template <typename Derived>
26void Basis<Derived>::perform_sorter_checks(const std::vector<TransformationType> &labels) const {
27 // Check if the labels are valid sorting labels
28 for (const auto &label : labels) {
29 if (!utils::is_sorting(label)) {
30 throw std::invalid_argument("One of the labels is not a valid sorting label.");
31 }
32 }
33}
34
35template <typename Derived>
37 const std::set<TransformationType> &unique_labels) const {
38 // Check if the states are sorted by the requested labels
39 std::set<TransformationType> unique_labels_present;
40 for (const auto &label : get_transformation().transformation_type) {
41 if (!utils::is_sorting(label) || unique_labels_present.size() >= unique_labels.size()) {
42 break;
43 }
44 unique_labels_present.insert(label);
45 }
46 if (unique_labels != unique_labels_present) {
47 throw std::invalid_argument("The states are not sorted by the requested labels.");
48 }
49
50 // Throw a meaningful error if getting the blocks by energy is requested as this might be a
51 // common mistake
52 if (unique_labels.count(TransformationType::SORT_BY_ENERGY) > 0) {
53 throw std::invalid_argument("States do not store the energy and thus no energy blocks can "
54 "be obtained. Use an energy operator instead.");
55 }
58template <typename Derived>
60 : kets(std::move(kets)), coefficients{{static_cast<Eigen::Index>(this->kets.size()),
61 static_cast<Eigen::Index>(this->kets.size())},
63 if (this->kets.empty()) {
64 throw std::invalid_argument("The basis must contain at least one element.");
65 }
66 state_index_to_quantum_number_f.reserve(this->kets.size());
67 state_index_to_quantum_number_m.reserve(this->kets.size());
68 state_index_to_parity.reserve(this->kets.size());
69 ket_to_ket_index.reserve(this->kets.size());
70 size_t index = 0;
71 for (const auto &ket : this->kets) {
72 state_index_to_quantum_number_f.push_back(ket->get_quantum_number_f());
73 state_index_to_quantum_number_m.push_back(ket->get_quantum_number_m());
74 state_index_to_parity.push_back(ket->get_parity());
75 ket_to_ket_index[ket] = index++;
76 if (ket->get_quantum_number_f() == std::numeric_limits<real_t>::max()) {
77 _has_quantum_number_f = false;
78 }
79 if (ket->get_quantum_number_m() == std::numeric_limits<real_t>::max()) {
80 _has_quantum_number_m = false;
81 }
82 if (ket->get_parity() == Parity::UNKNOWN) {
83 _has_parity = false;
84 }
85 }
86 state_index_to_ket_index.resize(this->kets.size());
87 std::iota(state_index_to_ket_index.begin(), state_index_to_ket_index.end(), 0);
88 ket_index_to_state_index.resize(this->kets.size());
89 std::iota(ket_index_to_state_index.begin(), ket_index_to_state_index.end(), 0);
90 coefficients.matrix.setIdentity();
93template <typename Derived>
95 return _has_quantum_number_f;
96}
97
98template <typename Derived>
100 return _has_quantum_number_m;
101}
103template <typename Derived>
105 return _has_parity;
107
108template <typename Derived>
109const Derived &Basis<Derived>::derived() const {
110 return static_cast<const Derived &>(*this);
111}
113template <typename Derived>
115 return kets;
118template <typename Derived>
119const Eigen::SparseMatrix<typename Basis<Derived>::scalar_t, Eigen::RowMajor> &
121 return coefficients.matrix;
122}
123
124template <typename Derived>
125Eigen::SparseMatrix<typename Basis<Derived>::scalar_t, Eigen::RowMajor> &
127 return coefficients.matrix;
128}
129
130template <typename Derived>
132 const Eigen::SparseMatrix<scalar_t, Eigen::RowMajor> &values) {
133 if (values.rows() != coefficients.matrix.rows()) {
134 throw std::invalid_argument("Incompatible number of rows.");
135 }
136 if (values.cols() != coefficients.matrix.cols()) {
137 throw std::invalid_argument("Incompatible number of columns.");
138 }
139
140 coefficients.matrix = values;
141
142 std::fill(ket_index_to_state_index.begin(), ket_index_to_state_index.end(),
143 std::numeric_limits<int>::max());
144 std::fill(state_index_to_ket_index.begin(), state_index_to_ket_index.end(),
145 std::numeric_limits<int>::max());
146 std::fill(state_index_to_quantum_number_f.begin(), state_index_to_quantum_number_f.end(),
147 std::numeric_limits<real_t>::max());
148 std::fill(state_index_to_quantum_number_m.begin(), state_index_to_quantum_number_m.end(),
149 std::numeric_limits<real_t>::max());
150 std::fill(state_index_to_parity.begin(), state_index_to_parity.end(), Parity::UNKNOWN);
151 _has_quantum_number_f = false;
152 _has_quantum_number_m = false;
153 _has_parity = false;
154}
155
156template <typename Derived>
157int Basis<Derived>::get_ket_index_from_ket(std::shared_ptr<const ket_t> ket) const {
158 if (ket_to_ket_index.count(ket) == 0) {
159 return -1;
160 }
161 return ket_to_ket_index.at(ket);
162}
163
164template <typename Derived>
166Basis<Derived>::get_amplitudes(std::shared_ptr<const ket_t> ket) const {
167 int ket_index = get_ket_index_from_ket(ket);
168 if (ket_index < 0) {
169 throw std::invalid_argument("The ket does not belong to the basis.");
170 }
171 // The following line is a more efficient alternative to
172 // "get_amplitudes(get_canonical_state_from_ket(ket)).transpose()"
173 return coefficients.matrix.row(ket_index);
174}
175
176template <typename Derived>
177Eigen::SparseMatrix<typename Basis<Derived>::scalar_t, Eigen::RowMajor>
178Basis<Derived>::get_amplitudes(std::shared_ptr<const Derived> other) const {
179 return other->coefficients.matrix.adjoint() * coefficients.matrix;
180}
181
182template <typename Derived>
184Basis<Derived>::get_overlaps(std::shared_ptr<const ket_t> ket) const {
185 return get_amplitudes(ket).cwiseAbs2();
186}
187
188template <typename Derived>
189Eigen::SparseMatrix<typename Basis<Derived>::real_t, Eigen::RowMajor>
190Basis<Derived>::get_overlaps(std::shared_ptr<const Derived> other) const {
191 return get_amplitudes(other).cwiseAbs2();
192}
193
194template <typename Derived>
196 real_t quantum_number_f = state_index_to_quantum_number_f.at(state_index);
197 if (quantum_number_f == std::numeric_limits<real_t>::max()) {
198 throw std::invalid_argument("The state does not have a well-defined quantum number f.");
199 }
200 return quantum_number_f;
201}
202
203template <typename Derived>
205 real_t quantum_number_m = state_index_to_quantum_number_m.at(state_index);
206 if (quantum_number_m == std::numeric_limits<real_t>::max()) {
207 throw std::invalid_argument("The state does not have a well-defined quantum number m.");
208 }
209 return quantum_number_m;
210}
211
212template <typename Derived>
213Parity Basis<Derived>::get_parity(size_t state_index) const {
214 Parity parity = state_index_to_parity.at(state_index);
215 if (parity == Parity::UNKNOWN) {
216 throw std::invalid_argument("The state does not have a well-defined parity.");
217 }
218 return parity;
219}
220
221template <typename Derived>
222std::shared_ptr<const typename Basis<Derived>::ket_t>
223Basis<Derived>::get_corresponding_ket(size_t state_index) const {
224 size_t ket_index = state_index_to_ket_index.at(state_index);
225 if (ket_index == std::numeric_limits<int>::max()) {
226 throw std::invalid_argument("The state does not belong to a ket in a well-defined way.");
227 }
228 return kets[ket_index];
229}
230
231template <typename Derived>
232std::shared_ptr<const typename Basis<Derived>::ket_t>
233Basis<Derived>::get_corresponding_ket(std::shared_ptr<const Derived> /*state*/) const {
234 throw std::runtime_error("Not implemented yet.");
235}
236
237template <typename Derived>
238std::shared_ptr<const Derived> Basis<Derived>::get_state(size_t state_index) const {
239 // Create a copy of the current object
240 auto restricted = std::make_shared<Derived>(derived());
241
242 // Restrict the copy to the state with the largest overlap
243 restricted->coefficients.matrix = restricted->coefficients.matrix.col(state_index);
244
245 std::fill(restricted->ket_index_to_state_index.begin(),
246 restricted->ket_index_to_state_index.end(), std::numeric_limits<int>::max());
247 restricted->ket_index_to_state_index[state_index_to_ket_index[state_index]] = 0;
248
249 restricted->state_index_to_quantum_number_f = {state_index_to_quantum_number_f[state_index]};
250 restricted->state_index_to_quantum_number_m = {state_index_to_quantum_number_m[state_index]};
251 restricted->state_index_to_parity = {state_index_to_parity[state_index]};
252 restricted->state_index_to_ket_index = {state_index_to_ket_index[state_index]};
253
254 restricted->_has_quantum_number_f =
255 restricted->state_index_to_quantum_number_f[0] != std::numeric_limits<real_t>::max();
256 restricted->_has_quantum_number_m =
257 restricted->state_index_to_quantum_number_m[0] != std::numeric_limits<real_t>::max();
258 restricted->_has_parity = restricted->state_index_to_parity[0] != Parity::UNKNOWN;
259
260 return restricted;
261}
262
263template <typename Derived>
264std::shared_ptr<const typename Basis<Derived>::ket_t>
265Basis<Derived>::get_ket(size_t ket_index) const {
266 return kets[ket_index];
267}
268
269template <typename Derived>
270std::shared_ptr<const Derived> Basis<Derived>::get_corresponding_state(size_t ket_index) const {
271 size_t state_index = ket_index_to_state_index.at(ket_index);
272 if (state_index == std::numeric_limits<int>::max()) {
273 throw std::runtime_error("The ket does not belong to a state in a well-defined way.");
274 }
275 return get_state(state_index);
276}
277
278template <typename Derived>
279std::shared_ptr<const Derived>
280Basis<Derived>::get_corresponding_state(std::shared_ptr<const ket_t> ket) const {
281 int ket_index = get_ket_index_from_ket(ket);
282 if (ket_index < 0) {
283 throw std::invalid_argument("The ket does not belong to the basis.");
284 }
285 return get_corresponding_state(ket_index);
286}
287
288template <typename Derived>
289size_t Basis<Derived>::get_corresponding_state_index(size_t ket_index) const {
290 int state_index = ket_index_to_state_index.at(ket_index);
291 if (state_index == std::numeric_limits<int>::max()) {
292 throw std::runtime_error("The ket does not belong to a state in a well-defined way.");
293 }
294 return state_index;
295}
296
297template <typename Derived>
298size_t Basis<Derived>::get_corresponding_state_index(std::shared_ptr<const ket_t> ket) const {
299 int ket_index = get_ket_index_from_ket(ket);
300 if (ket_index < 0) {
301 throw std::invalid_argument("The ket does not belong to the basis.");
302 }
303 return get_corresponding_state_index(ket_index);
304}
305
306template <typename Derived>
307size_t Basis<Derived>::get_corresponding_ket_index(size_t state_index) const {
308 int ket_index = state_index_to_ket_index.at(state_index);
309 if (ket_index == std::numeric_limits<int>::max()) {
310 throw std::runtime_error("The state does not belong to a ket in a well-defined way.");
311 }
312 return ket_index;
313}
314
315template <typename Derived>
316size_t Basis<Derived>::get_corresponding_ket_index(std::shared_ptr<const Derived> /*state*/) const {
317 throw std::runtime_error("Not implemented yet.");
318}
319
320template <typename Derived>
321std::shared_ptr<const Derived>
323 // Create a copy of the current object
324 auto created = std::make_shared<Derived>(derived());
325
326 // Fill the copy with the state corresponding to the ket index
327 created->coefficients.matrix =
328 Eigen::SparseMatrix<scalar_t, Eigen::RowMajor>(coefficients.matrix.rows(), 1);
329 created->coefficients.matrix.coeffRef(ket_index, 0) = 1;
330 created->coefficients.matrix.makeCompressed();
331
332 std::fill(created->ket_index_to_state_index.begin(), created->ket_index_to_state_index.end(),
333 std::numeric_limits<int>::max());
334 created->ket_index_to_state_index[ket_index] = 0;
335
336 created->state_index_to_quantum_number_f = {kets[ket_index]->get_quantum_number_f()};
337 created->state_index_to_quantum_number_m = {kets[ket_index]->get_quantum_number_m()};
338 created->state_index_to_parity = {kets[ket_index]->get_parity()};
339 created->state_index_to_ket_index = {ket_index};
340
341 created->_has_quantum_number_f =
342 created->state_index_to_quantum_number_f[0] != std::numeric_limits<real_t>::max();
343 created->_has_quantum_number_m =
344 created->state_index_to_quantum_number_m[0] != std::numeric_limits<real_t>::max();
345 created->_has_parity = created->state_index_to_parity[0] != Parity::UNKNOWN;
346
347 return created;
348}
349
350template <typename Derived>
351std::shared_ptr<const Derived>
352Basis<Derived>::get_canonical_state_from_ket(std::shared_ptr<const ket_t> ket) const {
353 int ket_index = get_ket_index_from_ket(ket);
354 if (ket_index < 0) {
355 throw std::invalid_argument("The ket does not belong to the basis.");
356 }
357 return get_canonical_state_from_ket(ket_index);
358}
359
360template <typename Derived>
362 return kets.begin();
363}
364
365template <typename Derived>
367 return kets.end();
368}
369
370template <typename Derived>
371Basis<Derived>::Iterator::Iterator(typename ketvec_t::const_iterator it) : it{std::move(it)} {}
372
373template <typename Derived>
375 return other.it != it;
376}
377
378template <typename Derived>
379std::shared_ptr<const typename Basis<Derived>::ket_t> Basis<Derived>::Iterator::operator*() const {
380 return *it;
381}
382
383template <typename Derived>
385 ++it;
386 return *this;
387}
388
389template <typename Derived>
391 return coefficients.matrix.cols();
392}
393
394template <typename Derived>
396 return coefficients.matrix.rows();
397}
398
399template <typename Derived>
402 return coefficients;
403}
404
405template <typename Derived>
408 Transformation<scalar_t> transformation{{static_cast<Eigen::Index>(coefficients.matrix.rows()),
409 static_cast<Eigen::Index>(coefficients.matrix.rows())},
411
412 std::vector<Eigen::Triplet<scalar_t>> entries;
413
414 for (size_t idx_initial = 0; idx_initial < kets.size(); ++idx_initial) {
415 real_t f = kets[idx_initial]->get_quantum_number_f();
416 real_t m_initial = kets[idx_initial]->get_quantum_number_m();
417
418 assert(2 * f == std::floor(2 * f) && f >= 0);
419 assert(2 * m_initial == std::floor(2 * m_initial) && m_initial >= -f && m_initial <= f);
420
421 for (real_t m_final = -f; m_final <= f; // NOSONAR m_final is precisely representable
422 ++m_final) {
423 auto val = wigner::wigner_uppercase_d_matrix<scalar_t>(f, m_initial, m_final, alpha,
424 beta, gamma);
425 size_t idx_final = get_ket_index_from_ket(
426 kets[idx_initial]->get_ket_for_different_quantum_number_m(m_final));
427 entries.emplace_back(idx_final, idx_initial, val);
428 }
429 }
430
431 transformation.matrix.setFromTriplets(entries.begin(), entries.end());
432 transformation.matrix.makeCompressed();
433
434 return transformation;
435}
436
437template <typename Derived>
438Sorting Basis<Derived>::get_sorter(const std::vector<TransformationType> &labels) const {
439 perform_sorter_checks(labels);
440
441 // Throw a meaningful error if sorting by energy is requested as this might be a common mistake
442 if (std::find(labels.begin(), labels.end(), TransformationType::SORT_BY_ENERGY) !=
443 labels.end()) {
444 throw std::invalid_argument("States do not store the energy and thus can not be sorted by "
445 "the energy. Use an energy operator instead.");
446 }
447
448 // Initialize transformation
449 Sorting transformation;
450 transformation.matrix.resize(coefficients.matrix.cols());
451 transformation.matrix.setIdentity();
452
453 // Get the sorter
454 get_sorter_without_checks(labels, transformation);
455
456 // Check if all labels have been used for sorting
457 if (labels != transformation.transformation_type) {
458 throw std::invalid_argument("The states could not be sorted by all the requested labels.");
459 }
460
461 return transformation;
462}
463
464template <typename Derived>
465std::vector<IndicesOfBlock>
466Basis<Derived>::get_indices_of_blocks(const std::vector<TransformationType> &labels) const {
467 perform_sorter_checks(labels);
468
469 std::set<TransformationType> unique_labels(labels.begin(), labels.end());
470 perform_blocks_checks(unique_labels);
471
472 // Get the blocks
473 IndicesOfBlocksCreator blocks_creator({0, static_cast<size_t>(coefficients.matrix.cols())});
474 get_indices_of_blocks_without_checks(unique_labels, blocks_creator);
475
476 return blocks_creator.create();
477}
478
479template <typename Derived>
480void Basis<Derived>::get_sorter_without_checks(const std::vector<TransformationType> &labels,
481 Sorting &transformation) const {
482 constexpr real_t numerical_precision = 100 * std::numeric_limits<real_t>::epsilon();
483
484 int *perm_begin = transformation.matrix.indices().data();
485 int *perm_end = perm_begin + coefficients.matrix.cols();
486 const int *perm_back = perm_end - 1;
487
488 // Sort the vector based on the requested labels
489 std::stable_sort(perm_begin, perm_end, [&](int a, int b) {
490 for (const auto &label : labels) {
491 switch (label) {
493 if (state_index_to_parity[a] != state_index_to_parity[b]) {
494 return state_index_to_parity[a] < state_index_to_parity[b];
495 }
496 break;
498 if (std::abs(state_index_to_quantum_number_m[a] -
499 state_index_to_quantum_number_m[b]) > numerical_precision) {
500 return state_index_to_quantum_number_m[a] < state_index_to_quantum_number_m[b];
501 }
502 break;
504 if (std::abs(state_index_to_quantum_number_f[a] -
505 state_index_to_quantum_number_f[b]) > numerical_precision) {
506 return state_index_to_quantum_number_f[a] < state_index_to_quantum_number_f[b];
507 }
508 break;
510 if (state_index_to_ket_index[a] != state_index_to_ket_index[b]) {
511 return state_index_to_ket_index[a] < state_index_to_ket_index[b];
512 }
513 break;
514 default:
515 std::abort(); // Can't happen because of previous checks
516 }
517 }
518 return false; // Elements are equal
519 });
520
521 // Check for invalid values and add transformation types
522 for (const auto &label : labels) {
523 switch (label) {
525 if (state_index_to_parity[*perm_back] == Parity::UNKNOWN) {
526 throw std::invalid_argument(
527 "States cannot be labeled and thus not sorted by the parity.");
528 }
530 break;
532 if (state_index_to_quantum_number_m[*perm_back] == std::numeric_limits<real_t>::max()) {
533 throw std::invalid_argument(
534 "States cannot be labeled and thus not sorted by the quantum number m.");
535 }
536 transformation.transformation_type.push_back(
538 break;
540 if (state_index_to_quantum_number_f[*perm_back] == std::numeric_limits<real_t>::max()) {
541 throw std::invalid_argument(
542 "States cannot be labeled and thus not sorted by the quantum number f.");
543 }
544 transformation.transformation_type.push_back(
546 break;
548 if (state_index_to_ket_index[*perm_back] == std::numeric_limits<int>::max()) {
549 throw std::invalid_argument(
550 "States cannot be labeled and thus not sorted by kets.");
551 }
553 break;
554 default:
555 std::abort(); // Can't happen because of previous checks
556 }
557 }
558}
559
560template <typename Derived>
562 const std::set<TransformationType> &unique_labels,
563 IndicesOfBlocksCreator &blocks_creator) const {
564 constexpr real_t numerical_precision = 100 * std::numeric_limits<real_t>::epsilon();
565
566 auto last_quantum_number_f = state_index_to_quantum_number_f[0];
567 auto last_quantum_number_m = state_index_to_quantum_number_m[0];
568 auto last_parity = state_index_to_parity[0];
569 auto last_ket = state_index_to_ket_index[0];
570
571 for (int i = 0; i < coefficients.matrix.cols(); ++i) {
572 for (auto label : unique_labels) {
574 if (std::abs(state_index_to_quantum_number_f[i] - last_quantum_number_f) >
575 numerical_precision) {
576 blocks_creator.add(i);
577 break;
578 }
580 if (std::abs(state_index_to_quantum_number_m[i] - last_quantum_number_m) >
581 numerical_precision) {
582 blocks_creator.add(i);
583 break;
584 }
585 } else if (label == TransformationType::SORT_BY_PARITY) {
586 if (state_index_to_parity[i] != last_parity) {
587 blocks_creator.add(i);
588 break;
589 }
590 } else if (label == TransformationType::SORT_BY_KET) {
591 if (state_index_to_ket_index[i] != last_ket) {
592 blocks_creator.add(i);
593 break;
594 }
595 }
596 }
597 last_quantum_number_f = state_index_to_quantum_number_f[i];
598 last_quantum_number_m = state_index_to_quantum_number_m[i];
599 last_parity = state_index_to_parity[i];
600 last_ket = state_index_to_ket_index[i];
601 }
602}
603
604template <typename Derived>
605std::shared_ptr<const Derived> Basis<Derived>::transformed(const Sorting &transformation) const {
606 // Create a copy of the current object
607 auto transformed = std::make_shared<Derived>(derived());
608
609 if (coefficients.matrix.cols() == 0) {
610 return transformed;
611 }
612
613 // Apply the transformation
614 transformed->coefficients.matrix = coefficients.matrix * transformation.matrix;
615 transformed->coefficients.transformation_type = transformation.transformation_type;
616
617 transformed->state_index_to_quantum_number_f.resize(transformation.matrix.size());
618 transformed->state_index_to_quantum_number_m.resize(transformation.matrix.size());
619 transformed->state_index_to_parity.resize(transformation.matrix.size());
620 transformed->state_index_to_ket_index.resize(transformation.matrix.size());
621
622 for (int i = 0; i < transformation.matrix.size(); ++i) {
623 transformed->state_index_to_quantum_number_f[i] =
624 state_index_to_quantum_number_f[transformation.matrix.indices()[i]];
625 transformed->state_index_to_quantum_number_m[i] =
626 state_index_to_quantum_number_m[transformation.matrix.indices()[i]];
627 transformed->state_index_to_parity[i] =
628 state_index_to_parity[transformation.matrix.indices()[i]];
629 transformed->state_index_to_ket_index[i] =
630 state_index_to_ket_index[transformation.matrix.indices()[i]];
631 transformed->ket_index_to_state_index
632 [state_index_to_ket_index[transformation.matrix.indices()[i]]] = i;
633 }
634
635 return transformed;
636}
637
638template <typename Derived>
639std::shared_ptr<const Derived>
641 // TODO why is "numerical_precision = 100 * std::sqrt(coefficients.matrix.rows()) *
642 // std::numeric_limits<real_t>::epsilon()" too small for figuring out whether m is conserved?
643 real_t numerical_precision = 0.001;
644
645 // If the transformation is a rotation, it should be a rotation and nothing else
646 bool is_rotation = false;
647 for (auto t : transformation.transformation_type) {
649 is_rotation = true;
650 break;
651 }
652 }
653 if (is_rotation && transformation.transformation_type.size() != 1) {
654 throw std::invalid_argument("A rotation can not be combined with other transformations.");
655 }
656
657 // To apply a rotation, the object must only be sorted but other transformations are not allowed
658 if (is_rotation) {
659 for (auto t : coefficients.transformation_type) {
660 if (!utils::is_sorting(t)) {
661 throw std::runtime_error(
662 "If the object was transformed by a different transformation "
663 "than sorting, it can not be rotated.");
664 }
665 }
666 }
667
668 // Create a copy of the current object
669 auto transformed = std::make_shared<Derived>(derived());
670
671 if (coefficients.matrix.cols() == 0) {
672 return transformed;
673 }
674
675 // Apply the transformation
676 // If a quantum number turns out to be conserved by the transformation, it will be
677 // rounded to the nearest half integer to avoid loss of numerical_precision.
678 transformed->coefficients.matrix = coefficients.matrix * transformation.matrix;
679 transformed->coefficients.transformation_type = transformation.transformation_type;
680
681 Eigen::SparseMatrix<real_t> probs = transformation.matrix.cwiseAbs2().transpose();
682
683 {
684 auto map = Eigen::Map<const Eigen::VectorX<real_t>>(state_index_to_quantum_number_f.data(),
685 state_index_to_quantum_number_f.size());
686 Eigen::VectorX<real_t> val = probs * map;
687 Eigen::VectorX<real_t> sq = probs * map.cwiseAbs2();
688 Eigen::VectorX<real_t> diff = (val.cwiseAbs2() - sq).cwiseAbs();
689 transformed->state_index_to_quantum_number_f.resize(probs.rows());
690
691 for (size_t i = 0; i < transformed->state_index_to_quantum_number_f.size(); ++i) {
692 if (diff[i] < numerical_precision) {
693 transformed->state_index_to_quantum_number_f[i] = std::round(val[i] * 2) / 2;
694 } else {
695 transformed->state_index_to_quantum_number_f[i] =
696 std::numeric_limits<real_t>::max();
697 transformed->_has_quantum_number_f = false;
698 }
699 }
700 }
701
702 {
703 auto map = Eigen::Map<const Eigen::VectorX<real_t>>(state_index_to_quantum_number_m.data(),
704 state_index_to_quantum_number_m.size());
705 Eigen::VectorX<real_t> val = probs * map;
706 Eigen::VectorX<real_t> sq = probs * map.cwiseAbs2();
707 Eigen::VectorX<real_t> diff = (val.cwiseAbs2() - sq).cwiseAbs();
708 transformed->state_index_to_quantum_number_m.resize(probs.rows());
709
710 for (size_t i = 0; i < transformed->state_index_to_quantum_number_m.size(); ++i) {
711 if (diff[i] < numerical_precision) {
712 transformed->state_index_to_quantum_number_m[i] = std::round(val[i] * 2) / 2;
713 } else {
714 transformed->state_index_to_quantum_number_m[i] =
715 std::numeric_limits<real_t>::max();
716 transformed->_has_quantum_number_m = false;
717 }
718 }
719 }
720
721 {
722 using utype = std::underlying_type<Parity>::type;
723 Eigen::VectorX<real_t> map(state_index_to_parity.size());
724 for (size_t i = 0; i < state_index_to_parity.size(); ++i) {
725 map[i] = static_cast<utype>(state_index_to_parity[i]);
726 }
727 Eigen::VectorX<real_t> val = probs * map;
728 Eigen::VectorX<real_t> sq = probs * map.cwiseAbs2();
729 Eigen::VectorX<real_t> diff = (val.cwiseAbs2() - sq).cwiseAbs();
730 transformed->state_index_to_parity.resize(probs.rows());
731
732 for (size_t i = 0; i < transformed->state_index_to_parity.size(); ++i) {
733 if (diff[i] < numerical_precision) {
734 transformed->state_index_to_parity[i] = static_cast<Parity>(std::lround(val[i]));
735 } else {
736 transformed->state_index_to_parity[i] = Parity::UNKNOWN;
737 transformed->_has_parity = false;
738 }
739 }
740 }
741
742 {
743 // In the following, we obtain a bijective map between state index and ket index.
744
745 // Find the maximum value in each row and column
746 std::vector<real_t> max_in_row(transformed->coefficients.matrix.rows(), 0);
747 std::vector<real_t> max_in_col(transformed->coefficients.matrix.cols(), 0);
748 for (int row = 0; row < transformed->coefficients.matrix.outerSize(); ++row) {
749 for (typename Eigen::SparseMatrix<scalar_t, Eigen::RowMajor>::InnerIterator it(
750 transformed->coefficients.matrix, row);
751 it; ++it) {
752 real_t val = std::pow(std::abs(it.value()), 2);
753 max_in_row[row] = std::max(max_in_row[row], val);
754 max_in_col[it.col()] = std::max(max_in_col[it.col()], val);
755 }
756 }
757
758 // Use the maximum values to define a cost for a sub-optimal mapping
759 std::vector<real_t> costs;
760 std::vector<std::pair<int, int>> mappings;
761 costs.reserve(transformed->coefficients.matrix.nonZeros());
762 mappings.reserve(transformed->coefficients.matrix.nonZeros());
763 for (int row = 0; row < transformed->coefficients.matrix.outerSize(); ++row) {
764 for (typename Eigen::SparseMatrix<scalar_t, Eigen::RowMajor>::InnerIterator it(
765 transformed->coefficients.matrix, row);
766 it; ++it) {
767 real_t val = std::pow(std::abs(it.value()), 2);
768 real_t cost = max_in_row[row] + max_in_col[it.col()] - 2 * val;
769 costs.push_back(cost);
770 mappings.push_back({row, it.col()});
771 }
772 }
773
774 // Obtain from the costs in which order the mappings should be considered
775 std::vector<size_t> order(costs.size());
776 std::iota(order.begin(), order.end(), 0);
777 std::sort(order.begin(), order.end(),
778 [&](size_t a, size_t b) { return costs[a] < costs[b]; });
779
780 // Fill ket_index_to_state_index with invalid values as there can be more kets than states
781 std::fill(transformed->ket_index_to_state_index.begin(),
782 transformed->ket_index_to_state_index.end(), std::numeric_limits<int>::max());
783
784 // Generate the bijective map
785 std::vector<bool> row_used(transformed->coefficients.matrix.rows(), false);
786 std::vector<bool> col_used(transformed->coefficients.matrix.cols(), false);
787 int num_used = 0;
788 for (size_t idx : order) {
789 int row = mappings[idx].first; // corresponds to the ket index
790 int col = mappings[idx].second; // corresponds to the state index
791 if (!row_used[row] && !col_used[col]) {
792 row_used[row] = true;
793 col_used[col] = true;
794 num_used++;
795 transformed->state_index_to_ket_index[col] = row;
796 transformed->ket_index_to_state_index[row] = col;
797 }
798 if (num_used == transformed->coefficients.matrix.cols()) {
799 break;
800 }
801 }
802 assert(num_used == transformed->coefficients.matrix.cols());
803 }
804
805 return transformed;
806}
807
808template <typename Derived>
809size_t Basis<Derived>::hash::operator()(const std::shared_ptr<const ket_t> &k) const {
810 return typename ket_t::hash()(*k);
811}
812
813template <typename Derived>
814bool Basis<Derived>::equal_to::operator()(const std::shared_ptr<const ket_t> &lhs,
815 const std::shared_ptr<const ket_t> &rhs) const {
816 return *lhs == *rhs;
817}
818
819// Explicit instantiations
820template class Basis<BasisAtom<double>>;
821template class Basis<BasisAtom<std::complex<double>>>;
822template class Basis<BasisPair<double>>;
823template class Basis<BasisPair<std::complex<double>>>;
824} // namespace pairinteraction
std::shared_ptr< const ket_t > operator*() const
Definition: Basis.cpp:379
bool operator!=(const Iterator &other) const
Definition: Basis.cpp:374
Base class for a basis.
Definition: Basis.hpp:41
const Transformation< scalar_t > & get_transformation() const override
Definition: Basis.cpp:401
Iterator begin() const
Definition: Basis.cpp:361
void get_indices_of_blocks_without_checks(const std::set< TransformationType > &unique_labels, IndicesOfBlocksCreator &blocks) const
Definition: Basis.cpp:561
void get_sorter_without_checks(const std::vector< TransformationType > &labels, Sorting &transformation) const
Definition: Basis.cpp:480
int get_ket_index_from_ket(std::shared_ptr< const ket_t > ket) const
Definition: Basis.cpp:157
typename traits::CrtpTraits< Derived >::ketvec_t ketvec_t
Definition: Basis.hpp:46
Iterator end() const
Definition: Basis.cpp:366
size_t get_number_of_states() const
Definition: Basis.cpp:390
Transformation< scalar_t > get_rotator(real_t alpha, real_t beta, real_t gamma) const override
Definition: Basis.cpp:407
void perform_sorter_checks(const std::vector< TransformationType > &labels) const
Definition: Basis.cpp:26
void perform_blocks_checks(const std::set< TransformationType > &unique_labels) const
Definition: Basis.cpp:36
std::vector< IndicesOfBlock > get_indices_of_blocks(const std::vector< TransformationType > &labels) const override
Definition: Basis.cpp:466
typename traits::CrtpTraits< Derived >::real_t real_t
Definition: Basis.hpp:44
std::shared_ptr< const Derived > transformed(const Transformation< scalar_t > &transformation) const
Definition: Basis.cpp:640
Sorting get_sorter(const std::vector< TransformationType > &labels) const override
Definition: Basis.cpp:438
size_t get_number_of_kets() const
Definition: Basis.cpp:395
Matrix< Type, Dynamic, 1 > VectorX
bool is_sorting(TransformationType label)
Eigen::PermutationMatrix< Eigen::Dynamic, Eigen::Dynamic > matrix
std::vector< TransformationType > transformation_type
Eigen::SparseMatrix< Scalar, Eigen::RowMajor > matrix
std::vector< TransformationType > transformation_type