41#include "boost/math/tools/toms748_solve.hpp"
42#include "nlohmann/json.hpp"
45namespace superancillary{
50template<
typename Matrix>
56 D = Matrix::Identity(A.rows(), A.cols());
57 bool converged =
false;
60 for (Eigen::Index i = 0; i < A.rows(); ++i) {
61 double c = Aprime.col(i).template lpNorm<p>();
62 double r = Aprime.row(i).template lpNorm<p>();
63 double s = pow(c, p) + pow(r, p);
79 if (pow(c, p) + pow(r, p) < 0.95*s) {
94 auto N = coeffs.size() - 1;
95 if (A.rows() != N){
throw std::invalid_argument(
"A.rows() != N"); }
100 A.col(N-1) = -coeffs.head(N)/(2.0*coeffs(N));
101 A(N - 2, N - 1) += 0.5;
103 for (
int j = 1; j < N - 1; ++j) {
109 Eigen::ArrayXd coeffs_ = Eigen::Map<const Eigen::ArrayXd>(&coeffs[0], coeffs.size());
123 Eigen::MatrixXd L(N + 1, N + 1);
124 Eigen::MatrixXd U(N + 1, N + 1);
125 for (std::size_t j = 0; j <= N; ++j) {
126 for (std::size_t k = j; k <= N; ++k) {
127 double p_j = (j == 0 || j == N) ? 2 : 1;
128 double p_k = (k == 0 || k == N) ? 2 : 1;
129 double cosjPikN = cos((j*EIGEN_PI*k) / N);
130 L(j, k) = 2.0/(p_j*p_k*N)*cosjPikN;
139 return std::make_tuple(L, U);
143 Eigen::Map<const Eigen::ArrayXd>
X(&x[0], x.size());
144 return X.tail(M).matrix().norm() /
X.head(M).matrix().norm();
148 return x.tail(M).matrix().norm() / x.head(M).matrix().norm();
155template<
typename Function,
typename Container>
156inline auto dyadic_splitting(
const std::size_t N,
const Function& func,
const double xmin,
const double xmax,
157 const int M=3,
const double tol=1e-12,
const int max_refine_passes = 8,
158 const std::optional<std::function<
void(
int,
const Container&)>>& callback = std::nullopt) -> Container
160 using CE_t = std::decay_t<
decltype(Container().front())>;
161 using ArrayType = std::decay_t<
decltype(CE_t().coeff())>;
163 Eigen::MatrixXd Lmat, Umat;
166 auto builder = [&](
double xmin,
double xmax) -> CE_t{
168 auto get_nodes_realworld = [N, xmin, xmax]() -> Eigen::ArrayXd{
169 Eigen::ArrayXd nodes = (Eigen::ArrayXd::LinSpaced(N + 1, 0,
static_cast<double>(N)).array()*EIGEN_PI / N).cos();
170 return ((xmax - xmin)*nodes + (xmax + xmin))*0.5;
173 Eigen::ArrayXd x = get_nodes_realworld();
177 for (
auto j = 0L; j < x.size(); ++j){
178 x(j) = func(j,
static_cast<long>(x.size()), x(j));
182 Eigen::ArrayXd c = Lmat*x.matrix();
185 if (!c.allFinite() ) {
186 throw std::invalid_argument(
"At least one coefficient is non-finite");
188 if constexpr (std::is_same_v<ArrayType, std::vector<double>>){
189 return {xmin, xmax, std::vector<double>(&c[0], &c[0] + c.size())};
193 return {xmin, xmax, c};
198 Container expansions;
199 expansions.emplace_back(builder(xmin, xmax));
202 for (
int refine_pass = 0; refine_pass < max_refine_passes; ++refine_pass) {
203 bool all_converged =
true;
205 for (
int iexpansion =
static_cast<int>(expansions.size())-1; iexpansion >= 0; --iexpansion) {
206 auto& expan = expansions[iexpansion];
211 auto xmid = (expan.xmin() + expan.xmax()) / 2;
212 CE_t newleft{builder(expan.xmin(), xmid)};
213 CE_t newright{builder(xmid, expan.xmax())};
215 expansions.at(iexpansion) = std::move(newleft);
216 expansions.insert(expansions.begin() + iexpansion+1, newright);
217 all_converged =
false;
222 callback.value()(refine_pass, expansions);
224 if (all_converged) {
break; }
242template<
typename ArrayType>
260 const auto xmin()
const {
return m_xmin; }
263 const auto xmax()
const {
return m_xmax; }
266 const auto&
coeff()
const {
return m_coeff; }
274 T xscaled = (2.0*x - (m_xmax + m_xmin)) / (m_xmax - m_xmin);
275 int Norder =
static_cast<int>(m_coeff.size()) - 1;
276 T u_k = 0, u_kp1 = m_coeff[Norder], u_kp2 = 0;
277 for (
int k = Norder-1; k > 0; k--){
279 u_k = 2.0*xscaled*u_kp1 - u_kp2 + m_coeff[k];
281 u_kp2 = u_kp1; u_kp1 = u_k;
283 T retval = m_coeff[0] + xscaled*u_kp1 - u_kp2;
290 if (x.size() != y.size()){
throw std::invalid_argument(
"x and y are not the same size"); }
291 for (
auto i = 0; i < x.size(); ++i){ y(i) =
eval(x(i)); }
297 for (std::size_t i = 0; i < N; ++i){ y[i] =
eval(x[i]); }
303 if (x.size() != y.size()){
throw std::invalid_argument(
"x and y are not the same size"); }
309 Eigen::Index N = m_coeff.size()-1;
310 Eigen::ArrayXd nodes = (Eigen::ArrayXd::LinSpaced(N + 1, 0, N).array()*EIGEN_PI / N).cos();
311 return ((m_xmax - m_xmin)*nodes + (m_xmax + m_xmin))*0.5;
327 auto solve_for_x_count(
double y,
double a,
double b,
unsigned int bits, std::size_t max_iter,
double boundsytol)
const{
328 using namespace boost::math::tools;
329 std::size_t counter = 0;
330 auto f = [&](
double x){ counter++;
return eval(x) - y; };
332 if (std::abs(fa) < boundsytol){
return std::make_tuple(a, std::size_t{1}); }
334 if (std::abs(fb) < boundsytol){
return std::make_tuple(b, std::size_t{2}); }
335 boost::math::uintmax_t max_iter_ =
static_cast<boost::math::uintmax_t
>(max_iter);
336 auto [l, r] = toms748_solve(f, a, b, fa, fb, eps_tolerance<double>(bits), max_iter_);
337 return std::make_tuple((r+l)/2.0, counter);
342 auto solve_for_x(
double y,
double a,
double b,
unsigned int bits, std::size_t max_iter,
double boundsytol)
const{
348 auto solve_for_x_many(
const T& y,
double a,
double b,
unsigned int bits, std::size_t max_iter,
double boundsytol, T& x, T& counts)
const{
349 if (x.size() != y.size()){
throw std::invalid_argument(
"x and y are not the same size"); }
350 for (
auto i = 0; i < x.size(); ++i){ std::tie(x(i), counts(i)) =
solve_for_x_count(y(i), a, b, bits, max_iter, boundsytol); }
355 auto solve_for_x_manyC(
const T y[], std::size_t N,
double a,
double b,
unsigned int bits, std::size_t max_iter,
double boundsytol, T x[], T counts[])
const{
356 for (std::size_t i = 0; i < N; ++i){
357 std::tie(x[i], counts[i]) =
solve_for_x_count(y[i], a, b, bits, max_iter, boundsytol);
364 ArrayType c = m_coeff;
365 for (std::size_t deriv_counter = 0; deriv_counter < Nderiv; ++deriv_counter) {
366 std::size_t N = c.size() - 1,
369 for (std::size_t r = 0; r <= Nd; ++r) {
371 for (std::size_t k = r + 1; k <= N; ++k) {
373 if ((k - r) % 2 == 1) {
382 cd[r] /= (m_xmax-m_xmin)/2.0;
395static_assert(std::is_move_assignable_v<ChebyshevExpansion<std::vector<double>>>);
429template<
typename ArrayType=Eigen::ArrayXd>
432 const double thresh_imag = 1e-15;
433 std::vector<ChebyshevExpansion<ArrayType>> m_expansions;
434 std::vector<double> m_x_at_extrema;
435 std::vector<IntervalMatch> m_monotonic_intervals;
437 Eigen::ArrayXd head(
const Eigen::ArrayXd& c, Eigen::Index N)
const{
440 std::vector<double> head(
const std::vector<double>& c, Eigen::Index N)
const{
441 return std::vector<double>(c.begin(), c.begin()+N);
448 std::vector<double> determine_extrema(
const std::decay_t<
decltype(m_expansions)>& expansions,
double thresh_im)
const{
449 std::vector<double> x_at_extrema;
450 Eigen::MatrixXd companion_matrix, cprime, D;
451 for (
auto& expan : expansions){
453 auto cd = expan.do_derivs(1);
455 int ilastnonzero =
static_cast<int>(cd.size())-1;
456 for (
int i =
static_cast<int>(cd.size())-1; i >= 0; --i){
457 if (std::abs(cd[i]) != 0){
458 ilastnonzero = i;
break;
461 if (ilastnonzero !=
static_cast<int>(cd.size()-1)){
462 cd = head(cd, ilastnonzero);
466 if (companion_matrix.rows() !=
static_cast<int>(cd.size()-1)){
467 companion_matrix.resize(cd.size()-1, cd.size()-1); companion_matrix.setZero();
468 D.resizeLike(companion_matrix); D.setZero();
469 cprime.resizeLike(companion_matrix); cprime.setZero();
472 cprime = companion_matrix;
475 for (
auto &root : companion_matrix.eigenvalues()){
477 auto re_n11 = root.real(), im = root.imag();
478 if (std::abs(im) < thresh_im){
479 if (re_n11 >= -1 && re_n11 <= 1){
480 x_at_extrema.push_back(((expan.xmax() - expan.xmin())*re_n11 + (expan.xmax() + expan.xmin())) / 2.0);
485 std::sort(x_at_extrema.begin(), x_at_extrema.end());
493 std::vector<IntervalMatch> build_monotonic_intervals(
const std::vector<double>& x_at_extrema)
const{
494 std::vector<IntervalMatch> intervals;
496 auto sort = [](
double& x,
double &y){
if (x > y){ std::swap(x, y);} };
499 auto xmin = m_expansions.front().xmin();
500 auto xmax = m_expansions.back().xmax();
515 intervals.push_back(im);
518 auto newx = x_at_extrema;
519 newx.insert(newx.begin(), m_expansions.front().xmin());
520 newx.insert(newx.end(), m_expansions.back().xmax());
523 auto interval_intersection = [](
const auto&t1,
const auto& t2){
524 auto a = std::max(t1.xmin(), t2.xmin);
525 auto b = std::min(t1.xmax(), t2.xmax);
526 return std::make_tuple(a, b);
529 for (
auto j = 0; j < static_cast<int>(newx.size()-1); ++j){
530 double xmin = newx[j],
xmax = newx[j+1];
534 for (
auto i = 0UL; i < m_expansions.size(); ++i){
536 auto [a,b] = interval_intersection(m_expansions[i], A{
xmin,
xmax} );
538 const auto&e = m_expansions[i];
543 double ymin = e.eval(a), ymax = e.eval(b); sort(ymin, ymax);
552 double yminoverall =
eval(
xmin), ymaxoverall =
eval(
xmax); sort(yminoverall, ymaxoverall);
553 im.
ymin = yminoverall;
554 im.
ymax = ymaxoverall;
555 intervals.push_back(im);
567 m_expansions(std::move(expansions)),
568 m_x_at_extrema(determine_extrema(m_expansions, thresh_imag)),
569 m_monotonic_intervals(build_monotonic_intervals(m_x_at_extrema)),
575 m_expansions(other.m_expansions),
576 m_x_at_extrema(other.m_x_at_extrema),
577 m_monotonic_intervals(other.m_monotonic_intervals),
578 m_xmin(other.
xmin()),
585 std::swap(m_expansions, other.m_expansions);
586 std::swap(m_x_at_extrema, other.m_x_at_extrema);
587 std::swap(m_monotonic_intervals, other.m_monotonic_intervals);
588 std::swap(m_xmin, other.m_xmin);
589 std::swap(m_xmax, other.m_xmax);
603 const auto xmin()
const {
return m_xmin; }
606 const auto xmax()
const {
return m_xmax; }
611 return m_monotonic_intervals.size() == 1;
622 auto midpoint_Knuth = [](
int x,
int y) {
623 return (x & y) + ((x ^ y) >> 1);
626 int iL = 0U, iR =
static_cast<int>(m_expansions.size()) - 1, iM;
627 while (iR - iL > 1) {
628 iM = midpoint_Knuth(iL, iR);
629 if (x >= m_expansions[iM].
xmin()) {
636 return (x < m_expansions[iL].
xmax()) ? iL : iR;
644 return m_expansions[
get_index(x)].eval(x);
648 template<
typename Container>
649 const auto eval_many(
const Container& x, Container& y)
const {
650 if (x.size() != y.size()){
throw std::invalid_argument(
"x and y are not the same size"); }
651 for (
auto i = 0U; i < x.size(); ++i){
657 template<
typename Container>
658 const auto eval_manyC(
const Container x[], Container y[], std::size_t N)
const {
659 for (
auto i = 0U; i < N; ++i){
666 std::vector<IntervalMatch> matches;
667 for (
auto & interval : m_monotonic_intervals){
668 if (y >= interval.ymin && y <= interval.ymax){
669 matches.push_back(interval);
677 const auto get_x_for_y(
double y,
unsigned int bits, std::size_t max_iter,
double boundsftol)
const {
678 std::vector<std::pair<double, int>> solns;
679 for (
const auto& interval: m_monotonic_intervals){
683 if (interval.contains_y(y)){
685 for (
const auto& ei: interval.expansioninfo){
688 if (ei.contains_y(y)){
690 auto [xvalue, num_steps] = e.
solve_for_x_count(y, ei.xmin, ei.xmax, bits, max_iter, boundsftol);
691 solns.emplace_back(xvalue,
static_cast<int>(num_steps));
700 template<
typename Container>
701 const auto count_x_for_y_many(
const Container& y,
unsigned int bits, std::size_t max_iter,
double boundsftol, Container& x)
const {
702 if (x.size() != y.size()){
throw std::invalid_argument(
"x and y are not the same size"); }
703 for (
auto i = 0U; i < x.size(); ++i){
704 x(i) =
get_x_for_y(y(i), bits, max_iter, boundsftol).size();
709 template<
typename Container>
710 const auto count_x_for_y_manyC(
const Container y[],
size_t N,
unsigned int bits, std::size_t max_iter,
double boundsftol, Container x[])
const {
711 for (
auto i = 0U; i < N; ++i){
712 x[i] =
get_x_for_y(y[i], bits, max_iter, boundsftol).size();
717static_assert(std::is_copy_constructible_v<ChebyshevApproximation1D<std::vector<double>>>);
718static_assert(std::is_copy_assignable_v<ChebyshevApproximation1D<std::vector<double>>>);
734template<
typename ArrayType=Eigen::ArrayXd>
743 std::optional<ChebyshevApproximation1D<ArrayType>> m_hL,
753 double m_rhocrit_num;
763 auto loader(
const nlohmann::json &j,
const std::string& key){
764 std::vector<ChebyshevExpansion<ArrayType>> buf;
773 for (
auto& block : j[key]){
774 buf.emplace_back(block.at(
"xmin"), block.at(
"xmax"), block.at(
"coef"));
782 auto make_invlnp(Eigen::Index Ndegree){
787 const double EPSILON = std::numeric_limits<double>::epsilon();
789 auto func = [&](
long i,
long Npts,
double lnp) ->
double{
793 if (solns.size() != 1){
794 if ((i == 0 || i == Npts-1) && ( p > pmin*(1-EPSILON*1000) && p < pmin*(1+EPSILON*1000))){
797 if ((i == 0 || i == Npts-1) && ( p > pmax*(1-EPSILON*1000) && p < pmax*(1+EPSILON*1000))){
800 std::stringstream ss;
801 ss << std::setprecision(20) <<
"Must have one solution for T(p), got " << solns.size() <<
" for " << p <<
" Pa; limits are [" << pmin << +
" Pa , " << pmax <<
" Pa]; i is " << i;
802 throw std::invalid_argument(ss.str());
804 auto [T, iters] = solns[0];
808 using CE_t = std::vector<ChebyshevExpansion<ArrayType>>;
809 return detail::dyadic_splitting<decltype(func), CE_t>(Ndegree, func, log(pmin), log(pmax), 3, 1e-12, 26);
818 m_rhoL(std::move(loader(j,
"jexpansions_rhoL"))),
819 m_rhoV(std::move(loader(j,
"jexpansions_rhoV"))),
820 m_p(std::move(loader(j,
"jexpansions_p"))),
822 m_Tcrit_num(j.at(
"meta").at(
"Tcrittrue / K")),
823 m_rhocrit_num(j.at(
"meta").at(
"rhocrittrue / mol/m^3")),
824 m_pmin(m_p.eval(m_p.xmin())),
825 m_pmax(m_p.eval(m_p.xmax()))
839 auto get_or_throw = [&](
const auto& v) ->
const auto& {
844 throw std::invalid_argument(
"unable to get the variable "+std::string(1, k)+
", make sure it has been added to superancillary");
848 case 'P':
return m_p;
849 case 'D':
return (Q == 0) ? m_rhoL : m_rhoV;
850 case 'H':
return (Q == 0) ? get_or_throw(m_hL) : get_or_throw(m_hV);
851 case 'S':
return (Q == 0) ? get_or_throw(m_sL) : get_or_throw(m_sV);
852 case 'U':
return (Q == 0) ? get_or_throw(m_uL) : get_or_throw(m_uV);
853 default:
throw std::invalid_argument(
"Bad key of '" + std::string(1, k) +
"'");
862 m_invlnp = make_invlnp(Ndegree);
882 void add_variable(
char var,
const std::function<
double(
double,
double)> & caller){
883 Eigen::MatrixXd Lmat, Umat;
886 auto builder = [&](
char var,
auto& variantL,
auto& variantV){
887 std::vector<ChebyshevExpansion<ArrayType>> newexpL, newexpV;
890 if (expsL.get_expansions().size() != expsV.get_expansions().size()){
891 throw std::invalid_argument(
"L&V are not the same size");
893 for (
auto i = 0U; i < expsL.get_expansions().size(); ++i){
894 const auto& expL = expsL.get_expansions()[i];
895 const auto& expV = expsV.get_expansions()[i];
896 const auto& T = expL.get_nodes_realworld();
898 Eigen::ArrayXd funcL = Umat*expL.coeff().matrix();
899 Eigen::ArrayXd funcV = Umat*expV.coeff().matrix();
902 for (
auto j = 0; j < funcL.size(); ++j){
903 funcL(j) = caller(T(j), funcL(j));
904 funcV(j) = caller(T(j), funcV(j));
907 newexpL.emplace_back(expL.xmin(), expL.xmax(), (Lmat*funcL.matrix()).eval());
908 newexpV.emplace_back(expV.xmin(), expV.xmax(), (Lmat*funcV.matrix()).eval());
911 variantL.emplace(std::move(newexpL));
912 variantV.emplace(std::move(newexpV));
916 case 'H': builder(var, m_hL, m_hV);
break;
917 case 'S': builder(var, m_sL, m_sV);
break;
918 case 'U': builder(var, m_uL, m_uV);
break;
919 default:
throw std::invalid_argument(
"nope");
929 if (Q == 1 || Q == 0){
933 throw std::invalid_argument(
"invalid_value for Q");
940 template <
typename Container>
941 void eval_sat_many(
const Container& T,
char k,
short Q, Container& y)
const {
942 if (T.size() != y.size()){
throw std::invalid_argument(
"T and y are not the same size"); }
944 for (
auto i = 0; i < T.size(); ++i){
945 y(i) = approx.eval(T(i));
952 template <
typename Container>
953 void eval_sat_manyC(
const Container T[], std::size_t N,
char k,
short Q, Container y[])
const {
956 for (std::size_t i = 0; i < N; ++i){
957 y[i] = approx.eval(T[i]);
970 auto solve_for_T(
double propval,
char k,
bool Q,
unsigned int bits=64,
unsigned int max_iter=100,
double boundsftol=1e-13)
const{
971 return get_approx1d(k, Q).get_x_for_y(propval, bits, max_iter, boundsftol);
985 return (1/propval-v_L)/(v_V-v_L);
990 return (propval-L)/(V-L);
1016 double v = q*v_V + (1-q)*v_L;
1022 return q*V + (1-q)*L;
1027 template <
typename Container>
1028 void get_yval_many(
const Container& T,
char k,
const Container& q, Container& y)
const{
1029 if (T.size() != y.size() || T.size() != q.size()){
throw std::invalid_argument(
"T, q, and y are not all the same size"); }
1035 for (
auto i = 0; i < T.size(); ++i){
1037 double v_L = 1.0/L.eval(T(i));
1038 double v_V = 1.0/V.eval(T(i));
1039 double v = q(i)*v_V + (1-q(i))*v_L;
1044 for (
auto i = 0; i < T.size(); ++i){
1045 y(i) = q(i)*V.eval(T(i)) + (1-q(i))*L.eval(T(i));
1056 auto get_all_intersections(
const char k,
const double val,
unsigned int bits, std::size_t max_iter,
double boundsftol)
const{
1059 auto TsatL = L.get_x_for_y(val, bits, max_iter, boundsftol);
1060 const auto TsatV = V.get_x_for_y(val, bits, max_iter, boundsftol);
1061 for (
auto &&el : TsatV){
1062 TsatL.push_back(el);
1084 std::optional<SuperAncillaryTwoPhaseSolution>
iterate_for_Tq_XY(
double Tmin,
double Tmax,
char ch1,
double val1,
char ch2,
double val2,
unsigned int bits, std::size_t max_iter,
double boundsftol)
const {
1086 std::size_t counter = 0;
1087 auto f = [&](
double T_){
1090 double resid =
get_yval(T_, q_fromv1, ch2) - val2;
1093 using namespace boost::math::tools;
1094 double fTmin = f(Tmin), fTmax = f(Tmax);
1101 auto returner = [&](){
1109 if (std::abs(fTmin) < boundsftol){
1110 T = Tmin;
return returner();
1112 if (std::abs(fTmax) < boundsftol){
1113 T = Tmax;
return returner();
1115 if (fTmin*fTmax > 0){
1118 return std::nullopt;
1122 boost::math::uintmax_t max_iter_ =
static_cast<boost::math::uintmax_t
>(max_iter);
1123 auto [l, r] = toms748_solve(f, Tmin, Tmax, fTmin, fTmax, eps_tolerance<double>(bits), max_iter_);
1128 std::cout <<
"fTmin,fTmax: " << fTmin <<
"," << fTmax << std::endl;
1142 std::optional<SuperAncillaryTwoPhaseSolution>
solve_for_Tq_DX(
const double rho,
const double propval,
const char k,
unsigned int bits, std::size_t max_iter,
double boundsftol)
const {
1147 std::size_t rhosat_soln_count = Tsat.size();
1149 std::tuple<double, double> Tsearchrange;
1150 if (rhosat_soln_count == 1){
1153 Tsearchrange = std::make_tuple(Lrho.xmin*0.999, std::get<0>(Tsat[0]));
1154 }
else if (rhosat_soln_count == 2){
1155 double y1 = std::get<0>(Tsat[0]), y2 = std::get<0>(Tsat[1]);
1156 if (y2 < y1){ std::swap(y2, y2); }
1157 Tsearchrange = std::make_tuple(y1, y2);
1160 throw std::invalid_argument(
"cannot have number of solutions other than 1 or 2; got "+
std::to_string(rhosat_soln_count)+
" solutions");
1162 auto [a, b] = Tsearchrange;
1163 return iterate_for_Tq_XY(a, b,
'D', rho, k, propval, bits, max_iter, boundsftol);
1167 template <
typename Container>
1168 void solve_for_Tq_DX_many(
const Container& rho,
const Container& propval,
const char k,
unsigned int bits, std::size_t max_iter,
double boundsftol, Container& T, Container& q, Container& count){
1169 if (std::set<std::size_t>({rho.size(), propval.size(), T.size(), q.size(), count.size()}).size() != 1){
1170 throw std::invalid_argument(
"rho, propval, T, q, count are not all the same size");
1172 for (
auto i = 0U; i < T.size(); ++i){
1173 auto osoln =
solve_for_Tq_DX(rho(i), propval(i), k, bits, max_iter, boundsftol);
1175 const auto& soln = osoln.value();
1178 count(i) = soln.counter;
1374static_assert(std::is_copy_constructible_v<SuperAncillary<std::vector<double>>>);
1375static_assert(std::is_copy_assignable_v<SuperAncillary<std::vector<double>>>);