Skip to content

Commit

Permalink
Support Nonlinear Expressions (#161)
Browse files Browse the repository at this point in the history
* Initial tree implementation

* add LCRSTrees pkg

* more LCRST progress

* starting nlp functions

* Added more NLP functions

* Added operator precedence to printing

* improve printing precedence

* Added expression mapping

* Performance improvements

* Added NLP transcription

* More additions/fixes

* bug fixes

* doctest fix

* add LCRST extension tests

* fix nlp expression map bug

* measure transcription bug

* Another measure trasncription bug fix

* test fix

* Temporary hack for transcription

* test fix

* Fix nlp to ast bug

* Added tests

* added tests and bug fix

* test fix

* test fix

* Added more tests and bug fixes

* More tests

* even more tests

* More tests

* more tests and bug fixes

* Minor fixes

* minor test fixes

* Fixed tests

* activate tests

* progress on the new docs

* doctest fix

* doctest fix 2

* Added more docs

* Updated docs

* doc fix

* added registration

* Finalizing touches

* Fixes and debug statement

* potential fixes

* doctest_fix

* module check fix

* doctest fix

* doc fixes

* [ci skip] minor doc addition
  • Loading branch information
pulsipher authored Oct 22, 2021
1 parent b6be9e3 commit a8eb2f6
Show file tree
Hide file tree
Showing 66 changed files with 4,818 additions and 1,337 deletions.
15 changes: 12 additions & 3 deletions Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,33 @@ authors = ["Joshua Pulsipher and Weiqi Zhang"]
version = "0.4.3"

[deps]
AbstractTrees = "1520ce14-60c1-5f80-bbc7-55ef81b5835c"
DataStructures = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8"
Distributions = "31c24e10-a181-5473-b8eb-7969acd0382f"
FastGaussQuadrature = "442a2c76-b920-505d-bb47-c5924d526838"
JuMP = "4076af6c-e467-56ae-b986-b466b2749572"
LeftChildRightSiblingTrees = "1d6d02ad-be62-4b6b-8a6d-2f90e265016e"
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
MutableArithmetics = "d8a4904e-b15c-11e9-3269-09a3773c0cb0"
Reexport = "189a3867-3050-52da-a836-e630ba90ab69"
SpecialFunctions = "276daf66-3868-5448-9aa4-cd146d93841b"

[compat]
AbstractTrees = "0.3"
DataStructures = "^0.14.2, 0.15, 0.16, 0.17, 0.18"
Distributions = "0.19, 0.20, 0.21, 0.22, 0.23, 0.24, 0.25"
FastGaussQuadrature = "^0.3.2, 0.4"
JuMP = "0.21.9"
JuMP = "0.21.10"
LeftChildRightSiblingTrees = "0.1"
MutableArithmetics = "0.2"
Reexport = "0.2, 1"
SpecialFunctions = "0.8, 0.9, 0.10, 1"
julia = "1"

[extras]
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
Suppressor = "fd094767-a336-5f1f-9728-57cf17d0bbfb"

[targets]
test = ["Test", "Random", "LinearAlgebra"]
test = ["Test", "Random", "Suppressor"]
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,10 @@ It builds upon `JuMP` to add support for many complex modeling objects which
include:
- Infinite parameters (e.g., time, space, uncertainty, etc.)
- Finite parameters (similar to `ParameterJuMP`)
- Infinite variables (e.g., `y(t, x)`)
- Infinite variables (decision functions) (e.g., `y(t, x)`)
- Derivatives (e.g., `∂y(t, x)/∂t`)
- Measures (e.g., `∫y(t,x)dt`, `𝔼[y(ξ)]`)
- More
- **1st class nonlinear modeling**

The unifying modeling abstraction behind `InfiniteOpt` captures a wide spectrum
of disciplines which include dynamic, PDE, stochastic, and semi-infinite
Expand Down
2 changes: 2 additions & 0 deletions docs/Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ Ipopt = "b6b21f68-93f8-5de0-b562-5493be1d77c9"
Literate = "98b081ad-f1c9-55d3-8b20-4c87d4299306"
Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80"
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
SpecialFunctions = "276daf66-3868-5448-9aa4-cd146d93841b"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"

[compat]
Expand All @@ -17,4 +18,5 @@ InfiniteOpt = "0.4"
Ipopt = "0.7"
Literate = "2.8"
Plots = "1"
SpecialFunctions = "1.7"
julia = "1.6"
56 changes: 40 additions & 16 deletions docs/src/develop/extensions.md
Original file line number Diff line number Diff line change
Expand Up @@ -714,7 +714,6 @@ extended using the following steps:
- [`InfiniteOpt.map_value`](@ref) (enables `JuMP.value`)
- [`InfiniteOpt.map_optimizer_index`](@ref) (enables `JuMP.optimizer_index`)
- [`InfiniteOpt.map_dual`](@ref) (enables `JuMP.dual`)
- [`InfiniteOpt.map_shadow_price`](@ref) (enables `JuMP.shadow_price`)
11. Extend [`InfiniteOpt.add_point_variable`](@ref) and
[`InfiniteOpt.add_semi_infinite_variable`](@ref) to use
[`expand_measure`](@ref) without modifying the infinite model.
Expand Down Expand Up @@ -801,6 +800,7 @@ build our `InfiniteModel` as normal, for example:
@objective(model, Min, z + expect(y[1] + y[2], ξ))
@constraint(model, 2y[1] - z <= 42)
@constraint(model, y[2]^2 + ξ == 2)
@constraint(model, sin(z) >= -1)
print(model)
# output
Expand All @@ -810,6 +810,7 @@ Subject to
y[2](ξ) ≥ 0.0, ∀ ξ ~ Uniform
2 y[1](ξ) - z ≤ 42.0, ∀ ξ ~ Uniform
y[2](ξ)² + ξ = 2.0, ∀ ξ ~ Uniform
sin(z) - -1 ≥ 0.0
```

We have defined our `InfiniteModel`, but now we need to specify how to
Expand Down Expand Up @@ -864,16 +865,13 @@ function _make_expression(
)
return _make_expression(opt_model, measure_function(expr))
end
# AffExpr
function _make_expression(opt_model::Model, expr::GenericAffExpr)
return @expression(opt_model, sum(c * _make_expression(opt_model, v)
for (c, v) in linear_terms(expr)) + constant(expr))
# AffExpr/QuadExpr
function _make_expression(opt_model::Model, expr::Union{GenericAffExpr, GenericQuadExpr})
return map_expression(v -> _make_expression(opt_model, v), expr)
end
# QuadExpr
function _make_expression(opt_model::Model, expr::GenericQuadExpr)
return @expression(opt_model, sum(c * _make_expression(opt_model, v1) *
_make_expression(opt_model, v2) for (c, v1, v2) in quad_terms(expr)) +
_make_expression(opt_model, expr.aff))
# NLPExpr
function _make_expression(opt_model::Model, expr::NLPExpr)
return add_NL_expression(opt_model, map_nlp_to_ast(v -> _make_expression(opt_model, v), expr))
end
# output
Expand All @@ -883,7 +881,8 @@ _make_expression (generic function with 8 methods)
For simplicity in example, above we assume that only `DistributionDomain`s are
used, there are not any `PointVariableRef`s, and all `MeasureRef`s correspond to
expectations. Naturally, a full extension should include checks to enforce that
such assumptions hold.
such assumptions hold. Notice that [`map_expression`](@ref) and
[`map_nlp_to_ast`](@ref) are useful for converting expressions.

Now let's extend [`build_optimizer_model!`](@ref) for `DeterministicModel`s.
Such extensions should build an optimizer model in place and in general should
Expand All @@ -904,6 +903,9 @@ function InfiniteOpt.build_optimizer_model!(
# clear the model for a build/rebuild
determ_model = InfiniteOpt.clear_optimizer_model_build!(model)
# add the registered functions if there are any
add_registered_to_jump(determ_model, model)
# add variables
for vref in all_variables(model)
dvref = dispatch_variable_ref(vref)
Expand All @@ -919,16 +921,31 @@ function InfiniteOpt.build_optimizer_model!(
end
# add the objective
set_objective(determ_model, objective_sense(model),
_make_expression(determ_model, objective_function(model)))
obj_func = _make_expression(determ_model, objective_function(model))
if obj_func isa NonlinearExpression
set_NL_objective(determ_model, objective_sense(model), obj_func)
else
set_objective(determ_model, objective_sense(model), obj_func)
end
# add the constraints
for cref in all_constraints(model)
if !InfiniteOpt._is_info_constraint(cref)
constr = constraint_object(cref)
new_constr = build_constraint(error, _make_expression(determ_model, constr.func),
constr.set)
new_cref = add_constraint(determ_model, new_constr, name(cref))
new_func = _make_expression(determ_model, constr.func)
if new_func isa NonlinearExpression
if constr.set isa MOI.LessThan
ex = :($new_func <= $(constr.set.upper))
elseif constr.set isa MOI.GreaterThan
ex = :($new_func >= $(constr.set.lower))
else # assume it is MOI.EqualTo
ex = :($new_func == $(constr.set.value))
end
new_cref = add_NL_constraint(determ_model, ex)
else
new_constr = build_constraint(error, new_func, constr.set)
new_cref = add_constraint(determ_model, new_constr, name(cref))
end
deterministic_data(determ_model).infconstr_to_detconstr[cref] = new_cref
end
end
Expand All @@ -954,6 +971,9 @@ Subject to
y[2]² = 1.5
y[1] ≥ 0.0
y[2] ≥ 0.0
subexpression[1] - 0.0 ≥ 0
With NL expressions
subexpression[1]: sin(z) - -1.0
```
Note that better variable naming could be used with the reformulated infinite
variables. Moreover, in general extensions of [`build_optimizer_model!`](@ref)
Expand Down Expand Up @@ -1040,3 +1060,7 @@ solution techniques. These extension packages can implement any of the extension
shown above and likely will want to introduce wrapper functions and macros to
use package specific terminology (e.g., using random variables instead of
infinite variables).

Please reach out to us via the
[discussion forum](https://github.com/pulsipher/InfiniteOpt.jl/discussions) to
discuss your plans before starting this on your own.
10 changes: 5 additions & 5 deletions docs/src/guide/constraint.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,6 @@ are enforced over some specified sub-domain of its infinite parameter
dependencies (e.g., boundary conditions). This page will highlight how to
implement these types of constraints in `InfiniteOpt`.

!!! note
Nonlinear constraints as defined by `JuMP.@NLconstraint` are not currently
supported by `InfiniteOpt`. See [Nonlinear Expressions](@ref) for more
information and possible workarounds.

## Basic Usage
Principally, the
[`@constraint`](https://jump.dev/JuMP.jl/v0.21.10/reference/constraints/#JuMP.@constraint)
Expand All @@ -48,6 +43,11 @@ julia> @variable(model, z[1:2]);
see [JuMP's constraint documentation](https://jump.dev/JuMP.jl/v0.21.10/manual/constraints/#Constraints)
for a thorough explanation of the supported types and syntax.

!!! note
Nonlinear constraints are defined simply by using `@constraint` and not
using `JuMP.@NLconstraint`. See [Nonlinear Expressions](@ref nlp_guide) for
more information.

### Scalar Constraints
Scalar constraints use scalar functions of variables. For example, let's define
the constraint
Expand Down
Loading

0 comments on commit a8eb2f6

Please sign in to comment.