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:
		 Massimiliano Culpo
					Massimiliano Culpo
				
			
				
					committed by
					
						 Todd Gamblin
						Todd Gamblin
					
				
			
			
				
	
			
			
			 Todd Gamblin
						Todd Gamblin
					
				
			
						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 | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user