-
Notifications
You must be signed in to change notification settings - Fork 12
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
6 changed files
with
173 additions
and
26 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
export type Vector = [number, number, number] | ||
export type NdVector = number[] | ||
|
||
export function norm(vec: NdVector): number { | ||
return Math.sqrt(vec.reduce((acc, val) => acc + val ** 2, 0)) | ||
} | ||
|
||
export function scale(vec: NdVector, factor: number): NdVector { | ||
return vec.map((val) => val * factor) | ||
} | ||
|
||
export function euclidean_dist(vec1: Vector, vec2: Vector): number { | ||
return norm(add(vec1, scale(vec2, -1))) | ||
} | ||
|
||
export function add(...vecs: NdVector[]): NdVector { | ||
// add up any number of same-length vectors | ||
const result = vecs[0].slice() | ||
for (const vec of vecs.slice(1)) { | ||
for (const [idx, val] of vec.entries()) { | ||
result[idx] += val | ||
} | ||
} | ||
return result | ||
} | ||
|
||
export function dot(x1: NdVector, x2: NdVector): number | number[] { | ||
// Handle the case where both inputs are scalars | ||
if (typeof x1 === `number` && typeof x2 === `number`) { | ||
return x1 * x2 | ||
} | ||
|
||
// Handle the case where one input is a scalar and the other is a vector | ||
if (typeof x1 === `number` && Array.isArray(x2)) { | ||
throw new Error(`Scalar and vector multiplication is not supported`) | ||
} | ||
if (Array.isArray(x1) && typeof x2 === `number`) { | ||
throw new Error(`Vector and scalar multiplication is not supported`) | ||
} | ||
|
||
// At this point, we know that both inputs are arrays | ||
const vec1 = x1 as number[] | ||
const vec2 = x2 as number[] | ||
|
||
// Handle the case where both inputs are vectors | ||
if (!Array.isArray(vec1[0]) && !Array.isArray(vec2[0])) { | ||
if (vec1.length !== vec2.length) { | ||
throw new Error(`Vectors must be of same length`) | ||
} | ||
return vec1.reduce((sum, val, index) => sum + val * vec2[index], 0) | ||
} | ||
|
||
// Handle the case where the first input is a matrix and the second is a vector | ||
if (Array.isArray(vec1[0]) && !Array.isArray(vec2[0])) { | ||
const mat1 = vec1 as number[][] | ||
if (mat1[0].length !== vec2.length) { | ||
throw new Error( | ||
`Number of columns in matrix must be equal to number of elements in vector`, | ||
) | ||
} | ||
return mat1.map((row) => | ||
row.reduce((sum, val, index) => sum + val * vec2[index], 0), | ||
) | ||
} | ||
|
||
// Handle the case where both inputs are matrices | ||
if (Array.isArray(vec1[0]) && Array.isArray(vec2[0])) { | ||
const mat1 = vec1 as number[][] | ||
const mat2 = vec2 as number[][] | ||
if (mat1[0].length !== mat2.length) { | ||
throw new Error( | ||
`Number of columns in first matrix must be equal to number of rows in second matrix`, | ||
) | ||
} | ||
return mat1.map((row, i) => | ||
mat2[0].map((_, j) => | ||
row.reduce((sum, _, k) => sum + mat1[i][k] * mat2[k][j], 0), | ||
), | ||
) | ||
} | ||
|
||
// Handle any other cases | ||
throw new Error( | ||
`Unsupported input dimensions. Inputs must be scalars, vectors, or matrices.`, | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
import type { NdVector, Vector } from '$lib' | ||
import { add, dot, euclidean_dist, norm, scale } from '$lib' | ||
import { expect, test } from 'vitest' | ||
|
||
test(`norm of vector`, () => { | ||
const vec: NdVector = [3, 4] | ||
const expected = 5 | ||
expect(norm(vec)).toEqual(expected) | ||
}) | ||
|
||
test(`scale vector`, () => { | ||
const vec: NdVector = [1, 2, 3] | ||
const factor = 3 | ||
const expected: NdVector = [3, 6, 9] | ||
expect(scale(vec, factor)).toEqual(expected) | ||
}) | ||
|
||
test(`euclidean_dist between two vectors/points`, () => { | ||
const vec1: Vector = [1, 2, 3] | ||
const vec2: Vector = [4, 5, 6] | ||
expect(euclidean_dist(vec1, vec2)).toEqual(Math.sqrt(27)) // sqrt((4-1)^2 + (5-2)^2 + (6-3)^2) | ||
}) | ||
|
||
test.each([ | ||
[ | ||
[1, 2], | ||
[3, 4], | ||
[4, 6], | ||
], | ||
[ | ||
[1, 2, 3], | ||
[4, 5, 6], | ||
[5, 7, 9], | ||
], | ||
[ | ||
[1, 2, 3, 4, 5, 6], | ||
[7, 8, 9, 10, 11, 12], | ||
[8, 10, 12, 14, 16, 18], | ||
], | ||
])(`add`, (vec1, vec2, expected) => { | ||
expect(add(vec1, vec2)).toEqual(expected) | ||
// test more than 2 inputs and self-consistency (of add, scale, and norm) | ||
expect(norm(add(vec1, vec2, scale(expected, -1)))).toEqual(0) | ||
}) | ||
|
||
test.each([ | ||
[[1, 2], [3, 4], 11], | ||
[[1, 2, 3], [4, 5, 6], 32], | ||
])(`add`, (vec1, vec2, expected) => { | ||
expect(dot(vec1, vec2)).toEqual(expected) | ||
}) | ||
|
||
test(`dot`, () => { | ||
const matrix: number[][] = [ | ||
[1, 2, 3], | ||
[4, 5, 6], | ||
[7, 8, 9], | ||
] | ||
const vector: NdVector = [2, 3, 4] | ||
const expected: number[] = [20, 47, 74] // [1*2 + 2*3 + 3*4, 4*2 + 5*3 + 6*4, 7*2 + 8*3 + 9*4] | ||
expect(dot(matrix, vector)).toEqual(expected) | ||
}) | ||
|
||
test(`dot`, () => { | ||
const matrix1: number[][] = [ | ||
[1, 2, 3], | ||
[4, 5, 6], | ||
] | ||
const matrix2: number[][] = [ | ||
[7, 8], | ||
[9, 10], | ||
[11, 12], | ||
] | ||
const expected: number[][] = [ | ||
[58, 64], // [1*7 + 2*9 + 3*11, 1*8 + 2*10 + 3*12] | ||
[139, 154], // [4*7 + 5*9 + 6*11, 4*8 + 5*10 + 6*12] | ||
] | ||
expect(dot(matrix1, matrix2)).toEqual(expected) | ||
}) |