mapflpy 1.1.9__tar.gz → 1.2.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.
Files changed (88) hide show
  1. {mapflpy-1.1.9 → mapflpy-1.2.0}/PKG-INFO +1 -1
  2. {mapflpy-1.1.9 → mapflpy-1.2.0}/environment.yml +1 -0
  3. {mapflpy-1.1.9 → mapflpy-1.2.0}/mapflpy/globals.py +33 -1
  4. {mapflpy-1.1.9 → mapflpy-1.2.0}/mapflpy/scripts.py +237 -2
  5. {mapflpy-1.1.9 → mapflpy-1.2.0}/mapflpy/tracer.py +11 -11
  6. {mapflpy-1.1.9 → mapflpy-1.2.0}/mapflpy/utils.py +5 -3
  7. {mapflpy-1.1.9 → mapflpy-1.2.0}/meson.build +20 -2
  8. {mapflpy-1.1.9 → mapflpy-1.2.0}/noxfile.py +17 -1
  9. {mapflpy-1.1.9 → mapflpy-1.2.0}/pyproject.toml +2 -2
  10. {mapflpy-1.1.9 → mapflpy-1.2.0}/src/mapfl/mapfl.f +233 -45
  11. {mapflpy-1.1.9 → mapflpy-1.2.0}/src/mapfl/psi_io.f90 +3 -1
  12. {mapflpy-1.1.9 → mapflpy-1.2.0}/src/mapflpy_fortran.f90 +6 -2
  13. {mapflpy-1.1.9 → mapflpy-1.2.0}/.f2py_f2cmap +0 -0
  14. {mapflpy-1.1.9 → mapflpy-1.2.0}/.gitattributes +0 -0
  15. {mapflpy-1.1.9 → mapflpy-1.2.0}/.github/workflows/docs.yml +0 -0
  16. {mapflpy-1.1.9 → mapflpy-1.2.0}/.github/workflows/publish.yml +0 -0
  17. {mapflpy-1.1.9 → mapflpy-1.2.0}/.gitignore +0 -0
  18. {mapflpy-1.1.9 → mapflpy-1.2.0}/.python-version +0 -0
  19. {mapflpy-1.1.9 → mapflpy-1.2.0}/LICENSE.txt +0 -0
  20. {mapflpy-1.1.9 → mapflpy-1.2.0}/README.rst +0 -0
  21. {mapflpy-1.1.9 → mapflpy-1.2.0}/docs/Makefile +0 -0
  22. {mapflpy-1.1.9 → mapflpy-1.2.0}/docs/make.bat +0 -0
  23. {mapflpy-1.1.9 → mapflpy-1.2.0}/docs/source/_static/.gitkeep +0 -0
  24. {mapflpy-1.1.9 → mapflpy-1.2.0}/docs/source/_static/assets/.gitkeep +0 -0
  25. {mapflpy-1.1.9 → mapflpy-1.2.0}/docs/source/_templates/autosummary/attribute.rst +0 -0
  26. {mapflpy-1.1.9 → mapflpy-1.2.0}/docs/source/_templates/autosummary/class.rst +0 -0
  27. {mapflpy-1.1.9 → mapflpy-1.2.0}/docs/source/_templates/autosummary/class_method.rst +0 -0
  28. {mapflpy-1.1.9 → mapflpy-1.2.0}/docs/source/_templates/autosummary/data.rst +0 -0
  29. {mapflpy-1.1.9 → mapflpy-1.2.0}/docs/source/_templates/autosummary/exception.rst +0 -0
  30. {mapflpy-1.1.9 → mapflpy-1.2.0}/docs/source/_templates/autosummary/function.rst +0 -0
  31. {mapflpy-1.1.9 → mapflpy-1.2.0}/docs/source/_templates/autosummary/method.rst +0 -0
  32. {mapflpy-1.1.9 → mapflpy-1.2.0}/docs/source/_templates/autosummary/module.rst +0 -0
  33. {mapflpy-1.1.9 → mapflpy-1.2.0}/docs/source/_templates/autosummary/package.rst +0 -0
  34. {mapflpy-1.1.9 → mapflpy-1.2.0}/docs/source/_templates/autosummary/property.rst +0 -0
  35. {mapflpy-1.1.9 → mapflpy-1.2.0}/docs/source/_templates/autosummary/static_method.rst +0 -0
  36. {mapflpy-1.1.9 → mapflpy-1.2.0}/docs/source/api/index.rst +0 -0
  37. {mapflpy-1.1.9 → mapflpy-1.2.0}/docs/source/conf.py +0 -0
  38. {mapflpy-1.1.9 → mapflpy-1.2.0}/docs/source/guide/development.rst +0 -0
  39. {mapflpy-1.1.9 → mapflpy-1.2.0}/docs/source/guide/index.rst +0 -0
  40. {mapflpy-1.1.9 → mapflpy-1.2.0}/docs/source/guide/installation.rst +0 -0
  41. {mapflpy-1.1.9 → mapflpy-1.2.0}/docs/source/guide/overview.rst +0 -0
  42. {mapflpy-1.1.9 → mapflpy-1.2.0}/docs/source/index.rst +0 -0
  43. {mapflpy-1.1.9 → mapflpy-1.2.0}/examples/01_getting_started/GALLERY_HEADER.rst +0 -0
  44. {mapflpy-1.1.9 → mapflpy-1.2.0}/examples/01_getting_started/p01_tracing_forward.py +0 -0
  45. {mapflpy-1.1.9 → mapflpy-1.2.0}/examples/01_getting_started/p02_tracing_backward.py +0 -0
  46. {mapflpy-1.1.9 → mapflpy-1.2.0}/examples/01_getting_started/p03_tracing_forward_backward.py +0 -0
  47. {mapflpy-1.1.9 → mapflpy-1.2.0}/examples/01_getting_started/p04_adding_a_magnetogram.py +0 -0
  48. {mapflpy-1.1.9 → mapflpy-1.2.0}/examples/02_using_tracer_class/GALLERY_HEADER.rst +0 -0
  49. {mapflpy-1.1.9 → mapflpy-1.2.0}/examples/02_using_tracer_class/p01_using_defaults.py +0 -0
  50. {mapflpy-1.1.9 → mapflpy-1.2.0}/examples/02_using_tracer_class/p02_setting_tracing_direction.py +0 -0
  51. {mapflpy-1.1.9 → mapflpy-1.2.0}/examples/02_using_tracer_class/p03_adjusting_mapfl_params.py +0 -0
  52. {mapflpy-1.1.9 → mapflpy-1.2.0}/examples/02_using_tracer_class/p04_manipulating_field_data.py +0 -0
  53. {mapflpy-1.1.9 → mapflpy-1.2.0}/examples/03_using_tracermp_class/GALLERY_HEADER.rst +0 -0
  54. {mapflpy-1.1.9 → mapflpy-1.2.0}/examples/03_using_tracermp_class/p01_using_defaults.py +0 -0
  55. {mapflpy-1.1.9 → mapflpy-1.2.0}/examples/03_using_tracermp_class/p02_creating_multiple_tracermp.py +0 -0
  56. {mapflpy-1.1.9 → mapflpy-1.2.0}/examples/03_using_tracermp_class/p03_magnetic_field.py +0 -0
  57. {mapflpy-1.1.9 → mapflpy-1.2.0}/examples/04_advanced_examples/GALLERY_HEADER.rst +0 -0
  58. {mapflpy-1.1.9 → mapflpy-1.2.0}/examples/04_advanced_examples/p01_interdomain_tracing.py +0 -0
  59. {mapflpy-1.1.9 → mapflpy-1.2.0}/examples/04_advanced_examples/p02_coloring_by_polarity.py +0 -0
  60. {mapflpy-1.1.9 → mapflpy-1.2.0}/examples/04_advanced_examples/p03_filtering_fieldlines.py +0 -0
  61. {mapflpy-1.1.9 → mapflpy-1.2.0}/examples/GALLERY_HEADER.rst +0 -0
  62. {mapflpy-1.1.9 → mapflpy-1.2.0}/mapflpy/__init__.py +0 -0
  63. {mapflpy-1.1.9 → mapflpy-1.2.0}/mapflpy/_version.py.in +0 -0
  64. {mapflpy-1.1.9 → mapflpy-1.2.0}/mapflpy/data.py +0 -0
  65. {mapflpy-1.1.9 → mapflpy-1.2.0}/mapflpy/fortran/__init__.py +0 -0
  66. {mapflpy-1.1.9 → mapflpy-1.2.0}/mapflpy/py.typed +0 -0
  67. {mapflpy-1.1.9 → mapflpy-1.2.0}/src/f2py_f2cmap_mapflpy +0 -0
  68. {mapflpy-1.1.9 → mapflpy-1.2.0}/tests/__init__.py +0 -0
  69. {mapflpy-1.1.9 → mapflpy-1.2.0}/tests/conftest.py +0 -0
  70. {mapflpy-1.1.9 → mapflpy-1.2.0}/tests/data/reference_traces.npz +0 -0
  71. {mapflpy-1.1.9 → mapflpy-1.2.0}/tests/test_module.py +0 -0
  72. {mapflpy-1.1.9 → mapflpy-1.2.0}/tests/test_tracer/__init__.py +0 -0
  73. {mapflpy-1.1.9 → mapflpy-1.2.0}/tests/test_tracer/test_class_primitives.py +0 -0
  74. {mapflpy-1.1.9 → mapflpy-1.2.0}/tests/test_tracer/test_fortran_calls.py +0 -0
  75. {mapflpy-1.1.9 → mapflpy-1.2.0}/tests/test_tracer/test_param_editing.py +0 -0
  76. {mapflpy-1.1.9 → mapflpy-1.2.0}/tests/test_tracer/test_trace_routines.py +0 -0
  77. {mapflpy-1.1.9 → mapflpy-1.2.0}/tests/test_tracermp/__init__.py +0 -0
  78. {mapflpy-1.1.9 → mapflpy-1.2.0}/tests/test_tracermp/test_class_primitives_.py +0 -0
  79. {mapflpy-1.1.9 → mapflpy-1.2.0}/tests/test_tracermp/test_fortran_calls_.py +0 -0
  80. {mapflpy-1.1.9 → mapflpy-1.2.0}/tests/test_tracermp/test_param_editing_.py +0 -0
  81. {mapflpy-1.1.9 → mapflpy-1.2.0}/tests/test_tracermp/test_trace_routines_.py +0 -0
  82. {mapflpy-1.1.9 → mapflpy-1.2.0}/tests/utils.py +0 -0
  83. {mapflpy-1.1.9 → mapflpy-1.2.0}/tools/make_build.py +0 -0
  84. {mapflpy-1.1.9 → mapflpy-1.2.0}/tools/make_docs.py +0 -0
  85. {mapflpy-1.1.9 → mapflpy-1.2.0}/tools/make_intersphinx.py +0 -0
  86. {mapflpy-1.1.9 → mapflpy-1.2.0}/tools/pyproject_version.py +0 -0
  87. {mapflpy-1.1.9 → mapflpy-1.2.0}/tools/python_version.py +0 -0
  88. {mapflpy-1.1.9 → mapflpy-1.2.0}/tools/python_versions.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mapflpy
3
- Version: 1.1.9
3
+ Version: 1.2.0
4
4
  Summary: Fast field line tracing for spherical vector fields
5
5
  Keywords: solar physics,space physics,heliophysics,magnetohydrodynamic,science,mas,predsci,psi,modeling,fortran
6
6
  Author: Ryder Davidson, Cooper Downs, Andres Reyes, Asher Pembroke
@@ -30,6 +30,7 @@ dependencies:
30
30
  - ruff
31
31
  - coverage
32
32
  - nox
33
+ - pre-commit
33
34
 
34
35
  # -------- Docs --------
35
36
  - sphinx
@@ -30,7 +30,7 @@ __all__ = [
30
30
  # It contains the geometry of the traces, their start and end positions,
31
31
  # and whether they were traced to a boundary.
32
32
  #------------------------------------------------------------------------------
33
- Traces = namedtuple('Traces', ['geometry', 'start_pos', 'end_pos', 'traced_to_boundary'])
33
+ Traces = namedtuple('Traces', ['geometry', 'start_pos', 'end_pos', 'traced_to_boundary', 'integral'])
34
34
  Traces.__doc__ = (
35
35
  """Named tuple for storing magnetic fieldline trace information.
36
36
 
@@ -45,9 +45,39 @@ Traces.__doc__ = (
45
45
  Array of shape (N, 3) containing the ending positions of each fieldline.
46
46
  traced_to_boundary : ndarray
47
47
  Boolean array of shape (N,) indicating whether each fieldline was traced to a boundary.
48
+ integral : ndarray
49
+ Array of shape (N,) containing the field aligned integral. By default the integral
50
+ is just the field line length, but certain mapflpy options can change this behavior,
51
+ currently `integrate_along_fl_`, `scalar_input_file_`, `weight_integral_by_area_`, and
52
+ max_along_fl_`.
48
53
  """
49
54
  )
50
55
 
56
+ # ------------------------------------------------------------------------------
57
+ # Named tuple for storing mapping information produced by some of the mapping
58
+ # scripts in `mapflpy.scripts`
59
+ # ------------------------------------------------------------------------------
60
+ Mapping = namedtuple('Mapping', ['r', 't', 'p', 'traced_to_boundary', 'integral'])
61
+ Mapping.__doc__ = (
62
+ """Named tuple for storing magnetic fieldline mapping information.
63
+
64
+ Attributes
65
+ ----------
66
+ r : ndarray
67
+ Array containing the radial positions of the mapping endpoints [R_sun]).
68
+ t : ndarray
69
+ Array containing the theta (co-latitude) positions of the mapping endpoints [radians].
70
+ p : ndarray
71
+ Array containing the phi (longitude) positions of the mapping endpoints [radians].
72
+ traced_to_boundary : ndarray
73
+ Boolean array indicating whether each fieldline was traced to a boundary.
74
+ integral : ndarray
75
+ Array containing the values of the field line integral. By default the integral
76
+ is just the field line length, but certain mapflpy options can change this behavior,
77
+ currently `integrate_along_fl_`, `scalar_input_file_`, `weight_integral_by_area_`, and
78
+ `max_along_fl_`.
79
+ """
80
+ )
51
81
 
52
82
  # ------------------------------------------------------------------------------
53
83
  # Type aliases for improved code readability.
@@ -182,6 +212,8 @@ DEFAULT_FIELDS = MappingProxyType({
182
212
  DEFAULT_PARAMS = MappingProxyType({
183
213
  'integrate_along_fl_': False,
184
214
  'scalar_input_file_': '',
215
+ 'weight_integral_by_area_': False,
216
+ 'max_along_fl_': False,
185
217
  'verbose_': False,
186
218
  'cubic_': False,
187
219
  'trace_fwd_': False,
@@ -20,15 +20,16 @@ concatenation.
20
20
 
21
21
  """
22
22
  from __future__ import annotations
23
+ import concurrent.futures
23
24
  import copy
24
25
  from contextlib import ExitStack
25
26
  from functools import partial
26
- from typing import Optional, Iterable, Tuple
27
+ from typing import Optional, Iterable, Tuple, Callable
27
28
 
28
29
  import numpy as np
29
30
  from numpy.typing import NDArray, ArrayLike
30
31
 
31
- from mapflpy.globals import DEFAULT_BUFFER_SIZE, Traces, PathType, DirectionType
32
+ from mapflpy.globals import DEFAULT_BUFFER_SIZE, Traces, Mapping, PathType, DirectionType, ContextType
32
33
  from mapflpy.tracer import TracerMP
33
34
  from mapflpy.utils import shift_phi_traces, shift_phi_lps, fetch_default_launch_points
34
35
 
@@ -180,6 +181,240 @@ def run_fwdbwd_tracing(br: PathType,
180
181
  return tracer.trace_fbwd(launch_points, buffer_size)
181
182
 
182
183
 
184
+ def map_field_lines_in_parallel(trace_function: Callable,
185
+ r_in: ArrayLike,
186
+ t_in: ArrayLike,
187
+ p_in: ArrayLike,
188
+ nproc: int = 4):
189
+ """Map field lines from r, t, p positions in parallel using a given tracing script.
190
+
191
+ The `trace_function` is a callable function that wraps the desired mapflpy script that uses TracerMP in
192
+ the background. It must only accept one argument (`launch_points`) and otherwise wraps a tracing
193
+ script that is defined with any specific keywords that determine how the trace is done.
194
+ Currently only :func:`run_forward_tracing`, :func:`run_backward_tracing` make sense to wrap here.
195
+
196
+ The r,t,p input arrays can be any shape (1D, 2D, 3D, etc.) as long as it is consistent.
197
+
198
+ .. warning::
199
+ The wrapped trace function should specify the `buffer_size` keyword to a small number (e.g. 3)
200
+ since the trace geometry is not used anyway. This is essential for keeping the memory footprint
201
+ small and for the mapping to compute as quickly as possible.
202
+
203
+ Parameters
204
+ ----------
205
+ trace_function : Callable
206
+ A function that wraps the mapflpy forward or backwards tracing script to call in parallel
207
+ (:func:`run_forward_tracing`, :func:`run_backward_tracing`) and sets the desired parameters to
208
+ run the wrapped script with.
209
+ Only the endpoints are used, so only one of forward or backwards mapping makes sense.
210
+ r_in : ArrayLike
211
+ An N-Dimensional array of radial positions to map from.
212
+ t_in : ArrayLike
213
+ An N-Dimensional array of theta positions to map from (shape must match `r_in`).
214
+ p_in : ArrayLike
215
+ An N-Dimensional array of phi positions to map from (shape must match `r_in`).
216
+ nproc : int, optional
217
+ The number of processes to spawn. This should be equal to or less than the number of threads
218
+ that can be used on the machine.
219
+
220
+ Returns
221
+ -------
222
+ mapping : :class:`~mapflpy.globals.Mapping`
223
+ A namedtuple containing the mapping results (:class:`mapflpy.globals.Mapping`).
224
+
225
+ See Also
226
+ --------
227
+ :func:`map_pt_forward`
228
+ :func:`map_pt_backward`
229
+ :func:`run_forward_tracing`
230
+ :func:`run_backward_tracing`
231
+ """
232
+
233
+ # convert the r,t,p positions to a list of launchpoints split into nproc chunks
234
+ r1d = r_in.ravel()
235
+ t1d = t_in.ravel()
236
+ p1d = p_in.ravel()
237
+ lp_all = np.stack([r1d, t1d, p1d], axis=0)
238
+ lp_chunks = np.array_split(lp_all, nproc, axis=1)
239
+
240
+ # run the mapping function on nprocs
241
+ with concurrent.futures.ThreadPoolExecutor(max_workers=nproc) as executor:
242
+ results = list(executor.map(trace_function, lp_chunks))
243
+
244
+ # collect the results back into one array
245
+ end_pos_1d = np.concatenate([result.end_pos for result in results], axis=1)
246
+ traced_to_boundary_1d = np.concatenate([result.traced_to_boundary for result in results])
247
+ integral_1d = np.concatenate([result.integral for result in results])
248
+
249
+ # reshape and return the Mapping object
250
+ r_out = np.reshape(end_pos_1d[0, :], r_in.shape)
251
+ t_out = np.reshape(end_pos_1d[1, :], t_in.shape)
252
+ p_out = np.reshape(end_pos_1d[2, :], p_in.shape)
253
+ traced_to_boundary = np.reshape(traced_to_boundary_1d, r_in.shape)
254
+ integral = np.reshape(integral_1d, r_in.shape)
255
+
256
+ return Mapping(r_out, t_out, p_out, traced_to_boundary, integral)
257
+
258
+
259
+ def map_pt_forward(br: PathType,
260
+ bt: PathType,
261
+ bp: PathType,
262
+ p1d: ArrayLike,
263
+ t1d: ArrayLike,
264
+ radius: float = 1.0,
265
+ timeout: float = 3600.,
266
+ context: Optional[ContextType] = 'fork',
267
+ nproc: int = 4,
268
+ **mapfl_params):
269
+ """Map field lines forwards on a phi, theta grid at constant radius.
270
+
271
+ The inputs are similar to instantiating a Tracer class except one also
272
+ specifies 1D arrays that define a grid in phi, theta and a radius to map from.
273
+
274
+ Currently the mapping radius defaults to 1.0 or must be set. In the future
275
+ this will default to the inner radius of the main mesh of the B files.
276
+
277
+ Notes
278
+ -----
279
+ This is a very specific helper function for a common task. Generic mappings
280
+ can be built by following this as an example for wrapping :func:`map_field_lines_in_parallel`.
281
+
282
+ Parameters
283
+ ----------
284
+ br : PathType
285
+ Path to the Br magnetic field file.
286
+ bt : PathType
287
+ Path to the Bt magnetic field file.
288
+ bp : PathType
289
+ Path to the Bp magnetic field file.
290
+ p1d : ArrayLike
291
+ A 1D numpy array of phi (longitude) positions in radians that will be used to
292
+ build the mapping grid.
293
+ t1d : ArrayLike
294
+ A 1D numpy array of theta (co-latitude) positions in radians that will be used to
295
+ build the mapping grid.
296
+ radius : float, optional
297
+ Radius to map forward from. Default 1.0.
298
+ timeout : float, optional
299
+ Timeout in seconds for interprocess communication. Default is 3600 seconds.
300
+ context : ContextType, optional
301
+ The multiprocessing context to use when spawning the subprocess. Since many
302
+ processes are launched, generally 'fork' is recommended. Behavior may depend
303
+ on the system architecture. Default is 'fork'.
304
+ nproc : int, optional
305
+ The number of processes to spawn. This should be equal to or less than the number of threads
306
+ that can be used on the machine.
307
+ **mapfl_params : dict
308
+ Additional tracing parameters passed to the subprocess.
309
+
310
+ Returns
311
+ -------
312
+ mapping : :class:`~mapflpy.globals.Mapping`
313
+ A namedtuple containing the mapping results (:class:`mapflpy.globals.Mapping`).
314
+
315
+ See Also
316
+ --------
317
+ :func:`map_field_lines_in_parallel`
318
+ """
319
+
320
+ # build a 2D grid of p and t locations from the input 1D arrays
321
+ p2d, t2d = np.meshgrid(p1d, t1d)
322
+ # the r locations are on the specified radius
323
+ r2d = np.ones_like(p2d)*radius
324
+
325
+ # define the wrapped trace function
326
+ def trace_function(launch_points):
327
+ traces = run_forward_tracing(br, bt, bp, launch_points=launch_points,
328
+ buffer_size=3, context=context, timeout=timeout,
329
+ **mapfl_params)
330
+ return traces
331
+
332
+ # compute the mapping
333
+ mapping = map_field_lines_in_parallel(trace_function, r2d, t2d, p2d, nproc=nproc)
334
+
335
+ return mapping
336
+
337
+
338
+ def map_pt_backward(br: PathType,
339
+ bt: PathType,
340
+ bp: PathType,
341
+ p1d: ArrayLike,
342
+ t1d: ArrayLike,
343
+ radius: float = 30.0,
344
+ timeout: float = 3600.,
345
+ context: Optional[ContextType] = 'fork',
346
+ nproc: int = 4,
347
+ **mapfl_params):
348
+ """Map field lines backwards on a phi, theta grid at constant radius.
349
+
350
+ The inputs are similar to instantiating a Tracer class except one also
351
+ specifies 1D arrays that define a grid in phi, theta and a radius to map from.
352
+
353
+ Currently the mapping radius defaults to 30 or must be set. In the future
354
+ this will default to the outer radius of the main mesh of the B files.
355
+
356
+ Notes
357
+ -----
358
+ This is a very specific helper function for a common task. Generic mappings
359
+ can be built by following this as an example for wrapping :func:`map_field_lines_in_parallel`.
360
+
361
+ Parameters
362
+ ----------
363
+ br : PathType
364
+ Path to the Br magnetic field file.
365
+ bt : PathType
366
+ Path to the Bt magnetic field file.
367
+ bp : PathType
368
+ Path to the Bp magnetic field file.
369
+ p1d : ArrayLike
370
+ A 1D numpy array of phi (longitude) positions in radians that will be used to
371
+ build the mapping grid.
372
+ t1d : ArrayLike
373
+ A 1D numpy array of theta (co-latitude) positions in radians that will be used to
374
+ build the mapping grid.
375
+ radius : float, optional
376
+ Radius to map backwards from. Default 30.0.
377
+ timeout : float, optional
378
+ Timeout in seconds for interprocess communication. Default is 3600 seconds.
379
+ context : ContextType, optional
380
+ The multiprocessing context to use when spawning the subprocess. Since many
381
+ processes are launched, generally 'fork' is recommended. Behavior may depend
382
+ on the system architecture. Default is 'fork'.
383
+ nproc : int, optional
384
+ The number of processes to spawn. This should be equal to or less than the number of threads
385
+ that can be used on the machine.
386
+ **mapfl_params : dict
387
+ Additional tracing parameters passed to the subprocess.
388
+
389
+ Returns
390
+ -------
391
+ mapping : :class:`~mapflpy.globals.Mapping`
392
+ A namedtuple containing the mapping results (:class:`mapflpy.globals.Mapping`).
393
+
394
+ See Also
395
+ --------
396
+ :func:`map_field_lines_in_parallel`
397
+ """
398
+
399
+
400
+ # build a 2D grid of p and t locations from the input 1D arrays
401
+ p2d, t2d = np.meshgrid(p1d, t1d)
402
+ # the r locations are on the specified radius
403
+ r2d = np.ones_like(p2d)*radius
404
+
405
+ # define the wrapped trace function
406
+ def trace_function(launch_points):
407
+ traces = run_backward_tracing(br, bt, bp, launch_points=launch_points,
408
+ buffer_size=3, context=context, timeout=timeout,
409
+ **mapfl_params)
410
+ return traces
411
+
412
+ # compute the mapping
413
+ mapping = map_field_lines_in_parallel(trace_function, r2d, t2d, p2d, nproc=nproc)
414
+
415
+ return mapping
416
+
417
+
183
418
  def inter_domain_tracing(br_cor: PathType,
184
419
  bt_cor: PathType,
185
420
  bp_cor: PathType,
@@ -54,9 +54,6 @@ _BS0 = np.zeros(3, order='F').astype(np.float64)
54
54
  _BS1 = np.zeros(3, order='F').astype(np.float64)
55
55
  """Sentinel array for :meth:`mapfl.trace` **bs1** parameter."""
56
56
 
57
- _S = np.zeros(1, order='F').astype(np.float64)
58
- """Sentinel array for :meth:`mapfl.trace` **s** parameter."""
59
-
60
57
 
61
58
  def state_modifier(method):
62
59
  """
@@ -289,19 +286,20 @@ def _mapflpy_trace_listener(pipe):
289
286
  traces = np.full((buffer_size, *lps.shape), np.nan, order='F').astype(np.float64)
290
287
  s1 = np.zeros(lps.shape, np.float64, order='F')
291
288
  mask = np.full((1, lps.shape[1]), False, order='F')
289
+ integral = np.full((1, lps.shape[1]), 0.0, order='F')
292
290
  for i in range(lps.shape[1]):
293
291
  trace_args = dict(
294
292
  s0=lps[:, i],
295
293
  s1=s1[:, i],
296
294
  bs0=_BS0,
297
295
  bs1=_BS1,
298
- s=_S,
296
+ s=integral[:,i],
299
297
  traced_to_r_boundary=mask[:, i],
300
298
  svec=traces[:, :, i],
301
299
  svec_n=buffer_size
302
300
  )
303
301
  mapfl.trace(**trace_args)
304
- return Traces(traces, lps, s1, mask[0, :])
302
+ return Traces(traces, lps, s1, mask[0, :], integral[0,:])
305
303
 
306
304
  while True:
307
305
  method, *args = pipe.recv()
@@ -866,27 +864,29 @@ class Tracer(_Tracer):
866
864
  Structured container with traced fieldline geometry, starting points, ending points,
867
865
  and whether the fieldlines reached the radial boundary of the provided mesh.
868
866
  """
869
- # `traces`, `s1`, and `mask` are initialized to Fortran-contiguous arrays
870
- # and are used to store the traced fieldlines, their end points, and boundary masks.
867
+ # `traces`, `s1`, `mask`, and `integral` are initialized to Fortran-contiguous arrays
868
+ # and are used to store the traced fieldlines, their end points, boundary masks, and
869
+ # the scalar field integral along the field line (length by default).
871
870
  # `mapflpy_fortran` alters these arrays in-place during tracing.
872
871
  # Each array must have a dimensionality > 1 so that a numpy view is passed.
873
872
  traces = np.full((buff, *lps.shape), np.nan, order='F').astype(np.float64)
874
873
  s1 = np.zeros(lps.shape, np.float64, order='F')
875
874
  mask = np.full((1, lps.shape[1]), False, order='F')
875
+ integral = np.full((1, lps.shape[1]), 0.0, order='F')
876
876
 
877
- # Since the parameters _bs0, _bs1, and _s are discarded after the trace,
878
- # we can use the globals `_BS0`, `_BS1`, and `_S` as placeholders to avoid having
877
+ # Since the parameters _bs0 and _bs1 are discarded after the trace,
878
+ # we can use the globals `_BS0` and `_BS1` as placeholders to avoid having
879
879
  # to repeatedly create and destruct arrays.
880
880
  for i in range(lps.shape[1]):
881
881
  self._mapfl.trace(s0=lps[:, i],
882
882
  s1=s1[:, i],
883
883
  bs0=_BS0,
884
884
  bs1=_BS1,
885
- s=_S,
885
+ s=integral[:,i],
886
886
  traced_to_r_boundary=mask[:, i],
887
887
  svec=traces[:, :, i],
888
888
  svec_n=buff)
889
- return Traces(traces, lps, s1, mask[0, :])
889
+ return Traces(traces, lps, s1, mask[0, :], integral[0,:])
890
890
 
891
891
 
892
892
  class TracerMP(_Tracer):
@@ -220,7 +220,8 @@ def combine_fwd_bwd_traces(fwd_traces: Traces,
220
220
  fwd_traces.geometry]),
221
221
  bwd_traces.end_pos,
222
222
  fwd_traces.end_pos,
223
- fwd_traces.traced_to_boundary & bwd_traces.traced_to_boundary)
223
+ fwd_traces.traced_to_boundary & bwd_traces.traced_to_boundary,
224
+ fwd_traces.integral + bwd_traces.integral)
224
225
  return combined_traces
225
226
 
226
227
 
@@ -423,7 +424,8 @@ def trim_fieldline_nan_buffer(traces: Traces | np.ndarray) -> List[np.ndarray]:
423
424
 
424
425
  def combine_and_pad_fieldlines(arrs: list | tuple,
425
426
  to_trace: bool = False,
426
- traced_to_boundary: Optional[NDArray[np.bool_]] = None
427
+ traced_to_boundary: Optional[NDArray[np.bool_]] = None,
428
+ integral: Optional[NDArray[np.bool_]] = None
427
429
  ) -> np.ndarray | Traces:
428
430
  """
429
431
  NaN-pad a list of variable-length 3D point arrays into a single dense array.
@@ -499,7 +501,7 @@ def combine_and_pad_fieldlines(arrs: list | tuple,
499
501
  geometry[:a.shape[1], :, i] = a.T # (3, ni) -> (ni, 3)
500
502
  start_pos[:, i] = a[:,0]
501
503
  end_pos[:, i] = a[:, -1]
502
- return Traces(geometry, start_pos, end_pos, traced_to_boundary)
504
+ return Traces(geometry, start_pos, end_pos, traced_to_boundary, integral)
503
505
 
504
506
 
505
507
 
@@ -47,8 +47,16 @@ fortranobject_c = f2py_inc_dir / 'fortranobject.c'
47
47
 
48
48
  # ---- HDF5 dependencies
49
49
  hdf5_c = dependency('hdf5', required : true)
50
- hdf5f = declare_dependency(link_args : ['-lhdf5_fortran'], dependencies : [hdf5_c])
51
- hdf5f_hl = declare_dependency(link_args : ['-lhdf5hl_fortran'], dependencies : [hdf5_c])
50
+
51
+ # On Linux, meson resolves pkg-config deps to absolute library paths, which
52
+ # drops the -L flag from the link command. Bare -lhdf5_fortran then fails
53
+ # because the conda env's lib/ is never on the search path. Use find_library
54
+ # with an explicit dir (taken from the HDF5 pkg-config libdir) so both macOS
55
+ # and Linux get a resolved path, not a bare -l flag.
56
+ _hdf5_libdir = hdf5_c.get_variable(pkgconfig : 'libdir', default_value : '')
57
+ _hdf5_dirs = _hdf5_libdir != '' ? [_hdf5_libdir] : []
58
+ hdf5f = cc.find_library('hdf5_fortran', dirs : _hdf5_dirs, required : true)
59
+ hdf5f_hl = cc.find_library('hdf5_hl_fortran', dirs : _hdf5_dirs, required : true)
52
60
 
53
61
  # Optional: OpenMP
54
62
  # omp_dep = dependency('openmp', required : false)
@@ -110,9 +118,19 @@ configure_file(
110
118
  install_subdir(
111
119
  'mapflpy',
112
120
  install_dir : py.get_install_dir(),
121
+ exclude_directories : ['fortran'],
113
122
  exclude_files: [
114
123
  '_version.py.in',
115
124
  '__pycache__',
116
125
  '*.pyc',
117
126
  ]
118
127
  )
128
+
129
+ # Install mapflpy/fortran/__init__.py separately, so that the install_subdir
130
+ # above can exclude the entire fortran/ directory. Without this exclusion,
131
+ # any compiled .so left in the source tree (e.g. from a dev install) would
132
+ # be bundled into every wheel produced by "python -m build".
133
+ install_data(
134
+ 'mapflpy/fortran/__init__.py',
135
+ install_dir : py.get_install_dir() / 'mapflpy' / 'fortran',
136
+ )
@@ -70,7 +70,23 @@ def _build_env(session: nox.Session) -> Path:
70
70
  *pyproject["tool"][PROJECT_NAME].get("conda", []),
71
71
  channel="conda-forge"
72
72
  )
73
- session.env.update(_darwin_sdk_env())
73
+ env_dir = Path(session.env_dir).resolve()
74
+ lib_dir = str(env_dir / "lib")
75
+ # Override CONDA_PREFIX so conda-forge gfortran's spec file uses this env's
76
+ # lib dir for its implicit library search paths, not the active outer env.
77
+ env = {"CONDA_PREFIX": str(env_dir)}
78
+ env.update(_darwin_sdk_env())
79
+ if platform.system() == "Darwin":
80
+ # Pass an explicit rpath so delocate-wheel can bundle deps even when
81
+ # meson would otherwise embed a @loader_path-relative path.
82
+ env["LDFLAGS"] = (env.get("LDFLAGS", "") + f" -Wl,-rpath,{lib_dir}").strip()
83
+ # conda-forge's ld64_osx-64 linker may be invoked from the micromamba
84
+ # pkgs cache path (hard-linked), so @loader_path/../lib/libtapi.dylib
85
+ # resolves against the cache dir rather than the installed env. Set a
86
+ # fallback so dyld finds libtapi.dylib in the env's lib/ dir instead.
87
+ existing = os.environ.get("DYLD_FALLBACK_LIBRARY_PATH", "")
88
+ env["DYLD_FALLBACK_LIBRARY_PATH"] = f"{lib_dir}:{existing}".strip(":")
89
+ session.env.update(env)
74
90
 
75
91
 
76
92
  def _dist_env(session: nox.Session) -> Path:
@@ -16,7 +16,7 @@ requires = [
16
16
  # ----------------
17
17
  [project]
18
18
  name = "mapflpy"
19
- version = "1.1.9"
19
+ version = "1.2.0"
20
20
  description = "Fast field line tracing for spherical vector fields"
21
21
  authors = [
22
22
  {name = "Predictive Science Inc.", email = "webmaster@predsci.com"},
@@ -296,4 +296,4 @@ conda = [
296
296
  "pkg-config",
297
297
  "hdf5",
298
298
  "zlib"
299
- ]
299
+ ]