Skip to content

Repository files navigation

Polyfacet

Polyfacet logo

Simplex-structured matrix factorization and polyhedral facet extraction in Python.

What is Polyfacet?

Polyfacet is a research-oriented Python library for factorizing matrices whose columns follow simplex structure. It brings facet-based identification (FPI) and complementary factorization methods under one stable interface, so methods can be tried on the same input and inspected with the same result vocabulary.

It includes:

  • Eight bundled algorithms: GFPI, EMFPI, MBFPI, SOFPI, SNPA, HyperCSI, MinVolNMF, and MVIE.
  • Reproducible synthetic simplex data, persisted Scenario/ScenarioMatrix grids, reconstruction and spectral-angle metrics, and comparison figures.
  • A typed evaluation runner with fresh algorithm factories, failure isolation, summaries, and reloadable JSON/NPZ estimates.
  • Bundled Samson, Jasper Ridge, and Moffett Field hyperspectral scenes.
  • Runnable scripts and eight notebooks, including scenario persistence, a two-algorithm visualization workflow, and an all-algorithm Moffett demo.

Every algorithm accepts a feature-by-sample matrix X.shape == (n_features, n_samples) and returns factor matrices W and H. Polyfacet does not make comparative performance claims; the provided examples are starting points for inspection on data relevant to your work.

Documentation

Installation

Polyfacet requires Python 3.11 or newer. Choose one installation path.

Install the Python package

Use this when you want to use a released Polyfacet version in an existing uv project. It records Polyfacet as a runtime dependency and installs it into the project environment:

# Add the latest published Polyfacet release to the current uv project.
uv add polyfacet

# Verify the package and its public API in the project environment.
uv run python -c "from polyfacet.data import Scenario; print(Scenario(seed=1).data.X.shape)"

Install from source

Use this when you want notebooks, examples, tests, documentation, or contributor tooling. uv creates the project environment from the locked dependencies:

# Clone the complete source repository.
git clone https://github.com/crogs-foundation/polyfacet.git
cd polyfacet

# Install the package in editable mode with notebook and test tools.
uv sync --group dev

# Optional: enable the repository quality checks for future commits.
uv run pre-commit install

Run one of the source examples to verify the checkout:

# Generate a rich synthetic scenario and factorize it with SO-FPI.
uv run python examples/basic_factorization.py

Useful examples

1. Generate data and factorize it

from polyfacet.algorithms import SOFPI
from polyfacet.data import PointsDistribution, Scenario
from polyfacet.metrics import reconstruction_error

scenario = Scenario(
    rank=3,
    m=6,
    snr=40.0,
    facet_points=PointsDistribution(n_points=70),
    interior_points=PointsDistribution(n_points=90),
    seed=7,
)
scenario.plot()

# Estimate three factors from the feature-by-sample matrix.
result = SOFPI().run(scenario.data.X, target_dim=scenario.rank, rank_X=scenario.data.r)

# Assess reconstruction for this generated instance.
print(reconstruction_error(scenario.data.X, result.W, result.H))

2. Run several algorithms through the same interface

from polyfacet.algorithms import EMFPI, SOFPI
from polyfacet.data import Scenario

# Use one input and rank for a qualitative side-by-side inspection.
scenario = Scenario(rank=3, m=6, seed=2)
methods = (EMFPI(), SOFPI())

for method in methods:
    # Every method returns W, H, and a common convergence indicator.
    result = method.run(scenario.data.X, target_dim=scenario.rank, rank_X=scenario.data.r)
    print(method.name, result.W.shape, result.H.shape, result.converged)

3. Load and visualize a bundled scene

from polyfacet.datasets import load_jasper
from polyfacet.visualization import plot_image

# Jasper Ridge supplies spatial dimensions and bundled reference factors.
scene = load_jasper()

# The helper converts three spectral bands into a displayable RGB-like image.
figure = plot_image(scene)
figure.show()

4. Compare estimated geometry

from polyfacet.algorithms import EMFPI, SOFPI
from polyfacet.data import Scenario
from polyfacet.visualization import plot_vertex_comparison

scenario = Scenario(rank=3, m=6, seed=3)

# Keep named results so the plot labels each method's estimated vertices.
results = {
    "SO-FPI": SOFPI().run(
        scenario.data.X, target_dim=scenario.rank, rank_X=scenario.data.r
    ),
    "EM-FPI": EMFPI().run(
        scenario.data.X, target_dim=scenario.rank, rank_X=scenario.data.r
    ),
}
figure = plot_vertex_comparison(scenario, results)
figure.show()

5. Create and persist a research scenario grid

from pathlib import Path

from polyfacet.data import PointsDistribution, ScenarioMatrix

# Expand every purity/trial combination into a concrete, deterministic scenario.
matrix = ScenarioMatrix(
    ranks=[3],
    purities=[0.7, 0.8],
    snr_values=[50.0],
    facet_points=PointsDistribution(n_points=12),
    interior_points=PointsDistribution(n_points=20),
    num_trials=2,
    seed=42,
    name="research_grid",
)

# Store readable parameters and exact generated matrices, then reload them.
path = matrix.save(Path("artifacts/scenarios/research_grid"))
restored = ScenarioMatrix.load(path)
print(restored.describe())

6. Evaluate algorithms over the grid

from polyfacet.algorithms import EMFPI, SOFPI
from polyfacet.evaluation import err, evaluate, reconstruction

# Factories guarantee fresh solver and extractor state for every scenario.
evaluation = evaluate(
    {"SO-FPI": SOFPI, "EM-FPI": EMFPI},
    restored,
    {"ERR": err, "reconstruction": reconstruction},
)

for summary in evaluation.summary():
    print(summary.algorithm, summary.metrics, summary.failures)

# Successful W/H estimates can be reloaded without rerunning algorithms.
evaluation.save("artifacts/evaluations/research_grid")

See examples/README.md for executable scripts and notebooks/README.md for a progressive notebook path. For the real-data all-method run, launch:

# The notebook continues if one method or solver fails.
uv run jupyter lab notebooks/05_moffett_all_algorithms.ipynb

Algorithms

All runnable algorithms are available from polyfacet.algorithms. Pluggable facet components are available from polyfacet.extractors; there is no generic public separation subsystem.

Family Methods
Facet-based GFPI, EMFPI, MBFPI, SOFPI
Other factorization methods SNPA, HyperCSI, MinVolNMF, MVIE

Every method offers run(X, target_dim=..., rank_X=...). Pass rank_X when the intrinsic or signal rank is known; noisy data can have a much larger numerical matrix rank. See the algorithm guide for options, assumptions, and FPI's custom facet-extractor extension point.

Bundled datasets

polyfacet.datasets ships Samson, Jasper Ridge, and Moffett Field inside the wheel. Samson and Jasper Ridge include reference factors; Moffett Field is a qualitative real-data example and has no bundled ground truth.

from polyfacet.datasets import load_moffett

# Select the target rank explicitly for Moffett Field.
scene = load_moffett(rank=3)
print(scene.name, scene.X.shape)

Provenance, checksums, preprocessing notes, and citations are maintained in src/polyfacet/datasets/REFERENCES.md.

Repository structure

polyfacet/
├── src/polyfacet/                 # Installable package
│   ├── algorithms/                # GFPI and non-FPI algorithm classes
│   ├── extractors/                # Pluggable FPI facet extractors and results
│   ├── data/                      # Generators, Scenario/ScenarioMatrix, artifacts
│   ├── datasets/                  # Samson, Jasper Ridge, Moffett loaders/data
│   ├── metrics/                   # Reconstruction and spectral-angle metrics
│   ├── evaluation/                # Typed scenario evaluation and persistence
│   ├── visualization/             # Geometry, metrics, heatmaps, figure export
│   ├── tuning/                    # Small Optuna tuning integration
│   └── logging.py                 # Explicit Loguru configuration
├── examples/                      # Short executable public-API workflows
├── notebooks/                     # Ordered, thin interactive demonstrations
├── docs/                          # Getting-started, API, dataset, and help guides
├── tests/                         # Public-contract and numerical smoke tests
├── publish/                       # Release helper and maintainer instructions
├── .github/workflows/             # Continuous integration and tag publishing
├── .pre-commit-config.yaml        # uv, hygiene, and Ruff commit hooks
├── README_PYPI.md                 # Compact description rendered on PyPI
├── pyproject.toml                 # Package metadata and dependencies
└── uv.lock                        # Reproducible dependency resolution

The design document records the package boundary and architectural decisions.

Development

# Lint and format checks.
uv run ruff check .
uv run ruff format --check .

# Static type checking.
uv run ty check

# Public-contract and dataset smoke tests.
uv run pytest

# Build the source distribution and wheel as a packaging check.
uv build --no-sources

# Exercise the installed wheel independently of the source checkout.
uv run --isolated --no-project --no-cache --with dist/*.whl tests/smoke_test.py

See CONTRIBUTING.md for the complete contribution workflow.

Citation and license

Polyfacet is distributed under the MIT License. For academic use, cite the software using CITATION.cff and cite the original publication for every algorithm used.

About

Algorithms for simplex-structured matrix factorization and polyhedral facet extraction

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages