Skip to main content

📊 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.

Metadata

Release files for otica 0.1.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for otica 0.1.1
File Size Uploaded
otica-0.1.1.tar.gz 27.3 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for otica 0.1.1
File Interpreter ABI Platform
otica-0.1.1-py3-none-any.whl Python 3 none any Details

Total release size: 48.6 kB

Release files / otica-0.1.1.tar.gz

Download URL otica-0.1.1.tar.gz
Size 27.3 kB
Tags Source
SHA-256 checksum
How to use checksums
9e2d3d24efb3a7e7e965e232ba9d5693de2c0a07de460e6570d096d07d9f484b
BLAKE2b-256 checksum
How to use checksums
e848e11a82357204ddad525f3ecf4691477da20de941454931abd7a9b1124e01
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.13.14

Release files / otica-0.1.1-py3-none-any.whl

Download URL otica-0.1.1-py3-none-any.whl
Size 21.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
07d291ecc028c7c7cd7a5841d7711ec381d80b4cc31996fae120b403c6d4271c
BLAKE2b-256 checksum
How to use checksums
5fd53d62d15217403ef4b131f730286417998c36ec50b3bed712dfa3878372dc
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.13.14

Release history Release notifications | RSS feed

0.2.1

2 release files

0.2.0

2 release files

0.1.4

2 release files

0.1.3

2 release files

0.1.2

2 release files

This release

0.1.1 This release

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page