#include "nlsat/transcendental_eval.h"
#include <cmath>
#include <limits>
#include <algorithm>
namespace nlsat {
namespace transcendental_eval {
char const* op_name(op_kind op) {
switch (op) {
case op_kind::SIN: return "sin";
case op_kind::COS: return "cos";
case op_kind::TAN: return "tan";
case op_kind::ASIN: return "asin";
case op_kind::ACOS: return "acos";
case op_kind::ATAN: return "atan";
case op_kind::SINH: return "sinh";
case op_kind::COSH: return "cosh";
case op_kind::TANH: return "tanh";
case op_kind::ASINH: return "asinh";
case op_kind::ACOSH: return "acosh";
case op_kind::ATANH: return "atanh";
case op_kind::EXP: return "exp";
case op_kind::LOG: return "log";
}
return "?";
}
double eval(op_kind op, double x) {
switch (op) {
case op_kind::SIN: return std::sin(x);
case op_kind::COS: return std::cos(x);
case op_kind::TAN: return std::tan(x);
case op_kind::ASIN: return std::asin(x);
case op_kind::ACOS: return std::acos(x);
case op_kind::ATAN: return std::atan(x);
case op_kind::SINH: return std::sinh(x);
case op_kind::COSH: return std::cosh(x);
case op_kind::TANH: return std::tanh(x);
case op_kind::ASINH: return std::asinh(x);
case op_kind::ACOSH: return std::acosh(x); case op_kind::ATANH: return std::atanh(x); case op_kind::EXP: return std::exp(x);
case op_kind::LOG: return std::log(x); }
return std::numeric_limits<double>::quiet_NaN();
}
double error_bound(op_kind op, double x, double fx) {
double const eps = std::numeric_limits<double>::epsilon();
double const safety = 64.0; double const boundary_safety = 4096.0; switch (op) {
case op_kind::SIN:
case op_kind::COS:
case op_kind::ATAN:
case op_kind::TANH:
case op_kind::ASINH:
return safety * eps * std::max(1.0, std::fabs(x));
case op_kind::SINH:
case op_kind::COSH:
case op_kind::EXP:
return safety * eps * std::max(1.0, std::fabs(fx));
case op_kind::TAN:
case op_kind::ASIN:
case op_kind::ACOS:
case op_kind::ATANH:
case op_kind::ACOSH:
case op_kind::LOG: return boundary_safety * eps * std::max(1.0, std::max(std::fabs(x), std::fabs(fx)));
}
return 1e-6;
}
rational to_rational(double d) {
if (d == 0.0)
return rational(0);
int exp = 0;
double mantissa = std::frexp(d, &exp); int64_t num = static_cast<int64_t>(std::ldexp(mantissa, 53));
exp -= 53;
rational r(num);
if (exp >= 0)
r *= rational::power_of_two(static_cast<unsigned>(exp));
else
r /= rational::power_of_two(static_cast<unsigned>(-exp));
return r;
}
void interval_eval(op_kind op, double lo, double hi, double& lo_val, double& hi_val) {
double f_lo = eval(op, lo);
double f_hi = eval(op, hi);
double lo_v = std::min(f_lo, f_hi);
double hi_v = std::max(f_lo, f_hi);
double slack = std::max(error_bound(op, lo, f_lo), error_bound(op, hi, f_hi));
lo_val = lo_v - slack;
hi_val = hi_v + slack;
}
static bool contains_multiple_of(double lo, double hi, double phase, double period) {
if (!(lo <= hi))
return false;
double k = std::ceil((lo - phase) / period);
double candidate = phase + k * period;
return candidate <= hi;
}
bool wide_interval_eval(op_kind op, double lo, double hi, double& lo_val, double& hi_val) {
if (!(lo <= hi))
return false;
double f_lo = eval(op, lo);
double f_hi = eval(op, hi);
if (!std::isfinite(f_lo) || !std::isfinite(f_hi))
return false;
double mn = std::min(f_lo, f_hi);
double mx = std::max(f_lo, f_hi);
double const pi = 3.14159265358979323846;
double const two_pi = 2 * pi;
switch (op) {
case op_kind::SIN:
if (contains_multiple_of(lo, hi, pi / 2, two_pi)) mx = 1.0;
if (contains_multiple_of(lo, hi, -pi / 2, two_pi)) mn = -1.0;
break;
case op_kind::COS:
if (contains_multiple_of(lo, hi, 0.0, two_pi)) mx = 1.0;
if (contains_multiple_of(lo, hi, pi, two_pi)) mn = -1.0;
break;
case op_kind::TAN:
if (contains_multiple_of(std::nextafter(lo, hi), std::nextafter(hi, lo), pi / 2, pi))
return false;
break;
case op_kind::COSH:
if (lo <= 0.0 && 0.0 <= hi) mn = 1.0;
break;
default:
break; }
double slack = std::max(error_bound(op, lo, f_lo), error_bound(op, hi, f_hi));
lo_val = mn - slack;
hi_val = mx + slack;
return true;
}
bool exp_taylor_bracket_at(rational const& xr, rational& lo, rational& hi) {
constexpr unsigned k_terms = 40;
if (xr <= rational(0) && xr > rational(-static_cast<int>(k_terms))) {
rational ax = -xr;
unsigned n0 = 0;
while (rational(n0 + 1) < ax)
++n0;
rational term(1); rational sum(0);
for (unsigned k = 0; k < n0; ++k) {
sum += term;
term = term * xr / rational(k + 1);
}
lo = sum; hi = sum;
for (unsigned k = n0; k < k_terms; ++k) {
rational next = sum + term;
lo = std::min(sum, next);
hi = std::max(sum, next);
sum = next;
term = term * xr / rational(k + 1);
}
return true;
}
if (xr > rational(0) && xr < rational(k_terms + 1)) {
rational term(1); rational sum(0);
for (unsigned k = 0; k < k_terms; ++k) {
sum += term;
term = term * xr / rational(k + 1);
}
rational q = xr / rational(k_terms + 1);
lo = sum;
hi = sum + term / (rational(1) - q);
return true;
}
return false;
}
bool log_taylor_bracket_at(rational const& xr, rational& lo, rational& hi) {
if (xr < rational(1) || xr > rational(2))
return false;
rational u = xr - rational(1);
constexpr unsigned k_terms = 60;
rational term = u; rational sum(0);
lo = sum; hi = sum;
for (unsigned n = 1; n <= k_terms; ++n) {
rational next = (n % 2 == 1) ? sum + term : sum - term;
lo = std::min(sum, next);
hi = std::max(sum, next);
sum = next;
term = term * u * rational(n) / rational(n + 1);
}
return true;
}
bool atan_taylor_bracket_at(rational const& xr, rational& lo, rational& hi) {
if (xr < rational(-1) || xr > rational(1))
return false;
constexpr unsigned k_terms = 40;
rational term = xr;
rational xr2 = xr * xr;
rational sum(0);
lo = sum; hi = sum;
for (unsigned k = 0; k < k_terms; ++k) {
rational next = sum + term / rational(2 * k + 1);
lo = std::min(sum, next);
hi = std::max(sum, next);
sum = next;
term = -term * xr2;
}
return true;
}
static bool sin_cos_taylor_bracket_at(rational const& xr, unsigned base_power, rational& lo, rational& hi) {
constexpr unsigned k_terms = 60;
rational x2 = xr * xr;
unsigned n0 = 0;
while (n0 < k_terms) {
rational p(2 * n0 + base_power);
if (x2 <= (p + rational(1)) * (p + rational(2)))
break;
++n0;
}
if (n0 >= k_terms)
return false;
auto factorial = [](unsigned n) {
rational r(1);
for (unsigned k = 2; k <= n; ++k)
r *= rational(k);
return r;
};
rational term = (base_power == 0) ? rational(1) : xr;
term /= factorial(base_power);
rational sum(0);
for (unsigned k = 0; k < n0; ++k) {
sum += term;
unsigned power = 2 * k + base_power;
term = -term * x2 / ((rational(power) + rational(1)) * (rational(power) + rational(2)));
}
lo = sum; hi = sum;
for (unsigned k = n0; k < k_terms; ++k) {
rational next = sum + term;
lo = std::min(sum, next);
hi = std::max(sum, next);
sum = next;
unsigned power = 2 * k + base_power;
term = -term * x2 / ((rational(power) + rational(1)) * (rational(power) + rational(2)));
}
return true;
}
bool sin_taylor_bracket_at(rational const& xr, rational& lo, rational& hi) {
return sin_cos_taylor_bracket_at(xr, 1, lo, hi);
}
bool cos_taylor_bracket_at(rational const& xr, rational& lo, rational& hi) {
return sin_cos_taylor_bracket_at(xr, 0, lo, hi);
}
static bool interval_contains_periodic_point(rational const& xlo, rational const& xhi,
rational const& phase_lo, rational const& phase_hi,
rational const& period_lo, rational const& period_hi) {
rational k_lo = floor((xlo - phase_hi) / period_lo) - rational(1);
rational k_hi = ceil((xhi - phase_lo) / period_lo) + rational(1);
if (k_hi - k_lo > rational(1000))
return true; for (rational k = k_lo; k <= k_hi; k += rational(1)) {
rational a = phase_lo + k * period_lo, b = phase_hi + k * period_hi;
rational c = phase_lo + k * period_hi, d = phase_hi + k * period_lo;
rational pos_lo = std::min(std::min(a, b), std::min(c, d));
rational pos_hi = std::max(std::max(a, b), std::max(c, d));
if (pos_lo <= xhi && pos_hi >= xlo)
return true;
}
return false;
}
bool sin_cos_taylor_bracket(op_kind op, rational const& xlo, rational const& xhi, rational& lo, rational& hi) {
if (op != op_kind::SIN && op != op_kind::COS)
return false;
rational lo1, hi1, lo2, hi2;
bool ok = (op == op_kind::SIN)
? (sin_taylor_bracket_at(xlo, lo1, hi1) && sin_taylor_bracket_at(xhi, lo2, hi2))
: (cos_taylor_bracket_at(xlo, lo1, hi1) && cos_taylor_bracket_at(xhi, lo2, hi2));
if (!ok)
return false;
lo = std::min(lo1, lo2);
hi = std::max(hi1, hi2);
static const rational g_pi_lo("3.14159265358979");
static const rational g_pi_hi("3.14159265358980");
rational period_lo = rational(2) * g_pi_lo, period_hi = rational(2) * g_pi_hi;
rational max_phase_lo, max_phase_hi, min_phase_lo, min_phase_hi;
if (op == op_kind::SIN) {
max_phase_lo = g_pi_lo / 2; max_phase_hi = g_pi_hi / 2; min_phase_lo = -g_pi_hi / 2; min_phase_hi = -g_pi_lo / 2; }
else {
max_phase_lo = rational(0); max_phase_hi = rational(0); min_phase_lo = g_pi_lo; min_phase_hi = g_pi_hi; }
if (interval_contains_periodic_point(xlo, xhi, max_phase_lo, max_phase_hi, period_lo, period_hi))
hi = std::max(hi, rational(1));
if (interval_contains_periodic_point(xlo, xhi, min_phase_lo, min_phase_hi, period_lo, period_hi))
lo = std::min(lo, rational(-1));
return true;
}
}
}