@mat3ra/made 2024.3.22-0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (176) hide show
  1. package/.babelrc +10 -0
  2. package/.eslintrc.json +11 -0
  3. package/.mocharc.json +5 -0
  4. package/.prettierignore +1 -0
  5. package/.prettierrc +6 -0
  6. package/LICENSE.md +15 -0
  7. package/README.md +167 -0
  8. package/dist/abstract/array_with_ids.d.ts +43 -0
  9. package/dist/abstract/array_with_ids.js +88 -0
  10. package/dist/abstract/scalar_with_id.d.ts +25 -0
  11. package/dist/abstract/scalar_with_id.js +44 -0
  12. package/dist/basis/basis.d.ts +269 -0
  13. package/dist/basis/basis.js +499 -0
  14. package/dist/basis/constrained_basis.d.ts +56 -0
  15. package/dist/basis/constrained_basis.js +90 -0
  16. package/dist/basis/types.d.ts +1 -0
  17. package/dist/basis/types.js +2 -0
  18. package/dist/cell/cell.d.ts +45 -0
  19. package/dist/cell/cell.js +88 -0
  20. package/dist/cell/conventional_cell.d.ts +22 -0
  21. package/dist/cell/conventional_cell.js +83 -0
  22. package/dist/cell/primitive_cell.d.ts +9 -0
  23. package/dist/cell/primitive_cell.js +166 -0
  24. package/dist/constants.d.ts +2 -0
  25. package/dist/constants.js +25 -0
  26. package/dist/constraints/constraints.d.ts +45 -0
  27. package/dist/constraints/constraints.js +49 -0
  28. package/dist/lattice/lattice.d.ts +104 -0
  29. package/dist/lattice/lattice.js +208 -0
  30. package/dist/lattice/lattice_bravais.d.ts +59 -0
  31. package/dist/lattice/lattice_bravais.js +120 -0
  32. package/dist/lattice/lattice_vectors.d.ts +46 -0
  33. package/dist/lattice/lattice_vectors.js +98 -0
  34. package/dist/lattice/reciprocal/lattice_reciprocal.d.ts +75 -0
  35. package/dist/lattice/reciprocal/lattice_reciprocal.js +148 -0
  36. package/dist/lattice/reciprocal/paths.d.ts +24 -0
  37. package/dist/lattice/reciprocal/paths.js +136 -0
  38. package/dist/lattice/reciprocal/symmetry_points.d.ts +8 -0
  39. package/dist/lattice/reciprocal/symmetry_points.js +866 -0
  40. package/dist/lattice/types.d.ts +49 -0
  41. package/dist/lattice/types.js +127 -0
  42. package/dist/lattice/unit_cell.d.ts +30 -0
  43. package/dist/lattice/unit_cell.js +31 -0
  44. package/dist/made.d.ts +40 -0
  45. package/dist/made.js +39 -0
  46. package/dist/material.d.ts +1562 -0
  47. package/dist/material.js +317 -0
  48. package/dist/math.d.ts +395 -0
  49. package/dist/math.js +7 -0
  50. package/dist/parsers/cif.d.ts +10 -0
  51. package/dist/parsers/cif.js +21 -0
  52. package/dist/parsers/errors.d.ts +5 -0
  53. package/dist/parsers/errors.js +11 -0
  54. package/dist/parsers/espresso.d.ts +10 -0
  55. package/dist/parsers/espresso.js +24 -0
  56. package/dist/parsers/native_format_parsers.d.ts +26 -0
  57. package/dist/parsers/native_format_parsers.js +52 -0
  58. package/dist/parsers/parsers.d.ts +13 -0
  59. package/dist/parsers/parsers.js +17 -0
  60. package/dist/parsers/poscar.d.ts +31 -0
  61. package/dist/parsers/poscar.js +180 -0
  62. package/dist/parsers/xyz.d.ts +62 -0
  63. package/dist/parsers/xyz.js +167 -0
  64. package/dist/parsers/xyz_combinatorial_basis.d.ts +64 -0
  65. package/dist/parsers/xyz_combinatorial_basis.js +241 -0
  66. package/dist/tools/basis.d.ts +22 -0
  67. package/dist/tools/basis.js +100 -0
  68. package/dist/tools/cell.d.ts +9 -0
  69. package/dist/tools/cell.js +39 -0
  70. package/dist/tools/index.d.ts +11 -0
  71. package/dist/tools/index.js +15 -0
  72. package/dist/tools/material.d.ts +25 -0
  73. package/dist/tools/material.js +54 -0
  74. package/dist/tools/supercell.d.ts +22 -0
  75. package/dist/tools/supercell.js +62 -0
  76. package/dist/tools/surface.d.ts +10 -0
  77. package/dist/tools/surface.js +147 -0
  78. package/dist/types.d.ts +3 -0
  79. package/dist/types.js +2 -0
  80. package/package.json +89 -0
  81. package/pyproject.toml +77 -0
  82. package/src/js/abstract/array_with_ids.ts +100 -0
  83. package/src/js/abstract/scalar_with_id.ts +53 -0
  84. package/src/js/basis/basis.ts +607 -0
  85. package/src/js/basis/constrained_basis.ts +107 -0
  86. package/src/js/basis/types.ts +1 -0
  87. package/src/js/cell/cell.ts +109 -0
  88. package/src/js/cell/conventional_cell.ts +89 -0
  89. package/src/js/cell/primitive_cell.ts +189 -0
  90. package/src/js/constants.js +4 -0
  91. package/src/js/constraints/constraints.ts +63 -0
  92. package/src/js/lattice/lattice.ts +229 -0
  93. package/src/js/lattice/lattice_bravais.ts +170 -0
  94. package/src/js/lattice/lattice_vectors.ts +126 -0
  95. package/src/js/lattice/reciprocal/lattice_reciprocal.js +155 -0
  96. package/src/js/lattice/reciprocal/paths.js +134 -0
  97. package/src/js/lattice/reciprocal/symmetry_points.ts +886 -0
  98. package/src/js/lattice/types.ts +142 -0
  99. package/src/js/lattice/unit_cell.ts +66 -0
  100. package/src/js/made.js +36 -0
  101. package/src/js/material.ts +398 -0
  102. package/src/js/math.js +6 -0
  103. package/src/js/parsers/cif.js +22 -0
  104. package/src/js/parsers/errors.js +7 -0
  105. package/src/js/parsers/espresso.ts +30 -0
  106. package/src/js/parsers/native_format_parsers.js +51 -0
  107. package/src/js/parsers/parsers.js +13 -0
  108. package/src/js/parsers/poscar.ts +201 -0
  109. package/src/js/parsers/xyz.ts +216 -0
  110. package/src/js/parsers/xyz_combinatorial_basis.js +243 -0
  111. package/src/js/tools/basis.js +116 -0
  112. package/src/js/tools/cell.js +36 -0
  113. package/src/js/tools/index.js +11 -0
  114. package/src/js/tools/material.js +60 -0
  115. package/src/js/tools/supercell.ts +80 -0
  116. package/src/js/tools/surface.js +176 -0
  117. package/src/js/types.ts +4 -0
  118. package/src/py/__init__.py +0 -0
  119. package/src/py/mat3ra/__init__.py +0 -0
  120. package/src/py/mat3ra/made/__init__.py +5 -0
  121. package/tests/.gitattributes +1 -0
  122. package/tests/fixtures/AsGe-basis.json +3 -0
  123. package/tests/fixtures/C2H4-translated.json +3 -0
  124. package/tests/fixtures/C2H4.json +3 -0
  125. package/tests/fixtures/FeLiSi-basis.json +3 -0
  126. package/tests/fixtures/FeO.json +3 -0
  127. package/tests/fixtures/Ge2-basis.json +3 -0
  128. package/tests/fixtures/Graphene.json +3 -0
  129. package/tests/fixtures/Graphene.poscar +3 -0
  130. package/tests/fixtures/H2+H-final.json +3 -0
  131. package/tests/fixtures/H2+H-image.json +3 -0
  132. package/tests/fixtures/H2+H-initial.json +3 -0
  133. package/tests/fixtures/H2O.poscar +3 -0
  134. package/tests/fixtures/LiFeSi-basis.json +3 -0
  135. package/tests/fixtures/Na.json +3 -0
  136. package/tests/fixtures/Na4Cl4-cartesian.json +3 -0
  137. package/tests/fixtures/Na4Cl4.json +3 -0
  138. package/tests/fixtures/Na4Cl4.poscar +3 -0
  139. package/tests/fixtures/Ni-hex.json +3 -0
  140. package/tests/fixtures/Ni-hex.poscar +3 -0
  141. package/tests/fixtures/OSi-basis.json +3 -0
  142. package/tests/fixtures/Si-hex.json +3 -0
  143. package/tests/fixtures/Si-hex.poscar +3 -0
  144. package/tests/fixtures/Si-pwscf.in +3 -0
  145. package/tests/fixtures/Si-slab.json +3 -0
  146. package/tests/fixtures/Si-supercell.json +3 -0
  147. package/tests/fixtures/Si.json +3 -0
  148. package/tests/fixtures/Si2-basis-repeated.json +3 -0
  149. package/tests/fixtures/Si2-basis.json +3 -0
  150. package/tests/fixtures/Zr1H23Zr1H1.json +3 -0
  151. package/tests/fixtures/Zr1H23Zr1H1.poscar +3 -0
  152. package/tests/fixtures/atomic-constraints.json +3 -0
  153. package/tests/js/basis/basis.js +221 -0
  154. package/tests/js/cell/cell.js +21 -0
  155. package/tests/js/cell/primitive_cell.js +17 -0
  156. package/tests/js/constraints/constraints.js +27 -0
  157. package/tests/js/enums.js +40 -0
  158. package/tests/js/lattice/lattice.js +31 -0
  159. package/tests/js/lattice/lattice_bravais.js +17 -0
  160. package/tests/js/lattice/lattice_reciprocal.js +99 -0
  161. package/tests/js/lattice/lattice_vectors.js +10 -0
  162. package/tests/js/material.test.js +11 -0
  163. package/tests/js/parsers/espresso.js +12 -0
  164. package/tests/js/parsers/native_formats.js +30 -0
  165. package/tests/js/parsers/poscar.js +21 -0
  166. package/tests/js/parsers/xyz.js +25 -0
  167. package/tests/js/parsers/xyz_combinatorial_basis.js +153 -0
  168. package/tests/js/setup.js +6 -0
  169. package/tests/js/tools/basis.js +18 -0
  170. package/tests/js/tools/supercell.js +23 -0
  171. package/tests/js/tools/surface.js +12 -0
  172. package/tests/js/utils.js +17 -0
  173. package/tests/py/__init__.py +0 -0
  174. package/tests/py/unit/__init__.py +0 -0
  175. package/tests/py/unit/test_sample.py +10 -0
  176. package/tsconfig.json +3 -0
@@ -0,0 +1,607 @@
1
+ // @ts-ignore
2
+ import { getElectronegativity, getElementAtomicRadius } from "@exabyte-io/periodic-table.js";
3
+ import _ from "underscore";
4
+ import s from "underscore.string";
5
+
6
+ import { ArrayWithIds } from "../abstract/array_with_ids";
7
+ import { ObjectWithIdAndValue, ValueOrObjectArray } from "../abstract/scalar_with_id";
8
+ import { ATOMIC_COORD_UNITS, HASH_TOLERANCE } from "../constants";
9
+ import { Lattice, nonPeriodicLatticeScalingFactor } from "../lattice/lattice";
10
+ import { Vector } from "../lattice/types";
11
+ import math from "../math";
12
+ import { Coordinate } from "./types";
13
+
14
+ export interface BasisProps {
15
+ elements: ValueOrObjectArray<string>; // chemical elements for atoms in basis.
16
+ coordinates: ValueOrObjectArray<Coordinate>; // coordinates for the atoms in basis.
17
+ labels?: { id: number; value: number }[];
18
+ units: string; // units for the coordinates (eg. angstrom, crystal).
19
+ cell: Vector[]; // crystal cell corresponding to the basis (eg. to convert to crystal coordinates).
20
+ isEmpty?: boolean; // crystal cell corresponding to the basis (eg. to convert to crystal coordinates).
21
+ }
22
+
23
+ export interface Atom {
24
+ id?: number; // numeric id of the element (optional).
25
+ element: string; // Chemical element
26
+ coordinate: Coordinate; // 3-dimensional coordinate
27
+ }
28
+
29
+ export interface ElementCount {
30
+ count: number;
31
+ value: string;
32
+ }
33
+
34
+ export interface BasisSchema {
35
+ elements: ObjectWithIdAndValue<string>[];
36
+ labels?: { id: number; value: number }[];
37
+ coordinates: ObjectWithIdAndValue<Coordinate>[];
38
+ units: string;
39
+ cell: Vector[];
40
+ }
41
+
42
+ interface Overlap {
43
+ id1: number;
44
+ id2: number;
45
+ element1: string;
46
+ element2: string;
47
+ }
48
+
49
+ /**
50
+ * A class representing a crystal basis.
51
+ */
52
+ export class Basis {
53
+ _elements: ArrayWithIds<string>;
54
+
55
+ _coordinates: ArrayWithIds<Coordinate>;
56
+
57
+ labels?: { id: number; value: number }[];
58
+
59
+ units: string;
60
+
61
+ cell: Vector[];
62
+
63
+ constructor({
64
+ elements = ["Si"],
65
+ coordinates = [[0, 0, 0]],
66
+ units,
67
+ cell = Basis.defaultCell, // by default, assume a cubic unary cell
68
+ isEmpty = false, // whether to generate an empty Basis
69
+ labels = [],
70
+ }: BasisProps) {
71
+ const _elements = isEmpty ? [] : elements;
72
+ const _coordinates = isEmpty ? [] : coordinates;
73
+ const _units = units || Basis.unitsOptionsDefaultValue;
74
+
75
+ if (!units) {
76
+ console.warn("Basis.constructor: units are not provided => set to crystal");
77
+ }
78
+
79
+ // assert that elements and coordinates have ids if not already passed in config + store Array helper classes
80
+ this._elements = new ArrayWithIds<string>(_elements);
81
+ this._coordinates = new ArrayWithIds<Coordinate>(_coordinates);
82
+ this.units = _units;
83
+ this.cell = cell;
84
+
85
+ if (!_.isEmpty(labels)) {
86
+ this.labels = labels;
87
+ }
88
+ }
89
+
90
+ static get unitsOptionsConfig() {
91
+ return ATOMIC_COORD_UNITS;
92
+ }
93
+
94
+ static get unitsOptionsDefaultValue() {
95
+ return ATOMIC_COORD_UNITS.crystal;
96
+ }
97
+
98
+ static get defaultCell() {
99
+ return new Lattice().vectorArrays;
100
+ }
101
+
102
+ /**
103
+ * Serialize class instance to JSON.
104
+ * @example As below:
105
+ {
106
+ "elements" : [
107
+ {
108
+ "id" : 0,
109
+ "value" : "Si"
110
+ },
111
+ {
112
+ "id" : 1,
113
+ "value" : "Si"
114
+ }
115
+ ],
116
+ "coordinates" : [
117
+ {
118
+ "id" : 0,
119
+ "value" : [
120
+ 0,
121
+ 0,
122
+ 0
123
+ ]
124
+ },
125
+ {
126
+ "id" : 1,
127
+ "value" : [
128
+ 0.25,
129
+ 0.25,
130
+ 0.25
131
+ ]
132
+ }
133
+ ],
134
+ "units" : "crystal",
135
+ "cell" : [
136
+ [
137
+ 1,
138
+ 0,
139
+ 6.12323399573677e-17
140
+ ],
141
+ [
142
+ 1.60812264967664e-16,
143
+ 1,
144
+ 6.12323399573677e-17
145
+ ],
146
+ [
147
+ 0,
148
+ 0,
149
+ 1
150
+ ]
151
+ ]
152
+ }
153
+ */
154
+ toJSON(): BasisSchema {
155
+ const json = {
156
+ elements: this.elements,
157
+ coordinates: this.coordinates,
158
+ units: this.units,
159
+ cell: this.cell,
160
+ };
161
+
162
+ if (!_.isEmpty(this.labels)) {
163
+ return JSON.parse(
164
+ JSON.stringify({
165
+ ...json,
166
+ labels: this.labels,
167
+ }),
168
+ );
169
+ }
170
+
171
+ return JSON.parse(JSON.stringify(json));
172
+ }
173
+
174
+ /**
175
+ * Create an identical copy of the class instance.
176
+ * @param extraContext - Extra context to be passed to the new class instance on creation.
177
+ */
178
+ clone(extraContext?: Partial<BasisProps>): Basis {
179
+ return new (this.constructor as typeof Basis)({
180
+ ...this.toJSON(),
181
+ ...extraContext,
182
+ });
183
+ }
184
+
185
+ getElementByIndex(idx: number): string {
186
+ return this._elements.getArrayElementByIndex(idx);
187
+ }
188
+
189
+ getCoordinateByIndex(idx: number): Coordinate {
190
+ return this._coordinates.getArrayElementByIndex(idx);
191
+ }
192
+
193
+ get elementsArray(): string[] {
194
+ return this._elements.array;
195
+ }
196
+
197
+ get elements(): ObjectWithIdAndValue<string>[] {
198
+ return this._elements.toJSON();
199
+ }
200
+
201
+ /**
202
+ * Set basis elements to passed array.
203
+ * @param elementsArray - New elements array.
204
+ */
205
+ set elements(elementsArray: string[] | ObjectWithIdAndValue<string>[]) {
206
+ this._elements = new ArrayWithIds<string>(elementsArray);
207
+ }
208
+
209
+ getElementsAsObject(): ObjectWithIdAndValue<string>[] {
210
+ return this._elements.toJSON();
211
+ }
212
+
213
+ get coordinates(): ObjectWithIdAndValue<Coordinate>[] {
214
+ return this._coordinates.toJSON();
215
+ }
216
+
217
+ /**
218
+ * Set basis elements to passed array.
219
+ * @param {Array|ArrayWithIds} coordinatesNestedArray - New coordinates array.
220
+ */
221
+ set coordinates(coordinatesNestedArray: Coordinate[] | ObjectWithIdAndValue<Coordinate>[]) {
222
+ this._coordinates = new ArrayWithIds<Coordinate>(coordinatesNestedArray);
223
+ }
224
+
225
+ get coordinatesAsArray() {
226
+ return this._coordinates.array;
227
+ }
228
+
229
+ get isInCrystalUnits() {
230
+ return this.units === ATOMIC_COORD_UNITS.crystal;
231
+ }
232
+
233
+ get isInCartesianUnits() {
234
+ return this.units === ATOMIC_COORD_UNITS.cartesian;
235
+ }
236
+
237
+ toCartesian() {
238
+ const unitCell = this.cell;
239
+ if (this.units === ATOMIC_COORD_UNITS.cartesian) return;
240
+ this._coordinates.mapArrayInPlace(
241
+ (point) => math.multiply(point, unitCell) as unknown as Coordinate,
242
+ );
243
+ this.units = ATOMIC_COORD_UNITS.cartesian;
244
+ }
245
+
246
+ toCrystal() {
247
+ const unitCell = this.cell;
248
+ if (this.units === ATOMIC_COORD_UNITS.crystal) return;
249
+ this._coordinates.mapArrayInPlace(
250
+ (point) => math.multiply(point, math.inv(unitCell)) as unknown as Coordinate,
251
+ );
252
+ this.units = ATOMIC_COORD_UNITS.crystal;
253
+ }
254
+
255
+ /**
256
+ * Asserts that all coordinates are in standardRepresentation (as explained below).
257
+ */
258
+ toStandardRepresentation() {
259
+ this.toCrystal();
260
+ this._coordinates.mapArrayInPlace((point) => point.map((x) => math.mod(x)) as Coordinate);
261
+ }
262
+
263
+ /** A representation where all coordinates are within 0 and 1 in crystal units */
264
+ get standardRepresentation(): BasisSchema {
265
+ const originalUnits = this.units;
266
+
267
+ this.toStandardRepresentation();
268
+ const result = this.toJSON();
269
+
270
+ // preserve the original state
271
+ if (originalUnits !== ATOMIC_COORD_UNITS.crystal) this.toCartesian();
272
+
273
+ return result;
274
+ }
275
+
276
+ /**
277
+ * Add atom with a chemical element at coordinate.
278
+ */
279
+ addAtom({ element = "Si", coordinate = [0.5, 0.5, 0.5] }: Atom) {
280
+ this._elements.addElement(element);
281
+ this._coordinates.addElement(coordinate);
282
+ }
283
+
284
+ /**
285
+ * Remove atom with a chemical element at coordinate either by passing the (element AND coordinate) OR id.
286
+ */
287
+ removeAtom({ element, coordinate, id }: Atom) {
288
+ if (element && coordinate.length > 0) {
289
+ this._elements.removeElement(element, id);
290
+ this._coordinates.removeElement(coordinate, id);
291
+ }
292
+ }
293
+
294
+ /**
295
+ * Unique names (symbols) of the chemical elements basis. E.g. `['Si', 'Li']`
296
+ */
297
+ get uniqueElements(): string[] {
298
+ return _.unique(this._elements.array);
299
+ }
300
+
301
+ /**
302
+ * Returns unique chemical elements with their count sorted by electronegativity.
303
+ * `{ "Fe": 4.0, "O": 8.0, "Li": 2.0}`.
304
+ */
305
+ get uniqueElementCountsSortedByElectronegativity() {
306
+ return _.chain(this.elements)
307
+ .sortBy("value")
308
+ .sortBy((x) => getElectronegativity(x.value))
309
+ .countBy((element) => element.value)
310
+ .value();
311
+ }
312
+
313
+ /**
314
+ * Returns chemical elements with their count wrt their original order in the basis.
315
+ * Note: entries for the same element separated by another element are considered separately.
316
+ * [{"count":1, "value":"Zr"}, {"count":23, "value":"H"}, {"count":11, "value":"Zr"}, {"count":1, "value":"H"}]
317
+ */
318
+ get elementCounts(): ElementCount[] {
319
+ const elementCounts: ElementCount[] = [];
320
+ this.getElementsAsObject().forEach((element, index) => {
321
+ const previousElement = this.getElementsAsObject()[index - 1];
322
+ if (previousElement && previousElement.value === element.value) {
323
+ const previousElementCount = elementCounts[elementCounts.length - 1];
324
+ previousElementCount.count += 1;
325
+ } else {
326
+ elementCounts.push({
327
+ count: 1,
328
+ value: element.value,
329
+ });
330
+ }
331
+ });
332
+ return elementCounts;
333
+ }
334
+
335
+ /**
336
+ * Reduced formula in IUPAC format. E.g., Na2SO4
337
+ */
338
+ get formula(): string {
339
+ const counts = this.uniqueElementCountsSortedByElectronegativity;
340
+ const countsValues = _.values(counts);
341
+ const gcd = countsValues.length > 1 ? math.gcd(...countsValues) : countsValues[0];
342
+ return _.pairs(counts)
343
+ .map((x) => x[0] + (x[1] / gcd === 1 ? "" : x[1] / gcd))
344
+ .reduce((mem, item) => {
345
+ return mem + item;
346
+ }, "");
347
+ }
348
+
349
+ /**
350
+ * Returns the unit cell formula as object `{ "Fe": 4.0, "O": 8.0, "Li": 2.0}`
351
+ */
352
+ get unitCellFormula(): string {
353
+ const counts = this.uniqueElementCountsSortedByElectronegativity;
354
+ return _.pairs(counts)
355
+ .map((x) => x[0] + (x[1] === 1 ? "" : x[1]))
356
+ .reduce((mem, item) => {
357
+ return mem + item;
358
+ }, "");
359
+ }
360
+
361
+ /**
362
+ * Returns a nested array with elements and their corresponding coordinates
363
+ * @example Output: [ ["Si", [0,0,0]], ["Si", [0.5,0.5,0.5]] ]
364
+ */
365
+ get elementsAndCoordinatesArray(): [string, Coordinate, string][] {
366
+ return this._elements.array.map((element, idx) => {
367
+ const coordinates = this.getCoordinateByIndex(idx);
368
+ const atomicLabel = this.atomicLabelsArray[idx];
369
+ return [element, coordinates, atomicLabel];
370
+ });
371
+ }
372
+
373
+ /**
374
+ * @summary Concatenates elements and sorts them in alphanumeric order.
375
+ * E.g.,
376
+ * ```
377
+ * elements: [{id: 0, value: 'Si'}, {id: 1, value: 'Si'}]
378
+ * coordinates: [{id: 0, value: [1,0,0]}, {id: 1, value: [0, 1, 0]}]
379
+ *
380
+ * result: "Si 0,1,0;Si 1,0,0"
381
+ * ```
382
+ * This guarantees the independence from the order in the elements array.
383
+ */
384
+ getAsSortedString(): string {
385
+ // make a copy to prevent modifying class values
386
+ const clsInstance = new Basis(this.toJSON());
387
+ clsInstance.toStandardRepresentation();
388
+ const standardRep = clsInstance.elementsAndCoordinatesArray.map((entry) => {
389
+ const element = entry[0];
390
+ const coordinate = entry[1];
391
+ const atomicLabel = entry[2];
392
+ const toleratedCoordinates = coordinate.map((x) => math.round(x, HASH_TOLERANCE));
393
+ return `${element}${atomicLabel} ${toleratedCoordinates.join()}`;
394
+ });
395
+ return `${standardRep.sort().join(";")};`;
396
+ }
397
+
398
+ /**
399
+ * Returns a string for hash calculation (in crystal units)
400
+ */
401
+ get hashString(): string {
402
+ const originallyInCartesianUnits = this.isInCartesianUnits;
403
+
404
+ this.toCrystal();
405
+ const hashString = this.getAsSortedString();
406
+
407
+ // preserve the original state
408
+ if (originallyInCartesianUnits) this.toCartesian();
409
+
410
+ return hashString;
411
+ }
412
+
413
+ /* Returns array of atomic labels E.g., ["1", "2", "", ""] */
414
+ get atomicLabelsArray(): string[] {
415
+ const labelsArray = [];
416
+ for (let i = 0; i < this.elements.length; i++) {
417
+ const labelObj = this.labels?.find((item) => item.id === i);
418
+ const atomicLabel = labelObj ? labelObj.value.toString() : "";
419
+ labelsArray.push(atomicLabel);
420
+ }
421
+ return labelsArray;
422
+ }
423
+
424
+ /* Returns array of elements with labels E.g., ["Fe1", "Fe2", "O", "O"] */
425
+ get elementsWithLabelsArray(): string[] {
426
+ const elements = this.elementsArray;
427
+ const labels = this.atomicLabelsArray;
428
+ const elementsWithLabels: string[] = [];
429
+ elements.forEach((symbol, idx) => elementsWithLabels.push(symbol + labels[idx]));
430
+ return elementsWithLabels;
431
+ }
432
+
433
+ /**
434
+ * Returns an array of strings with chemical elements and their atomic positions.
435
+ * E.g., ``` ['Si 0 0 0', 'Li 0.5 0.5 0.5']```
436
+ */
437
+ get atomicPositions(): string[] {
438
+ return this.elementsAndCoordinatesArray.map((entry, idx) => {
439
+ const element = entry[0];
440
+ const coordinate = entry[1];
441
+ const atomicLabel = this.atomicLabelsArray[idx];
442
+ return `${element}${atomicLabel} ${coordinate
443
+ .map((x) => s.sprintf("%14.9f", x).trim())
444
+ .join(" ")}`;
445
+ });
446
+ }
447
+
448
+ /**
449
+ * @summary Returns number of atoms in material
450
+ */
451
+ get nAtoms(): number {
452
+ return this._elements.array.length;
453
+ }
454
+
455
+ // helpers
456
+
457
+ /**
458
+ * @summary Returns true if bases are equal, otherwise - false.
459
+ * @param anotherBasisClsInstance {Basis} Another Basis.
460
+ */
461
+ isEqualTo(anotherBasisClsInstance: Basis): boolean {
462
+ return this.hashString === anotherBasisClsInstance.hashString;
463
+ }
464
+
465
+ /**
466
+ * @summary Returns true if basis cells are equal, otherwise - false.
467
+ * @param anotherBasisClsInstance {Basis} Another Basis.
468
+ */
469
+ hasEquivalentCellTo(anotherBasisClsInstance: Basis): boolean {
470
+ // this.cell {Array} - Cell Vectors 1, eg. [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]]
471
+ // prettier-ignore
472
+ return !this.cell
473
+ .map((vector, idx) => {
474
+ return math.vEqualWithTolerance(vector, anotherBasisClsInstance.cell[idx]);
475
+ })
476
+ .some((x) => !x);
477
+ }
478
+
479
+ /**
480
+ * @summary function returns the minimum basis lattice size for a structure.
481
+ * The lattice size is based on the atomic radius of an element if the basis contains a single atom.
482
+ * The lattice size is based on the maximum pairwise distance across a structure if the basis contains > 2 atoms.
483
+ */
484
+ getMinimumLatticeSize(latticeScalingFactor = nonPeriodicLatticeScalingFactor): number {
485
+ let latticeSizeAdditiveContribution = 0;
486
+ if (this._elements.array.length === 1) {
487
+ const elementSymbol = this._elements.getArrayElementByIndex(0);
488
+ latticeSizeAdditiveContribution = getElementAtomicRadius(elementSymbol);
489
+ }
490
+ const moleculeLatticeSize = this.maxPairwiseDistance * latticeScalingFactor;
491
+ const latticeSize = latticeSizeAdditiveContribution + moleculeLatticeSize;
492
+ return math.precise(latticeSize, 4);
493
+ }
494
+
495
+ /**
496
+ * @summary function returns an array of overlapping atoms within specified tolerance.
497
+ */
498
+ getOverlappingAtoms(): Overlap[] {
499
+ // to simplify calculations, convert to cartesian coordinates
500
+ this.toCartesian();
501
+ const { coordinates, elements } = this;
502
+ const overlaps: Overlap[] = [];
503
+ // temporary value for overlap approximation, where atoms most certainly can't be located
504
+ const overlapCoefficient = 0.75;
505
+
506
+ coordinates.forEach((entry1, i) => {
507
+ for (let j = i + 1; j < coordinates.length; j++) {
508
+ const entry2 = coordinates[j];
509
+ const el1 = elements[i].value;
510
+ const el2 = elements[j].value;
511
+
512
+ const tolerance =
513
+ overlapCoefficient *
514
+ (getElementAtomicRadius(el1) + getElementAtomicRadius(el2)); // in angstroms
515
+
516
+ // @ts-ignore
517
+ const distance = math.vDist(entry1.value, entry2.value) as number;
518
+ if (distance < tolerance) {
519
+ overlaps.push({
520
+ id1: i,
521
+ id2: j,
522
+ element1: el1,
523
+ element2: el2,
524
+ });
525
+ }
526
+ }
527
+ });
528
+
529
+ this.toCrystal();
530
+ return overlaps;
531
+ }
532
+
533
+ /**
534
+ * @summary function returns the max distance between pairs of basis coordinates by
535
+ * calculating the distance between pairs of basis coordinates.
536
+ * basis coordinates = [[x1, y1, z1], [x2, y2, z2], ... [xn, yn, zn]]
537
+ * n = last set of coordinates
538
+ * n-1 = second to last set of coordinates
539
+ *
540
+ * Iterate through pairs without redundancy.
541
+ * pair 0,1 pair 0,2 pair 0,3 ... pair 0,n
542
+ * - pair 1,2 pair 1,3 ... pair 1,n
543
+ * - - - pair 2,3 ... pair 2,n
544
+ * - - - ... ...
545
+ * - - - ... ... pair n-1, n
546
+ *
547
+ */
548
+ get maxPairwiseDistance(): number {
549
+ const originalUnits = this.units;
550
+ this.toCartesian();
551
+ let maxDistance = 0;
552
+ if (this._elements.array.length >= 2) {
553
+ for (let i = 0; i < this._elements.array.length; i++) {
554
+ for (let j = i + 1; j < this._elements.array.length; j++) {
555
+ const distance = math.vDist(
556
+ this._coordinates.getArrayElementByIndex(i),
557
+ this._coordinates.getArrayElementByIndex(j),
558
+ );
559
+ // @ts-ignore
560
+ if (distance > maxDistance) {
561
+ // @ts-ignore
562
+ maxDistance = distance;
563
+ }
564
+ }
565
+ }
566
+ }
567
+ if (originalUnits !== ATOMIC_COORD_UNITS.cartesian) this.toCrystal();
568
+ return math.precise(maxDistance, 4);
569
+ }
570
+
571
+ /**
572
+ * @summary Function takes basis coordinates and transposes them so that the values for each dimension of the
573
+ * the basis are in their own nested array.
574
+ * Then the center point for each dimension of the coordinates is calculated.
575
+ *
576
+ * initial basisCoordinates
577
+ * [[x1, y1, z1],
578
+ * [x2, y2, z2],
579
+ * [.., .., ..],
580
+ * [xn, yn, zn]]
581
+ *
582
+ * transposed basisCoordinates
583
+ * [[x1, x2, ...xn],
584
+ * [y1, y2, ...yn],
585
+ * [z1, z2, ...zn]]
586
+ *
587
+ * Returns an array = [xCenter, yCenter, zCenter]
588
+ */
589
+ get centerOfCoordinatesPoint(): number[] {
590
+ const transposedBasisCoordinates = math.transpose(this._coordinates.array);
591
+ const centerOfCoordinatesVectors = [];
592
+ for (let i = 0; i < 3; i++) {
593
+ const center = // @ts-ignore
594
+ transposedBasisCoordinates[i].reduce((a, b) => a + b) / this._elements.array.length;
595
+ centerOfCoordinatesVectors.push(math.precise(center, 4));
596
+ }
597
+ return centerOfCoordinatesVectors;
598
+ }
599
+
600
+ /**
601
+ * @summary Function translates coordinates by the vector passed as an argument.
602
+ */
603
+ translateByVector(translationVector: number[]) {
604
+ // @ts-ignore
605
+ this._coordinates.mapArrayInPlace((x) => math.add(x, translationVector));
606
+ }
607
+ }
@@ -0,0 +1,107 @@
1
+ import s from "underscore.string";
2
+
3
+ import { ArrayWithIds } from "../abstract/array_with_ids";
4
+ import { ObjectWithIdAndValue } from "../abstract/scalar_with_id";
5
+ import { AtomicConstraints, Constraint, ConstraintValue } from "../constraints/constraints";
6
+ import { Basis, BasisProps, BasisSchema } from "./basis";
7
+ import { Coordinate } from "./types";
8
+
9
+ export interface ConstrainedBasisProps extends BasisProps {
10
+ constraints: Constraint[];
11
+ }
12
+
13
+ export interface ConstrainedBasisJSON extends BasisSchema {
14
+ constraints: ObjectWithIdAndValue<ConstraintValue>[];
15
+ }
16
+
17
+ /**
18
+ * @summary Extension of the Basis class able to deal with atomic constraints.
19
+ * @extends Basis
20
+ */
21
+ export class ConstrainedBasis extends Basis {
22
+ _constraints: ArrayWithIds<ConstraintValue>;
23
+
24
+ /**
25
+ * Create a an array with ids.
26
+ * @param {Object} config
27
+ * @param {ArrayWithIds|Array} config.constraints - atomic constraints.
28
+ */
29
+ constructor(config: ConstrainedBasisProps) {
30
+ super(config);
31
+ this._constraints = new ArrayWithIds<ConstraintValue>(config.constraints); // `constraints` is an Array with ids
32
+ }
33
+
34
+ get constraints() {
35
+ return this._constraints.array;
36
+ }
37
+
38
+ set constraints(newConstraints: ConstraintValue[]) {
39
+ this._constraints = new ArrayWithIds(newConstraints);
40
+ }
41
+
42
+ getConstraintAsArray() {
43
+ return this._constraints;
44
+ }
45
+
46
+ get AtomicConstraints() {
47
+ return AtomicConstraints.fromArray(this.constraints);
48
+ }
49
+
50
+ /**
51
+ * Serialize class instance to JSON.
52
+ * @example As below:
53
+ {
54
+ ...Basis.toJSON(),
55
+ "constraints": [
56
+ {
57
+ "id" : 0,
58
+ "value" : [
59
+ 1,
60
+ 1,
61
+ 1
62
+ ]
63
+ },
64
+ ]
65
+ }
66
+ */
67
+ toJSON(): ConstrainedBasisJSON {
68
+ return {
69
+ ...super.toJSON(),
70
+ constraints: this._constraints.toJSON(),
71
+ };
72
+ }
73
+
74
+ getConstraintByIndex(idx: number): ConstraintValue {
75
+ return this._constraints.getArrayElementByIndex(idx) || [];
76
+ }
77
+
78
+ /**
79
+ * Helper function returning a nested array with [element, coordinates, constraints] as elements
80
+ */
81
+ get elementsCoordinatesConstraintsArray(): [string, Coordinate, ConstraintValue, string][] {
82
+ return this._elements.array.map((element, idx) => {
83
+ const coordinates = this.getCoordinateByIndex(idx);
84
+ const constraints = this.getConstraintByIndex(idx);
85
+ const atomicLabel = this.atomicLabelsArray[idx];
86
+ return [element, coordinates, constraints, atomicLabel];
87
+ });
88
+ }
89
+
90
+ /**
91
+ * Returns an array with atomic positions (with constraints) per atom stored as strings.
92
+ * E.g., ``` ['Si 0 0 0 0 1 0', 'Li 0.5 0.5 0.5 1 0 1']```
93
+ */
94
+ get atomicPositionsWithConstraints(): string[] {
95
+ return this.elementsCoordinatesConstraintsArray.map((entry) => {
96
+ const element = entry[0] + entry[3]; // element with label, Fe1
97
+ const coordinate = entry[1];
98
+ const constraint = entry[2];
99
+ return (
100
+ s.sprintf("%-4s", element) +
101
+ coordinate.map((x) => s.sprintf("%14.9f", x).trim()).join(" ") +
102
+ " " +
103
+ constraint.map((x) => (x ? 1 : 0)).join(" ")
104
+ );
105
+ });
106
+ }
107
+ }