Example #1
0
void FilterPress::EvalProducts()
{
	try {
		int idPressNote = 0, idWashNote = 1;
		MStreamI QFeed;
		FlwIOs.AddMixtureIn_Id(QFeed, idFeed);
		const double dFeedSolidMass = QFeed.Mass(MP_Sol);
		const double dFeedLiquidMass = QFeed.Mass(MP_Liq);

    MStreamI QWashWater;
		const bool bWashWaterConnected = (FlwIOs.getCount(idWash) > 0);
		if (bWashWaterConnected)
			FlwIOs.AddMixtureIn_Id(QWashWater, idWash);

		//MStream QUnwashedCake = QFeed; BAD idea, QUnwashedCake becomes a reference to QFeed!!!!
		MStreamI QUnwashedCake;
    QUnwashedCake = QFeed;
		QUnwashedCake.ZeroMass();

    MStream&	QFiltrate	= FlwIOs[FlwIOs.First[idFiltrate]].Stream;
    QFiltrate	= QFeed;
    MStream&	QCake		= FlwIOs[FlwIOs.First[idCake]].Stream;
    QCake		= QFeed;

		const bool bWashingsConnected = (FlwIOs.Count[idWashings] > 0);

		//First off: The filtrate & UnwashedCake:

		/*Equations Solved using Mathematica 6.0, under the constrainsts of:
		 - Conservation of mass
		 - Cake Moisture Content
		 - Filtrate Solid Concentration [Either in % or g/L]
			- When using g/L, assume solid and liquid densities don't change [The (Aq) conc's don't]
		*/
		double dSolConcConstFac, dLiqConcConstFac;
		bool bTooWet;
		switch (eFiltrateMethod) {
		case FM_SolidsToFiltrateFrac:
			dSolConcConstFac = dReqSolidsToFiltrate;
			dLiqConcConstFac = dReqSolidsToFiltrate;
			bTooWet = dFeedSolidMass / NZ(dFeedSolidMass + dFeedLiquidMass) < dReqSolidsToFiltrate;
			break;
		case FM_FiltrateConc:
			dSolConcConstFac = dReqFiltSolConc / QFeed.Density(MP_Sol, C_2_K(25));
			dLiqConcConstFac= dReqFiltSolConc / QFeed.Density(MP_Liq, C_2_K(25));
			bTooWet = dFeedSolidMass / NZ(QFeed.Volume(MP_SL, C_2_K(25))) < dReqFiltSolConc;
			break;
		}
		/*Situations where variables can be out of range:
		 - Mass comes in too wet - required loss causes all mass to be sent to filtrate
		 - Mass comes in too dry - mass is drier than required moisture frac.
		*/
		double dFiltSolidFactor, dFiltLiquidFactor, dCakeSolidFactor, dCakeLiquidFactor;
		if (bTooWet)
		{
			SetNote(idPressNote, "Input feed too wet: All feed solids sent to filtrate");
			dFiltSolidFactor = dFiltLiquidFactor = 1;
			dCakeSolidFactor = dCakeLiquidFactor = 0;
		}
		else if (dFeedLiquidMass / NZ(dFeedSolidMass + dFeedLiquidMass) < dReqCakeMoisture)
		{
			SetNote(idPressNote, "Input feed too dry: All feed liquids sent to Cake");
			dFiltSolidFactor = dFiltLiquidFactor = 0;
			dCakeSolidFactor = dCakeLiquidFactor = 1;
		}
		else
		{
			dFiltSolidFactor = (((dFeedLiquidMass * (dReqCakeMoisture - 1) + dFeedSolidMass * dReqCakeMoisture) * dLiqConcConstFac)
									/ NZ(dReqCakeMoisture * dLiqConcConstFac - (dReqCakeMoisture - 1)*(dSolConcConstFac-1))) / dFeedSolidMass;
			dFiltLiquidFactor = - (((dFeedLiquidMass * (dReqCakeMoisture - 1) + dFeedSolidMass * dReqCakeMoisture) * (dSolConcConstFac - 1))
									/ NZ(dReqCakeMoisture * dLiqConcConstFac - (dReqCakeMoisture - 1)*(dSolConcConstFac - 1))) / dFeedLiquidMass;

			dCakeSolidFactor = 1 - dFiltSolidFactor;
			dCakeLiquidFactor = 1 - dFiltLiquidFactor;
		}

		for (int i = 0; i < gs_MVDefn.Count(); i++)
			if (gs_MVDefn[i].IsSolid())
			{
				QFiltrate.M[i] = QFeed.M[i] * dFiltSolidFactor;
				QUnwashedCake.M[i] = QFeed.M[i] * dCakeSolidFactor;
			}
			else if (gs_MVDefn[i].IsLiquid())
			{
				QFiltrate.M[i] = QFeed.M[i] * dFiltLiquidFactor;
				QUnwashedCake.M[i] = QFeed.M[i] * dCakeLiquidFactor;
			}

		//Now, wash it:
		//First, add the washwater solids, and record what mass of solids we add.
		//The equations associated with this are exceedingly ugly.
		if (FlwIOs.getCount(idWash) > 0) {
			//Short variables to create fewer errors translating the equations
			double WS = QWashWater.Mass(MP_Sol);
			double WL = QWashWater.Mass(MP_Liq);
			double CS = QUnwashedCake.Mass(MP_Sol);
			double CL = QUnwashedCake.Mass(MP_Liq);
			double a, rWS, rWL, rCS, rCL;
			double m = dReqCakeMoisture;
			double w;
			switch (eWashMethod)
			{
			case WM_ConstantEfficiency:
				w = dReqWashEfficiency;
			case WM_WashRatio:
				dWashRatio = WL / NZ(CL);
				w = 1 - pow(1-dReqWashEfficiency, dWashRatio);
			}
			switch (eFiltrateMethod) {
			case FM_SolidsToFiltrateFrac:
				a = dReqSolidsToFiltrate;
				rWS = rWL = rCS = rCL = 1;
				break;
			case FM_FiltrateConc:
				a = dReqFiltSolConc;
				rWS = QWashWater.Density(MP_Sol, C_2_K(25));
				rWL = QWashWater.Density(MP_Liq, C_2_K(25));
				rCS = QUnwashedCake.Density(MP_Sol, C_2_K(25));
				rCL = QUnwashedCake.Density(MP_Liq, C_2_K(25));
				break;
			}
			/*Possible situations where variables are out of range:
			 - A: Too much wash liquid - all solids are sent out in washings ['Fix' by sending everyhing to washings. Assume we will not end up with only washing solids]
			 - B: Not enough wash liquid - required wash efficiency impossible ['Fix' by putting all wash water in cake]
			 - C: Cake comes in dry [And very dry] - wash efficiency is higher than requested even if no cake moisture is displaced. 
				[Fix by sending everything].
			 - D: Wash water has FAAR too many solids - cake moisture is lower than required. [Fix by putting everything into cake]
			 - Cake should never come in too wet.
			 */
			//CO - Cake Out. WO - Washings out.
			double dCLtoCO, dCStoCO, dWStoCO;
			
			double dTemp = (WS*rCL*rCS*rWL*(a-rWS)+(a*WL*rCL*rCS+(CS*rCL*(a-rCS)+a*CL*rCS)*rWL)*rWS)
				/ NZ(CS*(m*a*(1-w)*rCS*rWL+rCL*((1-m)*a*rWL+rCS*(m*a*w-(1-m)*rWL)))*rWS + WS*rCL*rCS*((1-m)*rWL*(a-rWS)+m*a*rWS));

			double dWLtoCO = m * (w*CS + WS) * dTemp;
			if (dWLtoCO > WL)
			{ //Case B - not enough wash liquid
				SetNote(idWashNote, "Not enough wash water: All wash water sent to cake");
				//double dTemp1 = (WS*rCL*rCS*(a-rWS)+(CS*rCL*(a-rCS)+a*CL*rCS)*rWS)
				//			/ NZ(CS*((1-m)*rCL*(a-rCS)+m*a*rCS)*rWS + WS*rCS*((1-m)*rCL*(a-rWS) + m*a*rWS));
				double dTemp1 = 1 / (CS * ((1-m) * rCL * (a-rCS) + m*a*rCS) * rWS + WS * rCS * ((1-m) * rCL * (a-rWS) + m*a*rWS));
				double dTemp2 = WS * rCL * rCS * (a-rWS) + (CS * rCL * (a-rCS) + a*(CL+WL)*rCS)*rWS;
				
				dWLtoCO = WL;
				dCLtoCO = (m*CS*CS*rCL*(a-rCS)*rWS + CS*((-(1-m)*WL*rCL*(a-rCS) + m*a*CL*rCS)*rWS+m*WS*rCL*(rCS*(a-2*rWS)+a*rWS))
							+ WS*rCS*(-(1-m)*WL*rCL*(a-rWS) + m*(WS*rCL*(a-rWS) + a*CL*rWS))) * dTemp1;
				dCStoCO = (1-m) * CS * dTemp1 * dTemp2;
				dWStoCO = (1-m) * WS * dTemp1 * dTemp2;
			}
			else
			{
				//dWLtoCO already set.
				dCLtoCO = m * (1-w) * CS * dTemp;
				dCStoCO = (1-m) * CS * dTemp;
				dWStoCO = (1-m) * WS * dTemp;
			}

			if (dCLtoCO > CL) { //Case C - not enough cake moisture [Can only happen if cake comes in with moisture content v. low.]
				dCLtoCO = CL;	//Although this results in a lower than required moisture content, well, the cake is comming in drier than required.
				SetNote(idWashNote, "Cake too dry"); }
			
			//Simple Cons of Mass:
			double dWLtoWO = WL - dWLtoCO;
			double dCLtoWO = CL - dCLtoCO;
			double dWStoWO = WS - dWStoCO;
			double dCStoWO = CS - dCStoCO;

			double dWLtoCOFrac,dCLtoCOFrac,dWStoCOFrac,dCStoCOFrac,dWLtoWOFrac,dCLtoWOFrac,dWStoWOFrac,dCStoWOFrac;
			if (dCStoCO < 0) //Case A, Too wet: Everything sent with washings
			{
				SetNote(idWashNote, "Too much liquid in WashWater. Everything sent to washings");
				dWLtoCOFrac=dCLtoCOFrac=dWStoCOFrac=dCStoCOFrac=0;
				dWLtoWOFrac=dCLtoWOFrac=dWStoWOFrac=dCStoWOFrac=1;
			}
			else if ((CL+WL)/NZ(CS+WS+CL+WL) < m)	//Case D, Too Dry: Send everything to cake.
			{
				SetNote(idWashNote, "Not enough liquid in washing stage. Everything sent to cake");
				dWLtoCOFrac=dCLtoCOFrac=dWStoCOFrac=dCStoCOFrac=1;
				dWLtoWOFrac=dCLtoWOFrac=dWStoWOFrac=dCStoWOFrac=0;
			}
			else
			{
				//Here's where we handle if we have any other zeros.
				//Also handle if a variable manages to slip by the other checks [It is possible with outlandish input parameters.]
				dWLtoCOFrac = WL > 0 ? dWLtoCO / WL : 0;
				dCLtoCOFrac = CL > 0 ? dCLtoCO / CL : 0;
				dWStoCOFrac = WS > 0 ? dWStoCO / WS : 0;
				dCStoCOFrac = CS > 0 ? dCStoCO / CS : 0;
				dWLtoWOFrac = WL > 0 ? dWLtoWO / WL : 0;
				dCLtoWOFrac = CL > 0 ? dCLtoWO / CL : 0;
				dWStoWOFrac = WS > 0 ? dWStoWO / WS : 0;
				dCStoWOFrac = CS > 0 ? dCStoWO / CS : 0;
			}

			dWashEfficiency = CL > 0 ? 1 - dCLtoCOFrac : dNAN;

		  //MStream QWashingOutput = QCake; BAD idea, QWashingOutput becomes a reference to QCake!!!!
			MStreamI QWashingOutput;
			QWashingOutput = QCake;
			QWashingOutput.ZeroMass();

			for (int i = 0; i < gs_MVDefn.Count(); i++)
				if (gs_MVDefn[i].IsSolid())
				{
					QWashingOutput.M[i] = QUnwashedCake.M[i] * dCStoWOFrac + QWashWater.M[i] * dWStoWOFrac;
					QCake.M[i] = QUnwashedCake.M[i] * dCStoCOFrac + QWashWater.M[i] * dWStoCOFrac;
				}
				else if (gs_MVDefn[i].IsLiquid())
				{
					QWashingOutput.M[i] = QUnwashedCake.M[i] * dCLtoWOFrac + QWashWater.M[i] * dWLtoWOFrac;
					QCake.M[i] = QUnwashedCake.M[i] * dCLtoCOFrac + QWashWater.M[i] * dWLtoCOFrac;
				}

			double dInputHf;
			
			MStream* pQWashingOutput;
			if (bWashingsConnected) //bWashingsConnected indicates whether OUTPUT washings are connected.
			{
				dInputHf = QUnwashedCake.totHf() + QWashWater.totHf();
				pQWashingOutput = &QWashingOutput;
			}
			else
			{
				dInputHf = QUnwashedCake.totHf() + QWashWater.totHf() + QFiltrate.totHf();
				for (int i = 0; i < gs_MVDefn.Count(); i++)
					QFiltrate.M[i] += QWashingOutput.M[i];
				pQWashingOutput = &QFiltrate;	//For thermal property managing
			}

			double dgbTi = pQWashingOutput->T;
			bool converged = false;
			for (int i = 0; i < 10 && !converged; i++)
			{
				double dbgTt = pQWashingOutput->T;

				double dOutputHf = QCake.totHf() + pQWashingOutput->totHf();

				double deltaT = -(dOutputHf - dInputHf) / NZ(pQWashingOutput->totCp() + QCake.totCp());

				pQWashingOutput->T += deltaT;
				QCake.T = pQWashingOutput->T;

				converged = abs(dInputHf - dOutputHf) < 1; //TODO: Check what sort of convergence we require.
			}

			double dbgT = pQWashingOutput->T;
			double dbgT2 = QWashingOutput.T;

			if (bWashingsConnected)
			{
				MStream& QWashings = FlwIOs[FlwIOs.First[idWashings]].Stream;
				QWashings = QWashingOutput;
			}
		}
		else //If we have no washings
		{
			if (bWashingsConnected)
			{
				MStream& QWashings = FlwIOs[FlwIOs.First[idWashings]].Stream;
				QWashings.ZeroMass();
			}
			QCake = QUnwashedCake;
		}

		//Vent:
		if (FlwIOs.Count[idVent] > 0)
		{
			MStream& QVent = FlwIOs[FlwIOs.First[idVent]].Stream;
			QVent = QCake;
			QVent.ZeroMass();
			MStreamI QWashWater;
			if (FlwIOs.Count[idWash] > 0)
				QWashWater = FlwIOs[FlwIOs.First[idWash]].Stream;
			else
				QWashWater.ZeroMass();

			for (int i = 0; i < gs_MVDefn.Count(); i++)
				if (gs_MVDefn[i].IsGas())
					QVent.M[i] = QCake.M[i] + QWashWater.M[i];
		}
		
		//Update "Actual" values.
		dCakeSolids = QCake.MassFrac(MP_Sol);
		dFiltSolids = QFiltrate.MassFrac(MP_Sol);
		dCakeSolConc = QCake.Mass(MP_Sol) / NZ(QCake.Volume(MP_All, C_2_K(25.0)));
		dFiltSolConc = QFiltrate.Mass(MP_Sol) / NZ(QFiltrate.Volume(MP_All, C_2_K(25.0)));
		if (bWashWaterConnected)
		{
			double CFeed = QFeed.SpecieConc(MP_Liq, nWashCompSpecie, C_2_K(25.0));
			double CCake = QCake.SpecieConc(MP_Liq, nWashCompSpecie, C_2_K(25.0));
			double CWash = QWashWater.SpecieConc(MP_Liq, nWashCompSpecie, C_2_K(25.0));
			dWashCompEff = (CFeed - CCake) / NZ(CFeed - CWash);
		}
		else
		{
			dWashCompEff = 0;
			dWashEfficiency = 0;
		}
	}
  catch (MMdlException &e)
    {
    Log.Message(MMsg_Error, e.Description);
    }
  catch (MFPPException &e)
    {
    e.ClearFPP();
    Log.Message(MMsg_Error, e.Description);
    }
  catch (MSysException &e)
    {
    Log.Message(MMsg_Error, e.Description);
    }
  catch (...)
    {
    Log.Message(MMsg_Error, "Some Unknown Exception occured");
    }
}
Example #2
0
void FlotationCell::EvalProducts()
{
	try
	{
		dPrimaryRecovery = dReqPrimaryRecovery;
		dPrimaryGrade = dReqPrimaryGrade;
		for (unsigned int i = 0; i < vSecondaryRecoveries.size(); i++)
			vSecondaryRecoveries.at(i) = vReqSecondaryRecoveries.at(i);

		//sum all input streams into a working copy
		MStreamI QI;
		FlwIOs.AddMixtureIn_Id(QI, idFeed);
		MStreamI QAir;
		FlwIOs.AddMixtureIn_Id(QAir, idAir);
		bool bAirOn = FlwIOs.Count[idAir] > 0;

		//get handles to output streams...
		MStream & QOT = FlwIOs[FlwIOs.First[idTail]].Stream;
		MStream & QOC = FlwIOs[FlwIOs.First[idConc]].Stream;
		MStream & QOV = FlwIOs[FlwIOs.First[idVent]].Stream;

		QOT = QI;
		QOT.ZeroMass();
		QOC = QI;
		QOC.ZeroMass();

		if (!m_bOn)
		{
			QOT = QI;
			QOV = QAir;
			dPrimaryRecovery = dPrimaryGrade = 0;
			for (vector<double>::iterator it = vSecondaryRecoveries.begin(); it != vSecondaryRecoveries.end(); it++)
				*it = 0.0;
			return;
		}
		if (QI.Mass(MP_Sol) > QI.Mass(MP_Liq))
			Log.SetCondition(true, LC_BadInput, MMsg_Warning, "Input stream contains more solid than liquid.");

		if (bAirOn && (QAir.Mass(MP_Sol) > UsableMass || QAir.Mass(MP_Liq) > UsableMass))
			Log.SetCondition(true, LC_BadAir, MMsg_Warning, "Air stream contains liquids or solids");
		if (bAirOn && QAir.Volume(MP_Gas) < 2 * QI.Volume(MP_Sol))
			Log.SetCondition(true, LC_LittleAir, MMsg_Warning, "Air stream contains very little gas");

		QI.AddF(QAir, MP_All, 1);

		//do the work...
		const int NumSpecies = gs_MVDefn.Count();

		//Primaries:
		double dPrimaryMassConc = 0.0;
		double dPrimaryElementConc = 0.0;
		for (unsigned int i = 0; i < vPrimaryIndices.size(); i++)
		{
			double temp = QI.M[vPrimaryIndices.at(i)];
			dPrimaryMassConc += temp * dReqPrimaryRecovery;
			if (eSpecType == FTST_ByElement)
				dPrimaryElementConc += temp * dPrimaryRecovery * ElementMassFrac(vPrimaryIndices.at(i), nPrimary1);
			else
				dPrimaryElementConc += temp * dPrimaryRecovery;

			QOT.M[vPrimaryIndices.at(i)] = temp * (1 - dReqPrimaryRecovery);
			QOC.M[vPrimaryIndices.at(i)] = temp * dReqPrimaryRecovery;
		}

		if (dPrimaryElementConc == 0)
		{
			dPrimaryRecovery = dNAN;
			Log.SetCondition(true, LC_NoPrimaries, MMsg_Warning, "No primary compounds found in feed.");
		}

		//Secondaries:
		double dSecondaryMassConc = 0.0;
		for (unsigned int i = 0; i < vSecondaryIndices.size(); i++)
		{
			double temp = QI.M[vSecondaryIndices.at(i)];
			dSecondaryMassConc += temp * vReqSecondaryRecoveries.at(i);
			QOT.M[vSecondaryIndices.at(i)] = temp * (1 - vReqSecondaryRecoveries.at(i));
			QOC.M[vSecondaryIndices.at(i)] = temp * vReqSecondaryRecoveries.at(i);
			if (eSpecType == FTST_ByElement)
				dPrimaryElementConc += temp * vReqSecondaryRecoveries.at(i) * ElementMassFrac(vSecondaryIndices.at(i), nPrimary1);
		}

		//Those not included in either primaries or secondaries.
		double dPrimaryElementInGangue = 0;
		double dOtherMassIn = 0.0;
		for (unsigned int i = 0; i < vOtherIndices.size(); i++)
		{
			dOtherMassIn += QI.M[vOtherIndices.at(i)];
			if (eSpecType == FTST_ByElement)
				dPrimaryElementInGangue += QI.M[vOtherIndices.at(i)] * ElementMassFrac(vOtherIndices.at(i), nPrimary1);
		}
		double dPrimaryGangueFrac = dPrimaryElementInGangue / NZ(dOtherMassIn);

		double dReqOtherMassOut = (dPrimaryElementConc - dReqPrimaryGrade * (dPrimaryMassConc + dSecondaryMassConc)) / NZ(dReqPrimaryGrade - dPrimaryGangueFrac);
		if (dReqOtherMassOut < 0) //If this is the case, we will put in no more impurities.
		{
			if (dPrimaryElementConc / QOC.Mass(MP_Sol) < dReqPrimaryGrade)
			{
				Log.SetCondition(true, LC_NoGrade, MMsg_Warning, "Secondary products prevent required primary grade");
				dReqOtherMassOut = 0;
			}
			else
			{
				Log.SetCondition(true, LC_NoGrade, MMsg_Warning, "Grade of gangue to concentrate higher than required grade");
				if (dPrimaryElementConc / QOC.Mass(MP_Sol) > dPrimaryGangueFrac) //We will get as close as possible...
					dReqOtherMassOut = dOtherMassIn;
			}
		}
		if (dReqOtherMassOut > dOtherMassIn)
		{
			Log.SetCondition(true, LC_NoGrade, MMsg_Warning, "Not enough unspecified input mass to achieve required primary grade");
			dReqOtherMassOut = dOtherMassIn;
		}
		for (unsigned int i = 0; i < vOtherIndices.size(); i++)
		{
			QOC.M[vOtherIndices.at(i)] = QI.M[vOtherIndices.at(i)] * dReqOtherMassOut / NZ(dOtherMassIn);
			QOT.M[vOtherIndices.at(i)] = QI.M[vOtherIndices.at(i)] * (1 - dReqOtherMassOut / NZ(dOtherMassIn));
		}
		dPrimaryElementConc += dReqOtherMassOut * dPrimaryGangueFrac;

		//Finally: We deal with non-solid species:
		/*for (int i = 0; i < gs_MVDefn.Count(); i++)
		{
			if (gs_MVDefn[i].IsSolid())
				continue;
			QOC.M[i] = QI.M[i] * dReqWaterFrac;
			QOT.M[i] = QI.M[i] * (1 - dReqWaterFrac);
		}*/
		QOC.AddM(QI, MP_Liq, QI.Mass(MP_Liq) * dReqWaterFrac);
		QOT.AddM(QI, MP_Liq, QI.Mass(MP_Liq) * (1 - dReqWaterFrac));
		if (FlwIOs.Count[idVent] > 0)
		{
			QOV = QI;
			QOV.ZeroMass();
			QOV.AddM(QI, MP_Gas, QI.Mass(MP_Gas));
		}
		else
		{
			QOC.AddM(QI, MP_Gas, QI.Mass(MP_Gas));
			if (FlwIOs.Count[idAir] > 0)
				Log.SetCondition(true, LC_Vent, MMsg_Warning, "Stream connected to air, but no stream connected to vent.");
		}

		if (QOC.Mass(MP_Sol) > 0)
			dPrimaryGrade = dPrimaryElementConc / NZ(QOC.Mass(MP_Sol));
		else
			dPrimaryGrade = dNAN;
		dSfConcentrate = QOC.MassFrac(MP_Sol);
		dSfTailings = QOT.MassFrac(MP_Sol);
  }
  catch (MMdlException &e)
    {
    Log.Message(MMsg_Error, e.Description);
    }
  catch (MFPPException &e)
    {
    e.ClearFPP();
    Log.Message(MMsg_Error, e.Description);
    }
  catch (MSysException &e)
    {
    Log.Message(MMsg_Error, e.Description);
    }
  catch (...)
    {
    Log.Message(MMsg_Error, "Some Unknown Exception occured");
    }
  }