From 18fba433f6fade2a2fec225ff9dd1a84dc79112e Mon Sep 17 00:00:00 2001 From: Todd Gamblin Date: Sun, 13 Oct 2019 23:34:22 -0700 Subject: concretizer: Use "competition" output format to avoid extra parsing Competition output only prints out one model, so we do not have to unnecessarily parse all the non-optimal models. We'll just look at the best model and bring that in. In practice, this saves a lot of JSON parsing and spec construction time. --- lib/spack/spack/cmd/solve.py | 11 +++++---- lib/spack/spack/solver/asp.py | 52 +++++++++++++++++++++++++++++++++---------- 2 files changed, 45 insertions(+), 18 deletions(-) (limited to 'lib') diff --git a/lib/spack/spack/cmd/solve.py b/lib/spack/spack/cmd/solve.py index 9b10a27cc7..7d26fb36bc 100644 --- a/lib/spack/spack/cmd/solve.py +++ b/lib/spack/spack/cmd/solve.py @@ -32,8 +32,8 @@ def setup_parser(subparser): help="outputs: a list with any of: " "%s (default), all" % ', '.join(dump_options)) subparser.add_argument( - '--models', action='store', type=int, default=1, - help="number of solutions to display (0 for all)") + '--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) arguments.add_common_arguments( @@ -87,7 +87,7 @@ def solve(parser, args): # dump generated ASP program result = asp.solve(specs, dump=dump, models=models) - if dump == ['asp']: + if 'solutions' not in dump: return # die if no solution was found @@ -101,9 +101,8 @@ def solve(parser, args): assert best[1] == result.answers[-1][1] opt, i, answer = best - tty.msg( - "%d Answers. Best optimization %s:" % (i + 1, opt) - ) + tty.msg("Best of %d answers." % (i + 1)) + tty.msg("Optimization %s" % opt) # iterate over roots from command line for spec in specs: diff --git a/lib/spack/spack/solver/asp.py b/lib/spack/spack/solver/asp.py index 76798f7fe1..05954d6335 100644 --- a/lib/spack/spack/solver/asp.py +++ b/lib/spack/spack/solver/asp.py @@ -6,7 +6,6 @@ from __future__ import print_function import collections -import json import pkgutil import re import sys @@ -299,7 +298,7 @@ class AspGenerator(object): union.update(s) values = union - for value in values: + for value in values: self.fact(fn.variant_possible_value(pkg.name, name, value)) self.out.write('\n') @@ -583,6 +582,11 @@ class ResultParser(object): action(*args) def parse_json(self, data, result): + """Parse Clingo's JSON output format, which can give a lot of answers. + + This can be slow, espeically if Clingo comes back having explored + a lot of models. + """ if data["Result"] == "UNSATISFIABLE": result.satisfiable = False return @@ -601,6 +605,33 @@ class ResultParser(object): self.call_actions_for_functions(functions) result.answers.append((opt, best_model_number, self._specs)) + def parse_best(self, output, result): + """Parse Clingo's competition output format, which gives one answer.""" + best_model_number = 0 + for line in output: + match = re.match(r"% Answer: (\d+)", line) + if match: + best_model_number = int(match.group(1)) + + if re.match("INCONSISTENT", line): + result.satisfiable = False + return + + if re.match("ANSWER", line): + result.satisfiable = True + + answer = next(output) + functions = [ + f.rstrip(".") for f in re.split(r"\s+", answer.strip()) + ] + self.call_actions_for_functions(functions) + + costs = re.split(r"\s+", next(output).strip()) + opt = [int(x) for x in costs[1:]] + + result.answers.append((opt, best_model_number, self._specs)) + + class Result(object): def __init__(self, asp): self.asp = asp @@ -648,13 +679,13 @@ def highlight(string): # # These are handwritten parts for the Spack ASP model. # -def solve(specs, dump=None, models=1): +def solve(specs, dump=None, models=0): """Solve for a stable model of specs. Arguments: specs (list): list of Specs to solve. dump (tuple): what to dump - models (int): number of satisfying specs to find (default: 1) + models (int): number of models to search (default: 0) """ clingo = which('clingo', required=True) parser = ResultParser() @@ -683,8 +714,10 @@ def solve(specs, dump=None, models=1): with tempfile.TemporaryFile("w+") as output: with tempfile.TemporaryFile() as warnings: clingo( - '--models=0', - '--outf=2', + '--models=%d' % models, + # 1 is "competition" format with just optimal answer + # 2 is JSON format with all explored answers + '--outf=1', input=program, output=output, error=warnings, @@ -698,25 +731,20 @@ def solve(specs, dump=None, models=1): if sys.stdout.isatty(): tty.msg('Clingo gave the following warnings:') colorize(result.warnings) - else: - if sys.stdout.isatty(): - tty.msg('No warnings.') output.seek(0) result.output = output.read() - data = json.loads(result.output) # dump the raw output of the solver if 'output' in dump: if sys.stdout.isatty(): tty.msg('Clingo output:') - print(result.output) if 'solutions' not in dump: return output.seek(0) - parser.parse_json(data, result) + parser.parse_best(output, result) return result -- cgit v1.2.3-60-g2f50