rapid-pe 0.0.4.dev20230213__tar.gz → 0.1.2.dev20251209__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.

Potentially problematic release.


This version of rapid-pe might be problematic. Click here for more details.

Files changed (31) hide show
  1. {rapid_pe-0.0.4.dev20230213 → rapid_pe-0.1.2.dev20251209}/PKG-INFO +22 -2
  2. rapid_pe-0.1.2.dev20251209/README.md +27 -0
  3. {rapid_pe-0.0.4.dev20230213 → rapid_pe-0.1.2.dev20251209}/bin/rapidpe_compute_intrinsic_grid +228 -27
  4. {rapid_pe-0.0.4.dev20230213 → rapid_pe-0.1.2.dev20251209}/bin/rapidpe_create_event_dag +25 -6
  5. {rapid_pe-0.0.4.dev20230213 → rapid_pe-0.1.2.dev20251209}/rapid_pe/__init__.py +1 -1
  6. {rapid_pe-0.0.4.dev20230213 → rapid_pe-0.1.2.dev20251209}/rapid_pe/amrlib.py +192 -85
  7. {rapid_pe-0.0.4.dev20230213 → rapid_pe-0.1.2.dev20251209}/rapid_pe/dagutils.py +82 -17
  8. {rapid_pe-0.0.4.dev20230213 → rapid_pe-0.1.2.dev20251209}/rapid_pe/lalsimutils.py +1 -1
  9. {rapid_pe-0.0.4.dev20230213 → rapid_pe-0.1.2.dev20251209}/rapid_pe/xmlutils.py +1 -1
  10. {rapid_pe-0.0.4.dev20230213 → rapid_pe-0.1.2.dev20251209}/rapid_pe.egg-info/PKG-INFO +23 -3
  11. rapid_pe-0.0.4.dev20230213/README.md +0 -21
  12. {rapid_pe-0.0.4.dev20230213 → rapid_pe-0.1.2.dev20251209}/COPYING +0 -0
  13. {rapid_pe-0.0.4.dev20230213 → rapid_pe-0.1.2.dev20251209}/bin/rapidpe_calculate_overlap +0 -0
  14. {rapid_pe-0.0.4.dev20230213 → rapid_pe-0.1.2.dev20251209}/bin/rapidpe_compute_intrinsic_fisher +0 -0
  15. {rapid_pe-0.0.4.dev20230213 → rapid_pe-0.1.2.dev20251209}/bin/rapidpe_integrate_extrinsic_likelihood +0 -0
  16. {rapid_pe-0.0.4.dev20230213 → rapid_pe-0.1.2.dev20251209}/bin/rapidpe_triangulation +0 -0
  17. {rapid_pe-0.0.4.dev20230213 → rapid_pe-0.1.2.dev20251209}/rapid_pe/common_cl.py +0 -0
  18. {rapid_pe-0.0.4.dev20230213 → rapid_pe-0.1.2.dev20251209}/rapid_pe/effectiveFisher.py +0 -0
  19. {rapid_pe-0.0.4.dev20230213 → rapid_pe-0.1.2.dev20251209}/rapid_pe/factored_likelihood.py +0 -0
  20. {rapid_pe-0.0.4.dev20230213 → rapid_pe-0.1.2.dev20251209}/rapid_pe/mcsampler.py +0 -0
  21. {rapid_pe-0.0.4.dev20230213 → rapid_pe-0.1.2.dev20251209}/rapid_pe/sph_harmonics.py +0 -0
  22. {rapid_pe-0.0.4.dev20230213 → rapid_pe-0.1.2.dev20251209}/rapid_pe/statutils.py +0 -0
  23. {rapid_pe-0.0.4.dev20230213 → rapid_pe-0.1.2.dev20251209}/rapid_pe/synchlib.py +0 -0
  24. {rapid_pe-0.0.4.dev20230213 → rapid_pe-0.1.2.dev20251209}/rapid_pe/tests/__init__.py +0 -0
  25. {rapid_pe-0.0.4.dev20230213 → rapid_pe-0.1.2.dev20251209}/rapid_pe/tests/test_common_cl.py +0 -0
  26. {rapid_pe-0.0.4.dev20230213 → rapid_pe-0.1.2.dev20251209}/rapid_pe.egg-info/SOURCES.txt +0 -0
  27. {rapid_pe-0.0.4.dev20230213 → rapid_pe-0.1.2.dev20251209}/rapid_pe.egg-info/dependency_links.txt +0 -0
  28. {rapid_pe-0.0.4.dev20230213 → rapid_pe-0.1.2.dev20251209}/rapid_pe.egg-info/requires.txt +0 -0
  29. {rapid_pe-0.0.4.dev20230213 → rapid_pe-0.1.2.dev20251209}/rapid_pe.egg-info/top_level.txt +0 -0
  30. {rapid_pe-0.0.4.dev20230213 → rapid_pe-0.1.2.dev20251209}/setup.cfg +0 -0
  31. {rapid_pe-0.0.4.dev20230213 → rapid_pe-0.1.2.dev20251209}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: rapid_pe
3
- Version: 0.0.4.dev20230213
3
+ Version: 0.1.2.dev20251209
4
4
  Summary: RapidPE: The original low-latency gravitational wave parameter estimation code.
5
5
  Home-page: https://git.ligo.org/rapidpe-rift/rapidpe/
6
6
  License: GPL-2+
@@ -19,6 +19,26 @@ Classifier: Topic :: Scientific/Engineering
19
19
  Classifier: Topic :: Scientific/Engineering :: Astronomy
20
20
  Classifier: Topic :: Scientific/Engineering :: Physics
21
21
  License-File: COPYING
22
+ Requires-Dist: bilby
23
+ Requires-Dist: h5py
24
+ Requires-Dist: healpy
25
+ Requires-Dist: lalsuite
26
+ Requires-Dist: ligo.skymap
27
+ Requires-Dist: lscsoft-glue
28
+ Requires-Dist: matplotlib
29
+ Requires-Dist: numpy
30
+ Requires-Dist: python-ligo-lw<1.9,>=1.8.1
31
+ Requires-Dist: scikit-learn
32
+ Requires-Dist: scipy
33
+ Requires-Dist: six
34
+ Dynamic: classifier
35
+ Dynamic: description
36
+ Dynamic: home-page
37
+ Dynamic: license
38
+ Dynamic: license-file
39
+ Dynamic: project-url
40
+ Dynamic: requires-dist
41
+ Dynamic: summary
22
42
 
23
43
  RapidPE was the first piece of software written for rapidly measuring the
24
44
  parameters of compact binary mergers observed via gravitational waves. It
@@ -0,0 +1,27 @@
1
+ # RapidPE
2
+
3
+ The original low-latency gravitational wave parameter estimation code.
4
+
5
+ # Installation
6
+
7
+ The typical way to run RapidPE is as part of the [RapidPE--RIFT Pipeline](https://git.ligo.org/rapidpe-rift/rapidpe-rift-pipe). If you do want to install RapidPE directly, you can install it in one of three ways
8
+ ```bash
9
+ # Install from Conda (recommended)
10
+ conda install rapid-pe
11
+ ```
12
+
13
+ ```bash
14
+ # Install from PyPI
15
+ pip install rapid-pe
16
+ ```
17
+
18
+ ```bash
19
+ # Install from source
20
+ git clone https://git.ligo.org/rapidpe-rift/rapidpe
21
+ cd rapidpe
22
+ pip install .
23
+ ```
24
+
25
+ # Credit
26
+
27
+ Copyright (c) 2012-2023 [RapidPE Contributors](CONTRIBUTORS.md)
@@ -28,12 +28,17 @@ import glob
28
28
  import json
29
29
  import bisect
30
30
  import re
31
+ import math
32
+ import operator
33
+ import warnings
31
34
  from collections import defaultdict
35
+ from functools import reduce
32
36
  from argparse import ArgumentParser
33
37
  from copy import copy
34
38
 
35
39
  import h5py
36
40
  import numpy
41
+ import numpy as np
37
42
  from scipy.special import binom
38
43
  from sklearn.neighbors import BallTree
39
44
 
@@ -217,7 +222,9 @@ grid_section.add_argument("--output-xml-file-name",default="", help="Set the nam
217
222
  grid_section.add_argument("-t", "--tmplt-bank", help="XML file with template bank.")
218
223
  grid_section.add_argument("-O", "--use-overlap", action="append",help="Use overlap information to define 'closeness'. If a list of files is given, the script will find the file with the closest template, and select nearby templates only from that file.")
219
224
  grid_section.add_argument("-T", "--overlap-threshold", default=0.9,type=float, help="Threshold on overlap value.")
220
- grid_section.add_argument("-s", "--points-per-side", type=int, default=10, help="Number of points per side, default is 10.")
225
+ point_specification = grid_section.add_mutually_exclusive_group()
226
+ point_specification.add_argument("-s", "--points-per-side", type=int, help="Number of points per side.")
227
+ point_specification.add_argument("--total-points", type=int, help="Requested total number of points in initial grid. Note that actual number will only approximate this.")
221
228
  grid_section.add_argument("-I", "--initial-region", action="append", help="Override the initial region with a custom specification. Specify multiple times like, -I mass1=1.0,2.0 -I mass2=1.0,1.5")
222
229
  grid_section.add_argument("-D", "--deactivate", action="store_true", help="Deactivate cells initially which have no template within them.")
223
230
  grid_section.add_argument("-P", "--prerefine", help="Refine this initial grid based on overlap values.")
@@ -235,6 +242,13 @@ opts = argp.parse_args()
235
242
  if not (opts.setup or opts.refine or opts.prerefine):
236
243
  exit("Either --setup or --refine or --prerefine must be chosen")
237
244
 
245
+ if opts.distance_coordinates == "mu1_mu2_q_s2z":
246
+ warnings.warn(
247
+ "--distance-coordinates=mu1_mu2_q_s2z has been deprecated, please use "
248
+ "--distance-coordinates=mu1_mu2_q_spin2z instead"
249
+ )
250
+ opts.distance_coordinates = "mu1_mu2_q_spin2z"
251
+
238
252
 
239
253
  # Hopefully the point is already present and we can just get it, otherwise it
240
254
  # could incur an overlap calculation, or suffer from the effects of being close
@@ -257,7 +271,7 @@ if not "spin1z" in intr_prms or not "spin2z" in intr_prms:
257
271
  else:
258
272
  sys.exit("spin1z or spin2z is specified but not the other spin. compute intrinsic grid is not setup to search just one")
259
273
  else:
260
- if opts.distance_coordinates == "mu1_mu2_q_s2z":
274
+ if opts.distance_coordinates == "mu1_mu2_q_spin2z":
261
275
  spin_transform = opts.distance_coordinates
262
276
  else:
263
277
  spin_transform = "chi_z"
@@ -421,21 +435,37 @@ if opts.result_file:
421
435
  #
422
436
  # Build (or retrieve) the initial region
423
437
  #
438
+ apply_truncation = False # Set to true if initial region truncation is needed
424
439
  if opts.refine or opts.prerefine:
425
- init_region, region_labels = amrlib.load_init_region(opts.refine or opts.prerefine, get_labels=True)
440
+ grid_init_region, region_labels = amrlib.load_init_region(
441
+ opts.refine or opts.prerefine, get_labels=True,
442
+ )
426
443
  else:
444
+ points_per_side = opts.points_per_side
445
+ if opts.total_points is not None:
446
+ points_per_side = math.ceil(opts.total_points ** (1./len(intr_prms)))
447
+
427
448
  ####### BEGIN INITIAL GRID CODE #########
428
449
  if opts.initial_region is None:
429
450
  #This is the only time anything from the overlap file is used anywhere
430
- init_region, idx = determine_region(pt, pts, ovrlp, opts.overlap_threshold, expand_prms)
451
+ grid_init_region, idx = determine_region(
452
+ pt, pts, ovrlp, opts.overlap_threshold, expand_prms
453
+ )
431
454
  region_labels = intr_prms
432
455
  # print "init trgion",len(pts[idx:])
433
456
  # FIXME: To be reimplemented in a different way
434
457
  #if opts.expand_param is not None:
435
- #expand_param(init_region, opts.expand_param)
458
+ #expand_param(grid_init_region, opts.expand_param)
459
+
436
460
  else:
437
461
  # Override initial region -- use with care
438
- _, init_region = common_cl.parse_param(opts.initial_region)
462
+ _, boundary_init_region = common_cl.parse_param(opts.initial_region)
463
+
464
+ if len(boundary_init_region) != len(intr_prms):
465
+ raise ValueError(
466
+ "Boundary and gridding coordinate systems must have the same "
467
+ "number of dimensions."
468
+ )
439
469
 
440
470
  # HACK: These labels do not actually match the data, but they mis-match
441
471
  # in a way that is self-consistent with the rest of the code.
@@ -446,12 +476,6 @@ else:
446
476
  param_mapping = {}
447
477
 
448
478
  def map_param(source, target):
449
- if source not in init_region:
450
- # User forgot to provide the required -I parameter
451
- raise ValueError(
452
- f"Parameter {source} must be provided by -I or "
453
- "--initial-region argument for the chosen coordinate system"
454
- )
455
479
  try:
456
480
  # Indicate that the input parameter maps to the given index
457
481
  param_mapping[source] = region_labels.index(target)
@@ -463,7 +487,7 @@ else:
463
487
  "from the intrinsic parameters"
464
488
  )
465
489
 
466
- if opts.distance_coordinates == "mu1_mu2_q_s2z":
490
+ if opts.distance_coordinates == "mu1_mu2_q_spin2z":
467
491
  map_param("mu1", "mass1")
468
492
  map_param("mu2", "mass2")
469
493
  map_param("q", "spin1z")
@@ -495,17 +519,187 @@ else:
495
519
  else:
496
520
  raise ValueError(f"Unknown spin transform {spin_transform}")
497
521
 
498
- if len(param_mapping) != len(init_region):
522
+ if len(param_mapping) != len(boundary_init_region):
499
523
  raise ValueError(
500
- f"Provided -I parameters {set(init_region.keys())}, but "
524
+ f"Provided -I parameters {set(boundary_init_region.keys())}, but "
501
525
  f"expected parameters {set(param_mapping.keys())}"
502
526
  )
503
527
 
504
- # Map the input ranges into an amrlib.Cell
505
- init_region_array = numpy.empty((len(region_labels), 2))
506
- for param_name, index in param_mapping.items():
507
- init_region_array[index] = init_region[param_name]
508
- init_region = amrlib.Cell(init_region_array)
528
+ # ### OLD ###
529
+ # NOTE: init_region has been re-named grid_init_region or
530
+ # boundary_init_region depending on coordinates
531
+
532
+ # # Map the input ranges into an amrlib.Cell
533
+ # init_region_array = numpy.empty((len(region_labels), 2))
534
+ # for param_name, index in param_mapping.items():
535
+ # init_region_array[index] = init_region[param_name]
536
+ # init_region = amrlib.Cell(init_region_array)
537
+
538
+ ### END ###
539
+
540
+ # There are two sets of coordinates:
541
+ # - the prior-boundary coordinates: `boundary_init_region`
542
+ # - the coordinates in which the grid is rectilinear:
543
+ # `distance_coordinates` and `spin_transform`
544
+ # which we'll call the "grid coordinates"
545
+ #
546
+ # Here we use a dense grid in the prior-boundary coordinates to
547
+ # approximately find the limits of the grid coordinates. In all but
548
+ # one case, the spin coordinates are the same for both, so we only worry
549
+ # about the mass coordinates.
550
+
551
+ if boundary_init_region.keys() == {"mu1", "mu2", "q", "spin2z"}:
552
+ # mu1-mu2-q-spin2z is a special case, where mass and spin are
553
+ # combined through mu1, mu2, and spin2z, so we have to consider
554
+ # everything.
555
+ boundary_check_mass_coordinate_names = boundary_init_region.keys()
556
+ spin_transform_bound = spin_transform
557
+ else:
558
+ # The standard case has 2 mass coordinates along with chieff, chia,
559
+ # so we strip out the two spins as they will not impact the
560
+ # boundaries.
561
+ boundary_check_mass_coordinate_names = (
562
+ boundary_init_region.keys() - {"chieff", "chia"}
563
+ )
564
+ assert len(boundary_check_mass_coordinate_names) == 2, \
565
+ f"Expected 2 mass parameters, but got: " + \
566
+ ", ".join(boundary_check_mass_coordinate_names)
567
+
568
+ spin_transform_bound = None if spin_transform is None else 'chi_z'
569
+
570
+
571
+ # Get a string representation of the prior-boundary coordinates
572
+ boundary_check_mass_coordinates = "_".join(
573
+ boundary_check_mass_coordinate_names
574
+ )
575
+ # Get the limits of the prior boundary, turning param_name -> [min, max]
576
+ # into a list of each [min, max]
577
+ boundary_values = list(boundary_init_region.values())
578
+
579
+ # Determine how dense to make the prior-boundary coordinate grids for
580
+ # bounds checking. While we'd like to go very dense, in higher
581
+ # dimensions this becomes computationally infeasible. In the future we
582
+ # should take a smarter approach than we do now.
583
+ if len(boundary_init_region.keys())>2:
584
+ warnings.warn('4 dimensions in initial boundary region. To reduce '
585
+ 'computation, using only 7 points per dimension '
586
+ 'to find the initial region boundaries.'
587
+ )
588
+ boundary_check_points_per_side = 7
589
+ else:
590
+ boundary_check_points_per_side = 50
591
+
592
+ # Construct 1-D grids along each of the prior-boundary coordinate axes
593
+ boundary_check_1d_grids = [
594
+ np.linspace(lower_bound, upper_bound,
595
+ boundary_check_points_per_side)
596
+ for lower_bound, upper_bound in boundary_values
597
+ ]
598
+ # Convert 1-D grids to a dense mesh grid
599
+ boundary_check_meshgrids = np.asarray(
600
+ np.meshgrid(*boundary_check_1d_grids, indexing="ij")
601
+ ).T
602
+
603
+ # Transform mesh grid in prior-boundary coordinates...
604
+ #
605
+ # ...first from prior-boundary coordinates to m1, m2[, spin1z, spin2z]
606
+ boundary_check_converted = amrlib.apply_inv_transform(
607
+ boundary_check_meshgrids,
608
+ intr_prms,
609
+ mass_transform=boundary_check_mass_coordinates,
610
+ spin_transform=spin_transform_bound,
611
+ )
612
+ # ...then from m1, m2[, spin1z, spin2z] to the grid coordinates.
613
+ boundary_check_converted = amrlib.apply_transform(
614
+ boundary_check_converted,
615
+ intr_prms,
616
+ mass_transform=opts.distance_coordinates,
617
+ spin_transform=spin_transform,
618
+ )
619
+
620
+ # Create a column matrix with the min and max of each grid coordinate
621
+ #
622
+ # [ min(grid_coord[0]), max(grid_coord[0]) ]
623
+ # [ ... , ... ]
624
+ # [ min(grid_coord[N]), max(grid_coord[N]) ]
625
+ axes_to_min_max = tuple(range(0, len(region_labels)))
626
+ grid_init_region_boundaries = np.column_stack((
627
+ np.nanmin(boundary_check_converted, axis=axes_to_min_max),
628
+ np.nanmax(boundary_check_converted, axis=axes_to_min_max),
629
+ ))
630
+
631
+ # Convert matrix into the initial AMR Cell
632
+ grid_init_region = amrlib.Cell(grid_init_region_boundaries)
633
+
634
+ # AMR requires `points_per_side`, but if `opts.total_points` was
635
+ # specified, and truncation may have occurred due to the different
636
+ # prior-boundary and grid coordinates, we need to find the minimal
637
+ # `points_per_side` that meets our `opts.total_points` requirement.
638
+ if opts.total_points is not None:
639
+ apply_truncation = True # Ensures truncation applied later
640
+ while True:
641
+ # Construct 1-D grids along each of the grid coordinate axes
642
+ total_points_check_1d_grids = [
643
+ np.linspace(lower_bound, upper_bound, points_per_side)
644
+ for lower_bound, upper_bound in grid_init_region_boundaries
645
+ ]
646
+ # Convert 1-D grids to a dense mesh grid
647
+ total_points_check_meshgrids = np.column_stack(
648
+ tuple(mesh.flatten() for mesh in np.meshgrid(
649
+ *total_points_check_1d_grids, indexing="ij")
650
+ ))
651
+ # Compute mask array which is True where the corresponding
652
+ # grid value is physical. Skipping this could cause issues
653
+ # with transformation functions.
654
+ bounds_mask = amrlib.check_grid(
655
+ total_points_check_meshgrids,
656
+ intr_prms,
657
+ opts.distance_coordinates,
658
+ )
659
+
660
+ # Transform mesh grid in grid coordinates...
661
+ #
662
+ # ...first from grid coordinates to m1, m2[, spin1z, spin2z]
663
+ total_points_check_converted = amrlib.apply_inv_transform(
664
+ total_points_check_meshgrids[bounds_mask],
665
+ intr_prms,
666
+ mass_transform=opts.distance_coordinates,
667
+ spin_transform=spin_transform,
668
+ )
669
+ # ...then from m1, m2[, spin1z, spin2z] to the prior-boundary
670
+ # coordinates.
671
+ total_points_check_converted = amrlib.apply_transform(
672
+ total_points_check_converted,
673
+ intr_prms,
674
+ mass_transform=boundary_check_mass_coordinates,
675
+ spin_transform=spin_transform_bound,
676
+ )
677
+ # Determine whether each point is within the prior-boundary
678
+ # limits
679
+ total_points_check_is_in_bounds = reduce(operator.and_, [
680
+ (lower_bound <= points) & (points <= upper_bound)
681
+ for points, (lower_bound, upper_bound)
682
+ in zip(total_points_check_converted.T, boundary_values)
683
+ ])
684
+ # Count the number of points which were in our prior boundary,
685
+ total_points = np.count_nonzero(total_points_check_is_in_bounds)
686
+ # If the count meets the `opts.total_points` requirement then
687
+ # we're done, otherwise try again with one more point per side.
688
+ if total_points >= opts.total_points:
689
+ break
690
+ else:
691
+ points_per_side += 1
692
+
693
+ # Later we'll want to re-use the boundary checking we've already
694
+ # done. We need to know the indices in
695
+ # `total_points_check_meshgrids` which were both physical and within
696
+ # the prior bounds. For that we need to combine the information in
697
+ # `bounds_mask` and `total_points_check_is_in_bounds`.
698
+ truncation_mask = (
699
+ np.zeros_like(total_points_check_meshgrids[:,0], dtype=bool)
700
+ )
701
+ truncation_mask[bounds_mask] = total_points_check_is_in_bounds
702
+
509
703
 
510
704
  # Old code below: used incorrect labeling
511
705
  # region_labels = init_region.keys()
@@ -513,8 +707,11 @@ else:
513
707
 
514
708
  # TODO: Alternatively, check density of points in the region to determine
515
709
  # the points to a side
516
- grid, spacing = amrlib.create_regular_grid_from_cell(init_region, side_pts=opts.points_per_side // 2, return_cells=True)
517
-
710
+ grid, spacing = amrlib.create_regular_grid_from_cell(
711
+ grid_init_region, side_pts=points_per_side,
712
+ return_cells=True,
713
+ )
714
+
518
715
  # "Deactivate" cells not close to template points
519
716
  # FIXME: This gets more and more dangerous in higher dimensions
520
717
  # FIXME: Move to function
@@ -537,8 +734,8 @@ intr_prms = list(region_labels)
537
734
  if opts.refine or opts.prerefine:
538
735
  res_pts = res_pts[:,reindex]
539
736
 
540
- extent_str = " ".join("(%f, %f)" % bnd for bnd in map(tuple, init_region._bounds))
541
- center_str = " ".join(map(str, init_region._center))
737
+ extent_str = " ".join("(%f, %f)" % bnd for bnd in map(tuple, grid_init_region._bounds))
738
+ center_str = " ".join(map(str, grid_init_region._center))
542
739
  label_str = ", ".join(region_labels)
543
740
  print("Initial region (" + label_str + ") has center " + center_str + " and extent " + extent_str)
544
741
 
@@ -567,13 +764,13 @@ if opts.prerefine:
567
764
  results = results[pt_select]
568
765
  grid, spacing = amrlib.refine_regular_grid(selected, spacing, return_cntr=True)
569
766
 
570
- else:
767
+ elif opts.refine:
571
768
  # print "selected",len(selected)
572
769
  grid, spacing = amrlib.refine_regular_grid(selected, spacing, return_cntr=opts.setup)
573
770
  # print "refine grid",len(grid)
574
771
 
575
772
  print("%d cells after refinement" % len(grid))
576
- grid = amrlib.prune_duplicate_pts(grid, init_region._bounds, spacing)
773
+ grid = amrlib.prune_duplicate_pts(grid, grid_init_region._bounds, spacing)
577
774
  #print "prune grid",len(grid)
578
775
  #
579
776
  # Clean up
@@ -581,6 +778,10 @@ grid = amrlib.prune_duplicate_pts(grid, init_region._bounds, spacing)
581
778
 
582
779
  grid = numpy.array(grid)
583
780
  bounds_mask = amrlib.check_grid(grid, intr_prms, opts.distance_coordinates)
781
+
782
+ if apply_truncation:
783
+ bounds_mask &= truncation_mask.flatten()
784
+
584
785
  grid = grid[bounds_mask]
585
786
  print("%d cells after bounds checking" % len(grid))
586
787
 
@@ -594,7 +795,7 @@ grid = amrlib.apply_inv_transform(grid, intr_prms, opts.distance_coordinates,spi
594
795
  cells = amrlib.grid_to_cells(grid, spacing)
595
796
  if opts.setup:
596
797
  hdf_filename = opts.setup+".hdf" if not ".hdf" in opts.setup else opts.setup
597
- grid_group = amrlib.init_grid_hdf(init_region, hdf_filename, opts.overlap_threshold, opts.distance_coordinates, intr_prms=intr_prms)
798
+ grid_group = amrlib.init_grid_hdf(grid_init_region, hdf_filename, opts.overlap_threshold, opts.distance_coordinates, intr_prms=intr_prms)
598
799
  level = amrlib.save_grid_cells_hdf(grid_group, cells, "mass1_mass2", intr_prms=intr_prms)
599
800
  else:
600
801
  grp = amrlib.load_grid_level(opts.refine, None)
@@ -25,6 +25,7 @@ from __future__ import print_function
25
25
  import sys
26
26
  import os
27
27
  import ast
28
+ import json
28
29
  import re
29
30
  from argparse import ArgumentParser
30
31
 
@@ -60,10 +61,13 @@ argp.add_argument("--write-script", action="store_true", help="In addition to th
60
61
  argp.add_argument("--write-eff-lambda", action="store_true", help="Use psi0 column of template bank XML as effective lambda point to calculate in DAG.")
61
62
  argp.add_argument("--write-deff-lambda", action="store_true", help="Use psi3 column of template bank XML as delta effective lambda point to calculate in DAG.")
62
63
  argp.add_argument("--condor-command", action="append", help="Append these condor commands to the submit files. Useful for account group information.")
64
+ argp.add_argument("--accounting-group-user",default=None, help="accounting-group-user for condor")
63
65
  argp.add_argument("--exe-integrate-likelihood", default=None, help="This is executable to use to integrate the extrinsic likelihood per intrinsic grid point. It will default to the lalsuite rapidpe_integrate_extrinsic_likelihood.")
64
66
  argp.add_argument("--integration-args-dict", default="", help="Pass these options as the kwargs input of the integrate dag creation function. They will be set, without editing, as input to the integration exe. If you use this, it will not be possible to also pass other command line arguments to the integration executable via the create_event_dag command line.")
67
+ argp.add_argument("--getenv", nargs="+", help="JSON encoded list of environment variable names to pass to Condor.")
68
+ argp.add_argument("--environment", help="JSON encoded dictionary of environment variables to pass to Condor.")
65
69
  argp.add_argument("--iteration-level", default=None, help="integer denoting the iteration level")
66
-
70
+ argp.add_argument("--cProfile", action="store_true", help="if True, cProfile each ILE job.")
67
71
 
68
72
  for cat, val in MAXJOBS.items():
69
73
  optname = "--maxjobs-%s" % cat.lower().replace("_", "-")
@@ -84,7 +88,13 @@ if not opts.template_bank_xml:
84
88
  condor_commands = None
85
89
  if opts.condor_command is not None:
86
90
  condor_commands = dict([c.split("=") for c in opts.condor_command])
91
+ if opts.accounting_group_user:
92
+ condor_commands['accounting_group_user'] = opts.accounting_group_user
87
93
 
94
+ if opts.environment is None:
95
+ environment_dict = None
96
+ else:
97
+ environment_dict = json.loads(opts.environment)
88
98
 
89
99
  #
90
100
  # Get trigger information from coinc xml file
@@ -125,14 +135,14 @@ if tmplt_bnk is None:
125
135
  use_bayespe_postproc = False
126
136
 
127
137
  # initialize the analysis subdag
128
- dag = pipeline.CondorDAG(log=os.getcwd())
138
+ dag = pipeline.CondorDAG(log=opts.log_directory)
129
139
 
130
140
  if opts.maxjobs_ile is not None:
131
141
  dag.add_maxjobs_category("ILE", opts.maxjobs_ile)
132
142
 
133
143
  # This is a subdag used for all our plotting and postproc so they don't block
134
144
  # completion of an individual event's ILEs
135
- ppdag = pipeline.CondorDAG(log=os.getcwd())
145
+ ppdag = pipeline.CondorDAG(log=opts.log_directory)
136
146
  ppdag.add_maxjobs_category("SQL", MAXJOBS["SQL"])
137
147
  ppdag.add_maxjobs_category("PLOT", MAXJOBS["PLOT"])
138
148
 
@@ -167,6 +177,8 @@ if opts.integration_args_dict != "":
167
177
  ncopies=opts.n_copies,
168
178
  output_file=opts.output_file,
169
179
  iteration_level=opts.iteration_level,
180
+ getenv=opts.getenv,
181
+ environment_dict=environment_dict,
170
182
  **ast.literal_eval(opts.integration_args_dict)
171
183
  )
172
184
 
@@ -194,6 +206,8 @@ else:
194
206
  save_samples=opts.save_samples,
195
207
  output_file=opts.output_file,
196
208
  iteration_level=opts.iteration_level,
209
+ getenv=opts.getenv,
210
+ environment_dict=environment_dict,
197
211
  n_eff=opts.n_eff,
198
212
  n_max=opts.n_max,
199
213
  ncopies=opts.n_copies,
@@ -204,17 +218,22 @@ else:
204
218
  skymap_file=(opts.skymap_file or False),
205
219
  distance_maximum=opts.distance_maximum,
206
220
  )
221
+
222
+ ile_sub_name = os.path.join(opts.working_directory,ile_sub_name)
223
+ ile_job_type.set_sub_file(ile_sub_name)
207
224
  ile_job_type.write_sub_file()
208
225
 
226
+
227
+
209
228
  with open(ile_sub_name,'r') as integrate_sub_file:
210
229
  integrate_subfile_content = integrate_sub_file.read()
211
230
 
212
- if 'cProfile' in ast.literal_eval(opts.integration_args_dict):
231
+ if opts.cProfile:
213
232
  uniq_str = "$(cluster)-$(process)"
214
233
  cprofile_stats_file = os.path.join(opts.log_directory, f'cprofile_integrate-{uniq_str}')
215
234
  replacement_pattern = rf'\1 -m cProfile -o {cprofile_stats_file}-\3.out {exe} \2\3\4'
216
235
  else:
217
- replacement_pattern = rf'\1{exe} \2\3\4'
236
+ replacement_pattern = rf'\1 {exe} \2\3\4'
218
237
 
219
238
  replaced_integrate_subfile_content = re.sub(r'^(\s*arguments\s*=\s*\")(.*)(ILE_iteration_[0-9]+)(.*)$', replacement_pattern,integrate_subfile_content,flags = re.MULTILINE)
220
239
 
@@ -224,7 +243,7 @@ with open(ile_sub_name,'w') as integrate_sub_file:
224
243
  if use_bayespe_postproc:
225
244
  if not os.path.exists(opts.web_output):
226
245
  os.makedirs(opts.web_output)
227
- bpp_plot_job_type, bpp_plot_job_name = dagutils.write_bayes_pe_postproc_sub(tag="bayes_pp_plot", log_dir=opts.log_directory, web_dir=opts.web_output)
246
+ bpp_plot_job_type, bpp_plot_job_name = dagutils.write_bayes_pe_postproc_sub(tag="bayes_pp_plot", log_dir=opts.log_directory, web_dir=opts.web_output, getenv=opts.getenv, environment_dict=environment_dict)
228
247
  bpp_plot_job_type.write_sub_file()
229
248
  bpp_plot_node = pipeline.CondorDAGNode(bpp_plot_job_type)
230
249
  bpp_plot_node.set_category("PLOT")
@@ -1,3 +1,3 @@
1
1
  # -*- coding: utf-8 -*-
2
2
 
3
- __version__ = "0.0.4"
3
+ __version__ = "0.1.2"
@@ -1,15 +1,16 @@
1
+ import functools
1
2
  import itertools
2
3
  import copy
3
4
 
4
5
  import numpy
6
+ import numpy as np
5
7
  import h5py
8
+ from scipy.optimize import bisect
6
9
 
7
10
  import lal
8
11
 
9
12
  from . import lalsimutils
10
13
 
11
- m1m2 = numpy.vectorize(lalsimutils.m1m2)
12
-
13
14
  #
14
15
  # Utility functions
15
16
  #
@@ -486,7 +487,7 @@ def transform_m1m2_mceta(m1, m2):
486
487
  return lalsimutils.Mceta(m1, m2)
487
488
 
488
489
  def transform_mceta_m1m2(mc, eta):
489
- return m1m2(mc, eta)
490
+ return lalsimutils.m1m2(mc, eta)
490
491
 
491
492
  __prefac_0 = 5. / 256 / numpy.pi
492
493
  __prefac_3 = 1. / 8
@@ -501,7 +502,7 @@ __prefac_tau = 5. / 32 / numpy.pi
501
502
  def transform_tau0tau3_m1m2(tau0, tau3, flow=40.):
502
503
  mt = __prefac_tau / numpy.pi / flow * tau3 / tau0
503
504
  eta = 1.0 / 8 / flow / tau3 * (tau0 / __prefac_tau / tau3)**(2./3)
504
- m1, m2 = m1m2(mt*eta**(3./5), eta)
505
+ m1, m2 = lalsimutils.m1m2(mt*eta**(3./5), eta)
505
506
  return m1 / __dim_mass, m2 / __dim_mass
506
507
 
507
508
  def transform_s1zs2z_chi(m1, m2, s1z, s2z):
@@ -532,6 +533,16 @@ def transform_mcq_m1m2(mc, q):
532
533
  m = mc * (1 + q)**(1./5)
533
534
  return m / q**(3./5), m * q**(2/5.)
534
535
 
536
+ def transform_mtotalq_m1m2(mtotal,q):
537
+ m1 = mtotal / (1.+q)
538
+ m2 = q*m1
539
+ return m1,m2
540
+
541
+ def transform_m1m2_mtotalq(m1,m2):
542
+ mtotal = m1+m2
543
+ q = numpy.min([m1, m2], axis=0) / numpy.max([m1, m2], axis=0)
544
+ return mtotal,q
545
+
535
546
  MsunToTime = 4.92659 * 10.0**(-6.0) # conversion from solar mass to seconds
536
547
  fref_mu = 200. # reference frequency to calculate mu1 and mu2 introduced in (TODO: add reference here once the paper is in arxiv)
537
548
  # coefficients of mu1 and mu2
@@ -583,31 +594,45 @@ def transform_m1m2s1zs2z_mu1mu2qs2z(m1, m2, s1z, s2z):
583
594
  s2z
584
595
  )
585
596
 
586
- def _cancel_psi3(mc, eta):
587
- return (mu_coeffs[0, 0] - mu_coeffs[1, 0] * mu_coeffs[0, 2] / mu_coeffs[1, 2]) * mcTopsi0(mc) + \
588
- (mu_coeffs[0, 1] - mu_coeffs[1, 1] * mu_coeffs[0, 2] / mu_coeffs[1, 2]) * mcetaTopsi2(mc, eta)
589
-
590
597
  def _mu1mu2etaTomc(mu1, mu2, eta):
591
- """Convert mu1, mu2, eta=m1*m2/(m1+m2)**2 into chirpmass using bisection search."""
592
- psi3 = mu1 - (mu_coeffs[0, 2] / mu_coeffs[1, 2]) * mu2
593
- mcmin = mcmax = (128. * mu1 / 3.)**(-3. / 5.) / (numpy.pi * fref_mu * MsunToTime)
594
- while _cancel_psi3(mcmax, eta) >= psi3:
595
- mcmax = mcmax * 2.
596
- while _cancel_psi3(mcmin, eta) < psi3:
597
- mcmin = mcmin * 0.5
598
- mcmid = (mcmin + mcmax) / 2.
599
- while ((mcmax - mcmin) / mcmin) > 10.**(-6.):
600
- if _cancel_psi3(mcmid, eta) > psi3:
601
- mcmin = mcmid
602
- else:
603
- mcmax = mcmid
604
- mcmid = (mcmin + mcmax) / 2.
605
- return mcmid
598
+ """Convert mu1, mu2, eta=m1*m2/(m1+m2)**2 into chirp mass.
599
+
600
+ mu1 and mu2 are given by linear combinations of psi0, psi2, and psi3:
601
+
602
+ .. math::
603
+
604
+ \mu^1 = k_0 \psi^0 + k_2 \psi^2 + k_3 \psi^3,
605
+ \mu^2 = l_0 \psi^0 + l_2 \psi^2 + l_3 \psi^3.
606
+
607
+ To cancel spin contributions, the following quantity is calculated:
608
+
609
+ .. math::
610
+
611
+ \alpha = \mu^1 - k_3 / l_3 \mu^2
612
+
613
+ Then, the following equation is solved with respect to chirpmass using bisection search:
614
+
615
+ .. math::
616
+
617
+ (k_0 - l_0 k_3 / l_3) \psi^0 + (k_2 - l_2 k_3 / l_3) \psi^2 = \alpha
618
+
619
+ """
620
+ alpha = mu1 - (mu_coeffs[0, 2] / mu_coeffs[1, 2]) * mu2
621
+ a = 3 / 128 * (mu_coeffs[0, 0] - mu_coeffs[1, 0] * mu_coeffs[0, 2] / mu_coeffs[1, 2])
622
+ b = 55 / 384 * (eta + 743 / 924) * eta**(-2 / 5) * (mu_coeffs[0, 1] - mu_coeffs[1, 1] * mu_coeffs[0, 2] / mu_coeffs[1, 2])
623
+ func = lambda y: a * y**5 + b * y**3 - alpha # y refers to (pi * mc * fref)^(-1 / 3)
624
+ # bisection search assuming a>0, b>0, and alpha>0
625
+ ymax = (alpha / a)**(1 / 5)
626
+ y = bisect(func, 0, ymax)
627
+ return 1 / (numpy.pi * fref_mu * MsunToTime * y**3)
628
+
606
629
 
607
630
  def mu1mu2etaTomc(mu1, mu2, eta):
608
631
  """Convert mu1, mu2, eta=m1*m2/(m1+m2)**2 into chirpmass using bisection search. This function allows array as input"""
609
632
  if type(mu1) is numpy.ndarray:
610
- return numpy.array([_mu1mu2etaTomc(_mu1, _mu2, _eta) for _mu1, _mu2, _eta in zip(mu1, mu2, eta)])
633
+ if mu1.size == 0:
634
+ return np.empty_like(mu1)
635
+ return numpy.vectorize(_mu1mu2etaTomc)(mu1, mu2, eta)
611
636
  else:
612
637
  return _mu1mu2etaTomc(mu1, mu2, eta)
613
638
 
@@ -622,9 +647,16 @@ def mu2mcetas2zTos1z(mu2, mc, eta, s2z):
622
647
  def transform_mu1mu2qs2z_m1m2s1zs2z(mu1, mu2, q, s2z):
623
648
  """mu1, mu2, q=m2/m1, z-component of secondary spin to component masses: m1, m2 and z-components of spins: s1z, s2z"""
624
649
  eta = qToeta(q)
625
- mc = mu1mu2etaTomc(mu1, mu2, eta)
650
+
651
+ eta_valid = (0 < eta) & (eta <= 0.25)
652
+
653
+ mc = numpy.full_like(eta, numpy.nan)
654
+ mc[eta_valid] = mu1mu2etaTomc(
655
+ mu1[eta_valid], mu2[eta_valid], eta[eta_valid],
656
+ )
657
+
626
658
  s1z = mu2mcetas2zTos1z(mu2, mc, eta, s2z)
627
- m1, m2 = m1m2(mc, eta)
659
+ m1, m2 = lalsimutils.m1m2(mc, eta)
628
660
  return m1, m2, s1z, s2z
629
661
 
630
662
  #
@@ -650,20 +682,33 @@ def check_mchirpeta(mchirp, eta):
650
682
 
651
683
  def check_mchirpq(mchirp, q):
652
684
  mchirp_arr = numpy.asarray(mchirp)
653
- q_arr = numpy.asarray(q)
654
- return (q_arr <= 0.25) & (q_arr > 0) & (mchirp_arr > 0)
685
+ return check_q(q) & (mchirp_arr > 0)
686
+
687
+ def check_mtotalq(mtotal, q):
688
+ mtotal_arr = numpy.asarray(mtotal)
689
+ return check_q(q) & (mtotal_arr > 0)
655
690
 
656
691
  def check_spins(spin):
657
692
  return numpy.sqrt(numpy.atleast_2d(spin**2).sum(axis=0)) <= 1
658
693
 
659
694
  def check_q(q):
660
- return (numpy.array(q)>0.)
695
+ q_arr = numpy.asarray(q)
696
+ return (q_arr > 0.) & (q_arr <= 1.)
661
697
 
662
698
  def check_mass1mass2(mass1,mass2):
663
699
  mass1_arr = numpy.array(mass1)
664
700
  mass2_arr = numpy.array(mass2)
665
701
  return (mass1_arr>mass2_arr) & (mass1_arr > 0.) & (mass2_arr > 0.)
666
702
 
703
+ BOUND_CHECK_MASS = {
704
+ "mchirp_eta": check_mchirpeta,
705
+ "mchirp_q": check_mchirpq,
706
+ "tau0_tau3": check_tau0tau3,
707
+ "mtotal_q": check_mtotalq,
708
+ "mass1_mass2": check_mass1mass2,
709
+ None: None
710
+ }
711
+
667
712
  # Make sure the new grid points are physical
668
713
  def check_grid(grid, intr_prms, distance_coordinates):
669
714
  """
@@ -672,20 +717,14 @@ def check_grid(grid, intr_prms, distance_coordinates):
672
717
  m1_axis, m2_axis = intr_prms.index("mass1"), intr_prms.index("mass2")
673
718
  grid_check = numpy.array(grid).T
674
719
  if distance_coordinates != 'mu1_mu2_q_s2z':
675
- if distance_coordinates == "tau0_tau3":
676
- bounds_mask = check_tau0tau3(grid_check[m1_axis], grid_check[m2_axis])
677
- elif distance_coordinates == "mchirp_eta":
678
- bounds_mask = check_mchirpeta(grid_check[m1_axis], grid_check[m2_axis])
679
- elif distance_coordinates == "mchirp_q":
680
- bounds_mask = check_mchirpq(grid_check[m1_axis], grid_check[m2_axis])
681
- elif distance_coordinates == "mass1_mass2":
682
- bounds_mask = check_mass1mass2(grid_check[m1_axis], grid_check[m2_axis])
683
- else:
720
+ try:
721
+ bound_check_func = BOUND_CHECK_MASS[distance_coordinates]
722
+ bounds_mask = bound_check_func(grid_check[m1_axis],grid_check[m2_axis])
723
+ except KeyError:
684
724
  raise NotImplementedError(
685
725
  f"Coordinate system {distance_coordinates} needs a bounds "
686
726
  f"check implemented."
687
727
  )
688
-
689
728
  # FIXME: Needs general spin
690
729
  if "spin1z" in intr_prms:
691
730
  s1_axis = intr_prms.index("spin1z")
@@ -697,7 +736,7 @@ def check_grid(grid, intr_prms, distance_coordinates):
697
736
  chi_axis = intr_prms.index("chi_z")
698
737
  bounds_mask &= check_spins(grid_check[chi_axis])
699
738
  else:
700
- # spin1z axis is replaced by values for q if distance_coordinates = mu1_mu2_q_s2z'
739
+ # spin1z axis is replaced by values for q if distance_coordinates = mu1_mu2_q_spin2z'
701
740
  # So, check_q takes values from spin1z axis
702
741
  s1_axis = intr_prms.index("spin1z")
703
742
  bounds_mask = check_q(grid_check[s1_axis])
@@ -706,10 +745,11 @@ def check_grid(grid, intr_prms, distance_coordinates):
706
745
  return bounds_mask
707
746
 
708
747
  VALID_TRANSFORMS_MASS = {
709
- "mchirp_eta": transform_m1m2_mceta,
710
- "mchirp_q": transform_m1m2_mcq,
711
- "tau0_tau3": transform_m1m2_tau0tau3,
712
- "mu1_mu2_q_s2z": transform_m1m2s1zs2z_mu1mu2qs2z,
748
+ frozenset({"mchirp","eta"}): transform_m1m2_mceta,
749
+ frozenset({"mchirp","q"}): transform_m1m2_mcq,
750
+ frozenset({"tau0","tau3"}): transform_m1m2_tau0tau3,
751
+ frozenset({"mtotal", "q"}): transform_m1m2_mtotalq,
752
+ frozenset({"mu1","mu2","q","spin2z"}): transform_m1m2s1zs2z_mu1mu2qs2z,
713
753
  None: None
714
754
  }
715
755
 
@@ -717,63 +757,130 @@ INVERSE_TRANSFORMS_MASS = {
717
757
  transform_m1m2_mceta: transform_mceta_m1m2,
718
758
  transform_m1m2_mcq: transform_mcq_m1m2,
719
759
  transform_m1m2_tau0tau3: transform_tau0tau3_m1m2,
760
+ transform_m1m2_mtotalq: transform_mtotalq_m1m2,
720
761
  transform_m1m2s1zs2z_mu1mu2qs2z: transform_mu1mu2qs2z_m1m2s1zs2z,
721
762
  None: None
722
763
  }
723
764
 
724
765
  def apply_transform(pts, intr_prms, mass_transform=None, spin_transform=None):
725
- ##% #FIXME TESTING
726
- ##% m1,m2 = 8.87543,16.149322
727
- ##% print "pre inv transofmr",m1,m2
728
- ##% t1,t2 = VALID_TRANSFORMS_MASS[mass_transform](m1,m2)
729
- ##% print "transform",t1,t2
730
- ##% print mass_transform
731
- ##% print "inv",INVERSE_TRANSFORMS_MASS[VALID_TRANSFORMS_MASS[mass_transform]](t1,t2)
732
- ##% assert 1==0
733
- ##%##%
734
- # You know what... recarrays are dumb, and so's your face.
735
- # FIXME: Why does numpy want me to repack this into tuples!?
736
- #tpts = numpy.array([tuple(pt) for pt in pts.T], dtype = numpy.dtype([(a ,"float64") for a in intr_prms]))
737
-
738
- m1_idx, m2_idx = intr_prms.index("mass1"), intr_prms.index("mass2")
739
- if mass_transform == "mu1_mu2_q_s2z":
740
- s1z_idx, s2z_idx = intr_prms.index("spin1z"), intr_prms.index("spin2z")
741
- pts[:,m1_idx], pts[:,m2_idx], pts[:,s1z_idx],pts[:,s2z_idx] = VALID_TRANSFORMS_MASS[mass_transform](pts[:,m1_idx], pts[:,m2_idx], pts[:,s1z_idx], pts[:,s2z_idx])
766
+ """
767
+ Transforms `pts` from `intr_prms` coordinates ("mass1", "mass2", and
768
+ optionally "spin1z" and "spin2z") into the system specified by
769
+ `mass_transform` and `spin_transform`. Transformation is performed
770
+ in-place.
771
+ """
772
+ mass_transform_name = mass_transform
773
+ if mass_transform is not None:
774
+ mass_transform = frozenset(mass_transform.split("_"))
775
+
776
+ m1_idx = intr_prms.index("mass1")
777
+ m2_idx = intr_prms.index("mass2")
778
+
779
+ if mass_transform == {"mu1", "mu2", "q", "spin2z"}:
780
+ s1z_idx = intr_prms.index("spin1z")
781
+ s2z_idx = intr_prms.index("spin2z")
782
+
783
+ mu1_idx = m1_idx
784
+ mu2_idx = m2_idx
785
+ q_idx = s1z_idx
786
+
787
+ (
788
+ pts[...,mu1_idx], pts[...,mu2_idx],
789
+ pts[...,q_idx], pts[...,s2z_idx],
790
+ ) = VALID_TRANSFORMS_MASS[mass_transform](
791
+ pts[...,m1_idx], pts[...,m2_idx],
792
+ pts[...,s1z_idx], pts[...,s2z_idx],
793
+ )
742
794
  else:
743
795
  if spin_transform:
744
796
  if spin_transform == "chi_z":
745
- s1z_idx, s2z_idx = intr_prms.index("spin1z"), intr_prms.index("spin2z")
746
- # chi_z = transform_s1zs2z_chi(pts[:,m1_idx], pts[:,m2_idx], pts[:,s1z_idx], pts[:,s2z_idx])
747
- #The grid is converted from m1 m2 s1 s2 to mc eta chi_eff because it's better to choose the closest grid points in mc eta chi_eff space.
748
- #chi_a is not considered when choosing the closes grid points, but we do need it to transform back to s1 s2
749
- pts[:,s1z_idx],pts[:,s2z_idx] = transform_s1zs2z_chi_eff_chi_a(pts[:,m1_idx], pts[:,m2_idx], pts[:,s1z_idx], pts[:,s2z_idx])
750
-
751
- # pts = numpy.vstack((pts.T, chi_eff)).T
752
- # intr_prms.append("chi_z")
753
-
754
- if mass_transform != "mass1_mass2":
755
- pts[:,m1_idx], pts[:,m2_idx] = VALID_TRANSFORMS_MASS[mass_transform](pts[:,m1_idx], pts[:,m2_idx])
756
-
797
+ s1z_idx = intr_prms.index("spin1z")
798
+ s2z_idx = intr_prms.index("spin2z")
799
+
800
+ chieff_idx = s1z_idx
801
+ chia_idx = s2z_idx
802
+
803
+ (
804
+ pts[...,chieff_idx], pts[...,chia_idx],
805
+ ) = transform_s1zs2z_chi_eff_chi_a(
806
+ pts[...,m1_idx], pts[...,m2_idx],
807
+ pts[...,s1z_idx], pts[...,s2z_idx],
808
+ )
809
+ else:
810
+ raise NotImplementedError(
811
+ f"Unknown spin_transform: {spin_transform}"
812
+ )
813
+
814
+ if mass_transform != {"mass1", "mass2"}:
815
+ mA_idx = m1_idx
816
+ mB_idx = m2_idx
817
+
818
+ (
819
+ pts[...,mA_idx], pts[...,mB_idx],
820
+ ) = VALID_TRANSFORMS_MASS[mass_transform](
821
+ pts[...,m1_idx], pts[...,m2_idx],
822
+ )
757
823
  # Independent transforms go here
758
824
 
759
825
  return pts
760
826
 
761
- def apply_inv_transform(pts, intr_prms, mass_transform=None,spin_transform=None):
762
- m1_idx, m2_idx = intr_prms.index("mass1"), intr_prms.index("mass2")
763
-
764
- if mass_transform == "mu1_mu2_q_s2z":
765
- s1z_idx, s2z_idx = intr_prms.index("spin1z"), intr_prms.index("spin2z")
766
- pts[:,m1_idx], pts[:,m2_idx], pts[:,s1z_idx],pts[:,s2z_idx] = INVERSE_TRANSFORMS_MASS[VALID_TRANSFORMS_MASS[mass_transform]](pts[:,m1_idx], pts[:,m2_idx], pts[:,s1z_idx], pts[:,s2z_idx])
827
+ def apply_inv_transform(pts, intr_prms,
828
+ mass_transform=None, spin_transform=None):
829
+ """
830
+ The inverse of `apply_transform`.
831
+ """
832
+ mass_transform_name = mass_transform
833
+ if mass_transform is not None:
834
+ mass_transform = frozenset(mass_transform.split("_"))
835
+
836
+ m1_idx = intr_prms.index("mass1")
837
+ m2_idx = intr_prms.index("mass2")
838
+
839
+ if mass_transform == {"mu1", "mu2", "q", "spin2z"}:
840
+ s1z_idx = intr_prms.index("spin1z")
841
+ s2z_idx = intr_prms.index("spin2z")
842
+
843
+ mu1_idx = m1_idx
844
+ mu2_idx = m2_idx
845
+ q_idx = s1z_idx
846
+
847
+ (
848
+ pts[...,m1_idx], pts[...,m2_idx],
849
+ pts[...,s1z_idx], pts[...,s2z_idx],
850
+ ) = INVERSE_TRANSFORMS_MASS[VALID_TRANSFORMS_MASS[mass_transform]](
851
+ pts[...,mu1_idx], pts[...,mu2_idx],
852
+ pts[...,q_idx], pts[...,s2z_idx],
853
+ )
767
854
  else:
768
- if mass_transform != "mass1_mass2":
769
- pts[:,m1_idx], pts[:,m2_idx] = INVERSE_TRANSFORMS_MASS[VALID_TRANSFORMS_MASS[mass_transform]](pts[:,m1_idx], pts[:,m2_idx])
770
-
855
+ mA_idx = m1_idx
856
+ mB_idx = m2_idx
857
+
858
+ if mass_transform != {"mass1", "mass2"}:
859
+ (
860
+ pts[...,m1_idx], pts[...,m2_idx],
861
+ ) = INVERSE_TRANSFORMS_MASS[VALID_TRANSFORMS_MASS[mass_transform]](
862
+ pts[...,mA_idx], pts[...,mB_idx],
863
+ )
864
+
771
865
  if spin_transform:
772
866
  if spin_transform == "chi_z":
773
- s1z_idx, s2z_idx = intr_prms.index("spin1z"), intr_prms.index("spin2z")
774
- pts[:,s1z_idx],pts[:,s2z_idx] =transform_chi_eff_chi_a_s1zs2z(pts[:,m1_idx], pts[:,m2_idx], pts[:,s1z_idx], pts[:,s2z_idx])
867
+ s1z_idx = intr_prms.index("spin1z")
868
+ s2z_idx = intr_prms.index("spin2z")
869
+
870
+ chieff_idx = s1z_idx
871
+ chia_idx = s2z_idx
872
+
873
+ (
874
+ pts[...,s1z_idx], pts[...,s2z_idx],
875
+ ) = transform_chi_eff_chi_a_s1zs2z(
876
+ pts[...,m1_idx], pts[...,m2_idx],
877
+ pts[...,chieff_idx], pts[...,chia_idx],
878
+ )
879
+ else:
880
+ raise NotImplementedError(
881
+ f"Unknown spin_transform: {spin_transform}"
882
+ )
775
883
 
776
-
777
884
  # Independent transforms go here
778
885
 
779
886
  return pts
@@ -21,6 +21,7 @@ A collection of routines to manage Condor workflows (DAGs).
21
21
  import warnings
22
22
  import os,ast
23
23
 
24
+ from pathlib import PurePath
24
25
  from glue import pipeline
25
26
 
26
27
  __author__ = "Evan Ochsner <evano@gravity.phys.uwm.edu>, Chris Pankow <pankow@gravity.phys.uwm.edu>"
@@ -42,8 +43,43 @@ def which(program):
42
43
 
43
44
  return None
44
45
 
46
+ def escape_quotes(s):
47
+ """
48
+ Escapes quotes in a string for use in a Condor submit file.
49
+
50
+ Any quote character is simply repeated twice, removing its special meaning
51
+ and making it behave as a literal quote character.
52
+ """
53
+ return s.replace('"', '""').replace("'", "''")
54
+
55
+ def format_getenv(getenv):
56
+ """
57
+ Produces the 'getenv' section's value for a Condor submit file, given a
58
+ list of environment variable names.
59
+ """
60
+ return ", ".join(getenv)
61
+
62
+ def format_environment(environment_dict):
63
+ """
64
+ Produces the 'environment' section's value for a Condor submit file, given
65
+ a dictionary of environment variable names and values.
66
+
67
+ This uses the 'new' format (space-delimited key=value pairs with
68
+ double-quotes enclosing the entire list), with literal quotes properly
69
+ escaped, and with single-quotes used to surround each value in case it
70
+ contains whitespace.
71
+ """
72
+ # Compute key='value' pairs, quoted properly
73
+ pairs = (f"{k}='{escape_quotes(v)}'"
74
+ for k, v in environment_dict.items())
75
+ # Join the pairs into a single string
76
+ contents = " ".join(pairs)
77
+ # Construct the final expression
78
+ return f'"{contents}"'
79
+
80
+
45
81
  # FIXME: Keep in sync with arguments of integrate_likelihood_extrinsic
46
- def write_integrate_likelihood_extrinsic_sub(tag='integrate', exe=None, log_dir=None, intr_prms=("mass1", "mass2"), ncopies=1, condor_commands=None, **kwargs):
82
+ def write_integrate_likelihood_extrinsic_sub(tag='integrate', exe=None, log_dir=None, intr_prms=("mass1", "mass2"), ncopies=1, condor_commands=None, getenv=None, environment_dict=None, **kwargs):
47
83
  """
48
84
  Write a submit file for launching jobs to marginalize the likelihood over
49
85
  extrinsic parameters.
@@ -98,8 +134,9 @@ def write_integrate_likelihood_extrinsic_sub(tag='integrate', exe=None, log_dir=
98
134
  #
99
135
  # Need to modify the output file so it's unique
100
136
  #
101
- ofname = kwargs["output-file"].split(".")
102
- ofname, ext = ofname[0], ".".join(ofname[1:])
137
+ ofname = PurePath(kwargs["output-file"])
138
+ ext = "".join(PurePath(ofname).suffixes[-2:])
139
+ ofname= kwargs["output-file"][:-len(ext)]
103
140
  ile_job.add_file_opt("output-file", "%s-%s.%s" % (ofname, uniq_str, ext))
104
141
  del kwargs["output-file"]
105
142
  #if kwargs.has_key("save-samples") and kwargs["save-samples"] == True:
@@ -107,8 +144,6 @@ def write_integrate_likelihood_extrinsic_sub(tag='integrate', exe=None, log_dir=
107
144
  ile_job.add_opt("save-samples", '')
108
145
  del kwargs["save-samples"]
109
146
 
110
- if "cProfile" in kwargs:
111
- del kwargs["cProfile"]
112
147
  del kwargs["iteration-level"]
113
148
  #
114
149
  # Add normal arguments
@@ -135,12 +170,17 @@ def write_integrate_likelihood_extrinsic_sub(tag='integrate', exe=None, log_dir=
135
170
  for p in intr_prms:
136
171
  ile_job.add_var_opt(p)
137
172
 
138
- ile_job.add_condor_cmd('getenv', 'True')
173
+ if getenv is not None and len(getenv) != 0:
174
+ ile_job.add_condor_cmd('getenv', format_getenv(getenv))
175
+ if environment_dict is not None and len(environment_dict) != 0:
176
+ ile_job.add_condor_cmd('environment',
177
+ format_environment(environment_dict))
139
178
  ile_job.add_condor_cmd('request_memory', '4096')
179
+ ile_job.add_condor_cmd('max_retries', '5')
140
180
  warnings.warn("Requesting hard-coded disk space for ILE job")
141
181
  ile_job.add_condor_cmd('request_disk', '1 GB')
142
182
  if 'gpu' in kwargs:
143
- ile_job.add_condor_cmd('request_GPUs', '1')
183
+ ile_job.add_condor_cmd('request_GPUs', '1')
144
184
  if condor_commands is not None:
145
185
  for cmd, value in condor_commands.items():
146
186
  ile_job.add_condor_cmd(cmd, value)
@@ -148,7 +188,7 @@ def write_integrate_likelihood_extrinsic_sub(tag='integrate', exe=None, log_dir=
148
188
 
149
189
  return ile_job, ile_sub_name
150
190
 
151
- def write_result_coalescence_sub(tag='coalesce', exe=None, log_dir=None, output_dir="./", use_default_cache=True):
191
+ def write_result_coalescence_sub(tag='coalesce', exe=None, log_dir=None, output_dir="./", use_default_cache=True, getenv=None, environment_dict=None):
152
192
  """
153
193
  Write a submit file for launching jobs to coalesce ILE output
154
194
  """
@@ -182,14 +222,19 @@ def write_result_coalescence_sub(tag='coalesce', exe=None, log_dir=None, output_
182
222
  sql_job.add_opt("tmp-space", tmpdir)
183
223
  sql_job.add_opt("verbose", '')
184
224
 
185
- sql_job.add_condor_cmd('getenv', 'True')
225
+ if getenv is not None and len(getenv) != 0:
226
+ sql_job.add_condor_cmd('getenv', format_getenv(getenv))
227
+ if environment_dict is not None and len(environment_dict) != 0:
228
+ sql_job.add_condor_cmd('environment',
229
+ format_environment(environment_dict))
230
+
186
231
  sql_job.add_condor_cmd('request_memory', '1024')
187
232
  warnings.warn("Requesting hard-coded disk space for SQL job")
188
233
  sql_job.add_condor_cmd('request_disk', '1 GB')
189
234
 
190
235
  return sql_job, sql_sub_name
191
236
 
192
- def write_posterior_plot_sub(tag='plot_post', exe=None, log_dir=None, output_dir="./"):
237
+ def write_posterior_plot_sub(tag='plot_post', exe=None, log_dir=None, output_dir="./", getenv=None, environment_dict=None):
193
238
  """
194
239
  Write a submit file for launching jobs to coalesce ILE output
195
240
  """
@@ -214,14 +259,19 @@ def write_posterior_plot_sub(tag='plot_post', exe=None, log_dir=None, output_dir
214
259
  plot_job.add_opt("input-cache", "ILE_all.cache")
215
260
  plot_job.add_opt("log-evidence", '')
216
261
 
217
- plot_job.add_condor_cmd('getenv', 'True')
262
+ if getenv is not None and len(getenv) != 0:
263
+ plot_job.add_condor_cmd('getenv', format_getenv(getenv))
264
+ if environment_dict is not None and len(environment_dict) != 0:
265
+ plot_job.add_condor_cmd('environment',
266
+ format_environment(environment_dict))
267
+
218
268
  plot_job.add_condor_cmd('request_memory', '1024')
219
269
  warnings.warn("Requesting hard-coded disk space for plot job")
220
270
  plot_job.add_condor_cmd('request_disk', '1 GB')
221
271
 
222
272
  return plot_job, plot_sub_name
223
273
 
224
- def write_tri_plot_sub(tag='plot_tri', injection_file=None, exe=None, log_dir=None, output_dir="./"):
274
+ def write_tri_plot_sub(tag='plot_tri', injection_file=None, exe=None, log_dir=None, output_dir="./", getenv=None, environment_dict=None):
225
275
  """
226
276
  Write a submit file for launching jobs to coalesce ILE output
227
277
  """
@@ -245,14 +295,19 @@ def write_tri_plot_sub(tag='plot_tri', injection_file=None, exe=None, log_dir=No
245
295
  plot_job.add_opt("injection", injection_file)
246
296
  plot_job.add_arg("ILE_$(macromassid).sqlite")
247
297
 
248
- plot_job.add_condor_cmd('getenv', 'True')
298
+ if getenv is not None and len(getenv) != 0:
299
+ plot_job.add_condor_cmd('getenv', format_getenv(getenv))
300
+ if environment_dict is not None and len(environment_dict) != 0:
301
+ plot_job.add_condor_cmd('environment',
302
+ format_environment(environment_dict))
303
+
249
304
  #plot_job.add_condor_cmd('request_memory', '2048')
250
305
  warnings.warn("Requesting hard-coded disk space for plot job")
251
306
  plot_job.add_condor_cmd('request_disk', '1 GB')
252
307
 
253
308
  return plot_job, plot_sub_name
254
309
 
255
- def write_1dpos_plot_sub(tag='1d_post_plot', exe=None, log_dir=None, output_dir="./"):
310
+ def write_1dpos_plot_sub(tag='1d_post_plot', exe=None, log_dir=None, output_dir="./", getenv=None, environment_dict=None):
256
311
  """
257
312
  Write a submit file for plotting 1d posterior cumulants.
258
313
  """
@@ -275,14 +330,19 @@ def write_1dpos_plot_sub(tag='1d_post_plot', exe=None, log_dir=None, output_dir=
275
330
  plot_job.add_opt("disable-triplot", '')
276
331
  plot_job.add_opt("disable-1d-density", '')
277
332
 
278
- plot_job.add_condor_cmd('getenv', 'True')
333
+ if getenv is not None and len(getenv) != 0:
334
+ plot_job.add_condor_cmd('getenv', format_getenv(getenv))
335
+ if environment_dict is not None and len(environment_dict) != 0:
336
+ plot_job.add_condor_cmd('environment',
337
+ format_environment(environment_dict))
338
+
279
339
  plot_job.add_condor_cmd('request_memory', '2048')
280
340
  warnings.warn("Requesting hard-coded disk space for plot job")
281
341
  plot_job.add_condor_cmd('request_disk', '1 GB')
282
342
 
283
343
  return plot_job, plot_sub_name
284
344
 
285
- def write_bayes_pe_postproc_sub(tag='bayespe_post_plot', exe=None, log_dir=None, web_dir="./", inj_xml=None):
345
+ def write_bayes_pe_postproc_sub(tag='bayespe_post_plot', exe=None, log_dir=None, web_dir="./", inj_xml=None, getenv=None, environment_dict=None):
286
346
  """
287
347
  Write a submit file for postprocessing output and pushing it through cbcBayesPostProc.py
288
348
  """
@@ -317,7 +377,12 @@ def write_bayes_pe_postproc_sub(tag='bayespe_post_plot', exe=None, log_dir=None,
317
377
  plot_job.add_opt("header", "header.txt")
318
378
  plot_job.add_opt("data", "tmp")
319
379
 
320
- plot_job.add_condor_cmd('getenv', 'True')
380
+ if getenv is not None and len(getenv) != 0:
381
+ plot_job.add_condor_cmd('getenv', format_getenv(getenv))
382
+ if environment_dict is not None and len(environment_dict) != 0:
383
+ plot_job.add_condor_cmd('environment',
384
+ format_environment(environment_dict))
385
+
321
386
  plot_job.add_condor_cmd('request_memory', '1024')
322
387
  warnings.warn("Requesting hard-coded disk space for plot job")
323
388
  plot_job.add_condor_cmd('request_disk', '1 GB')
@@ -739,7 +739,7 @@ def norm_sym_ratio(eta):
739
739
  #eta[np.isclose(eta, 0.25)] = 0.25
740
740
 
741
741
  # Assert phyisicality
742
- assert np.all(eta <= 0.25)
742
+ # assert np.all(eta <= 0.25)
743
743
 
744
744
  return np.sqrt(1 - 4. * eta)
745
745
 
@@ -286,5 +286,5 @@ if __file__ == sys.argv[0]:
286
286
  for m1i, m2i, loglikelihood in zip(m1, m2, loglikes):
287
287
  append_likelihood_result_to_xmldoc(xmldoc, loglikelihood, **{"mass1": m1i, "mass2": m2i})
288
288
 
289
- from glue.ligolw import utils
289
+ from ligo.lw import utils
290
290
  utils.write_filename(xmldoc, "iotest.xml.gz")
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
2
- Name: rapid-pe
3
- Version: 0.0.4.dev20230213
1
+ Metadata-Version: 2.4
2
+ Name: rapid_pe
3
+ Version: 0.1.2.dev20251209
4
4
  Summary: RapidPE: The original low-latency gravitational wave parameter estimation code.
5
5
  Home-page: https://git.ligo.org/rapidpe-rift/rapidpe/
6
6
  License: GPL-2+
@@ -19,6 +19,26 @@ Classifier: Topic :: Scientific/Engineering
19
19
  Classifier: Topic :: Scientific/Engineering :: Astronomy
20
20
  Classifier: Topic :: Scientific/Engineering :: Physics
21
21
  License-File: COPYING
22
+ Requires-Dist: bilby
23
+ Requires-Dist: h5py
24
+ Requires-Dist: healpy
25
+ Requires-Dist: lalsuite
26
+ Requires-Dist: ligo.skymap
27
+ Requires-Dist: lscsoft-glue
28
+ Requires-Dist: matplotlib
29
+ Requires-Dist: numpy
30
+ Requires-Dist: python-ligo-lw<1.9,>=1.8.1
31
+ Requires-Dist: scikit-learn
32
+ Requires-Dist: scipy
33
+ Requires-Dist: six
34
+ Dynamic: classifier
35
+ Dynamic: description
36
+ Dynamic: home-page
37
+ Dynamic: license
38
+ Dynamic: license-file
39
+ Dynamic: project-url
40
+ Dynamic: requires-dist
41
+ Dynamic: summary
22
42
 
23
43
  RapidPE was the first piece of software written for rapidly measuring the
24
44
  parameters of compact binary mergers observed via gravitational waves. It
@@ -1,21 +0,0 @@
1
- # RapidPE
2
-
3
- The original low-latency gravitational wave parameter estimation code.
4
-
5
-
6
- # Credit
7
-
8
- Copyright (c) 2012-2022 RapidPE Contributors
9
-
10
- Past and present RapidPE Contributors ordered by given name:
11
-
12
- - Caitlin Rose
13
- - Chris Pankow
14
- - Daniel Wysocki
15
- - Evan Oschner
16
- - Hong Qi
17
- - Patrick Brady
18
- - Richard O'Shaughnessy
19
- - Sinead Walsh
20
- - Soichiro Morisaki
21
- - Vinaya Valsan