Exemple #1
0
 def test_NBO5_vs_NBO7_hybridization_character(self):
     data5 = QCOutput(os.path.join(PymatgenTest.TEST_FILES_DIR, "molecules", "new_qchem_files", "nbo5_1.qout")).data
     data7 = QCOutput(os.path.join(PymatgenTest.TEST_FILES_DIR, "molecules", "new_qchem_files", "nbo7_1.qout")).data
     self.assertEqual(
         len(data5["nbo_data"]["hybridization_character"]), len(data7["nbo_data"]["hybridization_character"])
     )
     self.assertEqual(
         data5["nbo_data"]["hybridization_character"][3]["atom 2 pol coeff"][9],
         data7["nbo_data"]["hybridization_character"][3]["atom 2 pol coeff"][9],
     )
     self.assertEqual(
         data5["nbo_data"]["hybridization_character"][0]["s"][0],
         data7["nbo_data"]["hybridization_character"][0]["s"][0],
     )
     self.assertEqual(data5["nbo_data"]["hybridization_character"][1]["bond index"][7], "149")
     self.assertEqual(data7["nbo_data"]["hybridization_character"][1]["bond index"][7], "21")
    def setUp(self):
        self.opt_freq_params = {
            "rem": {
                "job_type": "opt",
                "method": "wb97x-d",
                "basis": "6-31g*",
                "max_scf_cycles": 200,
                "gen_scfman": True,
                "scf_algorithm": "diis",
                "geom_opt_max_cycles": 200
            }
        }

        self.sp_params = {
            "rem": {
                "job_type": "sp",
                "method": "wb97x-d",
                "basis": "6-311++g(d,p)",
                "max_scf_cycles": 200,
                "gen_scfman": True,
                "scf_algorithm": "diis",
                "solvent_method": "pcm"
            },
            "pcm": {
                "theory": "iefpcm"
            },
            "solvent": {
                "dielectric": 80.4
            }
        }

        out_file = os.path.join(files_dir, "qchem", "test.qout.opt_0")
        qc_out = QCOutput(filename=out_file)
        self.act_mol = qc_out.data["initial_molecule"]
        super(TestOptFreqSPFW, self).setUp(lpad=False)
Exemple #3
0
 def test_in_database_with_actual_database(self):
     db_file = os.path.join(db_dir, "db.json")
     dir2620 = os.path.join(module_dir, "..", "..", "test_files",
                            "2620_complete")
     mol2620 = QCOutput(os.path.join(
         dir2620, "mol.qout.opt_0")).data["initial_molecule"]
     parse_firetask = QChemToDb(calc_dir=dir2620, db_file=db_file)
     parse_firetask.run_task({})
     with patch("atomate.qchem.firetasks.fragmenter.FWAction"
                ) as FWAction_patch:
         ft = FragmentMolecule(
             molecule=self.neg_tfsi,
             db_file=db_file,
             depth=0,
             additional_charges=[-2, 1],
             do_triplets=False,
         )
         ft.run_task({})
         self.assertEqual(ft.check_db, True)
         self.assertEqual(ft.charges, [-1, 0, -2, 1])
         self.assertEqual(len(FWAction_patch.call_args[1]["additions"]),
                          623)
         self.assertEqual(ft._in_database(mol2620), True)
         mol2620.set_charge_and_spin(charge=0)
         self.assertEqual(ft._in_database(mol2620), False)
Exemple #4
0
 def check(self):
     """
     Checks output file for errors
     """
     self.outdata = QCOutput(self.output_file).data
     self.errors = self.outdata.get("errors")
     return len(self.errors) > 0
Exemple #5
0
 def generate_single_job_dict():
     """
     Used to generate test dictionary for single jobs.
     """
     single_job_dict = {}
     for file in single_job_out_names:
         single_job_dict[file] = QCOutput(os.path.join(PymatgenTest.TEST_FILES_DIR, "molecules", file)).data
     dumpfn(single_job_dict, "single_job.json")
Exemple #6
0
 def setUp(self, lpad=False):
     os.chdir(os.path.join(module_dir, "..", "..", "test_files",
                             "critic_test_files", "small_critic_example"))
     out_file = "mol.qout"
     qc_out = QCOutput(filename=out_file)
     self.mol = qc_out.data["initial_molecule"]
     self.cube_file = "dens.0.cube"
     super(TestRunCritic2, self).setUp(lpad=False)
Exemple #7
0
 def generate_single_job_dict():
     """
     Used to generate test dictionary for single jobs.
     """
     single_job_dict = {}
     for file in single_job_out_names:
         single_job_dict[file] = QCOutput(os.path.join(test_dir, file)).data
     dumpfn(single_job_dict, "single_job.json")
Exemple #8
0
    def test_NBO7_parsing(self):
        data = QCOutput(os.path.join(PymatgenTest.TEST_FILES_DIR, "molecules", "new_qchem_files", "nbo7_1.qout")).data
        self.assertEqual(data["nbo_data"]["perturbation_energy"][0]["perturbation energy"][9], 15.73)
        self.assertEqual(len(data["nbo_data"]["perturbation_energy"][0]["donor bond index"].keys()), 84)
        self.assertEqual(len(data["nbo_data"]["perturbation_energy"][1]["donor bond index"].keys()), 29)

        data = QCOutput(os.path.join(PymatgenTest.TEST_FILES_DIR, "molecules", "new_qchem_files", "nbo7_2.qout")).data
        self.assertEqual(data["nbo_data"]["perturbation_energy"][0]["perturbation energy"][13], 32.93)
        self.assertEqual(data["nbo_data"]["perturbation_energy"][0]["acceptor type"][13], "LV")
        self.assertEqual(data["nbo_data"]["perturbation_energy"][0]["acceptor type"][12], "RY")
        self.assertEqual(data["nbo_data"]["perturbation_energy"][0]["acceptor atom 1 symbol"][12], "Mg")

        data = QCOutput(os.path.join(PymatgenTest.TEST_FILES_DIR, "molecules", "new_qchem_files", "nbo7_3.qout")).data
        self.assertEqual(data["nbo_data"]["perturbation_energy"][0]["perturbation energy"][13], 34.54)
        self.assertEqual(data["nbo_data"]["perturbation_energy"][0]["acceptor type"][13], "BD*")
        self.assertEqual(data["nbo_data"]["perturbation_energy"][0]["acceptor atom 1 symbol"][13], "B")
        self.assertEqual(data["nbo_data"]["perturbation_energy"][0]["acceptor atom 2 symbol"][13], "Mg")
        self.assertEqual(data["nbo_data"]["perturbation_energy"][0]["acceptor atom 2 number"][13], 3)
Exemple #9
0
 def test_RunQChemFake(self):
     os.chdir(os.path.join(module_dir, "..", "fake_run"))
     firetask = RunQChemFake(ref_dir=os.path.join(module_dir, "..", "..",
                                                  "test_files", "real_run"))
     firetask.run_task(fw_spec={})
     ref_out = QCOutput(
         os.path.join(module_dir, "..", "..", "test_files", "real_run",
                      "mol.qout")).data
     this_out = QCOutput("mol.qout").data
     for key in ref_out:
         try:
             self.assertEqual(ref_out[key], this_out[key])
         except ValueError:
             np.testing.assert_array_equal(ref_out[key], this_out[key])
     for filename in os.listdir(
             os.path.join(module_dir, "..", "..", "test_files",
                          "real_run")):
         self.assertEqual(os.path.isfile(filename), True)
     os.chdir(module_dir)
Exemple #10
0
 def test_NBO_parsing(self):
     data = QCOutput(os.path.join(PymatgenTest.TEST_FILES_DIR, "molecules", "new_qchem_files", "nbo.qout")).data
     self.assertEqual(len(data["nbo_data"]["natural_populations"]), 3)
     self.assertEqual(len(data["nbo_data"]["hybridization_character"]), 4)
     self.assertEqual(len(data["nbo_data"]["perturbation_energy"]), 2)
     self.assertEqual(data["nbo_data"]["natural_populations"][0]["Density"][5], -0.08624)
     self.assertEqual(data["nbo_data"]["hybridization_character"][-1]["atom 2 pol coeff"][35], "-0.7059")
     next_to_last = list(data["nbo_data"]["perturbation_energy"][-1]["fock matrix element"].keys())[-2]
     self.assertEqual(data["nbo_data"]["perturbation_energy"][-1]["fock matrix element"][next_to_last], 0.071)
     self.assertEqual(data["nbo_data"]["perturbation_energy"][0]["acceptor type"][0], "RY*")
Exemple #11
0
 def setUp(self, lpad=False):
     os.chdir(os.path.join(module_dir, "..", "..", "test_files",
                             "critic_test_files", "critic_example"))
     out_file = "mol.qout"
     qc_out = QCOutput(filename=out_file)
     self.mol = qc_out.data["initial_molecule"]
     self.cube_file = "dens.0.cube.gz"
     shutil.move("bonding.json","bonding_correct.json")
     shutil.move("processed_critic2.json","processed_correct.json")
     super(TestProcessCritic2, self).setUp(lpad=False)
Exemple #12
0
 def check(self):
     # Checks output file for errors.
     self.outdata = QCOutput(self.output_file).data
     self.errors = self.outdata.get("errors")
     if "out_of_opt_cycles" not in self.errors and len(
             self.opt_error_history) > 0:
         self.opt_error_history = []
     if "out_of_opt_cycles" in self.errors:
         if self.outdata["structure_change"] == "unconnected_fragments":
             return False
     return len(self.errors) > 0
Exemple #13
0
def run_QChem(label,
              encode=None,
              rem=None,
              pcm=None,
              solvent=None,
              more_info=None,
              self_correct=True):
    inname = label + '.inp'
    outname = label + '.out'
    logname = label + '.log'
    command = 'qchem'
    handlers = [QChemErrorHandler(input_file=inname, output_file=outname)]
    """If no encoding provided, assume this is the first Firework in workflow and that input file is already written.
    'label' is the name of the file without the extension (e.g. .inp, .out). otherwise, take encoding, 
    form new QCInput and write input file, then run.
    """
    if encode != None:
        qcin = encode_to_QCInput(encode=encode,
                                 rem=rem,
                                 pcm=pcm,
                                 solvent=solvent)
        qcin.write_file(inname)

    if self_correct:
        jobs = [
            QCJob(input_file=inname,
                  output_file=outname,
                  qchem_command=command,
                  max_cores=multiprocessing.cpu_count(),
                  qclog_file=logname)
        ]
        c = Custodian(handlers, jobs, max_errors=10)
        c.run()
    else:
        job = QCJob(input_file=inname,
                    output_file=outname,
                    qchem_command=command,
                    max_cores=multiprocessing.cpu_count(),
                    qclog_file=logname)
        job.setup()
        p = job.run()
        p.wait()
        """
        qclog = open(logname, "w")
        current_command = ['qchem', '-nt', '20',inname]
        print(current_command)
        subprocess.run(current_command, stdout=qclog, shell=True)
        """

    try:
        output = [QCOutput(filename=outname)]
    except:
        output = QCOutput.multiple_outputs_from_file(QCOutput, filename)
    return QCOutput_to_encode(output, more_info=more_info)
Exemple #14
0
 def check(self):
     # Checks output file for errors.
     self.outdata = QCOutput(self.output_file).data
     self.errors = self.outdata.get("errors")
     self.warnings = self.outdata.get("warnings")
     # If we aren't out of optimization cycles, but we were in the past, reset the history
     if "out_of_opt_cycles" not in self.errors and len(self.opt_error_history) > 0:
         self.opt_error_history = []
     # If we're out of optimization cycles and we have unconnected fragments, no need to handle any errors
     if "out_of_opt_cycles" in self.errors and self.outdata["structure_change"] == "unconnected_fragments":
         return False
     return len(self.errors) > 0
Exemple #15
0
    def test_all(self):
        single_outs = dict()
        for file in single_job_out_names:
            single_outs[file] = QCOutput(os.path.join(test_dir, file)).data

        multi_outs = dict()
        for file in multi_job_out_names:
            multi_outs[file] = QCOutput.multiple_outputs_from_file(
                QCOutput, os.path.join(test_dir, file), keep_sub_files=False)

        for key in property_list:
            print('Testing ', key)
            self._test_property(key, single_outs, multi_outs)
Exemple #16
0
 def _test_property(self, key):
     for file in single_job_out_names:
         try:
             self.assertEqual(
                 QCOutput(os.path.join(test_dir, file)).data.get(key),
                 single_job_dict[file].get(key))
         except ValueError:
             self.assertArrayEqual(
                 QCOutput(os.path.join(test_dir, file)).data.get(key),
                 single_job_dict[file].get(key))
     for file in multi_job_out_names:
         outputs = QCOutput.multiple_outputs_from_file(QCOutput,
                                                       os.path.join(
                                                           test_dir, file),
                                                       keep_sub_files=False)
         for ii, sub_output in enumerate(outputs):
             try:
                 self.assertEqual(sub_output.data.get(key),
                                  multi_job_dict[file][ii].get(key))
             except ValueError:
                 self.assertArrayEqual(sub_output.data.get(key),
                                       multi_job_dict[file][ii].get(key))
Exemple #17
0
    def test_all(self):
        self.maxDiff = None
        single_outs = {}
        for file in single_job_out_names:
            single_outs[file] = QCOutput(os.path.join(PymatgenTest.TEST_FILES_DIR, "molecules", file)).data

        multi_outs = {}
        for file in multi_job_out_names:
            multi_outs[file] = QCOutput.multiple_outputs_from_file(
                QCOutput, os.path.join(PymatgenTest.TEST_FILES_DIR, "molecules", file), keep_sub_files=False
            )

        for key in property_list:
            print("Testing ", key)
            self._test_property(key, single_outs, multi_outs)
Exemple #18
0
 def process_qchemrun(dir_name, taskname, input_file, output_file):
     """
     Process a QChem calculation, aka an input/output pair.
     """
     qchem_input_file = os.path.join(dir_name, input_file)
     qchem_output_file = os.path.join(dir_name, output_file)
     d = QCOutput(qchem_output_file).data
     temp_input = QCInput.from_file(qchem_input_file)
     d["input"] = {}
     d["input"]["molecule"] = temp_input.molecule
     d["input"]["rem"] = temp_input.rem
     d["input"]["opt"] = temp_input.opt
     d["input"]["pcm"] = temp_input.pcm
     d["input"]["solvent"] = temp_input.solvent
     d["task"] = {"type": taskname, "name": taskname}
     return d
Exemple #19
0
 def setUp(self):
     out_file = os.path.join(files_dir, "qchem", "test.qout.opt_0")
     qc_out = QCOutput(filename=out_file)
     self.act_mol = qc_out.data["initial_molecule"]
     super(TestFrequencyFlatteningOptimizeFW, self).setUp(lpad=False)
    def opt_with_frequency_flattener(
        cls,
        qchem_command,
        multimode="openmp",
        input_file="mol.qin",
        output_file="mol.qout",
        qclog_file="mol.qclog",
        max_iterations=10,
        max_molecule_perturb_scale=0.3,
        check_connectivity=True,
        linked=True,
        transition_state=False,
        freq_before_opt=False,
        save_final_scratch=False,
        **QCJob_kwargs,
    ):
        """
        Optimize a structure and calculate vibrational frequencies to check if the
        structure is in a true minima.

        If there are an inappropriate number of imaginary frequencies (>0 for a
         minimum-energy structure, >1 for a transition-state), attempt to re-calculate
         using one of two methods:
            - Perturb the geometry based on the imaginary frequencies and re-optimize
            - Use the exact Hessian to inform a subsequent optimization
         After each geometry optimization, the frequencies are re-calculated to
         determine if a true minimum (or transition-state) has been found.

        Note: Very small imaginary frequencies (-15cm^-1 < nu < 0) are allowed
        if there is only one more than there should be. In other words, if there
        is one very small imaginary frequency, it is still treated as a minimum,
        and if there is one significant imaginary frequency and one very small
        imaginary frequency, it is still treated as a transition-state.

        Args:
            qchem_command (str): Command to run QChem.
            multimode (str): Parallelization scheme, either openmp or mpi.
            input_file (str): Name of the QChem input file.
            output_file (str): Name of the QChem output file.
            max_iterations (int): Number of perturbation -> optimization -> frequency
                iterations to perform. Defaults to 10.
            max_molecule_perturb_scale (float): The maximum scaled perturbation that
                can be applied to the molecule. Defaults to 0.3.
            check_connectivity (bool): Whether to check differences in connectivity
                introduced by structural perturbation. Defaults to True.
            linked (bool): Whether or not to use the linked flattener. If set to True (default),
                then the explicit Hessians from a vibrational frequency analysis will be used
                as the initial Hessian of subsequent optimizations. In many cases, this can
                significantly improve optimization efficiency.
            transition_state (bool): If True (default False), use a ts
                optimization (search for a saddle point instead of a minimum)
            freq_before_opt (bool): If True (default False), run a frequency
                calculation before any opt/ts searches to improve understanding
                of the local potential energy surface.
            save_final_scratch (bool): Whether to save full scratch directory contents
                at the end of the flattening. Defaults to False.
            **QCJob_kwargs: Passthrough kwargs to QCJob. See
                :class:`custodian.qchem.jobs.QCJob`.
        """
        if not os.path.exists(input_file):
            raise AssertionError("Input file must be present!")

        if transition_state:
            opt_method = "ts"
            perturb_index = 1
        else:
            opt_method = "opt"
            perturb_index = 0

        energy_diff_cutoff = 0.0000001

        orig_input = QCInput.from_file(input_file)
        freq_rem = copy.deepcopy(orig_input.rem)
        freq_rem["job_type"] = "freq"
        opt_rem = copy.deepcopy(orig_input.rem)
        opt_rem["job_type"] = opt_method
        first = True
        energy_history = list()

        if freq_before_opt:
            if not linked:
                warnings.warn(
                    "WARNING: This first frequency calculation will not inform subsequent optimization!"
                )
            yield (QCJob(
                qchem_command=qchem_command,
                multimode=multimode,
                input_file=input_file,
                output_file=output_file,
                qclog_file=qclog_file,
                suffix=".freq_pre",
                save_scratch=True,
                backup=first,
                **QCJob_kwargs,
            ))

            if linked:
                opt_rem["geom_opt_hessian"] = "read"
                opt_rem["scf_guess_always"] = True

            opt_QCInput = QCInput(
                molecule=orig_input.molecule,
                rem=opt_rem,
                opt=orig_input.opt,
                pcm=orig_input.pcm,
                solvent=orig_input.solvent,
                smx=orig_input.smx,
            )
            opt_QCInput.write_file(input_file)
            first = False

        if linked:
            opt_rem["geom_opt_hessian"] = "read"
            opt_rem["scf_guess_always"] = True

            for ii in range(max_iterations):
                yield (QCJob(
                    qchem_command=qchem_command,
                    multimode=multimode,
                    input_file=input_file,
                    output_file=output_file,
                    qclog_file=qclog_file,
                    suffix=".{}_".format(opt_method) + str(ii),
                    save_scratch=True,
                    backup=first,
                    **QCJob_kwargs,
                ))
                opt_outdata = QCOutput(output_file +
                                       ".{}_".format(opt_method) +
                                       str(ii)).data
                opt_indata = QCInput.from_file(input_file +
                                               ".{}_".format(opt_method) +
                                               str(ii))
                if opt_indata.rem["scf_algorithm"] != freq_rem["scf_algorithm"]:
                    freq_rem["scf_algorithm"] = opt_indata.rem["scf_algorithm"]
                    opt_rem["scf_algorithm"] = opt_indata.rem["scf_algorithm"]
                first = False
                if opt_outdata[
                        "structure_change"] == "unconnected_fragments" and not opt_outdata[
                            "completion"]:
                    if not transition_state:
                        warnings.warn(
                            "Unstable molecule broke into unconnected fragments which failed to optimize! Exiting..."
                        )
                        break
                energy_history.append(opt_outdata.get("final_energy"))
                freq_QCInput = QCInput(
                    molecule=opt_outdata.get(
                        "molecule_from_optimized_geometry"),
                    rem=freq_rem,
                    opt=orig_input.opt,
                    pcm=orig_input.pcm,
                    solvent=orig_input.solvent,
                    smx=orig_input.smx,
                )
                freq_QCInput.write_file(input_file)
                yield (QCJob(
                    qchem_command=qchem_command,
                    multimode=multimode,
                    input_file=input_file,
                    output_file=output_file,
                    qclog_file=qclog_file,
                    suffix=".freq_" + str(ii),
                    save_scratch=True,
                    backup=first,
                    **QCJob_kwargs,
                ))
                outdata = QCOutput(output_file + ".freq_" + str(ii)).data
                indata = QCInput.from_file(input_file + ".freq_" + str(ii))
                if indata.rem["scf_algorithm"] != freq_rem["scf_algorithm"]:
                    freq_rem["scf_algorithm"] = indata.rem["scf_algorithm"]
                    opt_rem["scf_algorithm"] = indata.rem["scf_algorithm"]
                errors = outdata.get("errors")
                if len(errors) != 0:
                    raise AssertionError(
                        "No errors should be encountered while flattening frequencies!"
                    )
                if not transition_state:
                    freq_0 = outdata.get("frequencies")[0]
                    freq_1 = outdata.get("frequencies")[1]
                    if freq_0 > 0.0:
                        warnings.warn("All frequencies positive!")
                        break
                    if abs(freq_0) < 15.0 and freq_1 > 0.0:
                        warnings.warn(
                            "One negative frequency smaller than 15.0 - not worth further flattening!"
                        )
                        break
                    if len(energy_history) > 1:
                        if abs(energy_history[-1] -
                               energy_history[-2]) < energy_diff_cutoff:
                            warnings.warn("Energy change below cutoff!")
                            break
                    opt_QCInput = QCInput(
                        molecule=opt_outdata.get(
                            "molecule_from_optimized_geometry"),
                        rem=opt_rem,
                        opt=orig_input.opt,
                        pcm=orig_input.pcm,
                        solvent=orig_input.solvent,
                        smx=orig_input.smx,
                    )
                    opt_QCInput.write_file(input_file)
                else:
                    freq_0 = outdata.get("frequencies")[0]
                    freq_1 = outdata.get("frequencies")[1]
                    freq_2 = outdata.get("frequencies")[2]
                    if freq_0 < 0.0 < freq_1:
                        warnings.warn("Saddle point found!")
                        break
                    if abs(freq_1) < 15.0 and freq_2 > 0.0:
                        warnings.warn(
                            "Second small imaginary frequency (smaller than 15.0) - not worth further flattening!"
                        )
                        break
                    opt_QCInput = QCInput(
                        molecule=opt_outdata.get(
                            "molecule_from_optimized_geometry"),
                        rem=opt_rem,
                        opt=orig_input.opt,
                        pcm=orig_input.pcm,
                        solvent=orig_input.solvent,
                        smx=orig_input.smx,
                    )
                    opt_QCInput.write_file(input_file)
            if not save_final_scratch:
                shutil.rmtree(os.path.join(os.getcwd(), "scratch"))

        else:
            orig_opt_input = QCInput.from_file(input_file)
            history = list()

            for ii in range(max_iterations):
                yield (QCJob(
                    qchem_command=qchem_command,
                    multimode=multimode,
                    input_file=input_file,
                    output_file=output_file,
                    qclog_file=qclog_file,
                    suffix=".{}_".format(opt_method) + str(ii),
                    backup=first,
                    **QCJob_kwargs,
                ))
                opt_outdata = QCOutput(output_file +
                                       ".{}_".format(opt_method) +
                                       str(ii)).data
                if first:
                    orig_species = copy.deepcopy(opt_outdata.get("species"))
                    orig_charge = copy.deepcopy(opt_outdata.get("charge"))
                    orig_multiplicity = copy.deepcopy(
                        opt_outdata.get("multiplicity"))
                    orig_energy = copy.deepcopy(
                        opt_outdata.get("final_energy"))
                first = False
                if opt_outdata[
                        "structure_change"] == "unconnected_fragments" and not opt_outdata[
                            "completion"]:
                    if not transition_state:
                        warnings.warn(
                            "Unstable molecule broke into unconnected fragments which failed to optimize! Exiting..."
                        )
                        break
                freq_QCInput = QCInput(
                    molecule=opt_outdata.get(
                        "molecule_from_optimized_geometry"),
                    rem=freq_rem,
                    opt=orig_opt_input.opt,
                    pcm=orig_opt_input.pcm,
                    solvent=orig_opt_input.solvent,
                    smx=orig_opt_input.smx,
                )
                freq_QCInput.write_file(input_file)
                yield (QCJob(
                    qchem_command=qchem_command,
                    multimode=multimode,
                    input_file=input_file,
                    output_file=output_file,
                    qclog_file=qclog_file,
                    suffix=".freq_" + str(ii),
                    backup=first,
                    **QCJob_kwargs,
                ))
                outdata = QCOutput(output_file + ".freq_" + str(ii)).data
                errors = outdata.get("errors")
                if len(errors) != 0:
                    raise AssertionError(
                        "No errors should be encountered while flattening frequencies!"
                    )
                if not transition_state:
                    freq_0 = outdata.get("frequencies")[0]
                    freq_1 = outdata.get("frequencies")[1]
                    if freq_0 > 0.0:
                        warnings.warn("All frequencies positive!")
                        if opt_outdata.get("final_energy") > orig_energy:
                            warnings.warn(
                                "WARNING: Energy increased during frequency flattening!"
                            )
                        break
                    if abs(freq_0) < 15.0 and freq_1 > 0.0:
                        warnings.warn(
                            "One negative frequency smaller than 15.0 - not worth further flattening!"
                        )
                        break
                    if len(energy_history) > 1:
                        if abs(energy_history[-1] -
                               energy_history[-2]) < energy_diff_cutoff:
                            warnings.warn("Energy change below cutoff!")
                            break
                else:
                    freq_0 = outdata.get("frequencies")[0]
                    freq_1 = outdata.get("frequencies")[1]
                    freq_2 = outdata.get("frequencies")[2]
                    if freq_0 < 0.0 < freq_1:
                        warnings.warn("Saddle point found!")
                        break
                    if abs(freq_1) < 15.0 and freq_2 > 0.0:
                        warnings.warn(
                            "Second small imaginary frequency (smaller than 15.0) - not worth further flattening!"
                        )
                        break

                hist = {}
                hist["molecule"] = copy.deepcopy(
                    outdata.get("initial_molecule"))
                hist["geometry"] = copy.deepcopy(
                    outdata.get("initial_geometry"))
                hist["frequencies"] = copy.deepcopy(outdata.get("frequencies"))
                hist["frequency_mode_vectors"] = copy.deepcopy(
                    outdata.get("frequency_mode_vectors"))
                hist["num_neg_freqs"] = sum(
                    1 for freq in outdata.get("frequencies") if freq < 0)
                hist["energy"] = copy.deepcopy(opt_outdata.get("final_energy"))
                hist["index"] = len(history)
                hist["children"] = list()
                history.append(hist)

                ref_mol = history[-1]["molecule"]
                geom_to_perturb = history[-1]["geometry"]
                negative_freq_vecs = history[-1]["frequency_mode_vectors"][
                    perturb_index]
                reversed_direction = False
                standard = True

                # If we've found one or more negative frequencies in two consecutive iterations, let's dig in
                # deeper:
                if len(history) > 1:
                    # Start by finding the latest iteration's parent:
                    if history[-1]["index"] in history[-2]["children"]:
                        parent_hist = history[-2]
                        history[-1]["parent"] = parent_hist["index"]
                    elif history[-1]["index"] in history[-3]["children"]:
                        parent_hist = history[-3]
                        history[-1]["parent"] = parent_hist["index"]
                    else:
                        raise AssertionError(
                            "ERROR: your parent should always be one or two iterations behind you! Exiting..."
                        )

                    # if the number of negative frequencies has remained constant or increased from parent to
                    # child,
                    if history[-1]["num_neg_freqs"] >= parent_hist[
                            "num_neg_freqs"]:
                        # check to see if the parent only has one child, aka only the positive perturbation has
                        # been tried,
                        # in which case just try the negative perturbation from the same parent
                        if len(parent_hist["children"]) == 1:
                            ref_mol = parent_hist["molecule"]
                            geom_to_perturb = parent_hist["geometry"]
                            negative_freq_vecs = parent_hist[
                                "frequency_mode_vectors"][perturb_index]
                            reversed_direction = True
                            standard = False
                            parent_hist["children"].append(len(history))
                        # If the parent has two children, aka both directions have been tried, then we have to
                        # get creative:
                        elif len(parent_hist["children"]) == 2:
                            # If we're dealing with just one negative frequency,
                            if parent_hist["num_neg_freqs"] == 1:
                                if history[parent_hist["children"][0]][
                                        "energy"] < history[-1]["energy"]:
                                    good_child = copy.deepcopy(
                                        history[parent_hist["children"][0]])
                                else:
                                    good_child = copy.deepcopy(history[-1])
                                if good_child["num_neg_freqs"] > 1:
                                    raise Exception(
                                        "ERROR: Child with lower energy has more negative frequencies! "
                                        "Exiting...")
                                if good_child["energy"] < parent_hist["energy"]:
                                    make_good_child_next_parent = True
                                elif (vector_list_diff(
                                        good_child["frequency_mode_vectors"]
                                    [perturb_index],
                                        parent_hist["frequency_mode_vectors"]
                                    [perturb_index],
                                ) > 0.2):
                                    make_good_child_next_parent = True
                                else:
                                    raise Exception(
                                        "ERROR: Good child not good enough! Exiting..."
                                    )
                                if make_good_child_next_parent:
                                    good_child["index"] = len(history)
                                    history.append(good_child)
                                    ref_mol = history[-1]["molecule"]
                                    geom_to_perturb = history[-1]["geometry"]
                                    negative_freq_vecs = history[-1][
                                        "frequency_mode_vectors"][
                                            perturb_index]
                            else:
                                raise Exception(
                                    "ERROR: Can't deal with multiple neg frequencies yet! Exiting..."
                                )
                        else:
                            raise AssertionError(
                                "ERROR: Parent cannot have more than two childen! Exiting..."
                            )
                    # Implicitly, if the number of negative frequencies decreased from parent to child,
                    # continue normally.
                if standard:
                    history[-1]["children"].append(len(history))

                min_molecule_perturb_scale = 0.1
                scale_grid = 10
                perturb_scale_grid = (max_molecule_perturb_scale -
                                      min_molecule_perturb_scale) / scale_grid

                structure_successfully_perturbed = False
                for molecule_perturb_scale in np.arange(
                        max_molecule_perturb_scale,
                        min_molecule_perturb_scale,
                        -perturb_scale_grid,
                ):
                    new_coords = perturb_coordinates(
                        old_coords=geom_to_perturb,
                        negative_freq_vecs=negative_freq_vecs,
                        molecule_perturb_scale=molecule_perturb_scale,
                        reversed_direction=reversed_direction,
                    )
                    new_molecule = Molecule(
                        species=orig_species,
                        coords=new_coords,
                        charge=orig_charge,
                        spin_multiplicity=orig_multiplicity,
                    )
                    if check_connectivity and not transition_state:
                        structure_successfully_perturbed = (
                            check_for_structure_changes(
                                ref_mol, new_molecule) == "no_change")
                        if structure_successfully_perturbed:
                            break
                if not structure_successfully_perturbed:
                    raise Exception(
                        "ERROR: Unable to perturb coordinates to remove negative frequency without changing "
                        "the connectivity! Exiting...")

                new_opt_QCInput = QCInput(
                    molecule=new_molecule,
                    rem=opt_rem,
                    opt=orig_opt_input.opt,
                    pcm=orig_opt_input.pcm,
                    solvent=orig_opt_input.solvent,
                    smx=orig_opt_input.smx,
                )
                new_opt_QCInput.write_file(input_file)
Exemple #21
0
# coding: utf-8

import os

from atomate.qchem.workflows.base.double_FF_opt import get_wf_double_FF_opt
from fireworks.core.launchpad import LaunchPad
from pymatgen.io.qchem.outputs import QCOutput

out_file = os.path.join("/global/cscratch1/sd/sblau", "FF_working",
                        "test.qout.opt_0")
qc_out = QCOutput(filename=out_file)
act_mol = qc_out.data["initial_molecule"]
wf = get_wf_double_FF_opt(molecule=act_mol,
                          pcm_dielectric=10.0,
                          max_cores=32,
                          qchem_input_params={
                              "basis_set": "6-311++g**",
                              "overwrite_inputs": {
                                  "rem": {
                                      "sym_ignore": "true"
                                  }
                              }
                          })
lp = LaunchPad.auto_load()
lp.add_wf(wf)
Exemple #22
0
    def get_unfinished_jobs(self,
                            sp_params,
                            name_pre="single_point",
                            dirs=None,
                            max_cores=32):
        """
        Look for jobs where optimization and frequency calculations have
        successfully completed, but single-point has not. Then, for these cases,
        construct a workflow which will only run the sp job.

        :param sp_params: dict containing input parameters for single-point job
        :param name_pre: str representing prefix for all jobs.
        :param dirs: list of subdirectories to check for unfinished jobs.
            Default None, meaning that all subdirectories will be checked.
        :param max_cores: max_cores (int): Maximum number of cores to
            parallelize over. Defaults to 24.
        :return:
        """

        if not self.subdirs:
            raise RuntimeError("Cannot run get_reaction_set_workflow();"
                               "Need reactions components to be isolated in"
                               "different subdirectories.")

        fws = []

        all_dirs = [
            d for d in listdir(self.base_dir) if isdir(join(self.base_dir, d))
        ]

        molecules_cleared = []

        appropriate_dirs = all_dirs

        if dirs is not None:
            appropriate_dirs = [d for d in appropriate_dirs if d in dirs]

        for d in appropriate_dirs:
            path = join(self.base_dir, d)
            file_map = associate_qchem_to_mol(self.base_dir, d)

            for key, values in file_map.items():
                mol_id = extract_id(key)

                if mol_id in molecules_cleared:
                    continue

                freq_complete = False
                sp_complete = False

                in_files = values["in"]
                out_files = values["out"]

                # Check if this molecule has finished freq, sp
                # If there is no sp output file, or if the sp output file did
                # not complete, then we may proceed
                for out_file in out_files:
                    if "freq" in out_file:
                        freq_out = QCOutput(join(path, out_file))

                        if freq_out.data.get("completion", []):
                            freq_complete = True
                    elif "sp" in out_file:
                        sp_out = QCOutput(join(path, out_file))

                        if sp_out.data.get("completion", []):
                            sp_complete = True

                if freq_complete and not sp_complete:
                    # Check if there is already an sp input file
                    freq_in_file = None

                    for in_file in in_files:
                        if "freq" in in_file:
                            freq_in_file = in_file

                    if freq_in_file is None:
                        # We could parse output files to get previous input
                        # information, but we should try to keep all input
                        # files in the same directory
                        continue
                    else:
                        infile = join(path, key.replace(".mol", "") + ".in")
                        outfile = join(path, key.replace(".mol", "") + ".out")
                        qclogfile = join(path,
                                         key.replace(".mol", "") + ".qclog")

                        freq_in_file = QCInput.from_file(
                            join(path, freq_in_file))
                        mol = freq_in_file.molecule

                        fw = SinglePointFW(molecule=mol,
                                           name="{}: {}/{}".format(
                                               name_pre, d, mol_id),
                                           qchem_cmd="qchem -slurm",
                                           multimode="openmp",
                                           input_file=infile,
                                           output_file=outfile,
                                           qclog_file=qclogfile,
                                           max_cores=max_cores,
                                           sp_params=sp_params)

                        fws.append(fw)
                        molecules_cleared.append(mol_id)

        return Workflow(fws)
Exemple #23
0
 def setUp(self, lpad=False):
     out_file = os.path.join(module_dir, "..", "..", "test_files",
                             "FF_working", "test.qout.opt_0")
     qc_out = QCOutput(filename=out_file)
     self.act_mol = qc_out.data["initial_molecule"]
     super(TestCore, self).setUp(lpad=False)
Exemple #24
0
    def test_torsion_potential(self):
        # location of test files
        test_tor_files = os.path.join(module_dir, "..", "..", "test_files",
                                      "torsion_wf")
        # define starting molecule and torsion potential workflow object
        initial_qcin = QCInput.from_file(
            os.path.join(test_tor_files, "initial_opt", "mol.qin"))
        initial_mol = initial_qcin.molecule
        atom_indexes = [6, 8, 9, 10]
        angles = [0.0, 90.0, 180.0]
        rem = []
        # add the first rem section
        rem.append({
            "jobtype": "opt",
            "method": "wb97m-v",
            "basis": "def2-tzvppd",
            "gen_scfman": "true",
            "geom_opt_max_cycles": 75,
            "max_scf_cycles": 300,
            "scf_algorithm": "diis",
            "scf_guess": "sad",
            "sym_ignore": "true",
            "symmetry": "false",
            "thresh": 14
        })

        # the second rem section
        rem.append({
            "jobtype": "opt",
            "method": "wb97m-v",
            "basis": "def2-tzvppd",
            "geom_opt_max_cycles": 75,
            "max_scf_cycles": 300,
            "scf_algorithm": "diis",
            "scf_guess": "sad",
            "sym_ignore": "true",
            "symmetry": "false",
            "thresh": 14
        })

        real_wf = get_wf_torsion_potential(molecule=initial_mol,
                                           atom_indexes=atom_indexes,
                                           angles=angles,
                                           rem=rem,
                                           db_file=">>db_file<<")
        # use powerup to replace run with fake run
        # def ref_dirs
        ref_dirs = {
            "initial_opt": os.path.join(test_tor_files, "initial_opt"),
            "opt_0": os.path.join(test_tor_files, "opt_0"),
            "opt_90": os.path.join(test_tor_files, "opt_90"),
            "opt_180": os.path.join(test_tor_files, "opt_180")
        }
        fake_wf = use_fake_qchem(real_wf, ref_dirs)

        self.lp.add_wf(fake_wf)
        rapidfire(
            self.lp,
            fworker=FWorker(env={"db_file": os.path.join(db_dir, "db.json")}))

        wf_test = self.lp.get_wf_by_fw_id(1)
        self.assertTrue(
            all([s == "COMPLETED" for s in wf_test.fw_states.values()]))

        # Checking of the inputs happens in fake_run_qchem so there is no point to retest the inputs
        # Check the output info that gets inserted in the DB
        init_opt = self.get_task_collection().find_one(
            {"task_label": "initial_opt"})
        init_opt_final_mol = Molecule.from_dict(
            init_opt["output"]["optimized_molecule"])
        init_opt_final_e = init_opt["output"]["final_energy"]
        # parse output file
        act_init_opt_out = QCOutput(
            os.path.join(test_tor_files, "initial_opt", "mol.qout"))
        act_init_opt_mol = act_init_opt_out.data[
            "molecule_from_optimized_geometry"]
        act_init_opt_final_e = act_init_opt_out.data["final_energy"]

        np.testing.assert_equal(act_init_opt_mol.species,
                                init_opt_final_mol.species)
        np.testing.assert_allclose(act_init_opt_mol.cart_coords,
                                   init_opt_final_mol.cart_coords,
                                   atol=0.0001)
        np.testing.assert_equal(act_init_opt_final_e, init_opt_final_e)

        # Optimization of 0 torsion
        opt_0 = self.get_task_collection().find_one({"task_label": "opt_0"})
        opt_0_final_mol = Molecule.from_dict(
            opt_0["output"]["optimized_molecule"])
        opt_0_final_e = opt_0["output"]["final_energy"]
        # parse output file
        act_opt_0_out = QCOutput(
            os.path.join(test_tor_files, "opt_0", "mol.qout"))
        act_opt_0_mol = act_opt_0_out.data["molecule_from_optimized_geometry"]
        act_opt_0_final_e = act_opt_0_out.data["final_energy"]

        np.testing.assert_equal(act_opt_0_mol.species, opt_0_final_mol.species)
        np.testing.assert_allclose(act_opt_0_mol.cart_coords,
                                   opt_0_final_mol.cart_coords,
                                   atol=0.0001)
        np.testing.assert_equal(act_opt_0_final_e, opt_0_final_e)

        # Optimization of 90 torsion
        opt_90 = self.get_task_collection().find_one({"task_label": "opt_90"})
        opt_90_final_mol = Molecule.from_dict(
            opt_90["output"]["optimized_molecule"])
        opt_90_final_e = opt_90["output"]["final_energy"]
        # parse output file
        act_opt_90_out = QCOutput(
            os.path.join(test_tor_files, "opt_90", "mol.qout"))
        act_opt_90_mol = act_opt_90_out.data[
            "molecule_from_optimized_geometry"]
        act_opt_90_final_e = act_opt_90_out.data["final_energy"]

        np.testing.assert_equal(act_opt_90_mol.species,
                                opt_90_final_mol.species)
        np.testing.assert_allclose(act_opt_90_mol.cart_coords,
                                   opt_90_final_mol.cart_coords,
                                   atol=0.0001)
        np.testing.assert_equal(act_opt_90_final_e, opt_90_final_e)

        # Optimization of 180 torsion
        opt_180 = self.get_task_collection().find_one(
            {"task_label": "opt_180"})
        opt_180_final_mol = Molecule.from_dict(
            opt_180["output"]["optimized_molecule"])
        opt_180_final_e = opt_180["output"]["final_energy"]
        # parse output file
        act_opt_180_out = QCOutput(
            os.path.join(test_tor_files, "opt_180", "mol.qout"))
        act_opt_180_mol = act_opt_180_out.data[
            "molecule_from_optimized_geometry"]
        act_opt_180_final_e = act_opt_180_out.data["final_energy"]

        np.testing.assert_equal(act_opt_180_mol.species,
                                opt_180_final_mol.species)
        np.testing.assert_allclose(act_opt_180_mol.cart_coords,
                                   opt_180_final_mol.cart_coords,
                                   atol=0.0001)
        np.testing.assert_equal(act_opt_180_final_e, opt_180_final_e)
Exemple #25
0
 def setUp(self):
     self.maxDiff = None
     out_file = os.path.join(files_dir, "qchem", "test.qout.opt_0")
     qc_out = QCOutput(filename=out_file)
     self.act_mol = qc_out.data["initial_molecule"]
     super(TestSinglePointFW, self).setUp(lpad=False)
Exemple #26
0
def associate_qchem_to_mol(base_dir, directory):
    """
    Assign all .in and .out files in a directory to one of the .mol files in that
    directory, based on the non-H atoms in those molecules.

    :param base_dir: Directory to begin search in.
    :param directory: Subdirectory of interes
    :return:
    """

    base_path = join(base_dir, directory)

    all_files = [
        e for e in listdir(base_path)
        if not e.startswith(".") and not e.endswith("swp")
    ]

    mol_files = [
        f for f in all_files if isfile(join(base_path, f))
        and f.endswith(".mol") and not f.startswith(".")
    ]
    # Note: This will catch .in and .out files for incomplete computations
    in_files = [
        f for f in all_files if isfile(join(base_path, f)) and (
            ".in" in f or ".qin" in f) and not f.startswith("atomate")
    ]
    out_files = [
        f for f in all_files if isfile(join(base_path, f)) and (
            ".out" in f or ".qout" in f) and not f.startswith("atomate")
    ]

    mapping = {mol: {"in": [], "out": []} for mol in mol_files}

    for file in in_files:
        qcin = QCInput.from_file(join(base_path, file))
        file_mol = qcin.molecule
        # Remove H because mol files may not begin with H included
        file_species = [str(s) for s in file_mol.species if str(s) != "H"]

        for mf in mol_files:
            mol_mol = Molecule.from_file(join(base_path, mf))
            mol_species = [str(s) for s in mol_mol.species if str(s) != "H"]
            # Preserve initial order because that gives a better guarantee
            # That the two are actually associated
            if mol_species == file_species:
                mapping[mf]["in"].append(file)
                break

    for file in out_files:
        qcout = QCOutput(join(base_path, file))
        file_mol = qcout.data["initial_molecule"]
        file_species = [str(s) for s in file_mol.species if str(s) != "H"]

        for mf in mol_files:
            mol_mol = Molecule.from_file(join(base_path, mf))
            mol_species = [str(s) for s in mol_mol.species if str(s) != "H"]

            # Preserve initial order because that gives a better guarantee
            # That the two are actually associated
            if mol_species == file_species:
                mapping[mf]["out"].append(file)
                break

    return mapping
Exemple #27
0
    def opt_with_frequency_flattener(cls,
                                     qchem_command,
                                     multimode="openmp",
                                     input_file="mol.qin",
                                     output_file="mol.qout",
                                     qclog_file="mol.qclog",
                                     max_iterations=10,
                                     max_molecule_perturb_scale=0.3,
                                     check_connectivity=True,
                                     **QCJob_kwargs):
        """
        Optimize a structure and calculate vibrational frequencies to check if the
        structure is in a true minima. If a frequency is negative, iteratively
        perturbe the geometry, optimize, and recalculate frequencies until all are
        positive, aka a true minima has been found.

        Args:
            qchem_command (str): Command to run QChem.
            multimode (str): Parallelization scheme, either openmp or mpi.
            input_file (str): Name of the QChem input file.
            output_file (str): Name of the QChem output file.
            max_iterations (int): Number of perturbation -> optimization -> frequency
                iterations to perform. Defaults to 10.
            max_molecule_perturb_scale (float): The maximum scaled perturbation that
                can be applied to the molecule. Defaults to 0.3.
            check_connectivity (bool): Whether to check differences in connectivity
                introduced by structural perturbation. Defaults to True.
            **QCJob_kwargs: Passthrough kwargs to QCJob. See
                :class:`custodian.qchem.jobs.QCJob`.
        """

        min_molecule_perturb_scale = 0.1
        scale_grid = 10
        perturb_scale_grid = (max_molecule_perturb_scale -
                              min_molecule_perturb_scale) / scale_grid

        if not os.path.exists(input_file):
            raise AssertionError('Input file must be present!')
        orig_opt_input = QCInput.from_file(input_file)
        orig_opt_rem = copy.deepcopy(orig_opt_input.rem)
        orig_freq_rem = copy.deepcopy(orig_opt_input.rem)
        orig_freq_rem["job_type"] = "freq"
        first = True
        reversed_direction = False
        num_neg_freqs = []

        for ii in range(max_iterations):
            yield (QCJob(qchem_command=qchem_command,
                         multimode=multimode,
                         input_file=input_file,
                         output_file=output_file,
                         qclog_file=qclog_file,
                         suffix=".opt_" + str(ii),
                         backup=first,
                         **QCJob_kwargs))
            first = False
            opt_outdata = QCOutput(output_file + ".opt_" + str(ii)).data
            if opt_outdata[
                    "structure_change"] == "unconnected_fragments" and not opt_outdata[
                        "completion"]:
                print(
                    "Unstable molecule broke into unconnected fragments which failed to optimize! Exiting..."
                )
                break
            else:
                freq_QCInput = QCInput(molecule=opt_outdata.get(
                    "molecule_from_optimized_geometry"),
                                       rem=orig_freq_rem,
                                       opt=orig_opt_input.opt,
                                       pcm=orig_opt_input.pcm,
                                       solvent=orig_opt_input.solvent)
                freq_QCInput.write_file(input_file)
                yield (QCJob(qchem_command=qchem_command,
                             multimode=multimode,
                             input_file=input_file,
                             output_file=output_file,
                             qclog_file=qclog_file,
                             suffix=".freq_" + str(ii),
                             backup=first,
                             **QCJob_kwargs))
                outdata = QCOutput(output_file + ".freq_" + str(ii)).data
                errors = outdata.get("errors")
                if len(errors) != 0:
                    raise AssertionError(
                        'No errors should be encountered while flattening frequencies!'
                    )
                if outdata.get('frequencies')[0] > 0.0:
                    print("All frequencies positive!")
                    break
                else:
                    num_neg_freqs += [
                        sum(1 for freq in outdata.get('frequencies')
                            if freq < 0)
                    ]
                    if len(num_neg_freqs) > 1:
                        if num_neg_freqs[-1] == num_neg_freqs[
                                -2] and not reversed_direction:
                            reversed_direction = True
                        elif num_neg_freqs[-1] == num_neg_freqs[
                                -2] and reversed_direction:
                            if len(num_neg_freqs) < 3:
                                raise AssertionError(
                                    "ERROR: This should only be possible after at least three frequency flattening iterations! Exiting..."
                                )
                            else:
                                raise Exception(
                                    "ERROR: Reversing the perturbation direction still could not flatten any frequencies. Exiting..."
                                )
                        elif num_neg_freqs[-1] != num_neg_freqs[
                                -2] and reversed_direction:
                            reversed_direction = False

                    negative_freq_vecs = outdata.get(
                        "frequency_mode_vectors")[0]
                    structure_successfully_perturbed = False

                    for molecule_perturb_scale in np.arange(
                            max_molecule_perturb_scale,
                            min_molecule_perturb_scale, -perturb_scale_grid):
                        new_coords = perturb_coordinates(
                            old_coords=outdata.get("initial_geometry"),
                            negative_freq_vecs=negative_freq_vecs,
                            molecule_perturb_scale=molecule_perturb_scale,
                            reversed_direction=reversed_direction)
                        new_molecule = Molecule(
                            species=outdata.get('species'),
                            coords=new_coords,
                            charge=outdata.get('charge'),
                            spin_multiplicity=outdata.get('multiplicity'))
                        if check_connectivity:
                            old_molgraph = MoleculeGraph.with_local_env_strategy(
                                outdata.get("initial_molecule"),
                                OpenBabelNN(),
                                reorder=False,
                                extend_structure=False)
                            new_molgraph = MoleculeGraph.with_local_env_strategy(
                                new_molecule,
                                OpenBabelNN(),
                                reorder=False,
                                extend_structure=False)
                            if old_molgraph.isomorphic_to(new_molgraph):
                                structure_successfully_perturbed = True
                                break
                    if not structure_successfully_perturbed:
                        raise Exception(
                            "ERROR: Unable to perturb coordinates to remove negative frequency without changing the connectivity! Exiting..."
                        )

                    new_opt_QCInput = QCInput(molecule=new_molecule,
                                              rem=orig_opt_rem,
                                              opt=orig_opt_input.opt,
                                              pcm=orig_opt_input.pcm,
                                              solvent=orig_opt_input.solvent)
                    new_opt_QCInput.write_file(input_file)
Exemple #28
0
 def test_NBO7_infinite_e2pert(self):
     data = QCOutput(os.path.join(PymatgenTest.TEST_FILES_DIR, "molecules", "new_qchem_files", "nbo7_inf.qout")).data
     self.assertEqual(data["nbo_data"]["perturbation_energy"][0]["perturbation energy"][0], float("inf"))
Exemple #29
0
 def setUp(self):
     self.maxDiff = None
     out_file = os.path.join(files_dir, "qchem", "fsm.qout")
     qc_out = QCOutput(filename=out_file)
     self.act_mol = qc_out.data["initial_molecule"]
     super(TestFrequencyFlatteningTransitionStateFW, self).setUp(lpad=False)
Exemple #30
0
    def opt_with_frequency_flattener(cls,
                                     qchem_command,
                                     multimode="openmp",
                                     input_file="mol.qin",
                                     output_file="mol.qout",
                                     qclog_file="mol.qclog",
                                     max_iterations=10,
                                     max_molecule_perturb_scale=0.3,
                                     check_connectivity=True,
                                     linked=True,
                                     **QCJob_kwargs):
        """
        Optimize a structure and calculate vibrational frequencies to check if the
        structure is in a true minima. If a frequency is negative, iteratively
        perturbe the geometry, optimize, and recalculate frequencies until all are
        positive, aka a true minima has been found.

        Args:
            qchem_command (str): Command to run QChem.
            multimode (str): Parallelization scheme, either openmp or mpi.
            input_file (str): Name of the QChem input file.
            output_file (str): Name of the QChem output file.
            max_iterations (int): Number of perturbation -> optimization -> frequency
                iterations to perform. Defaults to 10.
            max_molecule_perturb_scale (float): The maximum scaled perturbation that
                can be applied to the molecule. Defaults to 0.3.
            check_connectivity (bool): Whether to check differences in connectivity
                introduced by structural perturbation. Defaults to True.
            **QCJob_kwargs: Passthrough kwargs to QCJob. See
                :class:`custodian.qchem.jobs.QCJob`.
        """
        if not os.path.exists(input_file):
            raise AssertionError("Input file must be present!")

        if linked:

            energy_diff_cutoff = 0.0000001

            orig_input = QCInput.from_file(input_file)
            freq_rem = copy.deepcopy(orig_input.rem)
            freq_rem["job_type"] = "freq"
            opt_rem = copy.deepcopy(orig_input.rem)
            opt_rem["geom_opt_hessian"] = "read"
            opt_rem["scf_guess_always"] = True
            first = True
            energy_history = []

            for ii in range(max_iterations):
                yield (QCJob(qchem_command=qchem_command,
                             multimode=multimode,
                             input_file=input_file,
                             output_file=output_file,
                             qclog_file=qclog_file,
                             suffix=".opt_" + str(ii),
                             scratch_dir=os.getcwd(),
                             save_scratch=True,
                             save_name="chain_scratch",
                             backup=first,
                             **QCJob_kwargs))
                opt_outdata = QCOutput(output_file + ".opt_" + str(ii)).data
                first = False
                if (opt_outdata["structure_change"] == "unconnected_fragments"
                        and not opt_outdata["completion"]):
                    print(
                        "Unstable molecule broke into unconnected fragments which failed to optimize! Exiting..."
                    )
                    break
                else:
                    energy_history.append(opt_outdata.get("final_energy"))
                    freq_QCInput = QCInput(
                        molecule=opt_outdata.get(
                            "molecule_from_optimized_geometry"),
                        rem=freq_rem,
                        opt=orig_input.opt,
                        pcm=orig_input.pcm,
                        solvent=orig_input.solvent,
                        smx=orig_input.smx,
                    )
                    freq_QCInput.write_file(input_file)
                    yield (QCJob(qchem_command=qchem_command,
                                 multimode=multimode,
                                 input_file=input_file,
                                 output_file=output_file,
                                 qclog_file=qclog_file,
                                 suffix=".freq_" + str(ii),
                                 scratch_dir=os.getcwd(),
                                 save_scratch=True,
                                 save_name="chain_scratch",
                                 backup=first,
                                 **QCJob_kwargs))
                    outdata = QCOutput(output_file + ".freq_" + str(ii)).data
                    errors = outdata.get("errors")
                    if len(errors) != 0:
                        raise AssertionError(
                            "No errors should be encountered while flattening frequencies!"
                        )
                    if outdata.get("frequencies")[0] > 0.0:
                        print("All frequencies positive!")
                        break
                    elif (abs(outdata.get("frequencies")[0]) < 15.0
                          and outdata.get("frequencies")[1] > 0.0):
                        print(
                            "One negative frequency smaller than 15.0 - not worth further flattening!"
                        )
                        break
                    else:
                        if len(energy_history) > 1:
                            if (abs(energy_history[-1] - energy_history[-2]) <
                                    energy_diff_cutoff):
                                print("Energy change below cutoff!")
                                break
                        opt_QCInput = QCInput(
                            molecule=opt_outdata.get(
                                "molecule_from_optimized_geometry"),
                            rem=opt_rem,
                            opt=orig_input.opt,
                            pcm=orig_input.pcm,
                            solvent=orig_input.solvent,
                            smx=orig_input.smx,
                        )
                        opt_QCInput.write_file(input_file)
            if os.path.exists(os.path.join(os.getcwd(), "chain_scratch")):
                shutil.rmtree(os.path.join(os.getcwd(), "chain_scratch"))

        else:
            if not os.path.exists(input_file):
                raise AssertionError("Input file must be present!")
            orig_opt_input = QCInput.from_file(input_file)
            orig_opt_rem = copy.deepcopy(orig_opt_input.rem)
            orig_freq_rem = copy.deepcopy(orig_opt_input.rem)
            orig_freq_rem["job_type"] = "freq"
            first = True
            history = []

            for ii in range(max_iterations):
                yield (QCJob(qchem_command=qchem_command,
                             multimode=multimode,
                             input_file=input_file,
                             output_file=output_file,
                             qclog_file=qclog_file,
                             suffix=".opt_" + str(ii),
                             backup=first,
                             **QCJob_kwargs))
                opt_outdata = QCOutput(output_file + ".opt_" + str(ii)).data
                if first:
                    orig_species = copy.deepcopy(opt_outdata.get("species"))
                    orig_charge = copy.deepcopy(opt_outdata.get("charge"))
                    orig_multiplicity = copy.deepcopy(
                        opt_outdata.get("multiplicity"))
                    orig_energy = copy.deepcopy(
                        opt_outdata.get("final_energy"))
                first = False
                if (opt_outdata["structure_change"] == "unconnected_fragments"
                        and not opt_outdata["completion"]):
                    print(
                        "Unstable molecule broke into unconnected fragments which failed to optimize! Exiting..."
                    )
                    break
                else:
                    freq_QCInput = QCInput(
                        molecule=opt_outdata.get(
                            "molecule_from_optimized_geometry"),
                        rem=orig_freq_rem,
                        opt=orig_opt_input.opt,
                        pcm=orig_opt_input.pcm,
                        solvent=orig_opt_input.solvent,
                        smx=orig_opt_input.smx,
                    )
                    freq_QCInput.write_file(input_file)
                    yield (QCJob(qchem_command=qchem_command,
                                 multimode=multimode,
                                 input_file=input_file,
                                 output_file=output_file,
                                 qclog_file=qclog_file,
                                 suffix=".freq_" + str(ii),
                                 backup=first,
                                 **QCJob_kwargs))
                    outdata = QCOutput(output_file + ".freq_" + str(ii)).data
                    errors = outdata.get("errors")
                    if len(errors) != 0:
                        raise AssertionError(
                            "No errors should be encountered while flattening frequencies!"
                        )
                    if outdata.get("frequencies")[0] > 0.0:
                        print("All frequencies positive!")
                        if opt_outdata.get("final_energy") > orig_energy:
                            print(
                                "WARNING: Energy increased during frequency flattening!"
                            )
                        break
                    else:
                        hist = {}
                        hist["molecule"] = copy.deepcopy(
                            outdata.get("initial_molecule"))
                        hist["geometry"] = copy.deepcopy(
                            outdata.get("initial_geometry"))
                        hist["frequencies"] = copy.deepcopy(
                            outdata.get("frequencies"))
                        hist["frequency_mode_vectors"] = copy.deepcopy(
                            outdata.get("frequency_mode_vectors"))
                        hist["num_neg_freqs"] = sum(
                            1 for freq in outdata.get("frequencies")
                            if freq < 0)
                        hist["energy"] = copy.deepcopy(
                            opt_outdata.get("final_energy"))
                        hist["index"] = len(history)
                        hist["children"] = []
                        history.append(hist)

                        ref_mol = history[-1]["molecule"]
                        geom_to_perturb = history[-1]["geometry"]
                        negative_freq_vecs = history[-1][
                            "frequency_mode_vectors"][0]
                        reversed_direction = False
                        standard = True

                        # If we've found one or more negative frequencies in two consecutive iterations, let's dig in
                        # deeper:
                        if len(history) > 1:
                            # Start by finding the latest iteration's parent:
                            if history[-1]["index"] in history[-2]["children"]:
                                parent_hist = history[-2]
                                history[-1]["parent"] = parent_hist["index"]
                            elif history[-1]["index"] in history[-3][
                                    "children"]:
                                parent_hist = history[-3]
                                history[-1]["parent"] = parent_hist["index"]
                            else:
                                raise AssertionError(
                                    "ERROR: your parent should always be one or two iterations behind you! Exiting..."
                                )

                            # if the number of negative frequencies has remained constant or increased from parent to
                            # child,
                            if (history[-1]["num_neg_freqs"] >=
                                    parent_hist["num_neg_freqs"]):
                                # check to see if the parent only has one child, aka only the positive perturbation has
                                # been tried,
                                # in which case just try the negative perturbation from the same parent
                                if len(parent_hist["children"]) == 1:
                                    ref_mol = parent_hist["molecule"]
                                    geom_to_perturb = parent_hist["geometry"]
                                    negative_freq_vecs = parent_hist[
                                        "frequency_mode_vectors"][0]
                                    reversed_direction = True
                                    standard = False
                                    parent_hist["children"].append(
                                        len(history))
                                # If the parent has two children, aka both directions have been tried, then we have to
                                # get creative:
                                elif len(parent_hist["children"]) == 2:
                                    # If we're dealing with just one negative frequency,
                                    if parent_hist["num_neg_freqs"] == 1:
                                        make_good_child_next_parent = False
                                        if (history[parent_hist["children"]
                                                    [0]]["energy"] <
                                                history[-1]["energy"]):
                                            good_child = copy.deepcopy(history[
                                                parent_hist["children"][0]])
                                        else:
                                            good_child = copy.deepcopy(
                                                history[-1])
                                        if good_child["num_neg_freqs"] > 1:
                                            raise Exception(
                                                "ERROR: Child with lower energy has more negative frequencies! "
                                                "Exiting...")
                                        elif (good_child["energy"] <
                                              parent_hist["energy"]):
                                            make_good_child_next_parent = True
                                        elif (vector_list_diff(
                                                good_child[
                                                    "frequency_mode_vectors"]
                                            [0],
                                                parent_hist[
                                                    "frequency_mode_vectors"]
                                            [0],
                                        ) > 0.2):
                                            make_good_child_next_parent = True
                                        else:
                                            raise Exception(
                                                "ERROR: Good child not good enough! Exiting..."
                                            )
                                        if make_good_child_next_parent:
                                            good_child["index"] = len(history)
                                            history.append(good_child)
                                            ref_mol = history[-1]["molecule"]
                                            geom_to_perturb = history[-1][
                                                "geometry"]
                                            negative_freq_vecs = history[-1][
                                                "frequency_mode_vectors"][0]
                                    else:
                                        raise Exception(
                                            "ERROR: Can't deal with multiple neg frequencies yet! Exiting..."
                                        )
                                else:
                                    raise AssertionError(
                                        "ERROR: Parent cannot have more than two childen! Exiting..."
                                    )
                            # Implicitly, if the number of negative frequencies decreased from parent to child,
                            # continue normally.
                        if standard:
                            history[-1]["children"].append(len(history))

                        min_molecule_perturb_scale = 0.1
                        scale_grid = 10
                        perturb_scale_grid = (
                            max_molecule_perturb_scale -
                            min_molecule_perturb_scale) / scale_grid

                        structure_successfully_perturbed = False
                        for molecule_perturb_scale in np.arange(
                                max_molecule_perturb_scale,
                                min_molecule_perturb_scale,
                                -perturb_scale_grid,
                        ):
                            new_coords = perturb_coordinates(
                                old_coords=geom_to_perturb,
                                negative_freq_vecs=negative_freq_vecs,
                                molecule_perturb_scale=molecule_perturb_scale,
                                reversed_direction=reversed_direction,
                            )
                            new_molecule = Molecule(
                                species=orig_species,
                                coords=new_coords,
                                charge=orig_charge,
                                spin_multiplicity=orig_multiplicity,
                            )
                            if check_connectivity:
                                structure_successfully_perturbed = (
                                    check_for_structure_changes(
                                        ref_mol, new_molecule) == "no_change")
                                if structure_successfully_perturbed:
                                    break
                        if not structure_successfully_perturbed:
                            raise Exception(
                                "ERROR: Unable to perturb coordinates to remove negative frequency without changing "
                                "the connectivity! Exiting...")

                        new_opt_QCInput = QCInput(
                            molecule=new_molecule,
                            rem=orig_opt_rem,
                            opt=orig_opt_input.opt,
                            pcm=orig_opt_input.pcm,
                            solvent=orig_opt_input.solvent,
                            smx=orig_opt_input.smx,
                        )
                        new_opt_QCInput.write_file(input_file)