Beispiel #1
0
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)
Beispiel #2
0
    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("")
Beispiel #3
0
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
Beispiel #4
0
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")
Beispiel #5
0
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
Beispiel #6
0
    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()
Beispiel #7
0
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('..')
Beispiel #8
0
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))
Beispiel #9
0
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
Beispiel #10
0
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
Beispiel #11
0
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