Skip to content

Commit 96b6671

Browse files
authored
StochOptFormat v0.3 (#25)
1 parent 568a218 commit 96b6671

File tree

5 files changed

+189
-54
lines changed

5 files changed

+189
-54
lines changed

README.md

Lines changed: 27 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ optimization problems called _StochOptFormat_, with the file extension
77
For convenience, we abbreviate StochOptFormat to _SOF_.
88

99
StochOptFormat is defined by the [JSON schema](http://JSON-schema.org)
10-
[`https://odow.github.io/StochOptFormat/versions/sof-0.2.schema.json`](https://odow.github.io/StochOptFormat/versions/sof-0.2.schema.json).
10+
[`https://odow.github.io/StochOptFormat/versions/sof-0.3.schema.json`](https://odow.github.io/StochOptFormat/versions/sof-0.3.schema.json).
1111

1212
_Note: StochOptFormat is in development. If you have suggestions or comments,
1313
please [open an issue](https://github.com/odow/StochOptFormat/issues/new)._
@@ -247,36 +247,31 @@ Encoded in StochOptFormat, the newsvendor problem becomes:
247247
{
248248
"author": "Oscar Dowson",
249249
"name": "newsvendor",
250-
"date": "2020-07-10",
250+
"date": "2023-05-02",
251251
"description": "A StochOptFormat implementation of the classical two-stage newsvendor problem.",
252-
"version": {"major": 0, "minor": 2},
252+
"version": {"major": 0, "minor": 3},
253253
"root": {
254-
"state_variables": {
255-
"x": {"initial_value": 0.0}
256-
},
254+
"state_variables": {"x": 0.0},
257255
"successors": {"first_stage": 1.0}
258256
},
259257
"nodes": {
260258
"first_stage": {
261259
"subproblem": "first_stage_subproblem",
262-
"realizations": [],
263260
"successors": {"second_stage": 1.0}
264261
},
265262
"second_stage": {
266263
"subproblem": "second_stage_subproblem",
267264
"realizations": [
268265
{"probability": 0.4, "support": {"d": 10.0}},
269266
{"probability": 0.6, "support": {"d": 14.0}}
270-
],
271-
"successors": {}
267+
]
272268
}
273269
},
274270
"subproblems": {
275271
"first_stage_subproblem": {
276272
"state_variables": {
277273
"x": {"in": "x_in", "out": "x_out"}
278274
},
279-
"random_variables": [],
280275
"subproblem": {
281276
"version": {"major": 1, "minor": 2},
282277
"variables": [{"name": "x_in"}, {"name": "x_out"}],
@@ -341,13 +336,13 @@ Encoded in StochOptFormat, the newsvendor problem becomes:
341336
},
342337
"validation_scenarios": [
343338
[
344-
{"node": "first_stage", "support": {}},
339+
{"node": "first_stage"},
345340
{"node": "second_stage", "support": {"d": 10.0}}
346341
], [
347-
{"node": "first_stage", "support": {}},
342+
{"node": "first_stage"},
348343
{"node": "second_stage", "support": {"d": 14.0}}
349344
], [
350-
{"node": "first_stage", "support": {}},
345+
{"node": "first_stage"},
351346
{"node": "second_stage", "support": {"d": 9.0}}
352347
]
353348
]
@@ -383,12 +378,8 @@ After the optional metadata keys, there are four required keys:
383378
- `state_variables::Object`
384379

385380
An object describing the state variables in the problem. Each key is the
386-
unique name of a state variable. The value is an object with one required
387-
key:
388-
389-
- `initial_value::Number`
390-
391-
The value of the state variable at the root node.
381+
unique name of a state variable. The initial value of the state variable at
382+
the root node.
392383

393384
- `successors::Object`
394385

@@ -407,7 +398,7 @@ After the optional metadata keys, there are four required keys:
407398
nodes can refer to the same subproblem to reduce redundancy if they share
408399
the same structural form.
409400

410-
- `realizations::List{Object}`
401+
- `realizations::List{Object}` (Optional)
411402

412403
A list of objects describing the finite discrete realizations of the
413404
independent random variable in each node. Each object has two required keys:
@@ -423,7 +414,7 @@ After the optional metadata keys, there are four required keys:
423414
`random_variables`, and the values are the value of the random variable in
424415
that realization.
425416

426-
- `successors::Object`
417+
- `successors::Object` (Optional)
427418

428419
An object in which the keys correspond to nodes and the values correspond to
429420
the probability of transitioning from the current node to the key node.
@@ -449,7 +440,7 @@ After the optional metadata keys, there are four required keys:
449440
The name of the variable representing the outgoing state variable in the
450441
subproblem.
451442

452-
- `random_variables::List{String}`
443+
- `random_variables::List{String}` (Optional)
453444

454445
A list of strings describing the name of each random variable in the
455446
subproblem.
@@ -464,13 +455,20 @@ There is also an optional key:
464455

465456
Scenarios to be used to evaluate a policy. `validation_scenarios` is a list,
466457
containing one element for each scenario in the test set. Each element is a
467-
list of objects. Each object has two required nodes: `node::String` and
468-
`support::Object`. `node` is the name of the node to visit, and `support` is
469-
the realization of the random variable at that node. Note that `support` may
470-
be an _out-of-sample_ realization, that is, one which is not contained in the
471-
corresponding `realizations` field of the node. Testing a policy is a larger
472-
topic, so we expand on it in the section
473-
[Problems, policies, and algorithms](#problems-policies-and-algorithms).
458+
list of objects. Each object has two required fields:
459+
460+
- `node::String`
461+
462+
The name of the node to visit.
463+
464+
- `support::Object` (Optional)
465+
466+
The realization of the random variable at that node. Note that `support`
467+
may be an _out-of-sample_ realization, that is, one which is not contained
468+
in the corresponding `realizations` field of the node.
469+
470+
Testing a policy is a larger topic, so we expand on it in the next section,
471+
[Problems, policies, and algorithms](#problems-policies-and-algorithms).
474472

475473
## Problems, policies, and algorithms
476474

examples/lang-julia/TwoStageBenders.jl

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ import SHA
3434

3535
const SCHEMA_DIR = joinpath(dirname(dirname(@__DIR__)), "versions")
3636

37-
const SCHEMA_FILENAME = joinpath(SCHEMA_DIR, "sof-0.2.schema.json")
37+
const SCHEMA_FILENAME = joinpath(SCHEMA_DIR, "sof-0.3.schema.json")
3838

3939
const RESULT_SCHEMA_FILENAME = joinpath(SCHEMA_DIR, "sof-result.schema.json")
4040

@@ -62,7 +62,7 @@ function TwoStageProblem(filename::String; validate::Bool = true)
6262
_validate(data; schema_filename = SCHEMA_FILENAME)
6363
end
6464
@assert data["version"]["major"] == 0
65-
@assert data["version"]["minor"] == 2
65+
@assert data["version"]["minor"] == 3
6666
first, second = _get_stage_names(data)
6767
problem = TwoStageProblem(
6868
sha_256,
@@ -86,7 +86,7 @@ end
8686
function _initialize_first_stage(data::Dict, first::String, sp::JuMP.Model)
8787
for (name, init) in data["root"]["state_variables"]
8888
x = JuMP.variable_by_name(sp, sp.ext[:state_variables][name]["in"])
89-
JuMP.fix(x, init["initial_value"]; force = true)
89+
JuMP.fix(x, init; force = true)
9090
end
9191
JuMP.@variable(sp, -1e6 <= theta <= 1e6)
9292
JuMP.set_objective_function(sp, JuMP.objective_function(sp) + theta)
@@ -102,7 +102,7 @@ function _get_stage_names(data::Dict)
102102
@assert length(successors) == 1
103103
second_node, probability = first(successors)
104104
@assert probability == 1.0
105-
@assert length(data["nodes"][second_node]["successors"]) == 0
105+
@assert isempty(get(data["nodes"][second_node], "sucessors", Any[]))
106106
return first_node, second_node
107107
end
108108

@@ -118,7 +118,7 @@ function _mathoptformat_to_jump(data, name)
118118
JuMP.MOI.copy_to(subproblem, model)
119119
JuMP.set_silent(subproblem)
120120
subproblem.ext[:state_variables] = sp["state_variables"]
121-
subproblem.ext[:realizations] = node["realizations"]
121+
subproblem.ext[:realizations] = get(node, "realizations", Any[])
122122
_convert_realizations(subproblem.ext[:realizations])
123123
return subproblem
124124
end
@@ -274,10 +274,11 @@ function evaluate(
274274
name => first_sol["primal"][s["out"]]
275275
for (name, s) in problem.second.ext[:state_variables]
276276
)
277+
support = get(scenario[2], "support", Dict{String,Float64}())
277278
second_sol = _solve_second_stage(
278279
problem,
279280
incoming_state,
280-
convert(Dict{String, Float64}, scenario[2]["support"]),
281+
convert(Dict{String, Float64}, support),
281282
)
282283
push!(solutions, [first_sol, second_sol])
283284
end

examples/lang-python/TwoStageBenders.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ def __init__(self, filename, validate = True):
4242
),
4343
'versions',
4444
)
45-
self.schema = os.path.join(version_dir, 'sof-0.2.schema.json')
45+
self.schema = os.path.join(version_dir, 'sof-0.3.schema.json')
4646
self.result_schema = os.path.join(version_dir, 'sof-result.schema.json')
4747
if validate:
4848
self._validate_stochoptformat()
@@ -63,7 +63,7 @@ def train(self, iteration_limit):
6363
name: ret_first['primal'][s['out']]
6464
for (name, s) in self.second['state_variables'].items()
6565
}
66-
for realization in self.second['realizations']:
66+
for realization in self.second.get('realizations', []):
6767
ret = self._solve_second_stage(x, realization['support'])
6868
probabilities.append(realization['probability'])
6969
objectives.append(ret['objective'])
@@ -106,7 +106,7 @@ def evaluate(self, scenarios = None, filename = None):
106106
for (name, s) in self.second['state_variables'].items()
107107
}
108108
second_sol = self._solve_second_stage(
109-
incoming_state, scenario[1]['support']
109+
incoming_state, scenario[1].get('support', {})
110110
)
111111
solutions.append([first_sol, second_sol])
112112
solution = {
@@ -136,7 +136,7 @@ def _get_stage_names(self):
136136
assert len(successors) == 1
137137
second_node, probability = next(iter(successors.items()))
138138
assert probability == 1.0
139-
assert len(self.data['nodes'][second_node]['successors']) == 0
139+
assert len(self.data['nodes'][second_node].get('successors', [])) == 0
140140
return first_node, second_node
141141

142142
def _mathoptformat_to_pulp(self, name):
@@ -200,7 +200,7 @@ def _mathoptformat_to_pulp(self, name):
200200
'subproblem': prob,
201201
'vars': vars,
202202
'state_variables': subproblem['state_variables'],
203-
'realizations': node['realizations'],
203+
'realizations': node.get('realizations', []),
204204
}
205205

206206
def _incoming_state(self, sp, name):
@@ -212,8 +212,8 @@ def _outgoing_state(self, sp, name):
212212
def _initialize_first_stage(self, name):
213213
for (name, init) in self.data['root']['state_variables'].items():
214214
x = self.first['vars'][self.first['state_variables'][name]['in']]
215-
x.lowBound = init['initial_value']
216-
x.upBound = init['initial_value']
215+
x.lowBound = init
216+
x.upBound = init
217217
self.first['theta'] = pulp.LpVariable('theta', -10**6, 10**6)
218218
self.first['subproblem'].objective += self.first['theta']
219219
return

examples/problems/news_vendor.sof.json

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,31 @@
11
{
22
"author": "Oscar Dowson",
33
"name": "newsvendor",
4-
"date": "2020-07-10",
4+
"date": "2023-05-02",
55
"description": "A StochOptFormat implementation of the classical two-stage newsvendor problem.",
6-
"version": {"major": 0, "minor": 2},
6+
"version": {"major": 0, "minor": 3},
77
"root": {
8-
"state_variables": {
9-
"x": {"initial_value": 0.0}
10-
},
8+
"state_variables": {"x": 0.0},
119
"successors": {"first_stage": 1.0}
1210
},
1311
"nodes": {
1412
"first_stage": {
1513
"subproblem": "first_stage_subproblem",
16-
"realizations": [],
1714
"successors": {"second_stage": 1.0}
1815
},
1916
"second_stage": {
2017
"subproblem": "second_stage_subproblem",
2118
"realizations": [
2219
{"probability": 0.4, "support": {"d": 10.0}},
2320
{"probability": 0.6, "support": {"d": 14.0}}
24-
],
25-
"successors": {}
21+
]
2622
}
2723
},
2824
"subproblems": {
2925
"first_stage_subproblem": {
3026
"state_variables": {
3127
"x": {"in": "x_in", "out": "x_out"}
3228
},
33-
"random_variables": [],
3429
"subproblem": {
3530
"version": {"major": 1, "minor": 2},
3631
"variables": [{"name": "x_in"}, {"name": "x_out"}],
@@ -95,13 +90,13 @@
9590
},
9691
"validation_scenarios": [
9792
[
98-
{"node": "first_stage", "support": {}},
93+
{"node": "first_stage"},
9994
{"node": "second_stage", "support": {"d": 10.0}}
10095
], [
101-
{"node": "first_stage", "support": {}},
96+
{"node": "first_stage"},
10297
{"node": "second_stage", "support": {"d": 14.0}}
10398
], [
104-
{"node": "first_stage", "support": {}},
99+
{"node": "first_stage"},
105100
{"node": "second_stage", "support": {"d": 9.0}}
106101
]
107102
]

0 commit comments

Comments
 (0)