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 6 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
182 changes: 153 additions & 29 deletions heat/core/tensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -451,41 +451,93 @@ def __truediv__(self, other):
"""
return arithmetics.div(self, other)

def __rtruediv__(self, other):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a technicality, but shouldn't the functions be sorted alphabetically? (at least for the operators before it is the case)
EDIT: This goes for all the operators below

"""
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.tensor
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 __mod__(self, other):
"""
Element-wise division remainder of values of self by values of operand other (i.e. self % other), not commutative.
Takes the two operands (scalar or tensor) whose elements are to be divided (operand 1 by operand 2)
as arguments.
Element-wise division remainder of values of self by values of operand other (i.e. self % other), not commutative.
Takes the two operands (scalar or tensor) whose elements are to be divided (operand 1 by operand 2)
as arguments.

Parameters
----------
other: tensor or scalar
The second operand by whose values it self to be divided.
Parameters
----------
other: tensor or scalar
The second operand by whose values it self to be divided.

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

Examples:
---------
>>> import heat as ht
>>> ht.mod(2, 2)
tensor([0])

>>> T1 = ht.int32([[1, 2], [3, 4]])
>>> T2 = ht.int32([[2, 2], [2, 2]])
>>> T1 % T2
tensor([[1, 0],
[1, 0]], dtype=torch.int32)

>>> s = ht.int32([2])
>>> s % T1
tensor([[0, 0]
[2, 2]], dtype=torch.int32)
"""
Returns
-------
result: ht.tensor
A tensor containing the remainder of the element-wise division of self by other.

Examples:
---------
>>> import heat as ht
>>> ht.mod(2, 2)
tensor([0])

>>> T1 = ht.int32([[1, 2], [3, 4]])
>>> T2 = ht.int32([[2, 2], [2, 2]])
>>> T1 % T2
tensor([[1, 0],
[1, 0]], dtype=torch.int32)

>>> s = ht.int32([2])
>>> s % T1
tensor([[0, 0]
[2, 2]], dtype=torch.int32)
"""
return arithmetics.mod(self, other)

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 __eq__(self, other):
"""
Element-wise rich comparison of equality with values from second operand (scalar or tensor)
Expand Down Expand Up @@ -1014,6 +1066,33 @@ def __pow__(self, other):
"""
return arithmetics.pow(self, other)

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.tensor
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 resplit(self, axis=None):
"""
In-place redistribution of the content of the tensor. Allows to "unsplit" (i.e. gather) all values from all
Expand Down Expand Up @@ -1285,6 +1364,32 @@ def __sub__(self, other):
"""
return arithmetics.sub(self, other)

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.tensor
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 sum(self, axis=None, out=None):
# TODO: Allow also list of axes
"""
Expand Down Expand Up @@ -1484,6 +1589,25 @@ def __setitem__(self, key, value):
raise NotImplementedError(
'Not implemented for {}'.format(value.__class__.__name__))

"""
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__

# __rfloordiv__ // TODO: Implement me when implementing __floordiv__
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please provide both implementations (regular and right-hand side floor-div). Straight forward by adapting div (torch supports truediv as well)



def __factory(shape, dtype, split, local_factory, device, comm):
"""
Expand Down
36 changes: 36 additions & 0 deletions heat/core/tests/test_tensor.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 @@ -735,6 +737,40 @@ def test_empty_like(self):
with self.assertRaises(TypeError):
ht.empty_like(ones, split='axis')

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]])
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need test cases here for split tensors

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is related to #167, which needs to be fixed first
At the moment, binary operations do not work properly for split tensors.

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))

def test_eye(self):

def get_offset(tensor_array):
Expand Down