# 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]
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