packages feed

moonlight-algebra-0.1.0.0: src-abstract/Moonlight/Algebra/Pure/NumberTheory.hs

-- | Elementary number theory: primality, divisors, prime-power factorisation,
-- the Moebius function, and counts of group elements by multiplicative order.
module Moonlight.Algebra.Pure.NumberTheory
  ( countElementsWithOrderDividing,
    countExactOrderElements,
    divisorsFromPrimePowers,
    divisorsOf,
    isPrime,
    mobiusValue,
    primePowerFactors,
    primePowerPart,
  )
where

import Data.Function ((&))
import Moonlight.Algebra.Pure.GCD (gcd)
import Prelude hiding (gcd)

countExactOrderElements :: Integer -> [Integer] -> Integer
countExactOrderElements orderValue cyclicOrders
  | normalizedOrder <= 0 = 0
  | otherwise =
      divisorsOf normalizedOrder
        & fmap
          ( \divisorValue ->
              mobiusValue (normalizedOrder `div` divisorValue)
                * countElementsWithOrderDividing divisorValue cyclicOrders
          )
        & sum
  where normalizedOrder = abs orderValue

countElementsWithOrderDividing :: Integer -> [Integer] -> Integer
countElementsWithOrderDividing divisorValue =
  product . fmap (\cyclicOrder -> gcd (abs cyclicOrder) divisorValue)

mobiusValue :: Integer -> Integer
mobiusValue value
  | normalizedValue == 0 = 0
  | primePowers & any ((> 1) . snd) = 0
  | even (length primePowers) = 1
  | otherwise = -1
  where
    normalizedValue = abs value
    primePowers = primePowerFactors normalizedValue

divisorsOf :: Integer -> [Integer]
divisorsOf value
  | normalizedValue <= 0 = []
  | otherwise = divisorsFromPrimePowers (primePowerFactors normalizedValue)
  where normalizedValue = abs value

divisorsFromPrimePowers :: [(Integer, Int)] -> [Integer]
divisorsFromPrimePowers primePowers =
  case primePowers of
    [] -> [1]
    (primeValue, exponentValue) : remainingPrimePowers ->
      let remainingDivisors = divisorsFromPrimePowers remainingPrimePowers
          primePowersAtFactor = take (exponentValue + 1) (iterate (* primeValue) 1)
       in primePowersAtFactor >>= (\powerValue -> fmap (powerValue *) remainingDivisors)

primePowerFactors :: Integer -> [(Integer, Int)]
primePowerFactors value =
  factorFrom 2 (abs value)

primePowerPart :: Integer -> Integer -> Integer
primePowerPart primeValue value =
  let normalizedPrime = abs primeValue
      normalizedValue = abs value
   in if not (isPrime normalizedPrime)
        then 1
        else
          case lookup normalizedPrime (primePowerFactors normalizedValue) of
            Nothing -> 1
            Just exponentValue -> normalizedPrime ^ exponentValue

isPrime :: Integer -> Bool
isPrime value =
  let normalizedValue = abs value
   in normalizedValue > 1
        && primePowerFactors normalizedValue == [(normalizedValue, 1)]

factorFrom :: Integer -> Integer -> [(Integer, Int)]
factorFrom candidateValue remainingValue
  | remainingValue <= 1 = []
  | candidateValue * candidateValue > remainingValue = [(remainingValue, 1)]
  | remainingValue `mod` candidateValue == 0 =
      let (multiplicityValue, reducedValue) = factorMultiplicity candidateValue remainingValue 0
       in (candidateValue, multiplicityValue) : factorFrom (candidateValue + 1) reducedValue
  | otherwise = factorFrom (candidateValue + 1) remainingValue

factorMultiplicity :: Integer -> Integer -> Int -> (Int, Integer)
factorMultiplicity primeValue remainingValue multiplicityValue
  | remainingValue `mod` primeValue == 0 =
      factorMultiplicity primeValue (remainingValue `div` primeValue) (multiplicityValue + 1)
  | otherwise = (multiplicityValue, remainingValue)