Skip to content

Commit 5f3c34e

Browse files
committed
Add Matrix implementation, short README
1 parent 1202f57 commit 5f3c34e

File tree

3 files changed

+324
-3
lines changed

3 files changed

+324
-3
lines changed

README.md

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
Native matrix library
2+
-----------------
3+
4+
For a long time, PHP developers dreamed of being able to overload operators. And now, finally, this moment has come.
5+
Thanks to the PHP7.4 and the [lisachenko/z-engine](https://github.com/lisachenko/z-engine) package, we can overload the
6+
operators of comparison, addition, multiplication, casting and much more!
7+
8+
This library is first ever userland PHP extension, that implements operator overloading for the `Matrix` class.
9+
10+
[![GitHub release](https://img.shields.io/github/release/lisachenko/native-matrix.svg)](https://github.com/lisachenko/native-matrix/releases/latest)
11+
[![Minimum PHP Version](http://img.shields.io/badge/php-%3E%3D%207.4-8892BF.svg)](https://php.net/)
12+
[![License](https://img.shields.io/packagist/l/lisachenko/native-matrix.svg)](https://packagist.org/packages/lisachenko/native-matrix)
13+
14+
15+
Pre-requisites and initialization
16+
--------------
17+
18+
As this library depends on `FFI`, it requires PHP>=7.4 and `FFI` extension to be enabled. Also, current limitations of
19+
[lisachenko/z-engine](https://github.com/lisachenko/z-engine) are also applied (x64, NTS)
20+
21+
To install this library, simply add it via `composer`:
22+
```bash
23+
composer require lisachenko/native-types
24+
```
25+
26+
Now you can test it with following example:
27+
```php
28+
<?php
29+
declare(strict_types=1);
30+
31+
use Native\Type\Matrix;
32+
33+
$first = new Matrix([[10, 20, 30]]);
34+
$second = new Matrix([[2, 4, 6]]);
35+
$value = $first * 2 + $second; // Matrix([[22, 44, 66]])
36+
```
37+
38+
Supported features:
39+
- [x] Matrices addition (`$matrixA + $matrixB`)
40+
- [x] Matrices subtraction (`$matrixA - $matrixB`)
41+
- [x] Matrices multiplication (`$matrixA * $matrixB`)
42+
- [x] Matrices division (`$matrixA / $matrixB`)
43+
- [x] Matrices division (`$matrixA / $matrixB`)
44+
- [x] Matrices equality check (`$matrixA == $matrixB`)
45+
46+
For the future versions, I would like to implement native SSE/AVX assembly methods to improve the performance of calculation.
47+
48+

composer.json

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
2-
"name": "lisachenko/native-matrix",
3-
"description": "PHP Library that provides an implementation of userland Matrix, powered with overloaded operators",
2+
"name": "lisachenko/native-types",
3+
"description": "PHP Library that provides an implementation of userland types, powered with overloaded operators",
44
"type": "library",
55
"license": "MIT",
66
"authors": [
@@ -10,6 +10,12 @@
1010
}
1111
],
1212
"require": {
13-
"lisachenko/z-engine": "^0.6.0"
13+
"lisachenko/z-engine": "^0.6.0",
14+
"php": "~7.4"
15+
},
16+
"autoload": {
17+
"psr-4" : {
18+
"ZEngine\\Type\\" : "src/"
19+
}
1420
}
1521
}

src/Matrix.php

Lines changed: 267 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,267 @@
1+
<?php
2+
/**
3+
* Native matrix library
4+
*
5+
* @copyright Copyright 2019, Lisachenko Alexander <[email protected]>
6+
*
7+
* This source file is subject to the license that is bundled
8+
* with this source code in the file LICENSE.
9+
*/
10+
declare(strict_types = 1);
11+
12+
namespace Native\Type;
13+
14+
use InvalidArgumentException;
15+
use ZEngine\ClassExtension\ObjectCompareValuesInterface;
16+
use ZEngine\ClassExtension\ObjectCreateInterface;
17+
use ZEngine\ClassExtension\ObjectCreateTrait;
18+
use ZEngine\ClassExtension\ObjectDoOperationInterface;
19+
use ZEngine\System\OpCode;
20+
use function count, is_numeric;
21+
22+
/**
23+
* Simple class Matrix powered by custom operator handlers
24+
*/
25+
class Matrix implements ObjectCreateInterface, ObjectDoOperationInterface, ObjectCompareValuesInterface
26+
{
27+
use ObjectCreateTrait;
28+
29+
private array $matrix = [];
30+
private int $rows = 0;
31+
private int $columns = 0;
32+
33+
/**
34+
* Matrix constructor.
35+
*
36+
* @param array $matrix
37+
*/
38+
public function __construct(array $matrix)
39+
{
40+
$this->matrix = $matrix;
41+
$this->rows = count($matrix);
42+
$this->columns = count($matrix[0]);
43+
}
44+
45+
public function getRows(): int
46+
{
47+
return $this->rows;
48+
}
49+
50+
public function getColumns(): int
51+
{
52+
return $this->columns;
53+
}
54+
55+
public function isSquare(): bool
56+
{
57+
return $this->columns === $this->rows;
58+
}
59+
60+
/**
61+
* Performs multiplication of two matrices
62+
*
63+
* @param Matrix $multiplier
64+
*
65+
* @todo: Implement SSE/AVX support for multiplication
66+
*
67+
* @return $this Product of two matrices
68+
*/
69+
public function multiply(self $multiplier): self
70+
{
71+
if ($this->columns !== $multiplier->rows) {
72+
throw new InvalidArgumentException('Inconsistent matrix supplied');
73+
}
74+
75+
$totalColumns = $multiplier->columns;
76+
$result = [];
77+
foreach ($this->matrix as $row => $rowItems) {
78+
for ($column = 0; $column < $totalColumns; ++$column) {
79+
$columnItems = array_column($multiplier->matrix, $column);
80+
$cellValue = 0;
81+
foreach ($rowItems as $key => $value) {
82+
$cellValue += $value * $columnItems[$key];
83+
}
84+
85+
$result[$row][$column] = $cellValue;
86+
}
87+
}
88+
89+
return new static($result);
90+
}
91+
92+
/**
93+
* Performs division by scalar value
94+
*
95+
* @param int|float $value Divider
96+
*
97+
* @return $this
98+
*/
99+
public function divideByScalar($value): self
100+
{
101+
if (!is_numeric($value)) {
102+
throw new \InvalidArgumentException("Divide accepts only numeric values");
103+
}
104+
$result = [];
105+
for ($i = 0; $i < $this->rows; ++$i) {
106+
for ($j = 0; $j < $this->columns; ++$j) {
107+
$result[$i][$j] = $this->matrix[$i][$j] / $value;
108+
}
109+
}
110+
111+
return new static($result);
112+
}
113+
114+
/**
115+
* Performs multiplication by scalar value
116+
*
117+
* @param int|float $value Multiplier
118+
*
119+
* @return $this
120+
*/
121+
public function multiplyByScalar($value): self
122+
{
123+
if (!is_numeric($value)) {
124+
throw new \InvalidArgumentException("Multiply accepts only numeric values");
125+
}
126+
$result = [];
127+
for ($i = 0; $i < $this->rows; ++$i) {
128+
for ($j = 0; $j < $this->columns; ++$j) {
129+
$result[$i][$j] = $this->matrix[$i][$j] * $value;
130+
}
131+
}
132+
133+
return new static($result);
134+
}
135+
136+
/**
137+
* Performs addition of two matrices
138+
*
139+
* @param Matrix $value
140+
*
141+
* @return $this Sum of two matrices
142+
* @todo: Implement SSE/AVX support for addition
143+
*/
144+
public function sum(self $value): self
145+
{
146+
$result = [];
147+
for ($i = 0; $i < $this->rows; ++$i) {
148+
for ($k = 0; $k < $this->columns; ++$k) {
149+
$result[$i][$k] = $this->matrix[$i][$k] + $value->matrix[$i][$k];
150+
}
151+
}
152+
153+
return new static($result);
154+
}
155+
156+
/**
157+
* Performs subtraction of two matrices
158+
*
159+
* @param Matrix $value
160+
*
161+
* @todo: Implement SSE/AVX support for addition
162+
*
163+
* @return $this Subtraction of two matrices
164+
*/
165+
public function subtract(self $value): self
166+
{
167+
$result = [];
168+
for ($i = 0; $i < $this->rows; ++$i) {
169+
for ($k = 0; $k < $this->columns; ++$k) {
170+
$result[$i][$k] = $this->matrix[$i][$k] - $value->matrix[$i][$k];
171+
}
172+
}
173+
174+
return new static($result);
175+
}
176+
177+
/**
178+
* Checks if the given matrix equals to another one
179+
*
180+
* @param Matrix $another Another matrix
181+
*
182+
* @return bool
183+
*/
184+
public function equals(Matrix $another): bool
185+
{
186+
if ($another->rows !== $this->rows || $another->columns !== $this->columns) {
187+
return false;
188+
}
189+
for ($i = 0; $i < $this->rows; ++$i) {
190+
for ($k = 0; $k < $this->columns; ++$k) {
191+
$equals = $this->matrix[$i][$k] === $another->matrix[$i][$k];
192+
if (!$equals) {
193+
return false;
194+
}
195+
}
196+
}
197+
198+
return true;
199+
}
200+
201+
/**
202+
* Performs operation on given object
203+
*
204+
* @param int $opCode Operation code
205+
* @param Matrix|int|float $left left side of operation
206+
* @param Matrix|int|float $right Right side of operation
207+
*
208+
* @return Matrix Result of operation value
209+
*/
210+
public static function __doOperation(int $opCode, $left, $right): Matrix
211+
{
212+
$isLeftMatrix = $left instanceof Matrix;
213+
$isRightMatrix = $right instanceof Matrix;
214+
$isLeftNumeric = is_numeric($left);
215+
$isRightNumeric = is_numeric($right);
216+
217+
switch ($opCode) {
218+
case OpCode::ADD:
219+
if ($isLeftMatrix && $isRightMatrix) {
220+
return $left->sum($right);
221+
}
222+
break;
223+
case OpCode::SUB:
224+
if ($isLeftMatrix && $isRightMatrix) {
225+
return $left->subtract($right);
226+
}
227+
break;
228+
case OpCode::MUL:
229+
if ($isLeftMatrix && $isRightMatrix) {
230+
return $left->multiply($right);
231+
} elseif ($isLeftMatrix && $isRightNumeric) {
232+
return $left->multiplyByScalar($right);
233+
} elseif ($isLeftNumeric && $isRightMatrix) {
234+
return $right->multiplyByScalar($left);
235+
}
236+
break;
237+
case OpCode::DIV:
238+
if ($isLeftMatrix && $isRightNumeric) {
239+
return $left->divideByScalar($right);
240+
}
241+
break;
242+
}
243+
244+
throw new \LogicException('Unsupported ' . OpCode::name($opCode). ' operation or invalid arguments');
245+
}
246+
247+
/**
248+
* Performs comparison of given object with another value
249+
*
250+
* @param mixed $one First side of operation
251+
* @param mixed $another Another side of operation
252+
*
253+
* @return int Result of comparison: 1 is greater, -1 is less, 0 is equal
254+
*/
255+
public static function __compare($one, $another): int
256+
{
257+
if (!($one instanceof Matrix) || !($another instanceof Matrix)) {
258+
throw new \InvalidArgumentException('Matrix can be compared only with another matrix');
259+
}
260+
261+
if ($one->equals($another)) {
262+
return 0;
263+
}
264+
265+
return -2;
266+
}
267+
}

0 commit comments

Comments
 (0)