Project is now named Celeste
This commit is contained in:
parent
87917f9526
commit
269092fb53
45 changed files with 1507 additions and 12 deletions
0
celeste/math/__init__.py
Normal file
0
celeste/math/__init__.py
Normal file
124
celeste/math/crt.py
Normal file
124
celeste/math/crt.py
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
def crt(eqs: list[tuple[int, int]]) -> tuple[int, int]:
|
||||
'''
|
||||
Solves simultaneous linear diophantine equations
|
||||
using the Chinese-Remainder Theorem.
|
||||
Example:
|
||||
>>> crt([2, 3), (3, 5), (2, 7)])
|
||||
(23, 105)
|
||||
# aka x is congruent to 23 modulo 105
|
||||
'''
|
||||
# calculate the quotient and remainder form of the unknown
|
||||
q = 1
|
||||
r = 0
|
||||
# store remainders and moduli for each linear equation
|
||||
R = [eq[0] for eq in eqs]
|
||||
M = [eq[1] for eq in eqs]
|
||||
N = len(eqs)
|
||||
for i in range(N):
|
||||
(R_i, M_i) = (R[i], M[i])
|
||||
# adjust quotient and remainder
|
||||
r += R_i * q
|
||||
q *= M_i
|
||||
# apply current eq to future eqs
|
||||
for j in range(i+1, N):
|
||||
R[j] -= R_i
|
||||
R[j] *= pow(M_i, -1, M[j])
|
||||
R[j] %= M[j]
|
||||
return r # NOTE: forall integers k: qk+r is also a solution
|
||||
|
||||
|
||||
from math import isqrt, prod
|
||||
|
||||
def bsgs(g: int, h: int, n: int) -> int:
|
||||
'''
|
||||
The Baby-Step Giant-Step algorithm computes the
|
||||
discrete logarithm (or order) of an element in a
|
||||
finite abelian group.
|
||||
|
||||
Specifically, for a generator g of a finite abelian
|
||||
group G order n, and an element h in G, the BSGS
|
||||
algorithm returns the integer x such that g^x = h.
|
||||
For example, if G = Z_n the cyclic group order n then:
|
||||
bsgs solves g^x = h (mod n) for x.
|
||||
'''
|
||||
# ensure g and h are reduced modulo n
|
||||
g %= n
|
||||
h %= n
|
||||
# ignore trivial case
|
||||
# NOTE: for the Pohlig-Hellman algorithm to work properly
|
||||
# NOTE: BSGS must return 1 NOT 0 for bsgs(1, 1, n)
|
||||
if g == h: return 1
|
||||
m = isqrt(n) + 1 # ceil(sqrt(n))
|
||||
# store g^j values in a hash table
|
||||
H = {j: pow(g, j, n) for j in range(m)}
|
||||
I = pow(g, -m, n)
|
||||
y = h
|
||||
for i in range(m):
|
||||
for j in range(m):
|
||||
if H[j] == y:
|
||||
return i*m + j
|
||||
y = (y * I) % n
|
||||
return None # No Solutions
|
||||
|
||||
def factors_prod(pf: list[tuple[int, int]]) -> int:
|
||||
return prod(p**e for (p, e) in pf)
|
||||
|
||||
def sph(g: int, h: int, pf: list[tuple[int, int]]) -> int:
|
||||
'''
|
||||
The Silver-Pohlig-Hellman algorithm for computing
|
||||
discrete logarithms in finite abelian groups whose
|
||||
order is a smooth integer.
|
||||
|
||||
NOTE: this works in the special case,
|
||||
NOTE: but I can't get the general case to work :(
|
||||
'''
|
||||
# ignore the trivial case
|
||||
if g == h:
|
||||
return 1
|
||||
R = len(pf) # number of prime factors
|
||||
N = factors_prod(pf) # pf is the prime factorisation of N
|
||||
print('N', N)
|
||||
# Special Case: groups of prime-power order:
|
||||
if R == 1:
|
||||
(p, e) = pf[0]
|
||||
x = 0
|
||||
y = pow(g, p**(e-1), N)
|
||||
for k in range(e):
|
||||
# temporary variables for defining h_k
|
||||
w = pow(g, -x, N)
|
||||
# NOTE: by construction the order of h_k must divide p
|
||||
h_k = pow(w*h, p**(e-1-k), N)
|
||||
# apply BSGS to find d such that y^d = h_k
|
||||
d = bsgs(y, h_k, N)
|
||||
if d is None:
|
||||
return None # No Solutions
|
||||
x = x + (p**k)*d
|
||||
return x
|
||||
|
||||
# General Case:
|
||||
eqs = []
|
||||
for i in range(R):
|
||||
(p_i, e_i) = pf[i]
|
||||
# phi = (p_i - 1)*(p_i**(e_i - 1)) # Euler totient
|
||||
# pe = p_i**e_i
|
||||
# P = (N//(p_i**e_i)) % phi # reduce mod phi by Euler's theorem
|
||||
g_i = pow(g, N//(p_i**e_i), N)
|
||||
h_i = pow(h, N//(p_i**e_i), N)
|
||||
print("g and h", g_i, h_i)
|
||||
print("p^e", p_i, e_i)
|
||||
x_i = sph(g_i, h_i, [(p_i,e_i)])
|
||||
print("x_i", x_i)
|
||||
if x_i is None:
|
||||
return None # No Solutions
|
||||
eqs.append((x_i, p_i**e_i))
|
||||
print()
|
||||
# ignore the quotient, solve CRT for 0 <= x <= n-1
|
||||
x = crt(eqs) # NOTE: forall integers k: Nk+x is also a solution
|
||||
print('solution:', x)
|
||||
print(eqs)
|
||||
return x
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
result = sph(3, 11, [(2, 1),(7,1)])
|
||||
|
||||
2
celeste/math/factor/__init__.py
Normal file
2
celeste/math/factor/__init__.py
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
from .pftrialdivision import trial_division
|
||||
from .factordb import DBResult, FType, FCertainty
|
||||
161
celeste/math/factor/factordb.py
Normal file
161
celeste/math/factor/factordb.py
Normal file
|
|
@ -0,0 +1,161 @@
|
|||
'''
|
||||
Simple interface for https://factordb.com inspired by
|
||||
https://github.com/ihebski/factordb
|
||||
|
||||
TODO:
|
||||
1. Implement primality certificate generation, read this:
|
||||
https://reference.wolfram.com/language/PrimalityProving/ref/ProvablePrimeQ.html
|
||||
'''
|
||||
|
||||
import requests
|
||||
from enum import Enum, StrEnum
|
||||
|
||||
_FDB_URI = 'https://factordb.com'
|
||||
# Generated by https://www.asciiart.eu/text-to-ascii-art
|
||||
# using "ANSI Shadow" font and "Box drawings double" border with 1 H. Padding
|
||||
_BANNER = '''
|
||||
╔═══════════════════════════════════════════════════╗
|
||||
║ ███████╗ ██████╗████████╗██████╗ ██████╗ ██████╗ ║
|
||||
║ ██╔════╝██╔════╝╚══██╔══╝██╔══██╗██╔══██╗██╔══██╗ ║
|
||||
║ █████╗ ██║ ██║ ██████╔╝██║ ██║██████╔╝ ║
|
||||
║ ██╔══╝ ██║ ██║ ██╔══██╗██║ ██║██╔══██╗ ║
|
||||
║ ██║ ╚██████╗ ██║ ██║ ██║██████╔╝██████╔╝ ║
|
||||
║ ╚═╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝╚═════╝ ╚═════╝ ║
|
||||
╚═══════════════════════════════════════════════════╝
|
||||
╚═══ cli by imbored ═══╝
|
||||
'''.strip()
|
||||
|
||||
# Enumeration of number types based on their factorisation
|
||||
class FType(Enum):
|
||||
Unit = 1
|
||||
Composite = 2
|
||||
Prime = 3
|
||||
Unknown = 4
|
||||
|
||||
class FCertainty(Enum):
|
||||
Certain = 1
|
||||
Partial = 2
|
||||
Unknown = 3
|
||||
|
||||
# FactorDB result status codes
|
||||
class DBStatus(StrEnum):
|
||||
C = 'C'
|
||||
CF = 'CF'
|
||||
FF = 'FF'
|
||||
P = 'P'
|
||||
PRP = 'PRP'
|
||||
U = 'U'
|
||||
Unit = 'Unit' # just for 1
|
||||
N = 'N'
|
||||
Add = '*'
|
||||
|
||||
def is_unknown(self) -> bool:
|
||||
return (self in [DBStatus.U, DBStatus.N, DBStatus.Add])
|
||||
|
||||
def classify(self) -> tuple[FType, FCertainty]:
|
||||
return _STATUS_MAP[self]
|
||||
|
||||
def msg_verbose(self) -> str:
|
||||
return _STATUS_MSG_VERBOSE[self]
|
||||
|
||||
# Map of DB Status codes to their factorisation type and certainty
|
||||
_STATUS_MAP = {
|
||||
DBStatus.Unit: (FType.Unit, FCertainty.Certain),
|
||||
DBStatus.C: (FType.Composite, FCertainty.Unknown),
|
||||
DBStatus.CF: (FType.Composite, FCertainty.Partial),
|
||||
DBStatus.FF: (FType.Composite, FCertainty.Certain),
|
||||
DBStatus.P: (FType.Prime, FCertainty.Certain),
|
||||
DBStatus.PRP: (FType.Prime, FCertainty.Partial),
|
||||
DBStatus.U: (FType.Unknown, FCertainty.Unknown),
|
||||
DBStatus.N: (FType.Unknown, FCertainty.Unknown),
|
||||
DBStatus.Add: (FType.Unknown, FCertainty.Unknown),
|
||||
}
|
||||
|
||||
# Reference: https://factordb.com/status.html
|
||||
# NOTE: my factor messages differ slightly from the reference
|
||||
_STATUS_MSG_VERBOSE = {
|
||||
DBStatus.Unit: 'Unit, trivial factorisation',
|
||||
DBStatus.C: 'Composite, no factors known',
|
||||
DBStatus.CF: 'Composite, *partially* factors',
|
||||
DBStatus.FF: 'Composite, fully factored',
|
||||
DBStatus.P: 'Prime',
|
||||
DBStatus.PRP: 'Probable prime',
|
||||
DBStatus.U: 'Unknown (*but in database)',
|
||||
DBStatus.N: 'Not in database (-not added due to your settings)',
|
||||
DBStatus.Add: 'Not in database (+added during request)',
|
||||
}
|
||||
|
||||
# Struct for storing database results with named properties
|
||||
class DBResult:
|
||||
def __init__(self,
|
||||
status: DBStatus,
|
||||
factors: tuple[tuple[int, int]]) -> None:
|
||||
self.status = status
|
||||
self.factors = factors
|
||||
self.ftype, self.certainty = self.status.classify()
|
||||
|
||||
def _make_cookie(fdbuser: str | None) -> dict[str, str]:
|
||||
return {} if fdbuser is None else {'fdbuser': fdbuser}
|
||||
|
||||
def _get_key(by_id: bool):
|
||||
return 'id' if by_id else 'query'
|
||||
|
||||
def _api_query(n: int,
|
||||
fdbuser: str | None,
|
||||
by_id: bool = False) -> requests.models.Response:
|
||||
key = _get_key(by_id)
|
||||
uri = f'{_FDB_URI}/api?{key}={n}'
|
||||
return requests.get(uri, cookies=_make_cookie(fdbuser))
|
||||
|
||||
def _report_factor(n: int,
|
||||
factor: int,
|
||||
fdbuser: str | None,
|
||||
by_id: bool = False) -> requests.models.Response:
|
||||
key = _get_key(by_id)
|
||||
uri = f'{_FDB_URI}/reportfactor.php?{key}={n}&factor={factor}'
|
||||
return requests.get(uri, cookies=_make_cookie(fdbuser))
|
||||
|
||||
'''
|
||||
Attempts a query to FactorDB, returns a DBResult object
|
||||
on success, or None if the request failed due to the
|
||||
get request raising a RequestException.
|
||||
'''
|
||||
def query(n: int,
|
||||
token: str | None = None,
|
||||
by_id: bool = False,
|
||||
cli: bool = False) -> DBResult | None:
|
||||
if cli:
|
||||
print(_BANNER)
|
||||
try:
|
||||
resp = _api_query(n, token, by_id=by_id)
|
||||
except requests.exceptions.RequestException:
|
||||
return None
|
||||
content = resp.json()
|
||||
result = DBResult(
|
||||
DBStatus(content['status']),
|
||||
tuple((int(F[0]), F[1]) for F in content['factors'])
|
||||
)
|
||||
if cli:
|
||||
print(f'Status: {result.status.msg_verbose()}')
|
||||
print(result.factors)
|
||||
|
||||
# ensure the unit has the trivial factorisation (for consistency)
|
||||
if result.status == DBStatus.Unit:
|
||||
result.factors = ((1, 1),)
|
||||
return result
|
||||
|
||||
'''
|
||||
Reports a known factor to FactorDB, also tests it is
|
||||
actually a factor to avoid wasting FactorDBs resources.
|
||||
'''
|
||||
def report(n: int,
|
||||
factor: int,
|
||||
by_id: int,
|
||||
token: str | None = None) -> None:
|
||||
try:
|
||||
resp = _report_factor(n, factor, token)
|
||||
except requests.exceptions.RequestException:
|
||||
return None
|
||||
content = resp.json()
|
||||
print(content)
|
||||
|
||||
46
celeste/math/factor/pftrialdivision.py
Normal file
46
celeste/math/factor/pftrialdivision.py
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
'''
|
||||
The trial division algorithm is essentially the idea that
|
||||
all factors of an integer n are less than or equal to isqrt(n),
|
||||
where isqrt is floor(sqrt(n)).
|
||||
|
||||
Moreover, if p divides n, then all other factors of n must
|
||||
be factors of n//p. Hence they must be <= isqrt(n//p).
|
||||
'''
|
||||
from math import isqrt # integer square root
|
||||
|
||||
# Returns the "multiplicity" of a prime factor
|
||||
def pf_multiplicity(n: int, p: int) -> int:
|
||||
mult = 0
|
||||
while n % p == 0:
|
||||
n //= p
|
||||
mult += 1
|
||||
return n, mult
|
||||
|
||||
'''
|
||||
Trial division prime factorisation algorithm.
|
||||
Returns a list of tuples (p, m) where p is
|
||||
a prime factor and m is its multiplicity.
|
||||
'''
|
||||
def trial_division(n: int) -> list[tuple[int, int]]:
|
||||
factors = []
|
||||
|
||||
# determine multiplicity of the only even prime (2)
|
||||
n, mult_2 = pf_multiplicity(n, 2)
|
||||
if mult_2: factors.append((2, mult_2))
|
||||
# determine odd factors and their multiplicities
|
||||
p = 3
|
||||
mult = 0
|
||||
limit = isqrt(n)
|
||||
while p <= limit:
|
||||
n, mult = pf_multiplicity(n, p)
|
||||
if mult:
|
||||
factors.append((p, mult))
|
||||
limit = isqrt(n) # recalculate limit
|
||||
mult = 0 # reset
|
||||
else:
|
||||
p += 2
|
||||
# if n is still greater than 1, then n is a prime factor
|
||||
if n > 1:
|
||||
factors.append((n, 1))
|
||||
return factors
|
||||
|
||||
37
celeste/math/groups.py
Normal file
37
celeste/math/groups.py
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
'''
|
||||
This library exists to isolate all math functions
|
||||
related to groups and their representations.
|
||||
'''
|
||||
|
||||
from math import gcd
|
||||
|
||||
'''
|
||||
Returns the multiplicative cyclic subgroup
|
||||
generated by an element g modulo m.
|
||||
Returns the cyclic subgroup as a list[int],
|
||||
the order of that subgroup, and a boolean
|
||||
indicating whether g is infinitely repeating
|
||||
with period == ord<g> (or otherwise if it
|
||||
terminates with g**ord<g> == 0).
|
||||
'''
|
||||
def cyclic_subgrp(g: int,
|
||||
m: int,
|
||||
ignore_zero: bool = True) -> tuple[list[int], int, bool]:
|
||||
G = []
|
||||
order = 0
|
||||
periodic = True
|
||||
a = 1 # start at identity
|
||||
for _ in range(m):
|
||||
a = (a * g) % m
|
||||
if a == 0:
|
||||
if not ignore_zero:
|
||||
G.append(a)
|
||||
order += 1
|
||||
periodic = False
|
||||
break
|
||||
# check if we've reached something periodic
|
||||
elif a in G[:1]:
|
||||
break
|
||||
G.append(a)
|
||||
order += 1
|
||||
return G, order, periodic
|
||||
82
celeste/math/numbers/__init__.py
Normal file
82
celeste/math/numbers/__init__.py
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
'''
|
||||
Terminology:
|
||||
Although "divisor" and "factor" mean the same thing.
|
||||
When Celeste discusses "divisors of n" it is implied to
|
||||
mean "proper divisors of n + n itself", and "factors" are
|
||||
the "prime proper divisors of n".
|
||||
'''
|
||||
|
||||
from celeste.extern.primefac import primefac
|
||||
|
||||
def factors(n: int) -> int:
|
||||
pfactors: list[tuple[int, int]] = []
|
||||
# generate primes and progressively store them in pfactors
|
||||
pfgen = primefac(n)
|
||||
watching = next(pfgen)
|
||||
mult = 1
|
||||
# ASSUMPTION: prime generation is (non-strict) monotone increasing
|
||||
while True:
|
||||
p = next(pfgen, None)
|
||||
if p == watching:
|
||||
mult += 1
|
||||
else:
|
||||
pfactors.append((watching, mult))
|
||||
watching = p # reset
|
||||
mult = 1 # reset
|
||||
if p is None:
|
||||
break
|
||||
return pfactors
|
||||
|
||||
def factors2divisors(pfactors: list[tuple[int, int]],
|
||||
sorted: bool = True) -> list[int]:
|
||||
'''
|
||||
Generates all divisors < n of an integer n given its prime factorisation.
|
||||
Input: prime factorisation of n (excluding 1 and n, and duplicates)
|
||||
in the typical form: list[(prime, multiplicity)]
|
||||
'''
|
||||
divisors = [1]
|
||||
for (prime, multiplicity) in pfactors:
|
||||
extension = []
|
||||
for i in range(1, multiplicity+1):
|
||||
term = prime**i
|
||||
extension.extend(list([divisor*term for divisor in divisors]))
|
||||
divisors.extend(extension)
|
||||
if sorted: divisors.sort()
|
||||
return divisors
|
||||
|
||||
def factors2aliquots(pfactors: list[tuple[int, int]]) -> list[int]:
|
||||
return factors2divisors(pfactors)[:-1]
|
||||
|
||||
# "aliquots(n)" is an alias for "divisors(n)"
|
||||
def aliquots(n: int) -> int:
|
||||
'''
|
||||
Returns all aliquot parts (proper divisors) of
|
||||
an integer n, that is all divisors 0 < d <= n.
|
||||
'''
|
||||
return factors2aliquots(factors(n))
|
||||
|
||||
def divisors(n: int) -> int:
|
||||
'''
|
||||
Returns all divisors 0 < d < n of an integer n.
|
||||
'''
|
||||
return factors2divisors(factors(n))
|
||||
|
||||
def aliquot_sum(n: int) -> int:
|
||||
return sum(aliquots(n))
|
||||
|
||||
|
||||
def littleomega(n: int) -> int:
|
||||
'''
|
||||
The Little Omega function counts the number of
|
||||
distinct prime factors of an integer n.
|
||||
Ref: https://en.wikipedia.org/wiki/Prime_omega_function
|
||||
'''
|
||||
return len(factors(n))
|
||||
|
||||
def bigomega(n: int) -> int:
|
||||
'''
|
||||
The Big Omega function counts the total number of
|
||||
prime factors (including multiplicity) of an integer n.
|
||||
Ref: https://en.wikipedia.org/wiki/Prime_omega_function
|
||||
'''
|
||||
return sum(factor[1] for factor in factors(n))
|
||||
3
celeste/math/numbers/functions.py
Normal file
3
celeste/math/numbers/functions.py
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
def factorial(n: int) -> int:
|
||||
if n == 0: return 1
|
||||
return n * factorial(n-1)
|
||||
1
celeste/math/numbers/kinds.py
Normal file
1
celeste/math/numbers/kinds.py
Normal file
|
|
@ -0,0 +1 @@
|
|||
|
||||
21
celeste/math/numbertheory.py
Normal file
21
celeste/math/numbertheory.py
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
def euclidean_algo(a: int, b: int) -> int:
|
||||
while b: a, b = b, a % b
|
||||
return a
|
||||
|
||||
'''
|
||||
Calculates coefficients x and y of Bezout's identity: ax + by = gcd(a,b)
|
||||
NOTE: Based on the Extended Euclidean Algorithm's Wikipedia page
|
||||
'''
|
||||
def extended_euclid_algo(a: int, b: int) -> tuple[int, int]:
|
||||
(old_r, r) = (a, b)
|
||||
(old_s, s) = (1, 0)
|
||||
(old_t, t) = (0, 1)
|
||||
while r != 0:
|
||||
q = old_r // r
|
||||
(old_r, r) = (r, old_r - q*r)
|
||||
(old_s, s) = (s, old_s - q*s)
|
||||
(old_t, t) = (t, old_t - q*t)
|
||||
# Bezout cofficients: (old_s, old_t)
|
||||
# Greatest Common Divisor: old_r
|
||||
# Quotients by the gcd: (t, s)
|
||||
return (t, s)
|
||||
47
celeste/math/primes.py
Normal file
47
celeste/math/primes.py
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
from math import gcd, inf
|
||||
|
||||
from celeste.math.numbers import bigomega, factors
|
||||
from celeste.extern.primefac import (
|
||||
isprime,
|
||||
primegen as Primes,
|
||||
)
|
||||
|
||||
def coprime(n: int, m: int) -> bool:
|
||||
return gcd(n, m) == 1
|
||||
|
||||
def almostprime(n: int, k: int) -> bool:
|
||||
'''
|
||||
A natural n is "k-almost prime" if it has exactly
|
||||
k prime factors (including multiplicity).
|
||||
'''
|
||||
return (bigomega(n) == k)
|
||||
|
||||
def semiprime(n: int) -> bool:
|
||||
'''
|
||||
A semiprime number is one that is 2-almost prime.
|
||||
Ref: https://en.wikipedia.org/wiki/Semiprime
|
||||
'''
|
||||
return almostprime(n, 2)
|
||||
|
||||
def eulertotient(x: int | list) -> int:
|
||||
'''
|
||||
Evaluates Euler's Totient function.
|
||||
Input: `x: int` is prime factorised by Lucas A. Brown's primefac.py
|
||||
else `x: list` is assumed to the prime factorisation of `x: int`
|
||||
'''
|
||||
pfactors = x if isinstance(x, list) else factors(n)
|
||||
return prod((p-1)*(p**(e-1)) for (p, e) in pfactors)
|
||||
# def eulertotient(n: int) -> int:
|
||||
# '''
|
||||
# Uses trial division to compute
|
||||
# Euler's Totient (Phi) Function.
|
||||
# '''
|
||||
# phi = int(n > 1 and n)
|
||||
# for p in range(2, int(n ** .5) + 1):
|
||||
# if not n % p:
|
||||
# phi -= phi // p
|
||||
# while not n % p:
|
||||
# n //= p
|
||||
# #if n is > 1 it means it is prime
|
||||
# if n > 1: phi -= phi // n
|
||||
# return phi
|
||||
48
celeste/math/primes/__init__.py
Normal file
48
celeste/math/primes/__init__.py
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
from math import inf, isqrt # integer square root
|
||||
from itertools import takewhile, compress
|
||||
|
||||
SMALL_PRIMES = (2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59)
|
||||
|
||||
'''
|
||||
Euler's Totient (Phi) Function
|
||||
Implemented in O(nloglog(n)) using the Sieve of Eratosthenes.
|
||||
'''
|
||||
def eulertotient(n: int) -> int:
|
||||
phi = int(n > 1 and n)
|
||||
for p in range(2, isqrt(n) + 1):
|
||||
if not n % p:
|
||||
phi -= phi // p
|
||||
while not n % p:
|
||||
n //= p
|
||||
#if n is > 1 it means it is prime
|
||||
if n > 1: phi -= phi // n
|
||||
return phi
|
||||
|
||||
'''
|
||||
Tests the primality of an integer using its totient.
|
||||
NOTE: If totient(n) has already been calculated
|
||||
then pass it as the optional phi parameter.
|
||||
'''
|
||||
def is_prime(n: int, phi: int = None) -> bool:
|
||||
return n - 1 == (phi if phi is not None else eulertotient(n))
|
||||
|
||||
# Taken from Lucas A. Brown's primefac.py (some variables renamed)
|
||||
def primegen(limit=inf) -> int:
|
||||
ltlim = lambda x: x < limit
|
||||
yield from takewhile(ltlim, SMALL_PRIMES)
|
||||
pl, prime = [3,5,7], primegen()
|
||||
for p in pl: next(prime)
|
||||
n = next(prime); nn = n*n
|
||||
while True:
|
||||
n = next(prime); ll, nn = nn, n*n
|
||||
delta = nn - ll
|
||||
sieve = bytearray([True]) * delta
|
||||
for p in pl:
|
||||
k = (-ll) % p
|
||||
sieve[k::p] = bytearray([False]) * ((delta-k)//p + 1)
|
||||
if nn > limit: break
|
||||
yield from compress(range(ll,ll+delta,2), sieve[::2])
|
||||
pl.append(n)
|
||||
yield from takewhile(ltlim, compress(range(ll,ll+delta,2), sieve[::2]))
|
||||
|
||||
|
||||
29
celeste/math/util.py
Normal file
29
celeste/math/util.py
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
from collections.abc import Iterable
|
||||
from itertools import chain, combinations
|
||||
|
||||
def clamp(n: int, min: int, max: int) -> int:
|
||||
if n < min:
|
||||
return min
|
||||
elif n > max:
|
||||
return max
|
||||
return n
|
||||
|
||||
def clamp_max(n: int, max: int) -> int:
|
||||
return max if n > max else n
|
||||
|
||||
def clamp_min(n: int, max: int) -> int:
|
||||
return min if n < min else n
|
||||
|
||||
def digits(n: int) -> int:
|
||||
return len(str(n))
|
||||
|
||||
# NOTE: assumes A and B are equal length
|
||||
def xor_bytes(A: bytes, B: bytes) -> bytes:
|
||||
return b''.join([(a ^ b).to_bytes() for (a, b) in zip(A, B)])
|
||||
def xor_str(A: str, B: str) -> str:
|
||||
return ''.join([chr(ord(a) ^ ord(b)) for (a, b) in zip(A, B)])
|
||||
|
||||
|
||||
def powerset(iterable: Iterable) -> Iterable:
|
||||
s = list(iterable)
|
||||
return chain.from_iterable(combinations(s, r) for r in range(len(s)+1))
|
||||
Loading…
Add table
Add a link
Reference in a new issue