tengfeiluo's picture
Upload folder using huggingface_hub
01a0b26 verified
"""Depolarization factors for ellipsoidal inclusions.
Every effective medium model (Maxwell-Garnett, Bruggeman, Mori-Tanaka,
Nan, Hatta-Taya) requires depolarization factors L_a, L_b, L_c that
encode particle shape. This module provides closed-form expressions for
spheroids using the Osborn (1945) formulas and a dispatch helper for
common shape descriptors.
Constraint: L_a + L_b + L_c = 1 for any ellipsoid.
Convention
----------
- **Prolate spheroid** (fiber-like): long semi-axis *a*, short semi-axes
*b = c*; aspect ratio p = a/b > 1. L_a is along the long axis
(L_a < 1/3), L_b = L_c = (1 − L_a)/2.
- **Oblate spheroid** (disk-like): large semi-axes *a = b*, short
semi-axis *c*; aspect ratio p = c/a ∈ (0, 1]. L_c is along the thin
axis (L_c > 1/3), L_a = L_b = (1 − L_c)/2.
- **Needle** (p → ∞ prolate): (L_a, L_b, L_c) = (0, 1/2, 1/2).
- **Disk** (p → 0 oblate): (L_a, L_b, L_c) = (0, 0, 1).
References
----------
Osborn, J.A. (1945). Demagnetizing factors of the general ellipsoid.
*Physical Review*, 67(11-12), 351–357.
Landau, L.D. & Lifshitz, E.M. (1960). *Electrodynamics of Continuous
Media*. Pergamon, §4.
Nan, C.-W. et al. (1997). Effective thermal conductivity of particulate
composites with interfacial thermal resistance. *J. Appl. Phys.*,
81(10), 6692.
"""
from __future__ import annotations
import numpy as np
__all__ = [
"depol_sphere",
"depol_prolate",
"depol_oblate",
"depol_from_shape",
]
def depol_sphere() -> tuple[float, float, float]:
"""Depolarization factors for a perfect sphere.
Returns
-------
L_a, L_b, L_c : tuple of float
All three components equal 1/3 by cubic symmetry.
Examples
--------
>>> depol_sphere()
(0.3333333333333333, 0.3333333333333333, 0.3333333333333333)
"""
L = 1.0 / 3.0
return L, L, L
def depol_prolate(p: float) -> tuple[float, float, float]:
"""Depolarization factors for a prolate spheroid (fiber-like).
Uses the Osborn (1945) closed-form expression in terms of the
eccentricity e = √(1 − 1/p²).
Parameters
----------
p : float
Aspect ratio a/b ≥ 1, where *a* is the long semi-axis and
*b = c* are the short semi-axes. p = 1 returns sphere values;
p → ∞ approaches the needle limit (0, 0.5, 0.5).
Returns
-------
L_a, L_b, L_c : tuple of float
L_a is along the long (symmetry) axis; L_b = L_c transverse.
Satisfies L_a + L_b + L_c = 1 and 0 ≤ each L ≤ 1.
Raises
------
ValueError
If p < 1.
References
----------
Osborn (1945) eq. (6); Nan et al. (1997) eq. (A2).
Examples
--------
>>> La, Lb, Lc = depol_prolate(1.0)
>>> abs(La - 1 / 3) < 1e-10
True
>>> La, Lb, Lc = depol_prolate(1e6) # needle limit
>>> La < 1e-9
True
"""
p = float(p)
if p < 1.0:
raise ValueError(
f"Prolate aspect ratio p must be >= 1, got {p}."
)
if p == 1.0:
return depol_sphere()
e2 = 1.0 - 1.0 / p**2 # eccentricity squared
e = np.sqrt(e2)
# For e very small (p barely > 1) the direct formula suffers catastrophic
# cancellation in (-1 + arctanh(e)/e). Switch to the Taylor series:
# L_a = 1/3 - 2e²/15 - 2e⁴/35 + O(e⁶)
# (derived by expanding arctanh(e)/e = 1 + e²/3 + e⁴/5 + …).
if e < 1e-5:
L_a = 1.0 / 3.0 - 2.0 * e2 / 15.0 - 2.0 * e2**2 / 35.0
else:
# Osborn (1945): L_a = (1-e²)/e² * [-1 + ln((1+e)/(1-e)) / (2e)]
L_a = (1.0 - e2) / e2 * (-1.0 + np.log((1.0 + e) / (1.0 - e)) / (2.0 * e))
L_b = (1.0 - L_a) / 2.0
return float(L_a), float(L_b), float(L_b)
def depol_oblate(p: float) -> tuple[float, float, float]:
"""Depolarization factors for an oblate spheroid (disk-like).
Uses the Osborn (1945) closed-form expression in terms of the
eccentricity e = √(1 − p²).
Parameters
----------
p : float
Aspect ratio c/a ∈ (0, 1], where *c* is the short semi-axis
and *a = b* are the large semi-axes. p = 1 returns sphere
values; p → 0 approaches the disk limit (0, 0, 1).
Returns
-------
L_a, L_b, L_c : tuple of float
L_a = L_b are in-plane; L_c is along the thin axis.
Satisfies L_a + L_b + L_c = 1 and 0 ≤ each L ≤ 1.
Raises
------
ValueError
If p ≤ 0 or p > 1.
References
----------
Osborn (1945) eq. (7); Nan et al. (1997) eq. (A3).
Examples
--------
>>> La, Lb, Lc = depol_oblate(1.0)
>>> abs(Lc - 1 / 3) < 1e-10
True
>>> La, Lb, Lc = depol_oblate(0.001) # thin-disk limit
>>> Lc > 0.998
True
"""
p = float(p)
if p <= 0.0 or p > 1.0:
raise ValueError(
f"Oblate aspect ratio p must be in (0, 1], got {p}."
)
if p == 1.0:
return depol_sphere()
e2 = 1.0 - p**2 # eccentricity squared
e = np.sqrt(e2)
# Osborn (1945): L_c = (1/e²) * [1 - (√(1-e²)/e) * arcsin(e)]
L_c = (1.0 / e2) * (1.0 - (np.sqrt(1.0 - e2) / e) * np.arcsin(e))
L_a = (1.0 - L_c) / 2.0
return float(L_a), float(L_a), float(L_c)
def depol_from_shape(
shape: str,
aspect_ratio: float = 1.0,
) -> tuple[float, float, float]:
"""Dispatch depolarization factors from a shape descriptor string.
Parameters
----------
shape : str
One of ``"sphere"``, ``"prolate"``, ``"oblate"``,
``"needle"``, ``"disk"`` (case-insensitive).
aspect_ratio : float, optional
For ``"prolate"``: p = a/b ≥ 1 (default 1.0).
For ``"oblate"``: p = c/a ∈ (0, 1] (default 1.0).
Ignored for ``"sphere"``, ``"needle"``, and ``"disk"``.
Returns
-------
L_a, L_b, L_c : tuple of float
Depolarization factors along the three principal axes.
Raises
------
ValueError
If *shape* is not recognised.
Examples
--------
>>> depol_from_shape("sphere")
(0.3333333333333333, 0.3333333333333333, 0.3333333333333333)
>>> depol_from_shape("needle")
(0.0, 0.5, 0.5)
>>> depol_from_shape("disk")
(0.0, 0.0, 1.0)
"""
shape = shape.lower()
if shape == "sphere":
return depol_sphere()
elif shape == "prolate":
return depol_prolate(aspect_ratio)
elif shape == "oblate":
return depol_oblate(aspect_ratio)
elif shape == "needle":
return 0.0, 0.5, 0.5
elif shape == "disk":
return 0.0, 0.0, 1.0
else:
raise ValueError(
f"Unknown shape {shape!r}. Choose from "
"'sphere', 'prolate', 'oblate', 'needle', 'disk'."
)