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