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

Commit 6d7dcff

Browse files
Merge pull request #1308 from finos/1306-remove-partition-optimise-flags
remove ability to turn off partitioning and optimising
2 parents e856de4 + 0f52dd3 commit 6d7dcff

File tree

15 files changed

+392
-613
lines changed

15 files changed

+392
-613
lines changed

generator/src/main/java/com/scottlogic/deg/generator/decisiontree/DecisionTreeOptimiser.java

Lines changed: 217 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,221 @@
1616

1717
package com.scottlogic.deg.generator.decisiontree;
1818

19-
public interface DecisionTreeOptimiser {
20-
DecisionTree optimiseTree(DecisionTree tree);
19+
import com.scottlogic.deg.common.profile.constraints.atomic.AtomicConstraint;
20+
import com.scottlogic.deg.common.profile.constraints.atomic.NotConstraint;
21+
22+
import java.util.*;
23+
import java.util.function.Function;
24+
import java.util.stream.Collectors;
25+
import java.util.stream.Stream;
26+
27+
public class DecisionTreeOptimiser {
28+
private static final int MAX_ITERATIONS = 50;
29+
30+
public DecisionTree optimiseTree(DecisionTree tree){
31+
ConstraintNode newRootNode = optimiseLevelOfTree(tree.getRootNode());
32+
return new DecisionTree(newRootNode, tree.getFields());
33+
}
34+
35+
private ConstraintNode optimiseLevelOfTree(ConstraintNode rootNode){
36+
for (int iteration = 0; iteration < MAX_ITERATIONS; iteration++) {
37+
ConstraintNode newRootNode = optimiseDecisions(rootNode);
38+
39+
if (noChangeInDecisionCount(rootNode, newRootNode)) {
40+
return newRootNode;
41+
}
42+
43+
rootNode = newRootNode;
44+
}
45+
46+
return rootNode;
47+
}
48+
49+
private boolean noChangeInDecisionCount(ConstraintNode rootNode, ConstraintNode newRootNode) {
50+
return newRootNode.getDecisions().size() == rootNode.getDecisions().size();
51+
}
52+
53+
private ConstraintNode optimiseDecisions(ConstraintNode rootNode){
54+
AtomicConstraint mostProlificAtomicConstraint = getMostProlificAtomicConstraint(rootNode.getDecisions());
55+
if (mostProlificAtomicConstraint == null){
56+
return rootNode;
57+
}
58+
// Add negation of most prolific constraint to new decision node
59+
AtomicConstraint negatedMostProlificConstraint = mostProlificAtomicConstraint.negate();
60+
61+
List<DecisionNode> factorisableDecisionNodes = rootNode.getDecisions().stream()
62+
.filter(node -> this.decisionIsFactorisable(node, mostProlificAtomicConstraint, negatedMostProlificConstraint))
63+
.collect(Collectors.toList());
64+
if (factorisableDecisionNodes.size() < 2){
65+
return rootNode;
66+
}
67+
68+
// Add most prolific constraint to new decision node
69+
ConstraintNode factorisingConstraintNode = new ConstraintNodeBuilder().addAtomicConstraints(mostProlificAtomicConstraint).build();
70+
ConstraintNode negatedFactorisingConstraintNode = new ConstraintNodeBuilder().addAtomicConstraints(negatedMostProlificConstraint).build();
71+
72+
Set<ConstraintNode> otherOptions = new HashSet<>();
73+
Set<DecisionNode> decisionsToRemove = new HashSet<>();
74+
75+
for (DecisionNode decision : factorisableDecisionNodes) {
76+
DecisionAnalyser analyser = new DecisionAnalyser(decision, mostProlificAtomicConstraint);
77+
DecisionAnalysisResult result = analyser.performAnalysis();
78+
79+
// Perform movement of options
80+
factorisingConstraintNode = addOptionsAsDecisionUnderConstraintNode(factorisingConstraintNode, result.optionsToFactorise);
81+
negatedFactorisingConstraintNode = addOptionsAsDecisionUnderConstraintNode(negatedFactorisingConstraintNode, result.negatedOptionsToFactorise);
82+
otherOptions.addAll(result.adjacentOptions);
83+
decisionsToRemove.add(decision);
84+
}
85+
86+
// Add new decision node
87+
DecisionNode factorisedDecisionNode = new DecisionNode(
88+
Stream.concat(
89+
Stream.of(
90+
optimiseLevelOfTree(factorisingConstraintNode),
91+
optimiseLevelOfTree(negatedFactorisingConstraintNode)),
92+
otherOptions.stream())
93+
.collect(Collectors.toList()));
94+
95+
return rootNode.builder()
96+
.removeDecisions(decisionsToRemove)
97+
.addDecision(factorisedDecisionNode).build();
98+
}
99+
100+
private boolean constraintNodeContainsNegatedConstraints(ConstraintNode node, Set<AtomicConstraint> constraints){
101+
return node.getAtomicConstraints().stream()
102+
.map(AtomicConstraint::negate)
103+
.allMatch(constraints::contains);
104+
}
105+
106+
private ConstraintNode addOptionsAsDecisionUnderConstraintNode(
107+
ConstraintNode newNode,
108+
Collection<ConstraintNode> optionsToAdd) {
109+
if (optionsToAdd.isEmpty()) {
110+
return newNode;
111+
}
112+
113+
return newNode.builder().addDecision(new DecisionNode(optionsToAdd)).build();
114+
}
115+
116+
private int disfavourNotConstraints(Map.Entry<AtomicConstraint, List<AtomicConstraint>> entry){
117+
return entry.getKey() instanceof NotConstraint ? 1 : 0;
118+
}
119+
120+
private AtomicConstraint getMostProlificAtomicConstraint(Collection<DecisionNode> decisions) {
121+
Map<AtomicConstraint, List<AtomicConstraint>> decisionConstraints =
122+
decisions.stream()
123+
.flatMap(dn -> dn.getOptions().stream())
124+
.flatMap(option -> option.getAtomicConstraints().stream())
125+
.collect(Collectors.groupingBy(Function.identity()));
126+
127+
Comparator<Map.Entry<AtomicConstraint, List<AtomicConstraint>>> comparator = Comparator
128+
.comparing(entry -> entry.getValue().size());
129+
comparator = comparator.reversed()
130+
.thenComparing(this::disfavourNotConstraints)
131+
.thenComparing(entry -> entry.getKey().toString());
132+
133+
return decisionConstraints.entrySet()
134+
.stream()
135+
.filter(constraint -> constraint.getValue().size() > 1) // where the number of occurrences > 1
136+
.sorted(comparator)
137+
.map(entry -> getConstraint(entry.getValue()))
138+
.findFirst()
139+
.orElse(null); //otherwise return null
140+
}
141+
142+
private AtomicConstraint getConstraint(List<AtomicConstraint> identicalAtomicConstraints) {
143+
return identicalAtomicConstraints.iterator().next();
144+
}
145+
146+
private boolean decisionIsFactorisable(DecisionNode decision, AtomicConstraint factorisingConstraint, AtomicConstraint negatedFactorisingConstraint){
147+
// The decision should contain ONE option with the MPC
148+
boolean optionWithMPCExists = decision.getOptions().stream()
149+
.filter(option -> atomicConstraintExists(option, factorisingConstraint))
150+
.count() == 1;
151+
152+
// The decision should contain ONE separate option with the negated MPC (which is atomic).
153+
boolean optionWithNegatedMPCExists = decision.getOptions().stream()
154+
.filter(option -> atomicConstraintExists(option, negatedFactorisingConstraint) && option.getAtomicConstraints().size() == 1)
155+
.count() == 1;
156+
157+
return optionWithMPCExists && optionWithNegatedMPCExists;
158+
}
159+
160+
private boolean atomicConstraintExists(ConstraintNode atomicConstraints, AtomicConstraint constraint) {
161+
return atomicConstraints.getAtomicConstraints().stream()
162+
.anyMatch(c -> c.equals(constraint));
163+
}
164+
165+
class DecisionAnalyser {
166+
private DecisionNode decision;
167+
private AtomicConstraint factorisingConstraint;
168+
private AtomicConstraint negatedFactorisingConstraint;
169+
private Set<AtomicConstraint> atomicConstraintsAssociatedWithFactorisingOption = new HashSet<>();
170+
private Set<AtomicConstraint> atomicConstraintsAssociatedWithNegatedOption = new HashSet<>();
171+
172+
DecisionAnalyser(DecisionNode decisionNode, AtomicConstraint factorisingConstraint){
173+
this.decision = decisionNode;
174+
this.factorisingConstraint = factorisingConstraint;
175+
this.negatedFactorisingConstraint = factorisingConstraint.negate();
176+
}
177+
178+
/**
179+
* Iterate through a decision nodes options and determine whether factorisation is possible
180+
*/
181+
DecisionAnalysisResult performAnalysis() {
182+
DecisionAnalysisResult result = new DecisionAnalysisResult();
183+
List<ConstraintNode> otherOptions = new ArrayList<>();
184+
for (ConstraintNode option : decision.getOptions()) {
185+
boolean optionContainsProlificConstraint = atomicConstraintExists(option, factorisingConstraint);
186+
boolean optionContainsNegatedProlificConstraint = atomicConstraintExists(option, negatedFactorisingConstraint);
187+
if (optionContainsProlificConstraint && optionContainsNegatedProlificConstraint) {
188+
throw new RuntimeException("Contradictory constraint node");
189+
} else if (optionContainsProlificConstraint) {
190+
markOptionForFactorisation(factorisingConstraint, option, result.optionsToFactorise, atomicConstraintsAssociatedWithFactorisingOption);
191+
} else if (optionContainsNegatedProlificConstraint) {
192+
markOptionForFactorisation(negatedFactorisingConstraint, option, result.negatedOptionsToFactorise, atomicConstraintsAssociatedWithNegatedOption);
193+
} else {
194+
// This option does not contain the factorising constraint so add to a separate list.
195+
otherOptions.add(option);
196+
}
197+
}
198+
199+
// The following options need moving either to:
200+
// * an option under the factorising constraint node,
201+
// * an option under the negated factorising constraint node,
202+
// * or another option alongside the factorising constraint node
203+
for (ConstraintNode option : otherOptions) {
204+
boolean nodeCanBeMovedUnderFactorised = constraintNodeContainsNegatedConstraints(option, atomicConstraintsAssociatedWithFactorisingOption);
205+
boolean nodeCanBeMovedUnderNegatedFactorised = constraintNodeContainsNegatedConstraints(option, atomicConstraintsAssociatedWithNegatedOption);
206+
if (nodeCanBeMovedUnderFactorised) {
207+
result.optionsToFactorise.add(option);
208+
} else if (nodeCanBeMovedUnderNegatedFactorised) {
209+
result.negatedOptionsToFactorise.add(option);
210+
} else {
211+
result.adjacentOptions.add(option);
212+
}
213+
}
214+
return result;
215+
}
216+
217+
218+
private void markOptionForFactorisation(
219+
AtomicConstraint factorisingConstraint,
220+
ConstraintNode node,
221+
List<ConstraintNode> options,
222+
Set<AtomicConstraint> constraints) {
223+
ConstraintNode newOption = node.builder().removeAtomicConstraint(factorisingConstraint).build();
224+
if (!newOption.getAtomicConstraints().isEmpty()) {
225+
options.add(newOption);
226+
constraints.addAll(newOption.getAtomicConstraints());
227+
}
228+
}
229+
}
230+
231+
static class DecisionAnalysisResult {
232+
List<ConstraintNode> optionsToFactorise = new ArrayList<>();
233+
List<ConstraintNode> negatedOptionsToFactorise = new ArrayList<>();
234+
List<ConstraintNode> adjacentOptions = new ArrayList<>();
235+
}
21236
}

0 commit comments

Comments
 (0)