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"); } }
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"); } }