def main(): """ Main logic to find lowest energy conformation of the functional groups through an evolutionary. """ info("Welcome to confswitch; finding your lowest energy conformers") 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()))) # Functional group library is self initialising info("Groups in library: {}".format(functional_groups.group_list)) # Only use the cif backend here backends = [CifFileBackend()] # Optimise each custom string passed to fapswitch custom_strings = options.get('custom_strings') 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)) optimise_conformation(input_structure, site_list, backends=backends)
def log_info(self): """Send all the information about the group to the logging functions.""" info("[{}]".format(self.ident)) info("name = {}".format(self.name)) info("smiles = {}".format(self.smiles)) info("mepo_compatible = {}".format(self.mepo_compatible)) debug("atoms =") for atom in self.atoms: debug(" {0:4} {1:5} {2[0]:10.6f} {2[1]:10.6f} {2[2]:10.6f}". format(atom.type, atom.uff_type, atom.pos)) debug("orientation = {0[0]:.1f} {0[1]:.1f} {0[2]:.1f}".format( self.orientation)) debug("normal = {0[0]:.1f} {0[1]:.1f} {0[2]:.1f}".format(self.normal)) debug("carbon_bond = {}".format(self.bond_length)) debug("bonds =") for bond in self.bonds: debug(" {0[0]:4} {0[1]:4} {1[1]:5.2f}".format( bond, self.bonds[bond])) info("")
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 main(): """ Initialise everything needed to get the daemon running and start it up. """ info("Welcome to fapswitchd; the daemon 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()))) # 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') rotations = options.getint('rotations') info("Will rotate each group a maximum of {} times.".format(rotations)) 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()) # Make the program die if the daemon is called unsuccessfully if fapswitch_deamon(input_structure, backends=backends, rotations=rotations): info("Daemon completed successfully") else: error("Daemon did not complete successfully; check output")
def make_collision_tester(test_method='vdw', test_scale=1.122462048309373): """ Create a function that will test atom overlap based on separation and atomic radii. """ # OH NO MY CAPSLOCK GOT STUCK! test_method = test_method.lower() if test_method == 'cvdw': if _SCIPY_WEAVE == False or sys.platform in ['win32']: warning("No scipy.weave, cvdw not available, falling back to vdw") test_method = 'vdw' if test_method == 'cvdw': info('CVdW radii collision test, scale factor: %f' % test_scale) half_scale = test_scale*0.5 def collision(test_atom, atoms, cell, ignore=()): """Covalent radii collision test.""" pos = test_atom.ipos(cell.cell, cell.inverse) ipos = test_atom.ifpos(cell.inverse) for idx, atom in enumerate(atoms): # Skip non atoms if atom is None or idx in ignore: continue dist = wdist(pos, ipos, atom.ipos(cell.cell, cell.inverse), atom.ifpos(cell.inverse), cell.cell) min_dist = half_scale*(test_atom.vdw_radius + atom.vdw_radius) if dist < min_dist: return False return True elif test_method == 'vdw': info('VdW radii collision test, scale factor: %f' % test_scale) half_scale = test_scale*0.5 def collision(test_atom, atoms, cell, ignore=()): """Covalent radii collision test.""" pos = test_atom.ipos(cell.cell, cell.inverse) ipos = test_atom.ifpos(cell.inverse) for idx, atom in enumerate(atoms): # Skip non atoms if atom is None or idx in ignore: continue dist = min_vect(pos, ipos, atom.ipos(cell.cell, cell.inverse), atom.ifpos(cell.inverse), cell.cell) dist = (dot(dist, dist))**0.5 min_dist = half_scale*(test_atom.vdw_radius + atom.vdw_radius) if dist < min_dist: return False return True elif test_method == 'covalent': info('Covalent radii collision test, scale factor: %f' % test_scale) def collision(test_atom, atoms, cell, ignore=()): """Covalent radii collision test.""" # TODO(tdaff): Can't use cleaner .fractional tests as the structure # is not passed around to make _parent; can this be fixed pos = test_atom.ipos(cell.cell, cell.inverse) ipos = test_atom.ifpos(cell.inverse) for idx, atom in enumerate(atoms): # Skip non atoms if atom is None or idx in ignore: continue dist = min_vect(pos, ipos, atom.ipos(cell.cell, cell.inverse), atom.ifpos(cell.inverse), cell.cell) dist = (dot(dist, dist))**0.5 min_dist = test_scale*(test_atom.covalent_radius + atom.covalent_radius) if dist < min_dist: return False return True elif test_method == 'none': info('Collision detection turned off') def collision(*args, **kwargs): """Dummy method always reports no collision""" return True else: info('Collison test with absolute distance: %f' % test_scale) def collision(test_atom, atoms, cell, ignore=()): """Covalent radii collision test.""" pos = test_atom.ipos(cell.cell, cell.inverse) ipos = test_atom.ifpos(cell.inverse) for idx, atom in enumerate(atoms): # Skip non atoms if atom is None or idx in ignore: continue dist = min_vect(pos, ipos, atom.ipos(cell.cell, cell.inverse), atom.ifpos(cell.inverse), cell.cell) dist = (dot(dist, dist))**0.5 if dist < test_scale: return False return True # Make a closure for the tester function return collision
initial_directory = os.getcwd() os.chdir(datastore) available_structures = OrderedDict() available_structure_files = sorted(glob.glob('*.cif')) # Pulling out sites only works with well formatted cifs with site label # as the first item on the line and ' H ' within the line for attachment # sites. # Since you are running the web component, you should know what # you're doing... for available_structure in available_structure_files: sname = available_structure[:-4] available_structures[sname] = [] for line in open(available_structure): if ' H ' in line: available_structures[sname].append(line.split()[0]) # Populate this later initialised_structures = OrderedDict() # Functional group library is self initialising info("Groups in library: %s" % str(functional_groups.group_list)) #start tornado application.listen(TORNADO_PORT) info("Starting server on port number {}...".format(TORNADO_PORT)) info("Open at http://127.0.0.1:{}/index.html".format(TORNADO_PORT)) tornado.ioloop.IOLoop.instance().start()
def optimise_conformation(structure, site_list, backends): """ Optimise the conformation using a genetic algorithm """ # Set up the files and their names! known_name = path.join(os.getcwd(), '{}-known.json'.format(structure.name)) output_name = path.join(os.getcwd(), '{}-opt.csv'.format(structure.name)) opt_path = path.join(os.getcwd(), 'opt_{}'.format(structure.name)) output_file = open(output_name, 'w') output_file.write('#generation,most_fit,average\n') # Use a library if one exists if os.path.exists(known_name): known_individuals = json.load(open(known_name)) info("Existing library found") else: known_individuals = {} # Run in a subdirectory try: os.mkdir(opt_path) except OSError: pass os.chdir(opt_path) elitism = 3 max_pop = 30 mate_pop = 15 mutation_rate = 0.1 max_generations = 30 population = [] generation = 1 fill_with_randoms(population, max_pop, structure, site_list, backends) while True: for individual in population: info("Fitness of {}".format(individual['gene'])) if individual['gene'] in known_individuals: known_individual = known_individuals[individual['gene']] individual['fitness'] = known_individual['fitness'] else: individual['fitness'] = measure_fitness( structure, site_list, individual) info("From faps {}".format(individual['fitness'])) known_individuals[individual['gene']] = individual population.sort() most_fit = population[0] average_fit = sum(x['fitness'] for x in population) / len(population) info("Most fit {}: {}".format(most_fit['gene'], most_fit['fitness'])) info("Generation average: {}".format(average_fit)) output_file.write("{},{},{}\n".format(generation, most_fit['fitness'], average_fit)) output_file.flush() if generation > max_generations: break else: new_population = population[0:elitism] mate_genes(population, new_population, mate_pop, structure, site_list, backends, mutation_rate) fill_with_randoms(new_population, max_pop, structure, site_list, backends) population = new_population generation += 1 if not generation % 5: json.dump(known_individuals, open(known_name, 'w')) os.chdir('..')
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))
def freeform_replace(structure, replace_only=None, groups_only=None, num_groups=None, custom=None, rotations=36, max_different=0, prob_unfunc=0.5, backends=()): """ Replace sites with no symmetry constraint and with random rotations for successive insertion trials (i.e. there will be variation for the same structure) """ # Assume that the replace only is passed as a list or iterable # default to everything if replace_only is None: replace_only = list(structure.attachments) # Valid list is True where allowed to attach valid_list = [] for attachment, points in structure.attachments.items(): if attachment in replace_only: valid_list.extend([True] * len(points)) else: valid_list.extend([False] * len(points)) debug("Attachment mask %s" % valid_list) nsites = sum(valid_list) if groups_only is not None: local_groups = [x for x in functional_groups if x in groups_only] debug("Using only: %s" % local_groups) else: local_groups = list(functional_groups) debug("Using all groups: %s" % local_groups) if len(local_groups) > max_different > 0: local_groups = random.sample(local_groups, max_different) debug("Restricted to: %s" % local_groups) if custom is not None: # Specific functionalisation requested debug("Processing custom string: %s" % custom) func_repr = custom.strip('{}').split(".") if len(func_repr) > nsites: error("Expected %s sites; got %s" % (nsites, len(func_repr))) func_repr = func_repr[:nsites] warning("Truncated to {%s}" % ".".join(func_repr)) elif len(func_repr) < nsites: error("Expected %s sites; got %s" % (nsites, len(func_repr))) func_repr = func_repr + [''] * (nsites - len(func_repr)) warning("Padded to {%s}" % ".".join(func_repr)) for unmasked, site in zip(valid_list, func_repr): if unmasked is False and site != '': warning("Replacing masked site") else: # Randomise the selection if num_groups is None: num_groups = random.randint(1, nsites) debug("Randomly replacing %i sites" % num_groups) elif num_groups > nsites: warning("Too many sites requested; changing all %i" % nsites) num_groups = nsites func_repr = [random.choice(local_groups) for _ in range(num_groups)] # Pad to the correct length func_repr.extend([""] * (nsites - num_groups)) # Randomise random.shuffle(func_repr) # These need to be put in the unmasked slots masked_func_repr = [] for unmasked in valid_list: if unmasked: masked_func_repr.append(func_repr.pop(0)) else: masked_func_repr.append('') func_repr = masked_func_repr # Unique-ish unique_name = hashlib.md5(str(func_repr).encode('utf-8')).hexdigest() new_mof_name = [] new_mof = list(structure.atoms) new_mof_bonds = dict(structure.bonds) rotation_count = 0 for this_point, this_group in zip( chain(*[ structure.attachments[x] for x in sorted(structure.attachments) ]), func_repr): if this_group == "": new_mof_name.append("") continue else: new_mof_name.append(this_group) attachment = functional_groups[this_group] attach_id = this_point[0] attach_to = this_point[1] attach_at = structure.atoms[attach_to].pos attach_towards = this_point[2] attach_normal = structure.atoms[attach_to].normal #extracted_atoms = new_mof[attach_id:attach_id+1] new_mof[attach_id:attach_id + 1] = [None] start_idx = len(new_mof) for trial_rotation in range(rotations): incoming_group, incoming_bonds = (attachment.atoms_attached_to( attach_at, attach_towards, attach_normal, attach_to, start_idx)) for atom in incoming_group: if not test_collision( atom, new_mof, structure.cell, ignore=[attach_to]): rotation_count += 1 attach_normal = dot( rotation_about_angle(attach_towards, random.random() * np.pi * 2), attach_normal) break else: # Fits, so add and move on new_mof.extend(incoming_group) new_mof_bonds.update(incoming_bonds) break else: # this_point not valid warning("Failed to generate: %s" % ".".join([x or "" for x in func_repr])) warning("Stopped after: %s" % ".".join(new_mof_name)) debug("Needed {} rotations".format(rotation_count)) return False new_mof_name = ".".join(new_mof_name) # Counts how many have been made identifier = count() info("Generated (%i): {%s}" % (identifier, new_mof_name)) info("With unique name: %s" % unique_name) full_mof_name = "%s_free_%s" % (structure.name, new_mof_name) ligands = atoms_to_identifiers(new_mof, new_mof_bonds) cif_file = atoms_to_cif(new_mof, structure.cell, new_mof_bonds, full_mof_name, identifiers=ligands) if ligands is not None: ligand_strings = [ "{}:{}".format(ligand.smiles, ligand.sa_score) for ligand in ligands ] info("Ligands (%i): %s" % (identifier, ", ".join(ligand_strings))) for backend in backends: backend.add_freeform_structure(structure.name, func_repr, cif_file, ligands=ligands) # completed sucessfully return True
def site_replace(structure, replace_list, rotations=12, backends=(), manual_angles=None): """ Use replace list to modify the structure with (group, site) pairs. Will dump a cif to the backends on success, and return 1 for failed attempt. """ rotation_angle = 2 * np.pi / rotations new_mof_name = [] new_mof_friendly_name = [] # copy the atoms and bonds so we don't alter the original structure new_mof = list(structure.atoms) new_mof_bonds = dict(structure.bonds) rotation_count = 0 # here we can zip things up if manual_angles is None: manual_angles = [None for _ in replace_list] elif len(manual_angles) != len(replace_list): error("All angles must be specified in manual mode") for this_replace, this_manual in zip(replace_list, manual_angles): # Unpack the two values from replace list, allows us to zip with # manual angles this_group, this_site = this_replace attachment = functional_groups[this_group] # pre-calculate all the angles that we will use to zip with the # group positions, always generate a list of list of angles if this_manual is None or this_manual == '+': # generates all the possible angle sets with the same angle on # everything angles = [ trial_rotation * rotation_angle for trial_rotation in range(rotations) ] # regenerate the '+' if it is used, otherwise blank it angles_str = "" if not this_manual else '+' elif len(this_manual) == 1: # Assume single angle applies to all groups, just make one set angles = [2 * np.pi * (ord(this_manual[0]) - 97) / 26.0] angles_str = "%%%s" % this_manual debug("{:.1f} rotation for all".format(180 * angles[0] / np.pi)) elif '+' in this_manual: # keep manual angles fixed and scan all angles where it is '+' angles = [] for trial_rotation in range(rotations): this_angle = [] for x in this_manual: if x == '+': this_angle.append(trial_rotation * rotation_angle) else: this_angle.append(2 * np.pi * (ord(x) - 97) / 26.0) angles.append(this_angle) angles_str = "%%%s" % this_manual debug("Angles from: {}".format( [round(180 * x / np.pi, 1) for x in angles[0]])) debug("Angles to: {}".format( [round(180 * x / np.pi, 1) for x in angles[-1]])) else: # a = 0 degrees, z = 346 # _ is negative (or anything else negative...) angles = [[2 * np.pi * (ord(x) - 97) / 26.0 for x in this_manual]] angles_str = "%%%s" % this_manual debug("Angles: {}".format( [round(180 * x / np.pi, 1) for x in angles[0]])) new_mof_name.append("%s@%s%s" % (this_group, this_site, angles_str)) new_mof_friendly_name.append("%s@%s%s" % (attachment.name, this_site, angles_str)) current_mof = list(new_mof) current_mof_bonds = dict(new_mof_bonds) for this_angle in angles: # for single angles, make a list to zip with each site if isinstance(this_angle, float): this_angle = [this_angle] * len( structure.attachments[this_site]) if len(this_angle) < len(structure.attachments[this_site]): error("Not enough manual angles : {} needed, {} found".format( len(structure.attachments[this_site]), len(this_angle))) return False for this_point, this_rotation in zip( structure.attachments[this_site], this_angle): if this_rotation < 0: # manually specified empty site continue attach_id = this_point[0] attach_to = this_point[1] attach_at = structure.atoms[attach_to].pos attach_towards = this_point[2] attach_normal = structure.atoms[attach_to].normal new_mof[attach_id:attach_id + 1] = [None] start_idx = len(new_mof) attach_normal = dot( rotation_about_angle(attach_towards, this_rotation), attach_normal) incoming_group, incoming_bonds = attachment.atoms_attached_to( attach_at, attach_towards, attach_normal, attach_to, start_idx) group_fits = True for atom in incoming_group: if not test_collision( atom, new_mof, structure.cell, ignore=[attach_to]): group_fits = False break else: # Fits, so add and move on new_mof.extend(incoming_group) new_mof_bonds.update(incoming_bonds) if not group_fits: # kill this loop rotation_count += 1 new_mof = list(current_mof) new_mof_bonds = dict(current_mof_bonds) break else: # All sites attached without collision, # break out of the rotations loop break else: # Did not attach after all rotations fail_name = ".".join(["@".join(x) for x in replace_list]) warning("Failed: %s@%s from %s" % (this_group, this_site, fail_name)) debug("Needed {} rotations".format(rotation_count)) return False debug("Needed {} rotations".format(rotation_count)) new_mof_name = ".".join(new_mof_name) new_mof_friendly_name = ".".join(new_mof_friendly_name) # Counts how many have been made identifier = count() info("Generated (%i): [%s]" % (identifier, new_mof_friendly_name)) full_mof_name = "%s_func_%s" % (structure.name, new_mof_name) ligands = atoms_to_identifiers(new_mof, new_mof_bonds) cif_file = atoms_to_cif(new_mof, structure.cell, new_mof_bonds, full_mof_name, identifiers=ligands) if ligands is not None: ligand_strings = [ "{}:{}".format(ligand.smiles, ligand.sa_score) for ligand in ligands ] info("Ligands (%i): %s" % (identifier, ", ".join(ligand_strings))) for backend in backends: backend.add_symmetry_structure(structure.name, replace_list, cif_file, ligands=ligands, manual_angles=manual_angles) # successful return True
def load_structure(name): """ Load a structure from a pickle or generate a new one as required. Returns an initialised structure. Caches loaded structure on disk. """ info("Structure version {}.{}".format(*DOT_FAPSWITCH_VERSION)) pickle_file = "__{}.fapswitch".format(name) loaded = False if path.exists(pickle_file): info("Existing structure found: {}; loading...".format(pickle_file)) #TODO(tdaff): deal with errors try: with open(pickle_file, 'rb') as p_structure: structure = pickle.load(p_structure) # Negative versions ensure that very old caches will be removed if not hasattr(structure, 'fapswitch_version'): structure.fapswitch_version = (-1, -1) # Need to make sure it is still valid if structure.fapswitch_version[0] < DOT_FAPSWITCH_VERSION[0]: error("Old dot-fapswitch detected, re-initialising") loaded = False elif structure.fapswitch_version[1] < DOT_FAPSWITCH_VERSION[1]: warning( "Cached file {} may be out of date".format(pickle_file)) loaded = True else: debug("Finished loading") loaded = True except EOFError: warning("Corrupt pickle; re-initialising") loaded = False if not loaded: info("Initialising a new structure. This may take some time.") structure = Structure(name) structure.from_cif('{}.cif'.format(name)) # Ensure that atoms in the structure are properly typed structure.gen_factional_positions() bonding_src = options.get('connectivity') if bonding_src == 'file': # Rudimentary checks for poor structures if not hasattr(structure, 'bonds'): error("No bonding in input structure, will probably fail") elif len(structure.bonds) == 0: error("Zero bonds found, will fail") elif not hasattr(structure.atoms[0], 'uff_type'): warning("Atoms not properly typed, expect errors") else: info("Bonding from input file used") elif bonding_src == 'bondsonly': info("Generating atom types from cif bonding") structure.gen_types_from_bonds() elif bonding_src == 'openbabel': info("Generating topology with Open Babel") structure.gen_babel_uff_properties() # A couple of structure checks structure.check_close_contacts() structure.bond_length_check() # Initialise the sites after bonds are perceived structure.gen_attachment_sites() structure.gen_normals() # Cache the results info("Dumping cache of structure to {}".format(pickle_file)) structure.fapswitch_version = DOT_FAPSWITCH_VERSION with open(pickle_file, 'wb') as p_structure: pickle.dump(structure, p_structure, protocol=-1) return structure