diff --git a/src/Normalizers/Precompiled.php b/src/Normalizers/Precompiled.php index 72b9a90..20efaec 100644 --- a/src/Normalizers/Precompiled.php +++ b/src/Normalizers/Precompiled.php @@ -153,10 +153,10 @@ public function commonPrefixSearch($key): array $node_pos ^= $this->offset($unit); foreach (mb_str_split($key) as $c) { - if (ord($c) === 0) { + if (ord($c[0]) === 0) { break; } - $node_pos ^= ord($c); + $node_pos ^= ord($c[0]); $unit = $this->array[$node_pos]; if ($this->label($unit) !== mb_ord($c)) { return $results; diff --git a/src/Tensor/Tensor.php b/src/Tensor/Tensor.php index eb10625..c01e23c 100644 --- a/src/Tensor/Tensor.php +++ b/src/Tensor/Tensor.php @@ -820,25 +820,27 @@ public function normalize(int $p = 2, ?int $axis = null): static $norm = $result->norm($p, $axis, true); - foreach ($norm->buffer as $i => $value) { - $resultIndex = 0; + // TensorBuffer doesn't implement Iterator/IteratorAggregate, so foreach over it + // yields zero iterations; use the flattened logical values instead + foreach ($result->toBufferArray() as $i => $value) { + $normIndex = 0; $num = $i; - $resultMultiplier = 1; + $normMultiplier = 1; for ($j = $result->ndim() - 1; $j >= 0; --$j) { $size = $result->shape()[$j]; if ($j !== $axis) { $index = $num % $size; - $resultIndex += $index * $resultMultiplier; - $resultMultiplier *= $result->shape()[$j]; + $normIndex += $index * $normMultiplier; + $normMultiplier *= $result->shape()[$j]; } $num = floor($num / $size); } // Divide by normalized value - $result->buffer[$i] /= $norm->buffer[$resultIndex]; + $result->buffer[$i] /= $norm->buffer[$normIndex]; } return $result; @@ -873,8 +875,9 @@ public function norm(int $ord = 2, ?int $axis = null, bool $keepShape = false): // Create a new array to store the accumulated values $result = $this->zeros([count($this->buffer) / $this->shape()[$axis]]); - // Iterate over the data array - foreach ($this->buffer as $i => $value) { + // TensorBuffer doesn't implement Iterator/IteratorAggregate, so foreach over it + // yields zero iterations; use the flattened logical values instead + foreach ($this->toBufferArray() as $i => $value) { // Calculate the index in the resulting array $resultIndex = 0; $num = $i; @@ -893,10 +896,10 @@ public function norm(int $ord = 2, ?int $axis = null, bool $keepShape = false): } // Accumulate the value at the current index - $result[$resultIndex] += pow($this->buffer[$i], $ord); + $result[$resultIndex] += pow($value, $ord); } - if ($ord === 1) { + if ($ord !== 1) { $result = $mo->op($result, '**', 1 / $ord); } diff --git a/src/Utils/Downloader.php b/src/Utils/Downloader.php index 5e5cd1a..3e15002 100644 --- a/src/Utils/Downloader.php +++ b/src/Utils/Downloader.php @@ -110,7 +110,6 @@ public static function download(string $url, string $to, array $options = [], ?c if (curl_exec($curlHandle) === false) { $error = curl_error($curlHandle); - curl_close($curlHandle); fclose($headerHandle); fclose($bodyHandle); throw new \Exception("The \"$url\" file could not be downloaded: $error"); @@ -119,14 +118,11 @@ public static function download(string $url, string $to, array $options = [], ?c $statusCode = curl_getinfo($curlHandle, CURLINFO_RESPONSE_CODE); if ($statusCode < 200 || $statusCode >= 300) { - curl_close($curlHandle); fclose($headerHandle); fclose($bodyHandle); throw new \Exception("The \"$url\" file could not be downloaded: HTTP $statusCode"); } - curl_close($curlHandle); - rewind($headerHandle); $headers = stream_get_contents($headerHandle); diff --git a/tests/tensors/TensorTest.php b/tests/tensors/TensorTest.php index 3c76538..67e29aa 100644 --- a/tests/tensors/TensorTest.php +++ b/tests/tensors/TensorTest.php @@ -290,6 +290,33 @@ expect($values->toArray())->toBe([5.0, 4.0, 3.0]) ->and($indices->toArray())->toBe([4, 1, 2]); }); + + it('can calculate L2 norm along an axis', function () { + $t = new Tensor([[3.0, 4.0], [6.0, 8.0]]); + $norm = $t->norm(2, -1); + expect($norm->toArray())->toBe([5.0, 10.0]); + }); + + it('can calculate L2 norm along an axis while keeping the reduced dimension', function () { + $t = new Tensor([[3.0, 4.0], [6.0, 8.0]]); + $norm = $t->norm(2, -1, keepShape: true); + expect($norm->shape())->toBe([2, 1]) + ->and($norm->toArray())->toBe([[5.0], [10.0]]); + }); + + it('can normalize a tensor along an axis', function () { + $t = new Tensor([[3.0, 4.0], [6.0, 8.0]]); + $normalized = $t->normalize(2, -1)->toArray(); + expect($normalized[0])->toMatchArrayApproximately([0.6, 0.8], 1e-6) + ->and($normalized[1])->toMatchArrayApproximately([0.6, 0.8], 1e-6); + }); + + it('normalized rows have unit L2 norm', function () { + $t = new Tensor([[1.0, 2.0, 3.0, 4.0]]); + $normalized = $t->normalize(2, -1); + $sumOfSquares = array_sum(array_map(fn ($v) => $v ** 2, $normalized->toArray()[0])); + expect($sumOfSquares)->toEqualWithDelta(1.0, 1e-6); + }); }); describe('Error handling', function () {