Skip to content

Commit 746e173

Browse files
naik-aakashjanosh
andauthored
Add diffusive thermal conductivity model proposed by Agne et al. (#3546)
* add agne model * add test and source in doc string * add test and source in doc string * add types * add due.dcite decorator for Doi("10.1039/C7EE03256K") * refactor test --------- Co-authored-by: anaik <[email protected]> Co-authored-by: Janosh Riebesell <[email protected]>
1 parent 9751f96 commit 746e173

File tree

2 files changed

+86
-57
lines changed

2 files changed

+86
-57
lines changed

pymatgen/analysis/elasticity/elastic.py

Lines changed: 55 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,13 @@
2121
from pymatgen.analysis.elasticity.stress import Stress
2222
from pymatgen.core.tensors import DEFAULT_QUAD, SquareTensor, Tensor, TensorCollection, get_uvec
2323
from pymatgen.core.units import Unit
24+
from pymatgen.util.due import Doi, due
2425

2526
if TYPE_CHECKING:
2627
from collections.abc import Sequence
2728

29+
from numpy.typing import ArrayLike
30+
2831
from pymatgen.core import Structure
2932

3033

@@ -198,7 +201,7 @@ def y_mod(self) -> float:
198201
"""
199202
return 9.0e9 * self.k_vrh * self.g_vrh / (3 * self.k_vrh + self.g_vrh)
200203

201-
def directional_poisson_ratio(self, n, m, tol: float = 1e-8):
204+
def directional_poisson_ratio(self, n: ArrayLike, m: ArrayLike, tol: float = 1e-8) -> float:
202205
"""
203206
Calculates the poisson ratio for a specific direction
204207
relative to a second, orthogonal direction.
@@ -215,15 +218,15 @@ def directional_poisson_ratio(self, n, m, tol: float = 1e-8):
215218
v *= -1 / self.compliance_tensor.einsum_sequence([n] * 4)
216219
return v
217220

218-
def directional_elastic_mod(self, n):
221+
def directional_elastic_mod(self, n) -> float:
219222
"""Calculates directional elastic modulus for a specific vector."""
220223
n = get_uvec(n)
221224
return self.einsum_sequence([n] * 4)
222225

223226
@raise_if_unphysical
224-
def trans_v(self, structure: Structure):
227+
def trans_v(self, structure: Structure) -> float:
225228
"""
226-
Calculates transverse sound velocity (in SI units) using the
229+
Calculates transverse sound velocity using the
227230
Voigt-Reuss-Hill average bulk modulus.
228231
229232
Args:
@@ -233,19 +236,17 @@ def trans_v(self, structure: Structure):
233236
float: transverse sound velocity (in SI units)
234237
"""
235238
n_sites = len(structure)
236-
volume = structure.volume
237239
n_atoms = structure.composition.num_atoms
238240
weight = float(structure.composition.weight)
239-
mass_density = 1.6605e3 * n_sites * weight / (n_atoms * volume)
241+
mass_density = 1.6605e3 * n_sites * weight / (n_atoms * structure.volume)
240242
if self.g_vrh < 0:
241243
raise ValueError("k_vrh or g_vrh is negative, sound velocity is undefined")
242244
return (1e9 * self.g_vrh / mass_density) ** 0.5
243245

244246
@raise_if_unphysical
245-
def long_v(self, structure: Structure):
247+
def long_v(self, structure: Structure) -> float:
246248
"""
247-
Calculates longitudinal sound velocity (in SI units)
248-
using the Voigt-Reuss-Hill average bulk modulus.
249+
Calculates longitudinal sound velocity using the Voigt-Reuss-Hill average bulk modulus.
249250
250251
Args:
251252
structure: pymatgen structure object
@@ -254,18 +255,16 @@ def long_v(self, structure: Structure):
254255
float: longitudinal sound velocity (in SI units)
255256
"""
256257
n_sites = len(structure)
257-
volume = structure.volume
258258
n_atoms = structure.composition.num_atoms
259259
weight = float(structure.composition.weight)
260-
mass_density = 1.6605e3 * n_sites * weight / (n_atoms * volume)
260+
mass_density = 1.6605e3 * n_sites * weight / (n_atoms * structure.volume)
261261
if self.g_vrh < 0:
262262
raise ValueError("k_vrh or g_vrh is negative, sound velocity is undefined")
263263
return (1e9 * (self.k_vrh + 4 / 3 * self.g_vrh) / mass_density) ** 0.5
264264

265265
@raise_if_unphysical
266-
def snyder_ac(self, structure: Structure):
267-
"""
268-
Calculates Snyder's acoustic sound velocity (in SI units).
266+
def snyder_ac(self, structure: Structure) -> float:
267+
"""Calculates Snyder's acoustic sound velocity.
269268
270269
Args:
271270
structure: pymatgen structure object
@@ -274,20 +273,19 @@ def snyder_ac(self, structure: Structure):
274273
float: Snyder's acoustic sound velocity (in SI units)
275274
"""
276275
n_sites = len(structure)
277-
volume = structure.volume
278276
n_atoms = structure.composition.num_atoms
279-
num_density = 1e30 * n_sites / volume
277+
site_density = 1e30 * n_sites / structure.volume
280278
tot_mass = sum(e.atomic_mass for e in structure.species)
281279
avg_mass = 1.6605e-27 * tot_mass / n_atoms
282280
return (
283281
0.38483
284282
* avg_mass
285283
* ((self.long_v(structure) + 2 * self.trans_v(structure)) / 3) ** 3.0
286-
/ (300 * num_density ** (-2 / 3) * n_sites ** (1 / 3))
284+
/ (300 * site_density ** (-2 / 3) * n_sites ** (1 / 3))
287285
)
288286

289287
@raise_if_unphysical
290-
def snyder_opt(self, structure: Structure):
288+
def snyder_opt(self, structure: Structure) -> float:
291289
"""
292290
Calculates Snyder's optical sound velocity (in SI units).
293291
@@ -298,18 +296,17 @@ def snyder_opt(self, structure: Structure):
298296
float: Snyder's optical sound velocity (in SI units)
299297
"""
300298
n_sites = len(structure)
301-
volume = structure.volume
302-
num_density = 1e30 * n_sites / volume
299+
site_density = 1e30 * n_sites / structure.volume
303300
return (
304301
1.66914e-23
305302
* (self.long_v(structure) + 2 * self.trans_v(structure))
306303
/ 3.0
307-
/ num_density ** (-2 / 3)
304+
/ site_density ** (-2 / 3)
308305
* (1 - n_sites ** (-1 / 3))
309306
)
310307

311308
@raise_if_unphysical
312-
def snyder_total(self, structure: Structure):
309+
def snyder_total(self, structure: Structure) -> float:
313310
"""
314311
Calculates Snyder's total sound velocity (in SI units).
315312
@@ -322,7 +319,7 @@ def snyder_total(self, structure: Structure):
322319
return self.snyder_ac(structure) + self.snyder_opt(structure)
323320

324321
@raise_if_unphysical
325-
def clarke_thermalcond(self, structure: Structure):
322+
def clarke_thermalcond(self, structure: Structure) -> float:
326323
"""
327324
Calculates Clarke's thermal conductivity (in SI units).
328325
@@ -333,16 +330,15 @@ def clarke_thermalcond(self, structure: Structure):
333330
float: Clarke's thermal conductivity (in SI units)
334331
"""
335332
n_sites = len(structure)
336-
volume = structure.volume
337333
tot_mass = sum(e.atomic_mass for e in structure.species)
338334
n_atoms = structure.composition.num_atoms
339335
weight = float(structure.composition.weight)
340336
avg_mass = 1.6605e-27 * tot_mass / n_atoms
341-
mass_density = 1.6605e3 * n_sites * weight / (n_atoms * volume)
337+
mass_density = 1.6605e3 * n_sites * weight / (n_atoms * structure.volume)
342338
return 0.87 * 1.3806e-23 * avg_mass ** (-2 / 3) * mass_density ** (1 / 6) * self.y_mod**0.5
343339

344340
@raise_if_unphysical
345-
def cahill_thermalcond(self, structure: Structure):
341+
def cahill_thermalcond(self, structure: Structure) -> float:
346342
"""
347343
Calculates Cahill's thermal conductivity (in SI units).
348344
@@ -353,21 +349,47 @@ def cahill_thermalcond(self, structure: Structure):
353349
float: Cahill's thermal conductivity (in SI units)
354350
"""
355351
n_sites = len(structure)
356-
volume = structure.volume
357-
num_density = 1e30 * n_sites / volume
358-
return 1.3806e-23 / 2.48 * num_density ** (2 / 3) * (self.long_v(structure) + 2 * self.trans_v(structure))
352+
site_density = 1e30 * n_sites / structure.volume
353+
return 1.3806e-23 / 2.48 * site_density ** (2 / 3) * (self.long_v(structure) + 2 * self.trans_v(structure))
354+
355+
@due.dcite(
356+
Doi("10.1039/C7EE03256K"),
357+
description="Minimum thermal conductivity in the context of diffuson-mediated thermal transport",
358+
)
359+
@raise_if_unphysical
360+
def agne_diffusive_thermalcond(self, structure: Structure) -> float:
361+
"""
362+
Calculates Agne's diffusive thermal conductivity (in SI units).
363+
364+
Please cite the original authors if using this method
365+
M. T. Agne, R. Hanus, G. J. Snyder, Energy Environ. Sci. 2018, 11, 609-616.
366+
DOI: https://doi.org/10.1039/C7EE03256K
367+
368+
Args:
369+
structure: pymatgen structure object
370+
371+
Returns:
372+
float: Agne's diffusive thermal conductivity (in SI units)
373+
"""
374+
n_sites = len(structure)
375+
site_density = 1e30 * n_sites / structure.volume
376+
return (
377+
0.76
378+
* (site_density ** (2 / 3))
379+
* 1.3806e-23
380+
* ((1 / 3) * (2 * self.trans_v(structure) + self.long_v(structure)))
381+
)
359382

360383
@raise_if_unphysical
361-
def debye_temperature(self, structure: Structure):
384+
def debye_temperature(self, structure: Structure) -> float:
362385
"""
363-
Estimates the Debye temperature from longitudinal and
364-
transverse sound velocities.
386+
Estimates the Debye temperature from longitudinal and transverse sound velocities.
365387
366388
Args:
367389
structure: pymatgen structure object
368390
369391
Returns:
370-
float: debye temperature (in SI units)
392+
float: Debye temperature (in SI units)
371393
"""
372394
v0 = structure.volume * 1e-30 / len(structure)
373395
vl, vt = self.long_v(structure), self.trans_v(structure)

tests/analysis/elasticity/test_elastic.py

Lines changed: 31 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -117,52 +117,59 @@ def test_directional_poisson_ratio(self):
117117

118118
def test_structure_based_methods(self):
119119
# trans_velocity
120-
assert self.elastic_tensor_1.trans_v(self.structure) == approx(1996.35019877)
120+
struct = self.structure
121+
assert self.elastic_tensor_1.trans_v(struct) == approx(1996.35019877)
121122
# long_velocity
122-
assert self.elastic_tensor_1.long_v(self.structure) == approx(3534.68123832)
123+
assert self.elastic_tensor_1.long_v(struct) == approx(3534.68123832)
123124
# Snyder properties
124-
assert self.elastic_tensor_1.snyder_ac(self.structure) == approx(18.06127074)
125-
assert self.elastic_tensor_1.snyder_opt(self.structure) == approx(0.18937465)
126-
assert self.elastic_tensor_1.snyder_total(self.structure) == approx(18.25064540)
125+
assert self.elastic_tensor_1.snyder_ac(struct) == approx(18.06127074)
126+
assert self.elastic_tensor_1.snyder_opt(struct) == approx(0.18937465)
127+
assert self.elastic_tensor_1.snyder_total(struct) == approx(18.25064540)
127128
# Clarke
128-
assert self.elastic_tensor_1.clarke_thermalcond(self.structure) == approx(0.3450307)
129+
assert self.elastic_tensor_1.clarke_thermalcond(struct) == approx(0.3450307)
129130
# Cahill
130-
assert self.elastic_tensor_1.cahill_thermalcond(self.structure) == approx(0.37896275)
131+
cahill_thermal_cond = self.elastic_tensor_1.cahill_thermalcond(struct)
132+
assert cahill_thermal_cond == approx(0.37896275)
133+
# Agne
134+
agne_thermal_cond = self.elastic_tensor_1.agne_diffusive_thermalcond(struct)
135+
assert agne_thermal_cond == approx(0.23808966)
136+
# Test Agne / Cahill factor
137+
assert agne_thermal_cond / cahill_thermal_cond == approx(0.6282666)
131138
# Debye
132-
assert self.elastic_tensor_1.debye_temperature(self.structure) == approx(198.8037985019)
139+
assert self.elastic_tensor_1.debye_temperature(struct) == approx(198.8037985019)
133140

134141
# structure-property dict
135-
sprop_dict = self.elastic_tensor_1.get_structure_property_dict(self.structure)
136-
assert sprop_dict["long_v"] == approx(3534.68123832)
137-
for val in sprop_dict.values():
142+
struct_prop_dict = self.elastic_tensor_1.get_structure_property_dict(struct)
143+
assert struct_prop_dict["long_v"] == approx(3534.68123832)
144+
for val in struct_prop_dict.values():
138145
assert not isinstance(val, FloatWithUnit)
139-
for k, v in sprop_dict.items():
140-
if k == "structure":
141-
assert v == self.structure
146+
for key, val in struct_prop_dict.items():
147+
if key == "structure":
148+
assert val == struct
142149
else:
143-
f = getattr(self.elastic_tensor_1, k)
144-
if callable(f):
145-
assert getattr(self.elastic_tensor_1, k)(self.structure) == approx(v)
150+
attr = getattr(self.elastic_tensor_1, key)
151+
if callable(attr):
152+
assert getattr(self.elastic_tensor_1, key)(struct) == approx(val)
146153
else:
147-
assert getattr(self.elastic_tensor_1, k) == approx(v)
154+
assert getattr(self.elastic_tensor_1, key) == approx(val)
148155

149156
# Test other sprop dict modes
150-
sprop_dict = self.elastic_tensor_1.get_structure_property_dict(self.structure, include_base_props=False)
151-
assert "k_vrh" not in sprop_dict
157+
struct_prop_dict = self.elastic_tensor_1.get_structure_property_dict(struct, include_base_props=False)
158+
assert "k_vrh" not in struct_prop_dict
152159

153160
# Test ValueError being raised for structure properties
154161
test_et = deepcopy(self.elastic_tensor_1)
155162
test_et[0][0][0][0] = -100000
156163
prop_dict = test_et.property_dict
157-
for attr_name in sprop_dict:
164+
for attr_name in struct_prop_dict:
158165
if attr_name not in ([*prop_dict, "structure"]):
159166
with pytest.raises(
160167
ValueError, match="Bulk or shear modulus is negative, property cannot be determined"
161168
):
162-
getattr(test_et, attr_name)(self.structure)
169+
getattr(test_et, attr_name)(struct)
163170
with pytest.raises(ValueError, match="Bulk or shear modulus is negative, property cannot be determined"):
164-
test_et.get_structure_property_dict(self.structure)
165-
noval_sprop_dict = test_et.get_structure_property_dict(self.structure, ignore_errors=True)
171+
test_et.get_structure_property_dict(struct)
172+
noval_sprop_dict = test_et.get_structure_property_dict(struct, ignore_errors=True)
166173
assert noval_sprop_dict["snyder_ac"] is None
167174

168175
def test_new(self):

0 commit comments

Comments
 (0)