gtrack 0.3.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.
gtrack-0.3.0/PKG-INFO ADDED
@@ -0,0 +1,66 @@
1
+ Metadata-Version: 2.4
2
+ Name: gtrack
3
+ Version: 0.3.0
4
+ Summary: GPlates-based Tracking of Lithosphere and Kinematics
5
+ Author: S. Ghelichkhani
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/sghelichkhani/gtrack
8
+ Project-URL: Documentation, https://gtrack.gadopt.org
9
+ Project-URL: Repository, https://github.com/sghelichkhani/gtrack
10
+ Keywords: plate tectonics,seafloor age,geodynamics,pygplates,lithosphere,kinematics
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Science/Research
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.9
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Topic :: Scientific/Engineering
20
+ Requires-Python: >=3.9
21
+ Description-Content-Type: text/markdown
22
+ Requires-Dist: numpy>=1.20
23
+ Requires-Dist: scipy>=1.7
24
+ Requires-Dist: pygplates
25
+ Provides-Extra: visualization
26
+ Requires-Dist: matplotlib>=3.5; extra == "visualization"
27
+ Requires-Dist: cartopy>=0.21; extra == "visualization"
28
+ Provides-Extra: dev
29
+ Requires-Dist: pytest>=7.0; extra == "dev"
30
+ Requires-Dist: pytest-cov>=3.0; extra == "dev"
31
+ Provides-Extra: docs
32
+ Requires-Dist: mkdocs>=1.5; extra == "docs"
33
+ Requires-Dist: mkdocs-material>=9.0; extra == "docs"
34
+ Requires-Dist: mkdocstrings[python]>=0.24; extra == "docs"
35
+ Requires-Dist: mkdocs-jupyter>=0.24; extra == "docs"
36
+ Provides-Extra: demos
37
+ Requires-Dist: jupytext>=1.15; extra == "demos"
38
+ Requires-Dist: nbconvert>=7.0; extra == "demos"
39
+ Requires-Dist: ipykernel>=6.0; extra == "demos"
40
+ Requires-Dist: matplotlib>=3.5; extra == "demos"
41
+ Requires-Dist: cartopy>=0.21; extra == "demos"
42
+ Requires-Dist: h5py>=3.0; extra == "demos"
43
+ Provides-Extra: all
44
+ Requires-Dist: gtrack[demos,dev,docs,visualization]; extra == "all"
45
+
46
+ # gtrack
47
+
48
+ **GPlates-based Tracking of Lithosphere and Kinematics**
49
+
50
+ A Python package for computing lithospheric structure through geological time using plate tectonic reconstructions.
51
+
52
+ ## Installation
53
+
54
+ ```bash
55
+ pip install gtrack
56
+ ```
57
+
58
+ ## Documentation
59
+
60
+ For full documentation, examples, and API reference, visit:
61
+
62
+ **[https://gtrack.gadopt.org](https://gtrack.gadopt.org)**
63
+
64
+ ## License
65
+
66
+ MIT License
gtrack-0.3.0/README.md ADDED
@@ -0,0 +1,21 @@
1
+ # gtrack
2
+
3
+ **GPlates-based Tracking of Lithosphere and Kinematics**
4
+
5
+ A Python package for computing lithospheric structure through geological time using plate tectonic reconstructions.
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ pip install gtrack
11
+ ```
12
+
13
+ ## Documentation
14
+
15
+ For full documentation, examples, and API reference, visit:
16
+
17
+ **[https://gtrack.gadopt.org](https://gtrack.gadopt.org)**
18
+
19
+ ## License
20
+
21
+ MIT License
@@ -0,0 +1,137 @@
1
+ """
2
+ gtrack: High-performance seafloor age tracking and point rotation.
3
+
4
+ This package provides tools for:
5
+ - Computing seafloor ages from plate tectonic reconstructions using Lagrangian
6
+ particle tracking with pygplates' C++ backend (GPlately-compatible)
7
+ - Rotating user-provided points through geological time
8
+ - Filtering points by polygon containment (e.g., continental regions)
9
+
10
+ The main entry point is SeafloorAgeTracker, which provides both a stepwise
11
+ interface and a one-shot compute_ages() method.
12
+
13
+ Logging
14
+ -------
15
+ gtrack uses Python's logging module. Control verbosity via environment variable:
16
+
17
+ export GTRACK_LOGLEVEL=INFO # Progress messages
18
+ export GTRACK_LOGLEVEL=DEBUG # Detailed debug output
19
+ export GTRACK_LOGLEVEL=WARNING # Quiet (default)
20
+
21
+ Or programmatically:
22
+
23
+ >>> from gtrack import enable_verbose, enable_debug
24
+ >>> enable_verbose() # Show progress messages
25
+
26
+ Example
27
+ -------
28
+ >>> from gtrack import SeafloorAgeTracker, TracerConfig
29
+ >>>
30
+ >>> # Simple one-shot computation
31
+ >>> cloud = SeafloorAgeTracker.compute_ages(
32
+ ... target_age=100,
33
+ ... starting_age=200,
34
+ ... rotation_files=['rotations.rot'],
35
+ ... topology_files=['topologies.gpmlz']
36
+ ... )
37
+ >>> ages = cloud.get_property('age')
38
+ >>>
39
+ >>> # Or stepwise for intermediate states
40
+ >>> tracker = SeafloorAgeTracker(
41
+ ... rotation_files=['rotations.rot'],
42
+ ... topology_files=['topologies.gpmlz']
43
+ ... )
44
+ >>> tracker.initialize(starting_age=200)
45
+ >>> for age in range(199, -1, -1):
46
+ ... cloud = tracker.step_to(age)
47
+ """
48
+
49
+ __version__ = "0.2.0"
50
+
51
+ # Logging configuration (import first to configure before other modules)
52
+ from .logging import (
53
+ configure_logging,
54
+ set_log_level,
55
+ enable_verbose,
56
+ enable_debug,
57
+ disable_logging,
58
+ get_logger,
59
+ )
60
+
61
+ # Core seafloor age functionality
62
+ from .config import TracerConfig
63
+ from .hpc_integration import SeafloorAgeTracker
64
+
65
+ # Point rotation API
66
+ from .point_rotation import PointCloud, PointRotator
67
+ from .polygon_filter import PolygonFilter
68
+ from .io_formats import (
69
+ load_points_numpy,
70
+ load_points_latlon,
71
+ load_points_gpml,
72
+ save_points_numpy,
73
+ save_points_latlon,
74
+ save_points_gpml,
75
+ PointCloudCheckpoint,
76
+ )
77
+
78
+ # Utility modules (for advanced users)
79
+ from .mesh import (
80
+ create_sphere_mesh_xyz,
81
+ create_sphere_mesh_latlon,
82
+ )
83
+ from .mor_seeds import (
84
+ generate_mor_seeds,
85
+ generate_mor_seeds_with_plate_ids,
86
+ get_ridge_geometries,
87
+ )
88
+ from .initial_conditions import (
89
+ compute_initial_ages,
90
+ default_age_distance_law,
91
+ )
92
+ from .boundaries import (
93
+ ContinentalPolygonCache,
94
+ ResolvedTopologyCache,
95
+ extract_ridge_geometries,
96
+ extract_ridge_points_latlon,
97
+ )
98
+
99
+ __all__ = [
100
+ # Main API
101
+ "SeafloorAgeTracker",
102
+ "TracerConfig",
103
+ # Logging
104
+ "configure_logging",
105
+ "set_log_level",
106
+ "enable_verbose",
107
+ "enable_debug",
108
+ "disable_logging",
109
+ "get_logger",
110
+ # Point rotation
111
+ "PointCloud",
112
+ "PointRotator",
113
+ "PolygonFilter",
114
+ # IO utilities
115
+ "load_points_numpy",
116
+ "load_points_latlon",
117
+ "load_points_gpml",
118
+ "save_points_numpy",
119
+ "save_points_latlon",
120
+ "save_points_gpml",
121
+ "PointCloudCheckpoint",
122
+ # Mesh generation (advanced)
123
+ "create_sphere_mesh_xyz",
124
+ "create_sphere_mesh_latlon",
125
+ # MOR seeds (advanced)
126
+ "generate_mor_seeds",
127
+ "generate_mor_seeds_with_plate_ids",
128
+ "get_ridge_geometries",
129
+ # Initial conditions (advanced)
130
+ "compute_initial_ages",
131
+ "default_age_distance_law",
132
+ # Caching (advanced)
133
+ "ContinentalPolygonCache",
134
+ "ResolvedTopologyCache",
135
+ "extract_ridge_geometries",
136
+ "extract_ridge_points_latlon",
137
+ ]
@@ -0,0 +1,396 @@
1
+ """
2
+ Plate boundary caching and extraction utilities.
3
+
4
+ This module provides caching for resolved topologies and utility functions
5
+ for extracting plate boundary information. The main reconstruction is handled
6
+ by pygplates' C++ backend (TopologicalModel.reconstruct_geometry).
7
+ """
8
+
9
+ import numpy as np
10
+ import pygplates
11
+ from collections import OrderedDict
12
+ from typing import Dict, List, Optional, Tuple
13
+
14
+ from .spatial import find_polygons
15
+
16
+
17
+ class ResolvedTopologyCache:
18
+ """
19
+ LRU cache for resolved topologies at different timesteps.
20
+
21
+ Parameters
22
+ ----------
23
+ topology_features : pygplates.FeatureCollection or list
24
+ Topology features for resolving.
25
+ rotation_model : pygplates.RotationModel
26
+ Rotation model for reconstruction.
27
+ max_cache_size : int, default=10
28
+ Maximum number of timesteps to cache.
29
+ """
30
+
31
+ def __init__(
32
+ self,
33
+ topology_features,
34
+ rotation_model: pygplates.RotationModel,
35
+ max_cache_size: int = 10,
36
+ ):
37
+ self.topology_features = topology_features
38
+ self.rotation_model = rotation_model
39
+ self.max_cache_size = max_cache_size
40
+
41
+ # LRU cache: {time: (resolved_topologies, shared_boundary_sections)}
42
+ self._cache: OrderedDict = OrderedDict()
43
+
44
+ def get(self, time: float) -> Tuple[List, List]:
45
+ """
46
+ Get resolved topologies and shared boundary sections for a time.
47
+
48
+ Parameters
49
+ ----------
50
+ time : float
51
+ Geological time (Ma).
52
+
53
+ Returns
54
+ -------
55
+ resolved_topologies : list
56
+ List of resolved topology polygons.
57
+ shared_boundary_sections : list
58
+ List of shared boundary sections.
59
+ """
60
+ if time in self._cache:
61
+ # Move to end (most recently used)
62
+ self._cache.move_to_end(time)
63
+ return self._cache[time]
64
+
65
+ # Resolve topologies
66
+ resolved_topologies = []
67
+ shared_boundary_sections = []
68
+ pygplates.resolve_topologies(
69
+ self.topology_features,
70
+ self.rotation_model,
71
+ resolved_topologies,
72
+ time,
73
+ shared_boundary_sections,
74
+ )
75
+
76
+ # Add to cache
77
+ self._cache[time] = (resolved_topologies, shared_boundary_sections)
78
+
79
+ # Evict oldest if over capacity
80
+ while len(self._cache) > self.max_cache_size:
81
+ self._cache.popitem(last=False)
82
+
83
+ return resolved_topologies, shared_boundary_sections
84
+
85
+ def clear(self):
86
+ """Clear the cache."""
87
+ self._cache.clear()
88
+
89
+ def get_memory_usage(self) -> Dict[str, float]:
90
+ """Get approximate memory usage information."""
91
+ return {
92
+ 'cached_timesteps': len(self._cache),
93
+ 'max_cache_size': self.max_cache_size,
94
+ }
95
+
96
+
97
+ class ContinentalPolygonCache:
98
+ """
99
+ Cache for reconstructed continental polygons with efficient point-in-polygon queries.
100
+
101
+ Parameters
102
+ ----------
103
+ continental_polygons : str or pygplates.FeatureCollection
104
+ Continental polygon features.
105
+ rotation_model : pygplates.RotationModel
106
+ Rotation model for reconstruction.
107
+ max_cache_size : int, default=10
108
+ Maximum number of timesteps to cache.
109
+ """
110
+
111
+ def __init__(
112
+ self,
113
+ continental_polygons,
114
+ rotation_model: pygplates.RotationModel,
115
+ max_cache_size: int = 10,
116
+ ):
117
+ # Load continental polygons
118
+ if isinstance(continental_polygons, str):
119
+ self._polygon_features = pygplates.FeatureCollection(continental_polygons)
120
+ else:
121
+ self._polygon_features = continental_polygons
122
+
123
+ self.rotation_model = rotation_model
124
+ self.max_cache_size = max_cache_size
125
+
126
+ # LRU cache: {time: list of reconstructed polygon geometries}
127
+ self._cache: OrderedDict = OrderedDict()
128
+
129
+ def get_polygons(self, time: float) -> List[pygplates.PolygonOnSphere]:
130
+ """
131
+ Get reconstructed continental polygons at a time.
132
+
133
+ Parameters
134
+ ----------
135
+ time : float
136
+ Geological time (Ma).
137
+
138
+ Returns
139
+ -------
140
+ list of pygplates.PolygonOnSphere
141
+ Reconstructed continental polygon geometries.
142
+ """
143
+ if time in self._cache:
144
+ self._cache.move_to_end(time)
145
+ return self._cache[time]
146
+
147
+ # Reconstruct polygons
148
+ reconstructed = []
149
+ pygplates.reconstruct(
150
+ self._polygon_features,
151
+ self.rotation_model,
152
+ reconstructed,
153
+ time,
154
+ )
155
+
156
+ # Extract polygon geometries
157
+ polygon_geometries = []
158
+ for rfg in reconstructed:
159
+ geom = rfg.get_reconstructed_geometry()
160
+ if isinstance(geom, pygplates.PolygonOnSphere):
161
+ polygon_geometries.append(geom)
162
+
163
+ # Cache
164
+ self._cache[time] = polygon_geometries
165
+
166
+ # Evict oldest if over capacity
167
+ while len(self._cache) > self.max_cache_size:
168
+ self._cache.popitem(last=False)
169
+
170
+ return polygon_geometries
171
+
172
+ def get_continental_mask(
173
+ self,
174
+ lats: np.ndarray,
175
+ lons: np.ndarray,
176
+ time: float,
177
+ ) -> np.ndarray:
178
+ """
179
+ Get boolean mask indicating which points are inside continental polygons.
180
+
181
+ Uses ptt.utils.points_in_polygons.find_polygons for efficient vectorized
182
+ point-in-polygon testing with spatial tree acceleration.
183
+
184
+ Parameters
185
+ ----------
186
+ lats : np.ndarray
187
+ Point latitudes in degrees.
188
+ lons : np.ndarray
189
+ Point longitudes in degrees.
190
+ time : float
191
+ Geological time (Ma).
192
+
193
+ Returns
194
+ -------
195
+ np.ndarray
196
+ Boolean array, True for points inside continental polygons.
197
+ """
198
+ polygons = self.get_polygons(time)
199
+
200
+ if len(polygons) == 0:
201
+ return np.zeros(len(lats), dtype=bool)
202
+
203
+ # Create all points in single C++ call using MultiPointOnSphere
204
+ points = pygplates.MultiPointOnSphere(zip(lats, lons)).get_points()
205
+
206
+ # Use vectorized point-in-polygon with spatial tree
207
+ containing_polygons = find_polygons(
208
+ points, polygons, all_polygons=False
209
+ )
210
+
211
+ # Convert to boolean mask: True if point is in any polygon
212
+ mask = np.array([p is not None for p in containing_polygons], dtype=bool)
213
+
214
+ return mask
215
+
216
+ def clear(self):
217
+ """Clear the cache."""
218
+ self._cache.clear()
219
+
220
+
221
+ def extract_ridge_geometries(
222
+ time: float,
223
+ topology_features,
224
+ rotation_model: pygplates.RotationModel,
225
+ ) -> List[pygplates.PolylineOnSphere]:
226
+ """
227
+ Extract mid-ocean ridge geometries at a given time.
228
+
229
+ Parameters
230
+ ----------
231
+ time : float
232
+ Geological time (Ma).
233
+ topology_features : pygplates.FeatureCollection or list
234
+ Topology features.
235
+ rotation_model : pygplates.RotationModel
236
+ Rotation model.
237
+
238
+ Returns
239
+ -------
240
+ list of pygplates.PolylineOnSphere
241
+ Ridge geometries at the specified time.
242
+ """
243
+ resolved_topologies = []
244
+ shared_boundary_sections = []
245
+ pygplates.resolve_topologies(
246
+ topology_features,
247
+ rotation_model,
248
+ resolved_topologies,
249
+ time,
250
+ shared_boundary_sections,
251
+ )
252
+
253
+ ridge_geometries = []
254
+ for shared_boundary_section in shared_boundary_sections:
255
+ if (
256
+ shared_boundary_section.get_feature().get_feature_type()
257
+ == pygplates.FeatureType.create_gpml("MidOceanRidge")
258
+ ):
259
+ for shared_sub_segment in shared_boundary_section.get_shared_sub_segments():
260
+ ridge_geometries.append(shared_sub_segment.get_resolved_geometry())
261
+
262
+ return ridge_geometries
263
+
264
+
265
+ def extract_ridge_points_latlon(
266
+ time: float,
267
+ topology_features,
268
+ rotation_model: pygplates.RotationModel,
269
+ tessellate_degrees: float = 0.5,
270
+ ) -> Tuple[np.ndarray, np.ndarray]:
271
+ """
272
+ Extract mid-ocean ridge points as lat/lon arrays.
273
+
274
+ Parameters
275
+ ----------
276
+ time : float
277
+ Geological time (Ma).
278
+ topology_features : pygplates.FeatureCollection or list
279
+ Topology features.
280
+ rotation_model : pygplates.RotationModel
281
+ Rotation model.
282
+ tessellate_degrees : float, default=0.5
283
+ Tessellation resolution in degrees.
284
+
285
+ Returns
286
+ -------
287
+ lats : np.ndarray
288
+ Ridge point latitudes in degrees.
289
+ lons : np.ndarray
290
+ Ridge point longitudes in degrees.
291
+ """
292
+ ridge_geometries = extract_ridge_geometries(time, topology_features, rotation_model)
293
+
294
+ if len(ridge_geometries) == 0:
295
+ return np.array([]), np.array([])
296
+
297
+ all_lats = []
298
+ all_lons = []
299
+
300
+ for geom in ridge_geometries:
301
+ tessellated = geom.to_tessellated(np.radians(tessellate_degrees))
302
+ for point in tessellated:
303
+ lat, lon = point.to_lat_lon()
304
+ all_lats.append(lat)
305
+ all_lons.append(lon)
306
+
307
+ return np.array(all_lats), np.array(all_lons)
308
+
309
+
310
+ def extract_subduction_geometries(
311
+ time: float,
312
+ topology_features,
313
+ rotation_model: pygplates.RotationModel,
314
+ ) -> List[pygplates.PolylineOnSphere]:
315
+ """
316
+ Extract subduction zone geometries at a given time.
317
+
318
+ Parameters
319
+ ----------
320
+ time : float
321
+ Geological time (Ma).
322
+ topology_features : pygplates.FeatureCollection or list
323
+ Topology features.
324
+ rotation_model : pygplates.RotationModel
325
+ Rotation model.
326
+
327
+ Returns
328
+ -------
329
+ list of pygplates.PolylineOnSphere
330
+ Subduction zone geometries at the specified time.
331
+ """
332
+ resolved_topologies = []
333
+ shared_boundary_sections = []
334
+ pygplates.resolve_topologies(
335
+ topology_features,
336
+ rotation_model,
337
+ resolved_topologies,
338
+ time,
339
+ shared_boundary_sections,
340
+ )
341
+
342
+ subduction_geometries = []
343
+ for shared_boundary_section in shared_boundary_sections:
344
+ feature_type = shared_boundary_section.get_feature().get_feature_type()
345
+ if feature_type == pygplates.FeatureType.create_gpml("SubductionZone"):
346
+ for shared_sub_segment in shared_boundary_section.get_shared_sub_segments():
347
+ subduction_geometries.append(shared_sub_segment.get_resolved_geometry())
348
+
349
+ return subduction_geometries
350
+
351
+
352
+ def extract_subduction_points_latlon(
353
+ time: float,
354
+ topology_features,
355
+ rotation_model: pygplates.RotationModel,
356
+ tessellate_degrees: float = 0.5,
357
+ ) -> Tuple[np.ndarray, np.ndarray]:
358
+ """
359
+ Extract subduction zone points as lat/lon arrays.
360
+
361
+ Parameters
362
+ ----------
363
+ time : float
364
+ Geological time (Ma).
365
+ topology_features : pygplates.FeatureCollection or list
366
+ Topology features.
367
+ rotation_model : pygplates.RotationModel
368
+ Rotation model.
369
+ tessellate_degrees : float, default=0.5
370
+ Tessellation resolution in degrees.
371
+
372
+ Returns
373
+ -------
374
+ lats : np.ndarray
375
+ Subduction zone point latitudes in degrees.
376
+ lons : np.ndarray
377
+ Subduction zone point longitudes in degrees.
378
+ """
379
+ subduction_geometries = extract_subduction_geometries(
380
+ time, topology_features, rotation_model
381
+ )
382
+
383
+ if len(subduction_geometries) == 0:
384
+ return np.array([]), np.array([])
385
+
386
+ all_lats = []
387
+ all_lons = []
388
+
389
+ for geom in subduction_geometries:
390
+ tessellated = geom.to_tessellated(np.radians(tessellate_degrees))
391
+ for point in tessellated:
392
+ lat, lon = point.to_lat_lon()
393
+ all_lats.append(lat)
394
+ all_lons.append(lon)
395
+
396
+ return np.array(all_lats), np.array(all_lons)