├── src ├── Drivers │ ├── Driver.php │ ├── CLBlast.php │ ├── MatlibPHP │ │ ├── MatlibPhp.php │ │ ├── PhpBLASFactory.php │ │ ├── Utils.php │ │ ├── PhpBuffer.php │ │ └── PhpLapack.php │ ├── MatlibCL │ │ ├── MatlibCLFactory.php │ │ ├── params │ │ │ ├── ReduceSumTimesMode1.php │ │ │ ├── ReduceSumTimesMode3.php │ │ │ ├── ReduceSumTimesMode2.php │ │ │ ├── ReduceSumTimesMode0.php │ │ │ ├── ScatterAddTimesMode3.php │ │ │ └── ScatterAddTimesMode2.php │ │ └── CLInfo.php │ ├── Service.php │ ├── AbstractDriver.php │ ├── Selector.php │ └── AbstractMatlibService.php ├── C.php ├── R.php ├── Complex.php ├── Range.php ├── ComplexUtils.php ├── Random.php ├── NDArrayCL.php └── NDArrayPhp.php ├── bin └── rindow-math-matrix ├── phpstan.neon.dist ├── composer.json ├── phpstan-interop.neon ├── LICENSE ├── .github └── workflows │ ├── tests.yml │ └── develop.yml ├── README.md └── phpstan-baseline.neon /src/Drivers/Driver.php: -------------------------------------------------------------------------------- 1 | imag>=0)?'+':''; 17 | return "{$this->real}{$and}{$this->imag}i"; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /bin/rindow-math-matrix: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | 1) { 17 | if($argv[1]=='-v') { 18 | $verbose = 10; 19 | } 20 | } 21 | $mo = new MatrixOperator(verbose:$verbose); 22 | if($verbose!==null) { 23 | echo "\n"; 24 | } 25 | echo $mo->service()->info(); 26 | -------------------------------------------------------------------------------- /phpstan.neon.dist: -------------------------------------------------------------------------------- 1 | includes: 2 | - phpstan-interop.neon 3 | - phpstan-baseline.neon 4 | 5 | parameters: 6 | level: 6 7 | paths: 8 | - src 9 | excludePaths: 10 | - src/Drivers/MatlibCL/OpenCLMathTunner.php 11 | 12 | # ignoreErrors: 13 | # level 7 14 | # - 15 | # message: "#^Call to an undefined method object::#" 16 | # - 17 | # message: "#^Access to an undefined property object::\\$real#" 18 | # path: src/ComplexUtils.php 19 | # - 20 | # message: "#^Access to an undefined property object::\\$imag#" 21 | # path: src/ComplexUtils.php 22 | # level 8 23 | # - 24 | # message: "#^Cannot access property \\$real#" 25 | # - 26 | # message: "#^Cannot access property \\$imag#" 27 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rindow/rindow-math-matrix", 3 | "type": "library", 4 | "description": "The fundamental package for scientific matrix operation", 5 | "keywords": ["rindow","math","matrix","N-dimension","operation"], 6 | "license": "BSD-3-Clause", 7 | "require": { 8 | "php": "^8.1", 9 | "interop-phpobjects/polite-math": "^1.0.7" 10 | }, 11 | "suggest": { 12 | "rindow/math-plot": "for OpenCL tunning", 13 | "rindow/rindow-math-matrix-matlibffi": "^1.0.4" 14 | }, 15 | "autoload": { 16 | "psr-4": { 17 | "Rindow\\Math\\Matrix\\": "src/" 18 | } 19 | }, 20 | "bin": [ "bin/rindow-math-matrix" ] 21 | } 22 | -------------------------------------------------------------------------------- /src/Drivers/MatlibCL/MatlibCLFactory.php: -------------------------------------------------------------------------------- 1 | hasDiv5Bug()) { 26 | throw new RuntimeException("OpenCL Device has The Div5bug."); 27 | } 28 | return $openclmath; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Drivers/Service.php: -------------------------------------------------------------------------------- 1 | $options 24 | */ 25 | public function createQueue(?array $options=null) : object; 26 | } 27 | -------------------------------------------------------------------------------- /phpstan-interop.neon: -------------------------------------------------------------------------------- 1 | parameters: 2 | ignoreErrors: 3 | - 4 | message: "#^Call to an undefined method Interop\\\\Polite\\\\Math\\\\Matrix\\\\.*[Device\\|Linear]*Buffer\\:\\:dtype\\(\\)#" 5 | - 6 | message: "#^Call to an undefined method Interop\\\\Polite\\\\Math\\\\Matrix\\\\.*[Device\\|Linear]*Buffer\\:\\:dump\\(\\)#" 7 | - 8 | message: "#^Call to an undefined method Interop\\\\Polite\\\\Math\\\\Matrix\\\\.*[Device\\|Linear]*Buffer\\:\\:value_size\\(\\)#" 9 | - 10 | message: "#function count expects array|Countable, Interop\\\\Polite\\\\Math\\\\Matrix\\\\NDArray given#" 11 | - 12 | message: "#^Argument of an invalid type Interop\\\\Polite\\\\Math\\\\Matrix\\\\NDArray supplied for foreach, only iterables are supported#" 13 | - 14 | message: "#^Call to an undefined method Interop\\\\Polite\\\\Math\\\\Matrix\\\\DeviceBuffer\\:\\:bytes\\(\\)#" 15 | - 16 | message: "#^Call to an undefined method Interop\\\\Polite\\\\Math\\\\Matrix\\\\DeviceBuffer\\:\\:copy\\(\\)#" 17 | - 18 | message: "#^Call to an undefined method Interop\\\\Polite\\\\Math\\\\Matrix\\\\DeviceBuffer\\:\\:read\\(\\)#" 19 | -------------------------------------------------------------------------------- /src/Drivers/MatlibPHP/PhpBLASFactory.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | class Range implements IteratorAggregate,Countable 13 | { 14 | protected int|float $start; 15 | protected int|float $limit; 16 | protected int|float $delta; 17 | 18 | public function __construct( 19 | int|float $limit, 20 | int|float|null $start=null, 21 | int|float|null $delta=null 22 | ) { 23 | $this->limit = $limit; 24 | $this->start = $start ?? 0; 25 | $this->delta = $delta ?? (($limit>=$start)? 1 : -1); 26 | } 27 | 28 | public function start() : int|float 29 | { 30 | return $this->start; 31 | } 32 | 33 | public function limit() : int|float 34 | { 35 | return $this->limit; 36 | } 37 | 38 | public function delta() : int|float 39 | { 40 | return $this->delta; 41 | } 42 | 43 | public function getIterator() : Traversable 44 | { 45 | $index = 0; 46 | $value = $this->start; 47 | if($this->delta > 0) { 48 | while($value < $this->limit) { 49 | yield $index => $value; 50 | $index++; 51 | $value += $this->delta; 52 | } 53 | } else { 54 | while($value > $this->limit) { 55 | yield $index => $value; 56 | $index++; 57 | $value += $this->delta; 58 | } 59 | } 60 | } 61 | 62 | public function count() : int 63 | { 64 | $start = $this->start; 65 | $limit = $this->limit; 66 | $delta = $this->delta; 67 | 68 | if($delta==0.0) { 69 | throw new RuntimeException('infinite times'); 70 | } 71 | $count = (int)floor(($limit-$start)/$delta); 72 | return $count; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/Drivers/AbstractDriver.php: -------------------------------------------------------------------------------- 1 | strVersion($name); 29 | if(version_compare($currentVersion, $lowestVersion)<0|| 30 | version_compare($currentVersion, $overVersion)>=0) { 31 | throw new LogicException($name.' '.$currentVersion.' is an unsupported version. '. 32 | 'Supported versions are greater than or equal to '.$lowestVersion. 33 | ' and less than '.$overVersion.'.'); 34 | } 35 | } 36 | 37 | protected function assertVersion() : void 38 | { 39 | $this->assertExtensionVersion( 40 | $this->extName, 41 | $this->LOWEST_VERSION, 42 | $this->OVER_VERSION 43 | ); 44 | } 45 | 46 | public function name() : string 47 | { 48 | return $this->extName(); 49 | } 50 | 51 | public function isAvailable() : bool 52 | { 53 | return extension_loaded($this->extName); 54 | } 55 | 56 | public function extName() : string 57 | { 58 | return $this->extName; 59 | } 60 | 61 | public function version() : string 62 | { 63 | $version = $this->strVersion($this->extName); 64 | return $version; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/Drivers/Selector.php: -------------------------------------------------------------------------------- 1 | $catalog */ 11 | protected array $catalog; 12 | protected ?Service $recommended=null; 13 | protected int $logLevel = 10; 14 | 15 | /** 16 | * @param array $catalog 17 | */ 18 | public function __construct(?array $catalog = null) 19 | { 20 | $catalog = $catalog ?? [ 21 | 'Rindow\Math\Matrix\Drivers\MatlibFFI\MatlibFFI', 22 | 'Rindow\Math\Matrix\Drivers\MatlibExt\MatlibExt', 23 | ]; 24 | $this->catalog = $catalog; 25 | } 26 | 27 | protected function logging(int $level, string $message) : void 28 | { 29 | if($level < $this->logLevel) { 30 | return; 31 | } 32 | echo $message."\n"; 33 | } 34 | 35 | public function select(?int $verbose=null) : Service 36 | { 37 | if($this->recommended) { 38 | return $this->recommended; 39 | } 40 | $verbose ??= 0; 41 | $this->logLevel = 10 - $verbose; 42 | $recommended = null; 43 | $highestLevel = 0; 44 | foreach ($this->catalog as $name) { 45 | if(class_exists($name)) { 46 | $this->logging(0, 'Loading service: '.$name); 47 | $service = new $name(verbose:$verbose); 48 | if(!($service instanceof Service)) { 49 | throw new LogicException('Not service class: '.$name); 50 | } 51 | $level = $service->serviceLevel(); 52 | $this->logging(0, 'Service '.$name.' is level '.$level); 53 | if($level>$highestLevel) { 54 | $highestLevel = $level; 55 | $recommended = $service; 56 | $this->logging(0, 'Update recommend to '.$name); 57 | } 58 | } else { 59 | $this->logging(1, 'Service Not found: '.$name); 60 | } 61 | } 62 | if($highestLevel<=Service::LV_BASIC) { 63 | $recommended = new MatlibPhp(); 64 | } 65 | if($recommended==null) { 66 | throw new RuntimeException('Service not found'); 67 | } 68 | $this->recommended = $recommended; 69 | $this->logging(1, get_class($recommended).' service has been selected.'); 70 | return $recommended; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/Drivers/MatlibCL/params/ReduceSumTimesMode1.php: -------------------------------------------------------------------------------- 1 | $integerDtypes */ 13 | protected array $integerDtypes = [ 14 | NDArray::int8 => true, 15 | NDArray::uint8 => true, 16 | NDArray::int16 => true, 17 | NDArray::uint16 => true, 18 | NDArray::int32 => true, 19 | NDArray::uint32 => true, 20 | NDArray::int64 => true, 21 | NDArray::uint64 => true, 22 | ]; 23 | 24 | /** @var array $floatDtypes */ 25 | protected array $floatDtypes = [ 26 | NDArray::float32 => true, 27 | NDArray::float64 => true, 28 | ]; 29 | 30 | protected function assertShapeParameter( 31 | string $name, int $n) : void 32 | { 33 | if($n<1) { 34 | throw new InvalidArgumentException("Argument $name must be greater than 0."); 35 | } 36 | } 37 | 38 | protected function assertVectorBufferSpec( 39 | string $name, BufferInterface $buffer, int $n, int $offset, int $inc) : void 40 | { 41 | if($offset<0) { 42 | throw new InvalidArgumentException("Argument offset$name must be greater than equals 0."); 43 | } 44 | if($inc<1) { 45 | throw new InvalidArgumentException("Argument inc$name must be greater than 0."); 46 | } 47 | if($offset+($n-1)*$inc >= count($buffer)) { 48 | throw new InvalidArgumentException("Vector specification too large for buffer$name."); 49 | } 50 | } 51 | 52 | protected function assertMatrixBufferSpec( 53 | string $name, BufferInterface $buffer, 54 | int $m, int $n, int $offset, int $ld) : void 55 | { 56 | if($offset<0) { 57 | throw new InvalidArgumentException("Argument offset$name must be greater than equals 0."); 58 | } 59 | if($ld<1) { 60 | throw new InvalidArgumentException("Argument ld$name must be greater than 0."); 61 | } 62 | if($offset+($m-1)*$ld+($n-1) >= count($buffer)) { 63 | throw new InvalidArgumentException("Matrix specification too large for buffer$name."); 64 | } 65 | } 66 | 67 | protected function assertBufferSize( 68 | BufferInterface $buffer, 69 | int $offset, int $size, 70 | string $message) : void 71 | { 72 | if($size<1 || $offset<0 || count($buffer) < $offset+$size) { 73 | throw new InvalidArgumentException($message); 74 | } 75 | } 76 | 77 | protected function isIntegerDtype(int $dtype) : bool 78 | { 79 | return array_key_exists($dtype, $this->integerDtypes); 80 | } 81 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | The fundamental package for scientific matrix operation 2 | ======================================================= 3 | Status: 4 | [![Build Status](https://github.com/rindow/rindow-math-matrix/workflows/tests/badge.svg)](https://github.com/rindow/rindow-math-matrix/actions) 5 | [![Downloads](https://img.shields.io/packagist/dt/rindow/rindow-math-matrix)](https://packagist.org/packages/rindow/rindow-math-matrix) 6 | [![Latest Stable Version](https://img.shields.io/packagist/v/rindow/rindow-math-matrix)](https://packagist.org/packages/rindow/rindow-math-matrix) 7 | [![License](https://img.shields.io/packagist/l/rindow/rindow-math-matrix)](https://packagist.org/packages/rindow/rindow-math-matrix) 8 | 9 | Rindow Math Matrix is the fundamental package for scientific matrix operation 10 | 11 | - A powerful N-dimensional array object 12 | - Sophisticated (broadcasting) functions 13 | - BLAS functions 14 | - Functions useful for machine learning 15 | - Tools for integrating C/C++ through the FFI (OpenBLAS,Rindow-Matlib,CLBlast etc.) 16 | - GPU support on your laptop without n-vidia (OpenCL with Intel,AMD etc.) 17 | - Useful linear algebra and random number capabilities 18 | 19 | Please see the documents on [Rindow mathematics project](https://rindow.github.io/mathematics/) web pages. 20 | 21 | Requirements 22 | ============ 23 | 24 | - PHP 8.1 or PHP8.2 or PHP8.3 or PHP8.4 25 | - PHP7.2, PHP7.3, PHP7.4 and PHP 8.0 are not supported in this release. Please use Release 1.1, which supports them. 26 | 27 | ### Strong recommend ### 28 | You can perform very fast N-dimensional array operations in conjunction 29 | 30 | - [rindow-math-matrix-matlibffi](https://github.com/rindow/rindow-math-matrix-matlibffi): plug-in drivers for OpenBLAS,Rindow-Matlib,OpenCL,CLBlast for FFI 31 | - Pre-build binaries 32 | - [Rindow matlib](https://github.com/rindow/rindow-matlib/releases) 33 | - [OpenBLAS](https://github.com/OpenMathLib/OpenBLAS/releases) 34 | - [CLBlast](https://github.com/CNugteren/CLBlast/releases) 35 | 36 | Please see the [rindow-math-matrix-matlibffi](https://github.com/rindow/rindow-math-matrix-matlibffi) to setup plug-in and pre-build binaries. 37 | 38 | How to Setup 39 | ============ 40 | Set it up using composer. 41 | 42 | ```shell 43 | $ composer require rindow/rindow-math-matrix 44 | ``` 45 | 46 | You can use it as is, but you will need to speed it up to process at a practical speed. 47 | 48 | And then, Set up pre-build binaries for the required high-speed calculation libraries. Click [here](https://github.com/rindow/rindow-math-matrix-matlibffi) for details. 49 | 50 | ```shell 51 | $ composer require rindow/rindow-math-matrix-matlibffi 52 | ``` 53 | 54 | Sample programs 55 | =============== 56 | ```php 57 | array([[1,2],[3,4]]); 64 | $b = $mo->array([[2,3],[4,5]]); 65 | $c = $mo->cross($a,$b); 66 | echo $mo->toString($c,indent:true)."\n"; 67 | ``` 68 | ```shell 69 | $ php sample.php 70 | [ 71 | [10,13], 72 | [22,29] 73 | ] 74 | ``` 75 | -------------------------------------------------------------------------------- /src/ComplexUtils.php: -------------------------------------------------------------------------------- 1 | real, i:$value->imag); 18 | } 19 | 20 | protected function cisobject(mixed $value) : bool 21 | { 22 | if(!is_object($value)) { 23 | return false; 24 | } 25 | return (property_exists($value, 'real') && 26 | property_exists($value, 'imag')); 27 | } 28 | 29 | protected function cobjecttype(mixed $value) : string 30 | { 31 | if(is_object($value)) { 32 | return get_class($value); 33 | } 34 | return gettype($value); 35 | } 36 | 37 | protected function cistype(?int $dtype=null) : bool 38 | { 39 | return $dtype==NDArray::complex64||$dtype==NDArray::complex128; 40 | } 41 | 42 | protected function ciszero(object $value) : bool 43 | { 44 | return $value->real==0 && $value->imag==0; 45 | } 46 | 47 | protected function cisone(object $value) : bool 48 | { 49 | return $value->real==1 && $value->imag==0; 50 | } 51 | 52 | protected function cabs(object $value) : float 53 | { 54 | return sqrt($value->real*$value->real + $value->imag*$value->imag); 55 | } 56 | 57 | protected function cconj(object $value) : object 58 | { 59 | return C($value->real, i:-$value->imag); 60 | } 61 | 62 | protected function cadd(object $x, object $y) : object 63 | { 64 | return C( 65 | ($x->real+$y->real), 66 | i:($x->imag+$y->imag) 67 | ); 68 | } 69 | 70 | protected function csub(object $x, object $y) : object 71 | { 72 | return C( 73 | ($x->real-$y->real), 74 | i:($x->imag-$y->imag) 75 | ); 76 | } 77 | 78 | protected function cmul(object $x, object $y) : object 79 | { 80 | return C( 81 | (($x->real*$y->real)-($x->imag*$y->imag)), 82 | i:(($x->real*$y->imag)+($x->imag*$y->real)) 83 | ); 84 | } 85 | 86 | protected function cdiv(object $x, object $y) : object 87 | { 88 | $denominator = $y->real * $y->real + $y->imag * $y->imag; 89 | if($denominator==0) { 90 | return C(NAN, i:NAN); 91 | } 92 | $real = (($x->real*$y->real)+($x->imag*$y->imag))/$denominator; 93 | $imag = (($x->imag*$y->real)-($x->real*$y->imag))/$denominator; 94 | return C($real, i:$imag); 95 | } 96 | 97 | protected function csqrt(object $x) : object 98 | { 99 | $r = sqrt($x->real*$x->real + $x->imag*$x->imag); 100 | $theta = atan2($x->imag, $x->real) / 2.0; 101 | return C( 102 | (sqrt($r)*cos($theta)), 103 | i:(sqrt($r)*sin($theta)) 104 | ); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/Drivers/MatlibCL/params/ReduceSumTimesMode3.php: -------------------------------------------------------------------------------- 1 | mo = $matrixOperator; 17 | $this->defaultFloatType = $defaultFloatType; 18 | } 19 | 20 | protected function genRandNormal(float $av, float $sd) : float 21 | { 22 | $x=random_int(1, PHP_INT_MAX-1)/PHP_INT_MAX; 23 | $y=random_int(1, PHP_INT_MAX-1)/PHP_INT_MAX; 24 | return sqrt(-2*log($x))*cos(2*pi()*$y)*$sd+$av; 25 | } 26 | 27 | /** 28 | * @return array 29 | */ 30 | protected function checkSize(mixed $n) : array 31 | { 32 | if(is_array($n)) { 33 | $shape = $n; 34 | } elseif(is_int($n)) { 35 | $shape = [$n]; 36 | } else { 37 | throw new InvalidArgumentException('Type of argument must be int or array as shape.'); 38 | } 39 | return $shape; 40 | } 41 | 42 | public function rand(mixed $n, ?int $dtype=null) : NDArray 43 | { 44 | $shape = $this->checkSize($n); 45 | if($dtype===null) { 46 | $dtype = $this->defaultFloatType; 47 | } 48 | $array = $this->mo->zeros($shape, $dtype); 49 | $buffer = $array->buffer(); 50 | $size = $array->size(); 51 | for($i=0;$i<$size;$i++) { 52 | $buffer[$i] = $this->randomInt(PHP_INT_MAX)/PHP_INT_MAX; 53 | } 54 | return $array; 55 | } 56 | 57 | public function randn(mixed $n, ?int $dtype=null) : NDArray 58 | { 59 | $shape = $this->checkSize($n); 60 | if($dtype===null) { 61 | $dtype = $this->defaultFloatType; 62 | } 63 | $array = $this->mo->zeros($shape, $dtype); 64 | $buffer = $array->buffer(); 65 | $size = $array->size(); 66 | $av = 0.0; 67 | $sd = 1.0; 68 | for($i=0;$i<$size;$i++) { 69 | $buffer[$i] = $this->genRandNormal($av, $sd); 70 | } 71 | return $array; 72 | } 73 | 74 | public function randomInt(int $max) : int 75 | { 76 | return random_int(0, $max); 77 | } 78 | 79 | public function choice(mixed $a, ?int $size=null, ?bool $replace=null) : mixed 80 | { 81 | $arangeFlg = false; 82 | if(is_int($a)) { 83 | $a = $this->mo->arange($a); 84 | $arangeFlg = true; 85 | } elseif($a instanceof NDArray) { 86 | if($a->ndim()!=1) { 87 | throw new InvalidArgumentException('NDArray must be 1-D array.'); 88 | } 89 | } else { 90 | throw new InvalidArgumentException('First argument must be int or NDArray.'); 91 | } 92 | if($size===null) { 93 | $size = 1; 94 | } elseif($size<1) { 95 | throw new InvalidArgumentException('Size argument must be greater than or equal 1.'); 96 | } 97 | if($replace===null) { 98 | $replace = true; 99 | } 100 | 101 | if($size==1) { 102 | $idx = $this->randomInt($a->size()-1); 103 | return $a[$idx]; 104 | } 105 | 106 | $r = $this->mo->zeros([$size], $a->dtype()); 107 | $sourceSize = $a->size(); 108 | if($replace) { 109 | for($n=0;$n<$size;$n++) { 110 | $idx = $this->randomInt($sourceSize-1); 111 | $r[$n] = $a[$idx]; 112 | } 113 | } else { 114 | if($size>$sourceSize) { 115 | throw new InvalidArgumentException("The total number is smaller than the number of samples"); 116 | } 117 | if($arangeFlg) { 118 | $select = $a; 119 | } else { 120 | $select = $this->mo->arange($sourceSize); 121 | } 122 | for($n=0;$n<$size;$n++) { 123 | $idx = $this->randomInt($sourceSize-$n-1); 124 | $r[$n] = $a[$select[$idx]]; 125 | $select[$idx] = $select[$sourceSize-$n-1]; 126 | } 127 | } 128 | return $r; 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /src/Drivers/MatlibCL/params/ReduceSumTimesMode2.php: -------------------------------------------------------------------------------- 1 | \=" between int\<0, max\> and 0 is always true\.$#' 23 | identifier: greaterOrEqual.alwaysTrue 24 | count: 1 25 | path: src/Drivers/MatlibPHP/PhpLapack.php 26 | 27 | - 28 | message: '#^Constructor of class Rindow\\Math\\Matrix\\Drivers\\MatlibPHP\\PhpLapack has an unused parameter \$forceLapack\.$#' 29 | identifier: constructor.unusedParameter 30 | count: 1 31 | path: src/Drivers/MatlibPHP/PhpLapack.php 32 | 33 | - 34 | message: '#^Constructor of class Rindow\\Math\\Matrix\\Drivers\\MatlibPHP\\PhpLapack has an unused parameter \$lapack\.$#' 35 | identifier: constructor.unusedParameter 36 | count: 1 37 | path: src/Drivers/MatlibPHP/PhpLapack.php 38 | 39 | - 40 | message: '#^Comparison operation "\>" between 2 and 1 is always true\.$#' 41 | identifier: greater.alwaysTrue 42 | count: 1 43 | path: src/Drivers/MatlibPHP/PhpMath.php 44 | 45 | - 46 | message: '#^Constructor of class Rindow\\Math\\Matrix\\Drivers\\MatlibPHP\\PhpMath has an unused parameter \$forceMath\.$#' 47 | identifier: constructor.unusedParameter 48 | count: 1 49 | path: src/Drivers/MatlibPHP/PhpMath.php 50 | 51 | - 52 | message: '#^Constructor of class Rindow\\Math\\Matrix\\Drivers\\MatlibPHP\\PhpMath has an unused parameter \$math\.$#' 53 | identifier: constructor.unusedParameter 54 | count: 1 55 | path: src/Drivers/MatlibPHP/PhpMath.php 56 | 57 | - 58 | message: '#^If condition is always false\.$#' 59 | identifier: if.alwaysFalse 60 | count: 1 61 | path: src/Drivers/MatlibPHP/PhpMath.php 62 | 63 | - 64 | message: '#^Instanceof between Interop\\Polite\\Math\\Matrix\\NDArray and Interop\\Polite\\Math\\Matrix\\NDArray will always evaluate to true\.$#' 65 | identifier: instanceof.alwaysTrue 66 | count: 4 67 | path: src/LinearAlgebra.php 68 | 69 | - 70 | message: '#^Call to an undefined method Interop\\Polite\\Math\\Matrix\\Buffer\:\:copy\(\)\.$#' 71 | identifier: method.notFound 72 | count: 2 73 | path: src/LinearAlgebraCL.php 74 | 75 | - 76 | message: '#^Call to an undefined method Interop\\Polite\\Math\\Matrix\\Buffer\:\:fill\(\)\.$#' 77 | identifier: method.notFound 78 | count: 1 79 | path: src/LinearAlgebraCL.php 80 | 81 | - 82 | message: '#^Call to an undefined method Interop\\Polite\\Math\\Matrix\\NDArray\:\:getEvents\(\)\.$#' 83 | identifier: method.notFound 84 | count: 1 85 | path: src/LinearAlgebraCL.php 86 | 87 | - 88 | message: '#^Call to an undefined method Interop\\Polite\\Math\\Matrix\\NDArray\:\:toNDArray\(\)\.$#' 89 | identifier: method.notFound 90 | count: 1 91 | path: src/LinearAlgebraCL.php 92 | 93 | - 94 | message: '#^Call to an undefined method Interop\\Polite\\Math\\Matrix\\NDArray\:\:valueSize\(\)\.$#' 95 | identifier: method.notFound 96 | count: 1 97 | path: src/LinearAlgebraCL.php 98 | 99 | - 100 | message: '#^Instanceof between Interop\\Polite\\Math\\Matrix\\NDArray and Interop\\Polite\\Math\\Matrix\\NDArray will always evaluate to true\.$#' 101 | identifier: instanceof.alwaysTrue 102 | count: 4 103 | path: src/LinearAlgebraCL.php 104 | 105 | - 106 | message: '#^Call to an undefined method Interop\\Polite\\Math\\Matrix\\NDArray\:\:serialize\(\)\.$#' 107 | identifier: method.notFound 108 | count: 1 109 | path: src/MatrixOperator.php 110 | 111 | - 112 | message: '#^Instanceof between Interop\\Polite\\Math\\Matrix\\NDArray and Interop\\Polite\\Math\\Matrix\\NDArray will always evaluate to true\.$#' 113 | identifier: instanceof.alwaysTrue 114 | count: 1 115 | path: src/MatrixOperator.php 116 | 117 | - 118 | message: '#^Call to function is_numeric\(\) with int will always evaluate to true\.$#' 119 | identifier: function.alreadyNarrowedType 120 | count: 2 121 | path: src/NDArrayCL.php 122 | 123 | - 124 | message: '#^Instanceof between Interop\\Polite\\Math\\Matrix\\DeviceBuffer and Interop\\Polite\\Math\\Matrix\\DeviceBuffer will always evaluate to true\.$#' 125 | identifier: instanceof.alwaysTrue 126 | count: 1 127 | path: src/NDArrayCL.php 128 | 129 | - 130 | message: '#^Call to function is_numeric\(\) with int will always evaluate to true\.$#' 131 | identifier: function.alreadyNarrowedType 132 | count: 2 133 | path: src/NDArrayPhp.php 134 | -------------------------------------------------------------------------------- /src/Drivers/MatlibPHP/PhpBuffer.php: -------------------------------------------------------------------------------- 1 | 18 | */ 19 | class PhpBuffer extends SplFixedArray implements BufferInterface 20 | { 21 | use ComplexUtils; 22 | 23 | /** @var array $typeString */ 24 | protected static $typeString = [ 25 | NDArray::bool => 'uint8_t', 26 | NDArray::int8 => 'int8_t', 27 | NDArray::int16 => 'int16_t', 28 | NDArray::int32 => 'int32_t', 29 | NDArray::int64 => 'int64_t', 30 | NDArray::uint8 => 'uint8_t', 31 | NDArray::uint16 => 'uint16_t', 32 | NDArray::uint32 => 'uint32_t', 33 | NDArray::uint64 => 'uint64_t', 34 | //NDArray::float8 => 'N/A', 35 | //NDArray::float16 => 'N/A', 36 | NDArray::float32 => 'float', 37 | NDArray::float64 => 'double', 38 | //NDArray::complex16 => 'N/A', 39 | //NDArray::complex32 => 'N/A', 40 | NDArray::complex64 => 'complex_float', 41 | NDArray::complex128 => 'complex_double', 42 | ]; 43 | 44 | /** @var array $valueSize */ 45 | protected static $valueSize = [ 46 | NDArray::bool => 1, 47 | NDArray::int8 => 1, 48 | NDArray::int16 => 2, 49 | NDArray::int32 => 4, 50 | NDArray::int64 => 8, 51 | NDArray::uint8 => 1, 52 | NDArray::uint16 => 2, 53 | NDArray::uint32 => 4, 54 | NDArray::uint64 => 8, 55 | //NDArray::float8 => 'N/A', 56 | //NDArray::float16 => 'N/A', 57 | NDArray::float32 => 4, 58 | NDArray::float64 => 8, 59 | //NDArray::complex16 => 'N/A', 60 | //NDArray::complex32 => 'N/A', 61 | NDArray::complex64 => 8, 62 | NDArray::complex128 => 16, 63 | ]; 64 | 65 | /** @var array $pack */ 66 | protected static $pack = [ 67 | NDArray::bool => 'C', 68 | NDArray::int8 => 'c', 69 | NDArray::int16 => 's', 70 | NDArray::int32 => 'l', 71 | NDArray::int64 => 'q', 72 | NDArray::uint8 => 'C', 73 | NDArray::uint16 => 'S', 74 | NDArray::uint32 => 'L', 75 | NDArray::uint64 => 'Q', 76 | //NDArray::float8 => 'N/A', 77 | //NDArray::float16 => 'N/A', 78 | NDArray::float32 => 'g', 79 | NDArray::float64 => 'e', 80 | NDArray::complex64 => 'g', 81 | NDArray::complex128 => 'e', 82 | ]; 83 | 84 | protected int $dtype; 85 | 86 | public function __construct(int $size, int $dtype) 87 | { 88 | if(!isset(self::$typeString[$dtype])) { 89 | throw new InvalidArgumentException("Invalid data type"); 90 | } 91 | $this->dtype = $dtype; 92 | parent::__construct($size); 93 | } 94 | 95 | protected function isComplex(?int $dtype=null) : bool 96 | { 97 | $dtype = $dtype ?? $this->dtype; 98 | return $this->cistype($dtype); 99 | } 100 | 101 | /** 102 | * @param array $array 103 | */ 104 | public static function fromArray(array $array, bool $preserveKeys = true) : SplFixedArray 105 | { 106 | throw new LogicException("Unsupported operation"); 107 | } 108 | 109 | /** 110 | * @param array $array 111 | */ 112 | public static function fromArrayWithDtype(array $array, int $dtype) : BufferInterface 113 | { 114 | $a = new self(count($array), $dtype); 115 | foreach($array as $i => $v) { 116 | if(!is_int($i)) { 117 | throw new InvalidArgumentException("array must contain only positive integer keys"); 118 | } 119 | $a[$i] = $v; 120 | } 121 | return $a; 122 | } 123 | 124 | public function offsetSet($index, mixed $value) : void 125 | { 126 | if($this->isComplex()) { 127 | if(!$this->cisobject($value)) { 128 | throw new InvalidArgumentException("Cannot convert to complex number.: ".$this->cobjecttype($value)); 129 | } 130 | $value = $this->cbuild($value->real,$value->imag); 131 | } 132 | parent::offsetSet($index,$value); 133 | } 134 | 135 | public function dtype() : int 136 | { 137 | return $this->dtype; 138 | } 139 | 140 | public function value_size() : int 141 | { 142 | return $this::$valueSize[$this->dtype]; 143 | } 144 | 145 | public function dump() : string 146 | { 147 | $size = count($this); 148 | $fmt = self::$pack[$this->dtype]; 149 | $string = ''; 150 | if($this->isComplex()) { 151 | $fmt .= $fmt; 152 | for($i=0;$i<$size;++$i) { 153 | $v = $this->offsetGet($i); 154 | $string .= pack($fmt,$v->real,$v->imag); 155 | } 156 | } else { 157 | for($i=0;$i<$size;++$i) { 158 | $string .= pack($fmt,$this->offsetGet($i)); 159 | } 160 | } 161 | return $string; 162 | } 163 | 164 | public function load(string $string) : void 165 | { 166 | $fmt = self::$pack[$this->dtype].'*'; 167 | $data = unpack($fmt,$string); 168 | if($data===false) { 169 | throw new RuntimeException('Unpack error'); 170 | } 171 | $i = 0; 172 | $real = 0; 173 | if($this->isComplex()) { 174 | foreach($data as $value) { 175 | if($i%2 == 0) { 176 | $real = $value; 177 | } else { 178 | $value = $this->cbuild($real,$value); 179 | $this->offsetSet(intdiv($i,2) ,$value); 180 | } 181 | ++$i; 182 | } 183 | } else { 184 | foreach($data as $value) { 185 | $this->offsetSet($i,$value); 186 | ++$i; 187 | } 188 | } 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /.github/workflows/develop.yml: -------------------------------------------------------------------------------- 1 | name: tests 2 | 3 | on: 4 | push: 5 | branches: 6 | - develop 7 | 8 | jobs: 9 | tests: 10 | runs-on: ${{ matrix.os }} 11 | env: 12 | matlib: 1.1.0 13 | testlib: 0.0.23 14 | matlibrepo: ${{ github.repository_owner }}/rindow-matlib 15 | workspace: workspace 16 | owner: ${{ github.repository_owner }} 17 | 18 | strategy: 19 | fail-fast: false 20 | matrix: 21 | #ffi: ['','ffi'] 22 | include: 23 | #- name: PHP8.1 24 | # os: ubuntu-latest 25 | # arch: 'amd64' 26 | # php: '8.1' 27 | # phpunit: '10.5' 28 | # ffi: 'ffi' 29 | #- name: PHP8.3 30 | # os: ubuntu-latest 31 | # arch: 'amd64' 32 | # php: '8.3' 33 | # phpunit: '11.5' 34 | # ffi: 'ffi' 35 | - name: PHP8.4 36 | os: ubuntu-latest 37 | arch: 'amd64' 38 | php: '8.4' 39 | phpunit: '11.5' 40 | ffi: 'ffi' 41 | - name: Windows 42 | os: windows-latest 43 | arch: 'x64' 44 | php: '8.4' 45 | phpunit: '11.5' 46 | ffi: 'ffi' 47 | - name: macOS 48 | os: macos-latest 49 | arch: 'arm64' 50 | php: '8.4' 51 | phpunit: '11.5' 52 | ffi: 'ffi' 53 | - name: macOS-x86_64 54 | os: macos-13 55 | arch: 'x86_64' 56 | php: '8.4' 57 | phpunit: '11.5' 58 | ffi: 'ffi' 59 | 60 | steps: 61 | - name: Setup PHP ${{ matrix.php }} 62 | uses: shivammathur/setup-php@v2 63 | with: 64 | php-version: ${{ matrix.php }} 65 | # PHP Extras 66 | coverage: none 67 | tools: composer, phpstan 68 | ini-values: "memory_limit=512M" 69 | extensions: ${{ matrix.ffi }} 70 | env: 71 | fail-fast: true 72 | 73 | - name: Install Libs (Linux) 74 | if: ${{ startsWith(matrix.os, 'ubuntu-') && matrix.ffi == 'ffi' }} 75 | run: | 76 | sudo apt install -y libopenblas0 liblapacke 77 | wget https://github.com/${{ env.matlibrepo }}/releases/download/${{ env.testlib }}/rindow-matlib_${{ env.matlib }}-`lsb_release -sr`_amd64.deb 78 | sudo apt install -y ./rindow-matlib_${{ env.matlib }}-`lsb_release -sr`_amd64.deb 79 | 80 | - name: Install Libs (macOS) 81 | if: ${{ startsWith(matrix.os, 'macos-') && matrix.ffi == 'ffi' }} 82 | run: | 83 | # brew install libomp 84 | curl https://github.com/${{ env.matlibrepo }}/releases/download/${{ env.testlib }}/rindow-matlib-${{ env.matlib }}-Darwin-${{ matrix.arch }}.tar.gz -O -L 85 | tar -xzf rindow-matlib-${{ env.matlib }}-Darwin-${{ matrix.arch }}.tar.gz 86 | sudo cp -r usr/include /usr/local/ 87 | sudo cp -r usr/lib /usr/local/ 88 | 89 | #- name: Install Libs (Windows) 90 | # if: ${{ startsWith(matrix.os, 'windows-') && matrix.ffi == 'ffi' }} 91 | # run: | 92 | # Invoke-WebRequest -Uri https://github.com/OpenMathLib/OpenBLAS/releases/download/v0.3.27/OpenBLAS-0.3.27-x64.zip -OutFile openblas.zip 93 | # Expand-Archive -Path openblas.zip 94 | # Invoke-WebRequest -Uri https://github.com/${{ env.matlibrepo }}/releases/download/${{ env.testlib }}/rindow-matlib-${{ env.matlib }}-win64.zip -OutFile matlib.zip 95 | # Expand-Archive -Path matlib.zip 96 | # $currentDir = (Get-Location).Path 97 | # dir $currentDir 98 | # $OpenBLAS_DIR = Join-Path -Path $currentDir -ChildPath 'openblas' 99 | # $env:PATH = "$OpenBLAS_DIR/bin;$env:PATH" 100 | # dir $OpenBLAS_DIR/bin 101 | # $matlibDir = Join-Path -Path $currentDir -ChildPath 'matlib' 102 | # $env:PATH = "$matlibDir/bin;$env:PATH" 103 | # dir $matlibDir/bin 104 | 105 | - name: Checkout codes 106 | uses: "actions/checkout@v4" 107 | 108 | - name: Run Composer Update 109 | run: | 110 | composer update 111 | composer require --dev phpunit/phpunit=^${{ matrix.phpunit }} 112 | 113 | - name: Download Add-packs (Linux) 114 | if: ${{ startsWith(matrix.os, 'ubuntu-') }} 115 | run: | 116 | wget https://github.com/${{ env.owner }}/rindow-math-matrix-matlibffi/archive/refs/heads/${{ env.workspace }}.zip 117 | unzip ${{ env.workspace }}.zip 118 | rm ${{ env.workspace }}.zip 119 | wget https://github.com/${{ env.owner }}/rindow-math-buffer-ffi/archive/refs/heads/${{ env.workspace }}.zip 120 | unzip ${{ env.workspace }}.zip 121 | rm ${{ env.workspace }}.zip 122 | wget https://github.com/${{ env.owner }}/rindow-matlib-ffi/archive/refs/heads/${{ env.workspace }}.zip 123 | unzip ${{ env.workspace }}.zip 124 | rm ${{ env.workspace }}.zip 125 | wget https://github.com/${{ env.owner }}/rindow-openblas-ffi/archive/refs/heads/${{ env.workspace }}.zip 126 | unzip ${{ env.workspace }}.zip 127 | rm ${{ env.workspace }}.zip 128 | 129 | - name: Download Add-packs (macOS) 130 | if: ${{ startsWith(matrix.os, 'macos-') }} 131 | run: | 132 | curl https://github.com/${{ env.owner }}/rindow-math-matrix-matlibffi/archive/refs/heads/${{ env.workspace }}.zip -O -L 133 | unzip ${{ env.workspace }}.zip 134 | rm ${{ env.workspace }}.zip 135 | curl https://github.com/${{ env.owner }}/rindow-math-buffer-ffi/archive/refs/heads/${{ env.workspace }}.zip -O -L 136 | unzip ${{ env.workspace }}.zip 137 | rm ${{ env.workspace }}.zip 138 | curl https://github.com/${{ env.owner }}/rindow-matlib-ffi/archive/refs/heads/${{ env.workspace }}.zip -O -L 139 | unzip ${{ env.workspace }}.zip 140 | rm ${{ env.workspace }}.zip 141 | curl https://github.com/${{ env.owner }}/rindow-openblas-ffi/archive/refs/heads/${{ env.workspace }}.zip -O -L 142 | unzip ${{ env.workspace }}.zip 143 | rm ${{ env.workspace }}.zip 144 | 145 | - name: Download Add-packs (Windows) 146 | if: ${{ startsWith(matrix.os, 'windows-') }} 147 | run: | 148 | Invoke-WebRequest -Uri https://github.com/${{ env.owner }}/rindow-math-matrix-matlibffi/archive/refs/heads/${{ env.workspace }}.zip -OutFile ${{ env.workspace }}1.zip 149 | Expand-Archive -Path ${{ env.workspace }}1.zip -DestinationPath ${{ env.workspace }} 150 | Invoke-WebRequest -Uri https://github.com/${{ env.owner }}/rindow-math-buffer-ffi/archive/refs/heads/${{ env.workspace }}.zip -OutFile ${{ env.workspace }}2.zip 151 | Expand-Archive -Path ${{ env.workspace }}2.zip -DestinationPath ${{ env.workspace }} 152 | Invoke-WebRequest -Uri https://github.com/${{ env.owner }}/rindow-matlib-ffi/archive/refs/heads/${{ env.workspace }}.zip -OutFile ${{ env.workspace }}3.zip 153 | Expand-Archive -Path ${{ env.workspace }}3.zip -DestinationPath ${{ env.workspace }} 154 | Invoke-WebRequest -Uri https://github.com/${{ env.owner }}/rindow-openblas-ffi/archive/refs/heads/${{ env.workspace }}.zip -OutFile ${{ env.workspace }}4.zip 155 | Expand-Archive -Path ${{ env.workspace }}4.zip -DestinationPath ${{ env.workspace }} 156 | 157 | - name: PHP Static Analysys 158 | run: phpstan 159 | 160 | - name: PHPUnit Tests (${{ matrix.name }}). 161 | if: ${{ !startsWith(matrix.os, 'windows-') }} 162 | run: | 163 | WORKING_BRANCH=${{ env.workspace }}; export WORKING_BRANCH 164 | ADD_PACK=`pwd`; export ADD_PACK 165 | vendor/bin/phpunit -c tests/checkadvanced.xml 166 | 167 | - name: PHPUnit Tests (Windows) 168 | if: ${{ startsWith(matrix.os, 'windows-') }} 169 | run: | 170 | Invoke-WebRequest -Uri https://github.com/OpenMathLib/OpenBLAS/releases/download/v0.3.27/OpenBLAS-0.3.27-x64.zip -OutFile openblas.zip 171 | Expand-Archive -Path openblas.zip 172 | $currentDir = (Get-Location).Path 173 | dir $currentDir 174 | $OpenBLAS_DIR = Join-Path -Path $currentDir -ChildPath 'openblas' 175 | $env:PATH = "$OpenBLAS_DIR/bin;$env:PATH" 176 | dir $OpenBLAS_DIR/bin 177 | Invoke-WebRequest -Uri https://github.com/${{ env.matlibrepo }}/releases/download/${{ env.testlib }}/rindow-matlib-${{ env.matlib }}-win64.zip -OutFile matlib.zip 178 | Expand-Archive -Path matlib.zip 179 | $currentDir = (Get-Location).Path 180 | $matlibDir = Join-Path -Path $currentDir -ChildPath 'matlib' 181 | $env:PATH = "$matlibDir/bin;$env:PATH" 182 | dir $matlibDir/bin 183 | $env:WORKING_BRANCH = "${{ env.workspace }}" 184 | $env:ADD_PACK = "$currentDir/${{ env.workspace }}" 185 | vendor/bin/phpunit -c tests/checkadvanced.xml 186 | 187 | -------------------------------------------------------------------------------- /src/Drivers/MatlibCL/params/ScatterAddTimesMode3.php: -------------------------------------------------------------------------------- 1 | $levelString */ 20 | protected array $levelString = [ 21 | Service::LV_BASIC => 'Basic', 22 | Service::LV_ADVANCED => 'Advanced', 23 | Service::LV_ACCELERATED => 'Accelerated', 24 | ]; 25 | 26 | protected object $phpBLASFactory; 27 | protected object $phpblas; 28 | protected object $phplapack; 29 | protected object $phpmath; 30 | protected object $phpbuffer; 31 | 32 | protected object $blas; 33 | protected object $lapack; 34 | protected object $math; 35 | protected object $buffer; 36 | protected ?object $openclMath=null; 37 | protected ?object $clblastBlas=null; 38 | protected ?object $clblastMath=null; 39 | 40 | protected ?int $serviceLevel=null; 41 | protected int $logLevel = 10; 42 | 43 | public function __construct( 44 | protected ?object $bufferFactory=null, 45 | protected ?object $openblasFactory=null, 46 | protected ?object $mathFactory=null, 47 | protected ?object $openclFactory=null, 48 | protected ?object $clblastFactory=null, 49 | protected ?object $blasCLFactory=null, 50 | protected ?object $mathCLFactory=null, 51 | protected ?object $bufferCLFactory=null, 52 | protected ?int $verbose=null, 53 | ) { 54 | $this->phpBLASFactory = new PhpBLASFactory(); 55 | $this->phpblas = $this->phpBLASFactory->Blas(); 56 | $this->phplapack = $this->phpBLASFactory->Lapack(); 57 | $this->phpmath = $this->phpBLASFactory->Math(); 58 | $this->phpbuffer = $this->phpBLASFactory; 59 | $this->setVerbose($verbose); 60 | 61 | $this->injectDefaultFactories(); 62 | 63 | $level = $this->serviceLevel(); 64 | if($level>=Service::LV_ADVANCED) { 65 | $this->blas = $this->openblasFactory()->Blas(); 66 | $this->lapack = $this->openblasFactory()->Lapack(); 67 | $this->math = $this->mathFactory()->Math(); 68 | $this->buffer = $this->bufferFactory(); 69 | } else { 70 | $this->blas = $this->phpblas; 71 | $this->lapack = $this->phplapack; 72 | $this->math = $this->phpmath; 73 | $this->buffer = $this->phpbuffer; 74 | } 75 | } 76 | 77 | protected function setVerbose(?int $verbose=null) : void 78 | { 79 | $verbose ??= 0; 80 | $this->logLevel = 10 - $verbose; 81 | } 82 | 83 | protected function logging(int $level, string $message) : void 84 | { 85 | if($level < $this->logLevel) { 86 | return; 87 | } 88 | echo $message."\n"; 89 | } 90 | 91 | protected function bufferFactory() : object 92 | { 93 | if($this->bufferFactory==null) { 94 | throw new LogicException('bufferFactory is empty'); 95 | } 96 | return $this->bufferFactory; 97 | } 98 | 99 | protected function openblasFactory() : object 100 | { 101 | if($this->openblasFactory==null) { 102 | throw new LogicException('openblasFactory is empty'); 103 | } 104 | return $this->openblasFactory; 105 | } 106 | 107 | protected function mathFactory() : object 108 | { 109 | if($this->mathFactory==null) { 110 | throw new LogicException('mathFactory is empty'); 111 | } 112 | return $this->mathFactory; 113 | } 114 | 115 | protected function openclFactory() : object 116 | { 117 | if($this->openclFactory==null) { 118 | throw new LogicException('openclFactory is empty'); 119 | } 120 | return $this->openclFactory; 121 | } 122 | 123 | protected function clblastFactory() : object 124 | { 125 | if($this->clblastFactory==null) { 126 | throw new LogicException('clblastFactory is empty'); 127 | } 128 | return $this->clblastFactory; 129 | } 130 | 131 | protected function bufferCLFactory() : object 132 | { 133 | if($this->bufferCLFactory==null) { 134 | throw new LogicException('bufferCLFactory is empty'); 135 | } 136 | return $this->bufferCLFactory; 137 | } 138 | 139 | protected function blasCLFactory() : object 140 | { 141 | if($this->blasCLFactory==null) { 142 | throw new LogicException('blasCLFactory is empty'); 143 | } 144 | return $this->blasCLFactory; 145 | } 146 | 147 | protected function mathCLFactory() : object 148 | { 149 | if($this->mathCLFactory==null) { 150 | throw new LogicException('mathCLFactory is empty'); 151 | } 152 | return $this->mathCLFactory; 153 | } 154 | 155 | public function serviceLevel() : int 156 | { 157 | if($this->serviceLevel!==null) { 158 | return $this->serviceLevel; 159 | } 160 | 161 | $level = Service::LV_BASIC; 162 | $this->logging(0, 'Current level Basic.'); 163 | while(true) { 164 | if($this->bufferFactory===null || 165 | $this->openblasFactory===null || 166 | $this->mathFactory===null) { 167 | if($this->bufferFactory===null) { 168 | $this->logging(0, 'bufferFactory ** not found **.'); 169 | } 170 | if($this->openblasFactory===null) { 171 | $this->logging(0, 'openblasFactory ** not found **.'); 172 | } 173 | if($this->mathFactory===null) { 174 | $this->logging(0, 'mathFactory ** not found **.'); 175 | } 176 | break; 177 | } 178 | if(!$this->bufferFactory()->isAvailable()|| 179 | !$this->openblasFactory()->isAvailable()|| 180 | !$this->mathFactory()->isAvailable()) { 181 | if(!$this->bufferFactory()->isAvailable()) { 182 | $this->logging(0, get_class($this->bufferFactory()).' is ** not available **.'); 183 | } 184 | if(!$this->openblasFactory()->isAvailable()) { 185 | $this->logging(0, get_class($this->openblasFactory()).' is ** not available **.'); 186 | } 187 | if(!$this->mathFactory()->isAvailable()) { 188 | $this->logging(0, get_class($this->mathFactory()).' is ** not available **.'); 189 | } 190 | break; 191 | } 192 | $level = Service::LV_ADVANCED; 193 | $this->logging(0, 'Update current level to Advanced.'); 194 | 195 | if($this->openclFactory==null|| 196 | $this->clblastFactory==null) { 197 | if($this->openclFactory===null) { 198 | $this->logging(0, 'openclFactory ** not found **.'); 199 | } 200 | if($this->clblastFactory===null) { 201 | $this->logging(0, 'clblastFactory ** not found **.'); 202 | } 203 | break; 204 | } 205 | if(!$this->openclFactory()->isAvailable()|| 206 | !$this->clblastFactory()->isAvailable()) { 207 | if(!$this->openclFactory()->isAvailable()) { 208 | $this->logging(0, get_class($this->openclFactory()).' is ** not available **.'); 209 | } 210 | if(!$this->clblastFactory()->isAvailable()) { 211 | $this->logging(0, get_class($this->clblastFactory()).' is ** not available **.'); 212 | } 213 | break; 214 | } 215 | $level = Service::LV_ACCELERATED; 216 | $this->logging(0, 'Update current level to Accelerated.'); 217 | break; 218 | } 219 | 220 | $this->serviceLevel = $level; 221 | $this->logging(0, 'The service level was diagnosed as '.$this->levelString[$this->serviceLevel].'.'); 222 | return $level; 223 | } 224 | 225 | public function info() : string 226 | { 227 | $modes = [ 228 | 'SEQUENTIAL', 229 | 'THREAD', 230 | 'OPENMP', 231 | ]; 232 | $blasMode = $modes[$this->blas()->getParallel()]; 233 | $mathMode = $modes[$this->math()->getParallel()]; 234 | $info = "Service Level : ".$this->levelString[$this->serviceLevel]."\n"; 235 | $info .= "Buffer Factory : ".get_class($this->buffer)."\n"; 236 | $info .= "BLAS Driver : ".get_class($this->blas)."($blasMode)\n"; 237 | $info .= "LAPACK Driver : ".get_class($this->lapack)."\n"; 238 | $info .= "Math Driver : ".get_class($this->math)."($mathMode)\n"; 239 | if($this->serviceLevel()>=Service::LV_ACCELERATED) { 240 | $info .= "OpenCL Factory : ".get_class($this->openclFactory())."\n"; 241 | $info .= "CLBlast Factory : ".get_class($this->clblastFactory())."\n"; 242 | } 243 | return $info; 244 | } 245 | 246 | public function name() : string 247 | { 248 | return $this->name; 249 | } 250 | 251 | public function blas(?int $level=null) : object 252 | { 253 | $level = $level ?? Service::LV_ADVANCED; 254 | switch($level) { 255 | case Service::LV_BASIC: { 256 | return $this->phpblas; 257 | } 258 | case Service::LV_ADVANCED: { 259 | return $this->blas; 260 | } 261 | default: { 262 | throw new InvalidArgumentException('Unknown service level.'); 263 | } 264 | } 265 | } 266 | 267 | public function lapack(?int $level=null) : object 268 | { 269 | $level = $level ?? Service::LV_ADVANCED; 270 | switch($level) { 271 | case Service::LV_BASIC: { 272 | return $this->phplapack; 273 | } 274 | case Service::LV_ADVANCED: { 275 | return $this->lapack; 276 | } 277 | default: { 278 | throw new InvalidArgumentException('Unknown service level.'); 279 | } 280 | } 281 | } 282 | 283 | public function math(?int $level=null) : object 284 | { 285 | $level = $level ?? Service::LV_ADVANCED; 286 | switch($level) { 287 | case Service::LV_BASIC: { 288 | return $this->phpmath; 289 | } 290 | case Service::LV_ADVANCED: { 291 | return $this->math; 292 | } 293 | default: { 294 | throw new InvalidArgumentException('Unknown service level.'); 295 | } 296 | } 297 | } 298 | 299 | public function buffer(?int $level=null) : object 300 | { 301 | $level = $level ?? Service::LV_ADVANCED; 302 | switch($level) { 303 | case Service::LV_BASIC: { 304 | return $this->phpbuffer; 305 | } 306 | case Service::LV_ADVANCED: { 307 | return $this->buffer; 308 | } 309 | case Service::LV_ACCELERATED: { 310 | return $this->bufferCLFactory(); 311 | } 312 | default: { 313 | throw new InvalidArgumentException('Unknown service level.'); 314 | } 315 | } 316 | } 317 | 318 | public function opencl() : object 319 | { 320 | return $this->openclFactory(); 321 | } 322 | 323 | public function blasCL(object $queue) : object 324 | { 325 | return $this->blasCLFactory()->Blas($queue, service:$this); 326 | } 327 | 328 | public function blasCL2(object $queue) : object 329 | { 330 | return $this->mathCLFactory()->Blas($queue, service:$this); 331 | } 332 | 333 | public function mathCL(object $queue) : object 334 | { 335 | return $this->mathCLFactory()->Math($queue, service:$this); 336 | } 337 | 338 | public function mathCLBlast(object $queue) : object 339 | { 340 | return $this->clblastFactory()->Math($queue, service:$this); 341 | } 342 | 343 | public function createQueue(?array $options=null) : object 344 | { 345 | if($this->serviceLevel()getDevice($options['device']); 350 | } elseif(isset($options['deviceType'])) { 351 | $device = $this->searchDevice($options['deviceType']); 352 | } else { 353 | $device = OpenCL::CL_DEVICE_TYPE_DEFAULT; 354 | } 355 | $context = $this->openclFactory()->Context($device); 356 | $queue = $this->openclFactory()->CommandQueue($context); 357 | return $queue; 358 | } 359 | 360 | protected function getDevice(string $devOption) : object 361 | { 362 | $devOption = explode(',', $devOption); 363 | if(count($devOption)!=2) { 364 | throw new InvalidArgumentException('Device option must be two numeric with comma, etc."0,1"'); 365 | } 366 | [$platformId,$deviceId] = $devOption; 367 | if(!is_numeric($platformId)||!is_numeric($deviceId)) { 368 | throw new InvalidArgumentException('platformId and deviceId must be integer, etc."0,1"'); 369 | } 370 | $platformId = intval($platformId); 371 | $deviceId = intval($deviceId); 372 | $platform = $this->openclFactory()->PlatformList(); 373 | $platform = $platform->getOne($platformId); 374 | $device = $this->openclFactory()->DeviceList($platform); 375 | $device = $device->getOne($deviceId); 376 | return $device; 377 | } 378 | 379 | protected function searchDevice(int $deviceType) : object 380 | { 381 | $platformList = $this->openclFactory()->PlatformList(); 382 | $platformCount = $platformList->count(); 383 | for($p=0;$p<$platformCount;$p++) { 384 | $deviceList = $this->openclFactory()->DeviceList($platformList->getOne($p)); 385 | $deviceCount = $deviceList->count(); 386 | for($d=0;$d<$deviceCount;$d++) { 387 | if($deviceType==OpenCL::CL_DEVICE_TYPE_DEFAULT) { 388 | return $deviceList->getOne($d); 389 | } 390 | if($deviceList->getInfo($d, OpenCL::CL_DEVICE_TYPE)===$deviceType) { 391 | return $deviceList->getOne($d); 392 | } 393 | } 394 | } 395 | throw new InvalidArgumentException('The specified device type cannot be found'); 396 | } 397 | } 398 | -------------------------------------------------------------------------------- /src/Drivers/MatlibCL/CLInfo.php: -------------------------------------------------------------------------------- 1 | opencl = $opencl; 15 | } 16 | 17 | public function info() : void 18 | { 19 | $cl = $this->opencl; 20 | if(!$cl->isAvailable()) { 21 | throw new RuntimeException('OpenCL driver is not available: '.$cl->name()); 22 | } 23 | $platforms = $cl->PlatformList(); 24 | $numPltfrms = $platforms->count(); 25 | 26 | echo "=====================\n"; 27 | echo "===== Platforms =====\n"; 28 | echo "=====================\n"; 29 | echo "\n"; 30 | for($p=0;$p<$numPltfrms;$p++) { 31 | echo "===== Platform($p) =====\n"; 32 | $this->platformInfo($platforms->getOne($p)); 33 | } 34 | echo "\n"; 35 | echo "\n"; 36 | 37 | echo "=====================\n"; 38 | echo "====== Devices ======\n"; 39 | echo "=====================\n"; 40 | echo "\n"; 41 | for($p=0;$p<$numPltfrms;$p++) { 42 | echo "===== Platform($p) =====\n"; 43 | echo "\n"; 44 | $pltfrm = $platforms->getOne($p); 45 | $devices = $cl->DeviceList($pltfrm); 46 | $numDevs = $devices->count(); 47 | for($d=0;$d<$numDevs;$d++) { 48 | echo "===== Device($d) =====\n"; 49 | $dev = $devices->getOne($d); 50 | $this->deviceInfo($dev); 51 | echo "\n"; 52 | } 53 | } 54 | } 55 | 56 | public function platformInfo(object $platforms) : void 57 | { 58 | $i = 0; 59 | echo " CL_PLATFORM_NAME=".$platforms->getInfo($i, OpenCL::CL_PLATFORM_NAME)."\n"; 60 | echo " CL_PLATFORM_PROFILE=".$platforms->getInfo($i, OpenCL::CL_PLATFORM_PROFILE)."\n"; 61 | echo " CL_PLATFORM_VERSION=".$platforms->getInfo($i, OpenCL::CL_PLATFORM_VERSION)."\n"; 62 | echo " CL_PLATFORM_VENDOR=".$platforms->getInfo($i, OpenCL::CL_PLATFORM_VENDOR)."\n"; 63 | echo " CL_PLATFORM_EXTENSIONS=".$platforms->getInfo($i, OpenCL::CL_PLATFORM_EXTENSIONS)."\n"; 64 | } 65 | 66 | public function deviceInfo(object $devices) : void 67 | { 68 | $i = 0; 69 | assert(null!=$devices->getInfo($i, OpenCL::CL_DEVICE_NAME)); 70 | $version = $devices->getInfo($i, OpenCL::CL_DEVICE_VERSION); 71 | if(strpos($version, 'OpenCL ')===0) { 72 | $version = explode(' ', $version); 73 | $version = $version[1]; 74 | } else { 75 | $version = '0.0'; 76 | } 77 | 78 | echo " CL_DEVICE_VENDOR_ID=".$devices->getInfo($i, OpenCL::CL_DEVICE_VENDOR_ID)."\n"; 79 | echo " CL_DEVICE_NAME=".$devices->getInfo($i, OpenCL::CL_DEVICE_NAME)."\n"; 80 | echo " CL_DEVICE_TYPE=("; 81 | $device_type = $devices->getInfo($i, OpenCL::CL_DEVICE_TYPE); 82 | if($device_type&OpenCL::CL_DEVICE_TYPE_CPU) { 83 | echo "CPU,"; 84 | } 85 | if($device_type&OpenCL::CL_DEVICE_TYPE_GPU) { 86 | echo "GPU,"; 87 | } 88 | if($device_type&OpenCL::CL_DEVICE_TYPE_ACCELERATOR) { 89 | echo "ACCEL,"; 90 | } 91 | if($device_type&OpenCL::CL_DEVICE_TYPE_CUSTOM) { 92 | echo "CUSTOM,"; 93 | } 94 | echo ")\n"; 95 | echo " CL_DEVICE_MAX_WORK_ITEM_SIZES=(".implode(',', $devices->getInfo($i, OpenCL::CL_DEVICE_MAX_WORK_ITEM_SIZES)).")\n"; 96 | if(version_compare($version, '1.2')>=0) { 97 | echo " CL_DEVICE_PARTITION_TYPE=(".implode(',', $devices->getInfo($i, OpenCL::CL_DEVICE_PARTITION_TYPE)).")\n"; 98 | echo " CL_DEVICE_PARTITION_PROPERTIES=(".implode(',', array_map( 99 | function ($x) { return "0x".dechex($x);}, 100 | $devices->getInfo($i, OpenCL::CL_DEVICE_PARTITION_PROPERTIES) 101 | )).")\n"; 102 | } 103 | echo " CL_DEVICE_VENDOR=".$devices->getInfo($i, OpenCL::CL_DEVICE_VENDOR)."\n"; 104 | if(version_compare($version, '1.2')>=0) { 105 | echo " CL_DEVICE_BUILT_IN_KERNELS=".$devices->getInfo($i, OpenCL::CL_DEVICE_BUILT_IN_KERNELS)."\n"; 106 | } 107 | echo " CL_DEVICE_PROFILE=".$devices->getInfo($i, OpenCL::CL_DEVICE_PROFILE)."\n"; 108 | echo " CL_DRIVER_VERSION=".$devices->getInfo($i, OpenCL::CL_DRIVER_VERSION)."\n"; 109 | echo " CL_DEVICE_VERSION=".$devices->getInfo($i, OpenCL::CL_DEVICE_VERSION)."\n"; 110 | echo " CL_DEVICE_OPENCL_C_VERSION=".$devices->getInfo($i, OpenCL::CL_DEVICE_OPENCL_C_VERSION)."\n"; 111 | echo " CL_DEVICE_EXTENSIONS=".$devices->getInfo($i, OpenCL::CL_DEVICE_EXTENSIONS)."\n"; 112 | echo " CL_DEVICE_MAX_COMPUTE_UNITS=".$devices->getInfo($i, OpenCL::CL_DEVICE_MAX_COMPUTE_UNITS)."\n"; 113 | echo " CL_DEVICE_MAX_WORK_ITEM_DIMENSIONS=".$devices->getInfo($i, OpenCL::CL_DEVICE_MAX_WORK_ITEM_DIMENSIONS)."\n"; 114 | echo " CL_DEVICE_MAX_CLOCK_FREQUENCY=".$devices->getInfo($i, OpenCL::CL_DEVICE_MAX_CLOCK_FREQUENCY)."\n"; 115 | echo " CL_DEVICE_ADDRESS_BITS=".$devices->getInfo($i, OpenCL::CL_DEVICE_ADDRESS_BITS)."\n"; 116 | echo " CL_DEVICE_PREFERRED_VECTOR_WIDTH_CHAR=".$devices->getInfo($i, OpenCL::CL_DEVICE_PREFERRED_VECTOR_WIDTH_CHAR)."\n"; 117 | echo " CL_DEVICE_PREFERRED_VECTOR_WIDTH_SHORT=".$devices->getInfo($i, OpenCL::CL_DEVICE_PREFERRED_VECTOR_WIDTH_SHORT)."\n"; 118 | echo " CL_DEVICE_PREFERRED_VECTOR_WIDTH_INT=".$devices->getInfo($i, OpenCL::CL_DEVICE_PREFERRED_VECTOR_WIDTH_INT)."\n"; 119 | echo " CL_DEVICE_PREFERRED_VECTOR_WIDTH_LONG=".$devices->getInfo($i, OpenCL::CL_DEVICE_PREFERRED_VECTOR_WIDTH_LONG)."\n"; 120 | echo " CL_DEVICE_PREFERRED_VECTOR_WIDTH_FLOAT=".$devices->getInfo($i, OpenCL::CL_DEVICE_PREFERRED_VECTOR_WIDTH_FLOAT)."\n"; 121 | echo " CL_DEVICE_PREFERRED_VECTOR_WIDTH_DOUBLE=".$devices->getInfo($i, OpenCL::CL_DEVICE_PREFERRED_VECTOR_WIDTH_DOUBLE)."\n"; 122 | echo " CL_DEVICE_PREFERRED_VECTOR_WIDTH_HALF=".$devices->getInfo($i, OpenCL::CL_DEVICE_PREFERRED_VECTOR_WIDTH_HALF)."\n"; 123 | echo " CL_DEVICE_NATIVE_VECTOR_WIDTH_CHAR=".$devices->getInfo($i, OpenCL::CL_DEVICE_NATIVE_VECTOR_WIDTH_CHAR)."\n"; 124 | echo " CL_DEVICE_NATIVE_VECTOR_WIDTH_SHORT=".$devices->getInfo($i, OpenCL::CL_DEVICE_NATIVE_VECTOR_WIDTH_SHORT)."\n"; 125 | echo " CL_DEVICE_NATIVE_VECTOR_WIDTH_INT=".$devices->getInfo($i, OpenCL::CL_DEVICE_NATIVE_VECTOR_WIDTH_INT)."\n"; 126 | echo " CL_DEVICE_NATIVE_VECTOR_WIDTH_LONG=".$devices->getInfo($i, OpenCL::CL_DEVICE_NATIVE_VECTOR_WIDTH_LONG)."\n"; 127 | echo " CL_DEVICE_NATIVE_VECTOR_WIDTH_FLOAT=".$devices->getInfo($i, OpenCL::CL_DEVICE_NATIVE_VECTOR_WIDTH_FLOAT)."\n"; 128 | echo " CL_DEVICE_NATIVE_VECTOR_WIDTH_DOUBLE=".$devices->getInfo($i, OpenCL::CL_DEVICE_NATIVE_VECTOR_WIDTH_DOUBLE)."\n"; 129 | echo " CL_DEVICE_NATIVE_VECTOR_WIDTH_HALF=".$devices->getInfo($i, OpenCL::CL_DEVICE_NATIVE_VECTOR_WIDTH_HALF)."\n"; 130 | echo " CL_DEVICE_MAX_READ_IMAGE_ARGS=".$devices->getInfo($i, OpenCL::CL_DEVICE_MAX_READ_IMAGE_ARGS)."\n"; 131 | echo " CL_DEVICE_MAX_WRITE_IMAGE_ARGS=".$devices->getInfo($i, OpenCL::CL_DEVICE_MAX_WRITE_IMAGE_ARGS)."\n"; 132 | echo " CL_DEVICE_MAX_SAMPLERS=".$devices->getInfo($i, OpenCL::CL_DEVICE_MAX_SAMPLERS)."\n"; 133 | echo " CL_DEVICE_MEM_BASE_ADDR_ALIGN=".$devices->getInfo($i, OpenCL::CL_DEVICE_MEM_BASE_ADDR_ALIGN)."\n"; 134 | echo " CL_DEVICE_MIN_DATA_TYPE_ALIGN_SIZE=".$devices->getInfo($i, OpenCL::CL_DEVICE_MIN_DATA_TYPE_ALIGN_SIZE)."\n"; 135 | echo " CL_DEVICE_GLOBAL_MEM_CACHELINE_SIZE=".$devices->getInfo($i, OpenCL::CL_DEVICE_GLOBAL_MEM_CACHELINE_SIZE)."\n"; 136 | echo " CL_DEVICE_MAX_CONSTANT_ARGS=".$devices->getInfo($i, OpenCL::CL_DEVICE_MAX_CONSTANT_ARGS)."\n"; 137 | if(version_compare($version, '1.2')>=0) { 138 | echo " CL_DEVICE_PARTITION_MAX_SUB_DEVICES=".$devices->getInfo($i, OpenCL::CL_DEVICE_PARTITION_MAX_SUB_DEVICES)."\n"; 139 | echo " CL_DEVICE_REFERENCE_COUNT=".$devices->getInfo($i, OpenCL::CL_DEVICE_REFERENCE_COUNT)."\n"; 140 | } 141 | echo " CL_DEVICE_GLOBAL_MEM_CACHE_TYPE=".$devices->getInfo($i, OpenCL::CL_DEVICE_GLOBAL_MEM_CACHE_TYPE)."\n"; 142 | echo " CL_DEVICE_LOCAL_MEM_TYPE=".$devices->getInfo($i, OpenCL::CL_DEVICE_LOCAL_MEM_TYPE)."\n"; 143 | echo " CL_DEVICE_MAX_MEM_ALLOC_SIZE=".$devices->getInfo($i, OpenCL::CL_DEVICE_MAX_MEM_ALLOC_SIZE)."\n"; 144 | echo " CL_DEVICE_GLOBAL_MEM_CACHE_SIZE=".$devices->getInfo($i, OpenCL::CL_DEVICE_GLOBAL_MEM_CACHE_SIZE)."\n"; 145 | echo " CL_DEVICE_GLOBAL_MEM_SIZE=".$devices->getInfo($i, OpenCL::CL_DEVICE_GLOBAL_MEM_SIZE)."\n"; 146 | echo " CL_DEVICE_MAX_CONSTANT_BUFFER_SIZE=".$devices->getInfo($i, OpenCL::CL_DEVICE_MAX_CONSTANT_BUFFER_SIZE)."\n"; 147 | echo " CL_DEVICE_LOCAL_MEM_SIZE=".$devices->getInfo($i, OpenCL::CL_DEVICE_LOCAL_MEM_SIZE)."\n"; 148 | echo " CL_DEVICE_IMAGE_SUPPORT=".$devices->getInfo($i, OpenCL::CL_DEVICE_IMAGE_SUPPORT)."\n"; 149 | echo " CL_DEVICE_ERROR_CORRECTION_SUPPORT=".$devices->getInfo($i, OpenCL::CL_DEVICE_ERROR_CORRECTION_SUPPORT)."\n"; 150 | echo " CL_DEVICE_HOST_UNIFIED_MEMORY=".$devices->getInfo($i, OpenCL::CL_DEVICE_HOST_UNIFIED_MEMORY)."\n"; 151 | echo " CL_DEVICE_ENDIAN_LITTLE=".$devices->getInfo($i, OpenCL::CL_DEVICE_ENDIAN_LITTLE)."\n"; 152 | echo " CL_DEVICE_AVAILABLE=".$devices->getInfo($i, OpenCL::CL_DEVICE_AVAILABLE)."\n"; 153 | echo " CL_DEVICE_COMPILER_AVAILABLE=".$devices->getInfo($i, OpenCL::CL_DEVICE_COMPILER_AVAILABLE)."\n"; 154 | if(version_compare($version, '1.2')>=0) { 155 | echo " CL_DEVICE_LINKER_AVAILABLE=".$devices->getInfo($i, OpenCL::CL_DEVICE_LINKER_AVAILABLE)."\n"; 156 | echo " CL_DEVICE_PREFERRED_INTEROP_USER_SYNC=".$devices->getInfo($i, OpenCL::CL_DEVICE_PREFERRED_INTEROP_USER_SYNC)."\n"; 157 | } 158 | echo " CL_DEVICE_MAX_WORK_GROUP_SIZE=".$devices->getInfo($i, OpenCL::CL_DEVICE_MAX_WORK_GROUP_SIZE)."\n"; 159 | echo " CL_DEVICE_IMAGE2D_MAX_WIDTH=".$devices->getInfo($i, OpenCL::CL_DEVICE_IMAGE2D_MAX_WIDTH)."\n"; 160 | echo " CL_DEVICE_IMAGE2D_MAX_HEIGHT=".$devices->getInfo($i, OpenCL::CL_DEVICE_IMAGE2D_MAX_HEIGHT)."\n"; 161 | echo " CL_DEVICE_IMAGE3D_MAX_WIDTH=".$devices->getInfo($i, OpenCL::CL_DEVICE_IMAGE3D_MAX_WIDTH)."\n"; 162 | echo " CL_DEVICE_IMAGE3D_MAX_HEIGHT=".$devices->getInfo($i, OpenCL::CL_DEVICE_IMAGE3D_MAX_HEIGHT)."\n"; 163 | echo " CL_DEVICE_IMAGE3D_MAX_DEPTH=".$devices->getInfo($i, OpenCL::CL_DEVICE_IMAGE3D_MAX_DEPTH)."\n"; 164 | if(version_compare($version, '1.2')>=0) { 165 | echo " CL_DEVICE_IMAGE_MAX_BUFFER_SIZE=".$devices->getInfo($i, OpenCL::CL_DEVICE_IMAGE_MAX_BUFFER_SIZE)."\n"; 166 | echo " CL_DEVICE_IMAGE_MAX_ARRAY_SIZE=".$devices->getInfo($i, OpenCL::CL_DEVICE_IMAGE_MAX_ARRAY_SIZE)."\n"; 167 | } 168 | echo " CL_DEVICE_MAX_PARAMETER_SIZE=".$devices->getInfo($i, OpenCL::CL_DEVICE_MAX_PARAMETER_SIZE)."\n"; 169 | echo " CL_DEVICE_PROFILING_TIMER_RESOLUTION=".$devices->getInfo($i, OpenCL::CL_DEVICE_PROFILING_TIMER_RESOLUTION)."\n"; 170 | if(version_compare($version, '1.2')>=0) { 171 | echo " CL_DEVICE_PRINTF_BUFFER_SIZE=".$devices->getInfo($i, OpenCL::CL_DEVICE_PRINTF_BUFFER_SIZE)."\n"; 172 | } 173 | echo " CL_DEVICE_SINGLE_FP_CONFIG=("; 174 | $config = $devices->getInfo($i, OpenCL::CL_DEVICE_SINGLE_FP_CONFIG); 175 | if($config&OpenCL::CL_FP_DENORM) { 176 | echo "DENORM,"; 177 | } 178 | if($config&OpenCL::CL_FP_INF_NAN) { 179 | echo "INF_NAN,"; 180 | } 181 | if($config&OpenCL::CL_FP_ROUND_TO_NEAREST) { 182 | echo "ROUND_TO_NEAREST,"; 183 | } 184 | if($config&OpenCL::CL_FP_ROUND_TO_ZERO) { 185 | echo "ROUND_TO_ZERO,"; 186 | } 187 | if($config&OpenCL::CL_FP_ROUND_TO_INF) { 188 | echo "ROUND_TO_INF,"; 189 | } 190 | if($config&OpenCL::CL_FP_FMA) { 191 | echo "FMA,"; 192 | } 193 | if($config&OpenCL::CL_FP_SOFT_FLOAT) { 194 | echo "SOFT_FLOAT,"; 195 | } 196 | if($config&OpenCL::CL_FP_CORRECTLY_ROUNDED_DIVIDE_SQRT) { 197 | echo "CORRECTLY_ROUNDED_DIVIDE_SQRT,"; 198 | } 199 | echo ")\n"; 200 | echo " CL_DEVICE_DOUBLE_FP_CONFIG=("; 201 | $config = $devices->getInfo($i, OpenCL::CL_DEVICE_DOUBLE_FP_CONFIG); 202 | if($config&OpenCL::CL_FP_DENORM) { 203 | echo "DENORM,"; 204 | } 205 | if($config&OpenCL::CL_FP_INF_NAN) { 206 | echo "INF_NAN,"; 207 | } 208 | if($config&OpenCL::CL_FP_ROUND_TO_NEAREST) { 209 | echo "ROUND_TO_NEAREST,"; 210 | } 211 | if($config&OpenCL::CL_FP_ROUND_TO_ZERO) { 212 | echo "ROUND_TO_ZERO,"; 213 | } 214 | if($config&OpenCL::CL_FP_ROUND_TO_INF) { 215 | echo "ROUND_TO_INF,"; 216 | } 217 | if($config&OpenCL::CL_FP_FMA) { 218 | echo "FMA,"; 219 | } 220 | if($config&OpenCL::CL_FP_SOFT_FLOAT) { 221 | echo "SOFT_FLOAT,"; 222 | } 223 | if($config&OpenCL::CL_FP_CORRECTLY_ROUNDED_DIVIDE_SQRT) { 224 | echo "CORRECTLY_ROUNDED_DIVIDE_SQRT,"; 225 | } 226 | echo ")\n"; 227 | echo " CL_DEVICE_EXECUTION_CAPABILITIES=("; 228 | $config = $devices->getInfo($i, OpenCL::CL_DEVICE_EXECUTION_CAPABILITIES); 229 | if($config&OpenCL::CL_EXEC_KERNEL) { 230 | echo "KERNEL,"; 231 | } 232 | if($config&OpenCL::CL_EXEC_NATIVE_KERNEL) { 233 | echo "NATIVE_KERNEL,"; 234 | } 235 | echo ")\n"; 236 | echo " CL_DEVICE_QUEUE_PROPERTIES=("; 237 | $config = $devices->getInfo($i, OpenCL::CL_DEVICE_QUEUE_PROPERTIES); 238 | if($config&OpenCL::CL_QUEUE_OUT_OF_ORDER_EXEC_MODE_ENABLE) { 239 | echo "OUT_OF_ORDER_EXEC_MODE_ENABLE,"; 240 | } 241 | if($config&OpenCL::CL_QUEUE_PROFILING_ENABLE) { 242 | echo "PROFILING_ENABLE,"; 243 | } 244 | echo ")\n"; 245 | if(version_compare($version, '1.2')>=0) { 246 | echo " CL_DEVICE_PARENT_DEVICE=("; 247 | $parent_device = $devices->getInfo($i, OpenCL::CL_DEVICE_PARENT_DEVICE); 248 | if($parent_device) { 249 | echo " deivces(".$parent_device->count().")\n"; 250 | for($j=0;$j<$parent_device->count();$j++) { 251 | echo " CL_DEVICE_NAME=".$parent_device->getInfo($j, OpenCL::CL_DEVICE_NAME)."\n"; 252 | echo " CL_DEVICE_VENDOR=".$parent_device->getInfo($j, OpenCL::CL_DEVICE_VENDOR)."\n"; 253 | echo " CL_DEVICE_TYPE=("; 254 | $device_type = $parent_device->getInfo($j, OpenCL::CL_DEVICE_TYPE); 255 | if($device_type&OpenCL::CL_DEVICE_TYPE_CPU) { 256 | echo "CPU,"; 257 | } 258 | if($device_type&OpenCL::CL_DEVICE_TYPE_GPU) { 259 | echo "GPU,"; 260 | } 261 | if($device_type&OpenCL::CL_DEVICE_TYPE_ACCELERATOR) { 262 | echo "ACCEL,"; 263 | } 264 | if($device_type&OpenCL::CL_DEVICE_TYPE_CUSTOM) { 265 | echo "CUSTOM,"; 266 | } 267 | echo ")\n"; 268 | } 269 | } 270 | echo ")\n"; 271 | } 272 | echo " CL_DEVICE_PLATFORM=(\n"; 273 | $device_platform = $devices->getInfo($i, OpenCL::CL_DEVICE_PLATFORM); 274 | echo " platforms(".$device_platform->count().")\n"; 275 | for($j=0;$j<$device_platform->count();$j++) { 276 | echo " CL_PLATFORM_NAME=".$device_platform->getInfo($j, OpenCL::CL_PLATFORM_NAME)."\n"; 277 | echo " CL_PLATFORM_PROFILE=".$device_platform->getInfo($j, OpenCL::CL_PLATFORM_PROFILE)."\n"; 278 | echo " CL_PLATFORM_VERSION=".$device_platform->getInfo($j, OpenCL::CL_PLATFORM_VERSION)."\n"; 279 | echo " CL_PLATFORM_VENDOR=".$device_platform->getInfo($j, OpenCL::CL_PLATFORM_VENDOR)."\n"; 280 | echo " CL_PLATFORM_EXTENSIONS=".$device_platform->getInfo($j, OpenCL::CL_PLATFORM_EXTENSIONS)."\n"; 281 | } 282 | echo " )\n"; 283 | } 284 | } 285 | -------------------------------------------------------------------------------- /src/NDArrayCL.php: -------------------------------------------------------------------------------- 1 | 24 | */ 25 | class NDArrayCL implements NDArray, Countable, IteratorAggregate 26 | { 27 | const RANGE_STYLE_DEFAULT = 0; 28 | const RANGE_STYLE_1 = 1; 29 | const RANGE_STYLE_FORCE2 = 2; 30 | public static int $rangeStyle = self::RANGE_STYLE_DEFAULT; 31 | 32 | /** @var array $valueSizeTable */ 33 | protected static $valueSizeTable = [ 34 | NDArray::bool => 1, 35 | NDArray::int8 => 1, 36 | NDArray::int16 => 2, 37 | NDArray::int32 => 4, 38 | NDArray::int64 => 8, 39 | NDArray::uint8 => 1, 40 | NDArray::uint16 => 2, 41 | NDArray::uint32 => 4, 42 | NDArray::uint64 => 8, 43 | NDArray::float8 => 1, 44 | NDArray::float16 => 2, 45 | NDArray::float32 => 4, 46 | NDArray::float64 => 8, 47 | NDArray::complex16 => 2, 48 | NDArray::complex32 => 4, 49 | NDArray::complex64 => 8, 50 | NDArray::complex128 => 16, 51 | ]; 52 | protected Service $service; 53 | protected object $clBufferFactory; 54 | protected object $context; 55 | protected object $queue; 56 | /** @var array $shape */ 57 | protected array $shape; 58 | protected DeviceBuffer $buffer; 59 | protected int $offset; 60 | protected int $dtype; 61 | protected int $flags; 62 | protected bool $portableSerializeMode = false; 63 | protected object $events; 64 | 65 | /** 66 | * @param array $shape 67 | */ 68 | final public function __construct( 69 | object $queue, 70 | mixed $buffer=null, 71 | ?int $dtype=null, 72 | ?array $shape = null, 73 | ?int $offset=null, 74 | ?int $flags=null, 75 | ?Service $service=null 76 | ) { 77 | if($service===null) { 78 | throw new InvalidArgumentException("No service specified."); 79 | } 80 | $this->service = $service; 81 | $this->clBufferFactory = $service->buffer(Service::LV_ACCELERATED); 82 | $context = $queue->getContext(); 83 | $this->context = $context; 84 | $this->queue = $queue; 85 | if($dtype===null) { 86 | $dtype = NDArray::float32; 87 | } else { 88 | $dtype = $dtype; 89 | } 90 | if($offset===null) { 91 | $offset = 0; 92 | } 93 | if($flags===null) { 94 | $flags = OpenCL::CL_MEM_READ_WRITE; 95 | } 96 | if($shape===null) { 97 | throw new InvalidArgumentException("Invalid dimension size"); 98 | } 99 | 100 | $this->assertShape($shape); 101 | $this->shape = $shape; 102 | $this->flags = $flags; 103 | $size = (int)array_product($shape); 104 | if($buffer instanceof DeviceBuffer) { 105 | if($buffer->bytes() 106 | < ($size + $offset)*static::$valueSizeTable[$dtype]) { 107 | throw new InvalidArgumentException("Invalid dimension size"); 108 | } 109 | $this->dtype = $dtype; 110 | $this->buffer = $buffer; 111 | $this->offset = $offset; 112 | } elseif($buffer===null) { 113 | $size = (int)array_product($shape); 114 | $this->buffer = $this->newBuffer($context, $size, $dtype, $flags); 115 | $this->dtype = $dtype; 116 | $this->offset = 0; 117 | } elseif($buffer instanceof LinearBuffer) { 118 | $size = (int)array_product($shape); 119 | if($size > count($buffer)-$offset) { 120 | throw new InvalidArgumentException("host buffer is too small"); 121 | } 122 | $this->buffer = $this->newBuffer( 123 | $context, 124 | $size, 125 | $buffer->dtype(), 126 | $flags, 127 | $buffer, 128 | $offset 129 | ); 130 | $this->dtype = $buffer->dtype(); 131 | $this->offset = 0; 132 | } else { 133 | if(is_object($buffer)) { 134 | $typename = get_class($buffer); 135 | } else { 136 | $typename = gettype($buffer); 137 | } 138 | throw new InvalidArgumentException("Invalid type of array: ".$typename); 139 | } 140 | } 141 | 142 | public function service() : Service 143 | { 144 | return $this->service; 145 | } 146 | 147 | protected function newBuffer( 148 | object $context, 149 | int $size, 150 | int $dtype, 151 | int $flags=0, 152 | ?object $hostBuffer=null, 153 | int $hostOffset=0 154 | ) : DeviceBuffer { 155 | //if(!extension_loaded('rindow_opencl')) { 156 | // throw new LogicException("rindow_opencl extension is not loaded."); 157 | //} 158 | //return new OpenCLBuffer($context,static::$valueSizeTable[$dtype]*$size, 159 | // $flags,$hostBuffer,$hostOffset,$dtype); 160 | return $this->clBufferFactory->Buffer( 161 | $context, 162 | static::$valueSizeTable[$dtype]*$size, 163 | $flags, 164 | $hostBuffer, 165 | $hostOffset, 166 | $dtype 167 | ); 168 | } 169 | 170 | /** 171 | * @param array $shape 172 | */ 173 | protected function assertShape(array $shape) : void 174 | { 175 | foreach($shape as $num) { 176 | if(!is_int($num)) { 177 | throw new InvalidArgumentException( 178 | "Invalid shape numbers. It gives ".gettype($num) 179 | ); 180 | } 181 | if($num<=0) { 182 | throw new InvalidArgumentException( 183 | "Invalid shape numbers. It gives ".$num 184 | ); 185 | } 186 | } 187 | } 188 | 189 | /** 190 | * @return array 191 | */ 192 | public function shape() : array 193 | { 194 | return $this->shape; 195 | } 196 | 197 | public function ndim() : int 198 | { 199 | return count($this->shape); 200 | } 201 | 202 | public function dtype() : int 203 | { 204 | return $this->dtype; 205 | } 206 | 207 | public function flags() : int 208 | { 209 | return $this->flags; 210 | } 211 | 212 | public function buffer() : Buffer 213 | { 214 | return $this->buffer; 215 | } 216 | 217 | public function offset() : int 218 | { 219 | return $this->offset; 220 | } 221 | 222 | public function valueSize() : int 223 | { 224 | return static::$valueSizeTable[$this->dtype]; 225 | } 226 | 227 | public function size() : int 228 | { 229 | return (int)array_product($this->shape); 230 | } 231 | 232 | /** 233 | * @param array $shape 234 | */ 235 | public function reshape(array $shape) : NDArray 236 | { 237 | $this->assertShape($shape); 238 | if($this->size()!=array_product($shape)) { 239 | throw new InvalidArgumentException("Unmatch size to reshape: ". 240 | "[".implode(',', $this->shape())."]=>[".implode(',', $shape)."]"); 241 | } 242 | $newArray = new static($this->queue,$this->buffer, 243 | $this->dtype,$shape,$this->offset,$this->flags, service:$this->service()); 244 | return $newArray; 245 | } 246 | 247 | public function toArray() : mixed 248 | { 249 | return $this->toNDArray()->toArray(); 250 | } 251 | 252 | public function toNDArray( 253 | ?bool $blocking_read=null, 254 | ?object $events=null, 255 | ?object $waitEvents=null 256 | ) : NDArray { 257 | $blocking_read = $blocking_read ?? true; 258 | $array = new NDArrayPhp(null, $this->dtype, $this->shape, service:$this->service()); 259 | $valueSize = static::$valueSizeTable[$this->dtype]; 260 | $size = array_product($this->shape); 261 | $this->buffer->read( 262 | $this->queue, 263 | $array->buffer(), 264 | $size*$valueSize, 265 | $this->offset*$valueSize, 266 | $hostoffset=0, 267 | $blocking_read, 268 | $events, 269 | $waitEvents 270 | ); 271 | return $array; 272 | } 273 | 274 | /** 275 | * @return int|array|Range 276 | */ 277 | protected function castOffset(mixed $offset) : int|array|Range 278 | { 279 | if(!is_int($offset)&&!is_array($offset)&&!($offset instanceof Range)) { 280 | throw new InvalidArgumentException("Array offsets must be integers or ranges."); 281 | } 282 | return $offset; 283 | } 284 | 285 | public function offsetExists($offset) : bool 286 | { 287 | $offset = $this->castOffset($offset); 288 | if(is_array($offset) && self::$rangeStyle==self::RANGE_STYLE_FORCE2) { 289 | throw new InvalidArgumentException("offset style is old renge style."); 290 | } 291 | if(is_array($offset)) { 292 | if(count($offset)!=2 || 293 | !array_key_exists(0, $offset) || !array_key_exists(1, $offset) || 294 | $offset[0]>$offset[1]) { 295 | $det = ''; 296 | if(is_numeric($offset[0])&&is_numeric($offset[1])) { 297 | $det = ':['. implode(',', $offset).']'; 298 | } 299 | throw new OutOfRangeException("Illegal range specification.".$det); 300 | } 301 | $start = $offset[0]; 302 | $limit = $offset[1]; 303 | if(self::$rangeStyle==self::RANGE_STYLE_1) { 304 | ++$limit; 305 | } 306 | } elseif(is_int($offset)) { 307 | $start = $offset; 308 | $limit = $offset+1; 309 | } else { 310 | $start = $offset->start(); 311 | $limit = $offset->limit(); 312 | $delta = $offset->delta(); 313 | if($start>=$limit||$delta!=1) { 314 | $det = ":[$start,$limit".(($delta!=1)?",$delta":"").']'; 315 | throw new OutOfRangeException("Illegal range specification.".$det); 316 | } 317 | } 318 | if($start < 0 || $limit > $this->shape[0]) { 319 | return false; 320 | } 321 | return true; 322 | } 323 | 324 | public function offsetGet($offset) : mixed 325 | { 326 | $offset = $this->castOffset($offset); 327 | if(!$this->offsetExists($offset)) { 328 | if(count($this->shape)==0) { 329 | throw new OutOfRangeException("This object is scalar."); 330 | } else { 331 | if(is_array($offset)) { 332 | $string = '('.implode(',', $offset).')'; 333 | } elseif(is_object($offset)) { 334 | $string = '('.$offset->start().','.$offset->limit().')'; 335 | } else { 336 | $string = strval($offset); 337 | } 338 | throw new OutOfRangeException("Index is out of range. range allows (0,{$this->shape[0]}): $string given."); 339 | } 340 | } 341 | 342 | // for single index specification 343 | if(is_numeric($offset)) { 344 | $offset = (int)$offset; 345 | $shape = $this->shape; 346 | $max = array_shift($shape); 347 | if(count($shape)==0) { 348 | $new = new static($this->queue,$this->buffer,$this->dtype, 349 | $shape, $this->offset+$offset, $this->flags, service:$this->service()); 350 | return $new; 351 | } 352 | $size = (int)array_product($shape); 353 | $new = new static($this->queue, $this->buffer, $this->dtype, 354 | $shape, $this->offset+$offset*$size, $this->flags, service:$this->service()); 355 | return $new; 356 | } 357 | 358 | // for range spesification 359 | $shape = $this->shape; 360 | array_shift($shape); 361 | if(is_array($offset)) { 362 | $start = (int)$offset[0]; 363 | $limit = (int)$offset[1]; 364 | if(self::$rangeStyle==self::RANGE_STYLE_1) { 365 | ++$limit; 366 | } 367 | } else { 368 | $start = (int)$offset->start(); 369 | $limit = (int)$offset->limit(); 370 | if($offset->delta()!=1) { 371 | throw new OutOfRangeException("Illegal range specification.:delta=".$offset->delta()); 372 | } 373 | } 374 | $rowsCount = $limit-$start; 375 | if(count($shape)>0) { 376 | $itemSize = (int)array_product($shape); 377 | } else { 378 | $itemSize = 1; 379 | } 380 | if($rowsCount<0) { 381 | throw new OutOfRangeException('Invalid range'); 382 | } 383 | array_unshift($shape, $rowsCount); 384 | $size = (int)array_product($shape); 385 | $new = new static($this->queue,$this->buffer,$this->dtype, 386 | $shape,$this->offset+$start*$itemSize, $this->flags, service:$this->service()); 387 | return $new; 388 | } 389 | 390 | public function offsetSet($offset, $value) : void 391 | { 392 | $offset = $this->castOffset($offset); 393 | if(!$this->offsetExists($offset)) { 394 | if(count($this->shape)==0) { 395 | throw new OutOfRangeException("This object is scalar."); 396 | } else { 397 | throw new OutOfRangeException("Index is out of range"); 398 | } 399 | } 400 | // for range spesification 401 | if(!is_int($offset)) { 402 | throw new OutOfRangeException("Unsuppored to set for range specification."); 403 | } 404 | // for single index specification 405 | $shape = $this->shape; 406 | $max = array_shift($shape); 407 | if(count($shape)==0) { 408 | if(!($value instanceof NDArray)) { 409 | throw new LogicException("Must be NDArray on OpenCL."); 410 | } 411 | $buffer = $value->buffer(); 412 | if(!($buffer instanceof DeviceBuffer)) { 413 | throw new LogicException("Must be NDArray on OpenCL."); 414 | } 415 | $valueSize = static::$valueSizeTable[$value->dtype()]; 416 | $this->buffer->copy( 417 | $this->queue, 418 | $buffer, 419 | $valueSize, 420 | $value->offset()*$valueSize, 421 | ($this->offset+$offset)*$valueSize 422 | ); 423 | return; 424 | } 425 | 426 | if(!($value instanceof static)||$value->shape()!=$shape) { 427 | throw new LogicException("Unmatch shape numbers"); 428 | } 429 | $src = $value->buffer(); 430 | $size = (int)array_product($shape); 431 | $src_idx = $value->offset(); 432 | $idx=$this->offset+$offset*$size; 433 | 434 | $valueSize = static::$valueSizeTable[$value->dtype()]; 435 | $this->buffer->copy( 436 | $this->queue, 437 | $src, 438 | $size*$valueSize, 439 | $src_idx*$valueSize, 440 | $idx*$valueSize 441 | ); 442 | } 443 | 444 | public function offsetUnset($offset) : void 445 | { 446 | throw new LogicException("Unsuppored Operation"); 447 | } 448 | 449 | public function count() : int 450 | { 451 | return $this->shape[0]; 452 | } 453 | 454 | public function getIterator() : Traversable 455 | { 456 | $count = $this->shape[0]; 457 | for($i=0;$i<$count;$i++) { 458 | yield $i => $this->offsetGet($i); 459 | } 460 | } 461 | 462 | public function setPortableSerializeMode(bool $mode) : void 463 | { 464 | throw new LogicException("Unsuppored Operation"); 465 | } 466 | 467 | public function getPortableSerializeMode() : bool 468 | { 469 | return $this->portableSerializeMode; 470 | } 471 | 472 | public function __serialize() : array 473 | { 474 | throw new LogicException("Unsuppored Operation"); 475 | } 476 | 477 | /** 478 | * @param array $serialized 479 | */ 480 | public function __unserialize(array $serialized) : void 481 | { 482 | throw new LogicException("Unsuppored Operation"); 483 | } 484 | 485 | public function setEvents(object $events) : void 486 | { 487 | $this->events = $events; 488 | } 489 | 490 | public function getEvents() : object 491 | { 492 | return $this->events; 493 | } 494 | 495 | public function __clone() 496 | { 497 | if(!($this->buffer instanceof DeviceBuffer)) { 498 | throw new RuntimeException('Unknown buffer type is uncloneable:'.get_class($this->buffer)); 499 | } 500 | $bytes = $this->buffer->bytes(); 501 | $dtype = $this->buffer->dtype(); 502 | $flags = $this->flags & ~OpenCL::CL_MEM_COPY_HOST_PTR; 503 | $newBuffer = $this->clBufferFactory->Buffer( 504 | $this->context, 505 | $bytes, 506 | $flags, 507 | null, 508 | 0, 509 | $dtype 510 | ); 511 | $events = $this->service()->openCL()->EventList(); 512 | $newBuffer->copy($this->queue, $this->buffer, 0, 0, 0, $events); 513 | $events->wait(); 514 | $this->flags = $flags; 515 | $this->buffer = $newBuffer; 516 | } 517 | } 518 | -------------------------------------------------------------------------------- /src/Drivers/MatlibPHP/PhpLapack.php: -------------------------------------------------------------------------------- 1 | $intTypes */ 19 | protected $intTypes= [ 20 | NDArray::int8,NDArray::int16,NDArray::int32,NDArray::int64, 21 | NDArray::uint8,NDArray::uint16,NDArray::uint32,NDArray::uint64, 22 | ]; 23 | /** @var array $floatTypes */ 24 | protected $floatTypes= [ 25 | NDArray::float16,NDArray::float32,NDArray::float64, 26 | ]; 27 | 28 | public function __construct( 29 | ?object $lapack=null, 30 | ?bool $forceLapack=null 31 | ) 32 | { 33 | //$this->lapack = $lapack; 34 | //$this->forceLapack = $forceLapack; 35 | $this->service = $this->dummyService(); 36 | } 37 | 38 | protected function dummyService() : Service 39 | { 40 | $service = new class implements Service 41 | { 42 | protected object $bufferFactory; 43 | 44 | public function __construct() 45 | { 46 | $this->bufferFactory = new PhpBLASFactory(); 47 | } 48 | 49 | public function serviceLevel() : int 50 | { 51 | return Service::LV_BASIC; 52 | } 53 | 54 | public function buffer(?int $level=null) : object 55 | { 56 | return $this->bufferFactory; 57 | } 58 | public function info() : string {throw new \Exception("error");} 59 | public function name() : string {throw new \Exception("error");} 60 | public function blas(?int $level=null) : object {throw new \Exception("error");} 61 | public function lapack(?int $level=null) : object {throw new \Exception("error");} 62 | public function math(?int $level=null) : object {throw new \Exception("error");} 63 | public function openCL() : object {throw new \Exception("error");} 64 | public function blasCL(object $queue) : object {throw new \Exception("error");} 65 | public function mathCL(object $queue) : object {throw new \Exception("error");} 66 | public function mathCLBlast(object $queue) : object {throw new \Exception("error");} 67 | public function createQueue(?array $options=null) : object { throw new \Exception("error");} 68 | }; 69 | return $service; 70 | } 71 | 72 | //public function forceLapack($forceLapack) 73 | //{ 74 | // $this->forceLapack = $forceLapack; 75 | //} 76 | 77 | //protected function useLapack(Buffer $X) 78 | //{ 79 | // if($this->lapack===null) 80 | // return false; 81 | // return $this->forceLapack || in_array($X->dtype(),$this->floatTypes); 82 | //} 83 | 84 | /** 85 | * Below is the author of the original code 86 | * @author Yehia Abed 87 | * @copyright 2010 88 | * @see https://github.com/d3veloper/SVD 89 | * 90 | * **************** CAUTION ********************* 91 | * OpenBLAS has the DGESVD. But it is difficult to use DGESVD in LAPACKE 92 | * on PHP script. it is the implement temporarily. 93 | * 94 | */ 95 | public function gesvd( 96 | int $matrix_layout, 97 | int $jobu, 98 | int $jobvt, 99 | int $m, 100 | int $n, 101 | Buffer $A, int $offsetA, int $ldA, 102 | Buffer $S, int $offsetS, 103 | Buffer $U, int $offsetU, int $ldU, 104 | Buffer $VT, int $offsetVT, int $ldVT, 105 | Buffer $SuperB, int $offsetSuperB 106 | ) : void 107 | { 108 | //if($this->useLapack($A)) { 109 | // $this->lapack->gesvd( 110 | // $matrix_layout, 111 | // $jobu, 112 | // $jobvt, 113 | // $m, 114 | // $n, 115 | // $A, $offsetA, $ldA, 116 | // $S, $offsetS, 117 | // $U, $offsetU, $ldU, 118 | // $VT, $offsetVT, $ldVT, 119 | // $SuperB, $offsetSuperB 120 | // ); 121 | // return; 122 | //} 123 | if(method_exists($A,'dtype')) { 124 | $dtype = $A->dtype(); 125 | } else { 126 | $dtype = $this->defaultFloatType; 127 | } 128 | 129 | $transposed = false; 130 | $A = new NDArrayPhp($A,$dtype,[$m,$n],$offsetA,service:$this->service); 131 | $offsetV = $V = null; 132 | //if($m<$n) { 133 | // throw new InvalidArgumentException('unsuppoted shape by PhpLapack. M must be greater than N or equal.'); 134 | //} 135 | if($m<$n) { 136 | $transposed = true; 137 | $A = $this->transpose($A); 138 | [$m,$n] = [$n,$m]; 139 | [$ldU,$ldVT] = [$ldVT,$ldU]; 140 | [$U,$V] = [$V,$U]; 141 | [$offsetU,$offsetV] = [$offsetV,$offsetU]; 142 | } 143 | 144 | $U = new NDArrayPhp($U,$dtype,[$m,$ldU],$offsetU,service:$this->service); 145 | $V = new NDArrayPhp($V,$dtype,[$n,$ldVT],$offsetV,service:$this->service); 146 | 147 | $min_num = min($m,$n); 148 | for($i=0;$i<$m;$i++) { 149 | $this->copy($A[$i][R(0,(int)($min_num))], 150 | $U[$i][R(0,(int)($min_num))]); 151 | } 152 | for($i=0;$i<$min_num;$i++) { 153 | $this->copy($A[$i][R(0,(int)($n))], 154 | $V[$i][R(0,(int)($n))]); 155 | } 156 | 157 | #echo "--- a ---\n"; 158 | #$this->print($A); 159 | #echo "--- u ---\n"; 160 | #$this->print($U); 161 | #echo "--- v ---\n"; 162 | #$this->print($V); 163 | 164 | $eps = 2.22045e-016; 165 | 166 | // Decompose Phase 167 | 168 | // Householder reduction to bidiagonal form. 169 | $g = $scale = $anorm = 0.0; 170 | $l = 0; 171 | $rv1 = []; 172 | $W = []; 173 | for($i = 0; $i < $ldU; $i++){ 174 | $l = $i + 2; 175 | $rv1[$i] = $scale * $g; 176 | $g = $s = $scale = 0.0; 177 | if($i < $m){ 178 | for($k = $i; $k < $m; $k++) 179 | $scale += abs($U[$k][$i]); 180 | if($scale != 0.0) { 181 | for($k = $i; $k < $m; $k++) { 182 | $U[$k][$i] = $U[$k][$i] / $scale; 183 | $s += $U[$k][$i] * $U[$k][$i]; 184 | } 185 | $f = $U[$i][$i]; 186 | $g = - $this->sameSign(sqrt($s), $f); 187 | $h = $f * $g - $s; 188 | $U[$i][$i] = $f - $g; 189 | for($j = $l - 1; $j < $ldU; $j++){ 190 | for($s = 0.0, $k = $i; $k < $m; $k++) 191 | $s += $U[$k][$i] * $U[$k][$j]; 192 | $f = $s / $h; 193 | for($k = $i; $k < $m; $k++) 194 | $U[$k][$j] = $U[$k][$j] + $f * $U[$k][$i]; 195 | } 196 | for($k = $i; $k < $m; $k++) 197 | $U[$k][$i] = $U[$k][$i] * $scale; 198 | } 199 | } 200 | $W[$i] = $scale * $g; 201 | $g = $s = $scale = 0.0; 202 | if($i + 1 <= $m && $i + 1 != $ldU){ 203 | for ($k= $l - 1; $k < $ldU; $k++) 204 | $scale += abs($U[$i][$k]); 205 | if($scale != 0.0){ 206 | for ($k= $l - 1; $k < $ldU; $k++){ 207 | $U[$i][$k] = $U[$i][$k] / $scale; 208 | $s += $U[$i][$k] * $U[$i][$k]; 209 | } 210 | $f = $U[$i][$l - 1]; 211 | $g = - $this->sameSign(sqrt($s), $f); 212 | $h = $f * $g - $s; 213 | $U[$i][$l - 1] = $f - $g; 214 | for($k = $l - 1; $k < $ldU; $k++) 215 | $rv1[$k] = $U[$i][$k] / $h; 216 | for($j = $l - 1; $j < $m; $j++){ 217 | for($s = 0.0, $k = $l - 1; $k < $ldU; $k++) 218 | $s += $U[$j][$k] * $U[$i][$k]; 219 | for($k = $l - 1; $k < $ldU; $k++) 220 | $U[$j][$k] = $U[$j][$k] + $s * $rv1[$k]; 221 | } 222 | for($k= $l - 1; $k < $ldU; $k++) $U[$i][$k] *= $scale; 223 | } 224 | } 225 | $anorm = max($anorm, (abs($W[$i]) + abs($rv1[$i]))); 226 | } 227 | 228 | // Accumulation of right-hand transformations. 229 | for($i = $n - 1; $i >= 0; $i--){ 230 | if($i < $n - 1){ 231 | if($g != 0.0){ 232 | for($j = $l; $j < $n; $j++) // Double division to avoid possible underflow. 233 | $V[$j][$i] = ($U[$i][$j] / $U[$i][$l]) / $g; 234 | for($j = $l; $j < $n; $j++){ 235 | for($s = 0.0, $k = $l; $k < $n; $k++) 236 | $s += ($U[$i][$k] * $V[$k][$j]); 237 | for($k = $l; $k < $n; $k++) 238 | $V[$k][$j] = $V[$k][$j] + $s * $V[$k][$i]; 239 | } 240 | } 241 | for($j = $l; $j < $n; $j++) 242 | $V[$i][$j] = $V[$j][$i] = 0.0; 243 | } 244 | $V[$i][$i] = 1.0; 245 | $g = $rv1[$i]; 246 | $l = $i; 247 | } 248 | 249 | // Accumulation of left-hand transformations. 250 | for($i = min($m, $ldU) - 1; $i >= 0; $i--){ 251 | $l = $i + 1; 252 | $g = $W[$i]; 253 | for($j = $l; $j < $ldU; $j++) 254 | $U[$i][$j] = 0.0; 255 | if($g != 0.0){ 256 | $g = 1.0 / $g; 257 | for($j = $l; $j < $ldU; $j++){ 258 | for($s = 0.0, $k = $l; $k < $m; $k++) 259 | $s += $U[$k][$i] * $U[$k][$j]; 260 | $f = ($s / $U[$i][$i]) * $g; 261 | for($k = $i; $k < $m; $k++) 262 | $U[$k][$j] = $U[$k][$j] + $f * $U[$k][$i]; 263 | } 264 | for($j = $i; $j < $m; $j++) 265 | $U[$j][$i] = $U[$j][$i] * $g; 266 | }else { 267 | for($j = $i; $j < $m; $j++) 268 | $U[$j][$i] = 0.0; 269 | } 270 | $U[$i][$i] = $U[$i][$i]+1; 271 | } 272 | 273 | // Diagonalization of the bidiagonal form 274 | // Loop over singular values, and over allowed iterations. 275 | for($k = $n - 1; $k >= 0; $k--){ 276 | for($its = 0; $its < 30; $its++){ 277 | $flag = true; 278 | for($l = $k; $l >= 0; $l--){ 279 | $nm = $l - 1; 280 | if( $l == 0 || abs($rv1[$l]) <= $eps*$anorm){ 281 | $flag = false; 282 | break; 283 | } 284 | if(abs($W[$nm]) <= $eps*$anorm) break; 285 | } 286 | if($flag){ 287 | $c = 0.0; // Cancellation of rv1[l], if l > 0. 288 | $s = 1.0; 289 | for($i = $l; $i < $k + 1; $i++){ 290 | $f = $s * $rv1[$i]; 291 | $rv1[$i] = $c * $rv1[$i]; 292 | if(abs($f) <= $eps*$anorm) break; 293 | $g = $W[$i]; 294 | $h = $this->pythag($f,$g); 295 | $W[$i] = $h; 296 | $h = 1.0 / $h; 297 | $c = $g * $h; 298 | $s = -$f * $h; 299 | for($j = 0; $j < $m; $j++){ 300 | $y = $U[$j][$nm]; 301 | $z = $U[$j][$i]; 302 | $U[$j][$nm] = $y * $c + $z * $s; 303 | $U[$j][$i] = $z * $c - $y * $s; 304 | } 305 | } 306 | } 307 | $z = $W[$k]; 308 | if($l == $k){ 309 | if($z < 0.0){ 310 | $W[$k] = -$z; // Singular value is made nonnegative. 311 | for($j = 0; $j < $n; $j++) 312 | $V[$j][$k] = -$V[$j][$k]; 313 | } 314 | break; 315 | } 316 | if($its == 29) print("no convergence in 30 svd iterations"); 317 | $x = $W[$l]; // Shift from bottom 2-by-2 minor. 318 | $nm = $k - 1; 319 | $y = $W[$nm]; 320 | $g = $rv1[$nm]; 321 | $h = $rv1[$k]; 322 | $f = (($y - $z) * ($y + $z) + ($g - $h) * ($g + $h)) / (2.0 * $h * $y); 323 | $g = $this->pythag($f,1.0); 324 | $f = (($x - $z) * ($x + $z) + $h * (($y / ($f + $this->sameSign($g,$f))) - $h)) / $x; 325 | $c = $s = 1.0; 326 | for($j = $l; $j <= $nm; $j++){ 327 | $i = $j + 1; 328 | $g = $rv1[$i]; 329 | $y = $W[$i]; 330 | $h = $s * $g; 331 | $g = $c * $g; 332 | $z = $this->pythag($f,$h); 333 | $rv1[$j] = $z; 334 | $c = $f / $z; 335 | $s = $h / $z; 336 | $f = $x * $c + $g * $s; 337 | $g = $g * $c - $x * $s; 338 | $h = $y * $s; 339 | $y *= $c; 340 | for($jj = 0; $jj < $n; $jj++){ 341 | $x = $V[$jj][$j]; 342 | $z = $V[$jj][$i]; 343 | $V[$jj][$j] = $x * $c + $z * $s; 344 | $V[$jj][$i] = $z * $c - $x * $s; 345 | } 346 | $z = $this->pythag($f,$h); 347 | $W[$j] = $z; // Rotation can be arbitrary if z = 0. 348 | if($z){ 349 | $z = 1.0 / $z; 350 | $c = $f * $z; 351 | $s = $h * $z; 352 | } 353 | $f = $c * $g + $s * $y; 354 | $x = $c * $y - $s * $g; 355 | for($jj = 0; $jj < $m; $jj++){ 356 | $y = $U[$jj][$j]; 357 | $z = $U[$jj][$i]; 358 | $U[$jj][$j] = $y * $c + $z * $s; 359 | $U[$jj][$i] = $z * $c - $y * $s; 360 | } 361 | } 362 | $rv1[$l] = 0.0; 363 | $rv1[$k] = $f; 364 | $W[$k] = $x; 365 | } 366 | } 367 | 368 | // Reorder Phase 369 | // Sort. The method is Shell's sort. 370 | // (The work is negligible as compared to that already done in decompose phase.) 371 | $inc = 1; 372 | do { 373 | $inc = (int)($inc * 3); 374 | $inc++; 375 | } while($inc <= $n); 376 | 377 | do { 378 | $inc = (int)($inc / 3); 379 | for($i = $inc; $i < $n; $i++){ 380 | $sw = $W[$i]; 381 | $su = []; 382 | $sv = []; 383 | for($k = 0; $k < $m; $k++) $su[$k] = $U[$k][$i]; 384 | for($k = 0; $k < $n; $k++) $sv[$k] = $V[$k][$i]; 385 | $j = $i; 386 | while($W[$j - $inc] < $sw){ 387 | $W[$j] = $W[$j - $inc]; 388 | for($k = 0; $k < $m; $k++) $U[$k][$j] = $U[$k][$j - $inc]; 389 | for($k = 0; $k < $n; $k++) $V[$k][$j] = $V[$k][$j - $inc]; 390 | $j -= $inc; 391 | if($j < $inc) break; 392 | } 393 | $W[$j] = $sw; 394 | for($k = 0; $k < $m; $k++) $U[$k][$j] = $su[$k]; 395 | for($k = 0; $k < $n; $k++) $V[$k][$j] = $sv[$k]; 396 | } 397 | } while($inc > 1); 398 | 399 | #for($k = 0; $k < $n; $k++){ 400 | # $s = 0; 401 | # for($i = 0; $i < $m; $i++) if ($U[$i][$k] < 0.0) $s++; 402 | # for($j = 0; $j < $n; $j++) if ($V[$j][$k] < 0.0) $s++; 403 | # if($s > ($m + $n)/2) { 404 | # for($i = 0; $i < $m; $i++) $U[$i][$k] = - $U[$i][$k]; 405 | # for($j = 0; $j < $n; $j++) $V[$j][$k] = - $V[$j][$k]; 406 | # } 407 | #} 408 | 409 | // calculate the rank 410 | $rank = 0; 411 | for($i = 0; $i < count($W); $i++){ 412 | if(round($W[$i], 4) > 0){ 413 | $rank += 1; 414 | } 415 | } 416 | 417 | // Low-Rank Approximation 418 | $q = 0.9; 419 | $k = 0; 420 | $frobA = $frobAk = 0; 421 | for($i = 0; $i < $rank; $i++) $frobA += $W[$i]; 422 | do{ 423 | for($i = 0; $i <= $k; $i++) $frobAk += $W[$i]; 424 | $clt = $frobAk / $frobA; 425 | $k++; 426 | } while($clt < $q); 427 | 428 | // prepare S matrix as n*n daigonal matrix of singular values 429 | for($i = 0; $i < $n; $i++){ 430 | $S[$i] = $W[$i]; 431 | } 432 | 433 | if($transposed) { 434 | $UT = $this->transpose($V); 435 | $this->copy($UT,$V); 436 | $VT = new NDArrayPhp($VT,$dtype,[$ldU,$m],$offsetVT,service:$this->service); 437 | $this->copy($U,$VT); 438 | } else { 439 | $VT = new NDArrayPhp($VT,$dtype,[$ldVT,$n],$offsetVT,service:$this->service); 440 | for($i=0;$i<$ldVT;$i++) { 441 | for($j=0;$j<$n;$j++) { 442 | $VT[$i][$j] = $V[$j][$i]; 443 | } 444 | } 445 | } 446 | //$matrices['U'] = $U; 447 | //$matrices['S'] = $S; 448 | //$matrices['W'] = $W; 449 | //$matrices['V'] = $this->matrixTranspose($V); 450 | //$matrices['Rank'] = $rank; 451 | //$matrices['K'] = $k; 452 | 453 | #return [$U,$S,$this->transpose($V)]; 454 | } 455 | 456 | //private function print(NDArray $a) : void 457 | //{ 458 | // foreach($a->toArray() as $array) { 459 | // echo '['.implode(',',array_map(function($a){return sprintf('%5.2f',$a);},$array))."],\n"; 460 | // } 461 | //} 462 | 463 | /** 464 | * @param array $shape 465 | */ 466 | private function alloc(array $shape, int $dtype) : NDArray 467 | { 468 | return new NDArrayPhp(null,$dtype,$shape,service:$this->service); 469 | } 470 | 471 | //private function zeros(NDArray $X) : void 472 | //{ 473 | // $N = $X->size(); 474 | // $XX = $X->buffer(); 475 | // $offX = $X->offset(); 476 | // $posX = $offX; 477 | // for($i=0;$i<$N;$i++) { 478 | // $XX[$posX] = 0; 479 | // $posX++; 480 | // } 481 | //} 482 | 483 | private function copy(NDArray $X, NDArray $Y) : void 484 | { 485 | if($X->shape()!=$Y->shape()) { 486 | throw new InvalidArgumentException('unmatch shape:'); 487 | } 488 | $N = $X->size(); 489 | $XX = $X->buffer(); 490 | $offX = $X->offset(); 491 | $YY = $Y->buffer(); 492 | $offY = $Y->offset(); 493 | $posX = $offX; 494 | $posY = $offY; 495 | for($i=0;$i<$N;$i++) { 496 | $YY[$posY] = $XX[$posX]; 497 | $posX++; 498 | $posY++; 499 | } 500 | } 501 | 502 | private function sameSign(float $a, float $b) : float 503 | { 504 | if($b >= 0){ 505 | $result = abs($a); 506 | } else { 507 | $result = - abs($a); 508 | } 509 | return $result; 510 | } 511 | 512 | private function pythag(float $a, float $b) : float 513 | { 514 | $absa = abs($a); 515 | $absb = abs($b); 516 | 517 | if( $absa > $absb ){ 518 | return $absa * sqrt( 1.0 + pow( $absb / $absa , 2) ); 519 | } else { 520 | if( $absb > 0.0 ){ 521 | return $absb * sqrt( 1.0 + pow( $absa / $absb, 2 ) ); 522 | } else { 523 | return 0.0; 524 | } 525 | } 526 | } 527 | 528 | /** 529 | * copied from MatrixOperator 530 | */ 531 | public function transpose(NDArray $X) : NDArray 532 | { 533 | $shape = $X->shape(); 534 | $newShape = array_reverse($shape); 535 | $Y = $this->alloc($newShape, $X->dtype()); 536 | $w = 1; 537 | $posY = 0; 538 | $posX = 0; 539 | $this->_transpose($newShape, $w, $X->buffer(), $X->offset(), $posX, $Y->buffer(), $posY); 540 | return $Y; 541 | } 542 | 543 | /** 544 | * @param array $shape 545 | */ 546 | protected function _transpose( 547 | array $shape, int $w, 548 | Buffer $bufX, int $offX, int $posX, 549 | Buffer $bufY, int &$posY) : void 550 | { 551 | $n=array_shift($shape); 552 | $W = $w*$n; 553 | $deps = count($shape); 554 | for($i=0;$i<$n;$i++) { 555 | if($deps) { 556 | $this->_transpose($shape, $W, $bufX, $offX, $posX+$w*$i, $bufY, $posY); 557 | } else { 558 | $bufY[$posY] = $bufX[$offX + $posX+$w*$i]; 559 | $posY++; 560 | } 561 | } 562 | } 563 | } 564 | -------------------------------------------------------------------------------- /src/NDArrayPhp.php: -------------------------------------------------------------------------------- 1 | 23 | */ 24 | class NDArrayPhp implements NDArray, Countable, Serializable, IteratorAggregate 25 | { 26 | use ComplexUtils; 27 | 28 | const RANGE_STYLE_DEFAULT = 0; 29 | const RANGE_STYLE_1 = 1; 30 | const RANGE_STYLE_FORCE2 = 2; 31 | public static int $rangeStyle = self::RANGE_STYLE_DEFAULT; 32 | 33 | const SERIALIZE_NDARRAY_KEYWORD = 'NDArray:'; 34 | const SERIALIZE_OLDSTYLE_KEYWORD = 'O:29:"Rindow\Math\Matrix\NDArrayPhp"'; 35 | public static int $unserializeWarning = 2; 36 | 37 | /** @var array $_shape */ 38 | protected array $_shape; 39 | protected BufferInterface $_buffer; 40 | protected int $_offset; 41 | protected int $_dtype; 42 | protected bool $_portableSerializeMode = false; 43 | protected ?Service $service = null; 44 | 45 | /** 46 | * @param array $shape 47 | */ 48 | final public function __construct( 49 | mixed $array = null, 50 | ?int $dtype=null, 51 | ?array $shape = null, 52 | ?int $offset=null, 53 | ?Service $service=null, 54 | ) { 55 | if($service===null) { 56 | throw new InvalidArgumentException("No service specified."); 57 | } 58 | $this->service = $service; 59 | if($array===null && $dtype===null && $shape===null && $offset===null) { 60 | // Empty definition for Unserialize 61 | return; 62 | } 63 | 64 | $orgDtype = $dtype; 65 | if($dtype===null) { 66 | $dtype = NDArray::float32; 67 | } 68 | 69 | if($array===null && $shape!==null) { 70 | $this->assertShape($shape); 71 | $size = (int)array_product($shape); 72 | $this->_buffer = $this->newBuffer($size, $dtype); 73 | $this->_offset = 0; 74 | } elseif($array instanceof BufferInterface) { 75 | if($offset===null) { 76 | throw new InvalidArgumentException("Must specify offset with the buffer"); 77 | } 78 | if($shape===null) { 79 | throw new InvalidArgumentException("Invalid dimension size"); 80 | } 81 | $this->_buffer = $array; 82 | $this->_offset = $offset; 83 | } elseif(is_array($array)||$array instanceof ArrayObject) { 84 | $dummyBuffer = new ArrayObject(); 85 | $idx = 0; 86 | $this->array2Flat($array, $dummyBuffer, $idx, $prepare=true); 87 | $this->_buffer = $this->newBuffer($idx, $dtype); 88 | $idx = 0; 89 | $this->array2Flat($array, $this->_buffer, $idx, $prepare=false); 90 | $this->_offset = 0; 91 | if($shape===null) { 92 | $shape = $this->genShape($array); 93 | } 94 | } elseif(is_numeric($array)||is_bool($array)||$this->isComplexObject($array)) { 95 | if(is_numeric($array)) { 96 | if($orgDtype==null) { 97 | $dtype = NDArray::float32; 98 | } 99 | } elseif(is_bool($array)) { 100 | if($orgDtype==null) { 101 | $dtype = NDArray::bool; 102 | } else { 103 | if($dtype!=NDArray::bool) { 104 | throw new InvalidArgumentException("unmatch dtype with bool value"); 105 | } 106 | } 107 | } elseif($this->isComplexObject($array)) { 108 | if($orgDtype==null) { 109 | $dtype = NDArray::complex64; 110 | } else { 111 | if(!$this->isComplex($dtype)) { 112 | throw new InvalidArgumentException("unmatch dtype with complex value"); 113 | } 114 | } 115 | } 116 | $this->_buffer = $this->newBuffer(1, $dtype); 117 | $this->_buffer[0] = $array; 118 | $this->_offset = 0; 119 | if($shape===null) { 120 | $shape = []; 121 | } 122 | $this->assertShape($shape); 123 | if(array_product($shape)!=1) { 124 | throw new InvalidArgumentException("Invalid dimension size"); 125 | } 126 | } else { 127 | throw new InvalidArgumentException("Invalid type of array"); 128 | } 129 | $this->assertShape($shape); 130 | $this->_shape = $shape; 131 | 132 | $size = (int)array_product($shape); 133 | if(count($this->_buffer) - $this->_offset < $size) { 134 | throw new InvalidArgumentException("Invalid dimension size"); 135 | } 136 | 137 | $this->_dtype = $dtype; 138 | } 139 | 140 | public function service() : Service 141 | { 142 | if($this->service==null) { 143 | throw new RuntimeException('service is unavailable'); 144 | } 145 | return $this->service; 146 | } 147 | 148 | protected function newBuffer(int $size, int $dtype) : BufferInterface 149 | { 150 | return $this->service()->buffer()->Buffer($size, $dtype); 151 | } 152 | 153 | //protected function isBuffer(mixed $buffer) : bool 154 | //{ 155 | // if($buffer instanceof BufferInterface) { 156 | // return true; 157 | // } else { 158 | // return false; 159 | // } 160 | //} 161 | 162 | protected function isComplex(?int $dtype=null) : bool 163 | { 164 | $dtype = $dtype ?? $this->_dtype; 165 | return $this->cistype($dtype); 166 | } 167 | 168 | public function isComplexObject(mixed $value) : bool 169 | { 170 | return $this->cisObject($value); 171 | } 172 | 173 | /** 174 | * @param array $shape 175 | */ 176 | protected function assertShape(array $shape) : void 177 | { 178 | foreach($shape as $num) { 179 | if(!is_int($num)) { 180 | throw new InvalidArgumentException( 181 | "Invalid shape numbers. It gives ".gettype($num) 182 | ); 183 | } 184 | if($num<=0) { 185 | throw new InvalidArgumentException( 186 | "Invalid shape numbers. It gives ".$num 187 | ); 188 | } 189 | } 190 | } 191 | 192 | /** 193 | * @param array $A 194 | * @param BufferInterface|ArrayObject $F 195 | */ 196 | protected function array2Flat(iterable $A, BufferInterface|ArrayObject $F, int &$idx, bool $prepare) : int 197 | { 198 | if(is_array($A)) { 199 | ksort($A); 200 | } elseif($A instanceof ArrayObject) { 201 | $A->ksort(); 202 | } 203 | 204 | $num = null; 205 | foreach ($A as $key => $value) { 206 | if(!is_int($key)) { 207 | throw new InvalidArgumentException("Dimension must be integer"); 208 | } 209 | if(is_array($value)||$value instanceof ArrayObject) { 210 | $num2 = $this->array2Flat($value, $F, $idx, $prepare); 211 | if($num===null) { 212 | $num = $num2; 213 | } else { 214 | if($num!=$num2) { 215 | throw new InvalidArgumentException("The shape of the dimension is broken"); 216 | } 217 | } 218 | } else { 219 | if($num!==null) { 220 | throw new InvalidArgumentException("The shape of the dimension is broken"); 221 | } 222 | if(!$prepare) { 223 | $F[$idx] = $value; 224 | } 225 | $idx++; 226 | } 227 | } 228 | return count($A); 229 | } 230 | 231 | /** 232 | * @param array $shape 233 | * @return array 234 | */ 235 | protected function flat2Array(BufferInterface $F, int &$idx, array $shape) : array 236 | { 237 | $size = array_shift($shape); 238 | if(count($shape)) { 239 | $A = []; 240 | for($i=0; $i<$size; $i++) { 241 | $A[$i] = $this->flat2Array($F, $idx, $shape); 242 | } 243 | } else { 244 | $A = []; 245 | for($i=0; $i<$size; $i++) { 246 | $value = $F[$idx]; 247 | if($this->isComplex()) { 248 | $value = new Complex($value->real, $value->imag); 249 | } 250 | $A[$i] = $value; 251 | $idx++; 252 | } 253 | } 254 | return $A; 255 | } 256 | 257 | /** 258 | * @return array 259 | */ 260 | protected function genShape(mixed $A) : array 261 | { 262 | $shape = []; 263 | while(is_array($A) || $A instanceof ArrayObject) { 264 | $shape[] = count($A); 265 | $A = $A[0]; 266 | } 267 | return $shape; 268 | } 269 | 270 | /** 271 | * @return array 272 | */ 273 | public function shape() : array 274 | { 275 | return $this->_shape; 276 | } 277 | 278 | public function ndim() : int 279 | { 280 | return count($this->_shape); 281 | } 282 | 283 | public function dtype() : int 284 | { 285 | return $this->_dtype; 286 | } 287 | 288 | public function buffer() : BufferInterface 289 | { 290 | return $this->_buffer; 291 | } 292 | 293 | public function offset() : int 294 | { 295 | return $this->_offset; 296 | } 297 | 298 | public function size() : int 299 | { 300 | return (int)array_product($this->_shape); 301 | } 302 | 303 | /** 304 | * @param array $shape 305 | */ 306 | public function reshape(array $shape) : NDArray 307 | { 308 | $this->assertShape($shape); 309 | if($this->size()!=array_product($shape)) { 310 | throw new InvalidArgumentException("Unmatch size to reshape: ". 311 | "[".implode(',', $this->shape())."]=>[".implode(',', $shape)."]"); 312 | } 313 | $newArray = new static($this->buffer(),$this->dtype(),$shape,$this->offset(),service:$this->service()); 314 | return $newArray; 315 | } 316 | 317 | public function toArray() : mixed 318 | { 319 | if(count($this->_shape)==0) { 320 | $value = $this->_buffer[$this->_offset]; 321 | if($this->isComplex()) { 322 | $value = new Complex($value->real, $value->imag); 323 | } 324 | return $value; 325 | } 326 | $idx = $this->_offset; 327 | return $this->flat2Array($this->_buffer, $idx, $this->_shape); 328 | } 329 | 330 | /** 331 | * @return int|array|Range 332 | */ 333 | protected function castOffset(mixed $offset) : int|array|Range 334 | { 335 | if(!is_int($offset)&&!is_array($offset)&&!($offset instanceof Range)) { 336 | throw new InvalidArgumentException("Array offsets must be integers or ranges."); 337 | } 338 | return $offset; 339 | } 340 | 341 | public function offsetExists($offset) : bool 342 | { 343 | $offset = $this->castOffset($offset); 344 | if(is_array($offset) && self::$rangeStyle==self::RANGE_STYLE_FORCE2) { 345 | throw new InvalidArgumentException("offset style is old range style."); 346 | } 347 | if(count($this->_shape)==0) { 348 | return false; 349 | } 350 | if(is_array($offset)) { 351 | if(count($offset)!=2 || 352 | !array_key_exists(0, $offset) || !array_key_exists(1, $offset) || 353 | $offset[0]>$offset[1]) { 354 | $det = ''; 355 | if(is_numeric($offset[0])&&is_numeric($offset[1])) { 356 | $det = ':['. implode(',', $offset).']'; 357 | } 358 | throw new OutOfRangeException("Illegal range specification.".$det); 359 | } 360 | $start = $offset[0]; 361 | $limit = $offset[1]; 362 | if(self::$rangeStyle==self::RANGE_STYLE_1) { 363 | ++$limit; 364 | } 365 | } elseif(is_int($offset)) { 366 | $start = $offset; 367 | $limit = $offset+1; 368 | } else { 369 | $start = $offset->start(); 370 | $limit = $offset->limit(); 371 | $delta = $offset->delta(); 372 | if($start>=$limit||$delta!=1) { 373 | $det = ":[$start,$limit".(($delta!=1)?",$delta":"").']'; 374 | throw new OutOfRangeException("Illegal range specification.".$det); 375 | } 376 | } 377 | if($start < 0 || $limit > $this->_shape[0]) { 378 | return false; 379 | } 380 | return true; 381 | } 382 | 383 | public function offsetGet($offset) : mixed 384 | { 385 | $offset = $this->castOffset($offset); 386 | if(!$this->offsetExists($offset)) { 387 | if(count($this->_shape)==0) { 388 | throw new OutOfRangeException("This object is scalar."); 389 | } else { 390 | if(is_array($offset)) { 391 | $string = '('.implode(',', $offset).')'; 392 | } elseif(is_object($offset)) { 393 | $string = '('.$offset->start().','.$offset->limit().')'; 394 | } else { 395 | $string = strval($offset); 396 | } 397 | throw new OutOfRangeException("Index is out of range. range allows (0,{$this->_shape[0]}): $string given."); 398 | } 399 | } 400 | 401 | // for single index specification 402 | if(is_numeric($offset)) { 403 | $offset = (int)$offset; 404 | $shape = $this->_shape; 405 | $max = array_shift($shape); 406 | if(count($shape)==0) { 407 | $value = $this->_buffer[$this->_offset+$offset]; 408 | if($this->isComplex()) { 409 | $value = new Complex($value->real, $value->imag); 410 | } 411 | return $value; 412 | } 413 | $size = (int)array_product($shape); 414 | $new = new static($this->_buffer,$this->_dtype,$shape,$this->_offset+$offset*$size,service:$this->service()); 415 | return $new; 416 | } 417 | 418 | // for range spesification 419 | $shape = $this->_shape; 420 | array_shift($shape); 421 | if(is_array($offset)) { 422 | $start = (int)$offset[0]; 423 | $limit = (int)$offset[1]; 424 | if(self::$rangeStyle==self::RANGE_STYLE_1) { 425 | ++$limit; 426 | } 427 | } else { 428 | $start = (int)$offset->start(); 429 | $limit = (int)$offset->limit(); 430 | if($offset->delta()!=1) { 431 | throw new OutOfRangeException("Illegal range specification.:delta=".$offset->delta()); 432 | } 433 | } 434 | $rowsCount = $limit-$start; 435 | if(count($shape)>0) { 436 | $itemSize = (int)array_product($shape); 437 | } else { 438 | $itemSize = 1; 439 | } 440 | if($rowsCount<0) { 441 | throw new OutOfRangeException('Invalid range'); 442 | } 443 | array_unshift($shape, $rowsCount); 444 | $size = (int)array_product($shape); 445 | $new = new static($this->_buffer,$this->_dtype, 446 | $shape,$this->_offset+$start*$itemSize,service:$this->service()); 447 | return $new; 448 | } 449 | 450 | public function offsetSet($offset, $value) : void 451 | { 452 | $offset = $this->castOffset($offset); 453 | if(!$this->offsetExists($offset)) { 454 | throw new OutOfRangeException("Index is out of range"); 455 | } 456 | // for range spesification 457 | if(!is_int($offset)) { 458 | throw new OutOfRangeException("Unsuppored to set for range specification."); 459 | } 460 | // for single index specification 461 | $shape = $this->_shape; 462 | $max = array_shift($shape); 463 | if(!count($shape)) { 464 | if($this->isComplex()) { 465 | if(!($value instanceof Complex)) { 466 | throw new InvalidArgumentException("Must be complex type"); 467 | } 468 | } else { 469 | if(!is_scalar($value)) { 470 | throw new InvalidArgumentException("Must be scalar type"); 471 | } 472 | } 473 | $this->_buffer[$this->_offset+$offset] = $value; 474 | return; 475 | } 476 | 477 | if(!($value instanceof self)||$value->shape()!=$shape) { 478 | throw new InvalidArgumentException("Unmatch shape numbers"); 479 | } 480 | $copy = $value->buffer(); 481 | $size = (int)array_product($shape); 482 | $src_idx = $value->offset(); 483 | $idx=$this->_offset+$offset*$size; 484 | for($i=0 ; $i<$size ; $i++,$idx++,$src_idx++) { 485 | $this->_buffer[$idx] = $copy[$src_idx]; 486 | } 487 | } 488 | 489 | public function offsetUnset($offset) : void 490 | { 491 | throw new LogicException("Unsuppored Operation"); 492 | } 493 | 494 | public function count() : int 495 | { 496 | if(count($this->_shape)==0) { 497 | return 0; 498 | } 499 | return $this->_shape[0]; 500 | } 501 | 502 | public function getIterator() : Traversable 503 | { 504 | if(count($this->_shape)==0) { 505 | return []; 506 | } 507 | $count = $this->_shape[0]; 508 | for($i=0;$i<$count;$i++) { 509 | yield $i => $this->offsetGet($i); 510 | } 511 | } 512 | 513 | /** 514 | * It is an old specification and has no effect. 515 | */ 516 | public function setPortableSerializeMode(bool $mode) : void 517 | { 518 | $this->_portableSerializeMode = $mode ? true : false; 519 | } 520 | 521 | /** 522 | * It is an old specification and has no effect. 523 | */ 524 | public function getPortableSerializeMode() : bool 525 | { 526 | return $this->_portableSerializeMode; 527 | } 528 | 529 | public function serialize() : string 530 | { 531 | return static::SERIALIZE_NDARRAY_KEYWORD.serialize($this->__serialize()); 532 | } 533 | 534 | public function unserialize(string $data) : void 535 | { 536 | if(strpos($data, static::SERIALIZE_NDARRAY_KEYWORD)===0) { 537 | $data = substr($data, strlen(static::SERIALIZE_NDARRAY_KEYWORD)); 538 | $data = unserialize($data); 539 | if(is_array($data)) { 540 | $this->__unserialize($data); 541 | return; 542 | } 543 | } elseif(strpos($data, static::SERIALIZE_OLDSTYLE_KEYWORD)===0) { 544 | $data = unserialize($data); 545 | } else { 546 | throw new RuntimeException("Invalid saved data."); 547 | } 548 | if(!($data instanceof self)) { 549 | throw new RuntimeException("Invalid saved data."); 550 | } 551 | if(self::$unserializeWarning>=1) { 552 | echo "Warning: NDarayPhp data is saved using old procedure. We recommend that you resave using the new procedure.\n"; 553 | } 554 | $buffer = $data->buffer(); 555 | if(get_class($data->service())!==get_class($this->service())) { 556 | $newBuffer = $this->service()->buffer()->Buffer($buffer->count(), $buffer->dtype()); 557 | if($data->service()->serviceLevel()>=Service::LV_ADVANCED && 558 | $this->service()->serviceLevel()>=Service::LV_ADVANCED) { 559 | $newBuffer->load($buffer->dump()); 560 | } else { 561 | $count = $buffer->count(); 562 | for($i=0;$i<$count;$i++) { 563 | $newBuffer[$i] = $buffer[$i]; 564 | } 565 | } 566 | $buffer = $newBuffer; 567 | } 568 | $this->__construct( 569 | $buffer, 570 | dtype:$data->dtype(), 571 | shape:$data->shape(), 572 | offset:$data->offset(), 573 | service:$this->service(), 574 | ); 575 | } 576 | 577 | public function __serialize() : array 578 | { 579 | $mode = 'machine'; 580 | $buffer = $this->_buffer->dump(); 581 | return [ 582 | 'm'=>$mode, 583 | 's'=>$this->_shape, 584 | 'o'=>$this->_offset, 585 | 't'=>$this->_dtype, 586 | 'z'=>count($this->_buffer), 587 | 'b'=>$buffer, 588 | ]; 589 | } 590 | 591 | /** 592 | * @param array $data 593 | */ 594 | public function __unserialize(array $data) : void 595 | { 596 | if($this->service===null) { 597 | if(self::$unserializeWarning>=2) { 598 | throw new RuntimeException("Please unserialize using the unserialize method."); 599 | } 600 | if(self::$unserializeWarning>=1) { 601 | echo "Warning: NDArrayPhp is not initialized. Please unserialize using the unserialize method.\n"; 602 | } 603 | $selector = new Selector(); 604 | $this->service = $selector->select(); 605 | } 606 | $mode = $data['m']; 607 | if(!is_array($data['s'])) { 608 | throw new RuntimeException("invalid shape."); 609 | } 610 | $this->_shape = $data['s']; 611 | if(!is_int($data['o'])) { 612 | throw new RuntimeException("invalid offset."); 613 | } 614 | $this->_offset = $data['o']; 615 | if(!is_int($data['t'])) { 616 | throw new RuntimeException("invalid dtype."); 617 | } 618 | $this->_dtype = $data['t']; 619 | if($mode=='machine' || $mode=='rindow_openblas') { 620 | //if($this->service()->serviceLevel()_buffer = $this->service()->buffer()->Buffer($data['z'], $data['t']); 624 | $this->_buffer->load($data['b']); 625 | } elseif($mode=='linear-array') { 626 | // Compatibility with older specifications 627 | $this->_buffer = $this->service()->buffer()->Buffer($data['z'], $data['t']); 628 | if(!is_array($data['b'])) { 629 | throw new RuntimeException("invalid buffer data."); 630 | } 631 | foreach($data['b'] as $key => $value) { 632 | $this->_buffer[$key] = $value; 633 | } 634 | } else { 635 | throw new RuntimeException('Illegal save mode: '.$mode); 636 | } 637 | } 638 | 639 | public function __clone() 640 | { 641 | if($this->service()->serviceLevel()>=Service::LV_ADVANCED) { 642 | $newBuffer = $this->service()->buffer()->Buffer( 643 | count($this->_buffer), 644 | $this->_buffer->dtype() 645 | ); 646 | $newBuffer->load($this->_buffer->dump()); 647 | $this->_buffer = $newBuffer; 648 | } elseif($this->service()->serviceLevel()>=Service::LV_BASIC) { 649 | $this->_buffer = clone $this->_buffer; 650 | } else { 651 | throw new RuntimeException('Unknown buffer type is uncloneable:'.get_class($this->_buffer)); 652 | } 653 | } 654 | } 655 | -------------------------------------------------------------------------------- /src/Drivers/MatlibCL/params/ScatterAddTimesMode2.php: -------------------------------------------------------------------------------- 1 |