Program Listing for File rational.hpp

Return to documentation for file (SeQuant/core/rational.hpp)

#ifndef SEQUANT_CORE_RATIONAL_H
#define SEQUANT_CORE_RATIONAL_H

#include <SeQuant/core/hash.hpp>
#include <SeQuant/core/wstring.hpp>

#include <boost/multiprecision/cpp_int.hpp>
#include <boost/numeric/conversion/cast.hpp>

#include <memory>
#include <mutex>

namespace sequant {

using rational = boost::multiprecision::cpp_rational;

using ratio = rational;

using intmax_t = rational::value_type;

}  // namespace sequant

namespace boost {

inline auto hash_value(const sequant::rational& i) {
  auto val = sequant::hash::value(numerator(i));
  sequant::hash::combine(val, denominator(i));
  return val;
}

}  // namespace boost

namespace sequant {


template <typename T, typename = std::enable_if_t<std::is_floating_point_v<T>>>
inline rational to_rational(
    T t, T eps = std::sqrt(std::numeric_limits<T>::epsilon()),
    std::size_t max_niter = 1000) {
  if (std::isnan(t) || std::isinf(t)) {
    throw std::invalid_argument(
        "sequant::to_rational: cannot make a rational out of " +
        std::to_string(t));
  }
  // e.g.
  // https://gist.github.com/mikeando/7073d62385a34a61a6f7#file-main2-cpp-L42
  auto sbtree = [max_niter](T f, T tol) {
    using fraction = rational;
    using Int = rational::value_type;
    auto base = std::floor(f);
    auto base_Int = boost::numeric_cast<Int>(base);
    f -= base;
    if (f < tol) return fraction(base, 1);
    if (1 - tol < f) return fraction(base + 1, 1);

    fraction lower(0, 1);
    fraction upper(1, 1);

    std::size_t niter = 0;
    const auto f_plus_top_exact = fraction(f + tol);
    const auto f_minus_top_exact = fraction(f - tol);
    while (niter < max_niter) {
      fraction middle(numerator(lower) + numerator(upper),
                      denominator(lower) + denominator(upper));

      if (denominator(middle) * f_plus_top_exact < numerator(middle)) {
        upper = middle;
      } else if (numerator(middle) < denominator(middle) * f_minus_top_exact) {
        lower = middle;
      } else {
        return fraction(denominator(middle) * base_Int + numerator(middle),
                        denominator(middle));
      }
      ++niter;
    }
    throw std::invalid_argument(
        "sequant::rationalize: could not rationalize " + std::to_string(f) +
        " to eps=" + std::to_string(tol) + " in " + std::to_string(max_niter) +
        " iterations");  // unreachable
  };
  if (eps == 0)  // exact conversion ... rarely what's desired
    return rational(t);
  else
    return sbtree(t, eps);
}

template <typename T, typename = std::enable_if_t<std::is_integral_v<T>>>
rational to_rational(T t) {
  return rational{t};
}

template <typename T, typename = std::enable_if_t<std::is_floating_point_v<T> ||
                                                  std::is_integral_v<T>>>
inline ratio to_ratio(T t) {
  return to_rational(t);
}

inline std::string to_string(const rational& i) {
  return boost::lexical_cast<std::string>(i);
}

template <typename Backend>
inline std::string to_string(const boost::multiprecision::number<Backend>& i) {
  return boost::lexical_cast<std::string>(i);
}

inline std::wstring to_wstring(const rational& i) {
  return ::sequant::to_wstring(boost::lexical_cast<std::string>(i));
}

template <typename Backend>
inline std::wstring to_wstring(
    const boost::multiprecision::number<Backend>& i) {
  return ::sequant::to_wstring(boost::lexical_cast<std::string>(i));
}

template <typename Backend>
inline std::wstring to_latex(const boost::multiprecision::number<Backend>& t) {
  std::wstring result = L"{";
  using ::sequant::to_wstring;
  result += to_wstring(t) + L"}";
  return result;
}

inline std::wstring to_latex(const rational& t) {
  // n.b. skip enclosing braces to make Constant::to_latex to produce same
  // output as before std::wstring result = L"{";
  std::wstring result;
  if (denominator(t) == 1)
    result += to_latex(numerator(t));
  else {
    const auto num = numerator(t);
    // n.b. extra braces around \frac and use of to_wstring instead of to_latex
    // to avoid extra braces around args to \frac
    if (num > 0) {
      result += L"{\\frac{" + to_wstring(numerator(t)) + L"}{" +
                to_wstring(denominator(t)) + L"}}";
    } else if (num < 0) {
      result += L"{-\\frac{" + to_wstring(-num) + L"}{" +
                to_wstring(denominator(t)) + L"}}";
    } else
      result += L"0";
  }
  // n.b.
  // result += L"}";
  return result;
}

template <typename Backend>
inline std::wstring to_wolfram(
    const boost::multiprecision::number<Backend>& t) {
  return ::sequant::to_wstring(t);
}

inline std::wstring to_wolfram(const rational& t) {
  using ::sequant::to_wstring;
  if (denominator(t) == 1) {
    // n.b. use to_string to skip extra braces so that output agrees with code
    // that used scalars return to_wolfram(t.numerator());
    return to_wstring(numerator(t));
  } else
    return std::wstring(L"Rational[") + to_wolfram(numerator(t)) + L"," +
           to_wolfram(denominator(t)) + L"]";
}

}  // namespace sequant

#endif  // SEQUANT_CORE_RATIONAL_H