module yaeos__equilibria_boundaries_phase_envelopes_pt3 use yaeos__constants, only: pr, R use yaeos__equilibria_equilibrium_state, only: EquilibriumState use yaeos__models_ar, only: ArModel use yaeos__math, only: solve_system implicit none type :: PTEnvel3 real(pr), allocatable :: beta(:) !! Mole fraction between phase x and phase y real(pr), allocatable :: x(:, :) !! Mole fraction of phase x real(pr), allocatable :: y(:, :) !! Mole fraction of phase x real(pr), allocatable :: w(:, :) !! Mole fraction of phase x real(pr), allocatable :: P(:) real(pr), allocatable :: T(:) integer, allocatable :: ns(:) real(pr), allocatable :: S(:) end type PTEnvel3 real(pr), parameter :: lnK_min = 2.0_pr contains type(PTEnvel3) function pt_envelope_3ph(& model, z, x0, y0, w0, beta0, P0, T0, ns0, dS0, & points & ) result(envelope) class(ArModel), intent(in) :: model real(pr), intent(in) :: z(:) real(pr), intent(in) :: x0(:) real(pr), intent(in) :: y0(:) real(pr), intent(in) :: w0(:) real(pr), intent(in) :: beta0 real(pr), intent(in) :: P0 real(pr), intent(in) :: T0 integer, intent(in) :: ns0 real(pr), intent(in) :: dS0 integer, intent(in) :: points real(pr) :: kx(size(z)) real(pr) :: ky(size(z)) integer :: i integer :: nc real(pr) :: Xvars(size(z)*2 + 3), dX(size(z)*2 + 3) real(pr) :: F(size(z)*2+3), dF(size(z)*2 + 3, size(z)*2 + 3) integer :: ns !! Specified variable real(pr) :: S !! Specified value real(pr) :: dS !! Specified value step for next point extrapolation real(pr) :: dXdS(size(z)*2 + 3) real(pr) :: x(points, size(z)) real(pr) :: y(points, size(z)) real(pr) :: w(points, size(z)) real(pr) :: beta(points) real(pr) :: P(points) real(pr) :: T(points) integer :: its nc = size(z) ns = ns0 dS = dS0 kx = x0/w0 ky = y0/w0 Xvars = [log(kx), log(ky), log(P0), log(T0), beta0] S = Xvars(ns) allocate(envelope%S(0), envelope%ns(0)) do i=1, points call solve_point(model, z, ns, S, Xvars, F, dF, its, 1000) if (any(isnan(F)) .or. any(isnan(Xvars)) .or. its >= 1000) exit envelope%ns = [envelope%ns, ns] envelope%S = [envelope%S, S] call get_values_from_X(z, Xvars, x(i, :), y(i, :), w(i, :), P(i), T(i), beta(i)) call update_specification(its, Xvars, dF, dXdS, ns, dS) call detect_critical(Xvars, dXdS, ns, S, dS) dX = dXdS * dS Xvars = Xvars + dX S = Xvars(ns) end do i = i-1 envelope%x = x(:i, :) envelope%y = y(:i, :) envelope%w = w(:i, :) envelope%P = P(:i) envelope%T = T(:i) envelope%beta = beta(:i) end function pt_envelope_3ph subroutine get_values_from_X(z, Xvars, x, y, w, P, T, beta) real(pr), intent(in) :: z(:) real(pr), intent(in) :: Xvars(size(z)*2 + 3) real(pr), intent(out) :: x(size(z)) real(pr), intent(out) :: y(size(z)) real(pr), intent(out) :: w(size(z)) real(pr), intent(out) :: P real(pr), intent(out) :: T real(pr), intent(out) :: beta integer :: nc real(pr) :: Kx((Size(Xvars)-3)/2), Ky((Size(Xvars)-3)/2) nc = (Size(Xvars)-3)/2 Kx = exp(Xvars(1:nc)) Ky = exp(Xvars(nc + 1:2*nc)) P = exp(Xvars(2*nc + 1)) T = exp(Xvars(2*nc + 2)) beta = Xvars(2*nc + 3) w = z/(beta*Ky + (1 - beta)*Kx) x = w*Kx y = w*Ky end subroutine get_values_from_X subroutine update_specification(its, X, dF, dXdS, ns, dS) integer, intent(in) :: its real(pr), intent(in out) :: X(:) real(pr), intent(in out) :: dF(:, :) real(pr), intent(in out) :: dXdS(:) integer, intent(in out) :: ns real(pr), intent(in out) :: dS integer :: nc real(pr) :: dFdS(size(X)) dFdS = 0 dFdS(size(X)) = -1 nc = (size(X) - 3)/2 ns = maxloc(abs(dXdS), dim=1) dXdS = solve_system(dF, -dFdS) dS = dXdS(ns)*dS dXdS = dXdS/dXdS(ns) dS = sign(minval([abs(dS), 0.1_pr]), dS) end subroutine update_specification subroutine detect_critical(X, dXdS, ns, S, dS) !! # `detect_critical` !! Critical point detection !! !! # Description !! If the values of lnK (X[:nc]) change sign then a critical point !! Has passed, since for this to happen all variables should pass !! through zero. Near critical points (lnK < 0.05) points are harder !! to converge, so more steps in the extrapolation vector are made to !! jump over the critical point. !! If the critical point is detected then the kind of the point is !! changed and the point is saved using an interpolation knowing that !! !! \[ !! X_c = a * X + (1-a)*X_{new} !! \] !! !! With \(X_c\) is the variables at the critical point, \(X_{new}\) !! is the new initialization point of the method and \(a\) is the !! parameter to interpolate the values. This subroutine finds the !! value of \(a\) to obtain \(X_c\). real(pr), intent(in out) :: X(:) !! Vector of variables real(pr), intent(in out) :: dXdS(:) !! Variation of variables wrt S integer, intent(in out) :: ns !! Number of specified variable real(pr), intent(in out) :: S !! Specification value real(pr), intent(in out) :: dS !! Step in specification real(pr) :: Xc(size(X)) !! Value at (near) critical point real(pr) :: a !! Parameter for interpolation real(pr) :: Xold(size(X)) !! Old value of X real(pr) :: Xnew(size(X)) !! Value of the next initialization logical :: found_critical integer :: nc integer :: first_set((size(X)-3)/2), second_set((size(X)-3)/2), idx((size(X)-3)/2) integer :: i, critical_set((size(X)-3)/2) real(pr) :: step(size(X)) nc = (size(X)-3)/2 first_set = [(i, i=1, nc)] second_set = [(i, i=nc+1, 2*nc)] Xold = X Xnew = X + dXdS*dS found_critical = .false. do i=1,2 select case(i) case(1) idx = first_set case(2) idx = second_set end select do while(maxval(abs(Xnew(idx))) < 0.3) Xnew = Xnew + dXdS*dS end do if (all(Xnew(idx) * Xold(idx) < 0)) then ! If two steps imply the crossing of a critical point, then ! make those two steps to avoid falling into it found_critical = .true. critical_set = idx Xnew = Xnew + dXdS*dS end if end do if (found_critical) then a = critical_interpol(Xnew, Xold, critical_set) Xc = a * Xold + (1-a)*Xnew ! Xnew = Xc ! do while(maxval(abs(Xnew(idx))) < 0.5) ! Xnew = Xnew + dXdS*dS ! end do X = X + (Xnew - Xold) end if end subroutine detect_critical real(pr) function critical_interpol(Xnew, Xold, idx) result(a) !! # `critical_interpol` !! Critical point interpolation !! !! # Description !! This function calculates the parameter \(a\) to interpolate the !! values of the variables at the critical point. The interpolation !! is done using the equation: !! !! \[ !! 0 = a*X_{old}(ns) + (1-a)*X_{new}(ns) !! \] !! !! Where \(X_{old}\) is the old value of the variables and \(X_{new}\) !! is the new value of the variables. The critical point is the point !! where the variables change sign, so the interpolation is done to !! find the value of the variables at the critical point. real(pr), intent(in) :: Xnew(:) !! New value of the variables real(pr), intent(in) :: Xold(:) !! Old value of the variables integer, intent(in) :: idx(:) !! Index of the variables to interpolate integer :: ncomp ! 0 = a*X(ns) + (1-a)*Xnew(ns) < Interpolation equation to get X(ns) = 0 ! Find the component that is changing sign with the highest slope ncomp = maxloc(abs(Xold(idx) - Xnew(idx)), dim=1) a = -Xnew(ncomp)/(Xold(ncomp) - Xnew(ncomp)) end function critical_interpol subroutine pt_F_three_phases(model, z, Xvars, ns, S, F, dF) !! Function to solve at each point of a three phase envelope. !! !! The vector of variables X corresponds to: !! \( X = [lnKx_i, lnKy_i lnP, lnT, \beta] \) !! !! While the equations are: !! !! \( F = [ !! lnKx_i - ln \phi_i(x, P, T) + ln \phi_i(w, P, T), !! lnKy_i - ln \phi_i(y, P, T) + ln \phi_i(w, P, T), !! \sum_{i=1}^N (w_i) - 1, !! \sum_{i=1}^N (x_i - y_i), !! X_{ns} - S !! ] \) use iso_fortran_env, only: error_unit class(ArModel), intent(in) :: model real(pr), intent(in) :: z(:) real(pr), intent(in) :: Xvars(:) !! Vector of variables integer, intent(in) :: ns !! Number of specification real(pr), intent(in) :: S !! Specification value real(pr), intent(out) :: F(size(Xvars)) !! Vector of functions valuated real(pr), intent(out) :: df(size(Xvars), size(Xvars)) !! Jacobian matrix ! Xvars variables real(pr) :: Kx((Size(Xvars)-3)/2) real(pr) :: Ky((Size(Xvars)-3)/2) real(pr) :: P real(pr) :: T real(pr) :: beta ! Main phase 1 variables real(pr) :: Vx real(pr), dimension((Size(Xvars)-3)/2) :: x, lnphi_x, dlnphi_dt_x, dlnphi_dp_x real(pr), dimension((Size(Xvars)-3)/2, (Size(Xvars)-3)/2) :: dlnphi_dn_x ! Main phase 2 variables real(pr) :: Vy real(pr), dimension((Size(Xvars)-3)/2) :: y, lnphi_y, dlnphi_dt_y, dlnphi_dp_y real(pr), dimension((Size(Xvars)-3)/2, (Size(Xvars)-3)/2) :: dlnphi_dn_y ! Incipient phase variables real(pr) :: Vw real(pr), dimension((Size(Xvars)-3)/2) :: w, lnphi_w, dlnphi_dt_w, dlnphi_dp_w real(pr), dimension((Size(Xvars)-3)/2, (Size(Xvars)-3)/2) :: dlnphi_dn_w ! Derivative of w wrt beta real(pr) :: dwdb((Size(Xvars)-3)/2) real(pr) :: dwdKx((Size(Xvars)-3)/2), dxdKx((Size(Xvars)-3)/2), dydKx((Size(Xvars)-3)/2) real(pr) :: dwdKy((Size(Xvars)-3)/2), dxdKy((Size(Xvars)-3)/2), dydKy((Size(Xvars)-3)/2) integer :: i, j, nc nc = (Size(Xvars)-3)/2 Kx = exp(Xvars(1:nc)) Ky = exp(Xvars(nc + 1:2*nc)) P = exp(Xvars(2*nc + 1)) T = exp(Xvars(2*nc + 2)) beta = Xvars(2*nc + 3) w = z/(beta*Ky + (1 - beta)*Kx) x = w*Kx y = w*Ky call model%lnphi_pt(& x, P, T, V=Vx, root_type="stable", lnphi=lnphi_x, & dlnphidp=dlnphi_dp_x, dlnphidt=dlnphi_dt_x, dlnphidn=dlnphi_dn_x & ) call model%lnphi_pt(& y, P, T, V=Vy, root_type="stable", lnphi=lnphi_y, & dlnphidp=dlnphi_dp_y, dlnphidt=dlnphi_dt_y, dlnphidn=dlnphi_dn_y & ) call model%lnphi_pt(& w, P, T, V=Vw, root_type="stable", lnphi=lnphi_w, & dlnphidp=dlnphi_dp_w, dlnphidt=dlnphi_dt_w, dlnphidn=dlnphi_dn_w & ) F = 0 F(1:nc) = Xvars(1:nc) + lnphi_x - lnphi_w F(nc + 1:2*nc) = Xvars(nc + 1:2*nc) + lnphi_y - lnphi_w F(2*nc + 1) = sum(w) - 1 F(2*nc + 2) = sum(x - y) F(2*nc + 3) = Xvars(ns) - S df = 0 dwdb = z*(Kx - Ky)/((1 - beta)*Kx + beta*Ky)**2 dwdKx = -z*(1 - beta)/(Ky*beta + (1 - beta)*Kx)**2 dxdKx = Kx*dwdKx + w dydKx = Ky*dwdKx dwdKy = -z*(beta)/(Ky*beta + (1 - beta)*Kx)**2 dxdKy = Kx*dwdKy dydKy = Ky*dwdKy + w do i = 1, nc do j = 1, nc df(i, j) = Kx(j)*(dlnphi_dn_x(i, j)*dxdKx(j) & - dlnphi_dn_w(i, j)*dwdKx(j)) df(i + nc, j) = Kx(j)*(dlnphi_dn_y(i, j)*dydKx(j) & - dlnphi_dn_w(i, j)*dwdKx(j)) df(i, j + nc) = Ky(j)*(dlnphi_dn_x(i, j)*dxdKy(j) & - dlnphi_dn_w(i, j)*dwdKy(j)) df(i + nc, j + nc) = Ky(j)*(dlnphi_dn_y(i, j)*dydKy(j) & - dlnphi_dn_w(i, j)*dwdKy(j)) end do ! dlnK_i/dlnK_i df(i, i) = df(i, i) + 1 df(i + nc, i + nc) = df(i + nc, i + nc) + 1 df(i, 2*nc + 3) = sum(Kx*dlnphi_dn_x(i, :)*dwdb - dlnphi_dn_w(i, :)*dwdb) df(i + nc, 2*nc + 3) = sum(Ky*dlnphi_dn_y(i, :)*dwdb - dlnphi_dn_w(i, :)*dwdb) df(2*nc + 1, i) = Kx(i)*dwdKx(i) df(2*nc + 1, i + nc) = Ky(i)*dwdKy(i) df(2*nc + 2, i) = Kx(i)*dxdKx(i) - Kx(i)*dydKx(i) df(2*nc + 2, i + nc) = Ky(i)*dxdKy(i) - Ky(i)*dydKy(i) end do ! Derivatives wrt P df(:nc, 2*nc + 1) = P*(dlnphi_dp_x - dlnphi_dp_w) df(nc + 1:2*nc, 2*nc + 1) = P*(dlnphi_dp_y - dlnphi_dp_w) ! Derivatives wrt T df(:nc, 2*nc + 2) = T*(dlnphi_dt_x - dlnphi_dt_w) df(nc + 1:2*nc, 2*nc + 2) = T*(dlnphi_dt_y - dlnphi_dt_w) ! Derivatives wrt beta df(2*nc + 1, 2*nc + 3) = sum(dwdb) df(2*nc + 2, 2*nc + 3) = sum(Kx*dwdb - Ky*dwdb) ! Derivatives wrt Xs df(2*nc + 3, :) = 0 df(2*nc + 3, ns) = 1 end subroutine pt_F_three_phases subroutine solve_point(model, z, ns, S, X, F, dF, its, maxits) class(ArModel), intent(in) :: model real(pr), intent(in) :: z(:) integer, intent(in) :: ns real(pr), intent(in) :: S real(pr), intent(in out) :: X(:) real(pr), intent(out) :: F(:) real(pr), intent(out) :: dF(:,:) integer, intent(in out) :: its integer, intent(in) :: maxits real(pr) :: dX(size(X)) integer :: nc its = 0 F = 1 dX = 1 nc = (size(X) - 3)/2 do while((maxval(abs(F)) > 1e-5 .or. maxval(abs(dX)) > 1e-5) .and. its < maxits) its = its + 1 call pt_F_three_phases(model, z, X, ns, S, F, dF) dX = solve_system(dF, -F) do while((abs(dX(2*nc+1)/X(2*nc+1))) > 0.1) dX = dX/2 end do do while((abs(dX(2*nc+2)/X(2*nc+2))) > 0.1) dX = dX/2 end do do while(abs(dX(2*nc+3)) > 0.01) dX = dX/2 end do X = X + dX end do end subroutine solve_point end module yaeos__equilibria_boundaries_phase_envelopes_pt3