PSCF v1.1
fd1d/solvers/Block.cpp
1/*
2* PSCF - Polymer Self-Consistent Field Theory
3*
4* Copyright 2016 - 2022, The Regents of the University of Minnesota
5* Distributed under the terms of the GNU General Public License.
6*/
7
8#include "Block.h"
9#include <fd1d/domain/Domain.h>
10
11namespace Pscf {
12namespace Fd1d
13{
14
15 using namespace Util;
16
17 /*
18 * Constructor.
19 */
21 : domainPtr_(0),
22 ds_(0.0),
23 dsTarget_(0.0),
24 ns_(0)
25 {
26 propagator(0).setBlock(*this);
27 propagator(1).setBlock(*this);
28 }
29
30 /*
31 * Destructor.
32 */
34 {}
35
36 void Block::setDiscretization(Domain const & domain, double ds)
37 {
38 UTIL_CHECK(length() > 0);
39 UTIL_CHECK(domain.nx() > 1);
40 UTIL_CHECK(ds > 0.0);
41
42 // Set association to spatial domain
43 domainPtr_ = &domain;
44
45 // Set contour length discretization
46 dsTarget_ = ds;
47 ns_ = floor(length()/dsTarget_ + 0.5) + 1;
48 if (ns_%2 == 0) {
49 ns_ += 1;
50 }
51 ds_ = length()/double(ns_ - 1);
52
53 // Allocate all required memory
54 int nx = domain.nx();
55 dA_.allocate(nx);
56 dB_.allocate(nx);
57 uA_.allocate(nx - 1);
58 uB_.allocate(nx - 1);
59 lA_.allocate(nx - 1);
60 lB_.allocate(nx - 1);
61 v_.allocate(nx);
62 solver_.allocate(nx);
63 propagator(0).allocate(ns_, nx);
64 propagator(1).allocate(ns_, nx);
65 cField().allocate(nx);
66 }
67
68 void Block::setLength(double newLength)
69 {
71 if (dsTarget_ > 0) { // if setDiscretization has already been called
72 // Reset contour length discretization
73 UTIL_CHECK(ns_ > 0);
74 int oldNs = ns_;
75 ns_ = floor(length()/dsTarget_ + 0.5) + 1;
76 if (ns_%2 == 0) {
77 ns_ += 1;
78 }
79 ds_ = length()/double(ns_ - 1);
80
81 if (oldNs != ns_) {
82 // If propagators are already allocated and ns_ has changed,
83 // reallocate memory for solutions to MDE
84 propagator(0).reallocate(ns_);
85 propagator(1).reallocate(ns_);
86 }
87 }
88 }
89
90 /*
91 * Setup the contour length step algorithm.
92 *
93 * This implementation uses the Crank-Nicholson algorithm for stepping
94 * the modified diffusion equation. One step of this algorithm, which
95 * is implemented by the step() function, solves a matrix equation of
96 * the form
97 *
98 * A q(i) = B q(i-1)
99 *
100 * where A and B are domain().nx() x domain().nx() symmetric tridiagonal
101 * matrices given by
102 *
103 * A = 1 + 0.5*ds_*H
104 * B = 1 - 0.5*ds_*H
105 *
106 * in which ds_ is the contour step and
107 *
108 * H = -(b^2/6)d^2/dx^2 + w
109 *
110 * is a finite difference representation of the "Hamiltonian"
111 * operator, in which b = kuhn() is the statistical segment length.
112 *
113 * This function sets up arrays containing diagonal and off-diagonal
114 * elements of the matrices A and B, and computes the LU
115 * decomposition of matrix A. Arrays of domain().nx() diagonal elements
116 * of A and B are denoted by dA_ and dB_, respectively, while arrays of
117 * domain().nx() - 1 upper and lower off-diagonal elements of A and B
118 * are denoted by uA_, lA_, uB_, and lB_, respectively
119 */
121 {
122 // Preconditions
123 UTIL_CHECK(domainPtr_);
124 int nx = domain().nx();
125 UTIL_CHECK(nx > 0);
126 UTIL_CHECK(dA_.capacity() == nx);
127 UTIL_CHECK(uA_.capacity() == nx - 1);
128 UTIL_CHECK(lA_.capacity() == nx - 1);
129 UTIL_CHECK(dB_.capacity() == nx);
130 UTIL_CHECK(uB_.capacity() == nx - 1);
131 UTIL_CHECK(lB_.capacity() == nx - 1);
132 UTIL_CHECK(ns_ > 0);
133 UTIL_CHECK(propagator(0).isAllocated());
134 UTIL_CHECK(propagator(1).isAllocated());
135
136 // Set step size (in case block length has changed)
137 ds_ = length()/double(ns_ - 1);
138
139 // Chemical potential terms in matrix A
140 double halfDs = 0.5*ds_;
141 for (int i = 0; i < nx; ++i) {
142 dA_[i] = halfDs*w[i];
143 }
144
145 // Second derivative terms in matrix A
146 double dx = domain().dx();
147 double db = kuhn()/dx;
148 double c1 = halfDs*db*db/6.0;
149 double c2 = 2.0*c1;
150 GeometryMode mode = domain().mode();
151 if (mode == Planar) {
152
153 dA_[0] += c2;
154 uA_[0] = -c2;
155 for (int i = 1; i < nx - 1; ++i) {
156 dA_[i] += c2;
157 uA_[i] = -c1;
158 lA_[i-1] = -c1;
159 }
160 dA_[nx - 1] += c2;
161 lA_[nx - 2] = -c2;
162
163 } else {
164
165 double xMin = domain().xMin();
166 double xMax = domain().xMax();
167 double halfDx = 0.5*dx;
168 double x, rp, rm;
169 bool isShell = domain().isShell();
170
171 // First row: x = xMin
172 if (isShell) {
173 rp = 1.0 + halfDx/xMin;
174 if (mode == Spherical) {
175 rp *= rp;
176 }
177 } else {
178 if (mode == Spherical) {
179 rp = 3.0;
180 } else
181 if (mode == Cylindrical) {
182 rp = 2.0;
183 } else {
184 UTIL_THROW("Invalid GeometryMode");
185 }
186 }
187 rp *= c1;
188 dA_[0] += 2.0*rp;
189 uA_[0] = -2.0*rp;
190
191 // Interior rows
192 for (int i = 1; i < nx - 1; ++i) {
193 x = xMin + dx*i;
194 rm = 1.0 - halfDx/x;
195 rp = 1.0 + halfDx/x;
196 if (mode == Spherical) {
197 rm *= rm;
198 rp *= rp;
199 }
200 rm *= c1;
201 rp *= c1;
202 dA_[i] += rm + rp;
203 uA_[i] = -rp;
204 lA_[i-1] = -rm;
205 }
206
207 // Last row: x = xMax
208 rm = 1.0 - halfDx/xMax;
209 if (mode == Spherical) {
210 rm *= rm;
211 }
212 rm *= c1;
213 dA_[nx-1] += 2.0*rm;
214 lA_[nx-2] = -2.0*rm;
215 }
216
217 // Construct matrix B - 1
218 for (int i = 0; i < nx; ++i) {
219 dB_[i] = -dA_[i];
220 }
221 for (int i = 0; i < nx - 1; ++i) {
222 uB_[i] = -uA_[i];
223 }
224 for (int i = 0; i < nx - 1; ++i) {
225 lB_[i] = -lA_[i];
226 }
227
228 // Add diagonal identity terms to matrices A and B
229 for (int i = 0; i < nx; ++i) {
230 dA_[i] += 1.0;
231 dB_[i] += 1.0;
232 }
233
234 // Compute the LU decomposition of matrix A
235 solver_.computeLU(dA_, uA_, lA_);
236 }
237
238 /*
239 * Integrate to calculate monomer concentration for this block
240 */
241 void Block::computeConcentration(double prefactor)
242 {
243 // Preconditions
244 UTIL_CHECK(domain().nx() > 0);
245 UTIL_CHECK(ns_ > 0);
246 UTIL_CHECK(ds_ > 0);
247 UTIL_CHECK(dA_.capacity() == domain().nx());
248 UTIL_CHECK(dB_.capacity() == domain().nx());
249 UTIL_CHECK(uA_.capacity() == domain().nx() -1);
250 UTIL_CHECK(uB_.capacity() == domain().nx() -1);
251 UTIL_CHECK(propagator(0).isAllocated());
252 UTIL_CHECK(propagator(1).isAllocated());
253 UTIL_CHECK(cField().capacity() == domain().nx())
254
255 // Initialize cField to zero at all points
256 int i;
257 int nx = domain().nx();
258 for (i = 0; i < nx; ++i) {
259 cField()[i] = 0.0;
260 }
261
262 Propagator const & p0 = propagator(0);
263 Propagator const & p1 = propagator(1);
264
265 // Evaluate unnormalized integral with respect to s
266 // Uses trapezoidal rule for integration
267 for (i = 0; i < nx; ++i) {
268 cField()[i] += 0.5*p0.q(0)[i]*p1.q(ns_ - 1)[i];
269 }
270 for (int j = 1; j < ns_ - 1; ++j) {
271 for (i = 0; i < nx; ++i) {
272 cField()[i] += p0.q(j)[i]*p1.q(ns_ - 1 - j)[i];
273 }
274 }
275 for (i = 0; i < nx; ++i) {
276 cField()[i] += 0.5*p0.q(ns_ - 1)[i]*p1.q(0)[i];
277 }
278
279 // Normalize
280 prefactor *= ds_;
281 for (i = 0; i < nx; ++i) {
282 cField()[i] *= prefactor;
283 }
284
285 }
286
287 /*
288 * Propagate solution by one step.
289 *
290 * This function implements one step of the Crank-Nicholson algorithm.
291 * To do so, it solves A q(i+1) = B q(i), where A and B are constant
292 * matrices defined in the documentation of the setupStep() function.
293 */
295 {
296 int nx = domain().nx();
297 v_[0] = dB_[0]*q[0] + uB_[0]*q[1];
298 for (int i = 1; i < nx - 1; ++i) {
299 v_[i] = dB_[i]*q[i] + lB_[i-1]*q[i-1] + uB_[i]*q[i+1];
300 }
301 v_[nx - 1] = dB_[nx-1]*q[nx-1] + lB_[nx-2]*q[nx-2];
302 solver_.solve(v_, qNew);
303 }
304
305}
306}
double length() const
Get the length (number of monomers) in this block.
virtual void setLength(double length)
Set the length of this block.
double kuhn() const
Get monomer statistical segment length.
Definition: BlockTmpl.h:205
Propagator & propagator(int directionId)
Get a Propagator for a specified direction.
Definition: BlockTmpl.h:174
TP::CField & cField()
Get the associated monomer concentration field.
Definition: BlockTmpl.h:190
Domain const & domain() const
Return associated domain by reference.
virtual void setLength(double newLength)
Set length and readjust ds_ accordingly.
void step(DArray< double > const &q, DArray< double > &qNew)
Compute one step of integration loop, from i to i+1.
void setupSolver(DArray< double > const &w)
Set Crank-Nicholson solver for this block.
void setDiscretization(Domain const &domain, double ds)
Initialize discretization and allocate required memory.
void computeConcentration(double prefactor)
Compute concentration for block by integration.
One-dimensional spatial domain and discretization grid.
double dx() const
Get spatial grid step size.
bool isShell() const
Is this a cylindrical or spherical shell?
double xMin() const
Get minimum spatial coordinate.
double xMax() const
Get maximum spatial coordinate.
GeometryMode const & mode() const
Get coordinate system flag (Planar, Cylindrical or Spherical).
int nx() const
Get number of spatial grid points, including both endpoints.
void computeLU(const DArray< double > &d, const DArray< double > &u)
Compute LU decomposition of a symmetric tridiagonal matrix.
void solve(const DArray< double > &b, DArray< double > &x)
Solve Ax = b for known b to compute x.
void allocate(int n)
Allocate memory.
int capacity() const
Return allocated size.
Definition: Array.h:159
Dynamically allocatable contiguous array template.
Definition: DArray.h:32
void allocate(int capacity)
Allocate the underlying C array.
Definition: DArray.h:199
#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
GeometryMode
Enumeration of geometrical modes for functions of one coordinate.
Definition: GeometryMode.h:30
C++ namespace for polymer self-consistent field theory (PSCF).
Utility classes for scientific computation.
Definition: accumulators.mod:1