Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added a new "metastate" section type where one can combine states from different detectors #363

Draft
wants to merge 3 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 18 additions & 6 deletions gwsumm/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -305,28 +305,40 @@ def load_states(self, section='states'):
"""Read and format a list of `SummaryState` definitions from the
given :class:`~configparser.ConfigParser`
"""
from .state import (register_state, SummaryState,
from .state import (register_state, SummaryState, SummaryMetaState,
ALLSTATE, generate_all_state, get_state)
# parse the [states] section into individual state definitions
# Parse the [states] section into individual state definitions.
# Each state definition is amended to the GWSummConfigParser as a new
# section with name and definition key-value pairs.
try:
states = dict(self.nditems(section))
except configparser.NoSectionError:
self.add_section(section)
states = {}
for state in states:
if not (self.has_section('state-%s' % state) or
self.has_section('state %s' % state)):
section = 'state-%s' % state
if not (self.has_section(f'state-{state}') or
self.has_section(f'state {state}')):
section = f'state-{state}'
self.add_section(section)
self.set(section, 'name', state)
self.set(section, 'definition', states[state])

# parse each state section into a new state
# Parse each state section into a new state.
# Here we reset the states variable to an empty list because the
# previous code block added all of the states section into their own
# sections [state-<state>]. We register those states and metastates,
# appending them also to the states list. Metastates are states that
# use another state definition, where they look for the key name. If
# key is not defined, then name is used instead. The key or name is
# expected to have an 'H1-' or 'L1-' prefix.
states = []
for section in self.sections():
if re.match(r'state[-\s]', section):
states.append(register_state(
SummaryState.from_ini(self, section)))
elif re.match(r'metastate[-\s]', section):
states.append(register_state(
SummaryMetaState.from_ini(self, section)))

# register All state
start = self.getint(section, 'gps-start-time')
Expand Down
19 changes: 18 additions & 1 deletion gwsumm/plot/builtin.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"""

import os.path
import re
import warnings
from itertools import cycle

Expand All @@ -41,7 +42,7 @@
from ..data import (get_timeseries, get_spectrogram,
get_coherence_spectrogram, get_range_spectrogram,
get_spectrum, get_coherence_spectrum, get_range_spectrum)
from ..state import ALLSTATE
from ..state import ALLSTATE, SummaryMetaState
from .registry import (get_plot, register_plot)
from .mixins import DataLabelSvgMixin

Expand Down Expand Up @@ -498,16 +499,32 @@ def _draw(self):
else:
iterator = list(zip(self.channels, plotargs))

# loop over the channels
for chantuple in iterator:
channel = chantuple[0]
channel2 = chantuple[1]
pargs = chantuple[-1]

# get the state or segment
if self.state and not self.all_data:
valid = self.state
else:
valid = SegmentList([self.span])

# If the state is a metastate, then get the corresponding IFO-
# specific state from the global variables list
if isinstance(valid, SummaryMetaState):
reg = re.compile(channel.ifo)
matching = list(filter(reg.match, valid.uses))
assert len(matching) == 1, (
f"Failed to find a unique state for {valid.name} "
f"metastate. Found {len(matching)} matching states in "
f"{valid.uses} for {channel.ifo}")
try:
valid = globalv.STATES[matching[0].lower()]
except KeyError:
raise

if self.type == 'coherence-spectrum':
data = get_coherence_spectrum(
[str(channel), str(channel2)], valid, query=False)
Expand Down
4 changes: 2 additions & 2 deletions gwsumm/state/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,9 @@

"""

from .core import SummaryState
from .core import (SummaryState, SummaryMetaState)
from .registry import (get_state, get_states, register_state)
from .all import (ALLSTATE, generate_all_state)

__all__ = ['ALLSTATE', 'SummaryState', 'get_state', 'get_states',
'register_state', 'generate_all_state']
'register_state', 'generate_all_state', 'SummaryMetaState']
92 changes: 92 additions & 0 deletions gwsumm/state/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -349,3 +349,95 @@ def copy(self):

def __str__(self):
return self.name


class SummaryMetaState(SummaryState):
"""A meta state where different states may be used when processing a
`~gwsumm.tabs.DataTab`.

An example use ase is when one wants to plot two different state times on
the same plot. This currently has limitations as it expects the states to
be from different detectors. So when using this metastate, each value in
"uses" needs to be prefixed by "<IFO>"

Parameters
----------
name : `str`
name for this state
uses : `list`
list of strings for which states to use. Ex.: ['H1-quiet', 'L1-quiet']
known : `~gwpy.segments.SegmentList`, optional
list of known segments
active : `~gwpy.segments.SegmentList`, optional
list of active segments
description : `str`, optional
text describing what this state means
definition : `str`, optional
logical combination of flags that define known and active segments
for this state (see :attr:`documentation <SummaryState.definition>`
for details)
hours : `str`, optional
a string of the form "<start>-<end>,<IFO|utc|local>"
key : `str`, optional
registry key for this state, defaults to :attr:`~SummaryState.name`
filename : `str`, optional
path to filename with segments
url : `str`, optional
URL to read the segments
"""

def __init__(self, name, uses, known=SegmentList(), active=SegmentList(),
description=None, definition=None, hours=None, key=None,
filename=None, url=None):

super(SummaryMetaState, self).__init__(
name=name, known=known, active=active,
description=description, definition=definition, hours=hours,
key=key, filename=filename, url=url)

self.uses = uses

@classmethod
def from_ini(cls, config, section):
"""Create a new `SummaryMetaState` from a section in a `ConfigParser`.

Parameters
----------
config : :class:`~gwsumm.config.GWConfigParser`
customised configuration parser containing given section
section : `str`
name of section to parse

Returns
-------
`SummaryMetaState`
a new state, with attributes set from the options in the
configuration
"""
config = GWSummConfigParser.from_configparser(config)
# get parameters
params = dict(config.nditems(section))
# parse name
name = params.pop('name', section)
if re.match(r'metastate[-\s]', name):
name = section[10:]
# list states this uses
uses = params.pop('uses', section).split(',')

# generate metastate
return cls(name=name, uses=uses, **params)

def fetch(self, config=GWSummConfigParser(),
segmentcache=None, segdb_error='raise',
datacache=None, datafind_error='raise', nproc=1, nds=None,
**kwargs):
"""Finalise this state by fetching the states this metastate uses,
either from global memory, or from the segment database
"""

for idx, state in enumerate(self.uses):
globalv.STATES[state.lower()].fetch(
config=config, segmentcache=segmentcache,
segdb_error=segdb_error, datacache=datacache,
datafind_error=datafind_error, nproc=nproc, nds=nds,
**kwargs)
20 changes: 17 additions & 3 deletions gwsumm/tabs/data.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,8 @@
from ..data.utils import get_fftparams
from ..plot import get_plot
from ..segments import get_segments
from ..state import (generate_all_state, ALLSTATE, get_state)
from ..state import (generate_all_state, ALLSTATE, get_state,
SummaryMetaState)
from ..triggers import get_triggers
from ..utils import (re_flagdiv, vprint, safe_eval)

Expand Down Expand Up @@ -356,12 +357,25 @@ def process(self, config=ConfigParser(), nproc=1, **stateargs):
datafind_error=stateargs.get('datafind_error', 'raise'),
nproc=nproc, nds=stateargs.get('nds', None))
vprint("States finalised [%d total]\n" % len(self.states))

# loop over states for this tab and print out information
for state in self.states:
vprint(" {0.name}: {1} segments | {2} seconds".format(
state, len(state.active), abs(state.active)))
if isinstance(state, SummaryMetaState):
vprint(
f"Metastate {state.key} has {len(state.uses)} states")
else:
vprint(" {0.name}: {1} segments | {2} seconds".format(
state, len(state.active), abs(state.active)))
if state is self.defaultstate:
vprint(" [DEFAULT]")
vprint('\n')
if isinstance(state, SummaryMetaState):
for idx, this_state in enumerate(state.uses):
vprint(f" {this_state}: ")
vprint(f"{len(globalv.STATES[this_state.lower()].active)} "
"segments | ")
vprint(f"{abs(globalv.STATES[this_state.lower()].active)} "
"seconds\n")

# pre-process requests for 'all-data' plots
all_data = any([(p.all_data & p.new) for p in self.plots])
Expand Down
12 changes: 12 additions & 0 deletions gwsumm/tests/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,18 @@ def test_load_states(self):
assert states['locked'].definition == 'X1:TEST-STATE:1'
assert state.ALLSTATE in states

def test_load_state_metastate(self):
cp = self.new()
cp.set_date_options(0, 100)
cp.add_section('metastate-test')
cp.set('metastate-test', 'uses', 'locked')
cp.set('metastate-test', 'name', 'meta')
cp.load_states()
states = state.get_states()
assert len(states) == 3
assert 'meta' in states
assert states['meta'].uses == ['locked']

def test_load_plugins(self, cnfg):
# check that empty section doesn't cause havoc
cp = self.PARSER()
Expand Down