PSCF v1.1
FilmIteratorBase.tpp
1#ifndef PSPC_FILM_ITERATOR_BASE_TPP
2#define PSPC_FILM_ITERATOR_BASE_TPP
3
4/*
5* PSCF - Polymer Self-Consistent Field Theory
6*
7* Copyright 2016 - 2021, The Regents of the University of Minnesota
8* Distributed under the terms of the GNU General Public License.
9*/
10
11#include "FilmIteratorBase.h"
12#include "pscf/crystal/UnitCell.h"
13#include "pscf/crystal/groupFile.h"
14#include "pscf/math/RealVec.h"
15#include "pscf/crystal/SpaceGroup.h"
16#include "pspc/System.h"
17#include "pspc/field/RField.h"
18#include "pspc/field/FieldIo.h"
19#include "util/containers/FArray.h"
20#include "util/format/Dbl.h"
21#include <cmath>
22#include <iostream>
23
24namespace Pscf {
25namespace Pspc
26{
27
28 using namespace Util;
29
30 /*
31 * Constructor.
32 */
33 template <int D, typename IteratorType>
35 : Iterator<D>(system),
36 iterator_(system),
37 parameters_(),
38 normalVecId_(-1),
39 t_(-1.0),
40 T_(-1.0),
41 chiBottom_(),
42 chiTop_(),
43 chiBottomCurrent_(),
44 chiTopCurrent_(),
45 ungenerated_(true)
46 {
47 setClassName(iterator_.className().append("FilmBase").c_str());
48 system.mask().setFieldIo(system.fieldIo());
49 system.h().setFieldIo(system.fieldIo());
50 }
51
52 /*
53 * Destructor.
54 */
55 template <int D, typename IteratorType>
57 {}
58
59 /*
60 * Read iterator and wall data from parameter file
61 */
62 template <int D, typename IteratorType>
64 {
65 // Make iterator_ a child paramComponent of this object, so that it
66 // will be read/written correctly to/from param file with correct
67 // indentation
68 setParent(iterator_,false);
69 addComponent(iterator_,false);
70
71 // Read the Iterator parameters
72 iterator_.readParameters(in);
73
74 // Read required data defining the walls
75 read(in, "normalVecId", normalVecId_);
76 read(in, "interfaceThickness", t_);
77 read(in, "wallThickness", T_);
79 // Make sure inputs are valid
80 if (normalVecId_ > D || normalVecId_ < 0) {
81 UTIL_THROW("bad value for normalVecId, must be in [0,D)");
82 }
83 if (t_ > T_) {
84 UTIL_THROW("wallThickness must be larger than interfaceThickness");
85 }
86 if ((T_ <= 0) || (t_ <= 0)) {
87 UTIL_THROW("wallThickness and interfaceThickness must be >0");
88 }
89
90 // Allocate chiBottom_ and chiTop_ and set to zero before
91 // reading them in
92 int nm = system().mixture().nMonomer();
93 chiBottom_.allocate(nm);
94 chiTop_.allocate(nm);
95 for (int i = 0; i < nm; i++) {
96 chiBottom_[i] = 0.0;
97 chiTop_[i] = 0.0;
98 }
99 readDArray(in, "chiBottom", chiBottom_, nm);
100 readDArray(in, "chiTop", chiTop_, nm);
101
102 // If lattice parameters are flexible, determine which parameters
103 // are allowed to vary, store them in this object, and pass them
104 // into iterator_. The flexibleParams_ member of the iterator_
105 // should always be matched to that of this class.
106 setFlexibleParams();
107 }
108
109 /*
110 * Allocate required memory, perform necessary checks to ensure user
111 * input is compatible with a film constraint, and create the mask /
112 * external fields that will be used to represent the walls during
113 * iteration.
114 */
115 template <int D, typename IteratorType>
117 {
118 UTIL_CHECK(system().basis().isInitialized());
119 UTIL_CHECK(system().unitCell().isInitialized());
120
121 // Allocate the mask and external field containers if needed
122 if (!system().mask().isAllocated()) {
123 system().mask().allocate(system().basis().nBasis(),
124 system().mesh().dimensions());
125 }
126 if (!isAthermal()) {
127 if (!system().h().isAllocatedRGrid()) {
128 system().h().allocateRGrid(system().mesh().dimensions());
129 }
130 if (!system().h().isAllocatedBasis()) {
131 system().h().allocateBasis(system().basis().nBasis());
132 }
133 }
135 // Ensure that space group symmetry is compatible with the wall
136 checkSpaceGroup();
137
138 if (ungenerated_) {
139 // Generate the field representation of the walls (and external
140 // fields if needed) and provide them to iterator_ to use during
141 // iteration.
142 generateWallFields();
143 ungenerated_ = false;
144 } else {
145 // Update the concentration field of the walls based on the
146 // current state of Domain<D> and provide it to iterator_, as
147 // well as the external fields for each species if needed
148 updateWallFields();
149 }
150 }
151
152 /*
153 * Iterate to a solution
154 */
155 template <int D, typename IteratorType>
157 {
158 // Setup the FilmIteratorBase object, set external fields and mask
159 setup();
160
161 // solve
162 return iterator_.solve(isContinuation);
163 }
164
165 /*
166 * Generate the concentration field for the walls and pass it into
167 * iterator. Adjust phi values of species in the system to accommodate
168 * the volume occupied by the wall. Finally, generate the external
169 * fields that are created by the wall, if needed (if the wall is not
170 * athermal), and provide them to the iterator.
171 */
172 template <int D, typename IteratorType>
174 {
175 UTIL_CHECK(interfaceThickness() > 0);
176 UTIL_CHECK(wallThickness() > interfaceThickness());
177 UTIL_CHECK(system().mask().isAllocated());
178
179 if (ungenerated_) ungenerated_ = false;
180
181 // Ensure that unit cell is compatible with wall
182 checkLatticeVectors();
183
184 // Get the length L of the lattice basis vector normal to the walls
185 RealVec<D> a;
186 a = system().domain().unitCell().rBasis(normalVecId_);
187 double norm_sqd(0.0); // norm squared
188 for (int i = 0; i < D; i++) {
189 norm_sqd += a[i]*a[i];
190 }
191 double L(sqrt(norm_sqd));
192
193 // Create a 3 element vector 'dim' that contains the grid dimensions.
194 // If system is 2D (1D), then the z (y & z) dimensions are set to 1.
195 IntVec<3> dim;
196 for (int ind = 0; ind < 3; ind++) {
197 if (ind < D) {
198 dim[ind] = system().domain().mesh().dimensions()[ind];
199 } else {
200 dim[ind] = 1;
201 }
202 }
203
204 // Generate an r-grid representation of the walls
205 RField<D> rGrid;
206 rGrid.allocate(system().domain().mesh().dimensions());
207 int x, y, z;
208 int counter = 0;
209 FArray<int,3> coords;
210 double d, rho_w;
211
212 for (x = 0; x < dim[0]; x++) {
213 coords[0] = x;
214 for (y = 0; y < dim[1]; y++) {
215 coords[1] = y;
216 for (z = 0; z < dim[2]; z++) {
217 coords[2] = z;
218
219 // Get the distance 'd' traveled along the lattice basis
220 // vector that is normal to the walls
221 d = coords[normalVecId_] * L / dim[normalVecId_];
223 // Calculate wall volume fraction (rho_w) at gridpoint (x,y,z)
224 rho_w = 0.5*(1+tanh(4*(((.5*(T_-L))+fabs(d-(L/2)))/t_)));
225 rGrid[counter++] = 1-rho_w;
226 }
227 }
228 }
229
230 // Store this mask in System
231 system().mask().setRGrid(rGrid,true);
232
233 // Store lattice parameters associated with this maskBasis
234 parameters_ = system().domain().unitCell().parameters();
236 // Generate external fields if needed
237 generateExternalFields();
238 }
239
240 /*
241 * Update the mask in the iterator (the region in which the polymers are
242 * confined) if the lattice parameters have been updated since mask was
243 * last generated. Also update external fields.
244 *
245 * If the lattice parameters have not changed but the wall/polymer chi
246 * parameters have been updated since external fields were last generated,
247 * update the external fields (but not the mask).
248 */
249 template <int D, typename IteratorType>
251 {
252 // Check if system lattice parameters are different than parameters_
253 FSArray<double, 6> sysParams =
254 system().domain().unitCell().parameters();
255 UTIL_CHECK(sysParams.size() == parameters_.size());
256 bool identical = true; // are the two arrays identical?
257 for (int i = 0; i < parameters_.size(); i++) {
258 if (fabs(sysParams[i] - parameters_[i]) > 1e-10) {
259 identical = false;
260 break;
261 }
262 }
263
264 // Regenerate wall fields if the lattice parameters have changed
265 if (!identical) {
266 generateWallFields();
267 } else {
268 // If we did not regenerate wall fields, check if we need to
269 // regenerate external field (if chi array has changed)
270 bool newExternalFields = false; // Do we need new external fields?
271 for (int i = 0; i < chiBottom_.capacity(); i++) {
272 if ((chiBottom_[i] != chiBottomCurrent_[i]) ||
273 (chiTop_[i] != chiTopCurrent_[i])) {
274 newExternalFields = true;
275 break;
276 }
277 }
278 if (newExternalFields) {
279 generateExternalFields();
280 }
281 }
282 }
283
284 /*
285 * Generate the external fields that are imposed as a result of chemical
286 * interactions between the walls and the monomer species.
287 */
288 template <int D, typename IteratorType>
290 {
291 // Set chiBottomCurrent_ and chiTopCurrent_ equal to the current chi
292 // arrays associated with the external fields that are about to be set
293 chiBottomCurrent_ = chiBottom_;
294 chiTopCurrent_ = chiTop_;
295
296 // If walls are athermal then there is no external field needed.
297 // If an external field already exists in the System, we need to
298 // overwrite it with a field of all zeros, otherwise do nothing
299 if ((isAthermal()) && (!system().h().hasData())) {
300 return;
301 }
302
303 // If this point is reached, external field must be generated
304 UTIL_CHECK(system().h().isAllocatedRGrid());
305 UTIL_CHECK(system().h().isAllocatedBasis());
306 int nm = system().mixture().nMonomer();
307
308 // Get length L of the lattice basis vector normal to the walls
309 RealVec<D> a;
310 a = system().domain().unitCell().rBasis(normalVecId_);
311 double norm_sqd(0.0); // norm squared
312 for (int i = 0; i < D; i++) {
313 norm_sqd += a[i]*a[i];
314 }
315 double L(sqrt(norm_sqd));
316
317 // Create a 3 element vector 'dim' that contains the grid
318 // dimensions. If system is 2D (1D), then the z (y and z)
319 // dimensions are set to 1.
320 IntVec<3> dim;
321 for (int ind = 0; ind < 3; ind++) {
322 if (ind < D) {
323 dim[ind] = system().domain().mesh().dimensions()[ind];
324 } else {
325 dim[ind] = 1;
326 }
327 }
328
329 // Generate an r-grid representation of the external fields
330 DArray< RField<D> > hRGrid;
331 hRGrid.allocate(nm);
332 for (int i = 0; i < nm; i++) {
333 hRGrid[i].allocate(system().domain().mesh().dimensions());
334 }
335
336 int i, x, y, z;
337 int counter = 0;
338 FArray<int,3> coords;
339 double d, rho_w;
340
341 for (i = 0; i < nm; i++) {
342 for (x = 0; x < dim[0]; x++) {
343 coords[0] = x;
344 for (y = 0; y < dim[1]; y++) {
345 coords[1] = y;
346 for (z = 0; z < dim[2]; z++) {
347 coords[2] = z;
348
349 // Get the distance 'd' traveled along the lattice
350 // basis vector that is orthogonal to the walls
351 d = coords[normalVecId_] * L / dim[normalVecId_];
352
353 // Calculate wall volume fraction (rho_w) at gridpoint
354 // (x,y,z)
355 rho_w = 0.5*(1+tanh(4*(((.5*(T_-L))+fabs(d-(L/2)))/t_)));
356 if (d < (L/2)) {
357 hRGrid[i][counter++] = rho_w * chiBottom_[i];
358 } else {
359 hRGrid[i][counter++] = rho_w * chiTop_[i];
360 }
361 }
362 }
363 }
364 counter = 0;
365 }
366
367 // Pass h into the System
368 system().h().setRGrid(hRGrid,true);
369 }
370
371 /*
372 * Check that user-defined space group is compatible
373 * with the thin film constraint
374 */
375 template <int D, typename IteratorType>
377 {
378 // Setup
379 std::string groupName = system().groupName();
380 SpaceGroup<D> group;
381 std::ifstream in;
382
383 // Open and read file containing space group's symmetry operations
384 if (groupName == "I") {
385 // Create identity group by default
386 group.makeCompleteGroup();
387 } else {
388 bool foundFile = false;
389 {
390 // Search first in this directory
391 in.open(groupName);
392 if (in.is_open()) {
393 in >> group;
394 UTIL_CHECK(group.isValid());
395 foundFile = true;
396 }
397 }
398 if (!foundFile) {
399 // Search in the data directory containing standard space groups
400 std::string fileName = makeGroupFileName(D, groupName);
401 in.open(fileName);
402 if (in.is_open()) {
403 in >> group;
404 UTIL_CHECK(group.isValid());
405 } else {
406 Log::file() << "\nFailed to open group file: "
407 << fileName << "\n";
408 Log::file() << "\n Error: Unknown space group\n";
409 UTIL_THROW("Unknown space group");
410 }
411 }
412 }
413
414 // Make sure all symmetry operations are allowed
415 int nv = normalVecId();
416 bool symmetric = isSymmetric();
417 std::string msg = "Space group contains forbidden symmetry operations";
418 for (int i = 0; i < group.size(); i++) {
419 for (int j = 0; j < D; j++) {
420 int r = group[i].R(nv,j);
421 if (j == nv) {
422 if (r != 1) {
423 if ((r != -1) || (!symmetric)) {
424 UTIL_THROW(msg.c_str());
425 }
426 }
427 } else { // j != nv
428 if (r != 0) {
429 UTIL_THROW(msg.c_str());
430 }
431 }
432 }
433 if (group[i].t(nv) != 0) {
434 UTIL_THROW(msg.c_str());
435 }
436 }
437 }
438
439 /*
440 * Check whether or not the two walls are chemically identical using
441 * the chi array.
442 */
443 template <int D, typename IteratorType>
445 {
446 int nm = system().mixture().nMonomer();
447
448 UTIL_CHECK(nm > 0);
449 UTIL_CHECK(chiBottom_.capacity() == nm);
450 UTIL_CHECK(chiTop_.capacity() == nm);
451
452 for (int i = 0; i < nm; i++) {
453 if (fabs(chiBottom_[i]-chiTop_[i]) > 1e-7) {
454 return false;
455 }
456 }
457 return true;
458 }
459
460 /*
461 * Check whether or not the walls are athermal (only true if all values
462 * in chi array are zero)
463 */
464 template <int D, typename IteratorType>
466 {
467 int nm = system().mixture().nMonomer();
468
469 UTIL_CHECK(nm > 0);
470 UTIL_CHECK(chiBottom_.capacity() == nm);
471 UTIL_CHECK(chiTop_.capacity() == nm);
472
473 for (int i = 0; i < nm; i++) {
474 if ((fabs(chiBottom_[i]) >= 1e-7) || (fabs(chiTop_[i]) >= 1e-7)) {
475 return false;
476 }
477 }
478 return true;
479 }
480
481} // namespace Pspc
482} // namespace Pscf
483#endif
An IntVec<D, T> is a D-component vector of elements of integer type T.
Definition: IntVec.h:27
FilmIteratorBase(System< D > &system)
Constructor.
void generateExternalFields()
Generate external fields for the walls.
int solve(bool isContinuation=false)
Iterate to a solution.
bool isAthermal() const
Are the walls athermal?
void readParameters(std::istream &in)
Read and initialize.
bool isSymmetric() const
Are the walls chemically identical?
void setup()
Initialize just before entry to iterative loop.
void generateWallFields()
Generates mask and external field for the walls and stores in System.
void updateWallFields()
Updates the mask and external fields for the walls if needed.
void checkSpaceGroup() const
Check that space group is compatible with the thin film constraint.
Base class for iterative solvers for SCF equations.
System< D > const & system() const
Get parent system by const reference.
Field of real double precision values on an FFT mesh.
Definition: RField.h:29
void allocate(const IntVec< D > &meshDimensions)
Allocate the underlying C array for an FFT grid.
Definition: RField.tpp:97
Main class for SCFT simulation of one system.
Definition: pspc/System.h:76
A RealVec<D, T> is D-component vector with elements of floating type T.
Definition: RealVec.h:28
Crystallographic space group.
Definition: SpaceGroup.h:30
int size() const
Return number of elements in group (i.e., the order of the group).
void makeCompleteGroup()
Generate a complete group from the current elements.
Dynamically allocatable contiguous array template.
Definition: DArray.h:32
void allocate(int capacity)
Allocate the underlying C array.
Definition: DArray.h:199
A fixed size (static) contiguous array template.
Definition: FArray.h:47
A fixed capacity (static) contiguous array with a variable logical size.
Definition: FSArray.h:38
int size() const
Return logical size of this array (i.e., number of elements).
Definition: FSArray.h:207
static std::ostream & file()
Get log ostream by reference.
Definition: Log.cpp:57
void setClassName(const char *className)
Set class name string.
#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
std::string makeGroupFileName(int D, std::string groupName)
Generates the file name from a group name.
Definition: groupFile.cpp:25
C++ namespace for polymer self-consistent field theory (PSCF).
Utility classes for scientific computation.
Definition: accumulators.mod:1