gadopt 2.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- gadopt-2.0.0/LICENSE +21 -0
- gadopt-2.0.0/PKG-INFO +18 -0
- gadopt-2.0.0/README.md +7 -0
- gadopt-2.0.0/gadopt/__init__.py +13 -0
- gadopt-2.0.0/gadopt/approximations.py +210 -0
- gadopt-2.0.0/gadopt/diagnostics.py +40 -0
- gadopt-2.0.0/gadopt/energy_solver.py +91 -0
- gadopt-2.0.0/gadopt/equations.py +88 -0
- gadopt-2.0.0/gadopt/inverse.py +312 -0
- gadopt-2.0.0/gadopt/limiter.py +251 -0
- gadopt-2.0.0/gadopt/momentum_equation.py +209 -0
- gadopt-2.0.0/gadopt/scalar_equation.py +205 -0
- gadopt-2.0.0/gadopt/stokes_integrators.py +180 -0
- gadopt-2.0.0/gadopt/time_stepper.py +692 -0
- gadopt-2.0.0/gadopt/utility.py +444 -0
- gadopt-2.0.0/gadopt.egg-info/PKG-INFO +18 -0
- gadopt-2.0.0/gadopt.egg-info/SOURCES.txt +20 -0
- gadopt-2.0.0/gadopt.egg-info/dependency_links.txt +1 -0
- gadopt-2.0.0/gadopt.egg-info/top_level.txt +1 -0
- gadopt-2.0.0/pyproject.toml +20 -0
- gadopt-2.0.0/setup.cfg +11 -0
gadopt-2.0.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2021 G-ADOPT Authors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
gadopt-2.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: gadopt
|
|
3
|
+
Version: 2.0.0
|
|
4
|
+
Summary: A computational platform for inverse geodynamics
|
|
5
|
+
Author: G-ADOPT Contributors
|
|
6
|
+
Classifier: Programming Language :: Python :: 3
|
|
7
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
8
|
+
Classifier: Operating System :: OS Independent
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
|
|
12
|
+
This repository contains material and examples relating to the G-Adopt Platform, a computational platform for inverse geodynamics, being developed and maintained by researchers from the Research School of Earth Sciences at the Australian National University. It builds on a recent surge in accessible observational datasets and advances in inversion methods using sophisticated adjoint techniques that provide a mechanism for fusing these observations with dynamics, physics and chemistry.
|
|
13
|
+
|
|
14
|
+
G-ADOPT is supported and funded by the Australian Research Data Commons (ARDC), with additional partner contributions from AuScope, the NCI and Geosciences Australia.
|
|
15
|
+
|
|
16
|
+
At this stage, the repository contains material associated with Davies et al. 2021 -- Geoscientific Model Development -- "Automating Finite Element Methods for Geodynamics via Firedrake". The examples and test cases presented in this paper can be found in the Davies_etal_GMD_2021 directory.
|
|
17
|
+
|
|
18
|
+
Additional material will be added as the project develops.
|
gadopt-2.0.0/README.md
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
This repository contains material and examples relating to the G-Adopt Platform, a computational platform for inverse geodynamics, being developed and maintained by researchers from the Research School of Earth Sciences at the Australian National University. It builds on a recent surge in accessible observational datasets and advances in inversion methods using sophisticated adjoint techniques that provide a mechanism for fusing these observations with dynamics, physics and chemistry.
|
|
2
|
+
|
|
3
|
+
G-ADOPT is supported and funded by the Australian Research Data Commons (ARDC), with additional partner contributions from AuScope, the NCI and Geosciences Australia.
|
|
4
|
+
|
|
5
|
+
At this stage, the repository contains material associated with Davies et al. 2021 -- Geoscientific Model Development -- "Automating Finite Element Methods for Geodynamics via Firedrake". The examples and test cases presented in this paper can be found in the Davies_etal_GMD_2021 directory.
|
|
6
|
+
|
|
7
|
+
Additional material will be added as the project develops.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from firedrake import *
|
|
2
|
+
from .time_stepper import CrankNicolsonRK, ImplicitMidpoint
|
|
3
|
+
from .limiter import VertexBasedP1DGLimiter
|
|
4
|
+
from .utility import log, ParameterLog, TimestepAdaptor, LayerAveraging, timer_decorator
|
|
5
|
+
from .diagnostics import GeodynamicalDiagnostics
|
|
6
|
+
from .momentum_equation import StokesEquations
|
|
7
|
+
from .scalar_equation import EnergyEquation
|
|
8
|
+
from .stokes_integrators import StokesSolver, create_stokes_nullspace
|
|
9
|
+
from .energy_solver import EnergySolver
|
|
10
|
+
from .approximations import BoussinesqApproximation, ExtendedBoussinesqApproximation, AnelasticLiquidApproximation, TruncatedAnelasticLiquidApproximation
|
|
11
|
+
|
|
12
|
+
from firedrake.petsc import PETSc
|
|
13
|
+
PETSc.Sys.popErrorHandler()
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import abc
|
|
2
|
+
from firedrake import sym, grad, inner, div, Identity
|
|
3
|
+
from .utility import ensure_constant, vertical_component
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class BaseApproximation:
|
|
7
|
+
"""
|
|
8
|
+
Base class to provide expressions in (Navier?)-Stokes + energy equations
|
|
9
|
+
|
|
10
|
+
Basic assumption is that we are solving (to be extended when needed)
|
|
11
|
+
|
|
12
|
+
div(dev_stress) + grad p + buoyancy(T, p) * khat = 0
|
|
13
|
+
|
|
14
|
+
div(rho_continuity * u) = 0
|
|
15
|
+
|
|
16
|
+
rhocp DT/Dt + linearized_energy_sink(u) * T = div(kappa*grad(Tbar + T)) + energy_source(u)
|
|
17
|
+
|
|
18
|
+
where the following are provided by Approximation methods:
|
|
19
|
+
|
|
20
|
+
linearized_energy_sink(u) = 0 (BA/EBA) or Di*rhobar*alphabar*w (ALA)
|
|
21
|
+
kappa() is diffusivity or conductivity depending on rhocp()
|
|
22
|
+
Tbar (property) is 0 or reference temperature profile (ALA)
|
|
23
|
+
compressible (property) False or True
|
|
24
|
+
if compressible then dev_stess=mu*[sym(grad(u)-2/3 div(u()]
|
|
25
|
+
if not compressible then dev_stress=mu*sym(grad(u)) and rho_continuity is assumed to be 1
|
|
26
|
+
"""
|
|
27
|
+
@property
|
|
28
|
+
@abc.abstractmethod
|
|
29
|
+
def compressible(self):
|
|
30
|
+
"Whether approximation is compressible (True/False)"
|
|
31
|
+
pass
|
|
32
|
+
|
|
33
|
+
@abc.abstractmethod
|
|
34
|
+
def buoyancy(self, p, T):
|
|
35
|
+
"UFL expression for buoyancy (momentum source in gravity direction)"
|
|
36
|
+
pass
|
|
37
|
+
|
|
38
|
+
@abc.abstractmethod
|
|
39
|
+
def rho_continuity(self):
|
|
40
|
+
"UFL expression for density in mass continuity equation (=1 for incompressible"
|
|
41
|
+
pass
|
|
42
|
+
|
|
43
|
+
@abc.abstractmethod
|
|
44
|
+
def rhocp(self):
|
|
45
|
+
"UFL expression expression for coefficient in front of DT/dt in energy eequation"
|
|
46
|
+
pass
|
|
47
|
+
|
|
48
|
+
@abc.abstractmethod
|
|
49
|
+
def kappa(self):
|
|
50
|
+
"UFL expression for diffusivity/conductivity"
|
|
51
|
+
pass
|
|
52
|
+
|
|
53
|
+
@property
|
|
54
|
+
@abc.abstractmethod
|
|
55
|
+
def Tbar(self):
|
|
56
|
+
"Reference temperature profile"
|
|
57
|
+
pass
|
|
58
|
+
|
|
59
|
+
@abc.abstractmethod
|
|
60
|
+
def linearized_energy_sink(self, u):
|
|
61
|
+
"UFL expression for (temperature dependent) sink terms in energy equation."
|
|
62
|
+
pass
|
|
63
|
+
|
|
64
|
+
@abc.abstractmethod
|
|
65
|
+
def energy_source(self, u):
|
|
66
|
+
"UFL expression for any other terms (not dependent on T or u) in energy equation"
|
|
67
|
+
pass
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class BoussinesqApproximation(BaseApproximation):
|
|
71
|
+
"""Boussinesq approximation:
|
|
72
|
+
|
|
73
|
+
Small density variation linear in Temperature only, only taken into account in buoyancy term.
|
|
74
|
+
All references rho, cp, alpha are constant and typically incorporated in Ra
|
|
75
|
+
Viscosous dissipation is neglected (Di << 1)."""
|
|
76
|
+
compressible = False
|
|
77
|
+
|
|
78
|
+
def __init__(self, Ra, kappa=1, g=1, rho=1, alpha=1):
|
|
79
|
+
"""
|
|
80
|
+
:arg Ra: Rayleigh number
|
|
81
|
+
:arg kappa, g, rho, alpha: Diffusivity, gravitational acceleration, reference density and thermal expansion coefficient
|
|
82
|
+
Normally kept at 1 when non-dimensionalised."""
|
|
83
|
+
self.Ra = ensure_constant(Ra)
|
|
84
|
+
self._kappa = ensure_constant(kappa)
|
|
85
|
+
self.g = ensure_constant(g)
|
|
86
|
+
self.rho = ensure_constant(rho)
|
|
87
|
+
self.alpha = ensure_constant(alpha)
|
|
88
|
+
|
|
89
|
+
def buoyancy(self, p, T):
|
|
90
|
+
return self.Ra * self.g * self.alpha * self.rho * T
|
|
91
|
+
|
|
92
|
+
def rho_continuity(self):
|
|
93
|
+
return 1
|
|
94
|
+
|
|
95
|
+
def rhocp(self):
|
|
96
|
+
return 1
|
|
97
|
+
|
|
98
|
+
def kappa(self):
|
|
99
|
+
return self._kappa
|
|
100
|
+
|
|
101
|
+
Tbar = 0
|
|
102
|
+
|
|
103
|
+
def linearized_energy_sink(self, u):
|
|
104
|
+
return 0
|
|
105
|
+
|
|
106
|
+
def energy_source(self, u):
|
|
107
|
+
return 0
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
class ExtendedBoussinesqApproximation(BoussinesqApproximation):
|
|
111
|
+
"""
|
|
112
|
+
Extended Boussinesq
|
|
113
|
+
|
|
114
|
+
As Boussinesq but includes viscous dissipation and work against gravity (both scaled with Di)."""
|
|
115
|
+
compressible = False
|
|
116
|
+
|
|
117
|
+
def __init__(self, Ra, Di, mu=1, H=None, cartesian=True, **kwargs):
|
|
118
|
+
"""
|
|
119
|
+
:arg Ra: Rayleigh number
|
|
120
|
+
:arg Di: Dissipation number
|
|
121
|
+
:arg mu: Viscosity used in viscous dissipation
|
|
122
|
+
:arg H: Volumetric heat production
|
|
123
|
+
:arg cartesian: True: gravity points in negative z-direction, False: gravity points radially inward
|
|
124
|
+
:arg kappa, g, rho, alpha: Diffusivity, gravitational acceleration, reference density and thermal expansion coefficient
|
|
125
|
+
Normally kept at 1 when non-dimensionalised."""
|
|
126
|
+
|
|
127
|
+
super().__init__(Ra, **kwargs)
|
|
128
|
+
self.Di = Di
|
|
129
|
+
self.mu = mu
|
|
130
|
+
self.H = H
|
|
131
|
+
self.cartesian = cartesian
|
|
132
|
+
|
|
133
|
+
def viscous_dissipation(self, u):
|
|
134
|
+
stress = 2 * self.mu * sym(grad(u))
|
|
135
|
+
if self.compressible: # (used in AnelasticLiquidApproximations below)
|
|
136
|
+
stress -= 2/3 * self.mu * div(u) * Identity(u.ufl_shape[0])
|
|
137
|
+
phi = inner(stress, grad(u))
|
|
138
|
+
return phi * self.Di / self.Ra
|
|
139
|
+
|
|
140
|
+
def work_against_gravity(self, u, T):
|
|
141
|
+
w = vertical_component(u, self.cartesian)
|
|
142
|
+
return self.Di * self.alpha * self.rho * self.g * w * T
|
|
143
|
+
|
|
144
|
+
def linearized_energy_sink(self, u):
|
|
145
|
+
return self.work_against_gravity(u, 1)
|
|
146
|
+
|
|
147
|
+
def energy_source(self, u):
|
|
148
|
+
source = self.viscous_dissipation(u)
|
|
149
|
+
if self.H:
|
|
150
|
+
source += self.H * self.rho
|
|
151
|
+
|
|
152
|
+
return source
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
class TruncatedAnelasticLiquidApproximation(ExtendedBoussinesqApproximation):
|
|
156
|
+
"""
|
|
157
|
+
Truncated Anelastic Liquid Approximation
|
|
158
|
+
|
|
159
|
+
Compressible approximation. Excludes linear dependence of density on pressure (chi)"""
|
|
160
|
+
compressible = True
|
|
161
|
+
|
|
162
|
+
def __init__(self, Ra, Di,
|
|
163
|
+
Tbar=0, chi=1, cp=1,
|
|
164
|
+
gamma0=1, cp0=1, cv0=1,
|
|
165
|
+
**kwargs):
|
|
166
|
+
"""
|
|
167
|
+
:arg Ra: Rayleigh number
|
|
168
|
+
:arg Di: Dissipation number
|
|
169
|
+
Reference values that may be depth-dependent (default to 1 if not supplied):
|
|
170
|
+
:arg rho: reference density
|
|
171
|
+
:arg alpha: reference thermal expansion coefficient
|
|
172
|
+
:arg Tbar: reference temperature. In the diffusion term we use Tbar + T (i.e. T is the pertubartion) - default 0
|
|
173
|
+
:arg chi: reference isothermal compressibility
|
|
174
|
+
:arg cp: reference specific heat at constant pressure
|
|
175
|
+
:arg mu: Viscosity used in viscous dissipation
|
|
176
|
+
:arg H: Volumetric heat production - default 0
|
|
177
|
+
:arg cartesian: True: gravity points in negative z-direction, False: gravity points radially inward
|
|
178
|
+
:arg kappa, g: Diffusivity, gravitational acceleration
|
|
179
|
+
Constant coefficients in pressure dependent buoyancy term::w
|
|
180
|
+
:arg gamma0: Gruneisen number
|
|
181
|
+
:arg cp0, cv0: specific heat at constant pressure and volume reference for entire Mantle (all references above may be depth-dependent)."""
|
|
182
|
+
super().__init__(Ra, Di, **kwargs)
|
|
183
|
+
self.Tbar = Tbar
|
|
184
|
+
# Equation of State:
|
|
185
|
+
self.chi = chi
|
|
186
|
+
self.cp = cp
|
|
187
|
+
assert 'g' not in kwargs
|
|
188
|
+
self.gamma0, self.cp0, self.cv0 = gamma0, cp0, cv0
|
|
189
|
+
|
|
190
|
+
def rho_continuity(self):
|
|
191
|
+
return self.rho
|
|
192
|
+
|
|
193
|
+
def rhocp(self):
|
|
194
|
+
return self.rho * self.cp
|
|
195
|
+
|
|
196
|
+
def linearized_energy_sink(self, u):
|
|
197
|
+
w = vertical_component(u, self.cartesian)
|
|
198
|
+
return self.Di * self.rho * self.alpha * w
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
class AnelasticLiquidApproximation(TruncatedAnelasticLiquidApproximation):
|
|
202
|
+
"""
|
|
203
|
+
Anelastic Liquid Approximation
|
|
204
|
+
|
|
205
|
+
Compressible approximation. Includes linear dependence of density on pressure (chi)"""
|
|
206
|
+
|
|
207
|
+
def buoyancy(self, p, T):
|
|
208
|
+
pressure_part = -self.Di * self.cp0 / self.cv0 / self.gamma0 * self.g * self.rho * self.chi * p
|
|
209
|
+
temperature_part = super().buoyancy(p, T)
|
|
210
|
+
return pressure_part + temperature_part
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import firedrake
|
|
2
|
+
from firedrake import assemble, Constant, sqrt, dot, grad, FacetNormal
|
|
3
|
+
from .utility import CombinedSurfaceMeasure
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def domain_volume(mesh):
|
|
7
|
+
return assemble(Constant(1)*firedrake.dx(domain=mesh))
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class GeodynamicalDiagnostics:
|
|
11
|
+
|
|
12
|
+
def __init__(self, u, p, T, bottom_id, top_id, degree=4):
|
|
13
|
+
mesh = u.ufl_domain()
|
|
14
|
+
self.domain_volume = domain_volume(mesh)
|
|
15
|
+
self.u = u
|
|
16
|
+
self.p = p
|
|
17
|
+
self.T = T
|
|
18
|
+
self.dx = firedrake.dx(degree=degree)
|
|
19
|
+
if T.function_space().extruded:
|
|
20
|
+
ds = CombinedSurfaceMeasure(mesh, degree)
|
|
21
|
+
else:
|
|
22
|
+
ds = firedrake.ds(mesh)
|
|
23
|
+
self.ds_t = ds(top_id)
|
|
24
|
+
self.ds_b = ds(bottom_id)
|
|
25
|
+
self.n = FacetNormal(mesh)
|
|
26
|
+
|
|
27
|
+
def u_rms(self):
|
|
28
|
+
return sqrt(assemble(dot(self.u, self.u) * self.dx)) * sqrt(1./self.domain_volume)
|
|
29
|
+
|
|
30
|
+
def u_rms_top(self):
|
|
31
|
+
return sqrt(assemble(dot(self.u, self.u) * self.ds_t))
|
|
32
|
+
|
|
33
|
+
def Nu_top(self):
|
|
34
|
+
return -1 * assemble(dot(grad(self.T), self.n) * self.ds_t) * (1./assemble(Constant(1) * self.ds_t))
|
|
35
|
+
|
|
36
|
+
def Nu_bottom(self):
|
|
37
|
+
return assemble(dot(grad(self.T), self.n) * self.ds_b) * (1./assemble(Constant(1) * self.ds_b))
|
|
38
|
+
|
|
39
|
+
def T_avg(self):
|
|
40
|
+
return assemble(self.T * self.dx) / self.domain_volume
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
from firedrake import DirichletBC, Function
|
|
2
|
+
from .scalar_equation import EnergyEquation
|
|
3
|
+
from .utility import is_continuous, ensure_constant
|
|
4
|
+
from .utility import log_level, INFO, DEBUG
|
|
5
|
+
|
|
6
|
+
iterative_energy_solver_parameters = {
|
|
7
|
+
"mat_type": "aij",
|
|
8
|
+
"snes_type": "ksponly",
|
|
9
|
+
"ksp_type": "gmres",
|
|
10
|
+
"ksp_rtol": 1e-5,
|
|
11
|
+
"pc_type": "sor",
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
direct_energy_solver_parameters = {
|
|
15
|
+
"mat_type": "aij",
|
|
16
|
+
"snes_type": "ksponly",
|
|
17
|
+
"ksp_type": "preonly",
|
|
18
|
+
"pc_type": "lu",
|
|
19
|
+
"pc_factor_mat_solver_type": "mumps",
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class EnergySolver:
|
|
24
|
+
def __init__(self, T, u, approximation,
|
|
25
|
+
delta_t, timestepper, bcs=None, solver_parameters=None):
|
|
26
|
+
self.T = T
|
|
27
|
+
self.Q = T.function_space()
|
|
28
|
+
self.mesh = self.Q.mesh()
|
|
29
|
+
self.delta_t = delta_t
|
|
30
|
+
rhocp = approximation.rhocp()
|
|
31
|
+
self.eq = EnergyEquation(self.Q, self.Q, rhocp=rhocp)
|
|
32
|
+
self.fields = {
|
|
33
|
+
'diffusivity': ensure_constant(approximation.kappa()),
|
|
34
|
+
'reference_for_diffusion': approximation.Tbar,
|
|
35
|
+
'source': approximation.energy_source(u),
|
|
36
|
+
'velocity': u,
|
|
37
|
+
'advective_velocity_scaling': rhocp
|
|
38
|
+
}
|
|
39
|
+
sink = approximation.linearized_energy_sink(u)
|
|
40
|
+
if sink:
|
|
41
|
+
self.fields['absorption_coefficient'] = sink
|
|
42
|
+
|
|
43
|
+
if solver_parameters is None:
|
|
44
|
+
if self.mesh.topological_dimension() == 2:
|
|
45
|
+
self.solver_parameters = direct_energy_solver_parameters.copy()
|
|
46
|
+
if INFO >= log_level:
|
|
47
|
+
# not really "informative", but at least we get a 1-line message saying we've passed the energy solve
|
|
48
|
+
self.solver_parameters['ksp_converged_reason'] = None
|
|
49
|
+
else:
|
|
50
|
+
self.solver_parameters = iterative_energy_solver_parameters.copy()
|
|
51
|
+
if DEBUG >= log_level:
|
|
52
|
+
self.solver_parameters['ksp_monitor'] = None
|
|
53
|
+
elif INFO >= log_level:
|
|
54
|
+
self.solver_parameters['ksp_converged_reason'] = None
|
|
55
|
+
else:
|
|
56
|
+
self.solver_parameters = solver_parameters
|
|
57
|
+
apply_strongly = is_continuous(T)
|
|
58
|
+
self.strong_bcs = []
|
|
59
|
+
self.weak_bcs = {}
|
|
60
|
+
bcs = bcs or {}
|
|
61
|
+
for id, bc in bcs.items():
|
|
62
|
+
weak_bc = {}
|
|
63
|
+
for type, value in bc.items():
|
|
64
|
+
if type == 'T':
|
|
65
|
+
if apply_strongly:
|
|
66
|
+
self.strong_bcs.append(DirichletBC(self.Q, value, id))
|
|
67
|
+
else:
|
|
68
|
+
weak_bc['q'] = value
|
|
69
|
+
else:
|
|
70
|
+
weak_bc[type] = value
|
|
71
|
+
self.weak_bcs[id] = weak_bc
|
|
72
|
+
|
|
73
|
+
self.timestepper = timestepper
|
|
74
|
+
self.T_old = Function(self.Q)
|
|
75
|
+
# solver is setup only last minute
|
|
76
|
+
# so people can overwrite parameters we've setup here
|
|
77
|
+
self._solver_setup = False
|
|
78
|
+
|
|
79
|
+
def setup_solver(self):
|
|
80
|
+
"""Setup timestepper and associated solver"""
|
|
81
|
+
self.ts = self.timestepper(self.eq, self.T, self.fields, self.delta_t,
|
|
82
|
+
bnd_conditions=self.weak_bcs, solution_old=self.T_old,
|
|
83
|
+
strong_bcs=self.strong_bcs,
|
|
84
|
+
solver_parameters=self.solver_parameters)
|
|
85
|
+
self._solver_setup = True
|
|
86
|
+
|
|
87
|
+
def solve(self):
|
|
88
|
+
if not self._solver_setup:
|
|
89
|
+
self.setup_solver()
|
|
90
|
+
t = 0 # not used atm
|
|
91
|
+
self.ts.advance(t)
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
import firedrake
|
|
3
|
+
from .utility import CombinedSurfaceMeasure
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class BaseEquation(ABC):
|
|
7
|
+
"""An equation class that can produce the UFL for the registered terms."""
|
|
8
|
+
|
|
9
|
+
"""This should be a list of BaseTerm sub-classes that form the terms of the equation."""
|
|
10
|
+
terms = []
|
|
11
|
+
|
|
12
|
+
def __init__(self, test_space, trial_space, quad_degree=None, **kwargs):
|
|
13
|
+
"""
|
|
14
|
+
:arg test_space: the test functionspace
|
|
15
|
+
:arg trial_space: The trial functionspace
|
|
16
|
+
test and trial space are only used to determine the the discretisation that's used (ufl_element)
|
|
17
|
+
not what test and trial functions are actually used (these are provided seperately in residual())
|
|
18
|
+
:arg quad_degree: quadrature degree, default is 2*p+1 where p is the polynomial degree of trial_space
|
|
19
|
+
any further keywords arguments are passed on to the individual terms
|
|
20
|
+
"""
|
|
21
|
+
self.test_space = test_space
|
|
22
|
+
self.trial_space = trial_space
|
|
23
|
+
self.mesh = trial_space.mesh()
|
|
24
|
+
|
|
25
|
+
p = trial_space.ufl_element().degree()
|
|
26
|
+
if isinstance(p, int):
|
|
27
|
+
# isotropic element
|
|
28
|
+
if quad_degree is None:
|
|
29
|
+
quad_degree = 2*p + 1
|
|
30
|
+
else:
|
|
31
|
+
# tensorproduct element
|
|
32
|
+
p_h, p_v = p
|
|
33
|
+
if quad_degree is None:
|
|
34
|
+
quad_degree = 2*max(p_h, p_v) + 1
|
|
35
|
+
|
|
36
|
+
if trial_space.extruded:
|
|
37
|
+
# extruded mesh: create surface measures that treat the bottom and top boundaries the same as lateral boundaries
|
|
38
|
+
# so that ds or dS integrates over both horizontal and vertical boundaries
|
|
39
|
+
# and we can also use "bottom" and "top" as surface ids, e.g. ds("top")
|
|
40
|
+
self.ds = CombinedSurfaceMeasure(self.mesh, quad_degree)
|
|
41
|
+
self.dS = firedrake.dS_v(domain=self.mesh, degree=quad_degree) + firedrake.dS_h(domain=self.mesh, degree=quad_degree)
|
|
42
|
+
else:
|
|
43
|
+
self.ds = firedrake.ds(domain=self.mesh, degree=quad_degree)
|
|
44
|
+
self.dS = firedrake.dS(domain=self.mesh, degree=quad_degree)
|
|
45
|
+
|
|
46
|
+
self.dx = firedrake.dx(domain=self.mesh, degree=quad_degree)
|
|
47
|
+
|
|
48
|
+
# self._terms stores the actual instances of the BaseTerm-classes in self.terms
|
|
49
|
+
self._terms = []
|
|
50
|
+
for TermClass in self.terms:
|
|
51
|
+
self._terms.append(TermClass(test_space, trial_space, self.dx, self.ds, self.dS, **kwargs))
|
|
52
|
+
|
|
53
|
+
def mass_term(self, test, trial):
|
|
54
|
+
r"""Return the UFL for the mass term \int test * trial * dx typically used in the time term."""
|
|
55
|
+
return firedrake.inner(test, trial) * self.dx
|
|
56
|
+
|
|
57
|
+
def residual(self, test, trial, trial_lagged=None, fields=None, bcs=None):
|
|
58
|
+
"""Return the UFL for all terms (except the time derivative)."""
|
|
59
|
+
if trial_lagged is None:
|
|
60
|
+
trial_lagged = trial
|
|
61
|
+
if fields is None:
|
|
62
|
+
fields = {}
|
|
63
|
+
if bcs is None:
|
|
64
|
+
bcs = {}
|
|
65
|
+
F = 0
|
|
66
|
+
for term in self._terms:
|
|
67
|
+
F += term.residual(test, trial, trial_lagged, fields, bcs)
|
|
68
|
+
|
|
69
|
+
return F
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class BaseTerm(ABC):
|
|
73
|
+
"""A term in an equation, that can produce the UFL expression for its contribution to the FEM residual."""
|
|
74
|
+
def __init__(self, test_space, trial_space, dx, ds, dS, **kwargs):
|
|
75
|
+
self.test_space = test_space
|
|
76
|
+
self.trial_space = trial_space
|
|
77
|
+
self.dx = dx
|
|
78
|
+
self.ds = ds
|
|
79
|
+
self.dS = dS
|
|
80
|
+
self.mesh = test_space.mesh()
|
|
81
|
+
self.dim = self.mesh.geometric_dimension()
|
|
82
|
+
self.n = firedrake.FacetNormal(self.mesh)
|
|
83
|
+
self.term_kwargs = kwargs
|
|
84
|
+
|
|
85
|
+
@abstractmethod
|
|
86
|
+
def residual(self, test, trial, trial_lagged, fields):
|
|
87
|
+
"""Return the UFL for this term"""
|
|
88
|
+
pass
|