def _validate_num_orbitals(self, nelec_inactive: int,
                               molecule_data: QMolecule):
        """Validates the number of orbitals.

        Args:
            nelec_inactive: the computed number of inactive electrons.
            molecule_data: the `QMolecule` to be transformed.

        Raises:
            QiskitNatureError: if more orbitals were requested than are available in total or if the
                               number of selected orbitals mismatches the specified number of active
                               orbitals.
        """
        if self._active_orbitals is None:
            norbs_inactive = nelec_inactive // 2
            if norbs_inactive + self._num_molecular_orbitals > molecule_data.num_molecular_orbitals:
                raise QiskitNatureError(
                    "More orbitals requested than available.")
        else:
            if self._num_molecular_orbitals != len(self._active_orbitals):
                raise QiskitNatureError(
                    "The number of selected active orbital indices does not "
                    "match the specified number of active orbitals.")
            if max(self._active_orbitals
                   ) >= molecule_data.num_molecular_orbitals:
                raise QiskitNatureError(
                    "More orbitals requested than available.")
            if sum(self._mo_occ_total[
                    self._active_orbitals]) != self._num_electrons:
                raise QiskitNatureError(
                    "The number of electrons in the selected active orbitals "
                    "does not match the specified number of active electrons.")
Ejemplo n.º 2
0
    def _validate_num_orbitals(self, nelec_inactive: int,
                               particle_number: ParticleNumber) -> None:
        """Validates the number of orbitals.

        Args:
            nelec_inactive: the computed number of inactive electrons.
            particle_number: the `ParticleNumber` containing system size information.

        Raises:
            QiskitNatureError: if more orbitals were requested than are available in total or if the
                               number of selected orbitals mismatches the specified number of active
                               orbitals.
        """
        if self._active_orbitals is None:
            norbs_inactive = nelec_inactive // 2
            if (norbs_inactive + self._num_molecular_orbitals >
                    particle_number._num_spin_orbitals // 2):
                raise QiskitNatureError(
                    "More orbitals requested than available.")
        else:
            if self._num_molecular_orbitals != len(self._active_orbitals):
                raise QiskitNatureError(
                    "The number of selected active orbital indices does not "
                    "match the specified number of active orbitals.")
            if max(self._active_orbitals
                   ) >= particle_number._num_spin_orbitals // 2:
                raise QiskitNatureError(
                    "More orbitals requested than available.")
            expected_num_electrons = (self._num_electrons if isinstance(
                self._num_electrons, int) else sum(self._num_electrons))
            if sum(self._mo_occ_total[
                    self._active_orbitals]) != expected_num_electrons:
                raise QiskitNatureError(
                    "The number of electrons in the selected active orbitals "
                    "does not match the specified number of active electrons.")
Ejemplo n.º 3
0
    def run(self) -> GroupedSecondQuantizedProperty:
        """
        Returns:
            GroupedSecondQuantizedProperty re-constructed from the HDF5 file.

        Raises:
            LookupError: file not found.
            QiskitNatureError: Legacy HDF5 format.
            QiskitNatureError: if the HDF5 file did not contain a GroupedSecondQuantizedProperty.
        """
        hdf5_file = self._get_path()

        with h5py.File(hdf5_file, "r") as file:
            if "origin_driver" in file.keys():
                raise QiskitNatureError(
                    "Your HDF5 file contains a not supported legacy QMolecule object!"
                )

        driver_result = load_from_hdf5(str(hdf5_file))

        if not isinstance(driver_result, GroupedSecondQuantizedProperty):
            raise QiskitNatureError(
                f"Expected a GroupedSecondQuantizedProperty but found a {type(driver_result)} "
                "object instead.")

        return driver_result
Ejemplo n.º 4
0
    def _run_psi4(input_file, output_file):

        # Run psi4.
        process = None
        try:
            with subprocess.Popen(
                [_optionals.PSI4, input_file, output_file],
                    stdout=subprocess.PIPE,
                    universal_newlines=True,
            ) as process:
                stdout, _ = process.communicate()
                process.wait()
        except Exception as ex:
            if process is not None:
                process.kill()

            raise QiskitNatureError(
                f"{_optionals.PSI4} run has failed") from ex

        if process.returncode != 0:
            errmsg = ""
            if stdout is not None:
                lines = stdout.splitlines()
                for line in lines:
                    logger.error(line)
                    errmsg += line + "\n"
            raise QiskitNatureError(
                f"{_optionals.PSI4} process return code {process.returncode}\n{errmsg}"
            )
Ejemplo n.º 5
0
def _parse_molecule(val, units, charge, multiplicity):
    val = _check_molecule_format(val)

    parts = [x.strip() for x in val.split(";")]
    if parts is None or len(parts) < 1:  # pylint: disable=len-as-condition
        raise QiskitNatureError("Molecule format error: " + val)
    geom = []
    for n, _ in enumerate(parts):
        part = parts[n]
        geom.append(_parse_atom(part))

    if len(geom) < 1:  # pylint: disable=len-as-condition
        raise QiskitNatureError("Molecule format error: " + val)

    try:
        # pylint: disable=import-error,import-outside-toplevel
        from pyquante2 import (
            molecule, )

        return molecule(geom,
                        units=units,
                        charge=charge,
                        multiplicity=multiplicity)
    except Exception as exc:
        raise QiskitNatureError("Failed to create molecule") from exc
Ejemplo n.º 6
0
def _check_molecule_format(val):
    """If it seems to be zmatrix rather than xyz format we convert before returning"""
    atoms = [x.strip() for x in val.split(";")]
    if atoms is None or len(atoms) < 1:  # pylint: disable=len-as-condition
        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:
            zmat = []
            for atom in atoms:
                parts = [x.strip() for x in atom.split(" ")]
                z = [parts[0]]
                for i in range(1, len(parts), 2):
                    z.append(int(parts[i]))
                    z.append(float(parts[i + 1]))
                zmat.append(z)
            xyz = z2xyz(zmat)
            new_val = ""
            for atm in xyz:
                if atm[0].upper() == "X":
                    continue
                if new_val:
                    new_val += "; "
                new_val += "{} {} {} {}".format(atm[0], atm[1], atm[2], atm[3])
            return new_val
        except Exception as exc:
            raise QiskitNatureError("Failed to convert atom string: " +
                                    val) from exc

    return val
Ejemplo n.º 7
0
    def _find_taper_op(
        self,
        qubit_op: PauliSumOp,
        sector_locator: Optional[Callable[[Z2Symmetries],
                                          Optional[List[int]]]] = None,
    ) -> Tuple[PauliSumOp, Z2Symmetries]:
        # Return operator unchanged and empty symmetries if we do not taper
        tapered_qubit_op = qubit_op
        z2_symmetries = self._no_symmetries

        # If we were given a sector, or one might be located, we first need to find any symmetries
        if self.z2symmetry_reduction is not None:
            z2_symmetries = Z2Symmetries.find_Z2_symmetries(qubit_op)
            if z2_symmetries.is_empty():
                logger.debug("No Z2 symmetries found")
            else:
                # As we have symmetries, if we have a sector locator, if that provides one back
                # it will override any value defined on constructor
                if sector_locator is not None and self.z2symmetry_reduction == "auto":
                    z2symmetry_reduction = sector_locator(z2_symmetries)
                    if z2symmetry_reduction is not None:
                        self.z2symmetry_reduction = z2symmetry_reduction  # Overrides any value

                    # We may end up that neither were we given a sector nor that the locator
                    # returned one. Since though we may have found valid symmetries above we should
                    # simply just forget about them so as not to return something we are not using.
                    if self.z2symmetry_reduction is None:
                        z2_symmetries = self._no_symmetries

        # So now if we have a sector and have symmetries we found we can attempt to taper
        if (self.z2symmetry_reduction is not None
                and self.z2symmetry_reduction != "auto"
                and not z2_symmetries.is_empty()):
            # check sector definition fits to symmetries found
            if len(self._z2symmetry_reduction) != len(
                    z2_symmetries.symmetries):
                raise QiskitNatureError(
                    "z2symmetry_reduction tapering values list has "
                    "invalid length {} should be {}".format(
                        len(self._z2symmetry_reduction),
                        len(z2_symmetries.symmetries)))
            # Check all operators commute with main operator's symmetry
            logger.debug(
                "Sanity check that operator commutes with the symmetry")
            symmetry_ops = []
            for symmetry in z2_symmetries.symmetries:
                symmetry_ops.append(
                    PauliSumOp.from_list([(symmetry.to_label(), 1.0)]))
            commutes = QubitConverter._check_commutes(symmetry_ops, qubit_op)
            if not commutes:
                raise QiskitNatureError(
                    "Z2 symmetry failure. The operator must commute "
                    "with symmetries found from it!")

            z2_symmetries.tapering_values = self._z2symmetry_reduction
            tapered_qubit_op = z2_symmetries.taper(
                qubit_op) if commutes else None

        return tapered_qubit_op, z2_symmetries
Ejemplo n.º 8
0
def run_g16(cfg: str) -> str:
    """
    Runs Gaussian 16. We capture stdout and if error log the last 10 lines that
    should include the error description from Gaussian.

    Args:
        cfg: configuration

    Returns:
        Text log output

    Raises:
        QiskitNatureError: Failed run or log not captured.

    """
    process = None
    try:
        with Popen(_optionals.GAUSSIAN_16,
                   stdin=PIPE,
                   stdout=PIPE,
                   universal_newlines=True) as process:
            stdout, _ = process.communicate(cfg)
            process.wait()
    except Exception as ex:
        if process is not None:
            process.kill()

        raise QiskitNatureError(
            f"{_optionals.GAUSSIAN_16_DESC} run has failed") from ex

    if process.returncode != 0:
        errmsg = ""
        if stdout is not None:
            lines = stdout.splitlines()
            start = 0
            if len(lines) > 10:
                start = len(lines) - 10
            for i in range(start, len(lines)):
                logger.error(lines[i])
                errmsg += lines[i] + "\n"
        raise QiskitNatureError(
            f"{_optionals.GAUSSIAN_16_DESC} process return code {process.returncode}\n{errmsg}"
        )

    all_text = ""
    if stdout is not None:
        lines = stdout.splitlines()
        for line in lines:
            all_text += line + "\n"

    logger.debug("Gaussian output:\n%s", all_text)

    return all_text
Ejemplo n.º 9
0
    def _validate_num_electrons(self, nelec_inactive: int):
        """Validates the number of electrons.

        Args:
            nelec_inactive: the computed number of inactive electrons.

        Raises:
            QiskitNatureError: if the number of inactive electrons is either negative or odd.
        """
        if nelec_inactive < 0:
            raise QiskitNatureError("More electrons requested than available.")
        if nelec_inactive % 2 != 0:
            raise QiskitNatureError("The number of inactive electrons must be even.")
Ejemplo n.º 10
0
    def _get_excitation_generators(self) -> List[Callable]:
        logger.debug('Gathering excitation generators...')
        generators: List[Callable] = []

        extra_kwargs = {
            'alpha_spin': self._alpha_spin,
            'beta_spin': self._beta_spin,
            'max_spin_excitation': self._max_spin_excitation
        }

        if isinstance(self.excitations, str):
            for exc in self.excitations:
                generators.append(
                    partial(generate_fermionic_excitations,
                            num_excitations=self.EXCITATION_TYPE[exc],
                            **extra_kwargs))
        elif isinstance(self.excitations, int):
            generators.append(
                partial(generate_fermionic_excitations,
                        num_excitations=self.excitations,
                        **extra_kwargs))
        elif isinstance(self.excitations, list):
            for exc in self.excitations:  # type: ignore
                generators.append(
                    partial(generate_fermionic_excitations,
                            num_excitations=exc,
                            **extra_kwargs))
        elif callable(self.excitations):
            generators = [self.excitations]
        else:
            raise QiskitNatureError(
                "Invalid excitation configuration: {}".format(
                    self.excitations))

        return generators
Ejemplo n.º 11
0
def _parse_atom(val):
    if val is None or len(val) < 1:  # pylint: disable=len-as-condition
        raise QiskitNatureError("Molecule atom format error: empty")

    parts = re.split(r"\s+", val)
    if len(parts) != 4:
        raise QiskitNatureError("Molecule atom format error: " + val)

    parts[0] = parts[0].lower().capitalize()
    if not parts[0].isdigit():
        if parts[0] in QMolecule.symbols:
            parts[0] = QMolecule.symbols.index(parts[0])
        else:
            raise QiskitNatureError("Molecule atom symbol error: " + parts[0])

    return int(float(parts[0])), float(parts[1]), float(parts[2]), float(parts[3])
Ejemplo n.º 12
0
    def _single_mul(cls, label1: str, label2: str) -> Tuple[str, complex]:
        if len(label1) != len(label2):
            raise QiskitNatureError("Operators act on Fermion Registers of different length")

        new_label = []
        sign = 1

        # count the number of `+` and `-` in the first label ahead of time
        count = label1.count("+") + label1.count("-")

        for pair in zip(label1, label2):
            # update the count as we progress
            char1, char2 = pair
            if char1 in "+-":
                count -= 1

            new_char = cls._MAPPING[pair]
            if new_char == "0":
                # if the new symbol is a zero-op, return early
                return "I" * len(label1), 0
            new_label.append(new_char)
            # NOTE: we can ignore the type because the only scenario where an `int` occurs is caught
            # by the `if`-statement above.

            # If char2 is one of `+` or `-` we pick up a phase when commuting it to the position
            # of char1. However, we only care about this if the number of permutations has odd
            # parity.
            if count % 2 and char2 in "+-":
                sign *= -1

        return "".join(new_label), sign
    def to_second_q_op(self) -> VibrationalOp:
        """Creates the operator representing the Hamiltonian defined by these vibrational integrals.

        Returns:
            The :class:`~qiskit_nature.second_q.operators.VibrationalOp` given by these
            vibrational integrals.

        Raises:
            QiskitNatureError: if no basis has been set yet.
        """
        try:
            matrix = self.to_basis()
        except QiskitNatureError as exc:
            raise QiskitNatureError() from exc

        num_modals_per_mode = self.basis._num_modals_per_mode
        num_modes = len(num_modals_per_mode)

        nonzero = np.nonzero(matrix)

        if not np.any(np.asarray(nonzero)):
            return VibrationalOp.zero(num_modes, num_modals_per_mode)

        labels = []

        for coeff, indices in zip(matrix[nonzero], zip(*nonzero)):
            # the indices need to be grouped into triplets of the form: (mode, modal_1, modal_2)
            grouped_indices = [
                tuple(int(j) for j in indices[i : i + 3]) for i in range(0, len(indices), 3)
            ]
            # the index groups need to processed in sorted order to produce a valid label
            coeff_label = self._create_label_for_coeff(sorted(grouped_indices))
            labels.append((coeff_label, coeff))

        return VibrationalOp(labels, num_modes, num_modals_per_mode)
    def transform_basis(self, transform: ElectronicBasisTransform) -> OneBodyElectronicIntegrals:
        # pylint: disable=line-too-long
        """Transforms the integrals according to the given transform object.

        If the integrals are already in the correct basis, ``self`` is returned.

        Args:
            transform: the transformation object with the integral coefficients.

        Returns:
            The transformed
            :class:`~qiskit_nature.properties.second_quantization.electronic.integrals.ElectronicIntegrals`.

        Raises:
            QiskitNatureError: if the integrals do not match
                :class:`~qiskit_nature.properties.second_quantization.electronic.bases.ElectronicBasisTransform.initial_basis`.
        """
        if self._basis == transform.final_basis:
            return self

        if self._basis != transform.initial_basis:
            raise QiskitNatureError(
                f"The integrals' basis, {self._basis}, does not match the initial basis of the "
                f"transform, {transform.initial_basis}."
            )

        matrix_a = np.dot(np.dot(transform.coeff_alpha.T, self._matrices[0]), transform.coeff_alpha)
        matrix_b = None
        if self._matrices[1] is not None or not transform.is_alpha_equal_beta():
            matrix_b = np.dot(
                np.dot(transform.coeff_beta.T, self.get_matrix(1)), transform.coeff_beta
            )
        return OneBodyElectronicIntegrals(transform.final_basis, (matrix_a, matrix_b))
Ejemplo n.º 15
0
    def _determine_active_space(
        self, grouped_property: GroupedElectronicProperty
    ) -> Tuple[List[int], List[int]]:
        """Determines the active and inactive orbital indices.

        Args:
            grouped_property: the `ElectronicStructureDriverResult` to be transformed.

        Returns:
            The list of active and inactive orbital indices.

        Raises:
            QiskitNatureError: if a GroupedElectronicProperty is provided which is not also an
                               ElectronicElectronicStructureDriverResult.
        """
        if not isinstance(grouped_property, ElectronicStructureDriverResult):
            raise QiskitNatureError(
                "The FreezeCoreTransformer requires an `ElectronicStructureDriverResult`, not a "
                f"property of type {type(grouped_property)}."
            )

        molecule = grouped_property.molecule
        particle_number = grouped_property.get_property("ParticleNumber")

        inactive_orbs_idxs = list(range(self.count_core_orbitals(molecule.atoms)))
        if self._remove_orbitals is not None:
            inactive_orbs_idxs.extend(self._remove_orbitals)
        active_orbs_idxs = [
            o for o, _ in enumerate(particle_number.occupation_alpha) if o not in inactive_orbs_idxs
        ]
        self._active_orbitals = active_orbs_idxs
        self._num_molecular_orbitals = len(active_orbs_idxs)

        return (active_orbs_idxs, inactive_orbs_idxs)
Ejemplo n.º 16
0
    def run(self) -> GaussianLogResult:  # type: ignore
        """Runs the driver to produce a result given the supplied job control file.

        Returns:
            A log file result.

        Raises:
            QiskitNatureError: Missing output log
        """
        # The job control file, needs to end with a blank line to be valid for
        # Gaussian to process it. We simply add the blank line here if not.
        cfg = self._jcf
        while not cfg.endswith("\n\n"):
            cfg += "\n"

        logger.debug(
            "User supplied job control file raw: '%s'",
            cfg.replace("\r", "\\r").replace("\n", "\\n"),
        )
        logger.debug("User supplied job control file\n%s", cfg)

        all_text = run_g16(cfg)
        if not all_text:
            raise QiskitNatureError("Failed to capture log from stdout")

        return GaussianLogResult(all_text)
Ejemplo n.º 17
0
    def run_pyquante(self):
        """Runs the PyQuante 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 pyquante2 import rhf, uhf, rohf, basisset

        self._bfs = basisset(self._mol, self.basis.value)

        if self.method == MethodType.RHF:
            self._calc = rhf(self._mol, self._bfs)
        elif self.method == MethodType.ROHF:
            self._calc = rohf(self._mol, self._bfs)
        elif self.method == MethodType.UHF:
            self._calc = uhf(self._mol, self._bfs)
        else:
            raise QiskitNatureError(f"Invalid method type: {self.method}")

        self._calc.converge(tol=self.tol, maxiters=self.maxiters)
        logger.debug("PyQuante2 processing information:\n%s", self._calc)
Ejemplo n.º 18
0
def compute_integrals(atoms,
                      units,
                      charge,
                      multiplicity,
                      basis,
                      hf_method="rhf",
                      tol=1e-8,
                      maxiters=100):
    """Compute integrals"""
    # Get config from input parameters
    # Molecule is in this format xyz as below or in Z-matrix e.g "H; O 1 1.08; H 2 1.08 1 107.5":
    # atoms=H .0 .0 .0; H .0 .0 0.2
    # units=Angstrom
    # charge=0
    # multiplicity=1
    # where we support symbol for atom as well as number

    units = _check_units(units)
    mol = _parse_molecule(atoms, units, charge, multiplicity)
    hf_method = hf_method.lower()

    try:
        q_mol = _calculate_integrals(mol, basis, hf_method, tol, maxiters)
    except Exception as exc:
        raise QiskitNatureError(
            "Failed electronic structure computation") from exc

    return q_mol
Ejemplo n.º 19
0
    def _get_excitation_generators(self) -> List[Callable]:
        logger.debug("Gathering excitation generators...")
        generators: List[Callable] = []

        if isinstance(self.excitations, str):
            for exc in self.excitations:
                generators.append(
                    partial(
                        generate_vibration_excitations,
                        num_excitations=self.EXCITATION_TYPE[exc],
                    ))
        elif isinstance(self.excitations, int):
            generators.append(
                partial(
                    generate_vibration_excitations,
                    num_excitations=self.excitations,
                ))
        elif isinstance(self.excitations, list):
            for exc in self.excitations:  # type: ignore
                generators.append(
                    partial(
                        generate_vibration_excitations,
                        num_excitations=exc,
                    ))
        elif callable(self.excitations):
            generators = [self.excitations]
        else:
            raise QiskitNatureError(
                f"Invalid excitation configuration: {self.excitations}")

        return generators
def _build_single_hopping_operator(
    excitation: Tuple[Tuple[int, ...], Tuple[int, ...]],
    num_spin_orbitals: int,
    qubit_converter: QubitConverter,
) -> Tuple[PauliSumOp, List[bool]]:
    label = ["I"] * num_spin_orbitals
    for occ in excitation[0]:
        label[occ] = "+"
    for unocc in excitation[1]:
        label[unocc] = "-"
    fer_op = FermionicOp(("".join(label), 4.0**len(excitation[0])))

    qubit_op: PauliSumOp = qubit_converter.convert_match(fer_op)
    z2_symmetries = qubit_converter.z2symmetries

    commutativities = []
    if not z2_symmetries.is_empty():
        for symmetry in z2_symmetries.symmetries:
            symmetry_op = PauliSumOp.from_list([(symmetry.to_label(), 1.0)])
            commuting = qubit_op.primitive.table.commutes_with_all(
                symmetry_op.primitive.table)
            anticommuting = qubit_op.primitive.table.anticommutes_with_all(
                symmetry_op.primitive.table)

            if commuting != anticommuting:  # only one of them is True
                if commuting:
                    commutativities.append(True)
                elif anticommuting:
                    commutativities.append(False)
            else:
                raise QiskitNatureError(
                    "Symmetry {} is nor commute neither anti-commute "
                    "to exciting operator.".format(symmetry.to_label()))

    return qubit_op, commutativities
Ejemplo n.º 21
0
def _check_units(units):
    if units.lower() in ["angstrom", "ang", "a"]:
        units = "Angstrom"
    elif units.lower() in ["bohr", "b"]:
        units = "Bohr"
    else:
        raise QiskitNatureError("Molecule units format error: " + units)
    return units
Ejemplo n.º 22
0
 def _validate_num_particles(self, num_particles):
     try:
         assert num_particles[0] == num_particles[1]
     except AssertionError as exc:
         raise QiskitNatureError(
             'The PUCCD Ansatz only works for singlet-spin systems. However, you specified '
             'differing numbers of alpha and beta electrons:',
             str(num_particles)) from exc
Ejemplo n.º 23
0
def check_valid() -> None:
    """Checks if Gaussian is installed and available"""
    if _G16PROG is None:
        raise QiskitNatureError(
            "Could not locate {} executable '{}'. Please check that it is installed correctly.".format(
                _GAUSSIAN_16_DESC, _GAUSSIAN_16
            )
        )
Ejemplo n.º 24
0
 def num_particles(self) -> Tuple[int, int]:
     if self._grouped_property_transformed is None:
         raise QiskitNatureError(
             "`num_particles` is only available _after_ `second_q_ops()` has been called! "
             "Note, that if you run this manually, the method will run again during solving."
         )
     return self._grouped_property_transformed.get_property(
         "ParticleNumber").num_particles
Ejemplo n.º 25
0
def _check_units(units):
    if units.lower() in ["angstrom", "ang", "a"]:
        units = 'Angstrom'
    elif units.lower() in ["bohr", "b"]:
        units = 'Bohr'
    else:
        raise QiskitNatureError('Molecule units format error: ' + units)
    return units
Ejemplo n.º 26
0
    def get_qubit_operators(
        self,
        problem: BaseProblem,
        aux_operators: Optional[ListOrDictType[Union[SecondQuantizedOp, PauliSumOp]]] = None,
    ) -> Tuple[PauliSumOp, Optional[ListOrDictType[PauliSumOp]]]:
        """Gets the operator and auxiliary operators, and transforms the provided auxiliary operators"""
        # Note that ``aux_ops`` contains not only the transformed ``aux_operators`` passed by the
        # user but also additional ones from the transformation
        second_q_ops = problem.second_q_ops()
        aux_second_q_ops: ListOrDictType[SecondQuantizedOp]
        if isinstance(second_q_ops, list):
            main_second_q_op = second_q_ops[0]
            aux_second_q_ops = second_q_ops[1:]
        elif isinstance(second_q_ops, dict):
            name = problem.main_property_name
            main_second_q_op = second_q_ops.pop(name, None)
            if main_second_q_op is None:
                raise ValueError(
                    f"The main `SecondQuantizedOp` associated with the {name} property cannot be "
                    "`None`."
                )
            aux_second_q_ops = second_q_ops

        main_operator = self._qubit_converter.convert(
            main_second_q_op,
            num_particles=problem.num_particles,
            sector_locator=problem.symmetry_sector_locator,
        )
        aux_ops = self._qubit_converter.convert_match(aux_second_q_ops)

        if aux_operators is not None:
            wrapped_aux_operators: ListOrDict[Union[SecondQuantizedOp, PauliSumOp]] = ListOrDict(
                aux_operators
            )
            for name_aux, aux_op in iter(wrapped_aux_operators):
                if isinstance(aux_op, SecondQuantizedOp):
                    converted_aux_op = self._qubit_converter.convert_match(aux_op, True)
                else:
                    converted_aux_op = aux_op
                if isinstance(aux_ops, list):
                    aux_ops.append(converted_aux_op)
                elif isinstance(aux_ops, dict):
                    if name_aux in aux_ops.keys():
                        raise QiskitNatureError(
                            f"The key '{name_aux}' is already taken by an internally constructed "
                            "auxiliary operator! Please use a different name for your custom "
                            "operator."
                        )
                    aux_ops[name_aux] = converted_aux_op

        if isinstance(self._solver, EigensolverFactory):
            # this must be called after transformation.transform
            self._solver = self._solver.get_solver(problem)

        # if the eigensolver does not support auxiliary operators, reset them
        if not self._solver.supports_aux_operators():
            aux_ops = None
        return main_operator, aux_ops
Ejemplo n.º 27
0
    def _parse_atom(val: str) -> Tuple[int, float, float, float]:
        if val is None or len(val) < 1:
            raise QiskitNatureError("Molecule atom format error: empty")

        parts = re.split(r"\s+", val)
        if len(parts) != 4:
            raise QiskitNatureError("Molecule atom format error: " + val)

        parts[0] = parts[0].lower().capitalize()
        if not parts[0].isdigit():
            if parts[0] in PERIODIC_TABLE:
                parts[0] = PERIODIC_TABLE.index(parts[0])
            else:
                raise QiskitNatureError("Molecule atom symbol error: " +
                                        parts[0])

        return int(float(parts[0])), float(parts[1]), float(parts[2]), float(
            parts[3])
Ejemplo n.º 28
0
    def __init__(
        self,
        atoms: Union[str, List[str]] = "H 0.0 0.0 0.0; H 0.0 0.0 0.735",
        units: UnitsType = UnitsType.ANGSTROM,
        charge: int = 0,
        multiplicity: int = 1,
        basis: BasisType = BasisType.BSTO3G,
        method: MethodType = MethodType.RHF,
        tol: float = 1e-8,
        maxiters: int = 100,
    ) -> None:
        """
        Args:
            atoms: Atoms list or string separated by semicolons or line breaks. Each element in the
                list is an atom followed by position e.g. `H 0.0 0.0 0.5`. The preceding example
                shows the `XYZ` format for position but `Z-Matrix` format is supported too here.
            units: Angstrom or Bohr.
            charge: Charge on the molecule.
            multiplicity: Spin multiplicity (2S+1)
            basis: Basis set; sto3g, 6-31g or 6-31g**
            method: Hartree-Fock Method type.
            tol: Convergence tolerance see pyquante2.scf hamiltonians and iterators
            maxiters: Convergence max iterations see pyquante2.scf hamiltonians and iterators,
                has a min. value of 1.

        Raises:
            QiskitNatureError: Invalid Input
        """
        super().__init__()
        # pylint: disable=import-error
        from pyquante2 import molecule as pyquante_molecule
        from pyquante2 import rhf, uhf, rohf, basisset

        validate_min("maxiters", maxiters, 1)
        PyQuanteDriver.check_method_supported(method)
        if not isinstance(atoms, str) and not isinstance(atoms, list):
            raise QiskitNatureError(
                f"Invalid atom input for PYQUANTE Driver '{atoms}'")

        if isinstance(atoms, list):
            atoms = ";".join(atoms)
        elif isinstance(atoms, str):
            atoms = atoms.replace("\n", ";")

        self._atoms = atoms
        self._units = units
        self._charge = charge
        self._multiplicity = multiplicity
        self._basis = basis
        self._method = method
        self._tol = tol
        self._maxiters = maxiters

        self._mol: pyquante_molecule = None
        self._bfs: basisset = None
        self._calc: Union[rhf, rohf, uhf] = None
        self._nmo: int = None
Ejemplo n.º 29
0
 def num_spin_orbitals(self) -> int:
     """Returns the number of spin orbitals."""
     if self._grouped_property_transformed is None:
         raise QiskitNatureError(
             "`num_spin_orbitals` is only available _after_ `second_q_ops()` has been called! "
             "Note, that if you run this manually, the method will run again during solving."
         )
     return self._grouped_property_transformed.get_property(
         "ParticleNumber").num_spin_orbitals
    def _do_transform(
        self,
        watson: WatsonHamiltonian,
        aux_operators: Optional[List[Union[BosonicOperator,
                                           PauliSumOp]]] = None
    ) -> Tuple[PauliSumOp, List[PauliSumOp]]:

        self._num_modes = watson.num_modes  # type: ignore

        if self._transformation_type == 'harmonic':
            if isinstance(self._basis_size, int):
                self._basis_size = [self._basis_size] * self._num_modes
            self._h_mat = HarmonicBasis(
                watson,  # type: ignore
                self._basis_size,
                self._truncation_order).convert()
        else:
            raise QiskitNatureError('Unknown Transformation type')

        bos_op = BosonicOperator(self._h_mat, self._basis_size)
        qubit_op = bos_op.mapping(qubit_mapping=self._qubit_mapping)
        self._untapered_qubit_op = qubit_op
        qubit_op.name = 'Bosonic Operator'

        aux_ops = []

        def _add_aux_op(aux_op: BosonicOperator, name: str) -> None:
            """
            Add auxiliary operators

            Args:
                aux_op: auxiliary operators
                name: name

            """
            if not isinstance(aux_op, PauliSumOp):
                aux_qop = BosonicTransformation._map_bosonic_operator_to_qubit(
                    aux_op, self._qubit_mapping)
                aux_qop.name = name
            else:
                aux_qop = aux_op

            aux_ops.append(aux_qop)
            logger.debug('  pauli: %s', str(aux_qop))

        logger.debug('Creating aux op for number of occupied modals per mode')

        for mode in range(self._num_modes):
            _add_aux_op(bos_op.number_occupied_modals_per_mode(mode),
                        'Number of occupied modals in mode {}'.format(mode))

        # add user specified auxiliary operators
        if aux_operators is not None:
            for aux_op in aux_operators:
                _add_aux_op(aux_op, aux_op.name)  # type: ignore

        return qubit_op, aux_ops