From 5bef4ce62f586952cfd256aa01d2a4626023be8b Mon Sep 17 00:00:00 2001 From: Hector Brown Date: Sun, 8 Feb 2026 15:07:46 +1100 Subject: [PATCH 1/4] feat: add platonic universal dynamic decoupling sequence for qudits --- qctrlopencontrols/__init__.py | 2 + .../predefined.py | 180 ++++++++++++++++++ tests/test_predefined_dynamical_decoupling.py | 115 +++++++++++ 3 files changed, 297 insertions(+) diff --git a/qctrlopencontrols/__init__.py b/qctrlopencontrols/__init__.py index a796866..bf87611 100644 --- a/qctrlopencontrols/__init__.py +++ b/qctrlopencontrols/__init__.py @@ -48,6 +48,7 @@ new_walsh_sequence, new_x_concatenated_sequence, new_xy_concatenated_sequence, + new_platonic_sequence, ) __all__ = [ @@ -76,4 +77,5 @@ "new_walsh_sequence", "new_x_concatenated_sequence", "new_xy_concatenated_sequence", + "new_platonic_sequence", ] diff --git a/qctrlopencontrols/dynamic_decoupling_sequences/predefined.py b/qctrlopencontrols/dynamic_decoupling_sequences/predefined.py index 1f77fc1..3e695fc 100644 --- a/qctrlopencontrols/dynamic_decoupling_sequences/predefined.py +++ b/qctrlopencontrols/dynamic_decoupling_sequences/predefined.py @@ -1228,3 +1228,183 @@ def _concatenation_xy(concatenation_sequence) -> np.ndarray: if cumulations[-1] == -2 and cumulations[-2] == -2: cumulations = cumulations[0:-2] return cumulations + + +def new_platonic_sequence( + duration, sequence="Octahedral", pre_post_rotation=False, name=None +) -> DynamicDecouplingSequence: + r""" + Creates a platonic sequence. + + Parameters + ---------- + duration : float + Total duration of the sequence :math:`\tau` (in seconds). + sequence : string, optional. + Sequence to follow, one of ``"Dihedral"``, ``"Tetrahedral"``, + ``"Octahedral"``, ``"Icosahedral"``. Defaults to ``"Octahedral"``. + pre_post_rotation : bool, optional + If ``True``, a :math:`X_{\pi/2}` rotation is added at the + start and end of the sequence. Defaults to ``False``. + name : string, optional + Name of the sequence. Defaults to ``None``. + + Returns + ------- + DynamicDecouplingSequence + The platonic sequence. + + Notes + ----- + The platonic dynamic decoupling sequences use the symmetry of the point + groups associated with certain platonic solids in order to decouple spin-j + (:math:`j \le \frac{5}{2}`) systems from higher-order noise. The pulses are + equally spaced in time, and their number is set by the specific sequence as + illustrated below. + + .. list-table:: + :widths: 25 25 + :header-rows: 1 + + * - Sequence + - Number of pulses + * - Dihedral + - 8 + * - Tetrahedral + - 24 + * - Octahedral + - 48 + * - Icosahedral + - 120 + + For each sequence there are two generators, applied in a specific order so + as to traverse every edge of the associated point group which can be found + in [#]_ Appendix B. These generators are the rotations listed below, + + .. list-table:: + :widths: 25 25 25 + :header-rows: 1 + + * - Sequence + - Generator :math:`a` + - Generator :math:`b` + * - Dihedral + - :math:`\left(\left(1,0,0\right),\pi\right)` + - :math:`\left(\left(0,1,0\right),\pi\right)` + * - Tetrahedral + - :math:`\left(\left(0,0,1\right),\frac{2\pi}{3}\right)` + - :math:`\left(\left(\frac{\sqrt{2}}{3},\sqrt{\frac{2}{3}},\frac{1}{3}\right),\frac{2\pi}{3}\right)` + * - Octahedral + - :math:`\left(\left(0,0,1\right),\frac{2\pi}{4}\right)` + - :math:`\left(\frac{1}{\sqrt{3}}\left(1,1,1\right),\frac{2\pi}{3}\right)` + * - Icosahedral + - :math:`\left(\frac{\left(0,-1,\phi\right)}{\sqrt{\phi+2}},\frac{2\pi}{5}\right)` + - :math:`\left(\frac{\left(1-\phi,0,\phi\right)}{\sqrt{3}},\frac{2\pi}{3}\right)` + + where the rotations are given in axis-angle notation and + :math:`\phi=\frac{\sqrt{5}+1}{2}` is the golden ratio. + + References + ---------- + .. [#] `C. Read, E. Serrano-Ensástiga, and J. Martin, Quantum 9, 1661 (2025). + `_ + """ + check_arguments( + duration > 0, "Sequence duration must be positive.", {"duration": duration} + ) + check_arguments( + sequence in ["Dihedral", "Tetrahedral", "Octahedral", "Icosahedral"], + 'Sequence must be one of "Dihedral", "Tetrahedral", "Octahedral", or "Icosahedral".', + {"sequence": sequence}, + ) + + # The sequences outlined in the cited paper, each sequence is constructed + # from an Eulerian path on the Cayley graph associated with the relevant + # point group. Each sequence is constructed of 2 generating operations, in + # the order specified here. + # fmt: off + eulerian_paths = { + "Dihedral": [0, 1, 0, 1, 1, 0, 1, 0], + "Tetrahedral": [ 0, 1, 0, 0, 1, 0, 1, 1, 1, 0, 0, 1, 0, 1, 1, 1, 0, 0, 1, 0, 1, 1, 0, 0], + "Octahedral": [ 0, 1, 0, 0, 0, 1, 1, 1, 0, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1, 1 ], + "Icosahedral": [ 1, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 0, 1, 1, 1, 0, 0, 1, 0, 1, 1, 1, 0, 0, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0 ] + } + # fmt: on + + # The generators assocaited with each point group. + phi = (np.sqrt(5) + 1) / 2 # golden ratio + generators = { + "Dihedral": [ + {"Rabi": np.pi, "Azimuthal": 0, "Detuning": 0}, # rabi, azimuthal, detuning + {"Rabi": np.pi, "Azimuthal": np.pi / 2, "Detuning": 0}, + ], + "Tetrahedral": [ + {"Rabi": 0, "Azimuthal": 0, "Detuning": 2 * np.pi / 3}, + { + "Rabi": 4 * np.sqrt(2) * np.pi / 9, + "Azimuthal": np.pi / 3, + "Detuning": 2 * np.pi / 9, + }, + ], + "Octahedral": [ + {"Rabi": 0, "Azimuthal": 0, "Detuning": np.pi / 2}, + { + "Rabi": 2 * np.sqrt(2 / 3) * np.pi / 3, + "Azimuthal": np.pi / 4, + "Detuning": 2 * np.pi / 3 / np.sqrt(3), + }, + ], + "Icosahedral": [ + { + "Rabi": 2 * np.pi / 5 / np.sqrt(phi + 2), + "Azimuthal": 3 * np.pi / 2, + "Detuning": 2 * np.pi * phi / 5 / np.sqrt(phi + 2), + }, + { + "Rabi": 2 * np.pi * (phi - 1) / 3 / np.sqrt(3), + "Azimuthal": np.pi, + "Detuning": 2 * np.pi * phi / 3 / np.sqrt(3), + }, + ], + } + + # Re-use the CPMG offset function to obtain equally spaced pulses along a certain duration. + offsets = _carr_purcell_meiboom_gill_offsets( + duration, len(eulerian_paths[sequence]) + ) + rabi_rotations = np.array( + [generators[sequence][idx]["Rabi"] for idx in eulerian_paths[sequence]] + ) + azimuthal_angles = np.array( + [generators[sequence][idx]["Azimuthal"] for idx in eulerian_paths[sequence]] + ) + detuning_rotations = np.array( + [generators[sequence][idx]["Detuning"] for idx in eulerian_paths[sequence]] + ) + + if pre_post_rotation: + # Use a pi/2 followed by a -pi/2 X rotation as all the sequences + # correspond with an effective identity gate. + offsets = np.insert(offsets, [0, offsets.shape[0]], [0, duration]) + rabi_rotations = np.insert( + rabi_rotations, [0, rabi_rotations.shape[0]], [np.pi / 2, np.pi / 2] + ) + azimuthal_angles = np.insert( + azimuthal_angles, + [0, azimuthal_angles.shape[0]], + [0, np.pi], + ) + detuning_rotations = np.insert( + detuning_rotations, + [0, detuning_rotations.shape[0]], + [0, 0], + ) + + return DynamicDecouplingSequence( + duration=duration, + offsets=offsets, + rabi_rotations=rabi_rotations, + azimuthal_angles=azimuthal_angles, + detuning_rotations=detuning_rotations, + name=name, + ) diff --git a/tests/test_predefined_dynamical_decoupling.py b/tests/test_predefined_dynamical_decoupling.py index daf3a90..109646c 100644 --- a/tests/test_predefined_dynamical_decoupling.py +++ b/tests/test_predefined_dynamical_decoupling.py @@ -29,6 +29,7 @@ new_walsh_sequence, new_x_concatenated_sequence, new_xy_concatenated_sequence, + new_platonic_sequence, ) from qctrlopencontrols.constants import ( SIGMA_X, @@ -843,3 +844,117 @@ def test_if_xy_concatenated_sequence_is_identity(): ) assert _pulses_produce_identity(xy_concat_sequence) + + +def test_platonic_sequence(): + """ + Tests the platonic sequence. + """ + + duration = 10.0 + + for order in ["Dihedral", "Tetrahedral", "Octahedral", "Icosahedral"]: + sequence = new_platonic_sequence(duration=duration, sequence=order) + + count = { + "Dihedral": 8, + "Tetrahedral": 24, + "Octahedral": 48, + "Icosahedral": 120, + }[order] + _spacing = duration / count + + _offsets = np.array([(_spacing * 0.5 + i * _spacing) for i in range(count)]) + # fmt: off + eulerian_paths = { + "Dihedral": [0, 1, 0, 1, 1, 0, 1, 0], + "Tetrahedral": [ 0, 1, 0, 0, 1, 0, 1, 1, 1, 0, 0, 1, 0, 1, 1, 1, 0, 0, 1, 0, 1, 1, 0, 0], + "Octahedral": [ 0, 1, 0, 0, 0, 1, 1, 1, 0, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1, 1 ], + "Icosahedral": [ 1, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 0, 1, 1, 1, 0, 0, 1, 0, 1, 1, 1, 0, 0, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0 ] + } + # fmt: on + phi = (np.sqrt(5) + 1) / 2 # golden ratio + + _rabi_rotations = { + "Dihedral": np.ones(_offsets.shape) * np.pi, + "Tetrahedral": np.array(eulerian_paths["Tetrahedral"]) + * 4 + * np.sqrt(2) + * np.pi + / 9, + "Octahedral": np.array(eulerian_paths["Octahedral"]) + * 2 + * np.sqrt(2 / 3) + * np.pi + / 3, + "Icosahedral": -(np.array(eulerian_paths["Icosahedral"]) - 1) + * 2 + * np.pi + / 5 + / np.sqrt(phi + 2) + + np.array(eulerian_paths["Icosahedral"]) + * 2 + * np.pi + * (phi - 1) + / 3 + / np.sqrt(3), + }[order] + + _azimuthal_angles = { + "Dihedral": np.array(eulerian_paths["Dihedral"]) * np.pi / 2, + "Tetrahedral": np.array(eulerian_paths["Tetrahedral"]) * np.pi / 3, + "Octahedral": np.array(eulerian_paths["Octahedral"]) * np.pi / 4, + "Icosahedral": -(np.array(eulerian_paths["Icosahedral"]) - 1) + * 3 + * np.pi + / 2 + + np.array(eulerian_paths["Icosahedral"]) * np.pi, + }[order] + + _detuning_rotations = { + "Dihedral": np.zeros(_offsets.shape), + "Tetrahedral": -(np.array(eulerian_paths["Tetrahedral"]) - 1) + * 2 + * np.pi + / 3 + + np.array(eulerian_paths["Tetrahedral"]) * 2 * np.pi / 9, + "Octahedral": -(np.array(eulerian_paths["Octahedral"]) - 1) * np.pi / 2 + + np.array(eulerian_paths["Octahedral"]) * 2 * np.pi / 3 / np.sqrt(3), + "Icosahedral": -(np.array(eulerian_paths["Icosahedral"]) - 1) + * 2 + * np.pi + * phi + / 5 + / np.sqrt(phi + 2) + + np.array(eulerian_paths["Icosahedral"]) + * 2 + * np.pi + * phi + / 3 + / np.sqrt(3), + }[order] + + assert np.allclose(_offsets, sequence.offsets) + assert np.allclose(_rabi_rotations, sequence.rabi_rotations) + assert np.allclose(_azimuthal_angles, sequence.azimuthal_angles) + assert np.allclose(_detuning_rotations, sequence.detuning_rotations) + + sequence = new_platonic_sequence( + duration=duration, sequence=order, pre_post_rotation=True + ) + + _offsets = np.insert(_offsets, [0, _offsets.shape[0]], [0, duration]) + _rabi_rotations = np.insert( + _rabi_rotations, [0, _rabi_rotations.shape[0]], [np.pi / 2, np.pi / 2] + ) + _azimuthal_angles = np.insert( + _azimuthal_angles, [0, _azimuthal_angles.shape[0]], [0, np.pi] + ) + _detuning_rotations = np.insert( + _detuning_rotations, [0, _detuning_rotations.shape[0]], [0, 0] + ) + + assert np.allclose(_offsets, sequence.offsets) + assert np.allclose(_rabi_rotations, sequence.rabi_rotations) + assert np.allclose(_azimuthal_angles, sequence.azimuthal_angles) + assert np.allclose(_detuning_rotations, sequence.detuning_rotations) From 7569ca30880405ac930dc0b678cc45502980d931 Mon Sep 17 00:00:00 2001 From: Q-CTRL Robot <61040384+qctrlrobot@users.noreply.github.com> Date: Wed, 4 Feb 2026 12:44:42 +1100 Subject: [PATCH 2/4] chore: Update licensing comments (#333) --- docs/conf.py | 2 +- qctrlopencontrols/__init__.py | 2 +- qctrlopencontrols/constants.py | 2 +- qctrlopencontrols/driven_controls/__init__.py | 2 +- qctrlopencontrols/driven_controls/driven_control.py | 2 +- qctrlopencontrols/driven_controls/predefined.py | 2 +- qctrlopencontrols/dynamic_decoupling_sequences/__init__.py | 2 +- .../dynamic_decoupling_sequences/dynamic_decoupling_sequence.py | 2 +- qctrlopencontrols/dynamic_decoupling_sequences/predefined.py | 2 +- qctrlopencontrols/exceptions.py | 2 +- qctrlopencontrols/utils.py | 2 +- tests/__init__.py | 2 +- tests/test_driven_controls.py | 2 +- tests/test_dynamical_decoupling.py | 2 +- tests/test_predefined_driven_controls.py | 2 +- tests/test_predefined_dynamical_decoupling.py | 2 +- 16 files changed, 16 insertions(+), 16 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index c08c8c4..86b2dc2 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,4 +1,4 @@ -# Copyright 2025 Q-CTRL +# Copyright 2026 Q-CTRL # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/qctrlopencontrols/__init__.py b/qctrlopencontrols/__init__.py index bf87611..aff8490 100644 --- a/qctrlopencontrols/__init__.py +++ b/qctrlopencontrols/__init__.py @@ -1,4 +1,4 @@ -# Copyright 2025 Q-CTRL +# Copyright 2026 Q-CTRL # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/qctrlopencontrols/constants.py b/qctrlopencontrols/constants.py index d16167f..b667dac 100644 --- a/qctrlopencontrols/constants.py +++ b/qctrlopencontrols/constants.py @@ -1,4 +1,4 @@ -# Copyright 2025 Q-CTRL +# Copyright 2026 Q-CTRL # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/qctrlopencontrols/driven_controls/__init__.py b/qctrlopencontrols/driven_controls/__init__.py index 7acd2fc..46c05a7 100644 --- a/qctrlopencontrols/driven_controls/__init__.py +++ b/qctrlopencontrols/driven_controls/__init__.py @@ -1,4 +1,4 @@ -# Copyright 2025 Q-CTRL +# Copyright 2026 Q-CTRL # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/qctrlopencontrols/driven_controls/driven_control.py b/qctrlopencontrols/driven_controls/driven_control.py index 68cc59e..3a65e18 100644 --- a/qctrlopencontrols/driven_controls/driven_control.py +++ b/qctrlopencontrols/driven_controls/driven_control.py @@ -1,4 +1,4 @@ -# Copyright 2025 Q-CTRL +# Copyright 2026 Q-CTRL # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/qctrlopencontrols/driven_controls/predefined.py b/qctrlopencontrols/driven_controls/predefined.py index 524142f..dabf5e5 100644 --- a/qctrlopencontrols/driven_controls/predefined.py +++ b/qctrlopencontrols/driven_controls/predefined.py @@ -1,4 +1,4 @@ -# Copyright 2025 Q-CTRL +# Copyright 2026 Q-CTRL # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/qctrlopencontrols/dynamic_decoupling_sequences/__init__.py b/qctrlopencontrols/dynamic_decoupling_sequences/__init__.py index 7acd2fc..46c05a7 100644 --- a/qctrlopencontrols/dynamic_decoupling_sequences/__init__.py +++ b/qctrlopencontrols/dynamic_decoupling_sequences/__init__.py @@ -1,4 +1,4 @@ -# Copyright 2025 Q-CTRL +# Copyright 2026 Q-CTRL # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/qctrlopencontrols/dynamic_decoupling_sequences/dynamic_decoupling_sequence.py b/qctrlopencontrols/dynamic_decoupling_sequences/dynamic_decoupling_sequence.py index 57a5ce0..e546d1d 100644 --- a/qctrlopencontrols/dynamic_decoupling_sequences/dynamic_decoupling_sequence.py +++ b/qctrlopencontrols/dynamic_decoupling_sequences/dynamic_decoupling_sequence.py @@ -1,4 +1,4 @@ -# Copyright 2025 Q-CTRL +# Copyright 2026 Q-CTRL # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/qctrlopencontrols/dynamic_decoupling_sequences/predefined.py b/qctrlopencontrols/dynamic_decoupling_sequences/predefined.py index 3e695fc..20d8ce8 100644 --- a/qctrlopencontrols/dynamic_decoupling_sequences/predefined.py +++ b/qctrlopencontrols/dynamic_decoupling_sequences/predefined.py @@ -1,4 +1,4 @@ -# Copyright 2025 Q-CTRL +# Copyright 2026 Q-CTRL # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/qctrlopencontrols/exceptions.py b/qctrlopencontrols/exceptions.py index e503f78..a17bb90 100644 --- a/qctrlopencontrols/exceptions.py +++ b/qctrlopencontrols/exceptions.py @@ -1,4 +1,4 @@ -# Copyright 2025 Q-CTRL +# Copyright 2026 Q-CTRL # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/qctrlopencontrols/utils.py b/qctrlopencontrols/utils.py index ea6aa31..4e1777d 100644 --- a/qctrlopencontrols/utils.py +++ b/qctrlopencontrols/utils.py @@ -1,4 +1,4 @@ -# Copyright 2025 Q-CTRL +# Copyright 2026 Q-CTRL # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/tests/__init__.py b/tests/__init__.py index a41e90d..e2b52da 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -1,4 +1,4 @@ -# Copyright 2025 Q-CTRL +# Copyright 2026 Q-CTRL # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/tests/test_driven_controls.py b/tests/test_driven_controls.py index 4034e31..0de90ff 100644 --- a/tests/test_driven_controls.py +++ b/tests/test_driven_controls.py @@ -1,4 +1,4 @@ -# Copyright 2025 Q-CTRL +# Copyright 2026 Q-CTRL # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/tests/test_dynamical_decoupling.py b/tests/test_dynamical_decoupling.py index 18d6a39..669527f 100644 --- a/tests/test_dynamical_decoupling.py +++ b/tests/test_dynamical_decoupling.py @@ -1,4 +1,4 @@ -# Copyright 2025 Q-CTRL +# Copyright 2026 Q-CTRL # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/tests/test_predefined_driven_controls.py b/tests/test_predefined_driven_controls.py index 901b8bd..13a6247 100644 --- a/tests/test_predefined_driven_controls.py +++ b/tests/test_predefined_driven_controls.py @@ -1,4 +1,4 @@ -# Copyright 2025 Q-CTRL +# Copyright 2026 Q-CTRL # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/tests/test_predefined_dynamical_decoupling.py b/tests/test_predefined_dynamical_decoupling.py index 109646c..4e9ddbf 100644 --- a/tests/test_predefined_dynamical_decoupling.py +++ b/tests/test_predefined_dynamical_decoupling.py @@ -1,4 +1,4 @@ -# Copyright 2025 Q-CTRL +# Copyright 2026 Q-CTRL # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. From a165dc8f0be5f15e832b9d9dee3003802245d058 Mon Sep 17 00:00:00 2001 From: Hector Brown Date: Wed, 1 Apr 2026 16:46:09 +1100 Subject: [PATCH 3/4] split platonic test by sequence order and hardcode arrays --- tests/test_predefined_dynamical_decoupling.py | 1092 +++++++++++++++-- 1 file changed, 987 insertions(+), 105 deletions(-) diff --git a/tests/test_predefined_dynamical_decoupling.py b/tests/test_predefined_dynamical_decoupling.py index 4e9ddbf..33df83a 100644 --- a/tests/test_predefined_dynamical_decoupling.py +++ b/tests/test_predefined_dynamical_decoupling.py @@ -846,115 +846,997 @@ def test_if_xy_concatenated_sequence_is_identity(): assert _pulses_produce_identity(xy_concat_sequence) -def test_platonic_sequence(): +def test_dihedral_platonic_sequence(): """ - Tests the platonic sequence. + Tests the Dihedral order of the platonic sequence. """ + duration = 10.0 + sequence = new_platonic_sequence(duration=duration, sequence="Dihedral") + count = 8 + + _spacing = duration / count + + _offsets = np.array( + [ + _spacing * 0.5, + _spacing * 0.5 + _spacing, + _spacing * 0.5 + 2 * _spacing, + _spacing * 0.5 + 3 * _spacing, + _spacing * 0.5 + 4 * _spacing, + _spacing * 0.5 + 5 * _spacing, + _spacing * 0.5 + 6 * _spacing, + _spacing * 0.5 + 7 * _spacing, + ] + ) + + _rabi_rotations = np.ones(_offsets.shape) * np.pi + + _azimuthal_angles = np.array( + [0, np.pi / 2, 0, np.pi / 2, np.pi / 2, 0, np.pi / 2, 0] + ) + + _detuning_rotations = np.zeros(_offsets.shape) + + assert np.allclose(_offsets, sequence.offsets) + assert np.allclose(_rabi_rotations, sequence.rabi_rotations) + assert np.allclose(_azimuthal_angles, sequence.azimuthal_angles) + assert np.allclose(_detuning_rotations, sequence.detuning_rotations) + + sequence = new_platonic_sequence( + duration=duration, sequence="Dihedral", pre_post_rotation=True + ) + _offsets = np.insert(_offsets, [0, _offsets.shape[0]], [0, duration]) + _rabi_rotations = np.insert( + _rabi_rotations, [0, _rabi_rotations.shape[0]], [np.pi / 2, np.pi / 2] + ) + _azimuthal_angles = np.insert( + _azimuthal_angles, [0, _azimuthal_angles.shape[0]], [0, np.pi] + ) + _detuning_rotations = np.insert( + _detuning_rotations, [0, _detuning_rotations.shape[0]], [0, 0] + ) + + assert np.allclose(_offsets, sequence.offsets) + assert np.allclose(_rabi_rotations, sequence.rabi_rotations) + assert np.allclose(_azimuthal_angles, sequence.azimuthal_angles) + assert np.allclose(_detuning_rotations, sequence.detuning_rotations) + + +def test_tetrahedral_platonic_sequence(): + """ + Tests the Tetrahedral order of the platonic sequence. + """ duration = 10.0 + sequence = new_platonic_sequence(duration=duration, sequence="Tetrahedral") - for order in ["Dihedral", "Tetrahedral", "Octahedral", "Icosahedral"]: - sequence = new_platonic_sequence(duration=duration, sequence=order) - - count = { - "Dihedral": 8, - "Tetrahedral": 24, - "Octahedral": 48, - "Icosahedral": 120, - }[order] - _spacing = duration / count - - _offsets = np.array([(_spacing * 0.5 + i * _spacing) for i in range(count)]) - # fmt: off - eulerian_paths = { - "Dihedral": [0, 1, 0, 1, 1, 0, 1, 0], - "Tetrahedral": [ 0, 1, 0, 0, 1, 0, 1, 1, 1, 0, 0, 1, 0, 1, 1, 1, 0, 0, 1, 0, 1, 1, 0, 0], - "Octahedral": [ 0, 1, 0, 0, 0, 1, 1, 1, 0, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1, 1 ], - "Icosahedral": [ 1, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 0, 1, 1, 1, 0, 0, 1, 0, 1, 1, 1, 0, 0, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0 ] - } - # fmt: on - phi = (np.sqrt(5) + 1) / 2 # golden ratio - - _rabi_rotations = { - "Dihedral": np.ones(_offsets.shape) * np.pi, - "Tetrahedral": np.array(eulerian_paths["Tetrahedral"]) - * 4 - * np.sqrt(2) - * np.pi - / 9, - "Octahedral": np.array(eulerian_paths["Octahedral"]) - * 2 - * np.sqrt(2 / 3) - * np.pi - / 3, - "Icosahedral": -(np.array(eulerian_paths["Icosahedral"]) - 1) - * 2 - * np.pi - / 5 - / np.sqrt(phi + 2) - + np.array(eulerian_paths["Icosahedral"]) - * 2 - * np.pi - * (phi - 1) - / 3 - / np.sqrt(3), - }[order] - - _azimuthal_angles = { - "Dihedral": np.array(eulerian_paths["Dihedral"]) * np.pi / 2, - "Tetrahedral": np.array(eulerian_paths["Tetrahedral"]) * np.pi / 3, - "Octahedral": np.array(eulerian_paths["Octahedral"]) * np.pi / 4, - "Icosahedral": -(np.array(eulerian_paths["Icosahedral"]) - 1) - * 3 - * np.pi - / 2 - + np.array(eulerian_paths["Icosahedral"]) * np.pi, - }[order] - - _detuning_rotations = { - "Dihedral": np.zeros(_offsets.shape), - "Tetrahedral": -(np.array(eulerian_paths["Tetrahedral"]) - 1) - * 2 - * np.pi - / 3 - + np.array(eulerian_paths["Tetrahedral"]) * 2 * np.pi / 9, - "Octahedral": -(np.array(eulerian_paths["Octahedral"]) - 1) * np.pi / 2 - + np.array(eulerian_paths["Octahedral"]) * 2 * np.pi / 3 / np.sqrt(3), - "Icosahedral": -(np.array(eulerian_paths["Icosahedral"]) - 1) - * 2 - * np.pi - * phi - / 5 - / np.sqrt(phi + 2) - + np.array(eulerian_paths["Icosahedral"]) - * 2 - * np.pi - * phi - / 3 - / np.sqrt(3), - }[order] - - assert np.allclose(_offsets, sequence.offsets) - assert np.allclose(_rabi_rotations, sequence.rabi_rotations) - assert np.allclose(_azimuthal_angles, sequence.azimuthal_angles) - assert np.allclose(_detuning_rotations, sequence.detuning_rotations) - - sequence = new_platonic_sequence( - duration=duration, sequence=order, pre_post_rotation=True - ) + count = 24 + _spacing = duration / count - _offsets = np.insert(_offsets, [0, _offsets.shape[0]], [0, duration]) - _rabi_rotations = np.insert( - _rabi_rotations, [0, _rabi_rotations.shape[0]], [np.pi / 2, np.pi / 2] - ) - _azimuthal_angles = np.insert( - _azimuthal_angles, [0, _azimuthal_angles.shape[0]], [0, np.pi] - ) - _detuning_rotations = np.insert( - _detuning_rotations, [0, _detuning_rotations.shape[0]], [0, 0] - ) + _offsets = np.array( + [ + _spacing * 0.5, + _spacing * 0.5 + _spacing, + _spacing * 0.5 + 2 * _spacing, + _spacing * 0.5 + 3 * _spacing, + _spacing * 0.5 + 4 * _spacing, + _spacing * 0.5 + 5 * _spacing, + _spacing * 0.5 + 6 * _spacing, + _spacing * 0.5 + 7 * _spacing, + _spacing * 0.5 + 8 * _spacing, + _spacing * 0.5 + 9 * _spacing, + _spacing * 0.5 + 10 * _spacing, + _spacing * 0.5 + 11 * _spacing, + _spacing * 0.5 + 12 * _spacing, + _spacing * 0.5 + 13 * _spacing, + _spacing * 0.5 + 14 * _spacing, + _spacing * 0.5 + 15 * _spacing, + _spacing * 0.5 + 16 * _spacing, + _spacing * 0.5 + 17 * _spacing, + _spacing * 0.5 + 18 * _spacing, + _spacing * 0.5 + 19 * _spacing, + _spacing * 0.5 + 20 * _spacing, + _spacing * 0.5 + 21 * _spacing, + _spacing * 0.5 + 22 * _spacing, + _spacing * 0.5 + 23 * _spacing, + ] + ) - assert np.allclose(_offsets, sequence.offsets) - assert np.allclose(_rabi_rotations, sequence.rabi_rotations) - assert np.allclose(_azimuthal_angles, sequence.azimuthal_angles) - assert np.allclose(_detuning_rotations, sequence.detuning_rotations) + _rabi_rotations = np.array( + [ + 0, + 4 * np.sqrt(2) * np.pi / 9, + 0, + 0, + 4 * np.sqrt(2) * np.pi / 9, + 0, + 4 * np.sqrt(2) * np.pi / 9, + 4 * np.sqrt(2) * np.pi / 9, + 4 * np.sqrt(2) * np.pi / 9, + 0, + 0, + 4 * np.sqrt(2) * np.pi / 9, + 0, + 4 * np.sqrt(2) * np.pi / 9, + 4 * np.sqrt(2) * np.pi / 9, + 4 * np.sqrt(2) * np.pi / 9, + 0, + 0, + 4 * np.sqrt(2) * np.pi / 9, + 0, + 4 * np.sqrt(2) * np.pi / 9, + 4 * np.sqrt(2) * np.pi / 9, + 0, + 0, + ] + ) + + _azimuthal_angles = np.array( + [ + 0, + np.pi / 3, + 0, + 0, + np.pi / 3, + 0, + np.pi / 3, + np.pi / 3, + np.pi / 3, + 0, + 0, + np.pi / 3, + 0, + np.pi / 3, + np.pi / 3, + np.pi / 3, + 0, + 0, + np.pi / 3, + 0, + np.pi / 3, + np.pi / 3, + 0, + 0, + ] + ) + + _detuning_rotations = np.array( + [ + 2 * np.pi / 3, + 2 * np.pi / 9, + 2 * np.pi / 3, + 2 * np.pi / 3, + 2 * np.pi / 9, + 2 * np.pi / 3, + 2 * np.pi / 9, + 2 * np.pi / 9, + 2 * np.pi / 9, + 2 * np.pi / 3, + 2 * np.pi / 3, + 2 * np.pi / 9, + 2 * np.pi / 3, + 2 * np.pi / 9, + 2 * np.pi / 9, + 2 * np.pi / 9, + 2 * np.pi / 3, + 2 * np.pi / 3, + 2 * np.pi / 9, + 2 * np.pi / 3, + 2 * np.pi / 9, + 2 * np.pi / 9, + 2 * np.pi / 3, + 2 * np.pi / 3, + ] + ) + + assert np.allclose(_offsets, sequence.offsets) + assert np.allclose(_rabi_rotations, sequence.rabi_rotations) + assert np.allclose(_azimuthal_angles, sequence.azimuthal_angles) + assert np.allclose(_detuning_rotations, sequence.detuning_rotations) + + sequence = new_platonic_sequence( + duration=duration, sequence="Tetrahedral", pre_post_rotation=True + ) + + _offsets = np.insert(_offsets, [0, _offsets.shape[0]], [0, duration]) + _rabi_rotations = np.insert( + _rabi_rotations, [0, _rabi_rotations.shape[0]], [np.pi / 2, np.pi / 2] + ) + _azimuthal_angles = np.insert( + _azimuthal_angles, [0, _azimuthal_angles.shape[0]], [0, np.pi] + ) + _detuning_rotations = np.insert( + _detuning_rotations, [0, _detuning_rotations.shape[0]], [0, 0] + ) + + assert np.allclose(_offsets, sequence.offsets) + assert np.allclose(_rabi_rotations, sequence.rabi_rotations) + assert np.allclose(_azimuthal_angles, sequence.azimuthal_angles) + assert np.allclose(_detuning_rotations, sequence.detuning_rotations) + + +def test_octahedral_platonic_sequence(): + """ + Tests the Octahedral order of the platonic sequence. + """ + duration = 10.0 + sequence = new_platonic_sequence(duration=duration, sequence="Octahedral") + + count = 48 + _spacing = duration / count + + _offsets = np.array( + [ + _spacing * 0.5, + _spacing * 0.5 + _spacing, + _spacing * 0.5 + 2 * _spacing, + _spacing * 0.5 + 3 * _spacing, + _spacing * 0.5 + 4 * _spacing, + _spacing * 0.5 + 5 * _spacing, + _spacing * 0.5 + 6 * _spacing, + _spacing * 0.5 + 7 * _spacing, + _spacing * 0.5 + 8 * _spacing, + _spacing * 0.5 + 9 * _spacing, + _spacing * 0.5 + 10 * _spacing, + _spacing * 0.5 + 11 * _spacing, + _spacing * 0.5 + 12 * _spacing, + _spacing * 0.5 + 13 * _spacing, + _spacing * 0.5 + 14 * _spacing, + _spacing * 0.5 + 15 * _spacing, + _spacing * 0.5 + 16 * _spacing, + _spacing * 0.5 + 17 * _spacing, + _spacing * 0.5 + 18 * _spacing, + _spacing * 0.5 + 19 * _spacing, + _spacing * 0.5 + 20 * _spacing, + _spacing * 0.5 + 21 * _spacing, + _spacing * 0.5 + 22 * _spacing, + _spacing * 0.5 + 23 * _spacing, + _spacing * 0.5 + 24 * _spacing, + _spacing * 0.5 + 25 * _spacing, + _spacing * 0.5 + 26 * _spacing, + _spacing * 0.5 + 27 * _spacing, + _spacing * 0.5 + 28 * _spacing, + _spacing * 0.5 + 29 * _spacing, + _spacing * 0.5 + 30 * _spacing, + _spacing * 0.5 + 31 * _spacing, + _spacing * 0.5 + 32 * _spacing, + _spacing * 0.5 + 33 * _spacing, + _spacing * 0.5 + 34 * _spacing, + _spacing * 0.5 + 35 * _spacing, + _spacing * 0.5 + 36 * _spacing, + _spacing * 0.5 + 37 * _spacing, + _spacing * 0.5 + 38 * _spacing, + _spacing * 0.5 + 39 * _spacing, + _spacing * 0.5 + 40 * _spacing, + _spacing * 0.5 + 41 * _spacing, + _spacing * 0.5 + 42 * _spacing, + _spacing * 0.5 + 43 * _spacing, + _spacing * 0.5 + 44 * _spacing, + _spacing * 0.5 + 45 * _spacing, + _spacing * 0.5 + 46 * _spacing, + _spacing * 0.5 + 47 * _spacing, + ] + ) + + _rabi_rotations = np.array( + [ + 0, + 2 * np.sqrt(2 / 3) * np.pi / 3, + 0, + 0, + 0, + 2 * np.sqrt(2 / 3) * np.pi / 3, + 2 * np.sqrt(2 / 3) * np.pi / 3, + 2 * np.sqrt(2 / 3) * np.pi / 3, + 0, + 2 * np.sqrt(2 / 3) * np.pi / 3, + 0, + 0, + 2 * np.sqrt(2 / 3) * np.pi / 3, + 2 * np.sqrt(2 / 3) * np.pi / 3, + 2 * np.sqrt(2 / 3) * np.pi / 3, + 0, + 0, + 2 * np.sqrt(2 / 3) * np.pi / 3, + 0, + 2 * np.sqrt(2 / 3) * np.pi / 3, + 2 * np.sqrt(2 / 3) * np.pi / 3, + 0, + 0, + 0, + 0, + 2 * np.sqrt(2 / 3) * np.pi / 3, + 0, + 2 * np.sqrt(2 / 3) * np.pi / 3, + 2 * np.sqrt(2 / 3) * np.pi / 3, + 2 * np.sqrt(2 / 3) * np.pi / 3, + 0, + 2 * np.sqrt(2 / 3) * np.pi / 3, + 0, + 0, + 2 * np.sqrt(2 / 3) * np.pi / 3, + 2 * np.sqrt(2 / 3) * np.pi / 3, + 0, + 0, + 0, + 0, + 2 * np.sqrt(2 / 3) * np.pi / 3, + 0, + 2 * np.sqrt(2 / 3) * np.pi / 3, + 2 * np.sqrt(2 / 3) * np.pi / 3, + 2 * np.sqrt(2 / 3) * np.pi / 3, + 0, + 2 * np.sqrt(2 / 3) * np.pi / 3, + 2 * np.sqrt(2 / 3) * np.pi / 3, + ] + ) + + _azimuthal_angles = np.array( + [ + 0, + np.pi / 4, + 0, + 0, + 0, + np.pi / 4, + np.pi / 4, + np.pi / 4, + 0, + np.pi / 4, + 0, + 0, + np.pi / 4, + np.pi / 4, + np.pi / 4, + 0, + 0, + np.pi / 4, + 0, + np.pi / 4, + np.pi / 4, + 0, + 0, + 0, + 0, + np.pi / 4, + 0, + np.pi / 4, + np.pi / 4, + np.pi / 4, + 0, + np.pi / 4, + 0, + 0, + np.pi / 4, + np.pi / 4, + 0, + 0, + 0, + 0, + np.pi / 4, + 0, + np.pi / 4, + np.pi / 4, + np.pi / 4, + 0, + np.pi / 4, + np.pi / 4, + ] + ) + + _detuning_rotations = np.array( + [ + np.pi / 2, + 2 * np.pi / 3 / np.sqrt(3), + np.pi / 2, + np.pi / 2, + np.pi / 2, + 2 * np.pi / 3 / np.sqrt(3), + 2 * np.pi / 3 / np.sqrt(3), + 2 * np.pi / 3 / np.sqrt(3), + np.pi / 2, + 2 * np.pi / 3 / np.sqrt(3), + np.pi / 2, + np.pi / 2, + 2 * np.pi / 3 / np.sqrt(3), + 2 * np.pi / 3 / np.sqrt(3), + 2 * np.pi / 3 / np.sqrt(3), + np.pi / 2, + np.pi / 2, + 2 * np.pi / 3 / np.sqrt(3), + np.pi / 2, + 2 * np.pi / 3 / np.sqrt(3), + 2 * np.pi / 3 / np.sqrt(3), + np.pi / 2, + np.pi / 2, + np.pi / 2, + np.pi / 2, + 2 * np.pi / 3 / np.sqrt(3), + np.pi / 2, + 2 * np.pi / 3 / np.sqrt(3), + 2 * np.pi / 3 / np.sqrt(3), + 2 * np.pi / 3 / np.sqrt(3), + np.pi / 2, + 2 * np.pi / 3 / np.sqrt(3), + np.pi / 2, + np.pi / 2, + 2 * np.pi / 3 / np.sqrt(3), + 2 * np.pi / 3 / np.sqrt(3), + np.pi / 2, + np.pi / 2, + np.pi / 2, + np.pi / 2, + 2 * np.pi / 3 / np.sqrt(3), + np.pi / 2, + 2 * np.pi / 3 / np.sqrt(3), + 2 * np.pi / 3 / np.sqrt(3), + 2 * np.pi / 3 / np.sqrt(3), + np.pi / 2, + 2 * np.pi / 3 / np.sqrt(3), + 2 * np.pi / 3 / np.sqrt(3), + ] + ) + + assert np.allclose(_offsets, sequence.offsets) + assert np.allclose(_rabi_rotations, sequence.rabi_rotations) + assert np.allclose(_azimuthal_angles, sequence.azimuthal_angles) + assert np.allclose(_detuning_rotations, sequence.detuning_rotations) + + sequence = new_platonic_sequence( + duration=duration, sequence="Octahedral", pre_post_rotation=True + ) + + _offsets = np.insert(_offsets, [0, _offsets.shape[0]], [0, duration]) + _rabi_rotations = np.insert( + _rabi_rotations, [0, _rabi_rotations.shape[0]], [np.pi / 2, np.pi / 2] + ) + _azimuthal_angles = np.insert( + _azimuthal_angles, [0, _azimuthal_angles.shape[0]], [0, np.pi] + ) + _detuning_rotations = np.insert( + _detuning_rotations, [0, _detuning_rotations.shape[0]], [0, 0] + ) + + assert np.allclose(_offsets, sequence.offsets) + assert np.allclose(_rabi_rotations, sequence.rabi_rotations) + assert np.allclose(_azimuthal_angles, sequence.azimuthal_angles) + assert np.allclose(_detuning_rotations, sequence.detuning_rotations) + + +def test_icosahedral_platonic_sequence(): + """ + Tests the Icosahedral order of the platonic sequence. + """ + duration = 10.0 + + sequence = new_platonic_sequence(duration=duration, sequence="Icosahedral") + + count = 120 + _spacing = duration / count + + _offsets = np.array( + [ + _spacing * 0.5, + _spacing * 0.5 + _spacing, + _spacing * 0.5 + 2 * _spacing, + _spacing * 0.5 + 3 * _spacing, + _spacing * 0.5 + 4 * _spacing, + _spacing * 0.5 + 5 * _spacing, + _spacing * 0.5 + 6 * _spacing, + _spacing * 0.5 + 7 * _spacing, + _spacing * 0.5 + 8 * _spacing, + _spacing * 0.5 + 9 * _spacing, + _spacing * 0.5 + 10 * _spacing, + _spacing * 0.5 + 11 * _spacing, + _spacing * 0.5 + 12 * _spacing, + _spacing * 0.5 + 13 * _spacing, + _spacing * 0.5 + 14 * _spacing, + _spacing * 0.5 + 15 * _spacing, + _spacing * 0.5 + 16 * _spacing, + _spacing * 0.5 + 17 * _spacing, + _spacing * 0.5 + 18 * _spacing, + _spacing * 0.5 + 19 * _spacing, + _spacing * 0.5 + 20 * _spacing, + _spacing * 0.5 + 21 * _spacing, + _spacing * 0.5 + 22 * _spacing, + _spacing * 0.5 + 23 * _spacing, + _spacing * 0.5 + 24 * _spacing, + _spacing * 0.5 + 25 * _spacing, + _spacing * 0.5 + 26 * _spacing, + _spacing * 0.5 + 27 * _spacing, + _spacing * 0.5 + 28 * _spacing, + _spacing * 0.5 + 29 * _spacing, + _spacing * 0.5 + 30 * _spacing, + _spacing * 0.5 + 31 * _spacing, + _spacing * 0.5 + 32 * _spacing, + _spacing * 0.5 + 33 * _spacing, + _spacing * 0.5 + 34 * _spacing, + _spacing * 0.5 + 35 * _spacing, + _spacing * 0.5 + 36 * _spacing, + _spacing * 0.5 + 37 * _spacing, + _spacing * 0.5 + 38 * _spacing, + _spacing * 0.5 + 39 * _spacing, + _spacing * 0.5 + 40 * _spacing, + _spacing * 0.5 + 41 * _spacing, + _spacing * 0.5 + 42 * _spacing, + _spacing * 0.5 + 43 * _spacing, + _spacing * 0.5 + 44 * _spacing, + _spacing * 0.5 + 45 * _spacing, + _spacing * 0.5 + 46 * _spacing, + _spacing * 0.5 + 47 * _spacing, + _spacing * 0.5 + 48 * _spacing, + _spacing * 0.5 + 49 * _spacing, + _spacing * 0.5 + 50 * _spacing, + _spacing * 0.5 + 51 * _spacing, + _spacing * 0.5 + 52 * _spacing, + _spacing * 0.5 + 53 * _spacing, + _spacing * 0.5 + 54 * _spacing, + _spacing * 0.5 + 55 * _spacing, + _spacing * 0.5 + 56 * _spacing, + _spacing * 0.5 + 57 * _spacing, + _spacing * 0.5 + 58 * _spacing, + _spacing * 0.5 + 59 * _spacing, + _spacing * 0.5 + 60 * _spacing, + _spacing * 0.5 + 61 * _spacing, + _spacing * 0.5 + 62 * _spacing, + _spacing * 0.5 + 63 * _spacing, + _spacing * 0.5 + 64 * _spacing, + _spacing * 0.5 + 65 * _spacing, + _spacing * 0.5 + 66 * _spacing, + _spacing * 0.5 + 67 * _spacing, + _spacing * 0.5 + 68 * _spacing, + _spacing * 0.5 + 69 * _spacing, + _spacing * 0.5 + 70 * _spacing, + _spacing * 0.5 + 71 * _spacing, + _spacing * 0.5 + 72 * _spacing, + _spacing * 0.5 + 73 * _spacing, + _spacing * 0.5 + 74 * _spacing, + _spacing * 0.5 + 75 * _spacing, + _spacing * 0.5 + 76 * _spacing, + _spacing * 0.5 + 77 * _spacing, + _spacing * 0.5 + 78 * _spacing, + _spacing * 0.5 + 79 * _spacing, + _spacing * 0.5 + 80 * _spacing, + _spacing * 0.5 + 81 * _spacing, + _spacing * 0.5 + 82 * _spacing, + _spacing * 0.5 + 83 * _spacing, + _spacing * 0.5 + 84 * _spacing, + _spacing * 0.5 + 85 * _spacing, + _spacing * 0.5 + 86 * _spacing, + _spacing * 0.5 + 87 * _spacing, + _spacing * 0.5 + 88 * _spacing, + _spacing * 0.5 + 89 * _spacing, + _spacing * 0.5 + 90 * _spacing, + _spacing * 0.5 + 91 * _spacing, + _spacing * 0.5 + 92 * _spacing, + _spacing * 0.5 + 93 * _spacing, + _spacing * 0.5 + 94 * _spacing, + _spacing * 0.5 + 95 * _spacing, + _spacing * 0.5 + 96 * _spacing, + _spacing * 0.5 + 97 * _spacing, + _spacing * 0.5 + 98 * _spacing, + _spacing * 0.5 + 99 * _spacing, + _spacing * 0.5 + 100 * _spacing, + _spacing * 0.5 + 101 * _spacing, + _spacing * 0.5 + 102 * _spacing, + _spacing * 0.5 + 103 * _spacing, + _spacing * 0.5 + 104 * _spacing, + _spacing * 0.5 + 105 * _spacing, + _spacing * 0.5 + 106 * _spacing, + _spacing * 0.5 + 107 * _spacing, + _spacing * 0.5 + 108 * _spacing, + _spacing * 0.5 + 109 * _spacing, + _spacing * 0.5 + 110 * _spacing, + _spacing * 0.5 + 111 * _spacing, + _spacing * 0.5 + 112 * _spacing, + _spacing * 0.5 + 113 * _spacing, + _spacing * 0.5 + 114 * _spacing, + _spacing * 0.5 + 115 * _spacing, + _spacing * 0.5 + 116 * _spacing, + _spacing * 0.5 + 117 * _spacing, + _spacing * 0.5 + 118 * _spacing, + _spacing * 0.5 + 119 * _spacing, + ] + ) + + phi = (np.sqrt(5) + 1) / 2 # golden ratio + + _rabi_rotations = np.array( + [ + 2 * np.pi * (phi - 1) / 3 / np.sqrt(3), + 2 * np.pi / 5 / np.sqrt(phi + 2), + 2 * np.pi / 5 / np.sqrt(phi + 2), + 2 * np.pi / 5 / np.sqrt(phi + 2), + 2 * np.pi * (phi - 1) / 3 / np.sqrt(3), + 2 * np.pi * (phi - 1) / 3 / np.sqrt(3), + 2 * np.pi / 5 / np.sqrt(phi + 2), + 2 * np.pi / 5 / np.sqrt(phi + 2), + 2 * np.pi * (phi - 1) / 3 / np.sqrt(3), + 2 * np.pi / 5 / np.sqrt(phi + 2), + 2 * np.pi / 5 / np.sqrt(phi + 2), + 2 * np.pi / 5 / np.sqrt(phi + 2), + 2 * np.pi / 5 / np.sqrt(phi + 2), + 2 * np.pi / 5 / np.sqrt(phi + 2), + 2 * np.pi * (phi - 1) / 3 / np.sqrt(3), + 2 * np.pi * (phi - 1) / 3 / np.sqrt(3), + 2 * np.pi / 5 / np.sqrt(phi + 2), + 2 * np.pi / 5 / np.sqrt(phi + 2), + 2 * np.pi / 5 / np.sqrt(phi + 2), + 2 * np.pi * (phi - 1) / 3 / np.sqrt(3), + 2 * np.pi / 5 / np.sqrt(phi + 2), + 2 * np.pi * (phi - 1) / 3 / np.sqrt(3), + 2 * np.pi * (phi - 1) / 3 / np.sqrt(3), + 2 * np.pi * (phi - 1) / 3 / np.sqrt(3), + 2 * np.pi / 5 / np.sqrt(phi + 2), + 2 * np.pi * (phi - 1) / 3 / np.sqrt(3), + 2 * np.pi / 5 / np.sqrt(phi + 2), + 2 * np.pi / 5 / np.sqrt(phi + 2), + 2 * np.pi * (phi - 1) / 3 / np.sqrt(3), + 2 * np.pi * (phi - 1) / 3 / np.sqrt(3), + 2 * np.pi / 5 / np.sqrt(phi + 2), + 2 * np.pi / 5 / np.sqrt(phi + 2), + 2 * np.pi * (phi - 1) / 3 / np.sqrt(3), + 2 * np.pi * (phi - 1) / 3 / np.sqrt(3), + 2 * np.pi / 5 / np.sqrt(phi + 2), + 2 * np.pi * (phi - 1) / 3 / np.sqrt(3), + 2 * np.pi * (phi - 1) / 3 / np.sqrt(3), + 2 * np.pi / 5 / np.sqrt(phi + 2), + 2 * np.pi * (phi - 1) / 3 / np.sqrt(3), + 2 * np.pi * (phi - 1) / 3 / np.sqrt(3), + 2 * np.pi / 5 / np.sqrt(phi + 2), + 2 * np.pi * (phi - 1) / 3 / np.sqrt(3), + 2 * np.pi * (phi - 1) / 3 / np.sqrt(3), + 2 * np.pi * (phi - 1) / 3 / np.sqrt(3), + 2 * np.pi / 5 / np.sqrt(phi + 2), + 2 * np.pi / 5 / np.sqrt(phi + 2), + 2 * np.pi / 5 / np.sqrt(phi + 2), + 2 * np.pi / 5 / np.sqrt(phi + 2), + 2 * np.pi * (phi - 1) / 3 / np.sqrt(3), + 2 * np.pi / 5 / np.sqrt(phi + 2), + 2 * np.pi * (phi - 1) / 3 / np.sqrt(3), + 2 * np.pi * (phi - 1) / 3 / np.sqrt(3), + 2 * np.pi * (phi - 1) / 3 / np.sqrt(3), + 2 * np.pi / 5 / np.sqrt(phi + 2), + 2 * np.pi / 5 / np.sqrt(phi + 2), + 2 * np.pi / 5 / np.sqrt(phi + 2), + 2 * np.pi * (phi - 1) / 3 / np.sqrt(3), + 2 * np.pi / 5 / np.sqrt(phi + 2), + 2 * np.pi * (phi - 1) / 3 / np.sqrt(3), + 2 * np.pi * (phi - 1) / 3 / np.sqrt(3), + 2 * np.pi * (phi - 1) / 3 / np.sqrt(3), + 2 * np.pi / 5 / np.sqrt(phi + 2), + 2 * np.pi / 5 / np.sqrt(phi + 2), + 2 * np.pi / 5 / np.sqrt(phi + 2), + 2 * np.pi * (phi - 1) / 3 / np.sqrt(3), + 2 * np.pi / 5 / np.sqrt(phi + 2), + 2 * np.pi * (phi - 1) / 3 / np.sqrt(3), + 2 * np.pi * (phi - 1) / 3 / np.sqrt(3), + 2 * np.pi * (phi - 1) / 3 / np.sqrt(3), + 2 * np.pi / 5 / np.sqrt(phi + 2), + 2 * np.pi / 5 / np.sqrt(phi + 2), + 2 * np.pi * (phi - 1) / 3 / np.sqrt(3), + 2 * np.pi / 5 / np.sqrt(phi + 2), + 2 * np.pi * (phi - 1) / 3 / np.sqrt(3), + 2 * np.pi * (phi - 1) / 3 / np.sqrt(3), + 2 * np.pi / 5 / np.sqrt(phi + 2), + 2 * np.pi / 5 / np.sqrt(phi + 2), + 2 * np.pi * (phi - 1) / 3 / np.sqrt(3), + 2 * np.pi * (phi - 1) / 3 / np.sqrt(3), + 2 * np.pi / 5 / np.sqrt(phi + 2), + 2 * np.pi / 5 / np.sqrt(phi + 2), + 2 * np.pi * (phi - 1) / 3 / np.sqrt(3), + 2 * np.pi * (phi - 1) / 3 / np.sqrt(3), + 2 * np.pi / 5 / np.sqrt(phi + 2), + 2 * np.pi / 5 / np.sqrt(phi + 2), + 2 * np.pi * (phi - 1) / 3 / np.sqrt(3), + 2 * np.pi * (phi - 1) / 3 / np.sqrt(3), + 2 * np.pi * (phi - 1) / 3 / np.sqrt(3), + 2 * np.pi / 5 / np.sqrt(phi + 2), + 2 * np.pi * (phi - 1) / 3 / np.sqrt(3), + 2 * np.pi * (phi - 1) / 3 / np.sqrt(3), + 2 * np.pi * (phi - 1) / 3 / np.sqrt(3), + 2 * np.pi / 5 / np.sqrt(phi + 2), + 2 * np.pi / 5 / np.sqrt(phi + 2), + 2 * np.pi * (phi - 1) / 3 / np.sqrt(3), + 2 * np.pi / 5 / np.sqrt(phi + 2), + 2 * np.pi * (phi - 1) / 3 / np.sqrt(3), + 2 * np.pi * (phi - 1) / 3 / np.sqrt(3), + 2 * np.pi * (phi - 1) / 3 / np.sqrt(3), + 2 * np.pi / 5 / np.sqrt(phi + 2), + 2 * np.pi / 5 / np.sqrt(phi + 2), + 2 * np.pi * (phi - 1) / 3 / np.sqrt(3), + 2 * np.pi / 5 / np.sqrt(phi + 2), + 2 * np.pi * (phi - 1) / 3 / np.sqrt(3), + 2 * np.pi * (phi - 1) / 3 / np.sqrt(3), + 2 * np.pi * (phi - 1) / 3 / np.sqrt(3), + 2 * np.pi / 5 / np.sqrt(phi + 2), + 2 * np.pi / 5 / np.sqrt(phi + 2), + 2 * np.pi * (phi - 1) / 3 / np.sqrt(3), + 2 * np.pi / 5 / np.sqrt(phi + 2), + 2 * np.pi * (phi - 1) / 3 / np.sqrt(3), + 2 * np.pi * (phi - 1) / 3 / np.sqrt(3), + 2 * np.pi * (phi - 1) / 3 / np.sqrt(3), + 2 * np.pi / 5 / np.sqrt(phi + 2), + 2 * np.pi * (phi - 1) / 3 / np.sqrt(3), + 2 * np.pi / 5 / np.sqrt(phi + 2), + 2 * np.pi / 5 / np.sqrt(phi + 2), + 2 * np.pi / 5 / np.sqrt(phi + 2), + 2 * np.pi / 5 / np.sqrt(phi + 2), + 2 * np.pi / 5 / np.sqrt(phi + 2), + ] + ) + + _azimuthal_angles = np.array( + [ + np.pi, + 3 * np.pi / 2, + 3 * np.pi / 2, + 3 * np.pi / 2, + np.pi, + np.pi, + 3 * np.pi / 2, + 3 * np.pi / 2, + np.pi, + 3 * np.pi / 2, + 3 * np.pi / 2, + 3 * np.pi / 2, + 3 * np.pi / 2, + 3 * np.pi / 2, + np.pi, + np.pi, + 3 * np.pi / 2, + 3 * np.pi / 2, + 3 * np.pi / 2, + np.pi, + 3 * np.pi / 2, + np.pi, + np.pi, + np.pi, + 3 * np.pi / 2, + np.pi, + 3 * np.pi / 2, + 3 * np.pi / 2, + np.pi, + np.pi, + 3 * np.pi / 2, + 3 * np.pi / 2, + np.pi, + np.pi, + 3 * np.pi / 2, + np.pi, + np.pi, + 3 * np.pi / 2, + np.pi, + np.pi, + 3 * np.pi / 2, + np.pi, + np.pi, + np.pi, + 3 * np.pi / 2, + 3 * np.pi / 2, + 3 * np.pi / 2, + 3 * np.pi / 2, + np.pi, + 3 * np.pi / 2, + np.pi, + np.pi, + np.pi, + 3 * np.pi / 2, + 3 * np.pi / 2, + 3 * np.pi / 2, + np.pi, + 3 * np.pi / 2, + np.pi, + np.pi, + np.pi, + 3 * np.pi / 2, + 3 * np.pi / 2, + 3 * np.pi / 2, + np.pi, + 3 * np.pi / 2, + np.pi, + np.pi, + np.pi, + 3 * np.pi / 2, + 3 * np.pi / 2, + np.pi, + 3 * np.pi / 2, + np.pi, + np.pi, + 3 * np.pi / 2, + 3 * np.pi / 2, + np.pi, + np.pi, + 3 * np.pi / 2, + 3 * np.pi / 2, + np.pi, + np.pi, + 3 * np.pi / 2, + 3 * np.pi / 2, + np.pi, + np.pi, + np.pi, + 3 * np.pi / 2, + np.pi, + np.pi, + np.pi, + 3 * np.pi / 2, + 3 * np.pi / 2, + np.pi, + 3 * np.pi / 2, + np.pi, + np.pi, + np.pi, + 3 * np.pi / 2, + 3 * np.pi / 2, + np.pi, + 3 * np.pi / 2, + np.pi, + np.pi, + np.pi, + 3 * np.pi / 2, + 3 * np.pi / 2, + np.pi, + 3 * np.pi / 2, + np.pi, + np.pi, + np.pi, + 3 * np.pi / 2, + np.pi, + 3 * np.pi / 2, + 3 * np.pi / 2, + 3 * np.pi / 2, + 3 * np.pi / 2, + 3 * np.pi / 2, + ] + ) + + _detuning_rotations = np.array( + [ + 2 * np.pi * phi / 3 / np.sqrt(3), + 2 * np.pi * phi / 5 / np.sqrt(phi + 2), + 2 * np.pi * phi / 5 / np.sqrt(phi + 2), + 2 * np.pi * phi / 5 / np.sqrt(phi + 2), + 2 * np.pi * phi / 3 / np.sqrt(3), + 2 * np.pi * phi / 3 / np.sqrt(3), + 2 * np.pi * phi / 5 / np.sqrt(phi + 2), + 2 * np.pi * phi / 5 / np.sqrt(phi + 2), + 2 * np.pi * phi / 3 / np.sqrt(3), + 2 * np.pi * phi / 5 / np.sqrt(phi + 2), + 2 * np.pi * phi / 5 / np.sqrt(phi + 2), + 2 * np.pi * phi / 5 / np.sqrt(phi + 2), + 2 * np.pi * phi / 5 / np.sqrt(phi + 2), + 2 * np.pi * phi / 5 / np.sqrt(phi + 2), + 2 * np.pi * phi / 3 / np.sqrt(3), + 2 * np.pi * phi / 3 / np.sqrt(3), + 2 * np.pi * phi / 5 / np.sqrt(phi + 2), + 2 * np.pi * phi / 5 / np.sqrt(phi + 2), + 2 * np.pi * phi / 5 / np.sqrt(phi + 2), + 2 * np.pi * phi / 3 / np.sqrt(3), + 2 * np.pi * phi / 5 / np.sqrt(phi + 2), + 2 * np.pi * phi / 3 / np.sqrt(3), + 2 * np.pi * phi / 3 / np.sqrt(3), + 2 * np.pi * phi / 3 / np.sqrt(3), + 2 * np.pi * phi / 5 / np.sqrt(phi + 2), + 2 * np.pi * phi / 3 / np.sqrt(3), + 2 * np.pi * phi / 5 / np.sqrt(phi + 2), + 2 * np.pi * phi / 5 / np.sqrt(phi + 2), + 2 * np.pi * phi / 3 / np.sqrt(3), + 2 * np.pi * phi / 3 / np.sqrt(3), + 2 * np.pi * phi / 5 / np.sqrt(phi + 2), + 2 * np.pi * phi / 5 / np.sqrt(phi + 2), + 2 * np.pi * phi / 3 / np.sqrt(3), + 2 * np.pi * phi / 3 / np.sqrt(3), + 2 * np.pi * phi / 5 / np.sqrt(phi + 2), + 2 * np.pi * phi / 3 / np.sqrt(3), + 2 * np.pi * phi / 3 / np.sqrt(3), + 2 * np.pi * phi / 5 / np.sqrt(phi + 2), + 2 * np.pi * phi / 3 / np.sqrt(3), + 2 * np.pi * phi / 3 / np.sqrt(3), + 2 * np.pi * phi / 5 / np.sqrt(phi + 2), + 2 * np.pi * phi / 3 / np.sqrt(3), + 2 * np.pi * phi / 3 / np.sqrt(3), + 2 * np.pi * phi / 3 / np.sqrt(3), + 2 * np.pi * phi / 5 / np.sqrt(phi + 2), + 2 * np.pi * phi / 5 / np.sqrt(phi + 2), + 2 * np.pi * phi / 5 / np.sqrt(phi + 2), + 2 * np.pi * phi / 5 / np.sqrt(phi + 2), + 2 * np.pi * phi / 3 / np.sqrt(3), + 2 * np.pi * phi / 5 / np.sqrt(phi + 2), + 2 * np.pi * phi / 3 / np.sqrt(3), + 2 * np.pi * phi / 3 / np.sqrt(3), + 2 * np.pi * phi / 3 / np.sqrt(3), + 2 * np.pi * phi / 5 / np.sqrt(phi + 2), + 2 * np.pi * phi / 5 / np.sqrt(phi + 2), + 2 * np.pi * phi / 5 / np.sqrt(phi + 2), + 2 * np.pi * phi / 3 / np.sqrt(3), + 2 * np.pi * phi / 5 / np.sqrt(phi + 2), + 2 * np.pi * phi / 3 / np.sqrt(3), + 2 * np.pi * phi / 3 / np.sqrt(3), + 2 * np.pi * phi / 3 / np.sqrt(3), + 2 * np.pi * phi / 5 / np.sqrt(phi + 2), + 2 * np.pi * phi / 5 / np.sqrt(phi + 2), + 2 * np.pi * phi / 5 / np.sqrt(phi + 2), + 2 * np.pi * phi / 3 / np.sqrt(3), + 2 * np.pi * phi / 5 / np.sqrt(phi + 2), + 2 * np.pi * phi / 3 / np.sqrt(3), + 2 * np.pi * phi / 3 / np.sqrt(3), + 2 * np.pi * phi / 3 / np.sqrt(3), + 2 * np.pi * phi / 5 / np.sqrt(phi + 2), + 2 * np.pi * phi / 5 / np.sqrt(phi + 2), + 2 * np.pi * phi / 3 / np.sqrt(3), + 2 * np.pi * phi / 5 / np.sqrt(phi + 2), + 2 * np.pi * phi / 3 / np.sqrt(3), + 2 * np.pi * phi / 3 / np.sqrt(3), + 2 * np.pi * phi / 5 / np.sqrt(phi + 2), + 2 * np.pi * phi / 5 / np.sqrt(phi + 2), + 2 * np.pi * phi / 3 / np.sqrt(3), + 2 * np.pi * phi / 3 / np.sqrt(3), + 2 * np.pi * phi / 5 / np.sqrt(phi + 2), + 2 * np.pi * phi / 5 / np.sqrt(phi + 2), + 2 * np.pi * phi / 3 / np.sqrt(3), + 2 * np.pi * phi / 3 / np.sqrt(3), + 2 * np.pi * phi / 5 / np.sqrt(phi + 2), + 2 * np.pi * phi / 5 / np.sqrt(phi + 2), + 2 * np.pi * phi / 3 / np.sqrt(3), + 2 * np.pi * phi / 3 / np.sqrt(3), + 2 * np.pi * phi / 3 / np.sqrt(3), + 2 * np.pi * phi / 5 / np.sqrt(phi + 2), + 2 * np.pi * phi / 3 / np.sqrt(3), + 2 * np.pi * phi / 3 / np.sqrt(3), + 2 * np.pi * phi / 3 / np.sqrt(3), + 2 * np.pi * phi / 5 / np.sqrt(phi + 2), + 2 * np.pi * phi / 5 / np.sqrt(phi + 2), + 2 * np.pi * phi / 3 / np.sqrt(3), + 2 * np.pi * phi / 5 / np.sqrt(phi + 2), + 2 * np.pi * phi / 3 / np.sqrt(3), + 2 * np.pi * phi / 3 / np.sqrt(3), + 2 * np.pi * phi / 3 / np.sqrt(3), + 2 * np.pi * phi / 5 / np.sqrt(phi + 2), + 2 * np.pi * phi / 5 / np.sqrt(phi + 2), + 2 * np.pi * phi / 3 / np.sqrt(3), + 2 * np.pi * phi / 5 / np.sqrt(phi + 2), + 2 * np.pi * phi / 3 / np.sqrt(3), + 2 * np.pi * phi / 3 / np.sqrt(3), + 2 * np.pi * phi / 3 / np.sqrt(3), + 2 * np.pi * phi / 5 / np.sqrt(phi + 2), + 2 * np.pi * phi / 5 / np.sqrt(phi + 2), + 2 * np.pi * phi / 3 / np.sqrt(3), + 2 * np.pi * phi / 5 / np.sqrt(phi + 2), + 2 * np.pi * phi / 3 / np.sqrt(3), + 2 * np.pi * phi / 3 / np.sqrt(3), + 2 * np.pi * phi / 3 / np.sqrt(3), + 2 * np.pi * phi / 5 / np.sqrt(phi + 2), + 2 * np.pi * phi / 3 / np.sqrt(3), + 2 * np.pi * phi / 5 / np.sqrt(phi + 2), + 2 * np.pi * phi / 5 / np.sqrt(phi + 2), + 2 * np.pi * phi / 5 / np.sqrt(phi + 2), + 2 * np.pi * phi / 5 / np.sqrt(phi + 2), + 2 * np.pi * phi / 5 / np.sqrt(phi + 2), + ] + ) + + assert np.allclose(_offsets, sequence.offsets) + assert np.allclose(_rabi_rotations, sequence.rabi_rotations) + assert np.allclose(_azimuthal_angles, sequence.azimuthal_angles) + assert np.allclose(_detuning_rotations, sequence.detuning_rotations) + + sequence = new_platonic_sequence( + duration=duration, sequence="Icosahedral", pre_post_rotation=True + ) + + _offsets = np.insert(_offsets, [0, _offsets.shape[0]], [0, duration]) + _rabi_rotations = np.insert( + _rabi_rotations, [0, _rabi_rotations.shape[0]], [np.pi / 2, np.pi / 2] + ) + _azimuthal_angles = np.insert( + _azimuthal_angles, [0, _azimuthal_angles.shape[0]], [0, np.pi] + ) + _detuning_rotations = np.insert( + _detuning_rotations, [0, _detuning_rotations.shape[0]], [0, 0] + ) + + assert np.allclose(_offsets, sequence.offsets) + assert np.allclose(_rabi_rotations, sequence.rabi_rotations) + assert np.allclose(_azimuthal_angles, sequence.azimuthal_angles) + assert np.allclose(_detuning_rotations, sequence.detuning_rotations) From 0effe70d081b8b765faedc9fe7ef8e4cae633434 Mon Sep 17 00:00:00 2001 From: Hector Brown Date: Wed, 1 Apr 2026 16:46:39 +1100 Subject: [PATCH 4/4] change platonic udd sequence generator to if-else chain + use numpy to generate --- .../predefined.py | 107 ++++++++---------- 1 file changed, 46 insertions(+), 61 deletions(-) diff --git a/qctrlopencontrols/dynamic_decoupling_sequences/predefined.py b/qctrlopencontrols/dynamic_decoupling_sequences/predefined.py index 20d8ce8..f8005b4 100644 --- a/qctrlopencontrols/dynamic_decoupling_sequences/predefined.py +++ b/qctrlopencontrols/dynamic_decoupling_sequences/predefined.py @@ -1318,69 +1318,54 @@ def new_platonic_sequence( {"sequence": sequence}, ) - # The sequences outlined in the cited paper, each sequence is constructed - # from an Eulerian path on the Cayley graph associated with the relevant - # point group. Each sequence is constructed of 2 generating operations, in - # the order specified here. - # fmt: off - eulerian_paths = { - "Dihedral": [0, 1, 0, 1, 1, 0, 1, 0], - "Tetrahedral": [ 0, 1, 0, 0, 1, 0, 1, 1, 1, 0, 0, 1, 0, 1, 1, 1, 0, 0, 1, 0, 1, 1, 0, 0], - "Octahedral": [ 0, 1, 0, 0, 0, 1, 1, 1, 0, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1, 1 ], - "Icosahedral": [ 1, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 0, 1, 1, 1, 0, 0, 1, 0, 1, 1, 1, 0, 0, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0 ] - } - # fmt: on - - # The generators assocaited with each point group. - phi = (np.sqrt(5) + 1) / 2 # golden ratio - generators = { - "Dihedral": [ - {"Rabi": np.pi, "Azimuthal": 0, "Detuning": 0}, # rabi, azimuthal, detuning - {"Rabi": np.pi, "Azimuthal": np.pi / 2, "Detuning": 0}, - ], - "Tetrahedral": [ - {"Rabi": 0, "Azimuthal": 0, "Detuning": 2 * np.pi / 3}, - { - "Rabi": 4 * np.sqrt(2) * np.pi / 9, - "Azimuthal": np.pi / 3, - "Detuning": 2 * np.pi / 9, - }, - ], - "Octahedral": [ - {"Rabi": 0, "Azimuthal": 0, "Detuning": np.pi / 2}, - { - "Rabi": 2 * np.sqrt(2 / 3) * np.pi / 3, - "Azimuthal": np.pi / 4, - "Detuning": 2 * np.pi / 3 / np.sqrt(3), - }, - ], - "Icosahedral": [ - { - "Rabi": 2 * np.pi / 5 / np.sqrt(phi + 2), - "Azimuthal": 3 * np.pi / 2, - "Detuning": 2 * np.pi * phi / 5 / np.sqrt(phi + 2), - }, - { - "Rabi": 2 * np.pi * (phi - 1) / 3 / np.sqrt(3), - "Azimuthal": np.pi, - "Detuning": 2 * np.pi * phi / 3 / np.sqrt(3), - }, - ], - } + rabi_rotations, azimuthal_angles, detuning_rotations = None, None, None + + if sequence == "Dihedral": + eulerian_path = np.array([0, 1, 0, 1, 1, 0, 1, 0]) + + rabi_rotations = np.ones(eulerian_path.shape[0]) * np.pi + azimuthal_angles = eulerian_path * np.pi / 2 + detuning_rotations = np.zeros(eulerian_path.shape[0]) + elif sequence == "Tetrahedral": + eulerian_path = np.array( + [0, 1, 0, 0, 1, 0, 1, 1, 1, 0, 0, 1, 0, 1, 1, 1, 0, 0, 1, 0, 1, 1, 0, 0] + ) + + rabi_rotations = eulerian_path * 4 * np.sqrt(2) * np.pi / 9 + azimuthal_angles = eulerian_path * np.pi / 3 + detuning_rotations = ( + 1 ^ eulerian_path + ) * 2 * np.pi / 3 + eulerian_path * 2 * np.pi / 9 + elif sequence == "Octahedral": + # fmt:off + eulerian_path = np.array( + [ 0, 1, 0, 0, 0, 1, 1, 1, 0, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1, 1 ] + ) + # fmt:on + + rabi_rotations = eulerian_path * 2 * np.sqrt(2 / 3) * np.pi / 3 + azimuthal_angles = eulerian_path * np.pi / 4 + detuning_rotations = ( + 1 ^ eulerian_path + ) * np.pi / 2 + eulerian_path * 2 * np.pi / 3 / np.sqrt(3) + else: + # fmt:off + eulerian_path = np.array( + [ 1, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 0, 1, 1, 1, 0, 0, 1, 0, 1, 1, 1, 0, 0, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0 ] + ) + # fmt:on + + phi = (np.sqrt(5) + 1) / 2 # golden ratio + rabi_rotations = (1 ^ eulerian_path) * 2 * np.pi / 5 / np.sqrt( + phi + 2 + ) + eulerian_path * 2 * np.pi * (phi - 1) / 3 / np.sqrt(3) + azimuthal_angles = (1 ^ eulerian_path) * 3 * np.pi / 2 + eulerian_path * np.pi + detuning_rotations = (1 ^ eulerian_path) * 2 * np.pi * phi / 5 / np.sqrt( + phi + 2 + ) + eulerian_path * 2 * np.pi * phi / 3 / np.sqrt(3) # Re-use the CPMG offset function to obtain equally spaced pulses along a certain duration. - offsets = _carr_purcell_meiboom_gill_offsets( - duration, len(eulerian_paths[sequence]) - ) - rabi_rotations = np.array( - [generators[sequence][idx]["Rabi"] for idx in eulerian_paths[sequence]] - ) - azimuthal_angles = np.array( - [generators[sequence][idx]["Azimuthal"] for idx in eulerian_paths[sequence]] - ) - detuning_rotations = np.array( - [generators[sequence][idx]["Detuning"] for idx in eulerian_paths[sequence]] - ) + offsets = _carr_purcell_meiboom_gill_offsets(duration, rabi_rotations.shape[0]) if pre_post_rotation: # Use a pi/2 followed by a -pi/2 X rotation as all the sequences