@mat3ra/made 2026.8.7-0 → 2026.8.13-0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/.mocharc.json +2 -2
  2. package/dist/js/Material.d.ts +219 -0
  3. package/dist/js/Material.js +457 -0
  4. package/dist/js/basis/basis.d.ts +14 -9
  5. package/dist/js/basis/basis.js +15 -13
  6. package/dist/js/basis/constrained_basis.d.ts +9 -7
  7. package/dist/js/basis/constrained_basis.js +4 -4
  8. package/dist/js/generated/MaterialSchemaMixin.d.ts +5 -0
  9. package/dist/js/generated/MaterialSchemaMixin.js +69 -0
  10. package/dist/js/lattice/lattice.d.ts +4 -3
  11. package/dist/js/lattice/lattice.js +1 -2
  12. package/dist/js/made.d.ts +12 -12
  13. package/dist/js/made.js +38 -5
  14. package/dist/js/parsers/espresso.d.ts +2 -3
  15. package/dist/js/parsers/espresso.js +0 -1
  16. package/dist/js/parsers/native_format_parsers.d.ts +9 -8
  17. package/dist/js/parsers/native_format_parsers.js +6 -6
  18. package/dist/js/parsers/parsers.d.ts +6 -6
  19. package/dist/js/parsers/poscar.d.ts +5 -4
  20. package/dist/js/parsers/poscar.js +3 -2
  21. package/dist/js/parsers/xyz.d.ts +3 -5
  22. package/dist/js/parsers/xyz.js +3 -4
  23. package/dist/js/parsers/xyz_combinatorial_basis.js +3 -1
  24. package/dist/js/tools/basis.d.ts +1 -1
  25. package/dist/js/tools/index.d.ts +7 -7
  26. package/dist/js/tools/material.d.ts +1 -1
  27. package/dist/js/tools/material.js +8 -7
  28. package/dist/js/tools/supercell.d.ts +4 -5
  29. package/dist/js/tools/supercell.js +2 -4
  30. package/dist/js/tools/surface.d.ts +4 -4
  31. package/dist/js/tools/surface.js +2 -7
  32. package/package.json +6 -6
  33. package/scripts/generate-mixins.ts +28 -0
  34. package/src/js/Material.ts +618 -0
  35. package/src/js/basis/basis.ts +38 -25
  36. package/src/js/basis/constrained_basis.ts +12 -11
  37. package/src/js/basis/coordinates.ts +0 -1
  38. package/src/js/cell/cell.ts +0 -1
  39. package/src/js/generated/MaterialSchemaMixin.ts +79 -0
  40. package/src/js/lattice/lattice.ts +6 -4
  41. package/src/js/lattice/reciprocal/lattice_reciprocal.ts +19 -6
  42. package/src/js/made.ts +1 -1
  43. package/src/js/parsers/espresso.ts +2 -3
  44. package/src/js/parsers/native_format_parsers.ts +9 -8
  45. package/src/js/parsers/poscar.ts +12 -6
  46. package/src/js/parsers/xyz.ts +9 -6
  47. package/src/js/parsers/xyz_combinatorial_basis.ts +5 -2
  48. package/src/js/tools/basis.ts +0 -1
  49. package/src/js/tools/cell.ts +0 -1
  50. package/src/js/tools/material.ts +13 -11
  51. package/src/js/tools/supercell.ts +8 -7
  52. package/src/js/tools/surface.ts +7 -17
  53. package/src/py/mat3ra/made/material.py +6 -5
  54. package/tests/fixtures/si-standata.json +2 -2
  55. package/tests/js/basis/basis.ts +8 -8
  56. package/tests/js/fixtures.ts +12 -9
  57. package/tests/js/material/material.ts +161 -0
  58. package/tests/js/parsers/espresso.ts +2 -5
  59. package/tests/js/parsers/poscar.ts +2 -2
  60. package/tests/js/register-esse-schemas.cjs +10 -0
  61. package/tests/js/tools/supercell.ts +2 -2
  62. package/tests/js/tools/surface.ts +6 -10
  63. package/dist/js/material.d.ts +0 -169
  64. package/dist/js/material.js +0 -362
  65. package/src/js/material.ts +0 -458
  66. package/tests/js/material.test.ts +0 -42
@@ -5,10 +5,10 @@ import { Basis } from "../basis/basis";
5
5
  import { ConstrainedBasis } from "../basis/constrained_basis";
6
6
  import { Cell } from "../cell/cell";
7
7
  import { Lattice } from "../lattice/lattice";
8
- import type { Material } from "../material";
8
+ import type { MaterialSchemaMap } from "../Material";
9
+ import type Material from "../Material";
9
10
  import cellTools from "./cell";
10
11
 
11
-
12
12
  const ADD = Utils.math.add;
13
13
 
14
14
  /**
@@ -53,18 +53,19 @@ function generateNewBasisWithinSupercell(
53
53
 
54
54
  /**
55
55
  * @summary Generates supercell config for the specified material.
56
- * @param material
57
- * @param supercellMatrix {Number[][]}
58
56
  */
59
- function generateConfig(material: Material, supercellMatrix: Matrix3X3Schema) {
57
+ function generateConfig<Schemas extends MaterialSchemaMap = MaterialSchemaMap>(
58
+ material: Material<Schemas>,
59
+ supercellMatrix: Matrix3X3Schema,
60
+ ) {
60
61
  const det = Utils.math.det(supercellMatrix);
61
62
  if (det === 0) {
62
63
  throw new Error("Scaling matrix is degenerate.");
63
64
  }
64
- const cell = material.Lattice.vectors;
65
+ const cell = material.getLattice().vectors;
65
66
  const supercell = cell.cloneAndScaleByMatrix(supercellMatrix);
66
67
  const newBasis = generateNewBasisWithinSupercell(
67
- material.Basis,
68
+ material.getBasis(),
68
69
  cell,
69
70
  supercell,
70
71
  supercellMatrix,
@@ -1,14 +1,9 @@
1
- import {
2
- Coordinate3DSchema,
3
- MaterialSchema,
4
- Matrix3X3Schema,
5
- Vector3DSchema,
6
- } from "@mat3ra/esse/dist/js/types";
1
+ import { Coordinate3DSchema, Matrix3X3Schema, Vector3DSchema } from "@mat3ra/esse/dist/js/types";
7
2
  import { Utils } from "@mat3ra/utils";
8
3
 
9
4
  import { Cell } from "../cell/cell";
10
5
  import { Lattice } from "../lattice/lattice";
11
- import { Material } from "../material";
6
+ import Material, { type MaterialConfig, type MaterialSchemaMap } from "../Material";
12
7
  import SupercellTools from "./supercell";
13
8
 
14
9
  const MULT = Utils.math.multiply;
@@ -36,10 +31,6 @@ function extGCD(a: number, b: number): [number, number] {
36
31
  /**
37
32
  * Generates a slab scaling matrix for the specified cell based on miller indices.
38
33
  * Inspired by from https://gitlab.com/ase/ase/blob/master/ase/build/general_surface.py.
39
- * @param cell {Cell}
40
- * @param millerIndices {Number[]}
41
- * @param tol {Number} Zero-value tolerance
42
- * @return {Number[][]}
43
34
  */
44
35
  function getMillerScalingMatrix(
45
36
  cell: Cell,
@@ -132,7 +123,6 @@ function getMillerScalingMatrix(
132
123
  * @param thickness {Number} Surface (Slab) thickness in layers (Positive Integer).
133
124
  * @param vx {Number} Size of lateral supercell along the direction of the first (x) cell vector (Positive Integer).
134
125
  * @param vy {Number} Size of lateral supercell along the direction of the second (y) cell vector (Positive Integer).
135
- * @return {Number[][]}
136
126
  */
137
127
  function getDimensionsScalingMatrix(
138
128
  outOfPlaneAxisIndex: number,
@@ -158,7 +148,7 @@ function getDimensionsScalingMatrix(
158
148
  return transformationMatrix as Matrix3X3Schema;
159
149
  }
160
150
 
161
- export type SlabConfigSchema = MaterialSchema & {
151
+ export type SlabConfigSchema = MaterialConfig & {
162
152
  outOfPlaneAxisIndex: number;
163
153
  };
164
154
 
@@ -171,8 +161,8 @@ export type SlabConfigSchema = MaterialSchema & {
171
161
  * @param vy {Number} Size of lateral supercell along the direction of the second (y) cell vector (Positive Integer).
172
162
  * @return {Object}
173
163
  */
174
- function generateConfig(
175
- material: Material,
164
+ function generateConfig<Schemas extends MaterialSchemaMap = MaterialSchemaMap>(
165
+ material: Material<Schemas>,
176
166
  millerIndices: Coordinate3DSchema,
177
167
  numberOfLayers = 1,
178
168
  vx = 1,
@@ -181,7 +171,7 @@ function generateConfig(
181
171
  if (numberOfLayers < 1)
182
172
  throw new Error("Made.tools.surface.generateConfig: number of layers < 1.");
183
173
 
184
- const cell = material.Lattice.vectors;
174
+ const cell = material.getLattice().vectors;
185
175
  const millerScalingMatrix = getMillerScalingMatrix(cell, millerIndices);
186
176
  const millerSupercell = cell.cloneAndScaleByMatrix(millerScalingMatrix);
187
177
  const millerPlanePseudoNormal = cell.convertPointToCartesian(millerIndices);
@@ -195,7 +185,7 @@ function generateConfig(
195
185
  );
196
186
  const supercellMatrix = MULT(dimensionsScalingMatrix, millerScalingMatrix);
197
187
  const supercell = millerSupercell.cloneAndScaleByMatrix(dimensionsScalingMatrix);
198
- const tempBasis = material.Basis.clone();
188
+ const tempBasis = material.getBasis().clone();
199
189
  const newBasis = SupercellTools.generateNewBasisWithinSupercell(
200
190
  tempBasis,
201
191
  cell,
@@ -4,7 +4,7 @@ from typing import Any, List, Optional, Union
4
4
  from mat3ra.code.constants import AtomicCoordinateUnits, Units
5
5
  from mat3ra.code.entity import HasDescriptionHasMetadataNamedDefaultableInMemoryEntityPydantic
6
6
  from mat3ra.esse.models.material import MaterialSchema
7
- from pydantic import ConfigDict, SkipValidation, computed_field, field_serializer
7
+ from pydantic import ConfigDict, SkipValidation, computed_field
8
8
 
9
9
  from .basis import Basis
10
10
  from .lattice import Lattice
@@ -125,10 +125,11 @@ class Material(MaterialSchema, HasDescriptionHasMetadataNamedDefaultableInMemory
125
125
  def hash(self) -> str:
126
126
  return self.calculate_hash()
127
127
 
128
- @field_serializer("scaledHash")
129
- def serialize_scaled_hash(self, _scaled_hash: Optional[str]) -> str:
130
- return self.scaled_hash
131
-
132
128
  @property
133
129
  def scaled_hash(self) -> str:
134
130
  return self.calculate_hash(is_scaled=True)
131
+
132
+ @computed_field
133
+ @property
134
+ def scaledHash(self) -> str:
135
+ return self.scaled_hash
@@ -1,3 +1,3 @@
1
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:62175309558d4a75da4fd723ccb47dc558cdbb89fa224e61921c4ee2d5b48094
3
- size 1133
2
+ oid sha256:d81edc05f74463da35845fd348716de48459b8b3b5acfa5076fb621a8e23a74e
3
+ size 1454
@@ -5,7 +5,7 @@ import { Utils } from "@mat3ra/utils";
5
5
  import { expect } from "chai";
6
6
 
7
7
  import { Basis } from "../../../src/js/basis/basis";
8
- import { Material } from "../../../src/js/material";
8
+ import Material from "../../../src/js/Material";
9
9
  import {
10
10
  AsGeBasis,
11
11
  C2H4,
@@ -29,20 +29,20 @@ describe("Basis", () => {
29
29
  });
30
30
 
31
31
  it("should return true when basis is compared to its clone", () => {
32
- const basis1 = new Material(Na4Cl4).Basis;
32
+ const basis1 = new Material(Na4Cl4).getBasis();
33
33
  const basis2 = basis1.clone();
34
34
  expect(basis1.isEqualTo(basis2)).to.be.equal(true);
35
35
  expect(basis1.hasEquivalentCellTo(basis2)).to.be.equal(true);
36
36
  });
37
37
 
38
38
  it("should return jsonified basis", () => {
39
- const basis = new Material(Na4Cl4).Basis;
39
+ const basis = new Material(Na4Cl4).getBasis();
40
40
  expect(basis.toJSON()).to.be.deep.almost.equal(Na4Cl4.basis);
41
41
  });
42
42
 
43
43
  it("should return true if cells are equal", () => {
44
- const basis1 = new Material(Na4Cl4).Basis;
45
- const basis2 = new Material(Na4Cl4Cartesian).Basis;
44
+ const basis1 = new Material(Na4Cl4).getBasis();
45
+ const basis2 = new Material(Na4Cl4Cartesian).getBasis();
46
46
  expect(basis1.hasEquivalentCellTo(basis2)).to.be.equal(true);
47
47
  });
48
48
 
@@ -120,7 +120,7 @@ describe("Basis", () => {
120
120
  });
121
121
 
122
122
  it("should convert crystal to cartesian", () => {
123
- const basis = new Material(Na4Cl4).Basis;
123
+ const basis = new Material(Na4Cl4).getBasis();
124
124
  basis.toCartesian();
125
125
  expect(basis.isInCartesianUnits).to.be.equal(true);
126
126
  expect(basis.coordinates).to.be.deep.almost.equal(Na4Cl4Cartesian.basis.coordinates);
@@ -195,8 +195,8 @@ describe("Basis", () => {
195
195
  */
196
196
 
197
197
  it("should return standard representation", () => {
198
- const basis = new Material(Na4Cl4Cartesian).Basis;
199
- expect(basis.standardRepresentation).to.be.deep.almost.equal(Na4Cl4.basis);
198
+ const basis = new Material(Na4Cl4Cartesian).getBasis();
199
+ assertDeepAlmostEqual(basis.standardRepresentation, Na4Cl4.basis, ["constraints"]);
200
200
  });
201
201
 
202
202
  /**
@@ -1,4 +1,9 @@
1
- import { AtomicConstraintsSchema, BasisSchema, MaterialSchema } from "@mat3ra/esse/dist/js/types";
1
+ import {
2
+ AtomicConstraintsSchema,
3
+ BasisSchema,
4
+ MaterialHashedSchema,
5
+ MaterialSchema,
6
+ } from "@mat3ra/esse/dist/js/types";
2
7
  import { MaterialStandata } from "@mat3ra/standata";
3
8
  import fs from "fs";
4
9
  import path from "path";
@@ -9,7 +14,6 @@ import C2H4_JSON from "../fixtures/C2H4.json";
9
14
  import C2H4Translated_JSON from "../fixtures/C2H4-translated.json";
10
15
  import FeLiSiBasis_JSON from "../fixtures/FeLiSi-basis.json";
11
16
  import FeO_JSON from "../fixtures/FeO.json";
12
-
13
17
  import Ge2Basis_JSON from "../fixtures/Ge2-basis.json";
14
18
  import Graphene_JSON from "../fixtures/Graphene.json";
15
19
  import H2HFinal_JSON from "../fixtures/H2+H-final.json";
@@ -28,24 +32,23 @@ import SiSlab111Gamma120_JSON from "../fixtures/si-slab-111-0.5-vacuum-gamma-120
28
32
  import SiSlab111_JSON from "../fixtures/si-slab-111-0.5-vacuum-ratio.json";
29
33
  import SiSlab111NoVacuum_JSON from "../fixtures/si-slab-111-0-vacuum.json";
30
34
  import Silicon_JSON from "../fixtures/si-standata.json";
31
-
32
35
  import SiSupercell_JSON from "../fixtures/Si-supercell.json";
33
36
  import Si2Basis_JSON from "../fixtures/Si2-basis.json";
34
37
  import Si2BasisRepeated_JSON from "../fixtures/Si2-basis-repeated.json";
35
38
  import Zr1H23Zr1H1_JSON from "../fixtures/Zr1H23Zr1H1.json";
36
39
 
37
40
  // Material
38
- const Silicon = Silicon_JSON as unknown as MaterialSchema;
39
- const SiSupercell = SiSupercell_JSON as unknown as MaterialSchema;
41
+ const Silicon = Silicon_JSON as unknown as MaterialHashedSchema;
42
+ const SiSupercell = SiSupercell_JSON as unknown as MaterialHashedSchema;
40
43
  const SiSlab = SiSlab_JSON as unknown as MaterialSchema;
41
44
  const SiSlab100 = SiSlab100_JSON as unknown as MaterialSchema;
42
45
  const SiSlab111 = SiSlab111_JSON as unknown as MaterialSchema;
43
46
  const SiSlab111Gamma120 = SiSlab111Gamma120_JSON as unknown as MaterialSchema;
44
47
  const SiSlab111NoVacuum = SiSlab111NoVacuum_JSON as unknown as MaterialSchema;
45
48
  const FeO = FeO_JSON as unknown as MaterialSchema;
46
- const FeOStandata = MaterialStandata.getRuntimeDataConfigs().find(
47
- (m: any) => m.name?.includes("FeO"),
48
- ) as MaterialSchema;
49
+ const FeOStandata = MaterialStandata.getRuntimeDataConfigs().find((m: any) =>
50
+ m.name?.includes("FeO"),
51
+ ) as MaterialHashedSchema;
49
52
  const Na4Cl4 = Na4Cl4_JSON as unknown as MaterialSchema;
50
53
  const Na4Cl4Cartesian = Na4Cl4Cartesian_JSON as unknown as MaterialSchema;
51
54
  const C2H4 = C2H4_JSON as unknown as MaterialSchema;
@@ -55,7 +58,7 @@ const H2HInitial = H2HInitial_JSON as unknown as MaterialSchema;
55
58
  const H2HFinal = H2HFinal_JSON as unknown as MaterialSchema;
56
59
  const H2HImage = H2HImage_JSON as unknown as MaterialSchema;
57
60
  const Zr1H23Zr1H1 = Zr1H23Zr1H1_JSON as unknown as MaterialSchema;
58
- const Graphene = Graphene_JSON as unknown as MaterialSchema;
61
+ const Graphene = Graphene_JSON as unknown as MaterialHashedSchema;
59
62
  const NiHex = NiHex_JSON as unknown as MaterialSchema;
60
63
 
61
64
  // Basis
@@ -0,0 +1,161 @@
1
+ import { expect } from "chai";
2
+
3
+ import Material from "../../../src/js/Material";
4
+ import { FeOStandata, Graphene, Na4Cl4, Silicon } from "../fixtures";
5
+
6
+ const newBasisXYZ = `Si 0.000000 0.000000 0.000000
7
+ Ge 0.250000 0.250000 0.250000
8
+ `;
9
+
10
+ describe("Material", () => {
11
+ it("should return unique elements", () => {
12
+ const material = new Material(Na4Cl4);
13
+ expect(material.uniqueElements).to.have.same.members(["Na", "Cl"]);
14
+ });
15
+
16
+ it("should return cloned material", () => {
17
+ const material = new Material(Silicon);
18
+ const clonedMaterial = material.clone();
19
+ clonedMaterial.setBasis(newBasisXYZ, "xyz", clonedMaterial.getBasis().units);
20
+ expect(clonedMaterial.getBasis().elements).to.have.lengthOf(2);
21
+ });
22
+
23
+ it("defaults missing metadata to empty object", () => {
24
+ const material = new Material({
25
+ name: Silicon.name,
26
+ basis: Silicon.basis,
27
+ lattice: Silicon.lattice,
28
+ });
29
+ expect(material.metadata).to.deep.equal({});
30
+ expect(material.toJSON().metadata).to.deep.equal({});
31
+ });
32
+
33
+ describe("toJSON variants", () => {
34
+ const material = new Material(Silicon);
35
+
36
+ it("toJSONPure omits hash and basis.constraints", () => {
37
+ const json = material.toJSONPure();
38
+ expect(json).to.not.have.property("hash");
39
+ expect(json).to.not.have.property("scaledHash");
40
+ expect(json.basis).to.not.have.property("constraints");
41
+ expect(json.name).to.equal(Silicon.name);
42
+ });
43
+
44
+ it("toJSONEnhanced includes basis.constraints and omits hash", () => {
45
+ const json = material.toJSONEnhanced();
46
+ expect(json).to.not.have.property("hash");
47
+ expect(json).to.not.have.property("scaledHash");
48
+ expect(json.basis).to.have.property("constraints").that.is.an("array");
49
+ });
50
+
51
+ it("toJSONHashed includes hash and omits basis.constraints", () => {
52
+ const json = material.toJSONHashed();
53
+ expect(json.hash).to.be.a("string");
54
+ expect(json.hash.length).to.be.greaterThan(0);
55
+ expect(json.basis).to.not.have.property("constraints");
56
+ });
57
+
58
+ it("toJSONEnhancedHashed includes hash and basis.constraints", () => {
59
+ const json = material.toJSONEnhancedHashed();
60
+ expect(json.hash).to.be.a("string");
61
+ expect(json.hash.length).to.be.greaterThan(0);
62
+ expect(json.basis.constraints).to.be.an("array");
63
+ });
64
+
65
+ it("toJSON equals toJSONEnhancedHashed", () => {
66
+ expect(material.toJSON()).to.deep.equal(material.toJSONEnhancedHashed());
67
+ });
68
+ });
69
+
70
+ it("getBasisAsXyz includes atomic constraints via toJSONEnhanced", () => {
71
+ const material = new Material({
72
+ name: "Li-with-constraints",
73
+ basis: {
74
+ elements: [
75
+ { id: 0, value: "Li" },
76
+ { id: 1, value: "Li" },
77
+ ],
78
+ coordinates: [
79
+ { id: 0, value: [0, 0, 0] },
80
+ { id: 1, value: [0.25, 0.25, 0.25] },
81
+ ],
82
+ units: "crystal",
83
+ constraints: [
84
+ { id: 0, value: [true, true, false] },
85
+ { id: 1, value: [true, true, true] },
86
+ ],
87
+ },
88
+ lattice: Silicon.lattice,
89
+ });
90
+
91
+ const xyz = material.getBasisAsXyz();
92
+ expect(xyz).to.match(/Li\s+0\.000000\s+0\.000000\s+0\.000000\s+1\s+1\s+0/);
93
+ expect(xyz).to.match(/Li\s+0\.250000\s+0\.250000\s+0\.250000\s+1\s+1\s+1/);
94
+ // Regression: toJSONPure strips constraints — must not be used for XYZ display.
95
+ expect(material.toJSONPure().basis).to.not.have.property("constraints");
96
+ });
97
+
98
+ describe("calculateHash", () => {
99
+ [
100
+ { name: "Silicon", fixture: Silicon },
101
+ { name: "Graphene", fixture: Graphene },
102
+ ].forEach(({ name, fixture }) => {
103
+ it(`should match expected hash for ${name}`, () => {
104
+ const material = new Material(fixture);
105
+ expect(material.calculateHash()).to.equal(fixture.hash);
106
+ expect(material.scaledHash).to.equal(fixture.scaledHash);
107
+ });
108
+ });
109
+
110
+ it("should match expected hash for FeO (standata)", () => {
111
+ // eslint-disable-next-line global-require, @typescript-eslint/no-var-requires
112
+ const expectedHashes = require("../../fixtures/hashes.json");
113
+ const material = new Material(FeOStandata);
114
+ expect(material.calculateHash()).to.equal(expectedHashes.FeO.hash);
115
+ // scaledHash is stored on schema; standata config may omit it — match main's computed getter.
116
+ expect(material.calculateHash("", true)).to.equal(expectedHashes.FeO.scaledHash);
117
+ });
118
+
119
+ it("stores InChI hash for non-periodic materials when InChI is present", () => {
120
+ // MD5 of the InChI string — same contract as web-app MolView upload.
121
+ const inchi =
122
+ "1S/C8H18N4O2/c1-10-4-9-6-5(10)7(13)12(3)8(14)11(6)2/h5-9,13-14H,4H2,1-3H3";
123
+ const expectedInchiHash = "b3680772bb4f0cba602788d519531914";
124
+
125
+ const material = new Material({
126
+ name: "non-periodic-inchi",
127
+ isNonPeriodic: true,
128
+ basis: Silicon.basis,
129
+ lattice: Silicon.lattice,
130
+ derivedProperties: [{ name: "inchi", value: inchi }],
131
+ });
132
+
133
+ expect(material.isNonPeriodic).to.equal(true);
134
+ expect(material.getInchiStringForHash()).to.equal(inchi);
135
+ expect(material.calculateHash()).to.equal(expectedInchiHash);
136
+ expect(material.hash).to.equal(expectedInchiHash);
137
+
138
+ const geometricHashWithBypass = material.calculateHash("", false, true);
139
+ material.lattice = { ...material.lattice };
140
+ expect(material.hash).to.equal(expectedInchiHash);
141
+ expect(material.hash).to.not.equal(geometricHashWithBypass);
142
+ });
143
+
144
+ it("uses geometric hash when non-periodic but InChI is not derived yet", () => {
145
+ // Designer toggle sets isNonPeriodic then scales lattice before butler adds InChI.
146
+ const material = new Material({
147
+ name: "non-periodic-no-inchi",
148
+ isNonPeriodic: true,
149
+ basis: Silicon.basis,
150
+ lattice: Silicon.lattice,
151
+ });
152
+
153
+ const expectedGeometricHash = material.calculateHash("", false, true);
154
+ expect(material.hash).to.equal(expectedGeometricHash);
155
+ expect(() => material.calculateHash()).to.throw(/Missing InChI/);
156
+
157
+ material.lattice = { ...material.lattice };
158
+ expect(material.hash).to.equal(material.calculateHash("", false, true));
159
+ });
160
+ });
161
+ });
@@ -1,15 +1,12 @@
1
- import { MaterialSchema } from "@mat3ra/esse/dist/js/types";
2
1
  import { expect } from "chai";
3
2
 
4
- import { Material } from "../../../src/js/material";
3
+ import Material from "../../../src/js/Material";
5
4
  import parsers from "../../../src/js/parsers/parsers";
6
5
  import { Silicon, SiPWSCFInput } from "../fixtures";
7
6
 
8
7
  describe("Parsers:Espresso", () => {
9
8
  it("should return textual representation of a material according to QE pw.x input format", () => {
10
9
  const material = new Material(Silicon);
11
- expect(parsers.espresso.toEspressoFormat(material as MaterialSchema)).to.be.equal(
12
- SiPWSCFInput,
13
- );
10
+ expect(parsers.espresso.toEspressoFormat(material.toJSON())).to.be.equal(SiPWSCFInput);
14
11
  });
15
12
  });
@@ -1,7 +1,7 @@
1
1
  import { expect } from "chai";
2
2
 
3
3
  import { AtomicConstraints } from "../../../src/js/constraints/constraints";
4
- import { Material } from "../../../src/js/material";
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
7
  import {
@@ -81,7 +81,7 @@ direct
81
81
 
82
82
  const material = new Material(Silicon);
83
83
  const clonedMaterial = material.clone();
84
- clonedMaterial.setBasis(newBasisXYZ, "xyz", clonedMaterial.Basis.units);
84
+ clonedMaterial.setBasis(newBasisXYZ, "xyz", clonedMaterial.getBasis().units);
85
85
 
86
86
  const poscar = clonedMaterial.getAsPOSCAR(true);
87
87
  expect(poscar).to.be.equal(poscarConstraints);
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Registers ESSE schemas for made unit tests.
3
+ * Kept as CommonJS so mocha `--require` can load it without ESM JSON import issues.
4
+ * Library / web-app code must not import `@mat3ra/esse/dist/js/schemas.json` — the host
5
+ * (web-app) registers the (possibly extended) schema set once at startup.
6
+ */
7
+ const JSONSchemasInterface = require("@mat3ra/esse/dist/js/esse/JSONSchemasInterface").default;
8
+ const allSchemas = require("@mat3ra/esse/dist/js/schemas.json");
9
+
10
+ JSONSchemasInterface.setSchemas(allSchemas);
@@ -4,7 +4,7 @@ import { expect } from "chai";
4
4
 
5
5
  import { Basis } from "../../../src/js/basis/basis";
6
6
  import { Made } from "../../../src/js/made";
7
- import { Material } from "../../../src/js/material";
7
+ import Material from "../../../src/js/Material";
8
8
  import { Silicon, SiSupercell } from "../fixtures";
9
9
 
10
10
  const { assertDeepAlmostEqual } = Utils.assertion;
@@ -44,7 +44,7 @@ direct
44
44
  0.625000000 0.250000000 0.250000000 Ge`;
45
45
  const material = new Material(Silicon);
46
46
  const clonedMaterial = material.clone();
47
- clonedMaterial.setBasis(newBasisXYZ, "xyz", clonedMaterial.Basis.units);
47
+ clonedMaterial.setBasis(newBasisXYZ, "xyz", clonedMaterial.getBasis().units);
48
48
  const supercellConfig = Made.tools.supercell.generateConfig(clonedMaterial, [
49
49
  [2, 0, 0],
50
50
  [0, 1, 0],
@@ -1,7 +1,7 @@
1
1
  import type { MaterialSchema } from "@mat3ra/esse/dist/js/types";
2
2
  import { Utils } from "@mat3ra/utils";
3
3
 
4
- import { Material } from "../../../src/js/material";
4
+ import Material from "../../../src/js/Material";
5
5
  import tools from "../../../src/js/tools";
6
6
  import { SlabConfigSchema } from "../../../src/js/tools/surface";
7
7
  import { Silicon, SiSlab100, SiSlab111, SiSlab111NoVacuum } from "../fixtures";
@@ -39,6 +39,7 @@ describe("Tools:Surface", () => {
39
39
  const adjustedSilicon: MaterialSchema = {
40
40
  ...Silicon,
41
41
  basis: {
42
+ ...Silicon.basis,
42
43
  elements: [
43
44
  {
44
45
  id: 10,
@@ -72,9 +73,8 @@ describe("Tools:Surface", () => {
72
73
  const vacuumRatio = 0.5;
73
74
  const slabMaterial = generateSlabWithVacuum(slabConfig, vacuumRatio);
74
75
  const expectedSlabMaterial = new Material(SiSlab111);
75
- const expectedMaterialJSON = expectedSlabMaterial.toJSON();
76
- const slabMaterialJSON = slabMaterial.toJSON();
77
- assertDeepAlmostEqual(expectedMaterialJSON, slabMaterialJSON);
76
+ // Structural comparison: omit hash (now included in Material.toJSON).
77
+ assertDeepAlmostEqual(expectedSlabMaterial.toJSONEnhanced(), slabMaterial.toJSONEnhanced());
78
78
  });
79
79
 
80
80
  it("should return slab (111) with vacuum for gamma = 59.999", () => {
@@ -90,9 +90,7 @@ describe("Tools:Surface", () => {
90
90
  const vacuumRatio = 0.5;
91
91
  const slabMaterial = generateSlabWithVacuum(slabConfig, vacuumRatio);
92
92
  const expectedSlabMaterial = new Material(SiSlab111);
93
- const expectedMaterialJSON = expectedSlabMaterial.toJSON();
94
- const slabMaterialJSON = slabMaterial.toJSON();
95
- assertDeepAlmostEqual(expectedMaterialJSON, slabMaterialJSON);
93
+ assertDeepAlmostEqual(expectedSlabMaterial.toJSONEnhanced(), slabMaterial.toJSONEnhanced());
96
94
  });
97
95
 
98
96
  it("should return slab (111) with vacuum for gamma = 60.001", () => {
@@ -108,8 +106,6 @@ describe("Tools:Surface", () => {
108
106
  const vacuumRatio = 0.5;
109
107
  const slabMaterial = generateSlabWithVacuum(slabConfig, vacuumRatio);
110
108
  const expectedSlabMaterial = new Material(SiSlab111);
111
- const expectedMaterialJSON = expectedSlabMaterial.toJSON();
112
- const slabMaterialJSON = slabMaterial.toJSON();
113
- assertDeepAlmostEqual(expectedMaterialJSON, slabMaterialJSON);
109
+ assertDeepAlmostEqual(expectedSlabMaterial.toJSONEnhanced(), slabMaterial.toJSONEnhanced());
114
110
  });
115
111
  });