binaries.f90 Source File


This file depends on

sourcefile~~binaries.f90~~EfferentGraph sourcefile~binaries.f90 binaries.f90 sourcefile~ar_models.f90 ar_models.f90 sourcefile~binaries.f90->sourcefile~ar_models.f90 sourcefile~constants.f90 constants.f90 sourcefile~binaries.f90->sourcefile~constants.f90 sourcefile~equilibria_state.f90 equilibria_state.f90 sourcefile~binaries.f90->sourcefile~equilibria_state.f90 sourcefile~math.f90 math.f90 sourcefile~binaries.f90->sourcefile~math.f90 sourcefile~ar_models.f90->sourcefile~constants.f90 sourcefile~ar_models.f90->sourcefile~math.f90 sourcefile~base.f90~2 base.f90 sourcefile~ar_models.f90->sourcefile~base.f90~2 sourcefile~equilibria_state.f90->sourcefile~constants.f90 sourcefile~math.f90->sourcefile~constants.f90 sourcefile~auxiliar.f90 auxiliar.f90 sourcefile~math.f90->sourcefile~auxiliar.f90 sourcefile~continuation.f90 continuation.f90 sourcefile~math.f90->sourcefile~continuation.f90 sourcefile~linalg.f90 linalg.f90 sourcefile~math.f90->sourcefile~linalg.f90 sourcefile~auxiliar.f90->sourcefile~constants.f90 sourcefile~substance.f90 substance.f90 sourcefile~base.f90~2->sourcefile~substance.f90 sourcefile~continuation.f90->sourcefile~constants.f90 sourcefile~continuation.f90->sourcefile~auxiliar.f90 sourcefile~continuation.f90->sourcefile~linalg.f90 sourcefile~linalg.f90->sourcefile~constants.f90 sourcefile~linalg.f90->sourcefile~auxiliar.f90 sourcefile~substance.f90->sourcefile~constants.f90

Files dependent on this one

sourcefile~~binaries.f90~~AfferentGraph sourcefile~binaries.f90 binaries.f90 sourcefile~equilibria.f90 equilibria.f90 sourcefile~equilibria.f90->sourcefile~binaries.f90 sourcefile~yaeos.f90 yaeos.f90 sourcefile~yaeos.f90->sourcefile~equilibria.f90

Source Code

module yaeos__equilibria_binaries
   !! Module with routines particular to binary mixtures.
   use yaeos__constants, only: pr
   use yaeos__models_ar, only: ArModel
   use yaeos__equilibria_equilibrium_state, only: EquilibriumState
   use yaeos__math, only: solve_system

   private
   public :: find_llcl, three_phase_line_F_solve, three_phase_line_F
   public :: BinaryThreePhase, binary_llv_from_cep

   type :: BinaryThreePhase
      !! # `BinaryThreePhase`
      !! Structure to hold the results of a binary LLV line calculation.
      !! # Description
      !! This structure holds the results of a binary LLV line calculation.
      !! Pressures are calculated using the composition of the liquid phase `x`.
      real(pr), allocatable :: x1(:) !! Mole fraction of component 1 in liquid phase x
      real(pr), allocatable :: y1(:) !! Mole fraction of component 1 in liquid phase y
      real(pr), allocatable :: w1(:) !! Mole fraction of component 1 in vapor phase w
      real(pr), allocatable :: Vx(:) !! Volume of liquid phase x [L/mol]
      real(pr), allocatable :: Vy(:) !! Volume of liquid phase y [L/mol]
      real(pr), allocatable :: Vw(:) !! Volume of vapor phase w [L/mol]
      real(pr), allocatable :: T(:) !! Temperature [K]
      real(pr), allocatable :: P(:) !! Pressure [bar]
   end type BinaryThreePhase

   type :: LLVVarEnum
      integer :: x1=1
      integer :: x2=2
      integer :: y1=3
      integer :: y2=4
      integer :: w1=5
      integer :: w2=6
      integer :: Vx=7
      integer :: Vy=8
      integer :: Vw=9
      integer :: T=10
   end type LLVVarEnum

   type(LLVVarEnum), parameter :: llv_vars = LLVVarEnum()

   integer, parameter :: n_llv_vars = 10

contains

   subroutine find_llcl(model, z0, zi, P, a, V, T)
      !! # `find_llcl`
      !! Find an initial guess for the critical L-L line of a binary mixture.
      !!
      !! # Description
      !!
      !!
      !! # Examples
      !!
      !!
      !! # References
      !! [1] M. Cismondi, M.L. Michelsen, Global phase equilibrium
      !! calculations:
      !! Critical lines, critical end points and liquid–liquid–vapour
      !! equilibrium in binary mixtures, The Journal of Supercritical Fluids 39
      !!  (2007) 287–295. https://doi.org/10.1016/j.supflu.2006.03.011.
      implicit none
      class(ArModel), intent(in) :: model !! Thermodynamic model to use
      real(kind=pr), intent(in) :: P !! Pressure [bar]
      real(kind=pr), intent(in) :: z0(2) !! Mole fractions of original fluid
      real(kind=pr), intent(in) :: zi(2) !! Mole fractions of new fluid
      real(kind=pr), intent(out) :: a !! Mole fraction of new fluid
      real(kind=pr), intent(out) :: V !! Volume [L/mol]
      real(kind=pr), intent(in out) :: T !! Temperature [K]

      real(kind=pr) :: z(2), lnphi(2), dlnphidn(2, 2)

      integer :: i
      integer :: tries
      real(kind=pr) :: as(50)
      real(kind=pr) :: lambdas(50)

      do tries = 1, 2
         do i = 1, 50
            a = real(i, pr) / 51.
            z = a * zi + (1 - a) * z0
            call model%lnphi_pt(&
               z, P=P, T=T, V=V, lnPhi=lnPhi, dlnphidn=dlnphidn, root_type=&
               "liquid")
            lambdas(i) = 1 - dlnphidn(1, 2)

            as(i) = a
         end do

         i = minloc(lambdas, dim=1)
         a = as(i)
         z = a * zi + (1 - a) * z0

         if (lambdas(i) > 0) then
            do while (lambdas(i) > 0)
               T = T - 1
               call model%lnphi_pt(z, P=P, T=T, V=V, lnPhi=lnPhi, dlnphidn=&
                  dlnphidn, root_type="liquid")
               lambdas(i) = 1 - dlnphidn(1, 2)
            end do
         else
            do while (lambdas(i) < 0)
               T = T + 1
               call model%lnphi_pt(z, P=P, T=T, V=V, lnPhi=lnPhi, dlnphidn=&
                  dlnphidn, root_type="liquid")
               lambdas(i) = 1 - dlnphidn(1, 2)
            end do
         end if
      end do
   end subroutine find_llcl

   type(BinaryThreePhase) function binary_llv_from_cep(model, cep) result(llv)
      !! # `binary_llv_from_cep`
      !! Calculate the LLV line from a converged critical end point (CEP).
      !!
      !! # Description
      !! From a converged critical end point (CEP) of a binary mixture, this
      !! function calculates the three-phase line (LLV) by solving the
      !! corresponding system of equations (defined at [[three_phase_line_F]])
      !! at each point.
      !! To trace the whole line a continuation method is used to obtain good
      !! initial guesses for each point.
      !! The specification used to trace the line is initially the difference
      !! between the mole fractions of the two liquid phases, and then it is
      !! switched to temperature.
      class(ArModel), intent(in) :: model !! Thermodynamic model to use
      type(EquilibriumState), intent(in) :: cep !! Converged critical end point.

      integer, parameter :: nvars = 10

      real(pr) :: X(nvars)
      real(pr) :: dFdS(nvars), F(nvars), dF(nvars, nvars)
      integer :: ns
      real(pr) ::  dS, S, dXdS(nvars)

      real(pr) :: T, P, Vx, Vy, Vw
      real(pr) :: xx(2), y(2), w(2)

      real(pr), allocatable :: Ts(:), Ps(:)

      integer :: points
      integer :: iters

      real(pr) :: delta
      real(pr) :: imin

      dFdS = 0
      dFdS(10) = -1

      delta = 0.025 * minval(cep%x)
      xx(1) = cep%x(1) - delta
      y(1) = cep%x(1) + delta
      w(1) = cep%y(1)

      xx(2) = 1 - xx(1)

      y(2) = 1 - y(1)
      w(2) = 1 - w(1)

      T = cep%T
      P = cep%P

      call model%volume(xx, P, T, Vx, root_type="liquid")
      call model%volume(y, P, T, Vy, root_type="liquid")
      Vw = cep%Vy

      X = log([&
         xx, &
         y, &
         w, &
         Vx, &
         Vy, &
         Vw, &
         T &
         ])
      T = HUGE(1._pr)
      ns = 0
      S = y(1) - xx(1)
      dS = 2*delta

      points = 0
      F = 0
      P = 10
      allocate(&
         llv%T(0), llv%P(0), llv%x1(0), llv%y1(0), llv%w1(0), &
         llv%Vx(0), llv%Vy(0), llv%Vw(0))
      do while(&
            maxval(abs(F)) < 1e-9 .and. P > 1e-8 .and. points < 1000 &
         )
         points = points + 1
         call three_phase_line_F_solve(model, X, ns, S, F, dF, iters)
         if (iters >= 1000) then
            X = X - 0.9 * dXdS * dS 
            S = S - 0.9 * dS
            call three_phase_line_F_solve(model, X, ns, S, F, dF, iters)
         end if

         xx = exp(X(llv_vars%x1:llv_vars%x2))
         y = exp(X(llv_vars%y1:llv_vars%y2))
         w = exp(X(llv_vars%w1:llv_vars%w2))
         Vx = exp(X(llv_vars%Vx))
         Vy = exp(X(llv_vars%Vy))
         Vw = exp(X(llv_vars%Vw))
         T = exp(X(llv_vars%T))
         call model%pressure(xx, Vx, T, P)

         if (maxval(abs(F)) < 1e-9 .and. P > 0) then
            llv%T = [llv%T, T]
            llv%P = [llv%P, P]
            llv%x1 = [llv%x1, xx(1)]
            llv%y1 = [llv%y1, y(1)]
            llv%w1 = [llv%w1, w(1)]
            llv%Vx = [llv%Vx, Vx]
            llv%Vy = [llv%Vy, Vy]
            llv%Vw = [llv%Vw, Vw]
         end if


         dXdS = solve_system(dF, -dFdS)

         if (T < llv%T(1) - 5) then
            ns = llv_vars%T
            dS = dXdS(ns) * dS
            dS = sign(max(abs(dS), 0.001_pr), dS)
            dXdS = dXdS / dXdS(ns)
         end if

         ! do while(&
         !    abs(Vx - exp(X(llv_vars%Vx) + dS*dXdS(llv_vars%Vx))) > 0.5*Vx &
         !    .or. abs(Vy - exp(X(llv_vars%Vy) + dS*dXdS(llv_vars%Vy))) > 0.5*Vy &
         !    .or. abs(Vw - exp(X(llv_vars%Vw) + dS*dXdS(llv_vars%Vw))) > 0.5*Vw &
         !    .or. abs(T - exp(X(llv_vars%T) + dS*dXdS(llv_vars%T))) > 5 &
         !    )
         !    dS = dS/2
         ! end do

         X = X + dXdS * dS

         if (ns == 0) then
            S = exp(X(llv_vars%y1)) - exp(X(llv_vars%x1))
         else if (ns == -1) then
            S = X(llv_vars%Vx) - X(llv_vars%Vy)
         else
            S = X(ns)
         end if
      end do
   end function binary_llv_from_cep

   subroutine three_phase_line_F(model, Xvars, ns, S, F, dF)
      !! # `three_phase_line_F`
      !!
      !! # Description
      !! Calculate the function vector and Jacobian for the three-phase
      !! line (LLV) of a binary mixture. Phases are defined as `x`, `y` and `w`.
      !! Which are two liquid phases and one vapor phase respectively.
      !!
      !! The system of equations is defined as:
      !! \[
      !! \begin{align*}
      !! & \ln \phi_i^{(x)} - \ln \phi_i^{(w)} = 0 \\
      !! & \ln \phi_i^{(y)} - \ln \phi_i^{(w)} = 0 \\
      !! & P^{(x)} - P^{(w)} = 0 \\
      !! & P^{(y)} - P^{(w)} = 0 \\
      !! & \text{specification}
      !! \begin{cases}
      !! y_1 - x_1 - S = 0 & \text{if } ns = 0 \\
      !! \ln v_x - \ln v_y - S = 0 & \text{if } ns = -1 \\
      !! X_{ns} - S = 0 & \text{otherwise}
      !! \end{cases}
      !! \end{align*}
      !! \]
      !!
      !! # References
      !! [1] M. Cismondi, M.L. Michelsen, Global phase equilibrium
      !! calculations:
      !! Critical lines, critical end points and liquid–liquid–vapour
      !! equilibrium in binary mixtures, The Journal of Supercritical Fluids 39
      !!  (2007) 287–295. https://doi.org/10.1016/j.supflu.2006.03.011.
      use yaeos__math, only: derivative_dxk_dni
      class(ArModel), intent(in) :: model !! Thermodynamic model to use
      real(kind=pr), intent(in) :: Xvars(:) !! Input vector
      integer, intent(in) :: ns !! Specified variable index
      real(pr), intent(in) :: S !! Specified variable value
      real(kind=pr), intent(out) :: F(:) !! Function vector
      real(kind=pr), intent(out) :: dF(:, :) !! Jacobian

      ! Variables describing the three phases
      real(pr) :: x1, y1, w1
      real(pr) :: vx, vy, vw, T
      real(pr) :: x(2), y(2), w(2)

      real(pr) :: Px, Py, Pw
      real(pr) :: isofug_1(2), isofug_2(2)
      real(pr) :: Peq_1, Peq_2

      real(pr) :: lnf_x(2), lnf_y(2), lnf_w(2)
      real(pr) :: dlnfxdn(2, 2), dlnfydn(2, 2), dlnfwdn(2, 2)
      real(pr) :: dlnfxdv(2), dlnfydv(2), dlnfwdv(2)
      real(pr) :: dlnfxdP(2), dlnfydP(2), dlnfwdP(2)
      real(pr) :: dlnfxdT(2), dlnfydT(2), dlnfwdT(2)

      real(pr) :: dPxdN(2), dPydN(2), dPwdN(2)
      real(pr) :: dPxdV, dPydV, dPwdV
      real(pr) :: dPxdT, dPydT, dPwdT
      real(pr) :: dxdn(2, 2)

      x = exp(XVars(llv_vars%x1:llv_vars%x2))
      y = exp(XVars(llv_vars%y1:llv_vars%y2))
      w = exp(XVars(llv_vars%w1:llv_vars%w2))
      vx = exp(Xvars(llv_vars%Vx))
      vy = exp(Xvars(llv_vars%Vy))
      vw = exp(Xvars(llv_vars%Vw))
      T =  exp(Xvars(llv_vars%T))
      x1 = x(1)
      y1 = y(1)

      ! x = [x1, 1-x1]
      ! y = [y1, 1-y1]
      ! w = [w1, 1-w1]

      call model%lnfug_vt(&
         x, Vx, T, Px, lnf_x, &
         dlnfxdV, dlnfxdT, dlnfxdn, &
         dPxdV, dPxdT, dPxdn &
         )
      call model%lnfug_vt(&
         y, Vy, T, Py, lnf_y, &
         dlnfydV, dlnfydT, dlnfydn, &
         dPydV, dPydT, dPydn &
         )
      call model%lnfug_vt(&
         w, Vw, T, Pw, lnf_w, &
         dlnfwdV, dlnfwdT, dlnfwdn, &
         dPwdV, dPwdT, dPwdn &
         )


      ! Calculate isofugacity coefficients
      isofug_1 = lnf_x - lnf_w
      isofug_2 = lnf_y - lnf_w
      Peq_1 = Px - Pw
      Peq_2 = Py - Pw

      F(1:2) = isofug_1
      F(3:4) = isofug_2
      F(5) = Peq_1
      F(6) = Peq_2
      F(7) = sum(x) - 1
      F(8) = sum(y) - 1
      F(9) = sum(w) - 1

      if (ns == 0) then
         F(10) = y1 - x1 - S
      else if (ns == -1) then
         F(10) = log(vx / vy) - S
      else
         F(10) = Xvars(ns) - S
      end if

      df = 0

      ! Derivatives wrt x1
      df(1, llv_vars%x1) = dlnfxdn(1, 1)
      df(2, llv_vars%x1) = dlnfxdn(2, 1)
      df(3:4, llv_vars%x1) = 0
      df(5, llv_vars%x1) = dPxdN(1)
      df(6, llv_vars%x1) = 0
      df(7, llv_vars%x1) = 1

      ! Derivatives wrt x2
      df(1, llv_vars%x2) = dlnfxdn(1, 2)
      df(2, llv_vars%x2) = dlnfxdn(2, 2)
      df(3:4, llv_vars%x2) = 0
      df(5, llv_vars%x2) = dPxdN(2)
      df(6, llv_vars%x2) = 0
      df(7, llv_vars%x2) = 1

      ! Derivatives wrt y1
      df(1:2, llv_vars%y1) = 0
      df(3, llv_vars%y1) = dlnfydn(1, 1)
      df(4, llv_vars%y1) = dlnfydn(2, 1)
      df(5, llv_vars%y1) = 0
      df(6, llv_vars%y1) = dPydN(1)
      df(8, llv_vars%y1) = 1

      ! Derivatives wrt y2
      df(1:2, llv_vars%y2) = 0
      df(3, llv_vars%y2) = dlnfydn(1, 2)
      df(4, llv_vars%y2) = dlnfydn(2, 2)
      df(5, llv_vars%y2) = 0
      df(6, llv_vars%y2) = dPydN(2)
      df(8, llv_vars%y2) = 1

      ! Derivatives wrt w1
      df(1, llv_vars%w1) =  - dlnfwdn(1, 1)
      df(2, llv_vars%w1) =  - dlnfwdn(2, 1)
      df(3, llv_vars%w1) =  - dlnfwdn(1, 1)
      df(4, llv_vars%w1) =  - dlnfwdn(2, 1)
      df(5, llv_vars%w1) = - dPwdN(1)
      df(6, llv_vars%w1) = - dPwdN(1)
      df(9, llv_vars%w1) = 1

      ! Derivatives wrt w2
      df(1, llv_vars%w2) =  - dlnfwdn(1, 2)
      df(2, llv_vars%w2) =  - dlnfwdn(2, 2)
      df(3, llv_vars%w2) =  - dlnfwdn(1, 2)
      df(4, llv_vars%w2) =  - dlnfwdn(2, 2)
      df(5, llv_vars%w2) = - dPwdN(2)
      df(6, llv_vars%w2) = - dPwdN(2)
      df(9, llv_vars%w2) = 1

      ! Derivatives wrt Vx
      df(1, llv_vars%Vx) = dlnfxdv(1)
      df(2, llv_vars%Vx) = dlnfxdv(2)
      df(3:4, llv_vars%Vx) = 0
      df(5, llv_vars%Vx) = dPxdV
      df(6, llv_vars%Vx) = 0

      ! Derivatives wrt Vy
      df(1:2, llv_vars%Vy) = 0
      df(3, llv_vars%Vy) = dlnfydv(1)
      df(4, llv_vars%Vy) = dlnfydv(2)
      df(5, llv_vars%Vy) = 0
      df(6, llv_vars%Vy) = dPydV

      ! Derivatives wrt vw
      df(1, llv_vars%Vw) = -dlnfwdv(1)
      df(2, llv_vars%Vw) = -dlnfwdv(2)
      df(3, llv_vars%Vw) = -dlnfwdv(1)
      df(4, llv_vars%Vw) = -dlnfwdv(2)
      df(5, llv_vars%Vw) = -dPwdV
      df(6, llv_vars%Vw) = -dPwdV

      ! Derivatives wrt T
      df(1:2, llv_vars%T) = dlnfxdT - dlnfwdT
      df(3:4, llv_vars%T) = dlnfydT - dlnfwdT
      df(5, llv_vars%T) = dPxdT - dPwdT
      df(6, llv_vars%T) = dPydT - dPwdT

      if (ns == 0) then
         df(10, llv_vars%x1) = -1
         df(10, llv_vars%y1) = 1
      else if (ns == -1) then
         df(10, llv_vars%Vx) = 1/vx
         df(10, llv_vars%Vy) = -1/vy
      else
         df(10, ns) = 1
      end if

      ! Multiply by variable values due to log-transform
      df(:, llv_vars%x1) = df(:, llv_vars%x1) * x(1)
      df(:, llv_vars%x2) = df(:, llv_vars%x2) * x(2)
      df(:, llv_vars%y1) = df(:, llv_vars%y1) * y(1)
      df(:, llv_vars%y2) = df(:, llv_vars%y2) * y(2)
      df(:, llv_vars%w1) = df(:, llv_vars%w1) * w(1)
      df(:, llv_vars%w2) = df(:, llv_vars%w2) * w(2)
      df(:, llv_vars%Vx) = df(:, llv_vars%Vx) * vx
      df(:, llv_vars%Vy) = df(:, llv_vars%Vy) * vy
      df(:, llv_vars%Vw) = df(:, llv_vars%Vw) * vw
      df(:, llv_vars%T) = df(:, llv_vars%T) * T
   end subroutine three_phase_line_F

   subroutine three_phase_line_F_solve(model, X, ns, S, F, dF, iters)
      !! # `three_phase_line_F_solve`
      !!
      !! # Description
      !! Solve the system of equations defined in `three_phase_line_F` using
      !! a Newton-Raphson method.
      !! It will make a maximum of 50 iterations to converge. With a tolerance
      !! of 1e-9 in the maximum absolute value of the function vector or the
      !! step vector.
      use yaeos__math, only: solve_system
      class(ArModel), intent(in) :: model !! Thermodynamic model to use
      real(kind=pr), intent(in out) :: X(:) !! Input/output vector
      integer, intent(in) :: ns !! Specified variable index
      real(pr), intent(in) :: S !! Specified variable value
      real(kind=pr), intent(out) :: F(:) !! Function vector
      real(kind=pr), intent(out) :: dF(:, :) !! Jacobian
      integer, optional, intent(out) :: iters !! Number of iterations performed

      integer :: i
      integer :: max_tries
      real(kind=pr) :: tol
      real(kind=pr) :: res_norm
      real(kind=pr) :: dX(size(X))
      real(kind=pr) :: Xold(size(X))

      tol = 1e-9_pr
      max_tries = 100

      dX = 10
      F = 10
      do i = 1, max_tries
         call three_phase_line_F(model, X, ns, S, F, dF)
         res_norm = maxval(abs(F))
         if (res_norm < tol .or. maxval(abs(dX)) < 1e-9) exit

         Xold = X

         ! Solve the linear system dF * dX = -F
         dX = solve_system(dF, -F)

         X = Xold + dX


         if (present(iters)) iters = i
      end do
   end subroutine three_phase_line_F_solve
end module yaeos__equilibria_binaries