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}." )
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}.")
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)))
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]
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]
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
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
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)}")
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
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}`')
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}`')