-
Notifications
You must be signed in to change notification settings - Fork 43
added a call to an initialize function to generic_cylinders per a sug… #498
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 9 commits
ccf8bba
3c00a1d
136f4e1
d9d9533
411f600
1806fb3
03a9a17
e20ba01
76abab7
7e854e5
0ab7b0e
71c300c
57c651f
b9efc49
e8e845b
4dfbaca
a894a9f
fec12f7
975cc8c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -42,3 +42,41 @@ ignored. | |
probably needed on the command line. Consistency with the files in the | ||
pickle directory might not be checked by the program. | ||
|
||
A Class in the module | ||
--------------------- | ||
|
||
If you want to have a class in the module to provide helper functions, | ||
your module still needs to have an ``inparse_adder`` function and the module will need | ||
to have a function called ``get_mpisppy_helper_object(cfg)`` that returns | ||
the object. It is called by ``generic_cylinders.py`` after cfg is | ||
populated and can be used to create a class. Note that the function | ||
``inparser_adder`` cannot make use of the class because that function | ||
is called before ``get_function_object``. | ||
|
||
The class definition needs to include all helper functions other than | ||
``inparser_adder``. | ||
|
||
|
||
custom_writer | ||
------------- | ||
|
||
Advanced users might want to write their own solution output function. If the | ||
module contains a function called ``custom_writer()``, it will be called | ||
at the end of processing. If you are writing such a function, you should look | ||
in ``generic_cylinders.py`` to see the two places it appears and the arguments | ||
that are passed from each place (your function can test the type | ||
of the first argument to see whence it was called or it can | ||
examine the call stack for more information). | ||
|
||
|
||
Assuming the first formal parameter in your function is assigned | ||
to variable named wheel in the non-ef case, your function probably should | ||
include something along the lines of this: | ||
|
||
.. code_block:: python | ||
|
||
if wheel.spcomm.opt.cylinder_rank == 0: | ||
|
||
so that you avoid writing the output from every rank. | ||
DLWoodruff marked this conversation as resolved.
Show resolved
Hide resolved
|
||
Note this verification is automatically performed by WheelSpinner if you call your custom writer functions through | ||
``wheel.write_first_stage_solution(solution_file_name, first_stage_solution_writer=my_first_stage_writer)`` and ``wheel.write_tree_solution(solution_dir_name, scenario_tree_solution_writer=my_tree_solution_writer)``. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we just be handling this for the user, e.g., pass the function they provide into There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good point. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Agreed, that is probably safer and does not really restrict what the user can do; they can just put it inside the corresponding custom writer. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,173 @@ | ||
############################################################################### | ||
# mpi-sppy: MPI-based Stochastic Programming in PYthon | ||
# | ||
# Copyright (c) 2024, Lawrence Livermore National Security, LLC, Alliance for | ||
# Sustainable Energy, LLC, The Regents of the University of California, et al. | ||
# All rights reserved. Please see the files COPYRIGHT.md and LICENSE.md for | ||
# full copyright and license information. | ||
############################################################################### | ||
# modified April 2025 by DLW to illustrate the use of a class for helper functions | ||
''' Implementation of a simple network design problem. See README for more info | ||
|
||
Created: 9 November 2019 by DTM | ||
|
||
Scenario indices are ZERO based | ||
''' | ||
|
||
import os | ||
import mpisppy.utils.sputils as sputils | ||
import pyomo.environ as pyo | ||
import numpy as np | ||
from parse import parse | ||
|
||
#========= | ||
def inparser_adder(cfg): | ||
# add options unique to sizes | ||
# we don't want num_scens from the command line | ||
cfg.mip_options() | ||
cfg.add_to_config("instance_name", | ||
description="netdes instance name (e.g., network-10-20-L-01)", | ||
domain=str, | ||
default=None) | ||
cfg.add_to_config("netdes_data_path", | ||
description="path to detdes data (e.g., ./data)", | ||
domain=str, | ||
default=None) | ||
|
||
|
||
|
||
|
||
|
||
def get_mpisppy_helper_object(cfg): | ||
return NetDes(cfg) | ||
|
||
class NetDes: | ||
def __init__(self, cfg): | ||
self.cfg = cfg | ||
|
||
|
||
def scenario_creator(self, scenario_name, path=None): | ||
if path is None: | ||
raise RuntimeError('Must provide the name of the .dat file ' | ||
'containing the instance data via the ' | ||
'path argument to scenario_creator') | ||
|
||
scenario_ix = self._get_scenario_ix(scenario_name) | ||
model = self.build_scenario_model(path, scenario_ix) | ||
|
||
# now attach the one and only scenario tree node | ||
sputils.attach_root_node(model, model.FirstStageCost, [model.x[:,:], ]) | ||
|
||
return model | ||
|
||
|
||
def build_scenario_model(self,fname, scenario_ix): | ||
data = parse(fname, scenario_ix=scenario_ix) | ||
num_nodes = data['N'] | ||
adj = data['A'] # Adjacency matrix | ||
edges = data['el'] # Edge list | ||
c = data['c'] # First-stage cost matrix (per edge) | ||
d = data['d'] # Second-stage cost matrix (per edge) | ||
u = data['u'] # Capacity of each arc | ||
b = data['b'] # Demand of each node | ||
p = data['p'] # Probability of scenario | ||
|
||
model = pyo.ConcreteModel() | ||
model.x = pyo.Var(edges, domain=pyo.Binary) # First stage vars | ||
model.y = pyo.Var(edges, domain=pyo.NonNegativeReals) # Second stage vars | ||
|
||
model.edges = edges | ||
model._mpisppy_probability = p | ||
|
||
''' Objective ''' | ||
model.FirstStageCost = pyo.quicksum(c[e] * model.x[e] for e in edges) | ||
model.SecondStageCost = pyo.quicksum(d[e] * model.y[e] for e in edges) | ||
obj_expr = model.FirstStageCost + model.SecondStageCost | ||
model.MinCost = pyo.Objective(expr=obj_expr, sense=pyo.minimize) | ||
|
||
''' Variable upper bound constraints on each edge ''' | ||
model.vubs = pyo.ConstraintList() | ||
for e in edges: | ||
expr = model.y[e] - u[e] * model.x[e] | ||
model.vubs.add(expr <= 0) | ||
|
||
''' Flow balance constraints for each node ''' | ||
model.bals = pyo.ConstraintList() | ||
for i in range(num_nodes): | ||
in_nbs = np.where(adj[:,i] > 0)[0] | ||
out_nbs = np.where(adj[i,:] > 0)[0] | ||
lhs = pyo.quicksum(model.y[i,j] for j in out_nbs) - \ | ||
pyo.quicksum(model.y[j,i] for j in in_nbs) | ||
model.bals.add(lhs == b[i]) | ||
|
||
return model | ||
|
||
|
||
|
||
|
||
def scenario_denouement(self, rank, scenario_name, scenario): | ||
pass | ||
|
||
def _get_scenario_ix(self, sname): | ||
''' Get the scenario index from the given scenario name by strpiping all | ||
digits off of the right of the scenario name, until a non-digit is | ||
encountered. | ||
''' | ||
i = len(sname) - 1 | ||
while (i > 0 and sname[i-1].isdigit()): | ||
i -= 1 | ||
return int(sname[i:]) | ||
|
||
########## helper functions ######## | ||
|
||
#========= | ||
def scenario_names_creator(self, num_scens,start=None): | ||
# if start!=None, the list starts with the 'start' labeled scenario | ||
if (start is None) : | ||
start=0 | ||
return [f"Scenario{i}" for i in range(start,start+num_scens)] | ||
|
||
|
||
#========= | ||
def kw_creator(self, cfg): | ||
# linked to the scenario_creator and inparser_adder | ||
# side-effect is dealing with num_scens | ||
inst = cfg.instance_name | ||
ns = int(inst.split("-")[-3]) | ||
if hasattr(cfg, "num_scens"): | ||
if cfg.num_scens != ns: | ||
raise RuntimeError(f"Argument num-scens={cfg.num_scens} does not match the number " | ||
"implied by instance name={ns} " | ||
"\n(--num-scens is not needed for netdes)") | ||
else: | ||
cfg.add_and_assign("num_scens","number of scenarios", int, None, ns) | ||
path = os.path.join(cfg.netdes_data_path, f"{inst}.dat") | ||
kwargs = {"path": path} | ||
return kwargs | ||
|
||
def sample_tree_scen_creator(self, sname, stage, sample_branching_factors, seed, | ||
given_scenario=None, **scenario_creator_kwargs): | ||
""" Create a scenario within a sample tree. Mainly for multi-stage and simple for two-stage. | ||
(this function supports zhat and confidence interval code) | ||
Args: | ||
sname (string): scenario name to be created | ||
stage (int >=1 ): for stages > 1, fix data based on sname in earlier stages | ||
sample_branching_factors (list of ints): branching factors for the sample tree | ||
seed (int): To allow random sampling (for some problems, it might be scenario offset) | ||
given_scenario (Pyomo concrete model): if not None, use this to get data for ealier stages | ||
scenario_creator_kwargs (dict): keyword args for the standard scenario creator funcion | ||
Returns: | ||
scenario (Pyomo concrete model): A scenario for sname with data in stages < stage determined | ||
by the arguments | ||
""" | ||
# Since this is a two-stage problem, we don't have to do much. | ||
sca = scenario_creator_kwargs.copy() | ||
sca["seedoffset"] = seed | ||
sca["num_scens"] = sample_branching_factors[0] # two-stage problem | ||
return self.scenario_creator(sname, **sca) | ||
|
||
######## end helper functions ######### | ||
|
||
|
||
if __name__=='__main__': | ||
print('netdes.py has no main') |
Uh oh!
There was an error while loading. Please reload this page.