Skip to content

Commit 27a8873

Browse files
committed
Add single-particle Worm and partition improved estimators
1 parent fe3a94a commit 27a8873

37 files changed

Lines changed: 2376 additions & 219 deletions

c++/triqs_cthyb/container_set.cpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,12 @@ namespace triqs_cthyb {
3333
h5_write(grp, "asymmetry_G_tau", c.asymmetry_G_tau);
3434
h5_write(grp, "G_l", c.G_l);
3535
h5_write(grp, "O_tau", c.O_tau);
36+
h5_write(grp, "F_tau", c.F_tau);
37+
h5_write(grp, "F_tau_accum", c.F_tau_accum);
38+
h5_write(grp, "F_tau_partition", c.F_tau_partition);
39+
h5_write(grp, "F_tau_partition_accum", c.F_tau_partition_accum);
40+
h5_write(grp, "F_l_worm", c.F_l_worm);
41+
h5_write(grp, "F_l_partition", c.F_l_partition);
3642
h5_write(grp, "perturbation_order", c.perturbation_order);
3743
h5_write(grp, "perturbation_order_total", c.perturbation_order_total);
3844

@@ -57,6 +63,12 @@ namespace triqs_cthyb {
5763
h5_read(grp, "asymmetry_G_tau", c.asymmetry_G_tau);
5864
h5_read(grp, "G_l", c.G_l);
5965
h5::try_read(grp, "O_tau", c.O_tau);
66+
h5::try_read(grp, "F_tau", c.F_tau);
67+
h5::try_read(grp, "F_tau_accum", c.F_tau_accum);
68+
h5::try_read(grp, "F_tau_partition", c.F_tau_partition);
69+
h5::try_read(grp, "F_tau_partition_accum", c.F_tau_partition_accum);
70+
h5::try_read(grp, "F_l_worm", c.F_l_worm);
71+
h5::try_read(grp, "F_l_partition", c.F_l_partition);
6072
h5::try_read(grp, "perturbation_order", c.perturbation_order);
6173
h5::try_read(grp, "perturbation_order_total", c.perturbation_order_total);
6274

c++/triqs_cthyb/container_set.hpp

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,24 @@ namespace triqs_cthyb {
4747
/// General operator Green's function \f$ O(\tau) \f$ in imaginary time.
4848
std::optional<gf<imtime, scalar_valued>> O_tau;
4949

50+
/// Improved-estimator correlator \f$ F(\tau) = \langle T Q(\tau)c^\dagger(0)\rangle \f$ with \f$Q=[H_{int},c]\f$.
51+
std::optional<G_tau_t> F_tau;
52+
53+
/// Intermediate Green's function used to accumulate \f$ F(\tau) \f$ (real or complex).
54+
std::optional<G_tau_G_target_t> F_tau_accum;
55+
56+
/// Z-sector partition-space estimator of \f$ F(\tau) = \langle T Q(\tau)c^\dagger(0)\rangle \f$.
57+
std::optional<G_tau_t> F_tau_partition;
58+
59+
/// Intermediate Green's function used to accumulate the Z-sector partition-space \f$ F(\tau) \f$ estimator.
60+
std::optional<G_tau_G_target_t> F_tau_partition_accum;
61+
62+
/// Improved-estimator correlator \f$ F_l \f$ in the Legendre representation, measured with worm sampling.
63+
std::optional<G_l_t> F_l_worm;
64+
65+
/// Z-sector partition-space estimator of \f$ F_l \f$ in the Legendre representation.
66+
std::optional<G_l_t> F_l_partition;
67+
5068
// -- Two-particle Green's functions
5169

5270
/// Two-particle Green's function \f$ G^{(2)}(\tau_1,\tau_2,\tau_3) \f$ with three fermionic times.

c++/triqs_cthyb/impurity_trace.cpp

Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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 &current = 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

Comments
 (0)