@mat3ra/made 2024.4.16-0 → 2024.4.25-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/package.json
CHANGED
|
@@ -5,7 +5,7 @@ from .convert import convert_material_args_kwargs_to_atoms
|
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
@convert_material_args_kwargs_to_atoms
|
|
8
|
-
def
|
|
8
|
+
def get_average_interlayer_distance(
|
|
9
9
|
interface_atoms: Atoms, tag_substrate: str, tag_film: str, threshold: float = 0.5
|
|
10
10
|
) -> float:
|
|
11
11
|
"""
|
|
@@ -39,3 +39,19 @@ def calculate_average_interlayer_distance(
|
|
|
39
39
|
# Calculate the average distance between the top layer of substrate and the bottom layer of film
|
|
40
40
|
average_interlayer_distance = avg_z_bottom_film - avg_z_top_substrate
|
|
41
41
|
return abs(average_interlayer_distance)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@convert_material_args_kwargs_to_atoms
|
|
45
|
+
def get_surface_area(atoms: Atoms):
|
|
46
|
+
"""
|
|
47
|
+
Calculate the area of the surface perpendicular to the z-axis of the atoms structure.
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
atoms (ase.Atoms): The Atoms object to calculate the surface area of.
|
|
51
|
+
|
|
52
|
+
Returns:
|
|
53
|
+
float: The surface area of the atoms.
|
|
54
|
+
"""
|
|
55
|
+
matrix = atoms.cell
|
|
56
|
+
cross_product = np.cross(matrix[0], matrix[1])
|
|
57
|
+
return np.linalg.norm(cross_product)
|
|
@@ -2,6 +2,7 @@ from ase import Atoms
|
|
|
2
2
|
from ase.calculators.calculator import Calculator
|
|
3
3
|
|
|
4
4
|
from .convert import convert_material_args_kwargs_to_atoms
|
|
5
|
+
from .analyze import get_surface_area
|
|
5
6
|
|
|
6
7
|
|
|
7
8
|
@convert_material_args_kwargs_to_atoms
|
|
@@ -18,3 +19,94 @@ def calculate_total_energy(atoms: Atoms, calculator: Calculator):
|
|
|
18
19
|
"""
|
|
19
20
|
atoms.set_calculator(calculator)
|
|
20
21
|
return atoms.get_total_energy()
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@convert_material_args_kwargs_to_atoms
|
|
25
|
+
def calculate_total_energy_per_atom(atoms: Atoms, calculator: Calculator):
|
|
26
|
+
"""
|
|
27
|
+
Set calculator for ASE Atoms and calculate the total energy per atom.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
atoms (ase.Atoms): The Atoms object to calculate the energy of.
|
|
31
|
+
calculator (ase.calculators.calculator.Calculator): The calculator to use for the energy calculation.
|
|
32
|
+
|
|
33
|
+
Returns:
|
|
34
|
+
float: The energy per atom of the atoms.
|
|
35
|
+
"""
|
|
36
|
+
return calculate_total_energy(atoms, calculator) / atoms.get_global_number_of_atoms()
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@convert_material_args_kwargs_to_atoms
|
|
40
|
+
def calculate_surface_energy(slab: Atoms, bulk: Atoms, calculator: Calculator):
|
|
41
|
+
"""
|
|
42
|
+
Calculate the surface energy by subtracting the weighted bulk energy from the slab energy.
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
slab (ase.Atoms): The slab Atoms object to calculate the surface energy of.
|
|
46
|
+
bulk (ase.Atoms): The bulk Atoms object to calculate the surface energy of.
|
|
47
|
+
calculator (ase.calculators.calculator.Calculator): The calculator to use for the energy calculation.
|
|
48
|
+
|
|
49
|
+
Returns:
|
|
50
|
+
float: The surface energy of the slab.
|
|
51
|
+
"""
|
|
52
|
+
number_of_atoms = slab.get_global_number_of_atoms()
|
|
53
|
+
area = get_surface_area(slab)
|
|
54
|
+
return (
|
|
55
|
+
calculate_total_energy(slab, calculator) - calculate_total_energy_per_atom(bulk, calculator) * number_of_atoms
|
|
56
|
+
) / (2 * area)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@convert_material_args_kwargs_to_atoms
|
|
60
|
+
def calculate_adhesion_energy(interface: Atoms, substrate_slab: Atoms, layer_slab: Atoms, calculator: Calculator):
|
|
61
|
+
"""
|
|
62
|
+
Calculate the adhesion energy.
|
|
63
|
+
The adhesion energy is the difference between the energy of the interface and
|
|
64
|
+
the sum of the energies of the substrate and layer.
|
|
65
|
+
According to: 10.1088/0953-8984/27/30/305004
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
interface (ase.Atoms): The interface Atoms object to calculate the adhesion energy of.
|
|
69
|
+
substrate_slab (ase.Atoms): The substrate slab Atoms object to calculate the adhesion energy of.
|
|
70
|
+
layer_slab (ase.Atoms): The layer slab Atoms object to calculate the adhesion energy of.
|
|
71
|
+
calculator (ase.calculators.calculator.Calculator): The calculator to use for the energy calculation.
|
|
72
|
+
|
|
73
|
+
Returns:
|
|
74
|
+
float: The adhesion energy of the interface.
|
|
75
|
+
"""
|
|
76
|
+
energy_substrate_slab = calculate_total_energy(substrate_slab, calculator)
|
|
77
|
+
energy_layer_slab = calculate_total_energy(layer_slab, calculator)
|
|
78
|
+
energy_interface = calculate_total_energy(interface, calculator)
|
|
79
|
+
area = get_surface_area(interface)
|
|
80
|
+
return (energy_substrate_slab + energy_layer_slab - energy_interface) / area
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
@convert_material_args_kwargs_to_atoms
|
|
84
|
+
def calculate_interfacial_energy(
|
|
85
|
+
interface: Atoms,
|
|
86
|
+
substrate_slab: Atoms,
|
|
87
|
+
substrate_bulk: Atoms,
|
|
88
|
+
layer_slab: Atoms,
|
|
89
|
+
layer_bulk: Atoms,
|
|
90
|
+
calculator: Calculator,
|
|
91
|
+
):
|
|
92
|
+
"""
|
|
93
|
+
Calculate the interfacial energy.
|
|
94
|
+
The interfacial energy is the sum of the surface energies of the substrate and layer minus the adhesion energy.
|
|
95
|
+
According to Dupré's formula
|
|
96
|
+
|
|
97
|
+
Args:
|
|
98
|
+
interface (ase.Atoms): The interface Atoms object to calculate the interfacial energy of.
|
|
99
|
+
substrate_slab (ase.Atoms): The substrate slab Atoms object to calculate the interfacial energy of.
|
|
100
|
+
substrate_bulk (ase.Atoms): The substrate bulk Atoms object to calculate the interfacial energy of.
|
|
101
|
+
layer_slab (ase.Atoms): The layer slab Atoms object to calculate the interfacial energy of.
|
|
102
|
+
layer_bulk (ase.Atoms): The layer bulk Atoms object to calculate the interfacial energy of.
|
|
103
|
+
calculator (ase.calculators.calculator.Calculator): The calculator to use for the energy calculation.
|
|
104
|
+
|
|
105
|
+
Returns:
|
|
106
|
+
float: The interfacial energy of the interface.
|
|
107
|
+
"""
|
|
108
|
+
|
|
109
|
+
surface_energy_substrate = calculate_surface_energy(substrate_slab, substrate_bulk, calculator)
|
|
110
|
+
surface_energy_layer = calculate_surface_energy(layer_slab, layer_bulk, calculator)
|
|
111
|
+
adhesion_energy = calculate_adhesion_energy(interface, substrate_slab, layer_slab, calculator)
|
|
112
|
+
return surface_energy_layer + surface_energy_substrate - adhesion_energy
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from pymatgen.core.structure import Structure
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
# TODO: convert to accept ASE Atoms object
|
|
5
|
+
def translate_to_bottom_pymatgen_structure(structure: Structure):
|
|
6
|
+
"""
|
|
7
|
+
Translate the structure to the bottom of the cell.
|
|
8
|
+
Args:
|
|
9
|
+
structure (Structure): The pymatgen Structure object to translate.
|
|
10
|
+
|
|
11
|
+
Returns:
|
|
12
|
+
Structure: The translated pymatgen Structure object.
|
|
13
|
+
"""
|
|
14
|
+
min_c = min(site.c for site in structure)
|
|
15
|
+
translation_vector = [0, 0, -min_c]
|
|
16
|
+
translated_structure = structure.copy()
|
|
17
|
+
for site in translated_structure:
|
|
18
|
+
site.coords += translation_vector
|
|
19
|
+
return translated_structure
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import numpy as np
|
|
2
2
|
from ase.build import bulk
|
|
3
|
-
from mat3ra.made.tools.analyze import
|
|
3
|
+
from mat3ra.made.tools.analyze import get_average_interlayer_distance
|
|
4
|
+
from mat3ra.made.tools.analyze import get_surface_area
|
|
4
5
|
|
|
5
6
|
|
|
6
7
|
def test_calculate_average_interlayer_distance():
|
|
@@ -8,5 +9,11 @@ def test_calculate_average_interlayer_distance():
|
|
|
8
9
|
film = bulk("Cu", cubic=True)
|
|
9
10
|
interface_atoms = substrate + film
|
|
10
11
|
interface_atoms.set_tags([1] * len(substrate) + [2] * len(film))
|
|
11
|
-
distance =
|
|
12
|
+
distance = get_average_interlayer_distance(interface_atoms, 1, 2)
|
|
12
13
|
assert np.isclose(distance, 4.0725)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def test_calculate_surface_area():
|
|
17
|
+
atoms = bulk("Si", cubic=False)
|
|
18
|
+
area = get_surface_area(atoms)
|
|
19
|
+
assert np.isclose(area, 12.7673)
|
|
@@ -1,7 +1,29 @@
|
|
|
1
1
|
import numpy as np
|
|
2
|
-
from ase.build import bulk
|
|
2
|
+
from ase.build import bulk, surface, fcc111, graphene, add_adsorbate
|
|
3
3
|
from ase.calculators import emt
|
|
4
|
-
from mat3ra.made.tools.calculate import
|
|
4
|
+
from mat3ra.made.tools.calculate import (
|
|
5
|
+
calculate_total_energy,
|
|
6
|
+
calculate_total_energy_per_atom,
|
|
7
|
+
calculate_surface_energy,
|
|
8
|
+
calculate_adhesion_energy,
|
|
9
|
+
calculate_interfacial_energy,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
# Interface and its constituents structures setup
|
|
13
|
+
nickel_slab = fcc111("Ni", size=(2, 2, 3), vacuum=10, a=3.52)
|
|
14
|
+
graphene_layer = graphene(size=(1, 1, 1), vacuum=10)
|
|
15
|
+
graphene_layer.cell = nickel_slab.cell
|
|
16
|
+
interface = nickel_slab.copy()
|
|
17
|
+
add_adsorbate(interface, graphene_layer, height=2, position="ontop")
|
|
18
|
+
|
|
19
|
+
# Assign calculators
|
|
20
|
+
calculator = emt.EMT()
|
|
21
|
+
nickel_slab.set_calculator(calculator)
|
|
22
|
+
graphene_layer.set_calculator(calculator)
|
|
23
|
+
interface.set_calculator(calculator)
|
|
24
|
+
|
|
25
|
+
nickel_bulk = bulk("Ni", "fcc", a=3.52)
|
|
26
|
+
graphene_bulk = graphene_layer
|
|
5
27
|
|
|
6
28
|
|
|
7
29
|
def test_calculate_total_energy():
|
|
@@ -9,3 +31,34 @@ def test_calculate_total_energy():
|
|
|
9
31
|
calculator = emt.EMT()
|
|
10
32
|
energy = calculate_total_energy(atoms, calculator)
|
|
11
33
|
assert np.isclose(energy, 1.3612647524769237)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def test_calculate_total_energy_per_atom():
|
|
37
|
+
atoms = bulk("C", cubic=True)
|
|
38
|
+
calculator = emt.EMT()
|
|
39
|
+
print(atoms.get_global_number_of_atoms())
|
|
40
|
+
energy_per_atom = calculate_total_energy_per_atom(atoms, calculator)
|
|
41
|
+
assert np.isclose(energy_per_atom, 0.1701580940596)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def test_calculate_surface_energy():
|
|
45
|
+
atoms_slab = surface("C", (1, 1, 1), 3, vacuum=10)
|
|
46
|
+
atoms_bulk = bulk("C", cubic=True)
|
|
47
|
+
calculator = emt.EMT()
|
|
48
|
+
surface_energy = calculate_surface_energy(atoms_slab, atoms_bulk, calculator)
|
|
49
|
+
assert np.isclose(surface_energy, 0.148845)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def test_calculate_adhesion_energy():
|
|
53
|
+
adhesion_energy = calculate_adhesion_energy(interface, nickel_slab, graphene_layer, calculator)
|
|
54
|
+
assert np.isclose(adhesion_energy, 0.07345)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def test_calculate_interfacial_energy():
|
|
58
|
+
interfacial_energy = calculate_interfacial_energy(
|
|
59
|
+
interface, nickel_slab, nickel_bulk, graphene_layer, graphene_bulk, calculator
|
|
60
|
+
)
|
|
61
|
+
assert np.isclose(
|
|
62
|
+
interfacial_energy,
|
|
63
|
+
0.030331590159230523,
|
|
64
|
+
)
|