asp: refactor low level API to permit the injection of configuration
This allows writing extension commands that can benchmark different configurations in clingo, or try different configurations for a single test.
This commit is contained in:
parent
e90ea79347
commit
9a48035e49
@ -200,6 +200,7 @@ def setup(sphinx):
|
|||||||
("py:class", "_io.BufferedReader"),
|
("py:class", "_io.BufferedReader"),
|
||||||
("py:class", "unittest.case.TestCase"),
|
("py:class", "unittest.case.TestCase"),
|
||||||
("py:class", "_frozen_importlib_external.SourceFileLoader"),
|
("py:class", "_frozen_importlib_external.SourceFileLoader"),
|
||||||
|
("py:class", "clingo.Control"),
|
||||||
# Spack classes that are private and we don't want to expose
|
# Spack classes that are private and we don't want to expose
|
||||||
("py:class", "spack.provider_index._IndexBase"),
|
("py:class", "spack.provider_index._IndexBase"),
|
||||||
("py:class", "spack.repo._PrependFileLoader"),
|
("py:class", "spack.repo._PrependFileLoader"),
|
||||||
|
@ -42,13 +42,6 @@ def setup_parser(subparser):
|
|||||||
" solutions models found by asp program\n"
|
" solutions models found by asp program\n"
|
||||||
" all all of the above",
|
" all all of the above",
|
||||||
)
|
)
|
||||||
subparser.add_argument(
|
|
||||||
"--models",
|
|
||||||
action="store",
|
|
||||||
type=int,
|
|
||||||
default=0,
|
|
||||||
help="number of solutions to search (default 0 for all)",
|
|
||||||
)
|
|
||||||
|
|
||||||
# Below are arguments w.r.t. spec display (like spack spec)
|
# Below are arguments w.r.t. spec display (like spack spec)
|
||||||
arguments.add_common_arguments(subparser, ["long", "very_long", "install_status"])
|
arguments.add_common_arguments(subparser, ["long", "very_long", "install_status"])
|
||||||
@ -170,10 +163,6 @@ def solve(parser, args):
|
|||||||
% (d, ", ".join(show_options + ("all",)))
|
% (d, ", ".join(show_options + ("all",)))
|
||||||
)
|
)
|
||||||
|
|
||||||
models = args.models
|
|
||||||
if models < 0:
|
|
||||||
tty.die("model count must be non-negative: %d")
|
|
||||||
|
|
||||||
# Format required for the output (JSON, YAML or None)
|
# Format required for the output (JSON, YAML or None)
|
||||||
required_format = args.format
|
required_format = args.format
|
||||||
|
|
||||||
@ -195,7 +184,6 @@ def solve(parser, args):
|
|||||||
result = solver.solve(
|
result = solver.solve(
|
||||||
specs,
|
specs,
|
||||||
out=output,
|
out=output,
|
||||||
models=models,
|
|
||||||
timers=args.timers,
|
timers=args.timers,
|
||||||
stats=args.stats,
|
stats=args.stats,
|
||||||
setup_only=setup_only,
|
setup_only=setup_only,
|
||||||
@ -204,9 +192,7 @@ def solve(parser, args):
|
|||||||
_process_result(result, show, required_format, kwargs)
|
_process_result(result, show, required_format, kwargs)
|
||||||
else:
|
else:
|
||||||
for idx, result in enumerate(
|
for idx, result in enumerate(
|
||||||
solver.solve_in_rounds(
|
solver.solve_in_rounds(specs, out=output, timers=args.timers, stats=args.stats)
|
||||||
specs, out=output, models=models, timers=args.timers, stats=args.stats
|
|
||||||
)
|
|
||||||
):
|
):
|
||||||
if "solutions" in show:
|
if "solutions" in show:
|
||||||
tty.msg("ROUND {0}".format(idx))
|
tty.msg("ROUND {0}".format(idx))
|
||||||
|
@ -56,6 +56,35 @@
|
|||||||
parse_files = None
|
parse_files = None
|
||||||
|
|
||||||
|
|
||||||
|
#: Data class that contain configuration on what a
|
||||||
|
#: clingo solve should output.
|
||||||
|
#:
|
||||||
|
#: Args:
|
||||||
|
#: timers (bool): Print out coarse timers for different solve phases.
|
||||||
|
#: stats (bool): Whether to output Clingo's internal solver statistics.
|
||||||
|
#: out: Optional output stream for the generated ASP program.
|
||||||
|
#: setup_only (bool): if True, stop after setup and don't solve (default False).
|
||||||
|
OutputConfiguration = collections.namedtuple(
|
||||||
|
"OutputConfiguration", ["timers", "stats", "out", "setup_only"]
|
||||||
|
)
|
||||||
|
|
||||||
|
#: Default output configuration for a solve
|
||||||
|
DEFAULT_OUTPUT_CONFIGURATION = OutputConfiguration(
|
||||||
|
timers=False, stats=False, out=None, setup_only=False
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def default_clingo_control():
|
||||||
|
"""Return a control object with the default settings used in Spack"""
|
||||||
|
control = clingo.Control()
|
||||||
|
control.configuration.configuration = "tweety"
|
||||||
|
control.configuration.solve.models = 0
|
||||||
|
control.configuration.solver.heuristic = "Domain"
|
||||||
|
control.configuration.solve.parallel_mode = "1"
|
||||||
|
control.configuration.solver.opt_strategy = "usc,one"
|
||||||
|
return control
|
||||||
|
|
||||||
|
|
||||||
# backward compatibility functions for clingo ASTs
|
# backward compatibility functions for clingo ASTs
|
||||||
def ast_getter(*names):
|
def ast_getter(*names):
|
||||||
def getter(node):
|
def getter(node):
|
||||||
@ -520,6 +549,12 @@ def __init__(self, cores=True):
|
|||||||
self.out = llnl.util.lang.Devnull()
|
self.out = llnl.util.lang.Devnull()
|
||||||
self.cores = cores
|
self.cores = cores
|
||||||
|
|
||||||
|
# These attributes are part of the object, but will be reset
|
||||||
|
# at each call to solve
|
||||||
|
self.control = None
|
||||||
|
self.backend = None
|
||||||
|
self.assumptions = None
|
||||||
|
|
||||||
def title(self, name, char):
|
def title(self, name, char):
|
||||||
self.out.write("\n")
|
self.out.write("\n")
|
||||||
self.out.write("%" + (char * 76))
|
self.out.write("%" + (char * 76))
|
||||||
@ -556,43 +591,31 @@ def fact(self, head):
|
|||||||
if choice:
|
if choice:
|
||||||
self.assumptions.append(atom)
|
self.assumptions.append(atom)
|
||||||
|
|
||||||
def solve(
|
def solve(self, setup, specs, reuse=None, output=None, control=None):
|
||||||
self,
|
|
||||||
setup,
|
|
||||||
specs,
|
|
||||||
nmodels=0,
|
|
||||||
reuse=None,
|
|
||||||
timers=False,
|
|
||||||
stats=False,
|
|
||||||
out=None,
|
|
||||||
setup_only=False,
|
|
||||||
):
|
|
||||||
"""Set up the input and solve for dependencies of ``specs``.
|
"""Set up the input and solve for dependencies of ``specs``.
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
setup (SpackSolverSetup): An object to set up the ASP problem.
|
setup (SpackSolverSetup): An object to set up the ASP problem.
|
||||||
specs (list): List of ``Spec`` objects to solve for.
|
specs (list): List of ``Spec`` objects to solve for.
|
||||||
nmodels (int): Number of models to consider (default 0 for unlimited).
|
reuse (None or list): list of concrete specs that can be reused
|
||||||
reuse (None or list): list of concrete specs that can be reused
|
output (None or OutputConfiguration): configuration object to set
|
||||||
timers (bool): Print out coarse timers for different solve phases.
|
the output of this solve.
|
||||||
stats (bool): Whether to output Clingo's internal solver statistics.
|
control (clingo.Control): configuration for the solver. If None,
|
||||||
out: Optional output stream for the generated ASP program.
|
default values will be used
|
||||||
setup_only (bool): if True, stop after setup and don't solve (default False).
|
|
||||||
|
Return:
|
||||||
|
A tuple of the solve result, the timer for the different phases of the
|
||||||
|
solve, and the internal statistics from clingo.
|
||||||
"""
|
"""
|
||||||
|
output = output or DEFAULT_OUTPUT_CONFIGURATION
|
||||||
# allow solve method to override the output stream
|
# allow solve method to override the output stream
|
||||||
if out is not None:
|
if output.out is not None:
|
||||||
self.out = out
|
self.out = output.out
|
||||||
|
|
||||||
timer = spack.util.timer.Timer()
|
timer = spack.util.timer.Timer()
|
||||||
|
|
||||||
# Initialize the control object for the solver
|
# Initialize the control object for the solver
|
||||||
self.control = clingo.Control()
|
self.control = control or default_clingo_control()
|
||||||
self.control.configuration.configuration = "tweety"
|
|
||||||
self.control.configuration.solve.models = nmodels
|
|
||||||
self.control.configuration.solver.heuristic = "Domain"
|
|
||||||
self.control.configuration.solve.parallel_mode = "1"
|
|
||||||
self.control.configuration.solver.opt_strategy = "usc,one"
|
|
||||||
|
|
||||||
# set up the problem -- this generates facts and rules
|
# set up the problem -- this generates facts and rules
|
||||||
self.assumptions = []
|
self.assumptions = []
|
||||||
with self.control.backend() as backend:
|
with self.control.backend() as backend:
|
||||||
@ -622,8 +645,8 @@ def visit(node):
|
|||||||
parse_files([path], visit)
|
parse_files([path], visit)
|
||||||
|
|
||||||
# If we're only doing setup, just return an empty solve result
|
# If we're only doing setup, just return an empty solve result
|
||||||
if setup_only:
|
if output.setup_only:
|
||||||
return Result(specs)
|
return Result(specs), None, None
|
||||||
|
|
||||||
# Load the file itself
|
# Load the file itself
|
||||||
self.control.load(os.path.join(parent_dir, "concretize.lp"))
|
self.control.load(os.path.join(parent_dir, "concretize.lp"))
|
||||||
@ -682,18 +705,21 @@ def stringify(x):
|
|||||||
# record the number of models the solver considered
|
# record the number of models the solver considered
|
||||||
result.nmodels = len(models)
|
result.nmodels = len(models)
|
||||||
|
|
||||||
|
# record the possible dependencies in the solve
|
||||||
|
result.possible_dependencies = setup.pkgs
|
||||||
|
|
||||||
elif cores:
|
elif cores:
|
||||||
result.control = self.control
|
result.control = self.control
|
||||||
result.cores.extend(cores)
|
result.cores.extend(cores)
|
||||||
|
|
||||||
if timers:
|
if output.timers:
|
||||||
timer.write_tty()
|
timer.write_tty()
|
||||||
print()
|
print()
|
||||||
if stats:
|
if output.stats:
|
||||||
print("Statistics:")
|
print("Statistics:")
|
||||||
pprint.pprint(self.control.statistics)
|
pprint.pprint(self.control.statistics)
|
||||||
|
|
||||||
return result
|
return result, timer, self.control.statistics
|
||||||
|
|
||||||
|
|
||||||
class SpackSolverSetup(object):
|
class SpackSolverSetup(object):
|
||||||
@ -732,6 +758,9 @@ def __init__(self, tests=False):
|
|||||||
# If False allows for input specs that are not solved
|
# If False allows for input specs that are not solved
|
||||||
self.concretize_everything = True
|
self.concretize_everything = True
|
||||||
|
|
||||||
|
# Set during the call to setup
|
||||||
|
self.pkgs = None
|
||||||
|
|
||||||
def pkg_version_rules(self, pkg):
|
def pkg_version_rules(self, pkg):
|
||||||
"""Output declared versions of a package.
|
"""Output declared versions of a package.
|
||||||
|
|
||||||
@ -964,7 +993,8 @@ def pkg_rules(self, pkg, tests):
|
|||||||
|
|
||||||
# virtual preferences
|
# virtual preferences
|
||||||
self.virtual_preferences(
|
self.virtual_preferences(
|
||||||
pkg.name, lambda v, p, i: self.gen.fact(fn.pkg_provider_preference(pkg.name, v, p, i))
|
pkg.name,
|
||||||
|
lambda v, p, i: self.gen.fact(fn.pkg_provider_preference(pkg.name, v, p, i)),
|
||||||
)
|
)
|
||||||
|
|
||||||
def condition(self, required_spec, imposed_spec=None, name=None, msg=None):
|
def condition(self, required_spec, imposed_spec=None, name=None, msg=None):
|
||||||
@ -1063,7 +1093,8 @@ def provider_defaults(self):
|
|||||||
self.gen.h2("Default virtual providers")
|
self.gen.h2("Default virtual providers")
|
||||||
assert self.possible_virtuals is not None
|
assert self.possible_virtuals is not None
|
||||||
self.virtual_preferences(
|
self.virtual_preferences(
|
||||||
"all", lambda v, p, i: self.gen.fact(fn.default_provider_preference(v, p, i))
|
"all",
|
||||||
|
lambda v, p, i: self.gen.fact(fn.default_provider_preference(v, p, i)),
|
||||||
)
|
)
|
||||||
|
|
||||||
def external_packages(self):
|
def external_packages(self):
|
||||||
@ -1798,7 +1829,7 @@ def setup(self, driver, specs, reuse=None):
|
|||||||
if missing_deps:
|
if missing_deps:
|
||||||
raise spack.spec.InvalidDependencyError(spec.name, missing_deps)
|
raise spack.spec.InvalidDependencyError(spec.name, missing_deps)
|
||||||
|
|
||||||
pkgs = set(possible)
|
self.pkgs = set(possible)
|
||||||
|
|
||||||
# driver is used by all the functions below to add facts and
|
# driver is used by all the functions below to add facts and
|
||||||
# rules to generate an ASP program.
|
# rules to generate an ASP program.
|
||||||
@ -1835,7 +1866,7 @@ def setup(self, driver, specs, reuse=None):
|
|||||||
self.flag_defaults()
|
self.flag_defaults()
|
||||||
|
|
||||||
self.gen.h1("Package Constraints")
|
self.gen.h1("Package Constraints")
|
||||||
for pkg in sorted(pkgs):
|
for pkg in sorted(self.pkgs):
|
||||||
self.gen.h2("Package rules: %s" % pkg)
|
self.gen.h2("Package rules: %s" % pkg)
|
||||||
self.pkg_rules(pkg, tests=self.tests)
|
self.pkg_rules(pkg, tests=self.tests)
|
||||||
self.gen.h2("Package preferences: %s" % pkg)
|
self.gen.h2("Package preferences: %s" % pkg)
|
||||||
@ -2215,7 +2246,6 @@ def solve(
|
|||||||
self,
|
self,
|
||||||
specs,
|
specs,
|
||||||
out=None,
|
out=None,
|
||||||
models=0,
|
|
||||||
timers=False,
|
timers=False,
|
||||||
stats=False,
|
stats=False,
|
||||||
tests=False,
|
tests=False,
|
||||||
@ -2225,7 +2255,6 @@ def solve(
|
|||||||
Arguments:
|
Arguments:
|
||||||
specs (list): List of ``Spec`` objects to solve for.
|
specs (list): List of ``Spec`` objects to solve for.
|
||||||
out: Optionally write the generate ASP program to a file-like object.
|
out: Optionally write the generate ASP program to a file-like object.
|
||||||
models (int): Number of models to search (default: 0 for unlimited).
|
|
||||||
timers (bool): Print out coarse fimers for different solve phases.
|
timers (bool): Print out coarse fimers for different solve phases.
|
||||||
stats (bool): Print out detailed stats from clingo.
|
stats (bool): Print out detailed stats from clingo.
|
||||||
tests (bool or tuple): If True, concretize test dependencies for all packages.
|
tests (bool or tuple): If True, concretize test dependencies for all packages.
|
||||||
@ -2237,22 +2266,14 @@ def solve(
|
|||||||
reusable_specs = self._check_input_and_extract_concrete_specs(specs)
|
reusable_specs = self._check_input_and_extract_concrete_specs(specs)
|
||||||
reusable_specs.extend(self._reusable_specs())
|
reusable_specs.extend(self._reusable_specs())
|
||||||
setup = SpackSolverSetup(tests=tests)
|
setup = SpackSolverSetup(tests=tests)
|
||||||
return self.driver.solve(
|
output = OutputConfiguration(timers=timers, stats=stats, out=out, setup_only=setup_only)
|
||||||
setup,
|
result, _, _ = self.driver.solve(setup, specs, reuse=reusable_specs, output=output)
|
||||||
specs,
|
return result
|
||||||
nmodels=models,
|
|
||||||
reuse=reusable_specs,
|
|
||||||
timers=timers,
|
|
||||||
stats=stats,
|
|
||||||
out=out,
|
|
||||||
setup_only=setup_only,
|
|
||||||
)
|
|
||||||
|
|
||||||
def solve_in_rounds(
|
def solve_in_rounds(
|
||||||
self,
|
self,
|
||||||
specs,
|
specs,
|
||||||
out=None,
|
out=None,
|
||||||
models=0,
|
|
||||||
timers=False,
|
timers=False,
|
||||||
stats=False,
|
stats=False,
|
||||||
tests=False,
|
tests=False,
|
||||||
@ -2267,7 +2288,6 @@ def solve_in_rounds(
|
|||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
specs (list): list of Specs to solve.
|
specs (list): list of Specs to solve.
|
||||||
models (int): number of models to search (default: 0)
|
|
||||||
out: Optionally write the generate ASP program to a file-like object.
|
out: Optionally write the generate ASP program to a file-like object.
|
||||||
timers (bool): print timing if set to True
|
timers (bool): print timing if set to True
|
||||||
stats (bool): print internal statistics if set to True
|
stats (bool): print internal statistics if set to True
|
||||||
@ -2281,16 +2301,10 @@ def solve_in_rounds(
|
|||||||
setup.concretize_everything = False
|
setup.concretize_everything = False
|
||||||
|
|
||||||
input_specs = specs
|
input_specs = specs
|
||||||
|
output = OutputConfiguration(timers=timers, stats=stats, out=out, setup_only=False)
|
||||||
while True:
|
while True:
|
||||||
result = self.driver.solve(
|
result, _, _ = self.driver.solve(
|
||||||
setup,
|
setup, input_specs, reuse=reusable_specs, output=output
|
||||||
input_specs,
|
|
||||||
nmodels=models,
|
|
||||||
reuse=reusable_specs,
|
|
||||||
timers=timers,
|
|
||||||
stats=stats,
|
|
||||||
out=out,
|
|
||||||
setup_only=False,
|
|
||||||
)
|
)
|
||||||
yield result
|
yield result
|
||||||
|
|
||||||
|
@ -1700,7 +1700,7 @@ def test_version_weight_and_provenance(self):
|
|||||||
with spack.config.override("concretizer:reuse", True):
|
with spack.config.override("concretizer:reuse", True):
|
||||||
solver = spack.solver.asp.Solver()
|
solver = spack.solver.asp.Solver()
|
||||||
setup = spack.solver.asp.SpackSolverSetup()
|
setup = spack.solver.asp.SpackSolverSetup()
|
||||||
result = solver.driver.solve(setup, [root_spec], reuse=reusable_specs, out=sys.stdout)
|
result, _, _ = solver.driver.solve(setup, [root_spec], reuse=reusable_specs)
|
||||||
# The result here should have a single spec to build ('a')
|
# The result here should have a single spec to build ('a')
|
||||||
# and it should be using b@1.0 with a version badness of 2
|
# and it should be using b@1.0 with a version badness of 2
|
||||||
# The provenance is:
|
# The provenance is:
|
||||||
@ -1731,7 +1731,7 @@ def test_not_reusing_incompatible_os_or_compiler(self):
|
|||||||
with spack.config.override("concretizer:reuse", True):
|
with spack.config.override("concretizer:reuse", True):
|
||||||
solver = spack.solver.asp.Solver()
|
solver = spack.solver.asp.Solver()
|
||||||
setup = spack.solver.asp.SpackSolverSetup()
|
setup = spack.solver.asp.SpackSolverSetup()
|
||||||
result = solver.driver.solve(setup, [root_spec], reuse=reusable_specs, out=sys.stdout)
|
result, _, _ = solver.driver.solve(setup, [root_spec], reuse=reusable_specs)
|
||||||
concrete_spec = result.specs[0]
|
concrete_spec = result.specs[0]
|
||||||
assert concrete_spec.satisfies("%gcc@4.5.0")
|
assert concrete_spec.satisfies("%gcc@4.5.0")
|
||||||
assert concrete_spec.satisfies("os=debian6")
|
assert concrete_spec.satisfies("os=debian6")
|
||||||
|
@ -1670,7 +1670,7 @@ _spack_restage() {
|
|||||||
_spack_solve() {
|
_spack_solve() {
|
||||||
if $list_options
|
if $list_options
|
||||||
then
|
then
|
||||||
SPACK_COMPREPLY="-h --help --show --models -l --long -L --very-long -I --install-status -y --yaml -j --json -c --cover -N --namespaces -t --types --timers --stats -U --fresh --reuse"
|
SPACK_COMPREPLY="-h --help --show -l --long -L --very-long -I --install-status -y --yaml -j --json -c --cover -N --namespaces -t --types --timers --stats -U --fresh --reuse"
|
||||||
else
|
else
|
||||||
_all_packages
|
_all_packages
|
||||||
fi
|
fi
|
||||||
|
Loading…
Reference in New Issue
Block a user