add activity query for model_builder

This commit is contained in:
Laurent Perron
2023-03-03 12:12:37 +04:00
parent 228d434873
commit c957fd9798
10 changed files with 91 additions and 4 deletions

View File

@@ -98,7 +98,7 @@ public final class ModelSolver {
return helper.getBestObjectiveBound();
}
/** Checks that the solver has found a solution, and value of the given variable. */
/** Checks that the solver has found a solution, and returns the value of the given variable. */
public double getValue(Variable var) {
if (!helper.hasSolution()) {
throw new ModelSolverException(
@@ -106,7 +106,10 @@ public final class ModelSolver {
}
return helper.getVariableValue(var.getIndex());
}
/** Checks that the solver has found a solution, and reduced cost of the given variable. */
/**
* Checks that the solver has found a solution, and returns the reduced cost of the given
* variable.
*/
public double getReducedCost(Variable var) {
if (!helper.hasSolution()) {
throw new ModelSolverException(
@@ -115,7 +118,9 @@ public final class ModelSolver {
return helper.getReducedCost(var.getIndex());
}
/** Checks that the solver has found a solution, and dual value of the given constraint. */
/**
* Checks that the solver has found a solution, and returns the ual value of the given constraint.
*/
public double getDualValue(LinearConstraint ct) {
if (!helper.hasSolution()) {
throw new ModelSolverException(
@@ -124,6 +129,17 @@ public final class ModelSolver {
return helper.getDualValue(ct.getIndex());
}
/**
* Checks that the solver has found a solution, and returns the activity of the given constraint.
*/
public double getActivity(LinearConstraint ct) {
if (!helper.hasSolution()) {
throw new ModelSolverException(
"ModelSolver.getActivity())", "solve() was not called or no solution was found");
}
return helper.getActivity(ct.getIndex());
}
/** Sets the log callback for the solver. */
public void setLogCallback(Consumer<String> cb) {
this.logCallback = cb;

View File

@@ -14,6 +14,8 @@
# Description: java wrapping of the C++ code at ../
load("//bazel:swig_java.bzl", "ortools_java_wrap_cc")
load("@rules_jvm_external//:defs.bzl", "artifact")
load("@contrib_rules_jvm//java:defs.bzl", "java_junit5_test")
ortools_java_wrap_cc(
name = "modelbuilder",
@@ -30,3 +32,20 @@ ortools_java_wrap_cc(
"//ortools/linear_solver/wrappers:model_builder_helper",
],
)
java_junit5_test(
name = "ModelBuilderTest",
srcs = ["ModelBuilderTest.java"],
test_class = "com.google.ortools.modelbuilder.ModelBuilderTest",
deps = [
"//ortools/java/com/google/ortools:Loader",
"//ortools/java/com/google/ortools/modelbuilder",
"//ortools/linear_solver/java:modelbuilder",
"@maven//:com_google_truth_truth",
artifact("org.junit.jupiter:junit-jupiter-api"),
artifact("org.junit.jupiter:junit-jupiter-params"),
artifact("org.junit.jupiter:junit-jupiter-engine"),
artifact("org.junit.platform:junit-platform-launcher"),
artifact("org.junit.platform:junit-platform-reporting"),
],
)

View File

@@ -84,6 +84,10 @@ public final class ModelBuilderTest {
.isWithin(1e-5)
.of(4.0 - 1.0 * solver.getDualValue(c0) - 5.0 * solver.getDualValue(c1));
assertThat(solver.getActivity(c0)).isWithin(1e-5).of(100.0);
assertThat(solver.getActivity(c1)).isWithin(1e-5).of(600.0);
assertThat(solver.getActivity(c2)).isWithin(1e-5).of(200.0);
assertThat(model.exportToLpString(false)).contains("minimal_linear_example");
assertThat(model.exportToMpsString(false)).contains("minimal_linear_example");
}

View File

@@ -168,6 +168,7 @@ class GlobalRefGuard {
%rename (getVariableValue) operations_research::ModelSolverHelper::variable_value;
%rename (getReducedCost) operations_research::ModelSolverHelper::reduced_cost;
%rename (getDualValue) operations_research::ModelSolverHelper::dual_value;
%rename (getActivity) operations_research::ModelSolverHelper::activity;
%rename (getStatusString) operations_research::ModelSolverHelper::status_string;
%rename (getWallTime) operations_research::ModelSolverHelper::wall_time;
%rename (getUserTime) operations_research::ModelSolverHelper::user_time;

View File

@@ -694,7 +694,7 @@ def dot_variable_container(
if len(container.shape) != 1:
raise ValueError(
'dot_variable_container only supports 1D variable containers (shape ='
f' {container.shape}')
f' {container.shape})')
indices: npt.NDArray[np.int32] = container.variable_indices
if np.isscalar(arg):
return _WeightedSum(
@@ -1317,6 +1317,11 @@ class ModelSolver:
self.__check_has_feasible_solution()
return self.__solve_helper.dual_value(ct.index)
def activity(self, ct: LinearConstraint) -> np.double:
"""Returns the activity of a linear constraint after solve."""
self.__check_has_feasible_solution()
return self.__solve_helper.activity(ct.index)
@property
def objective_value(self) -> np.double:
"""Returns the value of the objective after solve."""

View File

@@ -97,6 +97,16 @@ class ModelBuilderTest(unittest.TestCase):
solver.reduced_cost(x3),
places=self.NUM_PLACES)
self.assertAlmostEqual(100.0,
solver.activity(c0),
places=self.NUM_PLACES)
self.assertAlmostEqual(600.0,
solver.activity(c1),
places=self.NUM_PLACES)
self.assertAlmostEqual(200.0,
solver.activity(c2),
places=self.NUM_PLACES)
self.assertIn('minimal_linear_example',
model.export_to_lp_string(False))
self.assertIn('minimal_linear_example',

View File

@@ -388,6 +388,7 @@ PYBIND11_MODULE(pywrap_model_builder_helper, m) {
.def("var_value", &ModelSolverHelper::variable_value, arg("var_index"))
.def("reduced_cost", &ModelSolverHelper::reduced_cost, arg("var_index"))
.def("dual_value", &ModelSolverHelper::dual_value, arg("ct_index"))
.def("activity", &ModelSolverHelper::activity, arg("ct_index"))
.def("variable_values",
[](const ModelSolverHelper& helper) {
if (!helper.has_response()) {

View File

@@ -13,7 +13,9 @@
#include "ortools/linear_solver/wrappers/model_builder_helper.h"
#include <cmath>
#include <functional>
#include <limits>
#include <optional>
#include <string>
#include <utility>
@@ -340,6 +342,12 @@ void ModelSolverHelper::Solve(const ModelBuilderHelper& model) {
MPSolverResponseStatus::MPSOLVER_SOLVER_TYPE_UNAVAILABLE);
}
}
if (response_->status() == MPSOLVER_OPTIMAL ||
response_->status() == MPSOLVER_FEASIBLE) {
model_of_last_solve_ = &model.model();
activities_.assign(model.num_constraints(),
std::numeric_limits<double>::quiet_NaN());
}
}
void ModelSolverHelper::SetLogCallback(
@@ -410,6 +418,25 @@ double ModelSolverHelper::dual_value(int ct_index) const {
return response_.value().dual_value(ct_index);
}
double ModelSolverHelper::activity(int ct_index) {
if (!has_response() || ct_index >= activities_.size() ||
!model_of_last_solve_.has_value()) {
return 0.0;
}
if (std::isnan(activities_[ct_index])) {
const MPConstraintProto& ct_proto =
model_of_last_solve_.value()->constraint(ct_index);
double result = 0.0;
for (int i = 0; i < ct_proto.var_index_size(); ++i) {
result += response_->variable_value(ct_proto.var_index(i)) *
ct_proto.coefficient(i);
}
activities_[ct_index] = result;
}
return activities_[ct_index];
}
std::string ModelSolverHelper::status_string() const {
if (!has_response()) return "";
return response_.value().status_str();

View File

@@ -160,6 +160,7 @@ class ModelSolverHelper {
double variable_value(int var_index) const;
double reduced_cost(int var_index) const;
double dual_value(int ct_index) const;
double activity(int ct_index);
std::string status_string() const;
double wall_time() const;
@@ -180,6 +181,8 @@ class ModelSolverHelper {
std::optional<MPModelRequest::SolverType> solver_type_;
std::optional<double> time_limit_in_second_;
std::string solver_specific_parameters_;
std::optional<const MPModelProto*> model_of_last_solve_;
std::vector<double> activities_;
bool solver_output_ = false;
};

View File

@@ -34,6 +34,7 @@
#include <cstring>
#include <memory>
#include <string>
#include <utility>
#include "absl/strings/str_format.h"
#include "ortools/base/integral_types.h"