Comparing loading-path trajectories
The differential scheme and path dependence shows that the incorporation order matters, through the endpoint k_eff of a fraction sweep. This page shows how it matters: several trajectories racing to the same target fractions, watched the whole way through $\tau \in [0, 1]$ rather than read off only at $\tau = 1$.
A two-phase RVE with a strong contrast
To make the divergence between trajectories visible at a glance, the inclusions here are a genuinely stiff and a genuinely soft phase — a 5× contrast on each side of the matrix, rather than the milder pair used in the previous tutorial:
using MeanFieldHom
using TensND
using Plots
gr() # headless backend; GKSwstype is set to "100" in make.jl
C_M = TensISO{3}(3 * 5.0, 2 * 2.0) # matrix : k = 5, μ = 2
C_I1 = TensISO{3}(3 * 30.0, 2 * 12.0) # phase I1 : stiff, 6× the matrix
C_I2 = TensISO{3}(3 * 0.5, 2 * 0.2) # phase I2 : compliant, 0.1× the matrix
F1, F2 = 0.2, 0.2 # target volume fractions
function build_rve()
rve = RVE(:M)
add_matrix!(rve, Ellipsoid(1.0), Dict(:C => C_M))
add_phase!(rve, :I1, Ellipsoid(1.0), Dict(:C => C_I1); fraction = F1)
add_phase!(rve, :I2, Ellipsoid(1.0), Dict(:C => C_I2); fraction = F2)
return rve
endFour trajectories to the same target
Every trajectory below reaches f₁ = f₂ = 0.2 at τ = 1 — only the path taken to get there differs:
Proportional— both phases grow in lock-step.Sequential(:I1, :I2)— the stiff phase is incorporated to completion first, the soft phase second, into the resulting (already stiffened) medium.Sequential(:I2, :I1)— the reverse order.Path(:I1 => τ -> τ^2, :I2 => τ -> 2τ - τ^2)— a continuous schedule where:I1is backloaded ($\tau^2$ starts flat, catches up near $\tau = 1$) and:I2is frontloaded ($2\tau - \tau^2 = 1-(1-\tau)^2$ grows fast then flattens) — both curves are monotone on[0, 1]with the required endpoints, but neither is proportional.
differential_path returns the full trajectory C^{hom}(\tau) in one solve, rather than the single value homogenize returns at τ = 1:
const NSTEPS = 200
function eval_path(traj)
scheme = DifferentialScheme(; trajectory = traj, nsteps = NSTEPS, abstol = 1e-9, reltol = 1e-7)
τ, Cs = differential_path(build_rve(), scheme, :C)
kμ = k_mu.(Cs)
return τ, first.(kμ), last.(kμ)
end
paths_to_run = (
("Proportional", Proportional(), :black),
("Sequential(I1 → I2)", Sequential(:I1, :I2), :blue),
("Sequential(I2 → I1)", Sequential(:I2, :I1), :red),
("Path (I1 backloaded, I2 frontloaded)", Path(:I1 => τ -> τ^2, :I2 => τ -> 2τ - τ^2), :green),
)
results = [(name, eval_path(traj)...) for (name, traj, _) in paths_to_run]Watching the curves diverge
p_k = plot(; xlabel = "τ", ylabel = "k_eff(τ)", title = "Bulk modulus", legend = :right, framestyle = :box)
p_μ = plot(; xlabel = "τ", ylabel = "μ_eff(τ)", title = "Shear modulus", legend = false, framestyle = :box)
for ((name, _, color), (_, τ, k, μ)) in zip(paths_to_run, results)
plot!(p_k, τ, k; label = name, color = color, lw = 2)
plot!(p_μ, τ, μ; label = name, color = color, lw = 2)
end
plot(p_k, p_μ; layout = (1, 2), size = (900, 420))All four curves start together — in the dilute limit, the order in which two vanishingly small increments are added cannot matter — and pull apart as $\tau$ grows. Sequential(I1, I2) ends up the stiffest of the four: the soft phase, added last, is diluted into an already-reinforced medium and does comparatively less damage than it would in a softer host. Sequential(I2, I1) is the mirror image and ends up the softest. The two Proportional and Path curves sit strictly between the two sequential extremes — expected, since every intermediate schedule interpolates between the two orderings in some sense — but they are not equal to each other, which is the point: the trajectory itself is a modeling choice, not just its start and end.
using Printf
for (name, _, k, μ) in results
@printf "%-38s k_eff(1) = %.5f μ_eff(1) = %.5f\n" name k[end] μ[end]
endProportional k_eff(1) = 4.10026 μ_eff(1) = 1.96032
Sequential(I1 → I2) k_eff(1) = 4.28232 μ_eff(1) = 2.03961
Sequential(I2 → I1) k_eff(1) = 3.92579 μ_eff(1) = 1.88116
Path (I1 backloaded, I2 frontloaded) k_eff(1) = 3.98228 μ_eff(1) = 1.90718A standalone, plain-Julia version of this comparison — same RVE, same four trajectories, writing a PNG instead of embedding it in the docs — lives in scripts/24_differential_loading_paths.jl.