def test_scalar_convert_units(self): scalar_data = UnitScalar(1, units=chr_units.length.m) conv_data = convert_units(scalar_data, chr_units.length.cm) self.assertEqual(conv_data, UnitScalar(100, units=chr_units.length.cm)) # convert back to original units and check conv_data_2 = convert_units(conv_data, scalar_data.units) self.assertEqual(conv_data_2, scalar_data)
def test_invalid_arguments(self): scalar_data = UnitScalar(1, units=chr_units.length.m) # raise error if the units are not consistent with self.assertRaises(InvalidConversion): convert_units(scalar_data, chr_units.volume.liter) # raise error if the data does not have units with self.assertRaises(ValueError): convert_units(1, chr_units.volume.liter)
def test_array_convert_units(self): array_data = UnitArray([1, 2, 3], units=chr_units.length.m) conv_data = convert_units(array_data, chr_units.length.cm) assert_allclose(conv_data.tolist(), (100 * array_data).tolist()) self.assertEqual(conv_data.units, chr_units.length.cm) # convert back to original units and check conv_data_2 = convert_units(conv_data, array_data.units) assert_allclose(conv_data_2.tolist(), array_data.tolist()) self.assertEqual(conv_data_2.units, array_data.units)
def test_g_per_liter_resin_to_cv(self): concentration = UnitScalar(8.03, units="g/L") vol = UnitScalar(0.0635, units=chr_units.g_per_liter_resin) # without a concentration, the conversion is impossible: with self.assertRaises(ValueError): convert_units(vol, tgt_unit="CV") new_vol = convert_units(vol, tgt_unit="CV", concentration=concentration) expected = UnitScalar(0.0635 / 8.03, units="CV") assert_unit_scalar_almost_equal(new_vol, expected)
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 calculate_step_yield(pool_concentration, pool_volume, load_step): """ Calculates protein yield, in percent of the mass of protein loaded (at load step). Parameters ---------- pool_concentration : UnitScalar Concentration of the pool extracted. pool_volume : UnitScalar Volume of the pool. load_step : MethodStep Step describing the load of the protein. """ # Make sure that both volumes are in the same unit load_mass = load_step.volume * load_step.solutions[0].product_concentration pool_mass = pool_concentration * pool_volume step_yield = pool_mass / load_mass if step_yield.units != fraction: msg = "Computation of step_yield: the formula doesn't provide a " \ "fraction (step_yield={!r}). Check the units.".format(step_yield) logger.error(msg) return np.nan step_yield = convert_units(step_yield, tgt_unit=percent) return step_yield
def test_g_per_liter_resin_to_cv_conc_units(self): """ g/L_resin -> CV, passing concentration in different unit. """ concentration = UnitScalar(8030, units="g/m**3") vol = UnitScalar(0.0635, units=chr_units.g_per_liter_resin) expected = UnitScalar(0.0635 / 8.03, units="CV") new_vol = convert_units(vol, tgt_unit="CV", concentration=concentration) assert_unit_scalar_almost_equal(new_vol, expected)
def _build_volume_strategy_msg(self): conc = self.mass_bal_analyzer.current_concentration vol = convert_units(self.mass_bal_analyzer.current_volume, tgt_unit=g_per_liter_resin, concentration=conc) target = self.mass_bal_analyzer.compute_loaded_vol( tgt_units=g_per_liter_resin) msg = "Clicking 'OK' will automatically adjust the Load step volume " \ "so the load rate changes from {0:.4g} {1} to {2:.4g} {1}." msg = msg.format(float(vol), vol.units.label, float(target)) self.mass_bal_strategy_msg = msg
def _get_product_component_purities(self): from kromatography.utils.chromatography_units import convert_units if self.solution_type == 'Pool': concentrations = self._product_component_concentrations else: concentrations = self.product_component_concentrations if concentrations is None or self.product_concentration is None: return return convert_units(concentrations / self.product_concentration, tgt_unit="%")
def calculate_pool_concentration(comp_concentrations): """Calculates total pool concentration in g/liter by summing individual component concentrations. """ if not isinstance(comp_concentrations, UnitArray): msg = "The component concentrations are expected to be passed as a " \ "UnitArray." logger.exception(msg) raise ValueError(msg) pool_concentration = UnitScalar(float(np.sum(comp_concentrations)), units=comp_concentrations.units) pool_concentration = convert_units(pool_concentration, tgt_unit="g/L") return pool_concentration
def loaded_mass_from_method(self): """ Returns loaded product mass in grams, as compute from method data. """ vol = self.current_volume if units_almost_equal(vol, column_volumes): vol = float(vol) * self.target_experiment.column.volume elif has_volume_units(vol): pass else: msg = "Unexpected unit for the load step volume: {}" msg = msg.format(vol.units.label) logger.exception(msg) raise NotImplementedError(msg) mass = vol * self.current_concentration return convert_units(mass, tgt_unit="gram")
def compute_concentration(self): """ Compute the load solution concentration that would match the UV data at constant load step volume. Returns ------- UnitScalar Load solution concentration, in g/L, that would be needed to match the UV data. """ target_mass = self.mass_from_uv vol = self.current_volume if units_almost_equal(vol, column_volumes): vol = float(vol) * self.target_experiment.column.volume concentration = target_mass / vol return convert_units(concentration, tgt_unit="g/L")
def loaded_mass_from_uv(self): """ Returns loaded product mass in grams, as compute from UV data. """ if not self.continuous_data: return data = self.continuous_data product = self.target_experiment.product ext_coeff = product.product_components[0].extinction_coefficient method_step_times = self.target_experiment.method_step_boundary_times t_stop = UnitScalar(method_step_times[-1], units=method_step_times.units) mass = compute_mass_from_abs_data(data, ext_coeff=ext_coeff, experim=self.target_experiment, t_start=self.time_of_origin, t_stop=t_stop) return convert_units(mass, tgt_unit="gram")
def build_cadet_input(simulation): """ Builds a valid CADETModel and CADETInput from the given Simulation. Note: sections and steps mean the same thing in the CADET and the experimental language respectively. Parameters ---------- simulation : Simulation Simulation for which to build the CADET input file. Returns ------- CADETInput Valid CADET input object that can be serialized to HDF5 and run by CADET. """ # FIXME: modularize this function! # Collect simulation components ------------------------------------------- column = simulation.column method = simulation.method simulated_steps = method.method_steps binding_model = simulation.binding_model transport_model = simulation.transport_model binding_type = binding_model.model_type lig_density = column.resin.ligand_density col_porosity = transport_model.column_porosity bead_porosity = transport_model.bead_porosity # Input validation if column.resin.resin_type not in ['CEX', 'AEX']: msg = 'The simulation is only supported for CEX and AEX resins.' logger.exception(msg) raise NotImplementedError(msg) num_sections = len(simulated_steps) section_times = simulation.section_times # Build CadetInput instance ----------------------------------------------- velocities = [step.flow_rate for step in simulated_steps] velocities = convert_units(unitted_list_to_array(velocities), SI.meter / SI.second) num_components = len(simulation.product.product_component_names) sma_binding_type = binding_type in [ STERIC_BINDING_MODEL, PH_STERIC_BINDING_MODEL ] if sma_binding_type: # +1 for the cation component (salt) if using the SMA binding model: num_components += 1 # configure CADETModel model = CADETModel(num_components, num_sections) # interstitial velocity for each step model.velocity[:] = velocities / transport_model.column_porosity # Initialize boundary conditions ------------------------------------------ inlet = model.inlet inlet.section_times[:] = section_times # Initial condition for component concentration (bulk mobile and bead # mobile phases): # FIXME: this assumes that first step isn't if sma_binding_type: model.init_c[0] = method.initial_buffer.cation_concentration[()] model.init_cp[0] = model.init_c[0] # This in theory should be equal to sma_lambda: if it is not, CADET # will enforce it. model.init_q[0] = float(lig_density / (1 - col_porosity) / (1 - bead_porosity)) # Component concentration evolution parameters ---------------------------- for ii, section in enumerate(inlet.section): step = simulated_steps[ii] # Computing concentrations for the step. By default, # concentration = const_coeff + lin_coeff t + quad_coeff t**2 if step.step_type == LOAD_STEP_TYPE: # This is a constant const_coeff = section.const_coeff sol = step.solutions[0] scalars = [ comp.molecular_weight for comp in sol.product.product_components ] molecular_weights = unitted_list_to_array(scalars) prod_comp_conc = (sol.product_component_concentrations / molecular_weights) if sma_binding_type: const_coeff[0] = sol.cation_concentration prod_comp_idx = slice(1, None) else: prod_comp_idx = slice(None, None) const_coeff[prod_comp_idx] = prod_comp_conc[:] elif step.step_type == GRADIENT_ELUT_STEP_TYPE and sma_binding_type: # This step has two solutions and implements sol -> sol2, so # cation concentration will see a gradient: sol_1, sol_2 = step.solutions const_coeff = section.const_coeff const_coeff[0] = sol_1.cation_concentration lin_coeff = section.lin_coeff conc_change = (sol_2.cation_concentration - sol_1.cation_concentration) section_duration = np.diff(section_times[ii:][:2]) lin_coeff[0] = conc_change / section_duration elif sma_binding_type: # There are no products at the inlet, so just buffer concentrations const_coeff = section.const_coeff const_coeff[0] = step.solutions[0].cation_concentration # Configure column/resin properties and set to SI units to make CADET-ready model.col_length = float(convert_units(column.bed_height_actual, SI.meter)) bead_radius = column.resin.average_bead_diameter / 2. model.par_radius = float(convert_units(bead_radius, SI.meter)) # No unit conversion is needed here as the transport model is assumed to be # in the right units model.col_porosity = transport_model.column_porosity model.col_dispersion = transport_model.axial_dispersion model.par_porosity = transport_model.bead_porosity # Vector quantities: remove the cation component for langmuir models: if sma_binding_type: transport_idx = slice(None, None) else: transport_idx = slice(1, None) model.film_diffusion = transport_model.film_mass_transfer[transport_idx] model.par_diffusion = transport_model.pore_diffusion[transport_idx] model.par_surfdiffusion = transport_model.surface_diffusion[transport_idx] # Attached binding model to CADETModel model.adsorption_type = ALL_CADET_TYPES[binding_type] model.adsorption = binding_model profile_models = [PH_STERIC_BINDING_MODEL, PH_LANGMUIR_BINDING_MODEL] if binding_type in profile_models: model.external = CADETPhExternalProfile.from_simulation(simulation) # finally, create the CADETInput model and return. cadet_input = CADETInput( chromatography_type=transport_model.model_type, model=model, discretization=simulation.discretization, solver=simulation.solver, ) return cadet_input
def calculate_exp_component_concentrations(exp, fraction_data, flow_rate, pool_volume, start_collect, stop_collect): """ Calculate the concentration of each component in the pool in g/L from experimentally measured fraction data. The mass of a component in the pool is its fraction at various times, times the product concentration integrated over duration of the pooling process. The pool component concentration is the """ from kromatography.utils.units_utils import is_volumetric_flow_rate, \ linear_flow_rate_to_volumetric from kromatography.utils.chromatography_units import column_volumes, \ convert_units from kromatography.utils.units_utils import unitted_list_to_array if is_volumetric_flow_rate(flow_rate): pooling_flow_rate = flow_rate else: d = exp.column.column_type.diameter pooling_flow_rate = linear_flow_rate_to_volumetric( flow_rate, diam=d, to_unit="liter/minute" ) comp_concentrations = [] for i, comp in enumerate(exp.product.product_components): # Collect component attributes: comp_name = comp.name comp_fraction_data = fraction_data[comp_name] avail_x = comp_fraction_data.x_data start_collect_idx = searchsorted(avail_x, start_collect) stop_collect_idx = searchsorted(avail_x, stop_collect) collect_idx = range(start_collect_idx, stop_collect_idx) ext_coeff = comp.extinction_coefficient.tolist() fraction_comp_conc = comp_fraction_data.y_data[collect_idx] / ext_coeff fraction_comp_conc = UnitArray(fraction_comp_conc, units='g/L') # Integration of these concentrations over time to get the total mass # of the component: if stop_collect_idx+1 > len(comp_fraction_data.x_data): msg = "Computing the total mass of component {} cannot complete " \ "because there are no fractions specified after the " \ "pooling step. Please add at least 1 fraction to your " \ "fractions for experiment {}." msg = msg.format(comp.name, exp.name) logger.exception(msg) raise ValueError(msg) collect_idx_times = range(start_collect_idx, stop_collect_idx+1) times = comp_fraction_data.x_data[collect_idx_times] time_units = comp_fraction_data.x_metadata["units"] delta_times = UnitArray(list(diff(times)), units=time_units) frac_comp_masses = fraction_comp_conc * delta_times * pooling_flow_rate frac_mass = float(frac_comp_masses.sum()) comp_mass = UnitScalar(frac_mass, units=frac_comp_masses.units) if pool_volume.units == column_volumes: pool_volume = float(pool_volume) * exp.column.volume comp_conc = comp_mass / pool_volume comp_concentrations.append(convert_units(comp_conc, 'g/L')) # Read off the units from the fraction tab in the Excel spreadsheet! product_component_concentrations = \ unitted_list_to_array(comp_concentrations) return product_component_concentrations
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 test_milli_au_to_au(self): x = UnitArray([1000, 2000, 3000], units=chr_units.milli_absorption_unit) conv_data = convert_units(x, chr_units.absorption_unit) expected = UnitArray([1, 2, 3], units=chr_units.absorption_unit) assert_unit_array_almost_equal(conv_data, expected)