在VBA中使用求解器,行

时间:2016-04-17 02:16:08

标签: excel vba excel-vba loops solver

我正在尝试使用求解器找到182个单元的根,每个单元仅依赖于另一个变量,输出单元从O2到O183,可变单元从P2到P183。我试图更改变量单元格以使输出单元格等于1.代码可以工作,但结果不会收敛。因为我只想要固定值1,所以我没有设置MaxMin值。 代码是:

Sub Solver()
    Dim setcellrange As Range, bychangerange As Range
    Dim i As Long
    For i = 3 To 5
        Set setcellrange = Sheets("AshfordPierce").Cells(i, 15)
        Set bychangerange = Sheets("AshfordPierce").Cells(i, 16)
        SolverReset
        SolverOk SetCell:=setcellrange.Address, ValueOf:=1, ByChange:=bychangerange.Address, Engine:=1, EngineDesc:="GRG NONLINEAR"

        SolverSolve

Next i
End Sub

当我使用VBA之外的解算器功能时,它可以工作,但VBA结果并不会收敛。任何帮助表示赞赏。

1 个答案:

答案 0 :(得分:2)

注意:我将使用这篇文章重点介绍在VBA中使用Solver的几个挑战和希望最佳做法。如果需要更新,请随时告诉我。

告诉求解器使用哪些细胞。

似乎(几乎)Solver的所有参数都是Variant类型。这可能会让您相信您在提供信息方面具有很大的灵活性。事实证明,您需要提供精心制作的文本字符串。

使用SolverSave作为示例,根据MS documentation 您必须指定SaveArea,如果SaveArea与活动工作表不同,则SaveArea必须包含工作表名称。

这有效:

SolverSave SaveArea:="Sheet2!A1"

事实上,所有MS文档都在其示例中使用了字符串文字。

这有效:

SolverSave SaveArea:="Sheet2!A1:A4"

只要Save只需要四行来存储其数据,这通常是,但并非总是如此。

这不起作用:

Set SaveRng = Sheets("Sheet2").Range("A1")
SolverSave SaveArea:=SaveRng

它不会抛出错误。它将一些信息放在Sheet2中的单元格A1中,但是活动工作表中的所有其他信息。

这不起作用:

Set SaveRng = Sheets("Sheet2").Range("A1:A4")
SolverSave SaveArea:=SaveRng

它会在SolverSave中抛出类型不匹配错误。

这项工作:

Set SaveRng = Sheets("Sheet2").Range("A1")
SaveAddress = Split(SaveRng.Address(external:=True), "[")(0) & Split(SaveRng.Address(external:=True), "]")(1)
SolverSave SaveArea:=SaveAddress

以上是我能找到的最简洁的方法,可以为包含工作表名称和单引号的范围构建完整地址"'"""在需要的时候。 (供您调查 - 为什么赢得SaveRng.Address工作?)

我建议对每个Solver例程使用最后一个方法(上面的参数需要一个地址)。默认情况下,Solver期望事物在ActiveSheet上,这可能会导致意外行为。 / p>

SolverReset - 危险。

请参阅this post,了解SolverReset和SolverSolve的组合,将您的计算模式设置为手动(并将其留在那里)。

SolverReset将所有求解器选项设置回默认值。这可以通过SolverLoad(假设您有一组已保存的默认值)或使用SolverGet / SolverOptions管理它们来完成。

请勿使用SolverReset。

SolverOK - 解决方案大部分没问题。

录制宏以获得Solver VBA代码示例时,如果选择默认值,您将获得" Engine:= 1"和" EngineDesc:=&#34 ; GRG Nonlinear""。根据{{​​3}},为Engine或EngineDesc设置值就像从“求解器参数”对话框的下拉列表中选择一个值。它还说Engine:= 1对应于Simplex LP方法, GRG非线性。在设定这两个参数时似乎存在冲突的机会。

测试此代码时......

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

它不是一次一个地解决第2,3和4行的问题,而是在第4行解决了三次问题。这是在使用此代码之前解决的最后一个问题。它的行为就好像SolverOK永远不会更新SetCell,ValueOf或ByChange值。没有错误。

但是,测试此代码(删除EngineDesc),所有行为都符合预期......

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

我建议只设置Engine或EngineDesc中的一个,而不是两者。

SolverSave - 解释您的结果

根据MS documentation,SolverSave会将Solver配置保存在一列信息中。根据我的经验,该列通常为4行。

让我解释运行此代码的结果......

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

此代码将保存工作表2中A列,B列和C列中三种不同求解器运行的设置。

运行后,在Sheet2上:单元格A1包含=$E$2=1,单元格B1包含=$E$3=2,单元格C1包含=$E$4=3。 SolverSave输出中的第一行是SetCell地址(您可以在SolverOK中指定),在我的情况下,将其设置为等于ValueOf值(您将在SolverOK中指定)。

单元格A2包含=COUNT($D$2),单元格B2包含=COUNT($D$3),单元格C2包含=COUNT($D$4)。 SolverSave输出中的第二行是ByChange地址(您将在SolverOK中指定)。需要进行更多调查以了解为何使用COUNT函数。

细胞A3,B3和C3含有

={32767,32767,0.000001,0.01,FALSE,FALSE,FALSE,1,2,1,0.0001,TRUE}

通过检查,看起来这是前12 MS Documentation的数组。使用SolverGet检索这些值,而不是32767,您得到2147483647 - 我希望它们在数据类型方面存在一些内部问题。

细胞A4,B4和C4均含有

={0,0,1,100,0,FALSE,FALSE,0.075,0,0,FALSE,30}

同样,通过检查,它似乎是SolverGet中最后12个TypeNum值的数组。由于SolverGet有29个TypeNum' s,似乎有5个不可用。但是,SolverOptions只有21个参数。

使用SolverSave保存配置后,可以修改单元格的内容,SolverLoad用于更改Solver配置(而不是SolverOK)。

我对发布的问题的原始答案在下面继续......

我需要更多地调查解算器。有几个片状的东西正在发生。我能够在VBA中使用循环来完成以下工作。

从此开始...

TypeNum values in SolverGet

我得到了这些结果......

enter image description here

使用此代码...

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

要诊断的事情:

  • SolverReset引起了一些非常奇怪的行为 - 使用它时, 随后对SolverSolve的调用导致Excel翻转为手动 计算
  • 第一次调用SolverOK会将问题解决,但是 后续调用不会修改问题。
  • SolverAdd,SolverChange等会影响约束,但不会(显然)影响基础 问题设置。