Skip to content

Commit fa060e5

Browse files
committed
Make cycle detection optional, to speed-up grounding and solving
1 parent 8499bc1 commit fa060e5

File tree

3 files changed

+35
-11
lines changed

3 files changed

+35
-11
lines changed

lib/spack/spack/solver/asp.py

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -782,6 +782,9 @@ def visit(node):
782782
self.control.load(os.path.join(parent_dir, "display.lp"))
783783
if not setup.concretize_everything:
784784
self.control.load(os.path.join(parent_dir, "when_possible.lp"))
785+
786+
if setup.use_cycle_detection:
787+
self.control.load(os.path.join(parent_dir, "cycle_detection.lp"))
785788
timer.stop("load")
786789

787790
# Grounding is the first step in the solve -- it turns our facts
@@ -904,6 +907,7 @@ def __init__(self, tests=False):
904907

905908
# If False allows for input specs that are not solved
906909
self.concretize_everything = True
910+
self.use_cycle_detection = False
907911

908912
# Set during the call to setup
909913
self.pkgs = None
@@ -2797,10 +2801,17 @@ def build_specs(self, function_tuples):
27972801
# fix flags after all specs are constructed
27982802
self.reorder_flags()
27992803

2804+
# cycle detection
2805+
try:
2806+
roots = [spec.root for spec in self._specs.values() if not spec.root.installed]
2807+
except RecursionError as e:
2808+
raise CycleDetectedError(
2809+
"detected cycles using a fast solve, falling back to slower algorithm"
2810+
) from e
2811+
28002812
# inject patches -- note that we' can't use set() to unique the
28012813
# roots here, because the specs aren't complete, and the hash
28022814
# function will loop forever.
2803-
roots = [spec.root for spec in self._specs.values() if not spec.root.installed]
28042815
roots = dict((id(r), r) for r in roots)
28052816
for root in roots.values():
28062817
spack.spec.Spec.inject_patches_variant(root)
@@ -2930,7 +2941,12 @@ def solve(self, specs, out=None, timers=False, stats=False, tests=False, setup_o
29302941
reusable_specs.extend(self._reusable_specs(specs))
29312942
setup = SpackSolverSetup(tests=tests)
29322943
output = OutputConfiguration(timers=timers, stats=stats, out=out, setup_only=setup_only)
2933-
result, _, _ = self.driver.solve(setup, specs, reuse=reusable_specs, output=output)
2944+
try:
2945+
result, _, _ = self.driver.solve(setup, specs, reuse=reusable_specs, output=output)
2946+
except CycleDetectedError as e:
2947+
warnings.warn(e)
2948+
setup.use_cycle_detection = True
2949+
result, _, _ = self.driver.solve(setup, specs, reuse=reusable_specs, output=output)
29342950
return result
29352951

29362952
def solve_in_rounds(self, specs, out=None, timers=False, stats=False, tests=False):
@@ -3010,3 +3026,7 @@ def __init__(self, provided, conflicts):
30103026
# Add attribute expected of the superclass interface
30113027
self.required = None
30123028
self.constraint_type = None
3029+
3030+
3031+
class CycleDetectedError(spack.error.SpackError):
3032+
pass

lib/spack/spack/solver/concretize.lp

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -407,15 +407,6 @@ error(10, "'{0}' is not a valid dependency for any package in the DAG", Package)
407407
:- attr("node", node(ID, Package)),
408408
not needed(node(ID, Package)).
409409

410-
% Avoid cycles in the DAG
411-
% some combinations of conditional dependencies can result in cycles;
412-
% this ensures that we solve around them
413-
path(Parent, Child) :- depends_on(Parent, Child).
414-
path(Parent, Descendant) :- path(Parent, A), depends_on(A, Descendant).
415-
error(100, "Cyclic dependency detected between '{0}' and '{1}' (consider changing variants to avoid the cycle)", A, B)
416-
:- path(A, B),
417-
path(B, A).
418-
419410
#defined dependency_type/2.
420411

421412
%-----------------------------------------------------------------------------
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
% Copyright 2013-2023 Lawrence Livermore National Security, LLC and other
2+
% Spack Project Developers. See the top-level COPYRIGHT file for details.
3+
%
4+
% SPDX-License-Identifier: (Apache-2.0 OR MIT)
5+
6+
% Avoid cycles in the DAG
7+
% some combinations of conditional dependencies can result in cycles;
8+
% this ensures that we solve around them
9+
path(Parent, Child) :- depends_on(Parent, Child).
10+
path(Parent, Descendant) :- path(Parent, A), depends_on(A, Descendant).
11+
error(100, "Cyclic dependency detected between '{0}' and '{1}' (consider changing variants to avoid the cycle)", A, B)
12+
:- path(A, B),
13+
path(B, A).

0 commit comments

Comments
 (0)