Example #1
0
    def _get_model_par_seq(self, par_seq, name):
        """
        Converts a provided sequence `par_seq` of model parameter names and
        indices to a list of indices, removes duplicates and checks if every
        provided name/index is valid.

        Parameters
        ----------
        par_seq : 1D array_like of {int, str}
            A sequence of integers and strings determining which model
            parameters need to be used for a certain operation.
        name : str
            A string stating the name of the variable the result of this method
            will be stored in. Used for error messages.

        Returns
        -------
        par_seq_conv : list of int
            The provided sequence `par_seq` converted to a sorted list of
            model parameter indices.

        """

        # Do some logging
        logger = getCLogger('INIT')
        logger.info("Converting sequence of model parameter names/indices.")

        # Remove all unwanted characters from the string and split it up
        par_seq = split_seq(par_seq)

        # Check elements if they are ints or strings, and if they are valid
        for i, par_idx in enumerate(par_seq):
            try:
                # If par_idx is a string, try to use it as a parameter name
                if isinstance(par_idx, str):
                    par_seq[i] = self._par_name.index(par_idx)
                # If not, try to use it as a parameter index
                else:
                    self._par_name[par_idx]
                    par_seq[i] = par_idx % self._n_par
            # If any operation above fails, raise error
            except Exception as error:
                err_msg = ("Input argument %r[%i] is invalid! (%s)" %
                           (name, i, error))
                raise_error(err_msg, InputError, logger)

        # If everything went without exceptions, check if list is not empty and
        # remove duplicates
        if par_seq:
            par_seq = list(sset(par_seq))
        else:
            err_msg = "Input argument %r is empty!" % (name)
            raise_error(err_msg, ValueError, logger)

        # Log end
        logger.info("Finished converting sequence of model parameter "
                    "names/indices.")

        # Return it
        return (par_seq)
Example #2
0
def test_raise_error():
    # Create a logger and check if an error can be properly raised and logged
    logger = logging.getLogger('TEST')
    with pytest.raises(ValueError, match='ERROR'):
        raise_error('ERROR', ValueError, logger)
    try:
        raise ValueError('Error')
    except Exception as error:
        with pytest.raises(ValueError, match='Test Error'):
            raise_error('Test ' + str(error), type(error), logger,
                        error.__traceback__)
Example #3
0
def check_compatibility(emul_version):
    """
    Checks if the provided `emul_version` is compatible with the current
    version of *PRISM*.
    Raises a :class:`~RequestError` if *False* and indicates which version of
    *PRISM* still supports the provided `emul_version`.

    """

    # Do some logging
    logger = getCLogger('COMP_CHECK')
    logger.info("Performing version compatibility check.")

    # Loop over all compatibility versions
    for version in compat_version:
        # If a compat_version is the same or newer than the emul_version
        # then it is incompatible
        if compare_versions(version, emul_version):
            err_msg = ("The provided emulator is incompatible with the current"
                       " version of PRISM (v%s). The last compatible version "
                       "is v%s." % (__version__, version))
            raise_error(err_msg, RequestError, logger)

    # Check if emul_version is 1.0.x and raise warning if so
    if not compare_versions(emul_version, '1.1.0'):
        warn_msg = ("The provided emulator was constructed with an "
                    "unmaintained version of PRISM (v%s). Compatibility with "
                    "the current version of PRISM cannot be guaranteed." %
                    (emul_version))
        raise_warning(warn_msg, RequestWarning, logger, 2)

    # Check if emul_version is not newer than prism_version
    if not compare_versions(__version__, emul_version):
        err_msg = ("The provided emulator was constructed with a version later"
                   " than the current version of PRISM (v%s). Use v%s or later"
                   " to use this emulator." % (__version__, emul_version))
        raise_error(err_msg, RequestError, logger)
    else:
        logger.info("Version compatibility check was successful.")
Example #4
0
    def _check_mod_set(self, mod_set, name):
        """
        Checks validity of provided set of model outputs `mod_set` in this
        :obj:`~ModelLink` instance.

        Parameters
        ----------
        mod_set : 1D or 2D array_like or dict
            Model output (set) to validate in this :obj:`~ModelLink` instance.
        name : str
            The name of the model output (set), which is used in the error
            message if the validation fails.

        Returns
        -------
        mod_set : 1D or 2D :obj:`~numpy.ndarray` object
            The provided `mod_set` if the validation was successful. If
            `mod_set` was a dict, it will be converted to a
            :obj:`~numpy.ndarray` object (sorted on :attr:`~data_idx`).

        """

        # Make logger
        logger = getCLogger('CHECK')
        logger.info("Validating provided set of model outputs %r." % (name))

        # If mod_set is a dict, try to convert it to a NumPy array
        if isinstance(mod_set, dict):
            try:
                mod_set = np_array([mod_set[idx] for idx in self._data_idx]).T
            except KeyError as error:
                err_msg = (
                    "Input argument %r is missing data identifier '%r'!" %
                    (name, error.args[0]))
                raise_error(err_msg, KeyError, logger)

        # Make sure that mod_set is a NumPy array
        mod_set = np_array(mod_set)

        # Raise error if mod_set is not 1D or 2D
        if not (mod_set.ndim == 1 or mod_set.ndim == 2):
            err_msg = ("Input argument %r is not one-dimensional or "
                       "two-dimensional!" % (name))
            raise_error(err_msg, ShapeError, logger)

        # Raise error if mod_set does not have n_data data values
        if not (mod_set.shape[-1] == self._n_data):
            err_msg = ("Input argument %r has incorrect number of data values "
                       "(%i != %i)!" % (name, mod_set.shape[-1], self._n_data))
            raise_error(err_msg, ShapeError, logger)

        # Check if mod_set solely consists out of floats
        mod_set = check_vals(mod_set, name, 'float')

        # Log again and return mod_set
        logger.info("Finished validating provided set of model outputs %r." %
                    (name))
        return (mod_set)
Example #5
0
def check_vals(values, name, *args):
    """
    Checks if all values in provided input argument `values` with `name` meet
    all criteria given in `args`. If no criteria are given, it is checked if
    all values are finite.
    Returns `values` (0 or 1 in case of bool) if *True* and raises a
    :class:`~ValueError` or :class:`~TypeError` if *False*.

    Parameters
    ----------
    values : array_like of {bool; complex; float; int; str}
        The values to be checked against all given criteria in `args`. It must
        be possible to convert `values` to a :obj:`~numpy.ndarray` object.
    name : str
        The name of the input argument, which is used in the error message if
        a criterion is not met.
    args : positional arguments in {'bool'; 'complex'; 'float'; 'int'; 'neg'; \
                                    'nneg'; 'normal'; 'npos'; 'nzero'; 'pos'; \
                                    'str'}
        Sequence of strings determining the criteria that `values` must meet.
        If `args` is empty, it is checked if `values` are finite.

    Returns
    -------
    return_values : array_like of {complex; float; int; str}
        If `args` contained 'bool', returns 0s or 1s. Else, returns `values`.

    Notes
    -----
    If `values` contains integers, but `args` contains 'float', `return_values`
    will be cast as float.

    """

    # Define logger
    logger = getRLogger('CHECK')

    # Convert args to a list
    args = list(args)

    # Check type of values
    if isinstance(values, tuple):
        arr_type = 'tuple'
    elif isinstance(values, list):
        arr_type = 'list'
    elif isinstance(values, np.ndarray):
        arr_type = 'ndarray'
    elif np.isscalar(values) or values is None:
        arr_type = 'scalar'
    else:
        err_msg = "Input argument %r is not array_like!" % (name)
        raise_error(err_msg, InputError, logger)

    # Convert values to a NumPy array
    try:
        values = np.asanyarray(values)
    except Exception as error:  # pragma: no cover
        err_msg = ("Input argument %r cannot be converted to a NumPy array! "
                   "(%s)" % (name, error))
        raise_error(err_msg, InputError, logger)
    else:
        # Since NumPy v1.16.0, anything can be converted to NumPy arrays
        # So, check if the dtype is not np.object_
        if issubclass(values.dtype.type, np.object_):
            err_msg = ("Input argument %r cannot be converted to a NumPy "
                       "dtype!" % (name))
            raise_error(err_msg, TypeError, logger)

    # Check if values is not empty and raise error if so
    if not values.size:
        err_msg = "Input argument %r is empty!" % (name)
        raise_error(err_msg, InputError, logger)

    # Loop over all criteria
    while args:
        # Check for bool
        if 'bool' in args:
            # Convert values to str
            values = np.char.lower(np.asanyarray(values, dtype=str))

            # Check if values available are accepted as bools
            check_list = np.zeros_like(values, dtype=int, subok=False)
            check_list[values == '0'] = 1
            check_list[values == 'false'] = 1
            values[values == 'false'] = '0'
            check_list[values == '1'] = 1
            check_list[values == 'true'] = 1
            values[values == 'true'] = '1'

            # Check if check_list solely contains 1s
            if not check_list.all():
                # If not, raise error
                index = np.unravel_index(np.argmin(check_list), values.shape)
                err_msg = ("Input argument '%s%s' is not of type 'bool'!" %
                           (name, list(index) if values.ndim != 0 else ''))
                raise_error(err_msg, TypeError, logger)
            else:
                # If so, convert values to integers and break the loop
                values = np.asanyarray(values, dtype=int)
                break

        # Check for string
        elif 'str' in args:
            # Check if str is provided and break if so
            if issubclass(values.dtype.type, str):
                break
            else:
                err_msg = "Input argument %r is not of type 'str'!" % (name)
                raise_error(err_msg, TypeError, logger)

        # Check for complex
        elif 'complex' in args:
            # Check if complex is provided and continue if so
            if issubclass(values.dtype.type,
                          (np.integer, np.floating, np.complexfloating)):
                # Remove 'complex' from args and check it again
                args.remove('complex')
                values = np.asanyarray(values, dtype=complex)
                continue
            else:
                err_msg = ("Input argument %r is not of type 'complex'!" %
                           (name))
                raise_error(err_msg, TypeError, logger)

        # Check for float
        elif 'float' in args:
            # Check if float is provided and continue if so
            if issubclass(values.dtype.type, (np.integer, np.floating)):
                # Remove 'float' from args and check it again
                args.remove('float')
                values = np.asanyarray(values, dtype=float)
                continue
            else:
                err_msg = "Input argument %r is not of type 'float'!" % (name)
                raise_error(err_msg, TypeError, logger)

        # Check for integer
        elif 'int' in args:
            # Check if int is provided and continue if so
            if issubclass(values.dtype.type, np.integer):
                # Remove 'int' from args and check it again
                args.remove('int')
                continue
            else:
                err_msg = "Input argument %r is not of type 'int'!" % (name)
                raise_error(err_msg, TypeError, logger)

        # Check for negative value
        elif 'neg' in args:
            # Check if value is negative and continue if so
            try:
                index = list(np.argwhere(values >= 0)[0])
            except IndexError:
                args.remove('neg')
                continue
            else:
                err_msg = ("Input argument '%s%s' is not negative!" %
                           (name, index if values.ndim != 0 else ''))
                raise_error(err_msg, ValueError, logger)

        # Check for non-negative value
        elif 'nneg' in args:
            # Check if value is non-negative and continue if so
            try:
                index = list(np.argwhere(values < 0)[0])
            except IndexError:
                args.remove('nneg')
                continue
            else:
                err_msg = ("Input argument '%s%s' is not non-negative!" %
                           (name, index if values.ndim != 0 else ''))
                raise_error(err_msg, ValueError, logger)

        # Check for normalized value [-1, 1]
        elif 'normal' in args:
            # Check if value is normal and continue if so
            try:
                index = list(np.argwhere(abs(values) > 1)[0])
            except IndexError:
                args.remove('normal')
                continue
            else:
                err_msg = ("Input argument '%s%s' is not normalized!" %
                           (name, index if values.ndim != 0 else ''))
                raise_error(err_msg, ValueError, logger)

        # Check for non-positive value
        elif 'npos' in args:
            # Check if value is non-positive and continue if so
            try:
                index = list(np.argwhere(values > 0)[0])
            except IndexError:
                args.remove('npos')
                continue
            else:
                err_msg = ("Input argument '%s%s' is not non-positive!" %
                           (name, index if values.ndim != 0 else ''))
                raise_error(err_msg, ValueError, logger)

        # Check for non-zero value
        elif 'nzero' in args:
            # Check if value is non-zero and continue if so
            try:
                index = list(np.argwhere(values == 0)[0])
            except IndexError:
                args.remove('nzero')
                continue
            else:
                err_msg = ("Input argument '%s%s' is not non-zero!" %
                           (name, index if values.ndim != 0 else ''))
                raise_error(err_msg, ValueError, logger)

        # Check for positive value
        elif 'pos' in args:
            # Check if value is positive and continue if so
            try:
                index = list(np.argwhere(values <= 0)[0])
            except IndexError:
                args.remove('pos')
                continue
            else:
                err_msg = ("Input argument '%s%s' is not positive!" %
                           (name, index if values.ndim != 0 else ''))
                raise_error(err_msg, ValueError, logger)

        # If none of the criteria is found, the criteria are invalid
        else:
            err_msg = (
                "Input argument 'args' contains invalid elements (%s)!" %
                (args))
            raise_error(err_msg, ValueError, logger)

    # If no criteria are left, it must be a finite value
    else:
        # Check if value is finite and continue if so
        try:
            index = list(np.argwhere(~np.isfinite(values))[0])
        except IndexError:
            pass
        except TypeError:
            err_msg = (
                "Input argument '%s%s' is not of type 'int' or 'float'!" %
                (name, index if values.ndim != 0 else ''))
            raise_error(err_msg, TypeError, logger)
        else:
            err_msg = ("Input argument '%s%s' is not finite!" %
                       (name, index if values.ndim != 0 else ''))
            raise_error(err_msg, ValueError, logger)

    # Convert values back to its original type
    if (arr_type == 'tuple'):
        values = tuple(values.tolist())
    elif (arr_type == 'list'):
        values = values.tolist()
    elif (arr_type == 'scalar'):
        values = values.item()

    # Return values
    return (values)
Example #6
0
    def _check_md_var(self, md_var, name):
        """
        Checks validity of provided set of model discrepancy variances `md_var`
        in this :obj:`~ModelLink` instance.

        Parameters
        ----------
        md_var : 1D or 2D array_like or dict
            Model discrepancy variance set to validate in this
            :obj:`~ModelLink` instance.
        name : str
            The name of the model discrepancy set, which is used in the error
            message if the validation fails.

        Returns
        -------
        md_var : 2D :obj:`~numpy.ndarray` object
            The (converted) provided `md_var` if the validation was successful.
            If `md_var` was a dict, it will be converted to a
            :obj:`~numpy.ndarray` object.

        """

        # Make logger
        logger = getCLogger('CHECK')
        logger.info("Validating provided set of model discrepancy variances "
                    "%r." % (name))

        # If md_var is a dict, convert it to a NumPy array
        if isinstance(md_var, dict):
            md_var = np_array([md_var[idx] for idx in md_var.keys()])

        # Make sure that md_var is a NumPy array
        md_var = np_array(md_var)

        # Raise error if md_var is not 1D or 2D
        if not (md_var.ndim == 1 or md_var.ndim == 2):
            err_msg = ("Input argument %r is not one-dimensional or "
                       "two-dimensional!" % (name))
            raise_error(err_msg, ShapeError, logger)

        # Check if md_var contains n_data values
        if not (md_var.shape[0] == self._n_data):
            err_msg = ("Received array of model discrepancy variances %r has "
                       "incorrect number of data points (%i != %i)!" %
                       (name, md_var.shape[0], self._n_data))
            raise ShapeError(err_msg)

        # Check if single or dual values were given
        if (md_var.ndim == 1):
            md_var = np_array([md_var] * 2).T
        elif (md_var.shape[1] == 2):
            pass
        else:
            err_msg = ("Received array of model discrepancy variances %r has "
                       "incorrect number of values (%i != 2)!" %
                       (name, md_var.shape[1]))
            raise ShapeError(err_msg)

        # Check if all values are non-negative floats
        md_var = check_vals(md_var, 'md_var', 'nneg', 'float')

        # Log again and return md_var
        logger.info("Finished validating provided set of model discrepancy "
                    "variances %r." % (name))
        return (md_var)
Example #7
0
    def _check_sam_set(self, sam_set, name):
        """
        Checks validity of provided set of model parameter samples `sam_set` in
        this :obj:`~ModelLink` instance.

        Parameters
        ----------
        sam_set : 1D or 2D array_like or dict
            Parameter/sample set to validate in this :obj:`~ModelLink`
            instance.
        name : str
            The name of the parameter/sample set, which is used in the error
            message if the validation fails.

        Returns
        -------
        sam_set : 1D or 2D :obj:`~numpy.ndarray` object
            The provided `sam_set` if the validation was successful. If
            `sam_set` was a dict, it will be converted to a
            :obj:`~numpy.ndarray` object.

        """

        # Make logger
        logger = getCLogger('CHECK')
        logger.info("Validating provided set of model parameter samples %r." %
                    (name))

        # If sam_set is a dict, convert it to a NumPy array
        if isinstance(sam_set, dict):
            sam_set = np_array(sdict(sam_set).values()).T

        # Make sure that sam_set is a NumPy array
        sam_set = np_array(sam_set)

        # Raise error if sam_set is not 1D or 2D
        if not (sam_set.ndim == 1 or sam_set.ndim == 2):
            err_msg = ("Input argument %r is not one-dimensional or "
                       "two-dimensional!" % (name))
            raise_error(err_msg, ShapeError, logger)

        # Raise error if sam_set does not have n_par parameter values
        if not (sam_set.shape[-1] == self._n_par):
            err_msg = ("Input argument %r has incorrect number of parameters "
                       "(%i != %i)!" % (name, sam_set.shape[-1], self._n_par))
            raise_error(err_msg, ShapeError, logger)

        # Check if sam_set solely consists out of floats
        sam_set = check_vals(sam_set, name, 'float')

        # Check if all samples are within parameter space
        sam_set_2D = np_array(sam_set, ndmin=2)
        rng = self._par_rng
        check = np.apply_along_axis(
            lambda x: ((rng[:, 0] <= x) * (x <= rng[:, 1])).all(), 1,
            sam_set_2D)

        # If check is not empty (can be indexed), raise error
        try:
            index = np.argwhere(~check)[0]
        except IndexError:
            pass
        else:
            err_msg = ("Input argument '%s%s' is outside parameter space!" %
                       (name, index if sam_set.ndim != 1 else ''))
            raise_error(err_msg, ValueError, logger)

        # Log again and return sam_set
        logger.info("Finished validating provided set of model parameter "
                    "samples %r." % (name))
        return (sam_set)
Example #8
0
def start_gui(pipeline_obj):  # pragma: no cover
    # Create a logger
    logger = getCLogger('GUI')
    logger.info("Starting %s." % (APP_NAME))

    # Import Pipeline class here to avoid an ImportError
    from prism import Pipeline

    # Check if provided pipeline_obj is an instance of the Pipeline class
    if not isinstance(pipeline_obj, Pipeline):
        err_msg = ("Input argument 'pipeline_obj' must be an instance of the "
                   "Pipeline class!")
        raise_error(err_msg, TypeError, logger)

    # Activate worker mode
    with pipeline_obj.worker_mode:
        if pipeline_obj._is_controller:
            # Temporarily switch the backend of MPL
            cur_backend = mpl.rcParams['backend']
            switch_backend('Agg')

            # Obtain application instance
            qapp = QW.QApplication.instance()

            # If qapp is None, create a new one
            if qapp is None:
                QW.QApplication.setAttribute(QC.Qt.AA_EnableHighDpiScaling)
                qapp = QW.QApplication([APP_NAME])

            # Set name of application
            qapp.setApplicationName(APP_NAME)

            # Hide the 'whats this' tooltip on Windows
            QW.QApplication.setAttribute(
                QC.Qt.AA_DisableWindowContextHelpButton)

            # Make sure that the application quits when last window closes
            qapp.lastWindowClosed.connect(qapp.quit,
                                          QC.Qt.QueuedConnection)

            # Initialize main window and draw (show) it
            main_window = MainViewerWindow(pipeline_obj)
            main_window.show()
            main_window.raise_()
            main_window.activateWindow()

            # Replace KeyboardInterrupt error by system's default handler
            signal.signal(signal.SIGINT, signal.SIG_DFL)

            # Start application
            qapp.exec_()

            # Delete this application to stop event processing
            del qapp

            # Switch back to previous backend
            switch_backend(cur_backend)

            # Log that GUI has been closed
            logger.info("Exited %s." % (APP_NAME))

            # Return the main window instance
            return(main_window)