@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,30 @@
|
|
|
1
|
+
import { MaterialSchema } from "@mat3ra/esse/lib/js/types";
|
|
2
|
+
import _ from "underscore";
|
|
3
|
+
import s from "underscore.string";
|
|
4
|
+
|
|
5
|
+
import { Lattice } from "../lattice/lattice";
|
|
6
|
+
import xyz from "./xyz";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Construct textual representation of a materialOrConfig according to Quantum ESPRESSO pw.x input format.
|
|
10
|
+
* @param materialOrConfig - material class instance or its config object
|
|
11
|
+
*/
|
|
12
|
+
function toEspressoFormat(materialOrConfig: MaterialSchema): string {
|
|
13
|
+
const l = new Lattice(materialOrConfig.lattice);
|
|
14
|
+
const vectors = l.vectorArrays;
|
|
15
|
+
const vectorsAsString = _.map(vectors, (v) => {
|
|
16
|
+
return `${s.sprintf("%14.9f", v[0])}\t${s.sprintf("%14.9f", v[1])}\t${s.sprintf(
|
|
17
|
+
"%14.9f",
|
|
18
|
+
v[2],
|
|
19
|
+
)}`;
|
|
20
|
+
}).join("\n");
|
|
21
|
+
return s.sprintf(
|
|
22
|
+
"CELL_PARAMETERS (angstroms)\n%s\n\nATOMIC_POSITIONS (crystal)\n%s",
|
|
23
|
+
vectorsAsString,
|
|
24
|
+
xyz.fromMaterial(materialOrConfig),
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export default {
|
|
29
|
+
toEspressoFormat,
|
|
30
|
+
};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import Poscar from "./poscar";
|
|
2
|
+
|
|
3
|
+
const NATIVE_FORMAT = {
|
|
4
|
+
JSON: "json",
|
|
5
|
+
POSCAR: "poscar",
|
|
6
|
+
CIF: "cif",
|
|
7
|
+
PWX: "pwx",
|
|
8
|
+
XYZ: "xyz",
|
|
9
|
+
UNKNOWN: "unknown",
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* @summary Detects the format of the input string
|
|
14
|
+
* @throws {Error} - If the input string is unknown format
|
|
15
|
+
* @param {string} text - input string to detect format
|
|
16
|
+
* @returns {NATIVE_FORMAT} - Format of the input string
|
|
17
|
+
*/
|
|
18
|
+
function detectFormat(text) {
|
|
19
|
+
const jsonRegex = /^\s*\{/;
|
|
20
|
+
if (jsonRegex.test(text)) return NATIVE_FORMAT.JSON;
|
|
21
|
+
if (Poscar.isPoscar(text)) return NATIVE_FORMAT.POSCAR;
|
|
22
|
+
|
|
23
|
+
return NATIVE_FORMAT.UNKNOWN;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* @summary Function to handle conversion from native formats
|
|
28
|
+
* @param {String} text - input string to detect format and convert
|
|
29
|
+
* @throws {Error} - If the input string is of unknown format
|
|
30
|
+
* @return {Object} - Material config
|
|
31
|
+
*/
|
|
32
|
+
function convertFromNativeFormat(text) {
|
|
33
|
+
const format = detectFormat(text);
|
|
34
|
+
|
|
35
|
+
switch (format) {
|
|
36
|
+
case NATIVE_FORMAT.JSON:
|
|
37
|
+
return JSON.parse(text);
|
|
38
|
+
case NATIVE_FORMAT.POSCAR:
|
|
39
|
+
return Poscar.fromPoscar(text);
|
|
40
|
+
case NATIVE_FORMAT.UNKNOWN:
|
|
41
|
+
throw new Error(`Unknown format`);
|
|
42
|
+
// TODO: add more formats
|
|
43
|
+
default:
|
|
44
|
+
throw new Error(`Unsupported format: ${format}`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export default {
|
|
49
|
+
detectFormat,
|
|
50
|
+
convertFromNativeFormat,
|
|
51
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import cif from "./cif";
|
|
2
|
+
import espresso from "./espresso";
|
|
3
|
+
import nativeFormatParsers from "./native_format_parsers";
|
|
4
|
+
import poscar from "./poscar";
|
|
5
|
+
import xyz from "./xyz";
|
|
6
|
+
|
|
7
|
+
export default {
|
|
8
|
+
xyz,
|
|
9
|
+
poscar,
|
|
10
|
+
cif,
|
|
11
|
+
espresso,
|
|
12
|
+
nativeFormatParsers,
|
|
13
|
+
};
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import s from "underscore.string";
|
|
2
|
+
|
|
3
|
+
import { ConstrainedBasis } from "../basis/constrained_basis";
|
|
4
|
+
import { Coordinate } from "../basis/types";
|
|
5
|
+
import { ATOMIC_COORD_UNITS } from "../constants";
|
|
6
|
+
import { Constraint, ConstraintValue } from "../constraints/constraints";
|
|
7
|
+
import { Lattice } from "../lattice/lattice";
|
|
8
|
+
import { Vector } from "../lattice/types";
|
|
9
|
+
import math from "../math";
|
|
10
|
+
import { MaterialJSON } from "../types";
|
|
11
|
+
|
|
12
|
+
const _print = (x: number, printFormat = "%14.9f") => s.sprintf(printFormat, math.precise(x));
|
|
13
|
+
const _latticeVectorsToString = (vectors: Vector[]) =>
|
|
14
|
+
vectors.map((v) => v.map((c) => _print(c)).join("\t")).join("\n");
|
|
15
|
+
const atomicConstraintsCharFromBool = (bool: boolean): string => (bool ? "T" : "F");
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Obtain a textual representation of a material in POSCAR format.
|
|
19
|
+
* @param materialOrConfig - material class instance or config object.
|
|
20
|
+
* @param omitConstraints - whether to discard constraints passed with material.
|
|
21
|
+
*/
|
|
22
|
+
function toPoscar(materialOrConfig: MaterialJSON, omitConstraints = false): string {
|
|
23
|
+
const lattice = new Lattice(materialOrConfig.lattice);
|
|
24
|
+
const vectorsAsString = _latticeVectorsToString(lattice.vectorArrays);
|
|
25
|
+
// @ts-ignore
|
|
26
|
+
const basis = new ConstrainedBasis({
|
|
27
|
+
...materialOrConfig.basis,
|
|
28
|
+
cell: lattice.vectorArrays,
|
|
29
|
+
});
|
|
30
|
+
const BasisLines: string[] = [];
|
|
31
|
+
let addSelectiveDynamics = false;
|
|
32
|
+
basis._elements.array.forEach((item, idx) => {
|
|
33
|
+
const coord = basis.getCoordinateByIndex(idx).map((x) => _print(x));
|
|
34
|
+
const constraintsAsString = omitConstraints
|
|
35
|
+
? ""
|
|
36
|
+
: basis.AtomicConstraints.getAsStringByIndex(idx, atomicConstraintsCharFromBool);
|
|
37
|
+
if (constraintsAsString && !omitConstraints) addSelectiveDynamics = true;
|
|
38
|
+
BasisLines.push([coord.join(" "), constraintsAsString, item].join(" "));
|
|
39
|
+
});
|
|
40
|
+
const basisContent = BasisLines.join("\n");
|
|
41
|
+
const elementsLine = basis.elementCounts.map((e) => e.value).join(" ");
|
|
42
|
+
const countsLine = basis.elementCounts.map((e) => parseInt(`${e.count}`, 10)).join(" ");
|
|
43
|
+
const coordsType =
|
|
44
|
+
materialOrConfig.basis.units === ATOMIC_COORD_UNITS.cartesian ? "cartesian" : "direct";
|
|
45
|
+
|
|
46
|
+
return [
|
|
47
|
+
materialOrConfig.name,
|
|
48
|
+
"1.0",
|
|
49
|
+
vectorsAsString,
|
|
50
|
+
elementsLine,
|
|
51
|
+
countsLine,
|
|
52
|
+
// add selective dynamics only if there are some constraints!
|
|
53
|
+
...(addSelectiveDynamics ? ["Selective dynamics"] : []),
|
|
54
|
+
coordsType,
|
|
55
|
+
basisContent,
|
|
56
|
+
].join("\n");
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* @summary calculates the number of atoms in a poscar file based on the summation of the numbers in line 7 of the file.
|
|
61
|
+
* Poscar file formatting: https://www.vasp.at/wiki/index.php/POSCAR
|
|
62
|
+
*/
|
|
63
|
+
export function atomsCount(poscarFileContent: string): number {
|
|
64
|
+
const atomsLine = poscarFileContent.split("\n")[6].split(/\s+/);
|
|
65
|
+
return atomsLine.map((x) => parseInt(x, 10)).reduce((a, b) => a + b);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Parses POSCAR file into a Material config object.
|
|
70
|
+
* @param fileContent - POSCAR file content.
|
|
71
|
+
* @return Material config.
|
|
72
|
+
*/
|
|
73
|
+
function fromPoscar(fileContent: string): object {
|
|
74
|
+
const lines = fileContent.split("\n");
|
|
75
|
+
|
|
76
|
+
const comment = lines[0];
|
|
77
|
+
const latticeConstant = parseFloat(lines[1].trim());
|
|
78
|
+
|
|
79
|
+
// Atom symbols and counts
|
|
80
|
+
const atomSymbols = lines[5].trim().split(/\s+/);
|
|
81
|
+
const atomCounts = lines[6].trim().split(/\s+/).map(Number);
|
|
82
|
+
|
|
83
|
+
// Check if selective dynamics and coordinates type is used
|
|
84
|
+
let selectiveDynamics = false;
|
|
85
|
+
let coordinateType = "";
|
|
86
|
+
let startLine = 7;
|
|
87
|
+
if (lines[startLine].trim()[0].toLowerCase() === "s") {
|
|
88
|
+
selectiveDynamics = true;
|
|
89
|
+
coordinateType = lines[8].trim()[0].toLowerCase();
|
|
90
|
+
startLine = 9;
|
|
91
|
+
} else {
|
|
92
|
+
coordinateType = lines[7].trim()[0].toLowerCase();
|
|
93
|
+
startLine = 8;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const elements: string[] = atomSymbols
|
|
97
|
+
.map((symbol: string, i: number): string[] => Array(atomCounts[i]).fill(symbol))
|
|
98
|
+
.reduce((a, b) => a.concat(b), []);
|
|
99
|
+
|
|
100
|
+
// Atom coordinates and constraints
|
|
101
|
+
const coordinates: Coordinate[] = [];
|
|
102
|
+
const constraints: Constraint[] = [];
|
|
103
|
+
let atomIndex = 0;
|
|
104
|
+
for (let i = 0; i < atomSymbols.length; i++) {
|
|
105
|
+
for (let j = 0; j < atomCounts[i]; j++) {
|
|
106
|
+
const lineComponents = lines[startLine + atomIndex].trim().split(/\s+/);
|
|
107
|
+
const coordinate: Coordinate = [
|
|
108
|
+
parseFloat(lineComponents[0]),
|
|
109
|
+
parseFloat(lineComponents[1]),
|
|
110
|
+
parseFloat(lineComponents[2]),
|
|
111
|
+
];
|
|
112
|
+
coordinates.push(coordinate);
|
|
113
|
+
|
|
114
|
+
// Add constraints if selective dynamics is used
|
|
115
|
+
if (selectiveDynamics) {
|
|
116
|
+
const constraint: ConstraintValue = [
|
|
117
|
+
lineComponents[3] === "T",
|
|
118
|
+
lineComponents[4] === "T",
|
|
119
|
+
lineComponents[5] === "T",
|
|
120
|
+
];
|
|
121
|
+
constraints.push({ id: j, value: constraint });
|
|
122
|
+
}
|
|
123
|
+
atomIndex += 1;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const lattice = Lattice.fromVectors({
|
|
128
|
+
a: lines[2].trim().split(/\s+/).map(Number) as Vector,
|
|
129
|
+
b: lines[3].trim().split(/\s+/).map(Number) as Vector,
|
|
130
|
+
c: lines[4].trim().split(/\s+/).map(Number) as Vector,
|
|
131
|
+
alat: latticeConstant,
|
|
132
|
+
units: "angstrom",
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
const basis = new ConstrainedBasis({
|
|
136
|
+
elements,
|
|
137
|
+
coordinates,
|
|
138
|
+
units: coordinateType === "c" ? ATOMIC_COORD_UNITS.cartesian : ATOMIC_COORD_UNITS.crystal,
|
|
139
|
+
cell: lattice.vectorArrays,
|
|
140
|
+
constraints,
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
const materialConfig = {
|
|
144
|
+
lattice: lattice.toJSON(),
|
|
145
|
+
basis: basis.toJSON(),
|
|
146
|
+
name: comment,
|
|
147
|
+
isNonPeriodic: false,
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
return materialConfig;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* @summary Checks if a string has a POSCAR format (first 8 lines are read)
|
|
155
|
+
* @param text - string to check
|
|
156
|
+
*/
|
|
157
|
+
function isPoscar(text: string): boolean {
|
|
158
|
+
const lines = text.split("\n");
|
|
159
|
+
|
|
160
|
+
// Checking number of lines, minimum requirement for POSCAR
|
|
161
|
+
if (lines.length < 7) {
|
|
162
|
+
return false;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Check the lattice constant (a single floating point number)
|
|
166
|
+
if (!/^[+-]?[\d.]+$/.test(lines[1].trim())) {
|
|
167
|
+
return false;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Check the lattice vectors (three lines, each with three floating point numbers)
|
|
171
|
+
for (let i = 2; i <= 4; i++) {
|
|
172
|
+
if (!/^[-+]?\d*\.\d+\s+[-+]?\d*\.\d+\s+[-+]?\d*\.\d+$/.test(lines[i].trim())) {
|
|
173
|
+
return false;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Check the atomic species line (alphabetic characters, space-separated)
|
|
178
|
+
if (!/^[a-zA-Z\s]+$/.test(lines[5].trim())) {
|
|
179
|
+
return false;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Check the number of atoms per species line (digits, space-separated)
|
|
183
|
+
if (!/^[\d\s]+$/.test(lines[6].trim())) {
|
|
184
|
+
return false;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Check the coordinate type line (only first character is neccessary, "s" or "S" for "Selective dynamics",
|
|
188
|
+
// "d" or "D" for "Direct", or "c" or "C" for "Cartesian")
|
|
189
|
+
if (!/^[sdc]/.test(lines[7].trim().toLowerCase())) {
|
|
190
|
+
return false;
|
|
191
|
+
}
|
|
192
|
+
return true;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
export default {
|
|
196
|
+
isPoscar,
|
|
197
|
+
toPoscar,
|
|
198
|
+
fromPoscar,
|
|
199
|
+
atomicConstraintsCharFromBool,
|
|
200
|
+
atomsCount,
|
|
201
|
+
};
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import { MaterialSchema } from "@mat3ra/esse/lib/js/types";
|
|
2
|
+
import _ from "underscore";
|
|
3
|
+
import s from "underscore.string";
|
|
4
|
+
|
|
5
|
+
import { Basis } from "../basis/basis";
|
|
6
|
+
import { ConstrainedBasis } from "../basis/constrained_basis";
|
|
7
|
+
import { Constraint } from "../constraints/constraints";
|
|
8
|
+
import { Lattice } from "../lattice/lattice";
|
|
9
|
+
import { Vector } from "../lattice/types";
|
|
10
|
+
import math from "../math";
|
|
11
|
+
import { InvalidLineError } from "./errors";
|
|
12
|
+
import { CombinatorialBasis } from "./xyz_combinatorial_basis";
|
|
13
|
+
|
|
14
|
+
// Regular expression for an XYZ line with atomic constraints, eg. Si 0.000000 0.500000 0.446678 1 1 1`
|
|
15
|
+
// eslint-disable-next-line max-len
|
|
16
|
+
const XYZ_LINE_REGEX =
|
|
17
|
+
/[A-Z][a-z]?(\d)?\s+((-?\d+\.?\d*|\.\d+)\s+(-?\d+\.?\d*|\.\d+)\s+(-?\d+\.?\d*|\.\d+)(\s+)?(\s+[0-1]\s+[0-1]\s+[0-1](\s+)?)?)$/;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Validates XYZ file's line. Line should be in following format "Si 0.5 0.5 0.5".
|
|
21
|
+
* Raises an error if line is in wrong format.
|
|
22
|
+
*/
|
|
23
|
+
function validateLine(xyzLine: string, index: number) {
|
|
24
|
+
const words = xyzLine.split(" ").filter((x) => x.trim() !== "");
|
|
25
|
+
|
|
26
|
+
if (!xyzLine.match(XYZ_LINE_REGEX)) {
|
|
27
|
+
throw new InvalidLineError(index, xyzLine);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const coordinates = [parseFloat(words[1]), parseFloat(words[2]), parseFloat(words[3])];
|
|
31
|
+
coordinates.forEach((num, i) => {
|
|
32
|
+
if (_.isNaN(num)) {
|
|
33
|
+
throw new Error(`Coordinates should be a number. Possible error in ${i} coordinate`);
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Validates that passed string is well-formed XYZ file.
|
|
40
|
+
*/
|
|
41
|
+
export function validate(xyzTxt: string) {
|
|
42
|
+
// The @types/underscore.string package does not provide interfaces for function chaining at the moment. Disable TS here
|
|
43
|
+
// @ts-ignore
|
|
44
|
+
s(xyzTxt)
|
|
45
|
+
.trim()
|
|
46
|
+
.lines()
|
|
47
|
+
.filter((x: string) => x.trim() !== "")
|
|
48
|
+
.forEach(validateLine);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface ParsedObject {
|
|
52
|
+
element: string;
|
|
53
|
+
coordinates: Vector;
|
|
54
|
+
constraints: [boolean, boolean, boolean];
|
|
55
|
+
label?: number;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Parses XYZ line and returns an object.
|
|
60
|
+
* @param line - line of text
|
|
61
|
+
*/
|
|
62
|
+
function _parseXYZLineAsWords(line: string): ParsedObject {
|
|
63
|
+
const words = s.words(line);
|
|
64
|
+
const elementWithOptionalLabel: string = words[0];
|
|
65
|
+
const element: string = elementWithOptionalLabel.replace(/\d$/, ""); // Fe1 => Fe
|
|
66
|
+
const constraint = (n: number) => parseInt(`${n}`, 10) !== 0;
|
|
67
|
+
|
|
68
|
+
const basisLineConfig: ParsedObject = {
|
|
69
|
+
element,
|
|
70
|
+
coordinates: [+words[1], +words[2], +words[3]],
|
|
71
|
+
// Below maps zero values to false (atom is fixed) and non-zero values to true (atom is moving)
|
|
72
|
+
constraints: [constraint(+words[4]), constraint(+words[5]), constraint(+words[6])],
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
if (elementWithOptionalLabel !== element) {
|
|
76
|
+
return {
|
|
77
|
+
...basisLineConfig,
|
|
78
|
+
label: parseInt(elementWithOptionalLabel[elementWithOptionalLabel.length - 1], 10),
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return basisLineConfig;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// TODO: reuse Basis Definition(s) from ESSE/Code.js instead
|
|
86
|
+
export interface BasisConfig {
|
|
87
|
+
elements: {
|
|
88
|
+
id: number;
|
|
89
|
+
value: string;
|
|
90
|
+
}[];
|
|
91
|
+
labels?: {
|
|
92
|
+
id: number;
|
|
93
|
+
value: number;
|
|
94
|
+
}[];
|
|
95
|
+
coordinates: {
|
|
96
|
+
id: number;
|
|
97
|
+
value: Vector;
|
|
98
|
+
}[];
|
|
99
|
+
units: string;
|
|
100
|
+
cell: Vector[];
|
|
101
|
+
constraints: Constraint[];
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Parse XYZ text for basis.
|
|
106
|
+
* @param txt Text
|
|
107
|
+
* @param units Coordinate units
|
|
108
|
+
* @param cell Basis Cell
|
|
109
|
+
*/
|
|
110
|
+
function toBasisConfig(txt: string, units = "angstrom", cell = Basis.defaultCell): BasisConfig {
|
|
111
|
+
// @ts-ignore
|
|
112
|
+
const lines: string[] = s(txt).trim().lines();
|
|
113
|
+
const listOfObjects = _.map(lines, _parseXYZLineAsWords);
|
|
114
|
+
|
|
115
|
+
const basisConfig = {
|
|
116
|
+
elements: listOfObjects.map((elm, idx) => {
|
|
117
|
+
return {
|
|
118
|
+
id: idx,
|
|
119
|
+
value: elm.element,
|
|
120
|
+
};
|
|
121
|
+
}),
|
|
122
|
+
coordinates: listOfObjects.map((elm, idx) => {
|
|
123
|
+
return {
|
|
124
|
+
id: idx,
|
|
125
|
+
value: elm.coordinates,
|
|
126
|
+
};
|
|
127
|
+
}),
|
|
128
|
+
units,
|
|
129
|
+
cell,
|
|
130
|
+
constraints: listOfObjects.map((elm, idx) => {
|
|
131
|
+
return {
|
|
132
|
+
id: idx,
|
|
133
|
+
value: elm.constraints,
|
|
134
|
+
};
|
|
135
|
+
}),
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
const labels: {
|
|
139
|
+
id: number;
|
|
140
|
+
value: number;
|
|
141
|
+
}[] = [];
|
|
142
|
+
|
|
143
|
+
listOfObjects.forEach((elm, idx) => {
|
|
144
|
+
if (elm.label) {
|
|
145
|
+
labels.push({
|
|
146
|
+
id: idx,
|
|
147
|
+
value: elm.label,
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
if (!_.isEmpty(labels)) {
|
|
153
|
+
return {
|
|
154
|
+
...basisConfig,
|
|
155
|
+
labels,
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return basisConfig;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Create XYZ from Basis class instance.
|
|
164
|
+
* @param basisClsInstance Basis class instance.
|
|
165
|
+
* @param printFormat Output format for coordinates.
|
|
166
|
+
* @param skipRounding Whether to round the numbers (ie. to avoid negative zeros).
|
|
167
|
+
* @return Basis string in XYZ format
|
|
168
|
+
*/
|
|
169
|
+
function fromBasis(
|
|
170
|
+
basisClsInstance: ConstrainedBasis,
|
|
171
|
+
printFormat = "%9.5f",
|
|
172
|
+
skipRounding = false,
|
|
173
|
+
) {
|
|
174
|
+
const XYZArray: string[] = [];
|
|
175
|
+
basisClsInstance._elements.array.forEach((item, idx) => {
|
|
176
|
+
// assume that _elements and _coordinates are indexed equivalently
|
|
177
|
+
const atomicLabel = basisClsInstance.atomicLabelsArray[idx];
|
|
178
|
+
const elementWithLabel = item + atomicLabel;
|
|
179
|
+
const element = s.sprintf("%-3s", elementWithLabel);
|
|
180
|
+
const coordinates = basisClsInstance.getCoordinateByIndex(idx).map((x) => {
|
|
181
|
+
return s.sprintf(printFormat, skipRounding ? x : math.precise(math.roundToZero(x)));
|
|
182
|
+
});
|
|
183
|
+
const constraints = basisClsInstance.constraints
|
|
184
|
+
? basisClsInstance.AtomicConstraints.getAsStringByIndex(idx)
|
|
185
|
+
: "";
|
|
186
|
+
XYZArray.push([element, coordinates.join(" "), constraints].join(" "));
|
|
187
|
+
});
|
|
188
|
+
return `${XYZArray.join("\n")}\n`;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Create XYZ from Material class instance (or its JSON config).
|
|
193
|
+
* @param materialOrConfig Material.
|
|
194
|
+
* @param fractional Coordinate units as fractional.
|
|
195
|
+
* @return Class Instance
|
|
196
|
+
*/
|
|
197
|
+
function fromMaterial(materialOrConfig: MaterialSchema, fractional = false): string {
|
|
198
|
+
const lattice = new Lattice(materialOrConfig.lattice);
|
|
199
|
+
// @ts-ignore
|
|
200
|
+
const basis = new ConstrainedBasis({
|
|
201
|
+
...materialOrConfig.basis,
|
|
202
|
+
cell: lattice.vectorArrays,
|
|
203
|
+
});
|
|
204
|
+
if (fractional) {
|
|
205
|
+
basis.toCrystal();
|
|
206
|
+
}
|
|
207
|
+
return fromBasis(basis, "%11.6f");
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
export default {
|
|
211
|
+
validate,
|
|
212
|
+
fromMaterial,
|
|
213
|
+
toBasisConfig,
|
|
214
|
+
fromBasis,
|
|
215
|
+
CombinatorialBasis,
|
|
216
|
+
};
|