The following program uses the Davidson-Fletcher-Powell (DFP) method to find the minimum of a function. This method is a quasi-Newton method. That is, the DFP algorithm is based on Newton's method but performs different calculations to update the guess refinements.
Click here to download a ZIP file containing the project files for this program.
The program prompts you to either use the predefined default input values or to enter the following:
1. The initial guesses for the optimum point for each variable..
2. The tolerance for each variable,
3. The tolerance for the function
4. The maximum number of iteration cycles
In case you choose the default input values, the program displays these values and proceeds to find the optimum point. In the case you select being prompted, the program displays the name of each input variable along with its default value. You can then either enter a new value or simply press Enter to use the default value. This approach allows you to quickly and efficiently change only a few input values if you so desire.
The program displays the following final results:
1. The coordinates of the minimum point.
2. The minimum function value.
3. The number of iterations
The current code finds the minimum for the following function:
f(x1,x2) = x1 - x2 + 2 * x1 ^ 2 + 2 * x1 * x2 + x2 ^ 2
Using, for each variable, an initial value of 0, initial step size of 0.1, minimum step size of 1e-7, and using a function tolerance of 1e-7. Here is the sample console screen:
Here is the listing for the main module. The module contains several test functions:
Module Module1 Sub Main() Dim nNumVars As Integer = 2 Dim fX() As Double = {0.5, 0.5} Dim fParam() As Double = {0, 0} Dim fToler() As Double = {0.00001, 0.00001} Dim nIter As Integer = 0 Dim nMaxIter As Integer = 100 Dim fEpsFx As Double = 0.0000001 Dim fEpsGrad As Double = 0.0000001 Dim I As Integer Dim fBestF As Double Dim sAnswer As String, sErrorMsg As String = "" Dim oOpt As COptimDFP1 Dim MyFx As MyFxDelegate = AddressOf Fx3 Dim SayFx As SayFxDelegate = AddressOf SayFx3 oOpt = New COptimDFP1 Console.WriteLine("Davidson-Fletcher-Powell Method (DFP) Optimization") Console.WriteLine("Finding the minimum of function:") Console.WriteLine(SayFx()) Console.Write("Use default input values? (Y/N) ") sAnswer = Console.ReadLine() If sAnswer.ToUpper() = "Y" Then For I = 0 To nNumVars - 1 Console.WriteLine("X({0}) = {1}", I + 1, fX(I)) Console.WriteLine("Tolerance({0}) = {1}", I + 1, fToler(I)) Next Console.WriteLine("Function tolerance = {0}", fEpsFx) Console.WriteLine("Maxumum cycles = {0}", nMaxIter) Else For I = 0 To nNumVars - 1 fX(I) = GetIndexedDblInput("X", I + 1, fX(I)) fToler(I) = GetIndexedDblInput("Tolerance", I + 1, fToler(I)) Next fEpsFx = GetDblInput("Function tolerance", fEpsFx) nMaxIter = GetIntInput("Maxumum cycles", nMaxIter) End If Console.WriteLine("******** FINAL RESULTS *************") fBestF = oOpt.CalcOptim(nNumVars, fX, fParam, fToler, fEpsFx, fEpsGrad, nMaxIter, nIter, sErrorMsg, MyFx) If sErrorMsg.Length > 0 Then Console.WriteLine("** NOTE: {0} ***", sErrorMsg) End If Console.WriteLine("Optimum at") For I = 0 To nNumVars - 1 Console.WriteLine("X({0}) = {1}", I + 1, fX(I)) Next Console.WriteLine("Function value = {0}", fBestF) Console.WriteLine("Number of iterations = {0}", nIter) Console.WriteLine() Console.Write("Press Enter to end the program ...") Console.ReadLine() End Sub Function GetDblInput(ByVal sPrompt As String, ByVal fDefInput As Double) As Double Dim sInput As String Console.Write("{0}? ({1}): ", sPrompt, fDefInput) sInput = Console.ReadLine() If sInput.Trim().Length > 0 Then Return Double.Parse(sInput) Else Return fDefInput End If End Function Function GetIntInput(ByVal sPrompt As String, ByVal nDefInput As Integer) As Integer Dim sInput As String Console.Write("{0}? ({1}): ", sPrompt, nDefInput) sInput = Console.ReadLine() If sInput.Trim().Length > 0 Then Return Integer.Parse(sInput) Else Return nDefInput End If End Function Function GetIndexedDblInput(ByVal sPrompt As String, ByVal nIndex As Integer, ByVal fDefInput As Double) As Double Dim sInput As String Console.Write("{0}({1})? ({2}): ", sPrompt, nIndex, fDefInput) sInput = Console.ReadLine() If sInput.Trim().Length > 0 Then Return Double.Parse(sInput) Else Return fDefInput End If End Function Function GetIndexedIntInput(ByVal sPrompt As String, ByVal nIndex As Integer, ByVal nDefInput As Integer) As Integer Dim sInput As String Console.Write("{0}({1})? ({2}): ", sPrompt, nIndex, nDefInput) sInput = Console.ReadLine() If sInput.Trim().Length > 0 Then Return Integer.Parse(sInput) Else Return nDefInput End If End Function Function SayFx1() As String Return "F(X) = 10 + (X(1) - 2) ^ 2 + (X(2) + 5) ^ 2" End Function Function Fx1(ByVal N As Integer, ByRef X() As Double, ByRef fParam() As Double) As Double Return 10 + (X(0) - 2) ^ 2 + (X(1) + 5) ^ 2 End Function Function SayFx2() As String Return "F(X) = 100 * (X(1) - X(2) ^ 2) ^ 2 + (X(2) - 1) ^ 2" End Function Function Fx2(ByVal N As Integer, ByRef X() As Double, ByRef fParam() As Double) As Double Return 100 * (X(0) - X(1) ^ 2) ^ 2 + (X(1) - 1) ^ 2 End Function Function SayFx3() As String Return "F(X) = X(1) - X(2) + 2 * X(1) ^ 2 + 2 * X(1) * X(2) + X(2) ^ 2" End Function Function Fx3(ByVal N As Integer, ByRef X() As Double, ByRef fParam() As Double) As Double Return X(0) - X(1) + 2 * X(0) ^ 2 + 2 * X(0) * X(1) + X(1) ^ 2 End Function End Module
Notice that the user-defined functions have accompanying helper functions to display the mathematical expression of the function being optimized. For example, function Fx1 has the helper function SayFx1 to list the function optimized in Fx1. Please observe the following rules::
The program uses the following class to optimize the objective function:
Public Delegate Function MyFxDelegate(ByVal nNumVars As Integer, ByRef fX() As Double, ByRef fParam() As Double) As Double Public Delegate Function SayFxDelegate() As String Public Class COptimDFP1 Dim m_MyFx As MyFxDelegate Protected Function MyFxEx(ByVal nNumVars As Integer, _ ByRef fX() As Double, ByRef fParam() As Double, _ ByRef fDeltaX() As Double, ByVal fLambda As Double) As Double Dim I As Integer Dim fXX(nNumVars) As Double For I = 0 To nNumVars - 1 fXX(I) = fX(I) + fLambda * fDeltaX(I) Next I MyFxEx = m_MyFx(nNumVars, fXX, fParam) End Function Protected Function FirstDeriv(ByVal nNumVars As Integer, _ ByRef fX() As Double, ByRef fParam() As Double, _ ByVal nIdxI As Integer) As Double Dim fXt, h, Fp, Fm As Double fXt = fX(nIdxI) h = 0.01 * (1 + Math.Abs(fXt)) fX(nIdxI) = fXt + h Fp = m_MyFx(nNumVars, fX, fParam) fX(nIdxI) = fXt - h Fm = m_MyFx(nNumVars, fX, fParam) fX(nIdxI) = fXt FirstDeriv = (Fp - Fm) / 2 / h End Function Protected Sub GetFirstDerives(ByVal nNumVars As Integer, _ ByRef fX() As Double, ByRef fParam() As Double, _ ByRef fFirstDerivX() As Double) Dim I As Integer For I = 0 To nNumVars - 1 fFirstDerivX(I) = FirstDeriv(nNumVars, fX, fParam, I) Next I End Sub Protected Function LinSearch_DirectSearch(ByVal nNumVars As Integer, ByRef fX() As Double, ByRef fParam() As Double, _ ByRef fLambda As Double, ByRef fDeltaX() As Double, ByVal fInitStep As Double, ByVal fMinStep As Double) As Boolean Dim F1, F2 As Double F1 = MyFxEx(nNumVars, fX, fParam, fDeltaX, fLambda) Do F2 = MyFxEx(nNumVars, fX, fParam, fDeltaX, fLambda + fInitStep) If F2 < F1 Then F1 = F2 fLambda += fInitStep Else F2 = MyFxEx(nNumVars, fX, fParam, fDeltaX, fLambda - fInitStep) If F2 < F1 Then F1 = F2 fLambda -= fInitStep Else ' reduce search step size fInitStep /= 10 End If End If Loop Until fInitStep < fMinStep Return True End Function Public Function CalcOptim(ByVal nNumVars As Integer, ByRef fX() As Double, ByRef fParam() As Double, _ ByRef fToler() As Double, ByVal fEpsFx As Double, ByVal fEpsGrad As Double, ByVal nMaxIter As Integer, _ ByRef nIter As Integer, ByRef sErrorMsg As String, _ ByVal MyFx As MyFxDelegate) As Double Dim I As Integer Dim fNorm As Double, fLambda As Double Dim bStop As Boolean Dim Index(nNumVars) As Integer Dim fGrad1(nNumVars) As Double Dim fGrad2(nNumVars) As Double Dim g(nNumVars) As Double Dim S(nNumVars) As Double Dim d(nNumVars) As Double Dim Bmat(nNumVars, nNumVars) As Double Dim Mmat(nNumVars, nNumVars) As Double Dim Nmat(nNumVars, nNumVars) As Double Dim MM1(nNumVars, nNumVars) As Double Dim MM2(nNumVars, nNumVars) As Double Dim MM3(nNumVars, nNumVars) As Double Dim MM4(nNumVars, nNumVars) As Double Dim MM5(nNumVars, nNumVars) As Double Dim VV1(nNumVars) As Double ' set error handler On Error GoTo HandleErr m_MyFx = MyFx ' set matrix B as an indentity MatrixLibVb.MatIdentity(Bmat) nIter = 0 ' calculate initial gradient GetFirstDerives(nNumVars, fX, fParam, fGrad1) ' start main loop Do nIter += 1 If nIter > nMaxIter Then sErrorMsg = "Iter limits" Exit Do End If ' calcuate vector S() and reverse it's sign MatrixLibVb.MatMultVect(Bmat, fGrad1, S) MatrixLibVb.VectMultValInPlace(S, -1) ' normailize vector S MatrixLibVb.NormalizeVect(S) fLambda = 0 LinSearch_DirectSearch(nNumVars, fX, fParam, fLambda, S, 0.1, 0.00001) ' calculate optimum fX() with the given fLambda For I = 0 To nNumVars - 1 d(I) = fLambda * S(I) fX(I) += d(I) Next I ' get new gradient GetFirstDerives(nNumVars, fX, fParam, fGrad2) ' calculate vector g() and shift fGrad2() into fGrad1() For I = 0 To nNumVars - 1 g(I) = fGrad2(I) - fGrad1(I) fGrad1(I) = fGrad2(I) Next I ' test for convergence bStop = True For I = 0 To nNumVars - 1 If Math.Abs(d(I)) > fToler(I) Then bStop = False Exit For End If Next I If bStop Then sErrorMsg = "Lam * S()" Exit Do End If ' exit if gradient is low If MatrixLibVb.VectNorm(g) < fEpsGrad Then sErrorMsg = "G Norm" Exit Do End If '------------------------------------------------- ' Start elaborate process to update matrix B ' ' First calculate matrix M (stored as Mmat) MatrixLibVb.VectToColumnMat(S, MM1) ' MM1 = S in matrix form MatrixLibVb.VectToRowMat(S, MM2) ' MM2 = S^T in matrix form MatrixLibVb.VectToColumnMat(g, MM3) ' MM3 = g in matrix form MatrixLibVb.MatMultMat(MM1, MM2, Mmat) ' Mmat = S * S^T MatrixLibVb.MatMultMat(MM2, MM3, MM4) ' MM4 = S^T * g MatrixLibVb.MatMultValInPlace(Mmat, _ fLambda / MM4(0, 0)) ' calculate natrix M ' Calculate matrix nNumVars (stored as Nmat) MatrixLibVb.MatMultVect(Bmat, g, VV1) ' VV1 = {B] g MatrixLibVb.VectToColumnMat(VV1, MM1) ' MM1 = VV1 in column matrix form ([B] g) MatrixLibVb.VectToRowMat(VV1, MM2) ' MM2 = VV1 in row matrix form ([B] g)^T MatrixLibVb.MatMultMat(MM1, MM2, Nmat) ' Nmat = ([B] g) * ([B] g)^T MatrixLibVb.VectToRowMat(g, MM1) ' MM1 = g^T MatrixLibVb.VectToColumnMat(g, MM2) ' MM2 = g MatrixLibVb.MatMultMat(MM1, Bmat, MM3) ' MM3 = g^T [B] MatrixLibVb.MatMultMat(MM3, MM2, MM4) ' MM4 = g^T [B] g MatrixLibVb.MatMultValInPlace(Nmat, _ -1 / MM4(0, 0)) ' calculate matrix nNumVars MatrixLibVb.MatAddMatInPlace(Bmat, Mmat) ' B = B + M MatrixLibVb.MatAddMatInPlace(Bmat, Nmat) ' B = B + nNumVars Loop ExitProc: Return MyFx(nNumVars, fX, fParam) HandleErr: sErrorMsg = "Error: " & Err.Description Resume ExitProc End Function End Class
Copyright (c) Namir Shammas. All rights reserved.