diff --git a/hog/forms.py b/hog/forms.py index 4a9439ad920f1796126b7f3085311ebe056adf36..85e93c2b02d67bbe5c189a48733b55c542675718 100644 --- a/hog/forms.py +++ b/hog/forms.py @@ -682,6 +682,144 @@ where fe_coefficients={"mu": coefficient_function_space}, ) +def full_stokes_viscoplastic( + trial: TrialSpace, + test: TestSpace, + geometry: ElementGeometry, + symbolizer: Symbolizer, + blending: GeometryMap = IdentityMap(), + component_trial: int = 0, + component_test: int = 0, + variable_viscosity: bool = True, + velocity_function_space: Optional[FunctionSpace] = None, + coefficient_function_space: Optional[FunctionSpace] = None, +) -> Form: + docstring = f""" +Implements the fully coupled viscous operator of the Stokes problem. +The latter is the extension of the Epsilon operator to the case where +the velocity field need not be divergence-free. This is e.g. the case +in the (truncated) anelastic liquid approximation of mantle convection. + +The strong representation of the operator is given by: + + - div[ μ (grad(u)+grad(u)ᵀ) ] + 2/3 grad[ μ div(u) ] + +Note that the factor 2/3 means that for 2D this is the pseudo-3D form +of the operator. + +Component trial: {component_trial} +Component test: {component_test} +Geometry map: {blending} + +Weak formulation + + u: trial function (vectorial space: {trial}) + v: test function (vectorial space: {test}) + μ: coefficient (scalar space: {coefficient_function_space}) + + ∫ μ {{ ( 2 ε(u) : ε(v) ) - (2/3) [ ( ∇ · u ) · ( ∇ · v ) ] }} + +where + + ε(w) := (1/2) (∇w + (∇w)ᵀ) +""" + + from hog.recipes.integrands.volume.full_stokes_viscoplastic import integrand + + return process_integrand( + integrand, + trial, + test, + geometry, + symbolizer, + blending=blending, + is_symmetric=trial == test, + docstring=docstring, + fe_coefficients={"mu_lin": coefficient_function_space, "ux": velocity_function_space, "uy": velocity_function_space}, + ) + +def evaluate_viscosity_viscoplastic( + trial: TrialSpace, + test: TestSpace, + geometry: ElementGeometry, + symbolizer: Symbolizer, + blending: GeometryMap = IdentityMap(), + component_trial: int = 0, + component_test: int = 0, + variable_viscosity: bool = True, + velocity_function_space: Optional[FunctionSpace] = None, + coefficient_function_space: Optional[FunctionSpace] = None, +) -> Form: + docstring = f""" +Implements the fully coupled viscous operator of the Stokes problem. +The latter is the extension of the Epsilon operator to the case where +the velocity field need not be divergence-free. This is e.g. the case +in the (truncated) anelastic liquid approximation of mantle convection. + +The strong representation of the operator is given by: + + - div[ μ (grad(u)+grad(u)ᵀ) ] + 2/3 grad[ μ div(u) ] + +Note that the factor 2/3 means that for 2D this is the pseudo-3D form +of the operator. + +Component trial: {component_trial} +Component test: {component_test} +Geometry map: {blending} + +Weak formulation + + u: trial function (vectorial space: {trial}) + v: test function (vectorial space: {test}) + μ: coefficient (scalar space: {coefficient_function_space}) + + ∫ μ {{ ( 2 ε(u) : ε(v) ) - (2/3) [ ( ∇ · u ) · ( ∇ · v ) ] }} + +where + + ε(w) := (1/2) (∇w + (∇w)ᵀ) +""" + + from hog.recipes.expressions.viscoplastic_rheology import expression + + return process_integrand( + expression, + trial, + test, + geometry, + symbolizer, + blending=blending, + is_symmetric=trial == test, + docstring=docstring, + fe_coefficients={"mu_lin": coefficient_function_space, "ux": velocity_function_space, "uy": velocity_function_space}, + dont_integrate=True + ) + +def all_ones( + trial: TrialSpace, + test: TestSpace, + geometry: ElementGeometry, + symbolizer: Symbolizer, + blending: GeometryMap = IdentityMap(), + velocity_function_space: Optional[FunctionSpace] = None, + coefficient_function_space: Optional[FunctionSpace] = None, +) -> Form: + docstring = f""" +""" + + from hog.recipes.expressions.ones import expression + + return process_integrand( + expression, + trial, + test, + geometry, + symbolizer, + blending=blending, + is_symmetric=trial == test, + docstring=docstring, + dont_integrate=True + ) def shear_heating( trial: TrialSpace, diff --git a/hog/integrand.py b/hog/integrand.py index 07313705041256cc2c0ffb8f2f35d62ee448dbbc..5e86bbe9621b0c721cabb83ea08d51816e59f98f 100644 --- a/hog/integrand.py +++ b/hog/integrand.py @@ -88,11 +88,15 @@ class Form: symmetric: bool free_symbols: List[sp.Symbol] = field(default_factory=lambda: list()) docstring: str = "" + dont_integrate: bool = False def integrate(self, quad: Quadrature, symbolizer: Symbolizer) -> sp.Matrix: """Integrates the form using the passed quadrature directly, i.e. without tabulations or loops.""" mat = self.tabulation.inline_tables(self.integrand) + if self.dont_integrate: + return + for row in range(mat.rows): for col in range(mat.cols): if self.symmetric and row > col: @@ -163,6 +167,13 @@ class IntegrandSymbols: # The keys are specified by the strings that are passed to process_integrand. grad_k: Dict[str, sp.Matrix] | None = None + k_dof_symbols: Dict[str, List[sp.Symbol]] | None = None + + grad_k_dof_symbols: Dict[str, List[sp.Symbol]] | None = None + + k_dof_symbol: Dict[str, sp.Symbol] | None = None + grad_k_dof_symbol: Dict[str, sp.Symbol] | None = None + # The geometry of the volume element. volume_geometry: ElementGeometry | None = None @@ -233,6 +244,7 @@ def process_integrand( is_symmetric: bool = False, rot_type: RotationType = RotationType.NO_ROTATION, docstring: str = "", + dont_integrate: bool = False, ) -> Form: """ Constructs an element matrix (:class:`~Form` object) from an integrand. @@ -293,7 +305,7 @@ def process_integrand( finite-element function coefficients, they will be available to the callable as `k` supply None as the FunctionSpace for a std::function-type coeff (only works for old forms) :param is_symmetric: whether the bilinear form is symmetric - this is exploited by the generator - :param rot_type: whether the operator has to be wrapped with rotation matrix and the type of rotation that needs + :param rot_type: whether the operator has to be wrapped with rotation matrix and the type of rotation that needs to be applied, only applicable for Vectorial spaces :param docstring: documentation of the integrand/bilinear form - will end up in the docstring of the generated code """ @@ -387,6 +399,13 @@ def process_integrand( s.k = dict() s.grad_k = dict() + + s.k_dof_symbols = dict() + s.grad_k_dof_symbols = dict() + + s.k_dof_symbol = dict() + s.grad_k_dof_symbol = dict() + for name, coefficient_function_space in fe_coefficients_modified.items(): if coefficient_function_space is None: k = scalar_space_dependent_coefficient( @@ -400,15 +419,15 @@ def process_integrand( symbolizer, domain="reference", function_id=name, - basis_eval=tabulation.register_phi_evals( - coefficient_function_space.shape(volume_geometry) - ), + # basis_eval=tabulation.register_phi_evals( + # coefficient_function_space.shape(volume_geometry) + # ), ) if isinstance(k, sp.Matrix) and k.shape == (1, 1): k = k[0, 0] - grad_k, _ = fem_function_gradient_on_element( + grad_k, grad_dof_symbols = fem_function_gradient_on_element( coefficient_function_space, volume_geometry, symbolizer, @@ -419,6 +438,9 @@ def process_integrand( s.k[name] = k s.grad_k[name] = grad_k + s.k_dof_symbols[name] = dof_symbols + s.grad_k_dof_symbols[name] = grad_dof_symbols + ############################## # Jacobians and determinants # ############################## @@ -482,16 +504,46 @@ def process_integrand( mat = create_empty_element_matrix(trial, test, volume_geometry) it = element_matrix_iterator(trial, test, volume_geometry) - for data in it: - s.u = data.trial_shape - s.grad_u = data.trial_shape_grad - s.hessian_u = data.trial_shape_hessian + # dont_integrate option only works well for P1 + ref_coords = [(0, 0), (1, 0), (0, 1)] + + if dont_integrate: + for data in it: + s.u = data.trial_shape + s.grad_u = data.trial_shape_grad + s.hessian_u = data.trial_shape_hessian + + s.v = data.test_shape + s.grad_v = data.test_shape_grad + s.hessian_v = data.test_shape_hessian + + if data.row == data.col: + for name in s.k_dof_symbols.keys(): + s.k_dof_symbol[name] = s.k[name].subs( + [ + (sp.Symbol("x_ref_0"), ref_coords[data.row][0]), + (sp.Symbol("x_ref_1"), ref_coords[data.row][1]), + ] + ) + s.grad_k_dof_symbol[name] = s.grad_k[name].subs( + [ + (sp.Symbol("x_ref_0"), ref_coords[data.row][0]), + (sp.Symbol("x_ref_1"), ref_coords[data.row][1]), + ] + ) + + mat[data.row, data.col] = integrand(**asdict(s)) + else: + for data in it: + s.u = data.trial_shape + s.grad_u = data.trial_shape_grad + s.hessian_u = data.trial_shape_hessian - s.v = data.test_shape - s.grad_v = data.test_shape_grad - s.hessian_v = data.test_shape_hessian + s.v = data.test_shape + s.grad_v = data.test_shape_grad + s.hessian_v = data.test_shape_hessian - mat[data.row, data.col] = integrand(**asdict(s)) + mat[data.row, data.col] = integrand(**asdict(s)) free_symbols_sorted = sorted(list(free_symbols), key=lambda x: str(x)) @@ -603,4 +655,5 @@ where symmetric=is_symmetric, free_symbols=free_symbols_sorted, docstring=docstring, + dont_integrate=dont_integrate, ) diff --git a/hog/operator_generation/operators.py b/hog/operator_generation/operators.py index 18dbfede7ce05b0f9b57714e3c3203fd8f7a8918..d666edddbbf1c1bc543e86d58b47b96ce61f3546 100644 --- a/hog/operator_generation/operators.py +++ b/hog/operator_generation/operators.py @@ -373,29 +373,30 @@ class HyTeGElementwiseOperator: else: mat = form.tabulation.inline_tables(mat) - if Opts.QUADLOOPS in optimizations: - quad_loop = QuadLoop( - self.symbolizer, - quad, - mat, - self._type_descriptor, - form.symmetric, - blending, - ) - mat = quad_loop.mat - else: - with TimedLogger( - f"integrating {mat.shape[0] * mat.shape[1]} expressions", - level=logging.DEBUG, - ): - for row in range(mat.rows): - for col in range(mat.cols): - if form.symmetric and row > col: - mat[row, col] = mat[col, row] - else: - mat[row, col] = quad.integrate( - mat[row, col], self.symbolizer, blending - ) + if not form.dont_integrate: + if Opts.QUADLOOPS in optimizations: + quad_loop = QuadLoop( + self.symbolizer, + quad, + mat, + self._type_descriptor, + form.symmetric, + blending, + ) + mat = quad_loop.mat + else: + with TimedLogger( + f"integrating {mat.shape[0] * mat.shape[1]} expressions", + level=logging.DEBUG, + ): + for row in range(mat.rows): + for col in range(mat.cols): + if form.symmetric and row > col: + mat[row, col] = mat[col, row] + else: + mat[row, col] = quad.integrate( + mat[row, col], self.symbolizer, blending + ) if volume_geometry.space_dimension not in self.integration_infos: self.integration_infos[volume_geometry.space_dimension] = [] diff --git a/hog/recipes/expressions/ones.py b/hog/recipes/expressions/ones.py new file mode 100644 index 0000000000000000000000000000000000000000..cdc9859da99015adf40688f2009530f5c31d85d5 --- /dev/null +++ b/hog/recipes/expressions/ones.py @@ -0,0 +1,34 @@ +# HyTeG Operator Generator +# Copyright (C) 2024 HyTeG Team +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. + +from hog.recipes.common import * + +def expression( + *, + jac_a_inv, + jac_a_abs_det, + jac_b_inv, + jac_b_abs_det, + grad_u, + grad_v, + k_dof_symbol, + grad_k_dof_symbol, + scalars, + tabulate, + **_, +): + + return 1.0 diff --git a/hog/recipes/expressions/viscoplastic_rheology.py b/hog/recipes/expressions/viscoplastic_rheology.py new file mode 100644 index 0000000000000000000000000000000000000000..b19614ac1e80311647526d575cc9176fa83e2742 --- /dev/null +++ b/hog/recipes/expressions/viscoplastic_rheology.py @@ -0,0 +1,68 @@ +# HyTeG Operator Generator +# Copyright (C) 2024 HyTeG Team +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. + +from hog.recipes.common import * + +def expression( + *, + x, + jac_a_inv, + jac_a_abs_det, + jac_b_inv, + jac_b_abs_det, + grad_u, + grad_v, + k, + grad_k, + k_dof_symbol, + grad_k_dof_symbol, + scalars, + tabulate, + **_, +): + + grad_u_chain = jac_b_inv.T * tabulate(jac_a_inv.T * grad_u) + grad_v_chain = jac_b_inv.T * tabulate(jac_a_inv.T * grad_v) + + def symm_grad(w): + return 0.5 * (w + w.T) + + mu_lin = k_dof_symbol["mu_lin"] + + grad_ux = grad_k_dof_symbol["ux"] + grad_uy = grad_k_dof_symbol["uy"] + + print("grad_ux = ", grad_ux) + # print("x = ", x) + + uxx = (jac_b_inv.T * jac_a_inv.T * grad_ux)[0] + uxy = (jac_b_inv.T * jac_a_inv.T * grad_ux)[1] + + uyx = (jac_b_inv.T * jac_a_inv.T * grad_uy)[0] + uyy = (jac_b_inv.T * jac_a_inv.T * grad_uy)[1] + + print("uxx = ", uxx) + + mu_star = scalars("mu_star") + sigma_y = scalars("sigma_y") + + strain = uxx * uxx + 0.5 * (uxy + uyx) * (uxy + uyx) + uyy * uyy + + mu_nonlin = mu_star + (sigma_y / (sp.sqrt(strain))) + + mu = 2.0 / ((1.0 / mu_lin) + (1.0 / mu_nonlin)) + + return mu diff --git a/hog/recipes/integrands/volume/full_stokes_viscoplastic.py b/hog/recipes/integrands/volume/full_stokes_viscoplastic.py new file mode 100644 index 0000000000000000000000000000000000000000..e1bf51663575e10ae9ac2b206b0caf95c494a7e7 --- /dev/null +++ b/hog/recipes/integrands/volume/full_stokes_viscoplastic.py @@ -0,0 +1,89 @@ +# HyTeG Operator Generator +# Copyright (C) 2024 HyTeG Team +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. + +from hog.recipes.common import * + + +def integrand( + *, + jac_a_inv, + jac_a_abs_det, + jac_b_inv, + jac_b_abs_det, + grad_u, + grad_v, + k, + grad_k, + scalars, + tabulate, + **_, +): + + grad_u_chain = jac_b_inv.T * tabulate(jac_a_inv.T * grad_u) + grad_v_chain = jac_b_inv.T * tabulate(jac_a_inv.T * grad_v) + + def symm_grad(w): + return 0.5 * (w + w.T) + + symm_grad_u = symm_grad(grad_u_chain) + symm_grad_v = symm_grad(grad_v_chain) + + div_u = (jac_b_inv.T * tabulate(jac_a_inv.T * grad_u)).trace() + div_v = (jac_b_inv.T * tabulate(jac_a_inv.T * grad_v)).trace() + + mu_lin = k["mu_lin"] + + grad_ux = grad_k["ux"] + grad_uy = grad_k["uy"] + + uxx = (jac_b_inv.T * jac_a_inv.T * grad_ux)[0] + uxy = (jac_b_inv.T * jac_a_inv.T * grad_ux)[1] + + uyx = (jac_b_inv.T * jac_a_inv.T * grad_uy)[0] + uyy = (jac_b_inv.T * jac_a_inv.T * grad_uy)[1] + + mu_star = scalars("mu_star") + sigma_y = scalars("sigma_y") + + strain = uxx * uxx + 0.5 * (uxy + uyx) * (uxy + uyx) + uyy * uyy + + mu_nonlin = mu_star + (sigma_y / (1e-20 + sp.sqrt(strain))) + + mu = 2.0 / ((1.0 / mu_lin) + (1.0 / mu_nonlin)) + + return mu * ( + ( + double_contraction(2 * symm_grad_u, symm_grad_v) + * tabulate(jac_a_abs_det) + * jac_b_abs_det + ) + - (2.0 / 3.0) * div_u * div_v * tabulate(jac_a_abs_det) * jac_b_abs_det + ) + +# uxx = (jac_affine_inv.T * grad_ux)[0] +# uxy = (jac_affine_inv.T * grad_ux)[1] + +# uyx = (jac_affine_inv.T * grad_uy)[0] +# uyy = (jac_affine_inv.T * grad_uy)[1] + +# etaStar = 0.001 +# sigmaY = 1.0 + +# epsContraction = uxx * uxx + 0.5 * (uxy + uyx) * (uxy + uyx) + uyy * uyy + +# etaNonlin = etaStar + (sigmaY / (sp.sqrt(epsContraction))) + +# eta = 2.0 / ((1.0 / etaLin[0]) + (1.0 / etaNonlin)) \ No newline at end of file