xrdkit 0.1.0__py3-none-any.whl

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.
xrdkit/density.py ADDED
@@ -0,0 +1,144 @@
1
+ """Theoretical density from a refined cell and a composition."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Mapping
6
+
7
+ import numpy as np
8
+ from scipy.constants import Avogadro
9
+
10
+ from xrdkit.indexing import TetragonalCell
11
+
12
+ __all__ = [
13
+ "ATOMIC_MASSES",
14
+ "cell_volume",
15
+ "formula_mass",
16
+ "theoretical_density",
17
+ ]
18
+
19
+ # Standard atomic weights in g/mol, IUPAC 2021 (Prohaska et al., Pure Appl.
20
+ # Chem. 94 (2022) 573). Elements whose standard atomic weight is an interval
21
+ # (Li, O, Mg, Si, Pb) take the IUPAC conventional value. The 2024 table revised
22
+ # Gd to 157.249 and Zr to 91.222; the 2021 values are kept here.
23
+ ATOMIC_MASSES: dict[str, float] = {
24
+ "Li": 6.94,
25
+ "O": 15.999,
26
+ "Na": 22.98976928,
27
+ "Mg": 24.305,
28
+ "Al": 26.9815384,
29
+ "Si": 28.085,
30
+ "K": 39.0983,
31
+ "Ca": 40.078,
32
+ "Ti": 47.867,
33
+ "Cr": 51.9961,
34
+ "Mn": 54.938043,
35
+ "Fe": 55.845,
36
+ "Co": 58.933194,
37
+ "Ni": 58.6934,
38
+ "Cu": 63.546,
39
+ "Zn": 65.38,
40
+ "Sr": 87.62,
41
+ "Y": 88.905838,
42
+ "Zr": 91.224,
43
+ "Nb": 92.90637,
44
+ "Sn": 118.710,
45
+ "Ba": 137.327,
46
+ "La": 138.90547,
47
+ "Ce": 140.116,
48
+ "Nd": 144.242,
49
+ "Sm": 150.36,
50
+ "Gd": 157.25,
51
+ "Hf": 178.486,
52
+ "Ta": 180.94788,
53
+ "W": 183.84,
54
+ "Pb": 207.2,
55
+ "Bi": 208.98040,
56
+ }
57
+
58
+ # Cubic angstroms in a cubic centimetre.
59
+ A3_PER_CM3 = 1e24
60
+
61
+
62
+ def formula_mass(composition: Mapping[str, float]) -> float:
63
+ """Return the mass of one formula unit in g/mol.
64
+
65
+ Parameters
66
+ ----------
67
+ composition
68
+ Element symbol to stoichiometric coefficient, such as
69
+ ``{"Sr": 1, "Ti": 1, "O": 3}``.
70
+
71
+ Raises
72
+ ------
73
+ ValueError
74
+ If the composition is empty, names an element not in
75
+ :data:`ATOMIC_MASSES`, or has a negative coefficient.
76
+ """
77
+ if not composition:
78
+ raise ValueError("composition is empty")
79
+ unknown = sorted(set(composition) - set(ATOMIC_MASSES))
80
+ if unknown:
81
+ raise ValueError(f"No atomic mass for {', '.join(unknown)}")
82
+ negative = sorted(element for element, n in composition.items() if n < 0)
83
+ if negative:
84
+ raise ValueError(f"Negative coefficient for {', '.join(negative)}")
85
+ return float(sum(ATOMIC_MASSES[element] * n for element, n in composition.items()))
86
+
87
+
88
+ def cell_volume(
89
+ cell: TetragonalCell,
90
+ esd_a: float | None = None,
91
+ esd_c: float | None = None,
92
+ ) -> tuple[float, float | None]:
93
+ """Return the volume a^2 c of a tetragonal cell and its esd, in cubic angstroms.
94
+
95
+ The esd is propagated to first order, sqrt((2ac esd_a)^2 + (a^2 esd_c)^2),
96
+ treating a and c as uncorrelated since the fits here report no covariance.
97
+ An esd left as ``None`` counts as zero, and the esd returned is ``None``
98
+ only when neither is given.
99
+ """
100
+ volume = cell.a**2 * cell.c
101
+ if esd_a is None and esd_c is None:
102
+ return float(volume), None
103
+ from_a = 2.0 * cell.a * cell.c * (esd_a or 0.0)
104
+ from_c = cell.a**2 * (esd_c or 0.0)
105
+ return float(volume), float(np.hypot(from_a, from_c))
106
+
107
+
108
+ def theoretical_density(
109
+ composition: Mapping[str, float],
110
+ z: float,
111
+ volume_a3: float,
112
+ esd_volume_a3: float | None = None,
113
+ ) -> tuple[float, float | None]:
114
+ """Return the X-ray density in g/cm^3 and its esd.
115
+
116
+ The density is Z M / (N_A V), with M the formula mass and V the cell
117
+ volume. Only the volume carries an error here, so the density has the same
118
+ relative esd as the volume; the esd returned is ``None`` when no volume esd
119
+ is given.
120
+
121
+ Parameters
122
+ ----------
123
+ composition
124
+ Element symbol to stoichiometric coefficient for one formula unit.
125
+ z
126
+ Formula units per cell.
127
+ volume_a3
128
+ Cell volume in cubic angstroms.
129
+ esd_volume_a3
130
+ Estimated standard deviation of the volume, in cubic angstroms.
131
+
132
+ Raises
133
+ ------
134
+ ValueError
135
+ If ``z`` or ``volume_a3`` is not positive, or from :func:`formula_mass`.
136
+ """
137
+ if z <= 0:
138
+ raise ValueError(f"z must be positive, got {z}")
139
+ if volume_a3 <= 0:
140
+ raise ValueError(f"volume_a3 must be positive, got {volume_a3}")
141
+ density = z * formula_mass(composition) / (Avogadro * volume_a3 / A3_PER_CM3)
142
+ if esd_volume_a3 is None:
143
+ return float(density), None
144
+ return float(density), float(density * esd_volume_a3 / volume_a3)