-
Notifications
You must be signed in to change notification settings - Fork 242
fix mean pressure in FFT preconditioner for ConjugateGradientPoissonSolver
for immersed boundaries
#4554
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
I only see a modification to the preconditioner for the Poisson solver. So I don't understand how this PR changes other problems. Can you explain? In any case, rather than hard coding something into a general solver, add a property to |
I'm referring to doing this alternative approach which I have not implemented:
but yes I agree doing something like |
residual :: F | ||
preconditioner :: M | ||
preconditioner_product :: P | ||
enforce_gauge_condition :: Bool |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this a good interface to apply this gauge condition? @glwagner happy to hear your recommendations
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, I would implement a generic interface, because we don't know what the gauge condition will be for an arbitrary linear_operation!
. Just like we can't have "apply preconditioner" as a boolean for all linear operations, we also need to have some kind of generic interface for enforcing gauge conditions that will work with any linear operation / preconditioner.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For example, the implementation of linear_operation!
is generic --- this can be a function or object which computes the result of applying a "linear operation" to an object.
similarly you can have enforce_gauge_condition!
which is a function operating on something. Or you can use an object-function interface, eg a property gauge_enforcer
, which is then involved when calling
enforce_gauge_condition!(modified_args..., gauge_enforcer::UserDefinedThing, other_args...)
I have changed enforce_gauge_condition!(x, r) Would there be a case where one might need to access more internals than |
Co-authored-by: Tomás Chor <[email protected]>
Is this ready to go? @glwagner @tomchor @simone-silvestri |
Given the importance of the pressure solver, I'm thinking we should add a test for this. @xkykai what do you think? |
Sounds great! Should we adopt it from what you've written here https://github.com/CliMA/Oceananigans.jl/blob/tc/perturb-adv-obc-tweaks/test/test_conjugate_gradient_poisson_solver.jl? What else do you wish to see in the test? |
I just tested and those tests will fail here. I'd say let's keep it intuitive and test that the immersed-aware mean pressure is zero at every preconditioning step, which is what this PR is designed to do. I'll add those other tests after the issue of open boundaries is fixed (probably after #4582?) |
For the record, running using Oceananigans
using Oceananigans.BoundaryConditions: PerturbationAdvectionOpenBoundaryCondition
using Oceananigans.Solvers: ConjugateGradientPoissonSolver
Lx, Lz = 10, 3
Nx = Nz = 8
grid_base = RectilinearGrid(topology = (Bounded, Flat, Bounded), size = (Nx, Nz), x = (0, Lx), z = (0, Lz))
flat_bottom(x) = 1
grid = ImmersedBoundaryGrid(grid_base, PartialCellBottom(flat_bottom))
U = 1
inflow_timescale = 0.0
outflow_timescale = Inf
u_boundaries = FieldBoundaryConditions(
west = PerturbationAdvectionOpenBoundaryCondition(U; inflow_timescale, outflow_timescale),
east = PerturbationAdvectionOpenBoundaryCondition(U; inflow_timescale, outflow_timescale),)
model = NonhydrostaticModel(; grid,
boundary_conditions = (; u = u_boundaries),
pressure_solver = ConjugateGradientPoissonSolver(grid, maxiter=10),
advection = WENO(; grid, order=5)
)
u₀(x, z) = U + 1e-2*rand()
set!(model, u=u₀, enforce_incompressibility=true) in this branch produces peaks in velocity approaching infinity, indicating that there's still an issue with open boundaries: julia> model.velocities.u
9×1×8 Field{Face, Center, Center} on ImmersedBoundaryGrid on CPU
├── grid: 8×1×8 ImmersedBoundaryGrid{Float64, Bounded, Flat, Bounded} on CPU with 4×0×4 halo
├── boundary conditions: FieldBoundaryConditions
│ └── west: Open{Oceananigans.BoundaryConditions.PerturbationAdvection{Float64}}, east: Open{Oceananigans.BoundaryConditions.PerturbationAdvection{Float64}}, south: Nothing, north: Nothing, bottom: ZeroFlux, top: ZeroFlux, immersed: ZeroFlux
└── data: 17×1×16 OffsetArray(::Array{Float64, 3}, -3:13, 1:1, -3:12) with eltype Float64 with indices -3:13×1:1×-3:12
└── max=2.11106e13, min=-2.11106e13, mean=1.00442 |
Sorry! I just tested this on #4563 (which should be the supposed PR to rule them all) the MWE works fine, with no explosion of velocities: julia> model.velocities.u
9×1×8 Field{Face, Center, Center} on ImmersedBoundaryGrid on CPU
├── grid: 8×1×8 ImmersedBoundaryGrid{Float64, Bounded, Flat, Bounded} on CPU with 4×0×4 halo
├── boundary conditions: FieldBoundaryConditions
│ └── west: Open{Oceananigans.BoundaryConditions.PerturbationAdvection{Float64}}, east: Open{Oceananigans.BoundaryConditions.PerturbationAdvection{Float64}}, south: Nothing, north: Nothing, bottom: ZeroFlux, top: ZeroFlux, immersed: ZeroFlux
└── data: 17×1×16 OffsetArray(::Array{Float64, 3}, -3:13, 1:1, -3:12) with eltype Float64 with indices -3:13×1:1×-3:12
└── max=1.00987, min=0.996446, mean=1.00329 |
I've added simple tests and I think it is ready to go! |
Closes #4553.
This fixes the immersed-aware mean pressure to be zero at every preconditioning step, effectively avoiding numerical blowup due to floating point error accumulation.
However this also means that without using the preconditioner, the error in mean pressure can and will accumulate and grow over time. However this is perhaps an unlikely use case as it garners no benefits vs. using the FFT preconditioner.
Here I show the results with FFT preconditioner with the test case in #4553
Here we see that the mean pressure is effectively maintained at zero.
Alternatively, to solve the issue in cases where no preconditioner is used as well, we can use @Yixiao-Zhang 's proposal which is to modify https://github.com/CliMA/Oceananigans.jl/blob/f6463bd4a8280dfa2d7182ccdc2d8e438e49d384/src/Solvers/conjugate_gradient_solver.jl#L242C1-L242C55:
This touches the internals of the
ConjugateGradientSolver
, and this will fail other arbitrary problems solvable by conjugate gradient where there is no gauge invariance. We can perhaps argue that solving the pressure Poisson equation with Neumann boundary conditions is a special use case of theConjugateGradientSolver
. If we decide to address the issue within the conjugate gradient iteration we should perhaps think of modifying the API slightly to not do it for all arbitrary problems. Also if we take this approach we should also do thesubtract_and_mask!
onx
as well, no reason not to.Also for reference, here is what the MWE script does:
vortex_sheet.mp4