@@ -21,6 +21,37 @@ import {
2121 cantorEnumerateRationals ,
2222} from '../numerics/numeric' ;
2323
24+ /**
25+ * Transform a List or Tuple with exactly 2 elements to an Interval in set contexts.
26+ *
27+ * This enables contextual parsing where `[a, b]` and `(a, b)` are interpreted as
28+ * intervals when used as operands of set operations like Element, Union, etc.
29+ *
30+ * - `["List", a, b]` → `["Interval", a, b]` (closed interval [a, b])
31+ * - `["Tuple", a, b]` → `["Interval", ["Open", a], ["Open", b]]` (open interval (a, b))
32+ *
33+ * Returns the original expression unchanged if it's not a 2-element List/Tuple.
34+ */
35+ function listToIntervalInSetContext (
36+ ce : ComputeEngine ,
37+ expr : BoxedExpression
38+ ) : BoxedExpression {
39+ // Transform List with 2 elements to closed Interval
40+ if ( expr . operator === 'List' && expr . nops === 2 ) {
41+ return ce . function ( 'Interval' , [ expr . op1 . canonical , expr . op2 . canonical ] ) ;
42+ }
43+
44+ // Transform Tuple with 2 elements to open Interval
45+ if ( expr . operator === 'Tuple' && expr . nops === 2 ) {
46+ return ce . function ( 'Interval' , [
47+ ce . function ( 'Open' , [ expr . op1 . canonical ] ) ,
48+ ce . function ( 'Open' , [ expr . op2 . canonical ] ) ,
49+ ] ) ;
50+ }
51+
52+ return expr . canonical ;
53+ }
54+
2455export const SETS_LIBRARY : SymbolDefinitions = {
2556 //
2657 // Constants
@@ -487,6 +518,21 @@ export const SETS_LIBRARY: SymbolDefinitions = {
487518 description :
488519 'Test whether a value is an element of a collection. ' +
489520 'Optional third argument is a boolean expression (condition) for filtered iteration in Sum/Product.' ,
521+ canonical : ( args , { engine : ce } ) => {
522+ if ( args . length < 2 ) return ce . _fn ( 'Element' , args ) ;
523+ const [ value , collection , condition ] = args ;
524+ // Transform List/Tuple with 2 elements to Interval in set context
525+ const canonicalCollection = listToIntervalInSetContext ( ce , collection ) ;
526+ // Only include condition if it's present and not Nothing
527+ if ( condition && condition . symbol !== 'Nothing' ) {
528+ return ce . _fn ( 'Element' , [
529+ value . canonical ,
530+ canonicalCollection ,
531+ condition . canonical ,
532+ ] ) ;
533+ }
534+ return ce . _fn ( 'Element' , [ value . canonical , canonicalCollection ] ) ;
535+ } ,
490536 evaluate : ( [ value , collection , _condition ] , { engine : ce } ) => {
491537 // Note: condition is only used during Sum/Product iteration,
492538 // not for standalone Element evaluation
@@ -514,6 +560,14 @@ export const SETS_LIBRARY: SymbolDefinitions = {
514560 signature : '(lhs:collection, rhs: collection) -> boolean' ,
515561 description :
516562 'Test whether the first collection is a strict subset of the second.' ,
563+ canonical : ( args , { engine : ce } ) => {
564+ if ( args . length !== 2 ) return ce . _fn ( 'Subset' , args ) ;
565+ // Transform List/Tuple with 2 elements to Interval in set context
566+ return ce . _fn ( 'Subset' , [
567+ listToIntervalInSetContext ( ce , args [ 0 ] ) ,
568+ listToIntervalInSetContext ( ce , args [ 1 ] ) ,
569+ ] ) ;
570+ } ,
517571 evaluate : ( [ lhs , rhs ] , { engine : ce } ) => {
518572 const result = subset ( lhs , rhs ) ;
519573 if ( result === true ) return ce . True ;
@@ -527,6 +581,14 @@ export const SETS_LIBRARY: SymbolDefinitions = {
527581 signature : '(lhs:collection, rhs: collection) -> boolean' ,
528582 description :
529583 'Test whether the first collection is a subset (possibly equal) of the second.' ,
584+ canonical : ( args , { engine : ce } ) => {
585+ if ( args . length !== 2 ) return ce . _fn ( 'SubsetEqual' , args ) ;
586+ // Transform List/Tuple with 2 elements to Interval in set context
587+ return ce . _fn ( 'SubsetEqual' , [
588+ listToIntervalInSetContext ( ce , args [ 0 ] ) ,
589+ listToIntervalInSetContext ( ce , args [ 1 ] ) ,
590+ ] ) ;
591+ } ,
530592 evaluate : ( [ lhs , rhs ] , { engine : ce } ) => {
531593 const result = subset ( lhs , rhs , false ) ;
532594 if ( result === true ) return ce . True ;
@@ -553,6 +615,14 @@ export const SETS_LIBRARY: SymbolDefinitions = {
553615 signature : '(lhs:collection, rhs: collection) -> boolean' ,
554616 description :
555617 'Test whether the first collection is a strict superset of the second.' ,
618+ canonical : ( args , { engine : ce } ) => {
619+ if ( args . length !== 2 ) return ce . _fn ( 'Superset' , args ) ;
620+ // Transform List/Tuple with 2 elements to Interval in set context
621+ return ce . _fn ( 'Superset' , [
622+ listToIntervalInSetContext ( ce , args [ 0 ] ) ,
623+ listToIntervalInSetContext ( ce , args [ 1 ] ) ,
624+ ] ) ;
625+ } ,
556626 evaluate : ( [ lhs , rhs ] , { engine : ce } ) => {
557627 const result = subset ( rhs , lhs ) ; // reversed
558628 if ( result === true ) return ce . True ;
@@ -566,6 +636,14 @@ export const SETS_LIBRARY: SymbolDefinitions = {
566636 signature : '(lhs:collection, rhs: collection) -> boolean' ,
567637 description :
568638 'Test whether the first collection is a superset (possibly equal) of the second.' ,
639+ canonical : ( args , { engine : ce } ) => {
640+ if ( args . length !== 2 ) return ce . _fn ( 'SupersetEqual' , args ) ;
641+ // Transform List/Tuple with 2 elements to Interval in set context
642+ return ce . _fn ( 'SupersetEqual' , [
643+ listToIntervalInSetContext ( ce , args [ 0 ] ) ,
644+ listToIntervalInSetContext ( ce , args [ 1 ] ) ,
645+ ] ) ;
646+ } ,
569647 evaluate : ( [ lhs , rhs ] , { engine : ce } ) => {
570648 const result = subset ( rhs , lhs , true ) ; // reversed
571649 if ( result === true ) return ce . True ;
@@ -643,13 +721,17 @@ export const SETS_LIBRARY: SymbolDefinitions = {
643721 canonical : ( args , { engine : ce } ) => {
644722 if ( args . length === 0 ) return ce . symbol ( 'EmptySet' ) ;
645723 if ( args . length === 1 ) return ce . symbol ( 'EmptySet' ) ;
646- args =
724+ // Transform List/Tuple with 2 elements to Interval in set context
725+ const transformedArgs = args . map ( ( arg ) =>
726+ listToIntervalInSetContext ( ce , arg )
727+ ) ;
728+ const validatedArgs =
647729 validateArguments (
648730 ce ,
649- flatten ( args , 'Intersection' ) ,
731+ flatten ( transformedArgs , 'Intersection' ) ,
650732 parseType ( '(set+) -> set' )
651- ) ?? args ;
652- return ce . _fn ( 'Intersection' , args ) ;
733+ ) ?? transformedArgs ;
734+ return ce . _fn ( 'Intersection' , validatedArgs ) ;
653735 } ,
654736 evaluate : intersection ,
655737 collection : {
@@ -669,16 +751,20 @@ export const SETS_LIBRARY: SymbolDefinitions = {
669751 description : 'Return the union of two or more collections as a set.' ,
670752 canonical : ( args , { engine : ce } ) => {
671753 if ( args . length === 0 ) return ce . symbol ( 'EmptySet' ) ;
672- args =
754+ // Transform List/Tuple with 2 elements to Interval in set context
755+ const transformedArgs = args . map ( ( arg ) =>
756+ listToIntervalInSetContext ( ce , arg )
757+ ) ;
758+ const validatedArgs =
673759 validateArguments (
674760 ce ,
675- flatten ( args , 'Union' ) ,
761+ flatten ( transformedArgs , 'Union' ) ,
676762 parseType ( '(collection+) -> set' )
677- ) ?? args ;
763+ ) ?? transformedArgs ;
678764 // Even if there is only one argument, we still need to call Union
679765 // to canonicalize the argument, since it may not be a set (it could
680766 // be a collection)
681- return ce . _fn ( 'Union' , args ) ;
767+ return ce . _fn ( 'Union' , validatedArgs ) ;
682768 } ,
683769 evaluate : union ,
684770
0 commit comments