@@ -78,6 +78,210 @@ namespace triqs_cthyb {
7878 }
7979 }
8080
81+ // -------- Attach an auxiliary operator --------
82+ // The trace bound assumes operator matrices of norm <= 1 (true for c/c_dag).
83+ // Aux operators (e.g. Q = [H_int, c]) can have larger norms; record them so that
84+ // compute_block_table_and_bound keeps returning a true upper bound.
85+ op_desc impurity_trace::attach_aux_operator (many_body_op_t const &op) {
86+ aux_operators.push_back (h_diag->get_op_mat (op));
87+ auto const &op_mat = aux_operators.back ();
88+
89+ std::vector<double > log_norms (n_blocks, 0.0 );
90+ for (int b = 0 ; b < n_blocks; ++b) {
91+ if (op_mat.connection (b) < 0 ) continue ;
92+ log_norms[b] = std::log (frobenius_norm2 (op_mat.block_mat [b]));
93+ }
94+ aux_log_norms.push_back (std::move (log_norms));
95+
96+ op_desc operator_desc{-1 , 0 , true , -static_cast <int >(aux_operators.size ())};
97+ return operator_desc;
98+ }
99+
100+ std::vector<h_scalar_t >
101+ impurity_trace::compute_single_replacement_traces (std::span<single_replacement_request const > requests) const {
102+ last_replacement_path_counts = {};
103+ if (requests.empty ()) return {};
104+ auto _ = arrays::range::all;
105+
106+ if (!trial_nodes.is_index_reset () || !removed_nodes.empty () || !backup_nodes.is_index_reset () || tree_size != tree.size ())
107+ TRIQS_RUNTIME_ERROR << " impurity_trace: compute_single_replacement_traces() requires a committed configuration" ;
108+
109+ std::vector<std::size_t > request_order;
110+ request_order.reserve (requests.size ());
111+ for (std::size_t i = 0 ; i < requests.size (); ++i) {
112+ auto const &[key, replacement] = requests[i];
113+ if (replacement.linear_index >= n_orbitals || replacement.linear_index < -static_cast <long >(aux_operators.size ()))
114+ TRIQS_RUNTIME_ERROR << " impurity_trace: invalid replacement operator at tau = " << key;
115+ request_order.push_back (i);
116+ }
117+ std::sort (request_order.begin (), request_order.end (), [&](std::size_t lhs, std::size_t rhs) { return requests[lhs].key < requests[rhs].key ; });
118+ for (std::size_t i = 1 ; i < request_order.size (); ++i)
119+ if (requests[request_order[i - 1 ]].key == requests[request_order[i]].key )
120+ TRIQS_RUNTIME_ERROR << " impurity_trace: duplicate replacement request at tau = " << requests[request_order[i]].key ;
121+
122+ std::vector<node> nodes;
123+ nodes.reserve (tree.size ());
124+ foreach_reverse (tree, [&](node n) {
125+ if (n->modified || n->delete_flag )
126+ TRIQS_RUNTIME_ERROR << " impurity_trace: compute_single_replacement_traces() found an uncommitted tree node" ;
127+ nodes.push_back (n);
128+ }); // numeric tau order: 0 -> beta
129+
130+ auto const n_nodes = nodes.size ();
131+ std::vector<std::size_t > request_at_node (n_nodes, requests.size ());
132+ for (auto request_index : request_order) {
133+ auto const key = requests[request_index].key ;
134+ auto node_it = std::lower_bound (nodes.begin (), nodes.end (), key, [](node n, time_pt const &candidate) { return n->key < candidate; });
135+ if (node_it == nodes.end () || (*node_it)->key != key)
136+ TRIQS_RUNTIME_ERROR << " No operator at tau = " << key << " in impurity_trace::compute_single_replacement_traces" ;
137+ request_at_node[std::distance (nodes.begin (), node_it)] = request_index;
138+ }
139+
140+ replacement_suffix_scratch.resize (n_nodes);
141+ for (auto &by_block : replacement_suffix_scratch) {
142+ by_block.resize (n_blocks);
143+ for (auto &product : by_block) product.output_block = -1 ;
144+ }
145+ auto &suffix = replacement_suffix_scratch;
146+
147+ auto const last_tau = double (nodes.back ()->key );
148+ for (int b = 0 ; b < n_blocks; ++b) {
149+ auto dim = get_block_dim (b);
150+ suffix.back ()[b].output_block = b;
151+ suffix.back ()[b].matrix = nda::eye<h_scalar_t >(dim);
152+ for (int u = 0 ; u < dim; ++u) suffix.back ()[b].matrix (u, u) *= std::exp (-(beta - last_tau) * get_block_eigenval (b, u));
153+ }
154+
155+ for (std::size_t j = n_nodes - 1 ; j-- > 0 ;) {
156+ auto const &next_op = nodes[j + 1 ]->op ;
157+ double dtau = double (nodes[j + 1 ]->key - nodes[j]->key );
158+ for (int b = 0 ; b < n_blocks; ++b) {
159+ int b_after_op = get_op_block_map (next_op, b);
160+ if (b_after_op < 0 ) continue ;
161+ auto const &tail = suffix[j + 1 ][b_after_op];
162+ if (tail.output_block < 0 ) continue ;
163+
164+ matrix_t op_with_evolution = get_op_block_matrix (next_op, b);
165+ for (int u = 0 ; u < get_block_dim (b); ++u)
166+ op_with_evolution (_, u) *= std::exp (-dtau * get_block_eigenval (b, u));
167+ suffix[j][b].output_block = tail.output_block ;
168+ suffix[j][b].matrix = tail.matrix * op_with_evolution;
169+ }
170+ }
171+
172+ replacement_prefix_scratch.resize (n_blocks);
173+ replacement_next_prefix_scratch.resize (n_blocks);
174+ for (auto &product : replacement_prefix_scratch) product.output_block = -1 ;
175+ auto &prefix = replacement_prefix_scratch;
176+ auto const first_tau = double (nodes.front ()->key );
177+ for (int b = 0 ; b < n_blocks; ++b) {
178+ auto dim = get_block_dim (b);
179+ prefix[b].output_block = b;
180+ prefix[b].matrix = nda::eye<h_scalar_t >(dim);
181+ for (int u = 0 ; u < dim; ++u) prefix[b].matrix (u, u) *= std::exp (-first_tau * get_block_eigenval (b, u));
182+ }
183+
184+ std::vector<h_scalar_t > committed_block_traces (n_blocks, h_scalar_t {0 });
185+ auto const &first_op = nodes.front ()->op ;
186+ for (int initial_block = 0 ; initial_block < n_blocks; ++initial_block) {
187+ auto const &left = prefix[initial_block];
188+ int original_output = get_op_block_map (first_op, left.output_block );
189+ if (original_output < 0 ) continue ;
190+ auto const &right = suffix.front ()[original_output];
191+ if (right.output_block != initial_block) continue ;
192+ matrix_t product = right.matrix * get_op_block_matrix (first_op, left.output_block ) * left.matrix ;
193+ for (int u = 0 ; u < get_block_dim (initial_block); ++u) committed_block_traces[initial_block] += product (u, u);
194+ }
195+
196+ std::vector<h_scalar_t > result (requests.size (), h_scalar_t {0 });
197+ for (std::size_t j = 0 ; j < n_nodes; ++j) {
198+ auto request_index = request_at_node[j];
199+ if (request_index != requests.size ()) {
200+ auto const &replacement = requests[request_index].replacement ;
201+ auto const &original = nodes[j]->op ;
202+ h_scalar_t replacement_trace{0 };
203+ for (int initial_block = 0 ; initial_block < n_blocks; ++initial_block) {
204+ auto const &left = prefix[initial_block];
205+ if (left.output_block < 0 ) {
206+ ++last_replacement_path_counts.structural_zero ;
207+ continue ;
208+ }
209+ int replacement_output = get_op_block_map (replacement, left.output_block );
210+ if (replacement_output < 0 ) {
211+ ++last_replacement_path_counts.structural_zero ;
212+ continue ;
213+ }
214+ auto const &right = suffix[j][replacement_output];
215+ if (right.output_block != initial_block) {
216+ ++last_replacement_path_counts.structural_zero ;
217+ continue ;
218+ }
219+
220+ bool is_proportional = get_op_block_map (original, left.output_block ) == replacement_output;
221+ h_scalar_t scale{0 };
222+ if (is_proportional) {
223+ auto const &original_matrix = get_op_block_matrix (original, left.output_block );
224+ auto const &replacement_matrix = get_op_block_matrix (replacement, left.output_block );
225+ double largest_original = 0 ;
226+ int pivot_row = 0 , pivot_col = 0 ;
227+ for (int row = 0 ; row < original_matrix.shape ()[0 ]; ++row)
228+ for (int col = 0 ; col < original_matrix.shape ()[1 ]; ++col)
229+ if (auto magnitude = std::abs (original_matrix (row, col)); magnitude > largest_original) {
230+ largest_original = magnitude;
231+ pivot_row = row;
232+ pivot_col = col;
233+ }
234+ if (largest_original == 0 ) {
235+ for (int row = 0 ; row < replacement_matrix.shape ()[0 ]; ++row)
236+ for (int col = 0 ; col < replacement_matrix.shape ()[1 ]; ++col)
237+ is_proportional = is_proportional && std::abs (replacement_matrix (row, col)) == 0 ;
238+ } else {
239+ scale = replacement_matrix (pivot_row, pivot_col) / original_matrix (pivot_row, pivot_col);
240+ double scale_reference = std::max (1.0 , std::abs (scale) * largest_original);
241+ for (int row = 0 ; row < original_matrix.shape ()[0 ]; ++row)
242+ for (int col = 0 ; col < original_matrix.shape ()[1 ]; ++col)
243+ is_proportional = is_proportional &&
244+ std::abs (replacement_matrix (row, col) - scale * original_matrix (row, col)) <= 1 .e -13 * scale_reference;
245+ }
246+ }
247+
248+ if (is_proportional) {
249+ ++last_replacement_path_counts.proportional ;
250+ replacement_trace += scale * committed_block_traces[initial_block];
251+ } else {
252+ ++last_replacement_path_counts.general ;
253+ matrix_t product = right.matrix * get_op_block_matrix (replacement, left.output_block ) * left.matrix ;
254+ for (int u = 0 ; u < get_block_dim (initial_block); ++u) replacement_trace += product (u, u);
255+ }
256+ }
257+ if (!isfinite (replacement_trace))
258+ TRIQS_RUNTIME_ERROR << " impurity_trace: non-finite single-replacement trace at tau = " << requests[request_index].key ;
259+ result[request_index] = replacement_trace;
260+ }
261+
262+ if (j + 1 == n_nodes) continue ;
263+ auto const &op = nodes[j]->op ;
264+ double dtau = double (nodes[j + 1 ]->key - nodes[j]->key );
265+ auto &next_prefix = replacement_next_prefix_scratch;
266+ for (auto &product : next_prefix) product.output_block = -1 ;
267+ for (int initial_block = 0 ; initial_block < n_blocks; ++initial_block) {
268+ auto const ¤t = prefix[initial_block];
269+ if (current.output_block < 0 ) continue ;
270+ int b_after_op = get_op_block_map (op, current.output_block );
271+ if (b_after_op < 0 ) continue ;
272+
273+ matrix_t evolved = get_op_block_matrix (op, current.output_block ) * current.matrix ;
274+ for (int u = 0 ; u < get_block_dim (b_after_op); ++u)
275+ evolved (u, _) *= std::exp (-dtau * get_block_eigenval (b_after_op, u));
276+ next_prefix[initial_block].output_block = b_after_op;
277+ next_prefix[initial_block].matrix = std::move (evolved);
278+ }
279+ prefix.swap (next_prefix);
280+ }
281+
282+ return result;
283+ }
284+
81285 // ====== Recursive operations ======
82286
83287 // For all recursive operations, the cache on the current node is updated as follows:
@@ -133,6 +337,7 @@ namespace triqs_cthyb {
133337
134338 int b2 = (n->delete_flag ? b1 : get_op_block_map (n, b1));
135339 if (b2 < 0 ) return {b2, 0 };
340+ if (!n->delete_flag && n->op .linear_index < 0 ) lnorm -= aux_log_norms[-n->op .linear_index - 1 ][b1];
136341
137342 int b3 = b2;
138343 if (n->left ) {
0 commit comments