Open schillic opened 6 years ago
i like option 3 as well. maybe a macro can be used like @concrete begin ... end
, such that A ā B
-> minkowski_sum(A, B, lazy=false)
.
Good idea š
This issue should resolve the lazy/concrete intersection usage. Currently we have:
julia> begin
E = Ellipsoid(ones(2), diagm([2.0, 0.5]))
B = Ball1([2.5, 1.5], .8)
import LazySets.Approximations.overapproximate
polyoverapprox(x) = HPolytope(overapproximate(x, 1e-3).constraints)
Epoly = polyoverapprox(E)
Bpoly = polyoverapprox(B)
X = intersect(Epoly, Bpoly)
end
julia> @which intersect(Epoly, Bpoly)
intersect(X, Y) in LazySets at /Users/forets/.julia/v0.6/LazySets/src/Intersection.jl:36
julia> using Polyhedra
julia> @which intersect(Epoly, Bpoly)
intersect(P1::LazySets.HPolytope{N}, P2::LazySets.HPolytope{N}) where N<:Real in LazySets at /Users/forets/.julia/v0.6/LazySets/src/HPolytope.jl:272
I think it is better to reserve Intersection(Epoly, Bpoly)
for the lazy intersection, compare e.g. to LinearMap
, while reserving intersect
, or intersection
, for the concrete operations.
Meanwhile, @concrete Intersection(Epoly, Bpoly)
should trigger the concrete function, if it exists.
Proposal: add a function concretize
which, given a set, constructs a concrete set by applying the concrete counterpart (e.g., minkowski_sum
for a MinkowskiSum
object) and (recursively!) calling concretize
on the operands.
Then the proposed @concrete X ā© Y ā Z
would just translate to concretize(X ā© Y ā Z)
. There is of course the overhead of creating the lazy objects but this macro should not be used in library code anyway.
The following techniques are relevant to this discussion: https://github.com/JuliaIntervals/IntervalArithmetic.jl/issues/355#issuecomment-582638887 and https://github.com/JuliaIntervals/IntervalArithmetic.jl/issues/355#issuecomment-582649415
EDIT. This is a proof of principle using Cassette:
using LazySets, Cassette
Cassette.@context ConcreteMinkowskiSumCtx
const concrete_minkowski_sum_ctx = Cassette.disablehooks(ConcreteMinkowskiSumCtx())
Cassette.overdub(::ConcreteMinkowskiSumCtx, ::typeof(+), X::LazySet, Y::LazySet) = LazySets.minkowski_sum(X, Y)
concrete_minkowski_sum(f) = Cassette.overdub(concrete_minkowski_sum_ctx, f)
concrete_minkowski_sum() do
X, Y = rand(VPolygon), rand(VPolygon)
X + Y
end
VPolygon{Float64,Array{Float64,1}}(Array{Float64,1}[[-4.387680426840783, 2.877203090673391], [...]]
concretize
was added in #2228.
concretize
can be thought as an expression transformation from CamelCase to underscore (the module StringCases.jl contains such transformations). so i would suggest to consider using a generated function (or a macro,@concretize
) that implements such rule once (ie. dispatch onLazySet
)
Originally posted by @mforets in https://github.com/JuliaReach/LazySets.jl/pull/2228#issuecomment-660794543
Here are other suggestions (from slack):
make lazy the default and move all other functions into a LazySets.Eager module where it could be used manually.
use materialize or collect (which kinda act like the Base operations for evaluating the lazy operations).
pass a keyword argument which would make a call eager if wanted.
In #148 we added functions like
minkowski_sum
. (The following is copied from the discussion started in #174.)Why did we not add a
MinkowskiSum
constructor forZonotope
s? Answer: Because we wanted to reserve theMinkowskiSum
constructor for the lazy operation.I think that currently the design is inconsistent. For some lazy operations we actually do a concrete operation (like
LinearMap(lm::LinearMap)
,MinkowskiSum(E::EmptySet, S::LazySet)
) and for some concrete operations and only some types we have another function (likeminkowski_sum
). Why should the user know what to use? (in particular in an application where you do not know the set type in advance!)I was thinking if we should have this:
Then we could remove the specific
MinkowskiSum
constructors likeMinkowskiSum(E::EmptySet, S::LazySet)
. The user would use this new function instead and "under the hood" we decide what happens (lazy or concrete).But now I realized that this is a design decision and I do not have an opinion here. Previously you had to call the constructor
MinkowskiSum
and it either created a lazyMinkowskiSum
object or, in special cases, skipped it (EmptySet
). Now we have the mixture (sometimes you have to call the constructor and sometimes it is concrete; sometimes you have to call this function to get the concrete implementation).The question is if we want to give the user the freedom to choose between lazy and concrete set operations. I see three options for who decides between lazy and concrete operations:
MinkowskiSum
constructor). In that case we should remove the functionminkowski_sum
wrote and add a respective constructor.MinkowskiSum
is always lazy; the functionminkowski_sum
is always concrete if possible (and lazy otherwise).We currently prefer the most general answer (3.), but it requires some discussion. Example implementation: The constructor
MinkowskiSum
is always lazy; the functionminkowski_sum
gets a new argument from the domain {"lazy", "concrete", "default"} with default value "default". Alternative: Have three functionsminkowski_sum_lazy
,minkowski_sum_concrete
, andminkowski_sum
.