From 42ce80c9b3876798bbe70e0f50c3652505e275c3 Mon Sep 17 00:00:00 2001 From: Dirk Toewe Date: Fri, 18 Oct 2019 18:43:54 +0200 Subject: [PATCH 1/4] Made norm underflow-safe + tests --- tfjs-core/src/ops/norm.ts | 23 +- tfjs-core/src/ops/norm_test.ts | 422 +++++++++++++++++++++++++++++++++ tfjs-core/src/tests.ts | 1 + 3 files changed, 442 insertions(+), 4 deletions(-) create mode 100644 tfjs-core/src/ops/norm_test.ts diff --git a/tfjs-core/src/ops/norm.ts b/tfjs-core/src/ops/norm.ts index e65b7254c7d..e6a011fd66d 100644 --- a/tfjs-core/src/ops/norm.ts +++ b/tfjs-core/src/ops/norm.ts @@ -15,14 +15,17 @@ * ============================================================================= */ +import {ENGINE} from '../engine'; import {Tensor} from '../tensor'; import {convertToTensor} from '../tensor_util_env'; import {TensorLike} from '../types'; import {parseAxisParam} from '../util'; import * as axis_util from './axis_util'; +import {div} from './binary_ops'; +import {where} from './logical_ops'; import {op} from './operation'; -import {scalar} from './tensor_ops'; +import {ones, scalar} from './tensor_ops'; /** * Computes the norm of scalar, vectors, and matrices. @@ -43,7 +46,7 @@ import {scalar} from './tensor_ops'; * | ord | norm for matrices | norm for vectors * |------------|---------------------------|--------------------- * |'euclidean' |Frobenius norm |2-norm - * |'fro' |Frobenius norm | + * |'fro' |Frobenius norm | * |Infinity |max(sum(abs(x), axis=1)) |max(abs(x)) * |-Infinity |min(sum(abs(x), axis=1)) |min(abs(x)) * |1 |max(sum(abs(x), axis=0)) |sum(abs(x)) @@ -86,6 +89,18 @@ function normImpl( return normImpl(x.reshape([-1]), p, axis); } + const froNorm = () => { + const xAbs = x.abs(), + xMax = ENGINE.tidy(() => { + const xMax = xAbs.max(axis, /*keepDims=*/true), + one = ones(xMax.shape); + return where( xMax.equal(0), one, xMax ); + }); // <- div. by largest absolute value prevents under- & overflow + + const unscaled = div(xAbs,xMax).pow(scalar(2, 'int32')).sum(axis).sqrt(); + return unscaled.mul( xMax.reshape(unscaled.shape) ); + }; + // vector if (x.rank === 1 || typeof axis === 'number' || Array.isArray(axis) && axis.length === 1) { @@ -100,7 +115,7 @@ function normImpl( } if (p === 'euclidean' || p === 2) { // norm(x, 2) = sum(abs(xi) ^ 2) ^ 1/2 - return x.abs().pow(scalar(2, 'int32')).sum(axis).sqrt(); + return froNorm(); } throw new Error(`Error in norm: invalid ord value: ${p}`); @@ -119,7 +134,7 @@ function normImpl( } if (p === 'fro' || p === 'euclidean') { // norm(x) = sqrt(sum(pow(x, 2))) - return x.square().sum(axis).sqrt(); + return froNorm(); } throw new Error(`Error in norm: invalid ord value: ${p}`); diff --git a/tfjs-core/src/ops/norm_test.ts b/tfjs-core/src/ops/norm_test.ts new file mode 100644 index 00000000000..242c927e727 --- /dev/null +++ b/tfjs-core/src/ops/norm_test.ts @@ -0,0 +1,422 @@ +/** + * @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 {ENGINE} from '../engine'; +import {ALL_ENVS, describeWithFlags} from '../jasmine_util'; +import {expectArraysClose/*, expectArraysEqual*/} from '../test_util'; +import {norm} from './norm'; + +describeWithFlags('norm', ALL_ENVS, () => { + it('computes Euclidean norm for 1D tensors correctly', async () => { + const vectors = [ + [13.37], + [2, 3], + [7.1, 3.3, 13.7], + [9.1, 17, 0.1, 0.0, 8.7], + [0], + [0,0], + [0,0,0] + ]; + + for( const p of ['euclidean', 2] as Array<'euclidean' | 2> ) { + for( const vec of vectors ) { + for( const axis of [undefined, 0, [0]] ) { + const ref = Math.hypot(...vec); + + for( const keepDims of [undefined,false] ) { + const n = norm(vec, p, axis, keepDims); + expectArraysClose(await n.array(), ref); + } + + const n = norm(vec, p, axis, true); + expectArraysClose(await n.array(), [ref]); + }}} + }); + + it('computes Euclidean norm for 2D tensors correctly', async () => { + const matrices = [ + [[0]], + + [[13.37]], + + [[4.2, 13.37]], + + [[ 4.2 ], + [13.37]], + + [[0.0, 0.0], + [0.0, 4.2]] + ]; + + for( const p of ['euclidean', 2] as Array<'euclidean' | 2> ) { + for( const mat of matrices ) { + for( const axis of [-1,1,[-1],[1]] ) { + const ref = mat.map( row => Math.hypot(...row) ); + + for( const keepDims of [undefined,false] ) { + const n = norm(mat, p, axis, keepDims); + expectArraysClose(await n.array(), ref); + } + + const n = norm(mat, p, axis, true); + expectArraysClose( + await n.array(), + ref.map(x => [x]) + ); + } + + for( const axis of [-2,0,[-2],[0]] ) { + const ref = mat.reduce( + (norm,row) => norm.map((x,i) => Math.hypot(x,row[i])), + /*init=*/mat[0].map(() => 0) + ); + + for( const keepDims of [undefined,false] ) { + const n = norm(mat, p, axis, keepDims); + expectArraysClose(await n.array(), ref); + } + + const n = norm(mat, p, axis, true); + expectArraysClose(await n.array(), [ref]); + } + }} + }); + + it('computes Euclidean norm for 3D tensors correctly', async () => { + const arrays = [ + [[[0.0, 7.7, 0.0], + [3.8, 1.3, 3.2]], + [[4.7, 6.1, 1.6], + [9.9, 2.5, 8.3]]], + + [[[0.0, 7.7], + [0.0, 0.0], + [0.0, 3.2]], + [[4.7, 6.1], + [0.0, 0.0], + [2.5, 8.3]]], + + [[[ 0.0 ]], + [[13.37]]], + + [[[ 0.0, 0.0 ]], + [[13.37, 0.0 ]]] + ]; + + for( const p of ['euclidean', 2] as Array<'euclidean' | 2> ) { + for( const arr of arrays ) { + for( const axis of [-1,2,[-1],[2]] ) { + const ref = arr.map( mat => + mat.map( row => Math.hypot(...row) )); + + for( const keepDims of [undefined,false] ) { + const n = norm(arr, p, axis, keepDims); + expectArraysClose(await n.array(), ref); + } + + const n = norm(arr, p, axis, true); + expectArraysClose( + await n.array(), + arr.map( mat => + mat.map( row => [Math.hypot(...row)] )) + ); + } + + for( const axis of [-2,1,[-2],[1]] ) + { + const ref = arr.map( mat => mat.reduce( + (norm,row) => norm.map((x,i) => Math.hypot(x,row[i])), + /*init=*/mat[0].map(() => 0) + )); + + for( const keepDims of [undefined,false] ) { + const n = norm(arr, p, axis, keepDims); + expectArraysClose(await n.array(), ref); + } + + const n = norm(arr, p, axis, true); + expectArraysClose( await n.array(), ref.map(x => [x]) ); + } + + for( const axis of [-3,0,[-3],[0]] ) + { + const ref = arr.reduce( + (nrm,mat) => nrm.map( (row,i) => + row.map( (nij,j) => Math.hypot(nij,mat[i][j]) )), + /*init=*/arr[0].map( row => row.map(() => 0) ) + ); + + for( const keepDims of [undefined,false] ) { + const n = norm(arr, p, axis, keepDims); + expectArraysClose(await n.array(), ref); + } + + const n = norm(arr, p, axis, true); + expectArraysClose( await n.array(), [ref]); + } + }} + }); + + it('computes Frobenius norm for 2D tensors correctly', async () => { + const matrices = [ + [[0]], + + [[13.37]], + + [[4.2, 13.37]], + + [[ 4.2 ], + [13.37]], + + [[0.0, 0.0], + [0.0, 4.2]] + ]; + + for( const p of ['euclidean', 'fro'] as Array<'euclidean' | 'fro'> ) { + for( const mat of matrices ) { + const axeBodySpray = [[ 0, 1], + [ 0,-1], + [-2, 1], + [-2,-1], + [ 1, 0], + [-1, 0], + [ 1,-2], + [-1,-2]]; + if( p === 'euclidean' ) { + axeBodySpray.push(null); + } + for( const axes of axeBodySpray ) { + const ref = Math.hypot(...Array.from( + function*(){ + for( const row of mat ) { + yield* row; + } + }() + )); + + for( const keepDims of [undefined,false] ) { + const n = norm(mat, p, axes, keepDims); + expectArraysClose(await n.array(), ref); + } + + const n = norm(mat, p, axes, true); + expectArraysClose(await n.array(), [[ref]]); + } + }} + }); + + it('computes Frobenius norm for 3D tensors correctly', async () => { + const arrays = [ + [[[0.0, 7.7, 0.0], + [3.8, 1.3, 3.2]], + [[4.7, 6.1, 1.6], + [9.9, 2.5, 8.3]]], + + [[[0.0, 7.7], + [0.0, 0.0], + [0.0, 3.2]], + [[4.7, 6.1], + [0.0, 0.0], + [2.5, 8.3]]], + + [[[ 0.0 ]], + [[13.37]]], + + [[[ 0.0, 0.0 ]], + [[13.37, 0.0 ]]] + ]; + + for( const p of ['euclidean', 'fro'] as Array<'euclidean' | 'fro'> ) { + for( const arr of arrays ) { + for( const axes of [[ 1, 2], + [-1, 1], + [-2, 2], + [-1,-2]] ) { + const ref = arr.map( mat => Math.hypot(...Array.from( + function*(){ + for( const row of mat ) { + yield *row; + } + }() + )) ); + + for( const keepDims of [undefined,false] ) { + const n = norm(arr, p, axes, keepDims); + expectArraysClose(await n.array(), ref); + } + + const n = norm(arr, p, axes, true); + expectArraysClose( + await n.array(), + ref.map(x => [[x]]) + ); + } + + for( const axes of [[ 0, 2], + [-1, 0], + [-3, 2], + [-1,-3]] ) + { + const ref = Array.from(arr[0], (_,j) => Math.hypot(...Array.from( + function*(){ + for( let i=arr .length; i-- > 0; ) { + for( let k=arr[0][0].length; k-- > 0; ) { + yield arr[i][j][k]; + }} + }() + )) ); + + for( const keepDims of [undefined,false] ) { + const n = norm(arr, p, axes, keepDims); + expectArraysClose(await n.array(), ref); + } + + const n = norm(arr, p, axes, true); + expectArraysClose( + await n.array(), + [ref.map(x => [x])] + ); + } + + for( const axes of [[ 0, 1], + [-2, 0], + [-3, 1], + [-2,-3]] ) + { + const ref = Array.from(arr[0][0], (_,k) => Math.hypot(...Array.from( + function*(){ + for( let i=arr .length; i-- > 0; ) { + for( let j=arr[0].length; j-- > 0; ) { + yield arr[i][j][k]; + }} + }() + )) ); + + for( const keepDims of [undefined,false] ) { + const n = norm(arr, p, axes, keepDims); + expectArraysClose(await n.array(), ref); + } + + const n = norm(arr, p, axes, true); + expectArraysClose(await n.array(), [[ref]]); + } + }} + }); + + it('computes Euclidean norm underflow-safely', async () => { + const small = function(){ + const floatBits = ENGINE.backend.floatPrecision(); + switch(floatBits) { + case 32: return 1e-30; + case 16: return 1e-4; + default: + throw new Error(`Test not implemented for ENV.engine.floatPrecision()=${ + floatBits}.`); + } + }(); + + const tolerance = small / 100; + + const vectors = [ + [ small], + [ 0,small], + [ small, 0], + [ small,small], + [ 0, 0,small], + [ 0,small, 0], + [ 0,small,small], + [small, 0,small], + [small,small, 0], + [small,small,small] + ]; + + for( const p of ['euclidean', 2] as Array<'euclidean' | 2> ) { + for( const vec of vectors ) { + for( const axis of [undefined, 0, [0]] ) { + const ref = Math.hypot(...vec); + expect(ref).toBeGreaterThan(0); + + for( const keepDims of [undefined,false] ) { + const n = norm(vec, p, axis, keepDims); + expectArraysClose(await n.array(), ref, tolerance); + } + + const n = norm(vec, p, axis, true); + expectArraysClose(await n.array(), [ref], tolerance); + }}} + }); + + it('computes Frobenius norm for 2D tensors correctly', async () => { + const small = function(){ + const floatBits = ENGINE.backend.floatPrecision(); + switch(floatBits) { + case 32: return 1e-30; + case 16: return 1e-4; + default: + throw new Error(`Test not implemented for ENV.engine.floatPrecision()=${ + floatBits}.`); + } + }(); + + const tolerance = small / 100; + + const matrices = [ + [[small]], + + [[small, small]], + + [[small], + [small]], + + [[small, 0.0,small], + [ 0.0,small,small]] + ]; + + for( const p of ['euclidean', 'fro'] as Array<'euclidean' | 'fro'> ) { + for( const mat of matrices ) { + const axeBodySpray = [[ 0, 1], + [ 0,-1], + [-2, 1], + [-2,-1], + [ 1, 0], + [-1, 0], + [ 1,-2], + [-1,-2]]; + if( p === 'euclidean' ) { + axeBodySpray.push(null); + } + for( const axes of axeBodySpray ) { + const ref = Math.hypot(...Array.from( + function*(){ + for( const row of mat ) { + yield* row; + } + }() + )); + expect(ref).toBeGreaterThan(0); + + for( const keepDims of [undefined,false] ) { + const n = norm(mat, p, axes, keepDims); + expectArraysClose(await n.array(), ref, tolerance); + } + + const n = norm(mat, p, axes, true); + expectArraysClose(await n.array(), [[ref]], tolerance); + } + }} + }); +}) diff --git a/tfjs-core/src/tests.ts b/tfjs-core/src/tests.ts index c1dde0528d8..3fe68183e07 100644 --- a/tfjs-core/src/tests.ts +++ b/tfjs-core/src/tests.ts @@ -73,6 +73,7 @@ import './ops/lstm_test'; import './ops/matmul_test'; import './ops/moving_average_test'; import './ops/multinomial_test'; +import './ops/norm_test'; import './ops/operation_test'; import './ops/pad_test'; import './ops/pool_test'; From 1c9365090d4fc4f7d8e32fb89d17d6c3b2f66683 Mon Sep 17 00:00:00 2001 From: Dirk Toewe Date: Fri, 18 Oct 2019 18:51:19 +0200 Subject: [PATCH 2/4] Replaced .pow(2) with .square() in norm() --- tfjs-core/src/ops/norm.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tfjs-core/src/ops/norm.ts b/tfjs-core/src/ops/norm.ts index e6a011fd66d..dedf7ad1708 100644 --- a/tfjs-core/src/ops/norm.ts +++ b/tfjs-core/src/ops/norm.ts @@ -25,7 +25,7 @@ import * as axis_util from './axis_util'; import {div} from './binary_ops'; import {where} from './logical_ops'; import {op} from './operation'; -import {ones, scalar} from './tensor_ops'; +import {ones} from './tensor_ops'; /** * Computes the norm of scalar, vectors, and matrices. @@ -97,7 +97,7 @@ function normImpl( return where( xMax.equal(0), one, xMax ); }); // <- div. by largest absolute value prevents under- & overflow - const unscaled = div(xAbs,xMax).pow(scalar(2, 'int32')).sum(axis).sqrt(); + const unscaled = div(xAbs,xMax).square().sum(axis).sqrt(); return unscaled.mul( xMax.reshape(unscaled.shape) ); }; From d3cb5aa775209042c6a77ea39ae9db7a7ecb4274 Mon Sep 17 00:00:00 2001 From: Dirk Toewe Date: Fri, 18 Oct 2019 19:01:57 +0200 Subject: [PATCH 3/4] Fixed linting. --- tfjs-core/src/ops/norm_test.ts | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/tfjs-core/src/ops/norm_test.ts b/tfjs-core/src/ops/norm_test.ts index 242c927e727..f9ffd04b1fb 100644 --- a/tfjs-core/src/ops/norm_test.ts +++ b/tfjs-core/src/ops/norm_test.ts @@ -318,16 +318,18 @@ describeWithFlags('norm', ALL_ENVS, () => { }); it('computes Euclidean norm underflow-safely', async () => { - const small = function(){ + const small = (() => { const floatBits = ENGINE.backend.floatPrecision(); switch(floatBits) { case 32: return 1e-30; case 16: return 1e-4; default: - throw new Error(`Test not implemented for ENV.engine.floatPrecision()=${ - floatBits}.`); + throw new Error( + 'Test not implemented for ' + + `ENGINE.backend.floatPrecision()=${floatBits}.` + ); } - }(); + })(); const tolerance = small / 100; @@ -361,16 +363,18 @@ describeWithFlags('norm', ALL_ENVS, () => { }); it('computes Frobenius norm for 2D tensors correctly', async () => { - const small = function(){ + const small = (() => { const floatBits = ENGINE.backend.floatPrecision(); switch(floatBits) { case 32: return 1e-30; case 16: return 1e-4; default: - throw new Error(`Test not implemented for ENV.engine.floatPrecision()=${ - floatBits}.`); + throw new Error( + 'Test not implemented for ' + + `ENGINE.backend.floatPrecision()=${floatBits}.` + ); } - }(); + })(); const tolerance = small / 100; @@ -419,4 +423,4 @@ describeWithFlags('norm', ALL_ENVS, () => { } }} }); -}) +}); From 07f1460aa7d8002daadd2e7804ccc390ccc10e49 Mon Sep 17 00:00:00 2001 From: Dirk Toewe Date: Fri, 18 Oct 2019 20:15:00 +0200 Subject: [PATCH 4/4] Reduces number of test to avoid timeout issues --- tfjs-core/src/ops/norm_test.ts | 199 +++++++++++++++------------------ 1 file changed, 88 insertions(+), 111 deletions(-) diff --git a/tfjs-core/src/ops/norm_test.ts b/tfjs-core/src/ops/norm_test.ts index f9ffd04b1fb..2c034f92ed8 100644 --- a/tfjs-core/src/ops/norm_test.ts +++ b/tfjs-core/src/ops/norm_test.ts @@ -23,38 +23,35 @@ import {norm} from './norm'; describeWithFlags('norm', ALL_ENVS, () => { it('computes Euclidean norm for 1D tensors correctly', async () => { const vectors = [ - [13.37], - [2, 3], - [7.1, 3.3, 13.7], - [9.1, 17, 0.1, 0.0, 8.7], + [3.7], + [4.2, 80.085, 13.37], [0], - [0,0], - [0,0,0] + [0,0] ]; + Object.freeze(vectors); - for( const p of ['euclidean', 2] as Array<'euclidean' | 2> ) { - for( const vec of vectors ) { - for( const axis of [undefined, 0, [0]] ) { + for( const vec of vectors ) + { const ref = Math.hypot(...vec); + Object.freeze(ref); - for( const keepDims of [undefined,false] ) { - const n = norm(vec, p, axis, keepDims); - expectArraysClose(await n.array(), ref); - } + for( const p of ['euclidean', 2] as Array<'euclidean' | 2> ) { + for( const axis of [undefined, 0, [0]] ) { + for( const keepDims of [undefined,false] ) { + const n = norm(vec, p, axis, keepDims); + expectArraysClose(await n.array(), ref); + } - const n = norm(vec, p, axis, true); - expectArraysClose(await n.array(), [ref]); - }}} + const n = norm(vec, p, axis, true); + expectArraysClose(await n.array(), [ref]); + }} + } }); it('computes Euclidean norm for 2D tensors correctly', async () => { const matrices = [ [[0]], - [[13.37]], - - [[4.2, 13.37]], - [[ 4.2 ], [13.37]], @@ -64,34 +61,40 @@ describeWithFlags('norm', ALL_ENVS, () => { for( const p of ['euclidean', 2] as Array<'euclidean' | 2> ) { for( const mat of matrices ) { - for( const axis of [-1,1,[-1],[1]] ) { + { const ref = mat.map( row => Math.hypot(...row) ); - - for( const keepDims of [undefined,false] ) { - const n = norm(mat, p, axis, keepDims); - expectArraysClose(await n.array(), ref); + Object.freeze(ref); + + for( const axis of [1,[-1]] ) { + for( const keepDims of [undefined,false] ) { + const n = norm(mat, p, axis, keepDims); + expectArraysClose(await n.array(), ref); + } + + const n = norm(mat, p, axis, true); + expectArraysClose( + await n.array(), + ref.map(x => [x]) + ); } - - const n = norm(mat, p, axis, true); - expectArraysClose( - await n.array(), - ref.map(x => [x]) - ); } - for( const axis of [-2,0,[-2],[0]] ) { + { const ref = mat.reduce( (norm,row) => norm.map((x,i) => Math.hypot(x,row[i])), /*init=*/mat[0].map(() => 0) ); + Object.freeze(ref); - for( const keepDims of [undefined,false] ) { - const n = norm(mat, p, axis, keepDims); - expectArraysClose(await n.array(), ref); - } + for( const axis of [-2,[0]] ) { + for( const keepDims of [undefined,false] ) { + const n = norm(mat, p, axis, keepDims); + expectArraysClose(await n.array(), ref); + } - const n = norm(mat, p, axis, true); - expectArraysClose(await n.array(), [ref]); + const n = norm(mat, p, axis, true); + expectArraysClose(await n.array(), [ref]); + } } }} }); @@ -108,65 +111,68 @@ describeWithFlags('norm', ALL_ENVS, () => { [0.0, 3.2]], [[4.7, 6.1], [0.0, 0.0], - [2.5, 8.3]]], - - [[[ 0.0 ]], - [[13.37]]], - - [[[ 0.0, 0.0 ]], - [[13.37, 0.0 ]]] + [2.5, 8.3]]] ]; for( const p of ['euclidean', 2] as Array<'euclidean' | 2> ) { for( const arr of arrays ) { - for( const axis of [-1,2,[-1],[2]] ) { + { const ref = arr.map( mat => mat.map( row => Math.hypot(...row) )); - - for( const keepDims of [undefined,false] ) { - const n = norm(arr, p, axis, keepDims); - expectArraysClose(await n.array(), ref); + Object.freeze(ref); + + for( const axis of [2,[-1]] ) { + for( const keepDims of [undefined,false] ) { + const n = norm(arr, p, axis, keepDims); + expectArraysClose(await n.array(), ref); + } + + const n = norm(arr, p, axis, true); + expectArraysClose( + await n.array(), + arr.map( mat => + mat.map( row => [Math.hypot(...row)] )) + ); } - - const n = norm(arr, p, axis, true); - expectArraysClose( - await n.array(), - arr.map( mat => - mat.map( row => [Math.hypot(...row)] )) - ); } - for( const axis of [-2,1,[-2],[1]] ) { const ref = arr.map( mat => mat.reduce( (norm,row) => norm.map((x,i) => Math.hypot(x,row[i])), /*init=*/mat[0].map(() => 0) )); + Object.freeze(ref); - for( const keepDims of [undefined,false] ) { - const n = norm(arr, p, axis, keepDims); - expectArraysClose(await n.array(), ref); - } + for( const axis of [-2,[1]] ) + { + for( const keepDims of [undefined,false] ) { + const n = norm(arr, p, axis, keepDims); + expectArraysClose(await n.array(), ref); + } - const n = norm(arr, p, axis, true); - expectArraysClose( await n.array(), ref.map(x => [x]) ); + const n = norm(arr, p, axis, true); + expectArraysClose( await n.array(), ref.map(x => [x]) ); + } } - for( const axis of [-3,0,[-3],[0]] ) { const ref = arr.reduce( (nrm,mat) => nrm.map( (row,i) => row.map( (nij,j) => Math.hypot(nij,mat[i][j]) )), /*init=*/arr[0].map( row => row.map(() => 0) ) ); + Object.freeze(ref); - for( const keepDims of [undefined,false] ) { - const n = norm(arr, p, axis, keepDims); - expectArraysClose(await n.array(), ref); - } + for( const axis of [0,[-3]] ) + { + for( const keepDims of [undefined,false] ) { + const n = norm(arr, p, axis, keepDims); + expectArraysClose(await n.array(), ref); + } - const n = norm(arr, p, axis, true); - expectArraysClose( await n.array(), [ref]); + const n = norm(arr, p, axis, true); + expectArraysClose( await n.array(), [ref]); + } } }} }); @@ -175,10 +181,6 @@ describeWithFlags('norm', ALL_ENVS, () => { const matrices = [ [[0]], - [[13.37]], - - [[4.2, 13.37]], - [[ 4.2 ], [13.37]], @@ -189,12 +191,8 @@ describeWithFlags('norm', ALL_ENVS, () => { for( const p of ['euclidean', 'fro'] as Array<'euclidean' | 'fro'> ) { for( const mat of matrices ) { const axeBodySpray = [[ 0, 1], - [ 0,-1], [-2, 1], - [-2,-1], - [ 1, 0], [-1, 0], - [ 1,-2], [-1,-2]]; if( p === 'euclidean' ) { axeBodySpray.push(null); @@ -231,20 +229,12 @@ describeWithFlags('norm', ALL_ENVS, () => { [0.0, 3.2]], [[4.7, 6.1], [0.0, 0.0], - [2.5, 8.3]]], - - [[[ 0.0 ]], - [[13.37]]], - - [[[ 0.0, 0.0 ]], - [[13.37, 0.0 ]]] + [2.5, 8.3]]] ]; for( const p of ['euclidean', 'fro'] as Array<'euclidean' | 'fro'> ) { for( const arr of arrays ) { for( const axes of [[ 1, 2], - [-1, 1], - [-2, 2], [-1,-2]] ) { const ref = arr.map( mat => Math.hypot(...Array.from( function*(){ @@ -266,9 +256,7 @@ describeWithFlags('norm', ALL_ENVS, () => { ); } - for( const axes of [[ 0, 2], - [-1, 0], - [-3, 2], + for( const axes of [[ 2, 0], [-1,-3]] ) { const ref = Array.from(arr[0], (_,j) => Math.hypot(...Array.from( @@ -292,10 +280,8 @@ describeWithFlags('norm', ALL_ENVS, () => { ); } - for( const axes of [[ 0, 1], - [-2, 0], - [-3, 1], - [-2,-3]] ) + for( const axes of [[ 0,-2], + [ 1,-3]] ) { const ref = Array.from(arr[0][0], (_,k) => Math.hypot(...Array.from( function*(){ @@ -334,16 +320,13 @@ describeWithFlags('norm', ALL_ENVS, () => { const tolerance = small / 100; const vectors = [ - [ small], - [ 0,small], - [ small, 0], - [ small,small], - [ 0, 0,small], - [ 0,small, 0], - [ 0,small,small], - [small, 0,small], - [small,small, 0], - [small,small,small] + [ small], + [ 0,small], + [ small, 0], + [ small,small], + [0, 0,small], + [0,small, 0], + [0,small,small] ]; for( const p of ['euclidean', 2] as Array<'euclidean' | 2> ) { @@ -381,8 +364,6 @@ describeWithFlags('norm', ALL_ENVS, () => { const matrices = [ [[small]], - [[small, small]], - [[small], [small]], @@ -393,12 +374,8 @@ describeWithFlags('norm', ALL_ENVS, () => { for( const p of ['euclidean', 'fro'] as Array<'euclidean' | 'fro'> ) { for( const mat of matrices ) { const axeBodySpray = [[ 0, 1], - [ 0,-1], [-2, 1], - [-2,-1], - [ 1, 0], [-1, 0], - [ 1,-2], [-1,-2]]; if( p === 'euclidean' ) { axeBodySpray.push(null);