PSCF v1.4.0
cpc/solvers/Block.tpp
1#ifndef CPC_BLOCK_TPP
2#define CPC_BLOCK_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 "Block.h"
12#include "Propagator.h"
13
14#include <prdc/cpu/WaveList.h>
15#include <prdc/cpu/FFT.h>
16#include <pscf/cpu/complex.h>
17#include <prdc/crystal/UnitCell.h>
18#include <prdc/crystal/shiftToMinimum.h>
19
20#include <pscf/chem/Edge.h>
21#include <pscf/mesh/Mesh.h>
22#include <pscf/mesh/MeshIterator.h>
23#include <pscf/math/IntVec.h>
24#include <pscf/solvers/BlockTmpl.tpp>
25
26#include <util/containers/DMatrix.h>
27#include <util/containers/DArray.h>
28#include <util/containers/FSArray.h>
29
30#include <cmath>
31
32namespace Pscf {
33namespace Cpc {
34
35 // Namespaces from which names may be used without qualification
36 using namespace Util;
37 using namespace Pscf::Prdc;
38 using namespace Pscf::Prdc::Cpu;
39
40 /*
41 * Constructor.
42 */
43 template <int D>
45 : meshPtr_(nullptr),
46 fftPtr_(nullptr),
47 unitCellPtr_(nullptr),
48 waveListPtr_(nullptr),
49 ds_(-1.0),
50 dsTarget_(-1.0),
51 ns_(-1),
52 isAllocated_(false),
53 hasExpKsq_(false)
54 {
55 propagator(0).setBlock(*this);
56 propagator(1).setBlock(*this);
57 }
58
59 /*
60 * Destructor.
61 */
62 template <int D>
65
66 /*
67 * Store addresses of mesh, FFT and unit cell.
68 */
69 template <int D>
70 void Block<D>::associate(Mesh<D> const & mesh,
71 FFT<D> const& fft,
72 UnitCell<D> const& cell,
73 WaveList<D>& wavelist)
74 {
75 // Preconditions
76 UTIL_CHECK(!isAllocated_);
77
78 // Set pointers to mesh and fft
79 meshPtr_ = &mesh;
80 fftPtr_ = &fft;
81 unitCellPtr_ = &cell;
82 waveListPtr_ = &wavelist;
83
84 hasExpKsq_ = false;
85 }
86
87 /*
88 * Compute number of contour steps and allocate all memory.
89 */
90 template <int D>
92 {
93 UTIL_CHECK(ds > 0.0);
94 UTIL_CHECK(meshPtr_);
95 UTIL_CHECK(fftPtr_);
96 UTIL_CHECK(unitCellPtr_);
97 UTIL_CHECK(waveListPtr_);
98 UTIL_CHECK(mesh().size() > 1);
99 UTIL_CHECK(mesh().dimensions() == fft().meshDimensions());
100 UTIL_CHECK(!isAllocated_);
101
102 // Allocate work arrays for MDE solution
103 expKsq_.allocate(mesh().dimensions());
104 expKsq2_.allocate(mesh().dimensions());
105 expW_.allocate(mesh().dimensions());
106 qr_.allocate(mesh().dimensions());
107 qr2_.allocate(mesh().dimensions());
108 qk_.allocate(mesh().dimensions());
109 qk2_.allocate(mesh().dimensions());
111 expW2_.allocate(mesh().dimensions());
112 } else {
113 expWInv_.allocate(mesh().dimensions());
114 }
115
116 // Allocate block concentration field
117 cField().allocate(mesh().dimensions());
118
119 dsTarget_ = ds;
120
121 // Compute ns_
123 // Set contour length discretization for this block
124 UTIL_CHECK(length() > 0.0);
125 int tempNs;
126 tempNs = floor(length() / (2.0 *ds) + 0.5);
127 if (tempNs == 0) {
128 tempNs = 1; // ensure ns_ >= 3
129 }
130 ns_ = 2*tempNs + 1;
131 ds_ = length()/double(ns_-1);
132 } else
133 if (PolymerModel::isBead()) {
134 ds_ = 1.0;
135 ns_ = nBead() + 2;
136 }
137
138 // Allocate memory for solutions to MDE (requires ns_)
139 propagator(0).allocate(ns_, mesh());
140 propagator(1).allocate(ns_, mesh());
141
142 isAllocated_ = true;
143 hasExpKsq_ = false;
144 }
145
146 /*
147 * Set or reset the the block length.
148 */
149 template <int D>
150 void Block<D>::setLength(double newLength)
151 {
153 Edge::setLength(newLength);
154
155 if (isAllocated_) {
156
157 int oldNs = ns_;
158
159 // Reset contour length discretization
160 UTIL_CHECK(dsTarget_ > 0);
161 int tempNs;
162 tempNs = floor( length()/(2.0 *dsTarget_) + 0.5 );
163 if (tempNs == 0) {
164 tempNs = 1;
165 }
166 ns_ = 2*tempNs + 1;
167 ds_ = length()/double(ns_-1);
168
169 // Reallocate propagators if ns_ has changed
170 if (oldNs != ns_) {
171 propagator(0).reallocate(ns_);
172 propagator(1).reallocate(ns_);
173 }
174
175 }
176 hasExpKsq_ = false;
177 }
178
179 /*
180 * Set or reset the the block length.
181 */
182 template <int D>
184 {
186 hasExpKsq_ = false;
187 }
188
189 /*
190 * Clear all internal data that depends on the unit cell parameters.
191 */
192 template <int D>
194 {
195 hasExpKsq_ = false;
196 }
197
198 /*
199 * Compute all elements of expKsq_ and expKsq2_ arrays
200 */
201 template <int D>
202 void Block<D>::computeExpKsq()
203 {
204 UTIL_CHECK(isAllocated_);
205 UTIL_CHECK(waveListPtr_);
206 UTIL_CHECK(waveListPtr_->isAllocated());
207
208 // Calculate KSq if necessary
209 if (!waveListPtr_->hasKSq()) {
210 waveListPtr_->computeKSq();
211 }
212 RField<D> const & kSq = waveListPtr_->kSq();
213 UTIL_CHECK(kSq.capacity() == mesh().size());
214
215 // Compute bSqFactor = b*b*ds/6
216 bool isThread = PolymerModel::isThread();
217 double bSqFactor = -1.0*kuhn()*kuhn() / 6.0;
218 if (isThread) {
219 bSqFactor *= ds_;
220 }
221
222 // Compute expKsq arrays
223 MeshIterator<D> iter;
224 iter.setDimensions(mesh().dimensions());
225 double arg;
226 int i;
227 for (iter.begin(); !iter.atEnd(); ++iter) {
228 i = iter.rank();
229 arg = kSq[i]*bSqFactor;
230 expKsq_[i] = exp(arg);
231 expKsq2_[i] = exp(0.5*arg);
232 }
233
234 hasExpKsq_ = true;
235 }
236
237 /*
238 * Setup the the step algorithm for a specific field configuration.
239 */
240 template <int D>
241 void
243 {
244 // Preconditions
245 int nx = mesh().size();
246 UTIL_CHECK(nx > 0);
247 UTIL_CHECK(isAllocated_);
248
249 // Compute expW arrays
250 fftw_complex arg, arg2;
252 double c = -0.5*ds_;
253 double c2 = -0.25*ds_;
254 for (int i = 0; i < nx; ++i) {
255
256 mul(arg, w[i], c);
257 mul(arg2, w[i], c2);
258 assignExp(expW_[i], arg);
259 assignExp(expW2_[i], arg2);
260
261 //arg = c*w[i];
262 //expW_[i] = exp(arg);
263 //expW2_[i] = exp(0.5*arg);
264
265 }
266 } else
267 if (PolymerModel::isBead()) {
268 for (int i = 0; i < nx; ++i) {
269
270 mul(arg, w[i], -1.0);
271 assignExp(expW_[i], arg);
272 inverse(expWInv_[i], expW_[i]);
273
274 //arg = -w[i];
275 //expW_[i] = exp(arg);
276 //expWInv_[i] = 1.0/expW_[i];
277
278 }
279 }
280
281 // Compute expKsq arrays if necessary
282 if (!hasExpKsq_) {
283 computeExpKsq();
284 }
285
286 }
287
288 /*
289 * Propagate solution by one step for the thread model.
290 */
291 template <int D>
292 void Block<D>::stepThread(CField<D> const & q, CField<D>& qout) const
293 {
295
296 // Preconditions on mesh and fft
297 int nx = mesh().size();
298 UTIL_CHECK(nx > 0);
299 UTIL_CHECK(fft().isSetup());
300 UTIL_CHECK(mesh().dimensions() == fft().meshDimensions());
301
302 // Internal preconditions
303 UTIL_CHECK(isAllocated_);
304 UTIL_CHECK(qk_.capacity() == nx);
305 UTIL_CHECK(expKsq_.capacity() == nx);
306 UTIL_CHECK(expKsq2_.capacity() == nx);
307 UTIL_CHECK(qr_.capacity() == nx);
308 UTIL_CHECK(expW_.capacity() == nx);
309 UTIL_CHECK(expW2_.capacity() == nx);
310 UTIL_CHECK(hasExpKsq_);
311
312 // Preconditions on parameters
314 UTIL_CHECK(q.capacity() == nx);
315 UTIL_CHECK(qout.isAllocated());
316 UTIL_CHECK(qout.capacity() == nx);
317
318 // Apply pseudo-spectral algorithm
319
320 // Step by ds/2 for qr_, step by ds/4 for qr2_
321 int i;
322 for (i = 0; i < nx; ++i) {
323
324 mul(qr_[i], q[i], expW_[i] );
325 mul(qr2_[i], q[i], expW2_[i]);
326
327 // qr_[i] = q[i]*expW_[i];
328 // qr2_[i] = q[i]*expW2_[i];
329 }
330 fft().forwardTransform(qr_, qk_);
331 fft().forwardTransform(qr2_, qk2_);
332 for (i = 0; i < nx; ++i) {
333 qk_[i][0] *= expKsq_[i];
334 qk_[i][1] *= expKsq_[i];
335 qk2_[i][0] *= expKsq2_[i];
336 qk2_[i][1] *= expKsq2_[i];
337 }
338 fft().inverseTransform(qk_, qr_);
339 fft().inverseTransform(qk2_, qr2_);
340 for (i = 0; i < nx; ++i) {
341 mulEq(qr_[i], expW_[i]);
342 mulEq(qr2_[i], expW_[i]);
343 // qr_[i] = qr_[i]*expW_[i];
344 // qr2_[i] = qr2_[i]*expW_[i];
345 }
346
347 // Note: Above, multiplying qr2_ by expW_, rather than by expW2_,
348 // combines required multiplications by expW2_ at the end of first
349 // half-step and at the beginning of the second.
350
351 // Finish second half-step of ds/2 for qr2_
352 fft().forwardTransform(qr2_, qk2_);
353 for (i = 0; i < nx; ++i) {
354 qk2_[i][0] *= expKsq2_[i];
355 qk2_[i][1] *= expKsq2_[i];
356 }
357 fft().inverseTransform(qk2_, qr2_);
358 for (i = 0; i < nx; ++i) {
359 mulEq(qr2_[i], expW2_[i]);
360 //qr2_[i] = qr2_[i]*expW2_[i];
361 }
362
363 // Richardson extrapolation
364 for (i = 0; i < nx; ++i) {
365 qout[i][0] = (4.0*qr2_[i][0] - qr_[i][0])/3.0;
366 qout[i][1] = (4.0*qr2_[i][1] - qr_[i][1])/3.0;
367 //qout[i] = (4.0*qr2_[i] - qr_[i])/3.0;
368 }
369
370 }
371
372 /*
373 * Apply one step of MDE solution for the bead model.
374 */
375 template <int D>
376 void Block<D>::stepBead(CField<D> const & q, CField<D>& qout) const
377 {
379 stepBondBead(q, qout);
380 stepFieldBead(qout);
381 }
382
383 /*
384 * Apply the bond operator for the bead model.
385 */
386 template <int D>
388 CField<D> & qout) const
389 {
390 // Prereconditions
391 UTIL_CHECK(isAllocated_);
392 UTIL_CHECK(hasExpKsq_);
393 int nx = mesh().size();
394 UTIL_CHECK(nx > 0);
395 UTIL_CHECK(q.capacity() == nx);
396 UTIL_CHECK(qout.capacity() == nx);
397 UTIL_CHECK(expKsq_.capacity() == nx);
398
399 // Apply bond operator
400 fft().forwardTransform(q, qk_);
401 for (int i = 0; i < nx; ++i) {
402 qk_[i][0] *= expKsq_[i];
403 qk_[i][1] *= expKsq_[i];
404 }
405 fft().inverseTransform(qk_, qout);
406 }
407
408 /*
409 * Apply the half-bond operator for the bead model.
410 */
411 template <int D>
413 CField<D> & qout) const
414 {
415 // Preconditions
416 UTIL_CHECK(isAllocated_);
417 UTIL_CHECK(hasExpKsq_);
418 int nx = mesh().size();
419 UTIL_CHECK(nx > 0);
420 UTIL_CHECK(q.capacity() == nx);
421 UTIL_CHECK(qout.capacity() == nx);
422 UTIL_CHECK(qk_.capacity() == nx);
423 UTIL_CHECK(expKsq2_.capacity() == nx);
424
425 // Apply bond operator
426 fft().forwardTransform(q, qk_);
427 for (int i = 0; i < nx; ++i) {
428 qk_[i][0] *= expKsq2_[i];
429 qk_[i][1] *= expKsq2_[i];
430 }
431 fft().inverseTransform(qk_, qout); // destroys qk_
432 }
433
434 /*
435 * Apply the local field operator for the bead model.
436 */
437 template <int D>
439 {
440 // Preconditions
441 int nx = mesh().size();
442 UTIL_CHECK(nx > 0);
443 UTIL_CHECK(expW_.capacity() == nx);
444 UTIL_CHECK(q.capacity() == nx);
445
446 // Apply field operator
447 for (int i = 0; i < nx; ++i) {
448 mulEq(q[i], expW_[i]);
449 //q[i] *= expW_[i];
450 }
451 }
452
453 /*
454 * Integrate to calculate monomer concentration for this block
455 */
456 template <int D>
457 void
458 Block<D>::computeConcentrationThread(fftw_complex const & prefactor)
459 {
460 // Preconditions
461 UTIL_CHECK(isAllocated_);
462 int nx = mesh().size();
463 UTIL_CHECK(nx > 0);
464 UTIL_CHECK(ns_ > 0);
465 UTIL_CHECK(propagator(0).isSolved());
466 UTIL_CHECK(propagator(1).isSolved());
467 UTIL_CHECK(cField().capacity() == nx);
468
469 CField<D> & c = cField();
470 double d;
471 int i, j;
472
473 // Initialize cField to zero at all points
474 d = 0.0;
475 for (i = 0; i < nx; ++i) {
476 assign(c[i], d);
477 }
478
479 // References to forward and reverse propagators
480 Propagator<D> const & p0 = propagator(0);
481 Propagator<D> const & p1 = propagator(1);
482 fftw_complex z;
483
484 // Evaluate unnormalized integral
485
486 // Initial (head) endpoint contribution
487 {
488 CField<D> const & hf = p0.q(0);
489 CField<D> const & hr = p1.q(ns_ - 1);
490 for (i = 0; i < nx; ++i) {
491 mul(z, hf[i], hr[i]);
492 addEq(c[i], z);
493 }
494 }
495
496 // Final (tail) endpoint contribution
497 {
498 CField<D> const & tf = p0.q(ns_ - 1);
499 CField<D> const & tr = p1.q(0);
500 for (i = 0; i < nx; ++i) {
501 mul(z, tf[i], tr[i]);
502 addEq(c[i], z);
503 }
504 }
505
506 // Odd indices
507 d = 4.0;
508 for (j = 1; j < (ns_ - 1); j += 2) {
509 CField<D> const & qf = p0.q(j);
510 CField<D> const & qr = p1.q(ns_ - 1 - j);
511 for (i = 0; i < nx; ++i) {
512 mul(z, qf[i], qr[i]);
513 mulEq(z, d);
514 addEq(c[i], z);
515 //c[i] += qf[i] * qr[i] * 4.0;
516 }
517 }
518
519 // Even indices
520 d = 2.0;
521 for (j = 2; j < (ns_ - 2); j += 2) {
522 CField<D> const & qf = p0.q(j);
523 CField<D> const & qr = p1.q(ns_ - 1 - j);
524 for (i = 0; i < nx; ++i) {
525 mul(z, qf[i], qr[i]);
526 mulEq(z, d);
527 addEq(c[i], z);
528 // c[i] += qf[i] * qr[i] * 2.0;
529 }
530 }
531
532 // Normalize the integral
533 d = ds_ / 3.0;
534 mul(z, prefactor, d);
535 // z = prefactor * ds_ / 3.0
536 for (i = 0; i < nx; ++i) {
537 mulEq(c[i], z);
538 // c[i] *= z;
539 }
540
541 }
542
543 /*
544 * Calculate monomer concentration for this block, bead model.
545 */
546 template <int D>
547 void Block<D>::computeConcentrationBead(fftw_complex const & prefactor)
548 {
549 // Preconditions
550 UTIL_CHECK(isAllocated_);
551 int nx = mesh().size();
552 UTIL_CHECK(nx > 0);
553 UTIL_CHECK(ns_ > 0);
554 UTIL_CHECK(propagator(0).isSolved());
555 UTIL_CHECK(propagator(1).isSolved());
556 UTIL_CHECK(cField().capacity() == nx);
557
558 CField<D> & c = cField();
559 fftw_complex z;
560 double d;
561 int i, j;
562
563 // Initialize cField to zero at all points
564 d = 0.0;
565 for (i = 0; i < nx; ++i) {
566 assign(c[i], d);
567 // c[i] = 0.0;
568 }
569
570 // References to forward and reverse propagators
571 Propagator<D> const & p0 = propagator(0);
572 Propagator<D> const & p1 = propagator(1);
573
574 // Sum over interior beads (j = 1, ... , ns_ -2)
575 for (j = 1; j < (ns_ -1); ++j) {
576 CField<D> const & qf = p0.q(j);
577 CField<D> const & qr = p1.q(ns_ - 1 - j);
578 for (i = 0; i < nx; ++i) {
579 mul(z, qf[i], qr[i]);
580 mulEq(z, expWInv_[i]);
581 addEq(c[i], z);
582 //c[i] += qf[i] * qr[i] * expWInv_[i];
583 }
584 }
585
586 // Note: Slices j = 0 and j = ns_ - 1 are phantom vertices
587
588 // Normalize the integral
589 for (i = 0; i < nx; ++i) {
590 mulEq(c[i], prefactor);
591 //c[i] *= prefactor;
592 }
593 }
594
595}
596}
597#endif
void clearUnitCellData()
Clear all internal data that depends on the unit cell parameters.
void stepFieldBead(CField< D > &q) const
Apply the exponential field operator for the bead model.
int nBead() const
Get the number of beads in this block, in the bead model.
Definition Edge.h:302
void computeConcentrationThread(fftw_complex const &prefactor)
Compute the concentration for this block, for the thread model.
void computeConcentrationBead(fftw_complex const &prefactor)
Compute the concentration for this block, using the bead model.
void allocate(double ds)
Allocate memory and set number of counter steps.
void stepBead(CField< D > const &qin, CField< D > &qout) const
Compute one step of solution of MDE for the bead model.
void stepHalfBondBead(CField< D > const &qin, CField< D > &qout) const
Apply a half-bond operator for the bead model.
void setLength(double newLength)
Set or reset block length (only used in thread model).
double ds() const
Get contour length step size.
void stepBondBead(CField< D > const &qin, CField< D > &qout) const
Apply a bond operator for the bead model.
void setKuhn(double kuhn)
Set or reset monomer statistical segment length.
void stepThread(CField< D > const &qin, CField< D > &qout) const
Compute one step of solution of MDE for the thread model.
CField< D > & cField()
Get the associated monomer concentration field.
double length() const
Get the length of this block, in the thread model.
Definition Edge.h:311
double kuhn() const
Get monomer statistical segment length.
Propagator< D > & propagator(int directionId)
Get a Propagator for a specified direction.
void associate(Mesh< D > const &mesh, FFT< D > const &fft, UnitCell< D > const &cell, WaveList< D > &wavelist)
Create permanent associations with related objects.
void setupSolver(CField< D > const &w)
Set up the MDE solver for this block.
MDE solver for one direction of one block.
const FieldT & q(int i) const
Return slice of q-field at a specified step.
virtual void setLength(double length)
Set the length of this block (only valid for thread model).
Definition Edge.cpp:62
Iterator over points in a Mesh<D>.
int rank() const
Get the rank of current element.
void begin()
Set iterator to the first point in the mesh.
bool atEnd() const
Is this the end (i.e., one past the last point)?
void setDimensions(const IntVec< D > &dimensions)
Set the grid dimensions in all directions.
Description of a regular grid of points in a periodic domain.
Definition Mesh.h:61
Field of complex double precision values on an FFT mesh.
Definition cpu/CField.h:29
Fourier transform wrapper.
Definition cpu/FFT.h:39
bool isAllocated() const
Return true if the FftwDArray has been allocated, false otherwise.
Definition FftwDArray.h:107
Field of real double precision values on an FFT mesh.
Definition cpu/RField.h:27
Class to compute and store properties associated with wavevectors.
Base template for UnitCell<D> classes, D=1, 2 or 3.
Definition UnitCell.h:56
int capacity() const
Return allocated size.
Definition Array.h:144
#define UTIL_CHECK(condition)
Assertion macro suitable for serial or parallel production code.
Definition global.h:68
void mulEq(fftw_complex &a, fftw_complex const &b)
In-place multiplication of two complex number, a *= b.
void addEq(fftw_complex &a, fftw_complex const &b)
In-place addition of fftw_complex numbers, a += b.
void inverse(fftw_complex &z, fftw_complex const &a)
Inversion of an fftw_complex number, z = 1 / a .
void assignExp(fftw_complex &z, fftw_complex const &a)
Exponentation of a ffts_complex variable, z = exp(a).
void assign(fftw_complex &z, double const &a, double const &b)
Create an fftw_complex from real and imaginary parts, z = a + ib.
void mul(fftw_complex &z, fftw_complex const &a, fftw_complex const &b)
Multiplication of fftw_complex numbers, z = a * b.
Complex periodic fields, CL-FTS (CPU).
Definition cpc.mod:6
bool isThread()
Is the thread model in use ?
bool isBead()
Is the bead model in use ?
Fields and FFTs for periodic boundary conditions (CPU)
Definition complex.cpp:12
Periodic fields and crystallography.
Definition complex.cpp:11
PSCF package top-level namespace.