Two-phase flow
MILET implements McKenzie-type two-phase flow — porous melt migration through a compacting viscous matrix — as a switchable extension of the Stokes solve (do_two_phase = true in the [twophase] section). The implementation lives in src/physics/TwoPhase.jl; the design spec is doc/design/two_phase_flow.md. At zero porosity the system reduces to single-phase Stokes, and a regression test enforces this limit.
Governing equations (3-field formulation)
Following Katz (2008) and Rhebergen, Wells & Katz (2014), with incompressible phases and the Boussinesq approximation, the unknowns are the solid velocity
The mixture density twophase_buoyancy, same volume-mean perturbation convention as the single-phase buoyancy_cellfield; body_force_full = true disables the reference subtraction). Melt buoyancy relative to the solid enters the
Constitutive closures
Built cell-wise from the marker-projected porosity by twophase_cell_fields (src/physics/TwoPhase.jl):
| Closure | Formula | Function | Config keys |
|---|---|---|---|
| Permeability | permeability | k0, phi0, n_perm, phi_min | |
| Compaction viscosity | inv_compaction_viscosity | phi_min | |
| Melt-weakened matrix viscosity | melt_weakened_viscosity | alpha_phi |
is the visco-elasto-plastic matrix viscosity from the marker projection (see Rheology);
The compaction length
is available as a diagnostic (compaction_length) and is the resolution guide: the grid should resolve
Weak form and discretization
build_twophase_forms assembles the symmetric indefinite bilinear form with the same integration-by-parts conventions as src/physics/Stokes.jl:
The optional build_stokes_forms; free-slip penalty and traction boundary conditions are reused from the single-phase path.
The
Spaces (build_twophase_spaces): FESpaceBundle velocity space, fe_order_v = 2) and two fresh fe_order_p = 1), combined into a 3-field MultiFieldFESpace (X3/Y3). By default pf_surface0 = true imposes homogeneous Dirichlet "surface" boundary tag so melt escapes freely at the surface (extraction sink).
Solver: solve_twophase! assembles an AffineFEOperator and solves the monolithic 3-field system with a cached MUMPS factorization (cached_mumps_solve!, src/solvers/Mumps.jl) — the sparsity pattern is constant across steps, so only the numeric factorization is repeated. See Linear solvers.
Driver coupling (src/time_stepping/Driver.jl): when do_two_phase is on, the step loop replaces picard_stokes! with a Picard iteration (n_picard iterations) of project_markers_to_cells! → project_porosity_to_cells! → solve_twophase! → recover_invariants!. The solved state.ph = pfh), and the per-cell compaction pressure is recovered as cell means by compaction_pressure_cells.
Porosity transport on markers
Porosity lives on the markers (mk.porosity::Vector{Float32}, src/particles/Particles.jl): it advects with the markers, is inherited on reseeding, and is checkpointed (format v3). Markers initialize at phi_init when set). project_porosity_to_cells! produces the arithmetic host-cell mean
Porosity evolves by operator splitting, after the two-phase solve (update_marker_porosity!):
evaluated per marker from the host cell's solved phi_max, default 0.5). The update is pure per-marker arithmetic and threads over markers on the Cartesian fast path.
Melting coupling
With two-phase flow enabled, melting sources porosity instead of deleting melt: in apply_marker_phase_and_melt! (Driver.jl), each marker's melt-fraction change apply_melting! is added to its porosity (clamped to melt_to_porosity! (TwoPhase.jl) implements the same rule.
Melt-segregation CFL
The Darcy segregation flux can outrun the solid-velocity CFL. The melt velocity is
and max_melt_segregation_speed estimates
with CFL = maxxystep, on top of the usual limits in Time stepping.
The φ = 0 limit (regression gate)
As test/smoke/test_twophase.jl enforces this on the falling-block case: with do_two_phase = true and
Configuration
All keys live in the [twophase] TOML section (defaults from cases/_defaults.toml; loaded into cfg.twophase_* by src/io/TOMLLoader.jl and bundled into the TwoPhaseParams struct):
[twophase]
do_two_phase = false
k0 = 1e-12 # reference permeability at phi0 (m²)
phi0 = 0.01 # reference porosity
n_perm = 3.0 # permeability exponent
mu_fluid = 1.0 # melt viscosity (Pa·s)
rho_fluid = 2800.0 # melt density (kg/m³)
alpha_phi = 27.0 # matrix melt-weakening exponent (Mei et al. 2002)
phi_min = 1e-4 # regularization porosity (phi → 0 limit)
phi_max = 0.5 # marker porosity cap
pf_surface0 = false # Dirichlet p_f = 0 at the surface (melt escapes)
phi_init = 0.0 # uniform initial marker porositySee also Parameter file, Melting, and Markers.