Source code for qaoa.problems.portfolio_rebalancing

import math 
from qrisp import cx, RYGate, ry, x, rzz, rz
import numpy as np 



[docs] def portfolio_cost_operator(problem): """ | Quantum cost operator for the discrete portfolio rebalancing problem, as described in https://arxiv.org/pdf/1911.05296.pdf. | It is depended on the problem instance, including the old portfolio positions, the normalized covariance matrix, the normalized asset returns and trading costs. See example implementation for formatting. Parameters ---------- problem : List A list containing a the relevant data for the problem instance. Returns ------- portfolio_cost_op: function A callable function to be applied to a QuantumArray for solving the problem instance. """ old_pos = problem[0] risk_return = problem[1] covar_matrix = problem[2] asset_return = problem[3] #tradiing_cost tc = problem[4]/4 def portfolio_cost_op(q_array, gamma): #does this work???? -- seems like it would #print("cost_op") #print(len(qv)) l = q_array[1] s = q_array[0] #risk-return function for i in range(len(s)): prefac = 0 for j in range(len(s)): prefac = gamma * covar_matrix[i][j]/4 * risk_return rzz(-prefac, l[i], s[j]) rzz(-prefac, s[i], l[j]) if i == j: # case of rzz on same spin vars rz(2*prefac, l[i]) rz(2*prefac, s[i]) continue rzz(prefac, l[i], l[j]) rzz(prefac, s[i], s[j]) prefac2 = gamma *asset_return[i] * (1-risk_return)/2 rz(prefac2, l[i]) rz(- prefac2, s[i]) #trading cost function # first added term can be dropped, as it just results in a phase for i in range(len(s)): old_val = old_pos[i] rz(gamma* tc*(1 - old_val^2 - old_val), l[i]) rz(gamma* tc*(1 - old_val^2 - old_val), s[i]) rzz(gamma* tc* (2*old_val^2 -1), l[i], s[i] ) return portfolio_cost_op
[docs] def portfolio_cl_cost_function(problem): """ | Classical cost function for the discrete portfolio rebalancing problem, as described in https://arxiv.org/pdf/1911.05296.pdf. |It is depended on the problem instance, including the old portfolio positions, the normalized covariance matrix, the normalized asset returns and trading costs. See example implementation for formatting. Parameters ---------- problem : List A list containing a the relevant data for the problem instance. Returns ------- cl_cost_function: function A callable function to calculate the cost value of the problem solution. """ old_pos = problem[0] risk_return = problem[1] covar_matrix = problem[2] asset_return = problem[3] #tradiing_cost tc = problem[4] def cl_cost_function(res): #print(res) energy = 0 counts = 0 half = int(len(list(res.keys())[0][0])) key_list = [] for key, val in res.items(): #half = len(key)/2 #new_key = [int(key[i])-int(key[i+half]) for i in range(half)] new_key = [int(key[0][i])-int(key[1][i]) for i in range(half)] # ?????? key_list.append(new_key) rr1 = sum([risk_return*covar_matrix[i][j] *new_key[i]*new_key[j] for i in range(half) for j in range(half)]) rr2 = sum([(1-risk_return)*asset_return[j] *new_key[j] for j in range(half)]) c_tc= sum([tc for i in range(half) if new_key[i] != old_pos[i]]) energy -= (rr1+ rr2+ c_tc)*val counts += val final = energy/counts #print(final) return final return cl_cost_function
[docs] def portfolio_init(lots): """ | Initial state for the discrete portfolio rebalancing problem, as described in https://arxiv.org/abs/1904.07358. | Depending on the number of lots a QuantumArray is prepared, where the first index describes the short positions held and the second index describes the long postions held. Parameters ---------- lots : Int The number of lots in the initial portfolio position Returns ------- state_prep: function A callable function to be applied to a QuantumArray to receive the initial state for the problem. """ def state_prep(q_array): l = q_array[1] s = q_array[0] n = len(l) band_prefix = dict() max_pref = 0 for index in range(n- lots +1): max_pref += math.comb(n, index)*math.comb(n, lots+ index) this_pref = math.comb(n, index)*math.comb(n, lots+ index) band_prefix.setdefault(str(index), this_pref) x(l[-lots:]) param = 2 * np.arccos(np.sqrt((band_prefix["0"])/(max_pref))) ry(param, s[-1]) qc_s = s[-1].qs() # how does one do the superpos? is everything controlled with everything that came before? or just the first? or only one before? for index1 in range(1,lots): param = 2 * np.arccos(np.sqrt((band_prefix[str(index1)])/(max_pref))) cry_gate = RYGate(param).control(1) qc_s.append(cry_gate, [s[-index1], s[-index1-1]]) # the lots+ index thing below may cause problems in the future... for index2 in range(1,lots+1): cx(s[-index2],l[-lots -index2]) return state_prep