Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 58 additions & 0 deletions mpisppy/tests/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -445,6 +445,64 @@ def test_rc_fixer_with_reduced_costs_does_not_raise(self):
cfg = self._make_rho_cfg(rc_fixer=True, reduced_costs=True)
cfg.checker()

def _add_log_dir_keys(self, cfg, hub_only=False, log_dir=None):
cfg.add_to_config("hub_only_solver_logs", description="x",
domain=bool, default=hub_only, argparse=False)
cfg.add_to_config("solver_log_dir", description="x",
domain=str, default=log_dir, argparse=False)

def test_hub_only_solver_logs_without_log_dir_raises(self):
cfg = self._make_rho_cfg()
self._add_log_dir_keys(cfg, hub_only=True, log_dir=None)
with self.assertRaises(ValueError):
cfg.checker()

def test_hub_only_solver_logs_with_log_dir_does_not_raise(self):
cfg = self._make_rho_cfg()
self._add_log_dir_keys(cfg, hub_only=True, log_dir="/tmp/logs")
cfg.checker()

def test_hub_only_solver_logs_default_off_does_not_raise(self):
cfg = self._make_rho_cfg()
self._add_log_dir_keys(cfg, hub_only=False, log_dir=None)
cfg.checker()


class TestSharedOptionsLogDir(unittest.TestCase):
"""Tests for solver_log_dir propagation through cfg_vanilla.shared_options."""

def _make_cfg(self, log_dir=None, hub_only=False):
cfg = Config()
cfg.popular_args()
cfg.solver_name = "gurobi"
cfg.solver_log_dir = log_dir
cfg.hub_only_solver_logs = hub_only
return cfg

def test_log_dir_propagates_to_hub_and_spoke_by_default(self):
import mpisppy.utils.cfg_vanilla as vanilla
cfg = self._make_cfg(log_dir="/tmp/logs", hub_only=False)
self.assertEqual(vanilla.shared_options(cfg, is_hub=True)["solver_log_dir"],
"/tmp/logs")
self.assertEqual(vanilla.shared_options(cfg, is_hub=False)["solver_log_dir"],
"/tmp/logs")

def test_hub_only_suppresses_spoke_but_keeps_hub(self):
import mpisppy.utils.cfg_vanilla as vanilla
cfg = self._make_cfg(log_dir="/tmp/logs", hub_only=True)
self.assertEqual(vanilla.shared_options(cfg, is_hub=True)["solver_log_dir"],
"/tmp/logs")
self.assertNotIn("solver_log_dir",
vanilla.shared_options(cfg, is_hub=False))

def test_no_log_dir_no_key_anywhere(self):
import mpisppy.utils.cfg_vanilla as vanilla
cfg = self._make_cfg(log_dir=None, hub_only=False)
self.assertNotIn("solver_log_dir",
vanilla.shared_options(cfg, is_hub=True))
self.assertNotIn("solver_log_dir",
vanilla.shared_options(cfg, is_hub=False))


class TestConfigFixerArgs(unittest.TestCase):
"""Tests for Config.fixer_args() and related extension args."""
Expand Down
11 changes: 6 additions & 5 deletions mpisppy/utils/cfg_vanilla.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ def _maybe_attach_jensens(spoke_dict, cfg, spoke_prefix,
"scenario_creator_kwargs": scenario_creator_kwargs,
}

def shared_options(cfg):
def shared_options(cfg, is_hub=False):
shoptions = {
"solver_name": cfg.solver_name,
"defaultPHrho": cfg.default_rho,
Expand Down Expand Up @@ -90,7 +90,8 @@ def shared_options(cfg):
if _hasit(cfg, "reduced_costs"):
shoptions["rc_bound_tol"] = cfg.rc_bound_tol
if _hasit(cfg, "solver_log_dir"):
shoptions["solver_log_dir"] = cfg.solver_log_dir
if is_hub or not cfg.get("hub_only_solver_logs", False):
shoptions["solver_log_dir"] = cfg.solver_log_dir
if _hasit(cfg, "obbt"):
shoptions["presolve_options"] = {
"obbt" : cfg.obbt,
Expand Down Expand Up @@ -154,7 +155,7 @@ def ph_hub(
):
from mpisppy.opt.ph import PH
from mpisppy.cylinders.hub import PHHub
shoptions = shared_options(cfg)
shoptions = shared_options(cfg, is_hub=True)
options = copy.deepcopy(shoptions)
options["convthresh"] = cfg.intra_hub_conv_thresh

Expand Down Expand Up @@ -272,7 +273,7 @@ def subgradient_hub(cfg,
):
from mpisppy.opt.subgradient import Subgradient
from mpisppy.cylinders.hub import SubgradientHub
shoptions = shared_options(cfg)
shoptions = shared_options(cfg, is_hub=True)
options = copy.deepcopy(shoptions)
options["convthresh"] = cfg.intra_hub_conv_thresh

Expand Down Expand Up @@ -316,7 +317,7 @@ def fwph_hub(cfg,
):
from mpisppy.opt.fwph import FWPH
from mpisppy.cylinders.hub import FWPHHub
shoptions = shared_options(cfg)
shoptions = shared_options(cfg, is_hub=True)
options = copy.deepcopy(shoptions)
options["convthresh"] = cfg.intra_hub_conv_thresh

Expand Down
9 changes: 9 additions & 0 deletions mpisppy/utils/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,9 @@ def _bad_options(msg):
if self.get("rc_fixer") and not self.get("reduced_costs"):
_bad_options("--rc-fixer requires --reduced-costs")

if self.get("hub_only_solver_logs") and not self.get("solver_log_dir"):
_bad_options("--hub-only-solver-logs requires --solver-log-dir")

def add_solver_specs(self, prefix=""):
sstr = f"{prefix}_solver" if prefix else "solver"
if prefix:
Expand Down Expand Up @@ -213,6 +216,12 @@ def popular_args(self):
domain=str,
default=None)

self.add_to_config("hub_only_solver_logs",
description="When set with --solver-log-dir, only the hub writes "
"solver logs; spokes do not. Requires --solver-log-dir.",
domain=bool,
default=False)

self.add_to_config("warmstart_subproblems",
description="Warmstart subproblems from prior solution.",
domain=bool,
Expand Down
Loading