Exemplo n.º 1
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
Exemplo n.º 2
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)
Exemplo n.º 3
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
Exemplo n.º 4
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)
Exemplo n.º 5
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
Exemplo n.º 6
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)
Exemplo n.º 7
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)
Exemplo n.º 8
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")
Exemplo n.º 9
0
    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)
Exemplo n.º 10
0
 def process_qchem_multirun(dir_name, input_files, output_files):
     """
     Process a QChem run which is known to include multiple calculations
     in a single input/output pair.
     """
     if len(input_files) != 1:
         raise ValueError(
             "ERROR: The drone can only process a directory containing a single input/output pair when each include multiple calculations."
         )
     else:
         for key in input_files:
             to_return = []
             qchem_input_file = os.path.join(dir_name, input_files.get(key))
             qchem_output_file = os.path.join(dir_name,
                                              output_files.get(key))
             multi_out = QCOutput.multiple_outputs_from_file(
                 QCOutput, qchem_output_file, keep_sub_files=False)
             multi_in = QCInput.from_multi_jobs_file(qchem_input_file)
             for ii, out in enumerate(multi_out):
                 d = out.data
                 d["input"] = {}
                 d["input"]["molecule"] = multi_in[ii].molecule
                 d["input"]["rem"] = multi_in[ii].rem
                 d["input"]["opt"] = multi_in[ii].opt
                 d["input"]["pcm"] = multi_in[ii].pcm
                 d["input"]["solvent"] = multi_in[ii].solvent
                 d["task"] = {"type": key, "name": "calc" + str(ii)}
                 to_return.append(d)
         return to_return
Exemplo n.º 11
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)
Exemplo n.º 12
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)
Exemplo n.º 13
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")
Exemplo n.º 14
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")
Exemplo n.º 15
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)
Exemplo n.º 16
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)
Exemplo n.º 17
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)
Exemplo n.º 18
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*")
Exemplo n.º 19
0
 def check(self):
     # Checks output file for errors.
     self.outdata = QCOutput(self.output_file).data
     self.errors = self.outdata.get("errors")
     # 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
Exemplo n.º 20
0
 def generate_multi_job_dict():
     """
     Used to generate test dictionary for multiple jobs.
     """
     multi_job_dict = {}
     for file in multi_job_out_names:
         outputs = QCOutput.multiple_outputs_from_file(
             QCOutput, os.path.join(test_dir, file), keep_sub_files=False)
         data = []
         for sub_output in outputs:
             data.append(sub_output.data)
         multi_job_dict[file] = data
     dumpfn(multi_job_dict, "multi_job.json")
Exemplo n.º 21
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))
Exemplo n.º 22
0
 def generate_multi_job_dict():
     """
     Used to generate test dictionary for multiple jobs.
     """
     multi_job_dict = {}
     for file in multi_job_out_names:
         outputs = QCOutput.multiple_outputs_from_file(
             QCOutput, os.path.join(test_dir, file), keep_sub_files=False)
         data = []
         for sub_output in outputs:
             data.append(sub_output.data)
         multi_job_dict[file] = data
     dumpfn(multi_job_dict, "multi_job.json")
Exemplo n.º 23
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)
Exemplo n.º 24
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
Exemplo n.º 25
0
class QChemSCFErrorHandler(ErrorHandler):
    """
    QChem ErrorHandler class that addresses SCF non-convergence.
    """

    is_monitor = False

    def __init__(
        self,
        input_file="mol.qin",
        output_file="mol.qout",
        rca_gdm_thresh=1.0e-3,
        scf_max_cycles=200,
    ):
        """
        Initializes the error handler from a set of input and output files.

        Args:
            input_file (str): Name of the QChem input file.
            output_file (str): Name of the QChem output file.
            rca_gdm_thresh (float): The threshold for the prior scf algorithm.
                If last deltaE is larger than the threshold try RCA_DIIS
                first, else, try DIIS_GDM first.
            scf_max_cycles (int): The max iterations to set to fix SCF failure.
        """
        self.input_file = input_file
        self.output_file = output_file
        self.scf_max_cycles = scf_max_cycles
        self.qcinp = QCInput.from_file(self.input_file)
        self.outdata = None
        self.errors = None

    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

    def correct(self):
        """
        Corrects errors, but it hasn't been implemented yet
        """
        print("This hasn't been implemented yet!")
        return {"errors": self.errors, "actions": None}
Exemplo n.º 26
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))
Exemplo n.º 27
0
class QChemSCFErrorHandler(ErrorHandler):
    """
    QChem ErrorHandler class that addresses SCF non-convergence.
    """

    is_monitor = False

    def __init__(self,
                 input_file="mol.qin",
                 output_file="mol.qout",
                 rca_gdm_thresh=1.0E-3,
                 scf_max_cycles=200):
        """
        Initializes the error handler from a set of input and output files.

        Args:
            input_file (str): Name of the QChem input file.
            output_file (str): Name of the QChem output file.
            rca_gdm_thresh (float): The threshold for the prior scf algorithm.
                If last deltaE is larger than the threshold try RCA_DIIS
                first, else, try DIIS_GDM first.
            scf_max_cycles (int): The max iterations to set to fix SCF failure.
        """
        self.input_file = input_file
        self.output_file = output_file
        self.scf_max_cycles = scf_max_cycles
        self.geom_max_cycles = geom_max_cycles
        self.qcinp = QCInput.from_file(self.input_file)
        self.outdata = None
        self.errors = None
        self.qchem_job = qchem_job

    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

    def correct(self):
        print("This hasn't been implemented yet!")
        return {"errors": self.errors, "actions": None}
Exemplo n.º 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"))
Exemplo n.º 29
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
Exemplo n.º 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,
                                     **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)
Exemplo n.º 31
0
class QChemErrorHandler(ErrorHandler):
    """
    Master QChemErrorHandler class that handles a number of common errors
    that occur during QChem runs.
    """

    is_monitor = False

    def __init__(self,
                 input_file="mol.qin",
                 output_file="mol.qout",
                 scf_max_cycles=200,
                 geom_max_cycles=200):
        """
        Initializes the error handler from a set of input and output files.

        Args:
            input_file (str): Name of the QChem input file.
            output_file (str): Name of the QChem output file.
            scf_max_cycles (int): The max iterations to set to fix SCF failure.
            geom_max_cycles (int): The max iterations to set to fix geometry
                optimization failure.
        """
        self.input_file = input_file
        self.output_file = output_file
        self.scf_max_cycles = scf_max_cycles
        self.geom_max_cycles = geom_max_cycles
        self.outdata = None
        self.errors = []
        self.opt_error_history = []

    def check(self):
        # Checks output file for errors.
        self.outdata = QCOutput(self.output_file).data
        self.errors = self.outdata.get("errors")
        # 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

    def correct(self):
        backup({self.input_file, self.output_file})
        actions = []
        self.qcinp = QCInput.from_file(self.input_file)

        if "SCF_failed_to_converge" in self.errors:
            # Check number of SCF cycles. If not set or less than scf_max_cycles,
            # increase to that value and rerun. If already set, check if
            # scf_algorithm is unset or set to DIIS, in which case set to GDM.
            # Otherwise, tell user to call SCF error handler and do nothing.
            if str(self.qcinp.rem.get("max_scf_cycles")) != str(
                    self.scf_max_cycles):
                self.qcinp.rem["max_scf_cycles"] = self.scf_max_cycles
                actions.append({"max_scf_cycles": self.scf_max_cycles})
            elif self.qcinp.rem.get("scf_algorithm", "diis").lower() == "diis":
                self.qcinp.rem["scf_algorithm"] = "gdm"
                actions.append({"scf_algorithm": "gdm"})
            elif self.qcinp.rem.get("scf_algorithm", "gdm").lower() == "gdm":
                self.qcinp.rem["scf_algorithm"] = "diis_gdm"
                actions.append({"scf_algorithm": "diis_gdm"})
            else:
                print(
                    "More advanced changes may impact the SCF result. Use the SCF error handler"
                )

        elif "out_of_opt_cycles" in self.errors:
            # Check number of opt cycles. If less than geom_max_cycles, increase
            # to that value, set last geom as new starting geom and rerun.
            if str(self.qcinp.rem.get(
                    "geom_opt_max_cycles")) != str(self.geom_max_cycles):
                self.qcinp.rem["geom_opt_max_cycles"] = self.geom_max_cycles
                actions.append({"geom_max_cycles:": self.scf_max_cycles})
                if len(self.outdata.get("energy_trajectory")) > 1:
                    self.qcinp.molecule = self.outdata.get(
                        "molecule_from_last_geometry")
                    actions.append({"molecule": "molecule_from_last_geometry"})
            # If already at geom_max_cycles, often can just get convergence by restarting
            # from the geometry of the last cycle. But we'll also save any structural
            # changes that happened along the way.
            else:
                self.opt_error_history += [self.outdata["structure_change"]]
                if len(self.opt_error_history) > 1:
                    if self.opt_error_history[-1] == "no_change":
                        # If no structural changes occured in two consecutive optimizations,
                        # and we still haven't converged, then just exit.
                        return {"errors": self.errors, "actions": None, "opt_error_history": self.opt_error_history}
                self.qcinp.molecule = self.outdata.get("molecule_from_last_geometry")
                actions.append({"molecule": "molecule_from_last_geometry"})

        elif "unable_to_determine_lamda" in self.errors:
            # Set last geom as new starting geom and rerun. If no opt cycles,
            # use diff SCF strat? Diff initial guess? Change basis?
            if len(self.outdata.get("energy_trajectory")) > 1:
                self.qcinp.molecule = self.outdata.get(
                    "molecule_from_last_geometry")
                actions.append({"molecule": "molecule_from_last_geometry"})
            elif self.qcinp.rem.get("scf_algorithm", "diis").lower() == "diis":
                self.qcinp.rem["scf_algorithm"] = "rca_diis"
                actions.append({"scf_algorithm": "rca_diis"})
                if self.qcinp.rem.get("gen_scfman"):
                    self.qcinp.rem["gen_scfman"] = False
                    actions.append({"gen_scfman": False})
            else:
                print(
                    "Use a different initial guess? Perhaps a different basis?"
                )

        elif "linear_dependent_basis" in self.errors:
            # DIIS -> RCA_DIIS. If already RCA_DIIS, change basis?
            if self.qcinp.rem.get("scf_algorithm", "diis").lower() == "diis":
                self.qcinp.rem["scf_algorithm"] = "rca_diis"
                actions.append({"scf_algorithm": "rca_diis"})
                if self.qcinp.rem.get("gen_scfman"):
                    self.qcinp.rem["gen_scfman"] = False
                    actions.append({"gen_scfman": False})
            else:
                print("Perhaps use a better basis?")

        elif "failed_to_transform_coords" in self.errors:
            # Check for symmetry flag in rem. If not False, set to False and rerun.
            # If already False, increase threshold?
            if not self.qcinp.rem.get("sym_ignore") or self.qcinp.rem.get(
                    "symmetry"):
                self.qcinp.rem["sym_ignore"] = True
                self.qcinp.rem["symmetry"] = False
                actions.append({"sym_ignore": True})
                actions.append({"symmetry": False})
            else:
                print("Perhaps increase the threshold?")

        elif "input_file_error" in self.errors:
            print(
                "Something is wrong with the input file. Examine error message by hand."
            )
            return {"errors": self.errors, "actions": None}

        elif "failed_to_read_input" in self.errors:
            # Almost certainly just a temporary problem that will not be encountered again. Rerun job as-is.
            actions.append({"rerun job as-is"})

        elif "IO_error" in self.errors:
            # Almost certainly just a temporary problem that will not be encountered again. Rerun job as-is.
            actions.append({"rerun job as-is"})

        elif "read_molecule_error" in self.errors:
            # Almost certainly just a temporary problem that will not be encountered again. Rerun job as-is.
            actions.append({"rerun job as-is"})

        elif "never_called_qchem" in self.errors:
            # Almost certainly just a temporary problem that will not be encountered again. Rerun job as-is.
            actions.append({"rerun job as-is"})

        elif "unknown_error" in self.errors:
            print("Examine error message by hand.")
            return {"errors": self.errors, "actions": None}

        else:
            # You should never get here. If correct is being called then errors should have at least one entry,
            # in which case it should have been caught by the if/elifs above.
            print(
                "If you get this message, something has gone terribly wrong!")
            return {"errors": self.errors, "actions": None}

        os.rename(self.input_file, self.input_file + ".last")
        self.qcinp.write_file(self.input_file)
        return {"errors": self.errors, "actions": actions}
Exemplo n.º 32
0
class QChemErrorHandler(ErrorHandler):
    """
    Master QChemErrorHandler class that handles a number of common errors
    that occur during QChem runs.
    """

    is_monitor = False

    def __init__(
        self,
        input_file="mol.qin",
        output_file="mol.qout",
        scf_max_cycles=200,
        geom_max_cycles=200,
    ):
        """
        Initializes the error handler from a set of input and output files.

        Args:
            input_file (str): Name of the QChem input file.
            output_file (str): Name of the QChem output file.
            scf_max_cycles (int): The max iterations to set to fix SCF failure.
            geom_max_cycles (int): The max iterations to set to fix geometry
                optimization failure.
        """
        self.input_file = input_file
        self.output_file = output_file
        self.scf_max_cycles = scf_max_cycles
        self.geom_max_cycles = geom_max_cycles
        self.outdata = None
        self.errors = []
        self.opt_error_history = []

    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

    def correct(self):
        """
        Perform corrections
        """
        backup({self.input_file, self.output_file})
        actions = []
        self.qcinp = QCInput.from_file(self.input_file)

        if "SCF_failed_to_converge" in self.errors:
            # Check number of SCF cycles. If not set or less than scf_max_cycles,
            # increase to that value and rerun. If already set, check if
            # scf_algorithm is unset or set to DIIS, in which case set to GDM.
            # Otherwise, tell user to call SCF error handler and do nothing.
            if str(self.qcinp.rem.get("max_scf_cycles")) != str(
                    self.scf_max_cycles):
                self.qcinp.rem["max_scf_cycles"] = self.scf_max_cycles
                actions.append({"max_scf_cycles": self.scf_max_cycles})
            elif self.qcinp.rem.get("thresh", "10") != "14":
                self.qcinp.rem["thresh"] = "14"
                actions.append({"thresh": "14"})
            elif self.qcinp.rem.get("scf_algorithm", "diis").lower() == "diis":
                self.qcinp.rem["scf_algorithm"] = "diis_gdm"
                actions.append({"scf_algorithm": "diis_gdm"})
            elif self.qcinp.rem.get("scf_algorithm",
                                    "diis").lower() == "diis_gdm":
                self.qcinp.rem["scf_algorithm"] = "gdm"
                actions.append({"scf_algorithm": "gdm"})
            elif self.qcinp.rem.get("scf_guess_always",
                                    "none").lower() != "true":
                self.qcinp.rem["scf_guess_always"] = True
                actions.append({"scf_guess_always": True})
            else:
                print(
                    "More advanced changes may impact the SCF result. Use the SCF error handler"
                )

        elif "out_of_opt_cycles" in self.errors:
            # Check number of opt cycles. If less than geom_max_cycles, increase
            # to that value, set last geom as new starting geom and rerun.
            if str(self.qcinp.rem.get("geom_opt_max_cycles")) != str(
                    self.geom_max_cycles):
                self.qcinp.rem["geom_opt_max_cycles"] = self.geom_max_cycles
                actions.append({"geom_max_cycles:": self.scf_max_cycles})
                if len(self.outdata.get("energy_trajectory")) > 1:
                    self.qcinp.molecule = self.outdata.get(
                        "molecule_from_last_geometry")
                    actions.append({"molecule": "molecule_from_last_geometry"})
            elif self.qcinp.rem.get("thresh", "10") != "14":
                self.qcinp.rem["thresh"] = "14"
                actions.append({"thresh": "14"})
            # Will need to try and implement this dmax handler below when I have more time
            # to fix the tests and the general handling procedure.
            # elif self.qcinp.rem.get("geom_opt_dmax",300) != 150:
            #     self.qcinp.rem["geom_opt_dmax"] = 150
            #     actions.append({"geom_opt_dmax": "150"})
            # If already at geom_max_cycles, thresh 14, and dmax 150, often can just get convergence
            # by restarting from the geometry of the last cycle. But we'll also save any structural
            # changes that happened along the way.
            else:
                self.opt_error_history += [self.outdata["structure_change"]]
                if len(self.opt_error_history) > 1:
                    if self.opt_error_history[-1] == "no_change":
                        # If no structural changes occurred in two consecutive optimizations,
                        # and we still haven't converged, then just exit.
                        return {
                            "errors": self.errors,
                            "actions": None,
                            "opt_error_history": self.opt_error_history,
                        }
                self.qcinp.molecule = self.outdata.get(
                    "molecule_from_last_geometry")
                actions.append({"molecule": "molecule_from_last_geometry"})

        elif "unable_to_determine_lamda" in self.errors:
            # Set last geom as new starting geom and rerun. If no opt cycles,
            # use diff SCF start? Diff initial guess? Change basis? Unclear.
            if len(self.outdata.get("energy_trajectory")) > 1:
                self.qcinp.molecule = self.outdata.get(
                    "molecule_from_last_geometry")
                actions.append({"molecule": "molecule_from_last_geometry"})
            elif self.qcinp.rem.get("thresh", "10") != "14":
                self.qcinp.rem["thresh"] = "14"
                actions.append({"thresh": "14"})
            else:
                print(
                    "Use a different initial guess? Perhaps a different basis?"
                )

        elif "premature_end_FileMan_error" in self.errors:
            if self.qcinp.rem.get("thresh", "10") != "14":
                self.qcinp.rem["thresh"] = "14"
                actions.append({"thresh": "14"})
            elif self.qcinp.rem.get("scf_guess_always",
                                    "none").lower() != "true":
                self.qcinp.rem["scf_guess_always"] = True
                actions.append({"scf_guess_always": True})
            else:
                print(
                    "We're in a bad spot if we get a FileMan error while always generating a new SCF guess..."
                )

        elif "hessian_eigenvalue_error" in self.errors:
            if self.qcinp.rem.get("thresh", "10") != "14":
                self.qcinp.rem["thresh"] = "14"
                actions.append({"thresh": "14"})
            else:
                print(
                    "Not sure how to fix hessian_eigenvalue_error if thresh is already 14!"
                )

        elif "NLebdevPts" in self.errors:
            # this error should only be possible if resp_charges or esp_charges is set
            if self.qcinp.rem.get("resp_charges") or self.qcinp.rem.get(
                    "esp_charges"):
                # This error is caused by insufficient no. of Lebedev points on
                # the grid used to compute RESP charges
                # Increase the density of points on the Lebedev grid using the
                # esp_surface_density argument (see manual >= v5.4)
                # the default value is 500 (=0.001 Angstrom)
                # or disable RESP charges as a last resort
                if int(self.qcinp.rem.get("esp_surface_density", 500)) >= 500:
                    self.qcinp.rem["esp_surface_density"] = "250"
                    actions.append({"esp_surface_density": "250"})
                elif int(self.qcinp.rem.get("esp_surface_density",
                                            250)) >= 250:
                    self.qcinp.rem["esp_surface_density"] = "125"
                    actions.append({"esp_surface_density": "125"})
                elif int(self.qcinp.rem.get("esp_surface_density",
                                            125)) >= 125:
                    # switch from Lebedev mode to spherical harmonics mode
                    if self.qcinp.rem.get("resp_charges"):
                        self.qcinp.rem["resp_charges"] = "2"
                        actions.append({"resp_charges": "2"})
                    if self.qcinp.rem.get("esp_charges"):
                        self.qcinp.rem["esp_charges"] = "2"
                        actions.append({"esp_charges": "2"})
                else:
                    if self.qcinp.rem.get("resp_charges"):
                        self.qcinp.rem["resp_charges"] = "false"
                        actions.append({"resp_charges": "false"})
                    if self.qcinp.rem.get("esp_charges"):
                        self.qcinp.rem["esp_charges"] = "false"
                        actions.append({"esp_charges": "false"})
            else:
                print(
                    "Not sure how to fix NLebdevPts error if resp_charges is disabled!"
                )

        elif "failed_to_transform_coords" in self.errors:
            # Check for symmetry flag in rem. If not False, set to False and rerun.
            # If already False, increase threshold?
            if not self.qcinp.rem.get("sym_ignore") or self.qcinp.rem.get(
                    "symmetry"):
                self.qcinp.rem["sym_ignore"] = True
                self.qcinp.rem["symmetry"] = False
                actions.append({"sym_ignore": True})
                actions.append({"symmetry": False})
            else:
                print("Perhaps increase the threshold?")

        elif "basis_not_supported" in self.errors:
            print(
                "Specify a different basis set. At least one of the atoms is not supported."
            )
            return {"errors": self.errors, "actions": None}

        elif "input_file_error" in self.errors:
            print(
                "Something is wrong with the input file. Examine error message by hand."
            )
            return {"errors": self.errors, "actions": None}

        elif "failed_to_read_input" in self.errors:
            # Almost certainly just a temporary problem that will not be encountered again. Rerun job as-is.
            actions.append({"rerun_job_no_changes": True})

        elif "read_molecule_error" in self.errors:
            # Almost certainly just a temporary problem that will not be encountered again. Rerun job as-is.
            actions.append({"rerun_job_no_changes": True})

        elif "never_called_qchem" in self.errors:
            # Almost certainly just a temporary problem that will not be encountered again. Rerun job as-is.
            actions.append({"rerun_job_no_changes": True})

        elif "licensing_error" in self.errors:
            # Almost certainly just a temporary problem that will not be encountered again. Rerun job as-is.
            actions.append({"rerun_job_no_changes": True})

        elif "unknown_error" in self.errors:
            if self.qcinp.rem.get("scf_guess", "none").lower() == "read":
                del self.qcinp.rem["scf_guess"]
                actions.append({"scf_guess": "deleted"})
            elif self.qcinp.rem.get("thresh", "10") != "14":
                self.qcinp.rem["thresh"] = "14"
                actions.append({"thresh": "14"})
            else:
                print("Unknown error. Examine output and log files by hand.")
                return {"errors": self.errors, "actions": None}

        else:
            # You should never get here. If correct is being called then errors should have at least one entry,
            # in which case it should have been caught by the if/elifs above.
            print("Errors:", self.errors)
            print(
                "Must have gotten an error which is correctly parsed but not included in the handler. FIX!!!"
            )
            return {"errors": self.errors, "actions": None}

        if {
                "molecule": "molecule_from_last_geometry"
        } in actions and str(
                self.qcinp.rem.get("geom_opt_hessian")).lower() == "read":
            del self.qcinp.rem["geom_opt_hessian"]
            actions.append({"geom_opt_hessian": "deleted"})
        os.rename(self.input_file, self.input_file + ".last")
        self.qcinp.write_file(self.input_file)
        return {
            "errors": self.errors,
            "warnings": self.warnings,
            "actions": actions
        }
Exemplo n.º 33
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,
        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)
Exemplo n.º 34
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)
Exemplo n.º 35
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)
Exemplo n.º 36
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)
Exemplo n.º 37
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)
Exemplo n.º 38
0
class QChemErrorHandler(ErrorHandler):
    """
    Master QChemErrorHandler class that handles a number of common errors
    that occur during QChem runs.
    """

    is_monitor = False

    def __init__(self,
                 input_file="mol.qin",
                 output_file="mol.qout",
                 scf_max_cycles=200,
                 geom_max_cycles=200):
        """
        Initializes the error handler from a set of input and output files.

        Args:
            input_file (str): Name of the QChem input file.
            output_file (str): Name of the QChem output file.
            scf_max_cycles (int): The max iterations to set to fix SCF failure.
            geom_max_cycles (int): The max iterations to set to fix geometry
                optimization failure.
        """
        self.input_file = input_file
        self.output_file = output_file
        self.scf_max_cycles = scf_max_cycles
        self.geom_max_cycles = geom_max_cycles
        self.outdata = None
        self.errors = []
        self.opt_error_history = []

    def check(self):
        # Checks output file for errors.
        self.outdata = QCOutput(self.output_file).data
        self.errors = self.outdata.get("errors")
        # 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

    def correct(self):
        backup({self.input_file, self.output_file})
        actions = []
        self.qcinp = QCInput.from_file(self.input_file)

        if "SCF_failed_to_converge" in self.errors:
            # Check number of SCF cycles. If not set or less than scf_max_cycles,
            # increase to that value and rerun. If already set, check if
            # scf_algorithm is unset or set to DIIS, in which case set to GDM.
            # Otherwise, tell user to call SCF error handler and do nothing.
            if str(self.qcinp.rem.get("max_scf_cycles")) != str(
                    self.scf_max_cycles):
                self.qcinp.rem["max_scf_cycles"] = self.scf_max_cycles
                actions.append({"max_scf_cycles": self.scf_max_cycles})
            elif self.qcinp.rem.get("scf_algorithm", "diis").lower() == "diis":
                self.qcinp.rem["scf_algorithm"] = "gdm"
                actions.append({"scf_algorithm": "gdm"})
            elif self.qcinp.rem.get("scf_algorithm", "gdm").lower() == "gdm":
                self.qcinp.rem["scf_algorithm"] = "diis_gdm"
                actions.append({"scf_algorithm": "diis_gdm"})
            else:
                print(
                    "More advanced changes may impact the SCF result. Use the SCF error handler"
                )

        elif "out_of_opt_cycles" in self.errors:
            # Check number of opt cycles. If less than geom_max_cycles, increase
            # to that value, set last geom as new starting geom and rerun.
            if str(self.qcinp.rem.get("geom_opt_max_cycles")) != str(
                    self.geom_max_cycles):
                self.qcinp.rem["geom_opt_max_cycles"] = self.geom_max_cycles
                actions.append({"geom_max_cycles:": self.scf_max_cycles})
                if len(self.outdata.get("energy_trajectory")) > 1:
                    self.qcinp.molecule = self.outdata.get(
                        "molecule_from_last_geometry")
                    actions.append({"molecule": "molecule_from_last_geometry"})
            # If already at geom_max_cycles, often can just get convergence by restarting
            # from the geometry of the last cycle. But we'll also save any structural
            # changes that happened along the way.
            else:
                self.opt_error_history += [self.outdata["structure_change"]]
                if len(self.opt_error_history) > 1:
                    if self.opt_error_history[-1] == "no_change":
                        # If no structural changes occured in two consecutive optimizations,
                        # and we still haven't converged, then just exit.
                        return {
                            "errors": self.errors,
                            "actions": None,
                            "opt_error_history": self.opt_error_history
                        }
                self.qcinp.molecule = self.outdata.get(
                    "molecule_from_last_geometry")
                actions.append({"molecule": "molecule_from_last_geometry"})

        elif "unable_to_determine_lamda" in self.errors:
            # Set last geom as new starting geom and rerun. If no opt cycles,
            # use diff SCF strat? Diff initial guess? Change basis?
            if len(self.outdata.get("energy_trajectory")) > 1:
                self.qcinp.molecule = self.outdata.get(
                    "molecule_from_last_geometry")
                actions.append({"molecule": "molecule_from_last_geometry"})
            elif self.qcinp.rem.get("scf_algorithm", "diis").lower() == "diis":
                self.qcinp.rem["scf_algorithm"] = "rca_diis"
                actions.append({"scf_algorithm": "rca_diis"})
                if self.qcinp.rem.get("gen_scfman"):
                    self.qcinp.rem["gen_scfman"] = False
                    actions.append({"gen_scfman": False})
            else:
                print(
                    "Use a different initial guess? Perhaps a different basis?"
                )

        elif "linear_dependent_basis" in self.errors:
            # DIIS -> RCA_DIIS. If already RCA_DIIS, change basis?
            if self.qcinp.rem.get("scf_algorithm", "diis").lower() == "diis":
                self.qcinp.rem["scf_algorithm"] = "rca_diis"
                actions.append({"scf_algorithm": "rca_diis"})
                if self.qcinp.rem.get("gen_scfman"):
                    self.qcinp.rem["gen_scfman"] = False
                    actions.append({"gen_scfman": False})
            else:
                print("Perhaps use a better basis?")

        elif "failed_to_transform_coords" in self.errors:
            # Check for symmetry flag in rem. If not False, set to False and rerun.
            # If already False, increase threshold?
            if not self.qcinp.rem.get("sym_ignore") or self.qcinp.rem.get(
                    "symmetry"):
                self.qcinp.rem["sym_ignore"] = True
                self.qcinp.rem["symmetry"] = False
                actions.append({"sym_ignore": True})
                actions.append({"symmetry": False})
            else:
                print("Perhaps increase the threshold?")

        elif "input_file_error" in self.errors:
            print(
                "Something is wrong with the input file. Examine error message by hand."
            )
            return {"errors": self.errors, "actions": None}

        elif "failed_to_read_input" in self.errors:
            # Almost certainly just a temporary problem that will not be encountered again. Rerun job as-is.
            actions.append({"rerun job as-is"})

        elif "IO_error" in self.errors:
            # Almost certainly just a temporary problem that will not be encountered again. Rerun job as-is.
            actions.append({"rerun job as-is"})

        elif "read_molecule_error" in self.errors:
            # Almost certainly just a temporary problem that will not be encountered again. Rerun job as-is.
            actions.append({"rerun job as-is"})

        elif "never_called_qchem" in self.errors:
            # Almost certainly just a temporary problem that will not be encountered again. Rerun job as-is.
            actions.append({"rerun job as-is"})

        elif "unknown_error" in self.errors:
            print("Examine error message by hand.")
            return {"errors": self.errors, "actions": None}

        else:
            # You should never get here. If correct is being called then errors should have at least one entry,
            # in which case it should have been caught by the if/elifs above.
            print(
                "If you get this message, something has gone terribly wrong!")
            return {"errors": self.errors, "actions": None}

        os.rename(self.input_file, self.input_file + ".last")
        self.qcinp.write_file(self.input_file)
        return {"errors": self.errors, "actions": actions}
Exemplo n.º 39
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)