#if !defined(__CRYOSIM_H)
#define __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/
 */

#if !defined(__STRING_H)
#include <string.h>
#endif

#if !defined(__MATH_H)
#include <math.h>
#endif

#if !defined(CLASSLIB_OBJSTRM_H)
//#include <classlib/objstrm.h>
#include "objstrm.h"
#endif

#define REAL 	double
#define INT 	int32
#define maxNameLen	32

class SoluteProperties;
class SoluteAmount;
class Solute;

struct SolutionContents;
class Solution;
typedef const Solution* CPSolution;

struct CompartmentContents;
class Compartment;

struct JWParameters;
class JWCompartment;

//class TJWMCompartment;

struct DParameters;
class DSubInterval;
class DCompartment;
typedef DSubInterval* PDSubInterval;

//struct DSoluteParms;
//class TDSolute;
//class TDHeat;
//class TDSoluteSubCompartment;


// Standard Units
//
// length		cm		centimeter
// SA			cm2		centimeter square
// volume		ml		millilitre
// temperature		K		kelvin
// heat			J		joule
// solute		mol		mole
// concentration	mol/ml		molar
// time			sec		second
// mass			g		gram

// Global Methods

void AddVariablePointerToList(uint32 index, void *p=0);
void WriteVariablePointersToStream(const Solution *solution, opstream &os);

template <class X> inline X SQR(X x)
  { return x*x; }

// Global variables

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


// Types

class SoluteProperties : public TStreamableBase {
 public:
  char name[maxNameLen];	// solute name
  int	 id;			// solute ID number (for comparison purposes)
  REAL MW,		// molecular weight (g/mol)
    FP,			// freezing point (pure) (K)
    dHf,		// latent heat of fusion at FP (J/mol)
    Tn;			// homogenous nucleation temperature (K)
  REAL D;		// diffusion coeff (cm2/sec)
  REAL a,		// phase diagram coefficient (K*ml/mol)
    b;         		// phase diagram coefficient (K*ml2/mol2)
  
  REAL aMV,		// aqueous molar volume (ml/mol)
    aCp,		// aqueous heat capacity (J/K/mol)
    aTC;		// aqueous thermal conductivity (J*cm/cm2/K/sec)
  
  REAL sMV,    		// solid molar volume (ml/mol)
    sCp,		// solid heat capacity (J/K/mol)
    sTC;		// solid thermal conductivity (J*cm/cm2/K/sec)
  
  REAL lMV,		// liquid molar volume (ml/mol)
    lCp,		// liquid heat capacity (J/K/mol)
    lTC;		// liquid thermal conductivity (J*cm/cm2/K/sec)
  
  REAL gMV,		// gas molar volume (ml/mol)
    gCp,		// gas heat capacity (J/K/mol)
    gTC;		// gas thermal conductivity (J*cm/cm2/K/sec)
  
  REAL vMV,		// glass molar volume (ml/mol)
    vCp,		// glass heat capacity (J/K/mol)
    vTC;		// glass thermal conductivity (J*cm/cm2/K/sec)
  
 public:
  SoluteProperties();
  virtual ~SoluteProperties() {}
  //	SoluteProperties(/*StreamableInit*/);
  //	static PTStreamable build();

  // C (mol/ml), returns freezing point depression (K)
  inline REAL FPDepression(REAL C) const
    { return a*C+b*C*C; }

  // C (mol/ml), returns freezing point gradient (K*ml/mol)
  inline REAL FPGradient(REAL C) const
    { return a+2*b*C; }
  
  // T (K), returns L (J/mol)
  inline REAL LatentHeatFusion(REAL T) const
    { return dHf + (sCp-lCp)*(T-FP); }
  
 protected:
  virtual void* read(ipstream&, uint32=0);
  virtual void write(opstream&) const;
  
 private:
  virtual const char* streamableName () const
    { return "SoluteProperties"; }
};


class SoluteAmount : public TStreamableBase {
 public:
  REAL amount;		// amount of solute (mol), or	       	[0x0010????]
  // when SoluteAmount is used as a flux (mol/sec)
  
  REAL rate;		// last known rate of change in amount	[0x0020????]
  // (mol/sec), or when SoluteAmount is used
  // as a flux (mol/sec2)
  
 public:
  SoluteAmount();
  //	~SoluteAmount() {}
  //	SoluteAmount(/*StreamableInit*/);
  //	static PTStreamable build();
  
 protected:
  virtual void* read(ipstream&, uint32=0)
    { return this; }
  virtual void write(opstream&) const {}
  
 private:
  virtual const char* streamableName () const
    { return "SoluteAmount"; }
};


class Solute : public TStreamableBase {
 public:
  const SoluteProperties &properties;		// properties of solute
  SoluteAmount aqueous, solid, liquid;		// amount of solute
  
 protected:
  Solution &solution;		// solution in which this solute is contained
  
 public:
  Solute(SoluteProperties& AProperties, Solution &ASolution);
  Solute(const Solute& ASolute, Solution &ASolution);
  //	virtual ~Solute () {}
  Solute(/*StreamableInit*/);
  //	static PTStreamable build();
  
  // amount (mol)
  inline void SetSolute(REAL aAmount=0, REAL sAmount=0, REAL lAmount=0)
    { aqueous.amount=aAmount; solid.amount=sAmount; liquid.amount=lAmount; }
  
  // rate (mol/sec)
  inline void SetRate(REAL aAmount=0, REAL sAmount=0, REAL lAmount=0)
    { aqueous.rate=aAmount; solid.rate=sAmount; liquid.rate=lAmount; }
  
  // volume (ml)
  void SetVolume(REAL aVolume=0, REAL sVolume=0, REAL lVolume=0);
  
  // mass (g)
  void SetMass(REAL aMass=0, REAL sMass=0, REAL lMass=0);
  
  //	inline void SetFlux(REAL aFlux=0, REAL sFlux=0, REAL lFlux=0, REAL gFlux=0)
  //	  { solid.flux = sFlux;		aqueous.flux = aFlux;
  //		liquid.flux = lFlux;	glass.flux = gFlux; }
  //	virtual void InitializeSimulation() {}
  
  // Simulation Methods
  
  // amount (mol)
  inline void AddSolute (REAL aAmount, REAL sAmount=0, REAL lAmount=0)
    { aqueous.amount+=aAmount; solid.amount+=sAmount; liquid.amount+=lAmount;}
  
  inline void AddSolute (const Solute &ASolute, REAL multiplier=1)
    { aqueous.amount+=ASolute.aqueous.amount*multiplier;
    solid.amount+=ASolute.solid.amount*multiplier;
    liquid.amount+=ASolute.liquid.amount*multiplier; }
  
  // Information Methods
  
  // based on entire solution, returns freezing temperature (K)
  REAL GetFreezingTemperature() const;
  
  // returns amount (mol)
  inline void GetSolute(REAL &aAmount, REAL &sAmount, REAL &lAmount) const
    {	sAmount=solid.amount; aAmount=aqueous.amount; lAmount=liquid.amount; }
  
  // returns volume (ml)
  void GetVolume(REAL &aVolume, REAL &sVolume, REAL &lVolume) const;
  
  // returns volume (ml)
  inline REAL GetAqueousVolume() const
    { return aqueous.amount*properties.aMV; }
  
  // returns aqueous (mol)
  inline REAL GetAqueous() const
    { return aqueous.amount; }
  
  // returns mass (g)
  void GetMass(REAL &aMass, REAL &sMass, REAL &lMass) const;
  
  // returns concentration (mol/ml)
  REAL GetAqueousConcentration() const;
  
  //	// returns concentration (mol/ml/sec)
  //	inline REAL GetAqueousConcentration2() const
  //	  { return aqueous.flux2; }
  
  // returns rate (mol/sec)
  inline void GetRate(REAL &aRate, REAL &sRate, REAL &lRate) const
    {	sRate = solid.rate;	aRate = aqueous.rate; lRate = liquid.rate; }
  
  // returns flux (mol/sec)
  inline REAL GetAqueousFlux() const
    { return aqueous.amount; }
  
  // returns flux rate (mol/sec2)
  inline REAL GetAqueousFluxRate() const
    { return aqueous.rate; }
  
  // returns volume (ml)
  inline REAL GetVolume() const
    { return aqueous.amount*properties.aMV + solid.amount*properties.sMV
	+ liquid.amount*properties.lMV; }
  
  // returns mass (g)
  inline REAL GetMass() const
    { return (aqueous.amount+solid.amount+liquid.amount)*properties.MW; }
  
  // returns HC (J/K)
  inline REAL GetHeatCapacity() const
    { return aqueous.amount*properties.aCp + solid.amount*properties.sCp
	+ liquid.amount*properties.lCp; }
  
  // T (K), returns enthalpy (J)
  inline REAL GetEnthalpy(REAL T) const
    { return solid.amount*properties.sCp*T + (aqueous.amount+liquid.amount)
	*(properties.sCp*properties.FP-properties.dHf) + 
	(aqueous.amount*properties.aCp+liquid.amount*properties.lCp)*
	(T-properties.FP); }
  
  // returns TC (J*cm3*cm/cm2/K/sec)
  REAL GetThermalConductivity() const;
  
  int AnyAqueous() const
    { return (aqueous.amount!=0); }
  
  inline const SoluteProperties& GetProperties() const
    { return properties; }
  
  // returns solute id (int)
  inline int GetId() const
    { return properties.id; }
  
  // returns solute name (char)
  inline const char* GetName() const
    { return properties.name; }
  
  inline Solution& GetSolution() const
    { return solution; }
  
  // Operators
  
  inline operator ==(const Solute& ASolute) const
    { return (properties.id==ASolute.properties.id); }
  inline operator !=(const Solute& ASolute) const
    { return (properties.id!=ASolute.properties.id); }
  inline operator <(const Solute& ASolute) const
    { return (properties.id<ASolute.properties.id); }
  inline operator >(const Solute& ASolute) const
    { return (properties.id>ASolute.properties.id); }
  
 protected:
  virtual void* read(ipstream&, uint32=0);
  virtual void write(opstream&) const;
  
  void ChangeProperties(SoluteProperties &AProperties);
  void ChangeSolution(Solution &ASolution);
  
  // Obsolete Methods
  
  //	void UpdateContents() const;
  //	void UpdateConcentration() const;
  //	virtual REAL CalculateFusionHeat (REAL outTemp);	// returns heat of fusion (J)
  //	virtual REAL CalculateGlassHeat (REAL outTemp);		// returns heat of glass transformation (J)
  //	virtual REAL CalculateSolutionHeat (REAL outTemp);	// returns heat of solution (J)
  
 private:
  virtual const char* streamableName () const
    { return "Solute"; }
  
  void AddVariablePointerToList(uint32 index, uint32 variable);
  
  friend Solution;
};


struct SolutionContents {
  REAL	Vs,	// volume of solution (ml)			[0x01000002]
    Va,		// volume of aqueous in solution (ml)
    Aq,		// total aqueous in solution (mol)
    ms,		// mass of solution (g)				[0x01000004]
    Cp;		// heat capacity of solution (J/K)
  
  REAL  F;	// freezing temperature (K)			[0x01000003]
};


//_CLASSDEF(Solution)
class Solution : public TStreamableBase {
 public:
  Solution *next, *previous;	// for making linked lists

 protected:
  REAL T;			// current temperature (K)	[0x01000001]
  REAL dTdt;			// rate of temp change (K/sec)
  
  Solute *solute;		// array of contained solutes (in order of id)
  int nSolutes;			// number of solutes in array
  int waterIndex;		// index in solute array of water solute
  
  char name[maxNameLen];		// solution name
  SolutionContents solutionContents;	// solution contents
  
 public:
  Solution(const char *Aname, REAL Atemp);
  Solution(const Solution& ASolution);
  virtual ~Solution();
  Solution(/*StreamableInit*/);
  //	static PTStreamable build()
  //	  { return new TSolution (streamableInit);  };
  
  virtual void InitializeSimulation();
  
  void EmptySolution();
  
  //	Solute* CombineSolutes(TSolute *dest, const TSolute *source, int subtract=0);
  //	virtual void AddSolutes (const TSolute *AList);
  //	virtual void RemoveSolutes (const TSolute *AList);
  
  //	virtual TSolute* CreateSoluteOfType(const TSolute &type)
  //	  { return new TSolute(type, this); }
  
  void AddSolute(const Solute *AList, REAL multiplier=1);
  
  void InsertSolute(const Solute& aSolute);
  
  inline void SetName(const char* AName) {
    if (AName) 
      strcpy(name, AName); 
  }
  
  // Simulation Methods
  
  // temperature (K)
  inline void SetTemperature(REAL aT) {
    T=aT; 
  }
  
  // temperature rate (K/sec)
  inline void SetTemperatureRate(REAL adTdt) {
    dTdt=adTdt; 
  }
  
  // solute (mol)
  inline void SetAmountForSolute(int index, REAL aAmount=0, 
				 REAL sAmount=0, REAL lAmount=0) {
    solute[index].SetSolute(aAmount, sAmount, lAmount);
    UpdateSolutionContents(); 
  }
  
  // rate (mol/sec)
  inline void SetRateForSolute(int index, REAL aRate=0, REAL sRate=0, 
			       REAL lRate=0) {
    solute[index].SetRate(aRate, sRate, lRate); 
  }
  
  // flux (mol/sec)
  inline void SetFluxForSolute(int index, REAL aFlux=0, REAL sFlux=0, 
			       REAL lFlux=0) {
    solute[index].SetSolute(aFlux, sFlux, lFlux); 
  }
  
  // flux rate (mol/sec2)
  inline void SetFluxRateForSolute(int index, REAL aRate=0, REAL sRate=0, 
				   REAL lRate=0) {
    solute[index].SetRate(aRate, sRate, lRate); 
  }
  
  // dt (sec)
  void ChangeFluxToSolute(REAL dt);
  
  // Informational Methods
  
  // return volume (ml)
  inline REAL GetVolume() const
    { return solutionContents.Vs; }

  // return mass (g)
  inline REAL GetMass() const
    { return solutionContents.ms; }
  
  // returns volume (ml)
  inline REAL GetAqueousVolume() const
    { return solutionContents.Va; }
  
  // returns aqueous (mol)
  inline REAL GetAqueous() const
    { return solutionContents.Aq; }
  
  // returns 1 if some aqueous exists in solution
  inline REAL AnyAqueous() const
    { return (solutionContents.Aq>0); }
  
  // returns heat capacity (J/K)
  inline REAL GetHeatCapacity() const
    { return solutionContents.Cp; }
  
  // returns enthalpy (J)
  virtual REAL GetTotalEnthalpy() const;
  
  // return volume (ml)
  virtual REAL GetTotalVolume() const
    { return solutionContents.Vs; }
  
  // return mass (g)
  virtual REAL GetTotalMass() const
    { return solutionContents.ms; }
  
  // returns temperature (K)
  inline REAL GetTemperature() const
    { return T; }
  
  // returns temperature rate (K/sec)
  inline REAL GetTemperatureRate() const
    { return dTdt; }
  
  // returns array of solutes
  inline const Solute* GetSolutes() const
    { return solute; }
  
  // returns size of array of solutes
  inline int GetNumberOfSolutes() const
    { return nSolutes; }
  
  // returns freezing temperature of water (K)
  inline REAL GetFreezingTemperature() const
    { return solutionContents.F; }
  
  // returns freezing temperature (K)
  REAL GetFreezingTemperature(int freezingSoluteId) const;
  
  // returns osmotic pressure (atm)
  inline REAL GetOsmoticPressure() const {
    return T*solute[waterIndex].properties.dHf*
      (solutionContents.F-solute[waterIndex].properties.FP)*9.8717/*ml*atm/J*/
      /(solute[waterIndex].properties.FP*solutionContents.F
	*solute[waterIndex].properties.aMV); 
  }
  
  // returns osmotic pressure (atm)
  inline REAL GetOsmoticPressure(int soluteIndex) const {
    if (solutionContents.Va) {
      REAL FD = solute[soluteIndex].properties.
	FPDepression(solute[soluteIndex].GetAqueousConcentration());
      return T*solute[waterIndex].properties.dHf*FD*9.8717/*ml*atm/J*/
	/(solute[waterIndex].properties.FP*
	  (solute[waterIndex].properties.FP+FD)*
	  solute[waterIndex].properties.aMV); }
    return 0; 
  }
  
  // returns index to solute or -1 if not found
  int FindSolute(int soluteId) const;
  
  inline const char* GetName() const	// returns solution name
    { return name; }
  
  //	virtual const Pchar GetClassName () const
  //	  { return "TSolution"; };
  
  // Obsolete Methods
  
  //	int IsNucleated (TSolute *ASolute) {return 0;}	// 0-no, 1-yes
  
 protected:
  virtual void* read(ipstream&, uint32=0);
  virtual void write(opstream&) const;
  
  virtual void AddVariablePointerToList(uint32 index, uint32 variable);
  
  void UpdateSolutionContents();
  
 private:
  virtual const char* streamableName () const
    { return "Solution"; }
};



struct CompartmentContents {
  long	nCompartments;	// number of compartments to simulate
  REAL	width,		// width of compartment (cm)		[0x02000001]
    SA;			// surface area (cm2)	  		[0x02000002]
  
  REAL	pos;		// position in parent compartment as fraction of width,
  // measured from outside boundary				[0x02000003]
};


class Compartment : public Solution {
 public:
  Compartment *next, *previous;	// pointers to sibling compartments
  
 protected:
  Compartment *parent;		// pointer to parent compartment
  Compartment *compartments;	// linked list of contained compartments
  
  Solution *scratchSolution;	// used for outgoing solutes and other internal
  //   calculations
  
  CompartmentContents compartment;
  
  REAL heatFlux;		// outgoing heat flux (J/sec)
  REAL heatFluxRate;		// outgoing heat flux rate (J/sec2)
  
 public:
  Compartment(Compartment *AParent);
  virtual ~Compartment();
  Compartment(/*StreamableInit*/);
  //	static PTStreamable build()
  //	  {  return new TCompartment (streamableInit);  };
  
  virtual void AddCompartment(Compartment *ACompartment);
  
  virtual void InitializeSimulation();

  inline void SetCompartment(CompartmentContents &cc)
    { compartment=cc; }
  
  // Simulation Methods
  
  // step 1 -- Calculate Transport Rates
  //	a solution is imposed on the compartment and the compartment must
  //	calculate the required transport rates between this compartment and
  //	its parent
  virtual void DoRateCalculations(const Solution& imposedSolution);
  
  // step 2 -- Finalize Transport
  virtual void FinalizeMovements(CPSolution &returnedSolution, 
				 REAL &returnedHeat);
  
  virtual void AddHeatAt(REAL pos, REAL inHeat)
    { T += inHeat/GetHeatCapacity(); }
  
  virtual void AddSolutionAt(REAL pos, const Solution& inSolution) {
    AddSolute(inSolution.GetSolutes());
    AddHeatAt(pos, inSolution.GetHeatCapacity()*
	      (inSolution.GetTemperature()-T));
  }
  
  // Information Methods
  
  // returns solution flux (mol/sec) into this child from parent
  const Solution& GetSolutionFlux()
    { return *scratchSolution; }
  
  // returns heat flux (J/sec) into this child from parent
  REAL GetHeatFlux()
    { return heatFlux*compartment.nCompartments; }
  
  // returns heat flux2 (J/sec2) into this child from parent
  REAL GetHeatFluxRate()
    { return heatFluxRate*compartment.nCompartments; }
  
  virtual const Solution& GetSolutionAt(REAL pos)
    { return *this; }
  
  // returns volume (cm3) * nCompartments
  virtual REAL GetTotalVolume() const;
  
  // returns mass (g) * nCompartments
  virtual REAL GetTotalMass() const;
  
  // returns enthalpy (J) * nCompartments
  virtual REAL GetTotalEnthalpy() const;
  
  // returns position as fraction of compartment width
  inline REAL GetPosition() const
    { return compartment.pos; }
  
  inline const Compartment* GetCompartments()
    { return compartments; }
  
  //	virtual const Pchar GetClassName () const
  //	  { return "TCompartment"; };
  
  // Obsolete Methods
  
  //	virtual TSolute* ImposeSolutes(const TSolute *AList);	//returns solutes transferred * nCompartments
  //	virtual REAL ImposeTemperature(REAL outTemp);	//returns heat transferred (J) * nCompartments
  
 protected:
  virtual void* read(ipstream&, uint32=0);
  virtual void write(opstream&) const;
  
  virtual void AddVariablePointerToList(uint32 index, uint32 variable);
  
  virtual REAL CalculateSurfaceArea();
  virtual REAL CalculateWidth();
  
  // Obsolete Methods
  
  //	virtual void CreateSolute (TSolute *ASolute);
  //	virtual TSolute *CalculateSoluteTransfer(const TSolute *AList);
  //returns solutes transferred
  //	virtual REAL CalculateHeatTransfer(REAL outTemp);
  //returns heat transferred
  
 private:
  virtual const char* streamableName () const
    { return "Compartment"; }
};


struct JWParameters {
  REAL	Lpg;		// (cm3/cm2/atm/sec)			[0x03000001]
  REAL	LpgTg;		// (K)
  REAL	LpgEa;		// (J/mol)				[0x03000002]
  
  REAL	Psg;		// (cm/sec)				[0x03000004]
  REAL	PsgTg;		// (K)
  REAL	PsgEa;		// (J/mol)				[0x03000005]
  
  REAL	sigma;		//					[0x03000007]
  
  REAL	Verror;		// (fraction error/sec)	whodt=0x00010001
  REAL	Serror;		// (fraction error/sec)	whodt=0x00010002
  REAL	Herror;		// (fraction error/sec)	whodt=0x00010003

  int	isSphere;	//specifies surface area based on sphere

  long	permeantId,	//
    impermeantId;	//

  REAL	lambda;		// thermal conductivity (through membrane) 
  // (J/cm2/K/sec)
};


//_CLASSDEF(TJWCompartment)
class JWCompartment : public Compartment {
 protected:
  int permeantIndex, impermeantIndex;
  
  REAL dVdt;		// ml/sec				[0x03000101]
  REAL dSdt;		// mol/sec				[0x03000102]
  
 public:
  JWParameters jwParameters;
  
  // Initialization Methods
  
  JWCompartment(Compartment *AParent);
  JWCompartment(/*StreamableInit*/);
  //	static PTStreamable build()
  //	  {  return new JWCompartment (streamableInit);  };
  
  virtual void InitializeSimulation();
  
  // Simulation Methods

  // step 1
  virtual void DoRateCalculations(const Solution& imposedSolution);
  // step 2
  virtual void FinalizeMovements(CPSolution &returnedSolution, 
				 REAL &returnedHeat);
  
  void Nucleate();

  // Information Methods
  
  inline static REAL Arrhenius(REAL Parm, REAL Ea, REAL T, REAL Tg)
    { return Parm * exp(Ea*(1/Tg-1/T)/8.315); }
  
  //	virtual const Pchar GetClassName () const
  //	  { return "TJWCompartment"; };
  
 protected:
  virtual void* read(ipstream&, uint32=0);
  virtual void write(opstream&) const;
  
  virtual void AddVariablePointerToList(uint32 index, uint32 variable);
  
  virtual REAL CalculateSurfaceArea();
  
 private:
  virtual const char* streamableName () const
    { return "JWCompartment"; }
};


/* NOTE: SubCompartment's are used for both TDSolute and TDHeat and thus units
 *	may be quoted for both solutes and heat, in which case, solute units
 *	are listed first while heat units are listed second
 * NOTE: outer subcompartment boundary is on the left 
 */
class DSubInterval : public TStreamableBase {
 public:
  REAL alpha,	// quadratic fit paramter, quadratic term (mol/cm3) or (K/cm2)
    beta,	// quadratic fit paramter, linear term (mol/cm2) or (K/cm)
    //gamma,	// quadratic fit paramter, constant term (mol/cm) or (K)
    total,	// quadratic fit paramter, total aqueous solute (mol) or (K*cm)
    dAlpha;     // dAlpha/dt (mol/cm3/sec) or (K/cm2/sec)
  REAL P,	// internal parameter, slope term (cm)
    Q,          // internal parameter, constant term (mol/cm2) or (K/cm)
    q;		// internal parameter, dQ/dt (mol/cm2/sec) or (K/cm/sec)
  REAL M,       // internal parameter, freezing point slope (K*sec/cm)
    N;		// internal parameter, freezing point constant (K)
  
  REAL x;	// distance from outer compartment boundary to
  //	outer subinterval boundary (cm)
  REAL dx;	// width of subinterval (cm)
  REAL SA;	// surface area at inner subinterval boundary (cm2)
  
  REAL dXdt;	// speed of phase change at outer subinterval boundary (cm/sec)
  
  REAL estConc;	// estimated concentration at outer subinterval boundary (mol/cm)
  
  // things that don't diffuse
  REAL totalSolid,	// amount of solid (mol), only for solute subinterval
    totalLiquid;	// amount of liquid (mol), only for solute subinterval
  
  REAL totalRate;	// rate of change in total, (mol/sec) or (K/sec)
  
  REAL HC;		// heat capacity (J/K), only for heat subinterval
  
  REAL Lrho;		// rate of heat release at interface (J/ml), heat only
  
 public:
  DSubInterval();
  //	~DSubInterval() {}
  
  // Simulation Methods
  
  // gradient (mol/cm2) or (K/cm)
  inline void InitialPQGradient(REAL gradient)
    { P = dx; Q = gradient; }
  
  // gradient (mol/cm2) or (K/cm)
  inline void InitialPQGradientAdd(REAL gradient)
    { P = dx; Q += gradient; }
  
  // dGdt (mol/cm2/sec) or (K/cm/sec)
  inline void InitialdQGradient(REAL dGdt)
    { q = dGdt; }
  
  // dGdt (mol/cm2/sec) or (K/cm/sec)
  inline void InitialdQGradientAdd(REAL dGdt)
    { q += dGdt; }
  
  // i1 is the next subinterval
  void CalculatePQ(DSubInterval &i1);
  
  // D (cm2/sec)
  void CalculatedQ(DSubInterval &i1, REAL D0, REAL D1)
    { q = (-12*(i1.dx+i1.P)*(D0*alpha-D1*i1.alpha) - 2*SQR(i1.dx)*i1.q)
	/ (3*(i1.dx+i1.P)*(i1.dx+dx) - 2*SQR(i1.dx)); }
  
  // heat only, solid on left, i1 is next subinterval,
  // N (K), Ks and Kl (J*cm/cm2/K/sec), M (K*sec/cm),
  void CalculatePQXL(DSubInterval &i1, REAL N, REAL Ks, REAL Kl, REAL M=0);
  
  // heat only, solid on left,
  // Ks and Kl (J*cm/cm2/K/sec), dNdt (K/sec), D (cm2/sec),
  inline void CalculatedQXL(DSubInterval &i1, REAL Ks, REAL Kl, REAL dNdt,
			    REAL D0, REAL D1) {
    q = (-2*i1.Lrho*i1.dx*(i1.dx+3*i1.P)*(dNdt-2*D0*alpha) 
	 +4*Kl*M*SQR(i1.dx)*i1.q
	 + 24*Kl*M*(i1.dx+i1.P)*(D0*alpha-D1*i1.alpha))
      / (-i1.Lrho*dx*i1.dx*(i1.dx+3*i1.P) - 2*Ks*M*i1.dx*(i1.dx+3*i1.P) 
	 -6*Kl*M*dx*(i1.dx+i1.P)); 
  }
  
  // solute only, returns concentration (mol/cm)
  inline REAL EstimateConcentrationL()
    { estConc = dx*dx*alpha/6 - dx*beta/2 + total/dx;
    if (estConc<0) estConc=0;
    return estConc; }
  
  // solid on left, solute only,
  // A (K*cm/mol), C (mol/cm), D(cm2/sec)
  void CalculateAndAddMNL(REAL &M, REAL &N, REAL A, REAL C, REAL D);
  
  // A (K*cm/mol), D (cm2/sec), returns dNdt (K/sec)
  inline REAL CalculatedNL(REAL A, REAL D)
    { return -A*dx*dx*q/(3*(dx+P)) + 2*A*D*alpha; }
  
  // value (mol/cm) or (K)
  void InitialAlphaBetaFixed(REAL value);
  
  // dCdt (mol/cm/sec) or (K/sec), D (cm2/sec),
  // returns dAlphadt (mol/cm3/sec) or (K/cm2/sec)
  inline REAL InitialdAlphaFixed(REAL dCdt, REAL D)
    { return dAlpha = 3*(q + (2*dCdt-4*D*alpha)/dx)/(dx+3*P); }
  
  // gradient (mol/cm2) or (K/cm)
  inline void InitialAlphaBetaGradient(REAL gradient)
    { alpha = (Q-gradient)/(P+dx);
    beta = (dx*Q+P*gradient)/(P+dx); }
  
  // dGdt (mol/cm2/sec) or (K/cm/sec),
  // returns dAlpha (mol/cm3/sec) or (K/cm2/sec)
  inline REAL InitialdAlphaGradient(REAL dGdt)
    { return dAlpha = (q-dGdt)/(dx+P); }
  
  // i0 is previous subinterval
  void CalculateAlphaBeta(DSubInterval &i0);
  
  // D (cm2/sec),
  // returns dAlphadt (mol/cm3/sec) or (K/cm2/sec)
  inline REAL CalculatedAlpha(DSubInterval &i0, REAL D0, REAL D1) {
    return dAlpha = (-2*SQR(i0.dx)*i0.dAlpha + 3*(i0.dx+dx)*q 
		     +12*(D0*i0.alpha-D1*alpha))
      / (3*(dx+P)*(i0.dx+dx)-2*SQR(dx)); 
  }
  
  // heat only, solid on left, i0 is previous subinterval,
  // Ks and Kl (J*cm/cm2/K/sec), returns dXdt (cm/sec),
  REAL CalculateAlphaBetaXLh(DSubInterval &i0, REAL Ks, REAL Kl);
  
  // heat only, solid on left, i0 is previous subinterval,
  // Ks and Kl (J*cm/cm2/K/sec),
  // dNdt (K/sec), D (cm2/sec), returns dAlpha (K/cm2/sec)
  inline REAL CalculatedAlphaXLh(DSubInterval &i0, REAL Ks, REAL Kl,
				 REAL dNdt, REAL D0, REAL D1) {
    return dAlpha = (-6*Lrho*i0.dx*(dNdt-2*D1*alpha) - 3*Lrho*i0.dx*dx*q 
		     +4*Ks*M*SQR(i0.dx)*i0.dAlpha
		     -24*Ks*M*(D0*i0.alpha-D1*alpha) - 6*M*(Kl*i0.dx+Ks*dx)*q)
      / (-Lrho*i0.dx*dx*(dx+3*P) - 2*Ks*M*dx*(dx+3*P) - 6*Kl*M*i0.dx*(dx+P)); 
  }
  
  // solute only, solid on left, dXdt (cm/sec), D (cm2/sec)
  void CalculateAlphaBetaXLs(REAL dXdt, REAL D);

  // solute only, solid on left, d2Xdt (cm/sec2), D (cm2/sec),
  // returns dAlpha (mol/cm3/sec)
  inline REAL CalculatedAlphaXLs(REAL d2Xdt, REAL D)
    { return dAlpha = (q + estConc*d2Xdt/D) / (dx+P); }
  
  // heat only, solid on left, i0 is previous subinterval,
  // Ks and Kl (J*cm/cm2/K/sec), dNdt (K/sec), D (cm2/sec),
  // returns d2Xdt (cm/sec2)
  inline REAL Calculated2Xdt(DSubInterval &i0, REAL Ks, REAL Kl, REAL dNdt,
			     REAL D0, REAL D1) {
    return (2*Ks*SQR(i0.dx)*dx*(dx+3*P)*i0.dAlpha/3 + 
	    2*Ks*dx*(dx+3*P)*(dNdt-2*D0*i0.alpha)
	    +2*Kl*i0.dx*SQR(dx)*q + 6*Kl*i0.dx*(dx+P)*(dNdt-2*D1*alpha))
      / (-Lrho*i0.dx*dx*(dx+3*P) - 2*Ks*M*dx*(dx+3*P) - 6*Kl*M*i0.dx*(dx+P)); 
  }
  
  // divides subinterval evenly between i0 and i1
  void DivideSubInterval(DSubInterval &i0, DSubInterval &i1);
  
  // Information Methods

  // x (cm) from outer boundary, returns gradient (mol/cm2) or (K/cm)
  inline REAL GetGradientAt(REAL x) const
    { return 2*alpha*(x-dx/2) + beta; }
  
  // returns gradient (mol/cm2) or (K/cm) at inner boundary
  inline REAL GetGradientI() const
    { return alpha*dx + beta; }
  
  // x (cm) from outer boundary, returns value (mol/cm) or (K)
  inline REAL GetValueAt(REAL x) const
    { return alpha*(x*x-x*dx+dx*dx/6)+beta*(x-dx/2)+total/dx; }
  
  // returns value (mol/cm) or (K) at inner boundary
  inline REAL GetValueI() const
    { return alpha*dx*dx/6 + beta*dx/2 + total/dx; }
  
  // x0, x1 (cm), returns total (mol) or (K*cm)
  inline REAL GetTotalInRange(REAL x0, REAL x1) {
    return (x1-x0)*(alpha*(x1*x1+x1*x0+x0*x0)/3 - (alpha*dx-beta)*(x1+x0)/2
		    + (alpha*dx/3-beta)*dx/2 + total/dx); 
  }
  
  // returns 0=no solid, 1=no aqueous, -1=otherwise (ie. dendrities)
  inline int SolidState() const
    { return totalSolid ? (total ? -1 : 1) : 0; }
  
 protected:
  virtual void* read(ipstream&, uint32=0)
    { return this; }
  virtual void write(opstream&) const {}
  
 private:
  virtual const char* streamableName () const
    { return "DSubInterval"; }
};


struct DParameters {
  int	isSphere;		// specifies surface area based on sphere
  int	diffuseId;		// id of diffusing solute
  
  REAL	Herror;			// (fraction error/sec)	whodt=0x00020100
  REAL  Serror;			// (fraction error/sec)	whodt=0x00020200
  REAL  dXmax;			// as fraction of width whodt=0x00020300
  
  int initialConditions;	// 1=all solid, no aqueous in first subcompartment
};


//_CLASSDEF(TDCompartment)
class DCompartment : public Compartment {
 public:
  DParameters dParameters;
  
 protected:
  int nSubInts;		// number of subintervals for each solute and heat
  PDSubInterval* subints;	// nSolute arrays of dParameters.N subintervals
  DSubInterval*  heat;		// array of dParameters.N subintervals for heat
  
  int firstSolute;		// first solute that diffuses, should be 0
  
  // 0=don't nucleate, 1=nucleate planar ice, -1=nucleate dendrites
  int nucleateFlag;
  
  REAL	X; 	// position of first phase boundary (cm)	[0x03000001]
  
 public:
  DCompartment(Compartment *AParent);
  DCompartment(/*StreamableInit*/);
  //	static PTStreamable build()
  //	  {  return new TDCompartment (streamableInit);  };
  ~DCompartment();
  
  virtual void InitializeSimulation();

  //	virtual TSolute* CreateSoluteOfType(const TSolute &type)
  //	  { return new TDSolute(type, this); }
  
  //	virtual void AddSolutes (const TSolute *AList);
  //	virtual void RemoveSolutes (const TSolute *AList);
  
  inline void SetNumberOfSubInts(int n)
    { nSubInts = n; }
  
  // Simulation Methods
  
  // step 1
  virtual void DoRateCalculations(const Solution& imposedSolution);
  
  // step 2
  virtual void FinalizeMovements(CPSolution &returnedSolution, 
				 REAL &returnedHeat);
  
  // inHeat (J)
  virtual void AddHeatAt(REAL pos, REAL inHeat);
  
  virtual void AddSolutionAt(REAL pos, const Solution& inSolution);
  
  // heat capacity (J/K)
  //	inline void AddHeatCapacityInSubInt(int index, REAL HC)
  //	  { heat->AddHeatCapacityInSubInt(index, HC); }
  
  REAL CalculateFreezingTemperatureInSubInt(int index, 
					    int freezingSolute) const;
  
  REAL CalculateFreezingTemperatureAt(int index, REAL x, 
				      int freezingSolute) const;
  
  REAL CalculateEstimatedFPInSubInt(int index, int freezingSolute) const;
  
  void DendriticBreakdown();

  // Information Methods
  
  //	virtual REAL GetTemperatureAt(REAL pos)
  //	  { return pos==1 ? heat->GetValueInSubCompartI(dParameters.heatN-1)
  //			: heat->GetTotalTempInSubCompart(pos*dParameters.heatN); }
  //	virtual REAL GetTemperatureRateAt(REAL pos)
  //	  { return 0; }
  //	virtual const TSolute* GetSolutesAt(REAL pos);
  
  virtual const Solution& GetSolutionAt(REAL pos);
  
  // returns heat capacity (J/K)
  REAL GetHeatCapacityInSubInt(int index) const;
  
  // returns volume (ml)
  REAL GetVolumeInSubInt(int index) const;
  
  // returns volume (ml)
  REAL GetAqueousVolumeInSubInt(int index) const;
  
  // returns volume/cm (ml/cm)
  REAL GetAqueousVolPerCMInSubInt(int j) const;
  
  // returns volume (cm)
  REAL GetWidthOfSubInt(int j) const;
  
  // returns enthalpy (J)
  virtual REAL GetTotalEnthalpy() const;
  
  //	virtual const Pchar GetClassName () const
  //	  { return "TDCompartment"; };
  
  // j<nSubInts-1, returns 1 for solid on left, -1 for solid on right,
  // 0 for no boundary
  inline int PhaseBoundaryType(int j) const {
    int i0=subints[waterIndex][j].SolidState();
    int i1=subints[waterIndex][j+1].SolidState();
    if (((i0!=1)&&(i1!=1))||(i0==i1)) 
      return 0;
    return i0==1 ? 1 : -1; 
  }
  
  void DisplayStatus(FILE *stream) const;
  
  // Obsolete Methods
  
 protected:
  virtual void* read(ipstream&, uint32=0);
  virtual void write(opstream&) const;
  
  virtual void AddVariablePointerToList(uint32 index, uint32 variable);
  
  virtual REAL CalculateWidth ();
  
  // Obsolete Methods
  
  //	virtual REAL CalculateSurfaceArea ();
  
 private:
  virtual const char* streamableName () const
    { return "DCompartment"; }
};


#endif
