def compatible(self, solution): compatible = True assert isinstance(solution, Solution) self.build_component_index() for c in solution.components: if 'concentration' not in c: compatible = False # Has a matching component of unknown concentration. assert isinstance(c['concentration'], ureg.Quantity) key = serialize(strip_internal(c['classification'])) if key in self.comp_index: c_target_conc = self.components[ self.comp_index[key]]['concentration'] if c['concentration'].to_base_units( ).units != c_target_conc.to_base_units().units: compatible = False return compatible
def build_component_index(self): cindex = {} ct = 0 if len(self.components) > 0: for c in self.components: if isinstance(c, dict) and 'classification' in c: si = strip_internal(c['classification']) key = serialize(si) if key in cindex: raise Exception('warning, found duplicate components'+\ ' in solution object') cindex[key] = ct ct += 1 else: # If a component does not have a classification, then it is # not indexed. E.g. want to keep a placeholder for something # ambiguous like "stabilizers" but don't yet have a rigorous # classification or concentration for that. pass self.comp_index = cindex else: self.comp_index = {}
def intersection_dist(self, another_solution): self.build_component_index() another_solution.build_component_index() dmax = 0 dist = 0 for c in self.components: assert isinstance(c['concentration'], ureg.Quantity) key = serialize(strip_internal(c['classification'])) if (('_ignore_concentration' in c) and \ c['_ignore_concentration'] == False) or \ ('_ignore_concentration' not in c): if key in another_solution.comp_index: dmax += 1 ind = another_solution.comp_index[key] c1 = c['concentration'] c2 = another_solution.components[ind]['concentration'] assert c1.to_base_units().units == c2.to_base_units().units mn = min(c1, c2).to_base_units().magnitude mx = max(c1, c2).to_base_units().magnitude if mn == 0: dist += 1 elif mx > 0: dist += 1 - mn / mx return dist / dmax
def intersection_dist(self, another_solution): self.build_component_index() another_solution.build_component_index() dmax = 0 dist = 0 for c in self.components: assert isinstance(c['concentration'], ureg.Quantity) key = serialize(strip_internal(c['classification'])) if (('_ignore_concentration' in c) and \ c['_ignore_concentration'] == False) or \ ('_ignore_concentration' not in c): if key in another_solution.comp_index: dmax += 1 ind = another_solution.comp_index[key] c1 = c['concentration'] c2 = another_solution.components[ind]['concentration'] assert c1.to_base_units().units == c2.to_base_units().units mn = min(c1, c2).to_base_units().magnitude mx = max(c1, c2).to_base_units().magnitude if mn == 0: dist += 1 elif mx > 0: dist += 1 - mn / mx return dist/dmax
def dist_self_to_target(self, target_solution, safety_first=True): '''using canberra distance for now''' #print "==========================" #print "== " + traceback.extract_stack()[-2][2] + ' / ' + traceback.extract_stack()[-1][2] #print "==========================" self.build_component_index() target_solution.build_component_index() #print target_solution.comp_index #print self.comp_index dist = 0 ignore_count = 0 for c in target_solution.components: assert isinstance(c['concentration'], ureg.Quantity) key = serialize(strip_internal(c['classification'])) if (('_ignore_concentration' in c) and \ c['_ignore_concentration'] == False) or \ ('_ignore_concentration' not in c): c1 = c['concentration'] if key not in self.comp_index: #print 'problem...' c2 = c1 * 0 else: ind = self.comp_index[key] c2 = self.components[ind]['concentration'] if safety_first: assert c1.to_base_units().units == c2.to_base_units().units v1 = c1.to_base_units().magnitude v2 = c2.to_base_units().magnitude denom = float(v1 + v2) if denom != 0: dist += abs(v1 - v2) / denom else: ignore_count += 1 #print dist / float(len(target_solution.components)) return dist / float(len(target_solution.components) - ignore_count)
def compatible(self, solution): compatible = True assert isinstance(solution, Solution) self.build_component_index() for c in solution.components: if 'concentration' not in c: compatible = False # Has a matching component of unknown concentration. assert isinstance(c['concentration'], ureg.Quantity) key = serialize(strip_internal(c['classification'])) if key in self.comp_index: c_target_conc = self.components[self.comp_index[key]]['concentration'] if c['concentration'].to_base_units().units != c_target_conc.to_base_units().units: compatible = False return compatible
def dist(self, another_solution, compare_volumes=True, safety_first=True): '''Compute the absolute molarity distance between two solutions, i.e. the sum of the absolute difference in the number of moles per unit volume present of each chemical. For components with '_ignore':True for both in a pair of matching components, the concentration difference for these will not be added to the total. This is to be used for diluents, such as water, in the case that the concentration of water is not relevant. ''' dist = 0 self.build_component_index() another_solution.build_component_index() dmax = len(self.comp_index) + len(another_solution.comp_index) for c in self.components: if ('classification' in c) and isinstance(c, dict): key = serialize(strip_internal(c['classification'])) if key in another_solution.comp_index: ind = another_solution.comp_index[key] if ('concentration' not in c) or ( 'concentration' not in another_solution.components[ind]): dist += 1 else: c1 = c['concentration'] c2 = another_solution.components[ind]['concentration'] if safety_first: assert c1.to_base_units( ).units == c2.to_base_units().units mn = min(c1, c2).to_base_units().magnitude mx = max(c1, c2).to_base_units().magnitude if mn == 0: dist += 1 elif mx > 0: dist += 1 - mn / mx # otherwise, they are both zero, add nothing to the dist else: dist += 1 else: dmax -= 1 for c in another_solution.components: if ('classification' in c) and isinstance(c, dict): key = serialize(strip_internal(c['classification'])) if key not in self.comp_index: if safety_first: assert isinstance(c['concentration'], ureg.Quantity) dist += 1 else: dmax -= 1 if dmax > 0: return dist / dmax else: # Not sure about this. There are two different uses, that of getting # a usable dist and that of testing whether two are equivalent. return dist
def add(self, solution, volume, hypothetical=False, safety_first=True): '''Add a specified volume of a solution to this solution.''' #print "==========================" #print "== " + traceback.extract_stack()[-2][2] + ' / ' + traceback.extract_stack()[-1][2] #print "==========================" # Any time solution add is called it should update the histories of # both solutions involved, adding a simple dict that specified what # operation was performed and some way that, given only one of the # solutions, you could reconstruct which chemials were added to the # solution, when. if safety_first: assert isinstance(volume, ureg.Quantity) assert isinstance(solution, Solution) vtot = self.volume + volume if vtot == 0.0 * ureg.microliter: return #print 'adding ' + str(volume) + ' from' #print solution.components #import ipdb; ipdb.set_trace() # Check that what would be the final volume will be less than the max # volume of the well containing the solution to which an addition # would be made. # Only relevant for soutions that are contained in some container. # Allows working with hypothetical solutions that don't have containers # as well. if not hypothetical: if self.container is not None and 'ctype' in self.container: assert vtot <= max_volume(self.container['ctype']) self.build_component_index() solution.build_component_index() for c in solution.components: if ('classification' in c) and ('concentration' in c): if safety_first: assert isinstance(c['concentration'], ureg.Quantity) key = serialize(strip_internal(c['classification'])) if key in self.comp_index: key_target = self.comp_index[key] c_target_conc = self.components[key_target][ 'concentration'] if safety_first: assert c['concentration'].to_base_units().units == c_target_conc.to_base_units().units, \ 'attempted to add two incompatible soutions, use solution.compatible(other_solution) to '+\ 'check compatibility before combining' #print 'add: found and adding' conc = volume / vtot * c[ 'concentration'] + self.volume / vtot * c_target_conc self.components[key_target]['concentration'] = conc.to( c['concentration'].units) #print c['concentration'], conc else: #print 'add: wasnt in index, appending' #print 'appending:' #print c self.components.append(copy(c)) self.comp_index[key] = len(self.comp_index.keys()) - 1 #print 'last before update' #print self.components[-1] self.components[-1][ 'concentration'] = volume / vtot * c['concentration'] #print 'looking up just appended' #print self.components[-1] if safety_first: assert isinstance(c['concentration'], ureg.Quantity) for c in self.components: if 'classification' in c: key = serialize(strip_internal(c['classification'])) if key not in solution.comp_index: #print 'add: not found but adding' conc = self.components[ self.comp_index[key]]['concentration'] self.components[self.comp_index[key]][ 'concentration'] = self.volume / vtot * conc self.volume = vtot if not hypothetical: solution.remove(volume) assert isinstance(self.volume, ureg.Quantity)
def dist(self, another_solution, compare_volumes=True, safety_first=True): '''Compute the absolute molarity distance between two solutions, i.e. the sum of the absolute difference in the number of moles per unit volume present of each chemical. For components with '_ignore':True for both in a pair of matching components, the concentration difference for these will not be added to the total. This is to be used for diluents, such as water, in the case that the concentration of water is not relevant. ''' dist = 0 self.build_component_index() another_solution.build_component_index() dmax = len(self.comp_index) + len(another_solution.comp_index) for c in self.components: if ('classification' in c) and isinstance(c, dict): key = serialize(strip_internal(c['classification'])) if key in another_solution.comp_index: ind = another_solution.comp_index[key] if ('concentration' not in c) or ('concentration' not in another_solution.components[ind]): dist += 1 else: c1 = c['concentration'] c2 = another_solution.components[ind]['concentration'] if safety_first: assert c1.to_base_units().units == c2.to_base_units().units mn = min(c1, c2).to_base_units().magnitude mx = max(c1, c2).to_base_units().magnitude if mn == 0: dist += 1 elif mx > 0: dist += 1 - mn / mx # otherwise, they are both zero, add nothing to the dist else: dist += 1 else: dmax -= 1 for c in another_solution.components: if ('classification' in c) and isinstance(c, dict): key = serialize(strip_internal(c['classification'])) if key not in self.comp_index: if safety_first: assert isinstance(c['concentration'], ureg.Quantity) dist += 1 else: dmax -= 1 if dmax > 0: return dist/dmax else: # Not sure about this. There are two different uses, that of getting # a usable dist and that of testing whether two are equivalent. return dist
def add(self, solution, volume, hypothetical=False, safety_first=True): '''Add a specified volume of a solution to this solution.''' #print "==========================" #print "== " + traceback.extract_stack()[-2][2] + ' / ' + traceback.extract_stack()[-1][2] #print "==========================" # Any time solution add is called it should update the histories of # both solutions involved, adding a simple dict that specified what # operation was performed and some way that, given only one of the # solutions, you could reconstruct which chemials were added to the # solution, when. if safety_first: assert isinstance(volume, ureg.Quantity) assert isinstance(solution, Solution) vtot = self.volume + volume if vtot == 0.0 * ureg.microliter: return #print 'adding ' + str(volume) + ' from' #print solution.components #import ipdb; ipdb.set_trace() # Check that what would be the final volume will be less than the max # volume of the well containing the solution to which an addition # would be made. # Only relevant for soutions that are contained in some container. # Allows working with hypothetical solutions that don't have containers # as well. if not hypothetical: if self.container is not None and 'ctype' in self.container: assert vtot <= max_volume(self.container['ctype']) self.build_component_index() solution.build_component_index() for c in solution.components: if ('classification' in c) and ('concentration' in c): if safety_first: assert isinstance(c['concentration'], ureg.Quantity) key = serialize(strip_internal(c['classification'])) if key in self.comp_index: key_target = self.comp_index[key] c_target_conc = self.components[key_target]['concentration'] if safety_first: assert c['concentration'].to_base_units().units == c_target_conc.to_base_units().units, \ 'attempted to add two incompatible soutions, use solution.compatible(other_solution) to '+\ 'check compatibility before combining' #print 'add: found and adding' conc = volume/vtot * c['concentration'] + self.volume/vtot * c_target_conc self.components[key_target]['concentration'] = conc.to(c['concentration'].units) #print c['concentration'], conc else: #print 'add: wasnt in index, appending' #print 'appending:' #print c self.components.append(copy(c)) self.comp_index[key] = len(self.comp_index.keys()) - 1 #print 'last before update' #print self.components[-1] self.components[-1]['concentration'] = volume/vtot * c['concentration'] #print 'looking up just appended' #print self.components[-1] if safety_first: assert isinstance(c['concentration'], ureg.Quantity) for c in self.components: if 'classification' in c: key = serialize(strip_internal(c['classification'])) if key not in solution.comp_index: #print 'add: not found but adding' conc = self.components[self.comp_index[key]]['concentration'] self.components[self.comp_index[key]]['concentration'] = self.volume/vtot * conc self.volume = vtot if not hypothetical: solution.remove(volume) assert isinstance(self.volume, ureg.Quantity)
def test_serialize(self): for c in self.serialize_test_cases: # Because key:value,key2:value2 == key2:value2,key:value assert unserialize(serialize(c[0])) == c[0]