packages feed

hspray 0.2.2.0 → 0.2.3.0

raw patch · 4 files changed

+192/−20 lines, 4 filesPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

+ Math.Algebra.Hspray: gcdQX :: Spray Rational -> Spray Rational -> Spray Rational

Files

CHANGELOG.md view
@@ -66,4 +66,13 @@ 
 ## 0.2.2.0 - 2024-03-26
 
-* Fixed an error in `esPolynomial`, which resulted to a bug in `isSymmetricSpray`.+* Fixed an error in `esPolynomial`, which resulted to a bug in `isSymmetricSpray`.
+
+
+## 0.2.3.0 - 2024-03-28
+
+* New unit tests.
+
+* Fixed `resultant` and `subresultants`: the variables of the sprays they return were incorrect.
+
+* New function `gcdQX`, to compute the greatest common divisor of two univariate sprays with rational coefficients.
hspray.cabal view
@@ -1,7 +1,7 @@ name:                hspray
-version:             0.2.2.0
+version:             0.2.3.0
 synopsis:            Multivariate polynomials.
-description:         Manipulation of multivariate polynomials on a ring and Gröbner bases.
+description:         Manipulation of multivariate polynomials on a ring, Gröbner basis, resultant and subresultants.
 homepage:            https://github.com/stla/hspray#readme
 license:             GPL-3
 license-file:        LICENSE
src/Math/Algebra/Hspray.hs view
@@ -61,6 +61,8 @@   , resultant1
   , subresultants
   , subresultants1
+  -- * Greatest common divisor
+  , gcdQX
   -- * Miscellaneous
   , fromList
   , toList
@@ -83,6 +85,7 @@                                                 , maximumBy 
                                                 , (\\)
                                                 , findIndices
+                                                , elemIndices
                                                 , nub
                                                 , foldl1'
                                                 )
@@ -334,7 +337,8 @@ 
 -- | number of variables in a spray
 numberOfVariables :: Spray a -> Int
-numberOfVariables spray = maximum (map nvariables powers)
+numberOfVariables spray =
+  if null powers then 0 else maximum (map nvariables powers)
   where
     powers = HM.keys spray
 
@@ -390,7 +394,7 @@   where
     n = numberOfVariables spray
     monomials = HM.toList spray
-    spray' = foldl1 (^+^) (map (fromMonomial . substituteMonomial subs) monomials)
+    spray' = foldl1' (^+^) (map (fromMonomial . substituteMonomial subs) monomials)
 
 -- | Converts a spray with rational coefficients to a spray with double coefficients
 -- (useful for evaluation)
@@ -447,7 +451,7 @@ 
 -- | Swaps two variables of a spray
 -- 
--- prop> swapVariables (1, 3) p == permuteVariables p [3, 2, 1]
+-- prop> swapVariables p (1, 3) == permuteVariables p [3, 2, 1]
 swapVariables :: Spray a -> (Int, Int) -> Spray a
 swapVariables spray (i, j) = 
   if i>=1 && j>=1  
@@ -855,7 +859,7 @@     esPolys = map (\i -> esPolynomial n i :: Spray a) indices
     yPolys = map (\i -> lone (n + i) :: Spray a) indices
     gPolys = zipWith (^-^) esPolys yPolys
-    gbasis = groebner gPolys False
+    gbasis = groebner0 gPolys
     g = sprayDivision spray gbasis
     gpowers = HM.keys g
     check1 = minimum (map nvariables gpowers) > n
@@ -934,10 +938,14 @@     times :: a -> a -> a
     times x y = if x == AlgAdd.zero then AlgAdd.zero else x AlgRing.* y
 
--- the coefficients of a spray as a spray with univariate spray coefficients
+-- the coefficients of a spray as a univariate spray in x with spray coefficients
 sprayCoefficients :: (Eq a, AlgRing.C a) => Spray a -> [Spray a]
-sprayCoefficients spray = reverse sprays
+sprayCoefficients spray = 
+  if n == 0 
+    then [constantSpray constantTerm]
+    else reverse sprays
   where
+    n = numberOfVariables spray 
     (powers, coeffs) = unzip (HM.toList spray)
     expnts = map exponents powers
     constantTerm = fromMaybe AlgAdd.zero (HM.lookup (Powers S.empty 0) spray)
@@ -948,12 +956,20 @@     sprays'' = zipWith (curry fromMonomial) powers'' coeffs'
     imap = IM.fromListWith (^+^) (zip xpows sprays'')
     imap' = IM.insertWith (^+^) 0 (constantSpray constantTerm) imap
-    sprays = [fromMaybe AlgAdd.zero (IM.lookup i imap') | i <- [0 .. maximum xpows]]
+    permutation = [2 .. n] ++ [1]
+    sprays = [
+        permuteVariables (fromMaybe AlgAdd.zero (IM.lookup i imap')) permutation 
+        | i <- [0 .. maximum xpows]
+      ]
 
--- | Resultant of two univariate sprays
+-- | Resultant of two /univariate/ sprays
 resultant1 :: (Eq a, AlgRing.C a) => Spray a -> Spray a -> a
-resultant1 p q = detLaplace $ sylvesterMatrix pcoeffs qcoeffs
+resultant1 p q = 
+  if n <= 1 
+    then detLaplace $ sylvesterMatrix pcoeffs qcoeffs
+    else error "resultant1: the two sprays must be univariate."
   where
+    n = max (numberOfVariables p) (numberOfVariables q)
     pexpnts = map (`index` 0) $ filter (not . S.null) (map exponents (HM.keys p))
     qexpnts = map (`index` 0) $ filter (not . S.null) (map exponents (HM.keys q))
     p0 = fromMaybe AlgAdd.zero (HM.lookup (Powers S.empty 0) p)
@@ -965,10 +981,13 @@       then [q0]
       else q0 : [fromMaybe AlgAdd.zero (HM.lookup (Powers (S.singleton i) 1) q) | i <- [1 .. maximum qexpnts]]
 
--- | Subresultants of two univariate sprays
+-- | Subresultants of two /univariate/ sprays
 subresultants1 :: (Eq a, AlgRing.C a) => Spray a -> Spray a -> [a]
-subresultants1 p q = map (detLaplace . sylvesterMatrix' pcoeffs qcoeffs) [0 .. min d e - 1]
+subresultants1 p q = if n <= 1 
+  then map (detLaplace . sylvesterMatrix' pcoeffs qcoeffs) [0 .. min d e - 1]
+  else error "subresultants1: the two sprays must be univariate."
   where
+    n = max (numberOfVariables p) (numberOfVariables q)
     pexpnts = map (`index` 0) $ filter (not . S.null) (map exponents (HM.keys p))
     qexpnts = map (`index` 0) $ filter (not . S.null) (map exponents (HM.keys q))
     p0 = fromMaybe AlgAdd.zero (HM.lookup (Powers S.empty 0) p)
@@ -990,13 +1009,15 @@   -> Spray a
 resultant var p q = 
   if var >= 1 && var <= n 
-    then detLaplace $ sylvesterMatrix (sprayCoefficients p') (sprayCoefficients q')
+    then permuteVariables det permutation'
     else error "resultant: invalid variable index."
   where
     n = max (numberOfVariables p) (numberOfVariables q)
     permutation = var : [1 .. var-1] ++ [var+1 .. n]
+    permutation' = [2 .. var] ++ (1 : [var+1 .. n])
     p' = permuteVariables p permutation
     q' = permuteVariables q permutation
+    det = detLaplace $ sylvesterMatrix (sprayCoefficients p') (sprayCoefficients q')
 
 -- | Subresultants of two sprays
 subresultants :: (Eq a, AlgRing.C a) 
@@ -1007,7 +1028,7 @@ subresultants var p q 
   | var < 1 = error "subresultants: invalid variable index."
   | var > n = error "subresultants: too large variable index."
-  | otherwise = map (detLaplace . sylvesterMatrix' pcoeffs qcoeffs) [0 .. min d e - 1]
+  | otherwise = map (permute . detLaplace . sylvesterMatrix' pcoeffs qcoeffs) [0 .. min d e - 1]
   where
     pcoeffs = sprayCoefficients p'
     qcoeffs = sprayCoefficients q'
@@ -1017,3 +1038,90 @@     permutation = var : [1 .. var-1] ++ [var+1 .. n]
     p' = permuteVariables p permutation
     q' = permuteVariables q permutation
+    permutation' = [2 .. var] ++ (1 : [var+1 .. n])
+    permute spray = permuteVariables spray permutation'
+
+
+-- GCD ------------------------------------------------------------------------
+
+-- the degree of a spray as a univariate spray in x with spray coefficients
+degree :: Eq a => Spray a -> Int
+degree spray = 
+  if numberOfVariables spray == 0 
+    then 
+      if spray == HM.empty then minBound else 0
+    else maximum xpows
+  where
+    expnts = map exponents $ HM.keys spray
+    expnts' = filter (not . S.null) expnts
+    xpows = map (`index` 0) expnts'
+
+-- the degree and the leading coefficient of a spray as a univariate spray in x with spray coefficients
+degreeAndLeadingCoefficient :: (Eq a, AlgRing.C a) => Spray a -> (Int, Spray a)
+degreeAndLeadingCoefficient spray = 
+  if n == 0 
+    then (if constantTerm == AlgAdd.zero then minBound else 0, constantSpray constantTerm)
+    else (deg, leadingCoeff)
+  where
+    n = numberOfVariables spray
+    (powers, coeffs) = unzip (HM.toList spray)
+    expnts = map exponents powers
+    constantTerm = fromMaybe AlgAdd.zero (HM.lookup (Powers S.empty 0) spray)
+    (expnts', coeffs') = unzip $ filter (\(s,_) -> not $ S.null s) (zip expnts coeffs)
+    xpows = map (`index` 0) expnts'
+    deg = maximum xpows
+    is = elemIndices deg xpows
+    expnts'' = [S.update 0 0 (expnts' !! i) | i <- is]
+    powers'' = map (\s -> Powers s (S.length s)) expnts''
+    coeffs'' = [coeffs' !! i | i <- is]
+    leadingCoeff = foldl1' (^+^) (zipWith (curry fromMonomial) powers'' coeffs'')
+
+-- pseudo-division of two sprays (assuming degA >= degB >= 0)
+pseudoDivision :: (Eq a, AlgRing.C a) 
+  => Spray a                       -- ^ A
+  -> Spray a                       -- ^ B
+  -> (Spray a, (Spray a, Spray a)) -- ^ (c, (Q, R)) such that c^*^A = B^*^Q ^+^ R
+pseudoDivision sprayA sprayB = (ellB ^**^ delta , go sprayA zeroSpray delta)
+  where
+    degA = degree sprayA
+    (degB, ellB) = degreeAndLeadingCoefficient sprayB
+    delta = degA - degB + 1
+    go sprayR sprayQ e = 
+      if degR < degB
+        then (q ^*^ sprayQ, q ^*^ sprayR)
+        else go (ellB ^*^ sprayR ^-^ sprayS ^*^ sprayB) (ellB ^*^ sprayQ ^+^ sprayS) (e - 1)
+      where
+        (degR, ellR) = degreeAndLeadingCoefficient sprayR
+        q = ellB ^**^ e
+        sprayX = lone 1
+        sprayS = ellR ^*^ sprayX ^**^ (degR - degB)
+
+-- The GCD of two /univariate/ sprays with rational coefficients
+gcdQX :: Spray Rational -> Spray Rational -> Spray Rational
+gcdQX sprayA sprayB
+  | n >= 2                        = error "gcdQX: the sprays are not univariate." 
+  | degree sprayB > degree sprayA = gcdQX sprayB sprayA 
+  | sprayB == zeroSpray           = sprayA
+  | otherwise                     = go sprayA' sprayB' 1 1
+  where
+    n = max (numberOfVariables sprayA) (numberOfVariables sprayB)
+    coefficients :: Spray Rational -> [Rational]
+    coefficients spray = map (getCoefficient []) (sprayCoefficients spray)
+    cont :: Spray Rational -> Rational
+    cont spray = maximum $ coefficients spray
+    reduce :: Spray Rational -> Spray Rational
+    reduce spray = HM.map (/ cont spray) spray
+    a = cont sprayA
+    b = cont sprayB
+    d = max a b
+    sprayA' = HM.map (/ a) sprayA
+    sprayB' = HM.map (/ b) sprayB
+    go sprayA'' sprayB'' g h 
+      | sprayR == zeroSpray           = d *^ reduce sprayB''
+      | numberOfVariables sprayR == 0 = constantSpray d
+      | otherwise = go sprayB'' (HM.map (/ (g*h^delta)) sprayR) ellAq'' (g^delta / h^(delta-1))
+        where
+          (_, (_, sprayR)) = pseudoDivision sprayA'' sprayB''
+          (degA'', ellA'') = degreeAndLeadingCoefficient sprayA''
+          ellAq'' = getCoefficient [] ellA''
+          delta = degA'' - degree sprayB''
tests/Main.hs view
@@ -29,7 +29,8 @@                                                   resultant,
                                                   subresultants,
                                                   resultant1,
-                                                  subresultants1
+                                                  subresultants1,
+                                                  gcdQX
                                                 )
 import           Test.Tasty                     ( defaultMain
                                                 , testGroup
@@ -164,15 +165,33 @@         p' = permuteVariables p [3, 2, 1]
       assertEqual "" p' (swapVariables p (1, 3)),
 
-    testCase "resultant" $ do
+    testCase "resultant w.r.t x" $ do
       let
         x = lone 1 :: Spray Rational
         y = lone 2 :: Spray Rational
         p = x^**^4 ^-^ x^**^3 ^+^ x^**^2 ^-^ 2*^ (x ^*^ y^**^2) ^+^ y^**^4 
         q = x ^-^ (2*^ y^**^2)
         r = resultant 1 p q
-      assertEqual "" r (x^**^4 ^-^ (8*^ x^**^6) ^+^ (16*^ x^**^8)),
+      assertEqual "" r (y^**^4 ^-^ (8*^ y^**^6) ^+^ (16*^ y^**^8)),
 
+    testCase "resultant w.r.t y" $ do
+      let
+        x = lone 1 :: Spray Rational
+        y = lone 2 :: Spray Rational
+        p = x^**^4 ^-^ x^**^3 ^+^ x^**^2 ^-^ 2*^ (x ^*^ y^**^2) ^+^ y^**^4 
+        q = x ^-^ (2*^ y^**^2)
+        r = resultant 2 p q
+      assertEqual "" r (16*^x^**^8 ^-^ 32*^x^**^7 ^+^ 24*^x^**^6 ^-^ 8*^x^**^5 ^+^ x^**^4),
+
+    testCase "resultant product rule" $ do
+      let
+        x = lone 1 :: Spray Rational
+        y = lone 2 :: Spray Rational
+        f = x^**^4 ^-^ x^**^3 ^+^ x^**^2 ^-^ 2*^ (x ^*^ y^**^2) ^+^ y^**^4 
+        g = x ^-^ (2*^ y^**^2)
+        h = x^**^2 ^*^ y ^+^ y^**^3 ^+^ unitSpray
+      assertEqual "" (resultant 1 (f^*^g) h) (resultant 1 f h ^*^ resultant 1 g h),
+
     testCase "subresultants" $ do
       let
         x = lone 1 :: Spray Rational
@@ -189,11 +208,47 @@         q = x^**^2 ^-^ 3*^x ^+^ constantSpray 2 
       assertEqual "" (resultant1 p q) (0%1),
 
+    testCase "resultant1 product rule" $ do
+      let
+        x = lone 1 :: Spray Rational
+        f = x^**^2 ^-^ 5*^x ^+^ constantSpray 6 
+        g = x^**^2 ^-^ 3*^x ^+^ constantSpray 2 
+        h = x^**^3 ^+^ x ^-^ constantSpray 3
+      assertEqual "" (resultant1 (f^*^g) h) (resultant1 f h * resultant1 g h),
+
     testCase "subresultants1" $ do
       let
         x = lone 1 :: Spray Rational
         p = x^**^2 ^-^ 5*^x ^+^ constantSpray 6 
         q = x^**^2 ^-^ 3*^x ^+^ constantSpray 2 
-      assertEqual "" (subresultants1 p q) [0%1, 2%1, 1%1]
+      assertEqual "" (subresultants1 p q) [0%1, 2%1, 1%1],
+
+    testCase "resultant agrees with resultant1 for univariate case" $ do
+      let
+        x = lone 1 :: Spray Rational
+        f = x^**^4 ^-^ x^**^3 ^+^ x^**^2 ^-^ 2*^x 
+        g = x ^-^ (2*^ x^**^2) ^+^ constantSpray 4
+        r = resultant 1 f g
+        r1 = resultant1 f g
+      assertEqual "" r1 (getCoefficient [] r),
+
+    testCase "gcdQX" $ do
+      let
+        x = lone 1 :: Spray Rational
+        sprayD = x^**^2 ^+^ unitSpray
+        sprayA = sprayD ^*^ (x^**^4 ^-^ x) 
+        sprayB = sprayD ^*^ (2*^x ^+^ unitSpray)
+        sprayGCD = gcdQX sprayA sprayB
+      assertEqual "" sprayGCD (2 *^ sprayD),
+
+    testCase "gcdQX with a constant spray" $ do
+      let
+        x = lone 1 :: Spray Rational
+        sprayA = 3 *^ x^**^4 ^-^ x 
+        b1 = 2 :: Rational
+        b2 = 4 :: Rational
+        sprayB1 = constantSpray b1
+        sprayB2 = constantSpray b2
+      assertBool "" (gcdQX sprayA sprayB1 == constantSpray 3 && gcdQX sprayA sprayB2 == constantSpray 4) 
 
   ]