1 {-# LANGUAGE BangPatterns #-}
2 {-# LANGUAGE LambdaCase #-}
3 {-# LANGUAGE ScopedTypeVariables #-}
4 module Distribution
.Solver
.Modular
.Explore
(backjumpAndExplore
) where
6 import Distribution
.Solver
.Compat
.Prelude
9 import qualified Distribution
.Solver
.Types
.Progress
as P
11 import qualified Data
.List
as L
(foldl')
12 import qualified Data
.Map
.Strict
as M
13 import qualified Data
.Set
as S
15 import Distribution
.Simple
.Setup
(asBool
)
17 import Distribution
.Solver
.Modular
.Assignment
18 import Distribution
.Solver
.Modular
.Dependency
19 import Distribution
.Solver
.Modular
.Index
20 import Distribution
.Solver
.Modular
.Log
21 import Distribution
.Solver
.Modular
.Message
22 import Distribution
.Solver
.Modular
.Package
23 import qualified Distribution
.Solver
.Modular
.PSQ
as P
24 import qualified Distribution
.Solver
.Modular
.ConflictSet
as CS
25 import Distribution
.Solver
.Modular
.RetryLog
26 import Distribution
.Solver
.Modular
.Tree
27 import Distribution
.Solver
.Modular
.Version
28 import qualified Distribution
.Solver
.Modular
.WeightedPSQ
as W
29 import Distribution
.Solver
.Types
.PackagePath
30 import Distribution
.Solver
.Types
.Settings
31 (CountConflicts
(..), EnableBackjumping
(..), FineGrainedConflicts
(..))
32 import Distribution
.Types
.VersionRange
(anyVersion
)
34 -- | This function takes the variable we're currently considering, a
35 -- last conflict set and a list of children's logs. Each log yields
36 -- either a solution or a conflict set. The result is a combined log for
37 -- the parent node that has explored a prefix of the children.
39 -- We can stop traversing the children's logs if we find an individual
40 -- conflict set that does not contain the current variable. In this
41 -- case, we can just lift the conflict set to the current level,
42 -- because the current level cannot possibly have contributed to this
43 -- conflict, so no other choice at the current level would avoid the
46 -- If any of the children might contain a successful solution, we can
47 -- return it immediately. If all children contain conflict sets, we can
48 -- take the union as the combined conflict set.
50 -- The last conflict set corresponds to the justification that we
51 -- have to choose this goal at all. There is a reason why we have
52 -- introduced the goal in the first place, and this reason is in conflict
53 -- with the (virtual) option not to choose anything for the current
54 -- variable. See also the comments for 'avoidSet'.
56 -- We can also skip a child if it does not resolve any of the conflicts paired
57 -- with the current variable in the previous child's conflict set. 'backjump'
58 -- takes a function to determine whether a child can be skipped. If the child
59 -- can be skipped, the function returns a new conflict set to be merged with the
60 -- previous conflict set.
62 backjump
:: forall w k a
. Maybe Int
64 -> FineGrainedConflicts
66 -> (k
-> S
.Set CS
.Conflict
-> Maybe ConflictSet
)
67 -- ^ Function that determines whether the given choice could resolve
68 -- the given conflict. It indicates false by returning 'Just',
69 -- with the new conflicts to be added to the conflict set.
71 -> (k
-> ConflictSet
-> ExploreState
-> ConflictSetLog a
)
72 -- ^ Function that logs the given choice that was skipped.
74 -> Var QPN
-- ^ The current variable.
76 -> ConflictSet
-- ^ Conflict set representing the reason that the goal
79 -> W
.WeightedPSQ w k
(ExploreState
-> ConflictSetLog a
)
80 -- ^ List of children's logs.
82 -> ExploreState
-> ConflictSetLog a
83 backjump mbj enableBj fineGrainedConflicts couldResolveConflicts
84 logSkippedChoice var lastCS xs
=
85 foldr combine avoidGoal
[(k
, v
) |
(_
, k
, v
) <- W
.toList xs
] CS
.empty Nothing
87 combine
:: (k
, ExploreState
-> ConflictSetLog a
)
88 -> (ConflictSet
-> Maybe ConflictSet
-> ExploreState
-> ConflictSetLog a
)
89 -> ConflictSet
-> Maybe ConflictSet
-> ExploreState
-> ConflictSetLog a
90 combine
(k
, x
) f csAcc mPreviousCS es
=
91 case (asBool fineGrainedConflicts
, mPreviousCS
) of
92 (True, Just previousCS
) ->
93 case CS
.lookup var previousCS
of
95 case couldResolveConflicts k conflicts
of
96 Nothing
-> retryNoSolution
(x es
) next
97 Just newConflicts
-> skipChoice
(previousCS `CS
.union` newConflicts
)
98 _
-> skipChoice previousCS
99 _
-> retryNoSolution
(x es
) next
101 next :: ConflictSet
-> ExploreState
-> ConflictSetLog a
102 next !cs es
' = if asBool enableBj
&& not (var `CS
.member` cs
)
103 then skipLoggingBackjump cs es
'
104 else f
(csAcc `CS
.union` cs
) (Just cs
) es
'
106 -- This function is for skipping the choice when it cannot resolve any
107 -- of the previous conflicts.
108 skipChoice
:: ConflictSet
-> ConflictSetLog a
110 retryNoSolution
(logSkippedChoice k newCS es
) $ \cs
' es
' ->
111 f
(csAcc `CS
.union` cs
') (Just cs
') $
113 -- Update the conflict map with the conflict set, to make up for
114 -- skipping the whole subtree.
115 es
' { esConflictMap
= updateCM cs
' (esConflictMap es
') }
117 -- This function represents the option to not choose a value for this goal.
118 avoidGoal
:: ConflictSet
-> Maybe ConflictSet
-> ExploreState
-> ConflictSetLog a
119 avoidGoal cs _mPreviousCS
!es
=
120 logBackjump mbj
(cs `CS
.union` lastCS
) $
122 -- Use 'lastCS' below instead of 'cs' since we do not want to
123 -- double-count the additionally accumulated conflicts.
124 es
{ esConflictMap
= updateCM lastCS
(esConflictMap es
) }
126 -- The solver does not count or log backjumps at levels where the conflict
127 -- set does not contain the current variable. Otherwise, there would be many
128 -- consecutive log messages about backjumping with the same conflict set.
129 skipLoggingBackjump
:: ConflictSet
-> ExploreState
-> ConflictSetLog a
130 skipLoggingBackjump cs es
= fromProgress
$ P
.Fail
(NoSolution cs es
)
132 -- | Creates a failing ConflictSetLog representing a backjump. It inserts a
133 -- "backjumping" message, checks whether the backjump limit has been reached,
134 -- and increments the backjump count.
135 logBackjump
:: Maybe Int -> ConflictSet
-> ExploreState
-> ConflictSetLog a
136 logBackjump mbj cs es
=
137 failWith
(Failure cs Backjump
) $
138 if reachedBjLimit
(esBackjumps es
)
140 else NoSolution cs es
{ esBackjumps
= esBackjumps es
+ 1 }
142 reachedBjLimit
= case mbj
of
143 Nothing
-> const False
144 Just limit
-> (>= limit
)
146 -- | Like 'retry', except that it only applies the input function when the
147 -- backjump limit has not been reached.
148 retryNoSolution
:: ConflictSetLog a
149 -> (ConflictSet
-> ExploreState
-> ConflictSetLog a
)
151 retryNoSolution lg f
= retry lg
$ \case
152 BackjumpLimit
-> fromProgress
(P
.Fail BackjumpLimit
)
153 NoSolution cs es
-> f cs es
155 -- | The state that is read and written while exploring the search tree.
156 data ExploreState
= ES
{
157 esConflictMap
:: !ConflictMap
158 , esBackjumps
:: !Int
161 data IntermediateFailure
=
162 NoSolution ConflictSet ExploreState
165 type ConflictSetLog
= RetryLog Message IntermediateFailure
167 getBestGoal
:: ConflictMap
-> P
.PSQ
(Goal QPN
) a
-> (Goal QPN
, a
)
170 ( flip (M
.findWithDefault
0) cm
171 . (\ (Goal v _
) -> v
)
174 getFirstGoal
:: P
.PSQ
(Goal QPN
) a
-> (Goal QPN
, a
)
177 (error "getFirstGoal: empty goal choice") -- empty goal choice is an internal error
178 (\ k v _xs
-> (k
, v
)) -- commit to the first goal choice
180 updateCM
:: ConflictSet
-> ConflictMap
-> ConflictMap
182 L
.foldl' (\ cmc k
-> M
.insertWith
(+) k
1 cmc
) cm
(CS
.toList cs
)
184 -- | Record complete assignments on 'Done' nodes.
185 assign
:: Tree d c
-> Tree Assignment c
186 assign tree
= go tree
(A M
.empty M
.empty M
.empty)
188 go
:: Tree d c
-> Assignment
-> Tree Assignment c
189 go
(Fail c fr
) _
= Fail c fr
190 go
(Done rdm _
) a
= Done rdm a
191 go
(PChoice qpn rdm y ts
) (A pa fa sa
) = PChoice qpn rdm y
$ W
.mapWithKey f
(fmap go ts
)
192 where f
(POption k _
) r
= r
(A
(M
.insert qpn k pa
) fa sa
)
193 go
(FChoice qfn rdm y t m d ts
) (A pa fa sa
) = FChoice qfn rdm y t m d
$ W
.mapWithKey f
(fmap go ts
)
194 where f k r
= r
(A pa
(M
.insert qfn k fa
) sa
)
195 go
(SChoice qsn rdm y t ts
) (A pa fa sa
) = SChoice qsn rdm y t
$ W
.mapWithKey f
(fmap go ts
)
196 where f k r
= r
(A pa fa
(M
.insert qsn k sa
))
197 go
(GoalChoice rdm ts
) a
= GoalChoice rdm
$ fmap ($ a
) (fmap go ts
)
199 -- | A tree traversal that simultaneously propagates conflict sets up
200 -- the tree from the leaves and creates a log.
201 exploreLog
:: Maybe Int
203 -> FineGrainedConflicts
206 -> Tree Assignment QGoalReason
207 -> ConflictSetLog
(Assignment
, RevDepMap
)
208 exploreLog mbj enableBj fineGrainedConflicts
(CountConflicts countConflicts
) idx t
=
211 getBestGoal
' :: P
.PSQ
(Goal QPN
) a
-> ConflictMap
-> (Goal QPN
, a
)
213 | asBool countConflicts
= \ ts cm
-> getBestGoal cm ts
214 |
otherwise = \ ts _
-> getFirstGoal ts
216 go
:: TreeF Assignment QGoalReason
217 (ExploreState
-> ConflictSetLog
(Assignment
, RevDepMap
), Tree Assignment QGoalReason
)
218 -> (ExploreState
-> ConflictSetLog
(Assignment
, RevDepMap
))
219 go
(FailF c fr
) = \ !es
->
220 let es
' = es
{ esConflictMap
= updateCM c
(esConflictMap es
) }
221 in failWith
(Failure c fr
) (NoSolution c es
')
222 go
(DoneF rdm a
) = \ _
-> succeedWith Success
(a
, rdm
)
223 go
(PChoiceF qpn _ gr ts
) =
224 backjump mbj enableBj fineGrainedConflicts
225 (couldResolveConflicts qpn
)
226 (logSkippedPackage qpn
)
227 (P qpn
) (avoidSet
(P qpn
) gr
) $ -- try children in order,
228 W
.mapWithKey
-- when descending ...
229 (\ k r es
-> tryWith
(TryP qpn k
) (r es
))
231 go
(FChoiceF qfn _ gr _ _ _ ts
) =
232 backjump mbj enableBj fineGrainedConflicts
234 (const logSkippedChoiceSimple
)
235 (F qfn
) (avoidSet
(F qfn
) gr
) $ -- try children in order,
236 W
.mapWithKey
-- when descending ...
237 (\ k r es
-> tryWith
(TryF qfn k
) (r es
))
239 go
(SChoiceF qsn _ gr _ ts
) =
240 backjump mbj enableBj fineGrainedConflicts
242 (const logSkippedChoiceSimple
)
243 (S qsn
) (avoidSet
(S qsn
) gr
) $ -- try children in order,
244 W
.mapWithKey
-- when descending ...
245 (\ k r es
-> tryWith
(TryS qsn k
) (r es
))
247 go
(GoalChoiceF _ ts
) = \ es
->
248 let (k
, (v
, tree
)) = getBestGoal
' ts
(esConflictMap es
)
249 in continueWith
(Next k
) $
250 -- Goal choice nodes are normally not counted as backjumps, since the
251 -- solver always explores exactly one choice, which means that the
252 -- backjump from the goal choice would be redundant with the backjump
253 -- from the PChoice, FChoice, or SChoice below. The one case where the
254 -- backjump is not redundant is when the chosen goal is a failure node,
255 -- so we log a backjump in that case.
257 Fail _ _
-> retryNoSolution
(v es
) $ logBackjump mbj
261 esConflictMap
= M
.empty
265 -- Is it possible for this package instance (QPN and POption) to resolve any
266 -- of the conflicts that were caused by the previous instance? The default
267 -- is true, because it is always safe to explore a package instance.
268 -- Skipping it is an optimization. If false, it returns a new conflict set
269 -- to be merged with the previous one.
270 couldResolveConflicts
:: QPN
-> POption
-> S
.Set CS
.Conflict
-> Maybe ConflictSet
271 couldResolveConflicts currentQPN
@(Q _ pn
) (POption i
@(I v _
) _
) conflicts
=
272 let (PInfo deps _ _ _
) = idx M
.! pn M
.! i
273 qdeps
= qualifyDeps
(defaultQualifyOptions idx
) currentQPN deps
275 couldBeResolved
:: CS
.Conflict
-> Maybe ConflictSet
276 couldBeResolved CS
.OtherConflict
= Nothing
277 couldBeResolved
(CS
.GoalConflict conflictingDep
) =
278 -- Check whether this package instance also has 'conflictingDep'
279 -- as a dependency (ignoring flag and stanza choices).
280 if null [() | Simple
(LDep _
(Dep
(PkgComponent qpn _
) _
)) _
<- qdeps
, qpn
== conflictingDep
]
283 couldBeResolved
(CS
.VersionConstraintConflict dep excludedVersion
) =
284 -- Check whether this package instance also excludes version
285 -- 'excludedVersion' of 'dep' (ignoring flag and stanza choices).
286 let vrs
= [vr | Simple
(LDep _
(Dep
(PkgComponent qpn _
) (Constrained vr
))) _
<- qdeps
, qpn
== dep
]
287 vrIntersection
= L
.foldl' (.&&.) anyVersion vrs
288 in if checkVR vrIntersection excludedVersion
290 else -- If we skip this package instance, we need to update the
291 -- conflict set to say that 'dep' was also excluded by
292 -- this package instance's constraint.
293 Just
$ CS
.singletonWithConflict
(P dep
) $
294 CS
.VersionConflict currentQPN
(CS
.OrderedVersionRange vrIntersection
)
295 couldBeResolved
(CS
.VersionConflict reverseDep
(CS
.OrderedVersionRange excludingVR
)) =
296 -- Check whether this package instance's version is also excluded
298 if checkVR excludingVR v
300 else -- If we skip this version, we need to update the conflict
301 -- set to say that the reverse dependency also excluded this
303 Just
$ CS
.singletonWithConflict
(P reverseDep
) (CS
.VersionConstraintConflict currentQPN v
)
304 in fmap CS
.unions
$ traverse couldBeResolved
(S
.toList conflicts
)
306 logSkippedPackage
:: QPN
-> POption
-> ConflictSet
-> ExploreState
-> ConflictSetLog a
307 logSkippedPackage qpn pOption cs es
=
308 tryWith
(TryP qpn pOption
) $
309 failWith
(Skip
(fromMaybe S
.empty $ CS
.lookup (P qpn
) cs
)) $
312 -- This function is used for flag and stanza choices, but it should not be
313 -- called, because there is currently no way to skip a value for a flag or
315 logSkippedChoiceSimple
:: ConflictSet
-> ExploreState
-> ConflictSetLog a
316 logSkippedChoiceSimple cs es
= fromProgress
$ P
.Fail
$ NoSolution cs es
318 -- | Build a conflict set corresponding to the (virtual) option not to
319 -- choose a solution for a goal at all.
321 -- In the solver, the set of goals is not statically determined, but depends
322 -- on the choices we make. Therefore, when dealing with conflict sets, we
323 -- always have to consider that we could perhaps make choices that would
324 -- avoid the existence of the goal completely.
326 -- Whenever we actually introduce a choice in the tree, we have already established
327 -- that the goal cannot be avoided. This is tracked in the "goal reason".
328 -- The choice to avoid the goal therefore is a conflict between the goal itself
329 -- and its goal reason. We build this set here, and pass it to the 'backjump'
330 -- function as the last conflict set.
332 -- This has two effects:
334 -- - In a situation where there are no choices available at all (this happens
335 -- if an unknown package is requested), the last conflict set becomes the
336 -- actual conflict set.
338 -- - In a situation where all of the children's conflict sets contain the
339 -- current variable, the goal reason of the current node will be added to the
342 avoidSet
:: Var QPN
-> QGoalReason
-> ConflictSet
343 avoidSet var
@(P qpn
) gr
=
344 CS
.union (CS
.singleton var
) (goalReasonToConflictSetWithConflict qpn gr
)
346 CS
.union (CS
.singleton var
) (goalReasonToConflictSet gr
)
350 -- Takes as an argument a limit on allowed backjumps. If the limit is 'Nothing',
351 -- then infinitely many backjumps are allowed. If the limit is 'Just 0',
352 -- backtracking is completely disabled.
353 backjumpAndExplore
:: Maybe Int
355 -> FineGrainedConflicts
358 -> Tree d QGoalReason
359 -> RetryLog Message SolverFailure
(Assignment
, RevDepMap
)
360 backjumpAndExplore mbj enableBj fineGrainedConflicts countConflicts idx
=
361 mapFailure convertFailure
362 . exploreLog mbj enableBj fineGrainedConflicts countConflicts idx
365 convertFailure
(NoSolution cs es
) = ExhaustiveSearch cs
(esConflictMap es
)
366 convertFailure BackjumpLimit
= BackjumpLimitReached