module Math.NumberTheory.Pell.PQa (
PQa(..),
pqa,
reduced,
period) where
import Data.Ratio ((%))
import Math.NumberTheory.Powers.Squares (isSquare, integerSquareRoot)
data PQa = PQa {
a :: Integer,
b :: Integer,
g :: Integer,
a' :: Integer,
p :: Integer,
q :: Integer } deriving Show
pqa :: Integer -> Integer -> Integer -> [PQa]
pqa p0 q0 d
| q0 == 0 = error "Q0 must not be zero."
| d <= 0 = error "D must be positive."
| isSquare d = error $ "D must not be a square, but D == " ++ show dd ++ "^2."
| (p0 * p0 d) `mod` q0 /= 0 = error $ "P0^2 must be equivalent to D modulo Q0, but "
++ show p0 ++ "^2 == " ++ show (p0 `mod` q0) ++ " /= " ++ show (d `mod` q0)
++ " == " ++ show d ++ " (mod " ++ show q0 ++ ")"
| otherwise = go p0 q0 (PQa 0 1 (p0) undefined undefined undefined) (PQa 1 0 q0 undefined undefined undefined)
where
dd = integerSquareRoot d
go :: Integer -> Integer -> PQa -> PQa -> [PQa]
go p' q' x y =
let
_a' = if q' > 0 then floor $ (p' + dd) % q' else floor $ (p' + dd + 1) % q'
_a = _a' * a y + a x
_b = _a' * b y + b x
_g = _a' * g y + g x
_p = _a' * q' p'
_q = (d _p * _p) `div` q'
z = PQa _a _b _g _a' p' q'
in
z : go _p _q y z
reduced :: Integer -> Integer -> Integer -> Bool
reduced x y dd
| y > 0 = (dd >= y x) && (dd < x + y) && (dd >= x)
| otherwise = (dd < y x) && (dd >= x + y) && (dd < x)
period :: Integer -> Integer -> Integer -> (Int, [PQa])
period p0 q0 d = u [] 0 $ pqa p0 q0 d where
dd = integerSquareRoot d
u acc i (x : xs)
| reduced (p x) (q x) dd = v (x : acc) i (p x) (q x) xs
| otherwise = u (x : acc) (succ i) xs
u _ _ _ = error "algorithm error"
v acc i pp qq (x : xs)
| (pp == p x) && (qq == q x) = (i, reverse acc)
| otherwise = v (x : acc) i pp qq xs
v _ _ _ _ _ = error "algorithm error"