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.
This commit is contained in:
parent
a332981f2f
commit
36ec66d997
@ -22,7 +22,7 @@
|
||||
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)
|
||||
|
@ -6,6 +6,7 @@
|
||||
from __future__ import print_function
|
||||
|
||||
import collections
|
||||
import json
|
||||
import pkgutil
|
||||
import re
|
||||
import sys
|
||||
@ -503,64 +504,60 @@ def depends_on(self, pkg, dep):
|
||||
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
|
||||
def parse_json(self, data, result):
|
||||
if data["Result"] == "UNSATISFIABLE":
|
||||
result.satisfiable = False
|
||||
return
|
||||
|
||||
match = re.match(r'UNSATISFIABLE', line)
|
||||
if match:
|
||||
result.satisfiable = False
|
||||
continue
|
||||
result.satisfiable = True
|
||||
if data["Result"] == "OPTIMUM FOUND":
|
||||
result.optimal = True
|
||||
|
||||
match = re.match(r'Answer: (\d+)', line)
|
||||
if not match:
|
||||
continue
|
||||
nmodels = data["Models"]["Number"]
|
||||
best_model_number = nmodels - 1
|
||||
best_model = data["Call"][0]["Witnesses"][best_model_number]
|
||||
opt = list(best_model["Costs"])
|
||||
|
||||
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))
|
||||
|
||||
# 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)
|
||||
|
||||
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 colorize(string):
|
||||
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 colorize(string):
|
||||
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
|
||||
|
Loading…
Reference in New Issue
Block a user