/**
 *  @file MultiTransport.cpp
 *  Implementation file for class MultiTransport
 */
/*
 *  Copyright 2001 California Institute of Technology
 *  See file License.txt for licensing information
 */

#include "cantera/thermo/ThermoPhase.h"

#include "cantera/transport/MultiTransport.h"
#include "cantera/numerics/ctlapack.h"

#include "cantera/numerics/DenseMatrix.h"
#include "cantera/base/utilities.h"
#include "cantera/base/utilities.h"
#include "L_matrix.h"
#include "cantera/transport/TransportParams.h"
#include "cantera/thermo/IdealGasPhase.h"

#include "cantera/transport/TransportFactory.h"
#include "cantera/base/stringUtils.h"

#include <iostream>
using namespace std;

/**
 * Mole fractions below MIN_X will be set to MIN_X when computing
 * transport properties.
 */

#define MIN_X 1.e-20


namespace Cantera
{


/////////////////////////// constants //////////////////////////

//    const doublereal ThreeSixteenths = 3.0/16.0;



///////////////////// helper functions /////////////////////////


/**
 *  @internal
 *
 *  The Parker temperature correction to the rotational collision
 *  number.
 *
 *  @param tr Reduced temperature \f$ \epsilon/kT \f$
 *  @param sqtr square root of tr.
 */
inline doublereal Frot(doublereal tr, doublereal sqtr)
{
    const doublereal c1 = 0.5*SqrtPi*Pi;
    const doublereal c2 = 0.25*Pi*Pi + 2.0;
    const doublereal c3 = SqrtPi*Pi;
    return 1.0 + c1*sqtr + c2*tr + c3*sqtr*tr;
}


/**
 * This method is used by GMRES to multiply the L matrix by a
 * vector b.  The L matrix has a 3x3 block structure, where each
 * block is a K x K matrix.  The elements of the upper-right and
 * lower-left blocks are all zero.  This method is defined so
 * that the multiplication only involves the seven non-zero
 * blocks.
 */
void L_Matrix::mult(const doublereal* b, doublereal* prod) const
{
    integer n = static_cast<int>(nRows())/3;
    integer n2 = 2*n;
    integer n3 = 3*n;
    ct_dgemv(ctlapack::ColMajor, ctlapack::NoTranspose, n, n2, 1.0,
             DATA_PTR(data()), static_cast<int>(nRows()), b, 1, 0.0, prod, 1);
    ct_dgemv(ctlapack::ColMajor, ctlapack::NoTranspose, n, n3, 1.0,
             DATA_PTR(data()) + n, static_cast<int>(nRows()),
             b, 1, 0.0, prod+n, 1);
    ct_dgemv(ctlapack::ColMajor, ctlapack::NoTranspose, n, n, 1.0,
             DATA_PTR(data()) + n*n3 + n2, static_cast<int>(nRows()),
             b + n, 1, 0.0, prod+n2, 1);
    for (int i = 0; i < n; i++) {
        prod[i + n2] += b[i + n2] * value(i + n2, i + n2);
    }
}

//////////////////// class MultiTransport methods //////////////


MultiTransport::MultiTransport(thermo_t* thermo)
    : Transport(thermo),
      m_temp(-1.0)
{
}


MultiTransport::~MultiTransport()
{

}
//====================================================================================================================
bool MultiTransport::initGas(GasTransportParams& tr)
{

    // constant mixture attributes
    //m_phase = tr.mix;
    m_thermo = tr.thermo;
    m_nsp   = m_thermo->nSpecies();
    m_tmin  = m_thermo->minTemp();
    m_tmax  = m_thermo->maxTemp();

    // 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_poly       = tr.poly;
    m_visccoeffs = tr.visccoeffs;
    m_diffcoeffs = tr.diffcoeffs;
    m_astar_poly = tr.astar_poly;
    m_bstar_poly = tr.bstar_poly;
    m_cstar_poly = tr.cstar_poly;
    m_om22_poly  = tr.omega22_poly;
    m_zrot       = tr.zrot;
    m_crot       = tr.crot;
    m_epsilon    = tr.epsilon;
    m_mode       = tr.mode_;
    m_diam       = tr.diam;
    m_eps        = tr.eps;
    m_alpha      = tr.alpha;
    m_dipoleDiag.resize(m_nsp);
    for (size_t i = 0; i < m_nsp; i++) {
        m_dipoleDiag[i] = tr.dipole(i,i);
    }

    // the L matrix
    m_Lmatrix.resize(3*m_nsp, 3*m_nsp);
    m_a.resize(3*m_nsp, 1.0);
    m_b.resize(3*m_nsp, 0.0);
    m_aa.resize(m_nsp, m_nsp, 0.0);

    m_frot_298.resize(m_nsp);
    m_rotrelax.resize(m_nsp);

    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_cinternal.resize(m_nsp);

    m_polytempvec.resize(5);
    m_visc.resize(m_nsp);
    m_sqvisc.resize(m_nsp);
    m_bdiff.resize(m_nsp, m_nsp);

    //m_poly.resize(m_nsp);
    m_om22.resize(m_nsp, m_nsp);
    m_astar.resize(m_nsp, m_nsp);
    m_bstar.resize(m_nsp, m_nsp);
    m_cstar.resize(m_nsp, m_nsp);

    m_molefracs.resize(m_nsp);

    // set flags all false
    m_visc_ok = false;
    m_spvisc_ok = false;
    m_diff_ok = false;
    m_abc_ok = false;
    m_l0000_ok = false;
    m_lmatrix_soln_ok = false;

    m_diff_tlast = 0.0;
    m_spvisc_tlast = 0.0;
    m_visc_tlast = 0.0;
    m_thermal_tlast = 0.0;

    // use LU decomposition by default
    m_gmres = false;

    // default GMRES parameters
    m_mgmres = 100;
    m_eps_gmres = 1.e-4;

    // some work space
    m_spwork.resize(m_nsp);
    m_spwork1.resize(m_nsp);
    m_spwork2.resize(m_nsp);
    m_spwork3.resize(m_nsp);


    // precompute and store log(epsilon_ij/k_B)
    m_log_eps_k.resize(m_nsp, m_nsp);
    //        int j;
    for (size_t i = 0; i < m_nsp; i++) {
        for (size_t j = i; j < m_nsp; j++) {
            m_log_eps_k(i,j) = log(tr.epsilon(i,j)/Boltzmann);
            m_log_eps_k(j,i) = m_log_eps_k(i,j);
        }
    }


    // precompute and store constant parts of the Parker rotational
    // collision number temperature correction
    const doublereal sq298 = sqrt(298.0);
    const doublereal kb298 = Boltzmann * 298.0;
    m_sqrt_eps_k.resize(m_nsp);
    for (size_t k = 0; k < m_nsp; k++) {
        m_sqrt_eps_k[k] = sqrt(tr.eps[k]/Boltzmann);
        m_frot_298[k] = Frot(tr.eps[k]/kb298,
                             m_sqrt_eps_k[k]/sq298);
    }

    //         // install updaters
    //         m_update_transport_T = m_thermo->installUpdater_T(
    //             new UpdateTransport_T<MultiTransport>(*this));
    //         m_update_transport_C = m_thermo->installUpdater_C(
    //             new UpdateTransport_C<MultiTransport>(*this));
    //         m_update_spvisc_T = m_thermo->installUpdater_T(
    //             new UpdateSpeciesVisc<MultiTransport>(*this));
    //         m_update_visc_T = m_thermo->installUpdater_T(
    //             new UpdateVisc_T<MultiTransport>(*this));
    //         m_update_diff_T = m_thermo->installUpdater_T(
    //             new UpdateDiff_T<MultiTransport>(*this));
    //         m_update_thermal_T = m_thermo->installUpdater_T(
    //             new UpdateThermal_T<MultiTransport>(*this));

    return true;
}


/******************  viscosity ******************************/

doublereal MultiTransport::viscosity()
{
    doublereal vismix = 0.0, denom;

    // update m_visc if necessary
    updateViscosity_T();

    // update the mole fractions
    updateTransport_C();

    for (size_t k = 0; k < m_nsp; k++) {
        denom = 0.0;
        for (size_t j = 0; j < m_nsp; j++) {
            denom += m_phi(k,j) * m_molefracs[j];
        }
        vismix += m_molefracs[k] * m_visc[k]/denom;
    }
    return vismix;
}

//====================================================================================================================

/******************* binary diffusion coefficients **************/

void MultiTransport::getBinaryDiffCoeffs(size_t ld, doublereal* d)
{
    // if necessary, evaluate the binary diffusion coefficients
    // from the polynomial fits
    updateDiff_T();

    doublereal p = pressure_ig();
    doublereal rp = 1.0/p;
    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);
        }
}



/****************** thermal conductivity **********************/

/**
 * @internal
 */
doublereal MultiTransport::thermalConductivity()
{
    solveLMatrixEquation();
    doublereal sum = 0.0;
    for (size_t k = 0; k  < 2*m_nsp; k++) {
        sum += m_b[k + m_nsp] * m_a[k + m_nsp];
    }
    return -4.0*sum;
}
//====================================================================================================================
// Return the thermal diffusion coefficients for the species
/*
 *
 *  @param dt thermal diffusion coefficients
 *             (length = m_nsp)
 */
void MultiTransport::getThermalDiffCoeffs(doublereal* const dt)
{
    solveLMatrixEquation();
    const doublereal c = 1.6/GasConstant;
    for (size_t k = 0; k < m_nsp; k++) {
        dt[k] = c * m_mw[k] * m_molefracs[k] * m_a[k];
    }
}
//====================================================================================================================

/**
 * @internal
 */
void MultiTransport::solveLMatrixEquation()
{

    // if T has changed, update the temperature-dependent
    // properties.

    updateThermal_T();
    updateTransport_C();

    // Copy the mole fractions twice into the last two blocks of
    // the right-hand-side vector m_b. The first block of m_b was
    // set to zero when it was created, and is not modified so
    // doesn't need to be reset to zero.
    for (size_t k = 0; k < m_nsp; k++) {
        m_b[k] = 0.0;
        m_b[k + m_nsp] = m_molefracs[k];
        m_b[k + 2*m_nsp] = m_molefracs[k];
    }

    // Set the right-hand side vector to zero in the 3rd block for
    // all species with no internal energy modes.  The
    // corresponding third-block rows and columns will be set to
    // zero, except on the diagonal of L01,01, where they are set
    // to 1.0. This has the effect of eliminating these equations
    // from the system, since the equation becomes: m_a[2*m_nsp +
    // k] = 0.0.

    // Note that this differs from the Chemkin procedure, where
    // all *monatomic* species are excluded. Since monatomic
    // radicals can have non-zero internal heat capacities due to
    // electronic excitation, they should be retained.
    //
    // But if CHEMKIN_COMPATIBILITY_MODE is defined, then all
    // monatomic species are excluded.

    for (size_t k = 0; k < m_nsp; k++) {
        if (!hasInternalModes(k)) {
            m_b[2*m_nsp + k] = 0.0;
        }
    }

    // evaluate the submatrices of the L matrix

    m_Lmatrix.resize(3*m_nsp, 3*m_nsp, 0.0);

    eval_L0000(DATA_PTR(m_molefracs));
    eval_L0010(DATA_PTR(m_molefracs));
    eval_L0001();
    eval_L1000();
    eval_L1010(DATA_PTR(m_molefracs));
    eval_L1001(DATA_PTR(m_molefracs));
    eval_L0100();
    eval_L0110();
    eval_L0101(DATA_PTR(m_molefracs));


    // Solve it using GMRES or LU decomposition. The last solution
    // in m_a should provide a good starting guess, so convergence
    // should be fast.

    //if (m_gmres) {
    //    gmres(m_mgmres, 3*m_nsp, m_Lmatrix, m_b.begin(),
    //        m_a.begin(), m_eps_gmres);
    //    m_lmatrix_soln_ok = true;
    //    m_l0000_ok = true;            // L matrix not modified by GMRES
    //}
    //else {
    copy(m_b.begin(), m_b.end(), m_a.begin());
    try {
        solve(m_Lmatrix, DATA_PTR(m_a));
    } catch (CanteraError& err) {
        err.save();
        //if (info != 0) {
        throw CanteraError("MultiTransport::solveLMatrixEquation",
                           "error in solving L matrix.");
    }
    m_lmatrix_soln_ok = true;
    m_l0000_ok = false;
    // L matrix is overwritten with LU decomposition
    //}
    m_lmatrix_soln_ok = true;
}

//====================================================================================================================
//  Get the species diffusive mass fluxes wrt to  the mass averaged velocity,
//  given the gradients in mole fraction and temperature
/*
 *  Units for the returned fluxes are kg m-2 s-1.
 *
 *  @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 MultiTransport::getSpeciesFluxes(size_t ndim, const doublereal* const grad_T, int ldx,
                                      const doublereal* const grad_X,
                                      int ldf, doublereal* const fluxes)
{

    // update the binary diffusion coefficients if necessary
    updateDiff_T();

    doublereal sum;

    // If any component of grad_T is non-zero, then get the
    // thermal diffusion coefficients

    bool addThermalDiffusion = false;
    for (size_t i = 0; i < ndim; i++) {
        if (grad_T[i] != 0.0) {
            addThermalDiffusion = true;
        }
    }
    if (addThermalDiffusion) {
        getThermalDiffCoeffs(DATA_PTR(m_spwork));
    }

    const doublereal* y = m_thermo->massFractions();
    doublereal rho = m_thermo->density();

    for (size_t i = 0; i < m_nsp; i++) {
        sum = 0.0;
        for (size_t j = 0; j < m_nsp; j++) {
            m_aa(i,j) = m_molefracs[j]*m_molefracs[i]/m_bdiff(i,j);
            sum += m_aa(i,j);
        }
        m_aa(i,i) -= sum;
    }

    // enforce the condition \sum Y_k V_k = 0. This is done by replacing
    // the flux equation with the largest gradx component in the first
    // coordinate direction with the flux balance condition.
    size_t jmax = 0;
    doublereal gradmax = -1.0;
    for (size_t j = 0; j < m_nsp; j++) {
        if (fabs(grad_X[j]) > gradmax) {
            gradmax = fabs(grad_X[j]);
            jmax = j;
        }
    }

    // set the matrix elements in this row to the mass fractions,
    // and set the entry in gradx to zero

    for (size_t j = 0; j < m_nsp; j++) {
        m_aa(jmax,j) = y[j];
    }
    vector_fp gsave(ndim), grx(ldx*m_nsp);
    for (size_t n = 0; n < ldx*ndim; n++) {
        grx[n] = grad_X[n];
    }
    //for (n = 0; n < ndim; n++) {
    //    gsave[n] = grad_X[jmax + n*ldx];   // save the input mole frac gradient
    //grad_X[jmax + n*ldx] = 0.0;
    //    grx[jmax + n*ldx] = 0.0;
    // }

    // copy grad_X to fluxes
    const doublereal* gx;
    for (size_t n = 0; n < ndim; n++) {
        gx = grad_X + ldx*n;
        copy(gx, gx + m_nsp, fluxes + ldf*n);
        fluxes[jmax + n*ldf] = 0.0;
    }

    // use LAPACK to solve the equations
    int info=0;
    ct_dgetrf(static_cast<int>(m_aa.nRows()),
              static_cast<int>(m_aa.nColumns()), m_aa.ptrColumn(0),
              static_cast<int>(m_aa.nRows()),
              &m_aa.ipiv()[0], info);
    if (info == 0) {
        ct_dgetrs(ctlapack::NoTranspose,
                  static_cast<int>(m_aa.nRows()), ndim,
                  m_aa.ptrColumn(0), static_cast<int>(m_aa.nRows()),
                  &m_aa.ipiv()[0], fluxes, ldf, info);
        if (info != 0) {
            info += 100;
        }
    } else
        throw CanteraError("MultiTransport::getSpeciesFluxes",
                           "Error in DGETRF");
    if (info > 50)
        throw CanteraError("MultiTransport::getSpeciesFluxes",
                           "Error in DGETRS");


    size_t offset;
    doublereal pp = pressure_ig();

    // multiply diffusion velocities by rho * V to create
    // mass fluxes, and restore the gradx elements that were
    // modified
    for (size_t n = 0; n < ndim; n++) {
        offset = n*ldf;
        for (size_t i = 0; i < m_nsp; i++) {
            fluxes[i + offset] *= rho * y[i] / pp;
        }
        //grad_X[jmax + n*ldx] = gsave[n];
    }

    // thermal diffusion
    if (addThermalDiffusion) {
        for (size_t n = 0; n < ndim; n++) {
            offset = n*ldf;
            doublereal grad_logt = grad_T[n]/m_temp;
            for (size_t i = 0; i < m_nsp; i++) {
                fluxes[i + offset] -= m_spwork[i]*grad_logt;
            }
        }
    }
}
//====================================================================================================================
// Get the mass diffusional fluxes [kg/m^2/s] of the species, given the thermodynamic
// state at two nearby points.
/*
 * The specific diffusional fluxes are calculated with reference to the mass averaged
 * velocity. This is a one-dimensional vector
 *
 * @param state1 Array of temperature, density, and mass
 *               fractions for state 1.
 * @param state2 Array of temperature, density, and mass
 *               fractions for state 2.
 * @param delta  Distance from state 1 to state 2 (m).
 * @param fluxes Output mass fluxes of the species.
 *               (length = m_nsp)
 */
void MultiTransport::getMassFluxes(const doublereal* state1, const doublereal* state2, doublereal delta,
                                   doublereal* fluxes)
{

    double* x1 = DATA_PTR(m_spwork1);
    double* x2 = DATA_PTR(m_spwork2);
    double* x3 = DATA_PTR(m_spwork3);
    size_t n, nsp = m_thermo->nSpecies();
    m_thermo->restoreState(nsp+2, state1);
    double p1 = m_thermo->pressure();
    double t1 = state1[0];
    m_thermo->getMoleFractions(x1);

    m_thermo->restoreState(nsp+2, state2);
    double p2 = m_thermo->pressure();
    double t2 = state2[0];
    m_thermo->getMoleFractions(x2);

    //
    double p = 0.5*(p1 + p2);
    double t = 0.5*(state1[0] + state2[0]);

    for (n = 0; n < nsp; n++) {
        x3[n] = 0.5*(x1[n] + x2[n]);
    }
    m_thermo->setState_TPX(t, p, x3);
    m_thermo->getMoleFractions(DATA_PTR(m_molefracs));

    // update the binary diffusion coefficients if necessary
    updateDiff_T();

    // If there is a temperature gadient, then get the
    // thermal diffusion coefficients

    bool addThermalDiffusion = false;
    if (state1[0] != state2[0]) {
        addThermalDiffusion = true;
        getThermalDiffCoeffs(DATA_PTR(m_spwork));
    }

    const doublereal* y = m_thermo->massFractions();
    doublereal rho = m_thermo->density();

    for (size_t i = 0; i < m_nsp; i++) {
        doublereal sum = 0.0;
        for (size_t j = 0; j < m_nsp; j++) {
            m_aa(i,j) = m_molefracs[j]*m_molefracs[i]/m_bdiff(i,j);
            sum += m_aa(i,j);
        }
        m_aa(i,i) -= sum;
    }

    // enforce the condition \sum Y_k V_k = 0. This is done by
    // replacing the flux equation with the largest gradx
    // component with the flux balance condition.
    size_t jmax = 0;
    doublereal gradmax = -1.0;
    for (size_t j = 0; j < m_nsp; j++) {
        if (fabs(x2[j] - x1[j]) > gradmax) {
            gradmax = fabs(x1[j] - x2[j]);
            jmax = j;
        }
    }

    // set the matrix elements in this row to the mass fractions,
    // and set the entry in gradx to zero

    for (size_t j = 0; j < m_nsp; j++) {
        m_aa(jmax,j) = y[j];
        fluxes[j] = x2[j] - x1[j];
    }
    fluxes[jmax] = 0.0;

    // use LAPACK to solve the equations
    int info=0;
    size_t nr = m_aa.nRows();
    size_t nc = m_aa.nColumns();

    ct_dgetrf(nr, nc, m_aa.ptrColumn(0), nr, &m_aa.ipiv()[0], info);
    if (info == 0) {
        int ndim = 1;
        ct_dgetrs(ctlapack::NoTranspose, nr, ndim,
                  m_aa.ptrColumn(0), nr, &m_aa.ipiv()[0], fluxes, nr, info);
        if (info != 0)
            throw CanteraError("MultiTransport::getMassFluxes",
                               "Error in DGETRS. Info = "+int2str(info));
    } else
        throw CanteraError("MultiTransport::getMassFluxes",
                           "Error in DGETRF.  Info = "+int2str(info));


    doublereal pp = pressure_ig();

    // multiply diffusion velocities by rho * Y_k to create
    // mass fluxes, and divide by pressure
    for (size_t i = 0; i < m_nsp; i++) {
        fluxes[i] *= rho * y[i] / pp;
    }

    // thermal diffusion
    if (addThermalDiffusion) {
        doublereal grad_logt = (t2 - t1)/m_temp;
        for (size_t i = 0; i < m_nsp; i++) {
            fluxes[i] -= m_spwork[i]*grad_logt;
        }
    }
}
//====================================================================================================================
void MultiTransport::getMolarFluxes(const doublereal* const state1,
                                    const doublereal* const state2,
                                    const doublereal delta,
                                    doublereal* const fluxes)
{
    getMassFluxes(state1, state2, delta, fluxes);
    for (size_t k = 0; k < m_thermo->nSpecies(); k++) {
        fluxes[k] /= m_mw[k];
    }
}
//====================================================================================================================
// Set the solution method for inverting the L matrix
/*
 *      @param method enum TRANSOLVE_TYPE Either use direct or TRANSOLVE_GMRES
 */
void MultiTransport::setSolutionMethod(TRANSOLVE_TYPE method)
{
    if (method == TRANSOLVE_GMRES) {
        m_gmres = true;
    } else {
        m_gmres = false;
    }
}
//====================================================================================================================
void MultiTransport::setOptions_GMRES(int m, doublereal eps)
{
    if (m > 0) {
        m_mgmres = m;
    }
    if (eps > 0.0) {
        m_eps_gmres = eps;
    }
}
//====================================================================================================================
void MultiTransport::getMultiDiffCoeffs(const size_t ld, doublereal* const d)
{
    doublereal p = pressure_ig();

    // update the mole fractions
    updateTransport_C();

    // update the binary diffusion coefficients
    updateDiff_T();

    // evaluate L0000 if the temperature or concentrations have
    // changed since it was last evaluated.
    if (!m_l0000_ok) {
        eval_L0000(DATA_PTR(m_molefracs));
    }

    // invert L00,00
    int ierr = invert(m_Lmatrix, m_nsp);
    if (ierr != 0) {
        throw CanteraError("MultiTransport::getMultiDiffCoeffs",
                           string(" invert returned ierr = ")+int2str(ierr));
    }
    m_l0000_ok = false;           // matrix is overwritten by inverse

    //doublereal pres = m_thermo->pressure();
    doublereal prefactor = 16.0 * m_temp
                           * m_thermo->meanMolecularWeight()/(25.0 * p);
    doublereal c;

    for (size_t i = 0; i < m_nsp; i++) {
        for (size_t j = 0; j < m_nsp; j++) {
            c = prefactor/m_mw[j];
            d[ld*j + i] = c*m_molefracs[i]*
                          (m_Lmatrix(i,j) - m_Lmatrix(i,i));
        }
    }
}
//====================================================================================================================

void MultiTransport::getMixDiffCoeffs(doublereal* const d)
{

    // update the mole fractions
    updateTransport_C();

    // update the binary diffusion coefficients if necessary
    updateDiff_T();

    doublereal mmw = m_thermo->meanMolecularWeight();
    doublereal sumxw = 0.0, sum2;
    doublereal p = pressure_ig();
    if (m_nsp == 1) {
        d[0] = m_bdiff(0,0) / p;
    } else {
        for (size_t k = 0; k < m_nsp; k++) {
            sumxw += m_molefracs[k] * m_mw[k];
        }
        for (size_t k = 0; k < m_nsp; k++) {
            sum2 = 0.0;
            for (size_t 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);
            }
        }
    }
}


void MultiTransport::updateTransport_T()
{
    //m_thermo->update_T(m_update_transport_T);
    _update_transport_T();
}

void MultiTransport::updateTransport_C()
{
    // {m_thermo->update_C(m_update_transport_C);
    _update_transport_C();
}


/**
 *  Update temperature-dependent quantities. This method is called
 *  by the temperature property updater.
 */
void MultiTransport::_update_transport_T()
{
    if (m_temp == m_thermo->temperature()) {
        return;
    }

    m_temp = m_thermo->temperature();
    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 fits will need to be
    // redone, and the L matrix reevaluated.
    m_visc_ok = false;
    m_spvisc_ok = false;
    m_diff_ok = false;
    m_abc_ok  = false;
    m_lmatrix_soln_ok = false;
    m_l0000_ok = false;
}

/**
 *  This is called the first time any transport property
 *  is requested from ThermoSubstance after the concentrations
 *  have changed.
 */
void MultiTransport::_update_transport_C()
{
    // signal that concentration-dependent quantities will need to
    // be recomputed before use, and update the local mole
    // fraction array.
    m_l0000_ok = false;
    m_lmatrix_soln_ok = false;
    m_thermo->getMoleFractions(DATA_PTR(m_molefracs));


    // add an offset to avoid a pure species condition
    // (check - this may be unnecessary)
    for (size_t k = 0; k < m_nsp; k++) {
        m_molefracs[k] = std::max(MIN_X, m_molefracs[k]);
    }
}


/*************************************************************************
 *
 *    methods to update temperature-dependent properties
 *
 *************************************************************************/

/**
 * @internal
 * Update the binary diffusion coefficients. These are evaluated
 * from the polynomial fits at unit pressure (1 Pa).
 */
void MultiTransport::updateDiff_T()
{
    if (m_diff_tlast == m_thermo->temperature()) {
        return;
    }
    _update_diff_T();
    m_diff_tlast = m_thermo->temperature();
    //m_thermo->update_T(m_update_diff_T);
}

void MultiTransport::_update_diff_T()
{
    updateTransport_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_diff_ok = true;
}


/**
 * @internal
 * 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 MultiTransport::updateSpeciesViscosities_T()
{
    if (m_spvisc_tlast == m_thermo->temperature()) {
        return;
    }
    _update_species_visc_T();
    //m_thermo->update_T(m_update_spvisc_T);
    m_spvisc_tlast = m_thermo->temperature();
}


void MultiTransport::_update_species_visc_T()
{
    updateTransport_T();

    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++) {
            //m_visc[k] = m_sqrt_t*dot5(m_polytempvec, m_visccoeffs[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;
}

/**
 * @internal
 */
void MultiTransport::updateViscosity_T()
{
    if (m_visc_tlast == m_thermo->temperature()) {
        return;
    }
    _update_visc_T();
    m_visc_tlast = m_thermo->temperature();
}

void MultiTransport::_update_visc_T()
{
    doublereal vratiokj, wratiojk, factor1;

    updateSpeciesViscosities_T();

    // 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];
            //rootwjk = sqrt(wratiojk);
            //factor1 = 1.0 + sqrt(vratiokj * rootwjk);
            //m_phi(k,j) = factor1*factor1 /
            //             (SqrtEight * sqrt(1.0 + m_mw[k]/m_mw[j]));
            //m_phi(j,k) = m_phi(k,j)/(vratiokj * wratiojk);

            // 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_visc_ok = true;
}


/**
 * @internal
 * Update the temperature-dependent terms needed to compute the
 * thermal conductivity and thermal diffusion coefficients.
 */
void MultiTransport::updateThermal_T()
{
    if (m_thermal_tlast == m_thermo->temperature()) {
        return;
    }
    _update_thermal_T();
    // m_thermo->update_T(m_update_thermal_T);
    m_thermal_tlast = m_thermo->temperature();
}

void MultiTransport::_update_thermal_T()
{
    // we need species viscosities and binary diffusion
    // coefficients
    updateSpeciesViscosities_T();
    updateDiff_T();

    // evaluate polynomial fits for A*, B*, C*
    doublereal z;
    int ipoly;
    for (size_t i = 0; i < m_nsp; i++) {
        for (size_t j = i; j < m_nsp; j++) {
            z = m_logt - m_log_eps_k(i,j);
            ipoly = m_poly[i][j];
            if (m_mode == CK_Mode) {
                m_om22(i,j) = poly6(z, DATA_PTR(m_om22_poly[ipoly]));
                m_astar(i,j) = poly6(z, DATA_PTR(m_astar_poly[ipoly]));
                m_bstar(i,j) = poly6(z, DATA_PTR(m_bstar_poly[ipoly]));
                m_cstar(i,j) = poly6(z, DATA_PTR(m_cstar_poly[ipoly]));
            } else {
                m_om22(i,j) = poly8(z, DATA_PTR(m_om22_poly[ipoly]));
                m_astar(i,j) = poly8(z, DATA_PTR(m_astar_poly[ipoly]));
                m_bstar(i,j) = poly8(z, DATA_PTR(m_bstar_poly[ipoly]));
                m_cstar(i,j) = poly8(z, DATA_PTR(m_cstar_poly[ipoly]));
            }
            m_om22(j,i)  = m_om22(i,j);
            m_astar(j,i) = m_astar(i,j);
            m_bstar(j,i) = m_bstar(i,j);
            m_cstar(j,i) = m_cstar(i,j);
        }
    }
    m_abc_ok = true;

    // evaluate the temperature-dependent rotational relaxation
    // rate

    doublereal tr, sqtr;
    for (size_t k = 0; k < m_nsp; k++) {
        tr = m_eps[k]/ m_kbt;
        sqtr = m_sqrt_eps_k[k] / m_sqrt_t;
        m_rotrelax[k] = std::max(1.0,m_zrot[k]) * m_frot_298[k]/Frot(tr, sqtr);
    }

    doublereal d;
    doublereal c = 1.2*GasConstant*m_temp;
    for (size_t k = 0; k < m_nsp; k++) {
        d = c * m_visc[k] * m_astar(k,k)/m_mw[k];
        m_bdiff(k,k) = d;
    }

    // Calculate the internal heat capacities by subtracting off the translational contributions
    /*
     *  HKM Exploratory comment:
     *       The translational component is 1.5
     *       The rotational component is 1.0 for a linear molecule and 1.5 for a nonlinear molecule
     *           and zero for a monatomic.
     *       Chemkin has traditionally subtracted 1.5 here (SAND86-8246).
     *       The original Dixon-Lewis paper subtracted 1.5 here.
     */
    const vector_fp& cp = ((IdealGasPhase*)m_thermo)->cp_R_ref();
    for (size_t k = 0; k < m_nsp; k++) {
        m_cinternal[k] = cp[k] - 2.5;
    }
}
//====================================================================================================================
/*
 * This function returns a Transport data object for a given species.
 *
 */
struct GasTransportData MultiTransport::
getGasTransportData(int kSpecies) {
    struct GasTransportData td;
    td.speciesName = m_thermo->speciesName(kSpecies);

    td.geometry = 2;
    if (m_crot[kSpecies] == 0.0) {
        td.geometry = 0;
    } else if (m_crot[kSpecies] == 1.0) {
        td.geometry = 1;
    }
    td.wellDepth = m_eps[kSpecies] / Boltzmann;
    td.dipoleMoment = m_dipoleDiag[kSpecies] * 1.0E25 / SqrtTen;
    td.diameter = m_diam(kSpecies, kSpecies) * 1.0E10;
    td.polarizability = m_alpha[kSpecies] * 1.0E30;
    td.rotRelaxNumber = m_zrot[kSpecies];

    return td;
}
//====================================================================================================================
}
