Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
50 changes: 50 additions & 0 deletions numpower.c
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Comment thread
Copilot marked this conversation as resolved.
Outdated
/**
* @brief Casts the NDArray to a new data type, returning a new NDArray.
*
* ```
* astype(string $dtype): NDArray
* ```
Comment thread
andrewdalpino marked this conversation as resolved.
*
* 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.
*
Expand Down Expand Up @@ -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)
Expand Down
75 changes: 75 additions & 0 deletions tests/types/029-astype-dtype-change.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
--TEST--
NDArray::astype() returns a new NDArray with the target dtype, preserving device
--FILE--
<?php
/* astype() must:
- return a NEW NDArray (original unchanged)
- convert element values and PHP types to the target dtype
- throw on an unknown dtype
- be a no-op copy when the target equals the source dtype */

/* 1. float32 -> 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
41 changes: 41 additions & 0 deletions tests/types/030-astype-dtype-change-gpu.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
--TEST--
NDArray::astype() on a GPU array keeps the array on GPU and converts values
--SKIPIF--
<?php try { (new NDArray([1.0]))->gpu(); } catch (Error $e) { die('skip ' . $e->getMessage()); } ?>
--FILE--
<?php
/* When the source array is on the GPU, astype() must keep the result on the
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]],
];

foreach ($cases as $src => [$dst, $vals, $expect]) {
Comment thread
Copilot marked this conversation as resolved.
Outdated
$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
Loading