@@ -26,16 +26,19 @@ class COPT(NLPsolver):
2626 """
2727 NLP interface for the COPT solver.
2828 """
29+ # Map between COPT status and CVXPY status
2930 STATUS_MAP = {
30- # Success cases
31- 0 : s .OPTIMAL , # Solve_Succeeded
32- 1 : s .OPTIMAL_INACCURATE , # Solved_To_Acceptable_Level
33- 6 : s .OPTIMAL , # Feasible_Point_Found
34-
35- # Infeasibility/Unboundedness
36- 2 : s .INFEASIBLE , # Infeasible_Problem_Detected
37- 4 : s .UNBOUNDED , # Diverging_Iterates
38- }
31+ 1 : s .OPTIMAL , # optimal
32+ 2 : s .INFEASIBLE , # infeasible
33+ 3 : s .UNBOUNDED , # unbounded
34+ 4 : s .INF_OR_UNB , # infeasible or unbounded
35+ 5 : s .SOLVER_ERROR , # numerical
36+ 6 : s .USER_LIMIT , # node limit
37+ 7 : s .OPTIMAL_INACCURATE , # imprecise
38+ 8 : s .USER_LIMIT , # time out
39+ 9 : s .SOLVER_ERROR , # unfinished
40+ 10 : s .USER_LIMIT # interrupted
41+ }
3942
4043 def name (self ):
4144 """
@@ -47,16 +50,18 @@ def import_solver(self):
4750 """
4851 Imports the solver.
4952 """
50- import cyipopt # noqa F401
53+ import coptpy # noqa F401
5154
5255 def invert (self , solution , inverse_data ):
5356 """
5457 Returns the solution to the original problem given the inverse_data.
5558 """
56- attr = {}
59+ attr = {
60+ s .NUM_ITERS : solution .get ('num_iters' ),
61+ s .SOLVE_TIME : solution .get ('solve_time_real' ),
62+ }
63+
5764 status = self .STATUS_MAP [solution ['status' ]]
58- attr [s .NUM_ITERS ] = solution ['iterations' ]
59-
6065 if status in s .SOLUTION_PRESENT :
6166 primal_val = solution ['obj_val' ]
6267 opt_val = primal_val + inverse_data .offset
@@ -106,7 +111,163 @@ def solve_via_data(self, data, warm_start: bool, verbose: bool, solver_opts, sol
106111 tuple
107112 (status, optimal value, primal, equality dual, inequality dual)
108113 """
109- raise NotImplementedError ("COPT NLP interface is not yet implemented." )
114+ import coptpy as copt
115+
116+ class COPTNlpCallbackCVXPY (copt .NlpCallbackBase ):
117+ def __init__ (self , oracles , m ):
118+ super ().__init__ ()
119+ self ._oracles = oracles
120+ self ._m = m
121+
122+ def EvalObj (self , xdata , outdata ):
123+ x = copt .NdArray (xdata )
124+ outval = copt .NdArray (outdata )
125+
126+ x_np = x .tonumpy ()
127+ outval_np = self ._oracles .objective (x_np )
128+
129+ outval [:] = outval_np
130+ return 0
131+
132+ def EvalGrad (self , xdata , outdata ):
133+ x = copt .NdArray (xdata )
134+ outval = copt .NdArray (outdata )
135+
136+ x_np = x .tonumpy ()
137+ outval_np = self ._oracles .gradient (x_np )
138+
139+ outval [:] = np .asarray (outval_np ).flatten ()
140+ return 0
141+
142+ def EvalCon (self , xdata , outdata ):
143+ if self ._m > 0 :
144+ x = copt .NdArray (xdata )
145+ outval = copt .NdArray (outdata )
146+
147+ x_np = x .tonumpy ()
148+ outval_np = self ._oracles .constraints (x_np )
149+
150+ outval [:] = np .asarray (outval_np ).flatten ()
151+ return 0
152+
153+ def EvalJac (self , xdata , outdata ):
154+ if self ._m > 0 :
155+ x = copt .NdArray (xdata )
156+ outval = copt .NdArray (outdata )
157+
158+ x_np = x .tonumpy ()
159+ outval_np = self ._oracles .jacobian (x_np )
160+
161+ outval [:] = np .asarray (outval_np ).flatten ()
162+ return 0
163+
164+ def EvalHess (self , xdata , sigma , lambdata , outdata ):
165+ x = copt .NdArray (xdata )
166+ lagrange = copt .NdArray (lambdata )
167+ outval = copt .NdArray (outdata )
168+
169+ x_np = x .tonumpy ()
170+ lagrange_np = lagrange .tonumpy ()
171+ outval_np = self ._oracles .hessian (x_np , lagrange_np , sigma )
172+
173+ outval [:] = np .asarray (outval_np ).flatten ()
174+ return 0
175+
176+ # Create COPT environment and model
177+ envconfig = copt .EnvrConfig ()
178+ if not verbose :
179+ envconfig .set ('nobanner' , '1' )
180+
181+ env = copt .Envr (envconfig )
182+ model = env .createModel ()
183+
184+ # Pass through verbosity
185+ model .setParam (copt .COPT .Param .Logging , verbose )
186+
187+ # Get oracles for function evaluation
188+ oracles = data ['oracles' ]
189+
190+ # Get the NLP problem data
191+ x0 = data ['x0' ]
192+ lb , ub = data ['lb' ].copy (), data ['ub' ].copy ()
193+ cl , cu = data ['cl' ].copy (), data ['cu' ].copy ()
194+
195+ lb [lb == - np .inf ] = - copt .COPT .INFINITY
196+ ub [ub == + np .inf ] = + copt .COPT .INFINITY
197+ cl [cl == - np .inf ] = - copt .COPT .INFINITY
198+ cu [cu == + np .inf ] = + copt .COPT .INFINITY
199+
200+ n = len (lb )
201+ m = len (cl )
202+
203+ cbtype = copt .COPT .EVALTYPE_OBJVAL | copt .COPT .EVALTYPE_CONSTRVAL | \
204+ copt .COPT .EVALTYPE_GRADIENT | copt .COPT .EVALTYPE_JACOBIAN | \
205+ copt .COPT .EVALTYPE_HESSIAN
206+ cbfunc = COPTNlpCallbackCVXPY (oracles , m )
207+
208+ if m > 0 :
209+ jac_rows , jac_cols = oracles .jacobianstructure ()
210+ nnz_jac = len (jac_rows )
211+ else :
212+ jac_rows = None
213+ jac_cols = None
214+ nnz_jac = 0
215+
216+ if n > 0 :
217+ hess_rows , hess_cols = oracles .hessianstructure ()
218+ nnz_hess = len (hess_rows )
219+ else :
220+ hess_rows = None
221+ hess_cols = None
222+ nnz_hess = 0
223+
224+ # Load NLP problem data
225+ model .loadNlData (n , # Number of variables
226+ m , # Number of constraints
227+ copt .COPT .MINIMIZE , # Objective sense
228+ copt .COPT .DENSETYPE_ROWMAJOR , None , # Dense objective gradient
229+ nnz_jac , jac_rows , jac_cols , # Sparse jacobian
230+ nnz_hess , hess_rows , hess_cols , # Sparse hessian
231+ lb , ub , # Variable bounds
232+ cl , cu , # Constraint bounds
233+ x0 , # Starting point
234+ cbtype , cbfunc # Callback function
235+ )
236+
237+ # Set parameters
238+ for key , value in solver_opts .items ():
239+ model .setParam (key , value )
240+
241+ # Solve problem
242+ model .solve ()
243+
244+ # Get solution
245+ nlp_status = model .status
246+ nlp_hassol = model .haslpsol
247+
248+ if nlp_hassol :
249+ objval = model .objval
250+ x_sol = model .getValues ()
251+ lambda_sol = model .getDuals ()
252+ else :
253+ objval = + np .inf
254+ x_sol = [0.0 ] * n
255+ lambda_sol = [0.0 ] * m
256+
257+ num_iters = model .barrieriter
258+ solve_time_real = model .solvingtime
259+
260+ # Return results in dictionary format expected by invert()
261+ solution = {
262+ 'status' : nlp_status ,
263+ 'obj_val' : objval ,
264+ 'x' : np .array (x_sol ),
265+ 'lambda' : np .array (lambda_sol ),
266+ 'num_iters' : num_iters ,
267+ 'solve_time_real' : solve_time_real
268+ }
269+
270+ return solution
110271
111272 def cite (self , data ):
112273 """Returns bibtex citation for the solver.
0 commit comments