@@ -207,6 +207,8 @@ def __init__(
207
207
exact = True ,
208
208
rtol = None ,
209
209
atol = None ,
210
+ open_lower = False ,
211
+ open_upper = False ,
210
212
):
211
213
"""**Initialisation**
212
214
@@ -249,6 +251,24 @@ def __init__(
249
251
250
252
.. versionadded:: 3.15.2
251
253
254
+ open_lower: `bool`, optional
255
+ Only applicable to the ``'wi'`` operator.
256
+ If True, open the interval at the lower
257
+ bound so that value0 is excluded from the
258
+ range. By default the interval is closed
259
+ so that value0 is included.
260
+
261
+ .. versionadded:: NEXTVERSION
262
+
263
+ open_upper: `bool`, optional
264
+ Only applicable to the ``'wi'`` operator.
265
+ If True, open the interval at the upper
266
+ bound so that value1 is excluded from the
267
+ range. By default the interval is closed
268
+ so that value1 is included.
269
+
270
+ .. versionadded:: NEXTVERSION
271
+
252
272
exact: deprecated at version 3.0.0.
253
273
Use `re.compile` objects in *value* instead.
254
274
@@ -289,6 +309,16 @@ def __init__(
289
309
self ._rtol = rtol
290
310
self ._atol = atol
291
311
312
+ if open_lower or open_upper :
313
+ if operator != "wi" :
314
+ raise ValueError (
315
+ "Can only set the 'open_lower' and 'open_upper' "
316
+ "parameters for the 'wi' operator"
317
+ )
318
+
319
+ self ._open_lower = open_lower
320
+ self ._open_upper = open_upper
321
+
292
322
def __dask_tokenize__ (self ):
293
323
"""Return a hashable value fully representative of the object.
294
324
@@ -316,6 +346,9 @@ def __dask_tokenize__(self):
316
346
if operator == "isclose" :
317
347
value += (self .rtol , self .atol )
318
348
349
+ if operator == "wi" :
350
+ value += (self .open_lower , self .open_upper )
351
+
319
352
return (self .__class__ , operator , self ._attr ) + value
320
353
321
354
def __deepcopy__ (self , memo ):
@@ -452,8 +485,22 @@ def __str__(self):
452
485
attr = "." .join (self ._attr )
453
486
operator = self ._operator
454
487
compound = self ._compound
488
+
489
+ # For "wi" queries only, open intervals are supported. For "wi" _value
490
+ # is a list of two values, with representation from string list form
491
+ # of '[a, b]' which corresponds to the standard mathematical notation
492
+ # for a closed interval, the default. But an open endpoint is indicated
493
+ # by a parenthesis, so adjust repr. to convert square bracket(s).
494
+ repr_value = str (self ._value )
495
+ if self .open_lower :
496
+ repr_value = "(" + repr_value [1 :]
497
+
498
+
499
+ if self .open_upper :
500
+ repr_value = repr_value [:- 1 ] + ")"
501
+
455
502
if not compound :
456
- out = f"{ attr } ({ operator } { self . _value !s } "
503
+ out = f"{ attr } ({ operator } { repr_value } "
457
504
rtol = self .rtol
458
505
if rtol is not None :
459
506
out += f" rtol={ rtol } "
@@ -596,6 +643,28 @@ def Units(self):
596
643
597
644
raise AttributeError (f"{ self !r} has indeterminate units" )
598
645
646
+ @property
647
+ def open_lower (self ):
648
+ """True if the interval is open at the (excludes the) lower bound.
649
+
650
+ .. versionadded:: NEXTVERSION
651
+
652
+ .. seealso:: `open_upper`
653
+
654
+ """
655
+ return getattr (self , "_open_lower" , False )
656
+
657
+ @property
658
+ def open_upper (self ):
659
+ """True if the interval is open at the (excludes the) upper bound.
660
+
661
+ .. versionadded:: NEXTVERSION
662
+
663
+ .. seealso:: `open_lower`
664
+
665
+ """
666
+ return getattr (self , "_open_upper" , False )
667
+
599
668
@property
600
669
def rtol (self ):
601
670
"""The tolerance on relative numerical differences.
@@ -644,8 +713,7 @@ def value(self):
644
713
return value
645
714
646
715
def addattr (self , attr ):
647
- """Return a `Query` object with a new left hand side operand
648
- attribute to be used during evaluation. TODO.
716
+ """Redefine the query to be on an object's attribute.
649
717
650
718
If another attribute has previously been specified, then the new
651
719
attribute is considered to be an attribute of the existing
@@ -803,6 +871,8 @@ def equals(self, other, verbose=None, traceback=False):
803
871
"_operator" ,
804
872
"_rtol" ,
805
873
"_atol" ,
874
+ "_open_lower" ,
875
+ "_open_upper" ,
806
876
):
807
877
x = getattr (self , attr , None )
808
878
y = getattr (other , attr , None )
@@ -905,7 +975,17 @@ def _evaluate(self, x, parent_attr):
905
975
if _wi is not None :
906
976
return _wi (value )
907
977
908
- return (x >= value [0 ]) & (x <= value [1 ])
978
+ if self .open_lower :
979
+ lower_bound = x > value [0 ]
980
+ else :
981
+ lower_bound = x >= value [0 ]
982
+
983
+ if self .open_upper :
984
+ upper_bound = x < value [1 ]
985
+ else :
986
+ upper_bound = x <= value [1 ]
987
+
988
+ return lower_bound & upper_bound
909
989
910
990
if operator == "eq" :
911
991
try :
@@ -1629,9 +1709,21 @@ def isclose(value, units=None, attr=None, rtol=None, atol=None):
1629
1709
)
1630
1710
1631
1711
1632
- def wi (value0 , value1 , units = None , attr = None ):
1712
+ def wi (
1713
+ value0 ,
1714
+ value1 ,
1715
+ units = None ,
1716
+ attr = None ,
1717
+ open_lower = False ,
1718
+ open_upper = False ,
1719
+ ):
1633
1720
"""A `Query` object for a "within a range" condition.
1634
1721
1722
+ The condition is a closed interval by default, inclusive of
1723
+ both the endpoints, but can be made open or half-open to exclude
1724
+ the endpoints on either end with use of the `open_lower` and
1725
+ `open_upper` parameters.
1726
+
1635
1727
.. seealso:: `cf.contains`, `cf.eq`, `cf.ge`, `cf.gt`, `cf.ne`,
1636
1728
`cf.le`, `cf.lt`, `cf.set`, `cf.wo`, `cf.isclose`
1637
1729
@@ -1643,6 +1735,22 @@ def wi(value0, value1, units=None, attr=None):
1643
1735
value1:
1644
1736
The upper bound of the range.
1645
1737
1738
+ open_lower: `bool`, optional
1739
+ If True, open the interval at the lower
1740
+ bound so that value0 is excluded from the
1741
+ range. By default the interval is closed
1742
+ so that value0 is included.
1743
+
1744
+ .. versionadded:: NEXTVERSION
1745
+
1746
+ open_upper: `bool`, optional
1747
+ If True, open the interval at the upper
1748
+ bound so that value1 is excluded from the
1749
+ range. By default the interval is closed
1750
+ so that value1 is included.
1751
+
1752
+ .. versionadded:: NEXTVERSION
1753
+
1646
1754
units: `str` or `Units`, optional
1647
1755
The units of *value*. By default, the same units as the
1648
1756
operand being tested are assumed, if applicable. If
@@ -1671,9 +1779,42 @@ def wi(value0, value1, units=None, attr=None):
1671
1779
True
1672
1780
>>> q.evaluate(4)
1673
1781
False
1782
+ >>> q.evaluate(5)
1783
+ True
1784
+ >>> q.evaluate(7)
1785
+ True
1786
+
1787
+ The interval can be made open on either side or both. Note that,
1788
+ as per mathematical interval notation, square brackets indicate
1789
+ closed endpoints and parentheses open endpoints in the representation:
1790
+
1791
+ >>> q = cf.wi(5, 7, open_upper=True)
1792
+ >>> q
1793
+ <CF Query: (wi [5, 7))>
1794
+ >>> q.evaluate(7)
1795
+ False
1796
+ >>> q = cf.wi(5, 7, open_lower=True)
1797
+ >>> q
1798
+ <CF Query: (wi (5, 7])>
1799
+ >>> q.evaluate(5)
1800
+ False
1801
+ >>> q = cf.wi(5, 7, open_lower=True, open_upper=True)
1802
+ >>> q
1803
+ <CF Query: (wi (5, 7))>
1804
+ >>> q.evaluate(5)
1805
+ False
1806
+ >>> q.evaluate(7)
1807
+ False
1674
1808
1675
1809
"""
1676
- return Query ("wi" , [value0 , value1 ], units = units , attr = attr )
1810
+ return Query (
1811
+ "wi" ,
1812
+ [value0 , value1 ],
1813
+ units = units ,
1814
+ attr = attr ,
1815
+ open_lower = open_lower ,
1816
+ open_upper = open_upper ,
1817
+ )
1677
1818
1678
1819
1679
1820
def wo (value0 , value1 , units = None , attr = None ):
@@ -2466,10 +2607,6 @@ def seasons(n=4, start=12):
2466
2607
.. seealso:: `cf.year`, `cf.month`, `cf.day`, `cf.hour`, `cf.minute`,
2467
2608
`cf.second`, `cf.djf`, `cf.mam`, `cf.jja`, `cf.son`
2468
2609
2469
- TODO
2470
-
2471
- .. seealso:: `cf.mam`, `cf.jja`, `cf.son`, `cf.djf`
2472
-
2473
2610
:Parameters:
2474
2611
2475
2612
n: `int`, optional
0 commit comments