예제 #1
0
    def parametrise(molecule):
        """Perform initial molecule parametrisation using OpenFF, Antechamber or XML."""

        # First copy the pdb and any other files into the folder
        copy(f'../{molecule.filename}', f'{molecule.filename}')

        # Parametrisation options:
        param_dict = {'antechamber': AnteChamber, 'xml': XML}

        try:
            param_dict['openff'] = OpenFF
        except ImportError:
            pass

        # If we are using xml we have to move it
        if molecule.parameter_engine == 'xml':
            copy(f'../{molecule.name}.xml', f'{molecule.name}.xml')

        # Perform the parametrisation
        param_dict[molecule.parameter_engine](molecule)

        append_to_log(
            f'Parametrised molecule with {molecule.parameter_engine}')

        return molecule
예제 #2
0
    def openmm_system(self):
        """Initialise the OpenMM system we will use to evaluate the energies."""

        # Load the initial coords into the system and initialise
        pdb = app.PDBFile(self.pdb)
        forcefield = app.ForceField(self.xml)
        modeller = app.Modeller(pdb.topology, pdb.positions)  # set the initial positions from the pdb
        self.system = forcefield.createSystem(modeller.topology, nonbondedMethod=app.NoCutoff, constraints=None)

        # Check what combination rule we should be using from the xml
        xmlstr = open(self.xml).read()
        # check if we have opls combination rules if the xml is present
        try:
            self.combination = ET.fromstring(xmlstr).find('NonbondedForce').attrib['combination']
            append_to_log('OPLS combination rules found in xml file', msg_type='minor')
        except AttributeError:
            pass
        except KeyError:
            pass

        if self.combination == 'opls':
            self.opls_lj()

        temperature = 298.15 * unit.kelvin
        integrator = mm.LangevinIntegrator(temperature, 5 / unit.picoseconds, 0.001 * unit.picoseconds)

        self.simulation = app.Simulation(modeller.topology, self.system, integrator)
        self.simulation.context.setPositions(modeller.positions)
예제 #3
0
    def stage_wrapper(self,
                      start_key,
                      begin_log_msg='',
                      fin_log_msg='',
                      torsion_options=None):
        """
        Firstly, check if the stage start_key is in self.order; this tells you if the stage should be called or not.
        If it isn't in self.order:
            - Do nothing
        If it is:
            - Unpickle the ligand object at the start_key stage
            - Write to the log that something's about to be done (if specified)
            - Make (if not restarting) and / or move into the working directory for that stage
            - Do the thing
            - Move back out of the working directory for that stage
            - Write to the log that something's been done (if specified)
            - Pickle the ligand object again with the next_key marker as its stage
        """

        mol = unpickle()[start_key]

        # Set the state for logging any exceptions should they arise
        mol.state = start_key

        # if we have a torsion options dictionary pass it to the molecule
        if torsion_options is not None:
            mol = self.store_torsions(mol, torsion_options)

        skipping = False
        if self.order[start_key] == self.skip:
            printf(f'Skipping stage: {start_key}')
            append_to_log(f'skipping stage: {start_key}')
            skipping = True
        else:
            if begin_log_msg:
                printf(f'{begin_log_msg}...', end=' ')

        home = os.getcwd()

        folder_name = f'{self.immutable_order.index(start_key) + 1}_{start_key}'
        # Make sure you don't get an error if restarting
        try:
            os.mkdir(folder_name)
        except FileExistsError:
            pass
        finally:
            os.chdir(folder_name)

        self.order[start_key](mol)
        self.order.pop(start_key, None)
        os.chdir(home)

        # Begin looping through self.order, but return after the first iteration.
        for key in self.order:
            next_key = key
            if fin_log_msg and not skipping:
                printf(fin_log_msg)

            mol.pickle(state=next_key)
            return next_key
예제 #4
0
    def qm_optimise(self, molecule):
        """Optimise the molecule with or without geometric."""

        # TODO This has gotten kinda gross, can we trim it and maybe move some logic back into engines.py?

        # TODO We initialise the PSI4 engine even if we're using QCEngine?
        qm_engine = self.engine_dict[molecule.bonds_engine](molecule)

        if molecule.geometric and molecule.bonds_engine == 'psi4':

            # Optimise the structure using QCEngine with geometric and psi4
            qceng = QCEngine(molecule)
            result = qceng.call_qcengine('geometric',
                                         'gradient',
                                         input_type='mm')
            # Check if converged and get the geometry
            if result['success']:
                # Load all of the frames into the molecules trajectory holder
                molecule.read_geometric_traj(result['trajectory'])
                # store the last frame as the qm optimised structure
                molecule.molecule['qm'] = molecule.molecule['traj'][-1]
                # Write out the trajectory file
                molecule.write_xyz(input_type='traj',
                                   name=f'{molecule.name}_opt')
                molecule.write_xyz(input_type='qm', name='opt')

            else:
                sys.exit('Molecule not optimised.')

        else:
            converged = qm_engine.generate_input(input_type='mm',
                                                 optimise=True)

            # Check the exit status of the job; if failed restart the job up to 2 times
            restart_count = 1
            while not converged and restart_count < 3:
                append_to_log(
                    f'{molecule.bonds_engine} optimisation failed; restarting',
                    msg_type='minor')
                converged = qm_engine.generate_input(input_type='mm',
                                                     optimise=True,
                                                     restart=True)
                restart_count += 1

            if not converged:
                sys.exit(
                    f'{molecule.bonds_engine} optimisation did not converge after 3 restarts; check log file.'
                )

            molecule.molecule[
                'qm'], molecule.qm_energy = qm_engine.optimised_structure()
            molecule.write_xyz(input_type='qm', name='opt')

        append_to_log(
            f'qm_optimised structure calculated{" with geometric" if molecule.geometric else ""}'
        )

        return molecule
예제 #5
0
    def mod_sem(molecule):
        """Modified Seminario for bonds and angles."""

        mod_sem = ModSeminario(molecule)
        mod_sem.modified_seminario_method()

        append_to_log('Mod_Seminario method complete')

        return molecule
예제 #6
0
    def lennard_jones(molecule):
        """Calculate Lennard-Jones parameters, and extract virtual sites."""

        os.system('cp ../7_charges/DDEC* .')
        lj = LennardJones(molecule)
        molecule.NonbondedForce = lj.calculate_non_bonded_force()

        append_to_log('Lennard-Jones parameters calculated')

        return molecule
예제 #7
0
    def mod_sem(molecule):
        """Modified Seminario for bonds and angles."""

        append_to_log('Starting mod_Seminario method')

        mod_sem = ModSeminario(molecule)
        mod_sem.modified_seminario_method()

        append_to_log('Finishing Mod_Seminario method')

        return molecule
예제 #8
0
    def torsion_optimise(molecule):
        """Perform torsion optimisation."""

        opt = TorsionOptimiser(molecule,
                               refinement=molecule.refinement_method,
                               vn_bounds=molecule.tor_limit)
        opt.run()

        append_to_log('Torsion_optimisations complete')

        return molecule
예제 #9
0
    def lennard_jones(molecule):
        """Calculate Lennard-Jones parameters, and extract virtual sites."""

        append_to_log('Starting Lennard-Jones parameter calculation')

        charges_fld = os.path.join(molecule.home, '7_charges')
        for file in os.listdir(charges_fld):
            if file.startswith('DDEC'):
                copy(os.path.join(charges_fld, file), file)
        lj = LennardJones(molecule)
        molecule.NonbondedForce = lj.calculate_non_bonded_force()

        append_to_log('Finishing Lennard-Jones parameter calculation')

        return molecule
예제 #10
0
    def charges(molecule):
        """Perform DDEC calculation with Chargemol."""

        # TODO add option to use chargemol on onetep cube files.
        # TODO Proper pathing

        copy(f'../6_density/{molecule.name}.wfx', f'{molecule.name}.wfx')
        c_mol = Chargemol(molecule)
        c_mol.generate_input()

        append_to_log(
            f'Charge analysis completed with Chargemol and DDEC{molecule.ddec_version}'
        )

        return molecule
예제 #11
0
    def mm_optimise(self, molecule):
        """
        Use an mm force field to get the initial optimisation of a molecule

        options
        ---------
        RDKit MFF or UFF force fields can have strange effects on the geometry of molecules

        Geometric / OpenMM depends on the force field the molecule was parameterised with gaff/2, OPLS smirnoff.
        """

        append_to_log('Starting mm_optimisation')

        # Check which method we want then do the optimisation
        if self.molecule.mm_opt_method == 'openmm':
            # Make the inputs
            molecule.write_pdb(input_type='input')
            molecule.write_parameters()
            # Run geometric
            # TODO Should this be moved to allow a decorator?
            with open('log.txt', 'w+') as log:
                sp.run(
                    f'geometric-optimize --reset --epsilon 0.0 --maxiter {molecule.iterations}  --pdb '
                    f'{molecule.name}.pdb --openmm {molecule.name}.xml '
                    f'{self.molecule.constraints_file if self.molecule.constraints_file is not None else ""}',
                    shell=True,
                    stdout=log,
                    stderr=log)

            # This will continue even if we don't converge this is fine
            # Read the xyz traj and store the frames
            molecule.read_xyz(f'{molecule.name}_optim.xyz')
            # Store the last from the traj as the mm optimised structure
            molecule.coords['mm'] = molecule.coords['traj'][-1]

        else:
            # TODO change to qcengine as this can already be done
            # Run an rdkit optimisation with the right FF
            rdkit_ff = {'rdkit_mff': 'MFF', 'rdkit_uff': 'UFF'}
            molecule.filename = RDKit().mm_optimise(
                molecule.filename, ff=rdkit_ff[self.molecule.mm_opt_method])

        append_to_log(
            f'Finishing mm_optimisation of the molecule with {self.molecule.mm_opt_method}'
        )

        return molecule
예제 #12
0
    def torsion_scan(molecule):
        """Perform torsion scan."""

        scan = TorsionScan(molecule)

        # Try to find a scan file; if none provided and more than one torsion detected: prompt user
        try:
            copy('../../QUBE_torsions.txt', 'QUBE_torsions.txt')
            scan.find_scan_order(file='QUBE_torsions.txt')
        except FileNotFoundError:
            scan.find_scan_order()

        scan.start_scan()

        append_to_log('Torsion_scans complete')

        return molecule
예제 #13
0
    def generate_input(self, execute=True):
        """Given a DDEC version (from the defaults), this function writes the job file for chargemol and executes it."""

        if (self.molecule.ddec_version != 6) and (self.molecule.ddec_version !=
                                                  3):
            append_to_log(
                message=
                'Invalid or unsupported DDEC version given, running with default version 6.',
                msg_type='warning')
            self.molecule.ddec_version = 6

        # Write the charges job file.
        with open('job_control.txt', 'w+') as charge_file:

            charge_file.write(
                f'<input filename>\n{self.molecule.name}.wfx\n</input filename>'
            )

            charge_file.write('\n\n<net charge>\n0.0\n</net charge>')

            charge_file.write(
                '\n\n<periodicity along A, B and C vectors>\n.false.\n.false.\n.false.'
            )
            charge_file.write('\n</periodicity along A, B and C vectors>')

            charge_file.write(
                f'\n\n<atomic densities directory complete path>\n{self.molecule.chargemol}'
                f'/atomic_densities/')
            charge_file.write('\n</atomic densities directory complete path>')

            charge_file.write(
                f'\n\n<charge type>\nDDEC{self.molecule.ddec_version}\n</charge type>'
            )

            charge_file.write('\n\n<compute BOs>\n.true.\n</compute BOs>')

        if execute:
            with open('log.txt', 'w+') as log:
                # TODO Check if windows
                control_path = 'chargemol_FORTRAN_09_26_2017/compiled_binaries/linux/' \
                               'Chargemol_09_26_2017_linux_serial job_control.txt'
                sp.run(os.path.join(self.molecule.chargemol, control_path),
                       shell=True,
                       stdout=log,
                       stderr=log)
예제 #14
0
    def charges(molecule):
        """Perform DDEC calculation with Chargemol."""

        # TODO add option to use chargemol on onetep cube files.

        append_to_log('Starting charge partitioning')
        copy(
            os.path.join(molecule.home,
                         os.path.join('6_density', f'{molecule.name}.wfx')),
            f'{molecule.name}.wfx')
        c_mol = Chargemol(molecule)
        c_mol.generate_input()

        append_to_log(
            f'Finishing Charge partitioning with Chargemol and DDEC{molecule.ddec_version}'
        )

        return molecule
예제 #15
0
    def torsion_optimise(molecule):
        """Perform torsion optimisation."""

        append_to_log('Starting torsion_optimisations')

        # First we should make sure we have collected the results of the scans
        if not molecule.qm_scans:
            os.chdir(os.path.join(molecule.home, '9_torsion_scan'))
            scan = TorsionScan(molecule)
            scan.find_scan_order()
            scan.collect_scan()
            os.chdir(os.path.join(molecule.home, '10_torsion_optimise'))

        opt = TorsionOptimiser(molecule,
                               refinement=molecule.refinement_method,
                               vn_bounds=molecule.tor_limit)
        opt.run()

        append_to_log('Finishing torsion_optimisations')

        return molecule
예제 #16
0
    def density(self, molecule):
        """Perform density calculation with the qm engine."""

        append_to_log('Starting density calculation')

        if molecule.density_engine == 'onetep':
            molecule.write_xyz(input_type='qm')
            # If using ONETEP, stop after this step
            append_to_log('Density analysis file made for ONETEP')

            # Edit the order to end here
            self.order = OrderedDict([('density', self.density),
                                      ('charges', self.skip),
                                      ('lennard_jones', self.skip),
                                      ('torsion_scan', self.torsion_scan),
                                      ('pause', self.pause)])

        else:
            qm_engine = self.engine_dict[molecule.density_engine](molecule)
            qm_engine.generate_input(input_type='qm',
                                     density=True,
                                     solvent=molecule.solvent)
            append_to_log('Finishing Density calculation')

        return molecule
예제 #17
0
    def torsion_scan(molecule):
        """Perform torsion scan."""
        # TODO find constraints file if present
        append_to_log('Starting torsion_scans')

        molecule.find_rotatable_dihedrals()
        molecule.symmetrise_from_topo()

        scan = TorsionScan(molecule)

        # Try to find a scan file; if none provided and more than one torsion detected: prompt user
        try:
            copy('../../QUBE_torsions.txt', 'QUBE_torsions.txt')
            scan.find_scan_order(file='QUBE_torsions.txt')
        except FileNotFoundError:
            scan.find_scan_order()

        scan.scan()

        append_to_log('Finishing torsion_scans')

        return molecule
예제 #18
0
    def hessian(self, molecule):
        """Using the assigned bonds engine, calculate and extract the Hessian matrix."""

        # TODO Because of QCEngine, nothing is being put into the hessian folder anymore
        #   Need a way of writing QCEngine output to log file; still waiting on documentation ...

        molecule.get_bond_lengths(input_type='qm')

        # Check what engine we want to use
        if molecule.bonds_engine == 'g09':
            qm_engine = self.engine_dict[molecule.bonds_engine](molecule)
            qm_engine.generate_input(input_type='qm', hessian=True)
            molecule.hessian = qm_engine.hessian()

        else:
            qceng = QCEngine(molecule)
            molecule.hessian = qceng.call_qcengine('psi4',
                                                   'hessian',
                                                   input_type='qm')

        append_to_log(f'Hessian calculated using {molecule.bonds_engine}')

        return molecule
예제 #19
0
    def density(self, molecule):
        """Perform density calculation with the qm engine."""

        if molecule.density_engine == 'onetep':
            molecule.write_xyz(input_type='qm')
            # If we use ONETEP we have to stop after this step
            append_to_log('Density analysis file made for ONETEP')

            # Now we have to edit the order to end here.
            self.order = OrderedDict([('density', self.density),
                                      ('charges', self.skip),
                                      ('lennard_jones', self.skip),
                                      ('torsion_scan', self.torsion_scan),
                                      ('pause', self.pause)])

        else:
            # Do normal density calculation
            qm_engine = self.engine_dict[molecule.density_engine](molecule)
            qm_engine.generate_input(input_type='qm',
                                     density=True,
                                     solvent=molecule.solvent)
            append_to_log('Density analysis complete')

        return molecule
예제 #20
0
    def parametrise(molecule):
        """Perform initial molecule parametrisation using OpenFF, Antechamber or XML."""

        append_to_log('Starting parametrisation')

        # Write the PDB file this covers us if we have a mol2 or xyz input file
        molecule.write_pdb()

        # Parametrisation options:
        param_dict = {'antechamber': AnteChamber, 'xml': XML, 'openff': OpenFF}

        # If we are using xml we have to move it
        if molecule.parameter_engine == 'xml':
            copy(os.path.join(molecule.home, f'{molecule.name}.xml'),
                 f'{molecule.name}.xml')

        # Perform the parametrisation
        param_dict[molecule.parameter_engine](molecule)

        append_to_log(
            f'Finishing parametrisation of molecule with {molecule.parameter_engine}'
        )

        return molecule
예제 #21
0
    def hessian(self, molecule):
        """Using the assigned bonds engine, calculate and extract the Hessian matrix."""

        # TODO Because of QCEngine, nothing is being put into the hessian folder anymore
        #   Need a way of writing QCEngine output to log file; still waiting on documentation ...

        append_to_log('Starting hessian calculation')
        molecule.get_bond_lengths(input_type='qm')

        # Check what engine to use
        if molecule.bonds_engine == 'g09':
            qm_engine = self.engine_dict[molecule.bonds_engine](molecule)

            # Use the checkpoint file as this has higher xyz precision
            try:
                copy(
                    os.path.join(molecule.home,
                                 os.path.join('3_qm_optimise', 'lig.chk')),
                    'lig.chk')
                result = qm_engine.generate_input(input_type='qm',
                                                  hessian=True,
                                                  restart=True)
            except FileNotFoundError:
                append_to_log(
                    'qm_optimise checkpoint not found, optimising first to refine atomic coordinates'
                )
                result = qm_engine.generate_input(input_type='qm',
                                                  optimise=True,
                                                  hessian=True)
            if result['success']:
                molecule.hessian = qm_engine.hessian()
            else:
                raise Exception(
                    'The hessian was not calculated check the log file.')

        else:
            qceng = QCEngine(molecule)
            molecule.hessian = qceng.call_qcengine('psi4',
                                                   'hessian',
                                                   input_type='qm')

        append_to_log(
            f'Finishing Hessian calculation using {molecule.bonds_engine}')

        return molecule
예제 #22
0
    def qm_optimise(self, molecule):
        """Optimise the molecule with or without geometric."""

        # TODO this method's not always printing completion to log file.

        append_to_log('Starting qm_optimisation')
        qm_engine = self.engine_dict[molecule.bonds_engine](molecule)

        if molecule.geometric and molecule.bonds_engine == 'psi4':

            qceng = QCEngine(molecule)
            # See if the structure is there if not we did not optimise
            if molecule.coords['mm'].any():
                result = qceng.call_qcengine('geometric',
                                             'gradient',
                                             input_type='mm')
            else:
                result = qceng.call_qcengine('geometric',
                                             'gradient',
                                             input_type='input')
            # Check if converged and get the geometry
            if result['success']:

                # Load all of the frames into the molecule's trajectory holder
                molecule.read_geometric_traj(result['trajectory'])

                # store the last frame as the qm optimised structure
                molecule.coords['qm'] = molecule.coords['traj'][-1]

                # Write out the trajectory file
                molecule.write_xyz(input_type='traj',
                                   name=f'{molecule.name}_opt')
                molecule.write_xyz(input_type='qm', name='opt')

                append_to_log(
                    f'Finishing qm_optimisation of molecule{" using geometric" if molecule.geometric else ""}'
                )

                return molecule

            else:
                # TODO catch the qcengine error here
                print(result)  # catch the steps done so far
                raise OptimisationFailed("The optimisation did not converge")

        elif molecule.coords['mm'].any():
            result = qm_engine.generate_input(input_type='mm', optimise=True)

        else:
            result = qm_engine.generate_input(input_type='input',
                                              optimise=True)

        # Check the exit status of the job; if failed restart the job up to 2 times
        restart_count = 1
        while not result['success'] and restart_count < 3:
            append_to_log(
                f'{molecule.bonds_engine} optimisation failed with error {result["error"]}; restarting',
                msg_type='minor')
            # Now we should handle the errors that we have in the results
            # 1) If we have a file read error just start again
            if result['error'] == 'FileIO':
                result = qm_engine.generate_input(input_type='mm',
                                                  optimise=True,
                                                  restart=True)
            # 2) If we have a distance matrix error we should start from a different structure try the input
            elif result['error'] == 'Distance matrix' and restart_count == 1:
                result = qm_engine.generate_input(input_type='input',
                                                  optimise=True)
            # 3) If we have already tried the starting structure generate a conformer and try again
            elif result['error'] == 'Distance matrix':
                molecule.write_pdb()
                rdkit = RDKit()
                molecule.coords['temp'] = rdkit.generate_conformers(
                    f'{molecule.name}.pdb')[0]
                result = qm_engine.generate_input(input_type='temp',
                                                  optimise=True)

            restart_count += 1

        if not result['success']:
            raise OptimisationFailed(
                f"{molecule.bonds_engine} "
                f"optimisation did not converge after 3 restarts; last error {result['error']}"
            )

        molecule.coords[
            'qm'], molecule.qm_energy = qm_engine.optimised_structure()
        molecule.write_xyz(input_type='qm', name='opt')

        append_to_log(
            f'Finishing qm_optimisation of molecule{" using geometric" if molecule.geometric else ""}'
        )

        return molecule
예제 #23
0
    def generate_input(self,
                       input_type='input',
                       optimise=False,
                       hessian=False,
                       density=False,
                       energy=False,
                       fchk=False,
                       restart=False,
                       execute=True):
        """
        Converts to psi4 input format to be run in psi4 without using geometric.
        :param input_type: The coordinate set of the molecule to be used
        :param optimise: Optimise the molecule to the desired convergence critera with in the iteration limit
        :param hessian: Calculate the hessian matrix
        :param density: Calculate the electron density
        :param energy: Calculate the single point energy of the molecule
        :param fchk: Write out a gaussian style Fchk file
        :param restart: Restart the calculation from a log point
        :param execute: Run the desired Psi4 job
        :return: The completion status of the job True if successful False if not run or failed
        """

        molecule = self.molecule.molecule[input_type]

        setters = ''
        tasks = ''

        # input.dat is the PSI4 input file.
        with open('input.dat', 'w+') as input_file:
            # opening tag is always writen
            input_file.write(
                f"memory {self.molecule.memory} GB\n\nmolecule {self.molecule.name} {{\n{self.molecule.charge} {self.molecule.multiplicity} \n"
            )
            # molecule is always printed
            for atom in molecule:
                input_file.write(
                    f' {atom[0]}    {float(atom[1]): .10f}  {float(atom[2]): .10f}  {float(atom[3]): .10f} \n'
                )
            input_file.write(
                f" units angstrom\n no_reorient\n}}\n\nset {{\n basis {self.molecule.basis}\n"
            )

            if energy:
                append_to_log('Writing psi4 energy calculation input')
                tasks += f"\nenergy  = energy('{self.molecule.theory}')"

            if optimise:
                append_to_log('Writing PSI4 optimisation input', 'minor')
                setters += f" g_convergence {self.molecule.convergence}\n GEOM_MAXITER {self.molecule.iterations}\n"
                tasks += f"\noptimize('{self.molecule.theory.lower()}')"

            if hessian:
                append_to_log('Writing PSI4 Hessian matrix calculation input',
                              'minor')
                setters += ' hessian_write on\n'

                tasks += f"\nenergy, wfn = frequency('{self.molecule.theory.lower()}', return_wfn=True)"

                tasks += '\nwfn.hessian().print_out()\n\n'

            if density:
                append_to_log('Writing PSI4 density calculation input',
                              'minor')
                setters += " cubeprop_tasks ['density']\n"

                overage = get_overage(self.molecule.name)
                setters += " CUBIC_GRID_OVERAGE [{0}, {0}, {0}]\n".format(
                    overage)
                setters += " CUBIC_GRID_SPACING [0.13, 0.13, 0.13]\n"
                tasks += f"grad, wfn = gradient('{self.molecule.theory.lower()}', return_wfn=True)\ncubeprop(wfn)"

            if fchk:
                append_to_log('Writing PSI4 input file to generate fchk file')
                tasks += f"\ngrad, wfn = gradient('{self.molecule.theory.lower()}', return_wfn=True)"
                tasks += '\nfchk_writer = psi4.core.FCHKWriter(wfn)'
                tasks += f'\nfchk_writer.write("{self.molecule.name}_psi4.fchk")\n'

            # TODO If overage cannot be made to work, delete and just use Gaussian.
            # if self.molecule.solvent:
            #     setters += ' pcm true\n pcm_scf_type total\n'
            #     tasks += '\n\npcm = {'
            #     tasks += '\n units = Angstrom\n Medium {\n  SolverType = IEFPCM\n  Solvent = Chloroform\n }'
            #     tasks += '\n Cavity {\n  RadiiSet = UFF\n  Type = GePol\n  Scaling = False\n  Area = 0.3\n  Mode = Implicit'
            #     tasks += '\n }\n}'

            setters += '}\n'

            if not execute:
                setters += f'set_num_threads({self.molecule.threads})\n'

            input_file.write(setters)
            input_file.write(tasks)

        if execute:
            with open('log.txt', 'w+') as log:
                sp.run(f'psi4 input.dat -n {self.molecule.threads}',
                       shell=True,
                       stdout=log,
                       stderr=log)

            # After running, check for normal termination
            return True if '*** Psi4 exiting successfully.' in open(
                'output.dat', 'r').read() else False
        else:
            return False