// (C) Copyright 2015 by Autodesk, Inc. //============================================================================= // // CLASS DOCloudSolver - IMPLEMENTATION // //============================================================================= //== INCLUDES ================================================================= //== COMPILE-TIME PACKAGE REQUIREMENTS ======================================== #include "DOCloudSolver.hh" //============================================================================= #if COMISO_DOCLOUD_AVAILABLE #include "DOCloudCache.hh" #include "DOCloudJob.hh" #include "cURLpp.hh" #include "CoMISo/Utils/CoMISoError.hh" #include #include #include #include #include DEB_module("DOCloudSolver") //== NAMESPACES =============================================================== namespace COMISO { //== IMPLEMENTATION ========================================================== namespace DOcloud { namespace { #define P(X) ((X).data()) #define XVAR(IDX) "x" << IDX class WriteExpression { // lp format allows a max line length = 560. // For simplicity we put end line when the number of written characters on the // same line exceeds LINE_TRESHOLD_LEN. enum { LINE_TRESHOLD_LEN = 100 }; public: WriteExpression(std::ostringstream& _out_str) : out_str_stream(_out_str) { start(); } void start() { f_size_ = out_str_stream.tellp(); at_start_ = true; } // Writes a monomial. void add_monomial(const double _coeff, const size_t _i_var) { if (_coeff == 0) return; add_monomial_internal(_coeff, _i_var); wrap_long_line(); } // Writes a binomial. void add_binomial(const double _coeff, const size_t _i_var, const size_t _j_var) { if (_coeff == 0) return; add_monomial_internal(_coeff, _i_var); if (_j_var == _i_var) out_str_stream << "^2"; else out_str_stream << " * " << XVAR(_j_var); wrap_long_line(); } private: void wrap_long_line() { const auto new_f_size = out_str_stream.tellp(); if (new_f_size - f_size_ > LINE_TRESHOLD_LEN) { out_str_stream << std::endl; f_size_ = new_f_size; } } void add_monomial_internal(const double _coeff, const size_t _i_var) { if (_coeff == 1) { if (!at_start_) out_str_stream << " + "; } else if (_coeff == -1) out_str_stream << " - "; else { if (!at_start_) { if (_coeff > 0) out_str_stream << " + "; else out_str_stream << ' '; } out_str_stream << _coeff << ' '; } out_str_stream << XVAR(_i_var); at_start_ = false; } private: std::ostringstream& out_str_stream; std::fstream::pos_type f_size_; bool at_start_; }; // Create a lp file for the given constraints and object function. // Here is the lp format specifications: // http://www-01.ibm.com/support/knowledgecenter/SSSA5P_12.6.1/ilog.odms.cplex.help/CPLEX/FileFormats/topics/LP.html std::string create_lp_string( NProblemInterface* _problem, const std::vector& _constraints, const std::vector& _discrete_constraints, const std::vector& _x ) { const int n_cols = _problem->n_unknowns(); // Unknowns # std::ostringstream lp_str_stream; // Set the ofstream options. lp_str_stream << std::setprecision(std::numeric_limits::digits10 + 2); lp_str_stream << "\\Problem name: " << std::endl << std::endl; lp_str_stream << "Minimize" << std::endl; // Writes objective function. lp_str_stream << "obj: "; WriteExpression wrte_expr(lp_str_stream); // 1. Linear part. std::vector objective(n_cols); _problem->eval_gradient(P(_x), P(objective)); for (size_t i = 0; i < objective.size(); ++i) wrte_expr.add_monomial(objective[i], i); // 2. Quadratic part (if requested). if (!_problem->constant_gradient()) { NProblemInterface::SMatrixNP H; _problem->eval_hessian(P(_x), H); lp_str_stream << " + [ "; for (int i = 0; i < H.outerSize(); ++i) { for (NProblemInterface::SMatrixNP::InnerIterator it(H, i); it; ++it) wrte_expr.add_binomial(it.value(), it.row(), it.col()); } lp_str_stream << " ] / 2"; } // Writes constraints. lp_str_stream << std::endl << "Subject To" << std::endl; for (const auto& cstr : _constraints) { NConstraintInterface::SVectorNC gc; cstr->eval_gradient(P(_x), gc); wrte_expr.start(); for (NConstraintInterface::SVectorNC::InnerIterator v_it(gc); v_it; ++v_it) { auto coeff = v_it.value(); wrte_expr.add_monomial(coeff, v_it.index()); } switch (cstr->constraint_type()) { case NConstraintInterface::NC_EQUAL: lp_str_stream << " = "; break; case NConstraintInterface::NC_GREATER_EQUAL: lp_str_stream << " >= "; break; case NConstraintInterface::NC_LESS_EQUAL: lp_str_stream << " <= "; break; } lp_str_stream << -cstr->eval_constraint(P(_x)) << std::endl; } // Writes the variables. lp_str_stream << "Bounds" << std::endl; for (size_t i = 0; i < n_cols; ++i) lp_str_stream << XVAR(i) << " Free" << std::endl; // Integer and binary variables. std::vector int_var, bin_var; for (const auto& dc : _discrete_constraints) { if (dc.second == Integer) int_var.push_back(dc.first); else if (dc.second == Binary) bin_var.push_back(dc.first); } auto write_var_set = [&lp_str_stream](const std::vector& _vars, const char* _type) { if (_vars.empty()) return; // Writes integer variables. lp_str_stream << _type << std::endl; auto var_it = _vars.begin(); lp_str_stream << XVAR(*var_it); size_t n_wrt_var = 1; while (++var_it != _vars.end()) { if (n_wrt_var++ % 16) // 16 variables per line. Lines length must be < 560. lp_str_stream << ' '; else lp_str_stream << std::endl; lp_str_stream << XVAR(*var_it); } lp_str_stream << std::endl; }; // Writes integer variables. write_var_set(int_var, "Integers"); // Writes Binary variables. write_var_set(bin_var, "Binary"); lp_str_stream << "End"; return std::string(lp_str_stream.str()); } #undef XVAR } // namespace } // namespace DOcloud void DOCloudSolver::solve( NProblemInterface* _problem, const std::vector& _constraints, const std::vector& _discrete_constraints, const double _time_limit ) { DEB_enter_func; DEB_warning_if(!_problem->constant_hessian(), 1, "DOCloudSolver received a problem with non-constant hessian!"); DEB_warning_if(!_problem->constant_gradient(), 1, "DOCloudSolver received a problem with non-constant gradient!"); std::vector x(_problem->n_unknowns(), 0.0); // solution const std::string mip_lp = DOcloud::create_lp_string(_problem, _constraints, _discrete_constraints, x); double obj_val; DOcloud::Cache cache(mip_lp); if (cache.restore_result(x, obj_val)) DEB_line(3, "MIP cached.") else { DEB_line(1, "MIP not cached, computing optimization."); const std::string lp_hash = cache.hash() + ".lp"; DOcloud::Job job(lp_hash, mip_lp); job.setup(); job.wait(); obj_val = job.solution(x); cache.store_result(x, obj_val); } COMISO_THROW_if(x.empty(), MIPS_NO_SOLUTION); DEB_only( // The lp problem ignores the constant term in the objective function. const std::vector x_zero(_problem->n_unknowns(), 0.0); const auto zero_val = _problem->eval_f(P(x_zero)); const auto test_obj_val = _problem->eval_f(P(x)); DEB_error_if(fabs(obj_val + zero_val - test_obj_val) > (1e-8 + zero_val * 0.01), "DOCloudSolver solved a wrong problem."); ) _problem->store_result(P(x)); } #undef P //============================================================================= } // namespace COMISO //============================================================================= #endif // COMISO_DOCLOUD_AVAILABLE //=============================================================================