Skip to content

Commit 61c1368

Browse files
Merge pull request #7 from Exabyte-io/chore/SOF-6008
Chore/sof 6008 - add utils
2 parents bd51c03 + a753bcd commit 61c1368

File tree

17 files changed

+757
-15
lines changed

17 files changed

+757
-15
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
"@babel/runtime-corejs3": "7.16.8",
4545
"crypto-js": "^4.1.1",
4646
"lodash": "^4.17.21",
47+
"mathjs": "^3.9.0",
4748
"mixwith": "^0.1.1",
4849
"underscore": "^1.13.3"
4950
},

src/constants.js

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
export const coefficients = {
2+
EV_TO_RY: 0.0734986176,
3+
BOHR_TO_ANGSTROM: 0.52917721092,
4+
ANGSTROM_TO_BOHR: 1 / 0.52917721092,
5+
EV_A_TO_RY_BOHR: 1 / 25.71104309541616,
6+
};
7+
8+
export const tolerance = {
9+
// in crystal coordinates
10+
length: 0.01,
11+
lengthAngstrom: 0.001,
12+
pointsDistance: 0.001,
13+
};
14+
15+
export const units = {
16+
bohr: "bohr",
17+
angstrom: "angstrom",
18+
degree: "degree",
19+
radian: "radian",
20+
alat: "alat",
21+
};
22+
23+
/**
24+
* @summary Coordinates units for a material's basis.
25+
*/
26+
export const ATOMIC_COORD_UNITS = {
27+
crystal: "crystal",
28+
cartesian: "cartesian",
29+
};
30+
31+
// Only 3 digits will be considered for lattice and basis params on hashing
32+
export const HASH_TOLERANCE = 3;
33+
34+
export default {
35+
coefficients,
36+
tolerance,
37+
units,
38+
ATOMIC_COORD_UNITS,
39+
};

src/entity/in_memory.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,9 @@ export class InMemoryEntity {
109109

110110
get cls() {return this.constructor.name}
111111

112+
// TODO: figure out why the above getter for `cls` returns `null` and use only one
113+
getClsName() {return this.constructor.name}
114+
112115
get slug() {return this.prop('slug')}
113116

114117
get isSystemEntity() {
@@ -127,7 +130,7 @@ export class InMemoryEntity {
127130
return {
128131
_id: this.id,
129132
slug: this.slug,
130-
cls: this.cls,
133+
cls: this.getClsName(),
131134
}
132135
}
133136

src/entity/index.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {
33
} from "./in_memory";
44

55
import {
6+
NamedInMemoryEntity,
67
DefaultableInMemoryEntity,
78
NamedDefaultableInMemoryEntity,
89
HasMetadataNamedDefaultableInMemoryEntity,
@@ -16,6 +17,7 @@ import {
1617
NamedEntityMixin,
1718
} from "./mixins";
1819

20+
import { InMemoryEntitySet } from "./set";
1921
import { ENTITY_SET_TYPES } from "./set/enums";
2022

2123
import { constructEntitySetFactoryByConfig } from "./set/factory";
@@ -26,6 +28,7 @@ import { OrderedInMemoryEntityInSetMixin, OrderedInMemoryEntitySetMixin } from "
2628
export {
2729

2830
InMemoryEntity,
31+
NamedInMemoryEntity,
2932
DefaultableInMemoryEntity,
3033
NamedDefaultableInMemoryEntity,
3134
HasMetadataNamedDefaultableInMemoryEntity,
@@ -36,6 +39,7 @@ export {
3639
TaggableMixin,
3740
NamedEntityMixin,
3841

42+
InMemoryEntitySet,
3943
ENTITY_SET_TYPES,
4044
constructEntitySetFactoryByConfig,
4145
selectorsForEntitySet,

src/entity/other.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,12 @@ export class DefaultableInMemoryEntity extends mix(InMemoryEntity).with(
66
DefaultableMixin
77
) {
88

9+
}
10+
11+
export class NamedInMemoryEntity extends mix(InMemoryEntity).with(
12+
NamedEntityMixin
13+
) {
14+
915
}
1016
export class NamedDefaultableInMemoryEntity extends mix(InMemoryEntity).with(
1117
DefaultableMixin,

src/entity/set.js

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,12 @@ import _ from "underscore";
22
import {mix} from "mixwith";
33

44
import { InMemoryEntity } from "./in_memory";
5-
import {NamedEntityMixin, TaggableMixin } from "./mixins";
65
import {InMemoryEntitySetMixin, InMemoryEntityInSetMixin } from "./set/mixins";
76

87
export class InMemoryEntitySet extends mix(InMemoryEntity).with(
98
InMemoryEntitySetMixin,
109
InMemoryEntityInSetMixin,
11-
NamedEntityMixin,
12-
TaggableMixin,
10+
1311
) {
1412

1513
get isEntitySet() {return this.prop('isEntitySet')}
@@ -18,7 +16,7 @@ export class InMemoryEntitySet extends mix(InMemoryEntity).with(
1816

1917
get entityCls() {return this.prop('entityCls')}
2018

21-
get cls() {return this.entityCls}
19+
get cls() {return this.entityCls || super.cls}
2220

2321
toJSONForInclusionInEntity() {
2422
return _.pick(this.toJSON(), ['_id', 'type']);

src/math.js

Lines changed: 257 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,257 @@
1+
import _ from "underscore";
2+
import mathjs from "mathjs";
3+
4+
import { tolerance as TOLERANCE } from "./constants";
5+
6+
/**
7+
* @summary Zero threshold. Numbers below it are put to zero exactly.
8+
* Used to avoid math.js bug in treating zero as X.XXe-16.
9+
*/
10+
const EPSILON = 1e-8;
11+
/**
12+
* @summary Returns scalar product of vectors
13+
* @param v1 {Number[]} Vector 1
14+
* @param v2 {Number[]} Vector 2
15+
* @return {Number}
16+
*/
17+
18+
const product = (v1, v2) => {
19+
return math.multiply(v1, math.transpose(v2));
20+
};
21+
22+
/**
23+
* @summary Returns length of a vector.
24+
* @param v {Number[]} Vector
25+
* @return {Number}
26+
*/
27+
const vlen = (v) => {
28+
return math.sqrt(product(v, v));
29+
};
30+
31+
/**
32+
* @summary Returns angle between `a` and `b` vectors.
33+
* @param a {Number[]} Vector a
34+
* @param b {Number[]} Vector b
35+
* @param [unit] {String} `rad`, `deg`
36+
* @return {Number}
37+
*/
38+
const angle = (a, b, unit) => {
39+
const lenA = vlen(a);
40+
const lenB = vlen(b);
41+
return math.unit(math.acos(product(a, b) / (lenA * lenB)), "rad").toNumber(unit || "deg");
42+
};
43+
44+
const angleUpTo90 = (...args) => {
45+
const angleUpTo180 = angle(...args);
46+
return angleUpTo180 < 90 ? angleUpTo180 : 180 - angleUpTo180;
47+
};
48+
49+
/**
50+
* @summary Returns distance between 2 vectors.
51+
* @param v1 {Number[]} Vector
52+
* @param v2 {Number[]} Vector
53+
* @return {Number}
54+
*/
55+
const vDist = (v1, v2) => {
56+
if (v1.length !== v2.length) {
57+
console.error(
58+
"Attempting to calculate distance between vectors of different dimensionality",
59+
);
60+
return;
61+
}
62+
return vlen(v1.map((coordinate, index) => coordinate - v2[index]));
63+
};
64+
65+
/**
66+
* @summary Returns checks whether 2 vector are equal within tolerance.
67+
* @param vec1 {Number[]} Vector
68+
* @param vec2 {Number[]} Vector
69+
* @param tolerance {Number} Tolerance
70+
* @return {Number}
71+
*/
72+
const vEqualWithTolerance = (vec1, vec2, tolerance = TOLERANCE.pointsDistance) =>
73+
vDist(vec1, vec2) <= tolerance;
74+
75+
/**
76+
* @summary Returns 0 if passed number is less than Made.math.EPSILON.
77+
* @param n {Number}
78+
* @return {Number}
79+
*/
80+
const roundToZero = (n) => {
81+
return Math.abs(n) < EPSILON ? 0 : n;
82+
};
83+
84+
/**
85+
* @summary Returns number with specified precision.
86+
* @param x {Number}
87+
* @param n {Number}
88+
* @return {Number}
89+
*/
90+
const precise = (x, n = 7) => {
91+
return Number(x.toPrecision(n));
92+
};
93+
94+
/**
95+
* @summary Returns mod of the passed value with the specified tolerance.
96+
* @param num {Number}
97+
* @param tolerance {Number}
98+
* @return {Number}
99+
*/
100+
const mod = (num, tolerance = 0.001) => {
101+
const m = num % 1;
102+
const x = num >= 0 ? m : 1 + m;
103+
104+
if (math.smallerEq(Math.abs(x - 1), tolerance) || math.smallerEq(Math.abs(x), tolerance)) {
105+
return 0;
106+
}
107+
return x;
108+
};
109+
110+
/**
111+
* @summary Returns cartesian of passed arrays.
112+
* @example combinations([1,2], [4,5], [6]) = [[1,4,6], [1,5,6], [2,4,6], [2,5,6]];
113+
*/
114+
const cartesianProduct = (...arg) => {
115+
const r = [];
116+
const max = arg.length - 1;
117+
118+
const helper = (arr, i) => {
119+
for (let j = 0, l = arg[i].length; j < l; j++) {
120+
const a = arr.slice(0); // clone arr
121+
a.push(arg[i][j]);
122+
if (i === max) {
123+
r.push(a);
124+
} else {
125+
helper(a, i + 1);
126+
}
127+
}
128+
};
129+
130+
helper([], 0);
131+
return r;
132+
};
133+
134+
/**
135+
* @summary Returns all possible positive integer combinations where each value changes from 0 to a, b, c.
136+
* @param a {Number}
137+
* @param b {Number}
138+
* @param tolerance {Number}
139+
*/
140+
const almostEqual = (a, b, tolerance = TOLERANCE.pointsDistance) => {
141+
return Math.abs(a - b) < tolerance;
142+
};
143+
144+
/**
145+
* @summary Returns true if number is 0 <= x < 1, inclusive, otherwise false.
146+
* Helper to deal with JS arithmetic artifacts.
147+
* @number number {Number}
148+
*/
149+
const isBetweenZeroInclusiveAndOne = (number, tolerance = TOLERANCE.length) => {
150+
return roundToZero(number) >= 0 && !almostEqual(number, 1, tolerance) && number < 1;
151+
};
152+
153+
/**
154+
* @summary Returns all possible positive integer combinations where each value changes from 0 to a, b, c.
155+
* @example
156+
* var comb = combinations(1, 2, 0);
157+
* // [[0, 0, 0], [0, 1, 0], [0, 2, 0], [1, 0, 0], [1, 1, 0], [1, 2, 0]]
158+
* @param a
159+
* @param b
160+
* @param c
161+
*/
162+
const combinations = (a, b, c) => {
163+
const combs = [];
164+
for (let i = 0; i <= a; i++) {
165+
for (let j = 0; j <= b; j++) {
166+
for (let k = 0; k <= c; k++) {
167+
combs.push([i, j, k]);
168+
}
169+
}
170+
}
171+
return combs;
172+
};
173+
174+
/*
175+
* @summary Same as `combinations` but accepting intervals (tuples) of integers: eg. [-3, 4]
176+
*/
177+
const combinationsFromIntervals = (arrA, arrB, arrC) => {
178+
const combs = [];
179+
for (let i = arrA[0]; i <= arrA[1]; i++) {
180+
for (let j = arrB[0]; j <= arrB[1]; j++) {
181+
for (let k = arrC[0]; k <= arrC[1]; k++) {
182+
combs.push([i, j, k]);
183+
}
184+
}
185+
}
186+
return combs;
187+
};
188+
189+
const roundValueToNDecimals = (value, decimals = 3) => {
190+
return parseFloat(value.toFixed(decimals));
191+
}
192+
193+
/**
194+
* @summary Returns n splits of the passed segment.
195+
* @param point1 {Number[]}
196+
* @param point2 {Number[]}
197+
* @param n {Number}
198+
*/
199+
const calculateSegmentsBetweenPoints3D = (point1, point2, n) => {
200+
// safely parse if passed strings
201+
const point1_ = point1.map(x => parseFloat(x));
202+
const point2_ = point2.map(x => parseFloat(x));
203+
const n_ = parseInt(n)
204+
205+
const result = [];
206+
for (let i = 1; i < n_; i++) {
207+
const lambda = i / (n_ - i);
208+
result.push([
209+
(point1_[0] + lambda * point2_[0]) / (1 + lambda),
210+
(point1_[1] + lambda * point2_[1]) / (1 + lambda),
211+
(point1_[2] + lambda * point2_[2]) / (1 + lambda)
212+
]);
213+
}
214+
return result;
215+
}
216+
217+
/**
218+
* @summary Wrapper for native [Number.toPrecision](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_objects/Number/toPrecision) method.
219+
* Returns a string representing the Number object to the specified precision.
220+
* @memberOf Helpers
221+
* @package exabyte:core
222+
* @locus Client
223+
* @method
224+
* @name toPrecision
225+
* @param {Number} number
226+
* @param {Number} precision Optional. An integer specifying the number of significant digits.
227+
*/
228+
export function numberToPrecision(number, precision) {
229+
230+
if (_.isNumber(number)) {
231+
return number.toPrecision(precision);
232+
}
233+
return number;
234+
}
235+
236+
export const math = {
237+
...mathjs,
238+
PI: Math.PI,
239+
trunc: Math.trunc,
240+
product,
241+
vlen,
242+
angle,
243+
angleUpTo90,
244+
vDist,
245+
vEqualWithTolerance,
246+
roundToZero,
247+
precise,
248+
mod,
249+
isBetweenZeroInclusiveAndOne,
250+
cartesianProduct,
251+
almostEqual,
252+
combinations,
253+
combinationsFromIntervals,
254+
calculateSegmentsBetweenPoints3D,
255+
roundValueToNDecimals,
256+
numberToPrecision,
257+
};

0 commit comments

Comments
 (0)