Skip to content

Commit 676b98b

Browse files
authored
Merge pull request #55 from JuliaDecisionFocusedLearning/cleanup
Global cleanup
2 parents ebc0af1 + b3a6a11 commit 676b98b

File tree

16 files changed

+93
-205
lines changed

16 files changed

+93
-205
lines changed

docs/src/custom_benchmarks.md

Lines changed: 2 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ AbstractBenchmark
1818
|------|----------|
1919
| `AbstractBenchmark` | Static, single-stage optimization (e.g. shortest path, portfolio) |
2020
| `AbstractStochasticBenchmark{true}` | Single-stage with exogenous uncertainty (scenarios drawn independently of decisions) |
21-
| `AbstractStochasticBenchmark{false}` | Single-stage with endogenous uncertainty (not yet used) |
21+
| `AbstractStochasticBenchmark{false}` | Single-stage with endogenous uncertainty |
2222
| `AbstractDynamicBenchmark{true}` | Multi-stage sequential decisions with exogenous uncertainty |
2323
| `AbstractDynamicBenchmark{false}` | Multi-stage sequential decisions with endogenous uncertainty |
2424

@@ -90,14 +90,9 @@ generate_instance(bench::MyStochasticBenchmark, rng::AbstractRNG; kwargs...) ->
9090

9191
# Draw one scenario given the instance encoded in context
9292
generate_scenario(bench::MyStochasticBenchmark, rng::AbstractRNG; context...) -> scenario
93-
# Note: sample.context is spread as kwargs when called by the framework
93+
# Note: sample.context is spread as kwargs when called
9494
```
9595

96-
The framework `generate_sample` calls `generate_instance`, draws `nb_scenarios`
97-
scenarios via `generate_scenario`, then:
98-
- If `target_policy` is provided: calls `target_policy(sample, scenarios) -> Vector{DataSample}`.
99-
- Otherwise: returns unlabeled samples with `extra=(; scenario=ξ)` for each scenario.
100-
10196
#### Anticipative solver (optional)
10297

10398
```julia
@@ -189,89 +184,3 @@ DataSample(; x=feat, y=nothing, instance=inst, extra=(; scenario=ξ))
189184
```
190185

191186
Keys must not appear in both `context` and `extra`, the constructor raises an error.
192-
193-
---
194-
195-
## Small examples
196-
197-
### Static benchmark
198-
199-
```julia
200-
using DecisionFocusedLearningBenchmarks
201-
const DFLBenchmarks = DecisionFocusedLearningBenchmarks
202-
203-
struct MyStaticBenchmark <: AbstractBenchmark end
204-
205-
function DFLBenchmarks.generate_instance(bench::MyStaticBenchmark, rng::AbstractRNG; kwargs...)
206-
instance = build_my_instance(rng)
207-
x = compute_features(instance)
208-
return DataSample(; x=x, instance=instance) # y = nothing
209-
end
210-
211-
212-
DFLBenchmarks.generate_statistical_model(bench::MyStaticBenchmark; seed=nothing) =
213-
Chain(Dense(10 => 32, relu), Dense(32 => 5))
214-
215-
DFLBenchmarks.generate_maximizer(bench::MyStaticBenchmark) =
216-
(θ; instance, kwargs...) -> solve_my_problem(θ, instance)
217-
```
218-
219-
### Stochastic benchmark
220-
221-
```julia
222-
223-
struct MyStochasticBenchmark <: AbstractStochasticBenchmark{true} end
224-
225-
function DFLBenchmarks.generate_instance(bench::MyStochasticBenchmark, rng::AbstractRNG; kwargs...)
226-
instance = build_my_instance(rng)
227-
x = compute_features(instance)
228-
return DataSample(; x=x, instance=instance)
229-
end
230-
231-
function DFLBenchmarks.generate_scenario(bench::MyStochasticBenchmark, rng::AbstractRNG; instance, kwargs...)
232-
return sample_scenario(instance, rng)
233-
end
234-
235-
DFLBenchmarks.generate_anticipative_solver(bench::MyStochasticBenchmark) =
236-
(scenario; instance, kwargs...) -> solve_with_scenario(instance, scenario)
237-
```
238-
239-
### Dynamic benchmark
240-
241-
```julia
242-
struct MyDynamicBenchmark <: AbstractDynamicBenchmark{true} end
243-
244-
mutable struct MyEnv <: AbstractEnvironment
245-
const instance::MyInstance
246-
const seed::Int
247-
state::MyState
248-
end
249-
250-
DFLBenchmarks.get_seed(env::MyEnv) = env.seed
251-
DFLBenchmarks.reset!(env::MyEnv; reset_rng=true, seed=env.seed) = (env.state = initial_state(env.instance))
252-
DFLBenchmarks.observe(env::MyEnv) = (env.state, nothing)
253-
DFLBenchmarks.step!(env::MyEnv, action) = apply_action!(env.state, action)
254-
DFLBenchmarks.is_terminated(env::MyEnv) = env.state.done
255-
256-
function DFLBenchmarks.generate_environment(bench::MyDynamicBenchmark, rng::AbstractRNG; kwargs...)
257-
inst = build_my_instance(rng)
258-
seed = rand(rng, Int)
259-
return MyEnv(inst, seed, initial_state(inst))
260-
end
261-
262-
function DFLBenchmarks.generate_baseline_policies(bench::MyDynamicBenchmark)
263-
greedy = function(env)
264-
samples = DataSample[]
265-
reset!(env)
266-
while !is_terminated(env)
267-
obs, _ = observe(env)
268-
x = compute_features(obs)
269-
y = greedy_action(obs)
270-
r = step!(env, y)
271-
push!(samples, DataSample(; x=x, y=y, instance=obs, extra=(; reward=r)))
272-
end
273-
return samples
274-
end
275-
return (; greedy)
276-
end
277-
```

docs/src/index.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -24,20 +24,20 @@ x \;\longrightarrow\; \boxed{\,\text{Statistical model } \varphi_w\,}
2424
```
2525

2626
Where:
27-
- **Statistical model** $\varphi_w$: machine learning predictor (e.g., neural network)
28-
- **CO algorithm** $f$: combinatorial optimization solver
2927
- **Instance** $x$: input data (e.g., features, context)
28+
- **Statistical model** $\varphi_w$: machine learning predictor (e.g., neural network)
3029
- **Parameters** $\theta$: predicted parameters for the optimization problem solved by `f`
30+
- **CO algorithm** $f$: combinatorial optimization solver
3131
- **Solution** $y$: output decision/solution
3232

3333
## Package Overview
3434

35-
**DecisionFocusedLearningBenchmarks.jl** provides a comprehensive collection of benchmark problems for evaluating decision-focused learning algorithms. The package offers:
35+
**DecisionFocusedLearningBenchmarks.jl** provides a collection of benchmark problems for evaluating decision-focused learning algorithms. The package offers:
3636

37-
- **Standardized benchmark problems** spanning diverse application domains
38-
- **Common interfaces** for creating datasets, statistical models, and optimization algorithms
39-
- **Ready-to-use DFL policies** compatible with [InferOpt.jl](https://github.com/JuliaDecisionFocusedLearning/InferOpt.jl) and the whole [JuliaDecisionFocusedLearning](https://github.com/JuliaDecisionFocusedLearning) ecosystem
40-
- **Evaluation tools** for comparing algorithm performance
37+
- **Collection of benchmark problems** spanning diverse applications
38+
- **Common tools** for creating datasets, statistical models, and optimization algorithms
39+
- **Generic interface** for building custom benchmarks
40+
- Compatibility with [InferOpt.jl](https://github.com/JuliaDecisionFocusedLearning/InferOpt.jl) and the whole [JuliaDecisionFocusedLearning](https://github.com/JuliaDecisionFocusedLearning) ecosystem
4141

4242
## Benchmark Categories
4343

docs/src/using_benchmarks.md

Lines changed: 50 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,42 @@
11
# Using Benchmarks
22

3-
This guide covers everything you need to work with existing benchmarks in
4-
DecisionFocusedLearningBenchmarks.jl: generating datasets, assembling DFL pipeline
5-
components, and evaluating results.
3+
This guide covers everything you need to work with existing benchmarks in DecisionFocusedLearningBenchmarks.jl: generating datasets, assembling DFL pipeline components, applying algorithms, and evaluating results.
4+
5+
---
6+
7+
## What is a benchmark?
8+
9+
A benchmark bundles a problem family (an instance generator, a combinatorial solver, and a statistical model architecture) into a single object. It provides everything needed to run a Decision-Focused Learning experiment out of the box, without having to create each component from scratch.
10+
Three abstract types cover the main settings:
11+
- **`AbstractBenchmark`**: static problems (one instance, one decision)
12+
- **`AbstractStochasticBenchmark{exogenous}`**: stochastic problems (type parameter indicates whether uncertainty is exogenous)
13+
- **`AbstractDynamicBenchmark`**: sequential / multi-stage problems
14+
15+
The sections below explain what changes between these settings. For most purposes, start with a static benchmark to understand the core workflow.
16+
17+
---
18+
19+
## Core workflow
20+
21+
Every benchmark exposes three key methods. For any static benchmark:
22+
23+
```julia
24+
bench = ArgmaxBenchmark()
25+
model = generate_statistical_model(bench; seed=0) # Flux model
26+
maximizer = generate_maximizer(bench) # combinatorial oracle
27+
dataset = generate_dataset(bench, 100; seed=0) # Vector{DataSample}
28+
```
29+
30+
- **`generate_statistical_model`**: returns an untrained neural network that maps input features `x` to cost parameters `θ`.
31+
- **`generate_maximizer`**: returns a callable `(θ; context...) -> y` that solves the combinatorial problem given cost parameters.
32+
- **`generate_dataset`**: returns labeled training data as a `Vector{DataSample}`.
33+
34+
At inference time these two pieces compose naturally as an end-to-end policy:
35+
36+
```julia
37+
θ = model(sample.x) # predict cost parameters
38+
y = maximizer(θ; sample.context...) # solve the optimization problem
39+
```
640

741
---
842

@@ -15,11 +49,10 @@ All data in the package is represented as [`DataSample`](@ref) objects.
1549
| `x` | any | Input features (fed to the statistical model) |
1650
| `θ` | any | Intermediate cost parameters |
1751
| `y` | any | Output decision / solution |
18-
| `context` | `NamedTuple` | Solver kwargs spread into `maximizer(θ; sample.context...)` |
19-
| `extra` | `NamedTuple` | Non-solver data (scenario, reward, step, …) never passed to the solver |
52+
| `context` | `NamedTuple` | Solver kwargs spread into `maximizer(θ; sample.context...)` |
53+
| `extra` | `NamedTuple` | Non-solver data (scenario, reward, step, …), never passed to the solver |
2054

21-
Not all fields are populated in every sample. For convenience, named entries inside
22-
`context` and `extra` can be accessed directly on the sample via property forwarding:
55+
Not all fields are populated in every sample, depending on the setting. For convenience, named entries inside `context` and `extra` can be accessed directly on the sample via property forwarding:
2356

2457
```julia
2558
sample.instance # looks up :instance in context first, then in extra
@@ -28,12 +61,11 @@ sample.scenario # looks up :scenario in context first, then in extra
2861

2962
---
3063

31-
## Generating datasets for training
64+
## Benchmark type specifics
3265

3366
### Static benchmarks
3467

35-
For static benchmarks (`<:AbstractBenchmark`) the framework already computes the
36-
ground-truth label `y`:
68+
For static benchmarks (`<:AbstractBenchmark`), `generate_dataset` may compute a default ground-truth label `y` if the benchmark implements it:
3769

3870
```julia
3971
bench = ArgmaxBenchmark()
@@ -43,15 +75,13 @@ dataset = generate_dataset(bench, 100; seed=0) # Vector{DataSample} with x, y,
4375
You can override the labels by providing a `target_policy`:
4476

4577
```julia
46-
my_policy = sample -> DataSample(; sample.context..., x=sample.x,
47-
y=my_algorithm(sample.instance))
78+
my_policy = sample -> DataSample(; sample.context..., x=sample.x, y=my_algorithm(sample.instance))
4879
dataset = generate_dataset(bench, 100; seed=0, target_policy=my_policy)
4980
```
5081

5182
### Stochastic benchmarks (exogenous)
5283

53-
For `AbstractStochasticBenchmark{true}` benchmarks the default call returns
54-
*unlabeled* samples, each sample carries one scenario in `sample.extra.scenario`:
84+
For `AbstractStochasticBenchmark{true}` benchmarks the default call returns *unlabeled* samples, each sample carries one scenario in `sample.extra.scenario`:
5585

5686
```julia
5787
bench = StochasticVehicleSchedulingBenchmark()
@@ -85,20 +115,22 @@ Dynamic benchmarks use a two-step workflow:
85115
```julia
86116
bench = DynamicVehicleSchedulingBenchmark()
87117

88-
# Step 1 create environments (reusable across experiments)
118+
# Step 1: create environments (reusable across experiments)
89119
envs = generate_environments(bench, 10; seed=0)
90120

91-
# Step 2 roll out a policy to collect training trajectories
121+
# Step 2: roll out a policy to collect training trajectories
92122
policy = generate_baseline_policies(bench)[1] # e.g. lazy policy
93123
dataset = generate_dataset(bench, envs; target_policy=policy)
94124
# dataset is a flat Vector{DataSample} of all steps across all trajectories
95125
```
96126

97-
`target_policy` is **required** for dynamic benchmarks (there is no default label).
127+
`target_policy` is **required** to create datasets for dynamic benchmarks (there is no default label).
98128
It must be a callable `(env) -> Vector{DataSample}` that performs a full episode
99129
rollout and returns the resulting trajectory.
100130

101-
### Seed / RNG control
131+
---
132+
133+
## Seed / RNG control
102134

103135
All `generate_dataset` and `generate_environments` calls accept either `seed`
104136
(creates an internal `MersenneTwister`) or `rng` for full control:
@@ -111,22 +143,6 @@ dataset = generate_dataset(bench, 50; rng=rng)
111143

112144
---
113145

114-
## DFL pipeline components
115-
116-
```julia
117-
model = generate_statistical_model(bench; seed=0) # untrained Flux model
118-
maximizer = generate_maximizer(bench) # combinatorial oracle
119-
```
120-
121-
These two pieces compose naturally:
122-
123-
```julia
124-
θ = model(sample.x) # predict cost parameters
125-
y = maximizer(θ; sample.context...) # solve the optimization problem
126-
```
127-
128-
---
129-
130146
## Evaluation
131147

132148
```julia

src/Argmax/Argmax.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ $TYPEDSIGNATURES
6060
6161
Return an argmax maximizer.
6262
"""
63-
function Utils.generate_maximizer(bench::ArgmaxBenchmark)
63+
function Utils.generate_maximizer(::ArgmaxBenchmark)
6464
return one_hot_argmax
6565
end
6666

src/DecisionFocusedLearningBenchmarks.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ export generate_scenario
7373
export generate_baseline_policies
7474
export generate_statistical_model
7575
export generate_maximizer
76-
export generate_anticipative_solution
76+
export generate_anticipative_solver, generate_parametric_anticipative_solver
7777
export is_exogenous, is_endogenous
7878

7979
export objective_value

src/DynamicAssortment/DynamicAssortment.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ function Utils.generate_baseline_policies(::DynamicAssortmentBenchmark)
139139
"policy that selects the assortment with the highest expected revenue",
140140
expert_policy,
141141
)
142-
return (expert, greedy)
142+
return (; expert, greedy)
143143
end
144144

145145
export DynamicAssortmentBenchmark

src/DynamicVehicleScheduling/DynamicVehicleScheduling.jl

Lines changed: 6 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -115,28 +115,14 @@ end
115115
"""
116116
$TYPEDSIGNATURES
117117
118-
Generate an anticipative solution for the dynamic vehicle scheduling benchmark.
119-
The solution is computed using the anticipative solver with the benchmark's feature configuration.
120-
"""
121-
function Utils.generate_anticipative_solution(
122-
b::DynamicVehicleSchedulingBenchmark, args...; kwargs...
123-
)
124-
return anticipative_solver(
125-
args...; kwargs..., two_dimensional_features=b.two_dimensional_features
126-
)
127-
end
128-
129-
"""
130-
$TYPEDSIGNATURES
131-
132118
Return the anticipative solver for the dynamic vehicle scheduling benchmark.
133-
The callable takes a scenario and solver kwargs (including `instance`) and returns a
134-
training trajectory as a `Vector{DataSample}`.
119+
The callable takes an environment and solver kwargs and returns a training trajectory
120+
as a `Vector{DataSample}`. Set `reset_env=true` (default) to reset the environment
121+
before solving, or `reset_env=false` to plan from the current state.
135122
"""
136123
function Utils.generate_anticipative_solver(::DynamicVehicleSchedulingBenchmark)
137-
return (scenario; instance, kwargs...) -> begin
138-
env = DVSPEnv(instance, scenario)
139-
_, trajectory = anticipative_solver(env; reset_env=false, kwargs...)
124+
return (env; reset_env=true, kwargs...) -> begin
125+
_, trajectory = anticipative_solver(env; reset_env, kwargs...)
140126
return trajectory
141127
end
142128
end
@@ -160,7 +146,7 @@ function Utils.generate_baseline_policies(::DynamicVehicleSchedulingBenchmark)
160146
"Greedy policy that dispatches vehicles to the nearest customer.",
161147
greedy_policy,
162148
)
163-
return (lazy, greedy)
149+
return (; lazy, greedy)
164150
end
165151

166152
"""

src/FixedSizeShortestPath/FixedSizeShortestPath.jl

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,4 @@ function Utils.generate_statistical_model(
142142
end
143143

144144
export FixedSizeShortestPathBenchmark
145-
export generate_dataset, generate_maximizer, generate_statistical_model
146-
147145
end

src/Maintenance/Maintenance.jl

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ The number of simultaneous maintenance operations is limited by a maintenance ca
2222
2323
# Fields
2424
$TYPEDFIELDS
25-
2625
"""
2726
struct MaintenanceBenchmark <: AbstractDynamicBenchmark{true}
2827
"number of components"
@@ -126,7 +125,7 @@ end
126125
"""
127126
$TYPEDSIGNATURES
128127
129-
Returns two policies for the dynamic assortment benchmark:
128+
Returns a policy for the maintenance benchmark:
130129
- `Greedy`: maintains components when they are in the last state before failure, up to the maintenance capacity
131130
"""
132131
function Utils.generate_baseline_policies(::MaintenanceBenchmark)
@@ -135,7 +134,7 @@ function Utils.generate_baseline_policies(::MaintenanceBenchmark)
135134
"policy that maintains components when they are in the last state before failure, up to the maintenance capacity",
136135
greedy_policy,
137136
)
138-
return (greedy,)
137+
return (; greedy)
139138
end
140139

141140
export MaintenanceBenchmark

src/PortfolioOptimization/PortfolioOptimization.jl

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,4 @@ function Utils.generate_statistical_model(
116116
end
117117

118118
export PortfolioOptimizationBenchmark
119-
export generate_dataset, generate_maximizer, generate_statistical_model
120-
121119
end

0 commit comments

Comments
 (0)