diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 87bfd1a..233d50a 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -6,35 +6,69 @@ on: - main jobs: - no_ext_tests: - runs-on: ubuntu-22.04 + tests: + runs-on: ${{ matrix.os }} strategy: fail-fast: true matrix: - php: ['8.1','8.2','8.3'] - + include: + - name: macOS + os: macos-latest + php: '8.3' + - name: PHP8.1 + os: ubuntu-latest + php: '8.1' + - name: PHP8.2 + os: ubuntu-latest + php: '8.2' + - name: PHP8.3 + os: ubuntu-latest + php: '8.3' + - name: Windows + os: windows-latest + php: '8.3' + steps: + - name: Setup PHP ${{ matrix.php }} + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + # PHP Extras + coverage: none + tools: composer, phpunit:10.5, phpstan + #ini-values: "memory_limit=512M" + extensions: ffi + - name: Checkout codes uses: "actions/checkout@v4" + #- name: Composer + # uses: php-actions/composer@v6 + # with: + # php_version: ${{ matrix.php }} + # php_extensions: ffi + - name: Composer - uses: php-actions/composer@v6 - with: - php_version: ${{ matrix.php }} - php_extensions: ffi + run: composer update + + #- name: PHP Static Analysys + # uses: php-actions/phpstan@v3 + # with: + # php_version: ${{ matrix.php }} + # path: src/ - name: PHP Static Analysys - uses: php-actions/phpstan@v3 - with: - php_version: ${{ matrix.php }} - path: src/ + run: phpstan + + #- name: PHPUnit Tests + # uses: php-actions/phpunit@v3 + # with: + # configuration: tests/phpunit.xml + # version: 10.5 + # php_version: ${{ matrix.php }} + # php_extensions: ffi - name: PHPUnit Tests - uses: php-actions/phpunit@v3 - with: - configuration: tests/phpunit.xml - version: 10.5 - php_version: ${{ matrix.php }} - php_extensions: ffi + run: phpunit -c tests diff --git a/README.md b/README.md index c2aca1e..fe4401e 100644 --- a/README.md +++ b/README.md @@ -21,8 +21,9 @@ Requirements How to setup ============ + Install using composer. +```shell $ composer require rindow/rindow-math-buffer-ffi - - +``` diff --git a/src/Buffer.php b/src/Buffer.php index a2efee6..efbfdcc 100644 --- a/src/Buffer.php +++ b/src/Buffer.php @@ -7,6 +7,7 @@ use InvalidArgumentException; use OutOfRangeException; use LogicException; +use RuntimeException; use FFI; class complex_t { @@ -66,10 +67,18 @@ class Buffer implements LinearBuffer public function __construct(int $size, int $dtype) { - if(self::$ffi===null) { - $code = file_get_contents(__DIR__.'/buffer.h'); + if ($size <= 0) { + throw new InvalidArgumentException("Size must be positive"); + } + + if (self::$ffi === null) { + $code = @file_get_contents(__DIR__ . '/buffer.h'); + if ($code === false) { + throw new RuntimeException("Unable to read buffer.h file"); + } self::$ffi = FFI::cdef($code); } + if(!isset(self::$typeString[$dtype])) { throw new InvalidArgumentException("Invalid data type"); } @@ -103,7 +112,7 @@ protected function assertOffsetIsInt(string $method, mixed $offset) : void protected function isComplex(int $dtype=null) : bool { $dtype = $dtype ?? $this->dtype; - return $dtype==NDArray::complex64||$dtype==NDArray::complex128; + return $dtype === NDArray::complex64 || $dtype === NDArray::complex128; } public function dtype() : int @@ -186,4 +195,9 @@ public function load(string $string) : void } FFI::memcpy($this->data,$string,$byte); } + + public function __clone() + { + $this->data = clone $this->data; + } } diff --git a/src/BufferNoHeader.php b/src/BufferNoHeader.php new file mode 100644 index 0000000..4939791 --- /dev/null +++ b/src/BufferNoHeader.php @@ -0,0 +1,88 @@ +=$limitsize) { + throw new InvalidArgumentException("Data size is too large."); + } + $this->size = $size; + $this->dtype = $dtype; + switch($dtype) { + case NDArray::complex64: { + $declaration = parent::$typeString[NDArray::float32]; + $size *= 2; + break; + } + case NDArray::complex128: { + $declaration = parent::$typeString[NDArray::float64]; + $size *= 2; + break; + } + default: { + $declaration = parent::$typeString[$dtype]; + break; + } + } + $this->data = parent::$ffi->new("{$declaration}[{$size}]"); + } + + public function addr(int $offset) : FFI\CData + { + if($this->isComplex()) { + $offset *= 2; + } + return FFI::addr($this->data[$offset]); + } + + public function offsetGet(mixed $offset): mixed + { + $this->assertOffset('offsetGet',$offset); + if($this->isComplex()) { + $offset *= 2; + $real = $this->data[$offset]; + $imag = $this->data[$offset+1]; + $value = (object)['real'=>$real,'imag'=>$imag]; + } else { + $value = $this->data[$offset]; + } + if($this->dtype===NDArray::bool) { + $value = $value ? true : false; + } + return $value; + } + + public function offsetSet(mixed $offset, mixed $value): void + { + $this->assertOffset('offsetSet',$offset); + if($this->isComplex()) { + if(is_array($value)) { + [$real,$imag] = $value; + } elseif(is_object($value)) { + $real = $value->real; + $imag = $value->imag; + } else { + $type = gettype($value); + throw new InvalidArgumentException("Cannot convert to complex number.: ".$type); + } + $offset *= 2; + $this->data[$offset] = $real; + $this->data[$offset+1] = $imag; + } else { + $this->data[$offset] = $value; + } + } +} diff --git a/tests/RindowTest/Math/Matrix/Buffer/FFI/BufferTest.php b/tests/RindowTest/Math/Matrix/Buffer/FFI/BufferTest.php index de1b3f5..efef1ec 100644 --- a/tests/RindowTest/Math/Matrix/Buffer/FFI/BufferTest.php +++ b/tests/RindowTest/Math/Matrix/Buffer/FFI/BufferTest.php @@ -4,6 +4,8 @@ use PHPUnit\Framework\TestCase; use Interop\Polite\Math\Matrix\NDArray; use Rindow\Math\Buffer\FFI\Buffer; +use Rindow\Math\Buffer\FFI\BufferMacOS; +use Rindow\Math\Buffer\FFI\BufferFactory; use ArgumentCountError; use LogicException; use RuntimeException; @@ -13,6 +15,12 @@ class BufferTest extends TestCase { + protected object $factory; + public function setUp() : void + { + $this->factory = new BufferFactory(); + } + //public function testExtensionVersion() //{ // $this->assertEquals('0.1.7',phpversion('rindow_openblas')); @@ -20,7 +28,7 @@ class BufferTest extends TestCase public function testNormal() { - $buf = new Buffer(3,NDArray::float32); + $buf = $this->factory->Buffer(3,NDArray::float32); $buf[0] = 0.5; $buf[1] = 1.5; $buf[2] = 2.5; @@ -34,73 +42,73 @@ public function testNormal() public function testDtypesAndOffsetOfDtypes() { - $buf = new Buffer(3,NDArray::bool); + $buf = $this->factory->Buffer(3,NDArray::bool); $buf[2] = true; $this->assertEquals(NDArray::bool,$buf->dtype()); $this->assertTrue(is_bool($buf[0])); $this->assertEquals(true,$buf[2]); - $buf = new Buffer(3,NDArray::int8); + $buf = $this->factory->Buffer(3,NDArray::int8); $buf[2] = -1; $this->assertEquals(NDArray::int8,$buf->dtype()); $this->assertTrue(is_int($buf[0])); $this->assertEquals(-1,$buf[2]); - $buf = new Buffer(3,NDArray::uint8); + $buf = $this->factory->Buffer(3,NDArray::uint8); $buf[2] = -1; $this->assertEquals(NDArray::uint8,$buf->dtype()); $this->assertTrue(is_int($buf[0])); $this->assertEquals(255,$buf[2]); - $buf = new Buffer(3,NDArray::int16); + $buf = $this->factory->Buffer(3,NDArray::int16); $buf[2] = -1; $this->assertEquals(NDArray::int16,$buf->dtype()); $this->assertTrue(is_int($buf[0])); $this->assertEquals(-1,$buf[2]); - $buf = new Buffer(3,NDArray::uint16); + $buf = $this->factory->Buffer(3,NDArray::uint16); $buf[2] = -1; $this->assertEquals(NDArray::uint16,$buf->dtype()); $this->assertTrue(is_int($buf[0])); $this->assertEquals(65535,$buf[2]); - $buf = new Buffer(3,NDArray::int32); + $buf = $this->factory->Buffer(3,NDArray::int32); $buf[2] = -1; $this->assertEquals(NDArray::int32,$buf->dtype()); $this->assertTrue(is_int($buf[0])); $this->assertEquals(-1,$buf[2]); - $buf = new Buffer(3,NDArray::uint32); + $buf = $this->factory->Buffer(3,NDArray::uint32); $buf[2] = -1; $this->assertEquals(NDArray::uint32,$buf->dtype()); $this->assertTrue(is_int($buf[0])); $this->assertEquals(4294967295,$buf[2]); - $buf = new Buffer(3,NDArray::int64); + $buf = $this->factory->Buffer(3,NDArray::int64); $buf[2] = -1; $this->assertEquals(NDArray::int64,$buf->dtype()); $this->assertTrue(is_int($buf[0])); $this->assertEquals(-1,$buf[2]); - $buf = new Buffer(3,NDArray::uint64); + $buf = $this->factory->Buffer(3,NDArray::uint64); $buf[2] = -1; $this->assertEquals(NDArray::uint64,$buf->dtype()); $this->assertTrue(is_int($buf[0])); $this->assertEquals(-1,$buf[2]); // *** CAUTION **** - $buf = new Buffer(3,NDArray::float32); + $buf = $this->factory->Buffer(3,NDArray::float32); $buf[2] = 0.5; $this->assertEquals(NDArray::float32,$buf->dtype()); $this->assertTrue(is_float($buf[0])); $this->assertEquals(0.5,$buf[2]); - $buf = new Buffer(3,NDArray::float64); + $buf = $this->factory->Buffer(3,NDArray::float64); $buf[2] = 0.5; $this->assertEquals(NDArray::float64,$buf->dtype()); $this->assertTrue(is_float($buf[0])); $this->assertEquals(0.5,$buf[2]); - $buf = new Buffer(3,NDArray::complex64); + $buf = $this->factory->Buffer(3,NDArray::complex64); $this->assertEquals(NDArray::complex64,$buf->dtype()); $this->assertEquals(3,count($buf)); $this->assertEquals(8,$buf->value_size()); @@ -113,8 +121,8 @@ public function testDtypesAndOffsetOfDtypes() $vv = $buf[2]; $this->assertEquals(3.5,$vv->real); $this->assertEquals(4.5,$vv->imag); - - $buf = new Buffer(3,NDArray::complex128); + + $buf = $this->factory->Buffer(3,NDArray::complex128); $this->assertEquals(NDArray::complex128,$buf->dtype()); $this->assertEquals(3,count($buf)); $this->assertEquals(16,$buf->value_size()); @@ -131,7 +139,7 @@ public function testDtypesAndOffsetOfDtypes() public function testOffsetExists() { - $buf = new Buffer(3,NDArray::float32); + $buf = $this->factory->Buffer(3,NDArray::float32); $this->assertTrue(isset($buf[0])); $this->assertTrue(isset($buf[2])); $this->assertFalse(isset($buf[-1])); @@ -140,7 +148,7 @@ public function testOffsetExists() public function testUnset() { - $buf = new Buffer(3,NDArray::float32); + $buf = $this->factory->Buffer(3,NDArray::float32); $buf[0] = 1; $this->assertEquals(1,$buf[0]); $this->expectException(LogicException::class); @@ -151,12 +159,12 @@ public function testUnset() public function testDumpAndLoad() { - $buf = new Buffer(3,NDArray::float32); + $buf = $this->factory->Buffer(3,NDArray::float32); $buf[0] = 1; $buf[1] = 2; $buf[2] = 3; - $buf2 = new Buffer(3,NDArray::float32); + $buf2 = $this->factory->Buffer(3,NDArray::float32); $buf2[0] = 0; $buf2[1] = 0; $buf2[2] = 0; @@ -171,7 +179,7 @@ public function testDumpAndLoad() public function testSetOutOfBoundsWithHighOffset() { //$buf = new \SplFixedArray(3); - $buf = new Buffer(3,NDArray::float32); + $buf = $this->factory->Buffer(3,NDArray::float32); $this->expectException(OutOfRangeException::class); $this->expectExceptionMessage('Index invalid or out of range'); $buf[3] = 1; @@ -180,7 +188,7 @@ public function testSetOutOfBoundsWithHighOffset() public function testSetOutOfBoundsWithLowOffset() { //$buf = new \SplFixedArray(3); - $buf = new Buffer(3,NDArray::float32); + $buf = $this->factory->Buffer(3,NDArray::float32); $this->expectException(OutOfRangeException::class); $this->expectExceptionMessage('Index invalid or out of range'); $buf[-1] = 1; @@ -189,7 +197,7 @@ public function testSetOutOfBoundsWithLowOffset() public function testGetOutOfBoundsWithHighOffset() { //$buf = new \SplFixedArray(3); - $buf = new Buffer(3,NDArray::float32); + $buf = $this->factory->Buffer(3,NDArray::float32); $this->expectException(OutOfRangeException::class); $this->expectExceptionMessage('Index invalid or out of range'); $x = $buf[3]; @@ -198,7 +206,7 @@ public function testGetOutOfBoundsWithHighOffset() public function testGetOutOfBoundsWithLowOffset() { //$buf = new \SplFixedArray(3); - $buf = new Buffer(3,NDArray::float32); + $buf = $this->factory->Buffer(3,NDArray::float32); $this->expectException(OutOfRangeException::class); $this->expectExceptionMessage('Index invalid or out of range'); $x = $buf[-1]; @@ -207,7 +215,7 @@ public function testGetOutOfBoundsWithLowOffset() public function testUnsetOutOfBoundsWithHighOffset() { //$buf = new \SplFixedArray(3); - $buf = new Buffer(3,NDArray::float32); + $buf = $this->factory->Buffer(3,NDArray::float32); $this->expectException(LogicException::class); $this->expectExceptionMessage('Illigal Operation'); unset($buf[3]); @@ -217,7 +225,7 @@ public function testUnsetOutOfBoundsWithHighOffset() public function testUnsetOutOfBoundsWithLowOffset() { //$buf = new \SplFixedArray(3); - $buf = new Buffer(3,NDArray::float32); + $buf = $this->factory->Buffer(3,NDArray::float32); $this->expectException(LogicException::class); $this->expectExceptionMessage('Illigal Operation'); unset($buf[-1]); @@ -227,20 +235,20 @@ public function testUnsetOutOfBoundsWithLowOffset() public function testIsExistsOutOfBoundsWithHighOffset() { //$buf = new \SplFixedArray(3); - $buf = new Buffer(3,NDArray::float32); + $buf = $this->factory->Buffer(3,NDArray::float32); $this->assertFalse(isset($buf[3])); } public function testIsExistsOutOfBoundsWithLowOffset() { //$buf = new \SplFixedArray(3); - $buf = new Buffer(3,NDArray::float32); + $buf = $this->factory->Buffer(3,NDArray::float32); $this->assertFalse(isset($buf[-1])); } public function testOffsetSetWithNoOffset() { - $buf = new Buffer(3,NDArray::float32); + $buf = $this->factory->Buffer(3,NDArray::float32); $this->expectException(ArgumentCountError::class); //if(version_compare(PHP_VERSION, '8.0.0')<0) { // $this->expectExceptionMessage('offsetSet() expects exactly 2 parameters, 0 given'); @@ -253,7 +261,7 @@ public function testOffsetSetWithNoOffset() public function testOffsetSetIllegalTypeOffset() { - $buf = new Buffer(3,NDArray::float32); + $buf = $this->factory->Buffer(3,NDArray::float32); $this->expectException(TypeError::class); if(version_compare(PHP_VERSION, '8.0.0')<0) { $this->expectExceptionMessage('offsetSet() expects parameter 1 to be int'); @@ -265,7 +273,7 @@ public function testOffsetSetIllegalTypeOffset() public function testOffsetGetWithNoOffset() { - $buf = new Buffer(3,NDArray::float32); + $buf = $this->factory->Buffer(3,NDArray::float32); $this->expectException(ArgumentCountError::class); //if(version_compare(PHP_VERSION, '8.0.0')<0) { // $this->expectExceptionMessage('offsetGet() expects exactly 1 parameter, 0 given'); @@ -278,7 +286,7 @@ public function testOffsetGetWithNoOffset() public function testOffsetGetIllegalType() { - $buf = new Buffer(3,NDArray::float32); + $buf = $this->factory->Buffer(3,NDArray::float32); $this->expectException(TypeError::class); if(version_compare(PHP_VERSION, '8.0.0')<0) { $this->expectExceptionMessage('offsetGet() expects parameter 1 to be int'); @@ -290,7 +298,7 @@ public function testOffsetGetIllegalType() public function testOffsetUnsetWithNoOffset() { - $buf = new Buffer(3,NDArray::float32); + $buf = $this->factory->Buffer(3,NDArray::float32); $this->expectException(ArgumentCountError::class); //if(version_compare(PHP_VERSION, '8.0.0')<0) { // $this->expectExceptionMessage('offsetUnset() expects exactly 1 parameter, 0 given'); @@ -303,7 +311,7 @@ public function testOffsetUnsetWithNoOffset() public function testOffsetUnsetIllegalType() { - $buf = new Buffer(3,NDArray::float32); + $buf = $this->factory->Buffer(3,NDArray::float32); //$this->expectException(TypeError::class); //if(version_compare(PHP_VERSION, '8.0.0')<0) { // $this->expectExceptionMessage('offsetUnset() expects parameter 1 to be int'); @@ -317,7 +325,7 @@ public function testOffsetUnsetIllegalType() public function testLoadWithNoOffset() { - $buf = new Buffer(3,NDArray::float32); + $buf = $this->factory->Buffer(3,NDArray::float32); $this->expectException(ArgumentCountError::class); //if(version_compare(PHP_VERSION, '8.0.0')<0) { // $this->expectExceptionMessage('load() expects exactly 1 parameter, 0 given'); @@ -330,7 +338,7 @@ public function testLoadWithNoOffset() public function testLoadIllegalType() { - $buf = new Buffer(3,NDArray::float32); + $buf = $this->factory->Buffer(3,NDArray::float32); $this->expectException(TypeError::class); if(version_compare(PHP_VERSION, '8.0.0')<0) { $this->expectExceptionMessage('load() expects parameter 1 to be string'); @@ -350,23 +358,23 @@ public function testConstractWithNoArgument() // $this->expectExceptionMessage('__construct() expects exactly 2 arguments, 0 given'); //} $this->expectExceptionMessage('Too few arguments to function'); - $buf = new Buffer(); + $buf = $this->factory->Buffer(); } public function testConstractIllegalType() { $this->expectException(TypeError::class); if(version_compare(PHP_VERSION, '8.0.0')<0) { - $this->expectExceptionMessage('__construct() expects parameter 1 to be int'); + $this->expectExceptionMessage('BufferFactory::Buffer() expects parameter 1 to be int'); } else { - $this->expectExceptionMessage('__construct(): Argument #1 ($size) must be of type int'); + $this->expectExceptionMessage('BufferFactory::Buffer(): Argument #1 ($size) must be of type int'); } - $buf = new Buffer(new \stdClass(),NDArray::float32); + $buf = $this->factory->Buffer(new \stdClass(),NDArray::float32); } public function testAddr() { - $buf = new Buffer(4,NDArray::int32); + $buf = $this->factory->Buffer(4,NDArray::int32); $buf[0] = 10; $buf[1] = 11; $buf[2] = 12; @@ -377,4 +385,23 @@ public function testAddr() $this->assertEquals(12,$addr[1]); $this->assertEquals(13,$addr[2]); } + + public function testClone() + { + $buf = $this->factory->Buffer(4,NDArray::int32); + $buf[0] = 10; + $buf[1] = 11; + $buf[2] = 12; + $buf[3] = 13; + $clone = clone $buf; + $buf[0] = 0; + $buf[1] = 1; + $buf[2] = 2; + $buf[3] = 3; + + $this->assertEquals(10,$clone[0]); + $this->assertEquals(11,$clone[1]); + $this->assertEquals(12,$clone[2]); + $this->assertEquals(13,$clone[3]); + } }