Skip to content

Commit

Permalink
Added experimental support for MathOpt, and unified expression buildi…
Browse files Browse the repository at this point in the history
…ng across linear, constraint, routing, and MathOpt
  • Loading branch information
ankane committed Oct 6, 2024
1 parent 525820a commit f75fc13
Show file tree
Hide file tree
Showing 27 changed files with 488 additions and 416 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 0.14.0 (unreleased)

- Added experimental support for `MathOpt`

## 0.13.1 (2024-10-05)

- Added binary installation for Debian 12
Expand Down
35 changes: 13 additions & 22 deletions ext/or-tools/constraint.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,32 +44,23 @@ namespace Rice::detail
public:
LinearExpr convert(VALUE v)
{
Object x(v);
LinearExpr expr;

if (x.respond_to("to_i")) {
expr = From_Ruby<int64_t>().convert(x.call("to_i").value());
} else if (x.respond_to("vars")) {
Array vars = x.call("vars");
for (const auto& v : vars) {
// TODO clean up
auto cvar = (Array) v;
Object var = cvar[0];
auto coeff = From_Ruby<int64_t>().convert(cvar[1].value());
Rice::Object utils = Rice::define_module("ORTools").const_get("Utils");

if (var.is_a(rb_cBoolVar)) {
expr += From_Ruby<BoolVar>().convert(var.value()) * coeff;
} else if (var.is_a(rb_cInteger)) {
expr += From_Ruby<int64_t>().convert(var.value()) * coeff;
} else {
expr += From_Ruby<IntVar>().convert(var.value()) * coeff;
}
}
} else {
if (x.is_a(rb_cBoolVar)) {
expr = From_Ruby<BoolVar>().convert(x.value());
Object x(v);
Rice::Hash coeffs = utils.call("index_expression", x);

for (const auto& entry : coeffs) {
Object var = entry.key;
auto coeff = From_Ruby<int64_t>().convert(entry.value.value());

if (var.is_nil()) {
expr += coeff;
} else if (var.is_a(rb_cBoolVar)) {
expr += From_Ruby<BoolVar>().convert(var.value()) * coeff;
} else {
expr = From_Ruby<IntVar>().convert(x.value());
expr += From_Ruby<IntVar>().convert(var.value()) * coeff;
}
}

Expand Down
66 changes: 61 additions & 5 deletions ext/or-tools/math_opt.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,77 @@

#include "ext.h"

using operations_research::math_opt::BoundedLinearExpression;
using operations_research::math_opt::LinearConstraint;
using operations_research::math_opt::Model;
using operations_research::math_opt::Solve;
using operations_research::math_opt::SolveArguments;
using operations_research::math_opt::SolveResult;
using operations_research::math_opt::SolverType;
using operations_research::math_opt::Variable;

void init_math_opt(Rice::Module& m) {
auto mathopt = Rice::define_module_under(m, "MathOpt");

Rice::define_class_under<Variable>(mathopt, "Variable");
Rice::define_class_under<Variable>(mathopt, "Variable")
.define_method("id", &Variable::id)
.define_method(
"_eql?",
[](Variable& self, Variable &other) {
return (bool) (self == other);
});

Rice::define_class_under<LinearConstraint>(mathopt, "LinearConstraint");

Rice::define_class_under<SolveResult>(mathopt, "SolveResult")
.define_method(
"objective_value",
[](SolveResult& self) {
return self.objective_value();
})
.define_method(
"variable_values",
[](SolveResult& self) {
Rice::Hash map;
for (auto& [k, v] : self.variable_values()) {
map[k] = v;
}
return map;
});

Rice::define_class_under<Model>(mathopt, "Model")
.define_constructor(Rice::Constructor<Model, std::string>())
.define_method("add_variable", &Model::AddContinuousVariable)
.define_method(
"add_linear_constraint",
[](Model& self, const BoundedLinearExpression& bounded_expr, const std::string& name) {
self.AddLinearConstraint(bounded_expr, name);
"_add_linear_constraint",
[](Model& self) {
return self.AddLinearConstraint();
})
.define_method(
"_set_upper_bound",
[](Model& self, LinearConstraint constraint, double upper_bound) {
self.set_upper_bound(constraint, upper_bound);
})
.define_method("_set_coefficient", &Model::set_coefficient)
.define_method(
"_set_objective_coefficient",
[](Model& self, Variable variable, double value) {
self.set_objective_coefficient(variable, value);
})
.define_method("_clear_objective", &Model::clear_objective)
.define_method(
"_set_objective_offset",
[](Model& self, double value) {
self.set_objective_offset(value);
})
.define_method(
"_set_maximize",
[](Model& self) {
self.set_maximize();
})
.define_method(
"_solve",
[](Model& self) {
SolveArguments args;
return *Solve(self, SolverType::kGlop, args);
});
}
2 changes: 1 addition & 1 deletion ext/or-tools/routing.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,7 @@ void init_routing(Rice::Module& m) {
if (o.respond_to("left")) {
operations_research::IntExpr* left(Rice::detail::From_Ruby<operations_research::IntVar*>().convert(o.call("left")));
operations_research::IntExpr* right(Rice::detail::From_Ruby<operations_research::IntVar*>().convert(o.call("right")));
auto op = o.call("operator").to_s().str();
auto op = o.call("op").to_s().str();
if (op == "==") {
constraint = self.MakeEquality(left, right);
} else if (op == "<=") {
Expand Down
27 changes: 12 additions & 15 deletions lib/or-tools.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,45 +2,42 @@
require "or_tools/ext"

# expressions
require_relative "or_tools/expression"
require_relative "or_tools/comparison"
require_relative "or_tools/comparison_operators"
require_relative "or_tools/constant"
require_relative "or_tools/product"
require_relative "or_tools/variable"

# bin packing
require_relative "or_tools/knapsack_solver"

# constraint
require_relative "or_tools/bool_var"
require_relative "or_tools/cp_model"
require_relative "or_tools/cp_solver"
require_relative "or_tools/cp_solver_solution_callback"
require_relative "or_tools/sat_int_var"
require_relative "or_tools/sat_linear_expr"
require_relative "or_tools/objective_solution_printer"
require_relative "or_tools/var_array_solution_printer"
require_relative "or_tools/var_array_and_objective_solution_printer"

# linear
require_relative "or_tools/linear_expr"
require_relative "or_tools/constant"
require_relative "or_tools/mp_variable"
require_relative "or_tools/linear_constraint"
require_relative "or_tools/product_cst"
require_relative "or_tools/solver"

# math opt
require_relative "or_tools/math_opt/model"
require_relative "or_tools/math_opt/variable"

# routing
require_relative "or_tools/int_var"
require_relative "or_tools/routing_index_manager"
require_relative "or_tools/routing_model"

# solution printers
require_relative "or_tools/objective_solution_printer"
require_relative "or_tools/var_array_solution_printer"
require_relative "or_tools/var_array_and_objective_solution_printer"

# higher level interfaces
require_relative "or_tools/basic_scheduler"
require_relative "or_tools/seating"
require_relative "or_tools/sudoku"
require_relative "or_tools/tsp"

# modules
require_relative "or_tools/utils"
require_relative "or_tools/version"

module ORTools
Expand Down
9 changes: 0 additions & 9 deletions lib/or_tools/bool_var.rb

This file was deleted.

17 changes: 7 additions & 10 deletions lib/or_tools/comparison.rb
Original file line number Diff line number Diff line change
@@ -1,19 +1,16 @@
module ORTools
class Comparison
attr_reader :operator, :left, :right
attr_reader :left, :op, :right

def initialize(operator, left, right)
@operator = operator
@left = left
@right = right
end

def to_s
"#{left} #{operator} #{right}"
def initialize(left, op, right)
@left = Expression.to_expression(left)
@op = op
@right = Expression.to_expression(right)
end

def inspect
"#<#{self.class.name} #{to_s}>"
"#{@left.inspect} #{@op} #{@right.inspect}"
end
alias_method :to_s, :inspect
end
end
9 changes: 0 additions & 9 deletions lib/or_tools/comparison_operators.rb

This file was deleted.

29 changes: 16 additions & 13 deletions lib/or_tools/constant.rb
Original file line number Diff line number Diff line change
@@ -1,23 +1,26 @@
module ORTools
class Constant < LinearExpr
def initialize(val)
@val = val
class Constant < Expression
attr_reader :value

def initialize(value)
@value = value
end

def to_s
@val.to_s
# simplify Ruby sum
def +(other)
@value == 0 ? other : super
end

def add_self_to_coeff_map_or_stack(coeffs, multiplier, stack)
coeffs[OFFSET_KEY] += @val * multiplier
def inspect
@value.to_s
end
end

class FakeMPVariableRepresentingTheConstantOffset
def solution_value
1
def -@
Constant.new(-value)
end
end

OFFSET_KEY = FakeMPVariableRepresentingTheConstantOffset.new
def vars
@vars ||= []
end
end
end
16 changes: 8 additions & 8 deletions lib/or_tools/cp_model.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,18 @@ def add(comparison)
case comparison
when Comparison
method_name =
case comparison.operator
when "=="
case comparison.op
when :==
:add_equality
when "!="
when :!=
:add_not_equal
when ">"
when :>
:add_greater_than
when ">="
when :>=
:add_greater_or_equal
when "<"
when :<
:add_less_than
when "<="
when :<=
:add_less_or_equal
else
raise ArgumentError, "Unknown operator: #{comparison.operator}"
Expand All @@ -32,7 +32,7 @@ def add(comparison)
end

def sum(arr)
arr.sum(SatLinearExpr.new)
Expression.new(arr)
end

def inspect
Expand Down
Loading

0 comments on commit f75fc13

Please sign in to comment.