Skip to content

Implementing PUT routine #582

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

Merged
merged 40 commits into from
Oct 19, 2022
Merged
Show file tree
Hide file tree
Changes from 30 commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
d4b6d69
towards implementing put
ipdemes Sep 9, 2022
4dd090a
fixing errors and improving test
ipdemes Sep 12, 2022
fc6ebe7
updating documentation
ipdemes Sep 12, 2022
1d87fd7
code clean-up
ipdemes Sep 12, 2022
15db53e
adding missig pragams for openmp
ipdemes Sep 13, 2022
d01e17c
fixing compile-time errors
ipdemes Sep 13, 2022
48d1920
fixing mypy errors
ipdemes Sep 13, 2022
458f1a2
fixing issue whith converting futures for put + modifying tests
ipdemes Sep 13, 2022
e67d0f3
adding check for repeated entires in indices array
ipdemes Sep 13, 2022
5b8a301
fixing mypy errors
ipdemes Sep 13, 2022
dd5f0a3
Update error message for wrong clip mode
ipdemes Sep 19, 2022
ca965ed
update warning message
ipdemes Sep 19, 2022
6e05573
adding _warn_and_convert function
ipdemes Sep 19, 2022
a821bf6
Merge branch 'put' of github.com:ipdemes/cunumeric into put
ipdemes Sep 19, 2022
9ead87f
fixed formatting error
ipdemes Sep 19, 2022
24cb096
fixing mypy errors
ipdemes Sep 19, 2022
ba0ec13
Merge remote-tracking branch 'origin/branch-22.10' into put
ipdemes Sep 27, 2022
41e8406
addressing PR comments
ipdemes Sep 27, 2022
4814b4c
Avoid emitting new warnings
manopapad Sep 27, 2022
5f64cad
fixing logic for PUT in the case of transformed arrays
ipdemes Sep 27, 2022
d5c4414
_warn_and_convert checks the target type already
manopapad Sep 28, 2022
9a8a3ca
addressing PR comments
ipdemes Sep 28, 2022
c91042a
Typo
manopapad Sep 28, 2022
5d6c9fa
adding check for out-of-the-bounds indices
ipdemes Sep 28, 2022
4ffae77
fixing the case when scalar walue needs to be wrapped
ipdemes Sep 28, 2022
f8af1de
adding bounds check to the cuda kernel
ipdemes Oct 6, 2022
f5b92d3
changing name of a bool variable in _convert_future_to_regionfield me…
ipdemes Oct 6, 2022
c893ee5
fixing the cases for scalar lhs in put operation
ipdemes Oct 7, 2022
9edae62
fixing out of the bounds check for ZIP cuda kernel
ipdemes Oct 7, 2022
7f59b15
fixing logic for negative indices
ipdemes Oct 7, 2022
6a9545f
Merge remote-tracking branch 'origin/branch-22.12' into put
ipdemes Oct 10, 2022
ed60e72
Merge branch 'branch-22.12' into put
manopapad Oct 13, 2022
67aa0ad
Update a leftover use of auto_convert
manopapad Oct 13, 2022
88cca6d
addressing PR comments
ipdemes Oct 14, 2022
92cf417
fixing logic for the bounds check
ipdemes Oct 17, 2022
e489d56
addressing PR comments
ipdemes Oct 18, 2022
d363758
Merge remote-tracking branch 'origin/branch-22.12' into put
ipdemes Oct 18, 2022
58cd174
addressing PR comments
ipdemes Oct 18, 2022
34fc00b
Merge remote-tracking branch 'origin/branch-22.12' into put
ipdemes Oct 19, 2022
be9f556
Merge branch 'branch-22.12' into put
ipdemes Oct 19, 2022
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
79 changes: 67 additions & 12 deletions cunumeric/array.py
Original file line number Diff line number Diff line change
Expand Up @@ -919,12 +919,8 @@ def _convert_key(self, key: Any, first: bool = True) -> Any:
key = convert_to_cunumeric_ndarray(key)
if key.dtype != bool and not np.issubdtype(key.dtype, np.integer):
raise TypeError("index arrays should be int or bool type")
if key.dtype != bool and key.dtype != np.int64:
runtime.warn(
"converting index array to int64 type",
category=RuntimeWarning,
)
key = key.astype(np.int64)
if key.dtype != bool:
key = key._warn_and_convert(np.dtype(np.int64))

return key._thunk

Expand Down Expand Up @@ -2092,12 +2088,8 @@ def compress(
raise ValueError(
"Dimension mismatch: condition must be a 1D array"
)
if condition.dtype != bool:
runtime.warn(
"converting condition to bool type",
category=RuntimeWarning,
)
condition = condition.astype(bool)

condition = condition._warn_and_convert(np.dtype(bool))

if axis is None:
axis = 0
Expand Down Expand Up @@ -2464,6 +2456,59 @@ def diagonal(
raise ValueError("Either axis1/axis2 or axes must be supplied")
return self._diag_helper(offset=offset, axes=axes, extract=extract)

@add_boilerplate("indices", "values")
def put(
self, indices: ndarray, values: ndarray, mode: str = "raise"
) -> None:
"""
Replaces specified elements of the array with given values.

Refer to :func:`cunumeric.put` for full documentation.

See Also
--------
cunumeric.put : equivalent function

Availability
--------
Multiple GPUs, Multiple CPUs

"""

if values.size == 0 or indices.size == 0 or self.size == 0:
return

if mode not in ("raise", "wrap", "clip"):
raise ValueError(
"mode must be one of 'clip', 'raise', or 'wrap' "
f"(got {mode})"
)

if mode == "wrap":
indices = indices % self.size
elif mode == "clip":
indices = indices.clip(0, self.size - 1)

indices = indices._warn_and_convert(np.dtype(np.int64))
values = values._warn_and_convert(self.dtype)

if indices.ndim > 1:
indices = indices.ravel()

if self.shape == ():
if values.shape == ():
v = values
else:
v = values[0]
self._thunk.copy(v._thunk, deep=False)
return

# call _wrap on the values if they need to be wrapped
if values.ndim != indices.ndim or values.size != indices.size:
values = values._wrap(indices.size)

self._thunk.put(indices._thunk, values._thunk)

@add_boilerplate()
def trace(
self,
Expand Down Expand Up @@ -3810,6 +3855,16 @@ def _maybe_convert(self, dtype: np.dtype[Any], hints: Any) -> ndarray:
copy._thunk.convert(self._thunk)
return copy

def _warn_and_convert(self, dtype: np.dtype[Any]) -> ndarray:
if self.dtype != dtype:
runtime.warn(
f"converting array to {dtype} type",
category=RuntimeWarning,
)
return self.astype(dtype)
else:
return self

# For performing normal/broadcast unary operations
@classmethod
def _perform_unary_op(
Expand Down
80 changes: 74 additions & 6 deletions cunumeric/deferred.py
Original file line number Diff line number Diff line change
Expand Up @@ -781,10 +781,16 @@ def _broadcast(self, shape: NdShape) -> Any:

return result

def _convert_future_to_regionfield(self) -> DeferredArray:
def _convert_future_to_regionfield(
self, change_shape: bool = False
) -> DeferredArray:
if change_shape and self.shape == ():
shape: NdShape = (1,)
else:
shape = self.shape
store = self.context.create_store(
self.dtype,
shape=self.shape,
shape=shape,
optimize_scalar=False,
)
thunk_copy = DeferredArray(
Expand Down Expand Up @@ -1660,6 +1666,60 @@ def _diag_helper(

task.execute()

@auto_convert([1, 2])
def put(self, indices: Any, values: Any) -> None:

if indices.base.kind == Future or indices.base.transformed:
change_shape = indices.base.kind == Future
indices = indices._convert_future_to_regionfield(change_shape)
if values.base.kind == Future or values.base.transformed:
change_shape = values.base.kind == Future
values = values._convert_future_to_regionfield(change_shape)

if self.base.kind == Future or self.base.transformed:
change_shape = self.base.kind == Future
self_tmp = self._convert_future_to_regionfield(change_shape)
else:
self_tmp = self

assert indices.size == values.size

# first, we create indirect array with PointN type that
# (indices.size,) shape and is used to copy data from values
# to the target ND array (self)
N = self_tmp.ndim
pointN_dtype = self.runtime.get_point_type(N)
indirect = cast(
DeferredArray,
self.runtime.create_empty_thunk(
shape=indices.shape,
dtype=pointN_dtype,
inputs=[indices],
),
)

shape = self_tmp.shape
task = self.context.create_task(CuNumericOpCode.WRAP)
task.add_output(indirect.base)
task.add_scalar_arg(shape, (ty.int64,))
task.add_scalar_arg(True, bool) # has_input
task.add_input(indices.base)
task.add_alignment(indices.base, indirect.base)
task.throws_exception(IndexError)
task.execute()
if indirect.base.kind == Future:
indirect = indirect._convert_future_to_regionfield()

copy = self.context.create_copy()
copy.set_target_indirect_out_of_range(False)
copy.add_input(values.base)
copy.add_target_indirect(indirect.base)
copy.add_output(self_tmp.base)
copy.execute()

if self_tmp is not self:
self.copy(self_tmp, deep=True)

# Create an identity array with the ones offset from the diagonal by k
def eye(self, k: int) -> None:
assert self.ndim == 2 # Only 2-D arrays should be here
Expand Down Expand Up @@ -2877,8 +2937,13 @@ def unary_op(
args: Any,
multiout: Optional[Any] = None,
) -> None:
lhs = self.base
rhs = src._broadcast(lhs.shape)

if self.shape == () and self.size == src.size:
lhs = self._broadcast(src.shape)
rhs = src.base
else:
lhs = self.base
rhs = src._broadcast(lhs.shape)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I have added this logic for the case when lhs has shape () and rhs has shape (1,). Otherwise we get an error

Copy link
Contributor

@manopapad manopapad Oct 17, 2022

Choose a reason for hiding this comment

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

Is this still required? I just ran the test suite without this change and everything seems to be working.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I don't think it is tested. I will add a test

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Actually, I can't really test it on high level. I will revert the change since we are not using it


task = self.context.create_auto_task(CuNumericOpCode.UNARY_OP)
task.add_output(lhs)
Expand Down Expand Up @@ -3334,9 +3399,11 @@ def unpackbits(
task.execute()

@auto_convert([1])
def _wrap(self, src: Any, new_len: int) -> None:
def _wrap(self, src: DeferredArray, new_len: int) -> None:
src = self.runtime.to_deferred_array(src)
if src.base.kind == Future or src.base.transformed:
src = src._convert_future_to_regionfield()
change_shape = src.base.kind == Future
src = src._convert_future_to_regionfield(change_shape)

# first, we create indirect array with PointN type that
# (len,) shape and is used to copy data from original array
Expand All @@ -3355,6 +3422,7 @@ def _wrap(self, src: Any, new_len: int) -> None:
task = self.context.create_task(CuNumericOpCode.WRAP)
task.add_output(indirect.base)
task.add_scalar_arg(src.shape, (ty.int64,))
task.add_scalar_arg(False, bool) # has_input
task.execute()

copy = self.context.create_copy()
Expand Down
7 changes: 7 additions & 0 deletions cunumeric/eager.py
Original file line number Diff line number Diff line change
Expand Up @@ -619,6 +619,13 @@ def _diag_helper(
axes = tuple(range(ndims - naxes, ndims))
self.array = diagonal_reference(rhs.array, axes)

def put(self, indices: Any, values: Any) -> None:
self.check_eager_args(indices, values)
if self.deferred is not None:
self.deferred.put(indices, values)
else:
np.put(self.array, indices.array, values.array)

def eye(self, k: int) -> None:
if self.deferred is not None:
self.deferred.eye(k)
Expand Down
45 changes: 39 additions & 6 deletions cunumeric/module.py
Original file line number Diff line number Diff line change
Expand Up @@ -2386,12 +2386,7 @@ def repeat(a: ndarray, repeats: Any, axis: Optional[int] = None) -> ndarray:
# repeats is an array
else:
# repeats should be integer type
if repeats.dtype != np.int64:
runtime.warn(
"converting repeats to an integer type",
category=RuntimeWarning,
)
repeats = repeats.astype(np.int64)
repeats = repeats._warn_and_convert(np.int64)
if repeats.shape[0] != array.shape[axis]:
raise ValueError("incorrect shape of repeats array")
result = array._thunk.repeat(
Expand Down Expand Up @@ -3449,6 +3444,44 @@ def diagonal(
)


@add_boilerplate("a", "indices", "values")
def put(
a: ndarray, indices: ndarray, values: ndarray, mode: str = "raise"
) -> None:
"""
Replaces specified elements of an array with given values.
The indexing works as if the target array is first flattened.

Parameters
----------
a : array_like
Array to put data into
indices : array_like
Target indices, interpreted as integers.
WARNING: In case there are repeated entries in the
indices array, Legate doesn't guarantee the order in
which values are updated.

values : array_like
Values to place in `a` at target indices. If values array is shorter
than indices, it will be repeated as necessary.
mode : {'raise', 'wrap', 'clip'}, optional
Specifies how out-of-bounds indices will behave.
'raise' : raise an error.
'wrap' : wrap around.
'clip' : clip to the range.

See Also
--------
numpy.put

Availability
--------
Multiple GPUs, Multiple CPUs
"""
a.put(indices=indices, values=values, mode=mode)


@add_boilerplate("a", "val")
def fill_diagonal(a: ndarray, val: ndarray, wrap: bool = False) -> None:
"""
Expand Down
4 changes: 4 additions & 0 deletions cunumeric/thunk.py
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,10 @@ def _diag_helper(
) -> None:
...

@abstractmethod
def put(self, indices: Any, values: Any) -> None:
...

@abstractmethod
def eye(self, k: int) -> None:
...
Expand Down
1 change: 1 addition & 0 deletions docs/cunumeric/source/api/indexing.rst
Original file line number Diff line number Diff line change
Expand Up @@ -43,5 +43,6 @@ Inserting data into arrays
:toctree: generated/

fill_diagonal
put
put_along_axis
place
16 changes: 9 additions & 7 deletions src/cunumeric/index/wrap.cc
Original file line number Diff line number Diff line change
Expand Up @@ -24,28 +24,30 @@ using namespace legate;

template <int DIM>
struct WrapImplBody<VariantKind::CPU, DIM> {
template <typename IND>
void operator()(const AccessorWO<Point<DIM>, 1>& out,
const Pitches<0>& pitches_out,
const Rect<1>& out_rect,
const Pitches<DIM - 1>& pitches_in,
const Rect<DIM>& in_rect,
const bool dense) const
const bool dense,
const IND& indices) const
{
const int64_t start = out_rect.lo[0];
const int64_t end = out_rect.hi[0];
const auto in_volume = in_rect.volume();
if (dense) {
int64_t out_idx = 0;
auto outptr = out.ptr(out_rect);
auto outptr = out.ptr(out_rect);
for (int64_t i = start; i <= end; i++) {
const int64_t input_idx = i % in_volume;
check_idx(i, in_volume, indices);
const int64_t input_idx = compute_idx(i, in_volume, indices);
auto point = pitches_in.unflatten(input_idx, in_rect.lo);
outptr[out_idx] = point;
out_idx++;
outptr[i - start] = point;
}
} else {
for (int64_t i = start; i <= end; i++) {
const int64_t input_idx = i % in_volume;
check_idx(i, in_volume, indices);
const int64_t input_idx = compute_idx(i, in_volume, indices);
auto point = pitches_in.unflatten(input_idx, in_rect.lo);
out[i] = point;
}
Expand Down
Loading