raplan.milp
¶
Linear Programming approach to the scheduling problem.
LpBuilder
dataclass
¶
LpBuilder(
time_segments: int = 1,
duration_scale: float = 1.0,
window_fallback_fn: Callable[
[int | float], Window
] = new_immovable,
force_sync: bool = False,
movement_objective: int | float = 100,
dynamic_movement_objective: Callable[
[UUID, float], float | None
] = lambda _id, _delta: None,
concurrent_objective: int | float = -250,
pair_objective: Callable[
[frozenset[UUID]], int | float | None
] = lambda _: None,
pair_exclusions: Callable[
[frozenset[UUID]], bool
] = lambda _: False,
only_positive: bool = False,
silent: bool = True,
)
Linear programming problem builder.
Attributes:
| Name | Type | Description |
|---|---|---|
lp |
Highs
|
Linear programming model. |
time_segments |
int
|
How many segments to subdivide each 1.0 of time in. |
duration_scale |
float
|
Scale to convert task durations to the absolute time scale used in scheduling. |
window_fallback_fn |
Callable[[int | float], Window]
|
Fallback function for maintenance that does not have a defined window. |
force_sync |
bool
|
Forcefully synchronize starting points, disallowing overlapping tasks with disjunct starting segments. |
movement_objective |
int | float
|
Movement penalty (>0) for moving a maintenance item 1 time unit. The penalty per segment is calculated internally. |
dynamic_movement_objective |
Callable[[UUID, float], float | None]
|
A callable that may return a specific time movement objective contribution for a given maintenance ID and shift in absolute time w.r.t. the project horizon. It converts the segment delta to the maximum amount of time it represents. |
concurrent_objective |
int | float
|
Concurrent execution reward (<0) to the objective function for when a task is executed concurrently for 1 time unit. The reward per segment is calculated internally. |
pair_objective |
Callable[[frozenset[UUID]], int | float | None]
|
Method to retrieve the objective function contribution for pairing two given maintenance items per unit of time. Discourage with a positive value (i.e. penalty) or encourage with a negative value (i.e. savings). |
pair_exclusions |
Callable[[frozenset[UUID]], bool]
|
Method to retrieve whether a specific maintenance UUID pairing should be disabled to start together at all times. |
only_positive |
bool
|
Whether to only include tasks and windows that result in positive times. |
silent |
bool
|
Whether to silence the solver's output while running. |
get_duration_segments
¶
get_movement_objective
¶
Get the movement objective value for a maintenance ID that moves this amount with respect to its Window's initial value.
Source code in src/raplan/milp.py
get_time_segment
¶
get_window_range
¶
LpCombinations
dataclass
¶
LpCombinations(
builder: LpBuilder,
pairs: list[tuple[int, UUID, UUID, highs_var]],
pair_constraints: list[
tuple[int, UUID, UUID, highs_cons]
],
disjoint_constraints: list[
tuple[int, UUID, UUID, highs_cons]
],
exclusion_constraints: list[
tuple[int, UUID, UUID, highs_cons]
],
)
Bases: WithBuilder
Synchronization that either dictates a matching start or a disjoint sequence.
Attributes:
| Name | Type | Description |
|---|---|---|
pairs |
list[tuple[int, UUID, UUID, highs_var]]
|
Pairwise start combination variables. |
disjoint_constraints |
list[tuple[int, UUID, UUID, highs_cons]]
|
Constraints to disable disjoint starts for overlapping maintenance. |
exclusion_constraints |
list[tuple[int, UUID, UUID, highs_cons]]
|
Constraints to prevent two maintenance items to overlap completely. |
build
staticmethod
¶
build(
builder: LpBuilder,
items: dict[UUID, LpMoveableItem],
candidates: dict[int, list[tuple[UUID, highs_var]]],
) -> LpCombinations
Build a set of synchronization constraints that prohibit overlap without a common starting segment.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
builder
|
LpBuilder
|
MILP model builder. |
required |
items
|
dict[UUID, LpMoveableItem]
|
Mapping of unique maintenance identifiers to their MILP synchronization descriptions. |
required |
candidates
|
dict[int, list[tuple[UUID, highs_var]]]
|
Mapping of segment indices to lists of pairs of maintenance identifiers and the scheduling booleans that may lead to occupation of the given segment. |
required |
Source code in src/raplan/milp.py
get_value
¶
run
¶
solve
¶
LpMoveableItem
dataclass
¶
LpMoveableItem(
builder: LpBuilder,
uuid: UUID,
initial: int,
window: range,
duration: int,
segment_vars: list[highs_var],
picking_constraint: highs_cons,
)
Bases: WithBuilder
MILP description for a single maintenance item for the synchronization problem.
Attributes:
| Name | Type | Description |
|---|---|---|
uuid |
UUID
|
Identifier of the corresponding maintenance item. |
initial |
int
|
Initial segment. |
window |
range
|
Segment range where this maintenance should fall within. These may be negative, so you should not index using values taken from this window, but rather enumerate it. |
duration |
int
|
Duration as a number of segments. |
segment_vars |
list[highs_var]
|
Variables corresponding to the segment toggles. |
picking_constraint |
highs_cons
|
Constraint that states exactly one segment should be picked. |
build
staticmethod
¶
build(
builder: LpBuilder, item: Maintenance | Procedure
) -> LpMoveableItem
Build MILP description for a Maintenance instance.
Adds binary toggles for each segment the maintenance could be planned in, as well as a constraint that only one can be picked.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
builder
|
LpBuilder
|
MILP model builder. |
required |
item
|
Maintenance | Procedure
|
Maintenance instance. |
required |
Returns:
| Type | Description |
|---|---|
LpMoveableItem
|
Linear programming representation of the maintenance instance. |
Source code in src/raplan/milp.py
disable_tail_start
¶
disable_tail_start(
other: LpMoveableItem,
) -> Generator[
tuple[int, UUID, UUID, highs_cons], None, None
]
Disable starting any maintenance in the tail segments of this maintenance.
Yields:
| Type | Description |
|---|---|
tuple[int, UUID, UUID, highs_cons]
|
Tuple of segment index, own UUID, other UUID and a disabling constraint. |
Source code in src/raplan/milp.py
exclude
¶
exclude(
other: LpMoveableItem,
) -> Generator[
tuple[int, UUID, UUID, highs_cons], None, None
]
Disallows the other maintenance item to start during this item.
Yields:
| Type | Description |
|---|---|
tuple[int, UUID, UUID, highs_cons]
|
Tuple of a starting segment, this item's ID, disabled item's ID and constraint. |
Source code in src/raplan/milp.py
get_value
¶
picked_segment
¶
picked_segment() -> int
The currently picked segment for this maintenance instance.
Source code in src/raplan/milp.py
run
¶
solve
¶
LpProcedureProject
dataclass
¶
LpProcedureProject(
builder: LpBuilder,
uuid: UUID,
procedures: list[LpMoveableItem],
occupation: list[LpSegmentOccupation],
combinations: LpCombinations,
)
Bases: WithBuilder
MILP description of a procedure scheduling problem for a project.
Attributes:
uuid: Unique identifier for the original Project being modeled.
procedures: List of procedures whose schedule is being optimized.
constraints: List of constraints.
apply_to
¶
Apply rescheduled maintenance results to this project.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
project
|
Project
|
Project to reschedule maintenance for. |
required |
new
|
bool
|
Whether to create a new deepcopy of this project. |
True
|
Returns:
| Type | Description |
|---|---|
Project
|
Project with updated maintenance. |
Source code in src/raplan/milp.py
build
staticmethod
¶
build(
builder: LpBuilder, project: Project
) -> LpProcedureProject
Build a MILP description for a procedure planning project.
Constraints can be added using the instance methods on LpSyncProject itself.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
project
|
Project
|
Project to encode as a procedure optimization problem. |
required |
Returns:
| Type | Description |
|---|---|
LpProcedureProject
|
MILP description for a procedure planning project. |
Source code in src/raplan/milp.py
get_value
¶
run
¶
solve
¶
LpSegmentOccupation
dataclass
¶
LpSegmentOccupation(
builder: LpBuilder,
segment: int,
occupation: highs_var | Literal[0],
concurrent: highs_var,
is_occupied: highs_var,
occupation_constraint: highs_cons,
concurrent_constraint: highs_cons,
)
Bases: WithBuilder
Segment occupation within a system.
Attributes:
| Name | Type | Description |
|---|---|---|
segment |
int
|
Segment that is encoded. |
occupation |
highs_var | Literal[0]
|
Variable denoting the sum of tasks occupying this segment. |
concurrent |
highs_var
|
Variable denoting the concurrent number of tasks: occupation minus 1, but 0 when 0. |
is_occupied |
highs_var
|
Variable denoting whether this segment is occupied. |
occupied_constraint |
highs_var
|
Constraint binding occupation to the sum of members using a big-M constraint. |
concurrent_constraint |
highs_cons
|
Binding concurrency to occupation minus is_occupied. |
build
staticmethod
¶
build(
builder: LpBuilder,
segment: int,
candidates: list[tuple[UUID, highs_var]],
) -> LpSegmentOccupation
Build a MILP description for a segment where system maintenance may be planned in. This method ties the potential benefits of concurrent task execution during a segment to the start time of the candidate tasks.
Source code in src/raplan/milp.py
get_value
¶
run
¶
solve
¶
LpSyncComponent
dataclass
¶
LpSyncComponent(
builder: LpBuilder,
uuid: UUID,
maintenance: list[LpMoveableItem],
)
Bases: WithBuilder
MILP description of a single component for the synchronization problem.
Attributes:
| Name | Type | Description |
|---|---|---|
uuid |
UUID
|
Identifier of the corresponding component. |
maintenance |
list[LpMoveableItem]
|
List of maintenance descriptions. |
apply_to
¶
Apply rescheduled maintenance results to this component.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
component
|
Component
|
Component to reschedule maintenance for. |
required |
new
|
bool
|
Whether to create a new deepcopy of this component. |
True
|
Returns:
| Type | Description |
|---|---|
Component
|
Component with updated maintenance. |
Source code in src/raplan/milp.py
build
staticmethod
¶
build(
builder: LpBuilder, component: Component
) -> LpSyncComponent
Build a linear programming problem for a Component instance.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
component
|
Component
|
Component instance. |
required |
Source code in src/raplan/milp.py
gen_maintenance_segments
¶
Generate all allocated maintenance segments for this component.
gen_maintenance_times
¶
Generate all maintenance times for this component.
get_value
¶
run
¶
solve
¶
LpSyncProject
dataclass
¶
LpSyncProject(
builder: LpBuilder,
uuid: UUID,
systems: list[LpSyncSystem],
)
Bases: WithBuilder
MILP description of a project.
It is encoded in such a way to look for the synchronization of maintenance tasks applied to components in a system, such that coinciding tasks can then be grouped into procedures.
Attributes:
| Name | Type | Description |
|---|---|---|
uuid |
UUID
|
Unique identifier of the project that has been encoded. |
systems |
list[LpSyncSystem]
|
MILP descriptions of all partaking systems. |
apply_to
¶
Apply rescheduled maintenance results to this project.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
project
|
Project
|
Project to reschedule maintenance for. |
required |
new
|
bool
|
Whether to create a new deepcopy of this project. |
True
|
Returns:
| Type | Description |
|---|---|
Project
|
Project with updated maintenance. |
Source code in src/raplan/milp.py
build
staticmethod
¶
build(
builder: LpBuilder, project: Project
) -> LpSyncProject
Build a linear programming problem for a Project.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
project
|
Project
|
|
required |
Source code in src/raplan/milp.py
gen_maintenance_segments
¶
Generate all allocated maintenance segments for this project's systems and components.
Yields:
| Type | Description |
|---|---|
tuple[UUID, int]
|
A tuple of each maintenance UUID and allocated segment index. |
Source code in src/raplan/milp.py
gen_maintenance_times
¶
get_value
¶
run
¶
solve
¶
LpSyncSystem
dataclass
¶
LpSyncSystem(
builder: LpBuilder,
uuid: UUID,
components: list[LpSyncComponent],
occupation: list[LpSegmentOccupation],
combinations: LpCombinations,
)
Bases: WithBuilder
MILP description of component maintenance and synchronization for a system.
Attributes:
| Name | Type | Description |
|---|---|---|
uuid |
UUID
|
Unique identifier of the corresponding system. |
components |
list[LpSyncComponent]
|
Component descriptions. |
segments |
list[LpSyncComponent]
|
System segments. |
synchronization |
list[LpSyncComponent]
|
Synchronization constraints for tasks that prohibit overlapping tasks without a common start. |
apply_to
¶
Apply rescheduled maintenance results to this system.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
system
|
System
|
System to reschedule maintenance for. |
required |
new
|
bool
|
Whether to create a new deepcopy of this system. |
True
|
Returns:
| Type | Description |
|---|---|
System
|
System with updated maintenance. |
Source code in src/raplan/milp.py
build
staticmethod
¶
build(builder: LpBuilder, system: System) -> LpSyncSystem
Build a linear programming problem for a System instance.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
system
|
System
|
System instance. |
required |
Source code in src/raplan/milp.py
gen_maintenance_segments
¶
gen_maintenance_times
¶
get_value
¶
run
¶
solve
¶
WithBuilder
dataclass
¶
WithBuilder(builder: LpBuilder)
duration_segments
¶
mul_squared_num
¶
overlap
¶
Calculate the overlapping time for a set of durations.
I.e. the total sum minus the longest duration.
Source code in src/raplan/milp.py
shared_range
¶
time_segment
¶
Calculate the segment a given relative time would be in w.r.t. to the horizon start as (0.0).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
x
|
float
|
Time relative to the horizon start. |
required |
time_segments
|
int
|
Number of segments a unit (1.0) of time should be subdivided in during discrete analyses. |
required |
Returns:
| Type | Description |
|---|---|
int
|
Discrete segment corresponding to this time (rounded down). |
Source code in src/raplan/milp.py
window_segments
¶
Get the range discrete time segments representing this time window.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
window
|
Window
|
Window to convert into a range of segments. |
required |
time_segments
|
int
|
Number of segments a unit (1.0) of time should be subdivided in during discrete analyses. |
required |