Copy class Tensor {
private array $data;
private array $shape;
public function __construct ( array $data) {
if ( ! is_array ( $data ) ) {
// Convert single values to array format
$data = [$data];
}
$this -> validateData ( $data ) ;
$this -> data = $data;
$this -> shape = $this -> calculateShape ( $data ) ;
}
private function validateData ( array $data) : void {
if ( empty ( $data ) ) {
throw new InvalidArgumentException ( "Tensor cannot be empty" );
}
$this -> validateNestedArrays ( $data ) ;
}
private function validateNestedArrays ( array $arr , ?int $depth = null ) : void {
$firstLength = count ( $arr ) ;
foreach ($arr as $element) {
if ( is_array ( $element ) ) {
if ($depth === null ) {
$depth = count ( $element ) ;
} elseif ( count ( $element ) !== $depth) {
throw new InvalidArgumentException ( "Inconsistent dimensions in tensor" );
}
$this -> validateNestedArrays ( $element , $depth ) ;
}
}
}
private function calculateShape ( array $data) : array {
$shape = [];
$current = $data;
while ( is_array ( $current ) ) {
$shape[] = count ( $current ) ;
$current = $current[ 0 ] ?? null ;
}
return $shape;
}
public function shape () : array {
return $this -> shape;
}
public function reshape ( array $newShape) : self {
$totalElements = array_product ( $this -> shape ) ;
$newTotalElements = array_product ( $newShape ) ;
if ($totalElements !== $newTotalElements) {
throw new InvalidArgumentException ( "Cannot reshape tensor: incompatible dimensions" );
}
$flatData = $this -> flatten ( $this -> data ) ;
$reshaped = $this -> reshapeArray ( $flatData , $newShape , 0 ) ;
return new self ($reshaped);
}
private function flatten ( array $array) : array {
$result = [];
array_walk_recursive ( $array , function ($value) use ( & $result) {
$result[] = $value;
} ) ;
return $result;
}
private function reshapeArray ( array $flatData , array $shape , int $offset) : array {
if ( empty ( $shape ) ) {
throw new InvalidArgumentException ( "Shape cannot be empty" );
}
$currentDim = array_shift ( $shape ) ;
$subSize = empty ( $shape ) ? 1 : array_product ( $shape ) ;
$result = [];
for ($i = 0 ; $i < $currentDim; $i ++ ) {
if ( empty ( $shape ) ) {
$result[] = $flatData[$offset + $i];
} else {
$result[] = $this -> reshapeArray ( $flatData , $shape , $offset + ($i * $subSize) ) ;
}
}
return $result;
}
public function add ( Tensor $other) : self {
if ( $this -> shape !== $other -> shape) {
throw new InvalidArgumentException ( "Tensors must have the same shape for addition" );
}
$result = $this -> elementWiseOperation ( $this -> data , $other -> data , fn ($a , $b) => $a + $b ) ;
return new self ($result);
}
public function subtract ( Tensor $other) : self {
if ( $this -> shape !== $other -> shape) {
throw new InvalidArgumentException ( "Tensors must have the same shape for subtraction" );
}
$result = $this -> elementWiseOperation ( $this -> data , $other -> data , fn ($a , $b) => $a - $b ) ;
return new self ($result);
}
public function multiply ( Tensor $other) : self {
if ( $this -> shape !== $other -> shape) {
throw new InvalidArgumentException ( "Tensors must have the same shape for element-wise multiplication" );
}
$result = $this -> elementWiseOperation ( $this -> data , $other -> data , fn ($a , $b) => $a * $b ) ;
return new self ($result);
}
public function divide ( Tensor $other) : self {
if ( $this -> shape !== $other -> shape) {
throw new InvalidArgumentException ( "Tensors must have the same shape for division" );
}
$result = $this -> elementWiseOperation ( $this -> data , $other -> data , function ($a , $b) {
if ($b == 0 ) {
throw new DivisionByZeroError ( "Division by zero" );
}
return $a / $b;
} ) ;
return new self ($result);
}
private function elementWiseOperation ( array $arr1 , array $arr2 , callable $operation) : array {
$result = [];
foreach ($arr1 as $key => $value) {
if ( is_array ( $value ) ) {
$result[$key] = $this -> elementWiseOperation ( $value , $arr2[$key] , $operation ) ;
} else {
$result[$key] = $operation ($value , $arr2[$key]);
}
}
return $result;
}
public function matrixMultiply ( Tensor $other) : self {
if ( count ( $this -> shape ) !== 2 || count ( $other -> shape ) !== 2 ) {
throw new InvalidArgumentException ( "Matrix multiplication requires 2D tensors" );
}
if ( $this -> shape[ 1 ] !== $other -> shape[ 0 ]) {
throw new InvalidArgumentException ( "Incompatible dimensions for matrix multiplication" );
}
$result = [];
for ($i = 0 ; $i < $this -> shape[ 0 ]; $i ++ ) {
$result[$i] = [];
for ($j = 0 ; $j < $other -> shape[ 1 ]; $j ++ ) {
$sum = 0 ;
for ($k = 0 ; $k < $this -> shape[ 1 ]; $k ++ ) {
$sum += $this -> data[$i][$k] * $other -> data[$k][$j];
}
$result[$i][$j] = $sum;
}
}
return new self ($result);
}
public function dotProduct ( Tensor $other) : float {
// Ensure both tensors are vectors (1D)
if ( count ( $this -> shape ) !== 1 || count ( $other -> shape ) !== 1 ) {
throw new InvalidArgumentException ( "Dot product requires 1D tensors (vectors)" );
}
// Check dimensions match
if ( $this -> shape[ 0 ] !== $other -> shape[ 0 ]) {
throw new InvalidArgumentException ( "Vectors must have the same dimension" );
}
$result = 0 ;
for ($i = 0 ; $i < $this -> shape[ 0 ]; $i ++ ) {
$result += $this -> data[$i] * $other -> data[$i];
}
return $result;
}
public function transpose () : self {
if ( count ( $this -> shape ) !== 2 ) {
throw new InvalidArgumentException ( "Transpose operation is only supported for 2D tensors" );
}
$result = [];
for ($i = 0 ; $i < $this -> shape[ 1 ]; $i ++ ) {
for ($j = 0 ; $j < $this -> shape[ 0 ]; $j ++ ) {
$result[$i][$j] = $this -> data[$j][$i];
}
}
return new self ($result);
}
public function determinant () : float {
if ( count ( $this -> shape ) !== 2 || $this -> shape[ 0 ] !== $this -> shape[ 1 ]) {
throw new InvalidArgumentException ( "Determinant requires a square matrix" );
}
$n = $this -> shape[ 0 ];
if ($n === 1 ) {
return $this -> data[ 0 ][ 0 ];
}
if ($n === 2 ) {
return $this -> data[ 0 ][ 0 ] * $this -> data[ 1 ][ 1 ] - $this -> data[ 0 ][ 1 ] * $this -> data[ 1 ][ 0 ];
}
$det = 0 ;
for ($j = 0 ; $j < $n; $j ++ ) {
$det += pow ( - 1 , $j ) * $this -> data[ 0 ][$j] * $this -> getMinor ( 0 , $j ) -> determinant () ;
}
return $det;
}
private function getMinor ( int $row , int $col) : self {
$minor = [];
$n = $this -> shape[ 0 ];
$r = 0 ;
for ($i = 0 ; $i < $n; $i ++ ) {
if ($i === $row) continue ;
$minor[$r] = [];
$c = 0 ;
for ($j = 0 ; $j < $n; $j ++ ) {
if ($j === $col) continue ;
$minor[$r][$c] = $this -> data[$i][$j];
$c ++ ;
}
$r ++ ;
}
return new self ($minor);
}
public function exp () : self {
return $this -> applyFunction ( fn ($x) => exp ( $x )) ;
}
public function log () : self {
return $this -> applyFunction ( fn ($x) => log ( $x )) ;
}
public function power ( float $n) : self {
return $this -> applyFunction ( fn ($x) => pow ( $x , $n )) ;
}
private function applyFunction ( callable $func) : self {
$result = $this -> applyFunctionToArray ( $this -> data , $func ) ;
return new self ($result);
}
private function applyFunctionToArray ( array $arr , callable $func) : array {
$result = [];
foreach ($arr as $key => $value) {
if ( is_array ( $value ) ) {
$result[$key] = $this -> applyFunctionToArray ( $value , $func ) ;
} else {
$result[$key] = $func ($value);
}
}
return $result;
}
public function getData () : array {
return $this -> data;
}
// Helper method to convert tensor to string for debugging
public function __toString () : string {
return json_encode ( $this -> data , JSON_PRETTY_PRINT ) ;
}
}