def calculate_convergence(gcf: GeneralizedContinuedFraction,
                          reference,
                          plot=False,
                          title=''):
    """
    calculate convergence rate of General Continued Fraction (in reference to some constant x).
    the result is the average number of decimal digits, per term of the general continued fraction.
    :param plot: whether or not to plot the graph of the log10 difference between convergent and x.
    :param gcf: General Continued Fraction to calculate.
    :param title: (optional) title of graph.
    :param reference: x
    """
    q_ = [0, 1]
    p_ = [1, gcf.a_[0]]
    log_diff = [mpmath.log10(abs((dec(p_[1]) / dec(q_[1])) - reference))]
    length = min(200, len(gcf.b_))
    for i in range(1, length):
        q_.append(gcf.a_[i] * q_[i] + gcf.b_[i - 1] * q_[i - 1])
        p_.append(gcf.a_[i] * p_[i] + gcf.b_[i - 1] * p_[i - 1])
        if q_[i + 1] == 0:
            part_convergent = 0
        else:
            part_convergent = dec(p_[i + 1]) / dec(q_[i + 1])
        if not mpmath.isfinite(part_convergent):
            length = i
            break
        log_diff.append(mpmath.log10(abs(part_convergent - reference)))
    if plot:
        plt.plot(range(length), log_diff)
        plt.title(title)
        plt.show()
    log_slope = 2 * (log_diff[length - 1] - log_diff[length // 2]) / length
    return -log_slope
Ejemplo n.º 2
0
 def __init__(self, num_of_iterations=300, enum_only_exp_conv=False, avoid_int_roots=True, should_gen_contfrac=True,
              avoid_zero_b=True, threshold=None, prec=80, special_params=None):
     """
     init
     :param self: self
     :param num_of_iterations: if should_gen_contfrac=True, then how many iterations should be promoted.
     :param enum_only_exp_conv: skip contfracs that surely won't converge (at least) exponentially
     :param avoid_int_roots: avoid contfracs for which b (or any of its interlaces) have an integer root.
                             it will affect only b polynomial of degree<=3.
                             notice that if interlace is used, this may skip valid contfracs (the i root value may be
                             assigned to another interlace polynomial instead of the one with the root).
     :param should_gen_contfrac: generate ContFrac object and return (cont_frac, pas, pbs) instead of (pas, pbs)
     :param avoid_zero_b: raise exception ZeroB and cancel if finite contfrac (b_i=0 for some i) is achieved
     :param threshold: threshold for considering contfrac_res == target_val
                       if abs(contfrac_res-target_val) < threshold.
                       Defaults to 10^-4.
     :param prec: decimal precision to be used for calculations
     :param special_params: unused. Used differently by inheriting subclasses.
     :return: nothing
     """
     self.num_of_iterations = num_of_iterations
     self._enum_only_exp_conv = enum_only_exp_conv
     self._avoid_int_roots = avoid_int_roots
     self._should_gen_contfrac = should_gen_contfrac
     self._avoid_zero_b = avoid_zero_b
     self.good_params = []
     if threshold is None:
         self.threshold = dec(10)**dec(-4)
     else:
         self.threshold = threshold
     self._prec = prec
     set_precision(prec)
def test_ulcd_evaluator(u, l, c, d, target_constant):
    ulcd_evaluator = lhs_evaluators.ULCDEvaluator((u, l, c, d),
                                                  target_constant)
    expected_value = (u / target_constant + target_constant / l + c) / d
    assert abs(ulcd_evaluator.get_val() - expected_value) < dec('1E-38')
    ulcd_evaluator.add_int(3)
    expected_value += 3
    assert abs(ulcd_evaluator.get_val() - expected_value) < dec('1E-38')
    ulcd_evaluator.flip_sign()
    expected_value *= -1
    assert abs(ulcd_evaluator.get_val() - expected_value) < dec('1E-38')
Ejemplo n.º 4
0
 def finalize_iterations(self, params, promo_mat, n):
     """Finalize the iteration by computing contfrac_res = p/q.
     p, q are taken from promo_mat (see self.iteration_algorithm for more info).
     contfrac_res is saved to params (see self.iteration_algorithm for more info).
     n is unused (and is here only to keep method signature)."""
     p_vec, q_vec = promo_mat
     p = dec(p_vec[0])
     q = dec(q_vec[0])
     if not isnormal(q):
         contfrac_res = dec('NaN')
     else:
         contfrac_res = p / q
     params['contfrac_res'] = contfrac_res
     return (params, promo_mat)
Ejemplo n.º 5
0
 def __call__(self, x=None):
     """
     apply transformation on X.
     :param x (for our project it will always be 1):
     :return: result of mobius transformation
     """
     a, b, c, d = self.__values()
     if x is not None:
         numerator = dec(a * x + b)
         denominator = dec(c * x + d)
     else:
         numerator = dec(b)
         denominator = dec(d)
     return numerator / denominator
Ejemplo n.º 6
0
def find_transform(x, y, limit, threshold=1e-7):
    """
    find a integer solution to ax + b - cxy - dy = 0
    this will give us the mobius transform: T(x) = y
    :param x: numeric constant to check
    :param y: numeric manipulation of constant
    :param limit: range to look at
    :param threshold: optimal solution threshold.
    :return MobiusTransform in case of success or None.
    """
    x1 = x
    x2 = dec(1.0)
    x3 = -x * y
    x4 = -y
    solver = Solver('mobius', Solver.CBC_MIXED_INTEGER_PROGRAMMING)
    a = solver.IntVar(-limit, limit, 'a')
    b = solver.IntVar(-limit, limit, 'b')
    c = solver.IntVar(-limit, limit, 'c')
    d = solver.IntVar(-limit, limit, 'd')
    f = solver.NumVar(0, 1, 'f')
    solver.Add(f == (a * x1 + b * x2 + c * x3 + d * x4))
    solver.Add(a * x1 + b >=
               1)  # don't except trivial solutions and remove some redundancy
    solver.Minimize(f)
    status = solver.Solve()
    if status == Solver.OPTIMAL:
        if abs(solver.Objective().Value()) <= threshold:
            res_a, res_b, res_c, res_d = int(a.solution_value()), int(b.solution_value()),\
                                         int(c.solution_value()), int(d.solution_value())
            ret = MobiusTransform(
                np.array([[res_a, res_b], [res_c, res_d]], dtype=object))
            ret.normalize()
            return ret
    else:
        return None
def test_rationalfunc_evaluator(numerator_coeffs, denominator_coeffs,
                                target_constant):
    rationalfunc_evaluator = lhs_evaluators.RationalFuncEvaluator(
        (numerator_coeffs, denominator_coeffs, 0), target_constant)
    expected_value = MathOperations.subs_in_polynom(numerator_coeffs, target_constant) / \
                     MathOperations.subs_in_polynom(denominator_coeffs, target_constant)
    assert abs(rationalfunc_evaluator.get_val() -
               expected_value) < dec('1E-38')
    rationalfunc_evaluator.add_int(3)
    expected_value += 3
    assert abs(rationalfunc_evaluator.get_val() -
               expected_value) < dec('1E-38')
    rationalfunc_evaluator.flip_sign()
    expected_value *= -1
    assert abs(rationalfunc_evaluator.get_val() -
               expected_value) < dec('1E-38')
Ejemplo n.º 8
0
def eval_dec_contfrac_by_polys(a, b, calculation_depth):
    """a, b are given as polynomials with an option to interlaced polynomials. See documentation for ContFrac for more
    details.
    Calculates the continued fraction into 'calculation_depth' depth.
    Calculations are done with Decimal, which enables arbitrary precision. Please set the Decimal context precision as
    needed."""
    if isinstance(a[0], (int, dec)):
        a = [a]
    if isinstance(b[0], (int, dec)):
        b = [b]

    a = [[dec(i) for i in p] for p in a]
    b = [[dec(i) for i in p] for p in b]
    return eval_contfrac([
        MathOperations.subs_in_polynom(a[i % len(a)], i)
        for i in range(calculation_depth)
    ], [
        MathOperations.subs_in_polynom(b[i % len(b)], i)
        for i in range(1, calculation_depth)
    ])
Ejemplo n.º 9
0
 def evaluate_contfrac(ab, accuracy, convergence_info):
     cont_frac = cont_fracs.ContFrac(a_coeffs=ab[0], b_coeffs=ab[1])
     if convergence_info:
         cont_frac.set_approach_type_and_params(convergence_info)
     # cont_frac.reinitialize(a_coeffs=ab[0], b_coeffs=ab[1])
     # This is a horrible hack, slowing down EVERYTHING. As a first step, this should be replaced by a small
     # correction to the convergence parameters. Maybe taking the upper/lower bound of confidence from the
     # fitting results.
     # TODO: fix accoridng to the above comment.
     cont_frac.gen_iterations(num_of_iterations,
                              dec('1E-%d' % (accuracy + 100)))
     return cont_frac.get_result()
    def _calc_val(self):
        """Calculates the value of the LHS based on the LHS parameters (numerator, denominator, target constant and
        added int)."""
        self.numerator = MathOperations.subs_in_polynom(
            self.numerator_p, self.target_constant)
        self.denominator = MathOperations.subs_in_polynom(
            self.denominator_p, self.target_constant)

        # if not self.denominator.is_normal() or not self.numerator.is_normal():
        if not isnormal(self.denominator) or not isnormal(self.numerator):
            self.val = dec('nan')
        else:
            self.val = self.numerator / self.denominator + self.added_int
Ejemplo n.º 11
0
    def iteration_algorithm(self, params, state_mat, i, print_calc):
        """Generates the next iteration, using the "matrix product" of params and promo_matrix of:
        | P_{i+1} Q_{i+1} |   | a_i b_i |   | P_i     Q_i     |   | P_0    Q_0    |   | a_0 1 |
        | P_i     Q_i     | = | 1   0   | X | P_{i-1} Q_{i-1} | , | P_{-1} Q_{-1} | = | 1   0 |
        contfrac_i = P_i/Q_i

        params - {'a_coeffs': ..., 'b_coeffs': ..., 'contfrac_res': ...}
        state_mat - [[p_i, p_{i-1}], [q_i, q_{i-1}]], is actually the current state matrix. Name is only for signature
                    compatibility.
        i - iteration index, used to generate the [a_i b_i; 1 0] matrix.
        print_calc - whether to print the calculation process. For debugging."""
        i += 1
        a_coeffs = params['a_coeffs']
        b_coeffs = params['b_coeffs']
        # state_mat - the [a_i b_i; 1 0] matrix. Actually, it's just a [a_i, b_i] list
        ab_mat = self._iter_promo_matrix_generator(i, a_coeffs, b_coeffs,
                                                   print_calc)
        p_vec, q_vec = state_mat

        # TODO: if calculations are slow, once in a while (i % 100 == 0?) check if gcd(p_i,p_{i-1}) != 0
        # TODO: and take it out as a factor (add p_gcd, q_gcd params)
        new_p_vec = (sum([l * m for l, m in zip(p_vec, ab_mat)]), p_vec[0])
        new_q_vec = (sum([l * m for l, m in zip(q_vec, ab_mat)]), q_vec[0])
        if dec('inf') in new_p_vec:
            raise ValueError('infinity p')
        if dec('inf') in new_q_vec:
            raise ValueError('infinity q')

        if self._logging:
            p_i = dec(new_p_vec[0])
            q_i = dec(new_q_vec[0])
            if not isnormal(q_i):
                contfrac_res_i = dec('NaN')
            else:
                contfrac_res_i = p_i / q_i
            params['contfrac_res'] = contfrac_res_i
        new_promo_mat = (new_p_vec, new_q_vec)

        return (params, new_promo_mat)
Ejemplo n.º 12
0
def check_and_modify_precision(const, transform, const_gen, offset):
    """
    tried to use this to calculate and enlarge precision along the way.. but it only made it slower.
    right now this is unused.
    :param const:
    :param transform:
    :param const_gen:
    :param offset:
    :return:
    """
    const_work = const
    while True:
        epsilon = dec(2)**(-mpmath.mp.prec + 5)
        const_d = const_work - epsilon
        const_u = const_work + epsilon
        next_d = floor(transform(const_d))
        next_u = floor(transform(const_u))
        if next_d == next_u:
            return next_d, const_work
        mpmath.mp.prec += 100
        const_work = const_gen() + offset
                              (1, 1, 0, -1),
                              (1, 1, 0, 1),
                              (1, 1, 1, -2),
                          })])
def test_ulcd_enumerator(u_range, l_range, c_range, d_range, results):
    ulcd_enum = lhs_evaluators.ULCDEnumerator(
        [u_range, l_range, c_range, d_range], target_constant=1)
    ulcds_generator = ulcd_enum.generator()
    assert {ulcd_eval.get_params()
            for ulcd_eval in ulcds_generator
            }.symmetric_difference(results) == set()


@pytest.mark.parametrize(
    'numerator_coeffs, denominator_coeffs, force_numerator_greater, target_constant, results',
    [([[-1, 2], [-1, 2]], [[-1, 2], [-1, 2]], False, dec(5), {
        ((-1, -1), (-1, -1)),
        ((-1, -1), (-1, 0)),
        ((-1, -1), (-1, 1)),
        ((-1, -1), (0, -1)),
        ((-1, -1), (0, 1)),
        ((-1, -1), (1, -1)),
        ((-1, -1), (1, 0)),
        ((-1, -1), (1, 1)),
        ((-1, 0), (-1, -1)),
        ((-1, 0), (-1, 1)),
        ((-1, 0), (0, -1)),
        ((-1, 0), (0, 1)),
        ((-1, 0), (1, -1)),
        ((-1, 0), (1, 1)),
        ((-1, 1), (-1, -1)),
Ejemplo n.º 14
0
 def evaluate(self):
     if self.A == 0:
         return dec(0)
     return dec(self.B) / dec(self.A)
Ejemplo n.º 15
0
    def _estimate_approach_type_and_params_inner_alg(
            self,
            find_poly_parameter=False,
            iters=5000,
            initial_cutoff=1500,
            iters_step=500,
            exponential_threshold=1.1):
        """Returns 'exp', 'super_exp', 'poly2sympoly', 'undefined', 'fast' and 'mixed', as a tuple of (string,num):
        (approach_type, approach_parameter) or ('poly2sympoly', (approach_parameter, R**2))."""
        # This is an old function. It's still in use, but it's yucky and I don't feel like going through it and
        # document it. Sorry :(
        # Hopefully it will be replaced with some theoretical insights.
        # HOWEVER, IT IS GENERAL AND MAY BE USEFUL FOR NEW, FUTURE REPRESENTATION METHODS.
        # See the "convergence_analysis.pdf" file for further details about theory behind this function.
        # It evaluates and categorizes by computing linear fitting to the error/iterations plot. Regular or logarithmic.

        effective_zero = dec(10)**-mp.dps

        if iters_step < 6:
            ValueError('iters_step should be at least 4')

        approach_type = None
        approach_parameter = 0

        delta_pair = []
        delta_odd = []
        self.gen_iterations(initial_cutoff)
        res_0 = self.get_result()
        self.add_iterations(1)
        res_1 = self.get_result()
        self.add_iterations(1)
        res_2 = self.get_result()
        self.add_iterations(1)
        res_3 = self.get_result()
        self.add_iterations(1)
        res_4 = self.get_result()
        self.add_iterations(1)
        res_5 = self.get_result()
        delta_pair.append((initial_cutoff, abs(res_2 - res_0)))
        delta_pair.append((initial_cutoff + 2, abs(res_4 - res_2)))
        delta_odd.append((initial_cutoff + 1, abs(res_3 - res_1)))
        delta_odd.append((initial_cutoff + 3, abs(res_5 - res_3)))

        for i in range(initial_cutoff + iters_step, iters + 1, iters_step):
            # -3 for the iterations of res_1, res_2, res_3 that were already executed
            self.add_iterations(iters_step - 5)
            res_0 = self.get_result()
            self.add_iterations(1)
            res_1 = self.get_result()
            self.add_iterations(1)
            res_2 = self.get_result()
            self.add_iterations(1)
            res_3 = self.get_result()
            self.add_iterations(1)
            res_4 = self.get_result()
            self.add_iterations(1)
            res_5 = self.get_result()
            delta_pair.append((i, abs(res_2 - res_0)))
            delta_pair.append((i + 2, abs(res_4 - res_2)))
            delta_odd.append((i + 1, abs(res_3 - res_1)))
            delta_odd.append((i + 3, abs(res_5 - res_3)))

        pair_diminish = False
        odd_diminish = False
        if len(delta_pair) > 3 and all(
            [abs(p[1]) < effective_zero for p in delta_pair[-3:]]):
            pair_diminish = True
        if len(delta_odd) > 3 and all(
            [abs(p[1]) < effective_zero for p in delta_odd[-3:]]):
            odd_diminish = True
        # if one diminishes and the other isn't, return 'undefined'
        if pair_diminish ^ odd_diminish:
            approach_type = 'undefined'
        elif pair_diminish and odd_diminish:
            approach_type = 'fast'

        # if approach_type:
        #     return (approach_type, approach_parameter)

        pair_ratio = [
            (delta_pair[i][0], delta_pair[i][1] / delta_pair[i + 1][1])
            for i in range(0, len(delta_pair), 2)
            if delta_pair[i][1] != 0 and delta_pair[i + 1][1] != 0
            and not isnan(delta_pair[i][1]) and not isnan(delta_pair[i + 1][1])
        ]
        odd_ratio = [
            (delta_odd[i][0], delta_odd[i][1] / delta_odd[i + 1][1])
            for i in range(0, len(delta_odd), 2)
            if delta_odd[i][1] != 0 and delta_odd[i + 1][1] != 0
            and not isnan(delta_odd[i][1]) and not isnan(delta_odd[i + 1][1])
        ]

        if len(pair_ratio) < 6:
            return (approach_type, approach_parameter)

        mean_pair_ratio = sum([p for i, p in pair_ratio]) / len(pair_ratio)
        mean_pair_ratio_avg_square_error = sum([
            (r - mean_pair_ratio)**2 for i, r in pair_ratio
        ]).sqrt() / len(pair_ratio)
        mean_odd_ratio = sum([p for i, p in odd_ratio]) / len(odd_ratio)
        mean_odd_ratio_avg_square_error = sum([
            (r - mean_odd_ratio)**2 for i, r in odd_ratio
        ]).sqrt() / len(odd_ratio)
        relative_pair_sq_err = mean_pair_ratio_avg_square_error / mean_pair_ratio
        relative_odd_sq_err = mean_odd_ratio_avg_square_error / mean_odd_ratio
        if relative_pair_sq_err > 0.5 or relative_odd_sq_err > 0.5:
            if all([
                    i[1] > 2
                    for i in (pair_ratio[3 * int(len(pair_ratio) / 4):] +
                              odd_ratio[3 * int(len(odd_ratio) / 4):])
            ]):

                approach_type = 'super_exp'
            else:
                approach_type = 'undefined'
        if (relative_odd_sq_err <= 0.5 and
                relative_pair_sq_err <= 0.5) or approach_type == 'super_exp':
            is_pair_exp = mean_pair_ratio > exponential_threshold
            is_odd_exp = mean_odd_ratio > exponential_threshold
            # in case one is exponential and the other isn't return 'mixed'
            if is_pair_exp ^ is_odd_exp:
                approach_type = 'mixed'
            elif is_pair_exp and is_odd_exp:
                if approach_type != 'super_exp':
                    approach_type = 'exp'
                approach_parameter_pair = mean_pair_ratio**type(
                    mean_pair_ratio)(0.5)
                approach_parameter_odd = mean_odd_ratio**type(mean_odd_ratio)(
                    0.5)
                approach_parameter = min(approach_parameter_pair,
                                         approach_parameter_odd)
                approach_coeff_pair_list = [
                    abs(delta_pair[i][1] *
                        approach_parameter**(delta_pair[i][0]) /
                        (1 - approach_parameter**(-2)))
                    for i in range(0, len(delta_pair))
                    if delta_pair[i][1] != 0 and not isnan(delta_pair[i][1])
                ]
                approach_coeff_pair = sum(approach_coeff_pair_list) / len(
                    approach_coeff_pair_list)
                approach_coeff_odd_list = [
                    abs(delta_odd[i][1] *
                        approach_parameter**(delta_odd[i][0]) /
                        (1 - approach_parameter**(-2)))
                    for i in range(0, len(delta_odd))
                    if delta_odd[i][1] != 0 and not isnan(delta_odd[i][1])
                ]
                approach_coeff_odd = sum(approach_coeff_odd_list) / len(
                    approach_coeff_odd_list)
                approach_coeff = min(approach_coeff_pair, approach_coeff_odd)
                approach_parameter = (approach_parameter, approach_coeff)
            else:
                approach_type = 'poly2sympoly'

        if approach_type != 'poly2sympoly' or not find_poly_parameter:
            return (approach_type, approach_parameter)

    #     We're requested to find the poly2sympoly parameter
        log_x_pair = [math.log(i) for i, d in delta_pair]
        log_y_pair = [math.log(d) for i, d in delta_pair]
        slope_pair, intercept_pair, r_value_pair, p_value_pair, std_err_pair = scipy.stats.linregress(
            log_x_pair, log_y_pair)
        log_x_odd = [math.log(i) for i, d in delta_odd]
        log_y_odd = [math.log(d) for i, d in delta_odd]
        slope_odd, intercept_odd, r_value_odd, p_value_odd, std_err_odd = scipy.stats.linregress(
            log_x_odd, log_y_odd)

        # TODO: replace the -1 by *0.95? need to make sure first what is the slop (is it negative?)
        approach_parameter = (min(abs(slope_pair), abs(slope_odd)) - 1,
                              min(intercept_pair, intercept_odd),
                              min(r_value_pair**2, r_value_odd**2))
        return (approach_type, approach_parameter)
def test_rationalfunc_canonalize():
    rationalfunc_eval = lhs_evaluators.RationalFuncEvaluator(
        [[0, 3 * 1, 3 * 2, 1], [0, 1, 2], 0], dec(5))
    rationalfunc_eval.canonalize_params()
    rationalfunc_eval.get_params() == ([0, 0, 1], [1, 2], 3)
Ejemplo n.º 17
0
import cont_fracs
import pytest
from mpmath import mpf as dec
import gen_consts


@pytest.fixture()
def cf():
    return cont_fracs.ContFrac([[1]], [[1]])


@pytest.mark.parametrize('a,b,num_of_iters,target_val,threshold', [
    ([[2]], [[1, -4, 4]], 550, 4/gen_consts.gen_pi_const()+1, dec('8E-4')),
    ([[6]], [[1, -4, 4]], 500, gen_consts.gen_pi_const()+3, dec('1E-8')),
    ([[1, 2]], [[0, 0, 1]], 200, 4/gen_consts.gen_pi_const(), dec('1E-15')),
    ([[2, 1]], [[0, -1]], 400, gen_consts.gen_e_const()/(gen_consts.gen_e_const()-1), dec('1E-15')),
    ([[1]], [[1]], 400, gen_consts.gen_phi_const(), dec('1E-15')),
    ([[2], [0, 12]], [[-1, 6], [-5, 6]], 400, dec('2')**(1/dec('12'))+1, dec('1E-15')),
])
def test_contfrac(cf, a, b, num_of_iters, target_val, threshold):
    cf.reinitialize(a, b)
    cf.gen_iterations(num_of_iters)
    assert cf.compare_result(target_val) < threshold