PSCF v1.4.0
NrIterator.cpp
1/*
2* PSCF - Polymer Self-Consistent Field
3*
4* Copyright 2015 - 2025, The Regents of the University of Minnesota
5* Distributed under the terms of the GNU General Public License.
6*/
7
8#include "NrIterator.h"
9#include <r1d/system/System.h>
10#include <r1d/solvers/Mixture.h>
11#include <r1d/solvers/Polymer.h>
12#include <pscf/interaction/Interaction.h>
13#include <pscf/chem/Ensemble.h>
14#include <util/misc/Log.h>
15
16#include <math.h>
17
18namespace Pscf {
19namespace R1d
20{
21
22 using namespace Util;
23
25 : Iterator(),
26 epsilon_(0.0),
27 maxItr_(0),
28 isAllocated_(false),
29 newJacobian_(false),
30 needsJacobian_(true),
31 isCanonical_(true)
32 { setClassName("NrIterator"); }
33
36 epsilon_(0.0),
37 maxItr_(0),
38 isAllocated_(false),
39 newJacobian_(false),
40 needsJacobian_(true),
41 isCanonical_(true)
42 { setClassName("NrIterator"); }
43
46
47 void NrIterator::readParameters(std::istream& in)
48 {
49 maxItr_ = 400;
50 read(in, "epsilon", epsilon_);
51 readOptional(in, "maxItr", maxItr_);
52 setup();
53 }
54
55 void NrIterator::setup()
56 {
57 int nm = system().mixture().nMonomer(); // number of monomer types
58 int nx = domain().nx(); // number of grid points
59 UTIL_CHECK(nm > 0);
60 UTIL_CHECK(nx > 0);
61 int nr = nm*nx; // number of residual components
62 if (isAllocated_) {
63 UTIL_CHECK(cArray_.capacity() == nm);
64 UTIL_CHECK(residual_.capacity() == nr);
65 } else {
66 cArray_.allocate(nm);
67 wArray_.allocate(nm);
68 residual_.allocate(nr);
69 jacobian_.allocate(nr, nr);
70 residualNew_.allocate(nr);
71 dOmega_.allocate(nr);
72 wFieldsNew_.allocate(nm);
73 cFieldsNew_.allocate(nm);
74 for (int i = 0; i < nm; ++i) {
75 wFieldsNew_[i].allocate(nx);
76 cFieldsNew_[i].allocate(nx);
77 }
78 solver_.allocate(nr);
79 isAllocated_ = true;
80 }
81 }
82
83 void NrIterator::computeResidual(Array<FieldT> const & wFields,
84 Array<FieldT> const & cFields,
85 Array<double>& residual)
86 {
87 DMatrix<double> const & chi = system().interaction().chi();
88 int nm = system().mixture().nMonomer(); // number of monomer types
89 int nx = domain().nx(); // number of grid points
90 int i; // grid point index
91 int j, k; // monomer indices
92 int ir; // residual index
93
94 // Loop over grid points
95 for (i = 0; i < nx; ++i) {
96
97 // Copy volume fractions at grid point i to cArray_
98 for (j = 0; j < nm; ++j) {
99 cArray_[j] = cFields[j][i];
100 }
101
102 // Compute w fields, without Langrange multiplier, from c fields
103 //system().interaction().computeW(cArray_, wArray_);
104 for (j = 0; j < nm; ++j) {
105 wArray_[j] = 0.0;
106 for (k = 0; k < nm; ++k) {
107 wArray_[j] += chi(j, k)*cArray_[k];
108 }
109 }
110
111 // Initial residual = wPredicted(from above) - actual w
112 for (j = 0; j < nm; ++j) {
113 ir = j*nx + i;
114 residual[ir] = wArray_[j] - wFields[j][i];
115 }
116
117 // Residuals j = 1, ..., nm-1 are differences from component j=0
118 for (j = 1; j < nm; ++j) {
119 ir = j*nx + i;
120 residual[ir] = residual[ir] - residual[i];
121 }
122
123 // Residual for component j=0 then imposes incompressiblity
124 residual[i] = -1.0;
125 for (j = 0; j < nm; ++j) {
126 residual[i] += cArray_[j];
127 }
128 }
129
130 /*
131 * Note: In canonical ensemble, the spatial integral of the
132 * incompressiblity residual is guaranteed to be zero, as a result of how
133 * volume fractions are computed in SCFT. One of the nx incompressibility
134 * constraints is thus redundant. To avoid this redundancy, replace the
135 * incompressibility residual at the last grid point by a residual that
136 * requires the w field for the last monomer type at the last grid point
137 * to equal zero.
138 */
139
140 if (isCanonical_) {
141 residual[nx-1] = wFields[nm-1][nx-1];
142 }
143
144 }
145
146 /*
147 * Compute Jacobian matrix numerically, by evaluating finite differences.
148 */
149 void NrIterator::computeJacobian()
150 {
151 int nm = system().mixture().nMonomer(); // number of monomer types
152 int nx = domain().nx(); // number of grid points
153 int i; // monomer index
154 int j; // grid point index
155
156 // Copy system().wFields to wFieldsNew.
157 for (i = 0; i < nm; ++i) {
158 for (j = 0; j < nx; ++j) {
159 UTIL_CHECK(nx == wFieldsNew_[i].capacity());
160 UTIL_CHECK(nx == system().wField(i).capacity());
161 wFieldsNew_[i][j] = system().wField(i)[j];
162 }
163 }
164
165 // Compute jacobian, column by column
166 double delta = 0.001;
167 int nr = nm*nx; // number of residual elements
168 int jr; // jacobian row index
169 int jc = 0; // jacobian column index
170 for (i = 0; i < nm; ++i) {
171 for (j = 0; j < nx; ++j) {
172 wFieldsNew_[i][j] += delta;
173 system().mixture().compute(wFieldsNew_, cFieldsNew_);
174 computeResidual(wFieldsNew_, cFieldsNew_, residualNew_);
175 for (jr = 0; jr < nr; ++jr) {
176 jacobian_(jr, jc) =
177 (residualNew_[jr] - residual_[jr])/delta;
178 }
179 wFieldsNew_[i][j] = system().wField(i)[j];
180 ++jc;
181 }
182 }
183
184 // Decompose Jacobian matrix
185 solver_.computeLU(jacobian_);
186
187 }
188
189 void NrIterator::incrementWFields(Array<FieldT> const & wOld,
190 Array<double> const & dW,
191 Array<FieldT> & wNew)
192 {
193 int nm = system().mixture().nMonomer(); // number of monomers types
194 int nx = domain().nx(); // number of grid points
195 int i; // monomer index
196 int j; // grid point index
197 int k = 0; // residual element index
198
199 // Add dW
200 for (i = 0; i < nm; ++i) {
201 for (j = 0; j < nx; ++j) {
202 wNew[i][j] = wOld[i][j] - dW[k];
203 ++k;
204 }
205 }
206
207 // If canonical, shift such that last element is exactly zero
208 if (isCanonical_) {
209 double shift = wNew[nm-1][nx-1];
210 for (i = 0; i < nm; ++i) {
211 for (j = 0; j < nx; ++j) {
212 wNew[i][j] -= shift;
213 }
214 }
215 }
216
217 }
218
219 double NrIterator::residualNorm(Array<double> const & residual) const
220 {
221 int nm = system().mixture().nMonomer(); // number of monomer types
222 int nx = domain().nx(); // number of grid points
223 int nr = nm*nx; // number of residual components
224 double value, norm;
225 norm = 0.0;
226 for (int ir = 0; ir < nr; ++ir) {
227 value = fabs(residual[ir]);
228 if (value > norm) {
229 norm = value;
230 }
231 }
232 return norm;
233 }
234
235 int NrIterator::solve(bool isContinuation)
236 {
237 int nm = system().mixture().nMonomer(); // number of monomer types
238 int np = system().mixture().nPolymer(); // number of polymer species
239 int nx = domain().nx(); // number of grid points
240 int nr = nm*nx; // number of residual elements
241
242 // Determine if isCanonical (iff all species ensembles are closed)
243 isCanonical_ = true;
244 Ensemble ensemble;
245 for (int i = 0; i < np; ++i) {
246 ensemble = system().mixture().polymer(i).ensemble();
247 if (ensemble == Ensemble::Unknown) {
248 UTIL_THROW("Unknown species ensemble");
249 }
250 if (ensemble == Ensemble::Open) {
251 isCanonical_ = false;
252 }
253 }
254
255 // If isCanonical, shift so that last element is zero.
256 // Note: This is one of the residuals in this case.
257 if (isCanonical_) {
258 double shift = wFields()[nm-1][nx-1];
259 int i, j;
260 for (i = 0; i < nm; ++i) {
261 for (j = 0; j < nx; ++j) {
262 wFields()[i][j] -= shift;
263 }
264 }
265 }
266
267 // Compute initial residual vector and norm
269 computeResidual(system().wFields(), system().cFields(), residual_);
270 double norm = residualNorm(residual_);
271
272 // Set Jacobian status
273 newJacobian_ = false;
274 if (!isContinuation) {
275 needsJacobian_ = true;
276 }
277
278 // Iterative loop
279 double normNew;
280 int i, j, k;
281 for (i = 0; i < maxItr_; ++i) {
282 Log::file() << "iteration " << i
283 << " , error = " << norm
284 << std::endl;
285
286 if (norm < epsilon_) {
287 Log::file() << "Converged" << std::endl;
289 // Success
290 return 0;
291 }
292
293 if (needsJacobian_) {
294 Log::file() << "Computing jacobian" << std::endl;;
295 computeJacobian();
296 newJacobian_ = true;
297 needsJacobian_ = false;
298 }
299
300 // Compute Newton-Raphson increment dOmega_
301 solver_.solve(residual_, dOmega_);
302
303 // Try full Newton-Raphson update
304 incrementWFields(system().wFields(), dOmega_, wFieldsNew_);
305 system().mixture().compute(wFieldsNew_, cFieldsNew_);
306 computeResidual(wFieldsNew_, cFieldsNew_, residualNew_);
307 normNew = residualNorm(residualNew_);
308
309 // Decrease increment if necessary
310 j = 0;
311 while (normNew > norm && j < 3) {
312 Log::file() << " decreasing increment, error = "
313 << normNew << std::endl;
314 needsJacobian_ = true;
315 for (k = 0; k < nr; ++k) {
316 dOmega_[k] *= 0.66666666;
317 }
318 incrementWFields(system().wFields(), dOmega_, wFieldsNew_);
319 system().mixture().compute(wFieldsNew_, cFieldsNew_);
320 computeResidual(wFieldsNew_, cFieldsNew_, residualNew_);
321 normNew = residualNorm(residualNew_);
322 ++j;
323 }
324
325 // If necessary, try reversing direction
326 if (normNew > norm) {
327 Log::file() << " reversing increment, norm = "
328 << normNew << std::endl;
329 needsJacobian_ = true;
330 for (k = 0; k < nr; ++k) {
331 dOmega_[k] *= -1.000;
332 }
333 incrementWFields(system().wFields(), dOmega_, wFieldsNew_);
334 system().mixture().compute(wFieldsNew_, cFieldsNew_);
335 computeResidual(wFieldsNew_, cFieldsNew_, residualNew_);
336 normNew = residualNorm(residualNew_);
337 }
338
339 // Accept or reject update
340 if (normNew < norm) {
341
342 // Update system fields and residual vector
343 for (j = 0; j < nm; ++j) {
344 for (k = 0; k < nx; ++k) {
345 system().wField(j)[k] = wFieldsNew_[j][k];
346 system().cField(j)[k] = cFieldsNew_[j][k];
347 }
348 }
349 for (j = 0; j < nr; ++j) {
350 residual_[j] = residualNew_[j];
351 }
352 newJacobian_ = false;
353 if (!needsJacobian_) {
354 if (normNew/norm > 0.5) {
355 needsJacobian_ = true;
356 }
357 }
358 norm = normNew;
359 } else {
360 Log::file() << "Iteration failed, norm = "
361 << normNew << std::endl;
362 if (newJacobian_) {
363 return 1;
364 Log::file() << "Unrecoverable failure " << std::endl;
365 } else {
366 Log::file() << "Try rebuilding Jacobian" << std::endl;
367 needsJacobian_ = true;
368 }
369 }
370
371 }
372
373 // Failure
374 return 1;
375 }
376
377} // namespace R1d
378} // namespace Pscf
DMatrix< double > const & chi() const
Return the chi matrix by const reference.
void allocate(int n)
Allocate memory.
Definition LuSolver.cpp:46
int nPolymer() const
Get number of polymer species.
int nMonomer() const
Get number of monomer types.
PolymerT & polymer(int id)
Get a polymer solver object by non-const reference.
int nx() const
Get number of spatial grid points, including both endpoints.
Iterator()
Default constructor.
void compute(DArray< FieldT > const &wFields, DArray< FieldT > &cFields)
Compute concentrations.
int solve(bool isContinuation=false)
Iterate self-consistent field equations to solution.
void readParameters(std::istream &in)
Read all parameters and initialize.
virtual ~NrIterator()
Destructor.
NrIterator()
Default constructor.
System::WField & wField(int monomerId)
Get chemical potential field for a specific monomer type.
const Domain & domain() const
Get spatial domain (including grid info) by reference.
DArray< System::CField > & cFields()
Get array of all chemical potential fields.
DArray< System::WField > & wFields()
Get array of all chemical potential fields.
const System & system() const
Get parent System by reference.
Main class in SCFT simulation of one system.
Interaction & interaction()
Get interaction (i.e., excess free energy) by reference.
void computeFreeEnergy()
Compute free energy density and pressure for current fields.
Field & cField(int monomerId)
Get chemical potential field for a specific monomer type.
WField & wField(int monomerId)
Get chemical potential field for a specific monomer type.
Mixture & mixture()
Get Mixture by reference.
Array container class template.
Definition Array.h:40
int capacity() const
Return allocated size.
Definition Array.h:144
void allocate(int capacity)
Allocate the underlying C array.
Definition DArray.h:269
void allocate(int capacity1, int capacity2)
Allocate memory for a matrix.
Definition DMatrix.h:170
static std::ostream & file()
Get log ostream by reference.
Definition Log.cpp:59
ScalarParam< Type > & read(std::istream &in, const char *label, Type &value)
Add and read a new required ScalarParam < Type > object.
void setClassName(const char *className)
Set class name string.
ScalarParam< Type > & readOptional(std::istream &in, const char *label, Type &value)
Add and read a new optional ScalarParam < Type > object.
#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
Ensemble
Statistical ensemble type for the number of molecules of one species.
Definition Ensemble.h:23
SCFT with real 1D fields.
PSCF package top-level namespace.