PSCF v1.3
AmIteratorTmpl.tpp
1#ifndef PSCF_AM_ITERATOR_TMPL_TPP
2#define PSCF_AM_ITERATOR_TMPL_TPP
3
4/*
5* PSCF - Polymer Self-Consistent Field
6*
7* Copyright 2015 - 2025, 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/format/Dbl.h>
15#include <util/format/Int.h>
16#include <util/misc/Timer.h>
17#include <util/misc/FileMaster.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 r_(0.9),
36 maxItr_(200),
37 maxHist_(50),
38 nBasis_(0),
39 totalItr_(0),
40 nElem_(0),
41 verbose_(1),
42 isAllocatedAM_(false)
43 { setClassName("AmIteratorTmpl"); }
44
45 /*
46 * Destructor
47 */
48 template <typename Iterator, typename T>
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 by prior call to setMaxItr
63 readOptional(in, "maxItr", maxItr_);
64
65 // Maximum number of previous states in history (optional)
66 // Default set in constructor (50) or by prior call to setMaxHist
67 readOptional(in, "maxHist", maxHist_);
68
69 // Verbosity level of error reporting, values 0-3 (optional)
70 // Initialized to 1 by default in constructor
71 // verbose_ = 0 => FTS
72 // verbose_ = 1 => concise
73 // verbose_ = 2 => report all error measures at end
74 // verbose_ = 3 => report all error measures every iteration
75 readOptional(in, "verbose", verbose_);
77 // Ramping parameter for correction step
78 // Default set to 0.9. lambda = 1 - r_^Nh for Nh < maxHist
79 readOptional(in, "correctionRamp", r_);
80
81 #ifdef PSCF_AM_TEST
82 // Default set to be false
83 readOptional(in, "hasAmTest", hasAmTest_);
84 #endif
85 }
86
87 /*
88 * Iteratively solve a fixed-point problem.
89 */
90 template <typename Iterator, typename T>
91 int AmIteratorTmpl<Iterator,T>::solve(bool isContinuation)
92 {
93 // Initialization and allocate operations on entry to loop.
94 setup(isContinuation);
95
96 // Preconditions for generic Anderson-mixing (AM) algorithm.
97 UTIL_CHECK(hasInitialGuess());
98 UTIL_CHECK(isAllocatedAM_);
99
100 // Start overall timer
101 timerTotal_.start();
102
103 // Solve MDE for initial state
104 timerMDE_.start();
105 evaluate();
106 timerMDE_.stop();
108 // Iterative loop
109 nBasis_ = fieldBasis_.size();
110 for (itr_ = 0; itr_ < maxItr_; ++itr_) {
111
112 // Append current field to fieldHists_ ringbuffer
113 getCurrent(temp_);
114 fieldHists_.append(temp_);
115
116 timerAM_.start();
117
118 if (verbose_ > 2) {
119 Log::file() << "------------------------------- \n";
120 }
121
122 if (verbose_ > 0){
123 Log::file() << " Iteration " << Int(itr_,5);
124 }
125
126 lambda_ = computeLambda(r_);
127
128 // Compute current residual vector, store in temp_
129 timerResid_.start();
130 getResidual(temp_);
131
132 // Append current residual to resHists_ ring buffer
133 resHists_.append(temp_);
134 timerResid_.stop();
135
136 // Compute scalar error used to test convergence
137 timerError_.start();
138 try {
139 error_ = computeError(verbose_);
140 } catch (const NanException&) {
141 Log::file() << ", error = NaN" << std::endl;
142 break; // Exit loop if a NanException is caught
143 }
144 if (verbose_ > 0 && verbose_ < 3) {
145 Log::file() << ", error = " << Dbl(error_, 15)
146 << std::endl;
147 }
148 timerError_.stop();
149
150 // Output additional details of this iteration to the log file
151 outputToLog();
152
153 #ifdef PSCF_AM_TEST
154 // Compute errors and ratios used for algorithm testing
155 if (hasAmTest_){
156 // Compute errors and ratios used for algorithm testing
157 correctionError_ = computeError(0);
158 if (itr_ > 0) {
159 projectionRatio_ += projectionError_/preError_;
160 correctionRatio_ += correctionError_/preError_;
161 testCounter ++;
162 }
163 preError_ = correctionError_;
164 }
165 #endif
166
167 // Check for convergence
168 if (error_ < epsilon_) {
169
170 // Stop timers
171 timerAM_.stop();
172 timerTotal_.stop();
173
174 if (verbose_ > 2) {
175 Log::file() << "-------------------------------\n";
176 }
177
178 if (verbose_ > 0) {
179 Log::file() << " Converged\n";
180 }
181
182 // Output error report if not done previously
183 if (verbose_ == 2) {
184 Log::file() << "\n";
185 computeError(2);
186 }
187
188 totalItr_ += itr_;
189
190 // Successful completion (i.e., converged within tolerance)
191 return 0;
192
193 } else {
195 // Compute optimal coefficients for basis vectors
196 timerCoeff_.start();
197 computeResidCoeff();
198 timerCoeff_.stop();
199
200 // Compute updated field and update the system
201 timerOmega_.start();
202 updateGuess();
203 timerOmega_.stop();
204
205 timerAM_.stop();
206
207 // Perform the main calculation of the parent system -
208 // Solve MDEs, compute phi's, compute stress if needed
209 timerMDE_.start();
210 evaluate();
211 timerMDE_.stop();
213
214 }
215
216 // Failure: Counter itr_ reached maxItr_ without converging
217 timerTotal_.stop();
218 Log::file() << "Iterator failed to converge.\n";
219 return 1;
220
221 }
222
223 // Protected member functions
224
225 /*
226 * Set value of maxItr.
227 */
228 template <typename Iterator, typename T>
230 { maxItr_ = maxItr; }
231
232 /*
233 * Set value of maxHist (number of retained previous states)
234 */
235 template <typename Iterator, typename T>
237 { maxHist_ = maxHist; }
238
239 /*
240 * Set and validate value of error type string.
241 */
242 template <typename Iterator, typename T>
244 {
246
247 // Check validity, normalize to standard form
248 bool isValid = isValidErrorType();
249
250 // If not valid, throw Exception with error message
251 if (!isValid) {
252 std::string msg = "Invalid iterator error type [";
253 msg += errorType;
254 msg += "] input in AmIteratorTmpl::setErrorType";
255 UTIL_THROW(msg.c_str());
256 }
257 }
258
259 /*
260 * Read and validate optional errorType string parameter.
261 */
262 template <typename Iterator, typename T>
264 {
265 // Note: errorType_ is initialized to "relNormResid" in constructor
266
267 // Read optional errorType_ string if present
268 readOptional(in, "errorType", errorType_);
269
270 // Check validity, normalize to standard form
271 bool isValid = isValidErrorType();
272
273 // If not valid, throw Exception with error message
274 if (!isValid) {
275 std::string msg = "Invalid iterator error type [";
276 msg += errorType_;
277 msg += "] in parameter file";
278 UTIL_THROW(msg.c_str());
279 }
280
281 }
282
283 /*
284 * Check validity of errorType_ string and normalize if valid.
285 */
286 template <typename Iterator, typename T>
288 {
289 // Process possible synonyms
290 if (errorType_ == "norm") errorType_ = "normResid";
291 if (errorType_ == "rms") errorType_ = "rmsResid";
292 if (errorType_ == "max") errorType_ = "maxResid";
293 if (errorType_ == "relNorm") errorType_ = "relNormResid";
294
295 // Check value
296 bool valid;
297 valid = (errorType_ == "normResid"
298 || errorType_ == "rmsResid"
299 || errorType_ == "maxResid"
300 || errorType_ == "relNormResid");
301 return valid;
302
303 }
304
305 /*
306 * Allocate memory required by the Anderson-Mixing algorithm, if needed.
307 * (protected, non-virtual)
308 */
309 template <typename Iterator, typename T>
311 {
312 // If already allocated, do nothing and return
313 if (isAllocatedAM_) return;
314
315 // Compute and set number of elements in a residual vector
316 nElem_ = nElements();
317
318 // Allocate ring buffers
319 fieldHists_.allocate(maxHist_+1);
320 resHists_.allocate(maxHist_+1);
321 fieldBasis_.allocate(maxHist_);
322 resBasis_.allocate(maxHist_);
323
324 // Allocate arrays used in iteration
325 fieldTrial_.allocate(nElem_);
326 resTrial_.allocate(nElem_);
327 temp_.allocate(nElem_);
328
329 // Allocate arrays/matrices used in coefficient calculation
330 U_.allocate(maxHist_, maxHist_);
331 v_.allocate(maxHist_);
332 coeffs_.allocate(maxHist_);
333
334 isAllocatedAM_ = true;
335 }
336
337 /*
338 * Clear all history and basis vector data.
339 */
340 template <typename Iterator, typename T>
342 {
343 if (!isAllocatedAM_) return;
344
345 // Clear histories and bases (ring buffers)
346 if (verbose_ > 0) {
347 Log::file() << "Clearing AM field history and basis vectors.\n";
348 }
349 resHists_.clear();
350 fieldHists_.clear();
351 resBasis_.clear();
352 fieldBasis_.clear();
353
354 return;
355 }
356
357 // Private non-virtual member functions
358
359 /*
360 * Compute optimal coefficients of basis vectors.
361 */
362 template <typename Iterator, typename T>
363 void AmIteratorTmpl<Iterator,T>::computeResidCoeff()
364 {
365 // If first iteration and history is empty
366 // then initialize U, v and coeff arrays
367 if (itr_ == 0 && nBasis_ == 0) {
368 int m, n;
369 for (m = 0; m < maxHist_; ++m) {
370 v_[m] = 0.0;
371 coeffs_[m] = 0.0;
372 for (n = 0; n < maxHist_; ++n) {
373 U_(m, n) = 0.0;
374 }
375 }
376 }
377
378 // Do nothing else on first iteration
379 if (itr_ == 0) return;
380
381 // Update fieldBasis_, resBasis_, U_ matrix and v_ vector
382 if (fieldHists_.size() > 1) {
383
384 // Update basis spanning differences of past field vectors
385 updateBasis(fieldBasis_, fieldHists_);
386
387 // Update basis spanning differences of past residual vectors
388 updateBasis(resBasis_, resHists_);
389
390 // Update nBasis_
391 nBasis_ = fieldBasis_.size();
392 UTIL_CHECK(fieldBasis_.size() == nBasis_);
393
394 // Update the U matrix and v vector.
395 updateU(U_, resBasis_, nBasis_);
396 }
397
398 UTIL_CHECK(nBasis_ > 0);
399
400 // Update v_ vector
401 updateV(v_, resHists_[0], resBasis_, nBasis_);
402
403 // Solve matrix equation problem to compute coefficients
404 // that minmize the L2 norm of the residual vector.
405 if (nBasis_ == 1) {
406 // Solve explicitly for coefficient
407 coeffs_[0] = v_[0] / U_(0,0);
408 } else
409 if (nBasis_ < maxHist_) {
410
411 // Create temporary smaller version of U_, v_, coeffs_ .
412 // This is done to avoid reallocating U_ with each iteration.
413 DMatrix<double> tempU;
414 DArray<double> tempv,tempcoeffs;
415 tempU.allocate(nBasis_,nBasis_);
416 tempv.allocate(nBasis_);
417 tempcoeffs.allocate(nBasis_);
418 for (int i = 0; i < nBasis_; ++i) {
419 tempv[i] = v_[i];
420 for (int j = 0; j < nBasis_; ++j) {
421 tempU(i,j) = U_(i,j);
422 }
423 }
424
425 // Solve matrix equation
426 LuSolver solver;
427 solver.allocate(nBasis_);
428 solver.computeLU(tempU);
429 solver.solve(tempv,tempcoeffs);
430
431 // Transfer solution to full-sized member variable
432 for (int i = 0; i < nBasis_; ++i) {
433 coeffs_[i] = tempcoeffs[i];
434 }
435
436 } else
437 if (nBasis_ == maxHist_) {
438 LuSolver solver;
439 solver.allocate(maxHist_);
440 solver.computeLU(U_);
441 solver.solve(v_, coeffs_);
442 }
443
444 return;
445 }
446
447 template <typename Iterator, typename T>
448 void AmIteratorTmpl<Iterator,T>::updateGuess()
449 {
450
451 // Set field and residual trial vectors to current values
452 setEqual(fieldTrial_, fieldHists_[0]);
453 setEqual(resTrial_, resHists_[0]);
454
455 // Add linear combinations of field and residual basis vectors
456 if (nBasis_ > 0) {
457
458 // Combine basis vectors into trial guess and predicted residual
459 addHistories(fieldTrial_, fieldBasis_, coeffs_, nBasis_);
460 addHistories(resTrial_, resBasis_, coeffs_, nBasis_);
461
462 }
463
464 #ifdef PSCF_AM_TEST
465 // Additional direct computation of error after projection step
466 if (hasAmTest_){
467 timerOmega_.stop();
468
469 // Additional MDE solution
470 timerMDE_.start();
471 update(fieldTrial_);
472 evaluate();
473 timerMDE_.stop();
474
475 // Residual computation
476 timerResid_.start();
477 getResidual(temp_);
478 timerResid_.stop();
479
480 // Compute error after projection
481 timerError_.start();
482 projectionError_ = computeError(temp_, fieldTrial_, errorType_, 0);
483 timerError_.stop();
484 timerOmega_.start();
485 }
486 #endif
487
488 // Correction (or "mixing") step
489 addPredictedError(fieldTrial_, resTrial_,lambda_);
490
491 // Update system using new trial field
492 update(fieldTrial_);
493
494
495 return;
496 }
497
498 // Private virtual member functions
499
500 /*
501 * Initialize just before entry to iterative loop.
502 * (virtual)
503 */
504 template <typename Iterator, typename T>
505 void AmIteratorTmpl<Iterator,T>::setup(bool isContinuation)
506 {
507 if (!isAllocatedAM()) {
508 allocateAM();
509 } else {
510 // Clear residual and field history buffers
511 resHists_.clear();
512 fieldHists_.clear();
513 if (!isContinuation) {
514 // Clear bases iff not a continuation
515 resBasis_.clear();
516 fieldBasis_.clear();
517 }
518 }
519 }
520
525 template <typename Iterator, typename T>
527 {
528 double lambda;
529 if (nBasis_ < maxHist_) {
530 lambda = 1.0 - pow(r, nBasis_ + 1);
531 } else {
532 lambda = 1.0;
533 }
534 return lambda;
535 }
536
537 /*
538 * Compute L2 norm of a vector.
539 */
540 template <typename Iterator, typename T>
542 {
543 double normSq = dotProduct(a, a);
544 return sqrt(normSq);
545 }
546
547 // Update entire U matrix
548 template <typename Iterator, typename T>
549 void
551 RingBuffer<T> const & resBasis,
552 int nHist)
553 {
554 // Update matrix U by shifting elements diagonally
555 int maxHist = U.capacity1();
556 for (int m = maxHist-1; m > 0; --m) {
557 for (int n = maxHist-1; n > 0; --n) {
558 U(m,n) = U(m-1,n-1);
559 }
560 }
561
562 // Compute U matrix's new row 0 and col 0
563 for (int m = 0; m < nHist; ++m) {
564 double dotprod = dotProduct(resBasis[0],resBasis[m]);
565 U(m,0) = dotprod;
566 U(0,m) = dotprod;
567 }
568 }
569
570 template <typename Iterator, typename T>
571 void
572 AmIteratorTmpl<Iterator, T>::updateV(DArray<double> & v,
573 T const & resCurrent,
574 RingBuffer<T> const & resBasis,
575 int nHist)
576 {
577 for (int m = 0; m < nHist; ++m) {
578 v[m] = dotProduct(resCurrent, resBasis[m]);
579 }
580 }
581
582 template <typename Iterator, typename T>
583 double AmIteratorTmpl<Iterator,T>::computeError(T&residTrial, T&fieldTrial,
584 std::string errorType,
585 int verbose)
586 {
587 double error = 0.0;
588
589 // Find max residual vector element
590 double maxRes = maxAbs(residTrial);
591
592 // Find norm of residual vector
593 double normRes = norm(residTrial);
594
595 // Check if calculation has diverged (normRes will be NaN)
596 UTIL_CHECK(!std::isnan(normRes));
597
598 // Find root-mean-squared residual element value
599 double rmsRes = normRes/sqrt(nElements());
600
601 // Find norm of residual vector relative to field
602 double normField = norm(fieldTrial);
603 double relNormRes = normRes/normField;
604
605 // Set error value
606 if (errorType == "maxResid") {
607 error = maxRes;
608 } else if (errorType == "normResid") {
609 error = normRes;
610 } else if (errorType == "rmsResid") {
611 error = rmsRes;
612 } else if (errorType == "relNormResid") {
613 error = relNormRes;
614 } else {
615 UTIL_THROW("Invalid iterator error type in parameter file.");
616 }
617
618 if (verbose > 1) {
619 Log::file() << "\n";
620 Log::file() << "Max Residual = " << Dbl(maxRes,15) << "\n";
621 Log::file() << "Residual Norm = " << Dbl(normRes,15) << "\n";
622 Log::file() << "RMS Residual = " << Dbl(rmsRes,15) << "\n";
623 Log::file() << "Relative Norm = " << Dbl(relNormRes,15)
624 << std::endl;
625 }
626
627 return error;
628 }
629
630 template <typename Iterator, typename T>
632 {
633 return computeError(resHists_[0], fieldHists_[0], errorType_, verbose);
634 }
635
636 template <typename Iterator, typename T>
637 void AmIteratorTmpl<Iterator,T>::outputTimers(std::ostream& out) const
638 {
639 // Output timing results, if requested.
640 double total = timerTotal_.time();
641 out << "\n";
642 out << " ";
643 out << "Total" << std::setw(22)<< "Per Iteration"
644 << std::setw(9) << "Fraction" << "\n";
645 out << "MDE solution: "
646 << Dbl(timerMDE_.time(), 9, 3) << " s, "
647 << Dbl(timerMDE_.time()/totalItr_, 9, 3) << " s, "
648 << Dbl(timerMDE_.time()/total, 9, 3) << "\n";
649 out << "residual computation: "
650 << Dbl(timerResid_.time(), 9, 3) << " s, "
651 << Dbl(timerResid_.time()/totalItr_, 9, 3) << " s, "
652 << Dbl(timerResid_.time()/total, 9, 3) << "\n";
653 out << "mixing coefficients: "
654 << Dbl(timerCoeff_.time(), 9, 3) << " s, "
655 << Dbl(timerCoeff_.time()/totalItr_, 9, 3) << " s, "
656 << Dbl(timerCoeff_.time()/total, 9, 3) << "\n";
657 out << "checking convergence: "
658 << Dbl(timerError_.time(), 9, 3) << " s, "
659 << Dbl(timerError_.time()/totalItr_, 9, 3) << " s, "
660 << Dbl(timerError_.time()/total, 9, 3) << "\n";
661 out << "updating guess: "
662 << Dbl(timerOmega_.time(), 9, 3) << " s, "
663 << Dbl(timerOmega_.time()/totalItr_, 9, 3) << " s, "
664 << Dbl(timerOmega_.time()/total, 9, 3)<< "\n";
665 out << "total time: "
666 << Dbl(total, 9, 3) << " s, "
667 << Dbl(total/totalItr_, 9, 3) << " s \n";
668 out << "\n";
669
670 #ifdef PSCF_AM_TEST
671 if (hasAmTest_){
672 out << "Average Projection Step Reduction Ratio: "
673 << Dbl(projectionRatio_/testCounter, 3, 3)<< "\n";
674 out << "Average Correction Step Reduction Ratio: "
675 << Dbl(correctionRatio_/testCounter, 3, 3)<< "\n";
676 }
677 #endif
678 }
679
680 template <typename Iterator, typename T>
682 {
683 timerMDE_.clear();
684 timerResid_.clear();
685 timerError_.clear();
686 timerCoeff_.clear();
687 timerOmega_.clear();
688 timerAM_.clear();
689 timerTotal_.clear();
690 totalItr_ = 0;
691 }
692
693}
694#endif
Template for Anderson mixing iterator algorithm.
virtual void setup(bool isContinuation)
Initialize just before entry to iterative loop.
ScalarParam< Type > & read(std::istream &in, const char *label, Type &value)
Add and read a new required ScalarParam < Type > object.
void setMaxHist(int maxHist)
Set value of maxHist (number of retained previous states)
virtual double computeLambda(double r)
Compute mixing parameter for correction step of Anderson mixing.
virtual double computeError(DArray< double > &residTrial, DArray< double > &fieldTrial, std::string errorType, int verbose)
void allocateAM()
Allocate memory required by AM algorithm, if necessary.
bool isAllocatedAM() const
Have data structures required by the AM algorithm been allocated?
virtual void clear()
Clear information about history.
void outputTimers(std::ostream &out) const
Log output timing results.
void setErrorType(std::string errorType)
Set and validate value of errorType string.
std::string errorType() const
Obtain error type.
ScalarParam< Type > & readOptional(std::istream &in, const char *label, Type &value)
void clearTimers()
Clear timers.
int verbose() const
Verbosity level, allowed values 0, 1, or 2.
~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.
std::string errorType_
Type of error criterion used to test convergence.
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.
Solve Ax=b by LU decomposition of A.
Definition LuSolver.h:31
void allocate(int n)
Allocate memory.
Definition LuSolver.cpp:46
Exception thrown when not-a-number (NaN) is encountered.
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:59
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
#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:49
PSCF package top-level namespace.
Definition param_pc.dox:1