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) specs = spack.cmd.parse_specs(args.specs)
# dump generated ASP program # set up solver parameters
result = asp.solve( solver = asp.Solver()
specs, dump=dump, models=models, timers=args.timers, stats=args.stats, solver.reuse = args.reuse
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: if 'solutions' not in dump:
return return

View File

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

View File

@ -529,7 +529,7 @@ def fact(self, head, assumption=False):
def solve( def solve(
self, solver_setup, specs, dump=None, nmodels=0, 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() timer = spack.util.timer.Timer()
@ -545,7 +545,7 @@ def solve(
self.assumptions = [] self.assumptions = []
with self.control.backend() as backend: with self.control.backend() as backend:
self.backend = backend self.backend = backend
solver_setup.setup(self, specs, tests=tests, reuse=reuse) solver_setup.setup(self, specs)
timer.phase("setup") timer.phase("setup")
# read in the main ASP program and display logic -- these are # read in the main ASP program and display logic -- these are
@ -640,7 +640,7 @@ def stringify(x):
class SpackSolverSetup(object): class SpackSolverSetup(object):
"""Class to set up and run a Spack concretization solve.""" """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.gen = None # set by setup()
self.declared_versions = {} self.declared_versions = {}
@ -665,6 +665,12 @@ def __init__(self):
# Caches to optimize the setup phase of the solver # Caches to optimize the setup phase of the solver
self.target_specs_cache = None 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): def pkg_version_rules(self, pkg):
"""Output declared versions of a package. """Output declared versions of a package.
@ -866,7 +872,7 @@ def pkg_rules(self, pkg, tests):
self.package_provider_rules(pkg) self.package_provider_rules(pkg)
# dependencies # dependencies
self.package_dependencies_rules(pkg, tests) self.package_dependencies_rules(pkg)
# virtual preferences # virtual preferences
self.virtual_preferences( self.virtual_preferences(
@ -932,17 +938,17 @@ def package_provider_rules(self, pkg):
)) ))
self.gen.newline() self.gen.newline()
def package_dependencies_rules(self, pkg, tests): def package_dependencies_rules(self, pkg):
"""Translate 'depends_on' directives into ASP logic.""" """Translate 'depends_on' directives into ASP logic."""
for _, conditions in sorted(pkg.dependencies.items()): for _, conditions in sorted(pkg.dependencies.items()):
for cond, dep in sorted(conditions.items()): for cond, dep in sorted(conditions.items()):
deptypes = dep.type.copy() deptypes = dep.type.copy()
# Skip test dependencies if they're not requested # Skip test dependencies if they're not requested
if not tests: if not self.tests:
deptypes.discard("test") deptypes.discard("test")
# ... or if they are requested only for certain packages # ... 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") deptypes.discard("test")
# if there are no dependency types to be considered # 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. # TODO: (or any mirror really) doesn't have binaries.
pass pass
def setup(self, driver, specs, tests=False, reuse=False): def setup(self, driver, specs):
"""Generate an ASP program with relevant constraints for specs. """Generate an ASP program with relevant constraints for specs.
This calls methods on the solve driver to set up the problem with 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.gen.h1("Concrete input spec definitions")
self.define_concrete_input_specs(specs, possible) self.define_concrete_input_specs(specs, possible)
if reuse: if self.reuse:
self.gen.h1("Installed packages") self.gen.h1("Installed packages")
self.gen.fact(fn.optimize_for_reuse()) self.gen.fact(fn.optimize_for_reuse())
self.gen.newline() self.gen.newline()
@ -1713,7 +1719,7 @@ def setup(self, driver, specs, tests=False, reuse=False):
self.gen.h1('Package Constraints') self.gen.h1('Package Constraints')
for pkg in sorted(pkgs): for pkg in sorted(pkgs):
self.gen.h2('Package rules: %s' % pkg) 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.gen.h2('Package preferences: %s' % pkg)
self.preferred_variants(pkg) self.preferred_variants(pkg)
self.preferred_targets(pkg) self.preferred_targets(pkg)
@ -2016,20 +2022,54 @@ def _develop_specs_from_env(spec, env):
spec.constrain(dev_info['spec']) spec.constrain(dev_info['spec'])
# class Solver(object):
# These are handwritten parts for the Spack ASP model. """This is the main external interface class for solving.
#
def solve(specs, dump=(), models=0, timers=False, stats=False, tests=False, It manages solver configuration and preferences in once place. It sets up the solve
reuse=False): and passes the setup method to the driver, as well.
"""Solve for a stable model of specs.
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)
""" """
def __init__(self):
self.set_default_configuration()
def set_default_configuration(self):
self.reuse = False
self.dump = ()
self.models = 0
self.timers = False
self.stats = False
self.tests = False
def solve(self, specs):
driver = PyclingoDriver() driver = PyclingoDriver()
if "asp" in dump: if "asp" in self.dump:
driver.out = sys.stdout driver.out = sys.stdout
# Check upfront that the variants are admissible # Check upfront that the variants are admissible
@ -2039,9 +2079,9 @@ def solve(specs, dump=(), models=0, timers=False, stats=False, tests=False,
continue continue
spack.spec.Spec.ensure_valid_variants(s) spack.spec.Spec.ensure_valid_variants(s)
setup = SpackSolverSetup() setup = SpackSolverSetup(reuse=self.reuse, tests=self.tests)
return driver.solve( return driver.solve(
setup, specs, dump, models, timers, stats, tests, reuse setup, specs, self.dump, self.models, self.timers, self.stats
) )

View File

@ -2615,7 +2615,11 @@ def _new_concretize(self, tests=False, reuse=False):
if self._concrete: if self._concrete:
return 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() result.raise_if_unsat()
# take the best answer # take the best answer