diff --git a/.gitignore b/.gitignore index 58f5d07..1502eec 100644 --- a/.gitignore +++ b/.gitignore @@ -45,4 +45,6 @@ test.php *.IoT *.loT tensor -test_img.jpg \ No newline at end of file +test_img.jpg +composer.lock +vendor \ No newline at end of file diff --git a/README.md b/README.md index 5fb358d..6bd9f03 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,23 @@ NumPower aims to manage memory more efficiently than a matrix in PHP arrays - **Optional (GPU)**: CUBLAS, CUDA Build Toolkit and cuDNN - **Optional (Image)**: PHP-GD -## Composer Install +## Compiling + +``` +$ phpize +$ ./configure +$ make install +``` + +## Compiling with GPU (CUDA) support + +``` +$ phpize +$ ./configure --with-cuda +$ make install-cuda +``` + +## Composer install The composer package provides a stubs file to facilitate autocomplete in the IDE. To install, simply run the command below in your environment with composer installed: ```bash @@ -42,7 +58,7 @@ $ composer require numpower/numpower The composer package will follow the same versioning as the extension. -## GPU Support +## GPU support If you have an NVIDIA graphics card with CUDA support, you can use your graphics card to perform operations. To do this, just copy your array to the GPU memory. diff --git a/benchmarks/initializers/ArangeInitializerBench.php b/benchmarks/initializers/ArangeInitializerBench.php new file mode 100644 index 0000000..4831fc7 --- /dev/null +++ b/benchmarks/initializers/ArangeInitializerBench.php @@ -0,0 +1,35 @@ +size = $params['size']; + } + + /** + * @BeforeMethods("setUp") + * @Revs(1000) + * @Iterations(5) + * @ParamProviders({ + * "provideSizes", + * }) + */ + public function benchARange($params) + { + $ndarray = \NDArray::arange($this->size, 0, 1); + } + public function provideSizes() { + yield ['size' => 100]; + yield ['size' => 500]; + yield ['size' => 1000]; + } + } +?> \ No newline at end of file diff --git a/benchmarks/initializers/ArrayInitializerBench.php b/benchmarks/initializers/ArrayInitializerBench.php new file mode 100644 index 0000000..ec9c811 --- /dev/null +++ b/benchmarks/initializers/ArrayInitializerBench.php @@ -0,0 +1,39 @@ +matrix = $params['matrix']; + } + + /** + * @BeforeMethods("setUp") + * @Revs(1000) + * @Iterations(5) + * @ParamProviders({ + * "provideMatrix", + * }) + */ + public function benchArray($params) + { + $ndarray = \NDArray::array($this->matrix); + } + + public function provideMatrix() { + yield ['matrix' => \NDArray::zeros([1, 100])]; + yield ['matrix' => \NDArray::zeros([1, 500])]; + yield ['matrix' => \NDArray::zeros([1, 1000])]; + yield ['matrix' => \NDArray::zeros([10, 100])]; + yield ['matrix' => \NDArray::zeros([1000, 500])]; + yield ['matrix' => \NDArray::zeros([10000, 1000])]; + } + } +?> diff --git a/benchmarks/initializers/FullInitializerBench.php b/benchmarks/initializers/FullInitializerBench.php new file mode 100644 index 0000000..1f8b100 --- /dev/null +++ b/benchmarks/initializers/FullInitializerBench.php @@ -0,0 +1,39 @@ +shape = $params['shape']; + } + + /** + * @BeforeMethods("setUp") + * @Revs(1000) + * @Iterations(5) + * @ParamProviders({ + * "provideShapes" + * }) + */ + public function benchFull($params): void + { + \NDArray::full($this->shape, 4); + } + + public function provideShapes() { + yield ['shape' => [100, 1]]; + yield ['shape' => [500, 1]]; + yield ['shape' => [1000, 1]]; + yield ['shape' => [10, 100]]; + yield ['shape' => [500, 1000]]; + yield ['shape' => [1000, 10000]]; + } + } +?> diff --git a/benchmarks/initializers/IdentityInitializerBench.php b/benchmarks/initializers/IdentityInitializerBench.php new file mode 100644 index 0000000..3e34287 --- /dev/null +++ b/benchmarks/initializers/IdentityInitializerBench.php @@ -0,0 +1,36 @@ +size = $params['size']; + } + + /** + * @BeforeMethods("setUp") + * @Revs(1000) + * @Iterations(5) + * @ParamProviders({ + * "provideSizes", + * }) + */ + public function benchIdentity($params) + { + $ndarray = \NDArray::identity($this->size); + } + + public function provideSizes() { + yield ['size' => 100]; + yield ['size' => 500]; + yield ['size' => 1000]; + } + } +?> \ No newline at end of file diff --git a/benchmarks/initializers/OnesInitializerBench.php b/benchmarks/initializers/OnesInitializerBench.php new file mode 100644 index 0000000..db99fb3 --- /dev/null +++ b/benchmarks/initializers/OnesInitializerBench.php @@ -0,0 +1,39 @@ +shape = $params['shape']; + } + + /** + * @BeforeMethods("setUp") + * @Revs(1000) + * @Iterations(5) + * @ParamProviders({ + * "provideShapes" + * }) + */ + public function benchOnes($params): void + { + \NDArray::ones($this->shape); + } + + public function provideShapes() { + yield ['shape' => [100, 1]]; + yield ['shape' => [500, 1]]; + yield ['shape' => [1000, 1]]; + yield ['shape' => [10, 100]]; + yield ['shape' => [500, 1000]]; + yield ['shape' => [1000, 10000]]; + } + } +?> diff --git a/benchmarks/initializers/ZerosInitializerBench.php b/benchmarks/initializers/ZerosInitializerBench.php new file mode 100644 index 0000000..43afa0d --- /dev/null +++ b/benchmarks/initializers/ZerosInitializerBench.php @@ -0,0 +1,39 @@ +shape = $params['shape']; + } + + /** + * @BeforeMethods("setUp") + * @Revs(1000) + * @Iterations(5) + * @ParamProviders({ + * "provideShapes" + * }) + */ + public function benchZeros($params): void + { + \NDArray::zeros($this->shape); + } + + public function provideShapes() { + yield ['shape' => [100, 1]]; + yield ['shape' => [500, 1]]; + yield ['shape' => [1000, 1]]; + yield ['shape' => [10, 100]]; + yield ['shape' => [500, 1000]]; + yield ['shape' => [1000, 10000]]; + } + } +?> diff --git a/benchmarks/linalg/CholeskyBench.php b/benchmarks/linalg/CholeskyBench.php new file mode 100644 index 0000000..311c61d --- /dev/null +++ b/benchmarks/linalg/CholeskyBench.php @@ -0,0 +1,49 @@ +testArray = $params['testArray']; + } + + /** + * @BeforeMethods("setUp") + * @Revs(1000) + * @Iterations(5) + * @ParamProviders({ + * "provideArrays" + * }) + */ + public function benchCholesky($params): void + { + \NDArray::cholesky($this->testArray); + } + + private function createArray($ndim) { + $symmetric = \NDArray::ones([$ndim, $ndim]); + for ($i=0; $i<$ndim; $i++) { + $symmetric[$i][$i] += $ndim * 10; + } + $L = \NDArray::cholesky($symmetric); + $L_T = \NDArray::transpose($L); + $positive_definite_matrix = \NDArray::matmul($L, $L_T); + return $positive_definite_matrix; + } + + public function provideArrays() { + $testSizes = array(10, 100, 1000, 10000); + foreach ($testSizes as &$value) { + $currTestMatrix = $this->createArray($value); + yield ['testArray' => $currTestMatrix]; + } + } + } +?> diff --git a/benchmarks/linalg/CondBench.php b/benchmarks/linalg/CondBench.php new file mode 100644 index 0000000..2815970 --- /dev/null +++ b/benchmarks/linalg/CondBench.php @@ -0,0 +1,46 @@ +testArray = $params['testArray']; + } + + /** + * @BeforeMethods("setUp") + * @Revs(1000) + * @Iterations(5) + * @ParamProviders({ + * "provideArrays" + * }) + */ + public function benchCond($params): void + { + \NDArray::cond($this->testArray); + } + + private function createArray($ndim) { + $symmetric = \NDArray::ones([$ndim, $ndim]); + for ($i=0; $i<$ndim; $i++) { + $symmetric[$i][$i] += $ndim * 10; + } + return $symmetric; + } + + public function provideArrays() { + $testSizes = array(10, 100, 1000); + foreach ($testSizes as &$value) { + $currTestMatrix = $this->createArray($value); + yield ['testArray' => $currTestMatrix]; + } + } + } +?> diff --git a/benchmarks/linalg/DetBench.php b/benchmarks/linalg/DetBench.php new file mode 100644 index 0000000..d734ad1 --- /dev/null +++ b/benchmarks/linalg/DetBench.php @@ -0,0 +1,39 @@ +testArray = $params['testArray']; + } + + /** + * @BeforeMethods("setUp") + * @Revs(1000) + * @Iterations(5) + * @ParamProviders({ + * "provideArrays" + * }) + */ + public function benchDet($params): void + { + \NDArray::det($this->testArray); + } + + public function provideArrays() { + yield ['testArray' => \NDArray::ones([1, 100])]; + yield ['testArray' => \NDArray::ones([1, 500])]; + yield ['testArray' => \NDArray::ones([1, 1000])]; + yield ['testArray' => \NDArray::ones([10, 100])]; + yield ['testArray' => \NDArray::ones([10, 500])]; + yield ['testArray' => \NDArray::ones([1000, 500])]; + } + } +?> diff --git a/benchmarks/linalg/DotBench.php b/benchmarks/linalg/DotBench.php new file mode 100644 index 0000000..8a392b3 --- /dev/null +++ b/benchmarks/linalg/DotBench.php @@ -0,0 +1,39 @@ +testArray = $params['testArray']; + } + + /** + * @BeforeMethods("setUp") + * @Revs(1000) + * @Iterations(5) + * @ParamProviders({ + * "provideArrays" + * }) + */ + public function benchDot($params): void + { + \NDArray::dot($this->testArray, \NDArray::transpose($this->testArray)); + } + + public function provideArrays() { + yield ['testArray' => \NDArray::ones([1, 100])]; + yield ['testArray' => \NDArray::ones([1, 500])]; + yield ['testArray' => \NDArray::ones([1, 1000])]; + yield ['testArray' => \NDArray::ones([10, 100])]; + yield ['testArray' => \NDArray::ones([1000, 500])]; + yield ['testArray' => \NDArray::ones([10000, 100])]; + } + } +?> diff --git a/benchmarks/linalg/EigBench.php b/benchmarks/linalg/EigBench.php new file mode 100644 index 0000000..d5495f5 --- /dev/null +++ b/benchmarks/linalg/EigBench.php @@ -0,0 +1,36 @@ +testArray = $params['testArray']; + } + + /** + * @BeforeMethods("setUp") + * @Revs(1000) + * @Iterations(5) + * @ParamProviders({ + * "provideArrays" + * }) + */ + public function benchEig($params): void + { + \NDArray::eig($this->testArray); + } + + public function provideArrays() { + yield ['testArray' => \NDArray::ones([100, 100])]; + yield ['testArray' => \NDArray::ones([500, 500])]; + yield ['testArray' => \NDArray::ones([1000, 1000])]; + } + } +?> diff --git a/benchmarks/linalg/InnerBench.php b/benchmarks/linalg/InnerBench.php new file mode 100644 index 0000000..b3b97af --- /dev/null +++ b/benchmarks/linalg/InnerBench.php @@ -0,0 +1,39 @@ +testArray = $params['testArray']; + } + + /** + * @BeforeMethods("setUp") + * @Revs(1000) + * @Iterations(5) + * @ParamProviders({ + * "provideArrays" + * }) + */ + public function benchInner($params): void + { + \NDArray::inner($this->testArray, $this->testArray); + } + + public function provideArrays() { + yield ['testArray' => \NDArray::ones([1, 100])]; + yield ['testArray' => \NDArray::ones([1, 500])]; + yield ['testArray' => \NDArray::ones([1, 1000])]; + yield ['testArray' => \NDArray::ones([10, 100])]; + yield ['testArray' => \NDArray::ones([1000, 500])]; + yield ['testArray' => \NDArray::ones([10000, 100])]; + } + } +?> diff --git a/benchmarks/linalg/InvBench.php b/benchmarks/linalg/InvBench.php new file mode 100644 index 0000000..3b3f8d8 --- /dev/null +++ b/benchmarks/linalg/InvBench.php @@ -0,0 +1,49 @@ +testArray = $params['testArray']; + } + + /** + * @BeforeMethods("setUp") + * @Revs(1000) + * @Iterations(5) + * @ParamProviders({ + * "provideArrays" + * }) + */ + public function benchInv($params): void + { + \NDArray::inv($this->testArray); + } + + private function createArray($ndim) { + $symmetric = \NDArray::ones([$ndim, $ndim]); + for ($i=0; $i<$ndim; $i++) { + $symmetric[$i][$i] += $ndim * 10; + } + return $symmetric; + } + + + public function provideArrays() { + $testSizes = array(10, 100, 1000); + foreach ($testSizes as &$value) { + $currTestMatrix = $this->createArray($value); + yield ['testArray' => $currTestMatrix]; + } + } + } +?> diff --git a/benchmarks/linalg/LstsqBench.php b/benchmarks/linalg/LstsqBench.php new file mode 100644 index 0000000..ae9e4f3 --- /dev/null +++ b/benchmarks/linalg/LstsqBench.php @@ -0,0 +1,40 @@ +testArray = $params['testArray']; + } + + /** + * @BeforeMethods("setUp") + * @Revs(1000) + * @Iterations(5) + * @ParamProviders({ + * "provideArrays" + * }) + */ + public function benchlstsq($params): void + { + \NDArray::lstsq($this->testArray, $this->testArray); + } + + public function provideArrays() { + yield ['testArray' => \NDArray::ones([1, 100])]; + yield ['testArray' => \NDArray::ones([1, 500])]; + yield ['testArray' => \NDArray::ones([1000, 1])]; + yield ['testArray' => \NDArray::ones([10, 100])]; + yield ['testArray' => \NDArray::ones([50, 100])]; + yield ['testArray' => \NDArray::ones([10, 500])]; + yield ['testArray' => \NDArray::ones([50, 500])]; + } + } +?> diff --git a/benchmarks/linalg/LuBench.php b/benchmarks/linalg/LuBench.php new file mode 100644 index 0000000..6bca723 --- /dev/null +++ b/benchmarks/linalg/LuBench.php @@ -0,0 +1,36 @@ +testArray = $params['testArray']; + } + + /** + * @BeforeMethods("setUp") + * @Revs(1000) + * @Iterations(5) + * @ParamProviders({ + * "provideArrays" + * }) + */ + public function benchLu($params): void + { + \NDArray::lu($this->testArray); + } + + public function provideArrays() { + yield ['testArray' => \NDArray::ones([100, 100])]; + yield ['testArray' => \NDArray::ones([500, 500])]; + yield ['testArray' => \NDArray::ones([1000, 1000])]; + } + } +?> diff --git a/benchmarks/linalg/MatmulBench.php b/benchmarks/linalg/MatmulBench.php new file mode 100644 index 0000000..00db476 --- /dev/null +++ b/benchmarks/linalg/MatmulBench.php @@ -0,0 +1,39 @@ +testArray = $params['testArray']; + } + + /** + * @BeforeMethods("setUp") + * @Revs(1000) + * @Iterations(5) + * @ParamProviders({ + * "provideArrays" + * }) + */ + public function benchMatmul($params): void + { + \NDArray::dot($this->testArray, \NDArray::transpose($this->testArray)); + } + + public function provideArrays() { + yield ['testArray' => \NDArray::ones([1, 100])]; + yield ['testArray' => \NDArray::ones([1, 500])]; + yield ['testArray' => \NDArray::ones([1, 1000])]; + yield ['testArray' => \NDArray::ones([10, 100])]; + yield ['testArray' => \NDArray::ones([1000, 500])]; + yield ['testArray' => \NDArray::ones([10000, 100])]; + } + } +?> diff --git a/benchmarks/linalg/Matrix_Rank_Bench.php b/benchmarks/linalg/Matrix_Rank_Bench.php new file mode 100644 index 0000000..78fb3dc --- /dev/null +++ b/benchmarks/linalg/Matrix_Rank_Bench.php @@ -0,0 +1,39 @@ +testArray = $params['testArray']; + } + + /** + * @BeforeMethods("setUp") + * @Revs(1000) + * @Iterations(5) + * @ParamProviders({ + * "provideArrays" + * }) + */ + public function benchMatrix_rank($params): void + { + \NDArray::matrix_rank($this->testArray, 0.0); + } + + public function provideArrays() { + yield ['testArray' => \NDArray::ones([1, 100])]; + yield ['testArray' => \NDArray::ones([1, 500])]; + yield ['testArray' => \NDArray::ones([1, 1000])]; + yield ['testArray' => \NDArray::ones([10, 100])]; + yield ['testArray' => \NDArray::ones([1000, 500])]; + yield ['testArray' => \NDArray::ones([10000, 100])]; + } + } +?> diff --git a/benchmarks/linalg/NormBench.php b/benchmarks/linalg/NormBench.php new file mode 100644 index 0000000..b6e7a24 --- /dev/null +++ b/benchmarks/linalg/NormBench.php @@ -0,0 +1,56 @@ +testArray = $params['testArray']; + } + + /** + * @BeforeMethods("setUp") + * @Revs(1000) + * @Iterations(5) + * @ParamProviders({ + * "provideArraysL1" + * }) + */ + public function benchNormL1($params): void + { + \NDArray::norm($this->testArray, 1); + } + + /** + * @BeforeMethods("setUp") + * @ParamProviders({ + * "provideArraysL2" + * }) + */ + public function benchNormL2($params): void + { + \NDArray::norm($this->testArray, 2); + } + + public function provideArraysL1() { + yield ['testArray' => \NDArray::ones([1, 100])]; + yield ['testArray' => \NDArray::ones([1, 500])]; + yield ['testArray' => \NDArray::ones([1, 1000])]; + yield ['testArray' => \NDArray::ones([10, 100])]; + yield ['testArray' => \NDArray::ones([1000, 500])]; + yield ['testArray' => \NDArray::ones([10000, 100])]; + } + + public function provideArraysL2() { + yield ['testArray' => \NDArray::ones([10, 100])]; + yield ['testArray' => \NDArray::ones([1000, 500])]; + yield ['testArray' => \NDArray::ones([10000, 100])]; + } + } +?> diff --git a/benchmarks/linalg/OuterBench.php b/benchmarks/linalg/OuterBench.php new file mode 100644 index 0000000..33ed60c --- /dev/null +++ b/benchmarks/linalg/OuterBench.php @@ -0,0 +1,36 @@ +testArray = $params['testArray']; + } + + /** + * @BeforeMethods("setUp") + * @Revs(1000) + * @Iterations(5) + * @ParamProviders({ + * "provideArrays" + * }) + */ + public function benchOuter($params): void + { + \NDArray::outer($this->testArray, $this->testArray); + } + + public function provideArrays() { + yield ['testArray' => \NDArray::ones([100])]; + yield ['testArray' => \NDArray::ones([500])]; + yield ['testArray' => \NDArray::ones([1000])]; + } + } +?> diff --git a/benchmarks/linalg/QrBench.php b/benchmarks/linalg/QrBench.php new file mode 100644 index 0000000..0ff4371 --- /dev/null +++ b/benchmarks/linalg/QrBench.php @@ -0,0 +1,38 @@ +testArray = $params['testArray']; + } + + /** + * @BeforeMethods("setUp") + * @Revs(1000) + * @Iterations(5) + * @ParamProviders({ + * "provideArrays" + * }) + */ + public function benchQr($params): void + { + \NDArray::qr($this->testArray); + } + + public function provideArrays() { + yield ['testArray' => \NDArray::ones([100, 100])]; + yield ['testArray' => \NDArray::ones([500, 500])]; + yield ['testArray' => \NDArray::ones([1000, 500])]; + yield ['testArray' => \NDArray::ones([1000, 1000])]; + yield ['testArray' => \NDArray::ones([10000, 1000])]; + } + } +?> diff --git a/benchmarks/linalg/SVDBench.php b/benchmarks/linalg/SVDBench.php new file mode 100644 index 0000000..894115c --- /dev/null +++ b/benchmarks/linalg/SVDBench.php @@ -0,0 +1,39 @@ +testArray = $params['testArray']; + } + + /** + * @BeforeMethods("setUp") + * @Revs(1000) + * @Iterations(5) + * @ParamProviders({ + * "provideArrays" + * }) + */ + public function benchSvd($params): void + { + \NDArray::svd($this->testArray); + } + + public function provideArrays() { + yield ['testArray' => \NDArray::ones([1, 100])]; + yield ['testArray' => \NDArray::ones([1, 500])]; + yield ['testArray' => \NDArray::ones([1, 1000])]; + yield ['testArray' => \NDArray::ones([10, 100])]; + yield ['testArray' => \NDArray::ones([1000, 500])]; + yield ['testArray' => \NDArray::ones([10000, 100])]; + } + } +?> diff --git a/benchmarks/linalg/SolveBench.php b/benchmarks/linalg/SolveBench.php new file mode 100644 index 0000000..2c38f54 --- /dev/null +++ b/benchmarks/linalg/SolveBench.php @@ -0,0 +1,41 @@ +testArrayA = $params['testArrayA']; + $this->testArrayB = $params['testArrayB']; + } + + /** + * @BeforeMethods("setUp") + * @Revs(1000) + * @Iterations(5) + * @ParamProviders({ + * "provideArrays" + * }) + */ + public function benchSolve($params): void + { + \NDArray::solve($this->testArrayA, $this->testArrayB); + } + + public function provideArrays() { + yield ['testArrayA' => \NDArray::ones([2, 2]), 'testArrayB' => \NDArray::full([2, 2], 10)]; + yield ['testArrayA' => \NDArray::ones([10, 10]), 'testArrayB' => \NDArray::full([10, 10], 10)]; + yield ['testArrayA' => \NDArray::ones([500, 500]), 'testArrayB' => \NDArray::full([500, 500], 10)]; + yield ['testArrayA' => \NDArray::ones([1000, 1000]), 'testArrayB' => \NDArray::full([1000, 1000], 10)]; + } + } +?> diff --git a/benchmarks/linalg/TraceBench.php b/benchmarks/linalg/TraceBench.php new file mode 100644 index 0000000..f23d036 --- /dev/null +++ b/benchmarks/linalg/TraceBench.php @@ -0,0 +1,36 @@ +testArray = $params['testArray']; + } + + /** + * @BeforeMethods("setUp") + * @Revs(1000) + * @Iterations(5) + * @ParamProviders({ + * "provideArrays" + * }) + */ + public function benchTrace($params): void + { + \NDArray::trace($this->testArray); + } + + public function provideArrays() { + yield ['testArray' => \NDArray::ones([100, 100])]; + yield ['testArray' => \NDArray::ones([500, 500])]; + yield ['testArray' => \NDArray::ones([1000, 1000])]; + } + } +?> diff --git a/benchmarks/math/AddBench.php b/benchmarks/math/AddBench.php new file mode 100644 index 0000000..dfdf1ba --- /dev/null +++ b/benchmarks/math/AddBench.php @@ -0,0 +1,39 @@ +testArray = $params['testArray']; + } + + /** + * @BeforeMethods("setUp") + * @Revs(1000) + * @Iterations(5) + * @ParamProviders({ + * "provideArrays" + * }) + */ + public function benchAdd($params): void + { + \NDArray::add($this->testArray, $this->testArray); + } + + public function provideArrays() { + yield ['testArray' => \NDArray::ones([1, 100])]; + yield ['testArray' => \NDArray::ones([1, 500])]; + yield ['testArray' => \NDArray::ones([1, 1000])]; + yield ['testArray' => \NDArray::ones([10, 100])]; + yield ['testArray' => \NDArray::ones([1000, 500])]; + yield ['testArray' => \NDArray::ones([10000, 100])]; + } + } +?> diff --git a/benchmarks/math/DivideBench.php b/benchmarks/math/DivideBench.php new file mode 100644 index 0000000..ac658ab --- /dev/null +++ b/benchmarks/math/DivideBench.php @@ -0,0 +1,39 @@ +testArray = $params['testArray']; + } + + /** + * @BeforeMethods("setUp") + * @Revs(1000) + * @Iterations(5) + * @ParamProviders({ + * "provideArrays" + * }) + */ + public function benchDivide($params): void + { + \NDArray::divide($this->testArray, $this->testArray); + } + + public function provideArrays() { + yield ['testArray' => \NDArray::full([1, 100], 2)]; + yield ['testArray' => \NDArray::full([1, 500], 2)]; + yield ['testArray' => \NDArray::full([1, 1000], 2)]; + yield ['testArray' => \NDArray::full([10, 100], 2)]; + yield ['testArray' => \NDArray::full([1000, 500], 2)]; + yield ['testArray' => \NDArray::full([10000, 100], 2)]; + } + } +?> diff --git a/benchmarks/math/ModBench.php b/benchmarks/math/ModBench.php new file mode 100644 index 0000000..d323b5c --- /dev/null +++ b/benchmarks/math/ModBench.php @@ -0,0 +1,39 @@ +testArray = $params['testArray']; + } + + /** + * @BeforeMethods("setUp") + * @Revs(1000) + * @Iterations(5) + * @ParamProviders({ + * "provideArrays" + * }) + */ + public function benchMod($params): void + { + \NDArray::mod($this->testArray, $this->testArray); + } + + public function provideArrays() { + yield ['testArray' => \NDArray::full([1, 100], 2)]; + yield ['testArray' => \NDArray::full([1, 500], 2)]; + yield ['testArray' => \NDArray::full([1, 1000], 2)]; + yield ['testArray' => \NDArray::full([10, 100], 2)]; + yield ['testArray' => \NDArray::full([1000, 500], 2)]; + yield ['testArray' => \NDArray::full([10000, 100], 2)]; + } + } +?> diff --git a/benchmarks/math/MultiplyBench.php b/benchmarks/math/MultiplyBench.php new file mode 100644 index 0000000..a1aaa9b --- /dev/null +++ b/benchmarks/math/MultiplyBench.php @@ -0,0 +1,39 @@ +testArray = $params['testArray']; + } + + /** + * @BeforeMethods("setUp") + * @Revs(1000) + * @Iterations(5) + * @ParamProviders({ + * "provideArrays" + * }) + */ + public function benchMultiply($params): void + { + \NDArray::multiply($this->testArray, $this->testArray); + } + + public function provideArrays() { + yield ['testArray' => \NDArray::full([1, 100], 2)]; + yield ['testArray' => \NDArray::full([1, 500], 2)]; + yield ['testArray' => \NDArray::full([1, 1000], 2)]; + yield ['testArray' => \NDArray::full([10, 100], 2)]; + yield ['testArray' => \NDArray::full([1000, 500], 2)]; + yield ['testArray' => \NDArray::full([10000, 100], 2)]; + } + } +?> diff --git a/benchmarks/math/NegativeBench.php b/benchmarks/math/NegativeBench.php new file mode 100644 index 0000000..be54f94 --- /dev/null +++ b/benchmarks/math/NegativeBench.php @@ -0,0 +1,39 @@ +testArray = $params['testArray']; + } + + /** + * @BeforeMethods("setUp") + * @Revs(1000) + * @Iterations(5) + * @ParamProviders({ + * "provideArrays" + * }) + */ + public function benchNegative($params): void + { + \NDArray::negative($this->testArray); + } + + public function provideArrays() { + yield ['testArray' => \NDArray::ones([1, 100])]; + yield ['testArray' => \NDArray::ones([1, 500])]; + yield ['testArray' => \NDArray::ones([1, 1000])]; + yield ['testArray' => \NDArray::ones([10, 100])]; + yield ['testArray' => \NDArray::ones([1000, 500])]; + yield ['testArray' => \NDArray::ones([10000, 100])]; + } + } +?> diff --git a/benchmarks/math/PositiveBench.php b/benchmarks/math/PositiveBench.php new file mode 100644 index 0000000..37a1115 --- /dev/null +++ b/benchmarks/math/PositiveBench.php @@ -0,0 +1,39 @@ +testArray = $params['testArray']; + } + + /** + * @BeforeMethods("setUp") + * @Revs(1000) + * @Iterations(5) + * @ParamProviders({ + * "provideArrays" + * }) + */ + public function benchPositive($params): void + { + \NDArray::positive($this->testArray); + } + + public function provideArrays() { + yield ['testArray' => \NDArray::full([1, 100], -2)]; + yield ['testArray' => \NDArray::full([1, 500], -2)]; + yield ['testArray' => \NDArray::full([1, 1000], -2)]; + yield ['testArray' => \NDArray::full([10, 100], -2)]; + yield ['testArray' => \NDArray::full([1000, 500], -2)]; + yield ['testArray' => \NDArray::full([10000, 100], -2)]; + } + } +?> diff --git a/benchmarks/math/PowBench.php b/benchmarks/math/PowBench.php new file mode 100644 index 0000000..ce4778b --- /dev/null +++ b/benchmarks/math/PowBench.php @@ -0,0 +1,39 @@ +testArray = $params['testArray']; + } + + /** + * @BeforeMethods("setUp") + * @Revs(1000) + * @Iterations(5) + * @ParamProviders({ + * "provideArrays" + * }) + */ + public function benchPow($params): void + { + \NDArray::pow($this->testArray, $this->testArray); + } + + public function provideArrays() { + yield ['testArray' => \NDArray::full([1, 100], 2)]; + yield ['testArray' => \NDArray::full([1, 500], 2)]; + yield ['testArray' => \NDArray::full([1, 1000], 2)]; + yield ['testArray' => \NDArray::full([10, 100], 2)]; + yield ['testArray' => \NDArray::full([1000, 500], 2)]; + yield ['testArray' => \NDArray::full([10000, 100], 2)]; + } + } +?> diff --git a/benchmarks/math/ProdBench.php b/benchmarks/math/ProdBench.php new file mode 100644 index 0000000..4614daf --- /dev/null +++ b/benchmarks/math/ProdBench.php @@ -0,0 +1,39 @@ +testArray = $params['testArray']; + } + + /** + * @BeforeMethods("setUp") + * @Revs(1000) + * @Iterations(5) + * @ParamProviders({ + * "provideArrays" + * }) + */ + public function benchProd($params): void + { + \NDArray::prod($this->testArray); + } + + public function provideArrays() { + yield ['testArray' => \NDArray::ones([1, 100])]; + yield ['testArray' => \NDArray::ones([1, 500])]; + yield ['testArray' => \NDArray::ones([1, 1000])]; + yield ['testArray' => \NDArray::ones([10, 100])]; + yield ['testArray' => \NDArray::ones([1000, 500])]; + yield ['testArray' => \NDArray::ones([10000, 100])]; + } + } +?> diff --git a/benchmarks/math/SubtractBench.php b/benchmarks/math/SubtractBench.php new file mode 100644 index 0000000..0493153 --- /dev/null +++ b/benchmarks/math/SubtractBench.php @@ -0,0 +1,39 @@ +testArray = $params['testArray']; + } + + /** + * @BeforeMethods("setUp") + * @Revs(1000) + * @Iterations(5) + * @ParamProviders({ + * "provideArrays" + * }) + */ + public function benchSubtract($params): void + { + \NDArray::subtract($this->testArray, $this->testArray); + } + + public function provideArrays() { + yield ['testArray' => \NDArray::ones([1, 100])]; + yield ['testArray' => \NDArray::ones([1, 500])]; + yield ['testArray' => \NDArray::ones([1, 1000])]; + yield ['testArray' => \NDArray::ones([10, 100])]; + yield ['testArray' => \NDArray::ones([1000, 500])]; + yield ['testArray' => \NDArray::ones([10000, 100])]; + } + } +?> diff --git a/benchmarks/math/SumBench.php b/benchmarks/math/SumBench.php new file mode 100644 index 0000000..925cf82 --- /dev/null +++ b/benchmarks/math/SumBench.php @@ -0,0 +1,39 @@ +testArray = $params['testArray']; + } + + /** + * @BeforeMethods("setUp") + * @Revs(1000) + * @Iterations(5) + * @ParamProviders({ + * "provideArrays" + * }) + */ + public function benchSum($params): void + { + \NDArray::sum($this->testArray); + } + + public function provideArrays() { + yield ['testArray' => \NDArray::ones([1, 100])]; + yield ['testArray' => \NDArray::ones([1, 500])]; + yield ['testArray' => \NDArray::ones([1, 1000])]; + yield ['testArray' => \NDArray::ones([10, 100])]; + yield ['testArray' => \NDArray::ones([1000, 500])]; + yield ['testArray' => \NDArray::ones([10000, 100])]; + } + } +?> diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..2ec3095 --- /dev/null +++ b/composer.json @@ -0,0 +1,14 @@ +{ + "name": "numpower/extension", + "license": "MIT", + "require-dev": { + "phpbench/phpbench": "^1.0" + }, + "autoload-dev": { + "psr-4": { + "Numpower\\Benchmarks\\": "benchmarks/initializers" + } + }, + "minimum-stability": "dev", + "prefer-stable": true +} diff --git a/numpower.c b/numpower.c index e6dfb6c..fcc4989 100644 --- a/numpower.c +++ b/numpower.c @@ -48,6 +48,39 @@ static zend_object_handlers ndarray_object_handlers; static zend_object_handlers arithmetic_object_handlers; +int * +zval_axis_argument(zval *arg, char *name, int *outsize) +{ + int *result; + zend_string *key; + zend_ulong idx; + zval *val; + *outsize = 0; + if (Z_TYPE_P(arg) != IS_LONG && Z_TYPE_P(arg) != IS_ARRAY) + { + zend_throw_error(NULL, "`%s` argument must be either integer or an array of integers.", name); + return NULL; + } + if (Z_TYPE_P(arg) == IS_LONG) { + result = emalloc(sizeof(int)); + *result = zval_get_long(arg); + *outsize = 1; + return result; + } + + result = emalloc(sizeof(int) * zend_array_count(Z_ARRVAL_P(arg))); + ZEND_HASH_FOREACH_KEY_VAL(Z_ARRVAL_P(arg), idx, key, val) { + if (Z_TYPE_P(val) != IS_LONG) { + efree(result); + zend_throw_error(NULL, "`%s` argument must be either integer or an array of integers.", name); + return NULL; + } + result[idx] = zval_get_long(val); + (*outsize)++; + } ZEND_HASH_FOREACH_END(); + return result; +} + int get_object_uuid(zval* obj) { return Z_LVAL_P(OBJ_PROP_NUM(Z_OBJ_P(obj), 0)); @@ -116,6 +149,29 @@ void RETURN_NDARRAY(NDArray* array, zval* return_value) { } } +NDArray** +ARRAY_OF_NDARRAYS(zval *array, int *size) { + zval *val; + NDArray **rtn = NULL; + int cur_index = 0; + rtn = emalloc(sizeof(NDArray*) * zend_array_count(Z_ARRVAL_P(array))); + ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(array), val) { + if (Z_TYPE_P(val) == IS_ARRAY) { + rtn[cur_index] = ZVAL_TO_NDARRAY(val); + } + if (Z_TYPE_P(val) == IS_OBJECT) { + zend_class_entry* ce = NULL; + ce = Z_OBJCE_P(val); + if (ce == phpsci_ce_NDArray) { + rtn[cur_index] = buffer_get(get_object_uuid(val)); + } + } + cur_index++; + } ZEND_HASH_FOREACH_END(); + *size = cur_index; + return rtn; +} + static int ndarray_objects_compare(zval *obj1, zval *obj2) { zval result; NDArray *a, *b, *c; @@ -1100,7 +1156,7 @@ PHP_METHOD(NDArray, diag) { NDArray *rtn = NULL; zval* target; ZEND_PARSE_PARAMETERS_START(1, 1) - Z_PARAM_ZVAL(target) + Z_PARAM_ZVAL(target) ZEND_PARSE_PARAMETERS_END(); NDArray *nda = ZVAL_TO_NDARRAY(target); if (nda == NULL) return; @@ -1148,30 +1204,42 @@ ZEND_END_ARG_INFO() PHP_METHOD(NDArray, full) { NDArray *rtn = NULL; zval* shape; + HashTable *shape_ht; double fill_value; int *new_shape; + zend_string *key; + zend_ulong idx; + zval *val; ZEND_PARSE_PARAMETERS_START(2, 2) - Z_PARAM_ZVAL(shape) - Z_PARAM_DOUBLE(fill_value) + Z_PARAM_ARRAY(shape) + Z_PARAM_DOUBLE(fill_value) ZEND_PARSE_PARAMETERS_END(); - NDArray *nda_shape = ZVAL_TO_NDARRAY(shape); - if (nda_shape == NULL) { + shape_ht = Z_ARRVAL_P(shape); + + ZEND_HASH_FOREACH_KEY_VAL(shape_ht, idx, key, val) { + if (Z_TYPE_P(val) != IS_LONG) { + zend_throw_error(NULL, "Invalid parameter: Shape elements must be integers."); + return; + } + } ZEND_HASH_FOREACH_END(); + + NDArray *nd_shape = ZVAL_TO_NDARRAY(shape); + + if (nd_shape == NULL) { return; } - if (NDArray_NDIM(nda_shape) != 1) { - zend_throw_error(NULL, "`shape` argument must be a vector"); - NDArray_FREE(nda_shape); + + if (NDArray_NUMELEMENTS(nd_shape) == 0) { + NDArray_FREE(nd_shape); + zend_throw_error(NULL, "Invalid parameter: Expected a non-empty array."); return; } - new_shape = emalloc(sizeof(int) * NDArray_NUMELEMENTS(nda_shape)); - for (int i = 0; i < NDArray_NUMELEMENTS(nda_shape); i++) { - new_shape[i] = (int) NDArray_DDATA(nda_shape)[i]; - } - rtn = NDArray_Full(new_shape, NDArray_NUMELEMENTS(nda_shape), fill_value); + + new_shape = NDArray_ToIntVector(nd_shape); + rtn = NDArray_Full(new_shape, NDArray_NUMELEMENTS(nd_shape), fill_value); + efree(new_shape); - if (Z_TYPE_P(shape) == IS_ARRAY) { - NDArray_FREE(nda_shape); - } + NDArray_FREE(nd_shape); RETURN_NDARRAY(rtn, return_value); } @@ -1330,36 +1398,45 @@ PHP_METHOD(NDArray, allclose) { */ ZEND_BEGIN_ARG_INFO_EX(arginfo_ndarray_transpose, 0, 0, 1) ZEND_ARG_INFO(0, array) -ZEND_ARG_INFO(0, axis) +ZEND_ARG_INFO(0, axes) ZEND_END_ARG_INFO() PHP_METHOD(NDArray, transpose) { NDArray *rtn = NULL; - zval *array; - long axis; - int axis_i; - ZEND_PARSE_PARAMETERS_START(1, 1) - Z_PARAM_ZVAL(array) - Z_PARAM_OPTIONAL - Z_PARAM_LONG(axis) + zval *array, *axes; + HashTable *axes_ht; + zend_string *key; + zend_ulong idx; + zval *val; + ZEND_PARSE_PARAMETERS_START(1, 2) + Z_PARAM_ZVAL(array) + Z_PARAM_OPTIONAL + Z_PARAM_ARRAY(axes) ZEND_PARSE_PARAMETERS_END(); NDArray *nda = ZVAL_TO_NDARRAY(array); if (nda == NULL) { return; } - axis_i = (int)axis; - if (ZEND_NUM_ARGS() == 1) { - rtn = NDArray_Transpose(nda); - add_to_buffer(rtn); - CHECK_INPUT_AND_FREE(array, nda); - RETURN_NDARRAY(rtn, return_value); - } else { - if (NDArray_DEVICE(nda) == NDARRAY_DEVICE_GPU) { - zend_throw_error(NULL, "Axis not supported for GPU operation"); - return; - } - zend_throw_error(NULL, "Not implemented"); - return; + NDArray_Dims *dims = NULL; + if (ZEND_NUM_ARGS() == 2) { + axes_ht = Z_ARRVAL_P(axes); + dims = emalloc(sizeof(NDArray_Dims)); + dims->len = (int)zend_array_count(axes_ht); + dims->ptr = emalloc(sizeof(int) * dims->len); + ZEND_HASH_FOREACH_KEY_VAL(axes_ht, idx, key, val) + { + if (Z_TYPE_P(val) != IS_LONG) { + zend_throw_error(NULL, "Invalid parameter: axes elements must be integers."); + return; + } + dims->ptr[(int)idx] = (int)zval_get_long(val); + }ZEND_HASH_FOREACH_END(); } + + rtn = NDArray_Transpose(nda, dims); + CHECK_INPUT_AND_FREE(array, nda); + if (ZEND_NUM_ARGS() == 2) efree(dims); + if (rtn == NULL) return; + RETURN_NDARRAY(rtn, return_value); } /** @@ -2272,6 +2349,77 @@ PHP_METHOD(NDArray, negative) { RETURN_NDARRAY(rtn, return_value); } +/** + * NDArray::positive + * + * @param execute_data + * @param return_value + */ +ZEND_BEGIN_ARG_INFO_EX(arginfo_ndarray_positive, 0, 0, 1) + ZEND_ARG_INFO(0, array) +ZEND_END_ARG_INFO() +PHP_METHOD(NDArray, positive) { + NDArray *rtn = NULL; + zval *array; + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_ZVAL(array) + ZEND_PARSE_PARAMETERS_END(); + NDArray *nda = ZVAL_TO_NDARRAY(array); + if (nda == NULL) { + return; + } + + if (NDArray_DEVICE(nda) == NDARRAY_DEVICE_CPU) { + rtn = NDArray_Map(nda, float_positive); + } else { +#ifdef HAVE_CUBLAS + rtn = NDArrayMathGPU_ElementWise(nda, cuda_float_positive); +#else + zend_throw_error(NULL, "GPU operations unavailable. CUBLAS not detected."); +#endif + } + if (Z_TYPE_P(array) == IS_ARRAY) { + NDArray_FREE(nda); + } + RETURN_NDARRAY(rtn, return_value); +} + +/** + * NDArray::reciprocal + * + * @param execute_data + * @param return_value + */ +ZEND_BEGIN_ARG_INFO_EX(arginfo_ndarray_reciprocal, 0, 0, 1) + ZEND_ARG_INFO(0, array) +ZEND_END_ARG_INFO() +PHP_METHOD(NDArray, reciprocal) { + NDArray *rtn = NULL; + zval *array; + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_ZVAL(array) + ZEND_PARSE_PARAMETERS_END(); + NDArray *nda = ZVAL_TO_NDARRAY(array); + if (nda == NULL) { + return; + } + + if (NDArray_DEVICE(nda) == NDARRAY_DEVICE_CPU) { + rtn = NDArray_Map(nda, float_reciprocal); + } else { +#ifdef HAVE_CUBLAS + rtn = NDArrayMathGPU_ElementWise(nda, cuda_float_reciprocal); +#else + zend_throw_error(NULL, "GPU operations unavailable. CUBLAS not detected."); +#endif + } + if (Z_TYPE_P(array) == IS_ARRAY) { + NDArray_FREE(nda); + } + RETURN_NDARRAY(rtn, return_value); +} + + /** * NDArray::sign * @@ -2420,20 +2568,27 @@ PHP_METHOD(NDArray, minimum) { ZEND_BEGIN_ARG_INFO_EX(arginfo_ndarray_argmax, 0, 0, 1) ZEND_ARG_INFO(0, a) ZEND_ARG_INFO(0, axis) + ZEND_ARG_INFO(0, keepdims) ZEND_END_ARG_INFO() PHP_METHOD(NDArray, argmax) { NDArray *rtn = NULL; zval *a; long axis; - ZEND_PARSE_PARAMETERS_START(2, 2) + bool keepdims = false; + ZEND_PARSE_PARAMETERS_START(1, 3) Z_PARAM_ZVAL(a) + Z_PARAM_OPTIONAL Z_PARAM_LONG(axis) + Z_PARAM_BOOL(keepdims) ZEND_PARSE_PARAMETERS_END(); NDArray *nda = ZVAL_TO_NDARRAY(a); if (nda == NULL) { return; } - rtn = NDArray_ArgMinMaxCommon(nda, (int)axis, 0, 1); + if (ZEND_NUM_ARGS() == 1) { + axis = 128; + } + rtn = NDArray_ArgMinMaxCommon(nda, (int)axis, keepdims, true); if (rtn == NULL) return; CHECK_INPUT_AND_FREE(a, nda); RETURN_NDARRAY(rtn, return_value); @@ -2446,22 +2601,29 @@ PHP_METHOD(NDArray, argmax) { * @param return_value */ ZEND_BEGIN_ARG_INFO_EX(arginfo_ndarray_argmin, 0, 0, 1) - ZEND_ARG_INFO(0, a) - ZEND_ARG_INFO(0, axis) + ZEND_ARG_INFO(0, a) + ZEND_ARG_INFO(0, axis) + ZEND_ARG_INFO(0, keepdims) ZEND_END_ARG_INFO() PHP_METHOD(NDArray, argmin) { NDArray *rtn = NULL; zval *a; long axis; - ZEND_PARSE_PARAMETERS_START(2, 2) + bool keepdims = false; + ZEND_PARSE_PARAMETERS_START(1, 3) Z_PARAM_ZVAL(a) + Z_PARAM_OPTIONAL Z_PARAM_LONG(axis) + Z_PARAM_BOOL(keepdims) ZEND_PARSE_PARAMETERS_END(); NDArray *nda = ZVAL_TO_NDARRAY(a); if (nda == NULL) { return; } - rtn = NDArray_ArgMinMaxCommon(nda, (int)axis, 0, 0); + if (ZEND_NUM_ARGS() == 1) { + axis = 128; + } + rtn = NDArray_ArgMinMaxCommon(nda, (int)axis, keepdims, false); if (rtn == NULL) return; CHECK_INPUT_AND_FREE(a, nda); RETURN_NDARRAY(rtn, return_value); @@ -3429,7 +3591,7 @@ PHP_METHOD(NDArray, expand_dims) { /** * NDArray::squeeze */ -ZEND_BEGIN_ARG_INFO(arginfo_ndarray_squeeze, 0) +ZEND_BEGIN_ARG_INFO_EX(arginfo_ndarray_squeeze, 0, 0, 1) ZEND_ARG_INFO(0, a) ZEND_ARG_INFO(0, axis) ZEND_END_ARG_INFO() @@ -3438,9 +3600,9 @@ PHP_METHOD(NDArray, squeeze) { zval *a; zval *axis; ZEND_PARSE_PARAMETERS_START(1, 2) - Z_PARAM_ZVAL(a) - Z_PARAM_OPTIONAL - Z_PARAM_ZVAL(axis) + Z_PARAM_ZVAL(a) + Z_PARAM_OPTIONAL + Z_PARAM_ZVAL(axis) ZEND_PARSE_PARAMETERS_END(); if (Z_TYPE_P(axis) != IS_ARRAY && Z_TYPE_P(axis) != IS_LONG && Z_TYPE_P(axis) != IS_OBJECT && ZEND_NUM_ARGS() > 1) { @@ -3466,7 +3628,7 @@ PHP_METHOD(NDArray, squeeze) { /** * NDArray::flip */ -ZEND_BEGIN_ARG_INFO(arginfo_ndarray_flip, 0) +ZEND_BEGIN_ARG_INFO_EX(arginfo_ndarray_flip, 0, 0, 1) ZEND_ARG_INFO(0, a) ZEND_ARG_INFO(0, axis) ZEND_END_ARG_INFO() @@ -3499,44 +3661,276 @@ PHP_METHOD(NDArray, flip) { } /** - * NDArray::append - */ -ZEND_BEGIN_ARG_INFO_EX(arginfo_ndarray_append, 0, 0, 1) -ZEND_ARG_INFO(0, arrays) -ZEND_ARG_INFO(0, axis) +* NDArray::swapaxes +*/ +ZEND_BEGIN_ARG_INFO_EX(arginfo_ndarray_swapaxes, 0, 0, 3) + ZEND_ARG_INFO(0, a) + ZEND_ARG_INFO(0, axis1) + ZEND_ARG_INFO(0, axis2) +ZEND_END_ARG_INFO() +PHP_METHOD(NDArray, swapaxes) { + NDArray *rtn = NULL; + zval *a; + long axis1, axis2; + ZEND_PARSE_PARAMETERS_START(3, 3) + Z_PARAM_ZVAL(a) + Z_PARAM_LONG(axis1) + Z_PARAM_LONG(axis2) + ZEND_PARSE_PARAMETERS_END(); + + NDArray *nda = ZVAL_TO_NDARRAY(a); + if (nda == NULL) { + return; + } + + rtn = NDArray_SwapAxes(nda, (int) axis1, (int) axis2); + + CHECK_INPUT_AND_FREE(a, nda); + if (rtn == NULL) { + return; + } + RETURN_NDARRAY(rtn, return_value); +} + +/** +* NDArray::rollaxis +*/ +ZEND_BEGIN_ARG_INFO_EX(arginfo_ndarray_rollaxis, 0, 0, 2) + ZEND_ARG_INFO(0, a) + ZEND_ARG_INFO(0, axis) + ZEND_ARG_INFO(0, start) +ZEND_END_ARG_INFO() +PHP_METHOD(NDArray, rollaxis) { + NDArray *rtn = NULL; + zval *a; + long axis, start = 0; + ZEND_PARSE_PARAMETERS_START(2, 3) + Z_PARAM_ZVAL(a) + Z_PARAM_LONG(axis) + Z_PARAM_OPTIONAL + Z_PARAM_LONG(start) + ZEND_PARSE_PARAMETERS_END(); + + NDArray *nda = ZVAL_TO_NDARRAY(a); + if (nda == NULL) { + return; + } + + rtn = NDArray_Rollaxis(nda, (int) axis, (int) start); + + CHECK_INPUT_AND_FREE(a, nda); + if (rtn == NULL) { + return; + } + RETURN_NDARRAY(rtn, return_value); +} + +/** +* NDArray::moveaxis +*/ +ZEND_BEGIN_ARG_INFO_EX(arginfo_ndarray_moveaxis, 0, 0, 3) +ZEND_ARG_INFO(0, a) +ZEND_ARG_INFO(0, source) +ZEND_ARG_INFO(0, destination) ZEND_END_ARG_INFO() -PHP_METHOD(NDArray, append) { +PHP_METHOD(NDArray, moveaxis) { + NDArray *rtn = NULL; + zval *a; + zval *source, *destination; + ZEND_PARSE_PARAMETERS_START(3, 3) + Z_PARAM_ZVAL(a) + Z_PARAM_ZVAL(source) + Z_PARAM_ZVAL(destination) + ZEND_PARSE_PARAMETERS_END(); + int src_size, dest_size; + int *src = zval_axis_argument(source, "source", &src_size); + int *dest = zval_axis_argument(destination, "destination", &dest_size); + if (src == NULL) return; + if (dest == NULL) return; + + NDArray *nda = ZVAL_TO_NDARRAY(a); + if (nda == NULL) { + return; + } + + rtn = NDArray_Moveaxis(nda, src, dest, src_size, dest_size); + + CHECK_INPUT_AND_FREE(a, nda); + efree(src); + efree(dest); + if (rtn == NULL) { + return; + } + RETURN_NDARRAY(rtn, return_value); +} + +/** +* NDArray::vstack +*/ +ZEND_BEGIN_ARG_INFO_EX(arginfo_ndarray_vstack, 0, 0, 1) + ZEND_ARG_INFO(0, arrays) +ZEND_END_ARG_INFO() +PHP_METHOD(NDArray, vstack) { NDArray *rtn = NULL; zval *arrays; - long axis; + int num_args; + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_ARRAY(arrays) + ZEND_PARSE_PARAMETERS_END(); + NDArray **ndarrays = ARRAY_OF_NDARRAYS(arrays, &num_args); + if (ndarrays == NULL) return; + rtn = NDArray_VSTACK(ndarrays, num_args); + + for (int i = 0; i < num_args; i++) { + NDArray_FREE(ndarrays[i]); + } + efree(ndarrays); + RETURN_NDARRAY(rtn, return_value); +} + +/** +* NDArray::hstack +*/ +ZEND_BEGIN_ARG_INFO_EX(arginfo_ndarray_hstack, 0, 0, 1) + ZEND_ARG_INFO(0, arrays) +ZEND_END_ARG_INFO() +PHP_METHOD(NDArray, hstack) { + NDArray *rtn = NULL; + zval *arrays; + int num_args; + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_ARRAY(arrays) + ZEND_PARSE_PARAMETERS_END(); + NDArray **ndarrays = ARRAY_OF_NDARRAYS(arrays, &num_args); + if (ndarrays == NULL) return; + rtn = NDArray_HSTACK(ndarrays, num_args); + + for (int i = 0; i < num_args; i++) { + NDArray_FREE(ndarrays[i]); + } + efree(ndarrays); + RETURN_NDARRAY(rtn, return_value); +} + +/** +* NDArray::dstack +*/ +ZEND_BEGIN_ARG_INFO_EX(arginfo_ndarray_dstack, 0, 0, 1) + ZEND_ARG_INFO(0, arrays) +ZEND_END_ARG_INFO() +PHP_METHOD(NDArray, dstack) { + NDArray *rtn = NULL; + zval *arrays; + int num_args; + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_ARRAY(arrays) + ZEND_PARSE_PARAMETERS_END(); + NDArray **ndarrays = ARRAY_OF_NDARRAYS(arrays, &num_args); + if (ndarrays == NULL) return; + rtn = NDArray_DSTACK(ndarrays, num_args); + + for (int i = 0; i < num_args; i++) { + NDArray_FREE(ndarrays[i]); + } + efree(ndarrays); + RETURN_NDARRAY(rtn, return_value); +} + +/** +* NDArray::column_stack +*/ +ZEND_BEGIN_ARG_INFO_EX(arginfo_ndarray_column_stack, 0, 0, 1) + ZEND_ARG_INFO(0, arrays) +ZEND_END_ARG_INFO() +PHP_METHOD(NDArray, column_stack) { + NDArray *rtn = NULL; + zval *arrays; + int num_args; + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_ARRAY(arrays) + ZEND_PARSE_PARAMETERS_END(); + NDArray **ndarrays = ARRAY_OF_NDARRAYS(arrays, &num_args); + if (ndarrays == NULL) return; + rtn = NDArray_ColumnStack(ndarrays, num_args); + + for (int i = 0; i < num_args; i++) { + NDArray_FREE(ndarrays[i]); + } + efree(ndarrays); + RETURN_NDARRAY(rtn, return_value); +} + +/** +* NDArray::concatenate +*/ +ZEND_BEGIN_ARG_INFO_EX(arginfo_ndarray_concatenate, 0, 0, 1) + ZEND_ARG_INFO(0, arrays) + ZEND_ARG_INFO(0, axis) +ZEND_END_ARG_INFO() +PHP_METHOD(NDArray, concatenate) { + NDArray *rtn = NULL; + zval *arrays; + int num_args; + zval *axis = NULL; ZEND_PARSE_PARAMETERS_START(1, 2) Z_PARAM_ARRAY(arrays) Z_PARAM_OPTIONAL - Z_PARAM_LONG(axis) + Z_PARAM_ZVAL(axis) ZEND_PARSE_PARAMETERS_END(); + NDArray **ndarrays = ARRAY_OF_NDARRAYS(arrays, &num_args); + if (ndarrays == NULL) return; - HashTable *ht = Z_ARRVAL_P(arrays); - zval* arr_val; - NDArray **ndarrays = emalloc(sizeof(NDArray*) * 128); - zval **input_zvals = emalloc(sizeof(zval*) * 128); - int num_arrays = 0; - ZEND_HASH_FOREACH_VAL(ht, arr_val) { - ndarrays[num_arrays] = ZVAL_TO_NDARRAY(arr_val); - input_zvals[num_arrays] = arr_val; - if (ndarrays[num_arrays] == NULL) { - goto fail; + if (ZEND_NUM_ARGS() > 1 && Z_TYPE_P(axis) == IS_NULL) { + rtn = NDArray_ConcatenateFlat(ndarrays, num_args); + } else { + if (ZEND_NUM_ARGS() == 1) { + rtn = NDArray_Concatenate(ndarrays, num_args, 0); + } else { + rtn = NDArray_Concatenate(ndarrays, num_args, zval_get_long(axis)); } - num_arrays++; - } ZEND_HASH_FOREACH_END(); - rtn = NDArray_Append(ndarrays, -1, num_arrays); + } - for (int i = 0; i < num_arrays; i++) { - CHECK_INPUT_AND_FREE(input_zvals[i], ndarrays[i]); + for (int i = 0; i < num_args; i++) { + NDArray_FREE(ndarrays[i]); } + efree(ndarrays); RETURN_NDARRAY(rtn, return_value); -fail: - efree(input_zvals); +} + +/** + * NDArray::append + */ +ZEND_BEGIN_ARG_INFO_EX(arginfo_ndarray_append, 0, 0, 2) + ZEND_ARG_INFO(0, array) + ZEND_ARG_INFO(0, values) + ZEND_ARG_INFO(0, axis) +ZEND_END_ARG_INFO() + PHP_METHOD(NDArray, append) { + NDArray *rtn = NULL; + int num_args; + zval *axis = NULL, *array, *values; + ZEND_PARSE_PARAMETERS_START(2, 3) + Z_PARAM_ZVAL(array) + Z_PARAM_ZVAL(values) + Z_PARAM_OPTIONAL + Z_PARAM_ZVAL(axis) + ZEND_PARSE_PARAMETERS_END(); + + NDArray **ndarrays = (NDArray**)emalloc(sizeof(NDArray*) * 2); + ndarrays[0] = ZVAL_TO_NDARRAY(array); + ndarrays[1] = ZVAL_TO_NDARRAY(values); + num_args = 2; + if (ndarrays == NULL) return; + + if (ZEND_NUM_ARGS() == 2) { + rtn = NDArray_ConcatenateFlat(ndarrays, num_args); + } else { + rtn = NDArray_Concatenate(ndarrays, num_args, zval_get_long(axis)); + } + CHECK_INPUT_AND_FREE(array, ndarrays[0]); + CHECK_INPUT_AND_FREE(values, ndarrays[1]); efree(ndarrays); + RETURN_NDARRAY(rtn, return_value); } /** @@ -4671,6 +5065,15 @@ static const zend_function_entry class_NDArray_methods[] = { ZEND_ME(NDArray, append, arginfo_ndarray_append, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) ZEND_ME(NDArray, expand_dims, arginfo_ndarray_expand_dims, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) ZEND_ME(NDArray, squeeze, arginfo_ndarray_squeeze, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) + ZEND_ME(NDArray, flip, arginfo_ndarray_flip, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) + ZEND_ME(NDArray, swapaxes, arginfo_ndarray_swapaxes, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) + ZEND_ME(NDArray, rollaxis, arginfo_ndarray_rollaxis, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) + ZEND_ME(NDArray, moveaxis, arginfo_ndarray_moveaxis, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) + ZEND_ME(NDArray, vstack, arginfo_ndarray_vstack, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) + ZEND_ME(NDArray, hstack, arginfo_ndarray_hstack, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) + ZEND_ME(NDArray, dstack, arginfo_ndarray_dstack, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) + ZEND_ME(NDArray, column_stack, arginfo_ndarray_column_stack, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) + ZEND_ME(NDArray, concatenate, arginfo_ndarray_concatenate, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) // INDEXING ZEND_ME(NDArray, diagonal, arginfo_ndarray_diagonal, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) @@ -4763,10 +5166,12 @@ static const zend_function_entry class_NDArray_methods[] = { ZEND_ME(NDArray, trunc, arginfo_ndarray_trunc, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) ZEND_ME(NDArray, sinc, arginfo_ndarray_sinc, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) ZEND_ME(NDArray, negative, arginfo_ndarray_negative, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) + ZEND_ME(NDArray, positive, arginfo_ndarray_positive, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) ZEND_ME(NDArray, sign, arginfo_ndarray_sign, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) ZEND_ME(NDArray, clip, arginfo_ndarray_clip, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) ZEND_ME(NDArray, round, arginfo_ndarray_round, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) ZEND_ME(NDArray, rsqrt, arginfo_ndarray_rsqrt, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) + ZEND_ME(NDArray, reciprocal, arginfo_ndarray_reciprocal, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) // STATISTICS ZEND_ME(NDArray, mean, arginfo_ndarray_mean, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) diff --git a/php_numpower.h b/php_numpower.h index 77d6118..c5056d5 100644 --- a/php_numpower.h +++ b/php_numpower.h @@ -64,7 +64,7 @@ ZEND_FUNCTION(print_r_); PHPAPI zend_class_entry *phpsci_ce_NDArray; PHPAPI zend_class_entry *phpsci_ce_ArithmeticOperand; -# define PHP_NDARRAY_VERSION "0.5.2" +# define PHP_NDARRAY_VERSION "0.6.0" # if defined(ZTS) && defined(COMPILE_DL_NDARRAY) ZEND_TSRMLS_CACHE_EXTERN() diff --git a/phpbench.json b/phpbench.json new file mode 100644 index 0000000..9f8ee65 --- /dev/null +++ b/phpbench.json @@ -0,0 +1,4 @@ +{ + "$schema":"./vendor/phpbench/phpbench/phpbench.schema.json", + "runner.bootstrap": "vendor/autoload.php" +} \ No newline at end of file diff --git a/src/dnn.c b/src/dnn.c index 9966dd2..546d56a 100644 --- a/src/dnn.c +++ b/src/dnn.c @@ -338,7 +338,7 @@ NDArrayDNN_Conv2D_Backward(NDArray *input, NDArray *y, NDArray *filters, int ker rtn_temp = NDArray_Empty(output_shape_dw, 4, NDArray_TYPE(input), NDArray_DEVICE(input)); NDArray_FREEDATA(rtn_temp); rtn_temp->data = (void*)outputs[0]; - rtn_dw = NDArray_Transpose(rtn_temp); + rtn_dw = NDArray_Transpose(rtn_temp, NULL); NDArray_FREE(rtn_temp); // Build dW diff --git a/src/initializers.c b/src/initializers.c index 6a600ea..ad00558 100644 --- a/src/initializers.c +++ b/src/initializers.c @@ -448,7 +448,7 @@ NDArray_Zeros(int *shape, int ndim, const char *type, const int device) { } /** - * Initialize NDArray with zeros + * Initialize NDArray with ones * * @param shape * @param ndim diff --git a/src/iterators.c b/src/iterators.c index b920322..31a2170 100644 --- a/src/iterators.c +++ b/src/iterators.c @@ -157,3 +157,112 @@ NDArray_NewElementWiseIter(NDArray *target) { return it; } +int +NDArray_PrepareTwoRawArrayIter(int ndim, int const *shape, + char *dataA, int const *stridesA, + char *dataB, int const *stridesB, + int *out_ndim, int *out_shape, + char **out_dataA, int *out_stridesA, + char **out_dataB, int *out_stridesB) +{ + ndarray_stride_sort_item strideperm[NDARRAY_MAX_DIMS]; + int i, j; + + /* Special case 0 and 1 dimensions */ + if (ndim == 0) { + *out_ndim = 1; + *out_dataA = dataA; + *out_dataB = dataB; + out_shape[0] = 1; + out_stridesA[0] = 0; + out_stridesB[0] = 0; + return 0; + } + else if (ndim == 1) { + int stride_entryA = stridesA[0], stride_entryB = stridesB[0]; + int shape_entry = shape[0]; + *out_ndim = 1; + out_shape[0] = shape[0]; + /* Always make a positive stride for the first operand */ + if (stride_entryA >= 0) { + *out_dataA = dataA; + *out_dataB = dataB; + out_stridesA[0] = stride_entryA; + out_stridesB[0] = stride_entryB; + } + else { + *out_dataA = dataA + stride_entryA * (shape_entry - 1); + *out_dataB = dataB + stride_entryB * (shape_entry - 1); + out_stridesA[0] = -stride_entryA; + out_stridesB[0] = -stride_entryB; + } + return 0; + } + + /* Sort the axes based on the destination strides */ + NDArray_CreateSortedStridePerm(ndim, stridesA, strideperm); + for (i = 0; i < ndim; ++i) { + int iperm = strideperm[ndim - i - 1].perm; + out_shape[i] = shape[iperm]; + out_stridesA[i] = stridesA[iperm]; + out_stridesB[i] = stridesB[iperm]; + } + + /* Reverse any negative strides of operand A */ + for (i = 0; i < ndim; ++i) { + int stride_entryA = out_stridesA[i]; + int stride_entryB = out_stridesB[i]; + int shape_entry = out_shape[i]; + + if (stride_entryA < 0) { + dataA += stride_entryA * (shape_entry - 1); + dataB += stride_entryB * (shape_entry - 1); + out_stridesA[i] = -stride_entryA; + out_stridesB[i] = -stride_entryB; + } + /* Detect 0-size arrays here */ + if (shape_entry == 0) { + *out_ndim = 1; + *out_dataA = dataA; + *out_dataB = dataB; + out_shape[0] = 0; + out_stridesA[0] = 0; + out_stridesB[0] = 0; + return 0; + } + } + + /* Coalesce any dimensions where possible */ + i = 0; + for (j = 1; j < ndim; ++j) { + if (out_shape[i] == 1) { + /* Drop axis i */ + out_shape[i] = out_shape[j]; + out_stridesA[i] = out_stridesA[j]; + out_stridesB[i] = out_stridesB[j]; + } + else if (out_shape[j] == 1) { + /* Drop axis j */ + } + else if (out_stridesA[i] * out_shape[i] == out_stridesA[j] && + out_stridesB[i] * out_shape[i] == out_stridesB[j]) { + /* Coalesce axes i and j */ + out_shape[i] *= out_shape[j]; + } + else { + /* Can't coalesce, go to next i */ + ++i; + out_shape[i] = out_shape[j]; + out_stridesA[i] = out_stridesA[j]; + out_stridesB[i] = out_stridesB[j]; + } + } + ndim = i+1; + + *out_dataA = dataA; + *out_dataB = dataB; + *out_ndim = ndim; + return 0; +} + + diff --git a/src/iterators.h b/src/iterators.h index d1daea3..d3914a6 100644 --- a/src/iterators.h +++ b/src/iterators.h @@ -32,6 +32,12 @@ int NDArrayIteratorPHP_ISDONE(NDArray* array); void NDArrayIteratorPHP_NEXT(NDArray* array); NDArrayIter* NDArray_NewElementWiseIter(NDArray *target); +int NDArray_PrepareTwoRawArrayIter(int ndim, int const *shape, + char *dataA, int const *stridesA, + char *dataB, int const *stridesB, + int *out_ndim, int *out_shape, + char **out_dataA, int *out_stridesA, + char **out_dataB, int *out_stridesB); #define _NDArray_ITER_NEXT1(it) do { \ (it)->dataptr += (it)->strides[0]; \ @@ -86,4 +92,38 @@ NDArrayIter* NDArray_NewElementWiseIter(NDArray *target); } \ } \ } while (0) + + +#define NDARRAY_RAW_ITER_START(idim, ndim, coord, shape) \ + memset((coord), 0, (ndim) * sizeof(coord[0])); \ + do { + +#define NDARRAY_RAW_ITER_ONE_NEXT(idim, ndim, coord, shape, data, strides) \ + for ((idim) = 1; (idim) < (ndim); ++(idim)) { \ + if (++(coord)[idim] == (shape)[idim]) { \ + (coord)[idim] = 0; \ + (data) -= ((shape)[idim] - 1) * (strides)[idim]; \ + } \ + else { \ + (data) += (strides)[idim]; \ + break; \ + } \ + } \ + } while ((idim) < (ndim)) + +#define NDARRAY_RAW_ITER_TWO_NEXT(idim, ndim, coord, shape, \ + dataA, stridesA, dataB, stridesB) \ + for ((idim) = 1; (idim) < (ndim); ++(idim)) { \ + if (++(coord)[idim] == (shape)[idim]) { \ + (coord)[idim] = 0; \ + (dataA) -= ((shape)[idim] - 1) * (stridesA)[idim]; \ + (dataB) -= ((shape)[idim] - 1) * (stridesB)[idim]; \ + } \ + else { \ + (dataA) += (stridesA)[idim]; \ + (dataB) += (stridesB)[idim]; \ + break; \ + } \ + } \ + } while ((idim) < (ndim)) #endif //PHPSCI_NDARRAY_ITERATORS_H diff --git a/src/manipulation.c b/src/manipulation.c index f872065..3bf0099 100644 --- a/src/manipulation.c +++ b/src/manipulation.c @@ -42,13 +42,10 @@ check_and_adjust_axis_msg(int *axis, int ndim) { if (axis == NULL) { return 0; } - - /* Check that index is valid, taking into account negative indices */ if (NDARRAY_UNLIKELY((*axis < -ndim) || (*axis >= ndim))) { - //zend_throw_error(NULL, "Axis is out of bounds for array dimension"); + zend_throw_error(NULL, "Axis is out of bounds for array dimension"); return -1; } - /* adjust negative indices */ if (*axis < 0) { *axis += ndim; @@ -68,40 +65,63 @@ check_and_adjust_axis(int *axis, int ndim) { * @return */ NDArray* -NDArray_Transpose(NDArray *a) { +NDArray_Transpose(NDArray *a, NDArray_Dims *permute) { + int *axes; + int i, n; + int permutation[NDARRAY_MAX_DIMS], reverse_permutation[NDARRAY_MAX_DIMS]; NDArray *ret = NULL; - NDArray *contiguous_ret = NULL; - if (NDArray_NDIM(a) < 2) { - int ndim = NDArray_NDIM(a); - return NDArray_FromNDArray(a, 0, NULL, NULL, &ndim); - } - int *new_shape = emalloc(sizeof(int) * NDArray_NDIM(a)); - int *new_strides = emalloc(sizeof(int) * NDArray_NDIM(a)); + if (permute == NULL) { + n = NDArray_NDIM(a); + for (i = 0; i < n; i++) { + permutation[i] = n-1-i; + } + } + else { + n = permute->len; + axes = permute->ptr; + if (n != NDArray_NDIM(a)) { + zend_throw_error(NULL, "axes don't match array"); + return NULL; + } + for (i = 0; i < n; i++) { + reverse_permutation[i] = -1; + } + for (i = 0; i < n; i++) { + int axis = axes[i]; + if (check_and_adjust_axis(&axis, NDArray_NDIM(a)) < 0) { + zend_throw_error(NULL, "axes don't match array"); + return NULL; + } + if (reverse_permutation[axis] != -1) { + zend_throw_error(NULL, "repeated axis in transpose"); + return NULL; + } + reverse_permutation[axis] = i; + permutation[i] = axis; + } + } - if (new_shape == NULL || new_strides == NULL) { - zend_throw_error(NULL, "failed to allocate memory for new_shape and new_strides."); + ret = NDArray_Copy(a, NDArray_DEVICE(a)); + if (ret == NULL) { return NULL; } - reverse_copy(NDArray_SHAPE(a), new_shape, NDArray_NDIM(a)); - reverse_copy(NDArray_STRIDES(a), new_strides, NDArray_NDIM(a)); + /* fix the dimensions and strides of the return-array */ + for (i = 0; i < n; i++) { + NDArray_SHAPE(ret)[i] = NDArray_SHAPE(a)[permutation[i]]; + NDArray_STRIDES(ret)[i] = NDArray_STRIDES(a)[permutation[i]]; + } + NDArray_ENABLEFLAGS(ret, NDARRAY_ARRAY_F_CONTIGUOUS); if (NDArray_DEVICE(a) == NDARRAY_DEVICE_CPU || (NDArray_DEVICE(a) == NDARRAY_DEVICE_GPU && NDArray_NDIM(a) != 2)) { - ret = NDArray_Copy(a, NDArray_DEVICE(a)); - efree(ret->strides); - efree(ret->dimensions); - ret->strides = new_strides; - ret->dimensions = new_shape; - NDArray_ENABLEFLAGS(ret, NDARRAY_ARRAY_F_CONTIGUOUS); + NDArray * contiguous_ret; contiguous_ret = NDArray_ToContiguous(ret); NDArray_FREE(ret); return contiguous_ret; } else { #ifdef HAVE_CUBLAS - efree(new_strides); - ret = NDArray_Empty(new_shape, NDArray_NDIM(a), NDArray_TYPE(a), NDArray_DEVICE(a)); - cuda_float_transpose(32, 8, NDArray_FDATA(a), NDArray_FDATA(ret), NDArray_SHAPE(a)[1], NDArray_SHAPE(a)[0]); + cuda_float_transpose(32, 8, NDArray_FDATA(ret), NDArray_FDATA(ret), NDArray_SHAPE(a)[1], NDArray_SHAPE(a)[0]); return ret; #endif } @@ -178,7 +198,7 @@ NDArray_Slice(NDArray* array, NDArray** indexes, int num_indices) { int new_strides[NDARRAY_MAX_DIMS]; int new_shape[NDARRAY_MAX_DIMS]; - int i, start = 0, stop = 0, step = 0, n_steps = 0, new_dim = 0, orig_dim = 0; + int i, start = 0, stop = 0, step = 0, n_steps = 0, new_dim = NDArray_NDIM(array), orig_dim = 0, new_dim_step = 0; char *data_ptr = NDArray_DATA(array); SliceObject sliceobj; @@ -210,9 +230,14 @@ NDArray_Slice(NDArray* array, NDArray** indexes, int num_indices) { start = 0; } data_ptr += NDArray_STRIDES(array)[orig_dim] * start; - new_strides[new_dim] = NDArray_STRIDES(array)[orig_dim] * step; - new_shape[new_dim] = n_steps; - new_dim += 1; + new_strides[new_dim_step] = NDArray_STRIDES(array)[orig_dim] * step; + new_shape[new_dim_step] = n_steps; + + if (NDArray_NUMELEMENTS(indexes[i]) == 1) { + new_dim--; + new_dim_step--; + } + new_dim_step += 1; orig_dim += 1; if (sliceobj.start != NULL) { efree(sliceobj.start); @@ -267,46 +292,72 @@ NDArray_Slice(NDArray* array, NDArray** indexes, int num_indices) { NDArray* NDArray_ConcatenateFlat(NDArray **arrays, int num_arrays) { - NDArray *rtn; int iarrays; - int *shape = emalloc(sizeof(int)); + int shape = 0; + NDArray *sliding_view = NULL; + int narrays = num_arrays; - *shape = 0; - if (num_arrays <= 0) { + if (narrays <= 0) { zend_throw_error(NULL, "need at least one array to concatenate"); return NULL; } - for (iarrays = 0; iarrays < num_arrays; ++iarrays) { - *shape += NDArray_NUMELEMENTS(arrays[iarrays]); - if (*shape < 0) { + /* + * Figure out the final concatenated shape starting from the first + * array's shape. + */ + for (iarrays = 0; iarrays < narrays; ++iarrays) { + shape += NDArray_NUMELEMENTS(arrays[iarrays]); + /* Check for overflow */ + if (shape < 0) { zend_throw_error(NULL, "total number of elements " "too large to concatenate"); return NULL; } } - rtn = NDArray_Zeros(shape, 1, NDArray_TYPE(arrays[0]), NDArray_DEVICE(arrays[0])); - int device = NDArray_DEVICE(rtn); + int *ret_shape = emalloc(sizeof(int)); + ret_shape[0] = shape; + NDArray *ret = NDArray_Zeros(ret_shape, 1, NDArray_TYPE(arrays[0]), NDArray_DEVICE(arrays[0])); + int stride = NDArray_ELSIZE(ret); + if (ret == NULL) { + return NULL; + } - int extra_bytes = 0; - char *tmp_data = NULL; - for (int i_array = 0; i_array < num_arrays; i_array++) { - extra_bytes = i_array * (NDArray_NUMELEMENTS(arrays[i_array]) * NDArray_ELSIZE(arrays[0])); - tmp_data = NDArray_DATA(rtn) + extra_bytes; + int *sliding_strides = emalloc(sizeof(int)); + int *sliding_shape = emalloc(sizeof(int)); + sliding_shape[0] = shape; + sliding_strides[0] = stride; + sliding_view = NDArray_FromNDArrayBase(ret, NDArray_DATA(ret), sliding_shape, sliding_strides, 1); + if (sliding_view == NULL) { + NDArray_FREE(ret); + return NULL; + } - switch (NDArray_DEVICE(rtn)) { - case NDARRAY_DEVICE_GPU: + for (iarrays = 0; iarrays < narrays; ++iarrays) { + sliding_view->dimensions[0] = NDArray_NUMELEMENTS(arrays[iarrays]); + if (NDArray_DEVICE(ret) == NDARRAY_DEVICE_CPU) { + memcpy(sliding_view->data, arrays[iarrays]->data, + sliding_view->strides[0] * NDArray_NUMELEMENTS(arrays[iarrays])); + } #ifdef HAVE_CUBLAS - -#endif - break; - default: - memcpy(tmp_data, NDArray_DATA(arrays[i_array]), NDArray_NUMELEMENTS(arrays[i_array]) * NDArray_ELSIZE(arrays[i_array])); + if (NDArray_DEVICE(ret) == NDARRAY_DEVICE_GPU) { + if (NDArray_NDIM(arrays[iarrays]) > 0) { + vmemcpyd2d(arrays[iarrays]->data, sliding_view->data, + sliding_view->strides[0] * NDArray_NUMELEMENTS(arrays[iarrays])); + } else { + vmemcpyh2d(arrays[iarrays]->data, sliding_view->data, + sliding_view->strides[0] * NDArray_NUMELEMENTS(arrays[iarrays])); + } } +#endif + /* Slide to the start of the next window */ + sliding_view->data += sliding_view->strides[0] * NDArray_NUMELEMENTS(arrays[iarrays]); } - return rtn; + + NDArray_FREE(sliding_view); + return ret; } /** @@ -544,8 +595,9 @@ NDArray_AtLeast3D(NDArray *a) { int *new_shape = emalloc(sizeof(int) * 2); new_shape[0] = 1; if (NDArray_NDIM(a) < 2) { - new_shape[1] = 1; - new_shape[2] = NDArray_NUMELEMENTS(a); + new_shape[0] = 1; + new_shape[1] = NDArray_NUMELEMENTS(a); + new_shape[2] = 1; } if (NDArray_NDIM(a) == 2) { new_shape[1] = NDArray_SHAPE(a)[0]; @@ -721,4 +773,307 @@ NDArray_Squeeze(NDArray *a, NDArray *axis) NDArray_RemoveAxesInPlace(ret, unit_dims); return ret; +} + +NDArray* +NDArray_SwapAxes(NDArray *a, int axis1, int axis2) +{ + NDArray_Dims new_axes; + int dims[NDARRAY_MAX_DIMS]; + int n = NDArray_NDIM(a); + int i; + + if (check_and_adjust_axis_msg(&axis1, n) < 0) { + return NULL; + } + if (check_and_adjust_axis_msg(&axis2, n) < 0) { + return NULL; + } + + for (i = 0; i < n; ++i) { + dims[i] = i; + } + dims[axis1] = axis2; + dims[axis2] = axis1; + + new_axes.ptr = dims; + new_axes.len = n; + + return NDArray_Transpose(a, &new_axes); +} + +NDArray* +NDArray_Rollaxis(NDArray *a, int axis, int start) +{ + NDArray *result; + int n = NDArray_NDIM(a); + + if (check_and_adjust_axis_msg(&axis, n) < 0) { + return NULL; + } + + if (start < 0) start += n; + + if (!(0 <= start < n + 1)) { + zend_throw_error(NULL, "'%s' arg requires %d <= %s < %d, but %d was passed in", "start", -n, "start", n+1, start); + return NULL; + } + + if (axis < start) start -= 1; + + if (axis == start) { + return NDArray_Copy(a, NDArray_DEVICE(a)); + } + + int *axes = emalloc(sizeof(int) * n); + for (int i = 0; i < n; i++) { + axes[i] = i; + } + + for (int i = n; i > start; i--) { + axes[i] = axes[i - 1]; + } + axes[start] = axis; + + NDArray_Dims dims; + dims.len = n; + dims.ptr = axes; + + result = NDArray_Transpose(a, &dims); + efree(axes); + return result; +} + + +NDArray* +NDArray_Moveaxis(NDArray *a, int* src, int* dest, int n_source, int n_dest) +{ + NDArray *result; + int order[NDARRAY_MAX_DIMS]; + int n = NDArray_NDIM(a); + + if (check_and_adjust_axis_msg(src, n) < 0) { + return NULL; + } + if (check_and_adjust_axis_msg(dest, n) < 0) { + return NULL; + } + + if (n_source != n_dest) { + zend_throw_error(NULL, "`source` and `destination` must have the same number of elements."); + return NULL; + } + + int found; + int count_order = 0; + for (int i = 0; i < n; i++) { + found = 0; + for (int j = 0; j < n_source; j++) { + if (src[j] == i) found = 1; + } + if (!found) { + order[count_order] = i; + count_order++; + } + } + + for (int i = 0; i < n_source; i++) { + if (dest[i] < 0) { + dest[i] = dest[i] + (count_order + 1); + } + order[dest[i]] = src[i]; + count_order++; + } + + NDArray_Dims order_dims; + order_dims.len = count_order; + order_dims.ptr = order; + return NDArray_Transpose(a, &order_dims); +} + +NDArray* +NDArray_Concatenate(NDArray **arrays, int narrays, int axis) +{ + int iarrays, idim, ndim; + int shape[NDARRAY_MAX_DIMS]; + NDArray *sliding_view = NULL; + + if (narrays <= 0) { + zend_throw_error(NULL, "need at least one array to concatenate"); + return NULL; + } + + ndim = NDArray_NDIM(arrays[0]); + + if (ndim == 0) { + zend_throw_error(NULL, "zero-dimensional arrays cannot be concatenated"); + return NULL; + } + + if (check_and_adjust_axis_msg(&axis, ndim) < 0) { + return NULL; + } + + /* + * Figure out the final concatenated shape starting from the first + * array's shape. + */ + memcpy(shape, NDArray_SHAPE(arrays[0]), ndim * sizeof(shape[0])); + for (iarrays = 1; iarrays < narrays; ++iarrays) { + int *arr_shape; + if (NDArray_NDIM(arrays[iarrays]) != ndim) { + zend_throw_error(NULL, + "all the input arrays must have same number of " + "dimensions, but the array at index %d has %d " + "dimension(s) and the array at index %d has %d " + "dimension(s)", + 0, ndim, iarrays, NDArray_NDIM(arrays[iarrays])); + return NULL; + } + arr_shape = NDArray_SHAPE(arrays[iarrays]); + + for (idim = 0; idim < ndim; ++idim) { + /* Build up the size of the concatenation axis */ + if (idim == axis) { + shape[idim] += arr_shape[idim]; + } + /* Validate that the rest of the dimensions match */ + else if (shape[idim] != arr_shape[idim]) { + zend_throw_error(NULL, + "all the input array dimensions except for the " + "concatenation axis must match exactly, but " + "along dimension %d, the array at index %d has " + "size %d and the array at index %d has size %d", + idim, 0, shape[idim], iarrays, arr_shape[idim]); + return NULL; + } + } + } + int s, strides[NDARRAY_MAX_DIMS]; + int strideperm[NDARRAY_MAX_DIMS]; + + NDArrayDescriptor *descr = NDArray_DESCRIPTOR(arrays[0]); + if (descr == NULL) { + return NULL; + } + + /* + * Figure out the permutation to apply to the strides to match + * the memory layout of the input arrays, using ambiguity + * resolution rules matching that of the NpyIter. + */ + NDArray_CreateMultiSortedStridePerm(narrays, arrays, ndim, strideperm); + s = descr->elsize; + for (idim = ndim-1; idim >= 0; --idim) { + int iperm = strideperm[idim]; + strides[iperm] = s; + s *= shape[iperm]; + } + + int *ret_shape = emalloc(sizeof(int) * ndim); + memcpy(ret_shape, shape, sizeof(int) * ndim); + int *sliding_shape = emalloc(sizeof(int) * ndim); + memcpy(sliding_shape, shape, sizeof(int) * ndim); + int *sliding_strides = emalloc(sizeof(int) * ndim); + /* Allocate the array for the result. This steals the 'dtype' reference. */ + NDArray *ret = NDArray_Zeros(ret_shape, ndim, NDArray_TYPE(arrays[0]), NDArray_DEVICE(arrays[0])); + memcpy(sliding_strides, NDArray_STRIDES(ret), sizeof(int) * ndim); + sliding_view = NDArray_FromNDArrayBase(ret, NDArray_DATA(ret), sliding_shape, sliding_strides ,ndim); + for (iarrays = 0; iarrays < narrays; ++iarrays) { + /* Set the dimension to match the input array's */ + sliding_view->dimensions[axis] = NDArray_SHAPE(arrays[iarrays])[axis]; + /* Copy the data for this array */ + if (NDArray_AssignArray(sliding_view, arrays[iarrays]) < 0) { + NDArray_FREE(sliding_view); + return NULL; + } + /* Slide to the start of the next window */ + sliding_view->data += sliding_view->dimensions[axis] * sliding_view->strides[axis]; + } + NDArray_FREE(sliding_view); + return ret; +} + + +NDArray* +NDArray_VSTACK(NDArray **arrays, int narrays) +{ + NDArray *result = NULL; + NDArray **parsed_arrays = emalloc(sizeof(NDArray*) * narrays); + for (int i = 0; i < narrays; i++) { + parsed_arrays[i] = NDArray_AtLeast2D(arrays[i]); + } + result = NDArray_Concatenate(parsed_arrays, narrays, 0); + for (int i = 0; i < narrays; i++) { + NDArray_FREE(parsed_arrays[i]); + } + efree(parsed_arrays); + return result; +} + +NDArray* +NDArray_HSTACK(NDArray **arrays, int narrays) +{ + NDArray **parsed_arrays = emalloc(sizeof(NDArray*) * narrays); + for (int i = 0; i < narrays; i++) { + parsed_arrays[i] = NDArray_AtLeast1D(arrays[i]); + } + + NDArray * result = NULL; + if (arrays && parsed_arrays[0]->ndim == 1) { + result = NDArray_Concatenate(parsed_arrays, narrays, 0); + } else { + result = NDArray_Concatenate(parsed_arrays, narrays, 1); + } + + for (int i = 0; i < narrays; i++) { + NDArray_FREE(parsed_arrays[i]); + } + efree(parsed_arrays); + return result; +} + +NDArray* +NDArray_DSTACK(NDArray **arrays, int narrays) +{ + NDArray **parsed_arrays = emalloc(sizeof(NDArray*) * narrays); + for (int i = 0; i < narrays; i++) { + parsed_arrays[i] = NDArray_AtLeast3D(arrays[i]); + } + + NDArray * result = NULL; + result = NDArray_Concatenate(parsed_arrays, narrays, 2); + + for (int i = 0; i < narrays; i++) { + NDArray_FREE(parsed_arrays[i]); + } + efree(parsed_arrays); + return result; +} + +NDArray* +NDArray_ColumnStack(NDArray **arrays, int narrays) +{ + NDArray *tmp; + NDArray **parsed_arrays = emalloc(sizeof(NDArray*) * narrays); + for (int i = 0; i < narrays; i++) { + tmp = NDArray_AtLeast2D(arrays[i]); + parsed_arrays[i] = NDArray_Transpose(tmp, NULL); + NDArray_FREE(tmp); + } + + NDArray * result = NULL; + result = NDArray_Concatenate(parsed_arrays, narrays, 1); + + for (int i = 0; i < narrays; i++) { + NDArray_FREE(parsed_arrays[i]); + } + efree(parsed_arrays); + return result; +} + +NDArray* +NDArray_Flip(NDArray *a, NDArray *axis) +{ + } \ No newline at end of file diff --git a/src/manipulation.h b/src/manipulation.h index 202a39c..5e09686 100644 --- a/src/manipulation.h +++ b/src/manipulation.h @@ -4,7 +4,7 @@ #include "ndarray.h" -NDArray* NDArray_Transpose(NDArray *a); +NDArray* NDArray_Transpose(NDArray *a, NDArray_Dims *permute); NDArray* NDArray_Reshape(NDArray *target, int *new_shape, int ndim); NDArray* NDArray_Flatten(NDArray *target); void reverse_copy(const int* src, int* dest, int size); @@ -20,5 +20,13 @@ NDArray* NDArray_AtLeast3D(NDArray *a); NDArray* NDArray_ConcatenateFlat(NDArray **arrays, int num_arrays); NDArray* NDArray_Flip(NDArray *a, NDArray *axis); NDArray* NDArray_Squeeze(NDArray *a, NDArray *axis); +NDArray* NDArray_SwapAxes(NDArray *a, int axis1, int axis2); +NDArray* NDArray_Rollaxis(NDArray *a, int axis, int start); +NDArray* NDArray_Moveaxis(NDArray *a, int* src, int* dest, int n_source, int n_dest); +NDArray* NDArray_Concatenate(NDArray **arrays, int narrays, int axis); +NDArray* NDArray_VSTACK(NDArray **arrays, int narrays); +NDArray* NDArray_HSTACK(NDArray **arrays, int narrays); +NDArray* NDArray_DSTACK(NDArray **arrays, int narrays); +NDArray* NDArray_ColumnStack(NDArray **arrays, int narrays); int NDArray_ConvertMultiAxis(NDArray *axis_in, int ndim, bool *out_axis_flags); #endif //PHPSCI_NDARRAY_MANIPULATION_H diff --git a/src/ndarray.c b/src/ndarray.c index 4142b0a..1f997e2 100644 --- a/src/ndarray.c +++ b/src/ndarray.c @@ -284,6 +284,77 @@ NDArray_ToGD(NDArray *a, NDArray *n_alpha, zval *output) { #endif +char* +convert_shape_to_string(int n, int const *vals, char *endin) +{ + int i; + if (n == 0) { + return NULL; + } + char *value = emalloc(sizeof(char) * (n + 3)); + value[0] = '('; + for (i = 1; i <= n; i++) { + value[i] = (char)vals[i]; + } + value[i + 1] = ')'; + value[i + 2] = *endin; + return value; +} + +int +broadcast_strides(int ndim, int const *shape, + int strides_ndim, int const *strides_shape, int const *strides, + char const *strides_name, + int *out_strides) +{ + int idim, idim_start = ndim - strides_ndim; + + /* Can't broadcast to fewer dimensions */ + if (idim_start < 0) { + goto broadcast_error; + } + + for (idim = ndim - 1; idim >= idim_start; --idim) { + int strides_shape_value = strides_shape[idim - idim_start]; + /* If it doesn't have dimension one, it must match */ + if (strides_shape_value == 1) { + out_strides[idim] = 0; + } + else if (strides_shape_value != shape[idim]) { + goto broadcast_error; + } + else { + out_strides[idim] = strides[idim - idim_start]; + } + } + + /* New dimensions get a zero stride */ + for (idim = 0; idim < idim_start; ++idim) { + out_strides[idim] = 0; + } + + return 0; + +broadcast_error: { + char *shape1 = convert_shape_to_string(strides_ndim, strides_shape, ""); + if (shape1 == NULL) { + return -1; + } + + char *shape2 = convert_shape_to_string(ndim, shape, ""); + if (shape2 == NULL) { + efree(shape1); + return -1; + } + zend_throw_error(NULL, + "could not broadcast %s from shape %s into shape %s", + strides_name, shape1, shape2); + efree(shape1); + efree(shape2); + return -1; + } +} + void apply_reduce(NDArray *result, NDArray *target, NDArray *(*operation)(NDArray *, NDArray *)) { NDArray *temp = operation(result, target); if (NDArray_DEVICE(target) == NDARRAY_DEVICE_CPU) { @@ -1330,4 +1401,280 @@ NDArray_Load(char * filename) // Close the file fclose(file); return out; +} + +NDArray* +NDArray_AssignRawScalar(NDArray *dst, NDArray *src) +{ + if (NDArray_DEVICE(dst) == NDARRAY_DEVICE_CPU) { + NDArray_FDATA(dst)[0] = NDArray_FDATA(src)[0]; + } +#ifdef HAVE_CUBLAS + if (NDArray_DEVICE(dst) == NDARRAY_DEVICE_GPU) { + vmemcpyd2d(NDArray_DATA(src), NDArray_DATA(dst), sizeof(float)); + } +#endif + return dst; +} + +int +NDArray_CompareLists(int const *l1, int const *l2, int n) +{ + int i; + + for (i = 0; i < n; i++) { + if (l1[i] != l2[i]) { + return 0; + } + } + return 1; +} + +int +raw_array_assign_array(int ndim, int const *shape, + NDArrayDescriptor *dst_dtype, char *dst_data, int const *dst_strides, + NDArrayDescriptor *src_dtype, char *src_data, int const *src_strides, + int device) +{ + int idim; + int shape_it[NDARRAY_MAX_DIMS]; + int dst_strides_it[NDARRAY_MAX_DIMS]; + int src_strides_it[NDARRAY_MAX_DIMS]; + int coord[NDARRAY_MAX_DIMS]; + + if (NDArray_PrepareTwoRawArrayIter( + ndim, shape, + dst_data, dst_strides, + src_data, src_strides, + &ndim, shape_it, + &dst_data, dst_strides_it, + &src_data, src_strides_it) < 0) { + return -1; + } + + /* + * Overlap check for the 1D case. Higher dimensional arrays and + * opposite strides cause a temporary copy before getting here. + */ + if (ndim == 1 && src_data < dst_data && + src_data + shape_it[0] * src_strides_it[0] > dst_data) { + src_data += (shape_it[0] - 1) * src_strides_it[0]; + dst_data += (shape_it[0] - 1) * dst_strides_it[0]; + src_strides_it[0] = -src_strides_it[0]; + dst_strides_it[0] = -dst_strides_it[0]; + } + + int strides[2] = {src_strides_it[0], dst_strides_it[0]}; + int size; + NDARRAY_RAW_ITER_START(idim, ndim, coord, shape_it) { + size = shape_it[0]; + for (int i = 0; i < size; i++) { + if (device == NDARRAY_DEVICE_CPU) { + memcpy(dst_data + (int) (i * dst_strides_it[0]), src_data + (int) (i * src_strides_it[0]), + sizeof(float)); + } + if (device == NDARRAY_DEVICE_GPU) { +#ifdef HAVE_CUBLAS + vmemcpyd2d(src_data + (int) (i * src_strides_it[0]), dst_data + (int) (i * dst_strides_it[0]), sizeof(float)); +#endif + } + } + } NDARRAY_RAW_ITER_TWO_NEXT(idim, ndim, coord, shape_it, + dst_data, dst_strides_it, + src_data, src_strides_it); + return 0; +fail: + return -1; +} + +int +NDArray_AssignArray(NDArray *dst, NDArray *src) +{ + int copied_src = 0; + + int src_strides[NDARRAY_MAX_DIMS]; + + if (NDArray_NDIM(src) == 0) { + NDArray_AssignRawScalar(dst, NDArray_DATA(src)); + return 0; + } + + if (NDArray_DATA(src) == NDArray_DATA(dst) && + NDArray_DESCRIPTOR(src) == NDArray_DESCRIPTOR(dst) && + NDArray_NDIM(src) == NDArray_NDIM(dst) && + NDArray_CompareLists(NDArray_SHAPE(src), + NDArray_SHAPE(dst), + NDArray_NDIM(src)) && + NDArray_CompareLists(NDArray_STRIDES(src), + NDArray_STRIDES(dst), + NDArray_NDIM(src))) { + return 0; + } + + if (NDArray_NDIM(src) > NDArray_NDIM(dst)) { + int ndim_tmp = NDArray_NDIM(src); + int *src_shape_tmp = NDArray_SHAPE(src); + int *src_strides_tmp = NDArray_STRIDES(src); + + while (ndim_tmp > NDArray_NDIM(dst) && src_shape_tmp[0] == 1) { + --ndim_tmp; + ++src_shape_tmp; + ++src_strides_tmp; + } + + if (broadcast_strides(NDArray_NDIM(dst), NDArray_SHAPE(dst), + ndim_tmp, src_shape_tmp, + src_strides_tmp, "input array", + src_strides) < 0) { + goto fail; + } + } + else { + if (broadcast_strides(NDArray_NDIM(dst), NDArray_SHAPE(dst), + NDArray_NDIM(src), NDArray_SHAPE(src), + NDArray_STRIDES(src), "input array", + src_strides) < 0) { + goto fail; + } + } + + if (raw_array_assign_array(NDArray_NDIM(dst), NDArray_SHAPE(dst), + NDArray_DESCRIPTOR(dst), NDArray_DATA(dst), NDArray_STRIDES(dst), + NDArray_DESCRIPTOR(src), NDArray_DATA(src), src_strides, NDArray_DEVICE(src)) < 0) { + goto fail; + } + + if (copied_src) { + NDArray_FREE(src); + } + return 0; + +fail: + if (copied_src) { + NDArray_FREE(src); + } + return -1; +} + +static inline int +s_intp_abs(int x) +{ +return (x < 0) ? -x : x; +} + +void +NDArray_CreateMultiSortedStridePerm(int narrays, NDArray **arrays, + int ndim, int *out_strideperm) +{ + int i0, i1, ipos, ax_j0, ax_j1, iarrays; + + for (i0 = 0; i0 < ndim; ++i0) { + out_strideperm[i0] = i0; + } + + for (i0 = 1; i0 < ndim; ++i0) { + ipos = i0; + ax_j0 = out_strideperm[i0]; + for (i1 = i0 - 1; i1 >= 0; --i1) { + int ambig = 1, shouldswap = 0; + + ax_j1 = out_strideperm[i1]; + + for (iarrays = 0; iarrays < narrays; ++iarrays) { + if (NDArray_SHAPE(arrays[iarrays])[ax_j0] != 1 && + NDArray_SHAPE(arrays[iarrays])[ax_j1] != 1) { + if (s_intp_abs(NDArray_STRIDES(arrays[iarrays])[ax_j0]) <= + s_intp_abs(NDArray_STRIDES(arrays[iarrays])[ax_j1])) { + /* + * Set swap even if it's not ambiguous already, + * because in the case of conflicts between + * different operands, C-order wins. + */ + shouldswap = 0; + } + else { + /* Only set swap if it's still ambiguous */ + if (ambig) { + shouldswap = 1; + } + } + + /* + * A comparison has been done, so it's + * no longer ambiguous + */ + ambig = 0; + } + } + /* + * If the comparison was unambiguous, either shift + * 'ipos' to 'i1' or stop looking for an insertion point + */ + if (!ambig) { + if (shouldswap) { + ipos = i1; + } + else { + break; + } + } + } + + /* Insert out_strideperm[i0] into the right place */ + if (ipos != i0) { + for (i1 = i0; i1 > ipos; --i1) { + out_strideperm[i1] = out_strideperm[i1-1]; + } + out_strideperm[ipos] = ax_j0; + } + } +} + +/* + * Sorts items so stride is descending, because C-order + * is the default in the face of ambiguity. + */ +static int _nd_stride_sort_item_comparator(const void *a, const void *b) +{ + int astride = ((const ndarray_stride_sort_item *)a)->stride, + bstride = ((const ndarray_stride_sort_item *)b)->stride; + + /* Sort the absolute value of the strides */ + if (astride < 0) { + astride = -astride; + } + if (bstride < 0) { + bstride = -bstride; + } + + if (astride == bstride) { + /* + * Make the qsort stable by next comparing the perm order. + * (Note that two perm entries will never be equal) + */ + int aperm = ((const ndarray_stride_sort_item *)a)->perm, + bperm = ((const ndarray_stride_sort_item *)b)->perm; + return (aperm < bperm) ? -1 : 1; + } + if (astride > bstride) { + return -1; + } + return 1; +} + +void +NDArray_CreateSortedStridePerm(int ndim, int const *strides, + ndarray_stride_sort_item *out_strideperm) +{ + int i; + + /* Set up the strideperm values */ + for (i = 0; i < ndim; ++i) { + out_strideperm[i].perm = i; + out_strideperm[i].stride = strides[i]; + } + + /* Sort them */ + qsort(out_strideperm, ndim, sizeof(raw_array_assign_array), + &_nd_stride_sort_item_comparator); } \ No newline at end of file diff --git a/src/ndarray.h b/src/ndarray.h index 77172c8..014e029 100644 --- a/src/ndarray.h +++ b/src/ndarray.h @@ -96,6 +96,10 @@ NDArray_CLEARFLAGS(NDArray *arr, int flags) { (arr)->flags &= ~flags; } +typedef struct { + int perm, stride; +} ndarray_stride_sort_item; + void NDArray_FREE(NDArray *array); char *NDArray_Print(NDArray *array, int do_return); NDArray *reduce(NDArray *array, int *axis, NDArray *(*operation)(NDArray *, NDArray *)); @@ -119,6 +123,11 @@ NDArray* NDArray_FromGD(zval *a, bool channel_last); void NDArray_ToGD(NDArray *a, NDArray *n_alpha, zval *output); void NDArray_Save(NDArray *a, char * filename, int length); NDArray* NDArray_Load(char * filename); +NDArray* NDArray_AssignRawScalar(NDArray *dst, NDArray *src); +int NDArray_AssignArray(NDArray *dst, NDArray *src); +int NDArray_CompareLists(int const *l1, int const *l2, int n); +void NDArray_CreateMultiSortedStridePerm(int narrays, NDArray **arrays, int ndim, int *out_strideperm); +void NDArray_CreateSortedStridePerm(int ndim, int const *strides, ndarray_stride_sort_item *out_strideperm); #ifdef __cplusplus } diff --git a/src/ndmath/calculation.c b/src/ndmath/calculation.c index 7948116..fa2573e 100644 --- a/src/ndmath/calculation.c +++ b/src/ndmath/calculation.c @@ -11,23 +11,19 @@ float_argmax(float *ip, int n, float *max_ind) { int i; float mp = *ip; - *max_ind = 0; - if (isnanf(mp)) { /* nan encountered; it's maximal */ return 0; } - for (i = 1; i < n; i++) { ip++; - /* * Propagate nans, similarly as max() and min() */ - if (!(*ip <= mp)) { /* negated, for correct nan handling */ + if (*ip > mp) { /* negated, for correct nan handling */ mp = *ip; - *max_ind = i; + *max_ind = (float)i; if (isnanf(mp)) { /* nan encountered, it's maximal */ break; @@ -42,10 +38,7 @@ float_argmin(float *ip, int n, float *min_ind) { int i; float mp = *ip; - *min_ind = 0; - - if (isnanf(mp)) { /* nan encountered; it's minimal */ return 0; @@ -53,15 +46,14 @@ float_argmin(float *ip, int n, float *min_ind) for (i = 1; i < n; i++) { ip++; - if (!(mp <= *ip)) { /* negated, for correct nan handling */ + + if (!_LESS_THAN_OR_EQUAL(mp, *ip)) { mp = *ip; *min_ind = (float)i; if (isnanf(mp)) { - /* nan encountered, it's minimal */ break; } } - } return 0; } @@ -79,7 +71,7 @@ float_argmin(float *ip, int n, float *min_ind) * @return */ NDArray * -NDArray_ArgMinMaxCommon(NDArray *op, int axis, int keepdims, bool is_argmax) { +NDArray_ArgMinMaxCommon(NDArray *op, int axis, bool keepdims, bool is_argmax) { if (NDArray_DEVICE(op) == NDARRAY_DEVICE_GPU) { zend_throw_error(NULL, "GPU not supported."); return NULL; @@ -93,7 +85,6 @@ NDArray_ArgMinMaxCommon(NDArray *op, int axis, int keepdims, bool is_argmax) { int elsize; // Keep a copy because axis changes via call to NDArray_CheckAxis int axis_copy = axis; - int _shape_buf[NDARRAY_MAX_DIMS]; int *out_shape; // Keep the number of dimensions and shape of // original array. Helps when `keepdims` is True. @@ -120,10 +111,7 @@ NDArray_ArgMinMaxCommon(NDArray *op, int axis, int keepdims, bool is_argmax) { dims[j] = j + 1; } dims[NDArray_NDIM(ap) - 1] = axis; - //@todo Use transpose permutation - //op = NDArray_Transpose(ap, &newaxes); - op = NDArray_Transpose(ap); - + op = NDArray_Transpose(ap, &newaxes); NDArray_FREE(ap); if (op == NULL) { return NULL; @@ -153,17 +141,13 @@ NDArray_ArgMinMaxCommon(NDArray *op, int axis, int keepdims, bool is_argmax) { memcpy(out_shape, NDArray_SHAPE(ap), sizeof(int) * NDArray_NDIM(ap)); } else { - out_shape = _shape_buf; + out_shape = emalloc(out_ndim * sizeof(int)); if (axis_copy == NDARRAY_MAX_DIMS) { - for (int i = 0; i < out_ndim; i++) { + for (i = 0; i < out_ndim; i++) { out_shape[i] = 1; } } else { - /* - * While `ap` may be transposed, we can ignore this for `out` because the - * transpose only reorders the size 1 `axis` (not changing memory layout). - */ memcpy(out_shape, original_op_shape, out_ndim * sizeof(int)); out_shape[axis] = 1; } diff --git a/src/ndmath/calculation.h b/src/ndmath/calculation.h index 4636c16..7f7e407 100644 --- a/src/ndmath/calculation.h +++ b/src/ndmath/calculation.h @@ -5,6 +5,8 @@ typedef int (NDArray_ArgFunc)(float*, int, float *); -NDArray * NDArray_ArgMinMaxCommon(NDArray *op, int axis, int keepdims, bool is_argmax); +#define _LESS_THAN_OR_EQUAL(a,b) ((a) <= (b)) + +NDArray * NDArray_ArgMinMaxCommon(NDArray *op, int axis, bool keepdims, bool is_argmax); #endif //NUMPOWER_CALCULATION_H diff --git a/src/ndmath/cuda/cuda_math.cu b/src/ndmath/cuda/cuda_math.cu index dff0c3c..c9fd57f 100644 --- a/src/ndmath/cuda/cuda_math.cu +++ b/src/ndmath/cuda/cuda_math.cu @@ -303,11 +303,29 @@ __global__ void negateFloatKernel(float* d_array, int size) { int index = threadIdx.x + blockIdx.x * blockDim.x; if (index < size) { - d_array[index] = -(d_array[index]); } } +__global__ +void positiveFloatKernel(float* d_array, int size) { + int index = threadIdx.x + blockIdx.x * blockDim.x; + if (index < size) { + if (d_array[index] < 0) { + d_array[index] = -(d_array[index]); + } + } +} + +__global__ +void reciprocalFloatKernel(float* d_array, int size) { + int index = threadIdx.x + blockIdx.x * blockDim.x; + if (index < size) { + d_array[index] = 1.0f / (d_array[index]); + } +} + + __device__ float sinc(float number) { if (number == 0.0) { @@ -1363,6 +1381,22 @@ extern "C" { cudaDeviceSynchronize(); } + void + cuda_float_positive(int nblocks, float *d_array) { + int blockSize = 256; // Number of threads per block. This is a typical choice. + int numBlocks = (nblocks + blockSize - 1) / blockSize; // Number of blocks in the grid. + positiveFloatKernel<<>>(d_array, nblocks); + cudaDeviceSynchronize(); + } + + void + cuda_float_reciprocal(int nblocks, float *d_array) { + int blockSize = 256; // Number of threads per block. This is a typical choice. + int numBlocks = (nblocks + blockSize - 1) / blockSize; // Number of blocks in the grid. + reciprocalFloatKernel<<>>(d_array, nblocks); + cudaDeviceSynchronize(); + } + void cuda_float_sign(int nblocks, float *d_array) { int blockSize = 256; // Number of threads per block. This is a typical choice. diff --git a/src/ndmath/cuda/cuda_math.h b/src/ndmath/cuda/cuda_math.h index 1c0bc91..3891ad3 100644 --- a/src/ndmath/cuda/cuda_math.h +++ b/src/ndmath/cuda/cuda_math.h @@ -33,7 +33,6 @@ float cuda_min_float(float *a, int nelements); void cuda_pow_float(int nblocks, float *a, float *b, float *rtn, int nelements); int cuda_equal_float(int nblocks, float *a, float *b, int nelements); void cuda_sum_float(int nblocks, float *a, float *rtn, int nelements); -void cuda_matmul_float(int nblocks, float *a, float *b, float *rtn, int widthA, int heightA, int widthB); void cuda_fill_float(float *a, float value, int n); int cuda_det_float(float *a, float *result, int n); void cuda_float_sin(int nblocks, float *d_array); @@ -76,6 +75,8 @@ void cuda_lstsq_float(float* A, int m, int n, float* B, int k, float* X); NDArray* NDArrayMathGPU_ElementWise2F(NDArray* ndarray, ElementWiseFloatGPUOperation2F op, float val1, float val2); NDArray* NDArrayMathGPU_ElementWise1F(NDArray* ndarray, ElementWiseFloatGPUOperation1F op, float val1); void cuda_float_transpose(int tiledim, int blockrows, const float *d_in, float *d_out, int width, int height); +void cuda_float_positive(int nblocks, float *d_array); +void cuda_float_reciprocal(int nblocks, float *d_array); #ifdef __cplusplus } diff --git a/src/ndmath/double_math.c b/src/ndmath/double_math.c index 7a98dc1..80a42b8 100644 --- a/src/ndmath/double_math.c +++ b/src/ndmath/double_math.c @@ -238,6 +238,11 @@ float float_negate(float val) { return -val; } +float float_positive(float val) { + if (val < 0) return -val; + return val; +} + float float_sign(float val) { return (float)((val > 0.0f) - (val < 0.0f)); } @@ -251,7 +256,10 @@ float float_round(float number, float decimals) { return roundf(number * factor) / factor; } -float float_arctan2(float x, float y) -{ +float float_arctan2(float x, float y) { return atan2f(x, y); +} + +float float_reciprocal(float val) { + return 1 / val; } \ No newline at end of file diff --git a/src/ndmath/double_math.h b/src/ndmath/double_math.h index 02e46f6..e577214 100644 --- a/src/ndmath/double_math.h +++ b/src/ndmath/double_math.h @@ -40,4 +40,6 @@ float float_clip(float val, float min, float max); float float_round(float number, float decimals); float float_rsqrt(float val); float float_arctan2(float x, float y); +float float_positive(float val); +float float_reciprocal(float val); #endif //PHPSCI_NDARRAY_DOUBLE_MATH_H diff --git a/src/ndmath/linalg.c b/src/ndmath/linalg.c index c8bfd69..29abe49 100644 --- a/src/ndmath/linalg.c +++ b/src/ndmath/linalg.c @@ -137,7 +137,7 @@ NDArray_SVD(NDArray *target) { } if(NDArray_DEVICE(target_ptr) == NDARRAY_DEVICE_GPU) { #ifdef HAVE_CUBLAS - target_ptr = NDArray_Transpose(target); + target_ptr = NDArray_Transpose(target, NULL); vmalloc((void**)&Sf, sizeof(float) * smallest_dim); vmalloc((void**)&Uf, sizeof(float) * NDArray_SHAPE(target)[0] * NDArray_SHAPE(target)[0]); vmalloc((void**)&Vf, sizeof(float) * NDArray_SHAPE(target)[1] * NDArray_SHAPE(target)[1]); @@ -425,7 +425,7 @@ NDArray_L1Norm(NDArray* target) { NDArray *rtn = NULL; float max_value = FLT_MIN; float *results = emalloc(sizeof(float) * NDArray_SHAPE(target)[NDArray_NDIM(target) - 2]); - NDArray *transposed = NDArray_Transpose(target); + NDArray *transposed = NDArray_Transpose(target, NULL); NDArray *ab = NDArray_Abs(transposed); NDArray_FREE(transposed); NDArray *slice; @@ -1031,7 +1031,6 @@ NDArray_Solve(NDArray *a, NDArray *b) { * NDArray::cond * * @param a - * @param b * @return */ NDArray* diff --git a/stubs/numpower.stubs.php b/stubs/numpower.stubs.php index 3cb87f8..a7b9b9c 100644 --- a/stubs/numpower.stubs.php +++ b/stubs/numpower.stubs.php @@ -77,7 +77,6 @@ public static function mod(NDArray|array|float|int $a, NDArray|array|float|int $ */ public static function multiply(NDArray|array|float|int $a, NDArray|array|float|int $b): NDArray|float|int {} - /** * Computes the element-wise negation (unary minus) of an array, returning a new array with the negation of each element. * @@ -88,6 +87,24 @@ public static function multiply(NDArray|array|float|int $a, NDArray|array|float| */ public static function negative(NDArray|array|float|int $a): NDArray|float|int {} + /** + * Numerical positive, element-wise. + * + * @param NDArray|array|float|int $a Input array + * @return NDArray|float|int + */ + public static function positive(NDArray|array|float|int $a): NDArray|float|int {} + + /** + * Return the reciprocal of the argument, element-wise. + * + * Calculates `1 / $a` + * + * @param NDArray|array|float|int $a Input array + * @return NDArray|float|int + */ + public static function reciprocal(NDArray|array|float|int $a): NDArray|float|int {} + /** * Raises each element of an array $a to a specified power $b and returns a new array containing the result. * @@ -668,10 +685,91 @@ public function toArray(): array {} * Return the transpose of matrix `$a` * * @param NDArray|array|float|int $a Target array + * @param array|null $axes For an n-D array, if $axes are given, their order indicates how the axes are permuted * @return NDArray $a transposed */ - public static function transpose(NDArray|array|float|int $a): NDArray {} + public static function transpose(NDArray|array|float|int $a, ?array $axes): NDArray {} + + /** + * Interchange two axes of an array. + * + * @param NDArray|array|float|int $a Target array + * @param int $axis1 First axis + * @param int $axis2 Second axis + * @return NDArray + */ + public static function swapaxes(NDArray|array|float|int $a, int $axis1, int $axis2): NDArray {} + + /** + * Roll the specified axis backwards, until it lies in a given position. + * + * @param NDArray|array|float|int $a Target array + * @param int $axis + * @param int $start + * @return NDArray + */ + public static function rollaxis(NDArray|array|float|int $a, int $axis, int $start = 0): NDArray {} + + /** + * Move axes of an array to new positions. + * + * @param NDArray|array|float|int $a Target array + * @param int|array $source + * @param int|array $destination + * @return NDArray + */ + public static function moveaxis(NDArray|array|float|int $a, int|array $source, int|array $destination): NDArray {} + + /** + * Stack arrays in sequence vertically (row wise). + * + * @param NDArray[] $arrays + * @return NDArray + */ + public static function vstack(array $arrays): NDArray {} + + /** + * Stack arrays in sequence horizontally (column wise). + * + * @param NDArray[] $arrays + * @return NDArray + */ + public static function hstack(array $arrays): NDArray {} + + /** + * Stack arrays in sequence depth wise (along third axis). + * + * @param NDArray[] $arrays + * @return NDArray + */ + public static function dstack(array $arrays): NDArray {} + + /** + * Join a sequence of arrays along an existing axis. + * + * @param NDArray[] $arrays + * @param int|null $axis + * @return NDArray + */ + public static function concatenate(array $arrays, ?int $axis = 0): NDArray {} + /** + * Append values to the end of an array. + * + * @param NDArray|array $array + * @param NDArray|array $values + * @param int|null $axis + * @return NDArray + */ + public static function append(NDArray|array $array, NDArray|array $values, ?int $axis): NDArray {} + + /** + * Stack 1-D arrays as columns into a 2-D array. + * + * @param NDArray[] $arrays + * @return NDArray + */ + public static function column_stack(array $arrays): NDArray {} /** * Creates a new NDArray from a PHP array. @@ -858,7 +956,7 @@ public static function outer(NDArray|array $a, NDArray|array $b): NDArray {} * * - **1** - L1-Norm * - **2** - L2-Norm - * + * * @param NDArray|array $a * @param int $order (1) L1-Norm, (2) L2-Norm * @return float @@ -985,5 +1083,84 @@ public static function cholesky(NDArray|array $a): NDArray {} */ public static function arange(float|int $stop, float|int $start = 0, float|int $step = 1): NDArray {} + /** + * Remove axes of length one from $a. + * + * @param NDArray|array $a Input array + * @param int|int[] $axis Selects a subset of the entries of length one in the shape. + * If an axis is selected with shape entry greater than one, an error is raised. + * @return NDArray|float The input array, but with all or a subset of the dimensions of length 1 removed. + * This is always $a itself or $a view into $a. Note that if all axes are squeezed, + * the result is a 0d array and not a scalar. + */ + public static function squeeze(NDArray|array $a, int|array $axis): NDArray|float {} + + /** + * Extract a diagonal or construct a diagonal array. + * + * @param NDArray|array $a + * @return NDArray + */ + public static function diag(NDArray|array $a): NDArray {} + /** + * Return a new array of given shape and type, filled with $fill_value. + * + * @param int[] $shape Shape of the new array + * @param float|int $fill_value Fill value + * @return NDArray + */ + public static function full(array $shape, float|int $fill_value): NDArray {} + + /** + * Fill the array with a scalar value. + * + * @param float|int $fill_value Fill value + * @return NDArray + */ + public function fill(float|int $fill_value): NDArray {} + + /** + * Returns the indices of the minimum values along an axis. + * + * @param NDArray|array $a Target array + * @param int|null $axis If NULL, the index is into the flattened array, otherwise along the specified axis. + * @param bool $keepdims + * @return NDArray Array of indices into the array. It has the same shape as $a with the dimension along $axis removed. + */ + public static function argmin(NDArray|array $a, ?int $axis, bool $keepdims = false): NDArray {} + + /** + * Returns the indices of the maximum values along an axis. + * + * @param NDArray|array $a Target array + * @param int|null $axis If NULL, the index is into the flattened array, otherwise along the specified axis. + * @param bool $keepdims + * @return NDArray Array of indices into the array. It has the same shape as $a with the dimension along axis removed. + */ + public static function argmax(NDArray|array $a, ?int $axis, bool $keepdims = false): NDArray {} + + /** + * Array slicing, each argument represents a slice of a dimension. + * + * Empty arrays represent all values of a dimension, arrays with values are treated in + * the format [start, stop, step], when only one value exists, it is automatically + * assigned to stop, the default value of start is 0 and step is 1. + * + * When instead of an array, a number is passed, it is also assigned to the + * stop of that dimension. + * + * Ex: Get the first row of a matrix: + * $array->slice(0) + * + * Ex: Get the last column of a matrix: + * $array->slice([], -1); + * + * Ex: Get the first two columns of the first row: + * $array->slice(0, [0,2]); + * + * @param array ...$indices + * @return NDArray|float + */ + public function slice(...$indices): NDArray|float {}; } \ No newline at end of file diff --git a/tests/manipulation/003-ndarray-append.phpt b/tests/manipulation/003-ndarray-append.phpt index 6499e8d..833560c 100644 --- a/tests/manipulation/003-ndarray-append.phpt +++ b/tests/manipulation/003-ndarray-append.phpt @@ -7,9 +7,9 @@ use \NDArray as nd; $a = nd::array([1, 2, 3, 4]); $b = nd::array([5, 6, 7, 8]); -print_r(nd::append([$a, $b])->toArray()); -print_r(nd::append([$a, $a])->toArray()); -print_r(nd::append([[1, 2, 3, 4], [1, 2, 3, 4]])->toArray()); +print_r(nd::append($a, $b)->toArray()); +print_r(nd::append($a, $a)->toArray()); +print_r(nd::append([1, 2, 3, 4], [1, 2, 3, 4])->toArray()); ?> --EXPECT-- Array