3

I tried to use Solver on a working sheet, say sheet A. I also have some cells with "=rand()" on sheet A, or say any formula on the sheet using custom functions with Application.Volatile written inside. I'd like these volatile cells to stop recalculate when I am doing solver, so I used Application.Calculation = xlCalculationManual in my Solver program, but turned out after I ran the Solver, I found that those volatile cells have changed. Where have I done wrong?

2 Answers 2

3

There are 2 ways to avoid this:

  1. Do not use solver! If you write an own sub to do the stuff you can use Range.Calculate while the calculation is manual to just calculate this cells (all other cells will be ignored).

  2. Change the formulas and go with iteration options -> formulas -> enable iterative calculation. Now use a helper-cell which holds true/false (or 1/0 or whatever) and the change the formulas you do not want to calculate as follows(helper-cell A1 / formula A2):


=IF(A1,A2,[your old formula])

This way, as long as A1 is true it will output the value from before the calculation.

As far as I know, there is no other way, because solver does a Calculate each time a new cycle is tested.


EDIT:
Having volatile UDF which do not need to be volatile the whole time, you can use a simple trick (again a helper-cell) is needed:

A1: [volatile function like =NOW() or =RAND()]
A2: =MY_UDF(A1)

In Module:

Public Function MY_UDF(a As Variant) As Double
  a = a
  MY_UDF = Now
End Function

As long as A1 holds something volatile, your UDF will recalculate every time. But if you empty out A1 (or make it at leas non-volatile) there will be no change anymore to the value submitted to your UDF and this way excel/vba assumes that also no change will happen and just skip it for recalculation. This way you also can build up your own RAND-UDF (with different name of course) to just stop ALL volatile functions in your workbook as long as your helper-cell is non-volatile.

As a note: after making A1 (in this example) non-volatile, the first calculation afterwards will still run 1 time like it is volatile. Also changing A1 from one value to another (like 0 to 1) will run one time.

Sign up to request clarification or add additional context in comments.

8 Comments

You are surely right. It is clearly impossible for the solver to do its magic without recalculating since the functions and constraints it is being asked to optimize consist of spreadsheet formulas.
But why Solver recalculates the whole sheet/whole sheets it operates on? Why doesn't it use range.calculate? I met this problem as I was doing a nonlinear OLS (probably no chance to skip Solver?), but I want to do it N times, each time the optimization function (the 1st argument in SolverOk) is constructed upon some volatile things (they are volatile simply becasue I used them somwhow to get N different optimization functions!), although they don't participate in the optimization directly, they can't be changed since they will change optimization function. So this is the story:(
BTW, what about copy them out to a new area as value?
Also, I was thinking if it's possible to forgo Application.Volatile in my custom functions, but use .CalculateFull method in another program. But I can't figure out how to CalculateFull only for those volatile functions? I have to recalculte these volatile functions to get N different optimization functions.
@NicholasLiu Simply because the solver stops automatic calculation. If you need to change... um.. A1 and C3, then it is not possible to do it in one step. But speed is important here and after changing 1 of n cells, you do not want to recalculate every time. At the other hand solver never knows the dependencies between the cells, this way everything needs to be recalculated after all cells have been changed. Having this as a general behavior of solver, then this also applies when changing just one cell :(
|
3

Here is a workaround that might help if you want to have random values in a spreadsheet that can be recalculated at will and whose volatility can be turned on and off.

First, create a 1-cell named range, say "trigger"

Then, put the following code in a standard code module:

Function SemiVolatileRand(x As Variant) As Double
    SemiVolatileRand = x - x + Rnd()
End Function

Sub ReSample()
    Randomize
    Range("trigger").Value = Range("trigger").Value + 0.01
End Sub

Attach the ReSample sub to a button. Replace all occurrences of =RAND() by =SemiVolatileRand(trigger). They will recalulcate whenever the button is pressed. Also, if you ever want to turn full volatility back on, just put the formula =RAND() in the trigger cell. (Getting full volatility in this last case seemed to require that my code does something with the dummy variable x, hence the somewhat poinless x - x).

Randomize reseeds the random number generator from the system clock. It should be called at least once per session. If you don't, then each time you open an Excel workbook which uses VBA rnd, you will get the same sequence of random values. You can verify this by making a blank workbook and in the ThisWorkbook code module put:

Private Sub Workbook_Open()
MsgBox Rnd()
End Sub

The message block will display the same value each time you open the workbook. On the other hand if you put the line Randomize before the MsgBox then you will get different values each time you open it.

Note that the workbook open event is a natural place to put the statement Randomize if you are planning to use rnd.

The reason I didn't put Randomize in the function itself was both to save CPU cycles and also because of a nagging concern that a certain percentage of the time you will be reseeding the random number generator with exactly the same system time. That might be impossible with modern architectures running recent versions of Excel, but e.g. does sometimes happen if you had Randomize Timer (which you sometimes encounter when reading other's code) since the timer function has 1 millisecond resolution irrespective of the system clock.

The code I have does have the drawback that if you bypass the button and just change the trigger cell then you could miss the reseeding. If this is a concern, 1 possibility would be like this:

Public Initialized as Boolean

Function SemiVolatileRand(x As Variant) As Double If Not Initialized Then Randomize Initialized = True End If SemiVolatileRand = x - x + Rnd() End Function

This will prevent the function from running if rnd isn't properly seeded.

Excel itself takes care of seeding automatically with the worksheet function Rand(), so it is strictly a VBA complication.

2 Comments

Why you put a Randomize in your ReSample() subroutine?
@NicholasLiu It was to seed the pseudo-random number generator. See the edit.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.