Example #1
0
def assign(value, error=None, unit=None, name=None, longname=None, value_unit=None, error_unit=None, ignore_dim=False):
    """ function to create a new quantity

    Args:
     value: number or string that can be parsed by numpy, or sympy
            expression. If it's a sympy expression containing quantities, it
            will perform the calculation, otherwise it just saves the value.
     error: number that is saved as the value's uncertainty. this will replace
            any error coming from a calculation.
     unit: sympy expression of Unit objects. This is used to convert and save
           value and error in base units. Replaces value_unit and error_unit if
           specified.
     name: short name of the quantity (usually one letter). If not specified,
           quantity will get a dummy name.
     longname: optional additional description of the quantity
     value_unit: unit of value. Use this if value and error have different units.
     error_unit: unit of error.
     ignore_dim: bool. Keeps function from raising an error even if calculated
                 and given unit don't match. Then given unit is used instead.
    """

    value_formula = None
    value_factor = 1
    value_dim = Dimension()

    error_formula = None
    error_factor = 1
    error_dim = Dimension()

    # parse units
    if unit is not None:
        # if one general unit is given
        value_factor, value_dim, value_unit = parse_unit(unit)
        error_factor = value_factor
        error_dim = value_dim
        error_unit = value_unit
    else:
        # if value unit is given
        if value_unit is not None:
            value_factor, value_dim, value_unit = parse_unit(value_unit)

        # if error unit is given
        if error_unit is not None:
            error_factor, error_dim, error_unit = parse_unit(error_unit)

            # check dimension consistency between value_dim and error_dim
            if value_unit is not None and not value_dim == error_dim:
                raise RuntimeError("dimension mismatch\n%s != %s" % (value_dim, error_dim))

    # process value

    # if it's a calculation
    if isinstance(value, Expr) and not value.is_number:
        value_formula = value
        value = get_value(value_formula)

        if ignore_dim:
            # with ignore_dim=True, calculated value is converted to given unit
            value = np.float_(value_factor)*np.float_(value)
        else:
            # calculate dimension from dependency
            calculated_dim = get_dimension(value_formula)
            if value_unit is None:
                value_dim = calculated_dim
            else:
                if not calculated_dim == value_dim:
                    raise RuntimeError("dimension mismatch \n%s != %s" % (value_dim, calculated_dim))

    # if it's a number
    else:
        value=np.float_(value_factor)*np.float_(value)

    # process error
    if error is not None:
        error=np.float_(error_factor)*np.float_(error)

        # check value and error shapes and duplicate error in case
        if error.shape == () or value.shape[-len(error.shape):] == error.shape:
            error = np.resize(error, value.shape)
        else:
            raise RuntimeError("length of value and error don't match and "\
                                "can't be adjusted by duplicating.\n"\
                                "%s and %s" % (value.shape, error.shape))

    # if error can be calculated
    elif value_formula is not None:
        error, error_formula = get_error(value_formula)

        if ignore_dim:
            # with ignore_dim=True, calculated error is converted to given unit
            error = np.float_(error_factor)*np.float_(error)


    q = Quantity(name, longname)
    q.value = value
    q.value_formula = value_formula
    q.error = error
    q.error_formula = error_formula
    if value_unit is not None:
        q.prefer_unit = value_unit
    else:
        q.prefer_unit = error_unit
    q.dim = value_dim

    return q
Example #2
0
def plot(expr_pairs, config, save=None, xunit=None, yunit=None, xrange=None, yrange=None, ignore_dim=False):

    # TODO it should be possible to e.g. plot the function [t,2*r] if r depends on t

    # one or multiple things to plot
    if len(expr_pairs) > 1:
        single_plot = False
    else:
        single_plot = True

    x_dim = None
    y_dim = None

    data_sets = []
    functions = []

    if ignore_dim:
        xunit = S.One
        yunit = S.One

    for expr_pair in expr_pairs:
        x = expr_pair[0]
        y = expr_pair[1]

        if not ignore_dim:
            # check dimensions
            if x_dim is None:
                x_dim = get_dimension(x)
            else:
                if not x_dim == get_dimension(x):
                    raise RuntimeError("dimension mismatch\n%s != %s" % (x_dim, get_dimension(x)))
            if y_dim is None:
                y_dim = get_dimension(y)
            else:
                if not y_dim == get_dimension(y):
                    raise RuntimeError("dimension mismatch\n%s != %s" % (y_dim, get_dimension(y)))

    	# if y contains x, it must be a function
        dummy = Quantity()
        rep_y = y.subs(x,dummy)
        if rep_y.has(dummy):
            # get titles
            if isinstance(x, Quantity):
                x_title = ((x.longname + " ") if x.longname else "") + str(x)
            else:
                x_title = str(x)
            y_title = str(y)

    		# check if x not only quantity but more complicated expression
            if not isinstance(x,Quantity):
    			# replace by Dummy
                y = rep_y
                x = dummy

            if not ignore_dim:
                # get factors
                x_factor, xunit = convert_to_unit(x_dim, outputUnit=xunit)
                y_factor, yunit = convert_to_unit(y_dim, outputUnit=yunit)

                # scale function to units
                y = y.subs(x,x*x_factor) / y_factor

            # if only one thing on plot, write labels to x-axis and y-axis
            if single_plot:
                title = None
                x_label = x_title + ("" if xunit == S.One else " [" + str(xunit) + "]")
                y_label = y_title + ("" if yunit == S.One else " [" + str(yunit) + "]")
            # if more than one thing, use legend for title
            else:
                title = y_title

            functions.append({"x":x,"term":y,"title":title})

    	# if y doesn't contain x, it must be a data set
        else:

            # calculate expressions first if necessary
            if isinstance(expr_pair[0], Quantity):
                x = expr_pair[0]
                x_title = ((x.longname + " ") if x.longname else "") + str(x)
            else:
                # dummy quantity for calculation
                x = Quantity()
                x.value = get_value(expr_pair[0])
                x.error = get_error(expr_pair[0])[0]
                x.dim = x_dim
                x_title = str(expr_pair[0])
            if isinstance(expr_pair[1], Quantity):
                y = expr_pair[1]
                y_title = ((y.longname + " ") if y.longname else "") + str(y)
            else:
                # dummy quantity for calculation
                y = Quantity()
                y.value = get_value(expr_pair[1])
                y.error = get_error(expr_pair[1])[0]
                y.dim = y_dim
                y_title = str(expr_pair[1])

            if ignore_dim:
                x_values = x.value
                x_errors = x.error
                y_values = y.value
                y_errors = y.error
            else:
                # get values and errors all in one unit
                x_values, x_errors, xunit = adjust_to_unit(x, unit = xunit)
                y_values, y_errors, yunit = adjust_to_unit(y, unit = yunit)


            # if only one thing on plot, write labels to x-axis and y-axis
            if single_plot:
                title = None
                x_label = x_title + ("" if xunit == S.One else " [" + str(xunit) + "]")
                y_label = y_title + ("" if yunit == S.One else " [" + str(yunit) + "]")
            # if more than one thing, use legend for title
            else:
                title = y_title

            data_sets.append({"x_values": x_values, "y_values": y_values, "x_errors": x_errors, "y_errors":y_errors, "title": title})



    # if more than one thing on plot, write only units to axes
    if not single_plot:
        x_label = ("" if xunit == S.One else "[" + str(xunit) + "]")
        y_label = ("" if yunit == S.One else "[" + str(yunit) + "]")

    # plot
    if config["plot_module"] == "matplotlib":
        from errorpro import plot_mat
        return plot_mat.plot(data_sets, functions, save=save, xrange=xrange, yrange=yrange, x_label=x_label, y_label=y_label)
    elif config["plot_module"] == "gnuplot":
        from errorpro import plot_gnu
        return plot_gnu.plot(data_sets, functions, save=save, xrange=xrange, yrange=yrange, x_label=x_label, y_label=y_label)
    else:
        raise ValueError("There is not plot module called '%s'" % config["plot_module"])
Example #3
0
    def assign(self, name, value=None, error=None, unit=None, longname=None, value_unit=None, error_unit=None, replace=False, ignore_dim=False):
        """ Assigns value and/or error to quantity

        Args:
            name: quantity name
            longname: description of quantity
            value: value to assign, can be expression, string, list or number
            error: error to assign, can be expression, string, list or number, but mustn't depend on other quantities
            unit: unit of both value and error, replaces 'value_unit' and 'error_unit' if given
            value_unit: value unit expression or string
            error_unit: error unit expression or string
            replace: if True, will replace quantity instead of trying to keep data
            ignore_dim: if True, will ignore calculated dimension and use given unit instead
        """

        if not unit is None:
            value_unit = unit
            error_unit = unit

        if value is None and error is None:
            raise ValueError("At least either value or error must be specified.")

        value_len = None
        value_dim = None
        value_formula = None
        error_len = None
        error_dim = None
        error_formula = None

        # if value is given
        if not value is None:

            # parse unit if given
            if not value_unit is None:
                factor, value_dim, value_unit = units.parse_unit(value_unit)

            # parse value
            if isinstance(value, list) or isinstance(value, tuple):
                # if it's a list, parse each element
                parsed_list = []
                for v in value:
                    parsed_list.append(quantities.parse_expr(v, self.data))
            elif isinstance(value, str) or isinstance(value, Expr):
                # if it's not a list, parse once
                value = quantities.parse_expr(value, self.data)

            # if it's a calculation
            if isinstance(value, Expr) and not value.is_number:
                # calculate value from dependency
                value_formula = value
                value = quantities.get_value(value_formula)

                # calculate dimension from dependency
                if not ignore_dim:
                    calculated_dim = quantities.get_dimension(value_formula)
                    if not value_dim is None and not calculated_dim == value_dim:
                        raise RuntimeError("dimension mismatch for '%s'\n%s != %s" % (name, value_dim, calculated_dim))
                    elif value_dim is None:
                        value_dim = calculated_dim
                else:
                    # if ignore_dim is True and there's no unit given -> dimensionless
                    if value_dim is None:
                        factor=1
                        value_dim = Dimension()
                        value_unit = S.One
                    # calculated value must be converted to given unit (ignore_dim=True)
                    value = np.float_(factor)*value


            # if it's a number
            else:
                # if no unit given, set dimensionless
                if value_unit is None:
                    factor = 1
                    value_dim = Dimension()
                    value_unit = S.One

                value=np.float_(factor)*np.float_(value)

            # calculate value length
            if isinstance(value,np.ndarray):
                value_len = len(value)
            else:
                value_len = 1


        # if error is given
        if not error is None:

            # parse unit if given
            if not error_unit is None:
                factor, error_dim, error_unit = units.parse_unit(error_unit)

            # parse value
            if isinstance(error, list) or isinstance(error, tuple):
                # if it's a list, parse each element
                parsed_list = []
                for u in error:
                    parsed_list.append(quantities.parse_expr(u, self.data))
            elif isinstance(error, str) or isinstance(error, Expr):
                # if it's not a list, parse once
                error = quantities.parse_expr(error, self.data)

            # make sure error is a number
            if isinstance(error, Expr) and not error.is_number:
                raise RuntimeError("error '%s' is not a number" % error)

            # if no unit given, set dimensionless
            if error_unit is None:
                factor = 1
                error_dim = Dimension()
                error_unit = S.One

            error=np.float_(factor)*np.float_(error)

            # calculate error length, ignore len(error)==1 because it can be duplicated to fit any value length
            if isinstance(error,np.ndarray):
                error_len = len(error)

        # if error can be calculated
        elif not value_formula is None:
            error, error_formula = quantities.get_error(value_formula)


        # merge dimensions
        dim = value_dim
        if not dim is None and not error_dim is None and not dim == error_dim:
            raise RuntimeError("value dimension and error dimension are not the same\n%s != %s" % (dim, error_dim))
        if not error_dim is None:
            dim = error_dim

        # merge lengths
        new_len = value_len
        if not new_len is None and not error_len is None and not new_len == error_len:
            raise RuntimeError("value length doesn't fit error length for '%s':\n%s != %s" % (name, new_len, error_len))
        if not error_len is None:
            new_len = error_len


        # if quantity didn't exist
        if not name in self.data or replace:
            self.data[name] = quantities.Quantity(name)
        # if it did exist
        else:
            # get old length, len(error)=1 is not a length, because it can be duplicated to fit any value length
            old_len = None
            if not self.data[name].value is None:
                if isinstance(self.data[name].value, np.ndarray):
                    old_len = len(self.data[name].value)
                else:
                    old_len = 1
            if not self.data[name].error is None and isinstance(self.data[name].error, np.ndarray):
                old_len = len(self.data[name].error)


            # if new dimension or new length, create new quantity
            if (not self.data[name].dim == dim or
                   (not old_len is None and not new_len is None and not old_len == new_len)):
                self.data[name] = quantities.Quantity(name)

        # save stuff
        if not longname is None:
            self.data[name].longname = longname
        if not value is None:
            self.data[name].value = value
            self.data[name].value_formula = value_formula
        if not value_unit is None:
            self.data[name].prefer_unit = value_unit
        elif not error_unit is None:
            self.data[name].prefer_unit = error_unit
        if not error is None:
            self.data[name].error = error
            self.data[name].error_formula = error_formula
        self.data[name].dim = dim



        # check if error must be duplicated to adjust to value length
        if isinstance(self.data[name].value, np.ndarray) and isinstance(self.data[name].error, np.float_):
            error_arr = np.full(len(self.data[name].value),self.data[name].error)
            self.data[name].error = error_arr
Example #4
0
    def fit(self, fit_function, xydata, parameters, weighted=None, plot=False, ignore_dim=False):
        """ fits function to data

        Args:
            fit_function: function to fit, e.g. "n*t**2 + m*t + b"
            xydata: pair of x-quantity and y-quantity of data to fit to, e.g. ["t","U"]
            parameters: list of parameters in fit function, e.g. ["n","m","b"]
            weighted: if True, will weight fit by errors (returns error if not possible)
                      if False, will not weight fit by errors
                      if None, will try to weight fit, but if at least one error is not given, will not weight it
            plot: Bool, if data and fit function should be plotted
            ignore_dim: if True, will ignore dimensions and just calculate in base units instead
        """


        if self.config["fit_module"] == "scipy":
            import errorpro.fit_scipy as fit_module
        else:
            raise ValueError("no fit module called '%s'." % self.config["fit_module"])

        # get parameter quantities
        parameters_obj = []
        for p in parameters:
            if isinstance(p, str):
                if not p in self.data:
                    self.data[p] = quantities.Quantity(p)
                    self.data[p].dim = Dimension()
                parameters_obj.append(self.data[p])
            elif isinstance(p, quantities.Quantity):
                parameters_obj.append(p)
            else:
                raise TypeError("parameters can only be strings or Quantity objects")

        # parse fit function
        fit_function = quantities.parse_expr(fit_function, self.data)

        # get data quantities
        x_data = quantities.parse_expr(xydata[0], self.data)
        # if x-data is an expression
        if not isinstance(x_data, quantities.Quantity):
            dummy = quantities.Quantity()
            fit_function = fit_function.subs(x_data,dummy)
            dummy.value = quantities.get_value(x_data)
            dummy.error = quantities.get_error(x_data)[0]
            dummy.dim = quantities.get_dimension(x_data)
            x_data = dummy
        y_data = quantities.parse_expr(xydata[1], self.data)
        # if y-data is an expression
        if not isinstance(y_data, quantities.Quantity):
            dummy = quantities.Quantity()
            dummy.value = quantities.get_value(y_data)
            dummy.error = quantities.get_error(y_data)[0]
            dummy.dim = quantities.get_dimension(y_data)
            y_data = dummy

        # check if dimension fits
        if not ignore_dim:
            try:
                dim_func = quantities.get_dimension(fit_function)
            except ValueError:
                dim_func = None
            if not dim_func == y_data.dim:
                # try to solve for dimensionless parameters
                known_dimensions = {x_data.name: x_data.dim}
                known_dimensions = dim_solve(fit_function, y_data.dim, known_dimensions)
                for q_name in known_dimensions:
                    if q_name in self.data:
                        if not self.data[q_name].dim == known_dimensions[q_name]:
                            self.data[q_name].dim = known_dimensions[q_name]
                            self.data[q_name].prefer_unit = None
                dim_func = quantities.get_dimension(fit_function)
                # if it still doesn't work, raise error
                if not dim_func == y_data.dim:
                    raise RuntimeError("Finding dimensions of fit parameters was not sucessful.\n"\
                                                     "Check fit function or specify parameter units manually.\n"\
                                                     "This error will occur until dimensions are right.")

        # fit
        values, errors = fit_module.fit(x_data, y_data, fit_function, parameters_obj, weighted)


        # save results
        i = 0
        for p in parameters_obj:
            p.value = values[i]
            p.value_formula = "fit"
            p.error = errors[i]
            p.error_formula = "fit"
            i += 1

        # plot
        if plot:
            return plotting.plot([(x_data, y_data), (x_data, fit_function)], self.config, ignore_dim=ignore_dim)
        else:
            return self.table(*parameters_obj)
Example #5
0
def plot(expr_pairs,
         config,
         save=None,
         xunit=None,
         yunit=None,
         xrange=None,
         yrange=None,
         ignore_dim=False):

    # TODO it should be possible to e.g. plot the function [t,2*r] if r depends on t

    # one or multiple things to plot
    if len(expr_pairs) > 1:
        single_plot = False
    else:
        single_plot = True

    x_dim = None
    y_dim = None

    data_sets = []
    functions = []

    if ignore_dim:
        xunit = S.One
        yunit = S.One

    for expr_pair in expr_pairs:
        x = expr_pair[0]
        y = expr_pair[1]

        if not ignore_dim:
            # check dimensions
            if x_dim is None:
                x_dim = get_dimension(x)
            else:
                if not x_dim == get_dimension(x):
                    raise RuntimeError("dimension mismatch\n%s != %s" %
                                       (x_dim, get_dimension(x)))
            if y_dim is None:
                y_dim = get_dimension(y)
            else:
                if not y_dim == get_dimension(y):
                    raise RuntimeError("dimension mismatch\n%s != %s" %
                                       (y_dim, get_dimension(y)))

    # if y contains x, it must be a function
        dummy = Quantity()
        rep_y = y.subs(x, dummy)
        if rep_y.has(dummy):
            # get titles
            if isinstance(x, Quantity):
                x_title = ((x.longname + " ") if x.longname else "") + str(x)
            else:
                x_title = str(x)
            y_title = str(y)

            # check if x not only quantity but more complicated expression
            if not isinstance(x, Quantity):
                # replace by Dummy
                y = rep_y
                x = dummy

            if not ignore_dim:
                # get factors
                x_factor, xunit = convert_to_unit(x_dim, outputUnit=xunit)
                y_factor, yunit = convert_to_unit(y_dim, outputUnit=yunit)

                # scale function to units
                y = y.subs(x, x * x_factor) / y_factor

            # if only one thing on plot, write labels to x-axis and y-axis
            if single_plot:
                title = None
                x_label = x_title + ("" if xunit == S.One else " [" +
                                     str(xunit) + "]")
                y_label = y_title + ("" if yunit == S.One else " [" +
                                     str(yunit) + "]")
            # if more than one thing, use legend for title
            else:
                title = y_title

            functions.append({"x": x, "term": y, "title": title})

    # if y doesn't contain x, it must be a data set
        else:

            # calculate expressions first if necessary
            if isinstance(expr_pair[0], Quantity):
                x = expr_pair[0]
                x_title = ((x.longname + " ") if x.longname else "") + str(x)
            else:
                # dummy quantity for calculation
                x = Quantity()
                x.value = get_value(expr_pair[0])
                x.error = get_error(expr_pair[0])[0]
                x.dim = x_dim
                x_title = str(expr_pair[0])
            if isinstance(expr_pair[1], Quantity):
                y = expr_pair[1]
                y_title = ((y.longname + " ") if y.longname else "") + str(y)
            else:
                # dummy quantity for calculation
                y = Quantity()
                y.value = get_value(expr_pair[1])
                y.error = get_error(expr_pair[1])[0]
                y.dim = y_dim
                y_title = str(expr_pair[1])

            if ignore_dim:
                x_values = x.value
                x_errors = x.error
                y_values = y.value
                y_errors = y.error
            else:
                # get values and errors all in one unit
                x_values, x_errors, xunit = adjust_to_unit(x, unit=xunit)
                y_values, y_errors, yunit = adjust_to_unit(y, unit=yunit)

            # if only one thing on plot, write labels to x-axis and y-axis
            if single_plot:
                title = None
                x_label = x_title + ("" if xunit == S.One else " [" +
                                     str(xunit) + "]")
                y_label = y_title + ("" if yunit == S.One else " [" +
                                     str(yunit) + "]")
            # if more than one thing, use legend for title
            else:
                title = y_title

            data_sets.append({
                "x_values": x_values,
                "y_values": y_values,
                "x_errors": x_errors,
                "y_errors": y_errors,
                "title": title
            })

    # if more than one thing on plot, write only units to axes
    if not single_plot:
        x_label = ("" if xunit == S.One else "[" + str(xunit) + "]")
        y_label = ("" if yunit == S.One else "[" + str(yunit) + "]")

    # plot
    if config["plot_module"] == "matplotlib":
        from errorpro import plot_mat
        return plot_mat.plot(data_sets,
                             functions,
                             save=save,
                             xrange=xrange,
                             yrange=yrange,
                             x_label=x_label,
                             y_label=y_label)
    elif config["plot_module"] == "gnuplot":
        from errorpro import plot_gnu
        return plot_gnu.plot(data_sets,
                             functions,
                             save=save,
                             xrange=xrange,
                             yrange=yrange,
                             x_label=x_label,
                             y_label=y_label)
    else:
        raise ValueError("There is not plot module called '%s'" %
                         config["plot_module"])
Example #6
0
    def assign(self,
               name,
               value=None,
               error=None,
               unit=None,
               longname=None,
               value_unit=None,
               error_unit=None,
               replace=False,
               ignore_dim=False):
        """ Assigns value and/or error to quantity

        Args:
            name: quantity name
            longname: description of quantity
            value: value to assign, can be expression, string, list or number
            error: error to assign, can be expression, string, list or number, but mustn't depend on other quantities
            unit: unit of both value and error, replaces 'value_unit' and 'error_unit' if given
            value_unit: value unit expression or string
            error_unit: error unit expression or string
            replace: if True, will replace quantity instead of trying to keep data
            ignore_dim: if True, will ignore calculated dimension and use given unit instead
        """

        if not unit is None:
            value_unit = unit
            error_unit = unit

        if value is None and error is None:
            raise ValueError(
                "At least either value or error must be specified.")

        value_len = None
        value_dim = None
        value_formula = None
        error_len = None
        error_dim = None
        error_formula = None

        # if value is given
        if not value is None:

            # parse unit if given
            if not value_unit is None:
                factor, value_dim, value_unit = units.parse_unit(value_unit)

            # parse value
            if isinstance(value, list) or isinstance(value, tuple):
                # if it's a list, parse each element
                parsed_list = []
                for v in value:
                    parsed_list.append(quantities.parse_expr(v, self.data))
            elif isinstance(value, str) or isinstance(value, Expr):
                # if it's not a list, parse once
                value = quantities.parse_expr(value, self.data)

            # if it's a calculation
            if isinstance(value, Expr) and not value.is_number:
                # calculate value from dependency
                value_formula = value
                value = quantities.get_value(value_formula)

                # calculate dimension from dependency
                if not ignore_dim:
                    calculated_dim = quantities.get_dimension(value_formula)
                    if not value_dim is None and not calculated_dim == value_dim:
                        raise RuntimeError(
                            "dimension mismatch for '%s'\n%s != %s" %
                            (name, value_dim, calculated_dim))
                    elif value_dim is None:
                        value_dim = calculated_dim
                else:
                    # if ignore_dim is True and there's no unit given -> dimensionless
                    if value_dim is None:
                        factor = 1
                        value_dim = Dimension()
                        value_unit = S.One
                    # calculated value must be converted to given unit (ignore_dim=True)
                    value = np.float_(factor) * value

            # if it's a number
            else:
                # if no unit given, set dimensionless
                if value_unit is None:
                    factor = 1
                    value_dim = Dimension()
                    value_unit = S.One

                value = np.float_(factor) * np.float_(value)

            # calculate value length
            if isinstance(value, np.ndarray):
                value_len = len(value)
            else:
                value_len = 1

        # if error is given
        if not error is None:

            # parse unit if given
            if not error_unit is None:
                factor, error_dim, error_unit = units.parse_unit(error_unit)

            # parse value
            if isinstance(error, list) or isinstance(error, tuple):
                # if it's a list, parse each element
                parsed_list = []
                for u in error:
                    parsed_list.append(quantities.parse_expr(u, self.data))
            elif isinstance(error, str) or isinstance(error, Expr):
                # if it's not a list, parse once
                error = quantities.parse_expr(error, self.data)

            # make sure error is a number
            if isinstance(error, Expr) and not error.is_number:
                raise RuntimeError("error '%s' is not a number" % error)

            # if no unit given, set dimensionless
            if error_unit is None:
                factor = 1
                error_dim = Dimension()
                error_unit = S.One

            error = np.float_(factor) * np.float_(error)

            # calculate error length, ignore len(error)==1 because it can be duplicated to fit any value length
            if isinstance(error, np.ndarray):
                error_len = len(error)

        # if error can be calculated
        elif not value_formula is None:
            error, error_formula = quantities.get_error(value_formula)

        # merge dimensions
        dim = value_dim
        if not dim is None and not error_dim is None and not dim == error_dim:
            raise RuntimeError(
                "value dimension and error dimension are not the same\n%s != %s"
                % (dim, error_dim))
        if not error_dim is None:
            dim = error_dim

        # merge lengths
        new_len = value_len
        if not new_len is None and not error_len is None and not new_len == error_len:
            raise RuntimeError(
                "value length doesn't fit error length for '%s':\n%s != %s" %
                (name, new_len, error_len))
        if not error_len is None:
            new_len = error_len

        # if quantity didn't exist
        if not name in self.data or replace:
            self.data[name] = quantities.Quantity(name)
        # if it did exist
        else:
            # get old length, len(error)=1 is not a length, because it can be duplicated to fit any value length
            old_len = None
            if not self.data[name].value is None:
                if isinstance(self.data[name].value, np.ndarray):
                    old_len = len(self.data[name].value)
                else:
                    old_len = 1
            if not self.data[name].error is None and isinstance(
                    self.data[name].error, np.ndarray):
                old_len = len(self.data[name].error)

            # if new dimension or new length, create new quantity
            if (not self.data[name].dim == dim
                    or (not old_len is None and not new_len is None
                        and not old_len == new_len)):
                self.data[name] = quantities.Quantity(name)

        # save stuff
        if not longname is None:
            self.data[name].longname = longname
        if not value is None:
            self.data[name].value = value
            self.data[name].value_formula = value_formula
        if not value_unit is None:
            self.data[name].prefer_unit = value_unit
        elif not error_unit is None:
            self.data[name].prefer_unit = error_unit
        if not error is None:
            self.data[name].error = error
            self.data[name].error_formula = error_formula
        self.data[name].dim = dim

        # check if error must be duplicated to adjust to value length
        if isinstance(self.data[name].value, np.ndarray) and isinstance(
                self.data[name].error, np.float_):
            error_arr = np.full(len(self.data[name].value),
                                self.data[name].error)
            self.data[name].error = error_arr
Example #7
0
    def fit(self,
            fit_function,
            xydata,
            parameters,
            weighted=None,
            plot=False,
            ignore_dim=False):
        """ fits function to data

        Args:
            fit_function: function to fit, e.g. "n*t**2 + m*t + b"
            xydata: pair of x-quantity and y-quantity of data to fit to, e.g. ["t","U"]
            parameters: list of parameters in fit function, e.g. ["n","m","b"]
            weighted: if True, will weight fit by errors (returns error if not possible)
                      if False, will not weight fit by errors
                      if None, will try to weight fit, but if at least one error is not given, will not weight it
            plot: Bool, if data and fit function should be plotted
            ignore_dim: if True, will ignore dimensions and just calculate in base units instead
        """

        if self.config["fit_module"] == "scipy":
            import errorpro.fit_scipy as fit_module
        else:
            raise ValueError("no fit module called '%s'." %
                             self.config["fit_module"])

        # get parameter quantities
        parameters_obj = []
        for p in parameters:
            if isinstance(p, str):
                if not p in self.data:
                    self.data[p] = quantities.Quantity(p)
                    self.data[p].dim = Dimension()
                parameters_obj.append(self.data[p])
            elif isinstance(p, quantities.Quantity):
                parameters_obj.append(p)
            else:
                raise TypeError(
                    "parameters can only be strings or Quantity objects")

        # parse fit function
        fit_function = quantities.parse_expr(fit_function, self.data)

        # get data quantities
        x_data = quantities.parse_expr(xydata[0], self.data)
        # if x-data is an expression
        if not isinstance(x_data, quantities.Quantity):
            dummy = quantities.Quantity()
            fit_function = fit_function.subs(x_data, dummy)
            dummy.value = quantities.get_value(x_data)
            dummy.error = quantities.get_error(x_data)[0]
            dummy.dim = quantities.get_dimension(x_data)
            x_data = dummy
        y_data = quantities.parse_expr(xydata[1], self.data)
        # if y-data is an expression
        if not isinstance(y_data, quantities.Quantity):
            dummy = quantities.Quantity()
            dummy.value = quantities.get_value(y_data)
            dummy.error = quantities.get_error(y_data)[0]
            dummy.dim = quantities.get_dimension(y_data)
            y_data = dummy

        # check if dimension fits
        if not ignore_dim:
            try:
                dim_func = quantities.get_dimension(fit_function)
            except ValueError:
                dim_func = None
            if not dim_func == y_data.dim:
                # try to solve for dimensionless parameters
                known_dimensions = {x_data.name: x_data.dim}
                known_dimensions = dim_solve(fit_function, y_data.dim,
                                             known_dimensions)
                for q_name in known_dimensions:
                    if q_name in self.data:
                        if not self.data[q_name].dim == known_dimensions[
                                q_name]:
                            self.data[q_name].dim = known_dimensions[q_name]
                            self.data[q_name].prefer_unit = None
                dim_func = quantities.get_dimension(fit_function)
                # if it still doesn't work, raise error
                if not dim_func == y_data.dim:
                    raise RuntimeError("Finding dimensions of fit parameters was not sucessful.\n"\
                                                     "Check fit function or specify parameter units manually.\n"\
                                                     "This error will occur until dimensions are right.")

        # fit
        values, errors = fit_module.fit(x_data, y_data, fit_function,
                                        parameters_obj, weighted)

        # save results
        i = 0
        for p in parameters_obj:
            p.value = values[i]
            p.value_formula = "fit"
            p.error = errors[i]
            p.error_formula = "fit"
            i += 1

        # plot
        if plot:
            return plotting.plot([(x_data, y_data), (x_data, fit_function)],
                                 self.config,
                                 ignore_dim=ignore_dim)
        else:
            return self.table(*parameters_obj)