com.sri.ai.grinder.sgdpllt.theory.equality.SatisfiabilityOfSingleVariableEqualityConstraintStepSolver Maven / Gradle / Ivy
/* * Copyright (c) 2013, SRI International * All rights reserved. * Licensed under the The BSD 3-Clause License; * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://opensource.org/licenses/BSD-3-Clause * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 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. * * Neither the name of the aic-expresso nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * 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 com.sri.ai.grinder.sgdpllt.theory.equality; import static com.sri.ai.expresso.helper.Expressions.TRUE; import static com.sri.ai.expresso.helper.Expressions.apply; import static com.sri.ai.expresso.helper.Expressions.zipApply; import static com.sri.ai.grinder.sgdpllt.library.FunctorConstants.DISEQUALITY; import static com.sri.ai.util.Util.arrayList; import static com.sri.ai.util.Util.arrayListFrom; import static com.sri.ai.util.Util.in; import static com.sri.ai.util.Util.list; import static com.sri.ai.util.Util.thereExists; import static com.sri.ai.util.Util.toLinkedHashSet; import static com.sri.ai.util.base.PairOf.makePairOf; import static com.sri.ai.util.collect.FunctionIterator.functionIterator; import java.util.ArrayList; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; import com.google.common.annotations.Beta; import com.google.common.base.Function; import com.sri.ai.expresso.api.Expression; import com.sri.ai.expresso.api.Type; import com.sri.ai.grinder.helper.GrinderUtil; import com.sri.ai.grinder.sgdpllt.api.Context; import com.sri.ai.grinder.sgdpllt.core.solver.AbstractBooleanWithPropagatedLiteralsRequiringPropagatedLiteralsAndCNFToBeSatisfiedStepSolver; import com.sri.ai.grinder.sgdpllt.library.Equality; import com.sri.ai.util.Util; import com.sri.ai.util.base.PairOf; import com.sri.ai.util.collect.CartesianProductIterator; import com.sri.ai.util.collect.FunctionIterator; import com.sri.ai.util.collect.NestedIterator; import com.sri.ai.util.collect.PairOfElementsInListIterator; import com.sri.ai.util.collect.PermutationIterator; import com.sri.ai.util.collect.PredicateIterator; import com.sri.ai.util.collect.SubsetsOfKIterator; /** * A {@link AbstractBooleanWithPropagatedLiteralsRequiringPropagatedLiteralsAndCNFToBeSatisfiedStepSolver} for a {@link SingleVariableEqualityConstraint}. *
!=. ** This step solver works by providing propagated literals of the form
Y op Z
* for every pair of literalsX = Y
andX op Z
, * whereX
is the constraint's variable andop
is either=
or* It also provides a propagated CNF that encodes the requirement that the number of distinct values * from which
X
is constrained to be disequal from does not exceed the domain size. * For example,X != Y and X != Z and X != a
, withX
's type {a, b, c, d}, * propagates the CNF(Y != b or Z = c) and (Y != b or Z != d) and (Y != c or Z != b) and (Y != c or Z != d) and (Y != d and Z != b) and (Y != d and Z != c)
. ** In general, we must generate a CNF with a clause negating each possible assignment of variables disequal from
X
* to a permutation ofk
distinct uniquely named constants not constrained to be disequal fromX
, * wherek
is the type size minus the number of uniquely named constants not constrained to be disequal fromX
. * The reason for that is that, if any of these assignments were true, thenX
would be constrained * to be difference from the uniquely named constants it already is constrained against in the constraint itself, * plus constrained to be different fromk
other uniquely named constants, * meaning that has to be disequal from a number of uniquely named constants equal to its type's size, * leaving no possible assignment to it. ** While the size of this propagated CNF depends on
X
's type size, which can be very large, * this check is only needed if the number of terms to whichX
is constrained to be * disequal from is equally large. This preserves * the fact that the algorithm's time complexity depends on the constraint size alone, * not the type size. * * @author braz * */ @Beta public class SatisfiabilityOfSingleVariableEqualityConstraintStepSolver extends AbstractBooleanWithPropagatedLiteralsRequiringPropagatedLiteralsAndCNFToBeSatisfiedStepSolver { private boolean alreadyCheckedIfNumberOfDistinctExpressionsIsLessThanStepSolverShouldBeMade = false; private NumberOfDistinctExpressionsIsLessThanStepSolver numberOfDistinctExpressionsIsLessThanStepSolver; public SatisfiabilityOfSingleVariableEqualityConstraintStepSolver(SingleVariableEqualityConstraint constraint) { super(constraint); } @Override public SingleVariableEqualityConstraint getConstraint() { return (SingleVariableEqualityConstraint) super.getConstraint(); } private NumberOfDistinctExpressionsIsLessThanStepSolver getNumberOfDistinctExpressionsIsLessThanStepSolver(Context context) { if ( ! alreadyCheckedIfNumberOfDistinctExpressionsIsLessThanStepSolverShouldBeMade) { long variableTypeSize = getConstraint().getVariableTypeSize(context); if (variableTypeSize >= 0) { numberOfDistinctExpressionsIsLessThanStepSolver = new NumberOfDistinctExpressionsIsLessThanStepSolver((int) variableTypeSize, getConstraint().getDisequals()); } alreadyCheckedIfNumberOfDistinctExpressionsIsLessThanStepSolverShouldBeMade = true; } return numberOfDistinctExpressionsIsLessThanStepSolver; } private void setNumberOfDistinctExpressionsIsLessThanStepSolver(NumberOfDistinctExpressionsIsLessThanStepSolver numberOfDistinctExpressionsIsLessThanStepSolver) { this.numberOfDistinctExpressionsIsLessThanStepSolver = numberOfDistinctExpressionsIsLessThanStepSolver; } @Override protected boolean usingDefaultImplementationOfMakePropagatedCNF() { return true; } @Override protected IterablegetPropagatedLiterals(Context context) { Iterator propagatedLiteralsIterator; if (getConstraint().getPropagateAllLiteralsWhenVariableIsBound()) { propagatedLiteralsIterator = new NestedIterator<>(getConstraint().getExternalLiterals()); } else { Iterator > pairsOfEqualsToVariableIterator = pairsOfEqualsToVariableIterator(); Iterator propagatedEqualities = functionIterator(pairsOfEqualsToVariableIterator, p -> Equality.make(p.first, p.second)); Iterator propagatedDisequalities = functionIterator(arrayListsOfEqualAndDisequalToVariableIterator(), p -> apply(DISEQUALITY, p)); propagatedLiteralsIterator = new NestedIterator<>(getConstraint().getExternalLiterals(), propagatedEqualities, propagatedDisequalities); } Iterable result = in(propagatedLiteralsIterator); return result; } protected Iterator > pairsOfEqualsToVariableIterator() { PairOfElementsInListIterator pairsOfPositiveAtomsIterator = new PairOfElementsInListIterator<>(getConstraint().getPositiveNormalizedAtoms()); // Function , PairOf > makePairOfSecondArguments = p -> makePairOf(p.first.get(1), p.second.get(1)); // above lambda somehow not working at Ciaran's environment, replacing with seemingly identical anonymous class object below Function , PairOf > makePairOfSecondArguments = new Function , PairOf >() { @Override public PairOf apply(PairOf p) { return makePairOf(p.first.get(1), p.second.get(1)); } }; Iterator > pairsOfEqualsToVariableIterator = functionIterator(pairsOfPositiveAtomsIterator, makePairOfSecondArguments); return pairsOfEqualsToVariableIterator; } @Override protected Iterable > getPropagatedCNFBesidesPropagatedLiterals(Context context) { if ( ! variableIsBoundToUniquelyNamedConstant(context)) { // the following logic only holds if the variable is not bound to a uniquely named constants, // since that eliminates all disequalities to other uniquely named constants as redundant long variableDomainSize = getConstraint().getVariableTypeSize(context); if (variableDomainSize >= 0 && getConstraint().numberOfDisequals() >= variableDomainSize) { // the following procedure can be very expensive but the condition above will rarely be satisfied ArrayList variableDisequals = getVariableDisequals(context); Set uniquelyNamedConstantDisequals = getUniquelyNamedConstantDisequals(context); Expression typeExpression = GrinderUtil.getTypeExpressionOfExpression(getConstraint().getVariable(), context); Type type = context.getTypeFromTypeExpression(typeExpression); ArrayList remainingUniquelyNamedConstants = arrayListFrom(new PredicateIterator<>(type.iterator(), c -> ! uniquelyNamedConstantDisequals.contains(c))); CartesianProductIterator > subsetOfVariableDisequalsAndRemainingConstantsPermutationIterator = new CartesianProductIterator >( () -> new SubsetsOfKIterator (variableDisequals, remainingUniquelyNamedConstants.size()), () -> new PermutationIterator (remainingUniquelyNamedConstants)); FunctionIterator >, Iterable > clausesIterator = FunctionIterator.make( subsetOfVariableDisequalsAndRemainingConstantsPermutationIterator, (ArrayList > subsetAndPermutation) -> clauseNegatingAssignmentOfSubsetOfVariablesToParticularPermutationOfRemainingConstants(subsetAndPermutation) ); Iterable > clauses = in(clausesIterator); return clauses; } } // otherwise, nothing is implied. return list(); } /** * @param subsetAndPermutation * @return */ private List clauseNegatingAssignmentOfSubsetOfVariablesToParticularPermutationOfRemainingConstants(ArrayList > subsetAndPermutation) { return zipApply( DISEQUALITY, list( subsetAndPermutation.get(0).iterator(), subsetAndPermutation.get(1).iterator())); } private Iterator > arrayListsOfEqualAndDisequalToVariableIterator() { Function , ArrayList > extractSecondArguments = equalityAndDisequality -> arrayList(equalityAndDisequality.get(0).get(1), equalityAndDisequality.get(1).get(1)); Iterator > result = FunctionIterator.make( new CartesianProductIterator ( () -> getConstraint().getPositiveNormalizedAtoms().iterator(), () -> getConstraint().getNegativeNormalizedAtoms().iterator()), extractSecondArguments); return result; } private boolean variableIsBoundToUniquelyNamedConstant(Context context) { return thereExists(getConstraint().getPositiveNormalizedAtoms(), l -> context.isUniquelyNamedConstant(l.get(1))); } private ArrayList getVariableDisequals(Context context) { return getConstraint().getNegativeNormalizedAtoms().stream(). map(e -> e.get(1)). // second arguments of Variable != Term filter(e -> ! context.isUniquelyNamedConstant(e)). // only Variables collect(Util.toArrayList(10)); } private LinkedHashSet getUniquelyNamedConstantDisequals(Context context) { return getConstraint().getNegativeNormalizedAtoms().stream(). map(e -> e.get(1)). // second arguments of Variable != Term filter(e -> context.isUniquelyNamedConstant(e)). // only constants collect(toLinkedHashSet()); } @Override protected Step solutionIfPropagatedLiteralsAndSplittersCNFAreSatisfied(Context context) { Step result; if (getNumberOfDistinctExpressionsIsLessThanStepSolver(context) != null) { Step numberStep = getNumberOfDistinctExpressionsIsLessThanStepSolver(context).step(context); if (numberStep.itDepends()) { SatisfiabilityOfSingleVariableEqualityConstraintStepSolver stepSolverIfExpressionIsTrue = clone(); stepSolverIfExpressionIsTrue.setNumberOfDistinctExpressionsIsLessThanStepSolver((NumberOfDistinctExpressionsIsLessThanStepSolver) numberStep.getStepSolverForWhenSplitterIsTrue()); SatisfiabilityOfSingleVariableEqualityConstraintStepSolver stepSolverIfExpressionIsFalse = clone(); stepSolverIfExpressionIsFalse.setNumberOfDistinctExpressionsIsLessThanStepSolver((NumberOfDistinctExpressionsIsLessThanStepSolver) numberStep.getStepSolverForWhenSplitterIsFalse()); result = new ItDependsOn(numberStep.getSplitterLiteral(), numberStep.getContextSplittingWhenSplitterIsLiteral(), stepSolverIfExpressionIsTrue, stepSolverIfExpressionIsFalse); } else { result = new Solution(numberStep.getValue()); } } else { result = new Solution(TRUE); } return result; } @Override public SatisfiabilityOfSingleVariableEqualityConstraintStepSolver clone() { return (SatisfiabilityOfSingleVariableEqualityConstraintStepSolver) super.clone(); } }