summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorTodd Gamblin <tgamblin@llnl.gov>2019-10-13 23:34:22 -0700
committerTodd Gamblin <tgamblin@llnl.gov>2020-11-17 10:04:13 -0800
commit18fba433f6fade2a2fec225ff9dd1a84dc79112e (patch)
tree12db2a8b9c08174d84263834fa4b350984cb1f4b /lib
parentb4e6d9d28ec9025c423b88d211bd16f54694b511 (diff)
downloadspack-18fba433f6fade2a2fec225ff9dd1a84dc79112e.tar.gz
spack-18fba433f6fade2a2fec225ff9dd1a84dc79112e.tar.bz2
spack-18fba433f6fade2a2fec225ff9dd1a84dc79112e.tar.xz
spack-18fba433f6fade2a2fec225ff9dd1a84dc79112e.zip
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.
Diffstat (limited to 'lib')
-rw-r--r--lib/spack/spack/cmd/solve.py11
-rw-r--r--lib/spack/spack/solver/asp.py52
2 files changed, 45 insertions, 18 deletions
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