-
Notifications
You must be signed in to change notification settings - Fork 2
D(n,k) calculation
Throughout,
We know that if
The first part of this is controlled by the function recurse()
, the second is primarily implemented by walk_v()
.
By convention,
In the recursive allocation we distinguish between forced and unforced primes. Any prime that divides two or more of the values is forced, so that the powers of that prime are allocated simultaneously to each of the affected values; for unforced primes we know that no other value is divisible by it. For primes force_all
-f
option), meaning that we will try setting every power at every position; or we can force it only at the positions we have to, leaving it to be allocated as an unforced prime in the remaining positions.
- The choice here can significantly affect speed. Eg for
$D(12, 9), p=7$ we must force$p$ at$v_0, v_1, v_7, v_8$ , but can leave it unforced for$v_2,v_3, ... v_6$ . Forcing it in those positions means we will specifically try allocating, in turn,$7^2, 7^5, 7^{11}, 7^1, 7^3$ at each position; unforced, we would only allocate$7^2, 7^5, 7^{11}$ - the possibility of$7^1$ or$7^3$ would be found only by factorization inwalk_v()
. Whether forcing all positions is faster seems to depend both on how many positions are affected and on how constraining an effect the additional allocation has: if it would leave only a square or a prime unallocated, it is much more likely to speed things up.
All forced primes are allocated, in order, as the first steps of the recursion. When all forced primes have been allocated, recursion continues by calling best_v()
to ask which value the next allocation should be applied to, then looping over the possible powers at that value, and for each power looping over the possible primes.
If best_v()
finds no value to allocate to, we call walk_v()
and derecurse. Otherwise we calculate the maximum prime lim_p
as a proxy for the cost of recursing further, if walk_v()
and derecurse, else we allocate a prime and recurse further. (-g
option.)
For each allocation, we manage the recursion by tracking what we have allocated and where we have allocated it in levels[level]
. We also keep a running calculation of
We also track allocations to individual values in value[i]
; for each allocation we record
For each value qq[i]
), where
We also calculate the inverse
We also classify each need_square[]
), 2 (need_prime[]
) or anything else (need_other[]
).
If need_square[]
is empty, we then iterate over the possible values of ati
). For each value we first check the inverses, then check the primes, then check the others, bailing out at the first rejection. If all tests pass we have a candidate.
- Note that the
need_other[]
values are tested in parallel, performing the fastest tests on each value before proceeding to slower tests. In latest code, this is also done for theneed_prime[]
values.
If need_square[]
is not empty (see nqc
in the code), we use a different path: if there are multiple squares, we invoke code to solve the Pell equation involving the first two, and iterate over the results. If there is exactly one square at recurse()
.