Skip to content

Commit 90beebf

Browse files
committed
Updates. will rebase
1 parent 16503fe commit 90beebf

File tree

16 files changed

+281
-207
lines changed

16 files changed

+281
-207
lines changed

smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/logic/bdd/Bdd.java

Lines changed: 46 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ public final class Bdd implements ToNode {
5656
private final Parameters parameters;
5757
private final List<Condition> conditions;
5858
private final List<Rule> results;
59-
private final List<int[]> nodes;
59+
private final int[][] nodes;
6060
private final int rootRef;
6161

6262
/**
@@ -83,7 +83,7 @@ static Bdd from(Cfg cfg, BddBuilder bddBuilder, ConditionOrderingStrategy orderi
8383
return new BddCompiler(cfg, orderingStrategy, bddBuilder).compile();
8484
}
8585

86-
public Bdd(Parameters params, List<Condition> conditions, List<Rule> results, List<int[]> nodes, int rootRef) {
86+
public Bdd(Parameters params, List<Condition> conditions, List<Rule> results, int[][] nodes, int rootRef) {
8787
this.parameters = Objects.requireNonNull(params, "params is null");
8888
this.conditions = conditions;
8989
this.results = results;
@@ -125,9 +125,9 @@ public List<Rule> getResults() {
125125
/**
126126
* Gets the BDD nodes.
127127
*
128-
* @return list of node triples
128+
* @return array of node triples
129129
*/
130-
public List<int[]> getNodes() {
130+
public int[][] getNodes() {
131131
return nodes;
132132
}
133133

@@ -174,24 +174,28 @@ && nodesEqual(nodes, other.nodes)
174174
&& Objects.equals(parameters, other.parameters);
175175
}
176176

177-
private static boolean nodesEqual(List<int[]> a, List<int[]> b) {
178-
if (a.size() != b.size()) {
177+
private static boolean nodesEqual(int[][] a, int[][] b) {
178+
if (a.length != b.length) {
179179
return false;
180-
} else {
181-
for (int i = 0; i < a.size(); i++) {
182-
if (!Arrays.equals(a.get(i), b.get(i))) {
183-
return false;
184-
}
180+
}
181+
for (int i = 0; i < a.length; i++) {
182+
if (!Arrays.equals(a[i], b[i])) {
183+
return false;
185184
}
186-
return true;
187185
}
186+
return true;
188187
}
189188

190189
@Override
191190
public int hashCode() {
192-
int hash = Objects.hash(rootRef, conditions, results, parameters);
193-
for (int[] node : nodes) {
194-
hash = 31 * hash + Arrays.hashCode(node);
191+
int hash = 31 * rootRef + nodes.length;
192+
// Sample up to 16 nodes distributed across the BDD
193+
int step = Math.max(1, nodes.length / 16);
194+
for (int i = 0; i < nodes.length; i += step) {
195+
int[] node = nodes[i];
196+
hash = 31 * hash + node[0];
197+
hash = 31 * hash + node[1];
198+
hash = 31 * hash + node[2];
195199
}
196200
return hash;
197201
}
@@ -208,18 +212,27 @@ public String toString() {
208212
* @return the given string builder.
209213
*/
210214
public StringBuilder toString(StringBuilder sb) {
215+
// Calculate max width needed for first column identifiers
216+
int maxConditionIdx = conditions.size() - 1;
217+
int maxResultIdx = results.size() - 1;
218+
219+
// Width needed for "C" + maxConditionIdx or "R" + maxResultIdx
220+
int conditionWidth = maxConditionIdx >= 0 ? String.valueOf(maxConditionIdx).length() + 1 : 2;
221+
int resultWidth = maxResultIdx >= 0 ? String.valueOf(maxResultIdx).length() + 1 : 2;
222+
int varWidth = Math.max(conditionWidth, resultWidth);
223+
211224
sb.append("Bdd{\n");
212225

213226
// Conditions
214227
sb.append(" conditions (").append(getConditionCount()).append("):\n");
215228
for (int i = 0; i < conditions.size(); i++) {
216-
sb.append(" C").append(i).append(": ").append(conditions.get(i)).append("\n");
229+
sb.append(String.format(" %"+varWidth+"s: %s%n", "C" + i, conditions.get(i)));
217230
}
218231

219232
// Results
220233
sb.append(" results (").append(results.size()).append("):\n");
221234
for (int i = 0; i < results.size(); i++) {
222-
sb.append(" R").append(i).append(": ");
235+
sb.append(String.format(" %"+varWidth+"s: ", "R" + i));
223236
appendResult(sb, results.get(i));
224237
sb.append("\n");
225238
}
@@ -228,24 +241,32 @@ public StringBuilder toString(StringBuilder sb) {
228241
sb.append(" root: ").append(formatReference(rootRef)).append("\n");
229242

230243
// Nodes
231-
sb.append(" nodes (").append(nodes.size()).append("):\n");
232-
for (int i = 0; i < nodes.size(); i++) {
233-
sb.append(" ").append(i).append(": ");
244+
sb.append(" nodes (").append(nodes.length).append("):\n");
245+
246+
// Calculate width needed for node indices
247+
int indexWidth = String.valueOf(nodes.length - 1).length();
248+
249+
for (int i = 0; i < nodes.length; i++) {
250+
sb.append(String.format(" %"+indexWidth+"d: ", i));
234251
if (i == 0) {
235252
sb.append("terminal");
236253
} else {
237-
int[] node = nodes.get(i);
254+
int[] node = nodes[i];
238255
int varIdx = node[0];
239256
sb.append("[");
257+
258+
// Use the calculated width for variable/result references
240259
if (varIdx < conditions.size()) {
241-
sb.append("C").append(varIdx);
260+
sb.append(String.format("%"+varWidth+"s", "C" + varIdx));
242261
} else {
243-
sb.append("R").append(varIdx - conditions.size());
262+
sb.append(String.format("%"+varWidth+"s", "R" + (varIdx - conditions.size())));
244263
}
264+
265+
// Format the references with consistent spacing
245266
sb.append(", ")
246-
.append(formatReference(node[1]))
267+
.append(String.format("%6s", formatReference(node[1])))
247268
.append(", ")
248-
.append(formatReference(node[2]))
269+
.append(String.format("%6s", formatReference(node[2])))
249270
.append("]");
250271
}
251272
sb.append("\n");

smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/logic/bdd/BddBuilder.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -550,6 +550,15 @@ public List<int[]> getNodes() {
550550
return copy;
551551
}
552552

553+
/**
554+
* Get the array of nodes.
555+
*
556+
* @return array of nodes.
557+
*/
558+
public int[][] getNodesArray() {
559+
return nodes.toArray(new int[0][]);
560+
}
561+
553562
private void validateBooleanOperands(int f, int g, String operation) {
554563
if (isResult(f) || isResult(g)) {
555564
throw new IllegalArgumentException("Cannot perform " + operation + " on result terminals");

smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/logic/bdd/BddCompiler.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ Bdd compile() {
6464
int rootRef = convertCfgToBdd(cfg.getRoot());
6565
rootRef = bddBuilder.reduce(rootRef);
6666
Parameters parameters = cfg.getRuleSet().getParameters();
67-
Bdd bdd = new Bdd(parameters, orderedConditions, indexedResults, bddBuilder.getNodes(), rootRef);
67+
Bdd bdd = new Bdd(parameters, orderedConditions, indexedResults, bddBuilder.getNodesArray(), rootRef);
6868

6969
long elapsed = System.currentTimeMillis() - start;
7070
LOGGER.fine(String.format(

smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/logic/bdd/BddEquivalenceChecker.java

Lines changed: 33 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
15
package software.amazon.smithy.rulesengine.logic.bdd;
26

37
import java.time.Duration;
@@ -21,8 +25,9 @@
2125
/**
2226
* Verifies functional equivalence between a CFG and its BDD representation.
2327
*
24-
* <p>This verifier uses structural equivalence checking to ensure that for every possible
25-
* combination of condition evaluations, both representations produce the same result.
28+
* <p>This verifier uses structural equivalence checking to ensure that both representations produce the same result.
29+
* When the BDD has fewer than 20 conditions, the checking is exhaustive. When there are more, random samples are
30+
* checked up the earlier of max samples being reached, or the max duration being reached.
2631
*/
2732
public final class BddEquivalenceChecker {
2833

@@ -60,7 +65,7 @@ private BddEquivalenceChecker(Cfg cfg, Bdd bdd) {
6065
/**
6166
* Sets the maximum number of samples to test for large condition sets.
6267
*
63-
* <p>Defaults to a max of 1M samples. Set to <= 0 to disable the max.
68+
* <p>Defaults to a max of 1M samples. Set to {@code <= 0} to disable the max.
6469
*
6570
* @param maxSamples the maximum number of samples
6671
* @return this verifier for method chaining
@@ -97,7 +102,9 @@ public void verify() {
97102
testsRun = 0;
98103

99104
LOGGER.info(() -> String.format("Verifying BDD with %d conditions (max samples: %d, timeout: %s)",
100-
bdd.getConditionCount(), maxSamples, timeout));
105+
bdd.getConditionCount(),
106+
maxSamples,
107+
timeout));
101108

102109
if (bdd.getConditionCount() <= EXHAUSTIVE_THRESHOLD) {
103110
verifyExhaustive();
@@ -129,11 +136,13 @@ private void verifyResults() {
129136
Set<Rule> inBddOnly = new HashSet<>(bddResults);
130137
inBddOnly.removeAll(cfgResults);
131138
throw new IllegalStateException(String.format(
132-
"Result mismatch: CFG has %d results, BDD has %d results (excluding NoMatchRule).\n" +
133-
"In CFG only: %s\n" +
134-
"In BDD only: %s",
135-
cfgResults.size(), bddResults.size(), inCfgOnly, inBddOnly
136-
));
139+
"Result mismatch: CFG has %d results, BDD has %d results (excluding NoMatchRule).%n" +
140+
"In CFG only: %s%n" +
141+
"In BDD only: %s",
142+
cfgResults.size(),
143+
bddResults.size(),
144+
inCfgOnly,
145+
inBddOnly));
137146
}
138147
}
139148

@@ -147,7 +156,7 @@ private void verifyExhaustive() {
147156
verifyCase(mask);
148157
if (hasEitherLimitBeenExceeded()) {
149158
LOGGER.info(String.format("Exhaustive verification stopped after %d tests "
150-
+ "(limit: %d samples or %s timeout)", testsRun, maxSamples, timeout));
159+
+ "(limit: %d samples or %s timeout)", testsRun, maxSamples, timeout));
151160
break;
152161
}
153162
}
@@ -159,7 +168,8 @@ private void verifyExhaustive() {
159168
*/
160169
private void verifyWithLimits() {
161170
LOGGER.info(() -> String.format("Running limited verification (will stop at %d samples OR %s timeout)",
162-
maxSamples, timeout));
171+
maxSamples,
172+
timeout));
163173
verifyCriticalCases();
164174

165175
while (!hasEitherLimitBeenExceeded()) {
@@ -235,8 +245,13 @@ private void verifyCase(long mask) {
235245
for (int i = 0; i < bdd.getConditions().size(); i++) {
236246
Condition condition = bdd.getConditions().get(i);
237247
boolean value = (mask & (1L << i)) != 0;
238-
errorMsg.append(" Condition ").append(i).append(" [").append(value).append("]: ")
239-
.append(condition).append("\n");
248+
errorMsg.append(" Condition ")
249+
.append(i)
250+
.append(" [")
251+
.append(value)
252+
.append("]: ")
253+
.append(condition)
254+
.append("\n");
240255
}
241256
errorMsg.append("\nResults:\n");
242257
errorMsg.append(" CFG result: ").append(describeResult(cfgResult)).append("\n");
@@ -255,8 +270,11 @@ private Rule evaluateCfgWithMask(ConditionEvaluator maskEvaluator) {
255270
}
256271

257272
// Recursively evaluates a CFG node.
258-
private CfgNode evaluateCfgNode(CfgNode node, Map<Condition, Integer> conditionToIndex,
259-
ConditionEvaluator maskEvaluator) {
273+
private CfgNode evaluateCfgNode(
274+
CfgNode node,
275+
Map<Condition, Integer> conditionToIndex,
276+
ConditionEvaluator maskEvaluator
277+
) {
260278
if (node instanceof ResultNode) {
261279
return node;
262280
}

smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/logic/bdd/BddEvaluator.java

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,20 +7,40 @@
77
import software.amazon.smithy.rulesengine.logic.ConditionEvaluator;
88

99
/**
10-
* Simple BDD evaluator.
10+
* Simple BDD evaluator that works directly with BDD nodes.
1111
*/
1212
public final class BddEvaluator {
1313

14-
private final Bdd bdd;
14+
private final int[][] nodes;
15+
private final int rootRef;
1516
private final int conditionCount;
1617

17-
private BddEvaluator(Bdd bdd) {
18-
this.bdd = bdd;
19-
this.conditionCount = bdd.getConditionCount();
18+
private BddEvaluator(int[][] nodes, int rootRef, int conditionCount) {
19+
this.nodes = nodes;
20+
this.rootRef = rootRef;
21+
this.conditionCount = conditionCount;
2022
}
2123

24+
/**
25+
* Create evaluator from a Bdd object.
26+
*
27+
* @param bdd the BDD
28+
* @return the evaluator
29+
*/
2230
public static BddEvaluator from(Bdd bdd) {
23-
return new BddEvaluator(bdd);
31+
return from(bdd.getNodes(), bdd.getRootRef(), bdd.getConditionCount());
32+
}
33+
34+
/**
35+
* Create evaluator from BDD data.
36+
*
37+
* @param nodes BDD nodes array
38+
* @param rootRef root reference
39+
* @param conditionCount number of conditions
40+
* @return the evaluator
41+
*/
42+
public static BddEvaluator from(int[][] nodes, int rootRef, int conditionCount) {
43+
return new BddEvaluator(nodes, rootRef, conditionCount);
2444
}
2545

2646
/**
@@ -30,7 +50,7 @@ public static BddEvaluator from(Bdd bdd) {
3050
* @return the result index, or -1 for no match
3151
*/
3252
public int evaluate(ConditionEvaluator evaluator) {
33-
int ref = bdd.getRootRef();
53+
int ref = rootRef;
3454

3555
while (true) {
3656
// Handle terminals
@@ -48,7 +68,7 @@ public int evaluate(ConditionEvaluator evaluator) {
4868
boolean complemented = ref < 0;
4969
int nodeIdx = (complemented ? -ref : ref) - 1;
5070

51-
int[] node = bdd.getNodes().get(nodeIdx);
71+
int[] node = nodes[nodeIdx];
5272
int varIdx = node[0];
5373

5474
// Evaluate condition and follow appropriate branch

0 commit comments

Comments
 (0)