def mat_to_float(m, rnum): nrow = len(m) values = [r(rnum, i) for i in range(rnum)] return sum([ sum([m[i][j] * values[j] for j in range(rnum)]) * r(nrow, i) for i in range(nrow) ])
def expr_to_float(expr): if type(expr) == int: return float(expr) if type(expr) == tuple: return expr_to_float(expr[0]) / expr_to_float(expr[1]) if is_sum(expr): return sum([expr_to_float(part) for part in expr[1:]]) if is_radical(expr): degree = expr[1] which = expr[2] radicand = expr[3] mult = expr[4] # make sure rounding errors don't affect the choice of nth root radicand_float = expr_to_float(radicand) if radicand_float.real < 0 and float_equal(radicand_float, radicand_float.real): radicand_float = radicand_float.real return mult * r(degree, which) * radicand_float**(1 / degree)
def roots_to_radicals(rnum): if rnum in r_x: return r_x[rnum] # prime factors of n - 1 # to match the expressions I found in 2013, factors are placed in increasing order except for one 2 at the end. # the last factor needs to be 2 for the real and imaginary parts of the final answer to be separable # other than that, any permutation of factors leads to an equally valid, distinct expression facts = factorize(rnum // 2) + [2] # multiplicative cycle of some primitive root, mod n power_cycle = make_power_cycle(rnum) # n_terms: number of a-sums on each iteration (see below) n_terms = 1 # all_a: list of all lists of a-sums (see below for what an a-sum is) from each loop iteration # all_a_x: expressions for all a-sums all_a = [] all_a_x = [] for i, c_size in enumerate(facts): # calculates: # - p_1 sums of (n - 1)/p_1 roots # - p_1*p_2 sums of (n - 1)/(p_1*p_2) roots # - p_1*p_2*p_3 sums of (n - 1)/(p_1*p_2*p_3) roots # etc, where a, b, c... are prime factors of n - 1 # let p_k = c_size be the prime factor of n - 1 considered on the kth iteration n_terms *= c_size # a: list of sums of (n - 1)/(p_1*...*p_k) roots (hereafter called a-sums) stored as lists # these sums can be grouped into (n - 1)/(p_1*...*p_{k - 1}) sets of p_k # a_x: expressions for a-sums in a # s: for each set of p_k a-sums a_0 to a_{p_k - 1}, a list of p_k sums (stored as lists or matrices where appropriate), where the i-th sum s_i is equal to a_j*r(p_k, i*j), for j from 0 to p_k - 1 # s is simply a list of all p_k sums for all (n - 1)/(p_1*...*p_{k - 1}) sets of a-sums # s_x: expressions for s-sums in s # ss: contains each s-sum, except for the first value in a p_k-size set, raised to the power p_k, expressed as lists # ss_coeff: coefficients in expression, in terms of 1 and a-sum values from p_{k - 1} level, for each value in ss # ss_x: expressions for values in ss a = [] a_x = [] s = [] ss = [] ss_coeff = [] s_x = [] ss_x = [] a_values = partition(rnum, power_cycle, n_terms) cyc = make_cycle(c_size) # iterate over each set of p_k terms for h in multi_range(tuple(facts[:i])): # construct a-sums part_ind = 0 mult = 1 for j in range(i): part_ind += mult * h[j] mult *= facts[j] for j in range(c_size): a.append(a_values[j * (n_terms // c_size) + part_ind]) # construct s-sums and ss values offset = 0 mult = c_size for j in range(i - 1, -1, -1): offset += mult * h[j] mult *= facts[j] s.append(sumlist(a[offset:offset + c_size], rnum)) ss.append(None) for j in range(1, c_size): if c_size == 2: s.append( plus(a[offset], itimes(-1, a[offset + 1], rnum), rnum)) ss.append(power(s[offset + 1], c_size, rnum)) else: s.append( msumlist([ xtimes(cyc[j * k % c_size], a[offset + k], rnum) for k in range(c_size) ], rnum)) ss.append(mpower(s[offset + j], c_size, rnum)) # construct terms with which to describe each ss expression as well as the 0th s-sum in each set. # these terms are 1 and the a-sums from the previous iteration, whose radical expressions are already found if i > 0: terms = [1] + all_a[i - 1] else: terms = [1] # build s0_coeff and ss_coeff s0_coeff = deconvert(s[offset], terms, rnum) ss_coeff.append(None) if c_size == 2: ss_coeff.append(deconvert(ss[offset + 1], terms, rnum)) else: for j in range(1, c_size): ss_coeff.append(mdeconvert(ss[offset + j], terms, rnum)) # retrieve the radical expression for each term used to construct s0_coeff and ss_coeff if i > 0: terms_x = [1] + all_a_x[i - 1] else: terms_x = [1] # build s0_x and ss_x # s0_x is the 0th s_x expression in each set but not the 0th member of s_x because s_x concatenates expression lists from every set on the p_k level s0_x = coeff_to_expr(s0_coeff, terms_x) ss_x.append(None) if c_size == 2: ss_x.append(coeff_to_expr(ss_coeff[offset + 1], terms_x)) else: for j in range(1, c_size): ss_x.append( mcoeff_to_expr(ss_coeff[offset + j], terms_x, roots_to_radicals(c_size))) s_x.append(s0_x) # build the other s_x expressions (the p_k-th roots of the ss_x expressions) for j in range(1, c_size): si_xs = [['r', c_size, k, ss_x[offset + j], 1] for k in range(c_size)] set = False for k in range(c_size): if c_size == 2: s_j_float = to_float(s[offset + j], rnum) else: s_j_float = mat_to_float(s[offset + j], rnum) if float_equal(s_j_float, expr_to_float(si_xs[k])): s_x.append(si_xs[k]) set = True break if not set: raise RuntimeError("Unable to resolve expression") # build the a_x expressions by adding the s_x expressions together and multiplying by the appropriate p_k-th roots of unity for j in range(c_size): a_x.append( etimes(esumlist(s_x[offset:offset + c_size]), (1, c_size))) for k in range(1, c_size): s_x[offset + k] = rtimes(-k, s_x[offset + k]) all_a.append(a) all_a_x.append(a_x) # on last iteration, a-sums are single roots of unity. bit_reverse() reorders these roots # x: list of expressions for all n-th roots of unity in order x = [1] + [None] * (rnum - 1) for i in range(len(a_x)): xind = power_cycle[bit_reverse(i, facts)] x[xind] = a_x[i] assert float_equal(expr_to_float(a_x[i]), r(rnum, xind)) r_x[rnum] = x return x
def to_float(l, rnum): values = [r(rnum, i) for i in range(rnum)] return sum([l[i] * values[i] for i in range(rnum)])