simple-graph-query 2.2.0 → 2.3.0
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.
|
@@ -27,6 +27,9 @@ export declare class ForgeExprEvaluator extends AbstractParseTreeVisitor<EvalRes
|
|
|
27
27
|
private getLabelAsString;
|
|
28
28
|
private getLabelAsBoolean;
|
|
29
29
|
private getLabelAsNumber;
|
|
30
|
+
private isConvertibleToNumber;
|
|
31
|
+
private isConvertibleToBoolean;
|
|
32
|
+
private convertToBoolean;
|
|
30
33
|
private dotJoin;
|
|
31
34
|
private cacheResult;
|
|
32
35
|
private getIden;
|
|
@@ -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" ||
|
|
@@ -48618,28 +48619,11 @@ class ForgeExprEvaluator extends AbstractParseTreeVisitor_1.AbstractParseTreeVis
|
|
|
48618
48619
|
this.relationCache = new Map();
|
|
48619
48620
|
this.relationIndexCache = new Map();
|
|
48620
48621
|
const relations = this.instanceData.getRelations();
|
|
48621
|
-
const isConvertibleToNumber = (value) => {
|
|
48622
|
-
return typeof value === "string" && !isNaN(Number(value));
|
|
48623
|
-
};
|
|
48624
|
-
const isConvertibleToBoolean = (value) => {
|
|
48625
|
-
if (typeof value === "boolean")
|
|
48626
|
-
return false; // already boolean
|
|
48627
|
-
return value === "true" || value === "#t" || value === "false" || value === "#f";
|
|
48628
|
-
};
|
|
48629
|
-
const convertToBoolean = (value) => {
|
|
48630
|
-
if (typeof value === "boolean")
|
|
48631
|
-
return value;
|
|
48632
|
-
if (value === "true" || value === "#t")
|
|
48633
|
-
return true;
|
|
48634
|
-
if (value === "false" || value === "#f")
|
|
48635
|
-
return false;
|
|
48636
|
-
throw new Error(`Cannot convert ${value} to boolean`);
|
|
48637
|
-
};
|
|
48638
48622
|
for (const relation of relations) {
|
|
48639
48623
|
let relationAtoms = relation.tuples.map((tuple) => tuple.atoms);
|
|
48640
48624
|
// Convert numeric and boolean strings to their actual types
|
|
48641
|
-
relationAtoms = relationAtoms.map((tuple) => tuple.map((value) => isConvertibleToNumber(value) ? Number(value) : value));
|
|
48642
|
-
relationAtoms = relationAtoms.map((tuple) => tuple.map((value) => isConvertibleToBoolean(value) ? convertToBoolean(value) : value));
|
|
48625
|
+
relationAtoms = relationAtoms.map((tuple) => tuple.map((value) => this.isConvertibleToNumber(value) ? Number(value) : value));
|
|
48626
|
+
relationAtoms = relationAtoms.map((tuple) => tuple.map((value) => this.isConvertibleToBoolean(value) ? this.convertToBoolean(value) : value));
|
|
48643
48627
|
this.relationCache.set(relation.name, relationAtoms);
|
|
48644
48628
|
// Build index for this relation: first element -> all tuples starting with that element
|
|
48645
48629
|
const relationIndex = new Map();
|
|
@@ -48750,6 +48734,39 @@ class ForgeExprEvaluator extends AbstractParseTreeVisitor_1.AbstractParseTreeVis
|
|
|
48750
48734
|
}
|
|
48751
48735
|
return labelNum;
|
|
48752
48736
|
}
|
|
48737
|
+
// Helper function to check if a value can be converted to a number
|
|
48738
|
+
isConvertibleToNumber(value) {
|
|
48739
|
+
if (typeof value === "number") {
|
|
48740
|
+
return true;
|
|
48741
|
+
}
|
|
48742
|
+
if (typeof value === "string") {
|
|
48743
|
+
return !isNaN(Number(value));
|
|
48744
|
+
}
|
|
48745
|
+
return false;
|
|
48746
|
+
}
|
|
48747
|
+
// Helper function to check if a value can be converted to a boolean
|
|
48748
|
+
isConvertibleToBoolean(value) {
|
|
48749
|
+
if (typeof value === "boolean") {
|
|
48750
|
+
return true;
|
|
48751
|
+
}
|
|
48752
|
+
if (typeof value === "string") {
|
|
48753
|
+
return (value === "true" || value === "#t" || value === "false" || value === "#f");
|
|
48754
|
+
}
|
|
48755
|
+
return false;
|
|
48756
|
+
}
|
|
48757
|
+
// Helper function to convert a value to boolean
|
|
48758
|
+
convertToBoolean(value) {
|
|
48759
|
+
if (typeof value === "boolean") {
|
|
48760
|
+
return value;
|
|
48761
|
+
}
|
|
48762
|
+
if (value === "true" || value === "#t") {
|
|
48763
|
+
return true;
|
|
48764
|
+
}
|
|
48765
|
+
if (value === "false" || value === "#f") {
|
|
48766
|
+
return false;
|
|
48767
|
+
}
|
|
48768
|
+
throw new Error(`Cannot convert ${value} to boolean`);
|
|
48769
|
+
}
|
|
48753
48770
|
// Optimized dotJoin that can use pre-built relation indexes
|
|
48754
48771
|
dotJoin(left, right, rightRelationName) {
|
|
48755
48772
|
const leftExpr = isSingleValue(left) ? [[left]] : left;
|
|
@@ -48959,7 +48976,26 @@ class ForgeExprEvaluator extends AbstractParseTreeVisitor_1.AbstractParseTreeVis
|
|
|
48959
48976
|
varNames.push(varName);
|
|
48960
48977
|
quantifiedSets.push(varQuantifiedSets[varName]);
|
|
48961
48978
|
}
|
|
48962
|
-
|
|
48979
|
+
// Try to optimize numeric comparisons
|
|
48980
|
+
let product;
|
|
48981
|
+
let useOptimizedPath = false;
|
|
48982
|
+
if (!isDisjoint && varNames.length >= 2 && (0, NumericConstraintOptimizer_1.areAllNumericSets)(quantifiedSets)) {
|
|
48983
|
+
// Try to detect and optimize numeric comparison patterns
|
|
48984
|
+
const pattern = (0, NumericConstraintOptimizer_1.detectNumericComparisonPattern)(barExpr, varNames);
|
|
48985
|
+
if (pattern && pattern.type !== 'none') {
|
|
48986
|
+
// Use optimized combination generation
|
|
48987
|
+
product = (0, NumericConstraintOptimizer_1.generateOptimizedNumericCombinations)(varNames, quantifiedSets, pattern);
|
|
48988
|
+
useOptimizedPath = true;
|
|
48989
|
+
}
|
|
48990
|
+
else {
|
|
48991
|
+
// Fall back to standard cartesian product
|
|
48992
|
+
product = getCombinations(quantifiedSets);
|
|
48993
|
+
}
|
|
48994
|
+
}
|
|
48995
|
+
else {
|
|
48996
|
+
// Fall back to standard cartesian product
|
|
48997
|
+
product = getCombinations(quantifiedSets);
|
|
48998
|
+
}
|
|
48963
48999
|
const result = [];
|
|
48964
49000
|
let foundTrue = false;
|
|
48965
49001
|
let foundFalse = false;
|
|
@@ -48990,10 +49026,19 @@ class ForgeExprEvaluator extends AbstractParseTreeVisitor_1.AbstractParseTreeVis
|
|
|
48990
49026
|
for (let j = 0; j < varNames.length; j++) {
|
|
48991
49027
|
quantDeclEnv.env[varNames[j]] = tuple[j];
|
|
48992
49028
|
}
|
|
48993
|
-
//
|
|
48994
|
-
|
|
48995
|
-
|
|
48996
|
-
|
|
49029
|
+
// If we used the optimized path, we can skip constraint evaluation
|
|
49030
|
+
// since the combinations already satisfy the constraint
|
|
49031
|
+
let barExprValue;
|
|
49032
|
+
if (useOptimizedPath) {
|
|
49033
|
+
barExprValue = true;
|
|
49034
|
+
}
|
|
49035
|
+
else {
|
|
49036
|
+
// now, we want to evaluate the barExpr
|
|
49037
|
+
const evalResult = this.visit(barExpr);
|
|
49038
|
+
if (!isBoolean(evalResult)) {
|
|
49039
|
+
throw new Error("Expected the expression after the bar to be a boolean!");
|
|
49040
|
+
}
|
|
49041
|
+
barExprValue = evalResult;
|
|
48997
49042
|
}
|
|
48998
49043
|
if (barExprValue) {
|
|
48999
49044
|
result.push(tuple);
|
|
@@ -49924,6 +49969,42 @@ class ForgeExprEvaluator extends AbstractParseTreeVisitor_1.AbstractParseTreeVis
|
|
|
49924
49969
|
// }
|
|
49925
49970
|
return value;
|
|
49926
49971
|
}
|
|
49972
|
+
// Handle iden (identity relation)
|
|
49973
|
+
if (constant.IDEN_TOK() !== undefined) {
|
|
49974
|
+
// The identity relation contains tuples (x, x) for every atom x in the universe
|
|
49975
|
+
const atoms = this.instanceData.getAtoms();
|
|
49976
|
+
const idenRelation = [];
|
|
49977
|
+
for (const atom of atoms) {
|
|
49978
|
+
let atomValue = atom.id;
|
|
49979
|
+
// Convert numeric and boolean strings to their actual types
|
|
49980
|
+
if (this.isConvertibleToNumber(atomValue)) {
|
|
49981
|
+
atomValue = Number(atomValue);
|
|
49982
|
+
}
|
|
49983
|
+
else if (this.isConvertibleToBoolean(atomValue)) {
|
|
49984
|
+
atomValue = this.convertToBoolean(atomValue);
|
|
49985
|
+
}
|
|
49986
|
+
idenRelation.push([atomValue, atomValue]);
|
|
49987
|
+
}
|
|
49988
|
+
return idenRelation;
|
|
49989
|
+
}
|
|
49990
|
+
// Handle univ (universal relation - all atoms)
|
|
49991
|
+
if (constant.UNIV_TOK() !== undefined) {
|
|
49992
|
+
// The universal relation contains all atoms as unary tuples
|
|
49993
|
+
const atoms = this.instanceData.getAtoms();
|
|
49994
|
+
const univRelation = [];
|
|
49995
|
+
for (const atom of atoms) {
|
|
49996
|
+
let atomValue = atom.id;
|
|
49997
|
+
// Convert numeric and boolean strings to their actual types
|
|
49998
|
+
if (this.isConvertibleToNumber(atomValue)) {
|
|
49999
|
+
atomValue = Number(atomValue);
|
|
50000
|
+
}
|
|
50001
|
+
else if (this.isConvertibleToBoolean(atomValue)) {
|
|
50002
|
+
atomValue = this.convertToBoolean(atomValue);
|
|
50003
|
+
}
|
|
50004
|
+
univRelation.push([atomValue]);
|
|
50005
|
+
}
|
|
50006
|
+
return univRelation;
|
|
50007
|
+
}
|
|
49927
50008
|
// Handle boolean constants
|
|
49928
50009
|
if (constant.text === 'true') {
|
|
49929
50010
|
return true;
|
|
@@ -49973,7 +50054,28 @@ class ForgeExprEvaluator extends AbstractParseTreeVisitor_1.AbstractParseTreeVis
|
|
|
49973
50054
|
varNames.push(varName);
|
|
49974
50055
|
quantifiedSets.push(varQuantifiedSets[varName]);
|
|
49975
50056
|
}
|
|
49976
|
-
|
|
50057
|
+
// Try to optimize numeric comparisons (same optimization as in quantifiers)
|
|
50058
|
+
// Note: Set comprehensions don't support the 'disj' keyword, so we don't check for it here.
|
|
50059
|
+
// Quantifiers do support 'disj', so they check !isDisjoint before optimizing.
|
|
50060
|
+
let product;
|
|
50061
|
+
let useOptimizedPath = false;
|
|
50062
|
+
if (varNames.length >= 2 && (0, NumericConstraintOptimizer_1.areAllNumericSets)(quantifiedSets)) {
|
|
50063
|
+
// Try to detect and optimize numeric comparison patterns
|
|
50064
|
+
const pattern = (0, NumericConstraintOptimizer_1.detectNumericComparisonPattern)(barExpr, varNames);
|
|
50065
|
+
if (pattern && pattern.type !== 'none') {
|
|
50066
|
+
// Use optimized combination generation
|
|
50067
|
+
product = (0, NumericConstraintOptimizer_1.generateOptimizedNumericCombinations)(varNames, quantifiedSets, pattern);
|
|
50068
|
+
useOptimizedPath = true;
|
|
50069
|
+
}
|
|
50070
|
+
else {
|
|
50071
|
+
// Fall back to standard cartesian product
|
|
50072
|
+
product = getCombinations(quantifiedSets);
|
|
50073
|
+
}
|
|
50074
|
+
}
|
|
50075
|
+
else {
|
|
50076
|
+
// Fall back to standard cartesian product
|
|
50077
|
+
product = getCombinations(quantifiedSets);
|
|
50078
|
+
}
|
|
49977
50079
|
const result = [];
|
|
49978
50080
|
// Optimize: create environment once and reuse it
|
|
49979
50081
|
const quantDeclEnv = {
|
|
@@ -49987,10 +50089,19 @@ class ForgeExprEvaluator extends AbstractParseTreeVisitor_1.AbstractParseTreeVis
|
|
|
49987
50089
|
for (let j = 0; j < varNames.length; j++) {
|
|
49988
50090
|
quantDeclEnv.env[varNames[j]] = tuple[j];
|
|
49989
50091
|
}
|
|
49990
|
-
//
|
|
49991
|
-
|
|
49992
|
-
|
|
49993
|
-
|
|
50092
|
+
// If we used the optimized path, we can skip constraint evaluation
|
|
50093
|
+
// since the combinations already satisfy the constraint
|
|
50094
|
+
let barExprValue;
|
|
50095
|
+
if (useOptimizedPath) {
|
|
50096
|
+
barExprValue = true;
|
|
50097
|
+
}
|
|
50098
|
+
else {
|
|
50099
|
+
// now, we want to evaluate the barExpr
|
|
50100
|
+
const evalResult = this.visit(barExpr);
|
|
50101
|
+
if (!isBoolean(evalResult)) {
|
|
50102
|
+
throw new Error("Expected the expression after the bar to be a boolean value!");
|
|
50103
|
+
}
|
|
50104
|
+
barExprValue = evalResult;
|
|
49994
50105
|
}
|
|
49995
50106
|
if (barExprValue) {
|
|
49996
50107
|
// will error if not boolean val, which we want
|
|
@@ -50125,46 +50236,15 @@ class ForgeExprEvaluator extends AbstractParseTreeVisitor_1.AbstractParseTreeVis
|
|
|
50125
50236
|
}
|
|
50126
50237
|
}
|
|
50127
50238
|
}
|
|
50128
|
-
//
|
|
50129
|
-
const isConvertibleToNumber = (value) => {
|
|
50130
|
-
if (typeof value === "number") {
|
|
50131
|
-
return true;
|
|
50132
|
-
}
|
|
50133
|
-
if (typeof value === "string") {
|
|
50134
|
-
return !isNaN(Number(value));
|
|
50135
|
-
}
|
|
50136
|
-
return false;
|
|
50137
|
-
};
|
|
50138
|
-
const isConvertibleToBoolean = (value) => {
|
|
50139
|
-
if (typeof value === "boolean") {
|
|
50140
|
-
return true;
|
|
50141
|
-
}
|
|
50142
|
-
if (typeof value === "string") {
|
|
50143
|
-
return (value === "true" || value === "#t" || value === "false" || value === "#f");
|
|
50144
|
-
}
|
|
50145
|
-
return false;
|
|
50146
|
-
};
|
|
50147
|
-
const convertToBoolean = (value) => {
|
|
50148
|
-
if (typeof value === "boolean") {
|
|
50149
|
-
return value;
|
|
50150
|
-
}
|
|
50151
|
-
if (value === "true" || value === "#t") {
|
|
50152
|
-
return true;
|
|
50153
|
-
}
|
|
50154
|
-
if (value === "false" || value === "#f") {
|
|
50155
|
-
return false;
|
|
50156
|
-
}
|
|
50157
|
-
throw new Error(`Cannot convert ${value} to boolean`);
|
|
50158
|
-
};
|
|
50159
|
-
// end of 3 helper functions
|
|
50239
|
+
// end of type search
|
|
50160
50240
|
// check if it is a relation - use cache for faster lookups
|
|
50161
50241
|
this.buildRelationCache();
|
|
50162
50242
|
if (this.relationCache.has(identifier)) {
|
|
50163
50243
|
return this.relationCache.get(identifier);
|
|
50164
50244
|
}
|
|
50165
50245
|
if (result !== undefined) {
|
|
50166
|
-
result = result.map((tuple) => tuple.map((value) => isConvertibleToNumber(value) ? Number(value) : value));
|
|
50167
|
-
result = result.map((tuple) => tuple.map((value) => isConvertibleToBoolean(value) ? convertToBoolean(value) : value));
|
|
50246
|
+
result = result.map((tuple) => tuple.map((value) => this.isConvertibleToNumber(value) ? Number(value) : value));
|
|
50247
|
+
result = result.map((tuple) => tuple.map((value) => this.isConvertibleToBoolean(value) ? this.convertToBoolean(value) : value));
|
|
50168
50248
|
return result;
|
|
50169
50249
|
}
|
|
50170
50250
|
// return identifier;
|
|
@@ -50657,6 +50737,200 @@ class ForgeExprFreeVariableFinder extends AbstractParseTreeVisitor_1.AbstractPar
|
|
|
50657
50737
|
exports.ForgeExprFreeVariableFinder = ForgeExprFreeVariableFinder;
|
|
50658
50738
|
|
|
50659
50739
|
|
|
50740
|
+
/***/ }),
|
|
50741
|
+
|
|
50742
|
+
/***/ "./src/NumericConstraintOptimizer.ts":
|
|
50743
|
+
/*!*******************************************!*\
|
|
50744
|
+
!*** ./src/NumericConstraintOptimizer.ts ***!
|
|
50745
|
+
\*******************************************/
|
|
50746
|
+
/***/ ((__unused_webpack_module, exports) => {
|
|
50747
|
+
|
|
50748
|
+
"use strict";
|
|
50749
|
+
|
|
50750
|
+
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
50751
|
+
exports.detectNumericComparisonPattern = detectNumericComparisonPattern;
|
|
50752
|
+
exports.areAllNumericSets = areAllNumericSets;
|
|
50753
|
+
exports.generateOptimizedNumericCombinations = generateOptimizedNumericCombinations;
|
|
50754
|
+
/**
|
|
50755
|
+
* Detects if a constraint expression is a simple numeric comparison between two variables.
|
|
50756
|
+
* Currently detects patterns like: a < b, a > b, a <= b, a >= b, a != b
|
|
50757
|
+
*
|
|
50758
|
+
* Note: Uses string matching on expression text which is simple but may miss expressions
|
|
50759
|
+
* with extra whitespace or parentheses. This is intentionally conservative - we prefer
|
|
50760
|
+
* to fall back to the standard approach rather than risk incorrect optimization.
|
|
50761
|
+
*
|
|
50762
|
+
* @param constraintExpr The constraint expression to analyze
|
|
50763
|
+
* @param varNames The variable names in scope
|
|
50764
|
+
* @returns Pattern info if detected, null otherwise
|
|
50765
|
+
*/
|
|
50766
|
+
function detectNumericComparisonPattern(constraintExpr, varNames) {
|
|
50767
|
+
// Parse the expression text to look for simple comparison patterns
|
|
50768
|
+
const exprText = constraintExpr.text;
|
|
50769
|
+
// Check if this is a simple binary comparison between two variables
|
|
50770
|
+
// Pattern: <varName1><compareOp><varName2> (no whitespace in text representation)
|
|
50771
|
+
// Note: The AST text property concatenates tokens without whitespace
|
|
50772
|
+
// Try to extract comparison operator and operands
|
|
50773
|
+
for (const leftVar of varNames) {
|
|
50774
|
+
for (const rightVar of varNames) {
|
|
50775
|
+
if (leftVar === rightVar)
|
|
50776
|
+
continue;
|
|
50777
|
+
// Check for different comparison operators (whitespace already removed in text)
|
|
50778
|
+
if (exprText === `${leftVar}<${rightVar}`) {
|
|
50779
|
+
return { type: 'less_than', leftVar, rightVar };
|
|
50780
|
+
}
|
|
50781
|
+
if (exprText === `${leftVar}>${rightVar}`) {
|
|
50782
|
+
return { type: 'greater_than', leftVar, rightVar };
|
|
50783
|
+
}
|
|
50784
|
+
if (exprText === `${leftVar}<=${rightVar}`) {
|
|
50785
|
+
return { type: 'less_equal', leftVar, rightVar };
|
|
50786
|
+
}
|
|
50787
|
+
if (exprText === `${leftVar}>=${rightVar}`) {
|
|
50788
|
+
return { type: 'greater_equal', leftVar, rightVar };
|
|
50789
|
+
}
|
|
50790
|
+
if (exprText === `${leftVar}!=${rightVar}`) {
|
|
50791
|
+
return { type: 'not_equal', leftVar, rightVar };
|
|
50792
|
+
}
|
|
50793
|
+
// Also check for negated equality: not a = b
|
|
50794
|
+
if (exprText === `not${leftVar}=${rightVar}`) {
|
|
50795
|
+
return { type: 'not_equal', leftVar, rightVar };
|
|
50796
|
+
}
|
|
50797
|
+
}
|
|
50798
|
+
}
|
|
50799
|
+
return null;
|
|
50800
|
+
}
|
|
50801
|
+
/**
|
|
50802
|
+
* Checks if all values in the sets are numbers (or tuples containing single numbers).
|
|
50803
|
+
* This is required for numeric comparison optimization to be applicable.
|
|
50804
|
+
*/
|
|
50805
|
+
function areAllNumericSets(sets) {
|
|
50806
|
+
for (const set of sets) {
|
|
50807
|
+
for (const tuple of set) {
|
|
50808
|
+
if (tuple.length !== 1)
|
|
50809
|
+
return false;
|
|
50810
|
+
if (typeof tuple[0] !== 'number')
|
|
50811
|
+
return false;
|
|
50812
|
+
}
|
|
50813
|
+
}
|
|
50814
|
+
return true;
|
|
50815
|
+
}
|
|
50816
|
+
/**
|
|
50817
|
+
* Extracts numbers from tuples that contain single numbers.
|
|
50818
|
+
* Precondition: This should only be called after areAllNumericSets validation.
|
|
50819
|
+
*/
|
|
50820
|
+
function extractNumbers(tuples) {
|
|
50821
|
+
return tuples.map(t => t[0]);
|
|
50822
|
+
}
|
|
50823
|
+
/**
|
|
50824
|
+
* Generates optimized combinations for numeric comparison constraints.
|
|
50825
|
+
* Instead of generating all combinations and filtering, generates only valid ones.
|
|
50826
|
+
*
|
|
50827
|
+
* @param varNames Variable names in order
|
|
50828
|
+
* @param quantifiedSets The sets each variable ranges over
|
|
50829
|
+
* @param pattern The detected numeric comparison pattern
|
|
50830
|
+
* @returns Only the tuples that satisfy the constraint
|
|
50831
|
+
*/
|
|
50832
|
+
function generateOptimizedNumericCombinations(varNames, quantifiedSets, pattern) {
|
|
50833
|
+
// Map variable names to their indices and value sets
|
|
50834
|
+
const varIndexMap = new Map();
|
|
50835
|
+
varNames.forEach((name, idx) => {
|
|
50836
|
+
varIndexMap.set(name, idx);
|
|
50837
|
+
});
|
|
50838
|
+
const leftIdx = varIndexMap.get(pattern.leftVar);
|
|
50839
|
+
const rightIdx = varIndexMap.get(pattern.rightVar);
|
|
50840
|
+
if (leftIdx === undefined || rightIdx === undefined) {
|
|
50841
|
+
// Pattern variables don't match our variable set - this shouldn't happen
|
|
50842
|
+
// if detectNumericComparisonPattern is working correctly, but throw an error
|
|
50843
|
+
// to prevent incorrect results (caller should have validated pattern matches variables)
|
|
50844
|
+
throw new Error(`Internal error: Pattern variables ${pattern.leftVar}, ${pattern.rightVar} not found in variable list`);
|
|
50845
|
+
}
|
|
50846
|
+
// Extract numeric values
|
|
50847
|
+
const leftNumbers = extractNumbers(quantifiedSets[leftIdx]);
|
|
50848
|
+
const rightNumbers = extractNumbers(quantifiedSets[rightIdx]);
|
|
50849
|
+
// For other variables, we still need all combinations
|
|
50850
|
+
const otherVars = [];
|
|
50851
|
+
const otherSets = [];
|
|
50852
|
+
for (let i = 0; i < varNames.length; i++) {
|
|
50853
|
+
if (i !== leftIdx && i !== rightIdx) {
|
|
50854
|
+
otherVars.push(i);
|
|
50855
|
+
otherSets.push(extractNumbers(quantifiedSets[i]));
|
|
50856
|
+
}
|
|
50857
|
+
}
|
|
50858
|
+
const result = [];
|
|
50859
|
+
// Generate combinations based on the comparison type
|
|
50860
|
+
const generatePairs = (compareFunc) => {
|
|
50861
|
+
for (const leftVal of leftNumbers) {
|
|
50862
|
+
for (const rightVal of rightNumbers) {
|
|
50863
|
+
if (compareFunc(leftVal, rightVal)) {
|
|
50864
|
+
// This pair satisfies the constraint
|
|
50865
|
+
// Now combine with all other variables if any
|
|
50866
|
+
if (otherVars.length === 0) {
|
|
50867
|
+
// Simple case: only two variables
|
|
50868
|
+
const tuple = new Array(varNames.length);
|
|
50869
|
+
tuple[leftIdx] = leftVal;
|
|
50870
|
+
tuple[rightIdx] = rightVal;
|
|
50871
|
+
result.push(tuple);
|
|
50872
|
+
}
|
|
50873
|
+
else {
|
|
50874
|
+
// Need to generate cartesian product with other variables
|
|
50875
|
+
const otherCombos = cartesianProduct(otherSets);
|
|
50876
|
+
for (const otherCombo of otherCombos) {
|
|
50877
|
+
const tuple = new Array(varNames.length);
|
|
50878
|
+
tuple[leftIdx] = leftVal;
|
|
50879
|
+
tuple[rightIdx] = rightVal;
|
|
50880
|
+
for (let i = 0; i < otherVars.length; i++) {
|
|
50881
|
+
tuple[otherVars[i]] = otherCombo[i];
|
|
50882
|
+
}
|
|
50883
|
+
result.push(tuple);
|
|
50884
|
+
}
|
|
50885
|
+
}
|
|
50886
|
+
}
|
|
50887
|
+
}
|
|
50888
|
+
}
|
|
50889
|
+
};
|
|
50890
|
+
switch (pattern.type) {
|
|
50891
|
+
case 'less_than':
|
|
50892
|
+
generatePairs((a, b) => a < b);
|
|
50893
|
+
break;
|
|
50894
|
+
case 'greater_than':
|
|
50895
|
+
generatePairs((a, b) => a > b);
|
|
50896
|
+
break;
|
|
50897
|
+
case 'less_equal':
|
|
50898
|
+
generatePairs((a, b) => a <= b);
|
|
50899
|
+
break;
|
|
50900
|
+
case 'greater_equal':
|
|
50901
|
+
generatePairs((a, b) => a >= b);
|
|
50902
|
+
break;
|
|
50903
|
+
case 'not_equal':
|
|
50904
|
+
generatePairs((a, b) => a !== b);
|
|
50905
|
+
break;
|
|
50906
|
+
default:
|
|
50907
|
+
// Unknown pattern, return empty (will fall back to standard approach)
|
|
50908
|
+
return [];
|
|
50909
|
+
}
|
|
50910
|
+
return result;
|
|
50911
|
+
}
|
|
50912
|
+
/**
|
|
50913
|
+
* Helper to generate cartesian product of number arrays
|
|
50914
|
+
*/
|
|
50915
|
+
function cartesianProduct(arrays) {
|
|
50916
|
+
if (arrays.length === 0)
|
|
50917
|
+
return [[]];
|
|
50918
|
+
if (arrays.some(arr => arr.length === 0))
|
|
50919
|
+
return [];
|
|
50920
|
+
let result = [[]];
|
|
50921
|
+
for (const arr of arrays) {
|
|
50922
|
+
const newResult = [];
|
|
50923
|
+
for (const existing of result) {
|
|
50924
|
+
for (const value of arr) {
|
|
50925
|
+
newResult.push([...existing, value]);
|
|
50926
|
+
}
|
|
50927
|
+
}
|
|
50928
|
+
result = newResult;
|
|
50929
|
+
}
|
|
50930
|
+
return result;
|
|
50931
|
+
}
|
|
50932
|
+
|
|
50933
|
+
|
|
50660
50934
|
/***/ }),
|
|
50661
50935
|
|
|
50662
50936
|
/***/ "./src/errorListener.ts":
|
package/package.json
CHANGED