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
Пример #3
0
    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()
Пример #4
0
    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
Пример #5
0
    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
Пример #6
0
    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
Пример #8
0
    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
Пример #9
0
    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
Пример #10
0
    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))
Пример #11
0
    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))
Пример #12
0
    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()
Пример #13
0
 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
Пример #14
0
    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))
Пример #15
0
    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
Пример #16
0
    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]
Пример #18
0
    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
Пример #19
0
    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
Пример #20
0
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)]
Пример #23
0
    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,
            )
Пример #24
0
    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]))
Пример #26
0
    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
Пример #27
0
    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
Пример #28
0
    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
Пример #29
0
    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