Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ Primes = "27ebfcd6-29c5-5fa9-bf4b-fb8fc14df3ae"
QuadGK = "1fd47b50-473d-5c70-9696-f719f8f3bcdc"
QuasiMonteCarlo = "8a4e6c94-4038-4cdc-81c3-7e6ffdb2a71b"
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
RecipesBase = "3cdcf5f2-1ef4-517c-9805-6587b60abb01"
Reexport = "189a3867-3050-52da-a836-e630ba90ab69"
Roots = "f2b01f46-fcfa-551c-844a-d8ac1e96c665"
Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"
Expand Down
24 changes: 24 additions & 0 deletions demo/plotting/plot_inputs.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using UncertaintyQuantification, Plots

X1 = RandomVariable(ProbabilityBox{Normal}(Dict(:μ => Interval(-1, 2), :σ => 1)), :X1)
X2 = RandomVariable(ProbabilityBox{Normal}(Dict(:μ => Interval(-1, 2), :σ => 2)), :X2)
X3 = IntervalVariable(-1, 2, :s)
h = RandomVariable(Normal(0.24, 0.01), :h) # height

plot(X1) # p-box
plot(X1, color = "red") # red p-box
plot(X3) # Interval
plot(h) # distribution

inputs = [X1, X2, X3, h]

plot(inputs) # Everything together

samples = sample(inputs, 200)

plot(X1)
plot!(samples.X1) # Samples ecdf samples of X1

plot(samples.X1[1], samples.X2[1]) # Plots 2D box

plot(samples.X1, samples.X2) # Plots bivariate random sets of X1 and X2
8 changes: 8 additions & 0 deletions src/UncertaintyQuantification.jl
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ using Random
using Reexport
using Roots
using StatsBase
using RecipesBase

@reexport using Distributions

Expand Down Expand Up @@ -231,4 +232,11 @@ include("reliability/probabilityoffailure.jl")
include("reliability/probabilityoffailure_imprecise.jl")
include("sensitivity/sobolindices.jl")

include("plotting/plot_recipes.jl")

include("util/fourier-transform.jl")
Copy link
Contributor

Choose a reason for hiding this comment

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

Why did you move these util includes down here? It's probably not to important, but I think they can stay at the top.

Copy link
Contributor

Choose a reason for hiding this comment

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

You defined a lo(X::Real) and hi(X::Real) function but I don't think you use it anywhere.

Do you really need a function for the .lb and .ub of the Interval? To get the bounds of a Vector{Interval} you could instead use getproperty.(X, :lb) and getproperty.(X, :ub).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks for the review 👍 I think you're right

On the last one, I do find it convenient to have hi and lo functions for intervals and reals. It lets you ask for bounds on random sample of inputs, without knowing which elements are interval or real.

Copy link
Contributor

Choose a reason for hiding this comment

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

In that case, keep them 👍

include("util/wrap.jl")
include("util/imprecise.jl")
include("util/kde.jl")

end
5 changes: 5 additions & 0 deletions src/inputs/imprecise/interval.jl
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,11 @@ function bounds(i::Interval)
return i.lb, i.ub
end

hi(X::Interval) = X.ub
lo(X::Interval) = X.lb
hi(X::Real) = X
lo(X::Real) = X

"""
IntervalVariable(lb::Real, ub::Real, name::Symbol)

Expand Down
292 changes: 292 additions & 0 deletions src/plotting/plot_recipes.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,292 @@
DEFAULT_ALPHA = 0.2
DEFAULT_LABEL = ""
DEFAULT_GRID = false
DEFAULT_LEGEND = true
DEFAULT_CDF = false
DEFAULT_FILL_DISTRIBUTION=true
DEFAULT_FILL_IMPRECISE=true
DEFAULT_DISTRIBUTION = :pdf
DEFAULT_FILL = :gray
DEFAULT_COLOUR_PDF = :blue
DEFAULT_COLOUR_UPPER = :red
DEFAULT_COLOUR_LOWER = :black

DEFAULT_INTERVAL_WIDTH=1.5
DEFAULT_INTERVAL_EDGE_ALPHA=1

DEFAULT_DISTRIBUTION_WIDTH=2

DEFAULT_PLOT_RANGE_EXTEND_DENSITY = 0.2
DEFAULT_PLOT_RANGE_EXTEND = 0.2
DEFAULT_PLOT_RANGE_INTERVAL = 0.4
DEFAULT_PLOT_GRID_NUMBER = 500
DEFAULT_FONT_SIZE = 18
DEFAULT_TICK_SIZE = 12

###
# Plots for UQInputs
###
@recipe function _plot(
x::RandomVariable{T}; cdf_on=DEFAULT_CDF, shade=DEFAULT_FILL_DISTRIBUTION
) where {T<:UnivariateDistribution}
grid --> DEFAULT_GRID
legend --> DEFAULT_LEGEND
label --> String(x.name)
(cdf_on ? ylabel --> "cdf" : ylabel --> "pdf")
seriescolor --> :auto

lo = quantile(x, 0.001)
hi = quantile(x, 0.999)
w = hi - lo
lo -= abs(w * DEFAULT_PLOT_RANGE_EXTEND_DENSITY)
hi += abs(w * DEFAULT_PLOT_RANGE_EXTEND_DENSITY)

xs = range(lo, hi, DEFAULT_PLOT_GRID_NUMBER)
ys = cdf_on ? cdf.(Ref(x), xs) : pdf.(Ref(x), xs)

# Primary line: either cdf or pdf
@series begin
seriestype := :path
fill := nothing
alpha --> 1
linewidth --> DEFAULT_DISTRIBUTION_WIDTH
label --> String(x.name)
xs, ys
end

# Optional fill for PDF only: reuse color, don't advance palette
if !cdf_on && shade
@series begin
primary := false # <-- reuse the color from the primary line
seriestype := :path
fillrange := 0 # fill down to baseline
fillcolor := :match
fillalpha --> DEFAULT_ALPHA
linewidth --> 0 # fill only; no extra line
label := ""
xs, ys
end
end
end

@recipe function _plot(x::IntervalVariable; shade=DEFAULT_FILL_IMPRECISE)
# --- plot-level defaults (soft) ---
grid --> DEFAULT_GRID
legend --> DEFAULT_LEGEND
ylabel --> "cdf"
label --> String(x.name) # single legend entry

seriescolor --> :auto

lo_grid = x.lb
hi_grid = x.ub

width = hi_grid - lo_grid

plot_lo = lo_grid - abs(width * DEFAULT_PLOT_RANGE_INTERVAL) # For adding a slight width to the left and right side of the interval plot
plot_hi = hi_grid + abs(width * DEFAULT_PLOT_RANGE_INTERVAL)

x_grid = range(lo_grid, hi_grid, DEFAULT_PLOT_GRID_NUMBER)

cdf_lo = x_grid .>= x.ub
cdf_hi = x_grid .> x.lb

# Plot upper cdf (primary, inherits colour and label)
@series begin
seriestype := :path
alpha --> 1
linewidth --> DEFAULT_DISTRIBUTION_WIDTH
x_grid, cdf_hi
end

# Plot lower cdf
@series begin
primary := false
seriestype := :path
alpha --> 1
label := ""
linewidth --> DEFAULT_DISTRIBUTION_WIDTH
x_grid, cdf_lo
end

# Add invisible lines so interval bounds don't touch plot boundaries
@series begin
primary := false
seriestype := :path
alpha --> 0
label := ""
linewidth --> 0
range(plot_lo, plot_hi, 100), zeros(100)
end

# Plot fill
if shade
@series begin
primary := false
seriestype := :path
fillcolor := :match # match this series' line color
fillrange := cdf_hi
color := DEFAULT_FILL
fillalpha := DEFAULT_ALPHA
linewidth --> 0 # draw fill only here
label := ""
x_grid, cdf_lo
end
end
end

@recipe function _plot(
x::RandomVariable{T}; shade=DEFAULT_FILL_IMPRECISE
) where {T<:ProbabilityBox}
# --- plot-level defaults (soft) ---
grid --> DEFAULT_GRID
legend --> DEFAULT_LEGEND
ylabel --> "cdf"
label --> String(x.name) # single legend entry

seriescolor --> :auto

lo_grid = quantile(x, 0.001).lb
hi_grid = quantile(x, 0.999).ub
width = hi_grid - lo_grid
lo_grid = lo_grid - abs(width * DEFAULT_PLOT_RANGE_EXTEND)
hi_grid = hi_grid + abs(width * DEFAULT_PLOT_RANGE_EXTEND)

x_grid = range(lo_grid, hi_grid, DEFAULT_PLOT_GRID_NUMBER)
cdf_evals = cdf.(Ref(x), x_grid)

# Plot upper cdf (primary, inherits colour and label)
@series begin
seriestype := :path
alpha --> 1
linewidth --> DEFAULT_DISTRIBUTION_WIDTH
x_grid, hi.(cdf_evals)
end

# Plot lower cdf
@series begin
primary := false
seriestype := :path
alpha --> 1
linewidth --> DEFAULT_DISTRIBUTION_WIDTH
label := ""
x_grid, lo.(cdf_evals)
end

# Plot fill
if shade
@series begin
primary := false
seriestype := :path
fillcolor := :match # match this series' line color
fillrange := hi.(cdf_evals)
fillalpha --> DEFAULT_ALPHA
linewidth --> 0 # draw fill only here
label := ""
x_grid, lo.(cdf_evals)
end
end
end

@recipe function _plot(x::Vector{T}) where {T<:UQInput}
# Filter out Parameter objects
x_no_params = filter(xi -> !isa(xi, Parameter), x)

N = length(x_no_params)
cols = ceil(Int, sqrt(N))
rows = ceil(Int, N / cols)
layout --> (rows, cols)

# Choose a grid palette once (users can still override via plot(...; palette=...))
# palette --> :default # or :default, :Dark2_8, etc.

for i in 1:N
@series begin
subplot := i
seriescolor --> i # <-- panel i uses the i-th color from the current palette
x_no_params[i]
end
end
end

###
# This code is a modified version of the plot recipe from IntervalArithmetic.jl
# https://github.com/JuliaIntervals/IntervalArithmetic.jl
###

# Plot a 2D IntervalBox:
@recipe function _plot(x::Interval, y::Interval)
seriesalpha --> DEFAULT_ALPHA
seriestype := :shape

label := false

linecolor --> :black # Explicitly set edge color
linewidth --> DEFAULT_INTERVAL_WIDTH # Make edges more visible
linealpha --> DEFAULT_INTERVAL_EDGE_ALPHA

x = [x.lb, x.ub, x.ub, x.lb]
y = [y.lb, y.lb, y.ub, y.ub]

x, y
end

# Plot a vector of 2D IntervalBoxes:
@recipe function _plot(xx::Vector{T}, yy::Vector{T}) where {T<:Interval}
seriesalpha --> DEFAULT_ALPHA
seriestype := :shape

label := false

linecolor := :black # Explicitly set edge color
linewidth --> DEFAULT_INTERVAL_WIDTH # Make edges more visible
linealpha --> DEFAULT_INTERVAL_EDGE_ALPHA

xs = Float64[]
ys = Float64[]

# build up coords: # (alternative: use @series)
for i in 1:length(xx)
(x, y) = (xx[i], yy[i])

# use NaNs to separate
append!(xs, [x.lb, x.ub, x.ub, x.lb, NaN])
append!(ys, [y.lb, y.lb, y.ub, y.ub, NaN])
end

xs, ys
end

###
# Plots for samples of data frames
###

@recipe function _plot(x::Vector{Interval})
if length(unique(x))==1
return x[1]
else
grid --> DEFAULT_GRID
legend --> DEFAULT_LEGEND

# xlabel --> x[1].name
ylabel --> "cdf"
N_samples = length(x)

lows = sort(lo.(x))
his = sort(hi.(x))

is = range(0, 1, length=N_samples)

@series begin
seriestype := :steppre
color --> DEFAULT_COLOUR_LOWER
lows, is
end

@series begin
seriestype := :steppost
color --> DEFAULT_COLOUR_UPPER
his, is
end
end
end
1 change: 1 addition & 0 deletions test/Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ Distributed = "8ba89e20-285c-5b6f-9357-94700520ee1b"
HCubature = "19dc6840-f33b-545b-b366-655c7e3ffd49"
HypothesisTests = "09f84164-cd44-5f33-b23f-e6b0d136a0d5"
InteractiveUtils = "b77e0a4c-d291-57a0-90e8-8db25a27a240"
Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80"
QuasiMonteCarlo = "8a4e6c94-4038-4cdc-81c3-7e6ffdb2a71b"
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
Revise = "295af30f-e4ad-537b-8983-00126c2a3abe"
Expand Down
8 changes: 8 additions & 0 deletions test/inputs/imprecise/interval.jl
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
using UncertaintyQuantification: hi, lo

@testset "Interval" begin
name = :l
lb = 0.14
Expand All @@ -16,6 +18,12 @@
@test !(0.17 ∈ interval)

@test sprint(show, interval) == "[0.14, 0.16]"

@test hi(interval) == interval.ub
@test lo(interval) == interval.lb

@test hi(2.0) == 2.0
@test lo(2.0) == 2.0
end

@testset "IntervalVariable" begin
Expand Down
Loading
Loading