sandbox/acastillo/output_fields/tests_quantities/test_interfacial_power_routes.c
Testing
that the two routes of interfacial_power.h agree
interfacial_power.h computes
\Psi_\sigma =
\int\mathbf{u}\cdot\mathbf{f}_\sigma\,dV twice over. Route 1
reads back the acceleration a that the solver actually
applied and divides out the face weights; route 2 rebuilds \phi = \sigma\kappa and forms \phi\nabla f with iforce.h’s
stencil. They should return the same number, which is what makes route 2
– the cheap one, insensitive to event ordering – usable in place of the
ground truth.
An oblate drop is released from rest with no gravity, so surface
tension is the only force in the problem and \mathbf{a} contains nothing else. The
comparison is made every timestep from a projection event,
which is the first hook where a is complete and
u is still the velocity the force acted on: the
acceleration events of tension.h and
iforce.h have run, and centered.h’s
correction() has not. Declaring the event here rather than
in a header is what puts it first among the projection
events, since same-named events run in reverse declaration order.
ag is {0} because there is no gravity. Were
there any, it would have to be passed in and subtracted; and under
REDUCED route 1 cannot be used at all,
reduced.h folding buoyancy into the same \phi.
The grid is uniform. On trees the two routes are expected to
differ wherever the interface sits on a refinement boundary,
iforce.h swapping f’s prolongation to the
pressure’s before differencing and route 2 not; that is a separate claim
and not what is tested here.
interfacial_power_routes.asc gets one row per timestep,
i err_routes psi, the deviation normalised by |\Psi_\sigma| and the value itself, so the
checker can reject a run in which \Psi_\sigma never departs from zero and the
agreement is vacuous. test_interfacial_power.py applies the
thresholds.
#include "navier-stokes/centered.h"
#include "two-phase.h"
#include "tension.h"
#include "acastillo/output_fields/interfacial_power.h"
#define RDROP 0.25
#define SIGMA 1.0
FILE * fp = NULL;
int main() {
L0 = 1.0;
X0 = Y0 = Z0 = -L0 / 2;A density ratio, so that alpha varies across the
interface and route 1’s division by the face weights is actually
exercised. Inviscid: viscosity would add nothing to \mathbf{a} but would shorten the
timestep.
rho1 = 1.;
rho2 = 0.1;
mu1 = mu2 = 0.;
f.sigma = SIGMA;
#if dimension == 3
N = 32;
#else
N = 64;
#endif
init_grid (N);
run();
}An oblate spheroid: at rest it is not an equilibrium shape, so the drop oscillates and \Psi_\sigma changes sign during the run.
event init (i = 0) {
fraction (f, sq (RDROP) - sq (x/1.3) - sq (1.3*y) - sq (z));
if (pid() == 0) {
fp = fopen ("interfacial_power_routes.asc", "w");
fprintf (fp, "# i err_routes psi\n");
}
}
event projection (i++) {
coord ag = {0.};
scalar d[];
double psi_a = interfacial_power_acceleration (ag);
double psi_c = interfacial_power_curvature (f, d);Normalised by the value itself, so the tolerance means the same thing at every amplitude. The first timestep starts from rest and gives \Psi_\sigma = 0 exactly, which would divide by zero.
double scale = fabs (psi_a) > 0. ? fabs (psi_a) : 1.;
if (pid() == 0) {
fprintf (fp, "%d %.17g %.17g\n", i, fabs (psi_a - psi_c)/scale, psi_a);
fflush (fp);
}
}
event stop (i = 20) {
if (pid() == 0) {
fclose (fp);The report goes to stderr, i.e. to the log diffed
against test_interfacial_power_routes.ref.
system ("python3 ../test_interfacial_power.py "
"--routes interfacial_power_routes.asc 1>&2");
}
return 1;
}