Skip to content

Commit

Permalink
StochOptFormat v0.3 (#25)
Browse files Browse the repository at this point in the history
  • Loading branch information
odow authored May 2, 2023
1 parent 568a218 commit 96b6671
Show file tree
Hide file tree
Showing 5 changed files with 189 additions and 54 deletions.
56 changes: 27 additions & 29 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ optimization problems called _StochOptFormat_, with the file extension
For convenience, we abbreviate StochOptFormat to _SOF_.

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

_Note: StochOptFormat is in development. If you have suggestions or comments,
please [open an issue](https://github.com/odow/StochOptFormat/issues/new)._
Expand Down Expand Up @@ -247,36 +247,31 @@ Encoded in StochOptFormat, the newsvendor problem becomes:
{
"author": "Oscar Dowson",
"name": "newsvendor",
"date": "2020-07-10",
"date": "2023-05-02",
"description": "A StochOptFormat implementation of the classical two-stage newsvendor problem.",
"version": {"major": 0, "minor": 2},
"version": {"major": 0, "minor": 3},
"root": {
"state_variables": {
"x": {"initial_value": 0.0}
},
"state_variables": {"x": 0.0},
"successors": {"first_stage": 1.0}
},
"nodes": {
"first_stage": {
"subproblem": "first_stage_subproblem",
"realizations": [],
"successors": {"second_stage": 1.0}
},
"second_stage": {
"subproblem": "second_stage_subproblem",
"realizations": [
{"probability": 0.4, "support": {"d": 10.0}},
{"probability": 0.6, "support": {"d": 14.0}}
],
"successors": {}
]
}
},
"subproblems": {
"first_stage_subproblem": {
"state_variables": {
"x": {"in": "x_in", "out": "x_out"}
},
"random_variables": [],
"subproblem": {
"version": {"major": 1, "minor": 2},
"variables": [{"name": "x_in"}, {"name": "x_out"}],
Expand Down Expand Up @@ -341,13 +336,13 @@ Encoded in StochOptFormat, the newsvendor problem becomes:
},
"validation_scenarios": [
[
{"node": "first_stage", "support": {}},
{"node": "first_stage"},
{"node": "second_stage", "support": {"d": 10.0}}
], [
{"node": "first_stage", "support": {}},
{"node": "first_stage"},
{"node": "second_stage", "support": {"d": 14.0}}
], [
{"node": "first_stage", "support": {}},
{"node": "first_stage"},
{"node": "second_stage", "support": {"d": 9.0}}
]
]
Expand Down Expand Up @@ -383,12 +378,8 @@ After the optional metadata keys, there are four required keys:
- `state_variables::Object`

An object describing the state variables in the problem. Each key is the
unique name of a state variable. The value is an object with one required
key:

- `initial_value::Number`

The value of the state variable at the root node.
unique name of a state variable. The initial value of the state variable at
the root node.

- `successors::Object`

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

- `realizations::List{Object}`
- `realizations::List{Object}` (Optional)

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

- `successors::Object`
- `successors::Object` (Optional)

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

- `random_variables::List{String}`
- `random_variables::List{String}` (Optional)

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

Scenarios to be used to evaluate a policy. `validation_scenarios` is a list,
containing one element for each scenario in the test set. Each element is a
list of objects. Each object has two required nodes: `node::String` and
`support::Object`. `node` is the name of the node to visit, and `support` is
the realization of the random variable at that node. Note that `support` may
be an _out-of-sample_ realization, that is, one which is not contained in the
corresponding `realizations` field of the node. Testing a policy is a larger
topic, so we expand on it in the section
[Problems, policies, and algorithms](#problems-policies-and-algorithms).
list of objects. Each object has two required fields:

- `node::String`

The name of the node to visit.

- `support::Object` (Optional)

The realization of the random variable at that node. Note that `support`
may be an _out-of-sample_ realization, that is, one which is not contained
in the corresponding `realizations` field of the node.

Testing a policy is a larger topic, so we expand on it in the next section,
[Problems, policies, and algorithms](#problems-policies-and-algorithms).

## Problems, policies, and algorithms

Expand Down
13 changes: 7 additions & 6 deletions examples/lang-julia/TwoStageBenders.jl
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ import SHA

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

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

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

Expand Down Expand Up @@ -62,7 +62,7 @@ function TwoStageProblem(filename::String; validate::Bool = true)
_validate(data; schema_filename = SCHEMA_FILENAME)
end
@assert data["version"]["major"] == 0
@assert data["version"]["minor"] == 2
@assert data["version"]["minor"] == 3
first, second = _get_stage_names(data)
problem = TwoStageProblem(
sha_256,
Expand All @@ -86,7 +86,7 @@ end
function _initialize_first_stage(data::Dict, first::String, sp::JuMP.Model)
for (name, init) in data["root"]["state_variables"]
x = JuMP.variable_by_name(sp, sp.ext[:state_variables][name]["in"])
JuMP.fix(x, init["initial_value"]; force = true)
JuMP.fix(x, init; force = true)
end
JuMP.@variable(sp, -1e6 <= theta <= 1e6)
JuMP.set_objective_function(sp, JuMP.objective_function(sp) + theta)
Expand All @@ -102,7 +102,7 @@ function _get_stage_names(data::Dict)
@assert length(successors) == 1
second_node, probability = first(successors)
@assert probability == 1.0
@assert length(data["nodes"][second_node]["successors"]) == 0
@assert isempty(get(data["nodes"][second_node], "sucessors", Any[]))
return first_node, second_node
end

Expand All @@ -118,7 +118,7 @@ function _mathoptformat_to_jump(data, name)
JuMP.MOI.copy_to(subproblem, model)
JuMP.set_silent(subproblem)
subproblem.ext[:state_variables] = sp["state_variables"]
subproblem.ext[:realizations] = node["realizations"]
subproblem.ext[:realizations] = get(node, "realizations", Any[])
_convert_realizations(subproblem.ext[:realizations])
return subproblem
end
Expand Down Expand Up @@ -274,10 +274,11 @@ function evaluate(
name => first_sol["primal"][s["out"]]
for (name, s) in problem.second.ext[:state_variables]
)
support = get(scenario[2], "support", Dict{String,Float64}())
second_sol = _solve_second_stage(
problem,
incoming_state,
convert(Dict{String, Float64}, scenario[2]["support"]),
convert(Dict{String, Float64}, support),
)
push!(solutions, [first_sol, second_sol])
end
Expand Down
14 changes: 7 additions & 7 deletions examples/lang-python/TwoStageBenders.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ def __init__(self, filename, validate = True):
),
'versions',
)
self.schema = os.path.join(version_dir, 'sof-0.2.schema.json')
self.schema = os.path.join(version_dir, 'sof-0.3.schema.json')
self.result_schema = os.path.join(version_dir, 'sof-result.schema.json')
if validate:
self._validate_stochoptformat()
Expand All @@ -63,7 +63,7 @@ def train(self, iteration_limit):
name: ret_first['primal'][s['out']]
for (name, s) in self.second['state_variables'].items()
}
for realization in self.second['realizations']:
for realization in self.second.get('realizations', []):
ret = self._solve_second_stage(x, realization['support'])
probabilities.append(realization['probability'])
objectives.append(ret['objective'])
Expand Down Expand Up @@ -106,7 +106,7 @@ def evaluate(self, scenarios = None, filename = None):
for (name, s) in self.second['state_variables'].items()
}
second_sol = self._solve_second_stage(
incoming_state, scenario[1]['support']
incoming_state, scenario[1].get('support', {})
)
solutions.append([first_sol, second_sol])
solution = {
Expand Down Expand Up @@ -136,7 +136,7 @@ def _get_stage_names(self):
assert len(successors) == 1
second_node, probability = next(iter(successors.items()))
assert probability == 1.0
assert len(self.data['nodes'][second_node]['successors']) == 0
assert len(self.data['nodes'][second_node].get('successors', [])) == 0
return first_node, second_node

def _mathoptformat_to_pulp(self, name):
Expand Down Expand Up @@ -200,7 +200,7 @@ def _mathoptformat_to_pulp(self, name):
'subproblem': prob,
'vars': vars,
'state_variables': subproblem['state_variables'],
'realizations': node['realizations'],
'realizations': node.get('realizations', []),
}

def _incoming_state(self, sp, name):
Expand All @@ -212,8 +212,8 @@ def _outgoing_state(self, sp, name):
def _initialize_first_stage(self, name):
for (name, init) in self.data['root']['state_variables'].items():
x = self.first['vars'][self.first['state_variables'][name]['in']]
x.lowBound = init['initial_value']
x.upBound = init['initial_value']
x.lowBound = init
x.upBound = init
self.first['theta'] = pulp.LpVariable('theta', -10**6, 10**6)
self.first['subproblem'].objective += self.first['theta']
return
Expand Down
19 changes: 7 additions & 12 deletions examples/problems/news_vendor.sof.json
Original file line number Diff line number Diff line change
@@ -1,36 +1,31 @@
{
"author": "Oscar Dowson",
"name": "newsvendor",
"date": "2020-07-10",
"date": "2023-05-02",
"description": "A StochOptFormat implementation of the classical two-stage newsvendor problem.",
"version": {"major": 0, "minor": 2},
"version": {"major": 0, "minor": 3},
"root": {
"state_variables": {
"x": {"initial_value": 0.0}
},
"state_variables": {"x": 0.0},
"successors": {"first_stage": 1.0}
},
"nodes": {
"first_stage": {
"subproblem": "first_stage_subproblem",
"realizations": [],
"successors": {"second_stage": 1.0}
},
"second_stage": {
"subproblem": "second_stage_subproblem",
"realizations": [
{"probability": 0.4, "support": {"d": 10.0}},
{"probability": 0.6, "support": {"d": 14.0}}
],
"successors": {}
]
}
},
"subproblems": {
"first_stage_subproblem": {
"state_variables": {
"x": {"in": "x_in", "out": "x_out"}
},
"random_variables": [],
"subproblem": {
"version": {"major": 1, "minor": 2},
"variables": [{"name": "x_in"}, {"name": "x_out"}],
Expand Down Expand Up @@ -95,13 +90,13 @@
},
"validation_scenarios": [
[
{"node": "first_stage", "support": {}},
{"node": "first_stage"},
{"node": "second_stage", "support": {"d": 10.0}}
], [
{"node": "first_stage", "support": {}},
{"node": "first_stage"},
{"node": "second_stage", "support": {"d": 14.0}}
], [
{"node": "first_stage", "support": {}},
{"node": "first_stage"},
{"node": "second_stage", "support": {"d": 9.0}}
]
]
Expand Down
Loading

0 comments on commit 96b6671

Please sign in to comment.