Loading [MathJax]/extensions/TeX/AMSmath.js
ESPResSo
Extensible Simulation Package for Research on Soft Matter Systems
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages Concepts
Correlator.cpp
Go to the documentation of this file.
1/*
2 * Copyright (C) 2010-2022 The ESPResSo project
3 *
4 * This file is part of ESPResSo.
5 *
6 * ESPResSo 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 * ESPResSo 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 should have received a copy of the GNU General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 */
19#include "Correlator.hpp"
20
21#include <utils/Vector.hpp>
22#include <utils/math/sqr.hpp>
24
25#include <boost/archive/binary_iarchive.hpp>
26#include <boost/archive/binary_oarchive.hpp>
27#include <boost/iostreams/device/array.hpp>
28#include <boost/iostreams/stream.hpp>
29#include <boost/serialization/string.hpp>
30#include <boost/serialization/vector.hpp>
31
32#include <algorithm>
33#include <array>
34#include <cassert>
35#include <cmath>
36#include <cstddef>
37#include <functional>
38#include <numeric>
39#include <sstream>
40#include <stdexcept>
41#include <string>
42#include <vector>
43
44namespace {
45int min(int i, unsigned int j) { return std::min(i, static_cast<int>(j)); }
46} // namespace
47
48namespace Accumulators {
49/** Compress computing arithmetic mean: A_compressed=(A1+A2)/2 */
50std::vector<double> compress_linear(std::vector<double> const &A1,
51 std::vector<double> const &A2) {
52 assert(A1.size() == A2.size());
53 std::vector<double> A_compressed(A1.size());
54
55 std::ranges::transform(A1, A2, A_compressed.begin(),
56 [](double a, double b) { return 0.5 * (a + b); });
57
58 return A_compressed;
59}
60
61/** Compress discarding the 1st argument and return the 2nd */
62std::vector<double> compress_discard1(std::vector<double> const &A1,
63 std::vector<double> const &A2) {
64 assert(A1.size() == A2.size());
65 std::vector<double> A_compressed(A2);
66 return A_compressed;
67}
68
69/** Compress discarding the 2nd argument and return the 1st */
70std::vector<double> compress_discard2(std::vector<double> const &A1,
71 std::vector<double> const &A2) {
72 assert(A1.size() == A2.size());
73 std::vector<double> A_compressed(A1);
74 return A_compressed;
75}
76
77std::vector<double> scalar_product(std::vector<double> const &A,
78 std::vector<double> const &B,
79 Utils::Vector3d const &) {
80 if (A.size() != B.size()) {
81 throw std::runtime_error(
82 "Error in scalar product: The vector sizes do not match");
83 }
84
85 auto const result = std::inner_product(A.begin(), A.end(), B.begin(), 0.0);
86 return {result};
87}
88
89std::vector<double> componentwise_product(std::vector<double> const &A,
90 std::vector<double> const &B,
91 Utils::Vector3d const &) {
92 std::vector<double> C(A.size());
93 if (A.size() != B.size()) {
94 throw std::runtime_error(
95 "Error in componentwise product: The vector sizes do not match");
96 }
97
98 std::ranges::transform(A, B, C.begin(), std::multiplies<>());
99
100 return C;
101}
102
103std::vector<double> tensor_product(std::vector<double> const &A,
104 std::vector<double> const &B,
105 Utils::Vector3d const &) {
106 std::vector<double> C(A.size() * B.size());
107 auto C_it = C.begin();
108
109 for (double a : A) {
110 for (double b : B) {
111 *(C_it++) = a * b;
112 }
113 }
114
115 return C;
116}
117
118std::vector<double> square_distance_componentwise(std::vector<double> const &A,
119 std::vector<double> const &B,
120 Utils::Vector3d const &) {
121 if (A.size() != B.size()) {
122 throw std::runtime_error(
123 "Error in square distance componentwise: The vector sizes do not "
124 "match.");
125 }
126
127 std::vector<double> C(A.size());
128
129 std::ranges::transform(A, B, C.begin(), [](double a, double b) -> double {
130 return Utils::sqr(a - b);
131 });
132
133 return C;
134}
135
136// note: the argument name wsquare denotes that its value is w^2 while the user
137// sets w
138std::vector<double> fcs_acf(std::vector<double> const &A,
139 std::vector<double> const &B,
140 Utils::Vector3d const &wsquare) {
141 if (A.size() != B.size()) {
142 throw std::runtime_error(
143 "Error in fcs_acf: The vector sizes do not match.");
144 }
145
146 auto const C_size = A.size() / 3u;
147 assert(3u * C_size == A.size());
148
149 std::vector<double> C{};
150 C.reserve(C_size);
151
152 for (std::size_t i = 0u; i < C_size; i++) {
153 auto acc = 0.;
154 for (std::size_t j = 0u; j < 3u; j++) {
155 auto const a = A[3u * i + j];
156 auto const b = B[3u * i + j];
157 acc -= Utils::sqr(a - b) / wsquare[j];
158 }
159 C.emplace_back(std::exp(acc));
160 }
161
162 return C;
163}
164
165void Correlator::initialize_operations() {
166 // Class members are assigned via the initializer list
167
168 if (m_tau_lin == 1) { // use the default
169 m_tau_lin = static_cast<int>(std::ceil(m_tau_max / m_dt));
170 m_tau_lin += m_tau_lin % 2;
171 }
172
173 if (m_tau_lin < 2) {
174 throw std::runtime_error("tau_lin must be >= 2");
175 }
176
177 if (m_tau_lin % 2) {
178 throw std::runtime_error("tau_lin must be divisible by 2");
179 }
180
181 if (m_tau_max <= m_dt) {
182 throw std::runtime_error("tau_max must be >= delta_t (delta_N too large)");
183 }
184 // set hierarchy depth which can accommodate at least m_tau_max
185 if ((m_tau_max / m_dt) < m_tau_lin) {
186 m_hierarchy_depth = 1;
187 } else {
188 auto const operand = (m_tau_max / m_dt) / double(m_tau_lin - 1);
189 assert(operand > 0.);
190 m_hierarchy_depth = static_cast<int>(std::ceil(1. + std::log2(operand)));
191 }
192
193 assert(A_obs);
194 assert(B_obs);
195 dim_A = A_obs->n_values();
196 dim_B = B_obs->n_values();
197
198 if (dim_A == 0u) {
199 throw std::runtime_error("dimension of first observable has to be >= 1");
200 }
201 if (dim_B == 0u) {
202 throw std::runtime_error("dimension of second observable has to be >= 1");
203 }
204
205 // choose the correlation operation
206 if (corr_operation_name == "componentwise_product") {
207 m_dim_corr = dim_A;
208 m_shape = A_obs->shape();
209 corr_operation = &componentwise_product;
210 m_correlation_args = Utils::Vector3d{0, 0, 0};
211 } else if (corr_operation_name == "tensor_product") {
212 m_dim_corr = dim_A * dim_B;
213 m_shape.clear();
214 m_shape.emplace_back(dim_A);
215 m_shape.emplace_back(dim_B);
216 corr_operation = &tensor_product;
217 m_correlation_args = Utils::Vector3d{0, 0, 0};
218 } else if (corr_operation_name == "square_distance_componentwise") {
219 m_dim_corr = dim_A;
220 m_shape = A_obs->shape();
221 corr_operation = &square_distance_componentwise;
222 m_correlation_args = Utils::Vector3d{0, 0, 0};
223 } else if (corr_operation_name == "fcs_acf") {
224 // note: user provides w=(wx,wy,wz) but we want to use
225 // wsquare=(wx^2,wy^2,wz^2)
226 if (not(m_correlation_args_input > Utils::Vector3d::broadcast(0.))) {
227 throw std::runtime_error("missing parameter for fcs_acf: w_x w_y w_z");
228 }
229 m_correlation_args = Utils::hadamard_product(m_correlation_args_input,
230 m_correlation_args_input);
231 if (dim_A % 3u)
232 throw std::runtime_error("dimA must be divisible by 3 for fcs_acf");
233 m_dim_corr = dim_A / 3u;
234 m_shape = A_obs->shape();
235 if (m_shape.back() != 3u)
236 throw std::runtime_error(
237 "the last dimension of dimA must be 3 for fcs_acf");
238 m_shape.pop_back();
239 corr_operation = &fcs_acf;
240 } else if (corr_operation_name == "scalar_product") {
241 m_dim_corr = 1u;
242 m_shape.clear();
243 m_shape.emplace_back(1u);
244 corr_operation = &scalar_product;
245 m_correlation_args = Utils::Vector3d{0, 0, 0};
246 } else {
247 throw std::invalid_argument("correlation operation '" +
248 corr_operation_name + "' not implemented");
249 }
250
251 // Choose the compression function
252 if (compressA_name == "discard2") {
253 compressA = &compress_discard2;
254 } else if (compressA_name == "discard1") {
255 compressA = &compress_discard1;
256 } else if (compressA_name == "linear") {
257 compressA = &compress_linear;
258 } else {
259 throw std::invalid_argument("unknown compression method '" +
260 compressA_name + "' for first observable");
261 }
262
263 if (compressB_name == "discard2") {
264 compressB = &compress_discard2;
265 } else if (compressB_name == "discard1") {
266 compressB = &compress_discard1;
267 } else if (compressB_name == "linear") {
268 compressB = &compress_linear;
269 } else {
270 throw std::invalid_argument("unknown compression method '" +
271 compressB_name + "' for second observable");
272 }
273}
274
275void Correlator::initialize_buffers() {
276 using index_type = decltype(result)::index;
277
278 A.resize(std::array<int, 2>{{m_hierarchy_depth, m_tau_lin + 1}});
279 std::fill_n(A.data(), A.num_elements(), std::vector<double>(dim_A, 0));
280 B.resize(std::array<int, 2>{{m_hierarchy_depth, m_tau_lin + 1}});
281 std::fill_n(B.data(), B.num_elements(), std::vector<double>(dim_B, 0));
282
283 n_data = 0;
284 A_accumulated_average = std::vector<double>(dim_A, 0);
285 B_accumulated_average = std::vector<double>(dim_B, 0);
286
287 auto const n_result = n_values();
288 n_sweeps = std::vector<std::size_t>(n_result, 0);
289 n_vals = std::vector<long>(m_hierarchy_depth, 0);
290
291 result.resize(std::array<std::size_t, 2>{{n_result, m_dim_corr}});
292 for (index_type i = 0; i < static_cast<index_type>(n_result); i++) {
293 for (index_type j = 0; j < static_cast<index_type>(m_dim_corr); j++) {
294 result[i][j] = 0.;
295 }
296 }
297
298 newest = std::vector<long>(m_hierarchy_depth, m_tau_lin);
299
300 tau.resize(n_result);
301 for (int i = 0; i < m_tau_lin + 1; i++) {
302 tau[i] = i;
303 }
304
305 for (int j = 1; j < m_hierarchy_depth; j++) {
306 for (int k = 0; k < m_tau_lin / 2; k++) {
307 tau[m_tau_lin + 1 + (j - 1) * m_tau_lin / 2 + k] =
308 (k + (m_tau_lin / 2) + 1) * (1 << j);
309 }
310 }
311}
312
313void Correlator::update(boost::mpi::communicator const &comm) {
314 if (finalized) {
315 throw std::runtime_error(
316 "No data can be added after finalize() was called.");
317 }
318
319 if (comm.rank() != 0) {
320 // worker nodes just need to update the observables and exit
321 A_obs->operator()(comm);
322 if (A_obs != B_obs) {
323 B_obs->operator()(comm);
324 }
325
326 return;
327 }
328
329 // We must now go through the hierarchy and make sure there is space for the
330 // new datapoint. For every hierarchy level we have to decide if it is
331 // necessary to move something
332 int highest_level_to_compress = -1;
333
334 t++;
335
336 // Let's find out how far we have to go back in the hierarchy to make space
337 // for the new value
338 {
339 auto const max_depth = m_hierarchy_depth - 1;
340 int i = 0;
341 while (true) {
342 if (i >= max_depth or n_vals[i] <= m_tau_lin) {
343 break;
344 }
345 auto const modulo = 1 << (i + 1);
346 auto const remainder = (t - (m_tau_lin + 1) * (modulo - 1) - 1) % modulo;
347 if (remainder != 0) {
348 break;
349 }
350 highest_level_to_compress += 1;
351 i++;
352 }
353 }
354
355 // Now we know we must make space on the levels 0..highest_level_to_compress
356 // Now let's compress the data level by level.
357
358 for (int i = highest_level_to_compress; i >= 0; i--) {
359 // We increase the index indicating the newest on level i+1 by one (plus
360 // folding)
361 newest[i + 1] = (newest[i + 1] + 1) % (m_tau_lin + 1);
362 n_vals[i + 1] += 1;
363 A[i + 1][newest[i + 1]] =
364 (*compressA)(A[i][(newest[i] + 1) % (m_tau_lin + 1)],
365 A[i][(newest[i] + 2) % (m_tau_lin + 1)]);
366 B[i + 1][newest[i + 1]] =
367 (*compressB)(B[i][(newest[i] + 1) % (m_tau_lin + 1)],
368 B[i][(newest[i] + 2) % (m_tau_lin + 1)]);
369 }
370
371 newest[0] = (newest[0] + 1) % (m_tau_lin + 1);
372 n_vals[0]++;
373
374 A[0][newest[0]] = A_obs->operator()(comm);
375 if (A_obs != B_obs) {
376 B[0][newest[0]] = B_obs->operator()(comm);
377 } else {
378 B[0][newest[0]] = A[0][newest[0]];
379 }
380
381 // Now we update the cumulated averages and variances of A and B
382 n_data++;
383 for (std::size_t k = 0; k < dim_A; k++) {
384 A_accumulated_average[k] += A[0][newest[0]][k];
385 }
386
387 for (std::size_t k = 0; k < dim_B; k++) {
388 B_accumulated_average[k] += B[0][newest[0]][k];
389 }
390
391 using index_type = decltype(result)::index;
392 // Now update the lowest level correlation estimates
393 for (long j = 0; j < min(m_tau_lin + 1, n_vals[0]); j++) {
394 auto const index_new = newest[0];
395 auto const index_old = (newest[0] - j + m_tau_lin + 1) % (m_tau_lin + 1);
396 auto const temp =
397 (corr_operation)(A[0][index_old], B[0][index_new], m_correlation_args);
398 assert(temp.size() == m_dim_corr);
399
400 n_sweeps[j]++;
401 for (index_type k = 0; k < static_cast<index_type>(m_dim_corr); k++) {
402 result[j][k] += temp[k];
403 }
404 }
405 // Now for the higher ones
406 for (int i = 1; i < highest_level_to_compress + 2; i++) {
407 for (long j = (m_tau_lin + 1) / 2 + 1; j < min(m_tau_lin + 1, n_vals[i]);
408 j++) {
409 auto const index_new = newest[i];
410 auto const index_old = (newest[i] - j + m_tau_lin + 1) % (m_tau_lin + 1);
411 auto const index_res =
412 m_tau_lin + (i - 1) * m_tau_lin / 2 + (j - m_tau_lin / 2 + 1) - 1;
413 auto const temp = (corr_operation)(A[i][index_old], B[i][index_new],
414 m_correlation_args);
415 assert(temp.size() == m_dim_corr);
416
417 n_sweeps[index_res]++;
418 for (index_type k = 0; k < static_cast<index_type>(m_dim_corr); k++) {
419 result[index_res][k] += temp[k];
420 }
421 }
422 }
423}
424
425int Correlator::finalize(boost::mpi::communicator const &comm) {
426 using index_type = decltype(result)::index;
427 if (finalized) {
428 throw std::runtime_error("Correlator::finalize() can only be called once.");
429 }
430 // We must now go through the hierarchy and make sure there is space for the
431 // new datapoint. For every hierarchy level we have to decide if it is
432 // necessary to move something
433
434 // mark the correlation as finalized
435 finalized = true;
436
437 // worker nodes don't need to do anything
438 if (comm.rank() != 0) {
439 return 0;
440 }
441
442 for (int ll = 0; ll < m_hierarchy_depth - 1; ll++) {
443 long vals_ll; // number of values remaining in the lowest level
444 if (n_vals[ll] > m_tau_lin + 1)
445 vals_ll = m_tau_lin + n_vals[ll] % 2;
446 else
447 vals_ll = n_vals[ll];
448
449 while (vals_ll) {
450 // Check, if we will want to push the value from the lowest level
451 auto highest_level_to_compress = (vals_ll % 2) ? ll : -1;
452
453 // Let's find out how far we have to go back in the hierarchy to make
454 // space for the new value
455 {
456 auto const max_depth = m_hierarchy_depth - 1;
457 int i = ll + 1; // lowest level for which to check for compression
458 while (highest_level_to_compress > -1) {
459 if (i >= max_depth or n_vals[i] % 2 == 0 or n_vals[i] <= m_tau_lin) {
460 break;
461 }
462 highest_level_to_compress += 1;
463 i++;
464 }
465 }
466 vals_ll -= 1;
467
468 // Now we know we must make space on the levels
469 // 0..highest_level_to_compress
470 // Now let's compress the data level by level.
471
472 for (int i = highest_level_to_compress; i >= ll; i--) {
473 // We increase the index indicating the newest on level i+1 by one (plus
474 // folding)
475 newest[i + 1] = (newest[i + 1] + 1) % (m_tau_lin + 1);
476 n_vals[i + 1] += 1;
477
478 (*compressA)(A[i][(newest[i] + 1) % (m_tau_lin + 1)],
479 A[i][(newest[i] + 2) % (m_tau_lin + 1)]);
480 (*compressB)(B[i][(newest[i] + 1) % (m_tau_lin + 1)],
481 B[i][(newest[i] + 2) % (m_tau_lin + 1)]);
482 }
483 newest[ll] = (newest[ll] + 1) % (m_tau_lin + 1);
484
485 // We only need to update correlation estimates for the higher levels
486 for (int i = ll + 1; i < highest_level_to_compress + 2; i++) {
487 for (long j = (m_tau_lin + 1) / 2 + 1;
488 j < min(m_tau_lin + 1, n_vals[i]); j++) {
489 auto const index_new = newest[i];
490 auto const index_old =
491 (newest[i] - j + m_tau_lin + 1) % (m_tau_lin + 1);
492 auto const index_res =
493 m_tau_lin + (i - 1) * m_tau_lin / 2 + (j - m_tau_lin / 2 + 1) - 1;
494
495 auto const temp = (corr_operation)(A[i][index_old], B[i][index_new],
496 m_correlation_args);
497 assert(temp.size() == m_dim_corr);
498
499 n_sweeps[index_res]++;
500 for (index_type k = 0; k < static_cast<index_type>(m_dim_corr); k++) {
501 result[index_res][k] += temp[k];
502 }
503 }
504 }
505 }
506 }
507 return 0;
508}
509
510std::vector<double> Correlator::get_correlation() {
511 using index_type = decltype(result)::index;
512 auto const n_result = n_values();
513 std::vector<double> res(n_result * m_dim_corr);
514
515 for (std::size_t i = 0; i < n_result; i++) {
516 auto const index = static_cast<index_type>(m_dim_corr * i);
517 for (index_type k = 0; k < static_cast<index_type>(m_dim_corr); k++) {
518 if (n_sweeps[i]) {
519 res[index + k] = result[static_cast<index_type>(i)][k] /
520 static_cast<double>(n_sweeps[i]);
521 }
522 }
523 }
524 return res;
525}
526
527std::vector<double> Correlator::get_lag_times() const {
528 std::vector<double> res(n_values());
529 std::ranges::transform(tau, res.begin(),
530 [dt = m_dt](auto const &a) { return a * dt; });
531 return res;
532}
533
535 std::stringstream ss;
536 boost::archive::binary_oarchive oa(ss);
537
538 oa << t;
539 oa << m_dt;
540 oa << m_shape;
541 oa << m_correlation_args_input;
542 oa << A;
543 oa << B;
544 oa << result;
545 oa << n_sweeps;
546 oa << n_vals;
547 oa << newest;
548 oa << A_accumulated_average;
549 oa << B_accumulated_average;
550 oa << n_data;
551
552 return ss.str();
553}
554
555void Correlator::set_internal_state(std::string const &state) {
556 namespace iostreams = boost::iostreams;
557 iostreams::array_source src(state.data(), state.size());
558 iostreams::stream<iostreams::array_source> ss(src);
559 boost::archive::binary_iarchive ia(ss);
560
561 ia >> t;
562 ia >> m_dt;
563 ia >> m_shape;
564 ia >> m_correlation_args_input;
565 ia >> A;
566 ia >> B;
567 ia >> result;
568 ia >> n_sweeps;
569 ia >> n_vals;
570 ia >> newest;
571 ia >> A_accumulated_average;
572 ia >> B_accumulated_average;
573 ia >> n_data;
574 initialize_operations();
575 m_system = nullptr;
576}
577
578} // namespace Accumulators
Vector implementation and trait types for boost qvm interoperability.
void const * m_system
for bookkeeping purposes
std::string get_internal_state() const final
void set_internal_state(std::string const &) final
std::vector< double > get_lag_times() const
int finalize(boost::mpi::communicator const &comm)
At the end of data collection, go through the whole hierarchy and correlate data left there.
std::vector< double > get_correlation()
Return correlation result.
void update(boost::mpi::communicator const &comm) override
The function to process a new datapoint of A and B.
static DEVICE_QUALIFIER constexpr Vector< T, N > broadcast(typename Base::value_type const &value) noexcept
Create a vector that has all entries set to the same value.
Definition Vector.hpp:111
std::vector< double > componentwise_product(std::vector< double > const &A, std::vector< double > const &B, Utils::Vector3d const &)
std::vector< double > tensor_product(std::vector< double > const &A, std::vector< double > const &B, Utils::Vector3d const &)
std::vector< double > compress_linear(std::vector< double > const &A1, std::vector< double > const &A2)
Compress computing arithmetic mean: A_compressed=(A1+A2)/2.
std::vector< double > scalar_product(std::vector< double > const &A, std::vector< double > const &B, Utils::Vector3d const &)
std::vector< double > compress_discard1(std::vector< double > const &A1, std::vector< double > const &A2)
Compress discarding the 1st argument and return the 2nd.
std::vector< double > compress_discard2(std::vector< double > const &A1, std::vector< double > const &A2)
Compress discarding the 2nd argument and return the 1st.
std::vector< double > fcs_acf(std::vector< double > const &A, std::vector< double > const &B, Utils::Vector3d const &wsquare)
std::vector< double > square_distance_componentwise(std::vector< double > const &A, std::vector< double > const &B, Utils::Vector3d const &)
DEVICE_QUALIFIER constexpr T sqr(T x)
Calculates the SQuaRe of x.
Definition sqr.hpp:28
auto hadamard_product(Vector< T, N > const &a, Vector< U, N > const &b)
Definition Vector.hpp:380
int min(int i, unsigned int j)