diff --git a/applications/cfd/qls_for_hybrid_solvers/banded_be.py b/applications/cfd/qls_for_hybrid_solvers/banded_be.py new file mode 100644 index 000000000..5e4f746b2 --- /dev/null +++ b/applications/cfd/qls_for_hybrid_solvers/banded_be.py @@ -0,0 +1,269 @@ +from scipy.sparse import csr_matrix +from classiq import * +import numpy as np + +ANGLE_THRESHOLD = 1e-13 + +""" +Functions for treating banded block encoding +""" + + +def nonzero_diagonals(sparse_mat: csr_matrix) -> list[int]: + """ + Return a sorted list of diagonal offsets (k) for which the + diagonal of the sparse matrix is non-zero (i.e., has at least one nonzero element). + + k = 0 -> main diagonal + k > 0 -> superdiagonals + k < 0 -> subdiagonals + """ + if not isinstance(sparse_mat, csr_matrix): + raise TypeError("Input must be a scipy.sparse.csr_matrix") + + rows, cols = sparse_mat.shape + diagonals = [] + + for k in range(-rows + 1, cols): # all possible diagonals + diag = sparse_mat.diagonal(k) + if np.any(diag != 0): + diagonals.append(k) + + return diagonals + + +def extract_diagonals(csr_mat: csr_matrix, offsets: list[int]) -> list[np.ndarray]: + """extracts the diagonals of a csr matrix given a list with the offsets - minus sign means lower diagonal""" + return [csr_mat.diagonal(offset) for offset in offsets] + + +def pad_arrays( + arrays: list[np.ndarray], offsets: list[int], pad_value: int = 0 +) -> list[np.ndarray]: + """ + Pads all NumPy arrays in a list to match the length of the longest one. + For negative offsets, padding is added to the beginning of the array. + + Parameters: + - arrays (list of np.ndarray): List of NumPy arrays with different lengths. + - offsets (list of int): List of diagonal offsets, same order as arrays. + - pad_value (int, optional): The value to pad with (default is 0). + + Returns: + - list of np.ndarray: Padded NumPy arrays. + """ + if not arrays: # Handle case where list is empty + return [] + + max_length = max(len(arr) for arr in arrays) # Find max length + + padded_arrays = [] + for arr, offset in zip(arrays, offsets): + padding_length = max_length - len(arr) + if offset < 0: + # Pad at the beginning + pad_width = (padding_length, 0) + else: + # Pad at the end + pad_width = (0, padding_length) + + padded_arr = np.pad(arr, pad_width, constant_values=pad_value) + padded_arrays.append(padded_arr) + + return padded_arrays + + +def get_be_banded_data( + sparse_mat: csr_matrix, +) -> tuple[list[int], list[list[float]], list[float], float]: + offsets = nonzero_diagonals(sparse_mat) + num_q_diag = int(np.ceil(np.log2(len(offsets)))) + diags = extract_diagonals(sparse_mat, offsets) + diags = pad_arrays(diags, offsets, 0) + diags_maxima = [np.max(np.abs(d)) for d in diags] + normalized_diags = [(d / d_max).tolist() for d, d_max in zip(diags, diags_maxima)] + prepare_norm = sum(diags_maxima) + normalized_diags_maxima = [d_max / prepare_norm for d_max in diags_maxima] + [0] * ( + 2**num_q_diag - len(offsets) + ) + + return offsets, normalized_diags, normalized_diags_maxima, prepare_norm + + +""" Loading of a single diagonal with a given offset""" + + +@qfunc +def load_diagonal(offset: int, diag: list[float], ind: QBit, x: QNum) -> None: + + if offset != 0: + inplace_add(offset, x) + assign_amplitude_table(diag, x, ind) + + +@qfunc +def load_banded_diagonals( + offsets: list[int], diags: list[list[float]], ind: QBit, x: QNum, s: QNum +) -> None: + for i in range(len(offsets)): + control(s == i, lambda: load_diagonal(-offsets[i], diags[i], ind, x)) + + +@qfunc +def block_encode_banded( + offsets: list[int], + diags: list[list[float]], + prep_diag: CArray[CReal], + block: QNum, + data: QNum, +) -> None: + s = QNum(size=block.size - 1) + ind = QBit() + bind(block, [s, ind]) + within_apply( + lambda: inplace_prepare_state(prep_diag, 0.0, s), + lambda: load_banded_diagonals(offsets, diags, ind, data, s), + ) + + X(ind) + bind([s, ind], block) + + +@qfunc +def block_encode_banded_controlled( + ctrl_state: CInt, + offsets: list[int], + diags: list[list[float]], + prep_diag: CArray[CReal], + block: QNum, + data: QNum, + ctrl: QNum, +) -> None: + if offsets.len < 2 ** ((offsets.len - 1).bit_length()): + """ + Efficient controlled version when the number of diagonals is not an exact power of 2. + """ + s = QNum(size=block.size - 1) + ind = QBit() + bind(block, [s, ind]) + within_apply( + lambda: control( + ctrl == ctrl_state, + lambda: inplace_prepare_state(prep_diag, 0.0, s), + lambda: apply_to_all(X, s), + ), + lambda: load_banded_diagonals(offsets, diags, ind, data, s), + ) + control(ctrl == ctrl_state, lambda: X(ind)) + bind([s, ind], block) + else: + control( + ctrl == ctrl_state, + lambda: block_encode_banded(offsets, diags, prep_diag, block, data), + ) + + +@qfunc +def be_e3(data: QBit, block: QBit): + lcu( + coefficients=[1, 1], + unitaries=[lambda: RY(np.pi, data), lambda: X(data)], + block=block, + ) + + +@qfunc +def block_encode_banded_sym( + offsets: list[int], + diags: list[list[float]], + prep_diag: CArray[CReal], + block: QArray, + data: QArray, +) -> None: + """ + This is a simple LCU of block encoding the upper-right and lower-left block. + However, we use an explicit controlled version of block_encode_banded, given by the function + block_encode_banded_controlled. + """ + lcu_block = QBit() + sym_block = QBit() + sym_data = QBit() + reduced_block = QArray() + reduced_data = QArray() + within_apply( + lambda: ( + bind( + data, [reduced_data, sym_data] + ), # separate to different variables for clarity + bind(block, [reduced_block, sym_block, lcu_block]), + H(lcu_block), + ), + lambda: ( + control(lcu_block == 1, lambda: be_e3(sym_data, sym_block)), + block_encode_banded_controlled( + ctrl=lcu_block, + ctrl_state=1, + offsets=offsets, + diags=diags, + prep_diag=prep_diag, + block=reduced_block, + data=reduced_data, + ), + control(lcu_block == 0, lambda: invert(lambda: be_e3(sym_data, sym_block))), + invert( + lambda: block_encode_banded_controlled( + ctrl=lcu_block, + ctrl_state=0, + offsets=offsets, + diags=diags, + prep_diag=prep_diag, + block=reduced_block, + data=reduced_data, + ) + ), + ), + ) + + +def get_banded_diags_be(mat_raw_scr): + """ + Get relevant block-encoding properties for `block_encode_banded` block encoding, + + Parameters + ---------- + mat_raw_scr : scipy.sparse.spmatrix + Square sparse matrix of shape (N, N), real or complex, to be block-encoded. + + Returns + ------- + data_size : int + Size of the data variable. + block_size : int + Size of the block variable. + be_scaling_factor : float + The scaling factor of the block-encoding unitary + BlockEncodedState : QStruct + QSVT-compatible QStruct holding the quantum variables, with fields: + - data : QNum[data_size] + - block : QNum[block_size] + be_qfunc : qfunc + Quantum function that implements the block encoding. Signature: + be_qfunc(be: BlockEncodedState) → None + """ + raw_size = mat_raw_scr.shape[0] + data_size = max(1, (raw_size - 1).bit_length()) + offsets, diags, diags_maxima, prepare_norm = get_be_banded_data(mat_raw_scr) + block_size = int(np.ceil(np.log2(len(offsets)))) + 1 + be_scaling_factor = prepare_norm + + @qfunc + def be_qfunc(block: QNum, data: QNum): + block_encode_banded( + offsets=offsets, + diags=diags, + prep_diag=diags_maxima, + block=block, + data=data, + ) + + return data_size, block_size, be_scaling_factor, be_qfunc diff --git a/applications/cfd/qls_for_hybrid_solvers/banded_be.synthesis_options.json b/applications/cfd/qls_for_hybrid_solvers/banded_be.synthesis_options.json index 998b597b2..8e767d278 100644 --- a/applications/cfd/qls_for_hybrid_solvers/banded_be.synthesis_options.json +++ b/applications/cfd/qls_for_hybrid_solvers/banded_be.synthesis_options.json @@ -6,28 +6,28 @@ "preferences": { "custom_hardware_settings": { "basis_gates": [ - "sx", - "p", - "tdg", + "z", + "ry", + "h", "x", - "cz", - "u2", + "u1", "id", "sxdg", - "y", - "cy", - "s", - "ry", - "cx", - "t", - "r", - "u", + "sx", "rz", - "u1", - "z", "rx", "sdg", - "h" + "tdg", + "cy", + "cz", + "cx", + "u", + "y", + "s", + "r", + "t", + "u2", + "p" ], "is_symmetric_connectivity": true }, @@ -36,7 +36,7 @@ "optimization_level": 1, "output_format": ["qasm"], "pretty_qasm": true, - "random_seed": 2454004842, + "random_seed": 2459810836, "synthesize_all_separately": false, "timeout_seconds": 300, "transpilation_option": "auto optimize" diff --git a/applications/cfd/qls_for_hybrid_solvers/banded_sym_be.synthesis_options.json b/applications/cfd/qls_for_hybrid_solvers/banded_sym_be.synthesis_options.json index 7013d6ec7..5d6535c7a 100644 --- a/applications/cfd/qls_for_hybrid_solvers/banded_sym_be.synthesis_options.json +++ b/applications/cfd/qls_for_hybrid_solvers/banded_sym_be.synthesis_options.json @@ -6,28 +6,28 @@ "preferences": { "custom_hardware_settings": { "basis_gates": [ - "sx", - "p", - "tdg", + "z", + "ry", + "h", "x", - "cz", - "u2", + "u1", "id", "sxdg", - "y", - "cy", - "s", - "ry", - "cx", - "t", - "r", - "u", + "sx", "rz", - "u1", - "z", "rx", "sdg", - "h" + "tdg", + "cy", + "cz", + "cx", + "u", + "y", + "s", + "r", + "t", + "u2", + "p" ], "is_symmetric_connectivity": true }, @@ -36,7 +36,7 @@ "optimization_level": 1, "output_format": ["qasm"], "pretty_qasm": true, - "random_seed": 3750507641, + "random_seed": 1840897986, "synthesize_all_separately": false, "timeout_seconds": 300, "transpilation_option": "auto optimize" diff --git a/applications/cfd/qls_for_hybrid_solvers/cheb_lcu_solver_banded_be.qmod b/applications/cfd/qls_for_hybrid_solvers/cheb_lcu_solver_banded_be.qmod index 78d2266f9..8112246d0 100644 --- a/applications/cfd/qls_for_hybrid_solvers/cheb_lcu_solver_banded_be.qmod +++ b/applications/cfd/qls_for_hybrid_solvers/cheb_lcu_solver_banded_be.qmod @@ -1,7 +1,7 @@ @disable_perm_check @disable_const_checks(packed_vars) -qperm reflect_about_zero_expanded___0(const packed_vars: qbit[5]) { - msbs: qnum<4, False, 0>; +qperm reflect_about_zero_expanded___0(const packed_vars: qbit[3]) { + msbs: qnum<2, False, 0>; lsb: qbit; packed_vars -> {msbs, lsb}; within { @@ -15,57 +15,11 @@ qperm reflect_about_zero_expanded___0(const packed_vars: qbit[5]) { {msbs, lsb} -> packed_vars; } -qfunc my_reflect_about_zero_expanded___0(qba: qbit[5]) { +qfunc my_reflect_about_zero_expanded___0(qba: qbit[3]) { reflect_about_zero_expanded___0(qba); RY(6.2832, qba[0]); } -qfunc unitaries_0_lambda___0_0_expanded___0(data_captured__be_e3__6: qbit) { - RY(3.1416, data_captured__be_e3__6); -} - -qperm unitaries_1_lambda___0_0_expanded___0(data_captured__be_e3__6: qbit) { - X(data_captured__be_e3__6); -} - -qfunc select_0_lambda___0_0_expanded___0(const _block: qnum<1, False, 0>, data_captured__be_e3__6: qbit) { - repeat (i: 2) { - control (_block == i) { - if (i == 0) { - unitaries_0_lambda___0_0_expanded___0(data_captured__be_e3__6); - } else { - if (i == 1) { - unitaries_1_lambda___0_0_expanded___0(data_captured__be_e3__6); - } - } - } - } -} - -qfunc prepare_select_expanded___0(block: qnum<1, False, 0>, data_captured__be_e3__6: qbit) { - within { - inplace_prepare_state([0.5, 0.5], 0, block); - } apply { - select_0_lambda___0_0_expanded___0(block, data_captured__be_e3__6); - if (False) { - } - } -} - -qfunc lcu_expanded___0(block: qnum<1, False, 0>, data_captured__be_e3__6: qbit) { - prepare_select_expanded___0(block, data_captured__be_e3__6); -} - -qfunc be_e3_expanded___0(data: qbit, block: qbit) { - lcu_expanded___0(block, data); -} - -qperm apply_to_all_expanded___0(target: qbit[2]) { - repeat (index: 2) { - X(target[index]); - } -} - qfunc assign_amplitude_table_expanded___0(const index: qbit[3], indicator: qbit) { RY(-1.8307, indicator); skip_control { @@ -197,61 +151,35 @@ qfunc load_banded_diagonals_expanded___0(ind: qbit, x: qnum<3, False, 0>, s: qnu } } -qfunc block_encode_banded_controlled_expanded___0(ctrl_state: int, prep_diag: real[], block: qnum<3, False, 0>, data: qnum<3, False, 0>, ctrl: qnum<1, False, 0>) { +qfunc block_encode_banded_expanded___0(prep_diag: real[], block: qnum<3, False, 0>, data: qnum<3, False, 0>) { s: qnum<2, False, 0>; ind: qbit; block -> {s, ind}; within { - control (ctrl == ctrl_state) { - inplace_prepare_state(prep_diag, 0.0, s); - } else { - apply_to_all_expanded___0(s); - } + inplace_prepare_state(prep_diag, 0.0, s); } apply { load_banded_diagonals_expanded___0(ind, data, s); } - control (ctrl == ctrl_state) { - X(ind); - } + X(ind); {s, ind} -> block; } -qfunc block_encode_banded_sym_expanded___0(prep_diag: real[], block: qbit[5], data: qbit[4]) { - lcu_block: qbit; - sym_block: qbit; - sym_data: qbit; - reduced_block: qbit[3]; - reduced_data: qbit[3]; - within { - data -> {reduced_data, sym_data}; - block -> {reduced_block, sym_block, lcu_block}; - H(lcu_block); - } apply { - control (lcu_block == 1) { - be_e3_expanded___0(sym_data, sym_block); - } - block_encode_banded_controlled_expanded___0(1, prep_diag, reduced_block, reduced_data, lcu_block); - control (lcu_block == 0) { - invert { - be_e3_expanded___0(sym_data, sym_block); - } - } - invert { - block_encode_banded_controlled_expanded___0(0, prep_diag, reduced_block, reduced_data, lcu_block); - } - } +qfunc be_qfunc_expanded___0(block: qnum<3, False, 0>, data: qnum<3, False, 0>) { + block_encode_banded_expanded___0([0.2416, 0.4992, 0.2591, 0], block, data); } -qfunc be_qfunc_expanded___0(block: qnum<5, False, 0>, data: qnum<4, False, 0>) { - block_encode_banded_sym_expanded___0([0.2416, 0.4992, 0.2591, 0], block, data); +qfunc block_enc_0_lambda___0_0_expanded___0(b: qnum<3, False, 0>, d: qbit[3]) { + invert { + be_qfunc_expanded___0(b, d); + } } -qfunc walk_operator_expanded___0(block: qbit[5], data: qbit[4]) { - be_qfunc_expanded___0(block, data); +qfunc walk_operator_expanded___0(block: qbit[3], data: qbit[3]) { + block_enc_0_lambda___0_0_expanded___0(block, data); my_reflect_about_zero_expanded___0(block); } -qfunc symmetrize_walk_operator_expanded___0(block: qnum<5, False, 0>, data: qbit[4]) { +qfunc symmetrize_walk_operator_expanded___0(block: qnum<3, False, 0>, data: qbit[3]) { my_reflect_about_zero_expanded___0(block); within { walk_operator_expanded___0(block, data); @@ -260,12 +188,12 @@ qfunc symmetrize_walk_operator_expanded___0(block: qnum<5, False, 0>, data: qbit } } -qfunc lcu_cheb_expanded___0(powers: int[], inv_coeffs: real[], mat_block: qnum<5, False, 0>, data: qbit[4], cheb_block: qbit[4]) { +qfunc lcu_cheb_expanded___0(powers: int[], inv_coeffs: real[], mat_block: qnum<3, False, 0>, data: qbit[3], cheb_block: qbit[5]) { within { inplace_prepare_state(inv_coeffs, 0.0, cheb_block); } apply { Z(cheb_block[0]); - repeat (i: 4) { + repeat (i: 5) { control (cheb_block[i]) { power (powers[i]) { symmetrize_walk_operator_expanded___0(mat_block, data); @@ -277,42 +205,51 @@ qfunc lcu_cheb_expanded___0(powers: int[], inv_coeffs: real[], mat_block: qnum<5 } } -qfunc main(output matrix_block: qnum<5, False, 0>, output data: qnum<4, False, 0>, output inv_block: qnum<4, False, 0>) { - allocate(4, inv_block); - allocate(5, matrix_block); - allocate(4, data); - data_array: qbit[4]; - within { - data -> data_array; - } apply { - inplace_prepare_amplitudes([ - 0.6193, - (-0.3565), - (-0.435), - (-0.2763), - 0.1567, - 0.4192, - 0.1533, - 0.0 - ], 0.0, data_array[0:3]); - X(data_array[3]); - } - lcu_cheb_expanded___0([1, 2, 4, 8], [ - 0.0721, - 0.0708, - 0.0695, - 0.0682, - 0.0669, - 0.0657, - 0.0644, - 0.0631, - 0.0618, +qfunc main(output matrix_block: qnum<3, False, 0>, output data: qnum<3, False, 0>, output inv_block: qnum<5, False, 0>) { + allocate(5, inv_block); + allocate(3, matrix_block); + prepare_amplitudes([ + 0.6193, + (-0.3565), + (-0.435), + (-0.2763), + 0.1567, + 0.4192, + 0.1533, + 0.0 + ], 0, data); + lcu_cheb_expanded___0([1, 2, 4, 8, 16], [ 0.0606, - 0.0593, - 0.058, + 0.0587, 0.0568, - 0.0555, - 0.0543, - 0.053 + 0.0549, + 0.053, + 0.0511, + 0.0492, + 0.0473, + 0.0455, + 0.0436, + 0.0417, + 0.0398, + 0.0379, + 0.036, + 0.0341, + 0.0322, + 0.0303, + 0.0284, + 0.0265, + 0.0246, + 0.0227, + 0.0208, + 0.0189, + 0.017, + 0.0152, + 0.0133, + 0.0114, + 0.0095, + 0.0076, + 0.0057, + 0.0038, + 0.0019 ], matrix_block, data, inv_block); } diff --git a/applications/cfd/qls_for_hybrid_solvers/cheb_lcu_solver_banded_be.synthesis_options.json b/applications/cfd/qls_for_hybrid_solvers/cheb_lcu_solver_banded_be.synthesis_options.json index cde7627ee..9b7542bb4 100644 --- a/applications/cfd/qls_for_hybrid_solvers/cheb_lcu_solver_banded_be.synthesis_options.json +++ b/applications/cfd/qls_for_hybrid_solvers/cheb_lcu_solver_banded_be.synthesis_options.json @@ -6,28 +6,28 @@ "preferences": { "custom_hardware_settings": { "basis_gates": [ - "z", - "id", - "sxdg", - "sdg", "y", + "p", + "cz", + "h", + "r", + "id", "cx", + "u", + "sdg", "cy", - "u1", - "r", + "x", + "s", + "u2", "rx", - "rz", + "tdg", "t", - "ry", - "p", + "rz", "sx", - "s", - "h", - "x", - "tdg", - "cz", - "u2", - "u" + "u1", + "z", + "sxdg", + "ry" ], "is_symmetric_connectivity": true }, @@ -36,7 +36,7 @@ "optimization_level": 1, "output_format": ["qasm"], "pretty_qasm": true, - "random_seed": 6454204, + "random_seed": 4147760831, "synthesize_all_separately": false, "timeout_seconds": 300, "transpilation_option": "auto optimize" diff --git a/applications/cfd/qls_for_hybrid_solvers/cheb_lcu_solver_pauli_be.qmod b/applications/cfd/qls_for_hybrid_solvers/cheb_lcu_solver_pauli_be.qmod index 218b2aee6..98ab56ba1 100644 --- a/applications/cfd/qls_for_hybrid_solvers/cheb_lcu_solver_pauli_be.qmod +++ b/applications/cfd/qls_for_hybrid_solvers/cheb_lcu_solver_pauli_be.qmod @@ -745,292 +745,71 @@ qfunc multiplex_ra_expanded___5(qba: qbit[5], ind: qbit) { } } -qfunc multiplex_ra_expanded___6(qba: qbit[5], ind: qbit) { - RZ(-1.5708, ind); - skip_control { - CX(qba[0], ind); - } - skip_control { - CX(qba[1], ind); - } - skip_control { - CX(qba[0], ind); - } - skip_control { - CX(qba[2], ind); - } - RZ(-0.3927, ind); - skip_control { - CX(qba[0], ind); - } - skip_control { - CX(qba[1], ind); - } - skip_control { - CX(qba[0], ind); - } - RZ(-0.3927, ind); - skip_control { - CX(qba[3], ind); - } - RZ(-0.3927, ind); - skip_control { - CX(qba[0], ind); - } - skip_control { - CX(qba[1], ind); - } - skip_control { - CX(qba[0], ind); - } - RZ(0.3927, ind); - skip_control { - CX(qba[2], ind); - } - skip_control { - CX(qba[0], ind); - } - skip_control { - CX(qba[1], ind); - } - skip_control { - CX(qba[0], ind); - } - RZ(-0.7854, ind); - skip_control { - CX(qba[4], ind); - } - skip_control { - CX(qba[0], ind); - } - skip_control { - CX(qba[1], ind); - } - skip_control { - CX(qba[0], ind); - } - skip_control { - CX(qba[2], ind); - } - RZ(0.3927, ind); - skip_control { - CX(qba[0], ind); - } - skip_control { - CX(qba[1], ind); - } - skip_control { - CX(qba[0], ind); - } - RZ(0.3927, ind); - skip_control { - CX(qba[3], ind); - } - RZ(0.3927, ind); - skip_control { - CX(qba[0], ind); - } - skip_control { - CX(qba[1], ind); - } - skip_control { - CX(qba[0], ind); - } - RZ(-0.3927, ind); - skip_control { - CX(qba[2], ind); - } - skip_control { - CX(qba[0], ind); - } - skip_control { - CX(qba[1], ind); - } - skip_control { - CX(qba[0], ind); - } - RZ(-0.7854, ind); - skip_control { - CX(qba[4], ind); - } -} - -qfunc multiplex_ra_expanded___7(qba: qbit[5], ind: qbit) { - RY(1.1781, ind); - skip_control { - CX(qba[0], ind); - } - skip_control { - CX(qba[1], ind); - } - skip_control { - CX(qba[0], ind); - } - skip_control { - CX(qba[2], ind); - } - RY(0.7854, ind); - skip_control { - CX(qba[0], ind); - } - skip_control { - CX(qba[1], ind); - } - skip_control { - CX(qba[0], ind); - } - RY(0.3927, ind); - skip_control { - CX(qba[3], ind); - } - RY(0.3927, ind); - skip_control { - CX(qba[0], ind); - } - skip_control { - CX(qba[1], ind); - } - skip_control { - CX(qba[0], ind); - } - RY(-0.7854, ind); - skip_control { - CX(qba[2], ind); - } - skip_control { - CX(qba[0], ind); - } - skip_control { - CX(qba[1], ind); - } - skip_control { - CX(qba[0], ind); - } - RY(1.1781, ind); - skip_control { - CX(qba[4], ind); - } - RY(0.3927, ind); - skip_control { - CX(qba[0], ind); - } - skip_control { - CX(qba[1], ind); - } - skip_control { - CX(qba[0], ind); - } - skip_control { - CX(qba[2], ind); - } - RY(-0.7854, ind); - skip_control { - CX(qba[0], ind); - } - skip_control { - CX(qba[1], ind); - } - skip_control { - CX(qba[0], ind); - } - RY(-0.3927, ind); - skip_control { - CX(qba[3], ind); - } - RY(-0.3927, ind); - skip_control { - CX(qba[0], ind); - } - skip_control { - CX(qba[1], ind); - } - skip_control { - CX(qba[0], ind); - } - RY(0.7854, ind); - skip_control { - CX(qba[2], ind); - } - skip_control { - CX(qba[0], ind); - } - skip_control { - CX(qba[1], ind); - } - skip_control { - CX(qba[0], ind); - } - RY(0.3927, ind); - skip_control { - CX(qba[4], ind); - } -} - qperm apply_phase_table_expanded___0(target: qbit[5]) { PHASE(-0.0982, target[0]); CX(target[0], target[1]); - PHASE(0.0982, target[1]); + PHASE(-0.6872, target[1]); CX(target[0], target[1]); PHASE(-0.2945, target[1]); CX(target[1], target[2]); - PHASE(-0.4909, target[2]); + PHASE(-0.8836, target[2]); CX(target[0], target[2]); PHASE(0.6872, target[2]); CX(target[1], target[2]); - PHASE(-1.0799, target[2]); + PHASE(-0.2945, target[2]); CX(target[0], target[2]); - PHASE(0.8836, target[2]); + PHASE(0.4909, target[2]); CX(target[2], target[3]); - PHASE(1.2763, target[3]); + PHASE(0.8836, target[3]); CX(target[0], target[3]); - PHASE(-0.2945, target[3]); + PHASE(-1.0799, target[3]); CX(target[1], target[3]); PHASE(0.2945, target[3]); CX(target[0], target[3]); - PHASE(0.2945, target[3]); + PHASE(0.6872, target[3]); CX(target[2], target[3]); PHASE(-0.6872, target[3]); CX(target[0], target[3]); - PHASE(-0.6872, target[3]); + PHASE(0.0982, target[3]); CX(target[1], target[3]); PHASE(0.2945, target[3]); CX(target[0], target[3]); - PHASE(1.0799, target[3]); + PHASE(0.2945, target[3]); CX(target[3], target[4]); PHASE(-0.8836, target[4]); CX(target[0], target[4]); PHASE(0.6872, target[4]); CX(target[1], target[4]); - PHASE(-1.0799, target[4]); + PHASE(-0.2945, target[4]); CX(target[0], target[4]); PHASE(-0.2945, target[4]); CX(target[2], target[4]); - PHASE(0.6872, target[4]); + PHASE(1.0799, target[4]); CX(target[0], target[4]); PHASE(-0.0982, target[4]); CX(target[1], target[4]); - PHASE(0.0982, target[4]); - CX(target[0], target[4]); PHASE(-0.6872, target[4]); - CX(target[3], target[4]); - PHASE(-1.0799, target[4]); CX(target[0], target[4]); + PHASE(-0.2945, target[4]); + CX(target[3], target[4]); PHASE(-0.6872, target[4]); + CX(target[0], target[4]); + PHASE(0.0982, target[4]); CX(target[1], target[4]); PHASE(0.2945, target[4]); CX(target[0], target[4]); - PHASE(-0.0982, target[4]); + PHASE(-0.4909, target[4]); CX(target[2], target[4]); PHASE(0.0982, target[4]); CX(target[0], target[4]); - PHASE(-0.2945, target[4]); + PHASE(-1.0799, target[4]); CX(target[1], target[4]); PHASE(0.2945, target[4]); CX(target[0], target[4]); - PHASE(1.4726, target[4]); + PHASE(0.6872, target[4]); } -qfunc lcu_paulis_graycode_expanded___0(data: qbit[4], block: qbit[5]) { +qfunc lcu_paulis_graycode_expanded___0(data: qbit[3], block: qbit[5]) { within { inplace_prepare_state([ 0.3022, @@ -1073,22 +852,26 @@ qfunc lcu_paulis_graycode_expanded___0(data: qbit[4], block: qbit[5]) { multiplex_ra_expanded___3(block, data[1]); multiplex_ra_expanded___4(block, data[2]); multiplex_ra_expanded___5(block, data[2]); - multiplex_ra_expanded___6(block, data[3]); - multiplex_ra_expanded___7(block, data[3]); apply_phase_table_expanded___0(block); } } -qfunc be_qfunc_expanded___0(block: qnum<5, False, 0>, data: qnum<4, False, 0>) { +qfunc be_qfunc_expanded___0(block: qnum<5, False, 0>, data: qnum<3, False, 0>) { lcu_paulis_graycode_expanded___0(data, block); } -qfunc walk_operator_expanded___0(block: qbit[5], data: qbit[4]) { - be_qfunc_expanded___0(block, data); +qfunc block_enc_0_lambda___0_0_expanded___0(b: qnum<5, False, 0>, d: qbit[3]) { + invert { + be_qfunc_expanded___0(b, d); + } +} + +qfunc walk_operator_expanded___0(block: qbit[5], data: qbit[3]) { + block_enc_0_lambda___0_0_expanded___0(block, data); my_reflect_about_zero_expanded___0(block); } -qfunc symmetrize_walk_operator_expanded___0(block: qnum<5, False, 0>, data: qbit[4]) { +qfunc symmetrize_walk_operator_expanded___0(block: qnum<5, False, 0>, data: qbit[3]) { my_reflect_about_zero_expanded___0(block); within { walk_operator_expanded___0(block, data); @@ -1097,12 +880,12 @@ qfunc symmetrize_walk_operator_expanded___0(block: qnum<5, False, 0>, data: qbit } } -qfunc lcu_cheb_expanded___0(powers: int[], inv_coeffs: real[], mat_block: qnum<5, False, 0>, data: qbit[4], cheb_block: qbit[4]) { +qfunc lcu_cheb_expanded___0(powers: int[], inv_coeffs: real[], mat_block: qnum<5, False, 0>, data: qbit[3], cheb_block: qbit[5]) { within { inplace_prepare_state(inv_coeffs, 0.0, cheb_block); } apply { Z(cheb_block[0]); - repeat (i: 4) { + repeat (i: 5) { control (cheb_block[i]) { power (powers[i]) { symmetrize_walk_operator_expanded___0(mat_block, data); @@ -1114,42 +897,51 @@ qfunc lcu_cheb_expanded___0(powers: int[], inv_coeffs: real[], mat_block: qnum<5 } } -qfunc main(output matrix_block: qnum<5, False, 0>, output data: qnum<4, False, 0>, output inv_block: qnum<4, False, 0>) { - allocate(4, inv_block); +qfunc main(output matrix_block: qnum<5, False, 0>, output data: qnum<3, False, 0>, output inv_block: qnum<5, False, 0>) { + allocate(5, inv_block); allocate(5, matrix_block); - allocate(4, data); - data_array: qbit[4]; - within { - data -> data_array; - } apply { - inplace_prepare_amplitudes([ - 0.6193, - (-0.3565), - (-0.435), - (-0.2763), - 0.1567, - 0.4192, - 0.1533, - 0.0 - ], 0.0, data_array[0:3]); - X(data_array[3]); - } - lcu_cheb_expanded___0([1, 2, 4, 8], [ - 0.0763, - 0.0745, - 0.0726, - 0.0707, - 0.0689, - 0.067, - 0.0652, - 0.0633, - 0.0615, - 0.0597, - 0.0579, - 0.0561, - 0.0543, - 0.0525, - 0.0507, - 0.049 + prepare_amplitudes([ + 0.6193, + (-0.3565), + (-0.435), + (-0.2763), + 0.1567, + 0.4192, + 0.1533, + 0.0 + ], 0, data); + lcu_cheb_expanded___0([1, 2, 4, 8, 16], [ + 0.0606, + 0.0587, + 0.0568, + 0.0549, + 0.053, + 0.0511, + 0.0492, + 0.0473, + 0.0455, + 0.0436, + 0.0417, + 0.0398, + 0.0379, + 0.036, + 0.0341, + 0.0322, + 0.0303, + 0.0284, + 0.0265, + 0.0246, + 0.0227, + 0.0208, + 0.0189, + 0.017, + 0.0152, + 0.0133, + 0.0114, + 0.0095, + 0.0076, + 0.0057, + 0.0038, + 0.0019 ], matrix_block, data, inv_block); } diff --git a/applications/cfd/qls_for_hybrid_solvers/cheb_lcu_solver_pauli_be.synthesis_options.json b/applications/cfd/qls_for_hybrid_solvers/cheb_lcu_solver_pauli_be.synthesis_options.json index 3b5c7be9c..09a29132f 100644 --- a/applications/cfd/qls_for_hybrid_solvers/cheb_lcu_solver_pauli_be.synthesis_options.json +++ b/applications/cfd/qls_for_hybrid_solvers/cheb_lcu_solver_pauli_be.synthesis_options.json @@ -6,28 +6,28 @@ "preferences": { "custom_hardware_settings": { "basis_gates": [ - "z", - "id", - "sxdg", - "sdg", "y", + "p", + "cz", + "h", + "r", + "id", "cx", + "u", + "sdg", "cy", - "u1", - "r", + "x", + "s", + "u2", "rx", - "rz", + "tdg", "t", - "ry", - "p", + "rz", "sx", - "s", - "h", - "x", - "tdg", - "cz", - "u2", - "u" + "u1", + "z", + "sxdg", + "ry" ], "is_symmetric_connectivity": true }, @@ -36,7 +36,7 @@ "optimization_level": 1, "output_format": ["qasm"], "pretty_qasm": true, - "random_seed": 1179536095, + "random_seed": 4083024099, "synthesize_all_separately": false, "timeout_seconds": 300, "transpilation_option": "auto optimize" diff --git a/applications/cfd/qls_for_hybrid_solvers/cheb_utils.py b/applications/cfd/qls_for_hybrid_solvers/cheb_utils.py index 74baa361f..b84736232 100644 --- a/applications/cfd/qls_for_hybrid_solvers/cheb_utils.py +++ b/applications/cfd/qls_for_hybrid_solvers/cheb_utils.py @@ -41,7 +41,7 @@ def reciprocal_approx(x, w_min, B, scale): def get_numpy_cheb_interpolate(w_min, B, scale, degree): # Numpy interpolation - degree = (degree // 2) * 2 + degree = (degree // 2) * 2 + 2 poly = Chebyshev.interpolate( lambda x: reciprocal_approx(x, w_min, B, scale), degree, domain=[-1, 1] ) diff --git a/applications/cfd/qls_for_hybrid_solvers/classical_functions_be.py b/applications/cfd/qls_for_hybrid_solvers/classical_functions_be.py index 14077312e..bccd2f8f9 100644 --- a/applications/cfd/qls_for_hybrid_solvers/classical_functions_be.py +++ b/applications/cfd/qls_for_hybrid_solvers/classical_functions_be.py @@ -1,31 +1,4 @@ -from openfermion import QubitOperator import numpy as np -from scipy.sparse import csr_matrix -from classiq import SparsePauliOp, SparsePauliTerm, IndexedPauli, Pauli - -from typing import cast -from openfermion.utils.operator_utils import count_qubits - - -# TODO: remove after bug fix -def of_op_to_cl_op(qubit_op: QubitOperator) -> SparsePauliOp: - n_qubits = cast(int, count_qubits(qubit_op)) - return SparsePauliOp( - terms=[ - SparsePauliTerm( - paulis=[ # type:ignore[arg-type] - IndexedPauli( - pauli=getattr(Pauli, pauli), - index=qubit, - ) - for qubit, pauli in term - ], - coefficient=coeff, - ) - for term, coeff in qubit_op.terms.items() - ], - num_qubits=n_qubits, - ) def get_projected_state_vector( # type: ignore[no-untyped-def] @@ -53,251 +26,9 @@ def get_projected_state_vector( # type: ignore[no-untyped-def] return np.real(proj_statevector / np.exp(1j * global_phase)) -""" -Functions for treating Pauli decomposition -""" - - -def elementary_to_pauli(e_basis, k): - if e_basis == 0: - return QubitOperator(" ", 1 / 2) + QubitOperator(f"Z{k}", 1 / 2) - elif e_basis == 1: - return QubitOperator(f"X{k}", 1 / 2) + QubitOperator(f"Y{k}", 1j / 2) - elif e_basis == 2: - return QubitOperator(f"X{k}", 1 / 2) + QubitOperator(f"Y{k}", -1j / 2) - else: - return QubitOperator(" ", 1 / 2) + QubitOperator(f"Z{k}", -1 / 2) - - -# This function returns the difference in the binary representation, and in turn, [0,1,2,3] which corresponds to the elementry basis -def binary_diff_to_elementary(b1: str, b2: str) -> np.ndarray: - return 2 * np.array(list(b1), dtype=int) + np.array(list(b2), dtype=int) - - -def initialize_paulis_from_csr( - rowstt: np.ndarray, col: np.ndarray, nq: int, to_symmetrize: bool = True -): - transform_matrix: list[list[complex]] = list() - coe_size = rowstt[-1::][0] - terms_list: list[tuple()] = list() - - scr_entry = 0 - for i in range(len(rowstt) - 1): - for j in range(rowstt[i], rowstt[i + 1]): - - b1 = np.binary_repr(i, width=nq)[::-1] - b2 = np.binary_repr(col[j], width=nq)[::-1] - diffarray = binary_diff_to_elementary( - b1, b2 - ) # this is an array of size N, contains 0,1,2, or 3. Each corresponds to elementry matrix - - entry_decomposed = np.prod( - [elementary_to_pauli(e_basis, k) for k, e_basis in enumerate(diffarray)] - ) - for term, value in entry_decomposed.terms.items(): - if term in terms_list: - # Update the coefficient of the existing term - index = terms_list.index(term) - transform_matrix[index][scr_entry] += value - else: - # Add the new term and its coefficient - new_raw = [0.0j] * coe_size - new_raw[scr_entry] = value - terms_list.append(term) - transform_matrix.append(new_raw) - - scr_entry += 1 - - if not to_symmetrize: - return terms_list.copy(), transform_matrix.copy() - - else: - paulis_sym_list = list() - transform_matrix_sym = list() - for term, coe in zip(terms_list, transform_matrix): - if [p[1] for p in term].count("Y") % 2 == 0: - paulis_sym_list.append(((nq, "X"),) + tuple((p[0], p[1]) for p in term)) - transform_matrix_sym.append((np.real(coe)).tolist()) - else: - paulis_sym_list.append(((nq, "Y"),) + tuple((p[0], p[1]) for p in term)) - transform_matrix_sym.append((np.imag(coe)).tolist()) - - return paulis_sym_list.copy(), transform_matrix_sym.copy() - - -# This function returns an evaluation of a symbolic pauli decomposition given a csr entries -def eval_pauli_op( - paulis_list: list[tuple()], - transform_mat: np.ndarray, - rval: np.ndarray, - precision: float = 1e-50, -) -> QubitOperator: - coes_list = np.array(transform_mat) @ np.array(rval) - return sum( - [ - QubitOperator(term, coe) - for term, coe in zip(paulis_list, coes_list) - if abs(coe) >= precision - ] - ) - - -def trim_hamiltonian(hamiltonian, relative_threshold, jump_threshold=1.1): - """ - Trim a Hamiltonian by keeping only Pauli terms with magnitude ≥ a data-driven cutoff. - - The coefficients are sorted (descending), “big jumps” are detected via - ratio ≥ `jump_threshold`, and contiguous value-intervals are formed. A - cutoff is chosen as the right edge of the interval that contains - `relative_threshold * second_largest_coeff` (or the interval immediately - to its left if the value lies between intervals). Terms with |coeff| > - this cutoff are retained. - - Parameters - ---------- - hamiltonian : SparsePauliOp - The Hamiltonian to trim. - relative_threshold : float - Multiplier applied to the 2nd largest coefficient to form the probe value. - jump_threshold : float, optional - Consecutive-coefficient ratio that defines a “big jump” (default 1.1). - - Returns - ------- - SparsePauliOp - New Hamiltonian containing only the retained terms. - """ - - # sort pauli terms - pauli_coe = np.array( - [np.abs(term.coefficient) for term in hamiltonian.terms], dtype=float - ) - sort_ind = np.argsort(pauli_coe)[::-1] - pauli_coe_sorted = pauli_coe[sort_ind] - - # relative error is w.r.t. the 2nd term (skip the 1st if it has a big jump) - relative_error = relative_threshold * pauli_coe_sorted[1] - - # jumps and their indices - jumps = pauli_coe_sorted[:-1] / pauli_coe_sorted[1:] - cut_idx = np.flatnonzero(jumps >= jump_threshold) # cut is between i and i+1 - - # left/right boundary values at the jump locations - left_points = pauli_coe_sorted[:-1][cut_idx] - right_points = pauli_coe_sorted[1:][cut_idx] - - # Build value-intervals from the cuts (inclusive) - # indices [0..cut0], [cut0+1..cut1], ..., [last_cut+1..N-1] - starts = np.r_[0, cut_idx + 1] - ends = np.r_[cut_idx, len(pauli_coe_sorted) - 1] - L = pauli_coe_sorted[starts] # interval left (larger) - R = pauli_coe_sorted[ends] # interval right (smaller) - - # Choose the right point according to the following rule: - # - if relative_error inside an interval [R[i], L[i]] -> pick R[i] - # - else (between intervals) -> pick R of the interval to its left - within = (relative_error <= L) & (relative_error >= R) - if np.any(within): - chosen_right = R[np.argmax(within)] - else: - # find largest i with L[i] >= relative_error (L is descending) - i_left = np.searchsorted(-L, -relative_error, side="left") - 1 - i_left = max(i_left, 0) # clamp to first interval if before all - chosen_right = R[i_left] - - trimmed_hamiltonian = SparsePauliOp( - terms=[ - term - for term in hamiltonian.terms - if np.abs(term.coefficient) >= chosen_right - ], - num_qubits=hamiltonian.num_qubits, - ) - - return trimmed_hamiltonian - - -""" -Functions for treating banded block encoding -""" - - -def nonzero_diagonals(sparse_mat: csr_matrix) -> list[int]: - """ - Return a sorted list of diagonal offsets (k) for which the - diagonal of the sparse matrix is non-zero (i.e., has at least one nonzero element). - - k = 0 -> main diagonal - k > 0 -> superdiagonals - k < 0 -> subdiagonals - """ - if not isinstance(sparse_mat, csr_matrix): - raise TypeError("Input must be a scipy.sparse.csr_matrix") - - rows, cols = sparse_mat.shape - diagonals = [] - - for k in range(-rows + 1, cols): # all possible diagonals - diag = sparse_mat.diagonal(k) - if np.any(diag != 0): - diagonals.append(k) - - return diagonals - - -def extract_diagonals(csr_mat: csr_matrix, offsets: list[int]) -> list[np.ndarray]: - """extracts the diagonals of a csr matrix given a list with the offsets - minus sign means lower diagonal""" - return [csr_mat.diagonal(offset) for offset in offsets] - - -def pad_arrays( - arrays: list[np.ndarray], offsets: list[int], pad_value: int = 0 -) -> list[np.ndarray]: - """ - Pads all NumPy arrays in a list to match the length of the longest one. - For negative offsets, padding is added to the beginning of the array. - - Parameters: - - arrays (list of np.ndarray): List of NumPy arrays with different lengths. - - offsets (list of int): List of diagonal offsets, same order as arrays. - - pad_value (int, optional): The value to pad with (default is 0). - - Returns: - - list of np.ndarray: Padded NumPy arrays. - """ - if not arrays: # Handle case where list is empty - return [] - - max_length = max(len(arr) for arr in arrays) # Find max length - - padded_arrays = [] - for arr, offset in zip(arrays, offsets): - padding_length = max_length - len(arr) - if offset < 0: - # Pad at the beginning - pad_width = (padding_length, 0) - else: - # Pad at the end - pad_width = (0, padding_length) - - padded_arr = np.pad(arr, pad_width, constant_values=pad_value) - padded_arrays.append(padded_arr) - - return padded_arrays - - -def get_be_banded_data( - sparse_mat: csr_matrix, -) -> tuple[list[int], list[list[float]], list[float], float]: - offsets = nonzero_diagonals(sparse_mat) - num_q_diag = int(np.ceil(np.log2(len(offsets)))) - diags = extract_diagonals(sparse_mat, offsets) - diags = pad_arrays(diags, offsets, 0) - diags_maxima = [np.max(np.abs(d)) for d in diags] - normalized_diags = [(d / d_max).tolist() for d, d_max in zip(diags, diags_maxima)] - prepare_norm = sum(diags_maxima) - normalized_diags_maxima = [d_max / prepare_norm for d_max in diags_maxima] + [0] * ( - 2**num_q_diag - len(offsets) - ) - - return offsets, normalized_diags, normalized_diags_maxima, prepare_norm +def get_svd_range(mat_raw_scr): + mat_raw = mat_raw_scr.toarray() + svd = np.linalg.svd(mat_raw)[1] + w_min = min(svd) + w_max = max(svd) + return w_min, w_max diff --git a/applications/cfd/qls_for_hybrid_solvers/pauli_be.py b/applications/cfd/qls_for_hybrid_solvers/pauli_be.py new file mode 100644 index 000000000..9262c419c --- /dev/null +++ b/applications/cfd/qls_for_hybrid_solvers/pauli_be.py @@ -0,0 +1,334 @@ +from openfermion import QubitOperator +from classiq import SparsePauliOp +from typing import cast +from openfermion.utils.operator_utils import count_qubits +from sympy import fwht +from classiq import * +import numpy as np +from classiq.open_library.functions.state_preparation import apply_phase_table + + +# TODO: remove after bug fix +def of_op_to_cl_op(qubit_op: QubitOperator) -> SparsePauliOp: + n_qubits = cast(int, count_qubits(qubit_op)) + return SparsePauliOp( + terms=[ + SparsePauliTerm( + paulis=[ # type:ignore[arg-type] + IndexedPauli( + pauli=getattr(Pauli, pauli), + index=qubit, + ) + for qubit, pauli in term + ], + coefficient=coeff, + ) + for term, coeff in qubit_op.terms.items() + ], + num_qubits=n_qubits, + ) + + +""" +Functions for treating Pauli decomposition +""" + + +def elementary_to_pauli(e_basis, k): + if e_basis == 0: + return QubitOperator(" ", 1 / 2) + QubitOperator(f"Z{k}", 1 / 2) + elif e_basis == 1: + return QubitOperator(f"X{k}", 1 / 2) + QubitOperator(f"Y{k}", 1j / 2) + elif e_basis == 2: + return QubitOperator(f"X{k}", 1 / 2) + QubitOperator(f"Y{k}", -1j / 2) + else: + return QubitOperator(" ", 1 / 2) + QubitOperator(f"Z{k}", -1 / 2) + + +# This function returns the difference in the binary representation, and in turn, [0,1,2,3] which corresponds to the elementry basis +def binary_diff_to_elementary(b1: str, b2: str) -> np.ndarray: + return 2 * np.array(list(b1), dtype=int) + np.array(list(b2), dtype=int) + + +def initialize_paulis_from_csr( + rowstt: np.ndarray, col: np.ndarray, nq: int, to_symmetrize: bool = True +): + transform_matrix: list[list[complex]] = list() + coe_size = rowstt[-1::][0] + terms_list: list[tuple()] = list() + + scr_entry = 0 + for i in range(len(rowstt) - 1): + for j in range(rowstt[i], rowstt[i + 1]): + + b1 = np.binary_repr(i, width=nq)[::-1] + b2 = np.binary_repr(col[j], width=nq)[::-1] + diffarray = binary_diff_to_elementary( + b1, b2 + ) # this is an array of size N, contains 0,1,2, or 3. Each corresponds to elementry matrix + + entry_decomposed = np.prod( + [elementary_to_pauli(e_basis, k) for k, e_basis in enumerate(diffarray)] + ) + for term, value in entry_decomposed.terms.items(): + if term in terms_list: + # Update the coefficient of the existing term + index = terms_list.index(term) + transform_matrix[index][scr_entry] += value + else: + # Add the new term and its coefficient + new_raw = [0.0j] * coe_size + new_raw[scr_entry] = value + terms_list.append(term) + transform_matrix.append(new_raw) + + scr_entry += 1 + + if not to_symmetrize: + return terms_list.copy(), transform_matrix.copy() + + else: + paulis_sym_list = list() + transform_matrix_sym = list() + for term, coe in zip(terms_list, transform_matrix): + if [p[1] for p in term].count("Y") % 2 == 0: + paulis_sym_list.append(((nq, "X"),) + tuple((p[0], p[1]) for p in term)) + transform_matrix_sym.append((np.real(coe)).tolist()) + else: + paulis_sym_list.append(((nq, "Y"),) + tuple((p[0], p[1]) for p in term)) + transform_matrix_sym.append((np.imag(coe)).tolist()) + + return paulis_sym_list.copy(), transform_matrix_sym.copy() + + +# This function returns an evaluation of a symbolic pauli decomposition given a csr entries +def eval_pauli_op( + paulis_list: list[tuple()], + transform_mat: np.ndarray, + rval: np.ndarray, + precision: float = 1e-50, +) -> QubitOperator: + coes_list = np.array(transform_mat) @ np.array(rval) + return sum( + [ + QubitOperator(term, coe) + for term, coe in zip(paulis_list, coes_list) + if abs(coe) >= precision + ] + ) + + +def trim_hamiltonian(hamiltonian, relative_threshold, jump_threshold=1.1): + """ + Trim a Hamiltonian by keeping only Pauli terms with magnitude ≥ a data-driven cutoff. + + The coefficients are sorted (descending), “big jumps” are detected via + ratio ≥ `jump_threshold`, and contiguous value-intervals are formed. A + cutoff is chosen as the right edge of the interval that contains + `relative_threshold * second_largest_coeff` (or the interval immediately + to its left if the value lies between intervals). Terms with |coeff| > + this cutoff are retained. + + Parameters + ---------- + hamiltonian : SparsePauliOp + The Hamiltonian to trim. + relative_threshold : float + Multiplier applied to the 2nd largest coefficient to form the probe value. + jump_threshold : float, optional + Consecutive-coefficient ratio that defines a “big jump” (default 1.1). + + Returns + ------- + SparsePauliOp + New Hamiltonian containing only the retained terms. + """ + + # sort pauli terms + pauli_coe = np.array( + [np.abs(term.coefficient) for term in hamiltonian.terms], dtype=float + ) + sort_ind = np.argsort(pauli_coe)[::-1] + pauli_coe_sorted = pauli_coe[sort_ind] + + # relative error is w.r.t. the 2nd term (skip the 1st if it has a big jump) + relative_error = relative_threshold * pauli_coe_sorted[1] + + # jumps and their indices + jumps = pauli_coe_sorted[:-1] / pauli_coe_sorted[1:] + cut_idx = np.flatnonzero(jumps >= jump_threshold) # cut is between i and i+1 + + # Build value-intervals from the cuts (inclusive) + # indices [0..cut0], [cut0+1..cut1], ..., [last_cut+1..N-1] + starts = np.r_[0, cut_idx + 1] + ends = np.r_[cut_idx, len(pauli_coe_sorted) - 1] + L = pauli_coe_sorted[starts] # interval left (larger) + R = pauli_coe_sorted[ends] # interval right (smaller) + + # Choose the right point according to the following rule: + # - if relative_error inside an interval [R[i], L[i]] -> pick R[i] + # - else (between intervals) -> pick R of the interval to its left + within = (relative_error <= L) & (relative_error >= R) + if np.any(within): + chosen_right = R[np.argmax(within)] + else: + # find largest i with L[i] >= relative_error (L is descending) + i_left = np.searchsorted(-L, -relative_error, side="left") - 1 + i_left = max(i_left, 0) # clamp to first interval if before all + chosen_right = R[i_left] + + trimmed_hamiltonian = SparsePauliOp( + terms=[ + term + for term in hamiltonian.terms + if np.abs(term.coefficient) >= chosen_right + ], + num_qubits=hamiltonian.num_qubits, + ) + + return trimmed_hamiltonian + + +ANGLE_THRESHOLD = 1e-13 + + +def get_graycode(size, i) -> int: + if i == 2**size: + return get_graycode(size, 0) + return i ^ (i >> 1) + + +def get_graycode_angles_wh(size, angles): + transformed_angles = fwht(np.array(angles) / 2**size) + return [transformed_angles[get_graycode(size, j)] for j in range(2**size)] + + +def get_graycode_ctrls(size): + return [ + (get_graycode(size, i) ^ get_graycode(size, i + 1)).bit_length() - 1 + for i in range(2**size) + ] + + +@qfunc +def multiplex_ra(a_y: float, a_z: float, angles: list[float], qba: QArray, ind: QBit): + assert a_y**2 + a_z**2 == 1 + # TODO support general (0,a_y,a_z) rotation + assert ( + a_z == 1.0 or a_y == 1.0 + ), "currently only strict y or z rotations are supported" + size = max(1, (len(angles) - 1).bit_length()) + extended_angles = angles + [0] * (2**size - len(angles)) + transformed_angles = get_graycode_angles_wh(size, extended_angles) + controllers = get_graycode_ctrls(size) + + for k in range(2**size): + if np.abs(transformed_angles[k]) > ANGLE_THRESHOLD: + if a_z == 0.0: + RY(transformed_angles[k], ind) + else: + RZ(transformed_angles[k], ind) + + skip_control(lambda: CX(qba[controllers[k]], ind)) + + +@qfunc +def lcu_paulis_graycode(terms: list[SparsePauliTerm], data: QArray, block: QArray): + n_qubits = data.len + n_terms = len(terms) + table_z = np.zeros([n_qubits, n_terms]) + table_y = np.zeros([n_qubits, n_terms]) + probs = [abs(term.coefficient) for term in terms] + [0.0] * (2**block.len - n_terms) + hamiltonian_coeffs = np.angle([term.coefficient for term in terms]).tolist() + [ + 0.0 + ] * (2**block.len - n_terms) + accumulated_phase = np.zeros(2**block.len).tolist() + + for k in range(n_terms): + for pauli in terms[k].paulis: + if pauli.pauli == Pauli.Z: + table_z[pauli.index, k] = -np.pi + accumulated_phase[k] += np.pi / 2 + elif pauli.pauli == Pauli.Y: + table_y[pauli.index, k] = -np.pi + accumulated_phase[k] += np.pi / 2 + elif pauli.pauli == Pauli.X: + table_z[pauli.index, k] = -np.pi + table_y[pauli.index, k] = np.pi + accumulated_phase[k] += np.pi / 2 + + def select_graycode(block: QArray, data: QArray): + for i in range(n_qubits): + multiplex_ra(0, 1, table_z[i, :], block, data[i]) + multiplex_ra(1, 0, table_y[i, :], block, data[i]) + apply_phase_table( + [p1 - p2 for p1, p2 in zip(hamiltonian_coeffs, accumulated_phase)], block + ) + + within_apply( + lambda: inplace_prepare_state(probs, 0.0, block), + lambda: select_graycode(block, data), + ) + + +def get_pauli_be(mat_raw_scr, pauli_trim_rel_tol=0.1): + """ + Get relevant block-encoding properties for `lcu_paulis_graycode` block encoding, + + Parameters + ---------- + mat_raw_scr : scipy.sparse.spmatrix + Square sparse matrix of shape (N, N), real or complex, to be block-encoded. + + Returns + ------- + data_size : int + Size of the data variable. + block_size : int + Size of the block variable. + be_scaling_factor : float + The scaling factor of the block-encoding unitary + BlockEncodedState : QStruct + QSVT-compatible QStruct holding the quantum variables, with fields: + - data : QNum[data_size] + - block : QNum[block_size] + be_qfunc : qfunc + Quantum function that implements the block encoding. Signature: + be_qfunc(be: BlockEncodedState) → None + """ + rval = mat_raw_scr.data + col = mat_raw_scr.indices + rowstt = mat_raw_scr.indptr + nr = mat_raw_scr.shape[0] + + raw_size = mat_raw_scr.shape[0] + data_size = max(1, (raw_size - 1).bit_length()) + + # Set to_symmetrize=False, since we are working with QSVT + paulis_list, transform_matrix = initialize_paulis_from_csr( + rowstt, col, data_size, to_symmetrize=False + ) + + qubit_op = eval_pauli_op(paulis_list, transform_matrix, rval) + qubit_op.compress(1e-12) + hamiltonian = of_op_to_cl_op(qubit_op) + hamiltonian_trimmed = trim_hamiltonian( + hamiltonian, pauli_trim_rel_tol, jump_threshold=1.1 + ) + + be_scaling_factor = sum( + [np.abs(term.coefficient) for term in hamiltonian_trimmed.terms] + ) + block_size = max(1, (len(hamiltonian_trimmed.terms) - 1).bit_length()) + + hamiltonian_trimmed = hamiltonian_trimmed * (1 / be_scaling_factor) + + print( + f"number of Paulis before/after trimming {len(hamiltonian.terms)}/{len(hamiltonian_trimmed.terms)}" + ) + + @qfunc + def be_qfunc(block: QNum, data: QNum): + lcu_paulis_graycode(hamiltonian_trimmed.terms, data, block) + + return data_size, block_size, be_scaling_factor, be_qfunc diff --git a/applications/cfd/qls_for_hybrid_solvers/pauli_be.synthesis_options.json b/applications/cfd/qls_for_hybrid_solvers/pauli_be.synthesis_options.json index c0d0cec5f..168e2a820 100644 --- a/applications/cfd/qls_for_hybrid_solvers/pauli_be.synthesis_options.json +++ b/applications/cfd/qls_for_hybrid_solvers/pauli_be.synthesis_options.json @@ -6,28 +6,28 @@ "preferences": { "custom_hardware_settings": { "basis_gates": [ - "sx", - "p", - "tdg", + "z", + "ry", + "h", "x", - "cz", - "u2", + "u1", "id", "sxdg", - "y", - "cy", - "s", - "ry", - "cx", - "t", - "r", - "u", + "sx", "rz", - "u1", - "z", "rx", "sdg", - "h" + "tdg", + "cy", + "cz", + "cx", + "u", + "y", + "s", + "r", + "t", + "u2", + "p" ], "is_symmetric_connectivity": true }, @@ -36,7 +36,7 @@ "optimization_level": 1, "output_format": ["qasm"], "pretty_qasm": true, - "random_seed": 808389605, + "random_seed": 2133197498, "synthesize_all_separately": false, "timeout_seconds": 300, "transpilation_option": "auto optimize" diff --git a/applications/cfd/qls_for_hybrid_solvers/pauli_sym_be.synthesis_options.json b/applications/cfd/qls_for_hybrid_solvers/pauli_sym_be.synthesis_options.json index dd16f6ba9..88e7bf1db 100644 --- a/applications/cfd/qls_for_hybrid_solvers/pauli_sym_be.synthesis_options.json +++ b/applications/cfd/qls_for_hybrid_solvers/pauli_sym_be.synthesis_options.json @@ -6,28 +6,28 @@ "preferences": { "custom_hardware_settings": { "basis_gates": [ - "sx", - "p", - "tdg", + "z", + "ry", + "h", "x", - "cz", - "u2", + "u1", "id", "sxdg", - "y", - "cy", - "s", - "ry", - "cx", - "t", - "r", - "u", + "sx", "rz", - "u1", - "z", "rx", "sdg", - "h" + "tdg", + "cy", + "cz", + "cx", + "u", + "y", + "s", + "r", + "t", + "u2", + "p" ], "is_symmetric_connectivity": true }, @@ -36,7 +36,7 @@ "optimization_level": 1, "output_format": ["qasm"], "pretty_qasm": true, - "random_seed": 1892445325, + "random_seed": 1015238994, "synthesize_all_separately": false, "timeout_seconds": 300, "transpilation_option": "auto optimize" diff --git a/applications/cfd/qls_for_hybrid_solvers/qls_chebyshev_lcu.ipynb b/applications/cfd/qls_for_hybrid_solvers/qls_chebyshev_lcu.ipynb index 73dbcbc16..c5a5f2267 100644 --- a/applications/cfd/qls_for_hybrid_solvers/qls_chebyshev_lcu.ipynb +++ b/applications/cfd/qls_for_hybrid_solvers/qls_chebyshev_lcu.ipynb @@ -35,14 +35,17 @@ "\n", "import matplotlib.pyplot as plt\n", "import numpy as np\n", + "from banded_be import get_banded_diags_be\n", "from cheb_utils import *\n", - "from classical_functions_be import *\n", - "from quantum_functions_be import *\n", + "from classical_functions_be import get_projected_state_vector, get_svd_range\n", + "from pauli_be import get_pauli_be\n", "from scipy import sparse\n", "\n", "from classiq import *\n", "\n", - "np.random.seed(53)" + "np.random.seed(53)\n", + "\n", + "PAULI_TRIM_REL_TOL = 0.1" ] }, { @@ -112,7 +115,7 @@ "id": "4", "metadata": {}, "source": [ - "Define functions to get properties of the block-encoding." + "The solvers were developed in the framework of exploring their performance in hybrid CFD schemes. For simplicity, it is assumed that all the properties of the matrices are known explicitly. In particular, we calculate its sigular values for identyfing the range in which we apply the inversion polnomial." ] }, { @@ -121,152 +124,13 @@ "id": "5", "metadata": {}, "outputs": [], - "source": [ - "PAULI_TRIM_REL_TOL = 0.1\n", - "\n", - "\n", - "def get_pauli_sym_be(mat_raw_scr):\n", - " \"\"\"\n", - " Get relevant block-encoding properties for `lcu_paulis_graycode` block encoding,\n", - " with matrix symmetrization.\n", - "\n", - " Parameters\n", - " ----------\n", - " mat_raw_scr : scipy.sparse.spmatrix\n", - " Square sparse matrix of shape (N, N), real or complex, to be block-encoded.\n", - "\n", - " Returns\n", - " -------\n", - " data_size : int\n", - " Size of the data variable.\n", - " block_size : int\n", - " Size of the block variable.\n", - " be_scaling_factor : float\n", - " The scaling factor of the block-encoding unitary\n", - " be_qfunc : qfunc\n", - " Quantum function that implements the block encoding. Signature:\n", - " be_qfunc(block: QNum, data: QNum) → None\n", - " \"\"\"\n", - "\n", - " rval = mat_raw_scr.data\n", - " col = mat_raw_scr.indices\n", - " rowstt = mat_raw_scr.indptr\n", - " nr = mat_raw_scr.shape[0]\n", - " data_size = int(np.log2(nr))\n", - "\n", - " # decompose to Paulis with symmetrizing\n", - " paulis_list, transform_matrix = initialize_paulis_from_csr(\n", - " rowstt, col, data_size, to_symmetrize=True\n", - " )\n", - " data_size += 1 # in the symmetric case the data size is increased by 1\n", - "\n", - " qubit_op = eval_pauli_op(paulis_list, transform_matrix, rval)\n", - " qubit_op.compress(1e-12)\n", - " hamiltonian = of_op_to_cl_op(qubit_op)\n", - " hamiltonian_trimmed = trim_hamiltonian(\n", - " hamiltonian, PAULI_TRIM_REL_TOL, jump_threshold=1.1\n", - " )\n", - "\n", - " be_scaling_factor = sum(\n", - " [np.abs(term.coefficient) for term in hamiltonian_trimmed.terms]\n", - " )\n", - " block_size = size = max(1, (len(hamiltonian_trimmed.terms) - 1).bit_length())\n", - "\n", - " hamiltonian_trimmed = hamiltonian_trimmed * (1 / be_scaling_factor)\n", - "\n", - " print(\n", - " f\"number of Paulis before/after trimming {len(hamiltonian.terms)}/{len(hamiltonian_trimmed.terms)}\"\n", - " )\n", - "\n", - " # Define block encoding function\n", - " @qfunc\n", - " def be_qfunc(block: QNum, data: QNum):\n", - " lcu_paulis_graycode(hamiltonian_trimmed.terms, data, block)\n", - "\n", - " return data_size, block_size, be_scaling_factor, be_qfunc\n", - "\n", - "\n", - "def get_banded_diags_sym_be(mat_raw_scr):\n", - " \"\"\"\n", - " Get relevant block-encoding properties for `block_encode_banded_sym` block encoding,\n", - "\n", - " Parameters\n", - " ----------\n", - " mat_raw_scr : scipy.sparse.spmatrix\n", - " Square sparse matrix of shape (N, N), real or complex, to be block-encoded.\n", - "\n", - " Returns\n", - " -------\n", - " data_size : int\n", - " Size of the data variable.\n", - " block_size : int\n", - " Size of the block variable.\n", - " be_scaling_factor : float\n", - " The scaling factor of the block-encoding unitary\n", - " be_qfunc : qfunc\n", - " Quantum function that implements the block encoding. Signature:\n", - " be_qfunc(block: QNum, data: QNum) → None\n", - " \"\"\"\n", - " offsets, diags, diags_maxima, prepare_norm = get_be_banded_data(mat_raw_scr)\n", - " data_size = int(np.ceil(np.log2(len(diags[0])))) + 1\n", - "\n", - " # Calculate scaling factor and block size\n", - " block_size = int(np.ceil(np.log2(len(offsets)))) + 3\n", - " be_scaling_factor = 2 * prepare_norm\n", - "\n", - " # Define block encoding function\n", - " @qfunc\n", - " def be_qfunc(block: QNum, data: QNum):\n", - " block_encode_banded_sym(\n", - " offsets=offsets, diags=diags, prep_diag=diags_maxima, block=block, data=data\n", - " )\n", - "\n", - " return data_size, block_size, be_scaling_factor, be_qfunc" - ] - }, - { - "cell_type": "markdown", - "id": "6", - "metadata": {}, - "source": [ - "The solvers were developed in the framework of exploring their performance in hybrid CFD schemes. For simplicity, it is assumed that all the properties of the matrices are known explicitly. In particular, we calculate its sigular values for identyfing the range in which we apply the inversion polnomial." - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "id": "7", - "metadata": {}, - "outputs": [], - "source": [ - "def get_eig_range(mat_raw_scr):\n", - " mat_raw = mat_raw_scr.toarray()\n", - " raw_size = mat_raw.shape[0]\n", - " mat_sym = np.block(\n", - " [\n", - " [np.zeros([raw_size, raw_size]), np.transpose(mat_raw)],\n", - " [mat_raw, np.zeros([raw_size, raw_size])],\n", - " ]\n", - " )\n", - " w, v = np.linalg.eig(mat_sym)\n", - " w_min = np.min(np.abs(w))\n", - " w_max = np.max(np.abs(w))\n", - " return w_min, w_max" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "id": "8", - "metadata": {}, - "outputs": [], "source": [ "def cheb_lcu_solver(\n", " mat_raw_scr,\n", " b_raw,\n", " log_poly_degree,\n", " be_method=\"banded\",\n", - " cheb_approx_type=\"optimized\",\n", + " cheb_approx_type=\"numpy_interpolated\",\n", " preferences=Preferences(),\n", " constraints=Constraints(),\n", " qmod_name=None,\n", @@ -275,35 +139,36 @@ " SCALE = 0.5\n", " b_norm = np.linalg.norm(b_raw) # b normalization\n", " b_normalized = b_raw / b_norm\n", - " data_size = max(1, (len(b_raw) - 1).bit_length()) + 1\n", + " data_size = max(1, (len(b_raw) - 1).bit_length())\n", "\n", " # Define block encoding\n", " if be_method == \"pauli\":\n", - " data_size, block_size, be_scaling_factor, be_qfunc = get_pauli_sym_be(\n", - " mat_raw_scr\n", - " )\n", + " data_size, block_size, be_scaling_factor, be_qfunc = get_pauli_be(mat_raw_scr)\n", " if be_method == \"banded\":\n", - " data_size, block_size, be_scaling_factor, be_qfunc = get_banded_diags_sym_be(\n", + " data_size, block_size, be_scaling_factor, be_qfunc = get_banded_diags_be(\n", " mat_raw_scr\n", " )\n", "\n", " # Get eigenvalues range\n", - " w_min, w_max = get_eig_range(mat_raw_scr / be_scaling_factor)\n", - "\n", + " w_min, w_max = get_svd_range(mat_raw_scr / be_scaling_factor)\n", " # Calculate approximated Chebyshev polynomial\n", - " poly_degree = 2 * 2**log_poly_degree\n", + " poly_degree = 2 * (2**log_poly_degree - 1) + 1\n", " pcoefs, poly_scale = get_cheb_coeff(\n", " w_min,\n", - " poly_degree + 1,\n", + " poly_degree,\n", " w_max,\n", " scale=SCALE,\n", " method=cheb_approx_type,\n", " epsilon=0.01,\n", " )\n", - " odd_coef = pcoefs[1:-1:2]\n", + " odd_coef = pcoefs[1::2]\n", " # Calculate prep for Chebyshev LCU\n", " lcu_size_inv = len(odd_coef).bit_length() - 1\n", " print(f\"Chebyshec LCU size: {lcu_size_inv} qubits.\")\n", + " odd_coeffs_signs = np.sign(odd_coef)\n", + " assert np.all(\n", + " odd_coeffs_signs == np.where(np.arange(len(odd_coeffs_signs)) % 2 == 0, 1, -1)\n", + " ), \"Non alternating signs for odd coefficients\"\n", " normalization_inv = sum(np.abs(odd_coef))\n", " print(f\"Normalization factor for inversion: {normalization_inv}\")\n", " prepare_probs_inv = (np.abs(odd_coef) / normalization_inv).tolist()\n", @@ -314,24 +179,15 @@ " data: Output[QNum[data_size]],\n", " inv_block: Output[QNum[lcu_size_inv]],\n", " ):\n", + "\n", " allocate(inv_block)\n", " allocate(matrix_block)\n", - " allocate(data)\n", - "\n", - " data_array = QArray()\n", - " within_apply(\n", - " lambda: bind(data, data_array),\n", - " lambda: (\n", - " inplace_prepare_amplitudes(\n", - " b_normalized.tolist(), 0.0, data_array[0 : data_size - 1]\n", - " ),\n", - " X(data_array[data_size - 1]),\n", - " ),\n", - " )\n", + " prepare_amplitudes(b_normalized.tolist(), 0, data)\n", + "\n", " lcu_cheb(\n", " powers=[2**i for i in range(lcu_size_inv)],\n", " inv_coeffs=prepare_probs_inv,\n", - " block_enc=be_qfunc,\n", + " block_enc=lambda b, d: invert(lambda: be_qfunc(b, d)),\n", " mat_block=matrix_block,\n", " data=data,\n", " cheb_block=inv_block,\n", @@ -368,8 +224,8 @@ }, { "cell_type": "code", - "execution_count": 7, - "id": "9", + "execution_count": 5, + "id": "6", "metadata": {}, "outputs": [], "source": [ @@ -384,8 +240,8 @@ }, { "cell_type": "code", - "execution_count": 8, - "id": "10", + "execution_count": 6, + "id": "7", "metadata": {}, "outputs": [ { @@ -409,8 +265,8 @@ }, { "cell_type": "code", - "execution_count": 9, - "id": "11", + "execution_count": 7, + "id": "8", "metadata": {}, "outputs": [], "source": [ @@ -422,26 +278,26 @@ "# transpilation_option=\"none\", optimization_level=0, debug_mode=False, qasm3=True\n", "# )\n", "\n", - "log_poly_degree = 4" + "log_poly_degree = 5" ] }, { "cell_type": "code", - "execution_count": 10, - "id": "12", + "execution_count": 8, + "id": "9", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "For error 0.01, and given kappa, the needed polynomial degree is: 1293\n", - "Taking optimized expansion from literature, with degree 33\n", - "Chebyshec LCU size: 4 qubits.\n", - "Normalization factor for inversion: 0.2880055089348309\n", - "time to syn: 116.59351897239685\n", - "time to exe: 31.3349609375\n", - "Quantum program link: https://platform.classiq.io/circuit/36cmFMf4DTuHBPJVbsByISTbGxl\n" + "For error 0.01, and given kappa, the needed polynomial degree is: 591\n", + "Performing numpy Chebyshev interpolation, with degree 63\n", + "Chebyshec LCU size: 5 qubits.\n", + "Normalization factor for inversion: 0.5926349343907911\n", + "time to syn: 90.84944820404053\n", + "time to exe: 16.944632053375244\n", + "Quantum program link: https://platform.classiq.io/circuit/37T1FyqFCScVkg82dkN2DCMwwbi\n" ] } ], @@ -460,8 +316,8 @@ }, { "cell_type": "code", - "execution_count": 11, - "id": "13", + "execution_count": 9, + "id": "10", "metadata": {}, "outputs": [ { @@ -470,12 +326,12 @@ "text": [ "number of Paulis before/after trimming 24/20\n", "For error 0.01, and given kappa, the needed polynomial degree is: 885\n", - "Taking optimized expansion from literature, with degree 33\n", - "Chebyshec LCU size: 4 qubits.\n", - "Normalization factor for inversion: 0.3876323138202917\n", - "time to syn: 94.30704188346863\n", - "time to exe: 17.712372303009033\n", - "Quantum program link: https://platform.classiq.io/circuit/36cmWZY5PH5QhGiEAljfYbCajpL\n" + "Performing numpy Chebyshev interpolation, with degree 63\n", + "Chebyshec LCU size: 5 qubits.\n", + "Normalization factor for inversion: 0.4151350173670834\n", + "time to syn: 119.41847610473633\n", + "time to exe: 23.28752899169922\n", + "Quantum program link: https://platform.classiq.io/circuit/37T1ZH62NbvjrOQ1k0N5khsvJwV\n" ] } ], @@ -494,23 +350,23 @@ }, { "cell_type": "code", - "execution_count": 12, - "id": "14", + "execution_count": 10, + "id": "11", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "[]" + "" ] }, - "execution_count": 12, + "execution_count": 10, "metadata": {}, "output_type": "execute_result" }, { "data": { - "image/png": "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", + "image/png": "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CAAABA4BUIyNHDnSatasacWLF7dWrVrZnDlzLCi+/PJLO/fcc93VOXV17ffee8+CZOjQodaiRQt3dfGKFSvaBRdcYEuXLrWgGDVqlDVu3Dh04bPWrVvbJ598YkH12GOPub+D/v37W1A88MADbp/Dh/r161uQrF692q688korV66clShRwo4//nj79ttvLShq1qy532dAQ58+fWK+LQRAMTR+/HgbMGCA6/o3f/58a9KkiXXs2NHWrVtnQbBt2za3zwoCg+iLL75wf+TffPONTZ061Xbv3m1nnXWWOy5BoCur66Q/b94894V/5pln2vnnn2+LFi2yoJk7d679v//3/1xAGDTHHXecpaWlhYavvvrKgmLDhg128sknW5EiRVzw/+OPP9pTTz1lZcqUsSB99tPC3n99F8oll1wS+41RN3jERsuWLb0+ffqExvfu3etVqVLFGzp0qBc0+ui9++67XpCtW7fOHYcvvvjCC6oyZcp4L730khckW7Zs8erWretNnTrVO/30071+/fp5QTF48GCvSZMmXlANHDjQO+WUU+K9GUeUfv36eXXq1PEyMjJi/tpkgGJk165d7pdv+/btI+43pvFZs2bFddsQH5s2bXKPZcuWtaDZu3evvfXWWy77paawIFEW8Jxzzon4LgiSn3/+2TWD165d27p162YrVqywoPjggw+sefPmLtuhZvATTzzRXnzxRQvyefGNN96wa6+9Nk9vOp5bBEAx8ueff7ov/UqVKkVM13h6enrctgvxkZGR4Wo/lA5v1KiRBcUPP/xgRx99tLv66w033GDvvvuuNWzY0IJCQZ+av1UPFkSqe3z11Vdt8uTJriZs+fLlduqpp7o7dwfBr7/+6va7bt26NmXKFLvxxhvtlltusddee82C6L333rONGzdajx494vL63A0eiFMWYOHChYGqf5B69erZggULXPbrnXfese7du7vaqCAEQStXrrR+/fq5mgd1ggiis88+O/R/1T8pIKpRo4a9/fbb1rNnTwvCDx9lgB599FE3rgyQvgdGjx7t/haC5uWXX3afCWUE44EMUIyUL1/eChUqZGvXro2YrvGUlJS4bRdir2/fvvbhhx/a9OnTXWFwkBQtWtSOOeYYa9asmcuCqCh++PDhFgRqAleHh6ZNm1rhwoXdoOBvxIgR7v/KEAdN6dKl7dhjj7VffvnFgqBy5cr7BfsNGjQIVDOg7/fff7fPPvvMrrvuOosXAqAYfvHrS//zzz+P+DWg8aDVQASVar8V/KjZZ9q0aVarVi0LOv0N7Ny504KgXbt2rglQGTB/UDZAdTD6v34gBc3WrVtt2bJlLjAIAjV5Z770xU8//eSyYEEzZswYVwelerh4oQkshtQFXmlOfem1bNnShg0b5opAr7nmGgvKl134Lz21/+uLX0XAqampFoRmr3Hjxtn777/vrgXk134lJye764EUdIMGDXLpbr3XqvnQsZgxY4arhQgCveeZ671KlizprgcTlDqw22+/3V0LTCf8NWvWuEuCKPDr2rWrBcGtt95qbdq0cU1gl156qbsO3AsvvOCGoP3wGTNmjDsfKvsZNzHvdxZwzz77rJeamuoVLVrUdYv/5ptvvKCYPn266/adeejevbsXBFntu4YxY8Z4QXDttdd6NWrUcJ/9ChUqeO3atfM+/fRTL8iC1g3+sssu8ypXruw+A1WrVnXjv/zyixckkyZN8ho1auQVK1bMq1+/vvfCCy94QTNlyhT33bd06dK4bkeC/olf+AUAABB71AABAIDAIQACAACBQwAEAAAChwAIAAAEDgEQAAAIHAIgAAAQOARAAAAgcAiAAABA4BAAAQCAwCEAAgAAgUMABAAAAocACAAAWND8f8LgQac8xRHpAAAAAElFTkSuQmCC", "text/plain": [ "
" ] @@ -527,23 +383,24 @@ "correct_sign = np.sign(expected_sol[ext_idx]) / np.sign(qsol_banded[ext_idx])\n", "qsol_banded *= correct_sign\n", "plt.plot(\n", - " qsol_banded[0 : len(b_raw)],\n", + " qsol_banded,\n", " \".\",\n", - " label=f\"Cheb-LCU-inv; Banded BE; degree {2*2**log_poly_degree+1}\",\n", + " label=f\"Cheb-LCU-inv; Banded BE; degree {2*2**log_poly_degree-1}\",\n", ")\n", "correct_sign = np.sign(expected_sol[ext_idx]) / np.sign(qsol_pauli[ext_idx])\n", "qsol_pauli *= correct_sign\n", "plt.plot(\n", - " qsol_pauli[0 : len(b_raw)],\n", + " qsol_pauli,\n", " \".\",\n", - " label=f\"Cheb-LCU-inv; Pauli BE; degree {2*2**log_poly_degree+1}\",\n", - ")" + " label=f\"Cheb-LCU-inv; Pauli BE; degree {2*2**log_poly_degree-1}\",\n", + ")\n", + "plt.legend()" ] }, { "cell_type": "code", - "execution_count": 13, - "id": "15", + "execution_count": 11, + "id": "12", "metadata": {}, "outputs": [], "source": [ diff --git a/applications/cfd/qls_for_hybrid_solvers/qls_qsvt.ipynb b/applications/cfd/qls_for_hybrid_solvers/qls_qsvt.ipynb index d06fb8320..20d40b955 100644 --- a/applications/cfd/qls_for_hybrid_solvers/qls_qsvt.ipynb +++ b/applications/cfd/qls_for_hybrid_solvers/qls_qsvt.ipynb @@ -35,15 +35,18 @@ "\n", "import matplotlib.pyplot as plt\n", "import numpy as np\n", + "from banded_be import get_banded_diags_be\n", "from cheb_utils import *\n", - "from classical_functions_be import *\n", - "from quantum_functions_be import *\n", + "from classical_functions_be import get_projected_state_vector, get_svd_range\n", + "from pauli_be import get_pauli_be\n", "from scipy import sparse\n", "\n", "from classiq import *\n", "from classiq.applications.qsp import qsvt_phases\n", "\n", - "np.random.seed(53)" + "np.random.seed(53)\n", + "\n", + "PAULI_TRIM_REL_TOL = 0.1" ] }, { @@ -51,7 +54,7 @@ "id": "3", "metadata": {}, "source": [ - "Define functions to get properties of the block-encoding." + "The solvers were developed in the framework of exploring their performance in hybrid CFD schemes. For simplicity, it is assumed that all the properties of the matrices are known explicitly. In particular, we calculate its sigular values for identyfing the range in which we apply the inversion polynomial." ] }, { @@ -60,161 +63,13 @@ "id": "4", "metadata": {}, "outputs": [], - "source": [ - "PAULI_TRIM_REL_TOL = 0.1\n", - "\n", - "\n", - "def get_pauli_be(mat_raw_scr):\n", - " \"\"\"\n", - " Get relevant block-encoding properties for `lcu_paulis_graycode` block encoding,\n", - "\n", - " Parameters\n", - " ----------\n", - " mat_raw_scr : scipy.sparse.spmatrix\n", - " Square sparse matrix of shape (N, N), real or complex, to be block-encoded.\n", - "\n", - " Returns\n", - " -------\n", - " data_size : int\n", - " Size of the data variable.\n", - " block_size : int\n", - " Size of the block variable.\n", - " be_scaling_factor : float\n", - " The scaling factor of the block-encoding unitary\n", - " BlockEncodedState : QStruct\n", - " QSVT-compatible QStruct holding the quantum variables, with fields:\n", - " - data : QNum[data_size]\n", - " - block : QNum[block_size]\n", - " be_qfunc : qfunc\n", - " Quantum function that implements the block encoding. Signature:\n", - " be_qfunc(be: BlockEncodedState) → None\n", - " \"\"\"\n", - " rval = mat_raw_scr.data\n", - " col = mat_raw_scr.indices\n", - " rowstt = mat_raw_scr.indptr\n", - " nr = mat_raw_scr.shape[0]\n", - "\n", - " raw_size = mat_raw_scr.shape[0]\n", - " data_size = max(1, (raw_size - 1).bit_length())\n", - "\n", - " # Set to_symmetrize=False, since we are working with QSVT\n", - " paulis_list, transform_matrix = initialize_paulis_from_csr(\n", - " rowstt, col, data_size, to_symmetrize=False\n", - " )\n", - "\n", - " qubit_op = eval_pauli_op(paulis_list, transform_matrix, rval)\n", - " qubit_op.compress(1e-12)\n", - " hamiltonian = of_op_to_cl_op(qubit_op)\n", - " hamiltonian_trimmed = trim_hamiltonian(\n", - " hamiltonian, PAULI_TRIM_REL_TOL, jump_threshold=1.1\n", - " )\n", - "\n", - " be_scaling_factor = sum(\n", - " [np.abs(term.coefficient) for term in hamiltonian_trimmed.terms]\n", - " )\n", - " block_size = size = max(1, (len(hamiltonian_trimmed.terms) - 1).bit_length())\n", - "\n", - " hamiltonian_trimmed = hamiltonian_trimmed * (1 / be_scaling_factor)\n", - "\n", - " print(\n", - " f\"number of Paulis before/after trimming {len(hamiltonian.terms)}/{len(hamiltonian_trimmed.terms)}\"\n", - " )\n", - "\n", - " class BlockEncodedState(QStruct):\n", - " data: QNum[data_size]\n", - " block: QNum[block_size]\n", - "\n", - " @qfunc\n", - " def be_qfunc(be: BlockEncodedState):\n", - " lcu_paulis_graycode(hamiltonian_trimmed.terms, be.data, be.block)\n", - "\n", - " return data_size, block_size, be_scaling_factor, BlockEncodedState, be_qfunc\n", - "\n", - "\n", - "def get_banded_diags_be(mat_raw_scr):\n", - " \"\"\"\n", - " Get relevant block-encoding properties for `block_encode_banded` block encoding,\n", - "\n", - " Parameters\n", - " ----------\n", - " mat_raw_scr : scipy.sparse.spmatrix\n", - " Square sparse matrix of shape (N, N), real or complex, to be block-encoded.\n", - "\n", - " Returns\n", - " -------\n", - " data_size : int\n", - " Size of the data variable.\n", - " block_size : int\n", - " Size of the block variable.\n", - " be_scaling_factor : float\n", - " The scaling factor of the block-encoding unitary\n", - " BlockEncodedState : QStruct\n", - " QSVT-compatible QStruct holding the quantum variables, with fields:\n", - " - data : QNum[data_size]\n", - " - block : QNum[block_size]\n", - " be_qfunc : qfunc\n", - " Quantum function that implements the block encoding. Signature:\n", - " be_qfunc(be: BlockEncodedState) → None\n", - " \"\"\"\n", - " raw_size = mat_raw_scr.shape[0]\n", - " data_size = max(1, (raw_size - 1).bit_length())\n", - " offsets, diags, diags_maxima, prepare_norm = get_be_banded_data(mat_raw_scr)\n", - " block_size = int(np.ceil(np.log2(len(offsets)))) + 1\n", - " be_scaling_factor = prepare_norm\n", - "\n", - " class BlockEncodedState(QStruct):\n", - " data: QNum[data_size]\n", - " block: QNum[block_size]\n", - "\n", - " @qfunc\n", - " def be_qfunc(be: BlockEncodedState):\n", - " block_encode_banded(\n", - " offsets=offsets,\n", - " diags=diags,\n", - " prep_diag=diags_maxima,\n", - " block=be.block,\n", - " data=be.data,\n", - " )\n", - "\n", - " return data_size, block_size, be_scaling_factor, BlockEncodedState, be_qfunc" - ] - }, - { - "cell_type": "markdown", - "id": "5", - "metadata": {}, - "source": [ - "The solvers were developed in the framework of exploring their performance in hybrid CFD schemes. For simplicity, it is assumed that all the properties of the matrices are known explicitly. In particular, we calculate its sigular values for identyfing the range in which we apply the inversion polnomial." - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "6", - "metadata": {}, - "outputs": [], - "source": [ - "def get_svd_range(mat_raw_scr):\n", - " mat_raw = mat_raw_scr.toarray()\n", - " svd = np.linalg.svd(mat_raw)[1]\n", - " w_min = min(svd)\n", - " w_max = max(svd)\n", - " return w_min, w_max" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "id": "7", - "metadata": {}, - "outputs": [], "source": [ "def qsvt_solver(\n", " mat_raw_scr,\n", " b_raw,\n", " poly_degree,\n", " be_method=\"banded\",\n", - " cheb_approx_type=\"optimized\",\n", + " cheb_approx_type=\"numpy_interpolated\",\n", " preferences=Preferences(),\n", " constraints=Constraints(),\n", " qmod_name=None,\n", @@ -226,21 +81,25 @@ "\n", " # Define block encoding\n", " if be_method == \"pauli\":\n", - " data_size, block_size, be_scaling_factor, BlockEncodedState, be_qfunc = (\n", - " get_pauli_be(mat_raw_scr)\n", + " data_size, block_size, be_scaling_factor, be_qfunc = get_pauli_be(\n", + " mat_raw_scr, PAULI_TRIM_REL_TOL\n", " )\n", " print(\n", " f\"Pauli block encoding with block size {block_size} and scaling factor {be_scaling_factor}\"\n", " )\n", "\n", " elif be_method == \"banded\":\n", - " data_size, block_size, be_scaling_factor, BlockEncodedState, be_qfunc = (\n", - " get_banded_diags_be(mat_raw_scr)\n", + " data_size, block_size, be_scaling_factor, be_qfunc = get_banded_diags_be(\n", + " mat_raw_scr\n", " )\n", " print(\n", " f\"Banded diagonal block encoding with block size {block_size} and scaling factor {be_scaling_factor}\"\n", " )\n", "\n", + " class BlockEncodedState(QStruct):\n", + " data: QNum[data_size]\n", + " block: QNum[block_size]\n", + "\n", " # Get SVD range\n", " w_min, w_max = get_svd_range(mat_raw_scr / be_scaling_factor)\n", " # Get Chebyshev polynomial and the corresponding QSVT angles\n", @@ -271,7 +130,7 @@ " lambda: qsvt_inversion(\n", " inv_phases,\n", " lambda aux: projector(be_state, aux),\n", - " lambda: be_qfunc(be_state),\n", + " lambda: be_qfunc(be_state.block, be_state.data),\n", " qsvt_aux,\n", " ),\n", " )\n", @@ -306,8 +165,8 @@ }, { "cell_type": "code", - "execution_count": 6, - "id": "8", + "execution_count": 4, + "id": "5", "metadata": {}, "outputs": [], "source": [ @@ -324,7 +183,7 @@ }, { "cell_type": "markdown", - "id": "9", + "id": "6", "metadata": {}, "source": [ "We run two examples, for the two different block encoding, but using the same polynomial degree." @@ -332,8 +191,8 @@ }, { "cell_type": "code", - "execution_count": 7, - "id": "10", + "execution_count": 5, + "id": "7", "metadata": {}, "outputs": [], "source": [ @@ -348,8 +207,8 @@ }, { "cell_type": "code", - "execution_count": 8, - "id": "11", + "execution_count": 6, + "id": "8", "metadata": {}, "outputs": [ { @@ -373,8 +232,8 @@ }, { "cell_type": "code", - "execution_count": 9, - "id": "12", + "execution_count": 7, + "id": "9", "metadata": {}, "outputs": [ { @@ -383,10 +242,10 @@ "text": [ "Banded diagonal block encoding with block size 3 and scaling factor 3.8927056451476174\n", "For error 0.01, and given kappa, the needed polynomial degree is: 591\n", - "Taking optimized expansion from literature, with degree 101\n", - "time to syn: 118.76460003852844\n", - "time to exe: 12.583821058273315\n", - "Quantum program link: https://platform.classiq.io/circuit/36cmEQmYNOVwmxq09cW1Pxntbqr\n" + "Performing numpy Chebyshev interpolation, with degree 101\n", + "time to syn: 115.33122491836548\n", + "time to exe: 11.221765995025635\n", + "Quantum program link: https://platform.classiq.io/circuit/37T1LWNPeODAI0UIjCyoWzInZE3\n" ] } ], @@ -405,8 +264,8 @@ }, { "cell_type": "code", - "execution_count": 10, - "id": "13", + "execution_count": 8, + "id": "10", "metadata": {}, "outputs": [ { @@ -416,10 +275,10 @@ "number of Paulis before/after trimming 24/20\n", "Pauli block encoding with block size 5 and scaling factor 5.557119918538639\n", "For error 0.01, and given kappa, the needed polynomial degree is: 885\n", - "Taking optimized expansion from literature, with degree 101\n", - "time to syn: 169.37596011161804\n", - "time to exe: 17.412251949310303\n", - "Quantum program link: https://platform.classiq.io/circuit/36cmdIpDThjRGtPTxzdJGjNkfqk\n" + "Performing numpy Chebyshev interpolation, with degree 101\n", + "time to syn: 169.5105390548706\n", + "time to exe: 17.674686193466187\n", + "Quantum program link: https://platform.classiq.io/circuit/37T1isgdOJ3sQH7PEr0IXXPrFEx\n" ] } ], @@ -438,23 +297,23 @@ }, { "cell_type": "code", - "execution_count": 11, - "id": "14", + "execution_count": 9, + "id": "11", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "" + "" ] }, - "execution_count": 11, + "execution_count": 9, "metadata": {}, "output_type": "execute_result" }, { "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAjgAAAGdCAYAAAAfTAk2AAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjAsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvlHJYcgAAAAlwSFlzAAAPYQAAD2EBqD+naQAASmZJREFUeJzt3Ql8FPX9//FPwqkcwXAFBBQQBUSsgiCKJ1gQxQO8+GNBRKwIFAGroFakVbFaBU+s1opUaBHFI6goCoJHOAQP5BIVJdxSIFxyZv6P95ffbDchCYnsZsPM6/l4LMscOzuzu9n57Of7+X4nyfM8zwAAAAIkOdE7AAAAEGsEOAAAIHAIcAAAQOAQ4AAAgMAhwAEAAIFDgAMAAAKHAAcAAAQOAQ4AAAic0hZC2dnZtmbNGqtUqZIlJSUlencAAEAhaGzibdu2We3atS05ueAcTSgDHAU3devWTfRuAACAXyEzM9Pq1KlT4DqhDHCUufFfoMqVKyd6dwAAQCFs3brVJSj883hBQhng+M1SCm4IcAAAOLIUpryEImMAABA4BDgAACBwCHAAAEDghLIGp7Bd0fbt22f79+9P9K4ACLBSpUpZ6dKlGbICiDECnDzs2bPH1q5dazt37kz0rgAIgaOPPtpq1aplZcuWTfSuAIFBgJPHIIArVqxwv6o0kJC+cPhlBSBemWL9oPr555/d906jRo0OOXgZgMIhwMlFXzYKctTPXr+qACCejjrqKCtTpoz99NNP7vunfPnyid4lIBD4qZAPfkUBKC583wCxRwYHABAz+7M9m7tik23YtstqVCpvreqnWqlkmvlR/AhwAAAxMfWbtTYifbGtzdoVmVcrpbwN79zUOjarldB9Q/iQF43jr5iM7/9rb3652t1ruiRQwfQbb7wR9+f56KOP3HNt2bIlJtv78ccf3fa+/PLLmGwPQOyDm74vL8gR3Mi6rF1uvpYDxYkMTsB+xaxbt84eeOABe/vtt2316tVWo0YN+81vfmO33XabtWvXzorLWWed5brap6SkFNtzAkgM/YDTd15eP+M0Tw1UWn5R0zSaq1BsyOAE6FeMshwtWrSw6dOn2yOPPGILFy60qVOn2gUXXGD9+vWz4qTu9WlpaXSxB0JANTe5v/NyBzlarvVCIWu12YpZB+6RMAQ4xfgrRrQ8Xs1Vt956qwso5s6da127drUTTzzRTj75ZBs8eLDNnj07z8fceeedbj11iW/QoIH96U9/sr1790aWf/XVVy5A0qXpdeV1BVCff/65W6ZurZ07d7ZjjjnGKlSo4J7rnXfeybeJ6tNPP7Xzzz/fPZce06FDB9u8ebNbpkCsbdu2VqVKFatatapdeuml9v3338fldQIQWyoojuV6R7QF48xGNzN7qfOBe00jIQhwAvIrZtOmTS5IUKZGwUZuChzyosBl7NixtnjxYnv88cft+eeft1GjRkWWd+/e3erUqWPz5s2z+fPn29ChQ92YHaLn2r17t82aNctli/76179axYoV83we1c6oiaxp06aWkZFhn3zyiQuO/Eth7NixwwViCp4+/PBD1232yiuvdGMSASjZ1FsqlusdsZSxSR9o5v3f95bu028jk5Mg1OAE5FfMd99950ZFbdy4cZEed88990T+f/zxx9vtt99u//nPf+yOO+5w81auXGl//OMfI9vVSKs+LVOm6JRTTnHTygDl5+GHH7aWLVvaM888E5mnjI9P24n2z3/+06pXr+4Cr2bNmhXpmAAUL3UFV52hmuLzyk+roTot5UCX8UDb9P3/ghuft99s0w9mKccmaq9Cq1gyOE8//bQ7eWqEztatW7smlIJMmjTJnVC1vk6efrNHXm655RbXFDJ69GgL868YBTe/xsSJE+3ss8929TLKvijgUeDiU1blpptusvbt29tDDz2Uo9noD3/4g91///3u8cOHD7evv/463+fxMzj5Wb58uXXr1s0FSWoK0+dFovcFQMmkwmF1ovCDmaTSWVbq6O/dvV+Fp+WBLzBObWiWlOu0mlTKLDX/H384ggMcnUB1ktQJcMGCBXbqqae62osNGzbkuf5nn33mTnS9e/e2L774wq644gp3++abbw5a9/XXX3e1JbpmVEn6FZPfn7Dm14rTrxhlVhToLV26tNCPUVORmqA6depkU6ZMca/33Xff7YaL99133322aNEiu+SSS1zxspqY9LqLAp8ffvjBfve737kmKmVonnzyyXyHoy+ImqvUzKYmsjlz5ribRO8LgJJLPUTHXH+6Va31pVU44SE7+rjn3b2mNT8U4+AoS9P58QNBjei+82iyN0ENcB577DHr06eP9erVy50cn332WVdkqiaIvKgOpGPHjq5ZpEmTJvaXv/zFTj/9dHvqqadyrKcu0AMGDLDx48dHakJK2q+YaPH+FZOamuoCR2XLVM+SW17j0SiYPO6441xQo+BEQZIKh3NTEfKgQYPs/fffty5dutiLL74YWaZrdimLNnnyZBsyZIgLUPLSvHlzV1uTl//+97+2bNkylz1Slkfvu198DODI8Zv6Sba3yiuWlHQgo6z7vce84uaHxuk9zG5baNZzyoF7TSN4AY5+faswVc0bkSdMTnbTyh7kRfOj1xeduKPXV+GpsgYKgqLrOPKjQtitW7fmuMX7V4zam6NpOt6/YhTcqGi3VatW9tprr7lmnyVLltgTTzxhbdq0OWh9BTRqAlLNjZqetJ6fnZFffvnF+vfv73pEKfBRLygVGysAEY2t895777mrICs7N2PGjMiy3IYNG+Yeq55easpSpmnMmDG2ceNG16NKPaeee+45V0ukTJGyfgCOLCu3rrRsy1mDku1lW+a2TAsVZWzqn0PmJshFxjp56YRbs2bNHPM1nV9Tigaqy2t9zfept07p0qVdDUhhjBw50kaMGGHFRUGMBrQq7uuxqH5FgYYG+lM2RQPtqVBXXbsVTOR22WWXucyMghgFgWqGUjdxNUtJqVKlXHalR48etn79eqtWrZrL4Pivpd5b9aRatWqVq5tR5i26B1buLJAyQHfddZcLwNRkpXosNUcq6FWQpfdTBcUnnXSSC7bUpRzAkaNe5XqWnJTsghqfputWqpvQ/UI4JXm/tjq1ENasWWPHHnusawqJziCoh87MmTMjdRa5B4h76aWX3InPp543OqnqJKuMkE7EOpH7tTcqSFU2Qbe86OStm08ZHDWtZGVluRNztF27drmMRP369V2RMwDEW5C+dyYvn2wjMka4IEfBzfA2w61Loy6J3i0EhM7fGiE/r/N3sWZw9ItfWQAFJtE0rV47edH8gtb/+OOPXYFyvXr1IsuVSVDGQj2pNJpvbuXKlXM3AEB8KZg5q/ZZrllKmZu0Cnl/1wNHdA2OsjFqHokuLlX9jKbzqgkRzc9djDpt2rTI+qq9UQ2Huh37N2VyVI+jehAAQGIpqDkj7QyCGwR7oD8Vi/bs2dP10lHthbIs6uWjXlWi+g41Y6lORgYOHGjnnXeePfroo64pSrUZGt1WBaiiYlTdoqkXlTI8qt0AAACIe4Bz7bXX2s8//2z33nuvKxTWla11SQG/kFi9eFRkGn0V6gkTJrguwypIVU+fN954g9FsAQBAySgyPhKLlIJU7AfgyMD3DhD7ImMutgkAAAKHAAcAAAQOAQ4AAAgcAhwUG42QrCLzMBzbDTfc4C4SW5Lp4qwq4AdiLmu12YpZB+6BBCHACZDMzEy78cYb3bhAGoNIF9JUt3tdbiGaihn/3//7f249FTTWqVPHLr/8cnf5DA2qqG736p6fF13lXRc/1ejROkHmd9MJPrfbb7893wtuxjv48PdLl/jQvusSFdu3b4/bc+qisWPHjs13ua7vFf166dIVuq6aPxyCT69jXq+vLosRdrrA629/+1s3bIReE42JlVfxri4nonUqVqxoXbt2PWggUV0iRON1aTDQoAbgxWrBOLPRzcxe6nzgXtNAAhDgBMQPP/zgxhrSBTb//e9/u4tW6srt/qCKmzZtcuvt3bvXLrroIleBrhOEruI9ceJEO+WUU9wVx9V9X+MP5XW1d41f9Morr7ggRxfO1LWudNOFPUXb8ufpBJ+bTjC5xzAqLgoetF8a6VrXMlMgodGv40VV/lWqVDnkev5rtnjxYvv9739vffv2PSgIVDDjv67+Te/xkUAX3I0XfR7btm3r3s/8KJBNT0+3SZMmucvD6PIxup5abvphoCEtcJiUsUkfaOZfi0r36beFLpOzbsc6m7t2rrsPrawSkMXzQigrK0td4919br/88ou3ePFid3/YtqzyvB9mHriPs44dO3p16tTxdu7cmWP+2rVrvaOPPtq75ZZb3PQXX3zhjv3HH3/Md1tvvfWWl5yc7P3000855r/44ote+fLlvc2bN+eYP2PGDLfN3PNzGz58uHfqqadGpnv27Oldfvnl3iOPPOKlpaV5qamp3q233urt2bPHLR82bJjXqlWrg7bTvHlzb8SIEQU+V0HPK3369HHPKePGjfNatGjhVaxY0atZs6bXrVs3b/369TmOOyUlJcfjX3/9dXfMhzq2/OT3mjVs2NB7+OGHC72dwvr222+9c845xytXrpzXpEkT7/3333fPr+PwrVy50rv66qvdsR5zzDHeZZdd5q1YsSKyfO/evd6AAQPccr1Xd9xxh9ejR48c+3feeed5/fr18wYOHOhVrVrVO//88938hQsXus9ohQoVvBo1anjXX3+99/PPP0cet3//fu/BBx/0jj/+ePcZ03s8adKkQh2b9lHHos92tC1btnhlypTJsZ0lS5a4dTMyMgr1OSkuMf3eSSR93w2vfPDth1leWLz27Wte85eae83GNnP3mg6d+S953n1VDrz3utd0MZy/cyODE4A0rbIzukzFrbfe6po6ommE5+7du7ssjYY80tXFNbDiq6++6q7hlZdOnTq5TE7uJpYXX3zR/fotTGaisGbMmGHff/+9u9dFVvWc/vNqv+fOneuW+xYtWuQu1aEmtuimnryuQVYQvU5+dkFZrb/85S/21VdfuZoUbSuvJrZ40nujATA18KWusl4U2teCrryuy6PofVOzpS5wq8zenXfemWMdvQYdOnSwSpUqueu9ffrppy7jpuyR/zopUzJ+/Hj3OdByjUeRVw2P3kc9l9bRcykzeOGFF9ppp53mRiXXcaqZ6Jprrok8RiOZjxs3zq2v91iZl+uvv95lXX4tXZhXx9W+ffvIvMaNG7vr2GVkZPzq7aIAqQ3NknKdVpJKmaU2sDBQxsa/0KjoXtOhyuRklZwsHgFOAN5gNUvpBNmkSZM8l2v+5s2b3YjSuizGE0884UaWPuaYY9yJRyd3NXH5dIFUXV5DgYY/DqSCDJ34lMqPJe3DU0895U48l156qWse85to1Kx06qmnupGtfTrBKgA44YQT3PTRRx/tLtGhuqGinPi0TR276Jguvvhia9CggZ155pnu9Xn33XfjWqPjU/2TAgkFBDr24cOH27nnnptjnSlTprh1om8PPvhgZHmtWrVyXHw2tw8++MDVVymA0Oup7Uc/XhQAKxD6xz/+4Zor9ZlRIKOAS0GkPPnkkzZs2DC78sor3ful9y2vYFejjz/88MPufdFN6ym40XPqcfq/mkAV1H777be2e/dut0zzFGTpfVDQpgDn73//+69+bTVyul7X3Puo4F3LEAcpx5p1fvxAUCO67zz6wPwQWLl1ZSS48WlaFx4NjU3f/+/c5/P2m2363zkmMJdqCKWC3uA4/qEfalBqfdmLii51DTCduGbPnu3qE3SCeeutt1x9jn/Sf+ihh9xJSIGATnYqzvWDgkPRSdinE5V+medFQYwCquiT9cKFCyPTyuLoxPenP/3JHZ9qT3R9M5+ub6aT96Fom9onZa2UkVAwoROvH/CoEFkZHAWCOtGLTu5Nmza1eFLQqKyJTvLKVvXv399SU1NdLY7vggsusDFjxuR4nNbx+ddxy8+SJUusbt26rqjcl/titzp21W1pX3IX6Sq4Vc2Wsi56vX1631Sc679ePs3LvW19jqI/Ez5tW1mWnTt3Rj57Pr1PCoZwhDm9h1nDdge+75S5CUlwI/Uq17PkpOQcQY6mdVX10GXxvOyEZ/EIcALwBiuboWYancj06zo3zVfTVPQvWZ3IOnfu7G7333+/++Wse/8ko1/h55xzjgts1PyhX/99+vRxz1MY0T1aChpOO3fmRduPPmF269bNNacsWLDAfvnlF9dT7NcUgyqToABOvaj8XmZ+oaqOXTdlh/Q6KbDRtN80oya93MGjTsqxoKH5/fdFwZ6akB544IEcAU6FChUiGat4UbZKgYleg9z0mhSF9jf3tvU5y6sYWAHtN9984/7/9ttvuwxjNPVs+rXUPKv3UE1k0Z99BWpahjhSUBOiwManq6f/qfW99pfZf7Zsy7ZkS3bTobqqesr/ZfHUaqEf9gnM4hHgBOANVs8kBSbPPPOMq12IrsNRKl4nLWVt8qOgQk0Hn332WY756i2lE+1ll11mq1evLlJdSqxOyGrC0dXldQwKcHScNWrUKPJ2FNDktU/K/qgbvbJVynKI6kRyn+C3bdvmgiH/5J1Xl+RYUFZExxlLam5SYKjeVwooRJm7aOr6r2Yqvbb5BaRq2lHvOb8JTdkwBZ6H6lqtbaunnTKACjBzU5ZMgYwCS73XsaKATQG0mjzVPdzvtabnyZ3BAmJh6jdr7dH0KrZ1x52WXHajZe+pZo+uqWIVO6+1js0O/O2FwuklI4tHDU483+DbFpr1nHLgXtNxpOYWNXMo8zBr1ix3QlMxpwKCE0880dXc+CdmjXmjImN1TVazxAsvvOCagTQ/2tVXX+1OEOq+rPFG/ACguKmZSuPyqClN/48l1a4o+FF9ieqQlOVRTVI01fyo1kdXt1eTiup3Chrjpig2bNjggtCffvrJHd+//vWvg94Hva9aJ/q2cePGQj+Himz1GVBdlZqL1Cx2991351hHr2u1atXcc2u5xkpSE6bGiFm1apVbZ8CAAa457M0333SBgsZYUpPeobJ6Cq5VCK9snAIkvYYqiu/Vq5cLkpRN1BhJCs5VoKzlCpz0nmg6P9qmPs/6HIv2SdN+fY266itIV5OmmsjUFKnnVHCjWiuf/gb8xym41P91i2cXdwQzuOn78gJbm7XLvH0ptn9nQ3e/LmuXm6/loZJyrFn9cxKaySPACcgbrCYlnTxUoKneKRrkT4WzOrH5PWL8jIh+SY8YMcKduPXrWmPWaDr3SU8n9euuu86dxGJdXFwUV111lcuyqE4j9+jAv7YXVXR2RsGKggtlEpTJ+dvf/nZQvcvLL79s77zzjivAVR2QanZiQU1nyqoou6SmOAWTOrFHU6CqdaJvGv+lsL2o1MT2+uuvu5O3amhuuukm1wyW+71WYKyATz2ulPVRcKAaHD+jo/1TkKL6LQUJ+kwpoD7U1a/VJKjPoIIZBcp6DW+77TbXbKR9EwWVqrNSAKXnVu8tNVmpCS8/CkZVo6N6KtFnVdPR9V6jRo1yxevK4CjzpKYpjf8UTa+HHqeCZhU96/+6acwcoDD2Z3s2In2x5VUF6c/Tcq2H4pOkvuIWMgVdbl1f6Pr1qi/WQ31xl3TqkfPYY4/ZtGnTcvxiDRLVCKlAWr/ii9KTKkjUrKNC5FgFXYWlWikFIwqoc2e9UDRB+t4Jo4zv/2vdns/Z7JuXf/c509o0TMxgp2E4f+dGDU6AKSujbI3qLfTL3f+1HCTKqijACWtwoz9yNeko2xFvakZ7//33XUClZjM1i/qX/QDCbMO2XTFdD7FBgBNwqjkIMjUthZl+yfg1MvGmAFnNeaqXUeK3WbNmboyd/MZfAsKiRqXyMV0PsUGAA6BQVGSuWhoAObWqn2q1Usq7guK8aj5Uhp+WUt6th+ITvDYLAACKUankJBve+cCgoLn7FPrTWq71UHwIcAAAOEwa52bM9ae7TE00TWt+qMbBKSFoogIAIAYUxFzUNM3mrtjkCopVc6NmKTI3iUGAAwBAjCiYoSt4yUATFQAACBwCHAAAEDgEOCg2Gmn3UBdmDCJdSiH3JSaKyr8kha6MHeTjBIBYIcAJEF1gU9eM0rV/dAFJXY9KF0TUdZyi+aPPaj0NC6/rU+kii7qy9vr1692owLq4ZV50fSJdv0ojJOuEm98tryuPa4A4Xdk5EYFV9L5pcLxzzjnHZs6caUES/Z7oquR6f/V+6VpiuQOlvG7+RSrDSldb19+Frt+mQQ11vaz8Bpds3Lix+9vRdbU0mnY0XetK19yqWrWqe13jdeV5AAUjwAkIXQm7ZcuWtnz5cncxSF0hWRcdVEChCyPqysuyd+9ed4VxDfGvL2JdgXnixInui1rZgZo1a7qLF+rq4rnt2LHDXnnlFXfS1IU9dULQ7bXXXnPLtS1/ni7gmZsuzqgv/UQ4+eSTI/uWkZHhLk6qizDqdQiSP//5z+4YV65caePHj3cX0NQVwXOLfq/8W40aNayk0wU7dQ2seNDlJ3Tx1XvuucdOPfXUPNf57LPP3AVH9TfwxRdfuIyVbt98802OvxNdDPWvf/1rXPYTQCF5IZSVlaXBJt19br/88ou3ePFid3+41m5f681ZM8fdx1vHjh29OnXqeDt37sy5D2vXekcffbR3yy23uOkvvvjCHfuPP/6Y77beeustLzk52fvpp59yzH/xxRe98uXLe5s3b84xf8aMGW6buefnNnz4cO/UU0+NTPfs2dO7/PLLvUceecRLS0vzUlNTvVtvvdXbs2ePWz5s2DCvVatWB22nefPm3ogRIwp8roKeVzIzM90+z507NzLv0Ucf9Zo1a+ZeL72Wffv29bZt25bj+FNSUrypU6d6jRs39ipUqOB16NDBW7NmTWSdffv2eYMGDXLr6Xj++Mc/ej169HDH6du/f7/34IMPescff7x7PXU8kyZNyrF/b7/9tteoUSO3/Pzzz3fPfajX+LjjjvNGjRqVY95f/vIXr2nTpkV+rw4lVsf55ptveieccIJXrlw5d5xjx47NsX/+a671mjRp4pUqVcpbsWKFt2vXLm/IkCFe7dq13fulz4mOLdrHH3/stW3b1j233s8BAwZ427dvL9TxnXfeed7AgQMPmn/NNdd4l1xySY55rVu39n7/+98ftK72U8eiv7lDieX3DhDW83duZHDiZPLyydbhtQ7W+/3e7l7T8aLszHvvvWe33nqrHXXUUTmWpaWlWffu3V2WRtcP0i9Upd9fffVV92s4L506dXKZHF13KPeVu7t06WJVqlSJ2b7PmDHDXSxS9y+99JJ7Tv95td9z5851y32LFi2yr7/+OnKBR7/J5ccffyzSL3Udi47jpJNOiszX6/LEE0+459C+TJ8+3e64444cj925c6f97W9/s3/9618uO6JMiZrefI8++qjbf2XAPvnkE/fevP766zm2MXLkSBs3bpzLsOm5Bg0aZNdff32kyUxNjXqdO3fu7Jo3brrpJhs6dGiRX9vVq1dbenq6tW7dukiPK8xrGovjVFPpVVdd5TIgX331lf3+97+3u++++6Dn0muubMg//vEPtx1lmvr37+8ycWpK1efh6quvto4dO7oMpugzo+muXbu65fr8az/1uMOh52zfvn2OeR06dHDzAZQwXgjFO4OjjE3zl5p7zcY2i9w0Ha9MzuzZs93xvP7663kuf+yxx9zy9evXu+mnnnrK/eqtVKmSd8EFF3h//vOfve+//z7HY4YOHerVr1/fy87OdtPfffedl5SU5H3wwQcHbf9wMjjKOigb4Lv66qu9a6+9NjKt9bV/PmV19IvZN2fOHO+kk07yVq1aVeDzKiOljItuOo7KlSt77777boH7q2xD1apVI9N+FkWvhe/pp5/2atasGZmuVauW9/DDD0em9+7d67IHfmZDmQe99p999lmO5+rdu7fXrVu3yDFGZ13kzjvvLFQGp2zZsu4YlbXQ+nqtoh/jv1f+a+Hfop+vMK9pLI5Tx6SMWbS77777oAyOpr/88svIOsosKpOzevXqHI9t166de+3857n55psPyujoc1CYv+38MjhlypTxJkyYkGOePgM1atQ4aF0yOEDskcFJsJVbV1q2l7NOQNOZ2zLj+rzK0BREhcfSr18/V1CqGg3V56hoUjUq06ZNi6yrYmX9wlZmRZTxUBHrhRdeWKh9Ub2Nf7vlllvyXU/Pq4JYX61atWzDhg2RaWVxJkyYEDk+1Rdpnq9Vq1auOPrYY48tcH+UqVE2RLf58+db37593a/+zz//PLKOrozdrl07t61KlSrZ7373O1egrQyC7+ijj7aGDRvmub+q51EtS3TGpHTp0q42yqfaKG1PdVDRr5EyHX6masmSJQdlXfQ+FcYf//hHd4zKWvgF3aqpyp2t+/jjjyOvh27RhbKHek1jdZyqAzrjjDNybFvPndfntnnz5pHphQsXuuNRMXD0tpUZ8retjJAyTNHLlWlR/Y4+1wCCj5GM46Be5XqWnJScI8jRdN1KdePyfCeccIJrUtCJ8corrzxouearaSq6aUkncDWB6Hb//fe7L3/d64QkKsJVTyMFNueff747MfXp08c9T2FE9xypXLlyvuupx1Y0bT+6iFQFnXfeeactWLDAfvnlF9d8c+2111pR6SSp18l32mmn2RtvvGGjR4+2l19+2TXHqOhYgc8DDzxgqamprklDxaR79uxxgU1++3uowDLa9u3b3f3bb799UABRrlw5O1zVqlWLHKfeQx2fgiMFqtFNK/Xr149pU2M8j1PNrtGfO21bQbEC1ejgWBTI+OuoySuvAut69erZr6UmX/U0jKZpzQdQshDgxEFahTQb3ma4jcgY4YIcBTea1vx4UM8kBSbPPPOMq3OIrsPxMzXK2uRHJw91e1UPkWg6ueuEf9lll7l6jry6fucnOpg4HOrCft5557ljUICj44xVbx+dHLVN0clSgZVqS1SLI+oxVhTqfq6Mzpw5c+zcc8918/bt2+e2ra710rRpU3eCV+2OjisvTZo0sbfeeivHvNmzZ//qYxT/OGMhVseprFruLtbqnXcoCk6VwVHmTEF4XrQfixcvjtnn0KdgUZmx6C7kynwWNsMGoPgUSxPV008/7Zo3NG6E0toqHC1IQeNMqJuzftFrfoUKFdxYHz169LA1a9ZYSdKlURd7r+t79s8O/3T3mo6np556yhXPKhOj4ldlOqZOneoCAqXy77333khmRWPeqMhYJwA1JbzwwguuWFTzo6kJRxkL/RLWuB5168YnA3UoapJSMak+F9HNU0WhE7CCPd1UiKpslY7fP2adCPXZevLJJ12XexURqzi2qDTu0EMPPeSyQ2rmUeF39OB8ypypKFmBqAqZ1aSi7JSeV9OiJj3to5qb1IyjJrrcBd/52bZtmztGNSHp70zbUPburLPOyrGeggP/9fBvOv7iPE59rvRY/T1/++23LqD0j7OgTKE+z/oc6O9eQx2oyUnHqqJmZYxE21TArqJifeb1er755puHLDL2m+yUAfr555/d//U5iT5u/V0pENa+a4wlNXNGb1cF19GP03uo6bCPMwQUOy/O/vOf/7jCx3/+85/eokWLvD59+nhVqlSJFLzm9umnn7oCQhUwqujunnvucYV9CxcudMu3bNnitW/f3ps4caK3dOlSLyMjw3URbdGiRYnrJl7cVNSowl0VvaqQVsfYpUsXb8eOHZF1fv75Z+8Pf/iDK+6sWLGiKzQ+5ZRTvL/97W+uW29uKtTUdl555ZV8n/dwu4lHU2GnCjyjabvqRqyi1ehu29HPrWMv6Hm1jn/TdnTMY8aMOagYW8WzRx11lOv+PW7cuDy7LEdTYXf0n5GKbXUMKmLW53zw4MEHdZ9W4fbo0aNdIa8+29WrV3fPN3PmzMg66enpke7T55xzjvv7KUyRcfRxarudOnXKUeTqv1553fS3VNjXNFbHmbubuN4TPbf/95fXay4aSuDee+91XdC1bb1vV155pff1119H1tEQABdddJH7nKuQWt3UH3jgAa8geb0uel2j6W/hxBNPdN9rJ598suvSH80vjM590+cwP0fy9w5QUouM4x7gKPjo169fZFonUY1dMXLkyDzXL8o4E9FfZDrg3OO2hC3AyU0nAH25+yeuINKJXydIf+wcHNmv6f333+96Y4VNkL53gFD0olJxptrlo4sbVd+g6fzGjfg140yoV4dS2vkVTarpZuvWrTluYTBixAg3rovqN+I1+muiqfnywQcfPKj4F0fGa6q6MdXd+M2CjzzyiPXs2TPuzwsg+OJaZLxx40ZXDKhB46JpWu3XeVE7dV7r59d+vWvXLtfert42+fXWUdu8TvZh1KtXLwsy1eXgyH1N/Xoo1a2od9OQIUNs2LBhxfb8AILriO5FpaLIa665xnXTHTNmTL7r6Qtz8ODBkWllcBJVMAvgf0aNGuVuAHBEBTgak0PdVIsybkRhx5nwg5uffvrJDalf0Fgr6q4aizFGAADAkSGuNTgaXK1FixaREVVFtSD+Fa4LGmciWu5xJvzgRultjT6bqCtUAwCAkDZRqWlIRYMaxl3DsGtk1R07dkRqQzSWhUY6VZ2MP86EBgbTOBMaYl7jn2icieeeey4S3OgCfRpTY8qUKa7Gx6/P0eiz/uUIDldRRqcFgMPB9w1wBAY4GlZfA2ZpoDkFIr/5zW/cQFl+IbFGOvVHjhUNSKaBze655x6766673HDzGkysWbNmbrlG1PVHedW2omk4el1W4HD4PUd0HZ3cV+YGgHjwr3dGb0AgdpLUV9xCRkXGGm5e3cvzqt3RKLAalVWXBNA1iAp7/SUAKAp9/Sq40cjSGuZCl8AA8OvP34HpRRUvfkFz9FWtASBeFNxwwU4gtghw8qCMjX5JKYNTlOvzAEBRqVkq91XRARw+ApwC6EuHLx4AAI48xXI1cQAAgOJEgAMAAAKHAAcAAAQOAU6MrduxzuaunevuAQBAYlBkHEOTl0+2ERkjLNvLtuSkZBveZrh1adQl0bsFAEDokMGJEWVs/OBGdK9pMjkAABQ/ApwYWbl1ZSS48Wk6c1tmwvYJAICwIsCJkXqV67lmqWiarlupbsL2CQCAsCLAiZG0Cmmu5sYPcvwaHM0HAADFiyLjGFJB8Vm1z3LNUsrcENwAAJAYBDgxpqCGwAYAgMSiiQoAAAQOAQ4AAAgcAhwAiKWs1WYrZh24B5Aw1OAAQKwsGGeWPtBMY2KpR2Xnx81O75HovQJCiQwOAMSCMjZ+cCO6T7+NTA6QIAQ4ABALm77/X3Dj8/abbfohUXsEhBoBDgDEQmrDA81S0ZJKmaU2SNQeAaFGgAMAsZBy7IGaGwU1ovvOow/MB1DsKDIGgFhRQXHDdgeapZS5IbgBEoYABwBiSUENgQ2QcAQ4MbQ/27O5KzbZhm27rEal8taqfqqVSk5K9G4BABA6BDgxMvWbtTYifbGtzdoVmVcrpbwN79zUOjarldB9AwAgbCgyjlFw0/flBTmCG1mXtcvN13IAAFB8CHBi0CylzI2XxzJ/npZrPQAAUDwIcA6Tam5yZ26iKazRcq0HAACKBwHOYVJBcSzXAwAAh48A5zCpt1Qs1wMAAIePAOcwqSu4ekvl1xlc87Vc6wEAgOJBgHOYNM6NuoJL7iDHn9ZyxsMBAITB/mzPMr7/r7355Wp3n6hONsUS4Dz99NN2/PHHW/ny5a1169Y2d+7cAtefNGmSNW7c2K1/yimn2DvvvJNjued5du+991qtWrXsqKOOsvbt29vy5cstUTTOzZjrT7e0lJzNUJrWfMbBAcJj3Y51NnftXHcPhM3Ub9Za279Ot27Pz7aB//nS3Ws6EcOlJHmKFuJo4sSJ1qNHD3v22WddcDN69GgXwCxbtsxq1Khx0PqfffaZnXvuuTZy5Ei79NJLbcKECfbXv/7VFixYYM2aNXPraFrLX3rpJatfv7796U9/soULF9rixYtdUHQoW7dutZSUFMvKyrLKlSvH7FgZyRgIt8nLJ9uIjBGW7WVbclKyDW8z3Lo06pLo3QKKdUy43EGFfxaMxQ/+opy/4x7gKKg544wz7KmnnnLT2dnZVrduXRswYIANHTr0oPWvvfZa27Fjh02ZMiUy78wzz7Tf/OY3LkjS7tauXduGDBlit99+u1uuA61Zs6aNHTvWrrvuuoQFOADCSxmbDq91cMGNT0HOe13fs7QKaQndNyDe9ANfmZr8hk1J+r9WjU/uvPCwfvgX5fwd1yaqPXv22Pz5810TUuQJk5PddEZGRp6P0fzo9aVDhw6R9VesWGHr1q3LsY4OVoFUftvcvXu3e1GibwAQSyu3rswR3IimM7dlJmyfgDCPCRfXAGfjxo22f/9+l12JpmkFKXnR/ILW9++Lsk01ZykI8m/KIAFALNWrXM9lbKJpum4lvm8QfBtK4JhwoehFNWzYMJfO8m+ZmfyiAhBbaoZSzY0f5Pg1ODRPIQxqlMAx4eJ6NfFq1apZqVKlbP369TnmazotLe8/es0vaH3/XvPUiyp6HdXp5KVcuXLuBgDxdHnDK6383ia2bNMKOym1vnVo2DjRuwQU65hwusi0V0ANTnGOCRfXDE7ZsmWtRYsW9uGHH0bmqchY023atMnzMZofvb5MmzYtsr56TSnIiV5HNTVz5szJd5sAUFzdY28d94M9PsVz94nqHgsUt5I4Jlzcm6gGDx5szz//vOvSvWTJEuvbt6/rJdWrVy+3XF3I1YTkGzhwoE2dOtUeffRRW7p0qd133332+eefW//+/d3ypKQku+222+z++++3t956y3UP1zbUs+qKK66I9+EAQL7dY3MXWerXrOYT5CAMOpawMeHi2kTld/v++eef3cB8KgJWM5ICGL9IeOXKla5nle+ss85yY9/cc889dtddd1mjRo3sjTfeiIyBI3fccYcLkm6++WbbsmWLtW3b1m2zMGPgAECsu8eOSF+cZ1pe8/R7VcsvaprGuFgIvI7NarnPekkYEy7u4+CURIyDAyBWNBS9Rms9lH/3OdPaNKxaLPsEBFWJGQcHAIKuJHaPBUCAAwCB6x4LgAAHAGLSPTa/CgPNr1XM3WMBEOAAQOC6xwIgwAGAwHWPBVAM3cQBIAxKUvdYAAQ4ABAzCmboCg6UDDRRAQCAwCHAAQAAgUOAAwAAAocABwAABA4BDgAAiKl1O9bZ3LVz3X2i0IsKAADEzOTlk21ExgjL9rItOSnZhrcZbl0adbHiRgYHAADEhDI2fnAjutd0IjI5BDgAACAmVm5dGQlufJrO3JZpxY0ABwAAxES9yvVcs1Q0TdetVNeKGwEOAACIibQKaa7mxg9y/BoczS9uFBkDAICYUUHxWbXPcs1SytwkIrgRAhwAABBTCmoSFdj4aKICAACBQ4ADAAAChwAHAAAEDgEOAAAIHAIcAAAQOAQ4AAAgcAhwAABA4BDgAACAwCHAAQAAgUOAAwAAAocABwAABA4BDgAACBwCHAAAEDgEOLGWtdpsxawD9wAAIHgBzqZNm6x79+5WuXJlq1KlivXu3du2b99e4GN27dpl/fr1s6pVq1rFihWta9eutn79+sjyr776yrp162Z169a1o446ypo0aWKPP/64lQgLxpmNbmb2UucD95oGAADBCnAU3CxatMimTZtmU6ZMsVmzZtnNN99c4GMGDRpk6enpNmnSJJs5c6atWbPGunTpElk+f/58q1Gjhr388stu23fffbcNGzbMnnrqKUsoZWzSB5p52QemdZ9+G5kcAAASIMnzPC8eG16yZIk1bdrU5s2bZy1btnTzpk6dap06dbJVq1ZZ7dq1D3pMVlaWVa9e3SZMmGBXXXWVm7d06VKXpcnIyLAzzzwzz+dSxkfPN3369ELt29atWy0lJcU9n7JLMaFmKWVucus5xaz+ObF5DgAAQmxrEc7fccvgKCBRs5Qf3Ej79u0tOTnZ5syZk+djlJ3Zu3evW8/XuHFjq1evnttefnSgqamp+S7fvXu3e1GibzGX2tAsKdfLmVTKLLVB7J8LAAAkJsBZt26da0qKVrp0aReIaFl+jylbtqwLjKLVrFkz38d89tlnNnHixAKbvkaOHOkiPv+m+p2YSznWrPPjB4Ia0X3n0QfmAwCAkh3gDB061JKSkgq8qVmpOHzzzTd2+eWX2/Dhw+23v/1tvuupRkdZHv+WmZkZnx06vYfZbQsPNEvpXtMAAKDYlS7qA4YMGWI33HBDges0aNDA0tLSbMOGDTnm79u3z/Ws0rK8aP6ePXtsy5YtObI46kWV+zGLFy+2du3auczNPffcU+D+lCtXzt2KhTI2ZG0AADiyAhwVAet2KG3atHGBiupqWrRo4eapCDg7O9tat26d52O0XpkyZezDDz903cNl2bJltnLlSrc9n3pPXXjhhdazZ0974IEHinoIAAAg4OLWi0ouvvhil3159tlnXfFwr169XNGxeknJ6tWrXRZm3Lhx1qpVKzevb9++9s4779jYsWNdhfSAAQMitTZ+s5SCmw4dOtgjjzwSea5SpUoVKvCKWy8qAAAQV0U5fxc5g1MU48ePt/79+7sgRr2nlJV54oknIssV9ChDs3Pnzsi8UaNGRdZV7ycFMs8880xk+auvvmo///yzGwdHN99xxx1nP/74YzwPBwAAHCHimsEpqcjgAABw5CkR4+AAAAAkCgEOAAAIHAIcAAAQOAQ4AAAgcAhwAABA4BDgAACAwCHAAQAAgUOAAwAAAocABwAABA4BDgAACBwCHAAAEDgEOAAAIHAIcAAAQOAQ4AAAgMAhwAEAAIFDgAMAAAKHAAcAAAQOAQ4AAAgcAhwAABA4BDgAACBwCHAAAEDgEOAAAIDAIcABAACBQ4ADAAAChwAHAAAEDgEOAAAIHAIcAAAQOAQ4AAAgcAhwAABA4BDgAACAwCHAAQAAgUOAAwAAAocABwAABE5cA5xNmzZZ9+7drXLlylalShXr3bu3bd++vcDH7Nq1y/r162dVq1a1ihUrWteuXW39+vV5rvvf//7X6tSpY0lJSbZly5Y4HQUAADjSxDXAUXCzaNEimzZtmk2ZMsVmzZplN998c4GPGTRokKWnp9ukSZNs5syZtmbNGuvSpUue6ypgat68eZz2HgAAHKmSPM/z4rHhJUuWWNOmTW3evHnWsmVLN2/q1KnWqVMnW7VqldWuXfugx2RlZVn16tVtwoQJdtVVV7l5S5cutSZNmlhGRoadeeaZkXXHjBljEydOtHvvvdfatWtnmzdvdlmiwti6daulpKS451N2CQAAlHxFOX/HLYOjgEQBhx/cSPv27S05OdnmzJmT52Pmz59ve/fudev5GjdubPXq1XPb8y1evNj+/Oc/27hx49z2DmX37t3uRYm+AQCA4IpbgLNu3TqrUaNGjnmlS5e21NRUtyy/x5QtW/agTEzNmjUjj1Gw0q1bN3vkkUdc4FMYI0eOdBGff6tbt+6vPi4AABDAAGfo0KGuqLegm5qV4mXYsGGuyer6668v0mOUzvJvmZmZcds/AACQeKWL+oAhQ4bYDTfcUOA6DRo0sLS0NNuwYUOO+fv27XM9q7QsL5q/Z88e1yMqOoujXlT+Y6ZPn24LFy60V1991U37JUTVqlWzu+++20aMGHHQdsuVK+duAAAgHIoc4KgIWLdDadOmjQtUVFfTokWLSHCSnZ1trVu3zvMxWq9MmTL24Ycfuu7hsmzZMlu5cqXbnrz22mv2yy+/RB6jIuYbb7zRPv74Y2vYsGFRDwcAAARQkQOcwlIzUseOHa1Pnz727LPPuuLh/v3723XXXRfpQbV69WrXA0rFwq1atXL1Mer6PXjwYFerowrpAQMGuODG70GVO4jZuHFj5PkK24sKAAAEW9wCHBk/frwLahTEqLeTsjJPPPFEZLmCHmVodu7cGZk3atSoyLoqKO7QoYM988wz8dxNAAAQMHEbB6ckYxwcAACOPCViHBwAAIBEIcABAACBQ4ADAAAChwAHAAAEDgEOAAAIHAIcAAAQOAQ4AAAgcAhwAABA4BDgAACAwCHAAQAAgUOAAwAAAocABwAABA4BDgAACBwCHAAAEDgEOAAAIHAIcAAAQOAQ4AAAgMAhwAEAAIFDgAMAAAKHAAcAAAQOAQ4AAAgcAhwAABA4BDgAACBwCHAAAEDgEOAAAIDAIcABAACBQ4ADAAAChwAHAAAEDgEOAAAIHAIcAAAQOAQ4AAAgcAhwAABA4BDgAACAwIlbgLNp0ybr3r27Va5c2apUqWK9e/e27du3F/iYXbt2Wb9+/axq1apWsWJF69q1q61fv/6g9caOHWvNmze38uXLW40aNdxjAAAA4h7gKLhZtGiRTZs2zaZMmWKzZs2ym2++ucDHDBo0yNLT023SpEk2c+ZMW7NmjXXp0iXHOo899pjdfffdNnToULf9Dz74wDp06BCvwwAAAEegJM/zvFhvdMmSJda0aVObN2+etWzZ0s2bOnWqderUyVatWmW1a9c+6DFZWVlWvXp1mzBhgl111VVu3tKlS61JkyaWkZFhZ555pm3evNmOPfZYFwS1a9fuV+/f1q1bLSUlxT2nMkwAAKDkK8r5Oy4ZHAUkapbygxtp3769JScn25w5c/J8zPz5823v3r1uPV/jxo2tXr16bnuibFB2dratXr3aBT516tSxa665xjIzMwvcn927d7sXJfoGAACCKy4Bzrp161xtTLTSpUtbamqqW5bfY8qWLesCo2g1a9aMPOaHH35wAc6DDz5oo0ePtldffdXV+lx00UW2Z8+efPdn5MiRLuLzb3Xr1o3JcQIAgAAEOKp7SUpKKvCmZqV4UXCjLM8TTzzh6m7UbPXvf//bli9fbjNmzMj3ccOGDXPpLP92qIwPAAA4spUuyspDhgyxG264ocB1GjRoYGlpabZhw4Yc8/ft2+eyLVqWF81XFmbLli05sjjqReU/platWu5e9T0+1e1Uq1bNVq5cme8+lStXzt0AAEA4FCnAUTCh26G0adPGBSqqq2nRooWbN336dJeBad26dZ6P0XplypSxDz/80HUPl2XLlrnARduTs88+OzJf9TeioGnjxo123HHHFeVQAABAgMWlF5VcfPHFLvvy7LPPumalXr16uaJj9ZISFQqrJ9S4ceOsVatWbl7fvn3tnXfecePcqDp6wIABbv5nn30W2e4VV1xh3333nT333HNuHTU/qTbnyy+/dAFSYdCLCgCAI0/Ce1HJ+PHjXS8oBTHqHt62bVsXlPgU9CgTs3Pnzsi8UaNG2aWXXuoyOOeee65rmpo8eXKO7SogUhbokksusfPOO88FNeqCXtjgBgAABF/cMjglGRkcAACOPCUigwMAAJAoBDiIqXU71tnctXPdPQAAR0QvKqAgk5dPthEZIyzby7bkpGQb3ma4dWmU81piAAAUBzI4iAllbPzgRnSvaTI5IZS12mzFrAP3AJAgZHAQEyu3rowENz5NZ27LtLQKeQ/uiABaMM4sfaCZPgtJyWadHzc7vUei9wpACJHBQUzUq1zPNUtF03TdSlz3KzSUsfGDG9F9+m1kcgAkBAEOYkJZGtXc+EGOX4ND9iZENn3/v+DG5+032/RDovYIQIjRRIWYUUHxWbXPcs1SytwQ3IRMasMDzVLRQU5SKbPUBoncKwAhRQYHMaWg5oy0Mwhuwijl2AM1NwpqRPedRx+YDwDFjAwOgNhRQXHDdgeapZS5IbgBkCAEOABiS0ENgQ2ABKOJCgAABA4BDgAACBwCHAAAEDgEOAAAIHAoMkbM7M/2bO6KTbZh2y6rUam8taqfaqWSkxK9WwCAECLAQUxM/WatjUhfbGuzdkXm1Uopb8M7N7WOzWoldN8AAOFDExViEtz0fXlBjuBG1mXtcvO1HACA4kSAg8NullLmxstjmT9Py7UeAADFhQAHh0U1N7kzN9EU1mi51gMAoLgQ4OCwqKA4lusBABALFBnjsKi3VCzXw5GNnnQASgoCHBwWncDUW0oFxXlV2ejUlpZy4ESHYKMnHYCShCYqHBb9OtcJTHL/TventZxf8cFGTzoAJQ0BDg6bfp2Puf50l6mJpmnN59d7sNGTDkBJRBMVYkJBzEVN06i/CKGi9KRr07Bqse4bgPAiwEHMKJjhBBY+uXvIJZXOsuSyGy17TzXz9qXkux4AxBMBDoDDEt1DrkzKPCtXa7IlJXnmeUm2e20X25t1xkHrAUC8UYMDICY96ZJLZ0WCG9G9pjVfy+lJB6A4EeAAiElPOjVL+cGNT9OaT086AMWNAAdATIrM77/0fDMvVxDjJbn59KQDUNwIcADExHWnN7fhbYZb8v99rehe05oPAMWNImMAMXPVSV2tbZ2zLXNbptWtVNfSKqQlepcAhFTcMjibNm2y7t27W+XKla1KlSrWu3dv2759e4GP2bVrl/Xr18+qVq1qFStWtK5du9r69etzrDNv3jxr166d2+YxxxxjHTp0sK+++ipehwGgiBTUnJF2BsENgGAGOApuFi1aZNOmTbMpU6bYrFmz7Oabby7wMYMGDbL09HSbNGmSzZw509asWWNdunSJLFeA1LFjR6tXr57NmTPHPvnkE6tUqZILcvbu3RuvQwEAAEeYJM/zYj5++pIlS6xp06Yu29KyZUs3b+rUqdapUydbtWqV1a5d+6DHZGVlWfXq1W3ChAl21VVXuXlLly61Jk2aWEZGhp155pn2+eef2xlnnGErV660unXrunUWLlxozZs3t+XLl9sJJ5xQqP3bunWrpaSkuOdUhgkAAJR8RTl/xyWDo4BETUh+cCPt27e35ORkl3nJy/z5810WRuv5Gjdu7LI12p6cdNJJrvnqhRdesD179tgvv/zi/q8g6Pjjj893f3bv3u1elOgbAAAIrrgEOOvWrbMaNWrkmFe6dGlLTU11y/J7TNmyZV1gFK1mzZqRx6g56qOPPrKXX37ZjjrqKFeno8zQu+++67afn5EjR7qIz7/52R8AABBMRQpwhg4daklJSQXe1KwUL8rYqFj57LPPttmzZ9unn35qzZo1s0suucQty8+wYcNcOsu/ZWZmxm0fAQDAEdZNfMiQIXbDDTcUuE6DBg0sLS3NNmzYkGP+vn37XM8qLcuL5qvZacuWLTmyOOpF5T9G9Tk//vija7JSc5c/T72p3nzzTbvuuuvy3Ha5cuXcDQAAhEORAhwVAet2KG3atHGBiupqWrRo4eZNnz7dsrOzrXXr1nk+RuuVKVPGPvzwQ9c9XJYtW+YKirU92blzpwtslCny+dPaNgAAQNxqcFT0q+7cffr0sblz57qmpP79+7sMi9+DavXq1a6IWMtFtTFqfho8eLDNmDHDBUe9evVywY16UMlFF11kmzdvdmPlqKeWuqFrHdXfXHDBBbyjAAAgvuPgjB8/3gUwGpRP3cPbtm1rzz33XGS5ekwpQ6OsjG/UqFF26aWXugzOueee65qmJk+eHFmu7WmcnK+//toFPuecc44bK0eFxrVqca0bAAAQx3FwSjrGwQEA4MiT8HFwAAAAEokABwAABA4BDhBLWavNVsw6cA8AODK6iQMowIJxZukDzbxss6Rks86Pm53eI9F7BQChRAYHiAVlbPzgRnSffhuZHABIEAIcIBY2ff+/4Mbn7Tfb9EOi9ggAQo0AB4iF1IYHmqWiJZUyS22QqD0CgFAjwAFiIeXYAzU3CmpE951HH5gPACh2FBkDsaKC4obtDjRLKXNDcAMACUOAA8SSghoCGwBIOJqoAABA4BDgAACAwCHAAQAAgUOAAwAAAocABwAABA4BDgAACBwCHAAAEDgEOAAAIHAIcAAAQOAQ4AAAgMAhwAEAAIFDgAMAAAKHAAcAAAQOAQ4AAAgcAhwAABA4BDgAACBwCHAAAEDgEOAAAIDAIcABAACBQ4ADAAAChwAHAAAEDgEOAAAInLgFOJs2bbLu3btb5cqVrUqVKta7d2/bvn17gY957rnn7Pzzz3ePSUpKsi1btsRkuwAAIFziFuAoCFm0aJFNmzbNpkyZYrNmzbKbb765wMfs3LnTOnbsaHfddVdMtwsAAMIlyfM8L9YbXbJkiTVt2tTmzZtnLVu2dPOmTp1qnTp1slWrVlnt2rULfPxHH31kF1xwgW3evNllaWK1Xd/WrVstJSXFsrKyXCYIAACUfEU5f8clg5ORkeECEz8Ikfbt21tycrLNmTOnxG0XAAAES+l4bHTdunVWo0aNnE9UurSlpqa6ZcW93d27d7tbdAQIAACCq0gZnKFDh7ri34JuS5cutZJm5MiRLqXl3+rWrZvoXQIAACUlgzNkyBC74YYbClynQYMGlpaWZhs2bMgxf9++fa4HlJb9Wr92u8OGDbPBgwfnyOAQ5AAAEFxFCnCqV6/ubofSpk0b18V7/vz51qJFCzdv+vTplp2dba1bt/7VO/trt1uuXDl3QzHIWm226Xuz1IZmKccmem8AACEVlyLjJk2auO7effr0sblz59qnn35q/fv3t+uuuy7S02n16tXWuHFjt9ynOpovv/zSvvvuOze9cOFCN60MTWG3iwRaMM5sdDOzlzofuNc0AABBGgdn/PjxLoBp166d68bdtm1bN5Cfb+/evbZs2TI39o3v2WeftdNOO80FMHLuuee66bfeeqvQ20UCMzfpA8287APTuk+/7cB8AACCMA5OScc4OHGwYtaBzE1uPaeY1T/HwmB/tmdzV2yyDdt2WY1K5a1V/VQrlZyU6N0CgFCev+PSTRwhpJqbpOT/ZXAkqZRZagMLg6nfrLUR6YttbdauyLxaKeVteOem1rFZrYTuGwCEERfbRGyooLjz4weCGtF959GhKDRWcNP35QU5ghtZl7XLzddyAEDxIoOD2Dm9h1nDdmabfjiQuQlBcKNmKWVu8mrn1Tw1UGn5RU3TaK4CgGJEBgexpaBGNTchCG5ENTe5Mze5gxwt13oAgOJDgAMcBhUUx3I9AEBsEOAAh0G9pWK5HgAgNghwgMOgruDqLZVfdY3ma7nWAwAUHwIc4DCocFhdwSV3kONPazkFxgBQvAhwgMOkcW7GXH+6paXkbIbStOYzDg4AFD+6iQMxoCBGXcEZyRgASgYCHCBGFMy0aVg10bsBAKCJCgAABBEBDhBD63ass7lr57p7AEDi0EQFxMjk5ZNtRMYIy/ayLTkp2Ya3GW5dGnVJ9G4BQCiRwQFiQBkbP7gR3WuaTA4AJAYBDhADK7eujAQ3Pk1nbstM2D4BQJgR4AAxUK9yPdcsFU3TdSvVTdg+AUCYEeAAMZBWIc3V3PhBjl+Do/kAgOJHkTEQIyooPqv2Wa5ZSpkbghsASBwCHCCGFNQQ2ABA4tFEBQAAAocABwAABA4BDgAACBwCHAAAEDgEOAAAIHAIcAAAQOAQ4AAAgMAhwAEAAIFDgAMAAAKHAAcAAAQOAQ4AAAicUF6LyvM8d79169ZE7woAACgk/7ztn8cLEsoAZ9u2be6+bt26id4VAADwK87jKSkpBa6T5BUmDAqY7OxsW7NmjVWqVMmSkpJiHl0qcMrMzLTKlStb2HD84T5+CftrEPbjl7C/BmE//ni+BgpZFNzUrl3bkpMLrrIJZQZHL0qdOnXi+hx6Q8P6wRaOP9zHL2F/DcJ+/BL21yDsxx+v1+BQmRsfRcYAACBwCHAAAEDgEODEWLly5Wz48OHuPow4/nAfv4T9NQj78UvYX4OwH39JeQ1CWWQMAACCjQwOAAAIHAIcAAAQOAQ4AAAgcAhwAABA4BDgxNDTTz9txx9/vJUvX95at25tc+fOtbCYNWuWde7c2Y0uqdGh33jjDQuTkSNH2hlnnOFGx65Ro4ZdccUVtmzZMguTMWPGWPPmzSMDe7Vp08beffddC6uHHnrI/S3cdtttFgb33XefO97oW+PGjS1sVq9ebddff71VrVrVjjrqKDvllFPs888/tzA4/vjjD/oM6NavX7+E7A8BToxMnDjRBg8e7LrFLViwwE499VTr0KGDbdiwwcJgx44d7pgV5IXRzJkz3R/x7Nmzbdq0abZ371777W9/616XsNDo4Dqpz58/332hX3jhhXb55ZfbokWLLGzmzZtnf//7313AFyYnn3yyrV27NnL75JNPLEw2b95sZ599tpUpU8YF94sXL7ZHH33UjjnmGAvL535t1Puv70K5+uqrE7ND6iaOw9eqVSuvX79+ken9+/d7tWvX9kaOHOmFjT5Wr7/+uhdmGzZscK/DzJkzvTA75phjvH/84x9emGzbts1r1KiRN23aNO+8887zBg4c6IXB8OHDvVNPPdULszvvvNNr27ZtonejxBg4cKDXsGFDLzs7OyHPTwYnBvbs2eN+tbZv3z7H9a40nZGRkdB9Q2JkZWW5+9TUVAuj/fv323/+8x+XwVJTVZgok3fJJZfk+D4Ii+XLl7tm6gYNGlj37t1t5cqVFiZvvfWWtWzZ0mUs1FR92mmn2fPPP29hPS++/PLLduONN8b8otaFRYATAxs3bnRf6DVr1swxX9Pr1q1L2H4hcVerV92FUtXNmjWzMFm4cKFVrFjRjV56yy232Ouvv25Nmza1sFBQpyZq1WSFjeoOx44da1OnTnX1WCtWrLBzzjnHXfk5LH744Qd37I0aNbL33nvP+vbta3/4wx/spZdesrB54403bMuWLXbDDTckbB9CeTVxIN6/4L/55pvQ1R/ISSedZF9++aXLYL366qvWs2dPV58UhiAnMzPTBg4c6OoO1NEgbC6++OLI/1V7pIDnuOOOs1deecV69+5tYflxowzOgw8+6KaVwdF3wbPPPuv+FsLkhRdecJ8JZfQShQxODFSrVs1KlSpl69evzzFf02lpaQnbLxS//v3725QpU2zGjBmu6DZsypYtayeccIK1aNHCZTFUeP74449bGKiZWp0KTj/9dCtdurS7Kbh74okn3P+V5Q2TKlWq2IknnmjfffedhUWtWrUOCuabNGkSuqa6n376yT744AO76aabErofBDgx+lLXF/qHH36YI5LXdNjqD8JKtdUKbtQkM336dKtfv36id6lE0N/B7t27LQzatWvnmuiUwfJv+jWvWhT9Xz+CwmT79u32/fffu5N+WKhZOvfwEN9++63LZIXJiy++6GqQVIuWSDRRxYi6iCsFqS+0Vq1a2ejRo12BZa9evSwsX2bRv9TU/q4vdRXZ1qtXz8LQLDVhwgR788033Vg4fu1VSkqKGwsjDIYNG+ZS0nq/VXeh1+Ojjz5ytQhhoPc9d81VhQoV3HgoYajFuv32291YWDqZr1mzxg2ZoaCuW7duFhaDBg2ys846yzVRXXPNNW4stOeee87dwvSj5sUXX3TnQ2UuEyohfbcC6sknn/Tq1avnlS1b1nUbnz17thcWM2bMcN2ic9969uzphUFex67biy++6IXFjTfe6B133HHu81+9enWvXbt23vvvv++FWZi6iV977bVerVq13Pt/7LHHuunvvvvOC5v09HSvWbNmXrly5bzGjRt7zz33nBcm7733nvvuW7ZsWaJ3xUvSP4kNsQAAAGKLGhwAABA4BDgAACBwCHAAAEDgEOAAAIDAIcABAACBQ4ADAAAChwAHAAAEDgEOAAAIHAIcAAAQOAQ4AAAgcAhwAABA4BDgAAAAC5r/D6v8b1wYvzhIAAAAAElFTkSuQmCC", + "image/png": "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dhfirVu3Zru9LrrdunWznj17uozCVVdd5W5Lliw5ZltlEtQ7SLN+FxYa62HsjedaSlLmVKwea3k0x4JQcKOi3RYtWtjbb7/tmn2WL1/umqBat259zPYKaNQEpJobNVFpOz87Iz///LP17dvX9YhS4KNeUCo2VgAiGlvno48+crMg67OdOXNmaF1Ww4YNc89VT6/vvvvOZZrGjh3rmqPUo0o9p8aPH+9qiZQp0t8MgJNHLL/7gGx5UdaiRQuvT58+ocdHjx71qlev7o0cOTLb7a+77jrv8ssvz7SsZcuW3p133plp2YYNG7xTTz3VW7JkiVe7dm1v1KhReT6m9PR0Fay4+6x+/vlnb9myZe7+RBw5muHN+X679+7XG9y9HheETZs2ufdb70nx4sXde/T73//emzlzpluv837nnXdC2993331exYoVvTJlynjXX3+9ex+TkpLcuoMHD3o33HCDV7NmTbcvfW59+/YNvTf6//r163slSpTwKleu7N10003e9u3b3Tq9nl5r586dodf67LPPvDZt2rjty5cv73Xo0CG0fvr06V6jRo3cuqZNm7ptw491zZo17vHXX39dIO8jUJAi9b1TGMTquw/xIT2X63dWCfqPRYnqJzTmyVtvveWyML5bbrnFNZm89957xzynVq1a7te7sgM+ZX80vYDGZBEVoKqo9corr7T+/fu75i9tH/6ccOrKrJtv9+7drmklPT09U0GtHDhwwGUk6tat65rIACDa+N4B8kbXb42Qn931u0CbqNT8oCaTqlWrZlqux5pSIDtafrztNd5K0aJFXS+evBg5cqR7Q/ybghsAABBcJ10vKg02pwHpNDhdXqcBUP2Hoj3/tn79+qgfJwAACGiAU6lSJStSpIht2bIl03I91jxF2dHy3Lb//PPPXYGymrKUxdFNBbDqwaOmquyUKFHCpbLCbwAAILgSoz3houYuCu8erPoZPc6uV49oedbuxNOnTw9trzFX1AtHA8f5N/Wi0mi76tEDAAAQ9ZGMVTCsomKNs6LuyxqvRuO09OjRw62/+eab7dRTT3V1MqKi4QsvvNCefPJJN7icujBrfiJ1IRZ1J9YtnOZGUobnjDPOiPbpAACAk0DUA5zrr7/etm3b5obhV6Hwb37zGzcppF9IrHFYNLGir02bNjZx4kQ36NsDDzzgxmpRDyqG6weAk0PavjRbt3ud1SpXy1JKZ1+OAERbVLuJn4zdzOiuCaCgBel7Z/KqyTYidYRleBmWmJBow1sPty4NusT6sBAQhaabOAAgvjI3fnAjutdjLQcKGgEOCowm7lQTZTyc26233pppcMvCSMMsqPkXiBQ1S/nBjU+P1+9haA4UPAKcANH4PrfddpvrVaYebJpIU0XbmswynFLh/+///T+3ndLhNWrUcKNCa34odclX0baKu7OjOcLOPfdc1yVfF8icbrrAZ3XvvffmOOFmtIMP/7g0rICOXZOH7t27N2qv6Y/VlBPN7xX+fmm29TPPPDNUTO/T+5jd+9uxY0eLd5rg9Xe/+53rdKD3RD0qs2v66dOnj9umTJky1rVr12OGodCAoertqeEkghqAFxTV3KhZKpwe1yzL4KooeAQ4AbF69WrXU00TbL7++utu0spx48aFuuTv2LHDbXf48GG79NJLXfulLhCaxVsTomrWdk2foeJv9V578cUXj3kN9X578803XZCjiTM3b97sbprYU7Qvf5ku8FnpApO1B1xBUfCg41q7dq0bCVuBhMZOiha1EZcvX/642/nv2bJly+zOO++03r17HxMEKpjx31f/ps/4ZKDpWqJFf49t27Z1n2dOFMhOmTLFJk2aZLNmzbJNmzZZly7H1oPoh4E6RODEqKBYNTd+kOPX4FBojJjw4lBBTLbp7Nrgeatn/XIfZR07dvRq1Kjh7d+/P9PyzZs3e6eccop31113ucearFLnvnbt2hz39f7773uJiYnejz/+mGn5Sy+95JUsWTLTBJo5TayZneHDh3tnn3126PEtt9ziXXnlld4TTzzhpaSkeMnJyd7dd9/tHTp0yK0fNmyYm6w1K03GOWLEiFxfK7fXlTvuuMO9pkyYMMFr1qyZm3C0atWqXrdu3bwtW7ZkOm9/AlKfJgEN/+eT07nlJKf3TJOXPv7443neT17997//9c4//3w3makmNf3444+PmXh13bp13rXXXuvOtUKFCm6SVk1y6jt8+LDXr18/t16f1f333+/dfPPNmY7vwgsvdJO99u/f303ietFFF7nlixcvdn+jpUuX9qpUqeLdeOON3rZt2zJNwvvYY495derUcX9j+ownTZqUp3PLaSLWXbt2ecWKFcu0n+XLl7ttU1NT8/R3UlCCNNmmbN672Zu/eb67B2I12SYZnGhZNMFsdBOzVzr/cq/HUaLsjAY5vPvuu11TRziND9S9e3eXpVGHucqVK7tu+ZoAVfOEZadTp04uk5O1ieWll15yv37zkpnIq5kzZ9oPP/zg7l955RX3mv7r6rjnz5/v1vuWLl3qBnpUE1t4U48yM/mh98nPLiir9ec//9lN5qqaFO0ruya2aNJno+ETNGxCy5Yt8/VcHetFF12U43oNrqnPTc2W8+bNc5m9IUOGZNpG70GHDh2sbNmybrTwL7/80mXclD3y3ydlSl577TX3d6D16s2QXQ2PPke9lrbRaykzeMkll9g555zjxrTSeaqZ6Lrrrgs9R+NgTZgwwW2vz1iZlxtvvNFlXU5kWhedlybm9TVs2NCNgp6amvqr94vjU8bmvJTzyNwg2OPgxKX0jWZT+pv5xXa6nzLArH47s6RTI/5yapbSBbJRo0bZrtfynTt3uvGINKjimDFj7P7777cRI0a4Zq2LL77YBRP16tVz22t6DQ3OqEDjD3/4gwsgFGTowqdRpSOpQoUK9uyzz7rX1MVHzWNqornjjjtcs9LZZ5/txkXScYgusAoATjvtNPdYs9VrgEfVDeXnwqd96qLrN0/49B7o/TnvvPNcjY4u8tGk+ifRbPcKRP70pz/ZBRdckGmbqVOnHnMcGiNKN6lWrZp7bk4++eQTV1+lIFh1V/LYY4/ZZZddFtpGAbD28Y9//CM0x5sCGQWzCiJV6/LMM8+4ed2uvvpqt16f24cffnjM62nsqscffzz0+JFHHnHBjV7TpyZQTXr73//+19WKaZ2O0x+xXJ/DF198YX//+9/dwJ+/hsbdUqCVNSDPbbJfAMFBgBMNO374X3Dj846a7VgdlQAn9BLHGdJIX/aiokuNIK0L19y5c119gi4w77//vqvP8S/6f/nLX1xmRYGALnYqzvWDguMJvyDrl7h+mWdHQYyCG58u1osXLw49VuCli6ECHJ2fak80OrZPo2Pr4n082qeOSVkrZSQUSOkC7Qc8KkRWBkeBoB8sKJvSuHFjiyYFjcqaKMBRtqpv376WnJzsanF8CkDHjh2b6XnaxuePAp6T5cuXu2DCD24k61QpOnfVbelYshbpKrhVzZayLnq/ffrcVJybNbjSsqz71t9RdsGi9q0sy/79+0N/ez59TgqMAODXIMCJhuT6ZiqyCw9yEoqYJf+SIYk0ZTP0q1sXMv/XdTgtV9NU+C9ZXcg6d+7sbvqFreYJ3fsXGf0KP//8811go+YPNR8oq5LXGdzDe7TkNhhT1syL9h9+wezWrZtrTlm0aJH9/PPPrqfYrykGVZZHAZx6Ufm9zPxCVZ27bsoO6X1SYKPHftOMmvSyBo+6KEeCBnbzPxcFe2pCevTRRzMFOKVLlw5lrKJF2SoFJnoPstJ7kh863qz71t9ZdsXACmiXLFni/v+DDz5wGcZw6tn0a6l5Vp+hmsjC//Zzm+wXQHAQ4ESDsjSdn/6lWUqZGwU3nUdHLXujnkkKTJ5//nlXuxBeh6NUvC5aytrkREGFmofmzJmTabl6S+lC+/vf/942btyYr7qUSF2Q1YSjJgqdgwIcnWeVKlXyvR8FNNkdk7I/6kavbJWyHKI6kawX+D179rhgyL94Z9clORKUFdF5RpKaKBUYqveVAgpR5i6cuv6rmUrvbU4BqZp21HvOb0JTNkyB5/G6Vmvf6mmnDKACzKyUJVMgo8Dy1zZHZUcBmwJoNXmqe7jfa02vk9Nkv8CJOprh2fw1O2zrngNWpWxJa1E32Yok5u2HISKLACdazr35l5obNUspcxPFpilRc4vm8fIzMcoMqFhTs6yffvrpbi4w/8I8fPhwNyu7Liy68KuQU81AWQtPr732WjdGiLovqwbDDwAKmpqpdMz6NT5q1KiI7lsFp3oPVF9y1113uWyCCo7DqeZHtT6qedH7oSxLbmPc5MfWrVtdM5DfRPWvf/3LrrnmmkzbaF3WmhEFCpUqVcrTa6jIVn8Dqqt64oknXHHwgw8+eMx7rHUaD0l1QAosf/zxRzeUgOq19Lhfv36uOUyBogJivWdq0jteVk/B9QsvvOCycdqXmtfUHKaxllTzo2yixkhScK7snbp+q0lMRcoKtnTcORXXK1hR128/eBFlZ3RTV30F6WrS1GtqXzoHBTetWrUK7UfHoiyT3mMFl37w6v/7APJq2pLNNmLKMtucfiC0rFpSSRveubF1bPLLjwsUIC8OFVg38QKm7rLqVqyuzgkJCe4cu3Tp4u3bty+0jbrm3nPPPV6TJk1ct+iyZct6Z511lve3v/3NddXNqlevXm4/b775Zo6ve6LdxMOpe7G6GofTftW9Wd3d9+zZk+1rh3dnPt7rZjVx4kTXPVmv0bp1a9dNPmu3Y3WnPu2007xSpUp5V1xxhTd+/PiIdBP3b0WLFvXq1q3r3Xvvvd7evXsz7Sd8O/92xhlnZNom63uW1cqVK722bdt6xYsX904//XRv2rRpx3QT15AC6vZdqVIl917Uq1fPdaf3/52om3jfvn29cuXKuW7kQ4YMcd3Kb7jhhtA+dBz6DLPrpn711Vd75cuXd+9hw4YNvQEDBngZGRluve5Hjx7tzktduytXrux16NDBmzVrVo7npO772b03+ix8+nesoQd0vPr70THoPMPpmLPbT25/U5F2Mn/v4Bf/WbzJqzNkqlc7y63O/920HgXbTZzJNgM82aayHk899ZTr+RT+izVIVCOkAmkNlJefnlRBomYdFSKrULogKdui5i91986a9UL+BOl7J16bpdr+dUamzE045ThTkkraF0MuobmqACfbpIkqwNQNXHUPqrdQ7xcVywaNuikrwInX4Eb/yNUTSQW60aYmq48//tgFVGo2U7OoP+0HEM9UcxMe3KTYT1Y3Mc3WZKRYmlV0KUGt13at68dmNPd4RIATcD169LAgUxf3eKZfMhs2bCiQ11KArNoj1cso8dukSRM3dk1O4y8B8UIFxb7risy0kUX/YUUSPDvqJdiwI7fbm0cvPmY7RB8BDoA8UZG5Cn8BZKbeUn7mxg9uRPePFf2nzT7a1GVy/O1QMILXZgEAQAFSV3D1llKzlB/c+IomZFidxC1uvbZDwSHAAQDgBKhwWF3B12akuGapcEe8RPsxo6pbT4FxwSLAAQDgBGmcm+E3Xmp/LdbbBTWi+8eL3eWWMw5OwaMGBwCACFAQc2njx+ybJd3t5y2rrFTVBjakyZlkbmKEAAcAgAhRMHNq/Uq2rvJ+O7VcJYKbGCLAAQAgQiavmmwjUkdYhpdhiQmJNrz1cOvSoEusDysuUYODAqORdo83MWMQaZLSq6666oT28dlnn7k5nzQzdpDPEziZpe1LCwU3ons91nIUPAKcANGM0bfddptVr17dTRJYu3Zt69+/v5stO5w/+qy207DwmkhRkyxqZu0tW7a4UYE1EWJ2NHmhZofWCMm64OZ0y27mcQ0Qp5mdYxFYhR+bBsc7//zz3SSjQRL+mWhWcn2++rw0IWbWQCm7W9YJPeONZlvXvwtNTKpBDQcMGJDj4JKabFT/ds466yw3mnY4TVCqyWkrVqzo3tdozTyPwmfd7nWh4Manx+v3rI/ZMcUzApyAWL16tTVv3txWrVplr7/+upshedy4cS6g0OzJmnlZDh8+bJdeeqkb4l9fxJqB+Y033nBf1MoOVK1a1S6//HI3u3hW+/btszfffNNdNBcsWOAuCLq9/fbbbr325S97+umnj3l+mTJl3Jd+LJx55pmhY0tNTbUGDRrYFVdc4d6HINFM4DpHzbL92muv2ezZs90M6FmFf1b+rUqVKlbYHT161M2BFQ2afqJy5cr20EMP2dlnn53tNnPmzHGzouvfwNdff+0yVrppFvrwfyeaEf2vf/1rVI4ThVetcrVcs1Q4Pa5ZtmbMjimueXGooGYT37x3szdv0zx3H20dO3b0atSo4e3fvz/zMWze7GZRvuuuu9xjzZCtc1+7dm2O+9Js2omJid6PP/54zOzNJUuWPGbW8BOdTfyJJ57wUlJSvOTkZDfz86FDh9z6YcOGeS1atDhmP02bNvVGjBiR62vl9rqyfv16d8zz588PLXvyySfdLOt6v/Re9u7dO9Ps5Tr/pKQkNxO3ZsMuXbq0m/F606b/zRJ85MgRb+DAgW47nc99993nZugOn1lcs7Y/9thjbgZzvZ86n0mTJmU6vg8++MBr0KCBW3/RRReFZs7O7T2uXbu2N2rUqEzL/vznP3uNGzfO92d1PJE6z/fee8/N0q7Zy3WeL7/8cqbj899zbdeoUSOvSJEibpbvAwcOeIMHD/aqV6/uPi/9nejcwn3++eduBnW9tj7Pfv36ZZqpPTc5zYp+3XXXeZdffnmmZS1btvTuvPPOY7bVcWadlT4nzCYeHG//922v6StNvSYvN3H3eozYzCZOBieKhWYd3u5gPT/u6e71OFqUnfnoo4/s7rvvtlKlSmVal5KSYt27d3dZGs0fpF+oSr+/9dZb7tdwdjp16uQyOZp3KOvM3V26dLHy5ctH7NhnzpzpJovU/SuvvOJe039dHff8+fPdet/SpUvtu+++C03w6De5rF27Nl+/1HUuOo8zzjgjtFzvy5gxY9xr6FhmzJhh999/f6bn7t+/3/72t7/Zv/71L5cdUaZETW++J5980h2/MmBffPGF+2zeeeedTPsYOXKkTZgwwWXY9FoDBw60G2+8MdRkpqZGvc+dO3d2zRu33367DR06NN/v7caNG23KlCnWsmXLfD0vL+9pJM5TTaXXXHONy4B8++23duedd9qDDz54zGvpPVc25B//+IfbjzJNffv2dZk4NaXq7+Haa6+1jh07ugym6G9Gj7t27erW6+9fx6nnnQi9Zvv27TMt69Chg1sOiAqKP+r6kb3Y4UV3T4FxDHlxKNoZHGVs/Ajev+lxtDI5c+fOdefzzjvvZLv+qaeecuu3bNniHj/77LPuV2/ZsmW9iy++2PvTn/7k/fDDD5meM3ToUK9u3bpeRkaGe/z99997CQkJ3ieffHLM/k8kg6Osg7IBvmuvvda7/vrrQ4+1vY7Pp6yOfjH75s2b551xxhnehg0bcn1dZaSUcdFN51GuXDnvP//5T67Hq2xDxYoVQ4/9LIreC99zzz3nVa1aNfS4WrVq3uOPPx56fPjwYZc98DMbyjzovZ8zZ06m1+rZs6fXrVu30DmGZ11kyJAhecrgFC9e3J2jshbaXu9V+HP8z8p/L/xb+Ovl5T2NxHnqnJQxC/fggw8ek8HR42+++Sa0jTKLyuRs3Lgx03PbtWvn3jv/dXr16nVMRkd/B3n5t51TBqdYsWLexIkTMy3T30CVKlWO2ZYMDhB5ZHDitNBMGZrcqPBY+vTp4wpKVaOh+hwVTapGZfr06aFtVaysX9jKrIgyHipiveSSS/J0LKq38W933XVXjtvpdVUQ66tWrZpt3bo19FhZnIkTJ4bOT/VFWuZr0aKFK44+9dRTcz0eZWqUDdFt4cKF1rt3b/er/6uvvgpto5mx27Vr5/ZVtmxZu+mmm1yBtjIIvlNOOcXq16+f7fGqnke1LOEZk6JFi7raKJ9qo7Q/1UGFv0fKdPiZquXLlx+TddHnlBf33XefO0dlLfyCbtVUZc3Wff7556H3Q7fwQtnjvaeROk/VAZ133nmZ9q3Xzu7vtmnTpqHHixcvduejYuDwfSsz5O9bGSFlmMLXK9Oi+h39XQMIPsbBiWKhWXiQE81Cs9NOO801KejCePXVVx+zXsvVNBXetKQLuJpAdHvkkUfcl7/udUESFeGqp5ECm4suushdmO644w73OnkR3nOkXLlyOW6nHlvhtP/wIlIVdA4ZMsQWLVpkP//8s2u+uf766y2/dJHU++Q755xz7N1337XRo0fbq6++6ppjVHSswOfRRx+15ORk16ShYtJDhw65wCan4z1eYBlu79697v6DDz44JoAoUaKEnahKlSqFzlOfoc5PwZEC1fCmlbp160a0qTGa56lm1/C/O+1bQbEC1fDgWBTI+NuoySu7AutatWrZr6UmX/U0DKfHWg6gcCHAiYKU0ilucKesgz1peTSoZ5ICk+eff97VOYTX4fiZGmVtcqKLh7q9qodIOF3cdcH//e9/7+o5suv6nZPwYOJEqAv7hRde6M5BAY7OM1K9fXRx1D5FF0sFVqotUS2OqMdYfqj7uTI68+bNswsuuMAtO3LkiNu3utZL48aN3QVetTs6r+w0atTI3n///UzL5s6d+6vPUfzzjIRInaeyalm7WKt33vEoOFUGR5kzBeHZ0XEsW7YsYn+HPgWLyoyFdyFX5jOvGTYABYcAJ0pUWNamehvXLKXMTbSCG9+zzz5rbdq0CWVi9AtdBZlqslAq/+GHHw5lVoYPH+6aX3QRUmZDqX0ViypTEk5NOPoFrF/CGtejZs3YdHVUk5SOWZmUUaNG/ap96ALsj/OyZ88eV3SqC6B/zroQqgv9M88847JaX375pSuOzS+NO/SXv/zFZU8UND711FOZBudT5kxFyQpEFVCpO7GafPR6ynTdcsstrklPgZY+OxUYK3DIWvCdE52bzlNZJWW7VCSt7J3+NsIpODhw4MAxgXLWDFU0z1N/V3qePgMF0/rb9M8zt0yh/p71N3HzzTe790kBz7Zt21zgoaYsNclpn61atXJFxXoPS5cu7T5vBSP6t3K8zKMyQNqnHuvfiP6t+OetgE2vq9dRkbOaOcePHx/ahwquFdht2rQp1BQnyvKQ6QEKkBeHCqqbeEFTUaMKd1X0qkJanWOXLl28ffv2hbbZtm2bd88997jizjJlyrhC47POOsv729/+5rr1ZqVCTe3nzTffzPF1T7SbeDgVdqrAM5z2q27EKloN77Yd/to699xeV9v4N+1H5zx27NhjirFVPFuqVCnX/XvChAnZdlkOp8Lu8H9GKrbVOaiIuXz58t6gQYOO6T6twu3Ro0e7Ql4VrVauXNm93qxZs0LbTJkyJdR9+vzzz/defPHFPBUZh5+n9tupU6dMRa7++5XdLTU1Nc/vaaTOM2s3cX0mem3/319277loKIGHH37YdUHXvvW5XX311d53330X2kZDAFx66aXu71yF1Oqm/uijj3q5ye590fsaTv8WTj/9dFfQfeaZZ7ou/eH8wuisN/0d5uRk/t4BCmuRcYEEOOq1oy8JfYlpvAr10siNvkD0pajtdSEO/wLRF9v9998fGq9EX2w33XTTMT0q4jHAyUoXAH25+xeuINKFXxdIf+wcnNzv6SOPPOJ6Y8WbIH3vAHHTi0pNAYMGDXJNDCoU1QihakYJ7ymTn5FC1TND+/nDH/7g7v3ReFUngsxGjBjhxnVR/Ua0Rn+NNdVwPPbYY3luWkHhek9VN6a6G43ErbGFnnjiCdd8BQAnKkFRjkWRupKqK6jf7q0LrWo5+vXrl+3gZeoho6HOp06dGlqmtnRN0phTTYS+INW99Mcff8xTD4ndu3e7QknVBGTt4aO6BHUjVQ2L5poBED2q0dGPINWt6N+uasOGDRvmup3HE753gLzJ7fqdVVS/RVQUqgJJfWH51ENF3VVzGvlTy5XxCaeMj7r05kQnqqLEnLq9auRa3cLfIACxp6LxX1s4DgC5iWoT1fbt2113Tg37H06Pc5q5WMvzs71++ajHhJq1cormNGS8Ij7/FqveQAAAoGCc1CMZq1vvdddd57rEjh07NsftlEFSlse/qfssAAAIrqg2UWlUVQ00lp+RP/M6Uqgf3KjuRpMi5tYWpwHH8jt6apRLkwAghO8b4CTL4GiArGbNmoXmxPGLjPU4p5E//ZFCw2UdKdQPbjRzsOYP0gBlkeL3HAmffwgAosn/vqE3IBA5Ue+qoIJhdfvURHzq6aS5cdRLqkePHm69RiPVXDWqk8nLSKEKbq655hrXRVw9rVTj49fnaP4gf0LJX0sZJxUr+93YNQdRXudfAoD8Zm4U3Oj7Rt87WefWAlCIAxx1+9aQ55oqQIGIuntPmzYtVEisIc39uX9EQ8pr9uiHHnrIHnjgATcUvHpQNWnSxK3XnEj+PD3aVzhNKKiJIU+U3xyW01g9ABBJCm6YxgE4ycbBOZn70Ss7pIwRAESLmqXI3AAn2Tg4Jzt96fDFAwDAyeek7iYOAACQHQIcAAAQOAQ4AAAgcAhwAABA4BDgAACAwCHAAQAAgUOAAwAAAocABwAABA4BDgAACBwCHAAAEDgEOAAAIHAIcAAAQOAQ4AAAgMAhwAEAAIFDgAMAAAKHAAcAAAQOAQ4AAAgcAhwAABA4BDgAACBwCHAAAEDgEOAAAIDAIcABAACBQ4ATaekbzdbM/uUeAADERNHYvGwwZSx8xRKmDLAEyzDPEs3rPNoSm90S68MCACDukMGJkJnzvzZvSn8X3IgLcqYMcMsBAEDBIsCJgGlLNtv4d6dbEfMyLS9iGfb3dz9x6wEAQMEhwDlBRzM8GzFlma3JSLGjXkKmdUe8RPsxo6pbr+0AAEDBIMA5QfPX7LDN6QcszSrasCO3u6BGdP/AkZ622Sq69doOAAAUDIqMT9DWPQdC///m0Yvts4S6VrXEattysJ5tPVon2+0AAEB0EeCcoCplS4b+v1jSAttfbbKtTfDM8xKs2OYudjj9vGO2AwAA0UUT1QlqUTfZqiWVtMSi6Vai2mRLSPil1kb3eqzlWq/tAABAwSDAOUFFEhNseOfGllh8eyi48emxlmu9tgMAIOiOZniW+sNP9t43G919rDrZFEiA89xzz1mdOnWsZMmS1rJlS5s/f36u20+aNMkaNmzotj/rrLPsww8/zLTe8zx7+OGHrVq1alaqVClr3769rVq1ymKlY5Nq9sgVF5ll6UWlx1qu9QAABN20JZut7V9nWLcX5lr/f3/j7vU4FsOlRD3AeeONN2zQoEE2fPhwW7RokZ199tnWoUMH27p1a7bbz5kzx7p162Y9e/a0r7/+2q666ip3W7JkSWibxx9/3MaMGWPjxo2zefPmWenSpd0+DxyIXSHvDec2teGth1vi/72lutdjLQcAIOimLdlsvV9d5HoOh0tLP+CWF3SQk+ApHRJFyticd9559uyzz7rHGRkZVrNmTevXr58NHTr0mO2vv/5627dvn02dOjW0rFWrVvab3/zGBTQ63OrVq9vgwYPt3nvvdevT09OtatWq9vLLL9sNN9xw3GPavXu3JSUlueeVK1cuouebti/N1u9ZbzXL1rSU0ikR3TcAAIXR0QzPZWr84CbFfrK6iWlujDgNo6L2jZSkkvbFkEtOqGQjP9fvqGZwDh06ZAsXLnRNSKEXTEx0j1NTU7N9jpaHby/Kzvjbr1mzxtLS0jJto5NVIJXTPg8ePOjelPBbtCioOS/lPIIbIF4x4S7ieEw4ua7ITJt8ykAbWO5v7l6PlUkp6DHhohrgbN++3Y4ePeqyK+H0WEFKdrQ8t+39+/zsc+TIkS4I8m/KIAFAxC2aYDa6idkrnX+512MgDmzd87/MTYsKr9tltapZz2pV3f15FV53y8O3Kwhx0Ytq2LBhLp3l39avXx/rQwIQNMrYTOlv5v0y4a67nzKATA7iQpX/G+utRvEf7M+VKlhGwi/NULp/pFIFO7X46kzbnfQBTqVKlaxIkSK2ZcuWTMv1OCUl+yYcLc9te/8+P/ssUaKEa6sLvwFARO344X/Bjc87arbjly92IB7GhPuxaLFQcOPT43VFixb4mHBRDXCKFy9uzZo1s08//TS0TEXGety6detsn6Pl4dvL9OnTQ9vXrVvXBTLh26imRr2pctonAETbzG1l7agrpfyfo5ZoM7eVidkxAQU9JtxPh+qZK7gJ55lbXtBjwkW9iUpdxF944QV75ZVXbPny5da7d2/XS6pHjx5u/c033+yakHz9+/e3adOm2ZNPPmkrVqywP/7xj/bVV19Z37593fqEhAQbMGCAPfLII/b+++/b4sWL3T7Us0rdyQGgoKn7622TN9mww1km3D3c0y2PxRggQEHTmG/P33CxlUi/4X/jwnkJ7rGWF/SYcFGfi0rdvrdt2+YG5lMRsLp7K4Dxi4TXrVvnelb52rRpYxMnTrSHHnrIHnjgAWvQoIG9++671qRJk9A2999/vwuSevXqZbt27bK2bdu6fWpgQAAo6O6xI6Yscz9aNeHu7KNNrU7iFlubUTXUPVbrL22cwojmCLyOTarZpY0fsI9WdLGVO9bYGcl1rUPDhjH524/6ODiFUTTHwQEQXzQUvUZrPZ7X72hlretXLJBjAoKq0IyDAwBBl9durwXZPRYAAQ4AnJC8dnstyO6xAAhwACAi3WNzqjDQ8oLuHguAAAcAItI9VrIGOf7jgu4eC4AABwAi0nNk7I3nuskEw+mxlhd091gABdBNHADip3tsiptMUAXFqrlRsxSZGyA2CHAAIEIUzNAVHCgcaKICAACBQ4ADAAAChwAHAAAEDgEOAAAIHAIcAAAQOAQ4AAAgcAhwAABA4BDgAACAwCHAAYAIStuXZvM3z3f3AGKHkYwBIEImr5psI1JHWIaXYYkJiTa89XDr0qBLrA8LiEtkcAAgApSx8YMb0b0ek8kBYoMABwAiYN3udaHgxqfH6/esj9kxAfGMAAcAIqBWuVquWSqcHtcsWzNmxwTEMwIcAIiAlNIprubGD3L8GhwtB1DwKDIGgAhRQXGb6m1cs5QyNwQ3QOwQ4ABABCmoIbABYo8mKgAAEDgEOAAAIHAIcAAAQOAQ4AAAgMAhwAEAAIFDgAMAAAKHAAcAAAQOAQ4AAAgcAhwAABA4UQ1wduzYYd27d7dy5cpZ+fLlrWfPnrZ3795cn3PgwAHr06ePVaxY0cqUKWNdu3a1LVu2hNZ/++231q1bN6tZs6aVKlXKGjVqZE8//XQ0TwMAAJxkohrgKLhZunSpTZ8+3aZOnWqzZ8+2Xr165fqcgQMH2pQpU2zSpEk2a9Ys27Rpk3Xp0iW0fuHChValShV79dVX3b4ffPBBGzZsmD377LPRPBUAAHASSfA8z4vGjpcvX26NGze2BQsWWPPmzd2yadOmWadOnWzDhg1WvXr1Y56Tnp5ulStXtokTJ9o111zjlq1YscJlaVJTU61Vq1bZvpYyPnq9GTNm5OnYdu/ebUlJSe71lF0CAACFX36u31HL4CggUbOUH9xI+/btLTEx0ebNm5ftc5SdOXz4sNvO17BhQ6tVq5bbX050osnJyRE+AwAAcLKK2mziaWlprikp04sVLeoCEa3L6TnFixd3gVG4qlWr5vicOXPm2BtvvGEffPBBjsdy8OBBdwuPAAEAQHDlO4MzdOhQS0hIyPWmZqWCsGTJErvyyitt+PDh9rvf/S7H7UaOHOlSWv5NBcoAACC48p3BGTx4sN166625blOvXj1LSUmxrVu3Zlp+5MgR17NK67Kj5YcOHbJdu3ZlyuKoF1XW5yxbtszatWvnipYfeuihXI9HRciDBg3KlMEhyAEAILjyHeCoCFi342ndurULVFRX06xZM7dMRcAZGRnWsmXLbJ+j7YoVK2affvqp6x4uK1eutHXr1rn9+dR76pJLLrFbbrnFHn300eMeS4kSJdwNAADEh6j1opLLLrvMZV/GjRvniod79Ojhio7VS0o2btzosjATJkywFi1auGW9e/e2Dz/80F5++WVXId2vX79QrY3fLKXgpkOHDvbEE0+EXqtIkSJ5CryEXlQAAJx88nP9jlqRsbz22mvWt29fF8So95SyMmPGjAmtV9CjDM3+/ftDy0aNGhXaVoXBCmSef/750Pq33nrLtm3b5sbB0c1Xu3ZtW7t2bTRPBwAAnCSimsEprMjgAABw8ikU4+AAAADECgEOAAAIHAIcAAAQOAQ4AAAgcAhwAABA4BDgAACAwCHAAQAAgUOAAwAAAocABwAABA4BDgAACBwCHAAAEDgEOAAAIHAIcAAAQOAQ4AAAgMAhwAEAAIFDgAMAAAKHAAcAAAQOAQ4AAAgcAhwAABA4BDgAACBwCHAAAEDgEOAAAIDAIcABAACBQ4ADAAAChwAHAAAEDgEOAAAIHAIcAAAQOAQ4AAAgcAhwAABA4BDgAACAwCHAAQAAgUOAAwAAAocABwAABE5UA5wdO3ZY9+7drVy5cla+fHnr2bOn7d27N9fnHDhwwPr06WMVK1a0MmXKWNeuXW3Lli3ZbvvTTz9ZjRo1LCEhwXbt2hWlswAAACebqAY4Cm6WLl1q06dPt6lTp9rs2bOtV69euT5n4MCBNmXKFJs0aZLNmjXLNm3aZF26dMl2WwVMTZs2jdLRAwCAk1WC53leNHa8fPlya9y4sS1YsMCaN2/ulk2bNs06depkGzZssOrVqx/znPT0dKtcubJNnDjRrrnmGrdsxYoV1qhRI0tNTbVWrVqFth07dqy98cYb9vDDD1u7du1s586dLkuUF7t377akpCT3esouAQCAwi8/1++oZXAUkCjg8IMbad++vSUmJtq8efOyfc7ChQvt8OHDbjtfw4YNrVatWm5/vmXLltmf/vQnmzBhgtsfAABAuKIWJWlpaValSpXML1a0qCUnJ7t1OT2nePHix2RiqlatGnrOwYMHrVu3bvbEE0+4wGf16tXHPRY9R7fwCBAAAARXvtMfQ4cOdUW9ud3UrBQtw4YNc01WN954Y56fM3LkSJfS8m81a9aM2vEBAICTMIMzePBgu/XWW3Pdpl69epaSkmJbt27NtPzIkSOuZ5XWZUfLDx065HpEhWdx1IvKf86MGTNs8eLF9tZbb7nHfglRpUqV7MEHH7QRI0ZkGxQNGjQoUwaHIAcAgODKd4CjImDdjqd169YuUFFdTbNmzULBSUZGhrVs2TLb52i7YsWK2aeffuq6h8vKlStt3bp1bn/y9ttv288//xx6joqYb7vtNvv888+tfv362e63RIkS7gYAAOJD1Gpw1IzUsWNHu+OOO2zcuHGueLhv3752ww03hHpQbdy40fWAUrFwixYtXPORun4r26JaHVVI9+vXzwU3fg+qrEHM9u3bQ6+X115UAAAg2KIW4Mhrr73mghoFMertpKzMmDFjQusV9ChDs3///tCyUaNGhbZVYXCHDh3s+eefj+ZhAgCAgInaODiFGePgAABw8ikU4+AAAADECgEOAAAIHAIcAAAQOAQ4AAAgcAhwAABA4BDgAACAwCHAAQAAgUOAAwAAAocABwAABA4BDgAACBwCHAAAEDgEOAAAIHAIcAAAQOAQ4AAAgMAhwAEAAIFDgAMAAAKHAAcAAAQOAQ4AAAgcAhwAABA4BDgAACBwCHAAAEDgEOAAAIDAIcABAACBQ4ADAAAChwAHAAAEDgEOAAAIHAIcAAAQOAQ4AAAgcAhwAABA4BDgAACAwCHAAQAAgUOAAwAAAocABwAABE7UApwdO3ZY9+7drVy5cla+fHnr2bOn7d27N9fnHDhwwPr06WMVK1a0MmXKWNeuXW3Lli3HbPfyyy9b06ZNrWTJklalShX3HAAAgKgHOApuli5datOnT7epU6fa7NmzrVevXrk+Z+DAgTZlyhSbNGmSzZo1yzZt2mRdunTJtM1TTz1lDz74oA0dOtTt/5NPPrEOHTpE6zQAAMBJKMHzPC/SO12+fLk1btzYFixYYM2bN3fLpk2bZp06dbINGzZY9erVj3lOenq6Va5c2SZOnGjXXHONW7ZixQpr1KiRpaamWqtWrWznzp126qmnuiCoXbt2v/r4du/ebUlJSe41lWECAACFX36u31HJ4CggUbOUH9xI+/btLTEx0ebNm5ftcxYuXGiHDx922/kaNmxotWrVcvsTZYMyMjJs48aNLvCpUaOGXXfddbZ+/fpcj+fgwYPuTQm/AQCA4IpKgJOWluZqY8IVLVrUkpOT3bqcnlO8eHEXGIWrWrVq6DmrV692Ac5jjz1mo0ePtrfeesvV+lx66aV26NChHI9n5MiRLuLzbzVr1ozIeQIAgAAEOKp7SUhIyPWmZqVoUXCjLM+YMWNc3Y2arV5//XVbtWqVzZw5M8fnDRs2zKWz/NvxMj4AAODkVjQ/Gw8ePNhuvfXWXLepV6+epaSk2NatWzMtP3LkiMu2aF12tFxZmF27dmXK4qgXlf+catWquXvV9/hUt1OpUiVbt25djsdUokQJdwMAAPEhXwGOggndjqd169YuUFFdTbNmzdyyGTNmuAxMy5Yts32OtitWrJh9+umnrnu4rFy50gUu2p/89re/DS1X/Y0oaNq+fbvVrl07P6cCAAACLCq9qOSyyy5z2Zdx48a5ZqUePXq4omP1khIVCqsn1IQJE6xFixZuWe/eve3DDz9049yoOrpfv35u+Zw5c0L7veqqq+z777+38ePHu23U/KTanG+++cYFSHlBLyoAAE4+Me9FJa+99prrBaUgRt3D27Zt64ISn4IeZWL2798fWjZq1Ci74oorXAbnggsucE1TkydPzrRfBUTKAl1++eV24YUXuqBGXdDzGtwAAIDgi1oGpzAjgwMAwMmnUGRwAAAAYoUABwAABA4BDgAACBwCHAAAEDgEOAAAIHAIcAAAQOAQ4AAAgMAhwAEAAIFDgAMgstI3mq2Z/cs9AJwMk20Cx6WL2o4fzJLrmyWdGuujQUFbNMFsSn8zL8MsIdGs89Nm594c66MCEIfI4CCyF7fRTcxe6fzLvR4jvoJbP7gR3U8ZQCYHQEwQ4CAyuLhBmTv/8/d5R812rI7VEQGIYwQ4iAwublCzpJqlwiUUMUuuF6sjAhDHCHAQGVzcoJor1dzocxfddx5NLRaAmKDIGJG9uKlZSpkbLm7xSQXF9dv9krlTcMvnDyBGCHAQOVzcIPrc+ewBxBgBDiLmaIZn87eXtK176lmVjJLWoqxnRRITYn1YAIA4RICDiJi2ZLONmLLMNqcfCC2rllTShndubB2bVIvpsQEA4g9FxohIcNP71UWZghtJSz/glms9AAAFiQAHJ9wspcyNl806f5nWazsAAAoKAQ5OyPw1O47J3IRTWKP12g4AgIJCDQ5OyNY9ByK6HQJQaL5mh/u8q5QtaS3qJlNoDiAmCHBwQnQRi+R2OHlRaA6gMKGJCidEv9B1EcvpN7qWa722Q3BRaA6gsCHAwQlR84N+ofvBTELRdCtyyg/u3g96tJ5miuCi0BxAYUSAgxOm5oexN55rFat9Y6VP+4udUvsFd6/HWk7zRLBRaA6gMCLAQUT8pm6CHS7/piUk/PIrXfeHK7zpliPYKDQHUBgR4CAi1u1eZxmWkWlZhpdh6/esj9kxoWBQaA6gMCLAQUTUKlfLEhMy/znpcc2yNWN2TCgYFJoDKIwIcBARKaVTbHjr4aEgR/d6rOWIr0LzcBSaA4iVBM/z4q5rw+7duy0pKcnS09OtXLlysT6cQEnbl+aapZS5IbiJL4yDA6AwXb8JcAhwgIhhJGMAheX6zUjGACJGwUzr+hVjfRgAEL0anB07dlj37t1dhFW+fHnr2bOn7d27N9fnHDhwwPr06WMVK1a0MmXKWNeuXW3Lli2ZtlmwYIG1a9fO7bNChQrWoUMH+/bbb6N1GgAA4CQUtQBHwc3SpUtt+vTpNnXqVJs9e7b16tUr1+cMHDjQpkyZYpMmTbJZs2bZpk2brEuXLqH1CpA6duxotWrVsnnz5tkXX3xhZcuWdUHO4cOHo3UqAADgJBOVGpzly5db48aNXbalefPmbtm0adOsU6dOtmHDBqtevfoxz1F7WuXKlW3ixIl2zTXXuGUrVqywRo0aWWpqqrVq1cq++uorO++882zdunVWs+Yv3Y8XL15sTZs2tVWrVtlpp52Wp+OjBgcAgJNPfq7fUcngKCBRE5If3Ej79u0tMTHRZV6ys3DhQpeF0Xa+hg0bumyN9idnnHGGa7765z//aYcOHbKff/7Z/b+CoDp16uR4PAcPHnRvSvgNAAAEV1QCnLS0NKtSpUqmZUWLFrXk5GS3LqfnFC9e3AVG4apWrRp6jpqjPvvsM3v11VetVKlSrk5HmaH//Oc/bv85GTlypIv4/Juf/QEAAMGUrwBn6NChlpCQkOtNzUrRooyNipV/+9vf2ty5c+3LL7+0Jk2a2OWXX+7W5WTYsGEuneXf1q9n+gAgmmMhzd88390DQKzkq5v44MGD7dZbb811m3r16llKSopt3bo10/IjR464nlValx0tV7PTrl27MmVx1IvKf47qc9auXeuarNTc5S9Tb6r33nvPbrjhhmz3XaJECXcDEF2TV022Eakj3Dxk/mjWXRr8r6MAABTKAEdFwLodT+vWrV2gorqaZs2auWUzZsywjIwMa9myZbbP0XbFihWzTz/91HUPl5UrV7qCYu1P9u/f7wIbZYp8/mPtG0DsKGPjBzeiez1uU70No1oDCEYNjop+1Z37jjvusPnz57umpL59+7oMi9+DauPGja6IWOtFtTFqfho0aJDNnDnTBUc9evRwwY16UMmll15qO3fudGPlqKeWuqFrG9XfXHzxxdE4FQD5mVH+/4IbHzPKAwjcODivvfaaC2A0KJ+6h7dt29bGjx8fWq8eU8rQKCvjGzVqlF1xxRUug3PBBRe4pqnJkyeH1mt/Gifnu+++c4HP+eef78bKUaFxtWrMdQPEEjPKAyhMmIuKcXCAiKEGB0A0MRcVgJhQMKOaG2aUBxBrBDgAIkpBDYENgMDW4AAAAMQKAQ4AAAgcAhwAABA4BDgAACBwCHAAAEDgEOAAAIDAIcABAACBQ4ADAAAChwAHAAAEDgEOAAAIHAIcAAAQOAQ4AAAgcAhwAABA4BDgAACAwCHAAQAAgUOAAwAAAocABwAABA4BDgAACBwCHAAAEDgEOAAAIHAIcAAAQOAQ4AAAgMAhwAEAAIFDgAMAAAKHAAcAAAQOAQ4AAAgcAhwAABA4BDgAACBwCHAAAEDgEOAAkZS+0WzN7F/uAQAxUzR2Lw0EzKIJZlP6m3kZZgmJZp2fNjv35lgfFQDEpahlcHbs2GHdu3e3cuXKWfny5a1nz562d+/eXJ8zfvx4u+iii9xzEhISbNeuXRHZLxB1ytj4wY3ofsoAMjkAELQAR0HI0qVLbfr06TZ16lSbPXu29erVK9fn7N+/3zp27GgPPPBARPcLRN2OH/4X3Pi8o2Y7VsfqiAAgriV4nudFeqfLly+3xo0b24IFC6x58+Zu2bRp06xTp062YcMGq169eq7P/+yzz+ziiy+2nTt3uixNpPbr2717tyUlJVl6errLBAEnTJma0U0yBzkJRcwGLDZLOjWWRwYAgZGf63dUMjipqakuMPGDEGnfvr0lJibavHnzCny/Bw8edG9K+A2IKAUxqrlRUCO67zya4AYAglRknJaWZlWqVMn8QkWLWnJysltX0PsdOXKkjRgx4le/LpAnKiiu3+6XZqnkegQ3ABBD+crgDB061BX/5nZbsWKFFTbDhg1z6Sz/tn79+lgfEoJKQU3d8wluAOBkyuAMHjzYbr311ly3qVevnqWkpNjWrVszLT9y5IjrAaV1v9av3W+JEiXcDQAAxId8BTiVK1d2t+Np3bq16+K9cOFCa9asmVs2Y8YMy8jIsJYtW/7qg43WfgEAQLBEpci4UaNGrrv3HXfcYfPnz7cvv/zS+vbtazfccEOop9PGjRutYcOGbr1PdTTffPONff/99+7x4sWL3WNlaPK6XwAAgKiNg/Paa6+5AKZdu3auG3fbtm3dQH6+w4cP28qVK93YN75x48bZOeec4wIYueCCC9zj999/P8/7BQAAiMo4OIUd4+AgGo5meDZ/zQ7buueAVSlb0lrUTbYiiQmxPiwAiMvrN3NRAREwbclmGzFlmW1OPxBaVi2ppA3v3Ng6NqkW02MDgHjEbOJABIKb3q8uyhTcSFr6Abdc6wEABYsABzjBZillbrJr5/WXab22AwAUHAIc4ASo5iZr5iacwhqt13YAgIJDgAOcABUUR3I7AEBkEOAAJ0C9pSK5HQAgMghwgBOgruDqLZVTZ3At13ptBwAoOAQ4wAnQODfqCi5Zgxz/sdYzHg4AFCwCHOAEaZybsTeeaylJmZuh9FjLGQcHAAoeA/0BEaAg5tLGKYxkDACFBAEOECEKZuqmHLYip2y0WuVqEdwAQAwR4AARMnnVZBuROsIyvAxLTEi04a2HW5cGXWJ9WAAQl6jBASIgbV9aKLgR3euxlgMACh4BDhAB63avCwU3Pj1ev2d9zI4JAOIZAQ4QAaq5UbNUOD2uWbZmzI4JAOIZAQ4QASmlU1zNjR/k+DU4Wg4AKHgUGQMRooLiNtXbuGYpZW4IbgAgdghwgAhSUENgAwCxRxMVAAAIHAIcAAAQOAQ4AAAgcAhwAABA4BDgAACAwCHAAQAAgUOAAwAAAocABwAABA4BDgAACBwCHAAAEDgEOAAAIHDici4qz/Pc/e7du2N9KAAAII/867Z/Hc9NXAY4e/bscfc1a9aM9aEAAIBfcR1PSkrKdZsELy9hUMBkZGTYpk2brGzZspaQkBDx6FKB0/r1661cuXIWbzj/+D5/iff3IN7PX+L9PYj384/me6CQRcFN9erVLTEx9yqbuMzg6E2pUaNGVF9DH2i8/mEL5x/f5y/x/h7E+/lLvL8H8X7+0XoPjpe58VFkDAAAAocABwAABA4BToSVKFHChg8f7u7jEecf3+cv8f4exPv5S7y/B/F+/oXlPYjLImMAABBsZHAAAEDgEOAAAIDAIcABAACBQ4ADAAAChwAngp577jmrU6eOlSxZ0lq2bGnz58+3eDF79mzr3LmzG11So0O/++67Fk9Gjhxp5513nhsdu0qVKnbVVVfZypUrLZ6MHTvWmjZtGhrYq3Xr1vaf//zH4tVf/vIX929hwIABFg/++Mc/uvMNvzVs2NDizcaNG+3GG2+0ihUrWqlSpeyss86yr776yuJBnTp1jvkb0K1Pnz4xOR4CnAh54403bNCgQa5b3KJFi+zss8+2Dh062NatWy0e7Nu3z52zgrx4NGvWLPePeO7cuTZ9+nQ7fPiw/e53v3PvS7zQ6OC6qC9cuNB9oV9yySV25ZVX2tKlSy3eLFiwwP7+97+7gC+enHnmmbZ58+bQ7YsvvrB4snPnTvvtb39rxYoVc8H9smXL7Mknn7QKFSpYvPzdbw77/PVdKNdee21sDkjdxHHiWrRo4fXp0yf0+OjRo1716tW9kSNHevFGf1bvvPOOF8+2bt3q3odZs2Z58axChQreP/7xDy+e7Nmzx2vQoIE3ffp078ILL/T69+/vxYPhw4d7Z599thfPhgwZ4rVt2zbWh1Fo9O/f36tfv76XkZERk9cngxMBhw4dcr9a27dvn2m+Kz1OTU2N6bEhNtLT0919cnKyxaOjR4/av//9b5fBUlNVPFEm7/LLL8/0fRAvVq1a5Zqp69WrZ927d7d169ZZPHn//fetefPmLmOhpupzzjnHXnjhBYvX6+Krr75qt912W8Qntc4rApwI2L59u/tCr1q1aqblepyWlhaz40LsZqtX3YVS1U2aNLF4snjxYitTpowbvfSuu+6yd955xxo3bmzxQkGdmqhVkxVvVHf48ssv27Rp01w91po1a+z88893Mz/Hi9WrV7tzb9CggX300UfWu3dvu+eee+yVV16xePPuu+/arl277NZbb43ZMcTlbOJAtH/BL1myJO7qD+SMM86wb775xmWw3nrrLbvllltcfVI8BDnr16+3/v37u7oDdTSIN5dddlno/1V7pICndu3a9uabb1rPnj0tXn7cKIPz2GOPucfK4Oi7YNy4ce7fQjz55z//6f4mlNGLFTI4EVCpUiUrUqSIbdmyJdNyPU5JSYnZcaHg9e3b16ZOnWozZ850Rbfxpnjx4nbaaadZs2bNXBZDhedPP/20xQM1U6tTwbnnnmtFixZ1NwV3Y8aMcf+vLG88KV++vJ1++un2/fffW7yoVq3aMcF8o0aN4q6p7scff7RPPvnEbr/99pgeBwFOhL7U9YX+6aefZork9Tje6g/ilWqrFdyoSWbGjBlWt27dWB9SoaB/BwcPHrR40K5dO9dEpwyWf9OvedWi6P/1Iyie7N2713744Qd30Y8XapbOOjzEf//7X5fJiicvvfSSq0FSLVos0UQVIeoirhSkvtBatGhho0ePdgWWPXr0sHj5Mgv/pab2d32pq8i2Vq1aFg/NUhMnTrT33nvPjYXj114lJSW5sTDiwbBhw1xKWp+36i70fnz22WeuFiEe6HPPWnNVunRpNx5KPNRi3XvvvW4sLF3MN23a5IbMUFDXrVs3ixcDBw60Nm3auCaq6667zo2FNn78eHeLpx81L730krseKnMZUzHpuxVQzzzzjFerVi2vePHirtv43LlzvXgxc+ZM1y066+2WW27x4kF2567bSy+95MWL2267zatdu7b7+69cubLXrl077+OPP/biWTx1E7/++uu9atWquc//1FNPdY+///57L95MmTLFa9KkiVeiRAmvYcOG3vjx47148tFHH7nvvpUrV8b6ULwE/Se2IRYAAEBkUYMDAAAChwAHAAAEDgEOAAAIHAIcAAAQOAQ4AAAgcAhwAABA4BDgAACAwCHAAQAAgUOAAwAAAocABwAABA4BDgAACBwCHAAAYEHz/wFPjoDpOOLzDgAAAABJRU5ErkJggg==", "text/plain": [ "
" ] @@ -480,7 +339,7 @@ }, { "cell_type": "markdown", - "id": "15", + "id": "12", "metadata": {}, "source": [ "**We can see that the results is better for the banded diagonal block-encoding. This is because it has a better scaling factor, and thus a better approximation for a given polynomial degree.**" @@ -488,8 +347,8 @@ }, { "cell_type": "code", - "execution_count": 12, - "id": "16", + "execution_count": 10, + "id": "13", "metadata": {}, "outputs": [], "source": [ diff --git a/applications/cfd/qls_for_hybrid_solvers/qsvt_solver_banded_be.qmod b/applications/cfd/qls_for_hybrid_solvers/qsvt_solver_banded_be.qmod index 87997f361..dd796bb66 100644 --- a/applications/cfd/qls_for_hybrid_solvers/qsvt_solver_banded_be.qmod +++ b/applications/cfd/qls_for_hybrid_solvers/qsvt_solver_banded_be.qmod @@ -163,25 +163,25 @@ qfunc block_encode_banded_expanded___0(prep_diag: real[], block: qnum<3, False, {s, ind} -> block; } -qfunc be_qfunc_expanded___0(be: BlockEncodedState) { - block_encode_banded_expanded___0([0.2416, 0.4992, 0.2591, 0], be.block, be.data); +qfunc be_qfunc_expanded___0(block: qnum<3, False, 0>, data: qnum<3, False, 0>) { + block_encode_banded_expanded___0([0.2416, 0.4992, 0.2591, 0], block, data); } -qfunc u_0_lambda___0_0_expanded___0(be_state_captured__main__0: BlockEncodedState) { - be_qfunc_expanded___0(be_state_captured__main__0); +qfunc u_0_lambda___0_0_expanded___0(be_state___block_captured__main__0: qnum<3, False, 0>, be_state___data_captured__main__0: qnum<3, False, 0>) { + be_qfunc_expanded___0(be_state___block_captured__main__0, be_state___data_captured__main__0); } -qfunc u_1_lambda___0_0_expanded___0(be_state_captured__main__0: BlockEncodedState) { +qfunc u_1_lambda___0_0_expanded___0(be_state___block_captured__main__0: qnum<3, False, 0>, be_state___data_captured__main__0: qnum<3, False, 0>) { invert { - u_0_lambda___0_0_expanded___0(be_state_captured__main__0); + u_0_lambda___0_0_expanded___0(be_state___block_captured__main__0, be_state___data_captured__main__0); } } qfunc qsvt_step_expanded___0(phase1: real, phase2: real, aux: qbit, be_state_captured__main__0: BlockEncodedState) { - u_1_lambda___0_0_expanded___0(be_state_captured__main__0); + u_1_lambda___0_0_expanded___0(be_state_captured__main__0.block, be_state_captured__main__0.data); projector_controlled_phase_expanded___0(phase1, aux, be_state_captured__main__0); invert { - u_1_lambda___0_0_expanded___0(be_state_captured__main__0); + u_1_lambda___0_0_expanded___0(be_state_captured__main__0.block, be_state_captured__main__0.data); } projector_controlled_phase_expanded___0(phase2, aux, be_state_captured__main__0); } @@ -194,7 +194,7 @@ qfunc qsvt_expanded___0(phase_seq: real[], aux: qbit, be_state_captured__main__0 } if (False) { } else { - u_1_lambda___0_0_expanded___0(be_state_captured__main__0); + u_1_lambda___0_0_expanded___0(be_state_captured__main__0.block, be_state_captured__main__0.data); projector_controlled_phase_expanded___0(phase_seq[101], aux, be_state_captured__main__0); } H(aux); @@ -222,108 +222,108 @@ qfunc main(output qsvt_aux: qbit, output data: qnum<3, False, 0>, output block: {data, block} -> be_state; } apply { qsvt_inversion_expanded___0([ - 3.1279, - 3.1446, - 3.1383, - 3.1452, - 3.1377, - 3.1459, - 3.1369, - 3.1467, - 3.1361, + 3.1409, + 3.143, + 3.1394, + 3.1445, + 3.138, + 3.146, + 3.1365, 3.1475, - 3.1352, - 3.1484, - 3.1343, - 3.1494, - 3.1332, - 3.1505, - 3.1321, - 3.1516, - 3.131, - 3.1529, - 3.1297, - 3.1541, - 3.1284, - 3.1555, - 3.127, + 3.135, + 3.149, + 3.1334, + 3.1506, + 3.1318, + 3.1521, + 3.1303, + 3.1537, + 3.1287, + 3.1553, + 3.1271, 3.1569, 3.1255, - 3.1584, - 3.124, - 3.1599, - 3.1225, - 3.1615, - 3.1209, - 3.1631, + 3.1585, + 3.1239, + 3.1601, + 3.1223, + 3.1616, + 3.1208, + 3.1632, 3.1192, - 3.1648, - 3.1176, - 3.1665, - 3.1159, - 3.1681, - 3.1142, - 3.1698, - 3.1125, - 3.1715, - 3.1109, + 3.1647, + 3.1177, + 3.1662, + 3.1162, + 3.1677, + 3.1148, + 3.1691, + 3.1133, + 3.1705, + 3.112, + 3.1719, + 3.1107, 3.1731, - 3.1093, - 3.1747, - 3.1077, - 3.1762, + 3.1095, + 3.1743, + 3.1083, + 3.1754, + 3.1072, + 3.1765, 3.1062, + 3.1774, + 3.1053, + 3.1053, + 3.1774, 3.1062, - 3.1762, - 3.1077, - 3.1747, - 3.1093, + 3.1765, + 3.1072, + 3.1754, + 3.1083, + 3.1743, + 3.1095, 3.1731, - 3.1109, - 3.1715, - 3.1125, - 3.1698, - 3.1142, - 3.1681, - 3.1159, - 3.1665, - 3.1176, - 3.1648, + 3.1107, + 3.1719, + 3.112, + 3.1705, + 3.1133, + 3.1691, + 3.1148, + 3.1677, + 3.1162, + 3.1662, + 3.1177, + 3.1647, 3.1192, - 3.1631, - 3.1209, - 3.1615, - 3.1225, - 3.1599, - 3.124, - 3.1584, + 3.1632, + 3.1208, + 3.1616, + 3.1223, + 3.1601, + 3.1239, + 3.1585, 3.1255, 3.1569, - 3.127, - 3.1555, - 3.1284, - 3.1541, - 3.1297, - 3.1529, - 3.131, - 3.1516, - 3.1321, - 3.1505, - 3.1332, - 3.1494, - 3.1343, - 3.1484, - 3.1352, + 3.1271, + 3.1553, + 3.1287, + 3.1537, + 3.1303, + 3.1521, + 3.1318, + 3.1506, + 3.1334, + 3.149, + 3.135, 3.1475, - 3.1361, - 3.1467, - 3.1369, - 3.1459, - 3.1377, - 3.1452, - 3.1383, - 3.1446, - (-314.1729) + 3.1365, + 3.146, + 3.138, + 3.1445, + 3.1394, + 3.143, + (-314.16) ], qsvt_aux, be_state); } } diff --git a/applications/cfd/qls_for_hybrid_solvers/qsvt_solver_banded_be.synthesis_options.json b/applications/cfd/qls_for_hybrid_solvers/qsvt_solver_banded_be.synthesis_options.json index 008ffeece..226e25bed 100644 --- a/applications/cfd/qls_for_hybrid_solvers/qsvt_solver_banded_be.synthesis_options.json +++ b/applications/cfd/qls_for_hybrid_solvers/qsvt_solver_banded_be.synthesis_options.json @@ -6,28 +6,28 @@ "preferences": { "custom_hardware_settings": { "basis_gates": [ + "h", "z", - "x", + "sdg", + "u1", + "u2", "rx", - "cz", - "cx", - "sxdg", + "cy", "tdg", - "u1", - "u", + "x", + "rz", "p", - "r", + "sx", "y", + "cz", + "s", "ry", - "cy", - "rz", - "sdg", - "u2", + "u", + "cx", "t", "id", - "h", - "sx", - "s" + "sxdg", + "r" ], "is_symmetric_connectivity": true }, @@ -36,7 +36,7 @@ "optimization_level": 1, "output_format": ["qasm"], "pretty_qasm": true, - "random_seed": 1296785197, + "random_seed": 293873243, "synthesize_all_separately": false, "timeout_seconds": 300, "transpilation_option": "auto optimize" diff --git a/applications/cfd/qls_for_hybrid_solvers/qsvt_solver_pauli_be.qmod b/applications/cfd/qls_for_hybrid_solvers/qsvt_solver_pauli_be.qmod index a9a44a30b..23f105075 100644 --- a/applications/cfd/qls_for_hybrid_solvers/qsvt_solver_pauli_be.qmod +++ b/applications/cfd/qls_for_hybrid_solvers/qsvt_solver_pauli_be.qmod @@ -855,25 +855,25 @@ qfunc lcu_paulis_graycode_expanded___0(data: qbit[3], block: qbit[5]) { } } -qfunc be_qfunc_expanded___0(be: BlockEncodedState) { - lcu_paulis_graycode_expanded___0(be.data, be.block); +qfunc be_qfunc_expanded___0(block: qnum<5, False, 0>, data: qnum<3, False, 0>) { + lcu_paulis_graycode_expanded___0(data, block); } -qfunc u_0_lambda___0_0_expanded___0(be_state_captured__main__0: BlockEncodedState) { - be_qfunc_expanded___0(be_state_captured__main__0); +qfunc u_0_lambda___0_0_expanded___0(be_state___block_captured__main__0: qnum<5, False, 0>, be_state___data_captured__main__0: qnum<3, False, 0>) { + be_qfunc_expanded___0(be_state___block_captured__main__0, be_state___data_captured__main__0); } -qfunc u_1_lambda___0_0_expanded___0(be_state_captured__main__0: BlockEncodedState) { +qfunc u_1_lambda___0_0_expanded___0(be_state___block_captured__main__0: qnum<5, False, 0>, be_state___data_captured__main__0: qnum<3, False, 0>) { invert { - u_0_lambda___0_0_expanded___0(be_state_captured__main__0); + u_0_lambda___0_0_expanded___0(be_state___block_captured__main__0, be_state___data_captured__main__0); } } qfunc qsvt_step_expanded___0(phase1: real, phase2: real, aux: qbit, be_state_captured__main__0: BlockEncodedState) { - u_1_lambda___0_0_expanded___0(be_state_captured__main__0); + u_1_lambda___0_0_expanded___0(be_state_captured__main__0.block, be_state_captured__main__0.data); projector_controlled_phase_expanded___0(phase1, aux, be_state_captured__main__0); invert { - u_1_lambda___0_0_expanded___0(be_state_captured__main__0); + u_1_lambda___0_0_expanded___0(be_state_captured__main__0.block, be_state_captured__main__0.data); } projector_controlled_phase_expanded___0(phase2, aux, be_state_captured__main__0); } @@ -886,7 +886,7 @@ qfunc qsvt_expanded___0(phase_seq: real[], aux: qbit, be_state_captured__main__0 } if (False) { } else { - u_1_lambda___0_0_expanded___0(be_state_captured__main__0); + u_1_lambda___0_0_expanded___0(be_state_captured__main__0.block, be_state_captured__main__0.data); projector_controlled_phase_expanded___0(phase_seq[101], aux, be_state_captured__main__0); } H(aux); @@ -914,108 +914,108 @@ qfunc main(output qsvt_aux: qbit, output data: qnum<3, False, 0>, output block: {data, block} -> be_state; } apply { qsvt_inversion_expanded___0([ - 3.0981, - 3.147, - 3.1359, - 3.1476, - 3.1352, - 3.1483, - 3.1345, - 3.149, - 3.1338, + 3.1411, + 3.1426, + 3.1401, + 3.1436, + 3.1391, + 3.1446, + 3.1381, + 3.1456, + 3.137, + 3.1467, + 3.136, + 3.1477, + 3.135, + 3.1488, + 3.1339, 3.1498, - 3.1331, - 3.1505, - 3.1323, - 3.1513, - 3.1314, - 3.1522, - 3.1306, - 3.153, + 3.1329, + 3.1509, + 3.1318, + 3.1519, + 3.1308, + 3.1529, 3.1297, - 3.1539, - 3.1288, - 3.1548, - 3.1279, - 3.1558, - 3.1269, - 3.1567, - 3.126, - 3.1577, - 3.125, - 3.1587, - 3.124, - 3.1597, - 3.123, - 3.1607, - 3.1219, - 3.1618, - 3.1209, - 3.1628, - 3.1199, - 3.1638, - 3.1189, + 3.154, + 3.1287, + 3.155, + 3.1276, + 3.1561, + 3.1266, + 3.1571, + 3.1256, + 3.1581, + 3.1246, + 3.1591, + 3.1236, + 3.1601, + 3.1226, + 3.1611, + 3.1216, + 3.1621, + 3.1206, + 3.163, + 3.1197, + 3.1639, + 3.1188, 3.1648, 3.1179, - 3.1658, - 3.1169, - 3.1667, - 3.116, - 3.1677, - 3.115, - 3.1686, - 3.1142, - 3.1142, - 3.1686, - 3.115, - 3.1677, - 3.116, - 3.1667, - 3.1169, - 3.1658, + 3.1657, + 3.1171, + 3.1665, + 3.1162, + 3.1162, + 3.1665, + 3.1171, + 3.1657, 3.1179, 3.1648, - 3.1189, - 3.1638, - 3.1199, - 3.1628, - 3.1209, - 3.1618, - 3.1219, - 3.1607, - 3.123, - 3.1597, - 3.124, - 3.1587, - 3.125, - 3.1577, - 3.126, - 3.1567, - 3.1269, - 3.1558, - 3.1279, - 3.1548, - 3.1288, - 3.1539, + 3.1188, + 3.1639, + 3.1197, + 3.163, + 3.1206, + 3.1621, + 3.1216, + 3.1611, + 3.1226, + 3.1601, + 3.1236, + 3.1591, + 3.1246, + 3.1581, + 3.1256, + 3.1571, + 3.1266, + 3.1561, + 3.1276, + 3.155, + 3.1287, + 3.154, 3.1297, - 3.153, - 3.1306, - 3.1522, - 3.1314, - 3.1513, - 3.1323, - 3.1505, - 3.1331, + 3.1529, + 3.1308, + 3.1519, + 3.1318, + 3.1509, + 3.1329, 3.1498, - 3.1338, - 3.149, - 3.1345, - 3.1483, - 3.1352, - 3.1476, - 3.1359, - 3.147, - (-314.2028) + 3.1339, + 3.1488, + 3.135, + 3.1477, + 3.136, + 3.1467, + 3.137, + 3.1456, + 3.1381, + 3.1446, + 3.1391, + 3.1436, + 3.1401, + 3.1426, + (-314.1598) ], qsvt_aux, be_state); } } diff --git a/applications/cfd/qls_for_hybrid_solvers/qsvt_solver_pauli_be.synthesis_options.json b/applications/cfd/qls_for_hybrid_solvers/qsvt_solver_pauli_be.synthesis_options.json index a95ee6304..998f0672b 100644 --- a/applications/cfd/qls_for_hybrid_solvers/qsvt_solver_pauli_be.synthesis_options.json +++ b/applications/cfd/qls_for_hybrid_solvers/qsvt_solver_pauli_be.synthesis_options.json @@ -6,28 +6,28 @@ "preferences": { "custom_hardware_settings": { "basis_gates": [ + "h", "z", - "x", + "sdg", + "u1", + "u2", "rx", - "cz", - "cx", - "sxdg", + "cy", "tdg", - "u1", - "u", + "x", + "rz", "p", - "r", + "sx", "y", + "cz", + "s", "ry", - "cy", - "rz", - "sdg", - "u2", + "u", + "cx", "t", "id", - "h", - "sx", - "s" + "sxdg", + "r" ], "is_symmetric_connectivity": true }, @@ -36,7 +36,7 @@ "optimization_level": 1, "output_format": ["qasm"], "pretty_qasm": true, - "random_seed": 2053604307, + "random_seed": 1040360724, "synthesize_all_separately": false, "timeout_seconds": 300, "transpilation_option": "auto optimize" diff --git a/applications/cfd/qls_for_hybrid_solvers/quantum_functions_be.py b/applications/cfd/qls_for_hybrid_solvers/quantum_functions_be.py deleted file mode 100644 index caffad8fa..000000000 --- a/applications/cfd/qls_for_hybrid_solvers/quantum_functions_be.py +++ /dev/null @@ -1,220 +0,0 @@ -from sympy import fwht -from classiq import * -import numpy as np -from classiq.open_library.functions.state_preparation import apply_phase_table - -ANGLE_THRESHOLD = 1e-13 - - -def get_graycode(size, i) -> int: - if i == 2**size: - return get_graycode(size, 0) - return i ^ (i >> 1) - - -def get_graycode_angles_wh(size, angles): - transformed_angles = fwht(np.array(angles) / 2**size) - return [transformed_angles[get_graycode(size, j)] for j in range(2**size)] - - -def get_graycode_ctrls(size): - return [ - (get_graycode(size, i) ^ get_graycode(size, i + 1)).bit_length() - 1 - for i in range(2**size) - ] - - -@qfunc -def multiplex_ra(a_y: float, a_z: float, angles: list[float], qba: QArray, ind: QBit): - assert a_y**2 + a_z**2 == 1 - # TODO support general (0,a_y,a_z) rotation - assert ( - a_z == 1.0 or a_y == 1.0 - ), "currently only strict y or z rotations are supported" - size = max(1, (len(angles) - 1).bit_length()) - extended_angles = angles + [0] * (2**size - len(angles)) - transformed_angles = get_graycode_angles_wh(size, extended_angles) - controllers = get_graycode_ctrls(size) - - for k in range(2**size): - if np.abs(transformed_angles[k]) > ANGLE_THRESHOLD: - if a_z == 0.0: - RY(transformed_angles[k], ind) - else: - RZ(transformed_angles[k], ind) - - skip_control(lambda: CX(qba[controllers[k]], ind)) - - -@qfunc -def lcu_paulis_graycode(terms: list[SparsePauliTerm], data: QArray, block: QArray): - n_qubits = data.len - n_terms = len(terms) - table_z = np.zeros([n_qubits, n_terms]) - table_y = np.zeros([n_qubits, n_terms]) - probs = [abs(term.coefficient) for term in terms] + [0.0] * (2**block.len - n_terms) - hamiltonian_coeffs = np.angle([term.coefficient for term in terms]).tolist() + [ - 0.0 - ] * (2**block.len - n_terms) - accumulated_phase = np.zeros(2**block.len).tolist() - - for k in range(n_terms): - for pauli in terms[k].paulis: - if pauli.pauli == Pauli.Z: - table_z[pauli.index, k] = -np.pi - accumulated_phase[k] += np.pi / 2 - elif pauli.pauli == Pauli.Y: - table_y[pauli.index, k] = -np.pi - accumulated_phase[k] += np.pi / 2 - elif pauli.pauli == Pauli.X: - table_z[pauli.index, k] = -np.pi - table_y[pauli.index, k] = np.pi - accumulated_phase[k] += np.pi / 2 - - def select_graycode(block: QArray, data: QArray): - for i in range(n_qubits): - multiplex_ra(0, 1, table_z[i, :], block, data[i]) - multiplex_ra(1, 0, table_y[i, :], block, data[i]) - apply_phase_table( - [p1 - p2 for p1, p2 in zip(hamiltonian_coeffs, accumulated_phase)], block - ) - - within_apply( - lambda: inplace_prepare_state(probs, 0.0, block), - lambda: select_graycode(block, data), - ) - - -""" Loading of a single diagonal with a given offset""" - - -@qfunc -def load_diagonal(offset: int, diag: list[float], ind: QBit, x: QNum) -> None: - - if offset != 0: - inplace_add(offset, x) - assign_amplitude_table(diag, x, ind) - - -@qfunc -def load_banded_diagonals( - offsets: list[int], diags: list[list[float]], ind: QBit, x: QNum, s: QNum -) -> None: - for i in range(len(offsets)): - control(s == i, lambda: load_diagonal(-offsets[i], diags[i], ind, x)) - - -@qfunc -def block_encode_banded( - offsets: list[int], - diags: list[list[float]], - prep_diag: CArray[CReal], - block: QNum, - data: QNum, -) -> None: - s = QNum(size=block.size - 1) - ind = QBit() - bind(block, [s, ind]) - within_apply( - lambda: inplace_prepare_state(prep_diag, 0.0, s), - lambda: load_banded_diagonals(offsets, diags, ind, data, s), - ) - - X(ind) - bind([s, ind], block) - - -@qfunc -def block_encode_banded_controlled( - ctrl_state: CInt, - offsets: list[int], - diags: list[list[float]], - prep_diag: CArray[CReal], - block: QNum, - data: QNum, - ctrl: QNum, -) -> None: - if offsets.len < 2 ** ((offsets.len - 1).bit_length()): - """ - Efficient controlled version when the number of diagonals is not an exact power of 2. - """ - s = QNum(size=block.size - 1) - ind = QBit() - bind(block, [s, ind]) - within_apply( - lambda: control( - ctrl == ctrl_state, - lambda: inplace_prepare_state(prep_diag, 0.0, s), - lambda: apply_to_all(X, s), - ), - lambda: load_banded_diagonals(offsets, diags, ind, data, s), - ) - control(ctrl == ctrl_state, lambda: X(ind)) - bind([s, ind], block) - else: - control( - ctrl == ctrl_state, - lambda: block_encode_banded(offsets, diags, prep_diag, block, data), - ) - - -@qfunc -def be_e3(data: QBit, block: QBit): - lcu( - coefficients=[1, 1], - unitaries=[lambda: RY(np.pi, data), lambda: X(data)], - block=block, - ) - - -@qfunc -def block_encode_banded_sym( - offsets: list[int], - diags: list[list[float]], - prep_diag: CArray[CReal], - block: QArray, - data: QArray, -) -> None: - """ - This is a simple LCU of block encoding the upper-right and lower-left block. - However, we use an explicit controlled version of block_encode_banded, given by the function - block_encode_banded_controlled. - """ - lcu_block = QBit() - sym_block = QBit() - sym_data = QBit() - reduced_block = QArray() - reduced_data = QArray() - within_apply( - lambda: ( - bind( - data, [reduced_data, sym_data] - ), # separate to different variables for clarity - bind(block, [reduced_block, sym_block, lcu_block]), - H(lcu_block), - ), - lambda: ( - control(lcu_block == 1, lambda: be_e3(sym_data, sym_block)), - block_encode_banded_controlled( - ctrl=lcu_block, - ctrl_state=1, - offsets=offsets, - diags=diags, - prep_diag=prep_diag, - block=reduced_block, - data=reduced_data, - ), - control(lcu_block == 0, lambda: invert(lambda: be_e3(sym_data, sym_block))), - invert( - lambda: block_encode_banded_controlled( - ctrl=lcu_block, - ctrl_state=0, - offsets=offsets, - diags=diags, - prep_diag=prep_diag, - block=reduced_block, - data=reduced_data, - ) - ), - ), - ) diff --git a/applications/cfd/qls_for_hybrid_solvers/verify_block_encoding.ipynb b/applications/cfd/qls_for_hybrid_solvers/verify_block_encoding.ipynb index e3392191a..7492d3a69 100644 --- a/applications/cfd/qls_for_hybrid_solvers/verify_block_encoding.ipynb +++ b/applications/cfd/qls_for_hybrid_solvers/verify_block_encoding.ipynb @@ -11,7 +11,7 @@ "1. Prepare and Select for Pauli decomposition of the matrix. The Select block is implemented with Gray code technique.\n", "2. Banded diagonal block encoding, according to Ref.\n", "\n", - "For both block encoding we construct a symmetric and non-symmetric versions. The former is needed for the Chebyshev LCU solver, and the latter for the QSVT solver." + "For both block encoding we construct a symmetric and non-symmetric versions." ] }, { @@ -34,8 +34,9 @@ "source": [ "import matplotlib.pyplot as plt\n", "import numpy as np\n", - "from classical_functions_be import *\n", - "from quantum_functions_be import *\n", + "from banded_be import *\n", + "from classical_functions_be import get_projected_state_vector\n", + "from pauli_be import *\n", "from scipy import sparse\n", "\n", "from classiq import *\n", @@ -190,7 +191,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "Quantum program link: https://platform.classiq.io/circuit/36clYt5vTgKwV322m9vX6voStLG\n" + "Quantum program link: https://platform.classiq.io/circuit/37T194tII0d7o0gv7lmtvsIhT81\n" ] } ], @@ -271,7 +272,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "Quantum program link: https://platform.classiq.io/circuit/36claAZvW0wzyZ1C4AQIlFi6x6Q\n" + "Quantum program link: https://platform.classiq.io/circuit/37T1AUJg5LXloVGSyrzhGapt3AC\n" ] } ], @@ -353,7 +354,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "Quantum program link: https://platform.classiq.io/circuit/36clcAZVufxjCkc3jHenNb4Dn9m\n" + "Quantum program link: https://platform.classiq.io/circuit/37T1ClQiOA8YDfpBcdJe8Wyq4cY\n" ] } ], @@ -443,7 +444,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "Quantum program link: https://platform.classiq.io/circuit/36cleTDZbz3YxhTYVIKpnxh1EUi\n" + "Quantum program link: https://platform.classiq.io/circuit/37T1F9ZCWg4Jvvd8k9exfSZJv7p\n" ] } ],