Skip to content

Commit 7af6ad4

Browse files
authored
Set up clean documentation (#168)
* Include MeasureBase docs in MeasureTheory * Clean up README * Remove support in docs * Remove useless MeasureBase import * Version bump
1 parent 725dc79 commit 7af6ad4

File tree

11 files changed

+368
-223
lines changed

11 files changed

+368
-223
lines changed

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
*.jl.cov
33
*.jl.mem
44
.DS_Store
5-
/Manifest.toml
5+
**/Manifest.toml
66
/docs/build/
77
/dev/
88
/.history/

Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name = "MeasureTheory"
22
uuid = "eadaa1a4-d27c-401d-8699-e962e1bbc33b"
33
authors = ["Chad Scherrer <[email protected]> and contributors"]
4-
version = "0.13.1"
4+
version = "0.13.2"
55

66
[deps]
77
Accessors = "7d9f7c33-5ae7-4f3b-8dc6-eff91059b697"

README.md

Lines changed: 8 additions & 181 deletions
Original file line numberDiff line numberDiff line change
@@ -5,201 +5,28 @@
55
[![Build Status](https://github.com/cscherrer/MeasureTheory.jl/workflows/CI/badge.svg)](https://github.com/cscherrer/MeasureTheory.jl/actions)
66
[![Coverage](https://codecov.io/gh/cscherrer/MeasureTheory.jl/branch/master/graph/badge.svg)](https://codecov.io/gh/cscherrer/MeasureTheory.jl)
77

8-
Check out our [JuliaCon submission](https://github.com/cscherrer/MeasureTheory.jl/blob/paper/paper/paper.pdf)
9-
108
`MeasureTheory.jl` is a package for building and reasoning about measures.
119

12-
# Why?
10+
## Why?
1311

14-
A distribution (as in Distributions.jl) is also called a _probability measure_, and carries with it the constraint of adding (or integrating) to one. Statistical work usually requires this "at the end of the day", but enforcing it at each step of a computation can have considerable overhead.
12+
A distribution (as provided by `Distributions.jl`) is also called a _probability measure_, and carries with it the constraint of adding (or integrating) to one. Statistical work usually requires this "at the end of the day", but enforcing it at each step of a computation can have considerable overhead. For instance, Bayesian modeling often requires working with unnormalized posterior densities or improper priors.
1513

1614
As a generalization of the concept of volume, measures also have applications outside of probability theory.
1715

18-
# Goals
19-
20-
## Distributions.jl Compatibility
21-
22-
Distirbutions.jl is wildly popular, and is large enough that replacing it all at once would be a major undertaking.
23-
24-
Instead, we should aim to make any Distribution easily usable as a Measure. We'll most likely implement this using an `IsMeasure` trait.
25-
26-
## Absolute Continuity
27-
28-
For two measures μ, ν on a set X, we say μ is _absolutely continuous_ with respect to ν if ν(A)=0 implies μ(A)=0 for every measurable subset A of X.
29-
30-
The following are equivalent:
31-
1. μ ≪ ν
32-
2. μ is absolutely continuous wrt ν
33-
3. There exists a function f such that μ = ∫f dν
34-
35-
So we'll need a `` operator. Note that `` is not antisymmetric; it's common for both `μ ≪ ν` and `ν ≪ μ` to hold.
36-
37-
If `μ ≪ ν` and `ν ≪ μ`, we say μ and ν are _equivalent_ and write `μ ≃ ν`. (This is often written as `μ ~ ν`, but we reserve `~` for random variables following a distribution, as is common in the literature and probabilistic programming languages.)
38-
39-
If we collapse the equivalence classes (under ≃), `` becomes a partial order.
16+
## Getting started
4017

41-
_We need ≃ and ≪ to be fast_. If the support of a measure can be determined statically from its type, we can define ≃ and ≪ as generated functions.
42-
43-
## Radon-Nikodym Derivatives
44-
45-
One of the equivalent conditions above was "There exists a function f such that μ = ∫f dν". In this case, `f` is called a _Radon-Nikodym derivative_, or (less formally) a _density_. In this case we often write `f = dμ/dν`.
46-
47-
For any measures μ and ν with μ≪ν, we should be able to represent this.
48-
49-
## Integration
50-
51-
More generally, we'll need to be able to represent change of measure as above, `∫f dν`. We'll need an `Integral` type
18+
To install `MeasureTheory.jl`, open the Julia Pkg REPL (by typing `]` in the standard REPL) and run
5219

5320
```julia
54-
struct Integral{F,M}
55-
f::F
56-
μ::M
57-
end
21+
pkg> add MeasureTheory
5822
```
5923

60-
Then we'll have a function ``. For cases where μ = ∫f dν, `∫(f, ν)` will just return `μ` (we can do this based on the types). For unknown cases (which will be most of them), we'll return `∫(f, ν) = Integral(f, ν)`.
61-
62-
## Measure Combinators
63-
64-
It should be very easy to build new measures from existing ones. This can be done using, for example,
65-
66-
- restriction
67-
- product measure
68-
- superposition
69-
- pushforward
70-
71-
There's also function spaces, but this gets much trickier. We'll need to determine a good way to reason about this.
72-
73-
## More???
74-
75-
This is very much a work in progress. If there are things you think we should have as goals, please add an issue with the `goals` label.
76-
77-
78-
------------------
79-
# Old Stuff
80-
81-
**WARNING: The current README is very developer-oriented. Casual use will be much simpler**
82-
83-
For an example, let's walk through the construction of `src/probability/Normal`.
84-
85-
First, we have
86-
87-
```julia
88-
@measure Normal
89-
```
90-
91-
this is just a little helper function, and is equivalent to
92-
93-
# TODO: Clean up
94-
```julia
95-
quote
96-
#= /home/chad/git/Measures.jl/src/Measures.jl:55 =#
97-
struct Normal{var"#10#P", var"#11#X"} <: Measures.AbstractMeasure{var"#11#X"}
98-
#= /home/chad/git/Measures.jl/src/Measures.jl:56 =#
99-
par::var"#10#P"
100-
end
101-
#= /home/chad/git/Measures.jl/src/Measures.jl:59 =#
102-
function Normal(var"#13#nt"::Measures.NamedTuple)
103-
#= /home/chad/git/Measures.jl/src/Measures.jl:59 =#
104-
#= /home/chad/git/Measures.jl/src/Measures.jl:60 =#
105-
var"#12#P" = Measures.typeof(var"#13#nt")
106-
#= /home/chad/git/Measures.jl/src/Measures.jl:61 =#
107-
return Normal{var"#12#P", Measures.eltype(Normal{var"#12#P"})}
108-
end
109-
#= /home/chad/git/Measures.jl/src/Measures.jl:64 =#
110-
Normal(; var"#14#kwargs"...) = begin
111-
#= /home/chad/git/Measures.jl/src/Measures.jl:64 =#
112-
Normal((; var"#14#kwargs"...))
113-
end
114-
#= /home/chad/git/Measures.jl/src/Measures.jl:66 =#
115-
(var"#8#basemeasure"(var"#15#μ"::Normal{var"#16#P", var"#17#X"}) where {var"#16#P", var"#17#X"}) = begin
116-
#= /home/chad/git/Measures.jl/src/Measures.jl:66 =#
117-
Lebesgue{var"#17#X"}
118-
end
119-
#= /home/chad/git/Measures.jl/src/Measures.jl:68 =#
120-
(var"#9#≪"(::Normal{var"#19#P", var"#20#X"}, ::Lebesgue{var"#20#X"}) where {var"#19#P", var"#20#X"}) = begin
121-
#= /home/chad/git/Measures.jl/src/Measures.jl:68 =#
122-
true
123-
end
124-
end
125-
```
126-
127-
Next we have
128-
129-
```julia
130-
Normal::Real, σ::Real) = Normal=μ, σ=σ)
131-
```
24+
To get an idea of the possibilities offered by this package, go to the [documentation](https://cscherrer.github.io/MeasureTheory.jl/stable).
13225

133-
This defines a default. If we just give two numbers as arguments (but no names), we'll assume this parameterization.
134-
135-
Next need to define a `eltype` function. This takes a constructor (here `Normal`) and a parameter, and tells us the space for which this defines a measure. Let's define this in terms of the types of the parameters,
136-
137-
```julia
138-
eltype(::Type{Normal}, ::Type{NamedTuple{(:μ, :σ), Tuple{A, B}}}) where {A,B} = promote_type(A,B)
139-
```
140-
141-
That's still kind of boring, so let's build the density. For this, we need to implement the trait
142-
143-
```julia
144-
@trait Density{M,X} where {X = domain{M}} begin
145-
basemeasure :: [M] => Measure{X}
146-
logdensity :: [M, X] => Real
147-
end
148-
```
149-
150-
A density doesn't exist by itself, but is defined relative to some _base measure_. For a normal distribution this is just Lebesgue measure on the real numbers. That, together with the usual Gaussian log-density, gives us
151-
152-
```julia
153-
@implement Density{Normal{X,P},X} where {X, P <: NamedTuple{(:μ, :σ)}} begin
154-
basemeasure(d) = Lebesgue(X)
155-
logdensity(d, x) = - (log(2) + log(π)) / 2 - log(d.par.σ) - (x - d.par.μ)^2 / (2 * d.par.σ^2)
156-
end
157-
```
158-
159-
Now we can compute the log-density:
160-
161-
```julia
162-
julia> logdensity(Normal(0.0, 0.5), 1.0)
163-
-2.2257913526447273
164-
```
165-
166-
And just to check that our default is working,
167-
168-
```julia
169-
julia> logdensity(Normal=0.0, σ=0.5), 1.0)
170-
-2.2257913526447273
171-
```
172-
173-
What about other parameterizations? Sure, no problem. Here's a way to write this for mean `μ` (as before), but using the _precision_ (inverse of the variance) instead of standard deviation:
174-
175-
```julia
176-
eltype(::Type{Normal}, ::Type{NamedTuple{(:μ, :τ), Tuple{A, B}}}) where {A,B} = promote_type(A,B)
177-
178-
@implement Density{Normal{X,P},X} where {X, P <: NamedTuple{(:μ, :τ)}} begin
179-
basemeasure(d) = Lebesgue(X)
180-
logdensity(d, x) = - (log(2) + log(π) - log(d.par.τ) + d.par.τ * (x - d.par.μ)^2) / 2
181-
end
182-
```
183-
184-
And another check:
185-
186-
```julia
187-
julia> logdensity(Normal=0.0, τ=4.0), 1.0)
188-
-2.2257913526447273
189-
```
190-
191-
We can combine measures in a few ways, for now just _scaling_ and _superposition_:
192-
193-
```julia
194-
julia> 2.0*Lebesgue(Float64) + Normal(0.0,1.0)
195-
SuperpositionMeasure{Float64,2}((MeasureTheory.WeightedMeasure{Float64,Float64}(2.0, Lebesgue{Float64}()), Normal{NamedTuple{(:μ, :σ),Tuple{Float64,Float64}},Float64}((μ = 0.0, σ = 1.0))))
196-
```
197-
198-
---
199-
200-
For an easy way to find expressions for the common log-densities, see [this gist](https://gist.github.com/cscherrer/47f0fc7597b4ffc11186d54cc4d8e577)
26+
To know more about the underlying theory and its applications to probabilistic programming, check out our [JuliaCon 2021 submission](https://arxiv.org/abs/2110.00602).
20127

20228
## Support
29+
20330
[<img src=https://user-images.githubusercontent.com/1184449/140397787-9b7e3eb7-49cd-4c63-8f3c-e5cdc41e393d.png width="49%">](https://informativeprior.com/) [<img src=https://planting.space/sponsor/PlantingSpace-sponsor-3.png width=49%>](https://planting.space)
20431

20532
## Stargazers over time

docs/make.jl

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,24 @@
1-
using MeasureTheory
21
using Documenter
2+
using MeasureTheory
33

4+
DocMeta.setdocmeta!(MeasureBase, :DocTestSetup, :(using MeasureBase); recursive=true)
5+
DocMeta.setdocmeta!(MeasureTheory, :DocTestSetup, :(using MeasureTheory); recursive=true)
46

57
pages = [
6-
"Introduction" => "intro.md"
7-
"Home" => "index.md"
8-
"Adding a New Measure" => "adding.md"
8+
"Home" => "index.md",
9+
"Tutorials" => [
10+
"Adding a new measure" => "adding.md",
11+
"Affine transformations" => "affine.md",
12+
],
13+
"API Reference" => [
14+
"MeasureBase" => "api_measurebase.md",
15+
"MeasureTheory" => "api_measuretheory.md",
16+
"Index" => "api_index.md",
17+
],
918
]
1019

1120
makedocs(;
12-
modules=[MeasureTheory],
21+
modules=[MeasureBase, MeasureTheory],
1322
authors="Chad Scherrer <[email protected]> and contributors",
1423
repo="https://github.com/cscherrer/MeasureTheory.jl/blob/{commit}{path}#L{line}",
1524
sitename="MeasureTheory.jl",

docs/src/affine.md

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
# Affine Transformations
2+
3+
It's very common for measures to be parameterized by `μ` and `σ`, for example as in `Normal(μ=3, σ=4)` or `StudentT(ν=1, μ=3, σ=4)`. In this context, `μ` and `σ` do not always refer to the mean and standard deviation (the `StudentT` above is equivalent to a Cauchy, so both are undefined).
4+
5+
Rather, `μ` is a "location parameter", and `σ` is a "scale parameter". Together these determine an affine transformation
6+
7+
```math
8+
f(z) = σ z + μ
9+
```
10+
11+
Here are below, we'll use ``z`` to represent an "un-transformed" variable, typically coming from a measure like `Normal()` with no location or scale parameters.
12+
13+
Affine transforms are often incorrectly referred to as "linear". Linearity requires ``f(ax + by) = a f(x) + b f(y)`` for scalars ``a`` and ``b``, which only holds for the above ``f`` if ``μ=0``.
14+
15+
16+
## Cholesky-based parameterizations
17+
18+
If the "un-transformed" `z` is a scalar, things are relatively simple. But it's important our approach handle the multivariate case as well.
19+
20+
In the literature, it's common for a multivariate normal distribution to be parameterized by a mean `μ` and covariance matrix `Σ`. This is mathematically convenient, but can be very awkward from a computational perspective.
21+
22+
While MeasureTheory.jl includes (or will include) a parameterization using `Σ`, we prefer to work in terms of its Cholesky decomposition ``σ``.
23+
24+
Using "``σ``" for this may seem strange at first, so we should explain the notation. Let ``σ`` be a lower-triangular matrix satisfying
25+
26+
```math
27+
σ σᵗ = Σ
28+
```
29+
30+
Then given a (multivariate) standard normal ``z``, the covariance matrix of ``σ z + μ`` is
31+
32+
```math
33+
𝕍[σ z + μ] = Σ
34+
```
35+
36+
Comparing to the one dimensional case where
37+
38+
```math
39+
𝕍[σ z + μ] = σ²
40+
```
41+
42+
shows that the lower Cholesky factor of the covariance generalizes the concept of standard deviation, justifying the notation.
43+
44+
## The "Cholesky precision" parameterization
45+
46+
The ``(μ,σ)`` parameterization is especially convenient for random sampling. Any `z ~ Normal()` determines an `x ~ Normal(μ,σ)` through
47+
48+
```math
49+
x = σ z + μ
50+
```
51+
52+
On the other hand, the log-density computation is not quite so simple. Starting with an ``x``, we need to find ``z`` using
53+
54+
```math
55+
z = σ⁻¹ (x - μ)
56+
```
57+
58+
so the log-density is
59+
60+
```julia
61+
logdensity(d::Normal{(:μ,:σ)}, x) = logdensity(d.σ \ (x - d.μ)) - logdet(d.σ)
62+
```
63+
64+
Here the `- logdet(σ)` is the "log absolute Jacobian", required to account for the stretching of the space.
65+
66+
The above requires solving a linear system, which adds some overhead. Even with the convenience of a lower triangular system, it's still not quite a efficient as a multiplication.
67+
68+
In addition to the covariance ``Σ``, it's also common to parameterize a multivariate normal by its _precision matrix_, ``Ω = Σ⁻¹``. Similarly to our use of ``σ``, we'll use ``ω`` for the lower Cholesky factor of ``Ω``.
69+
70+
This allows a more efficient log-density,
71+
72+
```julia
73+
logdensity(d::Normal{(:μ,:ω)}, x) = logdensity(d.ω * (x - d.μ)) + logdet(d.ω)
74+
```
75+
76+
## `AffineTransform`
77+
78+
Transforms like ``z → σ z + μ`` and ``z → ω \ z + μ`` can be represented using an `AffineTransform`. For example,
79+
80+
```julia
81+
julia> f = AffineTransform((μ=3.=2.))
82+
AffineTransform{(:μ, :σ), Tuple{Float64, Float64}}((μ = 3.0, σ = 2.0))
83+
84+
julia> f(1.0)
85+
5.0
86+
```
87+
88+
In the scalar case this is relatively simple to invert. But if `σ` is a matrix, this would require matrix inversion. Adding to this complication is that lower triangular matrices are not closed under matrix inversion.
89+
90+
Our multiple parameterizations make it convenient to deal with these issues. The inverse transform of a ``(μ,σ)`` transform will be in terms of ``(μ,ω)``, and vice-versa. So
91+
92+
```julia
93+
julia> f⁻¹ = inv(f)
94+
AffineTransform{(:μ, :ω), Tuple{Float64, Float64}}((μ = -1.5, ω = 2.0))
95+
96+
julia> f(f⁻¹(4))
97+
4.0
98+
99+
julia> f⁻¹(f(4))
100+
4.0
101+
```
102+
103+
## `Affine`
104+
105+
Of particular interest (the whole point of all of this, really) is to have a natural way to work with affine transformations of measures. In accordance with the principle of "common things should have shorter names", we call this `Affine`.
106+
107+
The structure of `Affine` is relatively simple:
108+
109+
```julia
110+
struct Affine{N,M,T} <: AbstractMeasure
111+
f::AffineTransform{N,T}
112+
parent::M
113+
end
114+
```

docs/src/api_index.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# Index
2+
3+
```@index
4+
```

docs/src/api_measurebase.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# MeasureBase API
2+
3+
```@autodocs
4+
Modules = [MeasureBase]
5+
```

docs/src/api_measuretheory.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# MeasureTheory API
2+
3+
```@autodocs
4+
Modules = [MeasureTheory]
5+
```

0 commit comments

Comments
 (0)