|
@@ -3,7 +3,6 @@ package pl.wojciechkarpiel.jhou.unifier.tree;
|
|
|
import pl.wojciechkarpiel.jhou.ast.Equality;
|
|
|
import pl.wojciechkarpiel.jhou.ast.Term;
|
|
|
import pl.wojciechkarpiel.jhou.ast.Variable;
|
|
|
-import pl.wojciechkarpiel.jhou.normalizer.Normalizer;
|
|
|
import pl.wojciechkarpiel.jhou.substitution.Substitution;
|
|
|
import pl.wojciechkarpiel.jhou.substitution.SubstitutionPair;
|
|
|
import pl.wojciechkarpiel.jhou.unifier.DisagreementPair;
|
|
@@ -17,21 +16,40 @@ import pl.wojciechkarpiel.jhou.util.Pair;
|
|
|
|
|
|
import java.util.*;
|
|
|
import java.util.stream.Collectors;
|
|
|
+import java.util.stream.Stream;
|
|
|
|
|
|
public class WorkWorkNode implements Tree {
|
|
|
|
|
|
private final Substitution fromParent;
|
|
|
private final DisagreementSet disagreementSet;
|
|
|
- private final boolean pretendYoureDoingFirstOrder;
|
|
|
+ private final boolean pretendYoureDoingFirstOrder; // hack
|
|
|
|
|
|
private List<Tree> children;
|
|
|
private final Tree parent;
|
|
|
|
|
|
- public WorkWorkNode(Tree parent, Substitution fromParent, DisagreementSet disagreementSet) {
|
|
|
- this(parent, fromParent, disagreementSet, false);
|
|
|
+ public static WorkWorkNode searchTreeRoot(Term a, Term b) {
|
|
|
+ return searchTreeRoot(new DisagreementSet(ListUtil.of(new DisagreementPair(a, b))));
|
|
|
}
|
|
|
|
|
|
- public WorkWorkNode(Tree parent, Substitution fromParent, DisagreementSet disagreementSet, boolean pretendYoureDoingFirstOrder) {
|
|
|
+ public static WorkWorkNode searchTreeRoot(DisagreementSet disagreementSet) {
|
|
|
+ return new WorkWorkNode(disagreementSet, false);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Hack for solving first-order problems faster. Will break if presented with a higher-order problem.
|
|
|
+ */
|
|
|
+ public static Tree firstOrderTree(DisagreementSet disagreementSet) {
|
|
|
+ return new WorkWorkNode(disagreementSet, true);
|
|
|
+ }
|
|
|
+
|
|
|
+ private WorkWorkNode(DisagreementSet disagreementSet, boolean pretendYoureDoingFirstOrder) {
|
|
|
+ this(null, Substitution.empty(), disagreementSet, pretendYoureDoingFirstOrder);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * @param pretendYoureDoingFirstOrder total hack, don't use. TODO: write a separate 1st order unification class
|
|
|
+ */
|
|
|
+ private WorkWorkNode(Tree parent, Substitution fromParent, DisagreementSet disagreementSet, boolean pretendYoureDoingFirstOrder) {
|
|
|
this.parent = parent;
|
|
|
this.fromParent = fromParent;
|
|
|
this.disagreementSet = disagreementSet;
|
|
@@ -46,9 +64,10 @@ public class WorkWorkNode implements Tree {
|
|
|
public boolean itsOver() {
|
|
|
if (children == null) return false;
|
|
|
else {
|
|
|
+ pruneDirectChildren();
|
|
|
boolean itsOver = children.stream().allMatch(Tree::itsOver);
|
|
|
// prune the tree
|
|
|
- if (itsOver) {
|
|
|
+ if (itsOver && !fatherOfSingleFailure()) {
|
|
|
children = ListUtil.of(new NagmiNode(this));
|
|
|
}
|
|
|
return itsOver;
|
|
@@ -58,6 +77,7 @@ public class WorkWorkNode implements Tree {
|
|
|
@Override
|
|
|
public Optional<WeBackNode> weBack() {
|
|
|
if (children == null) return Optional.empty();
|
|
|
+ pruneDirectChildren();
|
|
|
return children.stream()
|
|
|
.map(Tree::weBack)
|
|
|
.filter(Optional::isPresent)
|
|
@@ -68,15 +88,14 @@ public class WorkWorkNode implements Tree {
|
|
|
@Override
|
|
|
public void expandOnce() {
|
|
|
if (children != null) {
|
|
|
+ pruneDirectChildren();
|
|
|
children.forEach(Tree::expandOnce);
|
|
|
} else {
|
|
|
SimplificationResult simplify = Simplifier.simplify(disagreementSet);
|
|
|
-
|
|
|
- WorkWorkNode thiz = this;
|
|
|
children = simplify.visit(new SimplificationVisitor<List<Tree>>() {
|
|
|
@Override
|
|
|
public List<Tree> visitSuccess(SimplificationSuccess success) {
|
|
|
- return ListUtil.of(new WeBackNode(thiz, success.getSolution()));
|
|
|
+ return ListUtil.of(new WeBackNode(WorkWorkNode.this, success.getSolution()));
|
|
|
}
|
|
|
|
|
|
@Override
|
|
@@ -86,7 +105,7 @@ public class WorkWorkNode implements Tree {
|
|
|
|
|
|
@Override
|
|
|
public List<Tree> visitFailure(NonUnifiable nonUnifiable) {
|
|
|
- return ListUtil.of(new NagmiNode(thiz));
|
|
|
+ return ListUtil.of(new NagmiNode(WorkWorkNode.this));
|
|
|
}
|
|
|
});
|
|
|
}
|
|
@@ -99,55 +118,57 @@ public class WorkWorkNode implements Tree {
|
|
|
|
|
|
private List<Tree> createChildNodes(DisagreementSet disagreement) {
|
|
|
List<Tree> result = new ArrayList<>();
|
|
|
- List<Substitution> flattenedSubstitutions = disagreement.getDisagreements().stream()
|
|
|
+ Stream<Substitution> flattenedSubstitutionStream = disagreement.getDisagreements().stream()
|
|
|
.filter(dp -> dp.getType() == PairType.RIGID_FLEXIBLE)
|
|
|
.flatMap(disagreementPair ->
|
|
|
Matcher.match(disagreementPair.getMostRigid(), disagreementPair.getLeastRigid())
|
|
|
- .intoSubstitutionStream())
|
|
|
- .collect(Collectors.toList());
|
|
|
- flattenedSubstitutions = deduplicateSort(flattenedSubstitutions);
|
|
|
+ .intoSubstitutionStream());
|
|
|
+ List<Substitution> flattenedSubstitutions;
|
|
|
if (pretendYoureDoingFirstOrder) { // hack to seed up first order search
|
|
|
// this works because in 1st order search any found substitution must be a good one,
|
|
|
// so we don't need to create multiple tree branches (effectively turning exponential into linear)
|
|
|
- if (!flattenedSubstitutions.isEmpty()) flattenedSubstitutions = ListUtil.of(flattenedSubstitutions.get(0));
|
|
|
+ // but really, a much saner option would be to wire another class for 1st order unification, it's simple
|
|
|
+ flattenedSubstitutions = flattenedSubstitutionStream.findFirst().map(ListUtil::of).orElse(ListUtil.of());
|
|
|
+ } else {
|
|
|
+ flattenedSubstitutions = deduplicateSort(flattenedSubstitutionStream.collect(Collectors.toList()));
|
|
|
}
|
|
|
for (Substitution possibleSolution : flattenedSubstitutions) {
|
|
|
- DisagreementSet newDs = new DisagreementSet(disagreement.getDisagreements().stream().map(disagreementPair ->
|
|
|
- new DisagreementPair(
|
|
|
- Normalizer.betaNormalize(possibleSolution.substitute(disagreementPair.getMostRigid().backToTerm())),
|
|
|
- Normalizer.betaNormalize(possibleSolution.substitute(disagreementPair.getLeastRigid().backToTerm()))
|
|
|
- )).collect(Collectors.toList()));
|
|
|
-
|
|
|
- Tree tree = new WorkWorkNode(this, possibleSolution, newDs, pretendYoureDoingFirstOrder);
|
|
|
+ DisagreementSet newDisagreementSet = new DisagreementSet(disagreement.getDisagreements().stream()
|
|
|
+ .map(disagreementPair ->
|
|
|
+ new DisagreementPair(
|
|
|
+ possibleSolution.substitute(disagreementPair.getMostRigid().backToTerm()),
|
|
|
+ possibleSolution.substitute(disagreementPair.getLeastRigid().backToTerm())
|
|
|
+ )).collect(Collectors.toList()));
|
|
|
+
|
|
|
+ Tree tree = new WorkWorkNode(this, possibleSolution, newDisagreementSet, pretendYoureDoingFirstOrder);
|
|
|
result.add(tree);
|
|
|
}
|
|
|
-
|
|
|
return result;
|
|
|
}
|
|
|
|
|
|
/*
|
|
|
- * optiomize flatsubs in a folllowing way:
|
|
|
+ * Optimization pass aimed at minimizing tree branching
|
|
|
*
|
|
|
- * 1. deduplicate subs, ie let all x,S(x) be different
|
|
|
- * 2. Sort by count of sobstitutions for a var
|
|
|
+ * 1. Deduplicate substitutions, identifying alpha-equal ones
|
|
|
+ * 2. Sort by count of possible substitution for a given variable
|
|
|
* 3. generate children only for the least used var (but keep the other disagreement pairs!)
|
|
|
*
|
|
|
* method assumes that all inputs are single-variable substitutions
|
|
|
*/
|
|
|
private static List<Substitution> deduplicateSort(List<Substitution> input) {
|
|
|
if (input.isEmpty()) return input;
|
|
|
-
|
|
|
// List<Term> and not Set<Term> because of alpha-equality problem
|
|
|
Map<Variable, List<Term>> dedupByVar = new HashMap<>(input.size());
|
|
|
- for (Substitution pair_ : input) {
|
|
|
- if (pair_.getSubstitution().size() > 1) throw new RuntimeException();
|
|
|
- if (pair_.getSubstitution().isEmpty()) continue;
|
|
|
- SubstitutionPair pair = pair_.getSubstitution().get(0);
|
|
|
- List<Term> s_ = new ArrayList<>();
|
|
|
- List<Term> s = dedupByVar.putIfAbsent(pair.getVariable(), s_);
|
|
|
- if (s == null) s = s_;
|
|
|
+ for (Substitution substitution : input) {
|
|
|
+ if (substitution.getSubstitution().size() > 1) throw new RuntimeException();
|
|
|
+ if (substitution.getSubstitution().isEmpty()) continue;
|
|
|
+ SubstitutionPair pair = substitution.getSubstitution().get(0);
|
|
|
+ dedupByVar.putIfAbsent(pair.getVariable(), new ArrayList<>());
|
|
|
+ List<Term> termsForVar = dedupByVar.get(pair.getVariable());
|
|
|
// could be Set.add if not for alpha equality (bruijn would fix the problem of hashcode-equals for alpha)
|
|
|
- if (s.stream().noneMatch(t -> Equality.alphaEqual(t, pair.getTerm()))) s.add(pair.getTerm());
|
|
|
+ if (termsForVar.stream().noneMatch(t -> Equality.alphaEqual(t, pair.getTerm()))) {
|
|
|
+ termsForVar.add(pair.getTerm());
|
|
|
+ }
|
|
|
}
|
|
|
if (dedupByVar.isEmpty()) {
|
|
|
return input;
|
|
@@ -157,26 +178,34 @@ public class WorkWorkNode implements Tree {
|
|
|
int minCount = dedupByVar.values().stream().mapToInt(List::size).min().getAsInt();
|
|
|
dedupByVar.entrySet().stream().filter(p -> p.getValue().size() == minCount)
|
|
|
.forEach(p -> least.add(Pair.of(p.getKey(), p.getValue())));
|
|
|
-
|
|
|
least.sort(Comparator.comparingInt(o -> o.getLeft().getId().getId()));
|
|
|
-
|
|
|
Pair<Variable, List<Term>> minSized = least.get(0);
|
|
|
return minSized.getRight().stream()
|
|
|
// Sorting here doesn't help the algorithm, it's here to ensure deterministic outputs
|
|
|
+ // (technically it's not truly deterministic because of hashcode overlap, but it's good enough)
|
|
|
.sorted(Comparator.comparingInt(Term::hashCode))
|
|
|
- .map(sbs -> new Substitution(minSized.getLeft(), sbs))
|
|
|
+ .map(term -> new Substitution(minSized.getLeft(), term))
|
|
|
.collect(Collectors.toList());
|
|
|
}
|
|
|
-}
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
|
|
|
+ private void pruneDirectChildren() {
|
|
|
+ if (fatherOfSingleFailure()) return;
|
|
|
+ if (children.stream().anyMatch(WorkWorkNode::canBePruned)) {
|
|
|
+ children = children.stream().filter(c -> !WorkWorkNode.canBePruned(c)).collect(Collectors.toList());
|
|
|
+ if (children.isEmpty()) children = ListUtil.of(new NagmiNode(this));
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
+ /**
|
|
|
+ * Checks only direct children
|
|
|
+ */
|
|
|
+ private static boolean canBePruned(Tree child) {
|
|
|
+ if (child instanceof NagmiNode) return child.itsOver();
|
|
|
+ if (child instanceof WeBackNode) return child.itsOver();
|
|
|
+ return false;
|
|
|
+ }
|
|
|
|
|
|
+ private boolean fatherOfSingleFailure() {
|
|
|
+ return children.size() == 1 && children.get(0) instanceof NagmiNode;
|
|
|
+ }
|
|
|
+}
|