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

[WIP] Evaluation distance to set #1023

Closed
wants to merge 30 commits into from

Conversation

matbesancon
Copy link
Contributor

Fairly straightforward, just like eval_variables evaluates the value of a function with a given value mapping of the variables, this PR adds the evaluation of values belonging to a set.

I reused the Base.in function, which seemed appropriate here since it's not used elsewhere with this signature.

One thing to work out as pointed by @mtanneau is the propagation of absolute and relative tolerance

@matbesancon matbesancon changed the title Evaluation of values belonging to sets [WIP] Evaluation of values belonging to sets Feb 11, 2020
@codecov-io
Copy link

codecov-io commented Feb 11, 2020

Codecov Report

Merging #1023 into master will decrease coverage by 0.19%.
The diff coverage is 91.3%.

Impacted file tree graph

@@           Coverage Diff            @@
##           master   #1023     +/-   ##
========================================
- Coverage   95.19%     95%   -0.2%     
========================================
  Files         100     100             
  Lines       11279   11544    +265     
========================================
+ Hits        10737   10967    +230     
- Misses        542     577     +35
Impacted Files Coverage Δ
src/sets.jl 80.84% <91.3%> (-14.45%) ⬇️
src/Bridges/Constraint/geomean.jl 95.23% <0%> (-3.78%) ⬇️
src/FileFormats/MOF/nonlinear.jl 100% <0%> (ø) ⬆️
src/Bridges/Constraint/det.jl 100% <0%> (ø) ⬆️
src/Test/intlinear.jl 100% <0%> (ø) ⬆️
src/Test/contconic.jl 99.29% <0%> (+0.02%) ⬆️
src/Utilities/model.jl 94.91% <0%> (+0.04%) ⬆️
src/Utilities/mutable_arithmetics.jl 90.09% <0%> (+0.09%) ⬆️
src/FileFormats/MOF/write.jl 66.31% <0%> (+0.72%) ⬆️
... and 2 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 93cdc8a...b19f884. Read the comment docs.

@mlubin
Copy link
Member

mlubin commented Feb 11, 2020

IMO for this to be generally useful it needs to support tolerances in some form.

@matbesancon
Copy link
Contributor Author

Agreed, I wasn't sure yet how to propagate them, rtol and atol keywords similar to isapprox?

@odow
Copy link
Member

odow commented Feb 11, 2020

I would prefer a distance function which returned the distance from a point to feasibility inside the set.

function distance(x::T, set::LessThan{T}) where {T}
    return max(zero(T), x - set.upper)
end

function distance(x::T, set::Integer) where {T}
    return abs(x - round(Int, x))
end

function distance(x::Vector{T}, set::SOS1{T}) where {T}
    _, index = findmax(abs.(x))
    return sqrt(sum(x[i]^2 for i = 1:length(x) if i != index))
end

Feasibility checkers could then use these distances and some notion of tolerances.

@matbesancon
Copy link
Contributor Author

a notion of distance makes sense for convex sets, integer sets get more ambiguous

@odow
Copy link
Member

odow commented Feb 11, 2020

integer sets get more ambiguous

Sure. But if we have a notion of belonging to a set with a tolerance, then we need some corresponding distance to check against.

For some sets, e.g., SOSII, it isn't immediately obvious what a good distance is. L2 norm of the elements excluding the two largest adjacent absolute values?

We might even consider returning a vector to feasibility, rather than the norm. So a PSD constraint is the vector of eigenvalues min.(0.0, eigvals(X)).

That would let the feasibility checker choose an appropriate norm as well. In the SOSII case, it isn't apparent if you want to use the L2 norm.

@mtanneau
Copy link
Contributor

integer sets get more ambiguous

That can be the distance from x to the lattice, rather than from x to a continuous set.

Then, as odow seems to suggest, which norm to use could be up to the user.
In a flavor similar to norm(x, 2) or norm(x, Inf).

@mtanneau
Copy link
Contributor

One (probably obvious) point, maybe useful for the docs: for a constraint f(x) ∈ S, here we are talking of "feasibility" as the distance from f(x) to S.
The distance from x to the feasible set X = {x | f(x) ∈ S} can be something else entirely.

Thus, here we are really addressing the question of "how close is this constraint to being satisfied". (which is 100% fine by me)

@matbesancon
Copy link
Contributor Author

just to be sure: you mean we deal with distance in the image space, and not in the input space right?

@matbesancon
Copy link
Contributor Author

One more corner case, in the case of dimension mismatch, I simply returned false for set belonging. When using a distance, this would be +Inf I guess

@matbesancon
Copy link
Contributor Author

We might even consider returning a vector to feasibility, rather than the norm. So a PSD constraint is the vector of eigenvalues min.(0.0, eigvals(X)).

+1

@mtanneau
Copy link
Contributor

in the case of dimension mismatch

Why not throw a DimensionMismatch?

@matbesancon
Copy link
Contributor Author

Not a fan of proliferating thrown errors all over the place, but yeah seems appropriate with this change

@matbesancon
Copy link
Contributor Author

I've started a re-write up to NormSpectralCone.
One open question is whether to use min(a, b, f(x) - S) when requiring, say, a >= 0, b >= 0, f(x) in S

@matbesancon
Copy link
Contributor Author

note here that _distance returns an unsigned distance, which is then converted in set_distance

src/Utilities/set_eval.jl Outdated Show resolved Hide resolved
Copy link
Member

@odow odow left a comment

Choose a reason for hiding this comment

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

I would prefer that we define a distance_from_set(x, set) function and include them along-side the set definitions in set.jl. Then it's obvious which sets have distances and which don't.

If x is inside the set, the distance should be 0. We shouldn't measure distance from infeasibility.

src/Utilities/set_eval.jl Outdated Show resolved Hide resolved
src/Utilities/set_eval.jl Outdated Show resolved Hide resolved
src/Utilities/set_eval.jl Outdated Show resolved Hide resolved
src/Utilities/set_eval.jl Outdated Show resolved Hide resolved
@matbesancon
Copy link
Contributor Author

@odow the _distance function remains internal and computed the unsigned distance to avoid checking if >= 0 in every single method.
The external function is the top one, computing the distance, and then returning max(0, dist)

@odow
Copy link
Member

odow commented Feb 11, 2020

I understand the current split. I'm suggesting a public method for each set, with no re-direction to the zero-checker. Maybe a few more lines of code, but easier to understand for each set in isolation.

@matbesancon
Copy link
Contributor Author

Ok yes I see your point, will do that. I still think we should keep the non-checked version, which would be comparable to a noargcheck or @inbounds``

@odow
Copy link
Member

odow commented Feb 11, 2020

The zero check is essentially free. No one should be using this in any performance-critical code, and even if they are, max(0, x) isn't going to be the bottleneck. I'm in favor of simple as possible, one-way-to-do-things. You should wait for someone else to chime in though, in case I'm by myself on this.

@matbesancon
Copy link
Contributor Author

Maybe a few more lines of code, but easier to understand for each set in isolation.

the bother with this is mostly that it means maintaining boilerplate over all sets instead of just at one place. That's why one function could do the dimension check, possibly throwing an error, call the set-specific method and return the cleaned-up version, otherwise these three steps have to be copy-pasted all over the place

@matbesancon
Copy link
Contributor Author

weird sets can define their distance in whatever "best" way for them (indicator constraints are an example)

@matbesancon
Copy link
Contributor Author

Open questions for distances to set:
To extend the distance system, do we need a fallback for a new distance to the default one?
To extend sets, do you extend only the default distance?

@odow
Copy link
Member

odow commented May 16, 2020

Yes, presumably we have a fallback like:

function distance_to_set(::AbstractDistanceFunction, x, s)
    return distance_to_set(DefaultDistance(), x, s)
end

Note that DistanceFunction should probably be renamed AbstractDistanceFunction.

@matbesancon
Copy link
Contributor Author

matbesancon commented May 16, 2020

Another issue comes from complementarity constraints:

MOI.Complements() requires access to variable bounds to determine if a given vector of values
respects the complementarity constraint, which is not possible with the current signature.
A possible fix would be accessing the variable bounds in a generic way.
Another would be to specify complementarity constraints with the associated bounds Complementarity(Vector{<:AbstractSet}).

@odow
Copy link
Member

odow commented May 16, 2020

The main reason why complementary bounds are defined separately is to avoid issues like the following, where declare bounds twice.

model = Model()
@variable(model, 1 <= x <= 2)
@constraint(model, 2x - 1  { x >= 0})

It seems reasonable that we wouldn't implement the DefaultDistance for complements. They're a bit if a special case. You could imagine defining a new distance function that required the l and u bound vectors as input.

@matbesancon
Copy link
Contributor Author

Yes agreed, even though it makes Complements a bit of a corner case for many things outside of distance, because of this non-self-contained constraint

Copy link
Member

@odow odow left a comment

Choose a reason for hiding this comment

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

I think we still need to nail down the theoretical concept of the distance we are measuring, particularly for some of the conic sets with strict inequalities.

For some sets you can get closed-form expressions for the minimum L2 distance (e.g., #1023 (comment)), and I think we should use these where possible.

src/sets.jl Outdated
@@ -1,6 +1,8 @@
# Sets

# Note: When adding a new set, also add it to Utilities.Model.
import LinearAlgebra
using LinearAlgebra: dot
Copy link
Member

Choose a reason for hiding this comment

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

Why import dot but not norm2? Just prefix everything with LinearAlgebra.

src/sets.jl Outdated
distance_to_set(v, s)

Compute the distance of a value to a set.
For some vector-valued sets, can return a vector of distances.
Copy link
Member

Choose a reason for hiding this comment

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

This is still an issue.


Distance function used to evaluate the distance from a point to a set.
New subtypes of `AbstractDistance` must implement fallbacks for sets they don't cover and implement
`distance_to_set(::Distance, v, s::S)` for sets they override the distance for.
Copy link
Member

Choose a reason for hiding this comment

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

I thought we were providing the AbstractDistance fallback?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is still an issue.

mistake on my part this should be removed

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I thought we were providing the AbstractDistance fallback?

we can provide one d(::AbstractDistance, v, s) = d(::DefaultDistance, v, s) but not be more specific because of method ambiguity

src/sets.jl Outdated
t = v[1]
xs = v[2:end]
result = LinearAlgebra.norm2(xs) - t
return max(result, zero(result)) # avoids sqrt
Copy link
Member

Choose a reason for hiding this comment

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

What is the comment # avoids sqrt for?

I have a general unresolved concern over our choice of distance. It seems like this is the "epigraph violation" distance, which is not the same as the Euclidean distance to the set. What is the precedent for how other solvers measure constraint feasibility? (@chriscoey / @lkapelevich how does Hypatia?)

Copy link
Contributor

Choose a reason for hiding this comment

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

I personally use the epigraph violation d = x1 - norm(x[2:end], 2), mainly because

  • it's easy to compute
  • it tells by how much the conic constraint should be "relaxed" for the point to be feasible (where "relaxed" means that you shift the cone's apex)
  • if the SOC constraint is written as x2^2 + ... xn^2 <= x1^2, the epigraph violation is the square root of this quadratic constraint's absolute violation.

Looks like COSMO uses euclidean distance for computing projections.

Copy link
Member

Choose a reason for hiding this comment

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

What about sets like the exponential cone needing domain constraints like y > 0?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

What is the comment # avoids sqrt for?

legacy comment, just removed. For domain constraints it can be an open question, because some parts of the distance may be non-computable / infinite or complex if the domain is not respected

Copy link
Member

Choose a reason for hiding this comment

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

Okay, so for conic sets "Epigraph violation or Inf if point fails feasibility bounds" seems like an easily computable and sensible definition.

Copy link
Member

Choose a reason for hiding this comment

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

And you call it EpigraphDistance or something like...

Copy link
Member

Choose a reason for hiding this comment

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

For norm two distances you can use what proximal solvers do:
https://web.stanford.edu/~boyd/papers/pdf/prox_algs.pdf page 183 (section 6.3)

Copy link
Member

Choose a reason for hiding this comment

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

So, SOC is easy, PSD you just need eigen decomposition, Power and Exponential are well posed even with the extra constraints, but need a Newton...

src/sets.jl Outdated
u = v[2]
xs = v[3:end]
return LinearAlgebra.norm2(
(max(-t, zero(t)), max(-u, zero(u)), max(dot(xs,xs) - 2 * t * u))
Copy link
Member

Choose a reason for hiding this comment

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

I think we need to think this through a little. What space is this a distance in?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The core issue here is that the cone is characterized by three constraints.
With vector-distance, this would have been:

max(-u, 0)
max(-v, 0)
max(-2tu + norm_squared(x) , 0)

since we don't have that, the l2-norm of these three epigraph violations is the thing computed here.

return d
end
end
end
Copy link
Member

Choose a reason for hiding this comment

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

I don't think this function is correct. Something like:

function distance_to_set(::DefaultDistance, v::AbstractVector{T}, ::SOS1{T}) where {T <: Real}
    _check_dimension(v, s)
    _, i = findmax(abs.(v))
    return LinearAlgebra.norm2([v[j] for j = 1:length(v) if j != i])
end

@chriscoey
Copy link
Contributor

My comment here isn't actually very helpful but: Hypatia doesn’t use a notion of distance to set anywhere. It’s tough because there are many notions of distance and the right one depends on context. Also not all conic sets are typically defined by epigraph/hypographs, e.g. the PSD cone or the set of nonnegative polynomials or the copositive cone.

@chriscoey
Copy link
Contributor

Also for many sets the Euclidean distance can be hard to compute (eg for exponential cone in general, though some "cases" are just violation on variable nonnegativity)

@joaquimg
Copy link
Member

I will put it here, so it does not gets lost in the inner conversation:

For norm two distances you can use what proximal solvers do:
https://web.stanford.edu/~boyd/papers/pdf/prox_algs.pdf page 183 (section 6.3)

So, SOC is easy, PSD you just need eigen decomposition, Power and Exponential are well posed even with the extra constraints, but need a Newton...

@joaquimg
Copy link
Member

Many of these projections are implemented here: https://github.com/kul-forbes/ProximalOperators.jl/tree/master/src/functions

@odow
Copy link
Member

odow commented May 20, 2020

Considering this PR is approaching the longest PR in the history of MOI (which @matbesancon also holds first and second place in #709 and #877), maybe this is a sign it should be in a package first. Then @matbesancon can experiment with the different set definitions in peace, not worry about pulling in heavy dependencies for the proximal operators or the SVD, and see what works.

Edit: I will note, beginning the PR with "Fairly straightforward," was an understatement 😆

@matbesancon
Copy link
Contributor Author

maybe this is a sign it should be in a package first

do you mean the distances themselves or the advanced features?

Dear 2-month-ago self, what what you done?

@odow
Copy link
Member

odow commented May 20, 2020

I mean the distances, the feasibility checker, everything. Call it matbesancon/InfeasibilityToolkit.jl.

This approach worked for Dualization.jl, and we brought the dual_set definitions in once they stabilized.

@matbesancon
Copy link
Contributor Author

I should have a skeleton more or less there for Friday's meeting

@matbesancon
Copy link
Contributor Author

So, with the heart broken, we can close this PR which is replaced by https://github.com/matbesancon/MathOptSetDistances.jl

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

None yet

8 participants