Skip to content

Commit

Permalink
output answer set number as reported by solver
Browse files Browse the repository at this point in the history
need more tests
  • Loading branch information
Aluriak committed Oct 11, 2019
1 parent 1840c36 commit cc60217
Show file tree
Hide file tree
Showing 5 changed files with 49 additions and 19 deletions.
18 changes: 15 additions & 3 deletions clyngor/answers.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,8 @@ def __init__(self, answers:iter, command:str='', statistics:dict={},
"""
if not with_optimization:
# emulate optimization with None value
answers = ((answer, None, False) for answer in answers)
# TODO: verify that in absence of optimization, all answers are ALWAYS enumerated from 1 to n.
answers = ((answer, None, False, idx) for idx, answer in enumerate(answers, start=1))
self._answers = iter(answers)
self._command = str(command or '')
self._statistics = statistics # will be updated by reading method
Expand All @@ -72,6 +73,7 @@ def __init__(self, answers:iter, command:str='', statistics:dict={},
self._ignore_args = False
self._with_optimization = False
self._with_optimality = False
self._with_answer_number = False
self.__on_end = on_end or (lambda: None)

def __del__(self):
Expand Down Expand Up @@ -101,6 +103,14 @@ def with_optimality(self):
self._with_optimality = True
return self

@property
def with_answer_number(self):
"""Yield (model, optimization, optimality, answer_number) instead of just model"""
self._with_optimization = True
self._with_optimality = True
self._with_answer_number = True
return self

@property
def first_arg_only(self):
"""Keep only the first argument, and do not enclose it in a tuple."""
Expand Down Expand Up @@ -190,10 +200,12 @@ def __next__(self):

def __iter__(self):
"""Yield answer sets"""
for answer_set, optimization, optimality in self._answers:
for answer_set, optimization, optimality, answer_number in self._answers:
answer_set = tuple(self._parse_answer(answer_set))
parsed = self._format(answer_set)
if self._with_optimality:
if self._with_answer_number:
yield parsed, optimization, optimality, answer_number
elif self._with_optimality:
yield parsed, optimization, optimality
elif self._with_optimization:
yield parsed, optimization
Expand Down
1 change: 1 addition & 0 deletions clyngor/parsing.py
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,7 @@ def parse_clasp_output(output:iter or str, *, yield_stats:bool=False,
# first answer begins
while True:
if line.startswith(ASW_FLAG):
yield 'answer_number', int(line[len(ASW_FLAG):])
yield 'answer', next(output)
elif line.startswith(OPT_FLAG) and yield_opti:
yield 'optimization', tuple(map(int, line[len(OPT_FLAG):].strip().split()))
Expand Down
17 changes: 11 additions & 6 deletions clyngor/solving.py
Original file line number Diff line number Diff line change
Expand Up @@ -197,18 +197,22 @@ def clingo_version(clingo_bin_path:str=None) -> dict:


def _gen_answers(stdout:iter, stderr:iter, statistics:dict,
error_on_warning:bool) -> (str, int or None, bool):
"""Yield 3-uplet (answer set, optimization, optimum found),
error_on_warning:bool) -> (str, int or None, bool, int):
"""Yield 4-uplet (answer set, optimization, optimum found, answer number),
and update given statistics dict with statistics payloads
"""
answer = None # is used to generate a model only when we are sur there is (no) optimization
answer_number = None
optimization, optimum_found = None, False
for ptype, payload in parse_clasp_output(stdout, yield_stats=True):
if ptype == 'answer': # yield previously found answer
if ptype == 'answer_number':
if answer is not None:
yield answer, optimization, optimum_found
answer, optimization, optimum_found = payload, None, False
yield answer, optimization, optimum_found, answer_number
answer, optimization, optimum_found, answer_number = payload, None, False, None
answer_number = payload
elif ptype == 'answer': # yield previously found answer
answer = payload
elif ptype == 'optimum found':
optimum_found = payload
elif ptype == 'optimization':
Expand All @@ -220,7 +224,8 @@ def _gen_answers(stdout:iter, stderr:iter, statistics:dict,
else:
assert ptype in parse_clasp_output.out_types, 'solving.parse_clasp_output yields an unexpected type ' + repr(ptype)
if answer is not None: # if no optimization, probably one miss
yield answer, optimization, optimum_found
assert answer_number is not None, answer_number
yield answer, optimization, optimum_found, answer_number

# handle stderr
for payload in validate_clasp_stderr(stderr):
Expand Down
18 changes: 9 additions & 9 deletions clyngor/test/test_answers.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,11 +58,11 @@ def many_atoms_answers():
@pytest.fixture
def optimized_answers():
return Answers((
('edge(4,"s…lp.") r_e_l(1,2)', 1, False),
('edge(4,"s…lp.") r_e_l(1,2)', 1, False),
('edge(4,"s…lp.") r_e_l(1,2)', 2, False),
('edge(4,"s…lp.") r_e_l(1,2)', 3, False),
('edge(4,"s…lp.") r_e_l(1,2)', 4, False),
('edge(4,"s…lp.") r_e_l(1,2)', 1, False, 1),
('edge(4,"s…lp.") r_e_l(1,2)', 1, False, 2),
('edge(4,"s…lp.") r_e_l(1,2)', 2, False, 3),
('edge(4,"s…lp.") r_e_l(1,2)', 3, False, 4),
('edge(4,"s…lp.") r_e_l(1,2)', 4, False, 5),
), with_optimization=True)


Expand Down Expand Up @@ -288,8 +288,8 @@ def test_optimization_access(optimized_answers):
assert next(answers) == ({('edge', (4, 's…lp.')), ('r_e_l', (1, 2))}, 1)
answers = answers.no_arg
assert next(answers) == ({'edge', 'r_e_l'}, 2)
answers.atoms_as_string
assert next(answers) == ({'edge(4,"s…lp.")', 'r_e_l(1,2)'}, 3)
answers.with_optimality
assert next(answers) == ({'edge(4,"s…lp.")', 'r_e_l(1,2)'}, 4, False)
answers.atoms_as_string.with_optimality
assert next(answers) == ({'edge(4,"s…lp.")', 'r_e_l(1,2)'}, 3, False)
answers.with_answer_number
assert next(answers) == ({'edge(4,"s…lp.")', 'r_e_l(1,2)'}, 4, False, 5)
assert next(answers, None) is None
14 changes: 13 additions & 1 deletion clyngor/test/test_parsing.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,13 @@ def test_simple_case():
yield_stats=True, yield_info=True)
models = []
for type, payload in parsed:
assert type in ('statistics', 'info', 'answer')
assert type in ('statistics', 'info', 'answer', 'answer_number')
if type == 'statistics':
stats = payload
elif type == 'answer':
models.append(payload)
elif type == 'answer_number':
pass
else:
assert type == 'info'
info = payload
Expand Down Expand Up @@ -73,6 +75,8 @@ def test_parse_termset_impossible():
def test_string():
"""Show that string with comma in it is handled correctly"""
parsed = Parser().parse_clasp_output(OUTCLASP_STRING.splitlines())
type, answer_number = next(parsed)
assert type == 'answer_number'
type, model = next(parsed)
assert next(parsed, None) is None, "there is only one model"
assert type == 'answer', "the model is an answer"
Expand All @@ -84,6 +88,8 @@ def test_string():

def test_complex_atoms():
parsed = Parser().parse_clasp_output(OUTCLASP_COMPLEX_ATOMS.splitlines())
type, answer_number = next(parsed)
assert type == 'answer_number'
type, model = next(parsed)
assert next(parsed, None) is None, "there is only one model"
assert type == 'answer', "the model is an answer"
Expand Down Expand Up @@ -135,6 +141,8 @@ def test_time_limit():
assert model == next(expected_info)
elif type == 'answer':
assert model == frozenset(next(expected_answer))
elif type == 'answer_number':
assert isinstance(model, int) and model > 0, model
elif type == 'optimization':
assert model == (next(expected_optimization),)
elif type == 'progression':
Expand Down Expand Up @@ -162,6 +170,8 @@ def test_multiple_opt_values():
assert False, 'no info'
elif type == 'answer':
assert model == frozenset(next(expected_answer))
elif type == 'answer_number':
assert isinstance(model, int) and model > 0, model
elif type == 'optimization':
assert model == next(expected_optimization)
elif type == 'optimum found':
Expand Down Expand Up @@ -196,6 +206,8 @@ def test_multithread_with_progression():
assert False, 'no info'
elif type == 'answer':
assert model == frozenset(next(expected_answer))
elif type == 'answer_number':
assert isinstance(model, int) and model > 0, model
elif type == 'optimization':
assert model == next(expected_optimization)
elif type == 'progression':
Expand Down

0 comments on commit cc60217

Please sign in to comment.