Hi!

I’ve implemented a callback in amplpy to change the gap after a given number of explored nodes, and I notice three things that I would like to discuss here:

- With the callback the parallel mode is not applied, and only 1 thread is used to solve the problem (thus, more time was used to solve the problem).
- With the callback the MIP search method changed to traditional branch and cut, while without the callback dynamic search is used (actually I read that cplex disable dynamic search in presence of control callbacks).
- The count of nodes that I do with my callback is different from that displayed in the logfile, please, if you can explain to me that.

Finally, I ask for a method (if exist) to use callbacks preserving the parallel mode, as well as for suggestions to improve my callback.

My callback is as follows:

#Callback class

class MyCallback(ampls.GenericCallback):

```
def __init__(self, stoprule):
super(MyCallback, self).__init__()
self._stoprule = stoprule
self._current = 0
self._nMIPnodes = 0
self._continueOpt = True
def run(self):
t = self.getAMPLWhere()
if t == ampls.Where.MSG:
print('>' + self.getMessage())
elif t == ampls.Where.MIPNODE:
self._nMIPnodes += 1
print("New MIP node, count {}".format(self._nMIPnodes))
if self._nMIPnodes >= self._stoprule['nodes'][self._current]:
self._continueOpt = True
return -1
elif t == ampls.Where.MIPSOL:
print("MIP Solution = {}".format(self.getObj()))
return 0
def setCurrentGap(self):
gaptolpct = 100*self._stoprule['gaptol'][self._current]
stopnodes = self._stoprule['nodes'][self._current]
print("Increasing gap tolerance to "
f"{gaptolpct:.2f}% after {stopnodes:.1f} nodes")
ampls_model.setAMPLsParameter(ampls.SolverParams.DBL_MIPGap,
self._stoprule['gaptol'][self._current])
self._current += 1
```

#Solve using callbacks

ampls_model=ampl.exportModel(solver,[“return_mipgap=5”,“mipstartvalue=3”,“mipstartalg=2”,“mipdisplay=2”])

#Stopping rule

stopdict={‘nodes’:(100,200,300),

‘gaptol’:(.001,.02,.3)}

callback=MyCallback(stopdict)

ampls_model.setCallback(callback)

#Invoke solver

while callback._continueOpt:

callback._continueOpt = False

ampls_model.optimize()

if callback._continueOpt:

callback.setCurrentGap()

*Overview results without callback:*

…

MIP search method: dynamic search.

Parallel mode: deterministic, using up to 8 threads.

…

690 391 16850.0251 423 16859.1290 16819.9129 379322 0.23%

…

Total (root+branch&cut) = 3713.24 sec. (1033774.39 ticks)

CPLEX 20.1.0.0: optimal integer solution within mipgap or absmipgap; objective 16859.12903

*Overview results with the callback:*

…

MIP search method: traditional branch-and-cut.

Parallel mode: none, using 1 thread.

…

New MIP node, count 99

48 48 16827.1844 467 16882.0053 16819.8279 277235 0.37% x133190 U 48 45 24

New MIP node, count 100

Flow cuts applied: 461

Mixed integer rounding cuts applied: 1461

Root node processing (before b&c):

Total (root+branch&cut) = 5092.52 sec. (4336608.06 ticks)

Increasing gap tolerance to 0.10% after 100.0 nodes

…

Thanks in advance