Skip to content

Commit 2bd0383

Browse files
Add InfiniteInterpolate as an extension (#382)
* Update Fishing example docs * Adjust unit test of fishing example to test output values within tolerance * Update Hanging Chain example docs * Update Jennings example docs * Added unit tests to examples (excluding pandemic control) * Update tolerance to 1E-06 * Add InfiniteInterpolate with basic value function extension based on linear interpolation + test case * Add argument so that interpolation method is user defined * Add logic that throws an error for unsupported interpolation methods and handling for variables that don't rely on infinite parameters * Added unit tests for catching irregular grid method errors and checking interpolated values * Include InfiniteInterpolate test in runtests.jl * Add InfiniteInterpolate with basic value function extension based on linear interpolation + test case * Add argument so that interpolation method is user defined * Add logic that throws an error for unsupported interpolation methods and handling for variables that don't rely on infinite parameters * Added unit tests for catching irregular grid method errors and checking interpolated values * Include InfiniteInterpolate test in runtests.jl * Add docstring to InfiniteInterpolate ext & unit test for unsupported interpolation method error * Add InfiniteInterpolate with basic value function extension based on linear interpolation + test case * Add argument so that interpolation method is user defined * Add logic that throws an error for unsupported interpolation methods and handling for variables that don't rely on infinite parameters * Added unit tests for catching irregular grid method errors and checking interpolated values * Include InfiniteInterpolate test in runtests.jl * Add InfiniteInterpolate with basic value function extension based on linear interpolation + test case * Added unit tests for catching irregular grid method errors and checking interpolated values * Include InfiniteInterpolate test in runtests.jl * Add docstring to InfiniteInterpolate ext & unit test for unsupported interpolation method error * Add InfiniteInterpolate to Project.toml + use InfiniteInteprolate value func in hovercraft example + shorten doc string * Change Ipopt to mock optimizer in InfiniteInterpolate test case * Add semi-infinite, point, finite & derivative variables to InfiniteInterpolate test case * Add a small example on using InfiniteInterpolate in docs > src > guide > result.md * Add Interpolations.jl to [extras] and [targets] in Project.toml * Move Interpolations.jl import statement from runtests.jl to InfiniteInterpolate test file * Update results.md doc about type piracy issue with InfiniteInterpolate * Change Matrix datatype -> Array in InfiniteInterpolate wrapper function * Adjust documentation for InfiniteInterpolate in guide/result.md and Interpolations version in project.toml files * Add orthogonal collocation points to InfiniteInterpolate test case * Add degree + fallback functions to InfiniteInterpolate extension; update docs as necessary * Adjust InfiniteInterpolate documentation * Adjust make.jl to load in InfiniteInterpolate extension * Adjust hyperlinks to InfiniteInterpolate extension in technical manual * Simplify InfiniteInterpolate case in hovercraft example + InfiniteInterpolate documentation wording * Get rid of if statements in InfiniteInterpolate code and better utilize multiple dispatch + update docs accordingly * Add finite parameter unit test for InfiniteInterpolate case * Break up code lines to be around 80 char limit * Adjust line breaks in make.jl * Uncomment unit tests in runtests.jl --------- Co-authored-by: Joshua Pulsipher <[email protected]>
1 parent b5d7233 commit 2bd0383

File tree

9 files changed

+483
-10
lines changed

9 files changed

+483
-10
lines changed

Project.toml

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,12 @@ LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
1212
MutableArithmetics = "d8a4904e-b15c-11e9-3269-09a3773c0cb0"
1313
Reexport = "189a3867-3050-52da-a836-e630ba90ab69"
1414

15+
[weakdeps]
16+
Interpolations = "a98d9a8b-a2ab-59e6-89dd-64a1c18fca59"
17+
18+
[extensions]
19+
InfiniteInterpolate = "Interpolations"
20+
1521
[compat]
1622
DataStructures = "0.14.2 - 0.18, 0.19"
1723
Distributions = "0.19 - 0.25"
@@ -20,12 +26,14 @@ JuMP = "1.18"
2026
MutableArithmetics = "1"
2127
Reexport = "0.2, 1"
2228
julia = "^1.6"
29+
Interpolations = "0.16"
2330

2431
[extras]
2532
Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f"
2633
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
2734
Suppressor = "fd094767-a336-5f1f-9728-57cf17d0bbfb"
2835
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
36+
Interpolations = "a98d9a8b-a2ab-59e6-89dd-64a1c18fca59"
2937

3038
[targets]
31-
test = ["Pkg", "Test", "Random", "Suppressor"]
39+
test = ["Pkg", "Test", "Random", "Suppressor", "Interpolations"]

docs/Project.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
1111
SpecialFunctions = "276daf66-3868-5448-9aa4-cd146d93841b"
1212
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
1313
Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f"
14+
Interpolations = "a98d9a8b-a2ab-59e6-89dd-64a1c18fca59"
1415

1516
[compat]
1617
Distributions = "0.25"
@@ -23,3 +24,4 @@ JuMP = "1.23"
2324
Literate = "2.18"
2425
Plots = "1"
2526
SpecialFunctions = "2"
27+
Interpolations = "0.16"

docs/make.jl

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using Documenter, InfiniteOpt, Distributions, Literate, Random
1+
using Documenter, InfiniteOpt, Distributions, Literate, Random, Interpolations
22

33
if !@isdefined(EXAMPLE_DIR)
44
const EXAMPLE_DIR = joinpath(@__DIR__, "src", "examples")
@@ -96,7 +96,12 @@ makedocs(;
9696
],
9797
sitename = "InfiniteOpt.jl",
9898
authors = "Joshua Pulsipher and Weiqi Zhang",
99-
modules = [InfiniteOpt],
99+
modules = [
100+
InfiniteOpt,
101+
isdefined(Base, :get_extension) ?
102+
Base.get_extension(InfiniteOpt, :InfiniteInterpolate) :
103+
InfiniteOpt.InfiniteInterpolate
104+
],
100105
checkdocs = :none,
101106
linkcheck = true,
102107
linkcheck_ignore = [r"https://www.youtube.com/.*"],

docs/src/examples/Optimal Control/hovercraft.jl

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -61,15 +61,24 @@ end)
6161
# Optimize the model:
6262
optimize!(m)
6363

64-
# Extract the results:
65-
x_opt = value.(x);
64+
# Extract the results. The [`InfiniteInterpolate`](@ref infiniteInterpolate)
65+
# extension can be used to get a smooth interpolated function for x, which is
66+
# invoked when both the `Interpolations` and `InfiniteOpt` packages are imported.
67+
# Here, cubic splines are used as the interpolation method for both x1 and x2:
68+
using Interpolations
69+
xFunc = value.(x, cubic_spline_interpolation);
70+
71+
# Query our interpolated function for the values of x1 and x2:
72+
tvals = LinRange(0, 60, 100)
73+
x1Vals = xFunc[1](tvals)
74+
x2Vals = xFunc[2](tvals);
6675

6776
# Plot the results:
6877
using Plots
69-
scatter(xw[1,:], xw[2,:], label = "Waypoints", background_color = :transparent)
70-
plot!(x_opt[1], x_opt[2], label = "Trajectory")
78+
scatter(xw[1,:], xw[2,:], label = "Waypoints")
7179
xlabel!("x_1")
7280
ylabel!("x_2")
81+
plot!(x1Vals, x2Vals, label = "Trajectory")
7382

7483
# That's it, now we have our optimal trajectory!
7584

@@ -79,8 +88,10 @@ using Test
7988
tol = 1E-6
8089
@test termination_status(m) == MOI.LOCALLY_SOLVED
8190
@test has_values(m)
82-
@test x_opt isa Vector{<:Vector{<:Real}}
91+
@test x1Vals isa Vector{<:Real}
92+
@test x2Vals isa Vector{<:Real}
8393
@test isapprox(objective_value(m), 0.043685293177035435, atol=tol)
8494
@test isapprox(value(u[1])[end], -0.010503853944039986, atol=tol)
8595
@test isapprox(value(u[2])[end], 0.005456780217220367, atol=tol)
86-
96+
@test isapprox(x1Vals[15], 1.3837274935883543, atol=tol)
97+
@test isapprox(x2Vals[15], 1.6386021193421085, atol=tol)

docs/src/guide/result.md

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ parameter dependencies.
137137
This also holds true for many other methods in `InfiniteOpt`. For example,
138138
adding the dot also vectorizes `dual` and `set_binary`.
139139

140-
We can also query the dual of a constraint via
140+
We can query the dual of a constraint via
141141
[`dual`](@ref JuMP.dual(::InfOptConstraintRef)) if a model has duals available
142142
as indicated by [`has_duals`](@ref):
143143
```jldoctest results
@@ -184,6 +184,57 @@ These again all have a 1-to-1 correspondence.
184184
parameters, an n-dimensional array will typically be returned
185185
whose dimensions correspond to the supports of the infinite parameters.
186186

187+
## Interpolation-Based Continuous Values
188+
We can also get a continuous representation of a variable as an interpolations
189+
object from the `Interpolations.jl` package. This is based on the
190+
[`InfiniteInterpolate`](@ref infiniteInterpolate) extension, which is
191+
automatically loaded in when both `InfiniteOpt` and `Interpolations` are imported.
192+
The current supported methods are `linear_interpolation`, `constant_interpolation`
193+
and `cubic_spline_interpolation`.
194+
```jldoctest results
195+
julia> using Interpolations
196+
197+
julia> yFunc = value(y, linear_interpolation)
198+
10-element extrapolate(interpolate((::Vector{Float64},), ::Vector{Float64}, Gridded(Linear())), Throw()) with element type Float64:
199+
42.0
200+
20.999999995627107
201+
20.999999995628606
202+
20.999999995628603
203+
20.999999995628592
204+
20.999999995628603
205+
20.999999995634035
206+
20.999999995620904
207+
20.99999999562204
208+
20.9999999956286
209+
210+
julia> yFunc(5.12)
211+
20.9999999956286
212+
```
213+
Alternatively, we can specify the degree of interpolation with `Linear()`,
214+
`Constant()` or `Cubic()`. This will call the corresponding interpolation method.
215+
```jldoctest results
216+
julia> yFunc = value(y, Linear()) # equivalent to value(y, linear_interpolation)
217+
10-element extrapolate(interpolate((::Vector{Float64},), ::Vector{Float64}, Gridded(Linear())), Throw()) with element type Float64:
218+
42.0
219+
20.999999995627107
220+
20.999999995628606
221+
20.999999995628603
222+
20.999999995628592
223+
20.999999995628603
224+
20.999999995634035
225+
20.999999995620904
226+
20.99999999562204
227+
20.9999999956286
228+
229+
julia> yFunc(5.12)
230+
20.9999999956286
231+
```
232+
!!! warning
233+
There is a type piracy conflict between JuMP and OffsetArrays
234+
(a dependency of Interpolations.jl). As a result, type piracy issues may arise
235+
when Interpolations is loaded in. Please keep this in mind when using the
236+
InfiniteInterpolate extension.
237+
187238
## Termination Queries
188239
Termination queries are those that question about how the infinite model was
189240
solved and what its optimized state entails. Programmatically, such queries on

docs/src/manual/result.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,11 @@ JuMP.reduced_cost(::GeneralVariableRef)
3636
JuMP.optimizer_index(::GeneralVariableRef)
3737
```
3838

39+
## [InfiniteInterpolate](@id infiniteInterpolate)
40+
```@docs
41+
JuMP.value(::GeneralVariableRef, ::Interpolations.InterpolationType)
42+
```
43+
3944
## Constraints
4045
```@docs
4146
JuMP.has_duals(::InfiniteModel)

ext/InfiniteInterpolate.jl

Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
module InfiniteInterpolate
2+
3+
import JuMP
4+
import InfiniteOpt
5+
import Interpolations as IP
6+
7+
const _irregularGridMethods = Union{typeof(IP.linear_interpolation),
8+
typeof(IP.constant_interpolation)}
9+
10+
const _convenienceConstructors = Union{typeof(IP.linear_interpolation),
11+
typeof(IP.constant_interpolation),
12+
typeof(IP.cubic_spline_interpolation)}
13+
14+
"""
15+
JuMP.value(vref::GeneralVariableRef,
16+
method::Interpolations.InterpolationType);
17+
[kwargs...])
18+
19+
Extend `JuMP.value` to return `vref` as an interpolation object from
20+
Interpolations.jl, based on `method` which specifies the interpolation method.
21+
Currently supported method(s) are:
22+
- `linear_interpolation`
23+
- `constant_interpolation`
24+
- `cubic_spline_interpolation`
25+
26+
All methods support equidistant grid points. However, irregular grid points can
27+
only be used with `linear_interpolation` and `constant_interpolation`.
28+
29+
```julia
30+
JuMP.value(vref::GeneralVariableRef, degree::Interpolations.Degree; kwargs...)
31+
```
32+
Extend `JuMP.value` to return `vref` as an interpolation object from
33+
Interpolations.jl, based on `degree` which specifies the degree of interpolation.
34+
The currently supported degrees are:
35+
- `Linear()`
36+
- `Constant()`
37+
- `Cubic()`
38+
39+
All methods support equidistant grid points. However, irregular grid points can
40+
only be used with `Linear()` and `Constant()`.
41+
42+
**Examples**
43+
```julia-repl
44+
julia> zFunc = value(z, cubic_spline_interpolation)
45+
46+
julia> zFunc(5.4);
47+
42.0
48+
49+
julia> zFunc2 = value(z, Cubic())
50+
51+
julia> zFunc2(5.4)
52+
42.0
53+
```
54+
"""
55+
function JuMP.value(vref::InfiniteOpt.GeneralVariableRef,
56+
method::IP.InterpolationType; kwargs...)
57+
throw(ArgumentError("Unsupported interpolation type: $(method).
58+
Supported interpolation types are: linear_interpolation,
59+
constant_interpolation, and cubic_spline_interpolation."))
60+
end
61+
62+
function JuMP.value(vref::InfiniteOpt.GeneralVariableRef,
63+
interpMethod::typeof(IP.cubic_spline_interpolation);
64+
kwargs...)
65+
InfiniteOpt._check_result_is_current(JuMP.owner_model(vref), JuMP.value)
66+
67+
# Get infinite parameter references for which the variable depends on
68+
prefs = InfiniteOpt.parameter_refs(vref)
69+
70+
if isempty(prefs)
71+
# If no infinite parameters, return the value directly
72+
return InfiniteOpt._get_value(vref,
73+
InfiniteOpt._index_type(vref);
74+
kwargs...)
75+
else
76+
# Get the parameter supports
77+
Vparams = []
78+
for pref in prefs
79+
# If user defined irregular grid points for supports, throw an error
80+
suppsLabel = first(InfiniteOpt.core_object(pref).supports)[2]
81+
if !(InfiniteOpt.UniformGrid in suppsLabel)
82+
throw(ArgumentError("Interpolation method $(interpMethod) does
83+
not support irregular grids for supports. Please specify a
84+
uniform grid or choose a different interpolation method."))
85+
end
86+
87+
# Create an equidistant range for support values
88+
paramVals = InfiniteOpt._get_value(pref,
89+
InfiniteOpt._index_type(pref);
90+
kwargs...)
91+
numSupps = length(paramVals)
92+
paramRange = LinRange(paramVals[1], paramVals[end], numSupps)
93+
push!(Vparams, paramRange)
94+
end
95+
96+
# Ensure Vparams is a tuple for interpolation
97+
Vparams = Tuple(Vparams)
98+
99+
# Get the variable supports
100+
Vsupps = InfiniteOpt._get_value(vref,
101+
InfiniteOpt._index_type(vref);
102+
kwargs...)
103+
104+
# Pass the parameter and variable values to interpolation function
105+
varFunc = interpMethod(Vparams, Vsupps)
106+
return varFunc
107+
end
108+
end
109+
110+
function JuMP.value(vref::InfiniteOpt.GeneralVariableRef,
111+
interpMethod::_irregularGridMethods; kwargs...)
112+
InfiniteOpt._check_result_is_current(JuMP.owner_model(vref), JuMP.value)
113+
114+
# Get infinite parameter references for which the variable depends on
115+
prefs = InfiniteOpt.parameter_refs(vref)
116+
117+
if isempty(prefs)
118+
# If no infinite parameters, return the value directly
119+
return InfiniteOpt._get_value(vref, InfiniteOpt._index_type(vref); kwargs...)
120+
else
121+
# Get the parameter supports
122+
Vparams = []
123+
for pref in prefs
124+
paramVals = InfiniteOpt._get_value(pref,
125+
InfiniteOpt._index_type(pref);
126+
kwargs...)
127+
# Directly pass in a vector of support values, which may be irregularly spaced
128+
push!(Vparams, paramVals)
129+
end
130+
131+
# Ensure Vparams is a tuple for interpolation
132+
Vparams = Tuple(Vparams)
133+
134+
# Get the variable supports
135+
Vsupps = InfiniteOpt._get_value(vref,
136+
InfiniteOpt._index_type(vref);
137+
kwargs...)
138+
139+
# Pass the parameter and variable values to interpolation function
140+
varFunc = interpMethod(Vparams, Vsupps)
141+
return varFunc
142+
end
143+
end
144+
145+
# Fallback for unsupported interpolation degrees
146+
function JuMP.value(vref::InfiniteOpt.GeneralVariableRef,
147+
degree::IP.Degree; kwargs...)
148+
throw(ArgumentError("Unsupported interpolation degree: $(degree). Supported
149+
interpolation degrees are: Linear(), Constant(), and Cubic()."))
150+
end
151+
152+
function JuMP.value(vref::InfiniteOpt.GeneralVariableRef,
153+
degree::IP.Linear;
154+
kwargs...)
155+
return JuMP.value(vref, IP.linear_interpolation; kwargs...)
156+
end
157+
158+
function JuMP.value(vref::InfiniteOpt.GeneralVariableRef,
159+
degree::IP.Constant;
160+
kwargs...)
161+
return JuMP.value(vref, IP.constant_interpolation; kwargs...)
162+
end
163+
164+
function JuMP.value(vref::InfiniteOpt.GeneralVariableRef,
165+
degree::IP.Cubic;
166+
kwargs...)
167+
return JuMP.value(vref, IP.cubic_spline_interpolation; kwargs...)
168+
end
169+
end

0 commit comments

Comments
 (0)