1 //
2 // Copyright (c) 2006-2017 Benjamin Kaufmann
3 //
4 // This file is part of Clasp. See http://www.cs.uni-potsdam.de/clasp/
5 //
6 // Permission is hereby granted, free of charge, to any person obtaining a copy
7 // of this software and associated documentation files (the "Software"), to
8 // deal in the Software without restriction, including without limitation the
9 // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
10 // sell copies of the Software, and to permit persons to whom the Software is
11 // furnished to do so, subject to the following conditions:
12 //
13 // The above copyright notice and this permission notice shall be included in
14 // all copies or substantial portions of the Software.
15 //
16 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21 // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
22 // IN THE SOFTWARE.
23 //
24 #include <clasp/parser.h>
25 #include <clasp/minimize_constraint.h>
26 #include <clasp/solver.h>
27 #include <potassco/theory_data.h>
28 #include <potassco/aspif.h>
29 #include <potassco/smodels.h>
30 #include <potassco/string_convert.h>
31 #include "lpcompare.h"
32 #include "catch.hpp"
33 namespace Clasp { namespace Test {
34
35 template <class Api>
parse(Api & api,std::istream & str,const ParserOptions & opts=ParserOptions ())36 static bool parse(Api& api, std::istream& str, const ParserOptions& opts = ParserOptions()) {
37 ProgramParser& p = api.parser();
38 return p.accept(str, opts) && p.parse();
39 }
clear(VarVec & vec)40 VarVec& clear(VarVec& vec) { vec.clear(); return vec; }
operator ,(VarVec & vec,int val)41 static VarVec& operator, (VarVec& vec, int val) {
42 vec.push_back(val);
43 return vec;
44 }
45 static struct Empty {
46 template <class T>
operator Potassco::Span<T>Clasp::Test::Empty47 operator Potassco::Span<T> () const { return Potassco::toSpan<T>(); }
48 } empty;
49
50 #define NOT_EMPTY(X,...)
51 #define SPAN(VEC,...) NOT_EMPTY(__VA_ARGS__) Potassco::toSpan((clear(VEC) , __VA_ARGS__))
52
53 using Potassco::SmodelsOutput;
54 using Potassco::AspifOutput;
55 namespace {
56 struct Input {
57 std::stringstream prg;
toSmodelsClasp::Test::__anon65a961950111::Input58 void toSmodels(const char* txt, Var falseAt = 0) {
59 prg.clear();
60 prg.str("");
61 SmodelsOutput sm(prg, true, falseAt);
62 Potassco::AspifTextInput x(&sm);
63 std::stringstream temp; temp << txt;
64 REQUIRE(Potassco::readProgram(temp, x, 0) == 0);
65 }
toAspifClasp::Test::__anon65a961950111::Input66 void toAspif(const char* txt) {
67 prg.clear();
68 prg.str("");
69 AspifOutput aspif(prg);
70 Potassco::AspifTextInput x(&aspif);
71 std::stringstream temp; temp << txt;
72 REQUIRE(Potassco::readProgram(temp, x, 0) == 0);
73 }
operator std::stringstream&Clasp::Test::__anon65a961950111::Input74 operator std::stringstream& () { return prg; }
75 };
76 }
77 TEST_CASE("Smodels parser", "[parser][asp]") {
78 SharedContext ctx;
79 Input in;
80 Asp::LogicProgram api;
81 api.start(ctx, Asp::LogicProgram::AspOptions().noScc());
82 SECTION("testEmptySmodels") {
83 in.toSmodels("");
84 REQUIRE(parse(api, in));
85 api.endProgram();
86 std::stringstream empty;
87 REQUIRE(compareSmodels(empty, api));
88 }
89 SECTION("testSingleFact") {
90 in.toSmodels("x1.");
91 REQUIRE(parse(api, in));
92 api.endProgram();
93 REQUIRE(0u == ctx.numVars());
94 }
95 SECTION("testComputeStatementAssumptions") {
96 in.toSmodels(
97 "x1 :- not x2.\n"
98 "x2 :- not x3.\n"
99 "x3 :- not x4.\n"
100 "x4 :- not x3.\n"
101 ":- x2.\n", 2);
102 REQUIRE(parse(api, in));
103 api.endProgram() && ctx.endInit();
104 REQUIRE(ctx.master()->isTrue(api.getLiteral(3)));
105 REQUIRE(ctx.master()->isTrue(api.getLiteral(1)));
106 REQUIRE(ctx.master()->isFalse(api.getLiteral(2)));
107 REQUIRE(ctx.master()->isFalse(api.getLiteral(4)));
108 }
109 SECTION("testTransformSimpleConstraintRule") {
110 in.toSmodels(
111 "x1 :- 2{not x3, x2, x4}.\n"
112 "x2 :- not x3.\n"
113 "x3 :- not x2.\n"
114 "x4 :- not x3.\n");
115 api.setExtendedRuleMode(Asp::LogicProgram::mode_transform_weight);
116 REQUIRE(parse(api, in));
117 REQUIRE(api.endProgram());
118 REQUIRE(api.getLiteral(2) == api.getLiteral(1));
119 REQUIRE(api.getLiteral(4) == api.getLiteral(1));
120 in.toSmodels(
121 "x1 :- not x3.\n"
122 "x3 :-not x1.\n"
123 "x2 :- x1.\n"
124 "x4 :- x1.\n");
125 REQUIRE(compareSmodels(in, api));
126 }
127 SECTION("testTransformSimpleWeightRule") {
128 in.toSmodels(
129 "x1 :- 2 {x2=1, not x3=2, x4=3}.\n"
130 "x2 :- not x3.\n"
131 "x3 :- not x2.\n"
132 "x4 :-not x3.");
133 api.setExtendedRuleMode(Asp::LogicProgram::mode_transform_weight);
134 REQUIRE(parse(api, in));
135 REQUIRE(api.endProgram());
136 REQUIRE(1u == ctx.numVars());
137 in.toSmodels(
138 "x1 :- not x3.\n"
139 "x3 :- not x1.\n"
140 "x2 :- x1.\n"
141 "x4 :- x1.\n");
142 REQUIRE(compareSmodels(in, api));
143 }
144 SECTION("testTransformSimpleChoiceRule") {
145 in.toSmodels("{x1;x2;x3} :- x2, not x3.\n"
146 "x2 :- not x1.\n"
147 "x3 :- not x2.");
148 api.setExtendedRuleMode(Asp::LogicProgram::mode_transform_choice);
149 REQUIRE(parse(api, in));
150 REQUIRE(api.endProgram());
151 in.toSmodels("x2. x4.");
152 REQUIRE(compareSmodels(in, api));
153 }
154 SECTION("testSimpleConstraintRule") {
155 in.toSmodels("x1 :- 2 {x3, x2, x4}.\n"
156 "x2 :- not x3.\n"
157 "x3 :- not x2.\n"
158 "x4 :- not x3.");
159 REQUIRE(parse(api, in));
160 REQUIRE(api.endProgram());
161 REQUIRE(2u == ctx.numVars());
162 in.toSmodels("x1 :- 2 {x3, x2 = 2}.\n"
163 "x2 :- not x3.\n"
164 "x3 :- not x2.\n"
165 "x4 :- x2.");
166 REQUIRE(compareSmodels(in, api));
167 }
168 SECTION("testSimpleWeightRule") {
169 in.toSmodels("x1 :- 2 {x3 = 2, x2 = 1, x4 = 3}. % but (x4 = 3 -> x4 = 2).\n"
170 "x2 :- not x3.\n"
171 "x3 :- not x2.\n"
172 "x4 :- not x3.");
173 REQUIRE(parse(api, in));
174 REQUIRE(api.endProgram());
175 REQUIRE(2u == ctx.numVars());
176 in.toSmodels("x1 :- 2 {x3 = 2, x2 = 3}.\n"
177 "x2 :- not x3.\n"
178 "x3 :- not x2.\n"
179 "x4 :- x2.");
180 REQUIRE(compareSmodels(in, api));
181 }
182 SECTION("testSimpleChoiceRule") {
183 in.toSmodels("{x1;x2;x3} :- x2, not x3.\n"
184 "x2 :- not x1.\n"
185 "x3 :- not x2.\n");
186 REQUIRE(parse(api, in));
187 REQUIRE(api.endProgram());
188 in.toSmodels("x2.");
189 REQUIRE(compareSmodels(in, api));
190 }
191 SECTION("testMinimizePriority") {
192 in.toSmodels("{x1;x2;x3;x4}.\n"
193 "#minimize{not x1, not x2}.\n"
194 "#minimize{x3, x4}.");
195 REQUIRE(parse(api, in));
196 REQUIRE(api.endProgram());
197 SharedMinimizeData* m1 = ctx.minimize();
198 REQUIRE(m1->numRules() == 2);
199 REQUIRE(std::find(m1->lits, m1->lits + 2, WeightLiteral(api.getLiteral(3), 0)) != m1->lits + 2);
200 REQUIRE(std::find(m1->lits, m1->lits + 2, WeightLiteral(api.getLiteral(4), 0)) != m1->lits + 2);
201 REQUIRE(std::find(m1->lits + 2, m1->lits + 4, WeightLiteral(~api.getLiteral(1), 1)) != m1->lits + 4);
202 REQUIRE(std::find(m1->lits + 2, m1->lits + 4, WeightLiteral(~api.getLiteral(2), 1)) != m1->lits + 4);
203 }
204 SECTION("testEdgeDirectives") {
205 in.toSmodels("{x1;x2}.\n"
206 "#output _edge(0,1) : x1.\n"
207 "#output _acyc_1_1_0 : x2.");
208 REQUIRE(parse(api, in, ParserOptions().enableAcycEdges()));
209 REQUIRE((api.endProgram() && ctx.endInit()));
210 REQUIRE(uint32(2) == ctx.stats().acycEdges);
211 }
212 SECTION("testHeuristicDirectives") {
213 in.toSmodels("{x1;x2;x3}.\n"
214 "#output _heuristic(a,true,1) : x1.\n"
215 "#output _heuristic(_heuristic(a,true,1),true,1) : x2.\n"
216 "#output a : x3.");
217 REQUIRE(parse(api, in, ParserOptions().enableHeuristic()));
218 REQUIRE((api.endProgram() && ctx.endInit()));
219 REQUIRE(ctx.heuristic.size() == 2);
220 }
221 SECTION("testSimpleIncremental") {
222 in.toSmodels(
223 "#incremental.\n"
224 "{x1;x2} :-x3.\n"
225 "#external x3.\n"
226 "#step.\n"
227 "#external x3.[release]\n"
228 "{x3}.");
229 api.updateProgram();
230 ProgramParser& p = api.parser();
231 REQUIRE(p.accept(in));
232 REQUIRE(p.parse());
233 REQUIRE(api.endProgram());
234 REQUIRE(p.more());
235 REQUIRE(api.stats.rules[0].sum() == 1);
236 REQUIRE(api.isExternal(3));
237 api.updateProgram();
238 REQUIRE(p.parse());
239 REQUIRE(!p.more());
240 REQUIRE(api.stats.rules[0].sum() == 1);
241 REQUIRE(!api.isExternal(3));
242 }
243 SECTION("testIncrementalMinimize") {
244 in.toSmodels(
245 "#incremental.\n"
246 "{x1; x2}.\n"
247 "#minimize{not x1, not x2}.\n"
248 "#step."
249 "#minimize{x1, x2}.\n");
250 api.updateProgram();
251 ProgramParser& p = api.parser();
252 REQUIRE(p.accept(in));
253 REQUIRE(p.parse());
254 REQUIRE(api.endProgram());
255 SharedMinimizeData* m1 = ctx.minimize();
256 m1->share();
257 REQUIRE(std::find(m1->lits, m1->lits + 2, WeightLiteral(~api.getLiteral(1), 1)) != m1->lits + 2);
258 REQUIRE(std::find(m1->lits, m1->lits + 2, WeightLiteral(~api.getLiteral(2), 1)) != m1->lits + 2);
259
260 REQUIRE(p.more());
261 api.updateProgram();
262 REQUIRE(p.parse());
263 REQUIRE(!p.more());
264 REQUIRE(api.endProgram());
265 SharedMinimizeData* m2 = ctx.minimize();
266 REQUIRE(m1 != m2);
267 REQUIRE(isSentinel(m2->lits[2].first));
268 REQUIRE(std::find(m2->lits, m2->lits + 2, WeightLiteral(api.getLiteral(1), 1)) != m2->lits + 2);
269 REQUIRE(std::find(m2->lits, m2->lits + 2, WeightLiteral(api.getLiteral(2), 1)) != m2->lits + 2);
270 m1->release();
271 }
272 };
273
sameProgram(Asp::LogicProgram & a,std::stringstream & prg)274 static bool sameProgram(Asp::LogicProgram& a, std::stringstream& prg) {
275 std::stringstream out;
276 AspParser::write(a, out, AspParser::format_aspif);
277 prg.clear();
278 prg.seekg(0);
279 return compareProgram(prg, out);
280 }
281
282 TEST_CASE("Aspif parser", "[parser][asp]") {
283 SharedContext ctx;
284 Input in;
285 Asp::LogicProgram api;
286 api.start(ctx, Asp::LogicProgram::AspOptions().noScc());
287 SECTION("testEmptyProgram") {
288 in.toAspif("");
289 REQUIRE(parse(api, in));
290 REQUIRE(api.endProgram());
291 REQUIRE(api.stats.rules[0].sum() == 0);
292 }
293 SECTION("testSingleFact") {
294 in.toAspif("x1.");
295 REQUIRE(parse(api, in));
296 REQUIRE(api.endProgram());
297 REQUIRE(api.stats.rules[0].sum() == 1);
298 REQUIRE(api.getLiteral(1) == lit_true());
299 }
300 SECTION("testSimpleRule") {
301 in.toAspif("x1 :- x3, not x2.");
302 REQUIRE(parse(api, in));
303 REQUIRE(api.stats.rules[0].sum() == 1);
304 REQUIRE(api.numAtoms() == 3);
305 REQUIRE(api.numBodies() == 1);
306 REQUIRE(api.getBody(0)->size() == 2);
307 REQUIRE(api.getBody(0)->goal(0) == posLit(3));
308 REQUIRE(api.getBody(0)->goal(1) == negLit(2));
309 REQUIRE(sameProgram(api, in));
310 }
311 SECTION("testInvalidAtom") {
312 in.toAspif(POTASSCO_FORMAT("x%u :- not x2, x3.", varMax));
313 REQUIRE_THROWS_AS(parse(api, in), std::logic_error);
314 }
315 SECTION("testInvalidLiteral") {
316 in.toAspif(POTASSCO_FORMAT("x1 :- not x%u, x3.", varMax));
317 REQUIRE_THROWS_AS(parse(api, in), std::logic_error);
318 }
319 SECTION("testIntegrityConstraint") {
320 in.toAspif(":- not x1, x2.");
321 REQUIRE(parse(api, in));
322 REQUIRE(api.stats.rules[0].sum() == 1);
323 REQUIRE(api.numAtoms() == 2);
324 REQUIRE(api.numBodies() == 1);
325 REQUIRE(api.getBody(0)->size() == 2);
326 REQUIRE(api.getBody(0)->goal(0) == posLit(2));
327 REQUIRE(api.getBody(0)->goal(1) == negLit(1));
328 REQUIRE(api.getBody(0)->value() == value_false);
329 }
330 SECTION("testDisjunctiveRule") {
331 in.toAspif("x1|x2.");
332 REQUIRE(parse(api, in));
333 REQUIRE(api.stats.rules[0].sum() == 1);
334 REQUIRE(api.stats.disjunctions[0] == 1);
335 REQUIRE(api.numAtoms() == 2);
336 REQUIRE(sameProgram(api, in));
337 }
338 SECTION("testChoiceRule") {
339 in.toAspif("{x1;x2;x3}.");
340 REQUIRE(parse(api, in));
341 REQUIRE(api.stats.rules[0].sum() == 1);
342 REQUIRE(api.stats.rules[0][Asp::RuleStats::Choice] == 1);
343 REQUIRE(api.numAtoms() == 3);
344 REQUIRE(sameProgram(api, in));
345 }
346 SECTION("testWeightRule") {
347 in.toAspif(
348 "x1 :- 2{x2, x3, not x4}.\n"
349 "x5 :- 4{x2 = 2, x3, not x4 = 3}.\n");
350 REQUIRE(parse(api, in));
351 REQUIRE(api.stats.rules[0].sum() == 2);
352 REQUIRE(api.stats.bodies[0][Asp::Body_t::Sum] == 1);
353 REQUIRE(api.stats.bodies[0][Asp::Body_t::Count] == 1);
354 REQUIRE(api.numBodies() == 2);
355 REQUIRE((api.getBody(0)->bound() == 2 && !api.getBody(0)->hasWeights()));
356 REQUIRE((api.getBody(1)->bound() == 4 && api.getBody(1)->hasWeights()));
357 REQUIRE(sameProgram(api, in));
358 }
359 SECTION("testDisjunctiveWeightRule") {
360 in.toAspif("x1|x2 :- 2{x3, x4, not x5}.");
361 REQUIRE(parse(api, in));
362 REQUIRE(sameProgram(api, in));
363 }
364 SECTION("testChoiceWeightRule") {
365 in.toAspif("{x1;x2} :- 2{x3, x4, not x5}.");
366 REQUIRE(parse(api, in));
367 REQUIRE(sameProgram(api, in));
368 }
369 SECTION("testInvalidNegativeWeightInWeightRule") {
370 in.toAspif("x1 :- 2 {x2 = -1, not x3 = 1, x4}.");
371 REQUIRE_THROWS_AS(parse(api, in), std::logic_error);
372 }
373 SECTION("testInvalidHeadInWeightRule") {
374 in.prg
375 << "asp 1 0 0"
376 << "\n1 0 1 0 1 2 3 2 1 -3 1 4 1"
377 << "\n0\n";
378 REQUIRE_THROWS_AS(parse(api, in), std::logic_error);
379 }
380 SECTION("testNegativeBoundInWeightRule") {
381 in.toAspif("x1 :- -1 {x2, not x3, x4}.");
382 REQUIRE(parse(api, in));
383 REQUIRE(api.stats.rules[0].sum() == 1);
384 REQUIRE(api.stats.bodies[0][Asp::Body_t::Sum] == 0);
385 }
386 SECTION("testMinimizeRule") {
387 in.toAspif(
388 "{x1;x2;x3}.\n"
389 "#minimize{x1 = 2, x2, not x3 = 4}.");
390 REQUIRE(parse(api, in));
391 REQUIRE(api.stats.rules[0][Asp::RuleStats::Minimize] == 1);
392 REQUIRE(sameProgram(api, in));
393 }
394 SECTION("testMinimizeRuleMergePriority") {
395 in.toAspif(
396 "#minimize{x1 = 2, x2, not x3 = 4}@1.\n"
397 "#minimize{x4 = 2, x2 = 2, x3 = 4}@1.");
398 REQUIRE(parse(api, in));
399 REQUIRE(api.stats.rules[0].sum() == 1);
400 REQUIRE(api.stats.rules[0][Asp::RuleStats::Minimize] == 1);
401 }
402 SECTION("testMinimizeRuleWithNegativeWeights") {
403 in.toAspif("#minimize{x4 = -2, x2 = -1, x3 = 4}.");
404 REQUIRE(parse(api, in));
405 REQUIRE(api.stats.rules[0].sum() == 1);
406 REQUIRE(api.endProgram());
407 std::stringstream exp("6 0 2 2 4 2 2 1 ");
408 REQUIRE(findSmodels(exp, api));
409 }
410 SECTION("testIncremental") {
411 in.toAspif("#incremental.\n"
412 "{x1;x2}.\n"
413 "#minimize{x1=2, x2}.\n"
414 "#step.\n"
415 "{x3}.\n"
416 "#minimize{x3=4}.\n");
417
418 REQUIRE(parse(api, in));
419 REQUIRE(api.stats.rules[0].sum() == 2);
420 REQUIRE(api.endProgram());
421
422 ProgramParser& p = api.parser();
423 REQUIRE(p.more());
424 api.updateProgram();
425 REQUIRE(p.parse());
426 REQUIRE_FALSE(p.more());
427 REQUIRE(api.stats.rules[0].sum() > 0);
428 // minimize rule was merged
429 REQUIRE(ctx.minimize()->numRules() == 1);
430 }
431 SECTION("testIncrementalExternal") {
432 in.toAspif("#incremental."
433 "x1 :- x2.\n"
434 "#external x2. [true]\n"
435 "#step.\n"
436 "#external x2. [false]\n"
437 "#step.\n"
438 "#external x2. [release]\n");
439
440 REQUIRE(parse(api, in));
441 REQUIRE(api.endProgram());
442 LitVec assume;
443 api.getAssumptions(assume);
444 REQUIRE((assume.size() == 1 && assume[0] == api.getLiteral(2)));
445
446 ProgramParser& p = api.parser();
447 REQUIRE(p.more());
448 api.updateProgram();
449 REQUIRE(p.parse());
450
451 REQUIRE(api.endProgram());
452 assume.clear();
453 api.getAssumptions(assume);
454 REQUIRE((assume.size() == 1 && assume[0] == ~api.getLiteral(2)));
455
456 REQUIRE(p.more());
457 api.updateProgram();
458 REQUIRE(p.parse());
459 REQUIRE(!p.more());
460 REQUIRE(api.endProgram());
461 assume.clear();
462 api.getAssumptions(assume);
463 REQUIRE(assume.empty());
464 ctx.endInit();
465 REQUIRE(ctx.master()->isFalse(api.getLiteral(2)));
466 }
467 SECTION("testSimpleEdgeDirective") {
468 in.toAspif("{x1;x2}."
469 "#edge (0,1) : x1.\n"
470 "#edge (1,0) : x2.\n");
471 REQUIRE(parse(api, in));
472 REQUIRE((api.endProgram() && ctx.endInit()));
473 REQUIRE(ctx.stats().acycEdges == 2);
474 REQUIRE(sameProgram(api, in));
475 }
476 SECTION("testComplexEdgeDirective") {
477 in.toAspif("{x1;x2;x3;x4}."
478 "#edge (0,1) : x1, not x2.\n"
479 "#edge (1,0) : x3, not x4.\n");
480 REQUIRE(parse(api, in));
481 REQUIRE((api.endProgram() && ctx.endInit()));
482 REQUIRE(ctx.stats().acycEdges == 2);
483 REQUIRE(sameProgram(api, in));
484 }
485 SECTION("testHeuristicDirective") {
486 in.toAspif("{x1;x2;x3;x4}."
487 "#heuristic x1. [-1@1,sign]\n"
488 "#heuristic x1 : x3, not x4. [1@1,factor]");
489 REQUIRE(parse(api, in));
490 REQUIRE((api.endProgram() && ctx.endInit()));
491 REQUIRE(ctx.heuristic.size() == 2);
492 REQUIRE(ctx.heuristic.begin()->type() == DomModType::Sign);
493 REQUIRE((ctx.heuristic.begin() + 1)->type() == DomModType::Factor);
494 REQUIRE(ctx.heuristic.begin()->var() == (ctx.heuristic.begin() + 1)->var());
495 REQUIRE(sameProgram(api, in));
496 }
497
498 SECTION("testOutputDirective") {
499 in.toAspif("{x1;x2}."
500 "#output fact.\n"
501 "#output conj : x1, x2.");
502 REQUIRE(parse(api, in));
503 REQUIRE((api.endProgram() && ctx.endInit()));
504 REQUIRE(ctx.output.size() == 2);
505 REQUIRE(ctx.output.numFacts() == 1);
506 REQUIRE(sameProgram(api, in));
507 }
508 SECTION("testAssumptionDirective") {
509 in.toAspif("{x1;x2}."
510 "#assume{not x2, x1}.");
511 REQUIRE(parse(api, in));
512 REQUIRE(api.endProgram());
513 LitVec assume;
514 api.getAssumptions(assume);
515 REQUIRE(assume.size() == 2);
516 REQUIRE(std::find(assume.begin(), assume.end(), ~api.getLiteral(2)) != assume.end());
517 REQUIRE(std::find(assume.begin(), assume.end(), api.getLiteral(1)) != assume.end());
518 REQUIRE(sameProgram(api, in));
519 }
520 SECTION("testProjectionDirective") {
521 in.toAspif("{x1;x2;x3;x4}."
522 "#output a : x1.\n"
523 "#output b : x2.\n"
524 "#output c : x3.\n"
525 "#output d : x4.\n"
526 "#project{x1, x3}.");
527 REQUIRE(parse(api, in));
528 REQUIRE(api.endProgram());
529 REQUIRE(ctx.output.size() == 4);
530 Literal a = api.getLiteral(1);
531 Literal b = api.getLiteral(2);
532 Literal c = api.getLiteral(3);
533 Literal d = api.getLiteral(4);
534 OutputTable::lit_iterator proj_begin = ctx.output.proj_begin(), proj_end = ctx.output.proj_end();
535 REQUIRE(std::find(proj_begin, proj_end, a) != proj_end);
536 REQUIRE_FALSE(std::find(proj_begin, proj_end, b) != proj_end);
537 REQUIRE(std::find(proj_begin, proj_end, c) != proj_end);
538 REQUIRE_FALSE(std::find(proj_begin, proj_end, d) != proj_end);
539 REQUIRE(sameProgram(api, in));
540 }
541 SECTION("testEmptyProjectionDirective") {
542 in.toAspif("{x1;x2;x3;x4}."
543 "#project.");
544 REQUIRE(parse(api, in));
545 REQUIRE(api.endProgram());
546 REQUIRE(ctx.output.projectMode() == ProjectMode_t::Explicit);
547 REQUIRE(sameProgram(api, in));
548 }
549 SECTION("testIgnoreCommentDirective") {
550 in.prg
551 << "asp 1 0 0"
552 << "\n" << Potassco::Directive_t::Comment << " Ignore me!"
553 << "\n0\n";
554 REQUIRE(parse(api, in));
555 }
556 SECTION("testReadTheoryAtoms") {
557 AspifOutput aspif(in.prg);
558 aspif.initProgram(false);
559 aspif.beginStep();
560 aspif.theoryTerm(0, 1); // (number 1)
561 aspif.theoryTerm(1, 2); // (number 2)
562 aspif.theoryTerm(2, 3); // (number 3)
563 aspif.theoryTerm(3, 4); // (number 4)
564 aspif.theoryTerm(4, Potassco::toSpan("x")); // (string x)
565 aspif.theoryTerm(5, Potassco::toSpan("z")); // (string z)
566 aspif.theoryTerm(6, Potassco::toSpan("+")); // (string +)
567 aspif.theoryTerm(7, Potassco::toSpan("*")); // (string *)
568 VarVec ids;
569 aspif.theoryTerm(8, 4, SPAN(ids, 0)); // (function x(1))
570 aspif.theoryTerm(9, 4, SPAN(ids, 1)); // (function x(2))
571 aspif.theoryTerm(10, 4, SPAN(ids, 2)); // (function x(3))
572 aspif.theoryTerm(11, 7, SPAN(ids, 0, 8)); // (term 1*x(1))
573 aspif.theoryTerm(12, 7, SPAN(ids, 1, 9)); // (term 2*x(2))
574 aspif.theoryTerm(13, 7, SPAN(ids, 2, 10)); // (term 3*x(3))
575 aspif.theoryTerm(14, 7, SPAN(ids, 3, 5)); // (term 3*x(3))
576
577 aspif.theoryElement(0, SPAN(ids, 11), empty); // (element 1*x(1):)
578 aspif.theoryElement(1, SPAN(ids, 12), empty); // (element 2*x(2):)
579 aspif.theoryElement(2, SPAN(ids, 13), empty); // (element 3*x(3):)
580 aspif.theoryElement(3, SPAN(ids, 14), empty); // (element 4*z:)
581 aspif.theoryTerm(15, Potassco::toSpan("sum")); // (string sum)
582 aspif.theoryTerm(16, Potassco::toSpan(">=")); // (string >=)
583 aspif.theoryTerm(17, 42); // (number 42)
584 aspif.theoryAtom(1, 15, SPAN(ids, 0, 1, 2, 3), 16, 17); // (&sum { 1*x(1); 2*x(2); 3*x(3); 4*z } >= 42)
585 aspif.endStep();
586 REQUIRE(parse(api, in));
587
588 Potassco::TheoryData& t = api.theoryData();
589 REQUIRE(t.numAtoms() == 1);
590
591 const Potassco::TheoryAtom& a = **t.begin();
592 REQUIRE(a.atom() == 1);
593 REQUIRE(std::strcmp(t.getTerm(a.term()).symbol(), "sum") == 0);
594 REQUIRE(a.size() == 4);
595 for (Potassco::TheoryAtom::iterator it = a.begin(), end = a.end(); it != end; ++it) {
596 const Potassco::TheoryElement& e = t.getElement(*it);
597 REQUIRE(e.size() == 1);
598 REQUIRE(Literal::fromId(e.condition()) == lit_true());
599 REQUIRE(t.getTerm(*e.begin()).type() == Potassco::Theory_t::Compound);
600 }
601 REQUIRE(std::strcmp(t.getTerm(*a.guard()).symbol(), ">=") == 0);
602 REQUIRE(t.getTerm(*a.rhs()).number() == 42);
603
604
605 struct BuildStr : public Potassco::TheoryAtomStringBuilder {
getConditionClasp::Test::BuildStr606 virtual Potassco::LitSpan getCondition(Potassco::Id_t) const { return Potassco::toSpan<Potassco::Lit_t>(); }
getNameClasp::Test::BuildStr607 virtual std::string getName(Potassco::Atom_t) const { return ""; }
608 } builder;
609
610 std::string n = builder.toString(t, a);
611 REQUIRE(n == "&sum{1 * x(1); 2 * x(2); 3 * x(3); 4 * z} >= 42");
612 }
613
614 SECTION("testWriteTheoryAtoms") {
615 VarVec ids;
616 AspifOutput aspif(in.prg);
617 aspif.initProgram(true);
618 aspif.beginStep();
619 aspif.rule(Potassco::Head_t::Choice, SPAN(ids, 1), empty);
620 aspif.theoryTerm(0, 1); // (number 1)
621 aspif.theoryTerm(1, Potassco::toSpan("x")); // (string x)
622 aspif.theoryTerm(3, Potassco::toSpan("foo"));// (string foo)
623 aspif.theoryTerm(2, 1, SPAN(ids, 0)); // (function x(1))
624 aspif.theoryElement(0, SPAN(ids, 2), empty); // (element x(1):)
625 aspif.theoryAtom(1, 3, SPAN(ids, 0)); // (&foo { x(1); })
626 aspif.endStep();
627 std::stringstream step1;
628 step1 << in.prg.str();
629 aspif.beginStep();
630 aspif.rule(Potassco::Head_t::Choice, SPAN(ids, 2), empty);
631 aspif.theoryAtom(2, 3, SPAN(ids, 0)); // (&foo { x(1); })
632 aspif.endStep();
633 REQUIRE(parse(api, in));
634 REQUIRE((api.endProgram() && api.theoryData().numAtoms() == 1));
635 std::stringstream out;
636 AspParser::write(api, out);
637 REQUIRE(findProgram(step1, out));
638 ProgramParser& p = api.parser();
639 REQUIRE(p.more());
640 api.updateProgram();
641 REQUIRE(api.theoryData().numAtoms() == 0);
642 REQUIRE(p.parse());
643 REQUIRE((api.endProgram() && api.theoryData().numAtoms() == 1));
644 AspParser::write(api, out);
645 }
646 SECTION("testTheoryElementWithCondition") {
647 VarVec ids;
648 AspifOutput aspif(in.prg);
649 aspif.initProgram(false);
650 aspif.beginStep();
651 aspif.rule(Potassco::Head_t::Choice, SPAN(ids, 1, 2), empty);
652 aspif.theoryTerm(0, 1); // (number 1)
653 aspif.theoryTerm(1, Potassco::toSpan("foo"));
654 Potassco::Lit_t lits[2] = {1, -2};
655 aspif.theoryElement(0, SPAN(ids, 0), Potassco::toSpan(lits, 2));
656 aspif.theoryAtom(0, 1, SPAN(ids, 0));
657 aspif.endStep();
658 REQUIRE(parse(api, in));
659 in.prg.clear();
660 in.prg.seekg(0);
661 Potassco::TheoryData& t = api.theoryData();
662 REQUIRE(t.numAtoms() == 1);
663 const Potassco::TheoryAtom& a = **t.begin();
664 REQUIRE(a.size() == 1);
665 const Potassco::TheoryElement& e = t.getElement(*a.begin());
666 REQUIRE(e.condition() != 0);
667 Potassco::LitVec cond;
668 api.extractCondition(e.condition(), cond);
669 REQUIRE(cond.size() == 2);
670 std::stringstream out;
671 REQUIRE(api.endProgram());
672 AspParser::write(api, out);
673 REQUIRE(findProgram(out, in));
674 }
675 }
676
677 TEST_CASE("Dimacs parser", "[parser][sat]") {
678 SharedContext ctx;
679 SatBuilder api;
680 api.startProgram(ctx);
681 std::stringstream prg;
682 SECTION("testDimacs") {
683 prg << "c simple test case\n"
684 << "p cnf 4 3\n"
685 << "1 2 0\n"
686 << "3 4 0\n"
687 << "-1 -2 -3 -4 0\n";
688 REQUIRE((parse(api, prg) && api.endProgram()));
689 REQUIRE(ctx.numVars() == 4);
690 REQUIRE(ctx.numConstraints() == 3);
691 }
692
693 SECTION("testDimacsDontAddTaut") {
694 prg << "c simple test case\n"
695 << "p cnf 4 4\n"
696 << "1 2 0\n"
697 << "3 4 0\n"
698 << "-1 -2 -3 -4 0\n"
699 << "1 -2 -3 2 0\n";
700 REQUIRE((parse(api, prg) && api.endProgram()));
701 REQUIRE(ctx.numVars() == 4);
702 REQUIRE(ctx.numConstraints() == 3);
703 }
704
705 SECTION("testDimacsDontAddDupLit") {
706 prg << "c simple test case\n"
707 << "p cnf 2 4\n"
708 << "1 2 2 1 0\n"
709 << "1 2 1 2 0\n"
710 << "-1 -2 -1 0\n"
711 << "-2 -1 -2 0\n";
712 REQUIRE((parse(api, prg) && api.endProgram()));
713 REQUIRE(ctx.numVars() == 2);
714 REQUIRE(ctx.numConstraints() == 4);
715 REQUIRE(ctx.numBinary() == 4);
716 }
717
718 SECTION("testDimacsBadVars") {
719 prg << "p cnf 1 1\n"
720 << "1 2 0\n";
721 REQUIRE_THROWS_AS(parse(api, prg), std::logic_error);
722 }
723
724 SECTION("testWcnf") {
725 prg << "c comments Weighted Max-SAT\n"
726 << "p wcnf 3 5\n"
727 << "10 1 -2 0\n"
728 << "3 -1 2 -3 0\n"
729 << "8 -3 2 0\n"
730 << "5 1 3 0\n"
731 << "2 3 0\n";
732 REQUIRE((parse(api, prg) && api.endProgram()));
733 REQUIRE(ctx.numVars() == 7);
734 REQUIRE(ctx.output.size() == 3);
735 REQUIRE(ctx.numConstraints() == 4);
736
737 SharedMinimizeData* wLits = ctx.minimize();
738 REQUIRE(wLits->numRules() == 1);
739 REQUIRE(wLits->lits[0].second == 10);
740 REQUIRE(wLits->lits[1].second == 8);
741 REQUIRE(wLits->lits[2].second == 5);
742 REQUIRE(wLits->lits[3].second == 3);
743 }
744
745 SECTION("testPartialWcnf") {
746 prg << "c comments Weigthed Partial Max-SAT\n"
747 << "p wcnf 4 5 16\n"
748 << "16 1 -2 4 0\n"
749 << "16 -1 -2 3 0\n"
750 << "8 -2 -4 0\n"
751 << "4 -3 2 0\n"
752 << "1 1 3 0\n";
753 REQUIRE((parse(api, prg) && api.endProgram()));
754 REQUIRE(ctx.numVars() == 7);
755 REQUIRE(ctx.output.size() == 4);
756 REQUIRE(ctx.numConstraints() == 5);
757 SharedMinimizeData* wLits = ctx.minimize();;
758 REQUIRE(wLits->numRules() == 1);
759 REQUIRE(wLits->lits[0].second == 8);
760 REQUIRE(wLits->lits[1].second == 4);
761 REQUIRE(wLits->lits[2].second == 1);
762 }
763 SECTION("testDimacsExtSupportsGraph") {
764 prg
765 << "p cnf 4 3\n"
766 << "c graph 2\n"
767 << "c node 1 1\n"
768 << "c node 2 1\n"
769 << "c arc 1 1 2\n"
770 << "c arc 2 2 1\n"
771 << "c endgraph\n"
772 << "1 2 0\n"
773 << "3 4 0\n"
774 << "-1 -2 -3 -4 0\n";
775 REQUIRE((parse(api, prg, ParserOptions().enableAcycEdges()) && api.endProgram() && ctx.endInit()));
776 REQUIRE(uint32(2) == ctx.stats().acycEdges);
777 }
778 SECTION("testDimacsExtSupportsCostFunc") {
779 prg
780 << "p cnf 4 3\n"
781 << "c minweight 1 2 2 4 -3 1 -4 2 0\n"
782 << "1 2 0\n"
783 << "3 4 0\n"
784 << "-1 -2 -3 -4 0\n";
785 REQUIRE((parse(api, prg, ParserOptions().enableMinimize()) && api.endProgram()));
786 REQUIRE(ctx.hasMinimize());
787 SharedMinimizeData* wLits = ctx.minimize();;
788 REQUIRE(wLits->numRules() == 1);
789 REQUIRE(wLits->lits[0] == WeightLiteral(posLit(2), 4));
790 REQUIRE(wLits->lits[1] == WeightLiteral(posLit(1), 2));
791 REQUIRE(wLits->lits[2] == WeightLiteral(negLit(4), 2));
792 REQUIRE(wLits->lits[3] == WeightLiteral(negLit(3), 1));
793 }
794 SECTION("testDimacsExtSupportsProject") {
795 prg
796 << "p cnf 4 3\n"
797 << "c project 1 4\n"
798 << "1 2 0\n"
799 << "3 4 0\n"
800 << "-1 -2 -3 -4 0\n";
801 REQUIRE((parse(api, prg, ParserOptions().enableProject()) && api.endProgram()));
802 REQUIRE(ctx.output.projectMode() == ProjectMode_t::Explicit);
803 REQUIRE(std::find(ctx.output.proj_begin(), ctx.output.proj_end(), posLit(1)) != ctx.output.proj_end());
804 REQUIRE(std::find(ctx.output.proj_begin(), ctx.output.proj_end(), posLit(4)) != ctx.output.proj_end());
805 }
806 SECTION("testDimacsExtSupportsHeuristic") {
807 prg
808 << "p cnf 4 0\n"
809 << "c heuristic 4 1 1 0 0\n"
810 << "c heuristic 5 2 1 0 0\n"
811 << "c heuristic 5 3 1 0 -1\n";
812 REQUIRE((parse(api, prg, ParserOptions().enableHeuristic()) && api.endProgram()));
813 REQUIRE(ctx.heuristic.size() == 3);
814 DomainTable::iterator it, end = ctx.heuristic.end();
815 for (it = ctx.heuristic.begin(); it != end && it->var() != 3; ++it) { ; }
816 REQUIRE(it != end);
817 REQUIRE(it->bias() == 1);
818 REQUIRE(it->prio() == 0);
819 REQUIRE(it->type() == Potassco::Heuristic_t::False);
820 REQUIRE(it->cond() == negLit(1));
821 }
822 SECTION("testDimacsExtSupportsAssumptions") {
823 prg
824 << "p cnf 4 0\n"
825 << "c assume 1 -2 3\n";
826 REQUIRE((parse(api, prg, ParserOptions().enableAssume()) && api.endProgram()));
827 LitVec ass;
828 api.getAssumptions(ass);
829 REQUIRE(ass.size() == 3);
830 REQUIRE(ass[0] == posLit(1));
831 REQUIRE(ass[1] == negLit(2));
832 REQUIRE(ass[2] == posLit(3));
833 }
834 SECTION("testDimacsExtSupportsOutputRange") {
835 prg
836 << "p cnf 4 0\n"
837 << "c output range 2 3\n";
838 REQUIRE((parse(api, prg, ParserOptions().enableOutput()) && api.endProgram()));
839 REQUIRE(*ctx.output.vars_begin() == 2);
840 REQUIRE(*(ctx.output.vars_end()-1) == 3);
841 }
842 SECTION("testDimacsExtSupportsOutputTable") {
843 prg
844 << "p cnf 4 0\n"
845 << "c output 1 var(1) \n"
846 << "c output -2 not_var(2) \n"
847 << "c 0\n";
848 REQUIRE((parse(api, prg, ParserOptions().enableOutput()) && api.endProgram()));
849 REQUIRE(ctx.output.vars_begin() == ctx.output.vars_end());
850 REQUIRE(ctx.output.size() == 2);
851 for (OutputTable::pred_iterator it = ctx.output.pred_begin(), end = ctx.output.pred_end(); it != end; ++it) {
852 REQUIRE((
853 (it->name.c_str() == std::string("var(1)") && it->cond == posLit(1))
854 ||
855 (it->name.c_str() == std::string("not_var(2)") && it->cond == negLit(2))
856 ));
857 }
858 }
859 }
860
861 TEST_CASE("Opb parser", "[parser][pb]") {
862 SharedContext ctx;
863 PBBuilder api;
864 api.startProgram(ctx);
865 std::stringstream prg;
866
867 SECTION("testBadVar") {
868 prg << "* #variable= 1 #constraint= 1\n+1 x2 >= 1;\n";
869 REQUIRE_THROWS_AS(parse(api, prg), std::logic_error);
870 }
871 SECTION("testBadVarInGraph") {
872 prg << "* #variable= 1 #constraint= 1\n"
873 << "* graph 2\n"
874 << "* arc 1 1 2\n"
875 << "* arc 2 2 1\n"
876 << "* endgraph\n";
877 REQUIRE_THROWS_AS(parse(api, prg, ParserOptions().enableAcycEdges()), std::logic_error);
878 }
879 SECTION("testWBO") {
880 prg << "* #variable= 1 #constraint= 2 #soft= 2 mincost= 2 maxcost= 3 sumcost= 5\n"
881 << "soft: 6 ;\n"
882 << "[2] +1 x1 >= 1 ;\n"
883 << "[3] -1 x1 >= 0 ;\n";
884 REQUIRE((parse(api, prg) && api.endProgram()));
885 REQUIRE(ctx.numVars() == 3);
886 REQUIRE((ctx.numConstraints() == 0 || ctx.numConstraints() == 2));
887 REQUIRE(ctx.output.size() == 1);
888 SumVec bound;
889 api.getWeakBounds(bound);
890 REQUIRE((bound.size() == 1 && bound[0] == 5));
891 SharedMinimizeData* wLits = ctx.minimize();
892 REQUIRE((wLits && wLits->numRules() == 1));
893 REQUIRE(wLits->adjust(0) == 2);
894 }
895
896 SECTION("testNLC") {
897 prg << "* #variable= 5 #constraint= 4 #product= 5 sizeproduct= 13\n"
898 << "1 x1 +4 x1 ~x2 -2 x5 >=1;\n"
899 << "-1 x1 +4 x2 -2 x5 >= 3;\n"
900 << "10 x4 +4 x3 >= 10;\n"
901 << "2 x2 x3 +3 x4 ~x5 +2 ~x1 x2 +3 ~x1 x2 x3 ~x4 ~x5 >= 1 ;\n";
902 REQUIRE((parse(api, prg) && api.endProgram()));
903 REQUIRE(ctx.numVars() == 10);
904 REQUIRE(ctx.numConstraints() >= 4);
905 REQUIRE(ctx.output.size() == 5);
906 }
907
908 SECTION("testNLCUnsorted") {
909 prg << "* #variable= 4 #constraint= 2 #product= 2 sizeproduct= 8\n"
910 << "1 x1 +1 x2 x1 >=1;\n"
911 << "1 x1 +1 x2 x3 x4 ~x4 x2 x3 >=1;\n";
912 REQUIRE((parse(api, prg) && api.endProgram()));
913 REQUIRE(ctx.numVars() == 6);
914 REQUIRE(ctx.master()->isTrue(posLit(1)));
915 REQUIRE(ctx.master()->isFalse(posLit(6)));
916 }
917
918 SECTION("testPBEqualityBug") {
919 prg << "* #variable= 4 #constraint= 2\n"
920 << "+1 x1 = 1;\n"
921 << "+1 x1 +1 x2 +1 x3 +1 x4 = 1;\n";
922 REQUIRE((parse(api, prg) && api.endProgram()));
923 REQUIRE(ctx.master()->isTrue(posLit(1)));
924 REQUIRE(ctx.master()->isFalse(posLit(2)));
925 REQUIRE(ctx.master()->isFalse(posLit(3)));
926 REQUIRE(ctx.master()->isFalse(posLit(4)));
927 }
928 SECTION("testPBProject") {
929 prg << "* #variable= 6 #constraint= 0\n"
930 << "* project x1 x2\n"
931 << "* project x4\n";
932 REQUIRE((parse(api, prg, ParserOptions().enableProject()) && api.endProgram()));
933 REQUIRE(ctx.output.projectMode() == ProjectMode_t::Explicit);
934 REQUIRE(std::distance(ctx.output.proj_begin(), ctx.output.proj_end()) == 3u);
935 }
936 SECTION("testPBAssume") {
937 prg << "* #variable= 6 #constraint= 0\n"
938 << "* assume x1 -x5\n";
939 REQUIRE((parse(api, prg, ParserOptions().enableAssume()) && api.endProgram()));
940 LitVec ass;
941 api.getAssumptions(ass);
942 REQUIRE(ass.size() == 2);
943 REQUIRE(ass[0] == posLit(1));
944 REQUIRE(ass[1] == negLit(5));
945 }
946 SECTION("testPBOutput") {
947 prg << "* #variable= 6 #constraint= 0\n"
948 << "* output range x2 x4\n";
949 REQUIRE((parse(api, prg, ParserOptions().enableOutput()) && api.endProgram()));
950 REQUIRE(*ctx.output.vars_begin() == 2);
951 REQUIRE(*(ctx.output.vars_end()-1)== 4);
952 }
953 SECTION("testPBOutputTable") {
954 prg << "* #variable= 6 #constraint= 0\n"
955 << "* output x1 var(1)\n"
956 << "* output -x2 not_var(2)\n"
957 << "* 0\n";
958 REQUIRE((parse(api, prg, ParserOptions().enableOutput()) && api.endProgram()));
959 REQUIRE(ctx.output.vars_begin() == ctx.output.vars_end());
960 REQUIRE(ctx.output.size() == 2);
961 for (OutputTable::pred_iterator it = ctx.output.pred_begin(), end = ctx.output.pred_end(); it != end; ++it) {
962 REQUIRE((
963 (it->name.c_str() == std::string("var(1)") && it->cond == posLit(1))
964 ||
965 (it->name.c_str() == std::string("not_var(2)") && it->cond == negLit(2))
966 ));
967 }
968 }
969 }
970 } }
971