"""Core-shell equivalent inclusion models. Replaces a coated sphere (or multi-layer concentric sphere) with a single homogeneous sphere having the same far-field response. The returned equivalent property P_equiv can then be used as the filler property P_f in any EMT formula (Maxwell-Garnett, Bruggeman, Mori-Tanaka, etc.). Models implemented ------------------ hashin_csa — Single coated sphere (Hashin composite sphere assembly). multilayer_recursive — Multi-layer sphere by recursive Hashin CSA (Hervé 2002). References ---------- Hashin, Z. (1962). The elastic moduli of heterogeneous materials. *J. Appl. Mech.*, 29(1), 143–150. Nan, C.-W. et al. (1997). Effective thermal conductivity of particulate composites with interfacial thermal resistance. *J. Appl. Phys.*, 81(10), 6692. Hervé, E. (2002). Thermal and thermoelastic behaviour of multiply coated inclusion-reinforced composites. *Int. J. Solids Struct.*, 39(4), 1041–1058. """ from __future__ import annotations from typing import Sequence import numpy as np __all__ = ["hashin_csa", "multilayer_recursive"] def hashin_csa( P_core: float, P_shell: float, f_core: float, ) -> float: """Equivalent property of a single coated sphere (Hashin CSA). Computes the equivalent homogeneous property of a concentric two-phase sphere (core + shell) by matching the exact potential boundary-value solution—the Hashin composite sphere assembly (CSA). The result can be substituted as the filler property into any two-phase EMT formula. Parameters ---------- P_core : float Property of the inner core (e.g. W/(m·K) for thermal conductivity). P_shell : float Property of the outer shell (same units as P_core). f_core : float Volume fraction of the core, ``f_core = (r_core / r_shell)³``. Must satisfy 0 ≤ f_core ≤ 1. Returns ------- P_equiv : float Equivalent homogeneous property of the coated sphere. Raises ------ ValueError If *f_core* is outside [0, 1]. Notes ----- From the model catalog (Part VI) and Nan et al. (1997): .. math:: P_{\\text{equiv}} = P_{\\text{shell}}\\, \\frac{2P_{\\text{shell}} + P_{\\text{core}} - 2f(P_{\\text{shell}} - P_{\\text{core}})} {2P_{\\text{shell}} + P_{\\text{core}} + f(P_{\\text{shell}} - P_{\\text{core}})} where :math:`f = (r_{\\text{core}}/r_{\\text{shell}})^3`. **Limits:** - :math:`f \\to 0` (vanishing core): :math:`P_{\\text{equiv}} \\to P_{\\text{shell}}`. - :math:`f \\to 1` (vanishing shell): :math:`P_{\\text{equiv}} \\to P_{\\text{core}}`. - :math:`P_{\\text{core}} = P_{\\text{shell}}`: :math:`P_{\\text{equiv}} = P_{\\text{shell}}`. References ---------- Hashin, Z. (1962). *J. Appl. Mech.*, 29(1), 143. Catalog: Part VI, "Interphase and core-shell equivalent inclusion models". Examples -------- >>> hashin_csa(5.0, 5.0, 0.5) # no contrast → shell value 5.0 >>> hashin_csa(10.0, 1.0, 0.0) # f=0 → shell value 1.0 >>> hashin_csa(10.0, 1.0, 1.0) # f=1 → core value 10.0 >>> hashin_csa(1.0, 10.0, 0.125) # r_core/r_shell = 0.5 """ f = float(f_core) if not (0.0 <= f <= 1.0): raise ValueError( f"f_core must be in [0, 1], got {f}." ) P_c = float(P_core) P_s = float(P_shell) num = 2.0 * P_s + P_c - 2.0 * f * (P_s - P_c) den = 2.0 * P_s + P_c + f * (P_s - P_c) return P_s * num / den def multilayer_recursive( layers: Sequence[tuple[float, float]], ) -> float: """Equivalent property of a multi-layer sphere by recursive Hashin CSA. Starting from the innermost core, applies the Hashin CSA formula iteratively outward, following the Hervé & Zaoui (1993) / Hervé (2002) scheme. Each iteration wraps the current equivalent sphere with the next shell. Parameters ---------- layers : sequence of (P, r) tuples Each tuple ``(property, outer_radius)`` describes one concentric layer, listed from the **innermost** core to the **outermost** shell. At least two entries are required (core + one shell). Radii must be strictly increasing. Returns ------- P_equiv : float Equivalent homogeneous property of the full multi-layer sphere. Raises ------ ValueError If fewer than 2 layers are provided, or if radii are not strictly increasing. Notes ----- The recursion (Hervé, 2002) is: .. math:: P^{(i+1)}_{\\text{equiv}} = \\operatorname{hashin\\_csa}\\!\\left( P^{(i)}_{\\text{equiv}},\\; P^{(i+1)},\\; \\left(\\frac{r_i}{r_{i+1}}\\right)^{\\!3} \\right) initialised with :math:`P^{(0)}_{\\text{equiv}} = P_{\\text{core}}`. References ---------- Hervé, E. (2002). *Int. J. Solids Struct.*, 39(4), 1041. Examples -------- >>> multilayer_recursive([(5.0, 1e-9), (5.0, 2e-9)]) # uniform → 5.0 5.0 >>> # Two-layer: equivalent to hashin_csa(10.0, 1.0, 0.125) >>> multilayer_recursive([(10.0, 50e-9), (1.0, 100e-9)]) """ layers = list(layers) if len(layers) < 2: raise ValueError( "At least 2 layers (core + one shell) are required; " f"got {len(layers)}." ) # Validate strictly increasing radii radii = [r for _, r in layers] for i in range(len(radii) - 1): if radii[i + 1] <= radii[i]: raise ValueError( f"Radii must be strictly increasing; layer {i} has r={radii[i]}, " f"layer {i+1} has r={radii[i+1]}." ) # Start with core P_equiv = float(layers[0][0]) r_inner = float(layers[0][1]) # Wrap with each successive shell for P_shell, r_outer in layers[1:]: P_shell = float(P_shell) r_outer = float(r_outer) f_core = (r_inner / r_outer) ** 3 P_equiv = hashin_csa(P_equiv, P_shell, f_core) r_inner = r_outer return P_equiv