Note

This page was generated from docs/tutorials/05_problem_transformers.ipynb.

Transforming Problems#

This tutorial explains the various problem transformers available in Qiskit Nature.

Note: Currently, Qiskit Nature only has transformers which work with electronic structure problems.

The BasisTransformer#

This transformer allows you to transform an ElectronicStructureProblem from one basis into another. This is useful in certain settings such as: - when you obtained a problem description or the Hamiltonian coefficients in the AO basis from an external source - when you explicitly generated the problem in the AO basis to modify it manually before transforming into the MO basis - etc.

Since we can achieve the second scenario directly using Qiskit Nature, that is what we will be doing here. To learn more about how to do this, we recommend that you read the tutorial on the `QCSchema <08_qcschema.ipynb>`__.

First, we create a problem in the AO basis in order to demonstrate the basis transformation:

[1]:
from qiskit_nature.second_q.drivers import PySCFDriver
from qiskit_nature.second_q.problems import ElectronicBasis

driver = PySCFDriver()
driver.run_pyscf()

ao_problem = driver.to_problem(basis=ElectronicBasis.AO)
print(ao_problem.basis)

ao_hamil = ao_problem.hamiltonian
print(ao_hamil.electronic_integrals.alpha)
The provided alpha-beta overlap matrix is NOT unitary! This can happen when the alpha- and beta-spin orbitals do not span the same space. To provide an example of what this means, consider an active space chosen from unrestricted-spin orbitals. Computing <S^2> within this active space may not result in the same <S^2> value as obtained on the single-reference starting point. More importantly, this implies that the inactive subspace will account for the difference between these two <S^2> values, possibly resulting in significant spin contamination in both subspaces. You should verify whether this is intentional/acceptable or whether your choice of active space can be improved. As a reference, here is the summed-absolute deviation of `S^T @ S` from the identity: 7.064215023495638
ElectronicBasis.AO
Polynomial Tensor
 "+-":
array([[-1.12421758, -0.9652574 ],
       [-0.9652574 , -1.12421758]])
 "++--":
array([0.77460594, 0.44744572, 0.3009177 , 0.57187698, 0.44744572,
       0.77460594])

Next, we obtain the AO to MO transformer:

[2]:
from qiskit_nature.second_q.formats.qcschema_translator import get_ao_to_mo_from_qcschema

qcschema = driver.to_qcschema()

basis_transformer = get_ao_to_mo_from_qcschema(qcschema)
print(basis_transformer.initial_basis)
print(basis_transformer.final_basis)
ElectronicBasis.AO
ElectronicBasis.MO

And finally, we can use the transformer to obtain the problem in the MO basis:

[3]:
mo_problem = basis_transformer.transform(ao_problem)
print(mo_problem.basis)

mo_hamil = mo_problem.hamiltonian
print(mo_hamil.electronic_integrals.alpha)
ElectronicBasis.MO
Polynomial Tensor
 "+-":
array([[-1.25633907e+00, -1.37083854e-17],
       [-6.07732712e-17, -4.71896007e-01]])
 "++--":
array([[[[6.75710155e-01, 1.56722377e-16],
         [1.03976181e-16, 6.64581730e-01]],

        [[1.55375654e-16, 1.80931200e-01],
         [1.80931200e-01, 4.63164127e-16]]],


       [[[5.98074319e-17, 1.80931200e-01],
         [1.80931200e-01, 2.60271871e-16]],

        [[6.64581730e-01, 2.69194871e-16],
         [3.78920172e-16, 6.98573723e-01]]]])

If you need to construct your BasisTransformer manually, you can do this like so:

[4]:
import numpy as np
from qiskit_nature.second_q.operators import ElectronicIntegrals
from qiskit_nature.second_q.problems import ElectronicBasis
from qiskit_nature.second_q.transformers import BasisTransformer

ao2mo_alpha = np.random.random((2, 2))
ao2mo_beta = np.random.random((2, 2))

basis_transformer = BasisTransformer(
    ElectronicBasis.AO,
    ElectronicBasis.MO,
    ElectronicIntegrals.from_raw_integrals(ao2mo_alpha, h1_b=ao2mo_beta),
)

The FreezeCoreTransformer#

This transformer provides you with the very simple means to freeze the core orbitals of your molecular system. It requires your problem to contain the .molecule attribute from which it can extract the atomic information necessary to perform this Hilbert space reduction.

[5]:
from qiskit_nature.second_q.drivers import PySCFDriver

driver = PySCFDriver(atom="Li 0 0 0; H 0 0 1.5")

full_problem = driver.run()
print(full_problem.molecule)
print(full_problem.num_particles)
print(full_problem.num_spatial_orbitals)
Molecule:
        Multiplicity: 1
        Charge: 0
        Unit: Bohr
        Geometry:
                Li      (0.0, 0.0, 0.0)
                H       (0.0, 0.0, 2.8345891868475928)
        Masses:
                Li      7
                H       1
(2, 2)
6

In the following, we apply the FreezeCoreTransformer which in this case will remove the single lowest energy orbital (reducing the total number of spatial orbitals from 6 to 5) and also removing the two electrons from within that orbital (as reflected by the changed number of particles).

[ ]:

[6]:
from qiskit_nature.second_q.transformers import FreezeCoreTransformer

fc_transformer = FreezeCoreTransformer()

fc_problem = fc_transformer.transform(full_problem)
print(fc_problem.num_particles)
print(fc_problem.num_spatial_orbitals)
(1, 1)
5

Note, that this transformation will result in a constant energy offset resulting from the removal of the core electrons. This offset is registered inside of the hamiltonian’s constants attribute, which you can inspect like shown below:

[7]:
print(fc_problem.hamiltonian.constants)
{'nuclear_repulsion_energy': 1.05835442184, 'FreezeCoreTransformer': -7.84030604879426}

Furthermore, you can provide a list of orbital indices (0-based) which are to be removed from the system.

Note: these orbitals must be unoccupied, otherwise you will incur a large error in your computation (even if they are unoccupied you should know which orbitals you are removing, because removing the wrong ones can still incur large errors if the systems dynamics are altered significantly). No guards are in place to check that the provided orbital indices are indeed unoccupied, so this is up to you!

[8]:
fc_transformer = FreezeCoreTransformer(remove_orbitals=[4, 5])

fc_problem = fc_transformer.transform(full_problem)
print(fc_problem.num_particles)
print(fc_problem.num_spatial_orbitals)
(1, 1)
3

The ActiveSpaceTransformer#

This transformer generalizes the Hilbert space reduction which is performed by the FreezeCoreTransformer. Simply put, it allows you to specify an active space by selecting the number of active electrons and the number of active spatial orbitals. According to these settings, the active space will be chosen around the Fermi level.

[9]:
from qiskit_nature.second_q.drivers import PySCFDriver

driver = PySCFDriver(atom="Li 0 0 0; H 0 0 1.5")

full_problem = driver.run()
print(full_problem.num_particles)
print(full_problem.num_spatial_orbitals)
(2, 2)
6
[10]:
from qiskit_nature.second_q.transformers import ActiveSpaceTransformer

as_transformer = ActiveSpaceTransformer(2, 2)

as_problem = as_transformer.transform(full_problem)
print(as_problem.num_particles)
print(as_problem.num_spatial_orbitals)
print(as_problem.hamiltonian.electronic_integrals.alpha)
(1, 1)
2
Polynomial Tensor
 "+-":
array([[-0.78784474,  0.0469345 ],
       [ 0.0469345 , -0.36211749]])
 "++--":
array([[[[ 0.49428349, -0.0469345 ],
         [-0.0469345 ,  0.22662427]],

        [[-0.0469345 ,  0.01213863],
         [ 0.01213863,  0.00616268]]],


       [[[-0.0469345 ,  0.01213863],
         [ 0.01213863,  0.00616268]],

        [[ 0.22662427,  0.00616268],
         [ 0.00616268,  0.33881567]]]])

The ActiveSpaceTransformer in Qiskit Nature has one more trick up its sleeve because it even allows you to manually specify the indices of the active orbitals. This enables you to hand-pick active spaces which do not lie continuously around the Fermi level.

[11]:
as_transformer = ActiveSpaceTransformer(2, 2, active_orbitals=[0, 4])

as_problem = as_transformer.transform(full_problem)
print(as_problem.num_particles)
print(as_problem.num_spatial_orbitals)
print(as_problem.hamiltonian.electronic_integrals.alpha)
(1, 1)
2
Polynomial Tensor
 "+-":
array([[-4.00500243e+00,  2.10599641e-17],
       [ 2.10599641e-17, -6.19047188e-01]])
 "++--":
array([[[[ 1.65816678e+00, -2.09763285e-17],
         [-2.09763285e-17,  3.96308164e-01]],

        [[-2.09763285e-17,  9.81922731e-03],
         [ 9.81922731e-03,  2.77324964e-18]]],


       [[[-2.09763285e-17,  9.81922731e-03],
         [ 9.81922731e-03,  2.77324964e-18]],

        [[ 3.96308164e-01,  2.77324964e-18],
         [ 2.77324964e-18,  3.12945511e-01]]]])
[12]:
import tutorial_magics

%qiskit_version_table
%qiskit_copyright

Version Information

SoftwareVersion
qiskit1.0.1
qiskit_nature0.7.2
qiskit_algorithms0.3.0
System information
Python version3.8.18
OSLinux
Fri Feb 23 10:25:22 2024 UTC

This code is a part of a Qiskit project

© Copyright IBM 2017, 2024.

This code is licensed under the Apache License, Version 2.0. You may
obtain a copy of this license in the LICENSE.txt file in the root directory
of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.

Any modifications or derivative works of this code must retain this
copyright notice, and modified files need to carry a notice indicating
that they have been altered from the originals.