.. _theory_vectorization:

======================
Vectorization Methods
======================

.. contents:: Contents
   :local:
   :depth: 2

Introduction
============

**Why Vectorization?**

Persistence barcodes are variable-length representations - different images produce different numbers of topological features. However, most machine learning algorithms require **fixed-length feature vectors**.

**Vectorization** converts variable-length barcodes into fixed-length vectors suitable for:

* Classification
* Regression
* Clustering
* Statistical analysis

.. note::
   The mathematical definitions and theoretical foundations presented in this section are based on the comprehensive survey by Ali et al. [Ali2023]_, which provides detailed comparisons and benchmarks of vectorization methods for persistent homology barcodes.

The Challenge
-------------

Given a barcode with n features:

::

    barcode = [[b₁, d₁],
               [b₂, d₂],
               ...,
               [bₙ, dₙ]]

We need a function that produces a fixed-length vector regardless of n:

::

    vectorize: ℝⁿˣ² → ℝᵐ

where m is fixed.

Vectorization Strategies
========================

MedTDA provides 8 different vectorization methods, each with different properties and use cases.

1. Persistence Statistics
--------------------------

**Idea:** Compute statistical summaries of persistence values.

**Features Extracted (38 total):**

* **Birth times**
* **Death times**
* **Midpoints**
* **Lifespans**
* **Count** 
* **Entropy**

**Output Size:** 38 features

**Mathematical Definition:**

For a barcode B with intervals [p, q] and multiplicity function :math:`\mu:B \to \mathbb{Z}_+`, the persistence statistics vector consists of:

**Statistical measures of:**

1. **Birth times (p):** mean, std, median, IQR, range, percentiles (10th, 25th, 75th, 90th)
2. **Death times (q):** mean, std, median, IQR, range, percentiles (10th, 25th, 75th, 90th)
3. **Midpoints ((p+q)/2):** mean, std, median, IQR, range, percentiles (10th, 25th, 75th, 90th)
4. **Lifespans (q-p):** mean, std, median, IQR, range, percentiles (10th, 25th, 75th, 90th)
5. **Count:** Total number of features
6. **Entropy:** Information-theoretic measure defined as:

.. math::

   E_\mu = -\sum_{[p,q] \in B} \mu_{p,q} \cdot \left(\frac{q-p}{L_\mu}\right) \cdot \log\left(\frac{q-p}{L_\mu}\right)

where :math:`L_\mu` is the weighted sum of all persistence values:

.. math::

   L_\mu = \sum_{[p,q] \in B} \mu_{p,q} \cdot (q-p)

This vectorization was independently introduced and used in multiple works, providing a simple yet effective baseline for classification tasks.

**Example:**

::

    from medtda.vectorizers import persistence_stats
    
    stats = persistence_stats(barcode)
    # Returns array of length ~13 with interpretable values

2. Betti Curves
---------------

**Idea:** Count features alive at each filtration value.

**Process:**

1. Sample filtration values uniformly
2. At each value, count features alive (birth ≤ value < death)
3. Result is a curve: filtration value → count

**Output Size:** Configurable (default: 100)

**Mathematical Definition:**

The Betti curve :math:`\beta_\mu : \mathbb{R} \to \mathbb{R}` for a barcode with multiplicity function :math:`\mu:B \to \mathbb{Z}_+` is defined as:

.. math::

   \beta_\mu(t) = \sum_{[p,q] \in B} \mathbb{1}_{p \leq t < q} \cdot \mu_{p,q}

where :math:`\mathbb{1}` is the indicator function that equals 1 when the condition is true and 0 otherwise.

**Interpretation:** At each filtration value t, the Betti curve counts how many features are "alive" - i.e., features that have already been born (p ≤ t) but not yet died (t < q).

**Vectorization:** The curve is sampled at regular intervals :math:`t_1, t_2, \ldots, t_m` to produce a fixed-length feature vector of dimension m.

**Example:**

::

    from medtda.vectorizers import betti_curve
    
    curve = betti_curve(barcode, resolution=100)
    # Returns array of length 100

3. Persistence Images
----------------------

**Idea:** Create a 2D histogram of (birth, persistence) weighted by persistence.

**Process:**

1. Transform barcode to (birth, persistence) coordinates
2. Create 2D grid
3. Place Gaussian kernel at each point
4. Weight by persistence
5. Flatten to 1D vector

**Output Size:** resolution² (default: 20² = 400)

**Mathematical Definition:**

Persistence images transform each barcode interval [p,q] into the birth-persistence coordinate (p, q-p), then create a smoothed 2D representation.

**Persistence Surface:** Given a weighting function :math:`f:\mathbb{R}^2 \to \mathbb{R}_{\geq 0}` and probability distributions :math:`\psi_{p,q}` (typically Gaussian) centered at each (p, q-p), the persistence surface is:

.. math::

   \rho^\mu_{f,\Psi}(x,y) = \sum_{[p,q] \in B} \mu_{p,q} \cdot f(p,q-p) \cdot \psi_{p,q}(x,y)

**Typical choices:**

* Weighting: :math:`f(x,y) = y/\lambda_{\text{max}}` for :math:`0 < y < \lambda_{\text{max}}` (longer persistence → higher weight)
* Gaussian kernel: :math:`\psi_{p,q}(x,y) = (1/2\pi\sigma^2) \exp(-(x-p)^2 + (y-(q-p))^2)/(2\sigma^2))`

where :math:`\lambda_{\text{max}}` is the maximum persistence in the barcode.

**Vectorization:** The surface is discretized on a regular grid and flattened into a 1D vector. For a resolution of :math:`m \times m`, this produces a feature vector of dimension :math:`m^2`.

**Example:**

::

    from medtda.vectorizers import persistence_image
    
    pi = persistence_image(barcode, bandwidth=0.2, resolution=20)
    # Returns 20×20 array, flatten for ML: pi.flatten()

4. Persistence Landscapes
--------------------------

**Idea:** Functional representation using piecewise linear functions.

**Process:**

1. Create landscape functions from barcode
2. Sample multiple landscape levels
3. Sample each function at regular intervals
4. Concatenate all samples

**Output Size:** num_landscapes × resolution (default: 5 × 100 = 500)

**Mathematical Definition:**

Persistence landscapes define a sequence of continuous functions. For each interval [p,q], define a "tent" function:

.. math::

   \Delta([p,q], t) = \max(\min(t-p, q-t), 0)

This creates a triangular function that:

* Equals 0 for t < p or t > q
* Increases linearly from p to (p+q)/2
* Decreases linearly from (p+q)/2 to q
* Has maximum value (q-p)/2 at the midpoint

The **k-th landscape function** :math:`\Lambda^k_\mu : \mathbb{R} \to \mathbb{R}` is defined by taking, at each point t, the k-th largest value among all tent functions:

.. math::

   \Lambda^k_\mu(t) = k\text{-th largest value in } \{\mu_{p,q} \cdot \Delta([p,q],t) : [p,q] \in B\}

**Alternative definition:** 

.. math::

   \Lambda^k_\mu(t) = \sup\left\{s \geq 0 : \left(\sum_{[p,q] \in B} \mathbb{1}_{[t-s,t+s] \subset [p,q]} \cdot \mu_{p,q}\right) \geq k\right\}

**Stability:** Persistence landscapes are stable with respect to the bottleneck distance, making them theoretically well-founded for statistical analysis.

**Example:**

::

    from medtda.vectorizers import persistence_landscape
    
    landscape = persistence_landscape(barcode, resolution=100, num_landscapes=5)
    # Returns 5×100 array, flatten for ML: landscape.flatten()

5. Persistence Silhouettes
---------------------------

**Idea:** Average persistence landscape weighted by persistence.

**Process:**

1. Compute landscape functions
2. Weight by persistence
3. Average across all features
4. Sample at regular intervals

**Output Size:** Configurable (default: 100)

**Mathematical Definition:**

The persistence silhouette is a weighted average of landscape-like functions. Given a weight function :math:`w:B \to \mathbb{R}_+` (typically :math:`w_{p,q} = (q-p)^\alpha` for some :math:`\alpha \geq 0`), the silhouette is:

.. math::

   \phi^w_\mu(t) = \frac{\sum_{[p,q] \in B} w_{p,q} \cdot \mu_{p,q} \cdot \Delta([p,q],t)}{\sum_{[p,q] \in B} w_{p,q} \cdot \mu_{p,q}}

where :math:`\Delta([p,q],t) = \max(\min(t-p, q-t), 0)` is the tent function.

**Weighting choices:**

* :math:`\alpha = 0`: All features weighted equally
* :math:`\alpha = 1`: Features weighted by persistence (longer bars → more influence)
* :math:`\alpha = 2`: Quadratic weighting emphasizes long-lived features

**Example:**

::

    from medtda.vectorizers import persistence_silhouette
    
    silhouette = persistence_silhouette(barcode, resolution=100)
    # Returns array of length 100

6. Entropy Summary
-------------------

**Idea:** Information-theoretic features from persistence distribution.

**Process:**

1. Normalize persistence values
2. Treat as probability distribution
3. Compute entropy and related measures
4. Sample distribution at intervals

**Output Size:** Configurable (default: 100)

**Mathematical Definition:**

The entropy summary function represents the contribution to total entropy at each filtration value. Let :math:`L_\mu` be the total weighted persistence:

.. math::

   L_\mu = \sum_{[p,q] \in B} \mu_{p,q} \cdot (q-p)

The entropy summary function :math:`S_\mu : \mathbb{R} \to \mathbb{R}` is:

.. math::

   S_\mu(t) = -\sum_{[p,q] \in B} \mathbb{1}_{p \leq t < q} \cdot \mu_{p,q} \cdot \left(\frac{q-p}{L_\mu}\right) \cdot \log\left(\frac{q-p}{L_\mu}\right)

where :math:`\mathbb{1}_{p \leq t < q}` is the indicator function (equals 1 if feature is alive at time t).

**Interpretation:**

* Piecewise constant function
* At each t, sums entropy contributions from all alive features
* Normalized by total weighted persistence :math:`L_\mu`
* Also called the "life entropy curve" in literature

**Example:**

::

    from medtda.vectorizers import entropy_summary
    
    entropy = entropy_summary(barcode, resolution=100)
    # Returns array of length 100

7. Persistence Lifespan
------------------------

**Idea:** Histogram of persistence values.

**Process:**

1. Extract all persistence values
2. Create histogram with fixed bins
3. Normalize to probability distribution

**Output Size:** Configurable (default: 100 bins)

**Example:**

::

    from medtda.vectorizers import persistence_lifespan
    
    lifespan = persistence_lifespan(barcode, resolution=100)
    # Returns array of length 100

8. Tropical Coordinates
------------------------

**Idea:** Fixed-size encoding using symmetric tropical polynomial invariants of the barcode.

**Process:**

1. Convert barcode intervals to (birth, lifespan) representation
2. Sort intervals by lifespan in descending order
3. Evaluate 7 fixed symmetric tropical polynomial functions

**Output Size:** 7 features (fixed, independent of barcode size)

**Mathematical Definition:**

Tropical coordinates use **tropical algebra**, where standard operations (+, ×) are replaced by (max, +).

Given a barcode B with intervals sorted by lifespan :math:`\lambda_i = q_i - p_i` in descending order (so :math:`\lambda_1 \geq \lambda_2 \geq \cdots \geq \lambda_n`):

**The 7 tropical coordinate features** are:

1. Largest lifespan: :math:`F_1 = \lambda_1`

2. Sum of two largest: :math:`F_2 = \lambda_1 + \lambda_2`

3. Sum of three largest: :math:`F_3 = \lambda_1 + \lambda_2 + \lambda_3`

4. Sum of four largest: :math:`F_4 = \lambda_1 + \lambda_2 + \lambda_3 + \lambda_4`

5. Total lifespan sum: :math:`F_5 = \sum_i \lambda_i`

6. Birth-weighted sum: :math:`F_6 = \sum_i \min(r\lambda_i, p_i)`

7. Complementary spread: :math:`F_7 = \sum_i \left(M - \min(r\lambda_i, p_i) - \lambda_i\right)`

where :math:`r` is a scalar parameter (default 28) and :math:`M = \max_i \left(\min(r\lambda_i, p_i) + \lambda_i\right)`.

Features F1–F4 are set to 0 when fewer bars are available than required (e.g. F4 = 0 if fewer than 4 bars exist).

**Stability:** Tropical coordinates are stable with respect to the bottleneck distance — small changes in the barcode produce bounded changes in coordinate values.

**Example:**

::

    from medtda.vectorizers import persistence_tropical_coordinates

    coords = persistence_tropical_coordinates(barcode, r=28)
    # Returns array of length 7

Combining Methods
=================

Use multiple vectorization methods together:

::

    from medtda.vectorizers import (
        persistence_stats,
        betti_curve,
        persistence_image
    )
    import numpy as np
    
    # Extract using multiple methods
    stats = persistence_stats(barcode)
    curve = betti_curve(barcode, resolution=100)
    image = persistence_image(barcode, resolution=20).flatten()
    
    # Concatenate all features
    combined = np.concatenate([stats, curve, image])
    
    print(f"Combined feature vector size: {len(combined)}")
    # Size: 13 + 100 + 400 = 513

Multi-Dimensional Barcodes
===========================

Vectorize each homology dimension separately:

::

    from medtda import BarcodeExtractor
    from medtda.vectorizers import persistence_image
    import numpy as np
    
    # Extract barcodes
    extractor = BarcodeExtractor(max_dimension=2)
    barcodes = extractor.execute(image_3d)
    
    # Vectorize each dimension
    features = {}
    for dim in [0, 1, 2]:
        if len(barcodes[f'H{dim}']) > 0:
            pi = persistence_image(barcodes[f'H{dim}'], resolution=20)
            features[f'H{dim}'] = pi.flatten()
        else:
            # Handle empty barcodes
            features[f'H{dim}'] = np.zeros(400)
    
    # Concatenate all dimensions
    all_features = np.concatenate([
        features['H0'],
        features['H1'],
        features['H2']
    ])
    
    print(f"Total feature vector: {len(all_features)}")  # 1200

See Also
========

* :doc:`persistent_homology` - Computing barcodes
* :doc:`barcodes_diagrams` - Understanding barcodes
* :doc:`../user_guide/vectorization` - Vectorization user guide
* :doc:`../api/vectorizers` - Vectorization API reference
