2

I am using DEoptim in R in order to maximize a likelihood function for the bivariate student t distribution.

This distribution requires that the degrees of freedom be integer (I can already set the positive contrain using the lower and upperbound in DEoptim).

This pdf explains DEoptim in detail and states that

[fnmap] is an optional function that will be run after each population is created, but before the population is passed to the objective function. This allows the user to impose integer/cardinality constriants.

It does not go into detail about how you use the argument "fnmap", nor give any details. Looking online I found one example function given to impose a carnality constraint by the developer of the package, but he did not explain the rational there either.

So, if I have a parameter (for example 'z' in parameters (x,y,z)), what do I put as "fnmap" in order to restrict z to integer values?

Patty
  • 167
  • 1
  • 2
  • 11

2 Answers2

2

I suppose that here is what you want.

fnmap_f <- fuction(x) c(x[1], x[2], round(x[3]))
DEoptim(..., fnMap = fnmap_f)
cuttlefish44
  • 6,586
  • 2
  • 17
  • 34
2

The box constraints could be setup as penalty constraints to the objective function itself as below.

The value of lambda multiplier dictates the relative importance between objective function and constraints. With higher values of lambda the solver will prioritize constraints over the objective function.

Here we solve a simple equation for integer solutions of x1^2 + x2^2 = 25 with x2 < x1 as constraint, with expected output of c(4,3)

library("DEoptim")

#run DEoptim and set a seed first for replicability
#note the results are sensitive to seed values and parameters (lambda,NP,itermax,F,CR)

set.seed(1234)

#create a vector/grid of lambda values 
lambdaVec = sapply(0:12,function(x) 10^x)

lambdaVec
#[1] 1e+00 1e+01 1e+02 1e+03 1e+04 1e+05 1e+06 1e+07 1e+08 1e+09 1e+10


#For each value of lambda compute the output of optimization function and combine the results

optimSummary = do.call(rbind,lapply(lambdaVec, function(lambdaParam)  {



fnCustom = function(x,lambda=lambdaParam) {
    x1 <- x[1]
    x2 <- x[2]

    #integer param constraints  
    firstIntPenalty  <- (x1-round(x1))^2
    secondIntPenalty  <-  (x2-round(x2))^2

    # x2 < x1, note the sign is opposite of desired constraint
    inEqualityConstraint <- sum(x2>x1)


    100 * (x1^2 + x2^2 - 25)^2 + lambda * ( firstIntPenalty + secondIntPenalty + inEqualityConstraint ) 

}


lower <- c(0,0)
upper <- c(5,5)



#you will have to tinker with values of NP,F and CR and monitor convergence, see ?DEoptim last paragraph
outDEoptim <- DEoptim(fnCustom, lower, upper, DEoptim.control(NP = 80, itermax = 1000, F = 1.2, CR = 0.7,trace=TRUE))

#output data.frame of optimization result
optimRes <- data.frame(lambda = lambdaParam ,param1 = outDEoptim$optim$bestmem[1],param2 = outDEoptim$optim$bestmem[2])


rownames(optimRes) <- NULL

return(optimRes)

} ))

Floating Point Representation:

Due to floating point represenation the results in most cases will not to be exactly equal to your expected integer output. Depending on your domain will have to define an acceptable threshold below which we consider the numbers as integer.

For more details refer to ?.Machine and this R floating point precision

Output Convergence and verification:

threshold = 1e-6
expectedOut = c(4,3)

#optimization summary
optimSummary

#   lambda   param1       param2
#1   1e+00 4.999996 0.0002930537
#2   1e+01 4.000000 3.0000000000
#3   1e+02 4.000000 3.0000000000
#4   1e+03 4.000000 3.0000000000
#5   1e+04 4.000000 3.0000000000
#6   1e+05 4.000000 3.0000000000
#7   1e+06 4.000000 3.0000000000
#8   1e+07 4.000000 3.0000000000
#9   1e+08 4.000000 2.9999999962
#10  1e+09 3.999999 2.9999998843
#11  1e+10 4.000000 2.9999999569
#12  1e+11 4.000000 3.0000000140
#13  1e+12 4.000000 3.0000000194




#Verify output
#1)With constraintBreach1 and constraintBreach2 we check if difference between output and expected result
#has breached threshold 
#2)With constraintBreach3 we check if x1 > x2 condition is violated
#3)Columns with TRUE observations indicate breach of respective constraints at particular lambda values



verifyDF = data.frame(lambdaVec,constraintBreach1 = abs(optimSummary$param1-expectedOut[1]) > threshold
                            , constraintBreach2 = abs(optimSummary$param2-expectedOut[2]) > threshold
                            ,  constraintBreach3 =  optimSummary$param1 < optimSummary$param1)
verifyDF
#   lambdaVec constraintBreach1 constraintBreach2 constraintBreach3
#1      1e+00              TRUE              TRUE             FALSE
#2      1e+01             FALSE             FALSE             FALSE
#3      1e+02             FALSE             FALSE             FALSE
#4      1e+03             FALSE             FALSE             FALSE
#5      1e+04             FALSE             FALSE             FALSE
#6      1e+05             FALSE             FALSE             FALSE
#7      1e+06             FALSE             FALSE             FALSE
#8      1e+07             FALSE             FALSE             FALSE
#9      1e+08             FALSE             FALSE             FALSE
#10     1e+09             FALSE             FALSE             FALSE
#11     1e+10             FALSE             FALSE             FALSE
#12     1e+11             FALSE             FALSE             FALSE
#13     1e+12             FALSE             FALSE             FALSE

For lower levels of lambda the solver ignores constraints and with increasing lambda the solver assigns more weight to constraints relative to objective function and hence the constraints are satisfied.

You will have to tinker with values of lambda,NP,itermax,F,CR for your particular problem.

Community
  • 1
  • 1
Silence Dogood
  • 3,587
  • 1
  • 13
  • 17