Skip to content

Commit da70843

Browse files
authored
Merge branch 'main' into fwph_hub
2 parents a96a3f6 + 5f10194 commit da70843

27 files changed

+1018
-549
lines changed

Diff for: .github/workflows/test_pr_and_main.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -407,7 +407,7 @@ jobs:
407407
run: |
408408
cd mpisppy/tests
409409
python test_gradient_rho.py
410-
python test_w_writer.py
410+
python test_xbar_w_reader_writer.py
411411
412412
test-headers:
413413
name: header test

Diff for: examples/farmer/farmer_cylinders.py

+6
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@
88
###############################################################################
99
# general example driver for farmer with cylinders
1010

11+
## Uncomment the below to suppress warnings when running this file. Helpful for debugging.
12+
# import warnings
13+
# warnings.filterwarnings("ignore")
14+
1115
import farmer
1216

1317
# Make it all go
@@ -112,6 +116,8 @@ def main():
112116
ph_converger=ph_converger,
113117
rho_setter = rho_setter)
114118

119+
hub_dict['opt_kwargs']['options']['cfg'] = cfg
120+
115121
if cfg.primal_dual_converger:
116122
hub_dict['opt_kwargs']['options']\
117123
['primal_dual_converger_options'] = {

Diff for: examples/generic_cylinders.bash

+6
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@
44
SOLVER="cplex"
55
SPB=1
66

7+
echo "^^^ hub only with w-writer (smoke) ^^^"
8+
python -m mpi4py ../mpisppy/generic_cylinders.py --module-name farmer/farmer --num-scens 3 --solver-name ${SOLVER} --max-iterations 10 --max-solver-threads 4 --default-rho 1 --W-writer --W-fname w_values.csv
9+
10+
echo "^^^ hub only with w-reader (smoke) ^^^"
11+
python -m mpi4py ../mpisppy/generic_cylinders.py --module-name farmer/farmer --num-scens 3 --solver-name ${SOLVER} --max-iterations 10 --max-solver-threads 4 --default-rho 1 --W-reader --init-W-fname w_values.csv
12+
713
echo "^^^ Multi-stage AirCond ^^^"
814
mpiexec -np 3 python -m mpi4py ../mpisppy/generic_cylinders.py --module-name mpisppy.tests.examples.aircond --branching-factors "3 3 3" --solver-name ${SOLVER} --max-iterations 10 --max-solver-threads 4 --default-rho 1 --lagrangian --xhatxbar --rel-gap 0.01 --solution-base-name aircond_nonants
915
# --xhatshuffle --stag2EFsolvern

Diff for: mpi_one_sided_test.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,10 @@ def main():
2525
rank = mpi.COMM_WORLD.Get_rank()
2626

2727
array_size = 10
28-
win = mpi.Win.Allocate(mpi.DOUBLE.size*array_size, mpi.DOUBLE.size,
28+
win = mpi.Win.Allocate(mpi.DOUBLE.size*array_size, mpi.DOUBLE.size,
2929
comm=mpi.COMM_WORLD)
30-
buff = np.ndarray(buffer=win.tomemory(), dtype='d', shape=(array_size,))
31-
30+
buff = np.ndarray(buffer=win.tomemory(), dtype='d', shape=(array_size,))
31+
3232
if (rank == 0):
3333
buff[:] = 3. * np.ones(array_size, dtype='d')
3434
time.sleep(3)

Diff for: mpisppy/cylinders/cross_scen_spoke.py

+34-25
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,11 @@
66
# All rights reserved. Please see the files COPYRIGHT.md and LICENSE.md for
77
# full copyright and license information.
88
###############################################################################
9+
910
from pyomo.repn.standard_repn import generate_standard_repn
1011
from mpisppy import MPI
1112
from mpisppy.utils.lshaped_cuts import LShapedCutGenerator
13+
from mpisppy.cylinders.spwindow import Field
1214

1315
import numpy as np
1416
import pyomo.environ as pyo
@@ -18,7 +20,10 @@ class CrossScenarioCutSpoke(spoke.Spoke):
1820
def __init__(self, spbase_object, fullcomm, strata_comm, cylinder_comm, options=None):
1921
super().__init__(spbase_object, fullcomm, strata_comm, cylinder_comm, options=options)
2022

21-
def make_windows(self):
23+
def register_send_fields(self) -> None:
24+
25+
super().register_send_fields()
26+
2227
nscen = len(self.opt.all_scenario_names)
2328
if nscen == 0:
2429
raise RuntimeError(f"(rank: {self.cylinder_rank}), no local_scenarios")
@@ -29,24 +34,20 @@ def make_windows(self):
2934
for s in self.opt.local_scenarios.values():
3035
vbuflen += len(s._mpisppy_data.nonant_indices)
3136
local_scen_count = len(self.opt.local_scenario_names)
32-
self.nonant_per_scen = int(vbuflen / local_scen_count)
37+
(self.nonant_per_scen, remainder) = divmod(vbuflen, local_scen_count)
38+
assert(remainder == 0)
3339

3440
## the _locals will also have the kill signal
3541
self.all_nonant_len = vbuflen
3642
self.all_eta_len = nscen*local_scen_count
37-
self._locals = np.zeros(nscen*local_scen_count + vbuflen + 1)
38-
self._coefs = np.zeros(nscen*(nscen + self.nonant_per_scen) + 1 + 1)
39-
self._new_locals = False
4043

41-
# local, remote
42-
# send, receive
43-
self._make_windows(nscen*(self.nonant_per_scen + 1 + 1), nscen*local_scen_count + vbuflen)
44+
self.all_nonants = self.register_recv_field(Field.NONANT, 0, vbuflen)
45+
self.all_etas = self.register_recv_field(Field.CROSS_SCENARIO_COST, 0, nscen * nscen)
4446

45-
def _got_kill_signal(self):
46-
''' returns True if a kill signal was received,
47-
and refreshes the array and _locals'''
48-
self._new_locals = self.spoke_from_hub(self._locals)
49-
return self.remote_write_id == -1
47+
self.all_coefs = self.register_send_field(Field.CROSS_SCENARIO_CUT,
48+
nscen*(self.nonant_per_scen + 1 + 1))
49+
50+
return
5051

5152
def prep_cs_cuts(self):
5253
# create a map scenario -> index, this index is used for various lists containing scenario dependent info.
@@ -62,7 +63,7 @@ def prep_cs_cuts(self):
6263
# add copies of the nonanticipatory variables to the root problem
6364
# NOTE: the LShaped code expects the nonant vars to be in a particular
6465
# order and with a particular *name*.
65-
# We're also creating an index for reference against later
66+
# We're also creating an index for reference against later
6667
nonant_vid_to_copy_map = dict()
6768
root_vars = list()
6869
for v in non_ants:
@@ -84,7 +85,7 @@ def prep_cs_cuts(self):
8485
self.opt.root.eta = pyo.Var(self.opt.all_scenario_names)
8586

8687
self.opt.root.bender = LShapedCutGenerator()
87-
self.opt.root.bender.set_input(root_vars=self.opt.root_vars,
88+
self.opt.root.bender.set_input(root_vars=self.opt.root_vars,
8889
tol=1e-4, comm=self.cylinder_comm)
8990
self.opt.root.bender.set_ls(self.opt)
9091

@@ -127,36 +128,38 @@ def make_eta_lb_cut(self):
127128
## we'll be storing a matrix as an array
128129
## row_len is the length of each row
129130
row_len = 1+1+len(self.root_nonants)
130-
all_coefs = np.zeros( self.nscen*row_len+1, dtype='d')
131+
all_coefs = self.all_coefs
132+
all_coefs.value_array().fill(0.0)
131133
for idx, k in enumerate(self.opt.all_scenario_names):
132134
## cut_array -- [ constant, eta_coef, *nonant_coefs ]
133135
## this cut -- [ LB, -1, *0s ], i.e., -1*\eta + LB <= 0
134136
all_coefs[row_len*idx] = self._eta_lb_array[idx]
135137
all_coefs[row_len*idx+1] = -1
136-
self.spoke_to_hub(all_coefs)
138+
self.spoke_to_hub(all_coefs, Field.CROSS_SCENARIO_CUT)
137139

138140
def make_cut(self):
139141

140142
## cache opt
141143
opt = self.opt
142144

143145
## unpack these the way they were packed:
144-
all_nonants_and_etas = self._locals
146+
all_nonants = self.all_nonants
145147
nonants = dict()
146148
etas = dict()
147149
ci = 0
148150
for k, s in opt.local_scenarios.items():
149151
for ndn, i in s._mpisppy_data.nonant_indices:
150-
nonants[k, ndn, i] = all_nonants_and_etas[ci]
152+
nonants[k, ndn, i] = all_nonants[ci]
151153
ci += 1
152154

153155
# get all the etas
156+
all_etas = self.all_etas
157+
ci = 0
154158
for k, s in opt.local_scenarios.items():
155159
for sn in opt.all_scenario_names:
156-
etas[k, sn] = all_nonants_and_etas[ci]
160+
etas[k, sn] = all_etas[ci]
157161
ci += 1
158162

159-
## self.nscen == len(opt.all_scenario_names)
160163
# compute local min etas
161164
min_eta_vals = np.fromiter(( min(etas[k,sn] for k in opt.local_scenarios) \
162165
for sn in opt.all_scenario_names ),
@@ -215,7 +218,7 @@ def make_cut(self):
215218

216219
# if we are the winner, grab the xhat and bcast it to the other ranks
217220
if self.cylinder_comm.Get_rank() == global_rank[0]:
218-
farthest_xhat = np.fromiter( (nonants[local_winner, nname, ix]
221+
farthest_xhat = np.fromiter( (nonants[local_winner, nname, ix]
219222
for nname, ix in root_nonants),
220223
dtype='d', count=len(root_nonants) )
221224
else:
@@ -283,13 +286,14 @@ def make_cut(self):
283286
## we'll be storing a matrix as an array
284287
## row_len is the length of each row
285288
row_len = 1+1+len(root_nonants)
286-
all_coefs = np.zeros( self.nscen*row_len +1, dtype='d')
289+
# all_coefs = np.zeros( self.nscen*row_len +1, dtype='d')
290+
all_coefs = self.all_coefs
287291
for idx, k in enumerate(opt.all_scenario_names):
288292
if k in coef_dict:
289293
all_coefs[row_len*idx:row_len*(idx+1)] = coef_dict[k]
290294
elif feas_cuts:
291295
all_coefs[row_len*idx:row_len*(idx+1)] = feas_cuts.pop()
292-
self.spoke_to_hub(all_coefs)
296+
self.spoke_to_hub(all_coefs, Field.CROSS_SCENARIO_CUT)
293297

294298
def main(self):
295299
# call main cut generation routine
@@ -299,5 +303,10 @@ def main(self):
299303

300304
# main loop
301305
while not (self.got_kill_signal()):
302-
if self._new_locals:
306+
# if self._new_locals:
307+
if self.all_nonants.is_new() and self.all_etas.is_new():
303308
self.make_cut()
309+
## End if
310+
## End while
311+
312+
return

0 commit comments

Comments
 (0)