/**
* Author: Georg Hofferek <georg.hofferek@iaik.tugraz.at>
*/
package at.iaik.suraq.util.chain;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import at.iaik.suraq.proof.VeriTToken;
import at.iaik.suraq.proof.VeritProof;
import at.iaik.suraq.proof.VeritProofNode;
import at.iaik.suraq.smtlib.formula.AndFormula;
import at.iaik.suraq.smtlib.formula.DomainEq;
import at.iaik.suraq.smtlib.formula.DomainTerm;
import at.iaik.suraq.smtlib.formula.Formula;
import at.iaik.suraq.smtlib.formula.ImpliesFormula;
import at.iaik.suraq.smtlib.formula.NotFormula;
import at.iaik.suraq.smtlib.formula.PropositionalConstant;
import at.iaik.suraq.smtlib.formula.UninterpretedFunction;
import at.iaik.suraq.smtlib.formula.UninterpretedFunctionInstance;
import at.iaik.suraq.util.CongruenceClosure;
import at.iaik.suraq.util.Copyable;
import at.iaik.suraq.util.Justification;
import at.iaik.suraq.util.Util;
import at.iaik.suraq.util.graph.CloningGraph;
/**
* @author Georg Hofferek <georg.hofferek@iaik.tugraz.at>
*
*/
public class TransitivityCongruenceChain implements
Copyable<TransitivityCongruenceChain> {
/**
* Counter to give unique names to proof nodes created in this class.
*/
private static int proofNodeCounter = 1;
/**
* The starting point of the chain.
*/
private final TransitivityCongruenceChainElement start;
/**
* The target of the chain.
*/
private DomainTerm target;
/**
* The proof node from which literals of this chain originally came. This is
* necessary to avoid introducing symmetric literals.
*/
private final VeritProofNode proofNode;
/**
* Creates a chain for the given (axiomatic) proof node.
*
* @param node
* @return the chain constructed for the given <code>node</code>.
*/
public static TransitivityCongruenceChain create(VeritProofNode node) {
assert (node != null);
assert (node.isAxiom());
assert (node.checkProofNode());
Formula impliedLiteral = Util.getImpliedLiteral(node
.getLiteralConclusions());
assert (Util.isLiteral(impliedLiteral));
assert (!Util.isNegativeLiteral(impliedLiteral));
assert (impliedLiteral instanceof DomainEq);
assert (((DomainEq) impliedLiteral).isEqual());
assert (((DomainEq) impliedLiteral).getTerms().size() == 2);
assert (((DomainEq) impliedLiteral).getTerms().get(0) instanceof DomainTerm);
assert (((DomainEq) impliedLiteral).getTerms().get(1) instanceof DomainTerm);
List<DomainEq> equalities = new ArrayList<DomainEq>();
for (Formula literal : node.getLiteralConclusions()) {
if (!Util.isNegativeLiteral(literal)) {
assert (Util.isAtom(literal));
continue;
}
Formula positiveLiteral = Util.makeLiteralPositive(literal);
assert (positiveLiteral instanceof DomainEq);
equalities.add((DomainEq) positiveLiteral);
}
return TransitivityCongruenceChain.create((DomainEq) impliedLiteral,
equalities, node);
}
/**
*
* @param term1
* @param term2
* @param implyingLiteral
* @param node
* @return a chain from <code>term1</code> to <code>term2</code> using
* <code>implyingLiterals</code>.
*/
public static TransitivityCongruenceChain create(DomainTerm term1,
DomainTerm term2, List<DomainEq> implyingLiteral,
VeritProofNode node) {
List<DomainTerm> terms = new ArrayList<DomainTerm>(2);
terms.add(term1);
terms.add(term2);
DomainEq equality = DomainEq.create(terms, true);
return TransitivityCongruenceChain.create(equality, implyingLiteral,
node);
}
/**
* Creates a chain for the given <code>impliedLiteral</code> from the given
* <code>otherLiterals</code>.
*
* @param impliedLiteral
* @param implyingLiterals
* @return
*/
public static TransitivityCongruenceChain create(DomainEq impliedLiteral,
List<DomainEq> implyingLiterals, VeritProofNode node) {
assert (impliedLiteral != null);
assert (implyingLiterals != null);
assert (!implyingLiterals.isEmpty());
assert (Util.isLiteral(impliedLiteral));
assert (!Util.isNegativeLiteral(impliedLiteral));
assert (node == null ? true : node.checkProofNode());
assert (CongruenceClosure.checkLiteralImplication(implyingLiterals,
impliedLiteral));
CloningGraph<DomainTerm, Justification> graph = new CloningGraph<DomainTerm, Justification>(
true);
for (DomainEq literal : implyingLiterals) {
assert (literal.isEqual());
assert (literal.getTerms().size() == 2);
assert (literal.getTerms().get(0) instanceof DomainTerm);
assert (literal.getTerms().get(1) instanceof DomainTerm);
Justification justification = new Justification(literal);
graph.addEdge((DomainTerm) literal.getTerms().get(0),
(DomainTerm) literal.getTerms().get(1), justification);
graph.addEdge((DomainTerm) literal.getTerms().get(1),
(DomainTerm) literal.getTerms().get(0), justification);
Set<DomainTerm> additionalTerms = new HashSet<DomainTerm>();
if (literal.getTerms().get(0) instanceof UninterpretedFunctionInstance) {
additionalTerms.addAll(((UninterpretedFunctionInstance) literal
.getTerms().get(0)).getSubTerms());
}
if (literal.getTerms().get(1) instanceof UninterpretedFunctionInstance) {
additionalTerms.addAll(((UninterpretedFunctionInstance) literal
.getTerms().get(1)).getSubTerms());
}
for (DomainTerm term : additionalTerms)
graph.addNode(term);
}
assert (impliedLiteral.getTerms().size() == 2);
assert (impliedLiteral.getTerms().get(0) instanceof DomainTerm);
assert (impliedLiteral.getTerms().get(1) instanceof DomainTerm);
graph.addNode((DomainTerm) impliedLiteral.getTerms().get(0));
graph.addNode((DomainTerm) impliedLiteral.getTerms().get(1));
Set<DomainTerm> additionalTerms = new HashSet<DomainTerm>();
if (impliedLiteral.getTerms().get(0) instanceof UninterpretedFunctionInstance)
additionalTerms
.addAll(((UninterpretedFunctionInstance) impliedLiteral
.getTerms().get(0)).getSubTerms());
if (impliedLiteral.getTerms().get(1) instanceof UninterpretedFunctionInstance)
additionalTerms
.addAll(((UninterpretedFunctionInstance) impliedLiteral
.getTerms().get(1)).getSubTerms());
for (DomainTerm term : additionalTerms)
graph.addNode(term);
boolean addedSomething = true;
List<Justification> path = graph.findPath((DomainTerm) impliedLiteral
.getTerms().get(0),
(DomainTerm) impliedLiteral.getTerms().get(1));
while (path == null) {
assert (addedSomething);
addedSomething = false;
Set<DomainTerm> nodes = graph.getNodes();
for (DomainTerm term1 : nodes) {
if (!(term1 instanceof UninterpretedFunctionInstance))
continue;
for (DomainTerm term2 : nodes) {
if (!(term2 instanceof UninterpretedFunctionInstance))
continue;
if (term1 == term2)
continue;
if (!((UninterpretedFunctionInstance) term1).getFunction()
.equals(((UninterpretedFunctionInstance) term2)
.getFunction()))
continue;
if (graph.findPath(term1, term2) != null)
continue;
List<TransitivityCongruenceChain> localJustification = new ArrayList<TransitivityCongruenceChain>();
for (int count = 0; count < ((UninterpretedFunctionInstance) term1)
.getParameters().size(); count++) {
DomainTerm parameter1 = ((UninterpretedFunctionInstance) term1)
.getParameters().get(count);
DomainTerm parameter2 = ((UninterpretedFunctionInstance) term2)
.getParameters().get(count);
if (parameter1.equals(parameter2)) {
localJustification
.add(new TransitivityCongruenceChain(
parameter1, node));
} else {
List<Justification> localPath = graph.findPath(
parameter1, parameter2);
if (localPath == null) {
localJustification = null;
break;
}
localJustification
.add(new TransitivityCongruenceChain(
((UninterpretedFunctionInstance) term1)
.getParameters().get(count),
((UninterpretedFunctionInstance) term2)
.getParameters().get(count),
localPath, node));
}
}
if (localJustification != null) {
Justification completeLocalJustification = new Justification(
localJustification);
graph.addEdge(term1, term2, completeLocalJustification);
graph.addEdge(term2, term1,
completeLocalJustification.reverse());
addedSomething = true;
}
}
}
path = graph.findPath(
(DomainTerm) impliedLiteral.getTerms().get(0),
(DomainTerm) impliedLiteral.getTerms().get(1));
}
assert (path != null);
TransitivityCongruenceChain chain = new TransitivityCongruenceChain(
(DomainTerm) impliedLiteral.getTerms().get(0),
(DomainTerm) impliedLiteral.getTerms().get(1), path, node);
return chain;
}
/**
*
* Constructs a new <code>TransitivityCongruenceChain</code>.
*
* @param start
* the start term of the chain
* @param target
* the target term of the chain
* @param node
* the proof node corresponding to this chain (for
* <code>proof</code> and <code>targerLiterals</code>. Can be
* <code>null</code>.
*/
private TransitivityCongruenceChain(DomainTerm start, DomainTerm target,
VeritProofNode proofNode) {
assert (start != null);
assert (target != null);
assert (proofNode != null);
this.start = new TransitivityCongruenceChainElement(start, proofNode);
this.target = target;
this.proofNode = proofNode;
}
/**
*
* Constructs a new <code>TransitivityCongruenceChain</code>. This
* constructor is used to split a chain during creation of a colorable
* proof.
*
* @param start
* @param target
* @param proofNode
*/
private TransitivityCongruenceChain(
TransitivityCongruenceChainElement start, DomainTerm target,
VeritProofNode proofNode) {
assert (start != null);
assert (target != null);
assert (proofNode != null);
this.start = start;
this.proofNode = proofNode;
this.target = target;
}
/**
*
* Constructs a new <code>TransitivityCongruenceChain</code> which consists
* just of one reflexivity over <code>term</code>.
*
* @param reflexiveTerm
* @param proof
*/
private TransitivityCongruenceChain(DomainTerm reflexiveTerm,
VeritProofNode proofNode) {
this.start = new TransitivityCongruenceChainElement(reflexiveTerm,
proofNode);
this.target = reflexiveTerm;
this.proofNode = proofNode;
this.start.attachReflexivity();
assert (this.isComplete());
}
/**
* Constructs a new <code>TransitivityCongruenceChain</code> from a list of
* justifications.
*
* @param start
* @param end
* @param path
* @param proof
*/
private TransitivityCongruenceChain(DomainTerm start, DomainTerm end,
List<Justification> path, VeritProofNode proofNode) {
assert (path != null);
assert (start != null);
assert (end != null);
assert (proofNode != null);
this.start = new TransitivityCongruenceChainElement(start, proofNode);
this.target = end;
this.proofNode = proofNode;
for (Justification justification : path) {
assert (justification != null);
if (justification.isEqualityJustification()) {
boolean attached = this.getEnd().tryAttach(
justification.getEqualityJustification(), proofNode);
assert (attached);
continue;
}
if (justification.isCongruenceJustification()) {
boolean attached = this.getEnd().tryAttach(
justification.getCongruenceJustification(), proofNode);
assert (attached);
continue;
}
assert (false);
}
assert (this.isComplete());
assert (this.getEnd().getJustficiation() == null);
}
// /**
// *
// * @return a map of implied literals of congruence links to proof nodes
// for
// * them.
// */
// private Map<Formula, VeritProofNode> getProofsForCongruenceLinks() {
// assert (this.start != null);
// assert (this.proofNode != null);
// assert (this.allCongruenceLinksColorable()); // FIXME Expensive check?
//
// Map<Formula, VeritProofNode> result = new HashMap<Formula,
// VeritProofNode>(
// this.numCongruenceLinks() * 2);
//
// TransitivityCongruenceChainElement current = this.start;
// while (current != null) {
// if (current.getCongruenceJustification() != null) {
// assert (current.getEqualityJustification() == null);
// assert (current.getNext() != null);
// assert (current.getTerm() instanceof UninterpretedFunctionInstance);
// assert (current.getNext().getTerm() instanceof
// UninterpretedFunctionInstance);
// Formula literal = current.getLiteral();
// VeritProofNode node = TransitivityCongruenceChain
// .congruenceJustificationToColorableProof(current
// .getCongruenceJustification(),
// (UninterpretedFunctionInstance) current
// .getTerm(),
// (UninterpretedFunctionInstance) current
// .getNext().getTerm(), proofNode);
// result.put(literal, node);
// }
// }
// return result;
// }
/**
* If the same term is visited more than once in this chain (not considering
* subchains) the detour in between is removed.
*/
private void makeShortcuts() {
Map<DomainTerm, TransitivityCongruenceChainElement> termsVisited = new HashMap<DomainTerm, TransitivityCongruenceChainElement>(
this.length() * 2);
TransitivityCongruenceChainElement current = this.start;
while (current != null) {
assert (current.getTerm() != null);
if (current.getCongruenceJustification() != null) {
assert (current.getEqualityJustification() == null);
for (TransitivityCongruenceChain subchain : current
.getCongruenceJustification())
subchain.makeShortcuts();
}
if (termsVisited.containsKey(current.getTerm())) {
TransitivityCongruenceChainElement firstOccurrence = termsVisited
.get(current.getTerm());
if (firstOccurrence.getNext() != current) {
// This is not a reflexivity
firstOccurrence.makeShortcut(current);
current = this.start;
termsVisited.clear();
} else {
current = current.getNext();
continue;
}
}
termsVisited.put(current.getTerm(), current);
current = current.getNext();
}
}
/**
*
* @return a proof node implying the first local chunk, or <code>null</code>
* if the chain starts with a global term
*/
private VeritProofNode getProofForFirstLocalChunk() {
TransitivityCongruenceChainElement endOfFirstLocalChunk = findEndOfFirstLocalChunk();
if (endOfFirstLocalChunk == null)
return null;
VeritProofNode result = getProofForChunk(this.start,
endOfFirstLocalChunk);
return result;
}
/**
*
* @return the element that is at the end of the firstLocalChunk.
*/
private TransitivityCongruenceChainElement findEndOfFirstLocalChunk() {
int startPartition = this.getStartPartition();
if (startPartition == -1)
return null;
assert (startPartition != -1);
TransitivityCongruenceChainElement current = this.start;
while (current.hasNext()) {
int nextTermPartition = current.getNext().getTermPartition();
if (nextTermPartition != startPartition && nextTermPartition != -1) {
break;
}
current = current.getNext();
}
assert (current != this.start);
return current;
}
/**
*
* @return a proof implying the last local chunk, or <code>null</code> if
* the chain ends with a global term, or is just one segment and
* thus already handled by firstLocalChunk.
*/
private VeritProofNode getProofForLastLocalChunk() {
TransitivityCongruenceChainElement startOfLastLocalChunk = findStartOfLastLocalChunk();
if (startOfLastLocalChunk == null)
return null;
VeritProofNode result = getProofForChunk(startOfLastLocalChunk,
this.getEnd());
return result;
}
/**
*
* @return the start of the lastLocalChunk
*/
private TransitivityCongruenceChainElement findStartOfLastLocalChunk() {
int endPartition = this.getEndPartition();
if (endPartition == -1)
return null;
assert (endPartition != -1);
TransitivityCongruenceChainElement end = this.getEnd();
TransitivityCongruenceChainElement current = end;
while (current != null) {
TransitivityCongruenceChainElement predecessor = this
.getPredecessor(current);
if (predecessor == null) {
current = predecessor;
break;
}
int previousTermPartition = predecessor.getTermPartition();
if (previousTermPartition != endPartition
&& previousTermPartition != -1) {
return current;
}
current = predecessor;
}
assert (this.getStartPartition() == endPartition || this
.getStartPartition() == -1);
return this.start;
}
/**
*
* @param element
* @return the first element with a global term following the given
* <code>element</code>, or <code>null</code> if no such element
* exists in this chain.
*/
private TransitivityCongruenceChainElement findNextGlobalElement(
TransitivityCongruenceChainElement element) {
assert (element != null);
assert (element.hasNext());
assert (element.getTermPartition() == -1);
TransitivityCongruenceChainElement current = element.getNext();
assert (current != null);
while (current != null) {
if (current.getTermPartition() == -1)
return current;
current = current.getNext();
}
return null;
}
/**
*
* @return a proof implying the global chunk
*/
private VeritProofNode getProofForGlobalChunk() {
TransitivityCongruenceChainElement end = this.getEnd();
TransitivityCongruenceChainElement startOfGlobalChunk = findEndOfFirstLocalChunk();
if (startOfGlobalChunk == null) {
if (this.getStartPartition() == -1)
startOfGlobalChunk = this.start;
else {
// Whole chain already covered by firstLocalChunk
return null;
}
}
if (startOfGlobalChunk == end) {
// Whole chain already covered by firstLocalChunk
return null;
}
TransitivityCongruenceChainElement endOfGlobalChunk = findStartOfLastLocalChunk();
int endPartition = end.getTermPartition();
if (endOfGlobalChunk == null) {
if (endPartition == -1)
endOfGlobalChunk = end;
else {
// Whole chain already covered by firstLocalChunk
return null;
}
}
if (startOfGlobalChunk == this.start && endOfGlobalChunk == end) {
assert (startOfGlobalChunk.getTermPartition() == endOfGlobalChunk
.getTermPartition());
if (startOfGlobalChunk.getTermPartition() != -1)
return null; // whole chain one color, should be covered by
// firstLocalChunk
}
if (startOfGlobalChunk == endOfGlobalChunk)
return null;
assert (this.indexOf(startOfGlobalChunk) < this
.indexOf(endOfGlobalChunk));
Map<Formula, VeritProofNode> proofsForLocalSubchains = new HashMap<Formula, VeritProofNode>();
List<Formula> globalLiterals = new ArrayList<Formula>();
TransitivityCongruenceChainElement current = startOfGlobalChunk;
while (current != endOfGlobalChunk) {
assert (current.getTermPartition() == -1);
TransitivityCongruenceChainElement next = this
.findNextGlobalElement(current);
assert (next != null);
assert (next.getTermPartition() == -1);
Formula impliedLiteral = this.getLiteral(current, next);
VeritProofNode currentProof = this.getProofForChunk(current, next);
proofsForLocalSubchains.put(impliedLiteral, currentProof);
globalLiterals.add(impliedLiteral);
current = next;
}
assert (globalLiterals.size() == proofsForLocalSubchains.size());
Formula literalImpliedByGlobalChunk = this.getLiteral(
startOfGlobalChunk, endOfGlobalChunk);
List<Formula> globalConclusions = Util
.invertAllLiterals(globalLiterals);
globalConclusions.add(literalImpliedByGlobalChunk);
VeritProof proof = this.proofNode.getProof();
VeritProofNode result = proof.addProofNodeWithFreshName("glob", "",
VeriTToken.EQ_TRANSITIVE, globalConclusions, null, null, false);
for (Formula literal : globalLiterals) {
VeritProofNode other = proofsForLocalSubchains.get(literal);
assert (other != null);
result = result.resolveWith(other, false);
}
return result;
}
/**
*
* @param start
* @param end
* @return a proof for the chunk from start to end
*/
private VeritProofNode getProofForChunk(
TransitivityCongruenceChainElement start,
TransitivityCongruenceChainElement end) {
assert (start != null);
assert (end != null);
int startIndex = this.indexOf(start);
int endIndex = this.indexOf(end);
assert (startIndex >= 0);
assert (endIndex >= 0);
assert (startIndex < endIndex);
assert (start != end);
if (startIndex + 1 == endIndex) {
assert (start.getNext() == end);
return chunkWithLengthTwoToColorableProof(start);
}
assert (endIndex - startIndex >= 2);
List<Formula> conclusions = new ArrayList<Formula>(endIndex
- startIndex);
Map<Formula, VeritProofNode> proofsForCongruences = new HashMap<Formula, VeritProofNode>();
TransitivityCongruenceChainElement current = start;
while (current != end) {
assert (current != null);
Formula literal = current.getLiteral();
conclusions.add(NotFormula.create(literal));
if (current.getEqualityJustification() == null) {
assert (current.getCongruenceJustification() != null);
assert (current.getNext() != null);
assert (current.getTerm() instanceof UninterpretedFunctionInstance);
assert (current.getNext().getTerm() instanceof UninterpretedFunctionInstance);
VeritProofNode node = this
.congruenceJustificationToColorableProofNew(
current.getCongruenceJustification(), current,
current.getNext());
proofsForCongruences.put(literal, node);
}
current = current.getNext();
}
Formula impliedLiteral = this.getLiteral(start, end);
conclusions.add(impliedLiteral);
VeritProof proof = this.proofNode.getProof();
VeritProofNode currentNode = proof.addProofNodeWithFreshName(
"col_tran", "", VeriTToken.EQ_TRANSITIVE, conclusions, null,
null, false);
for (Formula literal : proofsForCongruences.keySet()) {
currentNode = currentNode.resolveWith(
proofsForCongruences.get(literal), false);
}
return currentNode;
}
/**
*
* @return a transitivity leaf using the first local chunk, the shortcut and
* the last local chunk.
*/
private VeritProofNode getProofForFirstShortcutLast() {
TransitivityCongruenceChainElement endOfFirstLocalChunk = findEndOfFirstLocalChunk();
if (endOfFirstLocalChunk == this.getEnd())
return null;
TransitivityCongruenceChainElement startOfLastLocalChunk = findStartOfLastLocalChunk();
if (startOfLastLocalChunk == this.start)
return null;
int startPartition = this.getStartPartition();
int endPartition = this.getEndPartition();
List<Formula> conclusions = new ArrayList<Formula>(this.length());
if (startPartition != -1) {
assert (endOfFirstLocalChunk != null);
assert (endOfFirstLocalChunk != this.start);
DomainEq firstChunkLiteral = this.getLiteral(start,
endOfFirstLocalChunk);
conclusions.add(NotFormula.create(firstChunkLiteral));
if (endPartition != -1) {
assert (startOfLastLocalChunk != null);
assert (startOfLastLocalChunk != this.getEnd());
assert (startOfLastLocalChunk.getTermPartition() == -1);
DomainEq shortcutLiteral = this.getLiteral(
endOfFirstLocalChunk, startOfLastLocalChunk);
conclusions.add(NotFormula.create(shortcutLiteral));
DomainEq lastChunkLiteral = this.getLiteral(
startOfLastLocalChunk, this.getEnd());
conclusions.add(NotFormula.create(lastChunkLiteral));
} else { // End of chain is global
assert (startOfLastLocalChunk == null);
DomainEq shortcutLiteral = getLiteral(endOfFirstLocalChunk,
this.getEnd());
conclusions.add(NotFormula.create(shortcutLiteral));
}
} else { // Start of chain is global
if (endPartition == -1) {
return null;
}
assert (endOfFirstLocalChunk == null);
assert (startOfLastLocalChunk != null);
assert (startOfLastLocalChunk.getTermPartition() == -1);
DomainEq shortcutLiteral = this.getLiteral(this.start,
startOfLastLocalChunk);
conclusions.add(NotFormula.create(shortcutLiteral));
DomainEq lastChunkLiteral = this.getLiteral(startOfLastLocalChunk,
this.getEnd());
conclusions.add(NotFormula.create(lastChunkLiteral));
}
DomainEq impliedLiteral = this.getLiteral(this.start, this.getEnd());
conclusions.add(impliedLiteral);
VeritProof proof = this.proofNode.getProof();
VeritProofNode result = proof.addProofNodeWithFreshName("fsl_", "",
VeriTToken.EQ_TRANSITIVE, conclusions, null, null, false);
return result;
}
/**
*
* @param firstLocalChunk
* @param globalChunk
* @param lastLocalChunk
* @param firstShortcutLast
* @return a node resolving the given nodes to the final result
*/
private VeritProofNode combineFirstGlobalLastChunks(
VeritProofNode firstLocalChunk, VeritProofNode globalChunk,
VeritProofNode lastLocalChunk, VeritProofNode firstShortcutLast) {
if (firstShortcutLast == null) {
if (firstLocalChunk == null)
if (globalChunk == null) {
assert (lastLocalChunk != null);
return lastLocalChunk;
} else {
return globalChunk;
}
else
return firstLocalChunk;
}
assert (firstLocalChunk != null || lastLocalChunk != null);
assert (globalChunk != null);
VeritProofNode result = null;
if (firstLocalChunk != null) {
result = firstShortcutLast.resolveWith(firstLocalChunk, false);
}
if (lastLocalChunk != null) {
result = (result == null ? firstShortcutLast : result).resolveWith(
lastLocalChunk, false);
}
assert (result != null);
result = result.resolveWith(globalChunk, false);
return result;
}
/**
*
* @param element1
* @param element2
* @return an equality literal between the terms of <code>element1</code>
* and <code>element2</code>. In case it occurs in the original
* proof node, the order of this appearance is respected. Literal
* will always be in positive phase.
*/
private DomainEq getLiteral(TransitivityCongruenceChainElement element1,
TransitivityCongruenceChainElement element2) {
List<DomainTerm> terms = new ArrayList<DomainTerm>(2);
assert (element1 != null);
assert (element2 != null);
terms.add(element1.getTerm());
terms.add(element2.getTerm());
DomainEq literal = DomainEq.create(terms, true);
DomainEq reversedLiteral = (DomainEq) Util.reverseEquality(literal);
NotFormula invertedLiteral = NotFormula.create(literal);
NotFormula invertedReversedLiteral = NotFormula.create(reversedLiteral);
List<Formula> conclusions = proofNode.getLiteralConclusions();
if (conclusions.contains(literal)
|| conclusions.contains(invertedLiteral)) {
assert (!conclusions.contains(reversedLiteral) && !conclusions
.contains(invertedReversedLiteral));
return literal;
}
if (conclusions.contains(reversedLiteral)
|| conclusions.contains(invertedReversedLiteral)) {
assert (!conclusions.contains(literal) && !conclusions
.contains(invertedLiteral));
return reversedLiteral;
}
// This is a new literal, not occurring in the proof node.
// return it in the order of the chain, by default.
return literal;
}
/**
*
* @return a proof node for this chain
*/
private VeritProofNode chunkWithLengthTwoToColorableProof(
TransitivityCongruenceChainElement element1) {
TransitivityCongruenceChainElement element2 = element1.getNext();
assert (element2 != null);
assert (element1.getTermPartition() == element2.getTermPartition()
|| element1.getTermPartition() == -1 || element2
.getTermPartition() == -1);
VeritProof proof = this.proofNode.getProof();
Formula literal = this.getLiteral(element1, element2);
Formula invertedLiteral = NotFormula.create(literal);
if (element1.getEqualityJustification() != null) {
assert (element1.getCongruenceJustification() == null);
// return a LEM
List<Formula> conclusions = new ArrayList<Formula>(2);
conclusions.add(invertedLiteral);
conclusions.add(literal);
VeritProofNode result = proof.addProofNodeWithFreshName("LEM", "",
VeriTToken.EQ_TRANSITIVE, conclusions, null, null, false);
return result;
} else {
assert (element1.getCongruenceJustification() != null);
VeritProofNode result = this
.congruenceJustificationToColorableProofNew(
element1.getCongruenceJustification(), element1,
element2);
return result;
}
}
/**
* Reimplementation of
* {@link TransitivityCongruenceChain#toColorableProof()}.
*
* @return a proof node that proofs what is implied by the chain, and is
* split in colorable subproofs.
*/
public VeritProofNode toColorableProofNew() {
assert (this.start != null);
assert (this.proofNode != null);
// Special case: length 1
if (this.start.getNext() == null) {
assert (this.length() == 1);
assert (this.start.getTerm().equals(this.target));
Formula reflexivity = Util.createReflexivity(this.target);
List<Formula> conclusions = new ArrayList<Formula>(1);
conclusions.add(reflexivity);
VeritProof proof = this.proofNode.getProof();
VeritProofNode result = proof
.addProofNodeWithFreshName("reflex", "",
VeriTToken.EQ_REFLEXIVE, conclusions, null, null,
false);
return result;
}
this.splitUncolorableCongruenceLinks();
this.makeShortcuts();
// Special case: length 2
if (this.start.getNext().getNext() == null) {
assert (this.length() == 2);
return chunkWithLengthTwoToColorableProof(this.start);
}
int startPartition = this.getStartPartition();
int endPartition = this.getEndPartition();
assert (startPartition == endPartition || startPartition == -1 || endPartition == -1);
VeritProofNode firstLocalChunk = getProofForFirstLocalChunk();
VeritProofNode globalChunk = getProofForGlobalChunk();
VeritProofNode lastLocalChunk = getProofForLastLocalChunk();
VeritProofNode firstShortcutLast = getProofForFirstShortcutLast();
VeritProofNode result = combineFirstGlobalLastChunks(firstLocalChunk,
globalChunk, lastLocalChunk, firstShortcutLast);
result = result.resolveNegatedReflexivities();
assert (Util.getImpliedLiteral(result.getLiteralConclusions())
.equals(this.getLiteral(this.start, this.getEnd())));
return result;
}
/**
*
* @return a proof node that proofs the targetLiterals, and is split in
* colorable subproofs.
*/
@Deprecated
public VeritProofNode toColorableProof() {
assert (this.proofNode != null);
this.splitUncolorableCongruenceLinks();
int startPartition = this.getStartPartition();
int endPartition = this.getEndPartition();
assert (startPartition == endPartition || startPartition == -1 || endPartition == -1);
TransitivityCongruenceChainElement currentElement = this.start;
TransitivityCongruenceChain firstLocalChunk = null;
TransitivityCongruenceChain chainWithGlobalEnds = null;
TransitivityCongruenceChain lastLocalChunk = null;
// Create chain for first local chunk
if (startPartition > 0) {
List<Justification> path = new ArrayList<Justification>();
DomainTerm currentTerm = null;
while (currentElement.getTermPartition() == startPartition
|| currentElement.getTermPartition() == -1) {
path.add(currentElement.getJustficiation());
currentTerm = currentElement.getTerm();
currentElement = currentElement.getNext();
if (currentElement == null)
return this.toColorableProofBaseCase();
}
path.remove(path.size() - 1);
firstLocalChunk = new TransitivityCongruenceChain(
this.start.getTerm(), currentTerm, path, this.proofNode);
currentElement = this.getPredecessor(currentElement);
assert (currentElement.getTermPartition() == -1);
}
// Create chain for last local chunk
TransitivityCongruenceChainElement globalStart = currentElement;
TransitivityCongruenceChainElement globalEnd = null;
assert (globalStart.getTermPartition() == -1);
if (endPartition > 0) {
currentElement = this.getEnd();
assert (currentElement.getNext() == null);
currentElement = this.getPredecessor(currentElement);
assert (currentElement != null);
List<Justification> path = new ArrayList<Justification>();
while (currentElement.getTermPartition() == endPartition
|| currentElement.getTermPartition() == -1) {
path.add(0, currentElement.getJustficiation());
if (currentElement == this.start)
return this.toColorableProofBaseCase();
currentElement = this.getPredecessor(currentElement);
}
assert (!path.isEmpty());
currentElement = currentElement.getNext();
assert (currentElement.getTermPartition() == -1);
lastLocalChunk = new TransitivityCongruenceChain(
currentElement.getTerm(), this.getEndTerm(), path,
this.proofNode);
globalEnd = currentElement;
}
if (globalEnd == null)
globalEnd = this.getEnd();
assert (globalEnd.getTermPartition() == -1);
assert (globalStart != globalEnd);
// Create chain with two global ends
currentElement = globalStart;
List<Justification> path = new ArrayList<Justification>();
while (currentElement != globalEnd) {
path.add(currentElement.getJustficiation());
currentElement = currentElement.getNext();
assert (currentElement != null);
}
assert (currentElement == globalEnd);
chainWithGlobalEnds = new TransitivityCongruenceChain(
globalStart.getTerm(), globalEnd.getTerm(), path,
this.proofNode);
VeritProofNode proofForMidSection = chainWithGlobalEnds
.chainWithGlobalEndsToColorableProof();
if (firstLocalChunk == null && lastLocalChunk == null)
return proofForMidSection;
// Create "shortcut"-literal for mid section
List<DomainTerm> terms = new ArrayList<DomainTerm>(2);
terms.add(globalStart.getTerm());
terms.add(globalEnd.getTerm());
DomainEq shortcutLiteral = DomainEq.create(terms, true);
// Create conclusions for new node (leaf with firstChunk, shortcut,
// lastChunk)
List<Formula> conclusions = new ArrayList<Formula>();
if (firstLocalChunk != null)
conclusions.addAll(Util.invertAllLiterals(firstLocalChunk
.usedLiterals()));
conclusions.add(Util.invertLiteral(shortcutLiteral));
if (lastLocalChunk != null)
conclusions.addAll(Util.invertAllLiterals(lastLocalChunk
.usedLiterals()));
List<DomainTerm> impliedLiteralTerms = new ArrayList<DomainTerm>(2);
impliedLiteralTerms
.add((DomainTerm) (firstLocalChunk != null ? firstLocalChunk
.getStart().getTerm() : shortcutLiteral.getTerms().get(
0)));
impliedLiteralTerms
.add((DomainTerm) (lastLocalChunk != null ? lastLocalChunk
.getEndTerm() : shortcutLiteral.getTerms().get(1)));
DomainEq impliedLiteral = DomainEq.create(impliedLiteralTerms, true);
conclusions.add(impliedLiteral);
VeritProofNode firstShortcutLast = this.proofNode
.getProof()
.addProofNode(
"fsl_" + TransitivityCongruenceChain.proofNodeCounter++,
VeriTToken.TRANS_CONGR, conclusions, null, null, false);
// Create the final result
List<VeritProofNode> resultSubProofs = new ArrayList<VeritProofNode>(2);
resultSubProofs.add(firstShortcutLast);
resultSubProofs.add(proofForMidSection);
List<Formula> resultConclusions = new ArrayList<Formula>();
for (Formula literal : firstShortcutLast.getLiteralConclusions()) {
if (!resultConclusions.contains(literal))
resultConclusions.add(literal);
}
for (Formula literal : proofForMidSection.getLiteralConclusions()) {
if (!resultConclusions.contains(literal))
resultConclusions.add(literal);
}
while (resultConclusions.contains(shortcutLiteral))
resultConclusions.remove(shortcutLiteral);
while (resultConclusions.contains(Util.invertLiteral(shortcutLiteral)))
resultConclusions.remove(Util.invertLiteral(shortcutLiteral));
Util.removeReflexiveLiterals(resultConclusions);
VeritProofNode result = this.proofNode.getProof().addProofNode(
"split_" + TransitivityCongruenceChain.proofNodeCounter++,
VeriTToken.RESOLUTION, resultConclusions, resultSubProofs,
null, false);
return result;
}
/**
* Call only on chains that span only one partition!
*
* @return a (colorable) VeritProofNode for this chain.
*/
@Deprecated
private VeritProofNode toColorableProofBaseCase() {
assert (this.proofNode != null);
Set<Integer> partitions = this.getPartitionsFromSymbols();
partitions.remove(-1);
// Create implied Literal
List<DomainTerm> terms = new ArrayList<DomainTerm>(2);
terms.add(this.getStart().getTerm());
terms.add(this.getEndTerm());
DomainEq impliedLiteral = DomainEq.create(terms, true);
if (partitions.size() <= 1) {
Set<Formula> usedLiterals = this.usedLiterals();
List<Formula> conclusions = new ArrayList<Formula>(
usedLiterals.size() + 1);
conclusions.addAll(Util.invertAllLiterals(usedLiterals));
conclusions.add(impliedLiteral);
Util.removeReflexiveLiterals(conclusions);
VeritProofNode result = proofNode.getProof().addProofNode(
proofNode.getProof().freshNodeName("col_", ""),
VeriTToken.TRANS_CONGR, conclusions, null, null, false);
return result;
} else {
// Special case: at least one chain link has a congruence
// justification for a local function over another partition
assert (this.length() >= 2);
List<VeritProofNode> congruences = new ArrayList<VeritProofNode>();
List<Formula> conclusions = new ArrayList<Formula>();
TransitivityCongruenceChainElement current = this.start;
partitions = new TreeSet<Integer>();
assert (current != null);
while (current != null) {
// Collect literals for colorable conclusion
// For congruences over other partitions, collect colorable
// subproofs.
if (current.isCongruenceOfLocalFunctionOverOtherPartition()
|| current.isCongruenceOverOtherPartition(partitions)) {
assert (current.getCongruenceJustification() != null);
assert (current.getTerm() != null);
assert (current.getTerm() instanceof UninterpretedFunctionInstance);
assert (current.getNext().getTerm() != null);
assert (current.getNext().getTerm() instanceof UninterpretedFunctionInstance);
VeritProofNode congruence = TransitivityCongruenceChain
.congruenceJustificationToColorableProof(current
.getCongruenceJustification(),
(UninterpretedFunctionInstance) current
.getTerm(),
(UninterpretedFunctionInstance) current
.getNext().getTerm(),
this.proofNode);
congruences.add(congruence);
assert (Util.findPositiveLiteral(congruence
.getLiteralConclusions()) != null);
Formula literal = Util.invertLiteral(Util
.findPositiveLiteral(congruence
.getLiteralConclusions()));
partitions.addAll(literal.getPartitionsFromSymbols());
partitions.remove(-1);
assert (partitions.size() <= 1);
conclusions.add(literal);
} else {
List<Formula> literals = Util.invertAllLiterals(current
.usedLiterals());
for (Formula literal : literals) {
partitions.addAll(literal.getPartitionsFromSymbols());
partitions.remove(-1);
assert (partitions.size() <= 1);
conclusions.add(literal);
}
}
current = current.getNext();
}
// Construct colorable leaf with all literals except the "details"
// of the congruences across other partitions
// First, we need to add the implied literal
conclusions.add(impliedLiteral);
// Now we actually construct the leaf
Util.removeReflexiveLiterals(conclusions);
VeritProofNode currentNode = proofNode.getProof().addProofNode(
proofNode.getProof().freshNodeName("col_", ""),
VeriTToken.TRANS_CONGR, conclusions, null, null, false);
// Add resolution steps to get the final conclusion
for (VeritProofNode currentCongruence : congruences) {
List<VeritProofNode> subProofs = new ArrayList<VeritProofNode>(
2);
subProofs.add(currentNode);
subProofs.add(currentCongruence);
Formula resolvingLiteral = Util.findResolvingLiteral(subProofs);
List<Formula> currentConclusions = new ArrayList<Formula>();
currentConclusions.addAll(subProofs.get(0)
.getLiteralConclusions());
currentConclusions.addAll(subProofs.get(1)
.getLiteralConclusions());
currentConclusions.remove(resolvingLiteral);
currentConclusions.remove(Util.invertLiteral(resolvingLiteral));
Util.removeReflexiveLiterals(currentConclusions);
currentNode = proofNode.getProof().addProofNode(
proofNode.getProof().freshNodeName("res.", ""),
VeriTToken.RESOLUTION, currentConclusions, subProofs,
null, false);
}
return currentNode;
}
}
/**
* Creates a colorable proof for a congruence between the given terms, using
* the given justification.
*
* @param congruenceJustification
* @param element1
*
* @param element2
*
* @return the proof node that asserts the congruence, based on colorable
* leaves.
*/
private VeritProofNode congruenceJustificationToColorableProofNew(
List<TransitivityCongruenceChain> congruenceJustification,
TransitivityCongruenceChainElement element1,
TransitivityCongruenceChainElement element2) {
assert (congruenceJustification != null);
assert (element1.getTerm() instanceof UninterpretedFunctionInstance);
assert (element2.getTerm() instanceof UninterpretedFunctionInstance);
assert (((UninterpretedFunctionInstance) element1.getTerm())
.getFunction().equals(((UninterpretedFunctionInstance) element2
.getTerm()).getFunction()));
int size = congruenceJustification.size();
Map<Formula, VeritProofNode> proofsForCongruence = new HashMap<Formula, VeritProofNode>();
Set<Formula> literalsImpliedBySubchains = new HashSet<Formula>(size * 2);
for (TransitivityCongruenceChain chain : congruenceJustification) {
Set<Integer> chainPartitions = chain.getPartitionsFromTermsOnly();
chainPartitions.remove(-1);
assert (chainPartitions.size() <= 1);
Formula impliedLiteral = chain.getLiteral(chain.start,
chain.getEnd());
if (literalsImpliedBySubchains.contains(impliedLiteral)) {
assert (proofsForCongruence.get(impliedLiteral) != null);
// We already have a proof for this literal
continue;
}
VeritProofNode currentNode = chain.toColorableProofNew();
proofsForCongruence.put(impliedLiteral, currentNode);
literalsImpliedBySubchains.add(impliedLiteral);
}
assert (literalsImpliedBySubchains.size() == proofsForCongruence.size());
Formula impliedLiteral = this.getLiteral(element1, element2);
List<Formula> conclusions = Util
.invertAllLiterals(literalsImpliedBySubchains);
conclusions.add(impliedLiteral);
VeritProof proof = this.proofNode.getProof();
VeritProofNode result = proof.addProofNodeWithFreshName(
"congrJustProof", "", VeriTToken.EQ_CONGRUENT, conclusions,
null, null, false);
for (Formula literal : literalsImpliedBySubchains) {
VeritProofNode other = proofsForCongruence.get(literal);
result = result.resolveWith(other, false);
}
return result;
}
/**
* Creates a colorable proof for a congruence between the given terms, using
* the given justification.
*
* @param congruenceJustification
* @param term1
* instance of a non-global function
* @param term2
* instance of a non-global function
* @param proof
* the proof to which nodes will be added
* @return the proof node that asserts the congruence, based on colorable
* leaves.
*/
@Deprecated
private static VeritProofNode congruenceJustificationToColorableProof(
List<TransitivityCongruenceChain> congruenceJustification,
UninterpretedFunctionInstance term1,
UninterpretedFunctionInstance term2, VeritProofNode proofNode) {
assert (congruenceJustification != null);
assert (term1.getFunction().equals(term2.getFunction()));
// Construct proof nodes for each chain in the congruence
// justification. Remember the implied literals (for later
// resolution).
int size = congruenceJustification.size();
List<VeritProofNode> proofsForCongruence = new ArrayList<VeritProofNode>(
size);
List<Formula> impliedLiterals = new ArrayList<Formula>(size);
for (TransitivityCongruenceChain chain : congruenceJustification) {
Set<Integer> chainPartitions = chain.getPartitionsFromTermsOnly();
chainPartitions.remove(-1);
assert (chainPartitions.size() <= 1);
VeritProofNode currentNode = chain.toColorableProofNew();
proofsForCongruence.add(currentNode);
Formula impliedLiteral = Util.getImpliedLiteral(currentNode
.getLiteralConclusions());
assert (Util.isGlobal(impliedLiteral));
impliedLiterals.add(impliedLiteral);
}
// Create the the implied literal of the congruence
List<DomainTerm> terms = new ArrayList<DomainTerm>(2);
terms.add(term1);
terms.add(term2);
DomainEq congruenceImpliedLiteral = DomainEq.create(terms, true);
List<Formula> congruenceConclusions = new ArrayList<Formula>(
impliedLiterals.size() + 1);
for (Formula literal : impliedLiterals) {
NotFormula negatedLiteral = NotFormula.create(literal);
congruenceConclusions.add(negatedLiteral);
}
congruenceConclusions.add(congruenceImpliedLiteral);
VeritProofNode congruenceNode = proofNode.getProof()
.addProofNodeWithFreshName("congr.", "",
VeriTToken.EQ_CONGRUENT, congruenceConclusions, null,
null, false);
VeritProofNode currentNode = congruenceNode;
for (VeritProofNode currentProofForCongruence : proofsForCongruence) {
List<VeritProofNode> subProofs = new ArrayList<VeritProofNode>(2);
subProofs.add(currentProofForCongruence);
subProofs.add(currentNode);
Formula resolvingLiteral = Util.findResolvingLiteral(subProofs);
List<Formula> conclusions = new ArrayList<Formula>(subProofs.get(0)
.getLiteralConclusions().size()
+ subProofs.get(1).getLiteralConclusions().size());
conclusions.addAll(subProofs.get(0).getLiteralConclusions());
conclusions.addAll(subProofs.get(1).getLiteralConclusions());
conclusions.remove(resolvingLiteral);
conclusions.remove(Util.invertLiteral(resolvingLiteral));
currentNode = proofNode.getProof().addProofNodeWithFreshName(
"res.", "", VeriTToken.RESOLUTION, conclusions, subProofs,
null, false);
}
return currentNode;
}
/**
* Converts a chain with two global Ends into a colorable proof.
*
* @return the colorable proof.
*/
private VeritProofNode chainWithGlobalEndsToColorableProof() {
assert (this.getStartPartition() == -1);
assert (this.getEndPartition() == -1);
Set<Integer> partitions = new HashSet<Integer>();
TransitivityCongruenceChainElement newStart = null;
TransitivityCongruenceChainElement current = start;
while (current != null) {
partitions.addAll(current.getTerm().getPartitionsFromSymbols());
partitions.remove(-1);
if (partitions.size() > 1) {
partitions.removeAll(current.getTerm()
.getPartitionsFromSymbols());
break;
}
current = current.getNext();
}
if (current == null)
newStart = this.getEnd();
else
newStart = this.getPredecessor(current);
assert (newStart != start);
assert (newStart.getTermPartition() == -1);
// Collect literals from the first part of the chain
List<Formula> conclusions = new ArrayList<Formula>();
current = start;
while (current != newStart) {
for (Formula literal : current.usedLiterals())
conclusions.add(NotFormula.create(literal));
current = current.getNext();
}
// Create and add the "shortcut"-literal, that connect from current to
// the end. This one will be used for resolution.
List<DomainTerm> terms = new ArrayList<DomainTerm>(2);
terms.add(current.getTerm());
terms.add(target);
Formula resolvingLiteral = DomainEq.create(terms, true);
conclusions.add(NotFormula.create(resolvingLiteral));
// Add the implied literal of the first part of the chain
List<DomainTerm> terms2 = new ArrayList<DomainTerm>(2);
terms2.add(start.getTerm());
terms2.add(target);
conclusions.add((DomainEq.create(terms2, true)));
// Remove negative reflexive literals (they are false anyway)
Util.removeReflexiveLiterals(conclusions);
VeritProofNode node1 = proofNode.getProof().addProofNodeWithFreshName(
"tcc_left_", "", VeriTToken.TRANS_CONGR, conclusions, null,
null, false);
assert (node1.isColorable());
if (newStart == this.getEnd()) {
// base case for recursion; whole chain in one partition
return node1;
}
TransitivityCongruenceChain secondPart = new TransitivityCongruenceChain(
newStart, target, proofNode);
assert (secondPart.getStartPartition() == -1);
assert (secondPart.getEndPartition() == -1);
VeritProofNode node2 = secondPart.chainWithGlobalEndsToColorableProof();
List<VeritProofNode> clauses = new ArrayList<VeritProofNode>(2);
clauses.add(node1);
clauses.add(node2);
List<Formula> finalConclusions = new ArrayList<Formula>();
finalConclusions.addAll(node1.getLiteralConclusions());
finalConclusions.addAll(node2.getLiteralConclusions());
finalConclusions.remove(resolvingLiteral);
finalConclusions.remove(Util.invertLiteral(resolvingLiteral));
VeritProofNode resNode = proofNode.getProof()
.addProofNodeWithFreshName("tcc_res_", "",
VeriTToken.RESOLUTION, finalConclusions, clauses, null,
false);
return resNode;
}
/**
* If a congruence link spans over more than one partition, a global
* intermediate is added in between.
*/
private void splitUncolorableCongruenceLinks() {
TransitivityCongruenceChainElement current = this.start;
while (current.hasNext()) {
if (current.getCongruenceJustification() != null) {
assert (current.getEqualityJustification() == null);
Set<Integer> partitions = new HashSet<Integer>();
// Reminder: Partitions of terms should not be added.
// This causes a problem when the congruence is about a local
// function.
// Since there are no bad literals at this point, there are also
// no bad terms. I.e., a local function is only applied to
// correct local terms, or to global terms.
// Thus, checking the partitions of the chain underlying
// congruence chain should suffice.
//
// partitions.addAll(current.getTerm().getPartitionsFromSymbols());
// partitions.addAll(current.getNext().getTerm()
// .getPartitionsFromSymbols());
for (TransitivityCongruenceChain chain : current
.getCongruenceJustification())
partitions.addAll(chain.getPartitionsFromSymbols());
partitions.remove(-1);
if (partitions.size() > 1)
this.splitUncolorableCongruenceLink(current);
}
current = current.getNext();
}
}
/**
* Splits the uncolorable congruence link at position <code>element</code>.
*
* @param element
* the left side of an uncolorable congruence link in this chain
*/
private void splitUncolorableCongruenceLink(
TransitivityCongruenceChainElement element) {
assert (element.hasNext());
assert (element.getCongruenceJustification() != null);
assert (element.getEqualityJustification() == null);
List<List<TransitivityCongruenceChain>> listOfSegments = new ArrayList<List<TransitivityCongruenceChain>>();
for (TransitivityCongruenceChain chain : element
.getCongruenceJustification()) {
chain.splitUncolorableCongruenceLinks();
assert (chain.allCongruenceLinksColorable());
TransitivityCongruenceChain chainSegment = chain;
List<TransitivityCongruenceChain> segments = new ArrayList<TransitivityCongruenceChain>();
while (true) {
segments.add(chainSegment);
Set<Integer> partitions = new HashSet<Integer>();
TransitivityCongruenceChainElement newStart = null;
TransitivityCongruenceChainElement current = chainSegment.start;
while (true) {
partitions.addAll(current.getTerm()
.getPartitionsFromSymbols());
partitions.remove(-1);
if (partitions.size() > 1) {
partitions.removeAll(current.getTerm()
.getPartitionsFromSymbols());
current = chainSegment.getPredecessor(current);
break;
}
if (!current.hasNext())
break;
current = current.getNext();
}
if (!current.hasNext()) { // whole (remaining) chain is one
// segment.
break;
} else {
newStart = current;
assert (newStart != chainSegment.start);
chainSegment = chainSegment.split(newStart);
}
}
listOfSegments.add(segments);
}
for (List<TransitivityCongruenceChain> segments : listOfSegments) {
assert (segments.size() > 0);
}
// Check if all segments in listOfSegments are of size 1.
// In that case, no splicing is necessary.
if (Util.allElementsSizeOne(listOfSegments))
return;
// Determine start and target partitions
Set<Integer> partitions = element.getTerm().getPartitionsFromSymbols();
partitions.remove(-1);
assert (partitions.size() <= 1);
int leftPartition = partitions.size() > 0 ? partitions.iterator()
.next() : -1;
partitions.clear();
partitions = element.getNext().getTerm().getPartitionsFromSymbols();
partitions.remove(-1);
assert (partitions.size() <= 1);
int rightPartition = partitions.size() > 0 ? partitions.iterator()
.next() : -1;
// Determine the function in question
assert (element.getTerm() instanceof UninterpretedFunctionInstance);
UninterpretedFunction function = ((UninterpretedFunctionInstance) element
.getTerm()).getFunction();
assert (element.getNext().getTerm() instanceof UninterpretedFunctionInstance);
assert (((UninterpretedFunctionInstance) element.getNext().getTerm())
.getFunction().equals(function));
// Create the patch to splice in
TransitivityCongruenceChain patch = new TransitivityCongruenceChain(
element.getTerm(), element.getNext().getTerm(), this.proofNode);
// Create intermediate elements from list of lists and attach them to
// the patch
// First step: From local partition to a global intermediate
List<TransitivityCongruenceChain> firstJustification = new ArrayList<TransitivityCongruenceChain>();
List<DomainTerm> firstIntermediateParameters = new ArrayList<DomainTerm>();
for (List<TransitivityCongruenceChain> segments : listOfSegments) {
assert (!segments.isEmpty());
TransitivityCongruenceChain firstSegment = segments.get(0);
partitions.clear();
partitions = firstSegment.getPartitionsFromTermsOnly();
partitions.remove(-1);
assert (partitions.size() <= 1);
assert (partitions.isEmpty() || partitions.contains(leftPartition) || Util
.isGlobal(firstSegment.getStart().getTerm()));
if (firstSegment.getStartPartition() != -1) {
firstIntermediateParameters.add(firstSegment.getEndTerm());
firstJustification.add(firstSegment);
segments.remove(0);
} else {
firstIntermediateParameters.add(firstSegment.getStart()
.getTerm());
firstJustification.add(new TransitivityCongruenceChain(
firstSegment.getStart().getTerm(), this.proofNode));
}
if (segments.isEmpty()) {
assert (Util.isGlobal(firstSegment.getEndTerm()) || firstSegment
.getEndPartition() == rightPartition);
segments.add(new TransitivityCongruenceChain(firstSegment
.getEndTerm(), this.proofNode));
assert (segments.size() == 1);
}
}
UninterpretedFunctionInstance nextTerm = null;
try {
nextTerm = UninterpretedFunctionInstance.create(function,
firstIntermediateParameters);
} catch (Exception exc) {
throw new RuntimeException(
"Unexpected exception while creating UninterpretedFunctionInstance. This should not happen.",
exc);
}
boolean firstAttach = patch.getEnd().tryAttach(nextTerm,
firstJustification, proofNode);
assert (firstAttach);
// Loop: From one global intermediate to another global intermediate
while (!(Util.allElementsSizeOne(listOfSegments))) {
List<TransitivityCongruenceChain> currentJustification = new ArrayList<TransitivityCongruenceChain>();
List<DomainTerm> currentIntermediateParameters = new ArrayList<DomainTerm>();
Integer partitionOfThisStep = null;
for (List<TransitivityCongruenceChain> segments : listOfSegments) {
assert (!segments.isEmpty());
TransitivityCongruenceChain currentSegment = segments.get(0);
if (segments.size() == 1) {
currentJustification.add(new TransitivityCongruenceChain(
currentSegment.start.getTerm(), this.proofNode));
currentIntermediateParameters.add(currentSegment.start
.getTerm());
} else {
partitions.clear();
partitions = currentSegment.getPartitionsFromTermsOnly();
partitions.remove(-1);
assert (partitions.size() <= 1);
if (!partitions.isEmpty()) {
if (partitionOfThisStep == null) {
partitionOfThisStep = partitions.iterator().next();
currentJustification.add(currentSegment);
currentIntermediateParameters.add(currentSegment
.getEndTerm());
segments.remove(0);
} else {
if (partitions.iterator().next()
.equals(partitionOfThisStep)) {
currentJustification.add(currentSegment);
currentIntermediateParameters
.add(currentSegment.getEndTerm());
segments.remove(0);
} else {
currentJustification
.add(new TransitivityCongruenceChain(
currentSegment.start.getTerm(),
this.proofNode));
currentIntermediateParameters
.add(currentSegment.start.getTerm());
}
}
} else {
currentJustification.add(currentSegment);
currentIntermediateParameters.add(currentSegment
.getEndTerm());
segments.remove(0);
}
}
}
try {
nextTerm = UninterpretedFunctionInstance.create(function,
currentIntermediateParameters);
} catch (Exception exc) {
throw new RuntimeException(
"Unexpected exception while creating UninterpretedFunctionInstance. This should not happen.",
exc);
}
boolean currentAttach = patch.getEnd().tryAttach(nextTerm,
currentJustification, proofNode);
assert (currentAttach);
}
// Last step: From a global intermediate to a local partition
assert (Util.allElementsSizeOne(listOfSegments));
List<TransitivityCongruenceChain> lastJustification = new ArrayList<TransitivityCongruenceChain>();
List<DomainTerm> lastIntermediateParameters = new ArrayList<DomainTerm>();
for (List<TransitivityCongruenceChain> segments : listOfSegments) {
assert (segments.size() == 1);
TransitivityCongruenceChain currentSegment = segments.get(0);
partitions.clear();
partitions = currentSegment.getPartitionsFromTermsOnly();
partitions.remove(-1);
assert (partitions.size() <= 1);
assert (partitions.isEmpty() || partitions.contains(rightPartition) || Util
.isGlobal(currentSegment.getEndTerm()));
lastIntermediateParameters.add(currentSegment.getEndTerm());
lastJustification.add(currentSegment);
segments.remove(0);
}
try {
nextTerm = UninterpretedFunctionInstance.create(function,
lastIntermediateParameters);
} catch (Exception exc) {
throw new RuntimeException(
"Unexpected exception while creating UninterpretedFunctionInstance. This should not happen.",
exc);
}
boolean lastAttach = patch.getEnd().tryAttach(nextTerm,
lastJustification, proofNode);
assert (lastAttach);
// Now splice in the patch
this.splice(element, patch);
}
/**
* Splits this chain at the given <code>element</code> and returns the
* second part.
*
* @param element
* @return the second part of the split of <code>this</code> chain, split at
* the given <code>element</code>.
*/
private TransitivityCongruenceChain split(
TransitivityCongruenceChainElement element) {
assert (element.hasNext());
// TransitivityCongruenceChainElement current = this.start;
// Set<Integer> partitions1 = new HashSet<Integer>();
// while (current != element) {
// partitions1.addAll(current.getTerm().getPartitionsFromSymbols());
// current = current.getNext();
// assert (current != null);
// }
TransitivityCongruenceChain result = new TransitivityCongruenceChain(
new TransitivityCongruenceChainElement(element), target,
proofNode);
this.target = element.getTerm();
element.makeNextNull();
return result;
}
/**
*
* @return a set of formulas used as links in this chain.
*/
public Set<Formula> usedLiterals() {
Set<Formula> result = new HashSet<Formula>();
TransitivityCongruenceChainElement current = start;
while (current.hasNext()) {
result.addAll(current.usedLiterals());
current = current.getNext();
}
return result;
}
/**
* Splices the given <code>patch</code> into <code>this</code> chain,
* starting at <code>start</code>.
*
* @param start
* the element where to start the splice. Must have the same term
* as the first element of <code>patch</code>.
* @param patch
* the (longer) chain to splice in
*/
public void splice(TransitivityCongruenceChainElement start,
TransitivityCongruenceChain patch) {
assert (start != null);
assert (patch != null);
assert (start.hasNext());
assert (start.getTerm().equals(patch.getStart().getTerm()));
assert (start.getNext().getTerm().equals(patch.getEndTerm()));
// Right splice must come first, otherwise we already overwrite
// start.getNext()
start.getNext().makeRightSplice(patch.getEnd());
start.makeLeftSplice(patch.getStart());
}
/**
* Traverses this chain and splits it at the first global term found. Both
* halfs contain the global term at their originally joint side. If no
* global term is found, uncolorable congruence links are split, before
* attempting to find a global term again.
*
* @return the second half of the chain.
*/
public TransitivityCongruenceChain splitAtGlobalTerm() {
assert (!this.isColorable());
TransitivityCongruenceChainElement currentElement = this.start;
while (currentElement != null) {
if (Util.isGlobal(currentElement.getTerm()))
break;
currentElement = currentElement.getNext();
}
if (currentElement == null) {
this.splitUncolorableCongruenceLinks();
currentElement = this.start;
while (currentElement != null) {
if (Util.isGlobal(currentElement.getTerm()))
break;
currentElement = currentElement.getNext();
}
}
assert (currentElement != null);
assert (currentElement != this.start);
TransitivityCongruenceChainElement copy = new TransitivityCongruenceChainElement(
currentElement);
currentElement.makeNextNull();
DomainTerm oldTarget = this.target;
this.target = currentElement.getTerm();
assert (this.isComplete());
TransitivityCongruenceChain result = new TransitivityCongruenceChain(
copy, oldTarget, this.proofNode);
assert (result.isComplete());
return result;
}
/**
* Splits the chain at the given term, and returns the right side.
*
* @param term
* @return the right side of the split, or <code>null</code> if the term was
* not contained in the chain
*/
public TransitivityCongruenceChain split(DomainTerm term) {
TransitivityCongruenceChainElement current = this.start;
while (current != null) {
if (current.getTerm().equals(term))
return this.split(current);
}
return null;
}
/**
*
* @return the set of partitions formed by all symbols in this chain.
*/
public Set<Integer> getPartitionsFromSymbols() {
Set<Integer> result = new HashSet<Integer>();
TransitivityCongruenceChainElement current = this.start;
while (current != null) {
result.addAll(current.getPartitionsFromSymbols());
current = current.getNext();
}
return result;
}
/**
*
* @return the set of partitions formed by all terms in this chain, without
* considering symbols only occurring in sub-chains (congruence
* justifications)
*/
public Set<Integer> getPartitionsFromTermsOnly() {
Set<Integer> result = new HashSet<Integer>();
TransitivityCongruenceChainElement current = this.start;
while (current != null) {
result.add(current.getTermPartition());
current = current.getNext();
}
return result;
}
/**
*
* @return <code>true</code> iff at most one partition appears in the used
* literals of this chain
*/
public boolean isColorable() {
Set<Integer> partitions = new TreeSet<Integer>();
for (Formula literal : this.usedLiterals())
partitions.addAll(literal.getPartitionsFromSymbols());
partitions.remove(-1);
return partitions.size() <= 1;
}
/**
*
* @return <code>true</code> iff all the terms in this chain (not
* considering sub-chains) can be colored with the same color.
*/
public boolean allTermsSameColor() {
TransitivityCongruenceChainElement current = this.start;
Set<Integer> partitions = new HashSet<Integer>(4);
while (current != null) {
partitions.add(current.getTermPartition());
if (partitions.size() > 2)
return false;
current = current.getNext();
}
partitions.remove(-1);
if (partitions.size() > 1)
return false;
else
return true;
}
/**
* This method (recursively) checks whether all congruence links in the
* chain are colorable. For a congruence link to be colorable, all
* equalities for the parameters as well as the implied literal (equality
* between function instances) have to be colorable with one color.
* Internally, the equalities for the parameters may use different colors,
* but they must be "summarizable" by literals of one color.
*
* @return <code>true</code> iff all congruence links in this chain (and
* subchains) are colorable.
*/
public boolean allCongruenceLinksColorable() {
TransitivityCongruenceChainElement current = this.start;
while (current.hasNext()) {
if (current.getEqualityJustification() != null) {
assert (current.getCongruenceJustification() == null);
current = current.getNext();
continue;
}
assert (current.getCongruenceJustification() != null);
if (!current.hasColorableCongruenceJustification())
return false;
for (TransitivityCongruenceChain subchain : current
.getCongruenceJustification()) {
if (!subchain.allCongruenceLinksColorable())
return false;
}
current = current.getNext();
}
return true;
}
/**
*
* @return the term (currently) at the end of this chain.
*/
public DomainTerm getEndTerm() {
TransitivityCongruenceChainElement current = start;
assert (current != null);
while (current.getNext() != null)
current = current.getNext();
assert (current != null);
return current.getTerm();
}
/**
*
* @return the element (currently) at the end of this chain.
*/
public TransitivityCongruenceChainElement getEnd() {
TransitivityCongruenceChainElement current = start;
assert (current != null);
while (current.getNext() != null)
current = current.getNext();
assert (current != null);
return current;
}
/**
*
* @return <code>true</code> if this chain has reached its target.
*/
public boolean isComplete() {
assert (target != null);
return target.equals(getEndTerm());
}
/**
*
* @return the length (number of elements) of this chain (counting complex
* congruence edges as 1).
*/
public int length() {
int count = 1;
TransitivityCongruenceChainElement current = start;
while (current.getNext() != null) {
current = current.getNext();
count++;
}
assert (current.getNext() == null);
return count;
}
/**
* @return the <code>start</code>
*/
public TransitivityCongruenceChainElement getStart() {
return start;
}
/**
* @return the <code>target</code>
*/
public DomainTerm getTarget() {
return target;
}
/**
*
* @return the partition of the start term.
*/
public int getStartPartition() {
Set<Integer> partitions = start.getTerm().getPartitionsFromSymbols();
if (partitions.size() > 1)
partitions.remove(-1);
assert (partitions.size() == 1);
return partitions.iterator().next();
}
/**
*
* @return the partition of the end term.
*/
public int getEndPartition() {
Set<Integer> partitions = this.getEndTerm().getPartitionsFromSymbols();
if (partitions.size() > 1)
partitions.remove(-1);
assert (partitions.size() == 1);
return partitions.iterator().next();
}
/**
* @return the reversed chain
*/
public TransitivityCongruenceChain reverse() {
List<Justification> reverseJustifications = new ArrayList<Justification>();
TransitivityCongruenceChainElement currentElement = this.start;
while (currentElement != null) {
Justification justification = currentElement.getJustficiation();
if (justification != null)
reverseJustifications.add(0, justification.reverse());
currentElement = currentElement.getNext();
}
TransitivityCongruenceChain result = new TransitivityCongruenceChain(
this.getEndTerm(), this.start.getTerm(), reverseJustifications,
this.proofNode);
return result;
}
/**
*
* @param element
* @return the predecessor of <code>element</code> in this chain, or
* <code>null</code> if either <code>element</code> is not part of
* the chain, or is the first element.
*/
public TransitivityCongruenceChainElement getPredecessor(
TransitivityCongruenceChainElement element) {
if (element == this.start)
return null;
TransitivityCongruenceChainElement currentElement = this.start;
while (currentElement.hasNext()) {
if (currentElement.getNext().equals(element))
return currentElement;
currentElement = currentElement.getNext();
}
return null;
}
/**
* Returns a deep copy of this chain.
*
* @see java.lang.Object#clone()
*/
@Override
public TransitivityCongruenceChain clone() {
List<Justification> clonedJustifications = new ArrayList<Justification>(
this.length() - 1);
TransitivityCongruenceChainElement current = this.start;
while (current.hasNext()) {
clonedJustifications.add(current.getJustficiation().clone());
current = current.getNext();
}
return new TransitivityCongruenceChain(this.start.getTerm(),
this.getEndTerm(), clonedJustifications, this.proofNode);
}
/**
*
* @return the number of congruence links in this chain (not considering
* subchains).
*/
public int numCongruenceLinks() {
TransitivityCongruenceChainElement current = this.start;
int count = 0;
while (current != null) {
if (current.getCongruenceJustification() != null) {
assert (current.getEqualityJustification() == null);
count++;
}
current = current.getNext();
}
return count;
}
/**
* Gives the index of the given element in the chain, or -1 if it is not
* contained. Checks for reference equality (<code>==</code>) and does not
* use <code>equals</code>.
*
* @param element
* @return the index of the given element or -1 if it is not contained in
* the chain.
*/
public int indexOf(TransitivityCongruenceChainElement element) {
assert (start != null);
if (element == null)
return -1;
int count = 0;
TransitivityCongruenceChainElement current = this.start;
while (current != null) {
if (current == element)
return count;
count++;
current = current.getNext();
}
return -1;
}
/**
*
* @param element
* @return <code>true</code> iff <code>element</code> is contained in the
* chain (reference equality).
*/
public boolean contains(TransitivityCongruenceChainElement element) {
return indexOf(element) >= 0;
}
/**
* {@link TransitivityCongruenceChain#getLiteral(TransitivityCongruenceChainElement, TransitivityCongruenceChainElement)}
*
* @return the literal implied by this chain, in the order in which it
* appears in the underlying proof node.
*/
public Formula getLiteral() {
return getLiteral(this.start, this.getEnd());
}
/**
* Computes an interpolant from this chain based on the algorithm presented
* by Fuchs et al. in the paper
* "Ground interpolation for the theory of equality".
*
* After subtracting 1, even partitions are "A", odd partitions are "B".
*
* @return an interpolant from this chain, as described in Fuchs et al.
*/
public Formula fuchsEtAlInterpolant() {
this.splitUncolorableCongruenceLinks();
Formula literal = this.getLiteral();
Set<Integer> partitions = literal.getPartitionsFromSymbols();
partitions.remove(-1);
if (partitions.size() > 1) {
int color = partitions.iterator().next() % 2;
for (int partition : partitions)
assert (partition % 2 == color);
}
if (partitions.isEmpty()) { // Treat global equality as B
Formula result = this.internalInterpolant();
return result;
}
assert (!partitions.isEmpty());
assert (partitions.size() >= 1);
int partition = (partitions.iterator().next() - 1) % 2;
if (partition == 1) { // B equality
Formula result = this.internalInterpolant();
return result;
}
assert (partition == 0); // A equality
Formula result = this.modifiedInternalInterpolant();
return result;
}
/**
*
* @return I(pi) according to Fuchs et al.
*/
private Formula internalInterpolant() {
List<TransitivityCongruenceChain> factors = this.factor();
if (factors.size() == 1) {
assert (factors.get(0).equals(this));
if (this.isBPath()) {
List<Formula> conjuncts = new ArrayList<Formula>();
for (TransitivityCongruenceChain chain : this.getSubChains())
conjuncts.add(chain.internalInterpolant());
Formula result = AndFormula.generate(conjuncts);
return result;
} else {
assert (this.isAPath());
Set<TransitivityCongruenceChain> bPremises = this
.getBPremises();
List<Formula> bPremiseEqualities = new ArrayList<Formula>(
bPremises.size());
List<Formula> conjuncts = new ArrayList<Formula>(
bPremises.size() + 1);
for (TransitivityCongruenceChain bPremise : bPremises) {
conjuncts.add(bPremise.internalInterpolant());
bPremiseEqualities.add(bPremise.getLiteral());
}
AndFormula bPremiseEqualitiesAnd = AndFormula
.generate(bPremiseEqualities);
Formula pathEquality = this.getLiteral();
Formula justification = ImpliesFormula.create(
bPremiseEqualitiesAnd, pathEquality);
conjuncts.add(justification);
Formula result = AndFormula.generate(conjuncts);
return result;
}
}
assert (factors.size() > 1);
List<Formula> conjuncts = new ArrayList<Formula>(factors.size());
for (TransitivityCongruenceChain factor : factors)
conjuncts.add(factor.internalInterpolant());
Formula result = AndFormula.generate(conjuncts);
return result;
}
/**
*
* @return I'(pi) according to Fuchs et al.
*/
private Formula modifiedInternalInterpolant() {
TransitivityCongruenceChain[] decomposition = this.decomposeABA();
assert (decomposition.length == 3);
List<Formula> conjuncts = new ArrayList<Formula>(3);
if (decomposition[1] != null) {
conjuncts.add(decomposition[1].internalInterpolant());
}
Set<TransitivityCongruenceChain> bPremises = new HashSet<TransitivityCongruenceChain>();
if (decomposition[0] != null) {
bPremises.addAll(decomposition[0].getBPremises());
}
if (decomposition[2] != null) {
bPremises.addAll(decomposition[2].getBPremises());
}
List<Formula> bPremiseEqualities = new ArrayList<Formula>(
bPremises.size());
for (TransitivityCongruenceChain bPremise : bPremises) {
conjuncts.add(bPremise.internalInterpolant());
bPremiseEqualities.add(bPremise.getLiteral());
}
AndFormula bPremiseEqualitiesAnd = AndFormula
.generate(bPremiseEqualities);
Formula negatedTheta = decomposition[1] != null ? NotFormula
.create(decomposition[1].getLiteral()) : PropositionalConstant
.create(false);
ImpliesFormula implication = ImpliesFormula.create(
bPremiseEqualitiesAnd, negatedTheta);
conjuncts.add(implication);
AndFormula result = AndFormula.generate(conjuncts);
return result;
}
/**
*
* @return a set of all (direct) subchains of this chain
*/
public Set<TransitivityCongruenceChain> getSubChains() {
Set<TransitivityCongruenceChain> result = new HashSet<TransitivityCongruenceChain>();
TransitivityCongruenceChainElement current = this.start;
while (current != null) {
if (current.getCongruenceJustification() != null) {
assert (current.getEqualityJustification() == null);
result.addAll(current.getCongruenceJustification());
}
current = current.getNext();
}
return result;
}
/**
* Checks whether all nodes on this path can be A-colored. Note that a
* "global" path will return true for both <code>isAPath</code> and
* <code>isBPath</code>.
*
* @return <code>false</code> iff there is at least one node that is only
* B-colorable
*/
public boolean isAPath() {
TransitivityCongruenceChainElement current = this.start;
while (current != null) {
DomainTerm term = current.getTerm();
Set<Integer> partitions = term.getPartitionsFromSymbols();
partitions.remove(-1);
assert (partitions.size() <= 1);
if (partitions.size() == 1) {
int partition = partitions.iterator().next();
if ((partition - 1) % 2 == 1)
return false;
}
current = current.getNext();
}
return true;
}
/**
* Checks whether all nodes on this path can be B-colored. Note that a
* "global" path will return true for both <code>isAPath</code> and
* <code>isBPath</code>.
*
* @return <code>false</code> iff there is at least one node that is only
* A-colorable
*/
public boolean isBPath() {
TransitivityCongruenceChainElement current = this.start;
while (current != null) {
DomainTerm term = current.getTerm();
Set<Integer> partitions = term.getPartitionsFromSymbols();
partitions.remove(-1);
assert (partitions.size() <= 1);
if (partitions.size() == 1) {
int partition = partitions.iterator().next();
if ((partition - 1) % 2 == 0)
return false;
}
current = current.getNext();
}
return true;
}
/**
* Decomposes this chain into pi1-theta-pi2 (see Fuchs et al.). The returned
* array will always be of size 3. If necessary, <code>null</code> values
* will be inserted. Global terms will be treated as B-colorable. The parts
* returned will be clones of this chain.
*
* @return a decomposition of this chain into pi1-theta-pi2, where theta is
* the maxium sub-path with B-colorable endpoints.
*/
public TransitivityCongruenceChain[] decomposeABA() {
TransitivityCongruenceChain[] result = new TransitivityCongruenceChain[3];
TransitivityCongruenceChain clone = this.clone();
TransitivityCongruenceChainElement current = clone.start;
while (current != null) {
DomainTerm term = current.getTerm();
Set<Integer> partitions = term.getPartitionsFromSymbols();
partitions.remove(-1);
assert (partitions.size() <= 1);
if (partitions.isEmpty())
break;
if (partitions.size() == 1) {
int partition = partitions.iterator().next();
if ((partition - 1) % 2 == 1) {
break;
}
}
current = current.getNext();
}
if (current == null || current == clone.getEnd()) {
// Whole chain only A-colorable (with a potential final G)
result[0] = clone;
result[1] = null;
result[2] = null;
return result;
}
if (current == clone.start) {
// First node is B-colorable
result[0] = null;
result[1] = clone;
} else {
// A real A-factor occurred.
// "current" is the global intermediate
assert (Util.isGlobal(current.getTerm()));
result[0] = clone;
result[1] = clone.split(current);
}
// Search for last B-colorable term
TransitivityCongruenceChainElement beginTheta = result[1].start;
current = result[1].getEnd();
while (current != beginTheta) {
DomainTerm term = current.getTerm();
Set<Integer> partitions = term.getPartitionsFromSymbols();
partitions.remove(-1);
assert (partitions.size() <= 1);
if (partitions.isEmpty())
break;
if (partitions.size() == 1) {
int partition = partitions.iterator().next();
if ((partition - 1) % 2 == 1) {
break;
}
}
current = result[1].getPredecessor(current);
}
if (current == result[1].getEnd()) {
// Chain ends with B-color
result[2] = null;
return result;
}
if (current == beginTheta) {
// no B-colorable term in the second half found
// --> Theta is null
result[0] = this.clone();
result[1] = null;
result[2] = null;
return result;
}
assert (Util.isGlobal(current.getTerm()));
result[2] = result[1].split(current);
return result;
}
/**
* Factors a clone of this chain into A- and B-colorable paths.
*
* @return
*/
public List<TransitivityCongruenceChain> factor() {
List<TransitivityCongruenceChain> result = new ArrayList<TransitivityCongruenceChain>();
TransitivityCongruenceChain clone = this.clone();
TransitivityCongruenceChainElement current = clone.start;
int currentColor = current.getTermPartition() > 0 ? (current
.getTermPartition() - 1) % 2 : -1;
while (current != null) {
int color = current.getTermPartition() > 0 ? (current
.getTermPartition() - 1) % 2 : -1;
if (color >= 0) {
if (currentColor < 0)
currentColor = color;
if (color != currentColor) {
TransitivityCongruenceChainElement predecessor = clone
.getPredecessor(current);
assert (predecessor.getTermPartition() == -1);
result.add(clone);
clone = clone.split(predecessor);
current = clone.start.getNext();
assert (current.getTermPartition() > 0);
currentColor = (current.getTermPartition() - 1) % 2;
continue;
}
}
current = current.getNext();
}
result.add(clone);
return result;
}
/**
*
* @return the B-premises of this chain (according to Fuchs et al.)
*/
public Set<TransitivityCongruenceChain> getBPremises() {
Set<TransitivityCongruenceChain> result = new HashSet<TransitivityCongruenceChain>();
if (this.isBPath()) {
result.add(this);
return result;
}
if (this.isAPath()) {
for (TransitivityCongruenceChain subchain : this.getSubChains()) {
result.addAll(subchain.getBPremises());
}
return result;
}
List<TransitivityCongruenceChain> factors = this.factor();
for (TransitivityCongruenceChain factor : factors) {
result.addAll(factor.getBPremises());
}
return result;
}
/**
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode() {
int result = this.start.hashCode() + this.target.hashCode() << 8 + this
.length() << 16;
return result;
}
/**
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (this.hashCode() != obj.hashCode())
return false;
if (!(obj instanceof TransitivityCongruenceChain))
return false;
TransitivityCongruenceChain other = (TransitivityCongruenceChain) obj;
if (!this.start.equals(other.start))
return false;
if (!this.target.equals(other.target))
return false;
TransitivityCongruenceChainElement current = this.start.getNext();
TransitivityCongruenceChainElement currentOhter = other.start.getNext();
while (current != null) {
if (!current.equals(currentOhter))
return false;
current = current.getNext();
currentOhter = currentOhter.getNext();
}
return true;
}
/**
* Returns the first global term. Splits uncolorable congruence links, if
* necessary.
*
* @return the first global term found in this chain, or <code>null</code>
* if there is none.
*
*/
public DomainTerm findGlobalTerm() {
TransitivityCongruenceChainElement current = this.start;
while (current != null) {
DomainTerm term = current.getTerm();
if (Util.isGlobal(term)) {
return term;
}
current = current.getNext();
}
splitUncolorableCongruenceLinks();
current = this.start;
while (current != null) {
DomainTerm term = current.getTerm();
if (Util.isGlobal(term)) {
return term;
}
current = current.getNext();
}
return null;
}
}