download as script · edit page

An isotropic material has the same elastic response in every direction. Its stiffness therefore needs only two independent numbers. A general anisotropic stiffness tensor needs as many as 21.

The engineering moduli are different ways to choose those two numbers. They include the shear and bulk moduli, Young’s modulus, Poisson’s ratio, and the Lame constants.

This page starts from one anisotropic crystal and makes an isotropic aggregate from randomly oriented copies. It then shows how to read, compare, and convert the resulting moduli.

import matplotlib.pyplot as plt
import numpy as np
from mtex import *

Start with an anisotropic crystal

Albite is triclinic and is about as anisotropic as a common mineral gets. Its density is in g/cm3, and its stiffness entries are in GPa.

rho = 2.6230

# crystal symmetry and crystal frame
cs = crystalFrame('-1', [8.290, 12.966, 7.151], np.array([91.18, 116.31, 90.14]) * degree, 'x||a*', 'y||b',
                  mineral='An0 Albite 2016')

# stiffness tensor
C = stiffnessTensor(
  [[68.30, 32.20, 30.40, 4.90, -2.30, -0.90],
   [32.20, 184.30, 5.00, -4.40, -7.70, -6.40],
   [30.40, 5.00, 180.00, -9.20, 7.50, -9.40],
   [4.90, -4.40, -9.20, 25.00, -2.40, -7.20],
   [-2.30, -7.70, 7.50, -2.40, 26.90, 0.60],
   [-0.90, -6.40, -9.40, -7.20, 0.60, 33.60]],
  cs, density=rho)

Average a random aggregate

A material made of these crystals in random orientations is isotropic. The orientation distribution function (ODF) records the volume fraction at each orientation. A uniform ODF represents the random orientation distribution used here.

Random orientations do not determine one exact aggregate stiffness. The result also depends on how the grains are arranged. The Voigt model assumes uniform strain and gives an upper bound. The Reuss model assumes uniform stress and gives a lower bound. The Hill estimate is the arithmetic mean of those two tensors.

C_iso_Voigt, C_iso_Reuss, C_iso_Hill = mean(C, uniformODF(C.CS), 'all')
C_iso_Voigt
stiffnessTensor (y↓→x)
  density: 2.623
  unit   : GPa
  rank   : 4 (3 × 3 × 3 × 3)
  tensor in Voigt matrix representation
  118.3333   35.4667   35.4667        0        0        0
   35.4667  118.3333   35.4667        0        0        0
   35.4667   35.4667  118.3333        0        0        0
         0         0         0  41.4333        0        0
         0         0         0        0  41.4333        0
         0         0         0        0        0  41.4333

C_iso_Reuss
stiffnessTensor (y↓→x)
  density: 2.623
  unit   : GPa
  rank   : 4 (3 × 3 × 3 × 3)
  tensor in Voigt matrix representation
  93.8309  34.1599  34.1599        0        0        0
  34.1599  93.8309  34.1599        0        0        0
  34.1599  34.1599  93.8309        0        0        0
        0        0        0  29.8355        0        0
        0        0        0        0  29.8355        0
        0        0        0        0        0  29.8355

C_iso_Hill
stiffnessTensor (y↓→x)
  density: 2.623
  unit   : GPa
  rank   : 4 (3 × 3 × 3 × 3)
  tensor in Voigt matrix representation
  106.0821   34.8133   34.8133        0        0        0
   34.8133  106.0821   34.8133        0        0        0
   34.8133   34.8133  106.0821        0        0        0
         0         0         0  35.6344        0        0
         0         0         0        0  35.6344        0
         0         0         0        0        0  35.6344

See what the average changed

Young’s modulus measures axial stiffness in a chosen loading direction. Compare its directional variation before and after averaging.

newMtexFigure(layout=[1, 2])
plot(C.YoungsModulus(), complete=True, upper=True)
mtexTitle('single albite crystal')

nextAxis()
plot(C_iso_Hill.YoungsModulus(), complete=True, upper=True)
mtexTitle('random aggregate, Hill estimate')

# a common colour range, otherwise the constant map is stretched over noise
setColorRange('equal')
mtexColorbar(title="Young's modulus in GPa")

The single-crystal map changes strongly with direction. The aggregate map is constant because the random orientations remove the directional preference.

Read the elastic moduli

Read four familiar moduli from the Voigt tensor. This tensor is the upper bound for the random aggregate.

G = C_iso_Voigt.shearModulus()
G
41.4333

K = C_iso_Voigt.bulkModulus()
K
63.0889

E = C_iso_Voigt.YoungsModulus(xvector)
E
101.9759

nu = C_iso_Voigt.PoissonRatio()
nu
0.2306

The shear modulus \(G\) measures resistance to shape change at fixed volume. The bulk modulus \(K\) measures resistance to a uniform volume change. Young’s modulus \(E\) relates axial stress to axial strain. Poisson’s ratio \(\nu\) is minus transverse strain divided by axial strain.

YoungsModulus asks for a direction. An isotropic tensor gives the same answer in every direction. That equality is a useful check that the average really is isotropic.

E_direction_check = C_iso_Voigt.YoungsModulus(cat(xvector, zvector))
E_direction_check
array([101.9759, 101.9759])

Tighter bounds from a microstructure assumption

The Voigt and Reuss bounds cannot be improved without more information about the material. The microstructures that attain them are extreme: they are layers of aligned crystals.

A quasihomogeneous material has the same elastic properties in any region much larger than a grain. This extra assumption admits narrower bounds. The bounds are due to Hashin and Shtrikman (1962). The computation below follows Brown (2015).

The calculation searches over isotropic comparison materials. Each candidate is specified by a bulk modulus and a shear modulus.

KMin, KMax = 1, 150  # minimum and maximum bulk moduli
GMin, GMax = 1, 150  # minimum and maximum shear moduli
Ko = np.linspace(KMin, KMax, 300)
Go = np.linspace(GMin, GMax, 300)
G0Mesh, K0Mesh = np.meshgrid(Go, Ko)

For every candidate, HashinShtrikmanModulus computes effective bulk and shear moduli. It also tests the residual stiffness tensor. A positive definite residual identifies a lower-bound candidate. A negative definite residual identifies an upper-bound candidate.

khs, ghs, definite = HashinShtrikmanModulus(C, K0Mesh, G0Mesh)

# largest value in the positive definite region: lower bound
khsLower = khs[definite == 1].max()
ghsLower = ghs[definite == 1].max()

# smallest value in the negative definite region: upper bound
khsUpper = khs[definite == -1].min()
ghsUpper = ghs[definite == -1].min()

Locate the Hashin-Shtrikman bounds

Plot the computed effective modulus for every comparison material. The white circles mark the lower and upper optima.

fig, axes = plt.subplots(1, 2, figsize=(10, 5))
for ax, M, lower, upper, name in zip(axes, [khs, ghs], [khsLower, ghsLower], [khsUpper, ghsUpper],
                                     ['effective bulk modulus', 'effective shear modulus']):
  im = ax.imshow(M, extent=[GMin, GMax, KMin, KMax], origin='lower')
  ax.set_title(name)
  ax.set_xlabel('comparison shear modulus')
  ax.set_ylabel('comparison bulk modulus')
  fig.colorbar(im, ax=ax, shrink=0.8)
  for value in (lower, upper):
    i, j = np.nonzero(M == value)
    ax.plot(Go[j], Ko[i], 'o', markeredgecolor='w', markerfacecolor='none', markeredgewidth=2)

Only the positive and negative definite regions contain valid candidates. The circles sit at the extrema of those two regions, not at arbitrary extrema of the coloured maps.

Compare all three estimates

Collect the Voigt, Reuss, Hill, and Hashin-Shtrikman results.

KReuss = C_iso_Reuss.bulkModulus()
KHill = C_iso_Hill.bulkModulus()
GVoigt = C_iso_Voigt.shearModulus()
GReuss = C_iso_Reuss.shearModulus()
GHill = C_iso_Hill.shearModulus()

print('bulk modulus')
cprintf([K, khsUpper, KHill, khsLower, KReuss], '-Lc', ['Voigt', '+HS', 'Hill', '-HS', 'Reuss'])
print()
print('shear modulus')
cprintf([GVoigt, ghsUpper, GHill, ghsLower, GReuss], '-Lc', ['Voigt', '+HS', 'Hill', '-HS', 'Reuss'])
bulk modulus
  Voigt     +HS    Hill     -HS   Reuss
63.0889  60.326 58.5696  57.107 54.0503

shear modulus
  Voigt     +HS    Hill     -HS   Reuss
41.4333 36.7537 35.6344 32.8495 29.8355

Read the two rows from outside towards the centre. For the bulk modulus, the Voigt and Reuss bounds are 63.1 and 54.1 GPa. They are nine GPa apart. The Hashin-Shtrikman bounds are 60.3 and 57.1 GPa. They are only three GPa apart.

For the shear modulus, the broad interval runs from 41.4 to 29.8 GPa. The Hashin-Shtrikman interval runs from 36.8 to 32.8 GPa. In both rows the Hill average sits inside the narrower pair. This is the reason the Hill estimate usually works.

Bounds on every other modulus follow from these values. Two moduli determine an isotropic material.

The maths behind isotropic stiffness

Any two elastic moduli determine the complete isotropic tensor. Start with the bulk and shear moduli from the Voigt estimate.

C11 = K + (4 / 3) * G
C12 = C11 - 2 * G
C44 = (C11 - C12) / 2

C_from_KG = stiffnessTensor(
  [[C11, C12, C12, 0.0, 0.0, 0.0],
   [C12, C11, C12, 0.0, 0.0, 0.0],
   [C12, C12, C11, 0.0, 0.0, 0.0],
   [0.0, 0.0, 0.0, C44, 0.0, 0.0],
   [0.0, 0.0, 0.0, 0.0, C44, 0.0],
   [0.0, 0.0, 0.0, 0.0, 0.0, C44]], cs)
C_from_KG
stiffnessTensor (An0 Albite 2016)
  unit: GPa
  rank: 4 (3 × 3 × 3 × 3)
  tensor in Voigt matrix representation
  118.3333   35.4667   35.4667        0        0        0
   35.4667  118.3333   35.4667        0        0        0
   35.4667   35.4667  118.3333        0        0        0
         0         0         0  41.4333        0        0
         0         0         0        0  41.4333        0
         0         0         0        0        0  41.4333

Young’s modulus and Poisson’s ratio give the same tensor through its inverse, the compliance tensor.

S11 = 1 / E
S12 = -nu / E
S44 = 2 * (S11 - S12)

C_from_Enu = inv(complianceTensor(
  [[S11, S12, S12, 0.0, 0.0, 0.0],
   [S12, S11, S12, 0.0, 0.0, 0.0],
   [S12, S12, S11, 0.0, 0.0, 0.0],
   [0.0, 0.0, 0.0, S44, 0.0, 0.0],
   [0.0, 0.0, 0.0, 0.0, S44, 0.0],
   [0.0, 0.0, 0.0, 0.0, 0.0, S44]], cs))
C_from_Enu
stiffnessTensor (An0 Albite 2016)
  unit: GPa
  rank: 4 (3 × 3 × 3 × 3)
  tensor in Voigt matrix representation
  118.3333   35.4667   35.4667        0        0        0
   35.4667  118.3333   35.4667        0        0        0
   35.4667   35.4667  118.3333        0        0        0
         0         0         0  41.4333        0        0
         0         0         0        0  41.4333        0
         0         0         0        0        0  41.4333

Both constructions reproduce the averaged tensor above entry for entry. The same equivalence gives direct conversion formulas between moduli.

# two formulas for Poisson's ratio
nu_from_EG = (E / G - 2) / 2
nu_from_KE = (3 * K - E) / (6 * K)

# two formulas for Young's modulus
E_from_Gnu = 2 * G * (1 + nu)
E_from_Knu = 3 * K * (1 - 2 * nu)

np.array([nu_from_EG, nu_from_KE, E_from_Gnu, E_from_Knu])
array([  0.2306,   0.2306, 101.9759, 101.9759])

Lame constants and Hooke’s law

The Lame constants are the pair usually preferred in theoretical work. They make isotropic Hooke’s law especially short.

lam = nu / (1 - 2 * nu) / (1 + nu) * E
mu = G

# rebuild the stiffness tensor from the Lame constants
C_from_Lame = 2 * mu * stiffnessTensor.eye(cs) + lam * dyad(tensor.eye(cs), tensor.eye(cs))
C_from_Lame
stiffnessTensor (An0 Albite 2016)
  unit: GPa
  rank: 4 (3 × 3 × 3 × 3)
  tensor in Voigt matrix representation
  118.3333   35.4667   35.4667        0        0        0
   35.4667  118.3333   35.4667        0        0        0
   35.4667   35.4667  118.3333        0        0        0
         0         0         0  41.4333        0        0
         0         0         0        0  41.4333        0
         0         0         0        0        0  41.4333

Apply Hooke’s law to a random strain, first by tensor contraction.

eps = strainTensor.rand(cs)
sigma_contraction = colon(C_iso_Voigt, eps)
sigma_contraction
stressTensor (y↓→x)
  rank: 2 (3 × 3)
  -77.6652   75.3156  73.4026
   75.3156  -77.8147  -1.7231
   73.4026   -1.7231  -8.9174

The Lame form gives exactly the same stress.

sigma_Lame = stressTensor(2 * mu * eps + lam * trace(eps) * tensor.eye(cs))
sigma_Lame
stressTensor (An0 Albite 2016)
  rank: 2 (3 × 3)
  -77.6652   75.3156  73.4026
   75.3156  -77.8147  -1.7231
   73.4026   -1.7231  -8.9174

References

Next

Anisotropic Theory removes the directional equality used here. It shows how crystal symmetry constrains a full stiffness tensor and how to read its directional elastic response.

Technical Details

MATLAB’s three outputs of mean(C, odf) are mean(C, odf, 'all') here, the Voigt, Reuss and Hill tensors in that order. The identity tensor.eye(cs) is written in the frame of the crystal, since a tensor without that frame cannot be added to one with it; MATLAB’s tensor.eye borrows the frame of the sum. HashinShtrikmanModulus returns NaN for the comparison materials whose residual is neither positive nor negative definite, which the maps leave white where MATLAB’s imagesc paints them in the lowest colour. The random strain differs from MATLAB’s run, so do the two stresses; they agree with each other.

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/IsotropicTheory_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.