예제 #1
0
    def init(self, mol, i, update=False, orb_dim=None, conf=0) -> None:

        self.index = i
        self.symbol = pt[mol.atomnos[i]].symbol
        neighbors_indexes = neighbors(mol.graph, i)
        
        self.neighbors_symbols = [pt[mol.atomnos[i]].symbol for i in neighbors_indexes]
        self.coord = mol.atomcoords[conf][i]
        self.others = mol.atomcoords[conf][neighbors_indexes]

        self.vectors = self.others - self.coord # vectors connecting reactive atom with neighbors

        v1 = self.vectors[0]
        # v1 connects first bonded atom to the metal itself

        neighbor_of_neighbor_index = neighbors(mol.graph, neighbors_indexes[0])[0]
        v2 = mol.atomcoords[conf][neighbor_of_neighbor_index] - self.coord
        # v2 connects first neighbor of the first neighbor to the metal itself

        self.orb_vec = norm(rot_mat_from_pointer(np.cross(v1, v2), 120) @ v1)
        # setting the pointer (orb_vec) so that orbitals are oriented correctly
        # (Lithium enolate in mind)

        steps = 4 # number of total orbitals
        self.orb_vecs = np.array([rot_mat_from_pointer(v1, angle) @ self.orb_vec for angle in range(0,360,int(360/steps))])

        if update:
            if orb_dim is None:
                orb_dim = orb_dim_dict[str(self)]
            
            self.center = (self.orb_vecs * orb_dim) + self.coord
예제 #2
0
    def init(self, mol, i, update=False, orb_dim=None, conf=0) -> None:
        '''
        '''
        self.index = i
        self.symbol = pt[mol.atomnos[i]].symbol
        neighbors_indexes = neighbors(mol.graph, i)
        


        self.neighbors_symbols = [pt[mol.atomnos[i]].symbol for i in neighbors_indexes]
        self.coord = mol.atomcoords[conf][i]
        self.others = mol.atomcoords[conf][neighbors_indexes]

        self.vectors = self.others - self.coord # vectors connecting reactive atom with neighbors
        self.orb_vec = norm(np.mean(np.array([np.cross(norm(self.vectors[0]), norm(self.vectors[1])),
                                              np.cross(norm(self.vectors[1]), norm(self.vectors[2])),
                                              np.cross(norm(self.vectors[2]), norm(self.vectors[0]))]), axis=0))

        self.orb_vecs = np.vstack((self.orb_vec, -self.orb_vec))

        if update:
            if orb_dim is None:
                key = self.symbol + ' ' + str(self)
                orb_dim = orb_dim_dict.get(key)
                
                if orb_dim is None:
                    orb_dim = orb_dim_dict['Fallback']
                    print(f'ATTENTION: COULD NOT SETUP REACTIVE ATOM ORBITAL FROM PARAMETERS. We have no parameters for {key}. Using {orb_dim} A.')
            
            self.center = self.orb_vecs * orb_dim      

            self.center += self.coord
예제 #3
0
    def init(self, mol, i, update=False, orb_dim=None, conf=0) -> None:
        '''
        '''
        self.index = i
        self.symbol = pt[mol.atomnos[i]].symbol
        neighbors_indexes = neighbors(mol.graph, i)
        


        self.neighbors_symbols = [pt[mol.atomnos[i]].symbol for i in neighbors_indexes]
        self.coord = mol.atomcoords[conf][i]
        self.others = mol.atomcoords[conf][neighbors_indexes]

        self.vectors = self.others - self.coord # vector connecting center to substituent

        if update:
            if orb_dim is None:
                key = self.symbol + ' ' + str(self)
                orb_dim = orb_dim_dict.get(key)
                
                if orb_dim is None:
                    orb_dim = orb_dim_dict['Fallback']
                    print(f'ATTENTION: COULD NOT SETUP REACTIVE ATOM ORBITAL FROM PARAMETERS. We have no parameters for {key}. Using {orb_dim} A.')
        
            if mol.sigmatropic[conf]:
                # two p lobes
                p_lobe = norm(np.cross(self.vectors[0], self.vectors[1]))*orb_dim
                self.orb_vecs = np.concatenate(([p_lobe], [-p_lobe]))

            else:
                # lone pair lobe
                self.orb_vecs = np.array([-norm(np.mean([norm(v) for v in self.vectors], axis=0))*orb_dim])

            self.center = self.orb_vecs + self.coord
예제 #4
0
    def init(self, mol, i, update=False, orb_dim=None, conf=0) -> None:
        '''
        '''
        self.index = i
        self.symbol = pt[mol.atomnos[i]].symbol
        neighbors_indexes = neighbors(mol.graph, i)
        


        self.neighbors_symbols = [pt[mol.atomnos[i]].symbol for i in neighbors_indexes]
        self.coord = mol.atomcoords[conf][i]
        self.others = mol.atomcoords[conf][neighbors_indexes]

        self.orb_vecs = self.others - self.coord # vectors connecting center to each of the two substituents

        if update:
            if orb_dim is None:
                key = self.symbol + ' ' + str(self)
                orb_dim = orb_dim_dict.get(key)
                
                if orb_dim is None:
                    orb_dim = orb_dim_dict['Fallback']
                    print(f'ATTENTION: COULD NOT SETUP REACTIVE ATOM ORBITAL FROM PARAMETERS. We have no parameters for {key}. Using {orb_dim} A.')

            self.orb_vecs = orb_dim * np.array([norm(v) for v in self.orb_vecs]) # making both vectors a fixed, defined length

            orb_mat = rot_mat_from_pointer(np.mean(self.orb_vecs, axis=0), 90) @ rot_mat_from_pointer(np.cross(self.orb_vecs[0], self.orb_vecs[1]), 180)

            # self.orb_vecs = np.array([orb_mat @ v for v in self.orb_vecs])
            self.orb_vecs = (orb_mat @ self.orb_vecs.T).T
            
            self.center = self.orb_vecs + self.coord
예제 #5
0
def PreventScramblingConstraint(graph,
                                atoms,
                                double_bond_protection=False,
                                fix_angles=False):
    '''
    graph: NetworkX graph of the molecule
    atoms: ASE atoms object

    return: FixInternals constraint to apply to ASE calculations
    '''
    angles_deg = None
    if fix_angles:
        allpaths = []

        for node in graph:
            allpaths.extend(findPaths(graph, node, 2))

        allpaths = {tuple(sorted(path)) for path in allpaths}

        angles_deg = []
        for path in allpaths:
            angles_deg.append([atoms.get_angle(*path), list(path)])

    bonds = []
    for bond in [[a, b] for a, b in graph.edges if a != b]:
        bonds.append([atoms.get_distance(*bond), bond])

    dihedrals_deg = None
    if double_bond_protection:
        double_bonds = get_double_bonds_indexes(atoms.positions,
                                                atoms.get_atomic_numbers())
        if double_bonds != []:
            dihedrals_deg = []
            for a, b in double_bonds:
                n_a = neighbors(graph, a)
                n_a.remove(b)

                n_b = neighbors(graph, b)
                n_b.remove(a)

                d = [n_a[0], a, b, n_b[0]]
                dihedrals_deg.append([atoms.get_dihedral(*d), d])

    return FixInternals(dihedrals_deg=dihedrals_deg,
                        angles_deg=angles_deg,
                        bonds=bonds,
                        epsilon=1)
예제 #6
0
    def init(self, mol, i, update=False, orb_dim=None, conf=0) -> None:
        '''
        '''
        self.index = i
        self.symbol = pt[mol.atomnos[i]].symbol
        neighbors_indexes = neighbors(mol.graph, i)

        self.neighbors_symbols = [pt[mol.atomnos[i]].symbol for i in neighbors_indexes]
        self.coord = mol.atomcoords[conf][i]
        self.other = mol.atomcoords[conf][neighbors_indexes][0]

        if not mol.sp3_sigmastar:
            self.orb_vecs = np.array([norm(self.coord - self.other)])

        else:
            other_reactive_indexes = list(mol.reactive_indexes)
            other_reactive_indexes.remove(i)
            for index in other_reactive_indexes:
                    if index in neighbors_indexes:
                        parnter_index = index
                        break
            # obtain the reference partner index

            partner = mol.atomcoords[conf][parnter_index]
            pivot = norm(partner - self.coord)

            neighbors_of_partner = neighbors(mol.graph, parnter_index)
            neighbors_of_partner.remove(i)
            orb_vec = norm(mol.atomcoords[conf][neighbors_of_partner[0]] - partner)
            orb_vec = orb_vec - orb_vec @ pivot * pivot

            steps = 3 # number of total orbitals
            self.orb_vecs = np.array([rot_mat_from_pointer(pivot, angle+60) @ orb_vec for angle in range(0,360,int(360/steps))])
            # orbitals are staggered in relation to sp3 substituents

            self.orb_vers = norm(self.orb_vecs[0])

        if update:
            if orb_dim is None:
                key = self.symbol + ' ' + str(self)
                orb_dim = orb_dim_dict.get(key)

                if orb_dim is None:
                    orb_dim = norm_of(self.coord - self.other)
                    print(f'ATTENTION: COULD NOT SETUP REACTIVE ATOM ORBITAL FROM PARAMETERS. We have no parameters for {key}. Using the bonding distance ({round(orb_dim, 3)} A).')

            self.center = orb_dim * self.orb_vecs + self.coord
예제 #7
0
def get_atom_type(graph, index):
    '''
    Returns the appropriate class to represent
    the atom with the given index on the graph
    '''
    nb = neighbors(graph, index)

    return atom_type_dict[pt[graph.nodes[index]['atomnos']].symbol + str(len(nb))]
예제 #8
0
def _is_free(index, graph):
    '''
    Return True if the index specified
    satisfies all of the following:
    - Is not a sp2 carbonyl carbon atom
    - Is not the oxygen atom of an ester
    - Is not the nitrogen atom of a secondary amide (CONHR)

    '''
    if all((graph.nodes[index]['atomnos'] == 6, is_sp_n(index, graph, 2), 8
            in (graph.nodes[n]['atomnos'] for n in neighbors(graph, index)))):
        return False

    if is_amide_n(index, graph, mode=1):
        return False

    if is_ester_o(index, graph):
        return False

    return True
예제 #9
0
    def get_anchors(self, mol, conf=0, aromatic=False):
        '''
        mol: a Hypermolecule object
        conf: the conformer index to be used

        returns a 3-tuple of arrays
        
        centers - absolute coordinates of points
        vectors - direction of center relative to its atom
        label - 0: electron-poor, 1: electron-rich, 2: aromatic
        '''

        centers, vectors, labels = [], [], []
        # initializing the lists

        for i, atom in enumerate(mol.atomnos):
            if atom in (7, 8):
                # N and O atoms
                atom_cls = get_atom_type(mol.graph, i)()
                atom_cls.init(mol, i, update=True, orb_dim=1, conf=conf)
                for c, v in zip(atom_cls.center, atom_cls.orb_vecs):
                    centers.append(c)
                    vectors.append(v)
                    labels.append(1)

            elif atom == 1 and any((mol.graph.nodes[n]['atomnos'] in (7, 8)
                                    for n in neighbors(mol.graph, i))):
                # protic H atoms
                atom_cls = get_atom_type(mol.graph, i)()
                atom_cls.init(mol, i, update=True, orb_dim=1, conf=conf)
                for c, v in zip(atom_cls.center, atom_cls.orb_vecs):
                    centers.append(c)
                    vectors.append(v)
                    labels.append(0)

        # looking for aromatic rings

        if aromatic and len(mol.atomnos) > 9:

            for coords_ in get_phenyls(mol.atomcoords[conf], mol.atomnos):

                mean = np.mean(coords_, axis=0)
                # getting the center of the ring

                vec = 1.8 * norm(
                    np.cross(coords_[0] - coords_[1], coords_[1] - coords_[2]))
                # normal vector orthogonal to the ring
                # 1.8 A so that rings will stack at around 3.6 A

                centers.append(mean + vec)
                vectors.append(vec)
                labels.append(2)

                centers.append(mean - vec)
                vectors.append(-vec)
                labels.append(2)

        centers = np.array(centers)
        vectors = np.array(vectors)
        labels = np.array(labels)

        return centers, vectors, labels
예제 #10
0
def ase_bend(embedder,
             original_mol,
             conf,
             pivot,
             threshold,
             title='temp',
             traj=None,
             check=True):
    '''
    embedder: TSCoDe embedder object
    original_mol: Hypermolecule object to be bent
    conf: index of conformation in original_mol to be used
    pivot: pivot connecting two Hypermolecule orbitals to be approached/distanced
    threshold: target distance for the specified pivot, in Angstroms
    title: name to be used for referring to this structure in the embedder log
    traj: if set to a string, traj+'.traj' is used as a filename for the bending trajectory.
          not only the atoms will be printed, but also all the orbitals and the active pivot.
    check: if True, after bending checks that the bent structure did not scramble.
           If it did, returns the initial molecule.
    '''

    identifier = np.sum(original_mol.atomcoords[conf])

    if hasattr(embedder, "ase_bent_mols_dict"):
        cached = embedder.ase_bent_mols_dict.get(
            (identifier, tuple(sorted(pivot.index)), round(threshold, 3)))
        if cached is not None:
            return cached

    if traj is not None:

        from ase.io.trajectory import Trajectory

        def orbitalized(atoms, orbitals, pivot=None):
            positions = np.concatenate((atoms.positions, orbitals))

            if pivot is not None:
                positions = np.concatenate(
                    (positions, [pivot.start], [pivot.end]))

            symbols = list(atoms.numbers) + [0 for _ in orbitals]

            if pivot is not None:
                symbols += [9 for _ in range(2)]
            # Fluorine (9) represents active orbitals

            new_atoms = Atoms(symbols, positions=positions)
            return new_atoms

        try:
            os.remove(traj)
        except FileNotFoundError:
            pass

    i1, i2 = original_mol.reactive_indexes

    neighbors_of_1 = neighbors(original_mol.graph, i1)
    neighbors_of_2 = neighbors(original_mol.graph, i2)

    mol = deepcopy(original_mol)
    final_mol = deepcopy(original_mol)

    for p in mol.pivots[conf]:
        if p.index == pivot.index:
            active_pivot = p
            break

    dist = norm_of(active_pivot.pivot)

    atoms = Atoms(mol.atomnos, positions=mol.atomcoords[conf])

    atoms.calc = get_ase_calc(embedder)

    if traj is not None:
        traj_obj = Trajectory(
            traj + f'_conf{conf}.traj',
            mode='a',
            atoms=orbitalized(
                atoms,
                np.vstack([
                    atom.center
                    for atom in mol.reactive_atoms_classes_dict[0].values()
                ]), active_pivot))
        traj_obj.write()

    unproductive_iterations = 0
    break_reason = 'MAX ITER'
    t_start = time.perf_counter()

    for iteration in range(500):

        atoms.positions = mol.atomcoords[0]

        orb_memo = {
            index: norm_of(atom.center[0] - atom.coord)
            for index, atom in mol.reactive_atoms_classes_dict[0].items()
        }

        orb1, orb2 = active_pivot.start, active_pivot.end

        c1 = OrbitalSpring(i1,
                           i2,
                           orb1,
                           orb2,
                           neighbors_of_1,
                           neighbors_of_2,
                           d_eq=threshold)

        c2 = PreventScramblingConstraint(
            mol.graph,
            atoms,
            double_bond_protection=embedder.options.double_bond_protection,
            fix_angles=embedder.options.fix_angles_in_deformation)

        atoms.set_constraint([
            c1,
            c2,
        ])

        opt = BFGS(atoms, maxstep=0.2, logfile=None, trajectory=None)

        try:
            opt.run(fmax=0.5, steps=1)
        except ValueError:
            # Shake did not converge
            break_reason = 'CRASHED'
            break

        if traj is not None:
            traj_obj.atoms = orbitalized(
                atoms,
                np.vstack([
                    atom.center
                    for atom in mol.reactive_atoms_classes_dict[0].values()
                ]))
            traj_obj.write()

        # check if we are stuck
        if np.max(
                np.abs(
                    np.linalg.norm(atoms.get_positions() - mol.atomcoords[0],
                                   axis=1))) < 0.01:
            unproductive_iterations += 1

            if unproductive_iterations == 10:
                break_reason = 'STUCK'
                break

        else:
            unproductive_iterations = 0

        mol.atomcoords[0] = atoms.get_positions()

        # Update orbitals and get temp pivots
        for index, atom in mol.reactive_atoms_classes_dict[0].items():
            atom.init(mol, index, update=True, orb_dim=orb_memo[index])
            # orbitals positions are calculated based on the conformer we are working on

        temp_pivots = embedder._get_pivots(mol)[0]

        for p in temp_pivots:
            if p.index == pivot.index:
                active_pivot = p
                break
        # print(active_pivot)

        dist = norm_of(active_pivot.pivot)
        # print(f'{iteration}. {mol.name} conf {conf}: pivot is {round(dist, 3)} (target {round(threshold, 3)})')

        if dist - threshold < 0.1:
            break_reason = 'CONVERGED'
            break
        # else:
        # print('delta is ', round(dist - threshold, 3))

    embedder.log(
        f'    {title} - conformer {conf} - {break_reason}{" "*(9-len(break_reason))} ({iteration+1}{" "*(3-len(str(iteration+1)))} iterations, {time_to_string(time.perf_counter()-t_start)})',
        p=False)

    if check:
        if not molecule_check(original_mol.atomcoords[conf],
                              mol.atomcoords[0],
                              mol.atomnos,
                              max_newbonds=1):
            mol.atomcoords[0] = original_mol.atomcoords[conf]
        # keep the bent structures only if no scrambling occurred between atoms

    final_mol.atomcoords[conf] = mol.atomcoords[0]

    # Now align the ensembles on the new reactive atoms positions

    reference, *targets = final_mol.atomcoords
    reference = np.array(reference)
    targets = np.array(targets)

    r = reference - np.mean(reference[final_mol.reactive_indexes], axis=0)
    ts = np.array(
        [t - np.mean(t[final_mol.reactive_indexes], axis=0) for t in targets])

    output = []
    output.append(r)
    for target in ts:
        matrix = kabsch(r, target)
        output.append([matrix @ vector for vector in target])

    final_mol.atomcoords = np.array(output)

    # Update orbitals and pivots
    for conf_, _ in enumerate(final_mol.atomcoords):
        for index, atom in final_mol.reactive_atoms_classes_dict[conf_].items(
        ):
            atom.init(final_mol, index, update=True, orb_dim=orb_memo[index])

    embedder._set_pivots(final_mol)

    # add result to cache (if we have it) so we avoid recomputing it
    if hasattr(embedder, "ase_bent_mols_dict"):
        embedder.ase_bent_mols_dict[(identifier, tuple(sorted(pivot.index)),
                                     round(threshold, 3))] = final_mol

    clean_directory()

    return final_mol
예제 #11
0
def _is_nondummy(i, root, graph) -> bool:
    '''
    Checks that a molecular rotation along the dihedral
    angle (*, root, i, *) is non-dummy, that is the atom
    at index i, in the direction opposite to the one leading
    to root, has different substituents. i.e. methyl and tBu
    rotations return False.

    Thought to eliminate methyl/tert-butyl-type rotations.
    '''

    if graph.nodes[i]['atomnos'] not in (6, 7):
        return True
    # for now, we only discard rotations around carbon
    # and nitrogen atoms, like methyl/tert-butyl/triphenyl
    # and flat symmetrical rings like phenyl, N-pyrrolyl...

    G = deepcopy(graph)
    nb = neighbors(G, i)
    nb.remove(root)

    if len(nb) == 1:
        if len(neighbors(G, nb[0])) == 2:
            return False
    # if node i has two bonds only (one with root and one with a)
    # and the other atom (a) has two bonds only (one with i)
    # the rotation is considered dummy: some other rotation
    # will account for its freedom (i.e. alkynes, hydrogen bonds)

    for n in nb:
        G.remove_edge(i, n)

    subgraphs_nodes = [
        _set for _set in nx.connected_components(G) if not root in _set
    ]

    if len(subgraphs_nodes) == 1:

        for n in nb:
            G.add_edge(i, n)
        # restore i-neighbor bonds removed previously

        subgraph = G.subgraph(list(subgraphs_nodes[0]) + [i])
        cycles = [l for l in nx.cycle_basis(subgraph, root=i) if len(l) != 1]
        # get all cycles that involve i, if any

        if cycles:
            # in this case, i is part of a cycle and root is exocyclic
            # and we will take care of this in a separate function
            return _is_cyclical_nondummy(G, cycles[0])

        return True
        # if not, the torsion is likely to be rotable
        # (tetramethylguanidyl alanine C(β)-N bond)

    subgraphs = [nx.subgraph(G, s) for s in subgraphs_nodes]
    for sub in subgraphs[1:]:
        if not nx.is_isomorphic(
                subgraphs[0],
                sub,
                node_match=lambda n1, n2: n1['atomnos'] == n2['atomnos']):
            return True
    # Care should be taken because chiral centers are not taken into account: a rotation
    # involving an index where substituents only differ by stereochemistry, and where a
    # rotation is not an element of symmetry of the subsystem, the rotation is discarded
    # even if it would be meaningful to keep it.

    return False
예제 #12
0
    def init(self, mol, i, update=False, orb_dim=None, conf=0) -> None:

        self.index = i
        self.symbol = pt[mol.atomnos[i]].symbol
        neighbors_indexes = neighbors(mol.graph, i)
        
        self.neighbors_symbols = [pt[mol.atomnos[i]].symbol for i in neighbors_indexes]

        self.coord = mol.atomcoords[conf][i]
        self.others = mol.atomcoords[conf][neighbors_indexes]

        self.vectors = self.others - self.coord # vector connecting center to substituent


        angle = vec_angle(norm(self.others[0] - self.coord),
                          norm(self.others[1] - self.coord))
        
        if np.abs(angle - 180) < 5:
            self.type = 'sp'

        else:
            self.type = 'bent carbene'

        self.allene = False
        self.ketene = False
        if self.type == 'sp' and all([s == 'C' for s in self.neighbors_symbols]):

            neighbors_of_neighbors_indexes = (neighbors(mol.graph, neighbors_indexes[0]),
                                              neighbors(mol.graph, neighbors_indexes[1]))

            neighbors_of_neighbors_indexes[0].remove(i)
            neighbors_of_neighbors_indexes[1].remove(i)

            if (len(side1) == len(side2) == 2 for side1, side2 in neighbors_of_neighbors_indexes):
                self.allene = True

        elif self.type == 'sp' and sorted(self.neighbors_symbols) in (['C', 'O'], ['C', 'S']):

            self.ketene = True

            neighbors_of_neighbors_indexes = (neighbors(mol.graph, neighbors_indexes[0]),
                                              neighbors(mol.graph, neighbors_indexes[1]))

            neighbors_of_neighbors_indexes[0].remove(i)
            neighbors_of_neighbors_indexes[1].remove(i)
                
            if len(neighbors_of_neighbors_indexes[0]) == 2:
                substituent = mol.atomcoords[conf][neighbors_of_neighbors_indexes[0][0]]
                ketene_atom = mol.atomcoords[conf][neighbors_indexes[0]]
                self.ketene_ref = substituent - ketene_atom

            elif len(neighbors_of_neighbors_indexes[1]) == 2:
                substituent = mol.atomcoords[conf][neighbors_of_neighbors_indexes[1][0]]
                ketene_atom = mol.atomcoords[conf][neighbors_indexes[1]]
                self.ketene_ref = substituent - ketene_atom

            else:
                self.ketene = False

        if update:
            if orb_dim is None:
                key = self.symbol + ' ' + self.type
                orb_dim = orb_dim_dict.get(key)
                
                if orb_dim is None:
                    orb_dim = orb_dim_dict['Fallback']
                    print(f'ATTENTION: COULD NOT SETUP REACTIVE ATOM ORBITAL FROM PARAMETERS. We have no parameters for {key}. Using {orb_dim} A.')
        
            if self.type == 'sp':

                v = np.random.rand(3)
                pivot1 = v - ((v @ norm(self.vectors[0])) * self.vectors[0])

                if self.allene or self.ketene:
                    # if we have an allene or ketene, pivot1 is aligned to
                    # one substituent so that the resulting positions
                    # for the four orbital centers make chemical sense.

                    axis = norm(self.others[0] - self.others[1])
                    # versor connecting reactive atom neighbors
                    
                    if self.allene:
                        ref = (mol.atomcoords[conf][neighbors_of_neighbors_indexes[0][0]] -
                               mol.atomcoords[conf][neighbors_indexes[0]])
                    else:
                        ref = self.ketene_ref

                    pivot1 = ref - ref @ axis * axis
                    # projection of ref orthogonal to axis (vector rejection)


                pivot2 = norm(np.cross(pivot1, self.vectors[0]))
                        
                self.orb_vecs = np.array([rot_mat_from_pointer(pivot2, 90) @
                                          rot_mat_from_pointer(pivot1, angle) @
                                          norm(self.vectors[0]) for angle in (0, 90, 180, 270)]) * orb_dim

                self.center = self.orb_vecs + self.coord
                # four vectors defining the position of the four orbital lobes centers



            else: # bent carbene case: three centers, sp2+p
                
                self.orb_vecs = np.array([-norm(np.mean([norm(v) for v in self.vectors], axis=0))*orb_dim])
                # one sp2 center first

                p_vec = np.cross(norm(self.vectors[0]), norm(self.vectors[1]))
                p_vecs = np.array([norm(p_vec)*orb_dim, -norm(p_vec)*orb_dim])
                self.orb_vecs = np.concatenate((self.orb_vecs, p_vecs))
                # adding two p centers

                self.center = self.orb_vecs + self.coord
예제 #13
0
    def init(self, mol, i, update=False, orb_dim=None, conf=0) -> None:
        '''
        '''
        self.index = i
        self.symbol = pt[mol.atomnos[i]].symbol
        neighbors_indexes = neighbors(mol.graph, i)       


        self.neighbors_symbols = [pt[mol.atomnos[i]].symbol for i in neighbors_indexes]
        self.coord = mol.atomcoords[conf][i]
        self.other = mol.atomcoords[conf][neighbors_indexes][0]

        self.vector = self.other - self.coord # vector connecting center to substituent

        if update:
            if orb_dim is None:
                key = self.symbol + ' ' + str(self)
                orb_dim = orb_dim_dict.get(key)
                
                if orb_dim is None:
                    orb_dim = orb_dim_dict['Fallback']
                    print(f'ATTENTION: COULD NOT SETUP REACTIVE ATOM ORBITAL FROM PARAMETERS. We have no parameters for {key}. Using {orb_dim} A.')

            neighbors_of_neighbor_indexes = neighbors(mol.graph, neighbors_indexes[0])
            neighbors_of_neighbor_indexes.remove(i)

            self.vector = norm(self.vector)*orb_dim

            if len(neighbors_of_neighbor_indexes) == 2:
            # if it is a normal ketone (or an enolate), n orbital lobes must be coplanar with
            # atoms connecting to ketone C atom, or p lobes must be placed accordingly

                a1 = mol.atomcoords[conf][neighbors_of_neighbor_indexes[0]]
                a2 = mol.atomcoords[conf][neighbors_of_neighbor_indexes[1]]
                pivot = norm(np.cross(a1 - self.coord, a2 - self.coord))

                if mol.sigmatropic[conf]:
                    # two p lobes
                    self.center = np.concatenate(([pivot*orb_dim], [-pivot*orb_dim]))

                else:
                    #two n lobes
                    self.center = np.array([rot_mat_from_pointer(pivot, angle) @ self.vector for angle in (120,240)])

            elif len(neighbors_of_neighbor_indexes) in (1, 3):
            # ketene or alkoxide
            
                ketene_sub_indexes = neighbors(mol.graph, neighbors_of_neighbor_indexes[0])
                ketene_sub_indexes.remove(neighbors_indexes[0])

                ketene_sub_coords = mol.atomcoords[conf][ketene_sub_indexes[0]]
                n_o_n_coords = mol.atomcoords[conf][neighbors_of_neighbor_indexes[0]]

                # vector connecting ketene R with C (O=C=C(R)R)
                v = (ketene_sub_coords - n_o_n_coords)

                # this vector is orthogonal to the ketene O=C=C and coplanar with the ketene
                pointer = v - ((v @ norm(self.vector)) * self.vector)
                pointer = norm(pointer) * orb_dim

                self.center = np.array([rot_mat_from_pointer(self.vector, 90*step) @ pointer for step in range(4)])
            
        
            self.orb_vecs = np.array([norm(center) for center in self.center])
            # unit vectors connecting reactive atom coord with orbital centers

            self.center += self.coord
예제 #14
0
    def init(self, mol, i, update=False, orb_dim=None, conf=0) -> None:

        self.index = i
        self.symbol = pt[mol.atomnos[i]].symbol
        neighbors_indexes = neighbors(mol.graph, i)
        self.neighbors_symbols = [pt[mol.atomnos[i]].symbol for i in neighbors_indexes]
        self.coord = mol.atomcoords[conf][i]
        self.others = mol.atomcoords[conf][neighbors_indexes]
        
        if not mol.sp3_sigmastar:

            if not hasattr(self, 'leaving_group_index'):
                self.leaving_group_index = None

            if len([atom for atom in self.neighbors_symbols if atom in ['O', 'N', 'Cl', 'Br', 'I']]) == 1: # if we can tell where is the leaving group
                self.leaving_group_coords = self.others[self.neighbors_symbols.index([atom for atom in self.neighbors_symbols if atom in ['O', 'Cl', 'Br', 'I']][0])]

            elif len([atom for atom in self.neighbors_symbols if atom not in ['H']]) == 1: # if no clear leaving group but we only have one atom != H
                self.leaving_group_coords = self.others[self.neighbors_symbols.index([atom for atom in self.neighbors_symbols if atom not in ['H']][0])]

            else: # if we cannot infer, ask user if we didn't have already 
                try:
                    self.leaving_group_coords = self._set_leaving_group(mol, neighbors_indexes)

                except Exception:
                # if something goes wrong, we fallback to command line input for reactive atom index collection

                    if self.leaving_group_index is None:

                        while True:

                            self.leaving_group_index = input(f'Please insert the index of the leaving group atom bonded to the sp3 reactive atom (index {self.index}) of molecule {mol.rootname} : ')
                            
                            if self.leaving_group_index == '' or self.leaving_group_index.lower().islower():
                                pass
                            
                            elif int(self.leaving_group_index) in neighbors_indexes:
                                self.leaving_group_index = int(self.leaving_group_index)
                                break

                            else:
                                print(f'Atom {self.leaving_group_index} is not bonded to the sp3 center with index {self.index}.')
                    
                    self.leaving_group_coords = self.others[neighbors_indexes.index(self.leaving_group_index)]

            self.orb_vecs = np.array([self.coord - self.leaving_group_coords])
            self.orb_vers = norm(self.orb_vecs[0])

        else: # Sigma bond type

            other_reactive_indexes = list(mol.reactive_indexes)
            other_reactive_indexes.remove(i)
            for index in other_reactive_indexes:
                    if index in neighbors_indexes:
                        parnter_index = index
                        break
            # obtain the reference partner index

            pivot = norm(mol.atomcoords[conf][parnter_index] - self.coord)

            other_neighbors = deepcopy(neighbors_indexes)
            other_neighbors.remove(parnter_index)
            orb_vec = norm(mol.atomcoords[conf][other_neighbors[0]] - self.coord)
            orb_vec = orb_vec - orb_vec @ pivot * pivot

            steps = 3 # number of total orbitals
            self.orb_vecs = np.array([rot_mat_from_pointer(pivot, angle+60) @ orb_vec for angle in range(0,360,int(360/steps))])
            # orbitals are staggered in relation to sp3 substituents

            self.orb_vers = norm(self.orb_vecs[0])

        if update:
            if orb_dim is None:
                key = self.symbol + ' ' + str(self)
                orb_dim = orb_dim_dict.get(key)
                
                if orb_dim is None:
                    orb_dim = orb_dim_dict['Fallback']
                    print(f'ATTENTION: COULD NOT SETUP REACTIVE ATOM ORBITAL FROM PARAMETERS. We have no parameters for {key}. Using {orb_dim} A.')

            self.center = np.array([orb_dim * norm(vec) + self.coord for vec in self.orb_vecs])