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 variable(vector(...)) idiom to create a vector-valued variable, and the constrain_second_order_cone(...) primitive to constrain the variables. A similar process can be used with variable(smatrix(...)) and constrain_semidefinite(...) to do semidefinite programming (SDP).