|
10 | 10 | */ |
11 | 11 |
|
12 | 12 | #include "Highs.h" |
| 13 | +#include <tuple> |
13 | 14 |
|
14 | 15 | void HighsIis::invalidate() { |
15 | 16 | this->valid_ = false; |
@@ -533,3 +534,244 @@ HighsStatus HighsIis::compute(const HighsLp& lp, const HighsOptions& options, |
533 | 534 | this->strategy_ = options.iis_strategy; |
534 | 535 | return HighsStatus::kOk; |
535 | 536 | } |
| 537 | + |
| 538 | +bool HighsIis::rowValueBounds(const HighsLp& lp, const HighsOptions& options) { |
| 539 | + // Look for infeasible rows based on row value bounds |
| 540 | + this->invalidate(); |
| 541 | + std::vector<double> lower_value; |
| 542 | + std::vector<double> upper_value; |
| 543 | + if (lp.a_matrix_.isColwise()) { |
| 544 | + lower_value.assign(lp.num_row_, 0); |
| 545 | + upper_value.assign(lp.num_row_, 0); |
| 546 | + for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) { |
| 547 | + const double lower = lp.col_lower_[iCol]; |
| 548 | + const double upper = lp.col_upper_[iCol]; |
| 549 | + for (HighsInt iEl = lp.a_matrix_.start_[iCol]; iEl < lp.a_matrix_.start_[iCol+1]; iEl++) { |
| 550 | + HighsInt iRow = lp.a_matrix_.index_[iEl]; |
| 551 | + double value = lp.a_matrix_.value_[iEl]; |
| 552 | + if (value > 0) { |
| 553 | + lower_value[iRow] += value * lower; |
| 554 | + upper_value[iRow] += value * upper; |
| 555 | + } else { |
| 556 | + lower_value[iRow] += value * upper; |
| 557 | + upper_value[iRow] += value * lower; |
| 558 | + } |
| 559 | + } |
| 560 | + } |
| 561 | + } else { |
| 562 | + for (HighsInt iRow = 0; iRow < lp.num_row_; iRow++) { |
| 563 | + double lower_row_value = 0; |
| 564 | + double upper_row_value = 0; |
| 565 | + for (HighsInt iEl = lp.a_matrix_.start_[iRow]; iEl < lp.a_matrix_.start_[iRow+1]; iEl++) { |
| 566 | + HighsInt iCol = lp.a_matrix_.index_[iEl]; |
| 567 | + const double lower = lp.col_lower_[iCol]; |
| 568 | + const double upper = lp.col_upper_[iCol]; |
| 569 | + double value = lp.a_matrix_.value_[iEl]; |
| 570 | + if (value > 0) { |
| 571 | + lower_row_value += value * lower; |
| 572 | + upper_row_value += value * upper; |
| 573 | + } else { |
| 574 | + lower_row_value += value * upper; |
| 575 | + upper_row_value += value * lower; |
| 576 | + } |
| 577 | + } |
| 578 | + lower_value[iRow] = lower_row_value; |
| 579 | + upper_value[iRow] = upper_row_value; |
| 580 | + } |
| 581 | + } |
| 582 | + bool below_lower; |
| 583 | + bool above_upper; |
| 584 | + for (HighsInt iRow = 0; iRow < lp.num_row_; iRow++) { |
| 585 | + below_lower = upper_value[iRow] < lp.row_lower_[iRow] - options.primal_feasibility_tolerance; |
| 586 | + above_upper = lower_value[iRow] > lp.row_upper_[iRow] + options.primal_feasibility_tolerance; |
| 587 | + if (below_lower || above_upper) { |
| 588 | + this->row_index_.push_back(iRow); |
| 589 | + if (below_lower) { |
| 590 | + this->row_bound_.push_back(kIisBoundStatusLower); |
| 591 | + } else { |
| 592 | + this->row_bound_.push_back(kIisBoundStatusUpper); |
| 593 | + } |
| 594 | + break; |
| 595 | + } |
| 596 | + } |
| 597 | + if (this->row_index_.size() == 0) return false; |
| 598 | + assert(below_lower || above_upper); |
| 599 | + assert(!(below_lower && above_upper)); |
| 600 | + // Found an infeasible row |
| 601 | + HighsInt iRow = this->row_index_[0]; |
| 602 | + if (below_lower) { |
| 603 | + printf("Row %d has maximum row value of %g, below lower bound of %g\n", |
| 604 | + int(iRow), upper_value[iRow], lp.row_lower_[iRow]); |
| 605 | + } else { |
| 606 | + printf("Row %d has minimum row value of %g, above upper bound of %g\n", |
| 607 | + int(iRow), lower_value[iRow], lp.row_upper_[iRow]); |
| 608 | + } |
| 609 | + std::vector<std::tuple<double, HighsInt, double>> row_data; |
| 610 | + double activity = 0; |
| 611 | + // Lambda for adding row data tuples |
| 612 | + auto addRowData = [&](HighsInt iCol, double value) { |
| 613 | + double bound; |
| 614 | + if (below_lower) { |
| 615 | + if (value > 0) { |
| 616 | + bound = lp.col_upper_[iCol]; |
| 617 | + } else { |
| 618 | + bound = lp.col_lower_[iCol]; |
| 619 | + } |
| 620 | + } else { |
| 621 | + if (value > 0) { |
| 622 | + bound = lp.col_lower_[iCol]; |
| 623 | + } else { |
| 624 | + bound = lp.col_upper_[iCol]; |
| 625 | + } |
| 626 | + } |
| 627 | + double term = bound * value; |
| 628 | + activity += term; |
| 629 | + printf("addRowData: tuple(%g, %d, %g)\n", term, int(iCol), value); |
| 630 | + row_data.push_back(std::make_tuple(term, iCol, value)); |
| 631 | + }; |
| 632 | + if (lp.a_matrix_.isColwise()) { |
| 633 | + for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) { |
| 634 | + for (HighsInt iEl = lp.a_matrix_.start_[iCol]; iEl < lp.a_matrix_.start_[iCol+1]; iEl++) { |
| 635 | + if (lp.a_matrix_.index_[iEl] == iRow) |
| 636 | + addRowData(iCol, lp.a_matrix_.value_[iEl]); |
| 637 | + } |
| 638 | + } |
| 639 | + } else { |
| 640 | + for (HighsInt iEl = lp.a_matrix_.start_[iRow]; iEl < lp.a_matrix_.start_[iRow+1]; iEl++) |
| 641 | + addRowData(lp.a_matrix_.index_[iEl], lp.a_matrix_.value_[iEl]); |
| 642 | + } |
| 643 | + if (below_lower) { |
| 644 | + assert(std::fabs(activity-upper_value[iRow]) < 1e-5); |
| 645 | + } else { |
| 646 | + assert(std::fabs(activity-lower_value[iRow]) < 1e-5); |
| 647 | + } |
| 648 | + std::sort(row_data.begin(), row_data.end()); |
| 649 | + HighsInt row_data_size = row_data.size(); |
| 650 | + for (HighsInt iisCol = 0; iisCol < row_data_size; iisCol++) { |
| 651 | + |
| 652 | + printf("row_data: tuple(%g, %d, %g)\n", |
| 653 | + std::get<0>(row_data[iisCol]), |
| 654 | + int(std::get<1>(row_data[iisCol])), |
| 655 | + std::get<2>(row_data[iisCol])); |
| 656 | + } |
| 657 | + |
| 658 | + // Determine whether any entries can be removed |
| 659 | + if (below_lower) { |
| 660 | + const double lower = lp.row_lower_[iRow] - options.primal_feasibility_tolerance; |
| 661 | + // Loop through the terms from most positive to most negative |
| 662 | + // removing them from the activity, until it is within the bound, |
| 663 | + // then use that column and any others in the IIS |
| 664 | + for (HighsInt iisCol = row_data_size-1; iisCol >= 0; iisCol--) { |
| 665 | + const double term = std::get<0>(row_data[iisCol]); |
| 666 | + const HighsInt iCol = std::get<1>(row_data[iisCol]); |
| 667 | + const double value = std::get<2>(row_data[iisCol]); |
| 668 | + double activity_m_term = activity-term; |
| 669 | + printf("Deletion loop: iisCol = %2d; iCol = %3d; term = %11.4g; value = %11.4g; lower = %11.4g; activity = %11.4g; activity - term = %11.4g\n", |
| 670 | + int(iisCol), int(iCol), term, value, lower, activity, activity_m_term); |
| 671 | + assert(activity < lower); |
| 672 | + if (activity - term <= lower) { |
| 673 | + // Column not in IIS |
| 674 | + activity -= term; |
| 675 | + } else { |
| 676 | + // Column in IIS |
| 677 | + this->col_index_.push_back(iCol); |
| 678 | + double bound; |
| 679 | + if (value > 0) { |
| 680 | + this->col_bound_.push_back(kIisBoundStatusUpper); |
| 681 | + bound = lp.col_upper_[iCol]; |
| 682 | + } else { |
| 683 | + this->col_bound_.push_back(kIisBoundStatusLower); |
| 684 | + bound = lp.col_lower_[iCol]; |
| 685 | + } |
| 686 | + if (bound * value != term) { |
| 687 | + printf("Deletion loop %d: bound(%g) * value(%g) = %g != %g = term\n", |
| 688 | + int(iisCol), bound, value, bound * value, term); |
| 689 | + } |
| 690 | + assert(bound * value == term); |
| 691 | + } |
| 692 | + } |
| 693 | + } else { |
| 694 | + const double upper = lp.row_upper_[iRow] + options.primal_feasibility_tolerance; |
| 695 | + // Loop through the terms from most negative to most positive, |
| 696 | + // removing them from the activity, until it is within the bound, |
| 697 | + // then use that column and any others in the IIS |
| 698 | + for (HighsInt iisCol = 0; iisCol < row_data_size; iisCol++) { |
| 699 | + const double term = std::get<0>(row_data[iisCol]); |
| 700 | + const HighsInt iCol = std::get<1>(row_data[iisCol]); |
| 701 | + const double value = std::get<2>(row_data[iisCol]); |
| 702 | + printf("Deletion loop: iisCol = %2d; iCol = %3d; term = %11.4g; value = %11.4g; upper = %11.4g; activity = %11.4g; activity - term = %11.4g\n", |
| 703 | + int(iisCol), int(iCol), term, value, upper, activity, activity-term); |
| 704 | + assert(activity > upper); |
| 705 | + if (activity - term >= upper) { |
| 706 | + // Column not in IIS |
| 707 | + activity -= term; |
| 708 | + } else { |
| 709 | + // Column in IIS |
| 710 | + this->col_index_.push_back(iCol); |
| 711 | + double bound; |
| 712 | + if (value > 0) { |
| 713 | + this->col_bound_.push_back(kIisBoundStatusLower); |
| 714 | + bound = lp.col_lower_[iCol]; |
| 715 | + } else { |
| 716 | + this->col_bound_.push_back(kIisBoundStatusUpper); |
| 717 | + bound = lp.col_upper_[iCol]; |
| 718 | + } |
| 719 | + assert(bound * value == term); |
| 720 | + } |
| 721 | + } |
| 722 | + } |
| 723 | + // There must be at least one column in the IIS |
| 724 | + assert(this->col_index_.size() > 0); |
| 725 | + this->valid_ = true; |
| 726 | + return this->valid_; |
| 727 | +} |
| 728 | + |
| 729 | +bool HighsIis::ok(const HighsLp& lp, const HighsOptions& options) const { |
| 730 | + if (!this->valid_) return true; |
| 731 | + const HighsLogOptions& log_options = options.log_options; |
| 732 | + Highs h; |
| 733 | + h.passOptions(options); |
| 734 | + h.passModel(lp); |
| 735 | + h.run(); |
| 736 | + if (h.getModelStatus() != HighsModelStatus::kInfeasible) { |
| 737 | + highsLogUser(log_options, HighsLogType::kError, "HighsIis: Given LP is not infeasible\n"); |
| 738 | + return false; |
| 739 | + } |
| 740 | + auto optimalOrUnbounded = [&]() -> bool { |
| 741 | + h.writeModel(""); |
| 742 | + h.run(); |
| 743 | + return |
| 744 | + h.getModelStatus() == HighsModelStatus::kOptimal || |
| 745 | + h.getModelStatus() == HighsModelStatus::kUnbounded; |
| 746 | + }; |
| 747 | + HighsInt num_iis_col = this->col_index_.size(); |
| 748 | + HighsInt num_iis_row = this->row_index_.size(); |
| 749 | + for (HighsInt iisCol = 0; iisCol < num_iis_col; iisCol++) { |
| 750 | + HighsInt iCol = this->col_index_[iisCol]; |
| 751 | + if (this->col_bound_[iisCol] == kIisBoundStatusLower || |
| 752 | + this->col_bound_[iisCol] == kIisBoundStatusBoxed) { |
| 753 | + h.changeColBounds(iCol, -kHighsInf, lp.col_upper_[iCol]); |
| 754 | + if (!optimalOrUnbounded()) { |
| 755 | + highsLogUser(log_options, HighsLogType::kError, |
| 756 | + "HighsIis: IIS column %d (LP column %d): relaxing lower bound of %g does not yield LP with optimal or unbounded status\n", |
| 757 | + int(iisCol), int(iCol), lp.col_lower_[iCol]); |
| 758 | + return false; |
| 759 | + } |
| 760 | + h.changeColBounds(iCol, lp.col_lower_[iCol], lp.col_upper_[iCol]); |
| 761 | + } |
| 762 | + if (this->col_bound_[iisCol] == kIisBoundStatusUpper || |
| 763 | + this->col_bound_[iisCol] == kIisBoundStatusBoxed) { |
| 764 | + h.changeColBounds(iCol, lp.col_lower_[iCol], kHighsInf); |
| 765 | + if (!optimalOrUnbounded()) { |
| 766 | + highsLogUser(log_options, HighsLogType::kError, |
| 767 | + "HighsIis: IIS column %d (LP column %d): relaxing upper bound of %g does not yield LP with optimal or unbounded status\n", |
| 768 | + int(iisCol), int(iCol), lp.col_upper_[iCol]); |
| 769 | + return false; |
| 770 | + } |
| 771 | + h.changeColBounds(iCol, lp.col_lower_[iCol], lp.col_upper_[iCol]); |
| 772 | + } |
| 773 | + } |
| 774 | + return true; |
| 775 | + |
| 776 | + |
| 777 | +} |
0 commit comments