Forum Discussion
FICO
8 months agoCommunity Manager
Variable object mapping issue in Python Callbacks with Presolve enabled
Hi,
I am implementing a Branch-and-Cut algorithm using the Xpress Python interface. I have structured my code as a class where I create variables (using xp.var) and store them in a dictionary attribute (e.g., self.variables['x']) to use them later in other methods or callbacks.
I noticed a strange behavior when running the model with Presolve enabled. When I access these stored variable objects inside a callback (like OptNodeCallback), they seem to point to completely wrong variables.
For instance, when I print my x variable dictionary inside the callback, I see y or w variables appearing in the values, even though the keys correspond to x variables. It looks like the internal variable indices are shifting due to presolve, but the Python objects are still pointing to the old indices (which now hold different variables). Since the variable order remains consistent, I suspect that when variables are removed during presolve, the internal indices shift (similar to a linked list or array removal), causing the Python objects to point to the wrong columns.
If I turn off presolve (prob.controls.presolve = 0), the mapping works perfectly and I get the correct x variables. However, I want to keep presolve on for performance reasons.
Is there a standard way to safely access the original Python variable objects inside a callback without disabling presolve?
Thanks in advance.
6 Replies
- FICOCommunity Manager
Hi,
The confusion with variable indices is a known issue in older versions of the Xpress Python interface. In later releases, accessing a variable object from a callback raises an error. This is because variable objects are associated with the original problem, but during a callback the problem is presolved, and Xpress does not allow access to the original problem data while the problem is presolved. The recommended way to access original variable properties during a callback is to cache the individual properties before the start of the solve, and then access e.g. self.variable_names['x'] instead of self.variables['x'].name from the callback.
If you need further help, please let me know which version of Xpress you are using, since the behaviour in this area has changed recently.
- FICOCommunity Manager
Hi Chris.
Thank you for your response.
I am using Xpress 9.6.0 and Python 3.13.9.
I am still a bit confused about how to properly access variables in the callback. I have included a skeleton of my code below. Could you please explain how I can modify it to access the variables correctly with presolve?
In addition, if there is any better way to get solution in callback using getCallbackSolution?
class XpressSolver(): def __init__(self): .... def build(self): self.prob = xp.problem() x = self.model.addVariables(..., name='x') y = self.model.addVariables(..., name='y') self.variables['x'] = x self.variables['y'] = y ........ def PreIntSolCallback(self, model, object, soltype, cutoff): x = self.variables['x'] y = self.variables['y'] .... x_sol, y_sol = {},{} for key, value in x.items(): x_sol[key] = model.getCallbackSolution(value) for key, value in y.items(): y_sol[key] = model.getCallbackSolution(value) ...... - FICOCommunity Manager
Your code is actually correct. It is allowed to use xp.var() objects in functions which operate on the original problem space, like model.getCallbackSolution. But if you try to print 'value' (or 'value.name') in your for-loop, the wrong name would be printed, due to the bug I mentioned. I am guessing that is the cause of your confusion. If you want to access the var names (or other properties like bounds), you would need to cache them like this:
class XpressSolver(): def __init__(self): .... def build(self): self.model = xp.problem() x = self.model.addVariables(..., name='x') y = self.model.addVariables(..., name='y') self.variables['x'] = x self.variables['y'] = y # cache var names for later access from callbacks self.var_names = [v.name for v in self.model.getVariable()] ........ def PreIntSolCallback(self, model, object, soltype, cutoff): x = self.variables['x'] y = self.variables['y'] .... x_sol, y_sol = {},{} for key, value in x.items(): x_sol[key] = model.getCallbackSolution(value) # use var index to look up its name in the original problem name = self.var_names[value.index] print(name, x_sol[key]) for key, value in y.items(): y_sol[key] = model.getCallbackSolution(value) name = self.var_names[value.index] print(name, y_sol[key]) - FICOCommunity Manager
Thank you for the clarification.
I understand that using the originally declared variable objects to query values is the most intuitive approach for solving separation problems. However, I am still confused about how the original variables map to the presolved columns, especially when some variables are eliminated during the presolve phase.
For example, let's say I am solving a TSP by generating subtour elimination constraints. To detect a subtour, I need to traverse the graph by checking the values of adjacent arc variables starting from a specific node.
If some arc variables are removed (presolved out), how can I access their values to check the connectivity? Does the interface allow me to query these "missing" variables (e.g., returning a fixed value), or is there a specific method to handle this mapping?
- FICOCommunity Manager
We recommend that you work in the original problem space where possible, so that you do not need to worry about the presolve mapping. The getCallbackSolution function (and other related functions) postsolves the solution, returning values for all of your original variables, including those which were eliminated during presolve. So you can check connectivity without needing to do any mapping.
Things get a bit complicated when you need to query problem data such as bounds on variables, right-hand side values of constraints, or names of objects. This data is not available in the original space during the solve, so if you need to query it, it must be cached beforehand, as shown in my example.
In some cases it is necessary to work in the presolved space, for example if you want to add cuts to the problem during the preintsol callback, you need to express them using presolve indices, but we provide APIs to translate from original space to presolve space for this purpose: problem.presolverow. See an example here. And you can query the presolve mapping by calling problem.getpresolvemap. But this is advanced functionality that you hopefully will not need.
- FICOCommunity Manager
Thanks for the detailed explanation. It really cleared things up for me.
Your help is greatly appreciated!
Related Content
Related Content
Variable names in callback (Java API)
I construct a cut in a preintsol callback using variables from the model containing the callback. When constructing the model, I give the variables names (using withName(...)) and store them in a vec...9 months agoPlace FICO® Xpress Optimization ForumFICO® Xpress Optimization Forum4 unread comments4 unread1View0likes4CommentsAre preintsol callbacks threadsafe?
The optimizer documentation states "When the MIP callbacks are called they are MUTEX protected to allow non threadsafe user callbacks." In my Java code for one-tree Benders, if I specify a single thr...9 months agoPlace FICO® Xpress Optimization ForumFICO® Xpress Optimization Forum3 unread comments3 unread2Views0likes3CommentsObtaining the variable values in a callback
In the XPRSoptNodeEvent callback, in order to separate valid inequalities, one must access the values of the variables. Let's say we want to obtain the values of XBRBvar[] myVarSubset. To my understa...5 years agoPlace FICO® Xpress Optimization ForumFICO® Xpress Optimization Forum2 unread comments2 unread0Views0likes2Comments