Spaces:
Sleeping
Sleeping
| """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'." | |
| ) | |