Skip to content
This repository has been archived by the owner on Apr 14, 2023. It is now read-only.

Commit

Permalink
Merge pull request #1308 from finos/1306-remove-partition-optimise-flags
Browse files Browse the repository at this point in the history
remove ability to turn off partitioning and optimising
  • Loading branch information
pdaulbyscottlogic authored Sep 11, 2019
2 parents e856de4 + 0f52dd3 commit 6d7dcff
Show file tree
Hide file tree
Showing 15 changed files with 392 additions and 613 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,221 @@

package com.scottlogic.deg.generator.decisiontree;

public interface DecisionTreeOptimiser {
DecisionTree optimiseTree(DecisionTree tree);
import com.scottlogic.deg.common.profile.constraints.atomic.AtomicConstraint;
import com.scottlogic.deg.common.profile.constraints.atomic.NotConstraint;

import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class DecisionTreeOptimiser {
private static final int MAX_ITERATIONS = 50;

public DecisionTree optimiseTree(DecisionTree tree){
ConstraintNode newRootNode = optimiseLevelOfTree(tree.getRootNode());
return new DecisionTree(newRootNode, tree.getFields());
}

private ConstraintNode optimiseLevelOfTree(ConstraintNode rootNode){
for (int iteration = 0; iteration < MAX_ITERATIONS; iteration++) {
ConstraintNode newRootNode = optimiseDecisions(rootNode);

if (noChangeInDecisionCount(rootNode, newRootNode)) {
return newRootNode;
}

rootNode = newRootNode;
}

return rootNode;
}

private boolean noChangeInDecisionCount(ConstraintNode rootNode, ConstraintNode newRootNode) {
return newRootNode.getDecisions().size() == rootNode.getDecisions().size();
}

private ConstraintNode optimiseDecisions(ConstraintNode rootNode){
AtomicConstraint mostProlificAtomicConstraint = getMostProlificAtomicConstraint(rootNode.getDecisions());
if (mostProlificAtomicConstraint == null){
return rootNode;
}
// Add negation of most prolific constraint to new decision node
AtomicConstraint negatedMostProlificConstraint = mostProlificAtomicConstraint.negate();

List<DecisionNode> factorisableDecisionNodes = rootNode.getDecisions().stream()
.filter(node -> this.decisionIsFactorisable(node, mostProlificAtomicConstraint, negatedMostProlificConstraint))
.collect(Collectors.toList());
if (factorisableDecisionNodes.size() < 2){
return rootNode;
}

// Add most prolific constraint to new decision node
ConstraintNode factorisingConstraintNode = new ConstraintNodeBuilder().addAtomicConstraints(mostProlificAtomicConstraint).build();
ConstraintNode negatedFactorisingConstraintNode = new ConstraintNodeBuilder().addAtomicConstraints(negatedMostProlificConstraint).build();

Set<ConstraintNode> otherOptions = new HashSet<>();
Set<DecisionNode> decisionsToRemove = new HashSet<>();

for (DecisionNode decision : factorisableDecisionNodes) {
DecisionAnalyser analyser = new DecisionAnalyser(decision, mostProlificAtomicConstraint);
DecisionAnalysisResult result = analyser.performAnalysis();

// Perform movement of options
factorisingConstraintNode = addOptionsAsDecisionUnderConstraintNode(factorisingConstraintNode, result.optionsToFactorise);
negatedFactorisingConstraintNode = addOptionsAsDecisionUnderConstraintNode(negatedFactorisingConstraintNode, result.negatedOptionsToFactorise);
otherOptions.addAll(result.adjacentOptions);
decisionsToRemove.add(decision);
}

// Add new decision node
DecisionNode factorisedDecisionNode = new DecisionNode(
Stream.concat(
Stream.of(
optimiseLevelOfTree(factorisingConstraintNode),
optimiseLevelOfTree(negatedFactorisingConstraintNode)),
otherOptions.stream())
.collect(Collectors.toList()));

return rootNode.builder()
.removeDecisions(decisionsToRemove)
.addDecision(factorisedDecisionNode).build();
}

private boolean constraintNodeContainsNegatedConstraints(ConstraintNode node, Set<AtomicConstraint> constraints){
return node.getAtomicConstraints().stream()
.map(AtomicConstraint::negate)
.allMatch(constraints::contains);
}

private ConstraintNode addOptionsAsDecisionUnderConstraintNode(
ConstraintNode newNode,
Collection<ConstraintNode> optionsToAdd) {
if (optionsToAdd.isEmpty()) {
return newNode;
}

return newNode.builder().addDecision(new DecisionNode(optionsToAdd)).build();
}

private int disfavourNotConstraints(Map.Entry<AtomicConstraint, List<AtomicConstraint>> entry){
return entry.getKey() instanceof NotConstraint ? 1 : 0;
}

private AtomicConstraint getMostProlificAtomicConstraint(Collection<DecisionNode> decisions) {
Map<AtomicConstraint, List<AtomicConstraint>> decisionConstraints =
decisions.stream()
.flatMap(dn -> dn.getOptions().stream())
.flatMap(option -> option.getAtomicConstraints().stream())
.collect(Collectors.groupingBy(Function.identity()));

Comparator<Map.Entry<AtomicConstraint, List<AtomicConstraint>>> comparator = Comparator
.comparing(entry -> entry.getValue().size());
comparator = comparator.reversed()
.thenComparing(this::disfavourNotConstraints)
.thenComparing(entry -> entry.getKey().toString());

return decisionConstraints.entrySet()
.stream()
.filter(constraint -> constraint.getValue().size() > 1) // where the number of occurrences > 1
.sorted(comparator)
.map(entry -> getConstraint(entry.getValue()))
.findFirst()
.orElse(null); //otherwise return null
}

private AtomicConstraint getConstraint(List<AtomicConstraint> identicalAtomicConstraints) {
return identicalAtomicConstraints.iterator().next();
}

private boolean decisionIsFactorisable(DecisionNode decision, AtomicConstraint factorisingConstraint, AtomicConstraint negatedFactorisingConstraint){
// The decision should contain ONE option with the MPC
boolean optionWithMPCExists = decision.getOptions().stream()
.filter(option -> atomicConstraintExists(option, factorisingConstraint))
.count() == 1;

// The decision should contain ONE separate option with the negated MPC (which is atomic).
boolean optionWithNegatedMPCExists = decision.getOptions().stream()
.filter(option -> atomicConstraintExists(option, negatedFactorisingConstraint) && option.getAtomicConstraints().size() == 1)
.count() == 1;

return optionWithMPCExists && optionWithNegatedMPCExists;
}

private boolean atomicConstraintExists(ConstraintNode atomicConstraints, AtomicConstraint constraint) {
return atomicConstraints.getAtomicConstraints().stream()
.anyMatch(c -> c.equals(constraint));
}

class DecisionAnalyser {
private DecisionNode decision;
private AtomicConstraint factorisingConstraint;
private AtomicConstraint negatedFactorisingConstraint;
private Set<AtomicConstraint> atomicConstraintsAssociatedWithFactorisingOption = new HashSet<>();
private Set<AtomicConstraint> atomicConstraintsAssociatedWithNegatedOption = new HashSet<>();

DecisionAnalyser(DecisionNode decisionNode, AtomicConstraint factorisingConstraint){
this.decision = decisionNode;
this.factorisingConstraint = factorisingConstraint;
this.negatedFactorisingConstraint = factorisingConstraint.negate();
}

/**
* Iterate through a decision nodes options and determine whether factorisation is possible
*/
DecisionAnalysisResult performAnalysis() {
DecisionAnalysisResult result = new DecisionAnalysisResult();
List<ConstraintNode> otherOptions = new ArrayList<>();
for (ConstraintNode option : decision.getOptions()) {
boolean optionContainsProlificConstraint = atomicConstraintExists(option, factorisingConstraint);
boolean optionContainsNegatedProlificConstraint = atomicConstraintExists(option, negatedFactorisingConstraint);
if (optionContainsProlificConstraint && optionContainsNegatedProlificConstraint) {
throw new RuntimeException("Contradictory constraint node");
} else if (optionContainsProlificConstraint) {
markOptionForFactorisation(factorisingConstraint, option, result.optionsToFactorise, atomicConstraintsAssociatedWithFactorisingOption);
} else if (optionContainsNegatedProlificConstraint) {
markOptionForFactorisation(negatedFactorisingConstraint, option, result.negatedOptionsToFactorise, atomicConstraintsAssociatedWithNegatedOption);
} else {
// This option does not contain the factorising constraint so add to a separate list.
otherOptions.add(option);
}
}

// The following options need moving either to:
// * an option under the factorising constraint node,
// * an option under the negated factorising constraint node,
// * or another option alongside the factorising constraint node
for (ConstraintNode option : otherOptions) {
boolean nodeCanBeMovedUnderFactorised = constraintNodeContainsNegatedConstraints(option, atomicConstraintsAssociatedWithFactorisingOption);
boolean nodeCanBeMovedUnderNegatedFactorised = constraintNodeContainsNegatedConstraints(option, atomicConstraintsAssociatedWithNegatedOption);
if (nodeCanBeMovedUnderFactorised) {
result.optionsToFactorise.add(option);
} else if (nodeCanBeMovedUnderNegatedFactorised) {
result.negatedOptionsToFactorise.add(option);
} else {
result.adjacentOptions.add(option);
}
}
return result;
}


private void markOptionForFactorisation(
AtomicConstraint factorisingConstraint,
ConstraintNode node,
List<ConstraintNode> options,
Set<AtomicConstraint> constraints) {
ConstraintNode newOption = node.builder().removeAtomicConstraint(factorisingConstraint).build();
if (!newOption.getAtomicConstraints().isEmpty()) {
options.add(newOption);
constraints.addAll(newOption.getAtomicConstraints());
}
}
}

static class DecisionAnalysisResult {
List<ConstraintNode> optionsToFactorise = new ArrayList<>();
List<ConstraintNode> negatedOptionsToFactorise = new ArrayList<>();
List<ConstraintNode> adjacentOptions = new ArrayList<>();
}
}
Loading

0 comments on commit 6d7dcff

Please sign in to comment.