---
title: "Robustness: constraints and trimming"
output: rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{Robustness: constraints and trimming}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

```{r, include = FALSE}
knitr::opts_chunk$set(collapse = TRUE, comment = "#>", fig.width = 6,
                      fig.height = 4)
library(simOutrank)
```

A decision maker rarely accepts a clustering as-is. `simOutrank` lets them
inject domain knowledge through **pairwise constraints** and remove
uninformative cases by **trimming**. This vignette follows Runs 2 and 3 of
Delias et al. (2023) on the bundled `illustrative_log`.

```{r base}
traces <- as_traces(illustrative_log, "case_id", "activity", "timestamp")
criteria <- list(
  crit_activity_profile(weight = 0.2, indifference = 0.7, similarity = 0.8,
                        veto = 0.4),
  crit_edit_distance(weight = 0.2, similarity = 2, indifference = 3, veto = 6),
  crit_nominal("status", weight = 0.3),
  crit_nominal("satisfaction", weight = 0.3)
)
sim <- outrank_similarity(traces, criteria)
run1 <- cluster_traces(sim, k = 4, seed = 42)$memberships
```

Each Run-1 cluster already contains a single tier: with these criteria and the
corrected normalisation, `simOutrank` separates `GOLD` from `NORMAL` without any
constraint. (The paper's original Run 1, under a different normalisation, mixed
some long-path GOLD customers with NORMAL ones.)

```{r run1-mix}
tier <- illustrative_log$status[match(names(run1), illustrative_log$case_id)]
table(cluster = run1, tier = tier)
```

## Run 2: cannot-link the tiers

Even when the base clustering separates the tiers, a decision maker may want to
*guarantee* it. `cannot_link()` severs the credibility of every mixed-tier pair
(`S <- S * (1 - M)`); passing an attribute name applies the constraint to all
pairs whose values differ.

```{r run2}
sim$S["1", "11"]                     # a GOLD-NORMAL pair, before
sim2 <- cannot_link(sim, "status")
sim2$S["1", "11"]                    # severed to 0
run2 <- cluster_traces(sim2, k = 4, seed = 42)$memberships
table(cluster = run2,
      tier = illustrative_log$status[match(names(run2), illustrative_log$case_id)])
```

The complementary operation, `must_link()`, rewards pairs that should stay
together (`S <- min(S + reward * M, 1)`), and likewise accepts either a matrix
of case-id pairs or an attribute name.

```{r mustlink}
sim_ml <- must_link(sim, cbind("24", "25"), reward = 2)   # the two outliers
sim_ml$S["24", "25"]
```

## Run 3: trim outliers

The two outliers dilute the clustering. `trim_outliers()` scores each case by
its total similarity to the others and removes the least-connected ones. The
genuine low-similarity case is flagged first:

```{r trim}
trimmed <- trim_outliers(sim, prop = 0.04)   # remove floor(0.04 * 25) = 1 case
attr(trimmed, "trimmed")
```

Case `24`, whose `A, B, E` flow matches neither tier, has the lowest
connectivity and is removed. (The paper additionally trims case `25` by domain
judgement; under this weighting `25` shares its tier and satisfaction with many
cases and is not flagged automatically -- a good illustration of why trimming is
offered as a tool, not an oracle.) For small logs the exact integer program is
also available:

```{r trim-lp}
identical(attr(trim_outliers(sim, prop = 0.04, method = "lp"), "trimmed"),
          attr(trim_outliers(sim, prop = 0.04, method = "greedy"), "trimmed"))
```

Clustering the trimmed matrix gives cleaner groups:

```{r trim-cluster}
run3 <- cluster_traces(trimmed, k = 4, seed = 42)$memberships
split(names(run3), run3)
```

## Putting it together

Constraints and trimming compose: trim first, then constrain (or the reverse),
and cluster the adjusted matrix.

```{r compose}
final <- cluster_traces(cannot_link(trimmed, "status"), k = 4, seed = 42)
if (requireNamespace("clValid", quietly = TRUE)) validate_clusters(final)
```

## Reference

Delias, P., Doumpos, M., Grigoroudis, E. and Matsatsinis, N. (2023). Improving
the non-compensatory trace-clustering decision process. *International
Transactions in Operational Research*, 30(3), 1387–1406.
doi:10.1111/itor.13062
```
