Skip to content

Commit abadb11

Browse files
committed
Public release update
Updated README and project.toml, added examples, added warning system to block extremely memory-intensive operations. Included parallel B&B algorithm (ParBB) in examples folder.
1 parent 4cdbd8b commit abadb11

File tree

10 files changed

+1262
-514
lines changed

10 files changed

+1262
-514
lines changed

Manifest.toml

Lines changed: 260 additions & 373 deletions
Large diffs are not rendered by default.

Project.toml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,16 @@ ModelingToolkit = "961ee093-0014-501f-94e3-6117800e7a78"
1010
SymbolicUtils = "d1185830-fcd6-423d-90d6-eec64667417b"
1111

1212
[compat]
13-
DocStringExtensions = "~0.8"
13+
DocStringExtensions = "0.8 - 0.9"
1414
IfElse = "0.1.0 - 1.1.1"
15-
ModelingToolkit = "~8"
16-
SymbolicUtils = "~0.19"
17-
julia = "~1.7"
15+
ModelingToolkit = "8"
16+
SymbolicUtils = "0.19.7"
17+
julia = "1.7"
1818

1919
[extras]
2020
McCormick = "53c679d3-6890-5091-8386-c291e8c8aaa1"
2121
Symbolics = "0c5d862f-8b57-4792-8d23-62f2024744c7"
2222
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
2323

2424
[targets]
25-
test = ["McCormick", "Symbolics", "Test"]
25+
test = ["McCormick", "Symbolics", "Test"]

README.md

Lines changed: 104 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,72 +1,78 @@
11
# SourceCodeMcCormick.jl
22

33
This package is an experimental approach to use source-code transformation to apply McCormick relaxations
4-
to symbolic functions for use in deterministic global optimization. While packages like `McCormick.jl`
4+
to symbolic functions for use in deterministic global optimization. While packages like `McCormick.jl` [1]
55
take set-valued McCormick objects and utilize McCormick relaxation rules to overload standard math operations,
6-
`SourceCodeMcCormick.jl` aims to interpret symbolic expressions, apply McCormick transformations, and
7-
return a new symbolic expression representing those relaxations. This functionality is designed to be
8-
used for both algebraic and dynamic systems.
6+
`SourceCodeMcCormick.jl` (SCMC) aims to interpret symbolic expressions, apply generalized McCormick rules,
7+
create source code that computes the McCormick relaxations and natural interval extension of the input,
8+
and compile the source code into functions that return pointwise values of the natural interval extension
9+
and convex/concave relaxations. This functionality is designed to be used for both algebraic and dynamic
10+
(in development) systems.
911

1012
## Algebraic Systems
1113

12-
For a given algebraic equation or system of equations, `SourceCodeMcCormick` is designed to provide
13-
symbolic transformations that represent the lower/upper bounds and convex/concave relaxations of the
14-
provided equation(s). Most notably, `SourceCodeMcCormick` uses this symbolic transformation to
15-
generate "evaluation functions" which, for a given expression, return the lower/upper bounds and
16-
convex/concave relaxations of an expression. E.g.:
14+
For a given algebraic equation or system of equations, `SCMC` is designed to provide symbolic transformations
15+
that represent the lower/upper bounds and convex/concave relaxations of the provided equation(s). Most notably,
16+
`SCMC` uses this symbolic transformation to generate "evaluation functions" which, for a given expression,
17+
return the natural interval extension and convex/concave relaxations of an expression. E.g.:
1718

1819
```
1920
using SourceCodeMcCormick, Symbolics
2021
21-
@variables x
22-
to_compute = x*(15+x)^2
23-
x_lo_eval, x_hi_eval, x_cv_eval, x_cc_eval, order = all_evaluators(to_compute)
22+
@variables x, y
23+
expr = exp(x/y) - (x*y^2)/(y+1)
24+
expr_lo_eval, expr_hi_eval, expr_cv_eval, expr_cc_eval, order = all_evaluators(expr)
2425
```
2526

2627
Here, the outputs marked `_eval` are the evaluation functions for the lower bound (`lo`), upper
27-
bound (`hi`), convex underestimator (`cv`), and concave overestimator (`cc`) of the `to_compute`
28-
expression. The inputs to each of these functions are described by the `order` vector, which
29-
in this case is `[x_cc, x_cv, x_hi, x_lo]`.
30-
31-
There are several important benefits of using these functions. First, they are fast. Normally,
32-
applying McCormick relaxations takes some time as the relaxation depends on the provided bounds
33-
and relaxation values of the input(s), but also on the form of the expression itself. Packages
34-
like `McCormick.jl` use control flow to quickly determine the form of the relaxations and provide
35-
McCormick objects of the results, but because the forms of the expressions are not known *a priori*,
36-
this control flow takes time to process. In contrast, `SourceCodeMcCormick` effectively hard-codes
37-
the relaxations for a specific, pre-defined function into the evaluation functions, making it
38-
inherently faster. For example:
28+
bound (`hi`), convex underestimator (`cv`), and concave overestimator (`cc`) of the symbolic
29+
expression given by `expr`. The inputs to each of these functions are described by the `order`
30+
vector, which in this case is `[x_cc, x_cv, x_hi, x_lo, y_cc, y_cv, y_hi, y_lo]`, representing
31+
the concave/convex relaxations and interval bounds of the variables `x` and `y`. E.g., if being
32+
used in a branch-and-bound (B&B) scheme, the interval bounds for each variable will be the lower and
33+
upper bounds of the B&B node for that variable, and the convex/concave relaxations will take the
34+
value where the relaxation of the original expression is desired.
35+
36+
One benefit of using a source code transformation approach such as this over a multiple dispatch
37+
approach like `McCormick.jl` is speed. When McCormick relaxations of functions are evaluated using
38+
`McCormick.jl`, there is overhead associated with finding the correct functions to call for each
39+
overloaded math operation. The functions generated by `SCMC`, however, eliminate this overhead by
40+
creating static functions with the correct McCormick rules already applied. While the `McCormick.jl`
41+
approach is flexible in quickly evaluating any new expression you provide, in the `SCMC` approach,
42+
one expression is selected up front, and relaxations and interval extension values are calculated
43+
for that expression quickly. For example:
3944

4045
```
4146
using BenchmarkTools, McCormick
4247
43-
xMC = MC{1, NS}(2.5, Interval(-1.0, 4.0), 1)
48+
xMC = MC{2, NS}(2.5, Interval(-1.0, 4.0), 1)
49+
yMC = MC{2, NS}(1.5, Interval(0.5, 3.0), 2)
4450
45-
@btime xMC*(15+xMC)^2
46-
# 228.381 ns (15 allocations: 384 bytes)
51+
@btime exp(xMC/yMC) - (xMC*yMC^2)/(yMC+1)
52+
# 497.382 ns (7 allocations: 560 bytes)
4753
48-
@btime x_cv_eval(2.5, 2.5, 4.0, -1.0)
49-
# 30.382 ns (1 allocation: 16 bytes)
54+
@btime expr_cv_eval(2.5, 2.5, 4.0, -1.0, 1.5, 1.5, 3.0, 0.5)
55+
# 184.964 ns (1 allocation: 16 bytes)
5056
```
5157

52-
This is not an *entirely* fair example because the operation with xMC is simultaneously calculating
53-
lower/upper bounds, both relaxations, and (sub)gradients of the convex/concave relaxations, but the
54-
`SourceCodeMcCormick` result is just under an order of magnitude faster. As the expression inceases
55-
in complexity, this speedup becomes even more apparent.
58+
Note that this is not an entirely fair comparison because `McCormick.jl`, by using the `MC` type and
59+
multiple dispatch, simultaneously calculates all of the following: natural interval extensions,
60+
convex and concave relaxations, and corresponding subgradients.
5661

57-
Second, these evaluation functions are compatible with `CUDA.jl` in that they are broadcastable
58-
over `CuArray`s. Depending on the GPU, number of evaluations, and complexity of the function,
59-
this can dramatically decrease the time to compute the numerical values of function bounds and
60-
relaxations. E.g.:
62+
Another benefit of the `SCMC` approach is its compatibility with `CUDA.jl` [2]: `SCMC` functions are
63+
broadcastable over `CuArray`s. Depending on the GPU, number of evaluations, and complexity of the
64+
function, this can dramatically decrease the time to compute the numerical values of interval
65+
extensions and relaxations. E.g.:
6166

6267
```
6368
using CUDA
6469
6570
# Using McCormick.jl
66-
xMC_array = MC{1,NS}.(rand(10000), Interval.(zeros(10000), ones(10000)), ones(Int, 10000))
71+
xMC_array = MC{2,NS}.(rand(10000), Interval.(zeros(10000), ones(10000)), ones(Int, 10000))
72+
yMC_array = MC{2,NS}.(rand(10000), Interval.(zeros(10000), ones(10000)), ones(Int, 10000).*2)
6773
68-
@btime xMC_array.*(15 .+ xMC_array).^2
69-
# 1.616 ms (120012 allocations: 2.37 MiB)
74+
@btime @. exp(xMC_array/yMC_array) - (xMC_array*yMC_array^2)/(yMC_array+1)
75+
# 2.365 ms (18 allocations: 703.81 KiB)
7076
7177
7278
# Using SourceCodeMcCormick.jl, broadcast using CPU
@@ -75,33 +81,40 @@ xcv = copy(xcc)
7581
xhi = ones(10000)
7682
xlo = zeros(10000)
7783
78-
@btime x_cv_eval.(xcc, xcv, xhi, xlo)
79-
# 100.100 μs (4 allocations: 78.27 KiB)
84+
ycc = rand(10000)
85+
ycv = copy(xcv)
86+
yhi = ones(10000)
87+
ylo = zeros(10000)
8088
89+
@btime expr_cv_eval.(xcc, xcv, xhi, xlo, ycc, ycv, yhi, ylo);
90+
# 1.366 ms (20 allocations: 78.84 KiB)
8191
82-
# Using SourceCodeMcCormick.jl and CUDA.jl, broadcast using GPU
83-
xcc_GPU = cu(xcc)
84-
xcv_GPU = cu(xcv)
85-
xhi_GPU = cu(xhi)
86-
xlo_GPU = cu(xlo)
8792
88-
@btime x_cv_eval.(xcc_GPU, xcv_GPU, xhi_GPU, xlo_GPU)
89-
# 6.575 μs (33 allocations: 2.34 KiB)
93+
# Using SourceCodeMcCormick.jl and CUDA.jl, broadcast using GPU
94+
xcc_GPU = CuArray(xcc)
95+
xcv_GPU = CuArray(xcv)
96+
xhi_GPU = CuArray(xhi)
97+
xlo_GPU = CuArray(xlo)
98+
ycc_GPU = CuArray(ycc)
99+
ycv_GPU = CuArray(ycv)
100+
yhi_GPU = CuArray(yhi)
101+
ylo_GPU = CuArray(ylo)
102+
103+
@btime CUDA.@sync expr_cv_eval.(xcc_GPU, xcv_GPU, xhi_GPU, xlo_GPU, ycc_GPU, ycv_GPU, yhi_GPU, ylo_GPU);
104+
# 29.800 μs (52 allocations: 3.88 KiB)
90105
```
91106

92107

93108
## Dynamic Systems
94109

95-
For dynamic systems, `SourceCodeMcCormick` was built with a differential inequalities
96-
approach in mind where the relaxations of derivatives are calculated in advance and
97-
the resulting (larger) differential equation system, with explicit definitions of
98-
the relaxations of derivatives, can be solved. For algebraic systems, the main
99-
product of this package is the broadcastable evaluation functions. For dynamic
100-
systems, this package follows the same idea as in algebraic systems but stops at
101-
the symbolic representations of relaxations. This functionality is designed to work
102-
with a `ModelingToolkit`-type `ODESystem` with factorable equations--`SourceCodeMcCormick`
103-
will take such a system and return a new `ODESystem` with expanded equations to
104-
provide interval extensions and (if desired) McCormick relaxations. E.g.:
110+
(In development) For dynamic systems, `SCMC` assumes a differential inequalities approach where the
111+
relaxations of derivatives are calculated in advance and the resulting (larger) differential equation
112+
system, with explicit definitions of the relaxations of derivatives, can be solved. For algebraic
113+
systems, the main product of this package is the broadcastable evaluation functions. For dynamic
114+
systems, this package follows the same idea as in algebraic systems but stops at the symbolic
115+
representations of relaxations. This functionality is designed to work with a `ModelingToolkit`-type
116+
`ODESystem` with factorable equations [3]--`SCMC` will take such a system and return a new `ODESystem`
117+
with expanded equations to provide interval extensions and (if desired) McCormick relaxations. E.g.:
105118

106119
```
107120
using SourceCodeMcCormick, ModelingToolkit
@@ -150,8 +163,35 @@ using `DiffEqGPU.jl`.
150163

151164
## Limitations
152165

153-
Currently, as proof-of-concept, `SourceCodeMcCormick` can only handle functions with
154-
addition (+), subtraction (-), multiplication (\*), division (/), powers of 2 (^2), natural base
155-
exponentials (exp), and minimum/maximum (min/max) expressions. Future work will include
156-
adding other operations found in `McCormick.jl`.
157-
166+
`SCMC` has several limitations, some of which are described here. Ongoing research effort seeks
167+
to address several of these.
168+
- SCMC does not calculate subgradients, which are used in the lower bounding routines of many
169+
global optimizers
170+
- Complicated expressions may cause significant compilation time. This can be manually avoided
171+
by combining results together in a user-defined function
172+
- `SCMC` is currently compatible with elementary arithmetic operations +, -, *, /, and the
173+
univariate intrinsic functions ^2 and exp. More diverse functions will be added in the future
174+
- Functions created with `SCMC` may only accept 32 CUDA arrays as inputs, so functions with more
175+
than 8 unique variables will need to be split/factored by the user to be accommodated
176+
- Due to the large number of floating point calculations required to calculate McCormick-based
177+
relaxations, it is highly recommended to use double-precision floating point numbers, including
178+
for operations on a GPU. Since most GPUs are designed for single-precision floating point operation,
179+
forcing double-precision will often result in a significant performance hit. GPUs designed for
180+
scientific computing, with a higher proportion of double-precision-capable cores, are recommended
181+
for optimal performance with `SCMC`.
182+
- Due to the high branching factor of McCormick-based relaxations and the possibility of warp
183+
divergence, there will likely be a performance gap between optimizations with variables covering
184+
positive-only domains and variables with mixed domains. Additionally, more complicated expressions
185+
where the structure of a McCormick relaxation changes more frequently with respect to the bounds
186+
on its domain will likely perform worse than problems where the structure of the relaxation is
187+
more consistent.
188+
189+
## References
190+
191+
1. M.E. Wilhelm, R.X. Gottlieb, and M.D. Stuber, PSORLab/McCormick.jl (2020), URL
192+
https://github.com/PSORLab/McCormick.jl.
193+
2. T. Besard, C. Foket, and B. De Sutter, Effective extensible programming: Unleashing Julia
194+
on GPUs, IEEE Transactions on Parallel and Distributed Systems (2018).
195+
3. Y. Ma, S. Gowda, R. Anantharaman, C. Laughman, V. Shah, C. Rackauckas, ModelingToolkit:
196+
A composable graph transformation system for equation-based modeling. arXiv preprint
197+
arXiv:2103.05244, 2021. doi: 10.48550/ARXIV.2103.05244.

examples/ParBB/extension.jl

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
2+
# This extension is meant to be used with SourceCodeMcCormick to overload
3+
# EAGO's standard branch-and-bound algorithm, to enable the parallel
4+
# evaluation of multiple B&B nodes.
5+
# See also: subroutines.jl, in this folder
6+
7+
using DocStringExtensions, EAGO
8+
"""
9+
$(TYPEDEF)
10+
11+
The ExtendGPU integrator is meant to be paired with the SourceCodeMcCormick
12+
package. A required component of ExtendGPU is the function `convex_func`,
13+
which should take arguments corresponding to the McCormick tuple [cc, cv, hi, lo]
14+
for each branch variable in the problem and return a vector of convex
15+
relaxation evaluations of the objective function, of length equal to the
16+
length of the inputs.
17+
18+
$(TYPEDFIELDS)
19+
"""
20+
Base.@kwdef mutable struct ExtendGPU <: EAGO.ExtensionType
21+
"A user-defined function taking argument `p` and returning a vector
22+
of convex evaluations of the objective function"
23+
convex_func
24+
"Number of decision variables"
25+
np::Int
26+
"The number of nodes to evaluate in parallel (default = 10000)"
27+
node_limit::Int64 = 50000
28+
"A parameter from Song et al. 2021 that determines how spread out
29+
blackbox points are (default = 0.01)"
30+
α::Float64 = 0.01
31+
"Lower bound storage to hold calculated lower bounds for multiple nodes."
32+
lower_bound_storage::Vector{Float64} = Vector{Float64}()
33+
"Upper bound storage to hold calculated upper bounds for multiple nodes."
34+
upper_bound_storage::Vector{Float64} = Vector{Float64}()
35+
"Node storage to hold individual nodes outside of the main stack"
36+
node_storage::Vector{NodeBB} = Vector{NodeBB}()
37+
"An internal tracker of nodes in internal storage"
38+
node_len::Int = 0
39+
"Variable lower bounds to evaluate"
40+
all_lvbs::Matrix{Float64} = Matrix{Float64}()
41+
"Variable upper bounds to evaluate"
42+
all_uvbs::Matrix{Float64} = Matrix{Float64}()
43+
"Internal tracker for the count in the main stack"
44+
# node_count::Int = 0
45+
"Flag for stack prepopulation. Good if the total number
46+
of nodes throughout the solve is expected to be large (default = true)"
47+
prepopulate::Bool = true
48+
"(In development) Number of points to use for multistarting the NLP solver"
49+
multistart_points::Int = 1
50+
end
51+
52+
function ExtendGPU(convex_func, var_count::Int; alpha::Float64 = 0.01, node_limit::Int = 50000,
53+
prepopulate::Bool = true, multistart_points::Int = 1)
54+
return ExtendGPU(convex_func, var_count, node_limit, alpha,
55+
Vector{Float64}(undef, node_limit), Vector{Float64}(undef, node_limit), Vector{NodeBB}(undef, node_limit), 0,
56+
Matrix{Float64}(undef, node_limit, var_count),
57+
Matrix{Float64}(undef, node_limit, var_count), prepopulate, multistart_points)
58+
end

0 commit comments

Comments
 (0)