def test_extend_from_module(self): # Custom unit is not known initially with self.assertRaises(UnableToParseUnits): unit_parser.parse_unit("custom_unit", suppress_unknown=False) unit_parser.parser.extend(sample_units) # Once parser is extended, the unit can be parsed expected_unit = sample_units.custom_unit self.assertEqual(unit_parser.parse_unit("custom_unit"), expected_unit) self.assertEqual(unit_parser.parse_unit("cuwl"), expected_unit)
def volumetric_CV_flow_rate_to_volumetric_flow_rate(vol_cv_flow_rate, column, to_unit=None): """ Convert a volumetric flow rate using the CV unit to a physical unit. Parameters ---------- vol_cv_flow_rate : UnitScalar Fow rate in column volume unit CV per unit time. column : Column Column object the flow happens in. to_unit : str or Unit Unit of the result flow rate. Returns ------- float Flow rate in SI compatible unit. """ if to_unit is None: to_unit = m**3 / second elif isinstance(to_unit, basestring): to_unit = unit_parser.parse_unit(to_unit) si_flow_rate = vol_cv_flow_rate * column.volume return chr_units.convert_units(si_flow_rate, tgt_unit=to_unit)
def test_strict_units_trait(self): obj = UnitsStrict(units='km') self.assertFalse(obj is None) self.assertEqual(obj.units.label, 'km') obj.units = 'm/sec**2' self.assertFalse(obj is None) self.assertEqual(obj.units.label, 'm/sec**2') self.assertEqual(obj.units.derivation, (1, 0, -2, 0, 0, 0, 0)) self.assertRaises(TraitError, setattr, obj, 'units', 'invalid') self.assertFalse(obj is None) self.assertEqual(obj.units.label, 'm/sec**2') self.assertEqual(obj.units.derivation, (1, 0, -2, 0, 0, 0, 0)) units = unit_parser.parse_unit('g/cc') self.assertFalse(units is None) self.assertEqual(units.label, 'g/cc') self.assertNotEqual(units.derivation, dimensionless.derivation) obj.units = units self.assertTrue(obj.units is units) return
def test_strict_units_trait(self): obj = UnitsStrict(units='km') self.failIf(obj is None) self.failUnlessEqual(obj.units.label, 'km') obj.units = 'm/sec**2' self.failIf(obj is None) self.failUnlessEqual(obj.units.label, 'm/sec**2') self.failUnlessEqual(obj.units.derivation, (1, 0, -2, 0, 0, 0, 0)) self.failUnlessRaises(TraitError, setattr, obj, 'units', 'invalid') self.failIf(obj is None) self.failUnlessEqual(obj.units.label, 'm/sec**2') self.failUnlessEqual(obj.units.derivation, (1, 0, -2, 0, 0, 0, 0)) units = unit_parser.parse_unit('g/cc') self.failIf(units is None) self.failUnlessEqual(units.label, 'g/cc') self.failIfEqual(units.derivation, dimensionless.derivation) obj.units = units self.failUnless(obj.units is units) return
def test_parse_absorption_units(self): # add custom units to the parser. initialize_unit_parser() # check we can parse based on the unit name u1 = unit_parser.parse_unit('absorption_unit', suppress_unknown=False) self.assertEqual(u1.label, 'absorption_unit') # check we can parse based on the unit label u2 = unit_parser.parse_unit('AU', suppress_unknown=False) self.assertEqual(u2.label, 'AU') # check the two units mean the same thing (except labels) self.assertEqual(u1.derivation, u2.derivation) self.assertEqual(u1.offset, u2.offset) self.assertEqual(u1.value, u2.value)
def test_calculate_component_concentrations(self): comp_absorb_data = { "Acidic_1_Sim": self.data, "Acidic_2_Sim": self.data, "Native_Sim": self.data } comps = [Prod001_comp1, Prod001_comp2, Prod001_comp3] product = Product(product_components=comps, **PRODUCT_DATA) start_collect_idx, stop_collect_idx = 1, 3 comp_conc = calculate_component_concentrations(product, comp_absorb_data, start_collect_idx, stop_collect_idx) self.assertIsInstance(comp_conc, UnitArray) self.assertEqual(comp_conc.units, unit_parser.parse_unit("g/liter")) # All the same value since all the same XYdata for concentration in comp_conc: self.assertEqual(concentration, comp_conc[0])
def convert_units(unitted_data, tgt_unit, **kwargs): """ Convert unitted data to the units specified by `tgt_unit`. Parameters ---------- unitted_data : UnitScalar or UnitArray The data to be converted to `tgt_unit` tgt_unit : scimath.unit or str The target units for `unitted_data`. kwargs : dict Additional arguments that may be needed by custom converters for special units. Returns ------- UnitScalar or UnitArray The converted data. """ if isinstance(tgt_unit, basestring): tgt_unit = unit_parser.parse_unit(tgt_unit) if isinstance(unitted_data, UnitScalar): unit_klass = UnitScalar elif isinstance(unitted_data, UnitArray): unit_klass = UnitArray else: msg = "The `unitted_data` argument must be an instance of either " \ "scimath's UnitScalar or UnitArray but got {}." msg = msg.format(unitted_data.__class__.__name__) logger.exception(msg) raise ValueError(msg) src_unit = unitted_data.units if (src_unit.label, tgt_unit.label) in CUSTOM_UNITS_CONVERTERS: converter = CUSTOM_UNITS_CONVERTERS[(src_unit.label, tgt_unit.label)] unitted_data = converter(unitted_data, **kwargs) return unit_klass(unitted_data, units=tgt_unit) else: data = np.array(unitted_data.tolist()) data = convert(data, src_unit, tgt_unit) return unit_klass(data, units=tgt_unit)
def build_flow_rate_array(times, experiment, to_unit="liter/minute"): """ Build array of flow rates in liter/min at each time of 'times' array. Parameters ---------- times : numpy.array Array of chromatogram times at which to extract the flow rates. experiment : Experiment Experiment to extract the flow rates from. to_unit : str Unit of the output. Returns ------- UnitArray Array of flow rates at the times of the times array. """ method_steps = experiment.method.method_steps step_boundary_times = experiment.method_step_boundary_times flow_rates = ones(times.shape) * nan for i, step in enumerate(method_steps): step_start_time = step_boundary_times[i] step_stop_time = step_boundary_times[i+1] mask = (times >= step_start_time) & (times < step_stop_time) if is_linear_flow_rate(step.flow_rate): diam = experiment.column.column_type.diameter flow_rate = linear_flow_rate_to_volumetric(step.flow_rate, diam, to_unit=to_unit) elif is_volumetric_flow_rate(step.flow_rate): flow_rate = convert(step.flow_rate, from_unit=step.flow_rate.units, to_unit=unit_parser.parse_unit(to_unit)) else: flow_rate = volumetric_CV_flow_rate_to_volumetric_flow_rate( step.flow_rate, experiment.column, to_unit=to_unit ) flow_rates[mask] = float(flow_rate) return UnitArray(flow_rates, units=to_unit)
def test_units_trait(self): obj = UnitsNonStrict(units='km') self.assertFalse(obj is None) self.assertEqual(obj.units.label, 'km') obj.units = 'm/sec**2' self.assertFalse(obj is None) self.assertEqual(obj.units.label, 'm/sec**2') obj.units = 'invalid' self.assertFalse(obj is None) self.assertEqual(obj.units.label, 'invalid') self.assertEqual(obj.units.derivation, dimensionless.derivation) units = unit_parser.parse_unit('g/cc') self.assertFalse(units is None) self.assertEqual(units.label, 'g/cc') self.assertNotEqual(units.derivation, dimensionless.derivation) obj.units = units self.assertTrue(obj.units is units) return
def test_units_trait(self): obj = UnitsNonStrict(units='km') self.failIf(obj is None) self.failUnlessEqual(obj.units.label, 'km') obj.units = 'm/sec**2' self.failIf(obj is None) self.failUnlessEqual(obj.units.label, 'm/sec**2') obj.units = 'invalid' self.failIf(obj is None) self.failUnlessEqual(obj.units.label, 'invalid') self.failUnlessEqual(obj.units.derivation, dimensionless.derivation) units = unit_parser.parse_unit('g/cc') self.failIf(units is None) self.failUnlessEqual(units.label, 'g/cc') self.failIfEqual(units.derivation, dimensionless.derivation) obj.units = units self.failUnless(obj.units is units) return
def compute_mass_from_abs_data(absorb_data, ext_coeff, experim, t_start=None, t_stop=None, t_start_idx=None, t_stop_idx=None): """ Compute total mass of a product component between start and stop times. The total mass is computed by integrating the specified chromatogram, between t_start and t_stop and using the specified extinction coefficient and flow rate at each time. Parameters ---------- absorb_data : XYData Data (fraction or continuous) to integrate to compute the contained mass. ext_coeff : UnitScalar Extinction coefficient to use to convert the absorbance to a product concentration. experim : Experiment Experiment from which to extract the method (and therefore flow rate) information and the system's path length. t_start : UnitScalar Time at which to start integrating, in minutes. Leave as None to use the t_start_idx to specify the time range to integrate. t_stop : UnitScalar Time at which to stop integrating, in minutes. Leave as None to use the t_stop_idx to specify the time range to integrate. t_start_idx : Int or None Index in the x_data to start integrating at (inclusive). t_stop_idx : Int or None Index in the x_data to stop integrating at (exclusive). Leave as None to go all the way to the end. Returns ------- UnitScalar Product mass, in grams, estimated to elute between t_start and t_stop. """ all_x_data = absorb_data.x_data all_y_data = absorb_data.y_data # Convert time inputs into minutes: data_time_unit = unit_parser.parse_unit(absorb_data.x_metadata["units"]) all_x_data = convert(all_x_data, from_unit=data_time_unit, to_unit=minute) if t_start is not None and t_stop is not None: t_start = convert_units(t_start, tgt_unit=minute) t_stop = convert_units(t_stop, tgt_unit=minute) t_start_idx = searchsorted(all_x_data, t_start) t_stop_idx = searchsorted(all_x_data, t_stop) if t_start_idx == t_stop_idx: msg = "Unable to compute the integral of the provided because" \ "t_start too close to t_stop." logger.warning(msg) return UnitScalar(0., units="gram") collect_idx = slice(t_start_idx, t_stop_idx) times = all_x_data[collect_idx] absorbances = all_y_data[collect_idx] #: Extract the flow rate from the experiment method: flow_rates = build_flow_rate_array(times, experim, to_unit="liter/minute") missing_flow_rates = where(isnan(flow_rates))[0] if len(missing_flow_rates) > 0: msg = "The time range requested to integrate results goes beyond the "\ "known method steps, and will need to be cropped by {} values." \ " Cropped values are {}.".format(len(missing_flow_rates), missing_flow_rates) logger.warning(msg) t_stop_idx = missing_flow_rates[0] collect_idx = slice(t_start_idx, t_stop_idx) times = all_x_data[collect_idx] absorbances = all_y_data[collect_idx] flow_rates = flow_rates[collect_idx] # Turn absorbances into AU/cm path_length = convert_units(experim.system.abs_path_length, "cm")[()] data_absorb_unit = unit_parser.parse_unit(absorb_data.y_metadata["units"]) absorbances_au = convert(absorbances, from_unit=data_absorb_unit, to_unit=absorption_unit) # Compute masses in grams masses = (absorbances_au*array(flow_rates)) / (path_length*ext_coeff[()]) total_mass = trapz(masses, times) return UnitScalar(total_mass, units="gram")
def units_compatible(u0, u1): return (unit_parser.parse_unit(u0).derivation == unit_parser.parse_unit(u1).derivation)
def unit_string_is_valid(units): return unit_parser.parse_unit(units).valid
def __parse_expr(self, node): """ helper function for get_variable that recursively evaluates the AST tree based on: https://stackoverflow.com/a/9558001. """ if node is None: return None elif isinstance(node, ast.Num): return node.n elif isinstance(node, ast.Str): return node.s elif isinstance(node, ast.Constant): return node.val # look for name in variable dictionary elif isinstance(node, ast.Name): # check if it is a unit val = unit_parser.parse_unit(node.id) if val.is_valid(): return convert(1, val, self._clk) #check if it is a variable val = self.__vars_dict.get(node.id, None) return val # define binary operators (+,-,*,/) elif isinstance(node, ast.BinOp): lhs = self.__parse_expr(node.left) rhs = self.__parse_expr(node.right) op = ast_ops_dict[type(node.op)] if isinstance(lhs, np.ndarray) or isinstance(rhs, np.ndarray): if not isinstance(lhs, np.ndarray): if np.issubdtype(rhs.dtype, np.integer): lhs = rhs.dtype.type(round(lhs)) else: lhs = rhs.dtype.type(lhs) if not isinstance(rhs, np.ndarray): if np.issubdtype(lhs.dtype, np.integer): rhs = lhs.dtype.type(round(rhs)) else: rhs = lhs.dtype.type(rhs) out = op(lhs, rhs) self.__proc_list.append((op, (lhs, rhs, out))) self.__proc_strs.append("Binary operator: " + op.__name__) return out return op(lhs, rhs) # define unary operators (-) elif isinstance(node, ast.UnaryOp): operand = self.__parse_expr(node.operand) op = ast_ops_dict[type(node.op)] out = op(operand) # if we have a np array, add to processor list if isinstance(out, np.ndarray): self.__proc_list.append((op, (operand, out))) self.__proc_strs.append("Unary operator: " + op.__name__) return out elif isinstance(node, ast.Subscript): # print(ast.dump(node)) val = self.__parse_expr(node.value) if isinstance(node.slice, ast.Index): if isinstance(val, np.ndarray): return val[..., self.__parse_expr(node.slice.value)] else: return val[self.__parse_expr(node.slice.value)] elif isinstance(node.slice, ast.Slice): if isinstance(val, np.ndarray): return val[..., slice(self.__parse_expr(node.slice.lower), self.__parse_expr(node.slice.upper), self.__parse_expr(node.slice.step))] else: print(self.__parse_expr(node.slice.upper)) return val[slice(self.__parse_expr(node.slice.upper), self.__parse_expr(node.slice.lower), self.__parse_expr(node.slice.step))] elif isinstance(node.slice, ast.ExtSlice): slices = tuple(node.slice.dims) for i, sl in enumerate(slices): if isinstance(sl, ast.index): slices[i] = self.__parse_expr(sl.value) else: slices[i] = slice(self.__parse_expr(sl.upper), self.__parse_expr(sl.lower), self.__parse_expr(sl.step)) return val[..., slices] # for name.attribute elif isinstance(node, ast.Attribute): val = self.__parse_expr(node.value) # get shape with buffer_len dimension removed if node.attr == 'shape' and isinstance(val, np.ndarray): return val.shape[1:] # for func([args]) elif isinstance(node, ast.Call): func = node.func.id # get length of 1D array variable if func == "len" and len(node.args) == 1 and isinstance( node.args[0], ast.Name): var = self.__parse_expr(node.args[0]) if isinstance(var, np.ndarray) and len(var.shape) == 2: return var.shape[1] else: raise ValueError("len(): " + node.args[0].id + "has wrong number of dims") elif func == "round" and len(node.args) == 1: var = self.__parse_expr(node.args[0]) return int(round(var)) # if this is a valid call to construct a new array, do so; otherwise raise an exception else: if len(node.args) == 2: shape = self.__parse_expr(node.args[0]) if isinstance(shape, (int, np.int32, np.int64)): shape = (self._block_width, shape) elif isinstance(shape, tuple): shape = (self._block_width, ) + shape else: raise ValueError( "Do not recognize call to " + func + " with arguments of types " + str([arg.__dict__ for arg in node.args])) try: dtype = np.dtype(node.args[1].id) except: raise ValueError( "Do not recognize call to " + func + " with arguments of types " + str([arg.__dict__ for arg in node.args])) if func in self.__vars_dict: var = self.__vars_dict[func] if not var.shape == shape and var.dtype == dtype: raise ValueError("Requested shape and type for " + func + " do not match existing values") return var else: var = np.zeros(shape, dtype, 'F') self.__vars_dict[func] = var self.__print( 2, 'Added variable ' + func + ' with shape ' + str(tuple(shape)) + ' and type ' + str(dtype)) return var else: raise ValueError( "Do not recognize call to " + func + " with arguments " + str([str(arg.__dict__) for arg in node.args])) raise ValueError("Cannot parse AST nodes of type " + str(node.__dict__))
def units_compatible(u0, u1): return (unit_parser.parse_unit(u0).derivation == unit_parser.parse_unit( u1).derivation)