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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1c21f77bdb14226464938b4407e6399e8d60247b519df13ae3671643eef4ca47
4
- data.tar.gz: e056e127a5ffae6aba598210df051e7d5bee7484b1415415054464cc115a0742
3
+ metadata.gz: 3d0010ac9fa141621f67cee4eb00ba077a5bafcec58c65202d477c1576ba797e
4
+ data.tar.gz: '0328a9b9f0fa296df875e0756b40dc16fdbf756725d5ecc4e3bc23c600245974'
5
5
  SHA512:
6
- metadata.gz: aab9c9e218da811b49805587642e19432d3a5356eb3a5bb75aacea7f2ef8a4370ae8a2d7821d34f15a728a6c03710e50bd10f21f2531bb2309d4b9e953a9efbb
7
- data.tar.gz: 79cfe7ee894e35272d3cf0f09f936e66ced59d84b9c6ce2096434ac30887db4c95f6836b12387859f2e089efabea5cb9fa8bdee2d488b00a56da2f5328f6dab2
6
+ metadata.gz: dd66a479521af3f036ed14f639f957cd6b608efed729a4e7ed1eb5fde749fd779ec471750b93ab5d48e0162116e9865c0763355fd72b610a0b74cf6747dad220
7
+ data.tar.gz: 41af8a12b7aa110baae1f62029c4bde6e291ea1b870f9435ba3463a1a9e52d41dc069ef049671a95802d3942e29402f7096709c6a8fd659382a372923e03002a
@@ -1,3 +1,8 @@
1
+ ## 0.1.2 (2020-02-18)
2
+
3
+ - Added support for scheduling
4
+ - Added `lib_version` method
5
+
1
6
  ## 0.1.1 (2020-02-16)
2
7
 
3
8
  - Added `RoutingModel`
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)
@@ -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.AddTerm(from_ruby<operations_research::sat::BoolVar>(cvar[0]), from_ruby<int64>(cvar[1]));
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) {
@@ -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"
@@ -7,5 +7,9 @@ module ORTools
7
7
  @left = left
8
8
  @right = right
9
9
  end
10
+
11
+ def inspect
12
+ "#{left.inspect} #{operator} #{right.inspect}"
13
+ end
10
14
  end
11
15
  end
@@ -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 objective_value
13
- @response.objective_value
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
@@ -0,0 +1,9 @@
1
+ module ORTools
2
+ class CpSolverSolutionCallback
3
+ attr_writer :response
4
+
5
+ def value(expr)
6
+ @response.solution_boolean_value(expr)
7
+ end
8
+ end
9
+ end
Binary file
@@ -16,6 +16,10 @@ module ORTools
16
16
  add(other, -1)
17
17
  end
18
18
 
19
+ def inspect
20
+ vars.map { |v| v[0].is_a?(BoolVar) ? v[0].name : v[0].name + " * " + v[1] }.join(" + ")
21
+ end
22
+
19
23
  private
20
24
 
21
25
  def add(other, sign)
@@ -1,3 +1,3 @@
1
1
  module ORTools
2
- VERSION = "0.1.1"
2
+ VERSION = "0.1.2"
3
3
  end
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.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-16 00:00:00.000000000 Z
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