Skip to content

Implemented dpctl.tensor.linspace per array API #875

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 3 commits into from
Aug 6, 2022
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
2 changes: 2 additions & 0 deletions dpctl/tensor/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
empty_like,
full,
full_like,
linspace,
ones,
ones_like,
zeros,
Expand Down Expand Up @@ -61,6 +62,7 @@
"zeros",
"ones",
"full",
"linspace",
"empty_like",
"zeros_like",
"ones_like",
Expand Down
130 changes: 109 additions & 21 deletions dpctl/tensor/_ctors.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.

import operator

import numpy as np

import dpctl
Expand Down Expand Up @@ -196,9 +198,12 @@ def _asarray_from_numpy_ndarray(
raise TypeError(f"Expected numpy.ndarray, got {type(ary)}")
if usm_type is None:
usm_type = "device"
if dtype is None:
dtype = ary.dtype
copy_q = normalize_queue_device(sycl_queue=None, device=sycl_queue)
if dtype is None:
ary_dtype = ary.dtype
dtype = _get_dtype(dtype, copy_q, ref_type=ary_dtype)
if dtype.itemsize > ary_dtype.itemsize:
dtype = ary_dtype
f_contig = ary.flags["F"]
c_contig = ary.flags["C"]
fc_contig = f_contig or c_contig
Expand Down Expand Up @@ -292,7 +297,7 @@ def asarray(
for output array allocation and copying. `sycl_queue` and `device`
are exclusive keywords, i.e. use one or another. If both are
specified, a `TypeError` is raised unless both imply the same
underlying SYCL queue to be used. If both a `None`, the
underlying SYCL queue to be used. If both are `None`, the
`dpctl.SyclQueue()` is used for allocation and copying.
Default: `None`.
"""
Expand Down Expand Up @@ -430,7 +435,7 @@ def empty(
for output array allocation and copying. `sycl_queue` and `device`
are exclusive keywords, i.e. use one or another. If both are
specified, a `TypeError` is raised unless both imply the same
underlying SYCL queue to be used. If both a `None`, the
underlying SYCL queue to be used. If both are `None`, the
`dpctl.SyclQueue()` is used for allocation and copying.
Default: `None`.
"""
Expand All @@ -453,20 +458,20 @@ def empty(
return res


def _coerce_and_infer_dt(*args, dt):
def _coerce_and_infer_dt(*args, dt, sycl_queue, err_msg, allow_bool=False):
"Deduce arange type from sequence spec"
nd, seq_dt, d = _array_info_sequence(args)
if d != _host_set or nd != (len(args),):
raise ValueError("start, stop and step must be Python scalars")
if dt is None:
dt = seq_dt
dt = np.dtype(dt)
raise ValueError(err_msg)
dt = _get_dtype(dt, sycl_queue, ref_type=seq_dt)
if np.issubdtype(dt, np.integer):
return tuple(int(v) for v in args), dt
elif np.issubdtype(dt, np.floating):
return tuple(float(v) for v in args), dt
elif np.issubdtype(dt, np.complexfloating):
return tuple(complex(v) for v in args), dt
elif allow_bool and dt.char == "?":
return tuple(bool(v) for v in args), dt
else:
raise ValueError(f"Data type {dt} is not supported")

Expand Down Expand Up @@ -519,27 +524,31 @@ def arange(
for output array allocation and copying. `sycl_queue` and `device`
are exclusive keywords, i.e. use one or another. If both are
specified, a `TypeError` is raised unless both imply the same
underlying SYCL queue to be used. If both a `None`, the
underlying SYCL queue to be used. If both are `None`, the
`dpctl.SyclQueue()` is used for allocation and copying.
Default: `None`.
"""
if stop is None:
stop = start
start = 0
(
dpctl.utils.validate_usm_type(usm_type, allow_none=False)
sycl_queue = normalize_queue_device(sycl_queue=sycl_queue, device=device)
(start, stop, step,), dt = _coerce_and_infer_dt(
start,
stop,
step,
), dt = _coerce_and_infer_dt(start, stop, step, dt=dtype)
dt=dtype,
sycl_queue=sycl_queue,
err_msg="start, stop, and step must be Python scalars",
allow_bool=False,
)
try:
tmp = _get_arange_length(start, stop, step)
sh = int(tmp)
if sh < 0:
sh = 0
except TypeError:
sh = 0
dpctl.utils.validate_usm_type(usm_type, allow_none=False)
sycl_queue = normalize_queue_device(sycl_queue=sycl_queue, device=device)
res = dpt.usm_ndarray(
(sh,),
dtype=dt,
Expand Down Expand Up @@ -579,7 +588,7 @@ def zeros(
for output array allocation and copying. `sycl_queue` and `device`
are exclusive keywords, i.e. use one or another. If both are
specified, a `TypeError` is raised unless both imply the same
underlying SYCL queue to be used. If both a `None`, the
underlying SYCL queue to be used. If both are `None`, the
`dpctl.SyclQueue()` is used for allocation and copying.
Default: `None`.
"""
Expand Down Expand Up @@ -627,7 +636,7 @@ def ones(
for output array allocation and copying. `sycl_queue` and `device`
are exclusive keywords, i.e. use one or another. If both are
specified, a `TypeError` is raised unless both imply the same
underlying SYCL queue to be used. If both a `None`, the
underlying SYCL queue to be used. If both are `None`, the
`dpctl.SyclQueue()` is used for allocation and copying.
Default: `None`.
"""
Expand Down Expand Up @@ -683,7 +692,7 @@ def full(
for output array allocation and copying. `sycl_queue` and `device`
are exclusive keywords, i.e. use one or another. If both are
specified, a `TypeError` is raised unless both imply the same
underlying SYCL queue to be used. If both a `None`, the
underlying SYCL queue to be used. If both are `None`, the
`dpctl.SyclQueue()` is used for allocation and copying.
Default: `None`.
"""
Expand Down Expand Up @@ -733,7 +742,7 @@ def empty_like(
for output array allocation and copying. `sycl_queue` and `device`
are exclusive keywords, i.e. use one or another. If both are
specified, a `TypeError` is raised unless both imply the same
underlying SYCL queue to be used. If both a `None`, the
underlying SYCL queue to be used. If both are `None`, the
`dpctl.SyclQueue()` is used for allocation and copying.
Default: `None`.
"""
Expand Down Expand Up @@ -790,7 +799,7 @@ def zeros_like(
for output array allocation and copying. `sycl_queue` and `device`
are exclusive keywords, i.e. use one or another. If both are
specified, a `TypeError` is raised unless both imply the same
underlying SYCL queue to be used. If both a `None`, the
underlying SYCL queue to be used. If both are `None`, the
`dpctl.SyclQueue()` is used for allocation and copying.
Default: `None`.
"""
Expand Down Expand Up @@ -847,7 +856,7 @@ def ones_like(
for output array allocation and copying. `sycl_queue` and `device`
are exclusive keywords, i.e. use one or another. If both are
specified, a `TypeError` is raised unless both imply the same
underlying SYCL queue to be used. If both a `None`, the
underlying SYCL queue to be used. If both are `None`, the
`dpctl.SyclQueue()` is used for allocation and copying.
Default: `None`.
"""
Expand Down Expand Up @@ -911,7 +920,7 @@ def full_like(
for output array allocation and copying. `sycl_queue` and `device`
are exclusive keywords, i.e. use one or another. If both are
specified, a `TypeError` is raised unless both imply the same
underlying SYCL queue to be used. If both a `None`, the
underlying SYCL queue to be used. If both are `None`, the
`dpctl.SyclQueue()` is used for allocation and copying.
Default: `None`.
"""
Expand Down Expand Up @@ -942,3 +951,82 @@ def full_like(
usm_type=usm_type,
sycl_queue=sycl_queue,
)


def linspace(
start,
stop,
/,
num,
*,
dtype=None,
device=None,
endpoint=True,
sycl_queue=None,
usm_type="device",
):
"""
linspace(start, stop, num, dtype=None, device=None, endpoint=True,
sycl_queue=None, usm_type=None): usm_ndarray

Returns evenly spaced numbers of specified interval.

Args:
start: the start of the interval.
stop: the end of the interval. If the `endpoint` is `False`, the
function must generate `num+1` evenly spaced points starting
with `start` and ending with `stop` and exclude the `stop`
from the returned array such that the returned array consists
of evenly spaced numbers over the half-open interval
`[start, stop)`. If `endpoint` is `True`, the output
array must consist of evenly spaced numbers over the closed
interval `[start, stop]`. Default: `True`.
num: number of samples. Must be a non-negative integer; otherwise,
the function must raise an exception.
dtype: output array data type. Should be a floating data type.
If `dtype` is `None`, the output array must be the default
floating point data type. Default: `None`.
device (optional): array API concept of device where the output array
is created. `device` can be `None`, a oneAPI filter selector string,
an instance of :class:`dpctl.SyclDevice` corresponding to a
non-partitioned SYCL device, an instance of
:class:`dpctl.SyclQueue`, or a `Device` object returnedby
`dpctl.tensor.usm_array.device`. Default: `None`.
usm_type ("device"|"shared"|"host", optional): The type of SYCL USM
allocation for the output array. Default: `"device"`.
sycl_queue (:class:`dpctl.SyclQueue`, optional): The SYCL queue to use
for output array allocation and copying. `sycl_queue` and `device`
are exclusive keywords, i.e. use one or another. If both are
specified, a `TypeError` is raised unless both imply the same
underlying SYCL queue to be used. If both are `None`, the
`dpctl.SyclQueue()` is used for allocation and copying.
Default: `None`.
endpoint: boolean indicating whether to include `stop` in the
interval. Default: `True`.
"""
sycl_queue = normalize_queue_device(sycl_queue=sycl_queue, device=device)
dpctl.utils.validate_usm_type(usm_type, allow_none=False)
if endpoint not in [True, False]:
raise TypeError("endpoint keyword argument must be of boolean type")
num = operator.index(num)
if num < 0:
raise ValueError("Number of points must be non-negative")
((start, stop,), dt) = _coerce_and_infer_dt(
start,
stop,
dt=dtype,
sycl_queue=sycl_queue,
err_msg="start and stop must be Python scalars.",
allow_bool=True,
)
if dtype is None and np.issubdtype(dt, np.integer):
dt = ti.default_device_fp_type(sycl_queue)
dt = np.dtype(dt)
start = float(start)
stop = float(stop)
res = dpt.empty(num, dtype=dt, sycl_queue=sycl_queue)
hev, _ = ti._linspace_affine(
start, stop, dst=res, include_endpoint=endpoint, sycl_queue=sycl_queue
)
hev.wait()
return res
28 changes: 28 additions & 0 deletions dpctl/tests/test_usm_ndarray_ctor.py
Original file line number Diff line number Diff line change
Expand Up @@ -1071,6 +1071,34 @@ def test_arange_fp():
assert dpt.arange(0, 1, 0.25, dtype="f4", device=q).shape == (4,)


@pytest.mark.parametrize(
"dt",
_all_dtypes,
)
def test_linspace(dt):
try:
q = dpctl.SyclQueue()
except dpctl.SyclQueueCreationError:
pytest.skip("Default queue could not be created")
X = dpt.linspace(0, 1, num=2, dtype=dt, sycl_queue=q)
assert np.allclose(dpt.asnumpy(X), np.linspace(0, 1, num=2, dtype=dt))


def test_linspace_fp():
try:
q = dpctl.SyclQueue()
except dpctl.SyclQueueCreationError:
pytest.skip("Default queue could not be created")
n = 16
X = dpt.linspace(0, n - 1, num=n, sycl_queue=q)
if q.sycl_device.has_aspect_fp64:
assert X.dtype == np.dtype("float64")
else:
assert X.dtype == np.dtype("float32")
assert X.shape == (n,)
assert X.strides == (1,)


@pytest.mark.parametrize(
"dt",
_all_dtypes,
Expand Down