Class: ORTools::BasicScheduler
- Inherits:
-
Object
- Object
- ORTools::BasicScheduler
- Defined in:
- lib/or_tools/basic_scheduler.rb
Instance Attribute Summary collapse
-
#assigned_hours ⇒ Object
readonly
Returns the value of attribute assigned_hours.
-
#assignments ⇒ Object
readonly
Returns the value of attribute assignments.
Instance Method Summary collapse
-
#initialize(people:, shifts:) ⇒ BasicScheduler
constructor
for time blocks (shifts and availability) could also use time range (starts_at..ends_at) or starts_at + duration keep current format for now for flexibility.
- #total_hours ⇒ Object
Constructor Details
#initialize(people:, shifts:) ⇒ BasicScheduler
for time blocks (shifts and availability) could also use time range (starts_at..ends_at) or starts_at + duration keep current format for now for flexibility
8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 |
# File 'lib/or_tools/basic_scheduler.rb', line 8 def initialize(people:, shifts:) @shifts = shifts model = ORTools::CpModel.new # create variables # a person must be available for the entire shift to be considered for it vars = [] shifts.each_with_index do |shift, i| people.each_with_index do |person, j| if person[:availability].any? { |a| a[:starts_at] <= shift[:starts_at] && a[:ends_at] >= shift[:ends_at] } vars << {shift: i, person: j, var: model.new_bool_var("{shift: #{i}, person: #{j}}")} end end end vars_by_shift = vars.group_by { |v| v[:shift] } vars_by_person = vars.group_by { |v| v[:person] } # one person per shift vars_by_shift.each do |j, vs| model.add(model.sum(vs.map { |v| v[:var] }) <= 1) end # one shift per day per person # in future, may also want to add option to ensure assigned shifts are N hours apart vars_by_person.each do |j, vs| vs.group_by { |v| shift_dates[v[:shift]] }.each do |_, vs2| model.add(model.sum(vs2.map { |v| v[:var] }) <= 1) end end # max hours per person # use seconds since model needs integers vars_by_person.each do |j, vs| max_hours = people[j][:max_hours] if max_hours model.add(model.sum(vs.map { |v| v[:var] * shift_duration[v[:shift]] }) <= max_hours * 3600) end end # maximize hours assigned # could also include distance from max hours model.maximize(model.sum(vars.map { |v| v[:var] * shift_duration[v[:shift]] })) # solve solver = ORTools::CpSolver.new status = solver.solve(model) raise Error, "No solution found" unless [:feasible, :optimal].include?(status) # read solution @assignments = [] vars.each do |v| if solver.value(v[:var]) @assignments << { person: v[:person], shift: v[:shift] } end end # can calculate manually if objective changes @assigned_hours = solver.objective_value / 3600.0 end |
Instance Attribute Details
#assigned_hours ⇒ Object (readonly)
Returns the value of attribute assigned_hours.
3 4 5 |
# File 'lib/or_tools/basic_scheduler.rb', line 3 def assigned_hours @assigned_hours end |
#assignments ⇒ Object (readonly)
Returns the value of attribute assignments.
3 4 5 |
# File 'lib/or_tools/basic_scheduler.rb', line 3 def assignments @assignments end |
Instance Method Details
#total_hours ⇒ Object
72 73 74 |
# File 'lib/or_tools/basic_scheduler.rb', line 72 def total_hours @total_hours ||= shift_duration.sum / 3600.0 end |