Example #1
0
def test_non_negative(**kwargs) -> None:
    """Raise a ValueError if at least one value of the objects given as keywords is
    negative.

    If all values are non negative, nothing happens:

    >>> from hydpy.auxs.validtools import test_non_negative
    >>> test_non_negative(arr1=numpy.array([1.0, 2.0]),
    ...                   arr2=numpy.array([3.0, 4.0]),
    ...                   arr3=numpy.array([5.0, 6.0]))

    If at least one value is negative, the following error is raised:

    >>> test_non_negative(arr1=numpy.array([1.0, 2.0]),
    ...                   arr2=numpy.array([-3.0, 4.0]),
    ...                   arr3=numpy.array([5.0, 6.0]))
    Traceback (most recent call last):
    ...
    ValueError: For the following objects, at least one value is negative: arr2.

    For flexibility in the functions application, it is allowed to pass no array at all:

    >>> test_non_negative()
    """
    names = list(kwargs.keys())
    negs = [numpy.nanmin(array) < 0.0 for array in kwargs.values()]
    if any(negs):
        string = objecttools.enumeration(
            name for name, neg in sorted(zip(names, negs)) if neg)
        raise ValueError(
            f"For the following objects, at least one value is negative: {string}."
        )
Example #2
0
def test_equal_shape(**kwargs) -> None:
    """Raise a ValueError if the shapes of the objects given as keywords are not equal.

    If all shapes are equal, nothing happens:

    >>> from hydpy.auxs.validtools import test_equal_shape
    >>> test_equal_shape(arr1=numpy.array([1.0, 2.0]),
    ...                  arr2=numpy.array([3.0, 4.0]),
    ...                  arr3=numpy.array([5.0, 6.0]))

    If at least one shape differs, the following error is raised:

    >>> test_equal_shape(arr1=numpy.array([1.0, 2.0]),
    ...                  arr2=numpy.array([3.0]),
    ...                  arr3=numpy.array([5.0, 6.0]))
    Traceback (most recent call last):
    ...
    ValueError: The shapes of the following objects are not equal: \
arr1 (2,), arr2 (1,), and arr3 (2,).

    For flexibility in the functions application, it is allowed to pass only one array
    or no arrays at all:

    >>> test_equal_shape(arr1=numpy.array([1.0, 2.0]))
    >>> test_equal_shape()
    """
    names = list(kwargs.keys())
    shapes = numpy.array(
        [numpy.array(array).shape for array in kwargs.values()])
    if any(shapes[:-1] != shapes[1:]):
        string = objecttools.enumeration(
            f"{name} {tuple(shape)}"
            for (name, shape) in sorted(zip(names, shapes)))
        raise ValueError(
            f"The shapes of the following objects are not equal: {string}.")
Example #3
0
 def __set__(self, obj, value):
     value = str(value)
     if value in obj._supportedmodes:
         self.value = value
     else:
         raise ValueError(
             'The given sequence file type `%s` is not implemented.  '
             'Please choose one of the following file types: %s.' %
             (value, objecttools.enumeration(obj._supportedmodes)))
Example #4
0
 def __init__(self, logfile: IO, logstyle: str, infotype: str):
     self.logfile = logfile
     try:
         stdtype2string = self.style2infotype2string[logstyle]
     except KeyError:
         styles = objecttools.enumeration(
             sorted(self.style2infotype2string.keys()))
         raise ValueError(
             f'The given log file style {logstyle} is not available.  '
             f'Please choose one of the following: {styles}.')
     self._string = stdtype2string[infotype]
Example #5
0
 def __init__(
     self,
     logfile: TextIO,
     logstyle: str,
     infotype: Literal["info", "warning", "exception"],
 ) -> None:
     self.logfile = logfile
     self._infotype = infotype
     try:
         stdtype2string = self.style2infotype2string[logstyle]
     except KeyError:
         styles = objecttools.enumeration(
             sorted(self.style2infotype2string.keys()))
         raise ValueError(
             f"The given log file style {logstyle} is not available.  "
             f"Please choose one of the following: {styles}.") from None
     self._string = stdtype2string[infotype]
Example #6
0
def execute_scriptfunction() -> Optional[int]:
    """Execute a HydPy script function.

    Function |execute_scriptfunction| is indirectly applied and explained in the
    documentation on module |hyd|.
    """
    logstyle = "plain"
    logfilepath = prepare_logfile("stdout")
    try:
        args_given = []
        kwargs_given = {}
        for arg in sys.argv[1:]:
            if len(arg) < 3:
                args_given.append(arg)
            else:
                result = parse_argument(arg)
                if isinstance(result, str):
                    args_given.append(arg)
                else:
                    kwargs_given[result[0]] = result[1]
        logfilepath = prepare_logfile(kwargs_given.pop("logfile", "stdout"))
        logstyle = kwargs_given.pop("logstyle", "plain")
        try:
            funcname = str(args_given.pop(0))
        except IndexError:
            raise ValueError(
                "The first positional argument defining the function to be called is "
                "missing.") from None
        try:
            func = hydpy.pub.scriptfunctions[funcname]
        except KeyError:
            available_funcs = objecttools.enumeration(
                sorted(hydpy.pub.scriptfunctions.keys()))
            raise ValueError(
                f"There is no `{funcname}` function callable by `hyd.py`.  Choose one "
                f"of the following instead: {available_funcs}.") from None
        argspec = inspect.getfullargspec(func)
        args_possible = argspec.args
        if argspec.defaults:
            args_required = args_possible[:-len(argspec.defaults)]
        else:
            args_required = args_possible
        nmb_args_possible = len(args_possible)
        nmb_args_required = len(args_required)
        nmb_args_given = len(args_given)
        if nmb_args_given < nmb_args_required or nmb_args_given > nmb_args_possible:
            enum_args_given = ""
            if nmb_args_given:
                enum_args_given = f" ({objecttools.enumeration(args_given)})"
            if nmb_args_given < nmb_args_required:
                args = args_required
                nmb_args = nmb_args_required
                condition = "requires"
            else:
                args = args_possible
                nmb_args = nmb_args_possible
                condition = "allows"
            enum_args = ""
            if nmb_args:
                enum_args = f" ({objecttools.enumeration(args)})"
            raise ValueError(
                f"Function `{funcname}` {condition} `{nmb_args:d}` "
                f"positional arguments{enum_args}, but "
                f"`{nmb_args_given:d}` are given{enum_args_given}.")
        with _activate_logfile(logfilepath, logstyle, "info", "warning"):
            return func(*args_given, **kwargs_given)
    except BaseException as exc:
        if logstyle not in LogFileInterface.style2infotype2string:
            logstyle = "plain"
        with _activate_logfile(logfilepath, logstyle, "exception",
                               "exception"):
            args = sys.argv[1:]
            nmb = len(args)
            if nmb > 1:
                argphrase = f"with arguments `{', '.join(args)}`"
            elif nmb == 1:
                argphrase = f"with argument `{args[0]}`"
            else:
                argphrase = "without arguments"
            print(
                f"Invoking hyd.py {argphrase} resulted in the following error:\n"
                f"{str(exc)}\n\nSee the following stack traceback for debugging:\n",
                file=sys.stderr,
            )
            traceback.print_tb(sys.exc_info()[2])
        return 1
Example #7
0
    def plot(self,
             filename,
             width=None,
             height=None,
             selected=None,
             activated=None):
        """Save a bokeh html file plotting the current test results.

        (Optional) arguments:
            * filename: Name of the file.  If necessary, the file ending
              `html` is added automatically.  The file is stored in the
              `html` folder of subpackage `docs`.
            * width: Width of the plot in screen units.  Defaults to 600.
            * height: Height of the plot in screen units.  Defaults to 300.
            * selected: List of the sequences to be plotted.
            * activated: List of the sequences to be shown initially.
        """
        if width is None:
            width = self.plotting_options.width
        if height is None:
            height = self.plotting_options.height
        if not filename.endswith('.html'):
            filename += '.html'
        if selected is None:
            selected = self.plotting_options.selected
            if selected is None:
                selected = self.parseqs
        if activated is None:
            activated = self.plotting_options.activated
            if activated is None:
                activated = self.parseqs
        activated = tuple(nm_.name if hasattr(nm_, 'name') else nm_.lower()
                          for nm_ in activated)
        path = os.path.join(docs.__path__[0], 'html', filename)
        plotting.output_file(path)
        plot = plotting.figure(x_axis_type="datetime",
                               tools=['pan', 'ywheel_zoom'],
                               toolbar_location=None)
        plot.toolbar.active_drag = plot.tools[0]
        plot.toolbar.active_scroll = plot.tools[1]
        plot.plot_width = width
        plot.plot_height = height
        legend_entries = []
        viridis = palettes.viridis
        headers = [header for header in self.raw_header_strings[1:] if header]
        zipped = zip(selected, viridis(len(selected)), headers)
        for (seq, col, header) in zipped:
            series = seq.series.copy()
            if not seq.NDIM:
                listofseries = [series]
                listofsuffixes = ['']
            else:
                nmb = seq.shape[0]
                listofseries = [series[:, idx] for idx in range(nmb)]
                if nmb == 1:
                    listofsuffixes = ['']
                else:
                    listofsuffixes = ['-%d' % idx for idx in range(nmb)]
            for subseries, suffix in zip(listofseries, listofsuffixes):
                line = plot.line(self._datetimes,
                                 subseries,
                                 alpha=0.8,
                                 muted_alpha=0.0,
                                 line_width=2,
                                 color=col)
                line.muted = seq.name not in activated
                if header.strip() == seq.name:
                    title = objecttools.classname(seq)
                else:
                    title = header.capitalize()
                title += suffix
                legend_entries.append((title, [line]))
        legend = models.Legend(items=legend_entries, click_policy='mute')
        legend.border_line_color = None
        plot.add_layout(legend, 'right')
        units = self.extract_units(selected)
        ylabel = objecttools.enumeration(units).replace('and', 'or')
        plot.yaxis.axis_label = ylabel
        plot.yaxis.axis_label_text_font_style = 'normal'
        plotting.save(plot)
        self._src = filename
        self._width = width
        self._height = height
Example #8
0
    def connect(self):
        """Connect the |InletSequence| and |OutletSequence| objects of
        the actual model to the |NodeSequence| objects handled by an
        arbitrary number of inlet and outlet nodes.

        To application models derived from |conv_model.Model|, you first
        need to define an |Element| connected with an arbitrary number of
        inlet and outlet nodes:

        >>> from hydpy import Element
        >>> conv = Element("conv",
        ...                inlets=["in1", "in2"],
        ...                outlets=["out1", "out2", "out3"])

        Second, you must define the inlet and outlet nodes' coordinates via
        parameter |InputCoordinates| and |OutputCoordinates|, respectively.
        In both cases, use the names of the |Node| objects as keyword arguments
        to pass the corresponding coordinates:

        >>> from hydpy.models.conv_v001 import *
        >>> parameterstep()
        >>> inputcoordinates(
        ...     in1=(0.0, 3.0),
        ...     in2=(2.0, -1.0))
        >>> outputcoordinates(
        ...     out1=(0.0, 3.0),
        ...     out2=(3.0, -2.0),
        ...     out3=(1.0, 2.0))
        >>> maxnmbinputs()
        >>> parameters.update()

        |conv| passes the current values of the inlet nodes correctly to
        the outlet nodes (note that node `in1` works with simulated values
        while node `in2` works with observed values, as we set its
        |Node.deploymode| to `obs`):

        >>> conv.model = model
        >>> conv.inlets.in1.sequences.sim = 1.0
        >>> conv.inlets.in2.deploymode = "obs"
        >>> conv.inlets.in2.sequences.obs = 2.0
        >>> model.simulate(0)
        >>> conv.outlets.out1.sequences.sim
        sim(1.0)
        >>> conv.outlets.out2.sequences.sim
        sim(2.0)
        >>> conv.outlets.out3.sequences.sim
        sim(1.0)

        When you forget a node (or misspell its name), you get the
        following error message:

        >>> outputcoordinates(
        ...     out1=(0.0, 3.0),
        ...     out2=(3.0, -2.0))
        >>> maxnmbinputs()
        >>> parameters.update()
        >>> conv.model = model
        Traceback (most recent call last):
        ...
        RuntimeError: While trying to connect model `conv_v001` of element \
`conv`, the following error occurred: The node handled by control parameter \
outputcoordinates (out1 and out2) are not the same as the outlet nodes \
handled by element conv (out1, out2, and out3).
        """
        try:
            for coordinates, sequence, nodes in (
                (
                    self.parameters.control.inputcoordinates,
                    self.sequences.inlets.inputs,
                    self.element.inlets,
                ),
                (
                    self.parameters.control.outputcoordinates,
                    self.sequences.outlets.outputs,
                    self.element.outlets,
                ),
            ):
                if nodes == devicetools.Nodes(coordinates.nodes):
                    sequence.shape = len(coordinates)
                    for idx, node in enumerate(coordinates.nodes):
                        sequence.set_pointer(
                            node.get_double(sequence.subseqs.name), idx)
                else:
                    parameternodes = objecttools.enumeration(coordinates.nodes)
                    elementnodes = objecttools.enumeration(nodes)
                    raise RuntimeError(
                        f"The node handled by control parameter "
                        f"{coordinates.name} ({parameternodes}) are not the "
                        f"same as the {sequence.subseqs.name[:-1]} nodes "
                        f"handled by element {self.element.name} "
                        f"({elementnodes}).")
        except BaseException:
            objecttools.augment_excmessage(
                f"While trying to connect model "
                f"{objecttools.elementphrase(self)}")
Example #9
0
def execute_scriptfunction() -> Optional[int]:
    """Execute a HydPy script function.

    Function |execute_scriptfunction| is indirectly applied and
    explained in the documentation on module |hyd|.
    """
    try:
        args_given = []
        kwargs_given = {}
        for arg in sys.argv[1:]:
            if len(arg) < 3:
                args_given.append(arg)
            else:
                try:
                    key, value = parse_argument(arg)
                    kwargs_given[key] = value
                except ValueError:
                    args_given.append(arg)
        logfilepath = prepare_logfile(kwargs_given.pop('logfile', 'stdout'))
        logstyle = kwargs_given.pop('logstyle', 'plain')
        try:
            funcname = str(args_given.pop(0))
        except IndexError:
            raise ValueError(
                'The first positional argument defining the function '
                'to be called is missing.')
        try:
            func = hydpy.pub.scriptfunctions[funcname]
        except KeyError:
            available_funcs = objecttools.enumeration(
                sorted(hydpy.pub.scriptfunctions.keys()))
            raise ValueError(
                f'There is no `{funcname}` function callable by `hyd.py`.  '
                f'Choose one of the following instead: {available_funcs}.')
        argspec = inspect.getfullargspec(func)
        args_possible = argspec.args
        if argspec.defaults:
            args_required = args_possible[:-len(argspec.defaults)]
        else:
            args_required = args_possible
        nmb_args_possible = len(args_possible)
        nmb_args_required = len(args_required)
        nmb_args_given = len(args_given)
        if (nmb_args_given < nmb_args_required
                or nmb_args_given > nmb_args_possible):
            enum_args_given = ''
            if nmb_args_given:
                enum_args_given = (f' ({objecttools.enumeration(args_given)})')
            if nmb_args_given < nmb_args_required:
                args = args_required
                nmb_args = nmb_args_required
                condition = 'requires'
            else:
                args = args_possible
                nmb_args = nmb_args_possible
                condition = 'allows'
            enum_args = ''
            if nmb_args:
                enum_args = f' ({objecttools.enumeration(args)})'
            raise ValueError(
                f'Function `{funcname}` {condition} `{nmb_args:d}` '
                f'positional arguments{enum_args}, but '
                f'`{nmb_args_given:d}` are given{enum_args_given}.')
        with _activate_logfile(logfilepath, logstyle, 'info', 'warning'):
            return func(*args_given, **kwargs_given)
    except BaseException as exc:
        if logstyle not in LogFileInterface.style2infotype2string:
            logstyle = 'plain'
        with _activate_logfile(logfilepath, logstyle, 'exception',
                               'exception'):
            args = sys.argv[1:]
            nmb = len(args)
            if nmb > 1:
                argphrase = f'with arguments `{", ".join(args)}`'
            elif nmb == 1:
                argphrase = f'with argument `{args[0]}`'
            else:
                argphrase = 'without arguments'
            print(
                f'Invoking hyd.py {argphrase} resulted in the following '
                f'error:\n{str(exc)}\n\n'
                f'See the following stack traceback for debugging:\n',
                file=sys.stderr)
            traceback.print_tb(sys.exc_info()[2])
        return 1
Example #10
0
    def search_elementnames(self,
                            *substrings: str,
                            name: str = 'elementnames') -> 'Selection':
        """Return a new selection containing all elements of the current
        selection with a name containing at least one of the given substrings.

        >>> from hydpy.examples import prepare_full_example_2
        >>> hp, pub, _ = prepare_full_example_2()

        Pass the (sub)strings as positional arguments and, optionally, the
        name of the newly created |Selection| object as a keyword argument:

        >>> test = pub.selections.complete.copy('test')
        >>> from hydpy import prepare_model
        >>> test.search_elementnames('dill', 'lahn_1')
        Selection("elementnames",
                  nodes=(),
                  elements=("land_dill", "land_lahn_1", "stream_dill_lahn_2",
                            "stream_lahn_1_lahn_2"))

        Wrong string specifications result in errors like the following:

        >>> test.search_elementnames(['dill', 'lahn_1'])
        Traceback (most recent call last):
        ...
        TypeError: While trying to determine the elements of selection \
`test` with names containing at least one of the given substrings \
`['dill', 'lahn_1']`, the following error occurred: 'in <string>' \
requires string as left operand, not list

        Method |Selection.select_elementnames| restricts the current selection
        to the one determined with the method |Selection.search_elementnames|:

        >>> test.select_elementnames('dill', 'lahn_1')
        Selection("test",
                  nodes=("dill", "lahn_1", "lahn_2", "lahn_3"),
                  elements=("land_dill", "land_lahn_1", "stream_dill_lahn_2",
                            "stream_lahn_1_lahn_2"))

        On the contrary, the method |Selection.deselect_elementnames|
        restricts the current selection to all devices not determined
        by the method |Selection.search_elementnames|:

        >>> pub.selections.complete.deselect_elementnames('dill', 'lahn_1')
        Selection("complete",
                  nodes=("dill", "lahn_1", "lahn_2", "lahn_3"),
                  elements=("land_lahn_2", "land_lahn_3",
                            "stream_lahn_2_lahn_3"))
        """
        try:
            selection = Selection(name)
            for element in self.elements:
                for substring in substrings:
                    if substring in element.name:
                        selection.elements += element
                        break
            return selection
        except BaseException:
            values = objecttools.enumeration(substrings)
            objecttools.augment_excmessage(
                f'While trying to determine the elements of selection '
                f'`{self.name}` with names containing at least one '
                f'of the given substrings `{values}`')
Example #11
0
    def search_modeltypes(self,
                          *models: ModelTypesArg,
                          name: str = 'modeltypes') -> 'Selection':
        """Return a |Selection| object containing only the elements
        currently handling models of the given types.

        >>> from hydpy.examples import prepare_full_example_2
        >>> hp, pub, _ = prepare_full_example_2()

        You can pass both |Model| objects and names and, as a keyword
        argument, the name of the newly created |Selection| object:

        >>> test = pub.selections.complete.copy('test')
        >>> from hydpy import prepare_model
        >>> hland_v1 = prepare_model('hland_v1')

        >>> test.search_modeltypes(hland_v1)
        Selection("modeltypes",
                  nodes=(),
                  elements=("land_dill", "land_lahn_1", "land_lahn_2",
                            "land_lahn_3"))
        >>> test.search_modeltypes(
        ...     hland_v1, 'hstream_v1', 'lland_v1', name='MODELTYPES')
        Selection("MODELTYPES",
                  nodes=(),
                  elements=("land_dill", "land_lahn_1", "land_lahn_2",
                            "land_lahn_3", "stream_dill_lahn_2",
                            "stream_lahn_1_lahn_2", "stream_lahn_2_lahn_3"))

        Wrong model specifications result in errors like the following:

        >>> test.search_modeltypes('wrong')
        Traceback (most recent call last):
        ...
        ModuleNotFoundError: While trying to determine the elements of \
selection `test` handling the model defined by the argument(s) `wrong` \
of type(s) `str`, the following error occurred: \
No module named 'hydpy.models.wrong'

        Method |Selection.select_modeltypes| restricts the current selection to
        the one determined with the method the |Selection.search_modeltypes|:

        >>> test.select_modeltypes(hland_v1)
        Selection("test",
                  nodes=(),
                  elements=("land_dill", "land_lahn_1", "land_lahn_2",
                            "land_lahn_3"))

        On the contrary, the method |Selection.deselect_upstream| restricts
        the current selection to all devices not determined by method the
        |Selection.search_upstream|:

        >>> pub.selections.complete.deselect_modeltypes(hland_v1)
        Selection("complete",
                  nodes=(),
                  elements=("stream_dill_lahn_2", "stream_lahn_1_lahn_2",
                            "stream_lahn_2_lahn_3"))
        """
        try:
            typelist = []
            for model in models:
                if not isinstance(model, modeltools.Model):
                    model = importtools.prepare_model(model)
                typelist.append(type(model))
            typetuple = tuple(typelist)
            selection = Selection(name)
            for element in self.elements:
                if isinstance(element.model, typetuple):
                    selection.elements += element
            return selection
        except BaseException:
            values = objecttools.enumeration(models)
            classes = objecttools.enumeration(
                objecttools.classname(model) for model in models)
            objecttools.augment_excmessage(
                f'While trying to determine the elements of selection '
                f'`{self.name}` handling the model defined by the '
                f'argument(s) `{values}` of type(s) `{classes}`')