@mat3ra/made 2026.5.28-0 → 2026.6.11-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/constants.json ADDED
@@ -0,0 +1,13 @@
1
+ {
2
+ "molecule": {
3
+ "nonPeriodicMinimumLatticeSize": 3.0,
4
+ "diatomicLatticePaddingFactor": 3.0,
5
+ "molecularLatticePaddingFactor": 2.0
6
+ },
7
+ "precision": {
8
+ "coordinatesCrystal": 9,
9
+ "coordinatesCartesian": 6,
10
+ "latticeLengths": 6,
11
+ "latticeAngles": 4
12
+ }
13
+ }
@@ -147,11 +147,12 @@ export declare class Basis extends InMemoryEntity implements BasisSchema {
147
147
  */
148
148
  hasEquivalentCellTo(anotherBasisClsInstance: Basis): boolean;
149
149
  /**
150
- * @summary function returns the minimum basis lattice size for a structure.
151
- * The lattice size is based on the atomic radius of an element if the basis contains a single atom.
152
- * The lattice size is based on the maximum pairwise distance across a structure if the basis contains > 2 atoms.
150
+ * @summary Returns the minimum cubic lattice size for a non-periodic structure.
151
+ * Single-atom structures use the element atomic radius floored at defaultMinimumLatticeSize.
152
+ * Multi-atom structures scale the maximum pairwise distance by 3 when W=min/max pairwise distance
153
+ * is ~1 (diatomic), otherwise by 2.
153
154
  */
154
- getMinimumLatticeSize(latticeScalingFactor?: number): number;
155
+ getMinimumLatticeSize(defaultMinimumLatticeSize?: number): number;
155
156
  /**
156
157
  * @summary function returns an array of overlapping atoms within specified tolerance.
157
158
  */
@@ -172,6 +173,8 @@ export declare class Basis extends InMemoryEntity implements BasisSchema {
172
173
  *
173
174
  */
174
175
  get maxPairwiseDistance(): number;
176
+ get minPairwiseDistance(): number;
177
+ getPairwiseDistanceExtremum(extremum: "max" | "min"): number;
175
178
  /**
176
179
  * @summary Function takes basis coordinates and transposes them so that the values for each dimension of the
177
180
  * the basis are in their own nested array.
@@ -366,19 +366,25 @@ class Basis extends entity_1.InMemoryEntity {
366
366
  .some((x) => !x);
367
367
  }
368
368
  /**
369
- * @summary function returns the minimum basis lattice size for a structure.
370
- * The lattice size is based on the atomic radius of an element if the basis contains a single atom.
371
- * The lattice size is based on the maximum pairwise distance across a structure if the basis contains > 2 atoms.
369
+ * @summary Returns the minimum cubic lattice size for a non-periodic structure.
370
+ * Single-atom structures use the element atomic radius floored at defaultMinimumLatticeSize.
371
+ * Multi-atom structures scale the maximum pairwise distance by 3 when W=min/max pairwise distance
372
+ * is ~1 (diatomic), otherwise by 2.
372
373
  */
373
- getMinimumLatticeSize(latticeScalingFactor = lattice_1.nonPeriodicLatticeScalingFactor) {
374
- let latticeSizeAdditiveContribution = 0;
374
+ getMinimumLatticeSize(defaultMinimumLatticeSize = lattice_1.defaultNonPeriodicMinimumLatticeSize) {
375
375
  if (this._elements.values.length === 1) {
376
376
  const elementSymbol = this._elements.getElementValueByIndex(0);
377
- latticeSizeAdditiveContribution = (0, periodic_table_js_1.getElementAtomicRadius)(elementSymbol);
377
+ const atomicRadius = (0, periodic_table_js_1.getElementAtomicRadius)(elementSymbol);
378
+ return Math.max(defaultMinimumLatticeSize, atomicRadius);
378
379
  }
379
- const moleculeLatticeSize = this.maxPairwiseDistance * latticeScalingFactor;
380
- const latticeSize = latticeSizeAdditiveContribution + moleculeLatticeSize;
381
- return latticeSize;
380
+ const maxDistance = this.maxPairwiseDistance;
381
+ const minDistance = this.minPairwiseDistance;
382
+ const widthRatio = maxDistance > 0 ? minDistance / maxDistance : 1;
383
+ const latticeScalingFactor = math_1.math.almostEqual(widthRatio, 1)
384
+ ? lattice_1.diatomicLatticePaddingFactor
385
+ : lattice_1.molecularLatticePaddingFactor;
386
+ const moleculeLatticeSize = maxDistance * latticeScalingFactor;
387
+ return Math.max(defaultMinimumLatticeSize, moleculeLatticeSize);
382
388
  }
383
389
  /**
384
390
  * @summary function returns an array of overlapping atoms within specified tolerance.
@@ -428,22 +434,33 @@ class Basis extends entity_1.InMemoryEntity {
428
434
  *
429
435
  */
430
436
  get maxPairwiseDistance() {
437
+ return this.getPairwiseDistanceExtremum("max");
438
+ }
439
+ get minPairwiseDistance() {
440
+ return this.getPairwiseDistanceExtremum("min");
441
+ }
442
+ getPairwiseDistanceExtremum(extremum) {
431
443
  const originalUnits = this.units;
432
444
  this.toCartesian();
433
- let maxDistance = 0;
445
+ let resultDistance = extremum === "max" ? 0 : Infinity;
434
446
  if (this._elements.values.length >= 2) {
435
447
  for (let i = 0; i < this._elements.values.length; i++) {
436
448
  for (let j = i + 1; j < this._elements.values.length; j++) {
437
449
  const distance = math_1.math.vDist(this._coordinates.getElementValueByIndex(i), this._coordinates.getElementValueByIndex(j));
438
- if (distance && distance > maxDistance) {
439
- maxDistance = distance;
450
+ if (!distance)
451
+ continue;
452
+ if (extremum === "max" && distance > resultDistance) {
453
+ resultDistance = distance;
454
+ }
455
+ if (extremum === "min" && distance < resultDistance) {
456
+ resultDistance = distance;
440
457
  }
441
458
  }
442
459
  }
443
460
  }
444
461
  if (originalUnits !== constants_1.ATOMIC_COORD_UNITS.cartesian)
445
462
  this.toCrystal();
446
- return maxDistance;
463
+ return extremum === "min" && resultDistance === Infinity ? 0 : resultDistance;
447
464
  }
448
465
  /**
449
466
  * @summary Function takes basis coordinates and transposes them so that the values for each dimension of the
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Constants for molecule handling and precision.
3
+ * Source of truth: /constants.json at repository root
4
+ *
5
+ * These values are duplicated here for TypeScript compilation.
6
+ * When updating these, also update constants.json.
7
+ */
8
+ export declare const defaultNonPeriodicMinimumLatticeSize = 3;
9
+ export declare const diatomicLatticePaddingFactor = 3;
10
+ export declare const molecularLatticePaddingFactor = 2;
11
+ export declare const PRECISION_MAP: {
12
+ coordinatesCrystal: number;
13
+ coordinatesCartesian: number;
14
+ latticeLengths: number;
15
+ latticeAngles: number;
16
+ };
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ /**
3
+ * Constants for molecule handling and precision.
4
+ * Source of truth: /constants.json at repository root
5
+ *
6
+ * These values are duplicated here for TypeScript compilation.
7
+ * When updating these, also update constants.json.
8
+ */
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.PRECISION_MAP = exports.molecularLatticePaddingFactor = exports.diatomicLatticePaddingFactor = exports.defaultNonPeriodicMinimumLatticeSize = void 0;
11
+ exports.defaultNonPeriodicMinimumLatticeSize = 3.0;
12
+ exports.diatomicLatticePaddingFactor = 3.0;
13
+ exports.molecularLatticePaddingFactor = 2.0;
14
+ exports.PRECISION_MAP = {
15
+ coordinatesCrystal: 9,
16
+ coordinatesCartesian: 6,
17
+ latticeLengths: 6,
18
+ latticeAngles: 4,
19
+ };
@@ -6,7 +6,7 @@ import { UnitCell } from "./unit_cell";
6
6
  * Scaling factor used to calculate the new lattice size for non-periodic systems.
7
7
  * The scaling factor ensures that a non-periodic structure will have have a lattice greater than the structures size.
8
8
  */
9
- export declare const nonPeriodicLatticeScalingFactor = 2;
9
+ export { defaultNonPeriodicMinimumLatticeSize, diatomicLatticePaddingFactor, molecularLatticePaddingFactor, } from "../constants/molecule";
10
10
  export declare class LatticeVectors extends Cell implements LatticeVectorsSchema {
11
11
  }
12
12
  export type { LatticeVectorsSchema };
@@ -23,7 +23,7 @@ var __importStar = (this && this.__importStar) || function (mod) {
23
23
  return result;
24
24
  };
25
25
  Object.defineProperty(exports, "__esModule", { value: true });
26
- exports.Lattice = exports.LatticeVectors = exports.nonPeriodicLatticeScalingFactor = void 0;
26
+ exports.Lattice = exports.LatticeVectors = exports.molecularLatticePaddingFactor = exports.diatomicLatticePaddingFactor = exports.defaultNonPeriodicMinimumLatticeSize = void 0;
27
27
  const constants_1 = require("@mat3ra/code/dist/js/constants");
28
28
  const entity_1 = require("@mat3ra/code/dist/js/entity");
29
29
  const math_1 = require("@mat3ra/code/dist/js/math");
@@ -36,7 +36,10 @@ const unit_cell_1 = require("./unit_cell");
36
36
  * Scaling factor used to calculate the new lattice size for non-periodic systems.
37
37
  * The scaling factor ensures that a non-periodic structure will have have a lattice greater than the structures size.
38
38
  */
39
- exports.nonPeriodicLatticeScalingFactor = 2.0;
39
+ var molecule_1 = require("../constants/molecule");
40
+ Object.defineProperty(exports, "defaultNonPeriodicMinimumLatticeSize", { enumerable: true, get: function () { return molecule_1.defaultNonPeriodicMinimumLatticeSize; } });
41
+ Object.defineProperty(exports, "diatomicLatticePaddingFactor", { enumerable: true, get: function () { return molecule_1.diatomicLatticePaddingFactor; } });
42
+ Object.defineProperty(exports, "molecularLatticePaddingFactor", { enumerable: true, get: function () { return molecule_1.molecularLatticePaddingFactor; } });
40
43
  class LatticeVectors extends cell_1.Cell {
41
44
  }
42
45
  exports.LatticeVectors = LatticeVectors;
package/dist/js/made.d.ts CHANGED
@@ -2,7 +2,7 @@ import { Basis } from "./basis/basis";
2
2
  import { Cell } from "./cell/cell";
3
3
  import { ATOMIC_COORD_UNITS, coefficients, tolerance, units } from "./constants";
4
4
  import { AtomicConstraints } from "./constraints/constraints";
5
- import { Lattice, nonPeriodicLatticeScalingFactor } from "./lattice/lattice";
5
+ import { Lattice, defaultNonPeriodicMinimumLatticeSize, diatomicLatticePaddingFactor, molecularLatticePaddingFactor } from "./lattice/lattice";
6
6
  import { DEFAULT_LATTICE_UNITS, LATTICE_TYPE_CONFIGS } from "./lattice/lattice_types";
7
7
  import { ReciprocalLattice } from "./lattice/reciprocal/lattice_reciprocal";
8
8
  import { UnitCell } from "./lattice/unit_cell";
@@ -37,7 +37,9 @@ export declare const Made: {
37
37
  Lattice: typeof Lattice;
38
38
  Cell: typeof Cell;
39
39
  UnitCell: typeof UnitCell;
40
- nonPeriodicLatticeScalingFactor: number;
40
+ defaultNonPeriodicMinimumLatticeSize: number;
41
+ diatomicLatticePaddingFactor: number;
42
+ molecularLatticePaddingFactor: number;
41
43
  ReciprocalLattice: typeof ReciprocalLattice;
42
44
  Basis: typeof Basis;
43
45
  AtomicConstraints: typeof AtomicConstraints;
@@ -54,7 +56,6 @@ export declare const Made: {
54
56
  toPoscar: (materialOrConfig: import("@mat3ra/esse/dist/js/types").MaterialSchema, omitConstraints?: boolean) => string;
55
57
  fromPoscar: (fileContent: string) => object;
56
58
  atomicConstraintsCharFromBool: (bool: boolean) => string;
57
- atomsCount: typeof import("./parsers/poscar").atomsCount;
58
59
  };
59
60
  cif: {
60
61
  parseMeta: (txt: string) => import("./parsers/cif").Meta;
@@ -99,4 +100,4 @@ export declare const Made: {
99
100
  };
100
101
  };
101
102
  };
102
- export { coefficients, tolerance, units, ATOMIC_COORD_UNITS, Material, defaultMaterialConfig, Lattice, Cell, UnitCell, nonPeriodicLatticeScalingFactor, ReciprocalLattice, Basis, AtomicConstraints, parsers, tools, LATTICE_TYPE_CONFIGS, DEFAULT_LATTICE_UNITS, };
103
+ export { coefficients, tolerance, units, ATOMIC_COORD_UNITS, Material, defaultMaterialConfig, Lattice, Cell, UnitCell, defaultNonPeriodicMinimumLatticeSize, diatomicLatticePaddingFactor, molecularLatticePaddingFactor, ReciprocalLattice, Basis, AtomicConstraints, parsers, tools, LATTICE_TYPE_CONFIGS, DEFAULT_LATTICE_UNITS, };
package/dist/js/made.js CHANGED
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.DEFAULT_LATTICE_UNITS = exports.LATTICE_TYPE_CONFIGS = exports.tools = exports.parsers = exports.AtomicConstraints = exports.Basis = exports.ReciprocalLattice = exports.nonPeriodicLatticeScalingFactor = exports.UnitCell = exports.Cell = exports.Lattice = exports.defaultMaterialConfig = exports.Material = exports.ATOMIC_COORD_UNITS = exports.units = exports.tolerance = exports.coefficients = exports.Made = void 0;
6
+ exports.DEFAULT_LATTICE_UNITS = exports.LATTICE_TYPE_CONFIGS = exports.tools = exports.parsers = exports.AtomicConstraints = exports.Basis = exports.ReciprocalLattice = exports.molecularLatticePaddingFactor = exports.diatomicLatticePaddingFactor = exports.defaultNonPeriodicMinimumLatticeSize = exports.UnitCell = exports.Cell = exports.Lattice = exports.defaultMaterialConfig = exports.Material = exports.ATOMIC_COORD_UNITS = exports.units = exports.tolerance = exports.coefficients = exports.Made = void 0;
7
7
  const basis_1 = require("./basis/basis");
8
8
  Object.defineProperty(exports, "Basis", { enumerable: true, get: function () { return basis_1.Basis; } });
9
9
  const cell_1 = require("./cell/cell");
@@ -17,7 +17,9 @@ const constraints_1 = require("./constraints/constraints");
17
17
  Object.defineProperty(exports, "AtomicConstraints", { enumerable: true, get: function () { return constraints_1.AtomicConstraints; } });
18
18
  const lattice_1 = require("./lattice/lattice");
19
19
  Object.defineProperty(exports, "Lattice", { enumerable: true, get: function () { return lattice_1.Lattice; } });
20
- Object.defineProperty(exports, "nonPeriodicLatticeScalingFactor", { enumerable: true, get: function () { return lattice_1.nonPeriodicLatticeScalingFactor; } });
20
+ Object.defineProperty(exports, "defaultNonPeriodicMinimumLatticeSize", { enumerable: true, get: function () { return lattice_1.defaultNonPeriodicMinimumLatticeSize; } });
21
+ Object.defineProperty(exports, "diatomicLatticePaddingFactor", { enumerable: true, get: function () { return lattice_1.diatomicLatticePaddingFactor; } });
22
+ Object.defineProperty(exports, "molecularLatticePaddingFactor", { enumerable: true, get: function () { return lattice_1.molecularLatticePaddingFactor; } });
21
23
  const lattice_types_1 = require("./lattice/lattice_types");
22
24
  Object.defineProperty(exports, "DEFAULT_LATTICE_UNITS", { enumerable: true, get: function () { return lattice_types_1.DEFAULT_LATTICE_UNITS; } });
23
25
  Object.defineProperty(exports, "LATTICE_TYPE_CONFIGS", { enumerable: true, get: function () { return lattice_types_1.LATTICE_TYPE_CONFIGS; } });
@@ -42,7 +44,9 @@ exports.Made = {
42
44
  Lattice: lattice_1.Lattice,
43
45
  Cell: cell_1.Cell,
44
46
  UnitCell: unit_cell_1.UnitCell,
45
- nonPeriodicLatticeScalingFactor: lattice_1.nonPeriodicLatticeScalingFactor,
47
+ defaultNonPeriodicMinimumLatticeSize: lattice_1.defaultNonPeriodicMinimumLatticeSize,
48
+ diatomicLatticePaddingFactor: lattice_1.diatomicLatticePaddingFactor,
49
+ molecularLatticePaddingFactor: lattice_1.molecularLatticePaddingFactor,
46
50
  ReciprocalLattice: lattice_reciprocal_1.ReciprocalLattice,
47
51
  Basis: basis_1.Basis,
48
52
  AtomicConstraints: constraints_1.AtomicConstraints,
@@ -11,7 +11,6 @@ declare const _default: {
11
11
  toPoscar: (materialOrConfig: import("@mat3ra/esse/dist/js/types").MaterialSchema, omitConstraints?: boolean) => string;
12
12
  fromPoscar: (fileContent: string) => object;
13
13
  atomicConstraintsCharFromBool: (bool: boolean) => string;
14
- atomsCount: typeof import("./poscar").atomsCount;
15
14
  };
16
15
  cif: {
17
16
  parseMeta: (txt: string) => import("./cif").Meta;
@@ -26,6 +26,5 @@ declare const _default: {
26
26
  toPoscar: typeof toPoscar;
27
27
  fromPoscar: typeof fromPoscar;
28
28
  atomicConstraintsCharFromBool: (bool: boolean) => string;
29
- atomsCount: typeof atomsCount;
30
29
  };
31
30
  export default _default;
@@ -5,6 +5,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.atomsCount = void 0;
7
7
  const math_1 = require("@mat3ra/code/dist/js/math");
8
+ const utils_1 = require("@mat3ra/utils");
8
9
  const underscore_string_1 = __importDefault(require("underscore.string"));
9
10
  const constrained_basis_1 = require("../basis/constrained_basis");
10
11
  const cell_1 = require("../cell/cell");
@@ -58,7 +59,8 @@ function toPoscar(materialOrConfig, omitConstraints = false) {
58
59
  * Poscar file formatting: https://www.vasp.at/wiki/index.php/POSCAR
59
60
  */
60
61
  function atomsCount(poscarFileContent) {
61
- const atomsLine = poscarFileContent.split("\n")[6].split(/\s+/);
62
+ const lines = utils_1.Utils.str.removeCommentsFromSourceCode(poscarFileContent).split("\n");
63
+ const atomsLine = lines[6].split(/\s+/);
62
64
  return atomsLine.map((x) => parseInt(x, 10)).reduce((a, b) => a + b);
63
65
  }
64
66
  exports.atomsCount = atomsCount;
@@ -68,7 +70,8 @@ exports.atomsCount = atomsCount;
68
70
  * @return Material config.
69
71
  */
70
72
  function fromPoscar(fileContent) {
71
- const lines = fileContent.split("\n");
73
+ const cleanContent = utils_1.Utils.str.removeCommentsFromSourceCode(fileContent, "fortran");
74
+ const lines = cleanContent.split("\n");
72
75
  const comment = lines[0];
73
76
  // TODO: alat should be handled!!!!
74
77
  // const latticeConstant = parseFloat(lines[1].trim());
@@ -143,7 +146,7 @@ function fromPoscar(fileContent) {
143
146
  * @param text - string to check
144
147
  */
145
148
  function isPoscar(text) {
146
- const lines = text.split("\n");
149
+ const lines = utils_1.Utils.str.removeCommentsFromSourceCode(text, "fortran").split("\n");
147
150
  // Checking number of lines, minimum requirement for POSCAR
148
151
  if (lines.length < 7) {
149
152
  return false;
@@ -178,5 +181,4 @@ exports.default = {
178
181
  toPoscar,
179
182
  fromPoscar,
180
183
  atomicConstraintsCharFromBool,
181
- atomsCount,
182
184
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mat3ra/made",
3
- "version": "2026.5.28-0",
3
+ "version": "2026.6.11-0",
4
4
  "description": "MAterials DEsign library",
5
5
  "scripts": {
6
6
  "lint": "eslint --cache src/js tests/js && prettier --write src/js tests/js",
@@ -37,7 +37,7 @@
37
37
  "@mat3ra/code": "2026.5.27-0",
38
38
  "@mat3ra/esse": "2026.5.27-0",
39
39
  "@mat3ra/tsconfig": "2024.6.3-0",
40
- "@mat3ra/utils": "2025.4.14-0",
40
+ "@mat3ra/utils": "^2026.6.11-0",
41
41
  "@types/crypto-js": "^4.2.2",
42
42
  "@typescript-eslint/eslint-plugin": "^5.9.1",
43
43
  "@typescript-eslint/parser": "^5.9.1",
@@ -79,7 +79,8 @@
79
79
  },
80
80
  "peerDependencies": {
81
81
  "@mat3ra/code": "*",
82
- "@mat3ra/esse": "*"
82
+ "@mat3ra/esse": "*",
83
+ "@mat3ra/utils": "*"
83
84
  },
84
85
  "lint-staged": {
85
86
  "*.js": "eslint --cache --fix",
@@ -7,7 +7,11 @@ import { chain, toPairs, uniq, values } from "lodash";
7
7
 
8
8
  import { Cell } from "../cell/cell";
9
9
  import { ATOMIC_COORD_UNITS, HASH_TOLERANCE } from "../constants";
10
- import { nonPeriodicLatticeScalingFactor } from "../lattice/lattice";
10
+ import {
11
+ defaultNonPeriodicMinimumLatticeSize,
12
+ diatomicLatticePaddingFactor,
13
+ molecularLatticePaddingFactor,
14
+ } from "../lattice/lattice";
11
15
  import { AtomicCoordinateValue, Coordinates } from "./coordinates";
12
16
  import { AtomicElementValue, Elements } from "./elements";
13
17
  import { AtomicLabelValue, Labels } from "./labels";
@@ -476,19 +480,27 @@ export class Basis extends InMemoryEntity implements BasisSchema {
476
480
  }
477
481
 
478
482
  /**
479
- * @summary function returns the minimum basis lattice size for a structure.
480
- * The lattice size is based on the atomic radius of an element if the basis contains a single atom.
481
- * The lattice size is based on the maximum pairwise distance across a structure if the basis contains > 2 atoms.
483
+ * @summary Returns the minimum cubic lattice size for a non-periodic structure.
484
+ * Single-atom structures use the element atomic radius floored at defaultMinimumLatticeSize.
485
+ * Multi-atom structures scale the maximum pairwise distance by 3 when W=min/max pairwise distance
486
+ * is ~1 (diatomic), otherwise by 2.
482
487
  */
483
- getMinimumLatticeSize(latticeScalingFactor = nonPeriodicLatticeScalingFactor): number {
484
- let latticeSizeAdditiveContribution = 0;
488
+ getMinimumLatticeSize(
489
+ defaultMinimumLatticeSize = defaultNonPeriodicMinimumLatticeSize,
490
+ ): number {
485
491
  if (this._elements.values.length === 1) {
486
492
  const elementSymbol = this._elements.getElementValueByIndex(0);
487
- latticeSizeAdditiveContribution = getElementAtomicRadius(elementSymbol);
493
+ const atomicRadius = getElementAtomicRadius(elementSymbol);
494
+ return Math.max(defaultMinimumLatticeSize, atomicRadius);
488
495
  }
489
- const moleculeLatticeSize = this.maxPairwiseDistance * latticeScalingFactor;
490
- const latticeSize = latticeSizeAdditiveContribution + moleculeLatticeSize;
491
- return latticeSize;
496
+ const maxDistance = this.maxPairwiseDistance;
497
+ const minDistance = this.minPairwiseDistance;
498
+ const widthRatio = maxDistance > 0 ? minDistance / maxDistance : 1;
499
+ const latticeScalingFactor = math.almostEqual(widthRatio, 1)
500
+ ? diatomicLatticePaddingFactor
501
+ : molecularLatticePaddingFactor;
502
+ const moleculeLatticeSize = maxDistance * latticeScalingFactor;
503
+ return Math.max(defaultMinimumLatticeSize, moleculeLatticeSize);
492
504
  }
493
505
 
494
506
  /**
@@ -545,9 +557,17 @@ export class Basis extends InMemoryEntity implements BasisSchema {
545
557
  *
546
558
  */
547
559
  get maxPairwiseDistance(): number {
560
+ return this.getPairwiseDistanceExtremum("max");
561
+ }
562
+
563
+ get minPairwiseDistance(): number {
564
+ return this.getPairwiseDistanceExtremum("min");
565
+ }
566
+
567
+ getPairwiseDistanceExtremum(extremum: "max" | "min"): number {
548
568
  const originalUnits = this.units;
549
569
  this.toCartesian();
550
- let maxDistance = 0;
570
+ let resultDistance = extremum === "max" ? 0 : Infinity;
551
571
  if (this._elements.values.length >= 2) {
552
572
  for (let i = 0; i < this._elements.values.length; i++) {
553
573
  for (let j = i + 1; j < this._elements.values.length; j++) {
@@ -555,14 +575,18 @@ export class Basis extends InMemoryEntity implements BasisSchema {
555
575
  this._coordinates.getElementValueByIndex(i) as Coordinate3DSchema,
556
576
  this._coordinates.getElementValueByIndex(j) as Coordinate3DSchema,
557
577
  );
558
- if (distance && distance > maxDistance) {
559
- maxDistance = distance;
578
+ if (!distance) continue;
579
+ if (extremum === "max" && distance > resultDistance) {
580
+ resultDistance = distance;
581
+ }
582
+ if (extremum === "min" && distance < resultDistance) {
583
+ resultDistance = distance;
560
584
  }
561
585
  }
562
586
  }
563
587
  }
564
588
  if (originalUnits !== ATOMIC_COORD_UNITS.cartesian) this.toCrystal();
565
- return maxDistance;
589
+ return extremum === "min" && resultDistance === Infinity ? 0 : resultDistance;
566
590
  }
567
591
 
568
592
  /**
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Constants for molecule handling and precision.
3
+ * Source of truth: /constants.json at repository root
4
+ *
5
+ * These values are duplicated here for TypeScript compilation.
6
+ * When updating these, also update constants.json.
7
+ */
8
+
9
+ export const defaultNonPeriodicMinimumLatticeSize = 3.0;
10
+ export const diatomicLatticePaddingFactor = 3.0;
11
+ export const molecularLatticePaddingFactor = 2.0;
12
+
13
+ export const PRECISION_MAP = {
14
+ coordinatesCrystal: 9,
15
+ coordinatesCartesian: 6,
16
+ latticeLengths: 6,
17
+ latticeAngles: 4,
18
+ };
@@ -20,7 +20,11 @@ import { UnitCell, UnitCellProps } from "./unit_cell";
20
20
  * Scaling factor used to calculate the new lattice size for non-periodic systems.
21
21
  * The scaling factor ensures that a non-periodic structure will have have a lattice greater than the structures size.
22
22
  */
23
- export const nonPeriodicLatticeScalingFactor = 2.0;
23
+ export {
24
+ defaultNonPeriodicMinimumLatticeSize,
25
+ diatomicLatticePaddingFactor,
26
+ molecularLatticePaddingFactor,
27
+ } from "../constants/molecule";
24
28
 
25
29
  export class LatticeVectors extends Cell implements LatticeVectorsSchema {}
26
30
 
package/src/js/made.ts CHANGED
@@ -2,7 +2,12 @@ import { Basis } from "./basis/basis";
2
2
  import { Cell } from "./cell/cell";
3
3
  import { ATOMIC_COORD_UNITS, coefficients, tolerance, units } from "./constants";
4
4
  import { AtomicConstraints } from "./constraints/constraints";
5
- import { Lattice, nonPeriodicLatticeScalingFactor } from "./lattice/lattice";
5
+ import {
6
+ Lattice,
7
+ defaultNonPeriodicMinimumLatticeSize,
8
+ diatomicLatticePaddingFactor,
9
+ molecularLatticePaddingFactor,
10
+ } from "./lattice/lattice";
6
11
  import { DEFAULT_LATTICE_UNITS, LATTICE_TYPE_CONFIGS } from "./lattice/lattice_types";
7
12
  import { ReciprocalLattice } from "./lattice/reciprocal/lattice_reciprocal";
8
13
  import { UnitCell } from "./lattice/unit_cell";
@@ -21,7 +26,9 @@ export const Made = {
21
26
  Lattice,
22
27
  Cell,
23
28
  UnitCell,
24
- nonPeriodicLatticeScalingFactor,
29
+ defaultNonPeriodicMinimumLatticeSize,
30
+ diatomicLatticePaddingFactor,
31
+ molecularLatticePaddingFactor,
25
32
  ReciprocalLattice,
26
33
  Basis,
27
34
  AtomicConstraints,
@@ -42,7 +49,9 @@ export {
42
49
  Lattice,
43
50
  Cell,
44
51
  UnitCell,
45
- nonPeriodicLatticeScalingFactor,
52
+ defaultNonPeriodicMinimumLatticeSize,
53
+ diatomicLatticePaddingFactor,
54
+ molecularLatticePaddingFactor,
46
55
  ReciprocalLattice,
47
56
  Basis,
48
57
  AtomicConstraints,
@@ -5,6 +5,7 @@ import {
5
5
  MaterialSchema,
6
6
  Vector3DSchema,
7
7
  } from "@mat3ra/esse/dist/js/types";
8
+ import { Utils } from "@mat3ra/utils";
8
9
  import s from "underscore.string";
9
10
 
10
11
  import { ConstrainedBasis } from "../basis/constrained_basis";
@@ -67,7 +68,8 @@ function toPoscar(materialOrConfig: MaterialSchema, omitConstraints = false): st
67
68
  * Poscar file formatting: https://www.vasp.at/wiki/index.php/POSCAR
68
69
  */
69
70
  export function atomsCount(poscarFileContent: string): number {
70
- const atomsLine = poscarFileContent.split("\n")[6].split(/\s+/);
71
+ const lines = Utils.str.removeCommentsFromSourceCode(poscarFileContent).split("\n");
72
+ const atomsLine = lines[6].split(/\s+/);
71
73
  return atomsLine.map((x) => parseInt(x, 10)).reduce((a, b) => a + b);
72
74
  }
73
75
 
@@ -77,7 +79,8 @@ export function atomsCount(poscarFileContent: string): number {
77
79
  * @return Material config.
78
80
  */
79
81
  function fromPoscar(fileContent: string): object {
80
- const lines = fileContent.split("\n");
82
+ const cleanContent = Utils.str.removeCommentsFromSourceCode(fileContent, "fortran");
83
+ const lines = cleanContent.split("\n");
81
84
 
82
85
  const comment = lines[0];
83
86
  // TODO: alat should be handled!!!!
@@ -163,7 +166,7 @@ function fromPoscar(fileContent: string): object {
163
166
  * @param text - string to check
164
167
  */
165
168
  function isPoscar(text: string): boolean {
166
- const lines = text.split("\n");
169
+ const lines = Utils.str.removeCommentsFromSourceCode(text, "fortran").split("\n");
167
170
 
168
171
  // Checking number of lines, minimum requirement for POSCAR
169
172
  if (lines.length < 7) {
@@ -205,5 +208,4 @@ export default {
205
208
  toPoscar,
206
209
  fromPoscar,
207
210
  atomicConstraintsCharFromBool,
208
- atomsCount,
209
211
  };
@@ -0,0 +1,11 @@
1
+ import json
2
+ import os
3
+
4
+ _CONSTANTS_FILE = os.path.join(os.path.dirname(__file__), "../../../../constants.json")
5
+ with open(_CONSTANTS_FILE) as f:
6
+ _CONSTANTS = json.load(f)
7
+
8
+ DEFAULT_NON_PERIODIC_MIN_LATTICE_SIZE = _CONSTANTS["molecule"]["nonPeriodicMinimumLatticeSize"]
9
+ DIATOMIC_LATTICE_PADDING_FACTOR = _CONSTANTS["molecule"]["diatomicLatticePaddingFactor"]
10
+ MOLECULAR_LATTICE_PADDING_FACTOR = _CONSTANTS["molecule"]["molecularLatticePaddingFactor"]
11
+ PRECISION_MAP = _CONSTANTS["precision"]
@@ -2,6 +2,7 @@ import inspect
2
2
  from functools import wraps
3
3
  from typing import Any, Callable, Dict, Union
4
4
 
5
+ from mat3ra.utils import remove_comments_from_source_code
5
6
  from mat3ra.utils.mixins import RoundNumericValuesMixin
6
7
 
7
8
  from mat3ra.made.material import Material
@@ -151,6 +152,10 @@ def to_poscar(material_or_material_data: Union[Material, Dict[str, Any]]) -> str
151
152
  return poscar.get_str()
152
153
 
153
154
 
155
+ def clean_qe_input(poscar):
156
+ return remove_comments_from_source_code(remove_comments_from_source_code(poscar, "fortran"), "python")
157
+
158
+
154
159
  def from_poscar(poscar: str) -> Dict[str, Any]:
155
160
  """
156
161
  Converts a POSCAR string to a material object in ESSE format.
@@ -161,10 +166,22 @@ def from_poscar(poscar: str) -> Dict[str, Any]:
161
166
  Returns:
162
167
  dict: A dictionary containing the material information in ESSE format.
163
168
  """
164
- structure = PymatgenStructure.from_str(poscar, "poscar")
169
+ poscar_clean = clean_qe_input(poscar)
170
+ structure = PymatgenStructure.from_str(poscar_clean, "poscar")
165
171
  return from_pymatgen(structure)
166
172
 
167
173
 
174
+ def from_poscar_molecule(poscar: str) -> Dict[str, Any]:
175
+ """
176
+ Converts a molecule POSCAR string to a non-periodic ESSE material.
177
+ """
178
+ poscar_clean = clean_qe_input(poscar)
179
+ structure = PymatgenStructure.from_str(poscar_clean, "poscar")
180
+ ase_atoms = PymatgenAseAtomsAdaptor.get_atoms(structure)
181
+ ase_atoms.set_pbc(False)
182
+ return from_ase(ase_atoms)
183
+
184
+
168
185
  def to_ase(material_or_material_data: Union[Material, Dict[str, Any]]) -> ASEAtoms:
169
186
  """
170
187
  Converts material object in ESSE format to an ASE Atoms object.
@@ -2,12 +2,39 @@ import json
2
2
  from typing import Any, Dict, List, Union
3
3
 
4
4
  import numpy as np
5
+ from mat3ra.made.constants import (
6
+ DEFAULT_NON_PERIODIC_MIN_LATTICE_SIZE,
7
+ DIATOMIC_LATTICE_PADDING_FACTOR,
8
+ MOLECULAR_LATTICE_PADDING_FACTOR,
9
+ )
10
+ from mat3ra.made.utils import get_center_of_coordinates, map_array_to_array_with_id_value
5
11
  from mat3ra.utils.object import NumpyNDArrayRoundEncoder
6
12
  from scipy.spatial.distance import pdist
7
13
 
8
- from mat3ra.made.utils import map_array_to_array_with_id_value, get_center_of_coordinates
9
- from .interface_parts_enum import INTERFACE_LABELS_MAP
10
14
  from ..third_party import ASEAtoms, PymatgenInterface, PymatgenStructure
15
+ from .interface_parts_enum import INTERFACE_LABELS_MAP
16
+
17
+
18
+ def _get_non_periodic_lattice_padding_factor(max_distance: float, min_distance: float) -> float:
19
+ if max_distance <= 0:
20
+ return MOLECULAR_LATTICE_PADDING_FACTOR
21
+ width_ratio = min_distance / max_distance
22
+ if np.isclose(width_ratio, 1.0):
23
+ return DIATOMIC_LATTICE_PADDING_FACTOR
24
+ return MOLECULAR_LATTICE_PADDING_FACTOR
25
+
26
+
27
+ def _get_non_periodic_lattice_size(
28
+ shifted_positions: np.ndarray,
29
+ default_min_size: float = DEFAULT_NON_PERIODIC_MIN_LATTICE_SIZE,
30
+ ) -> float:
31
+ if len(shifted_positions) < 2:
32
+ return max(default_min_size, 10.0)
33
+ distances = pdist(shifted_positions)
34
+ max_distance = float(np.max(distances))
35
+ min_distance = float(np.min(distances))
36
+ padding_factor = _get_non_periodic_lattice_padding_factor(max_distance, min_distance)
37
+ return max(default_min_size, max_distance * padding_factor)
11
38
 
12
39
 
13
40
  def extract_labels_from_pymatgen_structure(structure: PymatgenStructure) -> List[int]:
@@ -42,21 +69,21 @@ def extract_tags_from_ase_atoms(atoms: ASEAtoms) -> List[Union[str, int]]:
42
69
 
43
70
 
44
71
  def calculate_padded_cell_simple_cubic(
45
- coordinates: List[List[float]], padding_factor: float = 2.0
72
+ coordinates: List[List[float]],
73
+ default_min_size: float = DEFAULT_NON_PERIODIC_MIN_LATTICE_SIZE,
46
74
  ) -> List[List[float]]:
47
75
  """
48
76
  Calculate values for a padded cell for a molecule based on its coordinates.
49
77
  Args:
50
78
  coordinates (Array[Array[float]]): A list of atomic coordinates.
51
- padding_factor (float): The factor by which to multiply the maximum distance for padding.
79
+ default_min_size (float): Minimum cubic lattice parameter in angstroms.
52
80
  Returns:
53
81
  Array[float]: A list containing the final cell latice vectors with padding applied.
54
82
  """
55
83
  positions = np.array(coordinates)
56
84
  center = get_center_of_coordinates(positions)
57
85
  shifted_positions = positions - center
58
- max_distance = np.max(pdist(shifted_positions)) if len(positions) >= 2 else 10.0
59
- padding_value = padding_factor * max_distance
86
+ padding_value = _get_non_periodic_lattice_size(shifted_positions, default_min_size)
60
87
 
61
88
  return [
62
89
  [padding_value, 0.0, 0.0],
@@ -217,7 +217,7 @@ describe("Basis", () => {
217
217
  */
218
218
  it("should return minimum lattice size for an atom", () => {
219
219
  const basis = new Basis(Na.basis);
220
- const minimumLatticeSize = 1.8;
220
+ const minimumLatticeSize = 3;
221
221
  const latticeSize = basis.getMinimumLatticeSize();
222
222
  expect(latticeSize).to.be.equal(minimumLatticeSize);
223
223
  });
@@ -4,6 +4,18 @@ import { AtomicConstraints } from "../../../src/js/constraints/constraints";
4
4
  import { Material } from "../../../src/js/material";
5
5
  import parsers from "../../../src/js/parsers/parsers";
6
6
  import { atomsCount } from "../../../src/js/parsers/poscar";
7
+
8
+ const O2_POSCAR_WITH_COMMENTS = `O2 molecule in a box
9
+ 1.0 ! universal scaling parameters
10
+ 12.0 0.0 0.0 ! lattice vector a(1)
11
+ 0.0 12.0 0.0 ! lattice vector a(2)
12
+ 0.0 0.0 12.0 ! lattice vector a(3)
13
+ O
14
+ 2 ! number of atoms
15
+ cart ! positions in cartesian coordinates
16
+ 0 0 0 O
17
+ 0 0 1.21 O
18
+ `;
7
19
  import {
8
20
  atomicConstraints,
9
21
  H2OPoscar,
@@ -29,6 +41,21 @@ describe("Parsers.POSCAR", () => {
29
41
  expect(atomsCount(H2OPoscar)).to.be.equal(3);
30
42
  });
31
43
 
44
+ it("should detect POSCAR with VASP-style comments", () => {
45
+ expect(parsers.poscar.isPoscar(O2_POSCAR_WITH_COMMENTS)).to.be.equal(true);
46
+ expect(parsers.nativeFormatParsers.detectFormat(O2_POSCAR_WITH_COMMENTS)).to.be.equal(
47
+ "poscar",
48
+ );
49
+ });
50
+
51
+ it("should parse POSCAR with VASP-style comments", () => {
52
+ const materialConfig = parsers.poscar.fromPoscar(O2_POSCAR_WITH_COMMENTS);
53
+ expect(materialConfig).to.have.property("basis");
54
+ expect((materialConfig as { basis: { elements: object[] } }).basis.elements).to.have.length(
55
+ 2,
56
+ );
57
+ });
58
+
32
59
  it("should return constraints as string with given map function", () => {
33
60
  const constraints = AtomicConstraints.fromObjects(atomicConstraints);
34
61
  expect(
@@ -1,19 +1,24 @@
1
1
  import numpy as np
2
2
  import pytest
3
3
  from ase import Atoms
4
+ from ase.build import molecule
4
5
  from mat3ra.code.array_with_ids import ArrayWithIds
6
+ from mat3ra.made.material import Material
7
+ from mat3ra.made.tools.convert import from_ase, from_poscar, from_pymatgen, to_ase, to_poscar, to_pymatgen
8
+ from mat3ra.made.tools.convert.utils import (
9
+ DEFAULT_NON_PERIODIC_MIN_LATTICE_SIZE,
10
+ calculate_padded_cell_simple_cubic,
11
+ )
5
12
  from mat3ra.utils import assertion as assertion_utils
6
13
  from pymatgen.core.structure import Element, Lattice, Structure
7
14
 
8
- from mat3ra.made.material import Material
9
- from mat3ra.made.tools.convert import from_ase, from_poscar, from_pymatgen, to_ase, to_poscar, to_pymatgen
10
15
  from .fixtures.monolayer import GRAPHENE
11
16
  from .fixtures.thrid_party.ase_atoms import (
12
- ASE_BULK_Si,
13
17
  ASE_MOLECULE_H2O,
18
+ BULK_SI_LABELS,
14
19
  BULK_SI_LATTICE_A,
15
20
  BULK_SI_LATTICE_ALPHA,
16
- BULK_SI_LABELS,
21
+ ASE_BULK_Si,
17
22
  )
18
23
 
19
24
  PYMATGEN_LATTICE = Lattice.from_parameters(a=3.84, b=3.84, c=3.84, alpha=120, beta=90, gamma=60)
@@ -143,3 +148,30 @@ def test_from_ase(
143
148
  assert material_data["isNonPeriodic"] == expected_is_non_periodic
144
149
  if expected_lattice_type is not None:
145
150
  assert material_data["lattice"]["type"] == expected_lattice_type
151
+
152
+
153
+ TOLERANCE = 1e-4
154
+
155
+ H2_EXPECTED_LATTICE_A = DEFAULT_NON_PERIODIC_MIN_LATTICE_SIZE
156
+ O2_EXPECTED_LATTICE_A = 3.7379
157
+ H2O_EXPECTED_LATTICE_A = 3.0530
158
+ C2H4_EXPECTED_LATTICE_A = 6.1754
159
+
160
+
161
+ @pytest.mark.parametrize(
162
+ "molecule_name, expected_lattice_a",
163
+ [
164
+ ("H2", H2_EXPECTED_LATTICE_A),
165
+ ("O2", O2_EXPECTED_LATTICE_A),
166
+ ("H2O", H2O_EXPECTED_LATTICE_A),
167
+ ("C2H4", C2H4_EXPECTED_LATTICE_A),
168
+ ],
169
+ )
170
+ def test_calculate_padded_cell_simple_cubic(molecule_name, expected_lattice_a):
171
+ atoms = molecule(molecule_name)
172
+ cell_vectors = calculate_padded_cell_simple_cubic(atoms.get_positions().tolist())
173
+ lattice_a = cell_vectors[0][0]
174
+ assert np.isclose(lattice_a, expected_lattice_a, atol=TOLERANCE)
175
+ assert lattice_a >= DEFAULT_NON_PERIODIC_MIN_LATTICE_SIZE
176
+ assert np.isclose(cell_vectors[0][0], cell_vectors[1][1], atol=TOLERANCE)
177
+ assert np.isclose(cell_vectors[0][0], cell_vectors[2][2], atol=TOLERANCE)