diff --git a/.github/workflows/cicd.yml b/.github/workflows/cicd.yml index e3e6c484d..624284865 100644 --- a/.github/workflows/cicd.yml +++ b/.github/workflows/cicd.yml @@ -18,6 +18,8 @@ jobs: steps: - name: Checkout this repository uses: actions/checkout@v2 + with: + lfs: true - name: Checkout actions repository uses: actions/checkout@v2 @@ -40,6 +42,8 @@ jobs: steps: - name: Checkout this repository uses: actions/checkout@v2 + with: + lfs: true - name: Checkout actions repository uses: actions/checkout@v2 diff --git a/src/default_material.js b/src/default_material.js new file mode 100644 index 000000000..7b5736f7e --- /dev/null +++ b/src/default_material.js @@ -0,0 +1,56 @@ +import { ATOMIC_COORD_UNITS, units } from "./constants"; +import { LATTICE_TYPE } from "./lattice/types"; + +export const defaultMaterialConfig = { + name: "Silicon FCC", + basis: { + elements: [ + { + id: 1, + value: "Si", + }, + { + id: 2, + value: "Si", + }, + ], + coordinates: [ + { + id: 1, + value: [0.0, 0.0, 0.0], + }, + { + id: 2, + value: [0.25, 0.25, 0.25], + }, + ], + units: ATOMIC_COORD_UNITS.crystal, + }, + lattice: { + // Primitive cell for Diamond FCC Silicon at ambient conditions + type: LATTICE_TYPE.FCC, + a: 3.867, + b: 3.867, + c: 3.867, + alpha: 60, + beta: 60, + gamma: 60, + units: { + length: units.angstrom, + angle: units.degree, + }, + }, +}; + +/* + * Function returns the defaultMaterial object. + * @returns {Object} + */ +export function getDefaultMaterialConfig() { + return defaultMaterialConfig; +} + +export default { + defaultMaterialConfig, + getDefaultMaterialConfig, +}; diff --git a/src/made.js b/src/made.js index 6d4c1fef0..022e408d4 100644 --- a/src/made.js +++ b/src/made.js @@ -2,10 +2,11 @@ import { ArrayWithIds } from "./abstract/array_with_ids"; import { Basis } from "./basis/basis"; import { ATOMIC_COORD_UNITS, coefficients, tolerance, units } from "./constants"; import { AtomicConstraints } from "./constraints/constraints"; +import { defaultMaterialConfig, getDefaultMaterialConfig } from "./default_material"; import { Lattice, nonPeriodicLatticeScalingFactor } from "./lattice/lattice"; import { ReciprocalLattice } from "./lattice/reciprocal/lattice_reciprocal"; import { DEFAULT_LATTICE_UNITS, LATTICE_TYPE_CONFIGS } from "./lattice/types"; -import { defaultMaterialConfig, Material } from "./material"; +import { Material } from "./material"; import MadeMath from "./math"; import parsers from "./parsers/parsers"; import tools from "./tools/index"; @@ -19,6 +20,7 @@ export const Made = { Material, defaultMaterialConfig, + getDefaultMaterialConfig, Lattice, nonPeriodicLatticeScalingFactor, ReciprocalLattice, diff --git a/src/material.js b/src/material.js index 871e4b2c2..469434efc 100644 --- a/src/material.js +++ b/src/material.js @@ -7,53 +7,10 @@ import { PRIMITIVE_TO_CONVENTIONAL_CELL_LATTICE_TYPES, PRIMITIVE_TO_CONVENTIONAL_CELL_MULTIPLIERS, } from "./cell/conventional_cell"; -import { ATOMIC_COORD_UNITS, units } from "./constants"; import { Lattice } from "./lattice/lattice"; -import { LATTICE_TYPE } from "./lattice/types"; import parsers from "./parsers/parsers"; import supercellTools from "./tools/supercell"; -export const defaultMaterialConfig = { - name: "Silicon FCC", - basis: { - elements: [ - { - id: 1, - value: "Si", - }, - { - id: 2, - value: "Si", - }, - ], - coordinates: [ - { - id: 1, - value: [0.0, 0.0, 0.0], - }, - { - id: 2, - value: [0.25, 0.25, 0.25], - }, - ], - units: ATOMIC_COORD_UNITS.crystal, - }, - lattice: { - // Primitive cell for Diamond FCC Silicon at ambient conditions - type: LATTICE_TYPE.FCC, - a: 3.867, - b: 3.867, - c: 3.867, - alpha: 60, - beta: 60, - gamma: 60, - units: { - length: units.angstrom, - angle: units.degree, - }, - }, -}; - export class Material { constructor(config) { this._json = lodash.cloneDeep(config || {}); @@ -111,7 +68,7 @@ export class Material { * @returns {Object} */ getDerivedPropertyByName(name) { - return this.getDerivedProperties().find(x => x.name === name); + return this.getDerivedProperties().find((x) => x.name === name); } /** @@ -119,7 +76,7 @@ export class Material { * @returns {Array} */ getDerivedProperties() { - return this.prop('derivedProperties', []); + return this.prop("derivedProperties", []); } /** @@ -197,12 +154,11 @@ export class Material { * @returns {String} */ getInchiStringForHash() { - const inchi = this.getDerivedPropertyByName('inchi'); + const inchi = this.getDerivedPropertyByName("inchi"); if (inchi) { - return inchi.value - } else { - throw new Error("Hash cannot be created. Missing InChI string in derivedProperties") + return inchi.value; } + throw new Error("Hash cannot be created. Missing InChI string in derivedProperties"); } /** @@ -215,10 +171,11 @@ export class Material { * @param salt {String} Salt for hashing, empty string by default. * @param isScaled {Boolean} Whether to scale the lattice parameter 'a' to 1. */ - calculateHash(salt = '', isScaled = false) { + calculateHash(salt = "", isScaled = false) { let message; if (!this.isNonPeriodic) { - message = this.Basis.hashString + "#" + this.Lattice.getHashString(isScaled) + "#" + salt; + message = + this.Basis.hashString + "#" + this.Lattice.getHashString(isScaled) + "#" + salt; } else { message = this.getInchiStringForHash(); } @@ -226,11 +183,11 @@ export class Material { } set hash(hash) { - this.setProp('hash', hash); + this.setProp("hash", hash); } get hash() { - return this.prop('hash'); + return this.prop("hash"); } /** diff --git a/src/parsers/parsers.js b/src/parsers/parsers.js index 2e9924c32..1f088fe0c 100644 --- a/src/parsers/parsers.js +++ b/src/parsers/parsers.js @@ -3,9 +3,27 @@ import espresso from "./espresso"; import poscar from "./poscar"; import xyz from "./xyz"; +/** + * Function returns the number of atoms in a file using the proper parser function based on the file extension. + * @param {String} fileExtension + * @param {String} fileContent + * @returns {Number} + */ +export function getNumberOfAtomsInFileByExtension(fileExtension, fileContent) { + let numberOfAtoms = 0; + if (fileExtension === "poscar") { + numberOfAtoms = poscar.getAtomsCount(fileContent); + } + if (fileExtension === "xyz") { + numberOfAtoms = xyz.getAtomsCount(fileContent); + } + return numberOfAtoms; +} + export default { xyz, poscar, cif, espresso, + getNumberOfAtomsInFileByExtension, }; diff --git a/src/parsers/poscar.js b/src/parsers/poscar.js index 17e0244e9..4b991fb26 100644 --- a/src/parsers/poscar.js +++ b/src/parsers/poscar.js @@ -2,8 +2,10 @@ import s from "underscore.string"; import { ConstrainedBasis } from "../basis/constrained_basis"; import { ATOMIC_COORD_UNITS } from "../constants"; +import { getDefaultMaterialConfig } from "../default_material"; import { Lattice } from "../lattice/lattice"; import math from "../math"; +import xyz from "./xyz"; const _print = (x, printFormat = "%14.9f") => s.sprintf(printFormat, math.precise(x)); const _latticeVectorsToString = (vectors) => @@ -58,13 +60,44 @@ function toPoscar(materialOrConfig, omitConstraints = false) { * @param poscarFileContent * @returns {Number} */ -export function atomsCount(poscarFileContent) { +export function getAtomsCount(poscarFileContent) { const atomsLine = poscarFileContent.split("\n")[6].split(" "); return atomsLine.map((x) => parseInt(x, 10)).reduce((a, b) => a + b); } +/** + * Converts XYZ formated string content to POSCAR formated string content. + * @param xyzContent + * @returns {string} + */ +export function convertFromXyz(xyzContent) { + const xyzConfig = { ...getDefaultMaterialConfig() }; + const xyzArray = xyzContent.split(/\r?\n/); + const xyzArrayBasisOnly = xyzArray.slice(2, -1); + const xyzBasis = xyzArrayBasisOnly.join("\n"); + xyzConfig.basis = xyz.toBasisConfig(xyzBasis); + xyzConfig.basis.units = "cartesian"; + return toPoscar(xyzConfig); +} + +/** + * Function converts a string of content into a POSCAR formatted string of content. The function called to convert the + * format is determined by the format type passed to the function. + * @param {String} xyzContent + * @param {String} format + * @returns {String} + */ +export function convertFromOtherFormat(content, format) { + if (format === "xyz") { + return convertFromXyz(content); + } + return content; +} + export default { toPoscar, atomicConstraintsCharFromBool, - atomsCount, + getAtomsCount, + convertFromOtherFormat, + convertFromXyz, }; diff --git a/src/parsers/xyz.js b/src/parsers/xyz.js index 20da6e54d..070700c39 100644 --- a/src/parsers/xyz.js +++ b/src/parsers/xyz.js @@ -140,10 +140,21 @@ function fromMaterial(materialOrConfig, fractional = false) { return fromBasis(basis, "%11.6f"); } +/** + * Function splits the xyzFile string at new lines and then returns the first element of the array. + * The first line of the XYZ file should contain the number of atoms in the structure. + * @param {String} xyzFile + * @returns {Number} + */ +export function getAtomsCount(xyzFile) { + return parseInt(xyzFile.split(/\r?\n/)[0], 10); +} + export default { validate, fromMaterial, toBasisConfig, fromBasis, CombinatorialBasis, + getAtomsCount, }; diff --git a/tests/enums.js b/tests/enums.js index 1b876f279..99066d6c9 100644 --- a/tests/enums.js +++ b/tests/enums.js @@ -31,3 +31,6 @@ export const SiPWSCFInput = readFile(path.join(FIXTURES_DIR, "Si-pwscf.in")); export const Zr1H23Zr1H1 = readJSONFile(path.join(FIXTURES_DIR, "Zr1H23Zr1H1.json")); export const Zr1H23Zr1H1Poscar = readFile(path.join(FIXTURES_DIR, "Zr1H23Zr1H1.poscar")); export const H2O = readFile(path.join(FIXTURES_DIR, "H2O.poscar")); + +export const CH4 = readFile(path.join(FIXTURES_DIR, "CH4.xyz")); +export const CH4POSCAR = readFile(path.join(FIXTURES_DIR, "CH4.poscar")); diff --git a/tests/fixtures/CH4.poscar b/tests/fixtures/CH4.poscar new file mode 100644 index 000000000..025bf40a9 --- /dev/null +++ b/tests/fixtures/CH4.poscar @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7a0be33e7640e5f03bbab1c2d130eaa01f791710739919c0224593f8d9d15c95 +size 457 diff --git a/tests/fixtures/CH4.xyz b/tests/fixtures/CH4.xyz new file mode 100644 index 000000000..e0ea7d3ef --- /dev/null +++ b/tests/fixtures/CH4.xyz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:78e5d267cfe21a127d63beef525912bcff256e5b955c77a944b1a1c59721c989 +size 203 diff --git a/tests/parsers/poscar.js b/tests/parsers/poscar.js index c4642fb1a..cf61664c4 100644 --- a/tests/parsers/poscar.js +++ b/tests/parsers/poscar.js @@ -1,8 +1,17 @@ import { expect } from "chai"; import { Material } from "../../src/material"; -import { atomsCount } from "../../src/parsers/poscar"; -import { H2O, Na4Cl4, Na4Cl4Poscar, Zr1H23Zr1H1, Zr1H23Zr1H1Poscar } from "../enums"; +import { convertFromOtherFormat, getAtomsCount } from "../../src/parsers/poscar"; +import { + CH4, + CH4POSCAR, + H2O, + Na4Cl4, + Na4Cl4Poscar, + Zr1H23Zr1H1, + Zr1H23Zr1H1Poscar, +} from "../enums"; +import { assertDeepAlmostEqual } from "../utils"; describe("Parsers.POSCAR", () => { it("should return a valid poscar", () => { @@ -16,6 +25,9 @@ describe("Parsers.POSCAR", () => { }); it("should return the number of atoms for a molecule in a poscar file", () => { - expect(atomsCount(H2O)).to.be.equal(3); + expect(getAtomsCount(H2O)).to.be.equal(3); + }); + it("should return the xyz file content in poscar file format", () => { + assertDeepAlmostEqual(convertFromOtherFormat(CH4, "xyz"), CH4POSCAR); }); }); diff --git a/tests/parsers/xyz.js b/tests/parsers/xyz.js index 6d6856277..fe3090e71 100644 --- a/tests/parsers/xyz.js +++ b/tests/parsers/xyz.js @@ -1,5 +1,8 @@ +import { expect } from "chai"; + import parsers from "../../src/parsers/parsers"; -import { Si } from "../enums"; +import { getAtomsCount } from "../../src/parsers/xyz"; +import { CH4, Si } from "../enums"; import { assertDeepAlmostEqual } from "../utils"; describe("Parsers:XYZ", () => { @@ -11,4 +14,7 @@ describe("Parsers:XYZ", () => { "units", ]); }); + it("should return the number of atoms for a molecule in an xyz file", () => { + expect(getAtomsCount(CH4)).to.be.equal(5); + }); });