"""
\********************************************************************************
* 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.alg_primitives.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(str(hash(self)) + "_" + 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.alg_primitives.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.alg_primitives.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.alg_primitives.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.alg_primitives.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.alg_primitives.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.alg_primitives.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.alg_primitives.arithmetic import qf_inversion
return qf_inversion(self)
@gate_wrap(permeability=[1], is_qfree=True)
def __iadd__(self, other):
from qrisp.alg_primitives.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.alg_primitives.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.alg_primitives.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.alg_primitives.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.alg_primitives.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.alg_primitives.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.alg_primitives.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.alg_primitives.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.alg_primitives.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.alg_primitives.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.alg_primitives.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.alg_primitives.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])