/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /* */ /* This file is part of the program and library */ /* PaPILO --- Parallel Presolve for Integer and Linear Optimization */ /* */ /* Copyright (C) 2020-2022 Konrad-Zuse-Zentrum */ /* fuer Informationstechnik Berlin */ /* */ /* This program is free software: you can redistribute it and/or modify */ /* it under the terms of the GNU Lesser General Public License as published */ /* by the Free Software Foundation, either version 3 of the License, or */ /* (at your option) any later version. */ /* */ /* This program is distributed in the hope that it will be useful, */ /* but WITHOUT ANY WARRANTY; without even the implied warranty of */ /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ /* GNU Lesser General Public License for more details. */ /* */ /* You should have received a copy of the GNU Lesser General Public License */ /* along with this program. If not, see . */ /* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #ifndef _PAPILO_INTERFACES_SCIP_INTERFACE_HPP_ #define _PAPILO_INTERFACES_SCIP_INTERFACE_HPP_ #define UNUSED(expr) do { (void)(expr); } while (0) #include "papilo/misc/Vec.hpp" #include #include #include "papilo/core/Problem.hpp" #include "papilo/interfaces/SolverInterface.hpp" #include "scip/cons_linear.h" #include "scip/scip.h" #include "scip/struct_paramset.h" namespace papilo { template class ScipInterface : public SolverInterface { private: SCIP* scip; Vec vars; SCIP_RETCODE doSetUp( const Problem& problem, const Vec& origRowMap, const Vec& origColMap ) { int ncols = problem.getNCols(); int nrows = problem.getNRows(); const Vec& varNames = problem.getVariableNames(); const Vec& consNames = problem.getConstraintNames(); const VariableDomains& domains = problem.getVariableDomains(); const Objective& obj = problem.getObjective(); const auto& consMatrix = problem.getConstraintMatrix(); const auto& lhs_values = consMatrix.getLeftHandSides(); const auto& rhs_values = consMatrix.getRightHandSides(); const auto& rflags = problem.getRowFlags(); SCIP_CALL( SCIPcreateProbBasic( scip, problem.getName().c_str() ) ); vars.resize( problem.getNCols() ); for( int i = 0; i < ncols; ++i ) { SCIP_VAR* var; assert( !domains.flags[i].test( ColFlag::kInactive ) ); SCIP_Real lb = domains.flags[i].test( ColFlag::kLbInf ) ? -SCIPinfinity( scip ) : SCIP_Real( domains.lower_bounds[i] ); SCIP_Real ub = domains.flags[i].test( ColFlag::kUbInf ) ? SCIPinfinity( scip ) : SCIP_Real( domains.upper_bounds[i] ); SCIP_VARTYPE type; if( domains.flags[i].test( ColFlag::kIntegral ) ) { if( lb == REAL{ 0 } && ub == REAL{ 1 } ) type = SCIP_VARTYPE_BINARY; else type = SCIP_VARTYPE_INTEGER; } else if( domains.flags[i].test( ColFlag::kImplInt ) ) type = SCIP_VARTYPE_IMPLINT; else type = SCIP_VARTYPE_CONTINUOUS; SCIP_CALL( SCIPcreateVarBasic( scip, &var, varNames[origColMap[i]].c_str(), lb, ub, SCIP_Real( obj.coefficients[i] ), type ) ); SCIP_CALL( SCIPaddVar( scip, var ) ); vars[i] = var; SCIP_CALL( SCIPreleaseVar( scip, &var ) ); } Vec consvars; Vec consvals; consvars.resize( problem.getNCols() ); consvals.resize( problem.getNCols() ); for( int i = 0; i < nrows; ++i ) { SCIP_CONS* cons; auto rowvec = consMatrix.getRowCoefficients( i ); const REAL* vals = rowvec.getValues(); const int* inds = rowvec.getIndices(); SCIP_Real lhs = rflags[i].test( RowFlag::kLhsInf ) ? -SCIPinfinity( scip ) : SCIP_Real( lhs_values[i] ); SCIP_Real rhs = rflags[i].test( RowFlag::kRhsInf ) ? SCIPinfinity( scip ) : SCIP_Real( rhs_values[i] ); for( int k = 0; k != rowvec.getLength(); ++k ) { consvars[k] = vars[inds[k]]; consvals[k] = SCIP_Real( vals[k] ); } SCIP_CALL( SCIPcreateConsBasicLinear( scip, &cons, consNames[origRowMap[i]].c_str(), rowvec.getLength(), consvars.data(), consvals.data(), lhs, rhs ) ); SCIP_CALL( SCIPaddCons( scip, cons ) ); SCIP_CALL( SCIPreleaseCons( scip, &cons ) ); } if( obj.offset != REAL{ 0 } ) SCIP_CALL( SCIPaddOrigObjoffset( scip, SCIP_Real( obj.offset ) ) ); return SCIP_OKAY; } SCIP_RETCODE doSetUp( const Problem& problem, const Vec& origRowMap, const Vec& origColMap, const Components& components, const ComponentInfo& component ) { int ncols = components.getComponentsNumCols( component.componentid ); int nrows = components.getComponentsNumRows( component.componentid ); const int* colset = components.getComponentsCols( component.componentid ); const int* rowset = components.getComponentsRows( component.componentid ); const Vec& varNames = problem.getVariableNames(); const Vec& consNames = problem.getConstraintNames(); const VariableDomains& domains = problem.getVariableDomains(); const Objective& obj = problem.getObjective(); const auto& consMatrix = problem.getConstraintMatrix(); const auto& lhs_values = consMatrix.getLeftHandSides(); const auto& rhs_values = consMatrix.getRightHandSides(); const auto& rflags = problem.getRowFlags(); SCIP_CALL( SCIPcreateProbBasic( scip, problem.getName().c_str() ) ); vars.resize( ncols ); for( int i = 0; i < ncols; ++i ) { int col = colset[i]; SCIP_VAR* var; assert( !domains.flags[col].test( ColFlag::kInactive ) ); SCIP_Real lb = domains.flags[col].test( ColFlag::kLbInf ) ? -SCIPinfinity( scip ) : SCIP_Real( domains.lower_bounds[col] ); SCIP_Real ub = domains.flags[col].test( ColFlag::kUbInf ) ? SCIPinfinity( scip ) : SCIP_Real( domains.upper_bounds[col] ); SCIP_VARTYPE type; if( domains.flags[col].test( ColFlag::kIntegral ) ) { if( lb == REAL{ 0 } && ub == REAL{ 1 } ) type = SCIP_VARTYPE_BINARY; else type = SCIP_VARTYPE_INTEGER; } else type = SCIP_VARTYPE_CONTINUOUS; SCIP_CALL( SCIPcreateVarBasic( scip, &var, varNames[origColMap[col]].c_str(), lb, ub, SCIP_Real( obj.coefficients[col] ), type ) ); SCIP_CALL( SCIPaddVar( scip, var ) ); vars[i] = var; SCIP_CALL( SCIPreleaseVar( scip, &var ) ); } Vec consvars; Vec consvals; consvars.resize( ncols ); consvals.resize( ncols ); for( int i = 0; i < nrows; ++i ) { int row = rowset[i]; SCIP_CONS* cons; auto rowvec = consMatrix.getRowCoefficients( row ); const REAL* vals = rowvec.getValues(); const int* inds = rowvec.getIndices(); SCIP_Real lhs = rflags[row].test( RowFlag::kLhsInf ) ? -SCIPinfinity( scip ) : SCIP_Real( lhs_values[row] ); SCIP_Real rhs = rflags[row].test( RowFlag::kRhsInf ) ? SCIPinfinity( scip ) : SCIP_Real( rhs_values[row] ); for( int k = 0; k != rowvec.getLength(); ++k ) { consvars[k] = vars[components.getColComponentIdx( inds[k] )]; consvals[k] = SCIP_Real( vals[k] ); } SCIP_CALL( SCIPcreateConsBasicLinear( scip, &cons, consNames[origRowMap[row]].c_str(), rowvec.getLength(), consvars.data(), consvals.data(), lhs, rhs ) ); SCIP_CALL( SCIPaddCons( scip, cons ) ); SCIP_CALL( SCIPreleaseCons( scip, &cons ) ); } if( obj.offset != REAL{ 0 } ) SCIP_CALL( SCIPaddOrigObjoffset( scip, SCIP_Real( obj.offset ) ) ); return SCIP_OKAY; } public: ScipInterface() : scip( nullptr ) { if( SCIPcreate( &scip ) != SCIP_OKAY ) throw std::runtime_error( "could not create SCIP" ); } SCIP* getSCIP() { return scip; } void readSettings( const String& file ) override { if( SCIPreadParams( scip, file.c_str() ) != SCIP_OKAY ) this->status = SolverStatus::kError; } void setUp( const Problem& prob, const Vec& row_maps, const Vec& col_maps, const Components& components, const ComponentInfo& component ) override { if( doSetUp( prob, row_maps, col_maps, components, component ) != SCIP_OKAY ) this->status = SolverStatus::kError; } void setNodeLimit( int num ) override { if( SCIPsetLongintParam( scip, "limits/nodes", num ) != SCIP_OKAY ) this->status = SolverStatus::kError; } void setGapLimit( const REAL& gaplim ) override { if( SCIPsetRealParam( scip, "limits/gap", SCIP_Real( gaplim ) ) != SCIP_OKAY ) this->status = SolverStatus::kError; } void setSoftTimeLimit( double tlim ) override { if( SCIPsetIntParam( scip, "timing/clocktype", 2 ) != SCIP_OKAY ) this->status = SolverStatus::kError; if( SCIPsetRealParam( scip, "limits/softtime", SCIP_Real( tlim ) ) != SCIP_OKAY ) this->status = SolverStatus::kError; } void setTimeLimit( double tlim ) override { if( SCIPsetIntParam( scip, "timing/clocktype", 2 ) != SCIP_OKAY ) this->status = SolverStatus::kError; if( SCIPsetRealParam( scip, "limits/time", SCIP_Real( tlim ) ) != SCIP_OKAY ) this->status = SolverStatus::kError; } void setVerbosity( VerbosityLevel verbosity ) override { switch( verbosity ) { case VerbosityLevel::kQuiet: SCIP_CALL_ABORT( SCIPsetIntParam( scip, "display/verblevel", 0 ) ); break; case VerbosityLevel::kError: SCIP_CALL_ABORT( SCIPsetIntParam( scip, "display/verblevel", 1 ) ); break; case VerbosityLevel::kWarning: SCIP_CALL_ABORT( SCIPsetIntParam( scip, "display/verblevel", 2 ) ); break; case VerbosityLevel::kInfo: SCIP_CALL_ABORT( SCIPsetIntParam( scip, "display/verblevel", 4 ) ); break; case VerbosityLevel::kDetailed: SCIP_CALL_ABORT( SCIPsetIntParam( scip, "display/verblevel", 5 ) ); } } void setUp( const Problem& prob, const Vec& row_maps, const Vec& col_maps ) override { if( doSetUp( prob, row_maps, col_maps ) != SCIP_OKAY ) this->status = SolverStatus::kError; } void solve() override { assert( this->status != SolverStatus::kError ); if( SCIPsolve( scip ) != SCIP_OKAY ) { this->status = SolverStatus::kError; return; } switch( SCIPgetStatus( scip ) ) { case SCIP_STATUS_UNKNOWN: this->status = SolverStatus::kError; return; case SCIP_STATUS_USERINTERRUPT: case SCIP_STATUS_NODELIMIT: case SCIP_STATUS_TOTALNODELIMIT: case SCIP_STATUS_STALLNODELIMIT: case SCIP_STATUS_TIMELIMIT: case SCIP_STATUS_MEMLIMIT: case SCIP_STATUS_GAPLIMIT: case SCIP_STATUS_SOLLIMIT: case SCIP_STATUS_BESTSOLLIMIT: case SCIP_STATUS_RESTARTLIMIT: #if SCIP_VERSION_MAJOR >= 6 case SCIP_STATUS_TERMINATE: #endif this->status = SolverStatus::kInterrupted; break; case SCIP_STATUS_INFORUNBD: this->status = SolverStatus::kUnbndOrInfeas; return; case SCIP_STATUS_INFEASIBLE: this->status = SolverStatus::kInfeasible; return; case SCIP_STATUS_UNBOUNDED: this->status = SolverStatus::kUnbounded; return; case SCIP_STATUS_OPTIMAL: this->status = SolverStatus::kOptimal; } } REAL getDualBound() override { return SCIPgetDualbound( scip ); } bool getSolution( Solution& solbuffer ) override { SCIP_SOL* sol = SCIPgetBestSol( scip ); if( solbuffer.type != SolutionType::kPrimal ) return false; solbuffer.primal.resize( vars.size() ); if( sol != nullptr ) { SCIP_SOL* finitesol; SCIP_Bool success; #ifndef NDEBUG SCIP_Bool feasible; SCIP_CALL_ABORT( SCIPcheckSolOrig( scip, sol, &feasible, TRUE, TRUE ) ); #endif SCIP_CALL_ABORT( SCIPcreateFiniteSolCopy( scip, &finitesol, sol, &success ) ); if( finitesol != nullptr ) { for( std::size_t i = 0; i != vars.size(); ++i ) solbuffer.primal[i] = REAL( SCIPgetSolVal( scip, finitesol, vars[i] ) ); SCIP_CALL_ABORT( SCIPfreeSol( scip, &finitesol ) ); } else { for( std::size_t i = 0; i != vars.size(); ++i ) solbuffer.primal[i] = REAL( SCIPgetSolVal( scip, sol, vars[i] ) ); } return true; } return false; } bool getSolution( const Components& components, int component, Solution& solbuffer ) override { SCIP_SOL* sol = SCIPgetBestSol( scip ); if( solbuffer.type != SolutionType::kPrimal ) return false; if( sol != nullptr ) { SCIP_SOL* finitesol; SCIP_Bool success; const int* colset = components.getComponentsCols( component ); assert( components.getComponentsNumCols( component ) == vars.size() ); SCIP_CALL_ABORT( SCIPcreateFiniteSolCopy( scip, &finitesol, sol, &success ) ); if( finitesol != nullptr ) { for( std::size_t i = 0; i != vars.size(); ++i ) solbuffer.primal[colset[i]] = REAL( SCIPgetSolVal( scip, finitesol, vars[i] ) ); SCIP_CALL_ABORT( SCIPfreeSol( scip, &finitesol ) ); } else { for( std::size_t i = 0; i != vars.size(); ++i ) solbuffer.primal[colset[i]] = REAL( SCIPgetSolVal( scip, sol, vars[i] ) ); } return true; } return false; } SolverType getType() override { return SolverType::MIP; } String getName() override { return "SCIP"; } void printDetails() override { SCIP_RETCODE retcode = SCIPprintStatistics( scip, stdout ); UNUSED(retcode); assert( retcode == SCIP_OKAY ); } bool is_dual_solution_available() override { return false; } void addParameters( ParameterSet& paramSet ) override { SCIP_PARAM** params = SCIPgetParams( scip ); int nparams = SCIPgetNParams( scip ); for( int i = 0; i != nparams; ++i ) { switch( params[i]->paramtype ) { case SCIP_PARAMTYPE_BOOL: { unsigned int* valptr; if( params[i]->data.boolparam.valueptr != nullptr ) valptr = params[i]->data.boolparam.valueptr; else valptr = ¶ms[i]->data.boolparam.curvalue; paramSet.addParameter( params[i]->name, params[i]->desc, *reinterpret_cast( valptr ) ); break; } case SCIP_PARAMTYPE_CHAR: { char* valptr; if( params[i]->data.charparam.valueptr != nullptr ) valptr = params[i]->data.charparam.valueptr; else valptr = ¶ms[i]->data.charparam.curvalue; char* last = params[i]->data.charparam.allowedvalues; if( last != nullptr ) { while( *last != '\0' ) ++last; paramSet.addParameter( params[i]->name, params[i]->desc, *valptr, Vec( params[i]->data.charparam.allowedvalues, last ) ); } break; } case SCIP_PARAMTYPE_INT: { int* valptr; if( params[i]->data.intparam.valueptr != nullptr ) valptr = params[i]->data.intparam.valueptr; else valptr = ¶ms[i]->data.intparam.curvalue; paramSet.addParameter( params[i]->name, params[i]->desc, *valptr, params[i]->data.intparam.minvalue, params[i]->data.intparam.maxvalue ); break; } case SCIP_PARAMTYPE_LONGINT: { std::int64_t* valptr; if( params[i]->data.longintparam.valueptr != nullptr ) valptr = reinterpret_cast( params[i]->data.longintparam.valueptr ); else valptr = reinterpret_cast( ¶ms[i]->data.longintparam.curvalue ); paramSet.addParameter( params[i]->name, params[i]->desc, *valptr, std::int64_t( params[i]->data.longintparam.minvalue ), std::int64_t( params[i]->data.longintparam.maxvalue ) ); break; } case SCIP_PARAMTYPE_REAL: { double* valptr; if( params[i]->data.realparam.valueptr != nullptr ) valptr = params[i]->data.realparam.valueptr; else valptr = ¶ms[i]->data.realparam.curvalue; paramSet.addParameter( params[i]->name, params[i]->desc, *valptr, params[i]->data.realparam.minvalue, params[i]->data.realparam.maxvalue ); break; } case SCIP_PARAMTYPE_STRING: break; } } } ~ScipInterface() { if( scip != nullptr ) { SCIP_RETCODE retcode = SCIPfree( &scip ); UNUSED(retcode); assert( retcode == SCIP_OKAY ); } } }; template class ScipFactory : public SolverFactory { void ( *scipsetup )( SCIP* scip, void* usrdata ); void* scipsetup_usrdata; ScipFactory( void ( *scipsetup )( SCIP* scip, void* usrdata ), void* scipsetup_usrdata ) : scipsetup( scipsetup ), scipsetup_usrdata( scipsetup_usrdata ) { } public: virtual std::unique_ptr> newSolver( VerbosityLevel verbosity ) const { auto scip = std::unique_ptr>( new ScipInterface() ); if( scipsetup != nullptr ) scipsetup( static_cast*>( scip.get() )->getSCIP(), scipsetup_usrdata ); scip->setVerbosity( verbosity ); return std::move( scip ); } virtual void add_parameters( ParameterSet& parameter ) const { } static std::unique_ptr> create( void ( *scipsetup )( SCIP* scip, void* usrdata ) = nullptr, void* scipsetup_usrdata = nullptr ) { return std::unique_ptr>( new ScipFactory( scipsetup, scipsetup_usrdata ) ); } }; } // namespace papilo #endif