From 7748f6dda457d29ec8d130989ee6a04dd1810ee5 Mon Sep 17 00:00:00 2001 From: "J. S. Kottmann" Date: Thu, 4 Apr 2024 22:02:53 +0200 Subject: [PATCH 1/6] orbital names are better tracked through transformations --- .../quantumchemistry/chemistry_tools.py | 26 ++++++++++++------- .../quantumchemistry/orbital_optimizer.py | 2 +- .../quantumchemistry/psi4_interface.py | 2 +- .../quantumchemistry/pyscf_interface.py | 1 + src/tequila/quantumchemistry/qc_base.py | 9 ++++--- 5 files changed, 24 insertions(+), 16 deletions(-) diff --git a/src/tequila/quantumchemistry/chemistry_tools.py b/src/tequila/quantumchemistry/chemistry_tools.py index ff1f3ff1..00a65738 100644 --- a/src/tequila/quantumchemistry/chemistry_tools.py +++ b/src/tequila/quantumchemistry/chemistry_tools.py @@ -804,20 +804,18 @@ class IntegralManager: _one_body_integrals: numpy.ndarray = None _two_body_integrals: NBodyTensor = None _constant_term: float = None - _basis_type: str = "unknown" _basis_name: str = "unknown" _orbital_type: str = "unknown" # e.g. "HF", "PNO", "native" _orbital_coefficients: numpy.ndarray = None _active_space: ActiveSpaceData = None _orbitals: typing.List[OrbitalData] = None - def __init__(self, one_body_integrals, two_body_integrals, basis_type="custom", + def __init__(self, one_body_integrals, two_body_integrals, basis_name="unknown", orbital_type="unknown", constant_term=0.0, orbital_coefficients=None, active_space=None, overlap_integrals=None, orbitals=None, *args, **kwargs): self._one_body_integrals = one_body_integrals self._two_body_integrals = two_body_integrals self._constant_term = constant_term - self._basis_type = basis_type self._basis_name = basis_name self._orbital_type = orbital_type @@ -956,9 +954,16 @@ def transform_to_native_orbitals(self): """ c = self.get_orthonormalized_orbital_coefficients() self.orbital_coefficients=c - self._orbital_type="orthonormalized-{}-basis".format(self._orbital_type) + self._orbital_type="orthonormalized-{}-basis".format(self._basis_name) - def transform_orbitals(self, U): + def is_unitary(self, U): + if len(U.shape) != 2: return False + if U.shape[0] != U.shape[1]: return False + test = (U.conj().T).dot(U) - numpy.eye(U.shape[0]) + if not numpy.isclose(numpy.linalg.norm(test), 0.0): return False + return True + + def transform_orbitals(self, U, name=None): """ Transform orbitals Parameters @@ -969,10 +974,12 @@ def transform_orbitals(self, U): ------- updates the structure with new orbitals: c = cU """ - c = self.orbital_coefficients - c = numpy.einsum("ix, xj -> ij", c, U, optimize="greedy") - self.orbital_coefficients = c - self._orbital_type += "-transformed" + assert self.is_unitary(U) + self.orbital_coefficients = numpy.einsum("ix, xj -> ij", self.orbital_coefficients, U, optimize="greedy") + if name is None: + self._orbital_type += "-transformed" + else: + self._orbital_type = name def get_integrals(self, orbital_coefficients=None, ordering="openfermion", ignore_active_space=False, *args, **kwargs): """ @@ -1070,7 +1077,6 @@ def __str__(self): return result def print_basis_info(self, *args, **kwargs) -> None: - print("{:15} : {}".format("basis_type", self._basis_type), *args, **kwargs) print("{:15} : {}".format("basis_name", self._basis_name), *args, **kwargs) print("{:15} : {}".format("orbital_type", self._orbital_type), *args, **kwargs) print("{:15} : {}".format("orthogonal", self.basis_is_orthogonal()), *args, **kwargs) diff --git a/src/tequila/quantumchemistry/orbital_optimizer.py b/src/tequila/quantumchemistry/orbital_optimizer.py index a7bca8d7..5b10b258 100644 --- a/src/tequila/quantumchemistry/orbital_optimizer.py +++ b/src/tequila/quantumchemistry/orbital_optimizer.py @@ -140,7 +140,7 @@ def optimize_orbitals(molecule, circuit=None, vqe_solver=None, pyscf_arguments=N mc.kernel() # make new molecule - transformed_molecule = pyscf_molecule.transform_orbitals(orbital_coefficients=mc.mo_coeff) + transformed_molecule = pyscf_molecule.transform_orbitals(orbital_coefficients=mc.mo_coeff, name="optimized") result.molecule=transformed_molecule result.old_molecule=molecule result.mo_coeff=mc.mo_coeff diff --git a/src/tequila/quantumchemistry/psi4_interface.py b/src/tequila/quantumchemistry/psi4_interface.py index 7fc5ae7f..0c4d7dc5 100644 --- a/src/tequila/quantumchemistry/psi4_interface.py +++ b/src/tequila/quantumchemistry/psi4_interface.py @@ -188,7 +188,7 @@ def __init__(self, parameters: ParametersQC, # psi4 active space will be formed later super().__init__(parameters=parameters, transformation=transformation, active_orbitals=None, - reference_orbitals=reference_orbitals,frozen_orbitals=[], + reference_orbitals=reference_orbitals,frozen_orbitals=[], orbital_type="hf", *args, **kwargs) oenergies = [] diff --git a/src/tequila/quantumchemistry/pyscf_interface.py b/src/tequila/quantumchemistry/pyscf_interface.py index bed3cc5d..69b681f9 100644 --- a/src/tequila/quantumchemistry/pyscf_interface.py +++ b/src/tequila/quantumchemistry/pyscf_interface.py @@ -75,6 +75,7 @@ def __init__(self, parameters: ParametersQC, kwargs["two_body_integrals"] = g_ao kwargs["one_body_integrals"] = h_ao kwargs["orbital_coefficients"] = mo_coeff + kwargs["orbital_type"] = "hf" if "nuclear_repulsion" not in kwargs: kwargs["nuclear_repulsion"] = mol.energy_nuc() diff --git a/src/tequila/quantumchemistry/qc_base.py b/src/tequila/quantumchemistry/qc_base.py index 9689b042..cd828641 100644 --- a/src/tequila/quantumchemistry/qc_base.py +++ b/src/tequila/quantumchemistry/qc_base.py @@ -94,7 +94,7 @@ def __init__(self, parameters: ParametersQC, else: self.integral_manager = self.initialize_integral_manager(active_orbitals=active_orbitals, reference_orbitals=reference_orbitals, - orbitals=orbitals, frozen_orbitals=frozen_orbitals, orbital_type=orbital_type, *args, + orbitals=orbitals, frozen_orbitals=frozen_orbitals, orbital_type=orbital_type, basis_name=self.parameters.basis_set, *args, **kwargs) if orbital_type is not None and orbital_type.lower() == "native": @@ -543,11 +543,12 @@ def initialize_integral_manager(self, *args, **kwargs): return manager - def transform_orbitals(self, orbital_coefficients, *args, **kwargs): + def transform_orbitals(self, orbital_coefficients, name=None, *args, **kwargs): """ Parameters ---------- orbital_coefficients: second index is new orbital indes, first is old orbital index (summed over) + name: str, name the new orbitals args kwargs @@ -558,7 +559,7 @@ def transform_orbitals(self, orbital_coefficients, *args, **kwargs): # can not be an instance of a specific backend (otherwise we get inconsistencies with classical methods in the backend) integral_manager = copy.deepcopy(self.integral_manager) - integral_manager.transform_orbitals(U=orbital_coefficients) + integral_manager.transform_orbitals(U=orbital_coefficients, name=name) result = QuantumChemistryBase(parameters=self.parameters, integral_manager=integral_manager, transformation=self.transformation) return result @@ -583,7 +584,7 @@ def use_native_orbitals(self, inplace=False): else: integral_manager = copy.deepcopy(self.integral_manager) integral_manager.transform_to_native_orbitals() - result = QuantumChemistryBase(parameters=self.parameters, integral_manager=integral_manager, orbital_type="native", transformation=self.transformation) + result = QuantumChemistryBase(parameters=self.parameters, integral_manager=integral_manager, transformation=self.transformation) return result From d48a781b21bb2013137b9f1d8b6f1908e4623d7e Mon Sep 17 00:00:00 2001 From: "J. S. Kottmann" Date: Thu, 4 Apr 2024 22:40:28 +0200 Subject: [PATCH 2/6] keep basis info when re-initializing with mol.from_tequila --- .../quantumchemistry/orbital_optimizer.py | 7 ++++++- src/tequila/quantumchemistry/psi4_interface.py | 2 +- src/tequila/quantumchemistry/qc_base.py | 18 ++++++++++++++---- 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/src/tequila/quantumchemistry/orbital_optimizer.py b/src/tequila/quantumchemistry/orbital_optimizer.py index 5b10b258..a4519b83 100644 --- a/src/tequila/quantumchemistry/orbital_optimizer.py +++ b/src/tequila/quantumchemistry/orbital_optimizer.py @@ -78,7 +78,12 @@ def optimize_orbitals(molecule, circuit=None, vqe_solver=None, pyscf_arguments=N if pyscf_arguments is None: pyscf_arguments = {"max_cycle_macro": 10, "max_cycle_micro": 3} no = molecule.n_orbitals - pyscf_molecule = QuantumChemistryPySCF.from_tequila(molecule=molecule, transformation=molecule.transformation) + + if not isinstance(molecule, QuantumChemistryPySCF): + pyscf_molecule = QuantumChemistryPySCF.from_tequila(molecule=molecule, transformation=molecule.transformation) + else: + pyscf_molecule = molecule + mf = pyscf_molecule._get_hf() result=OptimizeOrbitalsResult() mc = mcscf.CASSCF(mf, pyscf_molecule.n_orbitals, pyscf_molecule.n_electrons) diff --git a/src/tequila/quantumchemistry/psi4_interface.py b/src/tequila/quantumchemistry/psi4_interface.py index 0c4d7dc5..7fc5ae7f 100644 --- a/src/tequila/quantumchemistry/psi4_interface.py +++ b/src/tequila/quantumchemistry/psi4_interface.py @@ -188,7 +188,7 @@ def __init__(self, parameters: ParametersQC, # psi4 active space will be formed later super().__init__(parameters=parameters, transformation=transformation, active_orbitals=None, - reference_orbitals=reference_orbitals,frozen_orbitals=[], orbital_type="hf", + reference_orbitals=reference_orbitals,frozen_orbitals=[], *args, **kwargs) oenergies = [] diff --git a/src/tequila/quantumchemistry/qc_base.py b/src/tequila/quantumchemistry/qc_base.py index 617b68f6..aff9f316 100644 --- a/src/tequila/quantumchemistry/qc_base.py +++ b/src/tequila/quantumchemistry/qc_base.py @@ -108,16 +108,26 @@ def __init__(self, parameters: ParametersQC, @classmethod - def from_tequila(cls, molecule, transformation=None, *args, **kwargs): - c, h1, h2 = molecule.get_integrals() + def from_tequila(cls, molecule, transformation=None, basis_name=None, *args, **kwargs): + c = molecule.integral_manager.constant_term + h1 = molecule.integral_manager.one_body_integrals + h2 = molecule.integral_manager.two_body_integrals + S = molecule.integral_manager.overlap_integrals + active_orbitals = [o.idx for o in molecule.integral_manager.active_orbitals] if transformation is None: transformation = molecule.transformation + if basis_name is None: + basis_name = molecule.integral_manager._orbital_type + parameters = molecule.parameters + parameters.basis_set = basis_name return cls(nuclear_repulsion=c, one_body_integrals=h1, two_body_integrals=h2, + overlap_integrals = S, + active_orbitals= active_orbitals, n_electrons=molecule.n_electrons, transformation=transformation, - parameters=molecule.parameters, *args, **kwargs) + parameters=parameters, *args, **kwargs) def supports_ucc(self): """ @@ -584,7 +594,7 @@ def use_native_orbitals(self, inplace=False): else: integral_manager = copy.deepcopy(self.integral_manager) integral_manager.transform_to_native_orbitals() - result = QuantumChemistryBase(parameters=self.parameters, integral_manager=integral_manager, transformation=self.transformation) + result = type(self)(parameters=self.parameters, integral_manager=integral_manager, transformation=self.transformation) return result From 897afea213df1e77bf772d8d59dd642f77188482 Mon Sep 17 00:00:00 2001 From: "J. S. Kottmann" Date: Thu, 4 Apr 2024 23:05:04 +0200 Subject: [PATCH 3/6] keep basis info when re-initializing with mol.from_tequila --- src/tequila/quantumchemistry/orbital_optimizer.py | 7 ++++--- src/tequila/quantumchemistry/qc_base.py | 10 +++++++++- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/tequila/quantumchemistry/orbital_optimizer.py b/src/tequila/quantumchemistry/orbital_optimizer.py index a4519b83..5972134a 100644 --- a/src/tequila/quantumchemistry/orbital_optimizer.py +++ b/src/tequila/quantumchemistry/orbital_optimizer.py @@ -37,7 +37,7 @@ def __call__(self, local_data, *args, **kwargs): self.iterations += 1 def optimize_orbitals(molecule, circuit=None, vqe_solver=None, pyscf_arguments=None, silent=False, - vqe_solver_arguments=None, initial_guess=None, return_mcscf=False, use_hcb=False, molecule_factory=None, molecule_arguments=None, *args, **kwargs): + vqe_solver_arguments=None, initial_guess=None, return_mcscf=False, use_hcb=False, molecule_factory=None, molecule_arguments=None, restrict_to_active_space=True, *args, **kwargs): """ Parameters @@ -145,10 +145,11 @@ def optimize_orbitals(molecule, circuit=None, vqe_solver=None, pyscf_arguments=N mc.kernel() # make new molecule - transformed_molecule = pyscf_molecule.transform_orbitals(orbital_coefficients=mc.mo_coeff, name="optimized") + mo_coeff = mc.mo_coeff + transformed_molecule = pyscf_molecule.transform_orbitals(orbital_coefficients=mo_coeff, name="optimized") result.molecule=transformed_molecule result.old_molecule=molecule - result.mo_coeff=mc.mo_coeff + result.mo_coeff=mo_coeff result.energy=mc.e_tot if return_mcscf: diff --git a/src/tequila/quantumchemistry/qc_base.py b/src/tequila/quantumchemistry/qc_base.py index aff9f316..59761019 100644 --- a/src/tequila/quantumchemistry/qc_base.py +++ b/src/tequila/quantumchemistry/qc_base.py @@ -567,9 +567,17 @@ def transform_orbitals(self, orbital_coefficients, name=None, *args, **kwargs): New molecule with transformed orbitals """ + U = numpy.eye(self.integral_manager.orbital_coefficients.shape[0]) + # mo_coeff by default only acts on the active space + active_indices = [o.idx_total for o in self.integral_manager.active_orbitals] + + for kk,k in enumerate(active_indices): + for ll,l in enumerate(active_indices): + U[k][l] = orbital_coefficients[kk][ll] + # can not be an instance of a specific backend (otherwise we get inconsistencies with classical methods in the backend) integral_manager = copy.deepcopy(self.integral_manager) - integral_manager.transform_orbitals(U=orbital_coefficients, name=name) + integral_manager.transform_orbitals(U=U, name=name) result = QuantumChemistryBase(parameters=self.parameters, integral_manager=integral_manager, transformation=self.transformation) return result From f6e41c069cac13eb0e203c0f38a6ab701a95c32b Mon Sep 17 00:00:00 2001 From: "J. S. Kottmann" Date: Thu, 4 Apr 2024 23:49:03 +0200 Subject: [PATCH 4/6] keep basis info when re-initializing with mol.from_tequila --- src/tequila/quantumchemistry/qc_base.py | 30 ++++++++++++++----------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/src/tequila/quantumchemistry/qc_base.py b/src/tequila/quantumchemistry/qc_base.py index 59761019..ff449e76 100644 --- a/src/tequila/quantumchemistry/qc_base.py +++ b/src/tequila/quantumchemistry/qc_base.py @@ -113,7 +113,7 @@ def from_tequila(cls, molecule, transformation=None, basis_name=None, *args, **k h1 = molecule.integral_manager.one_body_integrals h2 = molecule.integral_manager.two_body_integrals S = molecule.integral_manager.overlap_integrals - active_orbitals = [o.idx for o in molecule.integral_manager.active_orbitals] + active_orbitals = [o.idx_total for o in molecule.integral_manager.active_orbitals] if transformation is None: transformation = molecule.transformation if basis_name is None: @@ -553,11 +553,12 @@ def initialize_integral_manager(self, *args, **kwargs): return manager - def transform_orbitals(self, orbital_coefficients, name=None, *args, **kwargs): + def transform_orbitals(self, orbital_coefficients, ignore_active_space=False, name=None, *args, **kwargs): """ Parameters ---------- - orbital_coefficients: second index is new orbital indes, first is old orbital index (summed over) + orbital_coefficients: second index is new orbital indes, first is old orbital index (summed over), indices are assumed to be defined on the active space + ignore_active_space: if true orbital_coefficients are not assumed to be given in the active space name: str, name the new orbitals args kwargs @@ -571,9 +572,12 @@ def transform_orbitals(self, orbital_coefficients, name=None, *args, **kwargs): # mo_coeff by default only acts on the active space active_indices = [o.idx_total for o in self.integral_manager.active_orbitals] - for kk,k in enumerate(active_indices): - for ll,l in enumerate(active_indices): - U[k][l] = orbital_coefficients[kk][ll] + if ignore_active_space: + U = orbital_coefficients + else: + for kk,k in enumerate(active_indices): + for ll,l in enumerate(active_indices): + U[k][l] = orbital_coefficients[kk][ll] # can not be an instance of a specific backend (otherwise we get inconsistencies with classical methods in the backend) integral_manager = copy.deepcopy(self.integral_manager) @@ -886,13 +890,13 @@ def hcb_to_me(self, U=None, condensed=False): """ if U is None: U = QCircuit() - - # consistency - consistency = [x < self.n_orbitals for x in U.qubits] - if not all(consistency): - warnings.warn( - "hcb_to_me: given circuit is not defined on the first {} qubits. Is this a HCB circuit?".format( - self.n_orbitals)) + else: + ups = [self.transformation.up(i.idx) for i in self.orbitals] + consistency = [x in ups for x in U.qubits] + if not all(consistency): + warnings.warn( + "hcb_to_me: given circuit is not defined on all first {} qubits. Is this a HCB circuit?".format( + self.n_orbitals)) # map to alpha qubits if condensed: From 175bc4ab297e41e6f16dfbae6c6f380864d5ad40 Mon Sep 17 00:00:00 2001 From: "J. S. Kottmann" Date: Fri, 5 Apr 2024 00:26:59 +0200 Subject: [PATCH 5/6] keep basis info when re-initializing with mol.from_tequila --- src/tequila/quantumchemistry/chemistry_tools.py | 2 ++ src/tequila/quantumchemistry/qc_base.py | 8 +++----- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/tequila/quantumchemistry/chemistry_tools.py b/src/tequila/quantumchemistry/chemistry_tools.py index 00a65738..8cebef81 100644 --- a/src/tequila/quantumchemistry/chemistry_tools.py +++ b/src/tequila/quantumchemistry/chemistry_tools.py @@ -1008,7 +1008,9 @@ def get_integrals(self, orbital_coefficients=None, ordering="openfermion", ignor active_integrals = get_active_space_integrals(one_body_integrals=h, two_body_integrals=g, occupied_indices=self._active_space.frozen_reference_orbitals, active_indices=self._active_space.active_orbitals) + c = active_integrals[0] + c + h = active_integrals[1] g = NBodyTensor(elems=active_integrals[2], ordering="openfermion") g.reorder(to=ordering) diff --git a/src/tequila/quantumchemistry/qc_base.py b/src/tequila/quantumchemistry/qc_base.py index ff449e76..de0f370a 100644 --- a/src/tequila/quantumchemistry/qc_base.py +++ b/src/tequila/quantumchemistry/qc_base.py @@ -108,7 +108,7 @@ def __init__(self, parameters: ParametersQC, @classmethod - def from_tequila(cls, molecule, transformation=None, basis_name=None, *args, **kwargs): + def from_tequila(cls, molecule, transformation=None, *args, **kwargs): c = molecule.integral_manager.constant_term h1 = molecule.integral_manager.one_body_integrals h2 = molecule.integral_manager.two_body_integrals @@ -116,17 +116,15 @@ def from_tequila(cls, molecule, transformation=None, basis_name=None, *args, **k active_orbitals = [o.idx_total for o in molecule.integral_manager.active_orbitals] if transformation is None: transformation = molecule.transformation - if basis_name is None: - basis_name = molecule.integral_manager._orbital_type parameters = molecule.parameters - parameters.basis_set = basis_name return cls(nuclear_repulsion=c, one_body_integrals=h1, two_body_integrals=h2, overlap_integrals = S, + orbital_coefficients = molecule.integral_manager.orbital_coefficients, active_orbitals= active_orbitals, - n_electrons=molecule.n_electrons, transformation=transformation, + orbital_type=molecule.integral_manager._orbital_type, parameters=parameters, *args, **kwargs) def supports_ucc(self): From e967222511ae15a499ebc789fa7f228df1456bec Mon Sep 17 00:00:00 2001 From: "J. S. Kottmann" Date: Fri, 5 Apr 2024 00:45:57 +0200 Subject: [PATCH 6/6] keep basis info when re-initializing with mol.from_tequila --- src/tequila/quantumchemistry/qc_base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tequila/quantumchemistry/qc_base.py b/src/tequila/quantumchemistry/qc_base.py index de0f370a..d8a6882b 100644 --- a/src/tequila/quantumchemistry/qc_base.py +++ b/src/tequila/quantumchemistry/qc_base.py @@ -604,7 +604,7 @@ def use_native_orbitals(self, inplace=False): else: integral_manager = copy.deepcopy(self.integral_manager) integral_manager.transform_to_native_orbitals() - result = type(self)(parameters=self.parameters, integral_manager=integral_manager, transformation=self.transformation) + result = QuantumChemistryBase(parameters=self.parameters, integral_manager=integral_manager, transformation=self.transformation) return result