diff --git a/temporian/core/event_set_ops.py b/temporian/core/event_set_ops.py index 24ba42d0a..ee4e76e9f 100644 --- a/temporian/core/event_set_ops.py +++ b/temporian/core/event_set_ops.py @@ -556,34 +556,24 @@ def __neg__(self: EventSetOrNode) -> EventSetOrNode: return multiply_scalar(input=self, value=-1) def __invert__(self: EventSetOrNode) -> EventSetOrNode: - """Inverts a boolean [`EventSet`][temporian.EventSet] element-wise. + """Inverts a boolean or integer [`EventSet`][temporian.EventSet] + element-wise. - Swaps False <-> True. - - Does not work on integers, they should be cast to - [`tp.bool_`][temporian.bool_] beforehand, using - [`EventSet.cast()`][temporian.EventSet.cast]. + Swaps False <-> True. Applies bitwise NOT to each integer. Example: ```python >>> a = tp.event_set( ... timestamps=[1, 2], - ... features={"M": [1, 5], "N": [1.0, 5.5]}, + ... features={"M": [True, False], "N": [1, 5]}, ... ) - >>> # Boolean EventSet - >>> b = a < 2 - >>> b - indexes: ... - 'M': [ True False] - 'N': [ True False] - ... >>> # Inverted EventSet - >>> c = ~b - >>> c + >>> b = ~a + >>> b indexes: ... 'M': [False True] - 'N': [False True] + 'N': [-2 -6] ... ``` @@ -1470,6 +1460,145 @@ def __xor__(self: EventSetOrNode, other: Any) -> EventSetOrNode: self._raise_bool_error("^", other) assert False + def __lshift__(self:EventSetOrNode, other: Any) -> EventSetOrNode: + """Performs a left bitwise shift (`self << other`) element-wise with + another [`EventSet`][temporian.EventSet] or a scalar value. + + If `other` is an `EventSet`, each feature in `self` is shifted left by + the corresponding value in `other` at the same position. + If `other` is a scalar, each feature in `self` is shifted left by + the scalar value. + + When `other` is an `EventSet`, `self` and `other` must have the same + sampling and the same number of features. The feature types in `self` + must be non-negative integer types. + When `other` is a scalar, it must be a non-negative integer. + + Example with EventSet: + ```python + >>> a = tp.event_set( + ... timestamps=[1, 2, 3], + ... features={"f1": [2, 4, 8]} + ... ) + >>> b = tp.event_set( + ... timestamps=[1, 2, 3], + ... features={"f2": [1, 2, 3]}, + ... same_sampling_as=a + ... ) + >>> c = a << b + >>> c + indexes: [] + features: [('left_shift_f1_f2', int64)] + events: (3 events): + timestamps: [1. 2. 3.] + 'left_shift_f1_f2': [ 4 16 64] + ... + + ``` + + Example with scalar value: + ```python + >>> a = tp.event_set( + ... timestamps=[1, 2, 3], + ... features={"f1": [2, 4, 8]} + ... ) + >>> b = a << 2 + >>> b + indexes: [] + features: [('f1', int64)] + events: (3 events): + timestamps: [1. 2. 3.] + 'f1': [ 8 16 32] + ... + + ``` + + Args: + other: EventSet with only boolean features. + + Returns: + EventSet with result of the comparison. + """ + + if isinstance(other, self.__class__): + from temporian.core.operators.binary import left_shift + return left_shift(input_1=self, input_2=other) + + if isinstance(other, int): + from temporian.core.operators.scalar import left_shift_scalar + return left_shift_scalar(input=self, value=other) + + self._raise_error("<<", other, "int") + assert False + + def __rshift__(self:EventSetOrNode, other: Any) -> EventSetOrNode: + """Performs a right bitwise shift (`self >> other`) element-wise with + another [`EventSet`][temporian.EventSet] or a scalar value. + If `other` is an `EventSet`, each feature in `self` is shifted right + by the corresponding value in `other` at the same position. + If `other` is a scalar, each feature in `self` is shifted right by the + scalar value. + + When `other` is an `EventSet`, `self` and `other` must have the same + sampling and the same number of features. The feature types + in `self` must be non-negative integer types. + When `other` is a scalar, it must be a non-negative integer. + + Example with EventSet: + ```python + >>> a = tp.event_set( + ... timestamps=[1, 2, 3], + ... features={"f1": [8, 16, 32]} + ... ) + >>> b = tp.event_set( + ... timestamps=[1, 2, 3], + ... features={"f2": [1, 2, 3]}, + ... same_sampling_as=a + ... ) + >>> c = a >> b + >>> c + indexes: [] + features: [('right_shift_f1_f2', int64)] + events: (3 events): + timestamps: [1. 2. 3.] + 'right_shift_f1_f2': [4 4 4] + ... + + ``` + + Example with scalar value: + >>> a = tp.event_set( + ... timestamps=[1, 2, 3], + ... features={"f1": [8, 16, 32]} + ... ) + >>> b = a >> 2 + >>> b + indexes: [] + features: [('f1', int64)] + events: (3 events): + timestamps: [1. 2. 3.] + 'f1': [2 4 8] + ... + + ``` + + Args: + other: EventSet with only boolean features. + + Returns: + EventSet with result of the comparison. + """ + + if isinstance(other, self.__class__): + from temporian.core.operators.binary import right_shift + return right_shift(input_1=self, input_2=other) + + if isinstance(other, int): + from temporian.core.operators.scalar import right_shift_scalar + return right_shift_scalar(input=self, value=other) + + self._raise_error(">>", other, "int") + assert False ############# # OPERATORS # ############# @@ -1613,6 +1742,222 @@ def begin(self: EventSetOrNode) -> EventSetOrNode: return begin(self) + def bitwise_and(self: EventSetOrNode, other: Any) -> EventSetOrNode: + """Computes the bitwise AND between an [`EventSet`][temporian.EventSet] + or a scalar value to `self` element-wise. The operation is only + supported for Events with features that are integers. + + If an EventSet, each feature in `self` is bitwise ANDed to the feature + in `other` in the same position. `self` and `other` must have the same + sampling, index, number of features and dtype for the features in the + same positions. + + If a scalar, `other` is bitwise ANDed with each item in each feature + in `self`. + + Example with EventSet: + ```python + >>> a = tp.event_set( + ... timestamps=[1, 2, 3], + ... features={"f1": [2, 5, 7], "f2": [9, 11, 13]} + ... ) + >>> b = tp.event_set( + ... timestamps=[1, 2, 3], + ... features={"f3": [3, 4, 8], "f4": [10, 12, 15]}, + ... same_sampling_as=a + ... ) + + >>> c = a.bitwise_and(b) + >>> c + indexes: [] + features: [('bitwise_and_f1_f3', int64), ('bitwise_and_f2_f4', int64)] + events: + (3 events): + timestamps: [1. 2. 3.] + 'bitwise_and_f1_f3': [2 4 0] + 'bitwise_and_f2_f4': [ 8 8 13] + ... + + ``` + + Example with scalar value: + ```python + >>> a = tp.event_set( + ... timestamps=[1, 2, 3], + ... features={"f1": [2, 5, 7], "f2": [9, 11, 13]} + ... ) + + >>> b = a.bitwise_and(3) + >>> b + indexes: ... + timestamps: [1. 2. 3.] + 'f1': [2 1 3] + 'f2': [1 3 1] + ... + + ``` + + Args: + other: EventSet or scalar value. + + Returns: + Result of the operation. + """ + if isinstance(other, self.__class__): + from temporian.core.operators.binary import bitwise_and + return bitwise_and(input_1=self, input_2=other) + + if isinstance(other, int): + from temporian.core.operators.scalar import bitwise_and_scalar + return bitwise_and_scalar(input=self, value=other) + + self._raise_error("bitwise_and", other, "int") + assert False + + def bitwise_or(self: EventSetOrNode, other: Any) -> EventSetOrNode: + """Computes the bitwise OR between an [`EventSet`][temporian.EventSet] + or a scalar value to `self` element-wise. The operation is only + supported for Events with features that are integers. + + If an EventSet, each feature in `self` is bitwise ORed to the feature + in `other` in the same position. `self` and `other` must have the same + sampling, index, number of features and dtype for the features in the + same positions. + + If a scalar, `other` is bitwise ORed with each item in each feature + in `self`. + + Example with EventSet: + ```python + >>> a = tp.event_set( + ... timestamps=[1, 2, 3], + ... features={"f1": [2, 5, 7], "f2": [9, 11, 13]} + ... ) + >>> b = tp.event_set( + ... timestamps=[1, 2, 3], + ... features={"f3": [3, 4, 8], "f4": [10, 12, 15]}, + ... same_sampling_as=a + ... ) + + >>> c = a.bitwise_or(b) + >>> c + indexes: [] + features: [('bitwise_or_f1_f3', int64), ('bitwise_or_f2_f4', int64)] + events: + (3 events): + timestamps: [1. 2. 3.] + 'bitwise_or_f1_f3': [ 3 5 15] + 'bitwise_or_f2_f4': [11 15 15] + ... + + ``` + + Example with scalar value: + ```python + >>> a = tp.event_set( + ... timestamps=[1, 2, 3], + ... features={"f1": [2, 5, 7], "f2": [9, 11, 13]} + ... ) + + >>> b = a.bitwise_or(3) + >>> b + indexes: ... + timestamps: [1. 2. 3.] + 'f1': [3 7 7] + 'f2': [11 11 15] + ... + + ``` + + Args: + other: EventSet or scalar value. + + Returns: + Result of the operation. + """ + if isinstance(other, self.__class__): + from temporian.core.operators.binary import bitwise_or + return bitwise_or(input_1=self, input_2=other) + + if isinstance(other, int): + from temporian.core.operators.scalar import bitwise_or_scalar + return bitwise_or_scalar(input=self, value=other) + + self._raise_error("bitwise_or", other, "int") + assert False + + def bitwise_xor(self: EventSetOrNode, other: Any) -> EventSetOrNode: + """Computes the bitwise XOR between an [`EventSet`][temporian.EventSet] + or a scalar value to `self` element-wise. The operation is only + supported for Events with features that are integers. + + If an EventSet, each feature in `self` is bitwise XORed to the feature + in `other` in the same position. `self` and `other` must have the same + sampling, index, number of features and dtype for the features in the + same positions. + + If a scalar, `other` is bitwise XORed with each item in each feature + in `self`. + + Example with EventSet: + ```python + >>> a = tp.event_set( + ... timestamps=[1, 2, 3], + ... features={"f1": [2, 5, 7], "f2": [9, 11, 13]} + ... ) + >>> b = tp.event_set( + ... timestamps=[1, 2, 3], + ... features={"f3": [3, 4, 8], "f4": [10, 12, 15]}, + ... same_sampling_as=a + ... ) + + >>> c = a.bitwise_xor(b) + >>> c + indexes: [] + features: [('bitwise_xor_f1_f3', int64), ('bitwise_xor_f2_f4', int64)] + events: + (3 events): + timestamps: [1. 2. 3.] + 'bitwise_xor_f1_f3': [ 1 1 15] + 'bitwise_xor_f2_f4': [3 7 2] + ... + + ``` + + Example with scalar value: + ```python + >>> a = tp.event_set( + ... timestamps=[1, 2, 3], + ... features={"f1": [2, 5, 7], "f2": [9, 11, 13]} + ... ) + + >>> b = a.bitwise_xor(3) + >>> b + indexes: ... + timestamps: [1. 2. 3.] + 'f1': [1 6 4] + 'f2': [10 8 14] + ... + + ``` + + Args: + other: EventSet or scalar value. + + Returns: + Result of the operation. + """ + if isinstance(other, self.__class__): + from temporian.core.operators.binary import bitwise_xor + return bitwise_xor(input_1=self, input_2=other) + + if isinstance(other, int): + from temporian.core.operators.scalar import bitwise_xor_scalar + return bitwise_xor_scalar(input=self, value=other) + + self._raise_error("bitwise_xor", other, "int") + assert False + def calendar_day_of_month( self: EventSetOrNode, tz: Union[str, float, int] = 0 ) -> EventSetOrNode: diff --git a/temporian/core/operators/binary/BUILD b/temporian/core/operators/binary/BUILD index f2ec7f39f..9654d240c 100644 --- a/temporian/core/operators/binary/BUILD +++ b/temporian/core/operators/binary/BUILD @@ -14,6 +14,7 @@ py_library( ":arithmetic", ":logical", ":relational", + ":bitwise", ], ) @@ -73,3 +74,18 @@ py_library( "//temporian/core/data:schema", ], ) + +py_library( + name = "bitwise", + srcs = ["bitwise.py"], + srcs_version = "PY3", + deps = [ + ":base", + "//temporian/core:compilation", + "//temporian/core:operator_lib", + "//temporian/core:typing", + "//temporian/core/data:dtype", + "//temporian/core/data:node", + "//temporian/core/data:schema", + ], +) diff --git a/temporian/core/operators/binary/__init__.py b/temporian/core/operators/binary/__init__.py index dcbdfb498..6ce2f1e77 100644 --- a/temporian/core/operators/binary/__init__.py +++ b/temporian/core/operators/binary/__init__.py @@ -56,3 +56,16 @@ logical_or, logical_xor, ) + +from temporian.core.operators.binary.bitwise import ( + BitwiseAndOperator, + BitwiseOrOperator, + BitwiseXorOperator, + LeftShiftOperator, + RightShiftOperator, + bitwise_and, + bitwise_or, + bitwise_xor, + left_shift, + right_shift, +) diff --git a/temporian/core/operators/binary/bitwise.py b/temporian/core/operators/binary/bitwise.py new file mode 100644 index 000000000..75635648f --- /dev/null +++ b/temporian/core/operators/binary/bitwise.py @@ -0,0 +1,152 @@ +# Copyright 2021 Google LLC. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Binary logic operators classes and public API function definitions.""" + +from temporian.core import operator_lib +from temporian.core.compilation import compile +from temporian.core.data.dtype import DType +from temporian.core.data.node import EventSetNode +from temporian.core.data.schema import FeatureSchema +from temporian.core.operators.binary.base import BaseBinaryOperator +from temporian.core.typing import EventSetOrNode + + +class BaseBitwiseOperator(BaseBinaryOperator): + OP_NAME = "" + + def __init__(self, input_1: EventSetNode, input_2: EventSetNode): + super().__init__(input_1, input_2) + + # Check that all features are Integer + # Note: Assuming that input_1 and input_2 features have the same dtype + for feature in input_1.schema.features: + if feature.dtype != DType.INT32 and feature.dtype != DType.INT64: + raise ValueError( + "Bitwise operators only support INT32 or INT64 types, but " + f"feature {feature.name} has dtype {feature.dtype}" + ) + + def output_feature_dtype( + self, feature_1: FeatureSchema, feature_2: FeatureSchema + ) -> DType: + if feature_1.dtype == DType.INT64 or feature_2.dtype == DType.INT64: + return DType.INT64 + else: + return DType.INT32 + + @classmethod + def operator_def_key(cls) -> str: + return cls.OP_NAME.upper() + + @property + def prefix(self) -> str: + return self.OP_NAME.lower() + + +class BitwiseAndOperator(BaseBitwiseOperator): + OP_NAME = "bitwise_and" + + +class BitwiseOrOperator(BaseBitwiseOperator): + OP_NAME = "bitwise_or" + + +class BitwiseXorOperator(BaseBitwiseOperator): + OP_NAME = "bitwise_xor" + + +class LeftShiftOperator(BaseBitwiseOperator): + OP_NAME = "left_shift" + + +class RightShiftOperator(BaseBitwiseOperator): + OP_NAME = "right_shift" + + +@compile +def bitwise_and( + input_1: EventSetOrNode, + input_2: EventSetOrNode, +) -> EventSetOrNode: + assert isinstance(input_1, EventSetNode) + assert isinstance(input_2, EventSetNode) + + return BitwiseAndOperator( + input_1=input_1, + input_2=input_2, + ).outputs["output"] + + +@compile +def bitwise_or( + input_1: EventSetOrNode, + input_2: EventSetOrNode, +) -> EventSetOrNode: + assert isinstance(input_1, EventSetNode) + assert isinstance(input_2, EventSetNode) + + return BitwiseOrOperator( + input_1=input_1, + input_2=input_2, + ).outputs["output"] + + +@compile +def bitwise_xor( + input_1: EventSetOrNode, + input_2: EventSetOrNode, +) -> EventSetOrNode: + assert isinstance(input_1, EventSetNode) + assert isinstance(input_2, EventSetNode) + + return BitwiseXorOperator( + input_1=input_1, + input_2=input_2, + ).outputs["output"] + + +@compile +def left_shift( + input_1: EventSetOrNode, + input_2: EventSetOrNode, +) -> EventSetOrNode: + assert isinstance(input_1, EventSetNode) + assert isinstance(input_2, EventSetNode) + + return LeftShiftOperator( + input_1=input_1, + input_2=input_2, + ).outputs["output"] + + +@compile +def right_shift( + input_1: EventSetOrNode, + input_2: EventSetOrNode, +) -> EventSetOrNode: + assert isinstance(input_1, EventSetNode) + assert isinstance(input_2, EventSetNode) + + return RightShiftOperator( + input_1=input_1, + input_2=input_2, + ).outputs["output"] + + +operator_lib.register_operator(BitwiseAndOperator) +operator_lib.register_operator(BitwiseOrOperator) +operator_lib.register_operator(BitwiseXorOperator) +operator_lib.register_operator(LeftShiftOperator) +operator_lib.register_operator(RightShiftOperator) diff --git a/temporian/core/operators/scalar/BUILD b/temporian/core/operators/scalar/BUILD index 3bfe6ab00..5780609b2 100644 --- a/temporian/core/operators/scalar/BUILD +++ b/temporian/core/operators/scalar/BUILD @@ -13,6 +13,7 @@ py_library( deps = [ ":arithmetic_scalar", ":relational_scalar", + ":bitwise_scalar" ], ) @@ -57,3 +58,17 @@ py_library( "//temporian/core/data:schema", ], ) + +py_library( + name = "bitwise_scalar", + srcs = ["bitwise_scalar.py"], + srcs_version = "PY3", + deps = [ + ":base", + "//temporian/core:compilation", + "//temporian/core:operator_lib", + "//temporian/core:typing", + "//temporian/core/data:dtype", + "//temporian/core/data:node", + ], +) diff --git a/temporian/core/operators/scalar/__init__.py b/temporian/core/operators/scalar/__init__.py index 6e576c151..e03411735 100644 --- a/temporian/core/operators/scalar/__init__.py +++ b/temporian/core/operators/scalar/__init__.py @@ -47,3 +47,16 @@ LessEqualScalarOperator, LessScalarOperator, ) + +from temporian.core.operators.scalar.bitwise_scalar import ( + bitwise_and_scalar, + bitwise_or_scalar, + bitwise_xor_scalar, + left_shift_scalar, + right_shift_scalar, + BitwiseAndScalarOperator, + BitwiseOrScalarOperator, + BitwiseXorScalarOperator, + LeftShiftScalarOperator, + RightShiftScalarOperator, +) diff --git a/temporian/core/operators/scalar/bitwise_scalar.py b/temporian/core/operators/scalar/bitwise_scalar.py new file mode 100644 index 000000000..6633167c0 --- /dev/null +++ b/temporian/core/operators/scalar/bitwise_scalar.py @@ -0,0 +1,141 @@ +# Copyright 2021 Google LLC. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Event/scalar arithmetic operators classes and public API definitions.""" + +from typing import Union + +from temporian.core import operator_lib +from temporian.core.compilation import compile +from temporian.core.data.dtype import DType +from temporian.core.data.node import EventSetNode +from temporian.core.operators.scalar.base import ( + BaseScalarOperator, +) +from temporian.core.typing import EventSetOrNode + +class BaseBitwiseScalarOperator(BaseScalarOperator): + DEF_KEY = "" + def __init__( + self, + input: EventSetNode, + value: int, + is_value_first: bool = False, + ): + # Check that all features are Integer + for feature in input.schema.features: + if feature.dtype != DType.INT32 and feature.dtype != DType.INT64: + raise ValueError( + "Bitwise operators only support INT32 or INT64 types, but " + f"feature {feature.name} has dtype {feature.dtype}" + ) + # Check that the value is an integer + if DType.from_python_value(value) not in [DType.INT32, DType.INT64]: + raise ValueError( + "Bitwise operators only support int values, but value" + f" has type {DType.from_python_value(value)}" + ) + super().__init__(input, value, is_value_first) + + +class BitwiseAndScalarOperator(BaseBitwiseScalarOperator): + DEF_KEY = "BITWISE_AND_SCALAR" + + +class BitwiseOrScalarOperator(BaseBitwiseScalarOperator): + DEF_KEY = "BITWISE_OR_SCALAR" + + +class BitwiseXorScalarOperator(BaseBitwiseScalarOperator): + DEF_KEY = "BITWISE_XOR_SCALAR" + + +class LeftShiftScalarOperator(BaseBitwiseScalarOperator): + DEF_KEY = "LEFT_SHIFT_SCALAR" + + +class RightShiftScalarOperator(BaseBitwiseScalarOperator): + DEF_KEY = "RIGHT_SHIFT_SCALAR" + + +@compile +def bitwise_and_scalar( + input: EventSetOrNode, + value: int, +) -> EventSetOrNode: + assert isinstance(input, EventSetNode) + + return BitwiseAndScalarOperator( + input=input, + value=value, + ).outputs["output"] + + +@compile +def bitwise_or_scalar( + input: EventSetOrNode, + value: int, +) -> EventSetOrNode: + assert isinstance(input, EventSetNode) + + return BitwiseOrScalarOperator( + input=input, + value=value, + ).outputs["output"] + + +@compile +def bitwise_xor_scalar( + input: EventSetOrNode, + value: int, +) -> EventSetOrNode: + assert isinstance(input, EventSetNode) + + return BitwiseXorScalarOperator( + input=input, + value=value, + ).outputs["output"] + + +@compile +def left_shift_scalar( + input: EventSetOrNode, + value: int, +) -> EventSetOrNode: + assert isinstance(input, EventSetNode) + + return LeftShiftScalarOperator( + input=input, + value=value, + ).outputs["output"] + + +@compile +def right_shift_scalar( + input: EventSetOrNode, + value: int, +) -> EventSetOrNode: + assert isinstance(input, EventSetNode) + + return RightShiftScalarOperator( + input=input, + value=value, + ).outputs["output"] + + +operator_lib.register_operator(BitwiseAndScalarOperator) +operator_lib.register_operator(BitwiseOrScalarOperator) +operator_lib.register_operator(BitwiseXorScalarOperator) +operator_lib.register_operator(LeftShiftScalarOperator) +operator_lib.register_operator(RightShiftScalarOperator) diff --git a/temporian/core/operators/test/BUILD b/temporian/core/operators/test/BUILD index b872ffa97..1aff8ffb3 100644 --- a/temporian/core/operators/test/BUILD +++ b/temporian/core/operators/test/BUILD @@ -473,15 +473,38 @@ py_test( "//temporian", ], ) - -py_test( - name = "test_filter_empty_index", - srcs = ["test_filter_empty_index.py"], - srcs_version = "PY3", - deps = [ - "//temporian/implementation/numpy/data:io", - # "//temporian/core/data:duration", - "//temporian/test:utils", - ], -) - \ No newline at end of file + +py_test( + name = "test_bitwise", + srcs = ["test_bitwise.py"], + srcs_version = "PY3", + deps = [ + # already_there/absl/testing:absltest + # already_there/absl/testing:parameterized + "//temporian/implementation/numpy/data:io", + "//temporian/test:utils", + ], +) + +py_test( + name = "test_bitwise_scalar", + srcs = ["test_bitwise_scalar.py"], + srcs_version = "PY3", + deps = [ + # already_there/absl/testing:absltest + # already_there/absl/testing:parameterized + "//temporian/implementation/numpy/data:io", + "//temporian/test:utils", + ], +) + +py_test( + name = "test_filter_empty_index", + srcs = ["test_filter_empty_index.py"], + srcs_version = "PY3", + deps = [ + "//temporian/implementation/numpy/data:io", + # "//temporian/core/data:duration", + "//temporian/test:utils", + ], +) diff --git a/temporian/core/operators/test/test_bitwise.py b/temporian/core/operators/test/test_bitwise.py new file mode 100644 index 000000000..b66067bcc --- /dev/null +++ b/temporian/core/operators/test/test_bitwise.py @@ -0,0 +1,99 @@ +# Copyright 2021 Google LLC. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from absl.testing import absltest + +from temporian.implementation.numpy.data.io import event_set +from temporian.test.utils import assertOperatorResult +from temporian.core.operators.binary import ( + bitwise_and, + bitwise_or, + bitwise_xor, + left_shift, + right_shift, +) + + +class BitwiseTest(absltest.TestCase): + """Test bitwise ops: | (OR) & (AND) ^ (XOR) << (LEFT SHIFT) >> (RIGHT SHIFT)""" + + def setUp(self): + self.evset_1 = event_set( + timestamps=[1, 2, 3, 4], + features={"x": [85, 170, 255, 51]}, + ) + self.evset_2 = event_set( + timestamps=[1, 2, 3, 4], + features={"x": [2, 3, 5, 7]}, + same_sampling_as=self.evset_1, + ) + + def test_correct_and(self) -> None: + """Test correct AND operator.""" + expected = event_set( + timestamps=[1, 2, 3, 4], + features={"bitwise_and_x_x": [0, 2, 5, 3]}, + same_sampling_as=self.evset_1, + ) + assertOperatorResult( + self, bitwise_and(self.evset_1, self.evset_2), expected + ) + + def test_correct_or(self) -> None: + """Test correct OR operator.""" + expected = event_set( + timestamps=[1, 2, 3, 4], + features={"bitwise_or_x_x": [87, 171, 255, 55]}, + same_sampling_as=self.evset_1, + ) + assertOperatorResult( + self, bitwise_or(self.evset_1, self.evset_2), expected + ) + + def test_correct_xor(self) -> None: + """Test correct XOR operator.""" + expected = event_set( + timestamps=[1, 2, 3, 4], + features={"bitwise_xor_x_x": [87, 169, 250, 52]}, + same_sampling_as=self.evset_1, + ) + assertOperatorResult( + self, bitwise_xor(self.evset_1, self.evset_2), expected + ) + + def test_correct_left_shift(self) -> None: + """Test correct LEFT SHIFT operator.""" + expected = event_set( + timestamps=[1, 2, 3, 4], + features={"left_shift_x_x": [340, 1360, 8160, 6528]}, + same_sampling_as=self.evset_1, + ) + assertOperatorResult( + self, left_shift(self.evset_1, self.evset_2), expected + ) + + def test_correct_right_shift(self) -> None: + """Test correct RIGHT SHIFT operator.""" + expected = event_set( + timestamps=[1, 2, 3, 4], + features={"right_shift_x_x": [21, 21, 7, 0]}, + same_sampling_as=self.evset_1, + ) + assertOperatorResult( + self, right_shift(self.evset_1, self.evset_2), expected + ) + + +if __name__ == "__main__": + absltest.main() diff --git a/temporian/core/operators/test/test_bitwise_scalar.py b/temporian/core/operators/test/test_bitwise_scalar.py new file mode 100644 index 000000000..da1e43bf8 --- /dev/null +++ b/temporian/core/operators/test/test_bitwise_scalar.py @@ -0,0 +1,109 @@ +# Copyright 2021 Google LLC. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import numpy as np +from absl.testing import absltest +from temporian.implementation.numpy.data.io import event_set +from temporian.test.utils import assertOperatorResult +from temporian.core.operators.scalar import ( + bitwise_and_scalar, + bitwise_or_scalar, + bitwise_xor_scalar, + left_shift_scalar, + right_shift_scalar, +) + + +class ArithmeticScalarTest(absltest.TestCase): + """Test numpy implementation of all arithmetic operators: + addition, subtraction, division and multiplication""" + + def setUp(self): + self.evset = event_set( + timestamps=[1, 2, 3, 4], + features={"x": [2, 5, 7, 9]}, + ) + + def test_correct_and(self) -> None: + """Test correct and operator.""" + value = 3 + expected = event_set( + timestamps=[1, 2, 3, 4], + features={"x": [2, 1, 3, 1]}, + same_sampling_as=self.evset, + ) + + assertOperatorResult( + self, + bitwise_and_scalar(self.evset, value), + expected + ) + + def test_correct_or(self) -> None: + """Test correct or operator.""" + value = 3 + expected = event_set( + timestamps=[1, 2, 3, 4], + features={"x": [3, 7, 7, 11]}, + same_sampling_as=self.evset, + ) + + assertOperatorResult( + self, + bitwise_or_scalar(self.evset, value), + expected + ) + + def test_correct_xor(self) -> None: + """Test correct xor operator.""" + value = 3 + expected = event_set( + timestamps=[1, 2, 3, 4], + features={"x": [1, 6, 4, 10]}, + same_sampling_as=self.evset, + ) + + assertOperatorResult( + self, + bitwise_xor_scalar(self.evset, value), + expected + ) + + def test_correct_left_shift(self) -> None: + """Test correct left shift operator.""" + value = 2 + expected = event_set( + timestamps=[1, 2, 3, 4], + features={"x": [8, 20, 28, 36]}, + same_sampling_as=self.evset, + ) + assertOperatorResult( + self, left_shift_scalar(self.evset, value), expected + ) + + def test_correct_right_shift(self) -> None: + """Test correct right shift operator.""" + value = 1 + expected = event_set( + timestamps=[1, 2, 3, 4], + features={"x": [1, 2, 3, 4]}, + same_sampling_as=self.evset, + ) + assertOperatorResult( + self, right_shift_scalar(self.evset, value), expected + ) + + +if __name__ == "__main__": + absltest.main() diff --git a/temporian/core/operators/test/test_unary.py b/temporian/core/operators/test/test_unary.py index 0abe79bdc..cc1749c65 100644 --- a/temporian/core/operators/test/test_unary.py +++ b/temporian/core/operators/test/test_unary.py @@ -47,11 +47,36 @@ def test_invert_boolean(self) -> None: ) assertOperatorResult(self, ~evset, expected) - def test_error_nonboolean(self) -> None: - """Check that trying to invert a non-boolean raises ValueError""" - evset = event_set(timestamps=[1, 2, 3], features={"f": [1, 2, 3]}) + def test_invert_integer(self) -> None: + """Test inversion of boolean features""" + evset = event_set( + timestamps=[0, 1, 2, 1], + features={ + "store_id": [1, 1, 1, 2], + "product_id": [1, 2, 2, 1], + "int": [2, 5, 7, 9], + "bool": [True, False, True, False], + }, + indexes=["store_id", "product_id"], + ) + expected = event_set( + timestamps=[0, 1, 2, 1], + features={ + "store_id": [1, 1, 1, 2], + "product_id": [1, 2, 2, 1], + "int": [-3, -6, -8, -10], + "bool": [False, True, False, True], + }, + indexes=["store_id", "product_id"], + same_sampling_as=evset, + ) + assertOperatorResult(self, ~evset, expected) + + def test_error_invert_float(self) -> None: + """Check that trying to invert a float raises ValueError""" + evset = event_set(timestamps=[1, 2, 3], features={"f": [1.1, 2.1, 3.4]}) - with self.assertRaisesRegex(ValueError, "bool"): + with self.assertRaisesRegex(ValueError, "float"): _ = ~evset def test_correct_abs(self) -> None: diff --git a/temporian/core/operators/unary.py b/temporian/core/operators/unary.py index dceb5525a..2dbc40d4b 100644 --- a/temporian/core/operators/unary.py +++ b/temporian/core/operators/unary.py @@ -99,11 +99,11 @@ def op_key_definition(cls) -> str: @classmethod def allowed_dtypes(cls) -> List[DType]: - return [DType.BOOLEAN] + return [DType.BOOLEAN, DType.INT32, DType.INT64] @classmethod def get_output_dtype(cls, feature_dtype: DType) -> DType: - return DType.BOOLEAN + return feature_dtype class IsNanOperator(BaseUnaryOperator): diff --git a/temporian/core/test/registered_operators_test.py b/temporian/core/test/registered_operators_test.py index 155790bee..4d371644d 100644 --- a/temporian/core/test/registered_operators_test.py +++ b/temporian/core/test/registered_operators_test.py @@ -34,6 +34,12 @@ def test_base(self): "ARCSIN", "ARCTAN", "BEGIN", + "BITWISE_AND", + "BITWISE_AND_SCALAR", + "BITWISE_OR", + "BITWISE_OR_SCALAR", + "BITWISE_XOR", + "BITWISE_XOR_SCALAR", "CALENDAR_DAY_OF_MONTH", "CALENDAR_DAY_OF_WEEK", "CALENDAR_DAY_OF_YEAR", @@ -69,6 +75,8 @@ def test_base(self): "JOIN", "LAG", "LEAK", + "LEFT_SHIFT", + "LEFT_SHIFT_SCALAR", "LESS", "LESS_EQUAL", "LESS_EQUAL_SCALAR", @@ -95,6 +103,8 @@ def test_base(self): "PROPAGATE", "RENAME", "RESAMPLE", + "RIGHT_SHIFT", + "RIGHT_SHIFT_SCALAR", "SELECT", "SELECT_INDEX_VALUES", "SIMPLE_MOVING_AVERAGE", diff --git a/temporian/implementation/numpy/operators/BUILD b/temporian/implementation/numpy/operators/BUILD index bf3066249..50d305368 100644 --- a/temporian/implementation/numpy/operators/BUILD +++ b/temporian/implementation/numpy/operators/BUILD @@ -43,6 +43,7 @@ py_library( ":where", "//temporian/implementation/numpy/operators/binary:arithmetic", "//temporian/implementation/numpy/operators/binary:logical", + "//temporian/implementation/numpy/operators/binary:bitwise", "//temporian/implementation/numpy/operators/binary:relational", "//temporian/implementation/numpy/operators/calendar:day_of_month", "//temporian/implementation/numpy/operators/calendar:day_of_week", @@ -55,6 +56,7 @@ py_library( "//temporian/implementation/numpy/operators/calendar:year", "//temporian/implementation/numpy/operators/scalar:arithmetic_scalar", "//temporian/implementation/numpy/operators/scalar:relational_scalar", + "//temporian/implementation/numpy/operators/scalar:bitwise_scalar", "//temporian/implementation/numpy/operators/window:moving_count", "//temporian/implementation/numpy/operators/window:moving_max", "//temporian/implementation/numpy/operators/window:moving_min", diff --git a/temporian/implementation/numpy/operators/__init__.py b/temporian/implementation/numpy/operators/__init__.py index d417a2851..be6d574ae 100644 --- a/temporian/implementation/numpy/operators/__init__.py +++ b/temporian/implementation/numpy/operators/__init__.py @@ -35,9 +35,11 @@ from temporian.implementation.numpy.operators.binary import arithmetic from temporian.implementation.numpy.operators.binary import relational from temporian.implementation.numpy.operators.binary import logical +from temporian.implementation.numpy.operators.binary import bitwise from temporian.implementation.numpy.operators.scalar import arithmetic_scalar from temporian.implementation.numpy.operators.scalar import relational_scalar +from temporian.implementation.numpy.operators.scalar import bitwise_scalar from temporian.implementation.numpy.operators.window import simple_moving_average from temporian.implementation.numpy.operators.window import moving_product diff --git a/temporian/implementation/numpy/operators/binary/BUILD b/temporian/implementation/numpy/operators/binary/BUILD index 7199f58f8..4020e371c 100644 --- a/temporian/implementation/numpy/operators/binary/BUILD +++ b/temporian/implementation/numpy/operators/binary/BUILD @@ -14,6 +14,7 @@ py_library( ":arithmetic", ":logical", ":relational", + ":bitwise", ], ) @@ -68,3 +69,16 @@ py_library( "//temporian/implementation/numpy:implementation_lib", ], ) + +py_library( + name = "bitwise", + srcs = ["bitwise.py"], + srcs_version = "PY3", + deps = [ + ":base", + # already_there/numpy + "//temporian/core/data:dtype", + "//temporian/core/operators/binary", + "//temporian/implementation/numpy:implementation_lib", + ], +) diff --git a/temporian/implementation/numpy/operators/binary/__init__.py b/temporian/implementation/numpy/operators/binary/__init__.py index f8868eb37..19de4d2fa 100644 --- a/temporian/implementation/numpy/operators/binary/__init__.py +++ b/temporian/implementation/numpy/operators/binary/__init__.py @@ -21,3 +21,10 @@ LogicalOrNumpyImplementation, LogicalXorNumpyImplementation, ) +from temporian.implementation.numpy.operators.binary.bitwise import ( + BitwiseAndNumpyImplementation, + BitwiseOrNumpyImplementation, + BitwiseXorNumpyImplementation, + LeftShiftNumpyImplementation, + RightShiftNumpyImplementation, +) diff --git a/temporian/implementation/numpy/operators/binary/bitwise.py b/temporian/implementation/numpy/operators/binary/bitwise.py new file mode 100644 index 000000000..4c3147f4d --- /dev/null +++ b/temporian/implementation/numpy/operators/binary/bitwise.py @@ -0,0 +1,119 @@ +# Copyright 2021 Google LLC. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import numpy as np + +from temporian.core.data.dtype import DType +from temporian.implementation.numpy.operators.binary.base import ( + BaseBinaryNumpyImplementation, +) +from temporian.core.operators.binary import ( + BitwiseAndOperator, + BitwiseOrOperator, + BitwiseXorOperator, + LeftShiftOperator, + RightShiftOperator, +) +from temporian.implementation.numpy import implementation_lib + + +class BitwiseAndNumpyImplementation(BaseBinaryNumpyImplementation): + """Numpy implementation of the bitwise AND operator.""" + + def __init__(self, operator: BitwiseAndOperator) -> None: + super().__init__(operator) + + def _do_operation( + self, + evset_1_feature: np.ndarray, + evset_2_feature: np.ndarray, + dtype: DType, + ) -> np.ndarray: + return np.bitwise_and(evset_1_feature.data, evset_2_feature.data) + + +class BitwiseOrNumpyImplementation(BaseBinaryNumpyImplementation): + """Numpy implementation of the bitwise OR operator.""" + + def __init__(self, operator: BitwiseOrOperator) -> None: + super().__init__(operator) + + def _do_operation( + self, + evset_1_feature: np.ndarray, + evset_2_feature: np.ndarray, + dtype: DType, + ) -> np.ndarray: + return np.bitwise_or(evset_1_feature.data, evset_2_feature.data) + + +class BitwiseXorNumpyImplementation(BaseBinaryNumpyImplementation): + """Numpy implementation of the bitwise XOR operator.""" + + def __init__(self, operator: BitwiseXorOperator) -> None: + super().__init__(operator) + + def _do_operation( + self, + evset_1_feature: np.ndarray, + evset_2_feature: np.ndarray, + dtype: DType, + ) -> np.ndarray: + return np.bitwise_xor(evset_1_feature.data, evset_2_feature.data) + + +class LeftShiftNumpyImplementation(BaseBinaryNumpyImplementation): + """Numpy implementation of the Left Shift operator.""" + + def __init__(self, operator: LeftShiftOperator) -> None: + super().__init__(operator) + + def _do_operation( + self, + evset_1_feature: np.ndarray, + evset_2_feature: np.ndarray, + dtype: DType, + ) -> np.ndarray: + return np.left_shift(evset_1_feature.data, evset_2_feature.data) + + +class RightShiftNumpyImplementation(BaseBinaryNumpyImplementation): + """Numpy implementation of the Right Shift operator.""" + + def __init__(self, operator: RightShiftOperator) -> None: + super().__init__(operator) + + def _do_operation( + self, + evset_1_feature: np.ndarray, + evset_2_feature: np.ndarray, + dtype: DType, + ) -> np.ndarray: + return np.right_shift(evset_1_feature.data, evset_2_feature.data) + + +implementation_lib.register_operator_implementation( + BitwiseAndOperator, BitwiseAndNumpyImplementation +) +implementation_lib.register_operator_implementation( + BitwiseOrOperator, BitwiseOrNumpyImplementation +) +implementation_lib.register_operator_implementation( + BitwiseXorOperator, BitwiseXorNumpyImplementation +) +implementation_lib.register_operator_implementation( + LeftShiftOperator, LeftShiftNumpyImplementation +) +implementation_lib.register_operator_implementation( + RightShiftOperator, RightShiftNumpyImplementation +) diff --git a/temporian/implementation/numpy/operators/scalar/BUILD b/temporian/implementation/numpy/operators/scalar/BUILD index 8608f9782..2fd71217c 100644 --- a/temporian/implementation/numpy/operators/scalar/BUILD +++ b/temporian/implementation/numpy/operators/scalar/BUILD @@ -13,6 +13,7 @@ py_library( deps = [ ":arithmetic_scalar", ":relational_scalar", + ":bitwise_scalar", ], ) @@ -54,3 +55,16 @@ py_library( "//temporian/implementation/numpy:implementation_lib", ], ) + +py_library( + name = "bitwise_scalar", + srcs = ["bitwise_scalar.py"], + srcs_version = "PY3", + deps = [ + ":base", + # already_there/numpy + "//temporian/core/data:dtype", + "//temporian/core/operators/scalar", + "//temporian/implementation/numpy:implementation_lib", + ], +) diff --git a/temporian/implementation/numpy/operators/scalar/__init__.py b/temporian/implementation/numpy/operators/scalar/__init__.py index 454b90c63..7bf9447a1 100644 --- a/temporian/implementation/numpy/operators/scalar/__init__.py +++ b/temporian/implementation/numpy/operators/scalar/__init__.py @@ -17,3 +17,11 @@ LessEqualScalarNumpyImplementation, LessScalarNumpyImplementation, ) + +from temporian.implementation.numpy.operators.scalar.bitwise_scalar import ( + BitwiseAndScalarOperator, + BitwiseOrScalarOperator, + BitwiseXorScalarOperator, + LeftShiftScalarOperator, + RightShiftScalarOperator, +) diff --git a/temporian/implementation/numpy/operators/scalar/bitwise_scalar.py b/temporian/implementation/numpy/operators/scalar/bitwise_scalar.py new file mode 100644 index 000000000..1ce88f228 --- /dev/null +++ b/temporian/implementation/numpy/operators/scalar/bitwise_scalar.py @@ -0,0 +1,105 @@ +# Copyright 2021 Google LLC. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import numpy as np + +from temporian.core.data.dtype import DType +from temporian.implementation.numpy.operators.scalar.base import ( + BaseScalarNumpyImplementation, +) +from temporian.core.operators.scalar import ( + BitwiseAndScalarOperator, + BitwiseOrScalarOperator, + BitwiseXorScalarOperator, + LeftShiftScalarOperator, + RightShiftScalarOperator, +) +from temporian.implementation.numpy import implementation_lib + + +class BitwiseAndScalarNumpyImplementation(BaseScalarNumpyImplementation): + """Numpy implementation of the bitwise and scalar operator.""" + + def _do_operation( + self, + feature: np.ndarray, + value: int, + dtype: DType, + ) -> np.ndarray: + return np.bitwise_and(feature.data, value) + + +class BitwiseOrScalarNumpyImplementation(BaseScalarNumpyImplementation): + """Numpy implementation of the bitwise or scalar operator.""" + + def _do_operation( + self, + feature: np.ndarray, + value: int, + dtype: DType, + ) -> np.ndarray: + return np.bitwise_or(feature.data, value) + + +class BitwiseXorScalarNumpyImplementation(BaseScalarNumpyImplementation): + """Numpy implementation of the bitwise xor scalar operator.""" + + def _do_operation( + self, + feature: np.ndarray, + value: int, + dtype: DType, + ) -> np.ndarray: + return np.bitwise_xor(feature.data, value) + + +class LeftScalarNumpyImplementation(BaseScalarNumpyImplementation): + """Numpy implementation of the left shift scalar operator.""" + + def _do_operation( + self, + feature: np.ndarray, + value: int, + dtype: DType, + ) -> np.ndarray: + return np.left_shift(feature.data, value) + + +class RightScalarNumpyImplementation(BaseScalarNumpyImplementation): + """Numpy implementation of the right shift scalar operator.""" + + def _do_operation( + self, + feature: np.ndarray, + value: int, + dtype: DType, + ) -> np.ndarray: + return np.right_shift(feature.data, value) + + +implementation_lib.register_operator_implementation( + BitwiseAndScalarOperator, BitwiseAndScalarNumpyImplementation +) +implementation_lib.register_operator_implementation( + BitwiseOrScalarOperator, BitwiseOrScalarNumpyImplementation +) +implementation_lib.register_operator_implementation( + BitwiseXorScalarOperator, BitwiseXorScalarNumpyImplementation +) +implementation_lib.register_operator_implementation( + LeftShiftScalarOperator, LeftScalarNumpyImplementation +) +implementation_lib.register_operator_implementation( + RightShiftScalarOperator, RightScalarNumpyImplementation +) diff --git a/temporian/implementation/numpy/test/registered_operators_test.py b/temporian/implementation/numpy/test/registered_operators_test.py index 6cb447f16..73bcd235a 100644 --- a/temporian/implementation/numpy/test/registered_operators_test.py +++ b/temporian/implementation/numpy/test/registered_operators_test.py @@ -32,6 +32,12 @@ def test_base(self): "ARCSIN", "ARCTAN", "BEGIN", + "BITWISE_AND", + "BITWISE_AND_SCALAR", + "BITWISE_OR", + "BITWISE_OR_SCALAR", + "BITWISE_XOR", + "BITWISE_XOR_SCALAR", "CALENDAR_DAY_OF_MONTH", "CALENDAR_DAY_OF_WEEK", "CALENDAR_DAY_OF_YEAR", @@ -67,6 +73,8 @@ def test_base(self): "JOIN", "LAG", "LEAK", + "LEFT_SHIFT", + "LEFT_SHIFT_SCALAR", "LESS", "LESS_EQUAL", "LESS_EQUAL_SCALAR", @@ -93,6 +101,8 @@ def test_base(self): "PROPAGATE", "RENAME", "RESAMPLE", + "RIGHT_SHIFT", + "RIGHT_SHIFT_SCALAR", "SELECT", "SELECT_INDEX_VALUES", "SIMPLE_MOVING_AVERAGE",