Thank you very much.
I will start off a new thread given that the initial problem is solved a few weeks ago and now I have new questions related to callbacks.
Original Message:
Sent: Mon May 25, 2020 09:14 AM
From: Daniel Junglas
Subject: Docplex Python "Losing Constraint"
Sorry, I missed the detail about the new variables. This is actually not possible with CPLEX. From a callback you can only add new constraints. During a solve, variables can neither be added nor removed. In some cases it is possible to create the required variables a-priori, protect them from being removed in presolve and then do something with them in a callback. But that smells a lot like a hack.
------------------------------
Daniel Junglas
Original Message:
Sent: Mon May 25, 2020 08:24 AM
From: Fernando Dias
Subject: Docplex Python "Losing Constraint"
Thank you very much. This will give a clear idea of how to start.
One difference that I noticed is that you don't add any new variable when you add the cuts. In my case, every time, I'll add a cut a new binary related to the specific cut? Do you know if callback can handle that? Adding a new constraint and new variables?
Thank you
------------------------------
Fernando Dias
Original Message:
Sent: Mon May 25, 2020 12:46 AM
From: Daniel Junglas
Subject: Docplex Python "Losing Constraint"
Here is a short code example that uses a cut callback with CPLEX. In order to do this, you have to use the (undocumented) mixin class:
import sysfrom cplex.callbacks import UserCutCallbackfrom docplex.mp.callbacks.cb_mixin import *from docplex.mp.model import Modelclass MyCutCallback(ConstraintCallbackMixin, UserCutCallback): def __init__(self, env): UserCutCallback.__init__(self, env) ConstraintCallbackMixin.__init__(self) self.eps = 1e-6 self.nb_cuts = 0 def add_cut_constraint(self, ct): self.register_constraint(ct) @print_called("--> custom cut callback called: def __call__(self): sol = self.make_solution() unsats = self.get_cpx_unsatisfied_cts(self.cts, sol, self.eps) for ct, cut, sense, rhs in unsats: self.add(cut, sense, rhs) self.nb_cuts += 1 print('-- add new cut[{0}]: [{1!s}]'.format(self.nb_cuts, ct))def build_supply_model(fixed_costs, supply_costs, use_cuts=False, **kwargs): m = Model(name='suppy', **kwargs) nb_locations = len(fixed_costs) nb_clients = len(supply_costs) range_locations = range(nb_locations) range_clients = range(nb_clients) used = m.binary_var_list(range_locations, name='used') supply = m.continuous_var_matrix(range_clients, range_locations, lb=0, ub=1, name='supply') m.used = used m.supply = supply m.add_constraints(m.sum(supply[c, l] for l in range_locations) == 1 for c in range_clients) m.add_constraints( m.sum(supply[c, l] for c in range_clients) <= (nb_clients - 1) * used[l] for l in range_locations) total_fixed_cost = m.dot(used, fixed_costs) m.add_kpi(total_fixed_cost, 'Total fixed cost') total_supply_cost = m.sum(supply[c, l] * supply_costs[c][l] for c in range_clients for l in range_locations) m.add_kpi(total_supply_cost, 'Total supply cost') m.minimize(total_fixed_cost + total_supply_cost) if use_cuts: cut_cb = m.register_callback(MyCutCallback) for l in range_locations: location_used = used[l] for c in range_clients: cut_cb.add_cut_constraint(supply[c, l] <= location_used) print('* add cut constraints callback with {0} cuts'.format(len(cut_cb.cts))) m.cut_callback = cut_cb return mdef print_solution(m, tol=1e-6): used = m.used supply = m.supply n_locations = len(used) n_clients = (int)(len(supply) / n_locations) for l in range(n_locations): if used[l].solution_value >= 1 - tol: print('Facility %d is used, it serves clients %s' % (l, ', '.join((str(c) for c in range(n_clients) if supply[c, l].solution_value >= 1 - tol))))DEFAULT_FIXED_COSTS = [480, 200, 320, 340, 300]DEFAULT_SUPPLY_COSTS = [[24, 74, 31, 51, 84], [57, 54, 86, 61, 68], [57, 67, 29, 91, 71], [54, 54, 65, 82, 94], [98, 81, 16, 61, 27], [13, 92, 34, 94, 87], [54, 72, 41, 12, 78], [54, 64, 65, 89, 89]]def build_test_supply_model(use_cuts, **kwargs): return build_supply_model(DEFAULT_FIXED_COSTS, DEFAULT_SUPPLY_COSTS, use_cuts=use_cuts, **kwargs)if __name__ == "__main__": args = sys.argv use_cuts = True for arg in args[1:]: if arg == '-cuts': use_cuts = False elif arg == '-nocuts': use_cuts = False else: print('Unknown argument %s' % arg) random = False if random: import numpy as np nl = 20 nc = 100 fixed = np.random.randint(100, high=500, size=nl) supply = np.random.randint(1, high=100, size=(nc, nl)) else: fixed = DEFAULT_FIXED_COSTS supply = DEFAULT_SUPPLY_COSTS m = build_supply_model(fixed, supply, use_cuts=use_cuts) m.parameters.preprocessing.presolve = 0 m.print_information() s = m.solve(log_output=False) assert s m.report() print_solution(m) if not random: assert abs(m.objective_value - 843) <= 1e-4
------------------------------
Daniel Junglas
Original Message:
Sent: Sun May 24, 2020 11:41 PM
From: Fernando Dias
Subject: Docplex Python "Losing Constraint"
Thank you very much for your help
It was exactly the problem that I had.
Now furthering that, I wanna use that loop that I have that creates the cuts as a separated function and then use that function as part of a callback option in the DOCPLEX library (if they're available). Basically, the idea is to instead of solving the model from start in every iteration, I would start the code as it is, and from time to time, check if the cuts are necessary and add them, without stopping, going another iteration in the loop or rebuilding the model. So the callbacks would verify if cuts are necessary, process them and add them to the original model.
Any idea if that's possible in Python using the docplex library and how to start it?
Thank you
------------------------------
Fernando Dias
Original Message:
Sent: Tue May 12, 2020 09:38 AM
From: Daniel Junglas
Subject: Docplex Python "Losing Constraint"
How do you cuts look like? Are these just linear constraints? From your output you not only add constraints but also add variables. Could you export your model using Model.export_as_lp() for each iteration and then compare the LP files generated for each iteration? What is the difference between them? Can you maybe attach the two files here?
------------------------------
Daniel Junglas
Original Message:
Sent: Mon May 11, 2020 05:51 PM
From: Fernando Dias
Subject: Docplex Python "Losing Constraint"
Hi everyone,
I am working on a cutting plane algorithm using CPLEX on Python. The idea is something like: in each iteration, I take all the previous solutions from previous iterations that were violating a relaxation that I had and create a set of mixed-integer cuts. Those cuts are added into a model and the model is solved again. To make things more clear for me, in each iteration I build the model from scratch. In order to understand what's happening in that loop, I keep track of the number of constraints that are added by each iteration. However, after a couple of iterations, I observed that even though new constraints were required and properly calculated, the model is somehow "losing" some of them on the way.
For example, at iteration 3 there are 1312 constraints. However, in iteration 4, even after adding one more cut, the number of constraints dropped to 1273.
> ITERATION 3
Adding cut in y
Adding cut in y
Adding cut in y
Model: Action
- number of variables: 712
- binary=212, integer=0, continuous=500
- number of constraints: 1312
- linear=1272, quadratic=40
- parameters:
parameters.threads = 1
parameters.timelimit = 1000.00000000000000
parameters.mip.display = 1
- problem type is: MIQCP
> ITERATION 4
Adding cut in y
Model: Action
- number of variables: 714
- binary=214, integer=0, continuous=500
- number of constraints: 1273
- linear=1233, quadratic=40
- parameters:
parameters.threads = 1
parameters.timelimit = 1000.00000000000000
parameters.mip.display = 1
- problem type is: MIQCP
Has anyone faced something similar?
Any help would be highly appreciated!
Thank you very much
------------------------------
Fernando Dias
------------------------------
#DecisionOptimization