Difference between revisions of "Model Customization"

From DWSIM - Open Source Chemical Process Simulator
Jump to navigation Jump to search
Line 1: Line 1:
 
<div id="mw-content-text" lang="en" dir="ltr" class="mw-content-ltr"><table style="color: white; background-color: steelblue;" cellpadding="10" width="100%">
 
<div id="mw-content-text" lang="en" dir="ltr" class="mw-content-ltr"><table style="color: white; background-color: steelblue;" cellpadding="10" width="100%">
 
<tr>
 
<tr>
<td colspan="1"> [[File:Dialog-warning.png"|32px]] This tutorial requires advanced Python programming skills]]
+
<td colspan="1"> [[File:Dialog-warning.png|32px]] This tutorial requires advanced Python programming skills]]
 
</td></tr>
 
</td></tr>
 
</table>
 
</table>

Revision as of 19:01, 26 June 2019

Dialog-warning.png This tutorial requires advanced Python programming skills]]
Dialog-information.png You'll need at least DWSIM v5.6 with Update 12 or newer on Windows, Linux or macOS to run the Python scripts described on this article.

Introduction

You can override various internal model calculation routines with custom python code using the Script Manager. As of DWSIM v5.6 Update 12, you can override calculation routines from Unit Operations on the flowsheet, Fugacity, Enthalpy and Entropy calculation routines from Property Packages added to the flowsheet and use custom Flash calculation routines through User-Defined Flash Algorithm instances added to the flowsheet.

The custom functions must be defined and set only once before working with your simulation. For this reason it is better to associate your code with the Simulation Opened event after it is ready for production, so it can be automatically executed on every simulation file opening/loading.

<a href="/wiki/index.php?title=File:Link.png" class="image"><img alt="Link.png" src="/wiki/images/f/fd/Link.png" width="748" height="108" /></a>

If you choose to make any modifications to the code, don't forget to update it by running the script using the 'Run Script' button on the Script Manager.

Unit Operation / Material Stream Calculation Routine Override

Unit Operations

The following example shows how to override the calculation routine of a simple mixer. The Python code is a direct conversion from DWSIM's Mixer calculation routine, just for illustration purposes. It works with this sample flowsheet:

<a href="/wiki/index.php?title=File:Mixer.png" class="image"><img alt="Mixer.png" src="/wiki/images/4/4d/Mixer.png" width="569" height="444" /></a>

First, you must define a Python function which takes no arguments and does the calculation and sets properties of outlet material streams. This function doesn't need to return anything. Then you set the OverrideCalculationRoutine to true and the CalculationRoutineOverride property to your function's delegate (= name).


import clr

clr.AddReference('DWSIM.MathOps')
clr.AddReference('DWSIM.UnitOperations')
clr.AddReference('DWSIM.Interfaces')

from DWSIM import *
from DWSIM.Thermodynamics.Streams import *
from DWSIM.UnitOperations import *
from DWSIM.MathOps.MathEx import *
from System import *
from System.Collections.Generic import *

# gets the mixer object

mixer = Flowsheet.GetFlowsheetSimulationObject('MIX-004')

def CalcMixer():

    ms = MaterialStream()

    P = 0.0
    W = 0.0
    H = 0.0
    
    i = 0

    for cp in mixer.GraphicObject.InputConnectors:
        if cp.IsAttached:
            ms = Flowsheet.SimulationObjects[cp.AttachedConnector.AttachedFrom.Name]
            ms.Validate()
            if mixer.PressureCalculation == UnitOperations.Mixer.PressureBehavior.Minimum:
                print '[' + mixer.GraphicObject.Tag + '] ' + 'Mixer Mode: Outlet Pressure = Minimum Inlet Pressure'
                if ms.Phases[0].Properties.pressure < P:
                    P = ms.Phases[0].Properties.pressure
                elif P == 0.0:
                    P = ms.Phases[0].Properties.pressure
            elif mixer.PressureCalculation == UnitOperations.Mixer.PressureBehavior.Maximum:
                print '[' + mixer.GraphicObject.Tag + '] ' + 'Mixer Mode: Outlet Pressure = Maximum Inlet Pressure'
                if ms.Phases[0].Properties.pressure > P:
                    P = ms.Phases[0].Properties.pressure
                elif P == 0:
                    P = ms.Phases[0].Properties.pressure
            else:
                print '[' + mixer.GraphicObject.Tag + '] ' + 'Mixer Mode: Outlet Pressure = Inlet Average'
                P += ms.Phases[0].Properties.pressure
                i += 1
            We = ms.Phases[0].Properties.massflow
            W += We
            if not Double.IsNaN(ms.Phases[0].Properties.enthalpy): H += We * ms.Phases[0].Properties.enthalpy

    if W != 0.0: 
        Hs = H / W 
    else:
        Hs = 0.0

    if mixer.PressureCalculation == UnitOperations.Mixer.PressureBehavior.Average: P = P / (i - 1)

    print '[' + mixer.GraphicObject.Tag + '] ' + 'Mixture Pressure (Pa): ' + str(P)
    print '[' + mixer.GraphicObject.Tag + '] ' + 'Mixture Mass Flow (kg/s): ' + str(W)
    print '[' + mixer.GraphicObject.Tag + '] ' + 'Mixture Enthalpy (kJ/kg): ' + str(Hs)

    T = 0.0

    n = Flowsheet.SelectedCompounds.Count
    Vw = Dictionary[String, Double]()
    for cp in mixer.GraphicObject.InputConnectors:
        if cp.IsAttached:
            ms = Flowsheet.SimulationObjects[cp.AttachedConnector.AttachedFrom.Name]
            for comp in ms.Phases[0].Compounds.Values:
                if not Vw.ContainsKey(comp.Name):
                    Vw.Add(comp.Name, 0)
                Vw[comp.Name] += comp.MassFraction * ms.Phases[0].Properties.massflow
            if W != 0.0: T += ms.Phases[0].Properties.massflow / W * ms.Phases[0].Properties.temperature

    if W == 0.0: T = 273.15

    print '[' + mixer.GraphicObject.Tag + '] ' + 'Mixture Temperature Estimate (K): ' + str(T)

    omstr = Flowsheet.SimulationObjects[mixer.GraphicObject.OutputConnectors[0].AttachedConnector.AttachedTo.Name]
    omstr.Clear()
    omstr.ClearAllProps()
    if W != 0.0: omstr.Phases[0].Properties.enthalpy = Hs
    omstr.Phases[0].Properties.pressure = P
    omstr.Phases[0].Properties.massflow = W
    omstr.Phases[0].Properties.molarfraction = 1
    omstr.Phases[0].Properties.massfraction = 1
    for comp in omstr.Phases[0].Compounds.Values:
        if W != 0.0: comp.MassFraction = Vw[comp.Name] / W
    mass_div_mm = 0.0
    for sub1 in omstr.Phases[0].Compounds.Values:
        mass_div_mm += sub1.MassFraction / sub1.ConstantProperties.Molar_Weight
    for sub1 in omstr.Phases[0].Compounds.Values:
        if W != 0.0:
            sub1.MoleFraction = sub1.MassFraction / sub1.ConstantProperties.Molar_Weight / mass_div_mm
        else:
            sub1.MoleFraction = 0.0
        print '[' + mixer.GraphicObject.Tag + '] ' + sub1.Name + ' outlet molar fraction: ' + str(sub1.MoleFraction)
    omstr.Phases[0].Properties.temperature = T
    omstr.SpecType = Interfaces.Enums.StreamSpec.Pressure_and_Enthalpy

    print '[' + mixer.GraphicObject.Tag + '] ' + 'Outlet Stream variables set successfully.'

    return None

mixer.OverrideCalculationRoutine = True

mixer.CalculationRoutineOverride = CalcMixer

After updating the script and running the simulation, you should get the following output: Mixer out.png

Material Streams

Usually you don't need to override the calculation routines of material streams as they are very structured and involve a lot of internal checks and property updates.

Property Package Fugacity Coefficients / Enthalpy / Entropy Calculation Routine Override

Fugacity Coefficients

The following code overrides the fugacity coefficient calculation routines (called by the K-values calculation routine which is, by its turn, called by the flash algorithms to do the equilibrium calculations) from a Peng-Robinson Property Package instance added to the simulation. The code reproduces the internal DWSIM's PR routines, just for illustration purposes.


import clr
clr.AddReference('DWSIM.MathOps')

from DWSIM import *
from DWSIM.MathOps.MathEx import *
from DWSIM.Thermodynamics.PropertyPackages import *
from System import *

# gets the first Property Package added to the the simulation

pp = Flowsheet.PropertyPackagesArray[0]

def calcroots(coeffs):

    # auxiliary function
    # calculates the roots of of a cubic polynomial and returns only the real ones 

    a = coeffs[0]
    b = coeffs[1]
    c = coeffs[2]
    d = coeffs[3]

    # uses DWSIM's internal 'CalcRoots' function to calculate roots
    # https://github.com/DanWBR/dwsim5/blob/windows/DWSIM.Math/MATRIX2.vb#L29

    res = PolySolve.CalcRoots(a, b, c, d)

    roots = [[0] * 2 for i in range(3)]

    roots[0][0] = res[0, 0]
    roots[0][1] = res[0, 1]
    roots[1][0] = res[1, 0]
    roots[1][1] = res[1, 1]
    roots[2][0] = res[2, 0]
    roots[2][1] = res[2, 1]

    # orders the roots

    if roots[0][0] > roots[1][0]:
        tv = roots[1][0]
        roots[1][0] = roots[0][0]
        roots[0][0] = tv
        tv2 = roots[1][1]
        roots[1][1] = roots[0][1]
        roots[0][1] = tv2
    if roots[0][0] > roots[2][0]:
        tv = roots[2][0]
        roots[2][0] = roots[0][0]
        roots[0][0] = tv
        tv2 = roots[2][1]
        roots[2][1] = roots[0][1]
        roots[0][1] = tv2
    if roots[1][0] > roots[2][0]:
        tv = roots[2][0]
        roots[2][0] = roots[1][0]
        roots[1][0] = tv
        tv2 = roots[2][1]
        roots[2][1] = roots[1][1]
        roots[1][1] = tv2

    validroots = []

    if roots[0][1] == 0 and roots[0][0] > 0.0: validroots.append(roots[0][0])
    if roots[1][1] == 0 and roots[1][0] > 0.0: validroots.append(roots[1][0])
    if roots[2][1] == 0 and roots[2][0] > 0.0: validroots.append(roots[2][0])

    # returns only valid real roots

    return validroots

def fugcoeff(Vz, T, P, state):

    # calculates fugacity coefficients using PR EOS
    # Vx = composition vector in molar fractions
    # T = temperature in K
    # P = Pressure in Pa
    # state = mixture state (Liquid, Vapor or Solid)

    R = 8.314
    
    n = len(Vz)
    
    Tc = pp.RET_VTC() # critical temperatures
    Pc = pp.RET_VPC() # critical pressures
    w = pp.RET_VW()   # acentric factors

    alpha = [0] * n
    ai = [0] * n
    bi = [0] * n

    for i in range(n):
        alpha[i] = (1 + (0.37464 + 1.54226 * w[i] - 0.26992 * w[i] ** 2) * (1 - (T / Tc[i]) ** 0.5)) ** 2
        ai[i] = 0.45724 * alpha[i] * R ** 2 * Tc[i] ** 2 / Pc[i]
        bi[i] = 0.0778 * R * Tc[i] / Pc[i]

    a = [[0] * n for i in range(n)]

    # get binary interaction parameters (BIPs/kijs) from PR Property Package

    kij = [[0] * n for i in range(n)] 

    vkij = pp.RET_VKij()

    for i in range(n):
        for j in range(n):
            kij[i][j] = vkij[i, j]
            a[i][j] = (ai[i] * ai[j]) ** 0.5 * (1 - kij[i][j]) # <- default mixing rule for amix

    amix = 0.0
    bmix = 0.0
    amix2 = [0] * n
    
    for i in range(n):
        for j in range(n):
            amix += Vz[i] * Vz[j] * a[i][j] # <- default mixing rule for amix
            amix2[i] +=  Vz[j] * a[j][i]

    for i in range(n):
        bmix += Vz[i] * bi[i] # <- default mixing rule - no interaction parameters for bmix
        
    AG = amix * P / (R * T) ** 2
    BG = bmix * P / (R * T)

    coeff = [0] * 4

    coeff[0] = 1
    coeff[1] = BG - 1
    coeff[2] = AG - 3 * BG ** 2 - 2 * BG
    coeff[3] = -AG * BG + BG ** 2 + BG ** 3

    roots = calcroots(coeff) # <- get the real roots of the cubic equation

    # compressibility factor = cubic equation's root

    if state == State.Liquid:
        # liquid
        Z = min(roots)
    else:
        # vapor
        Z = max(roots)

    # gets a special zeroed vector from the property package because DWSIM requires a 
    # .NET array as the returning value, not a Python one

    fugcoeff = pp.RET_NullVector()

    for i in range(n):
        t1 = bi[i] * (Z - 1) / bmix
        t2 = -Math.Log(Z - BG)
        t3 = AG * (2 * amix2[i] / amix - bi[i] / bmix)
        t4 = Math.Log((Z + (1 + 2 ** 0.5) * BG) / (Z + (1 - 2 ** 0.5) * BG))
        t5 = 2 * 2 ** 0.5 * BG
        fugcoeff[i] = Math.Exp(t1 + t2 - (t3 * t4 / t5))

    # returns calculated fugacity coefficients

    print 'calculated fugacities = ' + str(fugcoeff) +  ' (' + str(state) + ')'

    return fugcoeff

# activate fugacity calculation override on PR Property Package

pp.OverrideKvalFugCoeff = True

# set the function that calculates the fugacity coefficient

pp.KvalFugacityCoefficientOverride = fugcoeff

Enthalpy/Entropy

The following code overrides the enthalpy calculation routine (called by the Material Stream's Phase Property and by Pressure-Enthalpy flash routines) from a Peng-Robinson Property Package instance added to the simulation. The code reproduces the internal DWSIM's PR routines, just for illustration purposes.


import clr
clr.AddReference('DWSIM.MathOps')

from DWSIM import *
from DWSIM.MathOps.MathEx import *
from DWSIM.Thermodynamics.PropertyPackages import *
from System import *

# gets the first Property Package added to the the simulation

pp = Flowsheet.PropertyPackagesArray[0]

def calcroots(coeffs):

    # auxiliary function
    # calculates the roots of of a cubic polynomial and returns only the real ones 

    a = coeffs[0]
    b = coeffs[1]
    c = coeffs[2]
    d = coeffs[3]

    # uses DWSIM's internal 'CalcRoots' function to calculate roots
    # https://github.com/DanWBR/dwsim5/blob/windows/DWSIM.Math/MATRIX2.vb#L29

    res = PolySolve.CalcRoots(a, b, c, d)

    roots = [[0] * 2 for i in range(3)]

    roots[0][0] = res[0, 0]
    roots[0][1] = res[0, 1]
    roots[1][0] = res[1, 0]
    roots[1][1] = res[1, 1]
    roots[2][0] = res[2, 0]
    roots[2][1] = res[2, 1]

    # orders the roots

    if roots[0][0] > roots[1][0]:
        tv = roots[1][0]
        roots[1][0] = roots[0][0]
        roots[0][0] = tv
        tv2 = roots[1][1]
        roots[1][1] = roots[0][1]
        roots[0][1] = tv2
    if roots[0][0] > roots[2][0]:
        tv = roots[2][0]
        roots[2][0] = roots[0][0]
        roots[0][0] = tv
        tv2 = roots[2][1]
        roots[2][1] = roots[0][1]
        roots[0][1] = tv2
    if roots[1][0] > roots[2][0]:
        tv = roots[2][0]
        roots[2][0] = roots[1][0]
        roots[1][0] = tv
        tv2 = roots[2][1]
        roots[2][1] = roots[1][1]
        roots[1][1] = tv2

    validroots = []

    if roots[0][1] == 0 and roots[0][0] > 0.0: validroots.append(roots[0][0])
    if roots[1][1] == 0 and roots[1][0] > 0.0: validroots.append(roots[1][0])
    if roots[2][1] == 0 and roots[2][0] > 0.0: validroots.append(roots[2][0])

    # returns only valid real roots

    return validroots

def enthalpy(Vz, T, P, state):

    # calculates enthalpy using PR EOS
    # Vx = composition vector in molar fractions
    # T = temperature in K
    # P = Pressure in Pa
    # state = mixture state (Liquid, Vapor or Solid)

    # ideal gas enthalpy contribution

    Hid = pp.RET_Hid(298.15, T, Vz)

    R = 8.314
    
    n = len(Vz)
    
    Tc = pp.RET_VTC() # critical temperatures
    Pc = pp.RET_VPC() # critical pressures
    w = pp.RET_VW()   # acentric factors

    alpha = [0] * n
    ai = [0] * n
    bi = [0] * n
    ci = [0] * n

    for i in range(n):
        alpha[i] = (1 + (0.37464 + 1.54226 * w[i] - 0.26992 * w[i] ** 2) * (1 - (T / Tc[i]) ** 0.5)) ** 2
        ai[i] = 0.45724 * alpha[i] * R ** 2 * Tc[i] ** 2 / Pc[i]
        bi[i] = 0.0778 * R * Tc[i] / Pc[i]
        ci[i] = 0.37464 + 1.54226 * w[i] - 0.26992 * w[i] ** 2

    a = [[0] * n for i in range(n)]

    # get binary interaction parameters (BIPs/kijs) from PR Property Package

    kij = [[0] * n for i in range(n)] 

    vkij = pp.RET_VKij()

    for i in range(n):
        for j in range(n):
            kij[i][j] = vkij[i, j]
            a[i][j] = (ai[i] * ai[j]) ** 0.5 * (1 - kij[i][j]) # <- default mixing rule for amix

    amix = 0.0
    bmix = 0.0
    amix2 = [0] * n
    
    for i in range(n):
        for j in range(n):
            amix += Vz[i] * Vz[j] * a[i][j] # <- default mixing rule for amix
            amix2[i] +=  Vz[j] * a[j][i]

    for i in range(n):
        bmix += Vz[i] * bi[i] # <- default mixing rule - no interaction parameters for bmix
        
    AG = amix * P / (R * T) ** 2
    BG = bmix * P / (R * T)

    coeff = [0] * 4

    coeff[0] = 1
    coeff[1] = BG - 1
    coeff[2] = AG - 3 * BG ** 2 - 2 * BG
    coeff[3] = -AG * BG + BG ** 2 + BG ** 3

    roots = calcroots(coeff) # <- get the real roots of the cubic equation

    # compressibility factor = cubic equation's root

    if state == State.Liquid:
        # liquid
        Z = min(roots)
    else:
        # vapor
        Z = max(roots)

    # amix temperature derivative

    dadT1 = -8.314 / 2 * (0.45724 / T) ** 0.5
    dadT2 = 0.0#
    for i in range(n):
        j = 0
        for j in range(n):
            dadT2 += Vz[i] * Vz[j] * (1 - kij[i][j]) * (ci[j] * (ai[i] * Tc[j] / Pc[j]) ** 0.5 + ci[i] * (ai[j] * Tc[i] / Pc[i]) ** 0.5)

    dadT = dadT1 * dadT2

    uu = 2
    ww = -1

    DAres = amix / (bmix * (uu ** 2 - 4 * ww) ** 0.5) * Math.Log((2 * Z + BG * (uu - (uu ** 2 - 4 * ww) ** 0.5)) / (2 * Z + BG * (uu + (uu ** 2 - 4 * ww) ** 0.5))) - R * T * Math.Log((Z - BG) / Z) - R * T * Math.Log(Z)
    DSres = R * Math.Log((Z - BG) / Z) + R * Math.Log(Z) - 1 / (8 ** 0.5 * bmix) * dadT * Math.Log((2 * Z + BG * (2 - 8 ** 0.5)) / (2 * Z + BG * (2 + 8 ** 0.5)))
    DHres = DAres + T * (DSres) + R * T * (Z - 1)

    # mixture molar weight (MW)

    MW = pp.AUX_MMM(Vz)

    print 'calculated enthalpy = ' + str(Hid + DHres/ MW) +  ' kJ/kg (' + str(state) + ')'

    return Hid + DHres / MW # kJ/kg

# activate enthalpy calculation override on PR Property Package

pp.OverrideEnthalpyCalculation = True

# set the function that calculates the enthalpy

pp.EnthalpyCalculationOverride = enthalpy

The Entropy override works the same way by defining the OverrideEntropyCalculation/EntropyCalculationOverride properties from the Property Package instance.

User-Defined Flash Algorithm Routines

DWSIM v5.6 Update 12 includes a new Flash Algorithm type, the User-Defined (Python) Flash Algorithm. With this flash type you can define custom python functions to calculate the Pressure/Temperature, Pressure/Enthalpy, Pressure/Entropy, Pressure/VaporFraction and Temperature/VaporFraction flashes.

Udflash0.png

As you define the flash algorithm delegates through the Script Manager, the Flash Algorithm configuration editor displays the current status of the defined routines. You can test them using the corresponding buttons on the editor.

Udflash.png

Udflash2.png

The following code sample reproduces the PT/PH/PS flash routines from DWSIM's Nested Loops VLE flash algorithm, for illustration purposes. More advanced calculations can be implemented by the user.

PT Flash


import clr

clr.AddReference('DWSIM.MathOps')

from DWSIM import *
from DWSIM.MathOps.MathEx import *
from DWSIM.Thermodynamics.PropertyPackages import *
from DWSIM.Thermodynamics.PropertyPackages.Auxiliary.FlashAlgorithms import *
from System import *
from System.Collections.Generic import *

# gets the first Flash Algorithm (= User-Defined) added to the simulation

udflash = Flowsheet.FlowsheetOptions.FlashAlgorithms[0]

def PTFlash(Vz, P, T, pp):

    n = len(Vz)

    L = 0.0
    V = 0.0

    Vy = [0] * n
    Vx = [0] * n

    Ki = [0] * n

    Vp = [0] * n

    count = 0

    alreadysolved = False

    if T > Common.Max(pp.RET_VTC(), Array[Double](Vz)):
        # supercritical mixture
        alreadysolved = True
        Vy = list(Vz)
        Vx = list(Vz)
        V = 1.0
        L = 0.0

    for i in range(n):
        Vp[i] = pp.AUX_PVAPi(i, T)
        Ki[i] = Vp[i] / P

    Px = 0.0
    for i in range(n):
        if Vp[i] != 0.0: Px += Vz[i] / Vp[i]
        
    Px = 1 / Px
    Pmin = Px
    
    Px = 0.0
    for i in range(n):
        Px += Vz[i] * Vp[i]
    
    Pmax = Px
    
    Pb = Pmax
    Pd = Pmin

    if Math.Abs(Pb - Pd) / Pb < 0.0000001:
        # one comp only
        alreadysolved = True
        Px = sum(Vp * Vz)
        if Px <= P:
            L = 1.0
            V = 0.0
            Vx = list(Vz)
            for i in range(n):
                Vy[i] = Vx[i] * Ki[i]
        else:
            L = 0.0
            V = 1.0
            Vy = list(Vz)
            for i in range(n):
                Vx[i] = Vy[i] / Ki[i]

    if alreadysolved == False:

        Vmin = 1.0#
        Vmax = 0.0#
        
        for i in range(n):
            if (Ki[i] * Vz[i] - 1) / (Ki[i] - 1) < Vmin: Vmin = (Ki[i] * Vz[i] - 1) / (Ki[i] - 1)
            if (1 - Vz[i]) / (1 - Ki[i]) > Vmax: Vmax = (1 - Vz[i]) / (1 - Ki[i])

        if Vmin < 0.0: Vmin = 0.0
        if Vmin == 1.0: Vmin = 0.0
        if Vmax == 0.0: Vmax = 1.0
        if Vmax > 1.0: Vmax = 1.0

        V = (Vmin + Vmax) / 2

        g = 0.0
        for i in range(n):
            g += Vz[i] * (Ki[i] - 1) / (V + (1 - V) * Ki[i])

        if g > 0.0: 
            Vmin = V 
        else: 
            Vmax = V

        V = Vmin + (Vmax - Vmin) / 2
        L = 1.0 - V

        for i in range(n):
            if Vz[i] != 0.0:
                Vy[i] = Vz[i] * Ki[i] / ((Ki[i] - 1) * V + 1)
                if Ki[i] != 0.0: 
                    Vx[i] = Vy[i] / Ki[i]
                else:
                    Vx[i] = Vz[i]
                if Vy[i] < 0.0: Vy[i] = 0.0
                if Vx[i] < 0.0: Vx[i] = 0.0
            else:
                Vy[i] = 0.0
                Vx[i] = 0.0

        error = 1E10

        Vy_prev = [0] * n
        Vx_prev = [0] * n
        Ki_prev = [0] * n
        V_ant = V

        while error > 1E-6:

            Ki_prev = list(Ki)
            Ki = pp.DW_CalcKvalue(Array[Double](Vx), Array[Double](Vy), T, P)

            Vy_prev = list(Vy)
            Vx_prev = list(Vx)

            if V == 1.0:
                Vy = list(Vz)
                for i in range(n):
                    Vx[i] = Vy[i] / Ki[i]
            elif V == 0.0:
                Vx = list(Vz)
                for i in range(n):
                    Vy[i] = Ki[i] * Vx[i]
            else:
                for i in range(n):
                    Vy[i] = Vz[i] * Ki[i] / ((Ki[i] - 1) * V + 1)
                    Vx[i] = Vy[i] / Ki[i]

            error = 0.0
            for i in range(n):
                error += Math.Abs(Vx[i] - Vx_prev[i]) + Math.Abs(Vy[i] - Vy_prev[i])

            V_prev = V

            F = 0.0
            dF = 0.0
            for i in range(n):
                if Vz[i] > 0.0:
                    F += Vz[i] * (Ki[i] - 1) / (1 + V * (Ki[i] - 1))
                    dF -= Vz[i] * (Ki[i] - 1) ** 2 / (1 + V * (Ki[i] - 1)) ** 2
                    
            if Math.Abs(F) < 1E-6: break

            V = -F / dF + V_prev

            L = 1.0 - V

            if V <= 0.0:
                V = 0.0
                L = 1.0
                Vx = list(Vz)
                for i in range(n):
                    Vy[i] = Ki[i] * Vx[i]
            if V >= 1.0:
                V = 1.0
                L = 0.0
                Vy = list(Vz)
                for i in range(n):
                    Vx[i] = Vy[i] / Ki[i]

            if Math.Abs(V - V_prev) < 1E-6: break

            count += 1

            if count > 100: raise Exception("PT Flash: Maximum Iterations Reached") 

            print 'PT Flash iteration #' + str(count) + ' (V = ' + str(V) + ', convergence error = ' + str(error) + ')'

    Vyn = [0] * n
    Vxn = [0] * n

    for i in range(n):
        Vyn[i] = V * Vy[i]
        Vxn[i] = L * Vx[i]
            
    results = FlashCalculationResult(pp.DW_GetConstantProperties())

    results.MixtureMoleAmounts = List[Double](Vz)

    zerovec = pp.RET_NullVector()

    results.VaporPhaseMoleAmounts = List[Double](Vyn) 
    results.LiquidPhase1MoleAmounts = List[Double](Vxn) 
    results.LiquidPhase2MoleAmounts = List[Double](zerovec) 
    results.SolidPhaseMoleAmounts = List[Double](zerovec) 
    results.Kvalues = List[Double](Ki)

    results.IterationsTaken = count

    return results

udflash.PTFlash = PTFlash

PH/PS Flash


import clr

clr.AddReference('DWSIM.MathOps')

from DWSIM import *
from DWSIM.MathOps.MathEx import *
from DWSIM.Thermodynamics.PropertyPackages import *
from DWSIM.Thermodynamics.PropertyPackages.Auxiliary.FlashAlgorithms import *
from System import *
from System.Collections.Generic import *

# gets the first Flash Algorithm (= User-Defined) added to the simulation

udflash = Flowsheet.FlowsheetOptions.FlashAlgorithms[0]

def PTFlash(Vz, P, T, pp):

    n = len(Vz)

    L = 0.0
    V = 0.0

    Vy = [0] * n
    Vx = [0] * n

    Ki = [0] * n

    Vp = [0] * n

    count = 0

    alreadysolved = False

    if T > Common.Max(pp.RET_VTC(), Array[Double](Vz)):
        # supercritical mixture
        alreadysolved = True
        Vy = list(Vz)
        Vx = list(Vz)
        V = 1.0
        L = 0.0

    for i in range(n):
        Vp[i] = pp.AUX_PVAPi(i, T)
        Ki[i] = Vp[i] / P

    Px = 0.0
    for i in range(n):
        if Vp[i] != 0.0: Px += Vz[i] / Vp[i]
        
    Px = 1 / Px
    Pmin = Px
    
    Px = 0.0
    for i in range(n):
        Px += Vz[i] * Vp[i]
    
    Pmax = Px
    
    Pb = Pmax
    Pd = Pmin

    if Math.Abs(Pb - Pd) / Pb < 0.0000001:
        # one comp only
        alreadysolved = True
        Px = sum(Vp * Vz)
        if Px <= P:
            L = 1.0
            V = 0.0
            Vx = list(Vz)
            for i in range(n):
                Vy[i] = Vx[i] * Ki[i]
        else:
            L = 0.0
            V = 1.0
            Vy = list(Vz)
            for i in range(n):
                Vx[i] = Vy[i] / Ki[i]

    if alreadysolved == False:

        Vmin = 1.0#
        Vmax = 0.0#
        
        for i in range(n):
            if (Ki[i] * Vz[i] - 1) / (Ki[i] - 1) < Vmin: Vmin = (Ki[i] * Vz[i] - 1) / (Ki[i] - 1)
            if (1 - Vz[i]) / (1 - Ki[i]) > Vmax: Vmax = (1 - Vz[i]) / (1 - Ki[i])

        if Vmin < 0.0: Vmin = 0.0
        if Vmin == 1.0: Vmin = 0.0
        if Vmax == 0.0: Vmax = 1.0
        if Vmax > 1.0: Vmax = 1.0

        V = (Vmin + Vmax) / 2

        g = 0.0
        for i in range(n):
            g += Vz[i] * (Ki[i] - 1) / (V + (1 - V) * Ki[i])

        if g > 0.0: 
            Vmin = V 
        else: 
            Vmax = V

        V = Vmin + (Vmax - Vmin) / 2
        L = 1.0 - V

        for i in range(n):
            if Vz[i] != 0.0:
                Vy[i] = Vz[i] * Ki[i] / ((Ki[i] - 1) * V + 1)
                if Ki[i] != 0.0: 
                    Vx[i] = Vy[i] / Ki[i]
                else:
                    Vx[i] = Vz[i]
                if Vy[i] < 0.0: Vy[i] = 0.0
                if Vx[i] < 0.0: Vx[i] = 0.0
            else:
                Vy[i] = 0.0
                Vx[i] = 0.0

        error = 1E10

        Vy_prev = [0] * n
        Vx_prev = [0] * n
        Ki_prev = [0] * n
        V_ant = V

        while error > 1E-6:

            Ki_prev = list(Ki)
            Ki = pp.DW_CalcKvalue(Array[Double](Vx), Array[Double](Vy), T, P)

            Vy_prev = list(Vy)
            Vx_prev = list(Vx)

            if V == 1.0:
                Vy = list(Vz)
                for i in range(n):
                    Vx[i] = Vy[i] / Ki[i]
            elif V == 0.0:
                Vx = list(Vz)
                for i in range(n):
                    Vy[i] = Ki[i] * Vx[i]
            else:
                for i in range(n):
                    Vy[i] = Vz[i] * Ki[i] / ((Ki[i] - 1) * V + 1)
                    Vx[i] = Vy[i] / Ki[i]

            error = 0.0
            for i in range(n):
                error += Math.Abs(Vx[i] - Vx_prev[i]) + Math.Abs(Vy[i] - Vy_prev[i])

            V_prev = V

            F = 0.0
            dF = 0.0
            for i in range(n):
                if Vz[i] > 0.0:
                    F += Vz[i] * (Ki[i] - 1) / (1 + V * (Ki[i] - 1))
                    dF -= Vz[i] * (Ki[i] - 1) ** 2 / (1 + V * (Ki[i] - 1)) ** 2
                    
            if Math.Abs(F) < 1E-6: break

            V = -F / dF + V_prev

            L = 1.0 - V

            if V <= 0.0:
                V = 0.0
                L = 1.0
                Vx = list(Vz)
                for i in range(n):
                    Vy[i] = Ki[i] * Vx[i]
            if V >= 1.0:
                V = 1.0
                L = 0.0
                Vy = list(Vz)
                for i in range(n):
                    Vx[i] = Vy[i] / Ki[i]

            if Math.Abs(V - V_prev) < 1E-6: break

            count += 1

            if count > 100: raise Exception("PT Flash: Maximum Iterations Reached") 

            print 'PT Flash iteration #' + str(count) + ' (V = ' + str(V) + ', convergence error = ' + str(error) + ')'

    Vyn = [0] * n
    Vxn = [0] * n

    for i in range(n):
        Vyn[i] = V * Vy[i]
        Vxn[i] = L * Vx[i]
            
    results = FlashCalculationResult(pp.DW_GetConstantProperties())

    results.MixtureMoleAmounts = List[Double](Vz)

    zerovec = pp.RET_NullVector()

    results.VaporPhaseMoleAmounts = List[Double](Vyn) 
    results.LiquidPhase1MoleAmounts = List[Double](Vxn) 
    results.LiquidPhase2MoleAmounts = List[Double](zerovec) 
    results.SolidPhaseMoleAmounts = List[Double](zerovec) 
    results.Kvalues = List[Double](Ki)

    results.IterationsTaken = count

    return results

udflash.PTFlash = PTFlash

def Herror(Href, Vz, P, Test, pp):

    tresult = PTFlash(Vz, P, Test, pp)

    Vw = tresult.GetVaporPhaseMassFraction()
    Lw = tresult.GetLiquidPhase1MassFraction()

    Vy = tresult.GetVaporPhaseMoleFractions()
    Vx = tresult.GetLiquidPhase1MoleFractions()

    Hv = 0.0
    Hl = 0.0

    if Vw > 0.0: Hv = pp.DW_CalcEnthalpy(Array[Double](Vy), Test, P, State.Vapor)
    if Lw > 0.0: Hl = pp.DW_CalcEnthalpy(Array[Double](Vx), Test, P, State.Liquid)

    # calculate mixture enthalpy in kJ/kg

    Hest = Vw * Hv + Lw * Hl

    return (Href - Hest)

def PHFlash(Vz, P, H, Test, pp):

    if Test == 0.0: Test = 273.15

    T = Test

    fx = 1E6

    count = 0

    while Math.Abs(fx) > 1E-4: 
        fx = Herror(H, Vz, P, T, pp)
        fx2 = Herror(H, Vz, P, T + 0.1, pp)
        dfdx = (fx2 - fx) / 0.1
        dx = fx / dfdx
        T = T - 0.7 * dx
        count += 1
        if count > 25: raise Exception("PH Flash: Maximum Iterations Reached") 
        print 'PH Flash iteration #' + str(count) + ' (T = ' + str(T) + ', current enthalpy error = ' + str(fx) + ' kJ/kg)'

    result = PTFlash(Vz, P, T, pp)

    result.CalculatedTemperature = T

    return result

# sets the PH Flash function

udflash.PHFlash = PHFlash

def Serror(Sref, Vz, P, Test, pp):

    tresult = PTFlash(Vz, P, Test, pp)

    Vw = tresult.GetVaporPhaseMassFraction()
    Lw = tresult.GetLiquidPhase1MassFraction()

    Vy = tresult.GetVaporPhaseMoleFractions()
    Vx = tresult.GetLiquidPhase1MoleFractions()

    Sv = 0.0
    Sl = 0.0

    if Vw > 0.0: Sv = pp.DW_CalcEntropy(Array[Double](Vy), Test, P, State.Vapor)
    if Lw > 0.0: Sl = pp.DW_CalcEntropy(Array[Double](Vx), Test, P, State.Liquid)

    # calculate mixture entropy in kJ/[kg.K]

    Sest = Vw * Sv + Lw * Sl

    return (Sref - Sest)

def PSFlash(Vz, P, S, Test, pp):

    if Test == 0.0: Test = 273.15

    T = Test

    fx = 1E6

    count = 0

    while Math.Abs(fx) > 1E-4: 
        fx = Serror(S, Vz, P, T, pp)
        fx2 = Serror(S, Vz, P, T + 0.1, pp)
        dfdx = (fx2 - fx) / 0.1
        dx = fx / dfdx
        T = T - 0.7 * dx
        count += 1
        if count > 25: raise Exception("PS Flash: Maximum Iterations Reached") 
        print 'PS Flash iteration #' + str(count) + ' (T = ' + str(T) + ', current entropy error = ' + str(fx) + ' kJ/[kg.K])'

    result = PTFlash(Vz, P, T, pp)

    result.CalculatedTemperature = T

    return result

# sets the PS Flash function

udflash.PSFlash = PSFlash

PVF/TVF Flash

Usually you don't need to set/define a function to Pressure-VaporFraction and/or Temperature-VaporFraction Flashes unless you're working with one or more of the following:

  • Phase Envelopes (Binary/Mixture)
  • Centrifugal Pump (NPSH calculation)
  • Force Bubble and Dew point calculation on Material Streams

Download Samples

More Information