Source code for qrisp.alg_primitives.arithmetic.arithmetic_tools

"""
********************************************************************************
* Copyright (c) 2025 the Qrisp authors
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the Eclipse
* Public License, v. 2.0 are satisfied: GNU General Public License, version 2
* with the GNU Classpath Exception which is
* available at https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
********************************************************************************
"""

from qrisp.core import Qubit
from qrisp.qtypes import QuantumFloat, QuantumBool
from qrisp.environments import control, invert, conjugate
from qrisp.core.gate_application_functions import x, cx, mcx
from qrisp.jasp import jrange, jnp
from typing import Tuple


[docs] def q_max(a: QuantumFloat, b: QuantumFloat) -> QuantumFloat: """ Computes the maximum of two QuantumFloats ``a`` and ``b``. Parameters ---------- a : QuantumFloat b : QuantumFloat Returns ------- QuantumFloat The maximum value between ``a`` and ``b``. Examples -------- >>> from qrisp import * >>> a = QuantumFloat(2) >>> b = QuantumFloat(2) >>> a += 2 >>> h(a[0]) >>> b+=1 >>> res_max = q_max(a,b) >>> multi_measurement([a,b,res_max]) {(2, 1, 2): 0.5, (3, 1, 3): 0.5} """ res_mshape = [ jnp.minimum(a.mshape[0], b.mshape[0]), jnp.maximum(a.mshape[1], b.mshape[1]), ] new_msize = res_mshape[1] - res_mshape[0] new_exponent = res_mshape[0] res = QuantumFloat(new_msize, new_exponent, signed=a.signed | b.signed) # We calculate the overlap of bits with the same significance of a and b. l = jnp.maximum(a.mshape[0], b.mshape[0]) r = jnp.minimum(a.mshape[1], b.mshape[1]) c = QuantumBool() compare_func = lambda x, y: x >= y injecteted_comp_func = c << compare_func with conjugate(injecteted_comp_func)(a, b): cx(a[l - a.exponent : r - a.exponent], res[l - new_exponent : r - new_exponent]) with conjugate(cx)( a[l - a.exponent : r - a.exponent], b[l - b.exponent : r - b.exponent] ): with control(c, 0): cx( b[l - b.exponent : r - b.exponent], res[l - new_exponent : r - new_exponent], ) with control(c): up_bd = jnp.minimum(a.size,l - a.exponent) cx(a[:up_bd], res[:up_bd]) lw_bd = jnp.maximum(r, a.exponent) cx(a[lw_bd - a.exponent : a.msize], res[lw_bd - new_exponent : a.mshape[1] - new_exponent]) if a.signed: cx(a.sign(), res.sign()) with control(c, 0): up_bd = jnp.minimum(b.size,l - b.exponent) cx(b[: up_bd], res[: up_bd]) lw_bd = jnp.maximum(r, b.exponent) cx(b[lw_bd - b.exponent : b.msize], res[lw_bd - new_exponent : b.mshape[1] - new_exponent]) if b.signed: cx(b.sign(), res.sign()) c.delete() return res
[docs] def q_min(a: QuantumFloat, b: QuantumFloat) -> QuantumFloat: """ Computes the minimum of two QuantumFloats ``a`` and ``b``. Parameters ---------- a : QuantumFloat b : QuantumFloat Returns ------- QuantumFloat The minimum value between ``a`` and ``b``. Examples -------- >>> from qrisp import * >>> a = QuantumFloat(2) >>> b = QuantumFloat(2) >>> a += 2 >>> h(a[0]) >>> b+=1 >>> res_min = q_min(a,b) >>> multi_measurement([a,b,res_min]) {(2, 1, 1): 0.5, (3, 1, 1): 0.5} """ res_mshape = [ jnp.minimum(a.mshape[0], b.mshape[0]), jnp.maximum(a.mshape[1], b.mshape[1]), ] new_msize = res_mshape[1] - res_mshape[0] new_exponent = res_mshape[0] res = QuantumFloat(new_msize, new_exponent, signed=a.signed | b.signed) # We calculate the overlap of bits with the same significance of a and b. l = jnp.maximum(a.mshape[0], b.mshape[0]) r = jnp.minimum(a.mshape[1], b.mshape[1]) c = QuantumBool() compare_func = lambda x, y: x <= y injecteted_comp_func = c << compare_func with conjugate(injecteted_comp_func)(a, b): cx(a[l - a.exponent : r - a.exponent], res[l - new_exponent : r - new_exponent]) with conjugate(cx)( a[l - a.exponent : r - a.exponent], b[l - b.exponent : r - b.exponent] ): with control(c, 0): cx( b[l - b.exponent : r - b.exponent], res[l - new_exponent : r - new_exponent], ) with control(c): up_bd = jnp.minimum(a.size,l - a.exponent) cx(a[:up_bd], res[:up_bd]) lw_bd = jnp.maximum(r, a.exponent) cx(a[lw_bd - a.exponent : a.msize], res[lw_bd - new_exponent : a.mshape[1] - new_exponent]) if a.signed: cx(a.sign(), res.sign()) with control(c, 0): up_bd = jnp.minimum(b.size,l - b.exponent) cx(b[: up_bd], res[: up_bd]) lw_bd = jnp.maximum(r, b.exponent) cx(b[lw_bd - b.exponent : b.msize], res[lw_bd - new_exponent : b.mshape[1] - new_exponent]) if b.signed: cx(b.sign(), res.sign()) c.delete() return res
[docs] def q_floor(a: QuantumFloat) -> QuantumFloat: """ Computes out-of-place the floor of a QuantumFloat. Parameters ---------- a : QuantumFloat Returns ------- QuantumFloat The floor of ``a``. Examples -------- >>> from qrisp import * >>> a = QuantumFloat(4,-2) >>> a[:] = {0.25: 0.25**0.5, 1.75: 0.75**0.5} >>> b = q_floor(a) >>> b.get_measurement() {1.0: 0.75, 0.0: 0.25} """ b = a.duplicate() for i in jrange(-a.exponent, a.size): cx(a[i], b[i]) return b
[docs] def q_ceil(a: QuantumFloat) -> QuantumFloat: """ Computes out-of-place the ceiling of a QuantumFloat. Parameters ---------- a : QuantumFloat Returns ------- QuantumFloat The ceiling of ``a``. Examples -------- >>> from qrisp import * >>> a = QuantumFloat(4,-2) >>> a[:] = {0.25: 0.25**0.5, 1.75: 0.75**0.5} >>> b = q_ceil(a) >>> b.get_measurement() {2.0: 0.75, 1.0: 0.25} .. warning:: Ceiling operations that would result in overflow, raise no errors. Instead, the operations are performed `modular <https://en.wikipedia.org/wiki/Modular_arithmetic>`_. """ b = q_floor(a) with control(a.exponent < 0): flag = QuantumFloat(1) # Logical OR on the fractional part logical_OR(a[:-a.exponent], flag[0]) with control(flag[0]): b += 1 logical_OR(a[:-a.exponent], flag[0]) flag.delete() return b
def logical_OR(operands: list[Qubit], flag: Qubit): # handle the case fo an empty list # Implement the case of a Quantum Variable with more the 2 qubits x(operands) x(flag) mcx(operands, flag) x(operands)
[docs] def q_round(a: QuantumFloat) -> QuantumFloat: """ Computes out-of-place the rounding of a QuantumFloat to the first significant digit. Parameters ---------- a : QuantumFloat Returns ------- QuantumFloat The rounding of ``a``. Examples -------- >>> from qrisp import * >>> a = QuantumFloat(4,-2) >>> a[:] = {0.25: 0.25**0.5, 1.75: 0.75**0.5} >>> b = q_round(a) >>> b.get_measurement() {2.0: 0.75, 0.0: 0.25} .. warning:: Rounding operations that would result in overflow, raise no errors. Instead, the operations are performed `modular <https://en.wikipedia.org/wiki/Modular_arithmetic>`_. """ b = q_floor(a) with control(a.exponent < 0): with control(a[-a.exponent - 1]): #Control on the first fractional bit (the one holding the value b*0.5) #specifies if the number is rounded up or down. b += 1 return b
[docs] def q_fractional(a: QuantumFloat) -> QuantumFloat: """ Computes out-of-place the fractional part of a QuantumFloat. Parameters ---------- a : QuantumFloat Returns ------- QuantumFloat The fractional part of ``a``. Examples -------- >>> from qrisp import * >>> a = QuantumFloat(4,-2) >>> a[:] = {0.25: 0.25**0.5, 1.75: 0.75**0.5} >>> b = q_fractional(a) >>> b.get_measurement() {0.75: 0.75, 0.25: 0.25} """ b = a.duplicate() for i in jrange(a.size): cx(a[i], b[i]) with control(a.signed): flag = QuantumFloat(1) cx(a[-1], flag) with control(flag): #If the QuantumFloat is signed and the value negative: substract the ceiling c = q_ceil(a) b -= c injected_qceil = c << q_ceil with invert(): injected_qceil(a) c.delete() with control(flag, 0): #If the QuantumFloat is signed and the value positive: substract the floor c = q_floor(a) b -= c injected_qfloor = c << q_floor with invert(): injected_qfloor(a) c.delete() cx(a[-1], flag) flag.delete() with control(not a.signed): #If the QuantumFloat is unsigned: substract the floor c = q_floor(a) b -= c injected_qfloor = c << q_floor with invert(): injected_qfloor(a) c.delete() return b
[docs] def q_modf(a: QuantumFloat) -> Tuple[QuantumFloat, QuantumFloat]: """ Computes out-of-place the integer and fractional parts of a QuantumFloat. Parameters ---------- a : QuantumFloat Returns ------- Tuple[QuantumFloat, QuantumFloat] A pair `(i, f)` where: - `i =` :func:`q_floor(a) <qrisp.q_floor>` is the integer part - `f =` :func:`q_fractional(a) <qrisp.q_fractional>` is the fractional part Both are new QuantumFloats with the same configuration as ``a``. Examples -------- >>> from qrisp import * >>> a = QuantumFloat(4, -2) >>> a[:] = {0.25: 0.25**0.5, 1.75: 0.75**0.5} >>> i, f = qmodf(a) >>> i.get_measurement() {1.0: 0.75, 0.0: 0.25} >>> f.get_measurement() {0.75: 0.75, 0.25: 0.25} """ return (q_floor(a), q_fractional(a))