refactor: convert spack.solver.asp.solve() to a class

The solver has a lot of configuration associated with it. Rather
than adding arguments to everything, we should encapsulate that
in a class. This is the start of that work; it replaces `solve()`
and its kwargs with a class and properties.
This commit is contained in:
Todd Gamblin 2022-01-16 22:40:48 -08:00 committed by Greg Becker
parent 87a3b72ef0
commit 1903e45eec
4 changed files with 93 additions and 44 deletions

View File

@ -102,11 +102,15 @@ def solve(parser, args):
specs = spack.cmd.parse_specs(args.specs)
# dump generated ASP program
result = asp.solve(
specs, dump=dump, models=models, timers=args.timers, stats=args.stats,
reuse=args.reuse,
)
# set up solver parameters
solver = asp.Solver()
solver.reuse = args.reuse
solver.dump = dump
solver.models = models
solver.timers = args.timers
solver.stats = args.stats
result = solver.solve(specs)
if 'solutions' not in dump:
return

View File

@ -748,11 +748,12 @@ def concretize_specs_together(*abstract_specs, **kwargs):
def _concretize_specs_together_new(*abstract_specs, **kwargs):
import spack.solver.asp
concretization_kwargs = {
'tests': kwargs.get('tests', False),
'reuse': kwargs.get('reuse', False)
}
result = spack.solver.asp.solve(abstract_specs, **concretization_kwargs)
solver = spack.solver.asp.Solver()
solver.tests = kwargs.get('tests', False)
solver.reuse = kwargs.get('reuse', False)
result = solver.solve(abstract_specs)
result.raise_if_unsat()
return [s.copy() for s in result.specs]

View File

@ -529,7 +529,7 @@ def fact(self, head, assumption=False):
def solve(
self, solver_setup, specs, dump=None, nmodels=0,
timers=False, stats=False, tests=False, reuse=False,
timers=False, stats=False,
):
timer = spack.util.timer.Timer()
@ -545,7 +545,7 @@ def solve(
self.assumptions = []
with self.control.backend() as backend:
self.backend = backend
solver_setup.setup(self, specs, tests=tests, reuse=reuse)
solver_setup.setup(self, specs)
timer.phase("setup")
# read in the main ASP program and display logic -- these are
@ -640,7 +640,7 @@ def stringify(x):
class SpackSolverSetup(object):
"""Class to set up and run a Spack concretization solve."""
def __init__(self):
def __init__(self, reuse=False, tests=False):
self.gen = None # set by setup()
self.declared_versions = {}
@ -665,6 +665,12 @@ def __init__(self):
# Caches to optimize the setup phase of the solver
self.target_specs_cache = None
# whether to add installed/binary hashes to the solve
self.reuse = reuse
# whether to add installed/binary hashes to the solve
self.tests = tests
def pkg_version_rules(self, pkg):
"""Output declared versions of a package.
@ -866,7 +872,7 @@ def pkg_rules(self, pkg, tests):
self.package_provider_rules(pkg)
# dependencies
self.package_dependencies_rules(pkg, tests)
self.package_dependencies_rules(pkg)
# virtual preferences
self.virtual_preferences(
@ -932,17 +938,17 @@ def package_provider_rules(self, pkg):
))
self.gen.newline()
def package_dependencies_rules(self, pkg, tests):
def package_dependencies_rules(self, pkg):
"""Translate 'depends_on' directives into ASP logic."""
for _, conditions in sorted(pkg.dependencies.items()):
for cond, dep in sorted(conditions.items()):
deptypes = dep.type.copy()
# Skip test dependencies if they're not requested
if not tests:
if not self.tests:
deptypes.discard("test")
# ... or if they are requested only for certain packages
if not isinstance(tests, bool) and pkg.name not in tests:
if not isinstance(self.tests, bool) and pkg.name not in self.tests:
deptypes.discard("test")
# if there are no dependency types to be considered
@ -1642,7 +1648,7 @@ def define_installed_packages(self, specs, possible):
# TODO: (or any mirror really) doesn't have binaries.
pass
def setup(self, driver, specs, tests=False, reuse=False):
def setup(self, driver, specs):
"""Generate an ASP program with relevant constraints for specs.
This calls methods on the solve driver to set up the problem with
@ -1689,7 +1695,7 @@ def setup(self, driver, specs, tests=False, reuse=False):
self.gen.h1("Concrete input spec definitions")
self.define_concrete_input_specs(specs, possible)
if reuse:
if self.reuse:
self.gen.h1("Installed packages")
self.gen.fact(fn.optimize_for_reuse())
self.gen.newline()
@ -1713,7 +1719,7 @@ def setup(self, driver, specs, tests=False, reuse=False):
self.gen.h1('Package Constraints')
for pkg in sorted(pkgs):
self.gen.h2('Package rules: %s' % pkg)
self.pkg_rules(pkg, tests=tests)
self.pkg_rules(pkg, tests=self.tests)
self.gen.h2('Package preferences: %s' % pkg)
self.preferred_variants(pkg)
self.preferred_targets(pkg)
@ -2016,33 +2022,67 @@ def _develop_specs_from_env(spec, env):
spec.constrain(dev_info['spec'])
#
# These are handwritten parts for the Spack ASP model.
#
def solve(specs, dump=(), models=0, timers=False, stats=False, tests=False,
reuse=False):
"""Solve for a stable model of specs.
class Solver(object):
"""This is the main external interface class for solving.
It manages solver configuration and preferences in once place. It sets up the solve
and passes the setup method to the driver, as well.
Properties of interest:
``reuse (bool)``
Whether to try to reuse existing installs/binaries
``show (tuple)``
What information to print to the console while running. Options are:
* asp: asp program text
* opt: optimization criteria for best model
* output: raw clingo output
* solutions: models found by asp program
* all: all of the above
``models (int)``
Number of models to search (default: 0 for unlimited)
``timers (bool)``
Print out coarse fimers for different solve phases.
``stats (bool)``
Print out detailed stats from clingo
``tests (bool or tuple)``
If ``True``, concretize test dependencies for all packages. If
a tuple of package names, concretize test dependencies for named
packages. If ``False``, do not concretize test dependencies.
Arguments:
specs (list): list of Specs to solve.
dump (tuple): what to dump
models (int): number of models to search (default: 0)
"""
driver = PyclingoDriver()
if "asp" in dump:
driver.out = sys.stdout
def __init__(self):
self.set_default_configuration()
# Check upfront that the variants are admissible
for root in specs:
for s in root.traverse():
if s.virtual:
continue
spack.spec.Spec.ensure_valid_variants(s)
def set_default_configuration(self):
self.reuse = False
self.dump = ()
self.models = 0
self.timers = False
self.stats = False
self.tests = False
setup = SpackSolverSetup()
return driver.solve(
setup, specs, dump, models, timers, stats, tests, reuse
)
def solve(self, specs):
driver = PyclingoDriver()
if "asp" in self.dump:
driver.out = sys.stdout
# Check upfront that the variants are admissible
for root in specs:
for s in root.traverse():
if s.virtual:
continue
spack.spec.Spec.ensure_valid_variants(s)
setup = SpackSolverSetup(reuse=self.reuse, tests=self.tests)
return driver.solve(
setup, specs, self.dump, self.models, self.timers, self.stats
)
class UnsatisfiableSpecError(spack.error.UnsatisfiableSpecError):

View File

@ -2615,7 +2615,11 @@ def _new_concretize(self, tests=False, reuse=False):
if self._concrete:
return
result = spack.solver.asp.solve([self], tests=tests, reuse=reuse)
solver = spack.solver.asp.Solver()
solver.reuse = reuse
solver.tests = tests
result = solver.solve([self])
result.raise_if_unsat()
# take the best answer