-
Notifications
You must be signed in to change notification settings - Fork 0
Getting Started with SMS++
This tutorial will guide you through your first steps with the SMS++ framework. We will assume that you are familiar with C++ development and CMake, and that SMS++ is installed in your system (see the installation guide).
All the classes and methods used in this tutorial are documented in the SMS++ API reference.
In your working directory, create a new C++ source file named tutorial.cpp
with the following content:
#include <iostream>
#include <SMS++/AbstractBlock.h>
using namespace SMSpp_di_unipi_it;
int main( int argc , char ** argv )
{
AbstractBlock b;
std::cout << b;
return( 0 );
}The above code creates an AbstractBlock and prints its content on the standard output.
An AbstractBlock is a Block (i.e., an optimization problem) that contains only an abstract representation, that is, a bunch of variables, constraints and objectives.
In the same directory, create a new CMakeLists.txt file with the following content:
cmake_minimum_required(VERSION 3.10)
project(Tutorial)
# Find SMS++ in the system
find_package(SMS++)
# Add our executable and link SMS++ to it
add_executable(tutorial tutorial.cpp)
target_link_libraries(tutorial PRIVATE SMS++::SMS++)Now you can configure and build the project with:
mkdir build && cd build
cmake ..
makeThen, launch the executable with:
./tutorial
AbstractBlock with:
0 types of static Variables, 0 types of dynamic Variables,
0 types of static Constraints, 0 types of dynamic Constraints,
0 inner BlocksAs we can expect, the Block is empty.
The following section shows how to add stuff to the AbstractBlock we created. Note that there are quicker ways to create and populate Blocks, but this code can help you understand the fundamentals.
The objects we are going to create live in a few more headers, so add the
following includes at the top of tutorial.cpp:
#include <SMS++/ColVariable.h>
#include <SMS++/LinearFunction.h>
#include <SMS++/FRealObjective.h>
#include <SMS++/FRowConstraint.h>
#include <SMS++/OneVarConstraint.h>Let's populate our Block so that it models the following LP problem:
First, create the two variables:
auto x0 = new ColVariable();
auto x1 = new ColVariable();There is a reason we are using dynamic memory allocation: since models can become very complex, with thousands of interdependent variables, constraints and bounds, SMS++ has mechanisms to keep internal consistency. When we create new elements from outside the Blocks and then add them to a Block, we hand it over the responsibility to manage them.
However, end users won't have to worry about using elements as they are: as we said, there are better ways to populate Blocks.
Then, we create two linear functions, and add our variables to them with their respective coefficients:
// f0: -5 * x0 - 2 * x1
auto f0 = new LinearFunction();
f0->add_variable( x0 , -5 );
f0->add_variable( x1 , -2 );
// f1: 2 * x0 + 1 * x1
auto f1 = new LinearFunction();
f1->add_variable( x0 , 2 );
f1->add_variable( x1 , 1 );The functions don't have a "purpose" yet. Let's use one of them as an objective, and give it a meaning:
// an objective that uses f0, to be minimized
auto obj = new FRealObjective();
obj->set_function( f0 );
// eMin is actually the default value
obj->set_sense( Objective::eMin );We will use the second one as a constraint, and set the LHS and RHS:
// a constraint that uses f1, with -inf <= f1 <= 9
auto con = new FRowConstraint();
con->set_function( f1 );
// for FRowConstraints, LHS and RHS are 0 by default
con->set_lhs( -Inf< double >() );
con->set_rhs( 9 );Bounds are treated as a special kind of constraint. The following code adds upper bounds to the variables:
// for BoxConstraints, LHS is 0 by default
auto bc0 = new BoxConstraint();
bc0->set_variable( x0 );
bc0->set_rhs( 4 );
auto bc1 = new BoxConstraint();
bc1->set_variable( x1 );
bc1->set_rhs( 7 );Let's now add the elements to the AbstractBlock:
b.add_static_variable( *x0 );
b.add_static_variable( *x1 );
b.add_static_constraint( *con );
b.add_static_constraint( *bc0 );
b.add_static_constraint( *bc1 );
b.set_objective( obj );As we said, the Block will take care of the elements from now on,
so we don't need to delete them. If we build and run the code, the output
will now be:
./tutorial
AbstractBlock with:
2 types of static Variables, 0 types of dynamic Variables,
3 types of static Constraints, 0 types of dynamic Constraints,
0 inner BlocksThe code above can actually be simplified in several ways:
// variable/coefficient pairs added immediately to the functions
auto f0 = new LinearFunction( { { x0 , -5 } , { x1 , -2 } } );
auto f1 = new LinearFunction( { { x0 , 2 } , { x1 , 1 } } );
// one-line initializations for objective, constraint and bounds
auto obj = new FRealObjective( nullptr , f0 );
auto con = new FRowConstraint( nullptr , -Inf< double >() , 9 , f1 );
auto bc0 = new BoxConstraint( nullptr , x0 , 0 , 4 );
auto bc1 = new BoxConstraint( nullptr , x1 , 0 , 7 );We have now a Block modeling a simple LP problem. The next step is to add a Solver that can solve the problem.
To solve the problem we modelled in the previous step, we need the MILPSolver module. MILPSolver can solve any Block whose abstract representation is a MILP/MIQP problem, by interfacing external optimization tools.
Some Blocks can have both an abstract and a physical representation, and can therefore be solved by Solvers that can read either one or the other. For example, the ThermalUnitBlock provided by the UCBlock module can be solved either by MILPSolver through its abstract representation or by ThermalUnitDPSolver through its physical representation.
The MILPSolver module ships several interchangeable back-ends; you just swap the class name to switch solver:
-
HiGHSMILPSolver— HiGHS, open source; -
SCIPMILPSolver— SCIP, open source; -
CPXMILPSolver— IBM ILOG CPLEX, commercial; -
GRBMILPSolver— Gurobi, commercial.
In this tutorial we use HiGHS via the HiGHSMILPSolver class, since it is
open source and fast; to use any of the others, just replace the class name
(and its header) below.
First, add the MILPSolver library in the CMakeLists.txt:
cmake_minimum_required(VERSION 3.10)
project(Tutorial)
# the SMS++ library is automatically retrieved by MILPSolver
find_package(MILPSolver)
add_executable(tutorial tutorial.cpp)
target_link_libraries(tutorial PRIVATE SMS++::MILPSolver)In the tutorial.cpp file, include the solver header:
#include <SMS++/HiGHSMILPSolver.h>then create a solver and register it to the Block:
HiGHSMILPSolver s;
b.register_Solver( & s );When the register_Solver() method is called, the Solver is initialized
with the data from the Block.
The next step is to let the Solver solve the problem:
s.compute();Once the Solver is done, we can use the get_var_value() method to see the
value of the solution:
std::cout << s.get_var_value() << std::endl;
// -22At this point, the solution is stored in the Solver.
If we want to write back the solution in the Block,
we can use the get_var_solution() method:
s.get_var_solution();The above method writes the solution into the ColVariables. In fact, we can now get it directly from those:
std::cout << x0->get_value() << std::endl; // 4
std::cout << x1->get_value() << std::endl; // 1Now that the ColVariables store the solution, we can tell the Function to compute its value and get it from there:
f0->compute();
std::cout << f0->get_value() << std::endl; // -22The code we wrote till now allowed us to model and solve a simple LP problem. You can download the source files here.
The method we used is fine when the problem is simple, but building the Blocks and registering the Solvers manually can become tedious as the problem gets more complex. In the next sections we will see how we can speed up the process.
Since we are dealing with an LP problem, it is good to know that the
AbstractBlock can read a problem from an MPS file.
The load() method reads a model from an input stream (MPS by default;
pass 'L' as the second argument for the CPLEX LP format).
If we have a tutorial.mps describing the above problem,
we can simplify our code as follows:
#include <fstream>AbstractBlock b;
std::ifstream file( "tutorial.mps" );
b.load( file );
HiGHSMILPSolver s;
b.register_Solver( & s );
s.compute();
std::cout << s.get_var_value() << std::endl;