from __future__ import division import os import logging import importlib import numpy as np from .ifo import IFOS from .struct import Struct from .plot import plot_noise def _load_module(name_or_path): """Load module from name or path. Return loaded module and module path. """ if os.path.exists(name_or_path): path = name_or_path.rstrip('/') modname = os.path.splitext(os.path.basename(path))[0] if os.path.isdir(path): path = os.path.join(path, '__init__.py') spec = importlib.util.spec_from_file_location(modname, path) mod = importlib.util.module_from_spec(spec) spec.loader.exec_module(mod) else: mod = importlib.import_module(name_or_path) try: path = mod.__path__[0] except AttributeError: path = mod.__file__ return mod, path def load_budget(name_or_path): """Load GWINC IFO Budget by name or from path. Named IFOs should correspond to one of the IFOs available in the gwinc package (see gwinc.IFOS). If a path is provided it should either be a budget package (directory) or module (ending in .py), or an IFO struct definition (see gwinc.Struct). If a budget package path is provided, and the package includes an 'ifo.yaml' file, that file will be loaded into a Struct and assigned as an attribute to the returned Budget class. If a bare struct is provided the base aLIGO budget definition will be assumed. Returns a Budget class with 'ifo', 'freq', and 'plot_style', ifo structure, frequency array, and plot style dictionary, with the last three being None if they are not defined in the budget. """ ifo = None if os.path.exists(name_or_path): path = name_or_path.rstrip('/') bname, ext = os.path.splitext(os.path.basename(path)) if ext in Struct.STRUCT_EXT: logging.info("loading struct {}...".format(path)) ifo = Struct.from_file(path) bname = 'aLIGO' modname = 'gwinc.ifo.aLIGO' logging.info("loading budget {}...".format(modname)) else: modname = path logging.info("loading module path {}...".format(modname)) else: if name_or_path not in IFOS: raise RuntimeError("Unknonw IFO '{}' (available IFOs: {}).".format( name_or_path, IFOS, )) bname = name_or_path modname = 'gwinc.ifo.'+name_or_path logging.info("loading module {}...".format(modname)) mod, modpath = _load_module(modname) Budget = getattr(mod, bname) ifopath = os.path.join(modpath, 'ifo.yaml') if not ifo and ifopath: ifo = Struct.from_file(ifopath) Budget.ifo = ifo return Budget def gwinc(freq, ifo, source=None, plot=False, PRfixed=True): """Calculate strain noise budget for a specified interferometer model. Argument `freq` is the frequency array for which the noises will be calculated, and `ifo` is the IFO model (see the `load_budget()` function). The nominal 'aLIGO' budget structure will be used. If `source` structure provided, so evaluates the sensitivity of the detector to several potential gravitational wave sources. If `plot` is True a plot of the budget will be created. Returns tuple of (score, noises, ifo) """ # assume generic aLIGO configuration # FIXME: how do we allow adding arbitrary addtional noise sources # from just ifo description, without having to specify full budget Budget = load_budget('aLIGO') traces = Budget(freq, ifo=ifo).run() plot_style = getattr(Budget, 'plot_style', {}) # construct matgwinc-compatible noises structure noises = {} for name, (data, style) in traces.items(): noises[style.get('label', name)] = data noises['Freq'] = freq pbs = ifo.gwinc.pbs parm = ifo.gwinc.parm finesse = ifo.gwinc.finesse prfactor = ifo.gwinc.prfactor if ifo.Laser.Power * prfactor != pbs: logging.warning("Thermal lensing limits input power to {} W".format(pbs/prfactor)) # report astrophysical scores if so desired score = None if source: logging.warning("Source score calculation currently not supported. See `inspiral-range` package for similar functionality:") logging.warning("https://git.ligo.org/gwinc/inspiral-range") # score = int731(freq, noises['Total'], ifo, source) # score.Omega = intStoch(freq, noises['Total'], 0, ifo, source) # -------------------------------------------------------- # output graphics if plot: logging.info('Laser Power: %7.2f Watt' % ifo.Laser.Power) logging.info('SRM Detuning: %7.2f degree' % (ifo.Optics.SRM.Tunephase*180/np.pi)) logging.info('SRM transmission: %9.4f' % ifo.Optics.SRM.Transmittance) logging.info('ITM transmission: %9.4f' % ifo.Optics.ITM.Transmittance) logging.info('PRM transmission: %9.4f' % ifo.Optics.PRM.Transmittance) logging.info('Finesse: %7.2f' % finesse) logging.info('Power Recycling Gain: %7.2f' % prfactor) logging.info('Arm Power: %7.2f kW' % (parm/1000)) logging.info('Power on BS: %7.2f W' % pbs) # coating and substrate thermal load on the ITM PowAbsITM = (pbs/2) * \ np.hstack([(finesse*2/np.pi) * ifo.Optics.ITM.CoatingAbsorption, (2 * ifo.Materials.MassThickness) * ifo.Optics.ITM.SubstrateAbsorption]) logging.info('Thermal load on ITM: %8.3f W' % sum(PowAbsITM)) logging.info('Thermal load on BS: %8.3f W' % (ifo.Materials.MassThickness*ifo.Optics.SubstrateAbsorption*pbs)) if (ifo.Laser.Power*prfactor != pbs): logging.info('Lensing limited input power: %7.2f W' % (pbs/prfactor)) if score: logging.info('BNS Inspiral Range: ' + str(score.effr0ns) + ' Mpc/ z = ' + str(score.zHorizonNS)) logging.info('BBH Inspiral Range: ' + str(score.effr0bh) + ' Mpc/ z = ' + str(score.zHorizonBH)) logging.info('Stochastic Omega: %4.1g Universes' % score.Omega) plot_noise(ifo, traces, **plot_style) return score, noises, ifo