ybeta <- function(beta=beta,q=q){
  if(q == 0){
    return(cumsum(beta))
  }
  if(q == 1){
    return(cumsum(cumsum(beta)))
  }
}

Dy <- function(y=y,q=q,U=U){
  if(q == 0){
    D = U-rev(cumsum(rev(y)))
    return(D)
  }
  if(q == 1){
    D = U-rev(cumsum(cumsum(rev(y))))
    return(D)
  }
}

Splicing <-

InvL0TFk <- function(A0=A0,y=y,q=q,k=k,H=H,U=U,first=first,last=last,max.step=50){
  n = length(y)
  All = 1:n
  S = 1:(q+1)
  AS = (q+2):n
  I0 = setdiff(AS,A0)
  for(j in 1:max.step){
    m = Splicing(A=A0,I=I0,k=k,y=y,q=q,H=H,U=U,first=first,last=last)
    A = m$A
    I = m$I
    beta = m$beta
    if(identical(A,A0) & identical(I,I0)){
      break
    }else{
      A0 = A
      I0 = I
    }
  }
  yhat = ybeta(beta=beta,q=q)
  Ahat = sort(A,decreasing = FALSE)
  return(list(betak=beta,yk=yhat,Ak=Ahat))
}

InverseL0TF <- function(y=y,kmax=kmax,q=q,first=0,last=1){
  n = length(y)
  All = 1:n
  S = 1:(q+1)
  AS = (q+2):n
  low = ceiling(first*n)
  high = floor(last*n)
  AS0 = intersect(low:high,AS)
  A0 = NULL
  I0 = setdiff(AS,A0)

  beta.all = NULL
  y.all = NULL
  A.all = list()
  mse = as.numeric(kmax)
  sic = as.numeric(kmax)
  eps = as.numeric(n)
  if(q == 0){
    H = n:1/n
    U = rev(cumsum(rev(y)))
  }
  if(q == 1){
    H = sapply(n:1, function(x) x*(x+1)*(2*x+1)/6)/n
    U = rev(cumsum(cumsum(rev(y))))
  }
  Al = sort(union(S,A0),decreasing = FALSE)
  beta = rep(0,n)
  beta[Al] = solMat(n=n,q=q,A=Al)%*%U[Al]
  D = Dy(y=ybeta(beta=beta,q=q),q=q,U=U)/n
  eps = D^2/H
  I00 = intersect(I0,AS0)
  A0 = union(A0,I00[which.max(eps[I00])])
  for(j in 1:kmax){
    result = InvL0TFk(A0=A0,y=y,q=q,k=j,H=H,U=U,first=first,last=last)
    beta.all = cbind(beta.all,result$betak)
    y.all = cbind(y.all,result$yk)
    A0 = result$Ak
    I0 = setdiff(AS,A0)
    A.all[[j]] = A0 - 1
    D = Dy(y=as.vector(y.all[,j]),q=q,U=U)/n
    eps = D^2/H
    I00 = intersect(I0,AS0)
    A0 = union(A0,I00[which.max(eps[I00])])
  }
  mse = colMeans((y-y.all)^2)
  df = 1:kmax + q + 1
  sic = n*log(mse) + 2*log(log(n))*log(n)*df
  bic = n*log(mse) + 2*log(n)*df
  return(list(beta.all=beta.all,y.all=y.all,A.all=A.all,
              sic=sic,bic=bic,mse=mse))
}

#' @title The inverse L0 trend filtering with fixed change points
#' @description Fit the input data points to a piecewise constant or piecewise linear trend with a given number of change points.
#' @param y The input data points
#' @param k The given number of change points
#' @param q 0 or 1. Correspond to a piecewise constant or piecewise linear trend
#' @param first The value ranges from 0 to 1. Represent the minimum percentile point where a change point may occur. If 'first' = 0.01, it means that change points cannot appear in the first 1\% of the data points. If 'first' = 0, there is no constraint on the position of the change point.
#' @param last The value ranges from 0 to 1. Represent the maximum percentile point where a change point may occur. If 'last' = 0.99, it means that change points cannot appear in the last 1\% of the data points. If 'last' = 1, there is no constraint on the position of the change point.
#' @return
#' An S3 object of type "L0TFinvfix". A list containing the fitted trend results:
#' \item{sic}{Information criterion value with a penalty term of \eqn{2\log(\log(n)) \times \log(n)}}
#' \item{bic}{Information criterion value with a penalty term of \eqn{2 \times \log(n)}}
#' \item{mse}{The mean square error between the fitted trend and the input data}
#' \item{y}{The input data points}
#' \item{betak}{The fitted \eqn{\hat{\boldsymbol{\beta}}} coefficients with the number of change points being \eqn{k} }
#' \item{yk}{The fitted trend with the number of change points being \eqn{k}}
#' \item{Ak}{The set of position indicators of the fitted change points with the number of change points being \eqn{k}}
#' \item{beta.all}{A data frame with dimensions \eqn{n \times k}, where each column represents the fitted \eqn{\hat{\boldsymbol{\beta}}} coefficients corresponding to a given number of change points}
#' \item{y.all}{A data frame with dimensions \eqn{n \times k}, where each column represents the fitted estimated trend corresponding to a given number of change points}
#' \item{A.all}{A list of length \eqn{k}, where each element corresponds to the set of position indicators of change points under a given number }
#'
#' @examples
#' tau = c(0.1, 0.3, 0.4, 0.7, 0.85)
#' h = c(-1, 5, 3, 0, -1, 2)
#' BlocksData <- SimuBlocksInv(n = 350, sigma = 0.2, seed = 50, tau = tau ,h = h)
#' res <- L0TFinv.fix(y=BlocksData$y, k=5, q=0, first=0.01, last=1)
#' print(res$Ak)
#' print(BlocksData$setA)
#' plot(BlocksData$x, BlocksData$y, xlab="", ylab="")
#' lines(BlocksData$x, BlocksData$y0, col = "red")
#' lines(BlocksData$x, res$yk, col = "lightgreen")
#'
#' tau1 = c(0.1, 0.3, 0.4, 0.7, 0.85)
#' h1 = c(-1, 5, 3, 0, -1, 2)
#' a0 = -10
#' WaveData <- SimuWaveInv(n = 2000, sigma = 0.1, seed = 50, tau = tau1, h = h1, a0 = a0)
#' res1 <- L0TFinv.fix(y=WaveData$y, k=5, q=1, first=0, last=0.99)
#' print(res1$Ak)
#' print(WaveData$setA)
#' plot(WaveData$x, WaveData$y, xlab="", ylab="")
#' lines(WaveData$x, WaveData$y0, col = "red")
#' lines(WaveData$x, res1$yk, col = "lightgreen")
#'
#' @seealso \code{\link{L0TFinv.opt}}
#' @export
L0TFinv.fix <- function(y=y, k=k, q=q, first=0, last=1){
  if ( !(q %in% c(0,1)) ){
    stop("The order is not supported")
  }
  if( first<0 | last>1 ){
    stop("
The maximum possible range for the change points should be within [0,1]")
  }
  if( length(y) <= k+q+1 ){
    stop("The number of data points should be greater than the number of change points")
  }
  res <- InverseL0TF(y=y, kmax=k, q=q, first=first, last=last)
  betak = res$beta.all[,k]
  Ak = sort(res$A.all[[k]])
  yk = res$y.all[,k]
  G = list(sic=res$sic,bic=res$bic,mse=res$mse,
           y=y,
           betak=betak,yk=yk,Ak=Ak,
           beta.all=res$beta.all,y.all=res$y.all,A.all=res$A.all)
  class(G) <- "L0TFinvfix"
  return(G)
}

#' @title The inverse L0 trend filtering with optimal change points
#' @description Fit the input data points to a piecewise constant or piecewise linear trend with optimal change points.
#' @param y The input data points
#' @param kmax The maximum number of change points
#' @param q 0 or 1. Correspond to a piecewise constant or piecewise linear trend
#' @param first The value ranges from 0 to 1. Represent the minimum percentile point where a change point may occur. If 'first' = 0.01, it means that change points cannot appear in the first 1\% of the data points. If 'first' = 0, there is no constraint on the position of the change point.
#' @param last The value ranges from 0 to 1. Represent the maximum percentile point where a change point may occur. If 'last' = 0.99, it means that change points cannot appear in the last 1\% of the data points. If 'last' = 1, there is no constraint on the position of the change point.
#' @param penalty 'sic' or 'bic' penalty
#' @return
#' An S3 object of type "L0TFinvopt". A list containing the fitted trend results:
#' \item{sic}{Information criterion value with a penalty term of \eqn{2\log(\log(n)) \times \log(n)}}
#' \item{bic}{Information criterion value with a penalty term of \eqn{2 \times \log(n)}}
#' \item{mse}{The mean square error between the fitted trend and the input data}
#' \item{y}{The input data points}
#' \item{betaopt}{The fitted \eqn{\hat{\boldsymbol{\beta}}} coefficients with optimal change points }
#' \item{yopt}{The fitted trend with optimal change points}
#' \item{Aopt}{The set of position indicators of the fitted change points with optimal change points}
#' \item{kopt}{Optimal number of change points}
#' \item{beta.all}{A data frame with dimensions \eqn{n \times k_{\text{max}}}, where each column represents the fitted \eqn{\hat{\boldsymbol{\beta}}} coefficients corresponding to a given number of change points}
#' \item{y.all}{A data frame with dimensions \eqn{n \times k_{\text{max}}}, where each column represents the fitted estimated trend corresponding to a given number of change points}
#' \item{A.all}{A list of length \eqn{k_{\text{max}}}, where each element corresponds to the set of position indicators of change points under a given number }
#'
#' @details
#' Let the fitted trend be denoted as \eqn{\hat{\boldsymbol{y}}}, then \deqn{\text{sic} = n \times \log(\frac{1}{n}\|\boldsymbol{y}-\hat{\boldsymbol{y}}\|_2^2)+2\log(\log(n)) \times \log(n) \times \text{df}(\hat{\boldsymbol{y}})} and \deqn{\text{bic} = n \times \log(\frac{1}{n}\|\boldsymbol{y}-\hat{\boldsymbol{y}}\|_2^2)+2\times \log(n) \times \text{df}(\hat{\boldsymbol{y}}).}
#' The term \eqn{\text{df}(\hat{\boldsymbol{y}})} represents the degrees of freedom for the estimated trend, where \eqn{\text{df}(\hat{\boldsymbol{y}})=k+q+1}. Here, \eqn{k} refers to the number of change points in the estimated trend.
#'
#' @examples
#' tau = c(0.1, 0.3, 0.4, 0.7, 0.85)
#' h = c(-1, 5, 3, 0, -1, 2)
#' BlocksData <- SimuBlocksInv(n = 500, sigma = 0.2, seed = 50, tau = tau ,h = h)
#' res <- L0TFinv.opt(y=BlocksData$y, kmax=20, q=0, first=0.01, last=1, penalty="bic")
#' print(res$Aopt)
#' print(BlocksData$setA)
#' plot(BlocksData$x, BlocksData$y, xlab="", ylab="")
#' lines(BlocksData$x, BlocksData$y0, col = "red")
#' lines(BlocksData$x, res$yopt, col = "lightgreen")
#'
#' tau1 = c(0.4, 0.6, 0.7)
#' h1 = c(-3, 5, -4, 6)
#' a0 = -10
#' WaveData <- SimuWaveInv(n = 500, sigma = 0.1, seed = 50, tau = tau1, h = h1, a0 = a0)
#' res1 <- L0TFinv.opt(y=WaveData$y, kmax=10, q=1, first=0, last=0.99, penalty="sic")
#' print(res1$Aopt)
#' print(WaveData$setA)
#' plot(WaveData$x, WaveData$y, xlab="", ylab="")
#' lines(WaveData$x, WaveData$y0, col = "red")
#' lines(WaveData$x, res1$yopt, col = "lightgreen")
#'
#' @export
L0TFinv.opt <- function(y=y, kmax=kmax, q=q, first=0, last=1, penalty="bic"){
  if ( !(q %in% c(0,1)) ){
    stop("The specified order is not supported")
  }
  if ( !(penalty %in% c("bic","sic")) ){
    stop("The specified penalty is not supported")
  }
  if( first<0 | last>1 ){
    stop("
The maximum possible range for the change points should be within [0,1]")
  }
  if( length(y) <= kmax+q+1 ){
    stop("The number of data points should be greater than the number of change points")
  }
  res <- InverseL0TF(y=y, kmax=kmax, q=q, first=first, last=last)
  if(penalty == "bic"){
    kopt = which.min(res$bic)
  }
  if(penalty == "sic"){
    kopt = which.min(res$sic)
  }
  betaopt = res$beta.all[,kopt]
  Aopt = sort(res$A.all[[kopt]])
  yopt = res$y.all[,kopt]
  G = list(sic=res$sic,bic=res$bic,mse=res$mse,
           y=y,
           betaopt=betaopt,yopt=yopt,Aopt=Aopt,kopt=kopt,
           beta.all=res$beta.all,y.all=res$y.all,A.all=res$A.all)
  class(G) <- "L0TFinvopt"
  return(G)
}

function(A=A,I=I,k=k,y=y,q=q,H=H,U=U,first=first,last=last){
  n = length(y)
  All = 1:n
  S = 1:(q+1)
  AS = (q+2):n
  low = ceiling(first*n)
  high = floor(last*n)
  AS0 = intersect(low:high,AS)
  eps1 = rep(0,n)
  eps2 = rep(0,n)
  Al = sort(union(S,A),decreasing = FALSE)
  beta = rep(0,n)
  beta[Al] = solMat(n=n,q=q,A=Al)%*%U[Al]
  yhat = ybeta(beta=beta,q=q)
  D = Dy(y=yhat,q=q,U=U)/n
  eps1 = ((beta)^2)*H
  eps2 = D^2/H
  L = sum((y-yhat)^2)
  for(j in 1:(k%/%4)){
    A0 = as.vector(A[order(eps1[A],decreasing = F)[1:j]])
    I00 = intersect(I,AS0)
    I0 = as.vector(I00[order(eps2[I00],decreasing = T)[1:j]])
    Anew = c(setdiff(A,A0),I0)
    Inew = c(setdiff(I,I0),A0)
    Alnew = sort(union(Anew,S),decreasing = FALSE)
    betanew = rep(0,n)
    betanew[Alnew] = solMat(n=n,q=q,A=Alnew)%*%U[Alnew]
    yhatnew = ybeta(beta=betanew,q=q)
    Lnew = sum((y-yhatnew)^2)
    if(L-Lnew>0){
      A = Anew
      I = Inew
      beta = betanew
      L = Lnew
      yhat = ybeta(beta=beta,q=q)
      D = Dy(y=yhat,q=q,U=U)/n
      eps1 = ((beta)^2)*H
      eps2 = D^2/H
    }
  }
  return(list(A=A,I=I,beta=beta))
}

#' @title Print four metrics about change point detection results
#' @description Prints four metrics to compare the quality of change point detection results.
#' @param y0 The underlying trend
#' @param tau The locations of change points in the underlying trend
#' @param yhat The fitted trend
#' @param cpts The positions of the fitted change points
#' @return
#' \item{MSE}{The mean square error between the fitted trend and the underlying trend}
#' \item{MAD}{The median absolute deviation between the fitted trend and the underlying trend}
#' \item{dH}{Hausdorff Distance (dH) measures the accuracy of the estimated change points}
#' \item{nknot}{The number of detected change points}
#' @details
#' \eqn{\hat{\boldsymbol{\tau}}} represents the estimated change point positions, while \eqn{\boldsymbol{\tau}} denotes the locations of change points in the underlying trend.
#' \deqn{d_H=\frac{1}{n} \max \{\max_k \min_j |\tau_j-\hat{\tau}_k|,\max_j \min_k |\tau_j-\hat{\tau}_k|\}.}
#' Note that the number of \eqn{\hat{\boldsymbol{\tau}}} and \eqn{\boldsymbol{\tau}} does not need to be the same.
#' @examples
#'
#' tau = c(0.1, 0.3, 0.4, 0.7, 0.85)
#' h = c(-1, 5, 3, 0, -1, 2)
#' n = 500
#' BlocksData <- SimuBlocksInv(n = n, sigma = 0.2, seed = 50, tau = tau ,h = h)
#' res <- L0TFinv.opt(y=BlocksData$y, kmax=10, q=0, first=0.01, last=1, penalty="bic")
#' metrics <- TFmetrics(BlocksData$y0,BlocksData$tau,res$yopt,res$Aopt/n)
#' print(metrics)
#'
#' tau1 = c(0.1, 0.3, 0.4, 0.7, 0.85)
#' h1 = c(-1, 5, 3, 0, -1, 2)
#' a0 = -10
#' n1 = 2000
#' WaveData <- SimuWaveInv(n = n1, sigma = 0.1, seed = 50, tau = tau1, h = h1, a0 = a0)
#' res1 <- L0TFinv.fix(y=WaveData$y, k=20, q=1, first=0, last=0.99)
#' metrics1 <- TFmetrics(WaveData$y0,WaveData$tau,res1$y.all[,5],res1$A.all[[5]]/n1)
#' print(metrics1)
#'
#' @export
TFmetrics <- function(y0, tau=NULL, yhat, cpts = NULL){
  mse = mean((yhat-y0)^2)
  mad = mean(abs(yhat-y0))
  if(is.null(cpts)){
    tab = data.frame(MSE=mse, MAD=mad)
  }else{
    n = length(yhat)
    n.cpts = length(cpts)
    segments.endpoints.true = sort(unique(tau))
    segments.endpoints.est = sort(unique(cpts))
    distm = abs(matrix(rep(segments.endpoints.est, length(segments.endpoints.true)),
        nrow=length(segments.endpoints.est))-matrix(rep(segments.endpoints.true,
        length(segments.endpoints.est)), nrow=length(segments.endpoints.est), byrow=TRUE))
    screening.dist = max(apply(distm, 2, min))/n
    precision.dist = max(apply(distm, 1, min))/n
    haus.dist = max(screening.dist, precision.dist)
    tab = data.frame(MSE=mse, MAD=mad, dH=haus.dist, nknot=n.cpts)
  }
  return(tab)
}

#' @title Simulate Blocks Data
#' @description This function generates data points of piecewise constant trends.
#' @param n Number of data points
#' @param sigma Standard deviation of the noise added to the signal
#' @param seed An optional seed for random number generation to make results reproducible
#' @param tau The locations of change points in the underlying trend
#' @param h The constant values of the \eqn{length(tau)+1} segments of the underlying trend
#' @return
#' A list containing the piecewise constant simulated data and the underlying trend:
#' \item{x}{The set \{ \eqn{\frac{1}{n}}, \eqn{\frac{2}{n}}, \eqn{\frac{3}{n}},\dots, \eqn{1}\}}
#' \item{y}{The piecewise constant simulated data of length \eqn{n}}
#' \item{y0}{The underlying trend of length \eqn{n}}
#' \item{setA}{The set of position indicators of change points in the simulated data}
#' \item{tau}{The locations of change points in the underlying trend}
#'
#' @details
#' \itemize{
#' \item{}{To simplify the analysis, normalize the change point positions to a range between 0 and 1. Require that all elements of the input \eqn{tau} are within this range. Consequently, the change point positions in simulated data forms a subset of the set \{ \eqn{\frac{1}{n}}, \eqn{\frac{2}{n}}, \eqn{\frac{3}{n}},\dots, 1\}.}
#' \item{}{In fact, \eqn{length(tau)} change points can divide the interval into \eqn{length(tau)+1} segments of constant function values. Therefore, ensure that the length of vector \eqn{h} is \eqn{length(tau)+1}.}
#' }
#' @examples
#' tau = c(0.1, 0.3, 0.4, 0.7, 0.85)
#' h = c(-1, 5, 3, 0, -1, 2)
#' BlocksData <- SimuBlocksInv(n = 350, sigma = 0.1, seed = 50, tau = tau ,h = h)
#' plot(BlocksData$x, BlocksData$y, xlab="", ylab="")
#' lines(BlocksData$x, BlocksData$y0, col = "red")
#' print(BlocksData$setA)
#' print(BlocksData$tau)
#' @importFrom stats rnorm
#' @export
SimuBlocksInv <- function (n, sigma, seed = NA, tau, h ){
  if (!is.na(seed)) set.seed(seed)
  if( min(tau)<=0 | max(tau)>=1 ){
    stop("
The maximum possible range for the change points should be within [0,1]")
  }
  if (n < length(tau)+1){
    stop("The number of data points should be greater than the number of change points")
  }
  if (length(h)!=length(tau)+1){
    stop("The length of the vector does not meet the condition")
  }
  x = seq(1/n, 1,length.out = n)
  A = sapply(tau, function(z) which(x>=z)[1])
  beta = rep(0,n)
  beta[1] = h[1]
  beta[A+1] = diff(h)
  y0 = cumsum(beta)
  y = y0 + sigma*rnorm(n)
  return(list(x = x, y = y, y0 = y0, setA = A,  tau = tau))
}

#' @title Simulate Wave Data
#' @description This function generates data points of piecewise linear trends.
#' @param n Number of data points
#' @param sigma Standard deviation of the noise added to the signal
#' @param seed An optional seed for random number generation to make results reproducible
#' @param tau The locations of change points in the underlying trend
#' @param h The slope of the \eqn{length(tau)+1} segments of the underlying trend
#' @param a0 The initial point value
#' @return
#' A list containing the piecewise linear simulated data and the underlying trend:
#' \item{x}{The set \{ \eqn{\frac{1}{n}}, \eqn{\frac{2}{n}}, \eqn{\frac{3}{n}},\dots, \eqn{1} \}}
#' \item{y}{The piecewise linear simulated data of length \eqn{n}}
#' \item{y0}{The underlying trend of length \eqn{n}}
#' \item{setA}{The set of position indicators of change points in the simulated data}
#' \item{tau}{The locations of change points in the underlying trend}
#'
#' @examples
#' tau = c(0.1, 0.3, 0.4, 0.7, 0.85)
#' h = c(-1, 5, 3, 0, -1, 2)
#' a0 = -10
#' WaveData <- SimuWaveInv(n = 650, sigma = 0.1, seed = 50, tau = tau, h = h, a0 = a0)
#' plot(WaveData$x, WaveData$y, xlab="", ylab="")
#' lines(WaveData$x, WaveData$y0, col = "red")
#' print(WaveData$setA)
#' print(WaveData$tau)
#' @seealso \code{\link{SimuBlocksInv}}
#' @importFrom stats rnorm
#' @export
SimuWaveInv <- function (n, sigma, seed = NA, tau, h, a0 = 0 ){
  if (!is.na(seed)) set.seed(seed)
  if( min(tau)<=0 | max(tau)>=1 ){
    stop("
The maximum possible range for the change points should be within [0,1]")
  }
  if (n < length(tau)+2){
    stop("The number of data points should be greater than the number of change points")
  }
  if (length(h)!=length(tau)+1){
    stop("The length of the vector does not meet the condition")
  }
  x = seq(1/n, 1,length.out = n)
  A = sapply(tau, function(z) which(x>=z)[1])
  beta = rep(0,n)
  beta[1] = a0
  beta[2] = h[1]/n-beta[1]
  beta[A+1] = (diff(h))/n
  y0 = cumsum(cumsum(beta))
  y = y0 + sigma*rnorm(n)
  return(list(x = x, y = y, y0 = y0, setA = A,  tau = tau))
}

#' @title Plot L0TFinvfix or L0TFinvopt object
#'
#' @description Plots a summary of L0TFinvfix or L0TFinvopt
#' @param x The output of L0TFinvfix or L0TFinvopt
#' @param type The values are taken as c("\eqn{mse}", "\eqn{sic}", "\eqn{bic}", "\eqn{yhat}"). If \eqn{type} is "\eqn{mse}", plot the mse as it changes with change points.
#' The same applies to "\eqn{sic}" and "\eqn{bic}". If \eqn{type} is "\eqn{yhat}", plot the trend of the estimated values against the input data.
#' @param k Only used for \eqn{type} = "\eqn{yhat}". The given number of change points.
#' By default, the L0TFinvfix object outputs the estimated trend that corresponds to the fixed number of change points within the model. Conversely, the L0TFinvopt object provides the estimated trend based on the optimal change points.
#' @param ... ignore
#' @method plot L0TFinvfix
#' @import ggplot2
#' @seealso \code{\link{print.L0TFinvfix}}
#' @export
plot.L0TFinvfix <- function(x, type = NULL, k = NULL, ...){
  if ( !(type %in% c("mse","sic","bic","yhat")) ){
    stop("The specified type is not supported")
  }
  if(type == "mse"){
    num = 1:(length(x$mse))
    val = x$mse
    plotObject <- ggplot(data.frame(num, val), aes(x = num, y = val)) +
      geom_line() +
      geom_point() +
      labs(x = "The number of change points", y = "MSE") +
      theme_minimal()
  }
  if(type == "sic"){
    num = 1:(length(x$sic))
    val = x$sic
    plotObject <- ggplot(data.frame(num, val), aes(x = num, y = val)) +
      geom_line() +
      geom_point() +
      labs(x = "The number of change points", y = "SIC") +
      theme_minimal()
  }
  if(type == "bic"){
    num = 1:(length(x$bic))
    val = x$bic
    plotObject <- ggplot(data.frame(num, val), aes(x = num, y = val)) +
      geom_line() +
      geom_point() +
      labs(x = "The number of change points", y = "BIC") +
      theme_minimal()
  }
  if(type == "yhat"){
    n = length(x$y)
    num = 1:n
    if(is.null(k)){
      k = length(x$mse)
      val = x$y.all[,k]
    }else{
      if(k > ncol(x$beta.all)){
        stop("The number of given change points exceeds the maximum range")
      }
      val = x$y.all[,k]
    }
    input = x$y
    df <- data.frame(num = num, input = input, val = val)
    plotObject <- ggplot(data = df, aes(x = num)) +
      geom_point(aes(y = input), color = "black") +
      geom_line(aes(y = val), color = "lightgreen") +
      labs(x = "Position indicators", y = "Value") +
      theme_minimal()
  }
  return(plotObject)
}

#' @rdname plot.L0TFinvfix
#' @method plot L0TFinvopt
#' @export
plot.L0TFinvopt <- function(x, type = "mse", k = NULL, ...){
  if ( !(type %in% c("mse","sic","bic","yhat")) ){
    stop("The specified type is not supported")
  }
  if(type == "mse"){
    num = 1:(length(x$mse))
    val = x$mse
    plotObject <- ggplot(data.frame(num, val), aes(x = num, y = val)) +
      geom_line() +
      geom_point() +
      labs(x = "The number of change points", y = "MSE") +
      theme_minimal()
  }
  if(type == "sic"){
    num = 1:(length(x$sic))
    val = x$sic
    plotObject <- ggplot(data.frame(num, val), aes(x = num, y = val)) +
      geom_line() +
      geom_point() +
      labs(x = "The number of change points", y = "SIC") +
      theme_minimal()
  }
  if(type == "bic"){
    num = 1:(length(x$bic))
    val = x$bic
    plotObject <- ggplot(data.frame(num, val), aes(x = num, y = val)) +
      geom_line() +
      geom_point() +
      labs(x = "The number of change points", y = "BIC") +
      theme_minimal()
  }
  if(type == "yhat"){
    n = length(x$y)
    num = 1:n
    if(is.null(k)){
      k = x$kopt
      val = x$y.all[,k]
    }else{
      if(k > ncol(x$beta.all)){
        stop("The number of given change points exceeds the maximum range")
      }
      val = x$y.all[,k]
    }
    input = x$y
    df <- data.frame(num = num, input = input, val = val)
    plotObject <- ggplot(data = df, aes(x = num)) +
      geom_point(aes(y = input), color = "black") +
      geom_line(aes(y = val), color = "lightgreen") +
      labs(x = "Position indicators", y = "Value") +
      theme_minimal()
  }
  return(plotObject)
}

#' @title Print L0TFinvfix or L0TFinvopt object
#' @description Prints a summary of L0TFinvfix or L0TFinvopt
#' @param x The output of L0TFinvfix or L0TFinvopt
#' @param ... ignore
#' @method print L0TFinvfix
#' @examples
#' library(ggplot2)
#'
#' tau = c(0.1, 0.3, 0.4, 0.7, 0.85)
#' h = c(-1, 5, 3, 0, -1, 2)
#' BlocksData <- SimuBlocksInv(n = 500, sigma = 0.2, seed = 50, tau = tau ,h = h)
#' res <- L0TFinv.opt(y=BlocksData$y, kmax=10, q=0, first=0.01, last=1, penalty="bic")
#' print(res)
#' coef(res,k=res$kopt)
#' plot(res,type="yhat")
#' plot(res,type="bic")
#'
#' tau1 = c(0.1, 0.3, 0.4, 0.7, 0.85)
#' h1 = c(-1, 5, 3, 0, -1, 2)
#' a0 = -10
#' WaveData <- SimuWaveInv(n = 2000, sigma = 0.1, seed = 50, tau = tau1, h = h1, a0 = a0)
#' res1 <- L0TFinv.fix(y=WaveData$y, k=20, q=1, first=0, last=0.99)
#' print(res1)
#' coef(res1,k=5)
#' plot(res1,type="yhat",k=5)
#' plot(res1,type="mse")
#'
#' @export
print.L0TFinvfix <- function(x, ...){
  return(list(sic=x$sic,bic=x$bic,mse=x$mse,
              A.all=x$A.all,beta.all=x$beta.all,y.all=x$y.all))
}

#' @rdname print.L0TFinvfix
#' @method print L0TFinvopt
#' @export
print.L0TFinvopt <- function(x, ...){
  return(list(sic=x$sic,bic=x$bic,mse=x$mse,kopt=x$kopt,
              A.all=x$A.all,beta.all=x$beta.all,y.all=x$y.all))
}

#' @_PACKAGE
#'
#' @name L0TFinv-package
#' @title A package for L0-regularized sparse approximation
#' @description
#' Trend filtering is a typical method for nonparametric regression.
#' The commonly used trend filtering models is the L1 trend filtering model \eqn{(a)} based on the difference matrix \eqn{\boldsymbol{D}^{(q+1)}}, as illustrated below.
#' \deqn{\min _{\boldsymbol{\alpha} \in \mathbb{R}^n} \frac{1}{2}\|\boldsymbol{y}-\boldsymbol{\alpha}\|_2^2 + \lambda\|\boldsymbol{D}^{(q+1)} \boldsymbol{\alpha}\|_{\ell_1}, \quad q=0,1,2, \ldots. \quad (a) }
#' L0 trend filtering \eqn{(b)} has a advantage over other trend filtering methods, especially in the detection of change points.
#' The expression for L0 trend filtering is as follows:
#' \deqn{\min _{\boldsymbol{\alpha} \in \mathbb{R}^n} \frac{1}{2}\|\boldsymbol{y}-\boldsymbol{\alpha}\|_2^2 + \lambda\|\boldsymbol{D}^{(q+1)} \boldsymbol{\alpha}\|_{\ell_0}. \quad (b)  }
#' We explore transforming the problem \eqn{(b)} into a L0-regularized sparse format \eqn{(c)} by introducing an artificial design matrix \eqn{\boldsymbol{X}^{(q+1)}} that corresponds to the difference matrix, thereby reformulating the L0 trend filtering problem into the following format.
#' \deqn{\min _{\boldsymbol{\beta} \in \mathbb{R}^n} \frac{1}{2}\|\boldsymbol{y}-\boldsymbol{X}^{(q+1)}\boldsymbol{\beta}\|_2^2 + \lambda \sum_{i=q+2}^n |\boldsymbol{\beta}_i|_{\ell_0}. \quad (c) }
#' In our practical approach, we consider the maximum number of change points \eqn{k_{\text{max}}} as a constraint, transforming the aforementioned L0 penalty problem \eqn{(c)} into the following L0 constraint problem.
#' \deqn{\text{ minimize }\frac{1}{2}\|\boldsymbol{y}-\boldsymbol{X}^{(q+1)}\boldsymbol{\beta}\|_2^2,\quad \text{ subject to } \sum_{i=q+2}^n |\boldsymbol{\beta}_i|_{\ell_0} \leq k_{\text{max}}. \quad (d)}
#' For such L0 constraint problems \eqn{(d)}, we employ a splicing-based approach to design algorithms for processing.
#' This package has the following seven main methods:
#' \itemize{
#' \item{\strong{matrix with special structure }}{\eqn{\quad}Generate \eqn{\boldsymbol{X}^{(q+1)}} or \eqn{\boldsymbol{D}^{(q+1)}} matrix.}
#' \item{\strong{inverse of the crossprod matrix }}{\eqn{\quad}Simplify the calculation of the inverse matrix of \eqn{(\boldsymbol{X}^{(q+1)}_A)^T \boldsymbol{X}^{(q+1)}_A} for the cases where \eqn{q=0} or \eqn{q=1}, which is frequently used in splicing algorithms.}
#' \item{\strong{inverse L0 trend filtering with fixed change points }}{\eqn{\quad}Fit a piecewise constant or piecewise linear estimated trend with a given number of change points.}
#' \item{\strong{inverse L0 trend filtering with optimal change points }}{\eqn{\quad}Fit a piecewise constant or piecewise linear estimated trend with a maximum number of change points, and select the optimal estimated trend using appropriate information criteria.}
#' \item{\strong{simulated data }}{\eqn{\quad}Generate piecewise constant or piecewise linear data.}
#' \item{\strong{print/coef}}{\eqn{\quad}Print a summary of the trend estimation results.}
#' \item{\strong{plot }}{\eqn{\quad}Plot a summary of the trend estimation results.}
#' }
#' @details
#' \itemize{
#' \item{}{In previous studies, algorithms solving trend filtering problems \eqn{(a)} necessitate the computation of \eqn{((\boldsymbol{D}^{(q+1)})^T \boldsymbol{D}^{(q+1)})^{-1}}.
#' When \eqn{n} is large, just fitting the matrix into memory becomes an issue.}
#' \item{}{In L0 trend filtering \eqn{(b)}, the positions of non-zero elements in the L0 norm correspond with the locations of change points.
#' We consider two subsets: the active set \eqn{A} for non-zero elements and the inactive set \eqn{I} for zero elements.
#' Despite this, computing \eqn{((\boldsymbol{D}^{(q+1)}_I)^T \boldsymbol{D}^{(q+1)}_I)^{-1}} remains a task involving a substantial matrix.}
#' \item{}{Due to the connection between L0 constraint problems and L0 penalty problems, and considering that the sparsity of \eqn{\boldsymbol{\beta}} is is more meaningful in practical applications than the selection of the hyperparameter \eqn{\lambda}.
#' We focus on the constraint that reflects our aim to achieve an estimated trend with a given number of change points.
#' So we transform the L0 penalty problem \eqn{(c)} into the L0 constraint problem \eqn{(d)}.}
#' }
#' @references
#' Kim SJ, Koh K, Boyd SP and Gorinevsky DM. L1 Trend Filtering. Society for Industrial and Applied Mathematics (2009).
#'
#' Wen C, Wang X and Zhang A. L0 Trend Filtering. INFORMS Journal on Computing (2023).
NULL

#' @title Generate the inverse of the crossprod matrix
#' @description Generate the inverse matrix of \eqn{(\boldsymbol{X}^{(q+1)}_A)^T \boldsymbol{X}^{(q+1)}_A} for the cases where \eqn{q=0} or \eqn{q=1}, commonly employed in splicing algorithms. Note that an explicit solution exists for the inverse when \eqn{q=0}, but not when \eqn{q=1}.
#' @param n The number of data points
#' @param q The order of the difference, 0 or 1
#' @param A The set of indicators, a subset of \eqn{\{1,2,3,\dots,n\}}
#' @return The inverse matrix of \eqn{(\boldsymbol{X}^{(q+1)}_A)^T \boldsymbol{X}^{(q+1)}_A} for the cases where \eqn{q=} 0 or 1.
#' @examples
#' Mat1 <- XMat(n = 10, q = 0)
#' A1 = c(1,2,5,8)
#' mat1 = as.matrix(Mat1[,A1])
#' S1 <- solMat(n = 10, q = 0, A = A1)
#' print(S1)
#' print(round(S1%*%t(mat1)%*%mat1,10))
#'
#' Mat2 <- XMat(n = 15, q = 1)
#' A2 = c(1,3,8,10,15)
#' mat2 = as.matrix(Mat2[,A2])
#' S2 <- solMat(n = 15, q = 1, A = A2)
#' print(S2)
#' print(round(S2%*%t(mat2)%*%mat2,10))
#' @importFrom Matrix solve
#' @export
solMat <- function(n, q, A){
  if ( !(q %in% c(0,1)) ){
    stop("The specified order is not supported")
  }
  if( min(A)<1 | max(A)>n ){
    stop("
The maximum possible range for the set indicators should be within {1,2,...,n}")
  }
  k = length(A)
  m = as.numeric(k)
  m = n + 1 - A
  phi = matrix(0, k, k)
  if(q == 0){
    if(k == 1){
      phi[1,1] = 1/m[1]
      return(phi)
    }
    if(k == 2){
      phi[1,1] = m[2]
      phi[1,2] = -m[2]
      phi[2,1] = -m[2]
      phi[2,2] = m[1]
      phi = phi/(m[2]*(m[1]-m[2]))
      return(phi)
    }
    for(i in 1:k){
      if(i == 1){
        phi[1,1] = 1/(m[1]-m[2])
        phi[1,2] = -1/(m[1]-m[2])
        next
      }
      if(i == k){
        phi[k,(k-1)] = -1/(m[(k-1)]-m[k])
        phi[k,k] = 1/(m[(k-1)]-m[k])+1/m[k]
        break
      }
      phi[i,(i-1)] = -1/(m[(i-1)]-m[i])
      phi[i,i] = 1/(m[(i-1)]-m[i])+1/(m[i]-m[(i+1)])
      phi[i,(i+1)] = -1/(m[i]-m[(i+1)])
    }
    return(phi)
  }
  if (q == 1) {
    for (j in 1:k) {
      for (i in j:k) {
        if (j == i) {
          phi[i, j] <- m[j] * (m[j] + 1) * (2 * m[j] + 1) / 6
        } else {
          phi[i, j] <- m[i] * (m[i] + 1) * (3 * m[j] - m[i] + 1) / 6
        }
      }
    }
    phi <- phi + t(phi) - diag(diag(phi))
    phi <- solve(phi)
    return(phi)
  }
}

#' @title Generate a difference matrix
#' @description This function generates a matrix for computing differences of a certain order, useful in numerical methods and for creating specific matrix patterns.
#' @param n The number of data points
#' @param q The order of the difference
#' @return  A matrix with dimensions \eqn{n-q-1} by \eqn{n}, whose elements correspond to the combinatorial values of \eqn{q}.
#' @examples
#' Mat1 <- DiffMat(n = 10, q = 0)
#' print(Mat1)
#'
#' Mat2 <- DiffMat(n = 15, q = 1)
#' print(Mat2)
#'
#' Mat3 <- DiffMat(n = 15, q = 2)
#' print(Mat3)
#'
#' @seealso \code{\link{XMat}}
#' @export
DiffMat <- function(n, q) {
  if( n <= q+1 ){
    stop("The number n should be greater than the order of the difference")
  }
  X <- matrix(0, (n-q-1), n)
  for (i in 1:(n-q-1)) {
    for (j in 1:n) {
      if (j >= i && j <= i + q + 1) {
        X[i, j] <- (-1)^(j - i + q - 1) * choose(q + 1, j - i)
      }
    }
  }
  return(X)
}

#' @title Generate an artificial design matrix
#' @description This matrix corresponds to the difference matrix, transforming the L0 trend filtering model into an inverse statistical problem.
#' @param n The number of data points
#' @param q The order of the difference
#' @return  A matrix with dimensions \eqn{n} by \eqn{n}, whose elements correspond to the difference matrix.
#' @examples
#' mat1 <- XMat(n = 10, q = 0)
#' print(mat1)
#'
#' mat2 <- XMat(n = 15, q = 1)
#' print(mat2)
#'
#' mat3 <- XMat(n = 15, q = 2)
#' print(mat3)
#'
#' Mat1 <- DiffMat(n = 10, q = 0)
#' Mat2 <- DiffMat(n = 15, q = 1)
#' Mat3 <- DiffMat(n = 15, q = 2)
#' print(Mat1%*%mat1)
#' print(Mat2%*%mat2)
#' print(Mat3%*%mat3)
#' @details Noticing the correspondence between \eqn{\boldsymbol{D}^{(q+1)}} and \eqn{\boldsymbol{X}^{(q+1)}}, the result of their matrix multiplication is a combination of a zero matrix and an identity matrix. Expressed as \eqn{\boldsymbol{D}^{(q+1)} \boldsymbol{X}^{(q+1)}=(\boldsymbol{O}_{(n-q-1)\times(q+1)},\quad \boldsymbol{I}_{(n-q-1)\times(n-q-1)})}. The result is advantageous for the invertible processing of the original L0 trend filtering problem.
#' @export
XMat <- function(n, q){
  X <- matrix(0,n,n)
  if(q == 0){
    for(i in 1:n){
      for(j in 1:i){
        X[i,j] <- 1
      }
    }
    return(X)
  }else{
    return(apply(XMat(n, q-1),2,cumsum))
  }
}

#' @title Extract estimated trends
#' @description Extract the coefficients of the estimated trends under the constraint of a given number of change points.
#' @param object The output of L0TFinvfix or L0TFinvopt
#' @param k The given number of change points
#' @method coef L0TFinvfix
#' @seealso \code{\link{print.L0TFinvfix}}
#' @export
coef.L0TFinvfix <- function(object, k=NULL) {
  if(is.null(k)){
    return(list(A.all=object$A.all,beta.all=object$beta.all,y.all=object$y.all))
  }else{
    if(k > ncol(object$beta.all)){
      stop("The number of given change points exceeds the maximum range")
    }else{
      return(list(A=object$A.all[[k]],beta=object$beta.all[,k],yhat=object$y.all[,k]))
    }
  }
}

#' @rdname coef.L0TFinvfix
#' @method coef L0TFinvopt
#' @export
coef.L0TFinvopt <- function(object, k=NULL) {
  if(is.null(k)){
    return(list(A.all=object$A.all,beta.all=object$beta.all,y.all=object$y.all))
  }else{
    if(k > ncol(object$beta.all)){
      stop("The number of given change points exceeds the maximum range")
    }else{
      return(list(A=object$A.all[[k]],beta=object$beta.all[,k],yhat=object$y.all[,k]))
    }
  }
}

This code segment functions to: Iteratively refine model components by exchanging elements between active and candidate sets based on error metrics, optimizing coefficients to minimize residual error while respecting positional constraints.

```r
[TASK_BEGIN]
Splicing <- function(A=A, I=I, k=k, y=y, q=q, H=H, U=U, first=first, last=last){
  n = length(y)
  S = 1:(q+1)
  A_combined = sort(union(S,A))
  beta = rep(0,n)
  beta[A_combined] = solMat(n=n,q=q,A=A_combined)%*%U[A_combined]
  D = Dy(y=ybeta(beta=beta,q=q),q=q,U=U)/n
  eps = D^2/H
  low = ceiling(first*n)
  high = floor(last*n)
  AS0 = intersect(low:high,(q+2):n)
  I0 = intersect(I,AS0)
  if(length(I0) > 0){
    i1 = I0[which.max(eps[I0])]
    A = union(A,i1)
    I = setdiff(I,i1)
  }
  A_candidate = setdiff(A,S)
  if(length(A_candidate) > k){
    A_eps = eps[A_candidate]
    remove = A_candidate[order(A_eps)[1:(length(A_candidate)-k)]]
    A = setdiff(A,remove)
    I = union(I,remove)
  }
  return(list(A=A,I=I,beta=beta))
}
[TASK_END]
```


#' @title Print four metrics about change point detection results
#' @description Prints four metrics to compare the quality of change point detection results.
#' @param y0 The underlying trend
#' @param tau The locations of change points in the underlying trend
#' @param yhat The fitted trend
#' @param cpts The positions of the fitted change points
#' @return
#' \item{MSE}{The mean square error between the fitted trend and the underlying trend}
#' \item{MAD}{The median absolute deviation between the fitted trend and the underlying trend}
#' \item{dH}{Hausdorff Distance (dH) measures the accuracy of the estimated change points}
#' \item{nknot}{The number of detected change points}
#' @details
#' \eqn{\hat{\boldsymbol{\tau}}} represents the estimated change point positions, while \eqn{\boldsymbol{\tau}} denotes the locations of change points in the underlying trend.
#' \deqn{d_H=\frac{1}{n} \max \{\max_k \min_j |\tau_j-\hat{\tau}_k|,\max_j \min_k |\tau_j-\hat{\tau}_k|\}.}
#' Note that the number of \eqn{\hat{\boldsymbol{\tau}}} and \eqn{\boldsymbol{\tau}} does not need to be the same.
#' @examples
#'
#' tau = c(0.1, 0.3, 0.4, 0.7, 0.85)
#' h = c(-1, 5, 3, 0, -1, 2)
#' n = 500
#' BlocksData <- SimuBlocksInv(n = n, sigma = 0.2, seed = 50, tau = tau ,h = h)
#' res <- L0TFinv.opt(y=BlocksData$y, kmax=10, q=0, first=0.01, last=1, penalty="bic")
#' metrics <- TFmetrics(BlocksData$y0,BlocksData$tau,res$yopt,res$Aopt/n)
#' print(metrics)
#'
#' tau1 = c(0.1, 0.3, 0.4, 0.7, 0.85)
#' h1 = c(-1, 5, 3, 0, -1, 2)
#' a0 = -10
#' n1 = 2000
#' WaveData <- SimuWaveInv(n = n1, sigma = 0.1, seed = 50, tau = tau1, h = h1, a0 = a0)
#' res1 <- L0TFinv.fix(y=WaveData$y, k=20, q=1, first=0, last=0.99)
#' metrics1 <- TFmetrics(WaveData$y0,WaveData$tau,res1$y.all[,5],res1$A.all[[5]]/n1)
#' print(metrics1)
#'
#' @export
TFmetrics <-

function(y0, tau=NULL, yhat, cpts = NULL){
  mse = mean((yhat-y0)^2)
  mad = mean(abs(yhat-y0))
  if(is.null(cpts)){
    tab = data.frame(MSE=mse, MAD=mad)
  }else{
    n = length(yhat)
    n.cpts = length(cpts)
    segments.endpoints.true = sort(unique(tau))
    segments.endpoints.est = sort(unique(cpts))
    distm = abs(matrix(rep(segments.endpoints.est, length(segments.endpoints.true)),
        nrow=length(segments.endpoints.est))-matrix(rep(segments.endpoints.true,
        length(segments.endpoints.est)), nrow=length(segments.endpoints.est), byrow=TRUE))
    screening.dist = max(apply(distm, 2, min))/n
    precision.dist = max(apply(distm, 1, min))/n
    haus.dist = max(screening.dist, precision.dist)
    tab = data.frame(MSE=mse, MAD=mad, dH=haus.dist, nknot=n.cpts)
  }
  return(tab)
}

ybeta <- function(beta=beta,q=q){
  if(q == 0){
    return(cumsum(beta))
  }
  if(q == 1){
    return(cumsum(cumsum(beta)))
  }
}

Dy <- function(y=y,q=q,U=U){
  if(q == 0){
    D = U-rev(cumsum(rev(y)))
    return(D)
  }
  if(q == 1){
    D = U-rev(cumsum(cumsum(rev(y))))
    return(D)
  }
}

Splicing <- function(A=A,I=I,k=k,y=y,q=q,H=H,U=U,first=first,last=last){
  n = length(y)
  All = 1:n
  S = 1:(q+1)
  AS = (q+2):n
  low = ceiling(first*n)
  high = floor(last*n)
  AS0 = intersect(low:high,AS)

  eps1 = rep(0,n)
  eps2 = rep(0,n)
  Al = sort(union(S,A),decreasing = FALSE)
  beta = rep(0,n)
  beta[Al] = solMat(n=n,q=q,A=Al)%*%U[Al]
  yhat = ybeta(beta=beta,q=q)
  D = Dy(y=yhat,q=q,U=U)/n
  eps1 = ((beta)^2)*H
  eps2 = D^2/H
  L = sum((y-yhat)^2)
  for(j in 1:(k%/%4)){
    A0 = as.vector(A[order(eps1[A],decreasing = F)[1:j]])
    I00 = intersect(I,AS0)
    I0 = as.vector(I00[order(eps2[I00],decreasing = T)[1:j]])
    Anew = c(setdiff(A,A0),I0)
    Inew = c(setdiff(I,I0),A0)
    Alnew = sort(union(Anew,S),decreasing = FALSE)
    betanew = rep(0,n)
    betanew[Alnew] = solMat(n=n,q=q,A=Alnew)%*%U[Alnew]
    yhatnew = ybeta(beta=betanew,q=q)
    Lnew = sum((y-yhatnew)^2)
    if(L-Lnew>0){
      A = Anew
      I = Inew
      beta = betanew
      L = Lnew
      yhat = ybeta(beta=beta,q=q)
      D = Dy(y=yhat,q=q,U=U)/n
      eps1 = ((beta)^2)*H
      eps2 = D^2/H
    }
  }
  return(list(A=A,I=I,beta=beta))
}

InvL0TFk <- function(A0=A0,y=y,q=q,k=k,H=H,U=U,first=first,last=last,max.step=50){
  n = length(y)
  All = 1:n
  S = 1:(q+1)
  AS = (q+2):n
  I0 = setdiff(AS,A0)
  for(j in 1:max.step){
    m = Splicing(A=A0,I=I0,k=k,y=y,q=q,H=H,U=U,first=first,last=last)
    A = m$A
    I = m$I
    beta = m$beta
    if(identical(A,A0) & identical(I,I0)){
      break
    }else{
      A0 = A
      I0 = I
    }
  }
  yhat = ybeta(beta=beta,q=q)
  Ahat = sort(A,decreasing = FALSE)
  return(list(betak=beta,yk=yhat,Ak=Ahat))
}

InverseL0TF <- function(y=y,kmax=kmax,q=q,first=0,last=1){
  n = length(y)
  All = 1:n
  S = 1:(q+1)
  AS = (q+2):n
  low = ceiling(first*n)
  high = floor(last*n)
  AS0 = intersect(low:high,AS)
  A0 = NULL
  I0 = setdiff(AS,A0)

  beta.all = NULL
  y.all = NULL
  A.all = list()
  mse = as.numeric(kmax)
  sic = as.numeric(kmax)
  eps = as.numeric(n)
  if(q == 0){
    H = n:1/n
    U = rev(cumsum(rev(y)))
  }
  if(q == 1){
    H = sapply(n:1, function(x) x*(x+1)*(2*x+1)/6)/n
    U = rev(cumsum(cumsum(rev(y))))
  }
  Al = sort(union(S,A0),decreasing = FALSE)
  beta = rep(0,n)
  beta[Al] = solMat(n=n,q=q,A=Al)%*%U[Al]
  D = Dy(y=ybeta(beta=beta,q=q),q=q,U=U)/n
  eps = D^2/H
  I00 = intersect(I0,AS0)
  A0 = union(A0,I00[which.max(eps[I00])])
  for(j in 1:kmax){
    result = InvL0TFk(A0=A0,y=y,q=q,k=j,H=H,U=U,first=first,last=last)
    beta.all = cbind(beta.all,result$betak)
    y.all = cbind(y.all,result$yk)
    A0 = result$Ak
    I0 = setdiff(AS,A0)
    A.all[[j]] = A0 - 1
    D = Dy(y=as.vector(y.all[,j]),q=q,U=U)/n
    eps = D^2/H
    I00 = intersect(I0,AS0)
    A0 = union(A0,I00[which.max(eps[I00])])
  }
  mse = colMeans((y-y.all)^2)
  df = 1:kmax + q + 1
  sic = n*log(mse) + 2*log(log(n))*log(n)*df
  bic = n*log(mse) + 2*log(n)*df
  return(list(beta.all=beta.all,y.all=y.all,A.all=A.all,
              sic=sic,bic=bic,mse=mse))
}

#' @title The inverse L0 trend filtering with fixed change points
#' @description Fit the input data points to a piecewise constant or piecewise linear trend with a given number of change points.
#' @param y The input data points
#' @param k The given number of change points
#' @param q 0 or 1. Correspond to a piecewise constant or piecewise linear trend
#' @param first The value ranges from 0 to 1. Represent the minimum percentile point where a change point may occur. If 'first' = 0.01, it means that change points cannot appear in the first 1\% of the data points. If 'first' = 0, there is no constraint on the position of the change point.
#' @param last The value ranges from 0 to 1. Represent the maximum percentile point where a change point may occur. If 'last' = 0.99, it means that change points cannot appear in the last 1\% of the data points. If 'last' = 1, there is no constraint on the position of the change point.
#' @return
#' An S3 object of type "L0TFinvfix". A list containing the fitted trend results:
#' \item{sic}{Information criterion value with a penalty term of \eqn{2\log(\log(n)) \times \log(n)}}
#' \item{bic}{Information criterion value with a penalty term of \eqn{2 \times \log(n)}}
#' \item{mse}{The mean square error between the fitted trend and the input data}
#' \item{y}{The input data points}
#' \item{betak}{The fitted \eqn{\hat{\boldsymbol{\beta}}} coefficients with the number of change points being \eqn{k} }
#' \item{yk}{The fitted trend with the number of change points being \eqn{k}}
#' \item{Ak}{The set of position indicators of the fitted change points with the number of change points being \eqn{k}}
#' \item{beta.all}{A data frame with dimensions \eqn{n \times k}, where each column represents the fitted \eqn{\hat{\boldsymbol{\beta}}} coefficients corresponding to a given number of change points}
#' \item{y.all}{A data frame with dimensions \eqn{n \times k}, where each column represents the fitted estimated trend corresponding to a given number of change points}
#' \item{A.all}{A list of length \eqn{k}, where each element corresponds to the set of position indicators of change points under a given number }
#'
#' @examples
#' tau = c(0.1, 0.3, 0.4, 0.7, 0.85)
#' h = c(-1, 5, 3, 0, -1, 2)
#' BlocksData <- SimuBlocksInv(n = 350, sigma = 0.2, seed = 50, tau = tau ,h = h)
#' res <- L0TFinv.fix(y=BlocksData$y, k=5, q=0, first=0.01, last=1)
#' print(res$Ak)
#' print(BlocksData$setA)
#' plot(BlocksData$x, BlocksData$y, xlab="", ylab="")
#' lines(BlocksData$x, BlocksData$y0, col = "red")
#' lines(BlocksData$x, res$yk, col = "lightgreen")
#'
#' tau1 = c(0.1, 0.3, 0.4, 0.7, 0.85)
#' h1 = c(-1, 5, 3, 0, -1, 2)
#' a0 = -10
#' WaveData <- SimuWaveInv(n = 2000, sigma = 0.1, seed = 50, tau = tau1, h = h1, a0 = a0)
#' res1 <- L0TFinv.fix(y=WaveData$y, k=5, q=1, first=0, last=0.99)
#' print(res1$Ak)
#' print(WaveData$setA)
#' plot(WaveData$x, WaveData$y, xlab="", ylab="")
#' lines(WaveData$x, WaveData$y0, col = "red")
#' lines(WaveData$x, res1$yk, col = "lightgreen")
#'
#' @seealso \code{\link{L0TFinv.opt}}
#' @export
L0TFinv.fix <- function(y=y, k=k, q=q, first=0, last=1){
  if ( !(q %in% c(0,1)) ){
    stop("The order is not supported")
  }
  if( first<0 | last>1 ){
    stop("
The maximum possible range for the change points should be within [0,1]")
  }
  if( length(y) <= k+q+1 ){
    stop("The number of data points should be greater than the number of change points")
  }
  res <- InverseL0TF(y=y, kmax=k, q=q, first=first, last=last)
  betak = res$beta.all[,k]
  Ak = sort(res$A.all[[k]])
  yk = res$y.all[,k]
  G = list(sic=res$sic,bic=res$bic,mse=res$mse,
           y=y,
           betak=betak,yk=yk,Ak=Ak,
           beta.all=res$beta.all,y.all=res$y.all,A.all=res$A.all)
  class(G) <- "L0TFinvfix"
  return(G)
}

#' @title The inverse L0 trend filtering with optimal change points
#' @description Fit the input data points to a piecewise constant or piecewise linear trend with optimal change points.
#' @param y The input data points
#' @param kmax The maximum number of change points
#' @param q 0 or 1. Correspond to a piecewise constant or piecewise linear trend
#' @param first The value ranges from 0 to 1. Represent the minimum percentile point where a change point may occur. If 'first' = 0.01, it means that change points cannot appear in the first 1\% of the data points. If 'first' = 0, there is no constraint on the position of the change point.
#' @param last The value ranges from 0 to 1. Represent the maximum percentile point where a change point may occur. If 'last' = 0.99, it means that change points cannot appear in the last 1\% of the data points. If 'last' = 1, there is no constraint on the position of the change point.
#' @param penalty 'sic' or 'bic' penalty
#' @return
#' An S3 object of type "L0TFinvopt". A list containing the fitted trend results:
#' \item{sic}{Information criterion value with a penalty term of \eqn{2\log(\log(n)) \times \log(n)}}
#' \item{bic}{Information criterion value with a penalty term of \eqn{2 \times \log(n)}}
#' \item{mse}{The mean square error between the fitted trend and the input data}
#' \item{y}{The input data points}
#' \item{betaopt}{The fitted \eqn{\hat{\boldsymbol{\beta}}} coefficients with optimal change points }
#' \item{yopt}{The fitted trend with optimal change points}
#' \item{Aopt}{The set of position indicators of the fitted change points with optimal change points}
#' \item{kopt}{Optimal number of change points}
#' \item{beta.all}{A data frame with dimensions \eqn{n \times k_{\text{max}}}, where each column represents the fitted \eqn{\hat{\boldsymbol{\beta}}} coefficients corresponding to a given number of change points}
#' \item{y.all}{A data frame with dimensions \eqn{n \times k_{\text{max}}}, where each column represents the fitted estimated trend corresponding to a given number of change points}
#' \item{A.all}{A list of length \eqn{k_{\text{max}}}, where each element corresponds to the set of position indicators of change points under a given number }
#'
#' @details
#' Let the fitted trend be denoted as \eqn{\hat{\boldsymbol{y}}}, then \deqn{\text{sic} = n \times \log(\frac{1}{n}\|\boldsymbol{y}-\hat{\boldsymbol{y}}\|_2^2)+2\log(\log(n)) \times \log(n) \times \text{df}(\hat{\boldsymbol{y}})} and \deqn{\text{bic} = n \times \log(\frac{1}{n}\|\boldsymbol{y}-\hat{\boldsymbol{y}}\|_2^2)+2\times \log(n) \times \text{df}(\hat{\boldsymbol{y}}).}
#' The term \eqn{\text{df}(\hat{\boldsymbol{y}})} represents the degrees of freedom for the estimated trend, where \eqn{\text{df}(\hat{\boldsymbol{y}})=k+q+1}. Here, \eqn{k} refers to the number of change points in the estimated trend.
#'
#' @examples
#' tau = c(0.1, 0.3, 0.4, 0.7, 0.85)
#' h = c(-1, 5, 3, 0, -1, 2)
#' BlocksData <- SimuBlocksInv(n = 500, sigma = 0.2, seed = 50, tau = tau ,h = h)
#' res <- L0TFinv.opt(y=BlocksData$y, kmax=20, q=0, first=0.01, last=1, penalty="bic")
#' print(res$Aopt)
#' print(BlocksData$setA)
#' plot(BlocksData$x, BlocksData$y, xlab="", ylab="")
#' lines(BlocksData$x, BlocksData$y0, col = "red")
#' lines(BlocksData$x, res$yopt, col = "lightgreen")
#'
#' tau1 = c(0.4, 0.6, 0.7)
#' h1 = c(-3, 5, -4, 6)
#' a0 = -10
#' WaveData <- SimuWaveInv(n = 500, sigma = 0.1, seed = 50, tau = tau1, h = h1, a0 = a0)
#' res1 <- L0TFinv.opt(y=WaveData$y, kmax=10, q=1, first=0, last=0.99, penalty="sic")
#' print(res1$Aopt)
#' print(WaveData$setA)
#' plot(WaveData$x, WaveData$y, xlab="", ylab="")
#' lines(WaveData$x, WaveData$y0, col = "red")
#' lines(WaveData$x, res1$yopt, col = "lightgreen")
#'
#' @export
L0TFinv.opt <- function(y=y, kmax=kmax, q=q, first=0, last=1, penalty="bic"){
  if ( !(q %in% c(0,1)) ){
    stop("The specified order is not supported")
  }
  if ( !(penalty %in% c("bic","sic")) ){
    stop("The specified penalty is not supported")
  }
  if( first<0 | last>1 ){
    stop("
The maximum possible range for the change points should be within [0,1]")
  }
  if( length(y) <= kmax+q+1 ){
    stop("The number of data points should be greater than the number of change points")
  }
  res <- InverseL0TF(y=y, kmax=kmax, q=q, first=first, last=last)
  if(penalty == "bic"){
    kopt = which.min(res$bic)
  }
  if(penalty == "sic"){
    kopt = which.min(res$sic)
  }
  betaopt = res$beta.all[,kopt]
  Aopt = sort(res$A.all[[kopt]])
  yopt = res$y.all[,kopt]
  G = list(sic=res$sic,bic=res$bic,mse=res$mse,
           y=y,
           betaopt=betaopt,yopt=yopt,Aopt=Aopt,kopt=kopt,
           beta.all=res$beta.all,y.all=res$y.all,A.all=res$A.all)
  class(G) <- "L0TFinvopt"
  return(G)
}

function(y0, tau=NULL, yhat, cpts = NULL) {}

This code segment functions to: Compute statistical measures to evaluate the accuracy of trend estimation and change point detection by comparing fitted results against ground truth values while accounting for positional discrepancies between detected and reference events.

```r
[TASK_BEGIN]
function(y0, tau = NULL, yhat, cpts = NULL) {
  MSE <- mean((y0 - yhat)^2)
  MAD <- median(abs(y0 - yhat))

  if (is.null(tau) || is.null(cpts)) {
    dH <- NA
  } else {
    term1 <- max(sapply(cpts, function(x) min(abs(tau - x))))
    term2 <- max(sapply(tau, function(x) min(abs(cpts - x))))
    dH <- max(term1, term2) / length(y0)
  }

  nknot <- ifelse(is.null(cpts), 0, length(cpts))

  list(MSE = MSE, MAD = MAD, dH = dH, nknot = nknot)
}
[TASK_END]
```


#' @title Generate a difference matrix
#' @description This function generates a matrix for computing differences of a certain order, useful in numerical methods and for creating specific matrix patterns.
#' @param n The number of data points
#' @param q The order of the difference
#' @return  A matrix with dimensions \eqn{n-q-1} by \eqn{n}, whose elements correspond to the combinatorial values of \eqn{q}.
#' @examples
#' Mat1 <- DiffMat(n = 10, q = 0)
#' print(Mat1)
#'
#' Mat2 <- DiffMat(n = 15, q = 1)
#' print(Mat2)
#'
#' Mat3 <- DiffMat(n = 15, q = 2)
#' print(Mat3)
#'
#' @seealso \code{\link{XMat}}
#' @export
DiffMat <-

#' @title Generate an artificial design matrix
#' @description This matrix corresponds to the difference matrix, transforming the L0 trend filtering model into an inverse statistical problem.
#' @param n The number of data points
#' @param q The order of the difference
#' @return  A matrix with dimensions \eqn{n} by \eqn{n}, whose elements correspond to the difference matrix.
#' @examples
#' mat1 <- XMat(n = 10, q = 0)
#' print(mat1)
#'
#' mat2 <- XMat(n = 15, q = 1)
#' print(mat2)
#'
#' mat3 <- XMat(n = 15, q = 2)
#' print(mat3)
#'
#' Mat1 <- DiffMat(n = 10, q = 0)
#' Mat2 <- DiffMat(n = 15, q = 1)
#' Mat3 <- DiffMat(n = 15, q = 2)
#' print(Mat1%*%mat1)
#' print(Mat2%*%mat2)
#' print(Mat3%*%mat3)
#' @details Noticing the correspondence between \eqn{\boldsymbol{D}^{(q+1)}} and \eqn{\boldsymbol{X}^{(q+1)}}, the result of their matrix multiplication is a combination of a zero matrix and an identity matrix. Expressed as \eqn{\boldsymbol{D}^{(q+1)} \boldsymbol{X}^{(q+1)}=(\boldsymbol{O}_{(n-q-1)\times(q+1)},\quad \boldsymbol{I}_{(n-q-1)\times(n-q-1)})}. The result is advantageous for the invertible processing of the original L0 trend filtering problem.
#' @export
XMat <- function(n, q){
  X <- matrix(0,n,n)
  if(q == 0){
    for(i in 1:n){
      for(j in 1:i){
        X[i,j] <- 1
      }
    }
    return(X)
  }else{
    return(apply(XMat(n, q-1),2,cumsum))
  }
}

function(n, q) {
  if( n <= q+1 ){
    stop("The number n should be greater than the order of the difference")
  }
  X <- matrix(0, (n-q-1), n)
  for (i in 1:(n-q-1)) {
    for (j in 1:n) {
      if (j >= i && j <= i + q + 1) {
        X[i, j] <- (-1)^(j - i + q - 1) * choose(q + 1, j - i)
      }
    }
  }
  return(X)
}

This code segment functions to: Construct a finite difference operator matrix for specified order calculations by applying combinatorial coefficients with alternating signs to sequential element groups, ensuring dimensional compatibility through input validation.

```r
[TASK_BEGIN]
function(n, q) {
  d <- q + 1
  nr <- n - q -1
  if(nr <= 0) return(matrix(0,0,n))

  D <- matrix(0, nrow = nr, ncol = n)
  coeffs <- (-1)^(0:d) * choose(d, 0:d)

  for(i in 1:nr) {
    cols <- i:(i+d)
    D[i, cols] <- coeffs
  }

  return(D)
}
[TASK_END]
```


This code segment functions to: Calculate error metrics between observed and predicted trends, including discrepancy measures for fitted values and detected change point positions, while returning a structured summary of accuracy assessments.

```r
[TASK_BEGIN]
TFmetrics <- function(y0, tau = NULL, yhat, cpts = NULL) {

    MSE <- mean( (y0 - yhat)^2 )
    MAD <- median( abs(y0 - yhat) )

    n <- length(y0)

    # Handle cases where either tau or cpts are missing/empty
    if ((is.null(tau) || length(tau) == 0) || (is.null(cpts) || length(cpts) == 0)) {
        dH <- NA
    } else {
        # Convert fractional positions to actual indices
        tau_pos <- tau * n
        cpts_pos <- cpts * n

        # Calculate directional Hausdorff distances
        d1 <- max(sapply(tau_pos, function(t) min(abs(t - cpts_pos))))
        d2 <- max(sapply(cpts_pos, function(c) min(abs(c - tau_pos))))
        dH <- max(d1, d2)/n
    }

    nknot <- ifelse(is.null(cpts), 0, length(cpts))

    return(list(MSE = MSE, MAD = MAD, dH = dH, nknot = nknot))
}
[TASK_END]
```

TFmetrics <- function(y0, tau = NULL, yhat, cpts = NULL) {
    MSE <- mean( (y0 - yhat)^2 )
    MAD <- median( abs(y0 - yhat) )
    n <- length(y0)
    if ((is.null(tau) || length(tau) == 0) || (is.null(cpts) || length(cpts) == 0)) {
        dH <- NA
    } else {
        tau_pos <- tau * n
        cpts_pos <- cpts * n
        d1 <- max(sapply(tau_pos, function(t) min(abs(t - cpts_pos))))
        d2 <- max(sapply(cpts_pos, function(c) min(abs(c - tau_pos))))
        dH <- max(d1, d2)/n
    }
    nknot <- ifelse(is.null(cpts), 0, length(cpts))
    return(list(MSE = MSE, MAD = MAD, dH = dH, nknot = nknot))
}


#' @title Plot L0TFinvfix or L0TFinvopt object
#'
#' @description Plots a summary of L0TFinvfix or L0TFinvopt
#' @param x The output of L0TFinvfix or L0TFinvopt
#' @param type The values are taken as c("\eqn{mse}", "\eqn{sic}", "\eqn{bic}", "\eqn{yhat}"). If \eqn{type} is "\eqn{mse}", plot the mse as it changes with change points.
#' The same applies to "\eqn{sic}" and "\eqn{bic}". If \eqn{type} is "\eqn{yhat}", plot the trend of the estimated values against the input data.
#' @param k Only used for \eqn{type} = "\eqn{yhat}". The given number of change points.
#' By default, the L0TFinvfix object outputs the estimated trend that corresponds to the fixed number of change points within the model. Conversely, the L0TFinvopt object provides the estimated trend based on the optimal change points.
#' @param ... ignore
#' @method plot L0TFinvfix
#' @import ggplot2
#' @seealso \code{\link{print.L0TFinvfix}}
#' @export
plot.L0TFinvfix <- function(x, type = NULL, k = NULL, ...){
  if ( !(type %in% c("mse","sic","bic","yhat")) ){
    stop("The specified type is not supported")
  }
  if(type == "mse"){
    num = 1:(length(x$mse))
    val = x$mse
    plotObject <- ggplot(data.frame(num, val), aes(x = num, y = val)) +
      geom_line() +
      geom_point() +
      labs(x = "The number of change points", y = "MSE") +
      theme_minimal()
  }
  if(type == "sic"){
    num = 1:(length(x$sic))
    val = x$sic
    plotObject <- ggplot(data.frame(num, val), aes(x = num, y = val)) +
      geom_line() +
      geom_point() +
      labs(x = "The number of change points", y = "SIC") +
      theme_minimal()
  }
  if(type == "bic"){
    num = 1:(length(x$bic))
    val = x$bic
    plotObject <- ggplot(data.frame(num, val), aes(x = num, y = val)) +
      geom_line() +
      geom_point() +
      labs(x = "The number of change points", y = "BIC") +
      theme_minimal()
  }
  if(type == "yhat"){
    n = length(x$y)
    num = 1:n
    if(is.null(k)){
      k = length(x$mse)
      val = x$y.all[,k]
    }else{
      if(k > ncol(x$beta.all)){
        stop("The number of given change points exceeds the maximum range")
      }
      val = x$y.all[,k]
    }
    input = x$y
    df <- data.frame(num = num, input = input, val = val)
    plotObject <- ggplot(data = df, aes(x = num)) +
      geom_point(aes(y = input), color = "black") +
      geom_line(aes(y = val), color = "lightgreen") +
      labs(x = "Position indicators", y = "Value") +
      theme_minimal()
  }
  return(plotObject)
}

#' @rdname plot.L0TFinvfix
#' @method plot L0TFinvopt
#' @export
plot.L0TFinvopt <-

function(x, type = "mse", k = NULL, ...){
  if ( !(type %in% c("mse","sic","bic","yhat")) ){
    stop("The specified type is not supported")
  }
  if(type == "mse"){
    num = 1:(length(x$mse))
    val = x$mse
    plotObject <- ggplot(data.frame(num, val), aes(x = num, y = val)) +
      geom_line() +
      geom_point() +
      labs(x = "The number of change points", y = "MSE") +
      theme_minimal()
  }
  if(type == "sic"){
    num = 1:(length(x$sic))
    val = x$sic
    plotObject <- ggplot(data.frame(num, val), aes(x = num, y = val)) +
      geom_line() +
      geom_point() +
      labs(x = "The number of change points", y = "SIC") +
      theme_minimal()
  }
  if(type == "bic"){
    num = 1:(length(x$bic))
    val = x$bic
    plotObject <- ggplot(data.frame(num, val), aes(x = num, y = val)) +
      geom_line() +
      geom_point() +
      labs(x = "The number of change points", y = "BIC") +
      theme_minimal()
  }
  if(type == "yhat"){
    n = length(x$y)
    num = 1:n
    if(is.null(k)){
      k = x$kopt
      val = x$y.all[,k]
    }else{
      if(k > ncol(x$beta.all)){
        stop("The number of given change points exceeds the maximum range")
      }
      val = x$y.all[,k]
    }
    input = x$y
    df <- data.frame(num = num, input = input, val = val)
    plotObject <- ggplot(data = df, aes(x = num)) +
      geom_point(aes(y = input), color = "black") +
      geom_line(aes(y = val), color = "lightgreen") +
      labs(x = "Position indicators", y = "Value") +
      theme_minimal()
  }
  return(plotObject)
}

function(x, type = "mse", k = NULL, ...) {}

This code segment functions to: Generate visualization outputs for model evaluation metrics and trend estimations, selecting either optimal or user-specified parameters to display statistical measures versus change point quantities or comparative data fitting results.

```r
[TASK_BEGIN]
plot.L0TFinvopt <- function(x, type = "mse", k = NULL, ...) {
  if (!(type %in% c("mse", "sic", "bic", "yhat"))) {
    stop("The specified type is not supported")
  }

  if (type != "yhat") {
    num <- 1:length(x[[type]])
    val <- x[[type]]
    plotObject <- ggplot(data.frame(num, val), aes(x = num, y = val)) +
      geom_line() +
      geom_point() +
      geom_vline(xintercept = x$k.opt, color = "red", linetype = "dashed") +
      labs(x = "The number of change points", y = toupper(type)) +
      theme_minimal()
  } else {
    if (!is.null(k)) {
      warning("k parameter is ignored for L0TFinvopt, using optimal solution")
    }
    n <- length(x$y)
    df <- data.frame(
      num = 1:n,
      input = x$y,
      val = x$y.opt
    )
    plotObject <- ggplot(df, aes(x = num)) +
      geom_point(aes(y = input), color = "black") +
      geom_line(aes(y = val), color = "lightgreen", linewidth = 1) +
      labs(x = "Position indicators", y = "Value") +
      theme_minimal()
  }
  return(plotObject)
}
[TASK_END]
```