def _set_operator_and_vqe(self, driver: BaseDriver): """ Initializes the operators using provided driver of qmolecule.""" if not isinstance(self._transformation, FermionicTransformation): raise QiskitNatureError('OrbitalOptimizationVQE requires a FermionicTransformation.') if not isinstance(driver, FermionicDriver): raise QiskitNatureError('OrbitalOptimizationVQE only works with Fermionic Drivers.') if self._qmolecule is None: # in future, self._transformation.transform should return also qmolecule # to avoid running the driver twice self._qmolecule = driver.run() operator, aux_operators = self._transformation._do_transform(self._qmolecule) else: operator, aux_operators = self._transformation._do_transform(self._qmolecule) if operator is None: # type: ignore raise QiskitNatureError("The operator was never provided.") if isinstance(self.solver, MinimumEigensolverFactory): # this must be called after transformation.transform self._vqe = self.solver.get_solver(self.transformation) else: self._vqe = self.solver if not isinstance(self._vqe, VQE): raise QiskitNatureError( "The OrbitalOptimizationVQE algorithm requires the use of the VQE " + "MinimumEigensolver.") if not isinstance(self._vqe.var_form, UCCSD): raise QiskitNatureError( "The OrbitalOptimizationVQE algorithm requires the use of the UCCSD varform.") self._vqe.operator = operator self._vqe.aux_operators = aux_operators
def _initialize_additional_parameters(self, driver: BaseDriver): """ Initializes additional parameters of the OOVQE algorithm. """ if not isinstance(self._transformation, FermionicTransformation): raise QiskitNatureError('OrbitalOptimizationVQE requires a FermionicTransformation.') self._set_operator_and_vqe(driver) if self._orbital_rotation is None: self._orbital_rotation = OrbitalRotation(num_qubits=self._vqe.var_form.num_qubits, transformation=self._transformation, qmolecule=self._qmolecule) self._num_parameters_oovqe = \ self._vqe.var_form.num_parameters + self._orbital_rotation.num_parameters if self.initial_point is None: self._set_initial_point() else: if len(self.initial_point) is not self._num_parameters_oovqe: raise QiskitNatureError( 'Number of parameters of OOVQE ({}) does not match the length of the ' 'intitial_point ({})'.format(self._num_parameters_oovqe, len(self.initial_point))) if self._bounds is None: self._set_bounds(self._orbital_rotation.parameter_bound_value) if self._iterative_oo_iterations < 1: raise QiskitNatureError( 'Please set iterative_oo_iterations parameter to a positive number,' ' got {} instead'.format(self._iterative_oo_iterations)) # copies to overcome incompatibilities with error checks in VariationalAlgorithm class self.var_form_num_parameters = self._vqe.var_form.num_parameters self.var_form_bounds = copy.copy(self._vqe.var_form._bounds) self._additional_params_initialized = True
def compute( self, ansatz: UCC | None = None, grouped_property: GroupedSecondQuantizedProperty | None = None, ) -> None: """Compute the initial point parameter for each excitation. See class documentation for more information. Args: grouped_property: A grouped second-quantized property. See :attr:`grouped_property`. ansatz: The UCC ansatz. See :attr:`ansatz`. Raises: QiskitNatureError: If :attr:`_excitation_list` or :attr:`_grouped_property` is not set. """ if ansatz is not None: self.ansatz = ansatz if self._excitation_list is None: raise QiskitNatureError( "The excitation list has not been set directly or via the ansatz. " "Not enough information has been provided to compute the initial point. " "Set the ansatz or call compute with it as an argument. " "The ansatz is not required if the excitation list has been set directly." ) if grouped_property is not None: self.grouped_property = grouped_property if self._grouped_property is None: raise QiskitNatureError("The grouped_property has not been set.") self._compute()
def __init__( self, gss: GroundStateSolver, tolerance: float = 1e-3, bootstrap: bool = True, num_bootstrap: Optional[int] = None, extrapolator: Optional[Extrapolator] = None, ) -> None: """ Args: gss: GroundStateSolver tolerance: Tolerance desired for minimum energy. bootstrap: Whether to warm-start the solution of variational minimum eigensolvers. num_bootstrap: Number of previous points for extrapolation and bootstrapping. If None and a list of extrapolators is defined, the first two points will be used for bootstrapping. If no extrapolator is defined and bootstrap is True, all previous points will be used for bootstrapping. extrapolator: Extrapolator objects that define space/window and method to extrapolate variational parameters. Raises: QiskitNatureError: If ``num_boostrap`` is an integer smaller than 2, or if ``num_boostrap`` is larger than 2 and the extrapolator is not an instance of ``WindowExtrapolator``. """ self._gss = gss self._tolerance = tolerance self._bootstrap = bootstrap self._problem: BaseProblem = None self._driver: Union[DeprecatedBaseDriver, BaseDriver] = None self._points: List[float] = None self._energies: List[float] = None self._raw_results: Dict[float, EigenstateResult] = None self._points_optparams: Dict[float, List[float]] = None self._num_bootstrap = num_bootstrap self._extrapolator = extrapolator if self._extrapolator: if num_bootstrap is None: # set default number of bootstrapping points to 2 self._num_bootstrap = 2 elif num_bootstrap >= 2: if not isinstance(self._extrapolator, WindowExtrapolator): raise QiskitNatureError( "If num_bootstrap >= 2 then the extrapolator must be an instance " f"of WindowExtrapolator, got {self._extrapolator} instead" ) self._num_bootstrap = num_bootstrap self._extrapolator.window = num_bootstrap # window for extrapolator else: raise QiskitNatureError( "num_bootstrap must be None or an integer greater than or equal to 2" ) if isinstance(self._gss.solver, VariationalAlgorithm): # type: ignore # Save initial point passed to min_eigensolver; # this will be used when NOT bootstrapping self._initial_point = self._gss.solver.initial_point # type: ignore
def _check_molecule_format(val: str) -> Union[str, List[str]]: """Ensures the molecule coordinates are in XYZ format. This utility automatically converts a Z-matrix coordinate format into XYZ coordinates. Args: val: the atomic coordinates. Raises: QiskitNatureError: If the provided coordinate are badly formatted. Returns: The coordinates in XYZ format. """ # pylint: disable=import-error from pyscf import gto atoms = [x.strip() for x in val.split(";")] if atoms is None or len(atoms) < 1: raise QiskitNatureError("Molecule format error: " + val) # An xyz format has 4 parts in each atom, if not then do zmatrix convert # Allows dummy atoms, using symbol 'X' in zmatrix format for coord computation to xyz parts = [x.strip() for x in atoms[0].split()] if len(parts) != 4: try: newval = [] for entry in gto.mole.from_zmatrix(val): if entry[0].upper() != "X": newval.append(entry) return newval except Exception as exc: raise QiskitNatureError("Failed to convert atom string: " + val) from exc return val
def grouped_property( self, grouped_property: GroupedSecondQuantizedProperty) -> None: electronic_energy: ElectronicEnergy | None = grouped_property.get_property( ElectronicEnergy) if electronic_energy is None: raise QiskitNatureError( "The `ElectronicEnergy` cannot be obtained from the `grouped_property`." ) two_body_mo_integral: ElectronicIntegrals | None = ( electronic_energy.get_electronic_integral(ElectronicBasis.MO, 2)) if two_body_mo_integral is None: raise QiskitNatureError( "The two-body MO `electronic_integrals` cannot be obtained from the `grouped_property`." ) orbital_energies: np.ndarray | None = electronic_energy.orbital_energies if orbital_energies is None: raise QiskitNatureError( "The `orbital_energies` cannot be obtained from the `grouped_property`." ) integral_matrix: np.ndarray = two_body_mo_integral.get_matrix() if not np.allclose(integral_matrix, two_body_mo_integral.get_matrix(2)): raise NotImplementedError( "`MP2InitialPoint` only supports restricted-spin setups. " "Alpha and beta spin orbitals must be identical. " "See https://github.com/Qiskit/qiskit-nature/issues/645.") reference_energy = electronic_energy.reference_energy if not None else 0.0 particle_number: ParticleNumber | None = grouped_property.get_property( ParticleNumber) if particle_number is None: raise QiskitNatureError( "The `ParticleNumber` cannot be obtained from the `grouped_property`." ) # Get number of occupied molecular orbitals as the number of alpha particles. # Only valid for restricted-spin setups. num_occ = particle_number.num_particles[0] self._invalidate() t2_amplitudes, energy_correction = _compute_mp2( num_occ, integral_matrix, orbital_energies) # Save state. self._grouped_property = grouped_property self._t2_amplitudes = t2_amplitudes self._energy_correction = energy_correction self._total_energy = reference_energy + energy_correction
def _energy_evaluation_oo(self, parameters: np.ndarray) -> Union[float, List[float]]: """ Evaluate energy at given parameters for the variational form and parameters for given rotation of orbitals. Args: parameters: parameters for variational form and orbital rotations. Returns: energy of the hamiltonian of each parameter. Raises: QiskitNatureError: Instantiate OrbitalRotation class and provide it to the orbital_rotation keyword argument """ if not isinstance(self._transformation, FermionicTransformation): raise QiskitNatureError('OrbitalOptimizationVQE requires a FermionicTransformation.') # slice parameter lists if self._iterative_oo: parameters_var_form = self._fixed_wavefunction_params parameters_orb_rot = parameters else: parameters_var_form = parameters[:self.var_form_num_parameters] parameters_orb_rot = parameters[self.var_form_num_parameters:] logger.info('Parameters of wavefunction are: \n%s', repr(parameters_var_form)) logger.info('Parameters of orbital rotation are: \n%s', repr(parameters_orb_rot)) # rotate the orbitals if self._orbital_rotation is None: raise QiskitNatureError( 'Instantiate OrbitalRotation class and provide it to the ' 'orbital_rotation keyword argument') self._orbital_rotation.orbital_rotation_matrix(parameters_orb_rot) # preserve original qmolecule and create a new one with rotated orbitals self._qmolecule_rotated = copy.copy(self._qmolecule) OrbitalOptimizationVQE._rotate_orbitals_in_qmolecule( self._qmolecule_rotated, self._orbital_rotation) # construct the qubit operator operator, aux_operators = self._transformation._do_transform(self._qmolecule_rotated) self._vqe.operator = operator self._vqe.aux_operators = aux_operators logger.debug('Orbital rotation parameters of matrix U at evaluation %d returned' '\n %s', self._vqe._eval_count, repr(self._orbital_rotation.matrix_a)) self._vqe.var_form._num_parameters = self.var_form_num_parameters # compute the energy on given state mean_energy = self._vqe._energy_evaluation(parameters=parameters_var_form) return mean_energy
def sample(self, problem: BaseProblem, points: List[float]) -> BOPESSamplerResult: """Run the sampler at the given points, potentially with repetitions. Args: problem: BaseProblem whose driver should be based on a Molecule object that has perturbations to be varied. points: The points along the degrees of freedom to evaluate. Returns: BOPES Sampler Result Raises: QiskitNatureError: if the driver does not have a molecule specified. """ self._problem = problem self._driver = problem.driver if isinstance(self._driver, DeprecatedBaseDriver): warn_deprecated( "0.2.0", DeprecatedType.CLASS, f"{self._driver.__class__.__module__}.{self._driver.__class__.__qualname__}", new_name= "ElectronicStructureMoleculeDriver or VibrationalStructureMoleculeDriver", additional_msg="from qiskit_nature.drivers.second_quantization", ) elif not isinstance(self._driver, (ElectronicStructureMoleculeDriver, VibrationalStructureMoleculeDriver)): raise QiskitNatureError( "Driver must be ElectronicStructureMoleculeDriver or VibrationalStructureMoleculeDriver." ) if self._driver.molecule is None: raise QiskitNatureError( "Driver MUST be configured with a Molecule.") # full dictionary of points self._raw_results = self._run_points(points) # create results dictionary with (point, energy) self._points = list(self._raw_results.keys()) self._energies = [] for res in self._raw_results.values(): energy = res.total_energies[0] self._energies.append(energy) result = BOPESSamplerResult(self._points, self._energies, self._raw_results) return result
def sample(self, problem: BaseProblem, points: List[float]) -> BOPESSamplerResult: """Run the sampler at the given points, potentially with repetitions. Args: problem: BaseProblem whose driver should be based on a Molecule object that has perturbations to be varied. points: The points along the degrees of freedom to evaluate. Returns: BOPES Sampler Result Raises: QiskitNatureError: if the driver does not have a molecule specified. """ self._problem = problem self._driver = problem.driver # We have to force the creation of the solver so that we work on the same solver # instance before and after _gss.solve self._gss.get_qubit_operators(problem, None) if isinstance(self._gss.solver, VariationalAlgorithm): # type: ignore # Save initial point passed to min_eigensolver; # this will be used when NOT bootstrapping self._initial_point = self._gss.solver.initial_point # type: ignore if not isinstance(self._driver, (ElectronicStructureMoleculeDriver, VibrationalStructureMoleculeDriver)): raise QiskitNatureError( "Driver must be ElectronicStructureMoleculeDriver or VibrationalStructureMoleculeDriver." ) if self._driver.molecule is None: raise QiskitNatureError( "Driver MUST be configured with a Molecule.") # full dictionary of points self._raw_results = self._run_points(points) # create results dictionary with (point, energy) self._points = list(self._raw_results.keys()) self._energies = [] for res in self._raw_results.values(): energy = res.total_energies[0] self._energies.append(energy) result = BOPESSamplerResult(self._points, self._energies, self._raw_results) return result
def __init__( self, extrapolator: Optional[Union[PolynomialExtrapolator, DifferentialExtrapolator]] = None, kernel: Optional[str] = None, window: int = 2, ) -> None: """ Constructor. Args: extrapolator: 'internal' extrapolator that performs extrapolation on variational parameters based on data window. kernel: Kernel (from sklearn) that specifies how dimensionality reduction should be done for PCA. Default value is None, and switches the extrapolation to standard PCA. window: Number of previous points to use for extrapolation. Raises: QiskitNatureError: if kernel is not defined in sklearn module. """ self._extrapolator = WindowExtrapolator(extrapolator=extrapolator, window=window) self._kernel = kernel if self._kernel is None: self._pca_model = PCA() elif self._kernel in ["linear", "poly", "rbf", "sigmoid", "cosine"]: self._pca_model = KernelPCA(kernel=self._kernel, fit_inverse_transform=True) else: raise QiskitNatureError("PCA kernel type {} not found".format( self._kernel))
def factory(mode: str, **kwargs) -> "Extrapolator": """ Factory method for constructing extrapolators. Args: mode: Extrapolator to instantiate. Can be one of: - 'window' - 'poly' - 'diff_model' - 'pca' - 'l1' kwargs: arguments to be passed to the constructor of an extrapolator Returns: A newly created extrapolator instance. Raises: QiskitNatureError: if specified mode is unknown. """ if mode == "window": return WindowExtrapolator(**kwargs) elif mode == "poly": return PolynomialExtrapolator(**kwargs) elif mode == "diff_model": return DifferentialExtrapolator(**kwargs) elif mode == "pca": return PCAExtrapolator(**kwargs) elif mode == "l1": return SieveExtrapolator(**kwargs) else: raise QiskitNatureError("No extrapolator called {}".format(mode))
def compute( self, ansatz: UCC | None = None, grouped_property: GroupedSecondQuantizedProperty | None = None, ) -> None: """Compute the parameter for each excitation. See further up for more information. Args: grouped_property: A grouped second-quantized property that may optionally contain the Hartree-Fock reference energy. This is for consistency with other initial points. ansatz: The UCC ansatz. Required to set the :attr:`excitation_list` to ensure that the initial point array has the correct shape. Raises: QiskitNatureError: If :attr`ansatz` is not set. """ if grouped_property is not None: self.grouped_property = grouped_property if ansatz is not None: self.ansatz = ansatz if self._excitation_list is None: raise QiskitNatureError( "The excitation list has not been set directly or via the ansatz. " "Not enough information has been provided to compute the initial point. " "Set the ansatz or call compute with it as an argument. " "The ansatz is not required if the excitation list has been set directly." ) self._compute()
def xcf_library(self, xcf_library: str) -> None: """Sets the Exchange-Correlation functional library.""" if xcf_library not in ("libxc", "xcfun"): raise QiskitNatureError( "Invalid XCF library. It can be either 'libxc' or 'xcfun', not " f"'{xcf_library}'" ) self._xcf_library = xcf_library
def compute( self, ansatz: UVCC | None = None, grouped_property: GroupedSecondQuantizedProperty | None = None, ) -> None: """Compute the initial point. See further up for more information. Args: ansatz: The UVCC ansatz. Required to set the :attr:`excitation_list` to ensure that the coefficients are mapped correctly in the initial point array. grouped_property: Not required to compute the VSCF initial point. Raises: QiskitNatureError: If :attr`ansatz` is not set. """ if ansatz is not None: # The ansatz setter also sets the private excitation_list. self.ansatz = ansatz if self._excitation_list is None: raise QiskitNatureError( "The excitation list has not been set directly or via the ansatz. " "Not enough information has been provided to compute the initial point. " "Set the ansatz or call compute with it as an argument. " "The ansatz is not required if the excitation list has been set directly." ) self._parameters = np.zeros(len(self._excitation_list))
def sample(self, problem: BaseProblem, points: List[float]) -> BOPESSamplerResult: """Run the sampler at the given points, potentially with repetitions. Args: problem: BaseProblem whose driver should be based on a Molecule object that has perturbations to be varied. points: The points along the degrees of freedom to evaluate. Returns: BOPES Sampler Result Raises: QiskitNatureError: if the driver does not have a molecule specified. """ self._problem = problem self._driver = problem.driver if self._driver.molecule is None: raise QiskitNatureError( 'Driver MUST be configured with a Molecule.') # full dictionary of points self._raw_results = self._run_points(points) # create results dictionary with (point, energy) self._points = list(self._raw_results.keys()) self._energies = [] for res in self._raw_results.values(): energy = res.total_energies[0] self._energies.append(energy) result = BOPESSamplerResult(self._points, self._energies, self._raw_results) return result
def sample(self, driver: BaseDriver, points: List[float]) -> BOPESSamplerResult: """Run the sampler at the given points, potentially with repetitions. Args: driver: BaseDriver specific for the problem. The driver should be based on a Molecule object that has perturbations to be varied. points: The points along the degrees of freedom to evaluate. Returns: BOPES Sampler Result Raises: QiskitNatureError: if the driver does not have a molecule specified. """ self._driver = driver if self._driver.molecule is None: raise QiskitNatureError('Driver MUST be configured with a Molecule.') # full dictionary of points self._raw_results = self._run_points(points) # create results dictionary with (point, energy) self._points = list(self._raw_results.keys()) self._energies = [] for key in self._raw_results: energy = self._raw_results[key].computed_energies[0] + \ self._raw_results[key].nuclear_repulsion_energy self._energies.append(energy) result = BOPESSamplerResult(self._points, self._energies, self._raw_results) return result
def __init__(self, transformation: FermionicTransformation, solver: Union[MinimumEigensolver, MinimumEigensolverFactory], initial_point: Optional[np.ndarray] = None, orbital_rotation: Optional['OrbitalRotation'] = None, bounds: Optional[np.ndarray] = None, iterative_oo: bool = True, iterative_oo_iterations: int = 2, ): """ Args: transformation: a fermionic driver to operator transformation strategy. solver: a VQE instance or a factory for the VQE solver employing any custom variational form, such as the `VQEUCCSDFactory`. Both need to use the UCCSD variational form. initial_point: An optional initial point (i.e. initial parameter values) for the optimizer. If ``None`` then VQE will look to the variational form for a preferred point and if not will simply compute a random one. orbital_rotation: instance of :class:`~qiskit_nature.ground_state_calculation.OrbitalRotation` class that creates the matrices that rotate the orbitals needed to produce the rotated MO coefficients C as C = C0 * exp(-kappa). bounds: bounds for variational form and orbital rotation parameters given to a classical optimizer. iterative_oo: when ``True`` optimize first the variational form and then the orbitals, iteratively. Otherwise, the wavefunction ansatz and orbitals are optimized simultaneously. iterative_oo_iterations: number of iterations in the iterative procedure, set larger to be sure to converge to the global minimum. Raises: QiskitNatureError: if the number of orbital optimization iterations is less or equal to zero. """ super().__init__(transformation, solver) if not isinstance(self._transformation, FermionicTransformation): raise QiskitNatureError('OrbitalOptimizationVQE requires a FermionicTransformation.') from typing import cast self._transformation = cast(FermionicTransformation, self._transformation) self.initial_point = initial_point self._orbital_rotation = orbital_rotation self._bounds = bounds self._iterative_oo = iterative_oo self._iterative_oo_iterations = iterative_oo_iterations # internal parameters of the algorithm self._driver = None # type: Optional[FermionicDriver] self._qmolecule = None # type: Optional[QMolecule] self._qmolecule_rotated = None # type: Optional[QMolecule] self._fixed_wavefunction_params = None self._num_parameters_oovqe = None self._additional_params_initialized = False self.var_form_num_parameters = None self.var_form_bounds = None self._vqe = None # type: Optional[VQE] self._bound_oo = None # type: Optional[List]
def grouped_property( self, grouped_property: GroupedSecondQuantizedProperty) -> None: electronic_energy: ElectronicEnergy | None = grouped_property.get_property( ElectronicEnergy) if electronic_energy is None: raise QiskitNatureError( "The ElectronicEnergy cannot be obtained from the grouped_property." ) two_body_mo_integral: ElectronicIntegrals | None = ( electronic_energy.get_electronic_integral(ElectronicBasis.MO, 2)) if two_body_mo_integral is None: raise QiskitNatureError( "The two body MO electronic integral cannot be obtained from the grouped property." ) orbital_energies: np.ndarray | None = electronic_energy.orbital_energies if orbital_energies is None: raise QiskitNatureError( "The orbital_energies cannot be obtained from the grouped property." ) self._integral_matrix = two_body_mo_integral.get_matrix() if not np.allclose(self._integral_matrix, two_body_mo_integral.get_matrix(2)): raise NotImplementedError( "MP2InitialPoint only supports restricted-spin setups. " "Alpha and beta spin orbitals must be identical. " "See https://github.com/Qiskit/qiskit-nature/issues/645.") # Invalidate any previous computation. self._corrections = None self._orbital_energies = orbital_energies self._reference_energy = electronic_energy.reference_energy if not None else 0.0 self._grouped_property = grouped_property
def _build_molecule(self) -> None: """Builds the PySCF molecule object. Raises: QiskitNatureError: If building the PySCF molecule object failed. """ # Get config from input parameters # molecule is in PySCF atom string format e.g. "H .0 .0 .0; H .0 .0 0.2" # or in Z-Matrix format e.g. "H; O 1 1.08; H 2 1.08 1 107.5" # other parameters are as per PySCF got.Mole format # pylint: disable=import-error from pyscf import gto from pyscf.lib import logger as pylogger from pyscf.lib import param atom = self._check_molecule_format(self.atom) if self._max_memory is None: self._max_memory = param.MAX_MEMORY try: verbose = pylogger.QUIET output = None if logger.isEnabledFor(logging.DEBUG): verbose = pylogger.INFO file, output = tempfile.mkstemp(suffix=".log") os.close(file) self._mol = gto.Mole( atom=atom, unit=self._unit.value, basis=self._basis, max_memory=self._max_memory, verbose=verbose, output=output, ) self._mol.symmetry = False self._mol.charge = self._charge self._mol.spin = self._spin self._mol.build(parse_arg=False) if output is not None: self._process_pyscf_log(output) try: os.remove(output) except Exception: # pylint: disable=broad-except pass except Exception as exc: raise QiskitNatureError("Failed to build the PySCF Molecule object.") from exc
def load_from_hdf5(filename: str, *, skip_unreadable_data: bool = False) -> HDF5Storable: """Loads a single Qiskit Nature object from an HDF5 file. .. code-block:: python my_driver_result = load_from_hdf5("my_driver_result.hdf5") Note: an `h5py.File` object could in theory store more than one `HDF5Storable`. However, in Qiskit Nature the `save_to_hdf5` method works as to always store a single object in a file. Aligned with that implementation, this method will always return the first `HDF5Storable` instance which is encounters in the file. Args: filename: the path to the HDF5 file. skip_unreadable_data: if set to True, unreadable data (which can be any of the errors documented below) will be skipped instead of raising those errors. Returns: The first `HDF5Storable` instance constructed from the data encountered in the file. Raises: QiskitNatureError: if an object without a `__class__` attribute is encountered. QiskitNatureError: if a non-native object (`__module__` outside of `qiskit_nature`) is encountered. QiskitNatureError: if an import failure occurs because `__class__` cannot be found inside of `__module__` QiskitNatureError: if `__class__` does not implement the :class:`~qiskit_nature.hdf5.HDF5Storable` protocol QiskitNatureError: if more than one :class:`~qiskit_nature.hdf5.HDF5Storable` object were encountered in the file. """ ret: HDF5Storable = None with h5py.File(filename, "r") as file: for obj in _import_and_build_from_hdf5( file, skip_unreadable_data=skip_unreadable_data): if ret is not None: msg = ( "Encountered more than one HDF5Storable object in the file! " "This is not supported and only the first one will be returned." ) if skip_unreadable_data: LOGGER.warning(msg) continue raise QiskitNatureError(msg) ret = obj return ret
def orbital_rotation_matrix(self, parameters: np.ndarray) -> Tuple[np.ndarray, np.ndarray]: """ Creates 2 matrices K_alpha, K_beta that rotate the orbitals through MO coefficient C_alpha = C_RHF * U_alpha where U = e^(K_alpha), similarly for beta orbitals. """ self._parameters = parameters # type: ignore k_matrix_alpha = np.zeros((self._dim_kappa_matrix, self._dim_kappa_matrix)) k_matrix_beta = np.zeros((self._dim_kappa_matrix, self._dim_kappa_matrix)) # allows to selectively rotate pairs of orbitals if self._orbital_rotations_beta is None: for i, exc in enumerate(self._orbital_rotations): k_matrix_alpha[exc[0]][exc[1]] = self._parameters[i] k_matrix_alpha[exc[1]][exc[0]] = -self._parameters[i] k_matrix_beta[exc[0]][exc[1]] = self._parameters[i] k_matrix_beta[exc[1]][exc[0]] = -self._parameters[i] else: for i, exc in enumerate(self._orbital_rotations): k_matrix_alpha[exc[0]][exc[1]] = self._parameters[i] k_matrix_alpha[exc[1]][exc[0]] = -self._parameters[i] for j, exc in enumerate(self._orbital_rotations_beta): k_matrix_beta[exc[0]][exc[1]] = self._parameters[j + len(self._orbital_rotations)] k_matrix_beta[exc[1]][exc[0]] = -self._parameters[j + len(self._orbital_rotations)] if self._freeze_core: half_as = int(self._dim_kappa_matrix + len(self._core_list)) k_matrix_alpha_full = np.zeros((half_as, half_as)) k_matrix_beta_full = np.zeros((half_as, half_as)) # rotating only non-frozen part of orbitals dim_full_k = k_matrix_alpha_full.shape[0] # pylint: disable=unsubscriptable-object if self._core_list is None: raise QiskitNatureError( 'Give _core_list, the list of molecular spatial orbitals that are ' 'frozen (e.g. [0] for the 1s or [0,1] for respectively Li2 or N2 ' 'for example).') lower = len(self._core_list) upper = dim_full_k k_matrix_alpha_full[lower:upper, lower:upper] = k_matrix_alpha k_matrix_beta_full[lower:upper, lower:upper] = k_matrix_beta self._matrix_a = expm(k_matrix_alpha_full) self._matrix_b = expm(k_matrix_beta_full) else: self._matrix_a = expm(k_matrix_alpha) self._matrix_b = expm(k_matrix_beta) return self._matrix_a, self._matrix_b
def _create_parameter_list_for_orbital_rotations(self) -> None: """ Initializes the initial values of orbital rotation matrix kappa. """ # creates the indices of matrix kappa and prevent user from trying to rotate only betas if self._orbital_rotations is None: self._create_orbital_rotation_list() elif self._orbital_rotations is None and self._orbital_rotations_beta is not None: raise QiskitNatureError( 'Only beta orbitals labels (orbital_rotations_beta) have been provided.' 'Please also specify the alpha orbitals (orbital_rotations) ' 'that are rotated as well. Do not specify anything to have by default ' 'all orbitals rotated.') if self._orbital_rotations_beta is not None: num_parameters = len(self._orbital_rotations + self._orbital_rotations_beta) else: num_parameters = len(self._orbital_rotations) self._parameters = [self._parameter_initial_value for _ in range(num_parameters)]
def run_pyscf(self) -> None: """Runs the PySCF calculation. This method is part of the public interface to allow the user to easily overwrite it in a subclass to further tailor the behavior to some specific use case. Raises: QiskitNatureError: If an invalid HF method type was supplied. """ # pylint: disable=import-error from pyscf import dft, scf from pyscf.lib import chkfile as lib_chkfile method_name = None method_cls = None try: # attempt to gather the SCF-method class specified by the MethodType method_name = self.method.value.upper() method_cls = getattr(scf, method_name) except AttributeError as exc: raise QiskitNatureError(f"Failed to load {method_name} HF object.") from exc self._calc = method_cls(self._mol) if method_name in ("RKS", "ROKS", "UKS"): self._calc._numint.libxc = getattr(dft, self.xcf_library) self._calc.xc = self.xc_functional if self._chkfile is not None and os.path.exists(self._chkfile): self._calc.__dict__.update(lib_chkfile.load(self._chkfile, "scf")) logger.info("PySCF loaded from chkfile e(hf): %s", self._calc.e_tot) else: self._calc.conv_tol = self._conv_tol self._calc.max_cycle = self._max_cycle self._calc.init_guess = self._init_guess self._calc.kernel() logger.info( "PySCF kernel() converged: %s, e(hf): %s", self._calc.converged, self._calc.e_tot, )
def get_solver(self, transformation: Transformation) -> MinimumEigensolver: """Returns a VQE with a UCCSD wavefunction ansatz, based on ``transformation``. This works only with a ``FermionicTransformation``. Args: transformation: a fermionic qubit operator transformation. Returns: A VQE suitable to compute the ground state of the molecule transformed by ``transformation``. Raises: QiskitNatureError: in case a Transformation of wrong type is given. """ if not isinstance(transformation, FermionicTransformation): raise QiskitNatureError( 'VQEUCCSDFactory.getsolver() requires a FermionicTransformation') num_orbitals = transformation.molecule_info['num_orbitals'] num_particles = transformation.molecule_info['num_particles'] qubit_mapping = transformation.qubit_mapping two_qubit_reduction = transformation.molecule_info['two_qubit_reduction'] z2_symmetries = transformation.molecule_info['z2_symmetries'] initial_state = HartreeFock(num_orbitals, num_particles, qubit_mapping, two_qubit_reduction, z2_symmetries.sq_list) self._vqe.var_form = UCCSD(num_orbitals=num_orbitals, num_particles=num_particles, initial_state=initial_state, qubit_mapping=qubit_mapping, two_qubit_reduction=two_qubit_reduction, z2_symmetries=z2_symmetries, method_singles=self._method_singles, method_doubles=self._method_doubles, excitation_type=self._excitation_type, same_spin_doubles=self._same_spin_doubles) return self._vqe
def _check_for_errors(self) -> None: """ Checks for errors such as incorrect number of parameters and indices of orbitals. """ # number of parameters check if self._orbital_rotations_beta is None and self._orbital_rotations is not None: if len(self._orbital_rotations) != len(self._parameters): raise QiskitNatureError( 'Please specify same number of params ({}) as there are ' 'orbital rotations ({})'.format(len(self._parameters), len(self._orbital_rotations))) elif self._orbital_rotations_beta is not None and self._orbital_rotations is not None: if len(self._orbital_rotations) + len(self._orbital_rotations_beta) != len( self._parameters): raise QiskitNatureError( 'Please specify same number of params ({}) as there are ' 'orbital rotations ({})'.format(len(self._parameters), len(self._orbital_rotations))) # indices of rotated orbitals check for exc in self._orbital_rotations: if exc[0] > (self._dim_kappa_matrix - 1): raise QiskitNatureError( 'You specified entries that go outside ' 'the orbital rotation matrix dimensions {}, '.format(exc[0])) if exc[1] > (self._dim_kappa_matrix - 1): raise QiskitNatureError( 'You specified entries that go outside ' 'the orbital rotation matrix dimensions {}'.format(exc[1])) if self._orbital_rotations_beta is not None: for exc in self._orbital_rotations_beta: if exc[0] > (self._dim_kappa_matrix - 1): raise QiskitNatureError( 'You specified entries that go outside ' 'the orbital rotation matrix dimensions {}'.format(exc[0])) if exc[1] > (self._dim_kappa_matrix - 1): raise QiskitNatureError( 'You specified entries that go outside ' 'the orbital rotation matrix dimensions {}'.format(exc[1]))
def _run_single_point(self, point: float) -> EigenstateResult: """Run the sampler at the given single point Args: point: The value of the degree of freedom to evaluate. Returns: Results for a single point. Raises: QiskitNatureError: Invalid Driver """ # update molecule geometry and thus resulting Hamiltonian based on specified point if isinstance(self._driver, DeprecatedBaseDriver): warn_deprecated( "0.2.0", DeprecatedType.CLASS, f"{self._driver.__class__.__module__}.{self._driver.__class__.__qualname__}", new_name= "ElectronicStructureMoleculeDriver or VibrationalStructureMoleculeDriver", additional_msg="from qiskit_nature.drivers.second_quantization", ) elif not isinstance(self._driver, (ElectronicStructureMoleculeDriver, VibrationalStructureMoleculeDriver)): raise QiskitNatureError( "Driver must be ElectronicStructureMoleculeDriver or VibrationalStructureMoleculeDriver." ) self._driver.molecule.perturbations = [point] # find closest previously run point and take optimal parameters if isinstance( self._gss.solver, VariationalAlgorithm) and self._bootstrap: # type: ignore prev_points = list(self._points_optparams.keys()) prev_params = list(self._points_optparams.values()) n_pp = len(prev_points) # set number of points to bootstrap if self._extrapolator is None: n_boot = len(prev_points) # bootstrap all points else: n_boot = self._num_bootstrap # Set initial params # if prev_points not empty if prev_points: if n_pp <= n_boot: distances = np.array(point) - np.array( prev_points).reshape(n_pp, -1) # find min 'distance' from point to previous points min_index = np.argmin(np.linalg.norm(distances, axis=1)) # update initial point self._gss.solver.initial_point = prev_params[ min_index] # type: ignore else: # extrapolate using saved parameters opt_params = self._points_optparams param_sets = self._extrapolator.extrapolate( points=[point], param_dict=opt_params) # update initial point, note param_set is a dictionary self._gss.solver.initial_point = param_sets.get( point) # type: ignore # the output is an instance of EigenstateResult result = self._gss.solve(self._problem) # Save optimal point to bootstrap if isinstance(self._gss.solver, VariationalAlgorithm): # type: ignore # at every point evaluation, the optimal params are updated optimal_params = result.raw_result.optimal_point self._points_optparams[point] = optimal_params return result
def solve( self, problem: BaseProblem, aux_operators: Optional[List[Union[SecondQuantizedOp, PauliSumOp]]] = None, ) -> "AdaptVQEResult": """Computes the ground state. Args: problem: a class encoding a problem to be solved. aux_operators: Additional auxiliary operators to evaluate. Raises: QiskitNatureError: if a solver other than VQE or a ansatz other than UCCSD is provided or if the algorithm finishes due to an unforeseen reason. Returns: An AdaptVQEResult which is an ElectronicStructureResult but also includes runtime information about the AdaptVQE algorithm like the number of iterations, finishing criterion, and the final maximum gradient. """ second_q_ops = problem.second_q_ops() self._main_operator = self._qubit_converter.convert( second_q_ops[0], num_particles=problem.num_particles, sector_locator=problem.symmetry_sector_locator) aux_ops = self._qubit_converter.convert_match(second_q_ops[1:]) if aux_operators is not None: for aux_op in aux_operators: if isinstance(aux_op, SecondQuantizedOp): aux_ops.append( self._qubit_converter.convert_match(aux_op, True)) else: aux_ops.append(aux_op) if isinstance(self._solver, MinimumEigensolverFactory): vqe = self._solver.get_solver(problem, self._qubit_converter) else: vqe = self._solver if not isinstance(vqe, VQE): raise QiskitNatureError( "The AdaptVQE algorithm requires the use of the VQE solver") if not isinstance(vqe.ansatz, UCC): raise QiskitNatureError( "The AdaptVQE algorithm requires the use of the UCC ansatz") # We construct the ansatz once to be able to extract the full set of excitation operators. self._ansatz = copy.deepcopy(vqe.ansatz) self._ansatz._build() self._excitation_pool = copy.deepcopy(self._ansatz.operators) threshold_satisfied = False alternating_sequence = False max_iterations_exceeded = False prev_op_indices: List[int] = [] theta: List[float] = [] max_grad: Tuple[float, Optional[PauliSumOp]] = (0., None) iteration = 0 while self._max_iterations is None or iteration < self._max_iterations: iteration += 1 logger.info('--- Iteration #%s ---', str(iteration)) # compute gradients cur_grads = self._compute_gradients(theta, vqe) # pick maximum gradient max_grad_index, max_grad = max(enumerate(cur_grads), key=lambda item: np.abs(item[1][0])) # store maximum gradient's index for cycle detection prev_op_indices.append(max_grad_index) # log gradients if logger.isEnabledFor(logging.INFO): gradlog = "\nGradients in iteration #{}".format(str(iteration)) gradlog += "\nID: Excitation Operator: Gradient <(*) maximum>" for i, grad in enumerate(cur_grads): gradlog += '\n{}: {}: {}'.format(str(i), str(grad[1]), str(grad[0])) if grad[1] == max_grad[1]: gradlog += '\t(*)' logger.info(gradlog) if np.abs(max_grad[0]) < self._threshold: logger.info( "Adaptive VQE terminated successfully " "with a final maximum gradient: %s", str(np.abs(max_grad[0]))) threshold_satisfied = True break # check indices of picked gradients for cycles if self._check_cyclicity(prev_op_indices): logger.info("Alternating sequence found. Finishing.") logger.info("Final maximum gradient: %s", str(np.abs(max_grad[0]))) alternating_sequence = True break # add new excitation to self._ansatz self._excitation_list.append(max_grad[1]) theta.append(0.0) # run VQE on current Ansatz self._ansatz.operators = self._excitation_list vqe.ansatz = self._ansatz vqe.initial_point = theta raw_vqe_result = vqe.compute_minimum_eigenvalue( self._main_operator) theta = raw_vqe_result.optimal_point.tolist() else: # reached maximum number of iterations max_iterations_exceeded = True logger.info("Maximum number of iterations reached. Finishing.") logger.info("Final maximum gradient: %s", str(np.abs(max_grad[0]))) # once finished evaluate auxiliary operators if any if aux_ops is not None: aux_values = self.evaluate_operators(raw_vqe_result.eigenstate, aux_ops) else: aux_values = None raw_vqe_result.aux_operator_eigenvalues = aux_values if threshold_satisfied: finishing_criterion = 'Threshold converged' elif alternating_sequence: finishing_criterion = 'Aborted due to cyclicity' elif max_iterations_exceeded: finishing_criterion = 'Maximum number of iterations reached' else: raise QiskitNatureError( 'The algorithm finished due to an unforeseen reason!') electronic_result = problem.interpret(raw_vqe_result) result = AdaptVQEResult() result.combine(electronic_result) result.num_iterations = iteration result.final_max_gradient = max_grad[0] result.finishing_criterion = finishing_criterion logger.info('The final energy is: %s', str(result.computed_energies[0])) return result
def solve(self, driver: BaseDriver, aux_operators: Optional[Union[List[FermionicOperator], List[BosonicOperator]]] = None) \ -> Union[ElectronicStructureResult, VibronicStructureResult]: """Computes the ground state. Args: driver: a chemistry driver. aux_operators: Additional auxiliary ``FermionicOperator`` instances to evaluate at the ground state. Raises: QiskitNatureError: if a solver other than VQE or a variational form other than UCCSD is provided or if the algorithm finishes due to an unforeseen reason. Returns: An AdaptVQEResult which is an ElectronicStructureResult but also includes runtime information about the AdaptVQE algorithm like the number of iterations, finishing criterion, and the final maximum gradient. """ operator, aux_ops = self._transformation.transform(driver, aux_operators) vqe = self._solver.get_solver(self._transformation) vqe.operator = operator if not isinstance(vqe, VQE): raise QiskitNatureError("The AdaptVQE algorithm requires the use of the VQE solver") if not isinstance(vqe.var_form, UCCSD): raise QiskitNatureError( "The AdaptVQE algorithm requires the use of the UCCSD variational form") vqe.var_form.manage_hopping_operators() excitation_pool = vqe.var_form.excitation_pool threshold_satisfied = False alternating_sequence = False max_iterations_exceeded = False prev_op_indices: List[int] = [] theta: List[float] = [] max_grad: Tuple[float, Optional[PauliSumOp]] = (0., None) iteration = 0 while self._max_iterations is None or iteration < self._max_iterations: iteration += 1 logger.info('--- Iteration #%s ---', str(iteration)) # compute gradients cur_grads = self._compute_gradients(excitation_pool, theta, vqe) # pick maximum gradient max_grad_index, max_grad = max(enumerate(cur_grads), key=lambda item: np.abs(item[1][0])) # store maximum gradient's index for cycle detection prev_op_indices.append(max_grad_index) # log gradients if logger.isEnabledFor(logging.INFO): gradlog = "\nGradients in iteration #{}".format(str(iteration)) gradlog += "\nID: Excitation Operator: Gradient <(*) maximum>" for i, grad in enumerate(cur_grads): gradlog += '\n{}: {}: {}'.format(str(i), str(grad[1]), str(grad[0])) if grad[1] == max_grad[1]: gradlog += '\t(*)' logger.info(gradlog) if np.abs(max_grad[0]) < self._threshold: logger.info("Adaptive VQE terminated successfully " "with a final maximum gradient: %s", str(np.abs(max_grad[0]))) threshold_satisfied = True break # check indices of picked gradients for cycles if self._check_cyclicity(prev_op_indices): logger.info("Alternating sequence found. Finishing.") logger.info("Final maximum gradient: %s", str(np.abs(max_grad[0]))) alternating_sequence = True break # add new excitation to self._var_form vqe.var_form.push_hopping_operator(max_grad[1]) theta.append(0.0) # run VQE on current Ansatz vqe.initial_point = theta raw_vqe_result = vqe.compute_minimum_eigenvalue(operator) theta = raw_vqe_result.optimal_point.tolist() else: # reached maximum number of iterations max_iterations_exceeded = True logger.info("Maximum number of iterations reached. Finishing.") logger.info("Final maximum gradient: %s", str(np.abs(max_grad[0]))) # once finished evaluate auxiliary operators if any if aux_ops is not None: aux_values = self.evaluate_operators(raw_vqe_result.eigenstate, aux_ops) else: aux_values = None raw_vqe_result.aux_operator_eigenvalues = aux_values if threshold_satisfied: finishing_criterion = 'Threshold converged' elif alternating_sequence: finishing_criterion = 'Aborted due to cyclicity' elif max_iterations_exceeded: finishing_criterion = 'Maximum number of iterations reached' else: raise QiskitNatureError('The algorithm finished due to an unforeseen reason!') electronic_result = self.transformation.interpret(raw_vqe_result) result = AdaptVQEResult() result.combine(electronic_result) result.num_iterations = iteration result.final_max_gradient = max_grad[0] result.finishing_criterion = finishing_criterion logger.info('The final energy is: %s', str(result.computed_energies[0])) return result
def __init__( self, atom: Union[str, List[str]] = "H 0.0 0.0 0.0; H 0.0 0.0 0.735", unit: UnitsType = UnitsType.ANGSTROM, charge: int = 0, spin: int = 0, basis: str = "sto3g", method: MethodType = MethodType.RHF, xc_functional: str = "lda,vwn", xcf_library: str = "libxc", conv_tol: float = 1e-9, max_cycle: int = 50, init_guess: InitialGuess = InitialGuess.MINAO, max_memory: Optional[int] = None, chkfile: Optional[str] = None, ) -> None: """ Args: atom: A string (or a list thereof) denoting the elements and coordinates of all atoms in the system. Two formats are allowed; first, the PySCF-style `XYZ` format which is a list of strings formatted as `{element symbol} {x_coord} {y_coord} {z_coord}`. If a single string is given, the list entries should be joined by `;` as in the example: `H 0.0 0.0 0.0; H 0.0 0.0 0.735`. Second, the `Z-Matrix` format which is explained at 1_. The previous example would be written as `H; H 3 0.735`. See also 2_ for more details on geometry specifications supported by PySCF. unit: Denotes the unit of coordinates. Valid values are given by the ``UnitsType`` enum. charge: The charge of the molecule. spin: The spin of the molecule. In accordance with PySCF's definition, the spin equals :math:`2*S`, where :math:`S` is the total spin number of the molecule. basis: A basis set name as recognized by PySCF (3_), e.g. `sto3g` (the default), `321g`, etc. Note, that more advanced configuration options like a Dictionary or custom basis sets are not allowed for the moment. Refer to 4_ for an extensive list of PySCF's valid basis set names. method: The SCF method type to be used for the PySCF calculation. While the name refers to HF methods, the PySCFDriver also supports KS methods. Refer to the ``MethodType`` for a list of the supported methods. xc_functional: One of the predefined Exchange-Correlation functional names as recognized by PySCF (5_). Defaults to PySCF's default: 'lda,vwn'. __Note: this setting only has an effect when a KS method is chosen for `method`.__ xcf_library: The Exchange-Correlation functional library to be used. This can be either 'libxc' (the default) or 'xcfun'. Depending on this value, a different set of values for `xc_functional` will be available. Refer to 5_ for more details. conv_tol: The SCF convergence tolerance. See 6_ for more details. max_cycle: The maximum number of SCF iterations. See 6_ for more details. init_guess: The method to make the initial guess for the SCF starting point. Valid values are given by the ``InitialGuess`` enum. See 6_ for more details. max_memory: The maximum memory that PySCF should use. See 6_ for more details. chkfile: The path to a PySCF checkpoint file from which to load a previously run calculation. The data stored in this file is assumed to be already converged. Refer to 6_ and 7_ for more details. Raises: QiskitNatureError: An invalid input was supplied. .. _1: https://en.wikipedia.org/wiki/Z-matrix_(chemistry) .. _2: https://pyscf.org/user/gto.html#geometry .. _3: https://pyscf.org/user/gto.html#basis-set .. _4: https://pyscf.org/pyscf_api_docs/pyscf.gto.basis.html#module-pyscf.gto.basis .. _5: https://pyscf.org/user/dft.html#predefined-xc-functionals-and-functional-aliases .. _6: https://pyscf.org/pyscf_api_docs/pyscf.scf.html#module-pyscf.scf.hf .. _7: https://pyscf.org/pyscf_api_docs/pyscf.lib.html#module-pyscf.lib.chkfile """ super().__init__() # pylint: disable=import-error from pyscf import gto, scf # First, ensure that PySCF supports the method PySCFDriver.check_method_supported(method) if isinstance(atom, list): atom = ";".join(atom) elif isinstance(atom, str): atom = atom.replace("\n", ";") else: raise QiskitNatureError( f"`atom` must be either a `str` or `List[str]`, but you passed {atom}" ) validate_min("max_cycle", max_cycle, 1) # we use the property-setter to deal with conversion self.atom = atom self._unit = unit self._charge = charge self._spin = spin self._basis = basis self._method = method self._xc_functional = xc_functional self.xcf_library = xcf_library # validate choice in property setter self._conv_tol = conv_tol self._max_cycle = max_cycle self._init_guess = init_guess.value self._max_memory = max_memory self._chkfile = chkfile self._mol: gto.Mole = None self._calc: scf.HF = None
def solve(self, driver: BaseDriver, aux_operators: Optional[Union[List[FermionicOperator], List[BosonicOperator]]] = None) \ -> ElectronicStructureResult: self._initialize_additional_parameters(driver) if not isinstance(self._transformation, FermionicTransformation): raise QiskitNatureError('OrbitalOptimizationVQE requires a FermionicTransformation.') self._vqe._eval_count = 0 # initial orbital rotation starting point is provided if self._orbital_rotation.matrix_a is not None and self._orbital_rotation.matrix_b is not \ None: self._qmolecule_rotated = copy.copy(self._qmolecule) OrbitalOptimizationVQE._rotate_orbitals_in_qmolecule( self._qmolecule_rotated, self._orbital_rotation) operator, aux_operators = self._transformation._do_transform(self._qmolecule_rotated) self._vqe.operator = operator self._vqe.aux_operators = aux_operators logger.info( '\n\nSetting the initial value for OO matrices and rotating Hamiltonian \n') logger.info('Optimising Orbital Coefficient Rotation Alpha: \n%s', repr(self._orbital_rotation.matrix_a)) logger.info('Optimising Orbital Coefficient Rotation Beta: \n%s', repr(self._orbital_rotation.matrix_b)) # save the original number of parameters as we modify their number to bypass the # error checks that are not tailored to OOVQE # iterative method if self._iterative_oo: for _ in range(self._iterative_oo_iterations): # optimize wavefunction ansatz logger.info('OrbitalOptimizationVQE: Ansatz optimization, orbitals fixed.') self._vqe.var_form._num_parameters = self.var_form_num_parameters self._vqe.var_form._bounds = self.var_form_bounds vqresult_wavefun = self._vqe.find_minimum( initial_point=self.initial_point[:self.var_form_num_parameters], var_form=self._vqe.var_form, cost_fn=self._vqe._energy_evaluation, optimizer=self._vqe.optimizer) self.initial_point[:self.var_form_num_parameters] = vqresult_wavefun.optimal_point # optimize orbitals logger.info('OrbitalOptimizationVQE: Orbital optimization, ansatz fixed.') self._vqe.var_form._bounds = self._bound_oo self._vqe.var_form._num_parameters = self._orbital_rotation.num_parameters self._fixed_wavefunction_params = vqresult_wavefun.optimal_point vqresult = self._vqe.find_minimum( initial_point=self.initial_point[self.var_form_num_parameters:], var_form=self._vqe.var_form, cost_fn=self._energy_evaluation_oo, optimizer=self._vqe.optimizer) self.initial_point[self.var_form_num_parameters:] = vqresult.optimal_point else: # simultaneous method (ansatz and orbitals are optimized at the same time) self._vqe.var_form._bounds = self._bounds self._vqe.var_form._num_parameters = len(self._bounds) vqresult = self._vqe.find_minimum(initial_point=self.initial_point, var_form=self._vqe.var_form, cost_fn=self._energy_evaluation_oo, optimizer=self._vqe.optimizer) # write original number of parameters to avoid errors due to parameter number mismatch self._vqe.var_form._num_parameters = self.var_form_num_parameters # extend VQE returned information with additional outputs result = OOVQEResult() result.computed_electronic_energy = vqresult.optimal_value result.num_optimizer_evals = vqresult.optimizer_evals result.optimal_point = vqresult.optimal_point if self._iterative_oo: result.optimal_point_ansatz = self.initial_point[self.var_form_num_parameters:] result.optimal_point_orbitals = self.initial_point[:self.var_form_num_parameters] else: result.optimal_point_ansatz = vqresult.optimal_point[:self.var_form_num_parameters] result.optimal_point_orbitals = vqresult.optimal_point[self.var_form_num_parameters:] result.eigenenergies = np.asarray([vqresult.optimal_value + 0j]) # copy parameters bypass the error checks that are not tailored to OOVQE _ret_temp_params = copy.copy(vqresult.optimal_point) self._vqe._ret = {} self._vqe._ret['opt_params'] = vqresult.optimal_point[:self.var_form_num_parameters] if self._iterative_oo: self._vqe._ret['opt_params'] = vqresult_wavefun.optimal_point result.eigenstates = [self._vqe.get_optimal_vector()] if not self._iterative_oo: self._vqe._ret['opt_params'] = _ret_temp_params if self._vqe.aux_operators is not None: # copy parameters bypass the error checks that are not tailored to OOVQE self._vqe._ret['opt_params'] = vqresult.optimal_point[:self.var_form_num_parameters] if self._iterative_oo: self._vqe._ret['opt_params'] = vqresult_wavefun.optimal_point self._vqe._eval_aux_ops() result.aux_operator_eigenvalues = self._vqe._ret['aux_ops'][0] if not self._iterative_oo: self._vqe._ret['opt_params'] = _ret_temp_params result.cost_function_evals = self._vqe._eval_count self.transformation.interpret(result) return result