<commit_before>import molecule
import linalg
import numpy
import UCONGA_analyse
from itertools import chain
import warnings
import mol_tree
import os
import glob
from scipy.optimize import leastsq
from math import degrees


def find_plane_of_ring(centered_ring_coords):
    '''
    Returns the normal to the plane of best fit of a ring system
    Uses scipy for the linear algebra
    '''
    p0 = [1.0, 1.0, 1.0, 1.0]
    def f_min(X, p):
        plane_xyz = p[0:3]
        distance = (plane_xyz*X).sum(axis=1) + p[3]
        return distance / numpy.linalg.norm(plane_xyz)
    def residuals(params, signal, X):
        return f_min(X, params)
    sol = leastsq(residuals, p0, args=(None,centered_ring_coords))[0]
    norm = numpy.array(sol[0:3])
    return norm


def is_flippable(mol, ring_at_ids):
    '''
    Test if a ring is flippable
    Rings which are noticeable non-planar are defined as being flippable
    '''
    if len(ring_at_ids) <= 3:
        return False # 3 points define a plane, and <4 atoms can't be non-planar
    ring_coords = numpy.array([mol.atoms[i].coords for i in ring_at_ids])
    center_of_ring = numpy.average(ring_coords, axis=0)
    ring_coords -= center_of_ring
    with warnings.catch_warnings(record=True) as warnings_log:
        norm = linalg.normalise(find_plane_of_ring(ring_coords))
    if len([i for i in warnings_log if 'reached maxfev' in str(i.message)]):
        # leastsq in find_plane_of_rings didn't converge.
        # Likely to be caused by a flat gradient near the minimum so it *shouldn't* effect anything
        # Let the user know just in case
        ring_name = ', '.join([str(i) for i in ring_at_ids])
        msg = 'Ring_lib.is_flippable() can\'t fit a plane to the ring with atoms %s. Continuing. This may result in identical conformers being produced, so please check the output ensemble'
        warnings.warn(msg %ring_name, RuntimeWarning)
    xy_align = linalg.rotation_from_axes(numpy.array([0.0, 0.0, 1.0]), norm)
    aligned_ring_coords = ring_coords.dot(xy_align)
    for each_at_coords in aligned_ring_coords:
        if each_at_coords[2] > 0.1: #Is this a suitable value? I don't know
            return True
    return False


def all_ring_conformers(mol):
    '''
    Find all combinations of all ring conformers for all the rings in a molecule
    '''
    all_ring_systems = find_ring_systems(mol) #Do we want to change this line?
    ring_conformer_combinations = [mol]
    for each_ring_system in all_ring_systems:
        if is_flippable(mol, each_ring_system):
            grouped_conformer_combinations = [find_ring_conformers(i, each_ring_system)
                                              for i in ring_conformer_combinations]
            ring_conformer_combinations = [i for i  in chain(*grouped_conformer_combinations)]
    return ring_conformer_combinations


def find_ring_conformers(base_mol, each_ring_system):
    '''
    Finds all unique conformers for a given ring system
    '''
    ret = []
    found_conformers = []
    all_dihedrals = [i for i in base_mol.all_torsions()]
    idx = 1
    for each_base_conformer in base_ring_variations(base_mol, each_ring_system):
        torsions = [int(degrees(each_base_conformer.get_torsion(*i))) for i in all_dihedrals]
        if (torsions not in found_conformers) and ([-1 * i for i in torsions] not in found_conformers):
            ret.append(each_base_conformer)
            found_conformers.append(torsions)
            mirror_image = flip_ring(each_base_conformer, each_ring_system)
            torsions = [int(degrees(mirror_image.get_torsion(*i))) for i in all_dihedrals]
            if (torsions not in found_conformers) and ([-1 * i for i in torsions] not in found_conformers):
                ret.append(mirror_image)
                found_conformers.append(torsions)
        idx += 1
    return ret

<commit_msg>Add control of ring conformer generation to ring_lib, along with removing unnecessary imports<commit_after>import linalg
import numpy
from itertools import chain
import warnings
import mol_tree
from scipy.optimize import leastsq
from math import degrees


def find_plane_of_ring(centered_ring_coords):
    '''
    Returns the normal to the plane of best fit of a ring system
    Uses scipy for the linear algebra
    '''
    p0 = [1.0, 1.0, 1.0, 1.0]
    def f_min(X, p):
        plane_xyz = p[0:3]
        distance = (plane_xyz*X).sum(axis=1) + p[3]
        return distance / numpy.linalg.norm(plane_xyz)
    def residuals(params, signal, X):
        return f_min(X, params)
    sol = leastsq(residuals, p0, args=(None,centered_ring_coords))[0]
    norm = numpy.array(sol[0:3])
    return norm


def is_flippable(mol, ring_at_ids):
    '''
    Test if a ring is flippable
    Rings which are noticeable non-planar are defined as being flippable
    '''
    if len(ring_at_ids) <= 3:
        return False # 3 points define a plane, and <4 atoms can't be non-planar
    ring_coords = numpy.array([mol.atoms[i].coords for i in ring_at_ids])
    center_of_ring = numpy.average(ring_coords, axis=0)
    ring_coords -= center_of_ring
    with warnings.catch_warnings(record=True) as warnings_log:
        norm = linalg.normalise(find_plane_of_ring(ring_coords))
    if len([i for i in warnings_log if 'reached maxfev' in str(i.message)]):
        # leastsq in find_plane_of_rings didn't converge.
        # Likely to be caused by a flat gradient near the minimum so it *shouldn't* effect anything
        # Let the user know just in case
        ring_name = ', '.join([str(i) for i in ring_at_ids])
        msg = 'Ring_lib.is_flippable() can\'t fit a plane to the ring with atoms %s. Continuing. This may result in identical conformers being produced, so please check the output ensemble'
        warnings.warn(msg %ring_name, RuntimeWarning)
    xy_align = linalg.rotation_from_axes(numpy.array([0.0, 0.0, 1.0]), norm)
    aligned_ring_coords = ring_coords.dot(xy_align)
    for each_at_coords in aligned_ring_coords:
        if each_at_coords[2] > 0.1: #Is this a suitable value? I don't know
            return True
    return False


def all_ring_conformers(mol, full_flip=2):
    '''
    Find all combinations of all ring conformers for all the rings in a molecule
    '''
    all_ring_systems = find_ring_systems(mol) #Do we want to change this line?
    ring_conformer_combinations = [mol]
    for each_ring_system in all_ring_systems:
        if is_flippable(mol, each_ring_system):
            grouped_conformer_combinations = [find_ring_conformers(i, each_ring_system, full_flip)
                                              for i in ring_conformer_combinations]
            ring_conformer_combinations = [i for i  in chain(*grouped_conformer_combinations)]
    return ring_conformer_combinations


def find_ring_conformers(base_mol, each_ring_system, full_flip=2):
    '''
    Finds all unique conformers for a given ring system
    '''
    if full_flip > 1:
        ret = []
        found_conformers = []
        all_dihedrals = [i for i in base_mol.all_torsions()]
        idx = 1
        for each_base_conformer in base_ring_variations(base_mol, each_ring_system):
            torsions = [int(degrees(each_base_conformer.get_torsion(*i))) for i in all_dihedrals]
            if (torsions not in found_conformers) and ([-1 * i for i in torsions] not in found_conformers):
                ret.append(each_base_conformer)
                found_conformers.append(torsions)
                mirror_image = flip_ring(each_base_conformer, each_ring_system)
                torsions = [int(degrees(mirror_image.get_torsion(*i))) for i in all_dihedrals]
                if (torsions not in found_conformers) and ([-1 * i for i in torsions] not in found_conformers):
                    ret.append(mirror_image)
                    found_conformers.append(torsions)
            idx += 1
    elif full_flip == 1:
        ret = [base_mol.copy(), flip_ring(base_mol, each_ring_system)]
    else:
        ret = [base_mol.copy()
    return ret


{'fraction_non_alphanumeric': 0.08235441139715072, 'fraction_numerical': 0.00899775056235941, 'mean_word_length': 3.0480526049570056, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 6, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 4, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '703794', 'n_tokens_mistral': 2700, 'n_tokens_neox': 2588, 'n_words': 744}