#include <iostream>
#include <fstream>

#include <boost/numeric/ublas/matrix.hpp>
#include <boost/numeric/ublas/vector.hpp>
#include <boost/numeric/ublas/io.hpp>

#include <vector>
#include <cmath>

#include "NRGclasses.hpp"
#include "NRGfunctions.hpp"
#include "TwoChQSz.hpp"

#ifndef pi
#define pi 3.141592653589793238462643383279502884197169
#endif

bool AeqB(CNRGbasisarray *pAeigCut, int ibl1,int ibl2){

  return (ibl1==ibl2);

}

double AtimesB(CNRGbasisarray *pAeig, CNRGbasisarray *pSingleSite,
	       int ibl1,int ibl2){

  return((double)ibl1*ibl2);

}

int main (){

  CNRGarray Aeig(2);

  CNRGbasisarray AeigCut(2);

  CNRGbasisarray Abasis(2);

  CNRGbasisarray SingleSite(2);

  // STL vector

  CNRGmatrix Qm1fNQ[2];

  CNRGmatrix MQQp1;

  double U,ed,Gamma1,Gamma2;
  vector<double> Params;
  double Lambda;
  double HalfLambdaFactor;
  double Dband=1.0;
  int calcdens;

  double DN=0.0;
  double TM=0.0;
  double betabar=0.727;
  double Temp=0.0;
  double Sus=0.0;
  //vector<double> SuscepChain;
  char arqSus[32],arqname[32];

  double chi_m1,chi_N[2];
  double daux[4];

  int Nsites,Nsitesmax=2;

  int Ncutoff=700;
  int UpdateBefCut=0;
  int auxIn;

  // outstream
  ofstream OutFile;
  // instream
  ifstream InFile;

  int ii,jj,i1,i2;

  // STL iterator:

  vector<double>::iterator diter;

  // Test
  CNRGmatrix HN;

  HN.CheckForMatEl=AeqB;
  HN.CalcMatEl=AtimesB;
  if (HN.CheckForMatEl(&Abasis,2,2)) cout << "TRUE!" << endl;
  else cout << "FALSE!" << endl;
  cout << HN.CalcMatEl(&Abasis,&SingleSite,3,4) << endl;
  exit(0);

  ///            ///
  /// Begin code ///
  ///            ///
  U=0.5;
  ed=-0.5*U;
  Gamma1=0.0282691;
  Gamma2=0.0;
  Lambda=2.5;

  // Steps: Input parameters
  InFile.open("nrg_input_TwoChAnderson.dat");
  if (InFile.is_open())
    {
      InFile >> Nsitesmax;
      InFile >> Ncutoff;
      InFile >> U;
      InFile >> Gamma1;
      InFile >> Gamma2;
      InFile >> ed;
      InFile >> Lambda;
      InFile >> Dband;
      InFile >> auxIn;
      InFile >> UpdateBefCut;
      InFile >> calcdens;
    }
  else cout << "can't open nrg_input_TwoChAnderson.dat" << endl;

  InFile.close();

  ///////////////////////////
  ///////////////////////////

  if (calcdens==2)
    {
      //SuscepChain.clear();
      strcpy(arqSus,"SuscepImp_25_726.dat");

      strcpy(arqname,"SuscepChain.dat");
      InFile.open(arqname);
      if (InFile.fail())
	{
	  cout << "Can't find " << arqname << endl;
	  strcpy(arqSus,"SuscepChain.dat");
	  calcdens=3;
	}
///////////////////////
//       else
// 	{
// 	  while (!InFile.eof())
// 	    {
// 	      InFile >> Temp >> daux[0] >> daux[0] >> Sus;
// 	      SuscepChain.push_back(Sus);
// 	    }
// 	  SuscepChain.pop_back();
// 	  if (SuscepChain.size()<Nsitesmax-1)
// 	    {
// 	      cout << "Nsuscep = " << SuscepChain.size() << " Nsitesmax = " << Nsitesmax << endl;
// 	      cout << " Longer SuscepChain needed! Doing it all over..." << endl;
// 	      SuscepChain.clear();
// 	      strcpy(arqSus,"SuscepChain.dat");
// 	      calcdens=3;
// 	    }
// 	}
//       InFile.close();
//////////////////////////
     }
  if (calcdens==3)
    {
      strcpy(arqSus,"SuscepChain.dat");
      U=0.0;ed=0.0;Gamma1=0.0;Gamma2=0.0;
    }

  ///////////////////////////
  ///////////////////////////

  HalfLambdaFactor=0.5*(1.0+(1.0/Lambda));
  chi_m1=sqrt(2.0*Gamma1/pi)/(sqrt(Lambda)*HalfLambdaFactor);

  double U_tilde=0.5*U/HalfLambdaFactor;
  double ed_tilde=ed/HalfLambdaFactor;
  double Gamma1_tilde=Gamma1*(2.0/pi)/(HalfLambdaFactor*HalfLambdaFactor);
  double Gamma2_tilde=Gamma2*(2.0/pi)/(HalfLambdaFactor*HalfLambdaFactor);

  OutFile.open("NRG_in.txt");
  OutFile << "Begin NRG 2-ch calculation" << endl;
  OutFile << " Nsitesmax    = " << Nsitesmax-1 << endl;
  OutFile << " Ncutoff      = " << Ncutoff << endl;
  OutFile << " U            = " << U << endl;
  OutFile << " Gamma1        = " << Gamma1 << endl;
  OutFile << " Gamma2        = " << Gamma2 << endl;
  OutFile << " ed           = " << ed << endl;
  OutFile << " Lambda       = " << Lambda << endl;
  OutFile << " Dband        = " << Dband << endl;
  OutFile << " UpdateBefCut = " << UpdateBefCut << endl;
  OutFile << " calcdens     = " << calcdens << endl;
  OutFile << "=================================" << endl;
  OutFile << "U~ = " << U_tilde << endl;
  OutFile << "ed~ = " <<  ed_tilde << endl;
  OutFile << "Gamma1~ = " <<  Gamma1_tilde<< endl;
  OutFile << "Gamma2~ = " <<  Gamma2_tilde<< endl;
  OutFile << "=================================" << endl;
  OutFile.close();

  // Define H0 (impurity + 1s site)
  // Output:
  //       Aeig,
  //       fd_{1sigma} and fd_{2sigma} matrix elements
  //

  // Set single site (use pointers in the subroutines!!)

  TwoChQSz_SetSingleSite(&SingleSite);

  Params.push_back(U_tilde/sqrt(Lambda));
  Params.push_back(ed_tilde/sqrt(Lambda));
  Params.push_back(sqrt(Gamma1_tilde/Lambda));
  Params.push_back(sqrt(Gamma2_tilde/Lambda));

  Nsites=0;
  DN=HalfLambdaFactor*pow(Lambda,(-(Nsites-1)/2.0) );
  TM=DN/betabar;
  cout << "DN = " << DN << "TM = " << TM << endl;

  //TwoChQSz_SetH0Anderson(Params,&SingleSite,&Aeig,Qm1fNQ);
  TwoChQSz_SetH0Anderson(Params,&SingleSite,&Aeig,&Abasis);

  Aeig.PrintEn();

  // Calculate Susceptibility

  if ( (calcdens==2)||(calcdens==3) )
    {
      TM=DN/betabar;
      Params.clear();
      Params.push_back(betabar);
      Sus=CalcSuscep(Params,&Aeig,1,false);
      double Sus0=0.0;
      int nlines=0;
      if (calcdens==2)
	{
	  InFile.open("SuscepChain.dat");
	  InFile.clear();
	  InFile.seekg(0, ios::beg); // rewind
	  if (InFile.fail())
	    {
	      cout << "Can't open  SuscepChain.dat"<< endl;
	    }
	  else
	    {
	      while ( (!InFile.eof())&&(nlines<=Nsites) )
		{
		  InFile >> daux[0] >> daux[0] >> daux[0] >> Sus0;
		  nlines++;
		}
	    }
	  InFile.close();
	}
      //if (calcdens==2) Sus0=SuscepChain[Nsites];
      else Sus-=1.0/8.0; // exclude dot site in the chain.
      if (Nsites==0) OutFile.open(arqSus);
      else OutFile.open(arqSus,ofstream::app);
      OutFile.precision(20);
      OutFile << scientific << TM << " " << Sus << " " << Sus0 << " " << Sus-Sus0 << endl;
      OutFile.close();

    }

  TwoChQSz_UpdateQm1fQ(&SingleSite,&Aeig,&Abasis,Qm1fNQ);

  // Check Matrix
  for (int ich=1;ich<=2;ich++)
    {
      cout << " Printing Qm1fQ channel: " << ich << endl;
      for (int ibl=0; ibl<Qm1fNQ[ich-1].NumMatBlocks();ibl++)
	{
	  Qm1fNQ[ich-1].PrintMatBlock(ibl);
	}
    }

  // Loop on Nsites: start from Nsites=0 (imp+1)
  //

  Nsites=1;
  while (Nsites<=Nsitesmax)
    {

      DN=HalfLambdaFactor*pow(Lambda,(-(Nsites-1)/2.0) );
      TM=DN/betabar;
      cout << "DN = " << DN << "TM = " << TM << endl;

      // 0 - Update chi_N, eps_N

      daux[0]=(double)( 1.0-pow(Lambda,(-Nsites)) );
      daux[1]=(double)sqrt( 1.0-pow(Lambda,-(2*Nsites-1)) );
      daux[2]=(double)sqrt( 1.0-pow(Lambda,-(2*Nsites+1)) );
      daux[3]=0.5*(1.0+(1.0/Lambda))*(double)sqrt(Lambda);

      chi_N[0]=daux[0]/(daux[1]*daux[2]);
      chi_N[1]=daux[0]/(daux[1]*daux[2]);

      cout << "chi_N = " << chi_N[0] << "  " << chi_N[1] << endl;

      cout << "Nsites = " << Nsites << endl;
      cout << "BEG Eig Nshell = " << Aeig.Nshell << endl;

      // 1 - Eliminate states and Build Abasis

      cout << "Cutting states..." << endl;
      AeigCut.CNRGbasisarray::ClearAll();
      AeigCut=CutStates(&Aeig, Ncutoff);

      cout << "... done cutting states." << endl;

      // Calculate new matrix elements using the CUT basis:
      // update Qm1fNQ, Qm1cdQ, etc.

      if ( (UpdateBefCut==0)&&(Nsites>1) )
	{
	  cout << "Updating matrices after cutting... " << endl;
	  TwoChQSz_UpdateMatrixAfterCutting(&SingleSite,
					    &AeigCut, &Abasis, Qm1fNQ, &MQQp1);
	  cout << "... done updating matrices. " << endl;
	}

      //Q1Q2Sz_BuildBasis(&AeigCut,&Abasis,&SingleSite,UpdateBefCut);
      // doesnt work
      QSz_BuildBasis(&AeigCut,&Abasis,&SingleSite,UpdateBefCut);

      cout << "No blocks = " << Abasis.NumBlocks() << endl;

      cout << "No states = " << Abasis.Nstates() << endl;

      //Abasis.PrintAll();

      // 2 - Build and diagonalize H_N+1

      Params.clear();
      Params.push_back(chi_N[0]);
      Params.push_back(chi_N[1]);
      Params.push_back(Lambda);

      cout << "Diagonalizing HN... " << endl;

      TwoChQSz_DiagHN(Params,&Abasis,&SingleSite,Qm1fNQ,&Aeig);

      Aeig.PrintEn();

      cout << "..done diagonalizing HN. " << endl;

      // 3 - Update Qm1f1NQ, Qm1f2Q, Qm1cdQ, etc.

      if ( (UpdateBefCut==1)&&(Nsites<Nsitesmax) )
	{
	  cout << "Updating matrices before cutting... " << endl;
	  TwoChQSz_UpdateQm1fQ(&SingleSite,&Aeig,&Abasis,Qm1fNQ);
	  cout << "... done updating matrices. " << endl;

	}

      // Calculate Susceptibility

      if ( (calcdens==2)||(calcdens==3) )
	{
	  TM=DN/betabar;
	  Params.clear();
	  Params.push_back(betabar);
	  Sus=CalcSuscep(Params,&Aeig,1,false);
	  double Sus0=0.0;
	  int nlines=0;
	  // Gets SuscepChain iteration by iteration
	  if (calcdens==2)
	    {
	      InFile.open("SuscepChain.dat");
	      InFile.clear();
	      InFile.seekg(0, ios::beg); // rewind
	      if (InFile.fail())
		{
		  cout << "Can't find " << arqname << endl;
		}
	      else
		{
		  while ( (!InFile.eof())&&(nlines<=Nsites) )
		    {
		      InFile >> daux[0] >> daux[0] >> daux[0] >> Sus0;
		      nlines++;
		    }
		}
	      InFile.close();
	    }
	  //if (calcdens==2) Sus0=SuscepChain[Nsites];
	  else Sus-=1.0/8.0; // exclude dot site in the chain.

	  if (Nsites==0) OutFile.open(arqSus);
	  else OutFile.open(arqSus,ofstream::app);
	  OutFile.precision(20);
	  OutFile << scientific << TM << " " << Sus << " " << Sus0 << " " << Sus-Sus0 << endl;
	  OutFile.close();
	}

      // 4 - Update Nsites

      Nsites++;

    }

  cout << "=== Calculation Finished! ==== "<< endl;
  OutFile.open("NRG_end.txt");
  OutFile << "END NRG calculation" << endl;
  OutFile.close();

  cout << "Calling destructors " << endl;

}
//END code


// Filename: CirculationModel.cpp
// Created on 20 Aug 2007 by Boyce Griffith

// Modified 2019, Alexander D. Kaiser

#include "CirculationModel.h"

/////////////////////////////// INCLUDES /////////////////////////////////////

#ifndef included_IBAMR_config
#include <IBAMR_config.h>
#define included_IBAMR_config
#endif

#ifndef included_SAMRAI_config
#include <SAMRAI_config.h>
#define included_SAMRAI_config
#endif

// SAMRAI INCLUDES
#include <CartesianGridGeometry.h>
#include <CartesianPatchGeometry.h>
#include <PatchLevel.h>
#include <SideData.h>
#include <tbox/RestartManager.h>
#include <tbox/SAMRAI_MPI.h>
#include <tbox/Utilities.h>

// C++ STDLIB INCLUDES
#include <cassert>
#include <cmath>

#include <Eigen/Dense>
using namespace Eigen;

/////////////////////////////// NAMESPACE ////////////////////////////////////

/////////////////////////////// STATIC ///////////////////////////////////////

#define MIN_PER_L_T0_SEC_PER_ML 60.0e-3   // 60/1000
#define MMHG_TO_CGS 1333.22368

#define USE_WINDKESSEL
// If not defined then this only computes fluxes
// Pressure is set to zero
// Currently hard coded for upper boundary

namespace
{
    // Name of output file.
    static const string DATA_FILE_NAME = "bc_data.m";

    // constants
    static const double C_PA         =  4.12; // Pulmonary artery compliance, ml / mmHg
    static const double C_LA_relaxed =  3*1.6;  // Left atrial compliance ml / mmHg
    static const double C_PV         = 10.0 - C_LA_relaxed;  // Pulmonary vein compliance, ml / mmHg

    static const double R_P  = (9.0/5.6) * MIN_PER_L_T0_SEC_PER_ML; // Pulmonary resistance, mmHg / (ml/s)

    static const double beat_time = 0.8;
    static const double T_on = .53;   // Pulmonary valve open
    static const double T_off = .75;  // Pulmonary valve closes
    static const double T_peak = T_on + 0.4 * (T_off - T_on); // Peak pulmonary valve flow
    static const double stroke_volume = 75.0; // ml
    static const double h = 2.0 * stroke_volume / (T_off - T_on); // Peak flow to get given stroke volume

    static const bool   atrial_kick_on = true;
    static const double atrial_kick_center = .44;
    static const double atrial_kick_time_radius = .09;
    static const double atrial_kick_time_width = 2.0 * atrial_kick_time_radius;

    inline double compute_Q_R(double t){
        // Triangle wave flux

        double t_reduced = t - beat_time * floor(t/beat_time);

        if (t_reduced <= T_on)
            return 0.0;
        else if (t_reduced <= T_peak)
            return ( h/(T_peak - T_on) )*t_reduced - (h/(T_peak - T_on)  )*T_on;
        else if (t_reduced <= T_off)
            return (-h/(T_off - T_peak))*t_reduced + (h/(T_off -  T_peak))*T_off;
        else if (t_reduced <= beat_time)
            return 0.0;

        TBOX_ERROR("Valid time for flux not found.");
        return 0.0;
    }

    inline double compute_C_LA(double t){

        if(atrial_kick_on){
            double t_reduced = t - beat_time * floor(t/beat_time);

            if (abs(t_reduced - atrial_kick_center) < atrial_kick_time_radius)
                return C_LA_relaxed * (1.0 - pow(cos(M_PI * (t_reduced - atrial_kick_center) / atrial_kick_time_width),2));
            else
                return C_LA_relaxed;
        }

        return C_LA_relaxed;
    }

    // Backward Euler update for windkessel model.
    inline void
    windkessel_be_update(double& Q_R, double& P_PA, double& Q_P, double& P_LA, const double Q_mi, const double t, const double dt)
    {

        Q_R = compute_Q_R(t);

        double C_LA_current = compute_C_LA(t);
        double C_LA_next    = compute_C_LA(t + dt);

        double a = C_PA/dt + 1/R_P;
        double b = -1/R_P;
        double c = -1/R_P;
        double d = (C_PV + C_LA_next)/dt + 1/R_P;

        double rhs[2];
        rhs[0] = (C_PA/dt)*P_PA + Q_R;
        rhs[1] = ((C_PV + C_LA_current)/dt)*P_LA - Q_mi;

        double det = a*d - b*c;

        // Closed form linear system solution
        P_PA = (1/det) * ( d*rhs[0] + -b*rhs[1]);
        P_LA = (1/det) * (-c*rhs[0] +  a*rhs[1]);

        Q_P = (1/R_P) * (P_PA - P_LA);

        return;
    } // windkessel_be_update

}

/////////////////////////////// PUBLIC ///////////////////////////////////////

CirculationModel::CirculationModel(const string& object_name, double P_PA_0, double P_LA_0, double t, bool register_for_restart)
    : d_object_name(object_name),
      d_registered_for_restart(register_for_restart),
      d_time(t),
      d_nsrc(1),           // number of sets of variables
      d_psrc(d_nsrc, 0.0), // pressure
      d_qsrc(d_nsrc, 0.0), // flux
      d_srcname(d_nsrc),
      d_P_PA(P_PA_0),
      d_P_LA(P_LA_0),
      d_Q_R(0.0),
      d_Q_P(0.0),
      d_Q_mi(0.0),
      d_bdry_interface_level_number(numeric_limits<int>::max())
{
#if !defined(NDEBUG)
    assert(!object_name.empty());
#endif
    if (d_registered_for_restart)
    {
        RestartManager::getManager()->registerRestartItem(d_object_name, this);
    }

    // Initialize object with data read from the input and restart databases.
    const bool from_restart = RestartManager::getManager()->isFromRestart();
    if (from_restart)
    {
        getFromRestart();
    }
    else
    {
        //   nsrcs = the number of sources in the valve tester:
        //           (1) left atrium
        d_srcname[0] = "left atrium       ";
    }
    return;
} // CirculationModel

CirculationModel::~CirculationModel()
{
    return;
} // ~CirculationModel

void
CirculationModel::advanceTimeDependentData(const double dt,
                                           const double Q_mi)
{
    /*
    // Compute the mean flow rates in the vicinity of the inflow and outflow
    // boundaries.
    std::fill(d_qsrc.begin(), d_qsrc.end(), 0.0);
    for (int ln = 0; ln <= hierarchy->getFinestLevelNumber(); ++ln)
    {
        Pointer<PatchLevel<NDIM> > level = hierarchy->getPatchLevel(ln);
        for (PatchLevel<NDIM>::Iterator p(level); p; p++)
        {
            Pointer<Patch<NDIM> > patch = level->getPatch(p());
            Pointer<CartesianPatchGeometry<NDIM> > pgeom = patch->getPatchGeometry();
            if (pgeom->getTouchesRegularBoundary())
            {
                Pointer<SideData<NDIM, double> > U_data = patch->getPatchData(U_idx);
                Pointer<SideData<NDIM, double> > wgt_sc_data = patch->getPatchData(wgt_sc_idx);
                const Box<NDIM>& patch_box = patch->getBox();
                // const double* const x_lower = pgeom->getXLower();
                const double* const dx = pgeom->getDx();
                double dV = 1.0;
                for (int d = 0; d < NDIM; ++d)
                {
                    dV *= dx[d];
                }

                static const int axis = 2;  // Always z axis here
                const int side = 1;         // Compute flux at the top only

                const bool is_lower = side == 0;
                if (pgeom->getTouchesRegularBoundary(axis, side))
                {

                    Vector n;
                    for (int d = 0; d < NDIM; ++d)
                    {
                        n[d] = axis == d ? (is_lower ? -1.0 : +1.0) : 0.0;
                    }
                    Box<NDIM> side_box = patch_box;
                    if (is_lower)
                    {
                        side_box.lower(axis) = patch_box.lower(axis);
                        side_box.upper(axis) = patch_box.lower(axis);
                    }
                    else
                    {
                        side_box.lower(axis) = patch_box.upper(axis) + 1;
                        side_box.upper(axis) = patch_box.upper(axis) + 1;
                    }
                    for (Box<NDIM>::Iterator b(side_box); b; b++)
                    {
                        const Index<NDIM>& i = b();

                        // no conditional here, just add the flux in
                        const SideIndex<NDIM> i_s(i, axis, SideIndex<NDIM>::Lower);
                        if ((*wgt_sc_data)(i_s) > std::numeric_limits<double>::epsilon())
                        {
                            double dA = n[axis] * dV / dx[axis];
                            d_qsrc[0] += (*U_data)(i_s)*dA;

                            // pout << "adding " << (*U_data)(i_s)*dA << "to the flux\n";

                        }
                    }
                }
            }
        }
    }
    SAMRAI_MPI::sumReduction(&d_qsrc[0], d_nsrc);

    // pout << "computed flux = " << d_qsrc[0] << "\n";
    */

    // The downstream (Atrial) pressure is determined by a zero-d model
    const double t = d_time;

    double& Q_R  = d_Q_R;
    double& P_PA = d_P_PA;
    double& Q_P  = d_Q_P;
    double& P_LA = d_P_LA;

    // Mitral flux passed in
    d_Q_mi = Q_mi;

    windkessel_be_update(Q_R, P_PA, Q_P, P_LA, d_Q_mi, t, dt);

    // model in mmHg, body force converts
    d_psrc[0] = d_P_LA;

    // Update the current time.
    d_time += dt;

    // Output the updated values.
    const long precision = plog.precision();
    plog.unsetf(ios_base::showpos);
    plog.unsetf(ios_base::scientific);

    plog.precision(12);

    plog << "============================================================================\n"
         << "Circulation model variables at time " << d_time << ":\n";

    plog << "P_PA (mmHg)\t P_LA (mmHg)\t Q_R (ml/s)\t Q_P (ml/s)\t Q_mi (ml/s)\n";
    plog.setf(ios_base::showpos);
    plog.setf(ios_base::scientific);

    plog << d_P_PA << ",\t " << d_P_LA << ",\t " << d_Q_R << ",\t " << d_Q_P << ",\t " << d_Q_mi << "\n";
    plog << "============================================================================\n";

    plog.unsetf(ios_base::showpos);
    plog.unsetf(ios_base::scientific);
    plog.precision(precision);

    // Write the current state to disk.
    writeDataFile();
    return;
} // advanceTimeDependentData

void
CirculationModel::putToDatabase(Pointer<Database> db)
{
    db->putDouble("d_time", d_time);
    db->putInteger("d_nsrc", d_nsrc);
    db->putDoubleArray("d_qsrc", &d_qsrc[0], d_nsrc);
    db->putDoubleArray("d_psrc", &d_psrc[0], d_nsrc);
    db->putStringArray("d_srcname", &d_srcname[0], d_nsrc);
    db->putDouble("d_P_PA", d_P_PA);
    db->putDouble("d_P_LA", d_P_LA);
    db->putDouble("d_Q_R", d_Q_R);
    db->putDouble("d_Q_P", d_Q_P);
    db->putDouble("d_Q_mi", d_Q_mi);
    db->putInteger("d_bdry_interface_level_number", d_bdry_interface_level_number);
    return;
} // putToDatabase

void CirculationModel::write_plot_code()
{
    static const int mpi_root = 0;
    if (SAMRAI_MPI::getRank() == mpi_root)
    {
        ofstream fout(DATA_FILE_NAME.c_str(), ios::app);
        fout.setf(ios_base::scientific);
        fout.setf(ios_base::showpos);
        fout.precision(10);
        fout << "];\n";
        fout << "fig = figure;\n";
        fout << "subplot(3,2,1)\n";
        fout << "plot(bc_vals(:,1), bc_vals(:,2))\n";
        fout << "title('P_{PA}')\n";
        fout << "subplot(3,2,2)\n";
        fout << "plot(bc_vals(:,1), bc_vals(:,3))\n";
        fout << "title('P_{LA}')\n";
        fout << "subplot(3,2,3)\n";
        fout << "plot(bc_vals(:,1), bc_vals(:,4))\n";
        fout << "title('Q_{R}')\n";
        fout << "subplot(3,2,4)\n";
        fout << "plot(bc_vals(:,1), bc_vals(:,5))\n";
        fout << "title('Q_{P}')\n";
        fout << "subplot(3,2,5)\n";
        fout << "plot(bc_vals(:,1), bc_vals(:,6))\n";
        fout << "title('Q_{mi}')\n";
        fout << "dt = bc_vals(2,1) - bc_vals(1,1);\n";
        fout << "net_flux = dt*cumsum(bc_vals(:,6));\n";
        fout << "subplot(3,2,6)\n";
        fout << "plot(bc_vals(:,1), net_flux)\n";
        fout << "title('net Q')\n";
        fout << "printfig(fig, 'bc_model_variables')\n";
    }
    return;
}

/////////////////////////////// PROTECTED ////////////////////////////////////

/////////////////////////////// PRIVATE //////////////////////////////////////

void
CirculationModel::writeDataFile() const
{
    static const int mpi_root = 0;
    if (SAMRAI_MPI::getRank() == mpi_root)
    {
        static bool file_initialized = false;
        const bool from_restart = RestartManager::getManager()->isFromRestart();
        if (!from_restart && !file_initialized)
        {
            ofstream fout(DATA_FILE_NAME.c_str(), ios::out);
            fout << "% time \t P_PA (mmHg)\t d_P_LA (mmHg)\t Q_R (ml/s)\t Q_P (ml/s)\t Q_mi (ml/s)"
                 << "\n"
                 << "bc_vals = [";
            file_initialized = true;
        }

        ofstream fout(DATA_FILE_NAME.c_str(), ios::app);
        for (int n = 0; n < d_nsrc; ++n)
        {
            fout << d_time;
            fout.setf(ios_base::scientific);
            fout.setf(ios_base::showpos);
            fout.precision(10);
            fout << " " << d_P_PA << " " << d_P_LA << " " << d_Q_R << " " << d_Q_P << " " << d_Q_mi << "; \n";
        }
    }
    return;
} // writeDataFile

void
CirculationModel::getFromRestart()
{
    Pointer<Database> restart_db = RestartManager::getManager()->getRootDatabase();
    Pointer<Database> db;
    if (restart_db->isDatabase(d_object_name))
    {
        db = restart_db->getDatabase(d_object_name);
    }
    else
    {
        TBOX_ERROR("Restart database corresponding to " << d_object_name << " not found in restart file.");
    }

    d_time = db->getDouble("d_time");
    d_nsrc = db->getInteger("d_nsrc");
    d_qsrc.resize(d_nsrc);
    d_psrc.resize(d_nsrc);
    d_srcname.resize(d_nsrc);
    db->getDoubleArray("d_qsrc", &d_qsrc[0], d_nsrc);
    db->getDoubleArray("d_psrc", &d_psrc[0], d_nsrc);
    db->getStringArray("d_srcname", &d_srcname[0], d_nsrc);
    d_P_PA = db->getDouble("d_P_PA");
    d_P_LA = db->getDouble("d_P_LA");
    d_Q_R  = db->getDouble("d_Q_R");
    d_Q_P  = db->getDouble("d_Q_P");
    d_Q_mi = db->getDouble("d_Q_mi");
    d_bdry_interface_level_number = db->getInteger("d_bdry_interface_level_number");
    return;
} // getFromRestart

/////////////////////////////// NAMESPACE ////////////////////////////////////

/////////////////////////////// TEMPLATE INSTANTIATION ///////////////////////

//////////////////////////////////////////////////////////////////////////////