-
Notifications
You must be signed in to change notification settings - Fork 13
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
…48) * refactor: optimize max_dist and nearest_neighbor functions for performance * add run time unit tests for max_dist, nearest_neighbor bonding algos to prevent perf regressions * decrease default bond_radius 0.1->0.05 in StructureScene.svelte * apply a 5x scaling factor to all max allowed run times if CI is found in env vars called ci_max_time_multiplier * only run unit test due to flakiness of playwright E2E tests * more unit tests for bond algos
- Loading branch information
Showing
4 changed files
with
237 additions
and
21 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,194 @@ | ||
import type { PymatgenStructure } from '$lib/structure' | ||
import type { BondingAlgo } from '$lib/structure/bonding' | ||
import { max_dist, nearest_neighbor } from '$lib/structure/bonding' | ||
import { performance } from 'perf_hooks' | ||
import { describe, expect, test } from 'vitest' | ||
|
||
const ci_max_time_multiplier = process.env.CI ? 5 : 1 | ||
|
||
// Function to generate a random structure | ||
function make_rand_structure(numAtoms: number) { | ||
return { | ||
sites: Array.from({ length: numAtoms }, () => ({ | ||
xyz: [Math.random() * 10, Math.random() * 10, Math.random() * 10], | ||
})), | ||
} as PymatgenStructure | ||
} | ||
|
||
// Updated performance test function | ||
function perf_test(func: BondingAlgo, atom_count: number, max_time: number) { | ||
const run = () => { | ||
const structure = make_rand_structure(atom_count) | ||
const start = performance.now() | ||
func(structure) | ||
const end = performance.now() | ||
return end - start | ||
} | ||
|
||
const time1 = run() | ||
const time2 = run() | ||
const avg_time = (time1 + time2) / 2 | ||
|
||
expect( | ||
avg_time, | ||
`average run time: ${Math.ceil(avg_time)}, max expected: ${max_time * ci_max_time_multiplier}`, // Apply scaling factor | ||
).toBeLessThanOrEqual(max_time * ci_max_time_multiplier) | ||
} | ||
|
||
describe(`Bonding Functions Performance Tests`, () => { | ||
const bonding_functions = [ | ||
{ | ||
func: max_dist, | ||
max_times: [ | ||
[10, 0.1], | ||
[100, 1], | ||
[1000, 40], | ||
[5000, 1000], | ||
], | ||
}, | ||
{ | ||
func: nearest_neighbor, | ||
max_times: [ | ||
[10, 0.2], | ||
[100, 3], | ||
[1000, 50], | ||
[5000, 1000], | ||
], | ||
}, | ||
] | ||
|
||
for (const { func, max_times } of bonding_functions) { | ||
for (const [atom_count, max_time] of max_times) { | ||
test(`${func.name} performance for ${atom_count} atoms`, () => { | ||
perf_test(func, atom_count, max_time) | ||
}) | ||
} | ||
} | ||
}) | ||
|
||
// Helper function to create a simple structure | ||
const make_struct = (sites: number[][]): PymatgenStructure => ({ | ||
sites: sites.map((xyz) => ({ xyz })), | ||
}) | ||
|
||
describe(`max_dist function`, () => { | ||
test(`should return correct bonds for a simple structure`, () => { | ||
const structure = make_struct([ | ||
[0, 0, 0], | ||
[1, 0, 0], | ||
[0, 1, 0], | ||
[0, 0, 1], | ||
]) | ||
const bonds = max_dist(structure, { | ||
max_bond_dist: 1.5, | ||
min_bond_dist: 0.5, | ||
}) | ||
expect(bonds).toHaveLength(6) | ||
expect(bonds).toContainEqual([[0, 0, 0], [1, 0, 0], 0, 1, 1]) | ||
expect(bonds).toContainEqual([[0, 0, 0], [0, 1, 0], 0, 2, 1]) | ||
expect(bonds).toContainEqual([[0, 0, 0], [0, 0, 1], 0, 3, 1]) | ||
}) | ||
|
||
test(`should not return bonds shorter than min_bond_dist`, () => { | ||
const structure = make_struct([ | ||
[0, 0, 0], | ||
[0.3, 0, 0], | ||
]) | ||
const bonds = max_dist(structure, { max_bond_dist: 1, min_bond_dist: 0.5 }) | ||
expect(bonds).toHaveLength(0) | ||
}) | ||
|
||
test(`should not return bonds longer than max_bond_dist`, () => { | ||
const structure = make_struct([ | ||
[0, 0, 0], | ||
[2, 0, 0], | ||
]) | ||
const bonds = max_dist(structure, { | ||
max_bond_dist: 1.5, | ||
min_bond_dist: 0.5, | ||
}) | ||
expect(bonds).toHaveLength(0) | ||
}) | ||
|
||
test(`should handle empty structures`, () => { | ||
const structure = make_struct([]) | ||
const bonds = max_dist(structure) | ||
expect(bonds).toHaveLength(0) | ||
}) | ||
}) | ||
|
||
describe(`nearest_neighbor function`, () => { | ||
test(`should return correct bonds for a simple structure`, () => { | ||
const structure = make_struct([ | ||
[0, 0, 0], | ||
[1, 0, 0], | ||
[0, 1, 0], | ||
[0, 0, 1], | ||
[2, 0, 0], | ||
]) | ||
const bonds = nearest_neighbor(structure, { | ||
scaling_factor: 1.1, | ||
min_bond_dist: 0.5, | ||
}) | ||
expect(bonds).toHaveLength(4) | ||
expect(bonds).toContainEqual([[0, 0, 0], [1, 0, 0], 0, 1, 1]) | ||
expect(bonds).toContainEqual([[0, 0, 0], [0, 1, 0], 0, 2, 1]) | ||
expect(bonds).toContainEqual([[0, 0, 0], [0, 0, 1], 0, 3, 1]) | ||
}) | ||
|
||
test(`should not return bonds shorter than min_bond_dist`, () => { | ||
const structure = make_struct([ | ||
[0, 0, 0], | ||
[0.05, 0, 0], | ||
[1, 0, 0], | ||
]) | ||
const bonds = nearest_neighbor(structure, { | ||
scaling_factor: 1.2, | ||
min_bond_dist: 0.1, | ||
}) | ||
expect(bonds).toHaveLength(2) | ||
expect(bonds).toContainEqual([[0, 0, 0], [1, 0, 0], 0, 2, 1]) | ||
}) | ||
|
||
test(`should handle structures with multiple equidistant nearest neighbors`, () => { | ||
const structure = make_struct([ | ||
[0, 0, 0], | ||
[1, 0, 0], | ||
[0, 1, 0], | ||
[0, 0, 1], | ||
]) | ||
const bonds = nearest_neighbor(structure, { | ||
scaling_factor: 1.1, | ||
min_bond_dist: 0.5, | ||
}) | ||
expect(bonds).toHaveLength(3) | ||
expect(bonds).toContainEqual([[0, 0, 0], [1, 0, 0], 0, 1, 1]) | ||
expect(bonds).toContainEqual([[0, 0, 0], [0, 1, 0], 0, 2, 1]) | ||
expect(bonds).toContainEqual([[0, 0, 0], [0, 0, 1], 0, 3, 1]) | ||
}) | ||
|
||
test(`should handle empty structures`, () => { | ||
const structure = make_struct([]) | ||
const bonds = nearest_neighbor(structure) | ||
expect(bonds).toHaveLength(0) | ||
}) | ||
|
||
test(`should respect the scaling_factor`, () => { | ||
const structure = make_struct([ | ||
[0, 0, 0], | ||
[1, 0, 0], | ||
[0, 1, 0], | ||
[0, 0, 1], | ||
[1.5, 0, 0], | ||
]) | ||
const bonds = nearest_neighbor(structure, { | ||
scaling_factor: 1.4, | ||
min_bond_dist: 0.5, | ||
}) | ||
expect(bonds).toHaveLength(4) | ||
expect(bonds).toContainEqual([[0, 0, 0], [1, 0, 0], 0, 1, 1]) | ||
expect(bonds).toContainEqual([[0, 0, 0], [0, 1, 0], 0, 2, 1]) | ||
expect(bonds).toContainEqual([[0, 0, 0], [0, 0, 1], 0, 3, 1]) | ||
expect(bonds).toContainEqual([[1, 0, 0], [1.5, 0, 0], 1, 4, 0.5]) | ||
}) | ||
}) |