Metadata-Version: 2.4
Name: openmrf
Version: 0.1.0
Summary: Composable factors and Markov random fields for small probabilistic models.
Author-email: Zhuolin Fu <fzhuolin@gmail.com>
Requires-Python: >=3.10
Description-Content-Type: text/markdown
Requires-Dist: numpy>=1.24
Requires-Dist: scipy>=1.10

# MRF Utility Suite

A compact, well-documented toolkit for building small Markov random fields (MRFs)
from composable functional factors.  It is designed for clarity, teaching,
prototyping, and testable numerical work rather than high-dimensional inference.

```python
from openmrf import Domain, Factor, MRF

spin = Domain.discrete([-1, 1])
edge = Factor.from_function(
    ("left", "right"),
    lambda a: np.exp(0.8 * a["left"] * a["right"]),
    domains={"left": spin, "right": spin},
)
field = Factor.from_function(
    ("left",), lambda a: np.exp(0.2 * a["left"]), domains={"left": spin}
)

mrf = MRF({"left": spin, "right": spin})
mrf.add_factor(edge)
mrf.add_factor(field)
print(mrf.sample(1_000, burn_in=200))
```

## Concepts

* `Domain` describes the state space of one variable.  A discrete domain stores
  finite values; a continuous domain stores finite integration/optimization
  bounds.
* `VectorDomain` describes a bounded continuous vector variable. Its bounds are
  supplied per coordinate, and factor functions receive a NumPy vector.
* `Factor` is immutable.  It maps assignments for a declared variable scope to
  non-negative (or, for general algebra, numeric) values. Factors may originate
  from a callable or a dense discrete table.
* `MRF` owns variables and factors and implements Gibbs sampling for discrete
  variables and random-walk Metropolis-within-Gibbs for continuous variables.

## Notes on numerical operations

Marginalization exactly sums finite discrete domains. Continuous variables are
integrated numerically over their `Domain` bounds with SciPy quadrature. `argmax`
uses exhaustive enumeration when possible and bounded numerical optimization
otherwise. Numerical methods are intended for modest dimensional scopes.

## Vector variables

```python
import numpy as np
from openmrf import Factor, VectorDomain

vector2 = VectorDomain.box(lower=[-1, -1], upper=[1, 1])
dot_product = Factor.from_function(
    ("x", "y"),
    lambda a: a["x"] @ a["y"],
    domains={"x": vector2, "y": vector2},
)
assert dot_product(x=np.array([1., 0.]), y=np.array([0.5, 0.5])) == 0.5
```

Vector-valued continuous variables work with conditioning, numerical
marginalization, maximization, Hessians (one row/column per coordinate), and
random-walk MRF sampling. Table factors remain scalar discrete only.

Run the test suite with `python -m pytest`.
