def mate_genes(old_population, new_population, count, structure, site_list, backends, mutation_rate): """ Make some new structures by mating current ones, based on position in the population. """ while count > 0: index_0 = int(math.floor(len(old_population) * (random.random()**2))) index_1 = int(math.floor(len(old_population) * (random.random()**2))) old_gene_0 = old_population[index_0]['gene'] old_gene_1 = old_population[index_1]['gene'] debug("Mating {} and {}".format(old_gene_0, old_gene_1)) new_gene = [] for gene_part_0, gene_part_1 in zip(old_gene_0.split('.'), old_gene_1.split('.')): # Make a random split somewhere inside split = random.randint(0, len(gene_part_0)) new_gene.append( mutate(gene_part_0[:split] + gene_part_1[split:], mutation_rate)) if any(x['gene'] == ".".join(new_gene) for x in new_population): # Alraedy in the population, don't include twice continue elif site_replace(structure, replace_list=site_list, manual_angles=new_gene, backends=backends): debug("Successfully mated as {}".format(new_gene)) individual = {'fitness': 0.0, 'gene': ".".join(new_gene)} new_population.append(individual) count -= 1
def fill_with_randoms(population, max_pop, structure, site_list, backends): """ Make the population up to max_pop in size with completely random individuals. """ while len(population) < max_pop: this_gene = [] for functional_group, site in site_list: this_gene.append("".join( random.choice(string.ascii_lowercase) for _ in structure.attachments[site])) if site_replace(structure, replace_list=site_list, manual_angles=this_gene, backends=backends): individual = {'fitness': 0.0, 'gene': ".".join(this_gene)} population.append(individual)
def fapswitch_deamon(structure, backends, rotations=12): """ Use sockets to listen and receive structures. """ timeout = options.getint('timeout') # set this to zero for random available port port = options.getint('port') listener = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # no host as this is running locally listener.bind(('', port)) port = listener.getsockname()[1] # Ensure that we always know the port critical("Listening on port {} ...".format(port)) listener.settimeout(timeout) listener.listen(1) # We only wait for a little bit so that process doesn't zombie forever try: conn, addr = listener.accept() except socket.timeout: error("No connection within {} seconds; exiting".format(timeout)) listener.close() return False info('Connected by {}'.format(addr)) # Server will continue until an empty input is sent or something times out conn.settimeout(timeout) while 1: try: line = conn.recv(1024).decode('utf-8') except socket.timeout: error( "Timed out after {} seconds waiting for input".format(timeout)) return False # Empty input closes server if not line: break # collect information to send what has been done back processed = [] # freeform strings are in braces {}, no spaces free_strings = re.findall('{(.*?)}', line) debug("Freeform strings: {}".format(free_strings)) for free_string in free_strings: complete = freeform_replace(structure, custom=free_string, backends=backends, rotations=rotations) processed.append('{{{}}}'.format(free_string)) processed.append('{}'.format(complete)) # site replacements in square brackets [], no spaces site_strings = re.findall(r'\[(.*?)\]', line) debug("Site replacement strings: {}".format(site_strings)) for site_string in site_strings: site_list = [] manual_angles = [] for site in [x for x in site_string.split('.') if x]: site_id, functionalisation = site.split('@') if '%' in functionalisation: functionalisation, manual = functionalisation.split('%') else: manual = None site_list.append([site_id, functionalisation]) manual_angles.append(manual) debug("{}".format(site_list)) debug("{}".format(manual_angles)) complete = site_replace(structure, site_list, backends=backends, rotations=rotations, manual_angles=manual_angles) processed.append('[{}]'.format(site_string)) processed.append('{}'.format(complete)) try: conn.sendall((':'.join(processed)).encode('utf-8')) except conn.timeout: error("Timed out sending status after {} seconds".format(timeout)) return False conn.close() return True
def handle_request(self): """Generate a random structure and return the rendered page.""" debug("Arguments: {}".format(self.request.arguments)) max_trials = 20 top_50_groups = [ "Me", "Ph", "Cl", "OMe", "OH", "Et", "OEt", "F", "Br", "NO2", "NH2", "CN", "COOEt", "COMe", "COOH", "Bnz", "COOMe", "iPr", "pTol", "4ClPh", "tBu", "4OMePh", "CF3", "COPh", "Pr", "NMe2", "Bu", "OBnz", "4NO2Ph", "OAc", "4FPh", "I", "4BrPh", "2ClPh", "All", "COH", "SMe", "CONH2", "NPh", "24DClPh", "CHex", "Morph", "HCO", "3ClPh", "oTol", "2Fur", "iBu", "NCOMe" ] small_groups = ["F", "Cl", "Me", "NH2", "OH", "CN"] # Possible options: # replace_only: tuple of sites to replace # groups_only: only use specific groups # max_different: restrict simultaneous types of groups # This is new every time and keeps all the information # we need specific to the web version backends = [WebStoreBackend()] failed = "" if 'mof-choice' in self.request.arguments: # Selected a specific MOF chosen_structure = self.get_argument('mof-choice') base_structure = get_structure(chosen_structure) replace_list = [] for site in available_structures[chosen_structure]: group = self.get_argument(site, None) if group is None or 'None' in group: continue elif 'Random' in group: replace_list.append([random.choice(top_50_groups), site]) else: replace_list.append([group, site]) # Now make the MOF status = site_replace(base_structure, replace_list=replace_list, backends=backends) if not status: # couldn't make it so just use clean structure failed = ".".join("{}@{}".format(x[0], x[1]) for x in replace_list) site_replace(base_structure, replace_list=[], backends=backends) else: # Completely random chosen_structure = random.choice(list(available_structures)) # Make sure we have functionalisation sites while len(available_structures[chosen_structure]) == 0: chosen_structure = random.choice(list(available_structures)) # Here's the actual structure base_structure = get_structure(chosen_structure) # Use several combinations to try to get something functionalised trial_number = 0 while trial_number < max_trials: if trial_number < max_trials / 4.0: debug("Trial all groups: {}".format(trial_number)) status = random_combination_replace( structure=base_structure, backends=backends, max_different=2) elif trial_number < 2.0 * max_trials / 4.0: debug("Trial max one group: {}".format(trial_number)) status = random_combination_replace( structure=base_structure, backends=backends, max_different=1) elif trial_number < 3.0 * max_trials / 4.0: debug("Trial top 50: {}".format(trial_number)) status = random_combination_replace( structure=base_structure, backends=backends, groups_only=top_50_groups, max_different=2) else: debug("Trial small groups: {}".format(trial_number)) status = random_combination_replace( structure=base_structure, backends=backends, groups_only=small_groups, max_different=1) # If functionalisation attempted if status: if backends[0].cifs[-1]['functions']: # it was successful; done here break else: # only increment if we actually tried to add groups trial_number += 1 else: site_replace(base_structure, replace_list=[], backends=backends) failed = "{} random combinations".format(max_trials) # Should always have a structure, even if it is clean; but failed will # be True for that cif_info = backends[0].cifs[-1] # MEPO compatibility if all groups are okay if all(functional_groups[function[0]].mepo_compatible for function in cif_info['functions']): mepo_compatible = "Yes" else: mepo_compatible = "No" collision_tester = options.get('collision_method') collision_cutoff = options.getfloat('collision_scale') if cif_info['ligands'] is None: ligands = [] else: ligands = cif_info['ligands'] if ligands: sa_score = max(ligand.sa_score for ligand in ligands) else: sa_score = 0.0 processed_ligands = make_ligands(ligands) extra_info = """<h4>Hypothetical functionalised MOF</h4> <p>Functional groups have been added using the crystal symmetry. A collision detection routine with a {} radius at {:.2f} was used to carry out the functionalisation. Note that although atoms may appear close, the bonding connectivity defined in the cif file will be correct.</p> """.format(collision_tester, collision_cutoff) # These references are always required local_references = [ references['Kadantsev2013'], references['Ertl2009'], references['Chung2014'] ] # Find all the references and add them too for reference in re.findall(r'\[(.*?)\]', extra_info): local_references.append(references[reference]) # Raw HTML anchors. Ugly. extra_info = re.sub( r'\[(.*?)\]', # non-greedy(?) find in square brackets r'[<a href="#\1">\1</a>]', # replace raw html extra_info) page = templates.load('random.html').generate( mepo_compatible=mepo_compatible, references=local_references, functional_groups=functional_groups, extra_info=extra_info, sa_score=sa_score, processed_ligands=processed_ligands, available_structures=available_structures, failed=failed, **cif_info) self.write(page)
def main(): """ Simple application that will read options and run the substitution for an input structure. """ info("Welcome to cliswitch; the command line interface to fapswitch") info("Using fapswitch version {}".format(fapswitch.__version__)) # Name for a the single structure job_name = options.get('job_name') # Load it input_structure = load_structure(job_name) # Structure is ready! # Begin processing info("Structure attachment sites: " "{}".format(list(input_structure.attachments))) info("Structure attachment multiplicities: " "{}".format(dict((key, len(val)) for key, val in input_structure.attachments.items()))) # Will use selected sites if specified, otherwise use all replace_only = options.gettuple('replace_only') if replace_only == (): replace_only = None # Functional group library is self initialising info("Groups in library: {}".format(functional_groups.group_list)) #Define some backends for where to send the structures backends = [] backend_options = options.gettuple('backends') if 'sqlite' in backend_options: # Initialise and add the database writer debug("Initialising the sqlite backend") try: from fapswitch.backend.sql import AlchemyBackend backend = AlchemyBackend(job_name) backend.populate_groups(functional_groups) backends.append(backend) except ImportError: error("SQLAlchemy not installed; sql backend unavailable") # done if 'file' in backend_options: # Just dumps to a named file debug("Initialising cif file writer backend") from fapswitch.backend.cif_file import CifFileBackend backends.append(CifFileBackend()) ## # User defined, single-shot functionalisations ## rotations = options.getint('rotations') info("Will rotate each group a maximum of {} times.".format(rotations)) custom_strings = options.get('custom_strings') # Pattern matching same as in the daemon # freeform strings are in braces {}, no spaces freeform_strings = re.findall('{(.*?)}', custom_strings) debug("Freeform option strings: {}".format(freeform_strings)) for freeform_string in freeform_strings: freeform_replace(input_structure, custom=freeform_string, backends=backends, rotations=rotations) # site replacements in square brackets [], no spaces site_strings = re.findall(r'\[(.*?)\]', custom_strings) debug("Site replacement options strings: {}".format(site_strings)) for site_string in site_strings: # These should be [email protected]_group2@site2 # with optional %angle site_list = [] manual_angles = [] for site in [x for x in site_string.split('.') if x]: site_id, functionalisation = site.split('@') if '%' in functionalisation: functionalisation, manual = functionalisation.split('%') else: manual = None site_list.append([site_id, functionalisation]) manual_angles.append(manual) debug(str(site_list)) debug(str(manual_angles)) site_replace(input_structure, site_list, backends=backends, rotations=rotations, manual_angles=manual_angles) ## # Full systematic replacement of everything start here ## # Only use these functional groups for replacements replace_groups = options.gettuple('replace_groups') if replace_groups == (): replace_groups = None max_different = options.getint('max_different') prob_unfunc = options.getfloat('unfunctionalised_probability') # Do absolutely every combination (might take a while) if options.getbool('replace_all_sites'): all_combinations_replace(input_structure, replace_only=replace_only, groups_only=replace_groups, max_different=max_different, backends=backends, rotations=rotations) # group@site randomisations random_count = options.getint('site_random_count') successful_randoms = 0 while successful_randoms < random_count: #function returns true if structure is generated if random_combination_replace(input_structure, replace_only=replace_only, groups_only=replace_groups, max_different=max_different, prob_unfunc=prob_unfunc, backends=backends, rotations=rotations): successful_randoms += 1 info("Generated %i of %i site random structures" % (successful_randoms, random_count)) # fully freeform randomisations random_count = options.getint('full_random_count') successful_randoms = 0 while successful_randoms < random_count: #function returns true if structure is generated if freeform_replace(input_structure, replace_only=replace_only, groups_only=replace_groups, max_different=max_different, prob_unfunc=prob_unfunc, backends=backends, rotations=rotations): successful_randoms += 1 info("Generated %i of %i fully random structures" % (successful_randoms, random_count))