---
title: "Defining Custom Distributions"
author: "Your Name"
date: "`r Sys.Date()`"
output: rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{Defining Custom Distributions}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

```{r setup, include=FALSE}
knitr::opts_chunk$set(echo = TRUE)
library(TKApprox)
```

## Introduction

TKApprox is designed to work with any user-specified univariate probability distribution. This vignette shows how to define custom distributions for use with the package.

## Required Functions

To use a distribution with TKApprox, you typically need to provide:

1. **PDF/PMF function**: `pdf(x, param)` or `pmf(x, param)` for discrete distributions
2. **CDF function**: `cdf(x, param)` (required for censored data)

The parameter `param` is always a numeric vector containing all distribution parameters.

## Continuous Distributions

### Example 1: Log-Normal Distribution

```{r}
# Define log-normal PDF
pdf_lognormal <- function(x, param) {
  dlnorm(x, meanlog = param[1], sdlog = param[2])
}

# Define log-normal CDF
cdf_lognormal <- function(x, param) {
  plnorm(x, meanlog = param[1], sdlog = param[2])
}

# Specify priors
prior_spec <- list(
  meanlog = list(family = "normal", hyperparameters = list(mean = 0, sd = 1)),
  sdlog = list(family = "gamma", hyperparameters = list(shape = 2, rate = 1))
)

# Generate data
set.seed(123)
data <- rlnorm(20, meanlog = 0, sdlog = 0.5)

# Fit the model
fit <- tk_fit(
  data = data,
  censoring_scheme = "complete",
  pdf = pdf_lognormal,
  cdf = cdf_lognormal,
  prior_spec = prior_spec,
  initial_values = c(meanlog = 0, sdlog = 0.5),
  loss_function = "sel"
)

summary(fit)
```

### Example 2: Pareto Distribution

```{r}
# Define Pareto PDF
pdf_pareto <- function(x, param) {
  xm <- param[1]  # scale parameter (minimum)
  alpha <- param[2]  # shape parameter
  ifelse(x >= xm, (alpha * xm^alpha) / (x^(alpha + 1)), 0)
}

# Define Pareto CDF
cdf_pareto <- function(x, param) {
  xm <- param[1]
  alpha <- param[2]
  ifelse(x >= xm, 1 - (xm / x)^alpha, 0)
}

# Specify priors
prior_spec <- list(
  xm = list(family = "gamma", hyperparameters = list(shape = 2, rate = 1)),
  alpha = list(family = "gamma", hyperparameters = list(shape = 2, rate = 1))
)

# Generate Pareto data
set.seed(123)
data <- (1 / (1 - runif(20)))^(1/2)  # Pareto(1, 2)

# Fit the model
fit <- tk_fit(
  data = data,
  censoring_scheme = "complete",
  pdf = pdf_pareto,
  cdf = cdf_pareto,
  prior_spec = prior_spec,
  initial_values = c(xm = 0.5, alpha = 1.5),
  loss_function = "sel"
)

summary(fit)
```

### Example 3: Burr Type XII Distribution

```{r}
# Define Burr Type XII PDF
pdf_burr <- function(x, param) {
  c <- param[1]  # shape parameter 1
  k <- param[2]  # shape parameter 2
  lambda <- param[3]  # scale parameter
  (c * k / lambda) * (x / lambda)^(c - 1) / (1 + (x / lambda)^c)^(k + 1)
}

# Define Burr Type XII CDF
cdf_burr <- function(x, param) {
  c <- param[1]
  k <- param[2]
  lambda <- param[3]
  1 - 1 / (1 + (x / lambda)^c)^k
}

# Specify priors
prior_spec <- list(
  c = list(family = "gamma", hyperparameters = list(shape = 2, rate = 1)),
  k = list(family = "gamma", hyperparameters = list(shape = 2, rate = 1)),
  lambda = list(family = "gamma", hyperparameters = list(shape = 2, rate = 1))
)

# Generate Burr data (using approximation)
set.seed(123)
data <- rburr <- function(n, c, k, lambda) {
  u <- runif(n)
  lambda * (u^(-1/k) - 1)^(-1/c)
}
data <- rburr(20, c = 2, k = 1, lambda = 1)

# Fit the model
fit <- tk_fit(
  data = data,
  censoring_scheme = "complete",
  pdf = pdf_burr,
  cdf = cdf_burr,
  prior_spec = prior_spec,
  initial_values = c(c = 1.5, k = 0.8, lambda = 1),
  loss_function = "sel"
)

summary(fit)
```

## Discrete Distributions

### Example 4: Poisson Distribution

```{r}
# Define Poisson PMF
pmf_poisson <- function(x, param) {
  dpois(x, lambda = param[1])
}

# For discrete distributions, CDF is still needed for censoring
cdf_poisson <- function(x, param) {
  ppois(x, lambda = param[1])
}

# Specify prior
prior_spec <- list(
  lambda = list(family = "gamma", hyperparameters = list(shape = 2, rate = 1))
)

# Generate Poisson data
set.seed(123)
data <- rpois(20, lambda = 3)

# Fit the model
fit <- tk_fit(
  data = data,
  censoring_scheme = "complete",
  pdf = pmf_poisson,  # Use pmf as pdf for discrete
  cdf = cdf_poisson,
  prior_spec = prior_spec,
  initial_values = c(lambda = 2),
  loss_function = "sel"
)

summary(fit)
```

### Example 5: Negative Binomial Distribution

```{r}
# Define Negative Binomial PMF
pmf_nbinom <- function(x, param) {
  size <- param[1]
  mu <- param[2]
  dnbinom(x, size = size, mu = mu)
}

# Define Negative Binomial CDF
cdf_nbinom <- function(x, param) {
  size <- param[1]
  mu <- param[2]
  pnbinom(x, size = size, mu = mu)
}

# Specify priors
prior_spec <- list(
  size = list(family = "gamma", hyperparameters = list(shape = 2, rate = 1)),
  mu = list(family = "gamma", hyperparameters = list(shape = 2, rate = 1))
)

# Generate Negative Binomial data
set.seed(123)
data <- rnbinom(20, size = 5, mu = 3)

# Fit the model
fit <- tk_fit(
  data = data,
  censoring_scheme = "complete",
  pdf = pmf_nbinom,
  cdf = cdf_nbinom,
  prior_spec = prior_spec,
  initial_values = c(size = 4, mu = 2.5),
  loss_function = "sel"
)

summary(fit)
```

## Using Custom Log-Likelihood Functions

For complex models, you can provide a custom log-likelihood function directly instead of relying on the automatic construction from PDF/CDF.

```{r}
# Custom log-likelihood for a mixture model
loglik_mixture <- function(param, data) {
  p <- param[1]  # mixing proportion
  lambda1 <- param[2]  # rate for component 1
  lambda2 <- param[3]  # rate for component 2
  
  # Enforce constraints
  if (p <= 0 || p >= 1 || lambda1 <= 0 || lambda2 <= 0) {
    return(-Inf)
  }
  
  # Log-likelihood
  ll <- sum(log(p * dexp(data, rate = lambda1) + (1 - p) * dexp(data, rate = lambda2)))
  
  if (!is.finite(ll)) {
    return(-Inf)
  }
  
  ll
}

# Specify priors
prior_spec <- list(
  p = list(family = "beta", hyperparameters = list(shape1 = 2, shape2 = 2)),
  lambda1 = list(family = "gamma", hyperparameters = list(shape = 2, rate = 1)),
  lambda2 = list(family = "gamma", hyperparameters = list(shape = 2, rate = 1))
)

# Generate mixture data
set.seed(123)
data <- c(rexp(10, rate = 1), rexp(10, rate = 5))

# Fit the model
fit <- tk_fit(
  data = data,
  censoring_scheme = "complete",
  pdf = NULL,  # Not needed when providing loglik
  cdf = NULL,
  prior_spec = prior_spec,
  loglik = loglik_mixture,
  initial_values = c(p = 0.5, lambda1 = 1, lambda2 = 5),
  loss_function = "sel"
)

summary(fit)
```

## Tips for Defining Distributions

1. **Parameter ordering**: Be consistent with parameter ordering in PDF and CDF functions
2. **Boundary handling**: Ensure your functions handle boundary cases gracefully
3. **Numerical stability**: Use log-scale computations when possible to avoid overflow/underflow
4. **Parameter constraints**: Use bounds in `tk_fit()` to keep parameters in valid ranges
5. **Initial values**: Good initial values are crucial for convergence in complex models

## Next Steps

- See "Censoring Schemes" for how to handle censored data with custom distributions
- See "Prior Specification" for advanced prior modeling techniques
