1 /*-------------------------------------------------------------------
2 Copyright 2011 Ravishankar Sundararaman, Kendra Letchworth Weaver, Deniz Gunceler
3 
4 This file is part of JDFTx.
5 
6 JDFTx is free software: you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation, either version 3 of the License, or
9 (at your option) any later version.
10 
11 JDFTx is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 GNU General Public License for more details.
15 
16 You should have received a copy of the GNU General Public License
17 along with JDFTx.  If not, see <http://www.gnu.org/licenses/>.
18 -------------------------------------------------------------------*/
19 
20 #include <electronic/Everything.h>
21 #include <fluid/NonlinearPCM.h>
22 #include <fluid/LinearPCM.h>
23 #include <fluid/PCM_internal.h>
24 #include <core/ScalarFieldIO.h>
25 #include <core/Util.h>
26 
27 //Utility functions to extract/set the members of a MuEps
getMuPlus(ScalarFieldMuEps & X)28 inline ScalarField& getMuPlus(ScalarFieldMuEps& X) { return X[0]; }
getMuPlus(const ScalarFieldMuEps & X)29 inline const ScalarField& getMuPlus(const ScalarFieldMuEps& X) { return X[0]; }
getMuMinus(ScalarFieldMuEps & X)30 inline ScalarField& getMuMinus(ScalarFieldMuEps& X) { return X[1]; }
getMuMinus(const ScalarFieldMuEps & X)31 inline const ScalarField& getMuMinus(const ScalarFieldMuEps& X) { return X[1]; }
getEps(ScalarFieldMuEps & X)32 inline VectorField getEps(ScalarFieldMuEps& X) { return VectorField(&X[2]); }
getEps(const ScalarFieldMuEps & X)33 inline const VectorField getEps(const ScalarFieldMuEps& X) { return VectorField(&X[2]); }
setMuEps(ScalarFieldMuEps & mueps,ScalarField muPlus,ScalarField muMinus,VectorField eps)34 inline void setMuEps(ScalarFieldMuEps& mueps, ScalarField muPlus, ScalarField muMinus, VectorField eps) { mueps[0]=muPlus; mueps[1]=muMinus; for(int k=0; k<3; k++) mueps[k+2]=eps[k]; }
35 
36 
setPreconditioner(double G,double kappaSqByEpsilon,double muByEpsSq)37 inline double setPreconditioner(double G, double kappaSqByEpsilon, double muByEpsSq)
38 {	double G2 = G*G;
39 	double den = G2 + kappaSqByEpsilon;
40 	return den ? muByEpsSq*G2/(den*den) : 0.;
41 }
42 
setMetric(int i,double Gsq,double qMetricSq,double * metric)43 inline void setMetric(int i, double Gsq, double qMetricSq, double* metric)
44 {	metric[i] = qMetricSq ? Gsq / (qMetricSq + Gsq) : 1.;
45 }
46 
NonlinearPCM(const Everything & e,const FluidSolverParams & fsp)47 NonlinearPCM::NonlinearPCM(const Everything& e, const FluidSolverParams& fsp)
48 : PCM(e, fsp), Pulay(fsp.scfParams), pMol(0.), ionNbulk(0.), ionZ(0.), screeningEval(0), dielectricEval(0)
49 {
50 	const auto& solvent = fsp.solvents[0];
51 	pMol = solvent->pMol ? solvent->pMol : solvent->molecule.getDipole().length();
52 
53 	//Initialize dielectric evaluation class:
54 	dielectricEval = new NonlinearPCMeval::Dielectric(fsp.linearDielectric,
55 		fsp.T, solvent->Nbulk, pMol, epsBulk, solvent->epsInf);
56 
57 	//Check and setup ionic screening:
58 	if(fsp.cations.size() > 1) die("NonlinearPCM currently only supports a single cationic component.\n");
59 	if(fsp.anions.size() > 1) die("NonlinearPCM currently only supports a single anionic component.\n");
60 	assert(fsp.anions.size() == fsp.cations.size()); //this should be ensured by charge neutrality check in FluidSolver constructor
61 	if(fsp.cations.size())
62 	{	//Ensure charge balanced:
63 		if(fabs(fsp.cations[0]->molecule.getCharge() + fsp.anions[0]->molecule.getCharge())>1e-12)
64 			die("NonlinearPCM currently only supports charge-balanced (Z:Z) electrolytes.\n");
65 		ionNbulk = fsp.cations[0]->Nbulk;
66 		ionZ = fsp.anions[0]->molecule.getCharge();
67 		double VhsCation = fsp.cations[0]->molecule.getVhs(); if(!VhsCation) VhsCation = (4*M_PI/3)*pow(fsp.cations[0]->Rvdw,3);
68 		double VhsAnion = fsp.anions[0]->molecule.getVhs(); if(!VhsAnion) VhsAnion = (4*M_PI/3)*pow(fsp.anions[0]->Rvdw,3);
69 		screeningEval = new NonlinearPCMeval::Screening(fsp.linearScreening, fsp.T, ionNbulk, ionZ, VhsCation, VhsAnion, epsBulk);
70 	}
71 	else
72 	{	ionNbulk = 0.;
73 		screeningEval = 0;
74 	}
75 
76 	if(fsp.nonlinearSCF)
77 	{	//Initialize lookup tables for SCF version:
78 		//--- Dielectric lookup table
79 		double dxMapped = 1./512;
80 		std::vector<double> samples;
81 		for(double xMapped=0.; xMapped<=1.; xMapped+=dxMapped)
82 		{	double x;
83 			if(xMapped==0.) x = 1e-12;
84 			else if(xMapped==1.) x = 1e+12;
85 			else x = xMapped/(1.-xMapped); //inverse of xMapped = x / (1 + x)
86 			samples.push_back(dielectricEval->eps_from_x(x)/x);
87 		}
88 		gLookup.init(0, samples, dxMapped);
89 		//--- Screening lookup table
90 		if(screeningEval)
91 		{
92 			double dVmapped = 1./512;
93 			std::vector<double> samples;
94 			for(double Vmapped=-1.; Vmapped<=1.; Vmapped+=dVmapped)
95 			{	if(fabs(Vmapped)==1)
96 					samples.push_back(0.);
97 				else
98 				{	double V = std::pow(Vmapped/(1.-Vmapped*Vmapped), 3.); //inverse of Vmapped = copysign(2cbrt(V) / (1 + sqrt(1 + (2cbrt(V))^2)), V)
99 					double x = screeningEval->x_from_V(V);
100 					double xMapped = 1./(1.+x); //maps [0,infty) -> (0,1]
101 					samples.push_back(xMapped);
102 				}
103 			}
104 			xLookup.init(1, samples, dVmapped);
105 		}
106 		//--- Pulay metric
107 		metric = std::make_shared<RealKernel>(gInfo);
108 		applyFuncGsq(e.gInfo, setMetric, std::pow(fsp.scfParams.qMetric,2), metric->data());
109 	}
110 	else
111 	{	//Initialize preconditioner (for mu channel):
112 		double muByEps = (ionZ/pMol) * (1.-dielectricEval->alpha/3); //relative scale between mu and eps
113 		preconditioner.init(0, 0.02, gInfo.GmaxGrid, setPreconditioner, k2factor/epsBulk, muByEps*muByEps);
114 	}
115 }
116 
~NonlinearPCM()117 NonlinearPCM::~NonlinearPCM()
118 {	delete dielectricEval;
119 	if(screeningEval) delete screeningEval;
120 	if(preconditioner) preconditioner.free();
121 	if(gLookup) gLookup.free();
122 	if(xLookup) xLookup.free();
123 }
124 
125 
set_internal(const ScalarFieldTilde & rhoExplicitTilde,const ScalarFieldTilde & nCavityTilde)126 void NonlinearPCM::set_internal(const ScalarFieldTilde& rhoExplicitTilde, const ScalarFieldTilde& nCavityTilde)
127 {
128 	bool setPhiFromState = false; //whether to set linearPCM phi from state (first time in SCF version when state has been read in)
129 
130 	if(fsp.nonlinearSCF || (!state))
131 	{	if(!linearPCM)
132 		{	logSuspend();
133 			linearPCM = std::make_shared<LinearPCM>(e, fsp);
134 			logResume();
135 			if(state) setPhiFromState = true; //set phi from state which has already been loaded (only in SCF mode)
136 		}
137 		logSuspend();
138 		linearPCM->atpos = atpos;
139 		linearPCM->set_internal(rhoExplicitTilde, nCavityTilde);
140 		logResume();
141 	}
142 
143 	//Initialize state if required:
144 	if(!state)
145 	{	logPrintf("Initializing state of NonlinearPCM using a similar LinearPCM: "); logFlush();
146 		FILE*& fpLog = ((MinimizeParams&)e.fluidMinParams).fpLog;
147 		fpLog = nullLog; //disable iteration log from LinearPCM
148 		linearPCM->minimizeFluid();
149 		fpLog = globalLog; //restore usual iteration log
150 		logFlush();
151 		//Guess nonlinear states based on the electrostatic potential of the linear version:
152 		//mu:
153 		ScalarField mu;
154 		if(screeningEval && screeningEval->linear)
155 		{	mu = (-ionZ/fsp.T) * I(linearPCM->state);
156 			mu -= integral(mu)/gInfo.detR; //project out G=0
157 		}
158 		else initZero(mu, gInfo); //initialization logic does not work well with hard sphere limit
159 		//eps:
160 		VectorField eps = (-pMol/fsp.T) * I(gradient(linearPCM->state));
161 		ScalarField E = sqrt(eps[0]*eps[0] + eps[1]*eps[1] + eps[2]*eps[2]);
162 		ScalarField Ecomb = 0.5*((dielectricEval->alpha-3.) + E);
163 		ScalarField epsByE = inv(E) * (Ecomb + sqrt(Ecomb*Ecomb + 3.*E));
164 		eps *= epsByE; //enhancement due to correlations
165 		//collect:
166 		setMuEps(state, mu, clone(mu), eps);
167 	}
168 
169 	this->rhoExplicitTilde = rhoExplicitTilde; zeroNyquist(this->rhoExplicitTilde);
170 	this->nCavity = I(nCavityTilde + getFullCore());
171 
172 	updateCavity();
173 
174 	if(setPhiFromState)
175 	{	ScalarFieldMuEps gradUnused;
176 		ScalarFieldTilde phiFluidTilde;
177 		(*this)(state, gradUnused, &phiFluidTilde);
178 		linearPCM->state = phiFluidTilde + coulomb(rhoExplicitTilde);
179 	}
180 }
181 
operator ()(const ScalarFieldMuEps & state,ScalarFieldMuEps & Adiel_state,ScalarFieldTilde * Adiel_rhoExplicitTilde,ScalarFieldTilde * Adiel_nCavityTilde,IonicGradient * forces,matrix3<> * Adiel_RRT) const182 double NonlinearPCM::operator()(const ScalarFieldMuEps& state, ScalarFieldMuEps& Adiel_state,
183 	ScalarFieldTilde* Adiel_rhoExplicitTilde, ScalarFieldTilde* Adiel_nCavityTilde, IonicGradient* forces, matrix3<>* Adiel_RRT) const
184 {
185 	EnergyComponents& Adiel = ((NonlinearPCM*)this)->Adiel;
186 	ScalarFieldArray Adiel_shape; if(Adiel_nCavityTilde) nullToZero(Adiel_shape, gInfo, shape.size());
187 
188 	ScalarFieldTilde rhoFluidTilde;
189 	ScalarField muPlus, muMinus, Adiel_muPlus, Adiel_muMinus;
190 	initZero(Adiel_muPlus, gInfo); initZero(Adiel_muMinus, gInfo);
191 	double mu0 = 0., Qexp = 0., Adiel_Qexp = 0.;
192 	if(screeningEval)
193 	{	//Get neutrality Lagrange multiplier:
194 		muPlus = getMuPlus(state);
195 		muMinus = getMuMinus(state);
196 		Qexp = integral(rhoExplicitTilde);
197 		mu0 = screeningEval->neutralityConstraint(muPlus, muMinus, shape.back(), Qexp);
198 		//Compute ionic free energy and bound charge
199 		ScalarField Aout, rhoIon;
200 		initZero(Aout, gInfo);
201 		initZero(rhoIon, gInfo);
202 		callPref(screeningEval->freeEnergy)(gInfo.nr, mu0, muPlus->dataPref(), muMinus->dataPref(), shape.back()->dataPref(),
203 			rhoIon->dataPref(), Aout->dataPref(), Adiel_muPlus->dataPref(), Adiel_muMinus->dataPref(), Adiel_shape.size() ? Adiel_shape.back()->dataPref() : 0);
204 		Adiel["Akappa"] = integral(Aout);
205 		rhoFluidTilde += J(rhoIon); //include bound charge due to ions
206 	}
207 
208 	//Compute the dielectric free energy and bound charge:
209 	VectorField eps = getEps(state), p, Adiel_eps;
210 	{	ScalarField Aout;
211 		initZero(Aout, gInfo);
212 		nullToZero(p, gInfo);
213 		nullToZero(Adiel_eps, gInfo);
214 		callPref(dielectricEval->freeEnergy)(gInfo.nr, eps.const_dataPref(), shape[0]->dataPref(),
215 			p.dataPref(), Aout->dataPref(), Adiel_eps.dataPref(), Adiel_shape.size() ? Adiel_shape[0]->dataPref() : 0);
216 		Adiel["Aeps"] = integral(Aout);
217 		rhoFluidTilde -= divergence(J(p)); //include bound charge due to dielectric
218 		if(!Adiel_RRT) p = 0; //only need later for lattice derivative
219 	} //scoped to automatically deallocate temporaries
220 
221 	//Compute the electrostatic terms:
222 	ScalarFieldTilde phiFluidTilde = coulomb(rhoFluidTilde);
223 	ScalarFieldTilde phiExplicitTilde = coulomb(rhoExplicitTilde);
224 	Adiel["Coulomb"] = dot(rhoFluidTilde, O(0.5*phiFluidTilde + phiExplicitTilde));
225 	if(Adiel_RRT)
226 		*Adiel_RRT += matrix3<>(1,1,1)*(Adiel["Coulomb"] + Adiel["Aeps"] + Adiel["Akappa"])
227 			+ coulombStress(rhoFluidTilde, 0.5*rhoFluidTilde+rhoExplicitTilde);
228 
229 	if(screeningEval)
230 	{	//Propagate gradients from rhoIon to mu, shape
231 		ScalarField Adiel_rhoIon = I(phiFluidTilde+phiExplicitTilde);
232 		callPref(screeningEval->convertDerivative)(gInfo.nr, mu0, muPlus->dataPref(), muMinus->dataPref(), shape.back()->dataPref(),
233 			Adiel_rhoIon->dataPref(), Adiel_muPlus->dataPref(), Adiel_muMinus->dataPref(), Adiel_shape.size() ? Adiel_shape.back()->dataPref() : 0);
234 		//Propagate gradients from mu0 to mu, shape, Qexp:
235 		double Adiel_mu0 = integral(Adiel_muPlus) + integral(Adiel_muMinus), mu0_Qexp;
236 		ScalarField mu0_muPlus, mu0_muMinus, mu0_shape;
237 		screeningEval->neutralityConstraint(muPlus, muMinus, shape.back(), Qexp, &mu0_muPlus, &mu0_muMinus, &mu0_shape, &mu0_Qexp);
238 		Adiel_muPlus += Adiel_mu0 * mu0_muPlus;
239 		Adiel_muMinus += Adiel_mu0 * mu0_muMinus;
240 		if(Adiel_shape.size()) Adiel_shape.back() += Adiel_mu0 * mu0_shape;
241 		Adiel_Qexp = Adiel_mu0 * mu0_Qexp;
242 	}
243 
244 	//Propagate gradients from p to eps, shape
245 	{	VectorField Adiel_p = I(gradient(phiFluidTilde+phiExplicitTilde)); //Because dagger(-divergence) = gradient
246 		callPref(dielectricEval->convertDerivative)(gInfo.nr, eps.const_dataPref(), shape[0]->dataPref(),
247 			Adiel_p.const_dataPref(), Adiel_eps.dataPref(), Adiel_shape.size() ? Adiel_shape[0]->dataPref() : 0);
248 		if(Adiel_RRT) *Adiel_RRT -= gInfo.dV * dotOuter(p, Adiel_p);
249 	}
250 
251 	//Optional outputs:
252 	if(Adiel_rhoExplicitTilde)
253 	{	if(fsp.nonlinearSCF && !useGummel())
254 		{	//Non-variational version (from inner solve):
255 			*Adiel_rhoExplicitTilde = linearPCM->state - phiExplicitTilde;
256 		}
257 		else
258 		{	//Variational version (from bound charge with neutrality constraint):
259 			*Adiel_rhoExplicitTilde = phiFluidTilde;
260 			(*Adiel_rhoExplicitTilde)->setGzero(Adiel_Qexp);
261 		}
262 	}
263 	if(Adiel_nCavityTilde)
264 	{	ScalarField Adiel_nCavity;
265 		propagateCavityGradients(Adiel_shape, Adiel_nCavity, *Adiel_rhoExplicitTilde, forces, Adiel_RRT);
266 		*Adiel_nCavityTilde = J(Adiel_nCavity);
267 	}
268 
269 	//Collect energy and gradient pieces:
270 	setMuEps(Adiel_state, Adiel_muPlus, Adiel_muMinus, Adiel_eps);
271 	Adiel_state *= gInfo.dV; //converts variational derivative to total derivative
272 	return Adiel;
273 }
274 
minimizeFluid()275 void NonlinearPCM::minimizeFluid()
276 {	if(fsp.nonlinearSCF)
277 	{	clearState();
278 		Pulay<ScalarFieldTilde>::minimize(compute(0,0));
279 	}
280 	else
281 		Minimizable<ScalarFieldMuEps>::minimize(e.fluidMinParams);
282 }
283 
loadState(const char * filename)284 void NonlinearPCM::loadState(const char* filename)
285 {	nullToZero(state, gInfo);
286 	state.loadFromFile(filename);
287 }
288 
saveState(const char * filename) const289 void NonlinearPCM::saveState(const char* filename) const
290 {	if(mpiWorld->isHead()) state.saveToFile(filename);
291 }
292 
get_Adiel_and_grad_internal(ScalarFieldTilde & Adiel_rhoExplicitTilde,ScalarFieldTilde & Adiel_nCavityTilde,IonicGradient * extraForces,matrix3<> * Adiel_RRT) const293 double NonlinearPCM::get_Adiel_and_grad_internal(ScalarFieldTilde& Adiel_rhoExplicitTilde, ScalarFieldTilde& Adiel_nCavityTilde, IonicGradient* extraForces, matrix3<>* Adiel_RRT) const
294 {	ScalarFieldMuEps Adiel_state;
295 	double A = (*this)(state, Adiel_state, &Adiel_rhoExplicitTilde, &Adiel_nCavityTilde, extraForces, Adiel_RRT);
296 	accumExtraForces(extraForces, Adiel_nCavityTilde);
297 	return A;
298 }
299 
step(const ScalarFieldMuEps & dir,double alpha)300 void NonlinearPCM::step(const ScalarFieldMuEps& dir, double alpha)
301 {	::axpy(alpha, dir, state);
302 }
303 
compute(ScalarFieldMuEps * grad,ScalarFieldMuEps * Kgrad)304 double NonlinearPCM::compute(ScalarFieldMuEps* grad, ScalarFieldMuEps* Kgrad)
305 {	ScalarFieldMuEps gradUnused;
306 	double E = (*this)(state, grad ? *grad : gradUnused);
307 	//Compute preconditioned gradient:
308 	if(Kgrad)
309 	{	const ScalarFieldMuEps& in = grad ? *grad : gradUnused;
310 		double dielPrefac = 1./(gInfo.dV * dielectricEval->NT);
311 		double ionsPrefac = screeningEval ? 1./(gInfo.dV * screeningEval->NT) : 0.;
312 		setMuEps(*Kgrad,
313 			ionsPrefac * I(preconditioner*J(getMuPlus(in))),
314 			ionsPrefac * I(preconditioner*J(getMuMinus(in))),
315 			dielPrefac * getEps(in));
316 	}
317 	return E;
318 }
319 
320 
dumpDensities(const char * filenamePattern) const321 void NonlinearPCM::dumpDensities(const char* filenamePattern) const
322 {	PCM::dumpDensities(filenamePattern);
323 
324 	//Output dielectric bound charge:
325 	string filename;
326 	{	ScalarField Aout; initZero(Aout, gInfo);
327 		VectorField p; nullToZero(p, gInfo);
328 		VectorField Adiel_eps; nullToZero(Adiel_eps, gInfo);
329 		callPref(dielectricEval->freeEnergy)(gInfo.nr, getEps(state).const_dataPref(), shape[0]->dataPref(),
330 			p.dataPref(), Aout->dataPref(), Adiel_eps.dataPref(), 0);
331 		ScalarField rhoDiel = -divergence(p); //include bound charge due to dielectric
332 		FLUID_DUMP(rhoDiel, "RhoDiel");
333 	}
334 
335 	//Output ionic bound charge (if any):
336 	if(screeningEval)
337 	{	ScalarField Nplus, Nminus;
338 		{	ScalarField muPlus = getMuPlus(state);
339 			ScalarField muMinus = getMuMinus(state);
340 			double Qexp = integral(rhoExplicitTilde);
341 			double mu0 = screeningEval->neutralityConstraint(muPlus, muMinus, shape.back(), Qexp);
342 			Nplus = ionNbulk * shape.back() * (fsp.linearScreening ? 1.+(mu0+muPlus) : exp(mu0+muPlus));
343 			Nminus = ionNbulk * shape.back() * (fsp.linearScreening ? 1.-(mu0+muMinus) : exp(-(mu0+muMinus)));
344 		}
345 		FLUID_DUMP(Nplus, "N+");
346 		FLUID_DUMP(Nminus, "N-");
347 		FLUID_DUMP(ionZ*(Nplus-Nminus), "RhoIon");
348 	}
349 }
350 
351 //--------- Interface for Pulay<ScalarFieldTilde> ---------
352 
cycle(double dEprev,std::vector<double> & extraValues)353 double NonlinearPCM::cycle(double dEprev, std::vector<double>& extraValues)
354 {	//Update epsilon / kappaSq based on current phi:
355 	phiToState(false);
356 	//Inner linear solve
357 	FILE*& fpLog = ((MinimizeParams&)e.fluidMinParams).fpLog;
358 	fpLog = nullLog; //disable iteration log from LinearPCM
359 	linearPCM->minimizeFluid();
360 	fpLog = globalLog; //restore usual iteration log
361 	//Update state from new phi:
362 	phiToState(true);
363 	return compute(0,0);
364 }
365 
366 
readVariable(ScalarFieldTilde & X,FILE * fp) const367 void NonlinearPCM::readVariable(ScalarFieldTilde& X, FILE* fp) const
368 {	nullToZero(X, gInfo);
369 	loadRawBinary(X, fp);
370 }
371 
writeVariable(const ScalarFieldTilde & X,FILE * fp) const372 void NonlinearPCM::writeVariable(const ScalarFieldTilde& X, FILE* fp) const
373 {	saveRawBinary(X, fp);
374 }
375 
getVariable() const376 ScalarFieldTilde NonlinearPCM::getVariable() const
377 {	return clone(linearPCM->state);
378 }
379 
setVariable(const ScalarFieldTilde & X)380 void NonlinearPCM::setVariable(const ScalarFieldTilde& X)
381 {	linearPCM->state = clone(X);
382 }
383 
precondition(const ScalarFieldTilde & X) const384 ScalarFieldTilde NonlinearPCM::precondition(const ScalarFieldTilde& X) const
385 {	return fsp.scfParams.mixFraction * X;
386 }
387 
applyMetric(const ScalarFieldTilde & X) const388 ScalarFieldTilde NonlinearPCM::applyMetric(const ScalarFieldTilde& X) const
389 {	return (*metric) * X;
390 }
391 
phiToState(bool setState)392 void NonlinearPCM::phiToState(bool setState)
393 {	//Initialize inputs:
394 	const ScalarField phi = I(linearPCM->state);
395 	const VectorField Dphi = I(gradient(linearPCM->state));
396 	//Prepare outputs:
397 	ScalarField epsilon, kappaSq;
398 	if(!setState)
399 	{	nullToZero(epsilon, gInfo);
400 		if(screeningEval)
401 			nullToZero(kappaSq, gInfo);
402 	}
403 	VectorField eps = getEps(state);
404 	ScalarField& muPlus = getMuPlus(state);
405 	ScalarField& muMinus = getMuMinus(state);
406 	//Calculate eps/mu or epsilon/kappaSq as needed:
407 	vector3<double*> vecDataUnused(0,0,0); double* dataUnused=0;
408 	callPref(dielectricEval->phiToState)(gInfo.nr, Dphi.dataPref(), shape[0]->dataPref(), gLookup, setState,
409 		setState ? eps.dataPref() : vecDataUnused,
410 		setState ? dataUnused : epsilon->dataPref() );
411 	if(screeningEval)
412 		callPref(screeningEval->phiToState)(gInfo.nr, phi->dataPref(), shape.back()->dataPref(), xLookup, setState,
413 			setState ? muPlus->dataPref() : dataUnused,
414 			setState ? muMinus->dataPref() : dataUnused,
415 			setState ? dataUnused : kappaSq->dataPref() );
416 	//Save to global state or linearPCM as required:
417 	if(setState)
418 		setMuEps(state, muPlus, muMinus, eps);
419 	else
420 		linearPCM->override(epsilon, kappaSq);
421 }
422