# Copyright (c) 2021 Institute for Quantum Computing, Baidu Inc. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """ Trotter Hamiltonian time evolution circuit module """ from paddle_quantum.utils import Hamiltonian from paddle_quantum.circuit import UAnsatz from collections import defaultdict import warnings import numpy as np import re import paddle PI = paddle.to_tensor(np.pi, dtype='float64') def construct_trotter_circuit( circuit: UAnsatz, hamiltonian: Hamiltonian, tau: float, steps: int, method: str = 'suzuki', order: int = 1, grouping: str = None, coefficient: np.ndarray or paddle.Tensor = None, permutation: np.ndarray = None ): r"""向 circuit 的后面添加 trotter 时间演化电路,即给定一个系统的哈密顿量 H,该电路可以模拟系统的时间演化 :math:`U_{cir}~ e^{-iHt}` 。 Args: circuit (UAnsatz): 需要添加时间演化电路的 UAnsatz 对象 hamiltonian (Hamiltonian): 需要模拟时间演化的系统的哈密顿量 H tau (float): 每个 trotter 块的演化时间长度 steps (int): 添加多少个 trotter 块(提示: ``steps * tau`` 即演化的时间总长度 t) method (str): 搭建时间演化电路的方法,默认为 ``'suzuki'`` ,即使用 Trotter-Suzuki 分解。可以设置为 ``'custom'`` 来使用自定义的演化策略 (需要用 permutation 和 coefficient 来定义) order (int): Trotter-Suzuki decomposition 的阶数,默认为 ``1`` ,仅在使用 ``method='suzuki'`` 时有效 grouping (str): 是否对哈密顿量进行指定策略的重新排列,默认为 ``None`` ,支持 ``'xyz'`` 和 ``'even_odd'`` 两种方法 coefficient (array or Tensor): 自定义时间演化电路的系数,对应哈密顿量中的各项,默认为 ``None`` ,仅在 ``method='custom'`` 时有效 permutation (array): 自定义哈密顿量的排列方式,默认为 ``None`` ,仅在 ``method='custom'`` 时有效 代码示例: .. code-block:: python from paddle_quantum.utils import Hamiltonian from paddle_quantum.circuit import UAnsatz from paddle_quantum.trotter import construct_trotter_circuit, get_1d_heisenberg_hamiltonian import numpy as np h = get_1d_heisenberg_hamiltonian(length=3) cir = UAnsatz(h.n_qubits) t = 1 r = 10 # 1st order product formula (PF) circuit construct_trotter_circuit(cir, h, tau=t/r, steps=r) # 2nd order product formula (PF) circuit construct_trotter_circuit(cir, h, tau=t/r, steps=r, order=2) # higher order product formula (PF) circuit construct_trotter_circuit(cir, h, tau=t/r, steps=r, order=10) # customize coefficient and permutation # the following codes is equivalent to adding the 1st order PF permutation = np.arange(h.n_terms) coefficients = np.ones(h.n_terms) construct_trotter_circuit(cir, h, tau=t/r, steps=r, method='custom', permutation=permutation, coefficient=coefficients) Hint: 对于该函数的原理以及其使用方法更详细的解释,可以参考量桨官网中量子模拟部分的教程 https://qml.baidu.com/tutorials/overview.html """ # check the legitimacy of the inputs (coefficient and permutation) def check_input_legitimacy(arg_in): if not isinstance(arg_in, np.ndarray) and not isinstance(arg_in, paddle.Tensor): arg_out = np.array(arg_in) else: arg_out = arg_in if arg_out.ndim == 1 and isinstance(arg_out, np.ndarray): arg_out = arg_out.reshape(1, arg_out.shape[0]) elif arg_out.ndim == 1 and isinstance(arg_out, paddle.Tensor): arg_out = arg_out.reshape([1, arg_out.shape[0]]) return arg_out # check compatibility between input method and customization arguments if (permutation is not None or coefficient is not None) and (method != 'custom'): warning_message = 'method {} is not compatible with customized permutation ' \ 'or coefficient and will be overlooked'.format(method) method = 'custom' warnings.warn(warning_message, RuntimeWarning) # check the legitimacy of inputs if method == 'suzuki': if order > 2 and order % 2 != 0 and type(order) != int: raise ValueError('The order of the trotter-suzuki decomposition should be either 1, 2 or 2k (k an integer)' ', got order = %i' % order) # check and reformat inputs for 'custom' mode elif method == 'custom': # check permutation if permutation is not None: permutation = np.array(check_input_legitimacy(permutation), dtype=int) # give a warning for using permutation and grouping at the same time if grouping: warning_message = 'Using specified permutation and automatic grouping {} at the same time, the ' \ 'permutation will act on the grouped Hamiltonian'.format(grouping) warnings.warn(warning_message, RuntimeWarning) # check coefficient if coefficient is not None: coefficient = check_input_legitimacy(coefficient) # if the permutation is not specified, then set it to [[1, 2, ...], ...] if coefficient is not None and permutation is None: permutation = np.arange(hamiltonian.n_terms) if coefficient.ndim == 1 \ else np.arange(hamiltonian.n_terms).reshape(1, hamiltonian.n_terms).repeat(coefficient.shape[0], axis=0) permutation = np.arange(hamiltonian.n_terms).reshape(1, hamiltonian.n_terms) permutation = permutation.repeat(coefficient.shape[0], axis=0) # if the coefficient is not specified, set a uniform (normalized) coefficient if permutation is not None and coefficient is None: coefficient = 1 / len(permutation) * np.ones_like(permutation) # the case that the shapes of input coefficient and permutations don't match if tuple(permutation.shape) != tuple(coefficient.shape): # case not-allowed if permutation.shape[1] != coefficient.shape[1]: raise ValueError('Shape of the permutation and coefficient array don\'t match, got {} and {}'.format( tuple(permutation.shape), tuple(coefficient.shape) )) # cases can be fixed by repeating one of the two inputs elif permutation.shape[0] != coefficient.shape[0] and permutation[0] == 1: permutation = permutation.repeat(coefficient.shape[0]) elif permutation.shape[0] != coefficient.shape[0] and coefficient[0] == 1: if isinstance(coefficient, paddle.Tensor): coefficient = paddle.stack([coefficient for _ in range(permutation.shape[0])])\ .reshape([permutation.shape[0], permutation.shape[1]]) elif isinstance((coefficient, np.ndarray)): coefficient = coefficient.repeat(permutation.shape[0]) # group the hamiltonian according to the input if not grouping: grouped_hamiltonian = [hamiltonian] elif grouping == 'xyz': grouped_hamiltonian = __group_hamiltonian_xyz(hamiltonian=hamiltonian) elif grouping == 'even_odd': grouped_hamiltonian = __group_hamiltonian_even_odd(hamiltonian=hamiltonian) else: raise ValueError("Grouping method %s is not supported, valid key words: 'xyz', 'even_odd'" % grouping) # apply trotter blocks for step in range(steps): if method == 'suzuki': _add_trotter_block(circuit=circuit, tau=tau, grouped_hamiltonian=grouped_hamiltonian, order=order) elif method == 'custom': _add_custom_block(circuit=circuit, tau=tau, grouped_hamiltonian=grouped_hamiltonian, custom_coefficients=coefficient, permutation=permutation) else: raise ValueError("The method %s is not supported, valid method keywords: 'suzuki', 'custom'" % method) def __get_suzuki_num(order): r"""计算阶数为 order 的 suzuki product formula 的 trotter 数。 """ if order == 1 or order == 2: n_suzuki = order elif order > 2 and order % 2 == 0: n_suzuki = 2 * 5 ** (order // 2 - 1) else: raise ValueError('The order of the trotter-suzuki decomposition should be either 1, 2 or 2k (k an integer)' ', got order = %i' % order) return n_suzuki def __sort_pauli_word(pauli_word, site): r""" 将 pauli_word 按照 site 的大小进行排列,并同时返回排序后的 pauli_word 和 site。 Note: 这是一个内部函数,一般你不需要直接使用它。 """ sort_index = np.argsort(np.array(site)) return ''.join(np.array(list(pauli_word))[sort_index].tolist()), np.array(site)[sort_index] def _add_trotter_block(circuit, tau, grouped_hamiltonian, order): r""" 添加一个 trotter 块,i.e. :math:`e^{-iH\tau}`,并使用 Trotter-Suzuki 分解对其进行展开。 Args: circuit (UAnsatz): 需要添加 trotter 块的电路 tau (float or tensor): 该 trotter 块的演化时间 grouped_hamiltonian (list): 一个由 Hamiltonian 对象组成的列表,该函数会默认该列表中的哈密顿量为 Trotter-Suzuki 展开的基本项 order (int): Trotter-Suzuki 展开的阶数 Note (关于 grouped_hamiltonian 的使用方法): 以二阶的 trotter-suzki 分解 S2(t) 为例,若 grouped_hamiltonian = [H_1, H_2],则会按照 (H_1, t/2)(H_2, t/2)(H_2, t/2)(H_1, t/2) 的方法进行添加 trotter 电路 特别地,若用户没有预先对 Hamiltonian 进行 grouping 的话,传入一个单个的 Hamiltonian 对象,则该函数会按照该 Hamiltonian 的顺序进行正则(canonical)的分解:依然以二阶 trotter 为例,若传入单个 H,则添加 (H[0:-1:1], t/2)(H[-1:0:-1], t/2) 的电路 Warning: 本函数一般情况下为内部函数,不会对输入的合法性进行检测和尝试修正。推荐使用 construct_trotter_circuit() 来构建时间演化电路 """ if order == 1: __add_first_order_trotter_block(circuit, tau, grouped_hamiltonian) elif order == 2: __add_second_order_trotter_block(circuit, tau, grouped_hamiltonian) else: __add_higher_order_trotter_block(circuit, tau, grouped_hamiltonian, order) pass def _add_custom_block(circuit, tau, grouped_hamiltonian, custom_coefficients, permutation): r""" 添加一个自定义形式的 trotter 块 Args: circuit (UAnsatz): 需要添加 trotter 块的电路 tau (float or tensor): 该 trotter 块的演化时间 grouped_hamiltonian (list): 一个由 Hamiltonian 对象组成的列表,该函数会默认该列表中的哈密顿量为 trotter-suzuki 展开的基本项 order (int): trotter-suzuki 展开的阶数 permutation (np.ndarray): 自定义置换 custom_coefficients (np.ndarray or Tensor): 自定义系数 Warning: 本函数一般情况下为内部函数,不会对输入的合法性进行检测和尝试修正。推荐使用 construct_trotter_circuit() 来构建时间演化电路 """ # combine the grouped hamiltonian into one single hamiltonian hamiltonian = sum(grouped_hamiltonian, Hamiltonian([])) # apply trotter circuit according to coefficient and h_coeffs, pauli_words, sites = hamiltonian.decompose_with_sites() for i in range(permutation.shape[0]): for term_index in range(permutation.shape[1]): custom_coeff = custom_coefficients[i][term_index] term_index = permutation[i][term_index] pauli_word, site = __sort_pauli_word(pauli_words[term_index], sites[term_index]) coeff = h_coeffs[term_index] * custom_coeff add_n_pauli_gate(circuit, 2 * tau * coeff, pauli_word, site) def __add_first_order_trotter_block(circuit, tau, grouped_hamiltonian, reverse=False): r""" 添加一阶 trotter-suzuki 分解的时间演化块 Notes: 这是一个内部函数,你不需要使用它 """ if not reverse: for hamiltonian in grouped_hamiltonian: assert isinstance(hamiltonian, Hamiltonian) #将原哈密顿量中相同site的XX,YY,ZZ组合到一起 grouped_hamiltonian = [] coeffs, pauli_words, sites = hamiltonian.decompose_with_sites() grouped_terms_indices = [] left_over_terms_indices = [] d = defaultdict(list) #合并相同site的XX,YY,ZZ for term_index in range(len(coeffs)): site = sites[term_index] pauli_word = pauli_words[term_index] for pauli in ['XX', 'YY', 'ZZ']: assert isinstance(pauli_word, str), "Each pauli word should be a string type" if (pauli_word==pauli or pauli_word==pauli.lower()): key = tuple(sorted(site)) d[key].append((pauli,term_index)) if len(d[key])==3: terms_indices_to_be_grouped = [x[1] for x in d[key]] grouped_terms_indices.extend(terms_indices_to_be_grouped) grouped_hamiltonian.append(hamiltonian[terms_indices_to_be_grouped]) #其他的剩余项 for term_index in range(len(coeffs)): if term_index not in grouped_terms_indices: left_over_terms_indices.append(term_index) if len(left_over_terms_indices): for term_index in left_over_terms_indices: grouped_hamiltonian.append(hamiltonian[term_index]) #得到新的哈密顿量 res = grouped_hamiltonian[0] for i in range(1,len(grouped_hamiltonian)): res+=grouped_hamiltonian[i] hamiltonian = res # decompose the Hamiltonian into 3 lists coeffs, pauli_words, sites = hamiltonian.decompose_with_sites() # apply rotational gate of each term term_index = 0 while term_index