PSCF v1.4.0
FhMixture.cpp
1/*
2* PSCF - Molecule Self-Consistent Field Theory
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 "FhMixture.tpp"
9#include <pscf/floryHuggins/FhInteraction.h>
10#include <pscf/math/LuSolver.h>
11#include <cmath>
12
13namespace Pscf {
14
15 using namespace Util;
16
17 /*
18 * Constructor.
19 */
22 molecules_(),
23 mu_(),
24 phi_(),
25 c_(),
26 w_(),
27 residual_(),
28 dX_(),
29 dWdC_(),
30 dWdPhi_(),
31 jacobian_(),
32 phiOld_(),
33 fHelmholtz_(0.0),
34 pressure_(0.0),
35 solverPtr_(0),
36 nMolecule_(0),
37 nMonomer_(0),
38 hasComposition_(false)
39 { setClassName("FhMixture"); }
40
41 /*
42 * Destructor.
43 */
45 {
46 if (solverPtr_) {
47 delete solverPtr_;
48 }
49 }
50
51 /*
52 * Read all parameters and initialize.
53 */
54 void FhMixture::readParameters(std::istream& in)
55 {
56 // Precondition
57 UTIL_ASSERT(molecules_.capacity() == 0);
58
59 read<int>(in, "nMonomer", nMonomer_);
60 c_.allocate(nMonomer_);
61 w_.allocate(nMonomer_);
62
63 read<int>(in, "nMolecule", nMolecule_);
64 molecules_.allocate(nMolecule_);
65 mu_.allocate(nMolecule_);
66 phi_.allocate(nMolecule_);
67 for (int i = 0; i < nMolecule_; ++i) {
68 readParamComposite(in, molecules_[i]);
69 }
70
71 validate();
72 }
73
75 {
76 UTIL_CHECK(molecules_.capacity() == 0);
78 nMolecule_ = nMolecule;
79 molecules_.allocate(nMolecule_);
80 mu_.allocate(nMolecule_);
81 phi_.allocate(nMolecule_);
82 }
83
85 {
86 UTIL_CHECK(nMonomer_ == 0);
88 nMonomer_ = nMonomer;
89 c_.allocate(nMonomer_);
90 w_.allocate(nMonomer_);
91 }
92
93 /*
94 * Set molecular and monomer volume fractions.
95 */
97 {
98 validate();
99 UTIL_ASSERT(phi.capacity() == nMolecule_);
100
101 // Set molecular volume fractions
102 double sum = 0;
103 for (int i = 0; i < nMolecule_ - 1; ++i) {
104 UTIL_CHECK(phi[i] >= 0.0);
105 UTIL_CHECK(phi[i] <= 1.0);
106 phi_[i] = phi[i];
107 sum += phi[i];
108 }
109 UTIL_CHECK(sum <= 1.0);
110 phi_[nMolecule_ -1] = 1.0 - sum;
111
112 computeC();
113 hasComposition_ = true;
114 }
115
116 void FhMixture::computeC()
117 {
118 // Initialize monomer fractions to zero
119 int k;
120 for (k = 0; k < nMonomer_; ++k) {
121 c_[k] = 0.0;
122 }
123
124 // Compute monomer volume fractions
125 double concentration;
126 int i, j;
127 for (i = 0; i < nMolecule_; ++i) {
128 FhMolecule& mol = molecules_[i];
129 concentration = phi_[i]/mol.size();
130 for (j = 0; j < molecules_[i].nClump(); ++j) {
131 k = mol.clump(j).monomerId();
132 c_[k] += concentration*mol.clump(j).size();
133 }
134 }
135
136 // Check and normalize monomer volume fractions
137 double sum = 0.0;
138 for (k = 0; k < nMonomer_; ++k) {
139 UTIL_CHECK(c_[k] >= 0.0);
140 UTIL_CHECK(c_[k] <= 1.0);
141 sum += c_[k];
142 }
143 UTIL_CHECK(sum > 0.9999);
144 UTIL_CHECK(sum < 1.0001);
145 for (k = 0; k < nMonomer_; ++k) {
146 c_[k] /= sum;
147 }
148
149 }
150
151 /*
152 * Compute thermodynamic properties.
153 */
154 void FhMixture::computeMu(FhInteraction const & interaction,
155 double xi)
156 {
157 UTIL_CHECK(interaction.nMonomer() == nMonomer_);
158 UTIL_CHECK(hasComposition_);
159
160 // Compute monomer excess chemical potentials
161 interaction.computeW(c_, w_);
162
163 int m; // molecule index
164 int c; // clump index
165 int t; // monomer type index
166 double mu, size;
167 for (m = 0; m < nMolecule_; ++m) {
168 FhMolecule& mol = molecules_[m];
169 mu = log( phi_[m] );
170 mu += xi*mol.size();
171 for (c = 0; c < mol.nClump(); ++c) {
172 t = mol.clump(c).monomerId();
173 size = mol.clump(c).size();
174 mu += size*w_[t];
175 }
176 mu_[m] = mu;
177 }
178 }
179
180 /*
181 * Compute composition from chemical potentials.
182 */
183 void FhMixture::computePhi(FhInteraction const & interaction,
184 DArray<double> const & mu,
185 DArray<double> const & phi,
186 double& xi)
187 {
188 UTIL_ASSERT(interaction.nMonomer() == nMonomer_);
189
190 // Allocate residual and jacobian on first use.
191 if (residual_.capacity() == 0) {
192 residual_.allocate(nMolecule_);
193 dX_.allocate(nMolecule_);
194 dWdC_.allocate(nMonomer_, nMonomer_);
195 dWdPhi_.allocate(nMonomer_, nMolecule_);
196 jacobian_.allocate(nMolecule_, nMolecule_);
197 phiOld_.allocate(nMolecule_);
198 solverPtr_ = new LuSolver();
199 solverPtr_->allocate(nMolecule_);
200 }
201
202 // Compute initial state
204 computeMu(interaction, xi);
205 adjustXi(mu, xi);
206
207 // Compute initial residual
208 double error;
209 double epsilon = 1.0E-10;
210 computeResidual(mu, error);
211
212 #if 0
213 std::cout << "mu[0] =" << mu[0] << std::endl;
214 std::cout << "mu[1] =" << mu[1] << std::endl;
215 std::cout << "mu_[0] =" << mu_[0] << std::endl;
216 std::cout << "mu_[1] =" << mu_[1] << std::endl;
217 std::cout << "residual[0] =" << residual_[0] << std::endl;
218 std::cout << "residual[1] =" << residual_[1] << std::endl;
219 std::cout << "error =" << error << std::endl;
220 #endif
221
222 if (error < epsilon) return;
223
224 double s1; // clump size
225 double v1; // molecule size
226 double f1; // clump fraction
227 int m1, m2; // molecule type indices
228 int c1; // clump index
229 int t1, t2; // monomer type indices
230
231 for (int it = 0; it < 50; ++it) {
232
233 // Compute matrix of derivative dWdC (C = monomer fraction)
234 interaction.computeDwDc(c_, dWdC_);
235
236 // Compute matrix derivative dWdPhi (Phi = molecule fraction)
237 for (t1 = 0; t1 < nMonomer_; ++t1) {
238 for (m1 = 0; m1 < nMolecule_; ++m1) {
239 dWdPhi_(t1, m1) = 0.0;
240 }
241 }
242 for (m1 = 0; m1 < nMolecule_; ++m1) {
243 v1 = molecule(m1).size();
244 for (c1 = 0; c1 < molecule(m1).nClump(); ++c1) {
245 t1 = molecule(m1).clump(c1).monomerId();
246 s1 = molecule(m1).clump(c1).size();
247 f1 = s1/v1;
248 for (t2 = 0; t2 < nMonomer_; ++t2) {
249 dWdPhi_(t2, m1) += dWdC_(t2, t1)*f1;
250 }
251 }
252 }
253
254 // Compute matrix d(mu)/d(Phi), stored in jacobian_
255 for (m1 = 0; m1 < nMolecule_; ++m1) {
256 for (m2 = 0; m2 < nMolecule_; ++m2) {
257 jacobian_(m1, m2) = 0.0;
258 }
259 jacobian_(m1, m1) = 1.0/phi_[m1];
260 }
261 for (m1 = 0; m1 < nMolecule_; ++m1) {
262 v1 = molecule(m1).size();
263 for (c1 = 0; c1 < molecule(m1).nClump(); ++c1) {
264 t1 = molecule(m1).clump(c1).monomerId();
265 s1 = molecule(m1).clump(c1).size();
266 for (m2 = 0; m2 < nMolecule_; ++m2) {
267 jacobian_(m1, m2) += s1*dWdPhi_(t1, m2);
268 }
269 }
270 }
271
272 // Impose incompressibility
273 int mLast = nMolecule_ - 1;
274 for (m1 = 0; m1 < nMolecule_; ++m1) {
275 for (m2 = 0; m2 < nMolecule_ - 1; ++m2) {
276 jacobian_(m1, m2) -= jacobian_(m1, mLast);
277 }
278 // Derivative of mu_[m1] with respect to xi
279 jacobian_(m1, mLast) = molecule(m1).size();
280 }
281
282 // Newton Raphson update of phi and xi fields
283 solverPtr_->computeLU(jacobian_);
284 solverPtr_->solve(residual_, dX_);
285
286 // Store old value of phi
287 for (m1 = 0; m1 < nMolecule_; ++m1) {
288 phiOld_[m1] = phi_[m1];
289 }
290
291 // Apply update
292 bool inRange = false;
293 for (int j = 0; j < 5; ++j) {
294
295 // Update volume fractions
296 double sum = 0.0;
297 for (m1 = 0; m1 < nMolecule_ - 1; ++m1) {
298 phi_[m1] = phiOld_[m1] - dX_[m1];
299 sum += phi_[m1];
300 }
301 phi_[mLast] = 1.0 - sum;
302
303 // Check if all volume fractions are in [0,1]
304 inRange = true;
305 for (m1 = 0; m1 < nMolecule_; ++m1) {
306 if (phi_[m1] < 0.0) inRange = false;
307 if (phi_[m1] > 1.0) inRange = false;
308 }
309
310 // Exit loop or reduce increment
311 if (inRange) {
312 break;
313 } else {
314 for (m1 = 0; m1 < nMolecule_; ++m1) {
315 dX_[m1] *= 0.5;
316 }
317 }
318
319 }
320 if (inRange) {
321 xi = xi - dX_[mLast];
322 } else {
323 UTIL_THROW("Volume fractions remain out of range");
324 }
325
326 // Compute residual
327 computeC();
328 computeMu(interaction, xi);
329 adjustXi(mu, xi);
330 computeResidual(mu, error);
331
332 #if 0
333 std::cout << "mu[0] =" << mu[0] << std::endl;
334 std::cout << "mu[1] =" << mu[1] << std::endl;
335 std::cout << "mu_[0] =" << mu_[0] << std::endl;
336 std::cout << "mu_[1] =" << mu_[1] << std::endl;
337 std::cout << "residual[0] =" << residual_[0] << std::endl;
338 std::cout << "residual[1] =" << residual_[1] << std::endl;
339 std::cout << "error =" << error << std::endl;
340 #endif
341
342 if (error < epsilon) return;
343 }
344
345 UTIL_THROW("Failed to converge");
346 }
347
348 void FhMixture::adjustXi(DArray<double> const & mu, double& xi)
349 {
350 double dxi = 0.0;
351 double sum = 0.0;
352 for (int i=0; i < nMolecule_; ++i) {
353 dxi += mu[i] - mu_[i];
354 sum += molecules_[i].size();
355 }
356 dxi = dxi/sum;
357 for (int i=0; i < nMolecule_; ++i) {
358 mu_[i] += dxi*molecules_[i].size();
359 }
360 xi += dxi;
361 }
362
363 void FhMixture::computeResidual(DArray<double> const & mu, double& error)
364 {
365 error = 0.0;
366 for (int i = 0; i < nMolecule_; ++i) {
367 residual_[i] = mu_[i] - mu[i];
368 if (std::abs(residual_[i]) > error) {
369 error = std::abs(residual_[i]);
370 }
371 }
372 }
373
374 /*
375 * Compute Helmoltz free energy and pressure
376 */
378 {
379 fHelmholtz_ = 0.0;
380
381 // Compute ideal gas contributions to fHelhmoltz_
382 double size;
383 for (int i = 0; i < nMolecule_; ++i) {
384 size = molecules_[i].size();
385 fHelmholtz_ += phi_[i]*( log(phi_[i]) - 1.0 )/size;
386 }
387
388 // Add average interaction free energy density per monomer
389 fHelmholtz_ += interaction.fHelmholtz(c_);
390
391 // Compute pressure
392 pressure_ = -fHelmholtz_;
393 for (int i = 0; i < nMolecule_; ++i) {
394 size = molecules_[i].size();
395 pressure_ += phi_[i]*mu_[i]/size;
396 }
397
398 }
399
400 /*
401 * Check validity after completing initialization.
402 */
404 {
405 UTIL_ASSERT(nMolecule_ > 0);
406 UTIL_ASSERT(nMonomer_ > 0);
407 for (int i = 0; i < nMolecule_; ++i) {
408 FhMolecule const & mol = molecules_[i];
409 UTIL_ASSERT(mol.nClump() > 0);
410 for (int j = 0; j < mol.nClump(); ++j) {
411 UTIL_ASSERT(mol.clump(j).monomerId() < nMonomer_);
412 }
413 }
414 }
415
416 // Explicit instantiation definition for member function template.
417 template
419
420} // namespace Pscf
int monomerId() const
Get the monomer type id.
Definition FhClump.h:127
double size() const
Get the size (number of monomers) in this block.
Definition FhClump.h:133
Flory-Huggins interaction model.
virtual void computeW(Array< double > const &c, Array< double > &w) const
Compute chemical potential from concentration.
int nMonomer() const
Get number of monomer types.
virtual void computeDwDc(Array< double > const &c, Matrix< double > &dWdC) const
Compute second derivatives of free energy.
virtual double fHelmholtz(Array< double > const &c) const
Compute excess Helmholtz free energy per monomer.
~FhMixture()
Destructor.
Definition FhMixture.cpp:44
void setComposition(DArray< double > const &phi)
Set system composition.
Definition FhMixture.cpp:96
FhMolecule & molecule(int id)
Get a molecule object (non-const reference).
Definition FhMixture.h:298
FhMixture()
Constructor.
Definition FhMixture.cpp:20
void initialize(MixtureBase< WT > const &mixture)
Initialize to properties of a MixtureBase descriptor.
Definition FhMixture.tpp:24
int nMolecule() const
Get number of molecule species (polymer + solvent).
Definition FhMixture.h:332
void validate() const
Validate all data structures.
void computePhi(FhInteraction const &interaction, DArray< double > const &mu, DArray< double > const &phi, double &xi)
Compute composition from chemical potentials.
double mu(int id) const
Return chemical potential for one species.
Definition FhMixture.h:305
int nMonomer() const
Get number of monomer types.
Definition FhMixture.h:335
void computeMu(FhInteraction const &interaction, double xi=0.0)
Compute chemical potential from preset composition.
void computeFreeEnergy(FhInteraction const &interaction)
Compute Helmholtz free energy and pressure.
double c(int id) const
Return monomer volume fraction for one monomer type.
Definition FhMixture.h:319
void setNMolecule(int nMolecule)
Set the number of molecular species and allocate memory.
Definition FhMixture.cpp:74
void setNMonomer(int nMonomer)
Set the number of monomer types.
Definition FhMixture.cpp:84
virtual void readParameters(std::istream &in)
Read parameters from file and initialize.
Definition FhMixture.cpp:54
double phi(int id) const
Return molecular volume fraction for one species.
Definition FhMixture.h:312
Molecular species in a homogeneous Flory-Huggins mixture.
Definition FhMolecule.h:36
double size() const
Total molecule size = volume / reference volume.
Definition FhMolecule.h:129
FhClump & clump(int id)
Get a specified FhClump.
Definition FhMolecule.h:139
int nClump() const
Number of monomer clumps (monomer types).
Definition FhMolecule.h:122
Solve Ax=b by LU decomposition of A.
Definition LuSolver.h:31
Abstract descriptor for a mixture of polymer and solvent species.
Definition MixtureBase.h:58
Dynamically allocatable contiguous array template.
Definition DArray.h:32
ScalarParam< Type > & read(std::istream &in, const char *label, Type &value)
Add and read a new required ScalarParam < Type > object.
void setClassName(const char *className)
Set class name string.
ParamComposite()
Constructor.
void readParamComposite(std::istream &in, ParamComposite &child, bool next=true)
Add and read a required child ParamComposite.
#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
#define UTIL_ASSERT(condition)
Assertion macro suitable for debugging serial or parallel code.
Definition global.h:75
double sum(Array< double > const &in)
Compute sum of array elements (real).
Definition Reduce.cpp:20
PSCF package top-level namespace.