Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 29 additions & 16 deletions ext/or-tools/routing.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,25 @@ namespace Rice::detail {
};
} // namespace Rice::detail

template<typename T>
operations_research::Constraint *make_constraint(operations_research::Solver &solver, operations_research::IntExpr *left, T right, const std::string &op) {
if (op == "==") {
return solver.MakeEquality(left, right);
} else if (op == "!=") {
return solver.MakeNonEquality(left, right);
} else if (op == "<") {
return solver.MakeLess(left, right);
} else if (op == "<=") {
return solver.MakeLessOrEqual(left, right);
} else if (op == ">") {
return solver.MakeGreater(left, right);
} else if (op == ">=") {
return solver.MakeGreaterOrEqual(left, right);
} else {
throw std::runtime_error{"Unknown operator"};
}
}

void init_routing(Rice::Module& m) {
auto rb_cRoutingSearchParameters = Rice::define_class_under<RoutingSearchParameters>(m, "RoutingSearchParameters");
auto rb_cIntVar = Rice::define_class_under<operations_research::IntVar>(m, "RoutingIntVar");
Expand Down Expand Up @@ -293,24 +312,18 @@ void init_routing(Rice::Module& m) {

Rice::define_class_under<operations_research::Solver>(m, "RoutingSolver")
.define_method(
"add",
[](operations_research::Solver& self, Object o) {
operations_research::Constraint* constraint;
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")));
std::string op = o.call("op").to_s().str();
if (op == "==") {
constraint = self.MakeEquality(left, right);
} else if (op == "<=") {
constraint = self.MakeLessOrEqual(left, right);
} else {
throw std::runtime_error{"Unknown operator"};
}
"add_constraint",
[](operations_research::Solver& self, operations_research::Constraint& constraint) {
self.AddConstraint(&constraint);
})
.define_method(
"_make_constraint",
[](operations_research::Solver& self, Object left, Object right, Symbol op) {
if (right.class_of().name() == "ORTools::Constant") {
return make_constraint(self, Rice::detail::From_Ruby<operations_research::IntVar*>().convert(left), Rice::detail::From_Ruby<int64_t>().convert(right.call("value")), op.str());
} else {
constraint = Rice::detail::From_Ruby<operations_research::Constraint*>().convert(o);
return make_constraint(self, Rice::detail::From_Ruby<operations_research::IntVar*>().convert(left), Rice::detail::From_Ruby<operations_research::IntVar*>().convert(right), op.str());
}
self.AddConstraint(constraint);
})
.define_method(
"fixed_duration_interval_var",
Expand Down
1 change: 1 addition & 0 deletions lib/or-tools.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
# routing
require_relative "or_tools/routing_index_manager"
require_relative "or_tools/routing_model"
require_relative "or_tools/routing_solver"

# higher level interfaces
require_relative "or_tools/basic_scheduler"
Expand Down
14 changes: 14 additions & 0 deletions lib/or_tools/routing_solver.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
module ORTools
class RoutingSolver
def add(comparison)
case comparison
when Comparison
add_constraint(_make_constraint(comparison.left, comparison.right, comparison.op))
when Constraint
add_constraint(comparison)
else
raise TypeError, "Not supported: RoutingSolver#add(#{comparison})"
end
end
end
end
112 changes: 112 additions & 0 deletions test/routing_constraints_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
require_relative "test_helper"

class RoutingConstraintsTest < Minitest::Test
def test_no_extra_constraints
build_routing
solve

assert_equal :success, @routing.status
assert_equal [0, 2, 1, 0], route
end

def test_var_less_than_or_equal_var
build_routing
@routing.solver.add(@distance_dimension.cumul_var(@manager.node_to_index(1)) <= @distance_dimension.cumul_var(@manager.node_to_index(2)))
solve

assert_equal :success, @routing.status
assert_equal [0, 1, 2, 0], route
end

def test_var_less_than_or_equal_const
build_routing
@routing.solver.add(@distance_dimension.cumul_var(@manager.node_to_index(1)) <= 2455)
solve

assert_equal :success, @routing.status
assert_equal [0, 1, 2, 0], route
end

def test_var_equal_const
build_routing
@routing.solver.add(@distance_dimension.cumul_var(@manager.node_to_index(1)) == 2451)
solve

assert_equal :success, @routing.status
assert_equal [0, 1, 2, 0], route
end

def test_var_equal_const_failure
build_routing
@routing.solver.add(@distance_dimension.cumul_var(@manager.node_to_index(1)) == 2455)
solve

assert_equal :fail, @routing.status
end

def test_var_not_equal_const
build_routing
@routing.solver.add(@distance_dimension.cumul_var(@manager.node_to_index(1)) != 2451)
solve

assert_equal :success, @routing.status
assert_equal [0, 2, 1, 0], route
end

def test_var_less_than_const
build_routing
@routing.solver.add(@distance_dimension.cumul_var(@manager.node_to_index(1)) < 2455)
solve

assert_equal :success, @routing.status
assert_equal [0, 1, 2, 0], route
end

def test_var_greater_than_const
build_routing
@routing.solver.add(@distance_dimension.cumul_var(@manager.node_to_index(1)) > 2455)
solve

assert_equal :success, @routing.status
assert_equal [0, 2, 1, 0], route
end

def test_var_greater_than_or_equal_const
build_routing
@routing.solver.add(@distance_dimension.cumul_var(@manager.node_to_index(1)) >= 2455)
solve

assert_equal :success, @routing.status
assert_equal [0, 2, 1, 0], route
end

private

def build_routing
@manager = ORTools::RoutingIndexManager.new(3, 1, 0)
@routing = ORTools::RoutingModel.new(@manager)
transit_callback_index = @routing.register_transit_matrix([
[0, 2451, 731],
[2451, 0, 1745],
[731, 1745, 0],
])
@routing.set_arc_cost_evaluator_of_all_vehicles(transit_callback_index)
@routing.add_dimension(transit_callback_index, 0, 10000, true, "Distance")
@distance_dimension = @routing.mutable_dimension("Distance")
end

def solve
@solution = @routing.solve(first_solution_strategy: :path_cheapest_arc)
end

def route
route = []
index = @routing.start(0)
while !@routing.end?(index)
route << @manager.index_to_node(index)
index = @solution.value(@routing.next_var(index))
end
route << @manager.index_to_node(index)
route
end
end