Skip to content

Commit c03fac5

Browse files
Merge pull request #428 from bknueven/sep_rho
SepRho and CoeffRho
2 parents 5bba5f4 + a73e318 commit c03fac5

12 files changed

+371
-12
lines changed

Diff for: doc/src/extensions.rst

+17
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,23 @@ constructor or in the hub dictionary under ``opt_kwargs`` as the
116116

117117
There is an example of the function in the sizes example (``_rho_setter``).
118118

119+
SepRho
120+
^^^^^^
121+
122+
Set per variable rho values using the "SEP" algorithm from
123+
124+
Progressive hedging innovations for a class of stochastic mixed-integer resource allocation problems
125+
Jean-Paul Watson, David L. Woodruff, Compu Management Science, 2011
126+
DOI 10.1007/s10287-010-0125-4
127+
128+
One can additional specify a multiplier on the computed value (default = 1.0).
129+
If the cost coefficient on a non-anticipative variable is 0, the default rho value is used instead.
130+
131+
CoeffRho
132+
^^^^^^^^
133+
134+
Set per variable rho values proportional to the cost coefficient on each non-anticipative variable,
135+
with an optional multiplier (default = 1.0). If the coefficient is 0, the default rho value is used instead.
119136

120137
wtracker_extension
121138
^^^^^^^^^^^^^^^^^^

Diff for: examples/afew.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ def do_one(dirname, progname, np, argstring):
4747
# for farmer, the first arg is num_scens and is required
4848
do_one("farmer", "farmer_cylinders.py", 3,
4949
"--num-scens=3 --bundles-per-rank=0 --max-iterations=50 "
50-
"--default-rho=1 --display-convergence-detail "
50+
"--default-rho=1 --sep-rho --display-convergence-detail "
5151
"--solver-name={} --xhatshuffle --lagrangian --use-norm-rho-updater".format(solver_name))
5252
do_one("farmer", "farmer_lshapedhub.py", 2,
5353
"--num-scens=3 --bundles-per-rank=0 --max-iterations=50 "
@@ -58,7 +58,7 @@ def do_one(dirname, progname, np, argstring):
5858
4,
5959
"--num-scens=3 --bundles-per-rank=0 --max-iterations=5 "
6060
"--iter0-mipgap=0.01 --iterk-mipgap=0.001 --linearize-proximal-terms "
61-
" --xhatshuffle --lagrangian --fwph "
61+
" --xhatshuffle --lagrangian --fwph --smoothing "
6262
"--default-rho=1 --solver-name={} --display-progress".format(solver_name))
6363

6464
do_one("hydro", "hydro_cylinders_pysp.py", 3,

Diff for: examples/farmer/farmer_cylinders.py

+11
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ def _parse_args():
4545
cfg.wxbar_read_write_args()
4646
cfg.tracking_args()
4747
cfg.reduced_costs_args()
48+
cfg.sep_rho_args()
49+
cfg.coeff_rho_args()
4850
cfg.add_to_config("crops_mult",
4951
description="There will be 3x this many crops (default 1)",
5052
domain=int,
@@ -125,6 +127,11 @@ def main():
125127
if cfg.reduced_costs:
126128
vanilla.add_reduced_costs_fixer(hub_dict, cfg)
127129

130+
if cfg.sep_rho:
131+
vanilla.add_sep_rho(hub_dict, cfg)
132+
if cfg.coeff_rho:
133+
vanilla.add_coeff_rho(hub_dict, cfg)
134+
128135
# FWPH spoke
129136
if cfg.fwph:
130137
fw_spoke = vanilla.fwph_spoke(*beans, scenario_creator_kwargs=scenario_creator_kwargs)
@@ -145,6 +152,10 @@ def main():
145152
ph_ob_spoke = vanilla.ph_ob_spoke(*beans,
146153
scenario_creator_kwargs=scenario_creator_kwargs,
147154
rho_setter = rho_setter)
155+
if cfg.sep_rho:
156+
vanilla.add_sep_rho(ph_ob_spoke, cfg)
157+
if cfg.coeff_rho:
158+
vanilla.add_coeff_rho(ph_ob_spoke, cfg)
148159

149160
# xhat looper bound spoke
150161
if cfg.xhatlooper:

Diff for: examples/run_all.py

+1-2
Original file line numberDiff line numberDiff line change
@@ -286,11 +286,10 @@ def do_one_mmw(dirname, runefstring, npyfile, mmwargstring):
286286
4,
287287
"--instance-name=sslp_15_45_10 --bundles-per-rank=2 "
288288
"--max-iterations=5 --default-rho=1 "
289-
"--subgradient --xhatshuffle --fwph "
289+
"--subgradient --xhatshuffle --fwph --coeff-rho "
290290
"--linearize-proximal-terms "
291291
"--rel-gap=0.0 "
292292
"--solver-name={} --fwph-stop-check-tol 0.01".format(solver_name))
293-
294293
do_one("sslp",
295294
"sslp_cylinders.py",
296295
3,

Diff for: examples/sslp/sslp_cylinders.py

+9
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ def _parse_args():
3232
cfg.xhatshuffle_args()
3333
cfg.subgradient_args()
3434
cfg.reduced_costs_args()
35+
cfg.coeff_rho_args()
3536
cfg.parse_command_line("sslp_cylinders")
3637
return cfg
3738

@@ -87,9 +88,15 @@ def main():
8788
if reduced_costs:
8889
vanilla.add_reduced_costs_fixer(hub_dict, cfg)
8990

91+
if cfg.coeff_rho:
92+
vanilla.add_coeff_rho(hub_dict, cfg)
93+
9094
# FWPH spoke
9195
if fwph:
9296
fw_spoke = vanilla.fwph_spoke(*beans, scenario_creator_kwargs=scenario_creator_kwargs)
97+
# Need to fix FWPH to support extensions
98+
# if cfg.coeff_rho:
99+
# vanilla.add_coeff_rho(fw_spoke, cfg)
93100

94101
# Standard Lagrangian bound spoke
95102
if lagrangian:
@@ -100,6 +107,8 @@ def main():
100107
subgradient_spoke = vanilla.subgradient_spoke(*beans,
101108
scenario_creator_kwargs=scenario_creator_kwargs,
102109
rho_setter = None)
110+
if cfg.coeff_rho:
111+
vanilla.add_coeff_rho(subgradient_spoke, cfg)
103112

104113
# xhat looper bound spoke
105114
if xhatlooper:

Diff for: mpisppy/extensions/coeff_rho.py

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
###############################################################################
2+
# mpi-sppy: MPI-based Stochastic Programming in PYthon
3+
#
4+
# Copyright (c) 2024, Lawrence Livermore National Security, LLC, Alliance for
5+
# Sustainable Energy, LLC, The Regents of the University of California, et al.
6+
# All rights reserved. Please see the files COPYRIGHT.md and LICENSE.md for
7+
# full copyright and license information.
8+
###############################################################################
9+
10+
import mpisppy.extensions.extension
11+
12+
from mpisppy.utils.sputils import nonant_cost_coeffs
13+
14+
15+
class CoeffRho(mpisppy.extensions.extension.Extension):
16+
"""
17+
Determine rho as a linear function of the objective coefficient
18+
"""
19+
20+
def __init__(self, ph):
21+
self.ph = ph
22+
self.multiplier = 1.0
23+
if (
24+
"coeff_rho_options" in ph.options
25+
and "multiplier" in ph.options["coeff_rho_options"]
26+
):
27+
self.multiplier = ph.options["coeff_rho_options"]["multiplier"]
28+
29+
def post_iter0(self):
30+
for s in self.ph.local_scenarios.values():
31+
cc = nonant_cost_coeffs(s)
32+
for ndn_i, rho in s._mpisppy_model.rho.items():
33+
if cc[ndn_i] != 0:
34+
rho._value = abs(cc[ndn_i]) * self.multiplier
35+
# if self.ph.cylinder_rank==0:
36+
# nv = s._mpisppy_data.nonant_indices[ndn_i] # var_data object
37+
# print(ndn_i,nv.getname(),cc[ndn_i],rho._value)
38+
39+
if self.ph.cylinder_rank == 0:
40+
print("Rho values updated by CoeffRho Extension")

Diff for: mpisppy/extensions/reduced_costs_fixer.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,7 @@ def reduced_costs_fixing(self, reduced_costs):
196196
if self.opt.cylinder_rank == 0 and self.verbose:
197197
print("All reduced costs are nan, heuristic fixing will not be applied")
198198
return
199-
199+
200200
# compute the quantile target
201201
abs_reduced_costs = np.abs(reduced_costs)
202202

Diff for: mpisppy/extensions/sep_rho.py

+170
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
###############################################################################
2+
# mpi-sppy: MPI-based Stochastic Programming in PYthon
3+
#
4+
# Copyright (c) 2024, Lawrence Livermore National Security, LLC, Alliance for
5+
# Sustainable Energy, LLC, The Regents of the University of California, et al.
6+
# All rights reserved. Please see the files COPYRIGHT.md and LICENSE.md for
7+
# full copyright and license information.
8+
###############################################################################
9+
10+
import mpisppy.extensions.extension
11+
import numpy as np
12+
import mpisppy.MPI as MPI
13+
14+
from mpisppy.utils.sputils import nonant_cost_coeffs
15+
16+
17+
class SepRho(mpisppy.extensions.extension.Extension):
18+
"""
19+
Rho determination algorithm "SEP" from
20+
Progressive hedging innovations for a class of stochastic mixed-integer
21+
resource allocation problems
22+
Jean-Paul Watson, David L. Woodruff, Compu Management Science, 2011
23+
DOI 10.1007/s10287-010-0125-4
24+
"""
25+
26+
def __init__(self, ph):
27+
self.ph = ph
28+
29+
self.multiplier = 1.0
30+
31+
if (
32+
"sep_rho_options" in ph.options
33+
and "multiplier" in ph.options["sep_rho_options"]
34+
):
35+
self.multiplier = ph.options["sep_rho_options"]["multiplier"]
36+
37+
def _compute_primal_residual_norm(self, ph):
38+
local_nodenames = []
39+
local_primal_residuals = {}
40+
global_primal_residuals = {}
41+
42+
for k, s in ph.local_scenarios.items():
43+
nlens = s._mpisppy_data.nlens
44+
for node in s._mpisppy_node_list:
45+
if node.name not in local_nodenames:
46+
ndn = node.name
47+
local_nodenames.append(ndn)
48+
nlen = nlens[ndn]
49+
50+
local_primal_residuals[ndn] = np.zeros(nlen, dtype="d")
51+
global_primal_residuals[ndn] = np.zeros(nlen, dtype="d")
52+
53+
for k, s in ph.local_scenarios.items():
54+
nlens = s._mpisppy_data.nlens
55+
xbars = s._mpisppy_model.xbars
56+
for node in s._mpisppy_node_list:
57+
ndn = node.name
58+
primal_residuals = local_primal_residuals[ndn]
59+
60+
unweighted_primal_residuals = np.fromiter(
61+
(
62+
abs(v._value - xbars[ndn, i]._value)
63+
for i, v in enumerate(node.nonant_vardata_list)
64+
),
65+
dtype="d",
66+
count=nlens[ndn],
67+
)
68+
primal_residuals += s._mpisppy_probability * unweighted_primal_residuals
69+
70+
for nodename in local_nodenames:
71+
ph.comms[nodename].Allreduce(
72+
[local_primal_residuals[nodename], MPI.DOUBLE],
73+
[global_primal_residuals[nodename], MPI.DOUBLE],
74+
op=MPI.SUM,
75+
)
76+
77+
primal_resid = {}
78+
for ndn, global_primal_resid in global_primal_residuals.items():
79+
for i, v in enumerate(global_primal_resid):
80+
primal_resid[ndn, i] = v
81+
82+
return primal_resid
83+
84+
@staticmethod
85+
def _compute_min_max(ph, npop, mpiop, start):
86+
local_nodenames = []
87+
local_xmaxmin = {}
88+
global_xmaxmin = {}
89+
90+
for k, s in ph.local_scenarios.items():
91+
nlens = s._mpisppy_data.nlens
92+
for node in s._mpisppy_node_list:
93+
if node.name not in local_nodenames:
94+
ndn = node.name
95+
local_nodenames.append(ndn)
96+
nlen = nlens[ndn]
97+
98+
local_xmaxmin[ndn] = start * np.ones(nlen, dtype="d")
99+
global_xmaxmin[ndn] = np.zeros(nlen, dtype="d")
100+
101+
for k, s in ph.local_scenarios.items():
102+
nlens = s._mpisppy_data.nlens
103+
for node in s._mpisppy_node_list:
104+
ndn = node.name
105+
xmaxmin = local_xmaxmin[ndn]
106+
107+
xmaxmin_partial = np.fromiter(
108+
(v._value for v in node.nonant_vardata_list),
109+
dtype="d",
110+
count=nlens[ndn],
111+
)
112+
xmaxmin = npop(xmaxmin, xmaxmin_partial)
113+
local_xmaxmin[ndn] = xmaxmin
114+
115+
for nodename in local_nodenames:
116+
ph.comms[nodename].Allreduce(
117+
[local_xmaxmin[nodename], MPI.DOUBLE],
118+
[global_xmaxmin[nodename], MPI.DOUBLE],
119+
op=mpiop,
120+
)
121+
122+
xmaxmin_dict = {}
123+
for ndn, global_xmaxmin_dict in global_xmaxmin.items():
124+
for i, v in enumerate(global_xmaxmin_dict):
125+
xmaxmin_dict[ndn, i] = v
126+
127+
return xmaxmin_dict
128+
129+
@staticmethod
130+
def _compute_xmax(ph):
131+
return SepRho._compute_min_max(ph, np.maximum, MPI.MAX, -np.inf)
132+
133+
@staticmethod
134+
def _compute_xmin(ph):
135+
return SepRho._compute_min_max(ph, np.minimum, MPI.MIN, np.inf)
136+
137+
def pre_iter0(self):
138+
pass
139+
140+
def post_iter0(self):
141+
ph = self.ph
142+
primal_resid = self._compute_primal_residual_norm(ph)
143+
xmax = self._compute_xmax(ph)
144+
xmin = self._compute_xmin(ph)
145+
146+
for s in ph.local_scenarios.values():
147+
cc = nonant_cost_coeffs(s)
148+
for ndn_i, rho in s._mpisppy_model.rho.items():
149+
if cc[ndn_i] != 0:
150+
nv = s._mpisppy_data.nonant_indices[ndn_i] # var_data object
151+
if nv.is_integer():
152+
rho._value = abs(cc[ndn_i]) / (xmax[ndn_i] - xmin[ndn_i] + 1)
153+
else:
154+
rho._value = abs(cc[ndn_i]) / max(1, primal_resid[ndn_i])
155+
156+
rho._value *= self.multiplier
157+
158+
# if ph.cylinder_rank==0:
159+
# print(ndn_i,nv.getname(),xmax[ndn_i],xmin[ndn_i],primal_resid[ndn_i],cc[ndn_i],rho._value)
160+
if ph.cylinder_rank == 0:
161+
print("Rho values updated by SepRho Extension")
162+
163+
def miditer(self):
164+
pass
165+
166+
def enditer(self):
167+
pass
168+
169+
def post_everything(self):
170+
pass

0 commit comments

Comments
 (0)