Example
This is an illustrative example of a OptiCVX program.
To run this code, create a file named 'HelloCVX.scala' apps/opticvx/src
and then compile it using an analogous procedure to that used for OptiML.
import ppl.dsl.opticvx._
import scala.util.Random
object HelloCVXRunner extends OptiCVXApplicationRunner with HelloCVX
trait HelloCVX extends OptiCVXApplication {
def main() = {
val x = variable()
val y = variable()
val z = variable()
min(min(x,y),z) >= 0
max(max(x,y),z) <= 5
val J = inv(x) + max(y,z)
minimize (J) over (x,y,z)
println("x = " + resolve(x))
println("y = " + resolve(y))
println("z = " + resolve(z))
println("J = " + resolve(J))
}
val max = cvxfun (convex) arguments (increasing, increasing) body ((x,y) => {
val t = variable()
t >= x
t >= y
minimize (t) over (t)
t
})
val min = cvxfun (concave) arguments (increasing, increasing) body ((x,y) => {
val t = variable()
t <= x
t <= y
minimize (-t) over (t)
t
})
val inv = cvxfun (convex) arguments (decreasing) body ((x) => {
val v = variable(vector(2))
val z = variable()
v(0) == inputscalar(1.0)
val J = z - v(1)
x == z + v(1)
constrain_secondordercone(v,z)
minimize (J) over (v, z)
J
})
}
We analyze this program from top to bottom to illustrate the features of the DSL.
import ppl.dsl.opticvx._
import scala.util.Random
object HelloCVXRunner extends OptiCVXApplicationRunner with HelloCVX
trait HelloCVX extends OptiCVXApplication {
This code is mostly boilerplate, and is needed to setup the application. First, we import the DSL code. Then, we create an OptiCVXApplicationRunner object and an OptiCVXApplication trait. This is an idiom commonly used in Delite code.
val x = variable()
val y = variable()
val z = variable()
This is where we create the variables used in the problem. In this case, we have
three free variables, x
, y
, and z
. Each
variable is created as a scala val
(this may be confusing to Scala
developers, since var
is typically used for variables in Scala),
using the variable()
function.
min(min(x,y),z) >= 0
max(max(x,y),z) <= 5
Here, we declare the constraints used in this problem. We constrain that all of these
variables are between zero and five. Note that the <=
and >=
functions, which are normally used to do boolean tests, are here used to indicate
constraints in a declarative manner. We also notice the functions max
and
min
, which can be invoked with OptiCVX expressions as arguments.
OptiCVX enforces the disciplined convex programming rules here, so any expression
appearing on the left of a <=
must be convex, and any expression
appearing on the right must be concave. OptiCVX keeps track of the vexity of all
expressions at staging time, so there is no walk-time overhead incurred by
DCP safety-checking.
val J = inv(x) + max(y,z)
minimize (J) over (x,y,z)
The first line here creates the objective as an expression in the variables.
The second line actually performs the minimization by calling the solver. We
note that, as above, vexity rules are checked, so we can only minimize a convex
expression. We also note that, in a minimize
statement, we must
list the variables over which we are minimizing. This binds those variables
to that minimization expression. Note that creating the variable J
was done purely for convenience; we could have put the full expression inside
the minimize statement.
println("x = " + resolve(x))
println("y = " + resolve(y))
println("z = " + resolve(z))
println("J = " + resolve(J))
This code demonstrates how the results of the minimization can be used by the DSL
program. Calling the resolve(...)
function converts an OptiCVX expression
into a normal double
type. It is only valid to invoke
resolve(...)
after the solver has run and once all variables
used in the expression are bound; doing so before will result in a staging-time
error.
val max = cvxfun (convex) arguments (increasing, increasing) body ((x,y) => {
val t = variable()
t >= x
t >= y
minimize (t) over (t)
t
})
This code demonstrates OptiCVX's facility to allow the user to program his/her own convex functions as partial optimization problems using the given CVX primitives. This allows a wide range of expressivity in describing functions.
The cvxfun
statement allows us to specify the vexity of the function,
and the monotonicity of each of its arguments. This information is necessary to
perform DCP validation. The body of the function simply consists of a lambda
function (the arguments of which are CVX expressions), that contains one or
more optimization functions and returns the desired result.
val inv = cvxfun (convex) arguments (decreasing) body ((x) => {
val v = variable(vector(2))
val z = variable()
v(0) == inputscalar(1.0)
val J = z - v(1)
x == z + v(1)
constrain_secondordercone(v,z)
minimize (J) over (v, z)
J
})
Here we see another example of a user-defined function. This one is based off
of second-order-cone programming, instead of linear programming. It uses the