planetmodel 1.0.0__py3-none-any.whl
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.
- planetmodel/__init__.py +208 -0
- planetmodel/behaviours.py +286 -0
- planetmodel/catalogue.py +274 -0
- planetmodel/character.py +146 -0
- planetmodel/deck.py +447 -0
- planetmodel/displacement.py +312 -0
- planetmodel/fields.py +599 -0
- planetmodel/frames.py +232 -0
- planetmodel/geometry.py +420 -0
- planetmodel/harmonics.py +184 -0
- planetmodel/layerfunction.py +503 -0
- planetmodel/loading/__init__.py +41 -0
- planetmodel/loading/assembly.py +391 -0
- planetmodel/loading/love.py +342 -0
- planetmodel/loading/material.py +184 -0
- planetmodel/mapping.py +512 -0
- planetmodel/materials.py +465 -0
- planetmodel/mesh1d/__init__.py +8 -0
- planetmodel/mesh1d/gll.py +81 -0
- planetmodel/mesh1d/gravity.py +152 -0
- planetmodel/mesh1d/mesh.py +266 -0
- planetmodel/mesh3d/__init__.py +41 -0
- planetmodel/mesh3d/_geometry.py +138 -0
- planetmodel/mesh3d/_orient.py +228 -0
- planetmodel/mesh3d/_session.py +76 -0
- planetmodel/mesh3d/_sizing.py +144 -0
- planetmodel/mesh3d/_tagging.py +201 -0
- planetmodel/mesh3d/_validate.py +134 -0
- planetmodel/mesh3d/_writer.py +90 -0
- planetmodel/mesh3d/export.py +514 -0
- planetmodel/mesh3d/layered.py +202 -0
- planetmodel/mesh3d/manifest.py +547 -0
- planetmodel/mesh3d/offset.py +246 -0
- planetmodel/mesh3d/spec.py +400 -0
- planetmodel/model.py +471 -0
- planetmodel/plotting.py +99 -0
- planetmodel/pushforward.py +322 -0
- planetmodel/randomfield/__init__.py +28 -0
- planetmodel/randomfield/fields.py +514 -0
- planetmodel/randomfield/operator.py +385 -0
- planetmodel/rheology.py +173 -0
- planetmodel/sampling.py +434 -0
- planetmodel/skeleton.py +285 -0
- planetmodel/testing.py +634 -0
- planetmodel/units.py +204 -0
- planetmodel/vocabulary.py +117 -0
- planetmodel-1.0.0.dist-info/METADATA +161 -0
- planetmodel-1.0.0.dist-info/RECORD +50 -0
- planetmodel-1.0.0.dist-info/WHEEL +4 -0
- planetmodel-1.0.0.dist-info/licenses/LICENSE +28 -0
planetmodel/__init__.py
ADDED
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
"""planetmodel: spherically layered planetary models, and their meshes.
|
|
2
|
+
|
|
3
|
+
A model is a skeleton of boundary radii, a geometry that places the
|
|
4
|
+
skeleton in the physical world through one continuous mapping, and a
|
|
5
|
+
bag of fields on each layer. This package holds the skeleton and the
|
|
6
|
+
geometry, the mappings that place them, the fields and the model, the
|
|
7
|
+
catalogue of named models, and the meshers that hand a model to a
|
|
8
|
+
solver. Every internal is numbers: the model alone carries units.
|
|
9
|
+
|
|
10
|
+
Modules reached by name, one level down:
|
|
11
|
+
|
|
12
|
+
frames the local spherical frame, tensors moved between frames, Voigt
|
|
13
|
+
materials velocity and moduli conversions, the elastic field, what a
|
|
14
|
+
layer's fields imply (fluidity, moduli)
|
|
15
|
+
rheology what a layer's rheology fields imply, and a model frozen at
|
|
16
|
+
a frequency with complex moduli
|
|
17
|
+
units Dimensions, Scales and the dimension constants
|
|
18
|
+
vocabulary the shipped field names, their characters and dimensions
|
|
19
|
+
behaviours the free functions as methods: `layer_method` and the mixins
|
|
20
|
+
catalogue PREM, LayeredIsotropicElastic and MineosModel, model types
|
|
21
|
+
deck tabulated models: decks, their formats, fields by interpolation
|
|
22
|
+
sampling a model on a radial mesh times an angular grid
|
|
23
|
+
harmonics real spherical harmonics; grid transforms via pyshtools (optional)
|
|
24
|
+
plotting radial profiles drawn one way, radius upward (matplotlib, optional)
|
|
25
|
+
mesh1d radial spectral-element meshes (GLL), nodal values, gravity
|
|
26
|
+
mesh3d 2D and 3D meshes via gmsh, and MFEM export
|
|
27
|
+
loading the loading and tidal problem on a radial mesh: Love numbers
|
|
28
|
+
randomfield Matern random fields on balls, annuli and layers
|
|
29
|
+
testing the executable contracts, `check_field` and its kin
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
from .behaviours import (
|
|
33
|
+
ConstantQ,
|
|
34
|
+
Elastic,
|
|
35
|
+
SelfGravitating,
|
|
36
|
+
Viscoelastic,
|
|
37
|
+
layer_method,
|
|
38
|
+
with_moduli,
|
|
39
|
+
with_velocities,
|
|
40
|
+
)
|
|
41
|
+
from .catalogue import PREM, LayeredIsotropicElastic, MineosModel
|
|
42
|
+
from .character import DENSITY, ELASTIC, SCALAR, STRESS, VECTOR, Character, Symmetry
|
|
43
|
+
from .deck import (
|
|
44
|
+
MINEOS,
|
|
45
|
+
Deck,
|
|
46
|
+
DeckFormat,
|
|
47
|
+
Tabulated,
|
|
48
|
+
deck_layers,
|
|
49
|
+
read_deck,
|
|
50
|
+
write_deck,
|
|
51
|
+
)
|
|
52
|
+
from .displacement import (
|
|
53
|
+
CallableDisplacement,
|
|
54
|
+
RadialDisplacement,
|
|
55
|
+
ZeroDisplacement,
|
|
56
|
+
as_displacement,
|
|
57
|
+
flattening,
|
|
58
|
+
layer_linear,
|
|
59
|
+
)
|
|
60
|
+
from .fields import (
|
|
61
|
+
AnalyticField,
|
|
62
|
+
ComposedField,
|
|
63
|
+
Field,
|
|
64
|
+
FieldBase,
|
|
65
|
+
RadialField,
|
|
66
|
+
constant_field,
|
|
67
|
+
)
|
|
68
|
+
from .geometry import Geometry, InterfaceInfo, LayerInfo
|
|
69
|
+
from .harmonics import analyse_grid, real_harmonics, synthesise, synthesise_grid
|
|
70
|
+
from .layerfunction import (
|
|
71
|
+
LayerFunction,
|
|
72
|
+
NumericLayer,
|
|
73
|
+
PolynomialLayer,
|
|
74
|
+
as_layer_function,
|
|
75
|
+
constant_layer,
|
|
76
|
+
polynomial_fit,
|
|
77
|
+
polynomial_layer,
|
|
78
|
+
)
|
|
79
|
+
from .mapping import (
|
|
80
|
+
IdentityMapping,
|
|
81
|
+
Mapping,
|
|
82
|
+
MappingBase,
|
|
83
|
+
MappingPerturbation,
|
|
84
|
+
RadialStretch,
|
|
85
|
+
ScaledMapping,
|
|
86
|
+
ValidityReport,
|
|
87
|
+
outer_radius_of,
|
|
88
|
+
validity_lattice,
|
|
89
|
+
)
|
|
90
|
+
from .materials import ElasticField, elastic_moduli, is_fluid, kappa_mu, moduli
|
|
91
|
+
from .mesh1d import RadialMesh
|
|
92
|
+
from .mesh1d.gravity import gravity, gravity_fields, mass
|
|
93
|
+
from .model import Layer, Model
|
|
94
|
+
from .pushforward import PulledBackField, PushedForwardField, pull_back, push_forward
|
|
95
|
+
from .rheology import (
|
|
96
|
+
dispersive_moduli,
|
|
97
|
+
frozen,
|
|
98
|
+
frozen_moduli,
|
|
99
|
+
is_viscoelastic,
|
|
100
|
+
reference_omega,
|
|
101
|
+
)
|
|
102
|
+
from .sampling import AngularGrid, Sample, equiangular, gauss_legendre, sample
|
|
103
|
+
from .skeleton import CoarseningMap, Location, Skeleton
|
|
104
|
+
from .units import EARTH_MEAN_DENSITY, G_SI, Dimensions, Scales
|
|
105
|
+
from .vocabulary import CONSTANTS, VOCABULARY, Constant, FieldSpec
|
|
106
|
+
from . import testing # noqa: F401
|
|
107
|
+
|
|
108
|
+
__version__ = "1.0.0"
|
|
109
|
+
|
|
110
|
+
__all__ = [
|
|
111
|
+
"Skeleton",
|
|
112
|
+
"Location",
|
|
113
|
+
"CoarseningMap",
|
|
114
|
+
"Geometry",
|
|
115
|
+
"LayerInfo",
|
|
116
|
+
"InterfaceInfo",
|
|
117
|
+
"Mapping",
|
|
118
|
+
"MappingBase",
|
|
119
|
+
"IdentityMapping",
|
|
120
|
+
"RadialStretch",
|
|
121
|
+
"ScaledMapping",
|
|
122
|
+
"ValidityReport",
|
|
123
|
+
"MappingPerturbation",
|
|
124
|
+
"validity_lattice",
|
|
125
|
+
"outer_radius_of",
|
|
126
|
+
"RadialDisplacement",
|
|
127
|
+
"ZeroDisplacement",
|
|
128
|
+
"CallableDisplacement",
|
|
129
|
+
"as_displacement",
|
|
130
|
+
"RadialMesh",
|
|
131
|
+
"Character",
|
|
132
|
+
"SCALAR",
|
|
133
|
+
"DENSITY",
|
|
134
|
+
"VECTOR",
|
|
135
|
+
"STRESS",
|
|
136
|
+
"ELASTIC",
|
|
137
|
+
"Symmetry",
|
|
138
|
+
"LayerFunction",
|
|
139
|
+
"PolynomialLayer",
|
|
140
|
+
"NumericLayer",
|
|
141
|
+
"as_layer_function",
|
|
142
|
+
"polynomial_layer",
|
|
143
|
+
"constant_layer",
|
|
144
|
+
"polynomial_fit",
|
|
145
|
+
"Field",
|
|
146
|
+
"FieldBase",
|
|
147
|
+
"RadialField",
|
|
148
|
+
"AnalyticField",
|
|
149
|
+
"ComposedField",
|
|
150
|
+
"constant_field",
|
|
151
|
+
"ElasticField",
|
|
152
|
+
"is_fluid",
|
|
153
|
+
"moduli",
|
|
154
|
+
"elastic_moduli",
|
|
155
|
+
"kappa_mu",
|
|
156
|
+
"is_viscoelastic",
|
|
157
|
+
"frozen_moduli",
|
|
158
|
+
"dispersive_moduli",
|
|
159
|
+
"frozen",
|
|
160
|
+
"reference_omega",
|
|
161
|
+
"push_forward",
|
|
162
|
+
"pull_back",
|
|
163
|
+
"PushedForwardField",
|
|
164
|
+
"PulledBackField",
|
|
165
|
+
"Dimensions",
|
|
166
|
+
"Scales",
|
|
167
|
+
"G_SI",
|
|
168
|
+
"EARTH_MEAN_DENSITY",
|
|
169
|
+
"FieldSpec",
|
|
170
|
+
"Constant",
|
|
171
|
+
"VOCABULARY",
|
|
172
|
+
"CONSTANTS",
|
|
173
|
+
"Layer",
|
|
174
|
+
"Model",
|
|
175
|
+
"layer_method",
|
|
176
|
+
"with_moduli",
|
|
177
|
+
"with_velocities",
|
|
178
|
+
"Elastic",
|
|
179
|
+
"ConstantQ",
|
|
180
|
+
"SelfGravitating",
|
|
181
|
+
"Viscoelastic",
|
|
182
|
+
"PREM",
|
|
183
|
+
"LayeredIsotropicElastic",
|
|
184
|
+
"MineosModel",
|
|
185
|
+
"Deck",
|
|
186
|
+
"DeckFormat",
|
|
187
|
+
"MINEOS",
|
|
188
|
+
"read_deck",
|
|
189
|
+
"write_deck",
|
|
190
|
+
"deck_layers",
|
|
191
|
+
"Tabulated",
|
|
192
|
+
"flattening",
|
|
193
|
+
"layer_linear",
|
|
194
|
+
"AngularGrid",
|
|
195
|
+
"Sample",
|
|
196
|
+
"sample",
|
|
197
|
+
"gauss_legendre",
|
|
198
|
+
"equiangular",
|
|
199
|
+
"real_harmonics",
|
|
200
|
+
"synthesise",
|
|
201
|
+
"synthesise_grid",
|
|
202
|
+
"analyse_grid",
|
|
203
|
+
"gravity",
|
|
204
|
+
"mass",
|
|
205
|
+
"gravity_fields",
|
|
206
|
+
"testing",
|
|
207
|
+
"__version__",
|
|
208
|
+
]
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
"""The free functions as methods: an adaptor, and stateless mixins.
|
|
2
|
+
|
|
3
|
+
A model type is a class derived from `Model` alone. What its
|
|
4
|
+
constructor is given is its base information, PREM's polynomial
|
|
5
|
+
coefficients say; the model is that information and everything derived
|
|
6
|
+
from it, fields and behaviour alike, and the mixins here are the common
|
|
7
|
+
derivations written once: the elastic description completed in both
|
|
8
|
+
directions (moduli from velocities and velocities from moduli), the
|
|
9
|
+
elastic tensor and its averages, gravity, and the linear rheologies
|
|
10
|
+
built from their static parts. A model type gets them by mixing in,
|
|
11
|
+
never by reimplementing them and never through a hierarchy of model
|
|
12
|
+
types. Two forms of wrapping are here.
|
|
13
|
+
|
|
14
|
+
`layer_method(fn)` turns a function of a layer, such as `moduli(layer)`,
|
|
15
|
+
into a method `model.moduli(which)` that resolves `which` (an index or a
|
|
16
|
+
name) through `model.layer`. A function of a model, such as
|
|
17
|
+
`gravity(model, radii)` or `frozen(model, omega)`, needs no adaptor:
|
|
18
|
+
assigned in a class body it is already a method.
|
|
19
|
+
|
|
20
|
+
The mixins bundle the wrapped methods a kind of model exposes. Each is
|
|
21
|
+
a class body of such assignments and nothing else, with one exception:
|
|
22
|
+
`Elastic` has a constructor hook that attaches the five Love moduli to
|
|
23
|
+
every layer as first-class fields, since a spherically symmetric
|
|
24
|
+
elastic medium is those five and there is no reason to hide them behind
|
|
25
|
+
a call. No mixin holds state of its own, so `class PREM(Elastic,
|
|
26
|
+
ConstantQ, SelfGravitating, Viscoelastic, Model)` is still one model
|
|
27
|
+
type derived from `Model` alone. The transformations `isotropic`,
|
|
28
|
+
`elastic` and `with_gravity` return copies of the same class through
|
|
29
|
+
`Model.replaced`.
|
|
30
|
+
"""
|
|
31
|
+
from __future__ import annotations
|
|
32
|
+
|
|
33
|
+
import functools
|
|
34
|
+
from collections.abc import Callable, Mapping
|
|
35
|
+
from typing import TYPE_CHECKING, Any
|
|
36
|
+
|
|
37
|
+
import numpy as np
|
|
38
|
+
|
|
39
|
+
from . import materials, rheology
|
|
40
|
+
from .character import SCALAR
|
|
41
|
+
from .fields import ComposedField, Field
|
|
42
|
+
from .mesh1d.gravity import gravity, gravity_fields, mass
|
|
43
|
+
|
|
44
|
+
if TYPE_CHECKING:
|
|
45
|
+
from .model import Model
|
|
46
|
+
|
|
47
|
+
__all__ = ["layer_method", "with_moduli", "with_velocities", "Elastic", "ConstantQ",
|
|
48
|
+
"SelfGravitating", "Viscoelastic", "ELASTIC_FAMILIES", "elastic_family"]
|
|
49
|
+
|
|
50
|
+
#: The velocity names an elastic description may be given in.
|
|
51
|
+
VELOCITY_NAMES = ("vp", "vs", "vpv", "vph", "vsv", "vsh", "eta")
|
|
52
|
+
|
|
53
|
+
#: The ways a layer describes its elastic medium, each a family of names
|
|
54
|
+
#: that is complete on its own: the velocities beside rho, the isotropic
|
|
55
|
+
#: moduli, the five Love moduli, and the tensor stored under
|
|
56
|
+
#: `elastic_moduli`.
|
|
57
|
+
ELASTIC_FAMILIES = (VELOCITY_NAMES,
|
|
58
|
+
materials.MODULI_NAMES[materials.Symmetry.ISOTROPIC],
|
|
59
|
+
materials.MODULI_NAMES[materials.Symmetry.VTI],
|
|
60
|
+
("elastic_moduli",))
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def elastic_family(name: str) -> tuple[str, ...]:
|
|
64
|
+
"""The family of `ELASTIC_FAMILIES` an elastic name belongs to."""
|
|
65
|
+
for family in ELASTIC_FAMILIES:
|
|
66
|
+
if name in family:
|
|
67
|
+
return family
|
|
68
|
+
raise KeyError(f"{name!r} is not an elastic name; those are "
|
|
69
|
+
f"{list(materials.ELASTIC_NAMES)}")
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def layer_method[T](fn: Callable[..., T]) -> Callable[..., T]:
|
|
73
|
+
"""`fn(layer, *args, **kwargs)` as a method `model.fn(which, *args, **kwargs)`.
|
|
74
|
+
|
|
75
|
+
`which` is a layer index (negatives counting back) or a layer name,
|
|
76
|
+
resolved by `model.layer`; the docstring and name are the function's.
|
|
77
|
+
"""
|
|
78
|
+
@functools.wraps(fn)
|
|
79
|
+
def method(self: Model, which: int | str, *args: object, **kwargs: object) -> T:
|
|
80
|
+
return fn(self.layer(which), *args, **kwargs)
|
|
81
|
+
return method
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def with_moduli(fields: Mapping[str, Field]) -> dict[str, Field]:
|
|
85
|
+
"""A layer's fields with the five Love moduli A, C, F, L, N added,
|
|
86
|
+
read from whatever elastic description it holds; unchanged where it
|
|
87
|
+
already holds the five, holds its tensor directly, or holds no
|
|
88
|
+
elastic description at all."""
|
|
89
|
+
out = dict(fields)
|
|
90
|
+
five = materials.MODULI_NAMES[materials.Symmetry.VTI]
|
|
91
|
+
if (all(n in out for n in five) or "elastic_moduli" in out
|
|
92
|
+
or not any(n in out for n in materials.ELASTIC_NAMES)):
|
|
93
|
+
return out
|
|
94
|
+
out.update(materials.moduli(out))
|
|
95
|
+
return out
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def with_velocities(fields: Mapping[str, Field]) -> dict[str, Field]:
|
|
99
|
+
"""A layer's fields with the velocities added from rho and the five
|
|
100
|
+
Love moduli: vp and vs where the five are isotropic, else vpv, vph,
|
|
101
|
+
vsv, vsh and eta. Square roots are taken pointwise, so these are
|
|
102
|
+
composed fields, not polynomials. Unchanged where the layer already
|
|
103
|
+
holds any velocity, lacks rho or the five, or holds its tensor
|
|
104
|
+
directly."""
|
|
105
|
+
out = dict(fields)
|
|
106
|
+
five = materials.MODULI_NAMES[materials.Symmetry.VTI]
|
|
107
|
+
if (any(n in out for n in VELOCITY_NAMES) or "rho" not in out
|
|
108
|
+
or not all(n in out for n in five) or "elastic_moduli" in out):
|
|
109
|
+
return out
|
|
110
|
+
rho = out["rho"]
|
|
111
|
+
A, C, F, L, N = (out[n] for n in five)
|
|
112
|
+
|
|
113
|
+
def speed(modulus: Field, name: str) -> Field:
|
|
114
|
+
return ComposedField(lambda m, d: np.sqrt(m / d), (modulus, rho),
|
|
115
|
+
character=SCALAR, name=name)
|
|
116
|
+
|
|
117
|
+
symmetry, _ = materials.independent_moduli(out)
|
|
118
|
+
if symmetry is materials.Symmetry.ISOTROPIC:
|
|
119
|
+
out["vp"] = speed(C, "vp")
|
|
120
|
+
out["vs"] = speed(L, "vs")
|
|
121
|
+
return out
|
|
122
|
+
out["vpv"], out["vph"] = speed(C, "vpv"), speed(A, "vph")
|
|
123
|
+
out["vsv"], out["vsh"] = speed(L, "vsv"), speed(N, "vsh")
|
|
124
|
+
out["eta"] = ComposedField(lambda f, a, l: f / (a - 2.0 * l), (F, A, L),
|
|
125
|
+
character=SCALAR, name="eta")
|
|
126
|
+
return out
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
class Elastic:
|
|
130
|
+
"""The elastic behaviour of a spherically symmetric model.
|
|
131
|
+
|
|
132
|
+
On construction the elastic description of every layer is completed
|
|
133
|
+
in both directions: the five Love moduli A, C, F, L, N are attached
|
|
134
|
+
where a layer was given rho with velocities or kappa and mu, exact
|
|
135
|
+
where those are polynomial, and the velocities where a layer was
|
|
136
|
+
given rho with the five (`with_moduli`, `with_velocities`); a layer
|
|
137
|
+
already holding both, or its tensor under `elastic_moduli`, is left
|
|
138
|
+
as it is. The methods then read the fields: `moduli(which)` the
|
|
139
|
+
five, `elastic_moduli` the tensor (the layer's own of any symmetry
|
|
140
|
+
where it holds one), `kappa_mu` the Voigt average, `is_fluid` the
|
|
141
|
+
vanishing of shear, and `isotropic()` the model re-described by its
|
|
142
|
+
Voigt average, kappa and mu with the five and the velocities
|
|
143
|
+
recomputed from them. A layer holding a general anisotropic tensor
|
|
144
|
+
is refused by the three that read the five, until the general Voigt
|
|
145
|
+
average is written.
|
|
146
|
+
|
|
147
|
+
The derived fields are kept in step by `with_field`: replacing an
|
|
148
|
+
elastic field on a layer makes the family it belongs to (rho with
|
|
149
|
+
the velocities, kappa and mu, or the five) the description, drops
|
|
150
|
+
the other elastic names from that layer, and completes the
|
|
151
|
+
description again, so the five follow a new `vs` and the velocities
|
|
152
|
+
a new `L`; a field added beside the existing ones is attached as it
|
|
153
|
+
is. A change that goes through `replaced` directly, as the
|
|
154
|
+
library's own transformations do, is the caller's to keep
|
|
155
|
+
consistent.
|
|
156
|
+
"""
|
|
157
|
+
|
|
158
|
+
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
|
159
|
+
super().__init__(*args, **kwargs)
|
|
160
|
+
from .model import Layer
|
|
161
|
+
self._layers = tuple(
|
|
162
|
+
Layer(layer.info, with_velocities(with_moduli(layer.fields)))
|
|
163
|
+
for layer in self._layers)
|
|
164
|
+
|
|
165
|
+
is_fluid = layer_method(materials.is_fluid)
|
|
166
|
+
moduli = layer_method(materials.moduli)
|
|
167
|
+
elastic_moduli = layer_method(materials.elastic_moduli)
|
|
168
|
+
kappa_mu = layer_method(materials.kappa_mu)
|
|
169
|
+
|
|
170
|
+
def with_field(self: Model, which: int | str, name: str, field: Field, *,
|
|
171
|
+
replace: bool = False) -> Model:
|
|
172
|
+
"""A copy with `field` attached to one layer under `name`.
|
|
173
|
+
|
|
174
|
+
Replacing an elastic field makes its family the layer's
|
|
175
|
+
description and derives the other elastic fields from it again;
|
|
176
|
+
a field added beside the existing ones, or a family too
|
|
177
|
+
incomplete to describe the medium, is attached as it is.
|
|
178
|
+
"""
|
|
179
|
+
from .model import Model
|
|
180
|
+
out = Model.with_field(self, which, name, field, replace=replace)
|
|
181
|
+
if not replace or name not in materials.ELASTIC_NAMES:
|
|
182
|
+
return out
|
|
183
|
+
i = self.layer(which).index
|
|
184
|
+
layers = [dict(layer.fields) for layer in out.layers]
|
|
185
|
+
keep = elastic_family(name)
|
|
186
|
+
fields = {k: f for k, f in layers[i].items()
|
|
187
|
+
if k not in materials.ELASTIC_NAMES or k in keep}
|
|
188
|
+
try:
|
|
189
|
+
layers[i] = with_velocities(with_moduli(fields))
|
|
190
|
+
except KeyError:
|
|
191
|
+
return out
|
|
192
|
+
return out.replaced(layers=layers)
|
|
193
|
+
|
|
194
|
+
def isotropic(self: Model) -> Model:
|
|
195
|
+
"""The model with every elastic description replaced by its Voigt
|
|
196
|
+
average: each layer keeps rho and everything that is not an
|
|
197
|
+
elastic name, and holds kappa and mu, and the five recomputed
|
|
198
|
+
from them, in place of whatever it described its medium by.
|
|
199
|
+
Exact on polynomial layers. A layer holding no elastic
|
|
200
|
+
description is left alone.
|
|
201
|
+
"""
|
|
202
|
+
layers = []
|
|
203
|
+
for layer in self.layers:
|
|
204
|
+
fields = {k: f for k, f in layer.fields.items()
|
|
205
|
+
if k not in materials.ELASTIC_NAMES}
|
|
206
|
+
if any(n in layer for n in materials.ELASTIC_NAMES):
|
|
207
|
+
kappa, mu = materials.kappa_mu(layer)
|
|
208
|
+
fields["kappa"], fields["mu"] = kappa, mu
|
|
209
|
+
fields = with_velocities(with_moduli(fields))
|
|
210
|
+
layers.append(fields)
|
|
211
|
+
return self.replaced(layers=layers)
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
class ConstantQ:
|
|
215
|
+
"""The Love moduli at a frequency under the constant-Q absorption
|
|
216
|
+
band: kappa dispersed by `qkappa` and mu by `qmu` about a reference
|
|
217
|
+
frequency, following the logarithmic dispersion relation, so that a
|
|
218
|
+
layer's five at angular frequency omega are complex fields. The
|
|
219
|
+
model is not changed; `Viscoelastic.frozen` is the model that is.
|
|
220
|
+
The reference frequency is the model's constant `omega_ref` where
|
|
221
|
+
it declares one, else 2 pi rad/s, a period of one second, in the
|
|
222
|
+
model's units (`reference_omega`).
|
|
223
|
+
"""
|
|
224
|
+
|
|
225
|
+
reference_omega = rheology.reference_omega
|
|
226
|
+
|
|
227
|
+
def moduli_at(self: Model, which: int | str, omega: float, *,
|
|
228
|
+
reference_omega: float | None = None) -> dict[str, Field]:
|
|
229
|
+
"""The five moduli of a layer at `omega`, complex where the layer
|
|
230
|
+
holds a Q, else its static five."""
|
|
231
|
+
ref = self.reference_omega() if reference_omega is None else reference_omega
|
|
232
|
+
return rheology.dispersive_moduli(self.layer(which), omega,
|
|
233
|
+
reference_omega=ref)
|
|
234
|
+
|
|
235
|
+
def elastic_moduli_at(self: Model, which: int | str, omega: float, *,
|
|
236
|
+
reference_omega: float | None = None
|
|
237
|
+
) -> materials.ElasticField:
|
|
238
|
+
"""The transversely isotropic tensor of a layer at `omega`."""
|
|
239
|
+
five = self.moduli_at(which, omega, reference_omega=reference_omega)
|
|
240
|
+
return materials.ElasticField(materials.Symmetry.VTI, five,
|
|
241
|
+
name="elastic_moduli")
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
class SelfGravitating:
|
|
245
|
+
"""The gravity and mass of a spherically symmetric model with a radial
|
|
246
|
+
density on every layer: the reference body's, computed on each call
|
|
247
|
+
and never stored, as numbers by `gravity` and `mass`, as one radial
|
|
248
|
+
field per layer by `gravity_fields`, and as a copy of the model
|
|
249
|
+
holding that field under the vocabulary name `g` by `with_gravity`.
|
|
250
|
+
A density that depends on direction is refused, and the geometry's
|
|
251
|
+
mapping does not enter (see `mesh1d.gravity`)."""
|
|
252
|
+
|
|
253
|
+
gravity = gravity
|
|
254
|
+
mass = mass
|
|
255
|
+
gravity_fields = gravity_fields
|
|
256
|
+
|
|
257
|
+
def with_gravity(self: Model, *, name: str = "g", replace: bool = False) -> Model:
|
|
258
|
+
"""The model with its gravity attached to every layer as a radial
|
|
259
|
+
field under `name`, exact where the density is polynomial; a
|
|
260
|
+
layer already holding `name` is refused unless `replace`."""
|
|
261
|
+
fields = gravity_fields(self)
|
|
262
|
+
layers = []
|
|
263
|
+
for layer, field in zip(self.layers, fields):
|
|
264
|
+
if name in layer and not replace:
|
|
265
|
+
raise ValueError(
|
|
266
|
+
f"layer {layer.index} ({layer.name!r}) already holds {name!r}; "
|
|
267
|
+
"pass replace=True to replace it")
|
|
268
|
+
layers.append({**layer.fields, name: field})
|
|
269
|
+
return self.replaced(layers=layers)
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
class Viscoelastic:
|
|
273
|
+
"""The rheology read from the fields: a layer's viscoelasticity, the
|
|
274
|
+
model frozen at a frequency, and the elastic model that drops the
|
|
275
|
+
rheology fields."""
|
|
276
|
+
|
|
277
|
+
is_viscoelastic = layer_method(rheology.is_viscoelastic)
|
|
278
|
+
frozen = rheology.frozen
|
|
279
|
+
|
|
280
|
+
def elastic(self: Model) -> Model:
|
|
281
|
+
"""The model without its rheology fields (`RHEOLOGY_NAMES`), so
|
|
282
|
+
that every layer is elastic; a frozen model keeps its complex
|
|
283
|
+
moduli, which are what its rheology became."""
|
|
284
|
+
layers = [{k: f for k, f in layer.fields.items()
|
|
285
|
+
if k not in rheology.RHEOLOGY_NAMES} for layer in self.layers]
|
|
286
|
+
return self.replaced(layers=layers, check=False)
|