TRIQS/triqs_cthyb 4.0.0
A TRIQS application
Loading...
Searching...
No Matches
impurity_trace.cpp
1/*******************************************************************************
2 *
3 * TRIQS: a Toolbox for Research in Interacting Quantum Systems
4 *
5 * Copyright (C) 2014, P. Seth, I. Krivenko, M. Ferrero and O. Parcollet
6 *
7 * TRIQS is free software: you can redistribute it and/or modify it under the
8 * terms of the GNU General Public License as published by the Free Software
9 * Foundation, either version 3 of the License, or (at your option) any later
10 * version.
11 *
12 * TRIQS is distributed in the hope that it will be useful, but WITHOUT ANY
13 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
14 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
15 * details.
16 *
17 * You should have received a copy of the GNU General Public License along with
18 * TRIQS. If not, see <http://www.gnu.org/licenses/>.
19 *
20 ******************************************************************************/
21#include "impurity_trace.hpp"
22#include <nda/nda.hpp>
23#include <algorithm>
24#include <limits>
25
26//#define TRACE_DEBUG_CHECKS
27#ifdef TRACE_DEBUG_CHECKS
28#define TRACE_CHECK_CACHE
29#define TRACE_CHECK_AGAINST_LINEAR_COMPUTATION
30#define TRACE_CHECK_MATRIX_BOUNDED_BY_BOUND
31#endif
32
33double double_max = std::numeric_limits<double>::max(); // easier to read
34
35template <typename T>
36// require( is_real_or_complex<T>) FIXME?
37double frobenius_norm2(nda::matrix<T> const &a) {
38 double r = 0;
39 for (int i = 0; i < a.shape()[0]; ++i)
40 for (int j = 0; j < a.shape()[1]; ++j) {
41 auto ab = std::abs(a(i, j));
42 r += ab * ab;
43 }
44 return std::sqrt(r);
46
47// -----------------------------------------------
49namespace triqs_cthyb {
51 // -------- Constructor --------
52 impurity_trace::impurity_trace(double beta, atom_diag const &h_diag_, histo_map_t *hist_map, bool use_norm_as_weight, bool measure_density_matrix,
53 bool performance_analysis)
55 use_norm_as_weight(use_norm_as_weight),
56 measure_density_matrix(measure_density_matrix),
57 h_diag(&h_diag_),
58 density_matrix(n_blocks),
59 atomic_rho(n_blocks),
60 atomic_z(partition_function(*h_diag, beta)),
61 atomic_norm(0),
62 histo(performance_analysis ? new histograms_t(h_diag_.n_subspaces(), *hist_map) : nullptr) {
63
64 // init density_matrix block + bool
65 for (int bl = 0; bl < n_blocks; ++bl) density_matrix[bl] = bool_and_matrix{false, matrix_t(get_block_dim(bl), get_block_dim(bl))};
67 // prepare atomic_rho and atomic_norm
69 auto rho = atomic_density_matrix(h_diag_, beta);
70 for (int bl = 0; bl < n_blocks; ++bl) {
71 atomic_rho[bl] = bool_and_matrix{true, rho[bl] * atomic_z};
72 for (int u = 0; u < get_block_dim(bl); ++u) {
73 auto xx = std::abs(rho[bl](u, u));
74 atomic_norm += xx * xx;
75 }
76 }
77 atomic_norm = std::sqrt(atomic_norm);
78 }
79 }
80
81 //====== Recursive operations ======
82
83 // For all recursive operations, the cache on the current node is updated as follows:
84 //
85 // node current: b_current
86 // / \
87 // / \
88 // / \
89 // / \
90 // node: b_left node right: b_right
91 // tau=beta <========<=========< tau=0
92 // That is, quantities are always updated from right -> left,
93 // following the time-ordering of beta (left) <- 0 (right).
94 // Accordingly, the blocks are connected as follows: b_left <- b_current <- b_right
95
96 // ------- Computation of the block table (only) -------------
97
98 // for subtree at node n, returns B' that block b at the node closest to tau=0 connects to
99 // precondition: b != -1, n != null
100 // returns -1 if cancellation structural
101 int impurity_trace::compute_block_table(node n, int b) {
103 if (b < 0) TRIQS_RUNTIME_ERROR << " b < 0";
104 if (!n->modified) return n->cache.block_table[b];
106 int b1 = (n->right ? compute_block_table(n->right, b) : b);
107 if (b1 < 0) return b1;
109 int b2 = (n->delete_flag ? b1 : get_op_block_map(n, b1));
110 if (b2 < 0) return b2;
112 return (n->left ? compute_block_table(n->left, b2) : b2);
113 }
114 // -------- Computation of the block table and bounds -------------
115
116 // for subtree at node n, return (B', bound)
117 // precondition: b !=-1, n != null
118 // returns -1 for structural and/or threshold cancellation
119 std::pair<int, double> impurity_trace::compute_block_table_and_bound(node n, int b, double lnorm_threshold, bool use_threshold) {
121 if (b < 0) TRIQS_RUNTIME_ERROR << " b < 0";
122 if (!n->modified) return {n->cache.block_table[b], n->cache.matrix_lnorms[b]};
124 double lnorm = 0;
125
126 int b1 = b;
127 if (n->right) {
128 std::tie(b1, lnorm) = compute_block_table_and_bound(n->right, b, lnorm_threshold, use_threshold);
129 if (b1 < 0) return {b1, 0};
130 lnorm += n->cache.dtau_r * get_block_emin(b1);
131 }
132 if (use_threshold && (lnorm > lnorm_threshold)) return {-1, 0};
133
134 int b2 = (n->delete_flag ? b1 : get_op_block_map(n, b1));
135 if (b2 < 0) return {b2, 0};
136
137 int b3 = b2;
138 if (n->left) {
139 lnorm += n->cache.dtau_l * get_block_emin(b2);
140 if (use_threshold && (lnorm > lnorm_threshold)) return {-1, 0};
141 double lnorm3;
142 std::tie(b3, lnorm3) = compute_block_table_and_bound(n->left, b2, lnorm_threshold, use_threshold);
143 if (b3 < 0) return {b3, 0};
144 lnorm += lnorm3;
145 }
146
147 if (use_threshold && (lnorm > lnorm_threshold)) return {-1, 0};
148
149 if (std::isinf(lnorm)) {
150 lnorm = double_max;
151 if (lnorm < 0) TRIQS_RUNTIME_ERROR << "Negative lnorm in compute_block_table_and_bound!";
152 }
154 return {b3, lnorm};
155 }
157 // -------- Computation of the matrix ------------------------------
158
159 // returns {block that b connects to at this node, matrix for this block on node n (if not structurally zero, i.e. if B' != -1)}
160 std::pair<int, matrix_t> impurity_trace::compute_matrix(node n, int b) {
162 if (b == -1) return {-1, {}};
163 if (n == nullptr) return {b, {}};
164 if (!n->modified && n->cache.matrix_norm_valid[b]) return {n->cache.block_table[b], n->cache.matrices[b]};
165 bool updating = (!n->modified && !n->cache.matrix_norm_valid[b]);
166
167 double dtau_l = 0, dtau_r = 0;
168 auto _ = arrays::range::all;
169
170 auto r = compute_matrix(n->right, b);
171 int b1 = r.first; // exit block of right subtree
172 if (b1 == -1) return {-1, {}};
173
174 int b2 = (n->delete_flag ? b1 : get_op_block_map(n, b1)); // relevant block on current node
175 if (b2 == -1) return {-1, {}};
176
177 matrix_t M = (!n->delete_flag ? get_op_block_matrix(n, b1) : nda::eye<h_scalar_t>(get_block_dim(b1)));
178
179 if (n->right) { // M <- M * exp * r[b]
180 dtau_r = double(n->key - tree.min_key(n->right));
181 auto dim = M.shape()[1]; // same as get_block_dim(b2);
182 for (int i = 0; i < dim; ++i) M(_, i) *= std::exp(-dtau_r * get_block_eigenval(b1, i)); // Create time-evolution matrix e^-H(t'-t)
183 if ((r.second.shape()[0] == 1) && (r.second.shape()[1] == 1))
184 M *= r.second(0, 0);
185 else
186 M = M * r.second; // FIXME could try to optimise lapack call?
187 }
188
189 int b3 = b2;
190 if (n->left) { // M <- l[b] * exp * M
191 auto l = compute_matrix(n->left, b2);
192 b3 = l.first;
193 if (b3 == -1) return {-1, {}};
194 dtau_l = double(tree.max_key(n->left) - n->key);
195 auto dim = M.shape()[0]; // same as get_block_dim(b1);
196 for (int i = 0; i < dim; ++i) M(i, _) *= std::exp(-dtau_l * get_block_eigenval(b2, i));
197 if ((l.second.shape()[0] == 1) && (l.second.shape()[1] == 1))
198 M *= l.second(0, 0);
199 else
200 M = l.second * M;
202
203 if (updating) {
204 n->cache.matrices[b] = M;
205 n->cache.matrix_norm_valid[b] = true;
206
207 // improve the norm if calculating the full_trace
208 if (use_norm_of_matrices_in_cache) { // seems slower
209 auto norm = frobenius_norm(M);
210 if (std::abs(norm - frobenius_norm2(M)) > 1.e-12) TRIQS_RUNTIME_ERROR << " FROB PB" << M;
211 //if (norm < frobenius_norm2(M)) TRIQS_RUNTIME_ERROR << " FROB PB";
212 //if (norm < frobenius_norm2(M)) std::cout <<norm <<" vs "<< frobenius_norm2(M)<<std::endl;// TRIQS_RUNTIME_ERROR << " FROB PB";
213 n->cache.matrix_lnorms[b] = -std::log(norm);
214 if (!isfinite(-std::log(norm))) { n->cache.matrix_lnorms[b] = double_max; }
215 }
217
218 return {b3, std::move(M)};
219 }
220
221 // ------- Update the cache -----------------------
222
223 void impurity_trace::update_cache() { update_cache_impl(tree.get_root()); }
224
225 // --------------------------------
226
227 void impurity_trace::update_cache_impl(node n) {
228
229 if ((n == nullptr) || (!n->modified)) return;
230 if (n->delete_flag) TRIQS_RUNTIME_ERROR << " Internal Error: node flagged for deletion in cache update ";
231 update_cache_impl(n->left);
232 update_cache_impl(n->right);
233 n->cache.dtau_r = (n->right ? double(n->key - tree.min_key(n->right)) : 0);
234 n->cache.dtau_l = (n->left ? double(tree.max_key(n->left) - n->key) : 0);
235 for (int b = 0; b < n_blocks; ++b) {
236 auto r = compute_block_table_and_bound(n, b, double_max, false);
237 n->cache.block_table[b] = r.first;
238 n->cache.matrix_lnorms[b] = r.second;
239 n->cache.matrix_norm_valid[b] = false;
240 }
241 // This is not necessary here as all modified nodes are "cleared"
242 // by tree::clear_modified in the try/cancel/confirm set
243 // n->modified = false;
244 }
245
246 // -------- Calculate the dtau for a given node to its left and right neighbours ----------------
247 void impurity_trace::update_dtau(node n) {
248 if ((n == nullptr) || (!n->modified)) return;
249 update_dtau(n->left);
250 update_dtau(n->right);
251 n->cache.dtau_r = (n->right ? double(n->key - tree.min_key(n->right)) : 0);
252 n->cache.dtau_l = (n->left ? double(tree.max_key(n->left) - n->key) : 0);
253 }
254
255 //-------- Compute the full trace ------------------------------------------
256 // Returns MC atomic weight and reweighting = trace/(atomic weight)
257 std::pair<h_scalar_t, h_scalar_t> impurity_trace::compute(double p_yee, double u_yee) {
258
259 double epsilon = 1.e-15; // Machine precision
260 auto log_epsilon0 = -std::log(1.e-15);
261 double lnorm_threshold = double_max - 100;
262 std::vector<std::pair<double, int>> init_to_sort_lnorm_b, to_sort_lnorm_b; // pairs of lnorm and b to sort in order of bound
263
264 // simplifies later code
265 if (tree_size == 0) {
266 if (use_norm_as_weight) {
267 density_matrix = atomic_rho;
268 return {atomic_norm, atomic_z / atomic_norm};
269 } else
270 return {atomic_z, 1};
271 }
272
273 auto root = tree.get_root();
274 // beta - tmax + tmin ! the tree is in REVERSE order
275 double dtau_beta = beta - tree.min_key();
276 double dtau_0 = double(tree.max_key());
277 double dtau = dtau_beta + dtau_0;
278
279 //FIXME
280 // #ifdef EXT_DEBUG
281 // std::cout << " Trace computed ---------------" << std::endl;
282 // tree.print(std::cout);
283 // std::cout << "dtau = " << dtau << std::endl;
284 // std::cout << *config << std::endl;
285 // tree.graphviz(std::ofstream("tree_start_compute_trace"));
286 // #endif
287
288 update_dtau(root); // recompute the dtau for modified nodes
289
290 for (int b = 0; b < n_blocks; ++b) {
291 auto block_lnorm_pair = compute_block_table_and_bound(root, b, lnorm_threshold);
292
293 // Check that the final block is the same as the initial block or -1, indicating structural cancellation
294 // This guarantees that the density matrix is blockwise diagonal (otherwise the code will have thrown an error).
295 if (measure_density_matrix) {
296 static bool first_warning_issued = false;
297 if ((not first_warning_issued) and (block_lnorm_pair.first != b) && (block_lnorm_pair.first != -1)) {
298 first_warning_issued = true;
299 mpi::communicator world;
300 if (world.rank() == 0)
301 std::cerr << "\nWARNING: The product of atomic operators has a matrix element in the off-diagonal blocks!!! \n"
302 << "You will not be able to use the measured density matrix to calculate expectations values\n"
303 "of operators that do not commute with the local Hamiltonian!\n"
304 << std::endl;
305 }
306 }
307
308 if (block_lnorm_pair.first == b) { // final structural check B ---> returns to B.
309 double lnorm = block_lnorm_pair.second + dtau * get_block_emin(b);
310 lnorm_threshold = std::min(lnorm_threshold, lnorm + log_epsilon0);
311 init_to_sort_lnorm_b.emplace_back(lnorm, b);
312 }
313 }
314
315 // recut since lnorm_threshold evolved in the previous loop
316 for (auto const &b_b : init_to_sort_lnorm_b)
317 if (b_b.first <= lnorm_threshold) to_sort_lnorm_b.push_back(b_b);
318
319 if (histo) histo->n_block_at_root << to_sort_lnorm_b.size();
320
321 if (to_sort_lnorm_b.size() == 0) return {0.0, 1}; // structural 0
322
323 // Now sort the blocks non structurally 0 according to the bound
324 std::sort(to_sort_lnorm_b.begin(), to_sort_lnorm_b.end());
325
326 // Prepare to loop over all blocks (in sorted order).
327 // According to estimator, truncate as epsilon.
328 h_scalar_t full_trace = 0, first_term = 0;
329 double norm_trace_sq = 0, trace_abs = 0;
330
331 // Put density_matrix to "not recomputed"
332 for (int bl = 0; bl < n_blocks; ++bl) density_matrix[bl].is_valid = false;
333
334 auto trace_contrib_block = std::vector<std::pair<double, int>>{}; //FIXME complex -- can histos handle this?
335
336 int n_bl = to_sort_lnorm_b.size(); // number of blocks
337 auto bound_cumul = std::vector<double>(n_bl + 1); // cumulative sum of the bounds
338 // The contribution to the trace from block B is bounded: |Tr_B| <= dim(B) * sum_{B} e^{Emin(B)*dtau}
339 // Here we calculate the cumulative bound from each contributing (structurally non-zero) block to
340 // determine at which block we have exceeded the bound and hence can stop.
341 // Can tighten bound on trace by using sqrt(dim(B)) in the case of Frobenius norm only.
342 bound_cumul[n_bl] = 0;
343 if (!use_norm_as_weight) {
344 for (int bl = n_bl - 1; bl >= 0; --bl)
345 bound_cumul[bl] = bound_cumul[bl + 1] + std::exp(-to_sort_lnorm_b[bl].first) * get_block_dim(to_sort_lnorm_b[bl].second);
346 } else {
347 for (int bl = n_bl - 1; bl >= 0; --bl) bound_cumul[bl] = bound_cumul[bl + 1] + std::exp(-to_sort_lnorm_b[bl].first) * std::sqrt(get_block_dim(to_sort_lnorm_b[bl].second));
348 }
349
350 int bl;
351 for (bl = 0; bl < n_bl; ++bl) { // sum over all blocks
352
353 // stopping criterion
354 if ((bl > 0) && (bound_cumul[bl] <= std::abs(full_trace) * epsilon)) break;
355
356 int block_index = to_sort_lnorm_b[bl].second; // index in original (unsorted) order
357
358 // additionnal Yee quick return criterion
359 if (p_yee >= 0.0) {
360 auto current_weight = (use_norm_as_weight ? std::sqrt(norm_trace_sq) : full_trace);
361 auto pmax = std::abs(p_yee) * (std::abs(current_weight) + bound_cumul[bl]);
362 if (pmax < u_yee) return {0, 1}; // pmax < u, we can reject
363 }
364
365 // computes the matrices, recursively along the modified path in the tree
366 auto b_mat = compute_matrix(root, block_index); // b_mat = {block that b connects to, matrix for this block}
367 if (b_mat.first == -1) TRIQS_RUNTIME_ERROR << " Internal error : B = -1 after compute matrix : " << block_index;
368
369#ifdef TRACE_CHECK_AGAINST_LINEAR_COMPUTATION
370 auto b_mat2 = check_one_block_matrix_linear(root, block_index);
371 if (max_element(abs(b_mat.second - b_mat2)) > 1.e-10) TRIQS_RUNTIME_ERROR << " Matrix failed against linear computation";
372#endif
373
374 // trace(mat * exp(- H * (beta - tmax)) * exp (- H * tmin)) to handle the piece outside of the first-last operators.
375 h_scalar_t trace_partial = 0;
376 auto dim = get_block_dim(block_index);
377 for (int u = 0; u < dim; ++u) {
378 auto x = b_mat.second(u, u) * std::exp(-dtau * get_block_eigenval(block_index, u));
379 trace_partial += x;
380 trace_abs += std::abs(x);
381 }
382
383 if (use_norm_as_weight) { // else we are not allowed to compute this matrix, may make no sense
384 // recompute the density matrix
385 density_matrix[block_index].is_valid = true;
386 double norm_trace_sq_partial = 0;
387 auto &mat = density_matrix[block_index].mat;
388 for (int u = 0; u < dim; ++u) {
389 for (int v = 0; v < dim; ++v) {
390 mat(u, v) = b_mat.second(u, v) * std::exp(-dtau_beta * get_block_eigenval(block_index, u) - dtau_0 * get_block_eigenval(block_index, v));
391 double xx = std::abs(mat(u, v));
392 norm_trace_sq_partial += xx * xx;
393 }
394 }
395 norm_trace_sq += norm_trace_sq_partial;
396 // internal check
397 if (std::abs(trace_partial) - 1.0000001 * std::sqrt(norm_trace_sq_partial) * get_block_dim(block_index) > 1.e-15)
398 TRIQS_RUNTIME_ERROR << "|trace| > dim * norm" << trace_partial << " " << std::sqrt(norm_trace_sq_partial) << " " << trace_abs;
399 auto dev = std::abs(trace_partial - trace(mat));
400 if (dev > 1.e-14) TRIQS_RUNTIME_ERROR << "Internal error : trace and density mismatch. Deviation: " << dev;
401 }
402
403#ifdef TRACE_CHECK_MATRIX_BOUNDED_BY_BOUND
404 if (std::abs(trace_partial) > 1.000001 * dim * std::exp(-to_sort_lnorm_b[bl].first))
405 TRIQS_RUNTIME_ERROR << "Matrix not bounded by the bound ! test is " << std::abs(trace_partial) << " < "
406 << dim * std::exp(-to_sort_lnorm_b[bl].first);
407#endif
408
409 full_trace += trace_partial; // sum for all blocks
410
411 // Analysis
412 if (histo) {
413 histo->trace_over_bound << std::abs(trace_partial) / std::exp(-to_sort_lnorm_b[bl].first);
414 trace_contrib_block.emplace_back(std::abs(trace_partial), block_index);
415 if (bl == 1) {
416 first_term = trace_partial;
417 histo->dominant_block_bound << block_index;
418 histo->dominant_block_energy_bound << get_block_emin(block_index);
419 } else if (first_term != 0.0) {
420 histo->trace_first_over_sec_term << real(trace_partial / first_term);
421 }
422 }
423 } // loop on block
424
425 double norm_trace = std::sqrt(norm_trace_sq);
426 if (!isfinite(full_trace)) TRIQS_RUNTIME_ERROR << " full_trace not finite" << full_trace;
427
428 // Analysis
429 if (histo && norm_trace != 0.0) {
430 histo->trace_over_norm << std::abs(full_trace) / norm_trace;
431 histo->trace_abs_over_norm << trace_abs / norm_trace;
432 histo->trace_over_trace_abs << real(full_trace / trace_abs);
433 std::sort(trace_contrib_block.begin(), trace_contrib_block.end(), std::greater<>());
434 histo->dominant_block_trace << begin(trace_contrib_block)->second;
435 histo->dominant_block_energy_trace << get_block_emin(begin(trace_contrib_block)->second);
436 histo->n_block_kept << bl;
437 histo->trace_first_term_trace << std::abs(first_term) / std::abs(full_trace);
438 }
439
440 // return {weight, reweighting}
441 if (!use_norm_as_weight) return {full_trace, 1};
442 // else determine reweighting
443 auto rw = full_trace / norm_trace;
444 if (!isfinite(rw)) rw = 1;
445 //FIXME if (!isfinite(rw)) TRIQS_RUNTIME_ERROR << "Atomic correlators : reweight not finite" << full_trace << " "<< norm_trace;
446 return {norm_trace, rw};
447 }
448
450 std::ostream &operator<<(std::ostream &out, impurity_trace const &imp_trace) {
451 out << "Impurity trace: size = " << imp_trace.tree_size << "\n";
452 out << "Trial nodes: index = " << imp_trace.trial_nodes.index() << "\n";
453 imp_trace.tree.graphviz(out);
454 return out;
455 }
456
457// code for check/debug
458#include "./impurity_trace.checks.cpp"
459
460} // namespace triqs_cthyb
bool use_norm_as_weight
Use the norm of the density matrix in the weight (instead of the trace)?