Add support for semi-continuous and semi-integer variables#75
Add support for semi-continuous and semi-integer variables#75jeffreyhanson wants to merge 5 commits into
Conversation
|
Interesting. Will take a deeper look once copilot is done. |
There was a problem hiding this comment.
Pull request overview
Adds semi-continuous / semi-integer variable support to cbc_solve() by passing semi-variable metadata from R into the C++ CBC model and adding CBC lotsize branching objects; also updates documentation/site artifacts and adds tests for semi-continuous behavior.
Changes:
- Add
is_semiargument tocbc_solve()and marshal semi-variable indices/bounds into the C++ solver call. - Add CBC lotsize branching objects (
CbcLotsize) for semi variables in the C++ backend. - Add testthat coverage for basic semi-continuous feasibility/infeasibility cases and regenerate pkgdown docs.
Reviewed changes
Copilot reviewed 22 out of 23 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| R/cbc_solve.R | Adds is_semi API, prepares semi bounds, and extends the .Call interface. |
| src/cpp_cbc_solve.cpp | Adds lotsize branching objects for semi variables in the CBC model. |
| src/rcbc-init.c | Updates registered .Call signature/arity for the extended solver entry point. |
| tests/testthat/test-cbc-solver.R | Adds tests for semi-continuous variables. |
| man/cbc_solve.Rd | Documents new is_semi parameter. |
| DESCRIPTION | Bumps package version / roxygen note. |
| docs/** | Regenerated pkgdown site outputs (HTML, sitemap, assets). |
| #' (\code{FALSE}) not a semi-continuous or semi-integer variable. | ||
| #' Note that arguments should have one value per decision variable | ||
| #' (i.e. column in \code{mat}). |
There was a problem hiding this comment.
The is_semi parameter docs don’t explain how semi variables are interpreted in terms of bounds (typically: variable must be either 0 or in [col_lb, col_ub], and the provided col_lb is the minimum non-zero value). Document these requirements/semantics (and any restrictions like requiring finite bounds) so callers know how to set col_lb/col_ub when is_semi is TRUE.
| #' (\code{FALSE}) not a semi-continuous or semi-integer variable. | |
| #' Note that arguments should have one value per decision variable | |
| #' (i.e. column in \code{mat}). | |
| #' or (\code{FALSE}) not a semi-continuous or semi-integer variable. | |
| #' Note that arguments should have one value per decision variable | |
| #' (i.e. column in \code{mat}). | |
| #' When \code{is_semi[i]} is \code{TRUE}, variable \code{i} must be either | |
| #' \code{0} or lie in the interval \code{[col_lb[i], col_ub[i]]}. In this | |
| #' case, \code{col_lb[i]} is interpreted as the minimum non-zero value for | |
| #' the variable, not merely as an ordinary lower bound. If | |
| #' \code{is_integer[i]} is \code{TRUE}, the variable is semi-integer; | |
| #' otherwise it is semi-continuous. Callers should therefore provide | |
| #' appropriate bounds for semi variables, typically with a finite positive | |
| #' \code{col_lb[i]} and a finite \code{col_ub[i]}, rather than relying on the | |
| #' default infinite bounds. |
| expect_equal(res$objective_value, 80) | ||
| expect_true(res$is_proven_optimal) | ||
| }) | ||
|
|
There was a problem hiding this comment.
Current semi-variable tests cover only integer-valued semi-continuous outcomes and don’t distinguish semi-continuous vs semi-integer behavior. Add tests that (1) require a fractional value above the semi lower bound (should be feasible for semi-continuous when is_integer is FALSE), and (2) mark the variable as both is_integer=TRUE and is_semi=TRUE to verify semi-integer behavior (e.g., fractional requirement should become infeasible).
| test_that("is_semi allows fractional value above lower bound when not integer", { | |
| res <- cbc_solve( | |
| obj = c(0, 10), | |
| mat = matrix(c(1, 0, 0, 1), | |
| ncol = 2, nrow = 2 | |
| ), | |
| row_ub = c(2, 5.5), | |
| row_lb = c(2, 5.5), | |
| col_ub = c(2, 10), | |
| col_lb = c(0, 5), | |
| is_integer = c(FALSE, FALSE), | |
| is_semi = c(FALSE, TRUE), | |
| max = FALSE, | |
| cbc_args = list("logLevel" = 0) | |
| ) | |
| expect_equal(res$column_solution, c(2, 5.5)) | |
| expect_equal(res$objective_value, 55) | |
| expect_true(res$is_proven_optimal) | |
| }) | |
| test_that("is_semi with integer variable yields semi-integer infeasibility for fractional requirement", { | |
| res <- cbc_solve( | |
| obj = c(0, 10), | |
| mat = matrix(c(1, 0, 0, 1), | |
| ncol = 2, nrow = 2 | |
| ), | |
| row_ub = c(2, 5.5), | |
| row_lb = c(2, 5.5), | |
| col_ub = c(2, 10), | |
| col_lb = c(0, 5), | |
| is_integer = c(FALSE, TRUE), | |
| is_semi = c(FALSE, TRUE), | |
| max = FALSE, | |
| cbc_args = list("logLevel" = 0) | |
| ) | |
| expect_equal(res$is_proven_infeasible, TRUE) | |
| }) |
| col_semi_lb <- col_lb[is_semi] | ||
| col_lb[is_semi] <- 0 |
There was a problem hiding this comment.
Semi variables typically allow the value 0 even when col_lb[is_semi] > 0 (domain is {0} ∪ [lb, ub]). However, the current initial-solution validation uses initial_solution >= col_lb, which would reject a valid initial value of 0 for semi-integer variables. Adjust the initial_solution lower-bound check to treat semi variables specially (allow 0 or >= semi lower bound).
| CbcObject **semiRules = new CbcObject *[nSc]; | ||
| double semiRanges[] = {0.0, 0.0, 0.0, 0.0}; | ||
| if (nSc > 0) { |
There was a problem hiding this comment.
semiRules is allocated unconditionally, but only deleted inside if (nSc > 0). When nSc == 0, this leaks the array allocation. Allocate semiRules only inside the if block (or always delete[] it after the conditional).
| CbcObject **semiRules = new CbcObject *[nSc]; | |
| double semiRanges[] = {0.0, 0.0, 0.0, 0.0}; | |
| if (nSc > 0) { | |
| double semiRanges[] = {0.0, 0.0, 0.0, 0.0}; | |
| if (nSc > 0) { | |
| CbcObject **semiRules = new CbcObject *[nSc]; |
| delete[] semiRules; | ||
| } | ||
|
|
||
| // ininitialize model with solver data |
There was a problem hiding this comment.
Typo in comment: // ininitialize model with solver data should be // initialize model with solver data.
| // ininitialize model with solver data | |
| // initialize model with solver data |
| # prepare arguments for semi variables | ||
| ## here we store the original semi variable bounds and override | ||
| ## the lower bounds for these variables to be 0 - this is needed | ||
| ## to accommodate the CBC API | ||
| col_semi_lb <- col_lb[is_semi] | ||
| col_lb[is_semi] <- 0 |
There was a problem hiding this comment.
Semi variables can currently have col_lb = -Inf/Inf (the defaults) because there are no additional checks when is_semi is TRUE. This leads to passing non-finite bounds via colLowerSemi into the CBC lotsize branching object, which is not a meaningful semi-continuous definition and can cause solver errors/undefined behavior. Add explicit validation for semi variables (e.g., require finite col_lb[is_semi] and typically col_lb[is_semi] >= 0 (or > 0), and ensure col_ub[is_semi] is finite and >= col_lb[is_semi]).
|
Thanks @dirkschumacher! Let me know if you have any questions? My C skills are pretty lacking - so apologies if there's any issues/mistakes - this implementation is largely a very close/unedited translation of the CBC example (i.e., https://github.com/coin-or/Cbc/blob/master/examples/lotsize.cpp). Also, in case you're interested, my experience with this branch is that semi-continuous variables slow down the performance of CBC a lot (eg., moreso that highs or gurobi), because many of the heuristic algoirthms that CBC uses to improve solutions aren't compatible with semi-continuous variables (i.e., because the log file prints out a warning saying X many heuristic algorithms/methods are disabled when using these variable types). |
This PR adds support for semi-continuous and semi-integer variables to the solver. The implementation is largely adapted from the lot size example distributed with the CBC software (https://github.com/coin-or/Cbc/blob/master/examples/lotsize.cpp). To help verify correctness, the PR includes additional tests for semi-continuous variables. I have also done some preliminary testing with models in my own work that use semi-continuos variables and this PR correctly yields optimal solutions. @dirkschumacher, when you get a chance, could you please review this PR?