Skip to content
Snippets Groups Projects
saftvrmie.hpp 33 KiB
Newer Older
  • Learn to ignore specific revisions
  • 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747
    /***
     
     \brief This file contains the contributions that can be composed together to form SAFT models
    
    */
    
    #pragma once
    
    #include "nlohmann/json.hpp"
    #include "teqp/types.hpp"
    #include "teqp/exceptions.hpp"
    #include "teqp/constants.hpp"
    #include "teqp/math/quadrature.hpp"
    #include <optional>
    
    namespace teqp {
    namespace SAFTVRMie {
    
    /// Coefficients for one fluid
    struct SAFTVRMieCoeffs {
        std::string name; ///< Name of fluid
        double m = -1, ///< number of segments
            sigma_Angstrom = -1, ///< [A] segment diameter
            epsilon_over_k = -1, ///< [K] depth of pair potential divided by Boltzman constant
            lambda_a = -1,
            lambda_r = -1;
        std::string BibTeXKey; ///< The BibTeXKey for the reference for these coefficients
    };
    
    /// Manager class for SAFT-VR-Mie coefficients
    class SAFTVRMieLibrary {
        std::map<std::string, SAFTVRMieCoeffs> coeffs;
    public:
        SAFTVRMieLibrary() {
            insert_normal_fluid("Methane", 1.0000, 3.7039, 150.03, 12, 6, "Lafitte-JCP-2001");
            insert_normal_fluid("Ethane", 1.6069, 3.5206, 191.42, 12, 6, "Lafitte-JCP-2001");
            insert_normal_fluid("Propane", 2.0020, 3.6184, 208.11, 12, 6, "Lafitte-JCP-2001");
        }
        void insert_normal_fluid(const std::string& name, double m, const double sigma_Angstrom, const double epsilon_over_k, const double lambda_a, const double lambda_r, const std::string& BibTeXKey) {
            SAFTVRMieCoeffs coeff;
            coeff.name = name;
            coeff.m = m;
            coeff.sigma_Angstrom = sigma_Angstrom;
            coeff.epsilon_over_k = epsilon_over_k;
            coeff.lambda_a = lambda_a;
            coeff.lambda_r = lambda_r;
            coeff.BibTeXKey = BibTeXKey;
            coeffs.insert(std::pair<std::string, SAFTVRMieCoeffs>(name, coeff));
        }
        const auto& get_normal_fluid(const std::string& name) {
            auto it = coeffs.find(name);
            if (it != coeffs.end()) {
                return it->second;
            }
            else {
                throw std::invalid_argument("Bad name:" + name);
            }
        }
        auto get_coeffs(const std::vector<std::string>& names){
            std::vector<SAFTVRMieCoeffs> c;
            for (auto n : names){
                c.push_back(get_normal_fluid(n));
            }
            return c;
        }
    };
    
    
    /**
    Sum up three array-like objects that can each have different container types and value types
    */
    template<typename VecType1, typename NType>
    auto powvec(const VecType1& v1, NType n) {
        auto o = v1;
        for (auto i = 0; i < v1.size(); ++i) {
            o[i] = pow(v1[i], n);
        }
        return o;
    }
    
    /**
    Sum up the coefficient-wise product of three array-like objects that can each have different container types and value types
    */
    template<typename VecType1, typename VecType2, typename VecType3>
    auto sumproduct(const VecType1& v1, const VecType2& v2, const VecType3& v3) {
        using ResultType = typename std::common_type_t<decltype(v1[0]), decltype(v2[0]), decltype(v3[0])>;
        return forceeval((v1.template cast<ResultType>().array() * v2.template cast<ResultType>().array() * v3.template cast<ResultType>().array()).sum());
    }
    
    /// Things that only depend on the components themselves, but not on composition, temperature, or density
    struct SAFTVRMieChainContributionTerms{
        private:
        
        /// The matrix of coefficients needed to evaluate f_k
        const Eigen::Matrix<double, 7, 7> phi{(Eigen::Matrix<double, 7, 7>() <<
            7.5365557, -359.44,  1550.9, -1.19932,  -1911.28,    9236.9,   10,
            -37.60463, 1825.6,   -5070.1, 9.063632,  21390.175, -129430,   10,
            71.745953, -3168.0,  6534.6, -17.9482,  -51320.7,    357230,   0.57,
            -46.83552, 1884.2,   -3288.7, 11.34027,  37064.54,   -315530,   -6.7,
            -2.467982, -0.82376, -2.7171, 20.52142,  1103.742,    1390.2,   -8,
            -0.50272,  -3.1935,  2.0883, -56.6377,  -3264.61,    -4518.2,   0,
            8.0956883, 3.7090,   0,       40.53683,  2556.181,    4241.6,   0 ).finished()};
        
        /// The matrix used to obtain the parameters c_1, c_2, c_3, and c_4 in Eq. A18
        const Eigen::Matrix<double, 4, 4> A{(Eigen::Matrix<double, 4, 4>() <<
             0.81096,  1.7888, -37.578,  92.284,
             1.0205,  -19.341,  151.26, -463.50,
             -1.9057, 22.845,  -228.14,  973.92,
             1.0885,  -6.1962,  106.98, -677.64).finished()};
    
        // Eq. A48
        auto get_lambda_k_ij(const Eigen::ArrayXd& lambda_k){
            Eigen::ArrayXXd mat(N,N);
            for (auto i = 0; i < lambda_k.size(); ++i){
                for (auto j = i; j < lambda_k.size(); ++j){
                    mat(i,j) = 3 + sqrt((lambda_k(i)-3)*(lambda_k(j)-3));
                    mat(j,i) = mat(i,j);
                }
            }
            return mat;
        }
    
        /// Eq. A3
        auto get_C_ij(){
            Eigen::ArrayXXd C(N,N);
            for (auto i = 0; i < N; ++i){
                for (auto j = i; j < N; ++j){
                    C(i,j) = lambda_r_ij(i,j)/(lambda_r_ij(i,j)-lambda_a_ij(i,j))*pow(lambda_r_ij(i,j)/lambda_a_ij(i,j), lambda_a_ij(i,j)/(lambda_r_ij(i,j)-lambda_a_ij(i,j)));
                    C(j,i) = C(i,j); // symmetric
                }
            }
            return C;
        }
        
        // Eq. A26
        auto get_fkij(){
            std::vector<Eigen::ArrayXXd> f_(8); // 0-th element is present, but not initialized
            for (auto k = 1; k < 8; ++k){
                f_[k].resize(N,N);
            };
            for (auto k = 1; k < 8; ++k){
                auto phik = phi.col(k-1); // phi matrix is indexed to start at 1, but our matrix starts at 0
                Eigen::ArrayXXd num(N,N), den(N,N); num.setZero(), den.setZero();
                for (auto n = 0; n < 4; ++n){
                    num += phik[n]*pow(alpha_ij, n);
                }
                for (auto n = 4; n < 7; ++n){
                    den += phik[n]*pow(alpha_ij, n-3);
                }
                f_[k] = num/(1 + den);
            }
            return f_;
        }
        
        /// Eq. A45
        auto get_sigma_ij(){
            Eigen::ArrayXXd sigma(N,N);
            for (auto i = 0; i < N; ++i){
                for (auto j = i; j < N; ++j){
                    sigma(i,j) = (sigma_m(i) + sigma_m(j))/2.0;
                    sigma(j,i) = sigma(i,j); // symmetric
                }
            }
            return sigma;
        }
        /// Eq. A45
        auto get_epsilon_ij(){
            Eigen::ArrayXXd eps_(N,N);
            for (auto i = 0; i < N; ++i){
                for (auto j = i; j < N; ++j){
                    eps_(i,j) = (1.0-kmat(i,j))*sqrt(pow(sigma_m(i,i),3)*pow(sigma_m(j,j),3)*epsilon_over_k(i)*epsilon_over_k(j))/pow(sigma_ij(i,j), 3);
                    eps_(j,i) = eps_(i,j); // symmetric
                }
            }
            return eps_;
        }
        std::size_t get_N(){
            auto sizes = (Eigen::ArrayXd(5) << m.size(), epsilon_over_k.size(), sigma_m.size(), lambda_a.size(), lambda_r.size()).finished();
            if (sizes.mean() != sizes.minCoeff()){
                throw teqp::InvalidArgument("sizes of pure component arrays are not all the same");
            }
            return sizes[0];
        }
    
        /// Eq. A18 for the attractive exponents
        auto get_cij(const Eigen::ArrayXd& lambdaij){
            std::vector<Eigen::ArrayXXd> cij(4);
            for (auto n = 0; n < 4; ++n){
                cij[n].resize(N,N);
            };
            for (auto i = 0; i < N; ++i){
                for (auto j = i; j < N; ++j){
                    using CV = Eigen::Vector<double, 4>;
                    const CV b{(CV() << 1, 1.0/lambdaij(i,j), 1.0/pow(lambdaij(i,j),2), 1.0/pow(lambdaij(i,j),3)).finished()};
                    auto c1234 = (A*b).eval();
                    cij[0](i,j) = c1234(0);
                    cij[1](i,j) = c1234(1);
                    cij[2](i,j) = c1234(2);
                    cij[3](i,j) = c1234(3);
                }
            }
            return cij;
        }
            
        /// Eq. A18 for the attractive exponents
        auto get_canij(){
            return get_cij(lambda_a_ij);
        }
        /// Eq. A18 for 2x the attractive exponents
        auto get_c2anij(){
            return get_cij(2.0*lambda_a_ij);
        }
        /// Eq. A18 for the repulsive exponents
        auto get_crnij(){
            return get_cij(lambda_r_ij);
        }
        /// Eq. A18 for the 2x the repulsive exponents
        auto get_c2rnij(){
            return get_cij(2.0*lambda_r_ij);
        }
        /// Eq. A18 for the 2x the repulsive exponents
        auto get_carnij(){
            return get_cij(lambda_r_ij + lambda_a_ij);
        }
        
        template<typename X>
        auto POW2(const X& x) const{
            return forceeval(x*x);
        };
        template<typename X>
        auto POW3(const X& x) const{
            return forceeval(x*POW2(x));
        };
        template<typename X>
        auto POW4(const X& x) const{
            return forceeval(POW2(x)*POW2(x));
        };
        
        public:
        
        // One entry per component
        const Eigen::ArrayXd m, epsilon_over_k, sigma_m, lambda_a, lambda_r;
        const Eigen::ArrayXXd kmat;
    
        const std::size_t N;
    
        // Calculated matrices for the ij pair
        const Eigen::ArrayXXd lambda_r_ij, lambda_a_ij, C_ij, alpha_ij, sigma_ij, epsilon_ij; // Matrices of parameters
    
        const std::vector<Eigen::ArrayXXd> canij, crnij, c2anij, c2rnij, carnij;
        const std::vector<Eigen::ArrayXXd> fkij; // Matrices of parameters
    
        SAFTVRMieChainContributionTerms(
                const Eigen::ArrayXd& m,
                const Eigen::ArrayXd& epsilon_over_k,
                const Eigen::ArrayXd& sigma_m,
                const Eigen::ArrayXd& lambda_r,
                const Eigen::ArrayXd& lambda_a,
                const Eigen::ArrayXd& kmat)
        :   m(m), epsilon_over_k(epsilon_over_k), sigma_m(sigma_m), lambda_a(lambda_a), lambda_r(lambda_r), kmat(kmat),
            N(get_N()),
            lambda_r_ij(get_lambda_k_ij(lambda_r)), lambda_a_ij(get_lambda_k_ij(lambda_a)),
            C_ij(get_C_ij()), alpha_ij(C_ij*(1/(lambda_a_ij-3) - 1/(lambda_r_ij-3))),
            sigma_ij(get_sigma_ij()), epsilon_ij(get_epsilon_ij()),
            crnij(get_crnij()), canij(get_canij()),
            c2rnij(get_c2rnij()), c2anij(get_c2anij()), carnij(get_carnij()),
            fkij(get_fkij())
        {}
        
        template<typename R>
        auto get_uii_over_kB(std::size_t i, const R& r) const {
            auto rstarinv = sigma_m[i]/r;
            return C_ij(i,i)*epsilon_over_k[i]*(pow(rstarinv, lambda_r[i]) - pow(rstarinv, lambda_a[i]));
        }
        
        template <typename TType>
        auto get_dii(std::size_t i, const TType &T) const{
            std::function<double(double)> integrand = [this, i, &T](const double& r){
                return 1.0-exp(-this->get_uii_over_kB(i, r)/T);
            };
            return quad<30, double>(integrand, 0.0, sigma_m[i]);
        }
        
        template <typename TType>
        auto get_dmat(const TType &T) const{
            Eigen::ArrayXXd d(N,N);
            // For the pure components, by integration
            for (auto i = 0; i < N; ++i){
                d(i,i) = get_dii(i, T);
            }
            // The cross terms, using the linear mixing rule
            for (auto i = 0; i < N; ++i){
                for (auto j = i+1; j < N; ++j){
                    d(i,j) = (d(i,i) + d(j,j))/2.0;
                    d(j,i) = d(i,j);
                }
            }
            return d;
        }
        // Calculate core parameters that depend on temperature, volume, and composition
        template <typename TType, typename RhoType, typename VecType>
        auto get_core_calcs(const TType& T, const RhoType& rhomolar, const VecType& molefracs) const{
            
            // Things that are easy to calculate
            // ....
            
            auto dmat = get_dmat(T); // Matrix of diameters of pure and cross terms
            auto rhoN = rhomolar*N_A; // Number density, in molecules/m^3
            auto mbar = forceeval((molefracs*m).sum()); // Mean number of segments, dimensionless
            auto rhos = forceeval(rhoN*mbar); // Mean segment number density
            auto xs = forceeval(m*molefracs/mbar); // Segment fractions
            using NumType = std::common_type_t<TType, RhoType, std::decay_t<decltype(molefracs[0])>>;
            Eigen::Array<NumType, 4, 1> xi;
            for (auto l = 0; l < xi.size(); ++l){
                NumType summer = 0.0;
                for (auto i = 0; i < N; ++i){
                    summer += xs(i)*pow(dmat(i,i), l);
                }
                xi(l) = static_cast<double>(EIGEN_PI)*rhos/6.0*summer;
            }
            NumType summer_xi_x = 0.0, summer_xi_x_bar = 0.0;
            for (auto i = 0; i < N; ++i){
                for (auto j = 0; j < N; ++j){
                    summer_xi_x += xs(i)*xs(j)*pow(dmat(i,j), 3);
                    summer_xi_x_bar += xs(i)*xs(j)*pow(sigma_ij(i,j), 3);
                }
            }
            constexpr double MY_PI = static_cast<double>(EIGEN_PI);
            NumType xi_x = MY_PI*rhos/6.0*summer_xi_x; // Eq. A13
            NumType xi_x_bar = MY_PI*rhos/6.0*summer_xi_x_bar; // Eq. A23
            NumType xi_x_bar5 = POW2(xi_x_bar)*POW3(xi_x_bar); // (xi_x_bar)^5
            NumType xi_x_bar8 = xi_x_bar5*POW3(xi_x_bar); // (xi_x_bar)^8
            
            // Coefficients in the gdHSij term, do not depend on component,
            // so calculate them here
            NumType X = POW3(1.0 - xi_x), X3=X;
            NumType X2 = POW2(1.0 - xi_x);
            auto k0 = -log(1-xi_x) + (42*xi_x - 39*POW2(xi_x) + 9*POW3(xi_x) - 2*POW4(xi_x))/(6*X3); // Eq. A30
            auto k1 = (POW4(xi_x) + 6*POW2(xi_x) - 12*xi_x)/(2*X3);
            auto k2 = -3*POW2(xi_x)/(8*X2);
            auto k3 = (-POW4(xi_x) + 3*POW2(xi_x) + 3*xi_x)/(6*X3);
            
            // Pre-calculate the cubes of the diameters
            auto dmat3 = dmat.array().pow(3).eval();
            
            NumType a1kB = 0.0;
            NumType a2kB2 = 0.0;
            NumType a3kB3 = 0.0;
            NumType a_chain = 0.0;
            
            auto K_HS = get_KHS(xi_x);
            auto rho_dK_HS_drho = get_rhos_dK_HS_drhos(xi_x);
            
            for (auto i = 0; i < N; ++i){
                for (auto j = i; j < N; ++j){
                    NumType x_0_ij = sigma_ij(i,j)/dmat(i, j);
                    
                    // -----------------------
                    // Calculations for a_1/kB
                    // -----------------------
                    
                    auto I = [&x_0_ij](double lambda_ij){
                        return forceeval(-(pow(x_0_ij, 3-lambda_ij)-1.0)/(lambda_ij-3.0)); // Eq. A14
                    };
                    auto J = [&x_0_ij](double lambda_ij){
                        return forceeval(-(pow(x_0_ij, 4-lambda_ij)*(lambda_ij-3)-pow(x_0_ij, 3-lambda_ij)*(lambda_ij-4)-1)/((lambda_ij-3)*(lambda_ij-4))); // Eq. A15
                    };
                    auto Bhatij_a = this->get_Bhatij(xi_x, X, I(lambda_a_ij(i,j)), J(lambda_a_ij(i,j)));
                    auto Bhatij_2a = this->get_Bhatij(xi_x, X, I(2*lambda_a_ij(i,j)), J(2*lambda_a_ij(i,j)));
                    auto Bhatij_r = this->get_Bhatij(xi_x, X, I(lambda_r_ij(i,j)), J(lambda_r_ij(i,j)));
                    auto Bhatij_2r = this->get_Bhatij(xi_x, X, I(2*lambda_r_ij(i,j)), J(2*lambda_r_ij(i,j)));
                    auto Bhatij_ar = this->get_Bhatij(xi_x, X, I(lambda_a_ij(i,j)+lambda_r_ij(i,j)), J(lambda_a_ij(i,j)+lambda_r_ij(i,j)));
                                                     
                    auto one_term =  [this, &x_0_ij, &I, &J, &xi_x, &X](double lambda_ij, const NumType& xi_x_eff){
                        return pow(x_0_ij, lambda_ij)*(this->get_Bhatij(xi_x, X, I(lambda_ij), J(lambda_ij))
                                                       + this->get_a1Shatij(xi_x_eff, lambda_ij));
                    };
                    NumType xi_x_eff_r = crnij[0](i,j)*xi_x + crnij[1](i,j)*POW2(xi_x) + crnij[2](i,j)*POW3(xi_x) + crnij[3](i,j)*POW4(xi_x);
                    NumType xi_x_eff_a = canij[0](i,j)*xi_x + canij[1](i,j)*POW2(xi_x) + canij[2](i,j)*POW3(xi_x) + canij[3](i,j)*POW4(xi_x);
                    NumType dxi_x_eff_dxix_r = crnij[0](i,j) + crnij[1](i,j)*2*xi_x + crnij[2](i,j)*3*POW2(xi_x) + crnij[3](i,j)*4*POW3(xi_x);
                    NumType dxi_x_eff_dxix_a = canij[0](i,j) + canij[1](i,j)*2*xi_x + canij[2](i,j)*3*POW2(xi_x) + canij[3](i,j)*4*POW3(xi_x);
    
                    NumType a1ij = forceeval(2*MY_PI*rhos*dmat3(i,j)*epsilon_ij(i,j)*C_ij(i,j)*(
                        one_term(lambda_a_ij(i,j), xi_x_eff_a) - one_term(lambda_r_ij(i,j), xi_x_eff_r)
                    )); // divided by k_B
                                        
                    NumType contribution = xs(i)*xs(j)*a1ij;
                    double factor = (i == j) ? 1 : 2; // Off-diagonal terms contribute twice
                    a1kB += contribution*factor;
                    
                    // --------------------------
                    // Calculations for a_2/k_B^2
                    // --------------------------
                    
                    NumType xi_x_eff_2r = c2rnij[0](i,j)*xi_x + c2rnij[1](i,j)*POW2(xi_x) + c2rnij[2](i,j)*POW3(xi_x) + c2rnij[3](i,j)*POW4(xi_x);
                    NumType xi_x_eff_2a = c2anij[0](i,j)*xi_x + c2anij[1](i,j)*POW2(xi_x) + c2anij[2](i,j)*POW3(xi_x) + c2anij[3](i,j)*POW4(xi_x);
                    NumType xi_x_eff_ar = carnij[0](i,j)*xi_x + carnij[1](i,j)*POW2(xi_x) + carnij[2](i,j)*POW3(xi_x) + carnij[3](i,j)*POW4(xi_x);
                    NumType dxi_x_eff_dxix_2r = c2rnij[0](i,j) + c2rnij[1](i,j)*2*xi_x + c2rnij[2](i,j)*3*POW2(xi_x) + c2rnij[3](i,j)*4*POW3(xi_x);
                    NumType dxi_x_eff_dxix_ar = carnij[0](i,j) + carnij[1](i,j)*2*xi_x + carnij[2](i,j)*3*POW2(xi_x) + carnij[3](i,j)*4*POW3(xi_x);
                    NumType dxi_x_eff_dxix_2a = c2anij[0](i,j) + c2anij[1](i,j)*2*xi_x + c2anij[2](i,j)*3*POW2(xi_x) + c2anij[3](i,j)*4*POW3(xi_x);
                    
                    double chi_ij = fkij[1](i,j)*xi_x_bar + fkij[2](i,j)*xi_x_bar5 + fkij[3](i,j)*xi_x_bar8;
                    auto a2ij = 0.5*K_HS*(1.0+chi_ij)*epsilon_ij(i,j)*POW2(C_ij(i,j))*(2*MY_PI*rhos*dmat3(i,j)*epsilon_ij(i,j))*(
                         one_term(2*lambda_a_ij(i,j), xi_x_eff_2a)
                      -2*one_term(lambda_a_ij(i,j)+lambda_r_ij(i,j), xi_x_eff_ar)
                        +one_term(2*lambda_r_ij(i,j), xi_x_eff_2r)
                    ); // divided by k_B^2
                                        
                    NumType contributiona2 = xs(i)*xs(j)*a2ij; // Eq. A19
                    a2kB2 += contributiona2*factor;
                    
                    // --------------------------
                    // Calculations for a_3/k_B^3
                    // --------------------------
                    auto a3ij = -POW3(epsilon_ij(i,j))*fkij[4](i,j)*xi_x_bar*exp(
                         fkij[5](i,j)*xi_x_bar + fkij[6](i,j)*POW2(xi_x_bar)
                    ); // divided by k_B^3
                    NumType contributiona3 = xs(i)*xs(j)*a3ij; // Eq. A25
                    a3kB3 += contributiona3*factor;
                    
                    if (i == j){
                        // ------------------
                        // Chain contribution
                        // ------------------
                        
                        // Eq. A29
                        auto gdHSii = exp(k0 + k1*x_0_ij + k2*POW2(x_0_ij) + k3*POW3(x_0_ij));
                        
                        // The g1 terms
                        // ....
                        
                        // This is the function for the second part (not the partial) that goes in g_{1,ii},
                        // divided by 2*PI*d_ij^3*epsilon*rhos
                        auto g1_term = [&one_term](double lambda_ij, const NumType& xi_x_eff){
                            return lambda_ij*one_term(lambda_ij, xi_x_eff);
                        };
                        auto g1_noderivterm = -C_ij(i,i)*(g1_term(lambda_a_ij(i,i), xi_x_eff_a)-g1_term(lambda_r_ij(i,i), xi_x_eff_r));
                        
                        // Bhat = B*rho*kappa; diff(Bhat, rho) = Bhat + rho*dBhat/drho; kappa = 2*pi*eps*d^3
                        // This is the function for the partial derivative rhos*(da1ij/drhos),
                        // divided by 2*PI*d_ij^3*epsilon*rhos
                        auto rhosda1iidrhos_term = [this, &x_0_ij, &I, &J, &xi_x, &X](double lambda_ij, const NumType& xi_x_eff, const NumType& dxixeff_dxix, const NumType& Bhatij){
                            auto I_ = I(lambda_ij);
                            auto J_ = J(lambda_ij);
                            auto rhosda1Sdrhos = this->get_rhoda1Shatijdrho(xi_x, xi_x_eff, dxixeff_dxix, lambda_ij);
                            auto rhosdBdrhos = this->get_rhodBijdrho(xi_x, X, I_, J_, Bhatij);
                            return pow(x_0_ij, lambda_ij)*(rhosda1Sdrhos + rhosdBdrhos);
                        };
                        // This is rhos*d(a_1ij)/drhos/(2*pi*d^3*eps*rhos)
                        auto da1iidrhos_term = C_ij(i,j)*(
                             rhosda1iidrhos_term(lambda_a_ij(i,i), xi_x_eff_a, dxi_x_eff_dxix_a, Bhatij_a)
                            -rhosda1iidrhos_term(lambda_r_ij(i,i), xi_x_eff_r, dxi_x_eff_dxix_r, Bhatij_r)
                        );
                        auto g1ii = 3.0*da1iidrhos_term + g1_noderivterm;
                        
                        // The g2 terms
                        // ....
                        
                        // This is the second part (not the partial deriv.) that goes in g_{2,ii},
                        // divided by 2*PI*d_ij^3*epsilon*rhos
                        auto g2_noderivterm = -POW2(C_ij(i,i))*K_HS*(
                           lambda_a_ij(i,j)*one_term(2*lambda_a_ij(i,j), xi_x_eff_2a)
                           -(lambda_a_ij(i,j)+lambda_r_ij(i,j))*one_term(lambda_a_ij(i,j)+lambda_r_ij(i,j), xi_x_eff_ar)
                           +lambda_r_ij(i,j)*one_term(2*lambda_r_ij(i,j), xi_x_eff_2r)
                        );
                        // This is [rhos*d(a_2ij/(1+chi_ij))/drhos]/(2*pi*d^3*eps*rhos)
                        auto da2iidrhos_term = 0.5*POW2(C_ij(i,j))*(
                            rho_dK_HS_drho*(
                                one_term(2*lambda_a_ij(i,j), xi_x_eff_2a)
                                -2*one_term(lambda_a_ij(i,j)+lambda_r_ij(i,j), xi_x_eff_ar)
                                +one_term(2*lambda_r_ij(i,j), xi_x_eff_2r))
                            +K_HS*(
                                rhosda1iidrhos_term(2*lambda_a_ij(i,i), xi_x_eff_2a, dxi_x_eff_dxix_2a, Bhatij_2a)
                                -2*rhosda1iidrhos_term(lambda_a_ij(i,i)+lambda_r_ij(i,i), xi_x_eff_ar, dxi_x_eff_dxix_ar, Bhatij_ar)
                                +rhosda1iidrhos_term(2*lambda_r_ij(i,i), xi_x_eff_2r, dxi_x_eff_dxix_2r, Bhatij_2r)
                                )
                            );
                        auto g2MCAij = 3.0*da2iidrhos_term + g2_noderivterm;
                        
                        auto betaepsilon = epsilon_ij(i,i)/T; // (1/(kB*T))/epsilon
                        auto theta = exp(betaepsilon)-1.0;
                        auto phi7 = phi.col(6);
                        auto gamma_cij = phi7(0)*(-tanh(phi7(1)*(phi7(2)-alpha_ij(i,j)))+1)*xi_x_bar*theta*exp(phi7(3)*xi_x_bar + phi7(4)*POW2(xi_x_bar)); // Eq. A37
                        auto g2ii = (1+gamma_cij)*g2MCAij;
                        
                        NumType giiMie = gdHSii*exp((betaepsilon*g1ii + POW2(betaepsilon)*g2ii)/gdHSii);
                        a_chain -= molefracs[i]*(m[i]-1)*log(giiMie);
                    }
                }
            }
            
            auto ahs = a_HS(rhos, xi);
            auto a_mono = mbar*(ahs + a1kB/T + a2kB2/(T*T) + a3kB3/(T*T*T));
            
            struct vals{
                decltype(dmat) dmat;
                decltype(rhos) rhos;
                decltype(mbar) mbar;
                decltype(xs) xs;
                decltype(xi) xi;
                decltype(xi_x) xi_x;
                decltype(xi_x_bar) xi_x_bar;
                decltype(a_mono) a_mono;
                decltype(a1kB) a1kB;
                decltype(a2kB2) a2kB2;
                decltype(a3kB3) a3kB3;
                decltype(a_chain) a_chain;
            };
            return vals{dmat, rhos, mbar, xs, xi, xi_x, xi_x_bar, a_mono, a1kB, a2kB2, a3kB3, a_chain};
        }
        
        template<typename RhoType>
        auto get_KHS(const RhoType& pf) const {
            return pow(1-pf,4)/(1 + 4*pf + 4*pf*pf - 4*pf*pf*pf + pf*pf*pf*pf);
        }
        
        /**
         \f[
          rhos\frac{\partial K_{HS}}{\partial \rho_s} = \xi\frac{\partial K_{HS}}{\partial \xi}
         \f]
         */
        template<typename RhoType>
        auto get_rhos_dK_HS_drhos(const RhoType& xi_x) const {
            auto num = -4*POW3(xi_x - 1)*(POW2(xi_x) - 5*xi_x - 2);
            auto den = POW2(POW4(xi_x) - 4*POW3(xi_x) + 4*POW2(xi_x) + 4*xi_x + 1);
            return num/den*xi_x;
        }
        
        template<typename RhoType, typename XiType>
        auto a_HS(const RhoType& rhos, const Eigen::Array<XiType, 4, 1>& xi) const{
            constexpr double MY_PI = static_cast<double>(EIGEN_PI);
            return 6/(MY_PI*rhos)*(3*xi[1]*xi[2]/(1-xi[3]) + POW3(xi[2])/(xi[3]*POW2(1-xi[3])) + (POW3(xi[2])/POW2(xi[3])-xi[0])*log(1-xi[3]));
        }
        
        /***
        Defining:
        \f[
        \hat B_{ij} \equiv \frac{B_{ij}}{2\pi\epsilon_{ij}d^3_{ij}\rho_s} = \frac{1-\xi_x/2}{(1-xi_x)^3}I-\frac{9\xi_x(1+\xi_x)}{2(1-\xi_x)^3}J
        \f]
        \note Eq. A12
        */
        template<typename XiType>
        auto get_Bhatij(const XiType& xi_x, const XiType& one_minus_xi_x3, double I, double J) const{
            return (1-xi_x/2.0)/one_minus_xi_x3*I - 9.0*xi_x*(1.0+xi_x)/(2*one_minus_xi_x3)*J;
        }
        
        /***
        \f[
        B = \hat B_{ij}\kappa \rho_s
        \f]
         \f[
         \frac{\partial B_{ij}}{\partial \rho_s}\right|_{T,\vec{z}} = \kappa\left(\hat B + \xi_x \frac{\partial \hat B}{\partial \xi_x}\right)
         \f]
        and thus
         \f[
        \rho_s \frac{\partial B_{ij}}{\partial \rho_s}\right|_{T,\vec{z}} = \hat B + \xi_x \frac{\partial \hat B}{\partial \xi_x}
         \f]
        */
        template<typename XiType>
        auto get_rhodBijdrho(const XiType& xi_x, const XiType& one_minus_xi_x3, double I, double J, const XiType& Bhatij) const{
            auto dBhatdxix = (-3*I*(xi_x - 2) - 27*J*xi_x*(xi_x + 1) + (xi_x - 1)*(I + 9*J*xi_x + 9*J*(xi_x + 1)))/(2*POW4(1-xi_x));
            return forceeval(Bhatij + dBhatdxix*xi_x);
        }
        
        template<typename XiType>
        auto get_a1Shatij(const XiType& xi_x_eff, double lambda_ij) const{
            return -1.0/(lambda_ij-3.0)*(1.0-xi_x_eff/2.0)/POW3(forceeval(1.0-xi_x_eff));
        }
        
        template<typename XiType>
        auto get_rhoda1Shatijdrho(const XiType& xi_x, const XiType& xi_x_eff, const XiType& dxixeffdxix, double lambda_ij) const{
            auto one_minus_xi_x_eff3 = POW3(1.0-xi_x_eff);
            auto da1Shatdxix = ((2.0*xi_x_eff - 5.0)*dxixeffdxix)/(2.0*(lambda_ij-3)*POW4(xi_x_eff-1.0))*xi_x;
            return forceeval(get_a1Shatij(xi_x_eff, lambda_ij) + da1Shatdxix);
        }
    };
    
    ///***
    // * \brief This class provides the evaluation of the chain contribution from SAFT-VR-Mie
    // */
    //class SAFTVRMieChainContribution{
    //    
    //private:
    //    const SAFTVRMieChainContributionTerms terms;
    //    const Eigen::ArrayXXd kmat; ///< binary interaction parameter matrix
    //public:
    //    SAFTVRMieChainContribution(const SAFTVRMieChainContributionTerms &terms, const Eigen::ArrayXXd &kmat)
    //    : terms(terms), kmat(kmat) {}
    //    
    //    SAFTVRMieChainContribution& operator=( const SAFTVRMieChainContribution& ) = delete; // non copyable
    //    
    //    template<typename TTYPE, typename RhoType, typename VecType>
    //    auto eval(const TTYPE& T, const RhoType& rhomolar, const VecType& mole_fractions) const {
    //        
    ////        std::size_t N = m.size();
    ////
    ////        if (mole_fractions.size() != N) {
    ////            throw std::invalid_argument("Length of mole_fractions (" + std::to_string(mole_fractions.size()) + ") is not the length of components (" + std::to_string(N) + ")");
    ////        }
    ////
    ////        using TRHOType = std::common_type_t<std::decay_t<TTYPE>, std::decay_t<RhoType>, std::decay_t<decltype(mole_fractions[0])>, std::decay_t<decltype(m[0])>>;
    ////
    ////        SAFTCalc<TTYPE, TRHOType> c;
    ////        c.m2_epsilon_sigma3_bar = static_cast<TRHOType>(0.0);
    ////        c.m2_epsilon2_sigma3_bar = static_cast<TRHOType>(0.0);
    ////        c.d.resize(N);
    ////        for (std::size_t i = 0; i < N; ++i) {
    ////            c.d[i] = sigma_Angstrom[i]*(1.0 - 0.12 * exp(-3.0*epsilon_over_k[i]/T)); // [A]
    ////            for (std::size_t j = 0; j < N; ++j) {
    ////                // Eq. A.5
    ////                auto sigma_ij = 0.5 * sigma_Angstrom[i] + 0.5 * sigma_Angstrom[j];
    ////                auto eij_over_k = sqrt(epsilon_over_k[i] * epsilon_over_k[j]) * (1.0 - kmat(i,j));
    ////                c.m2_epsilon_sigma3_bar = c.m2_epsilon_sigma3_bar + mole_fractions[i] * mole_fractions[j] * m[i] * m[j] * eij_over_k / T * pow(sigma_ij, 3);
    ////                c.m2_epsilon2_sigma3_bar = c.m2_epsilon2_sigma3_bar + mole_fractions[i] * mole_fractions[j] * m[i] * m[j] * pow(eij_over_k / T, 2) * pow(sigma_ij, 3);
    ////            }
    ////        }
    ////        auto mbar = (mole_fractions.template cast<TRHOType>().array()*m.template cast<TRHOType>().array()).sum();
    ////
    ////        /// Convert from molar density to number density in molecules/Angstrom^3
    ////        RhoType rho_A3 = rhomolar * N_A * 1e-30; //[molecules (not moles)/A^3]
    ////
    ////        constexpr double MY_PI = EIGEN_PI;
    ////        double pi6 = (MY_PI / 6.0);
    ////
    ////        /// Evaluate the components of zeta
    ////        using ta = std::common_type_t<decltype(pi6), decltype(m[0]), decltype(c.d[0]), decltype(rho_A3)>;
    ////        std::vector<ta> zeta(4);
    ////        for (std::size_t n = 0; n < 4; ++n) {
    ////            // Eqn A.8
    ////            auto dn = c.d.pow(n).eval();
    ////            TRHOType xmdn = forceeval((mole_fractions.template cast<TRHOType>().array()*m.template cast<TRHOType>().array()*dn.template cast<TRHOType>().array()).sum());
    ////            zeta[n] = forceeval(pi6*rho_A3*xmdn);
    ////        }
    ////
    ////        /// Packing fraction is the 4-th value in zeta, at index 3
    ////        auto eta = zeta[3];
    ////
    ////        auto [I1, etadI1deta] = get_I1(eta, mbar);
    ////        auto [I2, etadI2deta] = get_I2(eta, mbar);
    ////
    ////        // Hard chain contribution from G&S
    ////        using tt = std::common_type_t<decltype(zeta[0]), decltype(c.d[0])>;
    ////        Eigen::ArrayX<tt> lngii_hs(mole_fractions.size());
    ////        for (auto i = 0; i < lngii_hs.size(); ++i) {
    ////            lngii_hs[i] = log(gij_HS(zeta, c.d, i, i));
    ////        }
    ////        auto alphar_hc = forceeval(0.0);
    //        
    //        auto eta = 0.0;
    //        auto alphar_mono = forceeval(0.0);
    //        
    //        // Dispersive contribution
    //        auto alphar_chain = forceeval(0.0);
    //                                     
    ////        using eta_t = decltype(eta);
    //        using mono_t = decltype(alphar_mono);
    //        using chain_t = decltype(alphar_chain);
    //        struct SAFTVChainContributionTerms{
    ////            eta_t eta;
    //            mono_t alphar_mono;
    //            chain_t alphar_chain;
    //        };
    //        return SAFTVChainContributionTerms{forceeval(alphar_mono), forceeval(alphar_chain)};
    //    }
    //};
    //
    ///** A class used to evaluate mixtures using PC-SAFT model
    //
    //This is the classical Gross and Sadowski model from 2001: https://doi.org/10.1021/ie0003887
    //*/
    //class SAFTVRMie {
    //private:
    //    
    //    std::vector<std::string> names;
    //    Eigen::ArrayXXd kmat; ///< binary interaction parameter matrix
    //    
    //    SAFTVRMieChainContribution hardchain;
    //
    //    void check_kmat(std::size_t N) {
    //        if (kmat.cols() != kmat.rows()) {
    //            throw teqp::InvalidArgument("kmat rows and columns are not identical");
    //        }
    //        if (kmat.cols() == 0) {
    //            kmat.resize(N, N); kmat.setZero();
    //        }
    //        else if (kmat.cols() != N) {
    //            throw teqp::InvalidArgument("kmat needs to be a square matrix the same size as the number of components");
    //        }
    //    };
    //    auto get_coeffs_from_names(const std::vector<std::string> &names){
    //        SAFTVRMieLibrary library;
    //        return library.get_coeffs(names);
    //    }
    //    auto build_chain(const std::vector<SAFTVRMieCoeffs> &coeffs){
    //        check_kmat(coeffs.size());
    //        SAFTVRMieChainContributionTerms terms;
    //        auto i = 0;
    //        for (const auto &coeff : coeffs) {
    //            terms.m[i] = coeff.m;
    //            terms.mminus1[i] = m[i] - 1;
    //            sigma_Angstrom[i] = coeff.sigma_Angstrom;
    //            epsilon_over_k[i] = coeff.epsilon_over_k;
    //            names[i] = coeff.name;
    //            i++;
    //        }
    //        return SAFTVRMieChainContribution(std::move(terms), std::move(kmat));
    //    }
    //public:
    //    // SAFTVRMieMixture(const std::vector<std::string> &names, const Eigen::ArrayXXd& kmat = {}) : PCSAFTMixture(get_coeffs_from_names(names), kmat){};
    //    SAFTVRMieMixture(const std::vector<SAFTVRMieCoeffs> &coeffs, const Eigen::ArrayXXd &kmat = {}) : kmat(kmat), hardchain(build_chain(coeffs)) {};
    //    
    ////    PCSAFTMixture( const PCSAFTMixture& ) = delete; // non construction-copyable
    //    SAFTVRMieMixture& operator=( const SAFTVRMieMixture& ) = delete; // non copyable
    //    
    //    auto get_m() const { return m; }
    //    auto get_sigma_Angstrom() const { return sigma_Angstrom; }
    //    auto get_epsilon_over_k_K() const { return epsilon_over_k; }
    //    auto get_kmat() const { return kmat; }
    //    auto get_lambda_a() const { return lambda_a; }
    //    auto get_lambda_r() const { return lambda_r; }
    //    
    //    // template<typename VecType>
    //    // double max_rhoN(const double T, const VecType& mole_fractions) const {
    //    //     auto N = mole_fractions.size();
    //    //     Eigen::ArrayX<double> d(N);
    //    //     for (auto i = 0; i < N; ++i) {
    //    //         d[i] = sigma_Angstrom[i] * (1.0 - 0.12 * exp(-3.0 * epsilon_over_k[i] / T));
    //    //     }
    //    //     return 6 * 0.74 / EIGEN_PI / (mole_fractions*m*powvec(d, 3)).sum()*1e30; // particles/m^3
    //    // }
    //    
    //    template<class VecType>
    //    auto R(const VecType& molefrac) const {
    //        return get_R_gas<decltype(molefrac[0])>();
    //    }
    //
    //    template<typename TTYPE, typename RhoType, typename VecType>
    //    auto alphar(const TTYPE& T, const RhoType& rhomolar, const VecType& mole_fractions) const {
    //        // First values for the chain with dispersion (always included)
    //        auto vals = hardchain.eval(T, rhomolar, mole_fractions);
    //        auto alphar = forceeval(vals.alphar_hc + vals.alphar_disp);
    //        return forceeval(alphar);
    //    }
    //};
    
    } /* namespace SAFTVRMie */
    }; // namespace teqp