Skip to content

Commit

Permalink
Add ImplicationConstraint (#135)
Browse files Browse the repository at this point in the history
* Add ImplicationConstraint
  • Loading branch information
topnessman authored and jyluo committed Oct 31, 2018
1 parent 6fd58e5 commit 99abf1f
Show file tree
Hide file tree
Showing 19 changed files with 500 additions and 24 deletions.
2 changes: 1 addition & 1 deletion src/checkers/inference/model/Constraint.java
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ public Constraint(List<Slot> slots) {
public AnnotationLocation getLocation() {
return location;
}

public abstract <S, T> T serialize(Serializer<S, T> serializer);

/**
Expand Down
21 changes: 21 additions & 0 deletions src/checkers/inference/model/ConstraintManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,19 @@ private void add(Constraint constraint) {
}
}

/**
* Allows {@link ImplicationConstraint} to add its component assumptions
* directly to the manager as part of one of its normalization cases.
*
* @param constraints
* a collection of (possibly normalized) constraints
*/
protected void addAll(Iterable<Constraint> constraints) {
for (Constraint c : constraints) {
add(c);
}
}

public void startIgnoringConstraints() {
ignoreConstraints = true;
}
Expand Down Expand Up @@ -130,6 +143,10 @@ public ExistentialConstraint createExistentialConstraint(Slot slot,
ifNotExistsConstraints, getCurrentLocation());
}

public Constraint createImplicationConstraint(List<Constraint> assumptions, Constraint conclusion) {
return ImplicationConstraint.create(assumptions, conclusion, getCurrentLocation());
}

/**
* Create an {@link ArithmeticConstraint} for the given operation and slots.
*/
Expand Down Expand Up @@ -259,4 +276,8 @@ public void addArithmeticConstraint(ArithmeticOperationKind operation, Slot left
Slot rightOperand, ArithmeticVariableSlot result) {
add(createArithmeticConstraint(operation, leftOperand, rightOperand, result));
}

public void addImplicationConstraint(List<Constraint> assumptions, Constraint conclusion) {
add(createImplicationConstraint(assumptions, conclusion));
}
}
163 changes: 163 additions & 0 deletions src/checkers/inference/model/ImplicationConstraint.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
package checkers.inference.model;

import org.checkerframework.dataflow.util.HashCodeUtils;
import org.checkerframework.javacutil.BugInCF;

import checkers.inference.InferenceMain;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
* Constraint that models implication logic. If all the assumptions are
* satisfied, then conclusion should also be satisfied.
* <p>
* Suppose one needs to enforce this restriction: if {@code @A} is inferred as
* on declaration of class {@code MyClass}, then every usage of class
* {@code MyClass} needs to be inferred to {@code @A}.
* {@link ImplicationConstraint} can express this restriction:
* <p>
* {@code @1 == @A -> @2 == @A}, in which {@code @1} is the slot inserted on the
* class tree and {@code @2} is the slot that represents one usage of
* {@code MyClass}.
*/
public class ImplicationConstraint extends Constraint {

/**
* An immutable set of {@link Constraint}s that are conjuncted together.
*/
private final Set<Constraint> assumptions;

/**
* A single {@link Constraint} that is implicated by the
* {@link #assumptions}.
*/
private final Constraint conclusion;

public ImplicationConstraint(Set<Constraint> assumptions,
Constraint conclusion, AnnotationLocation location) {
super(extractAllSlots(assumptions, conclusion), location);

this.assumptions = Collections.unmodifiableSet(assumptions);
this.conclusion = conclusion;
}

private static List<Slot> extractAllSlots(Iterable<Constraint> assumptions,
Constraint conclusion) {
List<Slot> slots = new ArrayList<>();
for (Constraint a : assumptions) {
slots.addAll(a.getSlots());
}
slots.addAll(conclusion.getSlots());
return slots;
}

// TODO: the input should be a set of constraints instead of a list. This
// requires modifying the constraint manager and PICO.
public static Constraint create(List<Constraint> assumptions,
Constraint conclusion, AnnotationLocation currentLocation) {
if (assumptions == null || conclusion == null) {
throw new BugInCF(
"Adding implication constraint with null argument. assumptions: "
+ assumptions + " conclusion: " + conclusion);
}

// Normalization cases:
// 1) assumptions == empty ==> return conclusion
// 2) any assumption == FALSE ==> return TRUE
// 3) refinedAssumptions == empty ==> return conclusion
// 4) refinedAssumptions != empty && conclusion == TRUE ==> return TRUE
// 5) refinedAssumptions != empty && conclusion == FALSE ==> return
// conjunction of refinedAssumptions
// 6) refinedAssumptions != empty && conclusion != TRUE && conclusion !=
// FALSE ==> CREATE_REAL_IMPLICATION_CONSTRAINT

// 1) assumptions == empty ==> return conclusion
// Optimization for trivial cases when there are no preconditions for
// the conclusion to be true, meaning conclusion is a hard constraint
// that must be satisfied.
if (assumptions.isEmpty()) {
return conclusion;
}

// Otherwise, assumptions list is not empty
Set<Constraint> refinedAssumptions = new HashSet<>();
// Iterate over assumptions: if any assumption is false, directly return
// AlwaysTrueConstraint;
// If any assumption is true, don't add it to the refined assumptions
// set and continue the iteration.
for (Constraint assumption : assumptions) {
// 2) any assumption == FALSE ==> return TRUE
if (assumption instanceof AlwaysFalseConstraint) {
// assumption is false, the whole implication is true
return AlwaysTrueConstraint.create();
} else if (!(assumption instanceof AlwaysTrueConstraint)) {
// current assumption is not statically known to true or false.
refinedAssumptions.add(assumption);
}
}

// 3) refinedAssumptions == empty ==> return conclusion
if (refinedAssumptions.isEmpty()) {
// This covers the case where original assumptions list is not empty
// and every assumption is AlwaysTrueConstraint
return conclusion;
}

// 4) refinedAssumptions != empty && conclusion == TRUE ==> return TRUE
if (conclusion instanceof AlwaysTrueConstraint) {
return AlwaysTrueConstraint.create();
}

// 5) refinedAssumptions != empty && conclusion == FALSE ==> return
// conjunction of refinedAssumptions
if (conclusion instanceof AlwaysFalseConstraint) {
// Instead of creating a "conjunction constraint", here we directly
// add the set of constraints to the constraint manager
InferenceMain.getInstance().getConstraintManager()
.addAll(refinedAssumptions);
// since all assumptions are added to the constraint manager, we
// return a dummy always true constraint as the normalized result
return AlwaysTrueConstraint.create();
}

// 6) refinedAssumptions != empty && conclusion != TRUE && conclusion !=
// FALSE ==> CREATE_REAL_IMPLICATION_CONSTRAINT
return new ImplicationConstraint(refinedAssumptions, conclusion,
currentLocation);
}

@Override
public <S, T> T serialize(Serializer<S, T> serializer) {
return serializer.serialize(this);
}

public Set<Constraint> getAssumptions() {
return assumptions;
}

public Constraint getConclusion() {
return conclusion;
}

@Override
public int hashCode() {
return HashCodeUtils.hash(assumptions, conclusion);
}

@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || this.getClass() != obj.getClass()) {
return false;
}
ImplicationConstraint other = (ImplicationConstraint) obj;
return assumptions.equals(other.assumptions)
&& conclusion.equals(other.conclusion);
}
}
3 changes: 3 additions & 0 deletions src/checkers/inference/model/Serializer.java
Original file line number Diff line number Diff line change
Expand Up @@ -44,5 +44,8 @@ public interface Serializer<S, T> {

T serialize(PreferenceConstraint preferenceConstraint);

T serialize(ImplicationConstraint implicationConstraint);

T serialize(ArithmeticConstraint arithmeticConstraint);

}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import java.util.Map;

import checkers.inference.model.LubVariableSlot;
import checkers.inference.model.ImplicationConstraint;
import org.sat4j.core.VecInt;

import checkers.inference.SlotManager;
Expand Down Expand Up @@ -273,6 +274,12 @@ public VecInt[] serialize(PreferenceConstraint preferenceConstraint) {
throw new UnsupportedOperationException("APPLY WEIGHTING FOR WEIGHTED MAX-SAT");
}

@Override
public VecInt[] serialize(ImplicationConstraint implicationConstraint) {
throw new UnsupportedOperationException("ImplicationConstraint is supported in more-advanced" +
"MaxSAT backend. Use MaxSATSolver instead!");
}

public List<VecInt> convertAll(Iterable<Constraint> constraints) {
return convertAll(constraints, new LinkedList<VecInt>());
}
Expand Down
22 changes: 22 additions & 0 deletions src/checkers/inference/model/serialization/JsonSerializer.java
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
package checkers.inference.model.serialization;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;

import javax.lang.model.element.AnnotationMirror;

import checkers.inference.model.LubVariableSlot;
import checkers.inference.model.ImplicationConstraint;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import checkers.inference.model.ArithmeticConstraint;
Expand Down Expand Up @@ -139,6 +142,10 @@ public class JsonSerializer implements Serializer<String, JSONObject> {
protected static final String EXISTENTIAL_THEN = "then";
protected static final String EXISTENTIAL_ELSE = "else";

protected static final String IMPLICATION_CONSTRAINT_KEY = "implication";
protected static final String IMPLICATION_ASSUMPTIONS = "assumptions";
protected static final String IMPLICATION_CONCLUSTION = "conclusion";

protected static final String ARITH_LEFT_OPERAND = "left_operand";
protected static final String ARITH_RIGHT_OPERAND = "right_operand";
protected static final String ARITH_RESULT = "result";
Expand Down Expand Up @@ -335,6 +342,21 @@ public JSONObject serialize(PreferenceConstraint constraint) {
return obj;
}

@Override
public JSONObject serialize(ImplicationConstraint implicationConstraint) {
// If either assumptions or conclusion is null, the program would have been terminated
// when trying to create that ImplicationConstraint instance.
JSONObject obj = new JSONObject();
obj.put(CONSTRAINT_KEY, IMPLICATION_CONSTRAINT_KEY);
List<JSONObject> serializedAssumptions = new ArrayList<>();
for (Constraint assumption : implicationConstraint.getAssumptions()) {
serializedAssumptions.add(assumption.serialize(this));
}
obj.put(IMPLICATION_ASSUMPTIONS, serializedAssumptions);
obj.put(IMPLICATION_CONCLUSTION, implicationConstraint.getConclusion().serialize(this));
return obj;
}

@SuppressWarnings("unchecked")
@Override
public JSONObject serialize(ArithmeticConstraint constraint) {
Expand Down
40 changes: 37 additions & 3 deletions src/checkers/inference/model/serialization/ToStringSerializer.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import checkers.inference.model.EqualityConstraint;
import checkers.inference.model.ExistentialConstraint;
import checkers.inference.model.ExistentialVariableSlot;
import checkers.inference.model.ImplicationConstraint;
import checkers.inference.model.InequalityConstraint;
import checkers.inference.model.LubVariableSlot;
import checkers.inference.model.PreferenceConstraint;
Expand Down Expand Up @@ -267,6 +268,39 @@ public String serialize(ArithmeticConstraint arithmeticConstraint) {
return sb.toString();
}

@Override
public String serialize(ImplicationConstraint implicationConstraint) {
boolean prevShowVerboseVars = showVerboseVars;
showVerboseVars = false;
// format(non-empty): assumption1 & assumption 2 & ... & assumption n -> conclusion
// format(empty): [ ] -> conclusion
final StringBuilder sb = new StringBuilder();
sb.append(getCurrentIndentString());
int oldIndentationLevel = indentationLevel;
// This is to avoid indentation for each sub constraint that comprises the implicationConstraint
indentationLevel = 0;
String assumptions = getAssumptionsString(implicationConstraint);
String conclusion = implicationConstraint.getConclusion().serialize(this);
sb.append(assumptions).append(" -> ").append(conclusion);
// Recover the previous indentation level to not affect further serializations
indentationLevel = oldIndentationLevel;
showVerboseVars = prevShowVerboseVars;
return sb.toString();
}

private String getAssumptionsString(ImplicationConstraint implicationConstraint) {
if (implicationConstraint.getAssumptions().isEmpty()) {
return "[ ]";
}

List<String> serializedAssumptions = new ArrayList<>();
for (Constraint assumption : implicationConstraint.getAssumptions()) {
serializedAssumptions.add(assumption.serialize(this));
}

return String.join(" & ", serializedAssumptions);
}

// variables
@Override
public String serialize(VariableSlot slot) {
Expand All @@ -279,9 +313,9 @@ public String serialize(VariableSlot slot) {
@Override
public String serialize(RefinementVariableSlot slot) {
final StringBuilder sb = new StringBuilder();
sb.append(slot.getId())
.append(": refines ")
.append(Arrays.asList(slot.getRefined()));
sb.append(slot.getId());
// "↧" sign is \u21A7
sb.append("[ \u21A7 "+ slot.getRefined() + " ]");
optionallyShowVerbose(slot, sb);
return sb.toString();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import checkers.inference.model.EqualityConstraint;
import checkers.inference.model.ExistentialConstraint;
import checkers.inference.model.ExistentialVariableSlot;
import checkers.inference.model.ImplicationConstraint;
import checkers.inference.model.InequalityConstraint;
import checkers.inference.model.LubVariableSlot;
import checkers.inference.model.PreferenceConstraint;
Expand All @@ -23,6 +24,7 @@
import checkers.inference.solver.backend.encoder.binary.SubtypeConstraintEncoder;
import checkers.inference.solver.backend.encoder.combine.CombineConstraintEncoder;
import checkers.inference.solver.backend.encoder.existential.ExistentialConstraintEncoder;
import checkers.inference.solver.backend.encoder.implication.ImplicationConstraintEncoder;
import checkers.inference.solver.backend.encoder.preference.PreferenceConstraintEncoder;
import checkers.inference.solver.frontend.Lattice;

Expand Down Expand Up @@ -112,6 +114,8 @@ public abstract class AbstractFormatTranslator<SlotEncodingT, ConstraintEncoding
*/
protected ExistentialConstraintEncoder<ConstraintEncodingT> existentialConstraintEncoder;

protected ImplicationConstraintEncoder<ConstraintEncodingT> implicationConstraintEncoder;

/**
* {@code ArithmeticConstraintEncoder} to which encoding of {@link ArithmeticConstraint} is delegated.
*/
Expand All @@ -137,6 +141,7 @@ protected void finishInitializingEncoders() {
preferenceConstraintEncoder = encoderFactory.createPreferenceConstraintEncoder();
combineConstraintEncoder = encoderFactory.createCombineConstraintEncoder();
existentialConstraintEncoder = encoderFactory.createExistentialConstraintEncoder();
implicationConstraintEncoder = encoderFactory.createImplicationConstraintEncoder();
arithmeticConstraintEncoder = encoderFactory.createArithmeticConstraintEncoder();
}

Expand Down Expand Up @@ -190,6 +195,12 @@ public ConstraintEncodingT serialize(ExistentialConstraint constraint) {
ConstraintEncoderCoordinator.redirect(constraint, existentialConstraintEncoder);
}

@Override
public ConstraintEncodingT serialize(ImplicationConstraint constraint) {
return implicationConstraintEncoder == null ? null :
ConstraintEncoderCoordinator.redirect(constraint, implicationConstraintEncoder);
}

@Override
public ConstraintEncodingT serialize(ArithmeticConstraint constraint) {
return arithmeticConstraintEncoder == null ? null :
Expand Down
Loading

0 comments on commit 99abf1f

Please sign in to comment.