diff --git a/docs/_images/many_crystals_quartz.png b/docs/_images/many_crystals_quartz.png new file mode 100644 index 0000000..f68ae1a Binary files /dev/null and b/docs/_images/many_crystals_quartz.png differ diff --git a/docs/_images/single_crystal_quartz.png b/docs/_images/single_crystal_quartz.png new file mode 100644 index 0000000..23c3baf Binary files /dev/null and b/docs/_images/single_crystal_quartz.png differ diff --git a/docs/_images/testing_rocking_curves.png b/docs/_images/testing_rocking_curves.png new file mode 100644 index 0000000..5c5e53e Binary files /dev/null and b/docs/_images/testing_rocking_curves.png differ diff --git a/docs/_sources/gaussian_model_theory.rst.txt b/docs/_sources/gaussian_model_theory.rst.txt new file mode 100644 index 0000000..353fb63 --- /dev/null +++ b/docs/_sources/gaussian_model_theory.rst.txt @@ -0,0 +1,582 @@ +:orphan: + +.. _gaussian-model-theory: + +Gaussian model: theory and references +===================================== + +This page describes the scattering model and approximations underlying +:mod:`xrd_simulator.gaussian_crystal_model`. A worked example is provided in +the module documentation. + +.. contents:: On this page + :local: + :depth: 2 + +Gaussian subgrain representation +-------------------------------- + +Gaussian splatting represents a three-dimensional scene using Gaussian +primitives whose projected contributions can be rendered efficiently by +rasterization [Kerbl2023]_. Here, a polycrystal is represented by Gaussian +grains or subgrains, each with a Gaussian real-space density profile and a +narrow Gaussian distribution of lattice orientations. Under the approximations +below, each subgrain produces Gaussian-shaped diffraction spots on the +detector. Related Gaussian-basis methods for diffraction prediction and +partiality estimation have been demonstrated in serial crystallography +[Brehm2023]_. + +Several effects can contribute to diffraction-spot broadening: grain size, +detector point-spread, incident-beam bandwidth, incident-beam angular +divergence, lattice misorientation (mosaicity), and distributions of strain. +When these effects have Gaussian profiles and their deviations are small +enough to neglect nonlinear terms, they can be combined within a Gaussian +model. + +The model described here includes the real-space extent of the illuminated +subgrains and their mosaicity. The incident beam is monochromatic and +collimated, and each subgrain has a uniform strain tensor. A uniform strain +can shift a reflection but does not describe a distribution of lattice +spacings within that subgrain. Small sample rotations during an exposure are +approximated by additional Gaussian orientation broadening. + +.. note:: + + The Gaussian-splat rendering path does not apply a separate detector + point-spread convolution. The ``gaussian_sigma`` and + ``max_gaussian_kernel_radius`` detector settings retained in the worked + example therefore do not add this broadening to its Gaussian spots. + +Scattering theory +----------------- + +Scattered-beam distribution +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Let the incident beam have a phase-space density :math:`p(\mathbf{k})` +centred on :math:`\mathbf{k}_0`, with nominal wavenumber + +.. math:: + :label: gaussian-wavenumber + + k = \lvert\mathbf{k}_0\rvert = \frac{2\pi}{\lambda_0}. + +For a particular reflection, let :math:`f(\mathbf{q})` describe the subgrain's +reciprocal-space map (RSM) around a reciprocal-lattice vector +:math:`\mathbf{G}_0`. The scattered-beam distribution is written as + +.. math:: + :label: gaussian-scattering-integral + + I(\mathbf{p}) + = \int \mathrm{d}^{3}\mathbf{k} + \int \mathrm{d}^{3}\mathbf{q}\; + p(\mathbf{k})\,f(\mathbf{q})\, + \delta\!\left(\lvert\mathbf{k}\rvert-\lvert\mathbf{p}\rvert\right) + \delta^{(3)}\!\left(\mathbf{k}+\mathbf{q}-\mathbf{p}\right). + +The scalar Dirac delta enforces elastic scattering, while the +three-dimensional Dirac delta enforces momentum conservation. + +Incident-beam coordinates +~~~~~~~~~~~~~~~~~~~~~~~~~ + +Following the coordinate construction used by Poulsen et al. +[Poulsen2017]_, write the incident wavevector as + +.. math:: + :label: gaussian-incident-wavevector + + \mathbf{k} + = \mathbf{k}_0 + + k\left( + \varepsilon\,\hat{\mathbf{k}}_0 + + \zeta_{\parallel}\,\hat{\mathbf{k}}_{\parallel} + + \zeta_{\perp}\,\hat{\mathbf{k}}_{\perp} + \right). + +A hat denotes a unit vector. The vector +:math:`\hat{\mathbf{k}}_{\parallel}` is perpendicular to +:math:`\mathbf{k}_0` and lies in the plane spanned by :math:`\mathbf{k}_0` +and :math:`\mathbf{G}_0`. Its sign is chosen so that +:math:`\mathbf{G}_0\cdot\hat{\mathbf{k}}_{\parallel}>0`. Complete the +right-handed basis with + +.. math:: + + \hat{\mathbf{k}}_{\perp} + = \hat{\mathbf{k}}_0\times\hat{\mathbf{k}}_{\parallel}. + +The coordinate :math:`\varepsilon` describes the relative wavenumber +deviation, and :math:`\zeta_{\parallel}` and :math:`\zeta_{\perp}` describe +small angular deviations of the incident beam. + +Reciprocal-space coordinates +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Define the nominal Bragg angle and the exactly aligned scattering vector by + +.. math:: + :label: gaussian-nominal-scattering-vector + + \begin{aligned} + \theta_0 + &= \arcsin\!\left(\frac{\lvert\mathbf{G}_0\rvert}{2k}\right), \\ + \mathbf{Q} + &= 2k\sin\theta_0\left( + \cos\theta_0\,\hat{\mathbf{k}}_{\parallel} + - \sin\theta_0\,\hat{\mathbf{k}}_0 + \right) \\ + &= 2k\sin\theta_0\,\hat{\mathbf{Q}}. + \end{aligned} + +The vectors :math:`\mathbf{G}_0` and :math:`\mathbf{Q}` coincide when the +reflection is exactly aligned. For a small misalignment, their difference is +parallel, to first order, to the rocking direction + +.. math:: + + \hat{\mathbf{q}}_{\mathrm{rock}} + = \cos\theta_0\,\hat{\mathbf{k}}_0 + + \sin\theta_0\,\hat{\mathbf{k}}_{\parallel}. + +Use dimensionless rocking, strain, and rolling coordinates to write + +.. math:: + :label: gaussian-reciprocal-coordinates + + \begin{aligned} + \mathbf{q} + &= \mathbf{G}_0 + + 2k\sin\theta_0\left( + q_{\mathrm{rock}}\,\hat{\mathbf{q}}_{\mathrm{rock}} + + q_{\mathrm{strain}}\,\hat{\mathbf{Q}} + + q_{\mathrm{roll}}\,\hat{\mathbf{k}}_{\perp} + \right) \\ + &\approx \mathbf{Q} + + 2k\sin\theta_0\left( + (q_{\mathrm{rock}}-\delta q)\, + \hat{\mathbf{q}}_{\mathrm{rock}} + + q_{\mathrm{strain}}\,\hat{\mathbf{Q}} + + q_{\mathrm{roll}}\,\hat{\mathbf{k}}_{\perp} + \right), + \end{aligned} + +where + +.. math:: + :label: gaussian-rocking-offset + + \delta q + = \frac{ + (\mathbf{Q}-\mathbf{G}_0)\cdot\hat{\mathbf{q}}_{\mathrm{rock}} + }{2k\sin\theta_0}. + +This offset measures the reflection's misalignment and is equal to the +rocking angle to first order, with the sign convention above. The incident +coordinates :math:`(\varepsilon,\zeta_{\parallel},\zeta_{\perp})` and the +reciprocal-space coordinates +:math:`(q_{\mathrm{rock}},q_{\mathrm{strain}},q_{\mathrm{roll}})` separate +bandwidth, collimation, misorientation, and strain effects. + +Outgoing-beam coordinates +~~~~~~~~~~~~~~~~~~~~~~~~~ + +The nominal scattered wavevector is + +.. math:: + :label: gaussian-nominal-outgoing-wavevector + + \mathbf{k}_h + = \mathbf{k}_0+\mathbf{Q} + = k\left( + \cos(2\theta_0)\,\hat{\mathbf{k}}_0 + + \sin(2\theta_0)\,\hat{\mathbf{k}}_{\parallel} + \right). + +Define the radial direction perpendicular to :math:`\mathbf{k}_h` by + +.. math:: + + \hat{\mathbf{k}}_{\mathrm{rad}} + = \cos(2\theta_0)\,\hat{\mathbf{k}}_{\parallel} + - \sin(2\theta_0)\,\hat{\mathbf{k}}_0. + +The outgoing wavevector can then be written as + +.. math:: + :label: gaussian-outgoing-wavevector + + \mathbf{p} + = \mathbf{k}_h + + k\left( + \varepsilon'\,\hat{\mathbf{k}}_h + + \psi_{\mathrm{rad}}\,\hat{\mathbf{k}}_{\mathrm{rad}} + + \psi_{\mathrm{azim}}\,\hat{\mathbf{k}}_{\perp} + \right). + +Here :math:`\psi_{\mathrm{rad}}` and :math:`\psi_{\mathrm{azim}}` are small +transverse angular deviations from the nominal outgoing direction. + +Linearized conservation equations +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +To first order, + +.. math:: + + \lvert\mathbf{k}\rvert\approx k(1+\varepsilon), + \qquad + \lvert\mathbf{p}\rvert\approx k(1+\varepsilon'). + +Energy conservation therefore sets :math:`\varepsilon'=\varepsilon`. +Momentum conservation gives :math:`\mathbf{q}=\mathbf{p}-\mathbf{k}` and, +after substitution, + +.. math:: + :label: gaussian-linearized-momentum + + \begin{aligned} + &2\sin\theta_0\Bigl[ + (q_{\mathrm{rock}}-\delta q)\, + \hat{\mathbf{q}}_{\mathrm{rock}} + + q_{\mathrm{strain}}\,\hat{\mathbf{Q}} + + q_{\mathrm{roll}}\,\hat{\mathbf{k}}_{\perp} + \Bigr] \\ + &\qquad = 2\sin\theta_0\,\varepsilon\,\hat{\mathbf{Q}} + + \psi_{\mathrm{rad}}\,\hat{\mathbf{k}}_{\mathrm{rad}} \\ + &\qquad\quad + + \psi_{\mathrm{azim}}\,\hat{\mathbf{k}}_{\perp} + - \zeta_{\parallel}\,\hat{\mathbf{k}}_{\parallel} + - \zeta_{\perp}\,\hat{\mathbf{k}}_{\perp}. + \end{aligned} + +.. note:: + + The incident-divergence terms have minus signs because the incident + wavevector is subtracted in :math:`\mathbf{q}=\mathbf{p}-\mathbf{k}`. + This corrects the corresponding signs in the original workflow. It does + not affect the monochromatic, collimated specialization below, where + both terms vanish. + +Monochromatic, collimated specialization +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The current model assumes + +.. math:: + + \varepsilon=\zeta_{\parallel}=\zeta_{\perp}=0, + \qquad q_{\mathrm{strain}}=0. + +The last condition excludes strain-distribution broadening; it does not +require the uniform subgrain strain to vanish. The conservation equation +reduces to + +.. math:: + + \begin{aligned} + &2\sin\theta_0\left[ + (q_{\mathrm{rock}}-\delta q)\, + \hat{\mathbf{q}}_{\mathrm{rock}} + + q_{\mathrm{roll}}\,\hat{\mathbf{k}}_{\perp} + \right] \\ + &\qquad + = \psi_{\mathrm{rad}}\,\hat{\mathbf{k}}_{\mathrm{rad}} + + \psi_{\mathrm{azim}}\,\hat{\mathbf{k}}_{\perp}. + \end{aligned} + +For a nondegenerate Bragg geometry, the three coordinate equations are + +.. math:: + :label: gaussian-scattering-constraints + + q_{\mathrm{rock}}=\delta q, + \qquad + \psi_{\mathrm{rad}}=0, + \qquad + \psi_{\mathrm{azim}}=2\sin\theta_0\,q_{\mathrm{roll}}. + +Thus, the scattered angular distribution samples the RSM along a line +parallel to :math:`\hat{\mathbf{k}}_{\perp}`, offset by :math:`\delta q` +from its centre. For a Gaussian RSM, this gives a one-dimensional Gaussian +angular distribution. The finite real-space extent of the illuminated +subgrain is subsequently projected onto the detector to produce a +two-dimensional spot. + +Anisotropic Gaussian orientation distributions +---------------------------------------------- + +Tangent-space approximation +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Consider a narrow orientation distribution centred on an orientation +:math:`g_0`. Approximate orientation space by its left tangent space at +:math:`g_0`, so that a small rotation vector :math:`\mathbf{r}` acts in +laboratory coordinates: + +.. math:: + :label: gaussian-tangent-rotation + + g\approx R_{\mathbf{r}}g_0 + \approx + \begin{bmatrix} + 1 & -r_z & r_y \\ + r_z & 1 & -r_x \\ + -r_y & r_x & 1 + \end{bmatrix}g_0. + +The Gaussian orientation density used in the workflow is + +.. math:: + :label: gaussian-orientation-density + + f(\mathbf{r}) + = \frac{2}{\sqrt{\pi}}\sqrt{\det\mathbf{T}}\, + \exp\!\left(-\mathbf{r}^{\mathsf{T}}\mathbf{T}\mathbf{r}\right), + +where :math:`\mathbf{T}` is a symmetric positive-definite concentration +tensor. + +.. note:: + + The prefactor above retains the workflow's normalization convention. + With ordinary Euclidean tangent-space measure, + :math:`\int f(\mathbf{r})\,\mathrm{d}^{3}\mathbf{r}=2\pi`, not one. + It should therefore not be interpreted as a unit-normalized Euclidean + probability density without rescaling. + + For an exponential of the form + :math:`\exp(-\mathbf{r}^{\mathsf{T}}\mathbf{T}\mathbf{r})`, the + corresponding normalized Gaussian has covariance + :math:`\tfrac{1}{2}\mathbf{T}^{-1}`. The implementation directly inverts + ``misorientation_tensor`` to obtain :math:`\mathbf{T}`. Consequently, + the square roots of the input tensor's eigenvalues are Gaussian width + parameters, not standard deviations. For an input eigenvalue + :math:`a^2`, the standard deviation is :math:`a/\sqrt{2}`. + The real-space Gaussian uses the same exponential convention for + ``shape_tensor``. This distinction matters because the class attribute + descriptions currently use the word "covariance" for these inputs. + +Pole density as a line integral +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +For Miller indices :math:`(h,k,\ell)`, define the unit lattice direction in +crystal coordinates by + +.. math:: + + \mathbf{h} + = \frac{ + \mathbf{B}_0[h,k,\ell]^{\mathsf{T}} + }{ + \left\lvert\mathbf{B}_0[h,k,\ell]^{\mathsf{T}}\right\rvert + }, + \qquad + \mathbf{p}=g_0\mathbf{h}. + +Here :math:`\mathbf{B}_0` is the reciprocal-lattice basis matrix. In this +section, :math:`\mathbf{p}` denotes the mean lattice direction, rather than +the outgoing wavevector used in the scattering section. It is parallel to +:math:`\mathbf{G}_0`. Let :math:`\mathbf{y}` be a unit direction in +laboratory coordinates, parallel to a sampled reciprocal-space vector +:math:`\mathbf{q}`; it is not a Cartesian coordinate-axis label. + +The pole density, also called the pair-correlation function in texture +analysis, describes the density of lattice directions along +:math:`\mathbf{y}`. Its calculation normally involves an integral over a +circle in :math:`\mathrm{SO}(3)`. Within the tangent-space approximation, +replace this by an infinite line integral, parameterized as + +.. math:: + :label: gaussian-orientation-fibre + + \mathbf{r}(\lambda) + = \frac{\mathbf{p}\times\mathbf{y}}{\mathbf{p}\cdot\mathbf{y}} + + \lambda\mathbf{p} + = \mathbf{r}_0+\lambda\mathbf{p}. + +The parameter :math:`\lambda` here is a line-integration parameter, not the +X-ray wavelength. Substitution into the Gaussian density and completion of +the square give, with +:math:`d=\mathbf{p}^{\mathsf{T}}\mathbf{T}\mathbf{p}`, + +.. math:: + :label: gaussian-pole-density-integral + + \begin{aligned} + A(\mathbf{y},\mathbf{p};f) + &= \int_{-\infty}^{\infty} + f\!\left(\mathbf{r}_0+\lambda\mathbf{p}\right) + \,\mathrm{d}\lambda \\ + &= \frac{2\sqrt{\det\mathbf{T}}} + {\sqrt{d}} + \exp\!\left[ + -\mathbf{r}_0^{\mathsf{T}}\mathbf{T}\mathbf{r}_0 + + \frac{ + (\mathbf{r}_0^{\mathsf{T}}\mathbf{T}\mathbf{p})^2 + }{d} + \right]. + \end{aligned} + +Projected concentration tensor +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +For directions close to the pole, make the further approximation +:math:`\mathbf{p}\cdot\mathbf{y}\approx1`. Define + +.. math:: + + \mathbf{M} + = \mathbf{T} + - \frac{ + \mathbf{T}\mathbf{p}\mathbf{p}^{\mathsf{T}}\mathbf{T} + }{d}. + +Let :math:`[\mathbf{p}]_{\times}` denote the cross-product matrix, +:math:`[\mathbf{p}]_{\times}\mathbf{y}=\mathbf{p}\times\mathbf{y}`. Then + +.. math:: + :label: gaussian-projected-concentration + + \mathbf{T}_{\mathbf{p}} + = [\mathbf{p}]_{\times}^{\mathsf{T}} + \mathbf{M} + [\mathbf{p}]_{\times}, + +and the pole density becomes + +.. math:: + :label: gaussian-pole-density + + A(\mathbf{y},\mathbf{p};f) + \approx \frac{2\sqrt{\det\mathbf{T}}}{\sqrt{d}}\, + \exp\!\left( + -\mathbf{y}^{\mathsf{T}}\mathbf{T}_{\mathbf{p}}\mathbf{y} + \right). + +Equivalently, using the Levi-Civita symbol :math:`\epsilon_{ijk}` and +summation over repeated indices, + +.. math:: + + [\mathbf{T}_{\mathbf{p}}]_{ij} + = p_k\,\epsilon_{lki} + \left( + T_{lm}-\frac{T_{la}p_a p_b T_{bm}}{d} + \right) + \epsilon_{mnj}\,p_n. + +This absorbs the cross products into the projected tensor and makes the +subsequent Gaussian expressions more compact. + +Reciprocal-space map +~~~~~~~~~~~~~~~~~~~~ + +The pole density is defined for unit-vector arguments. With no +strain-distribution broadening, extend it to a reciprocal-space map supported +on the local plane :math:`q_{\mathrm{strain}}=0`. + +Introduce the two-column basis matrix and coordinate vector + +.. math:: + + \mathbf{U} + = \begin{bmatrix} + \hat{\mathbf{q}}_{\mathrm{rock}} & \hat{\mathbf{k}}_{\perp} + \end{bmatrix}, + \qquad + \mathbf{u} + = \begin{bmatrix} + q_{\mathrm{rock}} \\ + q_{\mathrm{roll}} + \end{bmatrix}. + +In the dimensionless reciprocal-space coordinates defined above, the +workflow's local RSM is + +.. math:: + :label: gaussian-local-rsm + + f(\mathbf{q}) + \approx \delta(q_{\mathrm{strain}})\, + \frac{2\sqrt{\det\mathbf{T}}}{\sqrt{d}}\, + \exp\!\left[ + -\mathbf{u}^{\mathsf{T}} + \left(\mathbf{U}^{\mathsf{T}} + \mathbf{T}_{\mathbf{p}}\mathbf{U}\right) + \mathbf{u} + \right]. + +The Dirac delta expresses the absence of a distribution of lattice spacings +within the subgrain. Sampling this Gaussian at +:math:`q_{\mathrm{rock}}=\delta q`, as required by +:eq:`gaussian-scattering-constraints`, gives the scattered angular +distribution for the reflection. + +Illustrative tests +------------------ + +Comparison with a tetrahedral crystal +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The workflow compares a one-degree rotation of a single quartz crystal +shaped as a symmetric tetrahedron with a Gaussian approximation of the same +crystal. The crystal is much larger than the detector pixels, and the largest +scattering angles exceed 60 degrees, making radial spot elongation from the +projection geometry visible. + +Seven Gaussians approximate the tetrahedron: one isotropic Gaussian at its +centre and six prolate Gaussians along its edges. The Gaussian model uses a +small orientation spread. + +The comparison shows similar peak positions and shapes. The Gaussian model +also produces additional weak peaks because its rotational integration uses +a Gaussian time window, whereas the tetrahedral model uses a top-hat time +window. + +.. figure:: ../_static/single_crystal_quartz.png + :alt: Quartz diffraction patterns from tetrahedral and Gaussian models. + :align: center + :width: 100% + + Comparison of the tetrahedral crystal model with a seven-Gaussian + approximation during a one-degree sample rotation. + +High-resolution rocking curve +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +A second test resolves a rocking curve and checks the shape of the resulting +three-dimensional reciprocal-space map. The workflow figure compares the +simulated profiles with the expected contributions from grain size and +misorientation. + +.. figure:: ../_static/testing_rocking_curves.png + :alt: Reciprocal-space map and profiles from a high-resolution rocking scan. + :align: center + :width: 100% + + High-resolution rocking-curve test showing the reciprocal-space map and + the contributions of grain size and misorientation to the simulated + profiles. + +References +---------- + +.. [Kerbl2023] Kerbl, B., Kopanas, G., Leimkühler, T. and Drettakis, G. + (2023). 3D Gaussian Splatting for Real-Time Radiance Field Rendering. + *ACM Transactions on Graphics*, **42**\ (4). + `doi:10.1145/3592433 `_. + +.. [Brehm2023] Brehm, W., White, T. and Chapman, H. N. (2023). + Crystal diffraction prediction and partiality estimation using Gaussian + basis functions. *Acta Crystallographica Section A: Foundations and + Advances*, **79**, 145–162. + `doi:10.1107/S2053273323000682 + `_. + +.. [Poulsen2017] Poulsen, H. F., Jakobsen, A. C., Simons, H., Ahl, S. R., + Cook, P. K. and Detlefs, C. (2017). X-ray diffraction microscopy based on + refractive optics. *Journal of Applied Crystallography*, **50**, 1441–1456. + `doi:10.1107/S1600576717011037 + `_. diff --git a/docs/_sources/index.rst.txt b/docs/_sources/index.rst.txt index 29d8fe8..61bc367 100644 --- a/docs/_sources/index.rst.txt +++ b/docs/_sources/index.rst.txt @@ -1,7 +1,3 @@ -.. toctree:: - :maxdepth: 2 - :caption: Contents: - .. include:: ../../README.rst ====================================== @@ -50,16 +46,16 @@ motion :members: :inherited-members: -templates +gaussian_crystal_model ====================================== -.. automodule:: xrd_simulator.templates - :members: - :inherited-members: +.. automodule:: xrd_simulator.gaussian_crystal_model + :members: + :inherited-members: -laue +templates ====================================== -.. automodule:: xrd_simulator.laue +.. automodule:: xrd_simulator.templates :members: :inherited-members: diff --git a/docs/gaussian_model_theory.html b/docs/gaussian_model_theory.html new file mode 100644 index 0000000..6e2c069 --- /dev/null +++ b/docs/gaussian_model_theory.html @@ -0,0 +1,626 @@ + + + + + + + + + Gaussian model: theory and references — xrd_simulator 0.0.0 documentation + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Gaussian model: theory and references

+

This page describes the scattering model and approximations underlying +xrd_simulator.gaussian_crystal_model. A worked example is provided in +the module documentation.

+ +
+

Gaussian subgrain representation

+

Gaussian splatting represents a three-dimensional scene using Gaussian +primitives whose projected contributions can be rendered efficiently by +rasterization [Kerbl2023]. Here, a polycrystal is represented by Gaussian +grains or subgrains, each with a Gaussian real-space density profile and a +narrow Gaussian distribution of lattice orientations. Under the approximations +below, each subgrain produces Gaussian-shaped diffraction spots on the +detector. Related Gaussian-basis methods for diffraction prediction and +partiality estimation have been demonstrated in serial crystallography +[Brehm2023].

+

Several effects can contribute to diffraction-spot broadening: grain size, +detector point-spread, incident-beam bandwidth, incident-beam angular +divergence, lattice misorientation (mosaicity), and distributions of strain. +When these effects have Gaussian profiles and their deviations are small +enough to neglect nonlinear terms, they can be combined within a Gaussian +model.

+

The model described here includes the real-space extent of the illuminated +subgrains and their mosaicity. The incident beam is monochromatic and +collimated, and each subgrain has a uniform strain tensor. A uniform strain +can shift a reflection but does not describe a distribution of lattice +spacings within that subgrain. Small sample rotations during an exposure are +approximated by additional Gaussian orientation broadening.

+
+

Note

+

The Gaussian-splat rendering path does not apply a separate detector +point-spread convolution. The gaussian_sigma and +max_gaussian_kernel_radius detector settings retained in the worked +example therefore do not add this broadening to its Gaussian spots.

+
+
+
+

Scattering theory

+
+

Scattered-beam distribution

+

Let the incident beam have a phase-space density \(p(\mathbf{k})\) +centred on \(\mathbf{k}_0\), with nominal wavenumber

+
+(1)\[k = \lvert\mathbf{k}_0\rvert = \frac{2\pi}{\lambda_0}.\]
+

For a particular reflection, let \(f(\mathbf{q})\) describe the subgrain’s +reciprocal-space map (RSM) around a reciprocal-lattice vector +\(\mathbf{G}_0\). The scattered-beam distribution is written as

+
+(2)\[I(\mathbf{p}) += \int \mathrm{d}^{3}\mathbf{k} + \int \mathrm{d}^{3}\mathbf{q}\; + p(\mathbf{k})\,f(\mathbf{q})\, + \delta\!\left(\lvert\mathbf{k}\rvert-\lvert\mathbf{p}\rvert\right) + \delta^{(3)}\!\left(\mathbf{k}+\mathbf{q}-\mathbf{p}\right).\]
+

The scalar Dirac delta enforces elastic scattering, while the +three-dimensional Dirac delta enforces momentum conservation.

+
+
+

Incident-beam coordinates

+

Following the coordinate construction used by Poulsen et al. +[Poulsen2017], write the incident wavevector as

+
+(3)\[\mathbf{k} += \mathbf{k}_0 + + k\left( + \varepsilon\,\hat{\mathbf{k}}_0 + + \zeta_{\parallel}\,\hat{\mathbf{k}}_{\parallel} + + \zeta_{\perp}\,\hat{\mathbf{k}}_{\perp} + \right).\]
+

A hat denotes a unit vector. The vector +\(\hat{\mathbf{k}}_{\parallel}\) is perpendicular to +\(\mathbf{k}_0\) and lies in the plane spanned by \(\mathbf{k}_0\) +and \(\mathbf{G}_0\). Its sign is chosen so that +\(\mathbf{G}_0\cdot\hat{\mathbf{k}}_{\parallel}>0\). Complete the +right-handed basis with

+
+\[\hat{\mathbf{k}}_{\perp} += \hat{\mathbf{k}}_0\times\hat{\mathbf{k}}_{\parallel}.\]
+

The coordinate \(\varepsilon\) describes the relative wavenumber +deviation, and \(\zeta_{\parallel}\) and \(\zeta_{\perp}\) describe +small angular deviations of the incident beam.

+
+
+

Reciprocal-space coordinates

+

Define the nominal Bragg angle and the exactly aligned scattering vector by

+
+(4)\[\begin{split}\begin{aligned} +\theta_0 +&= \arcsin\!\left(\frac{\lvert\mathbf{G}_0\rvert}{2k}\right), \\ +\mathbf{Q} +&= 2k\sin\theta_0\left( + \cos\theta_0\,\hat{\mathbf{k}}_{\parallel} + - \sin\theta_0\,\hat{\mathbf{k}}_0 + \right) \\ +&= 2k\sin\theta_0\,\hat{\mathbf{Q}}. +\end{aligned}\end{split}\]
+

The vectors \(\mathbf{G}_0\) and \(\mathbf{Q}\) coincide when the +reflection is exactly aligned. For a small misalignment, their difference is +parallel, to first order, to the rocking direction

+
+\[\hat{\mathbf{q}}_{\mathrm{rock}} += \cos\theta_0\,\hat{\mathbf{k}}_0 + + \sin\theta_0\,\hat{\mathbf{k}}_{\parallel}.\]
+

Use dimensionless rocking, strain, and rolling coordinates to write

+
+(5)\[\begin{split}\begin{aligned} +\mathbf{q} +&= \mathbf{G}_0 + + 2k\sin\theta_0\left( + q_{\mathrm{rock}}\,\hat{\mathbf{q}}_{\mathrm{rock}} + + q_{\mathrm{strain}}\,\hat{\mathbf{Q}} + + q_{\mathrm{roll}}\,\hat{\mathbf{k}}_{\perp} + \right) \\ +&\approx \mathbf{Q} + + 2k\sin\theta_0\left( + (q_{\mathrm{rock}}-\delta q)\, + \hat{\mathbf{q}}_{\mathrm{rock}} + + q_{\mathrm{strain}}\,\hat{\mathbf{Q}} + + q_{\mathrm{roll}}\,\hat{\mathbf{k}}_{\perp} + \right), +\end{aligned}\end{split}\]
+

where

+
+(6)\[\delta q += \frac{ + (\mathbf{Q}-\mathbf{G}_0)\cdot\hat{\mathbf{q}}_{\mathrm{rock}} + }{2k\sin\theta_0}.\]
+

This offset measures the reflection’s misalignment and is equal to the +rocking angle to first order, with the sign convention above. The incident +coordinates \((\varepsilon,\zeta_{\parallel},\zeta_{\perp})\) and the +reciprocal-space coordinates +\((q_{\mathrm{rock}},q_{\mathrm{strain}},q_{\mathrm{roll}})\) separate +bandwidth, collimation, misorientation, and strain effects.

+
+
+

Outgoing-beam coordinates

+

The nominal scattered wavevector is

+
+(7)\[\mathbf{k}_h += \mathbf{k}_0+\mathbf{Q} += k\left( + \cos(2\theta_0)\,\hat{\mathbf{k}}_0 + + \sin(2\theta_0)\,\hat{\mathbf{k}}_{\parallel} + \right).\]
+

Define the radial direction perpendicular to \(\mathbf{k}_h\) by

+
+\[\hat{\mathbf{k}}_{\mathrm{rad}} += \cos(2\theta_0)\,\hat{\mathbf{k}}_{\parallel} + - \sin(2\theta_0)\,\hat{\mathbf{k}}_0.\]
+

The outgoing wavevector can then be written as

+
+(8)\[\mathbf{p} += \mathbf{k}_h + + k\left( + \varepsilon'\,\hat{\mathbf{k}}_h + + \psi_{\mathrm{rad}}\,\hat{\mathbf{k}}_{\mathrm{rad}} + + \psi_{\mathrm{azim}}\,\hat{\mathbf{k}}_{\perp} + \right).\]
+

Here \(\psi_{\mathrm{rad}}\) and \(\psi_{\mathrm{azim}}\) are small +transverse angular deviations from the nominal outgoing direction.

+
+
+

Linearized conservation equations

+

To first order,

+
+\[\lvert\mathbf{k}\rvert\approx k(1+\varepsilon), +\qquad +\lvert\mathbf{p}\rvert\approx k(1+\varepsilon').\]
+

Energy conservation therefore sets \(\varepsilon'=\varepsilon\). +Momentum conservation gives \(\mathbf{q}=\mathbf{p}-\mathbf{k}\) and, +after substitution,

+
+(9)\[\begin{split}\begin{aligned} +&2\sin\theta_0\Bigl[ + (q_{\mathrm{rock}}-\delta q)\, + \hat{\mathbf{q}}_{\mathrm{rock}} + + q_{\mathrm{strain}}\,\hat{\mathbf{Q}} + + q_{\mathrm{roll}}\,\hat{\mathbf{k}}_{\perp} +\Bigr] \\ +&\qquad = 2\sin\theta_0\,\varepsilon\,\hat{\mathbf{Q}} + + \psi_{\mathrm{rad}}\,\hat{\mathbf{k}}_{\mathrm{rad}} \\ +&\qquad\quad + + \psi_{\mathrm{azim}}\,\hat{\mathbf{k}}_{\perp} + - \zeta_{\parallel}\,\hat{\mathbf{k}}_{\parallel} + - \zeta_{\perp}\,\hat{\mathbf{k}}_{\perp}. +\end{aligned}\end{split}\]
+
+

Note

+

The incident-divergence terms have minus signs because the incident +wavevector is subtracted in \(\mathbf{q}=\mathbf{p}-\mathbf{k}\). +This corrects the corresponding signs in the original workflow. It does +not affect the monochromatic, collimated specialization below, where +both terms vanish.

+
+
+
+

Monochromatic, collimated specialization

+

The current model assumes

+
+\[\varepsilon=\zeta_{\parallel}=\zeta_{\perp}=0, +\qquad q_{\mathrm{strain}}=0.\]
+

The last condition excludes strain-distribution broadening; it does not +require the uniform subgrain strain to vanish. The conservation equation +reduces to

+
+\[\begin{split}\begin{aligned} +&2\sin\theta_0\left[ + (q_{\mathrm{rock}}-\delta q)\, + \hat{\mathbf{q}}_{\mathrm{rock}} + + q_{\mathrm{roll}}\,\hat{\mathbf{k}}_{\perp} +\right] \\ +&\qquad += \psi_{\mathrm{rad}}\,\hat{\mathbf{k}}_{\mathrm{rad}} + + \psi_{\mathrm{azim}}\,\hat{\mathbf{k}}_{\perp}. +\end{aligned}\end{split}\]
+

For a nondegenerate Bragg geometry, the three coordinate equations are

+
+(10)\[q_{\mathrm{rock}}=\delta q, +\qquad +\psi_{\mathrm{rad}}=0, +\qquad +\psi_{\mathrm{azim}}=2\sin\theta_0\,q_{\mathrm{roll}}.\]
+

Thus, the scattered angular distribution samples the RSM along a line +parallel to \(\hat{\mathbf{k}}_{\perp}\), offset by \(\delta q\) +from its centre. For a Gaussian RSM, this gives a one-dimensional Gaussian +angular distribution. The finite real-space extent of the illuminated +subgrain is subsequently projected onto the detector to produce a +two-dimensional spot.

+
+
+
+

Anisotropic Gaussian orientation distributions

+
+

Tangent-space approximation

+

Consider a narrow orientation distribution centred on an orientation +\(g_0\). Approximate orientation space by its left tangent space at +\(g_0\), so that a small rotation vector \(\mathbf{r}\) acts in +laboratory coordinates:

+
+(11)\[\begin{split}g\approx R_{\mathbf{r}}g_0 +\approx +\begin{bmatrix} + 1 & -r_z & r_y \\ + r_z & 1 & -r_x \\ + -r_y & r_x & 1 +\end{bmatrix}g_0.\end{split}\]
+

The Gaussian orientation density used in the workflow is

+
+(12)\[f(\mathbf{r}) += \frac{2}{\sqrt{\pi}}\sqrt{\det\mathbf{T}}\, + \exp\!\left(-\mathbf{r}^{\mathsf{T}}\mathbf{T}\mathbf{r}\right),\]
+

where \(\mathbf{T}\) is a symmetric positive-definite concentration +tensor.

+
+

Note

+

The prefactor above retains the workflow’s normalization convention. +With ordinary Euclidean tangent-space measure, +\(\int f(\mathbf{r})\,\mathrm{d}^{3}\mathbf{r}=2\pi\), not one. +It should therefore not be interpreted as a unit-normalized Euclidean +probability density without rescaling.

+

For an exponential of the form +\(\exp(-\mathbf{r}^{\mathsf{T}}\mathbf{T}\mathbf{r})\), the +corresponding normalized Gaussian has covariance +\(\tfrac{1}{2}\mathbf{T}^{-1}\). The implementation directly inverts +misorientation_tensor to obtain \(\mathbf{T}\). Consequently, +the square roots of the input tensor’s eigenvalues are Gaussian width +parameters, not standard deviations. For an input eigenvalue +\(a^2\), the standard deviation is \(a/\sqrt{2}\). +The real-space Gaussian uses the same exponential convention for +shape_tensor. This distinction matters because the class attribute +descriptions currently use the word “covariance” for these inputs.

+
+
+
+

Pole density as a line integral

+

For Miller indices \((h,k,\ell)\), define the unit lattice direction in +crystal coordinates by

+
+\[\mathbf{h} += \frac{ + \mathbf{B}_0[h,k,\ell]^{\mathsf{T}} + }{ + \left\lvert\mathbf{B}_0[h,k,\ell]^{\mathsf{T}}\right\rvert + }, +\qquad +\mathbf{p}=g_0\mathbf{h}.\]
+

Here \(\mathbf{B}_0\) is the reciprocal-lattice basis matrix. In this +section, \(\mathbf{p}\) denotes the mean lattice direction, rather than +the outgoing wavevector used in the scattering section. It is parallel to +\(\mathbf{G}_0\). Let \(\mathbf{y}\) be a unit direction in +laboratory coordinates, parallel to a sampled reciprocal-space vector +\(\mathbf{q}\); it is not a Cartesian coordinate-axis label.

+

The pole density, also called the pair-correlation function in texture +analysis, describes the density of lattice directions along +\(\mathbf{y}\). Its calculation normally involves an integral over a +circle in \(\mathrm{SO}(3)\). Within the tangent-space approximation, +replace this by an infinite line integral, parameterized as

+
+(13)\[\mathbf{r}(\lambda) += \frac{\mathbf{p}\times\mathbf{y}}{\mathbf{p}\cdot\mathbf{y}} + + \lambda\mathbf{p} += \mathbf{r}_0+\lambda\mathbf{p}.\]
+

The parameter \(\lambda\) here is a line-integration parameter, not the +X-ray wavelength. Substitution into the Gaussian density and completion of +the square give, with +\(d=\mathbf{p}^{\mathsf{T}}\mathbf{T}\mathbf{p}\),

+
+(14)\[\begin{split}\begin{aligned} +A(\mathbf{y},\mathbf{p};f) +&= \int_{-\infty}^{\infty} + f\!\left(\mathbf{r}_0+\lambda\mathbf{p}\right) + \,\mathrm{d}\lambda \\ +&= \frac{2\sqrt{\det\mathbf{T}}} + {\sqrt{d}} + \exp\!\left[ + -\mathbf{r}_0^{\mathsf{T}}\mathbf{T}\mathbf{r}_0 + + \frac{ + (\mathbf{r}_0^{\mathsf{T}}\mathbf{T}\mathbf{p})^2 + }{d} + \right]. +\end{aligned}\end{split}\]
+
+
+

Projected concentration tensor

+

For directions close to the pole, make the further approximation +\(\mathbf{p}\cdot\mathbf{y}\approx1\). Define

+
+\[\mathbf{M} += \mathbf{T} + - \frac{ + \mathbf{T}\mathbf{p}\mathbf{p}^{\mathsf{T}}\mathbf{T} + }{d}.\]
+

Let \([\mathbf{p}]_{\times}\) denote the cross-product matrix, +\([\mathbf{p}]_{\times}\mathbf{y}=\mathbf{p}\times\mathbf{y}\). Then

+
+(15)\[\mathbf{T}_{\mathbf{p}} += [\mathbf{p}]_{\times}^{\mathsf{T}} + \mathbf{M} + [\mathbf{p}]_{\times},\]
+

and the pole density becomes

+
+(16)\[A(\mathbf{y},\mathbf{p};f) +\approx \frac{2\sqrt{\det\mathbf{T}}}{\sqrt{d}}\, + \exp\!\left( + -\mathbf{y}^{\mathsf{T}}\mathbf{T}_{\mathbf{p}}\mathbf{y} + \right).\]
+

Equivalently, using the Levi-Civita symbol \(\epsilon_{ijk}\) and +summation over repeated indices,

+
+\[[\mathbf{T}_{\mathbf{p}}]_{ij} += p_k\,\epsilon_{lki} + \left( + T_{lm}-\frac{T_{la}p_a p_b T_{bm}}{d} + \right) + \epsilon_{mnj}\,p_n.\]
+

This absorbs the cross products into the projected tensor and makes the +subsequent Gaussian expressions more compact.

+
+
+

Reciprocal-space map

+

The pole density is defined for unit-vector arguments. With no +strain-distribution broadening, extend it to a reciprocal-space map supported +on the local plane \(q_{\mathrm{strain}}=0\).

+

Introduce the two-column basis matrix and coordinate vector

+
+\[\begin{split}\mathbf{U} += \begin{bmatrix} + \hat{\mathbf{q}}_{\mathrm{rock}} & \hat{\mathbf{k}}_{\perp} + \end{bmatrix}, +\qquad +\mathbf{u} += \begin{bmatrix} + q_{\mathrm{rock}} \\ + q_{\mathrm{roll}} + \end{bmatrix}.\end{split}\]
+

In the dimensionless reciprocal-space coordinates defined above, the +workflow’s local RSM is

+
+(17)\[f(\mathbf{q}) +\approx \delta(q_{\mathrm{strain}})\, + \frac{2\sqrt{\det\mathbf{T}}}{\sqrt{d}}\, + \exp\!\left[ + -\mathbf{u}^{\mathsf{T}} + \left(\mathbf{U}^{\mathsf{T}} + \mathbf{T}_{\mathbf{p}}\mathbf{U}\right) + \mathbf{u} + \right].\]
+

The Dirac delta expresses the absence of a distribution of lattice spacings +within the subgrain. Sampling this Gaussian at +\(q_{\mathrm{rock}}=\delta q\), as required by +(10), gives the scattered angular +distribution for the reflection.

+
+
+
+

Illustrative tests

+
+

Comparison with a tetrahedral crystal

+

The workflow compares a one-degree rotation of a single quartz crystal +shaped as a symmetric tetrahedron with a Gaussian approximation of the same +crystal. The crystal is much larger than the detector pixels, and the largest +scattering angles exceed 60 degrees, making radial spot elongation from the +projection geometry visible.

+

Seven Gaussians approximate the tetrahedron: one isotropic Gaussian at its +centre and six prolate Gaussians along its edges. The Gaussian model uses a +small orientation spread.

+

The comparison shows similar peak positions and shapes. The Gaussian model +also produces additional weak peaks because its rotational integration uses +a Gaussian time window, whereas the tetrahedral model uses a top-hat time +window.

+
+Quartz diffraction patterns from tetrahedral and Gaussian models. + +
+

Comparison of the tetrahedral crystal model with a seven-Gaussian +approximation during a one-degree sample rotation.

+
+
+
+
+

High-resolution rocking curve

+

A second test resolves a rocking curve and checks the shape of the resulting +three-dimensional reciprocal-space map. The workflow figure compares the +simulated profiles with the expected contributions from grain size and +misorientation.

+
+Reciprocal-space map and profiles from a high-resolution rocking scan. + +
+

High-resolution rocking-curve test showing the reciprocal-space map and +the contributions of grain size and misorientation to the simulated +profiles.

+
+
+
+
+
+

References

+
+
+[Kerbl2023] +

Kerbl, B., Kopanas, G., Leimkühler, T. and Drettakis, G. +(2023). 3D Gaussian Splatting for Real-Time Radiance Field Rendering. +ACM Transactions on Graphics, 42(4). +doi:10.1145/3592433.

+
+
+[Brehm2023] +

Brehm, W., White, T. and Chapman, H. N. (2023). +Crystal diffraction prediction and partiality estimation using Gaussian +basis functions. Acta Crystallographica Section A: Foundations and +Advances, 79, 145–162. +doi:10.1107/S2053273323000682.

+
+
+[Poulsen2017] +

Poulsen, H. F., Jakobsen, A. C., Simons, H., Ahl, S. R., +Cook, P. K. and Detlefs, C. (2017). X-ray diffraction microscopy based on +refractive optics. Journal of Applied Crystallography, 50, 1441–1456. +doi:10.1107/S1600576717011037.

+
+
+
+
+ + +
+
+
+ +
+ +
+

© Copyright 2021, Axel Henningsson.

+
+ + Built with Sphinx using a + theme + provided by Read the Docs. + + +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/genindex.html b/docs/genindex.html index 38ad151..8420211 100644 --- a/docs/genindex.html +++ b/docs/genindex.html @@ -145,8 +145,12 @@

D

@@ -190,9 +194,15 @@

G

  • gaussian_kernel (xrd_simulator.detector.Detector attribute)
  • gaussian_sigma (xrd_simulator.detector.Detector attribute) +
  • +
  • GaussianBeam (class in xrd_simulator.beam) +
  • +
  • GaussianPolycrystal (class in xrd_simulator.gaussian_crystal_model)
    • +
    • GaussianSubgrain (class in xrd_simulator.gaussian_crystal_model) +
    • generate_mesh_from_levelset() (xrd_simulator.mesh.TetraMesh class method)
    • generate_mesh_from_vertices() (xrd_simulator.mesh.TetraMesh class method) @@ -256,6 +266,8 @@

      M

    • mesh_sample (xrd_simulator.polycrystal.Polycrystal attribute)
    • miller_indices (xrd_simulator.phase.Phase attribute) +
    • +
    • misorientation_tensor (xrd_simulator.gaussian_crystal_model.GaussianSubgrain attribute)
    • module @@ -265,7 +277,7 @@

      M

    • xrd_simulator.detector
    • -
    • xrd_simulator.laue +
    • xrd_simulator.gaussian_crystal_model
    • xrd_simulator.mesh
    • @@ -296,6 +308,8 @@

      N

      O

      @@ -315,6 +329,8 @@

      P

    • peaks_detector_intersection() (xrd_simulator.detector.Detector method)
    • Phase (class in xrd_simulator.phase) +
    • +
    • phase (xrd_simulator.gaussian_crystal_model.GaussianSubgrain attribute)
    • phases (xrd_simulator.polycrystal.Polycrystal attribute)
    • @@ -333,6 +349,8 @@

      P

    • Polycrystal (class in xrd_simulator.polycrystal)
    • polycrystal_from_odf() (in module xrd_simulator.templates) +
    • +
    • position (xrd_simulator.gaussian_crystal_model.GaussianSubgrain attribute)
    • @@ -341,17 +359,19 @@

      R

      + - - + diff --git a/docs/searchindex.js b/docs/searchindex.js index 77a7d7e..c49dbbd 100644 --- a/docs/searchindex.js +++ b/docs/searchindex.js @@ -1 +1 @@ -Search.setIndex({"alltitles": {"Anaconda installation (Linux and Macos)": [[0, "anaconda-installation-linux-and-macos"]], "Anaconda installation (Windows)": [[0, "anaconda-installation-windows"]], "Citation": [[0, "citation"]], "Credits": [[0, "credits"]], "Documentation": [[0, "documentation"]], "Installation": [[0, "installation"]], "Introduction": [[0, "introduction"]], "Pip Installation": [[0, "pip-installation"]], "Simulate X-ray Diffraction from Polycrystals in 3D.": [[0, null]], "Source installation": [[0, "source-installation"]], "beam": [[0, "module-xrd_simulator.beam"]], "detector": [[0, "module-xrd_simulator.detector"]], "laue": [[0, "module-xrd_simulator.laue"]], "mesh": [[0, "module-xrd_simulator.mesh"]], "motion": [[0, "module-xrd_simulator.motion"]], "phase": [[0, "module-xrd_simulator.phase"]], "polycrystal": [[0, "module-xrd_simulator.polycrystal"]], "templates": [[0, "module-xrd_simulator.templates"]]}, "docnames": ["index"], "envversion": {"sphinx": 64, "sphinx.domains.c": 3, "sphinx.domains.changeset": 1, "sphinx.domains.citation": 1, "sphinx.domains.cpp": 9, "sphinx.domains.index": 1, "sphinx.domains.javascript": 3, "sphinx.domains.math": 2, "sphinx.domains.python": 4, "sphinx.domains.rst": 2, "sphinx.domains.std": 2}, "filenames": ["index.rst"], "indexentries": {"__call__() (xrd_simulator.motion.rigidbodymotion method)": [[0, "xrd_simulator.motion.RigidBodyMotion.__call__", false]], "beam (class in xrd_simulator.beam)": [[0, "xrd_simulator.beam.Beam", false]], "centroid (xrd_simulator.beam.beam attribute)": [[0, "xrd_simulator.beam.Beam.centroid", false]], "centroid (xrd_simulator.mesh.tetramesh attribute)": [[0, "xrd_simulator.mesh.TetraMesh.centroid", false]], "contains() (xrd_simulator.detector.detector method)": [[0, "xrd_simulator.detector.Detector.contains", false]], "coord (xrd_simulator.mesh.tetramesh attribute)": [[0, "xrd_simulator.mesh.TetraMesh.coord", false]], "det_corner_0 (xrd_simulator.detector.detector attribute)": [[0, "xrd_simulator.detector.Detector.det_corner_0", false]], "det_corner_1 (xrd_simulator.detector.detector attribute)": [[0, "xrd_simulator.detector.Detector.det_corner_1", false]], "det_corner_2 (xrd_simulator.detector.detector attribute)": [[0, "xrd_simulator.detector.Detector.det_corner_2", false]], "detector (class in xrd_simulator.detector)": [[0, "xrd_simulator.detector.Detector", false]], "diffract() (xrd_simulator.polycrystal.polycrystal method)": [[0, "xrd_simulator.polycrystal.Polycrystal.diffract", false]], "dof (xrd_simulator.mesh.tetramesh attribute)": [[0, "xrd_simulator.mesh.TetraMesh.dof", false]], "ecentroids (xrd_simulator.mesh.tetramesh attribute)": [[0, "xrd_simulator.mesh.TetraMesh.ecentroids", false]], "efaces (xrd_simulator.mesh.tetramesh attribute)": [[0, "xrd_simulator.mesh.TetraMesh.efaces", false]], "element_phase_map (xrd_simulator.polycrystal.polycrystal attribute)": [[0, "xrd_simulator.polycrystal.Polycrystal.element_phase_map", false]], "enod (xrd_simulator.mesh.tetramesh attribute)": [[0, "xrd_simulator.mesh.TetraMesh.enod", false]], "enormals (xrd_simulator.mesh.tetramesh attribute)": [[0, "xrd_simulator.mesh.TetraMesh.enormals", false]], "eradius (xrd_simulator.mesh.tetramesh attribute)": [[0, "xrd_simulator.mesh.TetraMesh.eradius", false]], "espherecentroids (xrd_simulator.mesh.tetramesh attribute)": [[0, "xrd_simulator.mesh.TetraMesh.espherecentroids", false]], "evolumes (xrd_simulator.mesh.tetramesh attribute)": [[0, "xrd_simulator.mesh.TetraMesh.evolumes", false]], "frames (xrd_simulator.detector.detector attribute)": [[0, "xrd_simulator.detector.Detector.frames", false]], "gaussian_kernel (xrd_simulator.detector.detector attribute)": [[0, "xrd_simulator.detector.Detector.gaussian_kernel", false]], "gaussian_sigma (xrd_simulator.detector.detector attribute)": [[0, "xrd_simulator.detector.Detector.gaussian_sigma", false]], "generate_mesh_from_levelset() (xrd_simulator.mesh.tetramesh class method)": [[0, "xrd_simulator.mesh.TetraMesh.generate_mesh_from_levelset", false]], "generate_mesh_from_vertices() (xrd_simulator.mesh.tetramesh class method)": [[0, "xrd_simulator.mesh.TetraMesh.generate_mesh_from_vertices", false]], "get_uniform_powder_sample() (in module xrd_simulator.templates)": [[0, "xrd_simulator.templates.get_uniform_powder_sample", false]], "halfspaces (xrd_simulator.beam.beam attribute)": [[0, "xrd_simulator.beam.Beam.halfspaces", false]], "inverse() (xrd_simulator.motion.rigidbodymotion method)": [[0, "xrd_simulator.motion.RigidBodyMotion.inverse", false]], "kernel_threshold (xrd_simulator.detector.detector attribute)": [[0, "xrd_simulator.detector.Detector.kernel_threshold", false]], "load() (xrd_simulator.beam.beam class method)": [[0, "xrd_simulator.beam.Beam.load", false]], "load() (xrd_simulator.detector.detector class method)": [[0, "xrd_simulator.detector.Detector.load", false]], "load() (xrd_simulator.mesh.tetramesh class method)": [[0, "xrd_simulator.mesh.TetraMesh.load", false]], "load() (xrd_simulator.motion.rigidbodymotion class method)": [[0, "xrd_simulator.motion.RigidBodyMotion.load", false]], "load() (xrd_simulator.polycrystal.polycrystal class method)": [[0, "xrd_simulator.polycrystal.Polycrystal.load", false]], "lorentz_factor (xrd_simulator.detector.detector attribute)": [[0, "xrd_simulator.detector.Detector.lorentz_factor", false]], "mesh_lab (xrd_simulator.polycrystal.polycrystal attribute)": [[0, "xrd_simulator.polycrystal.Polycrystal.mesh_lab", false]], "mesh_sample (xrd_simulator.polycrystal.polycrystal attribute)": [[0, "xrd_simulator.polycrystal.Polycrystal.mesh_sample", false]], "miller_indices (xrd_simulator.phase.phase attribute)": [[0, "xrd_simulator.phase.Phase.miller_indices", false]], "module": [[0, "module-xrd_simulator.beam", false], [0, "module-xrd_simulator.detector", false], [0, "module-xrd_simulator.laue", false], [0, "module-xrd_simulator.mesh", false], [0, "module-xrd_simulator.motion", false], [0, "module-xrd_simulator.phase", false], [0, "module-xrd_simulator.polycrystal", false], [0, "module-xrd_simulator.templates", false]], "normal (xrd_simulator.detector.detector attribute)": [[0, "xrd_simulator.detector.Detector.normal", false]], "number_of_elements (xrd_simulator.mesh.tetramesh attribute)": [[0, "xrd_simulator.mesh.TetraMesh.number_of_elements", false]], "orientation_lab (xrd_simulator.polycrystal.polycrystal attribute)": [[0, "xrd_simulator.polycrystal.Polycrystal.orientation_lab", false]], "orientation_sample (xrd_simulator.polycrystal.polycrystal attribute)": [[0, "xrd_simulator.polycrystal.Polycrystal.orientation_sample", false]], "origin (xrd_simulator.motion.rigidbodymotion attribute)": [[0, "xrd_simulator.motion.RigidBodyMotion.origin", false]], "path_to_cif_file (xrd_simulator.phase.phase attribute)": [[0, "xrd_simulator.phase.Phase.path_to_cif_file", false]], "peaks_detector_intersection() (xrd_simulator.detector.detector method)": [[0, "xrd_simulator.detector.Detector.peaks_detector_intersection", false]], "phase (class in xrd_simulator.phase)": [[0, "xrd_simulator.phase.Phase", false]], "phases (xrd_simulator.polycrystal.polycrystal attribute)": [[0, "xrd_simulator.polycrystal.Polycrystal.phases", false]], "pixel_coordinates (xrd_simulator.detector.detector attribute)": [[0, "xrd_simulator.detector.Detector.pixel_coordinates", false]], "pixel_size_y (xrd_simulator.detector.detector attribute)": [[0, "xrd_simulator.detector.Detector.pixel_size_y", false]], "pixel_size_z (xrd_simulator.detector.detector attribute)": [[0, "xrd_simulator.detector.Detector.pixel_size_z", false]], "polarization_factor (xrd_simulator.detector.detector attribute)": [[0, "xrd_simulator.detector.Detector.polarization_factor", false]], "polarization_vector (xrd_simulator.beam.beam attribute)": [[0, "xrd_simulator.beam.Beam.polarization_vector", false]], "polycrystal (class in xrd_simulator.polycrystal)": [[0, "xrd_simulator.polycrystal.Polycrystal", false]], "polycrystal_from_odf() (in module xrd_simulator.templates)": [[0, "xrd_simulator.templates.polycrystal_from_odf", false]], "render() (xrd_simulator.detector.detector method)": [[0, "xrd_simulator.detector.Detector.render", false]], "rigidbodymotion (class in xrd_simulator.motion)": [[0, "xrd_simulator.motion.RigidBodyMotion", false]], "rotate() (xrd_simulator.mesh.tetramesh method)": [[0, "xrd_simulator.mesh.TetraMesh.rotate", false]], "rotate() (xrd_simulator.motion.rigidbodymotion method)": [[0, "xrd_simulator.motion.RigidBodyMotion.rotate", false]], "rotation_angle (xrd_simulator.motion.rigidbodymotion attribute)": [[0, "xrd_simulator.motion.RigidBodyMotion.rotation_angle", false]], "rotation_axis (xrd_simulator.motion.rigidbodymotion attribute)": [[0, "xrd_simulator.motion.RigidBodyMotion.rotation_axis", false]], "s3dxrd() (in module xrd_simulator.templates)": [[0, "xrd_simulator.templates.s3dxrd", false]], "save() (xrd_simulator.beam.beam method)": [[0, "xrd_simulator.beam.Beam.save", false]], "save() (xrd_simulator.detector.detector method)": [[0, "xrd_simulator.detector.Detector.save", false]], "save() (xrd_simulator.mesh.tetramesh method)": [[0, "xrd_simulator.mesh.TetraMesh.save", false]], "save() (xrd_simulator.motion.rigidbodymotion method)": [[0, "xrd_simulator.motion.RigidBodyMotion.save", false]], "save() (xrd_simulator.polycrystal.polycrystal method)": [[0, "xrd_simulator.polycrystal.Polycrystal.save", false]], "set_beam_vertices() (xrd_simulator.beam.beam method)": [[0, "xrd_simulator.beam.Beam.set_beam_vertices", false]], "sgname (xrd_simulator.phase.phase attribute)": [[0, "xrd_simulator.phase.Phase.sgname", false]], "strain_lab (xrd_simulator.polycrystal.polycrystal attribute)": [[0, "xrd_simulator.polycrystal.Polycrystal.strain_lab", false]], "strain_sample (xrd_simulator.polycrystal.polycrystal attribute)": [[0, "xrd_simulator.polycrystal.Polycrystal.strain_sample", false]], "structure_factor (xrd_simulator.detector.detector attribute)": [[0, "xrd_simulator.detector.Detector.structure_factor", false]], "structure_factors (xrd_simulator.phase.phase attribute)": [[0, "xrd_simulator.phase.Phase.structure_factors", false]], "tetramesh (class in xrd_simulator.mesh)": [[0, "xrd_simulator.mesh.TetraMesh", false]], "transform() (xrd_simulator.polycrystal.polycrystal method)": [[0, "xrd_simulator.polycrystal.Polycrystal.transform", false]], "translate() (xrd_simulator.mesh.tetramesh method)": [[0, "xrd_simulator.mesh.TetraMesh.translate", false]], "translate() (xrd_simulator.motion.rigidbodymotion method)": [[0, "xrd_simulator.motion.RigidBodyMotion.translate", false]], "translation (xrd_simulator.motion.rigidbodymotion attribute)": [[0, "xrd_simulator.motion.RigidBodyMotion.translation", false]], "unit_cell (xrd_simulator.phase.phase attribute)": [[0, "xrd_simulator.phase.Phase.unit_cell", false]], "update() (xrd_simulator.mesh.tetramesh method)": [[0, "xrd_simulator.mesh.TetraMesh.update", false]], "vertices (xrd_simulator.beam.beam attribute)": [[0, "xrd_simulator.beam.Beam.vertices", false]], "wave_vector (xrd_simulator.beam.beam attribute)": [[0, "xrd_simulator.beam.Beam.wave_vector", false]], "wavelength (xrd_simulator.beam.beam attribute)": [[0, "xrd_simulator.beam.Beam.wavelength", false]], "xrd_simulator.beam": [[0, "module-xrd_simulator.beam", false]], "xrd_simulator.detector": [[0, "module-xrd_simulator.detector", false]], "xrd_simulator.laue": [[0, "module-xrd_simulator.laue", false]], "xrd_simulator.mesh": [[0, "module-xrd_simulator.mesh", false]], "xrd_simulator.motion": [[0, "module-xrd_simulator.motion", false]], "xrd_simulator.phase": [[0, "module-xrd_simulator.phase", false]], "xrd_simulator.polycrystal": [[0, "module-xrd_simulator.polycrystal", false]], "xrd_simulator.templates": [[0, "module-xrd_simulator.templates", false]], "ydhat (xrd_simulator.detector.detector attribute)": [[0, "xrd_simulator.detector.Detector.ydhat", false]], "ymax (xrd_simulator.detector.detector attribute)": [[0, "xrd_simulator.detector.Detector.ymax", false]], "zdhat (xrd_simulator.detector.detector attribute)": [[0, "xrd_simulator.detector.Detector.zdhat", false]], "zmax (xrd_simulator.detector.detector attribute)": [[0, "xrd_simulator.detector.Detector.zmax", false]]}, "objects": {"xrd_simulator": [[0, 0, 0, "-", "beam"], [0, 0, 0, "-", "detector"], [0, 0, 0, "-", "laue"], [0, 0, 0, "-", "mesh"], [0, 0, 0, "-", "motion"], [0, 0, 0, "-", "phase"], [0, 0, 0, "-", "polycrystal"], [0, 0, 0, "-", "templates"]], "xrd_simulator.beam": [[0, 1, 1, "", "Beam"]], "xrd_simulator.beam.Beam": [[0, 2, 1, "", "centroid"], [0, 2, 1, "", "halfspaces"], [0, 3, 1, "", "load"], [0, 2, 1, "", "polarization_vector"], [0, 3, 1, "", "save"], [0, 3, 1, "", "set_beam_vertices"], [0, 2, 1, "", "vertices"], [0, 2, 1, "", "wave_vector"], [0, 2, 1, "", "wavelength"]], "xrd_simulator.detector": [[0, 1, 1, "", "Detector"]], "xrd_simulator.detector.Detector": [[0, 3, 1, "", "contains"], [0, 2, 1, "", "det_corner_0"], [0, 2, 1, "", "det_corner_1"], [0, 2, 1, "", "det_corner_2"], [0, 2, 1, "", "frames"], [0, 2, 1, "", "gaussian_kernel"], [0, 2, 1, "", "gaussian_sigma"], [0, 2, 1, "", "kernel_threshold"], [0, 3, 1, "", "load"], [0, 2, 1, "", "lorentz_factor"], [0, 2, 1, "", "normal"], [0, 3, 1, "", "peaks_detector_intersection"], [0, 2, 1, "", "pixel_coordinates"], [0, 2, 1, "", "pixel_size_y"], [0, 2, 1, "", "pixel_size_z"], [0, 2, 1, "", "polarization_factor"], [0, 3, 1, "", "render"], [0, 3, 1, "", "save"], [0, 2, 1, "", "structure_factor"], [0, 2, 1, "", "ydhat"], [0, 2, 1, "", "ymax"], [0, 2, 1, "", "zdhat"], [0, 2, 1, "", "zmax"]], "xrd_simulator.mesh": [[0, 1, 1, "", "TetraMesh"]], "xrd_simulator.mesh.TetraMesh": [[0, 2, 1, "", "centroid"], [0, 2, 1, "", "coord"], [0, 2, 1, "", "dof"], [0, 2, 1, "", "ecentroids"], [0, 2, 1, "", "efaces"], [0, 2, 1, "", "enod"], [0, 2, 1, "", "enormals"], [0, 2, 1, "", "eradius"], [0, 2, 1, "", "espherecentroids"], [0, 2, 1, "", "evolumes"], [0, 3, 1, "", "generate_mesh_from_levelset"], [0, 3, 1, "", "generate_mesh_from_vertices"], [0, 3, 1, "", "load"], [0, 2, 1, "", "number_of_elements"], [0, 3, 1, "", "rotate"], [0, 3, 1, "", "save"], [0, 3, 1, "", "translate"], [0, 3, 1, "", "update"]], "xrd_simulator.motion": [[0, 1, 1, "", "RigidBodyMotion"]], "xrd_simulator.motion.RigidBodyMotion": [[0, 3, 1, "", "__call__"], [0, 3, 1, "", "inverse"], [0, 3, 1, "", "load"], [0, 2, 1, "", "origin"], [0, 3, 1, "", "rotate"], [0, 2, 1, "", "rotation_angle"], [0, 2, 1, "", "rotation_axis"], [0, 3, 1, "", "save"], [0, 3, 1, "", "translate"], [0, 2, 1, "", "translation"]], "xrd_simulator.phase": [[0, 1, 1, "", "Phase"]], "xrd_simulator.phase.Phase": [[0, 2, 1, "", "miller_indices"], [0, 2, 1, "", "path_to_cif_file"], [0, 2, 1, "", "sgname"], [0, 2, 1, "", "structure_factors"], [0, 2, 1, "", "unit_cell"]], "xrd_simulator.polycrystal": [[0, 1, 1, "", "Polycrystal"]], "xrd_simulator.polycrystal.Polycrystal": [[0, 3, 1, "", "diffract"], [0, 2, 1, "", "element_phase_map"], [0, 3, 1, "", "load"], [0, 2, 1, "", "mesh_lab"], [0, 2, 1, "", "mesh_sample"], [0, 2, 1, "", "orientation_lab"], [0, 2, 1, "", "orientation_sample"], [0, 2, 1, "", "phases"], [0, 3, 1, "", "save"], [0, 2, 1, "", "strain_lab"], [0, 2, 1, "", "strain_sample"], [0, 3, 1, "", "transform"]], "xrd_simulator.templates": [[0, 4, 1, "", "get_uniform_powder_sample"], [0, 4, 1, "", "polycrystal_from_odf"], [0, 4, 1, "", "s3dxrd"]]}, "objnames": {"0": ["py", "module", "Python module"], "1": ["py", "class", "Python class"], "2": ["py", "attribute", "Python attribute"], "3": ["py", "method", "Python method"], "4": ["py", "function", "Python function"]}, "objtypes": {"0": "py:module", "1": "py:class", "2": "py:attribute", "3": "py:method", "4": "py:function"}, "terms": {"": 0, "0": 0, "001": 0, "0010000000000000002": 0, "02": 0, "0440173": 0, "064": 0, "08726646259971647": 0, "1": 0, "10": 0, "100": 0, "1024": 0, "1107": 0, "1129": 0, "12": 0, "120": 0, "123": 0, "1396": 0, "142938": 0, "153": 0, "180": 0, "181": 0, "191023": 0, "1982": 0, "1e6": 0, "1\u00b3": 0, "2": 0, "200": 0, "2023": 0, "2343": 0, "2345": 0, "25": 0, "255": 0, "256": 0, "26": 0, "27": 0, "28": 0, "282": 0, "285227": 0, "28523": 0, "28706938": 0, "29": 0, "292": 0, "2d": 0, "3": 0, "38400": 0, "3dxrd": 0, "4": 0, "4189": 0, "42": 0, "421": 0, "4234": 0, "450": [], "5": 0, "50": 0, "500": 0, "5000": 0, "512": 0, "55": 0, "550": 0, "56": 0, "5707963267948966": 0, "5x5": 0, "6": 0, "75": 0, "768": 0, "769": 0, "80": 0, "90": 0, "9164": 0, "926": 0, "93fisher_distribut": 0, "95689793": 0, "A": 0, "By": 0, "For": 0, "If": 0, "In": 0, "It": 0, "The": 0, "To": 0, "__call__": 0, "__file__": 0, "_render_airy_peak": 0, "_render_gauss_peak": 0, "_render_projected_volum": 0, "_scalartype_co": 0, "about": 0, "abov": 0, "absolut": 0, "access": 0, "accur": 0, "across": 0, "activ": 0, "ad": 0, "addit": 0, "addition": 0, "advanc": 0, "after": 0, "again": 0, "against": 0, "ahead": 0, "ai": 0, "airi": 0, "align": 0, "all": 0, "allow": 0, "along": 0, "alpha": 0, "also": 0, "an": 0, "analysi": 0, "analyt": 0, "angl": 0, "angstrom": 0, "angular": 0, "ani": 0, "anoth": 0, "answer": 0, "append": 0, "appl": 0, "appli": 0, "appropri": 0, "approxim": 0, "ar": 0, "arbitrari": 0, "arbitrarili": 0, "area": 0, "around": 0, "arrai": 0, "artifacts_dir": 0, "as_matrix": 0, "assign": 0, "associ": 0, "assum": 0, "assumpt": 0, "attribut": 0, "augment": 0, "auto": 0, "automat": 0, "ax": 0, "axi": 0, "b": 0, "ball": 0, "barebon": 0, "base": 0, "basi": 0, "beam_loaded_from_disc": 0, "beam_side_length_i": 0, "beam_side_length_z": 0, "beam_vertic": 0, "been": 0, "befor": 0, "being": 0, "below": 0, "beta": 0, "better": 0, "between": 0, "bin": 0, "bodi": 0, "bool": 0, "boolean": 0, "bore": 0, "bound": 0, "bounding_height": 0, "bounding_radiu": 0, "bragg": 0, "break": 0, "build": 0, "built": 0, "builtin": 0, "bung": 0, "butterworth": 0, "c": 0, "calcul": 0, "call": 0, "callabl": 0, "can": 0, "captur": 0, "case": 0, "cd": 0, "cell": 0, "centr": 0, "centroid": 0, "chang": 0, "channel": 0, "check": 0, "cif": 0, "cite": 0, "clamp": 0, "class": 0, "classmethod": 0, "cleanli": 0, "clone": 0, "cmap": 0, "code": 0, "coeffici": 0, "col": 0, "collect": 0, "color": 0, "column": 0, "com": 0, "combin": 0, "command": 0, "complic": 0, "compon": 0, "compos": 0, "comput": 0, "computation": 0, "conda": 0, "consid": 0, "consist": 0, "constant": 0, "construct": 0, "contain": 0, "control": 0, "convex": 0, "coord": 0, "coordin": 0, "corner": 0, "correct": 0, "correspond": 0, "cp": 0, "cpu": 0, "creat": 0, "creation": 0, "cross": 0, "cryst": 0, "crystal": 0, "crystallit": 0, "crystallograph": 0, "crystalograph": 0, "cube": 0, "cubic": 0, "current": 0, "custom": 0, "cylind": 0, "d": 0, "data": 0, "def": 0, "default": 0, "defin": 0, "degre": 0, "delta": 0, "demonstr": 0, "densiti": 0, "depend": 0, "describ": 0, "descript": 0, "desir": 0, "det": 0, "det_corner_0": 0, "det_corner_1": 0, "det_corner_2": 0, "detail": 0, "detector_center_pixel_i": 0, "detector_center_pixel_z": 0, "detector_dist": 0, "detector_loaded_from_disc": 0, "determin": 0, "develop": 0, "deviat": 0, "dict": 0, "dictionari": 0, "differ": 0, "diffraction_fram": 0, "diffraction_pattern": 0, "diffraction_tim": 0, "dimens": 0, "dimension": 0, "direct": 0, "directli": 0, "dirnam": 0, "disc": 0, "discard": 0, "discret": 0, "disk": 0, "distanc": 0, "distribut": 0, "dive": 0, "divid": 0, "do": 0, "doe": 0, "dof": 0, "doi": 0, "domain": 0, "domin": 0, "dot": 0, "down": 0, "draw": 0, "drop": 0, "dtype": 0, "dure": 0, "e": 0, "e2": 0, "each": 0, "ecentroid": 0, "efac": 0, "element": 0, "element_data": 0, "element_node_map": 0, "element_phase_map": 0, "els": 0, "emit": 0, "en": 0, "enabl": 0, "end": 0, "enod": 0, "enorm": 0, "entir": 0, "equal": 0, "equat": 0, "eradiu": 0, "erron": 0, "espherecentroid": 0, "essenti": 0, "euclidean": 0, "euler": 0, "everi": 0, "evolum": 0, "exampl": 0, "exclus": 0, "execut": 0, "exist": 0, "exist_ok": 0, "exp": 0, "experi": 0, "experiment": 0, "extend": 0, "extens": 0, "exterior": 0, "extract": 0, "f": 0, "fabl": 0, "face": 0, "factor": 0, "fail": 0, "fall": 0, "fast": 0, "favorit": 0, "feedstock": 0, "feel": 0, "few": 0, "field": 0, "fig": 0, "figsiz": 0, "file": 0, "filenam": 0, "fill": 0, "filter": 0, "final": 0, "find": 0, "finit": 0, "first": 0, "fisher": 0, "fix": 0, "flag": 0, "flavour": 0, "float": 0, "follow": 0, "forg": 0, "form": 0, "format": 0, "found": 0, "four": 0, "fourier": 0, "frame": 0, "frames_to_rend": 0, "freedom": 0, "full": 0, "fun": 0, "function": 0, "g": 0, "gamma": 0, "gaussian": 0, "gaussian_kernel": 0, "gaussian_sigma": 0, "gener": 0, "generate_mesh_from_levelset": 0, "generate_mesh_from_vertic": 0, "geometri": 0, "get": 0, "get_uniform_powder_sampl": 0, "git": 0, "github": 0, "give": 0, "given": 0, "global": 0, "go": 0, "good": 0, "gradient": 0, "grai": 0, "grain": 0, "green": 0, "group": 0, "h": 0, "ha": 0, "halfspac": 0, "hall": 0, "handl": 0, "hasattr": 0, "have": 0, "hedm": 0, "height": 0, "helic": 0, "help": 0, "helper": 0, "henningsson": 0, "here": 0, "hit": 0, "hkl": 0, "hold": 0, "hood": 0, "hope": 0, "host": 0, "how": 0, "howev": 0, "http": 0, "hull": 0, "i": 0, "idea": 0, "ident": 0, "iffract": 0, "illumin": 0, "imag": 0, "imaginari": 0, "implement": 0, "import": 0, "imshow": 0, "incident_angl": 0, "index": 0, "indic": 0, "individu": 0, "infinit": 0, "inform": 0, "inherit": 0, "initi": 0, "input": 0, "instanc": 0, "instanti": 0, "instrument": 0, "int": 0, "integ": 0, "integr": 0, "intend": 0, "intens": 0, "intensity_factor": 0, "interact": 0, "interest": 0, "interior": 0, "intern": 0, "intersect": 0, "interv": 0, "invalid": 0, "invers": 0, "its": 0, "iv": 0, "j": 0, "join": 0, "journal": 0, "justif": 0, "k": 0, "k_out": 0, "kappa": 0, "kei": 0, "kernel": 0, "kernel_threshold": 0, "l": 0, "lab": 0, "laboratori": 0, "lagrang": 0, "lambda": 0, "larger": 0, "last": 0, "later": 0, "lattic": 0, "least": 0, "len": 0, "length": 0, "less": 0, "let": 0, "level": 0, "level_set": 0, "like": 0, "likewis": 0, "linalg": 0, "line": 0, "linear": 0, "linearli": 0, "link": 0, "list": 0, "load": 0, "locat": 0, "london": 0, "longer": 0, "look": 0, "lorentz": 0, "lorentz_factor": 0, "love": 0, "macro": 0, "macro_grain_limit": 0, "mai": 0, "mainli": 0, "make": 0, "makedir": 0, "malici": 0, "mani": 0, "map": 0, "march": 0, "materi": 0, "matplotlib": 0, "matric": 0, "max_bin": 0, "max_bragg_angl": 0, "max_cell_circumradiu": 0, "max_gaussian_kernel_radiu": 0, "maximum": 0, "maximum_sampling_bin_seper": 0, "mean": 0, "meant": 0, "measur": 0, "medium": 0, "mesh_lab": 0, "mesh_loaded_from_disc": 0, "mesh_sampl": 0, "meshio": 0, "meshpi": 0, "messag": 0, "metadata": 0, "method": 0, "micro": 0, "micro_grain_limit": 0, "micron": 0, "microstructur": 0, "miller": 0, "miller_indic": 0, "min_bragg_angl": 0, "minim": 0, "minimum": 0, "mise": 0, "miss": 0, "model": 0, "modifi": 0, "modul": 0, "monochromat": 0, "more": 0, "morphologi": 0, "motion_loaded_from_disc": 0, "motiv": 0, "move": 0, "mu": 0, "multi": 0, "must": 0, "mutual": 0, "my_detector": 0, "my_mesh": 0, "my_mot": 0, "my_polycryst": 0, "my_xray_beam": 0, "n": 0, "n_elm": 0, "n_face": 0, "n_node": 0, "n_pixel": 0, "n_y": 0, "n_z": 0, "name": 0, "nano": 0, "natur": 0, "ndarrai": 0, "need": 0, "neg": 0, "neither": 0, "never": 0, "new": 0, "next": 0, "nodal": 0, "nodal_coordin": 0, "node": 0, "non": 0, "none": 0, "norm": 0, "normal": 0, "note": 0, "now": 0, "np": 0, "number": 0, "number_of_cryst": 0, "number_of_detector_pixels_i": 0, "number_of_detector_pixels_z": 0, "number_of_el": 0, "number_of_grain": 0, "numpi": 0, "o": 0, "object": 0, "odf": 0, "onc": 0, "one": 0, "onli": 0, "onto": 0, "optim": 0, "option": 0, "order": 0, "org": 0, "orient": 0, "orientation_density_funct": 0, "orientation_lab": 0, "orientation_sampl": 0, "origin": 0, "orthogon": 0, "ourselv": 0, "out": 0, "output": 0, "outsid": 0, "outward": 0, "over": 0, "overlai": 0, "p": 0, "p3221": 0, "packag": 0, "paper": 0, "paramet": 0, "parametr": 0, "paraview": 0, "part": 0, "pass": 0, "path": 0, "path_to_cif_fil": 0, "pattern": 0, "pc": 0, "peak": 0, "peaks_detector_intersect": 0, "peaks_dict": 0, "per": 0, "perform": 0, "physic": 0, "pi": 0, "pickl": 0, "pixel": 0, "pixel_coordin": 0, "pixel_s": 0, "pixel_side_length_i": 0, "pixel_side_length_z": 0, "pixel_size_i": 0, "pixel_size_z": 0, "plane": 0, "platform": 0, "plt": 0, "point": 0, "pointcloud": 0, "polar": 0, "polarization_factor": 0, "polarization_vector": 0, "polycrystal_from_odf": 0, "polycrystallin": 0, "polyhedra": 0, "polyhedron": 0, "posit": 0, "possibl": 0, "powder": 0, "pre": 0, "prefer": 0, "prescrib": 0, "present": 0, "previou": 0, "primit": 0, "print": 0, "probabl": 0, "process": 0, "produc": 0, "product": 0, "profil": 0, "program": 0, "progress": 0, "project": 0, "propag": 0, "properti": 0, "provid": 0, "py": 0, "pygalmesh": 0, "pyplot": 0, "python": 0, "q": 0, "quantiti": 0, "quartz": 0, "quaternion": 0, "question": 0, "quickli": 0, "r": 0, "radian": 0, "radii": 0, "radiu": 0, "rais": 0, "rand": 0, "random": 0, "randomli": 0, "rang": 0, "read": 0, "readabl": 0, "real": 0, "receiv": 0, "rectangular": 0, "refer": 0, "reflect": 0, "regim": 0, "relat": 0, "releas": 0, "render": 0, "render_dtyp": 0, "repo": 0, "repres": 0, "represent": 0, "requir": 0, "research": 0, "respect": 0, "respons": 0, "result": 0, "return": 0, "revers": 0, "rigid": 0, "rigid_body_mot": 0, "rigidbodymot": 0, "rock": 0, "rotat": 0, "rotation_angl": 0, "rotation_axi": 0, "rotation_step": 0, "row": 0, "s1600576722011001": 0, "s3dxrd": 0, "same": 0, "sampl": 0, "sample_bounding_cylinder_height": 0, "sample_bounding_cylinder_radiu": 0, "sample_bounding_radiu": 0, "save": 0, "save_mesh_as_xdmf": 0, "scalar": 0, "scan": 0, "scatter": 0, "scattering_unit": 0, "scatteringunit": 0, "scenario": 0, "scienc": 0, "scikit": 0, "scipi": 0, "script": 0, "secur": 0, "see": 0, "select": 0, "self": 0, "separ": 0, "sequenc": 0, "set": 0, "set_beam_vertic": 0, "setup": 0, "sever": 0, "sgname": 0, "shape": 0, "should": 0, "show": 0, "side": 0, "signal": 0, "simpli": 0, "singl": 0, "sio2": 0, "size": 0, "skimag": 0, "slower": 0, "smaller": 0, "so3": 0, "softwar": 0, "solid": 0, "solv": 0, "some": 0, "someth": 0, "space": 0, "spatial": 0, "special": 0, "specifi": 0, "sphere": 0, "spheric": 0, "split": 0, "spot": 0, "spread": 0, "sqrt": 0, "squar": 0, "standard": 0, "start": 0, "step": 0, "steplength": 0, "str": 0, "strain": 0, "strain_lab": 0, "strain_sampl": 0, "strain_tensor": 0, "strength": 0, "structur": 0, "structure_factor": 0, "sub": 0, "subplot": 0, "suitabl": 0, "support": 0, "surfac": 0, "system": 0, "t": 0, "tail": 0, "take": 0, "target": 0, "tensor": 0, "test_artifact": 0, "tetgen": 0, "tetra": 0, "tetrahedr": 0, "tetramesh": 0, "textur": 0, "than": 0, "thi": 0, "thing": 0, "those": 0, "three": 0, "threshold": 0, "through": 0, "time": 0, "topologi": 0, "torch": 0, "toward": 0, "transform": 0, "transformed_point": 0, "translat": 0, "translation_vector": 0, "true": 0, "truncat": 0, "tupl": 0, "two": 0, "type": 0, "u": 0, "unauthent": 0, "under": 0, "undergo": 0, "uniform": 0, "uniformli": 0, "uniqu": 0, "unit": 0, "unit_cel": 0, "unpickl": 0, "until": 0, "untrust": 0, "updat": 0, "upper": 0, "us": 0, "usag": 0, "use_lorentz": 0, "use_polar": 0, "use_structure_factor": 0, "user": 0, "util": 0, "valu": 0, "valueerror": 0, "vari": 0, "variabl": 0, "variou": 0, "vector": 0, "verbos": 0, "vertic": 0, "via": 0, "viewer": 0, "visibl": 0, "visual": 0, "vmax": 0, "volum": 0, "von": 0, "von_mis": 0, "wa": 0, "wai": 0, "warn": 0, "wave_vector": 0, "wavelength": 0, "wavevector": 0, "we": 0, "well": 0, "were": 0, "when": 0, "where": 0, "whether": 0, "which": 0, "while": 0, "why": 0, "wide": 0, "width": 0, "wiki": 0, "wikipedia": 0, "within": 0, "without": 0, "work": 0, "worri": 0, "would": 0, "wrapper": 0, "www": 0, "xdmf": 0, "xfab": 0, "xrai": 0, "xray_propagation_direct": 0, "xrd": 0, "xrd_simul": 0, "y": 0, "yd": 0, "ydhat": 0, "ymax": 0, "you": 0, "your": 0, "z": 0, "zd": 0, "zdhat": 0, "zero": 0, "zmax": 0, "\u00b5m": 0}, "titles": ["Simulate X-ray Diffraction from Polycrystals in 3D."], "titleterms": {"3d": 0, "anaconda": 0, "beam": 0, "citat": 0, "credit": 0, "detector": 0, "diffract": 0, "document": 0, "from": 0, "instal": 0, "introduct": 0, "laue": 0, "linux": 0, "maco": 0, "mesh": 0, "motion": 0, "phase": 0, "pip": 0, "polycryst": 0, "rai": 0, "simul": 0, "sourc": 0, "templat": 0, "window": 0, "x": 0}}) \ No newline at end of file +Search.setIndex({"alltitles": {"Anaconda installation (Linux and Macos)": [[1, "anaconda-installation-linux-and-macos"]], "Anaconda installation (Windows)": [[1, "anaconda-installation-windows"]], "Anisotropic Gaussian orientation distributions": [[0, "anisotropic-gaussian-orientation-distributions"]], "Citation": [[1, "citation"]], "Comparison with a tetrahedral crystal": [[0, "comparison-with-a-tetrahedral-crystal"]], "Credits": [[1, "credits"]], "Documentation": [[1, "documentation"]], "Gaussian model: theory and references": [[0, null]], "Gaussian subgrain representation": [[0, "gaussian-subgrain-representation"]], "High-resolution rocking curve": [[0, "high-resolution-rocking-curve"]], "Illustrative tests": [[0, "illustrative-tests"]], "Incident-beam coordinates": [[0, "incident-beam-coordinates"]], "Installation": [[1, "installation"]], "Introduction": [[1, "introduction"]], "Linearized conservation equations": [[0, "linearized-conservation-equations"]], "Monochromatic, collimated specialization": [[0, "monochromatic-collimated-specialization"]], "On this page": [[0, "on-this-page"]], "Outgoing-beam coordinates": [[0, "outgoing-beam-coordinates"]], "Pip Installation": [[1, "pip-installation"]], "Pole density as a line integral": [[0, "pole-density-as-a-line-integral"]], "Projected concentration tensor": [[0, "projected-concentration-tensor"]], "Reciprocal-space coordinates": [[0, "reciprocal-space-coordinates"]], "Reciprocal-space map": [[0, "reciprocal-space-map"]], "References": [[0, "references"]], "Scattered-beam distribution": [[0, "scattered-beam-distribution"]], "Scattering theory": [[0, "scattering-theory"]], "Simulate X-ray Diffraction from Polycrystals in 3D.": [[1, null]], "Source installation": [[1, "source-installation"]], "Tangent-space approximation": [[0, "tangent-space-approximation"]], "beam": [[1, "module-xrd_simulator.beam"]], "detector": [[1, "module-xrd_simulator.detector"]], "gaussian_crystal_model": [[1, "module-xrd_simulator.gaussian_crystal_model"]], "mesh": [[1, "module-xrd_simulator.mesh"]], "motion": [[1, "module-xrd_simulator.motion"]], "phase": [[1, "module-xrd_simulator.phase"]], "polycrystal": [[1, "module-xrd_simulator.polycrystal"]], "templates": [[1, "module-xrd_simulator.templates"]]}, "docnames": ["gaussian_model_theory", "index"], "envversion": {"sphinx": 64, "sphinx.domains.c": 3, "sphinx.domains.changeset": 1, "sphinx.domains.citation": 1, "sphinx.domains.cpp": 9, "sphinx.domains.index": 1, "sphinx.domains.javascript": 3, "sphinx.domains.math": 2, "sphinx.domains.python": 4, "sphinx.domains.rst": 2, "sphinx.domains.std": 2}, "filenames": ["gaussian_model_theory.rst", "index.rst"], "indexentries": {"__call__() (xrd_simulator.motion.rigidbodymotion method)": [[1, "xrd_simulator.motion.RigidBodyMotion.__call__", false]], "beam (class in xrd_simulator.beam)": [[1, "xrd_simulator.beam.Beam", false]], "centroid (xrd_simulator.beam.beam attribute)": [[1, "xrd_simulator.beam.Beam.centroid", false]], "centroid (xrd_simulator.mesh.tetramesh attribute)": [[1, "xrd_simulator.mesh.TetraMesh.centroid", false]], "contains() (xrd_simulator.detector.detector method)": [[1, "xrd_simulator.detector.Detector.contains", false]], "coord (xrd_simulator.mesh.tetramesh attribute)": [[1, "xrd_simulator.mesh.TetraMesh.coord", false]], "det_corner_0 (xrd_simulator.detector.detector attribute)": [[1, "xrd_simulator.detector.Detector.det_corner_0", false]], "det_corner_1 (xrd_simulator.detector.detector attribute)": [[1, "xrd_simulator.detector.Detector.det_corner_1", false]], "det_corner_2 (xrd_simulator.detector.detector attribute)": [[1, "xrd_simulator.detector.Detector.det_corner_2", false]], "detector (class in xrd_simulator.detector)": [[1, "xrd_simulator.detector.Detector", false]], "diffract() (xrd_simulator.gaussian_crystal_model.gaussianpolycrystal method)": [[1, "xrd_simulator.gaussian_crystal_model.GaussianPolycrystal.diffract", false]], "diffract() (xrd_simulator.polycrystal.polycrystal method)": [[1, "xrd_simulator.polycrystal.Polycrystal.diffract", false]], "dof (xrd_simulator.mesh.tetramesh attribute)": [[1, "xrd_simulator.mesh.TetraMesh.dof", false]], "ecentroids (xrd_simulator.mesh.tetramesh attribute)": [[1, "xrd_simulator.mesh.TetraMesh.ecentroids", false]], "efaces (xrd_simulator.mesh.tetramesh attribute)": [[1, "xrd_simulator.mesh.TetraMesh.efaces", false]], "element_phase_map (xrd_simulator.polycrystal.polycrystal attribute)": [[1, "xrd_simulator.polycrystal.Polycrystal.element_phase_map", false]], "enod (xrd_simulator.mesh.tetramesh attribute)": [[1, "xrd_simulator.mesh.TetraMesh.enod", false]], "enormals (xrd_simulator.mesh.tetramesh attribute)": [[1, "xrd_simulator.mesh.TetraMesh.enormals", false]], "eradius (xrd_simulator.mesh.tetramesh attribute)": [[1, "xrd_simulator.mesh.TetraMesh.eradius", false]], "espherecentroids (xrd_simulator.mesh.tetramesh attribute)": [[1, "xrd_simulator.mesh.TetraMesh.espherecentroids", false]], "evolumes (xrd_simulator.mesh.tetramesh attribute)": [[1, "xrd_simulator.mesh.TetraMesh.evolumes", false]], "frames (xrd_simulator.detector.detector attribute)": [[1, "xrd_simulator.detector.Detector.frames", false]], "gaussian_kernel (xrd_simulator.detector.detector attribute)": [[1, "xrd_simulator.detector.Detector.gaussian_kernel", false]], "gaussian_sigma (xrd_simulator.detector.detector attribute)": [[1, "xrd_simulator.detector.Detector.gaussian_sigma", false]], "gaussianbeam (class in xrd_simulator.beam)": [[1, "xrd_simulator.beam.GaussianBeam", false]], "gaussianpolycrystal (class in xrd_simulator.gaussian_crystal_model)": [[1, "xrd_simulator.gaussian_crystal_model.GaussianPolycrystal", false]], "gaussiansubgrain (class in xrd_simulator.gaussian_crystal_model)": [[1, "xrd_simulator.gaussian_crystal_model.GaussianSubgrain", false]], "generate_mesh_from_levelset() (xrd_simulator.mesh.tetramesh class method)": [[1, "xrd_simulator.mesh.TetraMesh.generate_mesh_from_levelset", false]], "generate_mesh_from_vertices() (xrd_simulator.mesh.tetramesh class method)": [[1, "xrd_simulator.mesh.TetraMesh.generate_mesh_from_vertices", false]], "get_uniform_powder_sample() (in module xrd_simulator.templates)": [[1, "xrd_simulator.templates.get_uniform_powder_sample", false]], "halfspaces (xrd_simulator.beam.beam attribute)": [[1, "xrd_simulator.beam.Beam.halfspaces", false]], "inverse() (xrd_simulator.motion.rigidbodymotion method)": [[1, "xrd_simulator.motion.RigidBodyMotion.inverse", false]], "kernel_threshold (xrd_simulator.detector.detector attribute)": [[1, "xrd_simulator.detector.Detector.kernel_threshold", false]], "load() (xrd_simulator.beam.beam class method)": [[1, "xrd_simulator.beam.Beam.load", false]], "load() (xrd_simulator.detector.detector class method)": [[1, "xrd_simulator.detector.Detector.load", false]], "load() (xrd_simulator.mesh.tetramesh class method)": [[1, "xrd_simulator.mesh.TetraMesh.load", false]], "load() (xrd_simulator.motion.rigidbodymotion class method)": [[1, "xrd_simulator.motion.RigidBodyMotion.load", false]], "load() (xrd_simulator.polycrystal.polycrystal class method)": [[1, "xrd_simulator.polycrystal.Polycrystal.load", false]], "lorentz_factor (xrd_simulator.detector.detector attribute)": [[1, "xrd_simulator.detector.Detector.lorentz_factor", false]], "mesh_lab (xrd_simulator.polycrystal.polycrystal attribute)": [[1, "xrd_simulator.polycrystal.Polycrystal.mesh_lab", false]], "mesh_sample (xrd_simulator.polycrystal.polycrystal attribute)": [[1, "xrd_simulator.polycrystal.Polycrystal.mesh_sample", false]], "miller_indices (xrd_simulator.phase.phase attribute)": [[1, "xrd_simulator.phase.Phase.miller_indices", false]], "misorientation_tensor (xrd_simulator.gaussian_crystal_model.gaussiansubgrain attribute)": [[1, "xrd_simulator.gaussian_crystal_model.GaussianSubgrain.misorientation_tensor", false]], "module": [[1, "module-xrd_simulator.beam", false], [1, "module-xrd_simulator.detector", false], [1, "module-xrd_simulator.gaussian_crystal_model", false], [1, "module-xrd_simulator.mesh", false], [1, "module-xrd_simulator.motion", false], [1, "module-xrd_simulator.phase", false], [1, "module-xrd_simulator.polycrystal", false], [1, "module-xrd_simulator.templates", false]], "normal (xrd_simulator.detector.detector attribute)": [[1, "xrd_simulator.detector.Detector.normal", false]], "number_of_elements (xrd_simulator.mesh.tetramesh attribute)": [[1, "xrd_simulator.mesh.TetraMesh.number_of_elements", false]], "orientation (xrd_simulator.gaussian_crystal_model.gaussiansubgrain attribute)": [[1, "xrd_simulator.gaussian_crystal_model.GaussianSubgrain.orientation", false]], "orientation_lab (xrd_simulator.polycrystal.polycrystal attribute)": [[1, "xrd_simulator.polycrystal.Polycrystal.orientation_lab", false]], "orientation_sample (xrd_simulator.polycrystal.polycrystal attribute)": [[1, "xrd_simulator.polycrystal.Polycrystal.orientation_sample", false]], "origin (xrd_simulator.motion.rigidbodymotion attribute)": [[1, "xrd_simulator.motion.RigidBodyMotion.origin", false]], "path_to_cif_file (xrd_simulator.phase.phase attribute)": [[1, "xrd_simulator.phase.Phase.path_to_cif_file", false]], "peaks_detector_intersection() (xrd_simulator.detector.detector method)": [[1, "xrd_simulator.detector.Detector.peaks_detector_intersection", false]], "phase (class in xrd_simulator.phase)": [[1, "xrd_simulator.phase.Phase", false]], "phase (xrd_simulator.gaussian_crystal_model.gaussiansubgrain attribute)": [[1, "xrd_simulator.gaussian_crystal_model.GaussianSubgrain.phase", false]], "phases (xrd_simulator.polycrystal.polycrystal attribute)": [[1, "xrd_simulator.polycrystal.Polycrystal.phases", false]], "pixel_coordinates (xrd_simulator.detector.detector attribute)": [[1, "xrd_simulator.detector.Detector.pixel_coordinates", false]], "pixel_size_y (xrd_simulator.detector.detector attribute)": [[1, "xrd_simulator.detector.Detector.pixel_size_y", false]], "pixel_size_z (xrd_simulator.detector.detector attribute)": [[1, "xrd_simulator.detector.Detector.pixel_size_z", false]], "polarization_factor (xrd_simulator.detector.detector attribute)": [[1, "xrd_simulator.detector.Detector.polarization_factor", false]], "polarization_vector (xrd_simulator.beam.beam attribute)": [[1, "xrd_simulator.beam.Beam.polarization_vector", false]], "polycrystal (class in xrd_simulator.polycrystal)": [[1, "xrd_simulator.polycrystal.Polycrystal", false]], "polycrystal_from_odf() (in module xrd_simulator.templates)": [[1, "xrd_simulator.templates.polycrystal_from_odf", false]], "position (xrd_simulator.gaussian_crystal_model.gaussiansubgrain attribute)": [[1, "xrd_simulator.gaussian_crystal_model.GaussianSubgrain.position", false]], "render() (xrd_simulator.detector.detector method)": [[1, "xrd_simulator.detector.Detector.render", false]], "render_gaussian_splats() (xrd_simulator.detector.detector method)": [[1, "xrd_simulator.detector.Detector.render_gaussian_splats", false]], "rigidbodymotion (class in xrd_simulator.motion)": [[1, "xrd_simulator.motion.RigidBodyMotion", false]], "rotate() (xrd_simulator.mesh.tetramesh method)": [[1, "xrd_simulator.mesh.TetraMesh.rotate", false]], "rotate() (xrd_simulator.motion.rigidbodymotion method)": [[1, "xrd_simulator.motion.RigidBodyMotion.rotate", false]], "rotation_angle (xrd_simulator.motion.rigidbodymotion attribute)": [[1, "xrd_simulator.motion.RigidBodyMotion.rotation_angle", false]], "rotation_axis (xrd_simulator.motion.rigidbodymotion attribute)": [[1, "xrd_simulator.motion.RigidBodyMotion.rotation_axis", false]], "s3dxrd() (in module xrd_simulator.templates)": [[1, "xrd_simulator.templates.s3dxrd", false]], "save() (xrd_simulator.beam.beam method)": [[1, "xrd_simulator.beam.Beam.save", false]], "save() (xrd_simulator.detector.detector method)": [[1, "xrd_simulator.detector.Detector.save", false]], "save() (xrd_simulator.mesh.tetramesh method)": [[1, "xrd_simulator.mesh.TetraMesh.save", false]], "save() (xrd_simulator.motion.rigidbodymotion method)": [[1, "xrd_simulator.motion.RigidBodyMotion.save", false]], "save() (xrd_simulator.polycrystal.polycrystal method)": [[1, "xrd_simulator.polycrystal.Polycrystal.save", false]], "set_beam_vertices() (xrd_simulator.beam.beam method)": [[1, "xrd_simulator.beam.Beam.set_beam_vertices", false]], "sgname (xrd_simulator.phase.phase attribute)": [[1, "xrd_simulator.phase.Phase.sgname", false]], "shape_tensor (xrd_simulator.gaussian_crystal_model.gaussiansubgrain attribute)": [[1, "xrd_simulator.gaussian_crystal_model.GaussianSubgrain.shape_tensor", false]], "splat_grainshapes() (xrd_simulator.gaussian_crystal_model.gaussianpolycrystal method)": [[1, "xrd_simulator.gaussian_crystal_model.GaussianPolycrystal.splat_grainshapes", false]], "strain_lab (xrd_simulator.polycrystal.polycrystal attribute)": [[1, "xrd_simulator.polycrystal.Polycrystal.strain_lab", false]], "strain_sample (xrd_simulator.polycrystal.polycrystal attribute)": [[1, "xrd_simulator.polycrystal.Polycrystal.strain_sample", false]], "strain_tensor (xrd_simulator.gaussian_crystal_model.gaussiansubgrain attribute)": [[1, "xrd_simulator.gaussian_crystal_model.GaussianSubgrain.strain_tensor", false]], "structure_factor (xrd_simulator.detector.detector attribute)": [[1, "xrd_simulator.detector.Detector.structure_factor", false]], "structure_factors (xrd_simulator.phase.phase attribute)": [[1, "xrd_simulator.phase.Phase.structure_factors", false]], "tetramesh (class in xrd_simulator.mesh)": [[1, "xrd_simulator.mesh.TetraMesh", false]], "transform() (xrd_simulator.gaussian_crystal_model.gaussianpolycrystal method)": [[1, "xrd_simulator.gaussian_crystal_model.GaussianPolycrystal.transform", false]], "transform() (xrd_simulator.polycrystal.polycrystal method)": [[1, "xrd_simulator.polycrystal.Polycrystal.transform", false]], "translate() (xrd_simulator.mesh.tetramesh method)": [[1, "xrd_simulator.mesh.TetraMesh.translate", false]], "translate() (xrd_simulator.motion.rigidbodymotion method)": [[1, "xrd_simulator.motion.RigidBodyMotion.translate", false]], "translation (xrd_simulator.motion.rigidbodymotion attribute)": [[1, "xrd_simulator.motion.RigidBodyMotion.translation", false]], "unit_cell (xrd_simulator.phase.phase attribute)": [[1, "xrd_simulator.phase.Phase.unit_cell", false]], "update() (xrd_simulator.mesh.tetramesh method)": [[1, "xrd_simulator.mesh.TetraMesh.update", false]], "vertices (xrd_simulator.beam.beam attribute)": [[1, "xrd_simulator.beam.Beam.vertices", false]], "wave_vector (xrd_simulator.beam.beam attribute)": [[1, "xrd_simulator.beam.Beam.wave_vector", false]], "wavelength (xrd_simulator.beam.beam attribute)": [[1, "xrd_simulator.beam.Beam.wavelength", false]], "xrd_simulator.beam": [[1, "module-xrd_simulator.beam", false]], "xrd_simulator.detector": [[1, "module-xrd_simulator.detector", false]], "xrd_simulator.gaussian_crystal_model": [[1, "module-xrd_simulator.gaussian_crystal_model", false]], "xrd_simulator.mesh": [[1, "module-xrd_simulator.mesh", false]], "xrd_simulator.motion": [[1, "module-xrd_simulator.motion", false]], "xrd_simulator.phase": [[1, "module-xrd_simulator.phase", false]], "xrd_simulator.polycrystal": [[1, "module-xrd_simulator.polycrystal", false]], "xrd_simulator.templates": [[1, "module-xrd_simulator.templates", false]], "ydhat (xrd_simulator.detector.detector attribute)": [[1, "xrd_simulator.detector.Detector.ydhat", false]], "ymax (xrd_simulator.detector.detector attribute)": [[1, "xrd_simulator.detector.Detector.ymax", false]], "zdhat (xrd_simulator.detector.detector attribute)": [[1, "xrd_simulator.detector.Detector.zdhat", false]], "zmax (xrd_simulator.detector.detector attribute)": [[1, "xrd_simulator.detector.Detector.zmax", false]]}, "objects": {"xrd_simulator": [[1, 0, 0, "-", "beam"], [1, 0, 0, "-", "detector"], [1, 0, 0, "-", "gaussian_crystal_model"], [1, 0, 0, "-", "mesh"], [1, 0, 0, "-", "motion"], [1, 0, 0, "-", "phase"], [1, 0, 0, "-", "polycrystal"], [1, 0, 0, "-", "templates"]], "xrd_simulator.beam": [[1, 1, 1, "", "Beam"], [1, 1, 1, "", "GaussianBeam"]], "xrd_simulator.beam.Beam": [[1, 2, 1, "", "centroid"], [1, 2, 1, "", "halfspaces"], [1, 3, 1, "", "load"], [1, 2, 1, "", "polarization_vector"], [1, 3, 1, "", "save"], [1, 3, 1, "", "set_beam_vertices"], [1, 2, 1, "", "vertices"], [1, 2, 1, "", "wave_vector"], [1, 2, 1, "", "wavelength"]], "xrd_simulator.detector": [[1, 1, 1, "", "Detector"]], "xrd_simulator.detector.Detector": [[1, 3, 1, "", "contains"], [1, 2, 1, "", "det_corner_0"], [1, 2, 1, "", "det_corner_1"], [1, 2, 1, "", "det_corner_2"], [1, 2, 1, "", "frames"], [1, 2, 1, "", "gaussian_kernel"], [1, 2, 1, "", "gaussian_sigma"], [1, 2, 1, "", "kernel_threshold"], [1, 3, 1, "", "load"], [1, 2, 1, "", "lorentz_factor"], [1, 2, 1, "", "normal"], [1, 3, 1, "", "peaks_detector_intersection"], [1, 2, 1, "", "pixel_coordinates"], [1, 2, 1, "", "pixel_size_y"], [1, 2, 1, "", "pixel_size_z"], [1, 2, 1, "", "polarization_factor"], [1, 3, 1, "", "render"], [1, 3, 1, "", "render_gaussian_splats"], [1, 3, 1, "", "save"], [1, 2, 1, "", "structure_factor"], [1, 2, 1, "", "ydhat"], [1, 2, 1, "", "ymax"], [1, 2, 1, "", "zdhat"], [1, 2, 1, "", "zmax"]], "xrd_simulator.gaussian_crystal_model": [[1, 1, 1, "", "GaussianPolycrystal"], [1, 1, 1, "", "GaussianSubgrain"]], "xrd_simulator.gaussian_crystal_model.GaussianPolycrystal": [[1, 3, 1, "", "diffract"], [1, 3, 1, "", "splat_grainshapes"], [1, 3, 1, "", "transform"]], "xrd_simulator.gaussian_crystal_model.GaussianSubgrain": [[1, 2, 1, "", "misorientation_tensor"], [1, 2, 1, "", "orientation"], [1, 2, 1, "", "phase"], [1, 2, 1, "", "position"], [1, 2, 1, "", "shape_tensor"], [1, 2, 1, "", "strain_tensor"]], "xrd_simulator.mesh": [[1, 1, 1, "", "TetraMesh"]], "xrd_simulator.mesh.TetraMesh": [[1, 2, 1, "", "centroid"], [1, 2, 1, "", "coord"], [1, 2, 1, "", "dof"], [1, 2, 1, "", "ecentroids"], [1, 2, 1, "", "efaces"], [1, 2, 1, "", "enod"], [1, 2, 1, "", "enormals"], [1, 2, 1, "", "eradius"], [1, 2, 1, "", "espherecentroids"], [1, 2, 1, "", "evolumes"], [1, 3, 1, "", "generate_mesh_from_levelset"], [1, 3, 1, "", "generate_mesh_from_vertices"], [1, 3, 1, "", "load"], [1, 2, 1, "", "number_of_elements"], [1, 3, 1, "", "rotate"], [1, 3, 1, "", "save"], [1, 3, 1, "", "translate"], [1, 3, 1, "", "update"]], "xrd_simulator.motion": [[1, 1, 1, "", "RigidBodyMotion"]], "xrd_simulator.motion.RigidBodyMotion": [[1, 3, 1, "", "__call__"], [1, 3, 1, "", "inverse"], [1, 3, 1, "", "load"], [1, 2, 1, "", "origin"], [1, 3, 1, "", "rotate"], [1, 2, 1, "", "rotation_angle"], [1, 2, 1, "", "rotation_axis"], [1, 3, 1, "", "save"], [1, 3, 1, "", "translate"], [1, 2, 1, "", "translation"]], "xrd_simulator.phase": [[1, 1, 1, "", "Phase"]], "xrd_simulator.phase.Phase": [[1, 2, 1, "", "miller_indices"], [1, 2, 1, "", "path_to_cif_file"], [1, 2, 1, "", "sgname"], [1, 2, 1, "", "structure_factors"], [1, 2, 1, "", "unit_cell"]], "xrd_simulator.polycrystal": [[1, 1, 1, "", "Polycrystal"]], "xrd_simulator.polycrystal.Polycrystal": [[1, 3, 1, "", "diffract"], [1, 2, 1, "", "element_phase_map"], [1, 3, 1, "", "load"], [1, 2, 1, "", "mesh_lab"], [1, 2, 1, "", "mesh_sample"], [1, 2, 1, "", "orientation_lab"], [1, 2, 1, "", "orientation_sample"], [1, 2, 1, "", "phases"], [1, 3, 1, "", "save"], [1, 2, 1, "", "strain_lab"], [1, 2, 1, "", "strain_sample"], [1, 3, 1, "", "transform"]], "xrd_simulator.templates": [[1, 4, 1, "", "get_uniform_powder_sample"], [1, 4, 1, "", "polycrystal_from_odf"], [1, 4, 1, "", "s3dxrd"]]}, "objnames": {"0": ["py", "module", "Python module"], "1": ["py", "class", "Python class"], "2": ["py", "attribute", "Python attribute"], "3": ["py", "method", "Python method"], "4": ["py", "function", "Python function"]}, "objtypes": {"0": "py:module", "1": "py:class", "2": "py:attribute", "3": "py:method", "4": "py:function"}, "terms": {"": [0, 1], "0": [0, 1], "00030625": 1, "001": 1, "0010000000000000002": 1, "01": 1, "02": 1, "03": 1, "0440173": 1, "064": 1, "08726646259971647": 1, "1": [0, 1], "10": [0, 1], "100": 1, "1000": 1, "1024": 1, "1107": [0, 1], "1129": 1, "1145": 0, "12": 1, "120": 1, "123": 1, "1396": 1, "142938": 1, "1441": 0, "145": 0, "1456": 0, "1500": 1, "153": 1, "162": 0, "180": 1, "181": 1, "191023": 1, "1982": 1, "1e": 1, "1e6": 1, "1\u00b3": 1, "2": [0, 1], "200": 1, "2000": 1, "20000": 1, "2017": 0, "2023": [0, 1], "2200": 1, "2343": 1, "2345": 1, "25": 1, "255": 1, "256": 1, "26": 1, "27": 1, "28": 1, "282": 1, "285227": 1, "28523": 1, "28706938": 1, "29": 1, "292": 1, "2d": 1, "2k": 0, "3": [0, 1], "300": 1, "32": 1, "3592433": 0, "38400": 1, "3d": 0, "3dxrd": 1, "4": [0, 1], "4189": 1, "42": [0, 1], "421": 1, "4234": 1, "450": [], "5": 1, "50": [0, 1], "500": 1, "5000": 1, "512": 1, "55": 1, "550": 1, "56": 1, "5707963267948966": 1, "5x5": 1, "6": 1, "60": 0, "75": 1, "768": 1, "769": 1, "79": 0, "8": 1, "80": 1, "90": 1, "9164": 1, "926": 1, "93fisher_distribut": 1, "95689793": 1, "A": [0, 1], "By": 1, "For": [0, 1], "If": 1, "In": [0, 1], "It": [0, 1], "Its": [0, 1], "The": [0, 1], "Then": 0, "These": 1, "To": [0, 1], "With": 0, "_": [0, 1], "_0": 0, "__call__": 1, "__file__": 1, "_h": 0, "_render_airy_peak": 1, "_render_gauss_peak": 1, "_render_projected_volum": 1, "_scalartype_co": 1, "about": 1, "abov": [0, 1], "absenc": 0, "absolut": 1, "absorb": 0, "access": 1, "accur": 1, "acm": 0, "across": 1, "act": 0, "acta": 0, "activ": 1, "ad": 1, "add": 0, "addit": [0, 1], "addition": 1, "advanc": [0, 1], "affect": 0, "after": [0, 1], "again": 1, "against": 1, "ahead": 1, "ahl": 0, "ai": 1, "airi": 1, "aka": 1, "al": 0, "align": [0, 1], "all": 1, "allow": 1, "along": [0, 1], "alpha": 1, "also": [0, 1], "an": [0, 1], "analysi": [0, 1], "analyt": 1, "angl": [0, 1], "angstrom": 1, "angular": [0, 1], "ani": 1, "anisotrop": 1, "anoth": 1, "answer": 1, "append": 1, "appl": 1, "appli": [0, 1], "appropri": 1, "approx": 0, "approx1": 0, "approxim": 1, "ar": [0, 1], "arbitrari": 1, "arbitrarili": 1, "arcsin": 0, "area": 1, "argument": 0, "around": [0, 1], "arrai": 1, "artifacts_dir": 1, "as_matrix": 1, "assembl": 1, "assign": 1, "associ": 1, "assum": [0, 1], "assumpt": 1, "attribut": [0, 1], "augment": 1, "auto": 1, "automat": 1, "avail": 1, "ax": 1, "axi": [0, 1], "axis_1": 1, "axis_2": 1, "azim": 0, "b": [0, 1], "ball": 1, "bandwidth": 0, "barebon": 1, "base": [0, 1], "basi": [0, 1], "basic": 1, "beam_centroid_posit": 1, "beam_loaded_from_disc": 1, "beam_side_length_i": 1, "beam_side_length_z": 1, "beam_vertic": 1, "becaus": 0, "becom": 0, "been": [0, 1], "befor": 1, "begin": 0, "being": 1, "bellow": 1, "below": [0, 1], "beta": 1, "better": 1, "between": 1, "bigl": 0, "bigr": 0, "bin": 1, "bm": 0, "bmatrix": 0, "bodi": 1, "bool": 1, "boolean": 1, "bore": 1, "both": [0, 1], "bound": 1, "bounding_height": 1, "bounding_radiu": 1, "bragg": [0, 1], "break": 1, "brehm": 0, "brehm2023": 0, "broaden": [0, 1], "build": 1, "built": 1, "builtin": 1, "bung": 1, "butterworth": 1, "c": [0, 1], "calcul": [0, 1], "call": [0, 1], "callabl": 1, "can": [0, 1], "captur": 1, "carri": 1, "cartesian": 0, "case": 1, "cax": 1, "cd": 1, "cdot": 0, "cell": 1, "center": 1, "centr": [0, 1], "centroid": 1, "chang": 1, "channel": 1, "chapman": 0, "check": [0, 1], "chosen": 0, "cif": 1, "circl": 0, "circular": 1, "cite": 1, "civita": 0, "clamp": 1, "class": [0, 1], "classmethod": 1, "cleanli": 1, "clone": 1, "close": 0, "cmap": 1, "co": 0, "code": 1, "coeffici": 1, "coincid": 0, "col": 1, "colect": 1, "collect": 1, "collim": 1, "color": 1, "colorbar": 1, "column": [0, 1], "com": 1, "combin": [0, 1], "command": 1, "compact": 0, "compar": 0, "complet": 0, "complic": 1, "compon": 1, "compos": 1, "comput": 1, "computation": 1, "concentarion": 1, "concentr": 1, "concentration_tensor": 1, "conda": 1, "condit": 0, "consequ": 0, "consid": [0, 1], "consist": 1, "constant": 1, "constrained_layout": 1, "construct": [0, 1], "contain": 1, "contribut": [0, 1], "control": 1, "convent": 0, "convex": 1, "convolut": 0, "cook": 0, "coord": 1, "coordin": 1, "corner": 1, "correct": [0, 1], "correl": 0, "correspond": [0, 1], "covari": [0, 1], "cp": 1, "cpu": 1, "creat": 1, "creation": 1, "cross": [0, 1], "cryst": 1, "crystal": 1, "crystallit": 1, "crystallograph": 1, "crystallographi": 0, "crystallographica": 0, "crystalograph": 1, "cube": 1, "cubic": 1, "current": [0, 1], "custom": 1, "cylind": 1, "d": [0, 1], "data": 1, "def": 1, "default": 1, "default_rng": 1, "defin": [0, 1], "definit": [0, 1], "degre": [0, 1], "delta": [0, 1], "demonstr": [0, 1], "denot": 0, "densiti": 1, "depend": 1, "describ": [0, 1], "descript": [0, 1], "desir": 1, "det": [0, 1], "det_corner_0": 1, "det_corner_1": 1, "det_corner_2": 1, "detach": 1, "detail": 1, "detector": 0, "detector_center_pixel_i": 1, "detector_center_pixel_z": 1, "detector_dist": 1, "detector_imag": 1, "detector_loaded_from_disc": 1, "determin": 1, "detlef": 0, "develop": 1, "deviat": [0, 1], "dict": 1, "dictionari": 1, "differ": [0, 1], "diffract": 0, "diffraction_fram": 1, "diffraction_pattern": 1, "diffraction_tim": 1, "dimens": 1, "dimension": [0, 1], "dimensionless": 0, "dirac": 0, "direct": [0, 1], "directli": [0, 1], "dirnam": 1, "disc": 1, "discard": 1, "discret": 1, "disk": 1, "distanc": 1, "distinct": [0, 1], "distribut": 1, "dive": 1, "diverg": 0, "divid": 1, "do": [0, 1], "document": 0, "doe": [0, 1], "dof": 1, "doi": [0, 1], "domain": 1, "domin": 1, "dot": 1, "down": 1, "draw": 1, "drettaki": 0, "drop": 1, "dtype": 1, "due": 1, "dure": [0, 1], "e": 1, "e2": 1, "each": [0, 1], "ecentroid": 1, "edg": 0, "efac": 1, "effect": 0, "effici": 0, "eigenvalu": [0, 1], "elast": 0, "element": 1, "element_data": 1, "element_node_map": 1, "element_phase_map": 1, "ell": 0, "elong": 0, "els": 1, "emit": 1, "en": 1, "enabl": 1, "end": [0, 1], "energi": 0, "enforc": 0, "enod": 1, "enorm": 1, "enough": 0, "entir": 1, "epsilon_": 0, "equal": [0, 1], "equat": 1, "equival": 0, "eradiu": 1, "erron": 1, "error": [], "espherecentroid": 1, "essenti": 1, "establish": [], "estim": 0, "et": 0, "euclidean": [0, 1], "euler": 1, "evalu": 1, "everi": 1, "evolum": 1, "exactli": [0, 1], "exampl": [0, 1], "exce": 0, "exclud": 0, "exclus": 1, "execut": 1, "exist": 1, "exist_ok": 1, "exp": [0, 1], "expect": 0, "experi": 1, "experiment": 1, "explain": 1, "exponenti": 0, "exposur": [0, 1], "express": [0, 1], "extend": [0, 1], "extens": 1, "extent": 0, "exterior": 1, "extract": 1, "ey": 1, "f": [0, 1], "fabl": 1, "face": 1, "factor": 1, "fail": 1, "fall": 1, "fals": 1, "fast": 1, "favorit": 1, "feedstock": 1, "feel": 1, "few": 1, "field": [0, 1], "fig": 1, "figsiz": 1, "figur": [0, 1], "file": 1, "filenam": 1, "fill": 1, "filter": 1, "final": 1, "find": 1, "finit": [0, 1], "first": [0, 1], "fisher": 1, "fix": 1, "flag": 1, "flavour": 1, "float": 1, "follow": [0, 1], "forg": 1, "form": [0, 1], "format": 1, "forward": 1, "found": 1, "foundat": 0, "four": 1, "fourier": 1, "frac": 0, "frame": 1, "frames_to_rend": 1, "freedom": 1, "from": 0, "full": 1, "fun": 1, "function": [0, 1], "further": 0, "g": [0, 1], "g_0": 0, "gamma": 1, "gaussian": 1, "gaussian_beam": 1, "gaussian_crystal_model": 0, "gaussian_kernel": 1, "gaussian_polycryst": 1, "gaussian_sigma": [0, 1], "gaussianbeam": 1, "gaussianpolycryst": 1, "gaussiansubgrain": 1, "gener": 1, "generate_mesh_from_levelset": 1, "generate_mesh_from_vertic": 1, "geometri": [0, 1], "get": 1, "get_uniform_powder_sampl": 1, "git": 1, "github": 1, "give": [0, 1], "given": 1, "global": 1, "go": 1, "good": 1, "gradient": 1, "grai": 1, "grain": [0, 1], "grain_list": 1, "grainshap": 1, "graphic": 0, "green": 1, "gridspec_kw": 1, "group": 1, "h": [0, 1], "ha": [0, 1], "halfspac": 1, "hall": 1, "hand": [0, 1], "handl": 1, "hasattr": 1, "hat": 0, "have": [0, 1], "hedm": 1, "height": 1, "helic": 1, "help": 1, "helper": 1, "henningsson": 1, "here": [0, 1], "hit": 1, "hkl": 1, "hold": 1, "hood": 1, "hope": 1, "host": 1, "how": 1, "howev": 1, "http": 1, "hull": 1, "i": [0, 1], "idea": 1, "ident": 1, "iffract": 1, "ij": 0, "ijk": 0, "illumin": [0, 1], "illustr": 1, "imag": 1, "imaginari": 1, "img": 1, "implement": [0, 1], "import": 1, "imshow": 1, "incid": 1, "incident_angl": 1, "includ": [0, 1], "index": 1, "indic": [0, 1], "individu": 1, "induc": 1, "infinit": [0, 1], "inform": 1, "infti": 0, "inherit": 1, "initi": 1, "input": [0, 1], "instanc": 1, "instanti": 1, "instead": 1, "instrument": 1, "int": [0, 1], "int_": 0, "integ": 1, "integr": 1, "intend": 1, "intens": 1, "intensity_factor": 1, "interact": 1, "interest": [], "interior": 1, "intern": [], "interpret": 0, "intersect": 1, "interv": 1, "introduc": 0, "invalid": 1, "invers": 1, "invert": 0, "involv": 0, "isotrop": 0, "its": [0, 1], "iv": 1, "j": 1, "jakobsen": 0, "jet": 1, "join": 1, "journal": [0, 1], "justif": 1, "k": [0, 1], "k_out": 1, "kappa": 1, "kei": 1, "kerbl": 0, "kerbl2023": 0, "kernel": 1, "kernel_threshold": 1, "kopana": 0, "l": 1, "la": 0, "lab": 1, "label": [0, 1], "laboratori": [0, 1], "labratori": 1, "lagrang": 1, "lambda": [0, 1], "lambda_0": 0, "larger": [0, 1], "largest": 0, "last": [0, 1], "later": 1, "lattic": [0, 1], "least": 1, "left": [0, 1], "leimk\u00fchler": 0, "len": 1, "length": 1, "less": 1, "let": [0, 1], "level": 1, "level_set": 1, "levi": 0, "li": 0, "like": 1, "likewis": 1, "linalg": 1, "line": 1, "linear": 1, "linearli": 1, "link": 1, "list": 1, "lki": 0, "lm": 0, "load": 1, "local": 0, "locat": 1, "log10": 1, "log_": 1, "log_intens": 1, "logarithm": 1, "london": 1, "long_axis_direct": 1, "long_axis_width": 1, "longer": 1, "longest": 1, "look": 1, "lorentz": 1, "lorentz_factor": 1, "love": 1, "lvert": 0, "m": 0, "macro": 1, "macro_grain_limit": 1, "magnifi": 1, "mai": 1, "mainli": [], "major": 1, "make": [0, 1], "make_random_tensor": 1, "makedir": 1, "malici": 1, "mani": 1, "map": 1, "march": 1, "match": 1, "materi": 1, "mathbf": 0, "mathrm": 0, "mathsf": 0, "matplotlib": 1, "matric": 1, "matrix": [0, 1], "matter": 0, "max_bin": 1, "max_bragg_angl": 1, "max_cell_circumradiu": 1, "max_gaussian_kernel_radiu": [0, 1], "max_grain_s": 1, "max_misorient": 1, "maximum": 1, "maximum_sampling_bin_seper": 1, "mean": [0, 1], "mean_scattering_direct": 1, "meant": 1, "measur": [0, 1], "medium": 1, "mesh_lab": 1, "mesh_loaded_from_disc": 1, "mesh_sampl": 1, "meshio": 1, "meshpi": 1, "messag": 1, "metadata": 1, "method": [0, 1], "micro": 1, "micro_grain_limit": 1, "micrometr": 1, "micron": 1, "microscopi": 0, "microstructur": 1, "miller": [0, 1], "miller_indic": 1, "min_bragg_angl": 1, "minim": 1, "minimum": 1, "minor": 1, "minu": 0, "misalign": 0, "mise": 1, "misorient": [0, 1], "misorientation_tensor": [0, 1], "miss": 1, "mnj": 0, "model": 1, "modifi": 1, "modul": [0, 1], "momentum": 0, "monochromat": 1, "more": [0, 1], "morphologi": 1, "mosaic": [0, 1], "motion_loaded_from_disc": 1, "motiv": 1, "move": 1, "mu": 1, "much": 0, "multi": 1, "multipl": [], "must": 1, "mutual": 1, "my_detector": 1, "my_mesh": 1, "my_mot": 1, "my_polycryst": 1, "my_xray_beam": 1, "n": [0, 1], "n_elm": 1, "n_face": 1, "n_node": 1, "n_pixel": 1, "n_y": 1, "n_z": 1, "name": 1, "nano": 1, "narrow": [0, 1], "natur": 1, "ndarrai": 1, "need": 1, "neg": 1, "neglect": 0, "neither": 1, "never": 1, "new": 1, "next": 1, "nodal": 1, "nodal_coordin": 1, "node": 1, "nomin": 0, "non": 1, "nondegener": 0, "none": 1, "nonlinear": 0, "norm": 1, "normal": [0, 1], "note": 1, "now": 1, "np": 1, "number": 1, "number_of_cryst": 1, "number_of_detector_pixels_i": 1, "number_of_detector_pixels_z": 1, "number_of_el": 1, "number_of_grain": 1, "numpi": 1, "o": 1, "object": 1, "obtain": 0, "odf": 1, "offset": 0, "onc": 1, "one": [0, 1], "onli": 1, "onto": [0, 1], "optic": 0, "optim": 1, "option": 1, "order": [0, 1], "ordinari": 0, "org": 1, "orient": 1, "orientation_density_funct": 1, "orientation_lab": 1, "orientation_sampl": 1, "origin": [0, 1], "orthogon": 1, "ourselv": 1, "out": 1, "outer": 1, "output": 1, "outsid": 1, "outward": 1, "over": [0, 1], "overlai": 1, "p": [0, 1], "p3221": 1, "p_a": 0, "p_b": 0, "p_k": 0, "p_n": 0, "packag": 1, "page": 1, "pair": 0, "paper": 1, "parallel": 0, "paramet": [0, 1], "parameter": 0, "parametr": 1, "paraview": 1, "part": 1, "partial": [0, 1], "particular": 0, "pass": 1, "patch": 1, "patch_siz": 1, "path": [0, 1], "path_to_cif_fil": 1, "pattern": 1, "pc": 1, "peak": [0, 1], "peaks_batch_s": 1, "peaks_detector_intersect": 1, "peaks_dict": 1, "per": 1, "perform": 1, "perp": 0, "perpendicular": 0, "phase": 0, "physic": 1, "pi": [0, 1], "pickl": 1, "pixel": [0, 1], "pixel_coordin": 1, "pixel_s": 1, "pixel_side_length_i": 1, "pixel_side_length_z": 1, "pixel_size_i": 1, "pixel_size_z": 1, "pixellength": 1, "place": 1, "plane": [0, 1], "platform": 1, "plot": 1, "plt": 1, "point": [0, 1], "pointcloud": 1, "polar": 1, "polarization_factor": 1, "polarization_vector": 1, "pole": 1, "polycryst": 0, "polycrystal_from_odf": 1, "polycrystallin": 1, "polyhedra": 1, "polyhedron": 1, "popul": 1, "posit": [0, 1], "possibl": 1, "poulsen": 0, "poulsen2017": 0, "powder": 1, "pre": 1, "precis": 1, "predict": 0, "prefactor": 0, "prefer": 1, "prepres": 1, "prescrib": 1, "present": 1, "previou": 1, "primit": [0, 1], "print": 1, "probabl": [0, 1], "process": 1, "produc": [0, 1], "product": [0, 1], "profil": [0, 1], "program": 1, "progress": 1, "project": 1, "projected_shape_pixelunit": 1, "projected_thicknes_scale_factor": 1, "prolat": 0, "propag": 1, "properti": 1, "provid": [0, 1], "psi_": 0, "py": 1, "pygalmesh": 1, "pyplot": 1, "python": 1, "q": [0, 1], "q_": 0, "qquad": 0, "quad": 0, "qualit": [], "quantit": [], "quantiti": 1, "quartz": [0, 1], "quaternion": 1, "question": 1, "quickli": 1, "r": [0, 1], "r_": 0, "r_x": 0, "r_y": 0, "r_z": 0, "rad": [0, 1], "radial": 0, "radian": 1, "radianc": 0, "radii": 1, "radiu": 1, "rai": 0, "rais": 1, "rand": 1, "random": 1, "random_direct": 1, "random_st": 1, "randomli": 1, "rang": 1, "raster": [0, 1], "rather": 0, "read": 1, "readabl": 1, "real": [0, 1], "realiz": 1, "receiv": 1, "reciproc": 1, "rectangular": 1, "reduc": 0, "refer": 1, "reflect": [0, 1], "refract": 0, "regim": 1, "region": 1, "rel": 0, "relat": [0, 1], "releas": 1, "render": [0, 1], "render_dtyp": 1, "render_gaussian_splat": 1, "repeat": 0, "replac": 0, "repo": 1, "repositori": 1, "repres": [0, 1], "represent": 1, "requir": [0, 1], "rescal": 0, "research": 1, "resolv": 0, "respect": 1, "respons": 1, "result": [0, 1], "retain": [0, 1], "return": 1, "revers": 1, "right": 0, "rigid": 1, "rigid_body_mot": 1, "rigidbodymot": 1, "rng": 1, "rock": 1, "roll": 0, "root": [0, 1], "rotat": [0, 1], "rotation_angl": 1, "rotation_axi": 1, "rotation_step": 1, "row": 1, "rsm": 0, "run": 1, "rvert": 0, "s1600576717011037": 0, "s1600576722011001": 1, "s2053273323000682": 0, "s3dxrd": 1, "same": [0, 1], "sampl": [0, 1], "sample_bounding_cylinder_height": 1, "sample_bounding_cylinder_radiu": 1, "sample_bounding_radiu": 1, "sample_orient": 1, "sample_rotation_during_exposur": 1, "sample_transl": 1, "save": 1, "save_mesh_as_xdmf": 1, "scalar": [0, 1], "scale": 1, "scale_factor": 1, "scan": 1, "scatter": 1, "scattering_unit": 1, "scatteringunit": 1, "scenario": 1, "scene": 0, "scienc": 1, "scikit": 1, "scipi": 1, "script": 1, "second": 0, "section": [0, 1], "secur": 1, "see": 1, "seed": 1, "select": 1, "self": 1, "separ": [0, 1], "sequenc": 1, "serial": 0, "set": [0, 1], "set_beam_vertic": 1, "set_titl": 1, "set_xlim": 1, "set_ylim": 1, "setup": 1, "seven": 0, "sever": [0, 1], "sgname": 1, "shape": [0, 1], "shape_concentration_tensor": 1, "shape_tensor": [0, 1], "shift": 0, "shize": 1, "short_axis_direct": 1, "short_axis_width": 1, "shortest": 1, "should": [0, 1], "show": [0, 1], "side": 1, "sign": 0, "signal": 1, "similar": 0, "simon": 0, "simpli": 1, "simul": 0, "sin": 0, "singl": [0, 1], "sio2": 1, "six": 0, "size": [0, 1], "skimag": 1, "slower": 1, "small": [0, 1], "smaller": 1, "so": [0, 1], "so3": 1, "softwar": 1, "solid": 1, "solv": [], "some": 1, "someth": 1, "space": 1, "span": 0, "spatial": 1, "special": 1, "specifi": 1, "speed": 1, "sphere": 1, "spheric": 1, "splat": 0, "splat_grainshap": 1, "split": 1, "spot": [0, 1], "spread": [0, 1], "sqrt": [0, 1], "squar": [0, 1], "stack": 1, "standard": [0, 1], "start": 1, "static": 1, "step": 1, "steplength": 1, "str": 1, "strain": [0, 1], "strain_lab": 1, "strain_sampl": 1, "strain_tensor": 1, "strength": 1, "structur": 1, "structure_factor": 1, "sub": 1, "subgrain": 1, "subplot": 1, "subsequ": 0, "substitut": 0, "subtract": 0, "suitabl": 1, "sum": 1, "summat": 0, "suppli": 1, "support": [0, 1], "surfac": 1, "symbol": 0, "symmetr": [0, 1], "symmetri": 1, "system": 1, "t": [0, 1], "t_": 0, "tail": 1, "take": 1, "tangent": 1, "target": 1, "tensor": 1, "term": 0, "test": 1, "test_artifact": 1, "tetgen": 1, "tetra": 1, "tetrahedr": 1, "tetrahedron": 0, "tetramesh": 1, "textur": [0, 1], "tfrac": 0, "than": [0, 1], "thei": 0, "theori": 1, "therefor": 0, "theshold": 1, "theta_0": 0, "thi": 1, "thick": 1, "thing": 1, "those": 1, "three": [0, 1], "threshold": 1, "through": 1, "thu": 0, "time": [0, 1], "top": 0, "topologi": 1, "torch": 1, "toward": 1, "transact": 0, "transform": 1, "transformed_point": 1, "translat": 1, "translation_vector": 1, "transvers": 0, "true": 1, "truncat": 1, "tupl": 1, "two": [0, 1], "type": 1, "u": [0, 1], "u_0": 1, "unauthent": 1, "under": [0, 1], "undergo": 1, "underli": 0, "uniform": [0, 1], "uniformli": 1, "uniqu": 1, "unit": [0, 1], "unit_cel": 1, "unpickl": 1, "until": 1, "untrust": 1, "up": 1, "updat": 1, "upper": 1, "us": [0, 1], "usag": 1, "use_lorentz": 1, "use_polar": 1, "use_structure_factor": 1, "user": [], "util": 1, "uv_corrd": 1, "v": 1, "v_0": 1, "valu": 1, "valueerror": 1, "vanish": 0, "varepsilon": 0, "vari": 1, "variabl": 1, "variou": 1, "vector": [0, 1], "verbos": 1, "vertic": 1, "via": 1, "viewer": 1, "visibl": [0, 1], "visual": 1, "vmax": 1, "vmin": 1, "volum": 1, "von": 1, "von_mis": 1, "w": [0, 1], "wa": 1, "wai": 1, "warn": 1, "wave_vector": 1, "wavelength": [0, 1], "wavenumb": 0, "wavevector": [0, 1], "we": 1, "weak": 0, "weight": 1, "well": 1, "were": 1, "when": [0, 1], "where": [0, 1], "wherea": 0, "whether": 1, "which": 1, "while": [0, 1], "white": 0, "whose": 0, "why": 1, "wide": 1, "width": [0, 1], "width_ratio": 1, "wiki": 1, "wikipedia": 1, "window": 0, "within": [0, 1], "without": [0, 1], "word": 0, "work": [0, 1], "workflow": [0, 1], "worri": 1, "would": 1, "wrapper": 1, "write": 0, "written": 0, "www": 1, "x": 0, "xdmf": 1, "xfab": 1, "xrai": 1, "xray_propagation_direct": 1, "xrd": 1, "xrd_simul": [0, 1], "y": [0, 1], "yd": 1, "ydhat": 1, "ymax": 1, "you": 1, "your": 1, "z": 1, "zd": 1, "zdhat": 1, "zero": 1, "zeta_": 0, "zmax": 1, "\u00b5m": 1}, "titles": ["Gaussian model: theory and references", "Simulate X-ray Diffraction from Polycrystals in 3D."], "titleterms": {"3d": 1, "On": 0, "anaconda": 1, "anisotrop": 0, "approxim": 0, "beam": [0, 1], "citat": 1, "collim": 0, "comparison": 0, "concentr": 0, "conserv": 0, "content": [], "coordin": 0, "credit": 1, "crystal": 0, "curv": 0, "densiti": 0, "detector": 1, "diffract": 1, "distribut": 0, "document": 1, "equat": 0, "from": 1, "gaussian": 0, "gaussian_crystal_model": 1, "high": 0, "illustr": 0, "incid": 0, "instal": 1, "integr": 0, "introduct": 1, "laue": [], "line": 0, "linear": 0, "linux": 1, "maco": 1, "map": 0, "mesh": 1, "model": 0, "monochromat": 0, "motion": 1, "orient": 0, "outgo": 0, "page": 0, "phase": 1, "pip": 1, "pole": 0, "polycryst": 1, "project": 0, "rai": 1, "reciproc": 0, "refer": 0, "represent": 0, "resolut": 0, "rock": 0, "scatter": 0, "simul": 1, "sourc": 1, "space": 0, "special": 0, "subgrain": 0, "tangent": 0, "templat": 1, "tensor": 0, "test": 0, "tetrahedr": 0, "theori": 0, "thi": 0, "window": 1, "x": 1}}) \ No newline at end of file diff --git a/docs/source/gaussian_model_theory.rst b/docs/source/gaussian_model_theory.rst new file mode 100644 index 0000000..353fb63 --- /dev/null +++ b/docs/source/gaussian_model_theory.rst @@ -0,0 +1,582 @@ +:orphan: + +.. _gaussian-model-theory: + +Gaussian model: theory and references +===================================== + +This page describes the scattering model and approximations underlying +:mod:`xrd_simulator.gaussian_crystal_model`. A worked example is provided in +the module documentation. + +.. contents:: On this page + :local: + :depth: 2 + +Gaussian subgrain representation +-------------------------------- + +Gaussian splatting represents a three-dimensional scene using Gaussian +primitives whose projected contributions can be rendered efficiently by +rasterization [Kerbl2023]_. Here, a polycrystal is represented by Gaussian +grains or subgrains, each with a Gaussian real-space density profile and a +narrow Gaussian distribution of lattice orientations. Under the approximations +below, each subgrain produces Gaussian-shaped diffraction spots on the +detector. Related Gaussian-basis methods for diffraction prediction and +partiality estimation have been demonstrated in serial crystallography +[Brehm2023]_. + +Several effects can contribute to diffraction-spot broadening: grain size, +detector point-spread, incident-beam bandwidth, incident-beam angular +divergence, lattice misorientation (mosaicity), and distributions of strain. +When these effects have Gaussian profiles and their deviations are small +enough to neglect nonlinear terms, they can be combined within a Gaussian +model. + +The model described here includes the real-space extent of the illuminated +subgrains and their mosaicity. The incident beam is monochromatic and +collimated, and each subgrain has a uniform strain tensor. A uniform strain +can shift a reflection but does not describe a distribution of lattice +spacings within that subgrain. Small sample rotations during an exposure are +approximated by additional Gaussian orientation broadening. + +.. note:: + + The Gaussian-splat rendering path does not apply a separate detector + point-spread convolution. The ``gaussian_sigma`` and + ``max_gaussian_kernel_radius`` detector settings retained in the worked + example therefore do not add this broadening to its Gaussian spots. + +Scattering theory +----------------- + +Scattered-beam distribution +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Let the incident beam have a phase-space density :math:`p(\mathbf{k})` +centred on :math:`\mathbf{k}_0`, with nominal wavenumber + +.. math:: + :label: gaussian-wavenumber + + k = \lvert\mathbf{k}_0\rvert = \frac{2\pi}{\lambda_0}. + +For a particular reflection, let :math:`f(\mathbf{q})` describe the subgrain's +reciprocal-space map (RSM) around a reciprocal-lattice vector +:math:`\mathbf{G}_0`. The scattered-beam distribution is written as + +.. math:: + :label: gaussian-scattering-integral + + I(\mathbf{p}) + = \int \mathrm{d}^{3}\mathbf{k} + \int \mathrm{d}^{3}\mathbf{q}\; + p(\mathbf{k})\,f(\mathbf{q})\, + \delta\!\left(\lvert\mathbf{k}\rvert-\lvert\mathbf{p}\rvert\right) + \delta^{(3)}\!\left(\mathbf{k}+\mathbf{q}-\mathbf{p}\right). + +The scalar Dirac delta enforces elastic scattering, while the +three-dimensional Dirac delta enforces momentum conservation. + +Incident-beam coordinates +~~~~~~~~~~~~~~~~~~~~~~~~~ + +Following the coordinate construction used by Poulsen et al. +[Poulsen2017]_, write the incident wavevector as + +.. math:: + :label: gaussian-incident-wavevector + + \mathbf{k} + = \mathbf{k}_0 + + k\left( + \varepsilon\,\hat{\mathbf{k}}_0 + + \zeta_{\parallel}\,\hat{\mathbf{k}}_{\parallel} + + \zeta_{\perp}\,\hat{\mathbf{k}}_{\perp} + \right). + +A hat denotes a unit vector. The vector +:math:`\hat{\mathbf{k}}_{\parallel}` is perpendicular to +:math:`\mathbf{k}_0` and lies in the plane spanned by :math:`\mathbf{k}_0` +and :math:`\mathbf{G}_0`. Its sign is chosen so that +:math:`\mathbf{G}_0\cdot\hat{\mathbf{k}}_{\parallel}>0`. Complete the +right-handed basis with + +.. math:: + + \hat{\mathbf{k}}_{\perp} + = \hat{\mathbf{k}}_0\times\hat{\mathbf{k}}_{\parallel}. + +The coordinate :math:`\varepsilon` describes the relative wavenumber +deviation, and :math:`\zeta_{\parallel}` and :math:`\zeta_{\perp}` describe +small angular deviations of the incident beam. + +Reciprocal-space coordinates +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Define the nominal Bragg angle and the exactly aligned scattering vector by + +.. math:: + :label: gaussian-nominal-scattering-vector + + \begin{aligned} + \theta_0 + &= \arcsin\!\left(\frac{\lvert\mathbf{G}_0\rvert}{2k}\right), \\ + \mathbf{Q} + &= 2k\sin\theta_0\left( + \cos\theta_0\,\hat{\mathbf{k}}_{\parallel} + - \sin\theta_0\,\hat{\mathbf{k}}_0 + \right) \\ + &= 2k\sin\theta_0\,\hat{\mathbf{Q}}. + \end{aligned} + +The vectors :math:`\mathbf{G}_0` and :math:`\mathbf{Q}` coincide when the +reflection is exactly aligned. For a small misalignment, their difference is +parallel, to first order, to the rocking direction + +.. math:: + + \hat{\mathbf{q}}_{\mathrm{rock}} + = \cos\theta_0\,\hat{\mathbf{k}}_0 + + \sin\theta_0\,\hat{\mathbf{k}}_{\parallel}. + +Use dimensionless rocking, strain, and rolling coordinates to write + +.. math:: + :label: gaussian-reciprocal-coordinates + + \begin{aligned} + \mathbf{q} + &= \mathbf{G}_0 + + 2k\sin\theta_0\left( + q_{\mathrm{rock}}\,\hat{\mathbf{q}}_{\mathrm{rock}} + + q_{\mathrm{strain}}\,\hat{\mathbf{Q}} + + q_{\mathrm{roll}}\,\hat{\mathbf{k}}_{\perp} + \right) \\ + &\approx \mathbf{Q} + + 2k\sin\theta_0\left( + (q_{\mathrm{rock}}-\delta q)\, + \hat{\mathbf{q}}_{\mathrm{rock}} + + q_{\mathrm{strain}}\,\hat{\mathbf{Q}} + + q_{\mathrm{roll}}\,\hat{\mathbf{k}}_{\perp} + \right), + \end{aligned} + +where + +.. math:: + :label: gaussian-rocking-offset + + \delta q + = \frac{ + (\mathbf{Q}-\mathbf{G}_0)\cdot\hat{\mathbf{q}}_{\mathrm{rock}} + }{2k\sin\theta_0}. + +This offset measures the reflection's misalignment and is equal to the +rocking angle to first order, with the sign convention above. The incident +coordinates :math:`(\varepsilon,\zeta_{\parallel},\zeta_{\perp})` and the +reciprocal-space coordinates +:math:`(q_{\mathrm{rock}},q_{\mathrm{strain}},q_{\mathrm{roll}})` separate +bandwidth, collimation, misorientation, and strain effects. + +Outgoing-beam coordinates +~~~~~~~~~~~~~~~~~~~~~~~~~ + +The nominal scattered wavevector is + +.. math:: + :label: gaussian-nominal-outgoing-wavevector + + \mathbf{k}_h + = \mathbf{k}_0+\mathbf{Q} + = k\left( + \cos(2\theta_0)\,\hat{\mathbf{k}}_0 + + \sin(2\theta_0)\,\hat{\mathbf{k}}_{\parallel} + \right). + +Define the radial direction perpendicular to :math:`\mathbf{k}_h` by + +.. math:: + + \hat{\mathbf{k}}_{\mathrm{rad}} + = \cos(2\theta_0)\,\hat{\mathbf{k}}_{\parallel} + - \sin(2\theta_0)\,\hat{\mathbf{k}}_0. + +The outgoing wavevector can then be written as + +.. math:: + :label: gaussian-outgoing-wavevector + + \mathbf{p} + = \mathbf{k}_h + + k\left( + \varepsilon'\,\hat{\mathbf{k}}_h + + \psi_{\mathrm{rad}}\,\hat{\mathbf{k}}_{\mathrm{rad}} + + \psi_{\mathrm{azim}}\,\hat{\mathbf{k}}_{\perp} + \right). + +Here :math:`\psi_{\mathrm{rad}}` and :math:`\psi_{\mathrm{azim}}` are small +transverse angular deviations from the nominal outgoing direction. + +Linearized conservation equations +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +To first order, + +.. math:: + + \lvert\mathbf{k}\rvert\approx k(1+\varepsilon), + \qquad + \lvert\mathbf{p}\rvert\approx k(1+\varepsilon'). + +Energy conservation therefore sets :math:`\varepsilon'=\varepsilon`. +Momentum conservation gives :math:`\mathbf{q}=\mathbf{p}-\mathbf{k}` and, +after substitution, + +.. math:: + :label: gaussian-linearized-momentum + + \begin{aligned} + &2\sin\theta_0\Bigl[ + (q_{\mathrm{rock}}-\delta q)\, + \hat{\mathbf{q}}_{\mathrm{rock}} + + q_{\mathrm{strain}}\,\hat{\mathbf{Q}} + + q_{\mathrm{roll}}\,\hat{\mathbf{k}}_{\perp} + \Bigr] \\ + &\qquad = 2\sin\theta_0\,\varepsilon\,\hat{\mathbf{Q}} + + \psi_{\mathrm{rad}}\,\hat{\mathbf{k}}_{\mathrm{rad}} \\ + &\qquad\quad + + \psi_{\mathrm{azim}}\,\hat{\mathbf{k}}_{\perp} + - \zeta_{\parallel}\,\hat{\mathbf{k}}_{\parallel} + - \zeta_{\perp}\,\hat{\mathbf{k}}_{\perp}. + \end{aligned} + +.. note:: + + The incident-divergence terms have minus signs because the incident + wavevector is subtracted in :math:`\mathbf{q}=\mathbf{p}-\mathbf{k}`. + This corrects the corresponding signs in the original workflow. It does + not affect the monochromatic, collimated specialization below, where + both terms vanish. + +Monochromatic, collimated specialization +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The current model assumes + +.. math:: + + \varepsilon=\zeta_{\parallel}=\zeta_{\perp}=0, + \qquad q_{\mathrm{strain}}=0. + +The last condition excludes strain-distribution broadening; it does not +require the uniform subgrain strain to vanish. The conservation equation +reduces to + +.. math:: + + \begin{aligned} + &2\sin\theta_0\left[ + (q_{\mathrm{rock}}-\delta q)\, + \hat{\mathbf{q}}_{\mathrm{rock}} + + q_{\mathrm{roll}}\,\hat{\mathbf{k}}_{\perp} + \right] \\ + &\qquad + = \psi_{\mathrm{rad}}\,\hat{\mathbf{k}}_{\mathrm{rad}} + + \psi_{\mathrm{azim}}\,\hat{\mathbf{k}}_{\perp}. + \end{aligned} + +For a nondegenerate Bragg geometry, the three coordinate equations are + +.. math:: + :label: gaussian-scattering-constraints + + q_{\mathrm{rock}}=\delta q, + \qquad + \psi_{\mathrm{rad}}=0, + \qquad + \psi_{\mathrm{azim}}=2\sin\theta_0\,q_{\mathrm{roll}}. + +Thus, the scattered angular distribution samples the RSM along a line +parallel to :math:`\hat{\mathbf{k}}_{\perp}`, offset by :math:`\delta q` +from its centre. For a Gaussian RSM, this gives a one-dimensional Gaussian +angular distribution. The finite real-space extent of the illuminated +subgrain is subsequently projected onto the detector to produce a +two-dimensional spot. + +Anisotropic Gaussian orientation distributions +---------------------------------------------- + +Tangent-space approximation +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Consider a narrow orientation distribution centred on an orientation +:math:`g_0`. Approximate orientation space by its left tangent space at +:math:`g_0`, so that a small rotation vector :math:`\mathbf{r}` acts in +laboratory coordinates: + +.. math:: + :label: gaussian-tangent-rotation + + g\approx R_{\mathbf{r}}g_0 + \approx + \begin{bmatrix} + 1 & -r_z & r_y \\ + r_z & 1 & -r_x \\ + -r_y & r_x & 1 + \end{bmatrix}g_0. + +The Gaussian orientation density used in the workflow is + +.. math:: + :label: gaussian-orientation-density + + f(\mathbf{r}) + = \frac{2}{\sqrt{\pi}}\sqrt{\det\mathbf{T}}\, + \exp\!\left(-\mathbf{r}^{\mathsf{T}}\mathbf{T}\mathbf{r}\right), + +where :math:`\mathbf{T}` is a symmetric positive-definite concentration +tensor. + +.. note:: + + The prefactor above retains the workflow's normalization convention. + With ordinary Euclidean tangent-space measure, + :math:`\int f(\mathbf{r})\,\mathrm{d}^{3}\mathbf{r}=2\pi`, not one. + It should therefore not be interpreted as a unit-normalized Euclidean + probability density without rescaling. + + For an exponential of the form + :math:`\exp(-\mathbf{r}^{\mathsf{T}}\mathbf{T}\mathbf{r})`, the + corresponding normalized Gaussian has covariance + :math:`\tfrac{1}{2}\mathbf{T}^{-1}`. The implementation directly inverts + ``misorientation_tensor`` to obtain :math:`\mathbf{T}`. Consequently, + the square roots of the input tensor's eigenvalues are Gaussian width + parameters, not standard deviations. For an input eigenvalue + :math:`a^2`, the standard deviation is :math:`a/\sqrt{2}`. + The real-space Gaussian uses the same exponential convention for + ``shape_tensor``. This distinction matters because the class attribute + descriptions currently use the word "covariance" for these inputs. + +Pole density as a line integral +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +For Miller indices :math:`(h,k,\ell)`, define the unit lattice direction in +crystal coordinates by + +.. math:: + + \mathbf{h} + = \frac{ + \mathbf{B}_0[h,k,\ell]^{\mathsf{T}} + }{ + \left\lvert\mathbf{B}_0[h,k,\ell]^{\mathsf{T}}\right\rvert + }, + \qquad + \mathbf{p}=g_0\mathbf{h}. + +Here :math:`\mathbf{B}_0` is the reciprocal-lattice basis matrix. In this +section, :math:`\mathbf{p}` denotes the mean lattice direction, rather than +the outgoing wavevector used in the scattering section. It is parallel to +:math:`\mathbf{G}_0`. Let :math:`\mathbf{y}` be a unit direction in +laboratory coordinates, parallel to a sampled reciprocal-space vector +:math:`\mathbf{q}`; it is not a Cartesian coordinate-axis label. + +The pole density, also called the pair-correlation function in texture +analysis, describes the density of lattice directions along +:math:`\mathbf{y}`. Its calculation normally involves an integral over a +circle in :math:`\mathrm{SO}(3)`. Within the tangent-space approximation, +replace this by an infinite line integral, parameterized as + +.. math:: + :label: gaussian-orientation-fibre + + \mathbf{r}(\lambda) + = \frac{\mathbf{p}\times\mathbf{y}}{\mathbf{p}\cdot\mathbf{y}} + + \lambda\mathbf{p} + = \mathbf{r}_0+\lambda\mathbf{p}. + +The parameter :math:`\lambda` here is a line-integration parameter, not the +X-ray wavelength. Substitution into the Gaussian density and completion of +the square give, with +:math:`d=\mathbf{p}^{\mathsf{T}}\mathbf{T}\mathbf{p}`, + +.. math:: + :label: gaussian-pole-density-integral + + \begin{aligned} + A(\mathbf{y},\mathbf{p};f) + &= \int_{-\infty}^{\infty} + f\!\left(\mathbf{r}_0+\lambda\mathbf{p}\right) + \,\mathrm{d}\lambda \\ + &= \frac{2\sqrt{\det\mathbf{T}}} + {\sqrt{d}} + \exp\!\left[ + -\mathbf{r}_0^{\mathsf{T}}\mathbf{T}\mathbf{r}_0 + + \frac{ + (\mathbf{r}_0^{\mathsf{T}}\mathbf{T}\mathbf{p})^2 + }{d} + \right]. + \end{aligned} + +Projected concentration tensor +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +For directions close to the pole, make the further approximation +:math:`\mathbf{p}\cdot\mathbf{y}\approx1`. Define + +.. math:: + + \mathbf{M} + = \mathbf{T} + - \frac{ + \mathbf{T}\mathbf{p}\mathbf{p}^{\mathsf{T}}\mathbf{T} + }{d}. + +Let :math:`[\mathbf{p}]_{\times}` denote the cross-product matrix, +:math:`[\mathbf{p}]_{\times}\mathbf{y}=\mathbf{p}\times\mathbf{y}`. Then + +.. math:: + :label: gaussian-projected-concentration + + \mathbf{T}_{\mathbf{p}} + = [\mathbf{p}]_{\times}^{\mathsf{T}} + \mathbf{M} + [\mathbf{p}]_{\times}, + +and the pole density becomes + +.. math:: + :label: gaussian-pole-density + + A(\mathbf{y},\mathbf{p};f) + \approx \frac{2\sqrt{\det\mathbf{T}}}{\sqrt{d}}\, + \exp\!\left( + -\mathbf{y}^{\mathsf{T}}\mathbf{T}_{\mathbf{p}}\mathbf{y} + \right). + +Equivalently, using the Levi-Civita symbol :math:`\epsilon_{ijk}` and +summation over repeated indices, + +.. math:: + + [\mathbf{T}_{\mathbf{p}}]_{ij} + = p_k\,\epsilon_{lki} + \left( + T_{lm}-\frac{T_{la}p_a p_b T_{bm}}{d} + \right) + \epsilon_{mnj}\,p_n. + +This absorbs the cross products into the projected tensor and makes the +subsequent Gaussian expressions more compact. + +Reciprocal-space map +~~~~~~~~~~~~~~~~~~~~ + +The pole density is defined for unit-vector arguments. With no +strain-distribution broadening, extend it to a reciprocal-space map supported +on the local plane :math:`q_{\mathrm{strain}}=0`. + +Introduce the two-column basis matrix and coordinate vector + +.. math:: + + \mathbf{U} + = \begin{bmatrix} + \hat{\mathbf{q}}_{\mathrm{rock}} & \hat{\mathbf{k}}_{\perp} + \end{bmatrix}, + \qquad + \mathbf{u} + = \begin{bmatrix} + q_{\mathrm{rock}} \\ + q_{\mathrm{roll}} + \end{bmatrix}. + +In the dimensionless reciprocal-space coordinates defined above, the +workflow's local RSM is + +.. math:: + :label: gaussian-local-rsm + + f(\mathbf{q}) + \approx \delta(q_{\mathrm{strain}})\, + \frac{2\sqrt{\det\mathbf{T}}}{\sqrt{d}}\, + \exp\!\left[ + -\mathbf{u}^{\mathsf{T}} + \left(\mathbf{U}^{\mathsf{T}} + \mathbf{T}_{\mathbf{p}}\mathbf{U}\right) + \mathbf{u} + \right]. + +The Dirac delta expresses the absence of a distribution of lattice spacings +within the subgrain. Sampling this Gaussian at +:math:`q_{\mathrm{rock}}=\delta q`, as required by +:eq:`gaussian-scattering-constraints`, gives the scattered angular +distribution for the reflection. + +Illustrative tests +------------------ + +Comparison with a tetrahedral crystal +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The workflow compares a one-degree rotation of a single quartz crystal +shaped as a symmetric tetrahedron with a Gaussian approximation of the same +crystal. The crystal is much larger than the detector pixels, and the largest +scattering angles exceed 60 degrees, making radial spot elongation from the +projection geometry visible. + +Seven Gaussians approximate the tetrahedron: one isotropic Gaussian at its +centre and six prolate Gaussians along its edges. The Gaussian model uses a +small orientation spread. + +The comparison shows similar peak positions and shapes. The Gaussian model +also produces additional weak peaks because its rotational integration uses +a Gaussian time window, whereas the tetrahedral model uses a top-hat time +window. + +.. figure:: ../_static/single_crystal_quartz.png + :alt: Quartz diffraction patterns from tetrahedral and Gaussian models. + :align: center + :width: 100% + + Comparison of the tetrahedral crystal model with a seven-Gaussian + approximation during a one-degree sample rotation. + +High-resolution rocking curve +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +A second test resolves a rocking curve and checks the shape of the resulting +three-dimensional reciprocal-space map. The workflow figure compares the +simulated profiles with the expected contributions from grain size and +misorientation. + +.. figure:: ../_static/testing_rocking_curves.png + :alt: Reciprocal-space map and profiles from a high-resolution rocking scan. + :align: center + :width: 100% + + High-resolution rocking-curve test showing the reciprocal-space map and + the contributions of grain size and misorientation to the simulated + profiles. + +References +---------- + +.. [Kerbl2023] Kerbl, B., Kopanas, G., Leimkühler, T. and Drettakis, G. + (2023). 3D Gaussian Splatting for Real-Time Radiance Field Rendering. + *ACM Transactions on Graphics*, **42**\ (4). + `doi:10.1145/3592433 `_. + +.. [Brehm2023] Brehm, W., White, T. and Chapman, H. N. (2023). + Crystal diffraction prediction and partiality estimation using Gaussian + basis functions. *Acta Crystallographica Section A: Foundations and + Advances*, **79**, 145–162. + `doi:10.1107/S2053273323000682 + `_. + +.. [Poulsen2017] Poulsen, H. F., Jakobsen, A. C., Simons, H., Ahl, S. R., + Cook, P. K. and Detlefs, C. (2017). X-ray diffraction microscopy based on + refractive optics. *Journal of Applied Crystallography*, **50**, 1441–1456. + `doi:10.1107/S1600576717011037 + `_. diff --git a/docs/source/index.rst b/docs/source/index.rst index 29d8fe8..61bc367 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -1,7 +1,3 @@ -.. toctree:: - :maxdepth: 2 - :caption: Contents: - .. include:: ../../README.rst ====================================== @@ -50,16 +46,16 @@ motion :members: :inherited-members: -templates +gaussian_crystal_model ====================================== -.. automodule:: xrd_simulator.templates - :members: - :inherited-members: +.. automodule:: xrd_simulator.gaussian_crystal_model + :members: + :inherited-members: -laue +templates ====================================== -.. automodule:: xrd_simulator.laue +.. automodule:: xrd_simulator.templates :members: :inherited-members: diff --git a/xrd_simulator/gaussian_crystal_model.py b/xrd_simulator/gaussian_crystal_model.py index b3b9e6c..63997b3 100644 --- a/xrd_simulator/gaussian_crystal_model.py +++ b/xrd_simulator/gaussian_crystal_model.py @@ -1,22 +1,206 @@ -from scipy.spatial.transform import Rotation as R +r"""Gaussian subgrain models for polycrystalline X-ray diffraction simulation. + +Represent a polycrystal as a collection of subgrains with Gaussian real-space +density profiles and narrow Gaussian orientation distributions. Each subgrain +is defined by its crystallographic phase, centroid, shape tensor, mean lattice +orientation, misorientation tensor, and uniform strain tensor. Both the shape +and orientation spread may be anisotropic. + +:class:`GaussianSubgrain` describes an individual subgrain, while +:class:`GaussianPolycrystal` assembles subgrains and renders diffraction +patterns using a :class:`~xrd_simulator.beam.GaussianBeam` and a +:class:`~xrd_simulator.detector.Detector`. + +The forward model computes beam-subgrain intersections, reflection +partialities, and scattered-beam angular distributions. Illuminated subgrain +shapes are projected onto the detector and combined with mosaicity-induced +angular broadening to form two-dimensional Gaussian spots. These contributions +are rasterized and summed into a detector intensity image. Small sample +rotations during an exposure are approximated by additional Gaussian +orientation broadening. + +The module also supports rigid-body transformations of the polycrystal and +pole-figure rendering. + +Notes +----- +The current implementation supports single-phase polycrystals and assumes a +monochromatic, collimated incident beam. Orientation distributions are +approximated in the tangent space about each subgrain's mean orientation, +requiring small misorientations. Uniform strain modifies reciprocal-lattice +vectors, but strain-distribution broadening is not included within an +individual subgrain. A distribution of strains can instead be approximated by +combining appropriately weighted diffraction patterns from subgrain +populations with different uniform strain tensors. + +.. seealso:: + + :doc:`Gaussian model: theory and references ` + Scattering geometry, Gaussian orientation distributions, reciprocal-space + maps, model assumptions, illustrative tests, and references. + +Examples +-------- +Simulate a static diffraction pattern from 2000 Gaussian quartz subgrains with +random positions, orientations, shapes, and anisotropic orientation spreads. + +Run the example from the repository root with ``xrd_simulator`` installed, so +that ``tests/data/quartz.cif`` is available. Real-space lengths are in +micrometres, the wavelength and unit-cell lengths are in angstroms, and +unit-cell angles are in degrees. Orientation-spread parameters are in radians. + +First, define the beam, detector, and crystallographic phase: + +.. code-block:: python + + import numpy as np + import matplotlib.pyplot as plt + from scipy.spatial.transform import Rotation as R + + from xrd_simulator.beam import GaussianBeam + from xrd_simulator.detector import Detector + from xrd_simulator.phase import Phase + from xrd_simulator.gaussian_crystal_model import ( + GaussianSubgrain, + GaussianPolycrystal, + ) + + rng = np.random.default_rng(0) + + gaussian_beam = GaussianBeam( + xray_propagation_direction=np.array([1.0, 0.0, 0.0]), + beam_centroid_position=np.array([0.0, 0.0, 0.0]), + wavelength=0.28523, + polarization_vector=np.array([0.0, 1.0, 0.0]), + long_axis_width=300.0, + long_axis_direction=np.array([0.0, 0.0, 1.0]), + short_axis_width=50.0, + short_axis_direction=np.array([0.0, 1.0, 0.0]), + ) + + detector = Detector( + det_corner_0=np.array([142938.3, -38400.0, -38400.0]), + det_corner_1=np.array([142938.3, 38400.0, -38400.0]), + det_corner_2=np.array([142938.3, -38400.0, 38400.0]), + pixel_size=(25.0, 25.0), + gaussian_sigma=1.0, + max_gaussian_kernel_radius=5, + ) + + quartz = Phase( + unit_cell=[4.926, 4.926, 5.4189, 90.0, 90.0, 120.0], + sgname="P3221", + path_to_cif_file="tests/data/quartz.cif", + ) + +Next, build the polycrystal. The helper generates a symmetric positive-definite +width tensor with a randomly oriented symmetry axis. Its eigenvalues are +``axis_1**2``, ``axis_1**2``, and ``axis_2**2``. Shape-width parameters range +from 50 to 200 micrometres, and misorientation-width parameters range from +0.01 to 0.03 radians. The distinction between these width parameters and +standard deviations is explained on the theory page. + +.. code-block:: python + + def make_random_tensor(axis_1, axis_2): + random_direction = rng.normal(size=3) + random_direction /= np.linalg.norm(random_direction) + return ( + axis_1**2 * np.eye(3) + + (axis_2**2 - axis_1**2) + * np.outer(random_direction, random_direction) + ) + + grain_list = [] + + for _ in range(2000): + position = rng.uniform(-500.0, 500.0, size=3) + shape_tensor = make_random_tensor( + rng.uniform(50.0, 200.0), + rng.uniform(50.0, 200.0), + ) + orientation = R.random(random_state=rng).as_matrix() + misorientation_tensor = make_random_tensor( + rng.uniform(0.01, 0.03), + rng.uniform(0.01, 0.03), + ) + + grain_list.append( + GaussianSubgrain( + phase=quartz, + position=position, + shape_tensor=shape_tensor, + orientation=orientation, + misorientation_tensor=misorientation_tensor, + strain_tensor=np.zeros((3, 3)), + ) + ) + + gaussian_polycrystal = GaussianPolycrystal( + grain_list, + max_misorientation=0.03, + max_grain_size=200.0, + ) + +Compute a diffraction pattern without sample rotation during the exposure: + +.. code-block:: python + + f = gaussian_polycrystal.diffract( + beam=gaussian_beam, + detector=detector, + verbose=True, + threshold=10.0, + ) + +Finally, plot the full detector image and a magnified region on the same +logarithmic intensity scale: + +.. code-block:: python + + log_intensity = np.log10(f.detach().cpu().numpy() + 1e-10) + + fig, axs = plt.subplots( + 1, 3, + figsize=(8, 4), + gridspec_kw={"width_ratios": (1, 1, 0.1)}, + constrained_layout=True, + ) + axs[0].imshow(log_intensity, cmap="jet", vmin=3, vmax=6) + img = axs[1].imshow(log_intensity, cmap="jet", vmin=3, vmax=6) + axs[0].set_title("Full detector") + axs[1].set_title("Magnified region") + axs[1].set_xlim(1500, 2200) + axs[1].set_ylim(2200, 1500) + fig.colorbar(img, cax=axs[2], label=r"$\log_{10}(I + 10^{-10})$") + plt.show() + +.. figure:: ../_static/many_crystals_quartz.png + :alt: Simulated quartz diffraction pattern and a magnified detector region. + :align: center + :width: 100% + + Representative output from the Gaussian workflow. The precise spot pattern + depends on the random subgrain realization. This image is retained from the + original workflow and need not match the seeded realization above exactly. +""" + +import time + import numpy as np import numpy.typing as npt import torch from torch import Tensor +from xfab.tools import form_b_mat -from xrd_simulator.phase import Phase -from xrd_simulator.utils import ensure_torch -from xrd_simulator.detector import Detector -from xrd_simulator.laue import _get_diffraction_arcsegment, _project_misorientation_tensor from xrd_simulator.beam import GaussianBeam +from xrd_simulator.detector import Detector +from xrd_simulator.laue import _get_diffraction_arcsegment from xrd_simulator.motion import RigidBodyMotion +from xrd_simulator.phase import Phase from xrd_simulator.scattering_factors import _polarization +from xrd_simulator.utils import ensure_torch -from xfab.tools import form_b_mat, genhkl_base -from xfab import sg - - -import time class GaussianSubgrain: """A `subgrain` has a 3D-gaussian density distribution and a narrow (<=0.1 rad) 3D @@ -33,21 +217,28 @@ class GaussianSubgrain: orientation : torch.Tensor | np.array Orientation of the centroid of the ODF as a rotation matrix, shape ``(3, 3,)`` misorientation_tensor : torch.Tensor | np.array - Covariance tensor of the grain orientation in the left-hand tangent space. + Covariance tensor of the grain orientation in the left-hand tangent space. aka. laboratory coordinates, shape ``(3, 3,)`` strain_tensor : torch.Tensor | np.array Strain tensor in laboratory coordinates, shape ``(3, 3,)`` """ - def __init__(self, + def __init__( + self, phase: Phase, - position: npt.NDArray | Tensor = np.array([0, 0, 0]), + position: npt.NDArray | Tensor = np.array([0, 0, 0]), shape_tensor: npt.NDArray | Tensor = np.eye(3), - orientation: npt.NDArray | Tensor = np.eye(3), # 3by3 Rotation matrix. - misorientation_tensor: npt.NDArray | Tensor = 0.0175**2 * np.eye(3), # Default one degree isotropic - strain_tensor: npt.NDArray | Tensor = np.zeros((3, 3,)), + orientation: npt.NDArray | Tensor = np.eye(3), # 3by3 Rotation matrix. + misorientation_tensor: npt.NDArray | Tensor = 0.0175**2 + * np.eye(3), # Default one degree isotropic + strain_tensor: npt.NDArray | Tensor = np.zeros( + ( + 3, + 3, + ) + ), ): - + self.phase = phase self.position = position self.shape_tensor = shape_tensor @@ -55,36 +246,53 @@ def __init__(self, self.misorientation_tensor = misorientation_tensor self.strain_tensor = strain_tensor + class GaussianPolycrystal: - """ Colection of ``GaussianSubgrain`` object representing a polycrystalline sample. + """Colection of ``GaussianSubgrain`` object representing a polycrystalline sample. Variables ``max_grain_size`` and ``max_misorientation`` can be supplied to speed up diffraction calculations. """ - def __init__(self, + def __init__( + self, grain_list: list[GaussianSubgrain], max_grain_size: float = 1000.0, - max_misorientation: float = 0.1, + max_misorientation: float = 0.1, ): - + phases_list = list(set([grain.phase for grain in grain_list])) n_phases = len(phases_list) if not n_phases == 1: - raise NotImplementedError('Multi-phase object has not been implemented yet.') + raise NotImplementedError( + "Multi-phase object has not been implemented yet." + ) self.n_grains = len(grain_list) self.max_grain_size = max_grain_size self.max_misorientation = max_misorientation self.phase = phases_list[0] - self.positions = torch.stack([ensure_torch(grain.position) for grain in grain_list]) - self.shape_concentration_tensors = torch.stack([ensure_torch(np.linalg.inv(grain.shape_tensor)) for grain in grain_list]) + self.positions = torch.stack( + [ensure_torch(grain.position) for grain in grain_list] + ) + self.shape_concentration_tensors = torch.stack( + [ensure_torch(np.linalg.inv(grain.shape_tensor)) for grain in grain_list] + ) - self.orientaions = torch.stack([ensure_torch(grain.orientation) for grain in grain_list]) - self.misori_concentration_tensors = torch.stack([ensure_torch(np.linalg.inv(grain.misorientation_tensor)) for grain in grain_list]) - - self.strains = torch.stack([ensure_torch(grain.strain_tensor) for grain in grain_list]) + self.orientaions = torch.stack( + [ensure_torch(grain.orientation) for grain in grain_list] + ) + self.misori_concentration_tensors = torch.stack( + [ + ensure_torch(np.linalg.inv(grain.misorientation_tensor)) + for grain in grain_list + ] + ) + + self.strains = torch.stack( + [ensure_torch(grain.strain_tensor) for grain in grain_list] + ) def diffract( self, @@ -94,10 +302,10 @@ def diffract( sample_translation: npt.NDArray | Tensor = np.zeros(3), sample_rotation_during_exposure: npt.NDArray | Tensor = np.zeros(3), verbose=False, - threshold = 6.0, - peaks_batch_size = 20000, + threshold=6.0, + peaks_batch_size=20000, ): - """ Render a single diffraction pattern. + """Render a single diffraction pattern. Parameters ---------- @@ -123,79 +331,121 @@ def diffract( wavelength = beam.wavelength sample_orientation = ensure_torch(sample_orientation) sample_rotation_during_exposure = ensure_torch(sample_rotation_during_exposure) - + # Rotate detector and incident beam by inverse of sample-rotation. - xray_propagation_direction = torch.einsum('ij,i->j', sample_orientation, ensure_torch(xray_propagation_direction) ) - detector_origin = detector.pixel_coordinates[0,0] + xray_propagation_direction = torch.einsum( + "ij,i->j", sample_orientation, ensure_torch(xray_propagation_direction) + ) + detector_origin = detector.pixel_coordinates[0, 0] W = np.stack([detector.zdhat, detector.ydhat]) - detector_norm = torch.einsum('ij,i->j', sample_orientation, ensure_torch(np.cross(W[0,:], W[1,:]))) - detector_origin = torch.einsum('ij,i->j', sample_orientation, ensure_torch(detector_origin)) - W = torch.einsum('ij,ui->uj', sample_orientation, ensure_torch(W)) + detector_norm = torch.einsum( + "ij,i->j", sample_orientation, ensure_torch(np.cross(W[0, :], W[1, :])) + ) + detector_origin = torch.einsum( + "ij,i->j", sample_orientation, ensure_torch(detector_origin) + ) + W = torch.einsum("ij,ui->uj", sample_orientation, ensure_torch(W)) pixellengths = torch.tensor([detector.pixel_size_y, detector.pixel_size_z]) - #Compute intersection of grains and beam - intersection_pos, intersection_shape_concentration_tensors, beam_intensity_factors, grains_hit\ - = beam._intersect( + # Compute intersection of grains and beam + ( + intersection_pos, + intersection_shape_concentration_tensors, + beam_intensity_factors, + grains_hit, + ) = beam._intersect( ensure_torch(self.positions), ensure_torch(self.shape_concentration_tensors), sample_orientation, sample_translation, self.max_grain_size, - threshold = threshold, + threshold=threshold, ) if verbose: - print(f'Beam-grain intersection took {time.time()-t0}.') + print(f"Beam-grain intersection took {time.time() - t0}.") t0 = time.time() # Simulate sample-rotation by adding a rotation to the grain misorientation sample_rotation_during_exposure = ensure_torch(sample_rotation_during_exposure) - rotation_vector = torch.einsum('ij,i->j', sample_orientation, sample_rotation_during_exposure) + rotation_vector = torch.einsum( + "ij,i->j", sample_orientation, sample_rotation_during_exposure + ) smeared_misorientation_tensors = torch.linalg.inv( - torch.linalg.inv(self.misori_concentration_tensors[grains_hit]) + torch.outer(rotation_vector, rotation_vector)) + torch.linalg.inv(self.misori_concentration_tensors[grains_hit]) + + torch.outer(rotation_vector, rotation_vector) + ) - #Construct some crystal and geometry information. + # Construct some crystal and geometry information. B = torch.Tensor(form_b_mat(self.phase.unit_cell)) - max_angle = detector._get_wrapping_cone(xray_propagation_direction, np.mean([0, 0, 0])) - self.phase._setup_diffracting_planes(wavelength=wavelength, min_bragg_angle=0.0, max_bragg_angle=max_angle+0.1) - + max_angle = detector._get_wrapping_cone( + xray_propagation_direction, np.mean([0, 0, 0]) + ) + self.phase._setup_diffracting_planes( + wavelength=wavelength, min_bragg_angle=0.0, max_bragg_angle=max_angle + 0.1 + ) + # Get miller indicies and structure factors miller_indices = torch.Tensor(self.phase.miller_indices) if self.phase.structure_factors is not None: - structure_factors = torch.sum(ensure_torch(self.phase.structure_factors) ** 2, axis=1) + structure_factors = torch.sum( + ensure_torch(self.phase.structure_factors) ** 2, axis=1 + ) miller_indices = miller_indices[structure_factors > 1e-6] structure_factors = structure_factors[structure_factors > 1e-6] else: # If no structure factors provided, use uniform intensity (all ones) structure_factors = torch.ones(miller_indices.shape[0]) - + # Get scattering vectors and scattering angle. - h = torch.einsum('ij,hj->hi', B, miller_indices) - p_vectors = torch.einsum('ghi,gij,kj->gkh', torch.eye(3)[None,:,:] - self.strains[grains_hit], self.orientaions[grains_hit], h) + h = torch.einsum("ij,hj->hi", B, miller_indices) + p_vectors = torch.einsum( + "ghi,gij,kj->gkh", + torch.eye(3)[None, :, :] - self.strains[grains_hit], + self.orientaions[grains_hit], + h, + ) p_vectors_norm = torch.linalg.norm(p_vectors, axis=-1) - theta_angle = torch.asin( p_vectors_norm * wavelength / 4 / np.pi ) + theta_angle = torch.asin(p_vectors_norm * wavelength / 4 / np.pi) # Filter out reflections far from the bragg-condition - dp = torch.einsum('i,ghi->gh', xray_propagation_direction, p_vectors) / p_vectors_norm - does_diffract = torch.abs( dp + torch.sin(theta_angle) ) \ - < threshold * (self.max_misorientation + torch.linalg.norm(sample_rotation_during_exposure)) #IDEA: Consider a per-gaussian max misorientation. + dp = ( + torch.einsum("i,ghi->gh", xray_propagation_direction, p_vectors) + / p_vectors_norm + ) + does_diffract = torch.abs(dp + torch.sin(theta_angle)) < threshold * ( + self.max_misorientation + torch.linalg.norm(sample_rotation_during_exposure) + ) # IDEA: Consider a per-gaussian max misorientation. # Exception if no grains diffract. if not torch.any(does_diffract): return torch.zeros(detector.shape) # Select the relevant reflections and flatten the grain- and hkl-indexes. - grain_does_diffract, hkl_does_diffract = torch.where(does_diffract) # Don't ask me what this line does - misori_concentration_tensors = smeared_misorientation_tensors[grain_does_diffract] + grain_does_diffract, hkl_does_diffract = torch.where( + does_diffract + ) # Don't ask me what this line does + misori_concentration_tensors = smeared_misorientation_tensors[ + grain_does_diffract + ] p_vectors = p_vectors[does_diffract] - shape_concentration_tensors = intersection_shape_concentration_tensors[grain_does_diffract] + shape_concentration_tensors = intersection_shape_concentration_tensors[ + grain_does_diffract + ] if verbose: - print(f'Bragg-condition filterin took {time.time()-t0}. ({does_diffract.shape[0]*does_diffract.shape[1]} -> {torch.sum(does_diffract)})') + print( + f"Bragg-condition filterin took {time.time() - t0}. ({does_diffract.shape[0] * does_diffract.shape[1]} -> {torch.sum(does_diffract)})" + ) t0 = time.time() # Find the parameters of the scattered rays. - mean_scattering_directions, log_partialities, normalization_factors, outgoing_beam_divergence_tensor = _get_diffraction_arcsegment( + ( + mean_scattering_directions, + log_partialities, + normalization_factors, + outgoing_beam_divergence_tensor, + ) = _get_diffraction_arcsegment( p_vectors, misori_concentration_tensors, xray_propagation_direction, @@ -203,60 +453,111 @@ def diffract( ) if verbose: - print(f'Reciprocal space part took {time.time()-t0}') + print(f"Reciprocal space part took {time.time() - t0}") t0 = time.time() # Splat grain realspace shapes (#NOTE Consider using the non-strained non-azimuthally shifted directions to simplify gradients) - detectorspace_grainshape_projections, projected_thicknes_scale_factors = self.splat_grainshapes( - mean_scattering_directions, shape_concentration_tensors, W, pixellengths,) + detectorspace_grainshape_projections, projected_thicknes_scale_factors = ( + self.splat_grainshapes( + mean_scattering_directions, + shape_concentration_tensors, + W, + pixellengths, + ) + ) if verbose: - print(f'Realspace proj took {time.time()-t0}') + print(f"Realspace proj took {time.time() - t0}") t0 = time.time() # Ray-trace onto detector plane pos = intersection_pos[grain_does_diffract] - ray_lengths = torch.einsum('xi,i->x', detector_origin[None, :] - pos, detector_norm) / torch.einsum('xi,i->x', mean_scattering_directions, detector_norm) - point_of_detector_intersection = pos + ray_lengths[:,None] * mean_scattering_directions - uv_coords = torch.einsum('xi,vi,v->xv',point_of_detector_intersection - detector_origin[None, :], W, 1/pixellengths) + ray_lengths = torch.einsum( + "xi,i->x", detector_origin[None, :] - pos, detector_norm + ) / torch.einsum("xi,i->x", mean_scattering_directions, detector_norm) + point_of_detector_intersection = ( + pos + ray_lengths[:, None] * mean_scattering_directions + ) + uv_coords = torch.einsum( + "xi,vi,v->xv", + point_of_detector_intersection - detector_origin[None, :], + W, + 1 / pixellengths, + ) # Smearing due to angular divergence of the scattered beam. - W_scaled = W * 1 / pixellengths[:, None] #TODO I think this assumes orthorgonal pixel directions. - divergence_smearing_tensor = torch.einsum('ui,xij,vj->xuv', W_scaled, outgoing_beam_divergence_tensor * ray_lengths[:, None, None]**2, W_scaled) - detspace_splat_concentration = torch.linalg.inv( torch.linalg.inv(detectorspace_grainshape_projections) + divergence_smearing_tensor) - intensity_spread_out_factor = torch.sqrt( torch.linalg.det(detspace_splat_concentration) / torch.linalg.det(detectorspace_grainshape_projections) ) + W_scaled = ( + W * 1 / pixellengths[:, None] + ) # TODO I think this assumes orthorgonal pixel directions. + divergence_smearing_tensor = torch.einsum( + "ui,xij,vj->xuv", + W_scaled, + outgoing_beam_divergence_tensor * ray_lengths[:, None, None] ** 2, + W_scaled, + ) + detspace_splat_concentration = torch.linalg.inv( + torch.linalg.inv(detectorspace_grainshape_projections) + + divergence_smearing_tensor + ) + intensity_spread_out_factor = torch.sqrt( + torch.linalg.det(detspace_splat_concentration) + / torch.linalg.det(detectorspace_grainshape_projections) + ) # Collect all intensity modifying factors - polarization_factors = _polarization(mean_scattering_directions, beam.polarization_vector) - solid_angle_factor = torch.abs(torch.einsum('xi,i->x',mean_scattering_directions, detector_norm)) - scalefactors = structure_factors[hkl_does_diffract] * projected_thicknes_scale_factors * np.exp(log_partialities) * normalization_factors * intensity_spread_out_factor\ - * beam_intensity_factors[grain_does_diffract]*polarization_factors*solid_angle_factor + polarization_factors = _polarization( + mean_scattering_directions, beam.polarization_vector + ) + solid_angle_factor = torch.abs( + torch.einsum("xi,i->x", mean_scattering_directions, detector_norm) + ) + scalefactors = ( + structure_factors[hkl_does_diffract] + * projected_thicknes_scale_factors + * np.exp(log_partialities) + * normalization_factors + * intensity_spread_out_factor + * beam_intensity_factors[grain_does_diffract] + * polarization_factors + * solid_angle_factor + ) # Discard reflections too far out on the rocking curve. - does_diffract = log_partialities > - 1 * threshold + does_diffract = log_partialities > -1 * threshold if verbose: - print(f'Raytracing took {time.time()-t0}') + print(f"Raytracing took {time.time() - t0}") t0 = time.time() n_batches = torch.sum(does_diffract) // peaks_batch_size + 1 f = torch.zeros(detector.shape) for peaks_batch in range(n_batches): - f += detector.render_gaussian_splats( - uv_coords[does_diffract][peaks_batch*peaks_batch_size:(peaks_batch+1)*peaks_batch_size], - scalefactors[does_diffract][peaks_batch*peaks_batch_size:(peaks_batch+1)*peaks_batch_size], - detspace_splat_concentration[does_diffract][peaks_batch*peaks_batch_size:(peaks_batch+1)*peaks_batch_size], - threshold + log_partialities[does_diffract][peaks_batch*peaks_batch_size:(peaks_batch+1)*peaks_batch_size], + uv_coords[does_diffract][ + peaks_batch * peaks_batch_size : (peaks_batch + 1) + * peaks_batch_size + ], + scalefactors[does_diffract][ + peaks_batch * peaks_batch_size : (peaks_batch + 1) + * peaks_batch_size + ], + detspace_splat_concentration[does_diffract][ + peaks_batch * peaks_batch_size : (peaks_batch + 1) + * peaks_batch_size + ], + threshold + + log_partialities[does_diffract][ + peaks_batch * peaks_batch_size : (peaks_batch + 1) + * peaks_batch_size + ], ) if verbose: - print(f'Rasterization took {time.time()-t0}') + print(f"Rasterization took {time.time() - t0}") t0 = time.time() return f - def splat_grainshapes( self, @@ -265,7 +566,7 @@ def splat_grainshapes( W: Tensor, pixellengths: Tensor, ): - """ Project the labratory space shape-concentration-tensors of a range of grains along a the scattering directions + """Project the labratory space shape-concentration-tensors of a range of grains along a the scattering directions into 2D detector pixels space. Parameters @@ -278,39 +579,50 @@ def splat_grainshapes( Pixel-direction unit vectors stacked, shape ``(2, 3)`` pixellengths : Tensor Pixel lengths, shape ``(2, 3)`` - + Returns ------- projected_shape_pixelunits : Tensor - Concentarion tensor of the projected grainshape in detector pixel units, shape ``(N, 2, 2)`` + Concentarion tensor of the projected grainshape in detector pixel units, shape ``(N, 2, 2)`` projected_thicknes_scale_factors : Tensor Intensity scaling factor due the projected thickness of the grain, shape ``(N,)`` """ # grain_volume = torch.sqrt(1/torch.linalg.det(shape_concentration_tensors)) - dSd = torch.einsum('xi,xij,xj->x', mean_scattering_directions, shape_concentration_tensors, mean_scattering_directions) - - inner_term = shape_concentration_tensors - torch.einsum( - 'xij,xj,xk,xkl->xil', - shape_concentration_tensors, - mean_scattering_directions, + dSd = torch.einsum( + "xi,xij,xj->x", mean_scattering_directions, shape_concentration_tensors, - ) / dSd[:, None, None] - + mean_scattering_directions, + ) + + inner_term = ( + shape_concentration_tensors + - torch.einsum( + "xij,xj,xk,xkl->xil", + shape_concentration_tensors, + mean_scattering_directions, + mean_scattering_directions, + shape_concentration_tensors, + ) + / dSd[:, None, None] + ) + W_scaled = W * pixellengths[:, None] projected_shape_pixelunits = torch.einsum( - 'ui,xij,vj->xuv', W_scaled, inner_term, W_scaled, + "ui,xij,vj->xuv", + W_scaled, + inner_term, + W_scaled, ) - return projected_shape_pixelunits, 1/torch.sqrt(dSd) - + return projected_shape_pixelunits, 1 / torch.sqrt(dSd) def transform( - self, - rigid_body_motion : RigidBodyMotion, - time : float = 1.0, - ): + self, + rigid_body_motion: RigidBodyMotion, + time: float = 1.0, + ): """Transform the polycrystal by performing a rigid body motion. This updates all the sample-information in-place. @@ -331,19 +643,27 @@ def transform( translation_vector = rigid_body_motion.translation * time # Rotate vectors: - self.positions = torch.einsum('ij,gj->gi', Rot_mat, self.positions-rigid_body_motion.origin[None,:])+rigid_body_motion.origin[None,:] + self.positions = ( + torch.einsum( + "ij,gj->gi", Rot_mat, self.positions - rigid_body_motion.origin[None, :] + ) + + rigid_body_motion.origin[None, :] + ) # Rotate lattices - self.orientaions = torch.einsum('ij,gjk->gik', Rot_mat, self.orientaions) + self.orientaions = torch.einsum("ij,gjk->gik", Rot_mat, self.orientaions) - #Rotate tensors - self.shape_concentration_tensors = torch.einsum('ij,gjk,lk ->gil', Rot_mat, self.shape_concentration_tensors, Rot_mat) - self.misori_concentration_tensors = torch.einsum('ij,gjk,lk ->gil', Rot_mat, self.misori_concentration_tensors, Rot_mat) - self.strains = torch.einsum('ij,gjk,lk ->gil', Rot_mat, self.strains, Rot_mat) - - #Translate - self.positions = self.positions + translation_vector[None, :] + # Rotate tensors + self.shape_concentration_tensors = torch.einsum( + "ij,gjk,lk ->gil", Rot_mat, self.shape_concentration_tensors, Rot_mat + ) + self.misori_concentration_tensors = torch.einsum( + "ij,gjk,lk ->gil", Rot_mat, self.misori_concentration_tensors, Rot_mat + ) + self.strains = torch.einsum("ij,gjk,lk ->gil", Rot_mat, self.strains, Rot_mat) + # Translate + self.positions = self.positions + translation_vector[None, :] # ------------------------------------------------------------------------------------------ # The methods below here are for computing polefigures, not needed for diffraction patterns. @@ -355,38 +675,48 @@ def render_polefigure( both_hemispheres: bool = False, max_misorientation: float = 0.1, ): - + # Make coordinate arrays if both_hemispheres: - polar, azim = np.meshgrid(np.linspace(0, np.pi, int(180//resolution_in_degrees)+1), - np.linspace(0, 2*np.pi, int(360//resolution_in_degrees)+1)) + polar, azim = np.meshgrid( + np.linspace(0, np.pi, int(180 // resolution_in_degrees) + 1), + np.linspace(0, 2 * np.pi, int(360 // resolution_in_degrees) + 1), + ) else: - polar, azim = np.meshgrid(np.linspace(0, np.pi/2, int(90//resolution_in_degrees)+1), - np.linspace(0, 2*np.pi, int(360//resolution_in_degrees)+1)) - - y_map = torch.tensor(np.stack([ - np.sin(polar) * np.cos(azim), - np.sin(polar) * np.sin(azim), - np.cos(polar) - ], axis=-1)) - + polar, azim = np.meshgrid( + np.linspace(0, np.pi / 2, int(90 // resolution_in_degrees) + 1), + np.linspace(0, 2 * np.pi, int(360 // resolution_in_degrees) + 1), + ) + + y_map = torch.tensor( + np.stack( + [ + np.sin(polar) * np.cos(azim), + np.sin(polar) * np.sin(azim), + np.cos(polar), + ], + axis=-1, + ) + ) + p, scale, T_proj = self.splat_onto_polefigure(hkl) patch_size = 16 - + f = self.rasterize_on_unitvector_map( y_map, p, scale, T_proj, - max_angle= 3*max_misorientation + (resolution_in_degrees*np.pi/180)*patch_size/2, + max_angle=3 * max_misorientation + + (resolution_in_degrees * np.pi / 180) * patch_size / 2, ) return f, polar, azim def splat_onto_polefigure( - self, - hkl: tuple[int], - ): + self, + hkl: tuple[int], + ): # A = form_a_mat(self.phase.unit_cell) # WARNING! xfab uses non-compatible A- and B-matrices. The B-matrix is considered the correct one. # B = 2 * np.pi * np.linalg.inv(A).T @@ -394,7 +724,7 @@ def splat_onto_polefigure( h = torch.tensor(B @ hkl) h = h / torch.linalg.norm(h) - levi_cita_symbol = np.zeros((3,3,3)) + levi_cita_symbol = np.zeros((3, 3, 3)) levi_cita_symbol[0, 1, 2] = 1 levi_cita_symbol[1, 2, 0] = 1 levi_cita_symbol[2, 0, 1] = 1 @@ -405,69 +735,91 @@ def splat_onto_polefigure( # TODO reduce the number of symmetries evaluated for low-multiplicity peaks n_symmetries = len(self.phase.rot) - + volumes = torch.sqrt(torch.linalg.det(self.shape_concentration_tensors)) - p_vectors = torch.einsum('gij,sjk,k->gsi', self.orientaions, ensure_torch(self.phase.rot), h) - + p_vectors = torch.einsum( + "gij,sjk,k->gsi", self.orientaions, ensure_torch(self.phase.rot), h + ) + # This is the trick: - pTp = torch.einsum('gsi,gij,gsj->gs', p_vectors, self.misori_concentration_tensors, p_vectors) - inner_part = self.misori_concentration_tensors[:, None, :, :] - torch.einsum( - 'gij,gsj,gsk,gkl->gsil', - self.misori_concentration_tensors, - p_vectors, - p_vectors, - self.misori_concentration_tensors, - ) / pTp[:, :, None, None] + pTp = torch.einsum( + "gsi,gij,gsj->gs", p_vectors, self.misori_concentration_tensors, p_vectors + ) + inner_part = ( + self.misori_concentration_tensors[:, None, :, :] + - torch.einsum( + "gij,gsj,gsk,gkl->gsil", + self.misori_concentration_tensors, + p_vectors, + p_vectors, + self.misori_concentration_tensors, + ) + / pTp[:, :, None, None] + ) projected_misorientation = torch.einsum( - 'gsj,ijk,gsil,lmn,gsm->gskn', + "gsj,ijk,gsil,lmn,gsm->gskn", p_vectors, levi_cita_symbol, inner_part, levi_cita_symbol, p_vectors, ) - - scale = 1 / n_symmetries / torch.sum(volumes) * volumes[:, None] * 2 * torch.sqrt(torch.linalg.det(self.misori_concentration_tensors))[:, None] / np.sqrt( pTp ) + + scale = ( + 1 + / n_symmetries + / torch.sum(volumes) + * volumes[:, None] + * 2 + * torch.sqrt(torch.linalg.det(self.misori_concentration_tensors))[:, None] + / np.sqrt(pTp) + ) return p_vectors, scale, projected_misorientation - + def rasterize_on_unitvector_map( self, - y : Tensor, - p : Tensor, - scale : Tensor, - T_proj : Tensor, + y: Tensor, + p: Tensor, + scale: Tensor, + T_proj: Tensor, max_angle: float, patch_size: int = 16, ): - + shape = y.shape[:2] min_dp = np.cos(max_angle) - n_patches_dim1 = (shape[0]-1)//patch_size+1 - n_patches_dim2 = (shape[1]-1)//patch_size+1 + n_patches_dim1 = (shape[0] - 1) // patch_size + 1 + n_patches_dim2 = (shape[1] - 1) // patch_size + 1 # Rasterization f = torch.zeros(shape) for patch_index_1 in range(n_patches_dim1): for patch_index_2 in range(n_patches_dim2): - # Figure out what splat lie in this pole figure patch - y_patch = y[patch_size*patch_index_1:patch_size*(patch_index_1+1), - patch_size*patch_index_2:patch_size*(patch_index_2+1)] + y_patch = y[ + patch_size * patch_index_1 : patch_size * (patch_index_1 + 1), + patch_size * patch_index_2 : patch_size * (patch_index_2 + 1), + ] patch_mean = torch.mean(y_patch, axis=(0, 1)) patch_mean_y = patch_mean / torch.linalg.norm(patch_mean) - include_index = torch.abs(torch.einsum('gsj,j->gs', p, patch_mean_y)) > min_dp - # If none, continue + include_index = ( + torch.abs(torch.einsum("gsj,j->gs", p, patch_mean_y)) > min_dp + ) + # If none, continue if not torch.any(include_index): continue # Evaluate gaussians - arg = -torch.einsum('pai,xij,paj->xpa', y_patch, T_proj[include_index], y_patch) + arg = -torch.einsum( + "pai,xij,paj->xpa", y_patch, T_proj[include_index], y_patch + ) vals = torch.exp(arg) * scale[include_index, np.newaxis, np.newaxis] - - f[patch_size*patch_index_1:patch_size*(patch_index_1+1), - patch_size*patch_index_2:patch_size*(patch_index_2+1)]\ - += torch.sum(vals, axis=0) + + f[ + patch_size * patch_index_1 : patch_size * (patch_index_1 + 1), + patch_size * patch_index_2 : patch_size * (patch_index_2 + 1), + ] += torch.sum(vals, axis=0) return f
      -
    • - xrd_simulator.laue + xrd_simulator.gaussian_crystal_model
    • diff --git a/docs/index.html b/docs/index.html index 2a7dac4..bf7f243 100644 --- a/docs/index.html +++ b/docs/index.html @@ -19,6 +19,7 @@ + @@ -124,6 +125,7 @@
    • Beam.load()
    • +
    • GaussianBeam
    • detector
    • @@ -171,13 +174,30 @@ +
    • gaussian_crystal_model +
    • templates
    • -
    • laue
    • @@ -206,9 +226,7 @@
      -
      -
      -https://github.com/FABLE-3DXRD/xrd_simulator/blob/main/docs/source/images/logo.png?raw=true + https://github.com/FABLE-3DXRD/xrd_simulator/blob/main/docs/source/images/logo.png?raw=true https://img.shields.io/pypi/pyversions/xrd-simulator.svg? https://github.com/FABLE-3DXRD/xrd_simulator/actions/workflows/python-package-run-tests-linux-py313.yml/badge.svg? @@ -582,7 +600,7 @@

      Documentation
      -diffract(beam: Beam, rigid_body_motion: RigidBodyMotion, min_bragg_angle: float = 0, max_bragg_angle: float = 1.5707963267948966, detector: Detector | None = None, verbose: bool = True) → Dict[str, Tensor | list][source]
      +diffract(beam: Beam, rigid_body_motion: RigidBodyMotion, min_bragg_angle: float = 0, max_bragg_angle: float = 1.5707963267948966, detector: Detector | None = None, verbose: bool = True) → dict[str, Tensor | list][source]

      Compute diffraction from the rotating and translating polycrystal.

      Simulates diffraction while the sample is illuminated by an X-ray beam. Returns peaks that can be rendered on a detector.

      @@ -1264,6 +1282,30 @@

      Documentation +
      +class xrd_simulator.beam.GaussianBeam(beam_centroid_position, xray_propagation_direction, wavelength, polarization_vector, long_axis_width=None, long_axis_direction=None, short_axis_width=None, short_axis_direction=None)[source]
      +

      Represents a monochromatic X-ray beam with a 2D gaussian cross-section.

      +
      +
      Parameters:
      +
        +
      • beam_centroid_position (torch.Tensor or numpy.ndarray) – A point on the center-line of the beam. (3,).

      • +
      • xray_propagation_direction (torch.Tensor or numpy.ndarray) – Propagation direction of X-rays, shape (3,).

      • +
      • wavelength (float) – X-ray wavelength in units of angstrom.

      • +
      • polarization_vector (torch.Tensor or numpy.ndarray) – Beam linear polarization unit vector, shape (3,). Must be +orthogonal to the X-ray propagation direction.

      • +
      • long_axis_width (float) – Width of the beam along the longest direction.

      • +
      • long_axis_direction (torch.Tensor or numpy.ndarray) – Direction of the major-axis of the beam, shape (3,). Must be +orthogonal to the X-ray propagation direction. If not given, the +beam is assumed circular with a width given by long_axis_width.

      • +
      • long_axis_width – Width of the beam along the shortest direction.

      • +
      • long_axis_direction – Direction of the minor-axis of the beam, shape (3,). Must be +orthogonal to the X-ray propagation direction.

      • +
      +
      +
      +
      +

      detector

      @@ -1660,6 +1702,32 @@

      Documentation +
      +render_gaussian_splats(uv_corrds: Tensor, scale_factors: Tensor, concentration_tensors: Tensor, threshold: Tensor, patch_size: int = 32)[source]
      +

      Basic 2D Gaussian rasterizer. Each gaussian has the expression:

      +
      +\[f = s \exp(-[u-u_0, v-v_0] S [u-u_0, v-v_0]^T).\]
      +
      +
      Parameters:
      +
        +
      • uv_corrds (Tensor) – Centroid pixel coordinates, shape (N, 2)

      • +
      • scale_factors (Tensor) – Intensity scale factors, shape (N,)

      • +
      • concentration_tensors (Tensor) – Shape concentration tensors, shape (N, 2, 2)

      • +
      • threshold (Tensor, shape (N,)) – Per gaussian threshold value used to truncate the Gaussians. Gaussians are set to zero +when the value falls bellow scale_factors*torch.exp(-theshold).

      • +
      • patch_size (int) – For evaluation the detector is split into patches of shize patch_size by patch_size.

      • +
      +
      +
      Returns:
      +

      detector_image – Detector image, shape self.shape.

      +
      +
      Return type:
      +

      Tensor

      +
      +
      +
      +
      peaks_detector_intersection(peaks_dict: Dict[str, Tensor | List[str]], frames_to_render: int = 1, verbose: bool = True) → Dict[str, Tensor | List[str]][source]
      @@ -1984,6 +2052,320 @@

      Documentation +

      gaussian_crystal_model

      +

      Gaussian subgrain models for polycrystalline X-ray diffraction simulation.

      +

      Represent a polycrystal as a collection of subgrains with Gaussian real-space +density profiles and narrow Gaussian orientation distributions. Each subgrain +is defined by its crystallographic phase, centroid, shape tensor, mean lattice +orientation, misorientation tensor, and uniform strain tensor. Both the shape +and orientation spread may be anisotropic.

      +

      GaussianSubgrain describes an individual subgrain, while +GaussianPolycrystal assembles subgrains and renders diffraction +patterns using a GaussianBeam and a +Detector.

      +

      The forward model computes beam-subgrain intersections, reflection +partialities, and scattered-beam angular distributions. Illuminated subgrain +shapes are projected onto the detector and combined with mosaicity-induced +angular broadening to form two-dimensional Gaussian spots. These contributions +are rasterized and summed into a detector intensity image. Small sample +rotations during an exposure are approximated by additional Gaussian +orientation broadening.

      +

      The module also supports rigid-body transformations of the polycrystal and +pole-figure rendering.

      +

      Notes

      +

      The current implementation supports single-phase polycrystals and assumes a +monochromatic, collimated incident beam. Orientation distributions are +approximated in the tangent space about each subgrain’s mean orientation, +requiring small misorientations. Uniform strain modifies reciprocal-lattice +vectors, but strain-distribution broadening is not included within an +individual subgrain. A distribution of strains can instead be approximated by +combining appropriately weighted diffraction patterns from subgrain +populations with different uniform strain tensors.

      +
      +

      See also

      +
      +
      Gaussian model: theory and references

      Scattering geometry, Gaussian orientation distributions, reciprocal-space +maps, model assumptions, illustrative tests, and references.

      +
      +
      +
      +

      Examples

      +

      Simulate a static diffraction pattern from 2000 Gaussian quartz subgrains with +random positions, orientations, shapes, and anisotropic orientation spreads.

      +

      Run the example from the repository root with xrd_simulator installed, so +that tests/data/quartz.cif is available. Real-space lengths are in +micrometres, the wavelength and unit-cell lengths are in angstroms, and +unit-cell angles are in degrees. Orientation-spread parameters are in radians.

      +

      First, define the beam, detector, and crystallographic phase:

      +
      import numpy as np
      +import matplotlib.pyplot as plt
      +from scipy.spatial.transform import Rotation as R
      +
      +from xrd_simulator.beam import GaussianBeam
      +from xrd_simulator.detector import Detector
      +from xrd_simulator.phase import Phase
      +from xrd_simulator.gaussian_crystal_model import (
      +    GaussianSubgrain,
      +    GaussianPolycrystal,
      +)
      +
      +rng = np.random.default_rng(0)
      +
      +gaussian_beam = GaussianBeam(
      +    xray_propagation_direction=np.array([1.0, 0.0, 0.0]),
      +    beam_centroid_position=np.array([0.0, 0.0, 0.0]),
      +    wavelength=0.28523,
      +    polarization_vector=np.array([0.0, 1.0, 0.0]),
      +    long_axis_width=300.0,
      +    long_axis_direction=np.array([0.0, 0.0, 1.0]),
      +    short_axis_width=50.0,
      +    short_axis_direction=np.array([0.0, 1.0, 0.0]),
      +)
      +
      +detector = Detector(
      +    det_corner_0=np.array([142938.3, -38400.0, -38400.0]),
      +    det_corner_1=np.array([142938.3, 38400.0, -38400.0]),
      +    det_corner_2=np.array([142938.3, -38400.0, 38400.0]),
      +    pixel_size=(25.0, 25.0),
      +    gaussian_sigma=1.0,
      +    max_gaussian_kernel_radius=5,
      +)
      +
      +quartz = Phase(
      +    unit_cell=[4.926, 4.926, 5.4189, 90.0, 90.0, 120.0],
      +    sgname="P3221",
      +    path_to_cif_file="tests/data/quartz.cif",
      +)
      +
      +
      +

      Next, build the polycrystal. The helper generates a symmetric positive-definite +width tensor with a randomly oriented symmetry axis. Its eigenvalues are +axis_1**2, axis_1**2, and axis_2**2. Shape-width parameters range +from 50 to 200 micrometres, and misorientation-width parameters range from +0.01 to 0.03 radians. The distinction between these width parameters and +standard deviations is explained on the theory page.

      +
      def make_random_tensor(axis_1, axis_2):
      +    random_direction = rng.normal(size=3)
      +    random_direction /= np.linalg.norm(random_direction)
      +    return (
      +        axis_1**2 * np.eye(3)
      +        + (axis_2**2 - axis_1**2)
      +        * np.outer(random_direction, random_direction)
      +    )
      +
      +grain_list = []
      +
      +for _ in range(2000):
      +    position = rng.uniform(-500.0, 500.0, size=3)
      +    shape_tensor = make_random_tensor(
      +        rng.uniform(50.0, 200.0),
      +        rng.uniform(50.0, 200.0),
      +    )
      +    orientation = R.random(random_state=rng).as_matrix()
      +    misorientation_tensor = make_random_tensor(
      +        rng.uniform(0.01, 0.03),
      +        rng.uniform(0.01, 0.03),
      +    )
      +
      +    grain_list.append(
      +        GaussianSubgrain(
      +            phase=quartz,
      +            position=position,
      +            shape_tensor=shape_tensor,
      +            orientation=orientation,
      +            misorientation_tensor=misorientation_tensor,
      +            strain_tensor=np.zeros((3, 3)),
      +        )
      +    )
      +
      +gaussian_polycrystal = GaussianPolycrystal(
      +    grain_list,
      +    max_misorientation=0.03,
      +    max_grain_size=200.0,
      +)
      +
      +
      +

      Compute a diffraction pattern without sample rotation during the exposure:

      +
      f = gaussian_polycrystal.diffract(
      +    beam=gaussian_beam,
      +    detector=detector,
      +    verbose=True,
      +    threshold=10.0,
      +)
      +
      +
      +

      Finally, plot the full detector image and a magnified region on the same +logarithmic intensity scale:

      +
      log_intensity = np.log10(f.detach().cpu().numpy() + 1e-10)
      +
      +fig, axs = plt.subplots(
      +    1, 3,
      +    figsize=(8, 4),
      +    gridspec_kw={"width_ratios": (1, 1, 0.1)},
      +    constrained_layout=True,
      +)
      +axs[0].imshow(log_intensity, cmap="jet", vmin=3, vmax=6)
      +img = axs[1].imshow(log_intensity, cmap="jet", vmin=3, vmax=6)
      +axs[0].set_title("Full detector")
      +axs[1].set_title("Magnified region")
      +axs[1].set_xlim(1500, 2200)
      +axs[1].set_ylim(2200, 1500)
      +fig.colorbar(img, cax=axs[2], label=r"$\log_{10}(I + 10^{-10})$")
      +plt.show()
      +
      +
      +
      +Simulated quartz diffraction pattern and a magnified detector region. + +
      +

      Representative output from the Gaussian workflow. The precise spot pattern +depends on the random subgrain realization. This image is retained from the +original workflow and need not match the seeded realization above exactly.

      +
      +
      +
      +
      +class xrd_simulator.gaussian_crystal_model.GaussianSubgrain(phase: Phase, position: ndarray[Any, dtype[_ScalarType_co]] | Tensor = array([0, 0, 0]), shape_tensor: ndarray[Any, dtype[_ScalarType_co]] | Tensor = array([[1., 0., 0.], [0., 1., 0.], [0., 0., 1.]]), orientation: ndarray[Any, dtype[_ScalarType_co]] | Tensor = array([[1., 0., 0.], [0., 1., 0.], [0., 0., 1.]]), misorientation_tensor: ndarray[Any, dtype[_ScalarType_co]] | Tensor = array([[0.00030625, 0., 0.], [0., 0.00030625, 0.], [0., 0., 0.00030625]]), strain_tensor: ndarray[Any, dtype[_ScalarType_co]] | Tensor = array([[0., 0., 0.], [0., 0., 0.], [0., 0., 0.]]))[source]
      +

      A subgrain has a 3D-gaussian density distribution and a narrow (<=0.1 rad) 3D +orientation distribution function.

      +
      +
      +phase[source]
      +

      Object representing the crystal structure of the grain.

      +
      +
      Type:
      +

      Phase

      +
      +
      +
      + +
      +
      +position[source]
      +

      Position of the grain centroid, shape (3,)

      +
      +
      Type:
      +

      torch.Tensor | np.array

      +
      +
      +
      + +
      +
      +shape_tensor[source]
      +

      Covariance tensor of the real-space grain shape. (3, 3,).

      +
      +
      Type:
      +

      torch.Tensor | np.array

      +
      +
      +
      + +
      +
      +orientation[source]
      +

      Orientation of the centroid of the ODF as a rotation matrix, shape (3, 3,)

      +
      +
      Type:
      +

      torch.Tensor | np.array

      +
      +
      +
      + +
      +
      +misorientation_tensor[source]
      +

      Covariance tensor of the grain orientation in the left-hand tangent space. +aka. laboratory coordinates, shape (3, 3,)

      +
      +
      Type:
      +

      torch.Tensor | np.array

      +
      +
      +
      + +
      +
      +strain_tensor[source]
      +

      Strain tensor in laboratory coordinates, shape (3, 3,)

      +
      +
      Type:
      +

      torch.Tensor | np.array

      +
      +
      +
      + +
      + +
      +
      +class xrd_simulator.gaussian_crystal_model.GaussianPolycrystal(grain_list: list[GaussianSubgrain], max_grain_size: float = 1000.0, max_misorientation: float = 0.1)[source]
      +

      Colection of GaussianSubgrain object representing a polycrystalline sample. +Variables max_grain_size and max_misorientation can be supplied to speed up +diffraction calculations.

      +
      +
      +diffract(beam: GaussianBeam, detector: Detector, sample_orientation: ndarray[Any, dtype[_ScalarType_co]] | Tensor = array([[1., 0., 0.], [0., 1., 0.], [0., 0., 1.]]), sample_translation: ndarray[Any, dtype[_ScalarType_co]] | Tensor = array([0., 0., 0.]), sample_rotation_during_exposure: ndarray[Any, dtype[_ScalarType_co]] | Tensor = array([0., 0., 0.]), verbose=False, threshold=6.0, peaks_batch_size=20000)[source]
      +

      Render a single diffraction pattern.

      +
      +
      Parameters:
      +
        +
      • beam (GaussianBeam) – Object representing the x-ray beam hitting the crystal.

      • +
      • detector (Detector) – Object representing the x-ray detector.

      • +
      • sample_orientation (NDArray, optional) – 3-by-3 Rotation matrix representing the orientation of the sample

      • +
      • sample_translation (NDArray, optional) – 3-vector prepresenting the translation of the sample. (the sample is first +rotated about the origin, then translated)

      • +
      • sample_rotation_during_exposure (NDArray, optional) – 3-vector representing a small rotation carried out during the measurement

      • +
      • verbose (bool, optional) – If True print timing information. Default is False.

      • +
      +
      +
      +
      + +
      +
      +splat_grainshapes(mean_scattering_directions: Tensor, shape_concentration_tensors: Tensor, W: Tensor, pixellengths: Tensor)[source]
      +

      Project the labratory space shape-concentration-tensors of a range of grains along a the scattering directions +into 2D detector pixels space.

      +
      +
      Parameters:
      +
        +
      • mean_scattering_directions (Tensor) – Scattering direction unit vectors, shape (N, 3)

      • +
      • shape_concentration_tensors (Tensor) – Shape concentration tensors in laboratory coordinates, shape (N, 3, 3)

      • +
      • W (Tensor) – Pixel-direction unit vectors stacked, shape (2, 3)

      • +
      • pixellengths (Tensor) – Pixel lengths, shape (2, 3)

      • +
      +
      +
      Returns:
      +

        +
      • projected_shape_pixelunits (Tensor) – Concentarion tensor of the projected grainshape in detector pixel units, shape (N, 2, 2)

      • +
      • projected_thicknes_scale_factors (Tensor) – Intensity scaling factor due the projected thickness of the grain, shape (N,)

      • +
      +

      +
      +
      +
      + +
      +
      +transform(rigid_body_motion: RigidBodyMotion, time: float = 1.0)[source]
      +

      Transform the polycrystal by performing a rigid body motion.

      +

      This updates all the sample-information in-place.

      +
      +
      Parameters:
      +
        +
      • rigid_body_motion (RigidBodyMotion) – Rigid body motion object describing the polycrystal transformation +as a function of time on the domain time=[0, 1].

      • +
      • time (float) – Time between [0, 1] at which to call the rigid body motion.

      • +
      +
      +
      +
      + +
      +

      templates

      @@ -2222,13 +2604,6 @@

      Documentation -

      laue

      -

      Collection of functions for solving time-dependent Laue equations.

      -

      This module provides functions for arbitrary rigid body motions. It is mainly -used internally by the xrd_simulator.polycrystal.Polycrystal. However, -for the advanced user, access to these functions may be of interest.

      diff --git a/docs/objects.inv b/docs/objects.inv index cc264cf..30ad941 100644 Binary files a/docs/objects.inv and b/docs/objects.inv differ diff --git a/docs/py-modindex.html b/docs/py-modindex.html index cc701cb..d1310f5 100644 --- a/docs/py-modindex.html +++ b/docs/py-modindex.html @@ -101,7 +101,7 @@

      Python Module Index

          - xrd_simulator.laue + xrd_simulator.gaussian_crystal_model