LCOV - code coverage report
Current view: top level - pairinteraction - HamiltonianTwo.cpp (source / functions) Hit Total Coverage
Test: coverage.info Lines: 281 330 85.2 %
Date: 2024-04-29 00:41:50 Functions: 2 6 33.3 %

          Line data    Source code
       1             : /*
       2             :  * Copyright (c) 2016 Sebastian Weber, Henri Menke. All rights reserved.
       3             :  *
       4             :  * This file is part of the pairinteraction library.
       5             :  *
       6             :  * The pairinteraction library is free software: you can redistribute it and/or modify
       7             :  * it under the terms of the GNU Lesser General Public License as published by
       8             :  * the Free Software Foundation, either version 3 of the License, or
       9             :  * (at your option) any later version.
      10             :  *
      11             :  * The pairinteraction library is distributed in the hope that it will be useful,
      12             :  * but WITHOUT ANY WARRANTY; without even the implied warranty of
      13             :  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      14             :  * GNU Lesser General Public License for more details.
      15             :  *
      16             :  * You should have received a copy of the GNU Lesser General Public License
      17             :  * along with the pairinteraction library. If not, see <http://www.gnu.org/licenses/>.
      18             :  */
      19             : 
      20             : #include "HamiltonianTwo.hpp"
      21             : 
      22             : #include "Constants.hpp"
      23             : 
      24             : #include <fmt/format.h>
      25             : 
      26             : #include <stdexcept>
      27             : #include <utility>
      28             : 
      29             : template <typename Scalar>
      30           2 : HamiltonianTwo<Scalar>::HamiltonianTwo(
      31             :     const Configuration &config, fs::path &path_cache,
      32             :     const std::shared_ptr<HamiltonianOne<Scalar>> &hamiltonian_one)
      33             :     : hamiltonian_one1(hamiltonian_one), hamiltonian_one2(hamiltonian_one),
      34           2 :       path_cache(path_cache) { // TODO
      35             : 
      36           2 :     samebasis = true;
      37             : 
      38           2 :     calculate(config);
      39           2 : }
      40             : 
      41             : template <typename Scalar>
      42           0 : HamiltonianTwo<Scalar>::HamiltonianTwo(const Configuration &config, fs::path &path_cache,
      43             :                                        std::shared_ptr<HamiltonianOne<Scalar>> hamiltonian_one1,
      44             :                                        std::shared_ptr<HamiltonianOne<Scalar>> hamiltonian_one2)
      45           0 :     : hamiltonian_one1(std::move(hamiltonian_one1)), hamiltonian_one2(std::move(hamiltonian_one2)),
      46           0 :       path_cache(path_cache) {
      47             : 
      48           0 :     samebasis = false;
      49             : 
      50           0 :     calculate(config);
      51           0 : }
      52             : 
      53             : template <typename Scalar>
      54           2 : void HamiltonianTwo<Scalar>::calculate(const Configuration &conf_tot) {
      55           4 :     fs::path path_cache_mat;
      56             :     if (utils::is_complex<Scalar>::value) {
      57           0 :         path_cache_mat = path_cache / "cache_matrix_complex";
      58             :     } else {
      59           2 :         path_cache_mat = path_cache / "cache_matrix_real";
      60             :     }
      61           2 :     if (!fs::exists(path_cache_mat)) {
      62           0 :         fs::create_directory(path_cache_mat);
      63             :     }
      64             : 
      65           2 :     double tol = 1e-32;
      66             : 
      67           2 :     if (hamiltonian_one1->size() != hamiltonian_one2->size()) {
      68           0 :         throw std::runtime_error(
      69             :             "The number of single atom Hamiltonians must be the same for both atoms.");
      70             :     }
      71             : 
      72           2 :     size_t nSteps_one = hamiltonian_one1->size();
      73             : 
      74             :     // --- generate configuration ---
      75           4 :     std::vector<Configuration> conf_mat;
      76           2 :     conf_mat.reserve(nSteps_one);
      77             : 
      78             :     // new, pair hamiltonian specific configuration
      79             : 
      80           2 :     if (samebasis) {
      81           2 :         this->basis = std::make_shared<BasisnamesTwo>(hamiltonian_one1->names()); // TODO remove
      82             :     } else {
      83           0 :         this->basis = std::make_shared<BasisnamesTwo>(hamiltonian_one1->names(),
      84           0 :                                                       hamiltonian_one2->names()); // TODO remove
      85             :     }
      86           4 :     Configuration conf_matpair = this->basis->getConf();
      87           2 :     conf_matpair["deltaEPair"] = conf_tot["deltaEPair"];
      88           2 :     conf_matpair["deltaNPair"] = conf_tot["deltaNPair"];
      89           2 :     conf_matpair["deltaLPair"] = conf_tot["deltaLPair"];
      90           2 :     conf_matpair["deltaJPair"] = conf_tot["deltaJPair"];
      91           2 :     conf_matpair["deltaMPair"] = conf_tot["deltaMPair"];
      92             :     // conf_matpair["conserveM"] = conf_tot["conserveM"];
      93             :     // conf_matpair["conserveParityL"] = conf_tot["conserveParityL"];
      94           2 :     conf_matpair["exponent"] = conf_tot["exponent"];
      95             : 
      96           4 :     for (size_t i = 0; i < nSteps_one; ++i) {
      97             :         // old, single atom hamiltonian specific configuration
      98           4 :         Configuration conf_matsingle = *hamiltonian_one1->getParams(i); // TODO
      99           2 :         conf_matsingle += conf_matpair;
     100           2 :         conf_mat.push_back(conf_matsingle);
     101             :         // conf_mat.push_back(conf_matsingle + conf_matpair); // conf_matpair overwrites settings in
     102             :         // conf_matsingle // TODO
     103             :     }
     104             : 
     105             :     // setup variables
     106           4 :     conf_mat.back()["species1"] >>
     107           2 :         species1; // TODO order state inside cinfiguration object config.order()
     108           2 :     conf_mat.back()["species2"] >> species2; // TODO order state inside cinfiguration object
     109           2 :     conf_mat.back()["deltaEPair"] >> deltaE;
     110           2 :     conf_mat.back()["deltaNPair"] >> deltaN;
     111           2 :     conf_mat.back()["deltaLPair"] >> deltaL;
     112           2 :     conf_mat.back()["deltaJPair"] >> deltaJ;
     113           2 :     conf_mat.back()["deltaMPair"] >> deltaM;
     114             :     // conserveM = conf_tot["conserveM"].str() == "true";
     115             :     // conserveParityL = conf_tot["conserveParityL"].str() == "true";
     116           2 :     conf_tot["steps"] >> nSteps_two;
     117           2 :     conf_tot["minR"] >> min_R;
     118           2 :     conf_tot["maxR"] >> max_R;
     119           2 :     conf_tot["exponent"] >> multipoleexponent;
     120             : 
     121             :     double minEx, minEy, minEz, maxEx, maxEy, maxEz, minBx, minBy, minBz, maxBx, maxBy, maxBz;
     122           2 :     conf_tot["minEx"] >> minEx;
     123           2 :     conf_tot["minEy"] >> minEy;
     124           2 :     conf_tot["minEz"] >> minEz;
     125           2 :     conf_tot["maxEx"] >> maxEx;
     126           2 :     conf_tot["maxEy"] >> maxEy;
     127           2 :     conf_tot["maxEz"] >> maxEz;
     128           2 :     conf_tot["minBx"] >> minBx;
     129           2 :     conf_tot["minBy"] >> minBy;
     130           2 :     conf_tot["minBz"] >> minBz;
     131           2 :     conf_tot["maxBx"] >> maxBx;
     132           2 :     conf_tot["maxBy"] >> maxBy;
     133           2 :     conf_tot["maxBz"] >> maxBz;
     134             :     // bool fields_change_m = (minEx != 0) || (minEy != 0) || (maxEx != 0) || (maxEy != 0) || (minBx
     135             :     // != 0) || (minBy != 0) || (maxBx != 0) || (maxBy != 0); // TODO wie richtig? so ist es eine
     136             :     // variable, die von mehreren matrizen abhaengt bool fields_change_l = (minEx != 0) || (minEy !=
     137             :     // 0) || (minEz != 0) || (maxEx != 0) || (maxEy != 0) || (maxEz != 0); // TODO wie richtig? so
     138             :     // ist es eine variable, die von mehreren matrizen abhaengt
     139             : 
     140             :     // fields_change_m = true; // TODO
     141             :     // fields_change_l = true; // TODO
     142             : 
     143           4 :     std::vector<parity_t> sym_inversion;
     144           2 :     if (conf_tot["invE"].str() == "true") {
     145           2 :         sym_inversion.push_back(EVEN);
     146             :     }
     147           2 :     if (conf_tot["invO"].str() == "true") {
     148           2 :         sym_inversion.push_back(ODD);
     149             :     }
     150           2 :     if (sym_inversion.empty()) {
     151           0 :         sym_inversion.push_back(NA);
     152             :     }
     153             : 
     154           4 :     std::vector<parity_t> sym_permutation;
     155           2 :     if (conf_tot["perE"].str() == "true") {
     156           2 :         sym_permutation.push_back(EVEN);
     157             :     }
     158           2 :     if (conf_tot["perO"].str() == "true") {
     159           2 :         sym_permutation.push_back(ODD);
     160             :     }
     161           2 :     if (sym_permutation.empty()) {
     162           0 :         sym_permutation.push_back(NA);
     163             :     }
     164             : 
     165           4 :     std::vector<parity_t> sym_reflection;
     166           2 :     if (conf_tot["refE"].str() == "true") {
     167           0 :         sym_reflection.push_back(EVEN);
     168             :     }
     169           2 :     if (conf_tot["refO"].str() == "true") {
     170           0 :         sym_reflection.push_back(ODD);
     171             :     }
     172           2 :     if (sym_reflection.empty()) {
     173           2 :         sym_reflection.push_back(NA);
     174             :     }
     175             : 
     176           2 :     bool conserveM = conf_tot["conserveM"].str() == "true";
     177             : 
     178           2 :     bool sametrafo = conf_tot["sametrafo"].str() == "true";
     179             : 
     180           2 :     bool zerotheta = conf_tot["zerotheta"].str() == "true";
     181             : 
     182           2 :     if (min_R == max_R && nSteps_one == 1) {
     183           0 :         nSteps_two = 1;
     184             :     }
     185             : 
     186             :     ////////////////////////////////////////////////////////
     187             :     ////// Restrict single atom states /////////////////////
     188             :     ////////////////////////////////////////////////////////
     189             : 
     190             :     // === Restrict states of atom 1 ===
     191             : 
     192           4 :     auto basis_one1 = hamiltonian_one1->names();
     193           4 :     std::vector<StateOneOld> initial1 = basis_one1->initial();
     194           4 :     std::vector<bool> necessary1(basis_one1->size(), false);
     195             : 
     196         226 :     for (const auto &state : *basis_one1) {
     197         224 :         bool validN = false;
     198         224 :         bool validL = false;
     199         224 :         bool validJ = false;
     200         224 :         bool validM = false;
     201             : 
     202         672 :         for (const auto &initial : initial1) {
     203         448 :             if (deltaN < 0 || std::abs(state.n - initial.n) <= deltaN) {
     204         448 :                 validN = true;
     205             :             }
     206         448 :             if (deltaL < 0 || std::abs(state.l - initial.l) <= deltaL) {
     207         448 :                 validL = true;
     208             :             }
     209         448 :             if (deltaJ < 0 || std::abs(state.j - initial.j) <= deltaJ) {
     210         448 :                 validJ = true;
     211             :             }
     212         448 :             if (deltaM < 0 || std::abs(state.m - initial.m) <= deltaM) {
     213         448 :                 validM = true;
     214             :             }
     215             :         }
     216             : 
     217         224 :         if (validN && validL && validJ && validM) {
     218         224 :             necessary1[state.idx] = true;
     219             :         }
     220             :     }
     221             : 
     222           2 :     hamiltonian_one1->removeUnnecessaryStates(necessary1);
     223             : 
     224             :     // === Restrict states of atom 2 ===
     225             : 
     226           2 :     if (!samebasis) {
     227           0 :         auto basis_one2 = hamiltonian_one2->names();
     228           0 :         std::vector<StateOneOld> initial2 = basis_one2->initial();
     229           0 :         std::vector<bool> necessary2(basis_one2->size(), false);
     230             : 
     231           0 :         for (const auto &state : *basis_one2) {
     232           0 :             bool validN = false;
     233           0 :             bool validL = false;
     234           0 :             bool validJ = false;
     235           0 :             bool validM = false;
     236             : 
     237           0 :             for (const auto &initial : initial2) {
     238           0 :                 if (deltaN < 0 || std::abs(state.n - initial.n) <= deltaN) {
     239           0 :                     validN = true;
     240             :                 }
     241           0 :                 if (deltaL < 0 || std::abs(state.l - initial.l) <= deltaL) {
     242           0 :                     validL = true;
     243             :                 }
     244           0 :                 if (deltaJ < 0 || std::abs(state.j - initial.j) <= deltaJ) {
     245           0 :                     validJ = true;
     246             :                 }
     247           0 :                 if (deltaM < 0 || std::abs(state.m - initial.m) <= deltaM) {
     248           0 :                     validM = true;
     249             :                 }
     250             :             }
     251             : 
     252           0 :             if (validN && validL && validJ && validM) {
     253           0 :                 necessary2[state.idx] = true;
     254             :             }
     255             :         }
     256             : 
     257           0 :         hamiltonian_one2->removeUnnecessaryStates(necessary2);
     258             :     }
     259             : 
     260             :     ////////////////////////////////////////////////////////
     261             :     ////// Build pair state basis //////////////////////////
     262             :     ////////////////////////////////////////////////////////
     263             : 
     264             :     // === Build pair state basis ===
     265             : 
     266           2 :     std::cout << "Two-atom Hamiltonian, build pair state basis" << std::endl;
     267             : 
     268           2 :     if (samebasis) {
     269           2 :         this->basis = std::make_shared<BasisnamesTwo>(hamiltonian_one1->names());
     270             :     } else {
     271           0 :         this->basis =
     272           0 :             std::make_shared<BasisnamesTwo>(hamiltonian_one1->names(), hamiltonian_one2->names());
     273             :     }
     274             : 
     275           2 :     std::cout << "Two-atom Hamiltonian, basis size without restrictions: " << this->basis->size()
     276           2 :               << std::endl;
     277             : 
     278             :     // === Determine necessary symmetries ===
     279           2 :     std::cout << "Two-atom Hamiltonian, determine symmetrized subspaces" << std::endl;
     280             : 
     281           4 :     StateTwoOld initial = this->basis->initial();
     282           2 :     parity_t initalParityL = (std::pow(-1, initial.l[0] + initial.l[1]) > 0) ? EVEN : ODD;
     283           2 :     int initalM = initial.m[0] + initial.m[1];
     284           2 :     int initalJ = initial.j[0] + initial.j[1];
     285           2 :     bool samestates = initial.first() == initial.second();
     286             : 
     287           4 :     std::vector<int> sym_rotation;
     288           2 :     if (conserveM) {
     289           2 :         if (!sametrafo) {
     290           0 :             for (const auto &state : *this->basis) {
     291           0 :                 sym_rotation.push_back(state.m[0] + state.m[1]);
     292             :             }
     293           2 :         } else if (!zerotheta) {
     294           0 :             for (int M = -initalJ; M <= initalJ; ++M) {
     295           0 :                 sym_rotation.push_back(M);
     296             :             }
     297             :         } else {
     298           2 :             sym_rotation.push_back(initalM);
     299             :         }
     300             :     } else {
     301           0 :         sym_rotation.push_back(NA);
     302             :     }
     303             : 
     304             :     Symmetry sym;
     305           4 :     std::set<Symmetry> symmetries_set;
     306           6 :     for (const parity_t &inv : sym_inversion) {
     307           4 :         sym.inversion = inv;
     308             : 
     309             :         // In case of even inversion symmetry and the same inital state for the first and second
     310             :         // atom: the inital state ist not contained in the corresponding block
     311           4 :         if (sym.inversion == EVEN && samestates && sametrafo) {
     312           0 :             continue;
     313             :         }
     314             : 
     315          12 :         for (const parity_t &per : sym_permutation) {
     316           8 :             sym.permutation = per;
     317             : 
     318             :             // In case of even permutation symmetry and the same inital state for the first and
     319             :             // second atom: the inital state ist not contained in the corresponding block
     320           8 :             if (sym.permutation == EVEN && samestates && sametrafo) {
     321           0 :                 continue;
     322             :             }
     323             : 
     324             :             // In case of inversion and permutation symmetry: the orbital parity is conserved and we
     325             :             // just use the blocks with the same parity as the inital state
     326           8 :             if (sym.inversion != NA && sym.permutation != NA && sametrafo) {
     327           8 :                 if (sym.inversion * sym.permutation != initalParityL) {
     328           4 :                     continue;
     329             :                 }
     330             :             }
     331             : 
     332           8 :             for (const parity_t &ref : sym_reflection) {
     333           4 :                 sym.reflection = ref;
     334           8 :                 for (const int &rot : sym_rotation) {
     335             : 
     336             :                     // In case of reflection symmetry: do just use the absolute value of rot
     337           4 :                     if (sym.reflection != NA) {
     338           0 :                         sym.rotation = std::abs(rot);
     339             :                     } else {
     340           4 :                         sym.rotation = rot;
     341             :                     }
     342             : 
     343           4 :                     symmetries_set.insert(sym);
     344             :                 }
     345             :             }
     346             :         }
     347             :     }
     348           4 :     std::vector<Symmetry> symmetries(symmetries_set.begin(), symmetries_set.end());
     349             : 
     350             :     // === Build up the list of necessary pair states ===
     351           2 :     std::cout << "Two-atom Hamiltonian, build up the list of necessary pair states" << std::endl;
     352             : 
     353             :     // Apply energy cutoff
     354           4 :     std::vector<bool> necessary_tmp(this->basis->size(), false);
     355             : 
     356           2 :     auto nSteps_one_i = static_cast<int>(nSteps_one);
     357             : 
     358           2 : #pragma omp parallel for
     359             :     for (int i = 0; i < nSteps_one_i; ++i) {
     360             :         energycutoff(*(hamiltonian_one1->get(i)), *(hamiltonian_one2->get(i)), deltaE,
     361             :                      necessary_tmp);
     362             :     }
     363             : 
     364             :     // Apply restrictions due to symmetries
     365           4 :     std::vector<bool> necessary(this->basis->size(), false);
     366             : 
     367       50178 :     for (const auto &state : *this->basis) {
     368      150528 :         for (Symmetry sym : symmetries) {
     369      100352 :             float M = state.m[0] + state.m[1];
     370      100352 :             int parityL = std::pow(-1, state.l[0] + state.l[1]);
     371             : 
     372             :             // In case of rotation symmetry: skip pair states with wrong total magnetic momentum
     373      100352 :             if (sym.rotation != NA && sym.rotation != M &&
     374       87262 :                 (sym.reflection == NA || sym.rotation != -M)) {
     375       87262 :                 continue;
     376             :             }
     377             : 
     378             :             // In case of inversion and permutation symmetry: skip pair states with wrong orbital
     379             :             // parity
     380       13090 :             if (sym.inversion != NA && sym.permutation != NA && parityL != initalParityL &&
     381             :                 sametrafo) {
     382        6690 :                 continue;
     383             :             }
     384             : 
     385        6400 :             necessary[state.idx] = necessary_tmp[state.idx];
     386             :         }
     387             :     }
     388             : 
     389           2 :     int numNecessary = std::count(necessary.begin(), necessary.end(), true);
     390           2 :     std::cout << "Two-atom Hamiltonian, basis size with restrictions: " << numNecessary
     391           2 :               << std::endl;
     392           2 :     std::cout << fmt::format(">>BAS{:7d}", numNecessary) << std::endl;
     393             : 
     394             :     // === Save pair state basis ===
     395           2 :     std::cout << "Two-atom Hamiltonian, save pair state basis" << std::endl;
     396             : 
     397             :     // initialize uuid generator
     398             :     boost::uuids::random_generator generator;
     399             : 
     400             :     // generate uuid
     401           4 :     std::string uuid;
     402           2 :     boost::uuids::uuid u = generator();
     403           2 :     boost::algorithm::hex(u.begin(), u.end(), std::back_inserter(uuid));
     404             : 
     405             :     // save pair state basis
     406           4 :     fs::path path_basis = fs::temp_directory_path();
     407           2 :     path_basis /= "basis_two_" + uuid + ".csv";
     408           4 :     this->basis->save(
     409             :         path_basis
     410           4 :             .string()); // TODO save only necessary entries, i.e. save pair state basis in sparse
     411             :                         // format (possibility, remove basis states but keep their idx - this would
     412             :                         // also make "if (necessary) continue" unneeded; then, "combine" has to
     413             :                         // check existence of basis element and the python script has to be adapted)
     414             : 
     415           4 :     std::cout << fmt::format(">>STA {:s}", path_basis.string()) << std::endl;
     416             : 
     417             :     ////////////////////////////////////////////////////////
     418             :     ////// Construct atom-atom interaction /////////////////
     419             :     ////////////////////////////////////////////////////////
     420             : 
     421             :     // Construct pair Hamiltonians for all orders of the multipole expansion
     422             : 
     423           4 :     std::vector<int> exponent_multipole;
     424           4 :     std::vector<Hamiltonianmatrix<Scalar>> mat_multipole;
     425           4 :     MatrixElements matrixelements_atom1(conf_tot, species1,
     426           4 :                                         (path_cache / "cache_elements.db").string());
     427           4 :     MatrixElements matrixelements_atom2(conf_tot, species2,
     428           4 :                                         (path_cache / "cache_elements.db").string());
     429           4 :     std::vector<idx_t> size_mat_multipole;
     430             : 
     431           2 :     int idx_multipole_max = -1;
     432             : 
     433           2 :     if (multipoleexponent > 2) {
     434             : 
     435             :         // --- Initialize two-atom interaction Hamiltonians ---
     436           2 :         std::cout << "Two-atom Hamiltonian, initialize interaction Hamiltonians" << std::endl;
     437             : 
     438           2 :         int kappa_min = 1; // spherical dipole operators
     439           2 :         int kappa_max = multipoleexponent - kappa_min - 1;
     440           2 :         int sumOfKappas_min = kappa_min + kappa_min;
     441           2 :         int sumOfKappas_max = kappa_max + kappa_min;
     442           2 :         idx_multipole_max = sumOfKappas_max - sumOfKappas_min;
     443             : 
     444           2 :         exponent_multipole.reserve(idx_multipole_max + 1);
     445           2 :         mat_multipole.reserve(idx_multipole_max + 1);
     446           2 :         size_mat_multipole.resize(idx_multipole_max + 1);
     447             : 
     448             :         // --- Precalculate matrix elements --- // TODO parallelization
     449           2 :         std::cout << "Two-atom Hamiltonian, get one-atom states needed for the pair state basis"
     450           2 :                   << std::endl;
     451             : 
     452           4 :         auto basis_one1_needed =
     453           4 :             std::make_shared<BasisnamesOne>(BasisnamesOne::fromFirst(this->basis));
     454           4 :         auto basis_one2_needed =
     455           4 :             std::make_shared<BasisnamesOne>(BasisnamesOne::fromSecond(this->basis));
     456             : 
     457           4 :         for (int kappa = kappa_min; kappa <= kappa_max; ++kappa) {
     458           2 :             std::cout << "Two-atom Hamiltonian, precalculate matrix elements for kappa = " << kappa
     459           2 :                       << std::endl;
     460           2 :             matrixelements_atom1.precalculateMultipole(basis_one1_needed, kappa);
     461           2 :             matrixelements_atom2.precalculateMultipole(basis_one2_needed, kappa);
     462             :         }
     463             : 
     464             :         // TODO if (samebasis) ...
     465             : 
     466             :         // --- Count entries of two-atom interaction Hamiltonians ---
     467             :         std::cout
     468           2 :             << "Two-atom Hamiltonian, count number of entries within the interaction Hamiltonians"
     469           2 :             << std::endl;
     470             : 
     471           4 :         for (int sumOfKappas = sumOfKappas_min; sumOfKappas <= sumOfKappas_max; ++sumOfKappas) {
     472           2 :             int idx_multipole = sumOfKappas - sumOfKappas_min;
     473             : 
     474       50178 :             for (const auto &state_col : *this->basis) { // TODO parallelization
     475       50176 :                 if (!necessary[state_col.idx]) {
     476       50056 :                     continue;
     477             :                 }
     478             : 
     479         120 :                 int M_col = state_col.first().m + state_col.second().m;
     480             : 
     481     6021240 :                 for (const auto &state_row : *this->basis) {
     482     6021120 :                     if (!necessary[state_row.idx]) {
     483     6006720 :                         continue;
     484             :                     }
     485             : 
     486       14400 :                     if (state_row.idx < state_col.idx) {
     487        7140 :                         continue;
     488             :                     }
     489        7260 :                     int M_row = state_row.first().m + state_row.second().m;
     490        7260 :                     if (M_col != M_row) {
     491           0 :                         continue;
     492             :                     }
     493             : 
     494             :                     // multipole interaction with 1/R^(sumOfKappas+1) = 1/R^(idx_multipole+3) decay
     495       14392 :                     for (int kappa1 = kappa_min; kappa1 <= sumOfKappas - 1; ++kappa1) {
     496        7260 :                         int kappa2 = sumOfKappas - kappa1;
     497             : 
     498             :                         // allowed deltaL, deltaJ, and deltaM?
     499       15128 :                         if (selectionRulesMultipole(state_row.first(), state_col.first(), kappa1) &&
     500        7868 :                             selectionRulesMultipole(state_row.second(), state_col.second(),
     501             :                                                     kappa2)) {
     502         128 :                             int q1 = state_row.first().m - state_col.first().m;
     503         128 :                             int q2 = state_row.second().m - state_col.second().m;
     504             : 
     505             :                             // total momentum preserved?
     506         128 :                             if (q1 == -q2) {
     507         128 :                                 size_mat_multipole[idx_multipole]++;
     508         128 :                                 break;
     509             :                             }
     510             :                         }
     511             :                     }
     512             :                 }
     513             :             }
     514             :         }
     515             : 
     516             :         // --- Construct two-atom interaction Hamiltonians ---
     517           2 :         size_t size_basis = this->basis->size();
     518             : 
     519           4 :         for (int sumOfKappas = sumOfKappas_min; sumOfKappas <= sumOfKappas_max; ++sumOfKappas) {
     520             :             std::cout
     521           2 :                 << "Two-atom Hamiltonian, construct interaction Hamiltonian that belongs to 1/R^"
     522           2 :                 << sumOfKappas + 1 << std::endl;
     523             : 
     524           2 :             int idx_multipole = sumOfKappas - sumOfKappas_min;
     525             : 
     526           2 :             exponent_multipole.push_back(sumOfKappas + 1);
     527           2 :             mat_multipole.emplace_back(
     528             :                 size_basis,
     529           2 :                 2 * size_mat_multipole[idx_multipole]); // factor of 2 because triangular matrix is
     530             :                                                         // not sufficient
     531             : 
     532       50178 :             for (const auto &state_col : *this->basis) { // TODO parallelization
     533       50176 :                 if (!necessary[state_col.idx]) {
     534       50056 :                     continue;
     535             :                 }
     536             : 
     537         120 :                 int M_col = state_col.first().m + state_col.second().m;
     538             : 
     539     6021240 :                 for (const auto &state_row : *this->basis) {
     540     6021120 :                     if (!necessary[state_row.idx]) {
     541     6006720 :                         continue;
     542             :                     }
     543             : 
     544       14400 :                     if (state_row.idx < state_col.idx) {
     545        7140 :                         continue;
     546             :                     }
     547        7260 :                     int M_row = state_row.first().m + state_row.second().m;
     548        7260 :                     if (M_col != M_row) {
     549           0 :                         continue;
     550             :                     }
     551             : 
     552             :                     // construct basis
     553        7260 :                     if (state_row.idx == state_col.idx) {
     554         120 :                         mat_multipole[idx_multipole].addBasis(state_row.idx, state_col.idx, 1);
     555             :                     }
     556             : 
     557             :                     // multipole interaction with 1/R^(sumOfKappas+1) = 1/R^(idx_multipole+3) decay
     558        7260 :                     double val = 0;
     559             : 
     560       14520 :                     for (int kappa1 = kappa_min; kappa1 <= sumOfKappas - 1; ++kappa1) {
     561        7260 :                         int kappa2 = sumOfKappas - kappa1;
     562             : 
     563             :                         // allowed deltaL, deltaJ, and deltaM?
     564       15128 :                         if (selectionRulesMultipole(state_row.first(), state_col.first(), kappa1) &&
     565        7868 :                             selectionRulesMultipole(state_row.second(), state_col.second(),
     566             :                                                     kappa2)) {
     567         128 :                             int q1 = state_row.first().m - state_col.first().m;
     568         128 :                             int q2 = state_row.second().m - state_col.second().m;
     569             : 
     570             :                             // total momentum preserved?
     571         128 :                             if (q1 == -q2) {
     572         384 :                                 double binomials = boost::math::binomial_coefficient<double>(
     573         128 :                                                        kappa1 + kappa2, kappa1 + q1) *
     574         256 :                                     boost::math::binomial_coefficient<double>(kappa1 + kappa2,
     575         128 :                                                                               kappa2 - q2);
     576         128 :                                 val += inverse_electric_constant * std::pow(-1, kappa2) *
     577         256 :                                     std::sqrt(binomials) *
     578         128 :                                     matrixelements_atom1.getMultipole(state_row.first(),
     579         256 :                                                                       state_col.first(), kappa1) *
     580         128 :                                     matrixelements_atom2.getMultipole(state_row.second(),
     581         256 :                                                                       state_col.second(), kappa2);
     582             :                             }
     583             :                         }
     584             :                     }
     585             : 
     586        7260 :                     if (std::abs(val) > tol) {
     587         128 :                         mat_multipole[idx_multipole].addEntries(state_row.idx, state_col.idx, val);
     588         128 :                         if (state_row.idx != state_col.idx) {
     589         128 :                             mat_multipole[idx_multipole].addEntries(
     590         128 :                                 state_col.idx, state_row.idx,
     591             :                                 val); // triangular matrix is not sufficient because of basis change
     592             :                         }
     593             :                     }
     594             : 
     595             :                     // TODO state_two soll std::array<state_one, 2> sein! Dann geht auch die Abfrage
     596             :                     // der selection rules eindeutiger
     597             :                 }
     598             :             }
     599             : 
     600             :             std::cout
     601           2 :                 << "Two-atom Hamiltonian, compress interaction Hamiltonian that belongs to 1/R^"
     602           2 :                 << sumOfKappas + 1 << std::endl;
     603             : 
     604           6 :             mat_multipole[idx_multipole].compress(
     605           2 :                 this->basis->dim(),
     606           2 :                 this->basis->dim()); // TODO substitute dim() by size()
     607             :         }
     608             :     }
     609             : 
     610             :     ////////////////////////////////////////////////////////
     611             :     ////// Prepare processing of Hamiltonians //////////////
     612             :     ////////////////////////////////////////////////////////
     613             : 
     614             :     // TODO Put the logic in its own class
     615             : 
     616           2 :     std::cout << "Two-atom Hamiltonian, process Hamiltonians" << std::endl;
     617             : 
     618             :     // === Open database ===
     619           4 :     fs::path path_db;
     620             : 
     621             :     if (utils::is_complex<Scalar>::value) {
     622           0 :         path_db = path_cache / "cache_matrix_complex.db";
     623             :     } else {
     624           2 :         path_db = path_cache / "cache_matrix_real.db";
     625             :     }
     626           4 :     sqlite::handle db(path_db.string());
     627           4 :     sqlite::statement stmt(db);
     628             : 
     629             :     // === Initialize variables ===
     630           2 :     bool flag_perhapsmissingtable = true;
     631             : 
     632           2 :     this->matrix_path.resize(nSteps_two * symmetries.size());
     633             : 
     634           2 :     auto indices_symmetry_i = static_cast<int>(symmetries.size());
     635             : 
     636             :     // --- Determine combined single atom matrices ---
     637             :     // Construct pair Hamiltonian consistent of combined one-atom Hamiltonians (1 x Hamiltonian2 +
     638             :     // Hamiltonian1 x 1)
     639             : 
     640           4 :     std::vector<Hamiltonianmatrix<Scalar>> mat_single;
     641             : 
     642             :     // Check if one_atom Hamiltonians change with step_two
     643             :     // It is assumed that nSteps_one = 1 if nSteps_two != nSteps_one // TODO introduce variable
     644             :     // "is_mat_single_const" to improve readability
     645           2 :     if (nSteps_two != nSteps_one) {
     646             :         std::cout
     647           2 :             << "Two-atom Hamiltonian, construct contribution of combined one-atom Hamiltonians"
     648           2 :             << std::endl;
     649             : 
     650           2 :         mat_single.resize(symmetries.size());
     651             : 
     652           2 : #pragma omp parallel for
     653             :         for (int idx_symmetry = 0; idx_symmetry < indices_symmetry_i; ++idx_symmetry) {
     654             :             Symmetry sym = symmetries[idx_symmetry];
     655             : 
     656             :             // Combine the Hamiltonians of the two atoms
     657             :             mat_single[idx_symmetry] = combine(
     658             :                 *(hamiltonian_one1->get(0)), *(hamiltonian_one2->get(0)), deltaE, this->basis, sym);
     659             : 
     660             :             // Remove more or less empty basis vectors
     661             :             mat_single[idx_symmetry].removeUnnecessaryBasisvectors();
     662             :         }
     663             :     }
     664             : 
     665             :     // --- Determine transformed interaction matrices ---
     666           4 :     std::vector<Hamiltonianmatrix<Scalar>> mat_multipole_transformed;
     667             : 
     668             :     // Check if one_atom Hamiltonians change with step_two
     669           2 :     if (nSteps_two != nSteps_one) {
     670           2 :         std::cout << "Two-atom Hamiltonian, construct transformed interaction matrices"
     671           2 :                   << std::endl;
     672             : 
     673           2 :         mat_multipole_transformed.resize(symmetries.size() * (idx_multipole_max + 1));
     674             : 
     675           2 : #pragma omp parallel for
     676             :         for (int idx_symmetry = 0; idx_symmetry < indices_symmetry_i; ++idx_symmetry) {
     677             :             for (int idx_multipole = 0; idx_multipole <= idx_multipole_max; ++idx_multipole) {
     678             :                 mat_multipole_transformed[idx_symmetry * (idx_multipole_max + 1) + idx_multipole] =
     679             :                     mat_multipole[idx_multipole].changeBasis(mat_single[idx_symmetry].basis());
     680             :             }
     681             :         }
     682             :     }
     683             : 
     684             :     ////////////////////////////////////////////////////////
     685             :     ////// Loop through steps and symmetries ///////////////
     686             :     ////////////////////////////////////////////////////////
     687             : 
     688           4 :     std::cout << fmt::format(">>TOT{:7d}", nSteps_two * symmetries.size()) << std::endl;
     689             : 
     690           2 :     auto nSteps_two_i = static_cast<int>(nSteps_two);
     691             : 
     692           2 : #pragma omp parallel for schedule(static, 1)
     693             : 
     694             :     // Loop through steps
     695             :     for (int step_two = 0; step_two < nSteps_two_i; ++step_two) {
     696             : 
     697             :         // Loop through symmetries
     698             :         for (size_t idx_symmetry = 0; idx_symmetry < symmetries.size(); ++idx_symmetry) {
     699             :             Symmetry sym = symmetries[idx_symmetry];
     700             : 
     701             :             size_t step = step_two * symmetries.size() + idx_symmetry;
     702             : 
     703             :             // === Get parameters for the current position inside the loop ===
     704             :             int single_idx = (nSteps_two == nSteps_one) ? step_two : 0;
     705             : 
     706             :             // Get interatomic distance
     707             :             double normalized_position = (nSteps_two > 1) ? step_two / (nSteps_two - 1.) : 0;
     708             :             double position = min_R + normalized_position * (max_R - min_R);
     709             : 
     710             :             // Get configuration and save postions and symmetries
     711             :             Configuration conf = conf_mat[single_idx];
     712             :             conf["R"] << position;
     713             :             conf["inversion"] << ((sym.inversion == NA) ? "" : std::to_string(sym.inversion));
     714             :             conf["permutation"] << ((sym.permutation == NA) ? "" : std::to_string(sym.permutation));
     715             :             conf["reflection"] << ((sym.reflection == NA) ? "" : std::to_string(sym.reflection));
     716             :             conf["rotation"] << ((sym.rotation == NA) ? "" : std::to_string(sym.rotation));
     717             : 
     718             :             // === Create table if necessary ===
     719             :             std::stringstream query;
     720             :             std::string spacer;
     721             : 
     722             :             if (flag_perhapsmissingtable) {
     723             :                 query << "CREATE TABLE IF NOT EXISTS cache_two (uuid text NOT NULL PRIMARY KEY, "
     724             :                          "created TIMESTAMP DEFAULT CURRENT_TIMESTAMP, "
     725             :                          "accessed TIMESTAMP DEFAULT CURRENT_TIMESTAMP";
     726             :                 for (auto p : conf) {
     727             :                     query << ", " << p.first << " text";
     728             :                 }
     729             :                 query << ", UNIQUE (";
     730             :                 for (auto p : conf) {
     731             :                     query << spacer << p.first;
     732             :                     spacer = ", ";
     733             :                 }
     734             :                 query << "));";
     735             : 
     736             : #pragma omp critical(database)
     737             :                 stmt.exec(query.str());
     738             : 
     739             :                 flag_perhapsmissingtable = false;
     740             :             }
     741             : 
     742             :             // === Get uuid as filename ===
     743             :             std::string uuid;
     744             :             query.str(std::string());
     745             :             spacer = "";
     746             :             query << "SELECT uuid FROM cache_two WHERE ";
     747             :             for (auto p : conf) {
     748             :                 query << spacer << p.first << "='" << p.second.str() << "'";
     749             :                 spacer = " AND ";
     750             :             }
     751             :             query << ";";
     752             : 
     753             : #pragma omp critical(database)
     754             :             {
     755             :                 sqlite::statement stmt(db, query.str());
     756             :                 stmt.prepare();
     757             :                 if (stmt.step()) {
     758             :                     uuid = stmt.get<std::string>(0);
     759             :                 }
     760             :             }
     761             : 
     762             :             if (!uuid.empty()) {
     763             :                 query.str(std::string());
     764             :                 query << "UPDATE cache_two SET accessed = CURRENT_TIMESTAMP WHERE uuid = '" << uuid
     765             :                       << "';";
     766             : #pragma omp critical(database)
     767             :                 stmt.exec(query.str()); // TODO check whether this slows down the program
     768             : 
     769             :             } else {
     770             :                 boost::uuids::uuid u = generator();
     771             :                 boost::algorithm::hex(u.begin(), u.end(), std::back_inserter(uuid));
     772             : 
     773             :                 query.str(std::string());
     774             :                 query << "INSERT INTO cache_two (uuid";
     775             :                 for (auto p : conf) {
     776             :                     query << ", " << p.first;
     777             :                 }
     778             :                 query << ") values ( '" << uuid << "'";
     779             :                 for (auto p : conf) {
     780             :                     query << ", "
     781             :                           << "'" << p.second.str() << "'";
     782             :                 }
     783             :                 query << ");";
     784             : #pragma omp critical(database)
     785             :                 stmt.exec(query.str());
     786             :             }
     787             : 
     788             :             // === Check existence of files ===
     789             : 
     790             :             // Check whether .mat and .json file exists and compare settings in program with
     791             :             // settings in .json file
     792             :             fs::path path, path_mat, path_json;
     793             : 
     794             :             path = path_cache_mat / ("two_" + uuid);
     795             :             path_mat = path;
     796             :             path_mat.replace_extension(".mat");
     797             :             path_json = path;
     798             :             path_json.replace_extension(".json");
     799             : 
     800             :             bool is_existing = false;
     801             :             if (fs::exists(path_mat)) {
     802             :                 if (fs::exists(path_json)) {
     803             :                     Configuration params_loaded;
     804             :                     params_loaded.load_from_json(path_json.string());
     805             :                     if (conf == params_loaded) {
     806             :                         is_existing = true;
     807             :                     }
     808             :                 }
     809             :             }
     810             : 
     811             :             // Create .json file if "is_existing" is false
     812             :             if (!is_existing) {
     813             :                 conf.save_to_json(path_json.string());
     814             :             }
     815             : 
     816             :             // === Build and diagonalize total matrix if not existent ===
     817             :             Hamiltonianmatrix<Scalar> totalmatrix;
     818             : 
     819             :             if (!is_existing || !totalmatrix.load(path_mat.string())) {
     820             : 
     821             :                 // --- Combine single atom matrices ---
     822             :                 if (nSteps_two == nSteps_one) {
     823             :                     totalmatrix =
     824             :                         combine(*(hamiltonian_one1->get(step_two)),
     825             :                                 *(hamiltonian_one2->get(step_two)), deltaE, this->basis, sym);
     826             :                     totalmatrix.removeUnnecessaryBasisvectors();
     827             :                 } else {
     828             :                     totalmatrix = mat_single[idx_symmetry];
     829             :                 }
     830             : 
     831             :                 // --- Add interaction ---
     832             :                 for (int idx_multipole = 0; idx_multipole <= idx_multipole_max; ++idx_multipole) {
     833             :                     double pos = 1. / std::pow(position, exponent_multipole[idx_multipole]);
     834             :                     if (nSteps_two == nSteps_one) {
     835             :                         totalmatrix +=
     836             :                             mat_multipole[idx_multipole].changeBasis(totalmatrix.basis()) * pos;
     837             :                     } else {
     838             :                         totalmatrix +=
     839             :                             mat_multipole_transformed[idx_symmetry * (idx_multipole_max + 1) +
     840             :                                                       idx_multipole] *
     841             :                             pos;
     842             :                     }
     843             :                 }
     844             : 
     845             :                 // Stdout: Hamiltonian assembled
     846             : #pragma omp critical(textoutput)
     847             :                 {
     848             :                     std::cout << fmt::format(">>DIM{:7d}", totalmatrix.num_basisvectors())
     849             :                               << std::endl;
     850             :                     std::cout << "Two-atom Hamiltonian, " << step + 1 << ". Hamiltonian assembled"
     851             :                               << std::endl;
     852             :                 }
     853             : 
     854             :                 // --- Diagonalize matrix and save diagonalized matrix ---
     855             :                 totalmatrix.diagonalize();
     856             :                 totalmatrix.save(path_mat.string());
     857             : 
     858             :                 // Stdout: Hamiltonian diagonalized
     859             : #pragma omp critical(textoutput)
     860             :                 {
     861             :                     std::cout << fmt::format(">>OUT{:7d}{:7d}{:7d}{:7d} {:s}", (step + 1), step_two,
     862             :                                              symmetries.size(), idx_symmetry, path.string())
     863             :                               << std::endl;
     864             :                     std::cout << "Two-atom Hamiltonian, " << step + 1
     865             :                               << ". Hamiltonian diagonalized" << std::endl;
     866             :                 }
     867             :             } else {
     868             : 
     869             :                 // Stdout: Hamiltonian loaded
     870             : #pragma omp critical(textoutput)
     871             :                 {
     872             :                     std::cout << fmt::format(">>DIM{:7d}", totalmatrix.num_basisvectors())
     873             :                               << std::endl;
     874             :                     std::cout << fmt::format(">>OUT{:7d}{:7d}{:7d}{:7d} {:s}", (step + 1), step_two,
     875             :                                              symmetries.size(), idx_symmetry, path.string())
     876             :                               << std::endl;
     877             :                     std::cout << "Two-atom Hamiltonian, " << step + 1 << ". Hamiltonian loaded"
     878             :                               << std::endl;
     879             :                 }
     880             :             }
     881             : 
     882             :             // === Store path to configuration and diagonalized matrix ===
     883             :             this->matrix_path[step] = path.string();
     884             :         }
     885             :     }
     886             : 
     887           2 :     std::cout << "Two-atom Hamiltonian, all Hamiltonians processed" << std::endl;
     888           2 : }
     889             : 
     890             : template class HamiltonianTwo<std::complex<double>>;
     891             : template class HamiltonianTwo<double>;

Generated by: LCOV version 1.14