#include <alloc.h>
#include <string.h>
#include <math.h>
//#include <classlib/objstrm.h>
#pragma hdrstop

#include "cryosim.h"

/* CryoSimulation v1.01
 * 
 * Written by:  Chris Studholme
 * Last Update: 28-Oct-1996
 * Copyright:   GPL (http://www.fsf.org/copyleft/gpl.html)
 * Web Site:    http://www.bus.ualberta.ca/cstudhol/msc/
 */


// Constants

#define R	   82.056	//atm*ml/mol/K
#define waterId		1
#define smallNumber 1e-300

// Options

#define useKK
//#define CHECKS

// Macros

#define ERROR(method,error) \
  fprintf(stderr,"ERROR in %s, %s\n",method,error)
#define WARNING(method,warning) \
  fprintf(stderr,"WARNING in %s, %s\n",method,warning)

// Global Variables

REAL t=0;			// current time (sec)
REAL dt=0;			// current delta t (sec)
int whodt=0;			// who adjusts dt

SoluteProperties dummySoluteProperties;
Solution dummySolution;

// Global Methods

template <class X> inline X MIN(X x, X y)
  { return x<y?x:y; }
template <class X> inline X MAX(X x, X y)
  { return x<y?y:x; }
template <class X> inline X ABS(X x)
  { return x<0?-x:x; }
template <class X> inline X CUBE(X x)
  { return x*x*x; }
template <class X> inline X SIGN(X x)
  { return x?(x>0?1:-1):0; }


//**************** SoluteProperties Methods ****************

SoluteProperties::SoluteProperties() {
  // THIS IS DANGEROUS!
  memset(&name, 0, sizeof(SoluteProperties)-4);
}

void* SoluteProperties::read(ipstream &is, uint32) {
  int ver=is.readWord16();
  if (ver!=0x100)
    ERROR("SoluteProperties::read", "invalid object version");
  
  is.readString(name, maxNameLen);
  id = is.readWord32();
  MW = is.readDouble();
  dHf = is.readDouble();
  FP = is.readDouble();
  Tn = is.readDouble();
  D = is.readDouble();
  a = is.readDouble();
  b = is.readDouble();
  
  aMV = is.readDouble();  aCp = is.readDouble();  aTC = is.readDouble();
  sMV = is.readDouble();  sCp = is.readDouble();  sTC = is.readDouble();
  lMV = is.readDouble();  lCp = is.readDouble();  lTC = is.readDouble();
  gMV = is.readDouble();  gCp = is.readDouble();  gTC = is.readDouble();
  vMV = is.readDouble();  vCp = is.readDouble();  vTC = is.readDouble();
  
  return this;
}

void SoluteProperties::write(opstream &os) const {
  os.writeWord16(0x100);	//version information
  
  os.writeString(name);
  os.writeWord32(id);
  os.writeDouble(MW);
  os.writeDouble(dHf);
  os.writeDouble(FP);
  os.writeDouble(Tn);
  os.writeDouble(D);
  os.writeDouble(a);
  os.writeDouble(b);
  
  os.writeDouble(aMV);	os.writeDouble(aCp);  os.writeDouble(aTC);
  os.writeDouble(sMV);	os.writeDouble(sCp);  os.writeDouble(sTC);
  os.writeDouble(lMV);	os.writeDouble(lCp);  os.writeDouble(lTC);
  os.writeDouble(gMV);  os.writeDouble(gCp);  os.writeDouble(gTC);
  os.writeDouble(vMV);	os.writeDouble(vCp);  os.writeDouble(vTC);
}


//**************** SoluteAmount Methods ****************

SoluteAmount::SoluteAmount() {
  amount=rate=0;
}


//**************** Solute Methods ****************

Solute::Solute(SoluteProperties& AProperties, Solution &ASolution)
  : properties(AProperties), solution(ASolution) {
}

Solute::Solute(const Solute& ASolute, Solution &ASolution)
  : properties(ASolute.properties), solution(ASolution) {
}

Solute::Solute(/*StreamableInit*/)
  : properties(dummySoluteProperties), solution(dummySolution) {
}

void Solute::SetVolume (REAL aVolume, REAL sVolume, REAL lVolume) {
  aqueous.amount = aVolume/properties.aMV;
  solid.amount   = sVolume/properties.sMV;
  liquid.amount  = lVolume/properties.lMV;
}

void Solute::SetMass (REAL aMass, REAL sMass, REAL lMass) {
  aqueous.amount = aMass/properties.MW;
  solid.amount   = sMass/properties.MW;
  liquid.amount  = lMass/properties.MW;
}

void Solute::GetVolume(REAL &aVolume, REAL &sVolume, REAL &lVolume) const {
  aVolume = aqueous.amount * properties.aMV;
  sVolume = solid.amount   * properties.sMV;
  lVolume = liquid.amount  * properties.lMV;
}

void Solute::GetMass(REAL &aMass, REAL &sMass, REAL &lMass) const {
  aMass = aqueous.amount * properties.MW;
  sMass = solid.amount   * properties.MW;
  lMass = liquid.amount  * properties.MW;
}

REAL Solute::GetThermalConductivity() const {
  return aqueous.amount*properties.aMV*properties.aTC
    + solid.amount*properties.sMV*properties.sTC
    + liquid.amount*properties.lMV*properties.lTC;
}

REAL Solute::GetFreezingTemperature() const {
  return solution.GetFreezingTemperature(properties.id);
}

REAL Solute::GetAqueousConcentration() const {
  return aqueous.amount / solution.GetAqueousVolume();
}

//REAL TSolute::ImposeTemperature(REAL outTemp)
//  {
//  // always ensure that CalculateSolutionHeat follows CalculateFusionHeat
//  return (CalculateFusionHeat (outTemp) + CalculateSolutionHeat (outTemp)
//	 + CalculateGlassHeat (outTemp));
//  }


/*
REAL TSolute::CalculateGlassHeat(REAL) {
  return 0;
  }

REAL TSolute::CalculateSolutionHeat(REAL) {
  if ((properties.id != 4) && (liquid.sol != 0))
	AddSolute (liquid.sol, 0, -liquid.sol, 0);
  return 0;
  }

REAL TSolute::CalculateFusionHeat (REAL outTemp) {
  REAL fp = CalculateFreezingTemperature();
  // freezing
  if ((outTemp < properties.Tn) || ((outTemp < fp) && (solution->IsNucleated(this)))) {
	REAL amountFreeze = (fp - outTemp) * solution->GetHeatCapacity() / properties.dHf;
	REAL liq = liquid.sol;
	if (amountFreeze > liq) amountFreeze = liq;
	if (amountFreeze > 0) AddSolute (0, amountFreeze, -amountFreeze, 0);

	if (amountFreeze > smallNumber) {
//	  REAL newdt = (maxFT/amountFreeze) * dt;
//	  if (newdt < nextdt) nextdt = newdt;
	  }

	return (amountFreeze * properties.dHf);
	}
  if (outTemp > fp) {	// thawing
	REAL amountThaw = (outTemp - fp) * solution->GetHeatCapacity() / properties.dHf;
	if (amountThaw > solid.sol) amountThaw = solid.sol;
	if (amountThaw > 0) AddSolute (0, -amountThaw, amountThaw, 0);

	if (amountThaw > smallNumber) {
//	  REAL newdt = (maxFT/amountThaw) * dt;
//	  if (newdt < nextdt) nextdt = newdt;
	  }

	return -(amountThaw * properties.dHf);
	}
  return 0;
  }
*/

void Solute::AddVariablePointerToList(uint32 index, uint32 variable) {
  void *p=0;
  switch(variable) {
  case 0x00100000:
    p = &aqueous.amount;		// amount (mol)
    break;
  case 0x00200000:
    p = &aqueous.rate;			// rate (mol/sec)
    break;
  case 0x00100001:
    p = &solid.amount;			// amount (mol)
    break;
  case 0x00200001:
    p = &solid.rate;			// rate (mol/sec)
    break;
  case 0x00100002:
    p = &liquid.amount;			// amount (mol)
    break;
  case 0x00200002:
    p = &liquid.rate;			// rate (mol/sec)
    break;
  }
  
  if (p)
    ::AddVariablePointerToList(index,p);
}

void* Solute::read(ipstream &is, uint32) {
  int ver=is.readWord16();
  if (ver!=0x100)
    ERROR("Solute::read", "invalid object version");
  
  SoluteProperties* AProperties = 
    dynamic_cast<SoluteProperties*>(is.readObject());
  if (AProperties)
    ChangeProperties(*AProperties);
  else
    ERROR("Solute::read", "solute properties invalid ");
  
  solid.amount = is.readDouble();
  aqueous.amount = is.readDouble();
  liquid.amount = is.readDouble();
  
  return this;
}

void Solute::write(opstream &os) const {
  os.writeWord16(0x100);	//version information
  
  os.writeObject(properties);
  
  os.writeDouble(solid.amount);
  os.writeDouble(aqueous.amount);
  os.writeDouble(liquid.amount);
}

/*
PTStreamable TSolute::build() {
  return new TSolute (streamableInit);
  }

TStreamableClass RegSolute ("TSolute", TSolute::build, __DELTA(TSolute));
*/


//***************************************************
//**************** Solution Methods ****************

Solution::Solution(const char* Aname, REAL temp) {
  next=previous=0;
  
  strcpy(name, Aname);
  T=temp;	
  dTdt=0;
  
  solute=0;	
  nSolutes=0;
  waterIndex=-1;
  
  memset(&solutionContents, 0, sizeof(solutionContents));
}

Solution::Solution(const Solution& ASolution) {
  next=previous=0;
  
  name[0]=0;
  T=ASolution.T;	
  dTdt=0;
  
  nSolutes = ASolution.nSolutes;
  solute = new Solute[nSolutes];
  for(int i=0; i<nSolutes; i++) {
    solute[i].ChangeSolution(*this);
    solute[i].ChangeProperties(*(SoluteProperties*)&ASolution.
			       solute[i].properties);
  }
  waterIndex=-1;
  memset(&solutionContents, 0, sizeof(solutionContents));
}

Solution::Solution(/*StreamableInit*/) {
  next=previous=0;
  
  name[0]=0;
  T=dTdt=0;
  
  solute=0;	
  nSolutes=0;
  waterIndex=-1;

  memset(&solutionContents, 0, sizeof(solutionContents));
}

Solution::~Solution () {
  delete[] solute;
}

void Solution::InitializeSimulation() {
  for(int i=0; i<nSolutes; i++)
    if (solute[i].properties.id==waterId)
      waterIndex=i;

  UpdateSolutionContents();
}

// makes room for a new solute in the solute array, then copies this solute
// to that location.  does not change the solutes solution to this solution
void Solution::InsertSolute(const Solute& aSolute) {
  // check to make sure the solute does not already exist
  int insertAt=0;
  for(int i=0; i<nSolutes; i++)
    if (solute[i].properties.id==aSolute.properties.id)
      return;
    else if (solute[i].properties.id<aSolute.properties.id)
      insertAt++;
  nSolutes++;
  
  Solute *oldSolute = solute;
  solute = new Solute[nSolutes];
  if (insertAt)
    memcpy(solute, oldSolute, sizeof(Solute)*insertAt);
  memcpy(&(solute[insertAt]), &aSolute, sizeof(Solute));
  memcpy(&(solute[insertAt+1]), &(oldSolute[insertAt]), 
	 sizeof(Solute)*(nSolutes-insertAt-1));
  
  delete[] oldSolute;
}

int Solution::FindSolute(int soluteId) const {
  for(int i=0; i<nSolutes; i++)
    if (solute[i].properties.id==soluteId)
      return i;
  return -1;
}

void Solution::EmptySolution() {
  memset(&solutionContents, 0, sizeof(solutionContents));
  for(int i=0; i<nSolutes; i++)
    solute[i].SetSolute();
}

// adds the solutes in AList to the solutes contained in this solution with
// the application of the multiplier.  AList is assumed to have the same number
// of solutes in the same order as the solution
void Solution::AddSolute(const Solute *AList, REAL multiplier) {
  for(int i=0; i<nSolutes; i++)
    solute[i].AddSolute(AList[i], multiplier);
  UpdateSolutionContents();
}

void Solution::ChangeFluxToSolute(REAL dt) {
  for (int i=0; i<nSolutes; i++) {
    REAL a, s, l;
    solute[i].GetSolute(a, s, l);
    solute[i].SetSolute(a*dt, s*dt, l*dt);
  }
  UpdateSolutionContents();
}

void Solution::UpdateSolutionContents() {
  // volume, mass, Cp
  solutionContents.Vs = solutionContents.Va =
    solutionContents.ms = solutionContents.Aq =
    solutionContents.Cp = 0;
  for(int i=0; i<nSolutes; i++) {
    solutionContents.Vs += solute[i].GetVolume();
    solutionContents.Va += solute[i].GetAqueousVolume();
    solutionContents.Aq += solute[i].GetAqueous();
    solutionContents.ms += solute[i].GetMass();
    solutionContents.Cp += solute[i].GetHeatCapacity();
  }
  
  if (waterIndex>-1) {
    solutionContents.F=solute[waterIndex].properties.FP;
    if (solutionContents.Va)
      for(int i=0; i<nSolutes; i++)
	if (i!=waterIndex)
	  solutionContents.F += 
	    solute[i].properties.
	    FPDepression(solute[i].GetAqueousConcentration());
  }
  else solutionContents.F=0;
}

REAL Solution::GetFreezingTemperature(int freezingSoluteId) const {
  REAL FP=0;
  for(int i=0; i<nSolutes; i++)
    if (solute[i].properties.id==freezingSoluteId)
      FP += solute[i].properties.FP;
    else
      FP += solute[i].properties.FPDepression(solute[i].
					      GetAqueousConcentration());
  return FP;
}

REAL Solution::GetTotalEnthalpy() const {
  REAL H=0;
  for(int i=0; i<nSolutes; i++)
    H += solute[i].GetEnthalpy(T);
  return H;
}

void Solution::AddVariablePointerToList(uint32 index, uint32 variable) {
  void *p=0;
  switch(variable) {
  case 0x01000001:
    p = &T;				// temperature (K)
    break;
  case 0x01000003:
    p = &solutionContents.F;		// freezing temperature (K)
    break;
  case 0x01000002:
    p = &solutionContents.Vs;		// volume (ml)
    break;
  case 0x01000004:
    p = &solutionContents.ms;		// mass (g)
    break;
  }
  
  if (p)
    ::AddVariablePointerToList(index,p);
}

void* Solution::read(ipstream &is, uint32) {
  int ver=is.readWord16();
  if (ver!=0x100)
    ERROR("Solution::read", "invalid object version");
  
  is.readString(name, maxNameLen);
  T = is.readDouble();
  
  nSolutes = is.readWord32();
  solute = new Solute[nSolutes];
  for(int i=0; i<nSolutes; i++) {
    if (!is.readObject(solute[i]))
      ERROR("Solution::read", "invalid solute");
    solute[i].ChangeSolution(*this);
  }
  
  uint32 variable, index;
  do {
    variable = is.readWord32();
    if (variable) {
      index = is.readWord32();
      if ((variable&0xFF000000)==0) {
	int soluteIndex = is.readWord32();
	if (soluteIndex>=0)
	  solute[soluteIndex].AddVariablePointerToList(index, variable);
	else
	  ERROR("Solution::read", "invalid solute pointer index");
      }
      else
	AddVariablePointerToList(index, variable);
    }
  } while (variable);
  
  return this;
}

void Solution::write(opstream &os) const {
  os.writeWord16(0x100);	//version information
  
  os.writeString(name);
  os.writeDouble(T);
  
  os.writeWord32(nSolutes);
  for(int i=0; i<nSolutes; i++)
    os.writeObject(solute[i]);
  
  WriteVariablePointersToStream(this, os);
  os.writeWord32(0);
}

/*
TStreamableClass RegSolution ("Solution", Solution::build, __DELTA(Solution));
*/


//**************** Compartment Methods ****************

Compartment::Compartment (Compartment *AParent) :
  Solution () {
  next=previous=0;
  parent = AParent;
  compartments=0;
  scratchSolution=0;
  
  heatFlux=heatFluxRate=0;
  
  memset(&compartment, 0, sizeof(compartment));
  compartment.nCompartments = 1;
}

Compartment::Compartment(/*StreamableInit*/)
  : Solution(/*streamableInit*/) {
  next=previous=0;
  parent=0;
  compartments=0;
  scratchSolution=0;

  heatFlux=heatFluxRate=0;

  memset(&compartment, 0, sizeof(compartment));
  compartment.nCompartments = 1;
}

Compartment::~Compartment () {
  if (scratchSolution)
    delete scratchSolution;
  
  while(compartments) {
    Compartment *c=compartments;
    compartments=compartments->next;
    delete c;
  }
}

void Compartment::AddCompartment(Compartment *ACompartment) {
  Compartment *c = compartments;
  if (c) {
    while (c->next) c=c->next;
    c->next = ACompartment;
  }
  else compartments = ACompartment;
  
  ACompartment->next = 0;
  ACompartment->previous = c;
  ACompartment->parent = this;
}

void Compartment::InitializeSimulation() {
  Solution::InitializeSimulation();
  
  if ((parent)&&(parent->GetNumberOfSolutes()!=nSolutes))
    ERROR("Compartment::InitializeSimulation", "invalid number of solutes");

  // call contained compartments
  Compartment *c=compartments;
  while (c) {
    c->InitializeSimulation();
    c=c->next;
  }

  if (!scratchSolution)
    scratchSolution = new Solution(*this);
  
  if (scratchSolution)
    scratchSolution->InitializeSimulation();
  else
    ERROR("Compartment::InitializeSimulation", "failed to create scratch solution");
}

void Compartment::DoRateCalculations(const Solution&) {
  // call contained compartments for rate calculations
  Compartment *c=compartments;
  while (c) {
    c->DoRateCalculations(GetSolutionAt(c->compartment.pos));
    c=c->next;
  }
}

void Compartment::FinalizeMovements(CPSolution &returnedSolution, 
				    REAL &returnedHeat) {
  // call contained compartments to finalize movements
  Compartment *c=compartments;
  while (c) {
    REAL inHeat;
    CPSolution inSolution;
    c->FinalizeMovements(inSolution, inHeat);
    
    AddHeatAt(c->compartment.pos, inHeat);
    AddSolutionAt(c->compartment.pos, *inSolution);
    
    c=c->next;
  }

  scratchSolution->ChangeFluxToSolute(-dt);
  
  returnedSolution=scratchSolution;
  returnedHeat=-heatFlux*dt;
}

REAL Compartment::GetTotalVolume() const {
  REAL V = Solution::GetTotalVolume();
  Compartment *c = compartments;
  while (c != NULL) {
    V += c->GetTotalVolume();
    c = c->next;
  }
  
  return V*compartment.nCompartments;
}

REAL Compartment::GetTotalMass() const {
  REAL m = Solution::GetTotalMass();
  Compartment *c = compartments;
  while (c != NULL) {
    m += c->GetTotalMass();
    c = c->next;
  }
  
  return m*compartment.nCompartments;
}

REAL Compartment::GetTotalEnthalpy() const {
  REAL H=Solution::GetTotalEnthalpy();
  Compartment *c = compartments;
  while (c != NULL) {
    H += c->GetTotalEnthalpy();
    c = c->next;
  }

  return H*compartment.nCompartments;
}

REAL Compartment::CalculateSurfaceArea () {
  return compartment.SA;
}

REAL Compartment::CalculateWidth () {
  return compartment.width;
}

void Compartment::AddVariablePointerToList(uint32 index, uint32 variable) {
  void *p=0;
  switch(variable) {
  case 0x02000001:
    p = &compartment.width;	// width
    break;
  case 0x02000002:
    p = &compartment.SA;	// surface area
    break;
  case 0x02000003:
    p = &compartment.pos;	// position in parent
    break;
    
  default:
    Solution::AddVariablePointerToList(index, variable);
  }
  
  if (p)
    ::AddVariablePointerToList(index,p);
}

void* Compartment::read(ipstream &is, uint32) {
  int ver=is.readWord16();
  if (ver!=0x100)
    ERROR("Compartment::read", "invalid object version");
  
  Solution::read(is);
  
  Compartment *c;
  while ((c=dynamic_cast<Compartment*>(is.readObject()))!=0)
    AddCompartment (c);
  
  compartment.nCompartments=is.readWord32();
  compartment.width = is.readDouble();
  compartment.SA = is.readDouble();
  compartment.pos = is.readDouble();
  
  return this;
}

void Compartment::write(opstream &os) const {
  os.writeWord16(0x100);	//version information
  
  Solution::write(os);
  
  Compartment *c = compartments;
  while (c) {
    os.writeObject(c);
    c = c->next;
  }
  os.writeObject(0);
  
  os.writeWord32(compartment.nCompartments);
  os.writeDouble(compartment.width);
  os.writeDouble(compartment.SA);
  os.writeDouble(compartment.pos);
}

/*
TStreamableClass RegCompartment ("Compartment", Compartment::build, __DELTA(Compartment));
*/


//**************** JWCompartment Methods ****************

JWCompartment::JWCompartment(Compartment *AParent)
  : Compartment(AParent) {
  memset(&jwParameters, 0, sizeof(jwParameters));
  dVdt=dSdt=0;
  
  jwParameters.permeantId = 2;
  jwParameters.impermeantId = 3;
  
  jwParameters.Verror = 1e-3;	// 0.1% error/sec
  jwParameters.Serror = 1e-3;	// 0.1% error/sec
  jwParameters.Herror = 1e-3;	// 0.1% error/sec
  
  permeantIndex=impermeantIndex = -1;
}

JWCompartment::JWCompartment(/*StreamableInit*/) 
  : Compartment(/*streamableInit*/) {
  memset(&jwParameters, 0, sizeof(jwParameters));
  dVdt=dSdt=0;
  permeantIndex=impermeantIndex = -1;
}

void JWCompartment::InitializeSimulation() {
  Compartment::InitializeSimulation();
  
  for(int i=0; i<nSolutes; i++)
    if (solute[i].properties.id==jwParameters.permeantId)
      permeantIndex=i;
    else if (solute[i].properties.id==jwParameters.impermeantId)
      impermeantIndex=i;
}

void JWCompartment::DoRateCalculations(const Solution& imposedSolution) {
  Compartment::DoRateCalculations(imposedSolution);
  
  scratchSolution->EmptySolution();
  
  // heat calculations and dt adjustment
  if (jwParameters.lambda>=0) {
    heatFlux = jwParameters.lambda * compartment.SA *
      (imposedSolution.GetTemperature()-T);
    heatFluxRate = jwParameters.lambda * compartment.SA *
      (imposedSolution.GetTemperatureRate()-heatFlux/GetHeatCapacity());
    if (heatFluxRate) {
      REAL newdt = ABS(jwParameters.Herror*T*GetHeatCapacity()/heatFluxRate);
      if (newdt<dt) dt=newdt, whodt=0x00010003;
    }
    scratchSolution->SetTemperature(T);  
  }
  else
    scratchSolution->SetTemperature(imposedSolution.GetTemperature());

  // Note: if lambda<0, lambda=infinity and the heatFlux and heatFluxRate from
  // the last iteration are used since they cannot be calculated here

  // permeation calculations
  dVdt=dSdt=0;
  if (imposedSolution.AnyAqueous()) {
    REAL iCs=0, 
      eCs=0,
      iV=GetAqueousVolume(),
      eV=imposedSolution.GetAqueousVolume(),
      iA=GetAqueous(),
      eA=imposedSolution.GetAqueous();
    
    if (permeantIndex>=0) {
      iCs = solute[permeantIndex].GetAqueousConcentration();
      eCs = imposedSolution.GetSolutes()[permeantIndex].
	GetAqueousConcentration();
    }

    REAL Lp = Arrhenius(jwParameters.Lpg,jwParameters.LpgEa,T,
			jwParameters.LpgTg);
    REAL Ps = Arrhenius(jwParameters.Psg,jwParameters.PsgEa,T,
			jwParameters.PsgTg);

    REAL iCi=0, eCi=0;
    if (impermeantIndex>=0) {
      iCi = solute[impermeantIndex].GetAqueousConcentration();
      eCi = imposedSolution.GetSolutes()[impermeantIndex].
	GetAqueousConcentration();
    }

#ifdef useKK
    REAL iPs=0, ePs=0;
    if (permeantIndex>=0) {
      iPs = GetOsmoticPressure(permeantIndex);
      ePs = imposedSolution.GetOsmoticPressure(permeantIndex);
    }

    REAL iP = GetOsmoticPressure();
    REAL eP = imposedSolution.GetOsmoticPressure();

    // Kedem & Katchalsky equations
    // ml/sec
    dVdt = Lp*compartment.SA*((iP-eP) - (1-jwParameters.sigma)*(iPs-ePs));
    
    REAL temp = (1-jwParameters.sigma)*(eCs+iCs)*dVdt/2; 	// (mol/sec)

    if (permeantIndex>=0)	// this is Chris's idea, may not be appropraite
      dVdt -= temp*solute[permeantIndex].properties.aMV;
    // dVdt is water flux at this point

    // mol/sec
    dSdt = Ps*compartment.SA*(eCs-iCs) + temp;

#else
    // Johnson & Wilson equations
    // ml/sec
    dVdt = Lp*R*T*compartment.SA * ((iCi-eCi) + jwParameters.sigma*(iCs-eCs));

    // mol/sec
    dSdt = Ps*compartment.SA*(eCs-iCs) + 
      (1-jwParameters.sigma)*(eCs+iCs)*dVdt/2;
#endif

    if ((iV==0)||(eV==0)) {
      dt=0;	// cannot adjust dt
      ERROR("JWCompartment::DoRateCalculations", "cannot adjust dt");
      return;
    }

    // water flux and dt adjustment
    if (dVdt>0)
      heatFlux += dVdt*(imposedSolution.GetTemperature()-T)/
	solute[waterIndex].properties.aMV*solute[waterIndex].properties.aCp;

    // ml/sec2
    REAL d2Vdt = Lp*R*T*compartment.SA / (iV*eV) *
      (jwParameters.sigma*(eV+iV)*dSdt -
       (iCi*eV+eCi*iV+jwParameters.sigma*(iCs*eV+eCs*iV))*dVdt);

    scratchSolution->SetFluxForSolute(waterIndex,dVdt/solute[waterIndex].
				      properties.aMV*
				      compartment.nCompartments);
    scratchSolution->SetFluxRateForSolute(waterIndex,d2Vdt/solute[waterIndex].
					  properties.aMV*
					  compartment.nCompartments);

    if (d2Vdt) {
      REAL newdt = ABS(2 * jwParameters.Verror * MIN(iV,eV) / d2Vdt);
      if (newdt<dt) dt=newdt, whodt=0x00010001;
    }

    // solute flux and dt adjustment
    if (permeantIndex>=0) {
      if (dSdt>0)
	heatFlux += dSdt*solute[permeantIndex].properties.aCp *
	  (imposedSolution.GetTemperature()-T);
      
      REAL temp1 = (-dSdt+dVdt*eCs)/eV;		// mol/ml/sec
      REAL temp2 = (-dSdt+dVdt*iCs)/iV;		// mol/ml/sec
      
      // mol/sec2
      REAL d2Sdt = Ps*compartment.SA*(temp1+temp2) +
	(1-jwParameters.sigma)/2*((temp1-temp2)*dVdt+(eCs+iCs)*d2Vdt);

      scratchSolution->SetFluxForSolute(permeantIndex,dSdt*
					compartment.nCompartments);
      scratchSolution->SetFluxRateForSolute(permeantIndex,d2Sdt*
					    compartment.nCompartments);

      if (d2Sdt) {
	REAL newdt = ABS(2 * jwParameters.Serror * MIN(iA,eA) / d2Sdt);
	if (newdt<dt) dt=newdt, whodt=0x00010002;
      }
    }
  }
}

void JWCompartment::FinalizeMovements(CPSolution &returnedSolution, 
				      REAL &returnedHeat) {
  // Note: if lambda<0, lambda=infinity and the heatFlux and heatFluxRate from
  // the last iteration are used since they cannot be calculated here
  if (jwParameters.lambda<0) {
    REAL newHeatFlux = (scratchSolution->GetTemperature()-T) *
      GetHeatCapacity()/dt;
    heatFluxRate = (newHeatFlux-heatFlux)/dt;
    heatFlux = newHeatFlux;
  }
  scratchSolution->SetTemperature(T);

  Compartment::FinalizeMovements(returnedSolution, returnedHeat);
  
  solute[waterIndex].aqueous.amount += 
    dVdt*dt/solute[waterIndex].properties.aMV;
  solute[waterIndex].aqueous.rate = dVdt/solute[waterIndex].properties.aMV;
  if (permeantIndex>=0) {
    solute[permeantIndex].aqueous.amount += dSdt*dt;
    solute[permeantIndex].aqueous.rate = dSdt;
  }
  UpdateSolutionContents();

  AddHeatAt(0, heatFlux*dt);
  dTdt = heatFlux/GetHeatCapacity();

  REAL s = solute[waterIndex].solid.amount;
  if (s) {
    REAL F = GetFreezingTemperature();
    REAL L = solute[waterIndex].properties.LatentHeatFusion(F);
    REAL HC = GetHeatCapacity();
    REAL v0 = GetAqueousVolume();
    REAL aMV = solute[waterIndex].properties.aMV;
    REAL C0dFdc=0;
    for(int i=0; i<nSolutes; i++)
      if (i!=waterIndex) {
	REAL C0 = solute[i].GetAqueousConcentration();
	C0dFdc += C0*solute[i].properties.FPGradient(C0);
      }

#ifdef CHECKS
    if (C0dFdc>0)
      ERROR("JWCompartment::FinalizeMovements", "C0dFdc positive");
    if (HC*v0*C0dFdc/L/aMV<0)
      ERROR("JWCompartment::FinalizeMovements", "sqrt error");
    REAL oldT = T;
#endif

    REAL diff = F-T-C0dFdc;
    REAL f = (v0/aMV-HC*diff/L)/2 -
      sqrt(SQR(v0/aMV+HC*diff/L)/4 + HC*v0*C0dFdc/L/aMV);

    REAL a = solute[waterIndex].aqueous.amount;
    if ((f<0)&&(-f>s)) f=-s;
    else if (f>=a) {

#ifdef CHECKS
      ERROR("JWCompartment::FinalizeMovements", "complete compartment freeze");
      fprintf(stderr,"  f=%g, a=%g, s=%g\n", f, a, s);
      fprintf(stderr,"  T=%g, F=%g\n", T, F);
#endif

      f=a*0.99999;
    }

    solute[waterIndex].AddSolute(-f, f);
    solute[waterIndex].aqueous.rate += -f/dt;
    solute[waterIndex].solid.rate += f/dt;
    UpdateSolutionContents();
    REAL LatentHeat = -f*solute[waterIndex].properties.LatentHeatFusion(T);
    AddHeatAt(0, LatentHeat);
    dTdt += LatentHeat/GetHeatCapacity()/dt;

#ifdef CHECKS
    REAL F1 = GetFreezingTemperature();
    if (ABS(T-F1)>0.001) {
      ERROR("JWCompartment::FinalizeMovements", "ice growth error");
      fprintf(stderr, "  T0=%g, F0=%g, f=%g, T1=%g, F1=%g\n", oldT,F,f,T,F1);
    }
#endif
  }
}

REAL JWCompartment::CalculateSurfaceArea() {
  // sample SA based on spherical shaped compartment
  if (jwParameters.isSphere) {
    REAL r = exp(log(3*GetTotalVolume()/(4*M_PI))/3);
    return (4*M_PI*r*r);
  }
  return compartment.SA;
}

void JWCompartment::Nucleate() {
  if (solute[waterIndex].solid.amount==0) {
    REAL f = solute[waterIndex].aqueous.amount*1e-6;
    
    solute[waterIndex].aqueous.amount -= f;
    solute[waterIndex].solid.amount += f;
    UpdateSolutionContents();
    
    T += -f*solute[waterIndex].properties.LatentHeatFusion(T)/
      solutionContents.Cp;
    
#ifdef CHECKS
    fprintf(stderr,"JWCompartment::Nucleate, compartment nucleated\n");
#endif
  }
}

void JWCompartment::AddVariablePointerToList(uint32 index, uint32 variable) {
  void *p=0;
  switch(variable) {
  case 0x03000001:
    p = &jwParameters.Lpg;
    break;
  case 0x03000002:
    p = &jwParameters.LpgEa;
    break;
  case 0x03000004:
    p = &jwParameters.Psg;
    break;
  case 0x03000005:
    p = &jwParameters.PsgEa;
    break;
  case 0x03000007:
    p = &jwParameters.sigma;
    break;
  case 0x03000101:
    p = &dVdt;
    break;
  case 0x03000102:
    p = &dSdt;
    break;
    
  default:
    Compartment::AddVariablePointerToList(index, variable);
  }
  
  if (p)
    ::AddVariablePointerToList(index,p);
}

void* JWCompartment::read(ipstream &is, uint32) {
  int ver=is.readWord16();
  if (ver!=102)
    ERROR("JWCompartment::read", "invalid object version");
  
  Compartment::read(is);
  
  jwParameters.Lpg = is.readDouble();
  jwParameters.LpgTg = is.readDouble();
  jwParameters.LpgEa = is.readDouble();
  
  jwParameters.Psg = is.readDouble();
  jwParameters.PsgTg = is.readDouble();
  jwParameters.PsgEa = is.readDouble();
  
  jwParameters.sigma = is.readDouble();
  
  jwParameters.Verror = is.readDouble();
  jwParameters.Serror = is.readDouble();
  jwParameters.Herror = is.readDouble();
  
  jwParameters.isSphere = is.readWord16();	// boolean
  
  jwParameters.permeantId = is.readWord32();
  jwParameters.impermeantId = is.readWord32();
  
  jwParameters.lambda = is.readDouble();
  
  return this;
}

void JWCompartment::write(opstream &os) const {
  os.writeWord16(102);	//version information
  
  Compartment::write(os);
  
  os.writeDouble(jwParameters.Lpg);
  os.writeDouble(jwParameters.LpgTg);
  os.writeDouble(jwParameters.LpgEa);

  os.writeDouble(jwParameters.Psg);
  os.writeDouble(jwParameters.PsgTg);
  os.writeDouble(jwParameters.PsgEa);

  os.writeDouble(jwParameters.sigma);

  os.writeDouble(jwParameters.Verror);
  os.writeDouble(jwParameters.Serror);
  os.writeDouble(jwParameters.Herror);

  os.writeWord16((uint16)jwParameters.isSphere);	// boolean

  os.writeWord32(jwParameters.permeantId);
  os.writeWord32(jwParameters.impermeantId);

  os.writeDouble(jwParameters.lambda);
}

/*
TStreamableClass RegJWCompartment ("TJWCompartment", TJWCompartment::build, __DELTA(TJWCompartment));
*/



//**************** DSubInterval ****************

DSubInterval::DSubInterval() {
  // THIS IS DANGEROUS!
  memset(&alpha, 0, sizeof(DSubInterval)-4);
}

void DSubInterval::CalculateAndAddMNL(REAL &M, REAL &N, 
				      REAL A, REAL C, REAL D) {
  // denom (cm)
  REAL denom = 3*(dx+P);
  
  // M (K*sec/cm)
  M += A*dx*(dx+3*P)*C/(2*D*denom);
  
  // N (K)
  N += -A*SQR(dx)*Q/denom + A*total/dx - A*C;
}

void DSubInterval::InitialAlphaBetaFixed(REAL value) {
  REAL diff = value - total/dx;
  
  // denom (cm2)
  REAL denom = dx * (dx+3*P);
  
  // alpha (mol/cm3) or (K/cm2)
  alpha = (3*dx*Q + 6*diff) / denom;
  
  // beta (mol/cm2) or (K/cm)
  beta  = (SQR(dx)*Q - 6*P*diff) / denom;
}


void DSubInterval::CalculatePQ(DSubInterval &i1) {
  REAL hP = i1.dx + i1.P;	// h+P
  REAL hh = i1.dx + dx;		// h+h
  
  // denom (cm2)
  REAL denom = 3*hP*hh - 2*SQR(i1.dx);
  
  // P (cm)
  P = (dx*hP*hh + 2*dx*i1.dx*i1.P) / denom;
  
  // Q (mol/cm2) or (K/cm)
  Q = (-6*hP*(total/dx-i1.total/i1.dx) - 2*SQR(i1.dx)*i1.Q) / denom;
}

void DSubInterval::CalculatePQXL(DSubInterval &i1, REAL N, REAL Ks, REAL Kl,
				 REAL M) {
  i1.M=M; i1.N=N;
  
  REAL hP = i1.dx + i1.P;		// h+P
  REAL h3P = i1.dx + 3*i1.P;	// h+3P
  
  REAL d1 = -i1.Lrho*dx*i1.dx*h3P;
  REAL d2 = -2*Ks*M*i1.dx*h3P;
  REAL d3 = -2*Kl*M*dx*hP;
  
  // denom (J)
  REAL denom =  d1 + d2 + 3*d3;
  
  // P (cm)
  P = dx * (d1/3 + d2 + d3) / denom;
  
  // Q (K/cm)
  Q = (-2*i1.Lrho*i1.dx*h3P*(N-total/dx)
       +12*Kl*M*hP*(total/dx-i1.total/i1.dx)
       +4*Kl*M*SQR(i1.dx)*i1.Q) / denom;
}

void DSubInterval::CalculateAlphaBeta(DSubInterval& i0) {
  REAL hh = dx + i0.dx;		// h+h
  REAL hP = dx + P;		// h+P
  REAL diff = i0.total/i0.dx - total/dx;
  
  // denom (cm2)
  REAL denom = 3*hP*hh - 2*SQR(dx);

  // alpha (mol/cm3) or (K/cm2)
  alpha = (-2*SQR(i0.dx)*i0.alpha + 3*hh*Q + 6*diff) / denom;
  
  // beta (mol/cm2) or (K/cm)
  beta  = (2*SQR(i0.dx)*P*i0.alpha + dx*(3*i0.dx+dx)*Q - 6*P*diff) / denom;
  
#ifdef CHECKS
  REAL lhs=-dx*alpha+beta;
  REAL rhs=i0.dx*i0.alpha+i0.beta;
  if (lhs-rhs>1e-3*(ABS(lhs)+ABS(rhs)))
    fprintf(stderr,"DSubInterval::CalculateAlphaBeta, eq(1) %g ?= %g\n",
	    lhs,rhs);
  lhs=SQR(dx)*alpha/6-dx*beta/2+total/dx;
  rhs=SQR(i0.dx)*i0.alpha/6+i0.dx*i0.beta/2+i0.total/i0.dx;
  if (lhs-rhs>1e-3*(ABS(lhs)+ABS(rhs)))
    fprintf(stderr,"DSubInterval::CalculateAlphaBeta, eq(2) %g ?= %g\n",
	    lhs,rhs);
  lhs=P*alpha+beta;
  rhs=Q;
  if ((lhs-rhs>1e-3*(ABS(lhs)+ABS(rhs)))&&(ABS(lhs)>1e-10))
    fprintf(stderr,"DSubInterval::CalculateAlphaBeta, eq(3) %g ?= %g\n",
	    lhs,rhs);
#endif
}

REAL DSubInterval::CalculateAlphaBetaXLh(DSubInterval &i0, REAL Ks, REAL Kl) {
  REAL hP = dx + P;		// h+P
  REAL h3P = dx + 3*P;		// h+3P
  REAL diff = i0.total/i0.dx - total/dx;
  REAL Ndiff = N - total/dx;
  
  // denom (J)
  REAL denom = -Lrho*i0.dx*dx*h3P - 2*Ks*M*dx*h3P -6*Kl*M*i0.dx*hP;

  // alpha (K/cm2)
  alpha = (4*Ks*M*SQR(i0.dx)*i0.alpha -3*Lrho*i0.dx*(2*Ndiff+dx*Q)
	   -12*Ks*M*diff -6*M*Q*(Kl*i0.dx+Ks*dx)) / denom;

  // beta (K/cm)
  beta =  (-4*Ks*M*SQR(i0.dx)*P*i0.alpha +6*Lrho*i0.dx*P*Ndiff
	   -Lrho*i0.dx*SQR(dx)*Q
	   +12*Ks*M*P*diff -2*M*dx*Q*(3*Kl*i0.dx+Ks*dx)) / denom;

  // dXdt (cm/sec)
  dXdt =  (2*Ks*dx*h3P*(SQR(i0.dx)*i0.alpha/3+N-i0.total/i0.dx)
	   +2*Kl*i0.dx*(SQR(dx)*Q+3*hP*Ndiff)) / denom;

#ifdef CHECKS
  REAL lhs=SQR(dx)*alpha/6-dx*beta/2+total/dx;
  REAL rhs=M*dXdt+N;
  if (lhs-rhs>1e-3*(ABS(lhs)+ABS(rhs)))
    fprintf(stderr,"DSubInterval::CalculateAlphaBetaXLh, eq(1) %g ?= %g\n",
	    lhs,rhs);
  lhs=SQR(i0.dx)*i0.alpha/6+i0.dx*i0.beta/2+i0.total/i0.dx;
  rhs=M*dXdt+N;
  if (lhs-rhs>1e-3*(ABS(lhs)+ABS(rhs)))
    fprintf(stderr,"DSubInterval::CalculateAlphaBetaXLh, eq(2) %g ?= %g\n",
	    lhs,rhs);
  lhs=Ks*i0.dx*i0.alpha+Ks*i0.beta+Kl*dx*alpha-Kl*beta;
  rhs=-Lrho*dXdt;
  if (lhs-rhs>1e-3*(ABS(lhs)+ABS(rhs)))
    fprintf(stderr,"DSubInterval::CalculateAlphaBetaXLh, eq(3) %g ?= %g\n",
	    lhs,rhs);
  lhs=P*alpha+beta;
  rhs=Q;
  if (lhs-rhs>1e-3*(ABS(lhs)+ABS(rhs)))
    fprintf(stderr,"DSubInterval::CalculateAlphaBetaXLh, eq(4) %g ?= %g\n",
	    lhs,rhs);
#endif
  
  return dXdt;
}

void DSubInterval::CalculateAlphaBetaXLs(REAL dXdt, REAL D) {
  REAL hP = dx + P;		// h+P
  REAL CDdXdt = estConc*dXdt/D;
  
  // alpha (mol/cm3)
  alpha = (Q + CDdXdt) / hP;
  
  // beta (mol/cm2)
  beta  = (dx*Q - P*CDdXdt) / hP;
}

void DSubInterval::DivideSubInterval(DSubInterval &i0, DSubInterval &i1) {
  i0.SA = i1.SA = SA;
  
  i0.totalSolid = i1.totalSolid = totalSolid/2;
  i0.totalLiquid = i1.totalLiquid = totalLiquid/2;
  
  i0.alpha = i1.alpha = alpha;
  
  REAL oldbeta = beta;
  i0.beta = oldbeta - alpha*dx/2;
  i1.beta = oldbeta + alpha*dx/2;
  
  REAL oldtotal = total;
  i0.total = oldtotal/2 - oldbeta*SQR(dx)/8;
  i1.total = oldtotal/2 + oldbeta*SQR(dx)/8;
  
  i0.HC = i1.HC = HC/2;
  i0.dx = i1.dx = dx/2;
  i1.x = i0.x + i0.dx;
}


//**************** TDCompartment Methods ****************

DCompartment::DCompartment(Compartment *AParent)
  : Compartment(AParent) {
  memset(&dParameters, 0, sizeof(dParameters));
  subints=0;	
  heat=0;
  nucleateFlag=0;
  nSubInts=0;
  firstSolute=0;
}

DCompartment::DCompartment(/*StreamableInit*/)
  : Compartment(/*streamableInit*/) {
  memset(&dParameters, 0, sizeof(dParameters));
  subints=0;	
  heat=0;
  nucleateFlag=0;
  nSubInts=0;
  firstSolute=0;
}

DCompartment::~DCompartment() {
  if (heat)
    delete[] heat;
  if (subints) {
    for(int i=0; i<nSolutes; i++)
      delete[] subints[i];
    delete[] subints;
  }
}

void DCompartment::InitializeSimulation() {
  Compartment::InitializeSimulation();
  
  if (!nSubInts)
    ERROR("DCompartment::InitializeSimulation", 
	  "invalid number of subintervals");
  
  if (!heat) heat = new DSubInterval[nSubInts];
  if (!subints) {
    subints = new PDSubInterval[nSolutes];
    for(int i=0; i<nSolutes; i++)
      subints[i] = new DSubInterval[nSubInts];
  }
  
  REAL x=0;
  for(int j=0; j<nSubInts; j++) {
    REAL dx=0, HC=0;
    for(int i=0; i<nSolutes; i++) {
      if (dParameters.initialConditions&1) {
	subints[i][j].total = (j==0 ?  0 : solute[i].aqueous.amount/
			       (nSubInts-1));
	subints[i][j].totalSolid = (j==0 ? solute[i].solid.amount : 0);
      }
      
      else {
	subints[i][j].total = solute[i].aqueous.amount/nSubInts;
	subints[i][j].totalSolid = solute[i].solid.amount/nSubInts;
      }

      subints[i][j].totalLiquid = solute[i].liquid.amount/nSubInts;
      subints[i][j].x = x;
      subints[i][j].SA = compartment.SA;
      dx += subints[i][j].total*solute[i].properties.aMV
	+subints[i][j].totalSolid*solute[i].properties.sMV
	+subints[i][j].totalLiquid*solute[i].properties.lMV;
      HC += subints[i][j].total*solute[i].properties.aCp
	+subints[i][j].totalSolid*solute[i].properties.sCp
	+subints[i][j].totalLiquid*solute[i].properties.lCp;
    }
    dx /= compartment.SA;

    for(int i=0; i<nSolutes; i++)
      subints[i][j].dx = dx;

    heat[j].total = T*dx;
    heat[j].HC = HC;
    heat[j].x = x;
    heat[j].dx = dx;
    heat[j].SA = compartment.SA;
    
    x += dx;
  }
  compartment.width = x;
  
  if (dParameters.initialConditions&1) {
    REAL F=CalculateFreezingTemperatureInSubInt(1, waterId);
    if (F*heat[0].dx<heat[0].total)
      heat[0].total = F*heat[0].dx;
  }
  
  while ((firstSolute<nSolutes)&&(solute[firstSolute].properties.D==0))
    firstSolute++;
  
  X = dParameters.initialConditions&1 ? heat[0].dx : -1;
}


void DCompartment::DoRateCalculations(const Solution& imposedSolution) {
  Compartment::DoRateCalculations(imposedSolution);
  
  // find heat and solute flux at inside boundary and initialize P and Q
  for(int i=0; i<nSolutes; i++)	{
    subints[i][nSubInts-1].InitialPQGradient(0);
    subints[i][nSubInts-1].InitialdQGradient(0);
  }
  heat[nSubInts-1].InitialPQGradient(0);
  heat[nSubInts-1].InitialdQGradient(0);
  
  REAL Kn = subints[waterIndex][nSubInts-1].SolidState() ?
    solute[waterIndex].properties.sTC : solute[waterIndex].properties.aTC;
  Compartment *c=compartments;
  while (c) {
    if (c->GetPosition()==1) {
      heat[nSubInts-1].InitialPQGradientAdd(-c->GetHeatFlux()/Kn);
      heat[nSubInts-1].InitialdQGradientAdd(-c->GetHeatFluxRate()/Kn);
      for (int i=firstSolute; i<nSolutes; i++) {
	subints[i][nSubInts-1].
	  InitialPQGradientAdd(-imposedSolution.GetSolutes()[i].
			       GetAqueousFlux()/solute[i].properties.D);
	subints[i][nSubInts-1].
	  InitialdQGradientAdd(-imposedSolution.GetSolutes()[i].
			       GetAqueousFluxRate()/solute[i].properties.D);
      }
    }
    c=c->next;
  }
  
  // calculate P's and Q's
  for(int j=nSubInts-2; j>=0; j--)
    switch(PhaseBoundaryType(j)) {
    case 1: {	// solid on left calculation
      REAL M=0, N=0;
      REAL volPerCM = GetAqueousVolPerCMInSubInt(j+1);
      for(int i=firstSolute; i<nSolutes; i++)
	if (i!=waterIndex) {
	  REAL C=subints[i][j+1].EstimateConcentrationL();
	  REAL A=solute[i].properties.FPGradient(C/volPerCM)/volPerCM;
	  subints[i][j+1].CalculateAndAddMNL(M,N,A,C,solute[i].properties.D);
	}
      REAL Fest = CalculateEstimatedFPInSubInt(j+1, waterId);
      N += Fest;
	
      heat[j+1].Lrho = solute[waterIndex].properties.LatentHeatFusion(Fest)/
	(heat[j+1].dXdt>0 ? solute[waterIndex].properties.aMV*heat[j].SA
	 /volPerCM : solute[waterIndex].properties.sMV);
	
      REAL Kl = (subints[waterIndex][j+1].SolidState() ? solute[waterIndex].
		 properties.sTC : solute[waterIndex].properties.aTC);
      heat[j].CalculatePQXL(heat[j+1],N,
			    solute[waterIndex].properties.sTC,Kl,M);
    }
    break;
      
    case -1:	// solid on right calculation
      ERROR("DCompartment::DoRateCalculations", 
	    "solid on right not supported");
      break;

    case 0:
      heat[j].CalculatePQ(heat[j+1]);
      if (subints[waterIndex][j].SolidState()!=1)
	for(int i=firstSolute; i<nSolutes; i++)
	  subints[i][j].CalculatePQ(subints[i][j+1]);
      break;
    }

  // calculate alpha's and beta's
  heat[0].InitialAlphaBetaFixed(imposedSolution.GetTemperature());
  if (subints[waterIndex][0].SolidState()!=1) {
    if (imposedSolution.AnyAqueous()) {
      REAL volPerCM = GetAqueousVolPerCMInSubInt(0);
      for(int i=firstSolute; i<nSolutes; i++)
	subints[i][0].
	  InitialAlphaBetaFixed(imposedSolution.GetSolutes()[i].
				GetAqueousConcentration()*volPerCM);
    }
    else
      for(int i=firstSolute; i<nSolutes; i++)
	subints[i][0].InitialAlphaBetaGradient(0);
  }

  for (int j=0; j<nSubInts-1; j++)
    switch(PhaseBoundaryType(j)) {
    case 1: {	// solid on left calculation
      REAL Kl = (subints[waterIndex][j+1].SolidState() ? 
		 solute[waterIndex].properties.sTC : 
		 solute[waterIndex].properties.aTC);
      REAL dXdt = 
	heat[j+1].CalculateAlphaBetaXLh(heat[j],solute[waterIndex].
					properties.sTC,Kl);
      
      for(int i=firstSolute; i<nSolutes; i++)
	subints[i][j+1].CalculateAlphaBetaXLs(dXdt, solute[i].properties.D);
      
      if (dXdt!=0) {
	REAL newdt = ABS(dParameters.dXmax*compartment.width/dXdt);
	if (newdt<dt) {
	  dt=newdt;
	  whodt=0x00020300;
	}
      }
    }
    break;
    
    case -1:	// solid on right calculation
      ERROR("DCompartment::DoRateCalculations", 
	    "solid on right not supported");
      break;
      
    case 0:
      heat[j+1].CalculateAlphaBeta(heat[j]);
      if (subints[waterIndex][j].SolidState()!=1)
	for(int i=firstSolute; i<nSolutes; i++)
	  subints[i][j+1].CalculateAlphaBeta(subints[i][j]);
      break;
    }

  // calculate dQdt's
  REAL Ds = solute[waterIndex].properties.sTC*solute[waterIndex].properties.sMV
    /solute[waterIndex].properties.sCp;
  REAL Dl = solute[waterIndex].properties.aTC*solute[waterIndex].properties.aMV
    /solute[waterIndex].properties.aCp;

  for(int j=nSubInts-2; j>=0; j--)
    switch (PhaseBoundaryType(j)) {
    case 1: {	// solid on left calculation
      REAL dNdt=0;
      REAL volPerCM = GetAqueousVolPerCMInSubInt(j+1);
      for(int i=firstSolute; i<nSolutes; i++)
	if (i!=waterIndex) {
	  REAL A=solute[i].properties.FPGradient(subints[i][j+1].estConc
						 /volPerCM)/volPerCM;
	  dNdt += subints[i][j+1].CalculatedNL(A, solute[i].properties.D);
	}
      REAL Kl = (subints[waterIndex][j+1].SolidState() ? solute[waterIndex].
		 properties.sTC : solute[waterIndex].properties.aTC);
      heat[j].CalculatedQXL(heat[j+1],solute[waterIndex].properties.sTC,
			    Kl,dNdt,Ds,Dl);
    }
    break;
    
    case -1:	// solid on right calculation
      ERROR("DCompartment::DoRateCalculations", 
	    "solid on right not supported");
      break;

    case 0: {
      for(int i=firstSolute; i<nSolutes; i++)
	subints[i][j].CalculatedQ(subints[i][j+1],solute[i].properties.D,
				  solute[i].properties.D);
      heat[j].CalculatedQ(heat[j+1],subints[waterIndex][j].SolidState()?Ds:Dl,
			  subints[waterIndex][j+1].SolidState()?Ds:Dl);
    }
    break;
    }
  
  // Calculate dAlpha's and adjust dt
  for (int j=0; j<nSubInts; j++) {
    REAL halpha=0;

    int solidState=subints[waterIndex][j].SolidState();

    if (j==0) {
      halpha = heat[0].InitialdAlphaFixed(dTdt, solidState ? Ds : Dl);
      if (solidState!=1) {
	if (imposedSolution.AnyAqueous()) {
	  REAL volPerCM = GetAqueousVolPerCMInSubInt(0);
	  for(int i=firstSolute; i<nSolutes; i++) {
	    subints[i][0].
	      InitialdAlphaFixed(imposedSolution.GetSolutes()[i].aqueous.rate*
				 volPerCM/imposedSolution.GetAqueousVolume(),
				 solute[i].properties.D);
#ifdef CHECKS
	    if (imposedSolution.GetSolutes()[i].aqueous.rate)
	      WARNING("DCompartment::DoRateCalculations", "non-zero rate");
#endif
	  }
	}
	
	else
	  for(int i=firstSolute; i<nSolutes; i++)
	    subints[i][0].InitialdAlphaGradient(0);
      }
    }
    
    else switch (PhaseBoundaryType(j-1)) {
    case 1: {	// solid on left calculation
      REAL dNdt=0;
      REAL volPerCM = GetAqueousVolPerCMInSubInt(j);
      for(int i=firstSolute; i<nSolutes; i++)
	if (i!=waterIndex) {
	  REAL A=solute[i].properties.FPGradient(subints[i][j].estConc
						 /volPerCM)/volPerCM;
	  dNdt += subints[i][j].CalculatedNL(A, solute[i].properties.D);
	}

      REAL Kl = solidState ? solute[waterIndex].properties.sTC :
	solute[waterIndex].properties.aTC;
      
      halpha = heat[j].CalculatedAlphaXLh(heat[j-1], solute[waterIndex].
					  properties.sTC, Kl, dNdt, Ds, Dl);
      
      REAL d2Xdt = heat[j].Calculated2Xdt(heat[j-1], solute[waterIndex].
					  properties.sTC, Kl, dNdt, Ds, Dl);

      for(int i=firstSolute; i<nSolutes; i++)
	subints[i][j].CalculatedAlphaXLs(d2Xdt, solute[i].properties.D);
    }
    break;

    case -1:	// solid on right calculation
      ERROR("DCompartment::DoRateCalculations", 
	    "solid on right not supported");
      break;
      
    case 0: {
      halpha = heat[j].CalculatedAlpha(heat[j-1], solidState ? Ds : Dl,
				       solidState ? Ds : Dl);
      
      if (solidState!=1)
	for(int i=firstSolute; i<nSolutes; i++)
	  subints[i][j].CalculatedAlpha(subints[i][j-1], solute[i].
					properties.D, solute[i].properties.D);
    }
    break;
    }
    
    if (solidState!=1) {
      REAL volPerCM = GetAqueousVolPerCMInSubInt(j);
      for(int i=firstSolute; i<nSolutes; i++)
	if (subints[i][j].dAlpha!=0) {
	  REAL newdt = ABS(dParameters.Serror*volPerCM/
			   (2*solute[i].properties.aMV*solute[i].properties.D*
			    subints[i][j].dAlpha));
	  if (newdt<dt) dt=newdt, whodt=0x00020200+j;
	}
    }
    
    if (halpha!=0) {
      REAL newdt = ABS(dParameters.Herror*heat[j].total/
		       (2*heat[j].dx*(solidState?Ds:Dl)*halpha));
      if (newdt<dt) dt=newdt, whodt=0x00020100+j;
    }
  }
  
  
  // set outside boundary flux
  heatFlux = -heat[0].GetGradientAt(0) * compartment.SA *
    (subints[waterIndex][0].SolidState() ? solute[waterIndex].properties.sTC :
     solute[waterIndex].properties.aTC);
  
  heatFluxRate = 0;		// THIS SHOULD BE SOMETHING ELSE!!!
  
  for(int i=0; i<nSolutes; i++) {
    scratchSolution->SetFluxForSolute(i, -subints[i][0].GetGradientAt(0)
				      *solute[i].properties.D);
    
    scratchSolution->SetFluxRateForSolute(i, 0);	// THIS TOO!!!
  }
  scratchSolution->SetTemperature(heat[0].GetValueAt(0));

  if (!subints[waterIndex][0].SolidState())
    if (imposedSolution.GetSolutes()[waterIndex].solid.amount) {
      if (imposedSolution.GetSolutes()[waterIndex].aqueous.amount)
	nucleateFlag=-1;		// dendritic ice formation
      else nucleateFlag=1;		// planar ice formation
    }
}

void DCompartment::FinalizeMovements(CPSolution &returnedSolution, 
				     REAL &returnedHeat) {
  heat[0].totalRate = heatFlux;
  for(int i=0; i<nSolutes; i++) {
    subints[i][0].totalRate = 
      -subints[i][0].GetGradientAt(0)*solute[i].properties.D;
    heat[0].totalRate += 
      subints[i][0].totalRate*solute[i].properties.aCp*
      scratchSolution->GetTemperature();
  }
  
  for(int j=0; j<nSubInts; j++) {
    REAL T0 = heat[j].total/heat[j].dx;
    REAL H = T0*heat[j].HC;
    
    if (j<nSubInts-1) {
      REAL K=0;
      int ss=subints[waterIndex][j].SolidState();
      if ((ss==1)||((ss==-1)&&(subints[waterIndex][j+1].SolidState()==-1)))
	K = solute[waterIndex].properties.sTC;
      else
	K = solute[waterIndex].properties.aTC;
      heat[j+1].totalRate = -heat[j].GetGradientI()*heat[j].SA*K;
      heat[j].totalRate -= heat[j+1].totalRate;
      
      int pbt=PhaseBoundaryType(j);
      if (pbt)
	for(int i=0; i<nSolutes; i++)
	  if (i==waterIndex) {
	    REAL T = heat[j].GetValueI();
	    REAL LH = heat[j+1].Lrho*heat[j+1].dXdt*heat[j].SA;
	    REAL freezeRate = LH/solute[waterIndex].properties.LatentHeatFusion(T);
	    // +ve for solid on left means ice growing
	    
	    if (pbt==1) {	// solid on left
	      subints[i][j+1].totalRate = -freezeRate;
	      solute[i].aqueous.amount += -freezeRate*dt;
	      
	      subints[i][j].totalSolid += freezeRate*dt;
	      solute[i].solid.amount += freezeRate*dt;
	      
	      heat[j+1].totalRate -= freezeRate*solute[i].properties.aCp*T;
	      heat[j].totalRate += freezeRate*solute[i].properties.sCp*T;
	    }
	    else {		// solid on right
	      subints[i][j+1].totalSolid = -freezeRate*dt;
	      solute[i].solid.amount += -freezeRate*dt;
	      
	      subints[i][j].totalRate += freezeRate;
	      solute[i].aqueous.amount += freezeRate*dt;
	      
	      heat[j+1].totalRate -= freezeRate*solute[i].properties.sCp*T;
	      heat[j].totalRate += freezeRate*solute[i].properties.aCp*T;
	    }
	    
	    heat[j+1].totalRate -= LH;	// LH<0 for dXdt>0 and solid on left
	  }
	  else
	    subints[i][j+1].totalRate = 0;
      
      else if (subints[waterIndex][j].SolidState()!=1)
	for(int i=0; i<nSolutes; i++) {
	  subints[i][j+1].totalRate = 
	    -subints[i][j].GetGradientI()*solute[i].properties.D;
	  subints[i][j].totalRate -= subints[i][j+1].totalRate;
	  
	  REAL hChange = subints[i][j+1].totalRate*
	    solute[i].properties.aCp*heat[j].GetValueI();
	  heat[j+1].totalRate += hChange;
	  heat[j].totalRate -= hChange;
	}
      
      else for(int i=0; i<nSolutes; i++)
	subints[i][j+1].totalRate = 0;
    }

    H += heat[j].totalRate*dt;
    REAL newHC=0;
    for(int i=0; i<nSolutes; i++) {
      subints[i][j].total += subints[i][j].totalRate*dt;
      newHC += subints[i][j].total*solute[i].properties.aCp
	+ subints[i][j].totalSolid*solute[i].properties.sCp
	+ subints[i][j].totalLiquid*solute[i].properties.lCp;
    }
    
    heat[j].HC = newHC;
    heat[j].total = heat[j].dx*H/newHC;

    heat[j].totalRate = (H/newHC - T0) / dt;
  }

  Compartment::FinalizeMovements(returnedSolution, returnedHeat);
  AddSolute(returnedSolution->GetSolutes(), -1);

  int dxAdjFlag=0;	// 1 indicates not to adjust any more dx's

/*
  // check nucleateFlag
  if ((!water->SolidStateInSubCompart(0))&&(nucleateFlag)) {
	if (nucleateFlag==1)	// planar ice formation
	  {
	  REAL dTdx = heat->GetAverageGradientInSubCompart(0,0);
	  REAL dFdx = 0;	// <<< PUT SOMETHING HERE
	  REAL L = water->GetLatentHeatFusion(heat->GetValueInSubCompartAt(0,0));

	  REAL dX = (water->GetProperties().lTC*dFdx - water->GetProperties().sTC*dTdx)
						* dt * water->GetProperties().sMV / L;

	  if (dX>0) {
#ifdef CHECKS
		printf("DCompartment::FinalizeMovements, ice nucleated, dX=%g\n",dX);
#endif

		REAL T = heat->GetValueInSubCompartAt(0,0);
		REAL dT = T - CalculateFreezingTemperatureAt(0,0,water->GetId());

		// figure out how much solute to freeze
//		REAL ratio = dT * water->GetProperties().lCp / water->GetProperties().dHf;
//		if (ratio>dParameters.dXmax*dParameters.heatN) ratio = dParameters.dXmax*dParameters.heatN;

		// combine compartment 0 into 1, but leave some solid in compartment 0
		REAL frozen=0;
		TSolute *sol=solutes;
		while (sol) {
		  REAL a, s, l, g;
		  ((TDSolute*)sol)->GetSoluteInSubCompart(0, a, s, l, g);

		  if (sol==water) {
			frozen = a * dX / water->GetWidthInSubCompart(0);
			((TDSolute*)sol)->AddSoluteInSubCompart(0, -a, -s+frozen, -l, -g);
			((TDSolute*)sol)->AddSoluteInSubCompart(1, a-frozen, s, l, g);
			}
		  else {
			((TDSolute*)sol)->AddSoluteInSubCompart(0, -a, -s, -l, -g);
			((TDSolute*)sol)->AddSoluteInSubCompart(1, a, s, l, g);
			((TDSolute*)sol)->ClearAlphaBetaInSubCompart(0);
			}
		  sol=sol->next;
		  }

		REAL latentHeat = -frozen*L;
		REAL heatForSolid = -frozen*water->GetProperties().sCp*dT;
		if (heatForSolid>latentHeat) heatForSolid = latentHeat;

		REAL dH = heat->GetTotalHeatInSubCompart(0);
		heat->AddHeatInSubCompart(0, -dH + frozen*water->GetProperties().sCp*T
											+ heatForSolid);
		heat->AddHeatInSubCompart(1, dH - frozen*water->GetProperties().lCp*T
											- heatForSolid + latentHeat);
		dxAdjFlag=1;
		}
	  }


	else if (nucleateFlag==-1) 	  // dendritic ice formation
	  {
	  REAL dT = heat->GetTotalTempInSubCompart(0) - CalculateFreezingTemperatureInSubCompart(0,water->GetId());
	  if (dT<0)
		ERROR("DCompartment::FinalizeMovements", "dendritic ice nucleation not supported");
	  }

	nucleateFlag=0;
	}
*/

  // dendritic ice formation
  for (int j=0; j<nSubInts; j++) {
    int ss=subints[waterIndex][j].SolidState();
    if ((ss==-1)||((ss==0)&&
		   (((j>0)&&
		     (subints[waterIndex][j-1].SolidState()==-1))||
		    ((j<nSubInts-1)&&
		     (subints[waterIndex][j+1].SolidState()==-1))))) {
      REAL T = heat[j].total/heat[j].dx;
      REAL F = CalculateFreezingTemperatureInSubInt(j, waterId);
      if ((ss)||(T<F)) {
	REAL HC = heat[j].HC;
	REAL L = solute[waterIndex].properties.LatentHeatFusion(F);
	REAL aMV = solute[waterIndex].properties.aMV;
	
	REAL v0 = 0;
	for(int i=0; i<nSolutes; i++)
	  v0 += subints[i][j].total*solute[i].properties.aMV;
	
	REAL C0dFdc=0;
	for(int i=0; i<nSolutes; i++)
	  if (i!=waterIndex) {
	    REAL C0 = subints[i][j].total/v0;
	    C0dFdc += C0*solute[i].properties.FPGradient(C0);
	  }
	
#ifdef CHECKS
	if (C0dFdc>0)
	  ERROR("DCompartment::FinalizeMovements", "C0dFdc positive");
	if (HC*v0*C0dFdc/L/aMV<0)
	  ERROR("DCompartment::FinalizeMovements", "sqrt error");
#endif
	
	REAL diff = F-T-C0dFdc;
	REAL f = (v0/aMV-HC*diff/L)/2
	  - sqrt(SQR(v0/aMV+HC*diff/L)/4 + HC*v0*C0dFdc/L/aMV);

	REAL a = subints[waterIndex][j].total;
	REAL s = subints[waterIndex][j].totalSolid;
	if ((f<0)&&(-f>s)) f=-s;
	else if (f>=a) {
#ifdef CHECKS
	  ERROR("DCompartment::FinalizeMovements", 
		"complete subinterval freeze");
	  fprintf(stderr,"  subinterval %d, s=%g, a=%g\n", j, s, a);
	  fprintf(stderr,"  T=%g, F=%g\n", T, F);
#endif
	  
	  f=a*0.99999;
	}
	
	subints[waterIndex][j].total -= f;
	solute[waterIndex].aqueous.amount -= f;
	
	subints[waterIndex][j].totalSolid += f;
	solute[waterIndex].solid.amount += f;
	
	subints[waterIndex][j].totalRate -= f/dt;
	heat[j].HC += f*(solute[waterIndex].properties.sCp -
			 solute[waterIndex].properties.aCp);
	
	REAL LatentHeat = -f*solute[waterIndex].properties.LatentHeatFusion(T);
	heat[j].total += LatentHeat*heat[j].dx/heat[j].HC;
	heat[j].totalRate += LatentHeat/heat[j].HC/dt;
	
#ifdef CHECKS
	REAL T1 = heat[j].total/heat[j].dx;
	REAL F1 = CalculateFreezingTemperatureInSubInt(j, waterId);
	if (ABS(T1-F1)>0.001) {
	  ERROR("DCompartment::FinalizeMovements", "dendrite ice error");
	  fprintf(stderr, "  T0=%g, F0=%g, f=%g, T1=%g, F1=%g\n", T, F, f, T1, F1);
	}
#endif
      }
    }
  }


  // adjust compartment widths
  X = -1;
  REAL dxIdeal = CalculateWidth() / nSubInts;
  for (int j=0; j<nSubInts-1; j++) {
    REAL dx0=GetWidthOfSubInt(j);
    if (PhaseBoundaryType(j)) {
      if ((dx0<dxIdeal/2)&&(j>0)) {			// too small
	// combine j into j-1
	for(int i=0; i<nSolutes; i++) {
	  subints[i][j-1].total += subints[i][j].total;
	  subints[i][j-1].totalSolid += subints[i][j].totalSolid;
	  subints[i][j-1].totalLiquid += subints[i][j].totalLiquid;
	  subints[i][j-1].dx += dx0;
	  subints[i][j].x += dx0;
	}
	REAL H = heat[j-1].total*heat[j-1].HC/heat[j-1].dx
	  + heat[j].total*heat[j].HC/heat[j].dx;
	heat[j-1].HC += heat[j].HC;
	heat[j-1].dx += dx0;
	heat[j].x += dx0;
	heat[j-1].total = H/heat[j-1].HC*heat[j-1].dx;
	
	// split j+1 into j and j+1
	for(int i=0; i<nSolutes; i++)
	  subints[i][j+1].DivideSubInterval(subints[i][j], subints[i][j+1]);
	heat[j+1].DivideSubInterval(heat[j], heat[j+1]);
	heat[j].dXdt = heat[j+1].dXdt;
	
	REAL newHC = GetHeatCapacityInSubInt(j);
	heat[j].total *= heat[j].HC / newHC;
	heat[j].HC = newHC;
	
	newHC = GetHeatCapacityInSubInt(j+1);
	heat[j+1].total *= heat[j+1].HC / newHC;
	heat[j+1].HC = newHC;
	
	dx0=GetWidthOfSubInt(j);
	dxAdjFlag=1;
      }
      
      else if ((dx0>7*dxIdeal/4)&&(j<nSubInts-2)) {	// too big
	// combine j+1 into j+2
	for(int i=0; i<nSolutes; i++) {
	  subints[i][j+2].total += subints[i][j+1].total;
	  subints[i][j+2].totalSolid += subints[i][j+1].totalSolid;
	  subints[i][j+2].totalLiquid += subints[i][j+1].totalLiquid;
	}
	REAL H = heat[j+1].total*heat[j+1].HC/heat[j+1].dx
	  + heat[j+2].total*heat[j+2].HC/heat[j+2].dx;
	heat[j+2].HC += heat[j+1].HC;
	heat[j+2].total = H/heat[j+2].HC*heat[j+2].dx;
	
	// split j into j and j+1
	for(int i=0; i<nSolutes; i++)
	  subints[i][j].DivideSubInterval(subints[i][j], subints[i][j+1]);
	heat[j].DivideSubInterval(heat[j], heat[j+1]);
	heat[j+2].dXdt = heat[j+1].dXdt;
	
	REAL newHC = GetHeatCapacityInSubInt(j);
	heat[j].total *= heat[j].HC / newHC;
	heat[j].HC = newHC;
	
	newHC = GetHeatCapacityInSubInt(j+1);
	heat[j+1].total *= heat[j+1].HC / newHC;
	heat[j+1].HC = newHC;
	
	dx0=GetWidthOfSubInt(j);
	dxAdjFlag=1;
      }
      
      if (X==-1) 
	X=heat[j].x+dx0;
    }
    
    else if ((!dxAdjFlag)&&((dx0<dxIdeal/2)||(dx0>3*dxIdeal/2))) {
      // no phase boundary

      REAL dx1=GetWidthOfSubInt(j+1);
      //REAL dxTotal=dx0+dx1;
      REAL dx0new=dx0 + (dx0<dx1 ? dxIdeal/10 : -dxIdeal/10);
      REAL volChange=0;
      REAL heatChange=0;
      REAL T;
      if (dx0new<dx0) 
	T=heat[j].GetValueAt((dx0new+dx0)/2);
      else 
	T=heat[j+1].GetValueAt((dx0new-dx0)/2);
      
      for(int i=0; i<nSolutes; i++) {
	REAL a, s, l;
	if (dx0new<dx0) {
	  a = -subints[i][j].GetTotalInRange(dx0new, dx0);
	  s = subints[i][j].totalSolid * (dx0new-dx0) / dx0;
	  l = subints[i][j].totalLiquid * (dx0new-dx0) / dx0;
	}
	else {
	  a = subints[i][j+1].GetTotalInRange(0, dx0new-dx0);
	  s = subints[i][j+1].totalSolid * (dx0new-dx0) / dx1;
	  l = subints[i][j+1].totalLiquid * (dx0new-dx0) / dx1;
	}
	
	subints[i][j].total += a;  subints[i][j].totalSolid += s;
	subints[i][j].totalLiquid += l;
	
	subints[i][j+1].total -= a;  subints[i][j+1].totalSolid -= s;
	subints[i][j+1].totalLiquid -= l;
	
	volChange += a*solute[i].properties.aMV + s*solute[i].properties.sMV
	  + l*solute[i].properties.lMV;
	
	heatChange += a*solute[i].properties.aCp + s*solute[i].properties.sCp
	  + l*solute[i].properties.lCp;
      }
      
      REAL oldHC = heat[j].HC;
      heat[j].HC += heatChange;
      heat[j].total = (heat[j].total*oldHC + heatChange*T*heat[j].dx)
	/ heat[j].HC;
      
      oldHC = heat[j+1].HC;
      heat[j+1].HC -= heatChange;
      heat[j+1].total = (heat[j+1].total*oldHC - heatChange*T*heat[j+1].dx)
	/ heat[j+1].HC;
      
      dx0 += volChange/heat[j].SA;
    }
    
    heat[j].total *= dx0/heat[j].dx;
    heat[j].dx = dx0;
    heat[j+1].x = heat[j].x + heat[j].dx;
    for(int i=0; i<nSolutes; i++) {
      subints[i][j].dx = dx0;
      subints[i][j+1].x = heat[j+1].x;
    }
  }
  
  REAL dx0=GetWidthOfSubInt(nSubInts-1);
  heat[nSubInts-1].total *= dx0/heat[nSubInts-1].dx;
  heat[nSubInts-1].dx = dx0;
  for(int i=0; i<nSolutes; i++)
    subints[i][nSubInts-1].dx = dx0;
}

void DCompartment::AddHeatAt(REAL pos, REAL inHeat) {
  int j=pos*nSubInts;
  if (j>=nSubInts) 
    j=nSubInts-1;
  heat[j].total += inHeat*heat[j].dx/heat[j].HC;
}

void DCompartment::AddSolutionAt(REAL pos, const Solution& inSolution) {
  AddSolute(inSolution.GetSolutes());
  int j = pos*nSubInts;
  if (j>=nSubInts) 
    j=nSubInts-1;
  
  for(int i=0; i<nSolutes; i++) {
    REAL a, s, l;
    inSolution.GetSolutes()[i].GetSolute(a, s, l);
    subints[i][j].total += a;
    subints[i][j].totalSolid += s;
    subints[i][j].totalLiquid += l;
  }

  REAL newHC = heat[j].HC + inSolution.GetHeatCapacity();
  heat[j].total *= heat[j].HC / newHC;
  heat[j].HC = newHC;
}

REAL DCompartment::CalculateFreezingTemperatureInSubInt(int j,int freezingSolute) const {
  REAL v=0;
  for(int i=0; i<nSolutes; i++)
    v += subints[i][j].total*solute[i].properties.aMV;

  REAL FP=0;
  for(int i=0; i<nSolutes; i++)
    if (solute[i].properties.id==freezingSolute)
      FP += solute[i].properties.FP;
    else if (v)
      FP += solute[i].properties.FPDepression(subints[i][j].total/v);
  
  return FP;
}

REAL DCompartment::CalculateFreezingTemperatureAt(int j,REAL x,
						  int freezingSolute) const {
  REAL FP=0;
  REAL volPerCM = GetAqueousVolPerCMInSubInt(j);
  for(int i=0; i<nSolutes; i++)
    if (solute[i].properties.id==freezingSolute)
      FP += solute[i].properties.FP;
    else if (volPerCM)
      FP += solute[i].properties.FPDepression(subints[i][j].GetValueAt(x)/
					      volPerCM);
  return FP;
}

REAL DCompartment::CalculateEstimatedFPInSubInt(int j, 
						int freezingSolute) const {
  REAL FP=0;
  REAL volPerCM = GetAqueousVolPerCMInSubInt(j);
  for(int i=0; i<nSolutes; i++)
    if (solute[i].properties.id==freezingSolute)
      FP += solute[i].properties.FP;
    else if (volPerCM)
      FP += solute[i].properties.FPDepression(subints[i][j].estConc/volPerCM);
  return FP;
}

REAL DCompartment::GetTotalEnthalpy() const {
  // don't call Compartment::GetTotalEnthalpy as T is not valid

  REAL H=0;
  for(int j=0; j<nSubInts; j++) {
    REAL T=heat[j].total/heat[j].dx;
    for(int i=0; i<nSolutes; i++)
      H += subints[i][j].totalSolid*solute[i].properties.sCp*T + 
	(subints[i][j].total+subints[i][j].totalLiquid)*
	(solute[i].properties.sCp*solute[i].properties.FP-
	 solute[i].properties.dHf) + 
	(subints[i][j].total*solute[i].properties.aCp+
	 subints[i][j].totalLiquid*solute[i].properties.lCp)*
	(T-solute[i].properties.FP);
  }
  
  Compartment *c = compartments;
  while(c) {
    H += c->GetTotalEnthalpy();
    c = c->next;
  }
  
  return H*compartment.nCompartments;
}

void DCompartment::DendriticBreakdown() {
  for (int j=0; j<nSubInts-1; j++) {
    int pbt = PhaseBoundaryType(j);
    if (pbt==1)	{	// solid on left
      if (subints[waterIndex][j+1].totalSolid==0) {
	REAL f = subints[waterIndex][j+1].total*1e-6;
	
	subints[waterIndex][j+1].total -= f;
	subints[waterIndex][j+1].totalSolid += f;
	solute[waterIndex].aqueous.amount -= f;
	solute[waterIndex].solid.amount += f;
	
	heat[j+1].HC += f*(solute[waterIndex].properties.sCp-
			   solute[waterIndex].properties.aCp);
	heat[j+1].total += -f*heat[j+1].dx/heat[j+1].HC*solute[waterIndex].
	  properties.LatentHeatFusion(heat[j+1].total/heat[j+1].dx);
	
#ifdef CHECKS
	fprintf(stderr,"DCompartment::DendriteBreakdown, dendrite breakdown at %d\n", j+1);
#endif
      }
    }

    else if (pbt==-1) {	// solid on right
      if (subints[waterIndex][j].totalSolid==0) {
	REAL f = subints[waterIndex][j].total/1000;
	
	subints[waterIndex][j].total -= f;
	subints[waterIndex][j].totalSolid += f;
	solute[waterIndex].aqueous.amount -= f;
	solute[waterIndex].solid.amount += f;
	
	heat[j].HC += f*(solute[waterIndex].properties.sCp-solute[waterIndex].
			 properties.aCp);
	heat[j].total += -f*heat[j].dx/heat[j].HC*solute[waterIndex].
	  properties.LatentHeatFusion(heat[j].total/heat[j].dx);
      }
    }
  }
}

REAL DCompartment::GetHeatCapacityInSubInt(int j) const {
  REAL HC=0;
  for(int i=0; i<nSolutes; i++)
    HC += subints[i][j].total*solute[i].properties.aCp
      + subints[i][j].totalSolid*solute[i].properties.sCp
      + subints[i][j].totalLiquid*solute[i].properties.lCp;
  return HC;
}

REAL DCompartment::GetVolumeInSubInt(int j) const {
  REAL V=0;
  for(int i=0; i<nSolutes; i++)
    V += subints[i][j].total*solute[i].properties.aMV
      + subints[i][j].totalSolid*solute[i].properties.sMV
      + subints[i][j].totalLiquid*solute[i].properties.lMV;
  return V;
}

REAL DCompartment::GetWidthOfSubInt(int j) const {
  return GetVolumeInSubInt(j)/heat[j].SA;
}

REAL DCompartment::GetAqueousVolumeInSubInt(int j) const {
  REAL V=0;
  for(int i=0; i<nSolutes; i++)
    V += subints[i][j].total*solute[i].properties.aMV;
  return V;
}

REAL DCompartment::GetAqueousVolPerCMInSubInt(int j) const {
  REAL V=0;
  for(int i=0; i<nSolutes; i++)
    V += subints[i][j].total*solute[i].properties.aMV;
  return V/heat[j].dx;
}

REAL DCompartment::CalculateWidth() {
  compartment.width=0;	// this could just use soluteContents.v
  for(int i=0; i<nSolutes; i++)
    compartment.width += solute[i].GetVolume() / compartment.SA;
/*
  if (dParameters.isSphere)
	{
	REAL r1 = 0;
	if (solution.Vt > solution.Vs)
	  r1 = exp (log (3 * (solution.Vt-solution.Vs) / (4*M_PI)) / 3);
	REAL r2 = exp (log (3 * (solution.Vt) / (4*M_PI)) / 3);
	return (r2-r1);
	}
*/
  return compartment.width;
}

/*
REAL DCompartment::CalculateSurfaceArea ()
  {
  // sample SA based on spherical shaped compartment
  if (dParameters.isSphere)
	{
	REAL r = exp (log (3 * solution.Vt / (4*M_PI)) / 3);
	return (4 * M_PI * r * r);
	}
  return compartment.SA;
  }
*/

const Solution& DCompartment::GetSolutionAt(REAL pos) {
  int j=pos*nSubInts;
  
  if (j>=nSubInts) {
    ERROR("DCompartment::GetSolutionAt", "end-point case not supported");
    // handle endpoint case
  }

  else {
    scratchSolution->EmptySolution();
    for(int i=0; i<nSolutes; i++) {
      scratchSolution->SetAmountForSolute(i, subints[i][j].total,
					  subints[i][j].totalSolid, 
					  subints[i][j].totalLiquid);
      scratchSolution->SetRateForSolute(i, subints[i][j].totalRate);
    }

    scratchSolution->SetTemperature(heat[j].total/heat[j].dx);
    scratchSolution->SetTemperatureRate(heat[j].totalRate/heat[j].dx);
  }
  
  return *scratchSolution;
}

void DCompartment::DisplayStatus(FILE *stream) const {
  fprintf(stream, "x(cm),h(cm),T(K),F(K)");
  for(int i=firstSolute; i<nSolutes; i++)
    fprintf(stream, ",C%d(mol/l)", i);
  fprintf(stream, ",state,dXdt(cm/sec)");
  for(int i=firstSolute; i<nSolutes; i++)
    fprintf(stream, ",dC%ddx(mol/ml/cm)", i);
  fprintf(stream, ",dFdx(K/cm),dTdx(K/cm)\n");
  
  for (int j=0; j<nSubInts; j++) {
    REAL x=heat[j].x;
    REAL dx=heat[j].dx;
    fprintf(stream, "%.05f,%.04f,%.03f", x, dx, heat[j].GetValueAt(0));
    // x (cm), dx (cm), T (K)
    
    fprintf(stream, ",%.03f", CalculateFreezingTemperatureAt(j, 0, waterId));
    // F (K)
    
    REAL volPerCM = GetAqueousVolPerCMInSubInt(j);
    if (volPerCM)
      for(int i=firstSolute; i<nSolutes; i++) {
	REAL C = subints[i][j].GetValueAt(0)/volPerCM;
	fprintf(stream, ",%.03f", C*1000);	  	// C (mol/l)
      }
    else
      for(int i=firstSolute; i<nSolutes; i++)
	fprintf(stream, ",");
    
    int ss=subints[waterIndex][j].SolidState();
    int pbt=(j>0 ? PhaseBoundaryType(j-1) : 0);
    
    char *state;
    switch (ss) {
    case 0:	state="aqueous";	break;
    case 1:	state="solid";		break;
    case -1:	state="dendrites";	break;
    }
    
    switch (pbt) {
    case 1:	fprintf(stream, ",phaseL");	break;
    case -1:	fprintf(stream, ",phaseR");	break;
    case 0:	fprintf(stream, ",%s", state);	break;
    }
    
    if (pbt) {
      fprintf(stream, ",%g",heat[j].dXdt);		// dXdt (cm/sec)
      
      REAL dFdx=0;
      for(int i=firstSolute; i<nSolutes; i++) {
	REAL C = subints[i][j].GetValueAt(0)/volPerCM;
	REAL dCdx = subints[i][j].GetGradientAt(0)/volPerCM;
	dFdx += dCdx*solute[i].properties.FPGradient(C);
	fprintf(stream, ",%g",dCdx);			// dCdx
      }
      
      fprintf(stream, ",%g",dFdx);				// dFdx (K/cm)
      fprintf(stream, ",%g",heat[j].GetGradientAt(0));
      // dTdx (K/cm)
    }
    
    fprintf(stream, "\n%.05f,,%.03f", x+dx/2, heat[j].GetValueAt(dx/2));
    // x (cm), dx (cm), T (K)
    
    fprintf(stream, ",%.03f", CalculateFreezingTemperatureAt(j,dx/2,waterId));
    // F (K)
    
    if (volPerCM)
      for(int i=firstSolute; i<nSolutes; i++) {
	REAL C = subints[i][j].GetValueAt(dx/2)/volPerCM;
	fprintf(stream, ",%.03f", C*1000);	  	// C (mol/l)
      }
    else
      for(int i=firstSolute; i<nSolutes; i++)
	fprintf(stream, ",");
    
    fprintf(stream, ",%s\n", state);
  }
  
  fprintf(stream, "\n");
}

void DCompartment::AddVariablePointerToList(uint32 index, uint32 variable) {
  void *p=0;
  switch(variable) {
  case 0x03000001:
    p = &X;						// phase boundary (cm)
    break;
    
  default:
    Compartment::AddVariablePointerToList(index, variable);
  }
  
  if (p)
    ::AddVariablePointerToList(index,p);
}

void* DCompartment::read(ipstream &is, uint32) {
  int ver = is.readWord16();
  if (ver!=102)
    ERROR("DCompartment::read", "invalid object version");
  
  Compartment::read(is);
  
  dParameters.isSphere = is.readWord16();	// boolean
  dParameters.diffuseId = is.readWord32();	// int
  nSubInts = is.readWord32();			// int
  
  dParameters.Herror = is.readDouble();
  dParameters.Serror = is.readDouble();
  dParameters.dXmax = is.readDouble();
  
  dParameters.initialConditions = is.readWord32();
  
  return this;
}

void DCompartment::write(opstream &os) const {
  os.writeWord16(102);	//version information
  
  Compartment::write(os);
  
  os.writeWord16((uint16)dParameters.isSphere);	 // boolean
  os.writeWord32(dParameters.diffuseId);	 // int
  os.writeWord32(nSubInts);			 // int

  os.writeDouble(dParameters.Herror);
  os.writeDouble(dParameters.Serror);
  os.writeDouble(dParameters.dXmax);

  os.writeWord32(dParameters.initialConditions);
}

/*
TStreamableClass RegDCompartment ("DCompartment", DCompartment::build, __DELTA(DCompartment));
*/
