Tags
Updated:
Many thanks to Joris Claassen who provided feedback on how to better optimize this code. Please find the updated code below.
Hey guys,
As I continue my quest to become a master VBA programmer, I would like to share a piece of code with you that I recently constructed. This could was the result of Excel Solver not being able to generate a satisfactory solution to my optimization problem. Thus, I wanted to run a sensitivity analysis to see how various initial values would affect my target cell. This code allows the user to specify a target cell, a set of change cells, a range of lower bounds (inclusive) and a range of upper bounds (inclusive), as well as a precision level. The code then one-by-one tests the sensitivity of each of your change cells on your target cell and generates graphs demonstrating the output. I would appreciate any feedback on the code!
Thanks,
Adam
'Created by Adam McElhinney, 8/4/2011 'This code allows a user to specify a range of cells to adjust, a target cell as well as lower and upper bounds for their adjustments. 'Then the code generates a charts that determine how sensititive the target cell is to the input parameters. This can be useful for 'cases where Solver cannot generate a satisfactory solution. Sub refit_model() Call clear_sheet Call calc_configs Call ArrangeMyCharts End Sub 'Clear Sheets Sub clear_sheet() Dim check As Worksheet Application.DisplayAlerts = False Sheets("output").Select Sheets("output").Cells.Clear Dim wsSheet As Worksheet On Error Resume Next Set wsSheet = Sheets("graphs") If Not wsSheet Is Nothing Then wsSheet.Delete Sheets.Add.Name = "graphs" Else Sheets.Add.Name = "graphs" End If End Sub Sub calc_configs() Application.ScreenUpdating = True 'Timer 'Dim Starttime As Double, EndTime As Double 'Startime = Timer 'Prompt user to select range of coefficients, lower bounds and upper bounds Worksheets("calculation_configs").Activate Dim variables As Range Dim change As Range Dim lower As Range Dim upper As Range Dim current As Range Dim target As Range Dim precision As Integer Dim test_mode As Integer Set variables = Application.InputBox("Select the row of variable names:", Default:=Range("a6:a19").Address, Type:=8) Set current = Application.InputBox("Select the row of cells that are the current configs:", Default:=Range("b6:b19").Address, Type:=8) Set change = Application.InputBox("Select the row of cells to change:", Default:=Range("c6:c19").Address, Type:=8) Set lower = Application.InputBox("Select the row of cells that are lower bound:", Default:=Range("d6:d19").Address, Type:=8) Set upper = Application.InputBox("Select the row of cells that are upper bound:", Default:=Range("e6:e19").Address, Type:=8) Set target = Application.InputBox("Select the target cell", Default:=Range("k16").Address, Type:=8) precision = Application.InputBox("Enter in the graphics precision:", Default:=10) test_mode = (MsgBox("Do you want to run in test mode? Click 'Yes' for test mode or 'No' for regular mode. In test mode, cells will not recalculate.", vbYesNo)) 'Check to ensure that change, lower and upper are all same length Dim j, k, l As Integer j = change.Rows.Count k = lower.Rows.Count l = upper.Rows.Count If j <> k Or k <> l Then MsgBox ("The number of cells to change must equal the number of upper bounds and the number of lower bounds") Exit Sub End If Application.ScreenUpdating = False 'Assign initial coefficients, lower bounds and upper bounds to an array ReDim coeff(j) As Double ReDim lowerb(j) As Double ReDim upperb(j) As Double Dim i As Integer i = 1 For i = 1 To j coeff(i) = change.Cells(i, 1).value lowerb(i) = lower.Cells(i, 1).value upperb(i) = upper.Cells(i, 1).value 'MsgBox (lower.Cells(i, 1).Value) Next i 'QC Code 'ReDim testdisplay(j) As Double 'testdisplay = lowerb 'MsgBox testdisplay(1) & "-" & testdisplay(2) & "-" & testdisplay(3) & "-" & testdisplay(4) & "-" & testdisplay(5) & "-" & testdisplay(6) & "-" & testdisplay(7) & "-" & testdisplay(8) & "-" & testdisplay(9) & "-" & _ 'testdisplay(10) & "-" & testdisplay(11) & "-" & testdisplay(12) & "-" & testdisplay(13) & "-" & testdisplay(14) 'Determine distance between upper and lower bounds, as well as upper bound and initial values and lower bound and initial values ReDim distance(j) As Double ReDim UpperDistance(j) As Double ReDim LowerDistance(j) As Double i = 0 For i = 1 To j distance(i) = upperb(i) - lowerb(i) UpperDistance(i) = upperb(i) - coeff(i) LowerDistance(i) = coeff(i) - lowerb(i) Next i 'Determine the graphing precision change intervals ReDim interval(j) As Double For i = 1 To j interval(i) = distance(i) / precision 'MsgBox (interval(i)) Next i 'QC Code 'ReDim testdisplay2(j) As Double 'testdisplay2 = distance 'MsgBox testdisplay2(1) & "-" & testdisplay2(2) & "-" & testdisplay2(3) & "-" & testdisplay2(4) & "-" & testdisplay2(5) & "-" & testdisplay2(6) & "-" & testdisplay2(7) & "-" & testdisplay2(8) & "-" & testdisplay2(9) & "-" & _ 'testdisplay2(10) & "-" & testdisplay2(11) & "-" & testdisplay2(12) & "-" & testdisplay2(13) & "-" & testdisplay2(14) 'Set all configs to current values change.value = current.value 'Calc sum squares If test_mode = vbNo Then Application.CalculateFull End If 'Record results 'Create index variable Dim ObsCol As Range 'ObsCol=Sheets("output"). Dim ObsNum As Integer ObsNum = 1 For i = 1 To j Sheets("output").Cells(1, i).value = variables.Cells(i, 1) Sheets("output").Cells(2, i).value = change.Cells(i, 1) Next i Sheets("output").Cells(1, j + 1).value = "Target" Sheets("output").Cells(1, j + 2).value = "ObsNum" Sheets("output").Cells(2, j + 1).value = target.Cells(1, 1).value Sheets("output").Cells(2, j + 2).value = ObsNum 'New Counters Dim n As Integer Dim p As Integer n = 1 p = 1 'Iterate through each variable one by one in increments equal to the precision, calculating the value of the target i = 1 'Loop for each variable For i = 1 To j 'Set all configs to lower bounds change.value = lower.value 'Loop for each increment For m = 1 To precision change.Cells(i, 1).value = m * interval(i) + lowerb(i) 'MsgBox (m * interval(i) + lowerb(i)) If test_mode = vbNo Then Application.CalculateFull End If 'Print output n = 1 For n = 1 To j 'Write change values Sheets("output").Cells(2 + i * precision - (precision - m), n).value = change.Cells(n, 1).value Next n 'Write target value Sheets("output").Cells(2 + i * precision - (precision - m), j + 1).value = target.value 'Obs Num Sheets("output").Cells(2 + i * precision - (precision - m), j + 2).value = i * precision - (precision - m) + 1 Next m 'Create graph of output Worksheets("output").Activate ActiveSheet.Shapes.AddChart.Select ActiveChart.SetSourceData Source:=Sheets("output").Range(Cells(i * precision - (precision - 1) + 2, j + 1), _ Cells(i * precision + 2, j + 1)) Debug.Print "Chart Target range:" & i * precision - (precision - 1) + 2 & "-" & j + 1 ActiveChart.ChartType = xlLine ActiveChart.SeriesCollection(1).XValues = Sheets("output").Range(Cells(i * precision - (precision - 1) + 2, i), _ Cells(i * precision + 2, i)) ActiveChart.HasTitle = True ActiveChart.ChartTitle.Text = "Change in Target for a Change in Variable " & variables(i) ActiveChart.Location xlLocationAsObject, "graphs" Debug.Print "Chart Series range:" & ii * precision - (precision - 1) + 2 & "-" & i Next i 'Save workbook Application.DisplayAlerts = False 'ActiveWorkbook.SaveAs Filename:="C:\Documents and Settings\amcelhinney\Desktop\" & test_file EndTime = Timer Debug.Print "Execution Time in seconds:", EndTime - Starttime End Sub Sub ArrangeMyCharts() Worksheets("graphs").Activate Dim iChart As Long Dim nCharts As Long Dim dTop As Double Dim dLeft As Double Dim dHeight As Double Dim dWidth As Double Dim nColumns As Long dTop = 75 ' top of first row of charts dLeft = 100 ' left of first column of charts dHeight = 225 ' height of all charts dWidth = 375 ' width of all charts nColumns = 3 ' number of columns of charts nCharts = ActiveSheet.ChartObjects.Count Debug.Print nCharts For iChart = 1 To nCharts With ActiveSheet.ChartObjects(iChart) .Height = dHeight .Width = dWidth .Top = dTop + Int((iChart - 1) / nColumns) * dHeight .Left = dLeft + ((iChart - 1) Mod nColumns) * dWidth End With Next End Sub Sub ArrangeMyCharts_2() Worksheets("Calculation_Configs").Activate Dim iChart2 As Long Dim nCharts2 As Long Dim dTop As Double Dim dLeft As Double Dim dHeight As Double Dim dWidth As Double Dim nColumns As Long dTop = 300 ' top of first row of charts dLeft = 500 ' left of first column of charts dHeight = 225 ' height of all charts dWidth = 375 ' width of all charts nColumns = 2 ' number of columns of charts nCharts2 = ActiveSheet.ChartObjects.Count Debug.Print nCharts2 For iChart2 = 1 To nCharts2 With ActiveSheet.ChartObjects(iChart2) .Height = dHeight .Width = dWidth .Top = dTop + Int((iChart2 - 1) / nColumns) * dHeight .Left = dLeft + ((iChart2 - 1) Mod nColumns) * dWidth End With Next End Sub