API — Schemes

MeanFieldHom.SchemesModule
MeanFieldHom.Schemes

Mean-field homogenization schemes. Provides the RVE container (matrix + named phases with their volume fractions or crack densities, plus an optional distribution shape) and the suite of homogenization HomogenizationScheme types: bounds (Voigt, Reuss), one-shot schemes with a matrix (Dilute, DiluteDual, MoriTanaka, Maxwell, PonteCastanedaWillis), iterative self-consistent schemes (SelfConsistent, AsymmetricSelfConsistent) and the differential scheme (Differential) with user-selectable trajectory.

Public entry point: homogenize(rve, scheme; property=:C). The scheme can also be passed as a Symbol shortcut (:MT, :SC, …).

RVE / Phase / Amount

MeanFieldHom.Schemes.RVEType
RVE{T<:Number, S<:Union{Nothing,AbstractDistributionShape}}

Multi-phase representative volume element. Fields:

  • matrix_name::Symbol — name of the phase that plays the role of the matrix (its amount is implicit, computed as 1 - Σ_inc f_inc).
  • phase_names::Vector{Symbol} — phases in insertion order (the matrix is the first entry).
  • phases::Dict{Symbol,Phase} — geometry + properties of each phase.
  • amounts::Dict{Symbol,AbstractAmount} — volume fraction or crack density of each non-matrix phase. Each entry keeps its own element type. The matrix entry, if present, is ignored when computing matrix_volume_fraction.
  • distribution_shape::S — outer envelope used by Maxwell / PCW; defaults to a unit sphere wrapped in UniformDistribution.
  • f_matrix — cached 1 - Σ f_inc, maintained by add_phase! and read by matrix_volume_fraction. Do not write amounts directly: that would leave the cache stale.

T is the declared amount element type, i.e. a floor for promotion, not a constraint: it seeds zero/one for an RVE whose amounts are all narrower (an Int fraction still yields a Float64 matrix fraction under the default T = Float64), and any amount handed to add_phase! that is wider than T is stored as such rather than converted down. Amounts, moduli and geometries therefore each carry their own element type, promoted only where the values meet:

rve = RVE(:M)                                          # nothing to declare
add_matrix!(rve, Ellipsoid(1.0), Dict(:C => C_complex))    # complex moduli
add_phase!(rve, :I, Ellipsoid(1.0), Dict(:C => C_complex);
           fraction = 0.3)                             # real fraction
add_phase!(rve, :J, Ellipsoid(1.0), Dict(:C => C1);
           fraction = dual_x)                          # Dual fraction

eltype(rve) reports the effective element type (the promotion of the floor with every stored amount); eltype(RVE{T,S}) reports the declared floor T.

Construction is two-step:

rve = RVE(:M; distribution_shape = nothing)   # or RVE{ComplexF64}(:M)
add_matrix!(rve, ellipsoid_matrix, Dict(:C => C0))
add_phase!(rve, :I1, ellipsoid_inc, Dict(:C => C1); fraction = 0.2)
add_phase!(rve, :CRACK, penny_crack, Dict(:C => C0); density = 0.05)

See also add_matrix!, add_phase!, matrix_volume_fraction, validate_rve.

MeanFieldHom.Schemes.PhaseType
Phase(geometry::AbstractInclusion, properties::Dict{Symbol,<:AbstractTens})

A single phase of a RVE: one inclusion geometry (ellipsoid, crack, …) together with one or several material property tensors indexed by symbol (:C for stiffness, :K for conductivity, …).

The geometry is field-typed AbstractInclusion (rather than parametric Phase{I}) so that a heterogeneous RVE mixing ellipsoids and cracks can be stored in a single Dict{Symbol,Phase} without losing information at construction time. Specialization happens at the dispatch site (hill_tensor(phase.geometry, …), cod_tensor(phase.geometry, …)).

MeanFieldHom.Schemes.AbstractAmountType
AbstractAmount{T<:Number}

Supertype for the quantity attached to a phase in a RVE. Two concrete subtypes:

  • VolumeFraction — for solid (ellipsoidal) inclusions and the matrix; obeys the unit-sum constraint f_matrix = 1 - Σ_other VolumeFraction.value.
  • CrackDensity — for flat cracks (Budiansky-O'Connell density); does not participate in the unit-sum constraint, since the volume contribution of a flat crack vanishes in the penny limit while the density remains finite.

The type parameter T is the element type of the stored value (Float64, ForwardDiff.Dual, Complex{Float64}, …) and propagates through every scheme that consumes the amount. Each amount carries its own T: a RVE may hold a Float64 fraction next to a ForwardDiff.Dual one, the element types being promoted where the values actually meet (see RVE).

MeanFieldHom.Schemes.AbstractDistributionShapeType
AbstractDistributionShape

Supertype for the outer envelope of the phase distribution used by the Maxwell and PonteCastanedaWillis schemes.

Currently a single concrete subtype is shipped:

  • UniformDistribution — a single shape applied to every inclusion phase (Maxwell 1873 ; Ponte-Castañeda & Willis 1995).

Future extension (placeholder, not implemented in this PR): a PairwiseDistribution carrying a per-pair (i, j) ↦ shape mapping (Willis 1982). Adding it will only require a new concrete subtype + matching _evaluate(rve, ::Maxwell|::PonteCastanedaWillis, …) methods — no public-API change.

MeanFieldHom.Schemes.UniformDistributionType
UniformDistribution(shape::AbstractInclusion) <: AbstractDistributionShape

Single distribution shape applied to every inclusion phase. The default constructor UniformDistribution() returns a unit sphere (isotropic distribution, recovers Mori-Tanaka in the limit P_d = P_inc).

MeanFieldHom.Schemes.AbstractSymmetrizeType
AbstractSymmetrize

Specifies how a phase's localization tensor (and the derived stiffness / compliance / conductivity / resistivity contributions) is averaged over an orientation distribution before being used in the homogenization formula.

Three concrete subtypes are shipped :

  • NoSymmetrize (default) — keep the contribution as computed for the single-orientation inclusion stored in the phase.
  • IsoSymmetrize — average over all rotations (uniform spatial distribution of orientations) ; produces an isotropic projection.
  • TISymmetrize — average over rotations around a specified axis (uniaxial uniform distribution) ; produces a transversely-isotropic projection.

This mirrors C++ ECHOES's symmetrize=[ISO] / symmetrize=[TI] keyword on ellipsoid(), but moved to the RVE side (just like volume fractions) : the same inclusion type can be re-used in different RVEs with different distribution assumptions, and a single inclusion remains usable for localization-tensor calculations without any RVE.

MeanFieldHom.Schemes.NoSymmetrizeType
NoSymmetrize() <: AbstractSymmetrize

Default. The localization tensor is used as computed for the single orientation defined by the inclusion's basis.

MeanFieldHom.Schemes.IsoSymmetrizeType
IsoSymmetrize() <: AbstractSymmetrize

The localization tensor is averaged over a uniform spatial distribution of orientations, equivalent to projecting onto the isotropic basis (J, K_proj) for 4th-order tensors and onto the spherical part for 2nd-order tensors. Produces an isotropic phase contribution regardless of the inclusion's actual shape.

MeanFieldHom.Schemes.TISymmetrizeType
TISymmetrize(axis = (0, 0, 1); matrix_projection = :iso) <: AbstractSymmetrize

The localization tensor is averaged exactly over rotations about axis (uniaxial uniform distribution). The average preserves the full axially-invariant structure — including the non-major-symmetric components of concentration tensors — and returns a TensND.TensTI{4,T,8} (resp. TensTI{2,T,3} at 2nd order).

matrix_projection controls how the reference medium is pre-projected before the localization tensor of this phase is computed :

  • :iso (default) — project the reference to its isotropic average. Every inclusion orientation then has an analytical, ForwardDiff-compatible Hill branch. This is an approximation whenever the reference is not isotropic; it is exact at the isotropic fixed point of a self-consistent iteration.
  • :none — use the reference as is. Non-coaxial anisotropic references route through the general-anisotropy Hill branch (NestedQuadGK, ForwardDiff-compatible but quadrature-priced).
  • :ti — project the reference to its best-fit TI form about axis. Only valid when the phase's inclusions are coaxial with axis (the TI-coaxial analytical Hill branch applies).
MeanFieldHom.Schemes.add_matrix!Function
add_matrix!(rve, geometry, properties::AbstractDict; symmetrize = nothing)

Register the matrix phase. Must be called before any add_phase!. The matrix has no explicit amount (its volume fraction is implicit).

Pass a symmetrize = :iso | :ti | TISymmetrize(axis) | NoSymmetrize() kwarg to declare an orientation-distribution projection of the matrix's localization tensor (see AbstractSymmetrize).

MeanFieldHom.Schemes.add_phase!Function
add_phase!(rve, name::Symbol, geometry, properties::AbstractDict;
           fraction = nothing, density = nothing, symmetrize = nothing)

Register an inclusion phase with the given geometry and material properties. Exactly one of fraction (for ellipsoidal inclusions and solid inhomogeneities) or density (for cracks) must be supplied; fraction produces a VolumeFraction, density a CrackDensity.

Both fraction and density are stored under the RVE's declared element-type floor T: widened to promote_type(T, typeof(value)), never narrowed. A complex, ForwardDiff.Dual or symbolic amount is therefore accepted by a plain RVE(:M), and phases may carry amounts of different element types.

The optional symmetrize kwarg declares an orientation-distribution projection of this phase's localization tensor : :iso (uniform spatial distribution → isotropic projection), :ti (uniaxial uniform around z-axis), TISymmetrize(axis) (around an arbitrary axis), or pass an explicit AbstractSymmetrize instance. The default NoSymmetrize keeps the inclusion's actual single-orientation tensor.

MeanFieldHom.Schemes.phase_propertyFunction
phase_property(rve, name::Symbol, key::Symbol) -> AbstractTens

Return the property tensor key (e.g. :C, :K) of the phase named name.

MeanFieldHom.Schemes.matrix_volume_fractionFunction
matrix_volume_fraction(rve::RVE) -> Number

Implicit matrix volume fraction 1 - Σ_inc f_inc (only VolumeFraction entries contribute; CrackDensity entries are ignored).

The value is cached on the RVE and refreshed by add_phase!; this is a read of the f_matrix field, not a recomputation.

The accumulator behind it is seeded with zero(T) (the declared floor) and then promoted by the stored amounts, so the result carries the effective element type — Dual as soon as one fraction is Dual, Complex as soon as one is complex. The unit is taken from the accumulator rather than from T, so a symbolic RVE yields 1 - f and not 1.0 - f.

MeanFieldHom.Schemes.validate_rveFunction
validate_rve(rve::RVE)

Sanity-check the RVE: matrix registered, all amounts non-negative, sum of VolumeFraction entries ≤ 1. Throws ArgumentError on hard failures; emits @warn if f_inc > 1 (non-physical RVE — useful for symbolic / Dual exploration but flagged).

MeanFieldHom.Schemes.promote_rveFunction
promote_rve(rve, ::Type{T}) -> RVE

Return a copy of rve whose declared floor is T and whose amounts are all converted to promote_type(T, ·). Rarely needed — amounts promote themselves where they are consumed — but useful to force an element type on an RVE built elsewhere.

Schemes

MeanFieldHom.Schemes.VoigtType
Voigt() <: HomogenizationScheme

Voigt (uniform-strain) upper bound: $\langle \mathbb C \rangle$.

MeanFieldHom.Schemes.ReussType
Reuss() <: HomogenizationScheme

Reuss (uniform-stress) lower bound: $\langle \mathbb S \rangle^{-1}$.

MeanFieldHom.Schemes.DiluteType
Dilute() <: HomogenizationScheme

Dilute scheme: $\mathbb C_{\mathrm{eff}} = \mathbb C_0 + \sum_i f_i \mathbb N_i$ where $\mathbb N_i = (\mathbb C_i - \mathbb C_0):\mathbb A_{\varepsilon\varepsilon}^{(i)}$ is the size-independent stiffness contribution (Eshelby 1957; Kachanov & Sevostianov 2018).

MeanFieldHom.Schemes.DiluteDualType
DiluteDual() <: HomogenizationScheme

Dual dilute scheme on the compliance: $\mathbb S_{\mathrm{eff}} = \mathbb S_0 + \sum_i f_i \mathbb H_i$, returning $\mathbb C_{\mathrm{eff}} = \mathbb S_{\mathrm{eff}}^{-1}$.

MeanFieldHom.Schemes.MaxwellType
Maxwell() <: HomogenizationScheme

Maxwell homogenization, using the RVE's distribution shape as the reference for the Hill polarization tensor.

MeanFieldHom.Schemes.SelfConsistentType
SelfConsistent(; algorithm = AndersonDefault(), kwargs...) <: HomogenizationScheme

Self-consistent scheme. The algorithm selects the non-linear solver; default is the built-in Anderson acceleration. Any solver from the SciML NonlinearSolve.jl package can be passed once using NonlinearSolve activates the weak extension MeanFieldHomNonlinearSolveExt.

Standard kwargs forwarded to the solver: abstol, reltol, maxiters, damping, verbose.

MeanFieldHom.Schemes.AsymmetricSelfConsistentType
AsymmetricSelfConsistent(; algorithm = AndersonDefault(), kwargs...) <: HomogenizationScheme

Asymmetric self-consistent scheme: iterates in stiffness or compliance space depending on the matrix-vs-Voigt-bound contrast, providing a better behavior than SelfConsistent in matrix-stiff / inclusion-soft regimes.

MeanFieldHom.Schemes.DifferentialSchemeType
DifferentialScheme(; trajectory = Proportional(), nsteps::Int = 100,
                     abstol::Real = 1e-8, reltol::Real = 1e-6,
                     alg = nothing, formulation = :stiffness, kwargs...)

Differential scheme : integrates the Norris ODE on the fictitious incorporation time τ ∈ [0, 1] (Norris 1985) :

\[\frac{\mathrm d \mathbb C^{hom}}{\mathrm d \tau} = \sum_i \frac{\mathrm d \varphi_i}{\mathrm d \tau}\, (\mathbb C_i - \mathbb C^{hom}):\mathbb A_i^{dil}(\mathbb C^{hom})\]

with the volume balance df = (𝟙 - f ⊗ 𝐔)·dφ inverted by Sherman- Morrison so the user supplies effective volume fractions f_α(τ) along the chosen trajectory.

Keyword arguments

  • trajectory — one of Proportional, Sequential, CustomPath, Path. Default Proportional().
  • formulation:stiffness (default) integrates the ODE above; :compliance integrates its exact dual $\mathrm d \mathbb S^{hom} / \mathrm d \tau = \sum_i \dot\varphi_i \, \mathbb H_i(\mathbb S^{hom})$ and inverts the result, so both return the same declared property. The two agree analytically (ℍ = −𝕊 : 𝐍 : 𝕊) and differ only in which variable carries the solver's error control: prefer :compliance for a medium softening towards percolation (porous, cracked), :stiffness for a stiffening one.
  • nsteps — density of save points along τ (passed as saveat to the SciML ODE solver). The integration step is controlled by abstol / reltol, not by nsteps. See differential_path to read the saved states back.
  • abstol, reltol — ODE solver tolerances (forwarded to OrdinaryDiffEq.solve).
  • alg — explicit ODE algorithm. nothing selects Tsit5() (5th order adaptive Runge-Kutta). Pass any OrdinaryDiffEqAlgorithm instance to override (e.g. Vern9() for higher accuracy). Implicit algorithms must be built with a non-AD Jacobian (Rosenbrock23(autodiff = AutoFiniteDiff())): the RHS calls the Hill-tensor backends, which are not differentiable with respect to the ODE state.
  • kwargs... — any other keyword is forwarded verbatim to OrdinaryDiffEq.solve (maxiters, dtmax, dt, callback, …).
MeanFieldHom.Schemes.DifferentialTrajectoryType
DifferentialTrajectory

Supertype describing the path through the multi-phase volume-fraction space used by the DifferentialScheme scheme. Concrete subtypes:

  • Proportional (default) — every phase grows linearly with the fictitious incorporation time τ ∈ [0, 1], all phases reaching their target simultaneously.
  • Sequential — phases are introduced one after the other in the user-supplied order, each occupying a contiguous slice of τ.
  • CustomPath — explicit per-phase trajectory as a vector of monotone non-decreasing values; piecewise-linear interpolated along τ ∈ [0, 1].
  • Path — explicit per-phase trajectory as a callable τ -> f(τ) (auto-differentiated by ForwardDiff); the natural API for the multi-phase incorporation-sequence ODE (Norris 1985; the user's hand-written DEM note).
MeanFieldHom.Schemes.SequentialType
Sequential(order::Vector{Symbol}) <: DifferentialTrajectory

Introduce phases in the given order. The first phase ramps from 0 to its target fraction over the steps it owns, then is frozen; the next phase ramps over its allotted steps; and so on.

MeanFieldHom.Schemes.CustomPathType
CustomPath(path::Dict{Symbol, <:AbstractVector{<:Real}}) <: DifferentialTrajectory
CustomPath(:phase => values, ...)

Explicit per-phase trajectory. path[:phase] must be a length-N monotone vector with path[:phase][1] = 0 and path[:phase][end] = 1, where N is the number of differential steps.

The pair form is a convenience constructor for the same thing: CustomPath(:I1 => [0.0, 0.5, 1.0], :I2 => [0.0, 0.2, 1.0]).

MeanFieldHom.Schemes.PathType
Path(path::Dict{Symbol, <:Function}) <: DifferentialTrajectory
Path(:phase => τ -> f(τ), ...)

Explicit per-phase trajectory as a callable. path[:phase] is a function of the fictitious incorporation time τ ∈ [0, 1] returning the effective volume fraction ratio f_α(τ) / f_α^∞ ∈ [0, 1] for solid phases, or the density ratio ε_α(τ) / ε_α^∞ for crack phases — f(0) = 0, f(1) = 1, monotone non-decreasing.

The derivative df/dτ is computed by ForwardDiff.derivative at each ODE step. This is the natural API for the multi-phase DEM incorporation-sequence ODE :

\[\frac{\mathrm d \mathbb C^{hom}}{\mathrm d \tau} = \sum_i \frac{\mathrm d \varphi_i}{\mathrm d \tau} (\mathbb C_i - \mathbb C^{hom}):\mathbb A_i^{dil}(\mathbb C^{hom})\]

with the volumetric balance dφ = (𝟙 - f ⊗ 𝐔)^{-1} · df (Sherman- Morrison) inverted at each τ to translate user-supplied f_α(τ) into the increments dφ_α / dτ.

The single-phase case is degenerate (f₁ itself serves as τ) and does not require a Path — the default Proportional is sufficient.

The pair form is a convenience constructor for the same thing:

Path(:I1 => τ -> τ^2, :I2 => τ -> 2τ - τ^2)
MeanFieldHom.Schemes.AutoNonlinearType
AutoNonlinear()

Marker selecting an auto-resolving nonlinear solver: at each call it checks, at runtime, whether the weak extension MeanFieldHomNonlinearSolveExt is active (Base.get_extension) — i.e. whether NonlinearSolve.jl has been loaded into the session, whether by an explicit using NonlinearSolve or transitively through some other dependency (empirically, on a typical installation today the extension is already active as soon as MeanFieldHom itself is loaded — the exact transitive path depends on the resolved dependency graph and is not guaranteed across versions).

  • If active, it dispatches to a globalized SciML algorithm (NonlinearSolve.TrustRegion() — chosen over a plain NewtonRaphson() for its better robustness near the self-consistent bifurcation), through the same ForwardDiff-safe path (implicit-function-theorem lift) as any other NonlinearSolve.jl algorithm passed explicitly.
  • Otherwise (a slimmed-down dependency set, or a future OrdinaryDiffEq that no longer needs NonlinearSolve.jl internally) it falls back to the dependency-free built-in NewtonDefault.

This gives a solver choice that always works, with or without NonlinearSolve.jl explicitly requested.

AutoNonlinear is not the default of SelfConsistent / AsymmetricSelfConsistent (which remains AndersonDefault): this is a numerical-robustness choice, not a dependency-cost one — the porous-percolation regime relies on the Picard positive-definite guard and select_best to track the physical branch through the bifurcation, a property a root-finder does not share. Pass algorithm = AutoNonlinear() explicitly to opt in.

Entry point

MeanFieldHom.Schemes.homogenizeFunction
homogenize(rve::RVE, scheme::HomogenizationScheme, property::Symbol; kw...)
homogenize(rve::RVE, scheme::Symbol, property::Symbol; kw...)

Compute the effective property of rve under the chosen scheme.

property is a required Symbol argument naming which stored phase property is homogenised. The order of the result (4th-order, 2nd-order, …) follows from the order of the tensor stored under that name in each phase ; the symbol itself is just a key — any user-chosen name (:stiffness, :conductivity, , …) works as long as every phase carries a tensor under that key. Common conventions are :C for elastic stiffness and :K for conductivity / diffusivity.

The scheme can be passed either as a type instance (full control of options — MoriTanaka(), SelfConsistent(algorithm = NewtonDefault(), abstol = 1e-12), …) or as a Symbol shortcut for the default constructor. The canonical Symbol aliases are lowercase (:mt, :sc, :voigt, …) to match the algorithm-method symbols (:auto, :residues, :decuhr, …) used by the underlying Hill-tensor backends; CamelCase and upper-case forms are also accepted (see SCHEME_ALIAS).

Extra kw... are forwarded to the scheme's _evaluate method (typically abstol / reltol / maxiters for iterative schemes, method = :auto | :decuhr | … for the underlying Hill-tensor backend).

MeanFieldHom.Schemes.differential_pathFunction
differential_path(rve, scheme::DifferentialScheme, property::Symbol; kw...)
    -> (τ::Vector, states::Vector{<:AbstractTens})

Effective property of rve all along the differential scheme's fictitious incorporation time τ ∈ [0, 1], instead of only at τ = 1 as homogenize returns.

τ carries nsteps + 1 uniformly spaced save points (the saveat of the underlying ODE solve — the integration step itself remains adaptive and controlled by abstol / reltol), and states[k] is the effective property after the fraction of each phase has been grown to f_α(τ[k]) along the scheme's trajectory. states[1] is the matrix property and states[end] is exactly what homogenize returns.

Useful to plot the construction history of a composite, and to compare incorporation trajectories at equal target fractions.

τ, Cs = differential_path(rve, DifferentialScheme(; nsteps = 200), :C)
ks = [k_mu(C)[1] for C in Cs]
MeanFieldHom.Schemes.SCHEME_ALIASConstant
SCHEME_ALIAS :: Dict{Symbol, Type{<:HomogenizationScheme}}

Maps Symbol shortcuts to concrete scheme types for the convenience overload homogenize(rve, ::Symbol).

The canonical aliases are all lowercase (:voigt, :reuss, :dilute, :dilute_dual, :mori_tanaka, :mt, :maxwell, :pcw, :sc, :asc, :differential, :diff) for consistency with the algorithm-method symbols accepted elsewhere in the package (:auto, :residues, :decuhr, :nestedquadgk, :analytical). The CamelCase forms (:MoriTanaka, :Differential) and the ECHOES-compatible upper-case codes (:MT, :DIFF, …) are kept as extra aliases for ease of porting.

Symmetry projections

Best-fit projection of a tensor onto a symmetry class. These force major symmetry, unlike the exact rotation-group averages MeanFieldHom.Core.isotropify / MeanFieldHom.Core.transverse_isotropify in API — Core; the two differ whenever the input is not major-symmetric, and the difference is worked through in Symmetrization showcase.

MeanFieldHom.Schemes.best_fit_isoFunction
best_fit_iso(t::AbstractTens{4,3}) -> TensND.TensISO{4}
best_fit_iso(t::AbstractTens{2,3}) -> TensND.TensISO{2}

Orthogonal (Frobenius) projection of t onto the isotropic basis — thin wrapper over TensND.proj_tens(Val(:ISO), t), TensND's canonical "paramsym"-style extraction. For minor-symmetric tensors this coincides with the exact SO(3) average MeanFieldHom.Core.isotropify (the isotropic subspace is {𝕁, 𝕂} either way).

MeanFieldHom.Schemes.best_fit_tiFunction
best_fit_ti(t::AbstractTens{4,3}, axis) -> TensND.TensTI{4,T,5}
best_fit_ti(t::AbstractTens{2,3}, axis) -> TensND.TensTI{2,T,2}

Orthogonal (Frobenius) projection of t onto the major-symmetric TI (Walpole) span about axis — thin wrapper over TensND.proj_tens( Val(:TI), t, axis), the analog of echoes' .paramsym(sym=TI) parameter extraction. Numerically identical to the previous in-house implementation (exact azimuthal average then forced major symmetry), verified to ~1e-11.

Warning

This is a reporting utility, NOT the orientation average : it forces major symmetry (ℓ₃+ℓ₄)/2 and drops the antisymmetric azimuthal couplings. Inside scheme kernels use _apply_symmetrize / MeanFieldHom.Core.transverse_isotropify instead.

MeanFieldHom.Schemes.best_fit_orthoFunction
best_fit_ortho(t::AbstractTens{4,3}, frame) -> TensND.TensOrtho
best_fit_ortho(t::AbstractTens{2,3}, frame) -> Matrix

Orthogonal (Frobenius) projection of t onto the orthotropic span in the given material frame — thin wrapper over TensND.proj_tens( Val(:ORTHO), t, frame), the analog of echoes' .paramsym(sym=ORTHO). There was previously no orthotropic parameter extraction in MeanFieldHom.jl; this closes that gap using the TI/ORTHO projection machinery already tested in TensND (test/test_tens_projection.jl).