or-tools 0.1.1 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|