Skip to content

Commit f0134c1

Browse files
bknuevenDLWoodruff
andauthored
Grad rho (#559)
* update farmer rho demo * update UC example * update docs * convert config / generic_cylinders * Update grad_rho.rst * very minimal test for grad_rho * ran ruff locally --------- Co-authored-by: David L Woodruff <[email protected]> Co-authored-by: Dave Woodruff <[email protected]>
1 parent cd945e1 commit f0134c1

File tree

13 files changed

+45
-1072
lines changed

13 files changed

+45
-1072
lines changed

.github/workflows/test_pr_and_main.yml

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -429,11 +429,6 @@ jobs:
429429
run: |
430430
pip install -e .
431431
432-
- name: Build Pyomo extensions
433-
run: |
434-
# some failures are expected, but this should succeed as long as pynumero is built correctly
435-
pyomo build-extensions || python -c "from pyomo.contrib.pynumero.asl import AmplInterface; exit(0) if AmplInterface.available() else exit(1)"
436-
437432
- name: run farmer tests
438433
timeout-minutes: 10
439434
run: |

doc/src/grad_rho.rst

Lines changed: 8 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -1,77 +1,13 @@
11
Gradient-based rho
22
==================
3+
You can find a detailed example using this code in ``examples.farmer.CI.farmer_rho_demo.py``.
34

4-
``mpisppy.utils.gradient.py`` computes rho
5-
using the gradient and writes them in a file.
5+
Grad-rho is also supported in ``generic_cylinders.py``.
66

7-
.. Note::
8-
This only works for two-stage problems for now.
7+
There are options in ``cfg`` to control dynamic updates:
98

10-
To compute gradient-based rho, you can call ``grad_cost_and_rho``
11-
after creating your config object.
12-
You will need to add ``gradient_args`` when you set up the config object.
13-
For example with farmer:
14-
15-
.. code-block:: python
16-
17-
import mpisppy.utils.gradient as grad
18-
19-
def _parse_args():
20-
cfg = config.Config()
21-
...
22-
cfg.gradient_args()
23-
24-
def main():
25-
... #after the config object is created
26-
grad.grad_cost_and_rho('farmer', cfg)
27-
28-
.. Note::
29-
This will write a rho file (resp. grad cost file)
30-
only if you include ``--grad-rho-file`` (resp. ``--grad-cost-file``)
31-
in your bash script.
32-
33-
You can find a detailed example using this code in ``examples.farmer.farmer_rho_demo.py``.
34-
35-
36-
compute_grad
37-
------------
38-
39-
This function computes the gradient of the objective function for each scenario.
40-
It will write the resulting gradient costs in a csv file
41-
containing each scenario name, variable name and the corresponding value.
42-
43-
To use it you should include the following in your bash script.
44-
45-
.. code-block:: bash
46-
47-
--xhatpath #path of your xhat file (.npy)
48-
--grad-cost-file #file where gradient costs will be written
49-
50-
51-
find_grad_rho
52-
-------------
53-
54-
This function computes rhos for each scenario and variable
55-
using the previously computed gradient costs.
56-
The rho values depend on both the scenario and the variable:
57-
``compute_rhos`` returns a dictionnary with the variable names
58-
and a list of corresponding rho values for each scenario.
59-
60-
61-
grad_cost_and_rho
62-
-----------------
63-
64-
This function computes a rho for each variable using the dictionnary
65-
returned by ``find_grad_rho``.
66-
To do so, it uses an order statistic which you should set with ``--order-stat``.
67-
It needs to be a float between 0 and 1: 0 (resp. 1, 0.5)
68-
corresponding to the min (resp. max, average).
69-
It will write the resulting rhos in a csv file
70-
containing each variable name and the corresponding value.
71-
72-
To use it you should include the following in your bash script.
73-
74-
.. code-block:: bash
75-
76-
--grad-rho-file #file where gradient rhos will be written
77-
--order-stat #float between 0 and 1
9+
* `--dynamic-rho-primal-crit` and `--dynamic-rho-dual-crit` are booleans that trigger dynamic rho
10+
* `--dynamic-rho-primal-thresh` and `--dynamic-rho-dual-thresh` control how sensitive the trigger is.
11+
They have default values, so do not need to be set. See
12+
``dyn_rho_base.py`` to see how the update is done if you don't like the default values.
13+
* `--grad-rho-multiplier` is a cummulative multiplier when rho is set or updated.

examples/farmer/CI/farmer_rho_demo.bash

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,7 @@
22

33
SOLVERNAME="cplex"
44

5-
# get an xhat
6-
# xhat output file name is hardwired to 'farmer_cyl_nonants.npy'
7-
mpiexec -np 3 python -m mpi4py farmer_cylinders.py --num-scens 3 --lagrangian --xhatshuffle --bundles-per-rank=0 --max-iterations=50 --default-rho=1 --solver-name=${SOLVERNAME}
8-
9-
mpiexec -np 3 python -m mpi4py farmer_rho_demo.py --num-scens 3 --bundles-per-rank=0 --max-iterations=100 --default-rho=1 --solver-name=${SOLVERNAME} --xhatpath=./farmer_cyl_nonants.npy --grad-order-stat 0.5 --xhatshuffle --lagrangian --max-stalled-iters 5000 --grad-rho-setter --rel-gap 0.001
5+
mpiexec -np 3 python -m mpi4py farmer_rho_demo.py --num-scens 3 --bundles-per-rank=0 --max-iterations=100 --default-rho=1 --solver-name=${SOLVERNAME} --grad-order-stat 0.5 --xhatshuffle --lagrangian --max-stalled-iters 5000 --grad-rho --rel-gap 0.001
106

117
#--rho-relative-bound
128
#--grad-rho-file=./grad_rhos_demo.csv --grad-cost-file=./grad_cost_demo.csv --whatpath=./grad_cost_demo.csv --order-stat=0.5

examples/farmer/CI/farmer_rho_demo.py

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626

2727
from mpisppy.extensions.norm_rho_updater import NormRhoUpdater
2828
from mpisppy.convergers.norm_rho_converger import NormRhoConverger
29-
from mpisppy.extensions.gradient_extension import Gradient_extension
29+
from mpisppy.extensions.grad_rho import GradRho
3030

3131
write_solution = False
3232

@@ -44,7 +44,7 @@ def _parse_args():
4444
cfg.lagrangian_args()
4545
cfg.lagranger_args()
4646
cfg.xhatshuffle_args()
47-
cfg.dynamic_gradient_args() # gets gradient args for free
47+
cfg.dynamic_rho_args() # gets gradient args for free
4848
cfg.add_to_config("crops_mult",
4949
description="There will be 3x this many crops (default 1)",
5050
domain=int,
@@ -102,8 +102,6 @@ def main():
102102
beans = (cfg, scenario_creator, scenario_denouement, all_scenario_names)
103103

104104
ext_classes = []
105-
if cfg.grad_rho:
106-
ext_classes.append(Gradient_extension)
107105

108106
if cfg.run_async:
109107
raise RuntimeError("APH not supported in this example.")
@@ -119,8 +117,8 @@ def main():
119117

120118
#gradient extension kwargs
121119
if cfg.grad_rho:
122-
ext_classes.append(Gradient_extension)
123-
hub_dict['opt_kwargs']['options']['gradient_extension_options'] = {'cfg': cfg}
120+
ext_classes.append(GradRho)
121+
hub_dict['opt_kwargs']['options']['grad_rho_options'] = {'cfg': cfg}
124122

125123
## Gabe's (way pre-pandemic) adaptive rho
126124
if cfg.use_norm_rho_updater:

examples/uc/gradient_uc_cylinders.py

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
from mpisppy.extensions.fixer import Fixer
2323
from mpisppy.extensions.mipgapper import Gapper
2424
from mpisppy.extensions.xhatclosest import XhatClosest
25-
from mpisppy.extensions.gradient_extension import Gradient_extension
25+
from mpisppy.extensions.grad_rho import GradRho
2626
from mpisppy.utils import config
2727
import mpisppy.utils.cfg_vanilla as vanilla
2828
from mpisppy.extensions.cross_scen_extension import CrossScenarioExtension
@@ -40,7 +40,9 @@ def _parse_args():
4040
cfg.xhatlooper_args()
4141
cfg.xhatshuffle_args()
4242
cfg.cross_scenario_cuts_args()
43-
cfg.dynamic_gradient_args() # gets you gradient_args for free
43+
cfg.dynamic_rho_args() # gets gradient args for free
44+
cfg.ph_ob_args()
45+
cfg.integer_relax_then_enforce_args()
4446
cfg.add_to_config("ph_mipgaps_json",
4547
description="json file with mipgap schedule (default None)",
4648
domain=str,
@@ -123,7 +125,8 @@ def main():
123125
ext_classes.append(XhatClosest)
124126

125127
if cfg.grad_rho:
126-
ext_classes.append(Gradient_extension)
128+
ext_classes.append(GradRho)
129+
hub_dict['opt_kwargs']['options']['grad_rho_options'] = {'cfg': cfg}
127130

128131
hub_dict["opt_kwargs"]["extension_kwargs"] = {"ext_classes" : ext_classes}
129132
if cross_scenario_cuts:
@@ -142,9 +145,6 @@ def main():
142145
"keep_solution" : True
143146
}
144147

145-
if cfg.grad_rho:
146-
hub_dict['opt_kwargs']['options']['gradient_extension_options'] = {'cfg': cfg}
147-
148148
if cfg.ph_mipgaps_json is not None:
149149
with open(cfg.ph_mipgaps_json) as fin:
150150
din = json.load(fin)
@@ -159,6 +159,9 @@ def main():
159159
if cfg.default_rho is None:
160160
# since we are using a rho_setter anyway
161161
hub_dict.opt_kwcfg.options["defaultPHrho"] = 1
162+
163+
if cfg.integer_relax_then_enforce:
164+
vanilla.add_integer_relax_then_enforce(hub_dict, cfg)
162165
### end ph spoke ###
163166

164167
# FWPH spoke
@@ -177,6 +180,7 @@ def main():
177180

178181
# ph outer bounder spoke removed August 2025
179182

183+
180184
# xhat shuffle bound spoke
181185
if xhatshuffle:
182186
xhatshuffle_spoke = vanilla.xhatshuffle_spoke(*beans, scenario_creator_kwargs=scenario_creator_kwargs)

examples/uc/gradient_uc_test.bash

Lines changed: 2 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,5 @@
11
#!/bin/bash
22

3-
SOLVERNAME="cplex"
3+
SOLVERNAME="xpress_direct"
44

5-
echo "First solve the EF to get a npy file for xhat ; the uc_cyl_nonants.npy name is hard-wired (you don't really such a good xhat, but this one is just easy to get)"
6-
python simple_ef.py $SOLVERNAME
7-
8-
echo "Now run the cylinders"
9-
mpiexec --oversubscribe -np 3 python -m mpi4py gradient_uc_cylinders.py --xhatshuffle --ph-ob --bundles-per-rank=0 --max-iterations=5 --default-rho=1 --num-scens=5 --max-solver-threads=2 --lagrangian-iter0-mipgap=1e-7 --ph-mipgaps-json=phmipgaps.json --solver-name=${SOLVERNAME} --xhatpath uc_cyl_nonants.npy --rel-gap 0.000001 --display-progress --grad-rho-setter --grad-order-stat 0.5
10-
11-
####mpiexec --oversubscribe -np 1 python -m mpi4py gradient_uc_cylinders.py --bundles-per-rank=0 --max-iterations=20 --default-rho=1 --num-scens=5 --max-solver-threads=2 --lagrangian-iter0-mipgap=1e-7 --ph-mipgaps-json=phmipgaps.json --solver-name=${SOLVERNAME} --xhatpath uc_cyl_nonants.npy --rel-gap 0.000001 --display-progress --grad-rho-setter --grad-order-stat 0.5
12-
13-
14-
# ? do we need ph_ob_rho_rescale_factors_json", ph_ob_gradient_rho", ??
15-
16-
# --fwph
17-
echo
18-
19-
# --rho-setter --order-stat 0.5
20-
# --display-progress
5+
mpiexec -np 3 python -m mpi4py gradient_uc_cylinders.py --xhatshuffle --lagrangian --bundles-per-rank=0 --max-iterations=10 --default-rho=1 --num-scens=5 --max-solver-threads=2 --lagrangian-iter0-mipgap=1e-7 --ph-mipgaps-json=phmipgaps.json --solver-name=${SOLVERNAME} --rel-gap 0.000001 --display-progress --grad-rho --grad-order-stat 0.5 --integer-relax-then-enforce --integer-relax-then-enforce-ratio=0.9

mpisppy/extensions/gradient_extension.py

Lines changed: 0 additions & 103 deletions
This file was deleted.

mpisppy/generic_cylinders.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
from mpisppy.extensions.extension import MultiExtension, Extension
2929
from mpisppy.extensions.norm_rho_updater import NormRhoUpdater
3030
from mpisppy.extensions.primal_dual_rho import PrimalDualRho
31-
from mpisppy.extensions.gradient_extension import Gradient_extension
31+
from mpisppy.extensions.grad_rho import GradRho
3232
from mpisppy.extensions.scenario_lp_mps_files import Scenario_lp_mps_files
3333

3434
from mpisppy.utils.wxbarwriter import WXBarWriter
@@ -237,8 +237,8 @@ def _do_decomp(module, cfg, scenario_creator, scenario_creator_kwargs, scenario_
237237
vanilla.add_integer_relax_then_enforce(hub_dict, cfg)
238238

239239
if cfg.grad_rho:
240-
ext_classes.append(Gradient_extension)
241-
hub_dict['opt_kwargs']['options']['gradient_extension_options'] = {'cfg': cfg}
240+
ext_classes.append(GradRho)
241+
hub_dict['opt_kwargs']['options']['grad_rho_options'] = {'cfg': cfg}
242242

243243
if cfg.write_scenario_lp_mps_files_dir is not None:
244244
ext_classes.append(Scenario_lp_mps_files)

0 commit comments

Comments
 (0)