PSCF v1.3
pscf/floryHuggins/Mixture.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 "Mixture.h"
9#include <pscf/chem/MixtureBase.h>
10#include <pscf/chem/PolymerSpecies.h>
11#include <pscf/chem/SolventSpecies.h>
12#include <pscf/chem/Edge.h>
13#include <pscf/inter/Interaction.h>
14#include <pscf/math/LuSolver.h>
15#include <cmath>
16
17namespace Pscf {
18namespace FloryHuggins {
19
20 using namespace Util;
21
22 /*
23 * Constructor.
24 */
27 molecules_(),
28 mu_(),
29 phi_(),
30 c_(),
31 w_(),
32 residual_(),
33 dX_(),
34 dWdC_(),
35 dWdPhi_(),
36 jacobian_(),
37 phiOld_(),
38 fHelmholtz_(0.0),
39 pressure_(0.0),
40 solverPtr_(0),
41 nMolecule_(0),
42 nMonomer_(0),
43 hasComposition_(false)
44 { setClassName("Mixture"); }
45
46 /*
47 * Destructor.
48 */
50 {
51 if (solverPtr_) {
52 delete solverPtr_;
53 }
54 }
55
56 /*
57 * Read all parameters and initialize.
58 */
59 void Mixture::readParameters(std::istream& in)
60 {
61 // Precondition
62 UTIL_ASSERT(molecules_.capacity() == 0);
63
64 read<int>(in, "nMonomer", nMonomer_);
65 c_.allocate(nMonomer_);
66 w_.allocate(nMonomer_);
67
68 read<int>(in, "nMolecule", nMolecule_);
69 molecules_.allocate(nMolecule_);
70 mu_.allocate(nMolecule_);
71 phi_.allocate(nMolecule_);
72 for (int i = 0; i < nMolecule_; ++i) {
73 readParamComposite(in, molecules_[i]);
74 }
75
76 validate();
77 }
78
80 {
81 UTIL_CHECK(molecules_.capacity() == 0);
83 nMolecule_ = nMolecule;
84 molecules_.allocate(nMolecule_);
85 mu_.allocate(nMolecule_);
86 phi_.allocate(nMolecule_);
87 }
88
90 {
91 UTIL_CHECK(nMonomer_ == 0);
93 nMonomer_ = nMonomer;
94 c_.allocate(nMonomer_);
95 w_.allocate(nMonomer_);
96 }
97
98 void Mixture::initialize(MixtureBase const & mixture)
99 {
100
101 // Set number of molecular species and monomers
102 int nm = mixture.nMonomer();
103 int np = mixture.nPolymer();
104 int ns = mixture.nSolvent();
105 int nt = np + ns;
106
107 // Check nMonomer
108 if (nMonomer() == 0) {
109 setNMonomer(nm);
110 }
111 UTIL_CHECK(nMonomer() == nm);
112 UTIL_CHECK(c_.capacity() == nm);
113 UTIL_CHECK(w_.capacity() == nm);
114
115 // Check nMolecule
116 if (nMolecule() == 0) {
117 setNMolecule(nt);
118 }
119 UTIL_CHECK(nMolecule() == nt);
120 UTIL_CHECK(molecules_.capacity() == nt);
121 UTIL_CHECK(phi_.capacity() == nt);
122 UTIL_CHECK(mu_.capacity() == nt);
123
124 // Work space of clump sizes
126 c_.allocate(nm);
127
128 int i; // species index
129 int j; // monomer index
130 int k; // block or clump index
131 int nb; // number of blocks
132 int nc; // number of clumps
133
134 // Loop over polymer molecule species
135 if (np > 0) {
136 for (i = 0; i < np; ++i) {
137 PolymerSpecies const & polymer = mixture.polymerSpecies(i);
138
139 // Initial array of clump sizes
140 for (j = 0; j < nm; ++j) {
141 c_[j] = 0.0;
142 }
143
144 // Compute clump sizes for all monomer types.
145 nb = polymer.nBlock();
146 for (k = 0; k < nb; ++k) {
147 Edge const& edge = polymer.edge(k);
148 j = edge.monomerId();
149 c_[j] += edge.length();
150 }
151
152 // Count the number of clumps of nonzero size
153 nc = 0;
154 for (j = 0; j < nm; ++j) {
155 if (c_[j] > 1.0E-8) {
156 ++nc;
157 }
158 }
159 molecule(i).setNClump(nc);
160
161 // Set clump properties for this FloryHuggins::Molecule
162 k = 0; // Clump index
163 for (j = 0; j < nm; ++j) {
164 if (c_[j] > 1.0E-8) {
165 molecule(i).clump(k).setMonomerId(j);
166 molecule(i).clump(k).setSize(c_[j]);
167 ++k;
168 }
169 }
170 UTIL_CHECK(k == nc);
172
173 }
174 }
175
176 // Add solvent contributions
177 if (ns > 0) {
178 double size;
179 int monomerId;
180 for (int is = 0; is < ns; ++is) {
181 i = is + np;
182 SolventSpecies const & solvent = mixture.solventSpecies(is);
183 monomerId = solvent.monomerId();
184 size = solvent.size();
185 molecule(i).setNClump(1);
186 molecule(i).clump(0).setMonomerId(monomerId);
187 molecule(i).clump(0).setSize(size);
189 }
190 }
191
192 }
193
194
195 /*
196 * Set molecular and monomer volume fractions.
197 */
199 {
200 validate();
201 UTIL_ASSERT(phi.capacity() == nMolecule_);
202
203 // Set molecular volume fractions
204 double sum = 0;
205 for (int i = 0; i < nMolecule_ - 1; ++i) {
206 UTIL_CHECK(phi[i] >= 0.0);
207 UTIL_CHECK(phi[i] <= 1.0);
208 phi_[i] = phi[i];
209 sum += phi[i];
210 }
211 UTIL_CHECK(sum <= 1.0);
212 phi_[nMolecule_ -1] = 1.0 - sum;
213
214 computeC();
215 hasComposition_ = true;
216 }
217
218 void Mixture::computeC()
219 {
220 // Initialize monomer fractions to zero
221 int k;
222 for (k = 0; k < nMonomer_; ++k) {
223 c_[k] = 0.0;
224 }
225
226 // Compute monomer volume fractions
227 double concentration;
228 int i, j;
229 for (i = 0; i < nMolecule_; ++i) {
230 Molecule& mol = molecules_[i];
231 concentration = phi_[i]/mol.size();
232 for (j = 0; j < molecules_[i].nClump(); ++j) {
233 k = mol.clump(j).monomerId();
234 c_[k] += concentration*mol.clump(j).size();
235 }
236 }
237
238 // Check and normalize monomer volume fractions
239 double sum = 0.0;
240 for (k = 0; k < nMonomer_; ++k) {
241 UTIL_CHECK(c_[k] >= 0.0);
242 UTIL_CHECK(c_[k] <= 1.0);
243 sum += c_[k];
244 }
245 UTIL_CHECK(sum > 0.9999);
246 UTIL_CHECK(sum < 1.0001);
247 for (k = 0; k < nMonomer_; ++k) {
248 c_[k] /= sum;
249 }
250
251 }
252
253 /*
254 * Compute thermodynamic properties.
255 */
256 void Mixture::computeMu(Interaction const & interaction,
257 double xi)
258 {
259 UTIL_CHECK(interaction.nMonomer() == nMonomer_);
260 UTIL_CHECK(hasComposition_);
261
262 // Compute monomer excess chemical potentials
263 interaction.computeW(c_, w_);
264
265 int m; // molecule index
266 int c; // clump index
267 int t; // monomer type index
268 double mu, size;
269 for (m = 0; m < nMolecule_; ++m) {
270 Molecule& mol = molecules_[m];
271 mu = log( phi_[m] );
272 mu += xi*mol.size();
273 for (c = 0; c < mol.nClump(); ++c) {
274 t = mol.clump(c).monomerId();
275 size = mol.clump(c).size();
276 mu += size*w_[t];
277 }
278 mu_[m] = mu;
279 }
280 }
281
282 /*
283 * Compute composition from chemical potentials.
284 */
285 void Mixture::computePhi(Interaction const & interaction,
286 DArray<double> const & mu,
287 DArray<double> const & phi,
288 double& xi)
289 {
290 UTIL_ASSERT(interaction.nMonomer() == nMonomer_);
291
292 // Allocate residual and jacobian on first use.
293 if (residual_.capacity() == 0) {
294 residual_.allocate(nMolecule_);
295 dX_.allocate(nMolecule_);
296 dWdC_.allocate(nMonomer_, nMonomer_);
297 dWdPhi_.allocate(nMonomer_, nMolecule_);
298 jacobian_.allocate(nMolecule_, nMolecule_);
299 phiOld_.allocate(nMolecule_);
300 solverPtr_ = new LuSolver();
301 solverPtr_->allocate(nMolecule_);
302 }
303
304 // Compute initial state
306 computeMu(interaction, xi);
307 adjustXi(mu, xi);
308
309 // Compute initial residual
310 double error;
311 double epsilon = 1.0E-10;
312 computeResidual(mu, error);
313
314 #if 0
315 std::cout << "mu[0] =" << mu[0] << std::endl;
316 std::cout << "mu[1] =" << mu[1] << std::endl;
317 std::cout << "mu_[0] =" << mu_[0] << std::endl;
318 std::cout << "mu_[1] =" << mu_[1] << std::endl;
319 std::cout << "residual[0] =" << residual_[0] << std::endl;
320 std::cout << "residual[1] =" << residual_[1] << std::endl;
321 std::cout << "error =" << error << std::endl;
322 #endif
323
324 if (error < epsilon) return;
325
326 double s1; // clump size
327 double v1; // molecule size
328 double f1; // clump fraction
329 int m1, m2; // molecule type indices
330 int c1; // clump index
331 int t1, t2; // monomer type indices
332
333 for (int it = 0; it < 50; ++it) {
334
335 // Compute matrix of derivative dWdC (C = monomer fraction)
336 interaction.computeDwDc(c_, dWdC_);
337
338 // Compute matrix derivative dWdPhi (Phi = molecule fraction)
339 for (t1 = 0; t1 < nMonomer_; ++t1) {
340 for (m1 = 0; m1 < nMolecule_; ++m1) {
341 dWdPhi_(t1, m1) = 0.0;
342 }
343 }
344 for (m1 = 0; m1 < nMolecule_; ++m1) {
345 v1 = molecule(m1).size();
346 for (c1 = 0; c1 < molecule(m1).nClump(); ++c1) {
347 t1 = molecule(m1).clump(c1).monomerId();
348 s1 = molecule(m1).clump(c1).size();
349 f1 = s1/v1;
350 for (t2 = 0; t2 < nMonomer_; ++t2) {
351 dWdPhi_(t2, m1) += dWdC_(t2, t1)*f1;
352 }
353 }
354 }
355
356 // Compute matrix d(mu)/d(Phi), stored in jacobian_
357 for (m1 = 0; m1 < nMolecule_; ++m1) {
358 for (m2 = 0; m2 < nMolecule_; ++m2) {
359 jacobian_(m1, m2) = 0.0;
360 }
361 jacobian_(m1, m1) = 1.0/phi_[m1];
362 }
363 for (m1 = 0; m1 < nMolecule_; ++m1) {
364 v1 = molecule(m1).size();
365 for (c1 = 0; c1 < molecule(m1).nClump(); ++c1) {
366 t1 = molecule(m1).clump(c1).monomerId();
367 s1 = molecule(m1).clump(c1).size();
368 for (m2 = 0; m2 < nMolecule_; ++m2) {
369 jacobian_(m1, m2) += s1*dWdPhi_(t1, m2);
370 }
371 }
372 }
373
374 // Impose incompressibility
375 int mLast = nMolecule_ - 1;
376 for (m1 = 0; m1 < nMolecule_; ++m1) {
377 for (m2 = 0; m2 < nMolecule_ - 1; ++m2) {
378 jacobian_(m1, m2) -= jacobian_(m1, mLast);
379 }
380 // Derivative of mu_[m1] with respect to xi
381 jacobian_(m1, mLast) = molecule(m1).size();
382 }
383
384 // Newton Raphson update of phi and xi fields
385 solverPtr_->computeLU(jacobian_);
386 solverPtr_->solve(residual_, dX_);
387
388 // Store old value of phi
389 for (m1 = 0; m1 < nMolecule_; ++m1) {
390 phiOld_[m1] = phi_[m1];
391 }
392
393 // Apply update
394 bool inRange = false;
395 for (int j = 0; j < 5; ++j) {
396
397 // Update volume fractions
398 double sum = 0.0;
399 for (m1 = 0; m1 < nMolecule_ - 1; ++m1) {
400 phi_[m1] = phiOld_[m1] - dX_[m1];
401 sum += phi_[m1];
402 }
403 phi_[mLast] = 1.0 - sum;
404
405 // Check if all volume fractions are in [0,1]
406 inRange = true;
407 for (m1 = 0; m1 < nMolecule_; ++m1) {
408 if (phi_[m1] < 0.0) inRange = false;
409 if (phi_[m1] > 1.0) inRange = false;
410 }
411
412 // Exit loop or reduce increment
413 if (inRange) {
414 break;
415 } else {
416 for (m1 = 0; m1 < nMolecule_; ++m1) {
417 dX_[m1] *= 0.5;
418 }
419 }
420
421 }
422 if (inRange) {
423 xi = xi - dX_[mLast];
424 } else {
425 UTIL_THROW("Volume fractions remain out of range");
426 }
427
428 // Compute residual
429 computeC();
430 computeMu(interaction, xi);
431 adjustXi(mu, xi);
432 computeResidual(mu, error);
433
434 #if 0
435 std::cout << "mu[0] =" << mu[0] << std::endl;
436 std::cout << "mu[1] =" << mu[1] << std::endl;
437 std::cout << "mu_[0] =" << mu_[0] << std::endl;
438 std::cout << "mu_[1] =" << mu_[1] << std::endl;
439 std::cout << "residual[0] =" << residual_[0] << std::endl;
440 std::cout << "residual[1] =" << residual_[1] << std::endl;
441 std::cout << "error =" << error << std::endl;
442 #endif
443
444 if (error < epsilon) return;
445 }
446
447 UTIL_THROW("Failed to converge");
448 }
449
450 void Mixture::adjustXi(DArray<double> const & mu, double& xi)
451 {
452 double dxi = 0.0;
453 double sum = 0.0;
454 for (int i=0; i < nMolecule_; ++i) {
455 dxi += mu[i] - mu_[i];
456 sum += molecules_[i].size();
457 }
458 dxi = dxi/sum;
459 for (int i=0; i < nMolecule_; ++i) {
460 mu_[i] += dxi*molecules_[i].size();
461 }
462 xi += dxi;
463 }
464
465 void Mixture::computeResidual(DArray<double> const & mu, double& error)
466 {
467 error = 0.0;
468 for (int i = 0; i < nMolecule_; ++i) {
469 residual_[i] = mu_[i] - mu[i];
470 if (std::abs(residual_[i]) > error) {
471 error = std::abs(residual_[i]);
472 }
473 }
474 }
475
476 /*
477 * Compute Helmoltz free energy and pressure
478 */
479 void Mixture::computeFreeEnergy(Interaction const & interaction)
480 {
481 fHelmholtz_ = 0.0;
482
483 // Compute ideal gas contributions to fHelhmoltz_
484 double size;
485 for (int i = 0; i < nMolecule_; ++i) {
486 size = molecules_[i].size();
487 fHelmholtz_ += phi_[i]*( log(phi_[i]) - 1.0 )/size;
488 }
489
490 // Add average interaction free energy density per monomer
491 fHelmholtz_ += interaction.fHelmholtz(c_);
492
493 // Compute pressure
494 pressure_ = -fHelmholtz_;
495 for (int i = 0; i < nMolecule_; ++i) {
496 size = molecules_[i].size();
497 pressure_ += phi_[i]*mu_[i]/size;
498 }
499
500 }
501
502 /*
503 * Check validity after completing initialization.
504 */
505 void Mixture::validate() const
506 {
507 UTIL_ASSERT(nMolecule_ > 0);
508 UTIL_ASSERT(nMonomer_ > 0);
509 for (int i = 0; i < nMolecule_; ++i) {
510 Molecule const & mol = molecules_[i];
511 UTIL_ASSERT(mol.nClump() > 0);
512 for (int j = 0; j < mol.nClump(); ++j) {
513 UTIL_ASSERT(mol.clump(j).monomerId() < nMonomer_);
514 }
515 }
516 }
517
518} // namespace FloryHuggins
519} // namespace Pscf
Descriptor for a block within a block polymer.
Definition Edge.h:59
int monomerId() const
Get the monomer type id for this block.
Definition Edge.h:284
double length() const
Get the length of this block, in the thread model.
Definition Edge.h:311
int monomerId() const
Get the monomer type id.
Definition Clump.h:127
void setSize(double size)
Set the size of this clump.
Definition Clump.cpp:30
double size() const
Get the size (number of monomers) in this block.
Definition Clump.h:133
void setMonomerId(int monomerId)
Set the monomer type id.
Definition Clump.cpp:24
int nMonomer() const
Get number of monomer types.
double phi(int id) const
Return molecular volume fraction for one species.
double c(int id) const
Return monomer volume fraction for one monomer type.
void setComposition(DArray< double > const &phi)
Set system composition.
void setNMolecule(int nMolecule)
Set the number of molecular species and allocate memory.
void computeMu(Interaction const &interaction, double xi=0.0)
Compute chemical potential from preset composition.
int nMolecule() const
Get number of molecule species (polymer + solvent).
void setNMonomer(int nMonomer)
Set the number of monomer types.
double mu(int id) const
Return chemical potential for one species.
Molecule & molecule(int id)
Get a molecule object (non-const reference).
void computeFreeEnergy(Interaction const &interaction)
Compute Helmholtz free energy and pressure.
void computePhi(Interaction const &interaction, DArray< double > const &mu, DArray< double > const &phi, double &xi)
Compute composition from chemical potentials.
void validate() const
Validate all data structures.
void initialize(MixtureBase const &mixture)
Initialize to properties of a MixtureBase Mixture descriptor.
virtual void readParameters(std::istream &in)
Read parameters from file and initialize.
Descriptor of a molecular species in a homogeneous mixture.
Definition Molecule.h:39
Clump & clump(int id)
Get a specified Clump.
Definition Molecule.h:141
void computeSize()
Compute total molecule size by adding clump sizes.
Definition Molecule.cpp:58
double size() const
Total molecule size = volume / reference volume.
Definition Molecule.h:132
int nClump() const
Number of monomer clumps (monomer types).
Definition Molecule.h:126
void setNClump(int nClump)
Set the number of clumps, and allocate memory.
Definition Molecule.cpp:47
Flory-Huggins excess free energy model.
Definition Interaction.h:26
virtual double fHelmholtz(Array< double > const &c) const
Compute excess Helmholtz free energy per monomer.
virtual void computeDwDc(Array< double > const &c, Matrix< double > &dWdC) const
Compute second derivatives of free energy.
virtual void computeW(Array< double > const &c, Array< double > &w) const
Compute chemical potential from concentration.
int nMonomer() const
Get number of monomer types.
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:57
virtual SolventSpecies const & solventSpecies(int id) const =0
Set a solvent solver object by const reference.
virtual PolymerSpecies const & polymerSpecies(int id) const =0
Get a PolymerSpecies by const reference.
int nPolymer() const
Get number of polymer species.
int nMonomer() const
Get number of monomer types.
int nSolvent() const
Get number of solvent (point particle) species.
Descriptor for a linear or acyclic branched block polymer.
virtual Edge & edge(int id)=0
Get a specified Edge (block descriptor) by non-const reference.
int nBlock() const
Number of blocks.
Descriptor for a solvent species.
int monomerId() const
Get the monomer type id.
double size() const
Get the size (number of monomers) in this solvent.
Dynamically allocatable contiguous array template.
Definition DArray.h:32
void allocate(int capacity)
Allocate the underlying C array.
Definition DArray.h:199
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
Flory-Huggins theory of spatially homogeneous mixtures.
Definition Clump.cpp:11
PSCF package top-level namespace.
Definition param_pc.dox:1