pacmap-gpu 1.1.0
GPU-Accelerated Pairwise Controlled Manifold Approximation for large-scale dimensionality reduction
License: Apache-2.0
Requires Python: >=3.10
Keywords: dimensionality-reduction, manifold-learning, gpu, cuda, machine-learning

# PaCMAP-GPU: GPU-Accelerated Pairwise Controlled Manifold Approximation

[![Python](https://img.shields.io/badge/Python-3.10%2B-blue)](https://www.python.org/)
[![CUDA](https://img.shields.io/badge/CUDA-12.0%2B-green)](https://developer.nvidia.com/cuda-toolkit)
[![PyTorch](https://img.shields.io/badge/PyTorch-2.0%2B-red)](https://pytorch.org/)
[![RAPIDS](https://img.shields.io/badge/RAPIDS-24.0%2B-purple)](https://rapids.ai/)
[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)

**PaCMAP-GPU** is a high-performance GPU implementation of Pairwise Controlled Manifold Approximation and Projection (PaCMAP), providing dramatic speedups for large-scale dimensionality reduction and manifold learning tasks.

> **🎉 NEW in v1.1**: **Adaptive Parameter Selection** - PaCMAP-GPU now automatically selects optimal `n_neighbors` values based on your dataset size, achieving complete feature parity with the original CPU implementation!

## 🚀 Key Features

- **GPU Acceleration**: Native CUDA implementation using PyTorch and CUDA graphs for maximum performance
- **Massive Speedups**: 5-50x faster than CPU implementations depending on dataset size
- **Adaptive Parameter Selection**: Automatic n_neighbors selection based on dataset size for optimal performance
- **Flexible Distance Metrics**: Support for euclidean, manhattan, cosine, and hamming distances
- **True Transform Method**: Out-of-sample embedding with frozen original points
- **Memory Efficient**: Optimized GPU memory usage with automatic cleanup
- **Drop-in Replacement**: Compatible API with sklearn-style transformers
- **Three-Phase Training**: Standard PaCMAP phases plus LocalMAP variant
- **Large Dataset Support**: Handle datasets with millions of points efficiently
- **CUDA Graph Optimization**: Minimal kernel launch overhead

## 📋 Requirements

### Hardware Requirements
- NVIDIA GPU with Compute Capability 6.0+ (Pascal architecture or newer)
- 4GB+ GPU memory (8GB+ recommended for large datasets)
- CUDA 12.0 or higher

### Software Dependencies
Core dependencies (automatically installed):
- PyTorch >= 2.0.0
- NumPy >= 1.21.0, < 2.0.0
- SciPy >= 1.7.0
- scikit-learn >= 1.0.0
- pandas >= 1.3.0
- matplotlib >= 3.5.0

GPU acceleration (required):
- cupy-cuda12x >= 12.0.0
- cuml-cu12 >= 24.0.0
- cudf-cu12 >= 24.0.0
- faiss-gpu-cu12

**Important: CUDA-enabled PyTorch Required**

> By default, `pip install pacmap-gpu` will install the CPU-only version of PyTorch.  
> **To use GPU acceleration, you must manually install the CUDA-enabled PyTorch wheel** that matches your system and CUDA version.  
> See [PyTorch Get Started](https://pytorch.org/get-started/locally/) for installation instructions.

**Example:**
```bash
# Example for CUDA 12.1
pip install torch==2.2.2+cu121 torchvision==0.17.2+cu121 torchaudio==2.2.2+cu121 \
  --index-url https://download.pytorch.org/whl/cu121
```

## 🔧 Installation

### Option 1: Install from PyPI (Recommended)

```bash
# Install PaCMAP-GPU with all dependencies
pip install pacmap-gpu

# Verify installation
pacmap-gpu-check
```

**Note:** PaCMAP-GPU requires CUDA-capable hardware and GPU libraries. All necessary RAPIDS and CUDA dependencies are automatically installed.

### Option 2: Install from Source

```bash
# Clone the repository
git clone https://github.com/AntonioRoye/PaCMAP-GPU.git
cd PaCMAP-GPU

# Create conda environment with RAPIDS and all dependencies (recommended for development)
conda create -n pacmap-gpu -c rapidsai -c conda-forge \
    cuml-cu12 cudf-cu12 cupy-cuda12x faiss-gpu-cu12 \
    python=3.10 pytorch pytorch-cuda=12.1 -c pytorch -c nvidia

# Activate environment
conda activate pacmap-gpu

# Install package in development mode
pip install -e .
```

### Alternative: Using requirements file

```bash
# Clone repository
git clone https://github.com/AntonioRoye/PaCMAP-GPU.git
cd PaCMAP-GPU

# Create virtual environment
python -m venv venv
source venv/bin/activate  # On Windows: venv\Scripts\activate

# Install CUDA 12 dependencies
pip install -r requirements-cuda12.txt

# Install package
pip install -e .
```

### Verify Installation

```bash
# Check installation
pacmap-gpu-check

# Or in Python
python -c "from pacmap_gpu import PaCMAPGPU, check_gpu_availability; print('GPU Available:', check_gpu_availability())"
```

## 🎯 Quick Start

### Basic Usage

```python
import numpy as np
from pacmap_gpu import PaCMAPGPU
from sklearn.datasets import make_swiss_roll

# Generate sample data
X, color = make_swiss_roll(n_samples=2000, noise=0.1, random_state=42)

# Create PaCMAP-GPU instance with adaptive parameters
reducer = PaCMAPGPU(
    n_components=2,
    # n_neighbors=None (default) - automatically selected based on dataset size
    distance='euclidean',
    num_iters=450,
    random_state=42,
    verbose=True
)

# Fit and transform the data
X_embedded = reducer.fit_transform(X)
print(f"Embedded shape: {X_embedded.shape}")
print(f"Automatically selected n_neighbors: {reducer._n_neighbors_final}")
```

### Adaptive Parameter Selection

**NEW**: PaCMAP-GPU now automatically selects optimal `n_neighbors` values based on your dataset size, just like the original CPU implementation:

```python
# For small datasets (≤10,000 samples): n_neighbors = 10
small_data = np.random.randn(5000, 50).astype(np.float32)
reducer_small = PaCMAPGPU(verbose=True)
reducer_small.fit(small_data)
print(f"Small dataset n_neighbors: {reducer_small._n_neighbors_final}")  # 10

# For large datasets (>10,000 samples): n_neighbors = 10 + 15 * (log10(n_samples) - 4)
large_data = np.random.randn(50000, 50).astype(np.float32)
reducer_large = PaCMAPGPU(verbose=True)
reducer_large.fit(large_data)
print(f"Large dataset n_neighbors: {reducer_large._n_neighbors_final}")  # ~26

# You can still override with explicit values
reducer_explicit = PaCMAPGPU(n_neighbors=15)
reducer_explicit.fit(large_data)
print(f"Explicit n_neighbors: {reducer_explicit._n_neighbors_final}")  # 15
```

### Using Different Distance Metrics

```python
import numpy as np
from pacmap_gpu import PaCMAPGPU

# Euclidean distance (default)
reducer_euclidean = PaCMAPGPU(distance='euclidean')
X_euclidean = reducer_euclidean.fit_transform(X)

# Manhattan (L1) distance  
reducer_manhattan = PaCMAPGPU(distance='manhattan')
X_manhattan = reducer_manhattan.fit_transform(X)

# Cosine distance (for text/high-dimensional sparse data)
reducer_cosine = PaCMAPGPU(distance='cosine')
X_cosine = reducer_cosine.fit_transform(X)

# Hamming distance (for binary data)
X_binary = np.random.binomial(1, 0.5, size=(1000, 64)).astype(np.uint8)
reducer_hamming = PaCMAPGPU(distance='hamming')
X_hamming_embedded = reducer_hamming.fit_transform(X_binary)
```

### Out-of-Sample Embedding with Transform

```python
import numpy as np
from pacmap_gpu import PaCMAPGPU
from sklearn.model_selection import train_test_split

# Split data into training and test sets
X_train, X_test = train_test_split(X, test_size=0.2, random_state=42)

# NEW: Fit and Transform API
# Method 1: Use the standalone fit() method
reducer = PaCMAPGPU(
    n_components=2,
    distance='euclidean',
    random_state=42,
    verbose=True
)

# Fit the model (returns self for chaining)
reducer.fit(X_train)

# Get training data embedding
X_train_embedded = reducer.transform()  # or reducer.transform(None)

# Transform new/test data (much faster than refitting)
X_test_embedded = reducer.transform(X_test)

print(f"Training embedding: {X_train_embedded.shape}")
print(f"Test embedding: {X_test_embedded.shape}")

# Method 2: Method chaining (more concise)
X_test_embedded = (PaCMAPGPU(random_state=42)
                   .fit(X_train)
                   .transform(X_test))

# Method 3: Traditional fit_transform (still available)
X_train_embedded = reducer.fit_transform(X_train)
X_test_embedded = reducer.transform(X_test)
```

### NEW: Intermediate Snapshots for Analysis

```python
# Capture intermediate training states for analysis
reducer = PaCMAPGPU(
    n_components=2,
    num_iters=200,
    random_state=42,
    verbose=True
)

# Option 1: Get intermediate snapshots with default intervals
snapshots = reducer.fit_transform(X, intermediate=True)
print(f"Captured {len(snapshots)} snapshots during training")

# Option 2: Specify custom snapshot iterations
custom_iterations = [0, 25, 50, 100, 150, 200]
reducer.fit(X, intermediate=True, inter_snapshots=custom_iterations)
snapshots = reducer.intermediate_states_

# Analyze embedding evolution
import matplotlib.pyplot as plt

fig, axes = plt.subplots(2, 3, figsize=(15, 10))
axes = axes.ravel()

for i, (snapshot, iteration) in enumerate(zip(snapshots[:6], custom_iterations[:6])):
    axes[i].scatter(snapshot[:, 0], snapshot[:, 1], alpha=0.6)
    axes[i].set_title(f'Iteration {iteration}')
    axes[i].set_aspect('equal')

plt.tight_layout()
plt.show()
```

### NEW: Model Persistence (Save/Load)

```python
# Train and save a model
reducer = PaCMAPGPU(
    n_components=2,
    distance='cosine',
    num_iters=450,
    random_state=42
)

# Fit the model
embedding = reducer.fit_transform(X_train)

# Save the trained model
reducer.save('my_pacmap_model.pth')

# Later: Load the saved model
loaded_reducer = PaCMAPGPU.load('my_pacmap_model.pth')

# The loaded model preserves all state
print(f"Loaded model is fitted: {loaded_reducer.is_fitted}")
print(f"Model distance metric: {loaded_reducer.distance}")

# Get original training embedding
original_embedding = loaded_reducer.transform()  # Same as transform(None)

# Transform new data using the loaded model
new_embedding = loaded_reducer.transform(X_test)

# Load model on different device (if needed)
cpu_reducer = PaCMAPGPU.load('my_pacmap_model.pth', device='cpu')
```

### Advanced Configuration

```python
# High-performance configuration for large datasets
reducer = PaCMAPGPU(
    n_components=2,
    n_neighbors=15,
    MN_ratio=0.5,
    FP_ratio=2.0,
    distance='euclidean',        # Choose from: euclidean, manhattan, cosine, hamming
    num_iters=450,
    batch_size=8192,
    init_method="pca",
    optimizer="adam",
    lr=0.01,
    low_dist_thres=1.0,         # LocalMAP parameter
    device="cuda",
    verbose=True,
    random_state=42
)

X_embedded = reducer.fit_transform(X)
```

### Visualization Example

```python
import matplotlib.pyplot as plt
from sklearn.datasets import load_digits

# Load digits dataset
digits = load_digits()
X, y = digits.data, digits.target

# Apply PaCMAP-GPU with cosine distance (good for sparse features)
reducer = PaCMAPGPU(
    n_components=2, 
    distance='cosine',
    verbose=True, 
    random_state=42
)
X_embedded = reducer.fit_transform(X)

# Plot results
plt.figure(figsize=(10, 8))
scatter = plt.scatter(X_embedded[:, 0], X_embedded[:, 1], 
                     c=y, cmap='tab10', alpha=0.7)
plt.colorbar(scatter)
plt.title('Digits Dataset - PaCMAP-GPU Embedding (Cosine Distance)')
plt.xlabel('Component 1')
plt.ylabel('Component 2')
plt.show()
```

## 📊 Performance Benchmarks

### Speedup Comparison

Performance varies based on dataset size and GPU hardware. Below are typical dataset configurations for benchmarking:

| Dataset Size | Features | Use Case | Notes |
|-------------|----------|----------|-------|
| 1K samples  | 50       | Quick testing | May show minimal speedup due to GPU overhead |
| 5K samples  | 100      | Standard evaluation | ~2-5x speedup typical |  
| 10K samples | 200      | Medium datasets | ~5-15x speedup expected |
| 50K samples | 500      | Large-scale analysis | ~10-50x speedup possible |

*Actual performance depends on GPU model, CUDA version, and data characteristics*

### Distance Metric Performance

Different distance metrics have varying computational costs:

| Distance Metric | Use Case | Performance | Notes |
|----------------|----------|-------------|--------|
| Euclidean | General purpose | Fastest | Default choice |
| Manhattan | Robust to outliers | Fast | Good for high-dimensional data |
| Cosine | Text/sparse data | Moderate | Includes normalization overhead |
| Hamming | Binary data | Fast | Requires faiss-gpu-cu12, uint8 data |

**Run your own benchmarks:**
```bash
pytest tests/test_benchmarks.py -v -m benchmark
```

### Transform Method Benefits

The new transform method provides significant advantages:

- **Speed**: 5-10x faster than re-running fit_transform
- **Memory**: No need to store multiple full embeddings
- **Consistency**: New points placed relative to fixed embedding
- **Scalability**: Transform thousands of new points efficiently

```python
# Example timing comparison
import time

# Traditional approach: fit_transform on all data
start = time.time()
all_embedded = reducer.fit_transform(np.vstack([X_train, X_test]))
traditional_time = time.time() - start

# New approach: fit_transform + transform
start = time.time()
train_embedded = reducer.fit_transform(X_train)
test_embedded = reducer.transform(X_test)
new_approach_time = time.time() - start

print(f"Traditional: {traditional_time:.2f}s")
print(f"New approach: {new_approach_time:.2f}s")
print(f"Speedup: {traditional_time / new_approach_time:.1f}x")
```

### Memory Efficiency

- **PaCMAP-GPU**: ~2-4GB GPU memory for 50K samples (with CUDA graph optimizations)
- **Traditional Methods**: ~4-8GB RAM for 50K samples (varies by dimensionality)  
- **Automatic Cleanup**: Memory pools and CUDA graph optimization prevent OOM errors

## 🔍 API Reference

### PaCMAPGPU Class

```python
PaCMAPGPU(
    n_components=2,          # Output dimensions
    n_neighbors=None,        # Neighbors for local structure (adaptive if None)
    MN_ratio=0.5,           # Mid-near pairs ratio
    FP_ratio=2.0,           # Further pairs ratio
    distance='euclidean',   # Distance metric
    num_iters=450,          # Total optimization iterations
    batch_size=8192,        # Batch size for stochastic optimization
    init_method="pca",      # Initialization method
    optimizer="adam",       # Optimizer type
    lr=0.01,               # Learning rate
    device="cuda",         # CUDA device
    low_dist_thres=1.0,    # LocalMAP distance threshold
    random_state=None,     # Random seed
    verbose=False          # Print progress
)
```

#### Adaptive Parameter Selection

**`n_neighbors`** parameter now supports adaptive selection:

- **`None` (default)**: Automatically selects optimal value based on dataset size:
  - Small datasets (≤10,000 samples): `n_neighbors = 10`
  - Large datasets (>10,000 samples): `n_neighbors = 10 + 15 × (log₁₀(n_samples) - 4)`
- **Integer value**: Uses the specified value, overriding adaptive selection
- **Validation**: Automatically validates against dataset constraints and issues warnings if adjustments are needed

#### Distance Metrics

- **`euclidean`**: Standard L2 distance (default)
- **`manhattan`**: L1/taxicab distance, robust to outliers
- **`cosine`**: Cosine similarity, good for high-dimensional sparse data
- **`hamming`**: Hamming distance for binary data (requires faiss-gpu-cu12)

#### Key Methods

**Training Methods:**
- `fit(X, intermediate=False, inter_snapshots=None)`: **NEW!** Primary training method, returns self for chaining
- `fit_transform(X, intermediate=False, inter_snapshots=None)`: Convenience method that fits and returns embedding/snapshots

**Transform Methods:**
- `transform(X_new=None)`: **UPDATED!** Dual functionality:
  - `transform()` or `transform(None)`: Returns training data embedding
  - `transform(X_new)`: Out-of-sample projection for new data

**Model Persistence:**
- `save(file_path)`: **NEW!** Save trained model to disk
- `PaCMAPGPU.load(file_path, device=None)`: **NEW!** Load model from disk (class method)

**Intermediate Snapshots:**
- When `intermediate=True` in `fit()` or `fit_transform()`:
  - `inter_snapshots`: List of iteration numbers to capture (default: every 5% of training)
  - Returns list of numpy arrays (snapshots) instead of single embedding
  - Access via `model.intermediate_states_` after fitting

#### Attributes

- `embedding_`: Final embedding coordinates (on GPU)
- `X_original_`: Original training data (preprocessed, on GPU)
- `preprocessing_stats_`: Preprocessing statistics for transform consistency
- `is_fitted`: Whether model has been fitted
- `n_samples`: Number of training samples
- `intermediate_states_`: **NEW!** List of intermediate embeddings (when requested)
- `_n_neighbors_final`: **NEW!** Final validated number of neighbors used (set after fit)
- `_n_mn_pairs_final`: **NEW!** Final number of mid-near pairs used (set after fit)
- `_n_fp_pairs_final`: **NEW!** Final number of further pairs used (set after fit)

#### Method Chaining

```python
# NEW: Fluent API support
embedding = (PaCMAPGPU(random_state=42)
             .fit(X_train)
             .transform(X_test))

# Equivalent to:
model = PaCMAPGPU(random_state=42)
model.fit(X_train)
embedding = model.transform(X_test)
```

### Utility Functions

```python
from pacmap_gpu import check_gpu_availability, get_gpu_info, clear_gpu_memory

# Check GPU status
gpu_available = check_gpu_availability()
gpu_info = get_gpu_info()

# Memory management
clear_gpu_memory()

# Installation verification
from pacmap_gpu import verify_installation, print_installation_report
print_installation_report()
```

## ⚡ Performance Tips

### Adaptive Parameter Selection
- **Use default `n_neighbors=None`**: Let PaCMAP-GPU automatically select optimal values
- **For experimentation**: Override with explicit values only when needed
- **Check final values**: Use `reducer._n_neighbors_final` to see what was selected
- **Large datasets**: Adaptive selection prevents over-parameterization automatically

### Distance Metric Selection
- **Euclidean**: Best general performance and quality
- **Manhattan**: Use when data has many outliers or dimensions have different scales
- **Cosine**: Ideal for text data, word embeddings, or sparse features
- **Hamming**: Only for binary data, requires data as uint8 arrays

### Dataset Size Guidelines
- **Small datasets (< 1K)**: GPU overhead may impact performance, but still required
- **Medium datasets (1K-10K)**: GPU shows significant speedup over traditional CPU methods  
- **Large datasets (10K+)**: Maximum GPU performance advantage

### Transform Method Optimization
```python
# For multiple batches of new data, reuse the same fitted model
reducer = PaCMAPGPU(distance='euclidean', random_state=42)
train_embedding = reducer.fit_transform(X_train)

# Transform multiple batches efficiently
for batch in new_data_batches:
    batch_embedding = reducer.transform(batch)
    process_batch_results(batch_embedding)
```

### Memory Optimization
```python
from pacmap_gpu import clear_gpu_memory

# Clear memory between runs
clear_gpu_memory()

# Use efficient data types
X = X.astype(np.float32)  # Use float32 instead of float64

# For binary data with Hamming distance
X_binary = X_binary.astype(np.uint8)  # Required for Hamming
```

### Hardware Optimization
- Use modern GPUs with Tensor Cores (RTX series, V100, A100)
- Ensure sufficient GPU memory (8GB+ for datasets > 50K samples)
- Use CUDA 12.0+ for best performance
- Consider batch_size parameter based on GPU memory

## 🧪 Running Tests and Benchmarks

```bash
# Install development dependencies
pip install -e ".[dev]"

# Run basic tests
pytest tests/ -v

# Run GPU tests (requires GPU)
pytest tests/ -v -m gpu

# Run benchmarks including new features
pytest tests/test_benchmarks.py -v -m benchmark

# Test distance metrics
pytest tests/test_core.py::TestPaCMAPGPUDistanceMetrics -v

# Test transform method
pytest tests/test_core.py::TestPaCMAPGPUTransformMethod -v

# Run without slow tests
pytest tests/ -v -m "not slow"
```

### Example Benchmark Output

```bash
pytest tests/test_benchmarks.py::TestPerformanceBenchmarks::test_distance_metrics_performance -v -s

=== EUCLIDEAN Distance Metric Benchmark ===
Dataset shape: (2000, 50)
Time: 1.8432s
Time per sample: 0.92ms

=== Transform vs Fit-Transform Speed Comparison ===
Training samples: 1600
Test samples: 400  
Fit-transform time: 2.1043s
Transform time: 0.3847s
Transform speedup: 5.47x
```

## 🐛 Troubleshooting

### Common Issues

1. **CUDA Not Found**
   ```bash
   # Check CUDA installation
   nvidia-smi
   python -c "import torch; print(torch.cuda.is_available())"
   ```

2. **Faiss Not Available (for Hamming distance)**
   ```bash
   # Install faiss-gpu-cu12
   conda install -c pytorch faiss-gpu-cu12
   # Or with pip
   pip install faiss-gpu-cu12
   ```

3. **Distance Metric Errors**
   ```python
   # Ensure correct data type for Hamming distance
   X_binary = X_binary.astype(np.uint8)
   
   # Check available metrics
   from pacmap_gpu import PaCMAPGPU
   print("Available distances: euclidean, manhattan, cosine, hamming")
   ```

4. **Transform Method Errors**
   ```python
   # Ensure model is fitted before transform
   reducer = PaCMAPGPU()
   reducer.fit_transform(X_train)  # Must call this first
   X_new_embedded = reducer.transform(X_new)  # Now this works
   
   # Check feature count compatibility
   print(f"Training features: {reducer.X_original_.shape[1]}")
   print(f"New data features: {X_new.shape[1]}")  # Must match
   ```

5. **Out of Memory Error**
   ```python
   # Reduce batch size or data size
   reducer = PaCMAPGPU(batch_size=2048, n_neighbors=5)
   
   # Or clear GPU memory
   from pacmap_gpu import clear_gpu_memory
   clear_gpu_memory()
   ```

6. **Import Errors**
   ```bash
   # Verify installation
   pacmap-gpu-check
   
   # Reinstall if needed
   pip install --force-reinstall pacmap-gpu
   ```

### Performance Issues

- **Slow on small datasets**: GPU overhead exists, but GPU is still required for functionality
- **GPU underutilized**: Increase dataset size or batch parameters
- **Memory warnings**: Monitor GPU memory and clear caches regularly
- **Transform slower than expected**: Check that you're not refitting the model

## 🤝 Contributing

We welcome contributions! Please follow these steps:

1. Fork the repository
2. Create a feature branch (`git checkout -b feature/amazing-feature`)
3. Make changes and add tests
4. Run the test suite (`pytest tests/`)
5. Commit changes (`git commit -m 'Add amazing feature'`)
6. Push to branch (`git push origin feature/amazing-feature`)
7. Open a Pull Request

### Development Setup

```bash
# Clone repository
git clone https://github.com/AntonioRoye/PaCMAP-GPU.git
cd PaCMAP-GPU

# Set up conda environment with all dependencies (recommended)
conda create -n pacmap-gpu-dev -c rapidsai -c conda-forge \
    cuml-cu12 cudf-cu12 cupy-cuda12x faiss-gpu-cu12 \
    python=3.10 pytorch pytorch-cuda=12.1 -c pytorch -c nvidia
conda activate pacmap-gpu-dev

# Install package and development tools
pip install -e ".[dev]"

# Or alternatively, install from requirements file + dev extras
# pip install -r requirements-cuda12.txt
# pip install -e ".[dev]"

# Run tests
pytest tests/

# Run GPU-specific tests (requires CUDA)
pytest tests/ -m gpu

# Run benchmarks including new features
pytest tests/test_benchmarks.py -m benchmark

# Format code
black source/

# Type checking
mypy source/
```

## 📚 Citation

If you use PaCMAP-GPU in your research, please cite:

```bibtex
@article{JMLR:v22:20-1061,
  author  = {Yingfan Wang and Haiyang Huang and Cynthia Rudin and Yaron Shaposhnik},
  title   = {Understanding How Dimension Reduction Tools Work: An Empirical Approach to Deciphering t-SNE, UMAP, TriMap, and PaCMAP for Data Visualization},
  journal = {Journal of Machine Learning Research},
  year    = {2021},
  volume  = {22},
  number  = {201},
  pages   = {1-73},
  url     = {http://jmlr.org/papers/v22/20-1061.html}
}

@software{pacmap_gpu2025,
  title={PaCMAP-GPU: GPU-Accelerated Pairwise Controlled Manifold Approximation},
  author={Antonio Roye-Azar},
  year={2025},
  url={https://github.com/AntonioRoye/PaCMAP-GPU}
}
```

## 📄 License

This project is licensed under the Apache License 2.0 - see the [LICENSE](LICENSE) file for details.

## 🔗 Links

- **Original PaCMAP**: [https://github.com/YingfanWang/PaCMAP](https://github.com/YingfanWang/PaCMAP)
- **PyTorch**: [https://pytorch.org/](https://pytorch.org/)
- **RAPIDS cuML**: [https://docs.rapids.ai/api/cuml/stable/](https://docs.rapids.ai/api/cuml/stable/)
- **Faiss**: [https://faiss.ai/](https://faiss.ai/)
- **Issues**: [https://github.com/AntonioRoye/PaCMAP-GPU/issues](https://github.com/AntonioRoye/PaCMAP-GPU/issues)

## 🙏 Acknowledgments

- Original PaCMAP authors for the algorithm and reference implementation
- RAPIDS team for GPU acceleration libraries
- PyTorch team for the deep learning framework
- Faiss team for efficient similarity search and clustering
- NVIDIA for CUDA and GPU computing platform

---

**Made with ❤️ for the GPU computing community**