1 // Copyright (c) 2018 ERGO-Code. See license.txt for license. 2 3 #ifndef IPX_MODEL_H_ 4 #define IPX_MODEL_H_ 5 6 #include <vector> 7 #include "control.h" 8 #include "sparse_matrix.h" 9 10 namespace ipx { 11 12 // Model provides the interface between an LP model given by the user, 13 // 14 // minimize obj'x (1) 15 // subject to A*x {=,<,>} rhs, lbuser <= x <= ubuser, 16 // 17 // and the computational form used by the solver, 18 // 19 // minimize c'x 20 // subject to AI*x = b, (dual: y) 21 // x-xl = lb, xl >= 0, (dual: zl >= 0) 22 // x+xu = ub, xu >= 0. (dual: zu >= 0) 23 // 24 // The matrix AI has m >= 0 rows and n+m columns, where n > 0 is the number of 25 // "structural" variables. The last m columns of AI form the identity matrix. 26 // The last m components of c do not need to be zero (can happen when the model 27 // was dualized in preprocessing). Entries of -lb and ub can be infinity. 28 // 29 // The user model is translated into computational form in two steps: 30 // (a) scaling, which consists of 31 // - applying an automatic scaling algorithm to A (optional), and 32 // - "flipping" variables for which lbuser[j] is infinite but ubuser[j] is 33 // finite by multiplying the column of A by -1. 34 // (b) dualization if appropriate 35 // 36 // A Model object cannot be modified other than discarding the data and loading 37 // a new user model. 38 39 class Model { 40 public: 41 // Constructs an empty model. 42 Model() = default; 43 44 // Initializes a Model object from the form (1). 45 // @num_constr: number of rows of A 46 // @num_var: number of columns of A 47 // @Ap, @Ai, @Ax: matrix A in CSC format, 0-based indexing 48 // @rhs: array of size num_constr 49 // @constr_type: array of size num_constr with entries '=', '<' or '>' 50 // @obj: array of size num_var 51 // @lbuser: array of size num_var, entries can be -INFINITY 52 // @ubuser: array of size num_var, entries can be +INFINITY 53 // If the input is invalid an error code is returned and the Model object 54 // becomes empty. 55 // Returns: 56 // 0 57 // IPX_ERROR_argument_null 58 // IPX_ERROR_invalid_dimension 59 // IPX_ERROR_invalid_matrix 60 // IPX_ERROR_invalid_vector 61 Int Load(const Control& control, Int num_constr, Int num_var, 62 const Int* Ap, const Int* Ai, const double* Ax, 63 const double* rhs, const char* constr_type, const double* obj, 64 const double* lbuser, const double* ubuser); 65 66 // Writes statistics of input data and preprocessing to @info. 67 void GetInfo(Info* info) const; 68 69 // Returns true if the model is empty. empty()70 bool empty() const { return cols() == 0; } 71 72 // Deallocates all memory; the object becomes empty. 73 void clear(); 74 75 // Returns the number of rows of AI. rows()76 Int rows() const { return num_rows_; } 77 78 // Returns the number of structural columns of AI (i.e. without the 79 // rightmost identity matrix). cols()80 Int cols() const { return num_cols_; } 81 82 // Returns the number of columns classified as dense. num_dense_cols()83 Int num_dense_cols() const { return num_dense_cols_; } 84 85 // Returns true if column j is classified as dense (0 <= j < n+m). IsDenseColumn(Int j)86 bool IsDenseColumn(Int j) const { 87 return AI_.entries(j) >= nz_dense_; 88 } 89 90 // Returns true if the user model was dualized in preprocessing. dualized()91 bool dualized() const { return dualized_; } 92 93 // Returns a reference to the matrix AI in CSC and CSR format. AI()94 const SparseMatrix& AI() const { return AI_; } AIt()95 const SparseMatrix& AIt() const { return AIt_; } 96 97 // Returns a reference to a model vector. b()98 const Vector& b() const { return b_; } c()99 const Vector& c() const { return c_; } lb()100 const Vector& lb() const { return lb_; } ub()101 const Vector& ub() const { return ub_; } 102 103 // Returns an entry of a model vector. b(Int i)104 double b(Int i) const { return b_[i]; } c(Int j)105 double c(Int j) const { return c_[j]; } lb(Int j)106 double lb(Int j) const { return lb_[j]; } ub(Int j)107 double ub(Int j) const { return ub_[j]; } 108 109 // Returns the infinity norm of [b; lb; ub], ignoring infinite entries. norm_bounds()110 double norm_bounds() const { return norm_bounds_; } 111 112 // Returns the infinity norm of c. norm_c()113 double norm_c() const { return norm_c_; } 114 115 // Transforms point from user model to solver model. Each of the pointer 116 // arguments can be NULL, in which case its components are assumed 0.0. 117 void PresolveStartingPoint(const double* x_user, const double* slack_user, 118 const double* y_user, const double* z_user, 119 Vector& x_solver, Vector& y_solver, 120 Vector& z_solver) const; 121 122 // Performs the inverse operations to PostsolveInteriorSolution(). 123 // The user vectors must all be given and must satisfy the sign conditions 124 // given in the reference documentation. Otherwise an error code will be 125 // returned. At the moment PresolveIPMStartingPoint() is not implemented 126 // for the case that the model was dualized in preprocessing. 127 // Returns: 128 // 0 129 // IPX_ERROR_argument_null 130 // IPX_ERROR_invalid_vector if a sign condition is violated 131 // IPX_ERROR_not_implemented if the model was dualized in preprocessing. 132 Int PresolveIPMStartingPoint(const double* x_user, 133 const double* xl_user, 134 const double* xu_user, 135 const double* slack_user, 136 const double* y_user, 137 const double* zl_user, 138 const double* zu_user, 139 Vector& x_solver, 140 Vector& xl_solver, 141 Vector& xu_solver, 142 Vector& y_solver, 143 Vector& zl_solver, 144 Vector& zu_solver) const; 145 146 // Given an IPM iterate, recovers the solution to the user model (see the 147 // reference documentation). Each of the pointer arguments can be NULL, in 148 // which case the quantity is not returned. The sign conditions on the dual 149 // variables and those on the primal slack variables are staisfied if 150 // xl_solver, xu_solver, zl_solver and zu_solver are nonnegative. 151 void PostsolveInteriorSolution(const Vector& x_solver, 152 const Vector& xl_solver, 153 const Vector& xu_solver, 154 const Vector& y_solver, 155 const Vector& zl_solver, 156 const Vector& zu_solver, 157 double* x_user, 158 double* xl_user, double* xu_user, 159 double* slack_user, 160 double* y_user, 161 double* zl_user, double* zu_user) const; 162 163 // Evaluates the solution to the user model obtained from postsolving the 164 // IPM iterate. The following info members are set: 165 // abs_presidual, abs_dresidual, rel_presidual, rel_dresidual, 166 // pobjval, dobjval, rel_objgap, complementarity, normx, normy, normx. 167 void EvaluateInteriorSolution(const Vector& x_solver, 168 const Vector& xl_solver, 169 const Vector& xu_solver, 170 const Vector& y_solver, 171 const Vector& zl_solver, 172 const Vector& zu_solver, 173 Info* info) const; 174 175 // Given a basic solution to the solver model, recovers the basic solution 176 // to the user model. Each of the pointer arguments can be NULL. 177 void PostsolveBasicSolution(const Vector& x_solver, 178 const Vector& y_solver, 179 const Vector& z_solver, 180 const std::vector<Int>& basic_status_solver, 181 double* x_user, double* slack_user, 182 double* y_user, double* z_user) const; 183 184 // Evaluates the solution to the user model obtained from postsolving the 185 // basic solution from the solver. The following info members are set: 186 // primal_infeas, dual_infeas, objval 187 void EvaluateBasicSolution(const Vector& x_solver, 188 const Vector& y_solver, 189 const Vector& z_solver, 190 const std::vector<Int>& basic_status_solver, 191 Info* info) const; 192 193 // Given a basic status for each variable in the solver model, recovers the 194 // basic statuses for constraints and variables in the user model. Each 195 // of the pointer arguments can be NULL. 196 void PostsolveBasis(const std::vector<Int>& basic_status_solver, 197 Int* cbasis, Int* vbasis) const; 198 199 private: 200 // Checks that the input is valid, and if so copies into the members below 201 // (see "User model after scaling"). If the input is invalid, an error code 202 // is returned and the object remains unchanged. 203 // Returns: 204 // 0 205 // IPX_ERROR_argument_null 206 // IPX_ERROR_invalid_dimension 207 // IPX_ERROR_invalid_matrix 208 // IPX_ERROR_invalid_vector 209 Int CopyInput(Int num_constr, Int num_var, const Int* Ap, const Int* Ai, 210 const double* Ax, const double* rhs, const char* constr_type, 211 const double* obj, const double* lbuser, 212 const double* ubuser); 213 214 // Scales A_, scaled_obj_, scaled_rhs_, scaled_lbuser_ and scaled_ubuser_ 215 // according to parameter control.scale(). The scaling factors are stored in 216 // colscale_ and rowscale_. If all factors are 1.0 (either because scaling 217 // was turned off or because the algorithm did nothing), rowscale_ and 218 // colscale_ have size 0. 219 // In any case, variables for which lbuser is infinite but ubbuser is finite 220 // are "flipped" and their indices are kept in flipped_vars_. 221 void ScaleModel(const Control& control); 222 223 // Builds computational form without dualization. In Julia notation: 224 // num_rows = nc 225 // num_cols = nv 226 // AI = [A eye(nc)] 227 // b = rhs 228 // c = [obj ; zeros(nc) ] 229 // lb = [lbuser ; constr_type_ .== '>' ? -Inf : 0] 230 // ub = [ubuser ; constr_type_ .== '<' ? +Inf : 0] 231 // dualized = false 232 // Here nc = num_constr and nv = num_var. The data must have been loaded 233 // into the class member below ("User model after scaling") before calling 234 // this method. 235 void LoadPrimal(); 236 237 // Builds computational form with dualization. In Julia notation: 238 // num_rows = nv 239 // num_cols = nc + nb 240 // AI = [A' -eye(nv)[:,jboxed] eye(nv)] 241 // b = obj 242 // c = [-rhs ; ubuser[jb] ; -lbuser ] 243 // lb = [constr_type .== '>' ? 0 : -Inf; zeros(nb) ; zeros(nv) ] 244 // ub = [constr_type .== '<' ? 0 : +Inf; Inf*ones(nb); Inf*ones(nv)] 245 // dualized = true 246 // Here nc = num_constr, nv = num_var, nb is the number of boxed variables 247 // and jboxed are their indices. Every variable with a finite upper bound 248 // must have a finite lower bound (this is ensured after scaling). If a 249 // variable j of the input LP is a free variable, then the j-th slack 250 // variable of the model gets a zero upper bound (i.e. it is fixed at zero) 251 // and its objective coefficient is set to zero. 252 void LoadDual(); 253 254 // Recursively equilibrates A_ in infinity norm using the algorithm from 255 // [1]. The scaling factors are truncated to powers of 2. Terminates when 256 // the entries of A_ are within the range [0.5,8). 257 // [1] P. A. Knight, D. Ruiz, B. Ucar, "A symmetry preserving algorithm for 258 // matrix scaling", SIAM J. Matrix Anal., 35(3), 2014. 259 void EquilibrateMatrix(); 260 261 // Initializes num_dense_cols_ and nz_dense_. We classify the maximum # 262 // columns as "dense" which have more than 40 nonzeros and more than 10 263 // times the # nonzeros than any column that is not "dense". If this yields 264 // more than 1000 dense columns, then no columns are classified as dense. 265 void FindDenseColumns(); 266 267 // Prints the coefficient ranges of input data to control.Log(). Must be 268 // called after CopyInput() and before ScaleModel(). 269 void PrintCoefficientRange(const Control& control) const; 270 271 // Prints preprocessing operations to control.Log(). 272 void PrintPreprocessingLog(const Control& control) const; 273 274 // Applies the operations from ScaleModel() to a primal-dual point. 275 void ScalePoint(Vector& x, Vector& slack, Vector& y, Vector& z) const; 276 void ScalePoint(Vector& x, Vector& xl, Vector& xu, Vector& slack, 277 Vector& y, Vector& zl, Vector& zu) const; 278 279 // ScaleBack*() do the reverse operation of ScaleModel(). 280 void ScaleBackInteriorSolution(Vector& x, Vector& xl, Vector& xu, 281 Vector& slack, Vector& y, Vector& zl, 282 Vector& zu) const; 283 void ScaleBackResiduals(Vector& rb, Vector& rc, Vector& rl, 284 Vector& ru) const; 285 void ScaleBackBasicSolution(Vector& x, Vector& slack, Vector& y, 286 Vector& z) const; 287 void ScaleBackBasis(std::vector<Int>& cbasis, 288 std::vector<Int>& vbasis) const; 289 290 // Applies the operations of LoadPrimal() or LoadDual() to a primal-dual 291 // point. 292 void DualizeBasicSolution(const Vector& x_user, const Vector& slack_user, 293 const Vector& y_user, const Vector& z_user, 294 Vector& x_solver, Vector& y_solver, 295 Vector& z_solver) const; 296 297 // Applies the operations of LoadPrimal() or LoadDual() to a primal-dual 298 // point. Currently only implemented for dualized_ == false. Otherwise an 299 // assertion will fail. 300 void DualizeIPMStartingPoint(const Vector& x_user, 301 const Vector& xl_user, 302 const Vector& xu_user, 303 const Vector& slack_user, 304 const Vector& y_user, 305 const Vector& zl_user, 306 const Vector& zu_user, 307 Vector& x_solver, 308 Vector& xl_solver, 309 Vector& xu_solver, 310 Vector& y_solver, 311 Vector& zl_solver, 312 Vector& zu_solver) const; 313 314 // DualizeBack*() do the reverse operations of LoadPrimal() or LoadDual(). 315 // Given the solution from the solver, they recover the solution to the 316 // scaled user model. 317 void DualizeBackInteriorSolution(const Vector& x_solver, 318 const Vector& xl_solver, 319 const Vector& xu_solver, 320 const Vector& y_solver, 321 const Vector& zl_solver, 322 const Vector& zu_solver, 323 Vector& x_user, 324 Vector& xl_user, 325 Vector& xu_user, 326 Vector& slack_user, 327 Vector& y_user, 328 Vector& zl_user, 329 Vector& zu_user) const; 330 void DualizeBackBasicSolution(const Vector& x_solver, 331 const Vector& y_solver, 332 const Vector& z_solver, 333 Vector& x_user, 334 Vector& slack_user, 335 Vector& y_user, 336 Vector& z_user) const; 337 void DualizeBackBasis(const std::vector<Int>& basic_status_solver, 338 std::vector<Int>& cbasis_user, 339 std::vector<Int>& vbasis_user) const; 340 341 void CorrectScaledBasicSolution(Vector& x, Vector& slack, Vector& y, 342 Vector& z, 343 const std::vector<Int> cbasis, 344 const std::vector<Int> vbasis) const; 345 346 // Performs lhs += alpha*A*rhs or lhs += alpha*A'rhs, where A is the user 347 // matrix after scaling. This matrix is not stored explicitly, but is used 348 // implicitly through AI. 349 // @trans: 't' or 'T' for multiplication with A'. 350 void MultiplyWithScaledMatrix(const Vector& rhs, double alpha, Vector& lhs, 351 char trans) const; 352 353 // Computational form model. 354 bool dualized_{false}; // model was dualized in preprocessing? 355 Int num_rows_{0}; // # rows of AI 356 Int num_cols_{0}; // # structural columns of AI 357 Int num_dense_cols_{0}; // # columns classified as dense 358 Int nz_dense_{0}; // minimum # nonzeros in a dense column 359 SparseMatrix AI_; // matrix AI columnwise 360 SparseMatrix AIt_; // matrix AI rowwise 361 Vector b_; 362 Vector c_; 363 Vector lb_; 364 Vector ub_; 365 double norm_bounds_{0.0}; // infinity norm of [b;lb;ub] 366 double norm_c_{0.0}; // infinity norm of c 367 368 // User model after scaling. The data members are first initialized by 369 // CopyInput() and the vectors and matrix are then modified by ScaleModel(). 370 Int num_constr_{0}; // # constraints 371 Int num_eqconstr_{0}; // # equality constraints 372 Int num_var_{0}; // # variables 373 Int num_free_var_{0}; // # free variables 374 Int num_entries_{0}; // # entries in input matrix 375 std::vector<Int> boxed_vars_; // indices of boxed variables 376 std::vector<char> constr_type_; 377 double norm_obj_{0.0}; // Infnorm(obj) as given by user 378 double norm_rhs_{0.0}; // Infnorm(rhs,lb,ub) as given by user 379 Vector scaled_obj_; 380 Vector scaled_rhs_; 381 Vector scaled_lbuser_; 382 Vector scaled_ubuser_; 383 SparseMatrix A_; // is cleared after preprocessing 384 385 // Data from ScaleModel() that is required by ScaleBack*(). 386 std::vector<Int> flipped_vars_; 387 Vector colscale_; 388 Vector rowscale_; 389 }; 390 391 // Returns the maximum violation of lb <= x <= ub. 392 double PrimalInfeasibility(const Model& model, const Vector& x); 393 394 // Returns the maximum violation of the dual feasibility condition 395 // z[j] <= 0 if x[j] > lb[j], 396 // z[j] >= 0 if x[j] < ub[j]. 397 // Note that dual feasibility implies complementarity, i.e. 398 // x[j] == lb[j] || x[j] == ub[j] || z[j] == 0. 399 double DualInfeasibility(const Model& model, const Vector& x, const Vector& z); 400 401 // Returns the maximum violation of Ax=b. 402 double PrimalResidual(const Model& model, const Vector& x); 403 404 // Returns the maximum violation of A'y+z=c. 405 double DualResidual(const Model& model, const Vector& y, const Vector& z); 406 407 } // namespace ipx 408 409 #endif // IPX_MODEL_H_ 410