Source code for hexrd.phase_transition.texture.uniform_odf
"""
Uniform Orientation Distribution Function (ODF)
Implements a constant ODF representing completely random texture where all
orientations are equally likely. The uniform ODF has a constant value of
1 MRD (multiples of a random distribution), the standard normalization
for texture analysis on SO(3).
"""
from typing import Optional, Union
import numpy as np
from hexrd.phase_transition.texture.kernels import (
_symmetry_quaternions,
_Symmetry,
)
[docs]class UniformODF:
"""
Uniform (random) orientation distribution function.
Represents a completely isotropic texture where all crystal orientations
are equally likely. The value is constant at 1 MRD (multiples of a
random distribution), the standard normalization where the uniform
distribution serves as the reference density.
Parameters
----------
crystal_symmetry : str or numpy.ndarray, optional
Crystal symmetry as a Laue group label ('ci', 'c2h', 'd2h', 'c4h',
'd4h', 's6', 'd3d', 'c6h', 'd6h', 'th', 'oh') or a quaternion
symmetry array. Validated but inert (the value is 1 MRD regardless);
default None.
sample_symmetry : str or numpy.ndarray, optional
Sample symmetry as a label ('triclinic', 'monoclinic',
'orthorhombic') or a quaternion array. Validated but inert;
default None.
Attributes
----------
value : float
Constant ODF value = 1.0 (MRD)
crystal_symmetry : str or None
Crystal symmetry label, or None if unset or given as an array
sample_symmetry : str or None
Sample symmetry label, or None if unset or given as an array
Examples
--------
>>> odf = UniformODF('d6h', 'triclinic') # hexagonal crystal
>>> orientations = np.eye(3).reshape(1, 3, 3)
>>> values = odf.eval(orientations)
>>> print(values[0]) # 1.0
"""
# Constant value for uniform ODF: 1 MRD (standard normalization)
_UNIFORM_VALUE = 1.0
def __init__(
self,
crystal_symmetry: _Symmetry = None,
sample_symmetry: _Symmetry = None,
) -> None:
"""
Initialize uniform ODF with optional symmetries.
Parameters
----------
crystal_symmetry : str or numpy.ndarray, optional
Crystal symmetry notation, default None
sample_symmetry : str or numpy.ndarray, optional
Sample symmetry notation, default None
"""
# Validate through the same path the kernel uses, so the texture
# package shares one accepted symmetry set and one error behavior.
# Symmetry is inert for a uniform ODF (the value is 1 MRD for every
# orientation); it is kept as metadata and for parity with the
# kernel-backed ODFs (e.g. UnimodalODF).
_symmetry_quaternions(crystal_symmetry, symtype='crystal')
_symmetry_quaternions(sample_symmetry, symtype='sample')
# Retain the string label; a symmetry given as a quaternion array
# has no recoverable label (mirrors the kernel's behavior).
self._crystal_symmetry = (
crystal_symmetry if isinstance(crystal_symmetry, str) else None
)
self._sample_symmetry = (
sample_symmetry if isinstance(sample_symmetry, str) else None
)
@property
def crystal_symmetry(self) -> Optional[str]:
"""Crystal symmetry label, or None if unset or given as an array."""
return self._crystal_symmetry
@property
def sample_symmetry(self) -> Optional[str]:
"""Sample symmetry label, or None if unset or given as an array."""
return self._sample_symmetry
@property
def value(self) -> float:
"""Constant ODF value in MRD."""
return self._UNIFORM_VALUE
[docs] def eval(
self, orientations: np.ndarray,
) -> Union[float, np.ndarray]:
"""
Evaluate uniform ODF at given orientations.
For a uniform ODF, all orientations return 1.0 MRD
(multiples of a random distribution).
Parameters
----------
orientations : array_like
Orientation matrices. Can be:
- Single 3x3 rotation matrix
- Array of shape (N, 3, 3) for N orientations
- Any shape ending in (3, 3) for rotation matrices
Returns
-------
float or numpy.ndarray
ODF values, all equal to 1.0 (MRD). A scalar float for a single
(3, 3) orientation; otherwise an array whose shape matches the
leading dimensions of the input.
Examples
--------
>>> odf = UniformODF('oh', 'triclinic')
>>>
>>> # Single orientation
>>> R = np.eye(3)
>>> value = odf.eval(R) # scalar
>>>
>>> # Multiple orientations
>>> Rs = np.array([np.eye(3), np.eye(3)]) # shape (2, 3, 3)
>>> values = odf.eval(Rs) # shape (2,)
"""
orientations = np.asarray(orientations)
# Validate input shape - must end with (3, 3)
if orientations.shape[-2:] != (3, 3):
raise ValueError(
f"Orientation matrices must have shape (..., 3, 3), "
f"got {orientations.shape}"
)
# Return array of uniform values with shape matching input leading dims
output_shape = orientations.shape[:-2]
if output_shape == ():
# Single orientation - return scalar
return self._UNIFORM_VALUE
else:
# Multiple orientations - return array
return np.full(output_shape, self._UNIFORM_VALUE)
def __repr__(self) -> str:
"""String representation of UniformODF."""
return (
f"UniformODF(crystal_symmetry={self.crystal_symmetry!r}, "
f"sample_symmetry={self.sample_symmetry!r}, "
f"value={self.value:.6f})"
)
def __str__(self) -> str:
"""String representation of UniformODF."""
return (
f"Uniform ODF with {self.crystal_symmetry or 'none'} crystal "
f"symmetry and {self.sample_symmetry or 'none'} sample symmetry\n"
f"Constant value: {self.value:.6f} (random texture)"
)