Simplex-structured matrix factorization and polyhedral facet extraction in Python.
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/ScenarioMatrixgrids, 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.
- Getting started — installation and first result
- Algorithms — methods and FPI extension point
- Datasets — bundled data and attribution
- Scenarios — research generation grids and persistence
- Evaluation — multi-scenario execution and artifacts
- Examples — runnable scripts and workflows
- Notebooks — guided, interactive workflows
- API guide — public namespaces and contracts
- Troubleshooting — setup and numerical issues
- Contributing and publishing
Polyfacet requires Python 3.11 or newer. Choose one installation path.
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)"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 installRun 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.pyfrom 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))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)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()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()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())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.ipynbAll 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.
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.
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.
# 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.pySee CONTRIBUTING.md for the complete contribution workflow.
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.