From 35aba718fa1ea07293df5ddfe00b04d3be2a09cb Mon Sep 17 00:00:00 2001 From: Andrew DalPino Date: Tue, 1 Sep 2026 09:41:04 -0500 Subject: [PATCH 1/6] Intial commit --- numpower.c | 50 +++++++++++++ tests/types/029-astype-dtype-change.phpt | 75 ++++++++++++++++++++ tests/types/030-astype-dtype-change-gpu.phpt | 41 +++++++++++ 3 files changed, 166 insertions(+) create mode 100644 tests/types/029-astype-dtype-change.phpt create mode 100644 tests/types/030-astype-dtype-change-gpu.phpt diff --git a/numpower.c b/numpower.c index f2e7507..3d31fb3 100644 --- a/numpower.c +++ b/numpower.c @@ -297,6 +297,55 @@ PHP_METHOD(NDArray, gpu) { #endif } +ZEND_BEGIN_ARG_INFO(arginfo_astype, 0) + ZEND_ARG_INFO(0, dtype) +ZEND_END_ARG_INFO(); +/** + * @brief Casts the NDArray to a new data type, returning a new NDArray. + * + * ``` + * astype(string $dtype): NDArray + * ``` + * + * The returned NDArray preserves the source device (CPU or GPU); the + * original array is left unchanged. The dtype is one of the supported + * types: float4, float8, float16, float32, float64, float128, int8, uint8, + * int16, uint16, int32, uint32, int64, uint64. + * + * @param dtype Target dtype alias. + * + * @throws \Error If the dtype is unknown. + */ +PHP_METHOD(NDArray, astype) { + char *dtype; + size_t dtypeLen = 0; + zval *obj_zval = getThis(); + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_STRING(dtype, dtypeLen) + ZEND_PARSE_PARAMETERS_END(); + + NDArray *ndarray = NDArrayFactory_restoreFromZval(obj_zval); + if (ndarray == NULL) { + return; + } + + const char *canonical = type_canonicalize(dtype); + if (canonical == NULL) { + zend_throw_error(NULL, + "Invalid data type '%s'. Supported: float4, float8, float16, " + "float32, float64, float128, int8, uint8, int16, uint16, " + "int32, uint32, int64, uint64", dtype); + return; + } + + NDArray *rtn = NDArray_AsType(ndarray, canonical); + if (rtn == NULL) { + return; + } + ndarray_install_object(rtn, return_value); +} + /** * @brief Fills the NDArray with a specified value. * @@ -7422,6 +7471,7 @@ static const zend_function_entry class_NDArray_methods[] = { ZEND_ME(NDArray, gpu, arginfo_gpu, ZEND_ACC_PUBLIC) ZEND_ME(NDArray, cpu, arginfo_cpu, ZEND_ACC_PUBLIC) ZEND_ME(NDArray, isGPU, arginfo_is_gpu, ZEND_ACC_PUBLIC) + ZEND_ME(NDArray, astype, arginfo_astype, ZEND_ACC_PUBLIC) ZEND_ME(NDArray, size, arginfo_size, ZEND_ACC_PUBLIC) ZEND_ME(NDArray, count, arginfo_count, ZEND_ACC_PUBLIC) diff --git a/tests/types/029-astype-dtype-change.phpt b/tests/types/029-astype-dtype-change.phpt new file mode 100644 index 0000000..b6f770c --- /dev/null +++ b/tests/types/029-astype-dtype-change.phpt @@ -0,0 +1,75 @@ +--TEST-- +NDArray::astype() returns a new NDArray with the target dtype, preserving device +--FILE-- + int32: values truncate, elements become ints. + Capture the original's values BEFORE the cast for an "unchanged" check. */ +$a = new NDArray([1.5, 2.5, 3.7], 'float32'); +$original_vals = $a->toArray(); +$r = $a->astype('int32'); +$php = $r->toArray(); +$ok = $r instanceof NDArray && $php === [1, 2, 3] + && array_map('gettype', $php) === ['integer', 'integer', 'integer']; +echo 'float32->int32: ', ($ok ? 'OK' : 'BAD'), ' ', json_encode($php), "\n"; + +/* 2. int32 -> float64: elements become floats, values preserved */ +$b = new NDArray([1, 2, 3], 'int32'); +$r = $b->astype('float64'); +$php = $r->toArray(); +$ok = $r instanceof NDArray && $php === [1.0, 2.0, 3.0] + && array_map('gettype', $php) === ['double', 'double', 'double']; +echo 'int32->float64: ', ($ok ? 'OK' : 'BAD'), ' ', json_encode($php), "\n"; + +/* 3. original array is unchanged by the cast (still the same float32 values) */ +$ok = $a->toArray() === $original_vals; +echo 'original-unchanged: ', ($ok ? 'OK' : 'BAD'), ' ', json_encode($a->toArray()), "\n"; + +/* 4. device preserved: a CPU array stays CPU after astype */ +$c = new NDArray([1.0, 2.0, 3.0], 'float32'); +$rc = $c->astype('float64'); +$ok = $rc instanceof NDArray && !$rc->isGPU() && $rc->toArray() === [1.0, 2.0, 3.0]; +echo 'device-stays-CPU: ', ($ok ? 'OK' : 'BAD'), ' isGPU=', ($rc->isGPU() ? 1 : 0), "\n"; + +/* 5. same-dtype cast returns a new, equal array */ +$d = new NDArray([4, 5, 6], 'int32'); +$rd = $d->astype('int32'); +$ok = ($rd !== $d) && $rd->toArray() === [4, 5, 6] && !$rd->isGPU(); +echo 'same-dtype-copy: ', ($ok ? 'OK' : 'BAD'), "\n"; + +/* 6. 0-D scalar (shape []) stays an NDArray after astype; __toString is "7\n" */ +$e = new NDArray(7.0); +$re = $e->astype('int32'); +$ok = $re instanceof NDArray && $re->shape() === [] && trim((string)$re) === '7'; +echo 'zero-d: ', ($ok ? 'OK' : 'BAD'), ' shape=', json_encode($re->shape()), "\n"; + +/* 7. unknown dtype throws, message matches the canonical list */ +try { + (new NDArray([1, 2, 3], 'float32'))->astype('badtype'); + echo "badtype: NO-THROW\n"; +} catch (Throwable $t) { + echo "badtype threw: ", get_class($t), " | ", $t->getMessage(), "\n"; +} + +/* 8. wrong argument count rejects */ +try { + (new NDArray([1, 2, 3], 'float32'))->astype(); + echo "no-arg: NO-THROW\n"; +} catch (Throwable $t) { + echo "no-arg threw: ", get_class($t), "\n"; +} +?> +--EXPECT-- +float32->int32: OK [1,2,3] +int32->float64: OK [1,2,3] +original-unchanged: OK [1.5,2.5,3.700000047683716] +device-stays-CPU: OK isGPU=0 +same-dtype-copy: OK +zero-d: OK shape=[] +badtype threw: Error | Invalid data type 'badtype'. Supported: float4, float8, float16, float32, float64, float128, int8, uint8, int16, uint16, int32, uint32, int64, uint64 +no-arg threw: ArgumentCountError diff --git a/tests/types/030-astype-dtype-change-gpu.phpt b/tests/types/030-astype-dtype-change-gpu.phpt new file mode 100644 index 0000000..7fc5b8f --- /dev/null +++ b/tests/types/030-astype-dtype-change-gpu.phpt @@ -0,0 +1,41 @@ +--TEST-- +NDArray::astype() on a GPU array keeps the array on GPU and converts values +--SKIPIF-- +gpu(); } catch (Error $e) { die('skip ' . $e->getMessage()); } ?> +--FILE-- +cpu() to inspect values. */ + +$cases = [ + 'float32' => ['int32', [1.5, 2.5, 3.5], [1, 2, 3]], + 'int32' => ['float64', [1, 2, 3], [1.0, 2.0, 3.0]], + 'float64' => ['float32', [1.5, 2.5, 0.5], [1.5, 2.5, 0.5]], + 'int32' => ['int16', [1, 2, 3], [1, 2, 3]], +]; + +foreach ($cases as $src => [$dst, $vals, $expect]) { + $g = (new NDArray($vals, $src))->gpu(); + $r = $g->astype($dst); + $on_gpu = $r->isGPU(); + $back = $r->cpu()->toArray(); + $ok = ($r instanceof NDArray) && $on_gpu && $back === $expect; + echo "$src->$dst: ", ($ok ? 'OK' : 'BAD'), + ' isGPU=', ($r->isGPU() ? 1 : 0), + ' vals=', json_encode($back), "\n"; +} + +/* unknown dtype on GPU throws too */ +try { + (new NDArray([1.0], 'float32'))->gpu()->astype('nope'); + echo "badtype: NO-THROW\n"; +} catch (Throwable $t) { + echo "badtype threw: ", get_class($t), "\n"; +} +?> +--EXPECT-- +float32->int32: OK isGPU=1 vals=[1,2,3] +int32->float64: OK isGPU=1 vals=[1,2,3] +float64->float32: OK isGPU=1 vals=[1.5,2.5,0.5] +int32->int16: OK isGPU=1 vals=[1,2,3] +badtype threw: Error From b335d3e9dfac5a7934044c3b6d5838015f282c6d Mon Sep 17 00:00:00 2001 From: Andrew DalPino Date: Tue, 1 Sep 2026 09:54:45 -0500 Subject: [PATCH 2/6] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- tests/types/030-astype-dtype-change-gpu.phpt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/types/030-astype-dtype-change-gpu.phpt b/tests/types/030-astype-dtype-change-gpu.phpt index 7fc5b8f..5b3983d 100644 --- a/tests/types/030-astype-dtype-change-gpu.phpt +++ b/tests/types/030-astype-dtype-change-gpu.phpt @@ -8,13 +8,13 @@ NDArray::astype() on a GPU array keeps the array on GPU and converts values GPU and convert the values. Cast-back via ->cpu() to inspect values. */ $cases = [ - 'float32' => ['int32', [1.5, 2.5, 3.5], [1, 2, 3]], - 'int32' => ['float64', [1, 2, 3], [1.0, 2.0, 3.0]], - 'float64' => ['float32', [1.5, 2.5, 0.5], [1.5, 2.5, 0.5]], - 'int32' => ['int16', [1, 2, 3], [1, 2, 3]], + ['float32', 'int32', [1.5, 2.5, 3.5], [1, 2, 3]], + ['int32', 'float64', [1, 2, 3], [1.0, 2.0, 3.0]], + ['float64', 'float32', [1.5, 2.5, 0.5], [1.5, 2.5, 0.5]], + ['int32', 'int16', [1, 2, 3], [1, 2, 3]], ]; -foreach ($cases as $src => [$dst, $vals, $expect]) { +foreach ($cases as [$src, $dst, $vals, $expect]) { $g = (new NDArray($vals, $src))->gpu(); $r = $g->astype($dst); $on_gpu = $r->isGPU(); From d05d3f13bb485df292cdaa125aae8135c1b5d41c Mon Sep 17 00:00:00 2001 From: Andrew DalPino Date: Tue, 1 Sep 2026 09:55:18 -0500 Subject: [PATCH 3/6] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- numpower.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/numpower.c b/numpower.c index 3d31fb3..a622369 100644 --- a/numpower.c +++ b/numpower.c @@ -297,7 +297,7 @@ PHP_METHOD(NDArray, gpu) { #endif } -ZEND_BEGIN_ARG_INFO(arginfo_astype, 0) +ZEND_BEGIN_ARG_INFO(arginfo_astype, 1) ZEND_ARG_INFO(0, dtype) ZEND_END_ARG_INFO(); /** From ec48e9968a0c595b05754bce13b0223c8098bf29 Mon Sep 17 00:00:00 2001 From: Andrew DalPino Date: Tue, 1 Sep 2026 10:38:32 -0500 Subject: [PATCH 4/6] Modify buffer in place --- numpower.c | 44 +++++++--- tests/types/029-astype-dtype-change.phpt | 75 ----------------- tests/types/029-settype-dtype-change.phpt | 81 +++++++++++++++++++ tests/types/030-astype-dtype-change-gpu.phpt | 41 ---------- tests/types/030-settype-dtype-change-gpu.phpt | 46 +++++++++++ 5 files changed, 159 insertions(+), 128 deletions(-) delete mode 100644 tests/types/029-astype-dtype-change.phpt create mode 100644 tests/types/029-settype-dtype-change.phpt delete mode 100644 tests/types/030-astype-dtype-change-gpu.phpt create mode 100644 tests/types/030-settype-dtype-change-gpu.phpt diff --git a/numpower.c b/numpower.c index a622369..d2e38c9 100644 --- a/numpower.c +++ b/numpower.c @@ -297,26 +297,30 @@ PHP_METHOD(NDArray, gpu) { #endif } -ZEND_BEGIN_ARG_INFO(arginfo_astype, 1) +ZEND_BEGIN_ARG_INFO(arginfo_settype, 0) ZEND_ARG_INFO(0, dtype) ZEND_END_ARG_INFO(); /** - * @brief Casts the NDArray to a new data type, returning a new NDArray. + * @brief Sets the data type of this NDArray in place. * * ``` - * astype(string $dtype): NDArray + * setType(string $dtype): void * ``` * - * The returned NDArray preserves the source device (CPU or GPU); the - * original array is left unchanged. The dtype is one of the supported - * types: float4, float8, float16, float32, float64, float128, int8, uint8, - * int16, uint16, int32, uint32, int64, uint64. + * Re-casts the array to the requested dtype, releasing the previous + * storage (unless still referenced by a live slice). This method mutates + * `$this` and returns nothing, consistent with the other in-place method + * `fill()`. The device (CPU or GPU) is preserved. If the target dtype + * equals the current dtype, the call is a true no-op with no allocation. + * + * Supported dtypes: float4, float8, float16, float32, float64, float128, + * int8, uint8, int16, uint16, int32, uint32, int64, uint64. * * @param dtype Target dtype alias. * * @throws \Error If the dtype is unknown. */ -PHP_METHOD(NDArray, astype) { +PHP_METHOD(NDArray, setType) { char *dtype; size_t dtypeLen = 0; zval *obj_zval = getThis(); @@ -339,11 +343,27 @@ PHP_METHOD(NDArray, astype) { return; } - NDArray *rtn = NDArray_AsType(ndarray, canonical); - if (rtn == NULL) { + /* Fast path: already this dtype — true no-op, no allocation, no swap. */ + if (is_type(NDArray_TYPE(ndarray), canonical)) { return; } - ndarray_install_object(rtn, return_value); + + /* NDArray_AsType always returns a fresh, caller-owned, unbuffered + NDArray; the source `ndarray` is left intact. We then swap the new + array into the same buffer slot (preserving the object's uuid) and + release the previous occupant. NDArray_FREE is refcount-aware: if a + live slice still references the old array, its data survives until + the last view is freed. */ + NDArray *casted = NDArray_AsType(ndarray, canonical); + if (casted == NULL) { + return; /* Original NDArray remains untouched. */ + } + + int uuid = NDArray_UUID(ndarray); + NDArray *prev = buffer_replace(uuid, casted); + if (prev) { + NDArray_FREE(prev); + } } /** @@ -7471,7 +7491,7 @@ static const zend_function_entry class_NDArray_methods[] = { ZEND_ME(NDArray, gpu, arginfo_gpu, ZEND_ACC_PUBLIC) ZEND_ME(NDArray, cpu, arginfo_cpu, ZEND_ACC_PUBLIC) ZEND_ME(NDArray, isGPU, arginfo_is_gpu, ZEND_ACC_PUBLIC) - ZEND_ME(NDArray, astype, arginfo_astype, ZEND_ACC_PUBLIC) + ZEND_ME(NDArray, setType, arginfo_settype, ZEND_ACC_PUBLIC) ZEND_ME(NDArray, size, arginfo_size, ZEND_ACC_PUBLIC) ZEND_ME(NDArray, count, arginfo_count, ZEND_ACC_PUBLIC) diff --git a/tests/types/029-astype-dtype-change.phpt b/tests/types/029-astype-dtype-change.phpt deleted file mode 100644 index b6f770c..0000000 --- a/tests/types/029-astype-dtype-change.phpt +++ /dev/null @@ -1,75 +0,0 @@ ---TEST-- -NDArray::astype() returns a new NDArray with the target dtype, preserving device ---FILE-- - int32: values truncate, elements become ints. - Capture the original's values BEFORE the cast for an "unchanged" check. */ -$a = new NDArray([1.5, 2.5, 3.7], 'float32'); -$original_vals = $a->toArray(); -$r = $a->astype('int32'); -$php = $r->toArray(); -$ok = $r instanceof NDArray && $php === [1, 2, 3] - && array_map('gettype', $php) === ['integer', 'integer', 'integer']; -echo 'float32->int32: ', ($ok ? 'OK' : 'BAD'), ' ', json_encode($php), "\n"; - -/* 2. int32 -> float64: elements become floats, values preserved */ -$b = new NDArray([1, 2, 3], 'int32'); -$r = $b->astype('float64'); -$php = $r->toArray(); -$ok = $r instanceof NDArray && $php === [1.0, 2.0, 3.0] - && array_map('gettype', $php) === ['double', 'double', 'double']; -echo 'int32->float64: ', ($ok ? 'OK' : 'BAD'), ' ', json_encode($php), "\n"; - -/* 3. original array is unchanged by the cast (still the same float32 values) */ -$ok = $a->toArray() === $original_vals; -echo 'original-unchanged: ', ($ok ? 'OK' : 'BAD'), ' ', json_encode($a->toArray()), "\n"; - -/* 4. device preserved: a CPU array stays CPU after astype */ -$c = new NDArray([1.0, 2.0, 3.0], 'float32'); -$rc = $c->astype('float64'); -$ok = $rc instanceof NDArray && !$rc->isGPU() && $rc->toArray() === [1.0, 2.0, 3.0]; -echo 'device-stays-CPU: ', ($ok ? 'OK' : 'BAD'), ' isGPU=', ($rc->isGPU() ? 1 : 0), "\n"; - -/* 5. same-dtype cast returns a new, equal array */ -$d = new NDArray([4, 5, 6], 'int32'); -$rd = $d->astype('int32'); -$ok = ($rd !== $d) && $rd->toArray() === [4, 5, 6] && !$rd->isGPU(); -echo 'same-dtype-copy: ', ($ok ? 'OK' : 'BAD'), "\n"; - -/* 6. 0-D scalar (shape []) stays an NDArray after astype; __toString is "7\n" */ -$e = new NDArray(7.0); -$re = $e->astype('int32'); -$ok = $re instanceof NDArray && $re->shape() === [] && trim((string)$re) === '7'; -echo 'zero-d: ', ($ok ? 'OK' : 'BAD'), ' shape=', json_encode($re->shape()), "\n"; - -/* 7. unknown dtype throws, message matches the canonical list */ -try { - (new NDArray([1, 2, 3], 'float32'))->astype('badtype'); - echo "badtype: NO-THROW\n"; -} catch (Throwable $t) { - echo "badtype threw: ", get_class($t), " | ", $t->getMessage(), "\n"; -} - -/* 8. wrong argument count rejects */ -try { - (new NDArray([1, 2, 3], 'float32'))->astype(); - echo "no-arg: NO-THROW\n"; -} catch (Throwable $t) { - echo "no-arg threw: ", get_class($t), "\n"; -} -?> ---EXPECT-- -float32->int32: OK [1,2,3] -int32->float64: OK [1,2,3] -original-unchanged: OK [1.5,2.5,3.700000047683716] -device-stays-CPU: OK isGPU=0 -same-dtype-copy: OK -zero-d: OK shape=[] -badtype threw: Error | Invalid data type 'badtype'. Supported: float4, float8, float16, float32, float64, float128, int8, uint8, int16, uint16, int32, uint32, int64, uint64 -no-arg threw: ArgumentCountError diff --git a/tests/types/029-settype-dtype-change.phpt b/tests/types/029-settype-dtype-change.phpt new file mode 100644 index 0000000..bc1daf3 --- /dev/null +++ b/tests/types/029-settype-dtype-change.phpt @@ -0,0 +1,81 @@ +--TEST-- +NDArray::setType() mutates the array's data type in place (void) +--FILE-- + int32: values truncate, elements become ints, same object */ +$a = new NDArray([1.5, 2.5, 3.7], 'float32'); +$ret = $a->setType('int32'); +$php = $a->toArray(); +$ok = ($a instanceof NDArray) && $php === [1, 2, 3] + && array_map('gettype', $php) === ['integer', 'integer', 'integer'] + && !$a->isGPU(); +echo 'float32->int32: ', ($ok ? 'OK' : 'BAD'), ' ', json_encode($php), "\n"; + +/* 2. int32 -> float64: elements become floats, values preserved */ +$b = new NDArray([1, 2, 3], 'int32'); +$b->setType('float64'); +$php = $b->toArray(); +$ok = array_map('gettype', $php) === ['double', 'double', 'double'] + && $php === [1.0, 2.0, 3.0]; +echo 'int32->float64: ', ($ok ? 'OK' : 'BAD'), ' ', json_encode($php), "\n"; + +/* 3. returns void (null), not a new array */ +echo 'return-void: ', (($ret === null) ? 'OK' : 'BAD'), + ' (', ($ret === null ? 'null' : 'non-null'), ')', "\n"; + +/* 4. unknown dtype throws and leaves the array unchanged */ +$c = new NDArray([1.0, 2.0], 'float64'); +$before = $c->toArray(); +$ctypes = array_map('gettype', $before); +$threw = false; +$msg = ''; +try { $c->setType('badtype'); } +catch (Throwable $t) { $threw = true; $msg = get_class($t) . ' | ' . $t->getMessage(); } +$still = $c->toArray(); +$ok = $threw && $still === $before && array_map('gettype', $still) === $ctypes; +echo "badtype-throws-unchanged: ", ($ok ? 'OK' : 'BAD'), " ", $msg, "\n"; + +/* 5. same-dtype cast is a true no-op: values and types preserved */ +$d = new NDArray([4, 5, 6], 'int32'); +$d->setType('int32'); +$php = $d->toArray(); +$ok = $php === [4, 5, 6] && array_map('gettype', $php) === ['integer','integer','integer'] && !$d->isGPU(); +echo 'same-dtype-noop: ', ($ok ? 'OK' : 'BAD'), ' ', json_encode($php), "\n"; + +/* 6. 0-D scalar (shape []) stays an NDArray; __toString is "7\n" */ +$e = new NDArray(7.0); +$e->setType('int32'); +$ok = ($e instanceof NDArray) && $e->shape() === [] && trim((string)$e) === '7'; +echo 'zero-d: ', ($ok ? 'OK' : 'BAD'), ' shape=', json_encode($e->shape()), "\n"; + +/* 7. device preserved: a CPU array stays CPU after setType */ +$f = new NDArray([1.0, 2.0, 3.0], 'float32'); +$f->setType('float64'); +$ok = ($f instanceof NDArray) && !$f->isGPU() && $f->toArray() === [1.0, 2.0, 3.0]; +echo 'device-stays-CPU: ', ($ok ? 'OK' : 'BAD'), ' isGPU=', ($f->isGPU() ? 1 : 0), "\n"; + +/* 8. wrong argument count rejects */ +try { + (new NDArray([1, 2, 3], 'float32'))->setType(); + echo "no-arg: NO-THROW\n"; +} catch (Throwable $t) { + echo "no-arg threw: ", get_class($t), "\n"; +} +?> +--EXPECT-- +float32->int32: OK [1,2,3] +int32->float64: OK [1,2,3] +return-void: OK (null) +badtype-throws-unchanged: OK Error | Invalid data type 'badtype'. Supported: float4, float8, float16, float32, float64, float128, int8, uint8, int16, uint16, int32, uint32, int64, uint64 +same-dtype-noop: OK [4,5,6] +zero-d: OK shape=[] +device-stays-CPU: OK isGPU=0 +no-arg threw: ArgumentCountError diff --git a/tests/types/030-astype-dtype-change-gpu.phpt b/tests/types/030-astype-dtype-change-gpu.phpt deleted file mode 100644 index 5b3983d..0000000 --- a/tests/types/030-astype-dtype-change-gpu.phpt +++ /dev/null @@ -1,41 +0,0 @@ ---TEST-- -NDArray::astype() on a GPU array keeps the array on GPU and converts values ---SKIPIF-- -gpu(); } catch (Error $e) { die('skip ' . $e->getMessage()); } ?> ---FILE-- -cpu() to inspect values. */ - -$cases = [ - ['float32', 'int32', [1.5, 2.5, 3.5], [1, 2, 3]], - ['int32', 'float64', [1, 2, 3], [1.0, 2.0, 3.0]], - ['float64', 'float32', [1.5, 2.5, 0.5], [1.5, 2.5, 0.5]], - ['int32', 'int16', [1, 2, 3], [1, 2, 3]], -]; - -foreach ($cases as [$src, $dst, $vals, $expect]) { - $g = (new NDArray($vals, $src))->gpu(); - $r = $g->astype($dst); - $on_gpu = $r->isGPU(); - $back = $r->cpu()->toArray(); - $ok = ($r instanceof NDArray) && $on_gpu && $back === $expect; - echo "$src->$dst: ", ($ok ? 'OK' : 'BAD'), - ' isGPU=', ($r->isGPU() ? 1 : 0), - ' vals=', json_encode($back), "\n"; -} - -/* unknown dtype on GPU throws too */ -try { - (new NDArray([1.0], 'float32'))->gpu()->astype('nope'); - echo "badtype: NO-THROW\n"; -} catch (Throwable $t) { - echo "badtype threw: ", get_class($t), "\n"; -} -?> ---EXPECT-- -float32->int32: OK isGPU=1 vals=[1,2,3] -int32->float64: OK isGPU=1 vals=[1,2,3] -float64->float32: OK isGPU=1 vals=[1.5,2.5,0.5] -int32->int16: OK isGPU=1 vals=[1,2,3] -badtype threw: Error diff --git a/tests/types/030-settype-dtype-change-gpu.phpt b/tests/types/030-settype-dtype-change-gpu.phpt new file mode 100644 index 0000000..3360212 --- /dev/null +++ b/tests/types/030-settype-dtype-change-gpu.phpt @@ -0,0 +1,46 @@ +--TEST-- +NDArray::setType() on a GPU array stays on GPU and converts values in place +--SKIPIF-- +gpu(); } catch (Error $e) { die('skip ' . $e->getMessage()); } ?> +--FILE-- +cpu() is used only to read back values — it returns a new array and + leaves $g on the GPU, so the isGPU() assertion is still meaningful. */ + +$cases = [ + ['float32', 'int32', [1.5, 2.5, 3.5], [1, 2, 3]], + ['int32', 'float64', [1, 2, 3], [1.0, 2.0, 3.0]], + ['float64', 'float32', [1.5, 2.5, 0.5], [1.5, 2.5, 0.5]], + ['int32', 'int16', [1, 2, 3], [1, 2, 3]], +]; + +foreach ($cases as [$src, $dst, $vals, $expect]) { + $g = (new NDArray($vals, $src))->gpu(); + $g->setType($dst); + $on_gpu = $g->isGPU(); /* still after setType() */ + $back = $g->cpu()->toArray(); /* cpu() returns a new array; $g stays GPU */ + $ok = ($g instanceof NDArray) && $on_gpu && $back === $expect; + echo "$src->$dst: ", ($ok ? 'OK' : 'BAD'), + ' isGPU=', ($g->isGPU() ? 1 : 0), + ' vals=', json_encode($back), "\n"; +} + +/* unknown dtype on GPU throws too, leaving the array intact on GPU */ +$g2 = (new NDArray([1.0, 2.0], 'float32'))->gpu(); +try { + $g2->setType('nope'); + echo "badtype: NO-THROW\n"; +} catch (Throwable $t) { + echo "badtype threw: ", get_class($t), + " isGPU=", ($g2->isGPU() ? 1 : 0), + " vals=", json_encode($g2->cpu()->toArray()), "\n"; +} +?> +--EXPECT-- +float32->int32: OK isGPU=1 vals=[1,2,3] +int32->float64: OK isGPU=1 vals=[1,2,3] +float64->float32: OK isGPU=1 vals=[1.5,2.5,0.5] +int32->int16: OK isGPU=1 vals=[1,2,3] +badtype threw: Error isGPU=1 vals=[1.0,2.0] From bbab8a07ad113666aa0d9d222d6b6b27162dae92 Mon Sep 17 00:00:00 2001 From: Andrew DalPino Date: Tue, 1 Sep 2026 11:22:17 -0500 Subject: [PATCH 5/6] Add accessor --- numpower.c | 42 ++++++- tests/types/029-setdatatype.phpt | 112 ++++++++++++++++++ tests/types/029-settype-dtype-change.phpt | 81 ------------- tests/types/030-setdatatype-gpu.phpt | 50 ++++++++ tests/types/030-settype-dtype-change-gpu.phpt | 46 ------- 5 files changed, 200 insertions(+), 131 deletions(-) create mode 100644 tests/types/029-setdatatype.phpt delete mode 100644 tests/types/029-settype-dtype-change.phpt create mode 100644 tests/types/030-setdatatype-gpu.phpt delete mode 100644 tests/types/030-settype-dtype-change-gpu.phpt diff --git a/numpower.c b/numpower.c index d2e38c9..f4cd122 100644 --- a/numpower.c +++ b/numpower.c @@ -297,14 +297,14 @@ PHP_METHOD(NDArray, gpu) { #endif } -ZEND_BEGIN_ARG_INFO(arginfo_settype, 0) +ZEND_BEGIN_ARG_INFO(arginfo_setdtype, 0) ZEND_ARG_INFO(0, dtype) ZEND_END_ARG_INFO(); /** * @brief Sets the data type of this NDArray in place. * * ``` - * setType(string $dtype): void + * setDataType(string $dtype): void * ``` * * Re-casts the array to the requested dtype, releasing the previous @@ -320,7 +320,7 @@ ZEND_END_ARG_INFO(); * * @throws \Error If the dtype is unknown. */ -PHP_METHOD(NDArray, setType) { +PHP_METHOD(NDArray, setDataType) { char *dtype; size_t dtypeLen = 0; zval *obj_zval = getThis(); @@ -366,6 +366,39 @@ PHP_METHOD(NDArray, setType) { } } +ZEND_BEGIN_ARG_INFO(arginfo_datatype, 0) +ZEND_END_ARG_INFO(); +/** + * @brief Returns the data type of this NDArray. + * + * ``` + * dataType(): string + * ``` + * + * One of the supported dtypes: float4, float8, float16, float32, + * float64, float128, int8, uint8, int16, uint16, int32, uint32, + * int64, uint64. + * + * Pair with `setDataType(string $dtype): void`. + */ +PHP_METHOD(NDArray, dataType) { + zval *obj_zval = getThis(); + + ZEND_PARSE_PARAMETERS_START(0, 0) + ZEND_PARSE_PARAMETERS_END(); + + NDArray *ndarray = NDArrayFactory_restoreFromZval(obj_zval); + if (ndarray == NULL) { + return; + } + + /* NDArray_TYPE() points at a canonical static string from src/types.h + (e.g. "float32"); RETVAL_STRINGL copies the bytes into a fresh + PHP string zval so the descriptor's storage is not affected. */ + const char *type = NDArray_TYPE(ndarray); + RETVAL_STRINGL(type, (size_t)strlen(type)); +} + /** * @brief Fills the NDArray with a specified value. * @@ -7491,7 +7524,8 @@ static const zend_function_entry class_NDArray_methods[] = { ZEND_ME(NDArray, gpu, arginfo_gpu, ZEND_ACC_PUBLIC) ZEND_ME(NDArray, cpu, arginfo_cpu, ZEND_ACC_PUBLIC) ZEND_ME(NDArray, isGPU, arginfo_is_gpu, ZEND_ACC_PUBLIC) - ZEND_ME(NDArray, setType, arginfo_settype, ZEND_ACC_PUBLIC) + ZEND_ME(NDArray, setDataType, arginfo_setdtype, ZEND_ACC_PUBLIC) + ZEND_ME(NDArray, dataType, arginfo_datatype, ZEND_ACC_PUBLIC) ZEND_ME(NDArray, size, arginfo_size, ZEND_ACC_PUBLIC) ZEND_ME(NDArray, count, arginfo_count, ZEND_ACC_PUBLIC) diff --git a/tests/types/029-setdatatype.phpt b/tests/types/029-setdatatype.phpt new file mode 100644 index 0000000..13eb580 --- /dev/null +++ b/tests/types/029-setdatatype.phpt @@ -0,0 +1,112 @@ +--TEST-- +NDArray::setDataType() mutates dtype in place; dataType() returns current type +--FILE-- + int32: values truncate, elements become ints */ +$a = new NDArray([1.5, 2.5, 3.7], 'float32'); +$pre = $a->dataType(); +$ret = $a->setDataType('int32'); +$php = $a->toArray(); +$post = $a->dataType(); +$ok = ($pre === 'float32') && $post === 'int32' + && ($ret === null) && $php === [1, 2, 3] + && array_map('gettype', $php) === ['integer', 'integer', 'integer'] + && !$a->isGPU(); +echo "float32->int32: pre=$pre post=$post ret=", ($ret === null ? 'null' : 'non-null'), + " values=", json_encode($php), + " ok=", ($ok ? 'OK' : 'BAD'), "\n"; + +/* 2. int32 -> float64: elements become PHP floats (values preserved) */ +$b = new NDArray([1, 2, 3], 'int32'); +$before = $b->dataType(); +$b->setDataType('float64'); +$after = $b->dataType(); +$php = $b->toArray(); +$ok = $before === 'int32' && $after === 'float64' + && array_map('gettype', $php) === ['double', 'double', 'double'] + && $php === [1.0, 2.0, 3.0]; +echo "int32->float64: pre=$before post=$after", + " values=", json_encode($php), + " types=", json_encode(array_map('gettype', $php)), + " ok=", ($ok ? 'OK' : 'BAD'), "\n"; + +/* 3. unknown dtype throws; dataType() and values unchanged */ +$c = new NDArray([1.0, 2.0], 'float64'); +$beforeType = $c->dataType(); +$beforeVals = $c->toArray(); +$threw = false; $tclass = ''; +try { $c->setDataType('badtype'); } +catch (Throwable $t) { $threw = true; $tclass = get_class($t); } +$ok = $threw + && $c->dataType() === $beforeType + && $c->toArray() === $beforeVals + && array_map('gettype', $c->toArray()) === ['double', 'double']; +echo "badtype: threw=", $tclass, " pre=$beforeType post=", $c->dataType(), + " vals=", json_encode($c->toArray()), + " ok=", ($ok ? 'OK' : 'BAD'), "\n"; + +/* 4. same-dtype cast is a true no-op: dtype and values unchanged */ +$d = new NDArray([4, 5, 6], 'int32'); +$d->setDataType('int32'); +$ok = $d->dataType() === 'int32' + && $d->toArray() === [4, 5, 6] + && array_map('gettype', $d->toArray()) === ['integer','integer','integer'] + && !$d->isGPU(); +echo "no-op: type=", $d->dataType(), " vals=", json_encode($d->toArray()), + " ok=", ($ok ? 'OK' : 'BAD'), "\n"; + +/* 5. 0-D scalar (shape []) stays an NDArray; __toString is "7\n" */ +$e = new NDArray(7.0, 'float64'); +$pre = $e->dataType(); +$e->setDataType('int32'); +$post = $e->dataType(); +$ok = ($e instanceof NDArray) && $e->shape() === [] + && ($pre === 'float64') && ($post === 'int32') + && trim((string)$e) === '7'; +echo "0-d: shape=", json_encode($e->shape()), " pre=$pre post=$post", + " str=", json_encode((string)$e), " ok=", ($ok ? 'OK' : 'BAD'), "\n"; + +/* 6. device preserved: a CPU array stays CPU after setDataType */ +$f = new NDArray([1.0, 2.0, 3.0], 'float64'); +$f->setDataType('float32'); +$ok = ($f instanceof NDArray) && !$f->isGPU() + && $f->toArray() === [1.0, 2.0, 3.0] + && $f->dataType() === 'float32'; +echo "cpu: isGPU=", ($f->isGPU() ? 1 : 0), " type=", $f->dataType(), + " vals=", json_encode($f->toArray()), " ok=", ($ok ? 'OK' : 'BAD'), "\n"; + +/* 7. wrong argument counts reject */ +try { + (new NDArray([1, 2, 3], 'float32'))->setDataType(); + echo "no-arg setDataType: NO-THROW\n"; +} catch (Throwable $t) { + echo "no-arg setDataType threw: ", get_class($t), "\n"; +} +try { + (new NDArray([1, 2, 3], 'float32'))->dataType(123); + echo "dataType(123): NO-THROW\n"; +} catch (Throwable $t) { + echo "dataType(123) threw: ", get_class($t), "\n"; +} +?> +--EXPECT-- +float32->int32: pre=float32 post=int32 ret=null values=[1,2,3] ok=OK +int32->float64: pre=int32 post=float64 values=[1,2,3] types=["double","double","double"] ok=OK +badtype: threw=Error pre=float64 post=float64 vals=[1,2] ok=OK +no-op: type=int32 vals=[4,5,6] ok=OK +0-d: shape=[] pre=float64 post=int32 str="7\n" ok=OK +cpu: isGPU=0 type=float32 vals=[1,2,3] ok=OK +no-arg setDataType threw: ArgumentCountError +dataType(123) threw: ArgumentCountError diff --git a/tests/types/029-settype-dtype-change.phpt b/tests/types/029-settype-dtype-change.phpt deleted file mode 100644 index bc1daf3..0000000 --- a/tests/types/029-settype-dtype-change.phpt +++ /dev/null @@ -1,81 +0,0 @@ ---TEST-- -NDArray::setType() mutates the array's data type in place (void) ---FILE-- - int32: values truncate, elements become ints, same object */ -$a = new NDArray([1.5, 2.5, 3.7], 'float32'); -$ret = $a->setType('int32'); -$php = $a->toArray(); -$ok = ($a instanceof NDArray) && $php === [1, 2, 3] - && array_map('gettype', $php) === ['integer', 'integer', 'integer'] - && !$a->isGPU(); -echo 'float32->int32: ', ($ok ? 'OK' : 'BAD'), ' ', json_encode($php), "\n"; - -/* 2. int32 -> float64: elements become floats, values preserved */ -$b = new NDArray([1, 2, 3], 'int32'); -$b->setType('float64'); -$php = $b->toArray(); -$ok = array_map('gettype', $php) === ['double', 'double', 'double'] - && $php === [1.0, 2.0, 3.0]; -echo 'int32->float64: ', ($ok ? 'OK' : 'BAD'), ' ', json_encode($php), "\n"; - -/* 3. returns void (null), not a new array */ -echo 'return-void: ', (($ret === null) ? 'OK' : 'BAD'), - ' (', ($ret === null ? 'null' : 'non-null'), ')', "\n"; - -/* 4. unknown dtype throws and leaves the array unchanged */ -$c = new NDArray([1.0, 2.0], 'float64'); -$before = $c->toArray(); -$ctypes = array_map('gettype', $before); -$threw = false; -$msg = ''; -try { $c->setType('badtype'); } -catch (Throwable $t) { $threw = true; $msg = get_class($t) . ' | ' . $t->getMessage(); } -$still = $c->toArray(); -$ok = $threw && $still === $before && array_map('gettype', $still) === $ctypes; -echo "badtype-throws-unchanged: ", ($ok ? 'OK' : 'BAD'), " ", $msg, "\n"; - -/* 5. same-dtype cast is a true no-op: values and types preserved */ -$d = new NDArray([4, 5, 6], 'int32'); -$d->setType('int32'); -$php = $d->toArray(); -$ok = $php === [4, 5, 6] && array_map('gettype', $php) === ['integer','integer','integer'] && !$d->isGPU(); -echo 'same-dtype-noop: ', ($ok ? 'OK' : 'BAD'), ' ', json_encode($php), "\n"; - -/* 6. 0-D scalar (shape []) stays an NDArray; __toString is "7\n" */ -$e = new NDArray(7.0); -$e->setType('int32'); -$ok = ($e instanceof NDArray) && $e->shape() === [] && trim((string)$e) === '7'; -echo 'zero-d: ', ($ok ? 'OK' : 'BAD'), ' shape=', json_encode($e->shape()), "\n"; - -/* 7. device preserved: a CPU array stays CPU after setType */ -$f = new NDArray([1.0, 2.0, 3.0], 'float32'); -$f->setType('float64'); -$ok = ($f instanceof NDArray) && !$f->isGPU() && $f->toArray() === [1.0, 2.0, 3.0]; -echo 'device-stays-CPU: ', ($ok ? 'OK' : 'BAD'), ' isGPU=', ($f->isGPU() ? 1 : 0), "\n"; - -/* 8. wrong argument count rejects */ -try { - (new NDArray([1, 2, 3], 'float32'))->setType(); - echo "no-arg: NO-THROW\n"; -} catch (Throwable $t) { - echo "no-arg threw: ", get_class($t), "\n"; -} -?> ---EXPECT-- -float32->int32: OK [1,2,3] -int32->float64: OK [1,2,3] -return-void: OK (null) -badtype-throws-unchanged: OK Error | Invalid data type 'badtype'. Supported: float4, float8, float16, float32, float64, float128, int8, uint8, int16, uint16, int32, uint32, int64, uint64 -same-dtype-noop: OK [4,5,6] -zero-d: OK shape=[] -device-stays-CPU: OK isGPU=0 -no-arg threw: ArgumentCountError diff --git a/tests/types/030-setdatatype-gpu.phpt b/tests/types/030-setdatatype-gpu.phpt new file mode 100644 index 0000000..200193e --- /dev/null +++ b/tests/types/030-setdatatype-gpu.phpt @@ -0,0 +1,50 @@ +--TEST-- +NDArray::setDataType() on GPU array stays on GPU and converts values in place +--SKIPIF-- +gpu(); } catch (Error $e) { die('skip ' . $e->getMessage()); } ?> +--FILE-- +cpu() is used only to read values back — it returns a new array and + leaves $g on the GPU, so the isGPU() assertion is still meaningful. */ + +$cases = [ + ['float32', 'int32', [1.5, 2.5, 3.5], [1, 2, 3]], + ['int32', 'float64', [1, 2, 3], [1, 2, 3]], + ['float64', 'float32', [1.5, 2.5, 0.5], [1.5, 2.5, 0.5]], + ['int32', 'int16', [1, 2, 3], [1, 2, 3]], +]; + +foreach ($cases as [$src, $dst, $vals, $expect]) { + $g = (new NDArray($vals, $src))->gpu(); + $pre = $g->dataType(); + $g->setDataType($dst); + $post = $g->dataType(); + $on_gpu = $g->isGPU(); /* still after setDataType() */ + $back = $g->cpu()->toArray(); /* cpu() returns a new array; $g stays GPU */ + $ok = ($g instanceof NDArray) && $pre === $src && $post === $dst && $on_gpu && $back === $expect; + echo "$src->$dst: pre=$pre post=$post", + " isGPU=", ($g->isGPU() ? 1 : 0), + " vals=", json_encode($back), + " ok=", ($ok ? 'OK' : 'BAD'), "\n"; +} + +/* unknown dtype on GPU throws too, leaving the array intact on GPU */ +$g2 = (new NDArray([1.0, 2.0], 'float32'))->gpu(); +$pre2 = $g2->dataType(); +try { + $g2->setDataType('nope'); + echo "badtype: NO-THROW\n"; +} catch (Throwable $t) { + echo "badtype: pre=$pre2 post=", $g2->dataType(), + " isGPU=", ($g2->isGPU() ? 1 : 0), + " vals=", json_encode($g2->cpu()->toArray()), "\n"; +} +?> +--EXPECT-- +float32->int32: pre=float32 post=int32 isGPU=1 vals=[1,2,3] ok=OK +int32->float64: pre=int32 post=float64 isGPU=1 vals=[1,2,3] ok=OK +float64->float32: pre=float64 post=float32 isGPU=1 vals=[1.5,2.5,0.5] ok=OK +int32->int16: pre=int32 post=int16 isGPU=1 vals=[1,2,3] ok=OK +badtype: pre=float32 post=float32 isGPU=1 vals=[1,2] diff --git a/tests/types/030-settype-dtype-change-gpu.phpt b/tests/types/030-settype-dtype-change-gpu.phpt deleted file mode 100644 index 3360212..0000000 --- a/tests/types/030-settype-dtype-change-gpu.phpt +++ /dev/null @@ -1,46 +0,0 @@ ---TEST-- -NDArray::setType() on a GPU array stays on GPU and converts values in place ---SKIPIF-- -gpu(); } catch (Error $e) { die('skip ' . $e->getMessage()); } ?> ---FILE-- -cpu() is used only to read back values — it returns a new array and - leaves $g on the GPU, so the isGPU() assertion is still meaningful. */ - -$cases = [ - ['float32', 'int32', [1.5, 2.5, 3.5], [1, 2, 3]], - ['int32', 'float64', [1, 2, 3], [1.0, 2.0, 3.0]], - ['float64', 'float32', [1.5, 2.5, 0.5], [1.5, 2.5, 0.5]], - ['int32', 'int16', [1, 2, 3], [1, 2, 3]], -]; - -foreach ($cases as [$src, $dst, $vals, $expect]) { - $g = (new NDArray($vals, $src))->gpu(); - $g->setType($dst); - $on_gpu = $g->isGPU(); /* still after setType() */ - $back = $g->cpu()->toArray(); /* cpu() returns a new array; $g stays GPU */ - $ok = ($g instanceof NDArray) && $on_gpu && $back === $expect; - echo "$src->$dst: ", ($ok ? 'OK' : 'BAD'), - ' isGPU=', ($g->isGPU() ? 1 : 0), - ' vals=', json_encode($back), "\n"; -} - -/* unknown dtype on GPU throws too, leaving the array intact on GPU */ -$g2 = (new NDArray([1.0, 2.0], 'float32'))->gpu(); -try { - $g2->setType('nope'); - echo "badtype: NO-THROW\n"; -} catch (Throwable $t) { - echo "badtype threw: ", get_class($t), - " isGPU=", ($g2->isGPU() ? 1 : 0), - " vals=", json_encode($g2->cpu()->toArray()), "\n"; -} -?> ---EXPECT-- -float32->int32: OK isGPU=1 vals=[1,2,3] -int32->float64: OK isGPU=1 vals=[1,2,3] -float64->float32: OK isGPU=1 vals=[1.5,2.5,0.5] -int32->int16: OK isGPU=1 vals=[1,2,3] -badtype threw: Error isGPU=1 vals=[1.0,2.0] From e063d042f93075688f6ba3b9392afc9fefe2bbc7 Mon Sep 17 00:00:00 2001 From: Andrew DalPino Date: Tue, 1 Sep 2026 11:33:58 -0500 Subject: [PATCH 6/6] Test that object ids are the same --- tests/types/029-setdatatype.phpt | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/tests/types/029-setdatatype.phpt b/tests/types/029-setdatatype.phpt index 13eb580..c0d5a55 100644 --- a/tests/types/029-setdatatype.phpt +++ b/tests/types/029-setdatatype.phpt @@ -14,16 +14,25 @@ NDArray::setDataType() mutates dtype in place; dataType() returns current type - return the current dtype as a string (e.g. 'float32') - reflect the immediately-preceding setDataType() call */ -/* 1. float32 -> int32: values truncate, elements become ints */ +/* 1. float32 -> int32: values truncate, elements become ints; + in-place: same PHP object identity, alias observes the change */ $a = new NDArray([1.5, 2.5, 3.7], 'float32'); +$aliasA = $a; /* alias must share the object */ +$idA = spl_object_id($a); $pre = $a->dataType(); $ret = $a->setDataType('int32'); $php = $a->toArray(); $post = $a->dataType(); +$aliasOK = (spl_object_id($aliasA) === $idA) + && ($aliasA->dataType() === 'int32') + && ($aliasA->toArray() === [1, 2, 3]) + && (array_map('gettype', $aliasA->toArray()) === ['integer','integer','integer']); $ok = ($pre === 'float32') && $post === 'int32' + && (spl_object_id($a) === $idA) /* same PHP object after setDataType */ && ($ret === null) && $php === [1, 2, 3] && array_map('gettype', $php) === ['integer', 'integer', 'integer'] - && !$a->isGPU(); + && !$a->isGPU() + && $aliasOK; echo "float32->int32: pre=$pre post=$post ret=", ($ret === null ? 'null' : 'non-null'), " values=", json_encode($php), " ok=", ($ok ? 'OK' : 'BAD'), "\n"; @@ -44,12 +53,14 @@ echo "int32->float64: pre=$before post=$after", /* 3. unknown dtype throws; dataType() and values unchanged */ $c = new NDArray([1.0, 2.0], 'float64'); +$idC = spl_object_id($c); $beforeType = $c->dataType(); $beforeVals = $c->toArray(); $threw = false; $tclass = ''; try { $c->setDataType('badtype'); } catch (Throwable $t) { $threw = true; $tclass = get_class($t); } $ok = $threw + && (spl_object_id($c) === $idC) /* object NOT replaced on throw */ && $c->dataType() === $beforeType && $c->toArray() === $beforeVals && array_map('gettype', $c->toArray()) === ['double', 'double']; @@ -59,8 +70,10 @@ echo "badtype: threw=", $tclass, " pre=$beforeType post=", $c->dataType(), /* 4. same-dtype cast is a true no-op: dtype and values unchanged */ $d = new NDArray([4, 5, 6], 'int32'); +$idD = spl_object_id($d); $d->setDataType('int32'); -$ok = $d->dataType() === 'int32' +$ok = (spl_object_id($d) === $idD) /* no-op must not replace object */ + && $d->dataType() === 'int32' && $d->toArray() === [4, 5, 6] && array_map('gettype', $d->toArray()) === ['integer','integer','integer'] && !$d->isGPU();