Skip to content
Merged
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
2 changes: 2 additions & 0 deletions api/v01/entities/vrp_input.rb
Original file line number Diff line number Diff line change
Expand Up @@ -347,8 +347,10 @@ module VrpMissions
optional(:timewindows, type: Array, desc: 'Time slot while the rest may begin. At most one timewindow per rest is supported.') do
use :vrp_request_timewindow
end
optional(:lapse, type: Integer, values: ->(v) { v.positive? }, desc: 'Repeating rest after this many seconds of work (travel + service). Exclusive with timewindows. Not available with periodic heuristic.', coerce_with: ->(value) { ScheduleType.type_cast(value) })
optional(:late_multiplier, type: Float, desc: 'Late multiplier applied for this rest')
optional(:exclusion_cost, type: Float, desc: 'Cost induced by non affectation of this rest')
mutually_exclusive :timewindows, :lapse
end

params :vrp_request_service do
Expand Down
2 changes: 2 additions & 0 deletions core/components/vehicle.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ module Vehicle
def adjust_vehicles_duration(vrp)
vrp.vehicles.select{ |v| v.duration? && !v.rests.empty? }.each{ |v|
v.rests.each{ |r|
next if Interpreters::RegulatoryRest.lapse_rest?(r)

v.duration += r.duration
}
}
Expand Down
22 changes: 22 additions & 0 deletions core/strategies/orchestration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,8 @@ def solve(service_vrp, job = nil, block = nil)
tic = Time.now

optim_solution = nil
periodic_heuristic_flag = false
regulatory_rest = Interpreters::RegulatoryRest.new

unfeasible_services = {}

Expand Down Expand Up @@ -177,6 +179,22 @@ def solve(service_vrp, job = nil, block = nil)
services_to_reinject << vrp.services.slice!(index)
end
}
if services_to_reinject.any?
excluded_ids = services_to_reinject.map(&:id)
id_summary =
if excluded_ids.size <= 20
excluded_ids.join(', ')
else
"#{excluded_ids.first(5).join(', ')}, ... (+#{excluded_ids.size - 5} more)"
end
log "Excluded #{excluded_ids.size} infeasible service(s) before #{service} solve " \
"(#{vrp.services.size} remaining): #{id_summary}",
level: :info
else
log "Infeasibility check: no services excluded before #{service} solve " \
"(#{vrp.services.size} services)",
level: :info
end

# vrp.periodic_heuristic check the first_solution_stategy which may change right after periodic heuristic
periodic_heuristic_flag = vrp.periodic_heuristic?
Expand All @@ -193,6 +211,7 @@ def solve(service_vrp, job = nil, block = nil)
end
end
end
regulatory_rest.apply!(vrp)
if vrp.configuration.resolution.solver && (!periodic_heuristic_flag || vrp.services.size < 200)
if vrp.configuration.preprocessing.cluster_threshold.to_f.positive?
block&.call(nil, nil, nil,
Expand Down Expand Up @@ -286,6 +305,7 @@ def solve(service_vrp, job = nil, block = nil)
optim_solution.configuration.csv = vrp.configuration.restitution.csv
optim_solution.configuration.geometry = vrp.configuration.restitution.geometry
optim_solution.unassigned_stops += unfeasible_services.values.flatten
regulatory_rest.patch_solution!(vrp, optim_solution)
Cleanse.cleanse(vrp, optim_solution)
optim_solution.parse(vrp)
if vrp.configuration.preprocessing.first_solution_strategy
Expand All @@ -297,6 +317,8 @@ def solve(service_vrp, job = nil, block = nil)

log "<-- optim_wrap::solve elapsed: #{(Time.now - tic).round(2)}sec", level: :debug
optim_solution
ensure
regulatory_rest&.rewind!(vrp)
end

def build_independent_vrps(vrp, skill_sets, vehicle_indices_by_skills, skill_service_ids)
Expand Down
19 changes: 18 additions & 1 deletion docs/Rest.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
# Rest

Inform about the drivers obligations to have some rest within a route
Inform about the drivers obligations to have some rest within a route.

A rest with `timewindows` is placed by the solver in that slot.

A rest with `lapse` is a repeating pause of `duration` seconds every `lapse` seconds of work (travel + service).
In the current implementation, travel and service times are inflated by `duration / lapse` for the solver (rounded up). Customer timewindow **starts** are delayed by the same rate from tour start, capped at `duration` (one pause); **ends** and vehicle amplitude stay unchanged. Discrete pauses are then reinserted: after the visit if the lapse is reached during service, before the next drive if travel would complete it (unless that misses the next stop's timewindow). Solvers never receive these rests.

```json
{
Expand Down Expand Up @@ -35,3 +40,15 @@ Inform about the drivers obligations to have some rest within a route
}]
}
```

Repeating regulatory pause (45 minutes every 6 hours of work):

```json
{
"rests": [{
"id": "Break-regulatory",
"duration": 2700,
"lapse": 21600
}]
}
```
Loading
Loading