PSCF v1.4.0
ScftThermo.tpp
1#ifndef RP_SCFT_THERMO_TPP
2#define RP_SCFT_THERMO_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 "ScftThermo.h"
12
13#include <prdc/crystal/Basis.h>
14#include <prdc/crystal/UnitCell.h>
15#include <pscf/chem/PolymerSpecies.h>
16#include <pscf/chem/SolventSpecies.h>
17#include <pscf/mesh/Mesh.h>
18#include <util/format/Int.h>
19#include <util/format/Dbl.h>
20
21namespace Pscf {
22namespace Rp {
23
24 using namespace Util;
25 using namespace Prdc;
26
27 /*
28 * Constructor.
29 */
30 template <int D, class T>
32 : SystemConstRefT(system),
33 fHelmholtz_(0.0),
34 fIdeal_(0.0),
35 fInter_(0.0),
36 fExt_(0.0),
37 pressure_(0.0),
38 hasData_(false)
39 {}
40
41 /*
42 * Destructor.
43 */
44 template <int D, class T>
47
48 /*
49 * Compute Helmholtz free energy and pressure.
50 */
51 template <int D, class T>
53 {
54 if (hasData_) return;
56 UTIL_CHECK(w().hasData());
57 UTIL_CHECK(c().hasData());
58
59 int nm = mixture().nMonomer(); // number of monomer types
60 int np = mixture().nPolymer(); // number of polymer species
61 int ns = mixture().nSolvent(); // number of solvent species
63 // Initialize all free energy contributions to zero
64 fHelmholtz_ = 0.0;
65 fIdeal_ = 0.0;
66 fInter_ = 0.0;
67 fExt_ = 0.0;
68
69 double phi, mu;
70
71 // Compute polymer ideal gas contributions to fIdeal_
72 if (np > 0) {
73 PolymerSpecies<double> const * polymerPtr;
74 double length;
75 for (int i = 0; i < np; ++i) {
76 polymerPtr = &mixture().polymerSpecies(i);
77 phi = polymerPtr->phi();
78 mu = polymerPtr->mu();
80 length = polymerPtr->length();
81 } else {
82 length = (double) polymerPtr->nBead();
83 }
84 if (phi > 1.0E-08) {
85 fIdeal_ += phi*( mu - 1.0 ) / length;
86 // Recall: mu = ln(phi/q)
87 }
88 }
89 }
90
91 // Compute solvent ideal gas contributions to fIdeal_
92 if (ns > 0) {
93 SolventSpecies<double> const * solventPtr;
94 double size;
95 for (int i = 0; i < ns; ++i) {
96 solventPtr = &mixture().solventSpecies(i);
97 phi = solventPtr->phi();
98 mu = solventPtr->mu();
99 size = solventPtr->size();
100 if (phi > 1.0E-08) {
101 fIdeal_ += phi*( mu - 1.0 )/size;
102 }
103 }
104 }
105
106 // Volume integrals with a mask: If the system has a mask, then the
107 // volume that should be used in calculating free energy/pressure
108 // is the volume available to the material, not the total unit cell
109 // volume. We thus divide all terms that involve integrating over
110 // the unit cell volume by quantity mask().phiTot(), which is the
111 // volume fraction of the unit cell that is occupied by material.
112 // This properly scales them to the correct value. fExt_, fInter_,
113 // and the Legendre transform component of fIdeal_ all require
114 // this scaling. If no mask is present, mask().phiTot() = 1 and no
115 // scaling occurs.
116 const double phiTot = mask().phiTot();
117
118 // Compute Legendre transform subtraction from fIdeal_
119 double temp = 0.0;
120 if (w().isSymmetric()) {
121 // Use expansion in symmetry-adapted orthonormal basis
122 UTIL_CHECK(w().isAllocatedBasis());
123 UTIL_CHECK(c().isAllocatedBasis());
124 const int nBasis = domain().basis().nBasis();
125 for (int i = 0; i < nm; ++i) {
126 for (int k = 0; k < nBasis; ++k) {
127 temp -= w().basis(i)[k] * c().basis(i)[k];
129 }
130 } else {
131 // Use summation over grid points
132 const int meshSize = domain().mesh().size();
133 for (int i = 0; i < nm; ++i) {
134 temp -= Reduce::innerProduct(w().rgrid(i), c().rgrid(i));
135 }
136 temp /= double(meshSize);
137 }
138 temp /= phiTot;
139 fIdeal_ += temp;
140 fHelmholtz_ += fIdeal_;
141
142 // Compute contribution from external fields, if they exist
143 if (h().hasData()) {
144 if (w().isSymmetric() && h().isSymmetric()) {
145 // Use expansion in symmetry-adapted orthonormal basis
146 UTIL_CHECK(h().isAllocatedBasis());
147 UTIL_CHECK(c().isAllocatedBasis());
148 const int nBasis = domain().basis().nBasis();
149 for (int i = 0; i < nm; ++i) {
150 for (int k = 0; k < nBasis; ++k) {
151 fExt_ += h().basis(i)[k] * c().basis(i)[k];
152 }
153 }
154 } else {
155 // Use summation over grid points
156 const int meshSize = domain().mesh().size();
157 for (int i = 0; i < nm; ++i) {
158 fExt_ += Reduce::innerProduct(h().rgrid(i), c().rgrid(i));
160 fExt_ /= double(meshSize);
161 }
162 fExt_ /= phiTot;
163 fHelmholtz_ += fExt_;
164 }
165
166 // Compute excess interaction free energy [ phi^{T}*chi*phi/2 ]
167 if (w().isSymmetric()) {
168 // Use basis expansion
169 UTIL_CHECK(c().isAllocatedBasis());
170 const int nBasis = domain().basis().nBasis();
171 double temp;
172 for (int i = 0; i < nm; ++i) {
173 for (int j = i; j < nm; ++j) {
174 const double chi = interaction().chi(i,j);
175 if (std::abs(chi) > 1.0E-9) {
176 temp = 0.0;
177 for (int k = 0; k < nBasis; ++k) {
178 temp += c().basis(i)[k] * c().basis(j)[k];
179 }
180 if (i == j) {
181 fInter_ += 0.5*chi*temp;
182 } else {
183 fInter_ += chi*temp;
184 }
185 }
186 }
187 }
188 } else {
189 // Use r-grid data
190 const int meshSize = domain().mesh().size();
191 double temp;
192 for (int i = 0; i < nm; ++i) {
193 for (int j = i; j < nm; ++j) {
194 const double chi = interaction().chi(i,j);
195 if (std::abs(chi) > 1.0E-9) {
196 temp = Reduce::innerProduct(c().rgrid(i), c().rgrid(j));
197 if (i == j) {
198 fInter_ += 0.5*chi*temp;
199 } else {
200 fInter_ += chi*temp;
201 }
202 }
203 }
204 }
205 fInter_ /= double(meshSize);
206 }
207 fInter_ /= phiTot;
208 fHelmholtz_ += fInter_;
209
210 // Initialize pressure (-1 x grand-canonical free energy / monomer)
211 pressure_ = -fHelmholtz_;
212
213 // Polymer chemical potential corrections to pressure
214 if (np > 0) {
215 PolymerSpecies<double> const * polymerPtr;
216 double length;
217 for (int i = 0; i < np; ++i) {
218 polymerPtr = &mixture().polymerSpecies(i);
219 phi = polymerPtr->phi();
220 mu = polymerPtr->mu();
222 length = polymerPtr->length();
223 } else {
224 length = (double) polymerPtr->nBead();
225 }
226 if (phi > 1.0E-08) {
227 pressure_ += mu * phi / length;
228 }
229 }
230 }
231
232 // Solvent corrections to pressure
233 if (ns > 0) {
234 SolventSpecies<double> const * solventPtr;
235 double size;
236 for (int i = 0; i < ns; ++i) {
237 solventPtr = &mixture().solventSpecies(i);
238 phi = solventPtr->phi();
239 mu = solventPtr->mu();
240 size = solventPtr->size();
241 if (phi > 1.0E-08) {
242 pressure_ += mu * phi / size;
243 }
244 }
245 }
246
247 hasData_ = true;
248 }
249
250 /*
251 * Clear stored values of thermodynamic properties.
252 */
253 template <int D, class T>
255 { hasData_ = false; }
256
257 /*
258 * Write thermodynamic properties to file.
259 */
260 template <int D, class T>
261 void ScftThermo<D,T>::write(std::ostream& out)
262 {
263 if (!hasData_) {
264 compute();
265 }
266
267 out << std::endl;
268 out << "fHelmholtz " << Dbl(fHelmholtz(), 18, 11) << std::endl;
269 out << "pressure " << Dbl(pressure(), 18, 11) << std::endl;
270 out << std::endl;
271 out << "fIdeal " << Dbl(fIdeal_, 18, 11) << std::endl;
272 out << "fInter " << Dbl(fInter_, 18, 11) << std::endl;
273 if (h().hasData()) {
274 out << "fExt " << Dbl(fExt_, 18, 11) << std::endl;
275 }
276 out << std::endl;
277
278 int np = mixture().nPolymer();
279 int ns = mixture().nSolvent();
280
281 if (np > 0) {
282 out << "polymers:" << std::endl;
283 out << " "
284 << " phi "
285 << " mu "
286 << std::endl;
287 for (int i = 0; i < np; ++i) {
288 out << Int(i, 5)
289 << " " << Dbl(mixture().polymer(i).phi(),18, 11)
290 << " " << Dbl(mixture().polymer(i).mu(), 18, 11)
291 << std::endl;
292 }
293 out << std::endl;
294 }
295
296 if (ns > 0) {
297 out << "solvents:" << std::endl;
298 out << " "
299 << " phi "
300 << " mu "
301 << std::endl;
302 for (int i = 0; i < ns; ++i) {
303 out << Int(i, 5)
304 << " " << Dbl(mixture().solvent(i).phi(),18, 11)
305 << " " << Dbl(mixture().solvent(i).mu(), 18, 11)
306 << std::endl;
307 }
308 out << std::endl;
309 }
310
311 out << "cellParams:" << std::endl;
312 for (int i = 0; i < domain().unitCell().nParameter(); ++i) {
313 out << Int(i, 5)
314 << " "
315 << Dbl(domain().unitCell().parameter(i), 18, 11)
316 << std::endl;
317 }
318 out << std::endl;
319 }
320
321} // namespace Rp
322} // namespace Pscf
323#endif
Descriptor for a linear or acyclic branched block polymer.
int nBead() const
Total number of beads in the polymer (bead model).
double length() const
Sum of the lengths of all blocks in the polymer (thread model).
~ScftThermo()
Destructor.
void clear()
Clear all thermodynamic data.
void write(std::ostream &out)
Write SCFT thermodynamic properties to a file.
void compute()
Compute SCFT free energy density and pressure for current fields.
typename T::System SystemT
Parent System type name alias.
double fHelmholtz() const
Get total Helmholtz free energy per monomer / kT.
typename T::SystemConstRef SystemConstRefT
Base class type name alias.
double pressure() const
Get the precomputed pressure times monomer volume / kT.
ScftThermo(SystemT const &system)
Constructor.
Descriptor for a solvent species.
double size() const
Get the size (number of monomers) in this solvent.
WT mu() const
Get the chemical potential for this species (units kT=1).
Definition Species.h:166
WT phi() const
Get the overall volume fraction for this species.
Definition Species.h:159
Wrapper for a double precision number, for formatted ostream output.
Definition Dbl.h:40
Wrapper for an int, for formatted ostream output.
Definition Int.h:37
#define UTIL_CHECK(condition)
Assertion macro suitable for serial or parallel production code.
Definition global.h:68
double innerProduct(Array< double > const &a, Array< double > const &b)
Compute Euclidean inner product of two real arrays .
Definition Reduce.cpp:117
bool isThread()
Is the thread model in use ?
Periodic fields and crystallography.
Definition complex.cpp:11
Class templates for real-valued periodic fields.
PSCF package top-level namespace.