Source code for qupulse.utils.numeric
from typing import Tuple, Type
from numbers import Rational
from math import gcd
from operator import le
from functools import partial
import sympy
try:
from math import lcm
except ImportError:
# python version < 3.9
def lcm(*integers: int):
"""Re-implementation of the least common multiple function that is in the standard library since python 3.9"""
result = 1
for value in integers:
result = result * value // gcd(value, result)
return result
[docs]def smallest_factor_ge(n: int, min_factor: int, brute_force: int = 5):
"""Find the smallest factor of n that is greater or equal min_factor
Args:
n: number to factorize
min_factor: factor must be larger this
brute_force: range(min_factor, min(min_factor + brute_force)) is probed by brute force
Returns:
Smallest factor of n that is greater or equal min_factor
"""
assert min_factor <= n
# this shortcut force shortcut costs 1us max
for factor in range(min_factor, min(min_factor + brute_force, n)):
if n % factor == 0:
return factor
else:
return min(filter(partial(le, min_factor),
sympy.ntheory.divisors(n, generator=True)))
def _approximate_int(alpha_num: int, d_num: int, den: int) -> Tuple[int, int]:
"""Find the best fraction approximation of alpha_num / den with an error smaller d_num / den. Best means the
fraction with the smallest denominator.
Algorithm from https://link.springer.com/content/pdf/10.1007%2F978-3-540-72914-3.pdf
Args:s
alpha_num: Numerator of number to approximate. 0 < alpha_num < den
d_num: Numerator of allowed absolute error.
den: Denominator of both numbers above.
Returns:
(numerator, denominator)
"""
assert 0 < alpha_num < den
lower_num = alpha_num - d_num
upper_num = alpha_num + d_num
p_a, q_a = 0, 1
p_b, q_b = 1, 1
p_full, q_full = p_b, q_b
to_left = True
while True:
# compute the number of steps to the left
x_num = den * p_b - alpha_num * q_b
x_den = -den * p_a + alpha_num * q_a
x = (x_num + x_den - 1) // x_den # ceiling division
p_full += x * p_a
q_full += x * q_a
p_prev = p_full - p_a
q_prev = q_full - q_a
# check whether we have a valid approximation
if (q_full * lower_num < p_full * den < q_full * upper_num or
q_prev * lower_num < p_prev * den < q_prev * upper_num):
bound_num = upper_num if to_left else lower_num
k_num = den * p_b - bound_num * q_b
k_den = bound_num * q_a - den * p_a
k = (k_num // k_den) + 1
return p_b + k * p_a, q_b + k * q_a
# update the interval
p_a = p_prev
q_a = q_prev
p_b = p_full
q_b = q_full
to_left = not to_left
[docs]def approximate_rational(x: Rational, abs_err: Rational, fraction_type: Type[Rational]) -> Rational:
"""Return the fraction with the smallest denominator in (x - abs_err, x + abs_err)"""
if abs_err <= 0:
raise ValueError('abs_err must be > 0')
xp, xq = x.numerator, x.denominator
if xq == 1:
return x
dp, dq = abs_err.numerator, abs_err.denominator
# separate integer part. alpha_num is guaranteed to be < xq
n, alpha_num = divmod(xp, xq)
# find common denominator of alpha_num / xq and dp / dq
den = lcm(xq, dq)
alpha_num = alpha_num * den // xq
d_num = dp * den // dq
if alpha_num < d_num:
p, q = 0, 1
else:
p, q = _approximate_int(alpha_num, d_num, den)
return fraction_type(p + n * q, q)
[docs]def approximate_double(x: float, abs_err: float, fraction_type: Type[Rational]) -> Rational:
"""Return the fraction with the smallest denominator in (x - abs_err, x + abs_err)."""
return approximate_rational(fraction_type(x), fraction_type(abs_err), fraction_type=fraction_type)