src-local/embed-vof-tube.h
embed-vof-tube.h
Compatibility layer for combining embedded boundaries
(embed.h) with axisymmetric Volume-Of-Fluid
two-phase flow (axi.h +
two-phase.h).
Why this header exists
Basilisk’s embedded boundaries and VOF advection are almost compatible out of the box, but three couplings must be handled by the case:
Missing axi metric after
solid()and after adaptation. With bothAXIandEMBEDdefined, the metric fields must satisfy \(c_m = y\,c_s\) and \(f_m = y\,f_s\).axi.hcomputes them once in itsmetricevent (whencs = 1everywhere), so every later call tosolid()— and everyadapt_wavelet()on trees, which re-prolongatescs/fs— leavescm/fmstale.axi.hprovidescm_update()andfm_update()for exactly this purpose and the upstream testsrc/test/missing_metric.cguards the coupling.embed_axi_metric_sync()wraps the required calls.VOF fraction inside the solid.
vof.honly updates cells withcs > 0, but grid adaptation prolongatesfwithout knowledge ofcs, so refined solid cells can inherit non-zeroffrom fluid neighbours. The height-function curvature used bytension.his not embed-aware: any spuriousfinside the solid corrupts height columns near the wall and hence the film curvature.vof_solid_cleanup()resetsfto the continuous-phase value (heref = 0) in all full-solid cells. This is consistent with a wall perfectly wetted by the continuous phase.Interface–wall separation.
heights.h/curvature.hhave no knowledge ofcs, so the wetting film between the interface and the embedded wall must stay resolved by several cells. This header cannot enforce that; cases must refine the wall region (e.g. by adapting oncs) and monitor the film thickness.
Surface tension itself is safe: iforce.h
guards its face loop with fm.x[] > 0, so
no force is applied on embedded faces.
Public API
embed_axi_metric_sync(): recomputecm/fmand restrict{cs, fs, cm, fm}; call aftersolid()and afteradapt_wavelet().tube_solid(Rtube): embed a cylindrical tube wall of radiusRtube(fluid aty < Rtube) and synchronise the metric.tube_refinement_geometry_begin()/tube_refinement_geometry_end(): bracket an explicitrefine()of a fixed tube, repairing MPI coarse/ghost geometry while leaving solution-field transfer operators unchanged.vof_solid_cleanup(f): clampfand reset it to the continuous phase in full-solid cells; call afteradapt_wavelet().
#ifndef EMBED_VOF_TUBE_H
#define EMBED_VOF_TUBE_H5556embed_axi_metric_sync()
Recomputes the metric fields from the current
cs/fs and restricts all four
fields on trees. With EMBED but no
AXI, only the restriction is needed (the
metric is the solid fraction itself, handled by
embed.h). After tube_solid()
registers a fixed cylindrical wall, the fractions and
metrics are reconstructed from that geometry at every
stored tree level, including MPI neighbour cells. This
specialisation assumes the wall remains a straight tube
of the registered radius.
#if TREE && AXI && EMBED
static double tube_metric_radius;
// Integral of the radius over the fluid portion of a radial cell interval,
// divided by its width. This is both cm and the axial-face metric of a tube.
static double tube_axial_face_metric (double r, double d)
{
double lo = r - d/2., hi = min(r + d/2., tube_metric_radius);
return hi > lo ? (hi*hi - lo*lo)/(2.*d) : 0.;
}
#endif
static inline void embed_axi_metric_sync (void)
{
#if defined(AXI) && defined(EMBED)
cm_update (cm, cs, fs);
fm_update (fm, cs, fs);
#if TREE
// A stationary tube has an exact geometric description at every level.
// Populate inactive parents and MPI neighbour storage as well as leaves:
// face restriction can read either side of a partition boundary.
if (tube_metric_radius > 0.)
foreach_cell() {
cs[] = clamp((tube_metric_radius - y)/Delta + .5, 0., 1.);
fs.x[] = cs[];
if (allocated(1)) fs.x[1] = cs[];
fs.y[] = y - Delta/2. < tube_metric_radius ? 1. : 0.;
if (allocated(0,1)) fs.y[0,1] = y + Delta/2. < tube_metric_radius ? 1. : 0.;
cm[] = tube_axial_face_metric(y, Delta);
fm.x[] = tube_axial_face_metric(y, Delta);
if (allocated(1)) fm.x[1] = tube_axial_face_metric(y, Delta);
double r = y - Delta/2.;
fm.y[] = r < tube_metric_radius ? max(r,1e-20) : 0.;
r = y + Delta/2.;
if (allocated(0,1)) fm.y[0,1] = r < tube_metric_radius ? max(r,1e-20) : 0.;
}
#endif
#endif
#if defined(EMBED) && TREE
// foreach_cell() does not perform automatic boundary-state tracking.
for (scalar s in {cs, fs, cm, fm}) set_dirty_stencil(s);
restriction ({cs, fs, cm, fm});
#endif
}tube_solid()
Embeds the tube wall: solid for \(y > R_{tube}\), fluid below.
Parameters
Rtube: tube radius in code units.
static inline void tube_solid (double Rtube)
{
#if TREE && AXI && EMBED
tube_metric_radius = Rtube;
#endif
solid (cs, fs, Rtube - y);
embed_axi_metric_sync();
}Exact tube geometry during explicit tree refinement
When a dump is restored with a different MPI
decomposition, coarse/ghost cs and
fs storage can be stale even when every
active leaf has the correct straight-wall geometry.
refine_embed_linear() legitimately requires
those coarse neighbours while prolongating pressure and
velocity. The callback below repairs the complete 3-by-3
coarse stencil of each parent before its children are
created, then gives the children their exact tube
fraction.
Bracket only explicit fixed-tube
refine() calls with the begin/end pair. The
standard embedded fraction operator is restored
immediately afterwards; solution-field and momentum
operators are never replaced. Follow the end call with
embed_axi_metric_sync() and the usual VOF
solid cleanup.
#if TREE && AXI && EMBED
static void tube_fraction_refine_exact (Point point, scalar field)
{
for (int i = -1; i <= 1; i++)
for (int j = -1; j <= 1; j++)
if (allocated(i,j)) {
double radius = y + j*Delta;
cs[i,j] = clamp((tube_metric_radius - radius)/Delta + .5, 0., 1.);
fs.x[i,j] = cs[i,j];
fs.y[i,j] = radius - Delta/2. < tube_metric_radius ? 1. : 0.;
}
foreach_child()
field[] = clamp((tube_metric_radius - y)/Delta + .5, 0., 1.);
}
#endif
static inline void tube_refinement_geometry_begin (void)
{
#if TREE && AXI && EMBED
assert (tube_metric_radius > 0.);
cs.refine = cs.prolongation = tube_fraction_refine_exact;
#endif
}
static inline void tube_refinement_geometry_end (void)
{
#if TREE && AXI && EMBED
cs.refine = embed_fraction_refine;
cs.prolongation = fraction_refine;
#endif
}vof_solid_cleanup()
Enforces the continuous-phase value
f = 0 in full-solid cells and clamps
f elsewhere. Cut cells
(0 < cs < 1) are left untouched:
their volume fraction is a valid fluid quantity.
Parameters
c: VOF volume fraction field (dispersed phase atc = 1).
static inline void vof_solid_cleanup (scalar c)
{
#if defined(EMBED)
foreach() {
if (cs[] <= 0.)
c[] = 0.;
else
c[] = clamp (c[], 0., 1.);
}
#endif
}
#endif