Source code for qrisp.qtypes.quantum_float

"""
\********************************************************************************
* Copyright (c) 2023 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
********************************************************************************/
"""


import numpy as np
import sympy as sp

# from qrisp.arithmetic import (
#     eq,
#     geq,
#     gt,
#     inpl_mult,
#     leq,
#     lt,
#     neq,
#     polynomial_encoder,
#     q_mult,
#     sbp_add,
#     quantum_bit_shift,
# )
from qrisp.core import QuantumVariable
from qrisp.misc import gate_wrap


def signed_int_iso(x, n):
    if int(x) < -(2**n) or int(x) >= 2**n:
        raise Exception("Applying signed integer isomorphism resulted in overflow")

    if x >= 0:
        return x % 2**n
    else:
        return -abs(x) % 2 ** (n + 1)


def signed_int_iso_inv(y, n):
    y = y % 2 ** (n + 1)
    if y < 2**n:
        return y
    else:
        return -(2 ** (n + 1)) + y


# Truncates a polynomial of the form p(x) = 2**k_0*x*i_0 + 2**k_1*x**i_1 ...
# where every summand where the power of the coefficients does not lie in the interval
# trunc bounds is removed
def trunc_poly(poly, trunc_bounds):
    # Convert to sympy polynomial
    poly = sp.poly(poly)

    # Clip upper bound
    poly = poly.trunc(2.0 ** (trunc_bounds[1]))

    # Clip lower bound
    poly = poly / 2.0 ** trunc_bounds[0]
    poly = poly - sp.poly(poly).trunc(1)
    poly = poly * 2.0 ** trunc_bounds[0]

    return poly.expr.expand()


[docs]class QuantumFloat(QuantumVariable): r""" This subclass of :ref:`QuantumVariable` can represent floating point numbers (signed and unsigned) up to an arbitrary precision. The technical details of the employed arithmetic can be found in this `article <https://ieeexplore.ieee.org/document/9815035>`_. To create a QuantumFloat we call the constructor: >>> from qrisp import QuantumFloat >>> a = QuantumFloat(3, -1, signed = False) Here, the 3 indicates the amount of mantissa qubits and the -1 indicates the exponent. For unsigned QuantumFloats, the decoder function is given by .. math:: f_{k}(i) = i2^{k} Where $k$ is the exponent. We can check which values can be represented: >>> for i in range(2**a.size): print(a.decoder(i)) 0.0 0.5 1.0 1.5 2.0 2.5 3.0 3.5 We see $2^3 = 8$ values, because we have 3 mantissa qubits. The exponent is -1, implying the precision is $0.5 = 2^{-1}$. For signed QuantumFloats, the decoder function is .. math:: f_{k}^{n}(i) = \begin{cases} i2^{k} & \text{if } i < 2^n \\ (i - 2^{n+1})2^k & \text{else} \end{cases} Where $k$ is again, the exponent and $n$ is the mantissa size. Another example: >>> b = QuantumFloat(2, -2, signed = True) >>> for i in range(2**b.size): print(b.decoder(i)) 0.0 0.25 0.5 0.75 -1.0 -0.75 -0.5 -0.25 Here, we have $2^2 = 4$ values and their signed equivalents. Their precision is $0.25 = 2^{-2}$. **Arithmetic** Many operations known from classical arithmetic work for QuantumFloats in infix notation. Addition: >>> a[:] = 1.5 >>> b[:] = 0.25 >>> c = a + b >>> print(c) {1.75: 1.0} Subtraction: >>> d = a - c >>> print(d) {-0.25: 1.0} Multiplication: >>> e = d * b >>> print(e) {-0.0625: 1.0} And even division: >>> a = QuantumFloat(3) >>> b = QuantumFloat(3) >>> a[:] = 7 >>> b[:] = 2 >>> c = a/b >>> print(c) {3.5: 1.0} Floor division: >>> d = a//b >>> print(d) {3.0: 1.0} Inversion: >>> a = QuantumFloat(3, -1) >>> a[:] = 3.5 >>> b= a**-1 >>> print(b) {0.25: 1.0} Note that the latter is only an approximate result. This is because in many cases, the results of division can not be stored in a finite amount of qubits, forcing us to approximate. To get a better approximation we can use the :meth:`q_div <qrisp.q_div>` and :meth:`qf_inversion <qrisp.qf_inversion>` functions and specify the precision: >>> from qrisp import q_div, qf_inversion >>> a = QuantumFloat(3) >>> a[:] = 1 >>> b = QuantumFloat(3) >>> b[:] = 7 >>> c = q_div(a, b, prec = 6) >>> print(c) {0.140625: 1.0} Comparing with the classical result (0.1428571428): >>> 1/7 - 0.140625 0.002232142857142849 We see that the result is inside the expected precision of $2^{-6} = 0.015625$. **In-place Operations** Further supported operations are inplace addition, subtraction (with both classical and quantum values): >>> a = QuantumFloat(4, signed = True) >>> a[:] = 4 >>> b = QuantumFloat(4) >>> b[:] = 3 >>> a += b >>> print(a) {7: 1.0} >>> a -= 2 >>> print(a) {5: 1.0} .. warning:: Additions that would result in overflow, raise no errors. Instead, the additions are performed `modular <https://en.wikipedia.org/wiki/Modular_arithmetic>`_. >>> c = QuantumFloat(3) >>> c += 9 >>> print(c) {1: 1.0} For inplace multiplications, only classical values are allowed: >>> a *= -3 >>> print(a) {-15: 1.0} .. note:: In-place multiplications can change the mantissa size to prevent overflow errors. If you want to prevent this behavior, look into :meth:`inpl_mult <qrisp.inpl_mult>`. >>> a.size 7 **Bitshifts** Bitshifts can be executed for free (i.e. not requiring any quantum gates). We can either use the :meth:`exp_shift <qrisp.QuantumFloat.exp_shift>` method or use the infix operators. Note that the bitshifts work in-place. >>> a.exp_shift(3) >>> print(a) {-120: 1.0} >>> a >> 5 >>> print(a) {-3.75: 1.0} **Comparisons** QuantumFloats can be compared to Python floats using the established operators. The return values are :ref:`QuantumBools <QuantumBool>`: >>> from qrisp import h >>> a = QuantumFloat(4) >>> h(a[2]) >>> print(a) {0: 0.5, 4: 0.5} >>> comparison_qbl_0 = (a < 4 ) >>> print(comparison_qbl_0) {False: 0.5, True: 0.5} Comparison to other QuantumFloats also works: >>> b = QuantumFloat(3) >>> b[:] = 4 >>> comparison_qbl_1 = (a == b) >>> comparison_qbl_1.qs.statevector() sqrt(2)*(|0>*|True>*|4>*|False> + |4>*|False>*|4>*|True>)/2 The first tensor factor containing a boolean value is corresponding to ``comparison_qbl_0`` and the second one is ``comparison_qbl_1``. """ def __init__(self, msize, exponent=0, qs=None, name=None, signed=False): # Boolean to indicate if the float is signed self.signed = signed # Exponent self.exponent = exponent # Size of the mantissa self.msize = msize # Array that consists of (log2(min), log2(max)) where min and max are the # minimal and maximal values of the absolutes that the QuantumFloat can # represent. self.mshape = np.array([exponent, exponent + msize]) # Initialize QuantumVariable if signed: super().__init__(msize + 1, qs, name=name) else: super().__init__(msize, qs, name=name) # Define outcome_labels def decoder(self, i): if self.signed: res = signed_int_iso_inv(i, self.size - 1) * 2.0**self.exponent else: res = i * 2**self.exponent if self.exponent >= 0: return int(res) else: return res # def encoder(self, i): # if self.signed: # res = int(signed_int_iso(i/2**self.exponent, self.size-1)) # else: # res = int(i/2**self.exponent) # return res
[docs] def sb_poly(self, m=0): """ Returns the semi-boolean polynomial of this `QuantumFloat` where `m` specifies the image extension parameter. For the technical details we refer to: https://ieeexplore.ieee.org/document/9815035 Parameters ---------- m : int, optional Image extension parameter. The default is 0. Returns ------- Sympy expression The semi-boolean polynomial of this QuantumFloat. Examples -------- >>> from qrisp import QuantumFloat >>> x = QuantumFloat(3, -1, signed = True, name = "x") >>> print(x.sb_poly(5)) 0.5*x_0 + 1.0*x_1 + 2.0*x_2 + 28.0*x_3 """ if m == 0: m = self.size symbols = [sp.symbols(self.name + "_" + str(i)) for i in range(self.size)] poly = sum([2.0 ** (i) * symbols[i] for i in range(self.size)]) if self.signed: poly += (2.0 ** (m + 1) - 2.0 ** (self.size)) * symbols[-1] return 2**self.exponent * poly
def encode(self, encoding_number, rounding=False, permit_dirtyness=False): if rounding: # Round value to closest fitting number outcome_labels = [self.decoder(i) for i in range(2**self.size)] encoding_number = outcome_labels[ np.argmin(np.abs(encoding_number - np.array(outcome_labels))) ] super().encode(encoding_number, permit_dirtyness=permit_dirtyness) @gate_wrap(permeability="args", is_qfree=True) def __mul__(self, other): from qrisp.arithmetic import q_mult, polynomial_encoder if isinstance(other, QuantumFloat): return q_mult(self, other) elif isinstance(other, int): bit_shift = 0 while not other % 2: other = other >> 1 bit_shift += 1 if self.signed or other < 0: output_qf = QuantumFloat( self.msize + int(np.ceil(np.log2(abs(other)))), self.exponent, signed=True, ) else: output_qf = QuantumFloat( self.msize + int(np.ceil(np.log2(abs(other)))), self.exponent, signed=False, ) polynomial_encoder([self], output_qf, other * sp.Symbol("x")) output_qf << bit_shift return output_qf else: raise Exception( "QuantumFloat multiplication for type " + str(type(other)) + "" " not implemented (available are QuantumFloat and int)" ) @gate_wrap(permeability="args", is_qfree=True) def __add__(self, other): from qrisp.arithmetic import sbp_add if isinstance(other, QuantumFloat): return sbp_add(self, other) elif isinstance(other, (int, float)): res = self.duplicate(init=True) res += other return res else: raise Exception( "Addition with type " + str(type(other)) + " not implemented" ) @gate_wrap(permeability="args", is_qfree=True) def __sub__(self, other): from qrisp.arithmetic import sbp_sub if isinstance(other, QuantumFloat): return sbp_sub(self, other) elif isinstance(other, (int, float)): res = self.duplicate(init=True) res -= other return res else: raise Exception( "Subtraction with type " + str(type(other)) + " not implemented" ) __radd__ = __add__ __rmul__ = __mul__ @gate_wrap(permeability="args", is_qfree=True) def __rsub__(self, other): from qrisp import x from qrisp.arithmetic import sbp_sub if isinstance(other, QuantumFloat): return sbp_sub(other, self) elif isinstance(other, (int, float)): res = self.duplicate(init=True) if not res.signed: res.add_sign() x(res) res += other + 2**res.exponent return res else: raise Exception( "Subtraction with type " + str(type(other)) + " not implemented" ) @gate_wrap(permeability="args", is_qfree=True) def __truediv__(self, other): from qrisp.arithmetic import q_div return q_div(self, other) @gate_wrap(permeability="args", is_qfree=True) def __floordiv__(self, other): if self.signed or other.signed: raise Exception("Floor division not implemented for signed QuantumFloats") if self.exponent < 0 or other.exponent < 0: raise Exception( "Tried to perform floor division on non-integer QuantumFloats" ) from qrisp.arithmetic import q_div return q_div(self, other, prec=0) @gate_wrap(permeability="args", is_qfree=True) def __pow__(self, power): if power != -1: raise Exception("Currently the only supported power is -1") from qrisp.arithmetic import qf_inversion return qf_inversion(self) @gate_wrap(permeability=[1], is_qfree=True) def __iadd__(self, other): from qrisp.arithmetic import polynomial_encoder if isinstance(other, QuantumFloat): input_qf_list = [other] poly = sp.symbols("x") polynomial_encoder(input_qf_list, self, poly) elif isinstance(other, (int, float)): # self.incr(other) if not int(other / 2**self.exponent) == other / 2**self.exponent: raise Exception( "Tried to perform in-place addition with invalid number. " "QuantumFloat precision too low." ) input_qf_list = [] poly = sp.sympify(other) polynomial_encoder(input_qf_list, self, poly) else: raise Exception( "In-place addition for type " + str(type(other)) + " not implemented" ) return self @gate_wrap(permeability=[1], is_qfree=True) def __isub__(self, other): from qrisp.arithmetic import polynomial_encoder if isinstance(other, QuantumFloat): input_qf_list = [other] poly = -sp.symbols("x") polynomial_encoder(input_qf_list, self, poly) elif isinstance(other, (int, float)): if not int(other / 2**self.exponent) == other / 2**self.exponent: raise Exception( "Tried to perform in-place subtraction with invalid number. " "QuantumFloat precision too low." ) input_qf_list = [] poly = -sp.sympify(other) polynomial_encoder(input_qf_list, self, poly) else: raise Exception( "In-place substraction for type " + str(type(other)) + " not implemented" ) return self @gate_wrap(permeability=[], is_qfree=True) def __imul__(self, other): from qrisp.arithmetic import inpl_mult inpl_mult(self, other) return self def __rshift__(self, k): self.exp_shift(-k) return self def __lshift__(self, k): self.exp_shift(k) return self def __lt__(self, other): from qrisp.arithmetic import lt if not isinstance(other, (QuantumFloat, int, float)): raise Exception(f"Comparison with type {type(other)} not implemented") return lt(self, other) def __gt__(self, other): from qrisp.arithmetic import gt if not isinstance(other, (QuantumFloat, int, float)): raise Exception(f"Comparison with type {type(other)} not implemented") return gt(self, other) def __le__(self, other): from qrisp.arithmetic import leq if not isinstance(other, (QuantumFloat, int, float)): raise Exception(f"Comparison with type {type(other)} not implemented") return leq(self, other) def __ge__(self, other): from qrisp.arithmetic import geq if not isinstance(other, (QuantumFloat, int, float)): raise Exception(f"Comparison with type {type(other)} not implemented") return geq(self, other) def __eq__(self, other): from qrisp.arithmetic import eq if not isinstance(other, (QuantumFloat, int, float)): raise Exception(f"Comparison with type {type(other)} not implemented") return eq(self, other) def __ne__(self, other): from qrisp.arithmetic import neq if not isinstance(other, (QuantumFloat, int, float)): raise Exception(f"Comparison with type {type(other)} not implemented") return neq(self, other)
[docs] def exp_shift(self, shift): """ Performs an internal bit shift. Note that this method doesn't cost any quantum gates. For the quantum version of this method, see :meth:`quantum_bit_shift<qrisp.QuantumFloat.quantum_bitshift>`. Parameters ---------- shift : int The amount to shift. Raises ------ Exception Tried to shift QuantumFloat exponent by non-integer value Examples -------- We create a QuantumFloat and perform a bitshift: >>> from qrisp import QuantumFloat >>> a = QuantumFloat(4) >>> a[:] = 2 >>> a.exp_shift(2) >>> print(a) {8: 1.0} >>> print(a.qs) :: QuantumCircuit: -------------- a.0: ───── ┌───┐ a.1: ┤ X ├ └───┘ a.2: ───── <BLANKLINE> a.3: ───── Live QuantumVariables: --------------------- QuantumFloat a """ if not isinstance(shift, int): raise Exception("Tried to shift QuantumFloat exponent by non-integer value") self.exponent += shift self.mshape = self.mshape + shift
def reduce(self, qubits, verify=False): QuantumVariable.reduce(self, qubits, verify) try: self.mshape[1] -= len(qubits) self.msize -= len(qubits) except TypeError: self.mshape[1] -= 1 self.msize -= 1 def extend(self, amount, position=-1): QuantumVariable.extend(self, amount, position=position) self.mshape[1] += amount self.msize += amount
[docs] def add_sign(self): """ Turns an unsigned QuantumFloat into its signed version. Raises ------ Exception Tried to add sign to signed QuantumFloat. Examples -------- >>> from qrisp import QuantumFloat >>> qf = QuantumFloat(4) >>> qf.signed False >>> qf.add_sign() >>> qf.signed True """ if self.signed: raise Exception(r'Tried to add sign to signed QuantumFloat') self.extend(1, self.size) self.mshape[1] -= 1 self.msize -= 1 self.signed = True
[docs] def sign(self): r""" Returns the sign qubit. This qubit is in state $\ket{1}$ if the QuantumFloat holds a negative value and in state $\ket{0}$ otherwise. For more information about the encoding of negative numbers check the `publication <https://ieeexplore.ieee.org/document/9815035>`_. .. warning:: Performing an X gate on this qubit does not flip the sign! Use inplace multiplication instead. >>> from qrisp import QuantumFloat >>> qf = QuantumFloat(3, signed = True) >>> qf[:] = 3 >>> qf *= -1 >>> print(qf) {-3: 1.0} Raises ------ Exception Tried to retrieve sign qubit of unsigned QuantumFloat. Returns ------- Qubit The qubit holding the sign. Examples -------- We create a QuantumFloat, initiate a state that has probability 2/3 of being negative and entangle a QuantumBool with the sign qubit. >>> from qrisp import QuantumFloat, QuantumBool, cx >>> qf = QuantumFloat(4, signed = True) >>> n_amp = 1/3**0.5 >>> qf[:] = {-1 : n_amp, -2 : n_amp, 1 : n_amp} >>> qbl = QuantumBool() >>> cx(qf.sign(), qbl) >>> print(qbl) {True: 0.6667, False: 0.3333} """ if not self.signed: raise Exception("Tried to retrieve sign qubit of unsigned QuantumFloat") return self[-1]
def init_from( self, other, ignore_rounding_errors=False, ignore_overflow_errors=False ): copy_qf( self, other, ignore_rounding_errors=ignore_rounding_errors, ignore_overflow_errors=ignore_overflow_errors, ) def incr(self, x=None): from qrisp.arithmetic.adders.incrementation import increment if x is None: x = 2**self.exponent increment(self, x) def __hash__(self): return id(self)
[docs] def significant(self, k): """ Returns the qubit with significance $k$. Parameters ---------- k : int The significance. Raises ------ Exception Tried to retrieve invalid significant from QuantumFloat Returns ------- Qubit The Qubit with significance $k$. Examples -------- We create a QuantumFloat and flip a qubit of specified significance. >>> from qrisp import QuantumFloat, x >>> qf = QuantumFloat(6, -3) >>> x(qf.significant(-2)) >>> print(qf) {0.25: 1.0} The qubit with significance $-2$ corresponds to the value $0.25 = 2^{-2}$. >>> x(qf.significant(2)) {4.25: 1.0} The qubit with significance $2$ corresponds to the value $4 = 2^{2}$. """ sig_list = list(range(self.mshape[0], self.mshape[1])) if k not in sig_list: raise Exception( f"Tried to retrieve invalid significant {k} " f"from QuantumFloat with mantissa shape {self.mshape}" ) return self[sig_list.index(k)]
[docs] def truncate(self, x): """ Receives a regular float and returns the float that is closest to the input but can still be encoded. Parameters ---------- x : float A float that is supposed to be truncated. Returns ------- float The truncated float. Examples -------- We create a QuantumFloat and round a value to fit the encoder and subsequently initiate: >>> from qrisp import QuantumFloat >>> qf = QuantumFloat(4, -1) >>> value = 0.5102341 >>> qf[:] = value Exception: Value 0.5102341 not supported by encoder. >>> rounded_value = qf.truncate(value) >>> rounded_value 0.5 >>> qf[:] = rounded_value >>> print(qf) {0.5: 1.0} """ decoder_values = np.array([self.decoder(i) for i in range(2**self.size)]) return decoder_values[np.argmin(np.abs(decoder_values - x))]
[docs] def get_ev(self, **mes_kwargs): """ Retrieves the expectation value of self. Parameters ---------- **mes_kwargs : dict Keyword arguments for the measurement. See :meth:`qrisp.QuantumVariable.get_measurement` for more information. Returns ------- float The expectation value. Examples -------- We set up a QuantumFloat in uniform superposition and retrieve the expectation value: >>> from qrisp import QuantumFloat, h >>> qf = QuantumFloat(4) >>> h(qf) >>> qf.get_ev() 7.5 """ mes_res = self.get_measurement(**mes_kwargs) return sum([k*v for k,v in mes_res.items()])
[docs] def quantum_bit_shift(self, shift_amount): """ Performs a bit shift in the quantum device. While :meth:`exp_shift<qrisp.QuantumFloat.exp_shift>` performs a bit shift in the compiler (thus costing no quantum gates) this method performs the bitshift on the hardware. This has the advantage, that it can be controlled if called within a :ref:`ControlEnvironment` and furthermore admits bit shifts based on the state of a QuantumFloat .. note:: Bit bit shifts based on a QuantumFloat are currently only possible if both self and ``shift_amount`` are unsigned. .. warning:: Quantum bit shifting extends the QuantumFloat (ie. it allocates additional qubits). Parameters ---------- shift_amount : int or QuantumFloat The amount to shift. Raises ------ Exception Tried to shift QuantumFloat exponent by non-integer value Exception Quantum-quantum bitshifting is currently only supported for unsigned arguments Examples -------- We create a QuantumFloat and a QuantumBool to perform a controlled bit shift. :: from qrisp import QuantumFloat, QuantumBool, h qf = QuantumFloat(4) qf[:] = 1 qbl = QuantumBool() h(qbl) with qbl: qf.quantum_bit_shift(2) Evaluate the result >>> print(qf.qs.statevector()) sqrt(2)*(|1>*|False> + |4>*|True>)/2 """ from qrisp.arithmetic import quantum_bit_shift quantum_bit_shift(self, shift_amount)
def duplicate(self, name=None, qs=None, init=False): res = QuantumVariable.duplicate(self, name, qs, init) res.mshape = np.array(self.mshape) return res
def create_output_qf(operands, op): if isinstance(op, sp.core.expr.Expr): from qrisp.arithmetic.poly_tools import expr_to_list expr_list = expr_to_list(op) for i in range(len(expr_list)): if not isinstance(expr_list[i][0], sp.Symbol): expr_list[i].pop(0) operands.sort(key=lambda x: x.name) def prod(iter): iter = list(iter) a = iter[0] for i in range(1, len(iter)): a *= iter[i] return a from sympy import Abs, Poly, Symbol poly = Poly(op) monom_list = [ a * prod(x**k for x, k in zip(poly.gens, mon)) for a, mon in zip(poly.coeffs(), poly.monoms()) ] max_value_dic = {Symbol(qf.name): 2.0 ** qf.mshape[1] for qf in operands} min_value_dic = {Symbol(qf.name): 2.0 ** qf.mshape[0] for qf in operands} abs_poly = sum([Abs(monom) for monom in monom_list], 0) min_poly_value = min( [float(Abs(monom).subs(min_value_dic)) for monom in monom_list] ) max_poly_value = float(abs_poly.subs(max_value_dic)) min_sig = int(np.floor(np.log2(min_poly_value))) max_sig = int(np.ceil(np.log2(max_poly_value))) msize = max_sig - min_sig exponent = min_sig signed = bool(sum([int(operand.signed) for operand in operands])) return QuantumFloat(msize, exponent=exponent, signed=signed) if op == "add": signed = operands[0].signed or operands[1].signed exponent = min(operands[0].exponent, operands[1].exponent) max_sig = int( np.ceil( np.log2(int(2 ** operands[0].mshape[1] + 2 ** operands[1].mshape[1])) ) ) msize = max_sig - exponent + 1 return QuantumFloat( msize, exponent, operands[0].qs, signed=signed, name="add_res*" ) if op == "mul": signed = operands[0].signed or operands[1].signed if operands[0].reg == operands[1].reg and ( operands[0].signed and operands[1].signed ): signed = False return QuantumFloat( operands[0].msize + operands[1].msize + operands[0].signed * operands[1].signed, operands[0].exponent + operands[1].exponent, operands[0].qs, signed=signed, name="mul_res*", ) if op == "sub": exponent = min(operands[0].exponent, operands[1].exponent) max_sig = int( np.ceil( np.log2(int(2 ** operands[0].mshape[1] + 2 ** operands[1].mshape[1])) ) ) msize = max_sig - exponent + 1 return QuantumFloat( msize, exponent, operands[0].qs, signed=True, name="sub_res*" ) # Initiates the value of qf2 into qf1 where qf1 has to hold the value 0 def copy_qf(qf1, qf2, ignore_overflow_errors=False, ignore_rounding_errors=False): # Lists that translate Qubit index => Significance qf1_sign_list = [qf1.exponent + i for i in range(qf1.size)] qf2_sign_list = [qf2.exponent + i for i in range(qf2.size)] # Check overflow/underflow if max(qf1_sign_list) < max(qf2_sign_list) and not ignore_overflow_errors: raise Exception( "Copy operation would result in overflow " "(use ignore_overflow_errors = True)" ) if min(qf1_sign_list) > min(qf2_sign_list) and not ignore_rounding_errors: raise Exception( "Copy operation would result in rounding " "(use ignore_rounding_errors = True)" ) qs = qf1.qs if qf2.signed: if not qf1.signed: raise Exception("Tried to copy signed into unsigend float") # Remove last entry from significance list (last qubit is the sign qubit) qf2_sign_list.pop(-1) qf1_sign_list.pop(-1) for i in range(len(qf1_sign_list)): # If we are in a realm where both floats have overlapping significance # => CNOT into each other if qf1_sign_list[i] in qf2_sign_list: qf2_index = qf2_sign_list.index(qf1_sign_list[i]) qs.cx(qf2[qf2_index], qf1[i]) continue # Otherwise copy the sign bit into the bits of higher significance than qf2 if qf1_sign_list[i] > max(qf2_sign_list) and qf2.signed: qs.cx(qf2[-1], qf1[i]) # Copy the sign bit if qf2.signed: qs.cx(qf2[-1], qf1[-1])