## ----include = FALSE----------------------------------------------------------
knitr::opts_chunk$set(
  collapse = TRUE,
  comment = "#>",
  fig.width = 7,
  fig.height = 4
)

## ----setup--------------------------------------------------------------------
library(rLifting)

if (!requireNamespace("ggplot2", quietly = TRUE)) {
  knitr::opts_chunk$set(eval = FALSE)
  message("'ggplot2' is required to render plots. Vignette code will not run.")
} else {
  library(ggplot2)
}

## ----builtin------------------------------------------------------------------
haar = lifting_scheme("haar")
cdf53 = lifting_scheme("cdf53")
cdf97 = lifting_scheme("cdf97")
dd4 = lifting_scheme("dd4")
print(cdf97)

## ----custom_define------------------------------------------------------------
p_step = lift_step(
  type = "predict",
  coeffs = c(0.5, 0.5),
  start_idx = 0
)

u_step = lift_step(
  type = "update",
  coeffs = c(0.25, 0.25),
  start_idx = -1
)

my_cdf53 = custom_wavelet(
  name = "MyCDF53",
  steps = list(p_step, u_step),
  norm = c(sqrt(2), 1/sqrt(2))
)

print(my_cdf53)

## ----custom_use---------------------------------------------------------------
x = 1:16
# local_linear extension preserves the linear trend at the boundary,
# so all detail coefficients vanish for a degree-1 input (2 VM).
fwd = lwt(
  x, my_cdf53, 
  levels = floor(log2(length(x))),
  extension = "local_linear"
)

# CDF 5/3 has 2 VM: detail coefficients are zero for a linear signal
print(round(fwd$coeffs$d1, 10))

# Perfect reconstruction
rec = ilwt(fwd)
all.equal(as.numeric(x), rec)

## ----diagnose-----------------------------------------------------------------
config = list(
  is_ortho = FALSE, # CDF 5/3 is biorthogonal, not orthogonal
  vm_degrees = c(0, 1), # degree 0 = constant, 1 = ramp
  max_taps = 5 # expected filter support width
)

diag = diagnose_wavelet(
  my_cdf53,
  config = config,
  plot = FALSE # suppress basis plot
)

## ----configs, eval=FALSE------------------------------------------------------
# diagnose_wavelet("haar", list(is_ortho = TRUE,  vm_degrees = 0,    max_taps = 2))
# diagnose_wavelet("db2", list(is_ortho = TRUE,  vm_degrees = 0:1,  max_taps = 4))
# diagnose_wavelet("cdf53", list(is_ortho = FALSE, vm_degrees = 0:1,  max_taps = 5))
# diagnose_wavelet("dd4", list(is_ortho = FALSE, vm_degrees = 0:3,  max_taps = 11))
# diagnose_wavelet("cdf97", list(is_ortho = FALSE, vm_degrees = 0:3,  max_taps = 12))

## ----pipeline, fig.cap="Figure 1: Custom denoising pipeline on a synthetic two-frequency signal. The noisy input (grey) is decomposed with CDF 9/7 at 5 levels; details d1–d3 are denoised with semisoft, d4 with hard, and d5 is left untouched to preserve coarse structure. The reconstructed signal (orange) tracks the underlying truth (black) closely."----
set.seed(42)
n = 512
t_v = seq(0, 1, length.out = n)
pure = sin(2 * pi * 5 * t_v) + 0.5 * sin(2 * pi * 20 * t_v)
noisy = pure + rnorm(n, sd = 0.4)

sch = lifting_scheme("cdf97")

# 1. Forward transform
decomp = lwt(noisy, sch, levels = 5)

# 2. Compute adaptive thresholds
lambdas = compute_adaptive_threshold(decomp, alpha = 0.3, beta = 1.2)

# 3. Custom strategy: semisoft on fine levels, hard on d4; leave d5 untouched
for (lev in 1:3) {
  dname = paste0("d", lev)
  decomp$coeffs[[dname]] = threshold(
    decomp$coeffs[[dname]], lambdas[[dname]], method = "semisoft"
  )
}
decomp$coeffs$d4 = threshold(decomp$coeffs$d4, lambdas$d4, method = "hard")
# d5 untouched to preserve coarse structure

# 4. Inverse transform
reconstructed = ilwt(decomp)

df_pipe = data.frame(
  t = rep(t_v, 3),
  value = c(noisy, pure, reconstructed),
  Signal = rep(c("Noisy", "Truth", "Filtered"), each = n)
)

ggplot(df_pipe, aes(x = t, y = value, colour = Signal)) +
  geom_line(alpha = 0.7, linewidth = 0.4) +
  labs(
    title = "Custom denoising pipeline (CDF 9/7, 5 levels)",
    x = "Time", y = "Amplitude"
  ) +
  scale_colour_manual(
    values = c(
      "Noisy" = "grey70", "Truth" = "black",
      "Filtered" = "#D55E00"
    )
  ) +
  theme_minimal()

## ----boundary_ll_k, eval=FALSE------------------------------------------------
# # Compare 2-point vs 4-point extrapolation on a noisy boundary
# res_k2 = denoise_signal_offline(
#   noisy, sch,
#   extension = "local_linear", ll_k = 2L
# )
# 
# res_k4 = denoise_signal_offline(
#   noisy, sch,
#   extension = "local_linear", ll_k = 4L
# )

## ----boundary_compare, fig.cap="Figure 2: Left-boundary zoom of an offline denoising on a trending signal (3·t + noise + sinusoid) using CDF 9/7 at 4 levels. symmetric (blue) and local_linear (green) reflect or extrapolate the boundary; one_sided (pink) uses only existing samples. On a smooth trending signal in offline mode all three are visually similar; differences become important in causal mode."----
set.seed(42)
n_bnd = 256
t_bnd = seq(0, 1, length.out = n_bnd)
trend = 3 * t_bnd
sig_b = trend + 0.3 * rnorm(n_bnd) + 0.5 * sin(2 * pi * 8 * t_bnd)
sch_b = lifting_scheme("cdf97")

res_sym = denoise_signal_offline(
  sig_b, sch_b, levels = 4,
  extension = "symmetric"
)

res_ll = denoise_signal_offline(
  sig_b, sch_b, levels = 4,
  extension = "local_linear"
)
res_os = denoise_signal_offline(
  sig_b, sch_b, levels = 4,
  extension = "one_sided"
)

df_bnd = data.frame(
  t = rep(t_bnd, 4),
  value = c(sig_b, res_sym, res_ll, res_os),
  Signal = rep(
    c("Noisy", "symmetric", "local_linear", "one_sided"),
    each = n_bnd
  )
)

ggplot(df_bnd, aes(x = t, y = value, colour = Signal, linewidth = Signal)) +
  geom_line(alpha = 0.85) +
  scale_linewidth_manual(
    values = c(
      "Noisy" = 0.3, "symmetric" = 0.7, "local_linear" = 0.9, "one_sided" = 0.9
    )
  ) +
  scale_colour_manual(values = c(
    "Noisy" = "grey75",
    "symmetric" = "#0072B2",
    "local_linear" = "#009E73",
    "one_sided" = "#CC79A7"
  )) +
  coord_cartesian(xlim = c(0, 0.15)) +
  labs(
    title = "Left-boundary zoom: symmetric vs local_linear vs one_sided",
    subtitle = "Linearly trending signal, CDF 9/7, 4 levels",
    x = "Time", y = "Amplitude"
  ) +
  theme_minimal()

## ----boundary_causal, fig.cap="Figure 3: Causal-stream denoising of a trending signal with sinusoidal component (Haar, window = 63). symmetric (blue) reflects the most recent sample inward at each step; one_sided (pink) renormalises the filter against the available window. Both follow the trend; the differences emerge in regions where the filter window contains a transition."----
set.seed(1)
n_c = 512
t_c = seq(0, 2, length.out = n_c)
x_c = 2 * t_c + 0.4 * rnorm(n_c) + sin(2 * pi * 4 * t_c)

sch_c = lifting_scheme("haar")
proc_sym = new_wavelet_stream(
  sch_c, window_size = 63, levels = 3,
  extension = "symmetric"
)

proc_os = new_wavelet_stream(
  sch_c, window_size = 63, levels = 3,
  extension = "one_sided"
)

out_sym = out_os = numeric(n_c)
for (i in seq_len(n_c)) {
  out_sym[i] = proc_sym(x_c[i])
  out_os[i]  = proc_os(x_c[i])
}

df_c = data.frame(
  t = rep(t_c, 3),
  value = c(x_c, out_sym, out_os),
  Signal = rep(c("Noisy", "symmetric", "one_sided"), each = n_c)
)

ggplot(df_c, aes(x = t, y = value, colour = Signal, linewidth = Signal)) +
  geom_line(alpha = 0.85) +
  scale_linewidth_manual(
    values = c("Noisy" = 0.3, "symmetric" = 0.8, "one_sided" = 0.8)
  ) +
  scale_colour_manual(
    values = c(
      "Noisy" = "grey75",
      "symmetric" = "#0072B2",
      "one_sided" = "#CC79A7"
    )
  ) +
  labs(
    title = "Causal stream: symmetric vs one_sided (Haar, window = 63)",
    subtitle = "Trending signal, differences emerge at window boundaries",
    x = "Time", y = "Amplitude"
  ) +
  theme_minimal()

## ----irr_custom---------------------------------------------------------------
# 4-point cubic interpolating predict (sum = 1 -> degree = 3)
p_cubic = lift_step(
  "predict",
  coeffs = c(-1/16, 9/16, 9/16, -1/16),
  start_idx = -1
)

u_cubic = lift_step(
  "update",
  coeffs = c(-1/32, 9/32, 9/32, -1/32),
  start_idx = -1
)

my_dd4 = custom_wavelet(
  "MyDD4", list(p_cubic, u_cubic), c(sqrt(2), 1/sqrt(2))
)

# Confirm the degree was inferred correctly (3 means cubic Lagrange)
my_dd4$steps[[1]]$degree

## ----irr_denoise, fig.cap="Figure 4: Custom 4-point cubic wavelet applied to a smooth signal on an irregular grid. The noisy input (grey dots) is sampled at non-uniform time positions drawn from a log-normal-ish process; the underlying signal (black dashed) is recovered by `denoise_signal_offline` with the custom wavelet and `t = t_irr` (orange). The predict step uses Lagrange interpolation evaluated at each sample's physical position rather than fixed coefficients."----
set.seed(20260523)
n_irr = 256

# Irregular grid: cumulative spacing with high variance
t_irr = cumsum(c(0, abs(rnorm(n_irr - 1, mean = 1, sd = 0.6))))

# Smooth underlying signal
pure_irr = sin(t_irr / 8) + 0.3 * cos(t_irr / 3)
noisy_irr = pure_irr + rnorm(n_irr, sd = 0.3)

# Denoise using the custom wavelet on the irregular grid
clean_irr = denoise_signal_offline(
  noisy_irr, my_dd4,
  levels = 4, t = t_irr,
  shrinkage = "semisoft", alpha = 0.3, beta = 1.2
)

df_irr = data.frame(
  t = rep(t_irr, 3),
  value = c(noisy_irr, pure_irr, clean_irr),
  Signal = factor(
    rep(c("Noisy", "Truth", "Denoised (MyDD4)"), each = n_irr),
    levels = c("Noisy", "Truth", "Denoised (MyDD4)")
  )
)

ggplot(df_irr, aes(x = t, y = value, colour = Signal)) +
  geom_point(
    data = subset(df_irr, Signal == "Noisy"),
    size = 0.6, alpha = 0.5
  ) +
  geom_line(
    data = subset(df_irr, Signal == "Truth"),
    linewidth = 0.5, linetype = "dashed"
  ) +
  geom_line(
    data = subset(df_irr, Signal == "Denoised (MyDD4)"),
    linewidth = 0.8
  ) +
  scale_colour_manual(values = c(
    "Noisy" = "grey60",
    "Truth" = "black",
    "Denoised (MyDD4)" = "#D55E00"
  )) +
  theme_minimal() +
  labs(
    title = "Custom wavelet on an irregular grid",
    subtitle = sprintf(
      "n = %d samples; spacing CV = %.0f%%",
      n_irr, 100 * sd(diff(t_irr)) / mean(diff(t_irr))
    ),
    x = "Physical time t", y = "Amplitude"
  )

## ----basis, fig.height=3, fig.cap="Figure 5: Scaling and wavelet basis functions of CDF 9/7 obtained by cascade iteration of the lifting steps. Useful for verifying that a custom wavelet produces reasonable basis functions and for diagnosing coefficient specification errors."----
plot(lifting_scheme("cdf97"))

