/** # Double projection This option for the [centered Navier--Stokes solver](centered.h) is inspired by [Almgren et al., 2000](#almgren200). These authors first recall that, while for the exact projection method, the definition of the pressure is unambiguous, in the case of the *approximate* projection method (used in [centered.h]()), several pressures can be defined. Exploiting the different properties of these different definitions may be useful in some cases. ## Standard approximate projection We first summarise the standard approximate projection scheme, as implemented in [centered.h](). 1. Advection/viscosity $$ \frac{u^{\star} - u^n}{\Delta t} = (u \cdot \nabla u)^{n + 1 / 2} + \alpha \nabla \cdot (\mu \nabla D)^{\star} $$ 2. Acceleration $$ u^{\star}_f = \overline{u^{\star}} + \Delta ta_f $$ 3. Projection $$ u^{n + 1}_f = P (u^{\star}_f, p^{n + 1}) $$ 4. Centered pressure gradient correction $$ g^{n + 1} = \overline{a_f - \alpha_f \nabla p^{n + 1}} $$ $$ u^{n + 1} = u^{\star} + \Delta tg^{n + 1} $$ with $P(u,p)$ the projection operator and the overline designating either cell-center to cell-face averaging or reciprocally. ## Double approximate projection As its name indicates this scheme adds an extra projection step and defines two pressures: the standard one used to project the face-centered velocity field $u_f$ (renamed $\mathbf{\delta p}$ below), and $p^{n+1}$ used to compute the cell-centered pressure gradient $g$. This new pressure is obtained by projecting only the *update* (i.e. its evolution in time) to the centered velocity field. Note that in the case of an exact projection these two projections are identical since the divergence of the velocity field at the start of the timestep is zero. The scheme can be summarised as: 1. Advection/viscosity $$ \frac{u^{\star} - u^n}{\Delta t} = (u \cdot \nabla u)^{n + 1 / 2} + \alpha \nabla \cdot (\mu \nabla D)^{\star} \mathbf{+ g^n = A^{n+1/2} + g^n} $$ $$ \mathbf{A_f = \overline{A^{n+1/2}} + \Delta ta_f} $$ 2. Acceleration $$ u^{\star}_f = \overline{u^{\star}} $$ 3. Projection $$ u^{n + 1}_f = P (u^{\star}_f, \mathbf{\delta p}) $$ 4. Approximate projection $$ u^{n + 1} = u^{\star} - \Delta t \overline{\alpha_f \nabla \delta p} $$ 5. Second projection $$ \mathbf{P(A_f,p^n+1)} $$ $$ g^{n + 1} = \overline{a_f - \alpha_f \nabla p^{n + 1}} $$ where the additions to the previous scheme are highlighted in bold. Why is this useful? The new pressure does not feel the *history* of divergence of the centered velocity field. This is useful in particular when this history includes the noise induced by adaptive mesh refinement. The cost to pay is however significant since an extra (potentially expensive) projection is required. ## Implementation We need a (temporary) field to store the update $A_f$ and a (permanent) field to store the projection pressure $\delta p$. */ face vector Af; scalar dp[]; /** We make heavy use of the [event inheritance mechanism](/Basilisk C#event-inheritance). All the events below are first defined in [centered.h](). At the beginning of the timestep (i.e. before advection), we store the (interpolated) value of the initial velocity field in $A_f$. */ event advection_term (i++) { Af = new face vector; foreach_face() Af.x[] = - fm.x[]*face_value (u.x, 0); } /** After the advection and diffusion terms have been added to $u$, we recover the update by adding the new face-interpolated value of the velocity field to the initial face velocity, and add the acceleration i.e. we perform step 1 above: $$ A_f = \overline{A^{n+1/2}} + \Delta ta_f $$ */ face vector ab; event acceleration (i++) { foreach_face() Af.x[] += fm.x[]*(face_value (u.x, 0) + dt*a.x[]); /** We also add the centered gradient $\mathbf{g^n}$ to the centered velocity field. */ correction (dt); /** Step 2 above (i.e. $u_f^{\star} = \overline{u^{\star}}$) is performed by the acceleration event of the [centered solver](centered.h#acceleration), but we need to reset the acceleration to zero. */ ab = a; a = zerof; } /** The projection step 3 is also performed by the [centered solver](centered.h#projection). We want to store the resulting pressure in $dp$ rather than $p$, so we swap the two fields, before performing the projection. */ event projection (i++) { scalar_clone (dp, p); swap (scalar, p, dp); } /** Step 4 is performed by the [centered solver](centered.h#projection). Step 5 is done at the end of the timestep. We first restore the fields modified above, then perform the second projection and compute the corresponding centered gradient $g^{n+1}$. */ event end_timestep (i++) { swap (scalar, p, dp); a = ab; // this could be optimised since we do not use Af mgp = project (Af, p, alpha, dt, mgp.nrelax); delete ((scalar *){Af}); centered_gradient (p, g); } /** ## References ~~~bib @article{almgren2000, title={Approximate projection methods: Part I. Inviscid analysis}, author={Almgren, Ann S and Bell, John B and Crutchfield, William Y}, journal={SIAM Journal on Scientific Computing}, volume={22}, number={4}, pages={1139--1159}, year={2000}, publisher={SIAM}, url={https://epubs.siam.org/doi/pdf/10.1137/S1064827599357024} } ~~~ */