Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implementing right hand side operations #204

Merged
merged 13 commits into from
Jun 4, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions heat/core/arithmetics.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
__all__ = [
'add',
'div',
'floordiv',
'fmod',
'mod',
'mul',
Expand Down Expand Up @@ -131,6 +132,38 @@ def fmod(t1, t2):
return operations.__binary_op(torch.fmod, t1, t2)


def floordiv(t1, t2):
"""
Element-wise floor division of value of operand t1 by values of operands t2 (i.e. t1 // t2), not commutative.
Takes the two operands (scalar or tensor) whose elements are to be divided (operand 1 by operand 2) as argument.

Parameters
----------
t1: tensor or scalar
The first operand whose values are divided
t2: tensor or scalar
The second operand by whose values is divided

Return
------
result: ht.DNDarray
A tensor containing the results of element-wise floor division (integer values) of t1 by t2.

Examples:
---------
>>> import heat as ht
>>> T1 = ht.float32([[1.7, 2.0], [1.9, 4.2]])
>>> ht.floordiv(T1, 1)
tensor([[1., 2.],
[1., 4.]])
>>> T2 = ht.float32([1.5, 2.5])
>>> ht.floordiv(T1, T2)
tensor([[1., 0.],
[1., 1.]])
"""
return operations.__binary_op(lambda a, b: torch.div(a, b).floor(), t1, t2)


def mod(t1, t2):
"""
Element-wise division remainder of values of operand t1 by values of operand t2 (i.e. t1 % t2), not commutative.
Expand Down
182 changes: 180 additions & 2 deletions heat/core/dndarray.py
Original file line number Diff line number Diff line change
Expand Up @@ -659,6 +659,36 @@ def cpu(self):
self.__array = self.__array.cpu()
return self

def __floordiv__(self, other):
"""
Element-wise floor division (i.e. result is rounded int (floor))
of the tensor by another tensor or scalar. Takes the first tensor by which it divides the second
not-heat-typed-parameter.

Parameters
----------
other: tensor or scalar
The second operand by whose values is divided

Return
------
result: ht.tensor
A tensor containing the results of element-wise floor division (integer values) of t1 by t2.

Examples:
---------
>>> import heat as ht
>>> T1 = ht.float32([[1.7, 2.0], [1.9, 4.2]])
>>> T1 // 1
tensor([[1., 2.],
[1., 4.]])
>>> T2 = ht.float32([1.5, 2.5])
>>> T1 // T2
tensor([[1., 0.],
[1., 1.]])
"""
return arithmetics.floordiv(self, other)

def __eq__(self, other):
"""
Element-wise rich comparison of equality with values from second operand (scalar or tensor)
Expand Down Expand Up @@ -1430,7 +1460,7 @@ def min(self, axis=None, out=None, keepdim=None):
#TODO: out : ht.DNDarray, optional
Alternative output array in which to place the result. Must be of the same shape and buffer length as the
expected output.
#TODO: initial : scalar, optional
#TODO: initial : scalar, optional
The maximum value of an output element. Must be present to allow computation on empty slice.
"""
return statistics.min(self, axis=axis, out=out, keepdim=keepdim)
Expand Down Expand Up @@ -1652,6 +1682,137 @@ def resplit(self, axis=None):

return self

def __rfloordiv__(self, other):
"""
Element-wise floor division (i.e. result is rounded int (floor))
of the not-heat-typed parameter by another tensor. Takes the first operand (scalar or tensor) by which to divide
as argument.

Parameters
----------
other: scalar or unknown data-type
this will be divided by the self-tensor

Return
------
result: ht.tensor
A tensor containing the results of element-wise floor division (integer values) of t1 by t2.

Examples:
---------
>>> import heat as ht
>>> T = ht.float32([[1.7, 2.0], [1.9, 4.2]])
>>> 5 // T
tensor([[2., 2.],
[2., 1.]])
"""
return arithmetics.floordiv(other, self)

def __rmod__(self, other):
"""
Element-wise division remainder of values of other by values of operand self (i.e. other % self),
not commutative.
Takes the two operands (scalar or tensor) whose elements are to be divided (operand 2 by operand 1)
as arguments.

Parameters
----------
other: scalar or unknown data-type
The second operand which values will be divided by self.

Returns
-------
result: ht.tensor
A tensor containing the remainder of the element-wise division of other by self.

Examples:
---------
>>> import heat as ht
>>> T = ht.int32([1, 3])
>>> 2 % T
tensor([0, 2], dtype=torch.int32)

"""
return arithmetics.mod(other, self)

def __rpow__(self, other):
"""
Element-wise exponential function of second operand (not-heat-typed) with values from first operand (tensor).
Takes the first operand (tensor) whose values are the exponent to be applied to the second
scalar or unknown data-type as argument.

Parameters
----------
other: scalar or unknown data-type
The value(s) in the base (element-wise)

Returns
-------
result: ht.NDNarray
A tensor containing the results of element-wise exponential operation.

Examples:
---------
>>> import heat as ht

>>> T = ht.float32([[1, 2], [3, 4]])
>>> 3 ** T
tensor([[ 3., 9.],
[27., 81.]])
"""
return arithmetics.pow(other, self)

def __rsub__(self, other):
"""
Element-wise subtraction of another tensor or a scalar from the tensor.
Takes the first operand (tensor) whose elements are to be subtracted from the second argument
(scalar or unknown data-type).

Parameters
----------
other: scalar or unknown data-type
The value(s) from which the self-tensor will be element wise subtracted.

Returns
-------
result: ht.DNDarray
A tensor containing the results of element-wise subtraction.

Examples:
---------
>>> import heat as ht
>>> T = ht.float32([[1, 2], [3, 4]])
>>> 5 - T
tensor([[4., 3.],
[2., 1.]])
"""
return arithmetics.sub(other, self)

def __rtruediv__(self, other):
"""
Element-wise true division (i.e. result is floating point value rather than rounded int (floor))
of the not-heat-type parameter by another tensor. Takes the first tensor by which it divides the second
not-heat-typed-parameter.

Parameters
----------
other: scalar or unknown data-type
this will be divided by the self-tensor

Returns
-------
result: ht.DNDarray
A tensor containing the results of element-wise division.

Examples:
---------
>>> import heat as ht
>>> T = ht.float32([2,3])
>>> 2 / T
tensor([1.0000, 0.6667])
"""
return arithmetics.div(other, self)

def save(self, path, *args, **kwargs):
"""
Save the tensor's data to disk. Attempts to auto-detect the file format by determining the extension.
Expand Down Expand Up @@ -2002,7 +2163,7 @@ def sum(self, axis=None, out=None, keepdim=None):
all of the elements of the input array. If axis is negative it counts
from the last to the first axis.

If axis is a tuple of ints, a sum is performed on all of the axes specified
If axis is a tuple of ints, a sum is performed on all of the axes specified
in the tuple instead of a single axis or all the axes as before.

Returns
Expand Down Expand Up @@ -2182,3 +2343,20 @@ def __truediv__(self, other):
[1.5, 2.0000]])
"""
return arithmetics.div(self, other)

"""
This ensures that commutative arithmetic operations work no matter on which side the heat-tensor is placed.

Examples
--------
>>> import heat as ht
>>> T = ht.float32([[1., 2.], [3., 4.,]])
>>> T + 1
tensor([[2., 3.],
[4., 5.]])
>>> 1 + T
tensor([[2., 3.],
[4., 5.]])
"""
__radd__ = __add__
__rmul__ = __mul__
37 changes: 37 additions & 0 deletions heat/core/tests/test_arithmetics.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import operator

import torch
import unittest

Expand Down Expand Up @@ -304,3 +306,38 @@ def test_sum(self):
ht.ones((4, 4)).sum(axis=0, out=out_noaxis)
with self.assertRaises(TypeError):
ht.ones(array_len).sum(axis='bad_axis_type')

def test_right_hand_side_operations(self):
"""
This test ensures that for each arithmetic operation (e.g. +, -, *, ...) that is implemented in the tensor
class, it works both ways.

Examples
--------
>>> import heat as ht
>>> T = ht.float32([[1., 2.], [3., 4.]])
>>> assert T * 3 == 3 * T

"""
operators = (
('__add__', operator.add, True),
('__sub__', operator.sub, False),
('__mul__', operator.mul, True),
('__truediv__', operator.truediv, False),
('__floordiv__', operator.floordiv, False),
('__mod__', operator.mod, False),
('__pow__', operator.pow, False)
)
tensor = ht.float32([[1, 4], [2, 3]])
num = 3
for (attr, op, commutative) in operators:
try:
func = tensor.__getattribute__(attr)
except AttributeError:
continue
self.assertTrue(callable(func))
res_1 = op(tensor, num)
res_2 = op(num, tensor)
if commutative:
self.assertTrue(ht.equal(res_1, res_2))
# TODO: Test with split tensors when binary operations are working properly for split tensors