Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
21040e2
Add Model::setMessagehdlr to install custom message handlers
hedtke Sep 24, 2026
6732cae
Add Model::includeEventhdlr
hedtke Sep 24, 2026
7a2d48b
Move usage documentation from readme to Doxygen
hedtke Sep 24, 2026
ba111d4
Move ObjSCIP stuff into own translation unit
hedtke Sep 24, 2026
97b3117
Add Conshdlr support
hedtke Sep 24, 2026
237d2c8
Simplify changelog
hedtke Sep 24, 2026
66d7271
Simplify include code
hedtke Sep 24, 2026
bb09213
Add Model::includeHeur
hedtke Sep 24, 2026
d163a3d
Add Model::includePresol
hedtke Sep 24, 2026
32e0c70
Add Model::includeProp
hedtke Sep 24, 2026
bd40390
Add Model::includeSepa
hedtke Sep 24, 2026
dc20cf0
Add Model::includeCutsel
hedtke Sep 24, 2026
ea45075
Add Model::includeBranchrule
hedtke Sep 24, 2026
33fbdaf
Add Model::includeNodesel
hedtke Sep 24, 2026
4f05cea
Add Model::includeRelax
hedtke Sep 24, 2026
aa78943
Add Model::includeDisp
hedtke Sep 24, 2026
2d633a8
Add Model::includeTable
hedtke Sep 24, 2026
264da11
Add Model::includeReader
hedtke Sep 24, 2026
bf9d002
Add Model::includeIISfinder
hedtke Sep 24, 2026
589c44e
Add Model::includePricer
hedtke Sep 24, 2026
6a82d5d
Add Model::includeBenders
hedtke Sep 24, 2026
a74e301
Add Model::includeBenderscut
hedtke Sep 24, 2026
1925543
Add Model constructor with custom problem data
hedtke Sep 24, 2026
34a569e
Add Model::addVar with custom variable data
hedtke Sep 24, 2026
21620ff
List all supported ObjSCIP features in one list
hedtke Sep 24, 2026
8034824
Format test_objscip.cpp with clang-format 17
hedtke Sep 24, 2026
aed5e42
Omit ObjTable support due to scipopt/scip#222 and cover ObjSCIP failu…
hedtke Sep 24, 2026
2dd7bf4
Merge remote-tracking branch 'origin/main' into ivhedtke-messagehdlr
hedtke Sep 24, 2026
6589483
Simplify include code
hedtke Oct 5, 2026
65d88d1
Update codecov action from v3 to v7
hedtke Oct 5, 2026
a77f116
Fix false positive branch coverage
hedtke Oct 5, 2026
d402e29
Restore old docu
hedtke Oct 6, 2026
8f212f7
Select the include function by overload resolution
hedtke Oct 16, 2026
bba7de0
Share the tail of both addVar overloads
hedtke Oct 16, 2026
a777b99
Use public counters in the test plugins
hedtke Oct 16, 2026
c4f81d7
Derive the cloneable Benders test plugin from the existing one
hedtke Oct 16, 2026
b6b4a0e
Share file handling between the reader and message handler tests
hedtke Oct 16, 2026
57437af
Drop copied comments from the coverage upload steps
hedtke Oct 16, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 16 additions & 3 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,24 @@ jobs:
run: pip install gcovr
- name: Extract Coverage
run: gcovr -r . -e ./test --object-directory=./build/Debug/CMakeFiles --exclude-unreachable-branches --exclude-throw-branches -b --sonarqube coverage.xml --xml cobertura.xml
- uses: codecov/codecov-action@v3
- name: Upload Coverage
uses: codecov/codecov-action@v7
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: ./boosttest.xml,./coverage.xml,./cobertura.xml
verbose: true # optional (default = false)
files: ./coverage.xml,./cobertura.xml
disable_search: true
verbose: true
fail_ci_if_error: true
# Codecov treats JUnit test results as a separate report type, so they need their own upload
- name: Upload Test Results
if: ${{ !cancelled() }}
uses: codecov/codecov-action@v7
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: ./boosttest.xml
disable_search: true
report_type: test_results
verbose: true
fail_ci_if_error: true
test_release_win:
strategy:
Expand Down
13 changes: 13 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased] - [Doc:Unreleased]

### Added

- [PR44](https://github.com/scipopt/SCIPpp/pull/44), [Issue29](https://github.com/scipopt/SCIPpp/issues/29) Support
of ObjSCIP without accessing the raw SCIP object:
- `Model::include` includes plugins derived from `ObjBranchrule`, `ObjConshdlr`, `ObjCutsel`,
`ObjDisp`, `ObjEventhdlr`, `ObjHeur`, `ObjIISfinder`, `ObjNodesel`,
`ObjPresol`, `ObjPricer`, `ObjProp`, `ObjReader`, `ObjRelax`, and `ObjSepa`,
- `Model::includeBenders` and `Model::includeBenderscut` include Benders' decompositions and cuts derived from
`ObjBenders` and `ObjBenderscut`,
- `Model::setMessagehdlr` installs message handlers derived from `ObjMessagehdlr`,
- a new constructor of `Model` takes problem data derived from `ObjProbData`, and
- a new overload of `Model::addVar` takes variable data derived from `ObjVardata`.

### Changed

- [PR45](https://github.com/scipopt/SCIPpp/pull/45) Update to SCIP 10.1.0.
Expand Down
219 changes: 219 additions & 0 deletions include/scippp/model.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,13 @@
#include <array>
#include <filesystem>
#include <functional>
#include <memory>
#include <objscip/objscip.h>
#include <optional>
#include <scip/scip.h>
#include <string>
#include <type_traits>
#include <utility>
#include <vector>

#include "scippp/constant_coefficient.hpp"
Expand Down Expand Up @@ -52,6 +55,83 @@ class Model {
//! Wrapper for every call to %SCIP's %C %API.
std::function<void(SCIP_Retcode)> m_scipCallWrapper;

/**
* Constructs a plugin and includes it, %SCIP takes ownership on success.
*
* @tparam Plugin Type of the plugin, derived from \p Base.
* @tparam Base objscip base class of the plugin.
* @tparam Args Types of the additional constructor arguments.
* @param scipInclude %SCIP's include function for \p Base, e.g., SCIPincludeObjEventhdlr.
* @param args passed to the constructor of \p Plugin after the %SCIP data structure.
* @return Non-owning pointer to the plugin, or \c nullptr if including failed.
*/
template <typename Plugin, typename Base, typename... Args>
Plugin* constructAndInclude(SCIP_RETCODE (*scipInclude)(SCIP*, Base*, SCIP_Bool), Args&&... args) const
{
static_assert(std::is_base_of_v<Base, Plugin>, "Plugin must derive from the objscip base class");
auto plugin { std::make_unique<Plugin>(m_scip, std::forward<Args>(args)...) };
const auto RETCODE { scipInclude(m_scip, plugin.get(), TRUE) };
// SCIP owns the plugin only on success, otherwise it is deleted when leaving this method.
Plugin* result { RETCODE == SCIP_OKAY ? plugin.release() : nullptr };
m_scipCallWrapper(RETCODE);
return result;
}

/**
* Maps an objscip base class to %SCIP's include function for it, so that include() can select it by overload
* resolution on a pointer to the plugin. A plugin deriving from several base classes is rejected as ambiguous.
* @{
*/
static auto includeFunction(scip::ObjBranchrule*) { return &SCIPincludeObjBranchrule; }
static auto includeFunction(scip::ObjConshdlr*) { return &SCIPincludeObjConshdlr; }
static auto includeFunction(scip::ObjCutsel*) { return &SCIPincludeObjCutsel; }
static auto includeFunction(scip::ObjDisp*) { return &SCIPincludeObjDisp; }
static auto includeFunction(scip::ObjEventhdlr*) { return &SCIPincludeObjEventhdlr; }
static auto includeFunction(scip::ObjHeur*) { return &SCIPincludeObjHeur; }
static auto includeFunction(scip::ObjIISfinder*) { return &SCIPincludeObjIISfinder; }
static auto includeFunction(scip::ObjNodesel*) { return &SCIPincludeObjNodesel; }
static auto includeFunction(scip::ObjPresol*) { return &SCIPincludeObjPresol; }
static auto includeFunction(scip::ObjPricer*) { return &SCIPincludeObjPricer; }
static auto includeFunction(scip::ObjProp*) { return &SCIPincludeObjProp; }
static auto includeFunction(scip::ObjReader*) { return &SCIPincludeObjReader; }
static auto includeFunction(scip::ObjRelax*) { return &SCIPincludeObjRelax; }
static auto includeFunction(scip::ObjSepa*) { return &SCIPincludeObjSepa; }
static auto includeFunction(scip::ObjBenders*) = delete; // use includeBenders()
static auto includeFunction(scip::ObjBenderscut*) = delete; // use includeBenderscut()
/** @} */

/**
* Adds a created variable to the problem and stores it.
*
* @param var created variable, %SCIP's reference to it is kept by this model.
* @return Reference to the stored variable.
*/
Var& addCreatedVar(SCIP_VAR* var);

/**
* Activates an included variable pricer, so that it is used when solving the model.
*
* @param pricer to activate.
*/
void activatePricer(const scip::ObjPricer& pricer) const;

/**
* Includes the default Benders' cuts for an included Benders' decomposition and activates it together with the
* constraint handlers enforcing it, so that it is used when solving the model.
*
* @param benders Benders' decomposition to activate.
* @param nSubproblems number of subproblems of the Benders' decomposition.
*/
void activateBenders(const scip::ObjBenders& benders, int nSubproblems) const;

/**
* Prepares the %SCIP data structure without creating a problem in it.
*
* @param scip to use. If \c nullptr, a new %SCIP data structure will be created.
* @param includeDefaultPlugins if \c true, the default plugins are added to \p scip.
*/
Model(SCIP* scip, bool includeDefaultPlugins);

public:
/**
* Creates an empty problem and sets the optimization goal to Sense::MINIMIZE.
Expand All @@ -65,6 +145,21 @@ class Model {
*/
explicit Model(const std::string& name, SCIP* scip = nullptr, bool includeDefaultPlugins = true);

/**
* Creates an empty problem with custom problem data and sets the optimization goal to Sense::MINIMIZE.
* @since 1.5.0
* @param name for the problem.
* @param probData to store in the problem, must not be \c nullptr. %SCIP takes ownership and deletes it when the
* problem is freed.
* @param scip to create the problem in. If \c nullptr, a new %SCIP data structure will be created.
* @param includeDefaultPlugins if \c true, the default plugins are added to \p scip.
*/
Model(
const std::string& name,
std::unique_ptr<scip::ObjProbData> probData,
SCIP* scip = nullptr,
bool includeDefaultPlugins = true);

/**
* Releases the variables and constraints.
* @since 1.0.0
Expand Down Expand Up @@ -102,6 +197,26 @@ class Model {
std::optional<SCIP_Real> lb = 0.0,
std::optional<SCIP_Real> ub = 1.0);

/**
* Adds a variable with custom variable data to the model.
* @since 1.5.0
* @param name of the variable when the model is written.
* @param vardata to store in the variable, must not be \c nullptr. %SCIP takes ownership and deletes it when the
* variable is freed.
* @param coeff Coefficient in the objective function.
* @param varType variable type.
* @param lb lower bound. \c std::nullopt is interpreted as -infinity.
* @param ub upper bound. \c std::nullopt is interpreted as infinity.
* @return Reference to the newly created variable.
*/
Var& addVar(
const std::string& name,
std::unique_ptr<scip::ObjVardata> vardata,
SCIP_Real coeff = 0.0,
VarType varType = VarType::CONTINUOUS,
std::optional<SCIP_Real> lb = 0.0,
std::optional<SCIP_Real> ub = 1.0);

/**
* Adds multiple variables to the model.
* @since 1.0.0
Expand Down Expand Up @@ -356,5 +471,109 @@ class Model {
* @return The generated %IIS.
*/
[[nodiscard]] IIS generateIIS() const;

/**
* Installs a custom message handler, i.e., all info, warning, and dialog messages of %SCIP are passed to it.
* @since 1.5.0
* @param handler to install, must not be \c nullptr. %SCIP takes ownership and deletes it when it is no longer
* used.
* @attention Must be called before solve(). Error messages are not passed to \p handler as %SCIP prints them via a
* global error printing function.
*/
void setMessagehdlr(std::unique_ptr<scip::ObjMessagehdlr> handler) const;

/**
* Includes a custom plugin, e.g., an event handler, a primal heuristic, or a separator.
*
* The plugin is constructed by this method as the objscip base classes require the %SCIP data structure in their
* constructors. %SCIP takes ownership and deletes it when the model is destructed. The kind of the plugin is
* determined by its objscip base class. Variable pricers are activated after including them. For Benders'
* decompositions and their cuts, use includeBenders() and includeBenderscut().
*
* @since 1.5.0
* @tparam Plugin Type of the plugin, derived from exactly one of the supported objscip base classes, see readme.
* @tparam Args Types of the additional constructor arguments.
* @param args passed to the constructor of \p Plugin after the %SCIP data structure.
* @return Non-owning pointer to the plugin, or \c nullptr if including failed.
* @attention Must be called before solve().
* @note SCIP++ cannot create constraints of a custom constraint handler. Thus, pass \c needscons = \c FALSE to the
* constructor of scip::ObjConshdlr, so that the handler is called without constraints, and lock the
* variables in \c scip_lock, which is then called with \c cons = \c nullptr.
* @note Constraints added via addConstr() are not modifiable, i.e., priced variables cannot be added to them.
*/
template <typename Plugin, typename... Args>
Plugin* include(Args&&... args) const
{
// overload resolution picks the include function of the base class, see includeFunction
auto* plugin { constructAndInclude<Plugin>(
includeFunction(static_cast<Plugin*>(nullptr)), std::forward<Args>(args)...) };
if constexpr (std::is_base_of_v<scip::ObjPricer, Plugin>) {
if (plugin != nullptr) {
activatePricer(*plugin);
}
}
return plugin;
}

/**
* Includes and activates a custom Benders' decomposition together with %SCIP's default Benders' cuts.
*
* The Benders' decomposition is constructed by this method as scip::ObjBenders requires the %SCIP data structure
* in its constructor. %SCIP takes ownership and deletes it when the model is destructed.
*
* Activating the Benders' decomposition also activates the constraint handlers \c benders and \c benderslp, which
* enforce it. If \p Benders is not cloneable, copying Benders' decompositions to sub-SCIPs is disabled via the
* parameter \c benders/copybenders, as %SCIP cannot copy it.
*
* @since 1.5.0
* @tparam Benders Type of the Benders' decomposition, derived from scip::ObjBenders.
* @tparam Args Types of the additional constructor arguments.
* @param nSubproblems number of subproblems of the Benders' decomposition.
* @param args passed to the constructor of \p Benders after the %SCIP data structure.
* @return Non-owning pointer to the Benders' decomposition, or \c nullptr if including failed.
* @attention Must be called before solve(). The priority of \p Benders has to be positive, as %SCIP expects
* active Benders' decompositions to have higher priorities than the inactive default one.
* @attention scip::ObjBenders always provides the callbacks to solve subproblems, so %SCIP does not solve them
* itself. Hence, \p Benders has to implement \c scip_solvesubconvex or \c scip_solvesub.
* @note The default Benders' cuts can be disabled via the parameters
* <code>benders/\<name\>/benderscut/\<cut\>/enabled</code>.
*/
template <typename Benders, typename... Args>
Benders* includeBenders(int nSubproblems, Args&&... args) const
{
auto* benders { constructAndInclude<Benders>(&SCIPincludeObjBenders, std::forward<Args>(args)...) };
if (benders != nullptr) {
activateBenders(*benders, nSubproblems);
}
return benders;
}

/**
* Includes a custom Benders' decomposition cut for a Benders' decomposition.
*
* The cut is constructed by this method as scip::ObjBenderscut requires the %SCIP data structure in its
* constructor. %SCIP takes ownership and deletes it when the model is destructed.
*
* @since 1.5.0
* @tparam Benderscut Type of the cut, derived from scip::ObjBenderscut.
* @tparam Args Types of the additional constructor arguments.
* @param benders Benders' decomposition to generate the cuts for, see includeBenders().
* @param args passed to the constructor of \p Benderscut after the %SCIP data structure.
* @return Non-owning pointer to the cut, or \c nullptr if including failed.
* @attention Must be called before solve().
*/
template <typename Benderscut, typename... Args>
Benderscut* includeBenderscut(scip::ObjBenders& benders, Args&&... args) const
{
// like constructAndInclude, but the include function additionally requires the Benders' decomposition
static_assert(
std::is_base_of_v<scip::ObjBenderscut, Benderscut>, "Benderscut must derive from scip::ObjBenderscut");
auto cut { std::make_unique<Benderscut>(m_scip, std::forward<Args>(args)...) };
const auto RETCODE { SCIPincludeObjBenderscut(m_scip, &benders, cut.get(), TRUE) };
// SCIP owns the cut only on success, otherwise it is deleted when leaving this method.
Benderscut* result { RETCODE == SCIP_OKAY ? cut.release() : nullptr };
m_scipCallWrapper(RETCODE);
return result;
}
};
}
46 changes: 46 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,52 @@ model.solve();
auto pb { model.getSolvingStatistic(statistics::PRIMALBOUND) };
```

### Custom Plugins (ObjSCIP)

Plugins derived from SCIP's [ObjSCIP](https://www.scipopt.org/doc/html/OBJ.php) base classes are included via
`Model::include`. The plugin is constructed by SCIP++, because the base classes need the SCIP data structure in their
constructors. Its constructor gets the `SCIP*` as first argument, followed by the arguments passed to `include`. SCIP
takes ownership of the plugin, `include` returns a non-owning pointer to it. Plugins have to be included before solving.

```cpp
class MyHeuristic : public scip::ObjHeur {
public:
MyHeuristic(SCIP* scip, const std::vector<Var>& vars)
: ObjHeur(scip, "myheur", "my heuristic", 'x', 1000, 1, 0, -1, SCIP_HEURTIMING_AFTERNODE, FALSE)
, m_vars(vars)
{
}
SCIP_DECL_HEUREXEC(scip_exec) override;
private:
std::vector<Var> m_vars;
};

auto vars = model.addVars("x_", 42);
auto* heur = model.include<MyHeuristic>(vars);
model.solve();
```

This works the same way for plugins derived from `ObjBranchrule`, `ObjConshdlr`, `ObjCutsel`, `ObjDisp`,
`ObjEventhdlr`, `ObjHeur`, `ObjIISfinder`, `ObjNodesel`, `ObjPresol`, `ObjPricer`, `ObjProp`, `ObjReader`, `ObjRelax`,
and `ObjSepa`, with the following exceptions:

* `ObjConshdlr`: SCIP++ cannot create constraints of a custom constraint handler. Pass `needscons = FALSE` to the
constructor, so that the handler is called without constraints, and lock the variables in `scip_lock`, which is then
called with `cons = nullptr`.
* `ObjPricer`: The pricer is activated after including it. Constraints added via `Model::addConstr` are not
modifiable, i.e., priced variables cannot be added to them.
* `ObjBenders`: Use `model.includeBenders<MyBenders>(nSubproblems, args...)`. It activates the decomposition together
with SCIP's default Benders' cuts. The priority has to be positive, and `scip_solvesubconvex` or `scip_solvesub` has
to be implemented, as SCIP does not solve the subproblems itself.
* `ObjBenderscut`: Use `model.includeBenderscut<MyBendersCut>(*benders, args...)` with the pointer returned by
`includeBenders`.
* `ObjMessagehdlr`: Is not constructed by SCIP++, install it via
`model.setMessagehdlr(std::make_unique<MyMessageHandler>())`. Error messages are not passed to it.
* `ObjProbData`: Pass it to the constructor, `Model model("name", std::make_unique<MyProblemData>())`.
* `ObjVardata`: Pass it when adding a variable, `model.addVar("x", std::make_unique<MyVariableData>(), 1.0)`.
* `ObjTable`: Is not supported, as SCIP does not export it from its shared library, see
[SCIP issue 222](https://github.com/scipopt/scip/issues/222).

### Features Not Yet Supported

For features not yet supported by SCIP++, one can access the underlying raw SCIP object via
Expand Down
Loading
Loading