or-tools 0.1.1 → 0.1.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +5 -0
- data/README.md +122 -1
- data/ext/or-tools/ext.cpp +46 -3
- data/lib/or-tools.rb +1 -0
- data/lib/or_tools/comparison.rb +4 -0
- data/lib/or_tools/cp_solver.rb +9 -2
- data/lib/or_tools/cp_solver_solution_callback.rb +9 -0
- data/lib/or_tools/ext.bundle +0 -0
- data/lib/or_tools/sat_linear_expr.rb +4 -0
- data/lib/or_tools/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3d0010ac9fa141621f67cee4eb00ba077a5bafcec58c65202d477c1576ba797e
|
4
|
+
data.tar.gz: '0328a9b9f0fa296df875e0756b40dc16fdbf756725d5ecc4e3bc23c600245974'
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: dd66a479521af3f036ed14f639f957cd6b608efed729a4e7ed1eb5fde749fd779ec471750b93ab5d48e0162116e9865c0763355fd72b610a0b74cf6747dad220
|
7
|
+
data.tar.gz: 41af8a12b7aa110baae1f62029c4bde6e291ea1b870f9435ba3463a1a9e52d41dc069ef049671a95802d3942e29402f7096709c6a8fd659382a372923e03002a
|
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
@@ -31,7 +31,7 @@ Constraint Optimization
|
|
31
31
|
|
32
32
|
Integer Optimization
|
33
33
|
|
34
|
-
- [Mixed-Integer Programming](mixed-integer-programming)
|
34
|
+
- [Mixed-Integer Programming](#mixed-integer-programming)
|
35
35
|
|
36
36
|
Routing
|
37
37
|
|
@@ -56,6 +56,10 @@ Assignment
|
|
56
56
|
- [Assignment as a Min Cost Problem](#assignment-as-a-min-cost-problem)
|
57
57
|
- [Assignment as a MIP Problem](#assignment-as-a-mip-problem)
|
58
58
|
|
59
|
+
Scheduling
|
60
|
+
|
61
|
+
- [Employee Scheduling](#employee-scheduling)
|
62
|
+
|
59
63
|
### The Glop Linear Solver
|
60
64
|
|
61
65
|
[Guide](https://developers.google.com/optimization/lp/glop)
|
@@ -964,6 +968,123 @@ puts
|
|
964
968
|
puts "Time = #{solver.wall_time} milliseconds"
|
965
969
|
```
|
966
970
|
|
971
|
+
## Employee Scheduling
|
972
|
+
|
973
|
+
[Guide](https://developers.google.com/optimization/scheduling/employee_scheduling)
|
974
|
+
|
975
|
+
Define the data
|
976
|
+
|
977
|
+
```ruby
|
978
|
+
num_nurses = 4
|
979
|
+
num_shifts = 3
|
980
|
+
num_days = 3
|
981
|
+
all_nurses = num_nurses.times.to_a
|
982
|
+
all_shifts = num_shifts.times.to_a
|
983
|
+
all_days = num_days.times.to_a
|
984
|
+
```
|
985
|
+
|
986
|
+
Create the variables
|
987
|
+
|
988
|
+
```ruby
|
989
|
+
model = ORTools::CpModel.new
|
990
|
+
|
991
|
+
shifts = {}
|
992
|
+
all_nurses.each do |n|
|
993
|
+
all_days.each do |d|
|
994
|
+
all_shifts.each do |s|
|
995
|
+
shifts[[n, d, s]] = model.new_bool_var("shift_n%id%is%i" % [n, d, s])
|
996
|
+
end
|
997
|
+
end
|
998
|
+
end
|
999
|
+
```
|
1000
|
+
|
1001
|
+
Assign nurses to shifts
|
1002
|
+
|
1003
|
+
```ruby
|
1004
|
+
all_days.each do |d|
|
1005
|
+
all_shifts.each do |s|
|
1006
|
+
model.add(model.sum(all_nurses.map { |n| shifts[[n, d, s]] }) == 1)
|
1007
|
+
end
|
1008
|
+
end
|
1009
|
+
|
1010
|
+
all_nurses.each do |n|
|
1011
|
+
all_days.each do |d|
|
1012
|
+
model.add(model.sum(all_shifts.map { |s| shifts[[n, d, s]] }) <= 1)
|
1013
|
+
end
|
1014
|
+
end
|
1015
|
+
```
|
1016
|
+
|
1017
|
+
Assign shifts evenly
|
1018
|
+
|
1019
|
+
```ruby
|
1020
|
+
min_shifts_per_nurse = (num_shifts * num_days) / num_nurses
|
1021
|
+
max_shifts_per_nurse = min_shifts_per_nurse + 1
|
1022
|
+
all_nurses.each do |n|
|
1023
|
+
num_shifts_worked = model.sum(all_days.flat_map { |d| all_shifts.map { |s| shifts[[n, d, s]] } })
|
1024
|
+
model.add(num_shifts_worked >= min_shifts_per_nurse)
|
1025
|
+
model.add(num_shifts_worked <= max_shifts_per_nurse)
|
1026
|
+
end
|
1027
|
+
```
|
1028
|
+
|
1029
|
+
Create a printer
|
1030
|
+
|
1031
|
+
```ruby
|
1032
|
+
class NursesPartialSolutionPrinter < ORTools::CpSolverSolutionCallback
|
1033
|
+
attr_reader :solution_count
|
1034
|
+
|
1035
|
+
def initialize(shifts, num_nurses, num_days, num_shifts, sols)
|
1036
|
+
super()
|
1037
|
+
@shifts = shifts
|
1038
|
+
@num_nurses = num_nurses
|
1039
|
+
@num_days = num_days
|
1040
|
+
@num_shifts = num_shifts
|
1041
|
+
@solutions = sols
|
1042
|
+
@solution_count = 0
|
1043
|
+
end
|
1044
|
+
|
1045
|
+
def on_solution_callback
|
1046
|
+
if @solutions.include?(@solution_count)
|
1047
|
+
puts "Solution #{@solution_count}"
|
1048
|
+
@num_days.times do |d|
|
1049
|
+
puts "Day #{d}"
|
1050
|
+
@num_nurses.times do |n|
|
1051
|
+
working = false
|
1052
|
+
@num_shifts.times do |s|
|
1053
|
+
if value(@shifts[[n, d, s]])
|
1054
|
+
working = true
|
1055
|
+
puts " Nurse %i works shift %i" % [n, s]
|
1056
|
+
end
|
1057
|
+
end
|
1058
|
+
unless working
|
1059
|
+
puts " Nurse #{n} does not work"
|
1060
|
+
end
|
1061
|
+
end
|
1062
|
+
end
|
1063
|
+
puts
|
1064
|
+
end
|
1065
|
+
@solution_count += 1
|
1066
|
+
end
|
1067
|
+
end
|
1068
|
+
```
|
1069
|
+
|
1070
|
+
Call the solver and display the results
|
1071
|
+
|
1072
|
+
```ruby
|
1073
|
+
solver = ORTools::CpSolver.new
|
1074
|
+
a_few_solutions = 5.times.to_a
|
1075
|
+
solution_printer = NursesPartialSolutionPrinter.new(
|
1076
|
+
shifts, num_nurses, num_days, num_shifts, a_few_solutions
|
1077
|
+
)
|
1078
|
+
solver.search_for_all_solutions(model, solution_printer)
|
1079
|
+
|
1080
|
+
puts
|
1081
|
+
puts "Statistics"
|
1082
|
+
puts " - conflicts : %i" % solver.num_conflicts
|
1083
|
+
puts " - branches : %i" % solver.num_branches
|
1084
|
+
puts " - wall time : %f s" % solver.wall_time
|
1085
|
+
puts " - solutions found : %i" % solution_printer.solution_count
|
1086
|
+
```
|
1087
|
+
|
967
1088
|
## History
|
968
1089
|
|
969
1090
|
View the [changelog](https://github.com/ankane/or-tools/blob/master/CHANGELOG.md)
|
data/ext/or-tools/ext.cpp
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
// or-tools
|
2
2
|
#include <ortools/algorithms/knapsack_solver.h>
|
3
|
+
#include <ortools/base/version.h>
|
3
4
|
#include <ortools/constraint_solver/routing.h>
|
4
5
|
#include <ortools/constraint_solver/routing_parameters.h>
|
5
6
|
#include <ortools/graph/assignment.h>
|
@@ -14,6 +15,7 @@
|
|
14
15
|
#include <rice/Constructor.hpp>
|
15
16
|
#include <rice/Hash.hpp>
|
16
17
|
#include <rice/Module.hpp>
|
18
|
+
#include <rice/String.hpp>
|
17
19
|
#include <rice/Symbol.hpp>
|
18
20
|
|
19
21
|
using operations_research::ArcIndex;
|
@@ -43,6 +45,8 @@ using operations_research::sat::BoolVar;
|
|
43
45
|
using operations_research::sat::CpModelBuilder;
|
44
46
|
using operations_research::sat::CpSolverResponse;
|
45
47
|
using operations_research::sat::CpSolverStatus;
|
48
|
+
using operations_research::sat::NewFeasibleSolutionObserver;
|
49
|
+
using operations_research::sat::SatParameters;
|
46
50
|
using operations_research::sat::SolutionIntegerValue;
|
47
51
|
|
48
52
|
using Rice::Array;
|
@@ -51,6 +55,7 @@ using Rice::Constructor;
|
|
51
55
|
using Rice::Hash;
|
52
56
|
using Rice::Module;
|
53
57
|
using Rice::Object;
|
58
|
+
using Rice::String;
|
54
59
|
using Rice::Symbol;
|
55
60
|
using Rice::define_module;
|
56
61
|
using Rice::define_class_under;
|
@@ -111,7 +116,7 @@ operations_research::sat::LinearExpr from_ruby<operations_research::sat::LinearE
|
|
111
116
|
// TODO clean up
|
112
117
|
Object o = cvar[0];
|
113
118
|
if (((Rice::String) o.call("class").call("name")).str() == "ORTools::BoolVar") {
|
114
|
-
expr.
|
119
|
+
expr.AddVar(from_ruby<operations_research::sat::BoolVar>(cvar[0]));
|
115
120
|
} else {
|
116
121
|
expr.AddTerm(from_ruby<operations_research::sat::IntVar>(cvar[0]), from_ruby<int64>(cvar[1]));
|
117
122
|
}
|
@@ -183,7 +188,13 @@ extern "C"
|
|
183
188
|
void Init_ext()
|
184
189
|
{
|
185
190
|
Module rb_mORTools = define_module("ORTools")
|
186
|
-
.define_singleton_method("default_routing_search_parameters", &DefaultRoutingSearchParameters)
|
191
|
+
.define_singleton_method("default_routing_search_parameters", &DefaultRoutingSearchParameters)
|
192
|
+
.define_singleton_method(
|
193
|
+
"lib_version",
|
194
|
+
*[]() {
|
195
|
+
return std::to_string(operations_research::OrToolsMajorVersion()) + "."
|
196
|
+
+ std::to_string(operations_research::OrToolsMinorVersion());
|
197
|
+
});
|
187
198
|
|
188
199
|
define_class_under<RoutingSearchParameters>(rb_mORTools, "RoutingSearchParameters")
|
189
200
|
.define_method(
|
@@ -392,7 +403,15 @@ void Init_ext()
|
|
392
403
|
|
393
404
|
// not to be confused with operations_research::IntVar
|
394
405
|
define_class_under<operations_research::sat::IntVar>(rb_mORTools, "SatIntVar");
|
395
|
-
define_class_under<BoolVar>(rb_mORTools, "BoolVar")
|
406
|
+
define_class_under<BoolVar>(rb_mORTools, "BoolVar")
|
407
|
+
.define_method("name", &BoolVar::Name)
|
408
|
+
.define_method("index", &BoolVar::index)
|
409
|
+
.define_method(
|
410
|
+
"inspect",
|
411
|
+
*[](BoolVar& self) {
|
412
|
+
String name(self.Name());
|
413
|
+
return "#<ORTools::BoolVar @name=" + name.inspect().str() + ">";
|
414
|
+
});
|
396
415
|
|
397
416
|
define_class_under<CpModelBuilder>(rb_mORTools, "CpModel")
|
398
417
|
.define_constructor(Constructor<CpModelBuilder>())
|
@@ -449,6 +468,25 @@ void Init_ext()
|
|
449
468
|
});
|
450
469
|
|
451
470
|
define_class_under(rb_mORTools, "CpSolver")
|
471
|
+
.define_method(
|
472
|
+
"_solve_with_observer",
|
473
|
+
*[](Object self, CpModelBuilder& model, Object callback) {
|
474
|
+
operations_research::sat::Model m;
|
475
|
+
|
476
|
+
// set parameters for SearchForAllSolutions
|
477
|
+
SatParameters parameters;
|
478
|
+
parameters.set_enumerate_all_solutions(true);
|
479
|
+
m.Add(NewSatParameters(parameters));
|
480
|
+
|
481
|
+
m.Add(NewFeasibleSolutionObserver(
|
482
|
+
[callback](const CpSolverResponse& r) {
|
483
|
+
// TODO find a better way to do this
|
484
|
+
callback.call("response=", r);
|
485
|
+
callback.call("on_solution_callback");
|
486
|
+
})
|
487
|
+
);
|
488
|
+
return SolveCpModel(model.Build(), &m);
|
489
|
+
})
|
452
490
|
.define_method(
|
453
491
|
"_solve",
|
454
492
|
*[](Object self, CpModelBuilder& model) {
|
@@ -462,6 +500,11 @@ void Init_ext()
|
|
462
500
|
|
463
501
|
define_class_under<CpSolverResponse>(rb_mORTools, "CpSolverResponse")
|
464
502
|
.define_method("objective_value", &CpSolverResponse::objective_value)
|
503
|
+
.define_method("num_conflicts", &CpSolverResponse::num_conflicts)
|
504
|
+
.define_method("num_branches", &CpSolverResponse::num_branches)
|
505
|
+
.define_method("wall_time", &CpSolverResponse::wall_time)
|
506
|
+
// .define_method("solution_integer_value", &SolutionIntegerValue)
|
507
|
+
.define_method("solution_boolean_value", &operations_research::sat::SolutionBooleanValue)
|
465
508
|
.define_method(
|
466
509
|
"status",
|
467
510
|
*[](CpSolverResponse& self) {
|
data/lib/or-tools.rb
CHANGED
@@ -6,6 +6,7 @@ require "or_tools/comparison"
|
|
6
6
|
require "or_tools/comparison_operators"
|
7
7
|
require "or_tools/cp_model"
|
8
8
|
require "or_tools/cp_solver"
|
9
|
+
require "or_tools/cp_solver_solution_callback"
|
9
10
|
require "or_tools/knapsack_solver"
|
10
11
|
require "or_tools/linear_expr"
|
11
12
|
require "or_tools/routing_model"
|
data/lib/or_tools/comparison.rb
CHANGED
data/lib/or_tools/cp_solver.rb
CHANGED
@@ -1,5 +1,11 @@
|
|
1
|
+
require "forwardable"
|
2
|
+
|
1
3
|
module ORTools
|
2
4
|
class CpSolver
|
5
|
+
extend Forwardable
|
6
|
+
|
7
|
+
def_delegators :@response, :objective_value, :num_conflicts, :num_branches, :wall_time
|
8
|
+
|
3
9
|
def solve(model)
|
4
10
|
@response = _solve(model)
|
5
11
|
@response.status
|
@@ -9,8 +15,9 @@ module ORTools
|
|
9
15
|
_solution_integer_value(@response, var)
|
10
16
|
end
|
11
17
|
|
12
|
-
def
|
13
|
-
@response
|
18
|
+
def search_for_all_solutions(model, observer)
|
19
|
+
@response = _solve_with_observer(model, observer)
|
20
|
+
@response.status
|
14
21
|
end
|
15
22
|
end
|
16
23
|
end
|
data/lib/or_tools/ext.bundle
CHANGED
Binary file
|
data/lib/or_tools/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: or-tools
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andrew Kane
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-02-
|
11
|
+
date: 2020-02-18 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rice
|
@@ -98,6 +98,7 @@ files:
|
|
98
98
|
- lib/or_tools/comparison_operators.rb
|
99
99
|
- lib/or_tools/cp_model.rb
|
100
100
|
- lib/or_tools/cp_solver.rb
|
101
|
+
- lib/or_tools/cp_solver_solution_callback.rb
|
101
102
|
- lib/or_tools/ext.bundle
|
102
103
|
- lib/or_tools/knapsack_solver.rb
|
103
104
|
- lib/or_tools/linear_expr.rb
|