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);
69 while (c < r / beta) {
74 while (c >= r * beta) {
79 if (pow(c, p) + pow(r, p) < 0.95 * s) {
94 auto N = coeffs.size() - 1;
96 throw std::invalid_argument(
"A.rows() != N");
102 A.col(N - 1) = -coeffs.head(N) / (2.0 * coeffs(N));
103 A(N - 2, N - 1) += 0.5;
105 for (
int j = 1; j < N - 1; ++j) {
111 Eigen::ArrayXd coeffs_ = Eigen::Map<const Eigen::ArrayXd>(&coeffs[0], coeffs.size());
124 Eigen::MatrixXd L(N + 1, N + 1);
125 Eigen::MatrixXd U(N + 1, N + 1);
126 for (std::size_t j = 0; j <= N; ++j) {
127 for (std::size_t k = j; k <= N; ++k) {
128 double p_j = (j == 0 || j == N) ? 2 : 1;
129 double p_k = (k == 0 || k == N) ? 2 : 1;
130 double cosjPikN = cos((j * EIGEN_PI * k) / N);
131 L(j, k) = 2.0 / (p_j * p_k * N) * cosjPikN;
140 return std::make_tuple(L, U);
144 Eigen::Map<const Eigen::ArrayXd>
X(&x[0], x.size());
145 return X.tail(M).matrix().norm() /
X.head(M).matrix().norm();
149 return x.tail(M).matrix().norm() / x.head(M).matrix().norm();
156template <
typename Function,
typename Container>
157inline auto dyadic_splitting(
const std::size_t N,
const Function& func,
const double xmin,
const double xmax,
const int M = 3,
158 const double tol = 1e-12,
const int max_refine_passes = 8,
159 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 {
167 auto get_nodes_realworld = [N, xmin, xmax]() -> Eigen::ArrayXd {
168 Eigen::ArrayXd nodes = (Eigen::ArrayXd::LinSpaced(N + 1, 0,
static_cast<double>(N)).array() * EIGEN_PI / N).cos();
169 return ((xmax - xmin) * nodes + (xmax + xmin)) * 0.5;
172 Eigen::ArrayXd x = get_nodes_realworld();
176 for (
auto j = 0L; j < x.size(); ++j) {
177 x(j) = func(j,
static_cast<long>(x.size()), x(j));
181 Eigen::ArrayXd c = Lmat * x.matrix();
184 if (!c.allFinite()) {
185 throw std::invalid_argument(
"At least one coefficient is non-finite");
187 if constexpr (std::is_same_v<ArrayType, std::vector<double>>) {
188 return {xmin, xmax, std::vector<double>(&c[0], &c[0] + c.size())};
191 return {xmin, xmax, c};
196 Container expansions;
197 expansions.emplace_back(builder(xmin, xmax));
200 for (
int refine_pass = 0; refine_pass < max_refine_passes; ++refine_pass) {
201 bool all_converged =
true;
203 for (
int iexpansion =
static_cast<int>(expansions.size()) - 1; iexpansion >= 0; --iexpansion) {
204 auto& expan = expansions[iexpansion];
209 auto xmid = (expan.xmin() + expan.xmax()) / 2;
210 CE_t newleft{builder(expan.xmin(), xmid)};
211 CE_t newright{builder(xmid, expan.xmax())};
213 expansions.at(iexpansion) = std::move(newleft);
214 expansions.insert(expansions.begin() + iexpansion + 1, newright);
215 all_converged =
false;
220 callback.value()(refine_pass, expansions);
241template <
typename ArrayType>
277 template <
typename T>
280 T xscaled = (2.0 * x - (m_xmax + m_xmin)) / (m_xmax - m_xmin);
281 int Norder =
static_cast<int>(m_coeff.size()) - 1;
282 T u_k = 0, u_kp1 = m_coeff[Norder], u_kp2 = 0;
283 for (
int k = Norder - 1; k > 0; k--) {
285 u_k = 2.0 * xscaled * u_kp1 - u_kp2 + m_coeff[k];
290 T retval = m_coeff[0] + xscaled * u_kp1
296 template <
typename T>
298 if (x.size() != y.size()) {
299 throw std::invalid_argument(
"x and y are not the same size");
301 for (
auto i = 0; i < x.size(); ++i) {
307 template <
typename T>
309 for (std::size_t i = 0; i < N; ++i) {
315 template <
typename T>
317 if (x.size() != y.size()) {
318 throw std::invalid_argument(
"x and y are not the same size");
325 Eigen::Index N = m_coeff.size() - 1;
326 Eigen::ArrayXd nodes = (Eigen::ArrayXd::LinSpaced(N + 1, 0, N).array() * EIGEN_PI / N).cos();
327 return ((m_xmax - m_xmin) * nodes + (m_xmax + m_xmin)) * 0.5;
343 auto solve_for_x_count(
double y,
double a,
double b,
unsigned int bits, std::size_t max_iter,
double boundsytol)
const {
344 using namespace boost::math::tools;
345 std::size_t counter = 0;
346 auto f = [&](
double x) {
351 if (std::abs(fa) < boundsytol) {
352 return std::make_tuple(a, std::size_t{1});
355 if (std::abs(fb) < boundsytol) {
356 return std::make_tuple(b, std::size_t{2});
358 boost::math::uintmax_t max_iter_ =
static_cast<boost::math::uintmax_t
>(max_iter);
359 auto [l, r] = toms748_solve(f, a, b, fa, fb, eps_tolerance<double>(bits), max_iter_);
360 return std::make_tuple((r + l) / 2.0, counter);
365 auto solve_for_x(
double y,
double a,
double b,
unsigned int bits, std::size_t max_iter,
double boundsytol)
const {
370 template <
typename T,
typename CountsT>
371 auto solve_for_x_many(
const T& y,
double a,
double b,
unsigned int bits, std::size_t max_iter,
double boundsytol, T& x, CountsT& counts)
const {
372 if (x.size() != y.size()) {
373 throw std::invalid_argument(
"x and y are not the same size");
375 for (
auto i = 0; i < x.size(); ++i) {
376 std::tie(x(i), counts(i)) =
solve_for_x_count(y(i), a, b, bits, max_iter, boundsytol);
381 template <
typename T,
typename CountsT>
382 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[],
383 CountsT counts[])
const {
384 for (std::size_t i = 0; i < N; ++i) {
385 std::tie(x[i], counts[i]) =
solve_for_x_count(y[i], a, b, bits, max_iter, boundsytol);
392 ArrayType c = m_coeff;
393 for (std::size_t deriv_counter = 0; deriv_counter < Nderiv; ++deriv_counter) {
394 std::size_t N = c.size() - 1,
397 for (std::size_t r = 0; r <= Nd; ++r) {
399 for (std::size_t k = r + 1; k <= N; ++k) {
401 if ((k - r) % 2 == 1) {
402 cd[r] += 2 * k * c[k];
410 cd[r] /= (m_xmax - m_xmin) / 2.0;
422static_assert(std::is_move_assignable_v<ChebyshevExpansion<std::vector<double>>>);
462template <
typename ArrayType = Eigen::ArrayXd>
466 const double thresh_imag = 1e-15;
467 std::vector<ChebyshevExpansion<ArrayType>> m_expansions;
468 std::vector<double> m_x_at_extrema;
469 std::vector<IntervalMatch> m_monotonic_intervals;
471 Eigen::ArrayXd head(
const Eigen::ArrayXd& c, Eigen::Index N)
const {
474 std::vector<double> head(
const std::vector<double>& c, Eigen::Index N)
const {
475 return std::vector<double>(c.begin(), c.begin() + N);
482 std::vector<double> determine_extrema(
const std::decay_t<
decltype(m_expansions)>& expansions,
double thresh_im)
const {
483 std::vector<double> x_at_extrema;
484 Eigen::MatrixXd companion_matrix, cprime, D;
485 for (
auto& expan : expansions) {
487 auto cd = expan.do_derivs(1);
489 int ilastnonzero =
static_cast<int>(cd.size()) - 1;
490 for (
int i =
static_cast<int>(cd.size()) - 1; i >= 0; --i) {
491 if (std::abs(cd[i]) != 0) {
496 if (ilastnonzero !=
static_cast<int>(cd.size() - 1)) {
497 cd = head(cd, ilastnonzero);
501 if (companion_matrix.rows() !=
static_cast<int>(cd.size() - 1)) {
502 companion_matrix.resize(cd.size() - 1, cd.size() - 1);
503 companion_matrix.setZero();
504 D.resizeLike(companion_matrix);
506 cprime.resizeLike(companion_matrix);
510 cprime = companion_matrix;
513 for (
auto& root : companion_matrix.eigenvalues()) {
515 auto re_n11 = root.real(), im = root.imag();
516 if (std::abs(im) < thresh_im) {
517 if (re_n11 >= -1 && re_n11 <= 1) {
518 x_at_extrema.push_back(((expan.xmax() - expan.xmin()) * re_n11 + (expan.xmax() + expan.xmin())) / 2.0);
523 std::sort(x_at_extrema.begin(), x_at_extrema.end());
531 std::vector<IntervalMatch> build_monotonic_intervals(
const std::vector<double>& x_at_extrema)
const {
532 std::vector<IntervalMatch> intervals;
534 auto sort = [](
double& x,
double& y) {
541 auto xmin = m_expansions.front().xmin();
542 auto xmax = m_expansions.back().xmax();
557 intervals.push_back(im);
559 auto newx = x_at_extrema;
560 newx.insert(newx.begin(), m_expansions.front().xmin());
561 newx.insert(newx.end(), m_expansions.back().xmax());
564 auto interval_intersection = [](
const auto& t1,
const auto& t2) {
565 auto a = std::max(t1.xmin(), t2.xmin);
566 auto b = std::min(t1.xmax(), t2.xmax);
567 return std::make_tuple(a, b);
570 for (
auto j = 0; j < static_cast<int>(newx.size() - 1); ++j) {
571 double xmin = newx[j],
xmax = newx[j + 1];
575 for (
auto i = 0UL; i < m_expansions.size(); ++i) {
580 auto [a, b] = interval_intersection(m_expansions[i], A{
xmin,
xmax});
582 const auto& e = m_expansions[i];
587 double ymin = e.eval(a), ymax = e.eval(b);
598 sort(yminoverall, ymaxoverall);
599 im.
ymin = yminoverall;
600 im.
ymax = ymaxoverall;
601 intervals.push_back(im);
612 : m_expansions(std::move(expansions)),
613 m_x_at_extrema(determine_extrema(m_expansions, thresh_imag)),
614 m_monotonic_intervals(build_monotonic_intervals(m_x_at_extrema)),
619 : m_expansions(other.m_expansions),
620 m_x_at_extrema(other.m_x_at_extrema),
621 m_monotonic_intervals(other.m_monotonic_intervals),
622 m_xmin(other.
xmin()),
623 m_xmax(other.
xmax()) {}
628 std::swap(m_expansions, other.m_expansions);
629 std::swap(m_x_at_extrema, other.m_x_at_extrema);
630 std::swap(m_monotonic_intervals, other.m_monotonic_intervals);
631 std::swap(m_xmin, other.m_xmin);
632 std::swap(m_xmax, other.m_xmax);
643 return m_x_at_extrema;
648 return m_monotonic_intervals;
664 return m_monotonic_intervals.size() == 1;
675 auto midpoint_Knuth = [](
int x,
int y) {
return (x & y) + ((x ^ y) >> 1); };
677 int iL = 0U, iR =
static_cast<int>(m_expansions.size()) - 1, iM;
678 while (iR - iL > 1) {
679 iM = midpoint_Knuth(iL, iR);
680 if (x >= m_expansions[iM].
xmin()) {
686 return (x < m_expansions[iL].
xmax()) ? iL : iR;
694 return m_expansions[
get_index(x)].eval(x);
698 template <
typename Container>
699 const auto eval_many(
const Container& x, Container& y)
const {
700 if (x.size() != y.size()) {
701 throw std::invalid_argument(
"x and y are not the same size");
703 for (
auto i = 0U; i < x.size(); ++i) {
709 template <
typename Container>
710 const auto eval_manyC(
const Container x[], Container y[], std::size_t N)
const {
711 for (
auto i = 0U; i < N; ++i) {
718 std::vector<IntervalMatch> matches;
719 for (
auto& interval : m_monotonic_intervals) {
720 if (y >= interval.ymin && y <= interval.ymax) {
721 matches.push_back(interval);
729 const auto get_x_for_y(
double y,
unsigned int bits, std::size_t max_iter,
double boundsftol)
const {
730 std::vector<std::pair<double, int>> solns;
731 for (
const auto& interval : m_monotonic_intervals) {
735 if (interval.contains_y(y)) {
737 for (
const auto& ei : interval.expansioninfo) {
740 if (ei.contains_y(y)) {
742 auto [xvalue, num_steps] = e.
solve_for_x_count(y, ei.xmin, ei.xmax, bits, max_iter, boundsftol);
743 solns.emplace_back(xvalue,
static_cast<int>(num_steps));
752 template <
typename YContainer,
typename CountContainer>
753 const auto count_x_for_y_many(
const YContainer& y,
unsigned int bits, std::size_t max_iter,
double boundsftol, CountContainer& x)
const {
754 if (x.size() != y.size()) {
755 throw std::invalid_argument(
"x and y are not the same size");
757 for (
auto i = 0U; i < x.size(); ++i) {
758 x(i) =
get_x_for_y(y(i), bits, max_iter, boundsftol).size();
763 template <
typename YContainer,
typename CountContainer>
764 const auto count_x_for_y_manyC(
const YContainer y[],
size_t N,
unsigned int bits, std::size_t max_iter,
double boundsftol,
765 CountContainer x[])
const {
766 for (
auto i = 0U; i < N; ++i) {
767 x[i] =
get_x_for_y(y[i], bits, max_iter, boundsftol).size();
772static_assert(std::is_copy_constructible_v<ChebyshevApproximation1D<std::vector<double>>>);
773static_assert(std::is_copy_assignable_v<ChebyshevApproximation1D<std::vector<double>>>);
790template <
typename ArrayType = Eigen::ArrayXd>
800 std::optional<ChebyshevApproximation1D<ArrayType>> m_hL,
810 double m_rhocrit_num;
818 auto loader(
const nlohmann::json& j,
const std::string& key) {
819 std::vector<ChebyshevExpansion<ArrayType>> buf;
828 for (
auto& block : j[key]) {
829 buf.emplace_back(block.at(
"xmin"), block.at(
"xmax"), block.at(
"coef"));
837 auto make_invlnp(Eigen::Index Ndegree) {
842 const double EPSILON = std::numeric_limits<double>::epsilon();
844 auto func = [&](
long i,
long Npts,
double lnp) ->
double {
848 if (solns.size() != 1) {
849 if ((i == 0 || i == Npts - 1) && (p > pmin * (1 - EPSILON * 1000) && p < pmin * (1 + EPSILON * 1000))) {
852 if ((i == 0 || i == Npts - 1) && (p > pmax * (1 - EPSILON * 1000) && p < pmax * (1 + EPSILON * 1000))) {
855 std::stringstream ss;
856 ss << std::setprecision(20) <<
"Must have one solution for T(p), got " << solns.size() <<
" for " << p <<
" Pa; limits are [" << pmin
857 << +
" Pa , " << pmax <<
" Pa]; i is " << i;
858 throw std::invalid_argument(ss.str());
860 auto [T, iters] = solns[0];
864 using CE_t = std::vector<ChebyshevExpansion<ArrayType>>;
865 return detail::dyadic_splitting<decltype(func), CE_t>(Ndegree, func, log(pmin), log(pmax), 3, 1e-12, 26);
874 : m_rhoL(std::move(loader(j,
"jexpansions_rhoL"))),
875 m_rhoV(std::move(loader(j,
"jexpansions_rhoV"))),
876 m_p(std::move(loader(j,
"jexpansions_p"))),
878 m_Tcrit_num(j.at(
"meta").at(
"Tcrittrue / K")),
879 m_rhocrit_num(j.at(
"meta").at(
"rhocrittrue / mol/m^3")),
880 m_pmin(m_p.eval(m_p.xmin())),
881 m_pmax(m_p.eval(m_p.xmax())) {};
894 auto get_or_throw = [&](
const auto& v) ->
const auto& {
898 throw std::invalid_argument(
"unable to get the variable " + std::string(1, k) +
", make sure it has been added to superancillary");
905 return (Q == 0) ? m_rhoL : m_rhoV;
907 return (Q == 0) ? get_or_throw(m_hL) : get_or_throw(m_hV);
909 return (Q == 0) ? get_or_throw(m_sL) : get_or_throw(m_sV);
911 return (Q == 0) ? get_or_throw(m_uL) : get_or_throw(m_uV);
913 throw std::invalid_argument(
"Bad key of '" + std::string(1, k) +
"'");
922 m_invlnp = make_invlnp(Ndegree);
944 return m_rhocrit_num;
952 void add_variable(
char var,
const std::function<
double(
double,
double)>& caller) {
953 Eigen::MatrixXd Lmat, Umat;
956 auto builder = [&](
char var,
auto& variantL,
auto& variantV) {
957 std::vector<ChebyshevExpansion<ArrayType>> newexpL, newexpV;
960 if (expsL.get_expansions().size() != expsV.get_expansions().size()) {
961 throw std::invalid_argument(
"L&V are not the same size");
963 for (
auto i = 0U; i < expsL.get_expansions().size(); ++i) {
964 const auto& expL = expsL.get_expansions()[i];
965 const auto& expV = expsV.get_expansions()[i];
966 const auto& T = expL.get_nodes_realworld();
968 Eigen::ArrayXd funcL = Umat * expL.coeff().matrix();
969 Eigen::ArrayXd funcV = Umat * expV.coeff().matrix();
972 for (
auto j = 0; j < funcL.size(); ++j) {
973 funcL(j) = caller(T(j), funcL(j));
974 funcV(j) = caller(T(j), funcV(j));
977 newexpL.emplace_back(expL.xmin(), expL.xmax(), (Lmat * funcL.matrix()).eval());
978 newexpV.emplace_back(expV.xmin(), expV.xmax(), (Lmat * funcV.matrix()).eval());
981 variantL.emplace(std::move(newexpL));
982 variantV.emplace(std::move(newexpV));
987 builder(var, m_hL, m_hV);
990 builder(var, m_sL, m_sV);
993 builder(var, m_uL, m_uV);
996 throw std::invalid_argument(
"nope");
1006 if (Q == 1 || Q == 0) {
1009 throw std::invalid_argument(
"invalid_value for Q");
1016 template <
typename Container>
1018 if (T.size() != y.size()) {
1019 throw std::invalid_argument(
"T and y are not the same size");
1022 for (
auto i = 0; i < T.size(); ++i) {
1023 y(i) = approx.eval(T(i));
1030 template <
typename Container>
1031 void eval_sat_manyC(
const Container T[], std::size_t N,
char k,
short Q, Container y[])
const {
1034 for (std::size_t i = 0; i < N; ++i) {
1035 y[i] = approx.eval(T[i]);
1048 auto solve_for_T(
double propval,
char k,
bool Q,
unsigned int bits = 64,
unsigned int max_iter = 100,
double boundsftol = 1e-13)
const {
1049 return get_approx1d(k, Q).get_x_for_y(propval, bits, max_iter, boundsftol);
1063 return (1 / propval - v_L) / (v_V - v_L);
1067 return (propval - L) / (V - L);
1093 double v = q * v_V + (1 - q) * v_L;
1098 return q * V + (1 - q) * L;
1103 template <
typename Container>
1104 void get_yval_many(
const Container& T,
char k,
const Container& q, Container& y)
const {
1105 if (T.size() != y.size() || T.size() != q.size()) {
1106 throw std::invalid_argument(
"T, q, and y are not all the same size");
1113 for (
auto i = 0; i < T.size(); ++i) {
1115 double v_L = 1.0 / L.eval(T(i));
1116 double v_V = 1.0 / V.eval(T(i));
1117 double v = q(i) * v_V + (1 - q(i)) * v_L;
1121 for (
auto i = 0; i < T.size(); ++i) {
1122 y(i) = q(i) * V.eval(T(i)) + (1 - q(i)) * L.eval(T(i));
1133 auto get_all_intersections(
const char k,
const double val,
unsigned int bits, std::size_t max_iter,
double boundsftol)
const {
1136 auto TsatL = L.get_x_for_y(val, bits, max_iter, boundsftol);
1137 const auto TsatV = V.get_x_for_y(val, bits, max_iter, boundsftol);
1138 for (
auto&& el : TsatV) {
1139 TsatL.push_back(el);
1161 std::optional<SuperAncillaryTwoPhaseSolution>
iterate_for_Tq_XY(
double Tmin,
double Tmax,
char ch1,
double val1,
char ch2,
double val2,
1162 unsigned int bits, std::size_t max_iter,
double boundsftol)
const {
1164 std::size_t counter = 0;
1165 auto f = [&](
double T_) {
1168 double resid =
get_yval(T_, q_fromv1, ch2) - val2;
1171 using namespace boost::math::tools;
1172 double fTmin = f(Tmin), fTmax = f(Tmax);
1179 auto returner = [&]() {
1187 if (std::abs(fTmin) < boundsftol) {
1191 if (std::abs(fTmax) < boundsftol) {
1195 if (fTmin * fTmax > 0) {
1198 return std::nullopt;
1202 boost::math::uintmax_t max_iter_ =
static_cast<boost::math::uintmax_t
>(max_iter);
1203 auto [l, r] = toms748_solve(f, Tmin, Tmax, fTmin, fTmax, eps_tolerance<double>(bits), max_iter_);
1207 std::cout <<
"fTmin,fTmax: " << fTmin <<
"," << fTmax << std::endl;
1221 std::optional<SuperAncillaryTwoPhaseSolution>
solve_for_Tq_DX(
const double rho,
const double propval,
const char k,
unsigned int bits,
1222 std::size_t max_iter,
double boundsftol)
const {
1227 std::size_t rhosat_soln_count = Tsat.size();
1229 std::tuple<double, double> Tsearchrange;
1230 if (rhosat_soln_count == 1) {
1233 Tsearchrange = std::make_tuple(Lrho.xmin * 0.999, std::get<0>(Tsat[0]));
1234 }
else if (rhosat_soln_count == 2) {
1235 double y1 = std::get<0>(Tsat[0]), y2 = std::get<0>(Tsat[1]);
1239 Tsearchrange = std::make_tuple(y1, y2);
1241 throw std::invalid_argument(
"cannot have number of solutions other than 1 or 2; got " +
std::to_string(rhosat_soln_count) +
" solutions");
1243 auto [a, b] = Tsearchrange;
1244 return iterate_for_Tq_XY(a, b,
'D', rho, k, propval, bits, max_iter, boundsftol);
1248 template <
typename Container>
1249 void solve_for_Tq_DX_many(
const Container& rho,
const Container& propval,
const char k,
unsigned int bits, std::size_t max_iter,
1250 double boundsftol, Container& T, Container& q, Container& count) {
1251 if (std::set<std::size_t>({rho.size(), propval.size(), T.size(), q.size(), count.size()}).size() != 1) {
1252 throw std::invalid_argument(
"rho, propval, T, q, count are not all the same size");
1254 for (
auto i = 0U; i < T.size(); ++i) {
1255 auto osoln =
solve_for_Tq_DX(rho(i), propval(i), k, bits, max_iter, boundsftol);
1257 const auto& soln = osoln.value();
1260 count(i) = soln.counter;
1455static_assert(std::is_copy_constructible_v<SuperAncillary<std::vector<double>>>);
1456static_assert(std::is_copy_assignable_v<SuperAncillary<std::vector<double>>>);