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 BatchPrecip::EvalProducts() { #if ForceOptimizeOff static Cnt = 0; if (stricmp(getTag(), "SpeciauxUE")==0) { Cnt++;//place breakpoint here to stop for specified model } #endif if (!IsSolveDirect)//(!IsProbal) return; bool Err = true; try { MStreamI Slurry; MStreamI Liquor; FlwIOs.AddMixtureIn_Id(Slurry, idSlurry); //sum all input slurry streams m_bHasLiquor = (FlwIOs.First[idLiquor]>=0); //test if liquor stream connected if (m_bHasLiquor) { FlwIOs.AddMixtureIn_Id(Liquor, idLiquor); //sum all input liquor streams //double LiquorSolids = Liquor.Mass(); } MStream & Prod = FlwIOs[FlwIOs.First[idDrawOff]].Stream; //get a reference to the single output stream Prod = Slurry; //set output stream = input stream (including PSD data, etc) m_dThermalLoss = 0.0; m_dYield = 0.0; m_dTempIn = Slurry.T; MIBayer & SlurryB = *Slurry.FindIF<MIBayer>(); //get access to bayer properties interface for stream if (!IsNothing(SlurryB)) { m_dACIn = SlurryB.AtoC(); m_dSolidConcIn = SlurryB.SolidsConc(C2K(25.0)); } else { m_dACIn = 0.0; m_dSolidConcIn = 0.0; } if (m_bOn && Slurry.Mass()>UsableMass) { MIBayer & ProdB = *Prod.FindIF<MIBayer>(); Log.SetCondition(IsNothing(ProdB), 1, MMsg_Warning, "Bad Slurry Stream - Not Bayer Model"); //expect stream to have bayer properties if (!IsNothing(SlurryB) && !IsNothing(ProdB)) { // --- --- Thermal Losses. ------------ // !! this should be done COMBINED with FLAHSING/evaporation as the Caustic concentration should increase Slurry.SetTP(Slurry.T -m_dFillingTempDrop, Slurry.P); Liquor.SetTP(Liquor.T -m_dFillingTempDrop, Liquor.P); double SlurryH0 = Slurry.totHf(MP_All, Slurry.T, Slurry.P); double LiquorH0 = (m_bHasLiquor ? Liquor.totHf(MP_All, Liquor.T, Liquor.P) : 0.0); //Prod.SetTemp(Prod.Temp()-m_dTempDrop); RunSteady(Slurry, Liquor, Prod); m_dThermalLoss = (SlurryH0+LiquorH0)-Prod.totHf(MP_All, Prod.T, Prod.P); m_dACOut = ProdB.AtoC(); m_dSolidConcOut = ProdB.SolidsConc(C2K(25.0)); //m_dResTime = TankVol/GTZ(Prod.Volume(som_SL)); double Cout = ProdB.CausticConc(Prod.T); m_dYield = Cout*(m_dACIn-m_dACOut); } if (sm_bCompletePopulation) m_QProd = Prod;//copy the content of the stream in the equipement only if STILL in Pop mode } else { if (m_bHasLiquor) { Prod.AddF(Liquor, MP_All, 1.0); } MIBayer & ProdB = *Prod.FindIF<MIBayer>(); if (!IsNothing(ProdB)) { m_dACOut = ProdB.AtoC(); m_dSolidConcOut = ProdB.SolidsConc(C2K(25.0)); } else { m_dACOut = 0.0; m_dSolidConcOut = 0.0; } } m_dTempOut = Prod.T; Err = false; } 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"); } if (Err) {//Something is wrong!!! Bug needs fixing!!! //So lets set product=feed.... MStreamI Feed; MStream & Prod = FlwIOs[FlwIOs.First[idDrawOff]].Stream; //get a reference to the single output stream FlwIOs.AddMixtureIn_Id(Feed, idSlurry); //sum all input slurry streams Prod = Feed; if (FlwIOs.First[idLiquor]>=0) { MStreamI Liquor; FlwIOs.AddMixtureIn_Id(Liquor, idLiquor); //sum all input liquor streams Prod.AddF(Liquor, MP_All, 1.0); } CString ProblemModel; ProblemModel = getTag(); //SetStopRequired("Phone Denis!"); } Log.SetCondition(Err, 5, MMsg_Error, "Error needs fixing!!!"); }