erosionfront 0.1.10__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.
Files changed (39) hide show
  1. erosionfront-0.1.10/PKG-INFO +69 -0
  2. erosionfront-0.1.10/README.md +43 -0
  3. erosionfront-0.1.10/pyproject.toml +43 -0
  4. erosionfront-0.1.10/setup.cfg +4 -0
  5. erosionfront-0.1.10/src/erosionfront/__init__.py +5 -0
  6. erosionfront-0.1.10/src/erosionfront/geomorphic/gma.py +473 -0
  7. erosionfront-0.1.10/src/erosionfront/geomorphic/model.py +534 -0
  8. erosionfront-0.1.10/src/erosionfront/geomorphic/substrate.py +221 -0
  9. erosionfront-0.1.10/src/erosionfront/geomorphic/surface.py +348 -0
  10. erosionfront-0.1.10/src/erosionfront/initialize.py +98 -0
  11. erosionfront-0.1.10/src/erosionfront/levelset/base.py +348 -0
  12. erosionfront-0.1.10/src/erosionfront/levelset/data.py +138 -0
  13. erosionfront-0.1.10/src/erosionfront/levelset/domain.py +72 -0
  14. erosionfront-0.1.10/src/erosionfront/levelset/elementary.py +384 -0
  15. erosionfront-0.1.10/src/erosionfront/levelset/export.py +108 -0
  16. erosionfront-0.1.10/src/erosionfront/levelset/gradient.py +67 -0
  17. erosionfront-0.1.10/src/erosionfront/levelset/grid.py +483 -0
  18. erosionfront-0.1.10/src/erosionfront/levelset/shock.py +170 -0
  19. erosionfront-0.1.10/src/erosionfront/levelset/solver.py +323 -0
  20. erosionfront-0.1.10/src/erosionfront/levelset/speed.py +127 -0
  21. erosionfront-0.1.10/src/erosionfront/misc/save.py +162 -0
  22. erosionfront-0.1.10/src/erosionfront/misc/utils.py +302 -0
  23. erosionfront-0.1.10/src/erosionfront/theory/arrays.py +100 -0
  24. erosionfront-0.1.10/src/erosionfront/theory/equations.py +210 -0
  25. erosionfront-0.1.10/src/erosionfront/theory/numerical.py +289 -0
  26. erosionfront-0.1.10/src/erosionfront/theory/symbols.py +41 -0
  27. erosionfront-0.1.10/src/erosionfront/topo/analysis.py +333 -0
  28. erosionfront-0.1.10/src/erosionfront/visualization/base.py +211 -0
  29. erosionfront-0.1.10/src/erosionfront/visualization/gmaviz.py +1103 -0
  30. erosionfront-0.1.10/src/erosionfront/visualization/simviz.py +629 -0
  31. erosionfront-0.1.10/src/erosionfront/visualization/speedviz.py +197 -0
  32. erosionfront-0.1.10/src/erosionfront/visualization/theoryviz.py +662 -0
  33. erosionfront-0.1.10/src/erosionfront/visualization/topoviz.py +295 -0
  34. erosionfront-0.1.10/src/erosionfront/visualization/viz2d.py +158 -0
  35. erosionfront-0.1.10/src/erosionfront.egg-info/PKG-INFO +69 -0
  36. erosionfront-0.1.10/src/erosionfront.egg-info/SOURCES.txt +37 -0
  37. erosionfront-0.1.10/src/erosionfront.egg-info/dependency_links.txt +1 -0
  38. erosionfront-0.1.10/src/erosionfront.egg-info/requires.txt +7 -0
  39. erosionfront-0.1.10/src/erosionfront.egg-info/top_level.txt +1 -0
@@ -0,0 +1,69 @@
1
+ Metadata-Version: 2.4
2
+ Name: erosionfront
3
+ Version: 0.1.10
4
+ Summary: Tools for simulating rock slope erosion and the emergent geometry of Richter slopes
5
+ Author-email: "Colin P. Stark" <cstarkjp@gmail.com>
6
+ Keywords: geomorphology,erosion,rockslope,mesa,butte,cliff retreat,Richter slope,non-convex,geomorphic Hamiltonian,Hamilton-Jacobi equation,level-set,Lax-Friedrichs,Finsler geometry
7
+ Classifier: Development Status :: 3 - Alpha
8
+ Classifier: Framework :: Jupyter
9
+ Classifier: Framework :: MkDocs
10
+ Classifier: Intended Audience :: Science/Research
11
+ Classifier: Programming Language :: Python :: Implementation :: CPython
12
+ Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3)
13
+ Classifier: Operating System :: MacOS
14
+ Classifier: Operating System :: POSIX :: Linux
15
+ Classifier: Operating System :: Microsoft :: Windows
16
+ Classifier: Topic :: Scientific/Engineering :: Physics
17
+ Requires-Python: >=3.14
18
+ Description-Content-Type: text/markdown
19
+ Requires-Dist: numpy
20
+ Requires-Dist: matplotlib
21
+ Requires-Dist: scipy
22
+ Requires-Dist: shapely
23
+ Requires-Dist: rasterio
24
+ Requires-Dist: ruptures
25
+ Requires-Dist: h5py
26
+
27
+ # ErosionFront
28
+
29
+ <div align="center">
30
+ <h3> Geomorphic Hamiltonian theory of rock slope erosion and the emergent geometry of Richter slopes
31
+ </h3>
32
+ </div>
33
+
34
+ <div align="center">
35
+
36
+ ![3d model of West Mitten Butte](
37
+ https://raw.githubusercontent.com/cstarkjp/ErosionFront/main/images/WestMittenButte/BlenderView1_reduced.png?raw=true
38
+ )
39
+
40
+ </div>
41
+
42
+ An iconic image of the American West is the mesa: a steep cliff, rising above a ramp-like rock slope, capped by a flat bench. This famous landform has long been assumed to develop where strong rock overlies weak, and where rockfall debris suppresses ramp erosion. Such an explanation cannot be true in general, however, because the archetypal geometry can arise even in uniform bedrock with no talus armouring. Here we argue instead that it is an emergent property. Theoretical evidence comes a simple model of scarp retreat whose combined rates of weathering and surface-normal erosion are written as a slowly varying function of gradient. Model analysis and simulation, using geometric mechanics and level sets, reveal the ramp-cliff transition to form automatically as a shock solution of a non-convex Hamilton-Jacobi equation (HJE). Erodibility contrasts are not needed to explain this behaviour, but when present they help lock the landform into its classic shape and allow it to persist long-term. These conclusions are vindicated by 3D topographic analysis of differential cliff recession in geologically homogeneous material.
43
+
44
+
45
+
46
+ ### Level-set solution of a geomorphic HJE
47
+
48
+ The purpose of the Python code presented here is to derive, analyze, and numerically solve a geomorphic Hamiltonian[^1] model of rock slope erosion and retreat[^2]. The code is provided as a
49
+ [Python library package](src/erosionfront)
50
+ and associated Jupyter notebooks (e.g., [here](notebooks/simulation/ErosionFront.ipynb) and [here](notebooks/analysis/3DProfiling.ipynb)).
51
+
52
+ <div align="center">
53
+
54
+ ![Animated set of HJE solutions of ramp-cliff retreat for varying ratio of upper/lower rock layer erodibility](
55
+ https://raw.githubusercontent.com/cstarkjp/ErosionFront/main/notebooks/simulation/combo/time_slices_test_2layer_ηul0p2.png?raw=true
56
+ )
57
+ </div>
58
+
59
+ Numerical solution of the model Hamilton-Jacobi equation is achieved with a level-set scheme[^3] that employs Lax-Friedrichs finite differencing to obtain stable viscosity solutions for a non-convex Hamiltonian. The level-set code is custom implemented in Python.
60
+
61
+ Model analysis is performed using some tools from geometric mechanics[^4]: having converted the rock-slope erosion model into geomorphic Hamiltonian $\mathcal{H}(\mathbf{r}, \mathbf{p})$ form, this Hamiltonian is then used to derive Hamilton's ray tracing equations $(\partial_{\mathbf{p}}\mathcal{H}, -\partial_{\mathbf{r}}\mathcal{H})$ and the co-metric tensor $g^{ij} = \partial_{ij}\mathcal{H}$; these properties are then probed to understand model stability, notably to place bounds on the non-convexity of $\mathcal{H}$ and to identify critical angles.
62
+
63
+ [^1]: [Stark, C.P., & Stark, G.J., 2022. The direction of landscape erosion. Earth Surface Dynamics, 10: 383-419.](https://doi.org/10.5194/esurf-10-383-2022)
64
+
65
+ [^2]: [Howard, A.D., & Selby, M.J., 2009. Rock Slopes. In: Parsons, A.J., Abrahams, A.D. (eds). Geomorphology of Desert Environments. Springer, Dordrecht. ](https://doi.org/10.1007/978-1-4020-5719-9_8)
66
+
67
+ [^3]: [Osher, S., & Fedkiw, R., 2003. Level Set Methods and Dynamic Implicit Surfaces. Springer-Verlag New York, Inc.](https://link.springer.com/book/10.1007/b98879) See page 50.
68
+
69
+ [^4]: [Holm, D.D., 2011. Geometric Mechanics. Part I: Dynamics and Symmetry (2nd Edition)](https://www.ma.imperial.ac.uk/~dholm/classnotes/HolmPart1-GM.pdf)
@@ -0,0 +1,43 @@
1
+ # ErosionFront
2
+
3
+ <div align="center">
4
+ <h3> Geomorphic Hamiltonian theory of rock slope erosion and the emergent geometry of Richter slopes
5
+ </h3>
6
+ </div>
7
+
8
+ <div align="center">
9
+
10
+ ![3d model of West Mitten Butte](
11
+ https://raw.githubusercontent.com/cstarkjp/ErosionFront/main/images/WestMittenButte/BlenderView1_reduced.png?raw=true
12
+ )
13
+
14
+ </div>
15
+
16
+ An iconic image of the American West is the mesa: a steep cliff, rising above a ramp-like rock slope, capped by a flat bench. This famous landform has long been assumed to develop where strong rock overlies weak, and where rockfall debris suppresses ramp erosion. Such an explanation cannot be true in general, however, because the archetypal geometry can arise even in uniform bedrock with no talus armouring. Here we argue instead that it is an emergent property. Theoretical evidence comes a simple model of scarp retreat whose combined rates of weathering and surface-normal erosion are written as a slowly varying function of gradient. Model analysis and simulation, using geometric mechanics and level sets, reveal the ramp-cliff transition to form automatically as a shock solution of a non-convex Hamilton-Jacobi equation (HJE). Erodibility contrasts are not needed to explain this behaviour, but when present they help lock the landform into its classic shape and allow it to persist long-term. These conclusions are vindicated by 3D topographic analysis of differential cliff recession in geologically homogeneous material.
17
+
18
+
19
+
20
+ ### Level-set solution of a geomorphic HJE
21
+
22
+ The purpose of the Python code presented here is to derive, analyze, and numerically solve a geomorphic Hamiltonian[^1] model of rock slope erosion and retreat[^2]. The code is provided as a
23
+ [Python library package](src/erosionfront)
24
+ and associated Jupyter notebooks (e.g., [here](notebooks/simulation/ErosionFront.ipynb) and [here](notebooks/analysis/3DProfiling.ipynb)).
25
+
26
+ <div align="center">
27
+
28
+ ![Animated set of HJE solutions of ramp-cliff retreat for varying ratio of upper/lower rock layer erodibility](
29
+ https://raw.githubusercontent.com/cstarkjp/ErosionFront/main/notebooks/simulation/combo/time_slices_test_2layer_ηul0p2.png?raw=true
30
+ )
31
+ </div>
32
+
33
+ Numerical solution of the model Hamilton-Jacobi equation is achieved with a level-set scheme[^3] that employs Lax-Friedrichs finite differencing to obtain stable viscosity solutions for a non-convex Hamiltonian. The level-set code is custom implemented in Python.
34
+
35
+ Model analysis is performed using some tools from geometric mechanics[^4]: having converted the rock-slope erosion model into geomorphic Hamiltonian $\mathcal{H}(\mathbf{r}, \mathbf{p})$ form, this Hamiltonian is then used to derive Hamilton's ray tracing equations $(\partial_{\mathbf{p}}\mathcal{H}, -\partial_{\mathbf{r}}\mathcal{H})$ and the co-metric tensor $g^{ij} = \partial_{ij}\mathcal{H}$; these properties are then probed to understand model stability, notably to place bounds on the non-convexity of $\mathcal{H}$ and to identify critical angles.
36
+
37
+ [^1]: [Stark, C.P., & Stark, G.J., 2022. The direction of landscape erosion. Earth Surface Dynamics, 10: 383-419.](https://doi.org/10.5194/esurf-10-383-2022)
38
+
39
+ [^2]: [Howard, A.D., & Selby, M.J., 2009. Rock Slopes. In: Parsons, A.J., Abrahams, A.D. (eds). Geomorphology of Desert Environments. Springer, Dordrecht. ](https://doi.org/10.1007/978-1-4020-5719-9_8)
40
+
41
+ [^3]: [Osher, S., & Fedkiw, R., 2003. Level Set Methods and Dynamic Implicit Surfaces. Springer-Verlag New York, Inc.](https://link.springer.com/book/10.1007/b98879) See page 50.
42
+
43
+ [^4]: [Holm, D.D., 2011. Geometric Mechanics. Part I: Dynamics and Symmetry (2nd Edition)](https://www.ma.imperial.ac.uk/~dholm/classnotes/HolmPart1-GM.pdf)
@@ -0,0 +1,43 @@
1
+ [project]
2
+ name = "erosionfront"
3
+ version = "0.1.10"
4
+ description = "Tools for simulating rock slope erosion and the emergent geometry of Richter slopes"
5
+ readme = "README.md"
6
+ classifiers = [
7
+ "Development Status :: 3 - Alpha",
8
+ "Framework :: Jupyter",
9
+ "Framework :: MkDocs",
10
+ "Intended Audience :: Science/Research",
11
+ "Programming Language :: Python :: Implementation :: CPython",
12
+ "License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
13
+ "Operating System :: MacOS",
14
+ "Operating System :: POSIX :: Linux",
15
+ "Operating System :: Microsoft :: Windows",
16
+ "Topic :: Scientific/Engineering :: Physics",
17
+ ]
18
+ requires-python = ">=3.14"
19
+ dependencies = [
20
+ "numpy", "matplotlib", "scipy", "shapely", "rasterio", "ruptures", "h5py"
21
+ ]
22
+ authors = [{ name = 'Colin P. Stark', email = 'cstarkjp@gmail.com' }]
23
+ keywords = [
24
+ "geomorphology",
25
+ "erosion",
26
+ "rockslope",
27
+ "mesa",
28
+ "butte",
29
+ "cliff retreat",
30
+ "Richter slope",
31
+ "non-convex",
32
+ "geomorphic Hamiltonian",
33
+ "Hamilton-Jacobi equation",
34
+ "level-set",
35
+ "Lax-Friedrichs",
36
+ "Finsler geometry"
37
+ ]
38
+
39
+ [[tool.uv.index]]
40
+ name = "testpypi"
41
+ url = "https://test.pypi.org/simple/"
42
+ publish-url = "https://test.pypi.org/legacy/"
43
+ explicit = true
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,5 @@
1
+ """
2
+ Initialize ErosionFront package.
3
+ """
4
+
5
+ from erosionfront import initialize
@@ -0,0 +1,473 @@
1
+ """
2
+ Geometric mechanics analysis of geomorphic Hamiltonian.
3
+ """
4
+ import warnings
5
+ from dataclasses import dataclass
6
+ from typing import Any
7
+ from collections.abc import Callable
8
+ from functools import partial
9
+
10
+ import numpy as np
11
+ from numpy.typing import NDArray
12
+ import scipy.interpolate
13
+ import scipy.optimize
14
+
15
+ from erosionfront.geomorphic.model import (
16
+ Isotropic,
17
+ Step,
18
+ ExponentialActivation,
19
+ )
20
+
21
+ warnings.filterwarnings("ignore")
22
+
23
+ __all__ = ["GeometricMechanicsAnalysis"]
24
+
25
+ @dataclass
26
+ class GeometricMechanicsAnalysis:
27
+ """
28
+ Analysis of geomorphic Hamiltonian using geometric mechanics.
29
+
30
+ Parameters
31
+ ----------
32
+ model: Isotropic | Step| ExponentialActivation
33
+ Surface-normal erosion function model.
34
+
35
+ Attributes
36
+ ----------
37
+ β_mpx: float | None = None
38
+ Surface slope angle at maximum px.
39
+
40
+ α_mpx: float | None = None
41
+ Ray angle at maximum px.
42
+
43
+ β_c0: float | None = None
44
+ Surface slope at first critical ray angle.
45
+
46
+ β_c1: float | None = None
47
+ Surface slope at second critical ray angle.
48
+
49
+ α_c0: float | None = None
50
+ First critical ray angle.
51
+
52
+ α_c1: float | None = None
53
+ Second critical ray angle.
54
+
55
+ β_rs0: float | None = None
56
+ Lower ramp-slope angle.
57
+
58
+ β_rs1: float | None = None
59
+ Upper ramp-slope angle.
60
+
61
+ α_rs0: float | None = None
62
+ Angle of ray for lower ramp.
63
+
64
+ α_rs1: float | None = None
65
+ Angle of ray for upper ramp.
66
+
67
+ """
68
+ β_mpx: float | None = None
69
+ α_mpx: float | None = None
70
+ β_c0: float | None = None
71
+ β_c1: float | None = None
72
+ α_c0: float | None = None
73
+ α_c1: float | None = None
74
+ β_rs0: float | None = None
75
+ β_rs1: float | None = None
76
+ α_rs0: float | None = None
77
+ α_rs1: float | None = None
78
+
79
+ def __init__(
80
+ self,
81
+ model: Isotropic | Step| ExponentialActivation,
82
+ ) -> None:
83
+ """
84
+ Instantiate `GeometricMechanicsAnalysis` class.
85
+
86
+ Performs a suite of analyses, generating a lot of new
87
+ attributes containing the output.
88
+ """
89
+ self.model = model
90
+ self.build_H_interpolation()
91
+ self.compute_dHdp()
92
+ self.compute_H_det_Hessian()
93
+ self.build_gstar_interpolation()
94
+ self.construct_detgstar_curve()
95
+ self.build_dHdp_interpolation()
96
+ self.construct_α_curve()
97
+ self.find_detgstar_zeros()
98
+
99
+ def px_onshell(self, β: float | NDArray,) -> float | NDArray:
100
+ """
101
+ Use speed function to compute slowness component px from β.
102
+
103
+ The suffix 'on-shell' emphasizes that this computation only works
104
+ for px/pz/β values where H(px,pz)=1/2.
105
+
106
+ Parameters
107
+ ----------
108
+ β: float | NDArray
109
+ Surface slope angle(s).
110
+
111
+ Attributes
112
+ ----------
113
+ Uses ξ_fn_β function attribute.
114
+
115
+ Returns
116
+ -------
117
+ float | NDArray:
118
+ Value(s) of slowness component px.
119
+ """
120
+ return np.sin(β)/self.model.ξ_fn_β(β)
121
+
122
+ def pz_onshell(self, β: float | NDArray,) -> float | NDArray:
123
+ """
124
+ Use speed function to compute slowness component pz from β.
125
+
126
+ The suffix 'on-shell' emphasizes that this computation only works
127
+ for px/pz/β values where H(px,pz)=1/2.
128
+
129
+ Parameters
130
+ ----------
131
+ β: float | NDArray
132
+ Surface slope angle(s).
133
+
134
+ Attributes
135
+ ----------
136
+ Uses ξ_fn_β function attribute.
137
+
138
+ Returns
139
+ -------
140
+ float | NDArray:
141
+ Value(s) of slowness component pz.
142
+ """
143
+ return -np.cos(β)/self.model.ξ_fn_β(β)
144
+
145
+ def p_dot_v(self) -> NDArray:
146
+ """
147
+ Compute inner product p(v).
148
+
149
+ Attributes
150
+ ----------
151
+ dHdpx_onshell: NDArray
152
+ px component of H gradient = vx
153
+
154
+ dHdpz_onshell: NDArray
155
+ pz component of H gradient = vz
156
+
157
+ px: NDArray
158
+ x components of p covector array
159
+
160
+ pz: NDArray
161
+ z components of p covector array
162
+
163
+ Returns
164
+ -------
165
+ NDArray:
166
+ Inner product p(v), which should all be ones.
167
+ """
168
+ return self.dHdpx_onshell*self.px + self.dHdpz_onshell*self.pz
169
+
170
+ def Hamiltonian(
171
+ self,
172
+ px: float | NDArray,
173
+ pz: float | NDArray
174
+ ) -> float | NDArray:
175
+ """
176
+ Compute Hamiltonian values H(px,pz) anywhere (not just for H=1/2, obvs).
177
+
178
+ Parameters
179
+ ----------
180
+ px: float | NDArray
181
+ x component(s) of p covector
182
+
183
+ pz: float | NDArray
184
+ z component(s) of p covector
185
+
186
+ Attributes
187
+ ----------
188
+ Uses ξ_fn_β function attribute.
189
+
190
+ Returns
191
+ -------
192
+ float | NDArray:
193
+ Value(s) of H 'off-shell' or on.
194
+ """
195
+ return 0.5*(self.model.ξ_fn_β(np.arctan2(px,-pz))**2)*(px**2+pz**2)
196
+
197
+ def build_H_interpolation(self) -> None:
198
+ """
199
+ Generate a 2D spline interpolant for off-shell Hamiltonian grid.
200
+
201
+ Attributes
202
+ ----------
203
+ Uses px_onshell, pz_onshell, Hamiltonian.
204
+ Modifies many.
205
+ """
206
+ n_H_grid_points: int = 3001
207
+ self.β: NDArray = np.linspace(0, np.pi, n_H_grid_points)
208
+ self.pz: NDArray = np.array(self.pz_onshell(self.β))
209
+ self.px: NDArray = np.array(self.px_onshell(self.β))
210
+ self.px_span: tuple[float,float] = (
211
+ np.min(self.px),
212
+ np.max(self.px)*1.05,
213
+ )
214
+ self.pz_span: tuple[float,float] = (
215
+ np.min(self.pz)*1.03,
216
+ np.max(self.pz)*1.05,
217
+ )
218
+ self.n_grid_px_pts: int = n_H_grid_points
219
+ self.n_grid_pz_pts: int = n_H_grid_points
220
+ self.pxz_points: tuple[NDArray, NDArray] = (
221
+ np.linspace(*self.px_span, self.n_grid_px_pts),
222
+ np.linspace(*self.pz_span, self.n_grid_pz_pts),
223
+ )
224
+ self.H_grid: NDArray = np.array(
225
+ self.Hamiltonian(*np.meshgrid(*self.pxz_points, indexing="ij"))
226
+ )
227
+ self.H_interpfn: Callable \
228
+ = scipy.interpolate.RegularGridInterpolator(
229
+ self.pxz_points, self.H_grid
230
+ )
231
+ self.n_interp_grid_px_pts: int = self.n_grid_px_pts
232
+ self.n_interp_grid_pz_pts: int = self.n_grid_pz_pts
233
+ px_pts: NDArray = np.linspace(*self.px_span, self.n_interp_grid_px_pts)
234
+ pz_pts: NDArray = np.linspace(*self.pz_span, self.n_interp_grid_pz_pts)
235
+ self.Δpx: float = px_pts[1]-px_pts[0]
236
+ self.Δpz: float = pz_pts[1]-pz_pts[0]
237
+ px_mg: NDArray
238
+ pz_mg: NDArray
239
+ (px_mg, pz_mg,) = np.meshgrid(px_pts, pz_pts, indexing="ij",)
240
+ self.px_pz_interp_grid_pts: NDArray \
241
+ = np.array([px_mg.ravel(), pz_mg.ravel()]).T
242
+ self.H_interp_grid: NDArray = self.H_interpfn(
243
+ self.px_pz_interp_grid_pts,
244
+ method="linear").reshape(
245
+ self.n_interp_grid_px_pts,
246
+ self.n_interp_grid_pz_pts,
247
+ )
248
+
249
+ def compute_dHdp(self) -> None:
250
+ """
251
+ Compute gradient of H.
252
+
253
+ Attributes
254
+ ----------
255
+ Uses H_grid, Δpx, Δpz.
256
+ Modifies dHdpx, dHdpz.
257
+ """
258
+ Δxz: tuple[float,float] = (self.Δpx, self.Δpz,)
259
+ self.dHdpx: NDArray
260
+ self.dHdpz: NDArray
261
+ (self.dHdpx, self.dHdpz,) = np.gradient(self.H_grid, *Δxz,)
262
+
263
+ def compute_H_det_Hessian(self) -> None:
264
+ """
265
+ Compute determinant of Hessian of H.
266
+
267
+ Attributes
268
+ ----------
269
+ Uses dHdpx, dHdpx.
270
+ Modifies det_gstar_grid.
271
+ """
272
+ Δxz: tuple[float,float] = (self.Δpx, self.Δpz,)
273
+ d2Hdpx2: NDArray
274
+ d2Hdpzdpx: NDArray
275
+ d2Hdpxdpz: NDArray
276
+ d2Hdpz2: NDArray
277
+ (d2Hdpx2, d2Hdpzdpx) = np.gradient(self.dHdpx, *Δxz,)
278
+ (d2Hdpxdpz, d2Hdpz2) = np.gradient(self.dHdpz, *Δxz,)
279
+ self.det_gstar_grid: NDArray = d2Hdpx2*d2Hdpz2 - d2Hdpxdpz*d2Hdpzdpx
280
+
281
+ def build_gstar_interpolation(self) -> None:
282
+ """
283
+ Generate an interpolating function for the det Hessian of H.
284
+
285
+ Attributes
286
+ ----------
287
+ Uses pxz_points, px_pz_interp_grid_pts, n_grid_px_pts, n_grid_pz_pts.
288
+ Modifies det_gstar_interpfn, det_gstar_interp_grid.
289
+ """
290
+ self.det_gstar_interpfn: Callable \
291
+ = scipy.interpolate.RegularGridInterpolator(
292
+ self.pxz_points,
293
+ self.det_gstar_grid
294
+ )
295
+ self.det_gstar_interp_grid: NDArray \
296
+ = self.det_gstar_interpfn(
297
+ self.px_pz_interp_grid_pts,
298
+ method="linear"
299
+ ).reshape(self.n_grid_px_pts, self.n_grid_pz_pts,)
300
+
301
+ def construct_detgstar_curve(self) -> None:
302
+ """
303
+ Generate a spline-interpolation fn for |g*(β)| for on-shell px, pz.
304
+
305
+ Attributes
306
+ ----------
307
+ Uses px, pz, β, det_gstar_interpfn, modifies det_gstar_onshell,
308
+ det_gstar_onshell_interpfn.
309
+ """
310
+ px_pz_onshell_pts: NDArray = np.array([self.px, self.pz]).T
311
+ self.det_gstar_onshell: NDArray \
312
+ = self.det_gstar_interpfn(px_pz_onshell_pts)
313
+ self.det_gstar_onshell_interpfn: Callable \
314
+ = scipy.interpolate.CubicSpline(
315
+ self.β,
316
+ self.det_gstar_onshell,
317
+ bc_type="clamped",
318
+ )
319
+
320
+ def build_dHdp_interpolation(self) -> None:
321
+ """
322
+ Build interpolating functions and grids for dHdpx, dHdpz.
323
+
324
+ Attributes
325
+ ----------
326
+ Uses pxz_points, pxz_points, dHdpx, dHdpz, px_pz_interp_grid_pts,
327
+ n_grid_px_pts, n_grid_pz_pts.
328
+
329
+ Modifies dHdpx_interpfn, dHdpz_interpfn, dHdpx_interp_grid,
330
+ dHdpx_interp_grid.
331
+ """
332
+ self.dHdpx_interpfn: Callable \
333
+ = scipy.interpolate.RegularGridInterpolator(
334
+ self.pxz_points,
335
+ self.dHdpx,
336
+ )
337
+ self.dHdpz_interpfn: Callable \
338
+ = scipy.interpolate.RegularGridInterpolator(
339
+ self.pxz_points,
340
+ self.dHdpz,
341
+ )
342
+ self.dHdpx_interp_grid: NDArray \
343
+ = self.dHdpx_interpfn(
344
+ self.px_pz_interp_grid_pts,
345
+ method="linear",
346
+ ).reshape(self.n_grid_px_pts, self.n_grid_pz_pts,)
347
+ self.dHdpz_interp_grid: NDArray \
348
+ = self.dHdpz_interpfn(
349
+ self.px_pz_interp_grid_pts,
350
+ method="linear",
351
+ ).reshape(self.n_grid_px_pts, self.n_grid_pz_pts,)
352
+
353
+ def construct_α_curve(self) -> None:
354
+ """
355
+ Generate several grids and spline interpolation fns.
356
+
357
+ Make grids for onshell dHdpx, dHdpz.
358
+ Build spline interpolation fns for on-shell dHdpx, dHdpz. ray angle α.
359
+
360
+ Attributes
361
+ ----------
362
+ Uses px, pz, β, dHdpx_interpfn, dHdpz_interpfn.
363
+
364
+ Modifies dHdpx_onshell, dHdpz_onshell, dHdpx_onshell_interpfn,
365
+ dHdpz_onshell_interpfn, α_onshell_interpfn.
366
+ """
367
+ px_pz_onshell_pts: NDArray = np.array([self.px, self.pz]).T
368
+ self.dHdpx_onshell: NDArray = self.dHdpx_interpfn(px_pz_onshell_pts)
369
+ self.dHdpz_onshell: NDArray = self.dHdpz_interpfn(px_pz_onshell_pts)
370
+ spline: Callable = partial(
371
+ scipy.interpolate.CubicSpline, x=self.β, bc_type="clamped",
372
+ )
373
+ self.dHdpx_onshell_interpfn: Callable = spline(y=self.dHdpx_onshell)
374
+ self.dHdpz_onshell_interpfn: Callable = spline(y=self.dHdpz_onshell)
375
+ self.α_onshell_interpfn: Callable = spline(
376
+ y=np.arctan2(self.dHdpz_onshell, self.dHdpx_onshell)
377
+ )
378
+
379
+ def find_detgstar_zeros(self) -> None:
380
+ """
381
+ Find critical angles.
382
+
383
+ Attributes
384
+ ----------
385
+ Uses several, modifies several.
386
+ """
387
+ # Find approx β where px is a maximum using brute force sampling
388
+ beta_samples: NDArray = np.linspace(1e-6, np.pi, 1000)
389
+ px_samples: NDArray = np.array(self.px_onshell(beta_samples))
390
+ # i_sample_max_px: int = np.where(px_samples>=np.max(px_samples))[0]
391
+ beta_sample_max: float = beta_samples[
392
+ px_samples>=np.max(px_samples)
393
+ ][0]
394
+ approx_zero: float = 1e-4
395
+ dgstar_max: float = np.max(self.det_gstar_onshell)
396
+ neg: NDArray = np.s_[
397
+ self.det_gstar_onshell/dgstar_max < (-approx_zero)
398
+ ]
399
+ critical_β_guesses: tuple[float,float] \
400
+ = (
401
+ (float(self.β[neg][0]), float(self.β[neg][-1]),)
402
+ if self.β[neg].shape[0]>0
403
+ else (np.pi, np.pi,)
404
+ )
405
+ print(f"critical_β_guesses: {critical_β_guesses}")
406
+ self.β_mpx = float(beta_sample_max)
407
+ self.α_mpx = float(self.α_onshell_interpfn(self.β_mpx))
408
+ print(f"β_mpx = {np.rad2deg(self.β_mpx):3.2f}")
409
+ print(f"α_mpx = {np.rad2deg(self.α_mpx):3.2f}")
410
+
411
+ # Make a wrapper for root finder
412
+ find_detgstar_root: Callable = partial(
413
+ scipy.optimize.root_scalar,
414
+ self.det_gstar_onshell_interpfn,
415
+ method="newton",
416
+ bracket=(np.pi/3000,np.pi/2,),
417
+ )
418
+ detgstar_roots: tuple[Any,Any] = (
419
+ find_detgstar_root(
420
+ x0=(
421
+ critical_β_guesses[0] if np.abs(critical_β_guesses[0])>1e-3
422
+ else beta_sample_max*1.4
423
+ # else np.pi/5
424
+ ),
425
+ x1=beta_sample_max*2,
426
+ ),
427
+ find_detgstar_root(
428
+ x0=critical_β_guesses[1]
429
+ ),
430
+ )
431
+
432
+ # Find β_c0, β_c1
433
+ if not detgstar_roots[0].converged:
434
+ print("Failed to find β_c0")
435
+ self.β_c0 = 0
436
+ if not detgstar_roots[1].converged:
437
+ print("Failed to find β_c1")
438
+ self.β_c1 = 0
439
+ if detgstar_roots[0].converged or detgstar_roots[1].converged:
440
+ det_gstar_zeros: list[float] = [
441
+ soln_.root if soln_.root>0 else np.pi+soln_.root
442
+ for soln_ in detgstar_roots
443
+ ]
444
+ self.β_c0 = float(det_gstar_zeros[0])
445
+ self.β_c1 = float(det_gstar_zeros[1])
446
+
447
+ # self.β_crit_guesses = critical_β_guesses
448
+ self.α_c0 = float(self.α_onshell_interpfn(self.β_c0))
449
+ self.α_c1 = float(self.α_onshell_interpfn(self.β_c1))
450
+
451
+ α_offset_fn_ = lambda β: self.α_onshell_interpfn(β) - self.α_c1
452
+ soln_ = scipy.optimize.root_scalar(
453
+ α_offset_fn_,
454
+ # x0 = self.β_c0*0.1,
455
+ method="bisect",
456
+ bracket=(0, np.pi/2.01,) #self.β_c0),
457
+ )
458
+ if not soln_.converged:
459
+ raise ValueError(r"Failed to find β_rs1")
460
+ self.β_rs1 = float(soln_.root)
461
+ self.α_rs1 = float(self.α_onshell_interpfn(self.β_rs1))
462
+
463
+ α_offset_fn_ = lambda β: self.α_onshell_interpfn(β) - self.α_c0
464
+ soln_ = scipy.optimize.root_scalar(
465
+ α_offset_fn_,
466
+ x0 = np.pi/4 if self.β_c1 is None else self.β_c1*1.1,
467
+ # method="bisect",
468
+ # bracket=(self.β_c1, np.pi/2),
469
+ )
470
+ if not soln_.converged:
471
+ raise ValueError(r"Failed to find β_rs0")
472
+ self.β_rs0 = float(soln_.root)
473
+ self.α_rs0 = float(self.α_onshell_interpfn(self.β_rs0))