"""
transparency.py — numerical companion to the glossary entry
'transparency'.

One block per paragraph of the entry (marked [P...]): the entry is a
regulation term, so its legal paragraphs (EU AI Act Arts. 13/26/50/86,
documentation duties) are expository; the demo illustrates the entry's
technical paragraph on ML methods that inherently offer transparency.
Self-contained (numpy/matplotlib only), fixed seed.

Blocks
------
[P-methods] "Some ML methods inherently offer transparency": (a)
            logistic regression quantifies the confidence of a
            classification via |h(x)| — predictions with large |h(x)|
            are empirically far more reliable than low-|h(x)| ones,
            so disclosing this value (as the entry's medical example
            requires) is informative; (b) a depth-2 decision tree is
            printable as human-readable if-then rules that exactly
            reproduce its predictions.

Outputs
-------
transparency.png : preview figure (checking only).

Data generated by pythondemos/transparency.py.
"""

import numpy as np
import matplotlib

matplotlib.use("Agg")
import matplotlib.pyplot as plt

rng = np.random.default_rng(42)
report = []


def check(name, ok):
    report.append((name, bool(ok)))
    print(f"  [{'ok' if ok else 'FAIL'}] {name}")


# ------------------------------------------------------- [P-methods]
print("[P-methods] (a) logistic regression: confidence via |h(x)|")
m = 4000
X = rng.normal(size=(m, 2))
w_true = np.array([2.0, -1.5])
p = 1 / (1 + np.exp(-(X @ w_true)))
y = (rng.uniform(size=m) < p).astype(float)
w = np.zeros(2)
for _ in range(400):                               # logistic regression GD
    s = 1 / (1 + np.exp(-(X @ w)))
    w -= 0.5 * X.T @ (s - y) / m                   # gradient of log loss
h = X @ w                                          # h(x) = w^T x
pred = (h > 0).astype(float)
lo = np.abs(h) < np.quantile(np.abs(h), 0.3)       # low-confidence tercile
hi = np.abs(h) > np.quantile(np.abs(h), 0.7)       # high-confidence tercile
acc_lo, acc_hi = np.mean(pred[lo] == y[lo]), np.mean(pred[hi] == y[hi])
print(f"    accuracy at low / high |h(x)|: {acc_lo:.2f} / {acc_hi:.2f}")
check("|h(x)| quantifies reliability: high-|h| predictions are far "
      "more accurate", acc_hi > acc_lo + 0.15)
check("disclosing |h(x)| separates confident from uncertain "
      "predictions (medical-example requirement)",
      acc_hi > 0.9)

print("[P-methods] (b) decision tree: human-readable rules")
x1_split, x2_split = 0.0, 0.5
def tree_predict(X):
    out = np.empty(len(X))
    for i, (a, b) in enumerate(X):
        if a <= x1_split:
            out[i] = 0.0 if b <= x2_split else 1.0
        else:
            out[i] = 1.0 if b <= x2_split else 0.0
    return out
rules = [
    f"IF x1 <= {x1_split} AND x2 <= {x2_split} THEN predict 0",
    f"IF x1 <= {x1_split} AND x2 >  {x2_split} THEN predict 1",
    f"IF x1 >  {x1_split} AND x2 <= {x2_split} THEN predict 1",
    f"IF x1 >  {x1_split} AND x2 >  {x2_split} THEN predict 0",
]
for r in rules:
    print("      " + r)
def rules_predict(X):
    out = np.empty(len(X))
    for i, (a, b) in enumerate(X):
        if a <= x1_split and b <= x2_split: out[i] = 0.0
        elif a <= x1_split: out[i] = 1.0
        elif b <= x2_split: out[i] = 1.0
        else: out[i] = 0.0
    return out
Xt = rng.normal(size=(500, 2))
check("the printed if-then rules exactly reproduce the tree's "
      "predictions on every input",
      np.array_equal(tree_predict(Xt), rules_predict(Xt)))
check("the rule list is small enough to read (4 rules, depth 2)",
      len(rules) == 4)

# ------------------------------------------------------------ preview
fig, ax = plt.subplots(figsize=(5.0, 3.2))
bins = np.quantile(np.abs(h), np.linspace(0, 1, 9))
accs = [np.mean(pred[(np.abs(h) >= a) & (np.abs(h) < b)]
        == y[(np.abs(h) >= a) & (np.abs(h) < b)])
        for a, b in zip(bins[:-1], bins[1:])]
ax.plot(0.5 * (bins[:-1] + bins[1:]), accs, "o-")
ax.set_xlabel("|h(x)|"); ax.set_ylabel("empirical accuracy")
ax.set_title("[P-methods] confidence |h(x)| tracks reliability")
fig.tight_layout()
fig.savefig("transparency.png", dpi=110)
print(f"\n{sum(ok for _, ok in report)}/{len(report)} checks passed")
assert all(ok for _, ok in report)
