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