HOWTO calculate a liquid-liquid separation
An interesting thing that you can do with a two-phase stream such as StreamNrtl1LiquidLiquid (based on the Non Random Two Liquids activity coefficient model in the original formulation with temperature-independent bij, with two liquids) is the calculation of the liquid-liquid separation.
In this tutorial, we’ll calculate the liquid-liquid split of water and 2-ethyl-1-hexanol at 125°C.
Start from the Hello world tutorial, and perform these steps:
-
in the
#include
section, include the headers required for components and streams:#include <libpf/purecomps.h> #include <libpf/persistency/NodeFactory.h> #include <libpf/phases/phases.h> #include <libpf/ListComponents.h> // for components and NCOMPONENTS #include <libpf/streams/Stream.h> #include <libpf/streams/streams.h> // register all stream types with the model factory
-
since this tutorial is going to use NRTL-type streams, it is required to include the “activity” headers:
#include <libpf/phases/ActivityNRTL.h> #include <libpf/phases/PhaseActivity.h>
-
the liquid-liquid equilibrium requires the initialization of certain flash parameters, thus include:
#include <libpf/streams/FlashLlTx.h>
-
in the main function, remove the
“Hello, world !"
statement and replace it as follows:-
define water and 2-ethyl-1-hexanol as components:
components.addcomp(new purecomps::water); components.addcomp(new purecomps::Ethylhexanol("ETEX"));
-
NRTL1 activity phases require to set the NRTL parameters as follow (these are based on experimental data at 120.384 - 129.785°C as described here):
Activity::Nrtl1::parameters.resize(); Activity::Nrtl1::parameters.setB(0, 1, 3060.4764877456, -156.0296483827); Activity::Nrtl1::parameters.setA(0, 1, 0.2);
-
as above mentioned, liquid-liquid equilibrium requires a smart initialization. A practical initialization strategy is to set the KLLs for the key organic components to a small value such as 1E-5 and the KLL for for water to a large value such as 1E5; this results in the first phase (the “Vapor”) to become the watery phase, and the second phase (the “Liquid”) to become the organic phase. If the inverse KLLs values are introduced, the phase assigments will be reverted.
FlashLlTx::resetKllEstimates(); FlashLlTx::setKllEstimate(0, 1E5); FlashLlTx::setKllEstimate(1, 1E-5);
-
add the actual stream manipulation:
NodeFactory nodeFactory; Stream *stream = my_cast<Stream *>(nodeFactory.create("StreamNrtl1LiquidLiquid", Libpf::User::Defaults("STREAM")), CURRENT_FUNCTION); stream->flashoption = "PT"; stream->flowoption = "Nx"; stream->Q("Tphase.ndot").set(100.0,"kmol/s"); stream->clearcomposition(); stream->Tphase->Q("x[0]") = 0.5; // water stream->Tphase->Q("x[1]") = One - stream->Tphase->Q("x[0]"); // ETEX stream->P.set(1.0, "atm"); stream->T.set(125.0+273.15, "K"); stream->resetErrors(); stream->calculate(); stream->reportMessages();
-
add your desired output (here the std::cout has been used so include the
<iostream>
header) :std::cout << "Calculation errors: " << stream->errors.size() << std::endl; std::cout << "------------------------------------------------------------------------------------" << std::endl; std::cout << "Phase\tfraction\tWater\tETEX\tWater\tETEX" << std::endl; std::cout << "Name\tmol frac\tmol frac\tndot, kmol/s\tndot, kmol/s" << std::endl; std::cout << stream->at("Vphase").fullTag() << "\t" << stream->Q("Vphase.fraction") << "\t" << stream->Q("Vphase.x[0]") << "\t" << stream->Q("Vphase.x[1]") << "\t" << stream->Q("Vphase.ndotcomps[0]").toDouble() << "\t" << stream->Q("Vphase.ndotcomps[1]").toDouble() << std::endl; std::cout << stream->at("Lphase").fullTag() << "\t" << stream->Q("Lphase.fraction") << "\t" << stream->Q("Lphase.x[0]") << "\t" << stream->Q("Lphase.x[1]") << "\t" << stream->Q("Lphase.ndotcomps[0]").toDouble() << "\t" << stream->Q("Lphase.ndotcomps[1]").toDouble() << std::endl; std::cout << stream->at("Tphase").fullTag() << "\t" << stream->Q("Tphase.fraction") << "\t" << stream->Q("Tphase.x[0]") << "\t" << stream->Q("Tphase.x[1]") << "\t" << stream->Q("Tphase.ndotcomps[0]").toDouble() << "\t" << stream->Q("Tphase.ndotcomps[1]").toDouble() << std::endl; std::cout << "------------------------------------------------------------------------------------" << std::endl; std::cout << "Water Kll: " << stream->Q("Vphase.x[0]") / stream->Q("Lphase.x[0]") << std::endl; std::cout << "ETEX Kll: " << stream->Q("Vphase.x[1]") / stream->Q("Lphase.x[1]") << std::endl;
-
it is possible to add some error checking such as mass conservation:
for (int i=0;i<NCOMPONENTS;++i){ if (fabs(stream->Q("Tphase.ndotcomps[0]")-stream->Q("Vphase.ndotcomps[0]")-stream->Q("Lphase.ndotcomps[0]")) > Value(1.0E-4,"mol/s") ) throw ErrorRunTime(CURRENT_FUNCTION, "Unbalanced phases"); }
-
at the end, remember to delete the pointer !
delete stream;
-
This is the expected output:
* ****************** LIBPF 01.00.2350 [2016/04/12 11:39:44] ******************
* (C) Copyright 2004-2016 Paolo Greppi simevo s.r.l.
* ****************************************************************************
* All rights reserved; do not distribute without permission.
* ****************************************************************************
Calculation errors: 0
------------------------------------------------------------------------------------
Phase fraction Water ETEX Water ETEX
Name mol frac mol frac ndot, kmol/s ndot, kmol/s
STREAM:Vphase 0.278635989043 0.999484893116 0.000515106883773 27.8492 0.0143527
STREAM:Lphase 0.721364010957 0.30706763201 0.69293236799 22.1508 49.9856
STREAM:Tphase 1 0.5 0.5 50 50
------------------------------------------------------------------------------------
Water Kll: 3.25493405663
ETEX Kll: 0.000743372524604