I have the following get_angles
function that creates angles from input_features
. The features returned by the function are being used to train a variational quantum circuit.
def get_angles(x):
beta0 = 2 * np.arcsin(np.sqrt(x[1] ** 2) / np.sqrt(x[0] ** 2 + x[1] ** 2 + 1e-12))
beta1 = 2 * np.arcsin(np.sqrt(x[2] ** 2) / np.sqrt(x[2] ** 2 + x[2] ** 2 + 1e-12))
beta2 = 2 * np.arcsin(np.linalg.norm(x[2:]) / np.linalg.norm(x))
return np.array([beta2, -beta1 / 2, beta1 / 2, -beta0 / 2, beta0 / 2])
As such:
input_features = [10, 20, 30, 40, 50]`
# Transform the features
features = np.array(get_angles(input_features))
Now, I would like to reverse this operation by taking the final features
values and transforming them back into the input_features
values used in the get_angles
function. Is there a way to reverse the get_angles
function defined above?
Thanks in advance.
Expecting to receive the input_features
from running the final features
through a get_reverse_angles
function, I tried multiple variations of a get_reverse_angles
function such as the one below to no avail.
def get_reverse_angles(angles):
# Extract the angles
beta2, neg_beta1, pos_beta1, neg_beta0, pos_beta0 = angles
# Solve for x using trigonometric equations
x0 = np.sqrt(2)
x1 = np.sin(beta2 / 2) * np.sqrt(2)
x2 = np.sin(pos_beta1 / 2) * np.sqrt(2)
x3 = np.sin(pos_beta0 / 2) * np.sqrt(2)
x4 = np.sin(neg_beta0 / 2) * np.sqrt(2)
# Compute x0 using the first equation
x0 = np.sqrt(x1 ** 2 + x2 ** 2 + x3 ** 2 + x4 ** 2)
# Return the values of the reversed operation
return np.array([x0, x1 * x0, x2 * x0, x3 * x0, x4 * x0])
The get_reverse_angles
function returned [ 1.79350156 2.41835701 0.97063605 1.33346136 -1.33346136] as opposed to the expected [10 20 30 40 50] input_features
.
This answer is rewritten based on additional comments by @camaya :
input_features = [22393, 22962, 22689, 21849, 20979]
.So what do we have?!
Incoming data input_features = [22393, 22962, 22689, 21849, 20979]
- a list of unsorted integers.
Three variables are declared in the get_angles
function:
Table "Stages of input data recovery" |
---|
Step | Variable | Elements involved in calculations |
---|---|---|
1 | beta0 | input[1] & input[0] it can be restored using features[4] |
2 | beta1 | input[2] cannot be restored using features[2] too many variations |
3 | beta2 | input[:] if you save input[2], you can restore (input[3], input[4]) or (input[4], input[3]) using features[0] |
(input[:] == input_features ; def get_angles(x): return features[:]
)
import numpy as np
from datetime import datetime
from typing import Union, List
def get_angles_or_features(
input: Union[List[int], np.ndarray[float]]
) -> Union[np.ndarray, List, None]:
""""""
input_type = type(input).__name__
# Check type input
if not (input_type == 'ndarray' or input_type == 'list'):
print("Input Error")
return None
# Processing get angles
elif input_type == 'list':
beta0 = (
2 * np.arcsin((np.sqrt(input[1] ** 2))
/ np.sqrt(input[0] ** 2 + input[1] ** 2 + 1e-12)
)
)
beta1 = (
2 * np.arcsin(np.sqrt(input[2] ** 2)
/ np.sqrt(input[2] ** 2 + input[2] ** 2 + 1e-12)
)
)
beta2 = (
2 * np.arcsin(np.linalg.norm(input[2:]) / np.linalg.norm(input))
)
return np.array([beta2, -beta1 / 2, beta1 / 2, -beta0 / 2, beta0 / 2])
# Restoring data input_features
elif input_type == 'ndarray':
beta0 = input[4]
x0, x1, x2, x3, x4 = None, None, None, None, None
start1 = datetime.now()
for x0 in range(1, 100000):
for x1 in range(1, 100000):
if (check := beta0 == (
np.arcsin(np.sqrt(x1 ** 2)
/ np.sqrt(x0 ** 2 + x1 ** 2 + 1e-12)))):
break
if check:
print("We spent on the selection "
f"of the first two elements: {datetime.now() - start1}")
break
return [x0, x1, x2, x3, x4]
# input_features = [10, 20, 30, 40, 50]
input_features = [35, 50, 65, 80, 90]
# input_features = [22393, 22962, 22689, 21849, 20979]
# input_features = [5, 4, 3, 2, 1]
# input_features = [99960, 99970, 99980, 99990, 99999]
# input_features = [3, 2, 1, 5, 4]
# Transform the features
features = np.array(get_angles_or_features(input_features))
print(f"input_features >>> {input_features}")
print(f"features >>> {features}")
# Restoring the original data input_features
restored_features = get_angles_or_features(features)
print(f"restored_features >>> {restored_features}")
Output to the console:
input_features >>> [35, 50, 65, 80, 90]
features >>> [ 2.3025178 -0.78539816 0.78539816 -0.96007036 0.96007036]
We spent on the selection of the first two elements: 0:00:25.827939
restored_features >>> [35, 50, None, None, None]
We are guaranteed to get the first two elements, but for this we used two nested for loops, the time complexity of this code is O(n^2) in the worst case.
It is not possible to define the third element in the second step, you can only reduce the number of iterations for the third step.
import numpy as np
from datetime import datetime
from typing import Union, List
def get_angles_or_features(
input: Union[List[int], np.ndarray[float]]
) -> Union[np.ndarray, List, None]:
""""""
input_type = type(input).__name__
# Check type input
if not (input_type == 'ndarray' or input_type == 'list'):
print("Input Error")
return None
# Processing get angles
elif input_type == 'list':
beta0 = (
2 * np.arcsin((np.sqrt(input[1] ** 2))
/ np.sqrt(input[0] ** 2 + input[1] ** 2 + 1e-12)
)
)
beta1 = (
2 * np.arcsin(np.sqrt(input[2] ** 2)
/ np.sqrt(input[2] ** 2 + input[2] ** 2 + 1e-12)
)
)
beta2 = (
2 * np.arcsin(np.linalg.norm(input[2:]) / np.linalg.norm(input))
)
return np.array([beta2, -beta1 / 2, beta1 / 2, -beta0 / 2, beta0 / 2])
# Restoring data input_features
elif input_type == 'ndarray':
beta0, beta1 = input[4], input[2]
x0, x1, x2, x3, x4 = None, None, None, None, None
# Step 1
start1 = datetime.now()
for x0 in range(1, 100000):
for x1 in range(1, 100000):
if (check := beta0 == (
np.arcsin(np.sqrt(x1 ** 2)
/ np.sqrt(x0 ** 2 + x1 ** 2 + 1e-12)))):
break
if check:
print("We spent on the selection "
f"of the first two elements: {datetime.now() - start1}"
)
break
# Step 2
start2 = datetime.now()
_x2 = tuple(x for x in range(1, 100000)
if beta1 == np.arcsin(np.sqrt(x ** 2)
/ np.sqrt(x ** 2 + x ** 2 + 1e-12)
)
)
end2 = datetime.now()
print("Reduced future iterations from 100000 "
f"to {(_len := len(_x2))} and wasted time: {end2 - start2}"
)
return [x0, x1, (type(_x2), f"len: {_len}"), x3, x4]
# input_features = [10, 20, 30, 40, 50]
input_features = [35, 50, 65, 80, 90]
# input_features = [22393, 22962, 22689, 21849, 20979]
# input_features = [5, 4, 3, 2, 1]
# input_features = [99960, 99970, 99980, 99990, 99999]
# input_features = [3, 2, 1, 5, 4]
# Transform the features
features = np.array(get_angles_or_features(input_features))
print(f"input_features >>> {input_features}")
print(f"features >>> {features}")
# Restoring the original data input_features
restored_features = get_angles_or_features(features)
print(f"restored_features >>> {restored_features}")
Output to the console:
input_features >>> [35, 50, 65, 80, 90]
features >>> [ 2.3025178 -0.78539816 0.78539816 -0.96007036 0.96007036]
We spent on the selection of the first two elements: 0:00:26.472476
Reduced future iterations from 100000 to 43309 and wasted time: 0:00:00.814264
restored_features >>> [35, 50, (<class 'tuple'>, 'len: 43309'), None, None]
However, 43309 iterations of the first for loop is too expensive...
This is due to the fact that only one element input_features is used to calculate beta1 — this increases the inverse variability.
beta1 = 2 * np.arcsin(np.sqrt(x[2] ** 2) / np.sqrt(x[2] ** 2 + x[2] ** 2 + 1e-12))
If it is acceptable to add one element to features for backward compatibility,
then the variability can be leveled.
from typing import Union, Tuple, List
from datetime import datetime
import numpy as np
def get_angles_or_features(
input: Union[List[int], np.ndarray[float]]
) -> Union[Tuple, None]:
""""""
input_type = type(input).__name__
# Check type input
if not (input_type == 'tuple' or input_type == 'list'):
print("Input Error")
return None
# Processing get angles
if input_type == 'list':
beta0 = (
2 * np.arcsin((np.sqrt(input[1] ** 2))
/ np.sqrt(input[0] ** 2 + input[1] ** 2 + 1e-12)))
beta1 = (
2 * np.arcsin(np.sqrt(input[2] ** 2)
/ np.sqrt(input[2] ** 2 + input[2] ** 2 + 1e-12)))
beta2 = (
2 * np.arcsin(np.linalg.norm(input[2:]) / np.linalg.norm(input)))
return (np.array([beta2, -beta1 / 2, beta1 / 2,
-beta0 / 2, beta0 / 2]), input[2])
# Conversion angles
elif input_type == 'tuple':
beta0, beta2 = input[0][4], input[0][0] / 2
x2 = int(input[-1])
start, end = (x2 - 3500 if x2 - 3500 >= 0 else 0,
x2 + 3501 if x2 + 3501 <= 43000 else 43000)
# Defining x0 & x1
_x0_x1 = tuple(
(x0, x1)
for x0 in range(start, end)
for x1 in range(start, end)
if (0.46 < x2 / (x0 + x1) < 0.51
and (beta0 == np.arcsin(np.sqrt(x1 ** 2)
/ np.sqrt(x0 ** 2 + x1 ** 2 + 1e-12)))
)
)[0]
x0, x1 = _x0_x1
# Defining x3 & x4
regeneraite_features = (
[x0, x1, x2, x3, x4]
for x3 in range(start, end)
for x4 in range(start, end)
if (0.5 < x2 / (x3 + x4) < 0.54
and (beta2 == np.arcsin(
np.linalg.norm([x2, x3, x4])
/ np.linalg.norm([x0, x1, x2, x3, x4])))
)
)
return tuple(regeneraite_features)
all_input_features = [
[20979, 20583, 19433, 18988, 18687],
[22689, 21849, 20979, 20583, 19433],
[22962, 22689, 21849, 20979, 20583],
[22393, 22962, 22689, 21849, 20979],
[21849, 20979, 20583, 19433, 18988]
]
if __name__ == "__main__":
for input_features in all_input_features:
# Transform the features
features = get_angles_or_features(input_features)
print(f"\ninput_features >>> {input_features}")
print(f"features >>> {features}")
start_time = datetime.now()
restored_features = get_angles_or_features(features)
# print(f"restored_features >>> {features}")
print(f"restored_features >>> {restored_features}")
print(f"Duration of the recovery process: {datetime.now() - start_time}")
Output to the console:
input_features >>> [20979, 20583, 19433, 18988, 18687]
features >>> (array([ 1.6856525 , -0.78539816, 0.78539816, -0.77587052, 0.77587052]), 19433)
restored_features >>> ([20979, 20583, 19433, 18687, 18988], [20979, 20583, 19433, 18988, 18687])
Duration of the recovery process: 0:08:55.662949
input_features >>> [22689, 21849, 20979, 20583, 19433]
features >>> (array([ 1.68262106, -0.78539816, 0.78539816, -0.7665401 , 0.7665401 ]), 20979)
restored_features >>> ([22689, 21849, 20979, 19433, 20583], [22689, 21849, 20979, 20583, 19433])
Duration of the recovery process: 0:09:29.221780
input_features >>> [22962, 22689, 21849, 20979, 20583]
features >>> (array([ 1.69663709, -0.78539816, 0.78539816, -0.77941808, 0.77941808]), 21849)
restored_features >>> ([22962, 22689, 21849, 19089, 22347], [22962, 22689, 21849, 20583, 20979], [22962, 22689, 21849, 20979, 20583], [22962, 22689, 21849, 22347, 19089])
Duration of the recovery process: 0:09:36.666942
input_features >>> [22393, 22962, 22689, 21849, 20979]
features >>> (array([ 1.73553479, -0.78539816, 0.78539816, -0.79794298, 0.79794298]), 22689)
restored_features >>> ([22393, 22962, 22689, 20979, 21849], [22393, 22962, 22689, 21849, 20979])
Duration of the recovery process: 0:10:10.256594
input_features >>> [21849, 20979, 20583, 19433, 18988]
features >>> (array([ 1.68858074, -0.78539816, 0.78539816, -0.76508714, 0.76508714]), 20583)
restored_features >>> ([21849, 20979, 20583, 18988, 19433], [21849, 20979, 20583, 19433, 18988])
Duration of the recovery process: 0:09:07.385657
Good. When you save the input[2]
you have the opportunity to get an acceptable result,
but it is still not absolutely pure.
In addition, the duration of the recovery process is still very long.
We had to take these three steps to show that this approach is not effective enough.
When you process data that you will need in the future, keep the opportunity to access it.
At least this way...
from typing import List, Union, Tuple, Dict
import numpy as np
def get_angles(x: List[int]
) -> Union[Tuple[np.ndarray, tuple], Dict[tuple, list]]:
""""""
beta0 = 2 * np.arcsin(np.sqrt(x[1] ** 2) / np.sqrt(x[0] ** 2 + x[1] ** 2 + 1e-12))
beta1 = 2 * np.arcsin(np.sqrt(x[2] ** 2) / np.sqrt(x[2] ** 2 + x[2] ** 2 + 1e-12))
beta2 = 2 * np.arcsin(np.linalg.norm(x[2:]) / np.linalg.norm(x))
return (np.array([beta2, -beta1 / 2, beta1 / 2, -beta0 / 2, beta0 / 2]),
(x[0], x[1], x[2], x[3], x[4])
)
# or this way...
return {(x[0], x[1], x[2], x[3], x[4]):
[beta2, -beta1 / 2, beta1 / 2, -beta0 / 2, beta0 / 2]
}
You can go even further...
from typing import List
import numpy as np
@freeze_arguments
def get_angles(x: List[int]) -> np.ndarray:
""""""
beta0 = 2 * np.arcsin(np.sqrt(x[1] ** 2) / np.sqrt(x[0] ** 2 + x[1] ** 2 + 1e-12))
beta1 = 2 * np.arcsin(np.sqrt(x[2] ** 2) / np.sqrt(x[2] ** 2 + x[2] ** 2 + 1e-12))
beta2 = 2 * np.arcsin(np.linalg.norm(x[2:]) / np.linalg.norm(x))
return np.array([beta2, -beta1 / 2, beta1 / 2, -beta0 / 2, beta0 / 2])