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