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 _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
        self.sq_pred = power_function(self.q_epochs, self.a, self.b) + np.tanh(np.power(self.d_est,2)*(self.q_epochs-self.epochs[-1]))
        # 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.sd_pred = np.concatenate([self.s_pred, np.array([self.d_pred])])
    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
    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
            predict = power_function(epoch, a, b) + np.tanh(np.power(self.d_est,2)*(epoch-self.epochs[-1]))
            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)])