API — Laplace-Carson viscoelasticity
The non-aging half of MeanFieldHomogenization.Viscoelasticity: numerical Laplace inversion, the rheological model catalog, the exact Kelvin ⇄ Maxwell conversion, and the homogenization driver that ties them together.
See the theory, the model manual and the inversion manual.
Numerical inversion
MeanFieldHomogenization.Viscoelasticity.AbstractLaplaceInversion Type
AbstractLaplaceInversionRoot supertype for numerical inverse-Laplace algorithms: GaverStehfest, FixedTalbot, TalbotTrefethen and DeHoog.
All are consumed through inverse_laplace and inverse_carson.
MeanFieldHomogenization.Viscoelasticity.GaverStehfest Type
GaverStehfest(N::Int = 16)Gaver-Stehfest inversion,
with the Salzer weights
All nodes are real and positive. That is why this is the default: a transform defined only for real homogenize_lc stays in real arithmetic, and ForwardDiff sees plain Dual numbers rather than Complex{Dual}.
N must be even with
More terms is not more accurate
The weights alternate in sign with magnitudes up to Float64 that puts a hard ceiling on N:
N | relative error on Float64 |
|---|---|
| 8 | 8e-3 |
| 12 | 4e-4 |
| 16 (default) | 1e-5 |
| 18 | 2e-6 — the optimum |
| 22 | 2e-3 |
| 26 | 4e-1 |
| 30 | 1e+3 — and it keeps growing |
Past N is validated rather than left free.
Working in BigFloat removes the ceiling entirely: the weights are cached as exact Rational{BigInt} and converted to the working type on demand, so under setprecision(256) the error keeps falling with N all the way to 1e-14 at Float64 is at 1e5. See the inversion tutorial §2 for the plot.
At its best on completely monotone kernels — creep and relaxation functions — and poor on oscillatory ones: on FixedTalbot reaches 1e-12.
Measured on
| relative error, | relative error, | |
|---|---|---|
| 0.01 | 4e-8 | 6e-8 |
| 1 | 3e-9 | 5e-7 |
| 10 | 3e-5 | 0.4 |
| 40 | 3e-6 | 5e11 |
The second column is not a defect of this method — see the note on the tail under inverse_laplace. The first is the regime viscoelastic solids actually live in.
MeanFieldHomogenization.Viscoelasticity.FixedTalbot Type
FixedTalbot(N::Int = 24)Fixed-Talbot inversion (Abate & Valkó): the Bromwich contour is deformed into the cotangent curve
on which
This is DEFAULT_INVERSION, and on every kernel tested it is also the most accurate of the four — around 1e-12–1e-13 relative, for 24 evaluations of the transform:
| transform | FixedTalbot(24) | GaverStehfest(16) |
|---|---|---|
| 3e-12 | 1e-5 | |
| 1e-14 | 3e-7 | |
| 4e-12 | 4e-7 | |
| 1e-12 | ||
| 2S2P1D creep kernel | 5e-13 | 3e-8 |
Branch cuts are fine — that is what the contour is for
The cotangent curve is a Hankel-type contour: it wraps around the negative real axis rather than crossing it, which is precisely how Talbot quadratures are designed to handle singularities there. Fractional models (ScottBlair, HuetSayegh, Model2S2P1D, Rabotnov) invert at full accuracy — verified against their exact test/Viscoelasticity/.
What it cannot do is reach a singularity in the right half-plane; use TalbotTrefethen(; shift = …) for that.
Requires the transform to accept complex arguments. When it must stay real — so that a whole homogenization scheme runs in real arithmetic — use GaverStehfest instead.
MeanFieldHomogenization.Viscoelasticity.TalbotTrefethen Type
TalbotTrefethen(N::Int = 24; shift = 0.0)Talbot inversion on the Trefethen-Weideman-Schmelzer contour,
with FixedTalbot it handles branch cuts on tests/python/creep/laplace_inversion.py), so results can be cross-checked against it directly.
It differs from FixedTalbot in one respect that matters: its contour is tuned for decaying kernels and degrades badly on oscillatory ones (6e-2 on 9e-13 for FixedTalbot). Prefer FixedTalbot unless cross-checking, or unless shift is needed.
shift moves the contour to the right when the transform has poles on the positive real axis.
MeanFieldHomogenization.Viscoelasticity.DeHoog Type
DeHoog(; N = 16, T = nothing, tol = 1e-9)De Hoog-Knight-Stokes inversion: the Bromwich integral is discretized into a Fourier series on the vertical line
whose slow convergence is accelerated by the quotient-difference algorithm and summed as a continued fraction. Ten to thirteen digits in practice.
Because the line stays in the right half-plane it never crosses a branch cut on GaverStehfest is wanted.
T is the scaling period. The nodes depend on T but not on t, so one node set — one pass over F — can serve several times at once. What limits the sharing is that the relative accuracy depends on the ratio
relative error (tol = 1e-9) | |
|---|---|
| 0.5 (i.e. | ≈ 1e-9 |
| 0.15 | ≈ 2e-10 |
| 0.05 | ≈ 1e-6 |
| 0.005 | ≈ 1e-2 |
| 0.0005 | ≈ 0.4 — meaningless |
and raising N barely helps below
Two modes follow:
T = nothing(the default) — on a grid, the times are sorted and split into blocks spanning at most a factor of three, each block getting its own and its own single pass overF. Accuracy is uniform, and a 200-point grid over seven decades costs roughly evaluations ofFinstead of . That is the reason to reach forDeHoogwhen each evaluation ofFis a homogenization scheme.Tgiven explicitly — one node set for everything, evaluations total. Only do this when the grid really is narrow; a warning is emitted for any time falling below .
Not a weighted sum
Unlike the other three, de Hoog is not linear in the transform values: the quotient-difference tables divide consecutive samples by one another. Tensor- and matrix-valued transforms are therefore inverted component by component, and a component that vanishes identically is short-circuited to zero rather than run through a
MeanFieldHomogenization.Viscoelasticity.DEFAULT_INVERSION Constant
DEFAULT_INVERSIONThe inversion method used when none is given: FixedTalbot(24).
Chosen on measured accuracy: it is the only one of the four that stays near 1e-12 on every kernel tested — exponential, polynomial, branch-cut, oscillatory and the fractional 2S2P1D pair alike (see FixedTalbot for the table).
Reasons to override it:
GaverStehfestwhen the transform must be evaluated at realonly — that keeps a whole homogenize_lcsweep in real arithmetic, costs a third fewer evaluations per point, and is the one way to useSelfConsistent(algorithm = NewtonDefault())in the Laplace-Carson route;DeHoogwhen inverting on a grid whose every point costs a homogenization, since one node set serves a whole block of times;TalbotTrefethento cross-check against the ECHOES reference, or when the transform has poles in the right half-plane.
MeanFieldHomogenization.Viscoelasticity.inverse_laplace Function
inverse_laplace(F, t, method = DEFAULT_INVERSION)
inverse_laplace(F, times::AbstractVector, method = DEFAULT_INVERSION)Numerically invert the Laplace transform F, returning
F is any callable p -> F(p) whose value supports + and multiplication by a scalar. Scalars, TensND tensors of any symmetry class and F returns — a TensTI transform gives back a TensTI with its axis preserved.
method selects the algorithm; see AbstractLaplaceInversion. FixedTalbot, TalbotTrefethen and DeHoog call F with Complex arguments; GaverStehfest only with real positive ones.
The vector form returns a Vector of results. With DeHoog(; T = ...) it shares one node set across the whole grid, which matters when each evaluation of F is a homogenization scheme.
Throws DomainError for
Name clash with Symbolics.jl
Symbolics exports a function also called inverse_laplace — a five-argument symbolic transform, inverse_laplace(expr, F, s, f, t), used for solving ODEs. The two are different functions, so using MeanFieldHomogenization, Symbolics makes the bare name ambiguous and Julia refuses it. Qualify whichever you mean:
MeanFieldHomogenization.inverse_laplace(F, t) # this one
Symbolics.inverse_laplace(expr, F, s, f, t) # theirsinverse_carson — the one to use for anything in the rheology catalog — is not affected.
Accuracy is absolute, not relative, in the tail
Every algorithm here controls the error against the scale of 1e-17, FixedTalbot(24) is off by a relative 1e4 — an absolute 1e-13, which is exactly what it promises.
This is intrinsic to numerical Laplace inversion, not a defect of one method, and it is why viscoelastic solids — whose relaxation function settles on a plateau
Examples
julia> using MeanFieldHomogenization
julia> isapprox(inverse_laplace(p -> 1 / (p + 2), 1.0), exp(-2.0); rtol = 1e-6)
trueSee also inverse_carson, inverse_carson_rate.
MeanFieldHomogenization.Viscoelasticity.inverse_carson Function
inverse_carson(Fstar, t, method = DEFAULT_INVERSION)
inverse_carson(Fstar, times::AbstractVector, method = DEFAULT_INVERSION)Invert the Laplace-Carson transform
i.e. inverse_laplace(p -> Fstar(p) / p, t, method).
Laplace-Carson is the transform of choice in viscoelasticity because it maps a constant to itself: an elastic modulus is its own transform, so the correspondence principle reads carson_relaxation and carson_creep.
Examples
julia> using MeanFieldHomogenization
julia> R = zener_maxwell(2.0, 3.0, 1.0); # E_∞ = 2, E₁ = 3, τ₁ = 1
julia> r = inverse_carson(p -> carson_relaxation(R, p), 0.5);
julia> isapprox(r, relaxation(R, 0.5); rtol = 1e-6)
trueMeanFieldHomogenization.Viscoelasticity.inverse_carson_rate Function
inverse_carson_rate(Fstar, t, method = DEFAULT_INVERSION; f_glassy)Return the time derivative Fstar, without differentiating the inversion.
Because
f_glassy is glassy_modulus.
Relation to ForwardDiff.derivative(t -> ..., t)
Differentiating the inversion with respect to t also works — every method here accepts a Dual time — and on the kernels tested the two routes are of comparable accuracy. The identity is worth having anyway because it asks nothing of the transform beyond what it already provides: no Dual is pushed into the nodes, so it applies when F is not differentiable, and it is the cheaper route when the rate is wanted alongside the value.
Internals
MeanFieldHomogenization.Viscoelasticity._accumulate Function
_accumulate(values, weights)Form weights[k] and values[k], seeding the accumulator with the first term rather than with zero(...).
That single choice carries two unrelated correctness requirements at once:
ForwardDiff— azero(T)seed derived from the node type would pin the accumulator to a plain float and silently drop the partials of the transform values;TensND—Base.zero(::AbstractTens{4,dim,T})returns aTensISOwhatever the input class is (the@evalloop overone/zeroinTensND/src/tens_isotropic.jl). Seeding withzero(A)would therefore collapse aTensTItransform onto the isotropic class and lose its axis. Never callingzerosidesteps the trap entirely.
MeanFieldHomogenization.Viscoelasticity._decompose Function
_decompose(x) -> (components::Vector, rebuild)Split a transform value into a vector of scalar components together with a closure putting them back into the same shape and symmetry class: TensND's to_vector / from_vector for a tensor.
Used only by DeHoog, whose quotient-difference acceleration divides samples by one another and is therefore not linear in the transform values. The other three methods never need this: they are weighted sums, so they run on the value type directly.
MeanFieldHomogenization.Viscoelasticity._gs_exact_weights Function
_gs_exact_weights(N) -> Vector{Rational{BigInt}}Salzer weights of the Gaver-Stehfest scheme, exactly,
MeanFieldHomogenization.Viscoelasticity._dehoog_from_values Function
_dehoog_from_values(values, t, method, Tscale)De Hoog-Knight-Stokes evaluation from transform samples already taken on the
The Fourier series R2M that removes the leading truncation error.
The QD tables divide samples by one another, so this is not linear in the transform values: tensor- and matrix-valued transforms are inverted component by component through _decompose.
MeanFieldHomogenization.Viscoelasticity._dehoog_blocks Function
_dehoog_blocks(order, times, ratio) -> Vector{UnitRange}Split the sorted indices order into consecutive runs whose times span at most ratio, so each run can share one de Hoog node set.
MeanFieldHomogenization.Viscoelasticity._DEHOOG_MIN_RATIO Constant
_DEHOOG_MIN_RATIOSmallest DeHoog still returns close to full accuracy. Measured on exponential and power-law kernels: the relative error is ≈ 2e-10 at N.
A shared node set therefore spans a factor _dehoog_blocks uses.
The model interface
MeanFieldHomogenization.Viscoelasticity.AbstractRheology Type
AbstractRheologyRoot supertype for scalar linear viscoelastic models — a spring, a Prony series, 2S2P1D, and everything in between.
A concrete model must define carson_relaxation; it should also define glassy_modulus and equilibrium_modulus, which are limits of that transform and are almost always elementary. Anything else it knows in closed form (relaxation, creep, carson_creep) is an override; what it does not know is obtained by numerical inversion.
Lift a pair of scalar models to a fourth-order isotropic tensor with iso_rheology / iso_rheology_E_nu; the result is an AbstractTensorRheology, which answers the same generics with tensors and additionally feeds the aging time-domain pipeline through ViscoLaw(model).
See the rheological model catalog.
MeanFieldHomogenization.Viscoelasticity.AbstractTensorRheology Type
AbstractTensorRheologyRoot supertype for tensor-valued viscoelastic models — a scalar model in each independent symmetry channel. The only concrete subtype at present is IsoRheology.
MeanFieldHomogenization.Viscoelasticity.carson_relaxation Function
carson_relaxation(model, p)The Laplace-Carson relaxation transform
p may be real (as GaverStehfest needs), complex (ForwardDiff.Dual, so implementations must stay generic in the argument type.
For an AbstractTensorRheology the value is a fourth-order tensor.
MeanFieldHomogenization.Viscoelasticity.carson_creep Function
carson_creep(model, p)The Laplace-Carson creep transform
In the Carson domain creep and relaxation are exact reciprocals, PronyCreep, burgers) override it and let carson_relaxation be the derived one.
MeanFieldHomogenization.Viscoelasticity.relaxation Function
relaxation(model, t)The relaxation function
Falls back to numerically inverting carson_relaxation with default_inversion; models with a closed form override it. glassy_modulus — the inversion itself has a pole there.
MeanFieldHomogenization.Viscoelasticity.creep Function
creep(model, t)The creep compliance
Falls back to numerically inverting carson_creep; models with a closed form override it.
MeanFieldHomogenization.Viscoelasticity.complex_modulus Function
complex_modulus(model, ω)The complex modulus
Its modulus storage_modulus, loss_modulus and loss_factor name the usual derived quantities.
MeanFieldHomogenization.Viscoelasticity.storage_modulus Function
storage_modulus(model, ω)MeanFieldHomogenization.Viscoelasticity.loss_modulus Function
loss_modulus(model, ω)MeanFieldHomogenization.Viscoelasticity.loss_factor Function
loss_factor(model, ω)MeanFieldHomogenization.Viscoelasticity.glassy_modulus Function
glassy_modulus(model)Closed form for every model in the catalog. Besides being the relaxation, it is the f_glassy argument of inverse_carson_rate.
MeanFieldHomogenization.Viscoelasticity.equilibrium_modulus Function
equilibrium_modulus(model)Zero exactly when the model is a fluid: stress relaxes away completely and the creep compliance grows without bound. See is_fluid.
MeanFieldHomogenization.Viscoelasticity.is_fluid Function
is_fluid(model) -> Booltrue when equilibrium_modulus vanishes, i.e. the model contains a dashpot in series with everything else and creeps indefinitely.
The distinction is structural rather than cosmetic: a fluid's creep compliance carries a term linear in PronyCreep, and it is what decides whether kelvin_to_maxwell and maxwell_to_kelvin look for a root in the outermost interval.
MeanFieldHomogenization.Viscoelasticity.default_inversion Function
default_inversion(model) -> AbstractLaplaceInversionThe inversion method relaxation and creep use when the model has no closed form and the caller gave none.
Defaults to DEFAULT_INVERSION; a model overrides it when its transform has a feature one algorithm handles better.
Discrete spectra, and the exact conversion
MeanFieldHomogenization.Viscoelasticity.PronyRelaxation Type
PronyRelaxation(E_inf, E, tau)A generalized Maxwell chain: an equilibrium spring E_inf in parallel with branches (E[i], tau[i]), each a spring and a dashpot in series.
Why the degenerate branches are separate fields
The ECHOES Python reference stores the equilibrium term inside the spectrum as a branch with Inf into a vector which is then sorted, exponentiated and differentiated — and Inf is poison for ForwardDiff, where Inf * 0 gives NaN in the partials. Keeping E_inf as its own scalar removes every special case.
Construction sorts the branches by increasing tau and merges any that agree to within merge_tol (relative), summing their moduli — two branches with the same relaxation time are one branch, not an error.
See maxwell_to_kelvin for the conversion, prony_fit_relaxation to obtain one by fitting an arbitrary transform, and zener_maxwell for the one-branch case.
MeanFieldHomogenization.Viscoelasticity.PronyCreep Type
PronyCreep(J_0, J, tau, phi = zero(J_0))A generalized Kelvin chain: an instantaneous spring of compliance J_0 in series with branches (J[i], tau[i]), each a spring and a dashpot in parallel, and — when phi > 0 — a dashpot of fluidity phi.
Fluidity, not viscosity
Inf does none of those things. It also makes the two Prony types exactly symmetric:
See kelvin_to_maxwell, prony_fit_creep, burgers and zener_kelvin.
MeanFieldHomogenization.Viscoelasticity.PRONY_MERGE_TOL Constant
PRONY_MERGE_TOLRelative spacing below which two branches of a Prony spectrum are considered the same branch and merged on construction. Also the scale below which maxwell_to_kelvin warns that the converted spectrum, though still mathematically exact, is ill-conditioned.
MeanFieldHomogenization.Viscoelasticity.PRONY_FLUID_TOL Constant
PRONY_FLUID_TOLRelative size below which an equilibrium modulus (or a fluidity) counts as zero, so the model is treated as a fluid (resp. a solid). Compared against the glassy modulus, so it is scale-free.
MeanFieldHomogenization.Viscoelasticity.maxwell_to_kelvin Function
maxwell_to_kelvin(m::PronyRelaxation; rtol = 1e-14) -> PronyCreepConvert a generalized Maxwell chain into the exactly equivalent generalized Kelvin chain, so that
How
In
with the last root present exactly when kelvin_to_maxwell, which does have a root below its first pole.
Each root is thus isolated before any arithmetic happens, and bisection in
and
Fluids
When
Differentiability
ForwardDiff traverses the conversion: the roots are lifted by the implicit function theorem (see _root_with_ad), so gradients of
Examples
julia> using MeanFieldHomogenization
julia> m = PronyRelaxation(8.0, [3.0, 17.0], [12.0, 23.0]);
julia> k = maxwell_to_kelvin(m);
julia> isapprox(carson_creep(k, 0.37) * carson_relaxation(m, 0.37), 1.0; rtol = 1e-12)
trueSee also kelvin_to_maxwell, prony_fit_creep.
MeanFieldHomogenization.Viscoelasticity.kelvin_to_maxwell Function
kelvin_to_maxwell(k::PronyCreep; rtol = 1e-14) -> PronyRelaxationConvert a generalized Kelvin chain into the exactly equivalent generalized Maxwell chain, so that maxwell_to_kelvin, and the operation Kelvin2Maxwell.py performs symbolically in ECHOES.
How
In
the last root existing exactly when
The residues, from
with
For a solid the result satisfies a free consistency identity — both sides are
which is checked internally and would catch any sign slip in the residues.
Examples
The Burgers model is a fluid Kelvin chain with one branch, so it must convert to a two-branch Maxwell chain with no equilibrium spring:
julia> using MeanFieldHomogenization
julia> b = burgers(1.0, 3.0, 2.0, 6.0);
julia> r = kelvin_to_maxwell(b);
julia> length(r), isapprox(equilibrium_modulus(r), 0.0; atol = 1e-12)
(2, true)See also maxwell_to_kelvin, prony_fit_relaxation.
MeanFieldHomogenization.Viscoelasticity.prony_fit_relaxation Function
prony_fit_relaxation(Rstar, taus; points = nothing, E_inf = nothing,
nonneg = true, lambda = 0.0) -> PronyRelaxationFit a generalized Maxwell chain with the prescribed relaxation times taus to an arbitrary Laplace-Carson relaxation transform Rstar, by least squares on a set of collocation points.
The model is linear in its unknowns,
so the fit is one linear solve — no iteration, no starting guess.
points— the Carson variables to collocate at. Defaults to2 * length(taus)values log-spaced over , which brackets the spectrum by a decade on each side.E_inf— the equilibrium modulus. Defaults toRstarevaluated at .Note
The ECHOES reference uses
rstar(1e-100)for this. That overflowsfor any fractional model — 2S2P1D included — so the default here is tied to the spectrum's own slowest time instead. nonneg— constrain (the default). A non-negative spectrum is what makes the fitted function completely monotone, hence passive; an unconstrained fit routinely produces small negative moduli that give a negativeloss_factorsomewhere.lambda— Tikhonov weight, useful when the times are packed more densely than the data can resolve.
Not differentiable
With nonneg = true the solve is an active-set method: the returned coefficients are a piecewise-smooth function of the input with a combinatorial switch in the middle. Treat a fit as a calibration step that produces a model, and differentiate the model — not the fit. An unconstrained fit (nonneg = false) is a plain linear solve and is differentiable.
See prony_fit_creep for the dual, and maxwell_to_kelvin for the exact conversion once a chain is in hand.
MeanFieldHomogenization.Viscoelasticity.prony_fit_creep Function
prony_fit_creep(Jstar, taus; points = nothing, J_0 = nothing, phi = 0.0,
nonneg = true, lambda = 0.0) -> PronyCreepThe creep-side twin of prony_fit_relaxation: fit a generalized Kelvin chain to a Laplace-Carson creep transform Jstar,
J_0 defaults to Jstar at
phi is not fitted: a
Internals
MeanFieldHomogenization.Viscoelasticity._phi_maxwell Function
_phi_maxwell(m::PronyRelaxation, σ)Its zeros are the retardation times of the equivalent Kelvin chain. Strictly increasing between consecutive poles, with
MeanFieldHomogenization.Viscoelasticity._dphi_maxwell Function
_dphi_maxwell(m::PronyRelaxation, σ)MeanFieldHomogenization.Viscoelasticity._psi_kelvin Function
_psi_kelvin(m::PronyCreep, σ)Its zeros are the relaxation times of the equivalent Maxwell chain. Strictly decreasing between consecutive poles, with
MeanFieldHomogenization.Viscoelasticity._dpsi_kelvin Function
_dpsi_kelvin(m::PronyCreep, σ)MeanFieldHomogenization.Viscoelasticity._bracketed_root Function
_bracketed_root(f, lo, hi; rtol = 1e-14, maxiter = 200) -> Float64Bisection in
Logarithmic rather than linear because relaxation spectra span six to ten decades: the geometric midpoint
The caller guarantees the sign change; a violation is a bug in the bracketing tables, not a user error, so it throws.
MeanFieldHomogenization.Viscoelasticity._expand_bracket Function
_expand_bracket(f, lo, growth, maxsteps) -> hiFind an upper end for a root known to lie somewhere in (lo, growth until the sign flips. Used for the outermost interval, which is unbounded whenever the model has a degenerate branch at the slow end.
MeanFieldHomogenization.Viscoelasticity._shrink_bracket Function
_shrink_bracket(f, hi, shrink, maxsteps) -> loThe mirror image of _expand_bracket for a root in (0, hi).
MeanFieldHomogenization.Viscoelasticity._root_with_ad Function
_root_with_ad(f, df, lo, hi) -> σLocate the root of f in [lo, hi] and hand back a value that carries the correct ForwardDiff partials.
The bisection runs on values only — an iteration count is discrete, and differentiating through it is meaningless. A single Newton step taken in the full Dual type then supplies the derivatives: since
which is exactly the implicit function theorem for a simple root. No nested duals, and no differentiation of the solver.
MeanFieldHomogenization.Viscoelasticity._sort_and_merge Function
_sort_and_merge(w, tau, merge_tol, who) -> (w, tau)Sort a spectrum by increasing time and fold together branches whose times agree to within merge_tol relative, summing their weights.
Merging is the correct preprocessing rather than an error: two branches with the same characteristic time are indistinguishable, and their sum is the same material. It also guarantees the strict interlacing the conversion relies on — maxwell_to_kelvin needs
MeanFieldHomogenization.Viscoelasticity._default_collocation Function
_default_collocation(taus) -> Vector{Float64}2 length(taus) Carson variables log-spaced over
Real and positive, so the transform is only ever sampled where every model in the catalog is defined — including the fractional ones, whose
MeanFieldHomogenization.Viscoelasticity._lsq Function
_lsq(A, b, nonneg, lambda) -> xLeast-squares solve of lambda and optionally constrained to
MeanFieldHomogenization.Viscoelasticity._nnls Function
_nnls(A, b; maxiter, tol) -> xLawson-Hanson non-negative least squares: minimize
Implemented here rather than pulled in as a dependency — it is forty lines, and the alternative in the ECHOES reference is an nlopt call. Being an active-set method it is not differentiable; see the warning on prony_fit_relaxation.
The model catalog
Elementary elements
MeanFieldHomogenization.Viscoelasticity.Spring Type
Spring(E)A Hookean spring:
The elastic limit of every other model, and the natural way to give a phase a constant modulus in one channel while another channel relaxes — the bulk modulus of a bituminous binder, for instance.
Being its own Laplace-Carson transform is not an accident: it is the property that makes the Carson convention the natural one in viscoelasticity.
MeanFieldHomogenization.Viscoelasticity.Dashpot Type
Dashpot(η)A Newtonian dashpot:
Its relaxation function is relaxation throws for this model — as does glassy_modulus, which would be infinite. A dashpot is meant to be assembled with springs (MaxwellUnit, burgers), not loaded on its own.
MeanFieldHomogenization.Viscoelasticity.MaxwellUnit Type
MaxwellUnit(E, η)A spring and a dashpot in series:
A fluid: the stress relaxes to zero and the strain grows without bound. Equivalent to PronyRelaxation(0, [E], [η/E]), and that is what kelvin_to_maxwell and friends produce; this type exists so the physical parameters
MeanFieldHomogenization.Viscoelasticity.KelvinUnit Type
KelvinUnit(E, η)A spring and a dashpot in parallel:
The strain is bounded but the instantaneous response is rigid, so the relaxation function again carries a Dirac and relaxation throws. Put it in series with a spring — zener_kelvin — for a usable solid.
MeanFieldHomogenization.Viscoelasticity.relaxation_time Function
relaxation_time(m::MaxwellUnit) -> NumberThe time constant
See retardation_time for the Kelvin counterpart.
MeanFieldHomogenization.Viscoelasticity.retardation_time Function
retardation_time(m::KelvinUnit) -> NumberThe time constant
The expression matches relaxation_time, but the two are not interchangeable: one describes a decay of stress, the other an approach to a bounded strain.
Named chains
MeanFieldHomogenization.Viscoelasticity.zener_maxwell Function
zener_maxwell(E_inf, E_1, tau_1) -> PronyRelaxationThe standard linear solid in its Maxwell (relaxation) representation: an equilibrium spring E_inf in parallel with one Maxwell branch.
zener_kelvin is the same material written the other way round, and maxwell_to_kelvin converts between them exactly — with, for one branch, the classical closed form
MeanFieldHomogenization.Viscoelasticity.zener_kelvin Function
zener_kelvin(E_glassy, E_delayed, tau_1) -> PronyCreepThe standard linear solid in its Kelvin (creep) representation: a spring of modulus E_glassy in series with one Kelvin cell of modulus E_delayed and retardation time tau_1.
See zener_maxwell and kelvin_to_maxwell.
MeanFieldHomogenization.Viscoelasticity.burgers Function
burgers(k_s, eta_s, k_p, eta_p) -> PronyCreepThe Burgers model: a Maxwell unit (k_s, eta_s) in series with a Kelvin cell (k_p, eta_p).
A fluid, so kelvin_to_maxwell returns a two-branch PronyRelaxation with no equilibrium spring — one branch more than the Kelvin form has, which is the degree count of the rational transform. Its relaxation function then matches the classical closed-form cosh/sinh expression to machine precision, which is how the fluid path of the conversion is validated (test/Viscoelasticity/test_prony.jl).
Parameter names follow the ECHOES reference tests/python/creep/fluage_echoes_ijss2013_jsanahuja_relaxBurgers.py.
Fractional elements
MeanFieldHomogenization.Viscoelasticity.ScottBlair Type
ScottBlair(V, α)The springpot (fractional dashpot, Scott-Blair element), interpolating continuously between a spring (
HuetSayegh and Model2S2P1D analytic in both domains.
The transform has a branch cut along the negative real axis, which the Talbot contours enclose rather than cross — FixedTalbot inverts it at 1e-12, no special handling needed.
MeanFieldHomogenization.Viscoelasticity.FractionalMaxwell Type
FractionalMaxwell(V_a, α, V_b, β)Two springpots in series:
By convention
MeanFieldHomogenization.Viscoelasticity.FractionalKelvin Type
FractionalKelvin(V_a, α, V_b, β)Two springpots in parallel:
With
MeanFieldHomogenization.Viscoelasticity.FractionalZener Type
FractionalZener(E_inf, E_0, tau, α)The fractional standard solid, also known as the Cole-Cole model:
with zener_maxwell; smaller
The time-domain form needs MittagLeffler.jl (a weak dependency); without it, relaxation falls back to numerically inverting the closed-form transform above, which costs a little accuracy and nothing else.
MeanFieldHomogenization.Viscoelasticity.Rabotnov Type
Rabotnov(mu_0, lambda_0, α, β)The Rabotnov fractional-exponential kernel, in the form ECHOES uses as its analytical benchmark for the aging pipeline (tests/python/creep/fluage_echoes_maxwell_papier_rabotnov.py):
The Carson transform is elementary
Since
with no Mittag-Leffler function anywhere. The model is therefore complete without the MittagLeffler extension: the transform is closed form and the inversion supplies the time domain to about 1e-10. With the extension loaded, relaxation uses the closed form instead — the two agree, which is one of the cross-checks in test/Viscoelasticity/test_rheology.jl.
mu_0 is the glassy modulus (the
Bituminous binders and long-term creep
MeanFieldHomogenization.Viscoelasticity.HuetSayegh Type
HuetSayegh(E00, E0, δ, τ, k, h)The Huet-Sayegh model: two parabolic (springpot) elements and a spring in series, the whole in parallel with a spring E00.
E00 is the static modulus, E0 the glassy one, and Model2S2P1D there is no series dashpot, so the strain stays bounded.
MeanFieldHomogenization.Viscoelasticity.Model2S2P1D Type
Model2S2P1D(E00, E0, δ, τ_E, k, h, β)The 2S2P1D model of Di Benedetto and Olard — 2 Springs, 2 Parabolic elements, 1 Dashpot — the reference rheological model for bituminous binders and mixtures:
E00 is the static modulus, E0 the glassy one,
One model, an exact pair in both domains
Because
term by term — see creep_kernel. So 2S2P1D is simultaneously an exact Laplace-Carson model and an exact Volterra model: the same material can be pushed through homogenize_lc and through homogenize_alv with no approximation on the input side, and the two must agree. That is what makes it the headline cross-validation of the two routes.
creep_kernel_law returns ViscoLaw for the aging pipeline.
The field names in older code are swapped
docs/src/applications/bituminous.md used to define its own struct whose fields were named E0 for the static modulus and Einf for the glassy one — the reverse of the convention here and in the literature. The formula and the positional order were already right, so migrating that page is a pure rename with no numerical change; the twelve calibrated parameter sets carry over argument for argument.
MeanFieldHomogenization.Viscoelasticity.creep_kernel Function
creep_kernel(m::Model2S2P1D, t)The dimensionless creep kernel
the exact term-by-term counterpart of carson_creep_kernel.
MeanFieldHomogenization.Viscoelasticity.carson_creep_kernel Function
carson_creep_kernel(m::Model2S2P1D, p)The dimensionless denominator creep_kernel.
MeanFieldHomogenization.Viscoelasticity.creep_kernel_law Function
creep_kernel_law(m::Model2S2P1D) -> ViscoLawThe dimensionless 2S2P1D creep kernel :creep ViscoLaw
This is the entry point that lets one 2S2P1D object drive both routes. The relaxation modulus in the time domain is
where
m = Model2S2P1D(1e-7, 1000.0, 2.2, 1.945e-3, 0.22, 0.63, 50.0)
Φ = trapezoidal_matrix(creep_kernel_law(m), times) # n × n
R = m.E00 * I + (m.E0 - m.E00) * volterra_inverse(Φ; block_size = 1)and that discrete R must agree with relaxation(m, t) obtained by inverting carson_relaxation — the two-route consistency check of test/Viscoelasticity/test_rheology.jl.
Reproduces VM(...) of the ECHOES reference tests/python/creep/modele2S2P1D.py.
MeanFieldHomogenization.Viscoelasticity.LogarithmicCreep Type
LogarithmicCreep(E, C, τ)The logarithmic creep law used for concrete at long times:
with
The transform is evaluated through SpecialFunctions.expintx, the scaled form
This is the non-aging skeleton of the aging law logcompliance in the ECHOES ageing_visco_mat.py; the aging version, where
Lifting to tensors, and homogenizing
MeanFieldHomogenization.Viscoelasticity.AbstractIsoPairing Type
AbstractIsoPairingWhich pair of scalar quantities an IsoRheology is built from. A trait parameterizing the type rather than a Symbol field, so the choice is resolved at compile time and stays extensible.
Concrete pairings: BulkShear, YoungPoisson.
MeanFieldHomogenization.Viscoelasticity.BulkShear Type
BulkShearPairing by bulk and shear: the two channels of the isotropic tensor relax independently, which is the physically natural description and the one every scheme works in internally.
MeanFieldHomogenization.Viscoelasticity.YoungPoisson Type
YoungPoissonPairing by Young's modulus and Poisson's ratio, the pair a laboratory reports.
A viscoelastic Poisson ratio is a Laplace-Carson object
The conversion
is a pointwise algebraic identity in the Carson domain only. In the time domain the same relation is a Volterra quotient: V3k = VE · inv(I - 2Vν) on discretized operators — tests/python/creep/modele2S2P1D.py.)
So a time-dependent Poisson ratio belongs on this side of the fence. A constant nu is unambiguous and is handled as a special case, including for the closed-form time-domain values.
MeanFieldHomogenization.Viscoelasticity.IsoRheology Type
IsoRheology{P, A, B}(a, b)An isotropic fourth-order viscoelastic model built from two scalar channels a and b, combined according to the pairing P.
Build one with iso_rheology or iso_rheology_E_nu rather than by calling the constructor.
It answers the same generics as a scalar model — carson_relaxation, carson_creep, relaxation, creep, complex_modulus — with TensISO{4,3} values, and converts to a ViscoLaw for the aging pipeline.
MeanFieldHomogenization.Viscoelasticity.iso_rheology Function
iso_rheology(k_model, mu_model) -> IsoRheology{BulkShear}Isotropic model whose bulk channel follows k_model and shear channel mu_model, each any AbstractRheology.
A Real in either slot is promoted to a Spring, so the common case of an elastic bulk modulus and a relaxing shear one reads
iso_rheology(2500.0, Model2S2P1D(1e-7, 1000.0, 2.2, 1.945e-3, 0.22, 0.63, 50.0))Examples
julia> using MeanFieldHomogenization, TensND
julia> m = iso_rheology(zener_maxwell(30.0, 10.0, 1.0), zener_maxwell(10.0, 5.0, 0.5));
julia> C = relaxation(m, 0.0); # the glassy stiffness
julia> all(isapprox.(get_data(C), (3 * 40.0, 2 * 15.0)))
trueMeanFieldHomogenization.Viscoelasticity.iso_rheology_E_nu Function
iso_rheology_E_nu(E_model, nu) -> IsoRheology{YoungPoisson}Isotropic model given by a Young's-modulus channel and a Poisson ratio, where nu is either a constant Real or itself an AbstractRheology.
See YoungPoisson for why a time-dependent nu only makes unambiguous sense in the Laplace-Carson domain.
MeanFieldHomogenization.Viscoelasticity.homogenize_lc Function
homogenize_lc(build_cell, scheme, property = :C; p)
homogenize_lc(build_cell, scheme, property = :C; times, method = DEFAULT_INVERSION, kw...)Homogenize a non-aging viscoelastic composite through the correspondence principle, and — in the second form — bring the answer back to the time domain.
build_cell(p) is a closure returning the homogenization cell with every phase property evaluated at the Carson variable RVE, a LayeredSphere, a Laminate, a nested chain of them. Extra keyword arguments are forwarded to homogenize.
Example
matrix = iso_rheology(Spring(2500.0), Model2S2P1D(1e-7, 1000.0, 2.2, 1.945e-3, 0.22, 0.63, 50.0))
aggreg = iso_rheology(Spring(30000.0), Spring(22000.0))
function cell(p)
rve = RVE()
add_phase!(rve, :MASTIC, Ellipsoid(1.0), Dict(:C => carson_relaxation(matrix, p)); fraction = :rest)
add_phase!(rve, :AGG, Ellipsoid(1.0), Dict(:C => carson_relaxation(aggreg, p));
fraction = 0.35)
return rve
end
E_star = homogenize_lc(cell, MoriTanaka(), :C; p = im * 2π * 10) # 10 Hz
R_of_t = homogenize_lc(cell, MoriTanaka(), :C; times = exp10.(-4:0.1:2))Choosing the inversion method
Each time costs one inversion, and each inversion costs
FixedTalbot(the default) — most accurate, 24 complex homogenizations per time;GaverStehfest— 16 real homogenizations per time. Real arithmetic is roughly twice as cheap per evaluation, and it is the only way to useSelfConsistent(algorithm = NewtonDefault())here, whoseForwardDiffJacobian cannot carry aDualover a complex scalar. The price is about five significant digits instead of twelve;DeHoog— one node set serves a whole block of times, so a multi-decade grid costs far fewer homogenizations than any per-point method. This is usually the right answer for a master curve.
Relation to the time-domain route
For a non-aging material this and homogenize_alv compute the same thing by disjoint means — no shared code — so agreement between them is a real check on both. ViscoLaw(model) turns the same model objects into the kernels homogenize_alv needs.
Optional Mittag-Leffler support
MeanFieldHomogenization.Viscoelasticity._mittag_leffler Function
_mittag_leffler(a, b, z) -> value or `nothing`The two-parameter Mittag-Leffler function
Returns nothing when MittagLeffler.jl is not loaded, which is the signal callers use to fall back on inverting the closed-form Carson transform:
ml = _mittag_leffler(α, one(α), -(t / τ)^α)
ml === nothing && return inverse_carson(p -> carson_relaxation(m, p), t, method)Load the package to switch the closed forms on:
using MittagLeffler # activates MeanFieldHomogenizationMittagLefflerExtThe extension declines more than it accepts
MittagLeffler.jl v1.0.0 silently returns 1.0 for mittleff(1.3, 1.0, -0.5) gives 1.0 where the defining series gives 0.6330079. The extension therefore answers only for
FractionalZener (Rabotnov with the physical
This replaces a Python detour
Before this extension, the Rabotnov benchmark reached scripts/52_rabotnov_mittag_leffler.jl, and §11 of the viscoelasticity manual). That is no longer needed: the transform is closed-form and the time-domain reference is a registered Julia package away.