@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.
- package/.babelrc +10 -0
- package/.eslintrc.json +11 -0
- package/.mocharc.json +5 -0
- package/.prettierignore +1 -0
- package/.prettierrc +6 -0
- package/LICENSE.md +15 -0
- package/README.md +167 -0
- package/dist/abstract/array_with_ids.d.ts +43 -0
- package/dist/abstract/array_with_ids.js +88 -0
- package/dist/abstract/scalar_with_id.d.ts +25 -0
- package/dist/abstract/scalar_with_id.js +44 -0
- package/dist/basis/basis.d.ts +269 -0
- package/dist/basis/basis.js +499 -0
- package/dist/basis/constrained_basis.d.ts +56 -0
- package/dist/basis/constrained_basis.js +90 -0
- package/dist/basis/types.d.ts +1 -0
- package/dist/basis/types.js +2 -0
- package/dist/cell/cell.d.ts +45 -0
- package/dist/cell/cell.js +88 -0
- package/dist/cell/conventional_cell.d.ts +22 -0
- package/dist/cell/conventional_cell.js +83 -0
- package/dist/cell/primitive_cell.d.ts +9 -0
- package/dist/cell/primitive_cell.js +166 -0
- package/dist/constants.d.ts +2 -0
- package/dist/constants.js +25 -0
- package/dist/constraints/constraints.d.ts +45 -0
- package/dist/constraints/constraints.js +49 -0
- package/dist/lattice/lattice.d.ts +104 -0
- package/dist/lattice/lattice.js +208 -0
- package/dist/lattice/lattice_bravais.d.ts +59 -0
- package/dist/lattice/lattice_bravais.js +120 -0
- package/dist/lattice/lattice_vectors.d.ts +46 -0
- package/dist/lattice/lattice_vectors.js +98 -0
- package/dist/lattice/reciprocal/lattice_reciprocal.d.ts +75 -0
- package/dist/lattice/reciprocal/lattice_reciprocal.js +148 -0
- package/dist/lattice/reciprocal/paths.d.ts +24 -0
- package/dist/lattice/reciprocal/paths.js +136 -0
- package/dist/lattice/reciprocal/symmetry_points.d.ts +8 -0
- package/dist/lattice/reciprocal/symmetry_points.js +866 -0
- package/dist/lattice/types.d.ts +49 -0
- package/dist/lattice/types.js +127 -0
- package/dist/lattice/unit_cell.d.ts +30 -0
- package/dist/lattice/unit_cell.js +31 -0
- package/dist/made.d.ts +40 -0
- package/dist/made.js +39 -0
- package/dist/material.d.ts +1562 -0
- package/dist/material.js +317 -0
- package/dist/math.d.ts +395 -0
- package/dist/math.js +7 -0
- package/dist/parsers/cif.d.ts +10 -0
- package/dist/parsers/cif.js +21 -0
- package/dist/parsers/errors.d.ts +5 -0
- package/dist/parsers/errors.js +11 -0
- package/dist/parsers/espresso.d.ts +10 -0
- package/dist/parsers/espresso.js +24 -0
- package/dist/parsers/native_format_parsers.d.ts +26 -0
- package/dist/parsers/native_format_parsers.js +52 -0
- package/dist/parsers/parsers.d.ts +13 -0
- package/dist/parsers/parsers.js +17 -0
- package/dist/parsers/poscar.d.ts +31 -0
- package/dist/parsers/poscar.js +180 -0
- package/dist/parsers/xyz.d.ts +62 -0
- package/dist/parsers/xyz.js +167 -0
- package/dist/parsers/xyz_combinatorial_basis.d.ts +64 -0
- package/dist/parsers/xyz_combinatorial_basis.js +241 -0
- package/dist/tools/basis.d.ts +22 -0
- package/dist/tools/basis.js +100 -0
- package/dist/tools/cell.d.ts +9 -0
- package/dist/tools/cell.js +39 -0
- package/dist/tools/index.d.ts +11 -0
- package/dist/tools/index.js +15 -0
- package/dist/tools/material.d.ts +25 -0
- package/dist/tools/material.js +54 -0
- package/dist/tools/supercell.d.ts +22 -0
- package/dist/tools/supercell.js +62 -0
- package/dist/tools/surface.d.ts +10 -0
- package/dist/tools/surface.js +147 -0
- package/dist/types.d.ts +3 -0
- package/dist/types.js +2 -0
- package/package.json +89 -0
- package/pyproject.toml +77 -0
- package/src/js/abstract/array_with_ids.ts +100 -0
- package/src/js/abstract/scalar_with_id.ts +53 -0
- package/src/js/basis/basis.ts +607 -0
- package/src/js/basis/constrained_basis.ts +107 -0
- package/src/js/basis/types.ts +1 -0
- package/src/js/cell/cell.ts +109 -0
- package/src/js/cell/conventional_cell.ts +89 -0
- package/src/js/cell/primitive_cell.ts +189 -0
- package/src/js/constants.js +4 -0
- package/src/js/constraints/constraints.ts +63 -0
- package/src/js/lattice/lattice.ts +229 -0
- package/src/js/lattice/lattice_bravais.ts +170 -0
- package/src/js/lattice/lattice_vectors.ts +126 -0
- package/src/js/lattice/reciprocal/lattice_reciprocal.js +155 -0
- package/src/js/lattice/reciprocal/paths.js +134 -0
- package/src/js/lattice/reciprocal/symmetry_points.ts +886 -0
- package/src/js/lattice/types.ts +142 -0
- package/src/js/lattice/unit_cell.ts +66 -0
- package/src/js/made.js +36 -0
- package/src/js/material.ts +398 -0
- package/src/js/math.js +6 -0
- package/src/js/parsers/cif.js +22 -0
- package/src/js/parsers/errors.js +7 -0
- package/src/js/parsers/espresso.ts +30 -0
- package/src/js/parsers/native_format_parsers.js +51 -0
- package/src/js/parsers/parsers.js +13 -0
- package/src/js/parsers/poscar.ts +201 -0
- package/src/js/parsers/xyz.ts +216 -0
- package/src/js/parsers/xyz_combinatorial_basis.js +243 -0
- package/src/js/tools/basis.js +116 -0
- package/src/js/tools/cell.js +36 -0
- package/src/js/tools/index.js +11 -0
- package/src/js/tools/material.js +60 -0
- package/src/js/tools/supercell.ts +80 -0
- package/src/js/tools/surface.js +176 -0
- package/src/js/types.ts +4 -0
- package/src/py/__init__.py +0 -0
- package/src/py/mat3ra/__init__.py +0 -0
- package/src/py/mat3ra/made/__init__.py +5 -0
- package/tests/.gitattributes +1 -0
- package/tests/fixtures/AsGe-basis.json +3 -0
- package/tests/fixtures/C2H4-translated.json +3 -0
- package/tests/fixtures/C2H4.json +3 -0
- package/tests/fixtures/FeLiSi-basis.json +3 -0
- package/tests/fixtures/FeO.json +3 -0
- package/tests/fixtures/Ge2-basis.json +3 -0
- package/tests/fixtures/Graphene.json +3 -0
- package/tests/fixtures/Graphene.poscar +3 -0
- package/tests/fixtures/H2+H-final.json +3 -0
- package/tests/fixtures/H2+H-image.json +3 -0
- package/tests/fixtures/H2+H-initial.json +3 -0
- package/tests/fixtures/H2O.poscar +3 -0
- package/tests/fixtures/LiFeSi-basis.json +3 -0
- package/tests/fixtures/Na.json +3 -0
- package/tests/fixtures/Na4Cl4-cartesian.json +3 -0
- package/tests/fixtures/Na4Cl4.json +3 -0
- package/tests/fixtures/Na4Cl4.poscar +3 -0
- package/tests/fixtures/Ni-hex.json +3 -0
- package/tests/fixtures/Ni-hex.poscar +3 -0
- package/tests/fixtures/OSi-basis.json +3 -0
- package/tests/fixtures/Si-hex.json +3 -0
- package/tests/fixtures/Si-hex.poscar +3 -0
- package/tests/fixtures/Si-pwscf.in +3 -0
- package/tests/fixtures/Si-slab.json +3 -0
- package/tests/fixtures/Si-supercell.json +3 -0
- package/tests/fixtures/Si.json +3 -0
- package/tests/fixtures/Si2-basis-repeated.json +3 -0
- package/tests/fixtures/Si2-basis.json +3 -0
- package/tests/fixtures/Zr1H23Zr1H1.json +3 -0
- package/tests/fixtures/Zr1H23Zr1H1.poscar +3 -0
- package/tests/fixtures/atomic-constraints.json +3 -0
- package/tests/js/basis/basis.js +221 -0
- package/tests/js/cell/cell.js +21 -0
- package/tests/js/cell/primitive_cell.js +17 -0
- package/tests/js/constraints/constraints.js +27 -0
- package/tests/js/enums.js +40 -0
- package/tests/js/lattice/lattice.js +31 -0
- package/tests/js/lattice/lattice_bravais.js +17 -0
- package/tests/js/lattice/lattice_reciprocal.js +99 -0
- package/tests/js/lattice/lattice_vectors.js +10 -0
- package/tests/js/material.test.js +11 -0
- package/tests/js/parsers/espresso.js +12 -0
- package/tests/js/parsers/native_formats.js +30 -0
- package/tests/js/parsers/poscar.js +21 -0
- package/tests/js/parsers/xyz.js +25 -0
- package/tests/js/parsers/xyz_combinatorial_basis.js +153 -0
- package/tests/js/setup.js +6 -0
- package/tests/js/tools/basis.js +18 -0
- package/tests/js/tools/supercell.js +23 -0
- package/tests/js/tools/surface.js +12 -0
- package/tests/js/utils.js +17 -0
- package/tests/py/__init__.py +0 -0
- package/tests/py/unit/__init__.py +0 -0
- package/tests/py/unit/test_sample.py +10 -0
- 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
|
+
}
|