PSCF v1.1
AmIteratorTmpl.tpp
1#ifndef PSCF_AM_ITERATOR_TMPL_TPP
2#define PSCF_AM_ITERATOR_TMPL_TPP
3
4/*
5* PSCF - Polymer Self-Consistent Field Theory
6*
7* Copyright 2016 - 2022, The Regents of the University of Minnesota
8* Distributed under the terms of the GNU General Public License.
9*/
10
11#include <pscf/inter/Interaction.h>
12#include <pscf/math/LuSolver.h>
13#include "NanException.h"
14#include <util/containers/FArray.h>
15#include <util/format/Dbl.h>
16#include <util/format/Int.h>
17#include <util/misc/Timer.h>
18#include <cmath>
19
20namespace Pscf
21{
22
23 using namespace Util;
24
25 // Public member functions
26
27 /*
28 * Constructor
29 */
30 template <typename Iterator, typename T>
32 : errorType_("relNormResid"),
33 epsilon_(0),
34 lambda_(0),
35 maxItr_(200),
36 maxHist_(50),
37 nBasis_(0),
38 itr_(0),
39 nElem_(0),
40 verbose_(0),
41 outputTime_(false),
42 isAllocatedAM_(false)
43 { setClassName("AmIteratorTmpl"); }
44
45 /*
46 * Destructor
47 */
48 template <typename Iterator, typename T>
50 {}
52 /*
53 * Read parameter file block.
54 */
55 template <typename Iterator, typename T>
57 {
58 // Error tolerance for scalar error. Stop when error < epsilon.
59 read(in, "epsilon", epsilon_);
60
61 // Maximum number of iterations (optional parameter)
62 // Default set in constructor (200) or before calling this function
63 readOptional(in, "maxItr", maxItr_);
64
65 // Maximum number of previous states in history (optional)
66 // Default set in constructor (50) or before calling this function
67 readOptional(in, "maxHist", maxHist_);
68
69 // Verbosity level of error reporting, values 0-2 (optional)
70 // Initialized to 0 by default in constructor
71 // verbose_ = 0 => concise
72 // verbose_ = 1 => report all error measures at end
73 // verbose_ = 2 => report all error measures every iteration
74 readOptional(in, "verbose", verbose_);
75
76 // Flag to output timing results (true) or skip (false)
77 // Initialized to false by default in constructor
78 readOptional(in, "outputTime", outputTime_);
79 }
80
81 /*
82 * Iteratively solve a SCF problem.
83 */
84 template <typename Iterator, typename T>
85 int AmIteratorTmpl<Iterator,T>::solve(bool isContinuation)
86 {
87 // Initialization and allocate operations on entry to loop.
88 setup(isContinuation);
89
90 // Preconditions for generic Anderson-mixing (AM) algorithm.
91 UTIL_CHECK(hasInitialGuess());
92 UTIL_CHECK(isAllocatedAM_);
93
94 // Timers for analyzing performance
95 Timer timerMDE;
96 Timer timerStress;
97 Timer timerAM;
98 Timer timerResid;
99 Timer timerError;
100 Timer timerCoeff;
101 Timer timerOmega;
102 Timer timerTotal;
103
104 // Start overall timer
105 timerTotal.start();
106
107 // Solve MDE for initial state
108 timerMDE.start();
109 evaluate();
110 timerMDE.stop();
111
112 // Iterative loop
113 nBasis_ = fieldBasis_.size();
114 for (itr_ = 0; itr_ < maxItr_; ++itr_) {
115
116 // Append current field to fieldHists_ ringbuffer
117 getCurrent(temp_);
118 fieldHists_.append(temp_);
119
120 timerAM.start();
121
122 if (verbose_ > 1) {
123 Log::file() << "------------------------------- \n";
124 }
125 Log::file() << " Iteration " << Int(itr_,5);
126
127
128 if (nBasis_ < maxHist_) {
129 lambda_ = 1.0 - pow(0.9, nBasis_ + 1);
130 } else {
131 lambda_ = 1.0;
132 }
133
134 // Compute residual vector
135 timerResid.start();
136 getResidual(temp_);
137
138 // Append current residual to resHists_ ringbuffer
139 resHists_.append(temp_);
140 timerResid.stop();
141
142 // Compute scalar error, output report to log file.
143 timerError.start();
144 double error;
145 try {
146 error = computeError(verbose_);
147 } catch (const NanException&) {
148 Log::file() << ", error = NaN" << std::endl;
149 break; // Exit loop if a NanException is caught
150 }
151 if (verbose_ < 2) {
152 Log::file() << ", error = " << Dbl(error, 15) << std::endl;
153 }
154 timerError.stop();
155
156 // Output additional details of this iteration to the log file
157 outputToLog();
158
159 // Check for convergence
160 if (error < epsilon_) {
161
162 // Stop timers
163 timerAM.stop();
164 timerTotal.stop();
165
166 if (verbose_ > 1) {
167 Log::file() << "-------------------------------\n";
168 }
169 Log::file() << " Converged\n";
170
171 // Output error report if not done previously
172 if (verbose_ == 1) {
173 Log::file() << "\n";
174 computeError(2);
175 }
176
177 // Output timing results, if requested.
178 if (outputTime_) {
179 double total = timerTotal.time();
180 Log::file() << "\n";
181 Log::file() << "Iterator times contributions:\n";
182 Log::file() << "\n";
183 Log::file() << "MDE solution: "
184 << timerMDE.time() << " s, "
185 << timerMDE.time()/total << "\n";
186 Log::file() << "residual computation: "
187 << timerResid.time() << " s, "
188 << timerResid.time()/total << "\n";
189 Log::file() << "mixing coefficients: "
190 << timerCoeff.time() << " s, "
191 << timerCoeff.time()/total << "\n";
192 Log::file() << "checking convergence: "
193 << timerError.time() << " s, "
194 << timerError.time()/total << "\n";
195 Log::file() << "updating guess: "
196 << timerOmega.time() << " s, "
197 << timerOmega.time()/total << "\n";
198 Log::file() << "total time: "
199 << total << " s \n";
200 }
201 Log::file() << "\n";
202
203 // Successful completion (i.e., converged within tolerance)
204 return 0;
205
206 } else {
207
208 // Compute optimal coefficients for basis vectors
209 timerCoeff.start();
210 computeResidCoeff();
211 timerCoeff.stop();
212
213 // Compute the trial updated field and update the system
214 timerOmega.start();
215 updateGuess();
216 timerOmega.stop();
217
218 timerAM.stop();
219
220 // Perform the main calculation of the parent system -
221 // solve MDEs, compute phi's, compute stress if needed
222 timerMDE.start();
223 evaluate();
224 timerMDE.stop();
225
226 }
227
228 }
229
230 // Failure: iteration counter itr reached maxItr without converging
231 timerTotal.stop();
232
233 Log::file() << "Iterator failed to converge.\n";
234 return 1;
235
236 }
237
238 // Protected member functions
239
240 /*
241 * Set value of maxItr.
242 */
243 template <typename Iterator, typename T>
245 { maxItr_ = maxItr; }
246
247 /*
248 * Set value of maxHist (number of retained previous states)
249 */
250 template <typename Iterator, typename T>
252 { maxHist_ = maxHist; }
253
254 /*
255 * Set and validate value of error type string.
256 */
257 template <typename Iterator, typename T>
259 {
260 errorType_ = errorType;
261
262 if (!isValidErrorType()) {
263 std::string msg = "Invalid iterator error type [";
264 msg += errorType;
265 msg += "] input in AmIteratorTmpl::setErrorType";
266 UTIL_THROW(msg.c_str());
267 }
268 }
269
270 /*
271 * Read and validate errorType string parameter.
272 */
273 template <typename Iterator, typename T>
275 {
276 // Read optional string
277 // Note: errorType_ is initialized to "relNormResid" in constructor
278 readOptional(in, "errorType", errorType_);
279
280 if (!isValidErrorType()) {
281 std::string msg = "Invalid iterator error type [";
282 msg += errorType_;
283 msg += "] in parameter file";
284 UTIL_THROW(msg.c_str());
285 }
286
287 }
288
289 template <typename Iterator, typename T>
291 {
292 // Process possible synonyms
293 if (errorType_ == "norm") errorType_ = "normResid";
294 if (errorType_ == "rms") errorType_ = "rmsResid";
295 if (errorType_ == "max") errorType_ = "maxResid";
296 if (errorType_ == "relNorm") errorType_ = "relNormResid";
297
298 // Check value
299 bool valid;
300 valid = (errorType_ == "normResid"
301 || errorType_ == "rmsResid"
302 || errorType_ == "maxResid"
303 || errorType_ == "relNormResid");
304 return valid;
305
306 }
307
308 /*
309 * Allocate memory required by the Anderson-Mixing algorithm, if needed.
310 * (protected, non-virtual)
311 */
312 template <typename Iterator, typename T>
314 {
315 // If already allocated, do nothing and return
316 if (isAllocatedAM_) return;
317
318 // Compute and set number of elements in a residual vector
319 nElem_ = nElements();
320
321 // Allocate ring buffers
322 fieldHists_.allocate(maxHist_+1);
323 resHists_.allocate(maxHist_+1);
324 fieldBasis_.allocate(maxHist_);
325 resBasis_.allocate(maxHist_);
326
327 // Allocate arrays used in iteration
328 fieldTrial_.allocate(nElem_);
329 resTrial_.allocate(nElem_);
330 temp_.allocate(nElem_);
331
332 // Allocate arrays/matrices used in coefficient calculation
333 U_.allocate(maxHist_, maxHist_);
334 v_.allocate(maxHist_);
335 coeffs_.allocate(maxHist_);
336
337 isAllocatedAM_ = true;
338 }
339
340 template <typename Iterator, typename T>
342 {
343 if (!isAllocatedAM_) return;
344
345 // Clear histories and bases (ring buffers)
346 Log::file() << "Clearing ring buffers\n";
347 resHists_.clear();
348 fieldHists_.clear();
349 resBasis_.clear();
350 fieldBasis_.clear();
351
352 return;
353 }
354
355 // Private non-virtual member functions
356
357 /*
358 * Compute coefficients of basis vectors.
359 */
360 template <typename Iterator, typename T>
362 {
363 // If first iteration and history is empty
364 // then initialize U, v and coeff arrays
365 if (itr_ == 0 && nBasis_ == 0) {
366 int m, n;
367 for (m = 0; m < maxHist_; ++m) {
368 v_[m] = 0.0;
369 coeffs_[m] = 0.0;
370 for (n = 0; n < maxHist_; ++n) {
371 U_(m, n) = 0.0;
372 }
373 }
374 }
375
376 // Do nothing else on first iteration
377 if (itr_ == 0) return;
378
379 // Update basis spanning differences of past field vectors
380 updateBasis(fieldBasis_, fieldHists_);
381
382 // Update basis spanning differences of past residual vectors
383 updateBasis(resBasis_, resHists_);
384
385 // Update nBasis_
386 nBasis_ = fieldBasis_.size();
387 UTIL_CHECK(fieldBasis_.size() == nBasis_);
388
389 // Update the U matrix and v vector.
390 updateU(U_, resBasis_, nBasis_);
391 updateV(v_, resHists_[0], resBasis_, nBasis_);
392 // Note: resHists_[0] is the current residual vector
393
394 // Solve matrix equation problem to compute coefficients
395 // that minmize the L2 norm of the residual vector.
396 if (nBasis_ == 1) {
397 // Solve explicitly for coefficient
398 coeffs_[0] = v_[0] / U_(0,0);
399 } else
400 if (nBasis_ < maxHist_) {
401
402 // Create temporary smaller version of U_, v_, coeffs_ .
403 // This is done to avoid reallocating U_ with each iteration.
404 DMatrix<double> tempU;
405 DArray<double> tempv,tempcoeffs;
406 tempU.allocate(nBasis_,nBasis_);
407 tempv.allocate(nBasis_);
408 tempcoeffs.allocate(nBasis_);
409 for (int i = 0; i < nBasis_; ++i) {
410 tempv[i] = v_[i];
411 for (int j = 0; j < nBasis_; ++j) {
412 tempU(i,j) = U_(i,j);
413 }
414 }
415
416 // Solve matrix equation
417 LuSolver solver;
418 solver.allocate(nBasis_);
419 solver.computeLU(tempU);
420 solver.solve(tempv,tempcoeffs);
421
422 // Transfer solution to full-sized member variable
423 for (int i = 0; i < nBasis_; ++i) {
424 coeffs_[i] = tempcoeffs[i];
425 }
426
427 } else
428 if (nBasis_ == maxHist_) {
429 LuSolver solver;
430 solver.allocate(maxHist_);
431 solver.computeLU(U_);
432 solver.solve(v_, coeffs_);
433 }
434
435 return;
436 }
437
438 template <typename Iterator, typename T>
439 void AmIteratorTmpl<Iterator,T>::updateGuess()
440 {
441
442 // Set field and residual trial vectors to current values
443 setEqual(fieldTrial_, fieldHists_[0]);
444 setEqual(resTrial_, resHists_[0]);
445
446 // Add linear combinations of field and residual basis vectors
447 if (nBasis_ > 0) {
448
449 // Combine basis vectors into trial guess and predicted residual
450 addHistories(fieldTrial_, fieldBasis_, coeffs_, nBasis_);
451 addHistories(resTrial_, resBasis_, coeffs_, nBasis_);
452
453 }
454
455 // Correct for predicted error
456 addPredictedError(fieldTrial_, resTrial_,lambda_);
457
458 // Update system using new trial field
459 update(fieldTrial_);
460
461 return;
462 }
463
464 // Private virtual member functions
465
466 /*
467 * Initialize just before entry to iterative loop.
468 * (virtual)
469 */
470 template <typename Iterator, typename T>
471 void AmIteratorTmpl<Iterator,T>::setup(bool isContinuation)
472 {
473 if (!isAllocatedAM()) {
474 allocateAM();
475 } else {
476 if (!isContinuation) {
477 clear();
478 }
479 }
480 }
481
482 /*
483 * Compute L2 norm of a vector.
484 */
485 template <typename Iterator, typename T>
487 {
488 double normSq = dotProduct(a, a);
489 return sqrt(normSq);
490 }
491
492 // Update entire U matrix
493 template <typename Iterator, typename T>
494 void
496 RingBuffer<T> const & resBasis,
497 int nHist)
498 {
499 // Update matrix U by shifting elements diagonally
500 int maxHist = U.capacity1();
501 for (int m = maxHist-1; m > 0; --m) {
502 for (int n = maxHist-1; n > 0; --n) {
503 U(m,n) = U(m-1,n-1);
504 }
505 }
506
507 // Compute U matrix's new row 0 and col 0
508 for (int m = 0; m < nHist; ++m) {
509 double dotprod = dotProduct(resBasis[0],resBasis[m]);
510 U(m,0) = dotprod;
511 U(0,m) = dotprod;
512 }
513 }
514
515 template <typename Iterator, typename T>
516 void
517 AmIteratorTmpl<Iterator, T>::updateV(DArray<double> & v,
518 T const & resCurrent,
519 RingBuffer<T> const & resBasis,
520 int nHist)
521 {
522 for (int m = 0; m < nHist; ++m) {
523 v[m] = dotProduct(resCurrent, resBasis[m]);
524 }
525 }
526
527 template <typename Iterator, typename T>
529 {
530 double error = 0.0;
531
532 if (verbose > 1) {
533
534 Log::file() << "\n";
535
536 // Find max residual vector element
537 double maxRes = maxAbs(resHists_[0]);
538 Log::file() << "Max Residual = " << Dbl(maxRes,15) << "\n";
539
540 // Find norm of residual vector
541 double normRes = norm(resHists_[0]);
542 Log::file() << "Residual Norm = " << Dbl(normRes,15) << "\n";
543
544 // Find root-mean-squared residual element value
545 double rmsRes = normRes/sqrt(nElem_);
546 Log::file() << "RMS Residual = " << Dbl(rmsRes,15) << "\n";
547
548 // Find norm of residual vector relative to field
549 double normField = norm(fieldHists_[0]);
550 double relNormRes = normRes/normField;
551 Log::file() << "Relative Norm = " << Dbl(relNormRes,15) << std::endl;
552
553 // Check if calculation has diverged (normRes will be NaN)
554 UTIL_CHECK(!std::isnan(normRes));
555
556 // Set error value
557 if (errorType_ == "maxResid") {
558 error = maxRes;
559 } else if (errorType_ == "normResid") {
560 error = normRes;
561 } else if (errorType_ == "rmsResid") {
562 error = rmsRes;
563 } else if (errorType_ == "relNormResid") {
564 error = relNormRes;
565 } else {
566 UTIL_THROW("Invalid iterator error type in parameter file.");
567 }
568
569 } else {
570
571 // Set error value
572 if (errorType_ == "maxResid") {
573 error = maxAbs(resHists_[0]);
574 } else if (errorType_ == "normResid") {
575 error = norm(resHists_[0]);
576 } else if (errorType_ == "rmsResid") {
577 error = norm(resHists_[0])/sqrt(nElem_);
578 } else if (errorType_ == "relNormResid") {
579 double normRes = norm(resHists_[0]);
580 double normField = norm(fieldHists_[0]);
581 error = normRes/normField;
582 } else {
583 UTIL_THROW("Invalid iterator error type in parameter file.");
584 }
585 //Log::file() << ", error = " << Dbl(error, 15) << "\n";
586
587 }
588
589 return error;
590 }
591
592}
593#endif
Template for Anderson mixing iterator algorithm.
virtual void setup(bool isContinuation)
Initialize just before entry to iterative loop.
void setMaxHist(int maxHist)
Set value of maxHist (number of retained previous states)
virtual double computeError(int verbose)
Compute and return error used to test for convergence.
void allocateAM()
Allocate memory required by AM algorithm, if necessary.
virtual void clear()
Clear information about history.
void setErrorType(std::string errorType)
Set and validate value of errorType string.
~AmIteratorTmpl()
Destructor.
void readErrorType(std::istream &in)
Read and validate the optional errorType string parameter.
void setMaxItr(int maxItr)
Set value of maxItr.
AmIteratorTmpl()
Constructor.
virtual bool isValidErrorType()
Checks if a string is a valid error type.
void readParameters(std::istream &in)
Read all parameters and initialize.
int solve(bool isContinuation=false)
Iterate to a solution.
virtual double norm(T const &hist)
Find the L2 norm of a vector.
Exception thrown when not-a-number (NaN) is encountered.
Definition: NanException.h:40
Dynamically allocatable contiguous array template.
Definition: DArray.h:32
void allocate(int capacity)
Allocate the underlying C array.
Definition: DArray.h:199
Dynamically allocated Matrix.
Definition: DMatrix.h:25
void allocate(int capacity1, int capacity2)
Allocate memory for a matrix.
Definition: DMatrix.h:170
Wrapper for a double precision number, for formatted ostream output.
Definition: Dbl.h:40
Wrapper for an int, for formatted ostream output.
Definition: Int.h:37
static std::ostream & file()
Get log ostream by reference.
Definition: Log.cpp:57
int capacity1() const
Get number of rows (range of the first array index).
Definition: Matrix.h:136
Class for storing history of previous values in an array.
Definition: RingBuffer.h:27
Wall clock timer.
Definition: Timer.h:35
void start(TimePoint begin)
Start timing from an externally supplied time.
Definition: Timer.cpp:49
double time()
Return the accumulated time, in seconds.
Definition: Timer.cpp:37
void stop(TimePoint end)
Stop the clock at an externally supplied time.
Definition: Timer.cpp:73
#define UTIL_CHECK(condition)
Assertion macro suitable for serial or parallel production code.
Definition: global.h:68
#define UTIL_THROW(msg)
Macro for throwing an Exception, reporting function, file and line number.
Definition: global.h:51
C++ namespace for polymer self-consistent field theory (PSCF).
Utility classes for scientific computation.
Definition: accumulators.mod:1