Skip to content

Commit

Permalink
Better handling of comparisons
Browse files Browse the repository at this point in the history
  • Loading branch information
baggepinnen committed Feb 23, 2020
1 parent 5331d81 commit da59bd8
Show file tree
Hide file tree
Showing 5 changed files with 100 additions and 26 deletions.
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name = "MonteCarloMeasurements"
uuid = "0987c9cc-fe09-11e8-30f0-b96dd679fdca"
authors = ["baggepinnen <[email protected]>"]
version = "0.8.1"
version = "0.8.2"

[deps]
Distributed = "8ba89e20-285c-5b6f-9357-94700520ee1b"
Expand Down
7 changes: 7 additions & 0 deletions docs/src/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,13 @@ p = 0 ± 1
```
Ideally, half of the particles should turn out negative and half positive when applying `negsquare(p)`. However, this will not happen as the `x > 0` is not defined for uncertain values. To circumvent this, define `negsquare` as a primitive using [`register_primitive`](@ref) described in [Overloading a new function](@ref). Particles will then be propagated one by one through the entire function `negsquare`. Common such functions from `Base`, such as `max/min` etc. are already registered.

## Comparison mode
Some functions perform checks like `if error < tol`. If `error isa Particles`, this will use a very conservative check by default by checking that all particles ∈ `error` fulfill the check. There are a few different options available for how to compare two uncertain quantities, chosen by specifying a comparison mode. The modes are chosen by `unsafe_comparisons(mode)` and the options are
- `:safe`: the default described above, throws an error if uncertain values share support.
- `:montecarlo`: slightly less conservative than `:safe`, checks if either all pairwise particles fulfill the comparison, *or* all pairwise particles fail the comparison. If some pairs pass and some fail, an error is thrown.
- `:reduction`: Reduce uncertain values to a single number, e.g. by calling `mean` (default) before performing the comparison, never throws an error.

To sum up, if two uncertain values are compared, and they have no mutual support, then all comparison modes are equal. If they share support, `:safe` will error and `:montecarlo` will work if the all pairwise particles either pass or fail the comparison. `:reduction` will always work, but is maximally unsafe in the sense that it might not perform a meaningful check for your application.



Expand Down
26 changes: 20 additions & 6 deletions src/MonteCarloMeasurements.jl
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,32 @@ using Distributions, StatsBase, Requires
const DEFAULT_NUM_PARTICLES = 10000
const DEFAULT_STATIC_NUM_PARTICLES = 100

"""
The function used to reduce particles to a number for comparison. Defaults to `mean`. Change using `unsafe_comparisons`.
"""
const COMPARISON_FUNCTION = Ref{Function}(mean)
const USE_UNSAFE_COMPARIONS = Ref(false)
const COMPARISON_MODE = Ref(:safe)

"""
unsafe_comparisons(onoff=true; verbose=true)
Toggle the use of a comparison function without warning. By default `mean` is used to reduce particles to a floating point number for comparisons. This function can be changed, example: `set_comparison_function(median)`
unsafe_comparisons(mode=:reduction; verbose=true)
One can also specify a comparison mode, `mode` can take the values `:safe, :montecarlo, :reduction`. `:safe` is the same as calling `unsafe_comparisons(false)` and `:reduction` corresponds to `true`.
If
"""
function unsafe_comparisons(onoff=true; verbose=true)
USE_UNSAFE_COMPARIONS[] = onoff
if onoff && verbose
@info "Unsafe comparisons using the function `$(COMPARISON_FUNCTION[])` has been enabled globally. Use `@unsafe` to enable in a local expression only or `unsafe_comparisons(false)` to turn off unsafe comparisons"
function unsafe_comparisons(mode=true; verbose=true)
mode == false && (mode = :safe)
mode == true && (mode = :reduction)
COMPARISON_MODE[] = mode
if mode != :safe && verbose
if mode === :reduction
@info "Unsafe comparisons using the function `$(COMPARISON_FUNCTION[])` has been enabled globally. Use `@unsafe` to enable in a local expression only or `unsafe_comparisons(false)` to turn off unsafe comparisons"
elseif mode === :montecarlo
@info "Comparisons using the monte carlo has been enabled globally. Call `unsafe_comparisons(false)` to turn off unsafe comparisons"
end
end
mode (:safe, :montecarlo, :reduction) && error("Got unsupported comparison model")
end
"""
set_comparison_function(f)
Expand All @@ -47,7 +61,7 @@ macro unsafe(ex)
:(res)
end
quote
previous_state = USE_UNSAFE_COMPARIONS[]
previous_state = COMPARISON_MODE[]
unsafe_comparisons(true, verbose=false)
local res
try
Expand Down
50 changes: 39 additions & 11 deletions src/particles.jl
Original file line number Diff line number Diff line change
Expand Up @@ -372,26 +372,54 @@ Base.:(==)(p1::AbstractParticles{T,N},p2::AbstractParticles{T,N}) where {T,N} =
Base.:(!=)(p1::AbstractParticles{T,N},p2::AbstractParticles{T,N}) where {T,N} = p1.particles != p2.particles


function _comparison_operator(p)
length(p) == 1 && return
USE_UNSAFE_COMPARIONS[] || error("Comparison operators are not well defined for uncertain values and are currently turned off. Call `unsafe_comparisons(true)` to enable comparison operators for particles using the current reduction function $(COMPARISON_FUNCTION[]). Change this function using `set_comparison_function(f)`.")
function zip_longest(a,b)
l = max(length(a), length(b))
Iterators.take(zip(Iterators.cycle(a), Iterators.cycle(b)), l)
end

function safe_comparison(a,b,op::F) where F
all(((a,b),)->op(a,b), Iterators.product(extrema(a),extrema(b))) && return true
!any(((a,b),)->op(a,b), Iterators.product(extrema(a),extrema(b))) && return false
_comparison_error()
end

function do_comparison(a,b,op::F) where F
mode = COMPARISON_MODE[]
if mode === :reduction
op(COMPARISON_FUNCTION[](a), COMPARISON_FUNCTION[](b))
elseif mode === :montecarlo
all(((a,b),)->op(a,b), zip_longest(a,b)) && return true
!any(((a,b),)->op(a,b), zip_longest(a,b)) && return false
_comparison_error()
elseif mode === :safe
safe_comparison(a,b,op)
else
error("Got unsupported comparison mode.")
end
end

function _comparison_error()
msg = "Comparison of uncertain values using comparison mode $(COMPARISON_MODE[]) failed. Comparison operators are not well defined for uncertain values. Call `unsafe_comparisons(true)` to enable comparison operators for particles using the current reduction function $(COMPARISON_FUNCTION[]). Change this function using `set_comparison_function(f)`. "
if COMPARISON_MODE[] === :safe
msg *= "For safety reasons, the default safe comparison function is maximally conservative and tests if the extreme values of the distributions fulfil the comparison operator."
elseif COMPARISON_MODE[] === :montecarlo
msg *= "For safety reasons, montecarlo comparison is conservative and tests if pairwise particles fulfil the comparison operator. If some do *and* some do not, this error is thrown. Consider if you can define a primitive function ([docs](https://baggepinnen.github.io/MonteCarloMeasurements.jl/stable/overloading/#Overloading-a-new-function-1)) or switch to `unsafe_comparisons(:reduction)`"
end

error(msg)
end

function Base.:<(a::Real,p::AbstractParticles)
_comparison_operator(p)
a < COMPARISON_FUNCTION[](p)
do_comparison(a,p,<)
end
function Base.:<(p::AbstractParticles,a::Real)
_comparison_operator(p)
COMPARISON_FUNCTION[](p) < a
do_comparison(p,a,<)
end
function Base.:<(p::AbstractParticles, a::AbstractParticles)
_comparison_operator(p)
COMPARISON_FUNCTION[](p) < COMPARISON_FUNCTION[](a)
do_comparison(p,a,<)
end
function Base.:(<=)(p::AbstractParticles{T,N}, a::AbstractParticles{T,N}) where {T,N}
_comparison_operator(p)
COMPARISON_FUNCTION[](p) <= COMPARISON_FUNCTION[](a)
do_comparison(p,a,<=)
end

"""
Expand Down
41 changes: 33 additions & 8 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ Random.seed!(0)
@test [0,0] [1.,1.] isa MonteCarloMeasurements.MvParticles

@info "Done"
PT = Particles
for PT = (Particles, StaticParticles)
@testset "$(repr(PT))" begin
@info "Running tests for $PT"
Expand Down Expand Up @@ -87,20 +88,44 @@ Random.seed!(0)
@test_throws ErrorException p>p
@test_throws ErrorException p>=p
@test_throws ErrorException p<=p
@unsafe begin
@test -10 < p
@test p <= p
@test p >= p
@test !(p < p)
@test !(p > p)
@test (p < 1+p)
@test (p+1 > p)

for mode in (:montecarlo, :reduction, :safe)
@show mode
unsafe_comparisons(mode, verbose=false)
@test p<100+p
@test p+100>p
@test p+100>=p
@test p<=100+p

@test p<100
@test 100>p
@test 100>=p
@test p<=100
@unsafe begin
@test -10 < p
@test p <= p
@test p >= p
@test !(p < p)
@test !(p > p)
@test (p < 1+p)
@test (p+1 > p)
end
end

@test_throws ErrorException p<p
@test_throws ErrorException p>p
@test_throws ErrorException @unsafe error("") # Should still be safe after error

@test_throws ErrorException p>=p
@test_throws ErrorException p<=p
unsafe_comparisons(:montecarlo, verbose=false)
@test p>=p
@test p<=p
@test !(p<p)
@test !(p>p)

unsafe_comparisons(false)

@unsafe tv = 2
@test tv == 2
@unsafe tv1,tv2 = 1,2
Expand Down

0 comments on commit da59bd8

Please sign in to comment.