-
Notifications
You must be signed in to change notification settings - Fork 29
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #279 from thetisproject/recovery
HessianRecoverer2D
- Loading branch information
Showing
8 changed files
with
263 additions
and
52 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
from thetis import * | ||
from thetis.diagnostics import * | ||
import pytest | ||
|
||
|
||
@pytest.fixture(params=[True, False]) | ||
def interp(request): | ||
return request.param | ||
|
||
|
||
def test_hessian_recovery2d(interp, tol=1.0e-08): | ||
r""" | ||
Apply Hessian recovery techniques to the quadratic | ||
polynomial :math:`f(x, y) = \frac12(x^2 + y^2)` and | ||
check that the result is the identity matrix. | ||
We get the gradient approximation as part of the | ||
recovery procedure, so check that this is also as | ||
expected. | ||
:arg interp: should the polynomial be interpolated as | ||
as a :class:`Function`, or left as a UFL expression? | ||
:kwarg tol: relative tolerance for value checking | ||
""" | ||
mesh2d = UnitSquareMesh(4, 4) | ||
x, y = SpatialCoordinate(mesh2d) | ||
bowl = 0.5*(x**2 + y**2) | ||
if interp: | ||
P2_2d = get_functionspace(mesh2d, 'CG', 2) | ||
bowl = interpolate(bowl, P2_2d) | ||
P1v_2d = get_functionspace(mesh2d, 'CG', 1, vector=True) | ||
P1t_2d = get_functionspace(mesh2d, 'CG', 1, tensor=True) | ||
|
||
# Recover derivatives | ||
g = Function(P1v_2d, name='Gradient') | ||
H = Function(P1t_2d, name='Hessian') | ||
hessian_recoverer = HessianRecoverer2D(bowl, H, g) | ||
hessian_recoverer.solve() | ||
|
||
# Check values | ||
g_expect = Function(P1v_2d, name='Expected gradient') | ||
g_expect.interpolate(as_vector([x, y])) | ||
H_expect = Function(P1t_2d, name='Expected Hessian') | ||
H_expect.interpolate(Identity(2)) | ||
err_g = errornorm(g, g_expect)/norm(g_expect) | ||
assert err_g < tol, f'Gradient approximation error {err_g:.4e}' | ||
err_H = errornorm(H, H_expect)/norm(H_expect) | ||
assert err_H < tol, f'Hessian approximation error {err_H:.4e}' | ||
|
||
|
||
def test_vorticity_calculation2d(interp, tol=1.0e-08): | ||
r""" | ||
Calculate the vorticity of the velocity field | ||
:math:`\mathbf u(x, y) = \frac12(y, -x)` and check | ||
that the result is unity. | ||
:arg interp: should the velocity be interpolated as | ||
as a :class:`Function`, or left as a UFL expression? | ||
:kwarg tol: relative tolerance for value checking | ||
""" | ||
mesh2d = UnitSquareMesh(4, 4) | ||
x, y = SpatialCoordinate(mesh2d) | ||
uv = 0.5*as_vector([y, -x]) | ||
if interp: | ||
P1v_2d = get_functionspace(mesh2d, 'CG', 1, vector=True) | ||
uv = interpolate(uv, P1v_2d) | ||
P1_2d = get_functionspace(mesh2d, 'CG', 1) | ||
|
||
# Recover vorticity | ||
omega = Function(P1_2d, name='Vorticity') | ||
vorticity_calculator = VorticityCalculator2D(uv, omega) | ||
vorticity_calculator.solve() | ||
|
||
# Check values | ||
omega_expect = Function(P1_2d, name='Expected vorticity') | ||
omega_expect.assign(1.0) | ||
err = errornorm(omega, omega_expect)/norm(omega_expect) | ||
assert err < tol, f'Vorticity approximation error {err:.4e}' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,149 @@ | ||
""" | ||
Classes for computing diagnostics. | ||
""" | ||
from .utility import * | ||
|
||
|
||
__all__ = ["VorticityCalculator2D", "HessianRecoverer2D"] | ||
|
||
|
||
class VorticityCalculator2D(object): | ||
r""" | ||
Linear solver for recovering fluid vorticity. | ||
It is recommended that the vorticity is sought | ||
in :math:`\mathbb P1` space. | ||
:arg uv_2d: horizontal velocity :class:`Function`. | ||
:arg vorticity_2d: :class:`Function` to hold calculated vorticity. | ||
:kwargs: to be passed to the :class:`LinearVariationalSolver`. | ||
""" | ||
@PETSc.Log.EventDecorator("thetis.VorticityCalculator2D.__init__") | ||
def __init__(self, uv_2d, vorticity_2d, **kwargs): | ||
fs = vorticity_2d.function_space() | ||
dim = fs.mesh().topological_dimension() | ||
if dim != 2: | ||
raise ValueError(f'Dimension {dim} not supported') | ||
if element_continuity(fs.ufl_element()).horizontal != 'cg': | ||
raise ValueError('Vorticity must be calculated in a continuous space') | ||
n = FacetNormal(fs.mesh()) | ||
|
||
# Weak formulation | ||
test = TestFunction(fs) | ||
a = TrialFunction(fs)*test*dx | ||
L = -inner(perp(uv_2d), grad(test))*dx \ | ||
+ dot(perp(uv_2d), test*n)*ds \ | ||
+ dot(avg(perp(uv_2d)), jump(test, n))*dS | ||
|
||
# Setup vorticity solver | ||
prob = LinearVariationalProblem(a, L, vorticity_2d) | ||
kwargs.setdefault('solver_parameters', { | ||
"ksp_type": "cg", | ||
"pc_type": "bjacobi", | ||
"sub_pc_type": "ilu", | ||
}) | ||
self.solver = LinearVariationalSolver(prob, **kwargs) | ||
|
||
@PETSc.Log.EventDecorator("thetis.VorticityCalculator2D.solve") | ||
def solve(self): | ||
self.solver.solve() | ||
|
||
|
||
class HessianRecoverer2D(object): | ||
r""" | ||
Linear solver for recovering Hessians. | ||
Hessians are recoved using double :math:`L^2` | ||
projection, which is implemented using a | ||
mixed finite element method. | ||
It is recommended that gradients and Hessians | ||
are sought in :math:`\mathbb P1` space of | ||
appropriate dimension. | ||
:arg field_2d: :class:`Function` to recover the Hessian of. | ||
:arg hessian_2d: :class:`Function` to hold recovered Hessian. | ||
:kwarg gradient_2d: :class:`Function` to hold recovered gradient. | ||
:kwargs: to be passed to the :class:`LinearVariationalSolver`. | ||
""" | ||
@PETSc.Log.EventDecorator("thetis.HessianRecoverer2D.__init__") | ||
def __init__(self, field_2d, hessian_2d, gradient_2d=None, **kwargs): | ||
self.hessian_2d = hessian_2d | ||
self.gradient_2d = gradient_2d | ||
Sigma = hessian_2d.function_space() | ||
mesh = Sigma.mesh() | ||
dim = mesh.topological_dimension() | ||
if dim != 2: | ||
raise ValueError(f'Dimension {dim} not supported') | ||
n = FacetNormal(mesh) | ||
|
||
# Extract function spaces | ||
if element_continuity(Sigma.ufl_element()).horizontal != 'cg': | ||
raise ValueError('Hessians must be calculated in a continuous space') | ||
if Sigma.dof_dset.dim != (2, 2): | ||
raise ValueError('Expecting a square tensor function') | ||
if gradient_2d is None: | ||
V = get_functionspace(mesh, 'CG', 1, vector=True) | ||
else: | ||
V = gradient_2d.function_space() | ||
if element_continuity(V.ufl_element()).horizontal != 'cg': | ||
raise ValueError('Gradients must be calculated in a continuous space') | ||
if V.dof_dset.dim != (2,): | ||
raise ValueError('Expecting a 2D vector function') | ||
|
||
# Setup function spaces | ||
W = V*Sigma | ||
g, H = TrialFunctions(W) | ||
phi, tau = TestFunctions(W) | ||
sol = Function(W) | ||
self._gradient, self._hessian = sol.split() | ||
|
||
# The formulation is chosen such that f does not need to have any | ||
# finite element derivatives | ||
a = inner(tau, H)*dx \ | ||
+ inner(div(tau), g)*dx \ | ||
+ inner(phi, g)*dx \ | ||
- dot(g, dot(tau, n))*ds \ | ||
- dot(avg(g), jump(tau, n))*dS | ||
L = field_2d*dot(phi, n)*ds \ | ||
+ avg(field_2d)*jump(phi, n)*dS \ | ||
- field_2d*div(phi)*dx | ||
|
||
# Apply stationary preconditioners in the Schur complement to get away | ||
# with applying GMRES to the whole mixed system | ||
sp = { | ||
"mat_type": "aij", | ||
"ksp_type": "gmres", | ||
"ksp_max_it": 20, | ||
"pc_type": "fieldsplit", | ||
"pc_fieldsplit_type": "schur", | ||
"pc_fieldsplit_0_fields": "1", | ||
"pc_fieldsplit_1_fields": "0", | ||
"pc_fieldsplit_schur_precondition": "selfp", | ||
"fieldsplit_0_ksp_type": "preonly", | ||
"fieldsplit_1_ksp_type": "preonly", | ||
"fieldsplit_1_pc_type": "gamg", | ||
"fieldsplit_1_mg_levels_ksp_max_it": 5, | ||
} | ||
if COMM_WORLD.size == 1: | ||
sp["fieldsplit_0_pc_type"] = "ilu" | ||
sp["fieldsplit_1_mg_levels_pc_type"] = "ilu" | ||
else: | ||
sp["fieldsplit_0_pc_type"] = "bjacobi" | ||
sp["fieldsplit_0_sub_ksp_type"] = "preonly" | ||
sp["fieldsplit_0_sub_pc_type"] = "ilu" | ||
sp["fieldsplit_1_mg_levels_pc_type"] = "bjacobi" | ||
sp["fieldsplit_1_mg_levels_sub_ksp_type"] = "preonly" | ||
sp["fieldsplit_1_mg_levels_sub_pc_type"] = "ilu" | ||
|
||
# Setup solver | ||
prob = LinearVariationalProblem(a, L, sol) | ||
kwargs.setdefault('solver_parameters', sp) | ||
self.solver = LinearVariationalSolver(prob, **kwargs) | ||
|
||
@PETSc.Log.EventDecorator("thetis.HessianRecoverer2D.solve") | ||
def solve(self): | ||
self.solver.solve() | ||
self.hessian_2d.assign(self._hessian) | ||
if self.gradient_2d is not None: | ||
self.gradient_2d.assign(self._gradient) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.