FlowField::FlowField ( int Nx, int Ny ) : _size_x ( Nx ), _size_y ( Ny ), _size_z ( 1 ), _cellsX (Nx+3), _cellsY(Ny+3), _cellsZ(1), // Pressure field doesn't need to have an extra layer, but this allows to address the same // positions with the same iterator for both pressures and velocities. _pressure ( ScalarField ( Nx + 3, Ny + 3 ) ), _viscosity ( ScalarField ( Nx + 3, Ny + 3 ) ), _velocity ( VectorField ( Nx + 3, Ny + 3 ) ), _flags ( IntScalarField ( Nx + 3, Ny + 3 ) ), _nearWallDis ( ScalarField ( Nx + 3, Ny + 3 ) ), _FGH ( VectorField ( Nx + 3, Ny + 3 ) ), _RHS ( ScalarField (Nx + 3, Ny + 3) ) { assertion ( Nx > 0 ); assertion ( Ny > 0 ); }
FlowField::FlowField ( int Nx, int Ny, int Nz ) : _size_x ( Nx ), _size_y ( Ny ), _size_z ( Nz ), _cellsX (Nx+3), _cellsY(Ny+3), _cellsZ(Nz+3), _pressure ( ScalarField ( Nx + 3, Ny + 3, Nz + 3 ) ), _viscosity ( ScalarField ( Nx + 3, Ny + 3 , Nz + 3) ), _velocity ( VectorField ( Nx + 3, Ny + 3, Nz + 3 ) ), _flags ( IntScalarField ( Nx + 3, Ny + 3, Nz +3 ) ), _nearWallDis ( ScalarField ( Nx + 3, Ny + 3, Nz +3 ) ), _FGH ( VectorField ( Nx + 3, Ny + 3, Nz + 3 ) ), _RHS ( ScalarField ( Nx + 3, Ny + 3, Nz + 3 ) ) { // Check that the provided data makes sense assertion ( Nx > 0 ); assertion ( Ny > 0 ); assertion ( Nz > 0 ); }
FlowField::FlowField (const Parameters & parameters): _size_x(parameters.parallel.localSize[0]), _size_y(parameters.parallel.localSize[1]), _size_z(parameters.parallel.localSize[2]), _cellsX(_size_x+3), _cellsY(_size_y+3), _cellsZ(_size_z+3), // Probably far from the best way to write this _pressure(parameters.geometry.dim==2?ScalarField(_size_x + 3, _size_y + 3): ScalarField(_size_x + 3, _size_y + 3, _size_z + 3)), // added for turbulence cases _viscosity(parameters.geometry.dim==2?ScalarField(_size_x + 3, _size_y + 3): ScalarField(_size_x + 3, _size_y + 3, _size_z + 3)), _velocity(parameters.geometry.dim==2?VectorField(_size_x + 3, _size_y + 3): VectorField(_size_x + 3, _size_y + 3, _size_z + 3)), _flags(parameters.geometry.dim==2?IntScalarField(_size_x + 3, _size_y + 3): IntScalarField(_size_x + 3, _size_y + 3, _size_z + 3)), _nearWallDis(parameters.geometry.dim==2?ScalarField(_size_x + 3, _size_y + 3): ScalarField(_size_x + 3, _size_y + 3, _size_z + 3)), _FGH(parameters.geometry.dim==2?VectorField(_size_x + 3, _size_y + 3): VectorField(_size_x + 3, _size_y + 3, _size_z + 3)), _RHS(parameters.geometry.dim==2?ScalarField(_size_x + 3, _size_y + 3): ScalarField(_size_x + 3, _size_y + 3, _size_z + 3)) { }
double FluidMixture::operator()(const ScalarFieldArray& indep, ScalarFieldArray& Phi_indep, Outputs outputs) const { static StopWatch watch("FluidMixture::operator()"); watch.start(); //logPrintf("indep.size: %d nIndep: %d\n",indep.size(),nIndep); myassert(indep.size()==get_nIndep()); //---------- Compute site densities from the independent variables --------- ScalarFieldTildeArray Ntilde(nDensities); //site densities (in reciprocal space) std::vector< vector3<> > P0(component.size()); //polarization densities G=0 for(unsigned ic=0; ic<component.size(); ic++) { const FluidComponent& c = *component[ic]; ScalarFieldArray N(c.molecule.sites.size()); c.idealGas->getDensities(&indep[c.offsetIndep], &N[0], P0[ic]); for(unsigned i=0; i<c.molecule.sites.size(); i++) { //Replace negative densities with 0: double Nmin, Nmax; callPref(eblas_capMinMax)(gInfo.nr, N[i]->dataPref(), Nmin, Nmax, 0.); //store site densities in fourier space Ntilde[c.offsetDensity+i] = J(N[i]); N[i] = 0; //Really skimp on memory! } } //----------- Handle density constraints ------------ std::vector<double> Nscale(component.size(), 1.0); //density scale factor that satisfies the constraint std::vector<double> Ntot_c; //vector of total number of molecules for each component std::vector<double> Nscale_Qfixed(component.size(), 0.); //derivative of Nscale w.r.t the fixed charge std::vector<std::vector<double> > Nscale_N0(component.size(), std::vector<double>(component.size(),0.0)); //jacobian of Nscale w.r.t the uncorrected molecule counts std::vector<string> names; //list of molecule names //Find fixed N and charged species: double Qfixed = 0.0; if(rhoExternal) Qfixed += integral(rhoExternal); std::vector<std::pair<double,double> > N0Q; for(unsigned ic=0; ic<component.size(); ic++) { const FluidComponent& c = *component[ic]; double Qmolecule = c.molecule.getCharge(); if(c.Nnorm>0 || Qmolecule) { double N0 = integral(Ntilde[c.offsetDensity])/c.molecule.sites[0]->positions.size(); if(c.Nnorm>0) { Nscale[ic] = c.Nnorm/N0; Nscale_N0[ic][ic] = -c.Nnorm/pow(N0,2); Qfixed += Qmolecule*c.Nnorm; } else { N0Q.push_back(std::make_pair(N0, Qmolecule)); names.push_back(c.molecule.name); } } } //Find the betaV (see Qtot()) that makes the unit cell neutral if(N0Q.size()==0) { if(fabs(Qfixed)>fabs(Qtol)) die("Unit cell has a fixed net charge %le," "and there are no free charged species to neutralize it.\n", Qfixed); } else { double Qprime, Qcell, betaV=0.0; if(Qfixed+Qtot(-HUGE_VAL,Qprime,N0Q)<0) die("Unit cell will always have a net negative charge (no free positive charges).\n") if(Qfixed+Qtot(+HUGE_VAL,Qprime,N0Q)>0) die("Unit cell will always have a net positive charge (no free negative charges).\n") for(int iter=0; iter<10; iter++) //while(1) { Qcell = Qfixed+Qtot(betaV,Qprime,N0Q,&names,&gInfo,verboseLog); if(verboseLog) logPrintf("betaV = %le, Qcell = %le, Qprime = %le\n", betaV, Qcell, Qprime); if(fabs(Qcell)<fabs(Qtol)) break; if(std::isnan(Qcell)) { betaV=0.0; break;} betaV -= Qcell/Qprime; //Newton-Raphson update } for(unsigned ic=0; ic<component.size(); ic++) { const FluidComponent& ci = *component[ic]; double Qi = ci.molecule.getCharge(); if(Qi && ci.Nnorm<=0) { Nscale[ic] = exp(-Qi*betaV); Nscale_Qfixed[ic] = exp(-Qi*betaV) * Qi / Qprime; for(unsigned jc=0; jc<component.size(); jc++) { const FluidComponent& cj = *component[jc]; double Qj = cj.molecule.getCharge(); if(Qj && cj.Nnorm<=0) Nscale_N0[ic][jc] += Qi*Qj*exp(-(Qi+Qj)*betaV)/Qprime; } } } } std::vector<double> Phi_Nscale(component.size(), 0.0); //accumulate explicit derivatives w.r.t Nscale here //Apply the scale factors to the site densities for(unsigned ic=0; ic<component.size(); ic++) { const FluidComponent& c = *component[ic]; for(unsigned i=0; i<c.molecule.sites.size(); i++) Ntilde[c.offsetDensity+i] *= Nscale[ic]; P0[ic] *= Nscale[ic]; Ntot_c.push_back(integral(Ntilde[c.offsetDensity])); } EnergyComponents Phi; //the grand free energy (with component information) ScalarFieldTildeArray Phi_Ntilde(nDensities); //gradients (functional derivative) w.r.t reciprocal space site densities nullToZero(Phi_Ntilde,gInfo); std::vector< vector3<> > Phi_P0(component.size()); //functional derivative w.r.t polarization density G=0 VectorFieldTilde Phi_epsMF; //functional derivative w.r.t mean field electric field //G=0 fix for mismatch in fluid-fluid vs. fluid-electron charge kernels //We do this AFTER we have applied the appropriate scale factors if( N0Q.size() && (!useMFKernel)) //if we have charged species in our fluid, and are using mismatched charge kernels { for(unsigned ic=0; ic<component.size(); ic++) { const FluidComponent& c = *component[ic]; for(unsigned i=0; i<c.molecule.sites.size(); i++) { const Molecule::Site& s = *(c.molecule.sites[i]); Phi["Gzero"] += Qfixed*(Ntot_c[ic]/gInfo.detR-c.idealGas->Nbulk)*s.positions.size()*s.deltaS; Phi_Ntilde[c.offsetDensity+i]->data()[0] += (1.0/gInfo.dV) * (Qfixed*s.deltaS); } } } //--------- Compute the (scaled) mean field coulomb interaction -------- { ScalarFieldTilde rho; //total charge density ScalarFieldTilde rhoMF; //effective charge density for mean-field term bool needRho = rhoExternal || outputs.Phi_rhoExternal; VectorFieldTilde epsMF = polarizable ? J(VectorField(&indep[nIndepIdgas])) : 0; //mean field electric field vector3<> P0tot; for(unsigned ic=0; ic<component.size(); ic++) { const FluidComponent& c = *component[ic]; for(unsigned i=0; i<c.molecule.sites.size(); i++) { const Molecule::Site& s = *(c.molecule.sites[i]); if(s.chargeKernel) { if(needRho) rho += s.chargeKernel * Ntilde[c.offsetDensity+i]; rhoMF += s.chargeKernel(0) * (c.molecule.mfKernel * Ntilde[c.offsetDensity+i]); } //Polarization contributions: if(polarizable && s.polKernel) { #define Polarization_Compute_Pi_Ni \ VectorField Pi = (Cpol*s.alpha) * I(c.molecule.mfKernel*epsMF - (rhoExternal ? gradient(s.polKernel*coulomb(rhoExternal)) : 0)); \ ScalarField Ni = I(Ntilde[c.offsetDensity+i]); Polarization_Compute_Pi_Ni ScalarField Phi_Ni = (0.5/(Cpol*s.alpha))*lengthSquared(Pi); Phi["Apol"] += gInfo.dV * dot(Ni, Phi_Ni); //Derivative contribution to site densities: Phi_Ntilde[c.offsetDensity+i] += Idag(Phi_Ni); Phi_Ni=0; //Update contributions to bound charge: VectorFieldTilde NPtilde = J(Ni * Pi); Pi=0; Ni=0; ScalarFieldTilde divNPbar; if(needRho) rho -= s.polKernel*divergence(NPtilde); VectorFieldTilde NPbarMF = c.molecule.mfKernel*NPtilde; NPtilde=0; rhoMF -= divergence(NPbarMF); Phi_epsMF += gInfo.nr * NPbarMF; P0tot += getGzero(NPbarMF); } } P0tot += P0[ic]; } if(rhoMF) { //External charge interaction: ScalarFieldTilde Phi_rho; ScalarFieldTilde Phi_rhoMF; if(needRho) { if(rhoExternal) { ScalarFieldTilde OdExternal = O(coulomb(rhoExternal)); if (!useMFKernel) { Phi["ExtCoulomb"] += dot(rho, OdExternal); Phi_rho += OdExternal; } if (useMFKernel) { Phi["ExtCoulomb"] += dot(rhoMF, OdExternal); Phi_rhoMF += OdExternal; } } if(outputs.Phi_rhoExternal) { if (!useMFKernel) *outputs.Phi_rhoExternal = coulomb(rho); if (useMFKernel) *outputs.Phi_rhoExternal = coulomb(rhoMF); } } //Mean field contributions: { ScalarFieldTilde OdMF = O(coulomb(rhoMF)); //mean-field electrostatic potential Phi["Coulomb"] += 0.5*dot(rhoMF, OdMF); Phi_rhoMF += OdMF; } //Polarization density interactions: if(outputs.electricP) *outputs.electricP = P0tot * gInfo.detR; //--- corrections for net dipole in cell: vector3<> Phi_P0tot = (4*M_PI*gInfo.detR) * P0tot; Phi["PsqCell"] += 0.5 * dot(Phi_P0tot, P0tot); //--- external electric field interactions: Phi["ExtCoulomb"] -= gInfo.detR * dot(Eexternal, P0tot); Phi_P0tot -= gInfo.detR * Eexternal; //Propagate gradients: for(unsigned ic=0; ic<component.size(); ic++) { const FluidComponent& c = *component[ic]; for(unsigned i=0; i<c.molecule.sites.size(); i++) { const Molecule::Site& s = *(c.molecule.sites[i]); if(s.chargeKernel) { if(Phi_rho) Phi_Ntilde[c.offsetDensity+i] += (1./gInfo.dV) * (s.chargeKernel * Phi_rho); Phi_Ntilde[c.offsetDensity+i] += (s.chargeKernel(0)/gInfo.dV) * (c.molecule.mfKernel * Phi_rhoMF); } //Polarization contributions: if(polarizable && s.polKernel) { VectorFieldTilde Phi_NPtilde = gradient(c.molecule.mfKernel*Phi_rhoMF + (needRho ? s.polKernel*Phi_rho : 0)); setGzero(Phi_NPtilde, getGzero(Phi_NPtilde) + Phi_P0tot); VectorField Phi_NP = Jdag(Phi_NPtilde); Phi_NPtilde=0; //propagate gradients from NP to N, epsMF and rhoExternal: Polarization_Compute_Pi_Ni #undef Polarization_Compute_Pi_Ni // --> via Ni ScalarField Phi_Ni; for(int k=0; k<3; k++) Phi_Ni += Phi_NP[k]*Pi[k]; Phi_Ntilde[c.offsetDensity+i] += (1./gInfo.dV) * Idag(Phi_Ni); Phi_Ni=0; // --> via Pi VectorFieldTilde Phi_PiTilde = Idag(Phi_NP * Ni); Phi_NP=0; Phi_epsMF += (Cpol*s.alpha/gInfo.dV)*(c.molecule.mfKernel*Phi_PiTilde); } } Phi_P0[ic] += (1./gInfo.detR) * Phi_P0tot; //convert to functional derivative } } } //--------- Hard sphere mixture and bonding ------------- { //Compute the FMT weighted densities: ScalarFieldTilde n0tilde, n1tilde, n2tilde, n3tilde, n1vTilde, n2mTilde; std::vector<ScalarField> n0mol(component.size(), 0); //partial n0 for molecules that need bonding corrections std::vector<int> n0mult(component.size(), 0); //number of sites which contribute to n0 for each molecule std::vector<std::map<double,int> > bond(component.size()); //sets of bonds for each molecule bool bondsPresent = false; //whether bonds are present for any molecule for(unsigned ic=0; ic<component.size(); ic++) { const FluidComponent& c = *component[ic]; bond[ic] = c.molecule.getBonds(); ScalarFieldTilde n0molTilde; for(unsigned i=0; i<c.molecule.sites.size(); i++) { const Molecule::Site& s = *(c.molecule.sites[i]); if(s.Rhs) { const ScalarFieldTilde& Nsite = Ntilde[c.offsetDensity+i]; n0mult[ic] += s.positions.size(); n0molTilde += s.w0 * Nsite; n1tilde += s.w1 * Nsite; n2tilde += s.w2 * Nsite; n3tilde += s.w3 * Nsite; n1vTilde += s.w1v * Nsite; n2mTilde += s.w2m * Nsite; } } if(n0molTilde) n0tilde += n0molTilde; if(bond[ic].size()) { n0mol[ic] = I(n0molTilde); bondsPresent = true; } } if(n0tilde) //at least one sphere in the mixture { ScalarField n0 = I(n0tilde); n0tilde=0; ScalarField n1 = I(n1tilde); n1tilde=0; ScalarField n2 = I(n2tilde); n2tilde=0; ScalarField Phi_n0, Phi_n1, Phi_n2; ScalarFieldTilde Phi_n3tilde, Phi_n1vTilde, Phi_n2mTilde; //Compute the sphere mixture free energy: Phi["MixedFMT"] += T * PhiFMT(n0, n1, n2, n3tilde, n1vTilde, n2mTilde, Phi_n0, Phi_n1, Phi_n2, Phi_n3tilde, Phi_n1vTilde, Phi_n2mTilde); //Bonding corrections if required if(bondsPresent) { for(unsigned ic=0; ic<component.size(); ic++) { const FluidComponent& c = *component[ic]; ScalarField Phi_n0mol; for(const auto& b: bond[ic]) Phi["Bonding"] += T * PhiBond(b.first, b.second*1./n0mult[ic], n0mol[ic], n2, n3tilde, Phi_n0mol, Phi_n2, Phi_n3tilde); if(Phi_n0mol) { //Propagate gradient w.r.t n0mol[ic] to the site densities: ScalarFieldTilde Phi_n0molTilde = Idag(Phi_n0mol); for(unsigned i=0; i<c.molecule.sites.size(); i++) { const Molecule::Site& s = *(c.molecule.sites[i]); if(s.Rhs) Phi_Ntilde[c.offsetDensity+i] += T * (s.w0 * Phi_n0molTilde); } } } } //Accumulate gradients w.r.t weighted densities to site densities: ScalarFieldTilde Phi_n0tilde = Idag(Phi_n0); Phi_n0=0; ScalarFieldTilde Phi_n1tilde = Idag(Phi_n1); Phi_n1=0; ScalarFieldTilde Phi_n2tilde = Idag(Phi_n2); Phi_n2=0; for(const FluidComponent* c: component) { for(unsigned i=0; i<c->molecule.sites.size(); i++) { const Molecule::Site& s = *(c->molecule.sites[i]); if(s.Rhs) { ScalarFieldTilde& Phi_Nsite = Phi_Ntilde[c->offsetDensity+i]; Phi_Nsite += T * (s.w0 * Phi_n0tilde); Phi_Nsite += T * (s.w1 * Phi_n1tilde); Phi_Nsite += T * (s.w2 * Phi_n2tilde); Phi_Nsite += T * (s.w3 * Phi_n3tilde); Phi_Nsite += T * (s.w1v * Phi_n1vTilde); Phi_Nsite += T * (s.w2m * Phi_n2mTilde); } } } } } //---------- Excess functionals -------------- for(const FluidComponent* c: component) if(c->fex) Phi["Fex("+c->molecule.name+")"] += c->fex->compute(Ñ[c->offsetDensity], &Phi_Ntilde[c->offsetDensity]); //--------- Mixing functionals -------------- for(const Fmix* fmix: fmixArr) Phi["Fmix("+fmix->getName()+")"] += fmix->compute(Ntilde, Phi_Ntilde); //--------- PhiNI --------- nullToZero(Phi_Ntilde, gInfo); if(outputs.N) outputs.N->resize(nDensities); //Put the site densities and gradients back in real space ScalarFieldArray N(nDensities); ScalarFieldArray Phi_N(nDensities); for(unsigned i=0; i<nDensities; i++) { N[i] = I(Ntilde[i]); Ntilde[i]=0; Phi_N[i] = Jdag(Phi_Ntilde[i]); Phi_Ntilde[i] = 0; if(outputs.N) (*outputs.N)[i] = N[i]; //Link site-density to return pointer if necessary } //Estimate psiEff based on gradients, if requested if(outputs.psiEff) { outputs.psiEff->resize(nDensities); for(const FluidComponent* c: component) for(unsigned i=0; i<c->molecule.sites.size(); i++) { ScalarField& psiCur = outputs.psiEff->at(c->offsetDensity+i); psiCur = Phi_N[c->offsetDensity+i] + c->idealGas->V[i]; if(i==0) psiCur -= c->idealGas->mu / c->molecule.sites[0]->positions.size(); psiCur *= (-1./T); } } for(unsigned ic=0; ic<component.size(); ic++) { const FluidComponent& c = *component[ic]; Phi["PhiNI("+c.molecule.name+")"] += c.idealGas->compute(&indep[c.offsetIndep], &N[c.offsetDensity], &Phi_N[c.offsetDensity], Nscale[ic], Phi_Nscale[ic]); //Fixed N correction to entropy: if(Nscale[ic]!=1.0) { double deltaTs = T*log(Nscale[ic]) / c.molecule.sites[0]->positions.size(); Phi_N[c.offsetDensity] += deltaTs; Phi["PhiNI("+c.molecule.name+")"] += integral(N[c.offsetDensity])*deltaTs; } } //Add in the implicit contributions to Phi_Nscale for(unsigned ic=0; ic<component.size(); ic++) { const FluidComponent& ci = *component[ic]; bool anyNonzero=false; for(unsigned jc=0; jc<component.size(); jc++) if(Nscale_N0[ic][jc]) anyNonzero=true; if(anyNonzero) { Phi_Nscale[ic] += gInfo.detR*dot(P0[ic], Phi_P0[ic])/ Nscale[ic]; for(unsigned i=0; i<ci.molecule.sites.size(); i++) Phi_Nscale[ic] += gInfo.dV*dot(N[ci.offsetDensity+i], Phi_N[ci.offsetDensity+i])/ Nscale[ic]; } } //Propagate gradients from Nscale to N: for(unsigned jc=0; jc<component.size(); jc++) { const FluidComponent& cj = *component[jc]; double Phi_Ncontrib = 0.0; for(unsigned ic=0; ic<component.size(); ic++) if(Nscale_N0[ic][jc]) Phi_Ncontrib += Phi_Nscale[ic] * Nscale_N0[ic][jc]; if(Phi_Ncontrib) Phi_N[cj.offsetDensity] += Phi_Ncontrib / (Nscale[jc] * cj.molecule.sites[0]->positions.size()); } //Propagate gradients from Phi_N and Phi_P to Phi_indep Phi_indep.resize(get_nIndep()); for(unsigned ic=0; ic<component.size(); ic++) { const FluidComponent& c = *component[ic]; c.idealGas->convertGradients(&indep[c.offsetIndep], &N[c.offsetDensity], &Phi_N[c.offsetDensity], Phi_P0[ic], &Phi_indep[c.offsetIndep], Nscale[ic]); } for(unsigned k=nIndepIdgas; k<get_nIndep(); k++) Phi_indep[k] = Jdag(Phi_epsMF[k-nIndepIdgas]); //Propagate gradients from Nscale to Qfixed / rhoExternal (Natural G=0 solution) if(outputs.Phi_rhoExternal) { double Phi_Qfixed = 0.; for(unsigned ic=0; ic<component.size(); ic++) { Phi_Qfixed += Phi_Nscale[ic] * Nscale_Qfixed[ic]; if (N0Q.size() && (!useMFKernel)) { const FluidComponent& c = *component[ic]; for(unsigned i=0; i<c.molecule.sites.size(); i++) { //Correction to lambda from charge kernel mismatch const Molecule::Site& s = *(c.molecule.sites[i]); double lambda_s = (Ntot_c[ic]/gInfo.detR-c.idealGas->Nbulk)*s.deltaS*s.positions.size(); if(verboseLog) logPrintf("Charge kernel mismatch correction for site %s of molecule %s: %lg\n", s.name.c_str(),c.molecule.name.c_str(),lambda_s); Phi_Qfixed += lambda_s; } if(verboseLog) logPrintf("Total number of molecules of type %s: %lg\n",c.molecule.name.c_str(),Ntot_c[ic]); } } nullToZero(*outputs.Phi_rhoExternal, gInfo); (*outputs.Phi_rhoExternal)->setGzero(Phi_Qfixed); } Phi["+pV"] += p * gInfo.detR; //background correction if(verboseLog) Phi.print(globalLog, true, "\t\t\t\t%15s = %25.16lf\n"); if(outputs.Phi) *(outputs.Phi) = Phi; Phi_indep *= gInfo.dV; //convert functional derivative to partial derivative watch.stop(); return Phi; }