Skip to content

Commit 6847abb

Browse files
Add input validation to optimize_orbitals per Gemini Code Assist review
Addresses both high-priority comments from the automated review on PR #1442: - validate one_body_integrals/two_body_integrals/one_rdm/two_rdm shapes against each other and against n_electrons parity (restricted/closed-shell requires an even electron count) before they reach energy(), where a mismatch would otherwise surface as an opaque broadcast/index error deep inside general_basis_change or rhf_params_to_matrix. - validate initial_guess size against the expected nocc * nvirt parameter count for the same reason. 4 new regression tests, all pre-existing tests still pass.
1 parent 533323b commit 6847abb

2 files changed

Lines changed: 88 additions & 0 deletions

File tree

src/openfermion/hamiltonians/orbital_optimization.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,36 @@ def optimize_orbitals(
128128
scipy.optimize.OptimizeResult. `result.x` is the optimal kappa
129129
parameter vector; `result.fun` is the optimized energy.
130130
"""
131+
if one_body_integrals.ndim != 2 or one_body_integrals.shape[0] != one_body_integrals.shape[1]:
132+
raise ValueError(
133+
f"one_body_integrals must be a square 2D array, got shape "
134+
f"{one_body_integrals.shape}"
135+
)
131136
n_orbitals = one_body_integrals.shape[0]
137+
if two_body_integrals.shape != (n_orbitals,) * 4:
138+
raise ValueError(
139+
f"two_body_integrals must have shape {(n_orbitals,) * 4} to match "
140+
f"one_body_integrals (n_orbitals={n_orbitals}), got "
141+
f"{two_body_integrals.shape}"
142+
)
143+
n_spin_orbitals = 2 * n_orbitals
144+
if one_rdm.shape != (n_spin_orbitals,) * 2:
145+
raise ValueError(
146+
f"one_rdm must have shape {(n_spin_orbitals,) * 2} (spin-orbital "
147+
f"basis, 2 * n_orbitals with n_orbitals={n_orbitals}), got "
148+
f"{one_rdm.shape}"
149+
)
150+
if two_rdm.shape != (n_spin_orbitals,) * 4:
151+
raise ValueError(
152+
f"two_rdm must have shape {(n_spin_orbitals,) * 4} (spin-orbital "
153+
f"basis, 2 * n_orbitals with n_orbitals={n_orbitals}), got "
154+
f"{two_rdm.shape}"
155+
)
156+
if n_electrons % 2 != 0:
157+
raise ValueError(
158+
f"optimize_orbitals is restricted (closed-shell) -- n_electrons "
159+
f"must be even, got {n_electrons}"
160+
)
132161
nocc = n_electrons // 2
133162
nvirt = n_orbitals - nocc
134163
if nocc <= 0 or nvirt <= 0:
@@ -159,6 +188,11 @@ def energy(params: np.ndarray) -> float:
159188
init_params = np.zeros(nocc * nvirt)
160189
else:
161190
init_params = np.asarray(initial_guess).flatten()
191+
if init_params.size != nocc * nvirt:
192+
raise ValueError(
193+
f"initial_guess has {init_params.size} parameters, expected "
194+
f"nocc * nvirt = {nocc} * {nvirt} = {nocc * nvirt}"
195+
)
162196

163197
sp_optimizer_options = {'disp': verbose}
164198
if sp_options is not None:

src/openfermion/hamiltonians/orbital_optimization_test.py

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,3 +187,57 @@ def test_optimize_orbitals_rejects_degenerate_orbital_split():
187187
optimize_orbitals(obi, tbi, one_rdm, two_rdm, n_electrons=4) # all occupied, no virtuals
188188
with pytest.raises(ValueError):
189189
optimize_orbitals(obi, tbi, one_rdm, two_rdm, n_electrons=0) # all virtual, no occupied
190+
191+
192+
def test_optimize_orbitals_rejects_mismatched_shapes():
193+
"""Gemini Code Assist review on PR #1442: no shape validation meant a
194+
mismatched one_body_integrals/two_body_integrals/one_rdm/two_rdm/
195+
n_electrons combination would fail deep inside energy() with a
196+
confusing broadcast/index error instead of a clear message at the
197+
call boundary."""
198+
n_orbitals = 3
199+
obi = np.zeros((n_orbitals, n_orbitals))
200+
tbi = np.zeros((n_orbitals,) * 4)
201+
one_rdm = np.zeros((2 * n_orbitals, 2 * n_orbitals))
202+
two_rdm = np.zeros((2 * n_orbitals,) * 4)
203+
204+
with pytest.raises(ValueError, match="one_body_integrals"):
205+
optimize_orbitals(np.zeros((n_orbitals, n_orbitals + 1)), tbi, one_rdm, two_rdm, 4)
206+
with pytest.raises(ValueError, match="two_body_integrals"):
207+
optimize_orbitals(obi, np.zeros((n_orbitals + 1,) * 4), one_rdm, two_rdm, 4)
208+
with pytest.raises(ValueError, match="one_rdm"):
209+
optimize_orbitals(obi, tbi, np.zeros((2 * n_orbitals + 1,) * 2), two_rdm, 4)
210+
with pytest.raises(ValueError, match="two_rdm"):
211+
optimize_orbitals(obi, tbi, one_rdm, np.zeros((2 * n_orbitals + 1,) * 4), 4)
212+
213+
214+
def test_optimize_orbitals_rejects_odd_electron_count():
215+
"""optimize_orbitals is restricted (closed-shell): n_electrons // 2
216+
silently rounds an odd count down, which would optimize the wrong
217+
number of occupied orbitals without any warning."""
218+
n_orbitals = 3
219+
obi = np.zeros((n_orbitals, n_orbitals))
220+
tbi = np.zeros((n_orbitals,) * 4)
221+
one_rdm = np.zeros((2 * n_orbitals, 2 * n_orbitals))
222+
two_rdm = np.zeros((2 * n_orbitals,) * 4)
223+
with pytest.raises(ValueError, match="even"):
224+
optimize_orbitals(obi, tbi, one_rdm, two_rdm, n_electrons=3)
225+
226+
227+
def test_optimize_orbitals_rejects_mismatched_initial_guess():
228+
"""A caller-supplied initial_guess of the wrong length would otherwise
229+
hit an IndexError deep inside rhf_params_to_matrix instead of a clear
230+
message naming the expected parameter count."""
231+
m = _load_h2()
232+
hamiltonian = m.get_molecular_hamiltonian()
233+
_, one_rdm, two_rdm = _fci_ground_state_rdms(hamiltonian, m.n_qubits)
234+
with pytest.raises(ValueError, match="initial_guess"):
235+
optimize_orbitals(
236+
m.one_body_integrals,
237+
m.two_body_integrals,
238+
one_rdm,
239+
two_rdm,
240+
m.n_electrons,
241+
initial_guess=np.zeros(5), # wrong size for this molecule (H2/sto-3g needs 1)
242+
verbose=False,
243+
)

0 commit comments

Comments
 (0)