diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 0000000..829f21c --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,60 @@ +name: Docs + +on: + push: + branches: [main] + workflow_dispatch: + +permissions: + contents: read + pages: write + id-token: write + +concurrency: + group: pages + cancel-in-progress: false + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-python@v5 + with: + python-version: '3.11' + cache: pip + cache-dependency-path: docs/requirements.txt + + - name: Install pandoc + run: sudo apt-get update && sudo apt-get install -y pandoc + + - name: Install Python deps + run: pip install -r docs/requirements.txt + + - name: Generate tutorial pages from .lhs + run: | + mkdir -p docs/mkdocs/docs/examples + for f in QueueModel OptionPricing GeneTranscription; do + pandoc --from=markdown+lhs --to=commonmark "tutorials/${f}.lhs" \ + -o "docs/mkdocs/docs/examples/${f}.md" + done + + - name: Build mkdocs site + run: cd docs/mkdocs && mkdocs build + + - uses: actions/configure-pages@v5 + + - uses: actions/upload-pages-artifact@v3 + with: + path: docs/mkdocs/site + + deploy: + needs: build + runs-on: ubuntu-latest + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + steps: + - id: deployment + uses: actions/deploy-pages@v4 diff --git a/.gitignore b/.gitignore index 656f417..7dea031 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,10 @@ stack.yaml.lock # Local dev config (kept locally, not shipped) .hlint.yaml +# mkdocs build outputs +docs/mkdocs/site/ +docs/mkdocs/docs/examples/*.md + # Python docs/.venv/ __pycache__/ diff --git a/README.md b/README.md index 16e9fc0..34890ce 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,19 @@ -

- GradInf Logo - GradInf Logo -

+
+ GradInf Logo + GradInf Logo +
+ # GradInf -This repository provides the Haskell implementation -of *gradient inference*, a new approach to gradient +GradInf is a research package that provides the Haskell implementation +of **gradient inference**, a new approach to gradient estimation described in [this PLDI'26 paper](https://dl.acm.org/doi/abs/10.1145/3808321). -**Polished documentation and tutorials will be added shortly!** +[![Build Status](https://github.com/probsys/grad-inf/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/probsys/grad-inf/actions/workflows/ci.yml) +[![](https://img.shields.io/badge/docs-main-blue.svg)](https://probsys.github.io/grad-inf/) +[![DOI](https://img.shields.io/badge/article-DOI%3A10.1145%2F3808321-B31B1B)](https://dl.acm.org/doi/abs/10.1145/3808321) ## Overview @@ -71,13 +74,11 @@ print gradientEstimate Here, `stratifiedImportanceResamplingInferenceAlg 1` is an *inference strategy* which enables sound and efficient gradient estimation. -See [Documentation](#documentation) for tutorials and details on the API. - [1] *Monte Carlo Gradient Estimation in Machine Learning*, Shakir Mohamed, Mihaela Rosca, Michael Figurnov, and Andriy Mnih. *Journal of Machine Learning Research* 21, 132 (2020), - -## Documentation +To learn more, check out the [documentation](https://probsys.github.io/grad-inf). +Additional tutorials and API details will be added shortly! -Documentation will be added shortly! + [1] *Monte Carlo Gradient Estimation in Machine Learning*, Shakir Mohamed, Mihaela Rosca, Michael Figurnov, and Andriy Mnih. *Journal of Machine Learning Research* 21, 132 (2020), ## Reproducible Artifact diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 0000000..a703d5a --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,26 @@ +SHELL := /bin/bash +ROOT := .. +TUTORIALS := $(ROOT)/tutorials +MKDOCS_DOCS := mkdocs/docs +EXAMPLES_DST := $(MKDOCS_DOCS)/examples +VENV := .venv + +TUTORIAL_NAMES := QueueModel OptionPricing GeneTranscription +TUTORIAL_OUTPUTS := $(addprefix $(EXAMPLES_DST)/, $(addsuffix .md, $(TUTORIAL_NAMES))) + +.PHONY: docs serve clean + +docs: $(TUTORIAL_OUTPUTS) + . $(VENV)/bin/activate && cd mkdocs && mkdocs build + +serve: $(TUTORIAL_OUTPUTS) + . $(VENV)/bin/activate && cd mkdocs && mkdocs serve + +$(EXAMPLES_DST)/%.md: $(TUTORIALS)/%.lhs | $(EXAMPLES_DST) + pandoc --from=markdown+lhs --to=commonmark $< -o $@ + +$(EXAMPLES_DST): + mkdir -p $@ + +clean: + rm -rf mkdocs/site $(EXAMPLES_DST)/*.md diff --git a/docs/assets/logo-white.png b/docs/assets/logo-white.png index 66e2802..ed5ce77 100644 Binary files a/docs/assets/logo-white.png and b/docs/assets/logo-white.png differ diff --git a/docs/assets/logo.png b/docs/assets/logo.png index b89cb03..dd02556 100644 Binary files a/docs/assets/logo.png and b/docs/assets/logo.png differ diff --git a/docs/mkdocs/docs/images/logo-white.png b/docs/mkdocs/docs/images/logo-white.png new file mode 120000 index 0000000..a3c773c --- /dev/null +++ b/docs/mkdocs/docs/images/logo-white.png @@ -0,0 +1 @@ +../../../assets/logo-white.png \ No newline at end of file diff --git a/docs/mkdocs/docs/images/logo.png b/docs/mkdocs/docs/images/logo.png new file mode 120000 index 0000000..ee728eb --- /dev/null +++ b/docs/mkdocs/docs/images/logo.png @@ -0,0 +1 @@ +../../../assets/logo.png \ No newline at end of file diff --git a/docs/mkdocs/docs/images/plot_MMC_queue_traces_raw-crop.png b/docs/mkdocs/docs/images/plot_MMC_queue_traces_raw-crop.png new file mode 100644 index 0000000..8d2f52b Binary files /dev/null and b/docs/mkdocs/docs/images/plot_MMC_queue_traces_raw-crop.png differ diff --git a/docs/mkdocs/docs/images/workflow-core.png b/docs/mkdocs/docs/images/workflow-core.png new file mode 120000 index 0000000..f8eb8b0 --- /dev/null +++ b/docs/mkdocs/docs/images/workflow-core.png @@ -0,0 +1 @@ +../../../assets/workflow-core.png \ No newline at end of file diff --git a/docs/mkdocs/docs/index.md b/docs/mkdocs/docs/index.md new file mode 100644 index 0000000..446c9bd --- /dev/null +++ b/docs/mkdocs/docs/index.md @@ -0,0 +1,97 @@ +

+ GradInf Logo +

+ +# GradInf: Gradient Estimation as Probabilistic Inference + +GradInf is a research package that provides the Haskell implementation +of *gradient inference*, a new approach to gradient +estimation described in +[this PLDI'26 paper](https://dl.acm.org/doi/abs/10.1145/3808321). + +## Overview + +

+ Core GradInf Workflow +

+ +GradInf automatically synthesizes gradient estimators +(i.e., estimators of gradients of expected values [1]) for +probabilistic programs. Users define a probabilistic +program using a mix of ordinary Haskell and library-provided +primitives. They then call `gradInfAD`, specifying their desired +probabilistic inference strategy. The result is an unbiased estimate of +the gradient of the expectation +of the original program with respect to its parameter. + +### Starter Example + +We can write a simple queuing model: +```haskell +{-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE RebindableSyntax #-} + +import Prelude hiding (flip) +import Numeric.GradInf.Primitives.DeterministicPrimitives +import Numeric.GradInf.Primitives.FlipCRN +import Numeric.GradInf.Primitives.IterateP + +queueKernel :: forall m d i b mat. + (DeterministicPrimitives d i b mat, FlipCRN m d b) + => d -> i -> m i +queueKernel theta x = do + let p = theta / (theta + if (isGreater x 25) :: b then 25 else toDouble x) + b :: b <- flipCRN p + let x' = if b then x + 1 else x - 1 + return x' + +queueModel :: forall m d i b mat. + (DeterministicPrimitives d i b mat, FlipCRN m d b, IterateP m i) + => Int -> d -> m d +queueModel n theta = do + x <- iterateP (queueKernel theta) 0 !! n + return (toDouble x) +``` +The annotated probability distributions (e.g. `flipCRN`) specify +*factorized coupling strategies*, which GradInf uses to form +lower variance gradient estimators. +We can now differentiate the program using the GradInf high-level API: +```haskell +import Data.Functor.Identity +import Numeric.GradInf + +let thetaToDifferentiateAt = Identity 15.0 +let n = 50 +gradientEstimate <- sampler $ gradInfAD (queueModel n . runIdentity) (stratifiedImportanceResamplingInferenceAlg 1) thetaToDifferentiateAt +print gradientEstimate +``` + +Here, `stratifiedImportanceResamplingInferenceAlg 1` is an *inference strategy* +which enables sound and efficient gradient estimation. +See the tutorials in this documentation site for more details on the API. + + [1] *Monte Carlo Gradient Estimation in Machine Learning*, Shakir Mohamed, Mihaela Rosca, Michael Figurnov, and Andriy Mnih. *Journal of Machine Learning Research* 21, 132 (2020), + +## Index + +For more usage examples, check out the tutorial pages on applying GradInf to [queuing models](examples/QueueModel.md), [financial models](examples/OptionPricing.md), and [biochemical models](examples/GeneTranscription.md). +*API documentation and additional tutorials will be added soon!* + +## Citation + +```bibtex +@article{arya2026gradinf, +title = {GradInf: Gradient Estimation as Probabilistic Inference}, +author = {Arya, Gaurav and Huot, Mathieu and Schauer, Moritz and Lew, Alexander K. and Saad, Feras A.}, +journal = {Proceedings of the ACM on Programming Languages}, +volume = {10}, +number = {PLDI}, +articleno = {243}, +month = jun, +pages = {1864--1890}, +year = {2026}, +publisher = {Association for Computing Machinery}, +address = {New York, NY, USA}, +doi = {10.1145/3808321}, +} +``` diff --git a/docs/mkdocs/docs/overrides.css b/docs/mkdocs/docs/overrides.css new file mode 100644 index 0000000..29fa732 --- /dev/null +++ b/docs/mkdocs/docs/overrides.css @@ -0,0 +1,22 @@ +p { + margin: 0 0 18px; +} + +.md-typeset h1, .md-typeset h2, .md-typeset h3, .md-typeset h4, .md-typeset h5, .md-typeset h6 { + margin-bottom: 18px; + color: black; + font-weight: 500; +} + +.md-typeset .output { + color: black; + background: white; +} + +/* Force the inline index-page logo to its `height` attribute. Material's + `.md-typeset img { height: auto }` otherwise overrides it. */ +.md-typeset img[alt="GradInf Logo"] { + height: 80px !important; + width: auto !important; +} + diff --git a/docs/mkdocs/docs/overrides.js b/docs/mkdocs/docs/overrides.js new file mode 100644 index 0000000..3c0cdf3 --- /dev/null +++ b/docs/mkdocs/docs/overrides.js @@ -0,0 +1,2 @@ +hljs.configure({languages:[]}); +hljs.initHighlightingOnLoad(); \ No newline at end of file diff --git a/docs/mkdocs/mkdocs.yml b/docs/mkdocs/mkdocs.yml new file mode 100644 index 0000000..c9ed84d --- /dev/null +++ b/docs/mkdocs/mkdocs.yml @@ -0,0 +1,37 @@ +site_name: GradInf + +theme: + name: 'material' + home: index + toc_depth: 2 + features: + - content.code.copy + - navigation.expand + logo: images/logo-white.png + palette: + primary: black + +nav: + - Home: index.md + - Examples: + - Differentiating a Queue Model: examples/QueueModel.md + - Estimating Greeks of Option Pricing Model: examples/OptionPricing.md + - Parameter Inference of Gene Transcription Model: examples/GeneTranscription.md + +markdown_extensions: + - pymdownx.arithmatex: + generic: true + - pymdownx.highlight: + anchor_linenums: true + line_spans: __span + pygments_lang_class: true + - pymdownx.inlinehilite + - pymdownx.snippets + - pymdownx.superfences + +extra_javascript: + - https://unpkg.com/mathjax@3/es5/tex-mml-chtml.js + - overrides.js + +extra_css: + - overrides.css diff --git a/docs/requirements.txt b/docs/requirements.txt new file mode 100644 index 0000000..f79c751 --- /dev/null +++ b/docs/requirements.txt @@ -0,0 +1,3 @@ +mkdocs +mkdocs-material +pymdown-extensions diff --git a/tutorials/GeneTranscription.lhs b/tutorials/GeneTranscription.lhs index 464b715..7088d86 100644 --- a/tutorials/GeneTranscription.lhs +++ b/tutorials/GeneTranscription.lhs @@ -18,6 +18,9 @@ and apply a loss function that measures the relative error between the simulated mean copy numbers of mRNA and protein and ground truth reference values. +Writing the Model +----------------- + \begin{code} {-# LANGUAGE RebindableSyntax #-} {-# LANGUAGE ScopedTypeVariables #-} @@ -33,8 +36,6 @@ import System.Random (setStdGen, mkStdGen) import Data.List (transpose) import Numeric.GradInf.Primitives.DeterministicPrimitives import Numeric.GradInf.Primitives.Uniform -import Numeric.GradInf.Primitives.Categorical -import Numeric.GradInf.Primitives.CategoricalScore import Numeric.GradInf.Primitives.CategoricalMaxIndep import Numeric.GradInf.Primitives.FoldP import Numeric.GradInf.Primitives.IterateP @@ -54,15 +55,12 @@ n = 1000 capT :: Double capT = 2.5 -data PrimitiveChoice = MaxIndepPrimitive | ScorePrimitive | NoAnnotationPrimitive - geneTranscriptionKernel :: forall m d i b mat. (DeterministicPrimitives d i b mat, - Categorical m d [i], CategoricalScore m d [i], CategoricalMaxIndep m d [i], Uniform m d) - => PrimitiveChoice -> [d] -> ([i], d, [d]) -> m ([i], d, [d]) -geneTranscriptionKernel primitiveChoice theta (x, t, acc) = do + => [d] -> ([i], d, [d]) -> m ([i], d, [d]) +geneTranscriptionKernel theta (x, t, acc) = do if extractDouble t > capT then return (x, t, acc) @@ -79,10 +77,7 @@ geneTranscriptionKernel primitiveChoice theta (x, t, acc) = do let totalRate :: d = sum rates let probs :: [d] = map (\q -> q / totalRate) rates - x' <- case primitiveChoice of - MaxIndepPrimitive -> categoricalMaxIndep (probs, [[m + 1, p], [m, p + 1], [m - 1, p], [m, p - 1]]) - ScorePrimitive -> categoricalScore (probs, [[m + 1, p], [m, p + 1], [m - 1, p], [m, p - 1]]) - NoAnnotationPrimitive -> categorical (probs, [[m + 1, p], [m, p + 1], [m - 1, p], [m, p - 1]]) + x' <- categoricalMaxIndep (probs, [[m + 1, p], [m, p + 1], [m - 1, p], [m, p - 1]]) u <- uniform let t' = t + log (1 / u) / totalRate @@ -97,14 +92,13 @@ geneTranscriptionModel :: forall m d i b mat. (DeterministicPrimitives d i b mat, IterateP m ([i], d, [d]), FoldrP m ([i], d, [d]) ([d], d), - Categorical m d [i], CategoricalScore m d [i], CategoricalMaxIndep m d [i], Uniform m d) - => PrimitiveChoice -> Int -> Int -> [d] -> m d -geneTranscriptionModel primitiveChoice numObs n theta = do + => Int -> Int -> [d] -> m d +geneTranscriptionModel numObs n theta = do let x0 = [5, 40] let simulateTrace :: m ([i], d, [d]) = - iterateP (geneTranscriptionKernel primitiveChoice theta) (x0, 0.0, [0.0, 0.0]) !! n + iterateP (geneTranscriptionKernel theta) (x0, 0.0, [0.0, 0.0]) !! n let foldF (_, t, timeInt) (accX, accT) = do if extractDouble t < capT then @@ -129,10 +123,13 @@ differentiating the gene transcription model. \begin{code} getSamples :: Sampler [Double] getSamples = - replicateM 5 (geneTranscriptionModel NoAnnotationPrimitive numObs n + replicateM 5 (geneTranscriptionModel numObs n (map log [18, 8, 1.5, 4])) \end{code} +Differentiating the Model +------------------------- + We now apply GradInf to compute gradient estimates with respect to all four parameters simultaneously using the `ZipList` functor. This computes the full gradient in a single call to `gradInfAD`. @@ -153,13 +150,12 @@ getGradientEstimates = let Coupled (accPA, accPB) = acc !! 1 let horizon = fromDouble $ min 0.5 (capT - extractDouble tA) let timeScale = 1 / 20 - let proteinAdj = 1.0 abs ((accMB - accMA) + (toDouble mB - toDouble mA) * horizon) / fromDouble 10.4 - + proteinAdj * abs ((accPB - accPA) + (toDouble pB - toDouble pA) * horizon) / fromDouble 22.3 + + abs ((accPB - accPA) + (toDouble pB - toDouble pA) * horizon) / fromDouble 22.3 + abs (tB - tA) * timeScale in replicateM 10 ( gradInfAD - (geneTranscriptionModel MaxIndepPrimitive numObs n . getZipList) + (geneTranscriptionModel numObs n . getZipList) ( TwistedSMCInference 1 twistFunc :: forall d i b mat. (DeterministicPrimitives d i b mat) => TwistedSMCInference Base CRN d i b diff --git a/tutorials/OptionPricing.lhs b/tutorials/OptionPricing.lhs index 284d3cc..f34bf7a 100644 --- a/tutorials/OptionPricing.lhs +++ b/tutorials/OptionPricing.lhs @@ -5,6 +5,9 @@ The model simulates the price of an asset in accordance with the and returns the expected payoff of a [European-style option](https://en.wikipedia.org/wiki/Option_style). +Writing the Model +----------------- + First, we define the model below. \begin{code} @@ -66,6 +69,9 @@ optionPricingModel n theta = do 0 \end{code} +Differentiating the Model +------------------------- + We now apply GradInf to compute gradient estimates. For our inference algorithm, we will use twisted Sequential Monte Carlo, supplying a simple twist diff --git a/tutorials/QueueModel.lhs b/tutorials/QueueModel.lhs index b5dec96..4de4279 100644 --- a/tutorials/QueueModel.lhs +++ b/tutorials/QueueModel.lhs @@ -2,7 +2,8 @@ In this example, we use GradInf to develop a new, lower-variance gradient estimator for an [M/M/C queue model](https://en.wikipedia.org/wiki/M/M/c_queue). -== Setup +Setup +----- This example needs the following imports. @@ -44,7 +45,8 @@ meanAndVariance xs = do (mu, var) \end{code} -== Writing the Model +Writing the Model +----------------- We first write the Markov kernel for the model, which simulates a single queuing event. @@ -124,6 +126,9 @@ at each of these parameters. ![](../images/plot_MMC_queue_traces_raw-crop.png) +Differentiating the Model +------------------------- + Let us now run GradInf on this program, first using an inference scheme based on stratified importance resampling (SIR).