Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

added ImplicitDistribution #37

Merged
merged 3 commits into from
Jun 22, 2020
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
10 changes: 9 additions & 1 deletion docs/src/distributions.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,20 @@ This package also supplies [`showdistribution`](@ref) for pretty printing distri

`SparseCat` is a sparse categorical distribution which is specified by simply providing a list of possible values (states or observations) and the probabilities corresponding to those particular objects.

Example: `SparseCat([1,2,3], [0.1,0.2,0.7])` is a categorical distribution that assignes probability 0.1 to `1`, 0.2 to `2`, 0.7 to `3`, and 0 to all other values.
Example: `SparseCat([1,2,3], [0.1,0.2,0.7])` is a categorical distribution that assigns probability 0.1 to `1`, 0.2 to `2`, 0.7 to `3`, and 0 to all other values.

```@docs
SparseCat
```

## Implicit

In situations where a distribution object is required, but the pdf is difficult to specify and only samples are required, `ImplicitDistribution` provides a convenient way to package a sampling function.

```@docs
ImplicitDistribution
```

## Bool Distribution

```@docs
Expand Down
4 changes: 4 additions & 0 deletions src/POMDPModelTools.jl
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,10 @@ export
UnsafeUniform
include("distributions/uniform.jl")

export
ImplicitDistribution
include("distributions/implicit.jl")

# convenient implementations
include("convenient_implementations.jl")

Expand Down
43 changes: 43 additions & 0 deletions src/distributions/implicit.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
"""
ImplicitDistribution(sample_function, args...)

Define a distribution that can only be sampled from using `rand`, but has no explicit `pdf`.

Each time `rand(rng, d::ImplicitDistribution)` is called,
```julia
sample_function(args..., rng)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Maybe have rng as the first argument? I feel like rng should always be the first argument to be consistent with rand. Then again, this is also different in other parts of POMDPs.jl.

Copy link
Member Author

@zsunberg zsunberg Jun 12, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't consider this a "nit" 😄 Yeah, I have wondered about this. I think that having rng as an optional first argument to rand was a design mistake that we shouldn't copy except when implementing new methods of Base.rand. Anyone else have strong opinions?

```
will be called to generate a new sample.

`ImplicitDistribution` is designed to be used with anonymous functions or the `do` syntax as follows:

# Examples

```julia
ImplicitDistribution(rng->rand(rng)^2)
```

```julia
struct MyMDP <: MDP{Float64, Int} end

function POMDPs.transition(m::MyMDP, s, a)
ImplicitDistribution(s, a) do s, a, rng
return s + a + 0.001*randn(rng)
end
end

td = transition(MyMDP(), 1.0, 1)
rand(td) # will return a number near 2
```
"""
struct ImplicitDistribution{F<:Function, A}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would drop the constraint of this being a function. It may also be useful to pass other callable objects here.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can make callable objects Functions e.g. struct MyType <: Function. Probably if someone passes an object here, it will be custom made for this purpose, or it is not too difficult to make a small closure. I think that the documentation/clarity bonus for including the constraint outweighs the downside. Am I forgetting anything?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess your are right. I forgot that you could just do sub-type Function.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, I am going to remove the Function constraint. It is an antipattern https://www.juliabloggers.com/julialang-antipatterns/

f::F
args::A
end

ImplicitDistribution(f::Function, args...) = ImplicitDistribution(f, args)

function Base.rand(rng::AbstractRNG, s::Random.SamplerTrivial{<:ImplicitDistribution})
d = s[]
d.f(d.args..., rng)
end
3 changes: 3 additions & 0 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ using SparseArrays
@testset "uniform" begin
include("test_uniform.jl")
end
@testset "implicit" begin
include("test_implicit.jl")
end
@testset "terminalstate" begin
include("test_terminal_state.jl")
end
Expand Down
12 changes: 12 additions & 0 deletions test/test_implicit.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
struct IMDP <: MDP{Float64, Int} end

function POMDPs.transition(m::IMDP, s, a)
ImplicitDistribution(s, a) do s, a, rng
return s + a + rand(rng)
end
end

m = IMDP()

td = transition(m, 1.0, 1)
@test 2 <= rand(td) <= 3