def _initialize_findif(mol, freq_irrep_only, mode, initialize_string, verbose=0): """Perform initialization tasks needed by all primary functions. Parameters ---------- mol : qcdb.molecule or psi4.core.Molecule The molecule to displace freq_irrep_only : int The Cotton ordered irrep to get frequencies for. Choose -1 for all irreps. mode : {"1_0", "2_0", "2_1"} The first number specifies the derivative level determined from displacements, and the second number is the level determined at. initialize_string : function A function that returns the string to print to show the caller was entered. The string is both caller-specific and dependent on values determined in this function. verbose : int Set to 0 to silence extra print information, regardless of the print level. Used so the information is printed only during geometry generation, and not during the derivative computation as well. :returns: *dict* Miscellaneous information required by callers. """ core.print_out( "\n ----------------------------------------------------------\n" ) core.print_out(" FINDIF\n") core.print_out(" R. A. King and Jonathon Misiewicz\n") core.print_out( " ---------------------------------------------------------\n\n" ) print_lvl = core.get_option("FINDIF", "PRINT") num_pts = core.get_option("FINDIF", "POINTS") disp_size = core.get_option("FINDIF", "DISP_SIZE") data = {"print_lvl": print_lvl, "num_pts": num_pts, "disp_size": disp_size} if print_lvl: core.print_out(initialize_string(data)) # Get settings for CdSalcList, then get the CdSalcList. method_allowed_irreps = 0x1 if mode == "1_0" else 0xFF t_project = not core.get_global_option("EXTERN") and ( not core.get_global_option("PERTURB_H")) # core.get_option returns an int, but CdSalcList expect a bool, so re-cast r_project = t_project and bool(core.get_option("FINDIF", "FD_PROJECT")) salc_list = core.CdSalcList(mol, method_allowed_irreps, t_project, r_project) n_atom = mol.natom() n_irrep = salc_list.nirrep() n_salc = salc_list.ncd() if print_lvl and verbose: core.print_out(" Number of atoms is {:d}.\n".format(n_atom)) if method_allowed_irreps != 0x1: core.print_out(" Number of irreps is {:d}.\n".format(n_irrep)) core.print_out(" Number of {!s}SALCs is {:d}.\n".format( "" if method_allowed_irreps != 0x1 else "symmetric ", n_salc)) core.print_out( " Translations projected? {:d}. Rotations projected? {:d}.\n". format(t_project, r_project)) # TODO: Replace with a generator from a stencil to a set of points. # Diagonal displacements differ between the totally symmetric irrep, compared to all others. # Off-diagonal displacements are the same for both. pts_dict = { 3: { "sym_irr": ((-1, ), (1, )), "asym_irr": ((-1, ), ), "off": ((1, 1), (-1, -1)) }, 5: { "sym_irr": ((-2, ), (-1, ), (1, ), (2, )), "asym_irr": ((-2, ), (-1, )), "off": ((-1, -2), (-2, -1), (-1, -1), (1, -1), (-1, 1), (1, 1), (2, 1), (1, 2)) } } if num_pts not in pts_dict: raise ValidationError("FINDIF: Invalid number of points!") # Convention: x_pi means x_per_irrep. The ith element is x for irrep i, with Cotton ordering. salc_indices_pi = [[] for h in range(n_irrep)] # Validate that we have an irrep matching the user-specified irrep, if any. try: salc_indices_pi[freq_irrep_only] except (TypeError, IndexError): if freq_irrep_only != -1: raise ValidationError("FINDIF: Irrep value not in valid range.") # Populate salc_indices_pi for all irreps. for i, salc in enumerate(salc_list): salc_indices_pi[salc.irrep_index()].append(i) # If the method allows more than one irrep, print how the irreps partition the SALCS. if print_lvl and method_allowed_irreps != 0x1 and verbose: core.print_out(" Index of SALCs per irrep:\n") for h in range(n_irrep): if print_lvl > 1 or freq_irrep_only in {h, -1}: tmp = (" {:d} " * len(salc_indices_pi[h])).format(*salc_indices_pi[h]) core.print_out(" {:d} : ".format(h + 1) + tmp + "\n") core.print_out(" Number of SALCs per irrep:\n") for h in range(n_irrep): if print_lvl > 1 or freq_irrep_only in {h, -1}: core.print_out(" Irrep {:d}: {:d}\n".format( h + 1, len(salc_indices_pi[h]))) # Now that we've printed the SALCs, clear any that are not of user-specified symmetry. if freq_irrep_only != -1: for h in range(n_irrep): if h != freq_irrep_only: salc_indices_pi[h].clear() n_disp_pi = [] disps = pts_dict[num_pts] # We previously validated num_pts in pts_dict. for irrep, indices in enumerate(salc_indices_pi): n_disp = len(indices) * len( disps["asym_irr" if irrep != 0 else "sym_irr"]) if mode == "2_0": # Either len(indices) or len(indices)-1 is even, so dividing by two is safe. n_disp += len(indices) * (len(indices) - 1) // 2 * len( disps["off"]) n_disp_pi.append(n_disp) # Let's print out the number of geometries, the displacement multiplicity, and the CdSALCs! if print_lvl and verbose: core.print_out( " Number of geometries (including reference) is {:d}.\n".format( sum(n_disp_pi) + 1)) if method_allowed_irreps != 0x1: core.print_out(" Number of displacements per irrep:\n") for i, ndisp in enumerate(n_disp_pi, start=1): core.print_out(" Irrep {:d}: {:d}\n".format(i, ndisp)) if print_lvl > 1 and verbose: for salc in salc_list: salc.print_out() data.update({ "n_disp_pi": n_disp_pi, "n_irrep": n_irrep, "n_salc": n_salc, "n_atom": n_atom, "salc_list": salc_list, "salc_indices_pi": salc_indices_pi, "disps": disps }) return data
def _initialize_findif(mol: Union["qcdb.Molecule", core.Molecule], freq_irrep_only: int, mode: str, stencil_size: int, step_size: float, initialize_string: Callable, t_project: bool, r_project: bool, initialize: bool, verbose: int = 0) -> Dict: """Perform initialization tasks needed by all primary functions. Parameters ---------- mol The molecule to displace freq_irrep_only The Cotton ordered irrep to get frequencies for. Choose -1 for all irreps. mode : {"1_0", "2_0", "2_1"} The first number specifies the derivative level determined from displacements, and the second number is the level determined at. stencil_size : {3, 5} Number of points to evaluate for each displacement basis vector inclusive of central reference geometry. step_size [a0] initialize_string A function that returns the string to print to show the caller was entered. The string is both caller-specific and dependent on values determined in this function. initialize For printing, whether call is from generator or assembly stages. verbose Set to 0 to silence extra print information, regardless of the print level. Used so the information is printed only during geometry generation, and not during the derivative computation as well. Returns ------- data Miscellaneous information required by callers. """ info = """ ---------------------------------------------------------- FINDIF R. A. King and Jonathon Misiewicz ---------------------------------------------------------- """ if initialize: core.print_out(info) logger.info(info) print_lvl = core.get_option("FINDIF", "PRINT") data = { "print_lvl": print_lvl, "stencil_size": stencil_size, "step_size": step_size } if print_lvl: info = initialize_string(data) core.print_out(info) logger.info(info) # Get settings for CdSalcList, then get the CdSalcList. method_allowed_irreps = 0x1 if mode == "1_0" else 0xFF # core.get_option returns an int, but CdSalcList expect a bool, so re-cast salc_list = core.CdSalcList(mol, method_allowed_irreps, t_project, r_project) n_atom = mol.natom() n_irrep = salc_list.nirrep() n_salc = salc_list.ncd() if print_lvl and verbose: info = f" Number of atoms is {n_atom}.\n" if method_allowed_irreps != 0x1: info += f" Number of irreps is {n_irrep}.\n" info += " Number of {!s}SALCs is {:d}.\n".format( "" if method_allowed_irreps != 0x1 else "symmetric ", n_salc) info += f" Translations projected? {t_project:d}. Rotations projected? {r_project:d}.\n" core.print_out(info) logger.info(info) # TODO: Replace with a generator from a stencil to a set of points. # Diagonal displacements differ between the totally symmetric irrep, compared to all others. # Off-diagonal displacements are the same for both. pts_dict = { 3: { "sym_irr": ((-1, ), (1, )), "asym_irr": ((-1, ), ), "off": ((1, 1), (-1, -1)) }, 5: { "sym_irr": ((-2, ), (-1, ), (1, ), (2, )), "asym_irr": ((-2, ), (-1, )), "off": ((-1, -2), (-2, -1), (-1, -1), (1, -1), (-1, 1), (1, 1), (2, 1), (1, 2)) } } try: disps = pts_dict[stencil_size] except KeyError: raise ValidationError( f"FINDIF: Number of points ({stencil_size}) not among {pts_dict.keys()}!" ) # Convention: x_pi means x_per_irrep. The ith element is x for irrep i, with Cotton ordering. salc_indices_pi = [[] for h in range(n_irrep)] # Validate that we have an irrep matching the user-specified irrep, if any. try: salc_indices_pi[freq_irrep_only] except (TypeError, IndexError): if freq_irrep_only != -1: raise ValidationError( f"FINDIF: 0-indexed Irrep value ({freq_irrep_only}) not in valid range: <{len(salc_indices_pi)}." ) # Populate salc_indices_pi for all irreps. # * Python error if iterate through `salc_list` for i in range(len(salc_list)): salc_indices_pi[salc_list[i].irrep_index()].append(i) # If the method allows more than one irrep, print how the irreps partition the SALCS. if print_lvl and method_allowed_irreps != 0x1 and verbose: info = " Index of SALCs per irrep:\n" for h in range(n_irrep): if print_lvl > 1 or freq_irrep_only in {h, -1}: tmp = (" {:d} " * len(salc_indices_pi[h])).format(*salc_indices_pi[h]) info += " {:d} : ".format(h + 1) + tmp + "\n" info += " Number of SALCs per irrep:\n" for h in range(n_irrep): if print_lvl > 1 or freq_irrep_only in {h, -1}: info += " Irrep {:d}: {:d}\n".format( h + 1, len(salc_indices_pi[h])) core.print_out(info) logger.info(info) # Now that we've printed the SALCs, clear any that are not of user-specified symmetry. if freq_irrep_only != -1: for h in range(n_irrep): if h != freq_irrep_only: salc_indices_pi[h].clear() n_disp_pi = [] for irrep, indices in enumerate(salc_indices_pi): n_disp = len(indices) * len( disps["asym_irr" if irrep != 0 else "sym_irr"]) if mode == "2_0": # Either len(indices) or len(indices)-1 is even, so dividing by two is safe. n_disp += len(indices) * (len(indices) - 1) // 2 * len( disps["off"]) n_disp_pi.append(n_disp) # Let's print out the number of geometries, the displacement multiplicity, and the CdSALCs! if print_lvl and verbose: info = f" Number of geometries (including reference) is {sum(n_disp_pi) + 1}.\n" if method_allowed_irreps != 0x1: info += " Number of displacements per irrep:\n" for i, ndisp in enumerate(n_disp_pi, start=1): info += f" Irrep {i}: {ndisp}\n" core.print_out(info) logger.info(info) if print_lvl > 1 and verbose: for i in range(len(salc_list)): salc_list[i].print_out() data.update({ "n_disp_pi": n_disp_pi, "n_irrep": n_irrep, "n_salc": n_salc, "n_atom": n_atom, "salc_list": salc_list, "salc_indices_pi": salc_indices_pi, "disps": disps, "project_translations": t_project, "project_rotations": r_project }) return data