diff --git a/src/math/math.ts b/src/math/math.ts index 55d3428574..791d4ee591 100644 --- a/src/math/math.ts +++ b/src/math/math.ts @@ -790,6 +790,15 @@ export abstract class NDArrayMath { } protected abstract sqrtInternal(ndarray: T): T; + /** + * Computes absolute value element-wise. + * @param ndarray The input NDArray. + */ + abs(ndarray: T): T { + return this.executeOp('abs', () => this.absInternal(ndarray)); + } + protected abstract absInternal(ndarray: T): T; + /** * Computes rectified linear element-wise, max(x, 0). * @param ndarray The input NDArray. @@ -809,22 +818,86 @@ export abstract class NDArrayMath { protected abstract sigmoidInternal(ndarray: T): T; /** - * Computes hyperbolic tangent of the input NDArray element-wise. + * Computes sin of the input NDArray element-wise, y = sin(x). * @param ndarray The input NDArray. */ - tanh(ndarray: T): T { - return this.executeOp('tanh', () => this.tanhInternal(ndarray)); + sin(ndarray: T): T { + return this.executeOp('sin', () => this.sinInternal(ndarray)); } - protected abstract tanhInternal(ndarray: T): T; + protected abstract sinInternal(ndarray: T): T; /** - * Computes sin of the input NDArray element-wise, y = sin(x). + * Computes cos of the input NDArray element-wise, y = cos(x). * @param ndarray The input NDArray. */ - sin(ndarray: T): T { - return this.executeOp('sin', () => this.sinInternal(ndarray)); + cos(ndarray: T): T { + return this.executeOp('cos', () => this.cosInternal(ndarray)); } - protected abstract sinInternal(ndarray: T): T; + protected abstract cosInternal(ndarray: T): T; + + /** + * Computes tan of the input NDArray element-wise, y = tan(x). + * @param ndarray The input NDArray. + */ + tan(ndarray: T): T { + return this.executeOp('tan', () => this.tanInternal(ndarray)); + } + protected abstract tanInternal(ndarray: T): T; + + /** + * Computes asin of the input NDArray element-wise, y = asin(x). + * @param ndarray The input NDArray. + */ + asin(ndarray: T): T { + return this.executeOp('asin', () => this.asinInternal(ndarray)); + } + protected abstract asinInternal(ndarray: T): T; + + /** + * Computes acos of the input NDArray element-wise, y = acos(x). + * @param ndarray The input NDArray. + */ + acos(ndarray: T): T { + return this.executeOp('acos', () => this.acosInternal(ndarray)); + } + protected abstract acosInternal(ndarray: T): T; + + /** + * Computes atan of the input NDArray element-wise, y = atan(x). + * @param ndarray The input NDArray. + */ + atan(ndarray: T): T { + return this.executeOp('atan', () => this.atanInternal(ndarray)); + } + protected abstract atanInternal(ndarray: T): T; + + /** + * Computes hyperbolic sin of the input NDArray element-wise, y = sinh(x). + * @param ndarray The input NDArray. + */ + sinh(ndarray: T): T { + return this.executeOp('sinh', () => this.sinhInternal(ndarray)); + } + protected abstract sinhInternal(ndarray: T): T; + + /** + * Computes hyperbolic cos of the input NDArray element-wise, y = cosh(x). + * @param ndarray The input NDArray. + */ + cosh(ndarray: T): T { + return this.executeOp('cosh', () => this.coshInternal(ndarray)); + } + protected abstract coshInternal(ndarray: T): T; + + /** + * Computes hyperbolic tangent of the input NDArray element-wise. + * @param ndarray The input NDArray. + */ + tanh(ndarray: T): T { + return this.executeOp('tanh', () => this.tanhInternal(ndarray)); + } + protected abstract tanhInternal(ndarray: T): T; + /** * Computes step of the input NDArray element-wise, y = 1 if x > 0 | 0 if x <= diff --git a/src/math/math_cpu.ts b/src/math/math_cpu.ts index 207f5e3a59..70b8d1fc96 100644 --- a/src/math/math_cpu.ts +++ b/src/math/math_cpu.ts @@ -337,20 +337,20 @@ export class NDArrayMathCPU extends NDArrayMath { return NDArray.make(ndarray.shape, {values: resultValues}); } - protected sigmoidInternal(ndarray: T): T { + protected absInternal(ndarray: T): T { const resultValues = new Float32Array(ndarray.size); const values = ndarray.getValues(); for (let i = 0; i < values.length; ++i) { - resultValues[i] = 1 / (1 + Math.exp(-values[i])); + resultValues[i] = Math.abs(values[i]); } return NDArray.make(ndarray.shape, {values: resultValues}); } - protected tanhInternal(ndarray: T): T { + protected sigmoidInternal(ndarray: T): T { const resultValues = new Float32Array(ndarray.size); const values = ndarray.getValues(); for (let i = 0; i < values.length; ++i) { - resultValues[i] = util.tanh(values[i]); + resultValues[i] = 1 / (1 + Math.exp(-values[i])); } return NDArray.make(ndarray.shape, {values: resultValues}); } @@ -364,6 +364,78 @@ export class NDArrayMathCPU extends NDArrayMath { return NDArray.make(ndarray.shape, {values: resultValues}); } + protected cosInternal(ndarray: T): T { + const resultValues = new Float32Array(ndarray.size); + const values = ndarray.getValues(); + for (let i = 0; i < values.length; ++i) { + resultValues[i] = Math.cos(values[i]); + } + return NDArray.make(ndarray.shape, {values: resultValues}); + } + + protected tanInternal(ndarray: T): T { + const resultValues = new Float32Array(ndarray.size); + const values = ndarray.getValues(); + for (let i = 0; i < values.length; ++i) { + resultValues[i] = Math.tan(values[i]); + } + return NDArray.make(ndarray.shape, {values: resultValues}); + } + + protected asinInternal(ndarray: T): T { + const resultValues = new Float32Array(ndarray.size); + const values = ndarray.getValues(); + for (let i = 0; i < values.length; ++i) { + resultValues[i] = Math.asin(values[i]); + } + return NDArray.make(ndarray.shape, {values: resultValues}); + } + + protected acosInternal(ndarray: T): T { + const resultValues = new Float32Array(ndarray.size); + const values = ndarray.getValues(); + for (let i = 0; i < values.length; ++i) { + resultValues[i] = Math.acos(values[i]); + } + return NDArray.make(ndarray.shape, {values: resultValues}); + } + + protected atanInternal(ndarray: T): T { + const resultValues = new Float32Array(ndarray.size); + const values = ndarray.getValues(); + for (let i = 0; i < values.length; ++i) { + resultValues[i] = Math.atan(values[i]); + } + return NDArray.make(ndarray.shape, {values: resultValues}); + } + + protected sinhInternal(ndarray: T): T { + const resultValues = new Float32Array(ndarray.size); + const values = ndarray.getValues(); + for (let i = 0; i < values.length; ++i) { + resultValues[i] = Math.sinh(values[i]); + } + return NDArray.make(ndarray.shape, {values: resultValues}); + } + + protected coshInternal(ndarray: T): T { + const resultValues = new Float32Array(ndarray.size); + const values = ndarray.getValues(); + for (let i = 0; i < values.length; ++i) { + resultValues[i] = Math.cosh(values[i]); + } + return NDArray.make(ndarray.shape, {values: resultValues}); + } + + protected tanhInternal(ndarray: T): T { + const resultValues = new Float32Array(ndarray.size); + const values = ndarray.getValues(); + for (let i = 0; i < values.length; ++i) { + resultValues[i] = util.tanh(values[i]); + } + return NDArray.make(ndarray.shape, {values: resultValues}); + } + protected stepInternal(ndarray: T): T { const resultValues = new Float32Array(ndarray.size); const values = ndarray.getValues(); diff --git a/src/math/math_gpu.ts b/src/math/math_gpu.ts index e11aaab27b..f7ac299180 100644 --- a/src/math/math_gpu.ts +++ b/src/math/math_gpu.ts @@ -23,6 +23,7 @@ import {AddScaledMatProgram} from './webgl/addscaledmat_gpu'; import {ArgMaxEqualsProgram} from './webgl/argmaxequals_gpu'; import {ArgMinMaxProgram} from './webgl/argminmax_gpu'; import {BatchNormProgram} from './webgl/batchnorm_gpu'; +import * as binaryop_gpu from './webgl/binaryop_gpu'; import {BinaryOpProgram} from './webgl/binaryop_gpu'; import {Concat3DProgram} from './webgl/concat3d_gpu'; // tslint:disable-next-line:max-line-length @@ -41,7 +42,8 @@ import {Pool2DProgram} from './webgl/pool_gpu'; import {ReduceSumProgram} from './webgl/reducesum_gpu'; import {ResizeBilinear3DProgram} from './webgl/resize_bilinear_gpu'; import {TextureManager} from './webgl/texture_manager'; -import {UnaryOp, UnaryOpProgram} from './webgl/unaryop_gpu'; +import * as unary_op from './webgl/unaryop_gpu'; +import {UnaryOpProgram} from './webgl/unaryop_gpu'; import * as webgl_util from './webgl/webgl_util'; export class NDArrayMathGPU extends NDArrayMath { @@ -116,7 +118,7 @@ export class NDArrayMathGPU extends NDArrayMath { } protected negInternal(a: T): T { - const program = new UnaryOpProgram(a.shape, UnaryOp.NEG); + const program = new UnaryOpProgram(a.shape, unary_op.NEG); return this.compileAndRun(program, [a]); } @@ -150,7 +152,7 @@ export class NDArrayMathGPU extends NDArrayMath { } protected multiplyInternal(a: T, b: T): T { - const program = new BinaryOpProgram('*', a.shape, b.shape); + const program = new BinaryOpProgram(binaryop_gpu.MUL, a.shape, b.shape); return this.compileAndRun(program, [a, b]); } @@ -222,17 +224,17 @@ export class NDArrayMathGPU extends NDArrayMath { } protected divideInternal(a: T, b: T): T { - const program = new BinaryOpProgram('/', a.shape, b.shape); + const program = new BinaryOpProgram(binaryop_gpu.DIV, a.shape, b.shape); return this.compileAndRun(program, [a, b]); } protected addInternal(a: T, b: T): T { - const program = new BinaryOpProgram('+', a.shape, b.shape); + const program = new BinaryOpProgram(binaryop_gpu.ADD, a.shape, b.shape); return this.compileAndRun(program, [a, b]); } protected subInternal(a: T, b: T): T { - const program = new BinaryOpProgram('-', a.shape, b.shape); + const program = new BinaryOpProgram(binaryop_gpu.SUB, a.shape, b.shape); return this.compileAndRun(program, [a, b]); } @@ -242,42 +244,83 @@ export class NDArrayMathGPU extends NDArrayMath { } protected expInternal(a: T): T { - const program = new UnaryOpProgram(a.shape, UnaryOp.EXP); + const program = new UnaryOpProgram(a.shape, unary_op.EXP); return this.compileAndRun(program, [a]); } protected logInternal(a: T): T { - const program = new UnaryOpProgram(a.shape, UnaryOp.LOG); + const program = new UnaryOpProgram(a.shape, unary_op.LOG); return this.compileAndRun(program, [a]); } protected sqrtInternal(a: T): T { - const program = new UnaryOpProgram(a.shape, UnaryOp.SQRT); + const program = new UnaryOpProgram(a.shape, unary_op.SQRT); return this.compileAndRun(program, [a]); } protected reluInternal(a: T): T { - const program = new UnaryOpProgram(a.shape, UnaryOp.RELU); + const program = new UnaryOpProgram(a.shape, unary_op.RELU); + return this.compileAndRun(program, [a]); + } + + protected absInternal(a: T): T { + const program = new UnaryOpProgram(a.shape, unary_op.ABS); return this.compileAndRun(program, [a]); } protected sigmoidInternal(a: T): T { - const program = new UnaryOpProgram(a.shape, UnaryOp.SIGMOID); + const program = new UnaryOpProgram(a.shape, unary_op.SIGMOID); return this.compileAndRun(program, [a]); } - protected tanhInternal(a: T): T { - const program = new UnaryOpProgram(a.shape, UnaryOp.TANH); + protected sinInternal(a: T): T { + const program = new UnaryOpProgram(a.shape, unary_op.SIN); return this.compileAndRun(program, [a]); } - protected sinInternal(a: T): T { - const program = new UnaryOpProgram(a.shape, UnaryOp.SIN); + protected cosInternal(a: T): T { + const program = new UnaryOpProgram(a.shape, unary_op.COS); + return this.compileAndRun(program, [a]); + } + + protected tanInternal(a: T): T { + const program = new UnaryOpProgram(a.shape, unary_op.TAN); + return this.compileAndRun(program, [a]); + } + + protected asinInternal(a: T): T { + const program = new UnaryOpProgram(a.shape, unary_op.ASIN); + return this.compileAndRun(program, [a]); + } + + protected acosInternal(a: T): T { + const program = new UnaryOpProgram(a.shape, unary_op.ACOS); return this.compileAndRun(program, [a]); } + protected atanInternal(a: T): T { + const program = new UnaryOpProgram(a.shape, unary_op.ATAN); + return this.compileAndRun(program, [a]); + } + + protected sinhInternal(a: T): T { + const program = new UnaryOpProgram(a.shape, unary_op.SINH); + return this.compileAndRun(program, [a]); + } + + protected coshInternal(a: T): T { + const program = new UnaryOpProgram(a.shape, unary_op.COSH); + return this.compileAndRun(program, [a]); + } + + protected tanhInternal(a: T): T { + const program = new UnaryOpProgram(a.shape, unary_op.TANH); + return this.compileAndRun(program, [a]); + } + + protected stepInternal(a: T): T { - const program = new UnaryOpProgram(a.shape, UnaryOp.STEP); + const program = new UnaryOpProgram(a.shape, unary_op.STEP); return this.compileAndRun(program, [a]); } diff --git a/src/math/math_gpu_test.ts b/src/math/math_gpu_test.ts index e6d334f4b7..27038d9a73 100644 --- a/src/math/math_gpu_test.ts +++ b/src/math/math_gpu_test.ts @@ -889,6 +889,23 @@ describe('NDArrayMathGPU unary ops', () => { a.dispose(); }); + it('abs', () => { + const a = Array1D.new([1, -2, 0, 3, -0.1]); + const result = math.abs(a); + expect(result.getValues()).toEqual(new Float32Array([1, 2, 0, 3, 0.1])); + + a.dispose(); + }); + + it('abs propagates NaNs', () => { + const a = Array1D.new([1, -2, 0, 3, -0.1, NaN]); + const result = math.abs(a); + expect(result.getValues()).toEqual(new Float32Array([ + 1, 2, 0, 3, 0.1, NaN + ])); + a.dispose(); + }); + it('step with 1d ndarray', () => { const a = Array1D.new([1, -2, 0, 3, -0.1]); const result = math.step(a); @@ -929,27 +946,6 @@ describe('NDArrayMathGPU unary ops', () => { a.dispose(); }); - it('tanh', () => { - const values = [1, -3, 2, 7, -4]; - const a = Array1D.new(values); - const result = math.tanh(a); - const expected = new Float32Array(a.size); - for (let i = 0; i < a.size; i++) { - expected[i] = util.tanh(values[i]); - } - test_util.expectArraysClose(result.getValues(), expected, 1e-6); - - a.dispose(); - }); - - it('tanh propagates NaNs', () => { - const a = Array1D.new([4, NaN, 0]); - const res = math.tanh(a).getValues(); - const expected = [util.tanh(4), NaN, util.tanh(0)]; - test_util.expectArraysClose(res, new Float32Array(expected), 1e-5); - a.dispose(); - }); - it('sigmoid', () => { const values = [1, -3, 2, 7, -4]; const a = Array1D.new(values); @@ -991,6 +987,174 @@ describe('NDArrayMathGPU unary ops', () => { test_util.expectArraysClose(res, new Float32Array(expected), 1e-4); a.dispose(); }); + + it('cos', () => { + const values = [1, -3, 2, 7, -4]; + const a = Array1D.new(values); + const result = math.cos(a); + const expected = new Float32Array(a.size); + for (let i = 0; i < a.size; i++) { + expected[i] = Math.cos(values[i]); + } + test_util.expectArraysClose(result.getValues(), expected, 1e-3); + + a.dispose(); + }); + + it('cos propagates NaNs', () => { + const a = Array1D.new([4, NaN, 0]); + const res = math.cos(a).getValues(); + const expected = [Math.cos(4), NaN, Math.cos(0)]; + test_util.expectArraysClose(res, new Float32Array(expected), 1e-4); + a.dispose(); + }); + + it('tan', () => { + const values = [1, -3, 2, 7, -4]; + const a = Array1D.new(values); + const result = math.tan(a); + const expected = new Float32Array(a.size); + for (let i = 0; i < a.size; i++) { + expected[i] = Math.tan(values[i]); + } + test_util.expectArraysClose(result.getValues(), expected, 1e-3); + + a.dispose(); + }); + + it('tan propagates NaNs', () => { + const a = Array1D.new([4, NaN, 0]); + const res = math.tan(a).getValues(); + const expected = [Math.tan(4), NaN, Math.tan(0)]; + test_util.expectArraysClose(res, new Float32Array(expected), 1e-4); + a.dispose(); + }); + + it('asin', () => { + const values = [1, -3, 2, 7, -4]; + const a = Array1D.new(values); + const result = math.asin(a); + const expected = new Float32Array(a.size); + for (let i = 0; i < a.size; i++) { + expected[i] = Math.asin(values[i]); + } + test_util.expectArraysClose(result.getValues(), expected, 1e-3); + + a.dispose(); + }); + + it('asin propagates NaNs', () => { + const a = Array1D.new([4, NaN, 0]); + const res = math.asin(a).getValues(); + const expected = [Math.asin(4), NaN, Math.asin(0)]; + test_util.expectArraysClose(res, new Float32Array(expected), 1e-4); + a.dispose(); + }); + + it('acos', () => { + const values = [1, -3, 2, 7, -4]; + const a = Array1D.new(values); + const result = math.acos(a); + const expected = new Float32Array(a.size); + for (let i = 0; i < a.size; i++) { + expected[i] = Math.acos(values[i]); + } + test_util.expectArraysClose(result.getValues(), expected, 1e-3); + + a.dispose(); + }); + + it('acos propagates NaNs', () => { + const a = Array1D.new([4, NaN, 0]); + const res = math.acos(a).getValues(); + const expected = [Math.acos(4), NaN, Math.acos(0)]; + test_util.expectArraysClose(res, new Float32Array(expected), 1e-4); + a.dispose(); + }); + + it('atan', () => { + const values = [1, -3, 2, 7, -4]; + const a = Array1D.new(values); + const result = math.atan(a); + const expected = new Float32Array(a.size); + for (let i = 0; i < a.size; i++) { + expected[i] = Math.atan(values[i]); + } + test_util.expectArraysClose(result.getValues(), expected, 1e-3); + + a.dispose(); + }); + + it('atan propagates NaNs', () => { + const a = Array1D.new([4, NaN, 0]); + const res = math.atan(a).getValues(); + const expected = [Math.atan(4), NaN, Math.atan(0)]; + test_util.expectArraysClose(res, new Float32Array(expected), 1e-4); + a.dispose(); + }); + + it('sinh', () => { + const values = [1, -3, 2, 7, -4]; + const a = Array1D.new(values); + const result = math.sinh(a); + const expected = new Float32Array(a.size); + for (let i = 0; i < a.size; i++) { + expected[i] = Math.sinh(values[i]); + } + test_util.expectArraysClose(result.getValues(), expected, 1e-3); + + a.dispose(); + }); + + it('sinh propagates NaNs', () => { + const a = Array1D.new([4, NaN, 0]); + const res = math.sinh(a).getValues(); + const expected = [Math.sinh(4), NaN, Math.sinh(0)]; + test_util.expectArraysClose(res, new Float32Array(expected), 1e-5); + a.dispose(); + }); + + it('cosh', () => { + const values = [1, -3, 2, 7, -4]; + const a = Array1D.new(values); + const result = math.cosh(a); + const expected = new Float32Array(a.size); + for (let i = 0; i < a.size; i++) { + expected[i] = Math.cosh(values[i]); + } + test_util.expectArraysClose(result.getValues(), expected, 1e-3); + + a.dispose(); + }); + + it('cosh propagates NaNs', () => { + const a = Array1D.new([4, NaN, 0]); + const res = math.cosh(a).getValues(); + const expected = [Math.cosh(4), NaN, Math.cosh(0)]; + test_util.expectArraysClose(res, new Float32Array(expected), 1e-5); + a.dispose(); + }); + + it('tanh', () => { + const values = [1, -3, 2, 7, -4]; + const a = Array1D.new(values); + const result = math.tanh(a); + const expected = new Float32Array(a.size); + for (let i = 0; i < a.size; i++) { + expected[i] = util.tanh(values[i]); + } + test_util.expectArraysClose(result.getValues(), expected, 1e-6); + + a.dispose(); + }); + + it('tanh propagates NaNs', () => { + const a = Array1D.new([4, NaN, 0]); + const res = math.tanh(a).getValues(); + const expected = [util.tanh(4), NaN, util.tanh(0)]; + test_util.expectArraysClose(res, new Float32Array(expected), 1e-5); + a.dispose(); + }); }); describe('NDArrayMathGPU min/max', () => { diff --git a/src/math/webgl/abs_gpu_test.ts b/src/math/webgl/abs_gpu_test.ts new file mode 100644 index 0000000000..a6abf99af3 --- /dev/null +++ b/src/math/webgl/abs_gpu_test.ts @@ -0,0 +1,46 @@ +/** + * @license + * Copyright 2017 Google Inc. All Rights Reserved. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============================================================================= + */ + +import * as test_util from '../../test_util'; +import {Array2D} from '../ndarray'; + +import * as unaryop_gpu from './unaryop_gpu'; +import * as unaryop_gpu_test from './unaryop_gpu_test'; + +describe('abs_gpu', () => { + it('returns a matrix with the same shape as the input matrix', () => { + const a = new Float32Array(23 * 32); + const result = uploadAbsDownload(a, 23, 32); + expect(result.length).toEqual(a.length); + }); + + it('calculates f(x)=abs x for every value in the matrix', () => { + const a = new Float32Array([-2, 1, 0, -3]); + const result = uploadAbsDownload(a, 1, a.length); + const expected = new Float32Array(a.length); + for (let i = 0; i < a.length; ++i) { + expected[i] = Math.abs(a[i]); + } + test_util.expectArraysClose(result, expected, 0.0001); + }); +}); + +function uploadAbsDownload( + a: Float32Array, rows: number, cols: number): Float32Array { + const arr = Array2D.new([rows, cols], a); + return unaryop_gpu_test.uploadUnaryDownload(arr, unaryop_gpu.ABS); +} diff --git a/src/math/webgl/binaryop_gpu.ts b/src/math/webgl/binaryop_gpu.ts index 1b4446664e..3d79126641 100644 --- a/src/math/webgl/binaryop_gpu.ts +++ b/src/math/webgl/binaryop_gpu.ts @@ -15,9 +15,15 @@ * ============================================================================= */ -import {GPGPUProgram} from './gpgpu_math'; import * as util from '../../util'; +import {GPGPUProgram} from './gpgpu_math'; + +export const ADD = 'return a + b;'; +export const SUB = 'return a - b;'; +export const MUL = 'return a * b;'; +export const DIV = 'return a / b;'; + export class BinaryOpProgram implements GPGPUProgram { variableNames = ['A', 'B']; params: Array<{}>; @@ -25,14 +31,18 @@ export class BinaryOpProgram implements GPGPUProgram { userCode: string; supportsBroadcasting = true; - constructor(op: '+' | '-' | '*' | '/', aShape: number[], bShape: number[]) { + constructor(op: string, aShape: number[], bShape: number[]) { this.params = [op]; this.outputShape = util.assertAndGetBroadcastedShape(aShape, bShape); this.userCode = ` + float binaryOperation(float a, float b) { + ${op} + } + void main() { float a = getAAtOutCoords(); float b = getBAtOutCoords(); - setOutput(a ${op} b); + setOutput(binaryOperation(a, b)); } `; } diff --git a/src/math/webgl/binaryop_gpu_test.ts b/src/math/webgl/binaryop_gpu_test.ts index 80e1b90966..e91be95e83 100644 --- a/src/math/webgl/binaryop_gpu_test.ts +++ b/src/math/webgl/binaryop_gpu_test.ts @@ -19,6 +19,7 @@ import * as test_util from '../../test_util'; // tslint:disable-next-line:max-line-length import {Array1D, Array2D, Array3D, initializeGPU, NDArray, Scalar} from '../ndarray'; +import * as binaryop_gpu from './binaryop_gpu'; import {BinaryOpProgram} from './binaryop_gpu'; import {GPGPUContext} from './gpgpu_context'; import * as gpgpu_math from './gpgpu_math'; @@ -28,21 +29,21 @@ describe('binaryop_gpu Add', () => { it('returns a matrix with the same shape as the input matrix', () => { const a = Scalar.new(0); const b = Array2D.zeros([12, 513]); - const result = uploadBinaryOpDownload(a, b, '+'); + const result = uploadBinaryOpDownload(a, b, binaryop_gpu.ADD); expect(result.length).toEqual(b.size); }); it('preserves the matrix when the scalar is 0', () => { const c = Scalar.new(0); const a = Array1D.new([1, 2, 3]); - const result = uploadBinaryOpDownload(c, a, '+'); + const result = uploadBinaryOpDownload(c, a, binaryop_gpu.ADD); test_util.expectArraysClose(result, new Float32Array([1, 2, 3]), 0); }); it('adds the scalar to every element in the matrix', () => { const a = Array1D.new([1, 2, 3, 4]); const c = Scalar.new(0.5); - const result = uploadBinaryOpDownload(c, a, '+'); + const result = uploadBinaryOpDownload(c, a, binaryop_gpu.ADD); test_util.expectArraysClose( result, new Float32Array([1.5, 2.5, 3.5, 4.5]), 0.0001); }); @@ -52,21 +53,21 @@ describe('binaryop_gpu Sub', () => { it('returns a matrix with the same shape as the input matrix', () => { const a = Array2D.zeros([12, 513]); const c = Scalar.new(0); - const result = uploadBinaryOpDownload(a, c, '-'); + const result = uploadBinaryOpDownload(a, c, binaryop_gpu.SUB); expect(result.length).toEqual(a.size); }); it('preserves the matrix when the scalar is 0', () => { const a = Array1D.new([1, 2, 3]); const c = Scalar.new(0); - const result = uploadBinaryOpDownload(a, c, '-'); + const result = uploadBinaryOpDownload(a, c, binaryop_gpu.SUB); test_util.expectArraysClose(result, new Float32Array([1, 2, 3]), 0); }); it('subtracts the scalar from every element in the matrix', () => { const a = Array1D.new([1, 2, 3, 4]); const c = Scalar.new(0.5); - const result = uploadBinaryOpDownload(a, c, '-'); + const result = uploadBinaryOpDownload(a, c, binaryop_gpu.SUB); test_util.expectArraysClose( result, new Float32Array([0.5, 1.5, 2.5, 3.5]), 0.0001); }); @@ -74,7 +75,7 @@ describe('binaryop_gpu Sub', () => { it('2D - 1D broadcasting', () => { const a = Array2D.new([3, 2], [[1, 2], [3, 4], [5, 6]]); const b = Array1D.new([1, 3]); - const result = uploadBinaryOpDownload(a, b, '-'); + const result = uploadBinaryOpDownload(a, b, binaryop_gpu.SUB); test_util.expectArraysClose( result, new Float32Array([0, -1, 2, 1, 4, 3]), 1e-4); }); @@ -83,7 +84,7 @@ describe('binaryop_gpu Sub', () => { const a = Array2D.new([3, 2], [[1, 2], [3, 4], [5, 6]]); const b = Array1D.new([1, 2, 3]); // shape [3, 2] is not compatible with shape [3]. - const f = () => uploadBinaryOpDownload(a, b, '-'); + const f = () => uploadBinaryOpDownload(a, b, binaryop_gpu.SUB); expect(f).toThrowError(); }); @@ -91,7 +92,7 @@ describe('binaryop_gpu Sub', () => { const a = Array3D.new([2, 2, 2], [[[1, 2], [3, 4]], [[5, 6], [7, 8]]]); const b = Array2D.new([2, 2], [[1, 2], [3, 5]]); // shape [3, 2] is not compatible with shape [3]. - const res = uploadBinaryOpDownload(a, b, '-'); + const res = uploadBinaryOpDownload(a, b, binaryop_gpu.SUB); test_util.expectArraysClose( res, new Float32Array([0, 0, 0, -1, 4, 4, 4, 3]), 1e-4); }); @@ -110,21 +111,21 @@ describe('binaryop_gpu Mul', () => { it('returns a matrix with the same shape as the input matrix', () => { const a = Array2D.zeros([12, 513]); const c = Scalar.new(0); - const result = uploadBinaryOpDownload(c, a, '*'); + const result = uploadBinaryOpDownload(c, a, binaryop_gpu.MUL); expect(result.length).toEqual(a.size); }); it('zeros out the matrix when the scalar is 0', () => { const a = Array1D.new([1, 2, 3]); const c = Scalar.new(0); - const result = uploadBinaryOpDownload(c, a, '*'); + const result = uploadBinaryOpDownload(c, a, binaryop_gpu.MUL); test_util.expectArraysClose(result, new Float32Array([0, 0, 0]), 0); }); it('triples the matrix when the scalar is 3', () => { const a = Array1D.new([1, 2, 3]); const c = Scalar.new(3); - const result = uploadBinaryOpDownload(c, a, '*'); + const result = uploadBinaryOpDownload(c, a, binaryop_gpu.MUL); test_util.expectArraysClose(result, new Float32Array([3, 6, 9]), 0); }); @@ -133,7 +134,7 @@ describe('binaryop_gpu Mul', () => { const expected = a.getValues(); const b = Array2D.zerosLike(a); b.fill(1.0); - const result = uploadBinaryOpDownload(a, b, '*'); + const result = uploadBinaryOpDownload(a, b, binaryop_gpu.MUL); expect(result).toEqual(expected); }); @@ -142,7 +143,7 @@ describe('binaryop_gpu Mul', () => { a.fill(1.0); const b = Array2D.zerosLike(a); const expected = b.getValues(); - const result = uploadBinaryOpDownload(a, b, '*'); + const result = uploadBinaryOpDownload(a, b, binaryop_gpu.MUL); expect(result).toEqual(expected); }); @@ -151,7 +152,7 @@ describe('binaryop_gpu Mul', () => { const expected = a.getValues(); const b = Array1D.zeros([16]); b.fill(1.0); - const result = uploadBinaryOpDownload(a, b, '*'); + const result = uploadBinaryOpDownload(a, b, binaryop_gpu.MUL); test_util.expectArraysClose(result, expected, 0.0001); }); @@ -159,7 +160,7 @@ describe('binaryop_gpu Mul', () => { const a = Array1D.new(test_util.randomArrayInRange(64, -10, 10)); const b = Array1D.new(test_util.randomArrayInRange(64, -10, 10)); const expected = cpuMultiply(a.getValues(), b.getValues()); - const result = uploadBinaryOpDownload(a, b, '*'); + const result = uploadBinaryOpDownload(a, b, binaryop_gpu.MUL); test_util.expectArraysClose(result, expected, 0.0001); }); }); @@ -168,7 +169,7 @@ describe('binaryop_gpu Divide', () => { it('Scalar / Matrix', () => { const c = Scalar.new(2); const a = Array2D.new([2, 3], [1, 2, 3, 4, 5, 6]); - const r = uploadBinaryOpDownload(c, a, '/'); + const r = uploadBinaryOpDownload(c, a, binaryop_gpu.DIV); expect(r[0]).toBeCloseTo(2 / 1); expect(r[1]).toBeCloseTo(2 / 2); expect(r[2]).toBeCloseTo(2 / 3); @@ -178,8 +179,22 @@ describe('binaryop_gpu Divide', () => { }); }); +describe('binaryop_gpu custom', () => { + it('a ^ 2 + b', () => { + const c = Scalar.new(2); + const a = Array2D.new([2, 3], [1, 2, 3, 4, 5, 6]); + const r = uploadBinaryOpDownload(c, a, 'return a * a + b;'); + expect(r[0]).toBeCloseTo(2 * 2 + 1); + expect(r[1]).toBeCloseTo(2 * 2 + 2); + expect(r[2]).toBeCloseTo(2 * 2 + 3); + expect(r[3]).toBeCloseTo(2 * 2 + 4); + expect(r[4]).toBeCloseTo(2 * 2 + 5); + expect(r[5]).toBeCloseTo(2 * 2 + 6); + }); +}); + function uploadBinaryOpDownload( - a: NDArray, b: NDArray, op: '+'|'-'|'*'|'/'): Float32Array { + a: NDArray, b: NDArray, op: string): Float32Array { const gpgpu = new GPGPUContext(); const textureManager = new TextureManager(gpgpu); initializeGPU(gpgpu, textureManager); diff --git a/src/math/webgl/exp_gpu_test.ts b/src/math/webgl/exp_gpu_test.ts index 5c650994c1..d66cfd16dc 100644 --- a/src/math/webgl/exp_gpu_test.ts +++ b/src/math/webgl/exp_gpu_test.ts @@ -16,10 +16,11 @@ */ import * as test_util from '../../test_util'; -import {UnaryOp} from './unaryop_gpu'; -import * as unaryop_gpu_test from './unaryop_gpu_test'; import {Array2D} from '../ndarray'; +import * as unaryop_gpu from './unaryop_gpu'; +import * as unaryop_gpu_test from './unaryop_gpu_test'; + describe('exp_gpu', () => { it('returns a matrix with the same shape as the input matrix', () => { const a = new Float32Array(23 * 32); @@ -55,5 +56,5 @@ describe('exp_gpu', () => { function uploadExpDownload( a: Float32Array, rows: number, cols: number): Float32Array { const arr = Array2D.new([rows, cols], a); - return unaryop_gpu_test.uploadUnaryDownload(arr, UnaryOp.EXP); + return unaryop_gpu_test.uploadUnaryDownload(arr, unaryop_gpu.EXP); } diff --git a/src/math/webgl/log_gpu_test.ts b/src/math/webgl/log_gpu_test.ts index 311bc7eb91..68448aaf38 100644 --- a/src/math/webgl/log_gpu_test.ts +++ b/src/math/webgl/log_gpu_test.ts @@ -16,10 +16,11 @@ */ import * as test_util from '../../test_util'; -import {UnaryOp} from './unaryop_gpu'; -import * as unaryop_gpu_test from './unaryop_gpu_test'; import {Array2D} from '../ndarray'; +import * as unaryop_gpu from './unaryop_gpu'; +import * as unaryop_gpu_test from './unaryop_gpu_test'; + describe('log_gpu', () => { it('returns a matrix with the same shape as the input matrix', () => { const a = new Float32Array(23 * 32); @@ -56,5 +57,5 @@ describe('log_gpu', () => { function uploadLogDownload( a: Float32Array, rows: number, cols: number): Float32Array { const arr = Array2D.new([rows, cols], a); - return unaryop_gpu_test.uploadUnaryDownload(arr, UnaryOp.LOG); + return unaryop_gpu_test.uploadUnaryDownload(arr, unaryop_gpu.LOG); } diff --git a/src/math/webgl/neg_gpu_test.ts b/src/math/webgl/neg_gpu_test.ts index 9ee45754cf..59ad24b377 100644 --- a/src/math/webgl/neg_gpu_test.ts +++ b/src/math/webgl/neg_gpu_test.ts @@ -16,10 +16,11 @@ */ import * as test_util from '../../test_util'; -import {UnaryOp} from './unaryop_gpu'; -import * as unaryop_gpu_test from './unaryop_gpu_test'; import {Array2D} from '../ndarray'; +import * as unaryop_gpu from './unaryop_gpu'; +import * as unaryop_gpu_test from './unaryop_gpu_test'; + describe('neg_gpu', () => { it('returns a matrix with the same shape as the input matrix', () => { const a = new Float32Array(28 * 32); @@ -56,5 +57,5 @@ describe('neg_gpu', () => { function uploadNegDownload( a: Float32Array, rows: number, cols: number): Float32Array { const arr = Array2D.new([rows, cols], a); - return unaryop_gpu_test.uploadUnaryDownload(arr, UnaryOp.NEG); + return unaryop_gpu_test.uploadUnaryDownload(arr, unaryop_gpu.NEG); } diff --git a/src/math/webgl/relu_gpu_test.ts b/src/math/webgl/relu_gpu_test.ts index f7e224471f..b76c39d15c 100644 --- a/src/math/webgl/relu_gpu_test.ts +++ b/src/math/webgl/relu_gpu_test.ts @@ -16,9 +16,10 @@ */ import * as test_util from '../../test_util'; -import {UnaryOp} from './unaryop_gpu'; +import {Array1D, Array2D, Array3D, NDArray, Scalar} from '../ndarray'; + +import * as unaryop_gpu from './unaryop_gpu'; import * as unaryop_gpu_test from './unaryop_gpu_test'; -import {Array2D, Array1D, NDArray, Scalar, Array3D} from '../ndarray'; describe('relu_gpu', () => { it('returns a matrix with the shape of the input matrix', () => { @@ -46,7 +47,7 @@ describe('relu_gpu', () => { }); it('operates on multiple values', () => { - const a = Array2D.new([3, 3], [[-1, 2, -3], [4, -5, 6], [-7, 8, -9]]); + const a = Array2D.new([3, 3], [[-1, 2, -3], [4, -5, 6], [-7, 8, -9]]); const result = uploadReluDownload(a); test_util.expectArraysClose( result, new Float32Array([0, 2, 0, 4, 0, 6, 0, 8, 0]), 0.0001); @@ -61,5 +62,5 @@ describe('relu_gpu', () => { }); function uploadReluDownload(a: NDArray): Float32Array { - return unaryop_gpu_test.uploadUnaryDownload(a, UnaryOp.RELU); + return unaryop_gpu_test.uploadUnaryDownload(a, unaryop_gpu.RELU); } diff --git a/src/math/webgl/sigmoid_gpu_test.ts b/src/math/webgl/sigmoid_gpu_test.ts index 82b4166031..e9a23358aa 100644 --- a/src/math/webgl/sigmoid_gpu_test.ts +++ b/src/math/webgl/sigmoid_gpu_test.ts @@ -16,10 +16,11 @@ */ import * as test_util from '../../test_util'; -import {UnaryOp} from './unaryop_gpu'; -import * as unaryop_gpu_test from './unaryop_gpu_test'; import {Array2D} from '../ndarray'; +import * as unaryop_gpu from './unaryop_gpu'; +import * as unaryop_gpu_test from './unaryop_gpu_test'; + describe('sigmoid_gpu', () => { it('returns a matrix with the same shape as the input matrix', () => { const a = new Float32Array(28 * 32); @@ -43,5 +44,5 @@ describe('sigmoid_gpu', () => { function uploadSigmoidDownload( a: Float32Array, rows: number, cols: number): Float32Array { const arr = Array2D.new([rows, cols], a); - return unaryop_gpu_test.uploadUnaryDownload(arr, UnaryOp.SIGMOID); + return unaryop_gpu_test.uploadUnaryDownload(arr, unaryop_gpu.SIGMOID); } diff --git a/src/math/webgl/step_gpu_test.ts b/src/math/webgl/step_gpu_test.ts index 70301a4856..cfc7715a7c 100644 --- a/src/math/webgl/step_gpu_test.ts +++ b/src/math/webgl/step_gpu_test.ts @@ -16,9 +16,10 @@ */ import * as test_util from '../../test_util'; -import {UnaryOp} from './unaryop_gpu'; +import {Array1D, Array2D, Array3D, NDArray, Scalar} from '../ndarray'; + +import * as unary_op from './unaryop_gpu'; import * as unaryop_gpu_test from './unaryop_gpu_test'; -import {NDArray, Array2D, Array1D, Array3D, Scalar} from '../ndarray'; describe('step_gpu', () => { it('returns a matrix with the shape of the input matrix', () => { @@ -70,5 +71,5 @@ describe('step_gpu', () => { }); function uploadStepDownload(a: NDArray): Float32Array { - return unaryop_gpu_test.uploadUnaryDownload(a, UnaryOp.STEP); + return unaryop_gpu_test.uploadUnaryDownload(a, unary_op.STEP); } diff --git a/src/math/webgl/trig_gpu_test.ts b/src/math/webgl/trig_gpu_test.ts index db6dc5f538..1315ea219f 100644 --- a/src/math/webgl/trig_gpu_test.ts +++ b/src/math/webgl/trig_gpu_test.ts @@ -17,14 +17,15 @@ import * as test_util from '../../test_util'; import * as util from '../../util'; -import {UnaryOp} from './unaryop_gpu'; +import {Array1D, Array2D, Array3D, Scalar} from '../ndarray'; + +import * as unaryop_gpu from './unaryop_gpu'; import * as unaryop_gpu_test from './unaryop_gpu_test'; -import {Scalar, Array1D, Array2D, Array3D} from '../ndarray'; describe('sin_gpu', () => { it('returns a matrix with the same shape as the input matrix', () => { const a = Array2D.zeros([28, 28]); - const result = unaryop_gpu_test.uploadUnaryDownload(a, UnaryOp.SIN); + const result = unaryop_gpu_test.uploadUnaryDownload(a, unaryop_gpu.SIN); expect(result.length).toEqual(a.size); }); @@ -36,16 +37,167 @@ describe('sin_gpu', () => { expectedResult[i] = Math.sin(a[i]); } const aArr = Array1D.new(a); - const result = unaryop_gpu_test.uploadUnaryDownload(aArr, UnaryOp.SIN); + const result = unaryop_gpu_test.uploadUnaryDownload(aArr, unaryop_gpu.SIN); test_util.expectArraysClose(result, expectedResult, 1e-3); }); }); +describe('cos_gpu', () => { + it('returns a matrix with the same shape as the input matrix', () => { + const a = Array2D.zeros([28, 28]); + const result = unaryop_gpu_test.uploadUnaryDownload(a, unaryop_gpu.COS); + expect(result.length).toEqual(a.size); + }); + + it('Cos equals CPU', () => { + const size = 10; + const a = test_util.randomArrayInRange(size, -1, 1); + const expectedResult = new Float32Array(size); + for (let i = 0; i < a.length; i++) { + expectedResult[i] = Math.cos(a[i]); + } + const aArr = Array1D.new(a); + const result = unaryop_gpu_test.uploadUnaryDownload(aArr, unaryop_gpu.COS); + test_util.expectArraysClose(result, expectedResult, 1e-3); + }); +}); + +describe('tan_gpu', () => { + it('returns a matrix with the same shape as the input matrix', () => { + const a = Array2D.zeros([28, 28]); + const result = unaryop_gpu_test.uploadUnaryDownload(a, unaryop_gpu.TAN); + expect(result.length).toEqual(a.size); + }); + + it('Tan equals CPU', () => { + const size = 10; + const a = test_util.randomArrayInRange(size, -1, 1); + const expectedResult = new Float32Array(size); + for (let i = 0; i < a.length; i++) { + expectedResult[i] = Math.tan(a[i]); + } + const aArr = Array1D.new(a); + const result = unaryop_gpu_test.uploadUnaryDownload(aArr, unaryop_gpu.TAN); + test_util.expectArraysClose(result, expectedResult, 1e-3); + }); +}); + +describe('asin_gpu', () => { + it('returns a matrix with the same shape as the input matrix', () => { + const a = Array2D.zeros([28, 28]); + const result = unaryop_gpu_test.uploadUnaryDownload(a, unaryop_gpu.ASIN); + expect(result.length).toEqual(a.size); + }); + + it('asin equals CPU', () => { + const size = 10; + const a = test_util.randomArrayInRange(size, -1, 1); + const expectedResult = new Float32Array(size); + for (let i = 0; i < a.length; i++) { + expectedResult[i] = Math.asin(a[i]); + } + const aArr = Array1D.new(a); + const result = unaryop_gpu_test.uploadUnaryDownload(aArr, unaryop_gpu.ASIN); + test_util.expectArraysClose(result, expectedResult, 1e-3); + }); +}); + +describe('acos_gpu', () => { + it('returns a matrix with the same shape as the input matrix', () => { + const a = Array2D.zeros([28, 28]); + const result = unaryop_gpu_test.uploadUnaryDownload(a, unaryop_gpu.ACOS); + expect(result.length).toEqual(a.size); + }); + + it('acos equals CPU', () => { + const size = 10; + const a = test_util.randomArrayInRange(size, -1, 1); + const expectedResult = new Float32Array(size); + for (let i = 0; i < a.length; i++) { + expectedResult[i] = Math.acos(a[i]); + } + const aArr = Array1D.new(a); + const result = unaryop_gpu_test.uploadUnaryDownload(aArr, unaryop_gpu.ACOS); + test_util.expectArraysClose(result, expectedResult, 1e-3); + }); +}); + +describe('atan_gpu', () => { + it('returns a matrix with the same shape as the input matrix', () => { + const a = Array2D.zeros([28, 28]); + const result = unaryop_gpu_test.uploadUnaryDownload(a, unaryop_gpu.ATAN); + expect(result.length).toEqual(a.size); + }); + + it('atan equals CPU', () => { + const size = 10; + const a = test_util.randomArrayInRange(size, -1, 1); + const expectedResult = new Float32Array(size); + for (let i = 0; i < a.length; i++) { + expectedResult[i] = Math.atan(a[i]); + } + const aArr = Array1D.new(a); + const result = unaryop_gpu_test.uploadUnaryDownload(aArr, unaryop_gpu.ATAN); + test_util.expectArraysClose(result, expectedResult, 1e-3); + }); +}); + +describe('sinh_gpu', () => { + it('returns a matrix with the same shape as the input matrix', () => { + const a = Array2D.zeros([28, 28]); + const result = unaryop_gpu_test.uploadUnaryDownload(a, unaryop_gpu.SINH); + expect(result.length).toEqual(a.size); + }); + + it('sinh equals CPU', () => { + const size = 10; + const a = test_util.randomArrayInRange(size, -1, 1); + const expectedResult = new Float32Array(size); + for (let i = 0; i < a.length; i++) { + expectedResult[i] = Math.sinh(a[i]); + } + const aArr = Array1D.new(a); + const result = unaryop_gpu_test.uploadUnaryDownload(aArr, unaryop_gpu.SINH); + test_util.expectArraysClose(result, expectedResult, 1e-3); + }); + + it('sinh(0) = 0', () => { + const a = Scalar.new(0); + const r = unaryop_gpu_test.uploadUnaryDownload(a, unaryop_gpu.SINH); + expect(r).toBeCloseTo(0); + }); +}); + +describe('cosh_gpu', () => { + it('returns a matrix with the same shape as the input matrix', () => { + const a = Array2D.zeros([28, 28]); + const result = unaryop_gpu_test.uploadUnaryDownload(a, unaryop_gpu.COSH); + expect(result.length).toEqual(a.size); + }); + + it('cosh equals CPU', () => { + const size = 10; + const a = test_util.randomArrayInRange(size, -1, 1); + const expectedResult = new Float32Array(size); + for (let i = 0; i < a.length; i++) { + expectedResult[i] = Math.cosh(a[i]); + } + const aArr = Array1D.new(a); + const result = unaryop_gpu_test.uploadUnaryDownload(aArr, unaryop_gpu.COSH); + test_util.expectArraysClose(result, expectedResult, 1e-3); + }); + + it('cosh(0) = 1', () => { + const a = Scalar.new(0); + const r = unaryop_gpu_test.uploadUnaryDownload(a, unaryop_gpu.COSH); + expect(r).toBeCloseTo(1); + }); +}); describe('tanh_gpu', () => { it('returns a matrix with the same shape as the input matrix', () => { const a = Array3D.zeros([28, 14, 2]); - const result = unaryop_gpu_test.uploadUnaryDownload(a, UnaryOp.TANH); + const result = unaryop_gpu_test.uploadUnaryDownload(a, unaryop_gpu.TANH); expect(result.length).toEqual(a.size); }); @@ -57,31 +209,31 @@ describe('tanh_gpu', () => { expectedResult[i] = util.tanh(a[i]); } const aArr = Array2D.new([2, 5], a); - const result = unaryop_gpu_test.uploadUnaryDownload(aArr, UnaryOp.TANH); + const result = unaryop_gpu_test.uploadUnaryDownload(aArr, unaryop_gpu.TANH); test_util.expectArraysClose(result, expectedResult, 1e-6); }); it('overflow', () => { const a = Scalar.new(100); - const r = unaryop_gpu_test.uploadUnaryDownload(a, UnaryOp.TANH); + const r = unaryop_gpu_test.uploadUnaryDownload(a, unaryop_gpu.TANH); expect(r).toBeCloseTo(1); }); it('tanh(0) = 0', () => { const a = Scalar.new(0); - const r = unaryop_gpu_test.uploadUnaryDownload(a, UnaryOp.TANH); + const r = unaryop_gpu_test.uploadUnaryDownload(a, unaryop_gpu.TANH); expect(r).toBeCloseTo(0); }); it('tanh(0.01) is close to 0.01', () => { const a = Scalar.new(0.01); - const r = unaryop_gpu_test.uploadUnaryDownload(a, UnaryOp.TANH); + const r = unaryop_gpu_test.uploadUnaryDownload(a, unaryop_gpu.TANH); expect(r).toBeCloseTo(0.01); }); it('underflow', () => { const a = Scalar.new(-100); - const r = unaryop_gpu_test.uploadUnaryDownload(a, UnaryOp.TANH); + const r = unaryop_gpu_test.uploadUnaryDownload(a, unaryop_gpu.TANH); expect(r).toBeCloseTo(-1); }); }); diff --git a/src/math/webgl/unaryop_gpu.ts b/src/math/webgl/unaryop_gpu.ts index ec82eb646a..720ba2073b 100644 --- a/src/math/webgl/unaryop_gpu.ts +++ b/src/math/webgl/unaryop_gpu.ts @@ -17,60 +17,103 @@ import {GPGPUProgram} from './gpgpu_math'; -export enum UnaryOp { - EXP, LOG, SQRT, NEG, RELU, SIGMOID, STEP, SIN, TANH -} - export class UnaryOpProgram implements GPGPUProgram { variableNames = ['A']; params: Array<{}>; userCode: string; outputShape: number[]; - constructor(aShape: number[], op: UnaryOp) { + constructor(aShape: number[], opSnippet: string) { this.outputShape = aShape; - this.params = [op]; + this.params = [opSnippet]; this.userCode = ` + float unaryOperation(float x) { + ${opSnippet} + } + void main() { - float v = getAAtOutCoords(); - ${getOpSnippet(op)} - setOutput(r); + float x = getAAtOutCoords(); + float y = unaryOperation(x); + + setOutput(y); } `; } } -const CHECK_NAN_SNIPPET = ` - if (isNaN(v)) { - setOutput(v); - return; +export const CHECK_NAN_SNIPPET = ` + if (isNaN(x)) { + return x; } `; -function getOpSnippet(op: UnaryOp) { - switch(op) { - case UnaryOp.EXP: - return 'float r = exp(v);'; - case UnaryOp.LOG: - return 'float r = log(v);'; - case UnaryOp.SQRT: - return CHECK_NAN_SNIPPET + - 'float r = sqrt(v);'; - case UnaryOp.NEG: - return 'float r = -v;'; - case UnaryOp.RELU: - return 'float r = (v < 0.0) ? 0.0 : v;'; - case UnaryOp.SIGMOID: - return 'float r = 1.0 / (1.0 + exp(-1.0 * v));'; - case UnaryOp.STEP: - return 'float r = (v == v) ? (v > 0.0 ? 1.0 : 0.0) : v;'; - case UnaryOp.SIN: - return CHECK_NAN_SNIPPET + - 'float r = sin(v);'; - case UnaryOp.TANH: - return `float e2x = exp(-2.0 * abs(v)); - float r = sign(v) * (1.0 - e2x) / (1.0 + e2x);`; - default: - throw Error('Unrecognized unary op type ' + op); - } -} +export const ABS = ` + return abs(x); +`; + +export const RELU = ` + return (x < 0.0) ? 0.0 : x; +`; + +export const STEP = ` + return (x == x) ? (x > 0.0 ? 1.0 : 0.0) : x; +`; + +export const NEG = ` + return -x; +`; + +export const EXP = ` + return exp(x); +`; + +export const LOG = ` + return log(x); +`; + +export const SQRT = CHECK_NAN_SNIPPET + ` + return sqrt(x); +`; + +export const SIGMOID = ` + return 1.0 / (1.0 + exp(-1.0 * x)); +`; + +export const SIN = CHECK_NAN_SNIPPET + ` + return sin(x); +`; + +export const COS = CHECK_NAN_SNIPPET + ` + return cos(x); +`; + +export const TAN = ` + return tan(x); +`; + +export const ASIN = CHECK_NAN_SNIPPET + ` + return asin(x); +`; + +export const ACOS = CHECK_NAN_SNIPPET + ` + return acos(x); +`; + +export const ATAN = CHECK_NAN_SNIPPET + ` + return atan(x); +`; + +export const SINH = ` + float e2x = exp(x); + return (e2x - 1.0 / e2x) / 2.0; +`; + +export const COSH = ` + float e2x = exp(-x); + return (e2x + 1.0 / e2x) / 2.0; +`; + +export const TANH = ` + float e2x = exp(-2.0 * abs(x)); + return sign(x) * (1.0 - e2x) / (1.0 + e2x); +`; diff --git a/src/math/webgl/unaryop_gpu_test.ts b/src/math/webgl/unaryop_gpu_test.ts index 0fd163f8c2..e1a3537c40 100644 --- a/src/math/webgl/unaryop_gpu_test.ts +++ b/src/math/webgl/unaryop_gpu_test.ts @@ -15,18 +15,20 @@ * ============================================================================= */ -import {NDArray, initializeGPU, Array2D} from '../ndarray'; -import {UnaryOp, UnaryOpProgram} from './unaryop_gpu'; -import {TextureManager} from './texture_manager'; +import {Array2D, initializeGPU, NDArray} from '../ndarray'; + import {GPGPUContext} from './gpgpu_context'; import * as gpgpu_math from './gpgpu_math'; +import {TextureManager} from './texture_manager'; +import {UnaryOpProgram} from './unaryop_gpu'; -export function uploadUnaryDownload(a: NDArray, op: UnaryOp): Float32Array { +export function uploadUnaryDownload( + a: NDArray, opSnippet: string): Float32Array { const gpgpu = new GPGPUContext(); const textureManager = new TextureManager(gpgpu); initializeGPU(gpgpu, textureManager); const out = Array2D.zerosLike(a); - const program = new UnaryOpProgram(a.shape, op); + const program = new UnaryOpProgram(a.shape, opSnippet); const binary = gpgpu_math.compileProgram(gpgpu, program, [a], out); gpgpu_math.runProgram(binary, [a], out); const result = out.getValues(); diff --git a/src/math/webgl/webgl_util.ts b/src/math/webgl/webgl_util.ts index 0a8c1e80d8..8c9510b5f0 100644 --- a/src/math/webgl/webgl_util.ts +++ b/src/math/webgl/webgl_util.ts @@ -69,9 +69,10 @@ export function isWebGL2Enabled() { if (gl != null) { WEBGL2_ENABLED = true; - const loseContextExtension = getExtensionOrThrow( - gl as WebGLRenderingContext, - 'WEBGL_lose_context') as WebGLLoseContextExtension; + const loseContextExtension = + getExtensionOrThrow( + gl as WebGLRenderingContext, 'WEBGL_lose_context') as + WebGLLoseContextExtension; loseContextExtension.loseContext(); } else { WEBGL2_ENABLED = false; @@ -87,10 +88,9 @@ export function createWebGLRenderingContextFromCanvas( if (isWebGL2Enabled()) { gl = canvas.getContext('webgl2', attributes) as WebGLRenderingContext; } else { - gl = - (canvas.getContext('webgl', attributes) || - canvas.getContext( - 'experimental-webgl', attributes)) as WebGLRenderingContext; + gl = (canvas.getContext('webgl', attributes) || + canvas.getContext('experimental-webgl', attributes)) as + WebGLRenderingContext; } if (gl == null) { @@ -171,13 +171,39 @@ export function createFragmentShader( callAndCheck(gl, () => gl.shaderSource(fragmentShader, fragmentShaderSource)); callAndCheck(gl, () => gl.compileShader(fragmentShader)); if (gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS) === false) { - console.log(fragmentShaderSource); - console.log(gl.getShaderInfoLog(fragmentShader)); + logShaderSourceAndInfoLog( + fragmentShaderSource, gl.getShaderInfoLog(fragmentShader)); throw new Error('Failed to compile fragment shader.'); } return fragmentShader; } +const lineNumberRegex = /^ERROR: [0-9]+:([0-9]+):/g; +function logShaderSourceAndInfoLog( + shaderSource: string, shaderInfoLog: string) { + const lineNumber = +lineNumberRegex.exec(shaderInfoLog)[1]; + + const shaderLines = shaderSource.split('\n'); + const pad = ('' + shaderLines.length).length + 2; + const linesWithLineNumbers = shaderLines.map( + (line, lineNumber) => util.rightPad('' + (lineNumber + 1), pad) + line); + let maxLineLength = 0; + for (let i = 0; i < linesWithLineNumbers.length; i++) { + maxLineLength = Math.max(linesWithLineNumbers[i].length, maxLineLength); + } + + const beforeErrorLines = linesWithLineNumbers.slice(0, lineNumber - 1); + const errorLine = linesWithLineNumbers.slice(lineNumber - 1, lineNumber); + const afterErrorLines = linesWithLineNumbers.slice(lineNumber); + + console.log(beforeErrorLines.join('\n')); + console.log(shaderInfoLog.split('\n')[0]); + console.log( + `%c ${util.rightPad(errorLine[0], maxLineLength)}`, + 'border:1px solid red; background-color:#e3d2d2; color:#a61717'); + console.log(afterErrorLines.join('\n')); +} + export function createProgram(gl: WebGLRenderingContext): WebGLProgram { return throwIfNull( gl, () => gl.createProgram(), 'Unable to create WebGLProgram.');