Note: I am going to use this post to highlight several challenges and hopefully best practices with using Solver in VBA. Please feel free to let me know if something needs updating.
Telling Solver what cells to use.
It seems (nearly) all of the parameters for Solver are of type Variant. This might lead you to believe that you have a lot of flexibility with how you provide it information. It turns out, you need to provide it carefully crafted text strings.
Using SolverSave as an example, as per the the MS documentation
you must specify the SaveArea, and if the SaveArea is on a different sheet than the active sheet, the SaveArea must include the worksheet name.
This works:
SolverSave SaveArea:="Sheet2!A1"
As a matter of fact, all of the MS documentation uses string literals in their examples.
And this works:
SolverSave SaveArea:="Sheet2!A1:A4"
As long as Save only needs four rows to store its data, which is typically, but not always, the case.
This does NOT work:
Set SaveRng = Sheets("Sheet2").Range("A1")
SolverSave SaveArea:=SaveRng
It does not throw an error. It puts some information in cell A1 in Sheet2, but everything else in the active worksheet.
This does NOT work:
Set SaveRng = Sheets("Sheet2").Range("A1:A4")
SolverSave SaveArea:=SaveRng
It throws a type mismatch error at SolverSave.
This DOES work:
Set SaveRng = Sheets("Sheet2").Range("A1")
SaveAddress = Split(SaveRng.Address(external:=True), "[")(0) & Split(SaveRng.Address(external:=True), "]")(1)
SolverSave SaveArea:=SaveAddress
Above is the most concise way I could find to build a full address for a range that includes the sheet name, and the single quote "'" when required. (For you to investigate - why won't SaveRng.Address work?)
I recommend using the last method (above) for every Solver routine with a parameter requiring an address. By default, Solver is expecting things to be on the ActiveSheet and this can cause unexpected behavior.
SolverReset - Danger.
See this post about the combination of SolverReset and SolverSolve setting your calculation mode to Manual (and leaving it there).
SolverReset sets all of the Solver Options back to their defaults. This can be accomplished with SolverLoad (assuming you have a saved set of defaults) or by managing them with SolverGet / SolverOptions.
DO NOT use SolverReset.
SolverOK - Solver Mostly OK.
When recording a macro in order to get an example of Solver VBA code, if you choose the defaults, you will get "Engine:=1", and "EngineDesc:="GRG Nonlinear"". According to the MS documentation, setting a value for Engine or EngineDesc is like choosing a value from the drop down list in the Solver Parameters dialog box. It also says that Engine:=1 corresponds to the Simplex LP method, not GRG Nonlinear. There appears to be opportunity for conflict in setting both of these parameters.
When testing this code ...
Sub mySolve()
Dim SetRng As Range, ChgRng As Range
Dim SetAddr As String, ChgAddr As String
Dim i As Long
For i = 2 To 4
Set SetRng = Sheets("Sheet1").Cells(i, 5)
Set ChgRng = Sheets("Sheet1").Cells(i, 4)
SetAddr = Split(SetRng.Address(external:=True), "[")(0) & Split(SetRng.Address(external:=True), "]")(1)
ChgAddr = Split(ChgRng.Address(external:=True), "[")(0) & Split(ChgRng.Address(external:=True), "]")(1)
SolverOk SetCell:=SetAddr, MaxMinVal:=3, _
ValueOf:=i, ByChange:=ChgAddr, _
Engine:=1, EngineDesc:="GRG NONLINEAR"
SolverSolve UserFinish:=True
Next i
End Sub
Instead of solving the problems on lines 2, 3, and 4 one at a time, it solved the problem on line 4 three times. That was the last problem solved, previous to using this code. It behaves as if SolverOK never updates the SetCell, ValueOf, or ByChange values. No error is thrown.
However, testing this code (removing EngineDesc), all behaves as expected ...
Sub mySolve()
Dim SetRng As Range, ChgRng As Range
Dim SetAddr As String, ChgAddr As String
Dim i As Long
For i = 2 To 4
Set SetRng = Sheets("Sheet1").Cells(i, 5)
Set ChgRng = Sheets("Sheet1").Cells(i, 4)
SetAddr = Split(SetRng.Address(external:=True), "[")(0) & Split(SetRng.Address(external:=True), "]")(1)
ChgAddr = Split(ChgRng.Address(external:=True), "[")(0) & Split(ChgRng.Address(external:=True), "]")(1)
SolverOk SetCell:=SetAddr, MaxMinVal:=3, _
ValueOf:=i, ByChange:=ChgAddr, _
Engine:=1
SolverSolve UserFinish:=True
Next i
End Sub
I recommend only setting one of Engine or EngineDesc, not both.
SolverSave - Interpreting your results
As per the MS Documentation, SolverSave will save the Solver configuration in a column of information. In my experience, that column is typically 4 rows long.
Let me explain the results from running this code ...
Sub mySolve3()
Dim SetRng As Range, ChgRng As Range, SavRng As Range
Dim SetAddr As String, ChgAddr As String, SavAddr As String
Dim iLoop As Long
For iLoop = 2 To 4
Set SetRng = Worksheets("Sheet1").Cells(iLoop, 5)
Set ChgRng = Worksheets("Sheet1").Cells(iLoop, 4)
Set SavRng = Worksheets("Sheet2").Cells(1, iLoop - 1)
SetAddr = Split(SetRng.Address(external:=True), "[")(0) & Split(SetRng.Address(external:=True), "]")(1)
ChgAddr = Split(ChgRng.Address(external:=True), "[")(0) & Split(ChgRng.Address(external:=True), "]")(1)
SavAddr = Split(SavRng.Address(external:=True), "[")(0) & Split(SavRng.Address(external:=True), "]")(1)
SolverOk SetCell:=SetAddr, MaxMinVal:=3, ValueOf:=(iLoop - 1), ByChange:=ChgAddr, Engine:=1
SolverSolve UserFinish:=True
SolverSave SaveArea:=SavAddr
Next iLoop
End Sub
This code will save the setup of three different solver runs in Columns A, B, and C in Sheet 2.
After it is run, on Sheet2: Cell A1 contains =$E$2=1
, Cell B1 contains =$E$3=2
, Cell C1 contains =$E$4=3
. The first row in the SolverSave's output is the SetCell address (that you might specify in SolverOK) and, in my case, setting it equal to the ValueOf value (that you would specify in SolverOK).
Cell A2 contains =COUNT($D$2)
, cell B2 contains =COUNT($D$3)
, and cell C2 contains =COUNT($D$4)
. The second row in SolverSave's output is ByChange address (that you would specify in SolverOK). More investigation is needed to understand why the COUNT function is used.
Cell A3, B3, and C3 contain
={32767,32767,0.000001,0.01,FALSE,FALSE,FALSE,1,2,1,0.0001,TRUE}
By inspection, it appears this is an array of the first 12 TypeNum values in SolverGet. Using SolverGet to retrieve these values, instead of 32767 you get 2147483647 - I expect they have some internal issues with data types.
Cells A4, B4, and C4 all contain
={0,0,1,100,0,FALSE,FALSE,0.075,0,0,FALSE,30}
Again, by inspection, it appears this is an array of the last 12 TypeNum values in SolverGet. Since SolverGet has 29 TypeNum's, it would seem there are 5 that are not available. SolverOptions, however, has only 21 parameters.
After using SolverSave to save a configuration, the contents of the cells can be modified and SolverLoad used to change the Solver configuration (instead of SolverOK).
My original answer to the posted question continues below ...
I need to investigate Solver more. There are several flaky things going on. I was able to make the following work using a loop in VBA.
Starting from this ...
I got these results ...
using this code ...
Sub mySolve()
Dim LoadRng As Range
Dim i As Long
Set LoadRng = Sheets("Sheet1").Range(Cells(1, 7), Cells(4, 7))
For i = 2 To 4
LoadRng.Cells(1, 1).Value = "=$E$" & i & "=1"
LoadRng.Cells(2, 1).Value = "=COUNT($D$" & i & ")"
LoadRng.Cells(3, 1).Value = "={32767,32767,0.000001,0.01,FALSE,FALSE,FALSE,1,2,1,0.0001,TRUE}"
LoadRng.Cells(4, 1).Value = "={0,0,1,100,0,FALSE,FALSE,0.075,0,0,FALSE,30}"
SolverLoad LoadArea:=LoadRng.Address
SolverSolve UserFinish:=True
SolverFinish KeepFinal:=1
Next i
End Sub
Things to diagnose:
- SolverReset caused some very strange behavior - when using it,
subsequent calls to SolverSolve caused Excel to flip to manual
calculation.
- The first call to SolverOK would set the problem up, but
subsequent calls would not modify the problem.
- SolverAdd, SolverChange, etc. affect constraints but not (apparently) the base
problem setup.