def _predicted_state_estimation(self):
        self.a, self.b = power_regression(self.epochs, self.s_est, np.ones(self.currentWinSize))
        # next q points predicted by current regression line
        # self.q_epochs = self.epochs[:self.pointPeriod] + self.currentWinSize * self.numEpsBtwVal
        self.q_epochs = np.arange(self.epochs[-1]+self.numEpsBtwVal, self.epochs[-1]+(self.pointPeriod+1)*self.numEpsBtwVal, self.numEpsBtwVal)
#        self.sq_pred = power_function(self.q_epochs, self.a, self.b) + self.d_est
        # if self.c_est - self.epochs[-1] == 0:
        #     self.sq_pred = power_function(self.q_epochs, self.a, self.b)
        # else:
        self.sq_pred = power_function(self.q_epochs, self.a, self.b) + power_function(self.q_epochs - self.epochs[-1], np.power(self.c_est,2), np.power(self.d_est, 2))
        # next state predition
        if self.currentWinSize + self.pointPeriod >= self.predWinSize:
            self.s_pred = np.concatenate([self.s_est[len(self.s_est)-(self.predWinSize-self.pointPeriod):], self.sq_pred])
        else:
            self.s_pred = np.concatenate([self.s_est, self.sq_pred])
        self.d_pred = self.d_est
        self.c_pred = self.c_est
        self.sd_pred = np.concatenate([self.s_pred, np.array([self.c_pred, self.d_pred])])
    def __init__(self,
                 num_epochs_between_val,
                 pred_win_size,
                 period,
                 init_x,
                 init_d,
                 process_noise_var=1e-6,
#                 num_extra_win_size=20,
                 noise_est_win_size=None
                ):
        if len(init_x) < period:
            print("Better set estimation start position larger than period")
        
        if noise_est_win_size == None:
            self.noise_est_win_size = pred_win_size
        else:
            self.noise_est_win_size = noise_est_win_size

        self.numEpsBtwVal = num_epochs_between_val
        self.predWinSize = pred_win_size
        self.currentWinSize = min(len(init_x), self.predWinSize)
        self.pointPeriod = period # in terms of # of points
        self.epochPeriod = period * num_epochs_between_val # in terms of # of epochs

        self.weights = np.ones(self.predWinSize) # the weights when doing regression

        # KF init
        # the # of points for R estimation, if any
#        self.RWinSize = num_extra_win_size + pred_win_size

        ## variance of process noise
        self.var_ud = process_noise_var
        epochs = (np.arange(len(init_x))+1)*self.numEpsBtwVal
        a, b = power_regression(epochs, init_x, np.ones(len(init_x)))
        s_est = power_function(epochs, a, b)
        # A = np.vstack([epochs, np.ones(len(init_x))]).T
        # a, b = np.linalg.lstsq(A, init_x, rcond=-1)[0]
        # s_est = a*epochs + b

        self.epochs = epochs[len(epochs) - self.currentWinSize:]
        self.x = init_x[len(init_x) - self.currentWinSize:]
        self.s_est = s_est[len(s_est) - self.currentWinSize:]
#        self.d_est = self.x[-1] - self.s_est[-1]
        self.d_est = init_d
        ## updated state/signal estimation
        self.sd_est = np.concatenate([self.s_est, np.array([self.d_est])])
        ## updated covariance estimation
        # self.M_est = np.zeros((self.currentWinSize+1,self.currentWinSize+1))
#        dev = self.x - self.s_est
#        dev_d = np.concatenate([dev, np.zeros(1)]).reshape([-1,1])
#        self.M_est = dev_d.dot(dev_d.T)
        self.M_est = np.ones((self.currentWinSize+1,self.currentWinSize+1))

        # the array that store all x and all s for now
        self.all_original_data = np.array(init_x)
        self.all_estimates = s_est
    def _f_derivative(self):
        log_epochs = np.log(self.epochs)
        var_log_epochs = np.var(log_epochs)
        mean_log_epochs = np.mean(log_epochs)

        coeff = (np.log(self.q_epochs) - mean_log_epochs)/var_log_epochs
        coeff = coeff.reshape((1,-1))
        dev = (log_epochs - mean_log_epochs).reshape((-1,1))
        part1 = 1 + dev.dot(coeff)

        diag = np.diag(np.reciprocal(self.s_est))
        part2 = diag.dot(part1)

        vertical_ones_w = np.ones(self.currentWinSize).reshape((-1,1))
        temp = self.sq_pred.reshape((1,-1))

        # the devirative of f_{1:q}
        dy_1q = (vertical_ones_w.dot(temp))*part2
        vertical_ones_q = np.ones(self.pointPeriod).reshape((-1,1))
#        df_1q = np.concatenate([dy_1q.T, vertical_ones_q], axis=1)
        df_dc = 2*self.c_est*power_function(self.q_epochs - self.epochs[-1], 1, np.power(self.d_est,2)).reshape(-1,1)
        df_dd = (np.power(self.c_est, 2)*2*self.d_est*np.log(self.q_epochs)*power_function(self.q_epochs - self.epochs[-1], 1, np.power(self.d_est, 2))).reshape(-1,1)
        df_1q = np.concatenate([dy_1q.T, df_dc, df_dd], axis=1)

        # get the derivative of whole f
        if self.currentWinSize >= self.predWinSize:
            I = np.eye(self.predWinSize - self.pointPeriod)
            zeros = np.zeros((self.predWinSize-self.pointPeriod, self.pointPeriod))
            zeros2 = np.zeros((self.predWinSize-self.pointPeriod, 2))
            df_qw = np.concatenate([zeros, I, zeros2], axis=1)
            dfd = np.concatenate([np.zeros((2,self.predWinSize)), np.eye(2)], axis=1)
        elif self.currentWinSize + self.pointPeriod < self.predWinSize:
            I = np.eye(self.currentWinSize)
            zeros = np.zeros((self.currentWinSize,2))
            df_qw = np.concatenate([I, zeros], axis=1)
            dfd = np.concatenate([np.zeros((2,self.currentWinSize)), np.eye(2)], axis=1)
        else:
            I = np.eye(self.predWinSize - self.pointPeriod)
            zeros = np.zeros((self.predWinSize - self.pointPeriod, self.currentWinSize - self.predWinSize + self.pointPeriod+2))
            df_qw = np.concatenate([I, zeros], axis=1)
            dfd = np.concatenate([np.zeros((2,self.currentWinSize)), np.eye(2)], axis=1)

        self.F = np.concatenate([df_qw, df_1q, dfd])
    def predictionByCurrent(self, num):
        s_queue = list(self.s_est)
        epoch_queue = list(self.epochs)
        predicts = []
        a, b = power_regression(np.array(epoch_queue), np.array(s_queue), np.ones(len(s_queue)))
        for i in range(1,num):
            # if (i-1) % self.pointPeriod == 0:
            #     a, b = power_regression(np.array(epoch_queue), np.array(s_queue), np.ones(len(s_queue)))
            epoch = self.epochs[-1] + i * self.numEpsBtwVal
            if len(epoch_queue) >= self.predWinSize:
                epoch_queue.pop(0)
            epoch_queue.append(epoch)

#            predict = power_function(epoch, a, b) + self.d_est
            if self.c_est == 0:
                predict = power_function(epoch, a, b)
            else:
                predict = power_function(epoch, a, b) + power_function(epoch - self.epochs[-1], np.power(self.c_est,2), np.power(self.d_est, 2))
            predicts.append(predict)
            if len(s_queue) >= self.predWinSize:
                s_queue.pop(0)
            s_queue.append(predict)

        return np.array(predicts), np.concatenate([self.all_estimates, np.array(predicts)])
    def __init__(
        self,
        num_epochs_between_val,
        # pred_win_size,
        # period,
        init_x,
        init_d,
        init_epochs,
        noise_est_win_size,
        process_noise_var=1e-6,
    ):
        self.noise_est_win_size = noise_est_win_size
        self.currentWinSize = len(init_x)
        self.numEpsBtwVal = num_epochs_between_val
        # self.predWinSize = pred_win_size
        # self.currentWinSize = min(len(init_x), self.predWinSize)
        # self.pointPeriod = period # in terms of # of points
        # self.epochPeriod = period * num_epochs_between_val # in terms of # of epochs

        # self.weights = np.ones(self.predWinSize) # the weights when doing regression

        # KF init
        ## variance of process noise
        self.var_ud = process_noise_var
        a, b = power_regression(init_epochs, init_x,
                                np.ones(self.currentWinSize))
        self.s_est = power_function(init_epochs, a, b)

        self.epochs = init_epochs
        self.x = init_x

        self.d_est = init_d
        ## updated state/signal estimation
        self.sd_est = np.concatenate([self.s_est, np.array([self.d_est])])
        ## updated covariance estimation
        self.M_est = np.ones(
            (self.currentWinSize + 1, self.currentWinSize + 1))

        # the array that store all x and all s for now
        self.all_original_data = np.array(init_x)
        self.all_estimates = self.s_est

        self.next_x = self.x
        self.nextWinSize = self.currentWinSize
        self.next_epochs = self.epochs