Skip to content

Commit 0acdf15

Browse files
committed
Fix perturbation label re-ordering bug (#307)
1 parent 02a8357 commit 0acdf15

File tree

3 files changed

+84
-2
lines changed

3 files changed

+84
-2
lines changed

qiskit_dynamics/perturbation/solve_lmde_perturbation.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -230,8 +230,9 @@ def solve_lmde_perturbation(
230230
else:
231231
# validate perturbation_labels
232232
perturbations_len = len(perturbation_labels)
233-
perturbation_labels = _clean_multisets(perturbation_labels)
234-
if len(perturbation_labels) != perturbations_len:
233+
perturbation_labels = [Multiset(x) for x in perturbation_labels]
234+
cleaned_perturbation_labels = _clean_multisets(perturbation_labels)
235+
if len(cleaned_perturbation_labels) != perturbations_len:
235236
raise QiskitError("perturbation_labels argument contains duplicates as multisets.")
236237

237238
expansion_labels = _merge_multiset_expansion_order_labels(
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
fixes:
3+
- |
4+
Fixes a bug in :func:`.solve_lmde_perturbation` in which the ``perturbation_labels`` argument
5+
was being unexpectedly re-ordered, leading to errors when retrieving results.
6+

test/dynamics/perturbation/test_solve_lmde_perturbation.py

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -576,6 +576,81 @@ def A1(t):
576576
self.assertAllClose(expected_D0001, results.perturbation_data.get_item([1, 1, 1, 10])[-1])
577577
self.assertAllClose(expected_D0011, results.perturbation_data.get_item([1, 1, 10, 10])[-1])
578578

579+
def test_dyson_analytic_case1_1d_reverse_order_labeled(self):
580+
"""This is the same numerical test as test_dyson_analytic_case1_1d, however it relabels
581+
the perturbation indices as 0 -> 1, and 1 -> 0. This is an integration test that would
582+
have caught multiset sorting bug that broke solve_lmde_perturbation results computation.
583+
"""
584+
585+
def generator(t):
586+
return Array([[1, 0], [0, 1]], dtype=complex).data
587+
588+
def A0(t):
589+
return Array([[0, t], [t**2, 0]], dtype=complex).data
590+
591+
def A1(t):
592+
return Array([[t, 0], [0, t**2]], dtype=complex).data
593+
594+
T = np.pi * 1.2341
595+
596+
results = solve_lmde_perturbation(
597+
perturbations=[A0, A1],
598+
t_span=[0, T],
599+
generator=generator,
600+
y0=np.array([0.0, 1.0], dtype=complex),
601+
expansion_method="dyson",
602+
expansion_order=2,
603+
expansion_labels=[[1, 1, 0], [1, 1, 1, 0], [1, 1, 0, 0]],
604+
perturbation_labels=[[1], [0]],
605+
dyson_in_frame=False,
606+
integration_method=self.integration_method,
607+
atol=1e-13,
608+
rtol=1e-13,
609+
)
610+
611+
T2 = T**2
612+
T3 = T * T2
613+
T4 = T * T3
614+
T5 = T * T4
615+
T6 = T * T5
616+
T7 = T * T6
617+
T8 = T * T7
618+
T9 = T * T8
619+
T10 = T * T9
620+
T11 = T * T10
621+
622+
U = expm(np.array(generator(0)) * T)
623+
624+
expected_D0 = U @ np.array([[T2 / 2], [0]], dtype=complex)
625+
expected_D1 = U @ np.array([[0], [T3 / 3]], dtype=complex)
626+
expected_D00 = U @ np.array([[0], [T5 / 10]], dtype=complex)
627+
expected_D01 = U @ (
628+
np.array([[T5 / 15], [0]], dtype=complex) + np.array([[T4 / 8], [0]], dtype=complex)
629+
)
630+
expected_D11 = U @ np.array([[0], [T6 / 18]], dtype=complex)
631+
expected_D001 = U @ (
632+
np.array([[0], [T8 / 120]], dtype=complex)
633+
+ np.array([[0], [T7 / 56]], dtype=complex)
634+
+ np.array([[0], [T8 / 80]], dtype=complex)
635+
)
636+
expected_D0001 = U @ np.array([[(T10 / 480) + (T9 / 280)], [0]], dtype=complex)
637+
expected_D0011 = U @ np.array(
638+
[
639+
[0],
640+
[(T11 / 396) + 23 * (T10 / 8400) + (T9 / 432)],
641+
],
642+
dtype=complex,
643+
)
644+
645+
self.assertAllClose(expected_D0, results.perturbation_data.get_item([1])[-1])
646+
self.assertAllClose(expected_D1, results.perturbation_data.get_item([0])[-1])
647+
self.assertAllClose(expected_D00, results.perturbation_data.get_item([1, 1])[-1])
648+
self.assertAllClose(expected_D01, results.perturbation_data.get_item([1, 0])[-1])
649+
self.assertAllClose(expected_D11, results.perturbation_data.get_item([0, 0])[-1])
650+
self.assertAllClose(expected_D001, results.perturbation_data.get_item([1, 1, 0])[-1])
651+
self.assertAllClose(expected_D0001, results.perturbation_data.get_item([1, 1, 1, 0])[-1])
652+
self.assertAllClose(expected_D0011, results.perturbation_data.get_item([1, 1, 0, 0])[-1])
653+
579654
def test_dyson_analytic_case1(self):
580655
"""Analytic test of computing dyson terms.
581656

0 commit comments

Comments
 (0)