diff options
author | Massimiliano Culpo <massimiliano.culpo@gmail.com> | 2020-10-26 20:37:38 +0100 |
---|---|---|
committer | Todd Gamblin <tgamblin@llnl.gov> | 2020-11-17 10:04:13 -0800 |
commit | 1cdee03c4beae49cbc9c07ca45ec77882679717d (patch) | |
tree | 1d565e23533d127a327a33246b219c4a3c1204c3 /lib | |
parent | 2595b58503f390dab2b4a53791afcfef36e214b2 (diff) | |
download | spack-1cdee03c4beae49cbc9c07ca45ec77882679717d.tar.gz spack-1cdee03c4beae49cbc9c07ca45ec77882679717d.tar.bz2 spack-1cdee03c4beae49cbc9c07ca45ec77882679717d.tar.xz spack-1cdee03c4beae49cbc9c07ca45ec77882679717d.zip |
concretizer: add conflict rules from packages
Conflict rules from packages are added as integrity
constraints in the ASP formulation. Most of the code
to generate them has been reused from PyclingoDriver.rules
Diffstat (limited to 'lib')
-rw-r--r-- | lib/spack/spack/solver/asp.py | 80 | ||||
-rw-r--r-- | lib/spack/spack/test/concretize.py | 18 | ||||
-rw-r--r-- | lib/spack/spack/test/conftest.py | 2 |
3 files changed, 72 insertions, 28 deletions
diff --git a/lib/spack/spack/solver/asp.py b/lib/spack/spack/solver/asp.py index 4a18409533..0f4eccb358 100644 --- a/lib/spack/spack/solver/asp.py +++ b/lib/spack/spack/solver/asp.py @@ -401,6 +401,19 @@ class ClingoDriver(object): return result +def _normalize_body(body): + """Accept an AspAnd object or a single Symbol and return a list of + symbols. + """ + if isinstance(body, AspAnd): + args = [f.symbol() for f in body.args] + elif isinstance(body, clingo.Symbol): + args = [body] + else: + raise TypeError("Invalid typee for rule body: ", type(body)) + return args + + class PyclingoDriver(object): def __init__(self, cores=True, asp=None): """Driver for the Python clingo interface. @@ -438,6 +451,18 @@ class PyclingoDriver(object): def _and(self, *args): return AspAnd(*args) + def _register_rule_for_cores(self, rule_str): + # rule atoms need to be choices before we can assume them + if self.cores: + rule_sym = clingo.Function("rule", [rule_str]) + rule_atom = self.backend.add_atom(rule_sym) + self.backend.add_rule([rule_atom], [], choice=True) + self.assumptions.append(rule_atom) + rule_atoms = [rule_atom] + else: + rule_atoms = [] + return rule_atoms + def fact(self, head): """ASP fact (a rule without a body).""" sym = head.symbol() @@ -450,12 +475,7 @@ class PyclingoDriver(object): def rule(self, head, body): """ASP rule (an implication).""" - if isinstance(body, AspAnd): - args = [f.symbol() for f in body.args] - elif isinstance(body, clingo.Symbol): - args = [body] - else: - raise TypeError("Invalid typee for rule body: ", type(body)) + args = _normalize_body(body) symbols = [head.symbol()] + args atoms = {} @@ -466,15 +486,7 @@ class PyclingoDriver(object): rule_str = "%s :- %s." % ( head.symbol(), ",".join(str(a) for a in args)) - # rule atoms need to be choices before we can assume them - if self.cores: - rule_sym = clingo.Function("rule", [rule_str]) - rule_atom = self.backend.add_atom(rule_sym) - self.backend.add_rule([rule_atom], [], choice=True) - self.assumptions.append(rule_atom) - rule_atoms = [rule_atom] - else: - rule_atoms = [] + rule_atoms = self._register_rule_for_cores(rule_str) # print rule before adding self.out.write("%s\n" % rule_str) @@ -483,6 +495,18 @@ class PyclingoDriver(object): [atoms[s] for s in args] + rule_atoms ) + def integrity_constraint(self, body): + symbols, atoms = _normalize_body(body), {} + for s in symbols: + atoms[s] = self.backend.add_atom(s) + + rule_str = ":- {0}.".format(",".join(str(a) for a in symbols)) + rule_atoms = self._register_rule_for_cores(rule_str) + + # print rule before adding + self.out.write("{0}\n".format(rule_str)) + self.backend.add_rule([], [atoms[s] for s in symbols] + rule_atoms) + def one_of_iff(self, head, versions): self.out.write("%s :- %s.\n" % (head, AspOneOf(*versions))) self.out.write("%s :- %s.\n" % (AspOneOf(*versions), head)) @@ -661,6 +685,27 @@ class SpackSolverSetup(object): self.version_constraints.add((spec.name, spec.versions)) return [fn.version_satisfies(spec.name, spec.versions)] + def conflict_rules(self, pkg): + for trigger, constraints in pkg.conflicts.items(): + for constraint, _ in constraints: + constraint_body = spack.spec.Spec(pkg.name) + constraint_body.constrain(constraint) + constraint_body.constrain(trigger) + + clauses = [] + for s in constraint_body.traverse(): + clauses += self.spec_clauses(s, body=True) + + # TODO: find a better way to generate clauses for integrity + # TODO: constraints, instead of generating them for the body + # TODO: of a rule and filter unwanted functions. + to_be_filtered = [ + 'node_compiler_hard', 'node_compiler_version_satisfies' + ] + clauses = [x for x in clauses if x.name not in to_be_filtered] + + self.gen.integrity_constraint(AspAnd(*clauses)) + def available_compilers(self): """Facts about available compilers.""" @@ -750,6 +795,9 @@ class SpackSolverSetup(object): self.gen.newline() + # conflicts + self.conflict_rules(pkg) + # default compilers for this package self.package_compiler_defaults(pkg) @@ -948,7 +996,7 @@ class SpackSolverSetup(object): try: target.optimization_flags(compiler.name, compiler.version) supported.append(target) - except llnl.util.cpu.UnsupportedMicroarchitecture as e: + except llnl.util.cpu.UnsupportedMicroarchitecture: continue return sorted(supported, reverse=True) diff --git a/lib/spack/spack/test/concretize.py b/lib/spack/spack/test/concretize.py index 72f8632ad2..4b087f8ad8 100644 --- a/lib/spack/spack/test/concretize.py +++ b/lib/spack/spack/test/concretize.py @@ -10,11 +10,11 @@ import llnl.util.lang import spack.architecture import spack.concretize +import spack.error import spack.repo -from spack.concretize import find_spec, NoValidVersionError -from spack.error import SpecError, SpackError -from spack.spec import Spec, CompilerSpec, ConflictsInSpecError +from spack.concretize import find_spec +from spack.spec import Spec, CompilerSpec from spack.version import ver from spack.util.mock_package import MockPackageMultiRepo import spack.compilers @@ -495,12 +495,10 @@ class TestConcretize(object): assert s['dyninst'].satisfies('%gcc') def test_conflicts_in_spec(self, conflict_spec): - # Check that an exception is raised an caught by the appropriate - # exception types. - for exc_type in (ConflictsInSpecError, RuntimeError, SpecError): - s = Spec(conflict_spec) - with pytest.raises(exc_type): - s.concretize() + s = Spec(conflict_spec) + with pytest.raises(spack.error.SpackError): + s.concretize() + assert not s.concrete def test_no_conflixt_in_external_specs(self, conflict_spec): # clear deps because external specs cannot depend on anything @@ -608,7 +606,7 @@ class TestConcretize(object): @pytest.mark.parametrize('spec', ['noversion', 'noversion-bundle']) def test_noversion_pkg(self, spec): """Test concretization failures for no-version packages.""" - with pytest.raises(SpackError): + with pytest.raises(spack.error.SpackError): Spec(spec).concretized() @pytest.mark.parametrize('spec, best_achievable', [ diff --git a/lib/spack/spack/test/conftest.py b/lib/spack/spack/test/conftest.py index 0a9d1e6a7b..8a94f6e82a 100644 --- a/lib/spack/spack/test/conftest.py +++ b/lib/spack/spack/test/conftest.py @@ -1185,9 +1185,7 @@ def installation_dir_with_headers(tmpdir_factory): @pytest.fixture( params=[ - 'conflict%clang', 'conflict%clang+foo', - 'conflict-parent%clang', 'conflict-parent@0.9^conflict~foo' ] ) |