Skip to content

Commit

Permalink
add lib/math.ts
Browse files Browse the repository at this point in the history
  • Loading branch information
janosh committed Jul 9, 2023
1 parent 483981d commit 58cf060
Show file tree
Hide file tree
Showing 6 changed files with 173 additions and 26 deletions.
2 changes: 2 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ All notable changes to this project will be documented in this file. Dates are d

#### [v0.2.0](https://github.com/janosh/elementari/compare/v0.1.8...v0.2.0)

> 8 July 2023
- Add `Lattice.svelte` [`#41`](https://github.com/janosh/elementari/pull/41)
- Show cylinder between active and hovered sites [`#40`](https://github.com/janosh/elementari/pull/40)
- Fix structure controls for `atom_radius`, `same_size_atoms` [`#38`](https://github.com/janosh/elementari/pull/38)
Expand Down
8 changes: 4 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"homepage": "https://janosh.github.io/elementari",
"repository": "https://github.com/janosh/elementari",
"license": "MIT",
"version": "0.2.0",
"version": "0.2.2",
"type": "module",
"svelte": "./dist/index.js",
"bugs": "https://github.com/janosh/elementari/issues",
Expand Down Expand Up @@ -36,14 +36,14 @@
"highlight.js": "^11.8.0",
"svelte": "4.0.5",
"svelte-multiselect": "^10.0.0",
"three": "^0.154.0"
"three": "^0.154.0",
"@threlte/core": "6.0.0-next.11",
"@threlte/extras": "5.0.0-next.16"
},
"devDependencies": {
"@playwright/test": "^1.35.1",
"@sveltejs/adapter-static": "2.0.2",
"@sveltejs/package": "^2.1.0",
"@threlte/core": "6.0.0-next.11",
"@threlte/extras": "5.0.0-next.16",
"@types/d3-array": "^3.0.5",
"@types/d3-color": "^3.1.0",
"@types/d3-interpolate-path": "^2.0.0",
Expand Down
1 change: 1 addition & 0 deletions src/lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export * from './element'
export { default as element_data } from './element/data'
export * from './labels'
export * from './material'
export * from './math'
export * from './periodic-table'
export * from './plot'
export * from './structure'
Expand Down
86 changes: 86 additions & 0 deletions src/lib/math.ts
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.`,
)
}
23 changes: 1 addition & 22 deletions src/lib/structure/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// Utilities for dealing with pymatgen Structures
import type { ElementSymbol } from '$lib'
import type { ElementSymbol, Vector } from '$lib'
import { pretty_num } from '$lib'
import element_data from '$lib/element/data'

Expand All @@ -16,9 +16,6 @@ export type Species = {
oxidation_state: number
}

export type Vector = [number, number, number]
export type NdVector = number[]

export type Site = {
species: Species[]
abc: Vector
Expand Down Expand Up @@ -104,13 +101,6 @@ export function density(structure: PymatgenStructure, prec = `.2f`) {
return pretty_num(dens, prec)
}

export function euclidean_dist(p1: Vector, p2: Vector): number {
const dx = p1[0] - p2[0]
const dy = p1[1] - p2[1]
const dz = p1[2] - p2[2]
return Math.sqrt(dx * dx + dy * dy + dz * dz)
}

function generate_permutations(length: number): number[][] {
const result: number[][] = []
for (let i = 0; i < Math.pow(2, length); i++) {
Expand Down Expand Up @@ -179,14 +169,3 @@ export function symmetrize_structure(

return symmetrized_structure
}

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
}
79 changes: 79 additions & 0 deletions tests/unit/math.test.ts
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)
})

0 comments on commit 58cf060

Please sign in to comment.