From a6f90205ae9a675c0bc557a6761844c40bdb0846 Mon Sep 17 00:00:00 2001 From: William C <2899523@gmail.com> Date: Wed, 5 Aug 2026 06:20:27 +0000 Subject: [PATCH 1/3] feat: attempt an implementation of mixed magnitude approximation --- qualtran/rotation_synthesis/__init__.py | 1 + .../rotation_synthesis/protocols/__init__.py | 1 + .../protocols/_clifford_t_synthesis.py | 134 ++++++++++++++++++ 3 files changed, 136 insertions(+) diff --git a/qualtran/rotation_synthesis/__init__.py b/qualtran/rotation_synthesis/__init__.py index 9a8b46f674..089a1da158 100644 --- a/qualtran/rotation_synthesis/__init__.py +++ b/qualtran/rotation_synthesis/__init__.py @@ -20,4 +20,5 @@ magnitude_approx, mixed_diagonal_protocol, mixed_fallback_protocol, + mixed_magnitude_approx, ) diff --git a/qualtran/rotation_synthesis/protocols/__init__.py b/qualtran/rotation_synthesis/protocols/__init__.py index a869319efb..d7121a660c 100644 --- a/qualtran/rotation_synthesis/protocols/__init__.py +++ b/qualtran/rotation_synthesis/protocols/__init__.py @@ -18,6 +18,7 @@ magnitude_approx, mixed_diagonal_protocol, mixed_fallback_protocol, + mixed_magnitude_approx, ) from qualtran.rotation_synthesis.protocols._diagonal import Diagonal from qualtran.rotation_synthesis.protocols._fallback import Fallback diff --git a/qualtran/rotation_synthesis/protocols/_clifford_t_synthesis.py b/qualtran/rotation_synthesis/protocols/_clifford_t_synthesis.py index 22a9071e3d..95dcdf594c 100644 --- a/qualtran/rotation_synthesis/protocols/_clifford_t_synthesis.py +++ b/qualtran/rotation_synthesis/protocols/_clifford_t_synthesis.py @@ -512,3 +512,137 @@ def magnitude_approx( return channels.UnitaryChannel.from_unitaries( rz1_approx, _su2_ct.HSqrt2, rx_approx, _su2_ct.HSqrt2.adjoint(), rz2_approx ) + + +def mixed_magnitude_approx( + unitary: np.ndarray, + eps: rst.Real, + max_n: int, + config: mc.MathConfig, + relative_norm_solver: relative_norm.CliffordTRelativeNormSolver = _DEFAULT_RELATIVE_NORM_SOLVER, + verbose: bool = False, +): + """Approximates a unitary using the mixed magnitude approximation protocol. + + This protocol does the following: + * Like magnitude approximation, reduces a unitary to a series of rotations ZXZ. + * Approximates X under- and over-rotations by producing Z rotations via mixed diagonal + approximation of equal rotational degree, and rotates it by applying an H channel. + * Based on the X approximations, approximates the two Z rotations. + * Computes a probability of each produced gate sequence and returns the probability + channel. + + Args: + unitary: the target unitary, this can be 2x2 numpy array of mpmath.mpc objects. + eps: Target error. + max_n: Maximum number of T gates to check. + config: A math config. + relative_norm_solver: The relative norm solver to use. + verbose: whether to print debug statements or not. + Returns: + A ProbabilisticChannel or None. + + References: + [Shorter quantum circuits via single-qubit gate approximation](https://arxiv.org/abs/2203.10064) + section 3.5 + """ + # From Proposition 3.21, this algorithm produces an estimation that is a + # $3\epsilon$-approximation to the target unitary. + eps = config.number(eps) / 3 + + alpha, theta, beta = matrix.su_unitary_to_zxz_angles( + unitary, + config, + ) + + rz_prob_approx = protocols.mixed_diagonal_protocol( + theta=-theta/2, + eps=eps, + max_n=max_n, + config=config, + verbose=verbose, + ) + if rz_prob_approx is None: + return None + + rz_under_rotation = rz_prob_approx.c2.to_matrix() + rz_over_rotation = rz_prob_approx.c1.to_matrix() + rx_under_rotation = (su2.HSqrt2 @ rz_under_rotation @ su2.HSqrt2.adjoint()).numpy(config) + rx_over_rotation = (su2.HSqrt2 @ rz_over_rotation @ su2.HSqrt2.adjoint()).numpy(config) + + # Probabilities produced by `mixed_diagonal_protocol` (Theorem 3.12) differ from what is + # calculated via mixed magnitue approximation (Proposition 3.21), so we need to recalculate + # them. Furthermore, rotating the under and over Z-approximations from mixed diagonalization + # to become X-approximations can add error proportional to Im(|rz[1, 0]|) ocasionally producing + # two under-rotations (and thus a negative probability value). + delta_under = config.arccos(abs(rx_under_rotation[0, 0])) + (-theta / 2) + delta_over = config.arccos(abs(rx_over_rotation[0, 0])) + (-theta / 2) + p = ( + config.sin(2 * delta_over) / ( + config.sin(2 * delta_over) - config.sin(2 * delta_under) + ) + ) + if p < 0: + return None + + zxz_under_rotation = matrix.su_unitary_to_zxz_angles( + rx_under_rotation, + config, + ) + zxz_over_rotation = matrix.su_unitary_to_zxz_angles( + rx_over_rotation, + config, + ) + + z_under_rotations = ( + protocols.diagonal_unitary_approx( + theta=-(alpha - zxz_under_rotation[0]) / 2, + eps=eps, + max_n=max_n, + config=config, + ), + protocols.diagonal_unitary_approx( + theta=-(beta - zxz_under_rotation[2]) / 2, + eps=eps, + max_n=max_n, + config=config, + ), + ) + + z_over_rotations = ( + protocols.diagonal_unitary_approx( + theta=-(alpha - zxz_over_rotation[0]) / 2, + eps=eps, + max_n=max_n, + config=config, + verbose=verbose, + ), + protocols.diagonal_unitary_approx( + theta=-(beta - zxz_over_rotation[2]) / 2, + eps=eps, + max_n=max_n, + config=config, + verbose=verbose, + ), + ) + + if None in [*z_under_rotations, *z_over_rotations]: + return None + + return rs.channels.ProbabilisticChannel( + c1=rs.channels.UnitaryChannel.from_unitaries( + z_under_rotations[0].to_matrix(), + su2.HSqrt2, + rz_under_rotation, + su2.HSqrt2.adjoint(), + z_under_rotations[1].to_matrix(), + ), + c2=rs.channels.UnitaryChannel.from_unitaries( + z_over_rotations[0].to_matrix(), + su2.HSqrt2, + rz_over_rotation, + su2.HSqrt2.adjoint(), + z_over_rotations[1].to_matrix(), + ), + probability=p, + ) From 85d7102bd18c90df7984e08f4a87c7edbe64961c Mon Sep 17 00:00:00 2001 From: William C <2899523@gmail.com> Date: Thu, 6 Aug 2026 02:10:14 +0000 Subject: [PATCH 2/3] feat: update mixed magnitude approximation with module imports --- .../protocols/_clifford_t_synthesis.py | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/qualtran/rotation_synthesis/protocols/_clifford_t_synthesis.py b/qualtran/rotation_synthesis/protocols/_clifford_t_synthesis.py index 95dcdf594c..57a9a79796 100644 --- a/qualtran/rotation_synthesis/protocols/_clifford_t_synthesis.py +++ b/qualtran/rotation_synthesis/protocols/_clifford_t_synthesis.py @@ -550,12 +550,12 @@ def mixed_magnitude_approx( # $3\epsilon$-approximation to the target unitary. eps = config.number(eps) / 3 - alpha, theta, beta = matrix.su_unitary_to_zxz_angles( + alpha, theta, beta = rsad.su_unitary_to_zxz_angles( unitary, config, ) - rz_prob_approx = protocols.mixed_diagonal_protocol( + rz_prob_approx = mixed_diagonal_protocol( theta=-theta/2, eps=eps, max_n=max_n, @@ -567,8 +567,8 @@ def mixed_magnitude_approx( rz_under_rotation = rz_prob_approx.c2.to_matrix() rz_over_rotation = rz_prob_approx.c1.to_matrix() - rx_under_rotation = (su2.HSqrt2 @ rz_under_rotation @ su2.HSqrt2.adjoint()).numpy(config) - rx_over_rotation = (su2.HSqrt2 @ rz_over_rotation @ su2.HSqrt2.adjoint()).numpy(config) + rx_under_rotation = (_su2_ct.HSqrt2 @ rz_under_rotation @ _su2_ct.HSqrt2.adjoint()).numpy(config) + rx_over_rotation = (_su2_ct.HSqrt2 @ rz_over_rotation @ _su2_ct.HSqrt2.adjoint()).numpy(config) # Probabilities produced by `mixed_diagonal_protocol` (Theorem 3.12) differ from what is # calculated via mixed magnitue approximation (Proposition 3.21), so we need to recalculate @@ -585,23 +585,23 @@ def mixed_magnitude_approx( if p < 0: return None - zxz_under_rotation = matrix.su_unitary_to_zxz_angles( + zxz_under_rotation = rsad.su_unitary_to_zxz_angles( rx_under_rotation, config, ) - zxz_over_rotation = matrix.su_unitary_to_zxz_angles( + zxz_over_rotation = rsad.su_unitary_to_zxz_angles( rx_over_rotation, config, ) z_under_rotations = ( - protocols.diagonal_unitary_approx( + diagonal_unitary_approx( theta=-(alpha - zxz_under_rotation[0]) / 2, eps=eps, max_n=max_n, config=config, ), - protocols.diagonal_unitary_approx( + diagonal_unitary_approx( theta=-(beta - zxz_under_rotation[2]) / 2, eps=eps, max_n=max_n, @@ -610,14 +610,14 @@ def mixed_magnitude_approx( ) z_over_rotations = ( - protocols.diagonal_unitary_approx( + diagonal_unitary_approx( theta=-(alpha - zxz_over_rotation[0]) / 2, eps=eps, max_n=max_n, config=config, verbose=verbose, ), - protocols.diagonal_unitary_approx( + diagonal_unitary_approx( theta=-(beta - zxz_over_rotation[2]) / 2, eps=eps, max_n=max_n, @@ -629,20 +629,20 @@ def mixed_magnitude_approx( if None in [*z_under_rotations, *z_over_rotations]: return None - return rs.channels.ProbabilisticChannel( - c1=rs.channels.UnitaryChannel.from_unitaries( + return channels.ProbabilisticChannel( + c1=channels.UnitaryChannel.from_unitaries( z_under_rotations[0].to_matrix(), - su2.HSqrt2, + _su2_ct.HSqrt2, rz_under_rotation, - su2.HSqrt2.adjoint(), + _su2_ct.HSqrt2.adjoint(), z_under_rotations[1].to_matrix(), ), - c2=rs.channels.UnitaryChannel.from_unitaries( + c2=channels.UnitaryChannel.from_unitaries( z_over_rotations[0].to_matrix(), - su2.HSqrt2, + _su2_ct.HSqrt2, rz_over_rotation, - su2.HSqrt2.adjoint(), + _su2_ct.HSqrt2.adjoint(), z_over_rotations[1].to_matrix(), ), probability=p, - ) + ) \ No newline at end of file From ceae3ee0694cd2cb421607d50bcf17f1910cf5ed Mon Sep 17 00:00:00 2001 From: William C <2899523@gmail.com> Date: Thu, 6 Aug 2026 03:31:44 +0000 Subject: [PATCH 3/3] lint: format --- .../protocols/_clifford_t_synthesis.py | 45 +++++-------------- 1 file changed, 12 insertions(+), 33 deletions(-) diff --git a/qualtran/rotation_synthesis/protocols/_clifford_t_synthesis.py b/qualtran/rotation_synthesis/protocols/_clifford_t_synthesis.py index 57a9a79796..0ef092a564 100644 --- a/qualtran/rotation_synthesis/protocols/_clifford_t_synthesis.py +++ b/qualtran/rotation_synthesis/protocols/_clifford_t_synthesis.py @@ -531,7 +531,7 @@ def mixed_magnitude_approx( * Based on the X approximations, approximates the two Z rotations. * Computes a probability of each produced gate sequence and returns the probability channel. - + Args: unitary: the target unitary, this can be 2x2 numpy array of mpmath.mpc objects. eps: Target error. @@ -550,24 +550,19 @@ def mixed_magnitude_approx( # $3\epsilon$-approximation to the target unitary. eps = config.number(eps) / 3 - alpha, theta, beta = rsad.su_unitary_to_zxz_angles( - unitary, - config, - ) + alpha, theta, beta = rsad.su_unitary_to_zxz_angles(unitary, config) rz_prob_approx = mixed_diagonal_protocol( - theta=-theta/2, - eps=eps, - max_n=max_n, - config=config, - verbose=verbose, + theta=-theta / 2, eps=eps, max_n=max_n, config=config, verbose=verbose ) if rz_prob_approx is None: return None rz_under_rotation = rz_prob_approx.c2.to_matrix() rz_over_rotation = rz_prob_approx.c1.to_matrix() - rx_under_rotation = (_su2_ct.HSqrt2 @ rz_under_rotation @ _su2_ct.HSqrt2.adjoint()).numpy(config) + rx_under_rotation = (_su2_ct.HSqrt2 @ rz_under_rotation @ _su2_ct.HSqrt2.adjoint()).numpy( + config + ) rx_over_rotation = (_su2_ct.HSqrt2 @ rz_over_rotation @ _su2_ct.HSqrt2.adjoint()).numpy(config) # Probabilities produced by `mixed_diagonal_protocol` (Theorem 3.12) differ from what is @@ -577,35 +572,19 @@ def mixed_magnitude_approx( # two under-rotations (and thus a negative probability value). delta_under = config.arccos(abs(rx_under_rotation[0, 0])) + (-theta / 2) delta_over = config.arccos(abs(rx_over_rotation[0, 0])) + (-theta / 2) - p = ( - config.sin(2 * delta_over) / ( - config.sin(2 * delta_over) - config.sin(2 * delta_under) - ) - ) + p = config.sin(2 * delta_over) / (config.sin(2 * delta_over) - config.sin(2 * delta_under)) if p < 0: return None - zxz_under_rotation = rsad.su_unitary_to_zxz_angles( - rx_under_rotation, - config, - ) - zxz_over_rotation = rsad.su_unitary_to_zxz_angles( - rx_over_rotation, - config, - ) + zxz_under_rotation = rsad.su_unitary_to_zxz_angles(rx_under_rotation, config) + zxz_over_rotation = rsad.su_unitary_to_zxz_angles(rx_over_rotation, config) z_under_rotations = ( diagonal_unitary_approx( - theta=-(alpha - zxz_under_rotation[0]) / 2, - eps=eps, - max_n=max_n, - config=config, + theta=-(alpha - zxz_under_rotation[0]) / 2, eps=eps, max_n=max_n, config=config ), diagonal_unitary_approx( - theta=-(beta - zxz_under_rotation[2]) / 2, - eps=eps, - max_n=max_n, - config=config, + theta=-(beta - zxz_under_rotation[2]) / 2, eps=eps, max_n=max_n, config=config ), ) @@ -645,4 +624,4 @@ def mixed_magnitude_approx( z_over_rotations[1].to_matrix(), ), probability=p, - ) \ No newline at end of file + )