Example #1
0
    def _load_xyzb(self, file):
        '''
		Method used to load xyz files

		file - string representing the path to the xyz file

		returns nothing, modifies self
		'''
        self.name = file.split('/')[-1].strip('.xyzb').capitalize()
        self.name = self.name.split('\\')[-1].strip('.xyzb').capitalize()

        coords = list(
            np.loadtxt(file, skiprows=2, usecols=(1, 2, 3), dtype=float))
        elements = list(np.loadtxt(file, skiprows=2, usecols=0, dtype=str))

        if 'VEC1' in elements:
            i = elements.index('VEC1')
            self.repeat_vector = coords[i]
            del (elements[i])
            del (coords[i])

        self.atoms = []
        for r in range(self.repeat):
            for e, c in zip(elements, coords):
                if r == 0:
                    self.atoms.append(Atom(e, c))
                else:
                    if not type(self.repeat_vector) is np.ndarray:
                        utils.message('Error: Please supply repeat vector.',
                                      colour='red')
                    else:
                        self.atoms.append(Atom(e,
                                               c + (r) * self.repeat_vector))

        self._mol_load_finish()
Example #2
0
	def evaluate(self, p, silent=False):
		if not silent: utils.message(f'Evaluating molecular orbital of {self.molecule.name} with energy {self.energy} eV')
		dens = np.zeros(p.size//3)
		for ao, w in zip(self.aos, self.weights):
			dens += w * ao.evaluate(p)

		return dens
Example #3
0
    def draw_electrostatic_potential(self,
                                     molecule,
                                     points=50000,
                                     colour_map=cmap.ElectroStat()):
        if not hasattr(molecule, '_elec_stat_pos'):
            utils.message(
                f'Calculating electrostatic potential of {molecule.name} ...')
            samples = 50 * points
            rang = np.amax([np.abs(atom.coords)
                            for atom in molecule.atoms]) + 4

            x, y, z = (
                (np.random.randint(-rang * 10000, rang * 10000, size=samples) /
                 10000),
                (np.random.randint(-rang * 10000, rang * 10000, size=samples) /
                 10000),
                (np.random.randint(-rang * 10000, rang * 10000, size=samples) /
                 10000))
            d = molecule.electrostatic_potential(np.asarray(
                (x, y, z)).T).flatten()
            index = abs(d).argsort()[::-1]
            d = np.maximum(d, np.mean(d) * 4)
            colours = colour_map[d].T

            x, y, z, colours = x[index][0:points], y[index][0:points], z[
                index][0:points], colours[index][0:points]
            molecule._elec_stat_pos, molecule._elec_stat_colours = np.asarray(
                (x, y, z)).T, colours

        self.draw_pixels(molecule._elec_stat_pos,
                         colour_array=molecule._elec_stat_colours)
Example #4
0
    def _mol_load_finish(self):
        '''
		Method that is called by both xyz and pubchem loading of molecules
		'''

        utils.message(f'Succesfully loaded {self.name}.', 'green')

        self.center()

        self.set_bonds()

        self._load_basis_set()
Example #5
0
	def load_basis(self):
		'''
		Method that loads the basis set for the atoms in the molecule. If the basis set 
		does not exist in the database, we will download the basis set from https://www.basissetexchange.org/
		using their API
		'''

		basis_type = self.basis_type.replace('*', '@')

		bsf_path = os.getcwd()+rf'\Basis_Sets\{basis_type}.bsf'
		if not os.path.exists(bsf_path):
			utils.message(f'Error: Basis set {self.basis_type} not found, downloading ...', colour='red')

			import requests

			response = requests.get("http://basissetexchange.org" + f'/api/basis/{self.basis_type}/format/json')
			if response:

				utils.message(f'Succesfully obtained basis set file', colour='green')

				with open(bsf_path, 'w+') as f:
					f.write(response.text)
				self.load_basis()
			else:
				utils.message(f'Error: Failed to obtain basis set file', colour='red')
		else:
			utils.message(f'Succesfully loaded {self.basis_type}', colour='green')
			with open(bsf_path, 'r') as f:
				# self.params = json.load(f)['elements'][str(self.atom.atomic_number)]['electron_shells']
				self.params = json.load(f)['elements']
Example #6
0
    def _load_from_pubchem(self, name):
        '''
		Method used to load data from pubchem

		name - name of compound to search

		returns nothing
		'''

        import pubchempy as pcp

        try:
            name = int(name)
        except:
            pass

        record_type = '3d'
        mol = pcp.get_compounds(name, ('name', 'cid')[type(name) is int],
                                record_type=record_type)

        if len(mol) == 0:
            utils.message(
                'Error: Could not find 3d structure of {name}... Attempting to find 2d structure...',
                'red')
            record_type = '2d'
            mol = pcp.get_compounds(name, ('name', 'cid')[type(name) is int],
                                    record_type=record_type)

        if len(mol) == 0:
            utils.message('Error: No structural data found for {name}.', 'red')

        else:
            mol = mol[0]

            coords = np.asarray([[a.x, a.y, a.z] for a in mol.atoms])
            coords = np.where(coords == None, 0, coords).astype(float)
            elements = np.asarray([a.element for a in mol.atoms])
            self.name = name.capitalize()

            self.atoms = []
            [
                self.atoms.append(Atom(elements[i], coords[i]))
                for i in range(len(coords))
            ]
            if record_type == '3d':
                self.save_to_xyz(os.getcwd() +
                                 rf'\Molecules\{name.lower()}.xyz')

            self._mol_load_finish()
Example #7
0
    def save(self,
             file=None,
             comment='generated via python\n',
             filetype='xyz'):
        '''
		Method that saves the molecule to a file

		file - string path to new file
		'''

        if file == None:
            file = os.getcwd() + rf'\Molecules\{self.name.lower()}.{filetype}'
        else:
            filetype = file.split('.')[-1]

        if not file.endswith('.' + filetype):
            file += '.' + filetype

        if not comment.endswith('\n'):
            comment += '\n'

        if filetype == 'xyz':  #standard xyz format
            with open(file, 'w+') as f:
                f.write(f'{self.natoms}\n')
                f.write(comment)
                for a in self.atoms:
                    f.write(
                        f'{a.symbol: <2} \t {a.coords[0]: >8.5f} \t {a.coords[1]: >8.5f} \t {a.coords[2]: >8.5f}\n'
                    )

        elif filetype == 'xyzb':  #xyz format plus bond information
            with open(file, 'w+') as f:
                f.write(f'{self.natoms}\n')
                f.write(comment)
                for a in self.atoms:
                    f.write(
                        f'{a.symbol: <2} \t {a.coords[0]: >8.5f} \t {a.coords[1]: >8.5f} \t {a.coords[2]: >8.5f}\n'
                    )

                f.write('\n')
                for a1, a2 in self.get_unique_bonds():
                    f.write(
                        f'{self.atoms.index(a1)} \t {self.atoms.index(a2)} \t {a1.bond_orders[a2]}\n'
                    )

        utils.message(f'Saved molecule to {file}')

        return file
Example #8
0
    def save_to_xyz(self, file='', comment='generated via python\n'):
        '''
		Method that saves the molecule to a file

		file - string path to new file
		'''
        if file == '':
            file = os.getcwd() + rf'\Molecules\{self.name.lower()}.xyz'

        if not comment.endswith('\n'):
            comment += '\n'

        with open(file, 'w+') as f:
            f.write(f'{self.natoms}\n')
            f.write(comment)
            for a in self.atoms:
                f.write(
                    f'{a.symbol} {a.coords[0]:.5f} {a.coords[1]:.5f} {a.coords[2]:.5f}\n'
                )

        utils.message(f'Saved molecule to {file}')
Example #9
0
    def _load_mol(self, molecule_file):
        #get the file extension:
        if '.' in molecule_file:
            name, filetype = molecule_file.split('.')
        else:
            utils.message(f'Searching pubchem for {molecule_file}...')
            self._load_from_pubchem(molecule_file)
            return

        if filetype == 'pcp':
            utils.message(f'Searching pubchem for {name}...')
            self._load_from_pubchem(name)
            return

        else:
            #check if file exists:
            if not os.path.exists(molecule_file):
                utils.message(
                    f'Error: File {molecule_file} does not exist. Searching pubchem ...',
                    colour='red')
                self._load_from_pubchem(molecule_file)
                return
            else:
                if filetype == 'xyz':
                    self._load_xyz(molecule_file)
                    return
                if filetype == 'xyzb':
                    self._load_xyzb(molecule_file)
                    return
Example #10
0
def extended_huckel(molecule, K=1.75):
	'''
	Function that performs the extended huckel method

	Returns energies and mo's
	'''

	utils.message(f'Performing Extended Huckel-Method for {molecule.name}.')

	aos = molecule.basis.atomic_orbitals

	dim = len(aos)
	H = np.zeros((dim, dim))

	for i in range(dim):
		H[i,i] = -aos[i].atom.ionisation_energy[sum(aos[i].cardinality)]

	for i in range(dim):
		for j in range(i+1, dim):
			H[i,j] = H[j,i] = K * overlap_integral(aos[i], aos[j]) * (H[i,i] + H[j,j])/2


	energies, weights = np.linalg.eigh(H)

	molecule.molecular_orbitals = []
	molecule.molecular_orbitals = sorted(molecule.molecular_orbitals, key=lambda x: x.energy)

	for energy, weight in zip(energies, weights.T):
		molecule.molecular_orbitals.append(MolecularOrbital(molecule, aos, weight, energy))

	utils.message(f'{len(energies)} molecular orbitals found.')
	utils.message(f'Highest and lowest energies: {max(energies)}, {min(energies)} eV')
Example #11
0
    def pre_render_densities(self,
                             orbitals,
                             points=50000,
                             colour_map=cmap.BlueRed(posneg_mode=True)):
        utils.message(
            f'Pre-rendering {len(orbitals)} orbitals ({points} points):')
        for i, orbital in enumerate(orbitals):

            utils.message(
                f'	Progress: {i+1}/{len(orbitals)} = {round((i+1)/len(orbitals)*100,2)}%'
            )
            samples = 10 * points
            ranges = orbital.ranges

            x, y, z = ((np.random.randint(
                ranges[0] * 10000, ranges[1] * 10000, size=samples) / 10000), (
                    np.random.randint(
                        ranges[2] * 10000, ranges[3] * 10000, size=samples) /
                    10000), (np.random.randint(
                        ranges[4] * 10000, ranges[5] * 10000, size=samples) /
                             10000))
            d = orbital.evaluate(np.asarray((x, y, z)), True).flatten()

            index = abs(d**2).argsort()[::-1]
            colours = colour_map[d].T

            x, y, z, colours = x[index][0:points], y[index][0:points], z[
                index][0:points], colours[index][0:points]
            dens_pos = self.rotate(
                np.asarray((x, y, z)).T, orbital.molecule.rotation)

            self._dens_pos[orbital], self._dens_colours[
                orbital] = dens_pos, colours
            orbital.molecule._dens_pos[
                orbital], orbital.molecule._dens_colours[
                    orbital] = dens_pos, colours

        utils.message(
            f'Orbitals prepared. Please use Screen3D.draw_density() to display the orbitals.'
        )
Example #12
0
def MOTD():
    utils.ff_print_source(False)
    utils.ff_use_colours(fancy_format_colours)
    utils.ff_print_time(False)
    os.system('color 07')
    features = [
        'Loading molecules from xyz files or alternatively download from pubchem.',
        'Visualising molecules as a ball-and-stick model or as a wireframe model.',
        'Algorithms for guessing atom types, bonds, and bond orders.',
        'Support for STO-nG basis set for atomic/molecular orbital visualtions and calculations.',
        'Crude extended-Hückel method for calculation of molecular orbitals.'
    ]
    planned_features = [
        'Improvements to extended-Hückel and molecular integrals.',
        'Marching cubes algorithm to visualise orbitals as iso-volumes instead of current random-dot style.'
    ]

    utils.message(f'Welcome, this project so far supports the following:',
                  colour='blue')
    [utils.message(f'-- {f}', colour='blue') for f in features]
    utils.message(f'Planned features:', colour='blue')
    [utils.message(f'-- {f}', colour='blue') for f in planned_features]
    print()
Example #13
0
updt = 0
time = 0
rot = np.array([0., 0.])
zoom = 0
pg.key.set_repeat()
run = True
mo_numb = 0
draw_dens = False
camera_range = max(max([a.coords[0] for a in mol.atoms]),
                   max([a.coords[1] for a in mol.atoms]),
                   max([a.coords[2] for a in mol.atoms])) + 10

screen.camera_position = np.asarray((0, 0, camera_range))

utils.message(
    'Please press ENTER to toggle orbital display. Use arrow-keys to switch between orbitals.'
)
utils.message('Hold CTRL and use mouse to rotate and move molecule.')

#####################
mols = [mol]

if randomize_structure:
    for a1, a2 in mol.get_rotatable_bonds():
        mol.rotate_bond(a1, a2, np.random.random() * 2 * 1 * 3.14 - 1 * 3.14)
    mol.shake(0.5)
mol.center()

if minimize_structure:
    mols, energies = minimizer.minimize(mol,
                                        'uff',
Example #14
0
import modules.utils as utils

utils.ff_verbosity(0)

utils.message(('a', 'b', 'c'), (1, 6, 9))
Example #15
0
    def guess_bond_orders(self):
        '''
		Method that guesses the bond orders of the molecule.
		
		Current strategy:
		- Sort elements from low valence to high valence (H < O < N < C, etc..) 
		  and loops over the elements.
			- Collect every atom of the element and checks its bond saturation.
			- If the atom is not saturated, loop over the atoms it is bonded to.
				- Check the saturation of the bonded atom. If the bonded atom 
				  is also not saturated, increase the bond order to that bond.
				- Terminate the loop if the current atom is saturated.
		'''

        self.reset_bond_orders()

        #sort the elements by valence
        valences = list(self.max_valence.items())
        valences = sorted(valences, key=lambda x: x[1])

        for el, _ in valences:
            #get all atoms of element el
            atoms = self.get_by_element(el).copy()
            if self.guess_bond_order_iters > 0:
                np.random.shuffle(atoms)
            else:
                atoms = sorted(atoms, key=lambda x: x.hybridisation)
            #loop over the atoms
            for a in atoms:
                #check if a is saturated
                if a.is_unsaturated():
                    #if not, get its neighbours
                    neighbours = np.copy(self.get_bonded_atoms(a))
                    # np.random.shuffle(neighbours)
                    neighbours = sorted(neighbours,
                                        key=lambda x: a.distance_to(x))
                    neighbour_hybrids = [x.hybridisation for x in neighbours]
                    #loop over the neighbours

                    if a.hybridisation == 'sp' and 'sp' in neighbour_hybrids:
                        x = neighbour_hybrids.index('sp')
                        a.bond_orders[neighbours[x]] = 3
                        neighbour.bond_orders[a] = 3

                    if a.is_unsaturated():
                        for neighbour in neighbours:
                            #check if the neighbour is also unsaturated and whether a has
                            #become saturated  in the meantime
                            if neighbour.is_unsaturated() and a.is_unsaturated(
                            ):
                                a.bond_orders[neighbour] += 1
                                neighbour.bond_orders[a] += 1

        #give warnings if necessary
        mbo = sum([a.is_saturated() for a in self.get_by_element('C')]) - len(
            self.get_by_element('C'))
        if self._warning_level == 2:
            if mbo < 0:
                utils.message(
                    f'Error: Bond order guessing was not succesful. Unsaturated atoms: {abs(mbo)} (iteration {self.guess_bond_order_iters}).',
                    'red')
            else:
                utils.message(
                    f'Bond order guessing succesful after {self.guess_bond_order_iters+1} iterations',
                    'green')

        elif self._warning_level == 1:
            if mbo == 0:
                utils.message(
                    f'Bond order guessing succesful after {self.guess_bond_order_iters+1} iteration{"s"*(self.guess_bond_order_iters>0)}.',
                    'green')
            elif self.guess_bond_order_iters == self.natoms:
                utils.message(
                    f'Bond order guessing was not succesful. Unsaturated atoms: {abs(mbo)}.',
                    'red')

        if mbo < 0 and self.guess_bond_order_iters < 5 * self.natoms:
            self.guess_bond_order_iters += 1
            self.guess_bond_orders()
Example #16
0
    def get_energy_from_coords(self,
                               coords,
                               morse_potential=True,
                               use_torsions=True):
        molecule = self.molecule
        atoms = molecule.atoms
        n = len(atoms)

        for c, a in zip(coords[:n * 3].reshape((n, 3)), atoms):
            a.coords = c

        if use_torsions:
            for t, a in zip(coords[n * 3:], molecule.get_rotatable_bonds()):
                self.mol.rotate_bond(*a, t)

        utils.message('ATOM TYPES', 1)
        utils.message(f'IDX | TYPE  | RING', 1)
        for i, a in enumerate(atoms):
            utils.message(f'{i: <3} | {self.get_atom_type(a): <5} | {a.ring}',
                          1)

        #### E = E_r + E_theta + E_phi + E_ohm + E_vdm + E_el
        ## E_r:
        utils.message('BOND STRETCH ENERGY', 2)
        utils.message('ATOM 1 | ATOM 2 | BO  | BOND LEN | IDEAL LEN | ENERGY',
                      2)

        E_r = 0
        for a1, a2 in molecule.get_unique_bonds():
            #some parameters for a1
            e1 = self.get_atom_type(a1)
            r1 = self.valence_bond[e1]
            chi1 = a1.electro_negativity
            Z1 = self.effective_charge[e1]

            #some parameters for a2
            e2 = self.get_atom_type(a2)
            r2 = self.valence_bond[e2]
            chi2 = a2.electro_negativity
            Z2 = self.effective_charge[e2]

            #dist between a1 and a2
            r = a1.distance_to(a2)
            #bo between a1 and a2
            n = a1.bond_orders[a2]
            if a1.ring == a2.ring == 'AR':
                n = 1.5

            if morse_potential:
                # r12 = r1 + r2 - (r1*r2*(sqrt(chi1) - sqrt(chi2))**2)/(chi1*r1 + chi2*r2)
                r12 = r1 + r2 - 0.1332 * (r1 + r2) * log(n) + r1 * r2 * (
                    sqrt(chi1) - sqrt(chi2))**2 / (chi1 * r1 + chi2 * r2)
                k12 = 664.12 * Z1 * Z2 / r12**3
                D12 = 70 * n
                alpha = sqrt(k12 / (2 * D12))
                E_ri = D12 * (exp(-alpha * (r - r12)) - 1)**2
            else:
                rBO = -0.1332 * (r1 + r2) * log(n)
                rEN = r1 * r2 * (sqrt(chi1) - sqrt(chi2))**2 / (chi1 * r1 +
                                                                chi2 * r2)
                r12 = r1 + r2 + rBO + rEN
                k12 = 664.12 * Z1 * Z2 / r12**3
                E_ri = .5 * k12 * (r - r12)**2

            E_r += E_ri

            utils.message(
                f'{e1: <6} | {e2: <6} | {n: <3.1f} | {r: <8.3f} | {r12: <8.3f} | {E_ri: <.3f}',
                2)

        ## E_theta:

        E_theta = 0
        for a1, a2, a3, theta in molecule.get_unique_bond_angles():
            e1 = self.get_atom_type(a1)
            e2 = self.get_atom_type(a2)
            e3 = self.get_atom_type(a3)
            Z1 = self.effective_charge[e1]
            Z2 = self.effective_charge[e2]
            Z3 = self.effective_charge[e3]

            r12, r23, r13 = a1.distance_to(a2), a2.distance_to(
                a3), a1.distance_to(a3)

            beta = 664.12 / (r12 * r23)
            t0 = self.valence_angle[e2] * pi / 180
            K123 = beta * Z1 * Z3 / r13**5 * r12 * r23 * (3 * r12 * r23 *
                                                          (1 - cos(t0)**2) -
                                                          r13**2 * cos(t0))
            C2 = 1 / (4 * sin(t0)**2)
            C1 = -4 * C2 * cos(t0)
            C0 = C2 * (2 * cos(t0)**2 + 1)
            E_theta_i = K123 * (C0 + C1 * cos(theta) + C2 * cos(2 * theta))
            E_theta += E_theta_i

        #E_phi:
        utils.message('BOND STRETCH ENERGY', 2)
        utils.message(
            'ATOM 1 | ATOM 2 | ATOM 3 | ATOM 4 | TORSION | IDEAL TORS | ENERGY',
            2)

        E_phi = 0
        for a1, a2, a3, a4, phi in molecule.get_unique_torsion_angles():
            e1 = self.get_atom_type(a1)
            e2 = self.get_atom_type(a2)
            e3 = self.get_atom_type(a3)
            e4 = self.get_atom_type(a4)

            Vbarr = 1
            if a2.hybridisation == a3.hybridisation == 3:
                V2, V3 = self.sp3_torsional_barrier_params[
                    e2], self.sp3_torsional_barrier_params[e3]
                Vbarr = sqrt(V2 * V3)
                n = 3
                phi0 = pi, pi / 3  #two different phi0 possible

            if (a2.hybridisation == 3
                    and a3.hybridisation == 2) or (a2.hybridisation == 2
                                                   and a3.hybridisation == 3):
                Vbarr = 1
                n = 6
                phi0 = 0, 0

            if a2.hybridisation == a3.hybridisation == 2:
                U2 = self.sp2_torsional_barrier_params[
                    e2]  # period starts at 1
                U3 = self.sp2_torsional_barrier_params[e3]
                Vbarr = 5 * sqrt(
                    U2 * U3) * (1 + 4.18 * log(a2.bond_orders[a3]))
                n = 2
                phi0 = pi, 1.047198

            #since two differen phi0 are possible, calculate both and return highest energy
            E_phi1 = 0.5 * Vbarr * (1 - cos(n * phi0[0]) * cos(n * phi))
            E_phi2 = 0.5 * Vbarr * (1 - cos(n * phi0[1]) * cos(n * phi))
            E_phi_i = min(E_phi1, E_phi2)
            E_phi += E_phi_i

            torsion = divmod(phi, phi0[0])[1] if phi0[0] is not 0 else 0
            utils.message(
                f'{e1: <6} | {e2: <6} | {e3: <6} | {e4: <6} | {torsion: <7.3f} | {phi0[0]: <10.3f} | {E_phi_i: <.3f}',
                2)

        #E_vdw:
        utils.message('VAN DER WAALS ENERGY', 2)
        utils.message('ATOM 1 | ATOM 2 | BOND LEN | FORCE CONST | ENERGY', 2)

        E_vdw = 0
        for a1, a2 in molecule.get_unique_atom_pairs(
        ):  #cutoff is at 2 angstrom
            if a1.bond_dist_to(a2) > 2:
                e1 = self.get_atom_type(a1)
                e2 = self.get_atom_type(a2)

                D1 = self.nonbond_energy[e1]
                D2 = self.nonbond_energy[e2]
                D12 = sqrt(D1 * D2)

                x = a1.distance_to(a2)
                x1 = self.nonbond_distance[e1]
                x2 = self.nonbond_distance[e2]
                x12 = .5 * (x1 + x2)

                E_vdw_i = D12 * (-2 * (x12 / x)**6 + (x12 / x)**12)
                E_vdw += E_vdw_i

                utils.message(
                    f'{e1: <6} | {e2: <6} | {x: <8.3f} | {D12: <11.3f} | {E_vdw_i: <.3f}',
                    2)

        utils.message(
            f'TOTAL BOND STRETCHING ENERGY = {round(E_r*4.2,3)} kJ/mol', 1)
        utils.message(
            f'TOTAL ANGLE BENDING ENERGY = {round(E_theta*4.2,3)} kJ/mol', 1)
        utils.message(f'TOTAL TORSIONAL ENERGY = {round(E_phi*4.2,3)} kJ/mol',
                      1)
        utils.message(
            f'TOTAL VAN DER WAAL\'S ENERGY = {round(E_vdw*4.2,3)} kJ/mol', 1)
        utils.message(
            f'TOTAL ENERGY = {round((E_r + E_theta + E_phi + E_vdw)*4.2,3)} kJ/mol',
            1)

        return E_r + E_theta + E_phi + E_vdw
Example #17
0
def perform_md(mol,
               ff='uff',
               run_time=.5e-12,
               time_step=.5e-15,
               bath_temp=273,
               sample_freq=5,
               temp_coupling_strength=0):
    '''
	Function to perform molecular dynamics using the leap frog algorithm
	'''

    utils.ff_block_source('uff.get_energy')

    if ff == 'uff':
        ff = uff.ForceField()

    atoms = mol.atoms
    l = len(atoms)

    masses = np.expand_dims(np.asarray([a.mass for a in atoms]), 1)

    #initialize velocities
    velocities = np.random.normal(loc=0.5, scale=0.5, size=(l, 3, 12))
    velocities = np.sum(velocities, axis=2) - 6
    velocities *= np.sqrt(k * bath_temp / masses)
    velocities *= sqrt(bath_temp / calculate_temp(velocities, masses))
    utils.message(
        f'Starting molecular dynamics simulation for molecule {mol.name} with the {ff.name} forcefield.'
    )
    utils.message(
        f'Run Time: {run_time:.2e} s; Time Step: {time_step:.2e} s; Temperature: {bath_temp} K',
        1)

    mol = copy.deepcopy(mol)
    mols = [mol]
    energies = [ff.get_energy(mol)]

    time = 0
    i = 0
    while time < run_time:
        forces = get_forces(mol, 1e-6, ff).reshape((l, 3))
        velocities += forces / masses * time_step
        temp = calculate_temp(velocities, masses)
        temp_couple_scaling = sqrt(1 + temp_coupling_strength *
                                   (bath_temp / temp - 1))
        velocities *= temp_couple_scaling

        if i % sample_freq == 0:
            mol.center()
            mols.append(copy.deepcopy(mol))
            energies.append(ff.get_energy(mol))

            utils.message((
                f'Current Time: {time:.2e} s with ENERGY = {energies[-1]:.2f} kcal/mol and TEMPERATURE = {temp:.2f} K',
                f'Current Time: {time:.2e} s with ENERGY = {energies[-1]:.2f} kcal/mol and TEMPERATURE = {temp:.2f} K\nCOORDINATES (angstrom):\n{mol}\n\nVELOCITIES (angstrom/s):\n{velocities}\n'
            ), (1, 2))

        for j, a in enumerate(mol.atoms):
            a.coords += velocities[j] * time_step

        time += time_step
        i += 1

    utils.ff_unblock_source('uff.get_energy')
    return mols, energies
Example #18
0
    def __init__(self, element, coords):
        self.coords = coords
        self.bonds = []
        self.bond_orders = {}
        self.selected = False

        try:
            el = pt.elements[element]
        except:
            try:
                el = pt.elements.symbol(element)
            except:
                try:
                    el = pt.elements.name(element)
                except:
                    utils.message('Error: Could not parse element {element}.',
                                  'red')

        self.ionisation_energy = np.genfromtxt('modules\\ionisation_energies',
                                               usecols=(1, 2),
                                               missing_values='',
                                               delimiter=';')[el.number]

        self.symbol = el.symbol
        self.name = el.name
        self.atomic_number = el.number
        self.mass = el.mass
        self.radius = el.covalent_radius
        self.electro_negativity = {
            1: 2.2,
            2: 0,
            3: 0.98,
            4: 1.57,
            5: 2.04,
            6: 2.55,
            7: 3.04,
            8: 3.44,
            9: 3.98,
            10: 0,
            11: 0.93,
            12: 1.31,
            13: 1.61,
            14: 1.9,
            15: 2.19,
            16: 2.58,
            17: 3.16,
            18: 0,
            19: 0.82,
            20: 1,
            21: 1.36,
            22: 1.54,
            23: 1.63,
            24: 1.66,
            25: 1.55,
            26: 1.83,
            27: 1.88,
            28: 1.91,
            29: 1.9,
            30: 1.65,
            31: 1.81,
            32: 2.01,
            33: 2.18,
            34: 2.55,
            35: 2.96,
            36: 3,
            37: 0.82,
            38: 0.95,
            39: 1.22,
            40: 1.33,
            41: 1.6,
            42: 2.16,
            43: 1.9,
            44: 2.2,
            45: 2.28,
            46: 2.2,
            47: 1.93,
            48: 1.69,
            49: 1.78,
            50: 1.96,
            51: 2.05,
            52: 2.1,
            53: 2.66,
            54: 2.6,
            55: 0.79,
            56: 0.89,
            57: 1.1,
            58: 1.12,
            59: 1.13,
            60: 1.14,
            61: 1.13,
            62: 1.17,
            63: 1.2,
            64: 1.2,
            65: 1.22,
            66: 1.23,
            67: 1.24,
            68: 1.24,
            69: 1.25,
            70: 1.1,
            71: 1.27,
            72: 1.3,
            73: 1.5,
            74: 2.36,
            75: 1.9,
            76: 2.2,
            77: 2.2,
            78: 2.28,
            79: 2.54,
            80: 2,
            81: 1.62,
            82: 2.33,
            83: 2.02,
            84: 2,
            85: 2.2,
            86: 0,
            87: 0.7,
            88: 0.89,
            89: 1.1,
            90: 1.3,
            91: 1.5,
            92: 1.38,
            93: 1.36,
            94: 1.28,
            95: 1.3,
            96: 1.3,
            97: 1.3,
            98: 1.3,
            99: 1.3,
            100: 1.3,
            101: 1.3,
            102: 1.3,
            103: 00,
            104: 0,
            105: 0,
            106: 0,
            107: 0,
            108: 0,
            109: 0,
            110: 0,
            111: 0,
            112: 0,
            113: 0,
            114: 0,
            115: 0,
            116: 0,
            117: 0,
            118: 0,
        }[self.atomic_number]

        try:
            self.GMP_electro_negativity = {
                1: 0.89,
                3: 0.97,
                4: 1.47,
                5: 1.6,
                6: 2,
                7: 2.61,
                8: 3.15,
                9: 3.98,
                11: 1.01,
                12: 1.23,
                13: 1.47,
                14: 1.58,
                15: 1.96,
                16: 2.35,
                17: 2.74,
                19: 0.91,
                20: 1.04,
                21: 1.2,
                22: 1.32,
                23: 1.45,
                24: 1.56,
                25: 1.6,
                26: 1.64,
                27: 1.7,
                28: 1.75,
                29: 1.75,
                30: 1.66,
                31: 1.82,
                32: 1.51,
                33: 2.23,
                34: 2.51,
                35: 2.58,
                37: 0.89,
                38: 0.99,
                39: 1.11,
                40: 1.22,
                41: 1.23,
                42: 1.3,
                44: 1.42,
                45: 1.54,
                46: 1.35,
                47: 1.42,
                48: 1.46,
                49: 1.49,
                50: 1.72,
                51: 1.72,
                52: 2.72,
                53: 2.38,
                55: 0.86,
                56: 0.97,
                57: 1.08,
                58: 1.08,
                59: 1.07,
                60: 1.07,
                62: 1.07,
                63: 1.01,
                64: 1.11,
                65: 1.1,
                66: 1.1,
                67: 1.1,
                68: 1.11,
                69: 1.11,
                70: 1.06,
                71: 1.14,
                72: 1.23,
                73: 1.33,
                74: 1.4,
                75: 1.46,
                77: 1.55,
                80: 1.44,
                81: 1.44,
                82: 1.55,
                83: 1.67,
                90: 1.11,
                92: 1.22,
            }[self.atomic_number]
        except:
            self.GMP_electro_negativity = 0

        #get period of atom
        if self.atomic_number in (1, 2):
            self.period = 1
        if self.atomic_number in range(3, 11):
            self.period = 2
        if self.atomic_number in range(11, 19):
            self.period = 3
        if self.atomic_number in range(19, 37):
            self.period = 4
        if self.atomic_number in range(37, 55):
            self.period = 5
        if self.atomic_number in range(55, 87):
            self.period = 6

        try:
            self.colour = self.draw_colour = {
                'C': (34, 34, 34),
                'H': (255, 255, 255),
                'O': (255, 22, 0),
                'N': (22, 33, 255),
                'S': (225, 225, 48),
                'Ca': (61, 255, 0),
                'Fe': (221, 119, 0),
                'Mg': (0, 119, 0),
                'P': (255, 153, 0),
                'Cl': (31, 240, 31),
                'Na': (119, 0, 255),
            }[self.symbol]
        except:
            utils.message(
                'Error: No default colour found for {self.symbol}. Defaulting to (0,0,0).',
                'red')
            self.colour = (0, 0, 0)

        try:
            self.max_valence = {
                'C': 4,
                'H': 1,
                'O': 2,
                'N': 3,
                'Mg': 2,
                'P': 6,
                'Cl': 1,
                'S': 6,
                'Na': 1,
                'Fe': 2,
            }[self.symbol]
        except:
            utils.message(
                'Error: No max_valence found for {self.symbol}. Defaulting to 1.',
                'red')
            self.max_valence = 1
Example #19
0
    def minimize(self,
                 method='sd',
                 max_steps=1500,
                 step_factor=4e-4,
                 sample_freq=10,
                 use_torsions=True,
                 converge_thresh=8e-2):
        '''
		Method that performs the energy minimization of self.mol

		method: str - specify method (steepest descent 'sd', conjugate gradient 'cg')
		max_steps: int - specify the maximum number of iterations allowed for minimization
		sample_freq: int - specify the frequency with which a snapshot is taken (in iterations)
		use_torsions: bool - specify whether to use rotation around bonds as coordinates in minimization

		returns tuple of lists containing molecule objects and energies
		'''

        assert (max_steps > 0)
        assert (step_factor > 0)
        assert (sample_freq > 0)

        mol = self._mol

        utils.message((
            f'Starting geometry optimisation for molecule {mol.name} with {self._ff.name} using steepest descent.',
            f'Starting geometry optimisation for molecule {mol.name} with {self._ff.name} using steepest descent.\nINITIAL COORDINATES (angstrom):\n{mol}'
        ), (0, 1))
        utils.message(
            f'Max. Steps: {max_steps}; Step-Factor: {step_factor:.2e}; Convergence Thresh.: {converge_thresh:.2e}; Apply to Bond Rotation: {use_torsions}',
            1)

        self._ff.molecule = mol
        energy = self._ff.get_energy
        mols = [copy.deepcopy(mol)]
        energies = [energy(mol)]

        if method == 'sd':
            gradient = nd.Gradient(self._ff.get_energy_from_coords)

            #get the coordinates:
            coords = [a.coords
                      for a in mol.atoms]  #cartesian coordinates of the atoms
            if use_torsions:
                coords += [0 for _ in mol.get_rotatable_bonds()]  #torsions
            coords = np.asarray(coords).flatten().astype(
                float)  #flatten vector

            #start minimization
            for i in range(max_steps):
                #calculate gradients and change coords
                forces = -gradient(coords) * step_factor
                coords += forces

                converged = np.all(np.absolute(forces) < converge_thresh)

                #sample mol
                if (i + 1) % sample_freq == 0 or converged:

                    mols.append(copy.deepcopy(mol))
                    energies.append(energy(mol))

                    if converged: break

                    utils.message((
                        f'Current Step: {i+1} with ENERGY = {energies[-1]:.6f} kcal/mol',
                        f'Current Step: {i+1} with ENERGY = {energies[-1]:.6f} kcal/mol\nCOORDINATES (angstrom):\n{mol}\n\nFORCES (kcal/mol/angstrom):\n{forces}\n'
                    ), (1, 2))

            #done
            if i < max_steps - 1:
                utils.message((
                    f'Molecule optimization succesful after {i+1} steps with ENERGY = {energy(mol):.6f} kcal/mol',
                    f'Molecule optimization succesful after {i+1} steps with ENERGY = {energy(mol):.6f} kcal/mol\nCOORDINATES (angstrom):\n\n{mol}\n\nFORCES (kcal/mol/angstrom):\n\n{forces}\n'
                ), (0, 2),
                              colour='green')
            else:
                utils.message((
                    f'Molecule optimization failed after {i+1} steps with ENERGY = {energy(mol):.6f} kcal/mol',
                    f'Molecule optimization failed after {i+1} steps with ENERGY = {energy(mol):.6f} kcal/mol\nCOORDINATES (angstrom):\n\n{mol}\n\nFORCES (kcal/mol/angstrom):\n\n{forces}\n'
                ), (0, 2),
                              colour='red')

            return mols, energies
Example #20
0
def minimize(mol,
             ff='uff',
             max_steps=1500,
             converge_thresh=8e-2,
             step_factor=4e-4,
             sample_freq=10,
             use_torsions=True,
             max_step_size=0.3,
             method='sd',
             fix_torsion=False):
    '''
	Energy minimization method that attempts to optimize the structureof a molecule using a given force field
	(must have a get_energy() method). The method used is the steepest descent method, which follows the 
	negative energy gradient with respect to the coordinates of the molecule multiplied by step_factor.
	Every sample_freq iterations a snapshot is taken.
	'''

    assert (max_steps > 0)
    assert (converge_thresh > 0)
    assert (step_factor > 0)
    assert (sample_freq > 0)

    if ff == 'uff':
        ff = uff.ForceField()

    utils.message(
        f'Starting geometry optimisation for molecule {mol.name} with {ff.name} using steepest descent.'
    )
    utils.message(
        f'Max. Steps: {max_steps}; Step-Factor: {step_factor:.2e}; Max. Step Size: {max_step_size}; Converge Thresh.: {converge_thresh:.2e}; Apply to Torsion: {use_torsions}',
        1)

    mol = copy.deepcopy(mol)
    mols = [mol]
    energies = [ff.get_energy(mol)]
    energy = 0

    if method == 'sd':
        for i in range(max_steps):
            forces = get_forces(mol, 1e-6, ff, use_torsions=use_torsions)
            prev_energy = energy
            energy = ff.get_energy(mol)

            converged = np.all(np.absolute(forces) < converge_thresh
                               ) or abs(energy - prev_energy) < converge_thresh

            if (i + 1) % sample_freq == 0 or converged:
                mol.center()
                mols.append(copy.deepcopy(mol))
                energies.append(ff.get_energy(mol))
                if converged:
                    break

                utils.message((
                    f'Current Step: {i+1} with ENERGY = {energies[-1]:.6f} kcal/mol',
                    f'Current Step: {i+1} with ENERGY = {energies[-1]:.6f} kcal/mol\nCOORDINATES (angstrom):\n\n{mol}\n\nFORCES (kcal/mol/angstrom):\n\n{forces}\n'
                ), (0, 2))

            if use_torsions:
                for j, a in enumerate(mol.atoms):
                    a.coords += (forces[3 * j:3 * j + 3] * step_factor)
                for k, t in enumerate(list(mol.get_rotatable_bonds())):
                    mol.rotate_bond(
                        t[0], t[1],
                        step_factor * forces[3 * len(mol.atoms) + k])
            else:
                for j, a in enumerate(mol.atoms):
                    a.coords += forces[3 * j:3 * j + 3] * step_factor

            if type(fix_torsion) is float:
                mol.set_torsion_angle(mol.atoms[3], mol.atoms[1], mol.atoms[0],
                                      mol.atoms[2], fix_torsion)

        if i < max_steps - 1:
            utils.message((
                f'Molecule optimization succesful after {i+1} steps with ENERGY = {ff.get_energy(mol):.6f} kcal/mol',
                f'Molecule optimization succesful after {i+1} steps with ENERGY = {ff.get_energy(mol):.6f} kcal/mol\nCOORDINATES (angstrom):\n\n{mol}\n\nFORCES (kcal/mol/angstrom):\n\n{forces}'
            ), (0, 2),
                          colour='green')
        else:
            utils.message((
                f'Molecule optimization failed after {i+1} steps with ENERGY = {ff.get_energy(mol):.6f} kcal/mol',
                f'Molecule optimization failed after {i+1} steps with ENERGY = {ff.get_energy(mol):.6f} kcal/mol\nCOORDINATES (angstrom):\n\n{mol}\n\nFORCES (kcal/mol/angstrom):\n\n{forces}'
            ), (0, 2),
                          colour='red')

        return mols, energies

    if method == 'cg':
        #first step is the same as steepest descent:
        forces = get_forces(mol, 1e-6, ff, use_torsions=use_torsions)
        step = forces * step_factor
        prev_step = step

        if use_torsions:
            for j, a in enumerate(mol.atoms):
                a.coords += (step[3 * j:3 * j + 3])
            for k, t in enumerate(list(mol.get_rotatable_bonds())):
                mol.rotate_bond(t[0], t[1], step[3 * len(mol.atoms) + k])
        else:
            for j, a in enumerate(mol.atoms):
                a.coords += step[3 * j:3 * j + 3]

        #next steps:
        for i in range(max_steps):
            forces = get_forces(mol, 1e-6, ff, use_torsions=use_torsions)
            gamma = np.dot(-forces, -forces) / np.dot(prev_step, prev_step)
            step = forces * gamma * prev_step
            prev_step = step

            if (i + 1) % sample_freq == 0 or np.all(
                    np.absolute(forces) < converge_thresh):
                mol.center()
                mols.append(copy.deepcopy(mol))
                energies.append(ff.get_energy(mol))
                if np.all(np.absolute(forces) < converge_thresh):
                    break

                utils.message((
                    f'Current Step: {i+1} with ENERGY = {energies[-1]:.6f} kcal/mol',
                    f'Current Step: {i+1} with ENERGY = {energies[-1]:.6f} kcal/mol\nCOORDINATES (angstrom):\n\n{mol}\n\nFORCES (kcal/mol/angstrom):\n\n{forces}'
                ), (1, 2))

            if use_torsions:
                for j, a in enumerate(mol.atoms):
                    a.coords += (step[3 * j:3 * j + 3])
                for k, t in enumerate(list(mol.get_rotatable_bonds())):
                    mol.rotate_bond(t[0], t[1], step[3 * len(mol.atoms) + k])
            else:
                for j, a in enumerate(mol.atoms):
                    a.coords += step[3 * j:3 * j + 3]

            return mols, energies