-
Notifications
You must be signed in to change notification settings - Fork 0
/
CAD.hs
137 lines (115 loc) · 4.59 KB
/
CAD.hs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
-- |
-- Cylindrical Algebraic Decomposition
--
module Theory.NonLinearRealArithmatic.CAD where
import Theory.NonLinearRealArithmatic.UnivariatePolynomial (UnivariatePolynomial, viewLeadTerm)
import qualified Theory.NonLinearRealArithmatic.UnivariatePolynomial as Uni
import Utils (adjacentPairs, count)
import Data.Maybe (maybeToList)
import Debug.Trace
data Interval a = Open a a | Closed a a
instance Show a => Show (Interval a) where
show (Open start end) = "(" ++ show start ++ "," ++ show end ++ ")"
show (Closed start end) = "[" ++ show start ++ "," ++ show end ++ "]"
getLowerBound :: Interval a -> a
getLowerBound = \case
Open lb _ -> lb
Closed lb _ -> lb
getUpperBound :: Interval a -> a
getUpperBound = \case
Open _ ub -> ub
Closed _ ub -> ub
isOpen :: Interval a -> Bool
isOpen (Open _ _) = True
isOpen (Closed _ _) = False
isClosed :: Interval a -> Bool
isClosed = not . isOpen
{-|
The Cauchy bound isolates the roots of a univariate polynomial down
to a closed interval. For example, a Cauchy bound of 15, means that
all roots are somewhere in the interval:
[-15, 15]
Returns `Nothing` if the polynomial only has a constant term.
Notice, if the polynomial is just a non-zero constant, it has no roots.
If it is zero, it's nothing but roots.
-}
cauchyBound :: (Fractional a, Ord a) => UnivariatePolynomial a -> Maybe (Interval a)
cauchyBound polynomial = do
((_, max_degree_coeff), rest_polynomial) <- Uni.viewLeadTerm polynomial
let other_coeffs = map snd $ Uni.toList rest_polynomial
let bound = 1 + maximum [ abs (coeff / max_degree_coeff) | coeff <- other_coeffs ]
return $ Closed (-bound) bound
-- | With the Sturm sequence, we can count the number of roots in an interval.
sturmSequence :: forall a. (Fractional a, Ord a) => UnivariatePolynomial a -> [UnivariatePolynomial a]
sturmSequence polynomial = takeWhile (/= 0) $ go polynomial (Uni.derivative polynomial)
where
go :: UnivariatePolynomial a -> UnivariatePolynomial a -> [UnivariatePolynomial a]
go p1 p2 = p1 : go p2 (negate $ snd $ p1 `Uni.divide` p2)
countSignChanges :: (Eq a, Num a) => [a] -> Int
countSignChanges as = count (uncurry (/=)) $ adjacentPairs signs
where signs = filter (/= 0) $ map signum as
-- | Count the roots of a univariate polynomial using Sturm sequence in a given interval.
countRootsIn :: (Eq a, Num a) => [UnivariatePolynomial a] -> Interval a -> Int
countRootsIn sturm_seq interval =
let
polynomial = head sturm_seq
start = getLowerBound interval
end = getUpperBound interval
sign_changes_start = countSignChanges $ map (Uni.eval start) sturm_seq
sign_changes_end = countSignChanges $ map (Uni.eval end) sturm_seq
root_count = sign_changes_start - sign_changes_end
in
-- With the Sturm sequence we technically count the roots in the interval
--
-- (start, end]
--
-- That is, the interval is *open* on the side of the lower bound and *closed*
-- on the side of the upper bound.
case interval of
-- So to get the root count for the closed interval [start,end] we have to add
-- one, if `start` itself is a root.
Closed _ _ ->
if Uni.eval start polynomial == 0 then
root_count + 1
else
root_count
-- For the root count of the open interval (start,end) we have to subtract one
-- if `end` is itself a root.
Open _ _ ->
if Uni.eval end polynomial == 0 then
root_count - 1
else
root_count
split :: Fractional a => Interval a -> [Interval a]
split = \case
Open start end ->
let mid = (start + end) / 2 in
[ Open start mid
, Closed mid mid
, Open mid end
]
Closed start end ->
let mid = (start + end) / 2 in
[ Closed start start
, Open start mid
, Closed mid mid
, Open mid end
, Closed end end
]
isolateRootsIn :: forall a. (Fractional a, Ord a, Show a) => Interval a -> UnivariatePolynomial a -> [Interval a]
isolateRootsIn initial_interval polynomial = go initial_interval
where
sturm_seq = sturmSequence polynomial
go :: Interval a -> [Interval a]
go interval
| root_count == 0 = []
| root_count == 1 = [ interval ]
| root_count >= 2 = concatMap go $ split interval
| otherwise = undefined
where
root_count = countRootsIn sturm_seq interval
isolateRoots :: forall a. (Fractional a, Ord a, Show a) => UnivariatePolynomial a -> [Interval a]
isolateRoots polynomial =
case cauchyBound polynomial of
Nothing -> []
Just interval -> isolateRootsIn interval polynomial