/** * @file AqueousTransport.cpp * Transport properties for aqueous systems */ #include "cantera/thermo/ThermoPhase.h" #include "cantera/transport/AqueousTransport.h" #include "cantera/base/utilities.h" #include "cantera/transport/TransportParams.h" #include "cantera/transport/LiquidTransportParams.h" #include "cantera/transport/TransportFactory.h" #include "cantera/numerics/ctlapack.h" #include "cantera/base/stringUtils.h" #include #include using namespace std; namespace Cantera { //==================================================================================================================== AqueousTransport::AqueousTransport() : m_iStateMF(-1), m_temp(-1.0), m_logt(0.0), m_sqrt_t(-1.0), m_t14(-1.0), m_t32(-1.0), m_sqrt_kbt(-1.0), m_press(-1.0), m_lambda(-1.0), m_viscmix(-1.0), m_viscmix_ok(false), m_viscwt_ok(false), m_spvisc_ok(false), m_diffmix_ok(false), m_bindiff_ok(false), m_spcond_ok(false), m_condmix_ok(false), m_mode(-1000), m_debug(false), m_nDim(1) { } //==================================================================================================================== // Initialize the object /* * This is where we dimension everything. */ bool AqueousTransport::initLiquid(LiquidTransportParams& tr) { // constant substance attributes m_thermo = tr.thermo; m_nsp = m_thermo->nSpecies(); // make a local copy of the molecular weights m_mw.resize(m_nsp); copy(m_thermo->molecularWeights().begin(), m_thermo->molecularWeights().end(), m_mw.begin()); // copy polynomials and parameters into local storage //m_visccoeffs = tr.visccoeffs; //m_condcoeffs = tr.condcoeffs; //m_diffcoeffs = tr.diffcoeffs; cout << "In AqueousTransport::initLiquid we need to replace" << endl << "LiquidTransportParams polynomial coefficients with" << endl << "those in LiquidTransportData as in SimpleTransport." << endl; m_mode = tr.mode_; m_phi.resize(m_nsp, m_nsp, 0.0); m_wratjk.resize(m_nsp, m_nsp, 0.0); m_wratkj1.resize(m_nsp, m_nsp, 0.0); for (size_t j = 0; j < m_nsp; j++) for (size_t k = j; k < m_nsp; k++) { m_wratjk(j,k) = sqrt(m_mw[j]/m_mw[k]); m_wratjk(k,j) = sqrt(m_wratjk(j,k)); m_wratkj1(j,k) = sqrt(1.0 + m_mw[k]/m_mw[j]); } m_polytempvec.resize(5); m_visc.resize(m_nsp); m_sqvisc.resize(m_nsp); m_cond.resize(m_nsp); m_bdiff.resize(m_nsp, m_nsp); m_molefracs.resize(m_nsp); m_spwork.resize(m_nsp); // resize the internal gradient variables m_Grad_X.resize(m_nDim * m_nsp, 0.0); m_Grad_T.resize(m_nDim, 0.0); m_Grad_V.resize(m_nDim, 0.0); m_Grad_mu.resize(m_nDim * m_nsp, 0.0); // set all flags to false m_viscmix_ok = false; m_viscwt_ok = false; m_spvisc_ok = false; m_spcond_ok = false; m_condmix_ok = false; m_spcond_ok = false; m_diffmix_ok = false; return true; } //==================================================================================================================== /* * The viscosity is computed using the Wilke mixture rule. * \f[ * \mu = \sum_k \frac{\mu_k X_k}{\sum_j \Phi_{k,j} X_j}. * \f] * Here \f$ \mu_k \f$ is the viscosity of pure species \e k, * and * \f[ * \Phi_{k,j} = \frac{\left[1 * + \sqrt{\left(\frac{\mu_k}{\mu_j}\sqrt{\frac{M_j}{M_k}}\right)}\right]^2} * {\sqrt{8}\sqrt{1 + M_k/M_j}} * \f] * @see updateViscosity_T(); */ doublereal AqueousTransport::viscosity() { update_T(); update_C(); if (m_viscmix_ok) { return m_viscmix; } // update m_visc[] and m_phi[] if necessary if (!m_viscwt_ok) { updateViscosity_T(); } multiply(m_phi, DATA_PTR(m_molefracs), DATA_PTR(m_spwork)); m_viscmix = 0.0; for (size_t k = 0; k < m_nsp; k++) { m_viscmix += m_molefracs[k] * m_visc[k]/m_spwork[k]; //denom; } return m_viscmix; } //==================================================================================================================== // Returns the pure species viscosities /* * * Controlling update boolean = m_viscwt_ok * * @param visc Vector of species viscosities */ void AqueousTransport::getSpeciesViscosities(doublereal* const visc) { updateViscosity_T(); copy(m_visc.begin(), m_visc.end(), visc); } //==================================================================================================================== void AqueousTransport::getBinaryDiffCoeffs(const size_t ld, doublereal* const d) { update_T(); // if necessary, evaluate the binary diffusion coefficients // from the polynomial fits if (!m_bindiff_ok) { updateDiff_T(); } doublereal pres = m_thermo->pressure(); doublereal rp = 1.0/pres; for (size_t i = 0; i < m_nsp; i++) for (size_t j = 0; j < m_nsp; j++) { d[ld*j + i] = rp * m_bdiff(i,j); } } //==================================================================================================================== // Get the electrical Mobilities (m^2/V/s). /* * This function returns the mobilities. In some formulations * this is equal to the normal mobility multiplied by faraday's constant. * * Frequently, but not always, the mobility is calculated from the * diffusion coefficient using the Einstein relation * * \f[ * \mu^e_k = \frac{F D_k}{R T} * \f] * * @param mobil_e Returns the mobilities of * the species in array \c mobil_e. The array must be * dimensioned at least as large as the number of species. */ void AqueousTransport::getMobilities(doublereal* const mobil) { getMixDiffCoeffs(DATA_PTR(m_spwork)); doublereal c1 = ElectronCharge / (Boltzmann * m_temp); for (size_t k = 0; k < m_nsp; k++) { mobil[k] = c1 * m_spwork[k]; } } //==================================================================================================================== void AqueousTransport::getFluidMobilities(doublereal* const mobil) { getMixDiffCoeffs(DATA_PTR(m_spwork)); doublereal c1 = 1.0 / (GasConstant * m_temp); for (size_t k = 0; k < m_nsp; k++) { mobil[k] = c1 * m_spwork[k]; } } //==================================================================================================================== void AqueousTransport::set_Grad_V(const doublereal* const grad_V) { for (size_t a = 0; a < m_nDim; a++) { m_Grad_V[a] = grad_V[a]; } } //==================================================================================================================== void AqueousTransport::set_Grad_T(const doublereal* const grad_T) { for (size_t a = 0; a < m_nDim; a++) { m_Grad_T[a] = grad_T[a]; } } //==================================================================================================================== void AqueousTransport::set_Grad_X(const doublereal* const grad_X) { size_t itop = m_nDim * m_nsp; for (size_t i = 0; i < itop; i++) { m_Grad_X[i] = grad_X[i]; } } //==================================================================================================================== /* * The thermal conductivity is computed from the following mixture rule: * \[ * \lambda = 0.5 \left( \sum_k X_k \lambda_k * + \frac{1}{\sum_k X_k/\lambda_k}\right) * \] */ doublereal AqueousTransport::thermalConductivity() { update_T(); update_C(); if (!m_spcond_ok) { updateCond_T(); } if (!m_condmix_ok) { doublereal sum1 = 0.0, sum2 = 0.0; for (size_t k = 0; k < m_nsp; k++) { sum1 += m_molefracs[k] * m_cond[k]; sum2 += m_molefracs[k] / m_cond[k]; } m_lambda = 0.5*(sum1 + 1.0/sum2); } return m_lambda; } //==================================================================================================================== // Return a vector of Thermal diffusion coefficients [kg/m/sec]. /* * The thermal diffusion coefficient \f$ D^T_k \f$ is defined * so that the diffusive mass flux of species k<\I> induced by the * local temperature gradient is given by the following formula * * \f[ * M_k J_k = -D^T_k \nabla \ln T. * \f] * * The thermal diffusion coefficient can be either positive or negative. * * In this method we set it to zero. * * @param dt On return, dt will contain the species thermal * diffusion coefficients. Dimension dt at least as large as * the number of species. Units are kg/m/s. */ void AqueousTransport::getThermalDiffCoeffs(doublereal* const dt) { for (size_t k = 0; k < m_nsp; k++) { dt[k] = 0.0; } } //==================================================================================================================== // Get the species diffusive mass fluxes wrt to the specified solution averaged velocity, // given the gradients in mole fraction and temperature /* * Units for the returned fluxes are kg m-2 s-1. * * Usually the specified solution average velocity is the mass averaged velocity. * This is changed in some subclasses, however. * * @param ndim Number of dimensions in the flux expressions * @param grad_T Gradient of the temperature * (length = ndim) * @param ldx Leading dimension of the grad_X array * (usually equal to m_nsp but not always) * @param grad_X Gradients of the mole fraction * Flat vector with the m_nsp in the inner loop. * length = ldx * ndim * @param ldf Leading dimension of the fluxes array * (usually equal to m_nsp but not always) * @param fluxes Output of the diffusive mass fluxes * Flat vector with the m_nsp in the inner loop. * length = ldx * ndim */ void AqueousTransport::getSpeciesFluxes(size_t ndim, const doublereal* const grad_T, size_t ldx, const doublereal* const grad_X, size_t ldf, doublereal* const fluxes) { set_Grad_T(grad_T); set_Grad_X(grad_X); getSpeciesFluxesExt(ldf, fluxes); } //==================================================================================================================== // Return the species diffusive mass fluxes wrt to the specified averaged velocity, /* * This method acts similarly to getSpeciesFluxesES() but * requires all gradients to be preset using methods set_Grad_X(), set_Grad_V(), set_Grad_T(). * See the documentation of getSpeciesFluxesES() for details. * * units = kg/m2/s * * Internally, gradients in the in mole fraction, temperature * and electrostatic potential contribute to the diffusive flux * * The diffusive mass flux of species \e k is computed from the following formula * * \f[ * j_k = - \rho M_k D_k \nabla X_k - Y_k V_c * \f] * * where V_c is the correction velocity * * \f[ * V_c = - \sum_j {\rho M_j D_j \nabla X_j} * \f] * * @param ldf Stride of the fluxes array. Must be equal to or greater than the number of species. * @param fluxes Output of the diffusive fluxes. Flat vector with the m_nsp in the inner loop. * length = ldx * ndim */ void AqueousTransport::getSpeciesFluxesExt(size_t ldf, doublereal* const fluxes) { update_T(); update_C(); getMixDiffCoeffs(DATA_PTR(m_spwork)); const vector_fp& mw = m_thermo->molecularWeights(); const doublereal* y = m_thermo->massFractions(); doublereal rhon = m_thermo->molarDensity(); // Unroll wrt ndim vector_fp sum(m_nDim,0.0); for (size_t n = 0; n < m_nDim; n++) { for (size_t k = 0; k < m_nsp; k++) { fluxes[n*ldf + k] = -rhon * mw[k] * m_spwork[k] * m_Grad_X[n*m_nsp + k]; sum[n] += fluxes[n*ldf + k]; } } // add correction flux to enforce sum to zero for (size_t n = 0; n < m_nDim; n++) { for (size_t k = 0; k < m_nsp; k++) { fluxes[n*ldf + k] -= y[k]*sum[n]; } } } //==================================================================================================================== /** * Mixture-averaged diffusion coefficients [m^2/s]. * * For the single species case or the pure fluid case * the routine returns the self-diffusion coefficient. * This is need to avoid a Nan result in the formula * below. */ void AqueousTransport::getMixDiffCoeffs(doublereal* const d) { update_T(); update_C(); // update the binary diffusion coefficients if necessary if (!m_bindiff_ok) { updateDiff_T(); } size_t k, j; doublereal mmw = m_thermo->meanMolecularWeight(); doublereal sumxw = 0.0, sum2; doublereal p = m_press; if (m_nsp == 1) { d[0] = m_bdiff(0,0) / p; } else { for (k = 0; k < m_nsp; k++) { sumxw += m_molefracs[k] * m_mw[k]; } for (k = 0; k < m_nsp; k++) { sum2 = 0.0; for (j = 0; j < m_nsp; j++) { if (j != k) { sum2 += m_molefracs[j] / m_bdiff(j,k); } } if (sum2 <= 0.0) { d[k] = m_bdiff(k,k) / p; } else { d[k] = (sumxw - m_molefracs[k] * m_mw[k])/(p * mmw * sum2); } } } } //==================================================================================================================== // Handles the effects of changes in the Temperature, internally // within the object. /* * This is called whenever a transport property is * requested. * The first task is to check whether the temperature has changed * since the last call to update_T(). * If it hasn't then an immediate return is carried out. * * @internal */ void AqueousTransport::update_T() { doublereal t = m_thermo->temperature(); if (t == m_temp) { return; } if (t < 0.0) { throw CanteraError("AqueousTransport::update_T", "negative temperature "+fp2str(t)); } // Compute various functions of temperature m_temp = t; m_logt = log(m_temp); m_kbt = Boltzmann * m_temp; m_sqrt_t = sqrt(m_temp); m_t14 = sqrt(m_sqrt_t); m_t32 = m_temp * m_sqrt_t; m_sqrt_kbt = sqrt(Boltzmann*m_temp); // compute powers of log(T) m_polytempvec[0] = 1.0; m_polytempvec[1] = m_logt; m_polytempvec[2] = m_logt*m_logt; m_polytempvec[3] = m_logt*m_logt*m_logt; m_polytempvec[4] = m_logt*m_logt*m_logt*m_logt; // temperature has changed, so polynomial temperature // interpolations will need to be reevaluated. // Set all of these flags to false m_viscmix_ok = false; m_spvisc_ok = false; m_viscwt_ok = false; m_spcond_ok = false; m_diffmix_ok = false; m_bindiff_ok = false; m_condmix_ok = false; // For now, for a concentration redo also m_iStateMF = -1; } //==================================================================================================================== /** * @internal This is called the first time any transport property * is requested from Mixture after the concentrations * have changed. */ void AqueousTransport::update_C() { doublereal pres = m_thermo->pressure(); // Check for changes in the mole fraction vector. //int iStateNew = m_thermo->getIStateMF(); //if (iStateNew == m_iStateMF) { // if (pres == m_press) { // return; // } // } else { // m_iStateMF = iStateNew; //} m_press = pres; // signal that concentration-dependent quantities will need to // be recomputed before use, and update the local mole // fractions. m_viscmix_ok = false; m_diffmix_ok = false; m_condmix_ok = false; m_thermo->getMoleFractions(DATA_PTR(m_molefracs)); // add an offset to avoid a pure species condition or // negative mole fractions. *Tiny* is 1.0E-20, a value // which is below the additive machine precision of mole fractions. for (size_t k = 0; k < m_nsp; k++) { m_molefracs[k] = std::max(Tiny, m_molefracs[k]); } } //==================================================================================================================== /* * Update the temperature-dependent parts of the mixture-averaged * thermal conductivity. */ void AqueousTransport::updateCond_T() { if (m_mode == CK_Mode) { for (size_t k = 0; k < m_nsp; k++) { m_cond[k] = exp(dot4(m_polytempvec, m_condcoeffs[k])); } } else { for (size_t k = 0; k < m_nsp; k++) { m_cond[k] = m_sqrt_t*dot5(m_polytempvec, m_condcoeffs[k]); } } m_spcond_ok = true; m_condmix_ok = false; } //==================================================================================================================== /* * Update the binary diffusion coefficients. These are evaluated * from the polynomial fits at unit pressure (1 Pa). */ void AqueousTransport::updateDiff_T() { // evaluate binary diffusion coefficients at unit pressure size_t ic = 0; if (m_mode == CK_Mode) { for (size_t i = 0; i < m_nsp; i++) { for (size_t j = i; j < m_nsp; j++) { m_bdiff(i,j) = exp(dot4(m_polytempvec, m_diffcoeffs[ic])); m_bdiff(j,i) = m_bdiff(i,j); ic++; } } } else { for (size_t i = 0; i < m_nsp; i++) { for (size_t j = i; j < m_nsp; j++) { m_bdiff(i,j) = m_temp * m_sqrt_t*dot5(m_polytempvec, m_diffcoeffs[ic]); m_bdiff(j,i) = m_bdiff(i,j); ic++; } } } m_bindiff_ok = true; m_diffmix_ok = false; } //==================================================================================================================== /* * Update the pure-species viscosities. */ void AqueousTransport::updateSpeciesViscosities() { if (m_mode == CK_Mode) { for (size_t k = 0; k < m_nsp; k++) { m_visc[k] = exp(dot4(m_polytempvec, m_visccoeffs[k])); m_sqvisc[k] = sqrt(m_visc[k]); } } else { for (size_t k = 0; k < m_nsp; k++) { // the polynomial fit is done for sqrt(visc/sqrt(T)) m_sqvisc[k] = m_t14*dot5(m_polytempvec, m_visccoeffs[k]); m_visc[k] = (m_sqvisc[k]*m_sqvisc[k]); } } m_spvisc_ok = true; } //==================================================================================================================== /* * Update the temperature-dependent viscosity terms. * Updates the array of pure species viscosities, and the * weighting functions in the viscosity mixture rule. * The flag m_visc_ok is set to true. */ void AqueousTransport::updateViscosity_T() { doublereal vratiokj, wratiojk, factor1; if (!m_spvisc_ok) { updateSpeciesViscosities(); } // see Eq. (9-5.15) of Reid, Prausnitz, and Poling for (size_t j = 0; j < m_nsp; j++) { for (size_t k = j; k < m_nsp; k++) { vratiokj = m_visc[k]/m_visc[j]; wratiojk = m_mw[j]/m_mw[k]; // Note that m_wratjk(k,j) holds the square root of // m_wratjk(j,k)! factor1 = 1.0 + (m_sqvisc[k]/m_sqvisc[j]) * m_wratjk(k,j); m_phi(k,j) = factor1*factor1 / (SqrtEight * m_wratkj1(j,k)); m_phi(j,k) = m_phi(k,j)/(vratiokj * wratiojk); } } m_viscwt_ok = true; } //==================================================================================================================== /* * This function returns a Transport data object for a given species. * */ LiquidTransportData AqueousTransport::getLiquidTransportData(int kSpecies) { LiquidTransportData td; td.speciesName = m_thermo->speciesName(kSpecies); return td; } //==================================================================================================================== /* * * Solve for the diffusional velocities in the Stefan-Maxwell equations * */ void AqueousTransport::stefan_maxwell_solve() { size_t VIM = 2; m_B.resize(m_nsp, VIM); // grab a local copy of the molecular weights const vector_fp& M = m_thermo->molecularWeights(); // get the mean molecular weight of the mixture //double M_mix = m_thermo->meanMolecularWeight(); // get the concentration of the mixture //double rho = m_thermo->density(); //double c = rho/M_mix; m_thermo->getMoleFractions(DATA_PTR(m_molefracs)); double T = m_thermo->temperature(); /* electrochemical potential gradient */ for (size_t i = 0; i < m_nsp; i++) { for (size_t a = 0; a < VIM; a++) { m_Grad_mu[a*m_nsp + i] = m_chargeSpecies[i] * Faraday * m_Grad_V[a] + (GasConstant*T/m_molefracs[i]) * m_Grad_X[a*m_nsp+i]; } } /* * Just for Note, m_A(i,j) refers to the ith row and jth column. * They are still fortran ordered, so that i varies fastest. */ switch (VIM) { case 1: /* 1-D approximation */ m_B(0,0) = 0.0; for (size_t j = 0; j < m_nsp; j++) { m_A(0,j) = 1.0; } for (size_t i = 1; i < m_nsp; i++) { m_B(i,0) = m_concentrations[i] * m_Grad_mu[i] / (GasConstant * T); for (size_t j = 0; j < m_nsp; j++) { if (j != i) { m_A(i,j) = m_molefracs[i] / (M[j] * m_DiffCoeff_StefMax(i,j)); m_A(i,i) -= m_molefracs[j] / (M[i] * m_DiffCoeff_StefMax(i,j)); } else if (j == i) { m_A(i,i) = 0.0; } } } //! invert and solve the system Ax = b. Answer is in m_B solve(m_A, m_B.ptrColumn(0)); m_flux = m_B; break; case 2: /* 2-D approximation */ m_B(0,0) = 0.0; m_B(0,1) = 0.0; for (size_t j = 0; j < m_nsp; j++) { m_A(0,j) = 1.0; } for (size_t i = 1; i < m_nsp; i++) { m_B(i,0) = m_concentrations[i] * m_Grad_mu[i] / (GasConstant * T); m_B(i,1) = m_concentrations[i] * m_Grad_mu[m_nsp + i] / (GasConstant * T); for (size_t j = 0; j < m_nsp; j++) { if (j != i) { m_A(i,j) = m_molefracs[i] / (M[j] * m_DiffCoeff_StefMax(i,j)); m_A(i,i) -= m_molefracs[j] / (M[i] * m_DiffCoeff_StefMax(i,j)); } else if (j == i) { m_A(i,i) = 0.0; } } } //! invert and solve the system Ax = b. Answer is in m_B //solve(m_A, m_B); m_flux = m_B; break; case 3: /* 3-D approximation */ m_B(0,0) = 0.0; m_B(0,1) = 0.0; m_B(0,2) = 0.0; for (size_t j = 0; j < m_nsp; j++) { m_A(0,j) = 1.0; } for (size_t i = 1; i < m_nsp; i++) { m_B(i,0) = m_concentrations[i] * m_Grad_mu[i] / (GasConstant * T); m_B(i,1) = m_concentrations[i] * m_Grad_mu[m_nsp + i] / (GasConstant * T); m_B(i,2) = m_concentrations[i] * m_Grad_mu[2*m_nsp + i] / (GasConstant * T); for (size_t j = 0; j < m_nsp; j++) { if (j != i) { m_A(i,j) = m_molefracs[i] / (M[j] * m_DiffCoeff_StefMax(i,j)); m_A(i,i) -= m_molefracs[j] / (M[i] * m_DiffCoeff_StefMax(i,j)); } else if (j == i) { m_A(i,i) = 0.0; } } } //! invert and solve the system Ax = b. Answer is in m_B //solve(m_A, m_B); m_flux = m_B; break; default: printf("unimplemented\n"); throw CanteraError("routine", "not done"); break; } } //==================================================================================================================== } //======================================================================================================================