Utilities
PASCal.utils
¶
A series of utility functions used in the operation of PASCal.
Charge: typing_extensions.TypeAlias
¶
Pressure: typing_extensions.TypeAlias
¶
Strain: typing_extensions.TypeAlias
¶
Temperature: typing_extensions.TypeAlias
¶
Volume: typing_extensions.TypeAlias
¶
Functions¶
round_array(var: Optional[numpy.ndarray], dec: int) -> Union[numpy.ndarray, float]
¶
Rounding the number to desired decimal places
round()
is more accurate at rounding float numbers than np.round()
.
Also lets string values pass through.
Primarily used to rendering the results tables.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
var |
Optional[numpy.ndarray] |
The array or scalar to round. |
required |
dec |
int |
The number of decimal places to round to. |
required |
Returns:
Type | Description |
---|---|
Union[numpy.ndarray, float] |
The rounded array or scalar. |
Source code in PASCal/utils.py
def round_array(var: Optional[np.ndarray], dec: int) -> Union[np.ndarray, float, None]:
"""Rounding the number to desired decimal places
`round()` is more accurate at rounding float numbers than `np.round()`.
Also lets string values pass through.
Primarily used to rendering the results tables.
Parameters:
var: The array or scalar to round.
dec: The number of decimal places to round to.
Returns:
The rounded array or scalar.
"""
if var is None:
return None
if var.ndim == 0:
return round(var, dec) # type: ignore
for indices, val in np.ndenumerate(var):
if isinstance(val, str) and val == "n/a":
var[indices] = val
else:
var[indices] = round(val, dec)
return var
orthomat(lattices: ndarray) -> ndarray
¶
Compute the corresponding change-of-basis transformations (square matrix \(M\)) to the orthonormal axes \(E\) from the crystallographic axes \(A\), \(E = M \times A\).
Parameters:
Name | Type | Description | Default |
---|---|---|---|
lattices |
ndarray |
An Nx6 array of lattice parameters \((a, b, c, α, β, γ)\) in Å and degrees, respectively. |
required |
Returns:
Type | Description |
---|---|
ndarray |
An array of change-of-basis matrices M for the N cells. |
Source code in PASCal/utils.py
def orthomat(lattices: np.ndarray) -> np.ndarray:
"""Compute the corresponding change-of-basis transformations
(square matrix $M$) to the orthonormal axes $E$ from the
crystallographic axes $A$, $E = M \\times A$.
Parameters:
lattices: An Nx6 array of lattice parameters $(a, b, c, α, β, γ)$
in Å and degrees, respectively.
Returns:
An array of change-of-basis matrices M for the N cells.
"""
orth = np.zeros((np.shape(lattices)[0], 3, 3))
alphas, betas, gammas = (
np.radians(lattices[:, 3]),
np.radians(lattices[:, 4]),
np.radians(lattices[:, 5]),
)
gaS = np.arccos(
(np.cos(alphas) * np.cos(betas) - np.cos(gammas))
/ (np.sin(alphas) * np.sin(betas))
)
orth[:, 0, 0] = 1 / (lattices[:, 0] * np.sin(betas) * np.sin(gaS))
orth[:, 1, 0] = np.cos(gaS) / (lattices[:, 1] * np.sin(alphas) * np.sin(gaS))
orth[:, 2, 0] = (
np.cos(alphas) * np.cos(gaS) / np.sin(alphas) + np.cos(betas) / np.sin(betas)
) / (-1 * lattices[:, 2] * np.sin(gaS))
orth[:, 1, 1] = 1 / (lattices[:, 1] * np.sin(alphas))
orth[:, 2, 1] = -1 * np.cos(alphas) / (lattices[:, 2] * np.sin(alphas))
orth[:, 2, 2] = 1 / lattices[:, 2]
return orth
cell_vols(lattices: ndarray) -> ndarray
¶
Calculates the unit-cell volumes of a series of N unit cells.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
lattices |
ndarray |
An N x 6 array of lattice parameters (a, b, c, α, β, γ) in Å and degrees, respectively. |
required |
Returns:
Type | Description |
---|---|
ndarray |
The unit cell volumes in ų. |
Source code in PASCal/utils.py
def cell_vols(lattices: np.ndarray) -> np.ndarray:
"""Calculates the unit-cell volumes of a series of N unit cells.
Parameters:
lattices: An N x 6 array of lattice parameters (a, b, c, α, β, γ)
in Å and degrees, respectively.
Returns:
The unit cell volumes in ų.
"""
alphas, betas, gammas = (
np.radians(lattices[:, 3]),
np.radians(lattices[:, 4]),
np.radians(lattices[:, 5]),
)
return np.prod(lattices[:, :3], axis=1) * np.sqrt(
(1 - np.cos(alphas) ** 2 - np.cos(betas) ** 2 - np.cos(gammas) ** 2)
+ (2 * np.cos(alphas) * np.cos(betas) * np.cos(gammas))
)
empirical_pressure_strain_relation(p: ndarray, ε_0: ndarray, λ: float, p_c: float, nu: float) -> ndarray
¶
Empirical strain fit for pressure-dependent input data, with free parameters \(\lambda\) and \(\nu\):
Parameters:
Name | Type | Description | Default |
---|---|---|---|
p |
ndarray |
array of pressure data points (\(\p\)), |
required |
ε_0 |
ndarray |
strain at critical pressure (\(\epsilon_0\)) |
required |
λ |
float |
compressibility in (GPa^-nu) (\(\lambda\)) |
required |
p_c |
float |
critical pressure (GPa) |
required |
nu |
float |
rate of stiffening (\(\nu\sim 0.5\)) |
required |
Returns:
Type | Description |
---|---|
ndarray |
The strain at each pressure point. |
Source code in PASCal/utils.py
def empirical_pressure_strain_relation(
p: np.ndarray, ε_0: np.ndarray, λ: float, p_c: float, nu: float
) -> np.ndarray:
"""Empirical strain fit for pressure-dependent input data, with free
parameters $\\lambda$ and $\\nu$:
$$
\\epsilon(p) = \\epsilon_0 + \\lambda (p(T) - p_c)^\\nu
$$
Parameters:
p: array of pressure data points ($\\p$),
ε_0: strain at critical pressure ($\\epsilon_0$)
λ: compressibility in (GPa^-nu) ($\\lambda$)
p_c: critical pressure (GPa)
nu: rate of stiffening ($\\nu\\sim 0.5$)
Returns:
The strain at each pressure point.
"""
return ε_0 + (λ * ((p - p_c) ** nu))
get_compressibility(p: ndarray, λ: float, p_c: float, nu: float) -> ndarray
¶
Calculate the compressibility from the derivative \(-\left(\frac{\mathrm{d}ε}{\mathrm{d}p}\right)_T\)
Parameters:
Name | Type | Description | Default |
---|---|---|---|
p |
ndarray |
array of pressure data points, |
required |
λ |
float |
compressibility in (GPa^-nu) |
required |
p_c |
float |
critical pressure (GPa) |
required |
nu |
float |
rate of stiffening (0.5) |
required |
Returns:
Type | Description |
---|---|
ndarray |
The compressibility at each pressure point. |
Source code in PASCal/utils.py
def get_compressibility(p: np.ndarray, λ: float, p_c: float, nu: float) -> np.ndarray:
"""Calculate the compressibility from the derivative
$-\\left(\\frac{\\mathrm{d}ε}{\\mathrm{d}p}\\right)_T$
Parameters:
p: array of pressure data points,
λ: compressibility in (GPa^-nu)
p_c: critical pressure (GPa)
nu: rate of stiffening (0.5)
Returns:
The compressibility at each pressure point.
"""
return -λ * nu * ((p - p_c) ** (nu - 1))
get_compressibility_errors(p_cov: ndarray, p: ndarray, λ: float, p_c: float, nu: float) -> ndarray
¶
Calculate errors in compressibilities.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
p_cov |
ndarray |
the estimated covariance of optimal values of the empirical parameters |
required |
p |
ndarray |
array of pressure data points, |
required |
λ |
float |
compressibility in (GPa^-nu) |
required |
p_c |
float |
critical pressure (GPa) |
required |
nu |
float |
rate of stiffening (0.5) |
required |
Returns:
Type | Description |
---|---|
ndarray |
The error in compressibility at each pressure point. |
Source code in PASCal/utils.py
def get_compressibility_errors(
p_cov: np.ndarray, p: np.ndarray, λ: float, p_c: float, nu: float
) -> np.ndarray:
"""Calculate errors in compressibilities.
Parameters:
p_cov: the estimated covariance of optimal values of the empirical parameters
p: array of pressure data points,
λ: compressibility in (GPa^-nu)
p_c: critical pressure (GPa)
nu: rate of stiffening (0.5)
Returns:
The error in compressibility at each pressure point.
"""
J = np.zeros((4, p.shape[0])) # jacobian matrix
k_errors = np.zeros((p.shape[0]))
J[1] = ((p - p_c) ** (nu - 1)) * nu
J[2] = -1 * λ * nu * (nu - 1) * (p - p_c) ** (nu - 2)
J[3] = (nu * np.log(p - p_c) + 1) * ((p - p_c) ** (nu - 1)) * λ
for j in range(4):
for i in range(4):
k_errors += J[j] * J[i] * p_cov[j][i]
k_errors = np.sqrt(k_errors)
return k_errors
eta(V: ndarray, V_0: float) -> ndarray
¶
Defining parameter to be used in Birch-Murnaghan equations of state, \(\eta = (V_0 / V)^{1/3}\)
Parameters:
Name | Type | Description | Default |
---|---|---|---|
V |
ndarray |
unit-cell volume at a pressure point in ų. |
required |
V_0 |
float |
the zero pressure unit-cell volume in ų. |
required |
Returns:
Type | Description |
---|---|
ndarray |
The η parameter to be used in Birch-Murnaghan equations of state. |
Source code in PASCal/utils.py
def eta(V: np.ndarray, V_0: float) -> np.ndarray:
"""Defining parameter to be used in Birch-Murnaghan equations of state,
$\\eta = (V_0 / V)^{1/3}$
Parameters:
V: unit-cell volume at a pressure point in ų.
V_0: the zero pressure unit-cell volume in ų.
Returns:
The η parameter to be used in Birch-Murnaghan equations of state.
"""
return np.abs(V_0 / V) ** (1 / 3)
birch_murnaghan_2nd(V: ndarray, V_0: float, B: float) -> ndarray
¶
The second-order Birch-Murnaghan fit corresponding the equation of state:
Parameters:
Name | Type | Description | Default |
---|---|---|---|
V |
ndarray |
unit-cell volume at a pressure point in ų. |
required |
V_0 |
float |
the zero pressure unit-cell volume in ų. |
required |
B |
float |
Bulk modulus in GPa. |
required |
Returns:
Type | Description |
---|---|
ndarray |
The second-order p(V) fit at each measured pressure point. |
Source code in PASCal/utils.py
def birch_murnaghan_2nd(V: np.ndarray, V_0: float, B: float) -> np.ndarray:
"""The second-order Birch-Murnaghan fit corresponding the equation of state:
$$
p(V) = \\left(\\frac{3 B}{2}\\right) [η⁷ - η⁵]
$$
Parameters:
V: unit-cell volume at a pressure point in ų.
V_0: the zero pressure unit-cell volume in ų.
B: Bulk modulus in GPa.
Returns:
The second-order p(V) fit at each measured pressure point.
"""
return (3 / 2) * B * (eta(V, V_0) ** 7 - eta(V, V_0) ** 5)
birch_murnaghan_2nd_jac(V: ndarray, V_0: float, B: float) -> ndarray
¶
The second-order Birch-Murnaghan Jacobian corresponding the equation of state:
Parameters:
Name | Type | Description | Default |
---|---|---|---|
V |
ndarray |
unit-cell volume at a pressure point in ų. |
required |
V_0 |
float |
the zero pressure unit-cell volume in ų. |
required |
B |
float |
Bulk modulus in GPa. |
required |
Returns:
Type | Description |
---|---|
ndarray |
Jacobian (partial first derivative w.r.t each parameter for each V). |
Source code in PASCal/utils.py
def birch_murnaghan_2nd_jac(V: np.ndarray, V_0: float, B: float) -> np.ndarray:
"""The second-order Birch-Murnaghan Jacobian corresponding the equation of state:
$$
p(V) = \\left(\\frac{3 B}{2}\\right) [η⁷ - η⁵]
$$
Parameters:
V: unit-cell volume at a pressure point in ų.
V_0: the zero pressure unit-cell volume in ų.
B: Bulk modulus in GPa.
Returns:
Jacobian (partial first derivative w.r.t each parameter for each V).
"""
jac = np.zeros((V.shape[0], 2))
jac[:, 0] = (B / V_0 / 2) * (
7 * (eta(V, V_0) ** 7) - 5 * (eta(V, V_0) ** 5)
) # derivative w.r.t V_0
jac[:, 1] = 3 / 2 * (eta(V, V_0) ** 7 - eta(V, V_0) ** 5) # derivative w.r.t. B
return jac
birch_murnaghan_3rd(V: ndarray, V_0: float, B_0: float, Bprime: float) -> ndarray
¶
The third-order Birch-Murnaghan fit corresponding to the equation of state:
Parameters:
Name | Type | Description | Default |
---|---|---|---|
V |
ndarray |
unit-cell volume at a pressure point in ų. |
required |
V_0 |
float |
the zero pressure unit-cell volume in ų. |
required |
B_0 |
float |
Bulk modulus in GPa. |
required |
Bprime |
float |
pressure derivative of the bulk modulus (GPa/ų). |
required |
Returns:
Type | Description |
---|---|
ndarray |
The third-order p(V) fit at each measured pressure point. |
Source code in PASCal/utils.py
def birch_murnaghan_3rd(
V: np.ndarray, V_0: float, B_0: float, Bprime: float
) -> np.ndarray:
"""The third-order Birch-Murnaghan fit corresponding to the equation of state:
$$
p(V) = \\left(\\frac{3 B_0}{2}\\right) [η⁷ - η⁵] * \\left[1 + \\left(\\frac{3(B' - 4)}{4}\\right)[η² - 1]\\right]
$$
Parameters:
V: unit-cell volume at a pressure point in ų.
V_0: the zero pressure unit-cell volume in ų.
B_0: Bulk modulus in GPa.
Bprime: pressure derivative of the bulk modulus (GPa/ų).
Returns:
The third-order p(V) fit at each measured pressure point.
"""
return (
3
/ 2
* B_0
* (eta(V, V_0) ** 7 - eta(V, V_0) ** 5)
* (1 + 3 / 4 * (Bprime - 4) * (eta(V, V_0) ** 2 - 1))
)
birch_murnaghan_3rd_jac(V: ndarray, V_0: float, B_0: float, Bprime: float) -> ndarray
¶
The third-order Birch-Murnaghan Jacobian corresponding to the equation of state: $$ p(V) = \left(\frac{3 B_0}{2}\right) [η⁷ - η⁵] * \left[1 + \left(\frac{3(B' - 4)}{4}\right)[η² - 1]\right] $$
Parameters:
Name | Type | Description | Default |
---|---|---|---|
V |
ndarray |
unit-cell volume at a pressure point in ų. |
required |
V_0 |
float |
the zero pressure unit-cell volume in ų. |
required |
B_0 |
float |
Bulk modulus in GPa. |
required |
Bprime |
float |
pressure derivative of the bulk modulus (GPa/ų). |
required |
Returns:
Type | Description |
---|---|
ndarray |
The third-order p(V) Jacobian (partial derivatives w.r.t each parameter) at each measured pressure point. |
Source code in PASCal/utils.py
def birch_murnaghan_3rd_jac(
V: np.ndarray, V_0: float, B_0: float, Bprime: float
) -> np.ndarray:
"""The third-order Birch-Murnaghan Jacobian corresponding to the equation of state:
$$
p(V) = \\left(\\frac{3 B_0}{2}\\right) [η⁷ - η⁵] * \\left[1 + \\left(\\frac{3(B' - 4)}{4}\\right)[η² - 1]\\right]
$$
Parameters:
V: unit-cell volume at a pressure point in ų.
V_0: the zero pressure unit-cell volume in ų.
B_0: Bulk modulus in GPa.
Bprime: pressure derivative of the bulk modulus (GPa/ų).
Returns:
The third-order p(V) Jacobian (partial derivatives w.r.t each parameter) at each measured pressure point.
"""
jac = np.zeros((V.shape[0], 3))
jac[:, 0] = (
B_0
* eta(V, V_0) ** 5
/ 8
/ V_0
* (
3 * Bprime * (5 - 14 * eta(V, V_0) ** 2 + 9 * eta(V, V_0) ** 4)
- 4 * (20 - 49 * eta(V, V_0) ** 2 + 27 * eta(V, V_0) ** 4)
)
)
jac[:, 1] = (
3
/ 2
* (eta(V, V_0) ** 7 - eta(V, V_0) ** 5)
* (1 + 3 / 4 * (Bprime - 4) * (eta(V, V_0) ** 2 - 1))
) # derviative w.r.t B_0
jac[:, 2] = (
3
/ 2
* B_0
* (eta(V, V_0) ** 7 - eta(V, V_0) ** 5)
* (3 / 4 * (eta(V, V_0) ** 2 - 1))
) # derivative w.r.t. BPrime
return jac
birch_murnaghan_3rd_pc(V: ndarray, V_0: float, B_0: float, Bprime: float, p_c: Optional[float] = None) -> ndarray
¶
The third-order Birch-Murnaghan fit corresponding the equation of state with incorporation of non-zero critical pressure:
from Sata et al, 10.1103/PhysRevB.65.104114 (2002) and Eq. 11 from Cliffe & Goodwin 10.1107/S0021889812043026 (2012).
Parameters:
Name | Type | Description | Default |
---|---|---|---|
V |
ndarray |
unit-cell volume at a pressure point in ų. |
required |
V_0 |
float |
the zero pressure unit-cell volume in ų. |
required |
B_0 |
float |
Bulk modulus in GPa. |
required |
Bprime |
float |
pressure derivative of the bulk modulus (GPa/ų). |
required |
p_c |
Optional[float] |
critical pressure (GPa) |
None |
Returns:
Type | Description |
---|---|
ndarray |
The third-order p(V) fit at each measured pressure point. |
Source code in PASCal/utils.py
def birch_murnaghan_3rd_pc(
V: np.ndarray,
V_0: float,
B_0: float,
Bprime: float,
p_c: Optional[float] = None,
) -> np.ndarray:
"""The third-order Birch-Murnaghan fit corresponding the equation of state
with incorporation of non-zero critical pressure:
$$
p(V) = η⁵ * \\left[p_c - \\frac{1}{2}(3 B_0 - 5 p_c)(1 - η²) + \\frac{9}{8} B_0 \\left(B' - 4 + \\frac{35 p_c}{9 B_0}\\right)(1 - η²)²\\right]
$$
from Sata et al, 10.1103/PhysRevB.65.104114 (2002) and Eq. 11
from Cliffe & Goodwin 10.1107/S0021889812043026 (2012).
Parameters:
V: unit-cell volume at a pressure point in ų.
V_0: the zero pressure unit-cell volume in ų.
B_0: Bulk modulus in GPa.
Bprime: pressure derivative of the bulk modulus (GPa/ų).
p_c: critical pressure (GPa)
Returns:
The third-order p(V) fit at each measured pressure point.
"""
if p_c is None:
raise RuntimeError("Critical pressure must be specified")
return (eta(V, V_0) ** 5) * (
p_c
- 1 / 2 * ((3 * B_0) - (5 * p_c)) * (1 - (eta(V, V_0) ** 2))
+ (9 / 8)
* B_0
* (Bprime - 4 + (35 * p_c) / (9 * B_0))
* (1 - (eta(V, V_0) ** 2)) ** 2
)
normalise_crys_axes(calc_crys_ax: ndarray, principal_components: ndarray) -> ndarray
¶
Normalise the crysallographic axes for the indicatrix plot by dividing through the maximum eigenvalue.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
calc_crys_ax |
ndarray |
calculated crysallographic axes |
required |
principal_components |
ndarray |
eigenvalues |
required |
Returns:
Type | Description |
---|---|
ndarray |
Normalised crysallographic axes |
Source code in PASCal/utils.py
def normalise_crys_axes(
calc_crys_ax: np.ndarray, principal_components: np.ndarray
) -> np.ndarray:
"""Normalise the crysallographic axes for the indicatrix plot
by dividing through the maximum eigenvalue.
Parameters:
calc_crys_ax: calculated crysallographic axes
principal_components: eigenvalues
Returns:
Normalised crysallographic axes
"""
maxalpha = np.abs(np.max(principal_components))
maxlen: float = np.max(np.linalg.norm(calc_crys_ax, axis=-1))
return calc_crys_ax * maxalpha / maxlen
indicatrix(principal_components: ndarray) -> Tuple[float, numpy.ndarray, numpy.ndarray, numpy.ndarray, numpy.ndarray]
¶
Generate angular data for indicatrix plot.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
principal_components |
ndarray |
eigenvalues |
required |
Returns:
Type | Description |
---|---|
Tuple[float, numpy.ndarray, numpy.ndarray, numpy.ndarray, numpy.ndarray] |
Index of the maximum principal component, and \((R, X, Y, Z)\) coordinates for the indicatrix plot. |
Source code in PASCal/utils.py
def indicatrix(
principal_components: np.ndarray,
) -> Tuple[float, np.ndarray, np.ndarray, np.ndarray, np.ndarray]:
"""Generate angular data for indicatrix plot.
Parameters:
principal_components: eigenvalues
Returns:
Index of the maximum principal component, and
$(R, X, Y, Z)$ coordinates for the indicatrix plot.
"""
theta, phi = np.linspace(0, np.pi, 100), np.linspace(0, 2 * np.pi, 2 * 100)
Θ, Φ = np.meshgrid(theta, phi)
max_component: float = np.max(np.abs(principal_components))
R = (
principal_components[0] * (np.sin(Θ) * np.cos(Φ)) ** 2
+ principal_components[1] * (np.sin(Θ) * np.sin(Φ)) ** 2
+ principal_components[2] * (np.cos(Θ) ** 2)
)
X = R * np.sin(Θ) * np.cos(Φ)
Y = R * np.sin(Θ) * np.sin(Φ)
Z = R * np.cos(Θ)
return max_component, R, X, Y, Z
calculate_strain(orthonormed_unit_cells: ndarray, mode: str = 'eulerian', finite: bool = True) -> ndarray
¶
Calculates the strain from the orthonormal basis.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
orthonormed_unit_cells |
ndarray |
An N x 6 array of unit cell parameters in orthonormal axes. |
required |
mode |
str |
The strain mode, either "eulerian" or "lagrangian". |
'eulerian' |
finite |
bool |
Whether to correct for finite strains or infinitesimal strains. |
True |
Returns:
Type | Description |
---|---|
ndarray |
The array of strains. |
Source code in PASCal/utils.py
def calculate_strain(
orthonormed_unit_cells: np.ndarray, mode: str = "eulerian", finite: bool = True
) -> np.ndarray:
"""Calculates the strain from the orthonormal basis.
Parameters:
orthonormed_unit_cells: An N x 6 array of unit cell parameters in orthonormal axes.
mode: The strain mode, either "eulerian" or "lagrangian".
finite: Whether to correct for finite strains or infinitesimal strains.
Returns:
The array of strains.
"""
if mode == "eulerian":
strain = np.identity(3) - np.matmul(
np.linalg.inv(orthonormed_unit_cells[0, :]), orthonormed_unit_cells[0:, :]
)
elif mode == "lagrangian":
strain = np.matmul(
np.linalg.inv(orthonormed_unit_cells[0:, :]), orthonormed_unit_cells[0, :]
) - np.identity(3)
else:
raise RuntimeError(f"Did not understand mode {mode!r}")
if finite:
strain = (
strain
+ (
np.transpose(strain, axes=[0, 2, 1])
+ np.matmul(strain, np.transpose(strain, axes=[0, 2, 1]))
)
) / 2
else:
strain = (strain + np.transpose(strain, axes=[0, 2, 1])) / 2
return strain
match_axes(principal_axes: ndarray, unit_cells: ndarray, diagonal_strain: ndarray) -> Tuple[numpy.ndarray, numpy.ndarray]
¶
Matches the axes of the strain eigenvectors to the first strain.
Source code in PASCal/utils.py
def match_axes(
principal_axes: np.ndarray, unit_cells: np.ndarray, diagonal_strain: np.ndarray
) -> Tuple[np.ndarray, np.ndarray]:
"""Matches the axes of the strain eigenvectors to the first strain."""
for n in range(2, len(unit_cells)):
# an array matching the axes against each other
match = np.dot(principal_axes[1, :].T, principal_axes[n, :])
# a list of the axes needed to convert the eigenvalues and vectors into a consistent format
axes_order = np.zeros((3), dtype=np.int8)
axes_order_cost = np.zeros((6)) # cost function for each item
permute = list(itertools.permutations([0, 1, 2]))
m = 0
for item in permute:
for i in range(3):
axes_order_cost[m] += np.abs(match[i][item[i]])
m += 1
axes_order = np.array(permute[np.argmax(axes_order_cost)])
diagonal_strain[n, [0, 1, 2]] = diagonal_strain[n, axes_order]
principal_axes[n, :, [0, 1, 2]] = principal_axes[n, :, axes_order]
return principal_axes, diagonal_strain
rotate_to_principal_axes(orthonormed_cells, principal_axes, median_x)
¶
Calculates the unit cell axes in the principal axis basis
Parameters:
Name | Type | Description | Default |
---|---|---|---|
orthonormed_unit_cells |
An N x 6 array of unit cell parameters in orthonormal axes. |
required | |
principal_axes |
eigenvectors in orthonormal coordinates |
required | |
median_x |
index of the median data point |
required |
Returns:
Type | Description |
---|---|
The principal axes in the crystallographic frame and the reverse. |
Source code in PASCal/utils.py
def rotate_to_principal_axes(orthonormed_cells, principal_axes, median_x):
"""Calculates the unit cell axes in the principal axis basis
Parameters:
orthonormed_unit_cells: An N x 6 array of unit cell parameters in orthonormal axes.
principal_axes: eigenvectors in orthonormal coordinates
median_x: index of the median data point
Returns:
The principal axes in the crystallographic frame and the reverse.
"""
principal_axis_crys = np.transpose(
np.matmul(orthonormed_cells, principal_axes[:, :, :]), axes=[0, 2, 1]
) # Eigenvector projected on crystallographic axes, UVW
crys_prin_ax = np.linalg.inv(
principal_axis_crys
) # Unit Cell in Principal axes coordinates, å
principal_axis_crys = (
principal_axis_crys.T / (np.sum(principal_axis_crys**2, axis=2) ** 0.5).T
).T # normalised to make UVW near 1
### Ensures the largest component of each eigenvector is positive to make comparing easier
max_axis = np.argmax(
np.abs(principal_axis_crys), axis=2
) # find the largest value of each eigenvector
I, J = np.indices(max_axis.shape) # noqa
mask = principal_axis_crys[I, J, max_axis] < 0
principal_axis_crys[mask, :] *= -1
# transpositions to take advantage of broadcasting, not maths
median_principal_axis_crys = principal_axis_crys[median_x]
return median_principal_axis_crys, principal_axis_crys, crys_prin_ax