|
| 1 | +#include <algorithm> |
1 | 2 | #include <cstdio> |
2 | 3 |
|
3 | 4 | #include "HCheckConfig.h" |
@@ -219,12 +220,12 @@ TEST_CASE("test-qod", "[qpsolver]") { |
219 | 220 | highs.addCol(-1, -inf, inf, 0, NULL, NULL); |
220 | 221 | if (dev_run) highs.writeModel(""); |
221 | 222 |
|
222 | | - // Cannot solve the model until the Hessian has been replaced |
| 223 | + // Can solve the model before the Hessian has been replaced |
223 | 224 | return_status = highs.run(); |
224 | | - REQUIRE(return_status == HighsStatus::kError); |
| 225 | + REQUIRE(return_status == HighsStatus::kOk); |
225 | 226 |
|
226 | 227 | model_status = highs.getModelStatus(); |
227 | | - REQUIRE(model_status == HighsModelStatus::kModelError); |
| 228 | + REQUIRE(model_status == HighsModelStatus::kUnbounded); |
228 | 229 |
|
229 | 230 | // Pass the new Hessian |
230 | 231 | hessian.dim_ = 2; |
@@ -600,3 +601,275 @@ TEST_CASE("test-semi-definite2", "[qpsolver]") { |
600 | 601 | REQUIRE(fabs(solution.col_value[0] + 1) < double_equal_tolerance); |
601 | 602 | REQUIRE(fabs(solution.col_value[1] - 2) < double_equal_tolerance); |
602 | 603 | } |
| 604 | + |
| 605 | +void hessianProduct(const HighsHessian& hessian, const std::vector<double>& arg, |
| 606 | + std::vector<double>& result) { |
| 607 | + HighsInt dim = hessian.dim_; |
| 608 | + assert(HighsInt(arg.size()) == dim); |
| 609 | + result.resize(dim); |
| 610 | + for (HighsInt iCol = 0; iCol < dim; iCol++) { |
| 611 | + double sum = 0; |
| 612 | + for (HighsInt iEl = hessian.start_[iCol]; iEl < hessian.start_[iCol + 1]; |
| 613 | + iEl++) |
| 614 | + sum += hessian.value_[iEl] * arg[hessian.index_[iEl]]; |
| 615 | + result[iCol] = sum; |
| 616 | + } |
| 617 | +} |
| 618 | + |
| 619 | +TEST_CASE("test-qp-modification", "[qpsolver]") { |
| 620 | + // HighsStatus return_status; |
| 621 | + // HighsModelStatus model_status; |
| 622 | + // double required_objective_function_value; |
| 623 | + |
| 624 | + HighsModel model; |
| 625 | + |
| 626 | + HighsLp& lp = model.lp_; |
| 627 | + HighsHessian& hessian = model.hessian_; |
| 628 | + |
| 629 | + lp.num_col_ = 2; |
| 630 | + lp.num_row_ = 1; |
| 631 | + lp.col_cost_ = {1.0, -1.0}; |
| 632 | + lp.col_lower_ = {-inf, -inf}; |
| 633 | + lp.col_upper_ = {inf, inf}; |
| 634 | + lp.sense_ = ObjSense::kMinimize; |
| 635 | + lp.offset_ = 0; |
| 636 | + lp.row_lower_ = {-inf}; |
| 637 | + lp.row_upper_ = {1}; |
| 638 | + lp.a_matrix_.format_ = MatrixFormat::kRowwise; |
| 639 | + lp.a_matrix_.start_ = {0, 2}; |
| 640 | + lp.a_matrix_.index_ = {0, 1}; |
| 641 | + lp.a_matrix_.value_ = {1.0, 1.0}; |
| 642 | + hessian.dim_ = 1; |
| 643 | + hessian.start_ = {0, 1}; |
| 644 | + hessian.value_ = {1.0}; |
| 645 | + |
| 646 | + Highs highs; |
| 647 | + highs.setOptionValue("output_flag", dev_run); |
| 648 | + const HighsModel& incumbent_model = highs.getModel(); |
| 649 | + // Cannot have Hessian with index exceeding hessian.dim_-1 |
| 650 | + hessian.index_ = {1}; |
| 651 | + REQUIRE(highs.passModel(model) == HighsStatus::kError); |
| 652 | + |
| 653 | + // Correct the Hessian index |
| 654 | + hessian.index_[0] = 0; |
| 655 | + REQUIRE(highs.passModel(model) == HighsStatus::kOk); |
| 656 | + if (dev_run) { |
| 657 | + printf("\nNow solve the QP\n\n"); |
| 658 | + incumbent_model.hessian_.print(); |
| 659 | + } |
| 660 | + highs.run(); |
| 661 | + if (dev_run) highs.writeSolution("", kSolutionStylePretty); |
| 662 | + // Add a new variables and ensure that the Hessian dimension is correct |
| 663 | + std::vector<HighsInt> index = {0}; |
| 664 | + std::vector<double> value = {1}; |
| 665 | + REQUIRE(highs.addCol(-1, 0, 1, 1, index.data(), value.data()) == |
| 666 | + HighsStatus::kOk); |
| 667 | + REQUIRE((incumbent_model.hessian_.dim_ == 0 || |
| 668 | + incumbent_model.hessian_.dim_ == incumbent_model.lp_.num_col_)); |
| 669 | + if (dev_run) { |
| 670 | + printf("\nNow solve the QP after adding new variable\n\n"); |
| 671 | + incumbent_model.hessian_.print(); |
| 672 | + } |
| 673 | + highs.run(); |
| 674 | + if (dev_run) highs.writeSolution("", kSolutionStylePretty); |
| 675 | + |
| 676 | + HighsInt dim = incumbent_model.lp_.num_col_; |
| 677 | + std::vector<double> arg0; |
| 678 | + std::vector<double> arg1; |
| 679 | + std::vector<double> result0; |
| 680 | + std::vector<double> result1; |
| 681 | + arg0.resize(dim); |
| 682 | + HighsRandom random; |
| 683 | + for (HighsInt iCol = 0; iCol < dim; iCol++) arg0[iCol] = random.fraction(); |
| 684 | + HighsHessian hessian0 = incumbent_model.hessian_; |
| 685 | + arg1 = arg0; |
| 686 | + |
| 687 | + // Deleting column 1 removes no nonzeros from the Hessian |
| 688 | + HighsInt delete_col = 1; |
| 689 | + REQUIRE(highs.deleteCols(delete_col, delete_col) == HighsStatus::kOk); |
| 690 | + REQUIRE((incumbent_model.hessian_.dim_ == 0 || |
| 691 | + incumbent_model.hessian_.dim_ == incumbent_model.lp_.num_col_)); |
| 692 | + if (dev_run) { |
| 693 | + printf("\nNow solve the QP after deleting column 1\n\n"); |
| 694 | + incumbent_model.hessian_.print(); |
| 695 | + } |
| 696 | + highs.run(); |
| 697 | + if (dev_run) highs.writeSolution("", kSolutionStylePretty); |
| 698 | + |
| 699 | + dim--; |
| 700 | + for (HighsInt iCol = delete_col; iCol < dim; iCol++) |
| 701 | + arg1[iCol] = arg1[iCol + 1]; |
| 702 | + arg0[delete_col] = 0; |
| 703 | + hessianProduct(hessian0, arg0, result0); |
| 704 | + for (HighsInt iCol = delete_col; iCol < dim; iCol++) |
| 705 | + result0[iCol] = result0[iCol + 1]; |
| 706 | + |
| 707 | + arg1.resize(dim); |
| 708 | + hessianProduct(incumbent_model.hessian_, arg1, result1); |
| 709 | + for (HighsInt iCol = 0; iCol < dim; iCol++) |
| 710 | + REQUIRE(result0[iCol] == result1[iCol]); |
| 711 | + |
| 712 | + // Deleting column 0 removes only nonzero from the Hessian, so problem is an |
| 713 | + // LP |
| 714 | + delete_col = 0; |
| 715 | + REQUIRE(highs.deleteCols(delete_col, delete_col) == HighsStatus::kOk); |
| 716 | + REQUIRE((incumbent_model.hessian_.dim_ == 0 || |
| 717 | + incumbent_model.hessian_.dim_ == incumbent_model.lp_.num_col_)); |
| 718 | + if (dev_run) { |
| 719 | + printf("\nNow solve the LP after deleting column 0\n\n"); |
| 720 | + incumbent_model.hessian_.print(); |
| 721 | + } |
| 722 | + highs.run(); |
| 723 | + if (dev_run) highs.writeSolution("", kSolutionStylePretty); |
| 724 | +} |
| 725 | + |
| 726 | +TEST_CASE("test-qp-delete-col", "[qpsolver]") { |
| 727 | + HighsModel model; |
| 728 | + HighsLp& lp = model.lp_; |
| 729 | + HighsHessian& hessian = model.hessian_; |
| 730 | + |
| 731 | + lp.num_col_ = 5; |
| 732 | + lp.num_row_ = 1; |
| 733 | + lp.col_cost_ = {-2, -1, 0, 1, 2}; |
| 734 | + lp.col_lower_ = {0, 0, 0, 0, 0}; |
| 735 | + lp.col_upper_ = {inf, inf, inf, inf, inf}; |
| 736 | + lp.sense_ = ObjSense::kMinimize; |
| 737 | + lp.offset_ = 0; |
| 738 | + lp.row_lower_ = {-inf}; |
| 739 | + lp.row_upper_ = {1}; |
| 740 | + lp.a_matrix_.format_ = MatrixFormat::kRowwise; |
| 741 | + lp.a_matrix_.start_ = {0, 5}; |
| 742 | + lp.a_matrix_.index_ = {0, 1, 2, 3, 4}; |
| 743 | + lp.a_matrix_.value_ = {1, 1, 1, 1, 1}; |
| 744 | + hessian.dim_ = 5; |
| 745 | + hessian.start_ = {0, 4, 7, 10, 11, 12}; |
| 746 | + hessian.index_ = {0, 1, 3, 4, 1, 2, 4, 2, 3, 4, 3, 4}; |
| 747 | + hessian.value_ = {11, 21, 41, 51, 22, 32, 52, 33, 43, 53, 44, 55}; |
| 748 | + |
| 749 | + Highs highs; |
| 750 | + highs.setOptionValue("output_flag", dev_run); |
| 751 | + const HighsModel& incumbent_model = highs.getModel(); |
| 752 | + REQUIRE(highs.passModel(model) == HighsStatus::kOk); |
| 753 | + if (dev_run) incumbent_model.hessian_.print(); |
| 754 | + |
| 755 | + HighsInt dim = incumbent_model.lp_.num_col_; |
| 756 | + std::vector<double> arg0; |
| 757 | + std::vector<double> arg1; |
| 758 | + std::vector<double> result0; |
| 759 | + std::vector<double> result1; |
| 760 | + arg0.resize(dim); |
| 761 | + HighsRandom random; |
| 762 | + for (HighsInt iCol = 0; iCol < dim; iCol++) arg0[iCol] = random.fraction(); |
| 763 | + HighsHessian hessian0 = incumbent_model.hessian_; |
| 764 | + arg1 = arg0; |
| 765 | + |
| 766 | + std::vector<HighsInt> set = {1, 3}; |
| 767 | + REQUIRE(highs.deleteCols(2, set.data()) == HighsStatus::kOk); |
| 768 | + if (dev_run) incumbent_model.hessian_.print(); |
| 769 | + |
| 770 | + arg1[1] = arg1[2]; |
| 771 | + arg1[2] = arg1[4]; |
| 772 | + arg0[1] = 0; |
| 773 | + arg0[3] = 0; |
| 774 | + hessianProduct(hessian0, arg0, result0); |
| 775 | + result0[1] = result0[2]; |
| 776 | + result0[2] = result0[4]; |
| 777 | + |
| 778 | + dim = 3; |
| 779 | + arg1.resize(dim); |
| 780 | + hessianProduct(incumbent_model.hessian_, arg1, result1); |
| 781 | + for (HighsInt iCol = 0; iCol < dim; iCol++) |
| 782 | + REQUIRE(result0[iCol] == result1[iCol]); |
| 783 | + |
| 784 | + dim = 100; |
| 785 | + lp.clear(); |
| 786 | + lp.num_col_ = dim; |
| 787 | + lp.num_row_ = 1; |
| 788 | + |
| 789 | + lp.col_cost_.resize(dim); |
| 790 | + lp.col_lower_.resize(dim); |
| 791 | + lp.col_upper_.resize(dim); |
| 792 | + lp.a_matrix_.index_.resize(dim); |
| 793 | + lp.a_matrix_.value_.resize(dim); |
| 794 | + |
| 795 | + for (HighsInt iCol = 0; iCol < dim; iCol++) { |
| 796 | + lp.col_cost_[iCol] = double(iCol); |
| 797 | + lp.col_lower_[iCol] = 0; |
| 798 | + lp.col_upper_[iCol] = inf; |
| 799 | + lp.a_matrix_.index_[iCol] = iCol; |
| 800 | + lp.a_matrix_.value_[iCol] = 1; |
| 801 | + } |
| 802 | + lp.a_matrix_.start_.push_back(dim); |
| 803 | + |
| 804 | + lp.sense_ = ObjSense::kMinimize; |
| 805 | + lp.offset_ = 0; |
| 806 | + lp.row_lower_ = {-inf}; |
| 807 | + lp.row_upper_ = {1}; |
| 808 | + lp.a_matrix_.format_ = MatrixFormat::kRowwise; |
| 809 | + |
| 810 | + hessian.clear(); |
| 811 | + hessian.dim_ = dim; |
| 812 | + std::vector<double> hessian_col(dim); |
| 813 | + for (HighsInt iCol = 0; iCol < dim; iCol++) { |
| 814 | + hessian_col.assign(dim, 0); |
| 815 | + HighsInt kmax = std::max(HighsInt(1), (dim - iCol) / 3); |
| 816 | + hessian_col[iCol] = dim * iCol + iCol; |
| 817 | + if (iCol < dim - 1) { |
| 818 | + for (HighsInt k = 0; k < kmax; k++) { |
| 819 | + HighsInt iRow = iCol + 1 + random.integer(dim - iCol - 1); |
| 820 | + assert(iRow >= 0); |
| 821 | + assert(iRow < dim); |
| 822 | + hessian_col[iRow] = dim * iCol + iRow; |
| 823 | + } |
| 824 | + } |
| 825 | + for (HighsInt iRow = iCol; iRow < dim; iRow++) { |
| 826 | + if (hessian_col[iRow]) { |
| 827 | + hessian.index_.push_back(iRow); |
| 828 | + hessian.value_.push_back(hessian_col[iRow]); |
| 829 | + } |
| 830 | + } |
| 831 | + hessian.start_.push_back(HighsInt(hessian.index_.size())); |
| 832 | + } |
| 833 | + |
| 834 | + REQUIRE(highs.passModel(model) == HighsStatus::kOk); |
| 835 | + if (dev_run) incumbent_model.hessian_.print(); |
| 836 | + |
| 837 | + arg0.resize(dim); |
| 838 | + for (HighsInt iCol = 0; iCol < dim; iCol++) arg0[iCol] = random.fraction(); |
| 839 | + hessian0 = incumbent_model.hessian_; |
| 840 | + arg1 = arg0; |
| 841 | + |
| 842 | + std::vector<HighsInt> mask; |
| 843 | + mask.assign(dim, 0); |
| 844 | + |
| 845 | + HighsInt kmax = std::max(HighsInt(1), dim / 3); |
| 846 | + for (HighsInt k = 0; k < kmax; k++) { |
| 847 | + HighsInt iRow = random.integer(dim); |
| 848 | + assert(iRow >= 0); |
| 849 | + assert(iRow < dim); |
| 850 | + mask[iRow] = 1; |
| 851 | + } |
| 852 | + highs.deleteCols(mask.data()); |
| 853 | + if (dev_run) incumbent_model.hessian_.print(); |
| 854 | + |
| 855 | + for (HighsInt iCol = 0; iCol < dim; iCol++) { |
| 856 | + HighsInt iRow = mask[iCol]; |
| 857 | + if (iRow < 0) { |
| 858 | + arg0[iCol] = 0; |
| 859 | + } else { |
| 860 | + arg1[iRow] = arg1[iCol]; |
| 861 | + } |
| 862 | + } |
| 863 | + hessianProduct(hessian0, arg0, result0); |
| 864 | + for (HighsInt iCol = 0; iCol < dim; iCol++) { |
| 865 | + HighsInt iRow = mask[iCol]; |
| 866 | + if (iRow >= 0) result0[iRow] = result0[iCol]; |
| 867 | + } |
| 868 | + dim = incumbent_model.hessian_.dim_; |
| 869 | + arg1.resize(dim); |
| 870 | + hessianProduct(incumbent_model.hessian_, arg1, result1); |
| 871 | + |
| 872 | + for (HighsInt iCol = 0; iCol < dim; iCol++) { |
| 873 | + REQUIRE(result0[iCol] == result1[iCol]); |
| 874 | + } |
| 875 | +} |
0 commit comments