diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 00000000..9c2333ea --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,60 @@ +name: Docs + +on: + push: + branches: [main] + paths: + - "docs/**" + - "mkdocs.yml" + - ".github/workflows/docs.yml" + workflow_dispatch: + +# Allow only one concurrent deployment, and let in-progress runs finish. +concurrency: + group: pages + cancel-in-progress: false + +permissions: + contents: read + pages: write + id-token: write + +jobs: + build: + name: Build site + runs-on: ubuntu-latest + steps: + - name: Check out repo + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.12" + + - name: Install MkDocs + run: | + python -m pip install -U pip + python -m pip install \ + "mkdocs-material>=9.5" \ + "pymdown-extensions>=10" + + - name: Build (strict) + run: mkdocs build --strict + + - name: Upload Pages artifact + uses: actions/upload-pages-artifact@v3 + with: + path: site + + deploy: + name: Deploy to GitHub Pages + needs: build + runs-on: ubuntu-latest + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + steps: + - name: Deploy + id: deployment + uses: actions/deploy-pages@v4 diff --git a/.gitignore b/.gitignore index 7c38440c..8cd4fa79 100644 --- a/.gitignore +++ b/.gitignore @@ -23,3 +23,6 @@ tools/NNS/src/*.o # Stray R plotting artifact from headless regeneration Rplots.pdf + +# MkDocs build output +/site/ diff --git a/README.md b/README.md index 5a179925..8e18db15 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,7 @@ [![PyPI package](https://img.shields.io/badge/package-ovvo--nns-blue)](https://pypi.org/project/ovvo-nns/) [![Python](https://img.shields.io/badge/python-3.11%2B-blue)](https://www.python.org/) +[![Docs](https://img.shields.io/badge/docs-ovvo--financial.github.io-blue)](https://ovvo-financial.github.io/NNS-python/) [![License](https://img.shields.io/badge/license-GPL--3.0--only-blue)](LICENSE) `ovvo-nns` brings Nonlinear Nonparametric Statistics to Python as the `nns` import package. It is a parity-focused port of the R `NNS` 13.0+ package, designed for real-world data that violate symmetry, linearity, or distributional assumptions. @@ -178,6 +179,9 @@ uv run python examples/run_all_vignettes.py ## Documentation +The full documentation site is hosted at +****. + - [API reference manual](docs/api_reference.md) - [API status and known gaps](docs/api_status.md) - [Behavior conventions and intentional divergences](docs/conventions.md) diff --git a/docs/assets/nns_hex_sticker.png b/docs/assets/nns_hex_sticker.png new file mode 100644 index 00000000..a0271fb3 Binary files /dev/null and b/docs/assets/nns_hex_sticker.png differ diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 00000000..43cf29f2 --- /dev/null +++ b/docs/index.md @@ -0,0 +1,119 @@ +NNS hex sticker + +[![PyPI package](https://img.shields.io/pypi/v/ovvo-nns?label=ovvo-nns&color=2780e3)](https://pypi.org/project/ovvo-nns/) +[![Python](https://img.shields.io/badge/python-3.11%2B-2780e3)](https://www.python.org/) +[![License](https://img.shields.io/badge/license-GPL--3.0--only-2780e3)](https://github.com/OVVO-Financial/NNS-python/blob/main/LICENSE) + +# NNS Python + +`ovvo-nns` brings **Nonlinear Nonparametric Statistics** to Python as the `nns` +import package. It is a parity-focused port of the R `NNS` 13.0+ package, +designed for real-world data that violate symmetry, linearity, or distributional +assumptions. + +NNS is built around partial moments — the lower and upper components of variance — +and uses them across nonlinear dependence, correlation, causation, regression, +classification, forecasting, stochastic dominance, stochastic superiority, Monte +Carlo simulation, and numerical differentiation workflows. + +!!! note "Origin" + NNS was created by Fred Viole as the companion R package to Viole, F. and + Nawrocki, D. (2013), *Nonlinear Nonparametric Statistics: Using Partial + Moments*. **Book (2nd Edition):** . + For a direct quantitative finance implementation of NNS, see + [OVVO Labs](https://www.ovvolabs.com). + +## Package at a glance + +| Item | Value | +|---|---| +| Distribution package | `ovvo-nns` | +| Import package | `nns` | +| Python | `>=3.11` | +| Required runtime dependencies | NumPy, SciPy | +| R required at runtime | No | +| Native acceleration | Private, optional `nns._nnscore` kernels where available | +| Public API status | Stable, parity-focused | +| License | GPL-3.0-only | + +The public package is Python-native and does not call R at runtime. Some core +kernels can use the private `_nnscore` extension when it is present, while public +functions keep Python implementations and explicit fallback behavior. + +## Get started + +
+ +- :material-download: **[Install](install.md)** + + `pip install ovvo-nns`, then `import nns`. + +- :material-rocket-launch: **[Quick start](quick_start.md)** + + Partial moments, dependence, regression, and forecasting in a few lines. + +- :material-book-open-variant: **[API reference](api_reference.md)** + + Function-by-function index with R `NNS` name crosswalks. + +- :material-check-decagram: **[API status](api_status.md)** + + Implemented, partial, guarded, and known-gap paths. + +
+ +## Main API areas + +| Area | Representative functions | +|---|---| +| Partial moments | `lpm`, `upm`, `lpm_ratio`, `upm_ratio`, `pm_matrix` | +| Classical moment helpers | `mean_pm`, `var_pm`, `skew_pm`, `kurt_pm`, `nns_moments` | +| Dependence, correlation, copula | `nns_dep`, `nns_cor`, `nns_copula` | +| Causation | `nns_causation`, `causal_matrix` | +| Regression and classification | `nns_reg`, `nns_m_reg`, `nns_stack`, `nns_boost` | +| Forecasting | `nns_seas`, `nns_arma`, `nns_arma_optim`, `nns_var` | +| Distribution tools | `nns_cdf`, `nns_anova`, `nns_norm` | +| Stochastic dominance | `fsd`, `ssd`, `tsd`, `nns_sd_cluster`, `sd_efficient_set` | +| Stochastic superiority and simulation | `nns_ss`, `nns_mc`, `nns_meboot` | +| Differentiation | `nns_diff`, `dy_dx`, `dy_d` | +| Categorical helpers | `encode_factor_codes`, `factor_2_dummy`, `factor_2_dummy_fr`, `prepare_factor_predictors` | + +See [API status](api_status.md) for implemented, partial, guarded, and known-gap +paths. + +## Design boundaries + +NNS Python prioritizes stable public behavior from installed R NNS 13.0+, not +private helper parity. The package returns NumPy arrays and plain dictionaries +rather than R `data.table` objects, uses explicit Python errors for several +unsafe R coercions, and generally ignores plotting side effects. + +See [behavior conventions](conventions.md) for detailed compatibility notes and +[parity with R NNS](parity.md) for the parity target and automation. + +## Links + +- [View on PyPI](https://pypi.org/project/ovvo-nns/) +- [Browse source code](https://github.com/OVVO-Financial/NNS-python) +- [Report a bug](https://github.com/OVVO-Financial/NNS-python/issues) + +## License + +[GPL-3.0-only](https://www.gnu.org/licenses/gpl-3.0.en.html) + +## Citation + +NNS is the companion to Viole, F. and Nawrocki, D. (2013), +*Nonlinear Nonparametric Statistics: Using Partial Moments* (ISBN: 1490523995). +2nd edition: . + +## Developers + +- **Fred Viole** — author and maintainer +- **Roberto Spadim** — contributor +- **Rasheed Khoshnaw** — contributor + +## Attribution + +Upstream R package and reference implementation: +[OVVO-Financial/NNS](https://github.com/OVVO-Financial/NNS). diff --git a/docs/install.md b/docs/install.md new file mode 100644 index 00000000..e3adc7aa --- /dev/null +++ b/docs/install.md @@ -0,0 +1,55 @@ +# Install + +## From PyPI + +```bash +pip install ovvo-nns +``` + +The distribution package is **`ovvo-nns`**; the import package is **`nns`**: + +```python +import nns + +print(nns.__version__) +``` + +Installing `ovvo-nns` includes the Matplotlib plotting API (`nns.plotting`). +Matplotlib is a regular dependency and is imported lazily, so `import nns` stays +light. See the [plot parity policy](plot_parity_policy.md) for details. + +## Requirements + +| Requirement | Value | +|---|---| +| Python | `>=3.11` (CPython 3.11, 3.12, 3.13, 3.14) | +| Runtime dependencies | NumPy, SciPy, Matplotlib | +| R at runtime | Not required | + +R is used only for parity tests and local cache regeneration, never at normal +runtime. + +## Wheels vs. source builds + +Published wheels should be preferred when available. They ship the optional +private native extension (`nns._nnscore`) prebuilt for supported platforms. + +Source builds compile the optional native extension with +[`scikit-build-core`](https://scikit-build-core.readthedocs.io/) and +[`nanobind`](https://nanobind.readthedocs.io/), which require a C++17 toolchain. +Public APIs keep Python implementations and explicit fallback behavior, so the +native extension remains a private, benchmark-backed implementation detail rather +than a public API. + +## Development install + +```bash +uv sync --group dev +uv run pytest +uv run ruff check . +uv run mypy +``` + +The default parity suite is cache-backed and does not require `Rscript`. +`Rscript` and the R `NNS` package are needed only when regenerating parity caches +or running live R comparison scripts. diff --git a/docs/quick_start.md b/docs/quick_start.md new file mode 100644 index 00000000..eb44605e --- /dev/null +++ b/docs/quick_start.md @@ -0,0 +1,75 @@ +# Quick start + +These snippets mirror the runnable +[example vignettes](https://github.com/OVVO-Financial/NNS-python/tree/main/examples/vignettes), +which are exercised in CI so they stay in sync with the package. + +## Partial moments + +```python +import numpy as np +from nns import lpm, upm + +x = np.array([-2.0, -1.0, 0.5, 3.0], dtype=np.float64) + +lower = lpm(degree=2, target=0.0, x=x) +upper = upm(degree=2, target=0.0, x=x) + +print("lower partial moment:", lower) +print("upper partial moment:", upper) +``` + +## Nonlinear dependence + +```python +import numpy as np +from nns import nns_cor, nns_dep + +grid = np.linspace(-2.0, 2.0, 80, dtype=np.float64) +y = grid**2 + +print("NNS dependence:", nns_dep(grid, y)) +print("NNS correlation:", nns_cor(grid, y)) +``` + +## Nonlinear regression + +Fit a nonlinear regression and estimate new points: + +```python +import numpy as np +from nns import nns_reg + +x = np.linspace(-3.0, 3.0, 80, dtype=np.float64) +y = np.sin(x) + 0.2 * x +points = np.array([-1.5, 0.0, 1.5], dtype=np.float64) + +fit = nns_reg(x, y, point_est=points, confidence_interval=None) + +print("R2:", fit["R2"]) +print(np.column_stack((points, fit["Point.est"]))) +``` + +## Forecasting + +Forecast a univariate series: + +```python +import numpy as np +from nns import nns_arma, nns_seas + +t = np.arange(1, 60, dtype=np.float64) +series = 10.0 + np.sin(t / 3.0) + 0.05 * t + +seasonality = nns_seas(series, modulo=[3, 4, 6], mod_only=True) +forecast = nns_arma(series, h=3, seasonal_factor=4, method="lin") + +print("best seasonal period:", seasonality["best.period"]) +print("forecast:", forecast) +``` + +## Next steps + +- Browse the full [API reference](api_reference.md). +- Check the [API status](api_status.md) for partial, guarded, and known-gap paths. +- Read the [behavior conventions](conventions.md) for intentional divergences from R. diff --git a/docs/release_notes.md b/docs/release_notes.md new file mode 100644 index 00000000..ec702f61 --- /dev/null +++ b/docs/release_notes.md @@ -0,0 +1,31 @@ +# Release notes + +The authoritative, per-version release history is published to: + +- **PyPI release history:** +- **GitHub Releases:** + +The currently published version is shown by the badge on the +[home page](index.md) and by: + +```python +import nns + +print(nns.__version__) +``` + +## Versioning + +`ovvo-nns` uses standard semantic-style version numbers (`MAJOR.MINOR.PATCH`). +The public API is **stable and parity-focused**: documented public behavior is +not expected to break across minor releases. Known partial, guarded, and +known-gap paths are tracked on the [API status](api_status.md) page, and +intentional divergences from R `NNS` are recorded in the +[conventions](conventions.md). + +Version numbers are kept consistent across `pyproject.toml`, the package +`__version__`, and the README. This is enforced in CI by +`scripts/check_version_consistency.py`. + +For how releases are built, signed, and published, see the +[release process](releasing.md). diff --git a/docs/stylesheets/cosmo.css b/docs/stylesheets/cosmo.css new file mode 100644 index 00000000..480c4ccd --- /dev/null +++ b/docs/stylesheets/cosmo.css @@ -0,0 +1,77 @@ +/* + * Cosmo-flavored theme overrides for Material for MkDocs. + * + * Goal: match the R NNS pkgdown site, which uses Bootstrap 5 with the + * Bootswatch "Cosmo" theme — a flat design with a blue (#2780e3) primary, + * a solid-blue top navbar, the Source Sans typeface, and squared corners. + */ + +/* ---- Light scheme: Cosmo palette ------------------------------------- */ +[data-md-color-scheme="default"] { + --md-primary-fg-color: #2780e3; + --md-primary-fg-color--light: #5a9ee9; + --md-primary-fg-color--dark: #1f6ec1; + --md-primary-bg-color: #ffffff; + --md-primary-bg-color--light: rgba(255, 255, 255, 0.7); + + --md-accent-fg-color: #1f6ec1; + + /* Cosmo body text + links */ + --md-typeset-color: #373a3c; + --md-typeset-a-color: #2780e3; + + /* Cosmo semantic accents (admonitions, etc.) */ + --md-code-hl-color: rgba(39, 128, 227, 0.15); +} + +/* ---- Dark scheme: keep the Cosmo blue identity ----------------------- */ +[data-md-color-scheme="slate"] { + --md-primary-fg-color: #2780e3; + --md-primary-fg-color--light: #5a9ee9; + --md-primary-fg-color--dark: #1f6ec1; + --md-accent-fg-color: #5a9ee9; + --md-typeset-a-color: #5a9ee9; +} + +/* ---- Flat, squared corners (Cosmo is flat) --------------------------- */ +.md-typeset pre > code, +.md-typeset code, +.md-typeset .highlight, +.md-typeset .admonition, +.md-typeset details, +.md-typeset table:not([class]), +.md-typeset .md-button, +.md-search__form, +.md-search__output { + border-radius: 0 !important; +} + +/* Cosmo navbar feel: tighter, uppercase-ish weight on the top tabs */ +.md-tabs { + font-weight: 600; +} + +/* Source Sans renders a touch light; nudge headings bolder like Cosmo */ +.md-typeset h1, +.md-typeset h2, +.md-typeset h3 { + font-weight: 700; + letter-spacing: -0.01em; +} + +/* Cosmo-style primary buttons (flat, solid blue) */ +.md-typeset .md-button--primary { + background-color: var(--md-primary-fg-color); + border-color: var(--md-primary-fg-color); + color: #fff; +} +.md-typeset .md-button--primary:hover { + background-color: var(--md-primary-fg-color--dark); + border-color: var(--md-primary-fg-color--dark); +} + +/* Cosmo navbar-brand: bold, slightly larger package name */ +.md-header__title { + font-weight: 700; + font-size: 1.05rem; +} diff --git a/mkdocs.yml b/mkdocs.yml new file mode 100644 index 00000000..178392c8 --- /dev/null +++ b/mkdocs.yml @@ -0,0 +1,93 @@ +site_name: NNS Python +site_description: Nonlinear Nonparametric Statistics for Python — a parity-focused port of R NNS. +site_url: https://ovvo-financial.github.io/NNS-python/ +site_author: Fred Viole + +repo_name: OVVO-Financial/NNS-python +repo_url: https://github.com/OVVO-Financial/NNS-python +edit_uri: edit/main/docs/ + +copyright: Developed by Fred Viole. + +theme: + name: material + # pkgdown's navbar uses a text brand ("NNS") with the hex sticker shown in + # the home page body, so we keep the header text-only and place the sticker + # on the home page instead of using it as a header logo. + favicon: assets/nns_hex_sticker.png + # Match the R NNS pkgdown site (Bootstrap 5 + Bootswatch "Cosmo"): + # flat design, a solid blue top navbar, and the Source Sans typeface. + font: + text: Source Sans 3 + code: Source Code Pro + features: + - navigation.instant + - navigation.tracking + - navigation.tabs + - navigation.tabs.sticky + - navigation.top + - toc.follow + - content.code.copy + - content.action.edit + - search.suggest + - search.highlight + palette: + - media: "(prefers-color-scheme: light)" + scheme: default + toggle: + icon: material/weather-night + name: Switch to dark mode + - media: "(prefers-color-scheme: dark)" + scheme: slate + toggle: + icon: material/weather-sunny + name: Switch to light mode + icon: + repo: fontawesome/brands/github + +extra_css: + - stylesheets/cosmo.css + +plugins: + - search + +markdown_extensions: + - admonition + - attr_list + - md_in_html + - tables + - toc: + permalink: true + - pymdownx.highlight: + anchor_linenums: true + - pymdownx.inlinehilite + - pymdownx.snippets + - pymdownx.superfences + - pymdownx.details + +extra: + social: + - icon: fontawesome/brands/github + link: https://github.com/OVVO-Financial/NNS-python + - icon: fontawesome/brands/python + link: https://pypi.org/project/ovvo-nns/ + +# Pages that exist in docs/ but are surfaced as sub-pages of the nav below. +# Keep this nav aligned with README "Documentation" links. +nav: + - Home: index.md + - Install: install.md + - Quick start: quick_start.md + - API reference: api_reference.md + - API status: api_status.md + - Conventions: + - Behavior conventions: conventions.md + - Plot parity policy: plot_parity_policy.md + - Parity with R NNS: + - Parity overview: parity.md + - Sync contract: sync_contract.md + - Sync status: sync_status.md + - Benchmarks: benchmarks.md + - Release notes: + - Release notes: release_notes.md + - Release process: releasing.md diff --git a/pyproject.toml b/pyproject.toml index f57dd863..518a3dbe 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -48,6 +48,10 @@ dev = [ "pytest-xdist>=3.8.0", "ruff", ] +docs = [ + "mkdocs-material>=9.5", + "pymdown-extensions>=10", +] [build-system] requires = ["scikit-build-core", "nanobind"]