def _parse_range(range_str):
    """
    Parse a range string for the problem into a dict or list of dict if
    multi-fit.

    :param range_str: The a string to parse
    :type range_str: string

    :return: The ranges in a dictionary with key as the var and value as a
             list with min and max
             e.g. {'x': [0, 10]}
    :rtype: dict
    """
    if not range_str:
        return {}

    output_ranges = {}
    range_str = range_str.strip('{').strip('}')
    tmp_ranges = range_str.split(',')
    ranges = []
    cur_str = ''
    for r in tmp_ranges:
        cur_str += r
        balanced = True
        for lb, rb in ['[]', '{}', '()']:
            if cur_str.count(lb) > cur_str.count(rb):
                balanced = False
            elif cur_str.count(lb) < cur_str.count(rb):
                raise ParsingError(
                    'Unbalanced brackets in range: {}'.format(r))
        if balanced:
            ranges.append(cur_str)
            cur_str = ''
        else:
            cur_str += ','

    for r in ranges:
        name, val = r.split(':')
        name = name.strip().strip('"').strip("'").lower()

        # Strip off brackets and split on comma
        val = val.strip(' ')[1:-1].split(',')
        val = [v.strip() for v in val]
        try:
            pair = [float(val[0]), float(val[1])]
        except ValueError:
            raise ParsingError('Expected floats in range: {}'.format(r))

        if pair[0] >= pair[1]:
            raise ParsingError('Min value must be smaller than max value '
                               'in range: {}'.format(r))

        output_ranges[name] = pair

    return output_ranges
Esempio n. 2
0
def nist_func_definition(function, param_names):
    """
    Processing a function plus different set of starting values as specified in
    the NIST problem definition file into a callable

    :param function: function string as defined in a NIST problem definition
                     file
    :type function: str
    :param param_names: names of the parameters in the function
    :type param_names: list

    :return: callable function
    :rtype: callable
    """
    function_scipy_format = format_function_scipy(function)

    # Create a function def for each starting set in startvals
    if not is_safe(function_scipy_format):
        raise ParsingError('Error while sanitizing input')
    # Sanitizing of function_scipy_format is done so exec use is valid
    # Param_names is sanitized in get_nist_param_names_and_values
    # pylint: disable=exec-used

    local_dict = {}
    global_dict = {'__builtins__': {}, 'np': np}
    exec("def fitting_function(x, " + ','.join(param_names) + "): return "
         + function_scipy_format, global_dict, local_dict)

    return local_dict['fitting_function']
Esempio n. 3
0
    def _parse_starting_values(self, lines):
        """
        Parses the starting values of a NIST file and converts them into an
        array.

        :param lines: All lines in the imported nist file
        :type lines: list of str

        :return: The starting values used in NIST problem
        :rtype: list of list of float
        """
        starting_vals = []
        for line in lines:
            if not line.strip() or line.startswith('Residual'):
                break

            startval_str = line.split()
            if not startval_str[0].isalnum():
                raise ParsingError('Could not parse starting parameters.')

            alt_values = self._get_startvals_floats(startval_str)

            if not starting_vals:
                starting_vals = [OrderedDict() for _ in alt_values]
            for i, a in enumerate(alt_values):
                starting_vals[i][startval_str[0]] = a

        return starting_vals
Esempio n. 4
0
    def _get_equation_text(self, lines, idxerr, idx):
        """
        Gets the equation text from the NIST file.

        :param lines: All lines in the imported nist file
        :type lines: list of str
        :param idxerr: boolean that points out if there were any problems
                       in finding the equation in the file
        :type idxerr: bool
        :param idx: the line at which the parser is at
        :type idx: int

        :return: The equation from the NIST file and the
                 new index
        :rtype: str and int
        """

        # Next non-empty lines are assumed to continue the equation
        equation_text = ''
        if idxerr is False:
            while lines[idx].strip():
                equation_text += lines[idx].strip()
                idx += 1

        if not equation_text:
            raise ParsingError("Could not find the equation!")

        return equation_text, idx
def _get_data_points(data_file_path):
    """
    Get the data points of the problem from the data file.

    :param data_file_path: The path to the file to load the points from
    :type data_file_path: str

    :return: data points
    :rtype: np.ndarray
    """

    with open(data_file_path, 'r') as f:
        data_text = f.readlines()

    # Find the line where data starts
    # i.e. the first line with a float on it
    first_row = 0

    # Loop until break statement
    while True:
        try:
            line = data_text[first_row].strip()
        except IndexError:
            raise ParsingError('Could not find data points')
        if line != '':
            x_val = line.split()[0]
            try:
                _ = float(x_val)
            except ValueError:
                pass
            else:
                break
        first_row += 1

    dim = len(data_text[first_row].split())
    data_points = np.zeros((len(data_text) - first_row, dim))

    for idx, line in enumerate(data_text[first_row:]):
        point_text = line.split()
        # Skip any values that can't be represented
        try:
            point = [float(val) for val in point_text]
        except ValueError:
            point = [np.nan for val in point_text]
        data_points[idx, :] = point

    # Strip all np.nan entries
    data_points = data_points[~np.isnan(data_points[:, 0]), :]

    return data_points
def _check_data(count, x, y, e):
    """
    Check that then number of data points for x, y, and errors are consistent.

    :param count: Expected number of datapoints
    :type count: int
    :param x: Number of x data points
    :type x: int
    :param y: Number of y data points
    :type y: int
    :param e: Number of error data points
    :type e: int
    :raises ParsingError: If x, y, or e does not match count. (e can also be 0)
    """

    if x != count:
        raise ParsingError('Wrong number of x data points. Got {}, '
                           'expected {}'.format(x, count))
    if y != count:
        raise ParsingError('Wrong number of y data points. Got {}, '
                           'expected {}'.format(y, count))
    if e not in (0, count):
        raise ParsingError('Wrong number of e data points. Got {}, '
                           'expected {}'.format(e, count))
def nist_jacobian_definition(jacobian, param_names):
    """
    Processing a Jacobian plus different set of starting values as specified in
    the NIST problem definition file into a callable

    :param jacobian: Jacobian string as defined in the data files for the
                     corresponding NIST problem definition file
    :type jacobian: str
    :param param_names: names of the parameters in the function
    :type param_names: list

    :return: callable function
    :rtype: callable
    """

    scipy_jacobian = []
    for jacobian_lines in jacobian:
        jacobian_scipy_format = format_function_scipy(jacobian_lines)
        # Create a function def for each starting set in startvals
        if not is_safe(jacobian_scipy_format):
            raise ParsingError('Error while sanitizing Jacobian input')

        # Checks to see if the value is an integer and if so reformats the
        # value to be a constant vector.
        if is_int(jacobian_scipy_format):
            jacobian_scipy_format += "*(np.ones(x.shape[0]))"
        scipy_jacobian.append(jacobian_scipy_format)
    jacobian_format = "-np.array([{}]).T".format(",".join(scipy_jacobian))

    new_param_name = "params"
    for i, name in enumerate(param_names):
        jacobian_format = jacobian_format.replace(
            name, "{0}[{1}]".format(new_param_name, i))

    # Sanitizing of jacobian_scipy_format is done so exec use is valid
    # Param_names is sanitized in get_nist_param_names_and_values
    # pylint: disable=exec-used
    local_dict = {}
    global_dict = {'__builtins__': {}, 'np': np}
    exec(
        "def jacobian_function(x, " + new_param_name + "): return " +
        jacobian_format, global_dict, local_dict)

    return local_dict['jacobian_function']
Esempio n. 8
0
    def _parse_equation(self, eq_text):
        """
        Parses the equation and converts it to the right format.

        :param eq_text: The equation
        :type eq_text: str

        :return: formatted equation
        :rtype: str
        """

        start_normal = r'\s*y\s*=(.+)'
        if re.match(start_normal, eq_text):
            match = re.search(r'y\s*=(.+)\s*\+\s*e', eq_text)
            equation = match.group(1).strip()
        else:
            raise ParsingError("Unrecognized equation syntax when trying to "
                               "parse a NIST equation: " + eq_text)

        equation = self._convert_nist_to_muparser(equation)
        return equation
Esempio n. 9
0
    def _get_data_txt(self, lines, idx):
        """
        Gets the data pattern from the NIST problem file.

        :param lines: All lines in the imported nist file
        :type lines: list of str
        :param idx: the line at which the parser is at
        :type idx: int

        :return: The data pattern and the new index
        :rtype: (list of str) and int
        """

        data_text = None
        data_text = lines[idx:]
        idx = len(lines)

        if not data_text:
            raise ParsingError("Could not find the data!")

        return data_text, idx
Esempio n. 10
0
    def _get_startvals_floats(self, startval_str):
        """
        Converts the starting values into floats.

        :param startval_str: Unparsed starting values
        :type startval_str: list of str

        :return: Starting values array of floats
        :rtype: list of float
        """

        # A bit weak/lax parsing, if there is one less column,
        # assume only one start point
        if len(startval_str) == 6:
            alt_values = [float(startval_str[2]), float(startval_str[3])]
        elif len(startval_str) == 5:
            alt_values = [float(startval_str[2])]
        # In the NIST format this can only contain 5 or 6 columns
        else:
            raise ParsingError("Failed to parse this line as starting "
                               "values information: {0}".format(startval_str))

        return alt_values
    def _parse_function(self):
        """
        Get the params from the function as a list of dicts from the data
        file.

        :return: Function definition in format:
                 [{name1: value1, name2: value2, ...}, ...]
        :rtype: list of dict
        """
        # pylint: disable=too-many-branches, too-many-statements
        function_def = []

        for f in self._entries['function'].split(';'):
            params_dict = OrderedDict()
            pop_stack = 0

            stack = [params_dict]
            for p in f.split(','):
                name, val = p.split('=', 1)
                name = name.strip()
                val = val.strip()

                l_count = val.count('(')
                r_count = val.count(')')
                if l_count > r_count:
                    # in brackets
                    # should be of the form 'varname=(othervar=3, ...)'

                    # Allow for nested brackets e.g. 'a=(b=(c=(d=1,e=2)))'
                    for _ in range(l_count - r_count):
                        # Cover case where formula mistyped
                        if '=' not in val:
                            raise ParsingError('Unbalanced brackets in '
                                               'function value: {}'.format(p))
                        # Must start with brackets
                        if val[0] != '(':
                            raise ParsingError('Bad placement of opening '
                                               'bracket in function: '
                                               '{}'.format(p))
                        # Create new dict for this entry and put at top of
                        # working stack
                        new_dict = OrderedDict()
                        stack[-1][name] = new_dict
                        stack.append(new_dict)
                        # Update name and val
                        name, val = val[1:].split('=', 1)
                elif l_count == r_count:
                    # Check if single item in brackets
                    while '=' in val:
                        if val[0] == '(' and val[-1] == ')':
                            val = val[1:-1]
                            new_dict = OrderedDict()
                            stack[-1][name] = new_dict
                            stack.append(new_dict)
                            name, val = val.split('=', 1)
                            pop_stack += 1
                        else:
                            raise ParsingError('Function value contains '
                                               'unexpected "=": {}'.format(p))
                elif l_count < r_count:
                    # exiting brackets
                    pop_stack = r_count - l_count
                    # must end with brackets
                    if val[-pop_stack:] != ')' * pop_stack:
                        raise ParsingError('Bad placement of closing bracket '
                                           'in function: {}'.format(p))
                    val = val[:-pop_stack]

                # Parse to an int/float if possible else assume string
                tmp_val = None
                for t in [int, float]:
                    if tmp_val is None:
                        try:
                            tmp_val = t(val)
                        except (ValueError, TypeError):
                            pass

                if tmp_val is not None:
                    val = tmp_val

                stack[-1][name] = val

                if pop_stack > 0:
                    if len(stack) <= pop_stack:
                        raise ParsingError('Too many closing brackets in '
                                           'function: {}'.format(p))
                    stack = stack[:-pop_stack]
                    pop_stack = 0

            if len(stack) != 1:
                raise ParsingError('Not all brackets are closed in function.')
            function_def.append(params_dict)

        return function_def