uniquac.f90 Source File


Source Code

module yaeos__models_ge_uniquac
   !! # UNIQUAC module
   !! UNIQUAC (**uni**versal **qua**si**c**hemical) Excess Gibbs free energy
   !! model.
   !!
   !! ## References
   !! 1. Maurer, G., & Prausnitz, J. M. (1978). On the derivation and extension
   !! of the UNIQUAC equation. Fluid Phase Equilibria, 2(2), 91-99.
   !! 2. Gmehling, Jurgen, Barbel Kolbe, Michael Kleiber, and Jurgen Rarey.
   !! Chemical Thermodynamics for Process Simulation. 1st edition. Weinheim:
   !! Wiley-VCH, 2012.
   !! 3. Caleb Bell and Contributors (2016-2024). Thermo: Chemical properties
   !! component of Chemical Engineering Design Library (ChEDL)
   !! https://github.com/CalebBell/thermo.
   !!
   use yaeos__constants, only: pr, R
   use yaeos__models_ge, only: GeModel
   use yaeos__math, only: derivative_dxk_dni, derivative_d2xk_dnidnj
   implicit none

   type, extends(GeModel) :: UNIQUAC
      !! # UNIQUAC model
      !!
      !! # Description
      !! UNIQUAC (**uni**versal **qua**si**c**hemical) Excess Gibbs free energy
      !! model.
      !!
      !! \[
      !! \frac{G^E}{RT} = \sum_k n_k \ln\frac{\phi_k}{x_k}
      !! + \frac{z}{2}\sum_k q_k n_k \ln\frac{\theta_k}{\phi_k}
      !! - \sum_k q_k n_k \ln\left(\sum_l \theta_l \tau_{kl} \right)
      !! \]
      !!
      !! With:
      !!
      !! \[ x_k = \frac{n_k}{\sum_l n_l} \]
      !!
      !! \[ \phi_k = \frac{r_k n_k}{\sum_l r_l n_l} \]
      !!
      !! \[ \theta_k = \frac{q_k n_k}{\sum_l q_l n_l} \]
      !!
      !! \[ \tau_{lk} = \exp \left[\frac{-\Delta U_{lk}}{R T} \right] \]
      !!
      !! \[
      !! \frac{-\Delta U_{lk}}{R T} = a_{lk}+\frac{b_{lk}}{T}+c_{lk}\ln T +
      !! d_{lk}T + e_{lk}{T^2}
      !! \]
      !!
      !! # Example
      !!
      !! ```fortran
      !! use yaeos, only: pr, setup_uniquac, UNIQUAC
      !!
      !! integer, parameter :: nc = 3
      !!
      !! real(pr) :: rs(nc), qs(nc)
      !! real(pr) :: b(nc, nc)
      !! real(pr) :: n(nc)
      !!
      !! real(pr) :: ln_gammas(nc), T
      !!
      !! type(UNIQUAC) :: model
      !!
      !! rs = [0.92_pr, 2.1055_pr, 3.1878_pr]
      !! qs = [1.4_pr, 1.972_pr, 2.4_pr]
      !!
      !! T = 298.15_pr
      !!
      !! ! Calculate bij from DUij. We need -DU/R to get bij
      !! b(1, :) = [0.0_pr, -526.02_pr, -309.64_pr]
      !! b(2, :) = [318.06_pr, 0.0_pr, 91.532_pr]
      !! b(3, :) = [-1325.1_pr, -302.57_pr, 0.0_pr]
      !!
      !! model = setup_uniquac(qs, rs, bij=b)
      !!
      !! n = [0.8_pr, 0.1_pr, 0.2_pr]
      !!
      !! call model%ln_activity_coefficient(n, T, ln_gammas)
      !!
      !! print *, exp(ln_gammas) ! [8.856, 0.860, 1.425]
      !!
      !! ```
      !!
      real(pr) :: z = 10.0_pr
      !! Model coordination number
      real(pr), allocatable :: qs(:)
      !! Molecule's relative areas \(Q_i\)
      real(pr), allocatable :: rs(:)
      !! Molecule's relative volumes \(R_i\)
      real(pr), allocatable :: aij(:,:)
      !! Interaction parameters matrix \(a_{ij}\)
      real(pr), allocatable :: bij(:,:)
      !! Interaction parameters matrix \(b_{ij}\)
      real(pr), allocatable :: cij(:,:)
      !! Interaction parameters matrix \(c_{ij}\)
      real(pr), allocatable :: dij(:,:)
      !! Interaction parameters matrix \(d_{ij}\)
      real(pr), allocatable :: eij(:,:)
      !! Interaction parameters matrix \(e_{ij}\)
   contains
      procedure :: excess_gibbs
      procedure :: taus
   end type UNIQUAC

contains
   subroutine excess_gibbs(self, n, T, Ge, GeT, GeT2, Gen, GeTn, Gen2)
      !! Calculate the excess Gibbs free energy and its derivatives of the
      !! UNIQUAC model.
      !!
      class(UNIQUAC), intent(in) :: self
      !! UNIQUAC model
      real(pr), intent(in) :: n(:)
      !! Moles vector [mol]
      real(pr), intent(in) :: T
      !! Temperature [K]
      real(pr), optional, intent(out) :: Ge
      !! Excess Gibbs energy
      real(pr), optional, intent(out) :: GeT
      !! \(\frac{dG^E}{dT}\)
      real(pr), optional, intent(out) :: GeT2
      !! \(\frac{d^2G^E}{dT^2}\)
      real(pr), optional, intent(out) :: Gen(size(n))
      !! \(\frac{dG^E}{dn}\)
      real(pr), optional, intent(out) :: GeTn(size(n))
      !! \(\frac{d^2G^E}{dTdn}\)
      real(pr), optional, intent(out) :: Gen2(size(n), size(n))
      !! \(\frac{d^2G^E}{dn^2}\)

      ! Main terms
      real(pr) :: thetak(size(n))
      real(pr) :: dthetak_dni(size(n), size(n))
      real(pr) :: d2thetak_dnidnj(size(n), size(n), size(n))

      real(pr) :: phik(size(n))
      real(pr) :: dphik_dn(size(n), size(n))
      real(pr) :: d2phik_dnidnj(size(n), size(n), size(n))

      real(pr) :: tau(size(n), size(n))
      real(pr) :: dtau(size(n), size(n))
      real(pr) :: d2tau(size(n), size(n))

      ! Indexes and lofical for optional arguments
      integer :: i, j, k

      logical :: dt, dt2, dn, dtn, dn2, dt_or_dtn

      ! Auxiliars
      integer :: nc
      real(pr) :: n_tot
      real(pr) :: xk(size(n))
      real(pr) :: r_i, q_i, r_j, q_j, r_k, q_k
      real(pr) :: sum_nq, sum_nr
      real(pr) :: Ge_comb, Ge_res
      real(pr) :: Ge_aux

      ! Auxiliars for temperature derivatives
      real(pr) :: sum_thetal_tau_lk(size(n))
      real(pr) :: sum_theta_l_dtau_lk(size(n))
      real(pr) :: sum_theta_l_d2tau_lk(size(n))

      real(pr) :: GeT_aux, GeT2_aux, diff_aux(size(n))

      ! Auxiliars for compositionals derivatives
      real(pr) :: Gen_comb(size(n)), Gen_res(size(n))
      real(pr) :: Gen_aux(size(n))

      real(pr) :: dxk_dni(size(n), size(n))
      real(pr) :: dln_nk_dni(size(n), size(n))

      ! Auxiliars for second compositionals derivatives
      ! terms of the math expression
      real(pr) :: trm1, trm2, trm3, trm4, trm5, trm6

      real(pr) :: d2xk_dnidnj(size(n), size(n), size(n))
      real(pr) :: sum_d2theta_tau_lk(size(n))
      real(pr) :: Gen2_aux(size(n), size(n))

      ! cross derivative auxiliars
      real(pr) :: sum_dtheta_l_dtau_lk(size(n), size(n))
      real(pr) :: sum_dtheta_l_tau_lk(size(n), size(n))
      real(pr) :: GeTn_aux(size(n))

      ! =======================================================================
      ! Logical variables
      ! -----------------------------------------------------------------------
      dt = present(GeT)
      dt2 = present(GeT2)
      dn = present(Gen)
      dtn = present(GeTn)
      dn2 = present(Gen2)

      dt_or_dtn = dt .or. dtn

      ! =======================================================================
      ! Auxiliars
      ! -----------------------------------------------------------------------
      nc = size(n)

      n_tot = sum(n)

      xk = n / n_tot

      sum_nq = sum(n * self%qs)
      sum_nr = sum(n * self%rs)
      ! =======================================================================
      ! tau call (temperature dependence term)
      ! -----------------------------------------------------------------------
      if (dt_or_dtn .and. .not. dt2) call self%taus(T, tau, dtau)
      if (.not. dt_or_dtn .and. dt2) call self%taus(T, tau, dtau, d2tau)
      if (dt_or_dtn .and. dt2) call self%taus(T, tau, dtau, d2tau)
      if (.not. dt_or_dtn .and. .not. dt2) call self%taus(T, tau)

      ! =======================================================================
      ! Mole fractions derivatives
      ! -----------------------------------------------------------------------
      if (dn .or. dtn .or. dn2) then
         dxk_dni = derivative_dxk_dni(n)
      end if

      if (dn2) then
         d2xk_dnidnj = derivative_d2xk_dnidnj(n)
      end if

      ! =======================================================================
      ! theta_k
      ! -----------------------------------------------------------------------
      thetak = n * self%qs / sum_nq

      if (dn .or. dn2 .or. dtn) then
         dthetak_dni = 0
         do concurrent(k=1:nc, i=1:nc)
            if (i == k) then
               dthetak_dni(i,i) = &
                  (self%qs(i) * sum_nq - n(i) * self%qs(i)**2) / sum_nq**2
            else
               dthetak_dni(k,i) = -n(k) * self%qs(i) * self%qs(k) / sum_nq**2
            end if
         end do
      end if

      if (dn2) then
         d2thetak_dnidnj = 0
         do concurrent(k=1:nc, i=1:nc, j=1:nc)
            if (i==k .and. j==k) then
               q_i = self%qs(i)

               d2thetak_dnidnj(k,i,j) = (&
                  2.0_pr * (q_i**3 * n(i) - q_i**2 * sum_nq) / sum_nq**3 &
                  )
            else if (i==k) then
               q_i = self%qs(i)
               q_j = self%qs(j)

               d2thetak_dnidnj(k,i,j) = (&
                  (2.0_pr * n(i) * q_i**2 * q_j - q_i*q_j*sum_nq) / sum_nq**3 &
                  )
            else if (j==k) then
               q_i = self%qs(i)
               q_j = self%qs(j)

               d2thetak_dnidnj(k,i,j) = (&
                  (2.0_pr * n(j) * q_j**2 * q_i - q_i*q_j*sum_nq) / sum_nq**3 &
                  )
            else
               q_i = self%qs(i)
               q_j = self%qs(j)
               q_k = self%qs(k)

               d2thetak_dnidnj(k,i,j) = (&
                  2.0_pr * n(k) * q_k * q_i * q_j / sum_nq**3 &
                  )
            end if
         end do
      end if

      ! =======================================================================
      ! phi_k
      ! -----------------------------------------------------------------------
      phik = n * self%rs / sum_nr

      if (dn .or. dn2 .or. dtn) then
         dphik_dn = 0
         do concurrent(k=1:nc, i=1:nc)
            if (i == k) then
               dphik_dn(i,i) = &
                  (-n(i) * self%rs(i)**2 + self%rs(i) * sum_nr) / sum_nr**2
            else
               dphik_dn(k,i) = -n(k) * self%rs(i) * self%rs(k) / sum_nr**2
            end if
         end do
      end if

      if (dn2) then
         d2phik_dnidnj = 0
         do concurrent(k=1:nc, i=1:nc, j=1:nc)
            if (i==k .and. j==k) then
               r_i = self%rs(i)

               d2phik_dnidnj(k,i,j) = (&
                  2.0_pr * (r_i**3 * n(i) - r_i**2 * sum_nr) / sum_nr**3 &
                  )
            else if (i==k) then
               r_i = self%rs(i)
               r_j = self%rs(j)

               d2phik_dnidnj(k,i,j) = (&
                  (2.0_pr * n(i) * r_i**2 * r_j - r_j*r_i*sum_nr) / sum_nr**3 &
                  )
            else if (j==k) then
               r_i = self%rs(i)
               r_j = self%rs(j)

               d2phik_dnidnj(k,i,j) = (&
                  (2.0_pr * n(j) * r_j**2 * r_i - r_j*r_i*sum_nr) / sum_nr**3 &
                  )
            else
               r_i = self%rs(i)
               r_j = self%rs(j)
               r_k = self%rs(k)

               d2phik_dnidnj(k,i,j) = (&
                  2.0_pr * n(k) * r_k * r_i * r_j / sum_nr**3 &
                  )
            end if
         end do
      end if

      ! ========================================================================
      ! Ge
      ! ------------------------------------------------------------------------
      ! Combinatorial term
      Ge_comb = ( &
         sum(n * log(phik / xk)) &
         + self%z / 2.0_pr * sum(n * self%qs * log(thetak / phik)) &
         )

      ! Residual term
      sum_thetal_tau_lk = 0.0_pr

      do k=1,nc
         sum_thetal_tau_lk(k) = sum(thetak * tau(:,k))
      end do

      Ge_res = -sum(n * self%qs * log(sum_thetal_tau_lk))

      Ge_aux = R * T * (Ge_comb + Ge_res)

      ! ========================================================================
      ! Ge Derivatives
      ! ------------------------------------------------------------------------
      if (dn .or. dtn .or. dn2) then
         do concurrent (k=1:nc, i=1:nc)
            sum_dtheta_l_tau_lk(i,k) = sum(dthetak_dni(:,i) * tau(:,k))
         end do
      end if

      ! Compositional derivarives
      ! dn
      if (dn .or. dtn) then
         do i=1,nc
            ! Combinatorial term
            Gen_comb(i) = (&
               log(phik(i) / xk(i)) + sum(n * (dphik_dn(:,i) / phik - dxk_dni(:,i) / xk)) &
               + self%z / 2.0_pr * self%qs(i) * log(thetak(i) / phik(i)) &
               + self%z / 2.0_pr * sum(n * self%qs * (dthetak_dni(:,i) / thetak - dphik_dn(:,i) / phik)) &
               )

            ! Residual term
            Gen_res(i) = (&
               -self%qs(i) * log(sum_thetal_tau_lk(i)) &
               -sum(n * self%qs * sum_dtheta_l_tau_lk(i,:) / sum_thetal_tau_lk) &
               )
         end do

         Gen_aux = R * T * (Gen_comb + Gen_res)
      end if

      ! dn2
      if (dn2) then
         do concurrent(i=1:nc, j=1:nc)
            trm1 = dphik_dn(i,j) / phik(i) - dxk_dni(i,j) / xk(i)

            trm2 = (&
               dphik_dn(j,i) / phik(j) - dxk_dni(j,i) / xk(j) &
               + sum(n * (d2phik_dnidnj(:,i,j) * phik - dphik_dn(:,i) * dphik_dn(:,j)) / phik**2) &
               - sum(n * (d2xk_dnidnj(:,i,j) * xk - dxk_dni(:,i) * dxk_dni(:,j)) / xk**2) &
               )

            trm3 = self%z / 2 * self%qs(i) * (dthetak_dni(i,j) / thetak(i) - dphik_dn(i,j) / phik(i))

            trm4 = (&
               self%z / 2 * self%qs(j) * (dthetak_dni(j,i) / thetak(j) - dphik_dn(j,i) / phik(j)) &
               + self%z / 2 * sum(&
               self%qs * n * (d2thetak_dnidnj(:,i,j) * thetak - dthetak_dni(:,i) * dthetak_dni(:,j)) / thetak**2 &
               ) &
               - self%z / 2 * sum(&
               self%qs * n * (d2phik_dnidnj(:,i,j) * phik - dphik_dn(:,i) * dphik_dn(:,j)) / phik**2 &
               ) &
               )

            trm5 = -self%qs(i) * (sum(dthetak_dni(:,j) * tau(:,i)) / sum_thetal_tau_lk(i))

            do k=1,nc
               sum_d2theta_tau_lk(k) = sum(d2thetak_dnidnj(:,i,j) * tau(:,k))
            end do

            trm6 = (&
               -self%qs(j) * (sum(dthetak_dni(:,i) * tau(:,j)) / sum_thetal_tau_lk(j)) &
               -sum(self%qs * n * (&
               sum_d2theta_tau_lk * sum_thetal_tau_lk &
               - sum_dtheta_l_tau_lk(i,:) * sum_dtheta_l_tau_lk(j,:) &
               ) / sum_thetal_tau_lk**2) &
               )

            Gen2_aux(i,j) = R * T * (trm1 + trm2 + trm3 + trm4 + trm5 + trm6)
         end do
      end if

      ! Temperature derivatives
      if (dt .or. dt2 .or. dtn) then
         sum_theta_l_dtau_lk = 0.0_pr

         do k=1,nc
            sum_theta_l_dtau_lk(k) = sum(thetak * dtau(:,k))
         end do
      end if

      if (dt) then
         GeT_aux = ( &
            Ge_aux/T - R*T*sum(n * self%qs * sum_theta_l_dtau_lk / sum_thetal_tau_lk)&
            )
      end if

      if (dt2) then
         sum_theta_l_d2tau_lk = 0.0_pr

         do k=1,nc
            sum_theta_l_d2tau_lk(k) = sum(thetak * d2tau(:,k))
         end do

         diff_aux = (&
            sum_theta_l_d2tau_lk / sum_thetal_tau_lk &
            - (sum_theta_l_dtau_lk / sum_thetal_tau_lk)**2 &
            )

         GeT2_aux = -R * ( &
            T * sum(self%qs * n * diff_aux) &
            + 2.0_pr*sum(self%qs * n * sum_theta_l_dtau_lk / sum_thetal_tau_lk)&
            )
      end if

      ! Cross derivative Tn
      if (dtn) then
         do concurrent (k=1:nc, i=1:nc)
            sum_dtheta_l_dtau_lk(i,k) = sum(dthetak_dni(:,i) * dtau(:,k))
         end do
      end if

      if (dtn) then
         do i=1,nc
            GeTn_aux(i) = ( &
               1.0_pr / T  * Gen_aux(i) &
               -R * T * (&
               self%qs(i) * sum_theta_l_dtau_lk(i) / sum_thetal_tau_lk(i) &
               + sum(n * self%qs * (&
               sum_dtheta_l_dtau_lk(i,:) * sum_thetal_tau_lk &
               - sum_theta_l_dtau_lk * sum_dtheta_l_tau_lk(i,:)) &
               / sum_thetal_tau_lk**2) &
               ) &
               )
         end do
      end if
      ! =======================================================================
      ! Excess Gibbs energy returns
      ! -----------------------------------------------------------------------
      if (present(Ge)) Ge = Ge_aux
      if (dt) GeT = GeT_aux
      if (dt2) GeT2 = GeT2_aux
      if (dn) Gen = Gen_aux
      if (dtn) GeTn = GeTn_aux
      if (dn2) Gen2 = Gen2_aux
   end subroutine excess_gibbs

   subroutine taus(self, T, tau, tauT, tauT2)
      !! Calculate the temperature dependence term of the UNIQUAC model.
      !!
      class(UNIQUAC), intent(in) :: self
      !! UNIQUAC model
      real(pr), intent(in) :: T
      !! Temperature [K]
      real(pr), optional, intent(out) :: tau(size(self%qs), size(self%qs))
      !! UNIQUAC temperature dependence term
      real(pr), optional, intent(out) :: tauT(size(self%qs), size(self%qs))
      !! \(\frac{d\tau_{ij}}{dT}\)
      real(pr), optional, intent(out) :: tauT2(size(self%qs), size(self%qs))
      !! \(\frac{d^2\tau_{ij}}{dT^2}\)

      ! aux
      real(pr) :: tau_aux(size(self%qs), size(self%qs))
      real(pr) :: u(size(self%qs), size(self%qs))
      real(pr) :: du(size(self%qs), size(self%qs))
      real(pr) :: d2u(size(self%qs), size(self%qs))

      ! Logical
      logical :: tt, dt, dt2

      tt = present(tau)
      dt = present(tauT)
      dt2 = present(tauT2)

      ! temperature function
      u = self%aij + self%bij/T + self%cij*log(T) + self%dij*T + self%eij*T**2

      ! tau_ij
      tau_aux = exp(u)

      ! dT
      if (dt .or. dt2) then
         du = -self%bij / T**2 + self%cij / T + self%dij + 2.0_pr * T * self%eij
      end if

      ! d2T
      if (dt2) then
         d2u = 2.0_pr * self%bij / T**3 - self%cij / T**2 + 2.0_pr * self%eij
      end if


      if (tt) tau = tau_aux
      if (dt) tauT = tau_aux * du
      if (dt2) tauT2 = tau_aux * du**2 + tau_aux * d2u
   end subroutine taus

   type(UNIQUAC) function setup_uniquac(qs, rs, aij, bij, cij, dij, eij)
      !! Instantiate a UNIQUAC model.
      !!
      !! Non provided interaction parameters are set to zero matrices.
      !!
      real(pr), intent(in) :: qs(:)
      !! Molecule's relative volumes \(Q_i\)
      real(pr), intent(in) :: rs(size(qs))
      !! Molecule's relative areas \(R_i\)
      real(pr), optional, intent(in) :: aij(size(qs),size(qs))
      !! Interaction parameters matrix \(a_{ij}\), zero matrix if no provided.
      real(pr), optional, intent(in) :: bij(size(qs),size(qs))
      !! Interaction parameters matrix \(b_{ij}\), zero matrix if no provided.
      real(pr), optional, intent(in) :: cij(size(qs),size(qs))
      !! Interaction parameters matrix \(c_{ij}\), zero matrix if no provided.
      real(pr), optional, intent(in) :: dij(size(qs),size(qs))
      !! Interaction parameters matrix \(d_{ij}\), zero matrix if no provided.
      real(pr), optional, intent(in) :: eij(size(qs),size(qs))
      !! Interaction parameters matrix \(e_{ij}\), zero matrix if no provided.

      ! aij
      if (present(aij)) then
         setup_uniquac%aij = aij
      else
         allocate(setup_uniquac%aij(size(qs),size(qs)))
         setup_uniquac%aij = 0.0_pr
      end if

      ! bij
      if (present(bij)) then
         setup_uniquac%bij = bij
      else
         allocate(setup_uniquac%bij(size(qs),size(qs)))
         setup_uniquac%bij = 0.0_pr
      end if

      ! cij
      if (present(cij)) then
         setup_uniquac%cij = cij
      else
         allocate(setup_uniquac%cij(size(qs),size(qs)))
         setup_uniquac%cij = 0.0_pr
      end if

      ! dij
      if (present(dij)) then
         setup_uniquac%dij = dij
      else
         allocate(setup_uniquac%dij(size(qs),size(qs)))
         setup_uniquac%dij = 0.0_pr
      end if

      ! eij
      if (present(eij)) then
         setup_uniquac%eij = eij
      else
         allocate(setup_uniquac%eij(size(qs),size(qs)))
         setup_uniquac%eij = 0.0_pr
      end if

      setup_uniquac%qs = qs
      setup_uniquac%rs = rs
   end function setup_uniquac
end module yaeos__models_ge_uniquac