---
title: "Integration with algebraic.mle and algebraic.dist"
output:
    rmarkdown::html_vignette:
        toc: true
vignette: >
  %\VignetteIndexEntry{Integration with algebraic.mle and algebraic.dist}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

```{r setup, include=FALSE}
knitr::opts_chunk$set(
    collapse = TRUE,
    comment = "#>",
    fig.width = 6,
    fig.height = 4
)
old_opts <- options(digits = 4)
```

## The Three-Package Ecosystem

The `likelihood.model` package is part of an ecosystem of three interoperable
packages:

- **`algebraic.dist`** -- probability distribution objects with a common interface
  (`params`, `sampler`, `expectation`, etc.)
- **`algebraic.mle`** -- maximum likelihood estimation infrastructure, defining
  the `mle` class and generics like `params`, `nparams`, `observed_fim`, `rmap`,
  `marginal`, and `expectation`
- **`likelihood.model`** -- likelihood model specification and Fisherian inference

When you fit a model with `likelihood.model`, the result is a `fisher_mle`
object that inherits from `algebraic.mle::mle`. This means all of the
`algebraic.mle` generics work on it directly:

```{r load, message=FALSE, warning=FALSE}
library(likelihood.model)
library(algebraic.mle)
```

## Basic Interface: `params`, `nparams`, `observed_fim`

Let's fit an exponential model and explore the MLE through the `algebraic.mle`
interface:

```{r basic-fit}
set.seed(42)
true_lambda <- 2.0
n <- 200
df <- data.frame(t = rexp(n, rate = true_lambda))

model <- exponential_lifetime("t")
mle_result <- fit(model)(df)

# algebraic.mle generics -- these work because fisher_mle inherits from mle
params(mle_result)
nparams(mle_result)
```

The MLE estimate is close to the true value (2.0), demonstrating good recovery
with $n = 200$ observations. The `params()` and `nparams()` generics work
identically to how they work on native `algebraic.mle::mle` objects -- because
`fisher_mle` inherits from that class, the entire `algebraic.mle` interface is
available without any adapter code.

The `observed_fim()` function returns the observed Fisher information matrix,
computed as the negative Hessian of the log-likelihood at the MLE:

```{r observed-fim}
observed_fim(mle_result)
```

For the exponential model, this is a 1x1 matrix equal to $n/\hat\lambda^2$.
It should be positive at the MLE:

```{r fim-pd}
cat("Observed FIM:", observed_fim(mle_result)[1,1], "\n")
cat("Positive:", observed_fim(mle_result)[1,1] > 0, "\n")
```

For comparison, `vcov()` is the inverse of the observed FIM:

```{r vcov-vs-fim}
vcov(mle_result)
1 / observed_fim(mle_result)
```

The numerical equality confirms that `vcov()` is computed as
`solve(observed_fim())`, the classical relationship
$\widehat{\text{Var}}(\hat\theta) = I(\hat\theta)^{-1}$.

## Parameter Transformations via `rmap`

One powerful feature of `algebraic.mle` is the `rmap()` function, which
transforms MLE parameters using the delta method. This propagates uncertainty
through nonlinear transformations.

For an Exponential($\lambda$) distribution, the mean lifetime is $1/\lambda$.
We can transform our rate MLE to get an MLE for the mean lifetime:

```{r rmap}
# Transform rate -> mean lifetime
mean_life_mle <- rmap(mle_result, function(p) {
  c(mean_lifetime = 1 / p[1])
})

params(mean_life_mle)
se(mean_life_mle)
```

Compare to the true mean lifetime:

```{r rmap-compare}
true_mean <- 1 / true_lambda
cat("True mean lifetime:", true_mean, "\n")
cat("Estimated mean lifetime:", params(mean_life_mle), "\n")
cat("95% CI:", confint(mean_life_mle), "\n")
```

We can also transform to multiple derived quantities at once:

```{r rmap-multi}
# Derive mean, variance, and median of the exponential distribution
derived_mle <- rmap(mle_result, function(p) {
  lam <- p[1]
  c(
    mean   = 1 / lam,
    var    = 1 / lam^2,
    median = log(2) / lam
  )
})

params(derived_mle)
se(derived_mle)
```

Notice that the variance has the largest SE relative to its estimate -- second
moments are inherently harder to estimate than first moments.

## Monte Carlo Expectations

The `expectation()` function computes Monte Carlo expectations over the MLE's
asymptotic sampling distribution.

```{r expectation}
set.seed(123)
# E[lambda^2] under the asymptotic distribution
e_lam_sq <- expectation(mle_result, function(p) p[1]^2,
                         control = list(n = 10000L))
cat("E[lambda^2]:", e_lam_sq, "\n")
cat("lambda^2 at MLE:", params(mle_result)[1]^2, "\n")

# Probability that lambda > 1.5 (under asymptotic distribution)
pr_lam_gt <- expectation(mle_result, function(p) as.numeric(p[1] > 1.5),
                          control = list(n = 10000L))
cat("P(lambda > 1.5):", pr_lam_gt, "\n")
```

## Mean Squared Error

Under standard MLE asymptotics, bias is zero and MSE equals the
variance-covariance matrix:

```{r mse}
mse(mle_result)
all.equal(mse(mle_result), vcov(mle_result))
```

The `TRUE` result confirms that MSE = Vcov under the assumption of zero
asymptotic bias.

## Bootstrap Integration

The `sampler()` generic creates a bootstrap sampling distribution. The resulting
`fisher_boot` object also satisfies the `mle` interface:

```{r bootstrap, cache=TRUE}
set.seed(42)
boot_sampler <- sampler(model, df = df, par = c(lambda = 2))
boot_result <- boot_sampler(n = 200)

# Same algebraic.mle generics work
params(boot_result)
nparams(boot_result)
se(boot_result)
bias(boot_result)
```

The bootstrap bias is negligible compared to the standard error, consistent with
the MLE being asymptotically unbiased.

Compare bootstrap and asymptotic confidence intervals:

```{r boot-compare}
cat("Asymptotic 95% CI:\n")
confint(mle_result)

cat("\nBootstrap percentile 95% CI:\n")
confint(boot_result, type = "perc")
```

## Using `algebraic.dist` Distributions

```{r dist-check, include=FALSE}
has_dist <- requireNamespace("algebraic.dist", quietly = TRUE)
```

```{r dist-section, eval=has_dist}
library(algebraic.dist)
```

The `algebraic.dist` package provides distribution objects with a consistent
interface. We can compare the MLE's asymptotic sampling distribution to
theoretical distribution objects.

For example, the MLE of an exponential rate parameter $\hat\lambda$ is
asymptotically normal with variance $\lambda^2/n$. We can create this
theoretical distribution:

```{r dist-compare, eval=has_dist}
set.seed(42)

cat("MLE:", params(mle_result), "\n")
cat("SE:", se(mle_result), "\n")

# Theoretical asymptotic distribution of the MLE
asymp_var <- true_lambda^2 / n
asymp_dist <- normal(mu = true_lambda, var = asymp_var)
cat("\nTheoretical asymptotic distribution:\n")
cat("  Mean:", params(asymp_dist)[1], "\n")
cat("  Variance:", params(asymp_dist)[2], "\n")

# Compare: sample from the MLE's estimated distribution
mle_sampler <- sampler(mle_result)
set.seed(1)
mle_samples <- mle_sampler(5000)

# vs. sample from the theoretical distribution
dist_sampler <- sampler(asymp_dist)
set.seed(1)
dist_samples <- dist_sampler(5000)

cat("\nMLE sampler mean:", mean(mle_samples), "\n")
cat("Theoretical sampler mean:", mean(dist_samples), "\n")
cat("MLE sampler sd:", sd(mle_samples), "\n")
cat("Theoretical sampler sd:", sd(dist_samples), "\n")
```

## Summary

The `fisher_mle` objects returned by `likelihood.model` are fully compatible
with the `algebraic.mle` interface:

| Generic | What it does on `fisher_mle` |
|---------|------------------------------|
| `params()` | Parameter estimates (same as `coef()`) |
| `nparams()` | Number of parameters |
| `se()` | Standard errors |
| `vcov()` | Variance-covariance matrix |
| `observed_fim()` | Observed Fisher information ($-H$) |
| `bias()` | Asymptotic bias (zero) |
| `mse()` | Mean squared error (equals `vcov` asymptotically) |
| `AIC()` | Akaike information criterion |
| `confint()` | Wald confidence intervals |
| `rmap()` | Delta method parameter transformations |
| `marginal()` | Marginal sampling distributions |
| `expectation()` | Monte Carlo expectations over sampling distribution |
| `sampler()` | Asymptotic or bootstrap sampling function |

```{r cleanup, include=FALSE}
options(old_opts)
```
