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