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 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