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 test_parse_unit(self): factor, dim, unit = units.parse_unit("13e4*W*s") self.assertEqual(factor, 130000) self.assertEqual(dim, Dimension(mass=1, length=2, time=-2)) self.assertEqual(unit, 13e4 * si["W"] * si["s"])
def test_parse_empty_unit(self): factor, dim, unit = units.parse_unit("") self.assertEqual(factor, 1) self.assertEqual(dim, Dimension()) self.assertEqual(unit, S.One)
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 test_parse_unit(self): factor, dim, unit = units.parse_unit("13e4*W*s") self.assertEqual(factor,130000) self.assertEqual(dim,Dimension(mass=1,length=2,time=-2)) self.assertEqual(unit,13e4*si["W"]*si["s"])
def test_parse_empty_unit(self): factor, dim, unit = units.parse_unit("") self.assertEqual(factor,1) self.assertEqual(dim,Dimension()) self.assertEqual(unit,S.One)
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