|
2 | 2 | #include "Highs.h" |
3 | 3 | #include "SpecialLps.h" |
4 | 4 | #include "catch.hpp" |
| 5 | +#include "mip/HighsCliqueTable.h" |
| 6 | +#include "mip/HighsMipSolver.h" |
| 7 | +#include "mip/HighsMipSolverData.h" |
5 | 8 |
|
6 | 9 | const bool dev_run = false; |
7 | 10 | const double double_equal_tolerance = 1e-5; |
@@ -1605,3 +1608,99 @@ TEST_CASE("issue-3118a", "[highs_test_mip_solver]") { |
1605 | 1608 |
|
1606 | 1609 | highs.resetGlobalScheduler(true); |
1607 | 1610 | } |
| 1611 | + |
| 1612 | +TEST_CASE("MIP-equality-clique-fixing", "[highs_test_mip_solver]") { |
| 1613 | + // Regression test: when an equality clique has all but one variable fixed, |
| 1614 | + // the last active variable must be fixed before the clique is removed. |
| 1615 | + const HighsInt ncols = 5; |
| 1616 | + HighsLp lp; |
| 1617 | + lp.num_col_ = ncols; |
| 1618 | + lp.num_row_ = 0; |
| 1619 | + lp.col_cost_.assign(ncols, 0.0); |
| 1620 | + lp.col_lower_.assign(ncols, 0.0); |
| 1621 | + lp.col_upper_.assign(ncols, 1.0); |
| 1622 | + lp.integrality_.assign(ncols, HighsVarType::kInteger); |
| 1623 | + lp.a_matrix_.start_.assign(ncols + 1, 0); |
| 1624 | + |
| 1625 | + Highs highs; |
| 1626 | + highs.setOptionValue("output_flag", dev_run); |
| 1627 | + highs.passModel(lp); |
| 1628 | + |
| 1629 | + HighsCallback callback(&highs); |
| 1630 | + const HighsOptions& options = highs.getOptions(); |
| 1631 | + HighsSolution solution; |
| 1632 | + HighsMipSolver mipsolver(callback, options, lp, solution); |
| 1633 | + mipsolver.mipdata_ = |
| 1634 | + std::unique_ptr<HighsMipSolverData>(new HighsMipSolverData(mipsolver)); |
| 1635 | + mipsolver.mipdata_->feastol = 1e-6; |
| 1636 | + mipsolver.mipdata_->setupDomainPropagation(); |
| 1637 | + |
| 1638 | + HighsCliqueTable& cliquetable = mipsolver.mipdata_->cliquetable; |
| 1639 | + HighsDomain& domain = mipsolver.mipdata_->getDomain(); |
| 1640 | + |
| 1641 | + // Add equality clique: x0 + x1 + x2 + x3 + x4 = 1 |
| 1642 | + HighsCliqueTable::CliqueVar clique[] = { |
| 1643 | + {0, 1}, {1, 1}, {2, 1}, {3, 1}, {4, 1}}; |
| 1644 | + cliquetable.doAddClique(clique, 5, true); |
| 1645 | + cliquetable.setPresolveFlag(true); |
| 1646 | + |
| 1647 | + // Fix x0..x3 = 0 via vertexInfeasible (simulates what cleanupFixed does) |
| 1648 | + for (HighsInt i = 0; i < 4; i++) { |
| 1649 | + domain.fixCol(i, 0.0); |
| 1650 | + cliquetable.vertexInfeasible(domain, i, 1); |
| 1651 | + } |
| 1652 | + |
| 1653 | + // The last variable x4 must now be fixed to 1 |
| 1654 | + REQUIRE(domain.isFixed(4)); |
| 1655 | + REQUIRE(domain.col_lower_[4] == 1.0); |
| 1656 | + |
| 1657 | + highs.resetGlobalScheduler(true); |
| 1658 | +} |
| 1659 | + |
| 1660 | +TEST_CASE("MIP-equality-clique-fixing-to-zero", "[highs_test_mip_solver]") { |
| 1661 | + // Same as above but with val=0 clique entries: the active value is 0. |
| 1662 | + // Fixing all but one variable to 1 (inactive) should fix the last to 0. |
| 1663 | + const HighsInt ncols = 5; |
| 1664 | + HighsLp lp; |
| 1665 | + lp.num_col_ = ncols; |
| 1666 | + lp.num_row_ = 0; |
| 1667 | + lp.col_cost_.assign(ncols, 0.0); |
| 1668 | + lp.col_lower_.assign(ncols, 0.0); |
| 1669 | + lp.col_upper_.assign(ncols, 1.0); |
| 1670 | + lp.integrality_.assign(ncols, HighsVarType::kInteger); |
| 1671 | + lp.a_matrix_.start_.assign(ncols + 1, 0); |
| 1672 | + |
| 1673 | + Highs highs; |
| 1674 | + highs.setOptionValue("output_flag", dev_run); |
| 1675 | + highs.passModel(lp); |
| 1676 | + |
| 1677 | + HighsCallback callback(&highs); |
| 1678 | + const HighsOptions& options = highs.getOptions(); |
| 1679 | + HighsSolution solution; |
| 1680 | + HighsMipSolver mipsolver(callback, options, lp, solution); |
| 1681 | + mipsolver.mipdata_ = |
| 1682 | + std::unique_ptr<HighsMipSolverData>(new HighsMipSolverData(mipsolver)); |
| 1683 | + mipsolver.mipdata_->feastol = 1e-6; |
| 1684 | + mipsolver.mipdata_->setupDomainPropagation(); |
| 1685 | + |
| 1686 | + HighsCliqueTable& cliquetable = mipsolver.mipdata_->cliquetable; |
| 1687 | + HighsDomain& domain = mipsolver.mipdata_->getDomain(); |
| 1688 | + |
| 1689 | + // Add equality clique with val=0: (1-x0) + (1-x1) + ... + (1-x4) = 1 |
| 1690 | + HighsCliqueTable::CliqueVar clique[] = { |
| 1691 | + {0, 0}, {1, 0}, {2, 0}, {3, 0}, {4, 0}}; |
| 1692 | + cliquetable.doAddClique(clique, 5, true); |
| 1693 | + cliquetable.setPresolveFlag(true); |
| 1694 | + |
| 1695 | + // Fix x0..x3 = 1 via vertexInfeasible (makes their val=0 entry infeasible) |
| 1696 | + for (HighsInt i = 0; i < 4; i++) { |
| 1697 | + domain.fixCol(i, 1.0); |
| 1698 | + cliquetable.vertexInfeasible(domain, i, 0); |
| 1699 | + } |
| 1700 | + |
| 1701 | + // The last variable x4 must now be fixed to 0 |
| 1702 | + REQUIRE(domain.isFixed(4)); |
| 1703 | + REQUIRE(domain.col_upper_[4] == 0.0); |
| 1704 | + |
| 1705 | + highs.resetGlobalScheduler(true); |
| 1706 | +} |
0 commit comments