# How to freeze orbitals (with the H₂O molecule as an example)¶

In this guide, we apply Entanglement Forging to compute the energy of a $$\mathrm{H}_2\mathrm{O}$$ molecule. We reduce the number of orbitals in the problem, in turn reducing the number of qubits needed in each circuit, following the logic given in the explanatory material.

## Import the relevant modules¶

First, we import the relevant modules. The imports are similar to the introductory tutorial, but this time, we also import the reduce_bitstrings function from circuit_knitting_toolbox.utils.

[1]:
from matplotlib import pyplot as plt
import numpy as np

from qiskit.circuit import QuantumCircuit, Parameter
from qiskit.circuit.library import TwoLocal
from qiskit.algorithms.optimizers import COBYLA
from qiskit_nature.drivers import Molecule
from qiskit_nature.drivers.second_quantization import PySCFDriver
from qiskit_nature.problems.second_quantization import ElectronicStructureProblem
from qiskit_nature.mappers.second_quantization import JordanWignerMapper
from qiskit_nature.converters.second_quantization import QubitConverter
from qiskit_nature.algorithms.ground_state_solvers import (
GroundStateEigensolver,
NumPyMinimumEigensolverFactory,
)

from circuit_knitting_toolbox.entanglement_forging import (
EntanglementForgingAnsatz,
EntanglementForgingGroundStateSolver,
)
from circuit_knitting_toolbox.utils import reduce_bitstrings

## Instantiate the ElectronicStructureProblem¶

Next, we set up the $$\mathrm{H}_2\mathrm{O}$$ molecule, specify the driver and converter, and instantiate an ElectronicStructureProblem.

[2]:
radius_1 = 0.958  # position for the first H atom
radius_2 = 0.958  # position for the second H atom
thetas_in_deg = 104.478  # bond angles.

H2_x = radius_2 * np.cos(np.pi / 180 * thetas_in_deg)
H2_y = radius_2 * np.sin(np.pi / 180 * thetas_in_deg)

molecule = Molecule(
geometry=[
["O", [0.0, 0.0, 0.0]],
["H", [H1_x, 0.0, 0.0]],
["H", [H2_x, H2_y, 0.0]],
],
charge=0,
multiplicity=1,
)
driver = PySCFDriver.from_molecule(molecule=molecule, basis="sto6g")
problem = ElectronicStructureProblem(driver)
converter = QubitConverter(JordanWignerMapper())

## Compute the classical result¶

For comparison, we also use numpy to compute the classical result.

[3]:
solver = GroundStateEigensolver(
converter, NumPyMinimumEigensolverFactory(use_default_filter_criterion=False)
)

result = solver.solve(problem)
classical_energy = result.total_energies[0]

print("Classical energy = ", classical_energy)
/Users/caleb/opt/anaconda3/envs/ckt/lib/python3.7/site-packages/qiskit_nature/problems/second_quantization/electronic/electronic_structure_problem.py:93: ListAuxOpsDeprecationWarning: List-based aux_operators are deprecated as of version 0.3.0 and support for them will be removed no sooner than 3 months after the release. Instead, use dict-based aux_operators. You can switch to the dict-based interface immediately, by setting qiskit_nature.settings.dict_aux_operators to True.
second_quantized_ops = self._grouped_property_transformed.second_q_ops()
Classical energy =  -75.72890671869246

## Prepare the bitstrings and the ansatz¶

The ansatz for Entanglement Forging consists of a set of input bitstrings and a parameterized circuit. (See the “explanatory material” section of the documentation for additional background on the method.) For this demo, we will use the same bitstrings and ansatz for both the U and V subsystems.

[4]:
theta = Parameter("θ")

hop_gate = QuantumCircuit(2, name="Hop gate")
hop_gate.h(0)
hop_gate.cx(1, 0)
hop_gate.cx(0, 1)
hop_gate.ry(-theta, 0)
hop_gate.ry(-theta, 1)
hop_gate.cx(0, 1)
hop_gate.h(0)

hop_gate.draw()
[4]:
┌───┐┌───┐     ┌────────────┐     ┌───┐
q_0: ┤ H ├┤ X ├──■──┤ Ry(-1.0*θ) ├──■──┤ H ├
└───┘└─┬─┘┌─┴─┐├────────────┤┌─┴─┐└───┘
q_1: ───────■──┤ X ├┤ Ry(-1.0*θ) ├┤ X ├─────
└───┘└────────────┘└───┘
[5]:
theta_1, theta_2, theta_3, theta_4 = (
Parameter("θ1"),
Parameter("θ2"),
Parameter("θ3"),
Parameter("θ4"),
)

circuit_u = QuantumCircuit(5)
circuit_u.append(hop_gate.to_gate({theta: theta_1}), [0, 1])
circuit_u.append(hop_gate.to_gate({theta: theta_2}), [3, 4])
circuit_u.append(hop_gate.to_gate({theta: 0}), [1, 4])
circuit_u.append(hop_gate.to_gate({theta: theta_3}), [0, 2])
circuit_u.append(hop_gate.to_gate({theta: theta_4}), [3, 4])

# Set our bitstrings, and then reduce the chosen orbitals
orbitals_to_reduce = [0, 3]
bitstrings_u = [(1, 1, 1, 1, 1, 0, 0), (1, 0, 1, 1, 1, 0, 1), (1, 0, 1, 1, 1, 1, 0)]
reduced_bitstrings = reduce_bitstrings(bitstrings_u, orbitals_to_reduce)

ansatz = EntanglementForgingAnsatz(circuit_u=circuit_u, bitstrings_u=reduced_bitstrings)

ansatz.circuit_u.draw()
[5]:
┌───────────────┐                ┌───────────────┐
q_0: ┤0              ├────────────────┤0              ├
│  Hop gate(θ1) │┌──────────────┐│               │
q_1: ┤1              ├┤0             ├┤  Hop gate(θ3) ├
└───────────────┘│              ││               │
q_2: ─────────────────┤              ├┤1              ├
┌───────────────┐│  Hop gate(0) │├───────────────┤
q_3: ┤0              ├┤              ├┤0              ├
│  Hop gate(θ2) ││              ││  Hop gate(θ4) │
q_4: ┤1              ├┤1             ├┤1              ├
└───────────────┘└──────────────┘└───────────────┘

From here, the problem can be solved following the same steps as in the tutorials.

[10]:
import qiskit.tools.jupyter

%qiskit_version_table

### Version Information

Qiskit SoftwareVersion
qiskit-terra0.22.0
qiskit-aer0.11.0
qiskit-ignis0.7.1
qiskit-ibmq-provider0.19.2
qiskit-nature0.4.5
System information
Python version3.9.7
Python compilerGCC 7.5.0
Python builddefault, Sep 16 2021 13:09:58
OSLinux
CPUs6
Memory (Gb)30.943462371826172
Wed Oct 26 13:40:28 2022 EDT