download as script · edit page

A scalar orientation function assigns one number to each orientation. A vector-valued orientation function assigns an array of numbers instead:

\[f\colon \mathrm{SO}(3) \to \mathbb{R}^n.\]

This is useful when several orientation-dependent quantities share the same nodes, symmetries and approximation method. MTEX stores the components together so that they can be interpolated and manipulated as one array of functions.

How evaluations are arranged

The nodes come first in an evaluated array. The dimensions of the vector-valued SO3Fun follow them.

Consider four nodes \(R_1,\ldots,R_4\) and six scalar functions arranged as a \(3\mathbin{\times}2\) array. Evaluation returns an array \(F\) of shape \((4, 3, 2)\):

\[F[:,:,0]=\begin{pmatrix} f_1(R_1)&f_2(R_1)&f_3(R_1)\\ f_1(R_2)&f_2(R_2)&f_3(R_2)\\ f_1(R_3)&f_2(R_3)&f_3(R_3)\\ f_1(R_4)&f_2(R_4)&f_3(R_4) \end{pmatrix}, \qquad F[:,:,1]=\begin{pmatrix} f_4(R_1)&f_5(R_1)&f_6(R_1)\\ f_4(R_2)&f_5(R_2)&f_6(R_2)\\ f_4(R_3)&f_5(R_3)&f_6(R_3)\\ f_4(R_4)&f_5(R_4)&f_6(R_4) \end{pmatrix}.\]

The harmonic coefficients are arranged the other way round: the last dimension of the coefficient array stores all Fourier coefficients of one scalar function, and the function array’s dimensions come before it. This coefficient dimension is an implementation detail. Indexing and shape report only the \(3\mathbin{\times}2\) function array seen by the user.

Interpolate several components from samples

First construct a list of approximately \(10^5\) orientations. The crystal and specimen symmetries determine the fundamental region sampled by the grid.

import warnings

import numpy as np

from mtex import *

nodes = equispacedSO3Grid(crystalFrame(), specimenFrame(), points=1e5)
nodes = nodes.flatten()

The first value column samples the Dubna model function. The second is a different scalar quantity evaluated at the same nodes. Assigning the Dubna crystal symmetry to the nodes makes their symmetry agree with the sampled model.

plottingConvention.default('y↑→x')
y = np.stack([SO3Fun.dubna(rotation(nodes)), (nodes.a * nodes.b).astype(complex) ** (1 / 4)], axis=-1)
nodes = orientation(rotation(nodes), SO3Fun.dubna().CS, nodes.SS)

SO3FunHarmonic.interpolate fits both columns in one call. The result is an array of two harmonic functions. Here the bandwidth is 48 and LSQR is limited to 10 iterations.

SO3F1 = SO3FunHarmonic.interpolate(nodes, y, maxIter=10, bandwidth=48)
SO3F1
SO3FunHarmonic (Quartz → y↑→x)
  size     : 2
  bandwidth: 48
  isReal   : false

The deliberately short iteration limit leaves the fit short of its optimum. Increase maxIter when convergence, rather than a compact teaching run, is required.

Evaluating four orientations demonstrates the layout directly: four rows are nodes and two columns are components.

SO3F1.eval(nodes[:4]).shape
(4, 2)

The generic interp route can instead build an SO3FunRBF from one value column.

SO3F2 = interp(nodes, np.real(y[:, 0]))
SO3F2
SO3FunRBF (Quartz → y↑→x)
  multimodal components
    kernel: de la Vallee Poussin, halfwidth 5°
    center: 20040 orientations
    weight: 1.001
    Bunge Euler angles in degree
    phi1   Phi  phi2    weight
      90  2.49   210  5.76e-06
      90  2.49   215  3.69e-06
      90  2.49   220  2.78e-06
      90  2.49   225   5.1e-06
      90  2.49   230  5.88e-06
       ⋮     ⋮     ⋮         ⋮
     7.5  97.1   7.5  1.53e-06
    52.5  97.1   292   1.2e-05
    57.5  97.1   298  8.75e-06
    62.5  97.1   302  5.43e-06
    67.5  97.1   308  6.34e-06

This interp syntax is available only for a univariate, or scalar-valued, function. Use the explicit harmonic interpolation above when several sampled components should remain together.

Construct a function from a Python function

Quadrature provides a second route when values can be evaluated at any orientation. The function below returns four columns for every input orientation.

f = lambda rot: np.stack([np.exp(rot.a + rot.b + rot.c) + 50 * (rot.b - np.cos(np.pi / 3)) ** 3 * (rot.b - np.cos(np.pi / 3) > 0),
                          rot.a, rot.b, rot.c], axis=-1)

SO3FunHarmonic.quadrature evaluates the function on a quadrature grid. It creates an array of four harmonic functions with bandwidth 50 and the same crystal symmetry as SO3F1.

SO3F3 = SO3FunHarmonic.quadrature(f, SO3F1.CS, bandwidth=50)
SO3F3
SO3FunHarmonic (Quartz → y↑→x)
  size     : 4
  bandwidth: 50

Construct a function from Fourier coefficients

Known Fourier coefficients can be passed directly to the SO3FunHarmonic constructor. Each row of the coefficient matrix becomes one component.

SO3F4 = SO3FunHarmonic(np.eye(10))
SO3F4
SO3FunHarmonic (1 → y↑→x)
  size     : 10
  bandwidth: 1
  isReal   : false

This stores the first ten Wigner-D functions as an array of ten functions. The coefficient dimension is hidden from the reported array size.

Index and reshape components

Function arrays follow ordinary array conventions. They can be concatenated, indexed and reshaped. Concatenating the two- and four-component arrays below produces six components, and indexing selects components two through four.

SO3F5 = SO3FunHarmonic.cat(SO3F1, SO3F3)
SO3F5[1:4]
SO3FunHarmonic (Quartz → y↑→x)
  size     : 3
  bandwidth: 50
  isReal   : false

Transpose and conjugation act on the function array and its Fourier coefficients. The transpose T changes only the array shape, whereas conj(f).T also conjugates complex coefficients.

conj(SO3F1)
SO3F1.T
conj(SO3F1).T
SO3FunHarmonic (Quartz → y↑→x)
  size     : 2 × 1
  bandwidth: 48
  isReal   : false

Standard array queries report the component array dimensions.

len(SO3F1)
2

SO3F3.shape
(4,)

SO3F4 = SO3F4.reshape(2, -1)
SO3F4
SO3FunHarmonic (1 → y↑→x)
  size     : 2 × 5
  bandwidth: 1
  isReal   : false

Integrals and component-wise reductions

With no axis argument, sum integrates every component over \(\mathrm{SO}(3)\) and mean returns its orientation-space mean. The result is a numeric array with the same component shape.

sum(SO3F1)
array([78.9607 +0.j    , 38.7031+16.0773j])

mean(SO3F4)
array([[1.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j],
       [0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j]])

Passing an axis changes the meaning to an ordinary pointwise reduction over the function array. The results below remain SO3FunHarmonic objects.

sum(SO3F1, axis=0)
SO3FunHarmonic (Quartz → y↑→x)
  bandwidth: 48
  mean     : 1.49+0.2036j
  isReal   : false

mean(SO3F4, axis=1)
SO3FunHarmonic (1 → y↑→x)
  size     : 2
  bandwidth: 1
  isReal   : false

Minima and maxima

With a single argument, min and max find a global extremum of every component separately. They return numeric arrays, with the positions, and require real-valued functions. Marking SO3F4 as real enforces the coefficient symmetry required by this search.

SO3F4.isReal = True
min(SO3F4)[0]
array([[ 1.    , -1.7321, -1.2247, -1.732 , -1.2247],
       [-1.7321, -1.2247, -1.732 , -1.2247, -1.7321]])

Passing an axis as min(SO3F, axis=d) instead computes a pointwise minimum along that component axis. The result is again a vector-valued SO3FunHarmonic. Since SO3F4 is \(2\mathbin{\times}5\), the following reduction leaves five functions.

min(SO3F4, axis=0)
SO3FunHarmonic (1 → y↑→x)
  size     : 5
  bandwidth: 1

Pointwise products

The product * of two functions is evaluated pointwise in orientation space. It is not the usual matrix product between the two function arrays. Both operands must have the same symmetries, so the symmetry of SO3F1 is set explicitly before multiplication, and its two functions are arranged as a column so that they multiply the rows of SO3F4.

SO3F1.CS = SO3F4.CS
SO3F1.reshape(2, 1) * SO3F4
SO3FunHarmonic (1 → y↑→x)
  size     : 2 × 5
  bandwidth: 49
  isReal   : false

Inspect all components

plotSpectrum draws the harmonic power spectrum of every component. The four curves show that components of one function array may have different distributions over harmonic degree.

plotSpectrum(SO3F3, lineWidth=2, figSize='small')

An orientation-space section plot displays only the first component of a vector-valued function, with a warning. Select and prepare a component explicitly when another component or its imaginary part is required.

with warnings.catch_warnings():
  warnings.simplefilter('ignore')
  plot(SO3F3)

The three-dimensional plot has the same first-component restriction. The shape therefore describes the real part of the first scalar function, not the magnitude of the four-component array.

with warnings.catch_warnings():
  warnings.simplefilter('ignore')
  plot3d(SO3F3)

A fibre is a one-dimensional family of orientations. In contrast to the section and three-dimensional plots, plotFibre draws every component along the selected beta fibre. Here it draws the real parts, so the four curves can be compared at the same orientations.

plotFibre(SO3F3, fibre.beta(), lineWidth=2)

References

  • P. J. Kostelec and D. N. Rockmore, FFTs on the rotation group, Journal of Fourier Analysis and Applications 14 (2008), 145–179, develops the Fourier representation on \(\mathrm{SO}(3)\) used for each component of an SO3FunHarmonic.

Next

Continue with Rotational Vector Fields to attach three components to specimen or crystal coordinates and account for how those vectors transform with orientation.

Technical Details

The port keeps a list of functions in NumPy’s layout: the coefficients on the last axis of fhat, where MATLAB has them on the first; a list of \(n\) functions has the shape (n,), where MATLAB’s is \(n\times 1\), and multiplying it with a \(2\times5\) array needs the column reshape(2, 1). An axis is counted from zero, MATLAB’s sum(SO3F1, 1) being sum(SO3F1, axis=0); min(f) returns the values with their positions. A function built from coefficients alone has the triclinic crystal frame on its right side where MATLAB’s has the specimen frame, so SO3F1.CS is set to that of SO3F4. Orientations take a frame at construction, so the relabelled nodes are built anew from their rotations, and the Dubna ODF is evaluated at the rotations of the triclinic nodes. The complex fourth root of \(ab\) is taken of complex numbers, as MATLAB’s power of a negative number gives. The function of the quadrature example is built from the quaternion components \(a, b, c\) of a rotation, which depend on which of \(\pm q\) represents it; the port and MATLAB choose differently, so the four functions and their spectra differ from MATLAB’s.

Citing this page. This page is part of the documentation of MTEX, a free and open source MATLAB toolbox for analyzing and modeling crystallographic textures. It was written by The MTEX Developers and is published at https://mtex-toolbox.github.io/SO3FunVectorValued_py.html. If you use MTEX, or reuse text or figures from this page, in your research, please cite

F. Bachmann, R. Hielscher, H. Schaeben: Texture Analysis with MTEX - Free and Open Source Software Toolbox, Solid State Phenomena 160 (2010), 63-68. 10.4028/www.scientific.net/SSP.160.63

BibTeX
@article{bachmann2010mtex,
  author  = {F. Bachmann and R. Hielscher and H. Schaeben},
  title   = {Texture Analysis with MTEX - Free and Open Source Software Toolbox},
  journal = {Solid State Phenomena},
  volume  = {160},
  pages   = {63-68},
  year    = {2010},
  doi     = {10.4028/www.scientific.net/SSP.160.63},
  url     = {https://doi.org/10.4028/www.scientific.net/SSP.160.63}
}

Other papers describing specific MTEX methods are listed under Publications — please cite the one that best fits your application. The MTEX source code is licensed under the GNU General Public License v2.0; the text and figures of this documentation are licensed under CC BY 4.0, which permits reuse — including by automated systems — provided The MTEX Developers and this page are credited.