def _populate_driver_result_metadata( self, driver_result: ElectronicStructureDriverResult) -> None: # pylint: disable=import-error from pyscf import __version__ as pyscf_version cfg = [ f"atom={self._atom}", f"unit={self._unit.value}", f"charge={self._charge}", f"spin={self._spin}", f"basis={self._basis}", f"method={self.method.value}", f"conv_tol={self._conv_tol}", f"max_cycle={self._max_cycle}", f"init_guess={self._init_guess}", f"max_memory={self._max_memory}", ] if self.method.value.lower() in ("rks", "roks", "uks"): cfg.extend([ f"xc_functional={self._xc_functional}", f"xcf_library={self._xcf_library}", ]) driver_result.add_property( DriverMetadata("PYSCF", pyscf_version, "\n".join(cfg + [""])))
def dump( driver_result: ElectronicStructureDriverResult, outpath: str, orbsym: Optional[List[str]] = None, isym: int = 1, ) -> None: """Convenience method to produce an FCIDump output file. Args: outpath: Path to the output file. driver_result: The ElectronicStructureDriverResult to be dumped. It is assumed that the nuclear_repulsion_energy contains the inactive core energy in its ElectronicEnergy property. orbsym: A list of spatial symmetries of the orbitals. isym: The spatial symmetry of the wave function. """ particle_number = cast(ParticleNumber, driver_result.get_property(ParticleNumber)) electronic_energy = cast(ElectronicEnergy, driver_result.get_property(ElectronicEnergy)) one_body_integrals = electronic_energy.get_electronic_integral(ElectronicBasis.MO, 1) two_body_integrals = electronic_energy.get_electronic_integral(ElectronicBasis.MO, 2) dump( outpath, particle_number.num_spin_orbitals // 2, particle_number.num_alpha + particle_number.num_beta, one_body_integrals._matrices, # type: ignore two_body_integrals._matrices[0:3], # type: ignore electronic_energy.nuclear_repulsion_energy, ms2=driver_result.molecule.multiplicity - 1, orbsym=orbsym, isym=isym, )
def _populate_driver_result_basis_transform( self, driver_result: ElectronicStructureDriverResult) -> None: # pylint: disable=import-error from pyscf.tools import dump_mat mo_coeff, mo_coeff_b = self._extract_mo_data("mo_coeff", array_dimension=3) if logger.isEnabledFor(logging.DEBUG): # Add some more to PySCF output... # First analyze() which prints extra information about MO energy and occupation self._mol.stdout.write("\n") self._calc.analyze() # Now labelled orbitals for contributions to the MOs for s,p,d etc of each atom self._mol.stdout.write("\n\n--- Alpha Molecular Orbitals ---\n\n") dump_mat.dump_mo(self._mol, mo_coeff, digits=7, start=1) if mo_coeff_b is not None: self._mol.stdout.write("\n--- Beta Molecular Orbitals ---\n\n") dump_mat.dump_mo(self._mol, mo_coeff_b, digits=7, start=1) self._mol.stdout.flush() driver_result.add_property( ElectronicBasisTransform( ElectronicBasis.AO, ElectronicBasis.MO, mo_coeff, mo_coeff_b, ))
def _populate_driver_result_particle_number( self, driver_result: ElectronicStructureDriverResult) -> None: driver_result.add_property( ParticleNumber( num_spin_orbitals=self._nmo * 2, num_particles=(self._mol.nup(), self._mol.ndown()), ))
def _populate_driver_result_particle_number( self, driver_result: ElectronicStructureDriverResult) -> None: mo_occ, mo_occ_b = self._extract_mo_data("mo_occ") driver_result.add_property( ParticleNumber( num_spin_orbitals=self._mol.nao * 2, num_particles=(self._mol.nelec[0], self._mol.nelec[1]), occupation=mo_occ, occupation_beta=mo_occ_b, ))
def run(self) -> ElectronicStructureDriverResult: atoms = self._atoms charge = self._charge multiplicity = self._multiplicity units = self._units basis = self.basis method = self.method q_mol = compute_integrals( atoms=atoms, units=units.value, charge=charge, multiplicity=multiplicity, basis=basis.value, method=method.value, tol=self._tol, maxiters=self._maxiters, ) q_mol.origin_driver_name = "PYQUANTE" cfg = [ f"atoms={atoms}", f"units={units.value}", f"charge={charge}", f"multiplicity={multiplicity}", f"basis={basis.value}", f"method={method.value}", f"tol={self._tol}", f"maxiters={self._maxiters}", "", ] q_mol.origin_driver_config = "\n".join(cfg) return ElectronicStructureDriverResult.from_legacy_driver_result(q_mol)
def run(self) -> ElectronicStructureDriverResult: cfg = self._config while not cfg.endswith("\n\n"): cfg += "\n" logger.debug( "User supplied configuration raw: '%s'", cfg.replace("\r", "\\r").replace("\n", "\\n"), ) logger.debug("User supplied configuration\n%s", cfg) # To the Gaussian section of the input file passed here as section string # add line '# Symm=NoInt output=(matrix,i4labels,mo2el) tran=full' # NB: Line above needs to be added in right context, i.e after any lines # beginning with % along with any others that start with # # append at end the name of the MatrixElement file to be written file, fname = tempfile.mkstemp(suffix=".mat") os.close(file) cfg = GaussianDriver._augment_config(fname, cfg) logger.debug("Augmented control information:\n%s", cfg) run_g16(cfg) q_mol = GaussianDriver._parse_matrix_file(fname) try: os.remove(fname) except Exception: # pylint: disable=broad-except logger.warning("Failed to remove MatrixElement file %s", fname) q_mol.origin_driver_name = "GAUSSIAN" q_mol.origin_driver_config = cfg return ElectronicStructureDriverResult.from_legacy_driver_result(q_mol)
def convert(self, replace: bool = False) -> None: """Converts a legacy QMolecule HDF5 file into the new Property-framework. Args: replace: if True, will replace the original HDF5 file. Otherwise `_new.hdf5` will be used as a suffix. Raises: LookupError: file not found. """ hdf5_file = self._get_path() warnings.filterwarnings("ignore", category=DeprecationWarning) q_mol = QMolecule(hdf5_file) warnings.filterwarnings("default", category=DeprecationWarning) q_mol.load() new_hdf5_file = hdf5_file if not replace: new_hdf5_file = hdf5_file.with_name( str(hdf5_file.stem) + "_new.hdf5") warnings.filterwarnings("ignore", category=DeprecationWarning) driver_result = ElectronicStructureDriverResult.from_legacy_driver_result( q_mol) warnings.filterwarnings("default", category=DeprecationWarning) save_to_hdf5(driver_result, str(new_hdf5_file), replace=replace)
def _populate_driver_result_metadata( self, driver_result: ElectronicStructureDriverResult) -> None: cfg = [ f"atoms={self.atoms}", f"units={self.units.value}", f"charge={self.charge}", f"multiplicity={self.multiplicity}", f"basis={self.basis.value}", f"method={self.method.value}", f"tol={self._tol}", f"maxiters={self._maxiters}", "", ] driver_result.add_property( DriverMetadata("PYQUANTE", "?", "\n".join(cfg)))
def _populate_driver_result_electronic_energy( self, driver_result: ElectronicStructureDriverResult) -> None: # pylint: disable=import-error from pyquante2 import onee_integrals from pyquante2.ints.integrals import twoe_integrals basis_transform = driver_result.get_property(ElectronicBasisTransform) integrals = onee_integrals(self._bfs, self._mol) hij = integrals.T + integrals.V hijkl = twoe_integrals(self._bfs) one_body_ao = OneBodyElectronicIntegrals(ElectronicBasis.AO, (hij, None)) two_body_ao = TwoBodyElectronicIntegrals( ElectronicBasis.AO, (hijkl.transform(np.identity(self._nmo)), None, None, None), ) one_body_mo = one_body_ao.transform_basis(basis_transform) two_body_mo = two_body_ao.transform_basis(basis_transform) electronic_energy = ElectronicEnergy( [one_body_ao, two_body_ao, one_body_mo, two_body_mo], nuclear_repulsion_energy=self._mol.nuclear_repulsion(), reference_energy=self._calc.energy, ) if hasattr(self._calc, "orbe"): orbs_energy = self._calc.orbe orbs_energy_b = None else: orbs_energy = self._calc.orbea orbs_energy_b = self._calc.orbeb orbital_energies = ((orbs_energy, orbs_energy_b) if orbs_energy_b is not None else orbs_energy) electronic_energy.orbital_energies = np.asarray(orbital_energies) electronic_energy.kinetic = OneBodyElectronicIntegrals( ElectronicBasis.AO, (integrals.T, None)) electronic_energy.overlap = OneBodyElectronicIntegrals( ElectronicBasis.AO, (integrals.S, None)) driver_result.add_property(electronic_energy)
def _populate_driver_result_electronic_dipole_moment( self, driver_result: ElectronicStructureDriverResult) -> None: basis_transform = driver_result.get_property(ElectronicBasisTransform) self._mol.set_common_orig((0, 0, 0)) ao_dip = self._mol.intor_symmetric("int1e_r", comp=3) d_m = self._calc.make_rdm1(self._calc.mo_coeff, self._calc.mo_occ) if not (isinstance(d_m, np.ndarray) and d_m.ndim == 2): d_m = d_m[0] + d_m[1] elec_dip = np.negative(np.einsum("xij,ji->x", ao_dip, d_m).real) elec_dip = np.round(elec_dip, decimals=8) nucl_dip = np.einsum("i,ix->x", self._mol.atom_charges(), self._mol.atom_coords()) nucl_dip = np.round(nucl_dip, decimals=8) logger.info("HF Electronic dipole moment: %s", elec_dip) logger.info("Nuclear dipole moment: %s", nucl_dip) logger.info("Total dipole moment: %s", nucl_dip + elec_dip) x_dip_ints = OneBodyElectronicIntegrals(ElectronicBasis.AO, (ao_dip[0], None)) y_dip_ints = OneBodyElectronicIntegrals(ElectronicBasis.AO, (ao_dip[1], None)) z_dip_ints = OneBodyElectronicIntegrals(ElectronicBasis.AO, (ao_dip[2], None)) x_dipole = DipoleMoment( "x", [x_dip_ints, x_dip_ints.transform_basis(basis_transform)]) y_dipole = DipoleMoment( "y", [y_dip_ints, y_dip_ints.transform_basis(basis_transform)]) z_dipole = DipoleMoment( "z", [z_dip_ints, z_dip_ints.transform_basis(basis_transform)]) driver_result.add_property( ElectronicDipoleMoment( [x_dipole, y_dipole, z_dipole], nuclear_dipole_moment=nucl_dip, reverse_dipole_sign=True, ))
def _populate_driver_result_basis_transform( self, driver_result: ElectronicStructureDriverResult) -> None: if hasattr(self._calc, "orbs"): mo_coeff = self._calc.orbs mo_coeff_b = None else: mo_coeff = self._calc.orbsa mo_coeff_b = self._calc.orbsb self._nmo = len(mo_coeff) driver_result.add_property( ElectronicBasisTransform( ElectronicBasis.AO, ElectronicBasis.MO, mo_coeff, mo_coeff_b, ))
def second_q_ops(self) -> ListOrDictType[SecondQuantizedOp]: """Returns the second quantized operators associated with this Property. If the arguments are returned as a `list`, the operators are in the following order: the Hamiltonian operator, total particle number operator, total angular momentum operator, total magnetization operator, and (if available) x, y, z dipole operators. The actual return-type is determined by `qiskit_nature.settings.dict_aux_operators`. Returns: A `list` or `dict` of `SecondQuantizedOp` objects. """ driver_result = self.driver.run() if self._legacy_driver: self._molecule_data = cast(QMolecule, driver_result) self._grouped_property = ElectronicStructureDriverResult.from_legacy_driver_result( self._molecule_data) if self._legacy_transform: self._molecule_data_transformed = self._transform( self._molecule_data) self._grouped_property_transformed = ( ElectronicStructureDriverResult.from_legacy_driver_result( self._molecule_data_transformed)) else: if not self.transformers: # if no transformers are supplied, we can still provide # `molecule_data_transformed` as a copy of `molecule_data` self._molecule_data_transformed = self._molecule_data self._grouped_property_transformed = self._transform( self._grouped_property) else: self._grouped_property = driver_result self._grouped_property_transformed = self._transform( self._grouped_property) second_quantized_ops = self._grouped_property_transformed.second_q_ops( ) return second_quantized_ops
def _populate_driver_result_molecule( self, driver_result: ElectronicStructureDriverResult) -> None: geometry: List[Tuple[str, List[float]]] = [] for atom in self._mol.atoms: atuple = atom.atuple() geometry.append( (PERIODIC_TABLE[atuple[0]], [a * BOHR for a in atuple[1:]])) driver_result.molecule = Molecule(geometry, multiplicity=self._mol.multiplicity, charge=self._mol.charge)
def _populate_driver_result_electronic_energy( self, driver_result: ElectronicStructureDriverResult) -> None: # pylint: disable=import-error from pyscf import gto basis_transform = driver_result.get_property(ElectronicBasisTransform) one_body_ao = OneBodyElectronicIntegrals( ElectronicBasis.AO, (self._calc.get_hcore(), None), ) two_body_ao = TwoBodyElectronicIntegrals( ElectronicBasis.AO, (self._mol.intor("int2e", aosym=1), None, None, None), ) one_body_mo = one_body_ao.transform_basis(basis_transform) two_body_mo = two_body_ao.transform_basis(basis_transform) electronic_energy = ElectronicEnergy( [one_body_ao, two_body_ao, one_body_mo, two_body_mo], nuclear_repulsion_energy=gto.mole.energy_nuc(self._mol), reference_energy=self._calc.e_tot, ) electronic_energy.kinetic = OneBodyElectronicIntegrals( ElectronicBasis.AO, (self._mol.intor_symmetric("int1e_kin"), None), ) electronic_energy.overlap = OneBodyElectronicIntegrals( ElectronicBasis.AO, (self._calc.get_ovlp(), None), ) orbs_energy, orbs_energy_b = self._extract_mo_data("mo_energy") orbital_energies = ((orbs_energy, orbs_energy_b) if orbs_energy_b is not None else orbs_energy) electronic_energy.orbital_energies = np.asarray(orbital_energies) driver_result.add_property(electronic_energy)
def _populate_driver_result_molecule( self, driver_result: ElectronicStructureDriverResult) -> None: coords = self._mol.atom_coords(unit="Angstrom") geometry = [(self._mol.atom_pure_symbol(i), list(xyz)) for i, xyz in enumerate(coords)] driver_result.molecule = Molecule( geometry, multiplicity=self._spin + 1, charge=self._charge, masses=list(self._mol.atom_mass_list()), )
def run(self) -> ElectronicStructureDriverResult: """Returns an ElectronicStructureDriverResult instance out of a FCIDump file.""" fcidump_data = parse(self._fcidump_input) hij = fcidump_data.get("hij", None) hij_b = fcidump_data.get("hij_b", None) hijkl = fcidump_data.get("hijkl", None) hijkl_ba = fcidump_data.get("hijkl_ba", None) hijkl_bb = fcidump_data.get("hijkl_bb", None) multiplicity = fcidump_data.get("MS2", 0) + 1 num_beta = (fcidump_data.get("NELEC") - (multiplicity - 1)) // 2 num_alpha = fcidump_data.get("NELEC") - num_beta particle_number = ParticleNumber( num_spin_orbitals=fcidump_data.get("NORB") * 2, num_particles=(num_alpha, num_beta), ) electronic_energy = ElectronicEnergy( [ OneBodyElectronicIntegrals(ElectronicBasis.MO, (hij, hij_b)), TwoBodyElectronicIntegrals(ElectronicBasis.MO, (hijkl, hijkl_ba, hijkl_bb, None)), ], nuclear_repulsion_energy=fcidump_data.get("ecore", None), ) driver_result = ElectronicStructureDriverResult() driver_result.add_property(electronic_energy) driver_result.add_property(particle_number) return driver_result
def test_from_hdf5(self): """Test from_hdf5.""" with h5py.File( self.get_resource_path( "electronic_structure_driver_result.hdf5", "properties/second_quantization/electronic/resources", ), "r", ) as file: for group in file.values(): prop = ElectronicStructureDriverResult.from_hdf5(group) for inner_prop in iter(prop): expected = self.expected.get_property(type(inner_prop)) self.assertEqual(inner_prop, expected)
def setUp(self): super().setUp() hdf5_file = self.get_resource_path( "test_driver_hdf5_legacy.hdf5", "drivers/second_quantization/hdf5d") # Using QMolecule directly here to avoid the deprecation on HDF5Driver.run method # to be triggered and let it be handled on the method test_convert # Those deprecation messages are shown only once and this one could prevent # the test_convert one to show if called first. molecule = QMolecule(hdf5_file) molecule.load() warnings.filterwarnings("ignore", category=DeprecationWarning) self.driver_result = ElectronicStructureDriverResult.from_legacy_driver_result( molecule) warnings.filterwarnings("default", category=DeprecationWarning)
def setUp(self): super().setUp() self.good_check = GaussianDriver.check_installed GaussianDriver.check_installed = _check_installed # We can now create a driver without the installed (check valid) test failing # and create a qmolecule from the saved output matrix file. This will test the # parsing of it into the qmolecule is correct. g16 = GaussianDriver() matfile = self.get_resource_path( "test_driver_gaussian_from_mat.mat", "drivers/second_quantization/gaussiand") try: q_mol = g16._parse_matrix_file(matfile) self.driver_result = ElectronicStructureDriverResult.from_legacy_driver_result( q_mol) except QiskitNatureError: self.tearDown() self.skipTest("GAUSSIAN qcmatrixio not found")
def run(self) -> GroupedSecondQuantizedProperty: """ Returns: GroupedSecondQuantizedProperty re-constructed from the HDF5 file. Raises: LookupError: file not found. QiskitNatureError: if the HDF5 file did not contain a GroupedSecondQuantizedProperty. """ hdf5_file = self._get_path() legacy_hdf5_file = False with h5py.File(hdf5_file, "r") as file: if "origin_driver" in file.keys(): legacy_hdf5_file = True warn_deprecated( "0.4.0", DeprecatedType.METHOD, "HDF5Driver.run with legacy HDF5 file", additional_msg= (". Your HDF5 file contains the legacy QMolecule object! You should consider " "converting it to the new property framework. See also HDF5Driver.convert" ), ) if legacy_hdf5_file: warnings.filterwarnings("ignore", category=DeprecationWarning) try: molecule = QMolecule(hdf5_file) molecule.load() return ElectronicStructureDriverResult.from_legacy_driver_result( molecule) finally: warnings.filterwarnings("default", category=DeprecationWarning) driver_result = load_from_hdf5(str(hdf5_file)) if not isinstance(driver_result, GroupedSecondQuantizedProperty): raise QiskitNatureError( f"Expected a GroupedSecondQuantizedProperty but found a {type(driver_result)} " "object instead.") return driver_result
def _construct_driver_result(self) -> ElectronicStructureDriverResult: driver_result = ElectronicStructureDriverResult() self._populate_driver_result_molecule(driver_result) self._populate_driver_result_metadata(driver_result) self._populate_driver_result_basis_transform(driver_result) self._populate_driver_result_particle_number(driver_result) self._populate_driver_result_electronic_energy(driver_result) # TODO: once https://github.com/Qiskit/qiskit-nature/issues/312 is fixed we can stop adding # these properties by default. # if not settings.dict_aux_operators: driver_result.add_property(AngularMomentum(self._nmo * 2)) driver_result.add_property(Magnetization(self._nmo * 2)) return driver_result
def run(self) -> ElectronicStructureDriverResult: """ Returns: ElectronicStructureDriverResult re-constructed from the HDF5 file. Raises: LookupError: file not found. """ hdf5_file = self._hdf5_input if self.work_path is not None and not os.path.isabs(hdf5_file): hdf5_file = os.path.abspath(os.path.join(self.work_path, hdf5_file)) if not os.path.isfile(hdf5_file): raise LookupError(f"HDF5 file not found: {hdf5_file}") warnings.filterwarnings("ignore", category=DeprecationWarning) molecule = QMolecule(hdf5_file) warnings.filterwarnings("default", category=DeprecationWarning) molecule.load() return ElectronicStructureDriverResult.from_legacy_driver_result( molecule)
def _construct_driver_result(self) -> ElectronicStructureDriverResult: # NOTE: under Python 3.6, pylint appears to be unable to properly identify this case of # nested abstract classes (cf. https://github.com/Qiskit/qiskit-nature/runs/3245395353). # However, since the tests pass I am adding an exception for this specific case. # pylint: disable=abstract-class-instantiated driver_result = ElectronicStructureDriverResult() self._populate_driver_result_molecule(driver_result) self._populate_driver_result_metadata(driver_result) self._populate_driver_result_basis_transform(driver_result) self._populate_driver_result_particle_number(driver_result) self._populate_driver_result_electronic_energy(driver_result) self._populate_driver_result_electronic_dipole_moment(driver_result) # TODO: once https://github.com/Qiskit/qiskit-terra/issues/6772 is resolved, we no longer # _have_ to add these properties. However, until then the interpret method relies on indices # of the aux_operators which are incorrect if these properties are not added. driver_result.add_property(AngularMomentum(self._mol.nao * 2)) driver_result.add_property(Magnetization(self._mol.nao * 2)) return driver_result
def run(self) -> ElectronicStructureDriverResult: """Returns an ElectronicStructureDriverResult instance out of a FCIDump file.""" fcidump_data = parse(self._fcidump_input) hij = fcidump_data.get("hij", None) hij_b = fcidump_data.get("hij_b", None) hijkl = fcidump_data.get("hijkl", None) hijkl_ba = fcidump_data.get("hijkl_ba", None) hijkl_bb = fcidump_data.get("hijkl_bb", None) multiplicity = fcidump_data.get("MS2", 0) + 1 num_beta = (fcidump_data.get("NELEC") - (multiplicity - 1)) // 2 num_alpha = fcidump_data.get("NELEC") - num_beta particle_number = ParticleNumber( num_spin_orbitals=fcidump_data.get("NORB") * 2, num_particles=(num_alpha, num_beta), ) electronic_energy = ElectronicEnergy( [ OneBodyElectronicIntegrals(ElectronicBasis.MO, (hij, hij_b)), TwoBodyElectronicIntegrals(ElectronicBasis.MO, (hijkl, hijkl_ba, hijkl_bb, None)), ], nuclear_repulsion_energy=fcidump_data.get("ecore", None), ) # NOTE: under Python 3.6, pylint appears to be unable to properly identify this case of # nested abstract classes (cf. https://github.com/Qiskit/qiskit-nature/runs/3245395353). # However, since the tests pass I am adding an exception for this specific case. # pylint: disable=abstract-class-instantiated driver_result = ElectronicStructureDriverResult() driver_result.add_property(electronic_energy) driver_result.add_property(particle_number) return driver_result
def test_tuple_num_electrons_with_manual_orbitals(self): """Regression test against https://github.com/Qiskit/qiskit-nature/issues/434.""" driver = HDF5Driver(hdf5_input=self.get_resource_path( "H2_631g.hdf5", "transformers/second_quantization/electronic")) driver_result = driver.run() trafo = ActiveSpaceTransformer( num_electrons=(1, 1), num_molecular_orbitals=2, active_orbitals=[0, 1], ) driver_result_reduced = trafo.transform(driver_result) expected = ElectronicStructureDriverResult() expected.add_property( ElectronicEnergy( [ OneBodyElectronicIntegrals( ElectronicBasis.MO, (np.asarray([[-1.24943841, 0.0], [0.0, -0.547816138] ]), None), ), TwoBodyElectronicIntegrals( ElectronicBasis.MO, ( np.asarray([ [ [[0.652098466, 0.0], [0.0, 0.433536565]], [[0.0, 0.0794483182], [0.0794483182, 0.0]], ], [ [[0.0, 0.0794483182], [0.0794483182, 0.0]], [[0.433536565, 0.0], [0.0, 0.385524695]], ], ]), None, None, None, ), ), ], energy_shift={"ActiveSpaceTransformer": 0.0}, )) expected.add_property( ElectronicDipoleMoment([ DipoleMoment( "x", [ OneBodyElectronicIntegrals(ElectronicBasis.MO, (np.zeros((2, 2)), None)) ], shift={"ActiveSpaceTransformer": 0.0}, ), DipoleMoment( "y", [ OneBodyElectronicIntegrals(ElectronicBasis.MO, (np.zeros((2, 2)), None)) ], shift={"ActiveSpaceTransformer": 0.0}, ), DipoleMoment( "z", [ OneBodyElectronicIntegrals( ElectronicBasis.MO, ( np.asarray([[0.69447435, -1.01418298], [-1.01418298, 0.69447435]]), None, ), ) ], shift={"ActiveSpaceTransformer": 0.0}, ), ])) self.assertDriverResult(driver_result_reduced, expected)
def transform( self, grouped_property: GroupedSecondQuantizedProperty ) -> GroupedElectronicProperty: """Reduces the given `GroupedElectronicProperty` to a given active space. Args: grouped_property: the `GroupedElectronicProperty` to be transformed. Returns: A new `GroupedElectronicProperty` instance. Raises: QiskitNatureError: If the provided `GroupedElectronicProperty` does not contain a `ParticleNumber` or `ElectronicBasisTransform` instance, if more electrons or orbitals are requested than are available, or if the number of selected active orbital indices does not match `num_molecular_orbitals`. """ if not isinstance(grouped_property, GroupedElectronicProperty): raise QiskitNatureError( "Only `GroupedElectronicProperty` objects can be transformed by this Transformer, " f"not objects of type, {type(grouped_property)}." ) particle_number = grouped_property.get_property(ParticleNumber) if particle_number is None: raise QiskitNatureError( "The provided `GroupedElectronicProperty` does not contain a `ParticleNumber` " "property, which is required by this transformer!" ) particle_number = cast(ParticleNumber, particle_number) electronic_basis_transform = grouped_property.get_property(ElectronicBasisTransform) if electronic_basis_transform is None: raise QiskitNatureError( "The provided `GroupedElectronicProperty` does not contain an " "`ElectronicBasisTransform` property, which is required by this transformer!" ) electronic_basis_transform = cast(ElectronicBasisTransform, electronic_basis_transform) # get molecular orbital occupation numbers occupation_alpha = particle_number.occupation_alpha occupation_beta = particle_number.occupation_beta self._mo_occ_total = occupation_alpha + occupation_beta # determine the active space self._active_orbs_indices, inactive_orbs_idxs = self._determine_active_space( grouped_property ) # get molecular orbital coefficients coeff_alpha = electronic_basis_transform.coeff_alpha coeff_beta = electronic_basis_transform.coeff_beta # initialize size-reducing basis transformation self._transform_active = ElectronicBasisTransform( ElectronicBasis.AO, ElectronicBasis.MO, coeff_alpha[:, self._active_orbs_indices], coeff_beta[:, self._active_orbs_indices], ) # compute inactive density matrix def _inactive_density(mo_occ, mo_coeff): return np.dot( mo_coeff[:, inactive_orbs_idxs] * mo_occ[inactive_orbs_idxs], np.transpose(mo_coeff[:, inactive_orbs_idxs]), ) self._density_inactive = OneBodyElectronicIntegrals( ElectronicBasis.AO, ( _inactive_density(occupation_alpha, coeff_alpha), _inactive_density(occupation_beta, coeff_beta), ), ) # construct new GroupedElectronicProperty grouped_property_transformed = ElectronicStructureDriverResult() grouped_property_transformed = self._transform_property(grouped_property) # type: ignore grouped_property_transformed.molecule = ( grouped_property.molecule # type: ignore[attr-defined] ) return grouped_property_transformed
def run(self) -> ElectronicStructureDriverResult: cfg = self._config psi4d_directory = Path(__file__).resolve().parent template_file = psi4d_directory.joinpath("_template.txt") qiskit_nature_directory = psi4d_directory.parent.parent warnings.filterwarnings("ignore", category=DeprecationWarning) molecule = QMolecule() warnings.filterwarnings("default", category=DeprecationWarning) input_text = [cfg] input_text += ["import sys"] syspath = ( "['" + qiskit_nature_directory.as_posix() + "','" + "','".join(Path(p).as_posix() for p in sys.path) + "']" ) input_text += ["sys.path = " + syspath + " + sys.path"] input_text += ["import warnings"] input_text += ["from qiskit_nature.drivers.qmolecule import QMolecule"] input_text += ["warnings.filterwarnings('ignore', category=DeprecationWarning)"] input_text += [f'_q_molecule = QMolecule("{Path(molecule.filename).as_posix()}")'] input_text += ["warnings.filterwarnings('default', category=DeprecationWarning)"] with open(template_file, "r", encoding="utf8") as file: input_text += [line.strip("\n") for line in file.readlines()] file_fd, input_file = tempfile.mkstemp(suffix=".inp") os.close(file_fd) with open(input_file, "w", encoding="utf8") as stream: stream.write("\n".join(input_text)) file_fd, output_file = tempfile.mkstemp(suffix=".out") os.close(file_fd) try: PSI4Driver._run_psi4(input_file, output_file) if logger.isEnabledFor(logging.DEBUG): with open(output_file, "r", encoding="utf8") as file: logger.debug("PSI4 output file:\n%s", file.read()) finally: run_directory = os.getcwd() for local_file in os.listdir(run_directory): if local_file.endswith(".clean"): os.remove(run_directory + "/" + local_file) try: os.remove("timer.dat") except Exception: # pylint: disable=broad-except pass try: os.remove(input_file) except Exception: # pylint: disable=broad-except pass try: os.remove(output_file) except Exception: # pylint: disable=broad-except pass warnings.filterwarnings("ignore", category=DeprecationWarning) _q_molecule = QMolecule(molecule.filename) warnings.filterwarnings("default", category=DeprecationWarning) _q_molecule.load() # remove internal file _q_molecule.remove_file() _q_molecule.origin_driver_name = "PSI4" _q_molecule.origin_driver_config = cfg return ElectronicStructureDriverResult.from_legacy_driver_result(_q_molecule)
def _parse_matrix_file( fname: str, useao2e: bool = False) -> ElectronicStructureDriverResult: """ get_driver_class is used here because the discovery routine will load all the gaussian binary dependencies, if not loaded already. It won't work without it. """ try: # add gauopen to sys.path so that binaries can be loaded gauopen_directory = os.path.join( os.path.dirname(os.path.realpath(__file__)), "gauopen") if gauopen_directory not in sys.path: sys.path.insert(0, gauopen_directory) # pylint: disable=import-outside-toplevel from .gauopen.QCMatEl import MatEl except ImportError as mnfe: msg = (( "qcmatrixio extension not found. " "See Gaussian driver readme to build qcmatrixio.F using f2py") if mnfe.name == "qcmatrixio" else str(mnfe)) logger.info(msg) raise QiskitNatureError(msg) from mnfe mel = MatEl(file=fname) logger.debug("MatrixElement file:\n%s", mel) driver_result = ElectronicStructureDriverResult() # molecule coords = np.reshape(mel.c, (len(mel.ian), 3)) geometry: list[tuple[str, list[float]]] = [] for atom, xyz in zip(mel.ian, coords): geometry.append((PERIODIC_TABLE[atom], BOHR * xyz)) driver_result.molecule = Molecule( geometry, multiplicity=mel.multip, charge=mel.icharg, ) # driver metadata driver_result.add_property(DriverMetadata("GAUSSIAN", mel.gversion, "")) # basis transform moc = GaussianDriver._get_matrix(mel, "ALPHA MO COEFFICIENTS") moc_b = GaussianDriver._get_matrix(mel, "BETA MO COEFFICIENTS") if np.array_equal(moc, moc_b): logger.debug( "ALPHA and BETA MO COEFFS identical, keeping only ALPHA") moc_b = None nmo = moc.shape[0] basis_transform = ElectronicBasisTransform(ElectronicBasis.AO, ElectronicBasis.MO, moc, moc_b) driver_result.add_property(basis_transform) # particle number num_alpha = (mel.ne + mel.multip - 1) // 2 num_beta = (mel.ne - mel.multip + 1) // 2 driver_result.add_property( ParticleNumber(num_spin_orbitals=nmo * 2, num_particles=(num_alpha, num_beta))) # electronic energy hcore = GaussianDriver._get_matrix(mel, "CORE HAMILTONIAN ALPHA") logger.debug("CORE HAMILTONIAN ALPHA %s", hcore.shape) hcore_b = GaussianDriver._get_matrix(mel, "CORE HAMILTONIAN BETA") if np.array_equal(hcore, hcore_b): # From Gaussian interfacing documentation: "The two core Hamiltonians are identical # unless a Fermi contact perturbation has been applied." logger.debug( "CORE HAMILTONIAN ALPHA and BETA identical, keeping only ALPHA" ) hcore_b = None logger.debug( "CORE HAMILTONIAN BETA %s", "- Not present" if hcore_b is None else hcore_b.shape, ) one_body_ao = OneBodyElectronicIntegrals(ElectronicBasis.AO, (hcore, hcore_b)) one_body_mo = one_body_ao.transform_basis(basis_transform) eri = GaussianDriver._get_matrix(mel, "REGULAR 2E INTEGRALS") logger.debug("REGULAR 2E INTEGRALS %s", eri.shape) if moc_b is None and mel.matlist.get("BB MO 2E INTEGRALS") is not None: # It seems that when using ROHF, where alpha and beta coeffs are # the same, that integrals # for BB and BA are included in the output, as well as just AA # that would have been expected # Using these fails to give the right answer (is ok for UHF). # So in this case we revert to # using 2 electron ints in atomic basis from the output and # converting them ourselves. useao2e = True logger.info( "Identical A and B coeffs but BB ints are present - using regular 2E ints instead" ) two_body_ao = TwoBodyElectronicIntegrals(ElectronicBasis.AO, (eri, None, None, None)) two_body_mo: TwoBodyElectronicIntegrals if useao2e: # eri are 2-body in AO. We can convert to MO via the ElectronicBasisTransform but using # ints in MO already, as in the else here, is better two_body_mo = two_body_ao.transform_basis(basis_transform) else: # These are in MO basis but by default will be reduced in size by frozen core default so # to use them we need to add Window=Full above when we augment the config mohijkl = GaussianDriver._get_matrix(mel, "AA MO 2E INTEGRALS") logger.debug("AA MO 2E INTEGRALS %s", mohijkl.shape) mohijkl_bb = GaussianDriver._get_matrix(mel, "BB MO 2E INTEGRALS") logger.debug( "BB MO 2E INTEGRALS %s", "- Not present" if mohijkl_bb is None else mohijkl_bb.shape, ) mohijkl_ba = GaussianDriver._get_matrix(mel, "BA MO 2E INTEGRALS") logger.debug( "BA MO 2E INTEGRALS %s", "- Not present" if mohijkl_ba is None else mohijkl_ba.shape, ) two_body_mo = TwoBodyElectronicIntegrals( ElectronicBasis.MO, (mohijkl, mohijkl_ba, mohijkl_bb, None)) electronic_energy = ElectronicEnergy( [one_body_ao, two_body_ao, one_body_mo, two_body_mo], nuclear_repulsion_energy=mel.scalar("ENUCREP"), reference_energy=mel.scalar("ETOTAL"), ) kinetic = GaussianDriver._get_matrix(mel, "KINETIC ENERGY") logger.debug("KINETIC ENERGY %s", kinetic.shape) electronic_energy.kinetic = OneBodyElectronicIntegrals( ElectronicBasis.AO, (kinetic, None)) overlap = GaussianDriver._get_matrix(mel, "OVERLAP") logger.debug("OVERLAP %s", overlap.shape) electronic_energy.overlap = OneBodyElectronicIntegrals( ElectronicBasis.AO, (overlap, None)) orbs_energy = GaussianDriver._get_matrix(mel, "ALPHA ORBITAL ENERGIES") logger.debug("ORBITAL ENERGIES %s", overlap.shape) orbs_energy_b = GaussianDriver._get_matrix(mel, "BETA ORBITAL ENERGIES") logger.debug("BETA ORBITAL ENERGIES %s", overlap.shape) orbital_energies = ( orbs_energy, orbs_energy_b) if moc_b is not None else orbs_energy electronic_energy.orbital_energies = np.asarray(orbital_energies) driver_result.add_property(electronic_energy) # dipole moment dipints = GaussianDriver._get_matrix(mel, "DIPOLE INTEGRALS") dipints = np.einsum("ijk->kji", dipints) x_dip_ints = OneBodyElectronicIntegrals(ElectronicBasis.AO, (dipints[0], None)) y_dip_ints = OneBodyElectronicIntegrals(ElectronicBasis.AO, (dipints[1], None)) z_dip_ints = OneBodyElectronicIntegrals(ElectronicBasis.AO, (dipints[2], None)) x_dipole = DipoleMoment( "x", [x_dip_ints, x_dip_ints.transform_basis(basis_transform)]) y_dipole = DipoleMoment( "y", [y_dip_ints, y_dip_ints.transform_basis(basis_transform)]) z_dipole = DipoleMoment( "z", [z_dip_ints, z_dip_ints.transform_basis(basis_transform)]) nucl_dip = np.einsum("i,ix->x", mel.ian, coords) nucl_dip = np.round(nucl_dip, decimals=8) driver_result.add_property( ElectronicDipoleMoment( [x_dipole, y_dipole, z_dipole], nuclear_dipole_moment=nucl_dip, reverse_dipole_sign=True, )) # extra properties # TODO: once https://github.com/Qiskit/qiskit-nature/issues/312 is fixed we can stop adding # these properties by default. # if not settings.dict_aux_operators: driver_result.add_property(AngularMomentum(nmo * 2)) driver_result.add_property(Magnetization(nmo * 2)) return driver_result
def test_arbitrary_active_orbitals(self): """Test manual selection of active orbital indices.""" driver = HDF5Driver(hdf5_input=self.get_resource_path( "H2_631g.hdf5", "transformers/second_quantization/electronic")) driver_result = driver.run() trafo = ActiveSpaceTransformer(num_electrons=2, num_molecular_orbitals=2, active_orbitals=[0, 2]) driver_result_reduced = trafo.transform(driver_result) expected = ElectronicStructureDriverResult() expected.add_property( ElectronicEnergy( [ OneBodyElectronicIntegrals( ElectronicBasis.MO, ( np.asarray([[-1.24943841, -0.16790838], [-0.16790838, -0.18307469]]), None, ), ), TwoBodyElectronicIntegrals( ElectronicBasis.MO, ( np.asarray([ [ [[0.65209847, 0.16790822], [0.16790822, 0.53250905]], [[0.16790822, 0.10962908], [0.10962908, 0.11981429]], ], [ [[0.16790822, 0.10962908], [0.10962908, 0.11981429]], [[0.53250905, 0.11981429], [0.11981429, 0.46345617]], ], ]), None, None, None, ), ), ], energy_shift={"ActiveSpaceTransformer": 0.0}, )) expected.add_property( ElectronicDipoleMoment([ DipoleMoment( "x", [ OneBodyElectronicIntegrals(ElectronicBasis.MO, (np.zeros((2, 2)), None)) ], shift={"ActiveSpaceTransformer": 0.0}, ), DipoleMoment( "y", [ OneBodyElectronicIntegrals(ElectronicBasis.MO, (np.zeros((2, 2)), None)) ], shift={"ActiveSpaceTransformer": 0.0}, ), DipoleMoment( "z", [ OneBodyElectronicIntegrals( ElectronicBasis.MO, (np.asarray([[0.69447435, 0.0], [0.0, 0.69447435] ]), None), ) ], shift={"ActiveSpaceTransformer": 0.0}, ), ])) self.assertDriverResult(driver_result_reduced, expected)