diff --git a/Project.toml b/Project.toml index c7331562dd..ff79142037 100644 --- a/Project.toml +++ b/Project.toml @@ -28,6 +28,7 @@ GLPKMathProgInterface = "≥ 0.4.0" GR = "≥ 0.39.1" IntervalArithmetic = "≥ 0.14.0" JuMP = "≥ 0.19.2" +Makie = "≥ 0.9.4" MathProgBase = "≥ 0.7.0" Optim = "≥ 0.14.1" Plots = "≥ 0.25.1" @@ -44,6 +45,7 @@ Expokit = "a1e7a1ef-7a5d-5822-a38c-be74e1bb89f4" GLPK = "60bf3e95-4087-53dc-ae20-288a0d20c6a6" GR = "28b8d3ca-fb5f-59d9-8090-bfdbd6d07a71" JuMP = "4076af6c-e467-56ae-b986-b466b2749572" +Makie = "ee78f7c6-11fb-53f2-987a-cfe4a2b5a57a" Optim = "429524aa-4258-5aef-a3af-852621145aeb" Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80" Polyhedra = "67491407-f73d-577b-9b50-8179a7c68029" @@ -51,5 +53,5 @@ TaylorModels = "314ce334-5f6e-57ae-acf6-00b6e903104a" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" [targets] -test = ["CDDLib", "Distributions", "Documenter", "Expokit", "GLPK", "GR", "JuMP", "Optim", "Plots", "Polyhedra", - "TaylorModels", "Test"] +test = ["CDDLib", "Distributions", "Documenter", "Expokit", "GLPK", "GR", "JuMP", + "Makie", "Optim", "Plots", "Polyhedra", "TaylorModels", "Test"] diff --git a/src/LazySets.jl b/src/LazySets.jl index 0fc62b3440..1ec9d07e56 100644 --- a/src/LazySets.jl +++ b/src/LazySets.jl @@ -26,6 +26,7 @@ include("helper_functions.jl") include("comparisons.jl") include("macros.jl") include("samples.jl") +include("mesh.jl") # ================== # Abstract set types diff --git a/src/init.jl b/src/init.jl index 81d9b25402..4740007f6d 100644 --- a/src/init.jl +++ b/src/init.jl @@ -3,6 +3,7 @@ function __init__() @require Optim = "429524aa-4258-5aef-a3af-852621145aeb" load_optim() @require Polyhedra = "67491407-f73d-577b-9b50-8179a7c68029" load_polyhedra() @require Distributions = "31c24e10-a181-5473-b8eb-7969acd0382f" load_distributions() + @require Makie = "ee78f7c6-11fb-53f2-987a-cfe4a2b5a57a" load_makie() end function load_expokit() @@ -20,12 +21,25 @@ function load_polyhedra() eval(load_polyhedra_hpolytope()) eval(load_polyhedra_hpolyhedron()) eval(load_polyhedra_vpolytope()) + initialize_mesh() end function load_distributions() eval(load_distributions_samples()) end +function load_makie() + initialize_mesh() +end + +function initialize_mesh() + if isdefined(@__MODULE__, :Polyhedra) && + isdefined(@__MODULE__, :Makie) + + eval(load_mesh()) + end +end + """ require(package::Symbol; fun_name::String="", explanation::String="") diff --git a/src/mesh.jl b/src/mesh.jl new file mode 100644 index 0000000000..599eebbeb9 --- /dev/null +++ b/src/mesh.jl @@ -0,0 +1,140 @@ +function load_mesh() +return quote + +using .Polyhedra: Mesh +using .Makie: mesh, mesh! +import .Makie.AbstractPlotting: Automatic + +export plot3d, plot3d! + +# helper function for 3D plotting; converts S to a polytope in H-representation +function plot3d_helper(S::LazySet{N}, backend) where {N} + @assert dim(S) <= 3 "plot3d can only be used to plot sets of dimension three (or lower); " * + "but the given set is $(dim(S))-dimensional" + + @assert applicable(constraints_list, S) "plot3d requires that the list of constraints of `S`, " * + "`constraints_list(S)` is applicable; try overapproximating with an `HPolytope` first" + + P = HPolytope(constraints_list(S)) + remove_redundant_constraints!(P) + P_poly = polyhedron(P, backend=backend) + P_poly_mesh = Mesh(P_poly) + return P_poly_mesh +end + +""" + plot3d(S::LazySet{N}; backend=default_polyhedra_backend(S, N), + alpha=1.0, color=:blue, colormap=:viridis, colorrange=Automatic(), + interpolate=false, linewidth=1, overdraw=false, shading=true, + transparency=true, visible=true) where {N} + +Plot a three-dimensional convex set using Makie. + +### Input + +- `S` -- convex set +- `backend` -- (optional, default: `default_polyhedra_backend(S, N)`) polyhedral + computations backend +- `alpha` -- (optional, default: `1.0`) float in `[0,1]`; the alpha or + transparency value +- `color` -- (optional, default: `:blue`) `Symbol` or `Colorant`; the color + of the main plot element (markers, lines, etc.) and it can be + a color symbol/string like `:red` +- `colormap` -- (optional, default: `:viridis`) the color map of the main plot; + call `available_gradients()` to see what gradients are available, + and it can also be used as `[:red, :black]` +- `colorrange` -- (optional, default: `Automatic()`) a tuple `(min, max)` where + `min` and `max` specify the data range to be used for indexing + the colormap +- `interpolate` -- (optional, default: `false`) a bool for heatmap and images, + it toggles color interpolation between nearby pixels +- `linewidth` -- (optional, default: `1`) a number that specifies the width of + the line in `line` and `linesegments` plots +- `overdraw` -- (optional, default: `false`) +- `shading` -- (optional, default: `true`) a boolean that specifies if shading + should be on or not (for meshes) +- `transparency` -- (optional, default: `true`) if `true`, the set is transparent + otherwise it is displayed as a solid object +- `visible` -- (optional, default: `true`) a bool that toggles visibility + of the plot + +For a complete list of attributes and usage see +[Makie's documentation](http://makie.juliaplots.org/stable/plot-attributes.html). + +### Notes + +This plot recipe works by computing the list of constraints of `S` and converting +to a polytope in H-representation. Then, this polytope is transformed with +`Polyhedra.Mesh` and it is plotted using the `mesh` function. + +If the function `constraints_list` is not applicable to your set `S`, try +overapproximation first; e.g. via + +```julia +julia> using LazySets.Approximations + +julia> Sapprox = overapproximate(S, SphericalDirections(10)) + +julia> plot3d(Sapprox) +``` +The number `10` above corresponds to the number of directions considered; for +better resolution use higher values (but it will take longer). + +For efficiency consider using the `CDDLib` backend, as in + +```julia +julia> using CDDLib + +julia> plot3d(Sapprox, backend=CDDLib.Library()) +``` + +### Examples + +The functionality requires *both* `Polyhedra` and `Makie`; so after +loading `LazySets`, do `using Makie, Polyhedra` (or `using Polyhedra, Makie`, the +order doesn't matter). + +```julia +julia> using LazySets, Makie, Polyhedra + +julia> plot3d(10. * rand(Hyperrectangle, dim=3)) + +julia> plot3d!(10. * rand(Hyperrectangle, dim=3), color=:red) +``` +""" +function plot3d(S::LazySet{N}; backend=default_polyhedra_backend(S, N), + alpha=1.0, color=:blue, colormap=:viridis, colorrange=Automatic(), interpolate=false, + linewidth=1, overdraw=false, shading=true, transparency=true, visible=true) where {N} + + P_poly_mesh = plot3d_helper(S, backend) + return mesh(P_poly_mesh, alpha=alpha, color=color, colormap=colormap, colorrange=colorrange, + interpolate=interpolate, linewidth=linewidth, transparency=transparency, visible=visible) +end + +""" + plot3d!(S::LazySet{N}; backend=default_polyhedra_backend(S, N), + alpha=1.0, color=:blue, colormap=:viridis, colorrange=Automatic(), interpolate=false, + linewidth=1, overdraw=false, shading=true, transparency=true, visible=true) where {N} + +Plot a three-dimensional convex set using Makie. + +### Input + +See `plot3d` for the description of the inputs. For a complete list of attributes +and usage see [Makie's documentation](http://makie.juliaplots.org/stable/plot-attributes.html). + +### Notes + +See the documentation of `plot3d` for examples. +""" +function plot3d!(S::LazySet{N}; backend=default_polyhedra_backend(S, N), + alpha=1.0, color=:blue, colormap=:viridis, colorrange=Automatic(), interpolate=false, + linewidth=1, overdraw=false, shading=true, transparency=true, visible=true) where {N} + + P_poly_mesh = plot3d_helper(S, backend) + return mesh!(P_poly_mesh, alpha=alpha, color=color, colormap=colormap, colorrange=colorrange, + interpolate=interpolate, linewidth=linewidth, transparency=transparency, visible=visible) +end + +end # quote +end # function load_mesh()