summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTodd Gamblin <tgamblin@llnl.gov>2019-10-13 21:05:35 -0700
committerTodd Gamblin <tgamblin@llnl.gov>2020-11-17 10:04:13 -0800
commit36ec66d997e91e3afcbd4a3c7b999370b24254a0 (patch)
tree7b1086fd12927fef0d0e6211e7654e2916dbe260
parenta332981f2f8d06189c50f651d7d9278f7b28bffe (diff)
downloadspack-36ec66d997e91e3afcbd4a3c7b999370b24254a0.tar.gz
spack-36ec66d997e91e3afcbd4a3c7b999370b24254a0.tar.bz2
spack-36ec66d997e91e3afcbd4a3c7b999370b24254a0.tar.xz
spack-36ec66d997e91e3afcbd4a3c7b999370b24254a0.zip
concretizer: use clingo json output instead of text
Clingo actually has an option to output JSON -- use that instead of parsing the raw otuput ourselves. This also allows us to pick the best answer -- modify the parser to *only* construct a spec for that one rather than building all of them like we did before.
-rw-r--r--lib/spack/spack/cmd/solve.py31
-rw-r--r--lib/spack/spack/solver/asp.py123
2 files changed, 85 insertions, 69 deletions
diff --git a/lib/spack/spack/cmd/solve.py b/lib/spack/spack/cmd/solve.py
index 7fea75233d..9b10a27cc7 100644
--- a/lib/spack/spack/cmd/solve.py
+++ b/lib/spack/spack/cmd/solve.py
@@ -22,7 +22,7 @@ section = 'developer'
level = 'long'
#: output options
-dump_options = ('asp', 'warnings', 'output', 'solutions')
+dump_options = ('asp', 'output', 'solutions')
def setup_parser(subparser):
@@ -87,6 +87,8 @@ def solve(parser, args):
# dump generated ASP program
result = asp.solve(specs, dump=dump, models=models)
+ if dump == ['asp']:
+ return
# die if no solution was found
# TODO: we need to be able to provide better error messages than this
@@ -95,13 +97,20 @@ def solve(parser, args):
# dump the solutions as concretized specs
if 'solutions' in dump:
- for i, answer in enumerate(result.answers):
- tty.msg("Answer %d" % (i + 1))
- for spec in specs:
- answer_spec = answer[spec.name]
- if args.yaml:
- out = answer_spec.to_yaml()
- else:
- out = answer_spec.tree(
- color=sys.stdout.isatty(), **kwargs)
- sys.stdout.write(out)
+ best = min(result.answers)
+ assert best[1] == result.answers[-1][1]
+
+ opt, i, answer = best
+ tty.msg(
+ "%d Answers. Best optimization %s:" % (i + 1, opt)
+ )
+
+ # iterate over roots from command line
+ for spec in specs:
+ answer_spec = answer[spec.name]
+ if args.yaml:
+ out = answer_spec.to_yaml()
+ else:
+ out = answer_spec.tree(
+ color=sys.stdout.isatty(), **kwargs)
+ sys.stdout.write(out)
diff --git a/lib/spack/spack/solver/asp.py b/lib/spack/spack/solver/asp.py
index f23fe351cd..4033362e74 100644
--- a/lib/spack/spack/solver/asp.py
+++ b/lib/spack/spack/solver/asp.py
@@ -6,6 +6,7 @@
from __future__ import print_function
import collections
+import json
import pkgutil
import re
import sys
@@ -503,64 +504,60 @@ class ResultParser(object):
self._specs[pkg]._add_dependency(
self._specs[dep], ('link', 'run'))
- def parse(self, stream, result):
- for line in stream:
- match = re.match(r'SATISFIABLE', line)
- if match:
- result.satisfiable = True
+ def call_actions_for_functions(self, function_strings):
+ function_re = re.compile(r'(\w+)\(([^)]*)\)')
+
+ # parse functions out of ASP output
+ functions = []
+ for string in function_strings:
+ m = function_re.match(string)
+ name, arg_string = m.groups()
+ args = re.split(r'\s*,\s*', arg_string)
+ args = [s.strip('"') if s.startswith('"') else int(s)
+ for s in args]
+ functions.append((name, args))
+
+ # Functions don't seem to be in particular order in output.
+ # Sort them here so that nodes are first, and so created
+ # before directives that need them (depends_on(), etc.)
+ functions.sort(key=lambda f: f[0] != "node")
+
+ self._specs = {}
+ for name, args in functions:
+ action = getattr(self, name, None)
+ if not action:
+ print("%s(%s)" % (name, ", ".join(str(a) for a in args)))
continue
+ assert action and callable(action)
+ action(*args)
- match = re.match(r'OPTIMUM FOUND', line)
- if match:
- result.satisfiable = True
- continue
-
- match = re.match(r'UNSATISFIABLE', line)
- if match:
- result.satisfiable = False
- continue
-
- match = re.match(r'Answer: (\d+)', line)
- if not match:
- continue
-
- answer_number = int(match.group(1))
- assert answer_number == len(result.answers) + 1
-
- answer = next(stream)
- tty.debug("Answer: %d" % answer_number, answer)
-
- # parse functions out of ASP output
- functions = []
- for m in re.finditer(r'(\w+)\(([^)]*)\)', answer):
- name, arg_string = m.groups()
- args = re.split(r'\s*,\s*', arg_string)
- args = [s.strip('"') if s.startswith('"') else int(s)
- for s in args]
- functions.append((name, args))
+ def parse_json(self, data, result):
+ if data["Result"] == "UNSATISFIABLE":
+ result.satisfiable = False
+ return
- # Functions don't seem to be in particular order in output.
- # Sort them here so that nodes are first, and so created
- # before directives that need them (depends_on(), etc.)
- functions.sort(key=lambda f: f[0] != "node")
+ result.satisfiable = True
+ if data["Result"] == "OPTIMUM FOUND":
+ result.optimal = True
- self._specs = {}
- for name, args in functions:
- action = getattr(self, name, None)
- if not action:
- print("%s(%s)" % (name, ", ".join(str(a) for a in args)))
- continue
- assert action and callable(action)
- action(*args)
+ nmodels = data["Models"]["Number"]
+ best_model_number = nmodels - 1
+ best_model = data["Call"][0]["Witnesses"][best_model_number]
+ opt = list(best_model["Costs"])
- result.answers.append(self._specs)
+ functions = best_model["Value"]
+ self.call_actions_for_functions(functions)
+ result.answers.append((opt, best_model_number, self._specs))
class Result(object):
def __init__(self, asp):
self.asp = asp
self.satisfiable = None
+ self.optimal = None
self.warnings = None
+
+ # specs ordered by optimization level
self.answers = []
@@ -625,12 +622,18 @@ def solve(specs, dump=None, models=1):
if 'asp' in dump:
if sys.stdout.isatty():
tty.msg('ASP program:')
- colorize(result.asp)
+
+ if dump == ['asp']:
+ print(result.asp)
+ return
+ else:
+ colorize(result.asp)
with tempfile.TemporaryFile("w+") as output:
with tempfile.TemporaryFile() as warnings:
clingo(
- '--models=%d' % models,
+ '--models=0',
+ '--outf=2',
input=program,
output=output,
error=warnings,
@@ -640,25 +643,29 @@ def solve(specs, dump=None, models=1):
result.warnings = warnings.read().decode("utf-8")
# dump any warnings generated by the solver
- if 'warnings' in dump:
- if result.warnings:
- if sys.stdout.isatty():
- tty.msg('Clingo gave the following warnings:')
- colorize(result.warnings)
- else:
- if sys.stdout.isatty():
- tty.msg('No warnings.')
+ if result.warnings:
+ 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:')
- colorize(result.output)
+
+ print(result.output)
+
+ if 'solutions' not in dump:
+ return
output.seek(0)
- parser.parse(output, result)
+ parser.parse_json(data, result)
return result