From 3fc9a3b3058a711575b783f9143680b41f873527 Mon Sep 17 00:00:00 2001 From: Antonius Weinzierl Date: Tue, 6 Oct 2020 02:36:36 +0200 Subject: [PATCH 01/27] Add parsing and initial rewriting of weak constraints. - Refined grammar file. - ParseTreeVisitor creates rules with WeakConstraintAtom in head. - New WeakConstraint type representing parsed weak constraints. - New WeakConstraintAtom as special head atom of weak constraints. - New WeakConstraintNormalization transforming WeakConstraint rules into normal rules with WeakConstraintAtom in head. - Removed dependence on Literals from IntervalTermToIntervalAtom and NaiveGrounder where Atoms are sufficient. --- .../at/ac/tuwien/kr/alpha/antlr/ASPCore2.g4 | 2 +- .../kr/alpha/common/rule/WeakConstraint.java | 43 ++++++ .../kr/alpha/grounder/NaiveGrounder.java | 2 +- .../grounder/atoms/WeakConstraintAtom.java | 139 ++++++++++++++++++ .../grounder/parser/ParseTreeVisitor.java | 14 +- .../IntervalTermToIntervalAtom.java | 18 ++- .../NormalizeProgramTransformation.java | 2 + .../WeakConstraintNormalization.java | 37 +++++ 8 files changed, 250 insertions(+), 7 deletions(-) create mode 100644 src/main/java/at/ac/tuwien/kr/alpha/common/rule/WeakConstraint.java create mode 100644 src/main/java/at/ac/tuwien/kr/alpha/grounder/atoms/WeakConstraintAtom.java create mode 100644 src/main/java/at/ac/tuwien/kr/alpha/grounder/transformation/WeakConstraintNormalization.java diff --git a/src/main/antlr/at/ac/tuwien/kr/alpha/antlr/ASPCore2.g4 b/src/main/antlr/at/ac/tuwien/kr/alpha/antlr/ASPCore2.g4 index aeca5cc2d..e462cd80f 100644 --- a/src/main/antlr/at/ac/tuwien/kr/alpha/antlr/ASPCore2.g4 +++ b/src/main/antlr/at/ac/tuwien/kr/alpha/antlr/ASPCore2.g4 @@ -40,7 +40,7 @@ aggregate_element : basic_terms? (COLON naf_literals?)?; aggregate_function : AGGREGATE_COUNT | AGGREGATE_MAX | AGGREGATE_MIN | AGGREGATE_SUM; -weight_at_level : term (AT term)? (COMMA terms)?; +weight_at_level : weight=term (AT level=term)? (COMMA termlist=terms)?; naf_literals : naf_literal (COMMA naf_literals)?; diff --git a/src/main/java/at/ac/tuwien/kr/alpha/common/rule/WeakConstraint.java b/src/main/java/at/ac/tuwien/kr/alpha/common/rule/WeakConstraint.java new file mode 100644 index 000000000..025dfd060 --- /dev/null +++ b/src/main/java/at/ac/tuwien/kr/alpha/common/rule/WeakConstraint.java @@ -0,0 +1,43 @@ +package at.ac.tuwien.kr.alpha.common.rule; + +import at.ac.tuwien.kr.alpha.Util; +import at.ac.tuwien.kr.alpha.common.atoms.Literal; +import at.ac.tuwien.kr.alpha.common.terms.Term; + +import java.util.List; + +/** + * Represents a weak constraint. + * + * Copyright (c) 2020, the Alpha Team. + */ +public class WeakConstraint extends BasicRule { + + private final Term weight; + private final Term level; + private final List termList; + + public WeakConstraint(List body, Term weight, Term level, List termList) { + super(null, body); + this.weight = weight; + this.level = level; + this.termList = termList; + } + + public Term getWeight() { + return weight; + } + + public Term getLevel() { + return level; + } + + public List getTermList() { + return termList; + } + + @Override + public String toString() { + return Util.join(":~ ", getBody(), "."); + } +} diff --git a/src/main/java/at/ac/tuwien/kr/alpha/grounder/NaiveGrounder.java b/src/main/java/at/ac/tuwien/kr/alpha/grounder/NaiveGrounder.java index 7ce329c8c..707085587 100644 --- a/src/main/java/at/ac/tuwien/kr/alpha/grounder/NaiveGrounder.java +++ b/src/main/java/at/ac/tuwien/kr/alpha/grounder/NaiveGrounder.java @@ -196,7 +196,7 @@ private Set getRulesWithUniqueHead() { } // Collect head and body variables. - HashSet occurringVariablesHead = new HashSet<>(headAtom.toLiteral().getBindingVariables()); + HashSet occurringVariablesHead = new HashSet<>(headAtom.getOccurringVariables()); HashSet occurringVariablesBody = new HashSet<>(); for (Literal lit : nonGroundRule.getPositiveBody()) { occurringVariablesBody.addAll(lit.getBindingVariables()); diff --git a/src/main/java/at/ac/tuwien/kr/alpha/grounder/atoms/WeakConstraintAtom.java b/src/main/java/at/ac/tuwien/kr/alpha/grounder/atoms/WeakConstraintAtom.java new file mode 100644 index 000000000..09119519e --- /dev/null +++ b/src/main/java/at/ac/tuwien/kr/alpha/grounder/atoms/WeakConstraintAtom.java @@ -0,0 +1,139 @@ +package at.ac.tuwien.kr.alpha.grounder.atoms; + +import at.ac.tuwien.kr.alpha.common.Predicate; +import at.ac.tuwien.kr.alpha.common.atoms.Atom; +import at.ac.tuwien.kr.alpha.common.atoms.Literal; +import at.ac.tuwien.kr.alpha.common.terms.ConstantTerm; +import at.ac.tuwien.kr.alpha.common.terms.FunctionTerm; +import at.ac.tuwien.kr.alpha.common.terms.Term; +import at.ac.tuwien.kr.alpha.common.terms.VariableTerm; +import at.ac.tuwien.kr.alpha.grounder.Substitution; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Objects; +import java.util.Set; + +import static at.ac.tuwien.kr.alpha.Util.oops; + +/** + * Represents the head of a weak constraint (i.e., a special internal atom indicating that a rule really is a weak + * constraint). + * + * Copyright (c) 2020, the Alpha Team. + */ +public class WeakConstraintAtom extends Atom { + private static final Predicate PREDICATE = Predicate.getInstance("_weakconstraint_", 3, true, true); + private static final String TERMLISTSYMBOL = "_tuple"; + + private final Term weight; + private final Term level; + private final FunctionTerm termList; + + private WeakConstraintAtom(Term weight, Term level, FunctionTerm termList) { + this.weight = weight; + this.level = level; + this.termList = termList; + } + + public static WeakConstraintAtom getInstance(Term weight, Term level, List termList) { + if (!isIntegerOrVariable(weight)) { + throw new IllegalArgumentException("WeakConstraint with non-integer weight encountered: " + weight); + } + if (level != null && !isIntegerOrVariable(level)) { + throw new IllegalArgumentException("WeakConstraint with non-integer level encountered: " + level); + } + Term actualLevel = level != null ? level : ConstantTerm.getInstance(0); + List actualTermlist = termList != null ? termList : Collections.emptyList(); + return new WeakConstraintAtom(weight, actualLevel, FunctionTerm.getInstance(TERMLISTSYMBOL,actualTermlist)); + } + + private static boolean isIntegerOrVariable(Term term) { + if (term instanceof VariableTerm) { + return true; + } + if (term instanceof ConstantTerm) { + Comparable constant = ((ConstantTerm) term).getObject(); + return constant instanceof Integer; + } + return false; + } + + @Override + public Predicate getPredicate() { + return PREDICATE; + } + + @Override + public List getTerms() { + List ret = new ArrayList<>(3); + ret.add(weight); + ret.add(level); + ret.add(termList); + return ret; + } + + @Override + public Atom withTerms(List terms) { + if (terms.size() != 3) { + throw oops("Trying to create WeakConstraintAtom with other than 3 terms."); + } + if (!(terms.get(2) instanceof FunctionTerm) || !((FunctionTerm) terms.get(2)).getSymbol().equals(TERMLISTSYMBOL)) { + throw oops("Trying to create WeakConstraintAtom with 3rd term not being the correct function term."); + } + return new WeakConstraintAtom(terms.get(0), terms.get(1), (FunctionTerm) terms.get(2)); + } + + @Override + public boolean isGround() { + return weight.isGround() && level.isGround() && termList.isGround(); + } + + @Override + public Atom substitute(Substitution substitution) { + if (isGround()) { + return this; + } + return new WeakConstraintAtom(weight.substitute(substitution), level.substitute(substitution), termList.substitute(substitution)); + } + + @Override + public Set getOccurringVariables() { + Set occurringVariables = new HashSet<>(); + occurringVariables.addAll(weight.getOccurringVariables()); + occurringVariables.addAll(level.getOccurringVariables()); + occurringVariables.addAll(termList.getOccurringVariables()); + return occurringVariables; + } + + @Override + public String toString() { + return PREDICATE.getName() + "(" + weight + "@" + level + ", " + termList + ")"; + } + + @Override + public Literal toLiteral(boolean positive) { + throw new UnsupportedOperationException("WeakConstraintAtom cannot be literalized."); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + WeakConstraintAtom that = (WeakConstraintAtom) o; + return weight.equals(that.weight) && + level.equals(that.level) && + termList.equals(that.termList); + } + + @Override + public int hashCode() { + return Objects.hash(weight, level, termList); + } +} diff --git a/src/main/java/at/ac/tuwien/kr/alpha/grounder/parser/ParseTreeVisitor.java b/src/main/java/at/ac/tuwien/kr/alpha/grounder/parser/ParseTreeVisitor.java index fa08f314e..25c8f2f2a 100644 --- a/src/main/java/at/ac/tuwien/kr/alpha/grounder/parser/ParseTreeVisitor.java +++ b/src/main/java/at/ac/tuwien/kr/alpha/grounder/parser/ParseTreeVisitor.java @@ -47,6 +47,7 @@ import at.ac.tuwien.kr.alpha.common.fixedinterpretations.PredicateInterpretation; import at.ac.tuwien.kr.alpha.common.program.InputProgram; import at.ac.tuwien.kr.alpha.common.rule.BasicRule; +import at.ac.tuwien.kr.alpha.common.rule.WeakConstraint; import at.ac.tuwien.kr.alpha.common.rule.head.ChoiceHead; import at.ac.tuwien.kr.alpha.common.rule.head.Head; import at.ac.tuwien.kr.alpha.common.rule.head.NormalHead; @@ -202,7 +203,18 @@ public Object visitStatement_rule(ASPCore2Parser.Statement_ruleContext ctx) { @Override public Object visitStatement_weightConstraint(ASPCore2Parser.Statement_weightConstraintContext ctx) { // WCONS body? DOT SQUARE_OPEN weight_at_level SQUARE_CLOSE - throw notSupported(ctx); + List bodyLiterals = ctx.body() != null ? visitBody(ctx.body()) : emptyList(); + Term weight = (Term)visit(ctx.weight_at_level().weight); + Term level = ctx.weight_at_level().level != null ? (Term)visit(ctx.weight_at_level().level) : null; + List termList = ctx.weight_at_level().termlist != null ? visitTerms(ctx.weight_at_level().termlist) : null; + programBuilder.addRule(new WeakConstraint(bodyLiterals, weight, level, termList)); + return null; + } + + @Override + public Object visitWeight_at_level(ASPCore2Parser.Weight_at_levelContext ctx) { + // weight_at_level : term (AT term)? (COMMA terms)?; + return null; } @Override diff --git a/src/main/java/at/ac/tuwien/kr/alpha/grounder/transformation/IntervalTermToIntervalAtom.java b/src/main/java/at/ac/tuwien/kr/alpha/grounder/transformation/IntervalTermToIntervalAtom.java index 6faa93fbe..0b1f019b8 100644 --- a/src/main/java/at/ac/tuwien/kr/alpha/grounder/transformation/IntervalTermToIntervalAtom.java +++ b/src/main/java/at/ac/tuwien/kr/alpha/grounder/transformation/IntervalTermToIntervalAtom.java @@ -66,7 +66,7 @@ private static NormalRule rewriteIntervalSpecifications(NormalRule rule) { rewrittenBody.add(rewriteLiteral(literal, intervalReplacements)); } NormalHead rewrittenHead = rule.isConstraint() ? null : - new NormalHead(rewriteLiteral(rule.getHeadAtom().toLiteral(), intervalReplacements).getAtom()); + new NormalHead(rewriteAtom(rule.getHeadAtom(), intervalReplacements)); // If intervalReplacements is empty, no IntervalTerms have been found, keep rule as is. if (intervalReplacements.isEmpty()) { @@ -83,8 +83,7 @@ private static NormalRule rewriteIntervalSpecifications(NormalRule rule) { /** * Replaces every IntervalTerm by a new variable and returns a mapping of the replaced VariableTerm -> IntervalTerm. */ - private static Literal rewriteLiteral(Literal lit, Map intervalReplacement) { - Atom atom = lit.getAtom(); + private static Atom rewriteAtom(Atom atom, Map intervalReplacement) { List termList = new ArrayList<>(atom.getTerms()); boolean didChange = false; for (int i = 0; i < termList.size(); i++) { @@ -103,7 +102,18 @@ private static Literal rewriteLiteral(Literal lit, Map IntervalTerm. + */ + private static Literal rewriteLiteral(Literal lit, Map intervalReplacement) { + Atom atom = lit.getAtom(); + Atom rewrittenAtom = rewriteAtom(atom, intervalReplacement); + if (rewrittenAtom != atom) { return lit.isNegated() ? rewrittenAtom.toLiteral().negate() : rewrittenAtom.toLiteral(); } return lit; diff --git a/src/main/java/at/ac/tuwien/kr/alpha/grounder/transformation/NormalizeProgramTransformation.java b/src/main/java/at/ac/tuwien/kr/alpha/grounder/transformation/NormalizeProgramTransformation.java index 3d68d7e06..909129c33 100644 --- a/src/main/java/at/ac/tuwien/kr/alpha/grounder/transformation/NormalizeProgramTransformation.java +++ b/src/main/java/at/ac/tuwien/kr/alpha/grounder/transformation/NormalizeProgramTransformation.java @@ -26,6 +26,8 @@ public NormalProgram apply(InputProgram inputProgram) { tmpPrg = new CardinalityNormalization(!this.useNormalizationGrid).apply(tmpPrg); // Transform sum aggregates. tmpPrg = new SumNormalization().apply(tmpPrg); + // Transform weak constraints. + tmpPrg = new WeakConstraintNormalization().apply(tmpPrg); // Transform enumeration atoms. tmpPrg = new EnumerationRewriting().apply(tmpPrg); EnumerationAtom.resetEnumerations(); diff --git a/src/main/java/at/ac/tuwien/kr/alpha/grounder/transformation/WeakConstraintNormalization.java b/src/main/java/at/ac/tuwien/kr/alpha/grounder/transformation/WeakConstraintNormalization.java new file mode 100644 index 000000000..48fa79dc3 --- /dev/null +++ b/src/main/java/at/ac/tuwien/kr/alpha/grounder/transformation/WeakConstraintNormalization.java @@ -0,0 +1,37 @@ +package at.ac.tuwien.kr.alpha.grounder.transformation; + +import at.ac.tuwien.kr.alpha.common.program.InputProgram; +import at.ac.tuwien.kr.alpha.common.rule.BasicRule; +import at.ac.tuwien.kr.alpha.common.rule.WeakConstraint; +import at.ac.tuwien.kr.alpha.common.rule.head.Head; +import at.ac.tuwien.kr.alpha.common.rule.head.NormalHead; +import at.ac.tuwien.kr.alpha.grounder.atoms.WeakConstraintAtom; + +import java.util.ArrayList; + +/** + * Rewrites weak constraints into normal rules with special head atom. + * + * Copyright (c) 2020, the Alpha Team. + */ +public class WeakConstraintNormalization extends ProgramTransformation { + @Override + public InputProgram apply(InputProgram inputProgram) { + InputProgram.Builder builder = new InputProgram.Builder(); + builder.addFacts(inputProgram.getFacts()); + builder.addInlineDirectives(inputProgram.getInlineDirectives()); + + for (BasicRule rule : inputProgram.getRules()) { + if (!(rule instanceof WeakConstraint)) { + builder.addRule(rule); + continue; + } + // Create rule with special head atom. + WeakConstraint weakConstraint = (WeakConstraint)rule; + Head weakConstraintHead = new NormalHead(WeakConstraintAtom.getInstance(weakConstraint.getWeight(), weakConstraint.getLevel(), weakConstraint.getTermList())); + BasicRule rewrittenWeakConstraint = new BasicRule(weakConstraintHead, new ArrayList<>(rule.getBody())); + builder.addRule(rewrittenWeakConstraint); + } + return builder.build(); + } +} From e8f090890fc03b7f999a7adc0431a1489be6ae72 Mon Sep 17 00:00:00 2001 From: Antonius Weinzierl Date: Mon, 19 Oct 2020 17:37:04 +0200 Subject: [PATCH 02/27] Refactor callback management for atoms in solver, grounder reports weak constraints to solver. - Introduce AtomCallbackManager. - Move callback functionality out of ChoiceManager. - Assignment and TrailAssignment work with AtomCallbackManager. - Add WeakConstraintsManager for weak constraint callbacks. - Callbacks for choice atoms now handled in ChoicePoint directly. - NoGoodGenerator generates nogoods representing weak constraints. - Solver relays weak constraint information to WeakConstraintsManager. --- .../ac/tuwien/kr/alpha/grounder/Grounder.java | 10 +++ .../kr/alpha/grounder/NaiveGrounder.java | 10 ++- .../kr/alpha/grounder/NoGoodGenerator.java | 42 ++++++---- .../grounder/WeakConstraintRecorder.java | 27 +++++++ .../grounder/atoms/WeakConstraintAtom.java | 10 ++- .../kr/alpha/solver/AtomCallbackManager.java | 59 ++++++++++++++ .../alpha/solver/ChoiceInfluenceManager.java | 22 +++--- .../tuwien/kr/alpha/solver/ChoiceManager.java | 7 +- .../tuwien/kr/alpha/solver/DefaultSolver.java | 49 ++++++------ .../kr/alpha/solver/TrailAssignment.java | 9 ++- .../alpha/solver/WeakConstraintsManager.java | 79 +++++++++++++++++++ .../kr/alpha/solver/WritableAssignment.java | 2 +- .../kr/alpha/grounder/ChoiceGrounder.java | 10 ++- .../kr/alpha/grounder/DummyGrounder.java | 10 ++- 14 files changed, 282 insertions(+), 64 deletions(-) create mode 100644 src/main/java/at/ac/tuwien/kr/alpha/grounder/WeakConstraintRecorder.java create mode 100644 src/main/java/at/ac/tuwien/kr/alpha/solver/AtomCallbackManager.java create mode 100644 src/main/java/at/ac/tuwien/kr/alpha/solver/WeakConstraintsManager.java diff --git a/src/main/java/at/ac/tuwien/kr/alpha/grounder/Grounder.java b/src/main/java/at/ac/tuwien/kr/alpha/grounder/Grounder.java index 41849fa53..b72f803e3 100644 --- a/src/main/java/at/ac/tuwien/kr/alpha/grounder/Grounder.java +++ b/src/main/java/at/ac/tuwien/kr/alpha/grounder/Grounder.java @@ -33,7 +33,9 @@ import at.ac.tuwien.kr.alpha.common.NoGood; import at.ac.tuwien.kr.alpha.grounder.atoms.RuleAtom; import org.apache.commons.lang3.tuple.Pair; +import org.apache.commons.lang3.tuple.Triple; +import java.util.List; import java.util.Map; import java.util.Set; @@ -60,6 +62,14 @@ public interface Grounder { */ Pair, Map> getChoiceAtoms(); + /** + * Returns the atomId of atoms representing weak constraint and the respective weight and level. + * Must be preceeded by a call to getNoGoods(). + * @return a list of triples (atomId, weight, level) where atomId is the atom that becomes true whenever the + * corresponding weak constraint is violated. + */ + List> getWeakConstraintInformation(); + /** * Updates the grounder with atoms assigned a positive truth value. * @param it an iterator over all newly assigned positive atoms. diff --git a/src/main/java/at/ac/tuwien/kr/alpha/grounder/NaiveGrounder.java b/src/main/java/at/ac/tuwien/kr/alpha/grounder/NaiveGrounder.java index 662e007ad..9f4afc114 100644 --- a/src/main/java/at/ac/tuwien/kr/alpha/grounder/NaiveGrounder.java +++ b/src/main/java/at/ac/tuwien/kr/alpha/grounder/NaiveGrounder.java @@ -54,6 +54,7 @@ import at.ac.tuwien.kr.alpha.grounder.structure.AnalyzeUnjustified; import org.apache.commons.lang3.tuple.ImmutablePair; import org.apache.commons.lang3.tuple.Pair; +import org.apache.commons.lang3.tuple.Triple; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -86,6 +87,7 @@ public class NaiveGrounder extends BridgedGrounder implements ProgramAnalyzingGr private final NogoodRegistry registry = new NogoodRegistry(); final NoGoodGenerator noGoodGenerator; private final ChoiceRecorder choiceRecorder; + private final WeakConstraintRecorder weakConstraintRecorder; private final InternalProgram program; private final AnalyzeUnjustified analyzeUnjustified; @@ -129,7 +131,8 @@ private NaiveGrounder(InternalProgram program, AtomStore atomStore, GrounderHeur final Set uniqueGroundRulePerGroundHead = getRulesWithUniqueHead(); choiceRecorder = new ChoiceRecorder(atomStore); - noGoodGenerator = new NoGoodGenerator(atomStore, choiceRecorder, factsFromProgram, this.program, uniqueGroundRulePerGroundHead); + weakConstraintRecorder = new WeakConstraintRecorder(); + noGoodGenerator = new NoGoodGenerator(atomStore, choiceRecorder, weakConstraintRecorder, factsFromProgram, this.program, uniqueGroundRulePerGroundHead); this.debugInternalChecks = debugInternalChecks; @@ -561,6 +564,11 @@ public Map> getHeadsToBodies() { return choiceRecorder.getAndResetHeadsToBodies(); } + @Override + public List> getWeakConstraintInformation() { + return weakConstraintRecorder.getAndResetWeakConstraintAtomWeightLevels(); + } + @Override public void updateAssignment(IntIterator it) { while (it.hasNext()) { diff --git a/src/main/java/at/ac/tuwien/kr/alpha/grounder/NoGoodGenerator.java b/src/main/java/at/ac/tuwien/kr/alpha/grounder/NoGoodGenerator.java index 75a27929d..9f2f5e911 100644 --- a/src/main/java/at/ac/tuwien/kr/alpha/grounder/NoGoodGenerator.java +++ b/src/main/java/at/ac/tuwien/kr/alpha/grounder/NoGoodGenerator.java @@ -27,19 +27,6 @@ */ package at.ac.tuwien.kr.alpha.grounder; -import static at.ac.tuwien.kr.alpha.common.Literals.atomOf; -import static at.ac.tuwien.kr.alpha.common.Literals.atomToLiteral; -import static at.ac.tuwien.kr.alpha.common.Literals.negateLiteral; -import static java.util.Collections.emptyList; -import static java.util.Collections.singletonList; - -import java.util.ArrayList; -import java.util.HashSet; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - import at.ac.tuwien.kr.alpha.common.AtomStore; import at.ac.tuwien.kr.alpha.common.NoGood; import at.ac.tuwien.kr.alpha.common.Predicate; @@ -50,6 +37,20 @@ import at.ac.tuwien.kr.alpha.common.rule.InternalRule; import at.ac.tuwien.kr.alpha.grounder.atoms.EnumerationAtom; import at.ac.tuwien.kr.alpha.grounder.atoms.RuleAtom; +import at.ac.tuwien.kr.alpha.grounder.atoms.WeakConstraintAtom; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import static at.ac.tuwien.kr.alpha.common.Literals.atomOf; +import static at.ac.tuwien.kr.alpha.common.Literals.atomToLiteral; +import static at.ac.tuwien.kr.alpha.common.Literals.negateLiteral; +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; /** * Class to generate ground NoGoods out of non-ground rules and grounding substitutions. @@ -58,13 +59,15 @@ public class NoGoodGenerator { private final AtomStore atomStore; private final ChoiceRecorder choiceRecorder; + private final WeakConstraintRecorder weakConstraintRecorder; private final Map> factsFromProgram; private final InternalProgram programAnalysis; private final Set uniqueGroundRulePerGroundHead; - NoGoodGenerator(AtomStore atomStore, ChoiceRecorder recorder, Map> factsFromProgram, InternalProgram programAnalysis, Set uniqueGroundRulePerGroundHead) { + NoGoodGenerator(AtomStore atomStore, ChoiceRecorder recorder, WeakConstraintRecorder weakConstraintRecorder, Map> factsFromProgram, InternalProgram programAnalysis, Set uniqueGroundRulePerGroundHead) { this.atomStore = atomStore; this.choiceRecorder = recorder; + this.weakConstraintRecorder = weakConstraintRecorder; this.factsFromProgram = factsFromProgram; this.programAnalysis = programAnalysis; this.uniqueGroundRulePerGroundHead = uniqueGroundRulePerGroundHead; @@ -97,6 +100,17 @@ List generateNoGoodsFromGroundSubstitution(final InternalRule nonGroundR final Atom groundHeadAtom = nonGroundRule.getHeadAtom().substitute(substitution); final int headId = atomStore.putIfAbsent(groundHeadAtom); + + if (groundHeadAtom instanceof WeakConstraintAtom) { + WeakConstraintAtom weakConstraintAtom = (WeakConstraintAtom) groundHeadAtom; + // Treat weak constraints: only generate nogood for the if-direction (body satisfied causes head to be true). + NoGood wcRule = NoGood.fromBodyInternal(posLiterals, negLiterals, headId); + result.add(wcRule); + + // Record weak constraint association. + weakConstraintRecorder.addWeakConstraint(headId, weakConstraintAtom.getWeight(), weakConstraintAtom.getLevel()); + return result; + } // Prepare atom representing the rule body. final RuleAtom bodyAtom = new RuleAtom(nonGroundRule, substitution); diff --git a/src/main/java/at/ac/tuwien/kr/alpha/grounder/WeakConstraintRecorder.java b/src/main/java/at/ac/tuwien/kr/alpha/grounder/WeakConstraintRecorder.java new file mode 100644 index 000000000..9b16a4cb8 --- /dev/null +++ b/src/main/java/at/ac/tuwien/kr/alpha/grounder/WeakConstraintRecorder.java @@ -0,0 +1,27 @@ +package at.ac.tuwien.kr.alpha.grounder; + +import org.apache.commons.lang3.tuple.ImmutableTriple; +import org.apache.commons.lang3.tuple.Triple; + +import java.util.ArrayList; +import java.util.List; + +/** + * Temporarily stores associations of weak constraint atoms, and their weights and levels. + * + * Copyright (c) 2020, the Alpha Team. + */ +class WeakConstraintRecorder { + + private ArrayList> weakConstraintAtomWeightLevels = new ArrayList<>(); + + void addWeakConstraint(int headId, Integer weight, Integer level) { + weakConstraintAtomWeightLevels.add(new ImmutableTriple<>(headId, weight, level)); + } + + List> getAndResetWeakConstraintAtomWeightLevels() { + List> currentWeakConstraintAtomWeightLevels = weakConstraintAtomWeightLevels; + this.weakConstraintAtomWeightLevels = new ArrayList<>(); + return currentWeakConstraintAtomWeightLevels; + } +} diff --git a/src/main/java/at/ac/tuwien/kr/alpha/grounder/atoms/WeakConstraintAtom.java b/src/main/java/at/ac/tuwien/kr/alpha/grounder/atoms/WeakConstraintAtom.java index 09119519e..801ed98e8 100644 --- a/src/main/java/at/ac/tuwien/kr/alpha/grounder/atoms/WeakConstraintAtom.java +++ b/src/main/java/at/ac/tuwien/kr/alpha/grounder/atoms/WeakConstraintAtom.java @@ -47,7 +47,15 @@ public static WeakConstraintAtom getInstance(Term weight, Term level, List } Term actualLevel = level != null ? level : ConstantTerm.getInstance(0); List actualTermlist = termList != null ? termList : Collections.emptyList(); - return new WeakConstraintAtom(weight, actualLevel, FunctionTerm.getInstance(TERMLISTSYMBOL,actualTermlist)); + return new WeakConstraintAtom(weight, actualLevel, FunctionTerm.getInstance(TERMLISTSYMBOL, actualTermlist)); + } + + public Integer getWeight() { + return (Integer) ((ConstantTerm)weight).getObject(); + } + + public Integer getLevel() { + return (Integer) ((ConstantTerm)level).getObject(); } private static boolean isIntegerOrVariable(Term term) { diff --git a/src/main/java/at/ac/tuwien/kr/alpha/solver/AtomCallbackManager.java b/src/main/java/at/ac/tuwien/kr/alpha/solver/AtomCallbackManager.java new file mode 100644 index 000000000..b57b939d7 --- /dev/null +++ b/src/main/java/at/ac/tuwien/kr/alpha/solver/AtomCallbackManager.java @@ -0,0 +1,59 @@ +package at.ac.tuwien.kr.alpha.solver; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Arrays; + +import static at.ac.tuwien.kr.alpha.Util.arrayGrowthSize; +import static at.ac.tuwien.kr.alpha.Util.oops; + +/** + * Manages the execution of {@link AtomCallback}s, i.e., callbacks when atoms change their truth value. + * + * Copyright (c) 2020, the Alpha Team. + */ +class AtomCallbackManager { + + private static final Logger LOGGER = LoggerFactory.getLogger(AtomCallbackManager.class); + + private AtomCallback[] atomCallbacks = new AtomCallback[0]; + + void recordCallback(int atom, AtomCallback atomCallback) { + if (atomCallbacks[atom] != null) { + throw oops("Recording callback for atom twice. Atom: " + atom); + } + atomCallbacks[atom] = atomCallback; + } + + void callbackOnChanged(int atom) { + if (LOGGER.isTraceEnabled()) { + LOGGER.trace("Callback received on callback atom: {}", atom); + } + AtomCallback atomCallback = atomCallbacks[atom]; + if (atomCallback != null) { + atomCallback.processCallback(); + } + } + + void growForMaxAtomId(int maxAtomId) { + // Grow arrays only if needed. + if (atomCallbacks.length > maxAtomId) { + return; + } + // Grow to default size, except if bigger array is required due to maxAtomId. + int newCapacity = arrayGrowthSize(atomCallbacks.length); + if (newCapacity < maxAtomId + 1) { + newCapacity = maxAtomId + 1; + } + atomCallbacks = Arrays.copyOf(atomCallbacks, newCapacity); + } + + /** + * Interface for callbacks to be called when atoms change their truth value. + */ + interface AtomCallback { + + void processCallback(); + } +} diff --git a/src/main/java/at/ac/tuwien/kr/alpha/solver/ChoiceInfluenceManager.java b/src/main/java/at/ac/tuwien/kr/alpha/solver/ChoiceInfluenceManager.java index 62624eecd..41e23e40b 100644 --- a/src/main/java/at/ac/tuwien/kr/alpha/solver/ChoiceInfluenceManager.java +++ b/src/main/java/at/ac/tuwien/kr/alpha/solver/ChoiceInfluenceManager.java @@ -92,6 +92,9 @@ private void addInformation(Map.Entry atomToEnabler, Map maxAtomId) { @@ -177,7 +170,7 @@ void setActivityListener(ActivityListener listener) { this.activityListener = listener; } - private class ChoicePoint { + private class ChoicePoint implements AtomCallbackManager.AtomCallback { final Integer atom; final int enabler; final int disabler; @@ -201,6 +194,11 @@ private boolean isNotChosen() { return atomTruth == null || atomTruth == MBT; } + @Override + public void processCallback() { + recomputeActive(); + } + void recomputeActive() { LOGGER.trace("Recomputing activity of atom {}.", atom); final boolean wasActive = isActive; @@ -228,7 +226,7 @@ public String toString() { } } - public static interface ActivityListener { + public interface ActivityListener { void callbackOnChanged(int atom, boolean active); } diff --git a/src/main/java/at/ac/tuwien/kr/alpha/solver/ChoiceManager.java b/src/main/java/at/ac/tuwien/kr/alpha/solver/ChoiceManager.java index 5feb085a3..353e6613d 100644 --- a/src/main/java/at/ac/tuwien/kr/alpha/solver/ChoiceManager.java +++ b/src/main/java/at/ac/tuwien/kr/alpha/solver/ChoiceManager.java @@ -73,7 +73,6 @@ public ChoiceManager(WritableAssignment assignment, NoGoodStore store) { this.assignment = assignment; this.choicePointInfluenceManager = new ChoiceInfluenceManager(assignment); this.choiceStack = new Stack<>(); - assignment.setCallback(this); this.bnpEstimation = store instanceof BinaryNoGoodPropagationEstimation ? (BinaryNoGoodPropagationEstimation)store : null; @@ -95,7 +94,7 @@ NoGood computeEnumeration() { @Override public void setChecksEnabled(boolean checksEnabled) { this.checksEnabled = checksEnabled; - this.choicePointInfluenceManager.setChecksEnabled(checksEnabled); + choicePointInfluenceManager.setChecksEnabled(checksEnabled); if (checksEnabled) { debugWatcher = new DebugWatcher(); @@ -108,10 +107,6 @@ public boolean isChecksEnabled() { return checksEnabled; } - public void callbackOnChanged(int atom) { - choicePointInfluenceManager.callbackOnChanged(atom); - } - public int getBackjumps() { return backjumps; } diff --git a/src/main/java/at/ac/tuwien/kr/alpha/solver/DefaultSolver.java b/src/main/java/at/ac/tuwien/kr/alpha/solver/DefaultSolver.java index a98d2c508..fbb505401 100644 --- a/src/main/java/at/ac/tuwien/kr/alpha/solver/DefaultSolver.java +++ b/src/main/java/at/ac/tuwien/kr/alpha/solver/DefaultSolver.java @@ -27,29 +27,6 @@ */ package at.ac.tuwien.kr.alpha.solver; -import static at.ac.tuwien.kr.alpha.Util.oops; -import static at.ac.tuwien.kr.alpha.common.Literals.atomOf; -import static at.ac.tuwien.kr.alpha.common.Literals.atomToLiteral; -import static at.ac.tuwien.kr.alpha.common.Literals.atomToNegatedLiteral; -import static at.ac.tuwien.kr.alpha.solver.NoGoodStore.LBD_NO_VALUE; -import static at.ac.tuwien.kr.alpha.solver.ThriceTruth.MBT; -import static at.ac.tuwien.kr.alpha.solver.heuristics.BranchingHeuristic.DEFAULT_CHOICE_LITERAL; -import static at.ac.tuwien.kr.alpha.solver.learning.GroundConflictNoGoodLearner.ConflictAnalysisResult.UNSAT; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.ArrayList; -import java.util.Iterator; -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.LinkedList; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Random; -import java.util.Set; -import java.util.function.Consumer; - import at.ac.tuwien.kr.alpha.common.AnswerSet; import at.ac.tuwien.kr.alpha.common.Assignment; import at.ac.tuwien.kr.alpha.common.AtomStore; @@ -71,6 +48,28 @@ import at.ac.tuwien.kr.alpha.solver.heuristics.HeuristicsConfiguration; import at.ac.tuwien.kr.alpha.solver.heuristics.NaiveHeuristic; import at.ac.tuwien.kr.alpha.solver.learning.GroundConflictNoGoodLearner; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.LinkedList; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Random; +import java.util.Set; +import java.util.function.Consumer; + +import static at.ac.tuwien.kr.alpha.Util.oops; +import static at.ac.tuwien.kr.alpha.common.Literals.atomOf; +import static at.ac.tuwien.kr.alpha.common.Literals.atomToLiteral; +import static at.ac.tuwien.kr.alpha.common.Literals.atomToNegatedLiteral; +import static at.ac.tuwien.kr.alpha.solver.NoGoodStore.LBD_NO_VALUE; +import static at.ac.tuwien.kr.alpha.solver.ThriceTruth.MBT; +import static at.ac.tuwien.kr.alpha.solver.heuristics.BranchingHeuristic.DEFAULT_CHOICE_LITERAL; +import static at.ac.tuwien.kr.alpha.solver.learning.GroundConflictNoGoodLearner.ConflictAnalysisResult.UNSAT; /** * The new default solver employed in Alpha. @@ -82,6 +81,7 @@ public class DefaultSolver extends AbstractSolver implements SolverMaintainingSt private final NoGoodStore store; private final ChoiceManager choiceManager; + private final WeakConstraintsManager weakConstraintsManager; private final WritableAssignment assignment; private final GroundConflictNoGoodLearner learner; @@ -103,6 +103,7 @@ public DefaultSolver(AtomStore atomStore, Grounder grounder, NoGoodStore store, this.assignment = assignment; this.store = store; this.choiceManager = new ChoiceManager(assignment, store); + this.weakConstraintsManager = new WeakConstraintsManager(assignment); this.learner = new GroundConflictNoGoodLearner(assignment, atomStore); this.branchingHeuristic = chainFallbackHeuristic(grounder, assignment, random, heuristicsConfiguration); this.disableJustifications = config.isDisableJustificationSearch(); @@ -134,6 +135,7 @@ protected boolean tryAdvance(Consumer action) { logStats(); return false; } + weakConstraintsManager.addWeakConstraintsInformation(grounder.getWeakConstraintInformation()); initialize = false; } else if (assignment.getDecisionLevel() == 0) { logStats(); @@ -202,6 +204,7 @@ protected boolean tryAdvance(Consumer action) { logStats(); return false; } + weakConstraintsManager.addWeakConstraintsInformation(grounder.getWeakConstraintInformation()); } else if (choose()) { LOGGER.debug("Did choice."); didChange = true; diff --git a/src/main/java/at/ac/tuwien/kr/alpha/solver/TrailAssignment.java b/src/main/java/at/ac/tuwien/kr/alpha/solver/TrailAssignment.java index 4e2974d51..2dd5a0361 100644 --- a/src/main/java/at/ac/tuwien/kr/alpha/solver/TrailAssignment.java +++ b/src/main/java/at/ac/tuwien/kr/alpha/solver/TrailAssignment.java @@ -69,7 +69,7 @@ public void decreaseActivity() { }; private final AtomStore atomStore; - private ChoiceManager choiceManagerCallback; + private AtomCallbackManager atomCallbackManager = new AtomCallbackManager(); /** * Contains for each known atom a value whose two least @@ -139,8 +139,8 @@ public void registerCallbackOnChange(int atom) { } @Override - public void setCallback(ChoiceManager choiceManager) { - choiceManagerCallback = choiceManager; + public AtomCallbackManager getAtomCallbackManager() { + return atomCallbackManager; } @Override @@ -204,7 +204,7 @@ int getOutOfOrderStrongDecisionLevel(int atom) { private void informCallback(int atom) { if (callbackUponChange[atom]) { - choiceManagerCallback.callbackOnChanged(atom); + atomCallbackManager.callbackOnChanged(atom); } } @@ -544,6 +544,7 @@ public void growForMaxAtomId() { impliedBy = Arrays.copyOf(impliedBy, newCapacity); callbackUponChange = Arrays.copyOf(callbackUponChange, newCapacity); trail = Arrays.copyOf(trail, newCapacity * 2); // Trail has at most 2 assignments (MBT+TRUE) for each atom. + atomCallbackManager.growForMaxAtomId(maxAtomId); } public int getNumberOfAssignedAtoms() { diff --git a/src/main/java/at/ac/tuwien/kr/alpha/solver/WeakConstraintsManager.java b/src/main/java/at/ac/tuwien/kr/alpha/solver/WeakConstraintsManager.java new file mode 100644 index 000000000..2565a79ee --- /dev/null +++ b/src/main/java/at/ac/tuwien/kr/alpha/solver/WeakConstraintsManager.java @@ -0,0 +1,79 @@ +package at.ac.tuwien.kr.alpha.solver; + +import at.ac.tuwien.kr.alpha.common.NoGood; +import org.apache.commons.lang3.tuple.Triple; + +import java.util.ArrayList; +import java.util.List; + +/** + * Manages weak constraints: stores the value of the current partial assignment, the best known value of any answer-set + * and handles callbacks/computations when atoms (that represent weak constraints) change their truth value. + * + * Copyright (c) 2020, the Alpha Team. + */ +public class WeakConstraintsManager { + + private ArrayList currentWeightAtLevels; + private ArrayList bestKnownWeightAtLevels; + private final WritableAssignment assignment; + private boolean isCurrentWorseThanBest; + + public WeakConstraintsManager(WritableAssignment assignment) { + this.assignment = assignment; + this.currentWeightAtLevels = new ArrayList<>(); + this.bestKnownWeightAtLevels = new ArrayList<>(); + this.isCurrentWorseThanBest = false; + } + + public void addWeakConstraintsInformation(List> weakConstraintAtomWeightLevels) { + // Register all newly obtained weak constraint atoms for callback at the assignment. + for (Triple weakConstraintAtomWeightLevel : weakConstraintAtomWeightLevels) { + WeakConstraintAtom wcA = new WeakConstraintAtom(weakConstraintAtomWeightLevel.getLeft(), weakConstraintAtomWeightLevel.getMiddle(), weakConstraintAtomWeightLevel.getRight()); + assignment.getAtomCallbackManager().recordCallback(wcA.atom, wcA); + } + } + + /** + * Marks the current weight as the one of the currently best of any known answer-set. + */ + public void markCurrentWeightAsBestKnown() { + bestKnownWeightAtLevels = currentWeightAtLevels; + } + + /** + * Returns whether the current partial interpretation is already worse that the best-known answer-set. + * @return true if the current partial interpretation has worse weight than the best-known answer-set. + */ + public boolean isCurrentWorseThanBest() { + return isCurrentWorseThanBest; + } + + /** + * Generates a NoGood that prevents the current weight to be derived again. + * @return + */ + public NoGood generateExcludingNoGood() { + return null; + } + + class WeakConstraintAtom implements AtomCallbackManager.AtomCallback { + private final int atom; + private final int weight; + private final int level; + + WeakConstraintAtom(int atom, int weight, int level) { + this.atom = atom; + this.weight = weight; + this.level = level; + } + + @Override + public void processCallback() { + // TODO: check if atom became true or unassigned/false, then increase or decrease weights. + // TODO: for increase apply weight to respective level of current weightatlevels. + // TODO: for decrease, remove weight from respective level of current weightatlevels. + // TODO: also adapt isCurrentWorseThanBest. + } + } +} diff --git a/src/main/java/at/ac/tuwien/kr/alpha/solver/WritableAssignment.java b/src/main/java/at/ac/tuwien/kr/alpha/solver/WritableAssignment.java index 806a29957..f6cd95ab9 100644 --- a/src/main/java/at/ac/tuwien/kr/alpha/solver/WritableAssignment.java +++ b/src/main/java/at/ac/tuwien/kr/alpha/solver/WritableAssignment.java @@ -74,7 +74,7 @@ default ConflictCause assign(int atom, ThriceTruth value) { void registerCallbackOnChange(int atom); - void setCallback(ChoiceManager choiceManager); + AtomCallbackManager getAtomCallbackManager(); default ConflictCause choose(int atom, boolean value) { return choose(atom, ThriceTruth.valueOf(value)); diff --git a/src/test/java/at/ac/tuwien/kr/alpha/grounder/ChoiceGrounder.java b/src/test/java/at/ac/tuwien/kr/alpha/grounder/ChoiceGrounder.java index e090fec20..3699829d2 100644 --- a/src/test/java/at/ac/tuwien/kr/alpha/grounder/ChoiceGrounder.java +++ b/src/test/java/at/ac/tuwien/kr/alpha/grounder/ChoiceGrounder.java @@ -45,11 +45,13 @@ import at.ac.tuwien.kr.alpha.grounder.atoms.RuleAtom; import org.apache.commons.lang3.tuple.ImmutablePair; import org.apache.commons.lang3.tuple.Pair; +import org.apache.commons.lang3.tuple.Triple; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; +import java.util.List; import java.util.Map; import java.util.Set; import java.util.SortedSet; @@ -61,6 +63,7 @@ import static at.ac.tuwien.kr.alpha.common.NoGood.headFirst; import static at.ac.tuwien.kr.alpha.common.NoGoodTest.fromOldLiterals; import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; import static java.util.Collections.singleton; /** @@ -182,7 +185,12 @@ public Pair, Map> getChoiceAtoms() { return new ImmutablePair<>(new HashMap<>(), new HashMap<>()); } } - + + @Override + public List> getWeakConstraintInformation() { + return emptyList(); + } + @Override public Map> getHeadsToBodies() { return Collections.emptyMap(); diff --git a/src/test/java/at/ac/tuwien/kr/alpha/grounder/DummyGrounder.java b/src/test/java/at/ac/tuwien/kr/alpha/grounder/DummyGrounder.java index 4ab84e0b2..091f221b2 100644 --- a/src/test/java/at/ac/tuwien/kr/alpha/grounder/DummyGrounder.java +++ b/src/test/java/at/ac/tuwien/kr/alpha/grounder/DummyGrounder.java @@ -44,11 +44,13 @@ import at.ac.tuwien.kr.alpha.grounder.atoms.RuleAtom; import org.apache.commons.lang3.tuple.ImmutablePair; import org.apache.commons.lang3.tuple.Pair; +import org.apache.commons.lang3.tuple.Triple; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; +import java.util.List; import java.util.Map; import java.util.Set; import java.util.SortedSet; @@ -59,6 +61,7 @@ import static at.ac.tuwien.kr.alpha.Util.entry; import static at.ac.tuwien.kr.alpha.common.NoGood.headFirst; import static at.ac.tuwien.kr.alpha.common.NoGoodTest.fromOldLiterals; +import static java.util.Collections.emptyList; import static java.util.Collections.singleton; import static java.util.Collections.singletonList; @@ -167,7 +170,12 @@ public Map getNoGoods(Assignment assignment) { public Pair, Map> getChoiceAtoms() { return new ImmutablePair<>(new HashMap<>(), new HashMap<>()); } - + + @Override + public List> getWeakConstraintInformation() { + return emptyList(); + } + @Override public Map> getHeadsToBodies() { return Collections.emptyMap(); From 26c0a7a21560219a479fd5e101a4c275b20cb934 Mon Sep 17 00:00:00 2001 From: Antonius Weinzierl Date: Mon, 26 Oct 2020 21:39:42 +0100 Subject: [PATCH 03/27] Enable weak constraints handling in solver; add containsWeakConstraints to internal Program. - Add containsWeakConstraints to InternalProgram, and modify AnalyzedProgram, Grounder, NaiveGrounder, StratifiedEvaluation, Alpha, ChoiceGrounder, DummyGrounder accordingly. - Introduce WeightedAnswerSet, adapt BasicAnswerSet, DefaultSolver, Main to print its valuation. - Fix bugs in NoGoodGenerator and AtomCallbackManager. - DefaultSolver switches to branch-and-bound if weak constraints are in input program. - WeakConstraintsManager maintains valuation of current and best known assignment/answer set. - Fix issue #272 in DefaultSolver. --- src/main/java/at/ac/tuwien/kr/alpha/Main.java | 14 +- .../java/at/ac/tuwien/kr/alpha/api/Alpha.java | 2 +- .../kr/alpha/common/BasicAnswerSet.java | 4 + .../kr/alpha/common/WeightedAnswerSet.java | 39 +++ .../alpha/common/program/AnalyzedProgram.java | 10 +- .../alpha/common/program/InternalProgram.java | 22 +- .../ac/tuwien/kr/alpha/grounder/Grounder.java | 6 + .../kr/alpha/grounder/NaiveGrounder.java | 5 + .../kr/alpha/grounder/NoGoodGenerator.java | 2 +- .../transformation/StratifiedEvaluation.java | 2 +- .../kr/alpha/solver/AtomCallbackManager.java | 4 +- .../tuwien/kr/alpha/solver/DefaultSolver.java | 24 +- .../alpha/solver/WeakConstraintsManager.java | 247 ++++++++++++++++-- .../kr/alpha/grounder/ChoiceGrounder.java | 5 + .../kr/alpha/grounder/DummyGrounder.java | 5 + 15 files changed, 352 insertions(+), 39 deletions(-) create mode 100644 src/main/java/at/ac/tuwien/kr/alpha/common/WeightedAnswerSet.java diff --git a/src/main/java/at/ac/tuwien/kr/alpha/Main.java b/src/main/java/at/ac/tuwien/kr/alpha/Main.java index bcc79c8e9..ed183ce6a 100644 --- a/src/main/java/at/ac/tuwien/kr/alpha/Main.java +++ b/src/main/java/at/ac/tuwien/kr/alpha/Main.java @@ -31,6 +31,7 @@ import at.ac.tuwien.kr.alpha.common.AnswerSet; import at.ac.tuwien.kr.alpha.common.AnswerSetFormatter; import at.ac.tuwien.kr.alpha.common.SimpleAnswerSetFormatter; +import at.ac.tuwien.kr.alpha.common.WeightedAnswerSet; import at.ac.tuwien.kr.alpha.common.depgraph.ComponentGraph; import at.ac.tuwien.kr.alpha.common.depgraph.DependencyGraph; import at.ac.tuwien.kr.alpha.common.graphio.ComponentGraphWriter; @@ -188,7 +189,11 @@ private static void computeAndConsumeAnswerSets(Alpha alpha, InputConfig inputCf final BiConsumer answerSetHandler; final AnswerSetFormatter fmt = new SimpleAnswerSetFormatter(alpha.getConfig().getAtomSeparator()); BiConsumer stdoutPrinter = (n, as) -> { - System.out.println("Answer set " + Integer.toString(n) + ":" + System.lineSeparator() + fmt.format(as)); + System.out.println("Answer set " + n + ":" + System.lineSeparator() + fmt.format(as)); + if (program.containsWeakConstraints()) { + // If weak constraints are presents, all answer sets are weighted. + System.out.println("Optimization: " + ((WeightedAnswerSet) as).getWeightsAsString()); + } }; if (inputCfg.isWriteAnswerSetsAsXlsx()) { BiConsumer xlsxWriter = new AnswerSetToXlsxWriter(inputCfg.getAnswerSetFileOutputPath()); @@ -210,7 +215,12 @@ private static void computeAndConsumeAnswerSets(Alpha alpha, InputConfig inputCf } } } else { - System.out.println("SATISFIABLE"); + if (program.containsWeakConstraints() && counter.get() < limit) { + // Note: this ignores the case where n answer sets are requested and the n-th is the optimum. For this, a solver state is needed. + System.out.println("OPTIMUM FOUND"); + } else { + System.out.println("SATISFIABLE"); + } } } else { // Note: Even though we are not consuming the result, we will still compute diff --git a/src/main/java/at/ac/tuwien/kr/alpha/api/Alpha.java b/src/main/java/at/ac/tuwien/kr/alpha/api/Alpha.java index 6a40b6a6c..9168e8842 100644 --- a/src/main/java/at/ac/tuwien/kr/alpha/api/Alpha.java +++ b/src/main/java/at/ac/tuwien/kr/alpha/api/Alpha.java @@ -128,7 +128,7 @@ public InternalProgram performProgramPreprocessing(InternalProgram program) { LOGGER.debug("Preprocessing InternalProgram!"); InternalProgram retVal = program; if (config.isEvaluateStratifiedPart()) { - AnalyzedProgram analyzed = new AnalyzedProgram(program.getRules(), program.getFacts()); + AnalyzedProgram analyzed = new AnalyzedProgram(program.getRules(), program.getFacts(), program.containsWeakConstraints()); retVal = new StratifiedEvaluation().apply(analyzed); } return retVal; diff --git a/src/main/java/at/ac/tuwien/kr/alpha/common/BasicAnswerSet.java b/src/main/java/at/ac/tuwien/kr/alpha/common/BasicAnswerSet.java index 7d4f8137d..220775628 100644 --- a/src/main/java/at/ac/tuwien/kr/alpha/common/BasicAnswerSet.java +++ b/src/main/java/at/ac/tuwien/kr/alpha/common/BasicAnswerSet.java @@ -39,6 +39,10 @@ public boolean isEmpty() { return predicates.isEmpty(); } + protected Map> getPredicateInstances() { + return predicateInstances; + } + @Override public String toString() { if (predicates.isEmpty()) { diff --git a/src/main/java/at/ac/tuwien/kr/alpha/common/WeightedAnswerSet.java b/src/main/java/at/ac/tuwien/kr/alpha/common/WeightedAnswerSet.java new file mode 100644 index 000000000..e4dcd0dd4 --- /dev/null +++ b/src/main/java/at/ac/tuwien/kr/alpha/common/WeightedAnswerSet.java @@ -0,0 +1,39 @@ +package at.ac.tuwien.kr.alpha.common; + +import java.util.ArrayList; + +/** + * Represents a weighted answer sets, i.e., it extends a {@link BasicAnswerSet} with weights information. + * + * Copyright (c) 2020, the Alpha Team. + */ +public class WeightedAnswerSet extends BasicAnswerSet { + + private final ArrayList weightsAtLevel; + + public WeightedAnswerSet(BasicAnswerSet basicAnswerSet, ArrayList weightsAtLevel) { + super(basicAnswerSet.getPredicates(), basicAnswerSet.getPredicateInstances()); + this.weightsAtLevel = weightsAtLevel; + } + + public String getWeightsAsString() { + StringBuilder sb = new StringBuilder(); + sb.append("["); + for (int i = 0; i < weightsAtLevel.size(); i++) { + if (i != 0) { + sb.append(","); + } + Integer weight = weightsAtLevel.get(i); + sb.append(weight); + sb.append("@"); + sb.append(i); + } + sb.append("]"); + return sb.toString(); + } + + @Override + public String toString() { + return super.toString() + getWeightsAsString(); + } +} diff --git a/src/main/java/at/ac/tuwien/kr/alpha/common/program/AnalyzedProgram.java b/src/main/java/at/ac/tuwien/kr/alpha/common/program/AnalyzedProgram.java index 1494729fe..1e29ff2e7 100644 --- a/src/main/java/at/ac/tuwien/kr/alpha/common/program/AnalyzedProgram.java +++ b/src/main/java/at/ac/tuwien/kr/alpha/common/program/AnalyzedProgram.java @@ -5,7 +5,7 @@ import at.ac.tuwien.kr.alpha.common.depgraph.DependencyGraph; import at.ac.tuwien.kr.alpha.common.depgraph.StronglyConnectedComponentsAlgorithm; import at.ac.tuwien.kr.alpha.common.rule.InternalRule; -import org.apache.commons.lang3.tuple.ImmutablePair; +import org.apache.commons.lang3.tuple.ImmutableTriple; import java.util.List; @@ -20,15 +20,15 @@ public class AnalyzedProgram extends InternalProgram { private final DependencyGraph dependencyGraph; private final ComponentGraph componentGraph; - public AnalyzedProgram(List rules, List facts) { - super(rules, facts); + public AnalyzedProgram(List rules, List facts, Boolean containsWeakConstraints) { + super(rules, facts, containsWeakConstraints); dependencyGraph = DependencyGraph.buildDependencyGraph(getRulesById()); componentGraph = buildComponentGraph(dependencyGraph); } public static AnalyzedProgram analyzeNormalProgram(NormalProgram prog) { - ImmutablePair, List> rulesAndFacts = InternalProgram.internalizeRulesAndFacts(prog); - return new AnalyzedProgram(rulesAndFacts.left, rulesAndFacts.right); + ImmutableTriple, List, Boolean> rulesAndFacts = InternalProgram.internalizeRulesAndFacts(prog); + return new AnalyzedProgram(rulesAndFacts.left, rulesAndFacts.middle, rulesAndFacts.right); } private ComponentGraph buildComponentGraph(DependencyGraph depGraph) { diff --git a/src/main/java/at/ac/tuwien/kr/alpha/common/program/InternalProgram.java b/src/main/java/at/ac/tuwien/kr/alpha/common/program/InternalProgram.java index 0a710e48b..05830a8c4 100644 --- a/src/main/java/at/ac/tuwien/kr/alpha/common/program/InternalProgram.java +++ b/src/main/java/at/ac/tuwien/kr/alpha/common/program/InternalProgram.java @@ -6,7 +6,8 @@ import at.ac.tuwien.kr.alpha.common.rule.NormalRule; import at.ac.tuwien.kr.alpha.grounder.FactIntervalEvaluator; import at.ac.tuwien.kr.alpha.grounder.Instance; -import org.apache.commons.lang3.tuple.ImmutablePair; +import at.ac.tuwien.kr.alpha.grounder.atoms.WeakConstraintAtom; +import org.apache.commons.lang3.tuple.ImmutableTriple; import java.util.ArrayList; import java.util.Collections; @@ -24,19 +25,22 @@ */ public class InternalProgram extends AbstractProgram { + private final boolean containsWeakConstraints; private final Map> predicateDefiningRules = new LinkedHashMap<>(); private final Map> factsByPredicate = new LinkedHashMap<>(); private final Map rulesById = new LinkedHashMap<>(); - public InternalProgram(List rules, List facts) { + public InternalProgram(List rules, List facts, boolean containsWeakConstraints) { super(rules, facts, null); + this.containsWeakConstraints = containsWeakConstraints; recordFacts(facts); recordRules(rules); } - static ImmutablePair, List> internalizeRulesAndFacts(NormalProgram normalProgram) { + static ImmutableTriple, List, Boolean> internalizeRulesAndFacts(NormalProgram normalProgram) { List internalRules = new ArrayList<>(); List facts = new ArrayList<>(normalProgram.getFacts()); + boolean containsWeakConstraints = false; for (NormalRule r : normalProgram.getRules()) { if (r.getBody().isEmpty()) { if (!r.getHead().isGround()) { @@ -45,14 +49,17 @@ static ImmutablePair, List> internalizeRulesAndFacts(No facts.add(r.getHeadAtom()); } else { internalRules.add(InternalRule.fromNormalRule(r)); + if (r.getHeadAtom() instanceof WeakConstraintAtom) { + containsWeakConstraints = true; + } } } - return new ImmutablePair<>(internalRules, facts); + return new ImmutableTriple<>(internalRules, facts, containsWeakConstraints); } public static InternalProgram fromNormalProgram(NormalProgram normalProgram) { - ImmutablePair, List> rulesAndFacts = InternalProgram.internalizeRulesAndFacts(normalProgram); - return new InternalProgram(rulesAndFacts.left, rulesAndFacts.right); + ImmutableTriple, List, Boolean> rulesAndFacts = InternalProgram.internalizeRulesAndFacts(normalProgram); + return new InternalProgram(rulesAndFacts.left, rulesAndFacts.middle, rulesAndFacts.right); } private void recordFacts(List facts) { @@ -90,4 +97,7 @@ public Map getRulesById() { return Collections.unmodifiableMap(rulesById); } + public boolean containsWeakConstraints() { + return containsWeakConstraints; + } } diff --git a/src/main/java/at/ac/tuwien/kr/alpha/grounder/Grounder.java b/src/main/java/at/ac/tuwien/kr/alpha/grounder/Grounder.java index b72f803e3..83a15af62 100644 --- a/src/main/java/at/ac/tuwien/kr/alpha/grounder/Grounder.java +++ b/src/main/java/at/ac/tuwien/kr/alpha/grounder/Grounder.java @@ -62,6 +62,12 @@ public interface Grounder { */ Pair, Map> getChoiceAtoms(); + /** + * States whether the input program given to the grounder contains weak constraints (which enables optimization). + * @return true if the input program to the grounder contains weak constraints. + */ + boolean inputProgramContainsWeakConstraints(); + /** * Returns the atomId of atoms representing weak constraint and the respective weight and level. * Must be preceeded by a call to getNoGoods(). diff --git a/src/main/java/at/ac/tuwien/kr/alpha/grounder/NaiveGrounder.java b/src/main/java/at/ac/tuwien/kr/alpha/grounder/NaiveGrounder.java index 9f4afc114..7c8de9177 100644 --- a/src/main/java/at/ac/tuwien/kr/alpha/grounder/NaiveGrounder.java +++ b/src/main/java/at/ac/tuwien/kr/alpha/grounder/NaiveGrounder.java @@ -559,6 +559,11 @@ public Pair, Map> getChoiceAtoms() { return choiceRecorder.getAndResetChoices(); } + @Override + public boolean inputProgramContainsWeakConstraints() { + return program.containsWeakConstraints(); + } + @Override public Map> getHeadsToBodies() { return choiceRecorder.getAndResetHeadsToBodies(); diff --git a/src/main/java/at/ac/tuwien/kr/alpha/grounder/NoGoodGenerator.java b/src/main/java/at/ac/tuwien/kr/alpha/grounder/NoGoodGenerator.java index 9f2f5e911..8269fa762 100644 --- a/src/main/java/at/ac/tuwien/kr/alpha/grounder/NoGoodGenerator.java +++ b/src/main/java/at/ac/tuwien/kr/alpha/grounder/NoGoodGenerator.java @@ -104,7 +104,7 @@ List generateNoGoodsFromGroundSubstitution(final InternalRule nonGroundR if (groundHeadAtom instanceof WeakConstraintAtom) { WeakConstraintAtom weakConstraintAtom = (WeakConstraintAtom) groundHeadAtom; // Treat weak constraints: only generate nogood for the if-direction (body satisfied causes head to be true). - NoGood wcRule = NoGood.fromBodyInternal(posLiterals, negLiterals, headId); + NoGood wcRule = NoGood.fromBodyInternal(posLiterals, negLiterals, atomToLiteral(headId)); result.add(wcRule); // Record weak constraint association. diff --git a/src/main/java/at/ac/tuwien/kr/alpha/grounder/transformation/StratifiedEvaluation.java b/src/main/java/at/ac/tuwien/kr/alpha/grounder/transformation/StratifiedEvaluation.java index 36cba1580..1e0a579cd 100644 --- a/src/main/java/at/ac/tuwien/kr/alpha/grounder/transformation/StratifiedEvaluation.java +++ b/src/main/java/at/ac/tuwien/kr/alpha/grounder/transformation/StratifiedEvaluation.java @@ -96,7 +96,7 @@ public InternalProgram apply(AnalyzedProgram inputProgram) { .forEach((entry) -> outputRules.add(entry.getValue())); // NOTE: if InternalProgram requires solved rules, they should be added here. - return new InternalProgram(outputRules, additionalFacts); + return new InternalProgram(outputRules, additionalFacts, inputProgram.containsWeakConstraints()); } private void evaluateComponent(SCComponent comp) { diff --git a/src/main/java/at/ac/tuwien/kr/alpha/solver/AtomCallbackManager.java b/src/main/java/at/ac/tuwien/kr/alpha/solver/AtomCallbackManager.java index b57b939d7..0579dc556 100644 --- a/src/main/java/at/ac/tuwien/kr/alpha/solver/AtomCallbackManager.java +++ b/src/main/java/at/ac/tuwien/kr/alpha/solver/AtomCallbackManager.java @@ -20,8 +20,8 @@ class AtomCallbackManager { private AtomCallback[] atomCallbacks = new AtomCallback[0]; void recordCallback(int atom, AtomCallback atomCallback) { - if (atomCallbacks[atom] != null) { - throw oops("Recording callback for atom twice. Atom: " + atom); + if (atomCallbacks[atom] != null && !atomCallbacks[atom].equals(atomCallback)) { + throw oops("Recording different callbacks for one atom. Atom: " + atom); } atomCallbacks[atom] = atomCallback; } diff --git a/src/main/java/at/ac/tuwien/kr/alpha/solver/DefaultSolver.java b/src/main/java/at/ac/tuwien/kr/alpha/solver/DefaultSolver.java index fbb505401..8acae6dbd 100644 --- a/src/main/java/at/ac/tuwien/kr/alpha/solver/DefaultSolver.java +++ b/src/main/java/at/ac/tuwien/kr/alpha/solver/DefaultSolver.java @@ -30,7 +30,9 @@ import at.ac.tuwien.kr.alpha.common.AnswerSet; import at.ac.tuwien.kr.alpha.common.Assignment; import at.ac.tuwien.kr.alpha.common.AtomStore; +import at.ac.tuwien.kr.alpha.common.BasicAnswerSet; import at.ac.tuwien.kr.alpha.common.NoGood; +import at.ac.tuwien.kr.alpha.common.WeightedAnswerSet; import at.ac.tuwien.kr.alpha.common.atoms.Atom; import at.ac.tuwien.kr.alpha.common.atoms.BasicAtom; import at.ac.tuwien.kr.alpha.common.atoms.ComparisonAtom; @@ -94,6 +96,7 @@ public class DefaultSolver extends AbstractSolver implements SolverMaintainingSt private final boolean disableJustifications; private boolean disableJustificationAfterClosing = true; // Keep disabled for now, case not fully worked out yet. private final boolean disableNoGoodDeletion; + private final boolean enableBranchAndBoundOptimization; private final PerformanceLog performanceLog; @@ -103,11 +106,14 @@ public DefaultSolver(AtomStore atomStore, Grounder grounder, NoGoodStore store, this.assignment = assignment; this.store = store; this.choiceManager = new ChoiceManager(assignment, store); + choiceManager.setChecksEnabled(config.isDebugInternalChecks()); this.weakConstraintsManager = new WeakConstraintsManager(assignment); + weakConstraintsManager.setChecksEnabled(config.isDebugInternalChecks()); this.learner = new GroundConflictNoGoodLearner(assignment, atomStore); this.branchingHeuristic = chainFallbackHeuristic(grounder, assignment, random, heuristicsConfiguration); this.disableJustifications = config.isDisableJustificationSearch(); this.disableNoGoodDeletion = config.isDisableNoGoodDeletion(); + this.enableBranchAndBoundOptimization = grounder.inputProgramContainsWeakConstraints(); this.performanceLog = new PerformanceLog(choiceManager, (TrailAssignment) assignment, 1000); } @@ -135,7 +141,9 @@ protected boolean tryAdvance(Consumer action) { logStats(); return false; } - weakConstraintsManager.addWeakConstraintsInformation(grounder.getWeakConstraintInformation()); + if (enableBranchAndBoundOptimization) { + weakConstraintsManager.addWeakConstraintsInformation(grounder.getWeakConstraintInformation()); + } initialize = false; } else if (assignment.getDecisionLevel() == 0) { logStats(); @@ -192,6 +200,15 @@ protected boolean tryAdvance(Consumer action) { } afterAllAtomsAssigned = false; } + } else if (enableBranchAndBoundOptimization && !weakConstraintsManager.isCurrentBetterThanBest()) { + // The solver is searching for optimal answer sets and the current assignment is worse than a previously found answer set. + NoGood excludingNoGood = weakConstraintsManager.generateExcludingNoGood(); + Map obtained = new LinkedHashMap<>(); + obtained.put(grounder.register(excludingNoGood), excludingNoGood); + if (!ingest(obtained)) { + logStats(); + return false; + } } else if (didChange) { // Ask the grounder for new NoGoods, then propagate (again). LOGGER.trace("Doing propagation step."); @@ -214,6 +231,11 @@ protected boolean tryAdvance(Consumer action) { } else if (assignment.getMBTCount() == 0) { // NOTE: If we would do optimization, we would now have a guaranteed upper bound. AnswerSet as = translate(assignment.getTrueAssignments()); + if (enableBranchAndBoundOptimization) { + // Enrich answer-set with weights information. + weakConstraintsManager.markCurrentWeightAsBestKnown(); + as = new WeightedAnswerSet((BasicAnswerSet)as, weakConstraintsManager.getCurrentWeightAtLevels()); + } LOGGER.debug("Answer-Set found: {}", as); action.accept(as); logStats(); diff --git a/src/main/java/at/ac/tuwien/kr/alpha/solver/WeakConstraintsManager.java b/src/main/java/at/ac/tuwien/kr/alpha/solver/WeakConstraintsManager.java index 2565a79ee..8bd858177 100644 --- a/src/main/java/at/ac/tuwien/kr/alpha/solver/WeakConstraintsManager.java +++ b/src/main/java/at/ac/tuwien/kr/alpha/solver/WeakConstraintsManager.java @@ -4,65 +4,232 @@ import org.apache.commons.lang3.tuple.Triple; import java.util.ArrayList; +import java.util.Collections; import java.util.List; +import java.util.Objects; + +import static at.ac.tuwien.kr.alpha.Util.oops; +import static at.ac.tuwien.kr.alpha.common.Literals.atomToLiteral; +import static at.ac.tuwien.kr.alpha.solver.ThriceTruth.MBT; +import static at.ac.tuwien.kr.alpha.solver.ThriceTruth.TRUE; /** - * Manages weak constraints: stores the value of the current partial assignment, the best known value of any answer-set - * and handles callbacks/computations when atoms (that represent weak constraints) change their truth value. + * Manages weak constraints: stores the value of the current partial assignment, the best known value of any answer-set, + * and handles callbacks/computations when certain atoms (that represent weak constraints) change their truth value. * * Copyright (c) 2020, the Alpha Team. */ -public class WeakConstraintsManager { +public class WeakConstraintsManager implements Checkable { private ArrayList currentWeightAtLevels; private ArrayList bestKnownWeightAtLevels; private final WritableAssignment assignment; - private boolean isCurrentWorseThanBest; + private boolean foundFirstAnswerSet; + private int maxLevelCurrentIsAllEqual; // The highest level for which holds: on all levels below (or equal), the current weights are the same as the weights for the best known. + private boolean isCurrentBetterThanBest; + private final ArrayList knownCallbackAtoms; + + private boolean checksEnabled; public WeakConstraintsManager(WritableAssignment assignment) { this.assignment = assignment; this.currentWeightAtLevels = new ArrayList<>(); this.bestKnownWeightAtLevels = new ArrayList<>(); - this.isCurrentWorseThanBest = false; + this.foundFirstAnswerSet = false; + this.maxLevelCurrentIsAllEqual = -1; + this.isCurrentBetterThanBest = true; + this.knownCallbackAtoms = new ArrayList<>(); } public void addWeakConstraintsInformation(List> weakConstraintAtomWeightLevels) { // Register all newly obtained weak constraint atoms for callback at the assignment. for (Triple weakConstraintAtomWeightLevel : weakConstraintAtomWeightLevels) { - WeakConstraintAtom wcA = new WeakConstraintAtom(weakConstraintAtomWeightLevel.getLeft(), weakConstraintAtomWeightLevel.getMiddle(), weakConstraintAtomWeightLevel.getRight()); + WeakConstraintAtomCallback wcA = new WeakConstraintAtomCallback(weakConstraintAtomWeightLevel.getLeft(), weakConstraintAtomWeightLevel.getMiddle(), weakConstraintAtomWeightLevel.getRight()); + assignment.registerCallbackOnChange(wcA.atom); assignment.getAtomCallbackManager().recordCallback(wcA.atom, wcA); + knownCallbackAtoms.add(wcA.atom); } } /** - * Marks the current weight as the one of the currently best of any known answer-set. + * Mark the current weight as being the best of all currently known answer-sets. */ public void markCurrentWeightAsBestKnown() { - bestKnownWeightAtLevels = currentWeightAtLevels; + foundFirstAnswerSet = true; + bestKnownWeightAtLevels = new ArrayList<>(currentWeightAtLevels); + maxLevelCurrentIsAllEqual = bestKnownWeightAtLevels.size() - 1; + isCurrentBetterThanBest = false; } /** - * Returns whether the current partial interpretation is already worse that the best-known answer-set. - * @return true if the current partial interpretation has worse weight than the best-known answer-set. + * Returns whether the current partial interpretation is already worse (or equal) than the best-known answer-set. + * @return true if the current partial interpretation has worse (or equal) weight than the best-known answer-set. */ - public boolean isCurrentWorseThanBest() { - return isCurrentWorseThanBest; + public boolean isCurrentBetterThanBest() { + return isCurrentBetterThanBest; } /** * Generates a NoGood that prevents the current weight to be derived again. - * @return + * @return a NoGood which excludes the exact weight of the current assignment. */ public NoGood generateExcludingNoGood() { - return null; + // Collect all currently true/mbt weights and put their respective literals in one nogood. + // Note: alternative to searching all known callback atoms would be maintaining a list of true ones, but this is probably less efficient. + ArrayList trueCallbackAtoms = new ArrayList<>(); + for (Integer callbackAtom : knownCallbackAtoms) { + if (assignment.getTruth(callbackAtom).toBoolean()) { + trueCallbackAtoms.add(atomToLiteral(callbackAtom)); + } + } + return NoGood.fromConstraint(trueCallbackAtoms, Collections.emptyList()); + } + + public ArrayList getCurrentWeightAtLevels() { + return currentWeightAtLevels; + } + + private void increaseCurrentWeight(WeakConstraintAtomCallback atom) { + // Ignore increase by 0 weight. + if (atom.weight == 0) { + return; + } + // Record the new weight. + expandLevels(atom.level); + int newWeight = currentWeightAtLevels.get(atom.level) + atom.weight; + currentWeightAtLevels.set(atom.level, newWeight); + + // Now check whether current is worse than best known. + if (atom.level > maxLevelCurrentIsAllEqual + 1) { + // Weight in some level where differences do not (yet) matter increased, but on lower level current partial assignment is still better than best found answer-set. + // Nothing to do here. + return; + } else if (atom.level <= maxLevelCurrentIsAllEqual) { + // Weight in level with previously equal weights increased, current is now worse than best. + isCurrentBetterThanBest = false; + maxLevelCurrentIsAllEqual = atom.level - 1; + } else if (atom.level == maxLevelCurrentIsAllEqual + 1) { + // Weight increased for the first level where current and best-known are not equal. + moveUpwardsMaxLevelCurrentIsAllEqual(); + recomputeIsCurrentBetterThanBest(); + } else { + throw oops("Increasing weight of current answer reached unforeseen state."); + } + } + + private void decreaseCurrentWeight(WeakConstraintAtomCallback atom) { + // Ignore decrease by 0 weight. + if (atom.weight == 0) { + return; + } + // Record the new weight. + int newWeight = currentWeightAtLevels.get(atom.level) - atom.weight; + currentWeightAtLevels.set(atom.level, newWeight); + + // Now check whether current is better than best known and adapt maxLevelCurrentIsAllEqual. + if (atom.level <= maxLevelCurrentIsAllEqual) { + // Improved below the point where current is equal to best, so current is better than best now. + isCurrentBetterThanBest = true; + maxLevelCurrentIsAllEqual = atom.level - 1; // All weights below the change are equal. + } else if (atom.level > maxLevelCurrentIsAllEqual + 1) { + // Decrease in such a high level, that there is at least one level (maxLevel+1) where weights are + // different and whose difference still dominates whether current is better than best known. + return; + } else if (atom.level == maxLevelCurrentIsAllEqual + 1) { + // Decrease might make current better than best known and change level of equals. + moveUpwardsMaxLevelCurrentIsAllEqual(); + recomputeIsCurrentBetterThanBest(); + } else { + throw oops("Decreasing weight of current answer reached unforeseen state."); + } + } + + private void moveUpwardsMaxLevelCurrentIsAllEqual() { + for (int i = maxLevelCurrentIsAllEqual; i < currentWeightAtLevels.size(); i++) { + int currentLevelWeight = currentWeightAtLevels.get(i); + int bestLevelWeight = bestKnownWeightAtLevels.get(i); + if (currentLevelWeight != bestLevelWeight) { + // Level is the first where values differ, previous level was maximum one where values are equal. + maxLevelCurrentIsAllEqual = i - 1; + return; + } + } + // Iterated all levels, so weights are the same on all levels. + maxLevelCurrentIsAllEqual = currentWeightAtLevels.size() - 1; + } + + private void recomputeIsCurrentBetterThanBest() { + // The weights at the first level above maxLevelCurrentIsAllEqual dominate whether current is worse than best known. + if (maxLevelCurrentIsAllEqual == currentWeightAtLevels.size() - 1) { + // Current and best known are equal even up to the last level. + isCurrentBetterThanBest = false; + return; + } + int currentLevelWeight = currentWeightAtLevels.get(maxLevelCurrentIsAllEqual + 1); + int bestLevelWeight = bestKnownWeightAtLevels.get(maxLevelCurrentIsAllEqual + 1); + isCurrentBetterThanBest = currentLevelWeight < bestLevelWeight; + } + + private void expandLevels(int newHighestLevel) { + while (currentWeightAtLevels.size() <= newHighestLevel) { + currentWeightAtLevels.add(0); + } + while (bestKnownWeightAtLevels.size() <= newHighestLevel) { + bestKnownWeightAtLevels.add(foundFirstAnswerSet ? 0 : Integer.MAX_VALUE); // Before first answer set is found, best known is maximally bad. + } + } + + @Override + public void setChecksEnabled(boolean checksEnabled) { + this.checksEnabled = checksEnabled; + } + + private void runChecks() { + if (currentWeightAtLevels.size() > bestKnownWeightAtLevels.size()) { + throw oops("WeakConstraintManager has more levels for current valuation than for best known."); + } + // Check whether isCurrentBetterThanBest flag is consistent. + if (isCurrentBetterThanBest != checkIsCurrentBetterThanBest()) { + throw oops("WeakConstraintManager detected valuation of current assignment is inconsistent with state flag."); + } + checkLevelBar(); + } + + private void checkLevelBar() { + int highestLevelWhereLowerLevelsAreEqual = -1; + for (int i = 0; i < currentWeightAtLevels.size(); i++) { + if (currentWeightAtLevels.get(i).intValue() != bestKnownWeightAtLevels.get(i).intValue()) { + break; + } + highestLevelWhereLowerLevelsAreEqual = i; + } + // The level bar should be at highestLevelWhereLowerLevelsAreEqual+1. + if (maxLevelCurrentIsAllEqual != highestLevelWhereLowerLevelsAreEqual) { + throw oops("WeakConstraintManager detected level bar at wrong level, it is " + maxLevelCurrentIsAllEqual + + "and should be " + highestLevelWhereLowerLevelsAreEqual); + } + } + + private boolean checkIsCurrentBetterThanBest() { + for (int i = 0; i < currentWeightAtLevels.size(); i++) { + if (currentWeightAtLevels.get(i) < bestKnownWeightAtLevels.get(i)) { + return true; + } + if (currentWeightAtLevels.get(i) > bestKnownWeightAtLevels.get(i)) { + return false; + } + } + // All levels have same weight for current as for best known, so at this point usually current is not better than best. + return !foundFirstAnswerSet; // If no answer set has been found, however, the current is always better than best known. } - class WeakConstraintAtom implements AtomCallbackManager.AtomCallback { + class WeakConstraintAtomCallback implements AtomCallbackManager.AtomCallback { private final int atom; private final int weight; private final int level; + private ThriceTruth lastTruthValue; - WeakConstraintAtom(int atom, int weight, int level) { + WeakConstraintAtomCallback(int atom, int weight, int level) { this.atom = atom; this.weight = weight; this.level = level; @@ -70,10 +237,50 @@ class WeakConstraintAtom implements AtomCallbackManager.AtomCallback { @Override public void processCallback() { - // TODO: check if atom became true or unassigned/false, then increase or decrease weights. - // TODO: for increase apply weight to respective level of current weightatlevels. - // TODO: for decrease, remove weight from respective level of current weightatlevels. - // TODO: also adapt isCurrentWorseThanBest. + if (checksEnabled) { + runChecks(); + } + ThriceTruth currentAtomTruth = assignment.getTruth(atom); + if (lastTruthValue == null) { + // Change from unassigned to some truth value. + if (currentAtomTruth == MBT) { + increaseCurrentWeight(this); + } + // Note: for assignment to FALSE or MBT->TRUE, no change needed. + lastTruthValue = currentAtomTruth; + } else { + if ((lastTruthValue == MBT || lastTruthValue == TRUE) && currentAtomTruth == null) { + // Change from TRUE/MBT to unassigned. + decreaseCurrentWeight(this); + } + // Note: for backtracking from TRUE to MBT no change is needed. + lastTruthValue = currentAtomTruth; + if (currentAtomTruth != null && lastTruthValue != TRUE && currentAtomTruth != MBT) { + throw oops("Unexpected case of weak constraint atom changing truth value encountered. Atom/LastTruth/CurrentTruth: " + atom + "/" + lastTruthValue + "/" + currentAtomTruth); + } + } + if (checksEnabled) { + runChecks(); + } + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + WeakConstraintAtomCallback that = (WeakConstraintAtomCallback) o; + return atom == that.atom && + weight == that.weight && + level == that.level; + } + + @Override + public int hashCode() { + return Objects.hash(atom, weight, level); } } } diff --git a/src/test/java/at/ac/tuwien/kr/alpha/grounder/ChoiceGrounder.java b/src/test/java/at/ac/tuwien/kr/alpha/grounder/ChoiceGrounder.java index 3699829d2..3aac5d799 100644 --- a/src/test/java/at/ac/tuwien/kr/alpha/grounder/ChoiceGrounder.java +++ b/src/test/java/at/ac/tuwien/kr/alpha/grounder/ChoiceGrounder.java @@ -186,6 +186,11 @@ public Pair, Map> getChoiceAtoms() { } } + @Override + public boolean inputProgramContainsWeakConstraints() { + return false; + } + @Override public List> getWeakConstraintInformation() { return emptyList(); diff --git a/src/test/java/at/ac/tuwien/kr/alpha/grounder/DummyGrounder.java b/src/test/java/at/ac/tuwien/kr/alpha/grounder/DummyGrounder.java index 091f221b2..0ab1db5a8 100644 --- a/src/test/java/at/ac/tuwien/kr/alpha/grounder/DummyGrounder.java +++ b/src/test/java/at/ac/tuwien/kr/alpha/grounder/DummyGrounder.java @@ -171,6 +171,11 @@ public Pair, Map> getChoiceAtoms() { return new ImmutablePair<>(new HashMap<>(), new HashMap<>()); } + @Override + public boolean inputProgramContainsWeakConstraints() { + return false; + } + @Override public List> getWeakConstraintInformation() { return emptyList(); From 79c016f3bd2409f4e9779b2c1d98678e5c6db433 Mon Sep 17 00:00:00 2001 From: Antonius Weinzierl Date: Wed, 19 May 2021 03:17:15 +0200 Subject: [PATCH 04/27] Polish some comments. --- src/main/java/at/ac/tuwien/kr/alpha/grounder/Grounder.java | 4 ++-- .../ac/tuwien/kr/alpha/grounder/parser/ParseTreeVisitor.java | 4 +++- src/main/java/at/ac/tuwien/kr/alpha/solver/DefaultSolver.java | 4 ++-- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/main/java/at/ac/tuwien/kr/alpha/grounder/Grounder.java b/src/main/java/at/ac/tuwien/kr/alpha/grounder/Grounder.java index 83a15af62..b76de869d 100644 --- a/src/main/java/at/ac/tuwien/kr/alpha/grounder/Grounder.java +++ b/src/main/java/at/ac/tuwien/kr/alpha/grounder/Grounder.java @@ -69,8 +69,8 @@ public interface Grounder { boolean inputProgramContainsWeakConstraints(); /** - * Returns the atomId of atoms representing weak constraint and the respective weight and level. - * Must be preceeded by a call to getNoGoods(). + * Returns the atomId of atoms representing weak constraints and the respective weights and levels. + * Must be preceded by a call to getNoGoods(). * @return a list of triples (atomId, weight, level) where atomId is the atom that becomes true whenever the * corresponding weak constraint is violated. */ diff --git a/src/main/java/at/ac/tuwien/kr/alpha/grounder/parser/ParseTreeVisitor.java b/src/main/java/at/ac/tuwien/kr/alpha/grounder/parser/ParseTreeVisitor.java index 25c8f2f2a..fd9e226a8 100644 --- a/src/main/java/at/ac/tuwien/kr/alpha/grounder/parser/ParseTreeVisitor.java +++ b/src/main/java/at/ac/tuwien/kr/alpha/grounder/parser/ParseTreeVisitor.java @@ -70,6 +70,7 @@ import java.util.TreeMap; import java.util.TreeSet; +import static at.ac.tuwien.kr.alpha.Util.oops; import static java.util.Collections.emptyList; /** @@ -214,7 +215,8 @@ public Object visitStatement_weightConstraint(ASPCore2Parser.Statement_weightCon @Override public Object visitWeight_at_level(ASPCore2Parser.Weight_at_levelContext ctx) { // weight_at_level : term (AT term)? (COMMA terms)?; - return null; + // Construction fully done at visitStatement_weightConstraint, should never descend to this. + throw oops("ParserTreeVisitor called method visitWeight_at_level."); } @Override diff --git a/src/main/java/at/ac/tuwien/kr/alpha/solver/DefaultSolver.java b/src/main/java/at/ac/tuwien/kr/alpha/solver/DefaultSolver.java index 8acae6dbd..7648acea4 100644 --- a/src/main/java/at/ac/tuwien/kr/alpha/solver/DefaultSolver.java +++ b/src/main/java/at/ac/tuwien/kr/alpha/solver/DefaultSolver.java @@ -229,10 +229,10 @@ protected boolean tryAdvance(Consumer action) { LOGGER.debug("Closed unassigned known atoms (assigning FALSE)."); afterAllAtomsAssigned = true; } else if (assignment.getMBTCount() == 0) { - // NOTE: If we would do optimization, we would now have a guaranteed upper bound. + // Found an answer-set. AnswerSet as = translate(assignment.getTrueAssignments()); if (enableBranchAndBoundOptimization) { - // Enrich answer-set with weights information. + // Enrich answer-set with weights information, record current-best upper bound. weakConstraintsManager.markCurrentWeightAsBestKnown(); as = new WeightedAnswerSet((BasicAnswerSet)as, weakConstraintsManager.getCurrentWeightAtLevels()); } From 84f27741e06c97cc93304b2f6f73b6ece6072d85 Mon Sep 17 00:00:00 2001 From: Antonius Weinzierl Date: Wed, 19 May 2021 04:51:25 +0200 Subject: [PATCH 05/27] Satisfy checkstyle and printing polished. --- src/main/java/at/ac/tuwien/kr/alpha/Main.java | 7 +++---- .../java/at/ac/tuwien/kr/alpha/grounder/NaiveGrounder.java | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/main/java/at/ac/tuwien/kr/alpha/Main.java b/src/main/java/at/ac/tuwien/kr/alpha/Main.java index ed183ce6a..1e8459797 100644 --- a/src/main/java/at/ac/tuwien/kr/alpha/Main.java +++ b/src/main/java/at/ac/tuwien/kr/alpha/Main.java @@ -215,11 +215,10 @@ private static void computeAndConsumeAnswerSets(Alpha alpha, InputConfig inputCf } } } else { + System.out.println("SATISFIABLE"); if (program.containsWeakConstraints() && counter.get() < limit) { - // Note: this ignores the case where n answer sets are requested and the n-th is the optimum. For this, a solver state is needed. - System.out.println("OPTIMUM FOUND"); - } else { - System.out.println("SATISFIABLE"); + // If less answer sets were found than requested and optimisation is enabled, then the last one is an optimal answer set. + System.out.println("OPTIMUM PROVEN"); } } } else { diff --git a/src/main/java/at/ac/tuwien/kr/alpha/grounder/NaiveGrounder.java b/src/main/java/at/ac/tuwien/kr/alpha/grounder/NaiveGrounder.java index 94a9b9dd1..5d6fee0f0 100644 --- a/src/main/java/at/ac/tuwien/kr/alpha/grounder/NaiveGrounder.java +++ b/src/main/java/at/ac/tuwien/kr/alpha/grounder/NaiveGrounder.java @@ -111,7 +111,7 @@ public NaiveGrounder(InternalProgram program, AtomStore atomStore, boolean debug } private NaiveGrounder(InternalProgram program, AtomStore atomStore, GrounderHeuristicsConfiguration heuristicsConfiguration, - boolean debugInternalChecks, Bridge... bridges) { + boolean debugInternalChecks, Bridge... bridges) { this(program, atomStore, p -> true, heuristicsConfiguration, debugInternalChecks, bridges); } From 98e7435a483a34f7f2f784be81bf370e624e0df4 Mon Sep 17 00:00:00 2001 From: Antonius Weinzierl Date: Thu, 20 May 2021 19:23:43 +0200 Subject: [PATCH 06/27] Add testing facilities for weighted answer sets. - AnswerSet.compareTo checks for same class. - WeightedAnswerSet implements compareTo, hashCode, and equals. - WeakConstraintsManager fix possible NPE. - TestUtils able to check that answer sets contain an optimum answer. - WeakConstraintsTests created with some simple integration tests. --- .../ac/tuwien/kr/alpha/common/AnswerSet.java | 3 + .../kr/alpha/common/WeightedAnswerSet.java | 55 +++++++++++++++++++ .../alpha/solver/WeakConstraintsManager.java | 3 +- .../kr/alpha/solver/WeakConstraintsTests.java | 33 +++++++++++ .../tuwien/kr/alpha/test/util/TestUtils.java | 48 ++++++++++++++++ 5 files changed, 141 insertions(+), 1 deletion(-) create mode 100644 src/test/java/at/ac/tuwien/kr/alpha/solver/WeakConstraintsTests.java diff --git a/src/main/java/at/ac/tuwien/kr/alpha/common/AnswerSet.java b/src/main/java/at/ac/tuwien/kr/alpha/common/AnswerSet.java index 40a316159..a31231992 100644 --- a/src/main/java/at/ac/tuwien/kr/alpha/common/AnswerSet.java +++ b/src/main/java/at/ac/tuwien/kr/alpha/common/AnswerSet.java @@ -14,6 +14,9 @@ public interface AnswerSet extends Comparable { @Override default int compareTo(AnswerSet other) { + if (other.getClass() != this.getClass()) { + return 1; + } final SortedSet predicates = this.getPredicates(); int result = Util.compareSortedSets(predicates, other.getPredicates()); diff --git a/src/main/java/at/ac/tuwien/kr/alpha/common/WeightedAnswerSet.java b/src/main/java/at/ac/tuwien/kr/alpha/common/WeightedAnswerSet.java index e4dcd0dd4..fb77732ef 100644 --- a/src/main/java/at/ac/tuwien/kr/alpha/common/WeightedAnswerSet.java +++ b/src/main/java/at/ac/tuwien/kr/alpha/common/WeightedAnswerSet.java @@ -1,6 +1,7 @@ package at.ac.tuwien.kr.alpha.common; import java.util.ArrayList; +import java.util.Objects; /** * Represents a weighted answer sets, i.e., it extends a {@link BasicAnswerSet} with weights information. @@ -16,6 +17,60 @@ public WeightedAnswerSet(BasicAnswerSet basicAnswerSet, ArrayList weigh this.weightsAtLevel = weightsAtLevel; } + public ArrayList getWeightsAtLevel() { + return weightsAtLevel; + } + + @Override + public int compareTo(AnswerSet other) { + if (!(other instanceof WeightedAnswerSet)) { + return -1; + } + int basicComparison = super.compareTo(other); + if (basicComparison != 0) { + return basicComparison; + } + WeightedAnswerSet otherWeighted = (WeightedAnswerSet) other; + if (weightsAtLevel.size() != otherWeighted.weightsAtLevel.size()) { + return weightsAtLevel.size() - otherWeighted.weightsAtLevel.size(); + } + for (int i = 0; i < weightsAtLevel.size(); i++) { + Integer thisWeightAtI = weightsAtLevel.get(i); + Integer otherWeightAtI = otherWeighted.weightsAtLevel.get(i); + if (!thisWeightAtI.equals(otherWeightAtI)) { + return thisWeightAtI - otherWeightAtI; + } + } + return 0; + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), weightsAtLevel); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof WeightedAnswerSet)) { + return false; + } + + WeightedAnswerSet that = (WeightedAnswerSet) o; + + if (!getPredicates().equals(that.getPredicates())) { + return false; + } + + if (!getPredicateInstances().equals(that.getPredicateInstances())) { + return false; + } + + return weightsAtLevel.equals(that.weightsAtLevel); + } + public String getWeightsAsString() { StringBuilder sb = new StringBuilder(); sb.append("["); diff --git a/src/main/java/at/ac/tuwien/kr/alpha/solver/WeakConstraintsManager.java b/src/main/java/at/ac/tuwien/kr/alpha/solver/WeakConstraintsManager.java index 8bd858177..3ac9fa49b 100644 --- a/src/main/java/at/ac/tuwien/kr/alpha/solver/WeakConstraintsManager.java +++ b/src/main/java/at/ac/tuwien/kr/alpha/solver/WeakConstraintsManager.java @@ -78,7 +78,8 @@ public NoGood generateExcludingNoGood() { // Note: alternative to searching all known callback atoms would be maintaining a list of true ones, but this is probably less efficient. ArrayList trueCallbackAtoms = new ArrayList<>(); for (Integer callbackAtom : knownCallbackAtoms) { - if (assignment.getTruth(callbackAtom).toBoolean()) { + ThriceTruth truth = assignment.getTruth(callbackAtom); + if (truth != null && truth.toBoolean()) { trueCallbackAtoms.add(atomToLiteral(callbackAtom)); } } diff --git a/src/test/java/at/ac/tuwien/kr/alpha/solver/WeakConstraintsTests.java b/src/test/java/at/ac/tuwien/kr/alpha/solver/WeakConstraintsTests.java new file mode 100644 index 000000000..a348bc7b9 --- /dev/null +++ b/src/test/java/at/ac/tuwien/kr/alpha/solver/WeakConstraintsTests.java @@ -0,0 +1,33 @@ +package at.ac.tuwien.kr.alpha.solver; + +import at.ac.tuwien.kr.alpha.common.AnswerSet; +import at.ac.tuwien.kr.alpha.test.util.TestUtils; +import org.junit.Test; + +import java.util.Set; + +/** + * Copyright (c) 2021, the Alpha Team. + */ +public class WeakConstraintsTests extends AbstractSolverTests { + + @Test + public void simpleWeightsSameLevel() { + String program = ":~a.[1@0,foo,bar]" + + ":~b.[2@0,baz]" + + "a :- not b. b:- not a."; + System.out.println(program); + Set actualAnswerSets = collectSet(program); + TestUtils.assertOptimumAnswerSetEquals("a", "1", actualAnswerSets); + } + + + @Test + public void simpleWeightedAnswerSet() { + String program = ":~a.[2@2,foo,bar]" + + ":~b.[1@1,baz]" + + "a :- not b. b:- not a."; + Set actualAnswerSets = collectSet(program); + TestUtils.assertOptimumAnswerSetEquals("b", "0:1:0", actualAnswerSets); + } +} diff --git a/src/test/java/at/ac/tuwien/kr/alpha/test/util/TestUtils.java b/src/test/java/at/ac/tuwien/kr/alpha/test/util/TestUtils.java index 296b3e4c7..b2d77694e 100644 --- a/src/test/java/at/ac/tuwien/kr/alpha/test/util/TestUtils.java +++ b/src/test/java/at/ac/tuwien/kr/alpha/test/util/TestUtils.java @@ -2,7 +2,9 @@ import at.ac.tuwien.kr.alpha.AnswerSetsParser; import at.ac.tuwien.kr.alpha.common.AnswerSet; +import at.ac.tuwien.kr.alpha.common.BasicAnswerSet; import at.ac.tuwien.kr.alpha.common.Predicate; +import at.ac.tuwien.kr.alpha.common.WeightedAnswerSet; import at.ac.tuwien.kr.alpha.common.atoms.Atom; import at.ac.tuwien.kr.alpha.common.atoms.BasicAtom; import at.ac.tuwien.kr.alpha.common.program.AbstractProgram; @@ -95,4 +97,50 @@ public static Atom basicAtomWithSymbolicTerms(String predicate, String... consta return new BasicAtom(pred, trms); } + public static void assertOptimumAnswerSetEquals(String expectedOptimumAnswerSet, String expectedWeightsAtLevels, Set actual) { + // Construct the weighted answer set from the given strings. + BasicAnswerSet basicOptimumAnswerSet = (BasicAnswerSet) AnswerSetsParser.parse("{ " + expectedOptimumAnswerSet + " }").iterator().next(); + String[] weightsAtLevels = expectedWeightsAtLevels.split(":"); + ArrayList expectedWeightsAtLevel = new ArrayList<>(); + for (String weight : weightsAtLevels) { + expectedWeightsAtLevel.add(Integer.parseInt(weight)); + } + WeightedAnswerSet optimumAnswerSet = new WeightedAnswerSet(basicOptimumAnswerSet, expectedWeightsAtLevel); + // Check the optimum is contained in the set of actual answer sets. + if (!actual.contains(optimumAnswerSet)) { + throw new AssertionError("Expected optimum answer set is not contained in actual.\n" + + "Expected optimum answer set: " + optimumAnswerSet + "\n" + + "Actual answer sets: " + actual); + } + // Ensure that there is no better answer set contained in the actual answer sets. + for (AnswerSet actualAnswerSet : actual) { + if (actualAnswerSet.equals(optimumAnswerSet)) { + // Skip optimum itself. + continue; + } + if (!(actualAnswerSet instanceof WeightedAnswerSet)) { + throw new AssertionError("Expecting weighted answer sets but obtained answer set is not: " + actualAnswerSet); + } + WeightedAnswerSet actualWeightedAnswerSet = (WeightedAnswerSet) actualAnswerSet; + ArrayList actualWeightsAtLevel = actualWeightedAnswerSet.getWeightsAtLevel(); + if (actualWeightsAtLevel.size() > expectedWeightsAtLevel.size()) { + continue; + } + if (actualWeightsAtLevel.size() < expectedWeightsAtLevel.size()) { + throw new AssertionError("Actual answer set is better than expected one.\n" + + "Expected: " + expectedWeightsAtLevel + "\n" + + "Actual: " + actualWeightsAtLevel); + } + for (int i = expectedWeightsAtLevel.size() - 1; i >= 0; i--) { + if (expectedWeightsAtLevel.get(i) < actualWeightsAtLevel.get(i)) { + break; + } + if (expectedWeightsAtLevel.get(i) > actualWeightsAtLevel.get(i)) { + throw new AssertionError("Actual answer set is better than expected one.\n" + + "Expected: " + expectedWeightsAtLevel + "\n" + + "Actual: " + actualWeightsAtLevel); + } + } + } + } } From 918f6aefa22258474c443e3aa6e9c50100aef974 Mon Sep 17 00:00:00 2001 From: Antonius Weinzierl Date: Tue, 1 Jun 2021 10:04:04 +0200 Subject: [PATCH 07/27] Extend weighted answer sets to allow negative levels. - WeightedAnswerSet employs TreeMap for weights at levels. - Add WeightedAnswerSetTest unit tests. - Refactor TestUtils, add helper to construct WeightedAnswerSet. --- .../kr/alpha/common/WeightedAnswerSet.java | 101 ++++++++++++------ .../grounder/atoms/WeakConstraintAtom.java | 25 +++-- .../alpha/common/WeightedAnswerSetTest.java | 67 ++++++++++++ .../tuwien/kr/alpha/test/util/TestUtils.java | 43 ++++---- 4 files changed, 171 insertions(+), 65 deletions(-) create mode 100644 src/test/java/at/ac/tuwien/kr/alpha/common/WeightedAnswerSetTest.java diff --git a/src/main/java/at/ac/tuwien/kr/alpha/common/WeightedAnswerSet.java b/src/main/java/at/ac/tuwien/kr/alpha/common/WeightedAnswerSet.java index fb77732ef..bd7a1dedb 100644 --- a/src/main/java/at/ac/tuwien/kr/alpha/common/WeightedAnswerSet.java +++ b/src/main/java/at/ac/tuwien/kr/alpha/common/WeightedAnswerSet.java @@ -1,24 +1,77 @@ package at.ac.tuwien.kr.alpha.common; -import java.util.ArrayList; +import java.util.Iterator; +import java.util.Map; import java.util.Objects; +import java.util.StringJoiner; +import java.util.TreeMap; /** - * Represents a weighted answer sets, i.e., it extends a {@link BasicAnswerSet} with weights information. + * Represents a weighted answer set, i.e., it extends a {@link BasicAnswerSet} with weights information. * - * Copyright (c) 2020, the Alpha Team. + * Weights are given as a mapping from levels to the respective weight. Note that negative levels and weights are allowed. + * The highest level is the most important one and levels not contained in the map have a weight of zero. + * + * Copyright (c) 2020-2021, the Alpha Team. */ public class WeightedAnswerSet extends BasicAnswerSet { - private final ArrayList weightsAtLevel; + private final TreeMap weightPerLevel; - public WeightedAnswerSet(BasicAnswerSet basicAnswerSet, ArrayList weightsAtLevel) { + public WeightedAnswerSet(BasicAnswerSet basicAnswerSet, TreeMap weightPerLevel) { super(basicAnswerSet.getPredicates(), basicAnswerSet.getPredicateInstances()); - this.weightsAtLevel = weightsAtLevel; + this.weightPerLevel = weightPerLevel; } - public ArrayList getWeightsAtLevel() { - return weightsAtLevel; + /** + * Compares the weights of this {@link WeightedAnswerSet} with the weights of the other one. + * @param other the {@link WeightedAnswerSet} to compare with. + * @return -1, 0, or 1 depending on whether the weights of this are better, equal, or worse than the other. + */ + public int compareWeights(WeightedAnswerSet other) { + Iterator thisDescIterator = this.weightPerLevel.descendingKeySet().iterator(); + Iterator otherDescIterator = other.weightPerLevel.descendingKeySet().iterator(); + while (true) { + // Descend this level down to one with non-zero weight. + Integer thisLevel = null; + Integer thisWeight = null; + while (thisDescIterator.hasNext()) { + thisLevel = thisDescIterator.next(); + thisWeight = this.weightPerLevel.get(thisLevel); + if (thisWeight != null && thisWeight != 0) { + break; + } + } + // Descend other level down to one with non-zero weight. + Integer otherLevel = null; + Integer otherWeight = null; + while (otherDescIterator.hasNext()) { + otherLevel = otherDescIterator.next(); + otherWeight = other.weightPerLevel.get(otherLevel); + if (otherWeight != null && otherWeight != 0) { + break; + } + } + if (thisWeight == null && otherWeight == null) { + return 0; + } + if (thisWeight == null) { + return otherWeight < 0 ? 1 : -1; + } + if (otherWeight == null) { + return thisWeight < 0 ? -1 : 1; + } + if (thisLevel.equals(otherLevel)) { + if (thisWeight.equals(otherWeight)) { + continue; + } else { + return thisWeight < otherWeight ? -1 : 1; + } + + } else { + return thisLevel < otherLevel ? -1 : 1; + } + } } @Override @@ -31,22 +84,12 @@ public int compareTo(AnswerSet other) { return basicComparison; } WeightedAnswerSet otherWeighted = (WeightedAnswerSet) other; - if (weightsAtLevel.size() != otherWeighted.weightsAtLevel.size()) { - return weightsAtLevel.size() - otherWeighted.weightsAtLevel.size(); - } - for (int i = 0; i < weightsAtLevel.size(); i++) { - Integer thisWeightAtI = weightsAtLevel.get(i); - Integer otherWeightAtI = otherWeighted.weightsAtLevel.get(i); - if (!thisWeightAtI.equals(otherWeightAtI)) { - return thisWeightAtI - otherWeightAtI; - } - } - return 0; + return compareWeights(otherWeighted); } @Override public int hashCode() { - return Objects.hash(super.hashCode(), weightsAtLevel); + return Objects.hash(super.hashCode(), weightPerLevel); } @Override @@ -68,23 +111,15 @@ public boolean equals(Object o) { return false; } - return weightsAtLevel.equals(that.weightsAtLevel); + return weightPerLevel.equals(that.weightPerLevel); } public String getWeightsAsString() { - StringBuilder sb = new StringBuilder(); - sb.append("["); - for (int i = 0; i < weightsAtLevel.size(); i++) { - if (i != 0) { - sb.append(","); - } - Integer weight = weightsAtLevel.get(i); - sb.append(weight); - sb.append("@"); - sb.append(i); + StringJoiner joiner = new StringJoiner(", ", "[", "]"); + for (Map.Entry weightPerLevel : weightPerLevel.entrySet()) { + joiner.add(weightPerLevel.getValue() + "@" + weightPerLevel.getKey()); } - sb.append("]"); - return sb.toString(); + return joiner.toString(); } @Override diff --git a/src/main/java/at/ac/tuwien/kr/alpha/grounder/atoms/WeakConstraintAtom.java b/src/main/java/at/ac/tuwien/kr/alpha/grounder/atoms/WeakConstraintAtom.java index 801ed98e8..945c1edb8 100644 --- a/src/main/java/at/ac/tuwien/kr/alpha/grounder/atoms/WeakConstraintAtom.java +++ b/src/main/java/at/ac/tuwien/kr/alpha/grounder/atoms/WeakConstraintAtom.java @@ -22,7 +22,7 @@ * Represents the head of a weak constraint (i.e., a special internal atom indicating that a rule really is a weak * constraint). * - * Copyright (c) 2020, the Alpha Team. + * Copyright (c) 2020-2021, the Alpha Team. */ public class WeakConstraintAtom extends Atom { private static final Predicate PREDICATE = Predicate.getInstance("_weakconstraint_", 3, true, true); @@ -33,18 +33,22 @@ public class WeakConstraintAtom extends Atom { private final FunctionTerm termList; private WeakConstraintAtom(Term weight, Term level, FunctionTerm termList) { + if (!isIntegerOrVariable(weight)) { + throw new IllegalArgumentException("WeakConstraint with non-integer weight encountered: " + weight); + } + if (isNegativeInteger(weight)) { + throw new IllegalArgumentException("WeakConstraint with negative weight encountered: " + weight + "@" + level + "\n" + + "Negative weights in weak constraints are not supported. Please rewrite your input program (turn negative weight if condition is met into positive weight if inverse condition is met)."); + } + if (!isIntegerOrVariable(level)) { + throw new IllegalArgumentException("WeakConstraint with non-integer level encountered: " + level); + } this.weight = weight; this.level = level; this.termList = termList; } public static WeakConstraintAtom getInstance(Term weight, Term level, List termList) { - if (!isIntegerOrVariable(weight)) { - throw new IllegalArgumentException("WeakConstraint with non-integer weight encountered: " + weight); - } - if (level != null && !isIntegerOrVariable(level)) { - throw new IllegalArgumentException("WeakConstraint with non-integer level encountered: " + level); - } Term actualLevel = level != null ? level : ConstantTerm.getInstance(0); List actualTermlist = termList != null ? termList : Collections.emptyList(); return new WeakConstraintAtom(weight, actualLevel, FunctionTerm.getInstance(TERMLISTSYMBOL, actualTermlist)); @@ -69,6 +73,13 @@ private static boolean isIntegerOrVariable(Term term) { return false; } + private static boolean isNegativeInteger(Term term) { + if (term instanceof ConstantTerm && ((ConstantTerm) term).getObject() instanceof Integer) { + return ((Integer)((ConstantTerm) term).getObject()) < 0; + } + return false; + } + @Override public Predicate getPredicate() { return PREDICATE; diff --git a/src/test/java/at/ac/tuwien/kr/alpha/common/WeightedAnswerSetTest.java b/src/test/java/at/ac/tuwien/kr/alpha/common/WeightedAnswerSetTest.java new file mode 100644 index 000000000..cc21369d3 --- /dev/null +++ b/src/test/java/at/ac/tuwien/kr/alpha/common/WeightedAnswerSetTest.java @@ -0,0 +1,67 @@ +package at.ac.tuwien.kr.alpha.common; + +import at.ac.tuwien.kr.alpha.test.util.TestUtils; +import org.junit.Test; + +import static org.junit.Assert.*; + +/** + * Copyright (c) 2021, the Alpha Team. + */ +public class WeightedAnswerSetTest { + + @Test + public void compareWeightedAnswerSets() { + String was1Atoms = "a, p(b)"; + WeightedAnswerSet was1 = TestUtils.weightedAnswerSetFromStrings(was1Atoms, "1@1"); + WeightedAnswerSet was2 = TestUtils.weightedAnswerSetFromStrings("a", "1@1, 2@3"); + WeightedAnswerSet was1Same = TestUtils.weightedAnswerSetFromStrings(was1Atoms, "1@1"); + + assertFalse(was1.getPredicates().isEmpty()); + assertFalse(was2.getPredicates().isEmpty()); + assertNotEquals(was1, was2); + assertEquals(was1Same, was1); + assertEquals(was1.getPredicateInstances(), was1Same.getPredicateInstances()); + } + + @Test + public void compareAnswerSetsSameAtoms() { + String was3Atoms = "q(0,1), b, foo(a)"; + WeightedAnswerSet was3 = TestUtils.weightedAnswerSetFromStrings(was3Atoms, "1@1, 2@3"); + WeightedAnswerSet was3AtomsNotWeights = TestUtils.weightedAnswerSetFromStrings(was3Atoms, "2@1, 2@3"); + + assertFalse(was3.getPredicates().isEmpty()); + assertNotEquals(was3, was3AtomsNotWeights); + assertEquals(was3.getPredicateInstances(), was3AtomsNotWeights.getPredicateInstances()); + } + + + @Test + public void compareAnswerSetsSameWeights() { + String was4Weights = "1@1, 4@3"; + WeightedAnswerSet was4 = TestUtils.weightedAnswerSetFromStrings("b, c", was4Weights); + WeightedAnswerSet was5 = TestUtils.weightedAnswerSetFromStrings("b, d", was4Weights); + + assertFalse(was4.getPredicates().isEmpty()); + assertFalse(was5.getPredicates().isEmpty()); + assertEquals(0, was4.compareWeights(was5)); + assertNotEquals(was4, was5); + assertNotEquals(was5, was4); + } + + @Test + public void compareWeights() { + WeightedAnswerSet was6 = TestUtils.weightedAnswerSetFromStrings("a", "2@3"); + WeightedAnswerSet was7 = TestUtils.weightedAnswerSetFromStrings("a", "1@1, 1@3"); + WeightedAnswerSet was8 = TestUtils.weightedAnswerSetFromStrings("a", "1@-3, 1@1, 1@3"); + assertEquals(-1, was7.compareWeights(was6)); + assertEquals(1, was6.compareWeights(was7)); + + assertEquals(-1, was7.compareWeights(was8)); + assertEquals(1, was8.compareWeights(was7)); + + assertEquals(-1, was8.compareWeights(was6)); + assertEquals(1, was6.compareWeights(was8)); + } + +} \ No newline at end of file diff --git a/src/test/java/at/ac/tuwien/kr/alpha/test/util/TestUtils.java b/src/test/java/at/ac/tuwien/kr/alpha/test/util/TestUtils.java index b2d77694e..6bc7b7ffe 100644 --- a/src/test/java/at/ac/tuwien/kr/alpha/test/util/TestUtils.java +++ b/src/test/java/at/ac/tuwien/kr/alpha/test/util/TestUtils.java @@ -18,6 +18,7 @@ import java.util.List; import java.util.Set; import java.util.StringJoiner; +import java.util.TreeMap; import static java.util.Collections.emptySet; @@ -97,15 +98,21 @@ public static Atom basicAtomWithSymbolicTerms(String predicate, String... consta return new BasicAtom(pred, trms); } - public static void assertOptimumAnswerSetEquals(String expectedOptimumAnswerSet, String expectedWeightsAtLevels, Set actual) { - // Construct the weighted answer set from the given strings. - BasicAnswerSet basicOptimumAnswerSet = (BasicAnswerSet) AnswerSetsParser.parse("{ " + expectedOptimumAnswerSet + " }").iterator().next(); - String[] weightsAtLevels = expectedWeightsAtLevels.split(":"); - ArrayList expectedWeightsAtLevel = new ArrayList<>(); - for (String weight : weightsAtLevels) { - expectedWeightsAtLevel.add(Integer.parseInt(weight)); + public static WeightedAnswerSet weightedAnswerSetFromStrings(String basicAnswerSetAsString, String weightAtLevelsAsString) { + BasicAnswerSet basicAnswerSet = (BasicAnswerSet) AnswerSetsParser.parse("{ " + basicAnswerSetAsString + " }").iterator().next(); + // Extract weights at levels from given string. + String[] weightsAtLevels = weightAtLevelsAsString.split(", "); + TreeMap weightAtLevelsTreeMap = new TreeMap<>(); + for (String weightsAtLevel : weightsAtLevels) { + String[] wAtL = weightsAtLevel.split("@"); + weightAtLevelsTreeMap.put(Integer.parseInt(wAtL[1]), Integer.parseInt(wAtL[0])); } - WeightedAnswerSet optimumAnswerSet = new WeightedAnswerSet(basicOptimumAnswerSet, expectedWeightsAtLevel); + return new WeightedAnswerSet(basicAnswerSet, weightAtLevelsTreeMap); + } + + public static void assertOptimumAnswerSetEquals(String expectedOptimumAnswerSet, String expectedWeightsAtLevels, Set actual) { + WeightedAnswerSet optimumAnswerSet = weightedAnswerSetFromStrings(expectedOptimumAnswerSet, expectedWeightsAtLevels); + // Check the optimum is contained in the set of actual answer sets. if (!actual.contains(optimumAnswerSet)) { throw new AssertionError("Expected optimum answer set is not contained in actual.\n" + @@ -122,24 +129,10 @@ public static void assertOptimumAnswerSetEquals(String expectedOptimumAnswerSet, throw new AssertionError("Expecting weighted answer sets but obtained answer set is not: " + actualAnswerSet); } WeightedAnswerSet actualWeightedAnswerSet = (WeightedAnswerSet) actualAnswerSet; - ArrayList actualWeightsAtLevel = actualWeightedAnswerSet.getWeightsAtLevel(); - if (actualWeightsAtLevel.size() > expectedWeightsAtLevel.size()) { - continue; - } - if (actualWeightsAtLevel.size() < expectedWeightsAtLevel.size()) { + if (optimumAnswerSet.compareWeights(actualWeightedAnswerSet) >= 0) { throw new AssertionError("Actual answer set is better than expected one.\n" + - "Expected: " + expectedWeightsAtLevel + "\n" + - "Actual: " + actualWeightsAtLevel); - } - for (int i = expectedWeightsAtLevel.size() - 1; i >= 0; i--) { - if (expectedWeightsAtLevel.get(i) < actualWeightsAtLevel.get(i)) { - break; - } - if (expectedWeightsAtLevel.get(i) > actualWeightsAtLevel.get(i)) { - throw new AssertionError("Actual answer set is better than expected one.\n" + - "Expected: " + expectedWeightsAtLevel + "\n" + - "Actual: " + actualWeightsAtLevel); - } + "Expected: " + optimumAnswerSet + "\n" + + "Actual: " + actualWeightedAnswerSet); } } } From c3ee67d6ffa125c69bd1f1a624d87443c4113fc4 Mon Sep 17 00:00:00 2001 From: Antonius Weinzierl Date: Tue, 1 Jun 2021 10:17:12 +0200 Subject: [PATCH 08/27] Enable negative levels in optimisation, fix ordering of levels. - WeakConstraintsManager adapted for correct ordering and negative levels. - Added more tests. --- .../alpha/solver/WeakConstraintsManager.java | 230 ++++++++++++++---- .../kr/alpha/solver/WeakConstraintsTests.java | 15 +- 2 files changed, 188 insertions(+), 57 deletions(-) diff --git a/src/main/java/at/ac/tuwien/kr/alpha/solver/WeakConstraintsManager.java b/src/main/java/at/ac/tuwien/kr/alpha/solver/WeakConstraintsManager.java index 3ac9fa49b..a5882d76b 100644 --- a/src/main/java/at/ac/tuwien/kr/alpha/solver/WeakConstraintsManager.java +++ b/src/main/java/at/ac/tuwien/kr/alpha/solver/WeakConstraintsManager.java @@ -2,32 +2,42 @@ import at.ac.tuwien.kr.alpha.common.NoGood; import org.apache.commons.lang3.tuple.Triple; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.util.ArrayList; import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Objects; +import java.util.TreeMap; import static at.ac.tuwien.kr.alpha.Util.oops; import static at.ac.tuwien.kr.alpha.common.Literals.atomToLiteral; import static at.ac.tuwien.kr.alpha.solver.ThriceTruth.MBT; import static at.ac.tuwien.kr.alpha.solver.ThriceTruth.TRUE; +import static java.lang.Math.max; /** * Manages weak constraints: stores the value of the current partial assignment, the best known value of any answer-set, * and handles callbacks/computations when certain atoms (that represent weak constraints) change their truth value. * - * Copyright (c) 2020, the Alpha Team. + * Copyright (c) 2020-2021, the Alpha Team. */ public class WeakConstraintsManager implements Checkable { + private static final Logger LOGGER = LoggerFactory.getLogger(WeakConstraintsManager.class); + private ArrayList currentWeightAtLevels; - private ArrayList bestKnownWeightAtLevels; + private ArrayList bestKnownWeightAtLevels; // Stores weights in inverted order: zero is highest violated level, may also grow to represent negative levels. private final WritableAssignment assignment; private boolean foundFirstAnswerSet; - private int maxLevelCurrentIsAllEqual; // The highest level for which holds: on all levels below (or equal), the current weights are the same as the weights for the best known. + private int maxOffsetCurrentIsAllEqualBest; // The highest level for which holds: on all levels below (or equal), the current weights are the same as the weights for the best known. private boolean isCurrentBetterThanBest; private final ArrayList knownCallbackAtoms; + private final ArrayList knownAtomCallbacksForFirstAnswerSet = new ArrayList<>(); + private int maxLevel; // Stores which actual level corresponds to array position 0 in bestKnownWeightAtLevels, i.e, the highest level that is violated. Value may decrease if better answer-sets are found. private boolean checksEnabled; @@ -36,7 +46,7 @@ public WeakConstraintsManager(WritableAssignment assignment) { this.currentWeightAtLevels = new ArrayList<>(); this.bestKnownWeightAtLevels = new ArrayList<>(); this.foundFirstAnswerSet = false; - this.maxLevelCurrentIsAllEqual = -1; + this.maxOffsetCurrentIsAllEqualBest = -1; this.isCurrentBetterThanBest = true; this.knownCallbackAtoms = new ArrayList<>(); } @@ -44,10 +54,17 @@ public WeakConstraintsManager(WritableAssignment assignment) { public void addWeakConstraintsInformation(List> weakConstraintAtomWeightLevels) { // Register all newly obtained weak constraint atoms for callback at the assignment. for (Triple weakConstraintAtomWeightLevel : weakConstraintAtomWeightLevels) { + if (weakConstraintAtomWeightLevel.getMiddle() == 0) { + // Skip weak constraints with weight 0 entirely. + continue; + } WeakConstraintAtomCallback wcA = new WeakConstraintAtomCallback(weakConstraintAtomWeightLevel.getLeft(), weakConstraintAtomWeightLevel.getMiddle(), weakConstraintAtomWeightLevel.getRight()); assignment.registerCallbackOnChange(wcA.atom); assignment.getAtomCallbackManager().recordCallback(wcA.atom, wcA); knownCallbackAtoms.add(wcA.atom); + if (!foundFirstAnswerSet) { + knownAtomCallbacksForFirstAnswerSet.add(wcA); + } } } @@ -55,17 +72,92 @@ public void addWeakConstraintsInformation(List * Mark the current weight as being the best of all currently known answer-sets. */ public void markCurrentWeightAsBestKnown() { - foundFirstAnswerSet = true; - bestKnownWeightAtLevels = new ArrayList<>(currentWeightAtLevels); - maxLevelCurrentIsAllEqual = bestKnownWeightAtLevels.size() - 1; + LOGGER.trace("Marking current answer-set as best known."); + if (!foundFirstAnswerSet) { + initializeFirstWeightsAtLevel(); + } else { + // Remove unnecessary high levels (with weights 0). + int trim = 0; + for (int i = 0; i < currentWeightAtLevels.size(); i++) { + if (currentWeightAtLevels.get(i) != 0) { + trim = i; + break; + } + } + if (trim > 0) { + bestKnownWeightAtLevels = new ArrayList<>(currentWeightAtLevels.subList(trim, currentWeightAtLevels.size())); + currentWeightAtLevels = new ArrayList<>(bestKnownWeightAtLevels); + } else { + bestKnownWeightAtLevels = new ArrayList<>(currentWeightAtLevels); + } + maxLevel -= trim; + } + maxOffsetCurrentIsAllEqualBest = bestKnownWeightAtLevels.size() - 1; + LOGGER.trace("Max offset current is all equal to best known is: {}", maxOffsetCurrentIsAllEqualBest); isCurrentBetterThanBest = false; } + private void initializeFirstWeightsAtLevel() { + // First answer set has been found, take its maximum level as zero-level. + LOGGER.trace("Initializing for first answer-set."); + foundFirstAnswerSet = true; + HashMap sumWeightsAtLevel = new HashMap<>(); + int highestLevel = 0; + for (WeakConstraintAtomCallback atomCallback : knownAtomCallbacksForFirstAnswerSet) { + ThriceTruth truth = assignment.getTruth(atomCallback.atom); + if (truth == null || !truth.toBoolean()) { + // Skip weak-constraint atoms being unassigned or false. + continue; + } + int level = atomCallback.level; + if (highestLevel < level) { + // Record level if it is highest so far. + highestLevel = level; + } + // Update weight information. + if (sumWeightsAtLevel.get(level) == null) { + sumWeightsAtLevel.put(level, atomCallback.weight); + } else { + int newWeight = sumWeightsAtLevel.get(level) + atomCallback.weight; + sumWeightsAtLevel.put(level, newWeight); + } + } + maxLevel = highestLevel; + LOGGER.trace("Maximum recorded level (0-offset) is: {} ({})", maxLevel, listOffsetToLevel(0)); + bestKnownWeightAtLevels = new ArrayList<>(highestLevel); + growForLevel(bestKnownWeightAtLevels, highestLevel); + for (Map.Entry levelWeight : sumWeightsAtLevel.entrySet()) { + bestKnownWeightAtLevels.set(maxLevel - levelWeight.getKey(), levelWeight.getValue()); + } + currentWeightAtLevels = new ArrayList<>(bestKnownWeightAtLevels); + LOGGER.trace("Initially, current/best known weights are: {}", bestKnownWeightAtLevels); + } + + private void growForLevel(ArrayList listToGrow, int level) { + int listOffset = levelToListOffset(level); + while (listOffset >= listToGrow.size()) { + listToGrow.add(0); + } + } + + private int levelToListOffset(int level) { + int levelInList = maxLevel - level; + if (levelInList < 0) { + throw oops("Level optimisation in WeakConstraintsManager is negative."); + } + return levelInList; + } + + private int listOffsetToLevel(int offset) { + return maxLevel - offset; + } + /** * Returns whether the current partial interpretation is already worse (or equal) than the best-known answer-set. * @return true if the current partial interpretation has worse (or equal) weight than the best-known answer-set. */ public boolean isCurrentBetterThanBest() { + LOGGER.trace("Is current better than best? {}", isCurrentBetterThanBest); return isCurrentBetterThanBest; } @@ -86,30 +178,39 @@ public NoGood generateExcludingNoGood() { return NoGood.fromConstraint(trueCallbackAtoms, Collections.emptyList()); } - public ArrayList getCurrentWeightAtLevels() { - return currentWeightAtLevels; + /** + * Returns the current weights and their levels. + * @return a TreeMap mapping levels to weights. Note that values may be non-continuous and negative levels are possible. + */ + public TreeMap getCurrentWeightAtLevels() { + TreeMap weightPerLevels = new TreeMap<>(); + for (int i = 0; i < currentWeightAtLevels.size(); i++) { + Integer weightAti = currentWeightAtLevels.get(i); + if (weightAti == 0) { + continue; + } + weightPerLevels.put(listOffsetToLevel(i), weightAti); + } + return weightPerLevels; } private void increaseCurrentWeight(WeakConstraintAtomCallback atom) { - // Ignore increase by 0 weight. - if (atom.weight == 0) { - return; - } // Record the new weight. - expandLevels(atom.level); - int newWeight = currentWeightAtLevels.get(atom.level) + atom.weight; - currentWeightAtLevels.set(atom.level, newWeight); + growForLevel(currentWeightAtLevels, atom.level); + int listOffset = levelToListOffset(atom.level); + int newWeight = currentWeightAtLevels.get(listOffset) + atom.weight; + currentWeightAtLevels.set(listOffset, newWeight); // Now check whether current is worse than best known. - if (atom.level > maxLevelCurrentIsAllEqual + 1) { + if (listOffset > maxOffsetCurrentIsAllEqualBest + 1) { // Weight in some level where differences do not (yet) matter increased, but on lower level current partial assignment is still better than best found answer-set. // Nothing to do here. return; - } else if (atom.level <= maxLevelCurrentIsAllEqual) { + } else if (listOffset <= maxOffsetCurrentIsAllEqualBest) { // Weight in level with previously equal weights increased, current is now worse than best. isCurrentBetterThanBest = false; - maxLevelCurrentIsAllEqual = atom.level - 1; - } else if (atom.level == maxLevelCurrentIsAllEqual + 1) { + maxOffsetCurrentIsAllEqualBest = listOffset - 1; + } else if (listOffset == maxOffsetCurrentIsAllEqualBest + 1) { // Weight increased for the first level where current and best-known are not equal. moveUpwardsMaxLevelCurrentIsAllEqual(); recomputeIsCurrentBetterThanBest(); @@ -119,24 +220,21 @@ private void increaseCurrentWeight(WeakConstraintAtomCallback atom) { } private void decreaseCurrentWeight(WeakConstraintAtomCallback atom) { - // Ignore decrease by 0 weight. - if (atom.weight == 0) { - return; - } // Record the new weight. - int newWeight = currentWeightAtLevels.get(atom.level) - atom.weight; - currentWeightAtLevels.set(atom.level, newWeight); + int listOffset = levelToListOffset(atom.level); + int newWeight = currentWeightAtLevels.get(listOffset) - atom.weight; + currentWeightAtLevels.set(listOffset, newWeight); - // Now check whether current is better than best known and adapt maxLevelCurrentIsAllEqual. - if (atom.level <= maxLevelCurrentIsAllEqual) { + // Now check whether current is better than best known and adapt maxOffsetCurrentIsAllEqualBest. + if (listOffset <= maxOffsetCurrentIsAllEqualBest) { // Improved below the point where current is equal to best, so current is better than best now. isCurrentBetterThanBest = true; - maxLevelCurrentIsAllEqual = atom.level - 1; // All weights below the change are equal. - } else if (atom.level > maxLevelCurrentIsAllEqual + 1) { + maxOffsetCurrentIsAllEqualBest = listOffset - 1; // All weights below the change are equal. + } else if (listOffset > maxOffsetCurrentIsAllEqualBest + 1) { // Decrease in such a high level, that there is at least one level (maxLevel+1) where weights are // different and whose difference still dominates whether current is better than best known. return; - } else if (atom.level == maxLevelCurrentIsAllEqual + 1) { + } else if (listOffset == maxOffsetCurrentIsAllEqualBest + 1) { // Decrease might make current better than best known and change level of equals. moveUpwardsMaxLevelCurrentIsAllEqual(); recomputeIsCurrentBetterThanBest(); @@ -146,73 +244,87 @@ private void decreaseCurrentWeight(WeakConstraintAtomCallback atom) { } private void moveUpwardsMaxLevelCurrentIsAllEqual() { - for (int i = maxLevelCurrentIsAllEqual; i < currentWeightAtLevels.size(); i++) { + for (int i = max(maxOffsetCurrentIsAllEqualBest, 0); i < currentWeightAtLevels.size(); i++) { int currentLevelWeight = currentWeightAtLevels.get(i); - int bestLevelWeight = bestKnownWeightAtLevels.get(i); + int bestLevelWeight = bestKnownWeightAtLevels.size() > i ? bestKnownWeightAtLevels.get(i) : 0; if (currentLevelWeight != bestLevelWeight) { // Level is the first where values differ, previous level was maximum one where values are equal. - maxLevelCurrentIsAllEqual = i - 1; + maxOffsetCurrentIsAllEqualBest = i - 1; return; } } // Iterated all levels, so weights are the same on all levels. - maxLevelCurrentIsAllEqual = currentWeightAtLevels.size() - 1; + maxOffsetCurrentIsAllEqualBest = currentWeightAtLevels.size() - 1; } private void recomputeIsCurrentBetterThanBest() { - // The weights at the first level above maxLevelCurrentIsAllEqual dominate whether current is worse than best known. - if (maxLevelCurrentIsAllEqual == currentWeightAtLevels.size() - 1) { + // The weights at the first level above maxOffsetCurrentIsAllEqualBest dominate whether current is worse than best known. + if (maxOffsetCurrentIsAllEqualBest == currentWeightAtLevels.size() - 1) { // Current and best known are equal even up to the last level. isCurrentBetterThanBest = false; return; } - int currentLevelWeight = currentWeightAtLevels.get(maxLevelCurrentIsAllEqual + 1); - int bestLevelWeight = bestKnownWeightAtLevels.get(maxLevelCurrentIsAllEqual + 1); + int comparisonOffset = maxOffsetCurrentIsAllEqualBest + 1; + int currentLevelWeight = currentWeightAtLevels.get(comparisonOffset); + int bestLevelWeight = bestKnownWeightAtLevels.size() > comparisonOffset ? bestKnownWeightAtLevels.get(comparisonOffset) : 0; isCurrentBetterThanBest = currentLevelWeight < bestLevelWeight; } - private void expandLevels(int newHighestLevel) { - while (currentWeightAtLevels.size() <= newHighestLevel) { - currentWeightAtLevels.add(0); - } - while (bestKnownWeightAtLevels.size() <= newHighestLevel) { - bestKnownWeightAtLevels.add(foundFirstAnswerSet ? 0 : Integer.MAX_VALUE); // Before first answer set is found, best known is maximally bad. - } - } - @Override public void setChecksEnabled(boolean checksEnabled) { this.checksEnabled = checksEnabled; } private void runChecks() { - if (currentWeightAtLevels.size() > bestKnownWeightAtLevels.size()) { - throw oops("WeakConstraintManager has more levels for current valuation than for best known."); + if (LOGGER.isTraceEnabled()) { + LOGGER.trace("Running checks."); + LOGGER.trace("Maximum recorded level (0-offset) is: {} ({})", maxLevel, listOffsetToLevel(0)); + LOGGER.trace("Currently best known weights are: {}", bestKnownWeightAtLevels); + LOGGER.trace("Current weights are: {}", currentWeightAtLevels); + LOGGER.trace("Max offset current is all equal to best known is: {}", maxOffsetCurrentIsAllEqualBest); + LOGGER.trace("Is current better than best-known: {}", isCurrentBetterThanBest); + + } + if (!foundFirstAnswerSet && !bestKnownWeightAtLevels.isEmpty()) { + throw oops("WeakConstraintManager has best known answer set information but no answer set was found yet."); + } + if (foundFirstAnswerSet && bestKnownWeightAtLevels.get(0) == 0) { + throw oops("WeakConstraintManager has best-known answer set with zero-weights at highest level."); } // Check whether isCurrentBetterThanBest flag is consistent. if (isCurrentBetterThanBest != checkIsCurrentBetterThanBest()) { throw oops("WeakConstraintManager detected valuation of current assignment is inconsistent with state flag."); } checkLevelBar(); + LOGGER.trace("Checks done."); } private void checkLevelBar() { int highestLevelWhereLowerLevelsAreEqual = -1; for (int i = 0; i < currentWeightAtLevels.size(); i++) { - if (currentWeightAtLevels.get(i).intValue() != bestKnownWeightAtLevels.get(i).intValue()) { + int levelValue = i < bestKnownWeightAtLevels.size() ? bestKnownWeightAtLevels.get(i) : 0; + if (currentWeightAtLevels.get(i) != levelValue) { break; } highestLevelWhereLowerLevelsAreEqual = i; } // The level bar should be at highestLevelWhereLowerLevelsAreEqual+1. - if (maxLevelCurrentIsAllEqual != highestLevelWhereLowerLevelsAreEqual) { - throw oops("WeakConstraintManager detected level bar at wrong level, it is " + maxLevelCurrentIsAllEqual + if (maxOffsetCurrentIsAllEqualBest != highestLevelWhereLowerLevelsAreEqual) { + throw oops("WeakConstraintManager detected level bar at wrong level, it is " + maxOffsetCurrentIsAllEqualBest + "and should be " + highestLevelWhereLowerLevelsAreEqual); } } private boolean checkIsCurrentBetterThanBest() { + if (!foundFirstAnswerSet) { + // If no answer set has been found, the current is always better than best known. + return true; + } for (int i = 0; i < currentWeightAtLevels.size(); i++) { + if (i >= bestKnownWeightAtLevels.size()) { + // Current has violations at level not violated by best. + return false; + } if (currentWeightAtLevels.get(i) < bestKnownWeightAtLevels.get(i)) { return true; } @@ -221,7 +333,7 @@ private boolean checkIsCurrentBetterThanBest() { } } // All levels have same weight for current as for best known, so at this point usually current is not better than best. - return !foundFirstAnswerSet; // If no answer set has been found, however, the current is always better than best known. + return false; } class WeakConstraintAtomCallback implements AtomCallbackManager.AtomCallback { @@ -238,10 +350,20 @@ class WeakConstraintAtomCallback implements AtomCallbackManager.AtomCallback { @Override public void processCallback() { + if (LOGGER.isTraceEnabled()) { + LOGGER.trace("Processing callback for atom {} with weight at level {}@{}, last truth value was {}.", atom, weight, level, lastTruthValue); + LOGGER.trace("Current atom truth value is: {}", assignment.getTruth(atom)); + } if (checksEnabled) { runChecks(); } ThriceTruth currentAtomTruth = assignment.getTruth(atom); + if (!foundFirstAnswerSet) { + // Record old truth value and return if no answer set has been found yet. + lastTruthValue = currentAtomTruth; + return; + } + LOGGER.trace("Current truth value is: {}", currentAtomTruth); if (lastTruthValue == null) { // Change from unassigned to some truth value. if (currentAtomTruth == MBT) { diff --git a/src/test/java/at/ac/tuwien/kr/alpha/solver/WeakConstraintsTests.java b/src/test/java/at/ac/tuwien/kr/alpha/solver/WeakConstraintsTests.java index a348bc7b9..63c2f4439 100644 --- a/src/test/java/at/ac/tuwien/kr/alpha/solver/WeakConstraintsTests.java +++ b/src/test/java/at/ac/tuwien/kr/alpha/solver/WeakConstraintsTests.java @@ -16,9 +16,8 @@ public void simpleWeightsSameLevel() { String program = ":~a.[1@0,foo,bar]" + ":~b.[2@0,baz]" + "a :- not b. b:- not a."; - System.out.println(program); Set actualAnswerSets = collectSet(program); - TestUtils.assertOptimumAnswerSetEquals("a", "1", actualAnswerSets); + TestUtils.assertOptimumAnswerSetEquals("a", "1@0", actualAnswerSets); } @@ -28,6 +27,16 @@ public void simpleWeightedAnswerSet() { ":~b.[1@1,baz]" + "a :- not b. b:- not a."; Set actualAnswerSets = collectSet(program); - TestUtils.assertOptimumAnswerSetEquals("b", "0:1:0", actualAnswerSets); + TestUtils.assertOptimumAnswerSetEquals("b", "1@1", actualAnswerSets); + } + + @Test + public void simpleMultiLevelWeightedAnswerSet() { + String program = ":~a.[2@2,foo,bar]" + + ":~b.[1@1,baz]" + + ":~b.[3@-4,baz]" + + "a :- not b. b:- not a."; + Set actualAnswerSets = collectSet(program); + TestUtils.assertOptimumAnswerSetEquals("b", "3@-4, 1@1", actualAnswerSets); } } From 4ab33072d3936344701defe58956676d1b7ffe3e Mon Sep 17 00:00:00 2001 From: Antonius Weinzierl Date: Tue, 1 Jun 2021 16:29:45 +0200 Subject: [PATCH 09/27] Increase test timeouts as ChoiceManager has internal checks enabled now. --- .../ac/tuwien/kr/alpha/solver/OmigaBenchmarksTest.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/test/java/at/ac/tuwien/kr/alpha/solver/OmigaBenchmarksTest.java b/src/test/java/at/ac/tuwien/kr/alpha/solver/OmigaBenchmarksTest.java index 2e3b97f8c..f79cc259a 100644 --- a/src/test/java/at/ac/tuwien/kr/alpha/solver/OmigaBenchmarksTest.java +++ b/src/test/java/at/ac/tuwien/kr/alpha/solver/OmigaBenchmarksTest.java @@ -45,22 +45,22 @@ public class OmigaBenchmarksTest extends AbstractSolverTests { private static final Logger LOGGER = LoggerFactory.getLogger(OmigaBenchmarksTest.class); - @Test(timeout = 10000) + @Test(timeout = 20000) public void test3Col_10_18() throws IOException { test("3col", "3col-10-18.txt"); } - @Test(timeout = 10000) + @Test(timeout = 20000) public void test3Col_20_38() throws IOException { test("3col", "3col-20-38.txt"); } - @Test(timeout = 15000) + @Test(timeout = 25000) public void testCutedge_100_30() throws IOException { test("cutedge", "cutedge-100-30.txt"); } - @Test(timeout = 15000) + @Test(timeout = 25000) public void testCutedge_100_50() throws IOException { test("cutedge", "cutedge-100-50.txt"); } @@ -77,7 +77,7 @@ public void testLocstrat_400() throws IOException { test("locstrat", "locstrat-400.txt"); } - @Test(timeout = 15000) + @Test(timeout = 25000) public void testReach_1() throws IOException { ignoreTestForNaiveSolver(); ignoreNonDefaultDomainIndependentHeuristics(); From 8c20bf4b049423052081d488e5c4e0f376315e35 Mon Sep 17 00:00:00 2001 From: Antonius Weinzierl Date: Fri, 6 Aug 2021 00:26:08 +0200 Subject: [PATCH 10/27] Fix bug in WeakConstraintsManager, polish WeightedAnswerSet. --- .../tuwien/kr/alpha/common/WeightedAnswerSet.java | 14 ++++++-------- .../kr/alpha/solver/WeakConstraintsManager.java | 10 +++++++--- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/src/main/java/at/ac/tuwien/kr/alpha/common/WeightedAnswerSet.java b/src/main/java/at/ac/tuwien/kr/alpha/common/WeightedAnswerSet.java index bd7a1dedb..347b6f4f3 100644 --- a/src/main/java/at/ac/tuwien/kr/alpha/common/WeightedAnswerSet.java +++ b/src/main/java/at/ac/tuwien/kr/alpha/common/WeightedAnswerSet.java @@ -61,16 +61,14 @@ public int compareWeights(WeightedAnswerSet other) { if (otherWeight == null) { return thisWeight < 0 ? -1 : 1; } - if (thisLevel.equals(otherLevel)) { - if (thisWeight.equals(otherWeight)) { - continue; - } else { - return thisWeight < otherWeight ? -1 : 1; - } - - } else { + // Found level and weight for this and other WeightedAnswerSet. + if (!thisLevel.equals(otherLevel)) { return thisLevel < otherLevel ? -1 : 1; } + if (!thisWeight.equals(otherWeight)) { + return thisWeight < otherWeight ? -1 : 1; + } + // Same level and same weight for both WeightedAnswerSet, now continue with next lower level. } } diff --git a/src/main/java/at/ac/tuwien/kr/alpha/solver/WeakConstraintsManager.java b/src/main/java/at/ac/tuwien/kr/alpha/solver/WeakConstraintsManager.java index a5882d76b..17dc92a2f 100644 --- a/src/main/java/at/ac/tuwien/kr/alpha/solver/WeakConstraintsManager.java +++ b/src/main/java/at/ac/tuwien/kr/alpha/solver/WeakConstraintsManager.java @@ -103,6 +103,7 @@ private void initializeFirstWeightsAtLevel() { foundFirstAnswerSet = true; HashMap sumWeightsAtLevel = new HashMap<>(); int highestLevel = 0; + int lowestLevel = 0; for (WeakConstraintAtomCallback atomCallback : knownAtomCallbacksForFirstAnswerSet) { ThriceTruth truth = assignment.getTruth(atomCallback.atom); if (truth == null || !truth.toBoolean()) { @@ -110,10 +111,13 @@ private void initializeFirstWeightsAtLevel() { continue; } int level = atomCallback.level; + // Record if level is highest or lowest so far. if (highestLevel < level) { - // Record level if it is highest so far. highestLevel = level; } + if (lowestLevel > level) { + lowestLevel = level; + } // Update weight information. if (sumWeightsAtLevel.get(level) == null) { sumWeightsAtLevel.put(level, atomCallback.weight); @@ -124,8 +128,8 @@ private void initializeFirstWeightsAtLevel() { } maxLevel = highestLevel; LOGGER.trace("Maximum recorded level (0-offset) is: {} ({})", maxLevel, listOffsetToLevel(0)); - bestKnownWeightAtLevels = new ArrayList<>(highestLevel); - growForLevel(bestKnownWeightAtLevels, highestLevel); + bestKnownWeightAtLevels = new ArrayList<>(); + growForLevel(bestKnownWeightAtLevels, lowestLevel); for (Map.Entry levelWeight : sumWeightsAtLevel.entrySet()) { bestKnownWeightAtLevels.set(maxLevel - levelWeight.getKey(), levelWeight.getValue()); } From 51f402c4bbd616204dc782412895422873067154 Mon Sep 17 00:00:00 2001 From: Antonius Weinzierl Date: Fri, 6 Aug 2021 03:50:15 +0200 Subject: [PATCH 11/27] Refactor WeakConstraintsManager internals. --- .../alpha/solver/WeakConstraintsManager.java | 474 +++++++++--------- 1 file changed, 248 insertions(+), 226 deletions(-) diff --git a/src/main/java/at/ac/tuwien/kr/alpha/solver/WeakConstraintsManager.java b/src/main/java/at/ac/tuwien/kr/alpha/solver/WeakConstraintsManager.java index 17dc92a2f..21ba1481a 100644 --- a/src/main/java/at/ac/tuwien/kr/alpha/solver/WeakConstraintsManager.java +++ b/src/main/java/at/ac/tuwien/kr/alpha/solver/WeakConstraintsManager.java @@ -28,26 +28,14 @@ public class WeakConstraintsManager implements Checkable { private static final Logger LOGGER = LoggerFactory.getLogger(WeakConstraintsManager.class); - - private ArrayList currentWeightAtLevels; - private ArrayList bestKnownWeightAtLevels; // Stores weights in inverted order: zero is highest violated level, may also grow to represent negative levels. + final EfficientWeightAtLevelsStore efficientWeightAtLevelsStore; private final WritableAssignment assignment; - private boolean foundFirstAnswerSet; - private int maxOffsetCurrentIsAllEqualBest; // The highest level for which holds: on all levels below (or equal), the current weights are the same as the weights for the best known. - private boolean isCurrentBetterThanBest; private final ArrayList knownCallbackAtoms; - private final ArrayList knownAtomCallbacksForFirstAnswerSet = new ArrayList<>(); - private int maxLevel; // Stores which actual level corresponds to array position 0 in bestKnownWeightAtLevels, i.e, the highest level that is violated. Value may decrease if better answer-sets are found. - private boolean checksEnabled; public WeakConstraintsManager(WritableAssignment assignment) { this.assignment = assignment; - this.currentWeightAtLevels = new ArrayList<>(); - this.bestKnownWeightAtLevels = new ArrayList<>(); - this.foundFirstAnswerSet = false; - this.maxOffsetCurrentIsAllEqualBest = -1; - this.isCurrentBetterThanBest = true; + this.efficientWeightAtLevelsStore = new EfficientWeightAtLevelsStore(assignment); this.knownCallbackAtoms = new ArrayList<>(); } @@ -62,98 +50,10 @@ public void addWeakConstraintsInformation(List assignment.registerCallbackOnChange(wcA.atom); assignment.getAtomCallbackManager().recordCallback(wcA.atom, wcA); knownCallbackAtoms.add(wcA.atom); - if (!foundFirstAnswerSet) { - knownAtomCallbacksForFirstAnswerSet.add(wcA); - } - } - } - - /** - * Mark the current weight as being the best of all currently known answer-sets. - */ - public void markCurrentWeightAsBestKnown() { - LOGGER.trace("Marking current answer-set as best known."); - if (!foundFirstAnswerSet) { - initializeFirstWeightsAtLevel(); - } else { - // Remove unnecessary high levels (with weights 0). - int trim = 0; - for (int i = 0; i < currentWeightAtLevels.size(); i++) { - if (currentWeightAtLevels.get(i) != 0) { - trim = i; - break; - } + if (!efficientWeightAtLevelsStore.foundFirstAnswerSet) { + efficientWeightAtLevelsStore.knownAtomCallbacksForFirstAnswerSet.add(wcA); } - if (trim > 0) { - bestKnownWeightAtLevels = new ArrayList<>(currentWeightAtLevels.subList(trim, currentWeightAtLevels.size())); - currentWeightAtLevels = new ArrayList<>(bestKnownWeightAtLevels); - } else { - bestKnownWeightAtLevels = new ArrayList<>(currentWeightAtLevels); - } - maxLevel -= trim; - } - maxOffsetCurrentIsAllEqualBest = bestKnownWeightAtLevels.size() - 1; - LOGGER.trace("Max offset current is all equal to best known is: {}", maxOffsetCurrentIsAllEqualBest); - isCurrentBetterThanBest = false; - } - - private void initializeFirstWeightsAtLevel() { - // First answer set has been found, take its maximum level as zero-level. - LOGGER.trace("Initializing for first answer-set."); - foundFirstAnswerSet = true; - HashMap sumWeightsAtLevel = new HashMap<>(); - int highestLevel = 0; - int lowestLevel = 0; - for (WeakConstraintAtomCallback atomCallback : knownAtomCallbacksForFirstAnswerSet) { - ThriceTruth truth = assignment.getTruth(atomCallback.atom); - if (truth == null || !truth.toBoolean()) { - // Skip weak-constraint atoms being unassigned or false. - continue; - } - int level = atomCallback.level; - // Record if level is highest or lowest so far. - if (highestLevel < level) { - highestLevel = level; - } - if (lowestLevel > level) { - lowestLevel = level; - } - // Update weight information. - if (sumWeightsAtLevel.get(level) == null) { - sumWeightsAtLevel.put(level, atomCallback.weight); - } else { - int newWeight = sumWeightsAtLevel.get(level) + atomCallback.weight; - sumWeightsAtLevel.put(level, newWeight); - } - } - maxLevel = highestLevel; - LOGGER.trace("Maximum recorded level (0-offset) is: {} ({})", maxLevel, listOffsetToLevel(0)); - bestKnownWeightAtLevels = new ArrayList<>(); - growForLevel(bestKnownWeightAtLevels, lowestLevel); - for (Map.Entry levelWeight : sumWeightsAtLevel.entrySet()) { - bestKnownWeightAtLevels.set(maxLevel - levelWeight.getKey(), levelWeight.getValue()); - } - currentWeightAtLevels = new ArrayList<>(bestKnownWeightAtLevels); - LOGGER.trace("Initially, current/best known weights are: {}", bestKnownWeightAtLevels); - } - - private void growForLevel(ArrayList listToGrow, int level) { - int listOffset = levelToListOffset(level); - while (listOffset >= listToGrow.size()) { - listToGrow.add(0); - } - } - - private int levelToListOffset(int level) { - int levelInList = maxLevel - level; - if (levelInList < 0) { - throw oops("Level optimisation in WeakConstraintsManager is negative."); } - return levelInList; - } - - private int listOffsetToLevel(int offset) { - return maxLevel - offset; } /** @@ -161,10 +61,18 @@ private int listOffsetToLevel(int offset) { * @return true if the current partial interpretation has worse (or equal) weight than the best-known answer-set. */ public boolean isCurrentBetterThanBest() { + boolean isCurrentBetterThanBest = efficientWeightAtLevelsStore.isCurrentBetterThanBest(); LOGGER.trace("Is current better than best? {}", isCurrentBetterThanBest); return isCurrentBetterThanBest; } + /** + * Mark the current weight as being the best of all currently known answer-sets. + */ + public void markCurrentWeightAsBestKnown() { + efficientWeightAtLevelsStore.markCurrentWeightAsBestKnown(); + } + /** * Generates a NoGood that prevents the current weight to be derived again. * @return a NoGood which excludes the exact weight of the current assignment. @@ -182,162 +90,276 @@ public NoGood generateExcludingNoGood() { return NoGood.fromConstraint(trueCallbackAtoms, Collections.emptyList()); } + @Override + public void setChecksEnabled(boolean checksEnabled) { + this.checksEnabled = checksEnabled; + } + /** * Returns the current weights and their levels. * @return a TreeMap mapping levels to weights. Note that values may be non-continuous and negative levels are possible. */ public TreeMap getCurrentWeightAtLevels() { TreeMap weightPerLevels = new TreeMap<>(); - for (int i = 0; i < currentWeightAtLevels.size(); i++) { - Integer weightAti = currentWeightAtLevels.get(i); + for (int i = 0; i < efficientWeightAtLevelsStore.currentWeightAtLevels.size(); i++) { + Integer weightAti = efficientWeightAtLevelsStore.currentWeightAtLevels.get(i); if (weightAti == 0) { continue; } - weightPerLevels.put(listOffsetToLevel(i), weightAti); + weightPerLevels.put(efficientWeightAtLevelsStore.listOffsetToLevel(i), weightAti); } return weightPerLevels; } - private void increaseCurrentWeight(WeakConstraintAtomCallback atom) { - // Record the new weight. - growForLevel(currentWeightAtLevels, atom.level); - int listOffset = levelToListOffset(atom.level); - int newWeight = currentWeightAtLevels.get(listOffset) + atom.weight; - currentWeightAtLevels.set(listOffset, newWeight); - - // Now check whether current is worse than best known. - if (listOffset > maxOffsetCurrentIsAllEqualBest + 1) { - // Weight in some level where differences do not (yet) matter increased, but on lower level current partial assignment is still better than best found answer-set. - // Nothing to do here. - return; - } else if (listOffset <= maxOffsetCurrentIsAllEqualBest) { - // Weight in level with previously equal weights increased, current is now worse than best. - isCurrentBetterThanBest = false; - maxOffsetCurrentIsAllEqualBest = listOffset - 1; - } else if (listOffset == maxOffsetCurrentIsAllEqualBest + 1) { - // Weight increased for the first level where current and best-known are not equal. - moveUpwardsMaxLevelCurrentIsAllEqual(); - recomputeIsCurrentBetterThanBest(); - } else { - throw oops("Increasing weight of current answer reached unforeseen state."); + public static class EfficientWeightAtLevelsStore { + private final WritableAssignment assignment; + // Stores weights in inverted order: zero is highest violated level, may also grow to represent negative levels. + public ArrayList currentWeightAtLevels; + public ArrayList bestKnownWeightAtLevels; + public boolean foundFirstAnswerSet; + // The highest level for which holds: on all levels below (or equal), the current weights are the same as the weights for the best known. + public int maxOffsetCurrentIsAllEqualBest; + public final ArrayList knownAtomCallbacksForFirstAnswerSet = new ArrayList<>(); + // Stores which actual level corresponds to array position 0 in bestKnownWeightAtLevels, i.e, the highest level that is violated. Value may decrease if better answer-sets are found. + public int maxLevel; + + private boolean isCurrentBetterThanBest; + + public EfficientWeightAtLevelsStore(WritableAssignment assignment) { + this.assignment = assignment; + currentWeightAtLevels = new ArrayList<>(); + bestKnownWeightAtLevels = new ArrayList<>(); + foundFirstAnswerSet = false; + maxOffsetCurrentIsAllEqualBest = -1; + isCurrentBetterThanBest = true; } - } - - private void decreaseCurrentWeight(WeakConstraintAtomCallback atom) { - // Record the new weight. - int listOffset = levelToListOffset(atom.level); - int newWeight = currentWeightAtLevels.get(listOffset) - atom.weight; - currentWeightAtLevels.set(listOffset, newWeight); - // Now check whether current is better than best known and adapt maxOffsetCurrentIsAllEqualBest. - if (listOffset <= maxOffsetCurrentIsAllEqualBest) { - // Improved below the point where current is equal to best, so current is better than best now. - isCurrentBetterThanBest = true; - maxOffsetCurrentIsAllEqualBest = listOffset - 1; // All weights below the change are equal. - } else if (listOffset > maxOffsetCurrentIsAllEqualBest + 1) { - // Decrease in such a high level, that there is at least one level (maxLevel+1) where weights are - // different and whose difference still dominates whether current is better than best known. - return; - } else if (listOffset == maxOffsetCurrentIsAllEqualBest + 1) { - // Decrease might make current better than best known and change level of equals. - moveUpwardsMaxLevelCurrentIsAllEqual(); - recomputeIsCurrentBetterThanBest(); - } else { - throw oops("Decreasing weight of current answer reached unforeseen state."); + public boolean isCurrentBetterThanBest() { + return isCurrentBetterThanBest; } - } - private void moveUpwardsMaxLevelCurrentIsAllEqual() { - for (int i = max(maxOffsetCurrentIsAllEqualBest, 0); i < currentWeightAtLevels.size(); i++) { - int currentLevelWeight = currentWeightAtLevels.get(i); - int bestLevelWeight = bestKnownWeightAtLevels.size() > i ? bestKnownWeightAtLevels.get(i) : 0; - if (currentLevelWeight != bestLevelWeight) { - // Level is the first where values differ, previous level was maximum one where values are equal. - maxOffsetCurrentIsAllEqualBest = i - 1; + public void increaseCurrentWeight(WeakConstraintAtomCallback atom) { + // Record the new weight. + growForLevel(currentWeightAtLevels, atom.level); + int listOffset = levelToListOffset(atom.level); + int newWeight = currentWeightAtLevels.get(listOffset) + atom.weight; + currentWeightAtLevels.set(listOffset, newWeight); + + // Now check whether current is worse than best known. + if (listOffset > maxOffsetCurrentIsAllEqualBest + 1) { + // Weight in some level where differences do not (yet) matter increased, but on lower level current partial assignment is still better than best found answer-set. + // Nothing to do here. return; + } else if (listOffset <= maxOffsetCurrentIsAllEqualBest) { + // Weight in level with previously equal weights increased, current is now worse than best. + isCurrentBetterThanBest = false; + maxOffsetCurrentIsAllEqualBest = listOffset - 1; + } else if (listOffset == maxOffsetCurrentIsAllEqualBest + 1) { + // Weight increased for the first level where current and best-known are not equal. + moveUpwardsMaxLevelCurrentIsAllEqual(); + recomputeIsCurrentBetterThanBest(); + } else { + throw oops("Increasing weight of current answer reached unforeseen state."); } } - // Iterated all levels, so weights are the same on all levels. - maxOffsetCurrentIsAllEqualBest = currentWeightAtLevels.size() - 1; - } - private void recomputeIsCurrentBetterThanBest() { - // The weights at the first level above maxOffsetCurrentIsAllEqualBest dominate whether current is worse than best known. - if (maxOffsetCurrentIsAllEqualBest == currentWeightAtLevels.size() - 1) { - // Current and best known are equal even up to the last level. - isCurrentBetterThanBest = false; - return; + private void decreaseCurrentWeight(WeakConstraintAtomCallback atom) { + // Record the new weight. + int listOffset = levelToListOffset(atom.level); + int newWeight = currentWeightAtLevels.get(listOffset) - atom.weight; + currentWeightAtLevels.set(listOffset, newWeight); + + // Now check whether current is better than best known and adapt maxOffsetCurrentIsAllEqualBest. + if (listOffset <= maxOffsetCurrentIsAllEqualBest) { + // Improved below the point where current is equal to best, so current is better than best now. + isCurrentBetterThanBest = true; + // All weights below the change are equal. + maxOffsetCurrentIsAllEqualBest = listOffset - 1; + } else if (listOffset > maxOffsetCurrentIsAllEqualBest + 1) { + // Decrease in such a high level, that there is at least one level (maxLevel+1) where weights are + // different and whose difference still dominates whether current is better than best known. + return; + } else if (listOffset == maxOffsetCurrentIsAllEqualBest + 1) { + // Decrease might make current better than best known and change level of equals. + moveUpwardsMaxLevelCurrentIsAllEqual(); + recomputeIsCurrentBetterThanBest(); + } else { + throw oops("Decreasing weight of current answer reached unforeseen state."); + } } - int comparisonOffset = maxOffsetCurrentIsAllEqualBest + 1; - int currentLevelWeight = currentWeightAtLevels.get(comparisonOffset); - int bestLevelWeight = bestKnownWeightAtLevels.size() > comparisonOffset ? bestKnownWeightAtLevels.get(comparisonOffset) : 0; - isCurrentBetterThanBest = currentLevelWeight < bestLevelWeight; - } - - @Override - public void setChecksEnabled(boolean checksEnabled) { - this.checksEnabled = checksEnabled; - } - private void runChecks() { - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("Running checks."); - LOGGER.trace("Maximum recorded level (0-offset) is: {} ({})", maxLevel, listOffsetToLevel(0)); - LOGGER.trace("Currently best known weights are: {}", bestKnownWeightAtLevels); - LOGGER.trace("Current weights are: {}", currentWeightAtLevels); - LOGGER.trace("Max offset current is all equal to best known is: {}", maxOffsetCurrentIsAllEqualBest); - LOGGER.trace("Is current better than best-known: {}", isCurrentBetterThanBest); + private void moveUpwardsMaxLevelCurrentIsAllEqual() { + for (int i = max(maxOffsetCurrentIsAllEqualBest, 0); i < currentWeightAtLevels.size(); i++) { + int currentLevelWeight = currentWeightAtLevels.get(i); + int bestLevelWeight = bestKnownWeightAtLevels.size() > i ? bestKnownWeightAtLevels.get(i) : 0; + if (currentLevelWeight != bestLevelWeight) { + // Level is the first where values differ, previous level was maximum one where values are equal. + maxOffsetCurrentIsAllEqualBest = i - 1; + return; + } + } + // Iterated all levels, so weights are the same on all levels. + maxOffsetCurrentIsAllEqualBest = currentWeightAtLevels.size() - 1; + } + private void recomputeIsCurrentBetterThanBest() { + // The weights at the first level above maxOffsetCurrentIsAllEqualBest dominate whether current is worse than best known. + if (maxOffsetCurrentIsAllEqualBest == currentWeightAtLevels.size() - 1) { + // Current and best known are equal even up to the last level. + isCurrentBetterThanBest = false; + return; + } + int comparisonOffset = maxOffsetCurrentIsAllEqualBest + 1; + int currentLevelWeight = currentWeightAtLevels.get(comparisonOffset); + int bestLevelWeight = bestKnownWeightAtLevels.size() > comparisonOffset ? bestKnownWeightAtLevels.get(comparisonOffset) : 0; + isCurrentBetterThanBest = currentLevelWeight < bestLevelWeight; } - if (!foundFirstAnswerSet && !bestKnownWeightAtLevels.isEmpty()) { - throw oops("WeakConstraintManager has best known answer set information but no answer set was found yet."); + + private int listOffsetToLevel(int offset) { + return maxLevel - offset; } - if (foundFirstAnswerSet && bestKnownWeightAtLevels.get(0) == 0) { - throw oops("WeakConstraintManager has best-known answer set with zero-weights at highest level."); + + private void growForLevel(ArrayList listToGrow, int level) { + int listOffset = levelToListOffset(level); + while (listOffset >= listToGrow.size()) { + listToGrow.add(0); + } } - // Check whether isCurrentBetterThanBest flag is consistent. - if (isCurrentBetterThanBest != checkIsCurrentBetterThanBest()) { - throw oops("WeakConstraintManager detected valuation of current assignment is inconsistent with state flag."); + + public void markCurrentWeightAsBestKnown() { + LOGGER.trace("Marking current answer-set as best known."); + if (!foundFirstAnswerSet) { + initializeFirstWeightsAtLevel(); + } else { + // Remove unnecessary high levels (with weights 0). + int trim = 0; + for (int i = 0; i < currentWeightAtLevels.size(); i++) { + if (currentWeightAtLevels.get(i) != 0) { + trim = i; + break; + } + } + if (trim > 0) { + bestKnownWeightAtLevels = new ArrayList<>(currentWeightAtLevels.subList(trim, currentWeightAtLevels.size())); + currentWeightAtLevels = new ArrayList<>(bestKnownWeightAtLevels); + } else { + bestKnownWeightAtLevels = new ArrayList<>(currentWeightAtLevels); + } + maxLevel = maxLevel - trim; + } + maxOffsetCurrentIsAllEqualBest = bestKnownWeightAtLevels.size() - 1; + LOGGER.trace("Max offset current is all equal to best known is: {}", maxOffsetCurrentIsAllEqualBest); + isCurrentBetterThanBest = false; } - checkLevelBar(); - LOGGER.trace("Checks done."); - } - private void checkLevelBar() { - int highestLevelWhereLowerLevelsAreEqual = -1; - for (int i = 0; i < currentWeightAtLevels.size(); i++) { - int levelValue = i < bestKnownWeightAtLevels.size() ? bestKnownWeightAtLevels.get(i) : 0; - if (currentWeightAtLevels.get(i) != levelValue) { - break; + private void initializeFirstWeightsAtLevel() { + // First answer set has been found, take its maximum level as zero-level. + LOGGER.trace("Initializing for first answer-set."); + foundFirstAnswerSet = true; + HashMap sumWeightsAtLevel = new HashMap<>(); + int highestLevel = 0; + int lowestLevel = 0; + for (WeakConstraintAtomCallback atomCallback : knownAtomCallbacksForFirstAnswerSet) { + ThriceTruth truth = assignment.getTruth(atomCallback.atom); + if (truth == null || !truth.toBoolean()) { + // Skip weak-constraint atoms being unassigned or false. + continue; + } + int level = atomCallback.level; + // Record if level is highest or lowest so far. + if (highestLevel < level) { + highestLevel = level; + } + if (lowestLevel > level) { + lowestLevel = level; + } + // Update weight information. + if (sumWeightsAtLevel.get(level) == null) { + sumWeightsAtLevel.put(level, atomCallback.weight); + } else { + int newWeight = sumWeightsAtLevel.get(level) + atomCallback.weight; + sumWeightsAtLevel.put(level, newWeight); + } + } + maxLevel = highestLevel; + LOGGER.trace("Maximum recorded level (0-offset) is: {} ({})", maxLevel, listOffsetToLevel(0)); + bestKnownWeightAtLevels = new ArrayList<>(); + growForLevel(bestKnownWeightAtLevels, lowestLevel); + for (Map.Entry levelWeight : sumWeightsAtLevel.entrySet()) { + bestKnownWeightAtLevels.set(maxLevel - levelWeight.getKey(), levelWeight.getValue()); } - highestLevelWhereLowerLevelsAreEqual = i; + currentWeightAtLevels = new ArrayList<>(bestKnownWeightAtLevels); + LOGGER.trace("Initially, current/best known weights are: {}", bestKnownWeightAtLevels); } - // The level bar should be at highestLevelWhereLowerLevelsAreEqual+1. - if (maxOffsetCurrentIsAllEqualBest != highestLevelWhereLowerLevelsAreEqual) { - throw oops("WeakConstraintManager detected level bar at wrong level, it is " + maxOffsetCurrentIsAllEqualBest - + "and should be " + highestLevelWhereLowerLevelsAreEqual); + + private int levelToListOffset(int level) { + int levelInList = maxLevel - level; + if (levelInList < 0) { + throw oops("Level optimisation in WeakConstraintsManager is negative."); + } + return levelInList; } - } - private boolean checkIsCurrentBetterThanBest() { - if (!foundFirstAnswerSet) { - // If no answer set has been found, the current is always better than best known. - return true; + private void runChecks() { + if (LOGGER.isTraceEnabled()) { + LOGGER.trace("Running checks."); + LOGGER.trace("Maximum recorded level (0-offset) is: {} ({})", maxLevel, listOffsetToLevel(0)); + LOGGER.trace("Currently best known weights are: {}", bestKnownWeightAtLevels); + LOGGER.trace("Current weights are: {}", currentWeightAtLevels); + LOGGER.trace("Max offset current is all equal to best known is: {}", maxOffsetCurrentIsAllEqualBest); + LOGGER.trace("Is current better than best-known: {}", isCurrentBetterThanBest()); + + } + if (!foundFirstAnswerSet && !bestKnownWeightAtLevels.isEmpty()) { + throw oops("WeakConstraintManager has best known answer set information but no answer set was found yet."); + } + if (foundFirstAnswerSet && bestKnownWeightAtLevels.get(0) == 0) { + throw oops("WeakConstraintManager has best-known answer set with zero-weights at highest level."); + } + // Check whether isCurrentBetterThanBest flag is consistent. + if (isCurrentBetterThanBest() != checkIsCurrentBetterThanBest()) { + throw oops("WeakConstraintManager detected valuation of current assignment is inconsistent with state flag."); + } + checkLevelBar(); + LOGGER.trace("Checks done."); } - for (int i = 0; i < currentWeightAtLevels.size(); i++) { - if (i >= bestKnownWeightAtLevels.size()) { - // Current has violations at level not violated by best. - return false; + + private void checkLevelBar() { + int highestLevelWhereLowerLevelsAreEqual = -1; + for (int i = 0; i < currentWeightAtLevels.size(); i++) { + int levelValue = i < bestKnownWeightAtLevels.size() ? bestKnownWeightAtLevels.get(i) : 0; + if (currentWeightAtLevels.get(i) != levelValue) { + break; + } + highestLevelWhereLowerLevelsAreEqual = i; } - if (currentWeightAtLevels.get(i) < bestKnownWeightAtLevels.get(i)) { + // The level bar should be at highestLevelWhereLowerLevelsAreEqual+1. + if (maxOffsetCurrentIsAllEqualBest != highestLevelWhereLowerLevelsAreEqual) { + throw oops("WeakConstraintManager detected level bar at wrong level, it is " + maxOffsetCurrentIsAllEqualBest + + "and should be " + highestLevelWhereLowerLevelsAreEqual); + } + } + + private boolean checkIsCurrentBetterThanBest() { + if (!foundFirstAnswerSet) { + // If no answer set has been found, the current is always better than best known. return true; } - if (currentWeightAtLevels.get(i) > bestKnownWeightAtLevels.get(i)) { - return false; + for (int i = 0; i < currentWeightAtLevels.size(); i++) { + if (i >= bestKnownWeightAtLevels.size()) { + // Current has violations at level not violated by best. + return false; + } + if (currentWeightAtLevels.get(i) < bestKnownWeightAtLevels.get(i)) { + return true; + } + if (currentWeightAtLevels.get(i) > bestKnownWeightAtLevels.get(i)) { + return false; + } } + // All levels have same weight for current as for best known, so at this point usually current is not better than best. + return false; } - // All levels have same weight for current as for best known, so at this point usually current is not better than best. - return false; } class WeakConstraintAtomCallback implements AtomCallbackManager.AtomCallback { @@ -359,10 +381,10 @@ public void processCallback() { LOGGER.trace("Current atom truth value is: {}", assignment.getTruth(atom)); } if (checksEnabled) { - runChecks(); + efficientWeightAtLevelsStore.runChecks(); } ThriceTruth currentAtomTruth = assignment.getTruth(atom); - if (!foundFirstAnswerSet) { + if (!efficientWeightAtLevelsStore.foundFirstAnswerSet) { // Record old truth value and return if no answer set has been found yet. lastTruthValue = currentAtomTruth; return; @@ -371,14 +393,14 @@ public void processCallback() { if (lastTruthValue == null) { // Change from unassigned to some truth value. if (currentAtomTruth == MBT) { - increaseCurrentWeight(this); + efficientWeightAtLevelsStore.increaseCurrentWeight(this); } // Note: for assignment to FALSE or MBT->TRUE, no change needed. lastTruthValue = currentAtomTruth; } else { if ((lastTruthValue == MBT || lastTruthValue == TRUE) && currentAtomTruth == null) { // Change from TRUE/MBT to unassigned. - decreaseCurrentWeight(this); + efficientWeightAtLevelsStore.decreaseCurrentWeight(this); } // Note: for backtracking from TRUE to MBT no change is needed. lastTruthValue = currentAtomTruth; @@ -387,7 +409,7 @@ public void processCallback() { } } if (checksEnabled) { - runChecks(); + efficientWeightAtLevelsStore.runChecks(); } } From ed22c662fd660515b6e4cf3e8564a93708f93c09 Mon Sep 17 00:00:00 2001 From: Antonius Weinzierl Date: Fri, 6 Aug 2021 04:00:57 +0200 Subject: [PATCH 12/27] Move WeakConstraintsManager to subpackage, adapt necessary visibilities. --- .../at/ac/tuwien/kr/alpha/solver/AtomCallbackManager.java | 6 +++--- .../java/at/ac/tuwien/kr/alpha/solver/DefaultSolver.java | 1 + .../solver/{ => optimization}/WeakConstraintsManager.java | 6 +++++- 3 files changed, 9 insertions(+), 4 deletions(-) rename src/main/java/at/ac/tuwien/kr/alpha/solver/{ => optimization}/WeakConstraintsManager.java (98%) diff --git a/src/main/java/at/ac/tuwien/kr/alpha/solver/AtomCallbackManager.java b/src/main/java/at/ac/tuwien/kr/alpha/solver/AtomCallbackManager.java index 0579dc556..119f9e146 100644 --- a/src/main/java/at/ac/tuwien/kr/alpha/solver/AtomCallbackManager.java +++ b/src/main/java/at/ac/tuwien/kr/alpha/solver/AtomCallbackManager.java @@ -13,13 +13,13 @@ * * Copyright (c) 2020, the Alpha Team. */ -class AtomCallbackManager { +public class AtomCallbackManager { private static final Logger LOGGER = LoggerFactory.getLogger(AtomCallbackManager.class); private AtomCallback[] atomCallbacks = new AtomCallback[0]; - void recordCallback(int atom, AtomCallback atomCallback) { + public void recordCallback(int atom, AtomCallback atomCallback) { if (atomCallbacks[atom] != null && !atomCallbacks[atom].equals(atomCallback)) { throw oops("Recording different callbacks for one atom. Atom: " + atom); } @@ -52,7 +52,7 @@ void growForMaxAtomId(int maxAtomId) { /** * Interface for callbacks to be called when atoms change their truth value. */ - interface AtomCallback { + public interface AtomCallback { void processCallback(); } diff --git a/src/main/java/at/ac/tuwien/kr/alpha/solver/DefaultSolver.java b/src/main/java/at/ac/tuwien/kr/alpha/solver/DefaultSolver.java index 7648acea4..bea5457d4 100644 --- a/src/main/java/at/ac/tuwien/kr/alpha/solver/DefaultSolver.java +++ b/src/main/java/at/ac/tuwien/kr/alpha/solver/DefaultSolver.java @@ -50,6 +50,7 @@ import at.ac.tuwien.kr.alpha.solver.heuristics.HeuristicsConfiguration; import at.ac.tuwien.kr.alpha.solver.heuristics.NaiveHeuristic; import at.ac.tuwien.kr.alpha.solver.learning.GroundConflictNoGoodLearner; +import at.ac.tuwien.kr.alpha.solver.optimization.WeakConstraintsManager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/src/main/java/at/ac/tuwien/kr/alpha/solver/WeakConstraintsManager.java b/src/main/java/at/ac/tuwien/kr/alpha/solver/optimization/WeakConstraintsManager.java similarity index 98% rename from src/main/java/at/ac/tuwien/kr/alpha/solver/WeakConstraintsManager.java rename to src/main/java/at/ac/tuwien/kr/alpha/solver/optimization/WeakConstraintsManager.java index 21ba1481a..c02301245 100644 --- a/src/main/java/at/ac/tuwien/kr/alpha/solver/WeakConstraintsManager.java +++ b/src/main/java/at/ac/tuwien/kr/alpha/solver/optimization/WeakConstraintsManager.java @@ -1,6 +1,10 @@ -package at.ac.tuwien.kr.alpha.solver; +package at.ac.tuwien.kr.alpha.solver.optimization; import at.ac.tuwien.kr.alpha.common.NoGood; +import at.ac.tuwien.kr.alpha.solver.AtomCallbackManager; +import at.ac.tuwien.kr.alpha.solver.Checkable; +import at.ac.tuwien.kr.alpha.solver.ThriceTruth; +import at.ac.tuwien.kr.alpha.solver.WritableAssignment; import org.apache.commons.lang3.tuple.Triple; import org.slf4j.Logger; import org.slf4j.LoggerFactory; From 7ab307f6bb2d0eaaa25258f4af0ded1342f6d971 Mon Sep 17 00:00:00 2001 From: Antonius Weinzierl Date: Fri, 6 Aug 2021 12:24:22 +0200 Subject: [PATCH 13/27] Refactor WeakConstraintsManager (move to own package, split into three classes). --- .../WeakConstraintAtomCallback.java | 50 ++ .../optimization/WeakConstraintsManager.java | 426 ++++-------------- .../optimization/WeightAtLevelsManager.java | 307 +++++++++++++ 3 files changed, 433 insertions(+), 350 deletions(-) create mode 100644 src/main/java/at/ac/tuwien/kr/alpha/solver/optimization/WeakConstraintAtomCallback.java create mode 100644 src/main/java/at/ac/tuwien/kr/alpha/solver/optimization/WeightAtLevelsManager.java diff --git a/src/main/java/at/ac/tuwien/kr/alpha/solver/optimization/WeakConstraintAtomCallback.java b/src/main/java/at/ac/tuwien/kr/alpha/solver/optimization/WeakConstraintAtomCallback.java new file mode 100644 index 000000000..709cac408 --- /dev/null +++ b/src/main/java/at/ac/tuwien/kr/alpha/solver/optimization/WeakConstraintAtomCallback.java @@ -0,0 +1,50 @@ +package at.ac.tuwien.kr.alpha.solver.optimization; + +import at.ac.tuwien.kr.alpha.solver.AtomCallbackManager; +import at.ac.tuwien.kr.alpha.solver.ThriceTruth; + +import java.util.Objects; + +/** + * Atom callbacks for weak constraints. + * + * Copyright (c) 2021, the Alpha Team. + */ +public class WeakConstraintAtomCallback implements AtomCallbackManager.AtomCallback { + private final WeakConstraintsManager weakConstraintsManager; + public final int atom; + public final int weight; + public final int level; + public ThriceTruth lastTruthValue; + + WeakConstraintAtomCallback(WeakConstraintsManager weakConstraintsManager, int atom, int weight, int level) { + this.atom = atom; + this.weight = weight; + this.level = level; + this.weakConstraintsManager = weakConstraintsManager; + } + + @Override + public void processCallback() { + weakConstraintsManager.processCallback(this); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + WeakConstraintAtomCallback that = (WeakConstraintAtomCallback) o; + return atom == that.atom && + weight == that.weight && + level == that.level; + } + + @Override + public int hashCode() { + return Objects.hash(atom, weight, level); + } +} diff --git a/src/main/java/at/ac/tuwien/kr/alpha/solver/optimization/WeakConstraintsManager.java b/src/main/java/at/ac/tuwien/kr/alpha/solver/optimization/WeakConstraintsManager.java index c02301245..b801da3da 100644 --- a/src/main/java/at/ac/tuwien/kr/alpha/solver/optimization/WeakConstraintsManager.java +++ b/src/main/java/at/ac/tuwien/kr/alpha/solver/optimization/WeakConstraintsManager.java @@ -1,7 +1,6 @@ package at.ac.tuwien.kr.alpha.solver.optimization; import at.ac.tuwien.kr.alpha.common.NoGood; -import at.ac.tuwien.kr.alpha.solver.AtomCallbackManager; import at.ac.tuwien.kr.alpha.solver.Checkable; import at.ac.tuwien.kr.alpha.solver.ThriceTruth; import at.ac.tuwien.kr.alpha.solver.WritableAssignment; @@ -11,17 +10,13 @@ import java.util.ArrayList; import java.util.Collections; -import java.util.HashMap; import java.util.List; -import java.util.Map; -import java.util.Objects; import java.util.TreeMap; +import java.util.stream.Collectors; import static at.ac.tuwien.kr.alpha.Util.oops; import static at.ac.tuwien.kr.alpha.common.Literals.atomToLiteral; import static at.ac.tuwien.kr.alpha.solver.ThriceTruth.MBT; -import static at.ac.tuwien.kr.alpha.solver.ThriceTruth.TRUE; -import static java.lang.Math.max; /** * Manages weak constraints: stores the value of the current partial assignment, the best known value of any answer-set, @@ -30,42 +25,102 @@ * Copyright (c) 2020-2021, the Alpha Team. */ public class WeakConstraintsManager implements Checkable { - private static final Logger LOGGER = LoggerFactory.getLogger(WeakConstraintsManager.class); - final EfficientWeightAtLevelsStore efficientWeightAtLevelsStore; - private final WritableAssignment assignment; + + final WritableAssignment assignment; + final WeightAtLevelsManager weightAtLevelsManager; private final ArrayList knownCallbackAtoms; - private boolean checksEnabled; + private final ArrayList knownAtomCallbacksForFirstAnswerSet = new ArrayList<>(); + private boolean foundFirstAnswerSet; public WeakConstraintsManager(WritableAssignment assignment) { this.assignment = assignment; - this.efficientWeightAtLevelsStore = new EfficientWeightAtLevelsStore(assignment); + this.weightAtLevelsManager = new WeightAtLevelsManager(this); this.knownCallbackAtoms = new ArrayList<>(); } + /** + * Registers newly obtained weak constraint atoms for callback at the assignment. + * @param weakConstraintAtomWeightLevels a set of triples (a,b,c) representing weak constraint atoms, where a is + * the atom, b the weight and c the level of the respective weak constraint. + */ public void addWeakConstraintsInformation(List> weakConstraintAtomWeightLevels) { - // Register all newly obtained weak constraint atoms for callback at the assignment. for (Triple weakConstraintAtomWeightLevel : weakConstraintAtomWeightLevels) { if (weakConstraintAtomWeightLevel.getMiddle() == 0) { // Skip weak constraints with weight 0 entirely. continue; } - WeakConstraintAtomCallback wcA = new WeakConstraintAtomCallback(weakConstraintAtomWeightLevel.getLeft(), weakConstraintAtomWeightLevel.getMiddle(), weakConstraintAtomWeightLevel.getRight()); + WeakConstraintAtomCallback wcA = new WeakConstraintAtomCallback(this, weakConstraintAtomWeightLevel.getLeft(), weakConstraintAtomWeightLevel.getMiddle(), weakConstraintAtomWeightLevel.getRight()); assignment.registerCallbackOnChange(wcA.atom); assignment.getAtomCallbackManager().recordCallback(wcA.atom, wcA); knownCallbackAtoms.add(wcA.atom); - if (!efficientWeightAtLevelsStore.foundFirstAnswerSet) { - efficientWeightAtLevelsStore.knownAtomCallbacksForFirstAnswerSet.add(wcA); + if (!foundFirstAnswerSet) { + knownAtomCallbacksForFirstAnswerSet.add(wcA); } } } + /** + * Process the the changed truth value of an atom representing a weak constraint. + * + * @param atomCallback the {@link WeakConstraintAtomCallback} whose assigned truth value did change. + */ + public void processCallback(WeakConstraintAtomCallback atomCallback) { + int atom = atomCallback.atom; + int level = atomCallback.level; + int weight = atomCallback.weight; + ThriceTruth lastTruthValue = atomCallback.lastTruthValue; + if (LOGGER.isTraceEnabled()) { + LOGGER.trace("Processing callback for atom {} with weight at level {}@{}, last truth value was {}.", atom, weight, level, lastTruthValue); + LOGGER.trace("Current atom truth value is: {}", assignment.getTruth(atom)); + } + ThriceTruth currentAtomTruth = assignment.getTruth(atom); + if (!foundFirstAnswerSet) { + // Record old truth value and return if no answer set has been found yet. + atomCallback.lastTruthValue = currentAtomTruth; + return; + } + LOGGER.trace("Current truth value is: {}", currentAtomTruth); + if (lastTruthValue == null) { + // Change from unassigned to some truth value. + if (currentAtomTruth == MBT) { + weightAtLevelsManager.increaseCurrentWeight(atomCallback); + } + // Note: for assignment to FALSE or MBT->TRUE, no change needed. + } else { + if (lastTruthValue.toBoolean() && currentAtomTruth == null) { + // Change from TRUE/MBT to unassigned. + weightAtLevelsManager.decreaseCurrentWeight(atomCallback); + } + // Note: for backtracking from TRUE to MBT no change is needed. + if (currentAtomTruth != null && lastTruthValue.toBoolean() && !currentAtomTruth.toBoolean()) { + throw oops("Unexpected case of weak constraint atom directly changing truth value from MBT/TRUE to FALSE encountered. Atom/LastTruth/CurrentTruth: " + atom + "/" + lastTruthValue + "/" + currentAtomTruth); + } + } + atomCallback.lastTruthValue = currentAtomTruth; + } + + /** + * For the first answer-set returns all those {@link WeakConstraintAtomCallback}s that are true. + * @return a list of all {@link WeakConstraintAtomCallback}s whose atom is MBT/TRUE. + * Throws an exception if called after the first answer-set has been found. + */ + public List getTrueWeakConstraintAtomCallbacksOfFirstAnswerSet() { + if (foundFirstAnswerSet) { + throw oops("Requesting true WeakConstraintAtomCallbacks after first answer-set has been computed."); + } + return knownAtomCallbacksForFirstAnswerSet.stream().filter(n -> { + ThriceTruth truth = assignment.getTruth(n.atom); + return truth != null && truth.toBoolean(); + }).collect(Collectors.toList()); + } + /** * Returns whether the current partial interpretation is already worse (or equal) than the best-known answer-set. * @return true if the current partial interpretation has worse (or equal) weight than the best-known answer-set. */ public boolean isCurrentBetterThanBest() { - boolean isCurrentBetterThanBest = efficientWeightAtLevelsStore.isCurrentBetterThanBest(); + boolean isCurrentBetterThanBest = weightAtLevelsManager.isCurrentBetterThanBest(); LOGGER.trace("Is current better than best? {}", isCurrentBetterThanBest); return isCurrentBetterThanBest; } @@ -74,7 +129,8 @@ public boolean isCurrentBetterThanBest() { * Mark the current weight as being the best of all currently known answer-sets. */ public void markCurrentWeightAsBestKnown() { - efficientWeightAtLevelsStore.markCurrentWeightAsBestKnown(); + weightAtLevelsManager.markCurrentWeightAsBestKnown(); + foundFirstAnswerSet = true; } /** @@ -96,344 +152,14 @@ public NoGood generateExcludingNoGood() { @Override public void setChecksEnabled(boolean checksEnabled) { - this.checksEnabled = checksEnabled; + weightAtLevelsManager.setChecksEnabled(checksEnabled); } /** * Returns the current weights and their levels. - * @return a TreeMap mapping levels to weights. Note that values may be non-continuous and negative levels are possible. + * @return a TreeMap mapping levels to weights. */ public TreeMap getCurrentWeightAtLevels() { - TreeMap weightPerLevels = new TreeMap<>(); - for (int i = 0; i < efficientWeightAtLevelsStore.currentWeightAtLevels.size(); i++) { - Integer weightAti = efficientWeightAtLevelsStore.currentWeightAtLevels.get(i); - if (weightAti == 0) { - continue; - } - weightPerLevels.put(efficientWeightAtLevelsStore.listOffsetToLevel(i), weightAti); - } - return weightPerLevels; - } - - public static class EfficientWeightAtLevelsStore { - private final WritableAssignment assignment; - // Stores weights in inverted order: zero is highest violated level, may also grow to represent negative levels. - public ArrayList currentWeightAtLevels; - public ArrayList bestKnownWeightAtLevels; - public boolean foundFirstAnswerSet; - // The highest level for which holds: on all levels below (or equal), the current weights are the same as the weights for the best known. - public int maxOffsetCurrentIsAllEqualBest; - public final ArrayList knownAtomCallbacksForFirstAnswerSet = new ArrayList<>(); - // Stores which actual level corresponds to array position 0 in bestKnownWeightAtLevels, i.e, the highest level that is violated. Value may decrease if better answer-sets are found. - public int maxLevel; - - private boolean isCurrentBetterThanBest; - - public EfficientWeightAtLevelsStore(WritableAssignment assignment) { - this.assignment = assignment; - currentWeightAtLevels = new ArrayList<>(); - bestKnownWeightAtLevels = new ArrayList<>(); - foundFirstAnswerSet = false; - maxOffsetCurrentIsAllEqualBest = -1; - isCurrentBetterThanBest = true; - } - - public boolean isCurrentBetterThanBest() { - return isCurrentBetterThanBest; - } - - public void increaseCurrentWeight(WeakConstraintAtomCallback atom) { - // Record the new weight. - growForLevel(currentWeightAtLevels, atom.level); - int listOffset = levelToListOffset(atom.level); - int newWeight = currentWeightAtLevels.get(listOffset) + atom.weight; - currentWeightAtLevels.set(listOffset, newWeight); - - // Now check whether current is worse than best known. - if (listOffset > maxOffsetCurrentIsAllEqualBest + 1) { - // Weight in some level where differences do not (yet) matter increased, but on lower level current partial assignment is still better than best found answer-set. - // Nothing to do here. - return; - } else if (listOffset <= maxOffsetCurrentIsAllEqualBest) { - // Weight in level with previously equal weights increased, current is now worse than best. - isCurrentBetterThanBest = false; - maxOffsetCurrentIsAllEqualBest = listOffset - 1; - } else if (listOffset == maxOffsetCurrentIsAllEqualBest + 1) { - // Weight increased for the first level where current and best-known are not equal. - moveUpwardsMaxLevelCurrentIsAllEqual(); - recomputeIsCurrentBetterThanBest(); - } else { - throw oops("Increasing weight of current answer reached unforeseen state."); - } - } - - private void decreaseCurrentWeight(WeakConstraintAtomCallback atom) { - // Record the new weight. - int listOffset = levelToListOffset(atom.level); - int newWeight = currentWeightAtLevels.get(listOffset) - atom.weight; - currentWeightAtLevels.set(listOffset, newWeight); - - // Now check whether current is better than best known and adapt maxOffsetCurrentIsAllEqualBest. - if (listOffset <= maxOffsetCurrentIsAllEqualBest) { - // Improved below the point where current is equal to best, so current is better than best now. - isCurrentBetterThanBest = true; - // All weights below the change are equal. - maxOffsetCurrentIsAllEqualBest = listOffset - 1; - } else if (listOffset > maxOffsetCurrentIsAllEqualBest + 1) { - // Decrease in such a high level, that there is at least one level (maxLevel+1) where weights are - // different and whose difference still dominates whether current is better than best known. - return; - } else if (listOffset == maxOffsetCurrentIsAllEqualBest + 1) { - // Decrease might make current better than best known and change level of equals. - moveUpwardsMaxLevelCurrentIsAllEqual(); - recomputeIsCurrentBetterThanBest(); - } else { - throw oops("Decreasing weight of current answer reached unforeseen state."); - } - } - - private void moveUpwardsMaxLevelCurrentIsAllEqual() { - for (int i = max(maxOffsetCurrentIsAllEqualBest, 0); i < currentWeightAtLevels.size(); i++) { - int currentLevelWeight = currentWeightAtLevels.get(i); - int bestLevelWeight = bestKnownWeightAtLevels.size() > i ? bestKnownWeightAtLevels.get(i) : 0; - if (currentLevelWeight != bestLevelWeight) { - // Level is the first where values differ, previous level was maximum one where values are equal. - maxOffsetCurrentIsAllEqualBest = i - 1; - return; - } - } - // Iterated all levels, so weights are the same on all levels. - maxOffsetCurrentIsAllEqualBest = currentWeightAtLevels.size() - 1; - } - - private void recomputeIsCurrentBetterThanBest() { - // The weights at the first level above maxOffsetCurrentIsAllEqualBest dominate whether current is worse than best known. - if (maxOffsetCurrentIsAllEqualBest == currentWeightAtLevels.size() - 1) { - // Current and best known are equal even up to the last level. - isCurrentBetterThanBest = false; - return; - } - int comparisonOffset = maxOffsetCurrentIsAllEqualBest + 1; - int currentLevelWeight = currentWeightAtLevels.get(comparisonOffset); - int bestLevelWeight = bestKnownWeightAtLevels.size() > comparisonOffset ? bestKnownWeightAtLevels.get(comparisonOffset) : 0; - isCurrentBetterThanBest = currentLevelWeight < bestLevelWeight; - } - - private int listOffsetToLevel(int offset) { - return maxLevel - offset; - } - - private void growForLevel(ArrayList listToGrow, int level) { - int listOffset = levelToListOffset(level); - while (listOffset >= listToGrow.size()) { - listToGrow.add(0); - } - } - - public void markCurrentWeightAsBestKnown() { - LOGGER.trace("Marking current answer-set as best known."); - if (!foundFirstAnswerSet) { - initializeFirstWeightsAtLevel(); - } else { - // Remove unnecessary high levels (with weights 0). - int trim = 0; - for (int i = 0; i < currentWeightAtLevels.size(); i++) { - if (currentWeightAtLevels.get(i) != 0) { - trim = i; - break; - } - } - if (trim > 0) { - bestKnownWeightAtLevels = new ArrayList<>(currentWeightAtLevels.subList(trim, currentWeightAtLevels.size())); - currentWeightAtLevels = new ArrayList<>(bestKnownWeightAtLevels); - } else { - bestKnownWeightAtLevels = new ArrayList<>(currentWeightAtLevels); - } - maxLevel = maxLevel - trim; - } - maxOffsetCurrentIsAllEqualBest = bestKnownWeightAtLevels.size() - 1; - LOGGER.trace("Max offset current is all equal to best known is: {}", maxOffsetCurrentIsAllEqualBest); - isCurrentBetterThanBest = false; - } - - private void initializeFirstWeightsAtLevel() { - // First answer set has been found, take its maximum level as zero-level. - LOGGER.trace("Initializing for first answer-set."); - foundFirstAnswerSet = true; - HashMap sumWeightsAtLevel = new HashMap<>(); - int highestLevel = 0; - int lowestLevel = 0; - for (WeakConstraintAtomCallback atomCallback : knownAtomCallbacksForFirstAnswerSet) { - ThriceTruth truth = assignment.getTruth(atomCallback.atom); - if (truth == null || !truth.toBoolean()) { - // Skip weak-constraint atoms being unassigned or false. - continue; - } - int level = atomCallback.level; - // Record if level is highest or lowest so far. - if (highestLevel < level) { - highestLevel = level; - } - if (lowestLevel > level) { - lowestLevel = level; - } - // Update weight information. - if (sumWeightsAtLevel.get(level) == null) { - sumWeightsAtLevel.put(level, atomCallback.weight); - } else { - int newWeight = sumWeightsAtLevel.get(level) + atomCallback.weight; - sumWeightsAtLevel.put(level, newWeight); - } - } - maxLevel = highestLevel; - LOGGER.trace("Maximum recorded level (0-offset) is: {} ({})", maxLevel, listOffsetToLevel(0)); - bestKnownWeightAtLevels = new ArrayList<>(); - growForLevel(bestKnownWeightAtLevels, lowestLevel); - for (Map.Entry levelWeight : sumWeightsAtLevel.entrySet()) { - bestKnownWeightAtLevels.set(maxLevel - levelWeight.getKey(), levelWeight.getValue()); - } - currentWeightAtLevels = new ArrayList<>(bestKnownWeightAtLevels); - LOGGER.trace("Initially, current/best known weights are: {}", bestKnownWeightAtLevels); - } - - private int levelToListOffset(int level) { - int levelInList = maxLevel - level; - if (levelInList < 0) { - throw oops("Level optimisation in WeakConstraintsManager is negative."); - } - return levelInList; - } - - private void runChecks() { - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("Running checks."); - LOGGER.trace("Maximum recorded level (0-offset) is: {} ({})", maxLevel, listOffsetToLevel(0)); - LOGGER.trace("Currently best known weights are: {}", bestKnownWeightAtLevels); - LOGGER.trace("Current weights are: {}", currentWeightAtLevels); - LOGGER.trace("Max offset current is all equal to best known is: {}", maxOffsetCurrentIsAllEqualBest); - LOGGER.trace("Is current better than best-known: {}", isCurrentBetterThanBest()); - - } - if (!foundFirstAnswerSet && !bestKnownWeightAtLevels.isEmpty()) { - throw oops("WeakConstraintManager has best known answer set information but no answer set was found yet."); - } - if (foundFirstAnswerSet && bestKnownWeightAtLevels.get(0) == 0) { - throw oops("WeakConstraintManager has best-known answer set with zero-weights at highest level."); - } - // Check whether isCurrentBetterThanBest flag is consistent. - if (isCurrentBetterThanBest() != checkIsCurrentBetterThanBest()) { - throw oops("WeakConstraintManager detected valuation of current assignment is inconsistent with state flag."); - } - checkLevelBar(); - LOGGER.trace("Checks done."); - } - - private void checkLevelBar() { - int highestLevelWhereLowerLevelsAreEqual = -1; - for (int i = 0; i < currentWeightAtLevels.size(); i++) { - int levelValue = i < bestKnownWeightAtLevels.size() ? bestKnownWeightAtLevels.get(i) : 0; - if (currentWeightAtLevels.get(i) != levelValue) { - break; - } - highestLevelWhereLowerLevelsAreEqual = i; - } - // The level bar should be at highestLevelWhereLowerLevelsAreEqual+1. - if (maxOffsetCurrentIsAllEqualBest != highestLevelWhereLowerLevelsAreEqual) { - throw oops("WeakConstraintManager detected level bar at wrong level, it is " + maxOffsetCurrentIsAllEqualBest - + "and should be " + highestLevelWhereLowerLevelsAreEqual); - } - } - - private boolean checkIsCurrentBetterThanBest() { - if (!foundFirstAnswerSet) { - // If no answer set has been found, the current is always better than best known. - return true; - } - for (int i = 0; i < currentWeightAtLevels.size(); i++) { - if (i >= bestKnownWeightAtLevels.size()) { - // Current has violations at level not violated by best. - return false; - } - if (currentWeightAtLevels.get(i) < bestKnownWeightAtLevels.get(i)) { - return true; - } - if (currentWeightAtLevels.get(i) > bestKnownWeightAtLevels.get(i)) { - return false; - } - } - // All levels have same weight for current as for best known, so at this point usually current is not better than best. - return false; - } - } - - class WeakConstraintAtomCallback implements AtomCallbackManager.AtomCallback { - private final int atom; - private final int weight; - private final int level; - private ThriceTruth lastTruthValue; - - WeakConstraintAtomCallback(int atom, int weight, int level) { - this.atom = atom; - this.weight = weight; - this.level = level; - } - - @Override - public void processCallback() { - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("Processing callback for atom {} with weight at level {}@{}, last truth value was {}.", atom, weight, level, lastTruthValue); - LOGGER.trace("Current atom truth value is: {}", assignment.getTruth(atom)); - } - if (checksEnabled) { - efficientWeightAtLevelsStore.runChecks(); - } - ThriceTruth currentAtomTruth = assignment.getTruth(atom); - if (!efficientWeightAtLevelsStore.foundFirstAnswerSet) { - // Record old truth value and return if no answer set has been found yet. - lastTruthValue = currentAtomTruth; - return; - } - LOGGER.trace("Current truth value is: {}", currentAtomTruth); - if (lastTruthValue == null) { - // Change from unassigned to some truth value. - if (currentAtomTruth == MBT) { - efficientWeightAtLevelsStore.increaseCurrentWeight(this); - } - // Note: for assignment to FALSE or MBT->TRUE, no change needed. - lastTruthValue = currentAtomTruth; - } else { - if ((lastTruthValue == MBT || lastTruthValue == TRUE) && currentAtomTruth == null) { - // Change from TRUE/MBT to unassigned. - efficientWeightAtLevelsStore.decreaseCurrentWeight(this); - } - // Note: for backtracking from TRUE to MBT no change is needed. - lastTruthValue = currentAtomTruth; - if (currentAtomTruth != null && lastTruthValue != TRUE && currentAtomTruth != MBT) { - throw oops("Unexpected case of weak constraint atom changing truth value encountered. Atom/LastTruth/CurrentTruth: " + atom + "/" + lastTruthValue + "/" + currentAtomTruth); - } - } - if (checksEnabled) { - efficientWeightAtLevelsStore.runChecks(); - } - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - WeakConstraintAtomCallback that = (WeakConstraintAtomCallback) o; - return atom == that.atom && - weight == that.weight && - level == that.level; - } - - @Override - public int hashCode() { - return Objects.hash(atom, weight, level); - } + return weightAtLevelsManager.getCurrentWeightAtLevels(); } } diff --git a/src/main/java/at/ac/tuwien/kr/alpha/solver/optimization/WeightAtLevelsManager.java b/src/main/java/at/ac/tuwien/kr/alpha/solver/optimization/WeightAtLevelsManager.java new file mode 100644 index 000000000..63c762e56 --- /dev/null +++ b/src/main/java/at/ac/tuwien/kr/alpha/solver/optimization/WeightAtLevelsManager.java @@ -0,0 +1,307 @@ +package at.ac.tuwien.kr.alpha.solver.optimization; + +import at.ac.tuwien.kr.alpha.solver.Checkable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; +import java.util.TreeMap; + +import static at.ac.tuwien.kr.alpha.Util.oops; +import static java.lang.Math.max; + +/** + * Manages two sets of weights-at-levels (basically the valuation of two potential answer-sets). One set is the + * valuation of the best-known answer-set at the time, the other is the (partial) valuation of the currently explored + * (partial) answer-set. + * + * Copyright (c) 2021, the Alpha Team. + */ +public class WeightAtLevelsManager implements Checkable { + private static final Logger LOGGER = LoggerFactory.getLogger(WeightAtLevelsManager.class); + private boolean checksEnabled; + + private final WeakConstraintsManager weakConstraintsManager; + // Stores weights in inverted order: zero is highest violated level, may also grow to represent negative levels. + private ArrayList currentWeightAtLevels; + private ArrayList bestKnownWeightAtLevels; + // The highest level for which holds: on all levels below (or equal), the current weights are the same as the weights for the best known. + private int maxOffsetCurrentIsAllEqualBest; + // Stores which actual level corresponds to array position 0 in bestKnownWeightAtLevels, i.e, the highest level that is violated. Value may decrease if better answer-sets are found. + private int maxLevel; + private boolean isCurrentBetterThanBest; + private boolean hasBestKnown; + + public WeightAtLevelsManager(WeakConstraintsManager weakConstraintsManager) { + this.weakConstraintsManager = weakConstraintsManager; + currentWeightAtLevels = new ArrayList<>(); + bestKnownWeightAtLevels = new ArrayList<>(); + maxOffsetCurrentIsAllEqualBest = -1; + isCurrentBetterThanBest = true; + } + + @Override + public void setChecksEnabled(boolean checksEnabled) { + this.checksEnabled = checksEnabled; + } + + /** + * Returns whether the current valuation is (still) better than the best-known one. + * @return true iff the current weights-at-levels are better than the best-known. + */ + public boolean isCurrentBetterThanBest() { + if (checksEnabled) { + runChecks(); + } + return isCurrentBetterThanBest; + } + + /** + * Returns the current weights and their levels. + * @return a TreeMap mapping levels to weights. Note that values may be non-continuous and negative levels are possible. + */ + public TreeMap getCurrentWeightAtLevels() { + TreeMap weightPerLevels = new TreeMap<>(); + for (int i = 0; i < currentWeightAtLevels.size(); i++) { + Integer weightAti = currentWeightAtLevels.get(i); + if (weightAti == 0) { + continue; + } + weightPerLevels.put(listOffsetToLevel(i), weightAti); + } + return weightPerLevels; + } + + public void increaseCurrentWeight(WeakConstraintAtomCallback atom) { + if (checksEnabled) { + runChecks(); + } + // Record the new weight. + growForLevel(currentWeightAtLevels, atom.level); + int listOffset = levelToListOffset(atom.level); + int newWeight = currentWeightAtLevels.get(listOffset) + atom.weight; + currentWeightAtLevels.set(listOffset, newWeight); + + // Now check whether current is worse than best known. + if (listOffset > maxOffsetCurrentIsAllEqualBest + 1) { + // Weight in some level where differences do not (yet) matter increased, but on lower level current partial assignment is still better than best found answer-set. + // Nothing to do here. + return; + } else if (listOffset <= maxOffsetCurrentIsAllEqualBest) { + // Weight in level with previously equal weights increased, current is now worse than best. + isCurrentBetterThanBest = false; + maxOffsetCurrentIsAllEqualBest = listOffset - 1; + } else if (listOffset == maxOffsetCurrentIsAllEqualBest + 1) { + // Weight increased for the first level where current and best-known are not equal. + moveUpwardsMaxLevelCurrentIsAllEqual(); + recomputeIsCurrentBetterThanBest(); + } else { + throw oops("Increasing weight of current answer reached unforeseen state."); + } + } + + public void decreaseCurrentWeight(WeakConstraintAtomCallback atom) { + if (checksEnabled) { + runChecks(); + } + // Record the new weight. + int listOffset = levelToListOffset(atom.level); + int newWeight = currentWeightAtLevels.get(listOffset) - atom.weight; + currentWeightAtLevels.set(listOffset, newWeight); + + // Now check whether current is better than best known and adapt maxOffsetCurrentIsAllEqualBest. + if (listOffset <= maxOffsetCurrentIsAllEqualBest) { + // Improved below the point where current is equal to best, so current is better than best now. + isCurrentBetterThanBest = true; + // All weights below the change are equal. + maxOffsetCurrentIsAllEqualBest = listOffset - 1; + } else if (listOffset > maxOffsetCurrentIsAllEqualBest + 1) { + // Decrease in such a high level, that there is at least one level (maxLevel+1) where weights are + // different and whose difference still dominates whether current is better than best known. + return; + } else if (listOffset == maxOffsetCurrentIsAllEqualBest + 1) { + // Decrease might make current better than best known and change level of equals. + moveUpwardsMaxLevelCurrentIsAllEqual(); + recomputeIsCurrentBetterThanBest(); + } else { + throw oops("Decreasing weight of current answer reached unforeseen state."); + } + } + + private void moveUpwardsMaxLevelCurrentIsAllEqual() { + for (int i = max(maxOffsetCurrentIsAllEqualBest, 0); i < currentWeightAtLevels.size(); i++) { + int currentLevelWeight = currentWeightAtLevels.get(i); + int bestLevelWeight = bestKnownWeightAtLevels.size() > i ? bestKnownWeightAtLevels.get(i) : 0; + if (currentLevelWeight != bestLevelWeight) { + // Level is the first where values differ, previous level was maximum one where values are equal. + maxOffsetCurrentIsAllEqualBest = i - 1; + return; + } + } + // Iterated all levels, so weights are the same on all levels. + maxOffsetCurrentIsAllEqualBest = currentWeightAtLevels.size() - 1; + } + + private void recomputeIsCurrentBetterThanBest() { + // The weights at the first level above maxOffsetCurrentIsAllEqualBest dominate whether current is worse than best known. + if (maxOffsetCurrentIsAllEqualBest == currentWeightAtLevels.size() - 1) { + // Current and best known are equal even up to the last level. + isCurrentBetterThanBest = false; + return; + } + int comparisonOffset = maxOffsetCurrentIsAllEqualBest + 1; + int currentLevelWeight = currentWeightAtLevels.get(comparisonOffset); + int bestLevelWeight = bestKnownWeightAtLevels.size() > comparisonOffset ? bestKnownWeightAtLevels.get(comparisonOffset) : 0; + isCurrentBetterThanBest = currentLevelWeight < bestLevelWeight; + } + + private int listOffsetToLevel(int offset) { + return maxLevel - offset; + } + + private void growForLevel(ArrayList listToGrow, int level) { + int listOffset = levelToListOffset(level); + while (listOffset >= listToGrow.size()) { + listToGrow.add(0); + } + } + + /** + * Marks the current valuation/weights-at-levels as being the best-known one. + */ + public void markCurrentWeightAsBestKnown() { + LOGGER.trace("Marking current answer-set as best known."); + if (!hasBestKnown) { + initializeFirstWeightsAtLevel(); + } else { + trimZeroWeightLevels(); + bestKnownWeightAtLevels = new ArrayList<>(currentWeightAtLevels); + } + maxOffsetCurrentIsAllEqualBest = bestKnownWeightAtLevels.size() - 1; + LOGGER.trace("Max offset current is all equal to best known is: {}", maxOffsetCurrentIsAllEqualBest); + isCurrentBetterThanBest = false; + } + + /** + * Removes unnecessary high levels (with weights 0) from current valuation. + */ + private void trimZeroWeightLevels() { + int trim = 0; + for (int i = 0; i < currentWeightAtLevels.size(); i++) { + if (currentWeightAtLevels.get(i) != 0) { + trim = i; + break; + } + } + if (trim > 0) { + currentWeightAtLevels = new ArrayList<>(currentWeightAtLevels.subList(trim, currentWeightAtLevels.size())); + maxLevel -= trim; + } + } + + private void initializeFirstWeightsAtLevel() { + // First answer set has been found, take its maximum level as zero-level. + LOGGER.trace("Initializing for first answer-set."); + HashMap sumWeightsAtLevel = new HashMap<>(); + int highestLevel = 0; + int lowestLevel = 0; + for (WeakConstraintAtomCallback atomCallback : weakConstraintsManager.getTrueWeakConstraintAtomCallbacksOfFirstAnswerSet()) { + int level = atomCallback.level; + // Record if level is highest or lowest so far. + if (highestLevel < level) { + highestLevel = level; + } + if (lowestLevel > level) { + lowestLevel = level; + } + // Update weight information. + if (sumWeightsAtLevel.get(level) == null) { + sumWeightsAtLevel.put(level, atomCallback.weight); + } else { + int newWeight = sumWeightsAtLevel.get(level) + atomCallback.weight; + sumWeightsAtLevel.put(level, newWeight); + } + } + maxLevel = highestLevel; + LOGGER.trace("Maximum recorded level (0-offset) is: {} ({})", maxLevel, listOffsetToLevel(0)); + bestKnownWeightAtLevels = new ArrayList<>(); + growForLevel(bestKnownWeightAtLevels, lowestLevel); + for (Map.Entry levelWeight : sumWeightsAtLevel.entrySet()) { + bestKnownWeightAtLevels.set(maxLevel - levelWeight.getKey(), levelWeight.getValue()); + } + currentWeightAtLevels = new ArrayList<>(bestKnownWeightAtLevels); + hasBestKnown = true; + LOGGER.trace("Initially, current/best known weights are: {}", bestKnownWeightAtLevels); + } + + private int levelToListOffset(int level) { + int levelInList = maxLevel - level; + if (levelInList < 0) { + throw oops("Level optimisation in WeakConstraintsManager is negative."); + } + return levelInList; + } + + private void runChecks() { + if (LOGGER.isTraceEnabled()) { + LOGGER.trace("Running checks."); + LOGGER.trace("Maximum recorded level (0-offset) is: {} ({})", maxLevel, listOffsetToLevel(0)); + LOGGER.trace("Currently best known weights are: {}", bestKnownWeightAtLevels); + LOGGER.trace("Current weights are: {}", currentWeightAtLevels); + LOGGER.trace("Max offset current is all equal to best known is: {}", maxOffsetCurrentIsAllEqualBest); + LOGGER.trace("Is current better than best-known: {}", isCurrentBetterThanBest); + + } + if (!hasBestKnown && !bestKnownWeightAtLevels.isEmpty()) { + throw oops("WeakConstraintManager has best known answer set information but no answer set was found yet."); + } + if (hasBestKnown && bestKnownWeightAtLevels.get(0) == 0) { + throw oops("WeakConstraintManager has best-known answer set with zero-weights at highest level."); + } + // Check whether isCurrentBetterThanBest flag is consistent. + if (isCurrentBetterThanBest != checkIsCurrentBetterThanBest()) { + throw oops("WeakConstraintManager detected valuation of current assignment is inconsistent with state flag."); + } + checkLevelBar(); + LOGGER.trace("Checks done."); + } + + private void checkLevelBar() { + int highestLevelWhereLowerLevelsAreEqual = -1; + for (int i = 0; i < currentWeightAtLevels.size(); i++) { + int levelValue = i < bestKnownWeightAtLevels.size() ? bestKnownWeightAtLevels.get(i) : 0; + if (currentWeightAtLevels.get(i) != levelValue) { + break; + } + highestLevelWhereLowerLevelsAreEqual = i; + } + // The level bar should be at highestLevelWhereLowerLevelsAreEqual+1. + if (maxOffsetCurrentIsAllEqualBest != highestLevelWhereLowerLevelsAreEqual) { + throw oops("WeakConstraintManager detected level bar at wrong level, it is " + maxOffsetCurrentIsAllEqualBest + + "and should be " + highestLevelWhereLowerLevelsAreEqual); + } + } + + private boolean checkIsCurrentBetterThanBest() { + if (!hasBestKnown) { + // If no answer set has been found, the current is always better than best known. + return true; + } + for (int i = 0; i < currentWeightAtLevels.size(); i++) { + if (i >= bestKnownWeightAtLevels.size()) { + // Current has violations at level not violated by best. + return false; + } + if (currentWeightAtLevels.get(i) < bestKnownWeightAtLevels.get(i)) { + return true; + } + if (currentWeightAtLevels.get(i) > bestKnownWeightAtLevels.get(i)) { + return false; + } + } + // All levels have same weight for current as for best known, so at this point usually current is not better than best. + return false; + } +} From b97dfd59558301910333dde3e0d42c65fee42759 Mon Sep 17 00:00:00 2001 From: Antonius Weinzierl Date: Wed, 11 Aug 2021 08:19:50 +0200 Subject: [PATCH 14/27] Bugfix: produce weak constraint association only once per level+terms. --- .../kr/alpha/grounder/NoGoodGenerator.java | 31 ++++++++++++------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/src/main/java/at/ac/tuwien/kr/alpha/grounder/NoGoodGenerator.java b/src/main/java/at/ac/tuwien/kr/alpha/grounder/NoGoodGenerator.java index 8269fa762..9ba3723b9 100644 --- a/src/main/java/at/ac/tuwien/kr/alpha/grounder/NoGoodGenerator.java +++ b/src/main/java/at/ac/tuwien/kr/alpha/grounder/NoGoodGenerator.java @@ -40,6 +40,7 @@ import at.ac.tuwien.kr.alpha.grounder.atoms.WeakConstraintAtom; import java.util.ArrayList; +import java.util.Collections; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; @@ -96,21 +97,14 @@ List generateNoGoodsFromGroundSubstitution(final InternalRule nonGroundR return singletonList(NoGood.fromConstraint(posLiterals, negLiterals)); } - final List result = new ArrayList<>(); - final Atom groundHeadAtom = nonGroundRule.getHeadAtom().substitute(substitution); - final int headId = atomStore.putIfAbsent(groundHeadAtom); if (groundHeadAtom instanceof WeakConstraintAtom) { - WeakConstraintAtom weakConstraintAtom = (WeakConstraintAtom) groundHeadAtom; - // Treat weak constraints: only generate nogood for the if-direction (body satisfied causes head to be true). - NoGood wcRule = NoGood.fromBodyInternal(posLiterals, negLiterals, atomToLiteral(headId)); - result.add(wcRule); - - // Record weak constraint association. - weakConstraintRecorder.addWeakConstraint(headId, weakConstraintAtom.getWeight(), weakConstraintAtom.getLevel()); - return result; + return generateWeakConstraintNogoods(posLiterals, negLiterals, groundHeadAtom); } + + final List result = new ArrayList<>(); + final int headId = atomStore.putIfAbsent(groundHeadAtom); // Prepare atom representing the rule body. final RuleAtom bodyAtom = new RuleAtom(nonGroundRule, substitution); @@ -152,6 +146,21 @@ List generateNoGoodsFromGroundSubstitution(final InternalRule nonGroundR return result; } + private List generateWeakConstraintNogoods(List posLiterals, List negLiterals, Atom groundHeadAtom) { + int headId; + if (!atomStore.contains(groundHeadAtom)) { + headId = atomStore.putIfAbsent(groundHeadAtom); + // Record weak constraint association only the first time it is encountered. + WeakConstraintAtom weakConstraintAtom = (WeakConstraintAtom) groundHeadAtom; + weakConstraintRecorder.addWeakConstraint(headId, weakConstraintAtom.getWeight(), weakConstraintAtom.getLevel()); + } else { + headId = atomStore.get(groundHeadAtom); + } + // Treat weak constraints: generate NoGood for the if-direction (body satisfied causes head to be true). + NoGood wcRule = NoGood.fromBodyInternal(posLiterals, negLiterals, atomToLiteral(headId)); + return Collections.singletonList(wcRule); + } + List collectNegLiterals(final InternalRule nonGroundRule, final Substitution substitution) { final List bodyLiteralsNegative = new ArrayList<>(); for (Literal lit : nonGroundRule.getNegativeBody()) { From 0f76369284bd73ef3daaa6532a1d8ca902eaa259 Mon Sep 17 00:00:00 2001 From: Antonius Weinzierl Date: Thu, 12 Aug 2021 11:35:37 +0200 Subject: [PATCH 15/27] Bugfix: update weak constraints information at the right time (after growing and before propagating). --- .../java/at/ac/tuwien/kr/alpha/solver/DefaultSolver.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/at/ac/tuwien/kr/alpha/solver/DefaultSolver.java b/src/main/java/at/ac/tuwien/kr/alpha/solver/DefaultSolver.java index bea5457d4..c816088d2 100644 --- a/src/main/java/at/ac/tuwien/kr/alpha/solver/DefaultSolver.java +++ b/src/main/java/at/ac/tuwien/kr/alpha/solver/DefaultSolver.java @@ -142,9 +142,6 @@ protected boolean tryAdvance(Consumer action) { logStats(); return false; } - if (enableBranchAndBoundOptimization) { - weakConstraintsManager.addWeakConstraintsInformation(grounder.getWeakConstraintInformation()); - } initialize = false; } else if (assignment.getDecisionLevel() == 0) { logStats(); @@ -222,7 +219,6 @@ protected boolean tryAdvance(Consumer action) { logStats(); return false; } - weakConstraintsManager.addWeakConstraintsInformation(grounder.getWeakConstraintInformation()); } else if (choose()) { LOGGER.debug("Did choice."); didChange = true; @@ -495,6 +491,10 @@ private boolean ingest(Map obtained) { branchingHeuristic.growForMaxAtomId(maxAtomId); branchingHeuristic.newNoGoods(obtained.values()); + if (enableBranchAndBoundOptimization) { + weakConstraintsManager.addWeakConstraintsInformation(grounder.getWeakConstraintInformation()); + } + LinkedList> noGoodsToAdd = new LinkedList<>(obtained.entrySet()); Map.Entry entry; while ((entry = noGoodsToAdd.poll()) != null) { From c8351fb571590ce639c3536b0d2594f6463e5565 Mon Sep 17 00:00:00 2001 From: Antonius Weinzierl Date: Thu, 12 Aug 2021 13:52:35 +0200 Subject: [PATCH 16/27] Refactor WeightAtLevelsManager, WeakConstraintsManager, and fix several bugs. Also adding more tests. - WeakConstraintAtomCallback add toString(). - WeightAtLevelsManager only deals with weights and levels, and no longer needs to know about weak constraints. - WeakConstraintsManager keeps track of true atoms above level of WeightAtLevelsManager and keeps weak constraints away from WeightAtLevelsManager. - WeightAtLevelsManagerTest added. - WeakConstraintsTests comes with significantly more tests. --- .../WeakConstraintAtomCallback.java | 5 + .../optimization/WeakConstraintsManager.java | 99 ++++++++++--- .../optimization/WeightAtLevelsManager.java | 139 ++++++++++-------- .../kr/alpha/solver/WeakConstraintsTests.java | 65 ++++++++ .../WeightAtLevelsManagerTest.java | 77 ++++++++++ 5 files changed, 307 insertions(+), 78 deletions(-) create mode 100644 src/test/java/at/ac/tuwien/kr/alpha/solver/optimization/WeightAtLevelsManagerTest.java diff --git a/src/main/java/at/ac/tuwien/kr/alpha/solver/optimization/WeakConstraintAtomCallback.java b/src/main/java/at/ac/tuwien/kr/alpha/solver/optimization/WeakConstraintAtomCallback.java index 709cac408..41e1d75eb 100644 --- a/src/main/java/at/ac/tuwien/kr/alpha/solver/optimization/WeakConstraintAtomCallback.java +++ b/src/main/java/at/ac/tuwien/kr/alpha/solver/optimization/WeakConstraintAtomCallback.java @@ -47,4 +47,9 @@ public boolean equals(Object o) { public int hashCode() { return Objects.hash(atom, weight, level); } + + @Override + public String toString() { + return atom + "[" + weight + "@" + level + "]=" + lastTruthValue; + } } diff --git a/src/main/java/at/ac/tuwien/kr/alpha/solver/optimization/WeakConstraintsManager.java b/src/main/java/at/ac/tuwien/kr/alpha/solver/optimization/WeakConstraintsManager.java index b801da3da..6e3d58232 100644 --- a/src/main/java/at/ac/tuwien/kr/alpha/solver/optimization/WeakConstraintsManager.java +++ b/src/main/java/at/ac/tuwien/kr/alpha/solver/optimization/WeakConstraintsManager.java @@ -10,6 +10,7 @@ import java.util.ArrayList; import java.util.Collections; +import java.util.HashSet; import java.util.List; import java.util.TreeMap; import java.util.stream.Collectors; @@ -32,10 +33,11 @@ public class WeakConstraintsManager implements Checkable { private final ArrayList knownCallbackAtoms; private final ArrayList knownAtomCallbacksForFirstAnswerSet = new ArrayList<>(); private boolean foundFirstAnswerSet; + private final HashSet aboveMaxLevelTrueAtoms = new HashSet<>(); public WeakConstraintsManager(WritableAssignment assignment) { this.assignment = assignment; - this.weightAtLevelsManager = new WeightAtLevelsManager(this); + this.weightAtLevelsManager = new WeightAtLevelsManager(); this.knownCallbackAtoms = new ArrayList<>(); } @@ -54,6 +56,9 @@ public void addWeakConstraintsInformation(List assignment.registerCallbackOnChange(wcA.atom); assignment.getAtomCallbackManager().recordCallback(wcA.atom, wcA); knownCallbackAtoms.add(wcA.atom); + if (assignment.getTruth(wcA.atom) != null) { + throw oops("Adding weak constraints information and atom callback already has a truth value assigned."); + } if (!foundFirstAnswerSet) { knownAtomCallbacksForFirstAnswerSet.add(wcA); } @@ -78,41 +83,46 @@ public void processCallback(WeakConstraintAtomCallback atomCallback) { if (!foundFirstAnswerSet) { // Record old truth value and return if no answer set has been found yet. atomCallback.lastTruthValue = currentAtomTruth; + LOGGER.trace("End processing callback as no first answer set has been found."); return; } LOGGER.trace("Current truth value is: {}", currentAtomTruth); + atomCallback.lastTruthValue = currentAtomTruth; if (lastTruthValue == null) { // Change from unassigned to some truth value. if (currentAtomTruth == MBT) { - weightAtLevelsManager.increaseCurrentWeight(atomCallback); + increaseWeight(atomCallback); } // Note: for assignment to FALSE or MBT->TRUE, no change needed. } else { + // Decrease if change from TRUE/MBT to unassigned. if (lastTruthValue.toBoolean() && currentAtomTruth == null) { - // Change from TRUE/MBT to unassigned. - weightAtLevelsManager.decreaseCurrentWeight(atomCallback); + decreaseWeight(atomCallback); } // Note: for backtracking from TRUE to MBT no change is needed. if (currentAtomTruth != null && lastTruthValue.toBoolean() && !currentAtomTruth.toBoolean()) { throw oops("Unexpected case of weak constraint atom directly changing truth value from MBT/TRUE to FALSE encountered. Atom/LastTruth/CurrentTruth: " + atom + "/" + lastTruthValue + "/" + currentAtomTruth); } } - atomCallback.lastTruthValue = currentAtomTruth; + LOGGER.trace("End Processing callback for atom {} with weight at level {}@{}, last truth value was {}, current is {}.", atom, weight, level, lastTruthValue, currentAtomTruth); } - /** - * For the first answer-set returns all those {@link WeakConstraintAtomCallback}s that are true. - * @return a list of all {@link WeakConstraintAtomCallback}s whose atom is MBT/TRUE. - * Throws an exception if called after the first answer-set has been found. - */ - public List getTrueWeakConstraintAtomCallbacksOfFirstAnswerSet() { - if (foundFirstAnswerSet) { - throw oops("Requesting true WeakConstraintAtomCallbacks after first answer-set has been computed."); + private void increaseWeight(WeakConstraintAtomCallback atomCallback) { + if (weightAtLevelsManager.getMaxLevel() < atomCallback.level) { + LOGGER.info("Adding higher level than possible with weightAtLevelsManager: level={} and maxLevel={}, callback is: {}.", atomCallback.level, weightAtLevelsManager.getMaxLevel(), atomCallback); + aboveMaxLevelTrueAtoms.add(atomCallback); + } else { + weightAtLevelsManager.increaseCurrentWeight(atomCallback.level, atomCallback.weight); + } + } + + private void decreaseWeight(WeakConstraintAtomCallback atomCallback) { + if (weightAtLevelsManager.getMaxLevel() < atomCallback.level) { + LOGGER.info("Removing higher level than possible with weightAtLevelsManager: level={} and maxLevel={}, callback is: {}.", atomCallback.level, weightAtLevelsManager.getMaxLevel(), atomCallback); + aboveMaxLevelTrueAtoms.remove(atomCallback); + } else { + weightAtLevelsManager.decreaseCurrentWeight(atomCallback.level, atomCallback.weight); } - return knownAtomCallbacksForFirstAnswerSet.stream().filter(n -> { - ThriceTruth truth = assignment.getTruth(n.atom); - return truth != null && truth.toBoolean(); - }).collect(Collectors.toList()); } /** @@ -120,7 +130,10 @@ public List getTrueWeakConstraintAtomCallbacksOfFirs * @return true if the current partial interpretation has worse (or equal) weight than the best-known answer-set. */ public boolean isCurrentBetterThanBest() { - boolean isCurrentBetterThanBest = weightAtLevelsManager.isCurrentBetterThanBest(); + if (!foundFirstAnswerSet) { + return true; + } + boolean isCurrentBetterThanBest = aboveMaxLevelTrueAtoms.isEmpty() && weightAtLevelsManager.isCurrentBetterThanBest(); LOGGER.trace("Is current better than best? {}", isCurrentBetterThanBest); return isCurrentBetterThanBest; } @@ -129,8 +142,39 @@ public boolean isCurrentBetterThanBest() { * Mark the current weight as being the best of all currently known answer-sets. */ public void markCurrentWeightAsBestKnown() { + if (!foundFirstAnswerSet) { + initializeFirstWeightsAtLevel(); + foundFirstAnswerSet = true; + } else { + if (!isCurrentBetterThanBest()) { + throw oops("WeakConstraintsManager instructed to mark current valuation as best-known, but there is a better one already."); + } + } + if (!aboveMaxLevelTrueAtoms.isEmpty()) { + throw oops("WeakConstraintsManager has aboveMaxLevelTrueAtoms but is called to markCurrentWeightsAsBestKnown."); + } weightAtLevelsManager.markCurrentWeightAsBestKnown(); - foundFirstAnswerSet = true; + } + + private void initializeFirstWeightsAtLevel() { + // First answer set has been found, find its maximum level and inform WeightAtLevelsManager. + LOGGER.trace("Initializing for first answer-set."); + int highestLevel = 0; + List trueWeakConstraintAtomCallbacks = knownAtomCallbacksForFirstAnswerSet + .stream() + .filter(wca -> assignment.getTruth(wca.atom).toBoolean()) + .collect(Collectors.toList()); + for (WeakConstraintAtomCallback weakConstraintAtomCallback : trueWeakConstraintAtomCallbacks) { + int level = weakConstraintAtomCallback.level; + if (highestLevel < level) { + highestLevel = level; + } + } + weightAtLevelsManager.setMaxLevel(highestLevel); + // Now inform WeightAtLevelsManager about all true weak constraint atoms. + for (WeakConstraintAtomCallback weakConstraintAtomCallback : trueWeakConstraintAtomCallbacks) { + weightAtLevelsManager.increaseCurrentWeight(weakConstraintAtomCallback.level, weakConstraintAtomCallback.weight); + } } /** @@ -138,6 +182,7 @@ public void markCurrentWeightAsBestKnown() { * @return a NoGood which excludes the exact weight of the current assignment. */ public NoGood generateExcludingNoGood() { + LOGGER.trace("Generating excluding NoGood."); // Collect all currently true/mbt weights and put their respective literals in one nogood. // Note: alternative to searching all known callback atoms would be maintaining a list of true ones, but this is probably less efficient. ArrayList trueCallbackAtoms = new ArrayList<>(); @@ -147,6 +192,7 @@ public NoGood generateExcludingNoGood() { trueCallbackAtoms.add(atomToLiteral(callbackAtom)); } } + LOGGER.trace("True weak constraint representing atoms are: {}", trueCallbackAtoms); return NoGood.fromConstraint(trueCallbackAtoms, Collections.emptyList()); } @@ -160,6 +206,19 @@ public void setChecksEnabled(boolean checksEnabled) { * @return a TreeMap mapping levels to weights. */ public TreeMap getCurrentWeightAtLevels() { - return weightAtLevelsManager.getCurrentWeightAtLevels(); + LOGGER.trace("Reporting current weight at levels."); + TreeMap weightAtLevels = weightAtLevelsManager.getCurrentWeightAtLevels(); + if (aboveMaxLevelTrueAtoms.isEmpty()) { + LOGGER.trace("Current weight at levels: {}", weightAtLevels); + return weightAtLevels; + } + // Add weights above the maximum level stored in the WeightAtLevelsManager. + for (WeakConstraintAtomCallback aboveMaxLevelTrueAtom : aboveMaxLevelTrueAtoms) { + Integer weightAtLevel = weightAtLevels.get(aboveMaxLevelTrueAtom.level); + weightAtLevel = weightAtLevel == null ? 0 : weightAtLevel; + weightAtLevels.putIfAbsent(aboveMaxLevelTrueAtom.level, weightAtLevel + aboveMaxLevelTrueAtom.weight); + } + LOGGER.trace("Current weight at levels: {}", weightAtLevels); + return weightAtLevels; } } diff --git a/src/main/java/at/ac/tuwien/kr/alpha/solver/optimization/WeightAtLevelsManager.java b/src/main/java/at/ac/tuwien/kr/alpha/solver/optimization/WeightAtLevelsManager.java index 63c762e56..d224cc117 100644 --- a/src/main/java/at/ac/tuwien/kr/alpha/solver/optimization/WeightAtLevelsManager.java +++ b/src/main/java/at/ac/tuwien/kr/alpha/solver/optimization/WeightAtLevelsManager.java @@ -5,8 +5,6 @@ import org.slf4j.LoggerFactory; import java.util.ArrayList; -import java.util.HashMap; -import java.util.Map; import java.util.TreeMap; import static at.ac.tuwien.kr.alpha.Util.oops; @@ -17,13 +15,18 @@ * valuation of the best-known answer-set at the time, the other is the (partial) valuation of the currently explored * (partial) answer-set. * + * Weights-at-levels are stored in arrays in reverse order (array position 0 is most important level). Therefore, the + * maximum level must be declared upfront. Furthermore, the array may grow if less-important levels are added later and + * it is automatically trimmed if highest-level weights are 0 when marking the current valuation as the best-known one. + * + * Code using this class must therefore ensure not to add weights at levels beyond the current maximum one. + * * Copyright (c) 2021, the Alpha Team. */ public class WeightAtLevelsManager implements Checkable { private static final Logger LOGGER = LoggerFactory.getLogger(WeightAtLevelsManager.class); private boolean checksEnabled; - private final WeakConstraintsManager weakConstraintsManager; // Stores weights in inverted order: zero is highest violated level, may also grow to represent negative levels. private ArrayList currentWeightAtLevels; private ArrayList bestKnownWeightAtLevels; @@ -34,9 +37,7 @@ public class WeightAtLevelsManager implements Checkable { private boolean isCurrentBetterThanBest; private boolean hasBestKnown; - public WeightAtLevelsManager(WeakConstraintsManager weakConstraintsManager) { - this.weakConstraintsManager = weakConstraintsManager; - currentWeightAtLevels = new ArrayList<>(); + public WeightAtLevelsManager() { bestKnownWeightAtLevels = new ArrayList<>(); maxOffsetCurrentIsAllEqualBest = -1; isCurrentBetterThanBest = true; @@ -55,6 +56,9 @@ public boolean isCurrentBetterThanBest() { if (checksEnabled) { runChecks(); } + if (!hasBestKnown) { + throw oops("WeightAtLevelsManager has no best-known valuation yet."); + } return isCurrentBetterThanBest; } @@ -74,14 +78,31 @@ public TreeMap getCurrentWeightAtLevels() { return weightPerLevels; } - public void increaseCurrentWeight(WeakConstraintAtomCallback atom) { + /** + * Returns the current maximum level. Note that this may be smaller than a previously set one as the maximum + * level may decrease following the marking of another valuation as best-known. + * @return the current maximum level. + */ + public int getMaxLevel() { + return maxLevel; + } + + /** + * Increases the current valuation at the given level by the given weight. + * @param level the level to increase. + * @param weight the weight at the level. + */ + public void increaseCurrentWeight(int level, int weight) { if (checksEnabled) { runChecks(); } + if (level > maxLevel) { + throw oops("WeightAtLevelsManager invoked to increase violation above maximum declared level."); + } // Record the new weight. - growForLevel(currentWeightAtLevels, atom.level); - int listOffset = levelToListOffset(atom.level); - int newWeight = currentWeightAtLevels.get(listOffset) + atom.weight; + growForLevel(currentWeightAtLevels, level); + int listOffset = levelToListOffset(level); + int newWeight = currentWeightAtLevels.get(listOffset) + weight; currentWeightAtLevels.set(listOffset, newWeight); // Now check whether current is worse than best known. @@ -95,20 +116,31 @@ public void increaseCurrentWeight(WeakConstraintAtomCallback atom) { maxOffsetCurrentIsAllEqualBest = listOffset - 1; } else if (listOffset == maxOffsetCurrentIsAllEqualBest + 1) { // Weight increased for the first level where current and best-known are not equal. - moveUpwardsMaxLevelCurrentIsAllEqual(); + moveUpwardsMaxOffsetCurrentIsAllEqual(); recomputeIsCurrentBetterThanBest(); } else { throw oops("Increasing weight of current answer reached unforeseen state."); } + if (checksEnabled) { + runChecks(); + } } - public void decreaseCurrentWeight(WeakConstraintAtomCallback atom) { + /** + * Decreases the current valuation at the given level by the given weight. + * @param level the level to decrease. + * @param weight the weight at the level. + */ + public void decreaseCurrentWeight(int level, int weight) { if (checksEnabled) { runChecks(); } + if (level > maxLevel) { + throw oops("WeightAtLevelsManager invoked to decrease violation above maximum declared level."); + } // Record the new weight. - int listOffset = levelToListOffset(atom.level); - int newWeight = currentWeightAtLevels.get(listOffset) - atom.weight; + int listOffset = levelToListOffset(level); + int newWeight = currentWeightAtLevels.get(listOffset) - weight; currentWeightAtLevels.set(listOffset, newWeight); // Now check whether current is better than best known and adapt maxOffsetCurrentIsAllEqualBest. @@ -123,14 +155,17 @@ public void decreaseCurrentWeight(WeakConstraintAtomCallback atom) { return; } else if (listOffset == maxOffsetCurrentIsAllEqualBest + 1) { // Decrease might make current better than best known and change level of equals. - moveUpwardsMaxLevelCurrentIsAllEqual(); + moveUpwardsMaxOffsetCurrentIsAllEqual(); recomputeIsCurrentBetterThanBest(); } else { throw oops("Decreasing weight of current answer reached unforeseen state."); } + if (checksEnabled) { + runChecks(); + } } - private void moveUpwardsMaxLevelCurrentIsAllEqual() { + private void moveUpwardsMaxOffsetCurrentIsAllEqual() { for (int i = max(maxOffsetCurrentIsAllEqualBest, 0); i < currentWeightAtLevels.size(); i++) { int currentLevelWeight = currentWeightAtLevels.get(i); int bestLevelWeight = bestKnownWeightAtLevels.size() > i ? bestKnownWeightAtLevels.get(i) : 0; @@ -170,18 +205,22 @@ private void growForLevel(ArrayList listToGrow, int level) { /** * Marks the current valuation/weights-at-levels as being the best-known one. + * + * May shrink the underlying maximum level if current highest level(s) are zero. */ public void markCurrentWeightAsBestKnown() { LOGGER.trace("Marking current answer-set as best known."); if (!hasBestKnown) { - initializeFirstWeightsAtLevel(); - } else { - trimZeroWeightLevels(); - bestKnownWeightAtLevels = new ArrayList<>(currentWeightAtLevels); + hasBestKnown = true; + LOGGER.trace(" Initially, current/best known weights are: {}", currentWeightAtLevels); } + trimZeroWeightLevels(); + bestKnownWeightAtLevels = new ArrayList<>(currentWeightAtLevels); maxOffsetCurrentIsAllEqualBest = bestKnownWeightAtLevels.size() - 1; - LOGGER.trace("Max offset current is all equal to best known is: {}", maxOffsetCurrentIsAllEqualBest); + LOGGER.trace(" Max offset current is all equal to best known is: {}", maxOffsetCurrentIsAllEqualBest); + LOGGER.trace(" Current/Best known weights are: {}", bestKnownWeightAtLevels); isCurrentBetterThanBest = false; + LOGGER.trace("End marking current answer-set as best known."); } /** @@ -201,39 +240,21 @@ private void trimZeroWeightLevels() { } } - private void initializeFirstWeightsAtLevel() { - // First answer set has been found, take its maximum level as zero-level. - LOGGER.trace("Initializing for first answer-set."); - HashMap sumWeightsAtLevel = new HashMap<>(); - int highestLevel = 0; - int lowestLevel = 0; - for (WeakConstraintAtomCallback atomCallback : weakConstraintsManager.getTrueWeakConstraintAtomCallbacksOfFirstAnswerSet()) { - int level = atomCallback.level; - // Record if level is highest or lowest so far. - if (highestLevel < level) { - highestLevel = level; - } - if (lowestLevel > level) { - lowestLevel = level; - } - // Update weight information. - if (sumWeightsAtLevel.get(level) == null) { - sumWeightsAtLevel.put(level, atomCallback.weight); - } else { - int newWeight = sumWeightsAtLevel.get(level) + atomCallback.weight; - sumWeightsAtLevel.put(level, newWeight); - } - } - maxLevel = highestLevel; - LOGGER.trace("Maximum recorded level (0-offset) is: {} ({})", maxLevel, listOffsetToLevel(0)); - bestKnownWeightAtLevels = new ArrayList<>(); - growForLevel(bestKnownWeightAtLevels, lowestLevel); - for (Map.Entry levelWeight : sumWeightsAtLevel.entrySet()) { - bestKnownWeightAtLevels.set(maxLevel - levelWeight.getKey(), levelWeight.getValue()); + /** + * Initializes the maximum level that will ever be encountered while solving. + * This method may only be called once and must be called before the first weights are set/increased. + * + * Note that the maximum level may decrease automatically following calls to markCurrentWeightAsBestKnown. + * + * @param maxLevel the highest level ever to be encountered. + */ + public void setMaxLevel(int maxLevel) { + if (currentWeightAtLevels != null) { + throw oops("WeightAtLevelsManager.setMaxLevel called more than once."); } - currentWeightAtLevels = new ArrayList<>(bestKnownWeightAtLevels); - hasBestKnown = true; - LOGGER.trace("Initially, current/best known weights are: {}", bestKnownWeightAtLevels); + this.maxLevel = maxLevel; + LOGGER.trace("Maximum level (0-offset) is set to: {} ({})", maxLevel, listOffsetToLevel(0)); + currentWeightAtLevels = new ArrayList<>(); } private int levelToListOffset(int level) { @@ -257,14 +278,16 @@ private void runChecks() { if (!hasBestKnown && !bestKnownWeightAtLevels.isEmpty()) { throw oops("WeakConstraintManager has best known answer set information but no answer set was found yet."); } - if (hasBestKnown && bestKnownWeightAtLevels.get(0) == 0) { + if (hasBestKnown && !bestKnownWeightAtLevels.isEmpty() && bestKnownWeightAtLevels.get(0) == 0) { throw oops("WeakConstraintManager has best-known answer set with zero-weights at highest level."); } - // Check whether isCurrentBetterThanBest flag is consistent. - if (isCurrentBetterThanBest != checkIsCurrentBetterThanBest()) { - throw oops("WeakConstraintManager detected valuation of current assignment is inconsistent with state flag."); + if (hasBestKnown) { + // Check whether isCurrentBetterThanBest flag is consistent. + if (isCurrentBetterThanBest != checkIsCurrentBetterThanBest()) { + throw oops("WeakConstraintManager detected valuation of current assignment is inconsistent with state flag."); + } + checkLevelBar(); } - checkLevelBar(); LOGGER.trace("Checks done."); } diff --git a/src/test/java/at/ac/tuwien/kr/alpha/solver/WeakConstraintsTests.java b/src/test/java/at/ac/tuwien/kr/alpha/solver/WeakConstraintsTests.java index 63c2f4439..1830ea115 100644 --- a/src/test/java/at/ac/tuwien/kr/alpha/solver/WeakConstraintsTests.java +++ b/src/test/java/at/ac/tuwien/kr/alpha/solver/WeakConstraintsTests.java @@ -30,6 +30,15 @@ public void simpleWeightedAnswerSet() { TestUtils.assertOptimumAnswerSetEquals("b", "1@1", actualAnswerSets); } + @Test + public void simpleWeightedAnswerSetWithNegativeLevel() { + String program = ":~a.[2@1,foo,bar]" + + ":~b.[1@-1,baz]" + + "a :- not b. b:- not a."; + Set actualAnswerSets = collectSet(program); + TestUtils.assertOptimumAnswerSetEquals("b", "1@-1", actualAnswerSets); + } + @Test public void simpleMultiLevelWeightedAnswerSet() { String program = ":~a.[2@2,foo,bar]" + @@ -39,4 +48,60 @@ public void simpleMultiLevelWeightedAnswerSet() { Set actualAnswerSets = collectSet(program); TestUtils.assertOptimumAnswerSetEquals("b", "3@-4, 1@1", actualAnswerSets); } + + @Test + public void sameWeightSummedUpInLevel() { + String program = "{a;b}." + + ":- not a, not b." + + ":~b.[1@3]" + + ":~a.[2@1,foo]" + + ":~a.[2@1,bar]"; + Set actualAnswerSets = collectSet(program); + TestUtils.assertOptimumAnswerSetEquals("a", "4@1", actualAnswerSets); + } + + @Test + public void sameWeightSameTermNotSummedUpInLevel() { + String program = "{a;b}." + + ":- not a, not b." + + ":~b.[1@3]" + + ":~a.[2@1,foo]" + + ":~a.[2@1,foo]"; + Set actualAnswerSets = collectSet(program); + TestUtils.assertOptimumAnswerSetEquals("a", "2@1", actualAnswerSets); + } + + @Test(expected = IllegalArgumentException.class) + public void negativeWeightThrowsException() { + String program = "p(1..9)." + + "a :- q(X)." + + "{ q(X) } :- p(X)." + + "has_q :- q(X)." + + ":- not has_q." + + ":- q(X), q(Y), X != Y." + + "w(Z) :- Z = 8 - K, q(K)." + + ":~a,w(Z).[Z@1]"; + Set actualAnswerSets = collectSet(program); + // In case negative weights can be dealt with (e.g. by fully grounding or under certain restrictions), + // the optimum answer set of above program is: p(1),...,p(9),q(9),w(-1),a,has_q at valuation -1@1 + // Under current behaviour we expect the computation of answer-sets to fail already. + TestUtils.assertOptimumAnswerSetEquals( + "p(1),p(2),p(3),p(4),p(5),p(6),p(7),p(8),p(9),q(9),w(-1),a,has_q", "-1@1", actualAnswerSets); + } + + @Test + public void complexValuationWithMultipleWeightsOnMultipleLevels() { + String program = "dom(1..3)." + + "{ a(X) } :- dom(X)." + + "{ b(X) } :- dom(X)." + + "{ c(X) } :- dom(X)." + + "weightatlevel(W,L) :- a(X),b(Y),c(Z), W = 2*X+Y, L = Z*Y." + + ":~ weightatlevel(W,L).[W@L]" + + "has_wal :- weightatlevel(W,L)." + + ":- not has_wal."; + System.out.println(program); + Set actualAnswerSets = collectSet(program); + TestUtils.assertOptimumAnswerSetEquals( + "dom(1), dom(2), dom(3), c(1), b(1), a(1), weightatlevel(3,1), has_wal", "3@1", actualAnswerSets); + } } diff --git a/src/test/java/at/ac/tuwien/kr/alpha/solver/optimization/WeightAtLevelsManagerTest.java b/src/test/java/at/ac/tuwien/kr/alpha/solver/optimization/WeightAtLevelsManagerTest.java new file mode 100644 index 000000000..ddc78748f --- /dev/null +++ b/src/test/java/at/ac/tuwien/kr/alpha/solver/optimization/WeightAtLevelsManagerTest.java @@ -0,0 +1,77 @@ +package at.ac.tuwien.kr.alpha.solver.optimization; + +import org.junit.Test; + +import java.util.TreeMap; + +import static org.junit.Assert.*; + +/** + * Copyright (c) 2021, the Alpha Team. + */ +public class WeightAtLevelsManagerTest { + + @Test + public void simpleWeightAtTwoLevels() { + WeightAtLevelsManager weightAtLevelsManager = new WeightAtLevelsManager(); + weightAtLevelsManager.setMaxLevel(3); + assertTrue(weightAtLevelsManager.getCurrentWeightAtLevels().isEmpty()); + weightAtLevelsManager.increaseCurrentWeight(0, 2); + weightAtLevelsManager.increaseCurrentWeight(3, 1); + TreeMap currentWeightsAfterIncrease = weightAtLevelsManager.getCurrentWeightAtLevels(); + assertEquals(1, currentWeightsAfterIncrease.get(3).longValue()); + assertEquals(2, currentWeightsAfterIncrease.get(0).longValue()); + assertEquals(2, currentWeightsAfterIncrease.size()); + + weightAtLevelsManager.decreaseCurrentWeight(0, 2); + + TreeMap currentWeightsAfterDecrease = weightAtLevelsManager.getCurrentWeightAtLevels(); + assertEquals(1, currentWeightsAfterDecrease.get(3).longValue()); + assertNull(currentWeightsAfterDecrease.get(0)); + assertEquals(1, currentWeightsAfterDecrease.size()); + } + + @Test + public void currentIsBestAfterDecrease() { + WeightAtLevelsManager weightAtLevelsManager = new WeightAtLevelsManager(); + weightAtLevelsManager.setMaxLevel(0); + assertTrue(weightAtLevelsManager.getCurrentWeightAtLevels().isEmpty()); + weightAtLevelsManager.increaseCurrentWeight(0, 2); + weightAtLevelsManager.increaseCurrentWeight(0, 2); + weightAtLevelsManager.markCurrentWeightAsBestKnown(); + + assertFalse(weightAtLevelsManager.isCurrentBetterThanBest()); + + weightAtLevelsManager.decreaseCurrentWeight(0, 2); + assertTrue(weightAtLevelsManager.isCurrentBetterThanBest()); + } + + @Test(expected = RuntimeException.class) + public void addAtomOnHigherThanMaxLevel() { + WeightAtLevelsManager weightAtLevelsManager = new WeightAtLevelsManager(); + weightAtLevelsManager.setMaxLevel(3); + assertTrue(weightAtLevelsManager.getCurrentWeightAtLevels().isEmpty()); + weightAtLevelsManager.increaseCurrentWeight(3, 1); + weightAtLevelsManager.increaseCurrentWeight(0, 2); + weightAtLevelsManager.markCurrentWeightAsBestKnown(); + + assertFalse(weightAtLevelsManager.isCurrentBetterThanBest()); + + weightAtLevelsManager.decreaseCurrentWeight(0, 2); + assertTrue(weightAtLevelsManager.isCurrentBetterThanBest()); + // Now increase some weight at a level beyond the declared maxLevel, so an Exception is expected here. + weightAtLevelsManager.increaseCurrentWeight(4, 3); + } + + @Test + public void markingCurrentWeightAsBestDecreasesMaximumLevel() { + WeightAtLevelsManager weightAtLevelsManager = new WeightAtLevelsManager(); + weightAtLevelsManager.setMaxLevel(3); + assertTrue(weightAtLevelsManager.getCurrentWeightAtLevels().isEmpty()); + weightAtLevelsManager.increaseCurrentWeight(1, 1); + + assertEquals(3, weightAtLevelsManager.getMaxLevel()); + weightAtLevelsManager.markCurrentWeightAsBestKnown(); + assertTrue(weightAtLevelsManager.getMaxLevel() < 3); + } +} \ No newline at end of file From 264da8ea09634c6eb74c1b40b6abffbb188f032a Mon Sep 17 00:00:00 2001 From: Antonius Weinzierl Date: Thu, 12 Aug 2021 23:53:28 +0200 Subject: [PATCH 17/27] Fix merge, adapt tests to junit5. --- .../alpha/common/WeightedAnswerSetTest.java | 6 +- .../kr/alpha/solver/WeakConstraintsTests.java | 82 ++++++++++--------- .../WeightAtLevelsManagerTest.java | 14 +++- .../tuwien/kr/alpha/test/util/TestUtils.java | 25 ++---- 4 files changed, 64 insertions(+), 63 deletions(-) diff --git a/src/test/java/at/ac/tuwien/kr/alpha/common/WeightedAnswerSetTest.java b/src/test/java/at/ac/tuwien/kr/alpha/common/WeightedAnswerSetTest.java index cc21369d3..e3fa0d68a 100644 --- a/src/test/java/at/ac/tuwien/kr/alpha/common/WeightedAnswerSetTest.java +++ b/src/test/java/at/ac/tuwien/kr/alpha/common/WeightedAnswerSetTest.java @@ -1,9 +1,11 @@ package at.ac.tuwien.kr.alpha.common; import at.ac.tuwien.kr.alpha.test.util.TestUtils; -import org.junit.Test; +import org.junit.jupiter.api.Test; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotEquals; /** * Copyright (c) 2021, the Alpha Team. diff --git a/src/test/java/at/ac/tuwien/kr/alpha/solver/WeakConstraintsTests.java b/src/test/java/at/ac/tuwien/kr/alpha/solver/WeakConstraintsTests.java index 1830ea115..3ffd00fe5 100644 --- a/src/test/java/at/ac/tuwien/kr/alpha/solver/WeakConstraintsTests.java +++ b/src/test/java/at/ac/tuwien/kr/alpha/solver/WeakConstraintsTests.java @@ -1,78 +1,80 @@ package at.ac.tuwien.kr.alpha.solver; import at.ac.tuwien.kr.alpha.common.AnswerSet; -import at.ac.tuwien.kr.alpha.test.util.TestUtils; -import org.junit.Test; import java.util.Set; +import static at.ac.tuwien.kr.alpha.test.util.TestUtils.assertOptimumAnswerSetEquals; +import static at.ac.tuwien.kr.alpha.test.util.TestUtils.collectRegressionTestAnswerSets; +import static org.junit.jupiter.api.Assertions.assertThrows; + /** * Copyright (c) 2021, the Alpha Team. */ -public class WeakConstraintsTests extends AbstractSolverTests { +public class WeakConstraintsTests { - @Test - public void simpleWeightsSameLevel() { + @RegressionTest + public void simpleWeightsSameLevel(RegressionTestConfig cfg) { String program = ":~a.[1@0,foo,bar]" + ":~b.[2@0,baz]" + "a :- not b. b:- not a."; - Set actualAnswerSets = collectSet(program); - TestUtils.assertOptimumAnswerSetEquals("a", "1@0", actualAnswerSets); + Set actualAnswerSets = collectRegressionTestAnswerSets(program, cfg); + assertOptimumAnswerSetEquals("a", "1@0", actualAnswerSets); } - @Test - public void simpleWeightedAnswerSet() { + @RegressionTest + public void simpleWeightedAnswerSet(RegressionTestConfig cfg) { String program = ":~a.[2@2,foo,bar]" + ":~b.[1@1,baz]" + "a :- not b. b:- not a."; - Set actualAnswerSets = collectSet(program); - TestUtils.assertOptimumAnswerSetEquals("b", "1@1", actualAnswerSets); + Set actualAnswerSets = collectRegressionTestAnswerSets(program, cfg); + assertOptimumAnswerSetEquals("b", "1@1", actualAnswerSets); } - @Test - public void simpleWeightedAnswerSetWithNegativeLevel() { + @RegressionTest + public void simpleWeightedAnswerSetWithNegativeLevel(RegressionTestConfig cfg) { String program = ":~a.[2@1,foo,bar]" + ":~b.[1@-1,baz]" + "a :- not b. b:- not a."; - Set actualAnswerSets = collectSet(program); - TestUtils.assertOptimumAnswerSetEquals("b", "1@-1", actualAnswerSets); + Set actualAnswerSets = collectRegressionTestAnswerSets(program, cfg); + assertOptimumAnswerSetEquals("b", "1@-1", actualAnswerSets); } - @Test - public void simpleMultiLevelWeightedAnswerSet() { + @RegressionTest + public void simpleMultiLevelWeightedAnswerSet(RegressionTestConfig cfg) { String program = ":~a.[2@2,foo,bar]" + ":~b.[1@1,baz]" + ":~b.[3@-4,baz]" + "a :- not b. b:- not a."; - Set actualAnswerSets = collectSet(program); - TestUtils.assertOptimumAnswerSetEquals("b", "3@-4, 1@1", actualAnswerSets); + Set actualAnswerSets = collectRegressionTestAnswerSets(program, cfg); + assertOptimumAnswerSetEquals("b", "3@-4, 1@1", actualAnswerSets); } - @Test - public void sameWeightSummedUpInLevel() { + @RegressionTest + public void sameWeightSummedUpInLevel(RegressionTestConfig cfg) { String program = "{a;b}." + ":- not a, not b." + ":~b.[1@3]" + ":~a.[2@1,foo]" + ":~a.[2@1,bar]"; - Set actualAnswerSets = collectSet(program); - TestUtils.assertOptimumAnswerSetEquals("a", "4@1", actualAnswerSets); + Set actualAnswerSets = collectRegressionTestAnswerSets(program, cfg); + assertOptimumAnswerSetEquals("a", "4@1", actualAnswerSets); } - @Test - public void sameWeightSameTermNotSummedUpInLevel() { + @RegressionTest + public void sameWeightSameTermNotSummedUpInLevel(RegressionTestConfig cfg) { String program = "{a;b}." + ":- not a, not b." + ":~b.[1@3]" + ":~a.[2@1,foo]" + ":~a.[2@1,foo]"; - Set actualAnswerSets = collectSet(program); - TestUtils.assertOptimumAnswerSetEquals("a", "2@1", actualAnswerSets); + Set actualAnswerSets = collectRegressionTestAnswerSets(program, cfg); + assertOptimumAnswerSetEquals("a", "2@1", actualAnswerSets); } - @Test(expected = IllegalArgumentException.class) - public void negativeWeightThrowsException() { + @RegressionTest + public void negativeWeightThrowsException(RegressionTestConfig cfg) { String program = "p(1..9)." + "a :- q(X)." + "{ q(X) } :- p(X)." + @@ -81,16 +83,18 @@ public void negativeWeightThrowsException() { ":- q(X), q(Y), X != Y." + "w(Z) :- Z = 8 - K, q(K)." + ":~a,w(Z).[Z@1]"; - Set actualAnswerSets = collectSet(program); - // In case negative weights can be dealt with (e.g. by fully grounding or under certain restrictions), - // the optimum answer set of above program is: p(1),...,p(9),q(9),w(-1),a,has_q at valuation -1@1 - // Under current behaviour we expect the computation of answer-sets to fail already. - TestUtils.assertOptimumAnswerSetEquals( - "p(1),p(2),p(3),p(4),p(5),p(6),p(7),p(8),p(9),q(9),w(-1),a,has_q", "-1@1", actualAnswerSets); + assertThrows(IllegalArgumentException.class, () -> { + Set actualAnswerSets = collectRegressionTestAnswerSets(program, cfg); + // In case negative weights can be dealt with (e.g. by fully grounding or under certain restrictions), + // the optimum answer set of above program is: p(1),...,p(9),q(9),w(-1),a,has_q at valuation -1@1 + // Under current behaviour we expect the computation of answer-sets to fail already. + assertOptimumAnswerSetEquals( + "p(1),p(2),p(3),p(4),p(5),p(6),p(7),p(8),p(9),q(9),w(-1),a,has_q", "-1@1", actualAnswerSets); + }); } - @Test - public void complexValuationWithMultipleWeightsOnMultipleLevels() { + @RegressionTest + public void complexValuationWithMultipleWeightsOnMultipleLevels(RegressionTestConfig cfg) { String program = "dom(1..3)." + "{ a(X) } :- dom(X)." + "{ b(X) } :- dom(X)." + @@ -100,8 +104,8 @@ public void complexValuationWithMultipleWeightsOnMultipleLevels() { "has_wal :- weightatlevel(W,L)." + ":- not has_wal."; System.out.println(program); - Set actualAnswerSets = collectSet(program); - TestUtils.assertOptimumAnswerSetEquals( + Set actualAnswerSets = collectRegressionTestAnswerSets(program, cfg); + assertOptimumAnswerSetEquals( "dom(1), dom(2), dom(3), c(1), b(1), a(1), weightatlevel(3,1), has_wal", "3@1", actualAnswerSets); } } diff --git a/src/test/java/at/ac/tuwien/kr/alpha/solver/optimization/WeightAtLevelsManagerTest.java b/src/test/java/at/ac/tuwien/kr/alpha/solver/optimization/WeightAtLevelsManagerTest.java index ddc78748f..650948664 100644 --- a/src/test/java/at/ac/tuwien/kr/alpha/solver/optimization/WeightAtLevelsManagerTest.java +++ b/src/test/java/at/ac/tuwien/kr/alpha/solver/optimization/WeightAtLevelsManagerTest.java @@ -1,10 +1,14 @@ package at.ac.tuwien.kr.alpha.solver.optimization; -import org.junit.Test; +import org.junit.jupiter.api.Test; import java.util.TreeMap; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; /** * Copyright (c) 2021, the Alpha Team. @@ -46,7 +50,7 @@ public void currentIsBestAfterDecrease() { assertTrue(weightAtLevelsManager.isCurrentBetterThanBest()); } - @Test(expected = RuntimeException.class) + @Test public void addAtomOnHigherThanMaxLevel() { WeightAtLevelsManager weightAtLevelsManager = new WeightAtLevelsManager(); weightAtLevelsManager.setMaxLevel(3); @@ -60,7 +64,9 @@ public void addAtomOnHigherThanMaxLevel() { weightAtLevelsManager.decreaseCurrentWeight(0, 2); assertTrue(weightAtLevelsManager.isCurrentBetterThanBest()); // Now increase some weight at a level beyond the declared maxLevel, so an Exception is expected here. - weightAtLevelsManager.increaseCurrentWeight(4, 3); + assertThrows(RuntimeException.class, () -> { + weightAtLevelsManager.increaseCurrentWeight(4, 3); + }); } @Test diff --git a/src/test/java/at/ac/tuwien/kr/alpha/test/util/TestUtils.java b/src/test/java/at/ac/tuwien/kr/alpha/test/util/TestUtils.java index e5bf7c85f..934f6bc8a 100644 --- a/src/test/java/at/ac/tuwien/kr/alpha/test/util/TestUtils.java +++ b/src/test/java/at/ac/tuwien/kr/alpha/test/util/TestUtils.java @@ -1,26 +1,10 @@ package at.ac.tuwien.kr.alpha.test.util; -import static java.util.Collections.emptySet; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTimeoutPreemptively; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.time.Duration; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Set; -import java.util.StringJoiner; - -import org.junit.jupiter.api.Assumptions; -import org.junit.jupiter.api.function.Executable; - import at.ac.tuwien.kr.alpha.AnswerSetsParser; import at.ac.tuwien.kr.alpha.common.AnswerSet; -import at.ac.tuwien.kr.alpha.common.BasicAnswerSet; import at.ac.tuwien.kr.alpha.common.AtomStore; import at.ac.tuwien.kr.alpha.common.AtomStoreImpl; +import at.ac.tuwien.kr.alpha.common.BasicAnswerSet; import at.ac.tuwien.kr.alpha.common.Predicate; import at.ac.tuwien.kr.alpha.common.WeightedAnswerSet; import at.ac.tuwien.kr.alpha.common.atoms.Atom; @@ -42,8 +26,10 @@ import at.ac.tuwien.kr.alpha.solver.Solver; import at.ac.tuwien.kr.alpha.solver.SolverFactory; import at.ac.tuwien.kr.alpha.solver.heuristics.BranchingHeuristicFactory; -import org.junit.Assert; +import org.junit.jupiter.api.Assumptions; +import org.junit.jupiter.api.function.Executable; +import java.time.Duration; import java.util.ArrayList; import java.util.Arrays; import java.util.LinkedHashSet; @@ -53,6 +39,9 @@ import java.util.TreeMap; import static java.util.Collections.emptySet; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTimeoutPreemptively; +import static org.junit.jupiter.api.Assertions.assertTrue; public class TestUtils { From 271fc3a484d45df5ba90041037c5f30f6fd108db Mon Sep 17 00:00:00 2001 From: Antonius Weinzierl Date: Sun, 15 Aug 2021 04:25:07 +0200 Subject: [PATCH 18/27] Fix failing tests. - WeightAtLevelsManager corrected logging, tolerates 0 weight at level 0. - WeightAtLevelsManagerTest runs with checks now, added more tests. - WeakConstraintsTests ignored for naive solver as that one does not optimize. --- .../optimization/WeightAtLevelsManager.java | 22 ++++++---- .../kr/alpha/solver/WeakConstraintsTests.java | 11 ++++- .../WeightAtLevelsManagerTest.java | 40 +++++++++++++++++++ 3 files changed, 65 insertions(+), 8 deletions(-) diff --git a/src/main/java/at/ac/tuwien/kr/alpha/solver/optimization/WeightAtLevelsManager.java b/src/main/java/at/ac/tuwien/kr/alpha/solver/optimization/WeightAtLevelsManager.java index d224cc117..f41f56483 100644 --- a/src/main/java/at/ac/tuwien/kr/alpha/solver/optimization/WeightAtLevelsManager.java +++ b/src/main/java/at/ac/tuwien/kr/alpha/solver/optimization/WeightAtLevelsManager.java @@ -260,7 +260,7 @@ public void setMaxLevel(int maxLevel) { private int levelToListOffset(int level) { int levelInList = maxLevel - level; if (levelInList < 0) { - throw oops("Level optimisation in WeakConstraintsManager is negative."); + throw oops("Level optimisation in WeightAtLevelsManager is negative."); } return levelInList; } @@ -276,21 +276,29 @@ private void runChecks() { } if (!hasBestKnown && !bestKnownWeightAtLevels.isEmpty()) { - throw oops("WeakConstraintManager has best known answer set information but no answer set was found yet."); - } - if (hasBestKnown && !bestKnownWeightAtLevels.isEmpty() && bestKnownWeightAtLevels.get(0) == 0) { - throw oops("WeakConstraintManager has best-known answer set with zero-weights at highest level."); + throw oops("WeightAtLevelsManager has best known answer set information but no answer set was found yet."); } + checkBestKnownFreeOfLeadingZeroLevels(); if (hasBestKnown) { // Check whether isCurrentBetterThanBest flag is consistent. if (isCurrentBetterThanBest != checkIsCurrentBetterThanBest()) { - throw oops("WeakConstraintManager detected valuation of current assignment is inconsistent with state flag."); + throw oops("WeightAtLevelsManager detected valuation of current assignment is inconsistent with state flag."); } checkLevelBar(); } LOGGER.trace("Checks done."); } + private void checkBestKnownFreeOfLeadingZeroLevels() { + // Tolerate Zero weight if there is only one level. + if (!hasBestKnown || bestKnownWeightAtLevels.size() == 1) { + return; + } + if (!bestKnownWeightAtLevels.isEmpty() && bestKnownWeightAtLevels.get(0) == 0) { + throw oops("WeightAtLevelsManager has best-known answer set with zero-weights at highest level."); + } + } + private void checkLevelBar() { int highestLevelWhereLowerLevelsAreEqual = -1; for (int i = 0; i < currentWeightAtLevels.size(); i++) { @@ -302,7 +310,7 @@ private void checkLevelBar() { } // The level bar should be at highestLevelWhereLowerLevelsAreEqual+1. if (maxOffsetCurrentIsAllEqualBest != highestLevelWhereLowerLevelsAreEqual) { - throw oops("WeakConstraintManager detected level bar at wrong level, it is " + maxOffsetCurrentIsAllEqualBest + throw oops("WeightAtLevelsManager detected level bar at wrong level, it is " + maxOffsetCurrentIsAllEqualBest + "and should be " + highestLevelWhereLowerLevelsAreEqual); } } diff --git a/src/test/java/at/ac/tuwien/kr/alpha/solver/WeakConstraintsTests.java b/src/test/java/at/ac/tuwien/kr/alpha/solver/WeakConstraintsTests.java index 3ffd00fe5..19d495c4a 100644 --- a/src/test/java/at/ac/tuwien/kr/alpha/solver/WeakConstraintsTests.java +++ b/src/test/java/at/ac/tuwien/kr/alpha/solver/WeakConstraintsTests.java @@ -1,6 +1,7 @@ package at.ac.tuwien.kr.alpha.solver; import at.ac.tuwien.kr.alpha.common.AnswerSet; +import at.ac.tuwien.kr.alpha.test.util.TestUtils; import java.util.Set; @@ -15,6 +16,7 @@ public class WeakConstraintsTests { @RegressionTest public void simpleWeightsSameLevel(RegressionTestConfig cfg) { + TestUtils.ignoreTestForNaiveSolver(cfg); String program = ":~a.[1@0,foo,bar]" + ":~b.[2@0,baz]" + "a :- not b. b:- not a."; @@ -25,6 +27,7 @@ public void simpleWeightsSameLevel(RegressionTestConfig cfg) { @RegressionTest public void simpleWeightedAnswerSet(RegressionTestConfig cfg) { + TestUtils.ignoreTestForNaiveSolver(cfg); String program = ":~a.[2@2,foo,bar]" + ":~b.[1@1,baz]" + "a :- not b. b:- not a."; @@ -34,6 +37,7 @@ public void simpleWeightedAnswerSet(RegressionTestConfig cfg) { @RegressionTest public void simpleWeightedAnswerSetWithNegativeLevel(RegressionTestConfig cfg) { + TestUtils.ignoreTestForNaiveSolver(cfg); String program = ":~a.[2@1,foo,bar]" + ":~b.[1@-1,baz]" + "a :- not b. b:- not a."; @@ -43,6 +47,7 @@ public void simpleWeightedAnswerSetWithNegativeLevel(RegressionTestConfig cfg) { @RegressionTest public void simpleMultiLevelWeightedAnswerSet(RegressionTestConfig cfg) { + TestUtils.ignoreTestForNaiveSolver(cfg); String program = ":~a.[2@2,foo,bar]" + ":~b.[1@1,baz]" + ":~b.[3@-4,baz]" + @@ -53,6 +58,7 @@ public void simpleMultiLevelWeightedAnswerSet(RegressionTestConfig cfg) { @RegressionTest public void sameWeightSummedUpInLevel(RegressionTestConfig cfg) { + TestUtils.ignoreTestForNaiveSolver(cfg); String program = "{a;b}." + ":- not a, not b." + ":~b.[1@3]" + @@ -64,6 +70,7 @@ public void sameWeightSummedUpInLevel(RegressionTestConfig cfg) { @RegressionTest public void sameWeightSameTermNotSummedUpInLevel(RegressionTestConfig cfg) { + TestUtils.ignoreTestForNaiveSolver(cfg); String program = "{a;b}." + ":- not a, not b." + ":~b.[1@3]" + @@ -75,9 +82,10 @@ public void sameWeightSameTermNotSummedUpInLevel(RegressionTestConfig cfg) { @RegressionTest public void negativeWeightThrowsException(RegressionTestConfig cfg) { + TestUtils.ignoreTestForNaiveSolver(cfg); String program = "p(1..9)." + "a :- q(X)." + - "{ q(X) } :- p(X)." + + "{ q(X) } :- p(X), X != 8. % exclude case where violation is 0, which could be encountered before negative weight.\n" + "has_q :- q(X)." + ":- not has_q." + ":- q(X), q(Y), X != Y." + @@ -95,6 +103,7 @@ public void negativeWeightThrowsException(RegressionTestConfig cfg) { @RegressionTest public void complexValuationWithMultipleWeightsOnMultipleLevels(RegressionTestConfig cfg) { + TestUtils.ignoreTestForNaiveSolver(cfg); String program = "dom(1..3)." + "{ a(X) } :- dom(X)." + "{ b(X) } :- dom(X)." + diff --git a/src/test/java/at/ac/tuwien/kr/alpha/solver/optimization/WeightAtLevelsManagerTest.java b/src/test/java/at/ac/tuwien/kr/alpha/solver/optimization/WeightAtLevelsManagerTest.java index 650948664..7bbe7a74f 100644 --- a/src/test/java/at/ac/tuwien/kr/alpha/solver/optimization/WeightAtLevelsManagerTest.java +++ b/src/test/java/at/ac/tuwien/kr/alpha/solver/optimization/WeightAtLevelsManagerTest.java @@ -18,6 +18,7 @@ public class WeightAtLevelsManagerTest { @Test public void simpleWeightAtTwoLevels() { WeightAtLevelsManager weightAtLevelsManager = new WeightAtLevelsManager(); + weightAtLevelsManager.setChecksEnabled(true); weightAtLevelsManager.setMaxLevel(3); assertTrue(weightAtLevelsManager.getCurrentWeightAtLevels().isEmpty()); weightAtLevelsManager.increaseCurrentWeight(0, 2); @@ -38,6 +39,7 @@ public void simpleWeightAtTwoLevels() { @Test public void currentIsBestAfterDecrease() { WeightAtLevelsManager weightAtLevelsManager = new WeightAtLevelsManager(); + weightAtLevelsManager.setChecksEnabled(true); weightAtLevelsManager.setMaxLevel(0); assertTrue(weightAtLevelsManager.getCurrentWeightAtLevels().isEmpty()); weightAtLevelsManager.increaseCurrentWeight(0, 2); @@ -53,6 +55,7 @@ public void currentIsBestAfterDecrease() { @Test public void addAtomOnHigherThanMaxLevel() { WeightAtLevelsManager weightAtLevelsManager = new WeightAtLevelsManager(); + weightAtLevelsManager.setChecksEnabled(true); weightAtLevelsManager.setMaxLevel(3); assertTrue(weightAtLevelsManager.getCurrentWeightAtLevels().isEmpty()); weightAtLevelsManager.increaseCurrentWeight(3, 1); @@ -72,6 +75,7 @@ public void addAtomOnHigherThanMaxLevel() { @Test public void markingCurrentWeightAsBestDecreasesMaximumLevel() { WeightAtLevelsManager weightAtLevelsManager = new WeightAtLevelsManager(); + weightAtLevelsManager.setChecksEnabled(true); weightAtLevelsManager.setMaxLevel(3); assertTrue(weightAtLevelsManager.getCurrentWeightAtLevels().isEmpty()); weightAtLevelsManager.increaseCurrentWeight(1, 1); @@ -80,4 +84,40 @@ public void markingCurrentWeightAsBestDecreasesMaximumLevel() { weightAtLevelsManager.markCurrentWeightAsBestKnown(); assertTrue(weightAtLevelsManager.getMaxLevel() < 3); } + + @Test + public void indirectTrimmingCheck() { + WeightAtLevelsManager weightAtLevelsManager = new WeightAtLevelsManager(); + weightAtLevelsManager.setChecksEnabled(true); + weightAtLevelsManager.setMaxLevel(4); + assertTrue(weightAtLevelsManager.getCurrentWeightAtLevels().isEmpty()); + weightAtLevelsManager.increaseCurrentWeight(4, 1); + weightAtLevelsManager.markCurrentWeightAsBestKnown(); + + weightAtLevelsManager.decreaseCurrentWeight(4, 1); + weightAtLevelsManager.increaseCurrentWeight(2, 1); + weightAtLevelsManager.increaseCurrentWeight(1, 3); + weightAtLevelsManager.markCurrentWeightAsBestKnown(); + weightAtLevelsManager.decreaseCurrentWeight(1, 3); + // Note: the below assertion relies on the implementation of WeightAtLevelsManager doing trimming. + assertTrue(weightAtLevelsManager.getMaxLevel() < 3); + } + + @Test + public void trimmingAtLevelZero() { + WeightAtLevelsManager weightAtLevelsManager = new WeightAtLevelsManager(); + weightAtLevelsManager.setChecksEnabled(true); + weightAtLevelsManager.setMaxLevel(3); + assertTrue(weightAtLevelsManager.getCurrentWeightAtLevels().isEmpty()); + weightAtLevelsManager.increaseCurrentWeight(2, 1); + weightAtLevelsManager.markCurrentWeightAsBestKnown(); + + weightAtLevelsManager.decreaseCurrentWeight(2, 1); + weightAtLevelsManager.increaseCurrentWeight(1, 1); + weightAtLevelsManager.markCurrentWeightAsBestKnown(); + weightAtLevelsManager.decreaseCurrentWeight(1, 1); + + weightAtLevelsManager.markCurrentWeightAsBestKnown(); + assertFalse(weightAtLevelsManager.isCurrentBetterThanBest()); + } } \ No newline at end of file From 5b31ca744ac690693dbcc7dfaf63b2ad03ea8062 Mon Sep 17 00:00:00 2001 From: Antonius Weinzierl Date: Fri, 17 Sep 2021 13:19:26 +0200 Subject: [PATCH 19/27] Fix merge issues with aggregate rewriting. - WeakConstraint fixed toString. - AggregateOperatorNormalization returns WeakConstraint if rule is of that type. - IntervalTermToIntervalAtom rewriting of head no longer needs Literals. --- .../kr/alpha/common/rule/WeakConstraint.java | 3 ++- .../IntervalTermToIntervalAtom.java | 15 ++++++++--- .../AggregateOperatorNormalization.java | 19 +++++++++----- .../tuwien/kr/alpha/test/util/TestUtils.java | 26 +++---------------- 4 files changed, 29 insertions(+), 34 deletions(-) diff --git a/src/main/java/at/ac/tuwien/kr/alpha/common/rule/WeakConstraint.java b/src/main/java/at/ac/tuwien/kr/alpha/common/rule/WeakConstraint.java index 025dfd060..643cc85b3 100644 --- a/src/main/java/at/ac/tuwien/kr/alpha/common/rule/WeakConstraint.java +++ b/src/main/java/at/ac/tuwien/kr/alpha/common/rule/WeakConstraint.java @@ -38,6 +38,7 @@ public List getTermList() { @Override public String toString() { - return Util.join(":~ ", getBody(), "."); + String weightInformation = Util.join("[" + weight + "@" + level + (termList.isEmpty() ? "" : ", "), termList, "]"); + return Util.join(":~ ", getBody(), "." + weightInformation); } } diff --git a/src/main/java/at/ac/tuwien/kr/alpha/grounder/transformation/IntervalTermToIntervalAtom.java b/src/main/java/at/ac/tuwien/kr/alpha/grounder/transformation/IntervalTermToIntervalAtom.java index c0702d05a..4b1c3070b 100644 --- a/src/main/java/at/ac/tuwien/kr/alpha/grounder/transformation/IntervalTermToIntervalAtom.java +++ b/src/main/java/at/ac/tuwien/kr/alpha/grounder/transformation/IntervalTermToIntervalAtom.java @@ -72,7 +72,7 @@ private static NormalRule rewriteIntervalSpecifications(NormalRule rule) { } } NormalHead rewrittenHead = rule.isConstraint() ? null : - new NormalHead(rewriteLiteral(rule.getHeadAtom().toLiteral(), intervalReplacements).getAtom()); + new NormalHead(rewriteAtom(rule.getHeadAtom(), intervalReplacements)); // If intervalReplacements is empty, no IntervalTerms have been found, keep rule as is. if (intervalReplacements.isEmpty()) { @@ -106,6 +106,14 @@ private static Literal rewriteLiteral(Literal lit, Map intervalReplacement) { List termList = new ArrayList<>(atom.getTerms()); boolean didChange = false; for (int i = 0; i < termList.size(); i++) { @@ -124,10 +132,9 @@ private static Literal rewriteLiteral(Literal lit, Map intervalReplacement) { diff --git a/src/main/java/at/ac/tuwien/kr/alpha/grounder/transformation/aggregates/AggregateOperatorNormalization.java b/src/main/java/at/ac/tuwien/kr/alpha/grounder/transformation/aggregates/AggregateOperatorNormalization.java index 26c3f4531..b40c466fe 100644 --- a/src/main/java/at/ac/tuwien/kr/alpha/grounder/transformation/aggregates/AggregateOperatorNormalization.java +++ b/src/main/java/at/ac/tuwien/kr/alpha/grounder/transformation/aggregates/AggregateOperatorNormalization.java @@ -1,12 +1,5 @@ package at.ac.tuwien.kr.alpha.grounder.transformation.aggregates; -import static at.ac.tuwien.kr.alpha.common.ComparisonOperator.EQ; -import static at.ac.tuwien.kr.alpha.common.ComparisonOperator.LE; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - import at.ac.tuwien.kr.alpha.common.ComparisonOperator; import at.ac.tuwien.kr.alpha.common.atoms.AggregateAtom; import at.ac.tuwien.kr.alpha.common.atoms.AggregateAtom.AggregateFunctionSymbol; @@ -15,12 +8,20 @@ import at.ac.tuwien.kr.alpha.common.atoms.Literal; import at.ac.tuwien.kr.alpha.common.program.InputProgram; import at.ac.tuwien.kr.alpha.common.rule.BasicRule; +import at.ac.tuwien.kr.alpha.common.rule.WeakConstraint; import at.ac.tuwien.kr.alpha.common.terms.ArithmeticTerm; import at.ac.tuwien.kr.alpha.common.terms.ArithmeticTerm.ArithmeticOperator; import at.ac.tuwien.kr.alpha.common.terms.ConstantTerm; import at.ac.tuwien.kr.alpha.common.terms.Term; import at.ac.tuwien.kr.alpha.common.terms.VariableTerm; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import static at.ac.tuwien.kr.alpha.common.ComparisonOperator.EQ; +import static at.ac.tuwien.kr.alpha.common.ComparisonOperator.LE; + /** * Transforms an {@link InputProgram} such that, for all aggregate (body-)literals, only the comparison operators "=" * and "<=" are used. @@ -55,6 +56,10 @@ public static BasicRule normalize(BasicRule rule) { for (Literal lit : rule.getBody()) { rewrittenBody.addAll(rewriteLiteral(lit)); } + if (rule instanceof WeakConstraint) { + WeakConstraint wcRule = (WeakConstraint) rule; + return new WeakConstraint(rewrittenBody, wcRule.getWeight(), wcRule.getLevel(), wcRule.getTermList()); + } return new BasicRule(rule.getHead(), rewrittenBody); } diff --git a/src/test/java/at/ac/tuwien/kr/alpha/test/util/TestUtils.java b/src/test/java/at/ac/tuwien/kr/alpha/test/util/TestUtils.java index 8c00827da..4d4bcddca 100644 --- a/src/test/java/at/ac/tuwien/kr/alpha/test/util/TestUtils.java +++ b/src/test/java/at/ac/tuwien/kr/alpha/test/util/TestUtils.java @@ -1,27 +1,5 @@ package at.ac.tuwien.kr.alpha.test.util; -import static java.util.Collections.emptySet; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTimeoutPreemptively; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import com.ibm.icu.impl.Assert; - -import org.apache.commons.lang3.StringUtils; -import org.junit.jupiter.api.Assumptions; -import org.junit.jupiter.api.function.Executable; - -import java.time.Duration; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Set; -import java.util.StringJoiner; -import java.util.stream.Collectors; - -import at.ac.tuwien.kr.alpha.AnswerSetsParser; import at.ac.tuwien.kr.alpha.common.AnswerSet; import at.ac.tuwien.kr.alpha.common.AtomStore; import at.ac.tuwien.kr.alpha.common.AtomStoreImpl; @@ -50,17 +28,21 @@ import at.ac.tuwien.kr.alpha.solver.Solver; import at.ac.tuwien.kr.alpha.solver.SolverFactory; import at.ac.tuwien.kr.alpha.solver.heuristics.BranchingHeuristicFactory; +import com.ibm.icu.impl.Assert; +import org.apache.commons.lang3.StringUtils; import org.junit.jupiter.api.Assumptions; import org.junit.jupiter.api.function.Executable; import java.time.Duration; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; import java.util.StringJoiner; import java.util.TreeMap; +import java.util.stream.Collectors; import static java.util.Collections.emptySet; import static org.junit.jupiter.api.Assertions.assertEquals; From c5cc902f6555eee7f4ffbe832ecbfbee9863d740 Mon Sep 17 00:00:00 2001 From: Antonius Weinzierl Date: Wed, 6 Oct 2021 17:46:47 +0200 Subject: [PATCH 20/27] Introduce OptimizingSolver for weak constraints, SolverFactory returns OptimizingSolver if weak constraints are present. - OptimizingSolver extends DefaultSolver, implements branch-and-bound search. - DefaultSolver visibility of methods/fields adapted for inheritance. - AnswerSet provides getPredicateInstances for construction of WeightedAnswerSet from BasicAnswerSet. - Fix logging level in WeakConstraintsManager and WeakConstraintsTests. --- .../ac/tuwien/kr/alpha/common/AnswerSet.java | 9 +- .../kr/alpha/common/BasicAnswerSet.java | 6 +- .../kr/alpha/common/WeightedAnswerSet.java | 4 +- .../tuwien/kr/alpha/solver/DefaultSolver.java | 70 +++++--------- .../kr/alpha/solver/OptimizingSolver.java | 96 +++++++++++++++++++ .../tuwien/kr/alpha/solver/SolverFactory.java | 6 +- .../optimization/WeakConstraintsManager.java | 4 +- .../kr/alpha/solver/WeakConstraintsTests.java | 1 - 8 files changed, 138 insertions(+), 58 deletions(-) create mode 100644 src/main/java/at/ac/tuwien/kr/alpha/solver/OptimizingSolver.java diff --git a/src/main/java/at/ac/tuwien/kr/alpha/common/AnswerSet.java b/src/main/java/at/ac/tuwien/kr/alpha/common/AnswerSet.java index 894d81f8b..a02c99fdb 100644 --- a/src/main/java/at/ac/tuwien/kr/alpha/common/AnswerSet.java +++ b/src/main/java/at/ac/tuwien/kr/alpha/common/AnswerSet.java @@ -1,17 +1,20 @@ package at.ac.tuwien.kr.alpha.common; -import java.util.List; -import java.util.SortedSet; - import at.ac.tuwien.kr.alpha.Util; import at.ac.tuwien.kr.alpha.api.query.AnswerSetQuery; import at.ac.tuwien.kr.alpha.common.atoms.Atom; +import java.util.List; +import java.util.Map; +import java.util.SortedSet; + public interface AnswerSet extends Comparable { SortedSet getPredicates(); SortedSet getPredicateInstances(Predicate predicate); + Map> getPredicateInstances(); + boolean isEmpty(); @Override diff --git a/src/main/java/at/ac/tuwien/kr/alpha/common/BasicAnswerSet.java b/src/main/java/at/ac/tuwien/kr/alpha/common/BasicAnswerSet.java index 220775628..f768f958e 100644 --- a/src/main/java/at/ac/tuwien/kr/alpha/common/BasicAnswerSet.java +++ b/src/main/java/at/ac/tuwien/kr/alpha/common/BasicAnswerSet.java @@ -2,6 +2,7 @@ import at.ac.tuwien.kr.alpha.common.atoms.Atom; +import java.util.Collections; import java.util.Iterator; import java.util.Map; import java.util.Set; @@ -39,8 +40,9 @@ public boolean isEmpty() { return predicates.isEmpty(); } - protected Map> getPredicateInstances() { - return predicateInstances; + @Override + public Map> getPredicateInstances() { + return Collections.unmodifiableMap(predicateInstances); } @Override diff --git a/src/main/java/at/ac/tuwien/kr/alpha/common/WeightedAnswerSet.java b/src/main/java/at/ac/tuwien/kr/alpha/common/WeightedAnswerSet.java index 347b6f4f3..86d0f56aa 100644 --- a/src/main/java/at/ac/tuwien/kr/alpha/common/WeightedAnswerSet.java +++ b/src/main/java/at/ac/tuwien/kr/alpha/common/WeightedAnswerSet.java @@ -18,8 +18,8 @@ public class WeightedAnswerSet extends BasicAnswerSet { private final TreeMap weightPerLevel; - public WeightedAnswerSet(BasicAnswerSet basicAnswerSet, TreeMap weightPerLevel) { - super(basicAnswerSet.getPredicates(), basicAnswerSet.getPredicateInstances()); + public WeightedAnswerSet(AnswerSet answerSet, TreeMap weightPerLevel) { + super(answerSet.getPredicates(), answerSet.getPredicateInstances()); this.weightPerLevel = weightPerLevel; } diff --git a/src/main/java/at/ac/tuwien/kr/alpha/solver/DefaultSolver.java b/src/main/java/at/ac/tuwien/kr/alpha/solver/DefaultSolver.java index a0790ee3f..20383ace9 100644 --- a/src/main/java/at/ac/tuwien/kr/alpha/solver/DefaultSolver.java +++ b/src/main/java/at/ac/tuwien/kr/alpha/solver/DefaultSolver.java @@ -29,9 +29,7 @@ import at.ac.tuwien.kr.alpha.common.AnswerSet; import at.ac.tuwien.kr.alpha.common.AtomStore; -import at.ac.tuwien.kr.alpha.common.BasicAnswerSet; import at.ac.tuwien.kr.alpha.common.NoGood; -import at.ac.tuwien.kr.alpha.common.WeightedAnswerSet; import at.ac.tuwien.kr.alpha.common.atoms.Atom; import at.ac.tuwien.kr.alpha.common.atoms.BasicAtom; import at.ac.tuwien.kr.alpha.common.atoms.ComparisonAtom; @@ -49,7 +47,6 @@ import at.ac.tuwien.kr.alpha.solver.heuristics.HeuristicsConfiguration; import at.ac.tuwien.kr.alpha.solver.heuristics.NaiveHeuristic; import at.ac.tuwien.kr.alpha.solver.learning.GroundConflictNoGoodLearner; -import at.ac.tuwien.kr.alpha.solver.optimization.WeakConstraintsManager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -85,8 +82,7 @@ public class DefaultSolver extends AbstractSolver implements SolverMaintainingSt private final NoGoodStore store; private final ChoiceManager choiceManager; - private final WeakConstraintsManager weakConstraintsManager; - private final WritableAssignment assignment; + protected final WritableAssignment assignment; private final GroundConflictNoGoodLearner learner; private final BranchingHeuristic branchingHeuristic; @@ -95,7 +91,7 @@ public class DefaultSolver extends AbstractSolver implements SolverMaintainingSt private final boolean disableJustifications; private boolean disableJustificationAfterClosing = true; // Keep disabled for now, case not fully worked out yet. private final boolean disableNoGoodDeletion; - private static class SearchState { + protected static class SearchState { boolean hasBeenInitialized; boolean isSearchSpaceCompletelyExplored; /** @@ -103,25 +99,20 @@ private static class SearchState { */ boolean afterAllAtomsAssigned; } - private final SearchState searchState = new SearchState(); - private final boolean enableBranchAndBoundOptimization; + protected final SearchState searchState = new SearchState(); - private final PerformanceLog performanceLog; + protected final PerformanceLog performanceLog; public DefaultSolver(AtomStore atomStore, Grounder grounder, NoGoodStore store, WritableAssignment assignment, Random random, SystemConfig config, HeuristicsConfiguration heuristicsConfiguration) { super(atomStore, grounder); - this.assignment = assignment; this.store = store; this.choiceManager = new ChoiceManager(assignment, store); this.choiceManager.setChecksEnabled(config.isDebugInternalChecks()); - this.weakConstraintsManager = new WeakConstraintsManager(assignment); - this.weakConstraintsManager.setChecksEnabled(config.isDebugInternalChecks()); this.learner = new GroundConflictNoGoodLearner(assignment, atomStore); this.branchingHeuristic = chainFallbackHeuristic(grounder, assignment, random, heuristicsConfiguration); this.disableJustifications = config.isDisableJustificationSearch(); this.disableNoGoodDeletion = config.isDisableNoGoodDeletion(); - this.enableBranchAndBoundOptimization = grounder.inputProgramContainsWeakConstraints(); this.performanceLog = new PerformanceLog(choiceManager, (TrailAssignment) assignment, 1000); } @@ -155,15 +146,6 @@ protected boolean tryAdvance(Consumer action) { if (conflictCause != null) { LOGGER.debug("Conflict encountered, analyzing conflict."); learnFromConflict(conflictCause); - } else if (enableBranchAndBoundOptimization && !weakConstraintsManager.isCurrentBetterThanBest()) { - // The solver is searching for optimal answer sets and the current assignment is worse than a previously found answer set. - NoGood excludingNoGood = weakConstraintsManager.generateExcludingNoGood(); - Map obtained = new LinkedHashMap<>(); - obtained.put(grounder.register(excludingNoGood), excludingNoGood); - if (!ingest(obtained)) { - logStats(); - return false; - } } else if (assignment.didChange()) { LOGGER.debug("Updating grounder with new assignments and (potentially) obtaining new NoGoods."); grounder.updateAssignment(assignment.getNewPositiveAssignmentsIterator()); @@ -181,14 +163,14 @@ protected boolean tryAdvance(Consumer action) { } } - private void initializeSearch() { + protected void initializeSearch() { // Initially, get NoGoods from grounder. performanceLog.initialize(); getNoGoodsFromGrounderAndIngest(); searchState.hasBeenInitialized = true; } - private void prepareForSubsequentAnswerSet() { + protected void prepareForSubsequentAnswerSet() { // We already found one Answer-Set and are requested to find another one. searchState.afterAllAtomsAssigned = false; if (assignment.getDecisionLevel() == 0) { @@ -215,14 +197,14 @@ private void prepareForSubsequentAnswerSet() { } } - private void getNoGoodsFromGrounderAndIngest() { + protected void getNoGoodsFromGrounderAndIngest() { Map obtained = grounder.getNoGoods(assignment); if (!ingest(obtained)) { searchState.isSearchSpaceCompletelyExplored = true; } } - private void learnFromConflict(ConflictCause conflictCause) { + protected void learnFromConflict(ConflictCause conflictCause) { LOGGER.debug("Violating assignment is: {}", assignment); Antecedent conflictAntecedent = conflictCause.getAntecedent(); NoGood violatedNoGood = new NoGood(conflictAntecedent.getReasonLiterals().clone()); @@ -242,7 +224,7 @@ private void learnFromConflict(ConflictCause conflictCause) { } } - private ConflictCause propagate() { + protected ConflictCause propagate() { LOGGER.trace("Doing propagation step."); ConflictCause conflictCause = store.propagate(); LOGGER.trace("Assignment after propagation is: {}", assignment); @@ -254,19 +236,13 @@ private ConflictCause propagate() { } private void provideAnswerSet(Consumer action) { - // NOTE: If we would do optimization, we would now have a guaranteed upper bound. AnswerSet as = translate(assignment.getTrueAssignments()); - if (enableBranchAndBoundOptimization) { - // Enrich answer-set with weights information, record current-best upper bound. - weakConstraintsManager.markCurrentWeightAsBestKnown(); - as = new WeightedAnswerSet((BasicAnswerSet)as, weakConstraintsManager.getCurrentWeightAtLevels()); - } LOGGER.debug("Answer-Set found: {}", as); action.accept(as); logStats(); } - private void backtrackFromMBTsRemaining() { + protected void backtrackFromMBTsRemaining() { LOGGER.debug("Backtracking from wrong choices ({} MBTs).", assignment.getMBTCount()); searchState.afterAllAtomsAssigned = false; if (!justifyMbtAndBacktrack()) { @@ -443,7 +419,7 @@ private boolean treatConflictAfterClosing(Antecedent violatedNoGood) { return true; } - private boolean close() { + protected boolean close() { searchState.afterAllAtomsAssigned = true; return assignment.closeUnassignedAtoms(); } @@ -481,18 +457,10 @@ private boolean backtrack() { return assignment.getDecisionLevel() != 0; } - private boolean ingest(Map obtained) { - assignment.growForMaxAtomId(); - int maxAtomId = atomStore.getMaxAtomId(); - store.growForMaxAtomId(maxAtomId); - choiceManager.growForMaxAtomId(maxAtomId); - branchingHeuristic.growForMaxAtomId(maxAtomId); + protected boolean ingest(Map obtained) { + growForMaxAtomId(); branchingHeuristic.newNoGoods(obtained.values()); - if (enableBranchAndBoundOptimization) { - weakConstraintsManager.addWeakConstraintsInformation(grounder.getWeakConstraintInformation()); - } - LinkedList> noGoodsToAdd = new LinkedList<>(obtained.entrySet()); Map.Entry entry; while ((entry = noGoodsToAdd.poll()) != null) { @@ -509,6 +477,14 @@ private boolean ingest(Map obtained) { return true; } + protected void growForMaxAtomId() { + assignment.growForMaxAtomId(); + int maxAtomId = atomStore.getMaxAtomId(); + store.growForMaxAtomId(maxAtomId); + choiceManager.growForMaxAtomId(maxAtomId); + branchingHeuristic.growForMaxAtomId(maxAtomId); + } + /** * Attempts to fix a given conflict that arose from adding a nogood. * @param noGoodEntry the description of the NoGood that caused the conflict. @@ -536,7 +512,7 @@ private boolean fixContradiction(Map.Entry noGoodEntry, Conflic return addAndBackjumpIfNecessary(noGoodEntry.getKey(), noGoodEntry.getValue(), LBD_NO_VALUE); } - private boolean choose() { + protected boolean choose() { choiceManager.addChoiceInformation(grounder.getChoiceAtoms(), grounder.getHeadsToBodies()); choiceManager.updateAssignments(); @@ -596,7 +572,7 @@ public NoGoodCounter getNoGoodCounter() { return store.getNoGoodCounter(); } - private void logStats() { + protected void logStats() { if (LOGGER.isDebugEnabled()) { LOGGER.debug(getStatisticsString()); if (branchingHeuristic instanceof ChainedBranchingHeuristics) { diff --git a/src/main/java/at/ac/tuwien/kr/alpha/solver/OptimizingSolver.java b/src/main/java/at/ac/tuwien/kr/alpha/solver/OptimizingSolver.java new file mode 100644 index 000000000..5759e2109 --- /dev/null +++ b/src/main/java/at/ac/tuwien/kr/alpha/solver/OptimizingSolver.java @@ -0,0 +1,96 @@ +package at.ac.tuwien.kr.alpha.solver; + +import at.ac.tuwien.kr.alpha.common.AnswerSet; +import at.ac.tuwien.kr.alpha.common.AtomStore; +import at.ac.tuwien.kr.alpha.common.NoGood; +import at.ac.tuwien.kr.alpha.common.WeightedAnswerSet; +import at.ac.tuwien.kr.alpha.config.SystemConfig; +import at.ac.tuwien.kr.alpha.grounder.Grounder; +import at.ac.tuwien.kr.alpha.solver.heuristics.HeuristicsConfiguration; +import at.ac.tuwien.kr.alpha.solver.optimization.WeakConstraintsManager; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Random; +import java.util.function.Consumer; + +/** + * Copyright (c) 2021, the Alpha Team. + */ +public class OptimizingSolver extends DefaultSolver { + private static final Logger LOGGER = LoggerFactory.getLogger(OptimizingSolver.class); + private final WeakConstraintsManager weakConstraintsManager; + + public OptimizingSolver(AtomStore atomStore, Grounder grounder, NoGoodStore store, WritableAssignment assignment, Random random, SystemConfig config, HeuristicsConfiguration heuristicsConfiguration) { + super(atomStore, grounder, store, assignment, random, config, heuristicsConfiguration); + this.weakConstraintsManager = new WeakConstraintsManager(assignment); + this.weakConstraintsManager.setChecksEnabled(config.isDebugInternalChecks()); + } + + @Override + protected boolean tryAdvance(Consumer action) { + if (!searchState.hasBeenInitialized) { + initializeSearch(); + } else { + prepareForSubsequentAnswerSet(); + } + // Try all assignments until grounder reports no more NoGoods and all of them are satisfied + while (true) { + performanceLog.writeIfTimeForLogging(LOGGER); + if (searchState.isSearchSpaceCompletelyExplored) { + LOGGER.debug("Search space has been fully explored, there are no more answer-sets."); + logStats(); + return false; + } + ConflictCause conflictCause = propagate(); + if (conflictCause != null) { + LOGGER.debug("Conflict encountered, analyzing conflict."); + learnFromConflict(conflictCause); + } else if (!weakConstraintsManager.isCurrentBetterThanBest()) { + LOGGER.debug("Current assignment is worse than previously found answer set, backjumping now."); + backtrackFromBound(); + } else if (assignment.didChange()) { + LOGGER.debug("Updating grounder with new assignments and (potentially) obtaining new NoGoods."); + grounder.updateAssignment(assignment.getNewPositiveAssignmentsIterator()); + getNoGoodsFromGrounderAndIngest(); + } else if (choose()) { + LOGGER.debug("Did choice."); + } else if (close()) { + LOGGER.debug("Closed unassigned known atoms (assigning FALSE)."); + } else if (assignment.getMBTCount() == 0) { + provideAnswerSet(action); + return true; + } else { + backtrackFromMBTsRemaining(); + } + } + } + + private void backtrackFromBound() { + NoGood excludingNoGood = weakConstraintsManager.generateExcludingNoGood(); + Map obtained = new LinkedHashMap<>(); + obtained.put(grounder.register(excludingNoGood), excludingNoGood); + if (!ingest(obtained)) { + searchState.isSearchSpaceCompletelyExplored = true; + } + } + + private void provideAnswerSet(Consumer action) { + // Enrich answer-set with weights information and record current-best upper bound. + AnswerSet as = translate(assignment.getTrueAssignments()); + weakConstraintsManager.markCurrentWeightAsBestKnown(); + WeightedAnswerSet was = new WeightedAnswerSet(as, weakConstraintsManager.getCurrentWeightAtLevels()); + LOGGER.debug("Answer-Set found: {}", was); + action.accept(was); + logStats(); + } + + @Override + protected boolean ingest(Map obtained) { + growForMaxAtomId(); + weakConstraintsManager.addWeakConstraintsInformation(grounder.getWeakConstraintInformation()); + return super.ingest(obtained); + } +} diff --git a/src/main/java/at/ac/tuwien/kr/alpha/solver/SolverFactory.java b/src/main/java/at/ac/tuwien/kr/alpha/solver/SolverFactory.java index 414d30c31..eb6536f37 100644 --- a/src/main/java/at/ac/tuwien/kr/alpha/solver/SolverFactory.java +++ b/src/main/java/at/ac/tuwien/kr/alpha/solver/SolverFactory.java @@ -62,7 +62,11 @@ public static Solver getInstance(SystemConfig config, AtomStore atomStore, Groun case "naive" : return new NaiveSolver(atomStore, grounder); case "default": - return new DefaultSolver(atomStore, grounder, store, assignment, random, config, heuristicsConfiguration); + if (grounder.inputProgramContainsWeakConstraints()) { + return new OptimizingSolver(atomStore, grounder, store, assignment, random, config, heuristicsConfiguration); + } else { + return new DefaultSolver(atomStore, grounder, store, assignment, random, config, heuristicsConfiguration); + } } throw new IllegalArgumentException("Unknown solver requested."); } diff --git a/src/main/java/at/ac/tuwien/kr/alpha/solver/optimization/WeakConstraintsManager.java b/src/main/java/at/ac/tuwien/kr/alpha/solver/optimization/WeakConstraintsManager.java index 6e3d58232..c8a256239 100644 --- a/src/main/java/at/ac/tuwien/kr/alpha/solver/optimization/WeakConstraintsManager.java +++ b/src/main/java/at/ac/tuwien/kr/alpha/solver/optimization/WeakConstraintsManager.java @@ -109,7 +109,7 @@ public void processCallback(WeakConstraintAtomCallback atomCallback) { private void increaseWeight(WeakConstraintAtomCallback atomCallback) { if (weightAtLevelsManager.getMaxLevel() < atomCallback.level) { - LOGGER.info("Adding higher level than possible with weightAtLevelsManager: level={} and maxLevel={}, callback is: {}.", atomCallback.level, weightAtLevelsManager.getMaxLevel(), atomCallback); + LOGGER.trace("Adding higher level than possible with weightAtLevelsManager: level={} and maxLevel={}, callback is: {}.", atomCallback.level, weightAtLevelsManager.getMaxLevel(), atomCallback); aboveMaxLevelTrueAtoms.add(atomCallback); } else { weightAtLevelsManager.increaseCurrentWeight(atomCallback.level, atomCallback.weight); @@ -118,7 +118,7 @@ private void increaseWeight(WeakConstraintAtomCallback atomCallback) { private void decreaseWeight(WeakConstraintAtomCallback atomCallback) { if (weightAtLevelsManager.getMaxLevel() < atomCallback.level) { - LOGGER.info("Removing higher level than possible with weightAtLevelsManager: level={} and maxLevel={}, callback is: {}.", atomCallback.level, weightAtLevelsManager.getMaxLevel(), atomCallback); + LOGGER.trace("Removing higher level than possible with weightAtLevelsManager: level={} and maxLevel={}, callback is: {}.", atomCallback.level, weightAtLevelsManager.getMaxLevel(), atomCallback); aboveMaxLevelTrueAtoms.remove(atomCallback); } else { weightAtLevelsManager.decreaseCurrentWeight(atomCallback.level, atomCallback.weight); diff --git a/src/test/java/at/ac/tuwien/kr/alpha/solver/WeakConstraintsTests.java b/src/test/java/at/ac/tuwien/kr/alpha/solver/WeakConstraintsTests.java index 19d495c4a..7687871be 100644 --- a/src/test/java/at/ac/tuwien/kr/alpha/solver/WeakConstraintsTests.java +++ b/src/test/java/at/ac/tuwien/kr/alpha/solver/WeakConstraintsTests.java @@ -112,7 +112,6 @@ public void complexValuationWithMultipleWeightsOnMultipleLevels(RegressionTestCo ":~ weightatlevel(W,L).[W@L]" + "has_wal :- weightatlevel(W,L)." + ":- not has_wal."; - System.out.println(program); Set actualAnswerSets = collectRegressionTestAnswerSets(program, cfg); assertOptimumAnswerSetEquals( "dom(1), dom(2), dom(3), c(1), b(1), a(1), weightatlevel(3,1), has_wal", "3@1", actualAnswerSets); From 5c2b5a970478bdaa9de56e40edac8653cdedb76e Mon Sep 17 00:00:00 2001 From: Antonius Weinzierl Date: Fri, 8 Oct 2021 02:27:18 +0200 Subject: [PATCH 21/27] Add some javadoc to OptimizingSolver. --- .../java/at/ac/tuwien/kr/alpha/solver/OptimizingSolver.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/java/at/ac/tuwien/kr/alpha/solver/OptimizingSolver.java b/src/main/java/at/ac/tuwien/kr/alpha/solver/OptimizingSolver.java index 5759e2109..1f1dcc611 100644 --- a/src/main/java/at/ac/tuwien/kr/alpha/solver/OptimizingSolver.java +++ b/src/main/java/at/ac/tuwien/kr/alpha/solver/OptimizingSolver.java @@ -17,6 +17,9 @@ import java.util.function.Consumer; /** + * A solver providing optimization via weak constraints and a simpla branch-and-bound algorithm. + * The basis of this solver is the {@link DefaultSolver} and its algorithms. + * * Copyright (c) 2021, the Alpha Team. */ public class OptimizingSolver extends DefaultSolver { From ca66d6c2173d0915b566422d65a3683222ffc099 Mon Sep 17 00:00:00 2001 From: Antonius Weinzierl Date: Fri, 6 Jan 2023 01:17:31 +0100 Subject: [PATCH 22/27] Small test improvement. --- .../at/ac/tuwien/kr/alpha/solver/WeakConstraintsTests.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/test/java/at/ac/tuwien/kr/alpha/solver/WeakConstraintsTests.java b/src/test/java/at/ac/tuwien/kr/alpha/solver/WeakConstraintsTests.java index 7687871be..7663da586 100644 --- a/src/test/java/at/ac/tuwien/kr/alpha/solver/WeakConstraintsTests.java +++ b/src/test/java/at/ac/tuwien/kr/alpha/solver/WeakConstraintsTests.java @@ -73,11 +73,12 @@ public void sameWeightSameTermNotSummedUpInLevel(RegressionTestConfig cfg) { TestUtils.ignoreTestForNaiveSolver(cfg); String program = "{a;b}." + ":- not a, not b." + + "c :- a." + ":~b.[1@3]" + ":~a.[2@1,foo]" + - ":~a.[2@1,foo]"; + ":~c.[2@1,foo]"; Set actualAnswerSets = collectRegressionTestAnswerSets(program, cfg); - assertOptimumAnswerSetEquals("a", "2@1", actualAnswerSets); + assertOptimumAnswerSetEquals("a, c", "2@1", actualAnswerSets); } @RegressionTest From 63fadfd8e064005861ae3d0e2b8cdba77cf466a5 Mon Sep 17 00:00:00 2001 From: Antonius Weinzierl Date: Fri, 6 Jan 2023 04:23:25 +0100 Subject: [PATCH 23/27] Move WeakConstraintsManager into DefaultSolver. - OptimizingSolver directly uses WeakConstraintsManager from DefaultSolver. - Small polish on WeakConstraintRecorder. --- .../kr/alpha/grounder/WeakConstraintRecorder.java | 4 ++++ .../at/ac/tuwien/kr/alpha/solver/DefaultSolver.java | 5 +++++ .../ac/tuwien/kr/alpha/solver/OptimizingSolver.java | 11 ----------- 3 files changed, 9 insertions(+), 11 deletions(-) diff --git a/src/main/java/at/ac/tuwien/kr/alpha/grounder/WeakConstraintRecorder.java b/src/main/java/at/ac/tuwien/kr/alpha/grounder/WeakConstraintRecorder.java index 9b16a4cb8..47d81aa7f 100644 --- a/src/main/java/at/ac/tuwien/kr/alpha/grounder/WeakConstraintRecorder.java +++ b/src/main/java/at/ac/tuwien/kr/alpha/grounder/WeakConstraintRecorder.java @@ -4,6 +4,7 @@ import org.apache.commons.lang3.tuple.Triple; import java.util.ArrayList; +import java.util.Collections; import java.util.List; /** @@ -20,6 +21,9 @@ void addWeakConstraint(int headId, Integer weight, Integer level) { } List> getAndResetWeakConstraintAtomWeightLevels() { + if (weakConstraintAtomWeightLevels.isEmpty()) { + return Collections.emptyList(); + } List> currentWeakConstraintAtomWeightLevels = weakConstraintAtomWeightLevels; this.weakConstraintAtomWeightLevels = new ArrayList<>(); return currentWeakConstraintAtomWeightLevels; diff --git a/src/main/java/at/ac/tuwien/kr/alpha/solver/DefaultSolver.java b/src/main/java/at/ac/tuwien/kr/alpha/solver/DefaultSolver.java index 20383ace9..32d1a6b82 100644 --- a/src/main/java/at/ac/tuwien/kr/alpha/solver/DefaultSolver.java +++ b/src/main/java/at/ac/tuwien/kr/alpha/solver/DefaultSolver.java @@ -47,6 +47,7 @@ import at.ac.tuwien.kr.alpha.solver.heuristics.HeuristicsConfiguration; import at.ac.tuwien.kr.alpha.solver.heuristics.NaiveHeuristic; import at.ac.tuwien.kr.alpha.solver.learning.GroundConflictNoGoodLearner; +import at.ac.tuwien.kr.alpha.solver.optimization.WeakConstraintsManager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -85,6 +86,7 @@ public class DefaultSolver extends AbstractSolver implements SolverMaintainingSt protected final WritableAssignment assignment; private final GroundConflictNoGoodLearner learner; private final BranchingHeuristic branchingHeuristic; + protected final WeakConstraintsManager weakConstraintsManager; private int mbtAtFixpoint; private int conflictsAfterClosing; @@ -114,6 +116,8 @@ public DefaultSolver(AtomStore atomStore, Grounder grounder, NoGoodStore store, this.disableJustifications = config.isDisableJustificationSearch(); this.disableNoGoodDeletion = config.isDisableNoGoodDeletion(); this.performanceLog = new PerformanceLog(choiceManager, (TrailAssignment) assignment, 1000); + this.weakConstraintsManager = new WeakConstraintsManager(assignment); + this.weakConstraintsManager.setChecksEnabled(config.isDebugInternalChecks()); } private BranchingHeuristic chainFallbackHeuristic(Grounder grounder, WritableAssignment assignment, Random random, HeuristicsConfiguration heuristicsConfiguration) { @@ -459,6 +463,7 @@ private boolean backtrack() { protected boolean ingest(Map obtained) { growForMaxAtomId(); + weakConstraintsManager.addWeakConstraintsInformation(grounder.getWeakConstraintInformation()); branchingHeuristic.newNoGoods(obtained.values()); LinkedList> noGoodsToAdd = new LinkedList<>(obtained.entrySet()); diff --git a/src/main/java/at/ac/tuwien/kr/alpha/solver/OptimizingSolver.java b/src/main/java/at/ac/tuwien/kr/alpha/solver/OptimizingSolver.java index 1f1dcc611..b3e08df1a 100644 --- a/src/main/java/at/ac/tuwien/kr/alpha/solver/OptimizingSolver.java +++ b/src/main/java/at/ac/tuwien/kr/alpha/solver/OptimizingSolver.java @@ -7,7 +7,6 @@ import at.ac.tuwien.kr.alpha.config.SystemConfig; import at.ac.tuwien.kr.alpha.grounder.Grounder; import at.ac.tuwien.kr.alpha.solver.heuristics.HeuristicsConfiguration; -import at.ac.tuwien.kr.alpha.solver.optimization.WeakConstraintsManager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -24,12 +23,9 @@ */ public class OptimizingSolver extends DefaultSolver { private static final Logger LOGGER = LoggerFactory.getLogger(OptimizingSolver.class); - private final WeakConstraintsManager weakConstraintsManager; public OptimizingSolver(AtomStore atomStore, Grounder grounder, NoGoodStore store, WritableAssignment assignment, Random random, SystemConfig config, HeuristicsConfiguration heuristicsConfiguration) { super(atomStore, grounder, store, assignment, random, config, heuristicsConfiguration); - this.weakConstraintsManager = new WeakConstraintsManager(assignment); - this.weakConstraintsManager.setChecksEnabled(config.isDebugInternalChecks()); } @Override @@ -89,11 +85,4 @@ private void provideAnswerSet(Consumer action) { action.accept(was); logStats(); } - - @Override - protected boolean ingest(Map obtained) { - growForMaxAtomId(); - weakConstraintsManager.addWeakConstraintsInformation(grounder.getWeakConstraintInformation()); - return super.ingest(obtained); - } } From d53653e60623e9fae5870f0c8f5f2d0f352e3123 Mon Sep 17 00:00:00 2001 From: Antonius Weinzierl Date: Tue, 10 Jan 2023 05:04:51 +0100 Subject: [PATCH 24/27] Enable optimization based on command-line, also accept upper bound, and print valuation of answer sets if weak constraints are present. - CommandLineParser and SystemConfig handle options enabling optimization and maximum upper bound. - WeakConstraintsManager interface introduced. - WeakConstraintsManagerForBoundedOptimality for branch-and-bound search extracted. - WeakConstraintsManagerForUnboundedEnumeration for computing weights/levels of answer sets of programs with weak constraints. - WeightAtLevelsManager accepts maximum upper bound from comma-seperated weight@level pairs. - DefaultSolver adds weight/levels to answer sets if weak constraints are present. - OptimizingSolver handles maximum upper bound if specified. - SolverFactory returns OptimizingSolver based on flag, not presence of weak constraints in input. - Added initialization tests to WeightAtLevelsManagerTest. - Added to TestUtils methods for regression testing the OptimizingSolver, and using them in WeakConstraintsTests. --- src/main/java/at/ac/tuwien/kr/alpha/Main.java | 9 +- .../kr/alpha/config/CommandLineParser.java | 25 +- .../tuwien/kr/alpha/config/SystemConfig.java | 31 ++- .../tuwien/kr/alpha/solver/DefaultSolver.java | 10 +- .../kr/alpha/solver/OptimizingSolver.java | 9 +- .../tuwien/kr/alpha/solver/SolverFactory.java | 2 +- .../WeakConstraintAtomCallback.java | 8 +- .../optimization/WeakConstraintsManager.java | 216 +--------------- ...onstraintsManagerForBoundedOptimality.java | 231 ++++++++++++++++++ ...traintsManagerForUnboundedEnumeration.java | 52 ++++ .../optimization/WeightAtLevelsManager.java | 47 ++++ .../kr/alpha/solver/WeakConstraintsTests.java | 18 +- .../WeightAtLevelsManagerTest.java | 27 ++ .../tuwien/kr/alpha/test/util/TestUtils.java | 10 + 14 files changed, 455 insertions(+), 240 deletions(-) create mode 100644 src/main/java/at/ac/tuwien/kr/alpha/solver/optimization/WeakConstraintsManagerForBoundedOptimality.java create mode 100644 src/main/java/at/ac/tuwien/kr/alpha/solver/optimization/WeakConstraintsManagerForUnboundedEnumeration.java diff --git a/src/main/java/at/ac/tuwien/kr/alpha/Main.java b/src/main/java/at/ac/tuwien/kr/alpha/Main.java index 1e8459797..a945783a8 100644 --- a/src/main/java/at/ac/tuwien/kr/alpha/Main.java +++ b/src/main/java/at/ac/tuwien/kr/alpha/Main.java @@ -116,7 +116,7 @@ public static void main(String[] args) { if (cfg.getInputConfig().isWritePreprocessed()) { Main.writeInternalProgram(preprocessed, cfg.getInputConfig().getPreprocessedPath()); } - Main.computeAndConsumeAnswerSets(alpha, cfg.getInputConfig(), preprocessed); + Main.computeAndConsumeAnswerSets(alpha, cfg, preprocessed); } /** @@ -172,7 +172,8 @@ private static void writeInternalProgram(InternalProgram prg, String path) { } } - private static void computeAndConsumeAnswerSets(Alpha alpha, InputConfig inputCfg, InternalProgram program) { + private static void computeAndConsumeAnswerSets(Alpha alpha, AlphaConfig cfg, InternalProgram program) { + InputConfig inputCfg = cfg.getInputConfig(); Solver solver = alpha.prepareSolverFor(program, inputCfg.getFilter()); Stream stream = solver.stream(); if (alpha.getConfig().isSortAnswerSets()) { @@ -190,7 +191,7 @@ private static void computeAndConsumeAnswerSets(Alpha alpha, InputConfig inputCf final AnswerSetFormatter fmt = new SimpleAnswerSetFormatter(alpha.getConfig().getAtomSeparator()); BiConsumer stdoutPrinter = (n, as) -> { System.out.println("Answer set " + n + ":" + System.lineSeparator() + fmt.format(as)); - if (program.containsWeakConstraints()) { + if (as instanceof WeightedAnswerSet) { // If weak constraints are presents, all answer sets are weighted. System.out.println("Optimization: " + ((WeightedAnswerSet) as).getWeightsAsString()); } @@ -216,7 +217,7 @@ private static void computeAndConsumeAnswerSets(Alpha alpha, InputConfig inputCf } } else { System.out.println("SATISFIABLE"); - if (program.containsWeakConstraints() && counter.get() < limit) { + if (cfg.getSystemConfig().isAnswerSetOptimizationEnabled() && counter.get() < limit) { // If less answer sets were found than requested and optimisation is enabled, then the last one is an optimal answer set. System.out.println("OPTIMUM PROVEN"); } diff --git a/src/main/java/at/ac/tuwien/kr/alpha/config/CommandLineParser.java b/src/main/java/at/ac/tuwien/kr/alpha/config/CommandLineParser.java index 53528ff9c..add84fcfa 100644 --- a/src/main/java/at/ac/tuwien/kr/alpha/config/CommandLineParser.java +++ b/src/main/java/at/ac/tuwien/kr/alpha/config/CommandLineParser.java @@ -27,6 +27,9 @@ */ package at.ac.tuwien.kr.alpha.config; +import at.ac.tuwien.kr.alpha.grounder.transformation.aggregates.AggregateRewritingConfig; +import at.ac.tuwien.kr.alpha.solver.BinaryNoGoodPropagationEstimation; +import at.ac.tuwien.kr.alpha.solver.heuristics.BranchingHeuristicFactory.Heuristic; import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.DefaultParser; import org.apache.commons.cli.HelpFormatter; @@ -45,10 +48,6 @@ import java.util.Map; import java.util.function.Consumer; -import at.ac.tuwien.kr.alpha.grounder.transformation.aggregates.AggregateRewritingConfig; -import at.ac.tuwien.kr.alpha.solver.BinaryNoGoodPropagationEstimation; -import at.ac.tuwien.kr.alpha.solver.heuristics.BranchingHeuristicFactory.Heuristic; - /** * Parses given argument lists (as passed when Alpha is called from command line) into {@link SystemConfig}s and * {@link InputConfig}s. @@ -150,6 +149,11 @@ public class CommandLineParser { .desc("a character (sequence) to use as separator for atoms in printed answer sets (default: " + SystemConfig.DEFAULT_ATOM_SEPARATOR + ")") .build(); + private static final Option OPT_ENABLE_OPTIMIZATION = Option.builder("opt").longOpt("enableOptimization") + .desc("enables branch-and-bound answer-set optimization based on the weak constraints of the input program.").build(); + private static final Option OPT_MAXWEIGHT = Option.builder("mw").longOpt("maxWeight").hasArg(true).argName("weight") + .desc("the (weak constraints) valuation of answer sets must be below this weight, only has an effect if answer-set optimization is enabled. " + + "Weights are given as a comma-separated list of weight@level pairs, e.g.: 3@1,2@2,5@-1").build(); //@formatter:on private static final Options CLI_OPTS = new Options(); @@ -192,6 +196,8 @@ public class CommandLineParser { CommandLineParser.CLI_OPTS.addOption(CommandLineParser.OPT_GROUNDER_TOLERANCE_RULES); CommandLineParser.CLI_OPTS.addOption(CommandLineParser.OPT_GROUNDER_ACCUMULATOR_ENABLED); CommandLineParser.CLI_OPTS.addOption(CommandLineParser.OPT_OUTPUT_ATOM_SEPARATOR); + CommandLineParser.CLI_OPTS.addOption(CommandLineParser.OPT_ENABLE_OPTIMIZATION); + CommandLineParser.CLI_OPTS.addOption(CommandLineParser.OPT_MAXWEIGHT); } /* @@ -249,6 +255,8 @@ private void initializeGlobalOptionHandlers() { this.globalOptionHandlers.put(CommandLineParser.OPT_GROUNDER_TOLERANCE_RULES.getOpt(), this::handleGrounderToleranceRules); this.globalOptionHandlers.put(CommandLineParser.OPT_GROUNDER_ACCUMULATOR_ENABLED.getOpt(), this::handleGrounderNoInstanceRemoval); this.globalOptionHandlers.put(CommandLineParser.OPT_OUTPUT_ATOM_SEPARATOR.getOpt(), this::handleAtomSeparator); + this.globalOptionHandlers.put(CommandLineParser.OPT_ENABLE_OPTIMIZATION.getOpt(), this::handleEnableOptimization); + this.globalOptionHandlers.put(CommandLineParser.OPT_MAXWEIGHT.getOpt(), this::handleMaxWeight); } private void initializeInputOptionHandlers() { @@ -489,5 +497,12 @@ private void handleGrounderNoInstanceRemoval(Option opt, SystemConfig cfg) { private void handleAtomSeparator(Option opt, SystemConfig cfg) { cfg.setAtomSeparator(StringEscapeUtils.unescapeJava(opt.getValue(SystemConfig.DEFAULT_ATOM_SEPARATOR))); } - + + private void handleEnableOptimization(Option opt, SystemConfig cfg) { + cfg.setAnswerSetOptimizationEnabled(true); + } + + private void handleMaxWeight(Option opt, SystemConfig cfg) { + cfg.setAnswerSetsMaxWeightAtLevels(opt.getValue(SystemConfig.DEFAULT_MAX_WEIGHT_AT_LEVELS)); + } } diff --git a/src/main/java/at/ac/tuwien/kr/alpha/config/SystemConfig.java b/src/main/java/at/ac/tuwien/kr/alpha/config/SystemConfig.java index ae9173f32..65adddec0 100644 --- a/src/main/java/at/ac/tuwien/kr/alpha/config/SystemConfig.java +++ b/src/main/java/at/ac/tuwien/kr/alpha/config/SystemConfig.java @@ -27,16 +27,16 @@ */ package at.ac.tuwien.kr.alpha.config; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.stream.Collectors; - import at.ac.tuwien.kr.alpha.grounder.heuristics.GrounderHeuristicsConfiguration; import at.ac.tuwien.kr.alpha.grounder.transformation.aggregates.AggregateRewritingConfig; import at.ac.tuwien.kr.alpha.solver.BinaryNoGoodPropagationEstimation; import at.ac.tuwien.kr.alpha.solver.heuristics.BranchingHeuristicFactory.Heuristic; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + public class SystemConfig { // Note: Defining constants for default values here rather than just @@ -63,6 +63,8 @@ public class SystemConfig { public static final boolean DEFAULT_GROUNDER_ACCUMULATOR_ENABLED = false; public static final String DEFAULT_ATOM_SEPARATOR = ", "; public static final AggregateRewritingConfig DEFAULT_AGGREGATE_REWRITING_CONFIG = new AggregateRewritingConfig(); + public static final boolean DEFAULT_OPTIMIZATION_ENABLED = false; + public static final String DEFAULT_MAX_WEIGHT_AT_LEVELS = ""; private String grounderName = DEFAULT_GROUNDER_NAME; private String solverName = DEFAULT_SOLVER_NAME; @@ -84,6 +86,8 @@ public class SystemConfig { private boolean grounderAccumulatorEnabled = DEFAULT_GROUNDER_ACCUMULATOR_ENABLED; private String atomSeparator = DEFAULT_ATOM_SEPARATOR; private AggregateRewritingConfig aggregateRewritingConfig = DEFAULT_AGGREGATE_REWRITING_CONFIG; + private boolean answerSetOptimizationEnabled = DEFAULT_OPTIMIZATION_ENABLED; + private String answerSetsMaxWeightAtLevels = DEFAULT_MAX_WEIGHT_AT_LEVELS; public String getGrounderName() { return this.grounderName; @@ -256,5 +260,20 @@ public AggregateRewritingConfig getAggregateRewritingConfig() { public void setAggregateRewritingConfig(AggregateRewritingConfig aggregateRewritingConfig) { this.aggregateRewritingConfig = aggregateRewritingConfig; } - + + public boolean isAnswerSetOptimizationEnabled() { + return answerSetOptimizationEnabled; + } + + public void setAnswerSetOptimizationEnabled(boolean answerSetOptimizationEnabled) { + this.answerSetOptimizationEnabled = answerSetOptimizationEnabled; + } + + public String getAnswerSetsMaxWeightAtLevels() { + return answerSetsMaxWeightAtLevels; + } + + public void setAnswerSetsMaxWeightAtLevels(String answerSetsMaxWeightAtLevels) { + this.answerSetsMaxWeightAtLevels = answerSetsMaxWeightAtLevels; + } } diff --git a/src/main/java/at/ac/tuwien/kr/alpha/solver/DefaultSolver.java b/src/main/java/at/ac/tuwien/kr/alpha/solver/DefaultSolver.java index 32d1a6b82..39b0249d3 100644 --- a/src/main/java/at/ac/tuwien/kr/alpha/solver/DefaultSolver.java +++ b/src/main/java/at/ac/tuwien/kr/alpha/solver/DefaultSolver.java @@ -30,6 +30,7 @@ import at.ac.tuwien.kr.alpha.common.AnswerSet; import at.ac.tuwien.kr.alpha.common.AtomStore; import at.ac.tuwien.kr.alpha.common.NoGood; +import at.ac.tuwien.kr.alpha.common.WeightedAnswerSet; import at.ac.tuwien.kr.alpha.common.atoms.Atom; import at.ac.tuwien.kr.alpha.common.atoms.BasicAtom; import at.ac.tuwien.kr.alpha.common.atoms.ComparisonAtom; @@ -48,6 +49,7 @@ import at.ac.tuwien.kr.alpha.solver.heuristics.NaiveHeuristic; import at.ac.tuwien.kr.alpha.solver.learning.GroundConflictNoGoodLearner; import at.ac.tuwien.kr.alpha.solver.optimization.WeakConstraintsManager; +import at.ac.tuwien.kr.alpha.solver.optimization.WeakConstraintsManagerForUnboundedEnumeration; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -86,7 +88,7 @@ public class DefaultSolver extends AbstractSolver implements SolverMaintainingSt protected final WritableAssignment assignment; private final GroundConflictNoGoodLearner learner; private final BranchingHeuristic branchingHeuristic; - protected final WeakConstraintsManager weakConstraintsManager; + protected WeakConstraintsManager weakConstraintsManager; private int mbtAtFixpoint; private int conflictsAfterClosing; @@ -116,7 +118,7 @@ public DefaultSolver(AtomStore atomStore, Grounder grounder, NoGoodStore store, this.disableJustifications = config.isDisableJustificationSearch(); this.disableNoGoodDeletion = config.isDisableNoGoodDeletion(); this.performanceLog = new PerformanceLog(choiceManager, (TrailAssignment) assignment, 1000); - this.weakConstraintsManager = new WeakConstraintsManager(assignment); + this.weakConstraintsManager = new WeakConstraintsManagerForUnboundedEnumeration(assignment); this.weakConstraintsManager.setChecksEnabled(config.isDebugInternalChecks()); } @@ -241,6 +243,10 @@ protected ConflictCause propagate() { private void provideAnswerSet(Consumer action) { AnswerSet as = translate(assignment.getTrueAssignments()); + if (grounder.inputProgramContainsWeakConstraints()) { + // Adorn AnswerSet with weights if weak constraints are contained in the input program. + as = new WeightedAnswerSet(as, weakConstraintsManager.getCurrentWeightAtLevels()); + } LOGGER.debug("Answer-Set found: {}", as); action.accept(as); logStats(); diff --git a/src/main/java/at/ac/tuwien/kr/alpha/solver/OptimizingSolver.java b/src/main/java/at/ac/tuwien/kr/alpha/solver/OptimizingSolver.java index b3e08df1a..88283d2b5 100644 --- a/src/main/java/at/ac/tuwien/kr/alpha/solver/OptimizingSolver.java +++ b/src/main/java/at/ac/tuwien/kr/alpha/solver/OptimizingSolver.java @@ -7,6 +7,7 @@ import at.ac.tuwien.kr.alpha.config.SystemConfig; import at.ac.tuwien.kr.alpha.grounder.Grounder; import at.ac.tuwien.kr.alpha.solver.heuristics.HeuristicsConfiguration; +import at.ac.tuwien.kr.alpha.solver.optimization.WeakConstraintsManagerForBoundedOptimality; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -26,6 +27,8 @@ public class OptimizingSolver extends DefaultSolver { public OptimizingSolver(AtomStore atomStore, Grounder grounder, NoGoodStore store, WritableAssignment assignment, Random random, SystemConfig config, HeuristicsConfiguration heuristicsConfiguration) { super(atomStore, grounder, store, assignment, random, config, heuristicsConfiguration); + this.weakConstraintsManager = new WeakConstraintsManagerForBoundedOptimality(assignment, config.getAnswerSetsMaxWeightAtLevels()); + this.weakConstraintsManager.setChecksEnabled(config.isDebugInternalChecks()); } @Override @@ -47,7 +50,7 @@ protected boolean tryAdvance(Consumer action) { if (conflictCause != null) { LOGGER.debug("Conflict encountered, analyzing conflict."); learnFromConflict(conflictCause); - } else if (!weakConstraintsManager.isCurrentBetterThanBest()) { + } else if (!((WeakConstraintsManagerForBoundedOptimality)weakConstraintsManager).isCurrentBetterThanBest()) { LOGGER.debug("Current assignment is worse than previously found answer set, backjumping now."); backtrackFromBound(); } else if (assignment.didChange()) { @@ -68,7 +71,7 @@ protected boolean tryAdvance(Consumer action) { } private void backtrackFromBound() { - NoGood excludingNoGood = weakConstraintsManager.generateExcludingNoGood(); + NoGood excludingNoGood = ((WeakConstraintsManagerForBoundedOptimality)weakConstraintsManager).generateExcludingNoGood(); Map obtained = new LinkedHashMap<>(); obtained.put(grounder.register(excludingNoGood), excludingNoGood); if (!ingest(obtained)) { @@ -79,7 +82,7 @@ private void backtrackFromBound() { private void provideAnswerSet(Consumer action) { // Enrich answer-set with weights information and record current-best upper bound. AnswerSet as = translate(assignment.getTrueAssignments()); - weakConstraintsManager.markCurrentWeightAsBestKnown(); + ((WeakConstraintsManagerForBoundedOptimality)weakConstraintsManager).markCurrentWeightAsBestKnown(); WeightedAnswerSet was = new WeightedAnswerSet(as, weakConstraintsManager.getCurrentWeightAtLevels()); LOGGER.debug("Answer-Set found: {}", was); action.accept(was); diff --git a/src/main/java/at/ac/tuwien/kr/alpha/solver/SolverFactory.java b/src/main/java/at/ac/tuwien/kr/alpha/solver/SolverFactory.java index eb6536f37..acb5f5727 100644 --- a/src/main/java/at/ac/tuwien/kr/alpha/solver/SolverFactory.java +++ b/src/main/java/at/ac/tuwien/kr/alpha/solver/SolverFactory.java @@ -62,7 +62,7 @@ public static Solver getInstance(SystemConfig config, AtomStore atomStore, Groun case "naive" : return new NaiveSolver(atomStore, grounder); case "default": - if (grounder.inputProgramContainsWeakConstraints()) { + if (config.isAnswerSetOptimizationEnabled()) { return new OptimizingSolver(atomStore, grounder, store, assignment, random, config, heuristicsConfiguration); } else { return new DefaultSolver(atomStore, grounder, store, assignment, random, config, heuristicsConfiguration); diff --git a/src/main/java/at/ac/tuwien/kr/alpha/solver/optimization/WeakConstraintAtomCallback.java b/src/main/java/at/ac/tuwien/kr/alpha/solver/optimization/WeakConstraintAtomCallback.java index 41e1d75eb..088a726fd 100644 --- a/src/main/java/at/ac/tuwien/kr/alpha/solver/optimization/WeakConstraintAtomCallback.java +++ b/src/main/java/at/ac/tuwien/kr/alpha/solver/optimization/WeakConstraintAtomCallback.java @@ -11,22 +11,22 @@ * Copyright (c) 2021, the Alpha Team. */ public class WeakConstraintAtomCallback implements AtomCallbackManager.AtomCallback { - private final WeakConstraintsManager weakConstraintsManager; + private final WeakConstraintsManagerForBoundedOptimality weakConstraintsManagerForBoundedOptimality; public final int atom; public final int weight; public final int level; public ThriceTruth lastTruthValue; - WeakConstraintAtomCallback(WeakConstraintsManager weakConstraintsManager, int atom, int weight, int level) { + WeakConstraintAtomCallback(WeakConstraintsManagerForBoundedOptimality weakConstraintsManager, int atom, int weight, int level) { this.atom = atom; this.weight = weight; this.level = level; - this.weakConstraintsManager = weakConstraintsManager; + this.weakConstraintsManagerForBoundedOptimality = weakConstraintsManager; } @Override public void processCallback() { - weakConstraintsManager.processCallback(this); + weakConstraintsManagerForBoundedOptimality.processCallback(this); } @Override diff --git a/src/main/java/at/ac/tuwien/kr/alpha/solver/optimization/WeakConstraintsManager.java b/src/main/java/at/ac/tuwien/kr/alpha/solver/optimization/WeakConstraintsManager.java index c8a256239..c4d6dc32c 100644 --- a/src/main/java/at/ac/tuwien/kr/alpha/solver/optimization/WeakConstraintsManager.java +++ b/src/main/java/at/ac/tuwien/kr/alpha/solver/optimization/WeakConstraintsManager.java @@ -1,224 +1,28 @@ package at.ac.tuwien.kr.alpha.solver.optimization; -import at.ac.tuwien.kr.alpha.common.NoGood; import at.ac.tuwien.kr.alpha.solver.Checkable; -import at.ac.tuwien.kr.alpha.solver.ThriceTruth; -import at.ac.tuwien.kr.alpha.solver.WritableAssignment; import org.apache.commons.lang3.tuple.Triple; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashSet; import java.util.List; import java.util.TreeMap; -import java.util.stream.Collectors; - -import static at.ac.tuwien.kr.alpha.Util.oops; -import static at.ac.tuwien.kr.alpha.common.Literals.atomToLiteral; -import static at.ac.tuwien.kr.alpha.solver.ThriceTruth.MBT; /** - * Manages weak constraints: stores the value of the current partial assignment, the best known value of any answer-set, - * and handles callbacks/computations when certain atoms (that represent weak constraints) change their truth value. - * - * Copyright (c) 2020-2021, the Alpha Team. + * A {@link WeakConstraintsManager} enables the solver to compute the valuation of answer-sets if weak constraints are + * present in the input program. + * Copyright (c) 2023, the Alpha Team. */ -public class WeakConstraintsManager implements Checkable { - private static final Logger LOGGER = LoggerFactory.getLogger(WeakConstraintsManager.class); - - final WritableAssignment assignment; - final WeightAtLevelsManager weightAtLevelsManager; - private final ArrayList knownCallbackAtoms; - private final ArrayList knownAtomCallbacksForFirstAnswerSet = new ArrayList<>(); - private boolean foundFirstAnswerSet; - private final HashSet aboveMaxLevelTrueAtoms = new HashSet<>(); - - public WeakConstraintsManager(WritableAssignment assignment) { - this.assignment = assignment; - this.weightAtLevelsManager = new WeightAtLevelsManager(); - this.knownCallbackAtoms = new ArrayList<>(); - } +public interface WeakConstraintsManager extends Checkable { /** - * Registers newly obtained weak constraint atoms for callback at the assignment. - * @param weakConstraintAtomWeightLevels a set of triples (a,b,c) representing weak constraint atoms, where a is + * Registers newly obtained weak constraint atoms to be respected by the {@link WeakConstraintsManager}. + * @param weakConstraintAtomWeightLevels a list of triples (a,b,c) representing weak constraint atoms, where a is * the atom, b the weight and c the level of the respective weak constraint. */ - public void addWeakConstraintsInformation(List> weakConstraintAtomWeightLevels) { - for (Triple weakConstraintAtomWeightLevel : weakConstraintAtomWeightLevels) { - if (weakConstraintAtomWeightLevel.getMiddle() == 0) { - // Skip weak constraints with weight 0 entirely. - continue; - } - WeakConstraintAtomCallback wcA = new WeakConstraintAtomCallback(this, weakConstraintAtomWeightLevel.getLeft(), weakConstraintAtomWeightLevel.getMiddle(), weakConstraintAtomWeightLevel.getRight()); - assignment.registerCallbackOnChange(wcA.atom); - assignment.getAtomCallbackManager().recordCallback(wcA.atom, wcA); - knownCallbackAtoms.add(wcA.atom); - if (assignment.getTruth(wcA.atom) != null) { - throw oops("Adding weak constraints information and atom callback already has a truth value assigned."); - } - if (!foundFirstAnswerSet) { - knownAtomCallbacksForFirstAnswerSet.add(wcA); - } - } - } - - /** - * Process the the changed truth value of an atom representing a weak constraint. - * - * @param atomCallback the {@link WeakConstraintAtomCallback} whose assigned truth value did change. - */ - public void processCallback(WeakConstraintAtomCallback atomCallback) { - int atom = atomCallback.atom; - int level = atomCallback.level; - int weight = atomCallback.weight; - ThriceTruth lastTruthValue = atomCallback.lastTruthValue; - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("Processing callback for atom {} with weight at level {}@{}, last truth value was {}.", atom, weight, level, lastTruthValue); - LOGGER.trace("Current atom truth value is: {}", assignment.getTruth(atom)); - } - ThriceTruth currentAtomTruth = assignment.getTruth(atom); - if (!foundFirstAnswerSet) { - // Record old truth value and return if no answer set has been found yet. - atomCallback.lastTruthValue = currentAtomTruth; - LOGGER.trace("End processing callback as no first answer set has been found."); - return; - } - LOGGER.trace("Current truth value is: {}", currentAtomTruth); - atomCallback.lastTruthValue = currentAtomTruth; - if (lastTruthValue == null) { - // Change from unassigned to some truth value. - if (currentAtomTruth == MBT) { - increaseWeight(atomCallback); - } - // Note: for assignment to FALSE or MBT->TRUE, no change needed. - } else { - // Decrease if change from TRUE/MBT to unassigned. - if (lastTruthValue.toBoolean() && currentAtomTruth == null) { - decreaseWeight(atomCallback); - } - // Note: for backtracking from TRUE to MBT no change is needed. - if (currentAtomTruth != null && lastTruthValue.toBoolean() && !currentAtomTruth.toBoolean()) { - throw oops("Unexpected case of weak constraint atom directly changing truth value from MBT/TRUE to FALSE encountered. Atom/LastTruth/CurrentTruth: " + atom + "/" + lastTruthValue + "/" + currentAtomTruth); - } - } - LOGGER.trace("End Processing callback for atom {} with weight at level {}@{}, last truth value was {}, current is {}.", atom, weight, level, lastTruthValue, currentAtomTruth); - } - - private void increaseWeight(WeakConstraintAtomCallback atomCallback) { - if (weightAtLevelsManager.getMaxLevel() < atomCallback.level) { - LOGGER.trace("Adding higher level than possible with weightAtLevelsManager: level={} and maxLevel={}, callback is: {}.", atomCallback.level, weightAtLevelsManager.getMaxLevel(), atomCallback); - aboveMaxLevelTrueAtoms.add(atomCallback); - } else { - weightAtLevelsManager.increaseCurrentWeight(atomCallback.level, atomCallback.weight); - } - } - - private void decreaseWeight(WeakConstraintAtomCallback atomCallback) { - if (weightAtLevelsManager.getMaxLevel() < atomCallback.level) { - LOGGER.trace("Removing higher level than possible with weightAtLevelsManager: level={} and maxLevel={}, callback is: {}.", atomCallback.level, weightAtLevelsManager.getMaxLevel(), atomCallback); - aboveMaxLevelTrueAtoms.remove(atomCallback); - } else { - weightAtLevelsManager.decreaseCurrentWeight(atomCallback.level, atomCallback.weight); - } - } - - /** - * Returns whether the current partial interpretation is already worse (or equal) than the best-known answer-set. - * @return true if the current partial interpretation has worse (or equal) weight than the best-known answer-set. - */ - public boolean isCurrentBetterThanBest() { - if (!foundFirstAnswerSet) { - return true; - } - boolean isCurrentBetterThanBest = aboveMaxLevelTrueAtoms.isEmpty() && weightAtLevelsManager.isCurrentBetterThanBest(); - LOGGER.trace("Is current better than best? {}", isCurrentBetterThanBest); - return isCurrentBetterThanBest; - } - - /** - * Mark the current weight as being the best of all currently known answer-sets. - */ - public void markCurrentWeightAsBestKnown() { - if (!foundFirstAnswerSet) { - initializeFirstWeightsAtLevel(); - foundFirstAnswerSet = true; - } else { - if (!isCurrentBetterThanBest()) { - throw oops("WeakConstraintsManager instructed to mark current valuation as best-known, but there is a better one already."); - } - } - if (!aboveMaxLevelTrueAtoms.isEmpty()) { - throw oops("WeakConstraintsManager has aboveMaxLevelTrueAtoms but is called to markCurrentWeightsAsBestKnown."); - } - weightAtLevelsManager.markCurrentWeightAsBestKnown(); - } - - private void initializeFirstWeightsAtLevel() { - // First answer set has been found, find its maximum level and inform WeightAtLevelsManager. - LOGGER.trace("Initializing for first answer-set."); - int highestLevel = 0; - List trueWeakConstraintAtomCallbacks = knownAtomCallbacksForFirstAnswerSet - .stream() - .filter(wca -> assignment.getTruth(wca.atom).toBoolean()) - .collect(Collectors.toList()); - for (WeakConstraintAtomCallback weakConstraintAtomCallback : trueWeakConstraintAtomCallbacks) { - int level = weakConstraintAtomCallback.level; - if (highestLevel < level) { - highestLevel = level; - } - } - weightAtLevelsManager.setMaxLevel(highestLevel); - // Now inform WeightAtLevelsManager about all true weak constraint atoms. - for (WeakConstraintAtomCallback weakConstraintAtomCallback : trueWeakConstraintAtomCallbacks) { - weightAtLevelsManager.increaseCurrentWeight(weakConstraintAtomCallback.level, weakConstraintAtomCallback.weight); - } - } - - /** - * Generates a NoGood that prevents the current weight to be derived again. - * @return a NoGood which excludes the exact weight of the current assignment. - */ - public NoGood generateExcludingNoGood() { - LOGGER.trace("Generating excluding NoGood."); - // Collect all currently true/mbt weights and put their respective literals in one nogood. - // Note: alternative to searching all known callback atoms would be maintaining a list of true ones, but this is probably less efficient. - ArrayList trueCallbackAtoms = new ArrayList<>(); - for (Integer callbackAtom : knownCallbackAtoms) { - ThriceTruth truth = assignment.getTruth(callbackAtom); - if (truth != null && truth.toBoolean()) { - trueCallbackAtoms.add(atomToLiteral(callbackAtom)); - } - } - LOGGER.trace("True weak constraint representing atoms are: {}", trueCallbackAtoms); - return NoGood.fromConstraint(trueCallbackAtoms, Collections.emptyList()); - } - - @Override - public void setChecksEnabled(boolean checksEnabled) { - weightAtLevelsManager.setChecksEnabled(checksEnabled); - } + void addWeakConstraintsInformation(List> weakConstraintAtomWeightLevels); /** - * Returns the current weights and their levels. - * @return a TreeMap mapping levels to weights. + * Returns the valuation of the current assignment as weights and their levels. + * @return a TreeMap mapping levels to weight, only levels with weight != 0 are guaranteed to be reported. */ - public TreeMap getCurrentWeightAtLevels() { - LOGGER.trace("Reporting current weight at levels."); - TreeMap weightAtLevels = weightAtLevelsManager.getCurrentWeightAtLevels(); - if (aboveMaxLevelTrueAtoms.isEmpty()) { - LOGGER.trace("Current weight at levels: {}", weightAtLevels); - return weightAtLevels; - } - // Add weights above the maximum level stored in the WeightAtLevelsManager. - for (WeakConstraintAtomCallback aboveMaxLevelTrueAtom : aboveMaxLevelTrueAtoms) { - Integer weightAtLevel = weightAtLevels.get(aboveMaxLevelTrueAtom.level); - weightAtLevel = weightAtLevel == null ? 0 : weightAtLevel; - weightAtLevels.putIfAbsent(aboveMaxLevelTrueAtom.level, weightAtLevel + aboveMaxLevelTrueAtom.weight); - } - LOGGER.trace("Current weight at levels: {}", weightAtLevels); - return weightAtLevels; - } + TreeMap getCurrentWeightAtLevels(); } diff --git a/src/main/java/at/ac/tuwien/kr/alpha/solver/optimization/WeakConstraintsManagerForBoundedOptimality.java b/src/main/java/at/ac/tuwien/kr/alpha/solver/optimization/WeakConstraintsManagerForBoundedOptimality.java new file mode 100644 index 000000000..fb2aa9f41 --- /dev/null +++ b/src/main/java/at/ac/tuwien/kr/alpha/solver/optimization/WeakConstraintsManagerForBoundedOptimality.java @@ -0,0 +1,231 @@ +package at.ac.tuwien.kr.alpha.solver.optimization; + +import at.ac.tuwien.kr.alpha.common.NoGood; +import at.ac.tuwien.kr.alpha.solver.ThriceTruth; +import at.ac.tuwien.kr.alpha.solver.WritableAssignment; +import org.apache.commons.lang3.tuple.Triple; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.TreeMap; +import java.util.stream.Collectors; + +import static at.ac.tuwien.kr.alpha.Util.oops; +import static at.ac.tuwien.kr.alpha.common.Literals.atomToLiteral; +import static at.ac.tuwien.kr.alpha.solver.ThriceTruth.MBT; + +/** + * Manages weak constraints: stores the value of the current partial assignment, the best known value of any answer-set, + * and handles callbacks/computations when certain atoms (that represent weak constraints) change their truth value. + * + * Copyright (c) 2020-2021, the Alpha Team. + */ +public class WeakConstraintsManagerForBoundedOptimality implements WeakConstraintsManager { + private static final Logger LOGGER = LoggerFactory.getLogger(WeakConstraintsManagerForBoundedOptimality.class); + + final WritableAssignment assignment; + final WeightAtLevelsManager weightAtLevelsManager; + private final ArrayList knownCallbackAtoms; + private final ArrayList knownAtomCallbacksForFirstAnswerSet = new ArrayList<>(); + private boolean foundFirstAnswerSet; + private final HashSet aboveMaxLevelTrueAtoms = new HashSet<>(); + + public WeakConstraintsManagerForBoundedOptimality(WritableAssignment assignment, String answerSetsMaxWeightAtLevels) { + this.assignment = assignment; + if (answerSetsMaxWeightAtLevels.isEmpty()) { + this.weightAtLevelsManager = new WeightAtLevelsManager(); + } else { + // If maximum weights are given, initialize accordingly and consider it as an already found answer-set. + this.weightAtLevelsManager = new WeightAtLevelsManager(answerSetsMaxWeightAtLevels); + this.foundFirstAnswerSet = true; + } + this.knownCallbackAtoms = new ArrayList<>(); + } + + /** + * Registers newly obtained weak constraint atoms for callback at the assignment. + * @param weakConstraintAtomWeightLevels a set of triples (a,b,c) representing weak constraint atoms, where a is + * the atom, b the weight and c the level of the respective weak constraint. + */ + @Override + public void addWeakConstraintsInformation(List> weakConstraintAtomWeightLevels) { + for (Triple weakConstraintAtomWeightLevel : weakConstraintAtomWeightLevels) { + if (weakConstraintAtomWeightLevel.getMiddle() == 0) { + // Skip weak constraints with weight 0 entirely. + continue; + } + WeakConstraintAtomCallback wcA = new WeakConstraintAtomCallback(this, weakConstraintAtomWeightLevel.getLeft(), weakConstraintAtomWeightLevel.getMiddle(), weakConstraintAtomWeightLevel.getRight()); + assignment.registerCallbackOnChange(wcA.atom); + assignment.getAtomCallbackManager().recordCallback(wcA.atom, wcA); + knownCallbackAtoms.add(wcA.atom); + if (assignment.getTruth(wcA.atom) != null) { + throw oops("Adding weak constraints information and atom callback already has a truth value assigned."); + } + if (!foundFirstAnswerSet) { + knownAtomCallbacksForFirstAnswerSet.add(wcA); + } + } + } + + /** + * Process the the changed truth value of an atom representing a weak constraint. + * + * @param atomCallback the {@link WeakConstraintAtomCallback} whose assigned truth value did change. + */ + public void processCallback(WeakConstraintAtomCallback atomCallback) { + final int atom = atomCallback.atom; + final int level = atomCallback.level; + final int weight = atomCallback.weight; + ThriceTruth lastTruthValue = atomCallback.lastTruthValue; + if (LOGGER.isTraceEnabled()) { + LOGGER.trace("Processing callback for atom {} with weight at level {}@{}, last truth value was {}.", atom, weight, level, lastTruthValue); + LOGGER.trace("Current atom truth value is: {}", assignment.getTruth(atom)); + } + ThriceTruth currentAtomTruth = assignment.getTruth(atom); + if (!foundFirstAnswerSet) { + // Record old truth value and return if no answer set has been found yet. + atomCallback.lastTruthValue = currentAtomTruth; + LOGGER.trace("End processing callback as no first answer set has been found."); + return; + } + LOGGER.trace("Current truth value is: {}", currentAtomTruth); + atomCallback.lastTruthValue = currentAtomTruth; + if (lastTruthValue == null) { + // Change from unassigned to some truth value. + if (currentAtomTruth == MBT) { + increaseWeight(atomCallback); + } + // Note: for assignment to FALSE or MBT->TRUE, no change needed. + } else { + // Decrease if change from TRUE/MBT to unassigned. + if (lastTruthValue.toBoolean() && currentAtomTruth == null) { + decreaseWeight(atomCallback); + } + // Note: for backtracking from TRUE to MBT no change is needed. + if (currentAtomTruth != null && lastTruthValue.toBoolean() && !currentAtomTruth.toBoolean()) { + throw oops("Unexpected case of weak constraint atom directly changing truth value from MBT/TRUE to FALSE encountered. Atom/LastTruth/CurrentTruth: " + atom + "/" + lastTruthValue + "/" + currentAtomTruth); + } + } + LOGGER.trace("End Processing callback for atom {} with weight at level {}@{}, last truth value was {}, current is {}.", atom, weight, level, lastTruthValue, currentAtomTruth); + } + + private void increaseWeight(WeakConstraintAtomCallback atomCallback) { + if (weightAtLevelsManager.getMaxLevel() < atomCallback.level) { + LOGGER.trace("Adding higher level than possible with weightAtLevelsManager: level={} and maxLevel={}, callback is: {}.", atomCallback.level, weightAtLevelsManager.getMaxLevel(), atomCallback); + aboveMaxLevelTrueAtoms.add(atomCallback); + } else { + weightAtLevelsManager.increaseCurrentWeight(atomCallback.level, atomCallback.weight); + } + } + + private void decreaseWeight(WeakConstraintAtomCallback atomCallback) { + if (weightAtLevelsManager.getMaxLevel() < atomCallback.level) { + LOGGER.trace("Removing higher level than possible with weightAtLevelsManager: level={} and maxLevel={}, callback is: {}.", atomCallback.level, weightAtLevelsManager.getMaxLevel(), atomCallback); + aboveMaxLevelTrueAtoms.remove(atomCallback); + } else { + weightAtLevelsManager.decreaseCurrentWeight(atomCallback.level, atomCallback.weight); + } + } + + /** + * Returns whether the current partial interpretation is already worse (or equal) than the best-known answer-set. + * @return true if the current partial interpretation has worse (or equal) weight than the best-known answer-set. + */ + public boolean isCurrentBetterThanBest() { + if (!foundFirstAnswerSet) { + return true; + } + boolean isCurrentBetterThanBest = aboveMaxLevelTrueAtoms.isEmpty() && weightAtLevelsManager.isCurrentBetterThanBest(); + LOGGER.trace("Is current better than best? {}", isCurrentBetterThanBest); + return isCurrentBetterThanBest; + } + + /** + * Mark the current weight as being the best of all currently known answer-sets. + */ + public void markCurrentWeightAsBestKnown() { + if (!foundFirstAnswerSet) { + initializeFirstWeightsAtLevel(); + foundFirstAnswerSet = true; + } else { + if (!isCurrentBetterThanBest()) { + throw oops("WeakConstraintsManager instructed to mark current valuation as best-known, but there is a better one already."); + } + } + if (!aboveMaxLevelTrueAtoms.isEmpty()) { + throw oops("WeakConstraintsManager has aboveMaxLevelTrueAtoms but is called to markCurrentWeightsAsBestKnown."); + } + weightAtLevelsManager.markCurrentWeightAsBestKnown(); + } + + private void initializeFirstWeightsAtLevel() { + // First answer set has been found, find its maximum level and inform WeightAtLevelsManager. + LOGGER.trace("Initializing for first answer-set."); + int highestLevel = 0; + List trueWeakConstraintAtomCallbacks = knownAtomCallbacksForFirstAnswerSet + .stream() + .filter(wca -> assignment.getTruth(wca.atom).toBoolean()) + .collect(Collectors.toList()); + for (WeakConstraintAtomCallback weakConstraintAtomCallback : trueWeakConstraintAtomCallbacks) { + int level = weakConstraintAtomCallback.level; + if (highestLevel < level) { + highestLevel = level; + } + } + weightAtLevelsManager.setMaxLevel(highestLevel); + // Now inform WeightAtLevelsManager about all true weak constraint atoms. + for (WeakConstraintAtomCallback weakConstraintAtomCallback : trueWeakConstraintAtomCallbacks) { + weightAtLevelsManager.increaseCurrentWeight(weakConstraintAtomCallback.level, weakConstraintAtomCallback.weight); + } + } + + /** + * Generates a NoGood that prevents the current weight to be derived again. + * @return a NoGood which excludes the exact weight of the current assignment. + */ + public NoGood generateExcludingNoGood() { + LOGGER.trace("Generating excluding NoGood."); + // Collect all currently true/mbt weights and put their respective literals in one nogood. + // Note: alternative to searching all known callback atoms would be maintaining a list of true ones, but this is probably less efficient. + ArrayList trueCallbackAtoms = new ArrayList<>(); + for (Integer callbackAtom : knownCallbackAtoms) { + ThriceTruth truth = assignment.getTruth(callbackAtom); + if (truth != null && truth.toBoolean()) { + trueCallbackAtoms.add(atomToLiteral(callbackAtom)); + } + } + LOGGER.trace("True weak constraint representing atoms are: {}", trueCallbackAtoms); + return NoGood.fromConstraint(trueCallbackAtoms, Collections.emptyList()); + } + + @Override + public void setChecksEnabled(boolean checksEnabled) { + weightAtLevelsManager.setChecksEnabled(checksEnabled); + } + + /** + * Returns the current weights and their levels. + * @return a TreeMap mapping levels to weights. + */ + @Override + public TreeMap getCurrentWeightAtLevels() { + LOGGER.trace("Reporting current weight at levels."); + TreeMap weightAtLevels = weightAtLevelsManager.getCurrentWeightAtLevels(); + if (aboveMaxLevelTrueAtoms.isEmpty()) { + LOGGER.trace("Current weight at levels: {}", weightAtLevels); + return weightAtLevels; + } + // Add weights above the maximum level stored in the WeightAtLevelsManager. + for (WeakConstraintAtomCallback aboveMaxLevelTrueAtom : aboveMaxLevelTrueAtoms) { + Integer weightAtLevel = weightAtLevels.get(aboveMaxLevelTrueAtom.level); + weightAtLevel = weightAtLevel == null ? 0 : weightAtLevel; + weightAtLevels.putIfAbsent(aboveMaxLevelTrueAtom.level, weightAtLevel + aboveMaxLevelTrueAtom.weight); + } + LOGGER.trace("Current weight at levels: {}", weightAtLevels); + return weightAtLevels; + } +} diff --git a/src/main/java/at/ac/tuwien/kr/alpha/solver/optimization/WeakConstraintsManagerForUnboundedEnumeration.java b/src/main/java/at/ac/tuwien/kr/alpha/solver/optimization/WeakConstraintsManagerForUnboundedEnumeration.java new file mode 100644 index 000000000..e9f9fed7b --- /dev/null +++ b/src/main/java/at/ac/tuwien/kr/alpha/solver/optimization/WeakConstraintsManagerForUnboundedEnumeration.java @@ -0,0 +1,52 @@ +package at.ac.tuwien.kr.alpha.solver.optimization; + +import at.ac.tuwien.kr.alpha.solver.WritableAssignment; +import org.apache.commons.lang3.tuple.Triple; + +import java.util.ArrayList; +import java.util.List; +import java.util.TreeMap; + +/** + * Enables the valuation of an answer-set based on weak constraints. + * This implementation is not based on callbacks whenever atoms change their assignment but rather checks all + * {@link at.ac.tuwien.kr.alpha.grounder.atoms.WeakConstraintAtom}s sequentially whenever a valuation is requested. + * This implementation is intended to provide valuations only for full answer-sets. + * Copyright (c) 2023, the Alpha Team. + */ +public class WeakConstraintsManagerForUnboundedEnumeration implements WeakConstraintsManager { + + private final List> knownAtomValuations = new ArrayList<>(); + private final WritableAssignment assignment; + + public WeakConstraintsManagerForUnboundedEnumeration(WritableAssignment assignment) { + this.assignment = assignment; + } + + @Override + public void setChecksEnabled(boolean checksEnabled) { + + } + + @Override + public void addWeakConstraintsInformation(List> weakConstraintAtomWeightLevels) { + knownAtomValuations.addAll(weakConstraintAtomWeightLevels); + } + + @Override + public TreeMap getCurrentWeightAtLevels() { + TreeMap currentWeights = new TreeMap<>(); + for (Triple atomWeightLevel : knownAtomValuations) { + if (assignment.getTruth(atomWeightLevel.getLeft()).toBoolean()) { + Integer atomLevel = atomWeightLevel.getRight(); + Integer currentWeight = currentWeights.get(atomLevel); + if (currentWeight == null) { + currentWeights.put(atomLevel, atomWeightLevel.getMiddle()); + } else { + currentWeights.put(atomLevel, currentWeight + atomWeightLevel.getMiddle()); + } + } + } + return currentWeights; + } +} diff --git a/src/main/java/at/ac/tuwien/kr/alpha/solver/optimization/WeightAtLevelsManager.java b/src/main/java/at/ac/tuwien/kr/alpha/solver/optimization/WeightAtLevelsManager.java index f41f56483..af2720228 100644 --- a/src/main/java/at/ac/tuwien/kr/alpha/solver/optimization/WeightAtLevelsManager.java +++ b/src/main/java/at/ac/tuwien/kr/alpha/solver/optimization/WeightAtLevelsManager.java @@ -5,6 +5,9 @@ import org.slf4j.LoggerFactory; import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; import java.util.TreeMap; import static at.ac.tuwien.kr.alpha.Util.oops; @@ -43,6 +46,50 @@ public WeightAtLevelsManager() { isCurrentBetterThanBest = true; } + /** + * Initializes {@link WeightAtLevelsManager} with given upper bound, i.e., bound is taken valuation of a best-known answer-set. + * @param maximumWeightAtLevels a comma-separated list of weight@levels pairs. + */ + public WeightAtLevelsManager(String maximumWeightAtLevels) { + this(); + if (maximumWeightAtLevels.isEmpty()) { + throw new IllegalArgumentException("Initial maximum bound is empty."); + } + initializeFromString(maximumWeightAtLevels); + } + + private void initializeFromString(String maximumWeightAtLevels) { + HashMap levelsToWeight = new LinkedHashMap<>(); + int maxLevel = 0; + // Parse maximumWeightAtLevels string. + for (String weightAtLevel : maximumWeightAtLevels.split(",")) { + String[] weightAndLevel = weightAtLevel.split("@"); + if (weightAndLevel.length != 2) { + throw new IllegalArgumentException("Could not parse given comma-separated list of weight@level pairs. Given input was: " + maximumWeightAtLevels); + } + int weight = Integer.parseInt(weightAndLevel[0]); + Integer level = Integer.parseInt(weightAndLevel[1]); + if (weight == 0) { + continue; // Skip weights of zero. + } + levelsToWeight.putIfAbsent(level, 0); + levelsToWeight.put(level, weight + levelsToWeight.get(level)); + if (level > maxLevel) { + maxLevel = level; + } + } + // Initialize according to parsed weights at levels. + setMaxLevel(maxLevel); + for (Map.Entry levelToWeight : levelsToWeight.entrySet()) { + increaseCurrentWeight(levelToWeight.getKey(), levelToWeight.getValue()); + } + markCurrentWeightAsBestKnown(); + // Re-set the current weight at all levels back to zero. + for (Map.Entry levelToWeight : levelsToWeight.entrySet()) { + decreaseCurrentWeight(levelToWeight.getKey(), levelToWeight.getValue()); + } + } + @Override public void setChecksEnabled(boolean checksEnabled) { this.checksEnabled = checksEnabled; diff --git a/src/test/java/at/ac/tuwien/kr/alpha/solver/WeakConstraintsTests.java b/src/test/java/at/ac/tuwien/kr/alpha/solver/WeakConstraintsTests.java index 7663da586..5833f89e5 100644 --- a/src/test/java/at/ac/tuwien/kr/alpha/solver/WeakConstraintsTests.java +++ b/src/test/java/at/ac/tuwien/kr/alpha/solver/WeakConstraintsTests.java @@ -6,7 +6,7 @@ import java.util.Set; import static at.ac.tuwien.kr.alpha.test.util.TestUtils.assertOptimumAnswerSetEquals; -import static at.ac.tuwien.kr.alpha.test.util.TestUtils.collectRegressionTestAnswerSets; +import static at.ac.tuwien.kr.alpha.test.util.TestUtils.collectRegressionTestOptimalAnswerSets; import static org.junit.jupiter.api.Assertions.assertThrows; /** @@ -20,7 +20,7 @@ public void simpleWeightsSameLevel(RegressionTestConfig cfg) { String program = ":~a.[1@0,foo,bar]" + ":~b.[2@0,baz]" + "a :- not b. b:- not a."; - Set actualAnswerSets = collectRegressionTestAnswerSets(program, cfg); + Set actualAnswerSets = collectRegressionTestOptimalAnswerSets(program, cfg); assertOptimumAnswerSetEquals("a", "1@0", actualAnswerSets); } @@ -31,7 +31,7 @@ public void simpleWeightedAnswerSet(RegressionTestConfig cfg) { String program = ":~a.[2@2,foo,bar]" + ":~b.[1@1,baz]" + "a :- not b. b:- not a."; - Set actualAnswerSets = collectRegressionTestAnswerSets(program, cfg); + Set actualAnswerSets = collectRegressionTestOptimalAnswerSets(program, cfg); assertOptimumAnswerSetEquals("b", "1@1", actualAnswerSets); } @@ -41,7 +41,7 @@ public void simpleWeightedAnswerSetWithNegativeLevel(RegressionTestConfig cfg) { String program = ":~a.[2@1,foo,bar]" + ":~b.[1@-1,baz]" + "a :- not b. b:- not a."; - Set actualAnswerSets = collectRegressionTestAnswerSets(program, cfg); + Set actualAnswerSets = collectRegressionTestOptimalAnswerSets(program, cfg); assertOptimumAnswerSetEquals("b", "1@-1", actualAnswerSets); } @@ -52,7 +52,7 @@ public void simpleMultiLevelWeightedAnswerSet(RegressionTestConfig cfg) { ":~b.[1@1,baz]" + ":~b.[3@-4,baz]" + "a :- not b. b:- not a."; - Set actualAnswerSets = collectRegressionTestAnswerSets(program, cfg); + Set actualAnswerSets = collectRegressionTestOptimalAnswerSets(program, cfg); assertOptimumAnswerSetEquals("b", "3@-4, 1@1", actualAnswerSets); } @@ -64,7 +64,7 @@ public void sameWeightSummedUpInLevel(RegressionTestConfig cfg) { ":~b.[1@3]" + ":~a.[2@1,foo]" + ":~a.[2@1,bar]"; - Set actualAnswerSets = collectRegressionTestAnswerSets(program, cfg); + Set actualAnswerSets = collectRegressionTestOptimalAnswerSets(program, cfg); assertOptimumAnswerSetEquals("a", "4@1", actualAnswerSets); } @@ -77,7 +77,7 @@ public void sameWeightSameTermNotSummedUpInLevel(RegressionTestConfig cfg) { ":~b.[1@3]" + ":~a.[2@1,foo]" + ":~c.[2@1,foo]"; - Set actualAnswerSets = collectRegressionTestAnswerSets(program, cfg); + Set actualAnswerSets = collectRegressionTestOptimalAnswerSets(program, cfg); assertOptimumAnswerSetEquals("a, c", "2@1", actualAnswerSets); } @@ -93,7 +93,7 @@ public void negativeWeightThrowsException(RegressionTestConfig cfg) { "w(Z) :- Z = 8 - K, q(K)." + ":~a,w(Z).[Z@1]"; assertThrows(IllegalArgumentException.class, () -> { - Set actualAnswerSets = collectRegressionTestAnswerSets(program, cfg); + Set actualAnswerSets = collectRegressionTestOptimalAnswerSets(program, cfg); // In case negative weights can be dealt with (e.g. by fully grounding or under certain restrictions), // the optimum answer set of above program is: p(1),...,p(9),q(9),w(-1),a,has_q at valuation -1@1 // Under current behaviour we expect the computation of answer-sets to fail already. @@ -113,7 +113,7 @@ public void complexValuationWithMultipleWeightsOnMultipleLevels(RegressionTestCo ":~ weightatlevel(W,L).[W@L]" + "has_wal :- weightatlevel(W,L)." + ":- not has_wal."; - Set actualAnswerSets = collectRegressionTestAnswerSets(program, cfg); + Set actualAnswerSets = collectRegressionTestOptimalAnswerSets(program, cfg); assertOptimumAnswerSetEquals( "dom(1), dom(2), dom(3), c(1), b(1), a(1), weightatlevel(3,1), has_wal", "3@1", actualAnswerSets); } diff --git a/src/test/java/at/ac/tuwien/kr/alpha/solver/optimization/WeightAtLevelsManagerTest.java b/src/test/java/at/ac/tuwien/kr/alpha/solver/optimization/WeightAtLevelsManagerTest.java index 7bbe7a74f..f1514a62b 100644 --- a/src/test/java/at/ac/tuwien/kr/alpha/solver/optimization/WeightAtLevelsManagerTest.java +++ b/src/test/java/at/ac/tuwien/kr/alpha/solver/optimization/WeightAtLevelsManagerTest.java @@ -120,4 +120,31 @@ public void trimmingAtLevelZero() { weightAtLevelsManager.markCurrentWeightAsBestKnown(); assertFalse(weightAtLevelsManager.isCurrentBetterThanBest()); } + + @Test + public void initializationWithMaxBound() { + WeightAtLevelsManager weightAtLevelsManager = new WeightAtLevelsManager("1@1,5@6,3@2"); + weightAtLevelsManager.setChecksEnabled(true); + assertTrue(weightAtLevelsManager.isCurrentBetterThanBest()); + } + + @Test + public void initializationWithMaxBoundAllowsBetterUntilBound() { + WeightAtLevelsManager weightAtLevelsManager = new WeightAtLevelsManager("1@2,4@3"); + weightAtLevelsManager.setChecksEnabled(true); + TreeMap initialWeights = weightAtLevelsManager.getCurrentWeightAtLevels(); + assertEquals(initialWeights.size(), 0); + assertTrue(weightAtLevelsManager.isCurrentBetterThanBest()); + weightAtLevelsManager.increaseCurrentWeight(2, 1); + assertTrue(weightAtLevelsManager.isCurrentBetterThanBest()); + weightAtLevelsManager.increaseCurrentWeight(3, 3); + assertTrue(weightAtLevelsManager.isCurrentBetterThanBest()); + weightAtLevelsManager.increaseCurrentWeight(3, 1); + assertFalse(weightAtLevelsManager.isCurrentBetterThanBest()); + } + + @Test + public void initializationWithEmptyMaxBoundThrowsException() { + assertThrows(IllegalArgumentException.class, () -> new WeightAtLevelsManager("")); + } } \ No newline at end of file diff --git a/src/test/java/at/ac/tuwien/kr/alpha/test/util/TestUtils.java b/src/test/java/at/ac/tuwien/kr/alpha/test/util/TestUtils.java index 4d4bcddca..4695ad655 100644 --- a/src/test/java/at/ac/tuwien/kr/alpha/test/util/TestUtils.java +++ b/src/test/java/at/ac/tuwien/kr/alpha/test/util/TestUtils.java @@ -172,6 +172,16 @@ public static Solver buildSolverForRegressionTest(InputProgram prog, RegressionT return buildSolverFromSystemConfig(prog, cfg.toSystemConfig()); } + public static Solver buildSolverForOptimizationRegressionTest(InputProgram prog, RegressionTestConfig cfg) { + SystemConfig systemConfig = cfg.toSystemConfig(); + systemConfig.setAnswerSetOptimizationEnabled(true); + return buildSolverFromSystemConfig(prog, systemConfig); + } + + public static Set collectRegressionTestOptimalAnswerSets(String prog, RegressionTestConfig config) { + return buildSolverForOptimizationRegressionTest(new ProgramParser().parse(prog), config).collectSet(); + } + public static Solver buildSolverForRegressionTest(String prog, RegressionTestConfig cfg) { return buildSolverFromSystemConfig(new ProgramParser().parse(prog), cfg.toSystemConfig()); } From 527d9143f2f63c7ba3b1f66144f79fde7a1a9ab7 Mon Sep 17 00:00:00 2001 From: Antonius Weinzierl Date: Thu, 12 Jan 2023 04:13:21 +0100 Subject: [PATCH 25/27] Fix merge. --- .../at/ac/tuwien/kr/alpha/api/AnswerSet.java | 12 +- .../tuwien/kr/alpha/api/programs/Program.java | 10 +- .../main/java/at/ac/tuwien/kr/alpha/Main.java | 31 ++- .../kr/alpha/commons/BasicAnswerSet.java | 17 +- .../kr/alpha/commons}/WeightedAnswerSet.java | 29 ++- .../kr/alpha/commons/atoms/AbstractAtom.java | 8 +- .../alpha/commons/WeightedAnswerSetTest.java | 75 ++++++ .../alpha/core/atoms/WeakConstraintAtom.java | 53 ++-- .../kr/alpha/core/grounder/NaiveGrounder.java | 45 ++-- .../alpha/core/grounder/NoGoodGenerator.java | 27 ++- .../core/grounder/WeakConstraintRecorder.java | 2 +- .../alpha/core/parser/ParseTreeVisitor.java | 33 ++- .../alpha/core/programs/AbstractProgram.java | 13 +- .../kr/alpha/core/programs/InputProgram.java | 25 +- .../alpha/core/programs/InternalProgram.java | 31 +-- .../core/programs/NormalProgramImpl.java | 12 +- .../ArithmeticTermsRewriting.java | 8 +- .../IntervalTermToIntervalAtom.java | 12 +- .../VariableEqualityRemoval.java | 22 +- .../WeakConstraintNormalization.java | 25 +- .../AggregateOperatorNormalization.java | 15 +- .../encoders/AbstractAggregateEncoder.java | 13 +- .../StringtemplateBasedAggregateEncoder.java | 13 +- .../kr/alpha/core/rules/WeakConstraint.java | 8 +- .../core/solver/AtomCallbackManager.java | 6 +- .../kr/alpha/core/solver/DefaultSolver.java | 2 +- .../alpha/core/solver/OptimizingSolver.java | 18 +- .../WeakConstraintAtomCallback.java | 6 +- .../optimization/WeakConstraintsManager.java | 4 +- ...onstraintsManagerForBoundedOptimality.java | 14 +- ...traintsManagerForUnboundedEnumeration.java | 4 +- .../optimization/WeightAtLevelsManager.java | 29 +-- .../kr/alpha/core/WeightedAnswerSetTest.java | 69 ------ .../alpha/core/grounder/ChoiceGrounder.java | 46 ++-- .../kr/alpha/core/solver/SolverTests.java | 35 ++- .../core/solver/WeakConstraintsTests.java | 10 +- .../WeightAtLevelsManagerTest.java | 2 +- .../kr/alpha/core/test/util/TestUtils.java | 54 ++--- .../tuwien/kr/alpha/api/impl/AlphaImpl.java | 36 ++- .../kr/alpha/api/impl/AlphaImplTest.java | 57 +++-- gradle/wrapper/gradle-wrapper.jar | Bin 59536 -> 61574 bytes gradle/wrapper/gradle-wrapper.properties | 1 + gradlew | 28 ++- gradlew.bat | 15 +- .../java/at/ac/tuwien/kr/alpha/api/Alpha.java | 227 ------------------ .../ac/tuwien/kr/alpha/common/AnswerSet.java | 52 ---- .../AggregateOperatorNormalization.java | 128 ---------- 47 files changed, 513 insertions(+), 869 deletions(-) rename {alpha-core/src/main/java/at/ac/tuwien/kr/alpha/core/common => alpha-commons/src/main/java/at/ac/tuwien/kr/alpha/commons}/WeightedAnswerSet.java (77%) create mode 100644 alpha-commons/src/test/java/at/ac/tuwien/kr/alpha/commons/WeightedAnswerSetTest.java delete mode 100644 alpha-core/src/test/java/at/ac/tuwien/kr/alpha/core/WeightedAnswerSetTest.java delete mode 100644 src/main/java/at/ac/tuwien/kr/alpha/api/Alpha.java delete mode 100644 src/main/java/at/ac/tuwien/kr/alpha/common/AnswerSet.java delete mode 100644 src/main/java/at/ac/tuwien/kr/alpha/grounder/transformation/aggregates/AggregateOperatorNormalization.java diff --git a/alpha-api/src/main/java/at/ac/tuwien/kr/alpha/api/AnswerSet.java b/alpha-api/src/main/java/at/ac/tuwien/kr/alpha/api/AnswerSet.java index 748e1a781..7233d9855 100644 --- a/alpha-api/src/main/java/at/ac/tuwien/kr/alpha/api/AnswerSet.java +++ b/alpha-api/src/main/java/at/ac/tuwien/kr/alpha/api/AnswerSet.java @@ -1,11 +1,12 @@ package at.ac.tuwien.kr.alpha.api; -import java.util.List; -import java.util.SortedSet; - import at.ac.tuwien.kr.alpha.api.programs.Predicate; import at.ac.tuwien.kr.alpha.api.programs.atoms.Atom; +import java.util.List; +import java.util.Map; +import java.util.SortedSet; + /** * API representation of an answer set, i.e. a set of atoms that is a model of an ASP program. * @@ -23,6 +24,11 @@ public interface AnswerSet extends Comparable { */ SortedSet getPredicateInstances(Predicate predicate); + /** + * Returns a mapping of {@link Predicate}s to all respective instances. + */ + Map> getPredicateInstances(); + /** * Boolean flag indicating whether this {@link AnswerSet} represents the empty set. */ diff --git a/alpha-api/src/main/java/at/ac/tuwien/kr/alpha/api/programs/Program.java b/alpha-api/src/main/java/at/ac/tuwien/kr/alpha/api/programs/Program.java index 2e8c0f528..ad6c83733 100644 --- a/alpha-api/src/main/java/at/ac/tuwien/kr/alpha/api/programs/Program.java +++ b/alpha-api/src/main/java/at/ac/tuwien/kr/alpha/api/programs/Program.java @@ -1,11 +1,11 @@ package at.ac.tuwien.kr.alpha.api.programs; -import java.util.List; - import at.ac.tuwien.kr.alpha.api.programs.atoms.Atom; import at.ac.tuwien.kr.alpha.api.rules.Rule; import at.ac.tuwien.kr.alpha.api.rules.heads.Head; +import java.util.List; + /** * An ASP program as accepted by Alpha. * @@ -29,4 +29,10 @@ public interface Program> { */ List getRules(); + /** + * Indicates whether this program contains some weak constraints. + * @return true iff this program contains weak constraints. + */ + boolean containsWeakConstraints(); + } diff --git a/alpha-cli-app/src/main/java/at/ac/tuwien/kr/alpha/Main.java b/alpha-cli-app/src/main/java/at/ac/tuwien/kr/alpha/Main.java index ab6bde8a0..6dbc690c8 100644 --- a/alpha-cli-app/src/main/java/at/ac/tuwien/kr/alpha/Main.java +++ b/alpha-cli-app/src/main/java/at/ac/tuwien/kr/alpha/Main.java @@ -27,24 +27,8 @@ */ package at.ac.tuwien.kr.alpha; -import java.io.File; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.PrintStream; -import java.nio.file.Paths; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.function.BiConsumer; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import org.apache.commons.cli.ParseException; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import at.ac.tuwien.kr.alpha.api.Alpha; import at.ac.tuwien.kr.alpha.api.AnswerSet; -import at.ac.tuwien.kr.alpha.api.WeightedAnswerSet; import at.ac.tuwien.kr.alpha.api.DebugSolvingContext; import at.ac.tuwien.kr.alpha.api.Solver; import at.ac.tuwien.kr.alpha.api.StatisticsReportingSolver; @@ -60,7 +44,22 @@ import at.ac.tuwien.kr.alpha.app.ComponentGraphWriter; import at.ac.tuwien.kr.alpha.app.DependencyGraphWriter; import at.ac.tuwien.kr.alpha.app.config.CommandLineParser; +import at.ac.tuwien.kr.alpha.commons.WeightedAnswerSet; import at.ac.tuwien.kr.alpha.commons.util.SimpleAnswerSetFormatter; +import org.apache.commons.cli.ParseException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.PrintStream; +import java.nio.file.Paths; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.BiConsumer; +import java.util.stream.Collectors; +import java.util.stream.Stream; /** * Main entry point for Alpha. diff --git a/alpha-commons/src/main/java/at/ac/tuwien/kr/alpha/commons/BasicAnswerSet.java b/alpha-commons/src/main/java/at/ac/tuwien/kr/alpha/commons/BasicAnswerSet.java index dfb73de8d..cc2be8fbf 100644 --- a/alpha-commons/src/main/java/at/ac/tuwien/kr/alpha/commons/BasicAnswerSet.java +++ b/alpha-commons/src/main/java/at/ac/tuwien/kr/alpha/commons/BasicAnswerSet.java @@ -1,7 +1,10 @@ package at.ac.tuwien.kr.alpha.commons; -import static java.util.Collections.emptyMap; -import static java.util.Collections.emptySortedSet; +import at.ac.tuwien.kr.alpha.api.AnswerSet; +import at.ac.tuwien.kr.alpha.api.AnswerSetQuery; +import at.ac.tuwien.kr.alpha.api.programs.Predicate; +import at.ac.tuwien.kr.alpha.api.programs.atoms.Atom; +import at.ac.tuwien.kr.alpha.commons.util.Util; import java.util.Collections; import java.util.Iterator; @@ -10,11 +13,8 @@ import java.util.Set; import java.util.SortedSet; -import at.ac.tuwien.kr.alpha.api.AnswerSet; -import at.ac.tuwien.kr.alpha.api.AnswerSetQuery; -import at.ac.tuwien.kr.alpha.api.programs.Predicate; -import at.ac.tuwien.kr.alpha.api.programs.atoms.Atom; -import at.ac.tuwien.kr.alpha.commons.util.Util; +import static java.util.Collections.emptyMap; +import static java.util.Collections.emptySortedSet; /** * Copyright (c) 2016, the Alpha Team. @@ -107,6 +107,9 @@ public int hashCode() { @Override public int compareTo(AnswerSet other) { + if (other.getClass() != this.getClass()) { + return 1; + } final SortedSet predicates = this.getPredicates(); int result = Util.compareSortedSets(predicates, other.getPredicates()); diff --git a/alpha-core/src/main/java/at/ac/tuwien/kr/alpha/core/common/WeightedAnswerSet.java b/alpha-commons/src/main/java/at/ac/tuwien/kr/alpha/commons/WeightedAnswerSet.java similarity index 77% rename from alpha-core/src/main/java/at/ac/tuwien/kr/alpha/core/common/WeightedAnswerSet.java rename to alpha-commons/src/main/java/at/ac/tuwien/kr/alpha/commons/WeightedAnswerSet.java index 86d0f56aa..9720f1f54 100644 --- a/alpha-core/src/main/java/at/ac/tuwien/kr/alpha/core/common/WeightedAnswerSet.java +++ b/alpha-commons/src/main/java/at/ac/tuwien/kr/alpha/commons/WeightedAnswerSet.java @@ -1,4 +1,6 @@ -package at.ac.tuwien.kr.alpha.common; +package at.ac.tuwien.kr.alpha.commons; + +import at.ac.tuwien.kr.alpha.api.AnswerSet; import java.util.Iterator; import java.util.Map; @@ -120,6 +122,31 @@ public String getWeightsAsString() { return joiner.toString(); } + /** + * Extract weights at levels from string of comma-separated pairs of the form weight@level. + * Multiple weights for the same level are summed-up. + * @param weightAtLevels + * @return a TreeMap containing + */ + public static TreeMap weightPerLevelFromString(String weightAtLevels) { + String[] weightsAtLevels = weightAtLevels.split(","); + TreeMap weightAtLevelsTreeMap = new TreeMap<>(); + for (String weightsAtLevel : weightsAtLevels) { + String[] wAtL = weightsAtLevel.trim().split("@"); + if (wAtL.length != 2) { + throw new IllegalArgumentException("Could not parse given comma-separated list of weight@level pairs. Given input was: " + weightAtLevels); + } + int weight = Integer.parseInt(wAtL[0]); + int level = Integer.parseInt(wAtL[1]); + if (weight == 0) { + continue; // Skip zero weights. + } + weightAtLevelsTreeMap.putIfAbsent(level, 0); + weightAtLevelsTreeMap.put(level, weight + weightAtLevelsTreeMap.get(level)); + } + return weightAtLevelsTreeMap; + } + @Override public String toString() { return super.toString() + getWeightsAsString(); diff --git a/alpha-commons/src/main/java/at/ac/tuwien/kr/alpha/commons/atoms/AbstractAtom.java b/alpha-commons/src/main/java/at/ac/tuwien/kr/alpha/commons/atoms/AbstractAtom.java index f55611a0f..52e43ffc2 100644 --- a/alpha-commons/src/main/java/at/ac/tuwien/kr/alpha/commons/atoms/AbstractAtom.java +++ b/alpha-commons/src/main/java/at/ac/tuwien/kr/alpha/commons/atoms/AbstractAtom.java @@ -27,9 +27,6 @@ */ package at.ac.tuwien.kr.alpha.commons.atoms; -import java.util.List; -import java.util.Set; - import at.ac.tuwien.kr.alpha.api.grounder.Substitution; import at.ac.tuwien.kr.alpha.api.programs.Predicate; import at.ac.tuwien.kr.alpha.api.programs.atoms.Atom; @@ -38,6 +35,9 @@ import at.ac.tuwien.kr.alpha.api.terms.VariableTerm; import at.ac.tuwien.kr.alpha.commons.terms.Terms; +import java.util.List; +import java.util.Set; + /** * An Atom is the common superclass of all representations of ASP atoms used by Alpha. */ @@ -79,7 +79,7 @@ public Set getOccurringVariables() { /** * Returns whether this atom is ground, i.e., variable-free. * - * @return true iff the terms of this atom contain no {@link VariableTermImpl}. + * @return true iff the terms of this atom contain no {@link VariableTerm}. */ @Override public abstract boolean isGround(); diff --git a/alpha-commons/src/test/java/at/ac/tuwien/kr/alpha/commons/WeightedAnswerSetTest.java b/alpha-commons/src/test/java/at/ac/tuwien/kr/alpha/commons/WeightedAnswerSetTest.java new file mode 100644 index 000000000..213af8691 --- /dev/null +++ b/alpha-commons/src/test/java/at/ac/tuwien/kr/alpha/commons/WeightedAnswerSetTest.java @@ -0,0 +1,75 @@ +package at.ac.tuwien.kr.alpha.commons; + +import at.ac.tuwien.kr.alpha.api.AnswerSet; +import at.ac.tuwien.kr.alpha.commons.terms.Terms; +import org.junit.jupiter.api.Test; + +import static at.ac.tuwien.kr.alpha.commons.WeightedAnswerSet.weightPerLevelFromString; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotEquals; + +/** + * Copyright (c) 2021, the Alpha Team. + */ +public class WeightedAnswerSetTest { + + @Test + public void compareWeightedAnswerSets() { + AnswerSet answerSet1 = new AnswerSetBuilder().predicate("a").instance().predicate("p").instance(Terms.newSymbolicConstant("b")).build(); + AnswerSet answerSet2 = new AnswerSetBuilder().predicate("a").instance().build(); + WeightedAnswerSet was1 = new WeightedAnswerSet(answerSet1, weightPerLevelFromString("1@1")); + WeightedAnswerSet was2 = new WeightedAnswerSet(answerSet2, weightPerLevelFromString("1@1, 2@3")); + WeightedAnswerSet was1Same = new WeightedAnswerSet(answerSet1, weightPerLevelFromString("1@1")); + + assertFalse(was1.getPredicates().isEmpty()); + assertFalse(was2.getPredicates().isEmpty()); + assertNotEquals(was1, was2); + assertEquals(was1Same, was1); + assertEquals(was1.getPredicateInstances(), was1Same.getPredicateInstances()); + } + + @Test + public void compareAnswerSetsSameAtoms() { + AnswerSet answerSet = new AnswerSetBuilder().predicate("q").instance(Terms.newConstant(0), Terms.newConstant(1)) + .predicate("b").instance().predicate("foo").instance(Terms.newSymbolicConstant("a")).build(); + WeightedAnswerSet was3 = new WeightedAnswerSet(answerSet, weightPerLevelFromString("1@1, 2@3")); + WeightedAnswerSet was3AtomsNotWeights = new WeightedAnswerSet(answerSet, weightPerLevelFromString("2@1, 2@3")); + + assertFalse(was3.getPredicates().isEmpty()); + assertNotEquals(was3, was3AtomsNotWeights); + assertEquals(was3.getPredicateInstances(), was3AtomsNotWeights.getPredicateInstances()); + } + + @Test + public void compareAnswerSetsSameWeights() { + String was4Weights = "1@1, 4@3"; + AnswerSet answerSetBC = new AnswerSetBuilder().predicate("b").instance().predicate("c").instance().build(); + AnswerSet answerSetBD = new AnswerSetBuilder().predicate("b").instance().predicate("d").instance().build(); + WeightedAnswerSet was4 = new WeightedAnswerSet(answerSetBC, weightPerLevelFromString(was4Weights)); + WeightedAnswerSet was5 = new WeightedAnswerSet(answerSetBD, weightPerLevelFromString(was4Weights)); + + assertFalse(was4.getPredicates().isEmpty()); + assertFalse(was5.getPredicates().isEmpty()); + assertEquals(0, was4.compareWeights(was5)); + assertNotEquals(was4, was5); + assertNotEquals(was5, was4); + } + + @Test + public void compareWeights() { + AnswerSet answerSet = new AnswerSetBuilder().predicate("a").instance().build(); + WeightedAnswerSet was6 = new WeightedAnswerSet(answerSet, weightPerLevelFromString("2@3")); + WeightedAnswerSet was7 = new WeightedAnswerSet(answerSet, weightPerLevelFromString("1@1, 1@3")); + WeightedAnswerSet was8 = new WeightedAnswerSet(answerSet, weightPerLevelFromString("1@-3, 1@1, 1@3")); + assertEquals(-1, was7.compareWeights(was6)); + assertEquals(1, was6.compareWeights(was7)); + + assertEquals(-1, was7.compareWeights(was8)); + assertEquals(1, was8.compareWeights(was7)); + + assertEquals(-1, was8.compareWeights(was6)); + assertEquals(1, was6.compareWeights(was8)); + } + +} \ No newline at end of file diff --git a/alpha-core/src/main/java/at/ac/tuwien/kr/alpha/core/atoms/WeakConstraintAtom.java b/alpha-core/src/main/java/at/ac/tuwien/kr/alpha/core/atoms/WeakConstraintAtom.java index 945c1edb8..ddc84418a 100644 --- a/alpha-core/src/main/java/at/ac/tuwien/kr/alpha/core/atoms/WeakConstraintAtom.java +++ b/alpha-core/src/main/java/at/ac/tuwien/kr/alpha/core/atoms/WeakConstraintAtom.java @@ -1,13 +1,17 @@ -package at.ac.tuwien.kr.alpha.grounder.atoms; - -import at.ac.tuwien.kr.alpha.common.Predicate; -import at.ac.tuwien.kr.alpha.common.atoms.Atom; -import at.ac.tuwien.kr.alpha.common.atoms.Literal; -import at.ac.tuwien.kr.alpha.common.terms.ConstantTerm; -import at.ac.tuwien.kr.alpha.common.terms.FunctionTerm; -import at.ac.tuwien.kr.alpha.common.terms.Term; -import at.ac.tuwien.kr.alpha.common.terms.VariableTerm; -import at.ac.tuwien.kr.alpha.grounder.Substitution; +package at.ac.tuwien.kr.alpha.core.atoms; + +import at.ac.tuwien.kr.alpha.api.grounder.Substitution; +import at.ac.tuwien.kr.alpha.api.programs.Predicate; +import at.ac.tuwien.kr.alpha.api.programs.atoms.Atom; +import at.ac.tuwien.kr.alpha.api.programs.atoms.BasicAtom; +import at.ac.tuwien.kr.alpha.api.programs.literals.Literal; +import at.ac.tuwien.kr.alpha.api.terms.ConstantTerm; +import at.ac.tuwien.kr.alpha.api.terms.FunctionTerm; +import at.ac.tuwien.kr.alpha.api.terms.Term; +import at.ac.tuwien.kr.alpha.api.terms.VariableTerm; +import at.ac.tuwien.kr.alpha.commons.Predicates; +import at.ac.tuwien.kr.alpha.commons.atoms.AbstractAtom; +import at.ac.tuwien.kr.alpha.commons.terms.Terms; import java.util.ArrayList; import java.util.Collections; @@ -16,7 +20,7 @@ import java.util.Objects; import java.util.Set; -import static at.ac.tuwien.kr.alpha.Util.oops; +import static at.ac.tuwien.kr.alpha.commons.util.Util.oops; /** * Represents the head of a weak constraint (i.e., a special internal atom indicating that a rule really is a weak @@ -24,8 +28,8 @@ * * Copyright (c) 2020-2021, the Alpha Team. */ -public class WeakConstraintAtom extends Atom { - private static final Predicate PREDICATE = Predicate.getInstance("_weakconstraint_", 3, true, true); +public class WeakConstraintAtom extends AbstractAtom implements BasicAtom { + private static final Predicate PREDICATE = Predicates.getPredicate("_weakconstraint_", 3, true, true); private static final String TERMLISTSYMBOL = "_tuple"; private final Term weight; @@ -49,17 +53,17 @@ private WeakConstraintAtom(Term weight, Term level, FunctionTerm termList) { } public static WeakConstraintAtom getInstance(Term weight, Term level, List termList) { - Term actualLevel = level != null ? level : ConstantTerm.getInstance(0); + Term actualLevel = level != null ? level : Terms.newConstant(0); List actualTermlist = termList != null ? termList : Collections.emptyList(); - return new WeakConstraintAtom(weight, actualLevel, FunctionTerm.getInstance(TERMLISTSYMBOL, actualTermlist)); + return new WeakConstraintAtom(weight, actualLevel, Terms.newFunctionTerm(TERMLISTSYMBOL, actualTermlist)); } public Integer getWeight() { - return (Integer) ((ConstantTerm)weight).getObject(); + return (Integer) ((ConstantTerm)weight).getObject(); } public Integer getLevel() { - return (Integer) ((ConstantTerm)level).getObject(); + return (Integer) ((ConstantTerm)level).getObject(); } private static boolean isIntegerOrVariable(Term term) { @@ -67,15 +71,15 @@ private static boolean isIntegerOrVariable(Term term) { return true; } if (term instanceof ConstantTerm) { - Comparable constant = ((ConstantTerm) term).getObject(); + Comparable constant = ((ConstantTerm) term).getObject(); return constant instanceof Integer; } return false; } private static boolean isNegativeInteger(Term term) { - if (term instanceof ConstantTerm && ((ConstantTerm) term).getObject() instanceof Integer) { - return ((Integer)((ConstantTerm) term).getObject()) < 0; + if (term instanceof ConstantTerm && ((ConstantTerm) term).getObject() instanceof Integer) { + return ((Integer)((ConstantTerm) term).getObject()) < 0; } return false; } @@ -111,11 +115,11 @@ public boolean isGround() { } @Override - public Atom substitute(Substitution substitution) { + public WeakConstraintAtom substitute(Substitution substitution) { if (isGround()) { return this; } - return new WeakConstraintAtom(weight.substitute(substitution), level.substitute(substitution), termList.substitute(substitution)); + return new WeakConstraintAtom(weight.substitute(substitution), level.substitute(substitution), (FunctionTerm) termList.substitute(substitution)); } @Override @@ -155,4 +159,9 @@ public boolean equals(Object o) { public int hashCode() { return Objects.hash(weight, level, termList); } + + @Override + public Atom normalizeVariables(String prefix, int counterStartingValue) { + throw new UnsupportedOperationException("WeakConstraintAtom cannot normalize its variables."); + } } diff --git a/alpha-core/src/main/java/at/ac/tuwien/kr/alpha/core/grounder/NaiveGrounder.java b/alpha-core/src/main/java/at/ac/tuwien/kr/alpha/core/grounder/NaiveGrounder.java index 1d46b8785..6f79dd4a8 100644 --- a/alpha-core/src/main/java/at/ac/tuwien/kr/alpha/core/grounder/NaiveGrounder.java +++ b/alpha-core/src/main/java/at/ac/tuwien/kr/alpha/core/grounder/NaiveGrounder.java @@ -27,28 +27,6 @@ */ package at.ac.tuwien.kr.alpha.core.grounder; -import static at.ac.tuwien.kr.alpha.commons.util.Util.oops; -import static at.ac.tuwien.kr.alpha.core.atoms.Literals.atomOf; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.SortedSet; -import java.util.TreeSet; - -import org.apache.commons.lang3.tuple.ImmutablePair; -import org.apache.commons.lang3.tuple.Pair; -import org.apache.commons.lang3.tuple.Triple; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import at.ac.tuwien.kr.alpha.api.AnswerSet; import at.ac.tuwien.kr.alpha.api.config.GrounderHeuristicsConfiguration; import at.ac.tuwien.kr.alpha.api.grounder.Substitution; @@ -77,6 +55,27 @@ import at.ac.tuwien.kr.alpha.core.grounder.structure.AnalyzeUnjustified; import at.ac.tuwien.kr.alpha.core.programs.CompiledProgram; import at.ac.tuwien.kr.alpha.core.rules.CompiledRule; +import org.apache.commons.lang3.tuple.ImmutablePair; +import org.apache.commons.lang3.tuple.Pair; +import org.apache.commons.lang3.tuple.Triple; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.SortedSet; +import java.util.TreeSet; + +import static at.ac.tuwien.kr.alpha.commons.util.Util.oops; +import static at.ac.tuwien.kr.alpha.core.atoms.Literals.atomOf; /** * A semi-naive grounder. @@ -206,7 +205,7 @@ private Set getRulesWithUniqueHead() { } // Collect head and body variables. - HashSet occurringVariablesHead = new HashSet<>(headAtom.toLiteral().getBindingVariables()); + HashSet occurringVariablesHead = new HashSet<>(headAtom.getOccurringVariables()); HashSet occurringVariablesBody = new HashSet<>(); for (Literal lit : nonGroundRule.getPositiveBody()) { occurringVariablesBody.addAll(lit.getBindingVariables()); diff --git a/alpha-core/src/main/java/at/ac/tuwien/kr/alpha/core/grounder/NoGoodGenerator.java b/alpha-core/src/main/java/at/ac/tuwien/kr/alpha/core/grounder/NoGoodGenerator.java index 61a49cf60..5de03cd6a 100644 --- a/alpha-core/src/main/java/at/ac/tuwien/kr/alpha/core/grounder/NoGoodGenerator.java +++ b/alpha-core/src/main/java/at/ac/tuwien/kr/alpha/core/grounder/NoGoodGenerator.java @@ -27,19 +27,6 @@ */ package at.ac.tuwien.kr.alpha.core.grounder; -import static at.ac.tuwien.kr.alpha.core.atoms.Literals.atomOf; -import static at.ac.tuwien.kr.alpha.core.atoms.Literals.atomToLiteral; -import static at.ac.tuwien.kr.alpha.core.atoms.Literals.negateLiteral; -import static java.util.Collections.emptyList; -import static java.util.Collections.singletonList; - -import java.util.ArrayList; -import java.util.HashSet; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - import at.ac.tuwien.kr.alpha.api.grounder.Substitution; import at.ac.tuwien.kr.alpha.api.programs.Predicate; import at.ac.tuwien.kr.alpha.api.programs.atoms.Atom; @@ -54,6 +41,20 @@ import at.ac.tuwien.kr.alpha.core.programs.CompiledProgram; import at.ac.tuwien.kr.alpha.core.rules.CompiledRule; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import static at.ac.tuwien.kr.alpha.core.atoms.Literals.atomOf; +import static at.ac.tuwien.kr.alpha.core.atoms.Literals.atomToLiteral; +import static at.ac.tuwien.kr.alpha.core.atoms.Literals.negateLiteral; +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; + /** * Class to generate ground NoGoods out of non-ground rules and grounding substitutions. * Copyright (c) 2017-2018, the Alpha Team. diff --git a/alpha-core/src/main/java/at/ac/tuwien/kr/alpha/core/grounder/WeakConstraintRecorder.java b/alpha-core/src/main/java/at/ac/tuwien/kr/alpha/core/grounder/WeakConstraintRecorder.java index 47d81aa7f..42adc3ff3 100644 --- a/alpha-core/src/main/java/at/ac/tuwien/kr/alpha/core/grounder/WeakConstraintRecorder.java +++ b/alpha-core/src/main/java/at/ac/tuwien/kr/alpha/core/grounder/WeakConstraintRecorder.java @@ -1,4 +1,4 @@ -package at.ac.tuwien.kr.alpha.grounder; +package at.ac.tuwien.kr.alpha.core.grounder; import org.apache.commons.lang3.tuple.ImmutableTriple; import org.apache.commons.lang3.tuple.Triple; diff --git a/alpha-core/src/main/java/at/ac/tuwien/kr/alpha/core/parser/ParseTreeVisitor.java b/alpha-core/src/main/java/at/ac/tuwien/kr/alpha/core/parser/ParseTreeVisitor.java index 22923287e..68a755617 100644 --- a/alpha-core/src/main/java/at/ac/tuwien/kr/alpha/core/parser/ParseTreeVisitor.java +++ b/alpha-core/src/main/java/at/ac/tuwien/kr/alpha/core/parser/ParseTreeVisitor.java @@ -27,22 +27,6 @@ */ package at.ac.tuwien.kr.alpha.core.parser; -import static at.ac.tuwien.kr.alpha.Util.oops; -import static java.util.Collections.emptyList; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.SortedSet; -import java.util.TreeMap; -import java.util.TreeSet; - -import org.antlr.v4.runtime.RuleContext; -import org.antlr.v4.runtime.tree.TerminalNode; - import at.ac.tuwien.kr.alpha.api.AnswerSet; import at.ac.tuwien.kr.alpha.api.ComparisonOperator; import at.ac.tuwien.kr.alpha.api.common.fixedinterpretations.PredicateInterpretation; @@ -55,7 +39,6 @@ import at.ac.tuwien.kr.alpha.api.programs.atoms.ExternalAtom; import at.ac.tuwien.kr.alpha.api.programs.literals.AggregateLiteral; import at.ac.tuwien.kr.alpha.api.programs.literals.Literal; -import at.ac.tuwien.kr.alpha.api.rules.WeakConstraint; import at.ac.tuwien.kr.alpha.api.rules.heads.ChoiceHead; import at.ac.tuwien.kr.alpha.api.rules.heads.ChoiceHead.ChoiceElement; import at.ac.tuwien.kr.alpha.api.rules.heads.Head; @@ -77,6 +60,22 @@ import at.ac.tuwien.kr.alpha.core.antlr.ASPCore2Parser; import at.ac.tuwien.kr.alpha.core.programs.InputProgram; import at.ac.tuwien.kr.alpha.core.rules.BasicRule; +import at.ac.tuwien.kr.alpha.core.rules.WeakConstraint; +import org.antlr.v4.runtime.RuleContext; +import org.antlr.v4.runtime.tree.TerminalNode; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.SortedSet; +import java.util.TreeMap; +import java.util.TreeSet; + +import static at.ac.tuwien.kr.alpha.commons.util.Util.oops; +import static java.util.Collections.emptyList; /** * Copyright (c) 2016-2018, the Alpha Team. diff --git a/alpha-core/src/main/java/at/ac/tuwien/kr/alpha/core/programs/AbstractProgram.java b/alpha-core/src/main/java/at/ac/tuwien/kr/alpha/core/programs/AbstractProgram.java index 31a3617af..f2cbd824d 100644 --- a/alpha-core/src/main/java/at/ac/tuwien/kr/alpha/core/programs/AbstractProgram.java +++ b/alpha-core/src/main/java/at/ac/tuwien/kr/alpha/core/programs/AbstractProgram.java @@ -1,14 +1,14 @@ package at.ac.tuwien.kr.alpha.core.programs; -import java.util.Collections; -import java.util.List; - import at.ac.tuwien.kr.alpha.api.programs.InlineDirectives; import at.ac.tuwien.kr.alpha.api.programs.atoms.Atom; import at.ac.tuwien.kr.alpha.api.rules.Rule; import at.ac.tuwien.kr.alpha.api.rules.heads.Head; import at.ac.tuwien.kr.alpha.commons.util.Util; +import java.util.Collections; +import java.util.List; + /** * The parent type for all kinds of programs. Defines a program's basic structure (facts + rules + inlineDirectives) * @@ -21,11 +21,13 @@ public abstract class AbstractProgram> { private final List rules; private final List facts; private final InlineDirectives inlineDirectives; + private final boolean containsWeakConstraints; - public AbstractProgram(List rules, List facts, InlineDirectives inlineDirectives) { + public AbstractProgram(List rules, List facts, InlineDirectives inlineDirectives, boolean containsWeakConstraints) { this.rules = rules; this.facts = facts; this.inlineDirectives = inlineDirectives; + this.containsWeakConstraints = containsWeakConstraints; } public List getRules() { @@ -50,4 +52,7 @@ public String toString() { return Util.join(result, rules, ls, ls); } + public boolean containsWeakConstraints() { + return containsWeakConstraints; + } } diff --git a/alpha-core/src/main/java/at/ac/tuwien/kr/alpha/core/programs/InputProgram.java b/alpha-core/src/main/java/at/ac/tuwien/kr/alpha/core/programs/InputProgram.java index e9ba20032..7028452a5 100644 --- a/alpha-core/src/main/java/at/ac/tuwien/kr/alpha/core/programs/InputProgram.java +++ b/alpha-core/src/main/java/at/ac/tuwien/kr/alpha/core/programs/InputProgram.java @@ -27,16 +27,17 @@ */ package at.ac.tuwien.kr.alpha.core.programs; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - import at.ac.tuwien.kr.alpha.api.programs.ASPCore2Program; import at.ac.tuwien.kr.alpha.api.programs.InlineDirectives; import at.ac.tuwien.kr.alpha.api.programs.atoms.Atom; import at.ac.tuwien.kr.alpha.api.rules.Rule; import at.ac.tuwien.kr.alpha.api.rules.heads.Head; import at.ac.tuwien.kr.alpha.core.parser.InlineDirectivesImpl; +import at.ac.tuwien.kr.alpha.core.rules.WeakConstraint; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; /** * Alpha-internal representation of an ASP program, i.e., a set of ASP rules. @@ -45,14 +46,14 @@ */ public class InputProgram extends AbstractProgram> implements ASPCore2Program{ - public static final InputProgram EMPTY = new InputProgram(Collections.emptyList(), Collections.emptyList(), new InlineDirectivesImpl()); + public static final InputProgram EMPTY = new InputProgram(Collections.emptyList(), Collections.emptyList(), new InlineDirectivesImpl(), false); - public InputProgram(List> rules, List facts, InlineDirectives inlineDirectives) { - super(rules, facts, inlineDirectives); + public InputProgram(List> rules, List facts, InlineDirectives inlineDirectives, boolean containsWeakConstraints) { + super(rules, facts, inlineDirectives, containsWeakConstraints); } public InputProgram() { - super(new ArrayList<>(), new ArrayList<>(), new InlineDirectivesImpl()); + super(new ArrayList<>(), new ArrayList<>(), new InlineDirectivesImpl(), false); } public static Builder builder() { @@ -71,6 +72,7 @@ public static class Builder { private List> rules = new ArrayList<>(); private List facts = new ArrayList<>(); private InlineDirectives inlineDirectives = new InlineDirectivesImpl(); + private boolean containsWeakConstraints; public Builder(ASPCore2Program prog) { this.addRules(prog.getRules()); @@ -83,12 +85,15 @@ public Builder() { } public Builder addRules(List> rules) { - this.rules.addAll(rules); + for (Rule rule : rules) { + addRule(rule); + } return this; } public Builder addRule(Rule r) { this.rules.add(r); + this.containsWeakConstraints |= r instanceof WeakConstraint; return this; } @@ -112,7 +117,7 @@ public Builder accumulate(ASPCore2Program prog) { } public InputProgram build() { - return new InputProgram(this.rules, this.facts, this.inlineDirectives); + return new InputProgram(this.rules, this.facts, this.inlineDirectives, this.containsWeakConstraints); } } diff --git a/alpha-core/src/main/java/at/ac/tuwien/kr/alpha/core/programs/InternalProgram.java b/alpha-core/src/main/java/at/ac/tuwien/kr/alpha/core/programs/InternalProgram.java index 205bbe1f0..24e12d5c5 100644 --- a/alpha-core/src/main/java/at/ac/tuwien/kr/alpha/core/programs/InternalProgram.java +++ b/alpha-core/src/main/java/at/ac/tuwien/kr/alpha/core/programs/InternalProgram.java @@ -1,26 +1,25 @@ package at.ac.tuwien.kr.alpha.core.programs; -import java.util.ArrayList; -import java.util.Collections; -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; - -import org.apache.commons.lang3.tuple.ImmutablePair; - import at.ac.tuwien.kr.alpha.api.programs.NormalProgram; import at.ac.tuwien.kr.alpha.api.programs.Predicate; import at.ac.tuwien.kr.alpha.api.programs.atoms.Atom; import at.ac.tuwien.kr.alpha.api.rules.NormalRule; import at.ac.tuwien.kr.alpha.api.rules.Rule; -import at.ac.tuwien.kr.alpha.api.atoms.WeakConstraintAtom; import at.ac.tuwien.kr.alpha.api.rules.heads.NormalHead; import at.ac.tuwien.kr.alpha.commons.substitutions.Instance; +import at.ac.tuwien.kr.alpha.core.atoms.WeakConstraintAtom; import at.ac.tuwien.kr.alpha.core.grounder.FactIntervalEvaluator; import at.ac.tuwien.kr.alpha.core.rules.CompiledRule; import at.ac.tuwien.kr.alpha.core.rules.InternalRule; import at.ac.tuwien.kr.alpha.core.rules.NormalRuleImpl; +import org.apache.commons.lang3.tuple.ImmutableTriple; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; /** * A program in the internal representation needed for grounder and solver, i.e.: rules must have normal heads, all @@ -31,14 +30,12 @@ */ public class InternalProgram extends AbstractProgram implements CompiledProgram { - private final boolean containsWeakConstraints; private final Map> predicateDefiningRules = new LinkedHashMap<>(); private final Map> factsByPredicate = new LinkedHashMap<>(); private final Map rulesById = new LinkedHashMap<>(); public InternalProgram(List rules, List facts, boolean containsWeakConstraints) { - super(rules, facts, null); - this.containsWeakConstraints = containsWeakConstraints; + super(rules, facts, null, containsWeakConstraints); recordFacts(facts); recordRules(rules); } @@ -55,7 +52,7 @@ static ImmutableTriple, List, Boolean> internalizeRules facts.add(r.getHead().getAtom()); } else { internalRules.add(InternalRule.fromNormalRule(r)); - if (r.getHeadAtom() instanceof WeakConstraintAtom) { + if (!r.isConstraint() && r.getHead().getAtom() instanceof WeakConstraintAtom) { containsWeakConstraints = true; } } @@ -111,10 +108,6 @@ public NormalProgram toNormalProgram() { for (CompiledRule rule : getRules()) { normalRules.add(new NormalRuleImpl(rule.getHead(), new ArrayList<>(rule.getBody()))); } - return new NormalProgramImpl(normalRules, getFacts(), getInlineDirectives()); - } - - public boolean containsWeakConstraints() { - return containsWeakConstraints; + return new NormalProgramImpl(normalRules, getFacts(), getInlineDirectives(), containsWeakConstraints()); } } diff --git a/alpha-core/src/main/java/at/ac/tuwien/kr/alpha/core/programs/NormalProgramImpl.java b/alpha-core/src/main/java/at/ac/tuwien/kr/alpha/core/programs/NormalProgramImpl.java index dc886e47a..7d09e884a 100644 --- a/alpha-core/src/main/java/at/ac/tuwien/kr/alpha/core/programs/NormalProgramImpl.java +++ b/alpha-core/src/main/java/at/ac/tuwien/kr/alpha/core/programs/NormalProgramImpl.java @@ -1,8 +1,5 @@ package at.ac.tuwien.kr.alpha.core.programs; -import java.util.ArrayList; -import java.util.List; - import at.ac.tuwien.kr.alpha.api.programs.ASPCore2Program; import at.ac.tuwien.kr.alpha.api.programs.InlineDirectives; import at.ac.tuwien.kr.alpha.api.programs.NormalProgram; @@ -12,6 +9,9 @@ import at.ac.tuwien.kr.alpha.api.rules.heads.Head; import at.ac.tuwien.kr.alpha.core.rules.NormalRuleImpl; +import java.util.ArrayList; +import java.util.List; + /** * A program that only contains NormalRules. * @@ -19,8 +19,8 @@ */ public class NormalProgramImpl extends AbstractProgram implements NormalProgram { - public NormalProgramImpl(List rules, List facts, InlineDirectives inlineDirectives) { - super(rules, facts, inlineDirectives); + public NormalProgramImpl(List rules, List facts, InlineDirectives inlineDirectives, boolean containsWeakConstraints) { + super(rules, facts, inlineDirectives, containsWeakConstraints); } public static NormalProgramImpl fromInputProgram(ASPCore2Program inputProgram) { @@ -28,7 +28,7 @@ public static NormalProgramImpl fromInputProgram(ASPCore2Program inputProgram) { for (Rule r : inputProgram.getRules()) { normalRules.add(NormalRuleImpl.fromBasicRule(r)); } - return new NormalProgramImpl(normalRules, inputProgram.getFacts(), inputProgram.getInlineDirectives()); + return new NormalProgramImpl(normalRules, inputProgram.getFacts(), inputProgram.getInlineDirectives(), inputProgram.containsWeakConstraints()); } } diff --git a/alpha-core/src/main/java/at/ac/tuwien/kr/alpha/core/programs/transformation/ArithmeticTermsRewriting.java b/alpha-core/src/main/java/at/ac/tuwien/kr/alpha/core/programs/transformation/ArithmeticTermsRewriting.java index 259d410a9..cc114dfb6 100644 --- a/alpha-core/src/main/java/at/ac/tuwien/kr/alpha/core/programs/transformation/ArithmeticTermsRewriting.java +++ b/alpha-core/src/main/java/at/ac/tuwien/kr/alpha/core/programs/transformation/ArithmeticTermsRewriting.java @@ -1,8 +1,5 @@ package at.ac.tuwien.kr.alpha.core.programs.transformation; -import java.util.ArrayList; -import java.util.List; - import at.ac.tuwien.kr.alpha.api.programs.NormalProgram; import at.ac.tuwien.kr.alpha.api.programs.atoms.Atom; import at.ac.tuwien.kr.alpha.api.programs.atoms.BasicAtom; @@ -24,6 +21,9 @@ import at.ac.tuwien.kr.alpha.core.programs.NormalProgramImpl; import at.ac.tuwien.kr.alpha.core.rules.NormalRuleImpl; +import java.util.ArrayList; +import java.util.List; + /** * Transforms rules such that arithmetic terms only occur in comparison predicates. * For example p(X+1) :- q(Y/2), r(f(X*2),Y), X-2 = Y*3, X = 0..9. is transformed into @@ -52,7 +52,7 @@ public NormalProgram apply(NormalProgram inputProgram) { return inputProgram; } // Create new program with rewritten rules. - return new NormalProgramImpl(rewrittenRules, inputProgram.getFacts(), inputProgram.getInlineDirectives()); + return new NormalProgramImpl(rewrittenRules, inputProgram.getFacts(), inputProgram.getInlineDirectives(), inputProgram.containsWeakConstraints()); } /** diff --git a/alpha-core/src/main/java/at/ac/tuwien/kr/alpha/core/programs/transformation/IntervalTermToIntervalAtom.java b/alpha-core/src/main/java/at/ac/tuwien/kr/alpha/core/programs/transformation/IntervalTermToIntervalAtom.java index 6aef275b3..31597ff20 100644 --- a/alpha-core/src/main/java/at/ac/tuwien/kr/alpha/core/programs/transformation/IntervalTermToIntervalAtom.java +++ b/alpha-core/src/main/java/at/ac/tuwien/kr/alpha/core/programs/transformation/IntervalTermToIntervalAtom.java @@ -27,11 +27,6 @@ */ package at.ac.tuwien.kr.alpha.core.programs.transformation; -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; - import at.ac.tuwien.kr.alpha.api.programs.NormalProgram; import at.ac.tuwien.kr.alpha.api.programs.atoms.Atom; import at.ac.tuwien.kr.alpha.api.programs.atoms.BasicAtom; @@ -50,6 +45,11 @@ import at.ac.tuwien.kr.alpha.core.programs.NormalProgramImpl; import at.ac.tuwien.kr.alpha.core.rules.NormalRuleImpl; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + /** * Rewrites all interval terms in a rule into a new variable and an IntervalAtom. * Literals of the form "X = A..B" are rewritten with X being used directly and no new variable being introduced. @@ -189,6 +189,6 @@ public NormalProgram apply(NormalProgram inputProgram) { if (!didChange) { return inputProgram; } - return new NormalProgramImpl(rewrittenRules, inputProgram.getFacts(), inputProgram.getInlineDirectives()); + return new NormalProgramImpl(rewrittenRules, inputProgram.getFacts(), inputProgram.getInlineDirectives(), inputProgram.containsWeakConstraints()); } } diff --git a/alpha-core/src/main/java/at/ac/tuwien/kr/alpha/core/programs/transformation/VariableEqualityRemoval.java b/alpha-core/src/main/java/at/ac/tuwien/kr/alpha/core/programs/transformation/VariableEqualityRemoval.java index 1e1ae53a3..50a89100d 100644 --- a/alpha-core/src/main/java/at/ac/tuwien/kr/alpha/core/programs/transformation/VariableEqualityRemoval.java +++ b/alpha-core/src/main/java/at/ac/tuwien/kr/alpha/core/programs/transformation/VariableEqualityRemoval.java @@ -27,16 +27,6 @@ */ package at.ac.tuwien.kr.alpha.core.programs.transformation; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; - import at.ac.tuwien.kr.alpha.api.programs.ASPCore2Program; import at.ac.tuwien.kr.alpha.api.programs.atoms.Atom; import at.ac.tuwien.kr.alpha.api.programs.literals.ComparisonLiteral; @@ -52,6 +42,16 @@ import at.ac.tuwien.kr.alpha.core.programs.InputProgram; import at.ac.tuwien.kr.alpha.core.rules.BasicRule; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; + /** * Removes variable equalities from rules by replacing one variable with the other. * @@ -65,7 +65,7 @@ public ASPCore2Program apply(ASPCore2Program inputProgram) { for (Rule rule : inputProgram.getRules()) { rewrittenRules.add(findAndReplaceVariableEquality(rule)); } - return new InputProgram(rewrittenRules, inputProgram.getFacts(), inputProgram.getInlineDirectives()); + return new InputProgram(rewrittenRules, inputProgram.getFacts(), inputProgram.getInlineDirectives(), inputProgram.containsWeakConstraints()); } private Rule findAndReplaceVariableEquality(Rule rule) { diff --git a/alpha-core/src/main/java/at/ac/tuwien/kr/alpha/core/programs/transformation/WeakConstraintNormalization.java b/alpha-core/src/main/java/at/ac/tuwien/kr/alpha/core/programs/transformation/WeakConstraintNormalization.java index 48fa79dc3..b7c27c2c2 100644 --- a/alpha-core/src/main/java/at/ac/tuwien/kr/alpha/core/programs/transformation/WeakConstraintNormalization.java +++ b/alpha-core/src/main/java/at/ac/tuwien/kr/alpha/core/programs/transformation/WeakConstraintNormalization.java @@ -1,11 +1,14 @@ -package at.ac.tuwien.kr.alpha.grounder.transformation; +package at.ac.tuwien.kr.alpha.core.programs.transformation; -import at.ac.tuwien.kr.alpha.common.program.InputProgram; -import at.ac.tuwien.kr.alpha.common.rule.BasicRule; -import at.ac.tuwien.kr.alpha.common.rule.WeakConstraint; -import at.ac.tuwien.kr.alpha.common.rule.head.Head; -import at.ac.tuwien.kr.alpha.common.rule.head.NormalHead; -import at.ac.tuwien.kr.alpha.grounder.atoms.WeakConstraintAtom; + +import at.ac.tuwien.kr.alpha.api.programs.ASPCore2Program; +import at.ac.tuwien.kr.alpha.api.rules.Rule; +import at.ac.tuwien.kr.alpha.api.rules.heads.Head; +import at.ac.tuwien.kr.alpha.commons.rules.heads.Heads; +import at.ac.tuwien.kr.alpha.core.atoms.WeakConstraintAtom; +import at.ac.tuwien.kr.alpha.core.programs.InputProgram; +import at.ac.tuwien.kr.alpha.core.rules.BasicRule; +import at.ac.tuwien.kr.alpha.core.rules.WeakConstraint; import java.util.ArrayList; @@ -14,21 +17,21 @@ * * Copyright (c) 2020, the Alpha Team. */ -public class WeakConstraintNormalization extends ProgramTransformation { +public class WeakConstraintNormalization extends ProgramTransformation { @Override - public InputProgram apply(InputProgram inputProgram) { + public ASPCore2Program apply(ASPCore2Program inputProgram) { InputProgram.Builder builder = new InputProgram.Builder(); builder.addFacts(inputProgram.getFacts()); builder.addInlineDirectives(inputProgram.getInlineDirectives()); - for (BasicRule rule : inputProgram.getRules()) { + for (Rule rule : inputProgram.getRules()) { if (!(rule instanceof WeakConstraint)) { builder.addRule(rule); continue; } // Create rule with special head atom. WeakConstraint weakConstraint = (WeakConstraint)rule; - Head weakConstraintHead = new NormalHead(WeakConstraintAtom.getInstance(weakConstraint.getWeight(), weakConstraint.getLevel(), weakConstraint.getTermList())); + Head weakConstraintHead = Heads.newNormalHead(WeakConstraintAtom.getInstance(weakConstraint.getWeight(), weakConstraint.getLevel(), weakConstraint.getTermList())); BasicRule rewrittenWeakConstraint = new BasicRule(weakConstraintHead, new ArrayList<>(rule.getBody())); builder.addRule(rewrittenWeakConstraint); } diff --git a/alpha-core/src/main/java/at/ac/tuwien/kr/alpha/core/programs/transformation/aggregates/AggregateOperatorNormalization.java b/alpha-core/src/main/java/at/ac/tuwien/kr/alpha/core/programs/transformation/aggregates/AggregateOperatorNormalization.java index 10625da60..9129a0c6b 100644 --- a/alpha-core/src/main/java/at/ac/tuwien/kr/alpha/core/programs/transformation/aggregates/AggregateOperatorNormalization.java +++ b/alpha-core/src/main/java/at/ac/tuwien/kr/alpha/core/programs/transformation/aggregates/AggregateOperatorNormalization.java @@ -1,9 +1,5 @@ package at.ac.tuwien.kr.alpha.core.programs.transformation.aggregates; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - import at.ac.tuwien.kr.alpha.api.ComparisonOperator; import at.ac.tuwien.kr.alpha.api.programs.atoms.AggregateAtom; import at.ac.tuwien.kr.alpha.api.programs.atoms.AggregateAtom.AggregateFunctionSymbol; @@ -21,6 +17,11 @@ import at.ac.tuwien.kr.alpha.commons.terms.Terms; import at.ac.tuwien.kr.alpha.core.programs.InputProgram; import at.ac.tuwien.kr.alpha.core.rules.BasicRule; +import at.ac.tuwien.kr.alpha.core.rules.WeakConstraint; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; /** * Transforms an {@link InputProgram} such that, for all aggregate (body-)literals, only the comparison operators "=" @@ -42,7 +43,7 @@ * Note that input programs must only contain aggregate literals of form TERM OP #aggr{...} or #aggr{...} OP TERM, * i.e. with only * a left or right term and operator (but not both). When preprocessing programs, apply this transformation AFTER - * {@link at.ac.tuwien.kr.alpha.grounder.transformation.aggregates.AggregateLiteralSplitting}. + * {@link at.ac.tuwien.kr.alpha.core.programs.transformation.aggregates.AggregateLiteralSplitting}. * * Copyright (c) 2020-2021, the Alpha Team. */ @@ -57,6 +58,10 @@ public static Rule normalize(Rule rule) { for (Literal lit : rule.getBody()) { rewrittenBody.addAll(rewriteLiteral(lit)); } + if (rule instanceof WeakConstraint) { + WeakConstraint wcRule = (WeakConstraint) rule; + return new WeakConstraint(rewrittenBody, wcRule.getWeight(), wcRule.getLevel(), wcRule.getTermList()); + } return new BasicRule(rule.getHead(), rewrittenBody); } diff --git a/alpha-core/src/main/java/at/ac/tuwien/kr/alpha/core/programs/transformation/aggregates/encoders/AbstractAggregateEncoder.java b/alpha-core/src/main/java/at/ac/tuwien/kr/alpha/core/programs/transformation/aggregates/encoders/AbstractAggregateEncoder.java index 1e257d30a..e22177c07 100644 --- a/alpha-core/src/main/java/at/ac/tuwien/kr/alpha/core/programs/transformation/aggregates/encoders/AbstractAggregateEncoder.java +++ b/alpha-core/src/main/java/at/ac/tuwien/kr/alpha/core/programs/transformation/aggregates/encoders/AbstractAggregateEncoder.java @@ -1,11 +1,5 @@ package at.ac.tuwien.kr.alpha.core.programs.transformation.aggregates.encoders; -import java.util.ArrayList; -import java.util.List; -import java.util.Set; - -import org.apache.commons.collections4.ListUtils; - import at.ac.tuwien.kr.alpha.api.ComparisonOperator; import at.ac.tuwien.kr.alpha.api.programs.ASPCore2Program; import at.ac.tuwien.kr.alpha.api.programs.Predicate; @@ -26,6 +20,11 @@ import at.ac.tuwien.kr.alpha.core.programs.transformation.PredicateInternalizer; import at.ac.tuwien.kr.alpha.core.programs.transformation.aggregates.AggregateRewritingContext.AggregateInfo; import at.ac.tuwien.kr.alpha.core.rules.BasicRule; +import org.apache.commons.collections4.ListUtils; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; /** * Abstract base class for aggregate encoders. An aggregate encoder provides an encoding for a given aggregate literal, @@ -82,7 +81,7 @@ public ASPCore2Program encodeAggregateLiteral(AggregateInfo aggregateToEncode) { Rule elementRule = encodeAggregateElement(aggregateToEncode, elementToEncode); elementEncodingRules.add(PredicateInternalizer.makePrefixedPredicatesInternal(elementRule, aggregateId)); } - return new InputProgram(ListUtils.union(literalEncoding.getRules(), elementEncodingRules), literalEncoding.getFacts(), new InlineDirectivesImpl()); + return new InputProgram(ListUtils.union(literalEncoding.getRules(), elementEncodingRules), literalEncoding.getFacts(), new InlineDirectivesImpl(), literalEncoding.containsWeakConstraints()); } /** diff --git a/alpha-core/src/main/java/at/ac/tuwien/kr/alpha/core/programs/transformation/aggregates/encoders/StringtemplateBasedAggregateEncoder.java b/alpha-core/src/main/java/at/ac/tuwien/kr/alpha/core/programs/transformation/aggregates/encoders/StringtemplateBasedAggregateEncoder.java index ad8a9c932..06bde21d0 100644 --- a/alpha-core/src/main/java/at/ac/tuwien/kr/alpha/core/programs/transformation/aggregates/encoders/StringtemplateBasedAggregateEncoder.java +++ b/alpha-core/src/main/java/at/ac/tuwien/kr/alpha/core/programs/transformation/aggregates/encoders/StringtemplateBasedAggregateEncoder.java @@ -1,11 +1,5 @@ package at.ac.tuwien.kr.alpha.core.programs.transformation.aggregates.encoders; -import java.util.ArrayList; -import java.util.Collections; - -import org.apache.commons.collections4.ListUtils; -import org.stringtemplate.v4.ST; - import at.ac.tuwien.kr.alpha.api.ComparisonOperator; import at.ac.tuwien.kr.alpha.api.programs.ASPCore2Program; import at.ac.tuwien.kr.alpha.api.programs.ProgramParser; @@ -24,6 +18,11 @@ import at.ac.tuwien.kr.alpha.core.programs.transformation.EnumerationRewriting; import at.ac.tuwien.kr.alpha.core.programs.transformation.aggregates.AggregateRewritingContext.AggregateInfo; import at.ac.tuwien.kr.alpha.core.rules.BasicRule; +import org.apache.commons.collections4.ListUtils; +import org.stringtemplate.v4.ST; + +import java.util.ArrayList; +import java.util.Collections; /** * Abstract base class for aggregate encoders making use of stringtemplates in their rewriting workflow. @@ -86,7 +85,7 @@ protected ASPCore2Program encodeAggregateResult(AggregateInfo aggregateToEncode) // Add the programatically created bound rule and return return new InputProgram(ListUtils.union(coreEncoding.getRules(), Collections.singletonList(boundRule)), coreEncoding.getFacts(), - new InlineDirectivesImpl()); + new InlineDirectivesImpl(), coreEncoding.containsWeakConstraints()); } private String getBoundPredicateName(String aggregateId) { diff --git a/alpha-core/src/main/java/at/ac/tuwien/kr/alpha/core/rules/WeakConstraint.java b/alpha-core/src/main/java/at/ac/tuwien/kr/alpha/core/rules/WeakConstraint.java index 643cc85b3..2554b7877 100644 --- a/alpha-core/src/main/java/at/ac/tuwien/kr/alpha/core/rules/WeakConstraint.java +++ b/alpha-core/src/main/java/at/ac/tuwien/kr/alpha/core/rules/WeakConstraint.java @@ -1,8 +1,8 @@ -package at.ac.tuwien.kr.alpha.common.rule; +package at.ac.tuwien.kr.alpha.core.rules; -import at.ac.tuwien.kr.alpha.Util; -import at.ac.tuwien.kr.alpha.common.atoms.Literal; -import at.ac.tuwien.kr.alpha.common.terms.Term; +import at.ac.tuwien.kr.alpha.api.programs.literals.Literal; +import at.ac.tuwien.kr.alpha.api.terms.Term; +import at.ac.tuwien.kr.alpha.commons.util.Util; import java.util.List; diff --git a/alpha-core/src/main/java/at/ac/tuwien/kr/alpha/core/solver/AtomCallbackManager.java b/alpha-core/src/main/java/at/ac/tuwien/kr/alpha/core/solver/AtomCallbackManager.java index 119f9e146..8b636c1a0 100644 --- a/alpha-core/src/main/java/at/ac/tuwien/kr/alpha/core/solver/AtomCallbackManager.java +++ b/alpha-core/src/main/java/at/ac/tuwien/kr/alpha/core/solver/AtomCallbackManager.java @@ -1,12 +1,12 @@ -package at.ac.tuwien.kr.alpha.solver; +package at.ac.tuwien.kr.alpha.core.solver; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.Arrays; -import static at.ac.tuwien.kr.alpha.Util.arrayGrowthSize; -import static at.ac.tuwien.kr.alpha.Util.oops; +import static at.ac.tuwien.kr.alpha.commons.util.Util.arrayGrowthSize; +import static at.ac.tuwien.kr.alpha.commons.util.Util.oops; /** * Manages the execution of {@link AtomCallback}s, i.e., callbacks when atoms change their truth value. diff --git a/alpha-core/src/main/java/at/ac/tuwien/kr/alpha/core/solver/DefaultSolver.java b/alpha-core/src/main/java/at/ac/tuwien/kr/alpha/core/solver/DefaultSolver.java index 84f610dd7..037f711b2 100644 --- a/alpha-core/src/main/java/at/ac/tuwien/kr/alpha/core/solver/DefaultSolver.java +++ b/alpha-core/src/main/java/at/ac/tuwien/kr/alpha/core/solver/DefaultSolver.java @@ -28,7 +28,7 @@ package at.ac.tuwien.kr.alpha.core.solver; import at.ac.tuwien.kr.alpha.api.AnswerSet; -import at.ac.tuwien.kr.alpha.api.WeightedAnswerSet; +import at.ac.tuwien.kr.alpha.commons.WeightedAnswerSet; import at.ac.tuwien.kr.alpha.api.StatisticsReportingSolver; import at.ac.tuwien.kr.alpha.api.config.SystemConfig; import at.ac.tuwien.kr.alpha.api.grounder.Substitution; diff --git a/alpha-core/src/main/java/at/ac/tuwien/kr/alpha/core/solver/OptimizingSolver.java b/alpha-core/src/main/java/at/ac/tuwien/kr/alpha/core/solver/OptimizingSolver.java index 88283d2b5..2710a515d 100644 --- a/alpha-core/src/main/java/at/ac/tuwien/kr/alpha/core/solver/OptimizingSolver.java +++ b/alpha-core/src/main/java/at/ac/tuwien/kr/alpha/core/solver/OptimizingSolver.java @@ -1,13 +1,13 @@ -package at.ac.tuwien.kr.alpha.solver; +package at.ac.tuwien.kr.alpha.core.solver; -import at.ac.tuwien.kr.alpha.common.AnswerSet; -import at.ac.tuwien.kr.alpha.common.AtomStore; -import at.ac.tuwien.kr.alpha.common.NoGood; -import at.ac.tuwien.kr.alpha.common.WeightedAnswerSet; -import at.ac.tuwien.kr.alpha.config.SystemConfig; -import at.ac.tuwien.kr.alpha.grounder.Grounder; -import at.ac.tuwien.kr.alpha.solver.heuristics.HeuristicsConfiguration; -import at.ac.tuwien.kr.alpha.solver.optimization.WeakConstraintsManagerForBoundedOptimality; +import at.ac.tuwien.kr.alpha.api.AnswerSet; +import at.ac.tuwien.kr.alpha.core.common.AtomStore; +import at.ac.tuwien.kr.alpha.core.common.NoGood; +import at.ac.tuwien.kr.alpha.commons.WeightedAnswerSet; +import at.ac.tuwien.kr.alpha.api.config.SystemConfig; +import at.ac.tuwien.kr.alpha.core.grounder.Grounder; +import at.ac.tuwien.kr.alpha.core.solver.heuristics.HeuristicsConfiguration; +import at.ac.tuwien.kr.alpha.core.solver.optimization.WeakConstraintsManagerForBoundedOptimality; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/alpha-core/src/main/java/at/ac/tuwien/kr/alpha/core/solver/optimization/WeakConstraintAtomCallback.java b/alpha-core/src/main/java/at/ac/tuwien/kr/alpha/core/solver/optimization/WeakConstraintAtomCallback.java index 088a726fd..6d33198e1 100644 --- a/alpha-core/src/main/java/at/ac/tuwien/kr/alpha/core/solver/optimization/WeakConstraintAtomCallback.java +++ b/alpha-core/src/main/java/at/ac/tuwien/kr/alpha/core/solver/optimization/WeakConstraintAtomCallback.java @@ -1,7 +1,7 @@ -package at.ac.tuwien.kr.alpha.solver.optimization; +package at.ac.tuwien.kr.alpha.core.solver.optimization; -import at.ac.tuwien.kr.alpha.solver.AtomCallbackManager; -import at.ac.tuwien.kr.alpha.solver.ThriceTruth; +import at.ac.tuwien.kr.alpha.core.solver.AtomCallbackManager; +import at.ac.tuwien.kr.alpha.core.solver.ThriceTruth; import java.util.Objects; diff --git a/alpha-core/src/main/java/at/ac/tuwien/kr/alpha/core/solver/optimization/WeakConstraintsManager.java b/alpha-core/src/main/java/at/ac/tuwien/kr/alpha/core/solver/optimization/WeakConstraintsManager.java index c4d6dc32c..7110d3c81 100644 --- a/alpha-core/src/main/java/at/ac/tuwien/kr/alpha/core/solver/optimization/WeakConstraintsManager.java +++ b/alpha-core/src/main/java/at/ac/tuwien/kr/alpha/core/solver/optimization/WeakConstraintsManager.java @@ -1,6 +1,6 @@ -package at.ac.tuwien.kr.alpha.solver.optimization; +package at.ac.tuwien.kr.alpha.core.solver.optimization; -import at.ac.tuwien.kr.alpha.solver.Checkable; +import at.ac.tuwien.kr.alpha.core.solver.Checkable; import org.apache.commons.lang3.tuple.Triple; import java.util.List; diff --git a/alpha-core/src/main/java/at/ac/tuwien/kr/alpha/core/solver/optimization/WeakConstraintsManagerForBoundedOptimality.java b/alpha-core/src/main/java/at/ac/tuwien/kr/alpha/core/solver/optimization/WeakConstraintsManagerForBoundedOptimality.java index fb2aa9f41..13eda14cf 100644 --- a/alpha-core/src/main/java/at/ac/tuwien/kr/alpha/core/solver/optimization/WeakConstraintsManagerForBoundedOptimality.java +++ b/alpha-core/src/main/java/at/ac/tuwien/kr/alpha/core/solver/optimization/WeakConstraintsManagerForBoundedOptimality.java @@ -1,8 +1,8 @@ -package at.ac.tuwien.kr.alpha.solver.optimization; +package at.ac.tuwien.kr.alpha.core.solver.optimization; -import at.ac.tuwien.kr.alpha.common.NoGood; -import at.ac.tuwien.kr.alpha.solver.ThriceTruth; -import at.ac.tuwien.kr.alpha.solver.WritableAssignment; +import at.ac.tuwien.kr.alpha.core.common.NoGood; +import at.ac.tuwien.kr.alpha.core.solver.ThriceTruth; +import at.ac.tuwien.kr.alpha.core.solver.WritableAssignment; import org.apache.commons.lang3.tuple.Triple; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -14,9 +14,9 @@ import java.util.TreeMap; import java.util.stream.Collectors; -import static at.ac.tuwien.kr.alpha.Util.oops; -import static at.ac.tuwien.kr.alpha.common.Literals.atomToLiteral; -import static at.ac.tuwien.kr.alpha.solver.ThriceTruth.MBT; +import static at.ac.tuwien.kr.alpha.commons.util.Util.oops; +import static at.ac.tuwien.kr.alpha.core.atoms.Literals.atomToLiteral; +import static at.ac.tuwien.kr.alpha.core.solver.ThriceTruth.MBT; /** * Manages weak constraints: stores the value of the current partial assignment, the best known value of any answer-set, diff --git a/alpha-core/src/main/java/at/ac/tuwien/kr/alpha/core/solver/optimization/WeakConstraintsManagerForUnboundedEnumeration.java b/alpha-core/src/main/java/at/ac/tuwien/kr/alpha/core/solver/optimization/WeakConstraintsManagerForUnboundedEnumeration.java index e9f9fed7b..b822deb6c 100644 --- a/alpha-core/src/main/java/at/ac/tuwien/kr/alpha/core/solver/optimization/WeakConstraintsManagerForUnboundedEnumeration.java +++ b/alpha-core/src/main/java/at/ac/tuwien/kr/alpha/core/solver/optimization/WeakConstraintsManagerForUnboundedEnumeration.java @@ -1,6 +1,6 @@ -package at.ac.tuwien.kr.alpha.solver.optimization; +package at.ac.tuwien.kr.alpha.core.solver.optimization; -import at.ac.tuwien.kr.alpha.solver.WritableAssignment; +import at.ac.tuwien.kr.alpha.core.solver.WritableAssignment; import org.apache.commons.lang3.tuple.Triple; import java.util.ArrayList; diff --git a/alpha-core/src/main/java/at/ac/tuwien/kr/alpha/core/solver/optimization/WeightAtLevelsManager.java b/alpha-core/src/main/java/at/ac/tuwien/kr/alpha/core/solver/optimization/WeightAtLevelsManager.java index af2720228..2d619c5d9 100644 --- a/alpha-core/src/main/java/at/ac/tuwien/kr/alpha/core/solver/optimization/WeightAtLevelsManager.java +++ b/alpha-core/src/main/java/at/ac/tuwien/kr/alpha/core/solver/optimization/WeightAtLevelsManager.java @@ -1,16 +1,15 @@ -package at.ac.tuwien.kr.alpha.solver.optimization; +package at.ac.tuwien.kr.alpha.core.solver.optimization; -import at.ac.tuwien.kr.alpha.solver.Checkable; +import at.ac.tuwien.kr.alpha.commons.WeightedAnswerSet; +import at.ac.tuwien.kr.alpha.core.solver.Checkable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.ArrayList; -import java.util.HashMap; -import java.util.LinkedHashMap; import java.util.Map; import java.util.TreeMap; -import static at.ac.tuwien.kr.alpha.Util.oops; +import static at.ac.tuwien.kr.alpha.commons.util.Util.oops; import static java.lang.Math.max; /** @@ -59,23 +58,11 @@ public WeightAtLevelsManager(String maximumWeightAtLevels) { } private void initializeFromString(String maximumWeightAtLevels) { - HashMap levelsToWeight = new LinkedHashMap<>(); + TreeMap levelsToWeight = WeightedAnswerSet.weightPerLevelFromString(maximumWeightAtLevels); int maxLevel = 0; - // Parse maximumWeightAtLevels string. - for (String weightAtLevel : maximumWeightAtLevels.split(",")) { - String[] weightAndLevel = weightAtLevel.split("@"); - if (weightAndLevel.length != 2) { - throw new IllegalArgumentException("Could not parse given comma-separated list of weight@level pairs. Given input was: " + maximumWeightAtLevels); - } - int weight = Integer.parseInt(weightAndLevel[0]); - Integer level = Integer.parseInt(weightAndLevel[1]); - if (weight == 0) { - continue; // Skip weights of zero. - } - levelsToWeight.putIfAbsent(level, 0); - levelsToWeight.put(level, weight + levelsToWeight.get(level)); - if (level > maxLevel) { - maxLevel = level; + for (Map.Entry levelAndWeight : levelsToWeight.entrySet()) { + if (levelAndWeight.getKey() > maxLevel && levelAndWeight.getValue() > 0) { + maxLevel = levelAndWeight.getKey(); } } // Initialize according to parsed weights at levels. diff --git a/alpha-core/src/test/java/at/ac/tuwien/kr/alpha/core/WeightedAnswerSetTest.java b/alpha-core/src/test/java/at/ac/tuwien/kr/alpha/core/WeightedAnswerSetTest.java deleted file mode 100644 index e3fa0d68a..000000000 --- a/alpha-core/src/test/java/at/ac/tuwien/kr/alpha/core/WeightedAnswerSetTest.java +++ /dev/null @@ -1,69 +0,0 @@ -package at.ac.tuwien.kr.alpha.common; - -import at.ac.tuwien.kr.alpha.test.util.TestUtils; -import org.junit.jupiter.api.Test; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotEquals; - -/** - * Copyright (c) 2021, the Alpha Team. - */ -public class WeightedAnswerSetTest { - - @Test - public void compareWeightedAnswerSets() { - String was1Atoms = "a, p(b)"; - WeightedAnswerSet was1 = TestUtils.weightedAnswerSetFromStrings(was1Atoms, "1@1"); - WeightedAnswerSet was2 = TestUtils.weightedAnswerSetFromStrings("a", "1@1, 2@3"); - WeightedAnswerSet was1Same = TestUtils.weightedAnswerSetFromStrings(was1Atoms, "1@1"); - - assertFalse(was1.getPredicates().isEmpty()); - assertFalse(was2.getPredicates().isEmpty()); - assertNotEquals(was1, was2); - assertEquals(was1Same, was1); - assertEquals(was1.getPredicateInstances(), was1Same.getPredicateInstances()); - } - - @Test - public void compareAnswerSetsSameAtoms() { - String was3Atoms = "q(0,1), b, foo(a)"; - WeightedAnswerSet was3 = TestUtils.weightedAnswerSetFromStrings(was3Atoms, "1@1, 2@3"); - WeightedAnswerSet was3AtomsNotWeights = TestUtils.weightedAnswerSetFromStrings(was3Atoms, "2@1, 2@3"); - - assertFalse(was3.getPredicates().isEmpty()); - assertNotEquals(was3, was3AtomsNotWeights); - assertEquals(was3.getPredicateInstances(), was3AtomsNotWeights.getPredicateInstances()); - } - - - @Test - public void compareAnswerSetsSameWeights() { - String was4Weights = "1@1, 4@3"; - WeightedAnswerSet was4 = TestUtils.weightedAnswerSetFromStrings("b, c", was4Weights); - WeightedAnswerSet was5 = TestUtils.weightedAnswerSetFromStrings("b, d", was4Weights); - - assertFalse(was4.getPredicates().isEmpty()); - assertFalse(was5.getPredicates().isEmpty()); - assertEquals(0, was4.compareWeights(was5)); - assertNotEquals(was4, was5); - assertNotEquals(was5, was4); - } - - @Test - public void compareWeights() { - WeightedAnswerSet was6 = TestUtils.weightedAnswerSetFromStrings("a", "2@3"); - WeightedAnswerSet was7 = TestUtils.weightedAnswerSetFromStrings("a", "1@1, 1@3"); - WeightedAnswerSet was8 = TestUtils.weightedAnswerSetFromStrings("a", "1@-3, 1@1, 1@3"); - assertEquals(-1, was7.compareWeights(was6)); - assertEquals(1, was6.compareWeights(was7)); - - assertEquals(-1, was7.compareWeights(was8)); - assertEquals(1, was8.compareWeights(was7)); - - assertEquals(-1, was8.compareWeights(was6)); - assertEquals(1, was6.compareWeights(was8)); - } - -} \ No newline at end of file diff --git a/alpha-core/src/test/java/at/ac/tuwien/kr/alpha/core/grounder/ChoiceGrounder.java b/alpha-core/src/test/java/at/ac/tuwien/kr/alpha/core/grounder/ChoiceGrounder.java index 71165bd1f..ed189c467 100644 --- a/alpha-core/src/test/java/at/ac/tuwien/kr/alpha/core/grounder/ChoiceGrounder.java +++ b/alpha-core/src/test/java/at/ac/tuwien/kr/alpha/core/grounder/ChoiceGrounder.java @@ -27,30 +27,6 @@ */ package at.ac.tuwien.kr.alpha.core.grounder; -import static at.ac.tuwien.kr.alpha.commons.util.Util.entriesToMap; -import static at.ac.tuwien.kr.alpha.commons.util.Util.entry; -import static at.ac.tuwien.kr.alpha.core.common.NoGood.headFirst; -import static at.ac.tuwien.kr.alpha.core.common.NoGoodTest.fromOldLiterals; -import static java.util.Arrays.asList; -import static java.util.Collections.singleton; -import org.apache.commons.lang3.tuple.Triple; - -import java.util.Arrays; -import java.util.Collections; -import static java.util.Collections.emptyList; -import static java.util.Collections.singleton; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.SortedSet; -import java.util.TreeSet; -import java.util.stream.Stream; - -import org.apache.commons.lang3.tuple.ImmutablePair; -import org.apache.commons.lang3.tuple.Pair; - import at.ac.tuwien.kr.alpha.api.AnswerSet; import at.ac.tuwien.kr.alpha.api.programs.Predicate; import at.ac.tuwien.kr.alpha.api.programs.atoms.Atom; @@ -70,6 +46,28 @@ import at.ac.tuwien.kr.alpha.core.rules.BasicRule; import at.ac.tuwien.kr.alpha.core.rules.InternalRule; import at.ac.tuwien.kr.alpha.core.rules.NormalRuleImpl; +import org.apache.commons.lang3.tuple.ImmutablePair; +import org.apache.commons.lang3.tuple.Pair; +import org.apache.commons.lang3.tuple.Triple; + +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.SortedSet; +import java.util.TreeSet; +import java.util.stream.Stream; + +import static at.ac.tuwien.kr.alpha.commons.util.Util.entriesToMap; +import static at.ac.tuwien.kr.alpha.commons.util.Util.entry; +import static at.ac.tuwien.kr.alpha.core.common.NoGood.headFirst; +import static at.ac.tuwien.kr.alpha.core.common.NoGoodTest.fromOldLiterals; +import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; +import static java.util.Collections.singleton; /** * Represents a small ASP program with choices {@code { aa :- not bb. bb :- not aa. }}. diff --git a/alpha-core/src/test/java/at/ac/tuwien/kr/alpha/core/solver/SolverTests.java b/alpha-core/src/test/java/at/ac/tuwien/kr/alpha/core/solver/SolverTests.java index 911a2dd32..a544966a2 100644 --- a/alpha-core/src/test/java/at/ac/tuwien/kr/alpha/core/solver/SolverTests.java +++ b/alpha-core/src/test/java/at/ac/tuwien/kr/alpha/core/solver/SolverTests.java @@ -27,21 +27,6 @@ */ package at.ac.tuwien.kr.alpha.core.solver; -import static at.ac.tuwien.kr.alpha.core.test.util.TestUtils.assertRegressionTestAnswerSet; -import static at.ac.tuwien.kr.alpha.core.test.util.TestUtils.assertRegressionTestAnswerSets; -import static at.ac.tuwien.kr.alpha.core.test.util.TestUtils.assertRegressionTestAnswerSetsWithBase; -import static at.ac.tuwien.kr.alpha.core.test.util.TestUtils.buildSolverForRegressionTest; -import static at.ac.tuwien.kr.alpha.core.test.util.TestUtils.collectRegressionTestAnswerSets; -import static java.util.Collections.singleton; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import java.util.SortedSet; - import at.ac.tuwien.kr.alpha.api.AnswerSet; import at.ac.tuwien.kr.alpha.api.Solver; import at.ac.tuwien.kr.alpha.api.programs.Predicate; @@ -59,6 +44,21 @@ import at.ac.tuwien.kr.alpha.core.programs.InputProgram; import at.ac.tuwien.kr.alpha.core.test.util.AnswerSetsParser; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.SortedSet; + +import static at.ac.tuwien.kr.alpha.core.test.util.TestUtils.assertRegressionTestAnswerSet; +import static at.ac.tuwien.kr.alpha.core.test.util.TestUtils.assertRegressionTestAnswerSets; +import static at.ac.tuwien.kr.alpha.core.test.util.TestUtils.assertRegressionTestAnswerSetsWithBase; +import static at.ac.tuwien.kr.alpha.core.test.util.TestUtils.buildSolverForRegressionTest; +import static at.ac.tuwien.kr.alpha.core.test.util.TestUtils.collectRegressionTestAnswerSets; +import static java.util.Collections.singleton; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + public class SolverTests { private static class Thingy implements Comparable { @@ -80,9 +80,8 @@ public void testObjectProgram(RegressionTestConfig cfg) { final Atom fact = Atoms.newBasicAtom(Predicates.getPredicate("foo", 1), Terms.newConstant(thingy)); final InputProgram program = new InputProgram( - Collections.emptyList(), - Collections.singletonList(fact), - new InlineDirectivesImpl() + Collections.emptyList(), Collections.singletonList(fact), + new InlineDirectivesImpl(), false ); assertEquals(singleton(new AnswerSetBuilder() diff --git a/alpha-core/src/test/java/at/ac/tuwien/kr/alpha/core/solver/WeakConstraintsTests.java b/alpha-core/src/test/java/at/ac/tuwien/kr/alpha/core/solver/WeakConstraintsTests.java index 5833f89e5..5c250f0cf 100644 --- a/alpha-core/src/test/java/at/ac/tuwien/kr/alpha/core/solver/WeakConstraintsTests.java +++ b/alpha-core/src/test/java/at/ac/tuwien/kr/alpha/core/solver/WeakConstraintsTests.java @@ -1,12 +1,12 @@ -package at.ac.tuwien.kr.alpha.solver; +package at.ac.tuwien.kr.alpha.core.solver; -import at.ac.tuwien.kr.alpha.common.AnswerSet; -import at.ac.tuwien.kr.alpha.test.util.TestUtils; +import at.ac.tuwien.kr.alpha.api.AnswerSet; +import at.ac.tuwien.kr.alpha.core.test.util.TestUtils; import java.util.Set; -import static at.ac.tuwien.kr.alpha.test.util.TestUtils.assertOptimumAnswerSetEquals; -import static at.ac.tuwien.kr.alpha.test.util.TestUtils.collectRegressionTestOptimalAnswerSets; +import static at.ac.tuwien.kr.alpha.core.test.util.TestUtils.assertOptimumAnswerSetEquals; +import static at.ac.tuwien.kr.alpha.core.test.util.TestUtils.collectRegressionTestOptimalAnswerSets; import static org.junit.jupiter.api.Assertions.assertThrows; /** diff --git a/alpha-core/src/test/java/at/ac/tuwien/kr/alpha/core/solver/optimization/WeightAtLevelsManagerTest.java b/alpha-core/src/test/java/at/ac/tuwien/kr/alpha/core/solver/optimization/WeightAtLevelsManagerTest.java index f1514a62b..f9b63e598 100644 --- a/alpha-core/src/test/java/at/ac/tuwien/kr/alpha/core/solver/optimization/WeightAtLevelsManagerTest.java +++ b/alpha-core/src/test/java/at/ac/tuwien/kr/alpha/core/solver/optimization/WeightAtLevelsManagerTest.java @@ -1,4 +1,4 @@ -package at.ac.tuwien.kr.alpha.solver.optimization; +package at.ac.tuwien.kr.alpha.core.solver.optimization; import org.junit.jupiter.api.Test; diff --git a/alpha-core/src/test/java/at/ac/tuwien/kr/alpha/core/test/util/TestUtils.java b/alpha-core/src/test/java/at/ac/tuwien/kr/alpha/core/test/util/TestUtils.java index e6400f5ed..1fd6a0ef6 100644 --- a/alpha-core/src/test/java/at/ac/tuwien/kr/alpha/core/test/util/TestUtils.java +++ b/alpha-core/src/test/java/at/ac/tuwien/kr/alpha/core/test/util/TestUtils.java @@ -1,26 +1,6 @@ package at.ac.tuwien.kr.alpha.core.test.util; -import static java.util.Collections.emptySet; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTimeoutPreemptively; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.time.Duration; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Set; -import java.util.StringJoiner; -import java.util.stream.Collectors; - -import org.apache.commons.lang3.StringUtils; -import org.junit.jupiter.api.Assumptions; -import org.junit.jupiter.api.function.Executable; - import at.ac.tuwien.kr.alpha.api.AnswerSet; -import at.ac.tuwien.kr.alpha.api.WeightedAnswerSet; import at.ac.tuwien.kr.alpha.api.Solver; import at.ac.tuwien.kr.alpha.api.config.Heuristic; import at.ac.tuwien.kr.alpha.api.config.SystemConfig; @@ -32,6 +12,7 @@ import at.ac.tuwien.kr.alpha.api.programs.atoms.BasicAtom; import at.ac.tuwien.kr.alpha.api.terms.Term; import at.ac.tuwien.kr.alpha.commons.Predicates; +import at.ac.tuwien.kr.alpha.commons.WeightedAnswerSet; import at.ac.tuwien.kr.alpha.commons.atoms.Atoms; import at.ac.tuwien.kr.alpha.commons.terms.Terms; import at.ac.tuwien.kr.alpha.core.common.AtomStore; @@ -46,6 +27,24 @@ import at.ac.tuwien.kr.alpha.core.programs.transformation.StratifiedEvaluation; import at.ac.tuwien.kr.alpha.core.solver.RegressionTestConfig; import at.ac.tuwien.kr.alpha.core.solver.SolverFactory; +import org.apache.commons.lang3.StringUtils; +import org.junit.jupiter.api.Assumptions; +import org.junit.jupiter.api.function.Executable; + +import java.time.Duration; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; +import java.util.StringJoiner; +import java.util.stream.Collectors; + +import static java.util.Collections.emptySet; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTimeoutPreemptively; +import static org.junit.jupiter.api.Assertions.assertTrue; public class TestUtils { @@ -167,14 +166,14 @@ public static Solver buildSolverForRegressionTest(ASPCore2Program prog, Regressi return buildSolverFromSystemConfig(prog, cfg.toSystemConfig()); } - public static Solver buildSolverForOptimizationRegressionTest(InputProgram prog, RegressionTestConfig cfg) { + public static Solver buildSolverForOptimizationRegressionTest(ASPCore2Program prog, RegressionTestConfig cfg) { SystemConfig systemConfig = cfg.toSystemConfig(); systemConfig.setAnswerSetOptimizationEnabled(true); return buildSolverFromSystemConfig(prog, systemConfig); } public static Set collectRegressionTestOptimalAnswerSets(String prog, RegressionTestConfig config) { - return buildSolverForOptimizationRegressionTest(new ProgramParser().parse(prog), config).collectSet(); + return buildSolverForOptimizationRegressionTest(new ProgramParserImpl().parse(prog), config).collectSet(); } public static Solver buildSolverForRegressionTest(String prog, RegressionTestConfig cfg) { return buildSolverFromSystemConfig(new ProgramParserImpl().parse(prog), cfg.toSystemConfig()); @@ -226,15 +225,8 @@ public static void ignoreTestForSimplifiedSumAggregates(RegressionTestConfig cfg Assumptions.assumeTrue(cfg.isSupportNegativeSumElements()); } public static WeightedAnswerSet weightedAnswerSetFromStrings(String basicAnswerSetAsString, String weightAtLevelsAsString) { - BasicAnswerSet basicAnswerSet = (BasicAnswerSet) AnswerSetsParser.parse("{ " + basicAnswerSetAsString + " }").iterator().next(); - // Extract weights at levels from given string. - String[] weightsAtLevels = weightAtLevelsAsString.split(", "); - TreeMap weightAtLevelsTreeMap = new TreeMap<>(); - for (String weightsAtLevel : weightsAtLevels) { - String[] wAtL = weightsAtLevel.split("@"); - weightAtLevelsTreeMap.put(Integer.parseInt(wAtL[1]), Integer.parseInt(wAtL[0])); - } - return new WeightedAnswerSet(basicAnswerSet, weightAtLevelsTreeMap); + AnswerSet basicAnswerSet = AnswerSetsParser.parse("{ " + basicAnswerSetAsString + " }").iterator().next(); + return new WeightedAnswerSet(basicAnswerSet, WeightedAnswerSet.weightPerLevelFromString(weightAtLevelsAsString)); } public static void assertOptimumAnswerSetEquals(String expectedOptimumAnswerSet, String expectedWeightsAtLevels, Set actual) { diff --git a/alpha-solver/src/main/java/at/ac/tuwien/kr/alpha/api/impl/AlphaImpl.java b/alpha-solver/src/main/java/at/ac/tuwien/kr/alpha/api/impl/AlphaImpl.java index c49a5ca39..98eb3d061 100644 --- a/alpha-solver/src/main/java/at/ac/tuwien/kr/alpha/api/impl/AlphaImpl.java +++ b/alpha-solver/src/main/java/at/ac/tuwien/kr/alpha/api/impl/AlphaImpl.java @@ -27,24 +27,6 @@ */ package at.ac.tuwien.kr.alpha.api.impl; -import java.io.IOException; -import java.io.InputStream; -import java.nio.channels.Channels; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import org.apache.commons.lang3.StringUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.google.common.annotations.VisibleForTesting; - import at.ac.tuwien.kr.alpha.api.Alpha; import at.ac.tuwien.kr.alpha.api.AnswerSet; import at.ac.tuwien.kr.alpha.api.DebugSolvingContext; @@ -72,6 +54,22 @@ import at.ac.tuwien.kr.alpha.core.programs.transformation.NormalizeProgramTransformation; import at.ac.tuwien.kr.alpha.core.programs.transformation.StratifiedEvaluation; import at.ac.tuwien.kr.alpha.core.solver.SolverFactory; +import com.google.common.annotations.VisibleForTesting; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.channels.Channels; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.Stream; public class AlphaImpl implements Alpha { @@ -144,7 +142,7 @@ InternalProgram performProgramPreprocessing(NormalProgram program) { LOGGER.debug("Preprocessing InternalProgram!"); InternalProgram retVal = InternalProgram.fromNormalProgram(program); if (config.isEvaluateStratifiedPart()) { - AnalyzedProgram analyzed = new AnalyzedProgram(retVal.getRules(), retVal.getFacts()); + AnalyzedProgram analyzed = new AnalyzedProgram(retVal.getRules(), retVal.getFacts(), program.containsWeakConstraints()); retVal = new StratifiedEvaluation().apply(analyzed); } return retVal; diff --git a/alpha-solver/src/test/java/at/ac/tuwien/kr/alpha/api/impl/AlphaImplTest.java b/alpha-solver/src/test/java/at/ac/tuwien/kr/alpha/api/impl/AlphaImplTest.java index 176862d40..2e616aa40 100644 --- a/alpha-solver/src/test/java/at/ac/tuwien/kr/alpha/api/impl/AlphaImplTest.java +++ b/alpha-solver/src/test/java/at/ac/tuwien/kr/alpha/api/impl/AlphaImplTest.java @@ -27,34 +27,6 @@ */ package at.ac.tuwien.kr.alpha.api.impl; -import static java.util.Arrays.asList; -import static java.util.Collections.emptyList; -import static java.util.Collections.emptySet; -import static java.util.Collections.singleton; -import static java.util.Collections.singletonList; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.nio.file.StandardOpenOption; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashSet; -import java.util.List; -import java.util.Optional; -import java.util.Set; -import java.util.stream.Collectors; - -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.Test; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import at.ac.tuwien.kr.alpha.api.Alpha; import at.ac.tuwien.kr.alpha.api.AnswerSet; import at.ac.tuwien.kr.alpha.api.config.Heuristic; @@ -79,6 +51,33 @@ import at.ac.tuwien.kr.alpha.core.programs.CompiledProgram; import at.ac.tuwien.kr.alpha.core.programs.InputProgram; import at.ac.tuwien.kr.alpha.core.rules.BasicRule; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardOpenOption; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; + +import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; +import static java.util.Collections.emptySet; +import static java.util.Collections.singleton; +import static java.util.Collections.singletonList; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; public class AlphaImplTest { @@ -323,7 +322,7 @@ public void withExternalSubtype() throws Exception { Alpha system = new AlphaImpl(); - InputProgram prog = new InputProgram(singletonList(rule), emptyList(), new InlineDirectivesImpl()); + InputProgram prog = new InputProgram(singletonList(rule), emptyList(), new InlineDirectivesImpl(), false); Set actual = system.solve(prog).collect(Collectors.toSet()); Set expected = new HashSet<>(singletonList(new AnswerSetBuilder().predicate("p").instance("x").build())); diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 7454180f2ae8848c63b8b4dea2cb829da983f2fa..943f0cbfa754578e88a3dae77fce6e3dea56edbf 100644 GIT binary patch delta 36900 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

yHlr~Qj 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 GTKgBj+ffbx diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index f42e62f37..2b22d057a 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-all.zip +networkTimeout=10000 zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index c53aefaa5..65dcd68d6 100755 --- a/gradlew +++ b/gradlew @@ -1,7 +1,7 @@ #!/bin/sh # -# Copyright © 2015-2021 the original authors. +# Copyright © 2015-2021 the original authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -32,10 +32,10 @@ # Busybox and similar reduced shells will NOT work, because this script # requires all of these POSIX shell features: # * functions; -# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», -# «${var#prefix}», «${var%suffix}», and «$( cmd )»; -# * compound commands having a testable exit status, especially «case»; -# * various built-in commands including «command», «set», and «ulimit». +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». # # Important for patching: # @@ -55,7 +55,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -80,10 +80,10 @@ do esac done -APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit - -APP_NAME="Gradle" +# This is normally unused +# shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' @@ -143,12 +143,16 @@ fi if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then case $MAX_FD in #( max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac case $MAX_FD in #( '' | soft) :;; #( *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac @@ -205,6 +209,12 @@ set -- \ org.gradle.wrapper.GradleWrapperMain \ "$@" +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + # Use "xargs" to parse quoted args. # # With -n1 it outputs one arg per line, with the quotes and backslashes removed. diff --git a/gradlew.bat b/gradlew.bat index ac1b06f93..6689b85be 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -14,7 +14,7 @@ @rem limitations under the License. @rem -@if "%DEBUG%" == "" @echo off +@if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @@ -25,7 +25,8 @@ if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @@ -40,7 +41,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto execute +if %ERRORLEVEL% equ 0 goto execute echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. @@ -75,13 +76,15 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar :end @rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd +if %ERRORLEVEL% equ 0 goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% :mainEnd if "%OS%"=="Windows_NT" endlocal diff --git a/src/main/java/at/ac/tuwien/kr/alpha/api/Alpha.java b/src/main/java/at/ac/tuwien/kr/alpha/api/Alpha.java deleted file mode 100644 index caf3c37b8..000000000 --- a/src/main/java/at/ac/tuwien/kr/alpha/api/Alpha.java +++ /dev/null @@ -1,227 +0,0 @@ -/** - * Copyright (c) 2017-2019, the Alpha Team. - * All rights reserved. - * - * Additional changes made by Siemens. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1) Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2) Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package at.ac.tuwien.kr.alpha.api; - -import org.antlr.v4.runtime.CharStream; -import org.antlr.v4.runtime.CharStreams; -import org.apache.commons.lang3.StringUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.IOException; -import java.nio.charset.CodingErrorAction; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import at.ac.tuwien.kr.alpha.Util; -import at.ac.tuwien.kr.alpha.common.AnswerSet; -import at.ac.tuwien.kr.alpha.common.AtomStore; -import at.ac.tuwien.kr.alpha.common.AtomStoreImpl; -import at.ac.tuwien.kr.alpha.common.Predicate; -import at.ac.tuwien.kr.alpha.common.fixedinterpretations.PredicateInterpretation; -import at.ac.tuwien.kr.alpha.common.program.AnalyzedProgram; -import at.ac.tuwien.kr.alpha.common.program.InputProgram; -import at.ac.tuwien.kr.alpha.common.program.InternalProgram; -import at.ac.tuwien.kr.alpha.common.program.NormalProgram; -import at.ac.tuwien.kr.alpha.config.InputConfig; -import at.ac.tuwien.kr.alpha.config.SystemConfig; -import at.ac.tuwien.kr.alpha.grounder.Grounder; -import at.ac.tuwien.kr.alpha.grounder.GrounderFactory; -import at.ac.tuwien.kr.alpha.grounder.heuristics.GrounderHeuristicsConfiguration; -import at.ac.tuwien.kr.alpha.grounder.parser.ProgramParser; -import at.ac.tuwien.kr.alpha.grounder.transformation.NormalizeProgramTransformation; -import at.ac.tuwien.kr.alpha.grounder.transformation.StratifiedEvaluation; -import at.ac.tuwien.kr.alpha.solver.Solver; -import at.ac.tuwien.kr.alpha.solver.SolverFactory; - -public class Alpha { - - private static final Logger LOGGER = LoggerFactory.getLogger(Alpha.class); - - private SystemConfig config = new SystemConfig(); // The config is initialized with default values. - - public Alpha(SystemConfig cfg) { - this.config = cfg; - } - - public Alpha() { - } - - public InputProgram readProgram(InputConfig cfg) throws IOException { - InputProgram.Builder prgBuilder = InputProgram.builder(); - InputProgram tmpProg; - if (!cfg.getFiles().isEmpty()) { - tmpProg = readProgramFiles(cfg.isLiterate(), cfg.getPredicateMethods(), cfg.getFiles()); - prgBuilder.accumulate(tmpProg); - } - if (!cfg.getAspStrings().isEmpty()) { - tmpProg = readProgramString(StringUtils.join(cfg.getAspStrings(), System.lineSeparator()), cfg.getPredicateMethods()); - prgBuilder.accumulate(tmpProg); - } - return prgBuilder.build(); - } - - public InputProgram readProgramFiles(boolean literate, Map externals, List paths) throws IOException { - return readProgramFiles(literate, externals, paths.stream().map(Paths::get).collect(Collectors.toList()).toArray(new Path[] {})); - } - - public InputProgram readProgramFiles(boolean literate, Map externals, Path... paths) throws IOException { - ProgramParser parser = new ProgramParser(externals); - InputProgram.Builder prgBuilder = InputProgram.builder(); - InputProgram tmpProg; - for (Path path : paths) { - CharStream stream; - if (!literate) { - stream = CharStreams.fromPath(path); - } else { - stream = CharStreams.fromChannel(Util.streamToChannel(Util.literate(Files.lines(path))), 4096, CodingErrorAction.REPLACE, path.toString()); - } - tmpProg = parser.parse(stream); - prgBuilder.accumulate(tmpProg); - } - return prgBuilder.build(); - } - - public InputProgram readProgramString(String aspString, Map externals) { - ProgramParser parser = new ProgramParser(externals); - return parser.parse(aspString); - } - - public InputProgram readProgramString(String aspString) { - return readProgramString(aspString, null); - } - - public NormalProgram normalizeProgram(InputProgram program) { - return new NormalizeProgramTransformation(config.getAggregateRewritingConfig()).apply(program); - } - - public InternalProgram performProgramPreprocessing(InternalProgram program) { - LOGGER.debug("Preprocessing InternalProgram!"); - InternalProgram retVal = program; - if (config.isEvaluateStratifiedPart()) { - AnalyzedProgram analyzed = new AnalyzedProgram(program.getRules(), program.getFacts(), program.containsWeakConstraints()); - retVal = new StratifiedEvaluation().apply(analyzed); - } - return retVal; - } - - public InternalProgram performProgramPreprocessing(AnalyzedProgram program) { - LOGGER.debug("Preprocessing AnalyzedProgram!"); - InternalProgram retVal = program; - if (config.isEvaluateStratifiedPart()) { - retVal = new StratifiedEvaluation().apply(program); - } - return retVal; - } - - /** - * Convenience method - overloaded version of solve({@link InternalProgram}) for cases where details of the - * program analysis and normalization aren't of interest. - */ - public Stream solve(InputProgram program) { - return solve(program, InputConfig.DEFAULT_FILTER); - } - - /** - * Convenience method - overloaded version of solve({@link InternalProgram}, {@link Predicate}) for cases where - * details of the program analysis and normalization aren't of interest. - */ - public Stream solve(InputProgram program, java.util.function.Predicate filter) { - NormalProgram normalized = normalizeProgram(program); - return solve(normalized, filter); - } - - /** - * Convenience method - overloaded version of solve({@link InternalProgram}) for cases where details of the - * program analysis aren't of interest. - */ - public Stream solve(NormalProgram program, java.util.function.Predicate filter) { - InternalProgram preprocessed = performProgramPreprocessing(InternalProgram.fromNormalProgram(program)); - return solve(preprocessed, filter); - } - - /** - * Overloaded version of solve({@link InternalProgram}, {@link Predicate}) that uses a default filter (accept - * everything). - * - * @param program the program to solve - * @return a stream of answer sets - */ - public Stream solve(InternalProgram program) { - return solve(program, InputConfig.DEFAULT_FILTER); - } - - /** - * Solves the given program and filters answer sets based on the passed predicate. - * - * @param program an {@link InternalProgram} to solve - * @param filter {@link Predicate} filtering {@at.ac.tuwien.kr.alpha.common.Predicate}s in the returned answer sets - * @return a Stream of answer sets representing stable models of the given program - */ - public Stream solve(InternalProgram program, java.util.function.Predicate filter) { - Stream retVal = prepareSolverFor(program, filter).stream(); - return config.isSortAnswerSets() ? retVal.sorted() : retVal; - } - - /** - * Prepares a solver (and accompanying grounder) instance pre-loaded with the given program. Use this if the - * solver is needed after reading answer sets (e.g. for obtaining statistics). - * - * @param program the program to solve. - * @param filter a (java util) predicate that filters (asp-)predicates which should be contained in the answer - * set stream from the solver. - * @return a solver (and accompanying grounder) instance pre-loaded with the given program. - */ - public Solver prepareSolverFor(InternalProgram program, java.util.function.Predicate filter) { - String grounderName = config.getGrounderName(); - boolean doDebugChecks = config.isDebugInternalChecks(); - - GrounderHeuristicsConfiguration grounderHeuristicConfiguration = GrounderHeuristicsConfiguration - .getInstance(config.getGrounderToleranceConstraints(), config.getGrounderToleranceRules()); - grounderHeuristicConfiguration.setAccumulatorEnabled(config.isGrounderAccumulatorEnabled()); - - AtomStore atomStore = new AtomStoreImpl(); - Grounder grounder = GrounderFactory.getInstance(grounderName, program, atomStore, filter, grounderHeuristicConfiguration, doDebugChecks); - - return SolverFactory.getInstance(config, atomStore, grounder); - } - - public SystemConfig getConfig() { - return config; - } - - public void setConfig(SystemConfig config) { - this.config = config; - } - -} diff --git a/src/main/java/at/ac/tuwien/kr/alpha/common/AnswerSet.java b/src/main/java/at/ac/tuwien/kr/alpha/common/AnswerSet.java deleted file mode 100644 index a02c99fdb..000000000 --- a/src/main/java/at/ac/tuwien/kr/alpha/common/AnswerSet.java +++ /dev/null @@ -1,52 +0,0 @@ -package at.ac.tuwien.kr.alpha.common; - -import at.ac.tuwien.kr.alpha.Util; -import at.ac.tuwien.kr.alpha.api.query.AnswerSetQuery; -import at.ac.tuwien.kr.alpha.common.atoms.Atom; - -import java.util.List; -import java.util.Map; -import java.util.SortedSet; - -public interface AnswerSet extends Comparable { - SortedSet getPredicates(); - - SortedSet getPredicateInstances(Predicate predicate); - - Map> getPredicateInstances(); - - boolean isEmpty(); - - @Override - default int compareTo(AnswerSet other) { - if (other.getClass() != this.getClass()) { - return 1; - } - final SortedSet predicates = this.getPredicates(); - int result = Util.compareSortedSets(predicates, other.getPredicates()); - - if (result != 0) { - return result; - } - - for (Predicate predicate : predicates) { - result = Util.compareSortedSets(this.getPredicateInstances(predicate), other.getPredicateInstances(predicate)); - - if (result != 0) { - return result; - } - } - - return 0; - } - - /** - * Applies a given {@link AnswerSetQuery} to this AnswerSet. - * - * @param query the query to apply - * @return all atoms that are instances of the predicate specified by the query and meet the filters of the query - */ - default List query(AnswerSetQuery query) { - return query.applyTo(this); - } -} diff --git a/src/main/java/at/ac/tuwien/kr/alpha/grounder/transformation/aggregates/AggregateOperatorNormalization.java b/src/main/java/at/ac/tuwien/kr/alpha/grounder/transformation/aggregates/AggregateOperatorNormalization.java deleted file mode 100644 index b40c466fe..000000000 --- a/src/main/java/at/ac/tuwien/kr/alpha/grounder/transformation/aggregates/AggregateOperatorNormalization.java +++ /dev/null @@ -1,128 +0,0 @@ -package at.ac.tuwien.kr.alpha.grounder.transformation.aggregates; - -import at.ac.tuwien.kr.alpha.common.ComparisonOperator; -import at.ac.tuwien.kr.alpha.common.atoms.AggregateAtom; -import at.ac.tuwien.kr.alpha.common.atoms.AggregateAtom.AggregateFunctionSymbol; -import at.ac.tuwien.kr.alpha.common.atoms.AggregateLiteral; -import at.ac.tuwien.kr.alpha.common.atoms.ComparisonAtom; -import at.ac.tuwien.kr.alpha.common.atoms.Literal; -import at.ac.tuwien.kr.alpha.common.program.InputProgram; -import at.ac.tuwien.kr.alpha.common.rule.BasicRule; -import at.ac.tuwien.kr.alpha.common.rule.WeakConstraint; -import at.ac.tuwien.kr.alpha.common.terms.ArithmeticTerm; -import at.ac.tuwien.kr.alpha.common.terms.ArithmeticTerm.ArithmeticOperator; -import at.ac.tuwien.kr.alpha.common.terms.ConstantTerm; -import at.ac.tuwien.kr.alpha.common.terms.Term; -import at.ac.tuwien.kr.alpha.common.terms.VariableTerm; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -import static at.ac.tuwien.kr.alpha.common.ComparisonOperator.EQ; -import static at.ac.tuwien.kr.alpha.common.ComparisonOperator.LE; - -/** - * Transforms an {@link InputProgram} such that, for all aggregate (body-)literals, only the comparison operators "=" - * and "<=" are used. - * - * Rewriting of "#count" and "#sum" aggregates is done using the following equivalences: - *

    - *
  • X < #aggr{...} == XP <= #aggr{...}, XP = X + 1
  • - *
  • X != #aggr{...} == not X = #aggr{...}
  • - *
  • X > #aggr{...} == not X <= #aggr{...}
  • - *
  • X >= #aggr{...} == not XP <= #aggr{...}, XP = X + 1
  • - *
  • not X < #aggr{...} == not XP <= #aggr{...}, XP = X + 1
  • - *
  • not X != #aggr{...} == X = #aggr{...}
  • - *
  • not X > #aggr{...} == X <= #aggr{...}
  • - *
  • not X >= #aggr{...} == XP <= #aggr{...}, XP = X + 1
  • - *
- * Operators for "#min" and "#max" aggregates are not rewritten. - * - * Note that input programs must only contain aggregate literals of form VAR OP #aggr{...}, i.e. with only - * a left term and operator. When preprocessing programs, apply this transformation AFTER - * {@link at.ac.tuwien.kr.alpha.grounder.transformation.aggregates.AggregateLiteralSplitting}. - * - * Copyright (c) 2020-2021, the Alpha Team. - */ -public final class AggregateOperatorNormalization { - - private AggregateOperatorNormalization() { - throw new UnsupportedOperationException("Utility class - cannot instantiate!"); - } - - public static BasicRule normalize(BasicRule rule) { - List rewrittenBody = new ArrayList<>(); - for (Literal lit : rule.getBody()) { - rewrittenBody.addAll(rewriteLiteral(lit)); - } - if (rule instanceof WeakConstraint) { - WeakConstraint wcRule = (WeakConstraint) rule; - return new WeakConstraint(rewrittenBody, wcRule.getWeight(), wcRule.getLevel(), wcRule.getTermList()); - } - return new BasicRule(rule.getHead(), rewrittenBody); - } - - private static List rewriteLiteral(Literal lit) { - if (lit instanceof AggregateLiteral) { - return rewriteAggregateOperator((AggregateLiteral) lit); - } else { - return Collections.singletonList(lit); - } - } - - private static List rewriteAggregateOperator(AggregateLiteral lit) { - AggregateAtom atom = lit.getAtom(); - if (lit.getAtom().getAggregatefunction() == AggregateFunctionSymbol.MIN || lit.getAtom().getAggregatefunction() == AggregateFunctionSymbol.MAX) { - // No operator normalization needed for #min/#max aggregates. - return Collections.singletonList(lit); - } - if (atom.getLowerBoundOperator() == EQ || atom.getLowerBoundOperator() == LE) { - // Nothing to do for operator "=" or "<=". - return Collections.singletonList(lit); - } else { - List retVal = new ArrayList<>(); - VariableTerm decrementedBound; - switch (atom.getLowerBoundOperator()) { - case LT: - decrementedBound = VariableTerm.getAnonymousInstance(); - retVal.add(createLowerBoundedAggregateLiteral(LE, decrementedBound, atom, !lit.isNegated())); - retVal.add(createPlusOneTerm(atom.getLowerBoundTerm(), decrementedBound)); - break; - case NE: - retVal.add(createLowerBoundedAggregateLiteral(EQ, atom.getLowerBoundTerm(), atom, lit.isNegated())); - break; - case GT: - retVal.add(createLowerBoundedAggregateLiteral(LE, atom.getLowerBoundTerm(), atom, lit.isNegated())); - break; - case GE: - decrementedBound = VariableTerm.getAnonymousInstance(); - retVal.add(createLowerBoundedAggregateLiteral(LE, decrementedBound, atom, lit.isNegated())); - retVal.add(createPlusOneTerm(atom.getLowerBoundTerm(), decrementedBound)); - break; - default: - throw new IllegalStateException("No operator rewriting logic available for literal: " + lit); - } - return retVal; - } - } - - private static AggregateLiteral createLowerBoundedAggregateLiteral(ComparisonOperator op, Term lowerBoundTerm, AggregateAtom aggregateAtom, boolean isNegated) { - return new AggregateLiteral(new AggregateAtom(op, lowerBoundTerm, aggregateAtom.getAggregatefunction(), - aggregateAtom.getAggregateElements()), isNegated); - } - - /** - * Creates a new {@link Literal} that assigns the given target variable to the given (integer) term plus one. - * - * @param term - * @param targetVariable - * @return - */ - private static Literal createPlusOneTerm(Term term, VariableTerm targetVariable) { - Term increment = ArithmeticTerm.getInstance(term, ArithmeticOperator.PLUS, ConstantTerm.getInstance(1)); - ComparisonAtom atom = new ComparisonAtom(targetVariable, increment, ComparisonOperator.EQ); - return atom.toLiteral(); - } - -} From afbb272f8148333c505b042dcfa95c6ce82df30d Mon Sep 17 00:00:00 2001 From: Antonius Weinzierl Date: Sat, 14 Jan 2023 04:53:57 +0100 Subject: [PATCH 26/27] Fix merge master. --- .../commons/programs/ASPCore2ProgramImpl.java | 75 +------------------ .../commons/programs/NormalProgramImpl.java | 8 +- .../kr/alpha/commons/programs/Programs.java | 23 +++--- .../programs}/rules/WeakConstraint.java | 4 +- .../alpha/commons/WeightedAnswerSetTest.java | 2 +- .../alpha/core/parser/ParseTreeVisitor.java | 33 ++++---- .../programs/atoms/WeakConstraintAtom.java | 12 +-- .../WeakConstraintNormalization.java | 16 ++-- .../AggregateOperatorNormalization.java | 6 +- .../kr/alpha/core/solver/DefaultSolver.java | 30 +------- ...onstraintsManagerForBoundedOptimality.java | 2 +- .../alpha/core/grounder/ChoiceGrounder.java | 1 - 12 files changed, 58 insertions(+), 154 deletions(-) rename {alpha-core/src/main/java/at/ac/tuwien/kr/alpha/core => alpha-commons/src/main/java/at/ac/tuwien/kr/alpha/commons/programs}/rules/WeakConstraint.java (89%) diff --git a/alpha-commons/src/main/java/at/ac/tuwien/kr/alpha/commons/programs/ASPCore2ProgramImpl.java b/alpha-commons/src/main/java/at/ac/tuwien/kr/alpha/commons/programs/ASPCore2ProgramImpl.java index 9c6b082bc..538bd88ac 100644 --- a/alpha-commons/src/main/java/at/ac/tuwien/kr/alpha/commons/programs/ASPCore2ProgramImpl.java +++ b/alpha-commons/src/main/java/at/ac/tuwien/kr/alpha/commons/programs/ASPCore2ProgramImpl.java @@ -32,17 +32,14 @@ import at.ac.tuwien.kr.alpha.api.programs.atoms.Atom; import at.ac.tuwien.kr.alpha.api.programs.rules.Rule; import at.ac.tuwien.kr.alpha.api.programs.rules.heads.Head; -import at.ac.tuwien.kr.alpha.core.parser.InlineDirectivesImpl; -import at.ac.tuwien.kr.alpha.core.rules.WeakConstraint; -import java.util.ArrayList; import java.util.Collections; import java.util.List; /** * Alpha-internal representation of an ASP program, i.e., a set of ASP rules. *

- * Copyright (c) 2017-2019, the Alpha Team. + * Copyright (c) 2017-2023, the Alpha Team. */ class ASPCore2ProgramImpl extends AbstractProgram> implements ASPCore2Program{ @@ -51,74 +48,4 @@ class ASPCore2ProgramImpl extends AbstractProgram> implements ASPCore ASPCore2ProgramImpl(List> rules, List facts, InlineDirectives inlineDirectives, boolean containsWeakConstraints) { super(rules, facts, inlineDirectives, containsWeakConstraints); } - - ASPCore2ProgramImpl() { - super(new ArrayList<>(), new ArrayList<>(), new InlineDirectivesImpl(), false); - } - - public static Builder builder() { - return new Builder(); - } - - public static Builder builder(ASPCore2Program prog) { - return new Builder(prog); - } - - /** - * Builder for more complex program construction scenarios, ensuring that an {@link InputProgram} is immutable - */ - public static class Builder { - - private List> rules = new ArrayList<>(); - private List facts = new ArrayList<>(); - private InlineDirectives inlineDirectives = new InlineDirectivesImpl(); - private boolean containsWeakConstraints; - - public Builder(ASPCore2Program prog) { - this.addRules(prog.getRules()); - this.addFacts(prog.getFacts()); - this.addInlineDirectives(prog.getInlineDirectives()); - } - - public Builder() { - - } - - public Builder addRules(List> rules) { - for (Rule rule : rules) { - addRule(rule); - } - return this; - } - - public Builder addRule(Rule r) { - this.rules.add(r); - this.containsWeakConstraints |= r instanceof WeakConstraint; - return this; - } - - public Builder addFacts(List facts) { - this.facts.addAll(facts); - return this; - } - - public Builder addFact(Atom fact) { - this.facts.add(fact); - return this; - } - - public Builder addInlineDirectives(InlineDirectives inlineDirectives) { - this.inlineDirectives.accumulate(inlineDirectives); - return this; - } - - public Builder accumulate(ASPCore2Program prog) { - return this.addRules(prog.getRules()).addFacts(prog.getFacts()).addInlineDirectives(prog.getInlineDirectives()); - } - - public InputProgram build() { - return new InputProgram(this.rules, this.facts, this.inlineDirectives, this.containsWeakConstraints); - } - } - } diff --git a/alpha-commons/src/main/java/at/ac/tuwien/kr/alpha/commons/programs/NormalProgramImpl.java b/alpha-commons/src/main/java/at/ac/tuwien/kr/alpha/commons/programs/NormalProgramImpl.java index 8bbfc4365..e1e418f17 100644 --- a/alpha-commons/src/main/java/at/ac/tuwien/kr/alpha/commons/programs/NormalProgramImpl.java +++ b/alpha-commons/src/main/java/at/ac/tuwien/kr/alpha/commons/programs/NormalProgramImpl.java @@ -1,12 +1,12 @@ package at.ac.tuwien.kr.alpha.commons.programs; -import java.util.List; - import at.ac.tuwien.kr.alpha.api.programs.InlineDirectives; import at.ac.tuwien.kr.alpha.api.programs.NormalProgram; import at.ac.tuwien.kr.alpha.api.programs.atoms.Atom; import at.ac.tuwien.kr.alpha.api.programs.rules.NormalRule; +import java.util.List; + /** * A program that only contains NormalRules. * @@ -14,8 +14,8 @@ */ class NormalProgramImpl extends AbstractProgram implements NormalProgram { - NormalProgramImpl(List rules, List facts, InlineDirectives inlineDirectives) { - super(rules, facts, inlineDirectives); + NormalProgramImpl(List rules, List facts, InlineDirectives inlineDirectives, boolean containsWeakConstraints) { + super(rules, facts, inlineDirectives, containsWeakConstraints); } } diff --git a/alpha-commons/src/main/java/at/ac/tuwien/kr/alpha/commons/programs/Programs.java b/alpha-commons/src/main/java/at/ac/tuwien/kr/alpha/commons/programs/Programs.java index 2bafac832..a4c975094 100644 --- a/alpha-commons/src/main/java/at/ac/tuwien/kr/alpha/commons/programs/Programs.java +++ b/alpha-commons/src/main/java/at/ac/tuwien/kr/alpha/commons/programs/Programs.java @@ -1,8 +1,5 @@ package at.ac.tuwien.kr.alpha.commons.programs; -import java.util.ArrayList; -import java.util.List; - import at.ac.tuwien.kr.alpha.api.programs.ASPCore2Program; import at.ac.tuwien.kr.alpha.api.programs.InlineDirectives; import at.ac.tuwien.kr.alpha.api.programs.NormalProgram; @@ -11,6 +8,10 @@ import at.ac.tuwien.kr.alpha.api.programs.rules.Rule; import at.ac.tuwien.kr.alpha.api.programs.rules.heads.Head; import at.ac.tuwien.kr.alpha.commons.programs.rules.Rules; +import at.ac.tuwien.kr.alpha.commons.programs.rules.WeakConstraint; + +import java.util.ArrayList; +import java.util.List; public final class Programs { @@ -22,8 +23,8 @@ public static ASPCore2Program emptyProgram() { return ASPCore2ProgramImpl.EMPTY; } - public static ASPCore2Program newASPCore2Program(List> rules, List facts, InlineDirectives inlineDirectives) { - return new ASPCore2ProgramImpl(rules, facts, inlineDirectives); + public static ASPCore2Program newASPCore2Program(List> rules, List facts, InlineDirectives inlineDirectives, boolean containsWeakConstraints) { + return new ASPCore2ProgramImpl(rules, facts, inlineDirectives, containsWeakConstraints); } public static ASPCore2ProgramBuilder builder() { @@ -34,16 +35,18 @@ public static ASPCore2ProgramBuilder builder(ASPCore2Program program) { return new ASPCore2ProgramBuilder(program); } - public static NormalProgram newNormalProgram(List rules, List facts, InlineDirectives inlineDirectives) { - return new NormalProgramImpl(rules, facts, inlineDirectives); + public static NormalProgram newNormalProgram(List rules, List facts, InlineDirectives inlineDirectives, boolean containsWeakConstraints) { + return new NormalProgramImpl(rules, facts, inlineDirectives, containsWeakConstraints); } public static NormalProgram toNormalProgram(ASPCore2Program inputProgram) { List normalRules = new ArrayList<>(); + boolean containsWeakConstraints = false; for (Rule r : inputProgram.getRules()) { normalRules.add(Rules.toNormalRule(r)); + containsWeakConstraints |= r instanceof WeakConstraint; } - return new NormalProgramImpl(normalRules, inputProgram.getFacts(), inputProgram.getInlineDirectives()); + return new NormalProgramImpl(normalRules, inputProgram.getFacts(), inputProgram.getInlineDirectives(), containsWeakConstraints); } public static InlineDirectives newInlineDirectives() { @@ -58,11 +61,13 @@ public static class ASPCore2ProgramBuilder { private List> rules = new ArrayList<>(); private List facts = new ArrayList<>(); private InlineDirectives inlineDirectives = new InlineDirectivesImpl(); + private boolean containsWeakConstraints; public ASPCore2ProgramBuilder(ASPCore2Program prog) { this.addRules(prog.getRules()); this.addFacts(prog.getFacts()); this.addInlineDirectives(prog.getInlineDirectives()); + this.containsWeakConstraints = prog.containsWeakConstraints(); } public ASPCore2ProgramBuilder() { @@ -99,7 +104,7 @@ public ASPCore2ProgramBuilder accumulate(ASPCore2Program prog) { } public ASPCore2Program build() { - return Programs.newASPCore2Program(this.rules, this.facts, this.inlineDirectives); + return Programs.newASPCore2Program(this.rules, this.facts, this.inlineDirectives, containsWeakConstraints); } } diff --git a/alpha-core/src/main/java/at/ac/tuwien/kr/alpha/core/rules/WeakConstraint.java b/alpha-commons/src/main/java/at/ac/tuwien/kr/alpha/commons/programs/rules/WeakConstraint.java similarity index 89% rename from alpha-core/src/main/java/at/ac/tuwien/kr/alpha/core/rules/WeakConstraint.java rename to alpha-commons/src/main/java/at/ac/tuwien/kr/alpha/commons/programs/rules/WeakConstraint.java index 2554b7877..f3dbcf023 100644 --- a/alpha-core/src/main/java/at/ac/tuwien/kr/alpha/core/rules/WeakConstraint.java +++ b/alpha-commons/src/main/java/at/ac/tuwien/kr/alpha/commons/programs/rules/WeakConstraint.java @@ -1,7 +1,7 @@ -package at.ac.tuwien.kr.alpha.core.rules; +package at.ac.tuwien.kr.alpha.commons.programs.rules; import at.ac.tuwien.kr.alpha.api.programs.literals.Literal; -import at.ac.tuwien.kr.alpha.api.terms.Term; +import at.ac.tuwien.kr.alpha.api.programs.terms.Term; import at.ac.tuwien.kr.alpha.commons.util.Util; import java.util.List; diff --git a/alpha-commons/src/test/java/at/ac/tuwien/kr/alpha/commons/WeightedAnswerSetTest.java b/alpha-commons/src/test/java/at/ac/tuwien/kr/alpha/commons/WeightedAnswerSetTest.java index 213af8691..fbe7961ac 100644 --- a/alpha-commons/src/test/java/at/ac/tuwien/kr/alpha/commons/WeightedAnswerSetTest.java +++ b/alpha-commons/src/test/java/at/ac/tuwien/kr/alpha/commons/WeightedAnswerSetTest.java @@ -1,7 +1,7 @@ package at.ac.tuwien.kr.alpha.commons; import at.ac.tuwien.kr.alpha.api.AnswerSet; -import at.ac.tuwien.kr.alpha.commons.terms.Terms; +import at.ac.tuwien.kr.alpha.commons.programs.terms.Terms; import org.junit.jupiter.api.Test; import static at.ac.tuwien.kr.alpha.commons.WeightedAnswerSet.weightPerLevelFromString; diff --git a/alpha-core/src/main/java/at/ac/tuwien/kr/alpha/core/parser/ParseTreeVisitor.java b/alpha-core/src/main/java/at/ac/tuwien/kr/alpha/core/parser/ParseTreeVisitor.java index 5eec86097..c7d829f14 100644 --- a/alpha-core/src/main/java/at/ac/tuwien/kr/alpha/core/parser/ParseTreeVisitor.java +++ b/alpha-core/src/main/java/at/ac/tuwien/kr/alpha/core/parser/ParseTreeVisitor.java @@ -27,19 +27,6 @@ */ package at.ac.tuwien.kr.alpha.core.parser; -import java.util.ArrayList; -import java.util.Collections; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.SortedSet; -import java.util.TreeMap; -import java.util.TreeSet; - -import org.antlr.v4.runtime.RuleContext; -import org.antlr.v4.runtime.tree.TerminalNode; - import at.ac.tuwien.kr.alpha.api.AnswerSet; import at.ac.tuwien.kr.alpha.api.ComparisonOperator; import at.ac.tuwien.kr.alpha.api.common.fixedinterpretations.PredicateInterpretation; @@ -71,13 +58,25 @@ import at.ac.tuwien.kr.alpha.commons.programs.atoms.Atoms; import at.ac.tuwien.kr.alpha.commons.programs.literals.Literals; import at.ac.tuwien.kr.alpha.commons.programs.rules.Rules; +import at.ac.tuwien.kr.alpha.commons.programs.rules.WeakConstraint; import at.ac.tuwien.kr.alpha.commons.programs.rules.heads.Heads; import at.ac.tuwien.kr.alpha.commons.programs.terms.Terms; import at.ac.tuwien.kr.alpha.core.antlr.ASPCore2BaseVisitor; import at.ac.tuwien.kr.alpha.core.antlr.ASPCore2Parser; -import at.ac.tuwien.kr.alpha.core.programs.InputProgram; -import at.ac.tuwien.kr.alpha.core.rules.BasicRule; -import at.ac.tuwien.kr.alpha.core.rules.WeakConstraint; +import org.antlr.v4.runtime.RuleContext; +import org.antlr.v4.runtime.tree.TerminalNode; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.SortedSet; +import java.util.TreeMap; +import java.util.TreeSet; + +import static at.ac.tuwien.kr.alpha.commons.util.Util.oops; /** * Copyright (c) 2016-2018, the Alpha Team. @@ -210,7 +209,7 @@ public Object visitStatement_rule(ASPCore2Parser.Statement_ruleContext ctx) { @Override public Object visitStatement_weightConstraint(ASPCore2Parser.Statement_weightConstraintContext ctx) { // WCONS body? DOT SQUARE_OPEN weight_at_level SQUARE_CLOSE - List bodyLiterals = ctx.body() != null ? visitBody(ctx.body()) : emptyList(); + List bodyLiterals = ctx.body() != null ? visitBody(ctx.body()) : Collections.emptyList(); Term weight = (Term)visit(ctx.weight_at_level().weight); Term level = ctx.weight_at_level().level != null ? (Term)visit(ctx.weight_at_level().level) : null; List termList = ctx.weight_at_level().termlist != null ? visitTerms(ctx.weight_at_level().termlist) : null; diff --git a/alpha-core/src/main/java/at/ac/tuwien/kr/alpha/core/programs/atoms/WeakConstraintAtom.java b/alpha-core/src/main/java/at/ac/tuwien/kr/alpha/core/programs/atoms/WeakConstraintAtom.java index ddc84418a..1d4e403de 100644 --- a/alpha-core/src/main/java/at/ac/tuwien/kr/alpha/core/programs/atoms/WeakConstraintAtom.java +++ b/alpha-core/src/main/java/at/ac/tuwien/kr/alpha/core/programs/atoms/WeakConstraintAtom.java @@ -5,13 +5,13 @@ import at.ac.tuwien.kr.alpha.api.programs.atoms.Atom; import at.ac.tuwien.kr.alpha.api.programs.atoms.BasicAtom; import at.ac.tuwien.kr.alpha.api.programs.literals.Literal; -import at.ac.tuwien.kr.alpha.api.terms.ConstantTerm; -import at.ac.tuwien.kr.alpha.api.terms.FunctionTerm; -import at.ac.tuwien.kr.alpha.api.terms.Term; -import at.ac.tuwien.kr.alpha.api.terms.VariableTerm; +import at.ac.tuwien.kr.alpha.api.programs.terms.ConstantTerm; +import at.ac.tuwien.kr.alpha.api.programs.terms.FunctionTerm; +import at.ac.tuwien.kr.alpha.api.programs.terms.Term; +import at.ac.tuwien.kr.alpha.api.programs.terms.VariableTerm; import at.ac.tuwien.kr.alpha.commons.Predicates; -import at.ac.tuwien.kr.alpha.commons.atoms.AbstractAtom; -import at.ac.tuwien.kr.alpha.commons.terms.Terms; +import at.ac.tuwien.kr.alpha.commons.programs.atoms.AbstractAtom; +import at.ac.tuwien.kr.alpha.commons.programs.terms.Terms; import java.util.ArrayList; import java.util.Collections; diff --git a/alpha-core/src/main/java/at/ac/tuwien/kr/alpha/core/programs/transformation/WeakConstraintNormalization.java b/alpha-core/src/main/java/at/ac/tuwien/kr/alpha/core/programs/transformation/WeakConstraintNormalization.java index b7c27c2c2..7f38f375d 100644 --- a/alpha-core/src/main/java/at/ac/tuwien/kr/alpha/core/programs/transformation/WeakConstraintNormalization.java +++ b/alpha-core/src/main/java/at/ac/tuwien/kr/alpha/core/programs/transformation/WeakConstraintNormalization.java @@ -2,13 +2,13 @@ import at.ac.tuwien.kr.alpha.api.programs.ASPCore2Program; -import at.ac.tuwien.kr.alpha.api.rules.Rule; -import at.ac.tuwien.kr.alpha.api.rules.heads.Head; -import at.ac.tuwien.kr.alpha.commons.rules.heads.Heads; +import at.ac.tuwien.kr.alpha.api.programs.rules.Rule; +import at.ac.tuwien.kr.alpha.api.programs.rules.heads.Head; +import at.ac.tuwien.kr.alpha.commons.programs.Programs; +import at.ac.tuwien.kr.alpha.commons.programs.rules.Rules; +import at.ac.tuwien.kr.alpha.commons.programs.rules.WeakConstraint; +import at.ac.tuwien.kr.alpha.commons.programs.rules.heads.Heads; import at.ac.tuwien.kr.alpha.core.atoms.WeakConstraintAtom; -import at.ac.tuwien.kr.alpha.core.programs.InputProgram; -import at.ac.tuwien.kr.alpha.core.rules.BasicRule; -import at.ac.tuwien.kr.alpha.core.rules.WeakConstraint; import java.util.ArrayList; @@ -20,7 +20,7 @@ public class WeakConstraintNormalization extends ProgramTransformation { @Override public ASPCore2Program apply(ASPCore2Program inputProgram) { - InputProgram.Builder builder = new InputProgram.Builder(); + Programs.ASPCore2ProgramBuilder builder = Programs.builder(); builder.addFacts(inputProgram.getFacts()); builder.addInlineDirectives(inputProgram.getInlineDirectives()); @@ -32,7 +32,7 @@ public ASPCore2Program apply(ASPCore2Program inputProgram) { // Create rule with special head atom. WeakConstraint weakConstraint = (WeakConstraint)rule; Head weakConstraintHead = Heads.newNormalHead(WeakConstraintAtom.getInstance(weakConstraint.getWeight(), weakConstraint.getLevel(), weakConstraint.getTermList())); - BasicRule rewrittenWeakConstraint = new BasicRule(weakConstraintHead, new ArrayList<>(rule.getBody())); + Rule rewrittenWeakConstraint = Rules.newRule(weakConstraintHead, new ArrayList<>(rule.getBody())); builder.addRule(rewrittenWeakConstraint); } return builder.build(); diff --git a/alpha-core/src/main/java/at/ac/tuwien/kr/alpha/core/programs/transformation/aggregates/AggregateOperatorNormalization.java b/alpha-core/src/main/java/at/ac/tuwien/kr/alpha/core/programs/transformation/aggregates/AggregateOperatorNormalization.java index 5cc4b0a0a..dda7f5a0a 100644 --- a/alpha-core/src/main/java/at/ac/tuwien/kr/alpha/core/programs/transformation/aggregates/AggregateOperatorNormalization.java +++ b/alpha-core/src/main/java/at/ac/tuwien/kr/alpha/core/programs/transformation/aggregates/AggregateOperatorNormalization.java @@ -1,9 +1,5 @@ package at.ac.tuwien.kr.alpha.core.programs.transformation.aggregates; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - import at.ac.tuwien.kr.alpha.api.ComparisonOperator; import at.ac.tuwien.kr.alpha.api.programs.atoms.AggregateAtom; import at.ac.tuwien.kr.alpha.api.programs.atoms.AggregateAtom.AggregateFunctionSymbol; @@ -19,8 +15,8 @@ import at.ac.tuwien.kr.alpha.commons.programs.atoms.Atoms; import at.ac.tuwien.kr.alpha.commons.programs.literals.Literals; import at.ac.tuwien.kr.alpha.commons.programs.rules.Rules; +import at.ac.tuwien.kr.alpha.commons.programs.rules.WeakConstraint; import at.ac.tuwien.kr.alpha.commons.programs.terms.Terms; -import at.ac.tuwien.kr.alpha.core.rules.WeakConstraint; import java.util.ArrayList; import java.util.Collections; diff --git a/alpha-core/src/main/java/at/ac/tuwien/kr/alpha/core/solver/DefaultSolver.java b/alpha-core/src/main/java/at/ac/tuwien/kr/alpha/core/solver/DefaultSolver.java index 1b7e27ace..d835cbe67 100644 --- a/alpha-core/src/main/java/at/ac/tuwien/kr/alpha/core/solver/DefaultSolver.java +++ b/alpha-core/src/main/java/at/ac/tuwien/kr/alpha/core/solver/DefaultSolver.java @@ -27,30 +27,7 @@ */ package at.ac.tuwien.kr.alpha.core.solver; -import static at.ac.tuwien.kr.alpha.commons.util.Util.oops; -import static at.ac.tuwien.kr.alpha.core.programs.atoms.Literals.atomOf; -import static at.ac.tuwien.kr.alpha.core.programs.atoms.Literals.atomToLiteral; -import static at.ac.tuwien.kr.alpha.core.programs.atoms.Literals.atomToNegatedLiteral; -import static at.ac.tuwien.kr.alpha.core.solver.NoGoodStore.LBD_NO_VALUE; -import static at.ac.tuwien.kr.alpha.core.solver.heuristics.BranchingHeuristic.DEFAULT_CHOICE_LITERAL; -import static at.ac.tuwien.kr.alpha.core.solver.learning.GroundConflictNoGoodLearner.ConflictAnalysisResult.UNSAT; - -import java.util.ArrayList; -import java.util.Iterator; -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.LinkedList; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Random; -import java.util.Set; -import java.util.function.Consumer; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import at.ac.tuwien.kr.alpha.api.AnswerSet; -import at.ac.tuwien.kr.alpha.commons.WeightedAnswerSet; import at.ac.tuwien.kr.alpha.api.StatisticsReportingSolver; import at.ac.tuwien.kr.alpha.api.config.SystemConfig; import at.ac.tuwien.kr.alpha.api.grounder.Substitution; @@ -59,6 +36,7 @@ import at.ac.tuwien.kr.alpha.api.programs.atoms.ComparisonAtom; import at.ac.tuwien.kr.alpha.api.programs.literals.Literal; import at.ac.tuwien.kr.alpha.api.programs.terms.ConstantTerm; +import at.ac.tuwien.kr.alpha.commons.WeightedAnswerSet; import at.ac.tuwien.kr.alpha.core.common.AtomStore; import at.ac.tuwien.kr.alpha.core.common.NoGood; import at.ac.tuwien.kr.alpha.core.grounder.Grounder; @@ -88,9 +66,9 @@ import java.util.function.Consumer; import static at.ac.tuwien.kr.alpha.commons.util.Util.oops; -import static at.ac.tuwien.kr.alpha.core.atoms.Literals.atomOf; -import static at.ac.tuwien.kr.alpha.core.atoms.Literals.atomToLiteral; -import static at.ac.tuwien.kr.alpha.core.atoms.Literals.atomToNegatedLiteral; +import static at.ac.tuwien.kr.alpha.core.programs.atoms.Literals.atomOf; +import static at.ac.tuwien.kr.alpha.core.programs.atoms.Literals.atomToLiteral; +import static at.ac.tuwien.kr.alpha.core.programs.atoms.Literals.atomToNegatedLiteral; import static at.ac.tuwien.kr.alpha.core.solver.NoGoodStore.LBD_NO_VALUE; import static at.ac.tuwien.kr.alpha.core.solver.heuristics.BranchingHeuristic.DEFAULT_CHOICE_LITERAL; import static at.ac.tuwien.kr.alpha.core.solver.learning.GroundConflictNoGoodLearner.ConflictAnalysisResult.UNSAT; diff --git a/alpha-core/src/main/java/at/ac/tuwien/kr/alpha/core/solver/optimization/WeakConstraintsManagerForBoundedOptimality.java b/alpha-core/src/main/java/at/ac/tuwien/kr/alpha/core/solver/optimization/WeakConstraintsManagerForBoundedOptimality.java index 13eda14cf..0072e1308 100644 --- a/alpha-core/src/main/java/at/ac/tuwien/kr/alpha/core/solver/optimization/WeakConstraintsManagerForBoundedOptimality.java +++ b/alpha-core/src/main/java/at/ac/tuwien/kr/alpha/core/solver/optimization/WeakConstraintsManagerForBoundedOptimality.java @@ -15,7 +15,7 @@ import java.util.stream.Collectors; import static at.ac.tuwien.kr.alpha.commons.util.Util.oops; -import static at.ac.tuwien.kr.alpha.core.atoms.Literals.atomToLiteral; +import static at.ac.tuwien.kr.alpha.core.programs.atoms.Literals.atomToLiteral; import static at.ac.tuwien.kr.alpha.core.solver.ThriceTruth.MBT; /** diff --git a/alpha-core/src/test/java/at/ac/tuwien/kr/alpha/core/grounder/ChoiceGrounder.java b/alpha-core/src/test/java/at/ac/tuwien/kr/alpha/core/grounder/ChoiceGrounder.java index ce959bc2a..fbd409064 100644 --- a/alpha-core/src/test/java/at/ac/tuwien/kr/alpha/core/grounder/ChoiceGrounder.java +++ b/alpha-core/src/test/java/at/ac/tuwien/kr/alpha/core/grounder/ChoiceGrounder.java @@ -68,7 +68,6 @@ import static at.ac.tuwien.kr.alpha.core.common.NoGoodTest.fromOldLiterals; import static java.util.Arrays.asList; import static java.util.Collections.emptyList; -import static java.util.Collections.singleton; /** * Represents a small ASP program with choices {@code { aa :- not bb. bb :- not aa. }}. From e7558d0f9438781841bd867ab8f47bba6de38840 Mon Sep 17 00:00:00 2001 From: Antonius Weinzierl Date: Mon, 16 Jan 2023 04:35:47 +0100 Subject: [PATCH 27/27] Correctly report if optimum answer set was found. - Add Solver::didExhaustSearchSpace method, realize it in Solver implementations and use it in Main. --- .../at/ac/tuwien/kr/alpha/api/Solver.java | 8 +++++ .../main/java/at/ac/tuwien/kr/alpha/Main.java | 4 +-- .../kr/alpha/core/solver/DefaultSolver.java | 5 +++ .../kr/alpha/core/solver/NaiveSolver.java | 32 +++++++++++-------- 4 files changed, 34 insertions(+), 15 deletions(-) diff --git a/alpha-api/src/main/java/at/ac/tuwien/kr/alpha/api/Solver.java b/alpha-api/src/main/java/at/ac/tuwien/kr/alpha/api/Solver.java index 2dbd6be90..6400ea4f5 100644 --- a/alpha-api/src/main/java/at/ac/tuwien/kr/alpha/api/Solver.java +++ b/alpha-api/src/main/java/at/ac/tuwien/kr/alpha/api/Solver.java @@ -38,4 +38,12 @@ default List collectList() { return stream().collect(Collectors.toList()); } + /** + * Reports whether this {@link Solver} completely searched all of the given search space for desired answer sets. + * For this to be true, the solver does not need to check every candidate explicitly, but also use learned knowledge or other + * factors (like optimality) in order to prove that no relevant answer set remains un-investigated. + * @return true iff the given search space was investigated exhaustively. + */ + boolean didExhaustSearchSpace(); + } diff --git a/alpha-cli-app/src/main/java/at/ac/tuwien/kr/alpha/Main.java b/alpha-cli-app/src/main/java/at/ac/tuwien/kr/alpha/Main.java index e9aed032b..a073a9af5 100644 --- a/alpha-cli-app/src/main/java/at/ac/tuwien/kr/alpha/Main.java +++ b/alpha-cli-app/src/main/java/at/ac/tuwien/kr/alpha/Main.java @@ -150,7 +150,7 @@ private static void writeComponentGraph(ComponentGraph cg, String path) { } /** - * Writes the given {@link CompiledProgram} to the destination passed as the second parameter + * Writes the given {@link NormalProgram} to the destination passed as the second parameter * * @param prg the program to write * @param path the path to write the program to @@ -212,7 +212,7 @@ private static void computeAndConsumeAnswerSets(Solver solver, AlphaConfig alpha } } else { System.out.println("SATISFIABLE"); - if (sysCfg.isAnswerSetOptimizationEnabled() && counter.get() < limit) { + if (sysCfg.isAnswerSetOptimizationEnabled() && solver.didExhaustSearchSpace()) { // If less answer sets were found than requested and optimisation is enabled, then the last one is an optimal answer set. System.out.println("OPTIMUM PROVEN"); } diff --git a/alpha-core/src/main/java/at/ac/tuwien/kr/alpha/core/solver/DefaultSolver.java b/alpha-core/src/main/java/at/ac/tuwien/kr/alpha/core/solver/DefaultSolver.java index d835cbe67..4aa68271c 100644 --- a/alpha-core/src/main/java/at/ac/tuwien/kr/alpha/core/solver/DefaultSolver.java +++ b/alpha-core/src/main/java/at/ac/tuwien/kr/alpha/core/solver/DefaultSolver.java @@ -536,6 +536,11 @@ protected boolean choose() { choiceManager.choose(new Choice(literal, false)); return true; } + + @Override + public boolean didExhaustSearchSpace() { + return searchState.isSearchSpaceCompletelyExplored; + } @Override public int getNumberOfChoices() { diff --git a/alpha-core/src/main/java/at/ac/tuwien/kr/alpha/core/solver/NaiveSolver.java b/alpha-core/src/main/java/at/ac/tuwien/kr/alpha/core/solver/NaiveSolver.java index 3da723e59..effd2eb01 100644 --- a/alpha-core/src/main/java/at/ac/tuwien/kr/alpha/core/solver/NaiveSolver.java +++ b/alpha-core/src/main/java/at/ac/tuwien/kr/alpha/core/solver/NaiveSolver.java @@ -27,10 +27,14 @@ */ package at.ac.tuwien.kr.alpha.core.solver; -import static at.ac.tuwien.kr.alpha.core.programs.atoms.Literals.atomOf; -import static at.ac.tuwien.kr.alpha.core.programs.atoms.Literals.isNegated; -import static at.ac.tuwien.kr.alpha.core.programs.atoms.Literals.isPositive; -import static java.lang.Math.abs; +import at.ac.tuwien.kr.alpha.api.AnswerSet; +import at.ac.tuwien.kr.alpha.core.common.AtomStore; +import at.ac.tuwien.kr.alpha.core.common.IntIterator; +import at.ac.tuwien.kr.alpha.core.common.NoGood; +import at.ac.tuwien.kr.alpha.core.grounder.Grounder; +import org.apache.commons.lang3.tuple.Pair; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.util.ArrayList; import java.util.HashMap; @@ -41,15 +45,10 @@ import java.util.Stack; import java.util.function.Consumer; -import org.apache.commons.lang3.tuple.Pair; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import at.ac.tuwien.kr.alpha.api.AnswerSet; -import at.ac.tuwien.kr.alpha.core.common.AtomStore; -import at.ac.tuwien.kr.alpha.core.common.IntIterator; -import at.ac.tuwien.kr.alpha.core.common.NoGood; -import at.ac.tuwien.kr.alpha.core.grounder.Grounder; +import static at.ac.tuwien.kr.alpha.core.programs.atoms.Literals.atomOf; +import static at.ac.tuwien.kr.alpha.core.programs.atoms.Literals.isNegated; +import static at.ac.tuwien.kr.alpha.core.programs.atoms.Literals.isPositive; +import static java.lang.Math.abs; /** * Copyright (c) 2016-2020, the Alpha Team. @@ -144,6 +143,13 @@ protected boolean tryAdvance(Consumer action) { } } + + @Override + public boolean didExhaustSearchSpace() { + // Note: private method NaiveSolver::didExhaustSearchSpace assumes being called only after the first choice and does not work here. + throw new UnsupportedOperationException("NaiveSolver cannot report whether search space was exhausted."); + } + private void assignUnassignedToFalse() { for (Integer atom : unassignedAtoms) { truthAssignments.put(atom, false);