Skip to content

Equations

equations

Generates the UFL form for an equation consisting of individual terms.

This module contains a dataclass to define the structure of mathematical equations within the G-ADOPT library. It provides a convenient way to generate the UFL form required by Firedrake solvers.

Equation(test, trial_space, residual_terms, *, mass_term=None, eq_attrs={}, approximation=None, bcs=dict(), quad_degree=None, rescale_factor=1) dataclass

Generates the UFL form for the sum of terms constituting an equation.

The generated UFL form corresponds to a sum of implemented term forms contributing to the equation's residual in the finite element discretisation.

Parameters:

Name Type Description Default
test Argument | Indexed

Firedrake test function.

required
trial_space WithGeometry

Firedrake function space of the trial function.

required
residual_terms InitVar[Callable | list[Callable]]

Equation term or a list thereof contributing to the residual.

required
mass_term Callable | None

Callable returning the equation's mass term.

None
eq_attrs InitVar[dict[str, Any]]

Dictionary of fields and parameters used in the equation's weak form.

{}
approximation BaseApproximation | None

G-ADOPT approximation for the system of equations considered.

None
bcs dict[int, dict[str, Any]]

Dictionary specifying weak boundary conditions (identifier, type, and value).

dict()
quad_degree InitVar[int | None]

Integer specifying the quadrature degree. If omitted, it is set to 2p + 1, where p is the polynomial degree of the trial space.

None
rescale_factor Number | Constant

A constant factor used to rescale mass and residual terms.

1

mass(trial)

Generates the UFL form corresponding to the mass term.

Source code in g-adopt/gadopt/equations.py
115
116
117
118
119
120
def mass(
    self, trial: fd.Argument | fd.ufl.indexed.Indexed | fd.Function
) -> fd.Form:
    """Generates the UFL form corresponding to the mass term."""

    return self.rescale_factor * self.mass_term(self, trial)

residual(trial)

Generates the UFL form corresponding to the residual terms.

Source code in g-adopt/gadopt/equations.py
122
123
124
125
126
127
128
129
def residual(
    self, trial: fd.Argument | fd.ufl.indexed.Indexed | fd.Function
) -> fd.Form:
    """Generates the UFL form corresponding to the residual terms."""

    return self.rescale_factor * sum(
        term(self, trial) for term in self.residual_terms
    )

cell_edge_integral_ratio(mesh, p)

Ratio C such that \int_f u^2 <= C Area(f)/Volume(e) \int_e u^2 for facets f, elements e, and polynomials u of degree p.

See Equation (3.7), Table 3.1, and Appendix C from Hillewaert's thesis: https://www.researchgate.net/publication/260085826

Source code in g-adopt/gadopt/equations.py
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
def cell_edge_integral_ratio(mesh: fd.MeshGeometry, p: int) -> int:
    r"""
    Ratio C such that \int_f u^2 <= C Area(f)/Volume(e) \int_e u^2 for facets f,
    elements e, and polynomials u of degree p.

    See Equation (3.7), Table 3.1, and Appendix C from Hillewaert's thesis:
    https://www.researchgate.net/publication/260085826
    """
    cell_type = mesh.ufl_cell().cellname()
    if cell_type == "triangle":
        return (p + 1) * (p + 2) / 2.0
    elif cell_type == "quadrilateral" or cell_type == "interval * interval":
        return (p + 1) ** 2
    elif cell_type == "triangle * interval":
        return (p + 1) ** 2
    elif cell_type == "quadrilateral * interval":
        # if e is a wedge and f is a triangle: (p+1)**2
        # if e is a wedge and f is a quad: (p+1)*(p+2)/2
        # here we just return the largest of the the two (for p>=0)
        return (p + 1) ** 2
    elif cell_type == "tetrahedron":
        return (p + 1) * (p + 3) / 3
    else:
        raise NotImplementedError("Unknown cell type in mesh: {}".format(cell_type))

interior_penalty_factor(eq, *, shift=0)

Interior Penalty method

For details on the choice of sigma, see https://www.researchgate.net/publication/260085826

We use Equations (3.20) and (3.23). Instead of getting the maximum over two adjacent cells (+ and -), we just sum (i.e. 2 * avg) and have an extra 0.5 for internal facets.

Source code in g-adopt/gadopt/equations.py
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
def interior_penalty_factor(eq: Equation, *, shift: int = 0) -> float:
    """Interior Penalty method

    For details on the choice of sigma, see
    https://www.researchgate.net/publication/260085826

    We use Equations (3.20) and (3.23). Instead of getting the maximum over two adjacent
    cells (+ and -), we just sum (i.e. 2 * avg) and have an extra 0.5 for internal
    facets.
    """
    degree = eq.trial_space.ufl_element().degree()
    if not isinstance(degree, int):
        degree = max(degree)

    if degree == 0:  # probably only works for orthogonal quads and hexes
        sigma = 1.0
    else:
        # safety factor: 1.0 is theoretical minimum
        alpha = getattr(eq, "interior_penalty", 2.0)
        num_facets = eq.mesh.ufl_cell().num_facets()
        sigma = alpha * cell_edge_integral_ratio(eq.mesh, degree + shift) * num_facets

    return sigma