TRIQS/TRIQS 4.0.0
Researching Interacting Quantum Systems
Loading...
Searching...
No Matches
worker.cpp
1// Copyright (c) 2017-2018 Commissariat à l'énergie atomique et aux énergies alternatives (CEA)
2// Copyright (c) 2017-2018 Centre national de la recherche scientifique (CNRS)
3// Copyright (c) 2018-2023 Simons Foundation
4// Copyright (c) 2017 Igor Krivenko
5//
6// This program is free software: you can redistribute it and/or modify
7// it under the terms of the GNU 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// This program 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 General Public License for more details.
15//
16// You may obtain a copy of the License at
17// https://www.gnu.org/licenses/gpl-3.0.txt
18//
19// Authors: Maxime Charlebois, Michel Ferrero, Igor Krivenko, Olivier Parcollet, Hugo U. R. Strand, Nils Wentzell
20
21#include "./worker.hpp"
27
28#include <nda/nda.hpp>
29
30#include <bitset>
31#include <limits>
32#include <map>
33#include <tuple>
34#include <utility>
35#include <vector>
36
37using namespace triqs::hilbert_space;
38
39namespace triqs::atom_diag {
40
41// Methods of atom_diag_worker
42#define ATOM_DIAG_WORKER_METHOD(RET, F) template <bool Complex> auto atom_diag_worker<Complex>::F->RET
43
44 //------------------------------------------------------------------------------------
45
46 // Filter the Fock states with a number of particles within [n_min;n_max]
47 ATOM_DIAG_WORKER_METHOD(bool, fock_state_filter(fock_state_t s)) {
48 auto c = std::bitset<64>(s).count();
49 return ((c >= n_min) && (c <= n_max));
50 }
51
52 //------------------------------------------------------------------------------------
53
54 ATOM_DIAG_WORKER_METHOD(void, autopartition(many_body_op_t const &hyb)) {
55 //ATOM_DIAG_WORKER_METHOD(void, autopartition()) {
56
57 fundamental_operator_set const &fops = hdiag->get_fops();
58 many_body_op_t const &h = hdiag->get_h_atomic();
59
61 imperative_operator_t hamiltonian(h, fops);
62 imperative_operator_t hybridization(hyb, fops);
63 state<class hilbert_space, scalar_t, true> st(hdiag->full_hs);
64
65 using space_partition_t = space_partition<state<class hilbert_space, scalar_t, true>, imperative_operator_t>;
66 // Split the Hilbert space
67 space_partition_t SP(st, hamiltonian, false, hybridization);
68
69 std::vector<typename space_partition_t::matrix_element_map_t> creation_melem(fops.size());
70 std::vector<typename space_partition_t::matrix_element_map_t> annihilation_melem(fops.size());
71 // Merge subspaces
72 for (auto const &o : fops) {
73 auto create = many_body_op_t::make_canonical(true, o.index);
74 auto destroy = many_body_op_t::make_canonical(false, o.index);
75
76 imperative_operator_t op_c_dag(create, fops), op_c(destroy, fops);
77
78 int n = o.linear_index;
79 std::tie(creation_melem[n], annihilation_melem[n]) = SP.merge_subspaces(op_c_dag, op_c, true);
80 }
81
82 // Fill subspaces
83 auto &h_spaces = hdiag->sub_hilbert_spaces;
84
85 h_spaces.reserve(SP.n_subspaces());
86 for (int sp = 0; sp < SP.n_subspaces(); ++sp) h_spaces.emplace_back(sp);
87
88 foreach (SP, [&](fock_state_t s, int spn) {
89 if (fock_state_filter(s)) h_spaces[spn].add_fock_state(s);
90 });
91
92 // Discard empty subspaces
93 h_spaces.erase(std::remove_if(h_spaces.begin(), h_spaces.end(), [](sub_hilbert_space const &sp) { return sp.size() == 0; }), h_spaces.end());
94
95 // Correspondence between subspace indices before and after filtering
96 std::map<int, int> remap;
97 for (auto const &sp : h_spaces) remap.emplace(sp.get_index(), remap.size());
98
99 // Fill connections
100 hdiag->creation_connection.resize(fops.size(), h_spaces.size());
101 hdiag->annihilation_connection.resize(fops.size(), h_spaces.size());
102 hdiag->creation_connection.as_array_view() = -1;
103 hdiag->annihilation_connection.as_array_view() = -1;
104
105 for (auto const &o : fops) {
106 int n = o.linear_index;
107 for (auto const &[fs_pair, val] : creation_melem[n]) {
108 auto [i, f] = fs_pair;
109 auto i_remap_it = remap.find(SP.lookup_basis_state(i));
110 if (i_remap_it == remap.end()) continue;
111 auto f_remap_it = remap.find(SP.lookup_basis_state(f));
112 if (f_remap_it == remap.end()) continue;
113 hdiag->creation_connection(n, i_remap_it->second) = f_remap_it->second;
114 }
115 for (auto const &[fs_pair, val] : annihilation_melem[n]) {
116 auto [i, f] = fs_pair;
117 auto i_remap_it = remap.find(SP.lookup_basis_state(i));
118 if (i_remap_it == remap.end()) continue;
119 auto f_remap_it = remap.find(SP.lookup_basis_state(f));
120 if (f_remap_it == remap.end()) continue;
121 hdiag->annihilation_connection(n, i_remap_it->second) = f_remap_it->second;
122 }
123 }
124
125 // Reindex subspaces
126 for (int i = 0; i < h_spaces.size(); ++i) h_spaces[i].set_index(i);
127
128 complete();
129 }
130
131 // -----------------------------------------------------------------
132
133 ATOM_DIAG_WORKER_METHOD(void, partition_with_qn(std::vector<many_body_op_t> const &qn_vector)) {
134
135 fundamental_operator_set const &fops = hdiag->get_fops();
136 class hilbert_space const &full_hs = hdiag->full_hs;
137
138 // Define a more tolerant comparison between vectors for the quantum numbers
139 auto lt_dbl = [](std::vector<double> const &v1, std::vector<double> const &v2) {
140 for (int i = 0; i < v1.size(); ++i) {
141 if (v1[i] < (v2[i] - 1e-8))
142 return true;
143 else if (v2[i] < (v1[i] - 1e-8))
144 return false;
145 }
146 return false;
147 };
148
149 // Quantum numbers -> Hilbert subspace mapping
150 std::map<std::vector<double>, int, decltype(lt_dbl)> map_qn_n(lt_dbl);
151
152 // The QN as operators: a vector of imperative operators for the quantum numbers
153 std::vector<imperative_operator<class hilbert_space, scalar_t>> qsize;
154 for (auto &qn : qn_vector) qsize.emplace_back(qn, fops);
155
156 // Helper function to get quantum numbers
157 auto get_quantum_numbers = [&qsize](state<class hilbert_space, scalar_t, false> const &s) {
158 std::vector<quantum_number_t> qn;
159 for (auto const &op : qsize) {
160 auto y = dot_product(s, op(s));
161 if (std::abs(std::imag(y)) > 1.e-10) TRIQS_RUNTIME_ERROR << "Quantum number is complex !";
162 qn.push_back(std::real(y));
163 }
164 return qn;
165 };
166
167 // The first part consists in dividing the full Hilbert space
168 // into smaller subspaces using the quantum numbers
169 for (int r = 0; r < full_hs.size(); ++r) {
170
171 // fock_state corresponding to r
172 fock_state_t fs = full_hs.get_fock_state(r);
173
174 // The state we'll act on
175 state<class hilbert_space, scalar_t, false> s(full_hs);
176 s(r) = 1.0;
177
178 // Create the vector with the quantum numbers
179 std::vector<quantum_number_t> qn = get_quantum_numbers(s);
180
181 // If first time we meet these quantum numbers create partial Hilbert space
182 if (map_qn_n.count(qn) == 0) {
183 auto n_blocks = hdiag->sub_hilbert_spaces.size();
184 hdiag->sub_hilbert_spaces.emplace_back(n_blocks); // a new sub_hilbert_space
185 hdiag->quantum_numbers.push_back(qn);
186 map_qn_n[qn] = n_blocks;
187 }
188
189 // Add fock state to partial Hilbert space
190 hdiag->sub_hilbert_spaces[map_qn_n[qn]].add_fock_state(fs);
191 }
192
193 // ---- Now make the creation/annihilation maps -----
194
195 auto creation_map = std::vector<std::vector<int>>(fops.size(), std::vector<int>(hdiag->sub_hilbert_spaces.size(), -1));
196 auto annihilation_map = creation_map;
197
198 // init the mapping tables
199 hdiag->creation_connection.resize(fops.size(), hdiag->sub_hilbert_spaces.size());
200 hdiag->annihilation_connection.resize(fops.size(), hdiag->sub_hilbert_spaces.size());
201 hdiag->creation_connection.as_array_view() = -1;
202 hdiag->annihilation_connection.as_array_view() = -1;
203
204 for (auto const &x : fops) {
205
206 // Get the operators and their index
207 int n = x.linear_index;
208 auto create = many_body_op_t::make_canonical(true, x.index);
209 auto destroy = many_body_op_t::make_canonical(false, x.index);
210
211 // Construct their imperative counterpart
212 imperative_operator<class hilbert_space, scalar_t> op_c_dag(create, fops), op_c(destroy, fops);
213
214 // to avoid declaring every time in the loop below
215 std::vector<quantum_number_t> qn_before, qn_after;
216
217 // Now act on the state with the c, c_dag to see how quantum numbers change
218 for (int r = 0; r < full_hs.size(); ++r) {
219
220 // The state we'll act on and its quantum numbers
221 state<class hilbert_space, scalar_t, false> s(full_hs);
222 s(r) = 1.0;
223 qn_before = get_quantum_numbers(s);
224
225 // Apply creation on state to figure quantum numbers
226 qn_after = get_quantum_numbers(op_c_dag(s));
227
228 // Insert in creation map checking whether it was already there
229 if (!triqs::utility::is_zero(dot_product(op_c_dag(s), op_c_dag(s)))) {
230 auto origin = hdiag->sub_hilbert_spaces[map_qn_n[qn_before]].get_index();
231 auto target = hdiag->sub_hilbert_spaces[map_qn_n[qn_after]].get_index();
232 if (creation_map[n][origin] == -1)
233 creation_map[n][origin] = target;
234 else if (creation_map[n][origin] != target)
235 TRIQS_RUNTIME_ERROR << "partition_with_qn(): internal error while filling creation_connection";
236 hdiag->creation_connection(n, map_qn_n[qn_before]) = map_qn_n[qn_after];
237 }
238
239 // apply annihilation on state to figure quantum numbers
240 qn_after = get_quantum_numbers(op_c(s));
241
242 // insert in annihilation map checking whether it was already there
243 if (!triqs::utility::is_zero(dot_product(op_c(s), op_c(s)))) {
244 auto origin = hdiag->sub_hilbert_spaces[map_qn_n[qn_before]].get_index();
245 auto target = hdiag->sub_hilbert_spaces[map_qn_n[qn_after]].get_index();
246 if (annihilation_map[n][origin] == -1)
247 annihilation_map[n][origin] = target;
248 else if (annihilation_map[n][origin] != target)
249 TRIQS_RUNTIME_ERROR << "partition_with_qn(): internal error while filling annihilation_connection";
250 hdiag->annihilation_connection(n, map_qn_n[qn_before]) = map_qn_n[qn_after];
251 }
252 }
253 }
254 complete();
255 }
256
257 // -----------------------------------------------------------------
258
259 ATOM_DIAG_WORKER_METHOD(matrix_t, make_op_matrix(many_body_op_t const &op, int from_spn, int to_spn) const) {
260
261 fundamental_operator_set const &fops = hdiag->get_fops();
262 class hilbert_space const &full_hs = hdiag->full_hs;
263 auto const &from_sp = hdiag->sub_hilbert_spaces[from_spn];
264 auto const &to_sp = hdiag->sub_hilbert_spaces[to_spn];
265
267
268 auto M = matrix_t::zeros({to_sp.size(), from_sp.size()});
269
270 for (int i = 0; i < from_sp.size(); ++i) { // loop on all fock states of the blocks
271 state<class hilbert_space, scalar_t, true> from_s(full_hs);
272 from_s(from_sp.get_fock_state(i)) = 1.0;
273 auto to_s = imp_op(from_s);
274 auto proj_s = project<state<sub_hilbert_space, scalar_t, true>>(to_s, to_sp);
275 foreach (proj_s, [&](int j, scalar_t ampl) { M(j, i) = ampl; });
276 }
277
278 return dagger(hdiag->eigensystems[to_spn].unitary_matrix) * M * hdiag->eigensystems[from_spn].unitary_matrix;
279 }
280
281 // -----------------------------------------------------------------
282
283 ATOM_DIAG_WORKER_METHOD(void, complete()) {
284
285 fundamental_operator_set const &fops = hdiag->get_fops();
286 many_body_op_t const &h = hdiag->get_h_atomic();
287
289
290 // Compute energy levels and eigenvectors of the local Hamiltonian
291 int n_subspaces = hdiag->sub_hilbert_spaces.size();
292 hdiag->eigensystems.resize(n_subspaces);
293 hdiag->gs_energy = std::numeric_limits<double>::infinity();
294
295 // Prepare the eigensystem in a temporary map to sort them by energy !
296 std::map<std::pair<double, int>, typename atom_diag<Complex>::eigensystem_t> eign_map;
297 double energy_split = 1.e-10; // to split the eigenvalues, which are numerically very close
298 for (int spn = 0; spn < n_subspaces; ++spn) {
299 auto const &sp = hdiag->sub_hilbert_spaces[spn];
300 typename atom_diag<Complex>::eigensystem_t eigensystem;
301
302 state<sub_hilbert_space, scalar_t, false> i_state(sp);
303 auto h_matrix = matrix_t(sp.size(), sp.size());
304
305 for (int i = 0; i < sp.size(); ++i) {
306 i_state.amplitudes()() = 0;
307 i_state(i) = 1;
308 auto f_state = hamiltonian(i_state);
309 h_matrix(range::all, i) = f_state.amplitudes();
310 }
311
312 auto eig = linalg::eigh(h_matrix);
313 eigensystem.eigenvalues = eig.first;
314 eigensystem.unitary_matrix = eig.second;
315 hdiag->gs_energy = std::min(hdiag->gs_energy, eigensystem.eigenvalues[0]);
316
317 eign_map.insert({{eigensystem.eigenvalues(0) + energy_split * spn, spn}, eigensystem});
318 }
319
320 // Reorder the block along their minimal energy
321 {
322 auto tmp = hdiag->sub_hilbert_spaces;
323 std::map<int, int> remap;
324 int i = 0;
325 for (auto const &x : eign_map) { // in order of min energy !
326 hdiag->eigensystems[i] = x.second;
327 tmp[i] = hdiag->sub_hilbert_spaces[x.first.second];
328 tmp[i].set_index(i);
329 remap[x.first.second] = i;
330 ++i;
331 }
332 std::swap(tmp, hdiag->sub_hilbert_spaces);
333 auto remap_connection = [&](matrix<long> &connection) {
334 auto c2 = connection;
335 for (int n = 0; n < first_dim(connection); ++n)
336 for (int j = 0; j < second_dim(connection); ++j) connection(n, remap[j]) = (c2(n, j) == -1 ? -1 : remap[static_cast<int>(c2(n, j))]);
337 };
338 remap_connection(hdiag->creation_connection);
339 remap_connection(hdiag->annihilation_connection);
340 } // end reordering
341
342 // Shift the ground state energy of the local Hamiltonian to zero.
343 for (auto &eigensystem : hdiag->eigensystems) eigensystem.eigenvalues() -= hdiag->get_gs_energy();
344
345 for (auto const &x : fops) {
346 // get the operators and their index
347 int n = x.linear_index;
348 // n is guaranteed to be 0, 1, 2, 3, ... by the fundamental_operator_set class ... but it is very weird...
349 // otherwise the push_back below is false.
350 auto create = many_body_op_t::make_canonical(true, x.index);
351 auto destroy = many_body_op_t::make_canonical(false, x.index);
352
353 // Compute the matrices of c, c dagger in the diagonalization base of H_loc
354 // first a lambda, since it is almost the same code for c and cdag
355 auto make_c_mat = [&](int m, matrix<long> const &connection, many_body_op_t const &op) {
356 std::vector<matrix_t> cmat(second_dim(connection));
357 for (int B = 0; B < second_dim(connection); ++B) {
358 auto Bp = connection(m, B);
359 if (Bp == -1) continue;
360 cmat[B] = make_op_matrix(op, B, Bp);
361 }
362 return cmat;
363 };
364
365 // now execute code...
366 hdiag->c_matrices.push_back(make_c_mat(n, hdiag->annihilation_connection, destroy));
367 hdiag->cdag_matrices.push_back(make_c_mat(n, hdiag->creation_connection, create));
368
369 } // end of loop on operators
370 }
371
372 // -----------------------------------------------------------------
373
374 // Explicit instantiations for real and complex atom_diag_worker
375 template struct atom_diag_worker<false>;
376 template struct atom_diag_worker<true>;
377
378} // namespace triqs::atom_diag
Class representing a fundamental operator set.
Fermionic Hilbert (Fock) space generated by a fundamental operator set.
Representation of a many-body operator acting on many-body states.
Automatic partitioning of a Hilbert (Fock) space into invariant subspaces of a Hermitian operator.
Subspace of a fermionic Hilbert (Fock) space.
auto size() const
Get the dimension of the subspace.
auto get_index() const
Get the subspace index .
uint64_t fock_state_t
Integer type representing a fermionic fock state .
TargetState project(OriginalState const &psi, hilbert_space const &proj_hs)
Project a state to a new Hilbert (Fock) space .
Definition state.hpp:500
nda::matrix< double > matrix_t
Matrix type for transformations involving real and reciprocal space vectors.
many_body_operator_generic< T > n(IndexTypes... indices)
Create a number operator .
many_body_operator_generic< T > c(IndexTypes... indices)
Create an annihilation operator .
#define TRIQS_RUNTIME_ERROR
Throw a triqs::runtime_error with the current source location.
bool is_zero(I const &x)
Exact zero check for integral values.
Provides a fundamental operator set class.
Provides a fast imperative representation of a many-body operator acting on states.
Provides generic many-body operators.
Provides the automatic partitioning algorithm of a Hilbert (Fock) space into invariant subspaces.
Provides a type for many-body states in a Hilbert (Fock) space.
Eigensystem of a single invariant subspace of the Hamiltonian .
vector< double > eigenvalues
Eigenvalues sorted in ascending order.