Source code for pycbc

# Copyright (C) 2012  Alex Nitz, Josh Willis
#
# This program is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the
# Free Software Foundation; either version 3 of the License, or (at your
# option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
# Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.


#
# =============================================================================
#
#                                   Preamble
#
# =============================================================================
#
"""PyCBC contains a toolkit for CBC gravitational wave analysis
"""
import subprocess, os, sys, signal, warnings

# Filter annoying Cython warnings that serve no good purpose.
warnings.filterwarnings("ignore", message="numpy.dtype size changed")
warnings.filterwarnings("ignore", message="numpy.ufunc size changed")
import logging
import random
import string
import importlib.util
import importlib.machinery
from datetime import datetime as dt

try:
    # This will fail when pycbc is imported during the build process,
    # before version.py has been generated.
    from .version import git_hash
    from .version import version as pycbc_version
    from .version import PyCBCVersionAction
except:
    git_hash = 'none'
    pycbc_version = 'none'
    PyCBCVersionAction = None

__version__ = pycbc_version


[docs] class LogFormatter(logging.Formatter): """ Format the logging appropriately This will return the log time in the ISO 6801 standard, but with millisecond precision https://en.wikipedia.org/wiki/ISO_8601 e.g. 2022-11-18T09:53:01.554+00:00 """ converter = dt.fromtimestamp
[docs] def formatTime(self, record, datefmt=None): ct = self.converter(record.created).astimezone() t = ct.strftime("%Y-%m-%dT%H:%M:%S") s = f"{t}.{int(record.msecs):03d}" timezone = ct.strftime('%z') timezone_colon = f"{timezone[:-2]}:{timezone[-2:]}" s += timezone_colon return s
[docs] def add_common_pycbc_options(parser): """ Common utility to add standard options to each PyCBC executable. Parameters ---------- parser : argparse.ArgumentParser The argument parser to which the options will be added """ group = parser.add_argument_group( title="PyCBC common options", description="Common options for PyCBC executables.", ) group.add_argument( '-v', '--verbose', action='count', default=0, help=( 'Add verbosity to logging. Adding the option ' 'multiple times makes logging progressively ' 'more verbose, e.g. --verbose or -v provides ' 'logging at the info level, but -vv or ' '--verbose --verbose provides debug logging.' ) ) group.add_argument( '--version', action=PyCBCVersionAction, )
# Loggers from other packages that talk far more than they are worth when # PyCBC is run verbosely. init_logging holds them one step behind the level # it sets for everything else. Only the one whose noise has actually been # measured is listed; others can be added as they are found. NOISY_LOGGERS = [ "matplotlib.font_manager", "scitokens", "Pegasus" ]
[docs] def init_logging(verbose=False, default_level=0, to_file=None, format='%(asctime)s %(levelname)s : %(message)s', reduce_log_level=NOISY_LOGGERS): """Common utility for setting up logging in PyCBC. Installs a signal handler such that verbosity can be activated at run-time by sending a SIGUSR1 to the process. Parameters ---------- verbose : bool or int, optional What level to set the verbosity level to. Accepts either a boolean or an integer representing the level to set. If True/False will set to ``logging.INFO``/``logging.WARN``. For higher logging levels, pass an integer representing the level to set. (1 = INFO, 2 = DEBUG). default_level : int, optional The default level, to be added to any verbose option if it is an integer, or set to this value if it is None or False to_file : filepath Set up logging to a file instead of the stderr. File will be overwritten if it already exists. format : str, optional The format to use for logging messages. reduce_log_level : list of str, optional Loggers to keep one step less verbose than everything else, so that their output does not bury PyCBC's own. Asking for more verbosity still reaches them. """ def sig_handler(signum, frame): logger = logging.getLogger() log_level = logger.level if log_level == logging.DEBUG: log_level = logging.WARN else: log_level = logging.DEBUG logging.warning('Got signal %d, setting log level to %d', signum, log_level) logger.setLevel(log_level) signal.signal(signal.SIGUSR1, sig_handler) # See https://docs.python.org/3/library/logging.html#levels # for log level definitions logger = logging.getLogger() # This resets duplicate loggers in preference of ours. # Otherwise, you may see duplicate messages logger.handlers.clear() verbose_int = default_level if verbose is None \ else int(verbose) + default_level logger.setLevel(logging.WARNING - verbose_int * 10) # Initial setting # These talk far more than they are worth: font_manager alone emits tens # of thousands of DEBUG records per figure. Held one step behind the # root rather than pinned, so a high enough verbosity still shows them. for name in reduce_log_level: logging.getLogger(name).setLevel(logger.level + 10) if to_file is not None: handler = logging.FileHandler(to_file, mode='w') else: handler = logging.StreamHandler() logger.addHandler(handler) handler.setFormatter(LogFormatter(fmt=format))
[docs] def makedir(path): """ Make the analysis directory path and any parent directories that don't already exist. Will do nothing if path already exists. """ if path is not None and not os.path.exists(path): os.makedirs(path)
# PyCBC-Specific Constants # Set the value we want any aligned memory calls to use # N.B.: *Not* all pycbc memory will be aligned to multiples # of this value PYCBC_ALIGNMENT = 32 # Dynamic range factor: a large constant for rescaling # GW strains. This is 2**69 rounded to 17 sig.fig. DYN_RANGE_FAC = 5.9029581035870565e+20 # String used to separate parameters in configuration file section headers. # This is used by the distributions and transforms modules VARARGS_DELIM = '+' # Check for optional CUDA support of the PyCBC Package try: #check if pycuda is installed import pycuda # If running documentation the import doesn't fail, but it's only a mock # import, so detect that if type(pycuda).__name__ in ('MagicMock', '_MockModule'): raise ImportError import pycuda.driver as _pycudadrv #check how many CUDA device is installed try: _pycudadrv.init() device_count = _pycudadrv.Device.count() except Exception: device_count = 0 #Set value to true if there is usable device HAVE_CUDA = (device_count > 0) if device_count == 0: warnings.warn("PyCUDA imported but no CUDA device found; disabling CUDA support") except ImportError: HAVE_CUDA = False # Check for MKL capability try: import pycbc.fft.mkl HAVE_MKL=True except (ImportError, OSError): HAVE_MKL=False # Check for openmp suppport, currently we pressume it exists, unless on # platforms (mac) that are silly and don't use the standard gcc. if sys.platform == 'darwin': HAVE_OMP = False else: HAVE_OMP = True # https://pynative.com/python-generate-random-string/
[docs] def random_string(stringLength=10): """Generate a random string of fixed length """ letters = string.ascii_lowercase return ''.join(random.choice(letters) for i in range(stringLength))
# This is needed as a backwards compatibility. The function was removed in # python 3.12.
[docs] def load_source(modname, filename): loader = importlib.machinery.SourceFileLoader(modname, filename) spec = importlib.util.spec_from_file_location(modname, filename, loader=loader) module = importlib.util.module_from_spec(spec) # The module is always executed and not cached in sys.modules. # Uncomment the following line to cache the module. # sys.modules[module.__name__] = module loader.exec_module(module) return module
# Expose some convenience functions at package level for backwards # compatibility and convenience: allow `pycbc.gps_now()` as well as # `pycbc.time.gps_now()`. try: from .time import gps_now # noqa: F401 except Exception: # If pycbc imported during build this may fail; silently ignore. gps_now = None