def plot(self, *expr_pairs, save=None, xunit=None, yunit=None, xrange=None, yrange=None, ignore_dim=False): """ Plots data or functions Args: expr_pairs: one or more pair of quantity on x-axis and on y-axis. e.g. ["p","V"] y-axis can also be a function. e.g. ["t", "7*exp(t/t0)"] save: string of file name without extension. if specified, plot will be saved to '<save>.png' xunit: unit on x-axis. if not given, will find unit on its own yunit: unit on y-axis. if not given, will find unit on its own xrange: pair of x-axis range, e.g. [-5,10] yrange: pair of y-axis range ignore_dim: if True, will skip dimension check """ if len(expr_pairs) == 0:# raise ValueError("nothing to plot specified.") expr_pairs_obj = [] for expr_pair in expr_pairs: # parse expressions expr_pairs_obj.append( (quantities.parse_expr(expr_pair[0], self.data), quantities.parse_expr(expr_pair[1], self.data)) ) if not xunit is None: xunit = units.parse_unit(xunit)[2] if not yunit is None: yunit = units.parse_unit(yunit)[2] if not xrange is None: xrange = [quantities.get_value(quantities.parse_expr(xrange[0], self.data)), quantities.get_value(quantities.parse_expr(xrange[1], self.data))] if not yrange is None: yrange = [quantities.get_value(quantities.parse_expr(yrange[0], self.data)), quantities.get_value(quantities.parse_expr(yrange[1], self.data))] return plotting.plot(expr_pairs_obj, self.config, save=save, xunit=xunit, yunit=yunit, xrange=xrange, yrange=yrange, ignore_dim=ignore_dim)
def plot(self, *expr_pairs, save=None, xunit=None, yunit=None, xrange=None, yrange=None, ignore_dim=False): """ Plots data or functions Args: expr_pairs: one or more pair of quantity on x-axis and on y-axis. e.g. ["p","V"] y-axis can also be a function. e.g. ["t", "7*exp(t/t0)"] save: string of file name without extension. if specified, plot will be saved to '<save>.png' xunit: unit on x-axis. if not given, will find unit on its own yunit: unit on y-axis. if not given, will find unit on its own xrange: pair of x-axis range, e.g. [-5,10] yrange: pair of y-axis range ignore_dim: if True, will skip dimension check """ if len(expr_pairs) == 0: # raise ValueError("nothing to plot specified.") expr_pairs_obj = [] for expr_pair in expr_pairs: # parse expressions expr_pairs_obj.append( (quantities.parse_expr(expr_pair[0], self.data), quantities.parse_expr(expr_pair[1], self.data))) if not xunit is None: xunit = units.parse_unit(xunit)[2] if not yunit is None: yunit = units.parse_unit(yunit)[2] if not xrange is None: xrange = [ quantities.get_value( quantities.parse_expr(xrange[0], self.data)), quantities.get_value( quantities.parse_expr(xrange[1], self.data)) ] if not yrange is None: yrange = [ quantities.get_value( quantities.parse_expr(yrange[0], self.data)), quantities.get_value( quantities.parse_expr(yrange[1], self.data)) ] return plotting.plot(expr_pairs_obj, self.config, save=save, xunit=xunit, yunit=yunit, xrange=xrange, yrange=yrange, ignore_dim=ignore_dim)
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"])
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
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
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)
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"])
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
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)