Skip to main content

Optimal Transport Independent Component Analysis

Project description

📊 Contrast-Free ICA

otica is a Python package for linear independent component analysis (ICA) based on optimal transport. It recovers latent sources by maximizing their empirical squared 2-Wasserstein distances to the standard Gaussian, using a fixed non-Gaussianity criterion that requires no user-chosen contrast function or nonlinearity.


✨ Features

  • Contrast-free source separation: Uses the squared 2-Wasserstein distance to the standard Gaussian as a fixed non-Gaussianity criterion.
  • Exact empirical objective: Computes the one-dimensional Wasserstein criterion directly from ordered samples and Gaussian quantiles, without density estimation.
  • Riemannian optimization: Optimizes the whitened ICA objective on the orthogonal group with a Picard-style limited-memory BFGS method and Armijo backtracking.
  • Dimension reduction: Supports extraction of a specified number of components through principal-component whitening.
  • Flexible initialization: Accepts FastICA, random, or user-provided initial unmixing matrices through w_init.
  • scikit-learn integration: Implements the standard transformer API, including fit, transform, fit_transform, and inverse_transform.

⚡ Method

For observations generated by the linear ICA model $X = S A^\mathsf{T}$, otica first centers and whitens the data. It then finds an orthogonal rotation that maximizes the sum of the empirical squared 2-Wasserstein distances between the recovered components and a standard Gaussian. Under the usual ICA assumptions, including mutually independent sources with at most one Gaussian component, the population objective identifies the sources up to permutation, sign, and scale.


🚀 Installation

pip install otica

🔧 Usage

Example

The following example generates three independent non-Gaussian signals, mixes them linearly, and recovers them with OTICA. Because ICA is identifiable only up to permutation and sign, recovery is evaluated using the best absolute correlation for each true source.

import matplotlib.pyplot as plt
import numpy as np

from otica import OTICA

rng = np.random.default_rng(42)
n_samples = 5_000
time = np.linspace(0.0, 8.0, n_samples)

# Generate independent, non-Gaussian latent sources.
sources = np.column_stack(
    [
        rng.laplace(size=n_samples),
        rng.uniform(-np.sqrt(3.0), np.sqrt(3.0), size=n_samples),
        rng.standard_t(df=5, size=n_samples) * np.sqrt(3.0 / 5.0),
    ]
)

# Mix the sources into the observed signals.
mixing = np.array(
    [
        [1.0, 0.5, -0.2],
        [0.2, 1.0, 0.4],
        [-0.4, 0.1, 1.0],
    ]
)
X = sources @ mixing.T

# Fit OTICA and recover the latent components.
model = OTICA(random_state=42)
estimated_sources = model.fit_transform(X)

correlations = np.corrcoef(sources.T, estimated_sources.T)[:3, 3:]
best_indices = np.abs(correlations).argmax(axis=1)
best_correlations = correlations[np.arange(3), best_indices]
recovered_sources = estimated_sources[:, best_indices] * np.sign(best_correlations)
print("Best absolute correlation per source:", np.abs(best_correlations))

fig, axes = plt.subplots(3, 2, sharex=True, figsize=(12, 6))
for component in range(3):
    axes[component, 0].plot(time[:500], sources[:500, component])
    axes[component, 1].plot(
        time[:500], recovered_sources[:500, component], color="tab:orange"
    )
    axes[component, 0].set_ylabel(f"Source {component + 1}")

axes[0, 0].set_title("True sources")
axes[0, 1].set_title("Recovered sources")
axes[-1, 0].set_xlabel("Time")
axes[-1, 1].set_xlabel("Time")
fig.tight_layout()
plt.show()

📖 Learn More

For the mathematical formulation, configuration details, and API reference, visit otica's documentation.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

otica-0.1.0.tar.gz (27.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

otica-0.1.0-py3-none-any.whl (21.3 kB view details)

Uploaded Python 3

File details

Details for the file otica-0.1.0.tar.gz.

File metadata

  • Download URL: otica-0.1.0.tar.gz
  • Upload date:
  • Size: 27.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.14

File hashes

Hashes for otica-0.1.0.tar.gz
Algorithm Hash digest
SHA256 87645ac769760b74354a2b816b7bc66f4fd50b1dc82b91a04ee07bcadf1bd560
MD5 1a487b6e901234c3300b4c9cd567146a
BLAKE2b-256 2b7785ecb7e5bcc4d51d0f05de11e67749a359e3c0fb6849f2fe71768e3295cd

See more details on using hashes here.

File details

Details for the file otica-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: otica-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 21.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.14

File hashes

Hashes for otica-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 3c419ed0bcdd1d0af802a8cc96dfd108dc4da7dea5ac1744649d740dbaf5d3ea
MD5 5e69e8bf76ea1085a7ec9fbf74246037
BLAKE2b-256 f16f0ddf719d3d01d35bd0d616333c7c0f3750797f167bc5fd0b0541be037eab

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page