Here is the example of implementation with R. R is flexible in regards of combining symbolic differentiation and numerical optimisation. This means that you can get from symbolic expression to function used in numerical optimisation quite fast. The cost is of course speed, since hand-written functions will certainly work faster.
So first create the expression:
n <- 3
wi <- paste("w",1:n,sep="")
sigma<-paste("sigma",1:n,sep="")
lfun <- paste(paste(wi,sigma,sep="^2*"),collapse="+")
rr <- paste("r",1:n,sep="")
res1 <- paste(paste(wi,rr,sep="*"),collapse="+")
res2 <- paste(wi,collapse="+")
optfun <- paste("1/2*(",lfun,")-lambda*(",res1,"-barr)-mu*(",res2,"-1)",collapse="")
vars <- c(wi,"lambda","mu")
optexpr <- parse(text=optfun)
Note that I only did string manipulation which is pretty readable. The result is:
> optexpr
expression(1/2*( w1^2*sigma1+w2^2*sigma2+w3^2*sigma3 )-lambda*( w1*r1+w2*r2+w3*r3 -barr)-mu*( w1+w2+w3 -1))
attr(,"srcfile")
<text>
Now use symbolic differentiation to get the equations which we need to solve:
> gradexpr <- lapply(vars,function(l)D(optexpr,name=l))
> gradexpr
[[1]]
1/2 * (2 * w1 * sigma1) - lambda * r1 - mu
[[2]]
1/2 * (2 * w2 * sigma2) - lambda * r2 - mu
[[3]]
1/2 * (2 * w3 * sigma3) - lambda * r3 - mu
[[4]]
-(w1 * r1 + w2 * r2 + w3 * r3 - barr)
[[5]]
-(w1 + w2 + w3 - 1)
Now each of the variables is separate variable. For numerical optimisation it makes sense to combine indexed w
, r
, and sigma
into vectors. This involves a little black magic, but it is not hard. We need the following function:
subvars <- function(expr,tb) {
if(length(expr)==1) {
nm <- deparse(expr)
if(nm %in% tb[,1]) {
e <- tb[tb[,1]==nm,2]
return(parse(text=e)[[1]])
}
else
return(expr)
}
else {
for (i in 2:length(expr)) {
expr[[i]] <- subvars(expr[[i]],tb)
}
}
return(expr)
}
This function substitutes the expressions according to substitution table. Here is the end result.
> subtable <- rbind(cbind(wi,paste("w[",1:n,"]",sep="")),
cbind(sigma,paste("sigma[",1:n,"]",sep="")),
cbind(rr,paste("r[",1:n,"]",sep=""))
)
> eqs <- lapply(gradexpr,function(l)subvars(l,subtable))
> eqs
[[1]]
1/2 * (2 * w[1] * sigma[1]) - lambda * r[1] - mu
[[2]]
1/2 * (2 * w[2] * sigma[2]) - lambda * r[2] - mu
[[3]]
1/2 * (2 * w[3] * sigma[3]) - lambda * r[3] - mu
[[4]]
-(w[1] * r[1] + w[2] * r[2] + w[3] * r[3] - barr)
[[5]]
-(w[1] + w[2] + w[3] - 1)
Now write a function which evaluates the equations.
eqs.fun <- function(w,sigma,r,barr,lambda,mu) {
cl <- match.call()
args <- as.list(cl)
args <- args[-1]
env <- parent.frame()
args <- lapply(args,eval,envir=env)
sapply(eqs,function(l)eval(l,env=args))
}
This is a human readable format, where we supply all the neccessary data to the equations. For optimisation we need a variant of this function where all the variables and the data is separated:
nl.eqs.fun <- function(p,sigma,r,barr) {
eqs.fun(w=p[1:3],sigma=sigma,r=r,barr=barr,lambda=p[4],mu=p[5])
}
And now we can solve the equations. For this use function nleqslv
from the package nleqslv:
nleqslv(c(0,0,0,0,0),nl.eqs.fun,nl.jac.fun,sigma=c(1,1,1),r=c(1,0.5,0.5),barr=1)
$x
[1] 1.000000e+00 -1.254481e-16 2.403703e-16 2.000000e+00 -1.000000e+00
$fvec
[1] -3.330669e-16 -5.551115e-16 -1.110223e-16 0.000000e+00 -2.220446e-16
$termcd
[1] 1
$message
[1] "Function criterion near zero"
$scalex
[1] 1 1 1 1 1
$nfcnt
[1] 1
$njcnt
[1] 1
We can confirm that the solution is correct with solve.QP
from the quadprog
package, since our problem is quadratic optimisation problem.
solve.QP(Dmat=diag(c(1,1,1)),dvec=rep(0,3),Amat=cbind(c(1,0.5,0.5),c(1,1,1)),bvec=c(1,1),meq=2)
$solution
[1] 1.000000e+00 0.000000e+00 -1.110223e-16
$value
[1] 0.5
$unconstrained.solution
[1] 0 0 0
$iterations
[1] 3 0
$Lagrangian
[1] 2 1
$iact
[1] 1 2
In this case approach described is an overkill, since there is readily available package to solve the problem. But it is not that hard to extend it to any optimisation function. Also you can calculate the jacobian of the equations very easy.
The main advantage is that this code is much easier to debug, and if you want to change your optimisation function, you only need to change the expressions, conversion to numerical functions is automatic (if the data remains the same).