Not having much luck wtih SplFixedArray. Here's a script:
class JMatrix {
public $data;
public function __construct($arr) {
if (is_a($arr, 'SplFixedArray')) {
// shortcut...provide SplFixedArray param, bypass checks and just assign
$this->data = $arr;
} elseif (!is_array($arr)) {
// if not SplFixedArray, must be standard array
throw new Exception('Constructor param must be an array');
} else {
// if it's an array, check if 2D array (matrix) or just 1D array vector, which we treat as matrix
if (!is_array($arr[0])) {
// the supplied parameter is a simple array, so we'll construct a row vector
// from its elements
$row_count = 1;
$col_count = sizeof($arr);
$this->data = new SplFixedArray(1);
$this->data->offsetSet(0, new SplFixedArray($col_count));
foreach($arr as $j => $v) {
$this->data->offsetGet(0)->offsetSet($j, $v);
}
} else {
// an array of arrays...sanity check on element counts
$row_count = sizeof($arr);
$col_count = sizeof($arr[0]);
foreach($arr as $v) {
if (sizeof($v) !== $col_count) {
throw new Exception('Invalid matrix array. Inconsistent column count in constructor array.');
}
}
$this->data = new SplFixedArray($row_count);
foreach($arr as $i => $row) {
$this->data->offsetSet($i, new SplFixedArray($col_count));
foreach($row as $j => $v) {
$this->data->offsetGet($i)->offsetSet($j, $v);
}
}
}
}
}
public function rowCount() {
return $this->data->getSize();
}
public function colCount() {
return $this->data->offsetGet(0)->getSize();
}
public function size() {
return [$this->rowCount(), $this->colCount()];
}
public function sizeAsString() {
return $this->rowCount() . 'x' . $this->colCount();
}
public function __toString() {
$retval = "[\n";
foreach($this->data as $i => $row) {
$retval .= "\t[" . implode(",", (array)$row) . "]\n";
}
$retval .= "]\n";
return $retval;
}
public function multiply($matrix) {
$iteration_count = $this->colCount();
if ($iteration_count !== $matrix->rowCount()) {
throw new Exception('nonconformant arguments (op1 is ' . $this->sizeAsString() . ', op2 is ' . $matrix->sizeAsString() . ')');
}
$result_rows = $this->rowCount();
$result_cols = $matrix->colCount();
// accumulate results in this array
$ret_array = new SplFixedArray($result_rows);
// temp vars to avoid object property lookup
$td = $this->data;
$md = $matrix->data;
for($i=0; $i<$result_rows; $i++) {
// current row from this matrix
$drow = $td->offsetGet($i);
// accumulate output matrix row here
$retrow = new SplFixedArray($result_cols);
// loop thru $matrix columns
for($j=0; $j<$result_cols; $j++) {
// accumulate product sum here
$sum = 0;
// iteration count is number of columns in this matrix == number of rows in $matrix
for($k=0; $k<$iteration_count; $k++) {
$sum += ($drow->offsetGet($k) * $matrix->data->offsetGet($k)->offsetGet($j));
}
// all products summed, store it in our output matrix row
$retrow->offsetSet($j, $sum);
}
// put the output matrix row into our output matrix
$ret_array->offsetSet($i, $retrow);
}
return new static($ret_array);
}
public function multiply2($matrix) {
$iteration_count = $this->colCount();
if ($iteration_count !== $matrix->rowCount()) {
throw new Exception('nonconformant arguments (op1 is ' . $this->sizeAsString() . ', op2 is ' . $matrix->sizeAsString() . ')');
}
$result_rows = $this->rowCount();
$result_cols = $matrix->colCount();
// accumulate results in this array
$ret_array = new SplFixedArray($result_rows);
// temp vars to avoid object property lookup
$td = $this->data;
$md = $matrix->data;
$td->rewind();
$i = 0;
while ($td->valid()) {
// current row from this matrix
$drow = $td->current();
// accumulate output matrix row here
$retrow = new SplFixedArray($result_cols);
// loop thru $matrix columns
for($j=0; $j<$result_cols; $j++) {
// accumulate product sum here
$sum = 0;
// iteration count is number of columns in this matrix == number of rows in $matrix
$md->rewind();
$k = 0;
while($md->valid()) {
$mrow = $md->current();
$sum += ($drow->offsetGet($k) * $mrow->offsetGet($j));
$md->next();
$k++;
}
// all products summed, store it in our output matrix row
$retrow->offsetSet($j, $sum);
}
// put the output matrix row into our output matrix
$ret_array->offsetSet($i, $retrow);
$td->next();
$i++;
}
return new static($ret_array);
}
public function transpose() {
$arr = [];
foreach($this->data as $row) {
$arr[] = (array)$row;
}
$arr = array_map(null, ...$arr);
return new static($arr);
}
}
echo "SANITY CHECK\n";
$a = new JMatrix([
[1, 4, -2],
[3, 5, -6]
]);
echo $a;
echo "size: " . $a->sizeAsString() . "\n";
$b = new JMatrix([
[5, 2, 8, -1],
[3, 6, 4, 5],
[-2, 9, 7, -3]
]);
echo $b;
echo "size: " . $b->sizeAsString() . "\n";
$v = $a->multiply2($b);
echo $v . "\n";
echo "size: " . $v->sizeAsString() . "\n";
$vt = $v->transpose();
echo "$vt\n";
echo "size: " . $vt->sizeAsString() . "\n";
$data_file = './training_data_sets.json';
$data = json_decode(file_get_contents($data_file), TRUE);
echo 'Xtrain rows: ' . sizeof($data['Xtrain']) . "\n";
echo 'Xtrain cols: ' . sizeof($data['Xtrain'][0]) . "\n";
$X = new JMatrix($data['Xtrain']);
$start = microtime(TRUE);
$XT = $X->transpose();
echo "transpose in " . (microtime(TRUE) - $start) . " seconds\n";
$start = microtime(TRUE);
$K = $X->multiply2($XT);
echo "multiply2 in " . (microtime(TRUE) - $start) . " seconds\n";
multiply
, my first attempt, runs in 973 seconds:
Xtrain rows: 1375
Xtrain cols: 1966
transpose in 0.79622912406921 seconds
multiply in 973.16395616531 seconds
multiply2
, where i attempt to avoid indexed lookups, actually takes longer -- about 20 minutes .
Xtrain rows: 1375
Xtrain cols: 1966
transpose in 0.79264497756958 seconds
multiply2 in 1199.6731460094 seconds