Skip to content

Commit

Permalink
refactor: match return type of BaseProblem.second_q_ops() to Terra
Browse files Browse the repository at this point in the history
The algorithms in Terra always take a tuple of the main operator and
some auxiliary operators. This commit changes the `BaseProblem`
interface to already return the generated operators in the same format.
This simplifies the handling of the main operator, a scenario that
occurs very often and required manual intervention thus far.
  • Loading branch information
mrossinek committed Aug 6, 2022
1 parent 123c97d commit 1e14e1d
Show file tree
Hide file tree
Showing 18 changed files with 78 additions and 116 deletions.
3 changes: 1 addition & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,7 @@ driver = PySCFDriver(atom='H .0 .0 .0; H .0 .0 0.735',
problem = ElectronicStructureProblem(driver)

# generate the second-quantized operators
second_q_ops = problem.second_q_ops()
main_op = second_q_ops['ElectronicEnergy']
main_op, _ = problem.second_q_ops()

particle_number = problem.grouped_property_transformed.get_property("ParticleNumber")

Expand Down
8 changes: 4 additions & 4 deletions docs/tutorials/01_electronic_structure.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -189,8 +189,8 @@
],
"source": [
"es_problem = ElectronicStructureProblem(driver)\n",
"second_q_op = es_problem.second_q_ops()\n",
"print(second_q_op[0])"
"main_op, aux_ops = es_problem.second_q_ops()\n",
"print(main_op)"
]
},
{
Expand Down Expand Up @@ -229,7 +229,7 @@
],
"source": [
"qubit_converter = QubitConverter(mapper=JordanWignerMapper())\n",
"qubit_op = qubit_converter.convert(second_q_op[0])\n",
"qubit_op = qubit_converter.convert(main_op)\n",
"print(qubit_op)"
]
},
Expand Down Expand Up @@ -259,7 +259,7 @@
],
"source": [
"qubit_converter = QubitConverter(mapper=ParityMapper(), two_qubit_reduction=True)\n",
"qubit_op = qubit_converter.convert(second_q_op[0], num_particles=es_problem.num_particles)\n",
"qubit_op = qubit_converter.convert(main_op, num_particles=es_problem.num_particles)\n",
"print(qubit_op)"
]
},
Expand Down
10 changes: 5 additions & 5 deletions docs/tutorials/02_vibrational_structure.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@
"from qiskit_nature.second_q.mappers import DirectMapper\n",
"\n",
"vibrational_problem = VibrationalStructureProblem(driver, num_modals=2, truncation_order=2)\n",
"second_q_ops = vibrational_problem.second_q_ops()"
"main_op, aux_ops = vibrational_problem.second_q_ops()"
]
},
{
Expand Down Expand Up @@ -270,7 +270,7 @@
}
],
"source": [
"print(second_q_ops[0])"
"print(main_op)"
]
},
{
Expand Down Expand Up @@ -342,7 +342,7 @@
],
"source": [
"qubit_converter = QubitConverter(mapper=DirectMapper())\n",
"qubit_op = qubit_converter.convert(second_q_ops[0])\n",
"qubit_op = qubit_converter.convert(main_op)\n",
"\n",
"print(qubit_op)"
]
Expand Down Expand Up @@ -611,11 +611,11 @@
],
"source": [
"vibrational_problem = VibrationalStructureProblem(driver, num_modals=3, truncation_order=2)\n",
"second_q_ops = vibrational_problem.second_q_ops()\n",
"main_op, aux_ops = vibrational_problem.second_q_ops()\n",
"\n",
"qubit_converter = QubitConverter(mapper=DirectMapper())\n",
"\n",
"qubit_op = qubit_converter.convert(second_q_ops[0])\n",
"qubit_op = qubit_converter.convert(main_op)\n",
"\n",
"print(qubit_op)"
]
Expand Down
38 changes: 19 additions & 19 deletions docs/tutorials/08_property_framework.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -454,7 +454,7 @@
}
],
"source": [
"hamiltonian = electronic_energy.second_q_ops()[0] # here, output length is always 1\n",
"hamiltonian = electronic_energy.second_q_ops()[\"ElectronicEnergy\"]\n",
"print(hamiltonian)"
]
},
Expand Down Expand Up @@ -957,7 +957,7 @@
"outputs": [],
"source": [
"from itertools import product\n",
"from typing import List\n",
"from typing import Dict\n",
"\n",
"import h5py\n",
"\n",
Expand Down Expand Up @@ -1001,8 +1001,8 @@
" def from_hdf5(cls, h5py_group: h5py.Group) -> \"ElectronicDensity\":\n",
" return ElectronicDensity(h5py_group.attrs[\"num_molecular_orbitals\"])\n",
"\n",
" def second_q_ops(self) -> List[FermionicOp]:\n",
" ops = []\n",
" def second_q_ops(self) -> Dict[str, FermionicOp]:\n",
" ops = {}\n",
"\n",
" # iterate all pairs of molecular orbitals\n",
" for mo_i, mo_j in product(range(self._num_molecular_orbitals), repeat=2):\n",
Expand All @@ -1017,7 +1017,7 @@
" one_body_ints = OneBodyElectronicIntegrals(\n",
" ElectronicBasis.MO, (number_op_matrix, number_op_matrix)\n",
" )\n",
" ops.append(one_body_ints.to_second_q_op())\n",
" ops[f\"{mo_i}_{mo_j}\"] = one_body_ints.to_second_q_op()\n",
"\n",
" return ops\n",
"\n",
Expand Down Expand Up @@ -1065,28 +1065,28 @@
"name": "stdout",
"output_type": "stream",
"text": [
"0 : Fermionic Operator\n",
"0_0 : Fermionic Operator\n",
"register length=4, number terms=2\n",
" (1+0j) * ( +_0 -_0 )\n",
"+ (1+0j) * ( +_2 -_2 )\n",
"1 : Fermionic Operator\n",
" 1.0 * ( +_0 -_0 )\n",
"+ 1.0 * ( +_2 -_2 )\n",
"0_1 : Fermionic Operator\n",
"register length=4, number terms=2\n",
" (1+0j) * ( +_0 -_1 )\n",
"+ (1+0j) * ( +_2 -_3 )\n",
"2 : Fermionic Operator\n",
" 1.0 * ( +_0 -_1 )\n",
"+ 1.0 * ( +_2 -_3 )\n",
"1_0 : Fermionic Operator\n",
"register length=4, number terms=2\n",
" (1+0j) * ( +_1 -_0 )\n",
"+ (1+0j) * ( +_3 -_2 )\n",
"3 : Fermionic Operator\n",
" 1.0 * ( +_1 -_0 )\n",
"+ 1.0 * ( +_3 -_2 )\n",
"1_1 : Fermionic Operator\n",
"register length=4, number terms=2\n",
" (1+0j) * ( +_1 -_1 )\n",
"+ (1+0j) * ( +_3 -_3 )\n"
" 1.0 * ( +_1 -_1 )\n",
"+ 1.0 * ( +_3 -_3 )\n"
]
}
],
"source": [
"for idx, op in enumerate(density.second_q_ops()):\n",
" print(idx, \":\", op)"
"for key, op in density.second_q_ops().items():\n",
" print(key, \":\", op)"
]
},
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,15 +66,7 @@ def get_qubit_operators(
"""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()
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_second_q_op, aux_second_q_ops = problem.second_q_ops()

main_operator = self._qubit_converter.convert(
main_second_q_op,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,11 +118,7 @@ def solve(
groundstate_result = self._gsc.solve(problem, aux_operators)

# 2. Prepare the excitation operators
second_q_ops = problem.second_q_ops()
if isinstance(second_q_ops, list):
main_second_q_op = second_q_ops[0]
elif isinstance(second_q_ops, dict):
main_second_q_op = second_q_ops.pop(problem.main_property_name)
main_second_q_op, _ = problem.second_q_ops()

self._untapered_qubit_op_main = self._gsc.qubit_converter.convert_only(
main_second_q_op, problem.num_particles
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,15 +102,7 @@ def get_qubit_operators(
"""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()
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_second_q_op, aux_second_q_ops = problem.second_q_ops()
main_operator = self._qubit_converter.convert(
main_second_q_op,
num_particles=problem.num_particles,
Expand Down
9 changes: 4 additions & 5 deletions qiskit_nature/second_q/problems/base_problem.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,13 +80,12 @@ def num_particles(self) -> Optional[Tuple[int, int]]:
return None

@abstractmethod
def second_q_ops(self) -> dict[str, SecondQuantizedOp]:
"""Returns the second quantized operators associated with this Property.
The actual return-type is determined by `qiskit_nature.settings.dict_aux_operators`.
def second_q_ops(self) -> tuple[SecondQuantizedOp, dict[str, SecondQuantizedOp]]:
"""Returns the second quantized operators associated with this problem.
Returns:
A `list` or `dict` of `SecondQuantizedOp` objects.
A tuple, with the first object being the main operator and the second being a dictionary
of auxiliary operators.
"""
raise NotImplementedError()

Expand Down
14 changes: 5 additions & 9 deletions qiskit_nature/second_q/problems/electronic_structure_problem.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,26 +111,22 @@ def num_spin_orbitals(self) -> int:
)
return self._grouped_property_transformed.get_property("ParticleNumber").num_spin_orbitals

def second_q_ops(self) -> dict[str, SecondQuantizedOp]:
def second_q_ops(self) -> tuple[SecondQuantizedOp, dict[str, SecondQuantizedOp]]:
"""Returns the second quantized operators associated with this Property.
If the arguments are returned as a `list`, the operators are in the following order: the
Hamiltonian operator, total particle number operator, total angular momentum operator, total
magnetization operator, and (if available) x, y, z dipole operators.
The actual return-type is determined by `qiskit_nature.settings.dict_aux_operators`.
Returns:
A `list` or `dict` of `SecondQuantizedOp` objects.
A tuple, with the first object being the main operator and the second being a dictionary
of auxiliary operators.
"""
driver_result = self.driver.run()

self._grouped_property = driver_result
self._grouped_property_transformed = self._transform(self._grouped_property)

second_quantized_ops = self._grouped_property_transformed.second_q_ops()
main_op = second_quantized_ops.pop(self._main_property_name)

return second_quantized_ops
return main_op, second_quantized_ops

def hopping_qeom_ops(
self,
Expand Down
9 changes: 4 additions & 5 deletions qiskit_nature/second_q/problems/lattice_model_problem.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,16 +41,15 @@ def __init__(self, lattice_model: LatticeModel) -> None:
super().__init__(main_property_name="LatticeEnergy")
self._lattice_model = lattice_model

def second_q_ops(self) -> dict[str, SecondQuantizedOp]:
def second_q_ops(self) -> tuple[SecondQuantizedOp, dict[str, SecondQuantizedOp]]:
"""Returns the second quantized operators created based on the lattice models.
Returns:
A ``list`` or ``dict`` of
:class:`~qiskit_nature.second_q.operators.SecondQuantizedOp`
A tuple, with the first object being the main operator and the second being a dictionary
of auxiliary operators.
"""
second_q_op = self._lattice_model.second_q_ops()
second_q_ops = {self._main_property_name: second_q_op}
return second_q_ops
return second_q_op, {}

def interpret(
self,
Expand Down
15 changes: 6 additions & 9 deletions qiskit_nature/second_q/problems/vibrational_structure_problem.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,16 +59,12 @@ def __init__(
self.num_modals = num_modals
self.truncation_order = truncation_order

def second_q_ops(self) -> dict[str, SecondQuantizedOp]:
"""Returns the second quantized operators created based on the driver and transformations.
If the arguments are returned as a `list`, the operators are in the following order: the
Vibrational Hamiltonian operator, occupied modal operators for each mode.
The actual return-type is determined by `qiskit_nature.settings.dict_aux_operators`.
def second_q_ops(self) -> tuple[SecondQuantizedOp, dict[str, SecondQuantizedOp]]:
"""Returns the second quantized operators associated with this problem.
Returns:
A `list` or `dict` of `SecondQuantizedOp` objects.
A tuple, with the first object being the main operator and the second being a dictionary
of auxiliary operators.
"""
driver_result = self.driver.run()

Expand All @@ -94,8 +90,9 @@ def second_q_ops(self) -> dict[str, SecondQuantizedOp]:
self._grouped_property_transformed.basis = basis

second_quantized_ops = self._grouped_property_transformed.second_q_ops()
main_op = second_quantized_ops.pop(self._main_property_name)

return second_quantized_ops
return main_op, second_quantized_ops

def hopping_qeom_ops(
self,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,9 @@ def setUp(self):

# because we create the initial state and ansatzes early, we need to ensure the qubit
# converter already ran such that convert_match works as expected
main_op, _ = self.electronic_structure_problem.second_q_ops()
_ = self.qubit_converter.convert(
self.electronic_structure_problem.second_q_ops()[
self.electronic_structure_problem.main_property_name
],
main_op,
self.num_particles,
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -157,9 +157,7 @@ def _setup_evaluation_operators(self):

# now we decide that we want to evaluate another operator
# for testing simplicity, we just use some pre-constructed auxiliary operators
second_q_ops = self.electronic_structure_problem.second_q_ops()
# Remove main op to leave just aux ops
second_q_ops.pop(self.electronic_structure_problem.main_property_name)
_, second_q_ops = self.electronic_structure_problem.second_q_ops()
aux_ops_dict = self.qubit_converter.convert_match(second_q_ops)
return calc, res, aux_ops_dict

Expand Down Expand Up @@ -276,9 +274,7 @@ def test_eval_op_qasm(self):
calc = GroundStateEigensolver(self.qubit_converter, solver)
res_qasm = calc.solve(self.electronic_structure_problem)

hamiltonian = self.electronic_structure_problem.second_q_ops()[
self.electronic_structure_problem.main_property_name
]
hamiltonian, _ = self.electronic_structure_problem.second_q_ops()
qubit_op = self.qubit_converter.map(hamiltonian)

ansatz = solver.get_solver(self.electronic_structure_problem, self.qubit_converter).ansatz
Expand Down Expand Up @@ -306,9 +302,7 @@ def test_eval_op_qasm_aer(self):
calc = GroundStateEigensolver(self.qubit_converter, solver)
res_qasm = calc.solve(self.electronic_structure_problem)

hamiltonian = self.electronic_structure_problem.second_q_ops()[
self.electronic_structure_problem.main_property_name
]
hamiltonian, _ = self.electronic_structure_problem.second_q_ops()
qubit_op = self.qubit_converter.map(hamiltonian)

ansatz = solver.get_solver(self.electronic_structure_problem, self.qubit_converter).ansatz
Expand Down
3 changes: 2 additions & 1 deletion test/second_q/mappers/test_qubit_converter.py
Original file line number Diff line number Diff line change
Expand Up @@ -271,8 +271,9 @@ def test_molecular_problem_sector_locator_z2_symmetry(self):

mapper = JordanWignerMapper()
qubit_conv = QubitConverter(mapper, two_qubit_reduction=True, z2symmetry_reduction="auto")
main_op, _ = problem.second_q_ops()
qubit_op = qubit_conv.convert(
problem.second_q_ops()[problem.main_property_name],
main_op,
self.num_particles,
sector_locator=problem.symmetry_sector_locator,
)
Expand Down
Loading

0 comments on commit 1e14e1d

Please sign in to comment.