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

#1051 - Add more list of constraints methods #1053

Merged
merged 24 commits into from
Jan 24, 2019
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
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 docs/src/lib/interfaces.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ RecipesBase.apply_recipe(::Dict{Symbol,Any}, ::LazySet)
RecipesBase.apply_recipe(::Dict{Symbol,Any}, ::Vector{S}) where {S<:LazySet}
RecipesBase.apply_recipe(::Dict{Symbol,Any}, ::LazySet, ::Float64)
RecipesBase.apply_recipe(::Dict{Symbol,Any}, ::Vector{S}, ::Float64) where {S<:LazySet}
tosimplehrep(::LazySet)
```

### Set functions that override Base functions
Expand Down
1 change: 1 addition & 0 deletions docs/src/lib/operations.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ dim(::IntersectionArray)
isbounded(::IntersectionArray)
∈(::AbstractVector{N}, ::IntersectionArray{N}) where {N<:Real}
array(::IntersectionArray{N, S}) where {N<:Real, S<:LazySet{N}}
constraints_list(::IntersectionArray{N}) where {N<:Real}
```
Inherited from [`LazySet`](@ref):
* [`norm`](@ref norm(::LazySet, ::Real))
Expand Down
2 changes: 1 addition & 1 deletion docs/src/lib/representations.md
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,7 @@ constraints_list(::HalfSpace{N}) where {N<:Real}
constrained_dimensions(::HalfSpace{N}) where {N<:Real}
halfspace_left(::AbstractVector{N}, ::AbstractVector{N}) where {N<:Real}
halfspace_right(::AbstractVector{N}, ::AbstractVector{N}) where {N<:Real}
tosimplehrep(::AbstractVector{HalfSpace{N}}) where {N<:Real}
```
Inherited from [`LazySet`](@ref):
* [`norm`](@ref norm(::LazySet, ::Real))
Expand Down Expand Up @@ -437,7 +438,6 @@ dim(::HPoly{N}) where {N<:Real}
∈(::AbstractVector{N}, ::HPoly{N}) where {N<:Real}
addconstraint!(::HPoly{N}, ::LinearConstraint{N}) where {N<:Real}
constraints_list(::HPoly{N}) where {N<:Real}
tosimplehrep(::HPoly{N}) where {N<:Real}
tohrep(::HPoly{N}) where {N<:Real}
isempty(::HPoly{N}) where {N<:Real}
cartesian_product(::HPoly{N}, ::HPoly{N}) where {N<:Real}
Expand Down
5 changes: 0 additions & 5 deletions src/AbstractPolytope.jl
Original file line number Diff line number Diff line change
Expand Up @@ -124,11 +124,6 @@ function isempty(P::AbstractPolytope)::Bool
return isempty(vertices_list(P))
end

# TODO simplify this
function tosimplehrep(P::LazySet)
return tosimplehrep(HPolyhedron(constraints_list(P)))
end

function default_polyhedra_backend(P, N)
@assert isdefined(@__MODULE__, :Polyhedra) "this function needs the package 'Polyhedra' to be loaded"
error("no default backend for numeric type $N")
Expand Down
111 changes: 89 additions & 22 deletions src/HPolyhedron.jl
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ export HPolyhedron,
dim, σ, ∈,
addconstraint!,
constraints_list,
tosimplehrep,
schillic marked this conversation as resolved.
Show resolved Hide resolved
tohrep, tovrep,
convex_hull,
cartesian_product,
Expand Down Expand Up @@ -369,31 +368,33 @@ function constraints_list(P::HPoly{N}
end

"""
tosimplehrep(P::HPoly{N}) where {N}
tosimplehrep(constraints::AbstractVector{LinearConstraint{N}}) where {N<:Real}

Return the simple H-representation ``Ax ≤ b`` of a polyhedron.
Return the simple H-representation ``Ax ≤ b`` of a list of constraints.

### Input

- `P` -- polyhedron
- `constraints` -- a list of constraints

### Output

The tuple `(A, b)` where `A` is the matrix of normal directions and `b` are the
offsets.
"""
function tosimplehrep(P::HPoly{N}) where {N<:Real}
n = length(constraints_list(P))
function tosimplehrep(constraints::AbstractVector{LinearConstraint{N}}) where {N<:Real}
n = length(constraints)
if n == 0
A = Matrix{N}(undef, 0, 0)
b = Vector{N}(undef, 0)
return (A, b)
end
A = zeros(N, n, dim(P))
A = zeros(N, n, dim(first(constraints)))
b = zeros(N, n)
for (i, Pi) in enumerate(constraints_list(P))
A[i, :] = Pi.a
b[i] = Pi.b
@inbounds begin
for (i, Pi) in enumerate(constraints)
A[i, :] = Pi.a
b[i] = Pi.b
end
end
return (A, b)
end
Expand Down Expand Up @@ -430,11 +431,12 @@ Remove the redundant constraints in a polyhedron in H-representation.
### Output

A polyhedron equivalent to `P` but with no redundant constraints, or an empty set
if `P` is detected to be empty (which happens if the constraints are infeasible).
if `P` is detected to be empty, which may happen if the constraints are infeasible.

### Algorithm

See [`remove_redundant_constraints!`](@ref) for details.
See [`remove_redundant_constraints!(::Vector{LinearConstraint{N}})`](@ref) for
details.
"""
function remove_redundant_constraints(P::PT;
backend=GLPKSolverLP())::Union{PT, EmptySet{N}} where {N, PT<:HPoly{N}}
Expand All @@ -461,23 +463,58 @@ is updated in-place.
### Output

`true` if the method was successful and the polyhedron `P` is modified by
removing its redundant constraints, and `false` if `P` is detected to be empty
(which happens if the constraints are infeasible).
removing its redundant constraints, and `false` if `P` is detected to be empty,
which may happen if the constraints are infeasible.

### Algorithm

If the polyhedron `P` has `m` constraints and its dimension is `n`,
this function checks one by one if each of the `m` constraints is
implied by the remaining ones. To check if the `k`-th constraint
is redundant, an LP is formulated.
See [`remove_redundant_constraints!(::Vector{LinearConstraint{N}})`](@ref) for
details.
"""
function remove_redundant_constraints!(P::PT;
backend=GLPKSolverLP())::Bool where {N, PT<:HPoly{N}}
remove_redundant_constraints!(P.constraints, backend=backend)
schillic marked this conversation as resolved.
Show resolved Hide resolved
end

"""
remove_redundant_constraints!(constraints::Vector{LinearConstraint{N}};
backend=GLPKSolverLP())::Bool where {N}

Remove the redundant constraints of a given list of linear constraints; the list
is updated in-place.

### Input

- `constraints` -- list of constraints
- `backend` -- (optional, default: `GLPKSolverLP`) the numeric LP solver backend

### Output

`true` if the method was successful and the list of constraints `constraints` is
modified by removing the redundant constraints, and `false` if the constraints
are infeasible.

### Algorithm

If there are `m` constraints in `n` dimensions, this function checks one by one
if each of the `m` constraints is implied by the remaining ones.

To check if the `k`-th constraint is redundant, an LP is formulated using the
constraints that have not yet being removed. If, at an intermediate step, it is
detected that a subgruop of the constraints is infeasible, this function returns
`false`; if the calculation finished successfully it returns `true`.

Note that the constraints being infeasible does not imply that `false` is returned.
For example, `x <= 0 && x >= 1` will return `true` without removing any constraint.
To check if the constraints are infeasible use `isempty(HPolyhedron(constraints)`.

For details, see [Fukuda's Polyhedra
FAQ](https://www.cs.mcgill.ca/~fukuda/soft/polyfaq/node24.html).
"""
function remove_redundant_constraints!(P::PT;
backend=GLPKSolverLP())::Bool where {N, PT<:HPoly{N}}
function remove_redundant_constraints!(constraints::AbstractVector{LinearConstraint{N}};
backend=GLPKSolverLP())::Bool where {N}

A, b = tosimplehrep(P)
A, b = tosimplehrep(constraints)
m, n = size(A)
non_redundant_indices = 1:m

Expand Down Expand Up @@ -506,10 +543,40 @@ function remove_redundant_constraints!(P::PT;
end
end

deleteat!(P.constraints, setdiff(1:m, non_redundant_indices))
deleteat!(constraints, setdiff(1:m, non_redundant_indices))
return true
end

"""
remove_redundant_constraints(constraints::AbstractVector{LinearConstraint{N}};
backend=GLPKSolverLP())::Union{AbstractVector{LinearConstraint{N}}, EmptySet{N}} where {N}

Remove the redundant constraints of a given list of linear constraints.

### Input

- `constraints` -- list of constraints
- `backend` -- (optional, default: `GLPKSolverLP`) the numeric LP solver backend

### Output

The list of constraints with the redundant ones removed, or an empty set
if the given constraints are infeasible.

### Algorithm

See [`remove_redundant_constraints!`](@ref) for details.
"""
function remove_redundant_constraints(constraints::AbstractVector{LinearConstraint{N}};
backend=GLPKSolverLP())::Union{AbstractVector{LinearConstraint{N}}, EmptySet{N}} where {N}
constraints_copy = copy(constraints)
if remove_redundant_constraints!(constraints_copy, backend=backend)
return constraints_copy
else # the constraints are infeasible
return EmptySet{N}()
end
end

"""
linear_map(M::AbstractMatrix{N}, P::PT; [cond_tol=DEFAULT_COND_TOL]::Number)
where {N<:Real, PT<:HPoly{N}}
Expand Down
71 changes: 70 additions & 1 deletion src/Intersection.jl
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ export Intersection,
swap,
use_precise_ρ,
IntersectionArray,
array
array,
constraints_list

"""
IntersectionCache
Expand Down Expand Up @@ -510,6 +511,41 @@ function ∈(x::AbstractVector{N}, cap::Intersection{N})::Bool where {N<:Real}
return (x ∈ cap.X) && (x ∈ cap.Y)
end

"""
constraints_list(cap::Intersection{N, S1, S2}) where {N<:Real,
S1<:AbstractPolytope{N}, S2<:AbstractPolytope{N}}

Return the list of constraints of an intersection of two (polyhedral) sets.

### Input

- `cap` -- intersection of two (polyhedral) sets

### Output

The list of constraints of the intersection.

### Notes

We assume that the underlying sets are polyhedral, i.e., offer a method
`constraints_list`.

### Algorithm

We create the polyhedron by taking the intersection of the `constraints_list`s of
the sets and remove redundant constraints.

This function ignores the boolean output from the in-place `remove_redundant_constraints!`,
which may inform the user that the constraints are infeasible. In that case, the
list of constraints at the moment when the infeasibility was detected is returned.
"""
function constraints_list(cap::Intersection{N, S1, S2}) where {N<:Real,
S1<:AbstractPolytope{N}, S2<:AbstractPolytope{N}}
constraints = [constraints_list(cap.X); constraints_list(cap.Y)]
remove_redundant_constraints!(constraints)
schillic marked this conversation as resolved.
Show resolved Hide resolved
return constraints
end


# --- Intersection functions ---

Expand Down Expand Up @@ -704,6 +740,39 @@ function ∈(x::AbstractVector{N}, ia::IntersectionArray{N})::Bool where {N<:Rea
return true
end

"""
constraints_list(ia::IntersectionArray{N}) where {N<:Real}

Return the list of constraints of an intersection of a finite number of
(polyhedral) sets.

### Input

- `ia` -- intersection of a finite number of (polyhedral) sets

### Output

The list of constraints of the intersection.

### Notes

We assume that the underlying sets are polyhedral, i.e., offer a method
`constraints_list`.

### Algorithm

We create the polyhedron from the `constraints_list`s of the sets and remove
redundant constraints.
"""
function constraints_list(ia::IntersectionArray{N}) where {N<:Real}
constraints = Vector{LinearConstraint{N}}()
for X in array(ia)
append!(constraints, constraints_list(X))
end
remove_redundant_constraints!(constraints)
return constraints
end

# ==================================
# Algorithms for lazy intersection
# ==================================
Expand Down
27 changes: 26 additions & 1 deletion src/LazySet.jl
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ export LazySet,
an_element,
isbounded, isbounded_unit_dimensions,
neutral,
absorbing
absorbing,
tosimplehrep

"""
LazySet{N}
Expand Down Expand Up @@ -320,3 +321,27 @@ completely independent object. See the documentation of `?deepcopy` for further
details.
"""
copy(S::LazySet) = deepcopy(S)

"""
tosimplehrep(S::LazySet)

Return the simple H-representation ``Ax ≤ b`` of a set from its list of
constraints.

### Input

- `S` -- set

### Output

The tuple `(A, b)` where `A` is the matrix of normal directions and `b` are the
offsets.

### Notes

This function uses `constraints_list(S)`. It is a fallback implementation that
works only for those sets that can be represented exactly by a list of linear
constraints, which is available through the `constraints_list(S)`
function.
"""
tosimplehrep(S::LazySet) = tosimplehrep(constraints_list(S))
14 changes: 14 additions & 0 deletions test/unit_Intersection.jl
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,13 @@ for N in [Float64, Rational{Int}, Float32]
@test isempty_known(I)
@test !isempty(I)

# constraints_list for polytopic intersection
@test ispermutation(constraints_list(I),
[HalfSpace{Float64}(N[1, 0], N(2)),
HalfSpace{Float64}(N[0, 1], N(2)),
HalfSpace{Float64}(N[-1, 0], N(0)),
HalfSpace{Float64}(N[0, -1], N(0))])

# =================
# IntersectionArray
# =================
Expand Down Expand Up @@ -64,6 +71,13 @@ for N in [Float64, Rational{Int}, Float32]
# constructor with size hint and type
IntersectionArray(10, N)

# constraints_list for polytopic intersection
@test ispermutation(constraints_list(IA),
[HalfSpace{Float64}(N[1, 0], N(2)),
HalfSpace{Float64}(N[0, 1], N(2)),
HalfSpace{Float64}(N[-1, 0], N(0)),
HalfSpace{Float64}(N[0, -1], N(0))])

# ================
# common functions
# ================
Expand Down
Loading