Source code for py3nt.core.factorize
"""Factorize integers"""
from collections import deque
from dataclasses import dataclass, field
import numpy as np
from py3nt.core.base import BaseFactorization, BaseSieveFactorization
from py3nt.core.primality_test import is_prime
from py3nt.core.sieve import SieveOfEratosthenes, SieveOfEratosthenesOptimized
from py3nt.defaults import (
BIGGEST_NUMBER,
LARGEST_SMALL_NUMBER,
LOGN_PRIME_FACTOR_FIELD,
MAX_LOGN_FACTORIZATION_LIMIT,
)
from py3nt.numbers.integer import Integer
[docs]
def is_prime_power(n: int) -> tuple[int, int]:
"""Check if :math:`n` is a prime power or not.
Parameters
----------
n : ``int``
A positive integer.
Returns
-------
tuple[int, int]
:math:`(p, e)` where :math:`n=p^{e}`.
"""
if n < 2:
return (0, 0)
lim: int = int(np.log2(n))
for i in range(1, lim + 1):
root = Integer(n).kthroot(k=i)
if root**i == n:
if is_prime(root):
return (root, i)
return (0, lim)
[docs]
@dataclass
class NaiveSqrtFactorization(BaseFactorization):
"""
Factorize small numbers in sqrt(n) complexity.
Methods
-------
factorize:
Factorize a positive integer using naive sqrt method.
"""
[docs]
def factorize(self, n: int) -> dict[int, int]:
root = int(np.floor(np.sqrt(1.0 * n)))
factorization = {}
for i in np.arange(start=2, step=1, stop=root + 1):
if (n % i) == 0:
prime_factor = i
multiplicity = 0
while (n % prime_factor) == 0:
n //= prime_factor
multiplicity += 1
factorization[prime_factor] = multiplicity
if n > 1:
factorization[n] = 1
return factorization
[docs]
@dataclass
class SieveSqrtFactorization(BaseSieveFactorization):
r"""
Factorization using naive sieve and primes not exceeding :math:`\sqrt{n}`.
Methods
-------
factorize:
Factorize a positive integer using sieve generated primes
not exceeding :math:`\sqrt{n}`.
"""
[docs]
def factorize(self, n: int) -> dict[int, int]:
primes = self.sieve.primes_
root = int(np.floor(np.sqrt(1.0 * n)))
factorization = {}
for prime in primes:
if prime > root:
break
if (n % prime) == 0:
multiplicity = 0
while (n % prime) == 0:
n //= prime
multiplicity += 1
factorization[prime] = multiplicity
if n > 1:
factorization[n] = 1
return factorization
[docs]
@dataclass
class LognSieveFactorization(BaseSieveFactorization):
"""
Factorize small numbers in logn complexity.
Methods
-------
factorize:
Factorize a positive integer using pre-stored prime factors.
"""
[docs]
def factorize(self, n: int) -> dict[int, int]:
factorization: dict[int, int] = {}
prime_factors = getattr(self.sieve, LOGN_PRIME_FACTOR_FIELD)
while n > 1:
prime_factor = prime_factors[n]
multiplcity = 0
while (n % prime_factor) == 0:
n //= prime_factor
multiplcity += 1
factorization[prime_factor] = multiplcity
return factorization
[docs]
@dataclass
class BigIntFactorization(BaseFactorization):
"""
Factorize large positive integers not exceeding a default biggest number.
Methods
-------
factorize:
Factorize a positive integer using Pollard's rho algorithm.
"""
[docs]
def factorize(self, n) -> dict[int, int]:
if np.greater(n, BIGGEST_NUMBER):
raise ValueError(
f"{n} is greater than the current default biggest number: {BIGGEST_NUMBER}"
)
queue = deque([n])
factorization: dict = {}
while queue:
cur = queue.popleft()
if is_prime(n=cur):
if cur in factorization:
factorization[cur] += 1
else:
factorization[cur] = 1
else:
factor = Integer(cur).brent_pollard_rho_factor()
queue.append(factor)
queue.append(cur // factor)
return factorization
[docs]
@dataclass
class FactorizationFactory:
"""Factorize positive integers not exceeding the default biggest number.
Methods
-------
factorize:
Factorize a positive integer.
Raises
------
ValueError
If n is negative or exceeds the default biggest number.
"""
N: int
with_sieve: bool = field(default=True)
factorizer: BaseFactorization = field(init=False)
def __post_init__(self) -> None:
self.factorizer = self._get_factorizer_class()
if isinstance(self.factorizer, BaseSieveFactorization):
self.factorizer.regenerate_primes()
def _get_factorizer_class(self) -> BaseFactorization:
if not self.with_sieve:
return NaiveSqrtFactorization()
if np.less_equal(self.N, MAX_LOGN_FACTORIZATION_LIMIT):
return LognSieveFactorization(
sieve=SieveOfEratosthenesOptimized(limit=self.N)
)
if np.less_equal(self.N, LARGEST_SMALL_NUMBER):
return SieveSqrtFactorization(
sieve=SieveOfEratosthenes(limit=int(np.floor(np.sqrt(self.N * 1.0))))
)
return BigIntFactorization()
[docs]
def factorize(self, n: int) -> dict[int, int]:
"""Factorize positive integers.
Parameters
----------
n : ``int``
A positive integer.
Returns
-------
dict[int, int]
Key value pairs of prime factorization.
Keys are prime factors.
Values are multiplicities of corresponding prime factors.
Raises
------
ValueError
If :math:`n` is not positive.
"""
if n < 1:
raise ValueError("n must be a positive integer")
if n == 1:
return {1: 1}
return self.factorizer.factorize(n=n)