Source code for pyrocko.squirrel.mantra

# http://pyrocko.org - GPLv3
#
# The Pyrocko Developers, 21st Century
# ---|P------/S----------~Lg----------

'''
Processing pipeline configuration and wiring.

A :py:class:`Mantra` describes a chain (or, more generally, connected
graph) of :py:class:`~pyrocko.squirrel.operators.base.BaseOperator`
instances to be connected on top of a
:py:class:`~pyrocko.squirrel.base.Squirrel`. Mantra configurations are
Guts-serializable YAML, typically loaded via ``--mantra PATH[:PATTERN,...]``
in :app:`squirrel mantra` and other CLI tools, or constructed
programmatically and connected with :py:meth:`Mantra.setup`.
'''

from pyrocko import util
from pyrocko.guts import Object, String, List

from .error import SquirrelError
from .operators.base import BaseOperator

guts_prefix = 'squirrel'


[docs] class MantraError(SquirrelError): ''' Raised on errors while planning or connecting a :py:class:`Mantra`'s operator graph, e.g. duplicate operator names, references to undefined operators, or unused operators. '''
[docs] class Mantra(Object): ''' Represents a connected graph of processing operators. ''' name = String.T( default='untitled', help='Name of this mantra, used e.g. to select it via ' '``--mantra PATH:PATTERN`` and as a namespacing element in ' 'derived data codes.') operators = List.T( BaseOperator.T(), help='Operators forming the processing graph, in connection ' 'order (see :py:meth:`setup`).') def _raise_error(self, message): raise MantraError(f'{message} (Mantra {self.name})') def _plan_connections(self, squirrel): ''' Work out which operator feeds which, without actually connecting them yet (see :py:meth:`setup`). Operators are chained implicitly, in list order: an operator with no ``input_names`` set takes the previous operator (or the squirrel, for the first one) as its sole input. An operator with ``input_names`` set instead takes each of the so-named, already-defined earlier operators as inputs (fan-in), by name. Every named provider (the squirrel, or an operator) must be consumed by something — either by a later operator, or by being the last operator in the list (the graph's outlet) — otherwise this is flagged as an error. :param squirrel: The squirrel instance to serve as the graph's root input. :type squirrel: :py:class:`~pyrocko.squirrel.base.Squirrel` :returns: ``(connections, outlet)``, where ``connections`` is a list of ``(operator, input)`` pairs, in an order suitable for feeding to successive :py:meth:`~pyrocko.squirrel.operators.base.\ BaseOperator.add_input` calls, and ``outlet`` is the final operator (or the squirrel itself, if there are no operators). :rtype: :py:class:`tuple` :raises MantraError: If an operator name is duplicated, an ``input_names`` entry refers to an undefined (or not-yet-defined) operator, or an operator's output is not consumed by anything. ''' if not self.operators: return [], squirrel previous_name, previous_operator = ('squirrel', squirrel) by_name = {'squirrel': squirrel} used = set() connections = [] for ioperator, operator in enumerate(self.operators): name = operator.name or f'operator_{ioperator}' if name in by_name: self._raise_error(f'Duplicate operator name: {name}') by_name[name] = operator if not operator.input_names: connections.append((operator, previous_operator)) used.add(previous_name) else: for input_name in operator.input_names: if input_name not in by_name: self._raise_error( f'Operator "{name}" requires input ' f'from yet undefined operator "{input_name}".') used.add(input_name) connections.append((operator, by_name[input_name])) previous_name, previous_operator = (name, operator) used.add(previous_name) unused = sorted(set(by_name) - used) if unused: self._raise_error( 'Unused provider%s: %s' % ( util.plural_s(unused), ', '.join(unused))) return connections, previous_operator
[docs] def setup(self, squirrel): ''' Connect operators into a processing graph on top of a squirrel. :param squirrel: The squirrel instance to serve as the graph's root input. :type squirrel: :py:class:`~pyrocko.squirrel.base.Squirrel` After this call, ``outlet`` holds the graph's final operator (or ``squirrel`` itself, if this mantra has no operators), ready to be queried like a squirrel (e.g. ``mantra.outlet.get_waveforms(...)``). ''' connections, outlet = self._plan_connections(squirrel) self.outlet = outlet for operator in self.operators: operator.set_mantra(self) for operator, input in connections: operator.add_input(input)
[docs] def describe(self): ''' Get textual description of the processing graph. :rtype: str Requires :py:meth:`setup` to have been called first. ''' lines = [] lines.append(f'Mantra: {self.name}\n operators:') for operator in self.operators: lines.extend( f' {line}' for line in operator.describe().splitlines()) return '\n'.join(lines)
__all__ = [ 'Mantra', 'MantraError', ]