From 8cc90d9860dce22c22128e369f09ee5cc72b5606 Mon Sep 17 00:00:00 2001 From: TheoLaudatQM <98808790+TheoLaudatQM@users.noreply.github.com> Date: Mon, 18 Jul 2022 10:21:34 +0200 Subject: [PATCH] Fix log scans with int bug (#117) * Fix log scans with int bug Fixed in from_array() and qua_logspace() * changelog --- CHANGELOG.md | 2 ++ qualang_tools/loops/loops.py | 44 ++++++++++++++---------------- tests_against_server/test_loops.py | 43 ++++++++++++++++++++++++++--- 3 files changed, 62 insertions(+), 27 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cd7e4bf0..9dcf7f24 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) ## [Unreleased] ### Added - Calibrations - a new package with an API to perform basic single qubit calibration protocols. +### Fixed +- Loops - Fixed qua_logspace() and from_array() for logarithmic increments with integers. ## [0.10.0] - 2022-07-04 ### Fixed diff --git a/qualang_tools/loops/loops.py b/qualang_tools/loops/loops.py index 40d8a9a2..0d284e13 100644 --- a/qualang_tools/loops/loops.py +++ b/qualang_tools/loops/loops.py @@ -33,7 +33,7 @@ def from_array(var, array): # Check array increment if np.isclose(np.std(np.diff(array)), 0): increment = "lin" - elif np.isclose(np.std(array[1:] / array[:-1]), 0): + elif np.isclose(np.std(array[1:] / array[:-1]), 0, atol=1e-3): increment = "log" else: raise Exception( @@ -95,24 +95,20 @@ def from_array(var, array): if var_type == "int": warnings.warn( - "When using logarithmic increments with QUA integers, the resulting values will slightly differ from the ones in numpy.logspace() because of rounding errors. \n Please use the get_equivalent_log_array() function to get the exact values taken by the QUA variable." + "When using logarithmic increments with QUA integers, the resulting values will slightly differ from the ones in numpy.logspace() because of rounding errors. \n Please use the get_equivalent_log_array() function to get the exact values taken by the QUA variable and note that the number of points may also change." ) - if not float(start).is_integer() or not float(stop).is_integer(): - raise Exception( - "When looping over a QUA int variable, the array elements must be integers." - ) if step > 1: return ( var, - float(start), - var < float(stop) * np.sqrt(float(step)), + round(start), + var < round(stop) * np.sqrt(float(step)), Cast.mul_int_by_fixed(var, float(step)), ) else: return ( var, - float(start), - var > float(stop) * np.sqrt(float(step)), + round(start), + var > round(stop) / np.sqrt(float(step)), Cast.mul_int_by_fixed(var, float(step)), ) @@ -261,24 +257,20 @@ def qua_logspace(var, start, stop, num): var_type = _get_qua_variable_type(var) if var_type == "int": warnings.warn( - "When using logarithmic increments with QUA integers, the resulting values will slightly differ from the ones in numpy.logspace() because of rounding errors. \n Please use the get_equivalent_log_array() function to get the exact values taken by the QUA variable." + "When using logarithmic increments with QUA integers, the resulting values will slightly differ from the ones in numpy.logspace() because of rounding errors. \n Please use the get_equivalent_log_array() function to get the exact values taken by the QUA variable and note that the number of points may also change." ) - if not float(start).is_integer() or not float(stop).is_integer(): - raise Exception( - "When looping over a QUA int variable, the array elements must be integers." - ) if step > 1: return ( var, - 10**start, - var < 10**stop * np.sqrt(step), + round(10**start), + var < round(10**stop * np.sqrt(step)), Cast.mul_int_by_fixed(var, float(step)), ) else: return ( var, - 10**start, - var > 10**stop * np.sqrt(step), + round(10**start), + var > round(10**stop / np.sqrt(step)), Cast.mul_int_by_fixed(var, float(step)), ) elif var_type == "fixed": @@ -305,16 +297,22 @@ def qua_logspace(var, start, stop, num): def get_equivalent_log_array(log_array): """Function returning the values taken by the QUA int variable within the logarithmic QUA `for_` loop. Because of rounding errors occuring with QUA integers, these values are not exactly the ones given by `numpy.logspace()`. + Note that the number of points may also change. :param log_array: a Python list or numpy array containing the values parametrizing the QUA `for_` loop. The spacing must be even in logarithmic scale and it cannot be a QUA array. :return: numpy array containing the values taken by the QUA int variable within the logarithmic QUA `for_` loop. """ a_log = [] - aprev = int(log_array[0]) + aprev = round(log_array[0]) step = np.mean(np.array(log_array[1:]) / np.array(log_array[:-1])) - for k in range(len(log_array)): - a_log.append(aprev) - aprev = int(aprev * step) + if step > 1: + while aprev < log_array[-1]: + a_log.append(aprev) + aprev = int(aprev * step) + else: + while aprev > log_array[-1]: + a_log.append(aprev) + aprev = int(aprev * step) return np.array(a_log) diff --git a/tests_against_server/test_loops.py b/tests_against_server/test_loops.py index 01776808..019391d7 100644 --- a/tests_against_server/test_loops.py +++ b/tests_against_server/test_loops.py @@ -95,11 +95,11 @@ def IQ_imbalance(g, phi): } -def simulate_program_and_return(config, prog, duration=2000): +def simulate_program_and_return(config, prog, duration=50000): qmm = QuantumMachinesManager(host="172.16.2.103", port=80) - qmm.close_all_quantum_machines() + # qmm.close_all_quantum_machines() # job = qmm.simulate( # config, # prog, @@ -198,6 +198,8 @@ def prog_maker(list_param): cfg = deepcopy(config) arange_param_list = [ + [np.logspace(np.log10(50), np.log10(12500), 19), "int"], + [np.logspace(np.log10(50000), np.log10(33), 72), "int"], [np.logspace(6, 4, 19), "int"], [np.logspace(3, 6, 199), "int"], [np.logspace(-3, 0, 99), "fixed"], @@ -227,10 +229,10 @@ def prog_maker(list_param): a_qua = job.result_handles.get("a").fetch_all()["value"] a_list = param[0] - assert len(a_list) == len(a_qua) if (param[1] == "int") and (np.isclose(a_list[1]/a_list[0], a_list[-1]/a_list[-2])): a_list = get_equivalent_log_array(a_list) + assert len(a_list) == len(a_qua) assert np.allclose(a_list, a_qua, atol=1e-4) print("PASSED !") @@ -276,7 +278,7 @@ def prog_maker(linspace_param): assert np.allclose(a_list, a_qua, atol=1e-4) -def test_qua_logspace(config): +def test_qua_logspace_fixed(config): def prog_maker(logspace_param): with program() as prog: a = declare(fixed) @@ -309,3 +311,36 @@ def prog_maker(logspace_param): a_list = np.logspace(param[0], param[1], param[2]) assert len(a_list) == len(a_qua) assert np.allclose(a_list, a_qua, atol=1e-4) + + +def test_qua_logspace_int(config): + def prog_maker(logspace_param): + with program() as prog: + t = declare(int) + t_st = declare_stream() + with for_( + *qua_logspace( + t, logspace_param[0], logspace_param[1], logspace_param[2] + ) + ): + play("readout", "resonator", duration=t) + save(t, t_st) + with stream_processing(): + t_st.save_all("a") + return prog + + cfg = deepcopy(config) + + arange_param_list = [ + [np.log10(500), np.log10(12500), 11], + [np.log10(5000), np.log10(125), 30], + [np.log10(40), np.log10(1001), 51], + ] + for param in arange_param_list: + print(f"Test qua_logspace with {param}:") + job = simulate_program_and_return(cfg, prog_maker(param)) + job.result_handles.wait_for_all_values() + a_qua = job.result_handles.get("a").fetch_all()["value"] + a_list = get_equivalent_log_array(np.round(np.logspace(param[0], param[1], param[2]))) + assert len(a_list) == len(a_qua) + assert np.allclose(a_list, a_qua, atol=1e-4)