Skip to content

Commit

Permalink
matrix: division, mapColumns, mapRows added
Browse files Browse the repository at this point in the history
  • Loading branch information
gyrdym committed Feb 18, 2019
1 parent 157c2c9 commit fff27e3
Show file tree
Hide file tree
Showing 5 changed files with 200 additions and 1 deletion.
5 changes: 5 additions & 0 deletions CHANGELOG.md
@@ -1,5 +1,10 @@
# Changelog

## 5.2.0
- MLMatrix: `/` operator added
- MLMatrix: `rowsMap` method added
- MLMatrix: `columnsMap` method added

## 5.1.0
- `max` and `min` methods added for matrix

Expand Down
12 changes: 12 additions & 0 deletions lib/matrix.dart
Expand Up @@ -56,6 +56,10 @@ abstract class MLMatrix {
/// whatever
MLMatrix operator *(Object value);

/// Performs division of the matrix by a matrix/ a vector/ a scalar/
/// whatever
MLMatrix operator /(Object value);

/// Performs transposition of the matrix
MLMatrix transpose();

Expand All @@ -79,6 +83,14 @@ abstract class MLMatrix {
MLVector reduceRows(MLVector combiner(MLVector combine, MLVector vector),
{MLVector initValue});

/// Performs column-wise mapping of this matrix to a new one via passed
/// [mapper] function
MLMatrix mapColumns(MLVector mapper(MLVector column));

/// Performs row-wise mapping of this matrix to a new one via passed
/// [mapper] function
MLMatrix mapRows(MLVector mapper(MLVector row));

/// Creates a new matrix, efficiently iterating through all the matrix
/// elements (several floating point elements in a time) and applying the
/// [mapper] function
Expand Down
48 changes: 48 additions & 0 deletions lib/src/matrix/ml_matrix_mixin.dart
Expand Up @@ -60,6 +60,21 @@ abstract class MLMatrixMixin<E, S extends List<E>>
}
}

/// Performs division of the matrix by vector, matrix or scalar
@override
MLMatrix operator /(Object value) {
if (value is MLVector) {
return _matrixByVectorDiv(value);
} else if (value is MLMatrix) {
return _matrixByMatrixDiv(value);
} else if (value is num) {
return _matrixByScalarDiv(value.toDouble());
} else {
throw UnsupportedError(
'Cannot divide a ${runtimeType} by a ${value.runtimeType}');
}
}

@override
List<double> operator [](int index) => _query(index * columnsNum, columnsNum);

Expand Down Expand Up @@ -136,6 +151,16 @@ abstract class MLMatrixMixin<E, S extends List<E>>
{MLVector initValue}) =>
_reduce(combiner, rowsNum, getRow, initValue: initValue);

@override
MLMatrix mapColumns(MLVector mapper(MLVector columns)) =>
MLMatrix.columns(List<MLVector>.generate(columnsNum,
(int i) => mapper(getColumn(i))));

@override
MLMatrix mapRows(MLVector mapper(MLVector row)) =>
MLMatrix.rows(List<MLVector>.generate(rowsNum,
(int i) => mapper(getRow(i))));

List<double> flatten2dimList(
Iterable<Iterable<double>> rows, int Function(int i, int j) accessor) {
int i = 0;
Expand Down Expand Up @@ -234,6 +259,24 @@ abstract class MLMatrixMixin<E, S extends List<E>>
return createMatrixFromFlattened(source, rowsNum, matrix.columnsNum);
}

MLMatrix _matrixByVectorDiv(MLVector vector) {
if (vector.length == rowsNum) {
return mapColumns((column) => column / vector);
}
if (vector.length == columnsNum) {
return mapRows((row) => row / vector);
}
throw Exception('Cannot divide the $rowsNum x $columnsNum matrix by a '
'vector of length equals ${vector.length}');
}

MLMatrix _matrixByMatrixDiv(MLMatrix matrix) {
checkDimensions(this, matrix, errorTitle: 'Cannot perform matrix by matrix '
'division');
return _matrix2matrixOperation(
matrix, (MLVector first, MLVector second) => first / second);
}

MLMatrix _matrixAdd(MLMatrix matrix) {
checkDimensions(this, matrix, errorTitle: 'Cannot perform matrix addition');
return _matrix2matrixOperation(
Expand All @@ -256,6 +299,9 @@ abstract class MLMatrixMixin<E, S extends List<E>>
MLMatrix _matrixScalarMul(double scalar) => _matrix2scalarOperation(
scalar, (double val, MLVector vector) => vector * val);

MLMatrix _matrixByScalarDiv(double scalar) => _matrix2scalarOperation(
scalar, (double val, MLVector vector) => vector / val);

MLMatrix _matrix2matrixOperation(
MLMatrix matrix, MLVector operation(MLVector first, MLVector second)) {
final elementGenFn = (int i) => operation(getRow(i), matrix.getRow(i));
Expand All @@ -265,6 +311,8 @@ abstract class MLMatrixMixin<E, S extends List<E>>

MLMatrix _matrix2scalarOperation(
double scalar, MLVector operation(double scalar, MLVector vector)) {
// TODO: use vectorized type (e.g. Float32x4) instead of `double`
// TODO: use then `fastMap` to accelerate computations
final elementGenFn = (int i) => operation(scalar, getRow(i));
final source = List<MLVector>.generate(rowsNum, elementGenFn);
return createMatrixFromRows(source);
Expand Down
2 changes: 1 addition & 1 deletion pubspec.yaml
@@ -1,6 +1,6 @@
name: ml_linalg
description: SIMD-based linear algebra with dart for machine learning purposes
version: 5.1.0
version: 5.2.0
author: Ilia Gyrdymov <ilgyrd@gmail.com>
homepage: https://github.com/gyrdym/ml_linalg

Expand Down
134 changes: 134 additions & 0 deletions test/float32x4_matrix_test.dart
Expand Up @@ -363,6 +363,38 @@ void main() {
expect(actual, equals(expected));
});

test('should perform column-wise mapping of the matrix to a new one', () {
final matrix = Float32x4Matrix.from([
[11.0, 12.0, 13.0, 14.0],
[15.0, 16.0, 17.0, 18.0],
[21.0, 22.0, 23.0, 24.0],
]);
final modifier = MLVector.filled(3, 2.0);
final actual = matrix.mapColumns((column) => column + modifier);
final expected = [
[13.0, 14.0, 15.0, 16.0],
[17.0, 18.0, 19.0, 20.0],
[23.0, 24.0, 25.0, 26.0],
];
expect(actual, equals(expected));
});

test('should perform row-wise mapping of the matrix to a new one', () {
final matrix = Float32x4Matrix.from([
[11.0, 12.0, 13.0, 14.0],
[15.0, 16.0, 17.0, 18.0],
[21.0, 22.0, 23.0, 24.0],
]);
final modifier = MLVector.filled(4, 1.0);
final actual = matrix.mapRows((row) => row - modifier);
final expected = [
[10.0, 11.0, 12.0, 13.0],
[14.0, 15.0, 16.0, 17.0],
[20.0, 21.0, 22.0, 23.0],
];
expect(actual, equals(expected));
});

test('should perform multiplication by a vector', () {
final matrix = Float32x4Matrix.from([
[1.0, 2.0, 3.0, 4.0],
Expand Down Expand Up @@ -430,6 +462,108 @@ void main() {
expect(() => matrix1 * matrix2, throwsException);
});

test('should perform row-wise division by a vector', () {
final matrix = Float32x4Matrix.from([
[4.0, 6.0, 20.0, 125.0],
[10.0, 18.0, 28.0, 40.0],
[18.0, .0, -12.0, -35.0],
]);
final vector = Float32x4Vector.from([2.0, 3.0, 4.0, 5.0]);
final actual = matrix / vector;
final expected = [
[2.0, 2.0, 5.0, 25.0],
[5.0, 6.0, 7.0, 8.0],
[9.0, .0, -3.0, -7.0],
];
expect(actual, equals(expected));
expect(actual.rowsNum, 3);
expect(actual.columnsNum, 4);
});

test('should perform column-wise division by a vector', () {
final matrix = Float32x4Matrix.from([
[4.0, 6.0, 20.0, 120.0],
[9.0, 18.0, 27.0, 45.0],
[14.0, .0, -21.0, -35.0],
]);
final vector = Float32x4Vector.from([2.0, 3.0, 7.0]);
final actual = matrix / vector;
final expected = [
[2.0, 3.0, 10.0, 60.0],
[3.0, 6.0, 9.0, 15.0],
[2.0, .0, -3.0, -5.0],
];
expect(actual, equals(expected));
expect(actual.rowsNum, 3);
expect(actual.columnsNum, 4);
});

test('should throw an error if one tries to divide by a vector of '
'unproper length', () {
final matrix = Float32x4Matrix.from([
[1.0, 2.0, 3.0, 4.0],
[5.0, 6.0, 7.0, 8.0],
[9.0, .0, -2.0, -3.0],
]);
final vector = Float32x4Vector.from([2.0, 3.0, 4.0, 5.0, 7.0]);
expect(() => matrix / vector, throwsException);
});

test('should perform division of a matrix by another matrix', () {
final matrix1 = Float32x4Matrix.from([
[1.0, 2.0, 3.0, 4.0],
[5.0, 6.0, 7.0, 8.0],
[9.0, .0, -2.0, -3.0],
]);
final matrix2 = Float32x4Matrix.from([
[1.0, 2.0, 3.0, 4.0],
[5.0, 6.0, 7.0, 8.0],
[9.0, 1.0, -2.0, -3.0],
]);
final actual = matrix1 / matrix2;
final expected = [
[1.0, 1.0, 1.0, 1.0],
[1.0, 1.0, 1.0, 1.0],
[1.0, .0, 1.0, 1.0],
];
expect(actual, equals(expected));
expect(actual.rowsNum, 3);
expect(actual.columnsNum, 4);
});

test('should throw an error if one tries to divide a matrix by another '
'matrix of unproper dimensions', () {
final matrix1 = Float32x4Matrix.from([
[1.0, 2.0, 3.0, 4.0],
[5.0, 6.0, 7.0, 8.0],
[9.0, .0, -2.0, -3.0],
]);
final matrix2 = Float32x4Matrix.from([
[1.0, 2.0],
[5.0, 6.0],
[9.0, .0],
]);
expect(() => matrix1 / matrix2, throwsException);
});

test('should perform division of a matrix by a scalar', () {
final matrix1 = Float32x4Matrix.from([
[1.0, 2.0, 3.0, 4.0],
[5.0, 6.0, 7.0, 8.0],
[9.0, .0, -2.0, -3.0],
]);
final scalar = 2.0;
final actual = matrix1 / scalar;
final expected = [
[.5, 1.0, 1.5, 2.0],
[2.5, 3.0, 3.5, 4.0],
[4.5, .0, -1.0, -1.5],
];
expect(actual, equals(expected));
expect(actual.rowsNum, 3);
expect(actual.columnsNum, 4);
});

test('should transpose a matrix', () {
final matrix = Float32x4Matrix.from([
[1.0, 2.0, 3.0, 4.0],
Expand Down

0 comments on commit fff27e3

Please sign in to comment.