simple-graph-query 2.1.4 → 2.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -17,13 +17,17 @@ export declare class ForgeExprEvaluator extends AbstractParseTreeVisitor<EvalRes
|
|
|
17
17
|
private freeVariables;
|
|
18
18
|
private cachedResults;
|
|
19
19
|
private instanceData;
|
|
20
|
+
private relationCache;
|
|
21
|
+
private relationIndexCache;
|
|
20
22
|
constructor(datum: IDataInstance);
|
|
23
|
+
private buildRelationCache;
|
|
21
24
|
private updateFreeVariables;
|
|
22
25
|
private constructFreeVariableKey;
|
|
23
26
|
private getLabelForValue;
|
|
24
27
|
private getLabelAsString;
|
|
25
28
|
private getLabelAsBoolean;
|
|
26
29
|
private getLabelAsNumber;
|
|
30
|
+
private dotJoin;
|
|
27
31
|
private cacheResult;
|
|
28
32
|
private getIden;
|
|
29
33
|
protected aggregateResult(aggregate: EvalResult, nextResult: EvalResult): EvalResult;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { ExprContext } from "./forge-antlr/ForgeParser";
|
|
2
|
+
import { Tuple } from "./ForgeExprEvaluator";
|
|
3
|
+
/**
|
|
4
|
+
* Analyzes set comprehension constraints to detect simple numeric comparison patterns
|
|
5
|
+
* that can be optimized by generating only valid combinations instead of filtering.
|
|
6
|
+
*/
|
|
7
|
+
export interface NumericConstraintPattern {
|
|
8
|
+
type: 'less_than' | 'greater_than' | 'less_equal' | 'greater_equal' | 'not_equal' | 'none';
|
|
9
|
+
leftVar: string;
|
|
10
|
+
rightVar: string;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Detects if a constraint expression is a simple numeric comparison between two variables.
|
|
14
|
+
* Currently detects patterns like: a < b, a > b, a <= b, a >= b, a != b
|
|
15
|
+
*
|
|
16
|
+
* Note: Uses string matching on expression text which is simple but may miss expressions
|
|
17
|
+
* with extra whitespace or parentheses. This is intentionally conservative - we prefer
|
|
18
|
+
* to fall back to the standard approach rather than risk incorrect optimization.
|
|
19
|
+
*
|
|
20
|
+
* @param constraintExpr The constraint expression to analyze
|
|
21
|
+
* @param varNames The variable names in scope
|
|
22
|
+
* @returns Pattern info if detected, null otherwise
|
|
23
|
+
*/
|
|
24
|
+
export declare function detectNumericComparisonPattern(constraintExpr: ExprContext, varNames: string[]): NumericConstraintPattern | null;
|
|
25
|
+
/**
|
|
26
|
+
* Checks if all values in the sets are numbers (or tuples containing single numbers).
|
|
27
|
+
* This is required for numeric comparison optimization to be applicable.
|
|
28
|
+
*/
|
|
29
|
+
export declare function areAllNumericSets(sets: Tuple[][]): boolean;
|
|
30
|
+
/**
|
|
31
|
+
* Generates optimized combinations for numeric comparison constraints.
|
|
32
|
+
* Instead of generating all combinations and filtering, generates only valid ones.
|
|
33
|
+
*
|
|
34
|
+
* @param varNames Variable names in order
|
|
35
|
+
* @param quantifiedSets The sets each variable ranges over
|
|
36
|
+
* @param pattern The detected numeric comparison pattern
|
|
37
|
+
* @returns Only the tuples that satisfy the constraint
|
|
38
|
+
*/
|
|
39
|
+
export declare function generateOptimizedNumericCombinations(varNames: string[], quantifiedSets: Tuple[][], pattern: NumericConstraintPattern): Tuple[];
|
|
@@ -48441,6 +48441,7 @@ exports.areTupleArraysEqual = areTupleArraysEqual;
|
|
|
48441
48441
|
const AbstractParseTreeVisitor_1 = __webpack_require__(/*! antlr4ts/tree/AbstractParseTreeVisitor */ "./node_modules/antlr4ts/tree/AbstractParseTreeVisitor.js");
|
|
48442
48442
|
const lodash_1 = __webpack_require__(/*! lodash */ "./node_modules/lodash/lodash.js");
|
|
48443
48443
|
const ForgeExprFreeVariableFinder_1 = __webpack_require__(/*! ./ForgeExprFreeVariableFinder */ "./src/ForgeExprFreeVariableFinder.ts");
|
|
48444
|
+
const NumericConstraintOptimizer_1 = __webpack_require__(/*! ./NumericConstraintOptimizer */ "./src/NumericConstraintOptimizer.ts");
|
|
48444
48445
|
///// HELPER FUNCTIONS /////
|
|
48445
48446
|
function isSingleValue(value) {
|
|
48446
48447
|
return (typeof value === "string" ||
|
|
@@ -48570,38 +48571,6 @@ function transitiveClosure(pairs) {
|
|
|
48570
48571
|
// convert the result back to a Tuple[] and return
|
|
48571
48572
|
return Array.from(transitiveClosureSet).map((pair) => JSON.parse(pair));
|
|
48572
48573
|
}
|
|
48573
|
-
function dotJoin(left, right) {
|
|
48574
|
-
const leftExpr = isSingleValue(left) ? [[left]] : left;
|
|
48575
|
-
const rightExpr = isSingleValue(right) ? [[right]] : right;
|
|
48576
|
-
// Optimize join using a Map for O(n+m) instead of O(n*m) lookup
|
|
48577
|
-
// Group right tuples by their first element for fast lookup
|
|
48578
|
-
const rightIndex = new Map();
|
|
48579
|
-
for (const rightTuple of rightExpr) {
|
|
48580
|
-
const key = rightTuple[0];
|
|
48581
|
-
if (!rightIndex.has(key)) {
|
|
48582
|
-
rightIndex.set(key, []);
|
|
48583
|
-
}
|
|
48584
|
-
rightIndex.get(key).push(rightTuple);
|
|
48585
|
-
}
|
|
48586
|
-
const result = [];
|
|
48587
|
-
for (const leftTuple of leftExpr) {
|
|
48588
|
-
const joinKey = leftTuple[leftTuple.length - 1];
|
|
48589
|
-
const matchingRightTuples = rightIndex.get(joinKey);
|
|
48590
|
-
if (matchingRightTuples) {
|
|
48591
|
-
for (const rightTuple of matchingRightTuples) {
|
|
48592
|
-
result.push([
|
|
48593
|
-
...leftTuple.slice(0, leftTuple.length - 1),
|
|
48594
|
-
...rightTuple.slice(1),
|
|
48595
|
-
]);
|
|
48596
|
-
}
|
|
48597
|
-
}
|
|
48598
|
-
}
|
|
48599
|
-
if (result.some(tuple => tuple.length === 0)) {
|
|
48600
|
-
throw new Error("Join would create a relation of arity 0");
|
|
48601
|
-
}
|
|
48602
|
-
// Deduplicate results to ensure set semantics
|
|
48603
|
-
return deduplicateTuples(result);
|
|
48604
|
-
}
|
|
48605
48574
|
function bitwidthWraparound(value, bitwidth) {
|
|
48606
48575
|
const modulus = Math.pow(2, bitwidth); // total number of Int values
|
|
48607
48576
|
const halfValue = Math.pow(2, bitwidth - 1); // halfway point
|
|
@@ -48632,11 +48601,61 @@ class ForgeExprEvaluator extends AbstractParseTreeVisitor_1.AbstractParseTreeVis
|
|
|
48632
48601
|
// NOTE: strings will be of the format "<var-name>=<value>|..." sorted in
|
|
48633
48602
|
// increasing lexicographic order of variable names
|
|
48634
48603
|
this.cachedResults = new Map();
|
|
48604
|
+
// Cache for relation lookups to avoid O(n) scans
|
|
48605
|
+
this.relationCache = null;
|
|
48606
|
+
// Index for relations: Map<relationName, Map<firstElement, Tuple[]>>
|
|
48607
|
+
// This allows O(1) lookup for patterns like atom.field
|
|
48608
|
+
this.relationIndexCache = null;
|
|
48635
48609
|
this.instanceData = datum;
|
|
48636
48610
|
this.environmentStack = [];
|
|
48637
48611
|
this.freeVariableFinder = new ForgeExprFreeVariableFinder_1.ForgeExprFreeVariableFinder(datum);
|
|
48638
48612
|
this.freeVariables = new Map();
|
|
48639
48613
|
}
|
|
48614
|
+
// helper function to build relation cache and indexes
|
|
48615
|
+
buildRelationCache() {
|
|
48616
|
+
if (this.relationCache !== null) {
|
|
48617
|
+
return; // already built
|
|
48618
|
+
}
|
|
48619
|
+
this.relationCache = new Map();
|
|
48620
|
+
this.relationIndexCache = new Map();
|
|
48621
|
+
const relations = this.instanceData.getRelations();
|
|
48622
|
+
const isConvertibleToNumber = (value) => {
|
|
48623
|
+
return typeof value === "string" && !isNaN(Number(value));
|
|
48624
|
+
};
|
|
48625
|
+
const isConvertibleToBoolean = (value) => {
|
|
48626
|
+
if (typeof value === "boolean")
|
|
48627
|
+
return false; // already boolean
|
|
48628
|
+
return value === "true" || value === "#t" || value === "false" || value === "#f";
|
|
48629
|
+
};
|
|
48630
|
+
const convertToBoolean = (value) => {
|
|
48631
|
+
if (typeof value === "boolean")
|
|
48632
|
+
return value;
|
|
48633
|
+
if (value === "true" || value === "#t")
|
|
48634
|
+
return true;
|
|
48635
|
+
if (value === "false" || value === "#f")
|
|
48636
|
+
return false;
|
|
48637
|
+
throw new Error(`Cannot convert ${value} to boolean`);
|
|
48638
|
+
};
|
|
48639
|
+
for (const relation of relations) {
|
|
48640
|
+
let relationAtoms = relation.tuples.map((tuple) => tuple.atoms);
|
|
48641
|
+
// Convert numeric and boolean strings to their actual types
|
|
48642
|
+
relationAtoms = relationAtoms.map((tuple) => tuple.map((value) => isConvertibleToNumber(value) ? Number(value) : value));
|
|
48643
|
+
relationAtoms = relationAtoms.map((tuple) => tuple.map((value) => isConvertibleToBoolean(value) ? convertToBoolean(value) : value));
|
|
48644
|
+
this.relationCache.set(relation.name, relationAtoms);
|
|
48645
|
+
// Build index for this relation: first element -> all tuples starting with that element
|
|
48646
|
+
const relationIndex = new Map();
|
|
48647
|
+
for (const tuple of relationAtoms) {
|
|
48648
|
+
if (tuple.length > 0) {
|
|
48649
|
+
const key = tuple[0];
|
|
48650
|
+
if (!relationIndex.has(key)) {
|
|
48651
|
+
relationIndex.set(key, []);
|
|
48652
|
+
}
|
|
48653
|
+
relationIndex.get(key).push(tuple);
|
|
48654
|
+
}
|
|
48655
|
+
}
|
|
48656
|
+
this.relationIndexCache.set(relation.name, relationIndex);
|
|
48657
|
+
}
|
|
48658
|
+
}
|
|
48640
48659
|
//helper function
|
|
48641
48660
|
updateFreeVariables(freeVars) {
|
|
48642
48661
|
if (this.freeVariables.size === 0) {
|
|
@@ -48732,6 +48751,45 @@ class ForgeExprEvaluator extends AbstractParseTreeVisitor_1.AbstractParseTreeVis
|
|
|
48732
48751
|
}
|
|
48733
48752
|
return labelNum;
|
|
48734
48753
|
}
|
|
48754
|
+
// Optimized dotJoin that can use pre-built relation indexes
|
|
48755
|
+
dotJoin(left, right, rightRelationName) {
|
|
48756
|
+
const leftExpr = isSingleValue(left) ? [[left]] : left;
|
|
48757
|
+
const rightExpr = isSingleValue(right) ? [[right]] : right;
|
|
48758
|
+
// Try to use pre-built index if available
|
|
48759
|
+
let rightIndex;
|
|
48760
|
+
if (rightRelationName && this.relationIndexCache) {
|
|
48761
|
+
rightIndex = this.relationIndexCache.get(rightRelationName);
|
|
48762
|
+
}
|
|
48763
|
+
// If no pre-built index, build one on the fly
|
|
48764
|
+
if (!rightIndex) {
|
|
48765
|
+
rightIndex = new Map();
|
|
48766
|
+
for (const rightTuple of rightExpr) {
|
|
48767
|
+
const key = rightTuple[0];
|
|
48768
|
+
if (!rightIndex.has(key)) {
|
|
48769
|
+
rightIndex.set(key, []);
|
|
48770
|
+
}
|
|
48771
|
+
rightIndex.get(key).push(rightTuple);
|
|
48772
|
+
}
|
|
48773
|
+
}
|
|
48774
|
+
const result = [];
|
|
48775
|
+
for (const leftTuple of leftExpr) {
|
|
48776
|
+
const joinKey = leftTuple[leftTuple.length - 1];
|
|
48777
|
+
const matchingRightTuples = rightIndex.get(joinKey);
|
|
48778
|
+
if (matchingRightTuples) {
|
|
48779
|
+
for (const rightTuple of matchingRightTuples) {
|
|
48780
|
+
result.push([
|
|
48781
|
+
...leftTuple.slice(0, leftTuple.length - 1),
|
|
48782
|
+
...rightTuple.slice(1),
|
|
48783
|
+
]);
|
|
48784
|
+
}
|
|
48785
|
+
}
|
|
48786
|
+
}
|
|
48787
|
+
if (result.some(tuple => tuple.length === 0)) {
|
|
48788
|
+
throw new Error("Join would create a relation of arity 0");
|
|
48789
|
+
}
|
|
48790
|
+
// Deduplicate results to ensure set semantics
|
|
48791
|
+
return deduplicateTuples(result);
|
|
48792
|
+
}
|
|
48735
48793
|
// helper function
|
|
48736
48794
|
cacheResult(ctx, freeVarsKey, result) {
|
|
48737
48795
|
if (!this.cachedResults.has(ctx)) {
|
|
@@ -48862,9 +48920,6 @@ class ForgeExprEvaluator extends AbstractParseTreeVisitor_1.AbstractParseTreeVis
|
|
|
48862
48920
|
if (foundAllVars && this.cachedResults.has(ctx)) {
|
|
48863
48921
|
if (this.cachedResults.get(ctx).has(freeVarsKey)) {
|
|
48864
48922
|
// cache hit!
|
|
48865
|
-
// console.log('cache hit for ctx:', ctx.text);
|
|
48866
|
-
// console.log('freeVarsKey:', freeVarsKey);
|
|
48867
|
-
// console.log('cachedResults:', this.cachedResults.get(ctx));
|
|
48868
48923
|
return this.cachedResults.get(ctx).get(freeVarsKey);
|
|
48869
48924
|
}
|
|
48870
48925
|
}
|
|
@@ -48905,7 +48960,26 @@ class ForgeExprEvaluator extends AbstractParseTreeVisitor_1.AbstractParseTreeVis
|
|
|
48905
48960
|
varNames.push(varName);
|
|
48906
48961
|
quantifiedSets.push(varQuantifiedSets[varName]);
|
|
48907
48962
|
}
|
|
48908
|
-
|
|
48963
|
+
// Try to optimize numeric comparisons
|
|
48964
|
+
let product;
|
|
48965
|
+
let useOptimizedPath = false;
|
|
48966
|
+
if (!isDisjoint && varNames.length >= 2 && (0, NumericConstraintOptimizer_1.areAllNumericSets)(quantifiedSets)) {
|
|
48967
|
+
// Try to detect and optimize numeric comparison patterns
|
|
48968
|
+
const pattern = (0, NumericConstraintOptimizer_1.detectNumericComparisonPattern)(barExpr, varNames);
|
|
48969
|
+
if (pattern && pattern.type !== 'none') {
|
|
48970
|
+
// Use optimized combination generation
|
|
48971
|
+
product = (0, NumericConstraintOptimizer_1.generateOptimizedNumericCombinations)(varNames, quantifiedSets, pattern);
|
|
48972
|
+
useOptimizedPath = true;
|
|
48973
|
+
}
|
|
48974
|
+
else {
|
|
48975
|
+
// Fall back to standard cartesian product
|
|
48976
|
+
product = getCombinations(quantifiedSets);
|
|
48977
|
+
}
|
|
48978
|
+
}
|
|
48979
|
+
else {
|
|
48980
|
+
// Fall back to standard cartesian product
|
|
48981
|
+
product = getCombinations(quantifiedSets);
|
|
48982
|
+
}
|
|
48909
48983
|
const result = [];
|
|
48910
48984
|
let foundTrue = false;
|
|
48911
48985
|
let foundFalse = false;
|
|
@@ -48936,10 +49010,19 @@ class ForgeExprEvaluator extends AbstractParseTreeVisitor_1.AbstractParseTreeVis
|
|
|
48936
49010
|
for (let j = 0; j < varNames.length; j++) {
|
|
48937
49011
|
quantDeclEnv.env[varNames[j]] = tuple[j];
|
|
48938
49012
|
}
|
|
48939
|
-
//
|
|
48940
|
-
|
|
48941
|
-
|
|
48942
|
-
|
|
49013
|
+
// If we used the optimized path, we can skip constraint evaluation
|
|
49014
|
+
// since the combinations already satisfy the constraint
|
|
49015
|
+
let barExprValue;
|
|
49016
|
+
if (useOptimizedPath) {
|
|
49017
|
+
barExprValue = true;
|
|
49018
|
+
}
|
|
49019
|
+
else {
|
|
49020
|
+
// now, we want to evaluate the barExpr
|
|
49021
|
+
const evalResult = this.visit(barExpr);
|
|
49022
|
+
if (!isBoolean(evalResult)) {
|
|
49023
|
+
throw new Error("Expected the expression after the bar to be a boolean!");
|
|
49024
|
+
}
|
|
49025
|
+
barExprValue = evalResult;
|
|
48943
49026
|
}
|
|
48944
49027
|
if (barExprValue) {
|
|
48945
49028
|
result.push(tuple);
|
|
@@ -49634,7 +49717,7 @@ class ForgeExprEvaluator extends AbstractParseTreeVisitor_1.AbstractParseTreeVis
|
|
|
49634
49717
|
}
|
|
49635
49718
|
}
|
|
49636
49719
|
// Box join: <expr-a>[<expr-b>] == <expr-b> . <expr-a>
|
|
49637
|
-
return dotJoin(insideBracesExprs, beforeBracesExpr);
|
|
49720
|
+
return this.dotJoin(insideBracesExprs, beforeBracesExpr);
|
|
49638
49721
|
}
|
|
49639
49722
|
return this.visitChildren(ctx);
|
|
49640
49723
|
}
|
|
@@ -49647,9 +49730,20 @@ class ForgeExprEvaluator extends AbstractParseTreeVisitor_1.AbstractParseTreeVis
|
|
|
49647
49730
|
}
|
|
49648
49731
|
const beforeDotExpr = this.visit(ctx.expr15());
|
|
49649
49732
|
const afterDotExpr = this.visit(ctx.expr16());
|
|
49650
|
-
//
|
|
49651
|
-
|
|
49652
|
-
|
|
49733
|
+
// Try to extract the relation name if the right side is a simple identifier/relation name
|
|
49734
|
+
let rightRelationName;
|
|
49735
|
+
// Simple heuristic: check if it's a tuple array (likely a relation)
|
|
49736
|
+
// and if so, try to find which relation it matches
|
|
49737
|
+
if (isTupleArray(afterDotExpr) && this.relationCache) {
|
|
49738
|
+
// Check if this matches any cached relation
|
|
49739
|
+
for (const [relName, relTuples] of this.relationCache.entries()) {
|
|
49740
|
+
if (relTuples === afterDotExpr) {
|
|
49741
|
+
rightRelationName = relName;
|
|
49742
|
+
break;
|
|
49743
|
+
}
|
|
49744
|
+
}
|
|
49745
|
+
}
|
|
49746
|
+
return this.dotJoin(beforeDotExpr, afterDotExpr, rightRelationName);
|
|
49653
49747
|
}
|
|
49654
49748
|
if (ctx.LEFT_SQUARE_TOK()) {
|
|
49655
49749
|
const beforeBracesName = this.visit(ctx.name());
|
|
@@ -49908,7 +50002,28 @@ class ForgeExprEvaluator extends AbstractParseTreeVisitor_1.AbstractParseTreeVis
|
|
|
49908
50002
|
varNames.push(varName);
|
|
49909
50003
|
quantifiedSets.push(varQuantifiedSets[varName]);
|
|
49910
50004
|
}
|
|
49911
|
-
|
|
50005
|
+
// Try to optimize numeric comparisons (same optimization as in quantifiers)
|
|
50006
|
+
// Note: Set comprehensions don't support the 'disj' keyword, so we don't check for it here.
|
|
50007
|
+
// Quantifiers do support 'disj', so they check !isDisjoint before optimizing.
|
|
50008
|
+
let product;
|
|
50009
|
+
let useOptimizedPath = false;
|
|
50010
|
+
if (varNames.length >= 2 && (0, NumericConstraintOptimizer_1.areAllNumericSets)(quantifiedSets)) {
|
|
50011
|
+
// Try to detect and optimize numeric comparison patterns
|
|
50012
|
+
const pattern = (0, NumericConstraintOptimizer_1.detectNumericComparisonPattern)(barExpr, varNames);
|
|
50013
|
+
if (pattern && pattern.type !== 'none') {
|
|
50014
|
+
// Use optimized combination generation
|
|
50015
|
+
product = (0, NumericConstraintOptimizer_1.generateOptimizedNumericCombinations)(varNames, quantifiedSets, pattern);
|
|
50016
|
+
useOptimizedPath = true;
|
|
50017
|
+
}
|
|
50018
|
+
else {
|
|
50019
|
+
// Fall back to standard cartesian product
|
|
50020
|
+
product = getCombinations(quantifiedSets);
|
|
50021
|
+
}
|
|
50022
|
+
}
|
|
50023
|
+
else {
|
|
50024
|
+
// Fall back to standard cartesian product
|
|
50025
|
+
product = getCombinations(quantifiedSets);
|
|
50026
|
+
}
|
|
49912
50027
|
const result = [];
|
|
49913
50028
|
// Optimize: create environment once and reuse it
|
|
49914
50029
|
const quantDeclEnv = {
|
|
@@ -49922,10 +50037,19 @@ class ForgeExprEvaluator extends AbstractParseTreeVisitor_1.AbstractParseTreeVis
|
|
|
49922
50037
|
for (let j = 0; j < varNames.length; j++) {
|
|
49923
50038
|
quantDeclEnv.env[varNames[j]] = tuple[j];
|
|
49924
50039
|
}
|
|
49925
|
-
//
|
|
49926
|
-
|
|
49927
|
-
|
|
49928
|
-
|
|
50040
|
+
// If we used the optimized path, we can skip constraint evaluation
|
|
50041
|
+
// since the combinations already satisfy the constraint
|
|
50042
|
+
let barExprValue;
|
|
50043
|
+
if (useOptimizedPath) {
|
|
50044
|
+
barExprValue = true;
|
|
50045
|
+
}
|
|
50046
|
+
else {
|
|
50047
|
+
// now, we want to evaluate the barExpr
|
|
50048
|
+
const evalResult = this.visit(barExpr);
|
|
50049
|
+
if (!isBoolean(evalResult)) {
|
|
50050
|
+
throw new Error("Expected the expression after the bar to be a boolean value!");
|
|
50051
|
+
}
|
|
50052
|
+
barExprValue = evalResult;
|
|
49929
50053
|
}
|
|
49930
50054
|
if (barExprValue) {
|
|
49931
50055
|
// will error if not boolean val, which we want
|
|
@@ -50092,15 +50216,10 @@ class ForgeExprEvaluator extends AbstractParseTreeVisitor_1.AbstractParseTreeVis
|
|
|
50092
50216
|
throw new Error(`Cannot convert ${value} to boolean`);
|
|
50093
50217
|
};
|
|
50094
50218
|
// end of 3 helper functions
|
|
50095
|
-
// check if it is a relation
|
|
50096
|
-
|
|
50097
|
-
|
|
50098
|
-
|
|
50099
|
-
let relationAtoms = relation.tuples.map((tuple) => tuple.atoms);
|
|
50100
|
-
relationAtoms = relationAtoms.map((tuple) => tuple.map((value) => isConvertibleToNumber(value) ? Number(value) : value));
|
|
50101
|
-
relationAtoms = relationAtoms.map((tuple) => tuple.map((value) => isConvertibleToBoolean(value) ? convertToBoolean(value) : value));
|
|
50102
|
-
return relationAtoms;
|
|
50103
|
-
}
|
|
50219
|
+
// check if it is a relation - use cache for faster lookups
|
|
50220
|
+
this.buildRelationCache();
|
|
50221
|
+
if (this.relationCache.has(identifier)) {
|
|
50222
|
+
return this.relationCache.get(identifier);
|
|
50104
50223
|
}
|
|
50105
50224
|
if (result !== undefined) {
|
|
50106
50225
|
result = result.map((tuple) => tuple.map((value) => isConvertibleToNumber(value) ? Number(value) : value));
|
|
@@ -50386,6 +50505,9 @@ class ForgeExprFreeVariableFinder extends AbstractParseTreeVisitor_1.AbstractPar
|
|
|
50386
50505
|
const quantDeclListVars = this.getQuantDeclListVarNames(ctx.quantDeclList());
|
|
50387
50506
|
// we need to get all the vars referenced here other than the vars
|
|
50388
50507
|
// bound by the quantifier (in quantDeclListVars)
|
|
50508
|
+
// First, get free variables from the quantDeclList itself
|
|
50509
|
+
// (e.g., in "some p2 : c.parent | ...", c is a free variable)
|
|
50510
|
+
const quantDeclListFreeVars = this.visit(ctx.quantDeclList());
|
|
50389
50511
|
const blockOrBar = ctx.blockOrBar();
|
|
50390
50512
|
if (blockOrBar === undefined) {
|
|
50391
50513
|
throw new Error("expected to quantify over something!");
|
|
@@ -50394,13 +50516,15 @@ class ForgeExprFreeVariableFinder extends AbstractParseTreeVisitor_1.AbstractPar
|
|
|
50394
50516
|
blockOrBar.expr() === undefined) {
|
|
50395
50517
|
throw new Error("Expected the quantifier to have a bar followed by an expr!");
|
|
50396
50518
|
}
|
|
50397
|
-
let
|
|
50519
|
+
let barExprFreeVars;
|
|
50398
50520
|
if (blockOrBar.block() !== undefined) {
|
|
50399
|
-
|
|
50521
|
+
barExprFreeVars = this.visit(blockOrBar.block());
|
|
50400
50522
|
}
|
|
50401
50523
|
else {
|
|
50402
|
-
|
|
50524
|
+
barExprFreeVars = this.visit(blockOrBar.expr());
|
|
50403
50525
|
}
|
|
50526
|
+
// Merge free variables from quantDeclList and barExpr
|
|
50527
|
+
const allFreeVars = this.aggregateResult(barExprFreeVars, quantDeclListFreeVars);
|
|
50404
50528
|
// the context node for the quantifier as a whole shouldn't have _all_ of these
|
|
50405
50529
|
// free vars; specifically, it should not include the vars that are being
|
|
50406
50530
|
// bound by the quantifier
|
|
@@ -50592,6 +50716,200 @@ class ForgeExprFreeVariableFinder extends AbstractParseTreeVisitor_1.AbstractPar
|
|
|
50592
50716
|
exports.ForgeExprFreeVariableFinder = ForgeExprFreeVariableFinder;
|
|
50593
50717
|
|
|
50594
50718
|
|
|
50719
|
+
/***/ }),
|
|
50720
|
+
|
|
50721
|
+
/***/ "./src/NumericConstraintOptimizer.ts":
|
|
50722
|
+
/*!*******************************************!*\
|
|
50723
|
+
!*** ./src/NumericConstraintOptimizer.ts ***!
|
|
50724
|
+
\*******************************************/
|
|
50725
|
+
/***/ ((__unused_webpack_module, exports) => {
|
|
50726
|
+
|
|
50727
|
+
"use strict";
|
|
50728
|
+
|
|
50729
|
+
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
50730
|
+
exports.detectNumericComparisonPattern = detectNumericComparisonPattern;
|
|
50731
|
+
exports.areAllNumericSets = areAllNumericSets;
|
|
50732
|
+
exports.generateOptimizedNumericCombinations = generateOptimizedNumericCombinations;
|
|
50733
|
+
/**
|
|
50734
|
+
* Detects if a constraint expression is a simple numeric comparison between two variables.
|
|
50735
|
+
* Currently detects patterns like: a < b, a > b, a <= b, a >= b, a != b
|
|
50736
|
+
*
|
|
50737
|
+
* Note: Uses string matching on expression text which is simple but may miss expressions
|
|
50738
|
+
* with extra whitespace or parentheses. This is intentionally conservative - we prefer
|
|
50739
|
+
* to fall back to the standard approach rather than risk incorrect optimization.
|
|
50740
|
+
*
|
|
50741
|
+
* @param constraintExpr The constraint expression to analyze
|
|
50742
|
+
* @param varNames The variable names in scope
|
|
50743
|
+
* @returns Pattern info if detected, null otherwise
|
|
50744
|
+
*/
|
|
50745
|
+
function detectNumericComparisonPattern(constraintExpr, varNames) {
|
|
50746
|
+
// Parse the expression text to look for simple comparison patterns
|
|
50747
|
+
const exprText = constraintExpr.text;
|
|
50748
|
+
// Check if this is a simple binary comparison between two variables
|
|
50749
|
+
// Pattern: <varName1><compareOp><varName2> (no whitespace in text representation)
|
|
50750
|
+
// Note: The AST text property concatenates tokens without whitespace
|
|
50751
|
+
// Try to extract comparison operator and operands
|
|
50752
|
+
for (const leftVar of varNames) {
|
|
50753
|
+
for (const rightVar of varNames) {
|
|
50754
|
+
if (leftVar === rightVar)
|
|
50755
|
+
continue;
|
|
50756
|
+
// Check for different comparison operators (whitespace already removed in text)
|
|
50757
|
+
if (exprText === `${leftVar}<${rightVar}`) {
|
|
50758
|
+
return { type: 'less_than', leftVar, rightVar };
|
|
50759
|
+
}
|
|
50760
|
+
if (exprText === `${leftVar}>${rightVar}`) {
|
|
50761
|
+
return { type: 'greater_than', leftVar, rightVar };
|
|
50762
|
+
}
|
|
50763
|
+
if (exprText === `${leftVar}<=${rightVar}`) {
|
|
50764
|
+
return { type: 'less_equal', leftVar, rightVar };
|
|
50765
|
+
}
|
|
50766
|
+
if (exprText === `${leftVar}>=${rightVar}`) {
|
|
50767
|
+
return { type: 'greater_equal', leftVar, rightVar };
|
|
50768
|
+
}
|
|
50769
|
+
if (exprText === `${leftVar}!=${rightVar}`) {
|
|
50770
|
+
return { type: 'not_equal', leftVar, rightVar };
|
|
50771
|
+
}
|
|
50772
|
+
// Also check for negated equality: not a = b
|
|
50773
|
+
if (exprText === `not${leftVar}=${rightVar}`) {
|
|
50774
|
+
return { type: 'not_equal', leftVar, rightVar };
|
|
50775
|
+
}
|
|
50776
|
+
}
|
|
50777
|
+
}
|
|
50778
|
+
return null;
|
|
50779
|
+
}
|
|
50780
|
+
/**
|
|
50781
|
+
* Checks if all values in the sets are numbers (or tuples containing single numbers).
|
|
50782
|
+
* This is required for numeric comparison optimization to be applicable.
|
|
50783
|
+
*/
|
|
50784
|
+
function areAllNumericSets(sets) {
|
|
50785
|
+
for (const set of sets) {
|
|
50786
|
+
for (const tuple of set) {
|
|
50787
|
+
if (tuple.length !== 1)
|
|
50788
|
+
return false;
|
|
50789
|
+
if (typeof tuple[0] !== 'number')
|
|
50790
|
+
return false;
|
|
50791
|
+
}
|
|
50792
|
+
}
|
|
50793
|
+
return true;
|
|
50794
|
+
}
|
|
50795
|
+
/**
|
|
50796
|
+
* Extracts numbers from tuples that contain single numbers.
|
|
50797
|
+
* Precondition: This should only be called after areAllNumericSets validation.
|
|
50798
|
+
*/
|
|
50799
|
+
function extractNumbers(tuples) {
|
|
50800
|
+
return tuples.map(t => t[0]);
|
|
50801
|
+
}
|
|
50802
|
+
/**
|
|
50803
|
+
* Generates optimized combinations for numeric comparison constraints.
|
|
50804
|
+
* Instead of generating all combinations and filtering, generates only valid ones.
|
|
50805
|
+
*
|
|
50806
|
+
* @param varNames Variable names in order
|
|
50807
|
+
* @param quantifiedSets The sets each variable ranges over
|
|
50808
|
+
* @param pattern The detected numeric comparison pattern
|
|
50809
|
+
* @returns Only the tuples that satisfy the constraint
|
|
50810
|
+
*/
|
|
50811
|
+
function generateOptimizedNumericCombinations(varNames, quantifiedSets, pattern) {
|
|
50812
|
+
// Map variable names to their indices and value sets
|
|
50813
|
+
const varIndexMap = new Map();
|
|
50814
|
+
varNames.forEach((name, idx) => {
|
|
50815
|
+
varIndexMap.set(name, idx);
|
|
50816
|
+
});
|
|
50817
|
+
const leftIdx = varIndexMap.get(pattern.leftVar);
|
|
50818
|
+
const rightIdx = varIndexMap.get(pattern.rightVar);
|
|
50819
|
+
if (leftIdx === undefined || rightIdx === undefined) {
|
|
50820
|
+
// Pattern variables don't match our variable set - this shouldn't happen
|
|
50821
|
+
// if detectNumericComparisonPattern is working correctly, but throw an error
|
|
50822
|
+
// to prevent incorrect results (caller should have validated pattern matches variables)
|
|
50823
|
+
throw new Error(`Internal error: Pattern variables ${pattern.leftVar}, ${pattern.rightVar} not found in variable list`);
|
|
50824
|
+
}
|
|
50825
|
+
// Extract numeric values
|
|
50826
|
+
const leftNumbers = extractNumbers(quantifiedSets[leftIdx]);
|
|
50827
|
+
const rightNumbers = extractNumbers(quantifiedSets[rightIdx]);
|
|
50828
|
+
// For other variables, we still need all combinations
|
|
50829
|
+
const otherVars = [];
|
|
50830
|
+
const otherSets = [];
|
|
50831
|
+
for (let i = 0; i < varNames.length; i++) {
|
|
50832
|
+
if (i !== leftIdx && i !== rightIdx) {
|
|
50833
|
+
otherVars.push(i);
|
|
50834
|
+
otherSets.push(extractNumbers(quantifiedSets[i]));
|
|
50835
|
+
}
|
|
50836
|
+
}
|
|
50837
|
+
const result = [];
|
|
50838
|
+
// Generate combinations based on the comparison type
|
|
50839
|
+
const generatePairs = (compareFunc) => {
|
|
50840
|
+
for (const leftVal of leftNumbers) {
|
|
50841
|
+
for (const rightVal of rightNumbers) {
|
|
50842
|
+
if (compareFunc(leftVal, rightVal)) {
|
|
50843
|
+
// This pair satisfies the constraint
|
|
50844
|
+
// Now combine with all other variables if any
|
|
50845
|
+
if (otherVars.length === 0) {
|
|
50846
|
+
// Simple case: only two variables
|
|
50847
|
+
const tuple = new Array(varNames.length);
|
|
50848
|
+
tuple[leftIdx] = leftVal;
|
|
50849
|
+
tuple[rightIdx] = rightVal;
|
|
50850
|
+
result.push(tuple);
|
|
50851
|
+
}
|
|
50852
|
+
else {
|
|
50853
|
+
// Need to generate cartesian product with other variables
|
|
50854
|
+
const otherCombos = cartesianProduct(otherSets);
|
|
50855
|
+
for (const otherCombo of otherCombos) {
|
|
50856
|
+
const tuple = new Array(varNames.length);
|
|
50857
|
+
tuple[leftIdx] = leftVal;
|
|
50858
|
+
tuple[rightIdx] = rightVal;
|
|
50859
|
+
for (let i = 0; i < otherVars.length; i++) {
|
|
50860
|
+
tuple[otherVars[i]] = otherCombo[i];
|
|
50861
|
+
}
|
|
50862
|
+
result.push(tuple);
|
|
50863
|
+
}
|
|
50864
|
+
}
|
|
50865
|
+
}
|
|
50866
|
+
}
|
|
50867
|
+
}
|
|
50868
|
+
};
|
|
50869
|
+
switch (pattern.type) {
|
|
50870
|
+
case 'less_than':
|
|
50871
|
+
generatePairs((a, b) => a < b);
|
|
50872
|
+
break;
|
|
50873
|
+
case 'greater_than':
|
|
50874
|
+
generatePairs((a, b) => a > b);
|
|
50875
|
+
break;
|
|
50876
|
+
case 'less_equal':
|
|
50877
|
+
generatePairs((a, b) => a <= b);
|
|
50878
|
+
break;
|
|
50879
|
+
case 'greater_equal':
|
|
50880
|
+
generatePairs((a, b) => a >= b);
|
|
50881
|
+
break;
|
|
50882
|
+
case 'not_equal':
|
|
50883
|
+
generatePairs((a, b) => a !== b);
|
|
50884
|
+
break;
|
|
50885
|
+
default:
|
|
50886
|
+
// Unknown pattern, return empty (will fall back to standard approach)
|
|
50887
|
+
return [];
|
|
50888
|
+
}
|
|
50889
|
+
return result;
|
|
50890
|
+
}
|
|
50891
|
+
/**
|
|
50892
|
+
* Helper to generate cartesian product of number arrays
|
|
50893
|
+
*/
|
|
50894
|
+
function cartesianProduct(arrays) {
|
|
50895
|
+
if (arrays.length === 0)
|
|
50896
|
+
return [[]];
|
|
50897
|
+
if (arrays.some(arr => arr.length === 0))
|
|
50898
|
+
return [];
|
|
50899
|
+
let result = [[]];
|
|
50900
|
+
for (const arr of arrays) {
|
|
50901
|
+
const newResult = [];
|
|
50902
|
+
for (const existing of result) {
|
|
50903
|
+
for (const value of arr) {
|
|
50904
|
+
newResult.push([...existing, value]);
|
|
50905
|
+
}
|
|
50906
|
+
}
|
|
50907
|
+
result = newResult;
|
|
50908
|
+
}
|
|
50909
|
+
return result;
|
|
50910
|
+
}
|
|
50911
|
+
|
|
50912
|
+
|
|
50595
50913
|
/***/ }),
|
|
50596
50914
|
|
|
50597
50915
|
/***/ "./src/errorListener.ts":
|
package/package.json
CHANGED