// ATC Headers
#include "ExtrinsicModelDriftDiffusion.h"
#include "ATC_Error.h"
#include "FieldEulerIntegrator.h"
#include "ATC_Coupling.h"
#include "LammpsInterface.h"
#include "PrescribedDataManager.h"
#include "PhysicsModel.h"
#include "LinearSolver.h"
#include "PoissonSolver.h"
#include "SchrodingerSolver.h"

// timer
#include "Utility.h"

#include <utility>

using ATC_Utility::to_string;
using std::cout;
using std::string;
using std::set;
using std::pair;
using std::vector;

namespace ATC {

  //--------------------------------------------------------
  //--------------------------------------------------------
  //  Class ExtrinsicModelDriftDiffusion
  //--------------------------------------------------------
  //--------------------------------------------------------

  //--------------------------------------------------------
  //  Constructor
  //--------------------------------------------------------
  ExtrinsicModelDriftDiffusion::ExtrinsicModelDriftDiffusion
                                (ExtrinsicModelManager * modelManager,
                                 ExtrinsicModelType modelType,
                                 string matFileName) :
    ExtrinsicModelTwoTemperature(modelManager,modelType,matFileName),
    continuityIntegrator_(NULL),
    
    poissonSolverType_(DIRECT), // ITERATIVE | DIRECT
    poissonSolver_(NULL),
    baseSize_(0),
    electronDensityEqn_(ELECTRON_CONTINUITY),
    fluxUpdateFreq_(1),
    schrodingerSolverType_(DIRECT), // ITERATIVE | DIRECT
    schrodingerSolver_(NULL),
    schrodingerPoissonMgr_(),
    schrodingerPoissonSolver_(NULL),
    maxConsistencyIter_(0), maxConstraintIter_(1), 
    safe_dEf_(0.1), Ef_shift_(0.0),
    oneD_(false), oneDcoor_(0), oneDconserve_(0)
  {
     // delete base class's version of the physics model
     if (physicsModel_) delete physicsModel_; 
     if (modelType == DRIFT_DIFFUSION_EQUILIBRIUM) {
       physicsModel_ = new PhysicsModelDriftDiffusionEquilibrium(matFileName); 
       electronDensityEqn_ = ELECTRON_EQUILIBRIUM;
     }
     else if (modelType == DRIFT_DIFFUSION_SCHRODINGER) {
       physicsModel_ = new PhysicsModelDriftDiffusionSchrodinger(matFileName); 
       electronDensityEqn_ = ELECTRON_SCHRODINGER;
       maxConsistencyIter_ = 1;
     }
     else if (modelType == DRIFT_DIFFUSION_SCHRODINGER_SLICE) {
       physicsModel_ = new PhysicsModelDriftDiffusionSchrodingerSlice(matFileName); 
       electronDensityEqn_ = ELECTRON_SCHRODINGER;
       maxConsistencyIter_ = 1;
     }
     else {
       physicsModel_ = new PhysicsModelDriftDiffusion(matFileName); 
     }
     atc_->useConsistentMassMatrix_(ELECTRON_DENSITY) = true;
     rhsMaskIntrinsic_(ELECTRON_TEMPERATURE,SOURCE) = true;
     //atc_->fieldMask_(ELECTRON_TEMPERATURE,EXTRINSIC_SOURCE) = true;
  }
  
  //--------------------------------------------------------
  //  Destructor
  //--------------------------------------------------------
  ExtrinsicModelDriftDiffusion::~ExtrinsicModelDriftDiffusion()
  {
    if(continuityIntegrator_) delete continuityIntegrator_;
    if(poissonSolver_) delete poissonSolver_;
    if(schrodingerSolver_) delete schrodingerSolver_;
    if(schrodingerPoissonSolver_) delete schrodingerPoissonSolver_;
  }

  //--------------------------------------------------------
  // modify 
  //--------------------------------------------------------
  bool ExtrinsicModelDriftDiffusion::modify(int narg, char **arg)
  {
    bool match = false;
    if (!match) {
      match = ExtrinsicModelTwoTemperature::modify(narg, arg);
    }
    return match;
  }

  //--------------------------------------------------------
  //  initialize
  //--------------------------------------------------------
  void ExtrinsicModelDriftDiffusion::initialize()
  {
    // xTTM sets rhsMaskIntrinsic_
    ExtrinsicModelTwoTemperature::initialize();
    nNodes_ = atc_->num_nodes();
    rhs_[ELECTRON_DENSITY].reset(nNodes_,1);
    rhs_[ELECTRIC_POTENTIAL].reset(nNodes_,1);
      
    // set up electron continuity integrator
    Array2D <bool> rhsMask(NUM_TOTAL_FIELDS,NUM_FLUX); 
    rhsMask = false;
    for (int i = 0; i < NUM_FLUX; i++) {
      rhsMask(ELECTRON_DENSITY,i) = atc_->fieldMask_(ELECTRON_DENSITY,i);
    }
    // need to create the bcs for the solver to configure properly
    atc_->set_fixed_nodes();

    if (continuityIntegrator_) delete continuityIntegrator_;
    if (electronTimeIntegration_ == TimeIntegrator::IMPLICIT) { 
      continuityIntegrator_ = new FieldImplicitEulerIntegrator(ELECTRON_DENSITY,
        physicsModel_, atc_->feEngine_, atc_, rhsMask);
    }
    else {
      continuityIntegrator_ = new FieldExplicitEulerIntegrator(ELECTRON_DENSITY,
        physicsModel_, atc_->feEngine_, atc_, rhsMask);
    }


    atc_->compute_mass_matrix(ELECTRON_DENSITY,physicsModel_);
    //(atc_->consistentMassMats_[ELECTRON_DENSITY].quantity()).print("PHYS MASS MAT");
    //DENS_MAT temp = atc_->consistentMassInverse_ - atc_->consistentMassMatInv_[ELECTRON_DENSITY];
    //temp.print("DIFF In MATS");

    // set up poisson solver
    rhsMask = false;
    for (int i = 0; i < NUM_FLUX; i++) {
      rhsMask(ELECTRIC_POTENTIAL,i) = atc_->fieldMask_(ELECTRIC_POTENTIAL,i);
    }
    int type = ATC::LinearSolver::ITERATIVE_SOLVE_SYMMETRIC;
    if (poissonSolverType_ == DIRECT) {  
      type = ATC::LinearSolver::DIRECT_SOLVE;
    }
    if (poissonSolver_) delete poissonSolver_;
    poissonSolver_ = new PoissonSolver(ELECTRIC_POTENTIAL,
      physicsModel_, atc_->feEngine_, atc_->prescribedDataMgr_, atc_,
      rhsMask,type, true);
    poissonSolver_->initialize();

    // set up schrodinger solver
    if ( electronDensityEqn_ == ELECTRON_SCHRODINGER ) {
      if ( schrodingerSolver_ )  delete schrodingerSolver_;
      if ( oneD_ ) { 
        EfHistory_.reset(oneDslices_.size(),2);
        schrodingerSolver_ = new SliceSchrodingerSolver(ELECTRON_DENSITY,
          physicsModel_, atc_->feEngine_, atc_->prescribedDataMgr_, atc_,
          oneDslices_,oneDdxs_);
      } 
      else {
        schrodingerSolver_ = new SchrodingerSolver(ELECTRON_DENSITY,
          physicsModel_, atc_->feEngine_, atc_->prescribedDataMgr_, atc_);
      }
      schrodingerSolver_->initialize();

      if ( schrodingerPoissonSolver_ )  delete schrodingerPoissonSolver_;
      schrodingerPoissonSolver_ = schrodingerPoissonMgr_.initialize(
      atc_, schrodingerSolver_, poissonSolver_, physicsModel_);

    }

    if (electronDensityEqn_ == ELECTRON_SCHRODINGER && !(atc_->is_initialized())) {
      ((atc_->fields())[ELECTRON_WAVEFUNCTION].set_quantity()).reset(nNodes_,1);
      ((atc_->fields())[ELECTRON_WAVEFUNCTIONS].set_quantity()).reset(nNodes_,nNodes_);
      ((atc_->fields())[ELECTRON_WAVEFUNCTION_ENERGIES].set_quantity()).reset(nNodes_,1);
    }

#if 0
    cout << " RHS MASK\n";
    print_mask(rhsMask);
#endif
  }

  //--------------------------------------------------------
  //  pre initial integration
  //--------------------------------------------------------
  void ExtrinsicModelDriftDiffusion::pre_init_integrate()
  {
    double dt = atc_->lammpsInterface_->dt();
    double time = atc_->time();
    int step = atc_->step();
    if (step % fluxUpdateFreq_ != 0) return; 

    // set Dirchlet data
    atc_->set_fixed_nodes();

    // set Neumann data (atc does not set these until post_final)
    atc_->set_sources();

    // subcyle integration of fast electron variable/s
    
    double idt = dt/nsubcycle_;
    for (int i = 0; i < nsubcycle_ ; ++i) {
      if (electronDensityEqn_ == ELECTRON_CONTINUITY) {
        // update continuity eqn
        if (! atc_->prescribedDataMgr_->all_fixed(ELECTRON_DENSITY) ) 
          continuityIntegrator_->update(idt,time,atc_->fields_,rhs_);
        atc_->set_fixed_nodes(); 
        // solve poisson eqn for electric potential
        if (! atc_->prescribedDataMgr_->all_fixed(ELECTRIC_POTENTIAL) )
          poissonSolver_->solve(atc_->fields(),rhs_);
      } 
      else if (electronDensityEqn_ == ELECTRON_SCHRODINGER) {
        if ( (! atc_->prescribedDataMgr_->all_fixed(ELECTRON_DENSITY) ) 
          || (! atc_->prescribedDataMgr_->all_fixed(ELECTRIC_POTENTIAL) )  )
          schrodingerPoissonSolver_->solve(rhs_,fluxes_);
      }
      // update electron temperature
      if (! atc_->prescribedDataMgr_->all_fixed(ELECTRON_TEMPERATURE) 
        && temperatureIntegrator_ )  {
#ifdef ATC_VERBOSE
        ATC::LammpsInterface::instance()->stream_msg_once("start temperature integration...",true,false);
#endif
        temperatureIntegrator_->update(idt,time,atc_->fields_,rhs_);
#ifdef ATC_VERBOSE
        ATC::LammpsInterface::instance()->stream_msg_once(" done",false,true);
#endif
      }
      atc_->set_fixed_nodes(); 
    }

    
  }
  //--------------------------------------------------------
  //  set coupling source terms
  //-------------------------------------------------------
  void ExtrinsicModelDriftDiffusion::set_sources(FIELDS & fields, FIELDS & sources)
  {
    atc_->evaluate_rhs_integral(rhsMaskIntrinsic_, fields,
                            sources,
                            atc_->source_integration(), physicsModel_);

  }

  //--------------------------------------------------------
  //  output
  //--------------------------------------------------------
  void ExtrinsicModelDriftDiffusion::output(OUTPUT_LIST & outputData)
  { 
#ifdef ATC_VERBOSE
//  ATC::LammpsInterface::instance()->print_msg_once("start output",true,false);
#endif
    ExtrinsicModelTwoTemperature::output(outputData);
    // fields

    outputData["dot_electron_density"] = & (atc_->dot_field(ELECTRON_DENSITY)).set_quantity();
    DENS_MAT & JE = rhs_[ELECTRON_TEMPERATURE].set_quantity();
    double totalJouleHeating =JE.sum();
    outputData["joule_heating"]        = & JE;
    atc_->feEngine_->add_global("total_joule_heating",totalJouleHeating);
    Array2D <bool> rhsMask(NUM_FIELDS,NUM_FLUX); rhsMask = false;
    rhsMask(ELECTRON_DENSITY,FLUX) = true;
    rhsMask(ELECTRIC_POTENTIAL,FLUX) = true;
    atc_->compute_flux(rhsMask,atc_->fields_,fluxes_,physicsModel_);
//(fluxes_[ELECTRON_DENSITY][0]).print("J_x");
    outputData["electron_flux_x"]      = & fluxes_[ELECTRON_DENSITY][0];
    outputData["electron_flux_y"]      = & fluxes_[ELECTRON_DENSITY][1];
    outputData["electron_flux_z"]      = & fluxes_[ELECTRON_DENSITY][2];
    outputData["electric_field_x"]     = & fluxes_[ELECTRIC_POTENTIAL][0];
    outputData["electric_field_y"]     = & fluxes_[ELECTRIC_POTENTIAL][1];
    outputData["electric_field_z"]     = & fluxes_[ELECTRIC_POTENTIAL][2];
    if (electronDensityEqn_ == ELECTRON_SCHRODINGER ) {
      SPAR_MAT K;
      Array2D <bool> rhsMask(NUM_FIELDS,NUM_FLUX);
      rhsMask = false;
      rhsMask(ELECTRON_WAVEFUNCTION,FLUX) = true;
      pair<FieldName,FieldName> row_col(ELECTRON_WAVEFUNCTION,
                                        ELECTRON_WAVEFUNCTION);
      atc_->feEngine_->compute_tangent_matrix(
        rhsMask, row_col, atc_->fields(), physicsModel_,
        atc_->element_to_material_map(), K);
      phiTotal_.reset(K.nRows(),1);
      const DIAG_MAT & inv_dV = (atc_->invNodeVolumes_).quantity();
      for (int i = 0; i < K.nRows() ; i++) {
        phiTotal_(i,0) = 0.0;
        for (int j = 0; j < K.nCols() ; j++) {
          phiTotal_(i,0) += K(i,j);
        }
        phiTotal_(i,0) *= inv_dV(i,i);
      }
      outputData["V_total"]      = & phiTotal_;
    }
    // globals
    double nSum = ((atc_->field(ELECTRON_DENSITY)).quantity()).col_sum();
    atc_->feEngine_->add_global("total_electron_density",nSum);
#ifdef ATC_VERBOSE
//  ATC::LammpsInterface::instance()->print_msg_once("... done",false,true);
#endif
  }

  //--------------------------------------------------------
  //  size_vector
  //--------------------------------------------------------
  int ExtrinsicModelDriftDiffusion::size_vector(int intrinsicSize)
  {
    int xSize = ExtrinsicModelTwoTemperature::size_vector(intrinsicSize);
    baseSize_ = intrinsicSize  + xSize;
    xSize += 2;
    return xSize;
  }

  //--------------------------------------------------------
  //  compute_vector
  //--------------------------------------------------------
  bool ExtrinsicModelDriftDiffusion::compute_vector(int n, double & value)
  {
    // output[1] = total electron density
    // output[2] = total joule heating

    bool match = ExtrinsicModelTwoTemperature::compute_vector(n,value);
    if (match) return match;

    if (n == baseSize_) {
      double nSum = ((atc_->field(ELECTRON_DENSITY)).quantity()).col_sum();
      value = nSum;
      return true;
    }
    if (n == baseSize_+1) {
      DENS_MAT & JE = rhs_[ELECTRON_TEMPERATURE].set_quantity();
      double totalJouleHeating =JE.sum();
      value = totalJouleHeating;
      return true;
    }
    return false;
  }

  //--------------------------------------------------------
  //--------------------------------------------------------
  //  Class ExtrinsicModelDriftDiffusionConvection
  //--------------------------------------------------------
  //--------------------------------------------------------

  //--------------------------------------------------------
  //  Constructor
  //--------------------------------------------------------
  ExtrinsicModelDriftDiffusionConvection::ExtrinsicModelDriftDiffusionConvection
                                (ExtrinsicModelManager * modelManager,
                                 ExtrinsicModelType modelType,
                                 string matFileName) :
    ExtrinsicModelDriftDiffusion(modelManager,modelType,matFileName),
    cddmPoissonSolver_(NULL),
    baseSize_(0)
  {
     // delete base class's version of the physics model
     if (physicsModel_) delete physicsModel_; 
     if (modelType == CONVECTIVE_DRIFT_DIFFUSION_SCHRODINGER) {
       physicsModel_ = new PhysicsModelDriftDiffusionConvectionSchrodinger(matFileName); 
       electronDensityEqn_ = ELECTRON_SCHRODINGER;
     }
     else {
       physicsModel_ = new PhysicsModelDriftDiffusionConvection(matFileName); 
     }
     atc_->useConsistentMassMatrix_(ELECTRON_VELOCITY) = false;
     atc_->useConsistentMassMatrix_(ELECTRON_TEMPERATURE) = false;
  }

  //--------------------------------------------------------
  //  Destructor
  //--------------------------------------------------------
  ExtrinsicModelDriftDiffusionConvection::~ExtrinsicModelDriftDiffusionConvection()
  {
    if (cddmPoissonSolver_) delete cddmPoissonSolver_;
    for (vector<LinearSolver * >::const_iterator iter=velocitySolvers_.begin();
                                   iter != velocitySolvers_.end(); iter++)
      if (*iter) delete *iter;
  }

  //--------------------------------------------------------
  //  initialize
  //--------------------------------------------------------
  void ExtrinsicModelDriftDiffusionConvection::initialize()
  {
    ExtrinsicModelDriftDiffusion::initialize();
    nNodes_ = atc_->num_nodes();
    nsd_ = atc_->nsd();
    rhs_[ELECTRON_VELOCITY].reset(nNodes_,nsd_);

    
    atc_->set_fixed_nodes(); // needed to correctly set BC data
    // initialize Poisson solver
    if (cddmPoissonSolver_) delete cddmPoissonSolver_;
    Array2D <bool> rhsMask(NUM_FIELDS,NUM_FLUX);
    rhsMask = false;
    rhsMask(ELECTRIC_POTENTIAL,FLUX) = true;
    pair<FieldName,FieldName> row_col(ELECTRIC_POTENTIAL,ELECTRIC_POTENTIAL);
    SPAR_MAT stiffness;
    (atc_->feEngine_)->compute_tangent_matrix(rhsMask,row_col, atc_->fields(), physicsModel_, 
                                                atc_->element_to_material_map(), stiffness);
    
    const BC_SET & bcs = (atc_->prescribedDataMgr_->bcs(ELECTRIC_POTENTIAL))[0];

    cddmPoissonSolver_ = new LinearSolver(stiffness, bcs, poissonSolverType_,
                                          -1, true);

    // initialize velocity solver
    const BCS & velocityBcs = atc_->prescribedDataMgr_->bcs(ELECTRON_VELOCITY);
    DENS_MAT velocityRhs(nNodes_,nsd_);
    atc_->compute_mass_matrix(ELECTRON_VELOCITY,physicsModel_);
    SPAR_MAT & velocityMassMat = (atc_->consistentMassMats_[ELECTRON_VELOCITY]).set_quantity();

    for (int i = 0; i < nsd_; i++ ) {
      LinearSolver * myVelocitySolver =
        new LinearSolver(velocityMassMat, velocityBcs[i],
                         LinearSolver::AUTO_SOLVE, -1, true);
      velocitySolvers_.push_back(myVelocitySolver);
    }
  }

  //--------------------------------------------------------
  //  pre initial integration
  //--------------------------------------------------------
  void ExtrinsicModelDriftDiffusionConvection::pre_init_integrate()
  {
    double dt = atc_->lammpsInterface_->dt();
    double time = atc_->time();
    int step = atc_->step();
    if (step % fluxUpdateFreq_ != 0) return; 

    // set Dirchlet data
    atc_->set_fixed_nodes();

    // set Neumann data (atc does not set these until post_final)
    atc_->set_sources();

    // subcyle integration of fast electron variable/s
    
    double idt = dt/nsubcycle_;
    for (int i = 0; i < nsubcycle_ ; ++i) {
      // update electron temperature mass matrix
      atc_->compute_mass_matrix(ELECTRON_VELOCITY,physicsModel_);
      // update electron velocity
      if (!(atc_->prescribedDataMgr_)->all_fixed(ELECTRON_VELOCITY))  {
        //const BCS & bcs 
        //  = atc_->prescribedDataMgr_->bcs(ELECTRON_VELOCITY);
        Array2D <bool> rhsMask(NUM_FIELDS,NUM_FLUX); 
        rhsMask = false;
        rhsMask(ELECTRON_VELOCITY,SOURCE) = atc_->fieldMask_(ELECTRON_VELOCITY,SOURCE);
        rhsMask(ELECTRON_VELOCITY,FLUX) = atc_->fieldMask_(ELECTRON_VELOCITY,FLUX);
        rhsMask(ELECTRON_VELOCITY,OPEN_SOURCE) = atc_->fieldMask_(ELECTRON_VELOCITY,OPEN_SOURCE);
        FIELDS rhs;
        rhs[ELECTRON_VELOCITY].reset(nNodes_,nsd_);
        atc_->compute_rhs_vector(rhsMask, atc_->fields_, rhs, atc_->source_integration(), physicsModel_);
        const DENS_MAT & velocityRhs =  rhs[ELECTRON_VELOCITY].quantity();

        // add a solver for electron momentum  
        DENS_MAT & velocity = (atc_->field(ELECTRON_VELOCITY)).set_quantity();
        for (int j = 0; j < nsd_; ++j) {
          if (! atc_->prescribedDataMgr_->all_fixed(ELECTRON_VELOCITY,j) ) {
            if (atc_->useConsistentMassMatrix_(ELECTRON_VELOCITY)) {
              //#ifdef ATC_PRINT_DEBUGGING
              const SPAR_MAT & velocityMassMat = (atc_->consistentMassMats_[ELECTRON_VELOCITY]).quantity();
              velocityMassMat.print("VMASS");
              //#endif
              CLON_VEC v = column(velocity,j);
              const CLON_VEC r = column(velocityRhs,j);
              (velocitySolvers_[j])->solve(v,r);
            }
            else {
              //velocityRhs.print("VRHS");
              //const DIAG_MAT & velocityMassMat = (atc_->massMats_[ELECTRON_VELOCITY]).quantity();
              //velocityMassMat.print("MASS");
              atc_->apply_inverse_mass_matrix(velocityRhs,velocity,ELECTRON_VELOCITY);
            }
          }
        }
      }
      
      //atc_->set_fixed_nodes();
      
      if (electronDensityEqn_ == ELECTRON_CONTINUITY) {
        // update continuity eqn
        Array2D <bool> rhsMask(NUM_FIELDS,NUM_FLUX); 
        rhsMask = false;
        rhsMask(ELECTRON_DENSITY,FLUX) = atc_->fieldMask_(ELECTRON_DENSITY,FLUX);
        rhsMask(ELECTRON_DENSITY,SOURCE) = atc_->fieldMask_(ELECTRON_DENSITY,SOURCE);
        rhsMask(ELECTRON_DENSITY,PRESCRIBED_SOURCE) = atc_->fieldMask_(ELECTRON_DENSITY,PRESCRIBED_SOURCE);
        FIELDS rhs;
        rhs[ELECTRON_DENSITY].reset(nNodes_,1);
        atc_->compute_rhs_vector(rhsMask, atc_->fields_, rhs, atc_->source_integration(), physicsModel_);
        if (! atc_->prescribedDataMgr_->all_fixed(ELECTRON_DENSITY) ) 
          continuityIntegrator_->update(idt,time,atc_->fields_,rhs);
        atc_->set_fixed_nodes(); 
        // solve poisson eqn for electric potential
        
        if (! atc_->prescribedDataMgr_->all_fixed(ELECTRIC_POTENTIAL) ) {
        //poissonSolver_->solve(atc_->fields_,rhs_);
          rhsMask = false;
          rhsMask(ELECTRIC_POTENTIAL,SOURCE) = atc_->fieldMask_(ELECTRIC_POTENTIAL,SOURCE);
          rhsMask(ELECTRIC_POTENTIAL,PRESCRIBED_SOURCE) = atc_->fieldMask_(ELECTRIC_POTENTIAL,PRESCRIBED_SOURCE);
          // FIELDS rhs;
          rhs[ELECTRIC_POTENTIAL].reset(nNodes_,1);
          atc_->compute_rhs_vector(rhsMask, atc_->fields_, rhs, atc_->source_integration(), physicsModel_);
          CLON_VEC x =column((atc_->field(ELECTRIC_POTENTIAL)).set_quantity(),0);
          const CLON_VEC r =column(rhs[ELECTRIC_POTENTIAL].quantity(),0);
          cddmPoissonSolver_->solve(x,r);
        }
      } 
      else if (electronDensityEqn_ == ELECTRON_SCHRODINGER) {
        schrodingerPoissonSolver_->solve(rhs_,fluxes_);
      }
      
      atc_->set_fixed_nodes(); 
      // update electron temperature mass matrix
      atc_->compute_mass_matrix(ELECTRON_TEMPERATURE,physicsModel_);
      // update electron temperature
      if (! atc_->prescribedDataMgr_->all_fixed(ELECTRON_TEMPERATURE) ) {
        //if (atc_->useConsistentMassMatrix_(ELECTRON_TEMPERATURE)) {
          temperatureIntegrator_->update(idt,time,atc_->fields_,rhs_);
          //}
          //else { // lumped mass matrix
          
        //}
      }
      atc_->set_fixed_nodes(); 
      
    }

  }

  //--------------------------------------------------------
  //  output
  //--------------------------------------------------------
  void ExtrinsicModelDriftDiffusionConvection::output(OUTPUT_LIST & outputData)
  {
    ExtrinsicModelDriftDiffusion::output(outputData);
    
    
    //FIELD jouleHeating(atc_->num_nodes(),1);
    //set_kinetic_energy_source(atc_->fields(),jouleHeating);
    outputData["joule_heating"] = & (atc_->extrinsic_source(TEMPERATURE)).set_quantity();

    // globals
    DENS_MAT nodalKineticEnergy;
    compute_nodal_kinetic_energy(nodalKineticEnergy);
    double kineticEnergy = nodalKineticEnergy.sum();
    atc_->feEngine_->add_global("total_electron_kinetic_energy",kineticEnergy);
  }

  //--------------------------------------------------------
  //  size_vector
  //--------------------------------------------------------
  int ExtrinsicModelDriftDiffusionConvection::size_vector(int intrinsicSize)
  {
    int xSize = ExtrinsicModelDriftDiffusion::size_vector(intrinsicSize);
    baseSize_ = intrinsicSize  + xSize;
    xSize += 1;
    return xSize;
  }

  //--------------------------------------------------------
  //  compute_vector
  //--------------------------------------------------------
  bool ExtrinsicModelDriftDiffusionConvection::compute_vector(int n, double & value)
  {
    // output[1] = total electron kinetic energy

    bool match = ExtrinsicModelDriftDiffusion::compute_vector(n,value);
    if (match) return match;

    if (n == baseSize_) {
      DENS_MAT nodalKineticEnergy;
      compute_nodal_kinetic_energy(nodalKineticEnergy);
      value = nodalKineticEnergy.sum();
      return true;
    }
    return false;
  }

  //--------------------------------------------------------
  //  compute_kinetic_energy
  //--------------------------------------------------------
  void ExtrinsicModelDriftDiffusionConvection::compute_nodal_kinetic_energy(DENS_MAT & kineticEnergy)
  {
    DENS_MAT & velocity((atc_->field(ELECTRON_VELOCITY)).set_quantity());
    SPAR_MAT & velocityMassMat = (atc_->consistentMassMats_[ELECTRON_VELOCITY]).set_quantity();
    kineticEnergy.reset(nNodes_,1);
    
    for (int j = 0; j < nsd_; j++) {
      CLON_VEC myVelocity(velocity,CLONE_COL,j);
      DENS_MAT velocityMat(nNodes_,1);
      for (int i = 0; i < nNodes_; i++)
        velocityMat(i,0) = myVelocity(i);
      kineticEnergy += velocityMat.mult_by_element(myVelocity);
    }

    kineticEnergy = 0.5*velocityMassMat*kineticEnergy;
  }
};
