Skip to content

Commit e3bf6ff

Browse files
authored
Merge pull request #51 from sdelannoypavy/solene
Maintenance benchmark problem
2 parents 64226f5 + 9894b43 commit e3bf6ff

File tree

14 files changed

+777
-16
lines changed

14 files changed

+777
-16
lines changed

Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name = "DecisionFocusedLearningBenchmarks"
22
uuid = "2fbe496a-299b-4c81-bab5-c44dfc55cf20"
3-
authors = ["Members of JuliaDecisionFocusedLearning"]
43
version = "0.4.0"
4+
authors = ["Members of JuliaDecisionFocusedLearning"]
55

66
[workspace]
77
projects = ["docs", "test"]

docs/src/api.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,18 @@ Modules = [DecisionFocusedLearningBenchmarks.FixedSizeShortestPath]
7272
Public = false
7373
```
7474

75+
## Maintenance
76+
77+
```@autodocs
78+
Modules = [DecisionFocusedLearningBenchmarks.Maintenance]
79+
Private = false
80+
```
81+
82+
```@autodocs
83+
Modules = [DecisionFocusedLearningBenchmarks.Maintenance]
84+
Public = false
85+
```
86+
7587
## Portfolio Optimization
7688

7789
```@autodocs

docs/src/benchmarks/maintenance.md

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
# Maintenance problem with resource constraint
2+
3+
The Maintenance problem with resource constraint is a sequential decision-making benchmark where an agent must repeatedly decide which components to maintain over time. The goal is to minimize total expected cost while accounting for independent degradation of components and limited maintenance capacity.
4+
5+
6+
## Problem Description
7+
8+
### Overview
9+
10+
In this benchmark, a system consists of $N$ identical components, each of which can degrade over $n$ discrete states. State $1$ means that the component is new, state $n$ means that the component is failed. At each time step, the agent can maintain up to $K$ components.
11+
12+
This forms an endogenous multistage stochastic optimization problem, where the agent must plan maintenance actions over the horizon.
13+
14+
### Mathematical Formulation
15+
16+
The maintenance problem can be formulated as a finite-horizon Markov Decision Process (MDP) with the following components:
17+
18+
**State Space** $\mathcal{S}$: At time step $t$, the state $s_t \in [1:n]^N$ is the degradation state for each component.
19+
20+
**Action Space** $\mathcal{A}$: The action at time $t$ is the set of components that are maintained at time $t$:
21+
```math
22+
a_t \subseteq \{1, 2, \ldots, N\} \text{ such that } |a_t| \leq K
23+
```
24+
### Transition Dynamics
25+
26+
The state transitions depend on whether a component is maintained or not:
27+
28+
For each component \(i\) at time \(t\):
29+
30+
- **Maintained component** (\(i \in a_t\)):
31+
32+
\[
33+
s_{t+1}^i = 1 \quad \text{(perfect maintenance)}
34+
\]
35+
36+
- **Unmaintained component** (\(i \notin a_t\)):
37+
38+
\[
39+
s_{t+1}^i =
40+
\begin{cases}
41+
\min(s_t^i + 1, n) & \text{with probability } p,\\
42+
s_t^i & \text{with probability } 1-p.
43+
\end{cases}
44+
\]
45+
46+
Here, \(p\) is the degradation probability, \(s_t^i\) is the current state of component \(i\), and \(n\) is the maximum (failed) state.
47+
48+
---
49+
50+
### Cost Function
51+
52+
The immediate cost at time \(t\) is:
53+
54+
$$
55+
c(s_t, a_t) = \Big( c_m \cdot |a_t| + c_f \cdot \#\{ i : s_t^i = n \} \Big)
56+
$$
57+
58+
Where:
59+
60+
- $c_m$ is the maintenance cost per component.
61+
- $|a_t|$ is the number of components maintained.
62+
- $c_f$ is the failure cost per failed component.
63+
- $\#\{ i : s_t^i = n \}$ counts the number of components in the failed state.
64+
65+
This formulation captures the total cost for maintaining components and penalizing failures.
66+
67+
**Objective**: Find a policy $\pi: \mathcal{S} \to \mathcal{A}$ that minimizes the expected cumulative cost:
68+
```math
69+
\min_\pi \mathbb{E}\left[\sum_{t=1}^T c(s_t, \pi(s_t)) \right]
70+
```
71+
72+
**Terminal Condition**: The episode terminates after $T$ time steps, with no terminal reward.
73+
74+
## Key Components
75+
76+
### [`MaintenanceBenchmark`](@ref)
77+
78+
The main benchmark configuration with the following parameters:
79+
80+
- `N`: number of components (default: 2)
81+
- `K`: maximum number of components that can be maintained simultaneously (default: 1)
82+
- `n`: number of degradation states per component (default: 3)
83+
- `p`: degradation probability (default: 0.2)
84+
- `c_f`: failure cost (default: 10.0)
85+
- `c_m`: maintenance cost (default: 3.0)
86+
- `max_steps`: Number of time steps per episode (default: 80)
87+
88+
### Instance Generation
89+
90+
Each problem instance includes:
91+
92+
- **Starting State**: Random starting degradation state in $[1,n]$ for each components.
93+
94+
### Environment Dynamics
95+
96+
The environment tracks:
97+
- Current time step
98+
- Current degradation state.
99+
100+
**State Observation**: Agents observe a normalized feature vector containing the degradation state of each component.
101+
102+
## Benchmark Policies
103+
104+
### Greedy Policy
105+
106+
Greedy policy that maintains components in the last two degradation states, up to the maintenance capacity. This provides a simple baseline.
107+

docs/src/index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ Single-stage optimization problems under uncertainty:
6161
Multi-stage sequential decision-making problems:
6262
- [`DynamicVehicleSchedulingBenchmark`](@ref): multi-stage vehicle scheduling under customer uncertainty
6363
- [`DynamicAssortmentBenchmark`](@ref): sequential product assortment selection with endogenous uncertainty
64+
- [`MaintenanceBenchmark`](@ref): maintenance problem with resource constraint
6465

6566
## Getting Started
6667

src/DecisionFocusedLearningBenchmarks.jl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ include("PortfolioOptimization/PortfolioOptimization.jl")
5757
include("StochasticVehicleScheduling/StochasticVehicleScheduling.jl")
5858
include("DynamicVehicleScheduling/DynamicVehicleScheduling.jl")
5959
include("DynamicAssortment/DynamicAssortment.jl")
60+
include("Maintenance/Maintenance.jl")
6061

6162
using .Utils
6263

@@ -89,6 +90,7 @@ using .PortfolioOptimization
8990
using .StochasticVehicleScheduling
9091
using .DynamicVehicleScheduling
9192
using .DynamicAssortment
93+
using .Maintenance
9294

9395
export Argmax2DBenchmark
9496
export ArgmaxBenchmark
@@ -100,5 +102,6 @@ export RankingBenchmark
100102
export StochasticVehicleSchedulingBenchmark
101103
export SubsetSelectionBenchmark
102104
export WarcraftBenchmark
105+
export MaintenanceBenchmark
103106

104107
end # module DecisionFocusedLearningBenchmarks

src/DynamicAssortment/environment.jl

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ Environment for the dynamic assortment problem.
77
$TYPEDFIELDS
88
"""
99
@kwdef mutable struct Environment{I<:Instance,R<:AbstractRNG,S<:Union{Nothing,Int}} <:
10-
Utils.AbstractEnvironment
10+
AbstractEnvironment
1111
"associated instance"
1212
instance::I
1313
"current step"
@@ -197,16 +197,25 @@ Features observed by the agent at current step, as a concatenation of:
197197
- change in hype and saturation features from the starting state
198198
- normalized current step (divided by max steps and multiplied by 10)
199199
All features are normalized by dividing by 10.
200+
201+
State
202+
Return as a tuple:
203+
- `env.features`: the current feature matrix (feature vector for all items).
204+
- `env.purchase_history`: the purchase history over the most recent steps.
200205
"""
201206
function Utils.observe(env::Environment)
202207
delta_features = env.features[2:3, :] .- env.instance.starting_hype_and_saturation
203-
return vcat(
204-
env.features,
205-
env.d_features,
206-
delta_features,
207-
ones(1, item_count(env)) .* (env.step / max_steps(env) * 10),
208-
) ./ 10,
209-
nothing
208+
features =
209+
vcat(
210+
env.features,
211+
env.d_features,
212+
delta_features,
213+
ones(1, item_count(env)) .* (env.step / max_steps(env) * 10),
214+
) ./ 10
215+
216+
state = (copy(env.features), copy(env.purchase_history))
217+
218+
return features, state
210219
end
211220

212221
"""

src/Maintenance/Maintenance.jl

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
module Maintenance
2+
3+
using ..Utils
4+
5+
using DocStringExtensions: TYPEDEF, TYPEDFIELDS, TYPEDSIGNATURES, SIGNATURES
6+
using Distributions: Uniform, Categorical
7+
using Flux: Chain, Dense
8+
using LinearAlgebra: dot
9+
using Random: Random, AbstractRNG, MersenneTwister
10+
using Statistics: mean
11+
12+
using Combinatorics: combinations
13+
14+
"""
15+
$TYPEDEF
16+
17+
Benchmark for a standard maintenance problem with resource constraints.
18+
Components are identical and degrade independently over time.
19+
A high cost is incurred for each component that reaches the final degradation level.
20+
A cost is also incurred for maintaining a component.
21+
The number of simultaneous maintenance operations is limited by a maintenance capacity constraint.
22+
23+
# Fields
24+
$TYPEDFIELDS
25+
26+
"""
27+
struct MaintenanceBenchmark <: AbstractDynamicBenchmark{true}
28+
"number of components"
29+
N::Int
30+
"maximum number of components that can be maintained simultaneously"
31+
K::Int
32+
"number of degradation states per component"
33+
n::Int
34+
"degradation probability"
35+
p::Float64
36+
"failure cost"
37+
c_f::Float64
38+
"maintenance cost"
39+
c_m::Float64
40+
"number of steps per episode"
41+
max_steps::Int
42+
43+
function MaintenanceBenchmark(N, K, n, p, c_f, c_m, max_steps)
44+
@assert K <= N "number of maintained components $K > number of components $N"
45+
@assert K >= 0 && N >= 0 "number of components should be positive"
46+
@assert 0 <= p <= 1 "degradation probability $p is not in [0, 1]"
47+
return new(N, K, n, p, c_f, c_m, max_steps)
48+
end
49+
end
50+
51+
"""
52+
MaintenanceBenchmark(;
53+
N=2,
54+
K=1,
55+
n=3,
56+
p=0.2
57+
c_f=10.0,
58+
c_m=3.0,
59+
max_steps=80,
60+
)
61+
62+
Constructor for [`MaintenanceBenchmark`](@ref).
63+
By default, the benchmark has 2 components, maintenance capacity 1, number of degradation levels 3,
64+
degradation probability 0.2, failure cost 10.0, maintenance cost 3.0, 80 steps per episode, and is exogenous.
65+
"""
66+
function MaintenanceBenchmark(; N=2, K=1, n=3, p=0.2, c_f=10.0, c_m=3.0, max_steps=80)
67+
return MaintenanceBenchmark(N, K, n, p, c_f, c_m, max_steps)
68+
end
69+
70+
# Accessor functions
71+
component_count(b::MaintenanceBenchmark) = b.N
72+
maintenance_capacity(b::MaintenanceBenchmark) = b.K
73+
degradation_levels(b::MaintenanceBenchmark) = b.n
74+
degradation_probability(b::MaintenanceBenchmark) = b.p
75+
failure_cost(b::MaintenanceBenchmark) = b.c_f
76+
maintenance_cost(b::MaintenanceBenchmark) = b.c_m
77+
max_steps(b::MaintenanceBenchmark) = b.max_steps
78+
79+
include("instance.jl")
80+
include("environment.jl")
81+
include("policies.jl")
82+
include("maximizer.jl")
83+
84+
"""
85+
$TYPEDSIGNATURES
86+
87+
Outputs a data sample containing an [`Instance`](@ref).
88+
"""
89+
function Utils.generate_sample(b::MaintenanceBenchmark, rng::AbstractRNG)
90+
return DataSample(; instance=Instance(b, rng))
91+
end
92+
93+
"""
94+
$TYPEDSIGNATURES
95+
96+
Generates a statistical model for the maintenance benchmark.
97+
The model is a small neural network with one hidden layer no activation function.
98+
"""
99+
function Utils.generate_statistical_model(b::MaintenanceBenchmark; seed=nothing)
100+
Random.seed!(seed)
101+
N = component_count(b)
102+
return Chain(Dense(N => N), Dense(N => N), vec)
103+
end
104+
105+
"""
106+
$TYPEDSIGNATURES
107+
108+
Outputs a top k maximizer, with k being the maintenance capacity of the benchmark.
109+
"""
110+
function Utils.generate_maximizer(b::MaintenanceBenchmark)
111+
return TopKPositiveMaximizer(maintenance_capacity(b))
112+
end
113+
114+
"""
115+
$TYPEDSIGNATURES
116+
117+
Creates an [`Environment`](@ref) from an [`Instance`](@ref) of the maintenance benchmark.
118+
The seed of the environment is randomly generated using the provided random number generator.
119+
"""
120+
function Utils.generate_environment(
121+
::MaintenanceBenchmark, instance::Instance, rng::AbstractRNG; kwargs...
122+
)
123+
seed = rand(rng, 1:typemax(Int))
124+
return Environment(instance; seed)
125+
end
126+
127+
"""
128+
$TYPEDSIGNATURES
129+
130+
Returns two policies for the dynamic assortment benchmark:
131+
- `Greedy`: maintains components when they are in the last state before failure, up to the maintenance capacity
132+
"""
133+
function Utils.generate_policies(::MaintenanceBenchmark)
134+
greedy = Policy(
135+
"Greedy",
136+
"policy that maintains components when they are in the last state before failure, up to the maintenance capacity",
137+
greedy_policy,
138+
)
139+
return (greedy,)
140+
end
141+
142+
export MaintenanceBenchmark
143+
144+
end

0 commit comments

Comments
 (0)