From 3b3dc6d0eefcccf7e937959fcad9ab6fcda8f4d5 Mon Sep 17 00:00:00 2001 From: "David L. Woodruff" Date: Thu, 13 Mar 2025 13:40:39 -0700 Subject: [PATCH 01/22] created an mps readers based on pulp --- doc/src/agnostic.rst | 7 +- doc/src/extensions.rst | 20 ++-- ...io_lpfiles.py => scenario_lp_mps_files.py} | 3 +- mpisppy/generic_cylinders.py | 10 +- mpisppy/utils/mps_reader.py | 96 +++++++++++++++++++ 5 files changed, 121 insertions(+), 15 deletions(-) rename mpisppy/extensions/{scenario_lpfiles.py => scenario_lp_mps_files.py} (91%) create mode 100644 mpisppy/utils/mps_reader.py diff --git a/doc/src/agnostic.rst b/doc/src/agnostic.rst index ea397a7d4..b00ae9500 100644 --- a/doc/src/agnostic.rst +++ b/doc/src/agnostic.rst @@ -4,7 +4,12 @@ AML Agnosticism The mpi-sppy package provides callouts so that algebraic modeling languages (AMLs) other than Pyomo can be used. A growing number of AMLs are supported as `guest` languages (we refer to mpi-sppy as the `host`). This code is -in an alpha-release state; use with extreme caution. +in an alpha-release state; use with extreme caution. This is referred to +as `tight` integration with the guest. It is also possible to simply read +scenario data from an mps file and the mps file (and the associated json +nonant file) that can be created however you like. Code for creating a +Pyomo model from an mps file is in ``mpisppy.utils.mps_reader.py``. We now +return to a discussion of tight integration with a guest AML. From the end-user's perspective ------------------------------- diff --git a/doc/src/extensions.rst b/doc/src/extensions.rst index 1c0df836e..ff35ed65d 100644 --- a/doc/src/extensions.rst +++ b/doc/src/extensions.rst @@ -274,11 +274,15 @@ If some variables have zero probability in all scenarios, then you will need to all variable probabilities! So you might want to set this to False to verify that the probabilities sum to one only for the Vars you expect before setting it to True. -Scenario_lpwriter ------------------ - -This extension writes an lp file with the model and json file with (a) list(s) of -scenario tree node names and nonanticaptive variables for each scenario before -the iteration zero solve of PH or APH. Note that for two-stage problems, all -json files will be the same. See ``mpisppy.generic_cylinders.py`` -for an example of use. +Scenario_lp_mps_writer +---------------------- + +This extension writes an lp file and an mps file with the model and +json file with (a) list(s) of scenario tree node names and +nonanticaptive variables for each scenario before the iteration zero +solve of PH or APH. Note that for two-stage problems, all json files +will be the same. See ``mpisppy.generic_cylinders.py`` for an example +of use. In that program it is activated with the +``--scenario-lp-mps-writer`` option. + +Unless you know why you need this, you probably don't. diff --git a/mpisppy/extensions/scenario_lpfiles.py b/mpisppy/extensions/scenario_lp_mps_files.py similarity index 91% rename from mpisppy/extensions/scenario_lpfiles.py rename to mpisppy/extensions/scenario_lp_mps_files.py index b1dbf2e89..41f74529a 100644 --- a/mpisppy/extensions/scenario_lpfiles.py +++ b/mpisppy/extensions/scenario_lp_mps_files.py @@ -22,7 +22,7 @@ def lpize(varname): return pyomo_label.cpxlp_label_from_name(varname) -class Scenario_lpfiles(mpisppy.extensions.extension.Extension): +class Scenario_lp_mps_files(mpisppy.extensions.extension.Extension): def __init__(self, ph): self.ph = ph @@ -30,6 +30,7 @@ def __init__(self, ph): def pre_iter0(self): for k, s in self.ph.local_subproblems.items(): s.write(f"{k}.lp", io_options={'symbolic_solver_labels': True}) + s.write(f"{k}.mps", io_options={'symbolic_solver_labels': True}) nonants_by_node = {nd.name: [lpize(var.name) for var in nd.nonant_vardata_list] for nd in s._mpisppy_node_list} with open(f"{k}_nonants.json", "w") as jfile: json.dump(nonants_by_node, jfile) diff --git a/mpisppy/generic_cylinders.py b/mpisppy/generic_cylinders.py index fe8133aa1..d78e4be76 100644 --- a/mpisppy/generic_cylinders.py +++ b/mpisppy/generic_cylinders.py @@ -29,7 +29,7 @@ from mpisppy.extensions.fixer import Fixer from mpisppy.extensions.mipgapper import Gapper from mpisppy.extensions.gradient_extension import Gradient_extension -from mpisppy.extensions.scenario_lpfiles import Scenario_lpfiles +from mpisppy.extensions.scenario_lp_mps_files import Scenario_lp_mps_files from mpisppy.utils.wxbarwriter import WXBarWriter from mpisppy.utils.wxbarreader import WXBarReader @@ -55,8 +55,8 @@ def _parse_args(m): description="The string used for a directory of ouput along with a csv and an npv file (default None, which means no soltion output)", domain=str, default=None) - cfg.add_to_config(name="scenario_lpfiles", - description="Invokes an extension that writes an model lp file and a nonants json file for each scenario before iteration 0", + cfg.add_to_config(name="scenario_lp_mps_files", + description="Invokes an extension that writes an model lp file, mps file and a nonants json file for each scenario before iteration 0", domain=bool, default=False) @@ -220,8 +220,8 @@ def _do_decomp(module, cfg, scenario_creator, scenario_creator_kwargs, scenario_ ext_classes.append(Gradient_extension) hub_dict['opt_kwargs']['options']['gradient_extension_options'] = {'cfg': cfg} - if cfg.scenario_lpfiles: - ext_classes.append(Scenario_lpfiles) + if cfg.scenario_lp_mps_files: + ext_classes.append(Scenario_lp_mps_files) if cfg.W_and_xbar_reader: ext_classes.append(WXBarReader) diff --git a/mpisppy/utils/mps_reader.py b/mpisppy/utils/mps_reader.py new file mode 100644 index 000000000..400cdc4cd --- /dev/null +++ b/mpisppy/utils/mps_reader.py @@ -0,0 +1,96 @@ +############################################################################### +# mpi-sppy: MPI-based Stochastic Programming in PYthon +# +# Copyright (c) 2025, 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. +############################################################################### +# written using many iterations with chatpgt + +import pulp +import re +from pyomo.environ import ConcreteModel, Var, Objective, Constraint, NonNegativeReals + +def sanitize_name(name): + """Ensure variable names and constraint names are valid Pyomo indices.""" + if not isinstance(name, str): + name = str(name) # Convert to string if not already + return re.sub(r"[^\w]", "_", name.strip()) # Strip spaces and replace special characters with underscores + +def read_mps_and_create_pyomo_model(mps_file): + """ + Reads an MPS file using PuLP and converts it into a Pyomo model. + + Parameters: + mps_file (str): Path to the MPS file. + + Returns: + Pyomo ConcreteModel: A Pyomo model equivalent to the one described in the MPS file. + """ + + # Read the MPS file using PuLP + varNames, lp_problem = pulp.LpProblem.fromMPS(mps_file) # Ensure we only use the problem object + + # Create a Pyomo model + model = ConcreteModel() + + # Define variables in Pyomo with sanitized names + var_names = [sanitize_name(v.name) for v in lp_problem.variables()] + model.variables = Var(var_names, domain=NonNegativeReals) # Indexed by sanitized names + + # Define the objective function correctly + if lp_problem.sense == pulp.LpMinimize: + model.obj = Objective( + expr=sum(coeff * model.variables[sanitize_name(var.name)] for var, coeff in lp_problem.objective.items()), + sense=1 # Minimize + ) + else: + model.obj = Objective( + expr=sum(coeff * model.variables[sanitize_name(var.name)] for var, coeff in lp_problem.objective.items()), + sense=-1 # Maximize + ) + + # Define constraints + def get_constraint_expr(constraint): + return sum(model.variables[sanitize_name(var)] * coeff for var, coeff in constraint) + + def constraint_rule(m, cname): + """Use the original constraint name for lookup but the sanitized name for Pyomo indexing.""" + constraint = lp_problem.constraints[cname] # Keep original constraint name for lookup + lhs_expr = get_constraint_expr(constraint.items()) + + if constraint.sense == pulp.LpConstraintEQ: + return lhs_expr == constraint.constant + elif constraint.sense == pulp.LpConstraintLE: + return lhs_expr <= constraint.constant + elif constraint.sense == pulp.LpConstraintGE: + return lhs_expr >= constraint.constant + + # Create constraints with sanitized names, but keep original names for lookup + constraint_mapping = {sanitize_name(cname): cname for cname in lp_problem.constraints.keys()} + model.constraints = Constraint( + constraint_mapping.keys(), + rule=lambda m, sanitized_cname: constraint_rule(m, constraint_mapping[sanitized_cname]) + ) + + return model + + + +########### main for debugging ########## +if __name__ == "__main__": + fname = "delme.mps" + print(f"about to read {fname}") + model = read_mps_and_create_pyomo_model(fname) + + for cname in model.constraints: + print(f"Constraint: {cname}") + print(model.constraints[cname].expr) + print("-" * 80) + model.constraints[cname].pprint() + print("+" * 80) + for v in model.variables: + print(f"Variable Name: '{v}'") # Quoting ensures visibility of leading/trailing spaces + model.pprint() + From 37d04c55bf3d406c963bc1e2ef69b6762c703def Mon Sep 17 00:00:00 2001 From: "David L. Woodruff" Date: Fri, 14 Mar 2025 12:35:11 -0700 Subject: [PATCH 02/22] created an mps_module to allow for mps based scenarios --- mpisppy/utils/mps_module.py | 122 ++++++++++++++++++++++++++++++++++++ 1 file changed, 122 insertions(+) create mode 100644 mpisppy/utils/mps_module.py diff --git a/mpisppy/utils/mps_module.py b/mpisppy/utils/mps_module.py new file mode 100644 index 000000000..c59e49016 --- /dev/null +++ b/mpisppy/utils/mps_module.py @@ -0,0 +1,122 @@ +##r########################################################################### +# mpi-sppy: MPI-based Stochastic Programming in PYthon +# +# Copyright (c) 2025, 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. +############################################################################### +# WARNING: the scenario_creator is very dependent on how the mps_reader works. +"""You pass in a path to a special mps_files_directory in the config and this +module will become a valid mpi-sppy module. +The directory has to have file pairs for each scenario where one file +is the mps file and other is a json file with a scenario tree dictionary +for the scenario. + +Scenario names must have a consistent base (e.g. "scenario" or "scen") and +must end in a serial number (unless you can't, you should start with 0; +otherwise, start with 1). + +note to dlw from dlw: + You could offer the option to split up the objective by stages in the lp file + You could also offer other classes of nonants in the json + +""" +import os +import re +import path +import json +import mpisppy.utils.mps_reader as mps_reader + +# assume you can get the path from config, set in kw_creator as a side-effect +mps_files_directory = None + +def scenario_creator(sname, cfg=None): + """ Load the model from an mps file + + Args: + scenario_name (str): + Name of the scenario to construct. + cfg (Pyomo config object): options + """ + + sharedPath = os.path.join(cfg.mps_files_directory, sname) + mpsPath = sharedPath + ".lp" + model = mps_reader(mpsPath) + # now read the JSON file and attach the tree information. + jsonPath = sharedPath + "_nonants.json" + with open(jsonPath) as f: + nonantDict = json.load(f) + try: + scenProb = nonantDict["scenProb"] + except Exception as e: + raise RuntimeError(f'Error getting scenProb from {jsonPath}: {e}') + assert "ROOT" in nonantDict, f'"ROOT" must be top node in {jsonPath}' + treeNodes = list() + parent_ndn = None # counting on the json file to have ordered nodes + for ndn in nonantDict: + cp = nonantDict[ndn]["condProb"] + nonant_list = nonantDict[ndn]["nonAnts"] + assert parent_ndn == sputils.parent_ndn, + f"bad node names or parent order in {jsonPath} detected at {ndn}" + treeNodes.append(scenario_tree.\ + ScenarioNode(name=ndn, + cond_prob=cp, + stage=stage, + cost_expression=0.0, + nonant_list=nonant_list, + scen_model=model, + nonant_ef_suppl_list = None, + parent_name = parent_ndn + ) + ) + parent_ndn = ndName + + model._mpisppy_probability = scenProb + model._mpisppy_node_list = treeNodes + return model + + +#========= +def scenario_names_creator(self, num_scens, start=None): + # validate the directory and use it to get names (that have to be numbered) + mps_files = [os.path.basename(f) + for f in glob.glob(os.path.join(mps_files_directory, "*.mps"))] + if start = None: + start = 0 + first = re.search(r"\d+$",mps_files[0][:-4]) # first scenario number + assert first == 0, "first scenario number should be zero," + f" found {first} for file {os.path.join(mps_files_directory, lpfiles[0])}" + + retval = [fn[:-4] for fn in mps_files[start, start+num_scens-1]] + return retval + +#========= +def inparser_adder(self, cfg): + # verify that that the mps_files_directory is there, or add it + if "mps_files_directory" not in cfg: + cfg.add_to_config("mps_files_directory", + "Directory with mps, json pairs for scenarios", + domain=str, + default=None, + argparse=True) + +#========= +def kw_creator(self, cfg): + # creates keywords for scenario creator + # SIDE EFFECT: A bit of hack to get the directory path + global mps_files_directory = cfg.mps_files_directory + return {"cfg": cfg} + + +# This is only needed for sampling +def sample_tree_scen_creator(self, sname, stage, sample_branching_factors, seed, + given_scenario=None, **scenario_creator_kwargs): + # assert two stage, then do the usual for two stages? + pass + + +#============================ +def scenario_denouement(self, rank, scenario_name, scenario): + pass + From a222aeae444c80804bd5ecbc8c67b06981436e73 Mon Sep 17 00:00:00 2001 From: "David L. Woodruff" Date: Fri, 14 Mar 2025 12:50:47 -0700 Subject: [PATCH 03/22] [WIP] added to the format for json files to accompany lp and mps files --- mpisppy/extensions/scenario_lp_mps_files.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/mpisppy/extensions/scenario_lp_mps_files.py b/mpisppy/extensions/scenario_lp_mps_files.py index 41f74529a..c2eab4bf9 100644 --- a/mpisppy/extensions/scenario_lp_mps_files.py +++ b/mpisppy/extensions/scenario_lp_mps_files.py @@ -31,9 +31,14 @@ def pre_iter0(self): for k, s in self.ph.local_subproblems.items(): s.write(f"{k}.lp", io_options={'symbolic_solver_labels': True}) s.write(f"{k}.mps", io_options={'symbolic_solver_labels': True}) - nonants_by_node = {nd.name: [lpize(var.name) for var in nd.nonant_vardata_list] for nd in s._mpisppy_node_list} + scenDict = {"scenProb": s._mpisppy_probability} # to be added to + nodeDict = dict() + for nd in s._mpisppy_node_list: + nodeDict[nd] = {"condProb": nd.cond_prob} + nodeDict[nd].update({"nonAnts": [lpize(var.name) for var in nd.nonant_vardata_list]}) + scenDict.update(nodeDict) with open(f"{k}_nonants.json", "w") as jfile: - json.dump(nonants_by_node, jfile) + json.dump(scenDict, jfile) def post_iter0(self): return From 6fd3bcfd4678df0a546cdc4548de3bb6faf19b78 Mon Sep 17 00:00:00 2001 From: "David L. Woodruff" Date: Sun, 16 Mar 2025 11:54:55 -0700 Subject: [PATCH 04/22] can now read mps using coin mip; here's a little demo --- mpisppy/utils/mps_coin_mip.py | 67 +++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 mpisppy/utils/mps_coin_mip.py diff --git a/mpisppy/utils/mps_coin_mip.py b/mpisppy/utils/mps_coin_mip.py new file mode 100644 index 000000000..f0c629c3f --- /dev/null +++ b/mpisppy/utils/mps_coin_mip.py @@ -0,0 +1,67 @@ +import mip # from coin-or +from pyomo.environ import ConcreteModel, Var, Objective, Constraint, NonNegativeReals, minimize, maximize + +def mip_to_pyomo(mps_path): + """ + Reads an MPS file using mip and converts it into a Pyomo ConcreteModel. + This function extracts the model structure without solving it. + + :param mps_path: Path to the MPS file. + :return: Pyomo ConcreteModel. + """ + # Read the MPS file + m = mip.Model(solver_name="cbc") + m.read(mps_path) + + # Create a Pyomo model + model = ConcreteModel() + + # Add variables to Pyomo model with their bounds + model.variables = Var( + range(len(m.vars)), + domain=NonNegativeReals # Assuming all vars are non-negative unless modified below + ) + + # Map mip variables to Pyomo variables + var_map = {v: model.variables[i] for i, v in enumerate(m.vars)} + + # Adjust variable bounds + for i, v in enumerate(m.vars): + if v.lb is not None: + model.variables[i].setlb(v.lb) + if v.ub is not None: + model.variables[i].setub(v.ub) + + # Add constraints + def constraint_rule(model, i): + c = m.constrs[i] + + # Extract terms correctly from LinExpr + expr = sum(coeff * var_map[var] for var, coeff in c.expr.expr.items()) # FIXED + + # Add the constant term if it exists + expr += c.expr.const if hasattr(c.expr, "const") else 0 + + # Return Pyomo constraint expression directly + return expr == c.rhs # Pyomo needs a symbolic equation, not an if-statement + + model.constraints = Constraint(range(len(m.constrs)), rule=constraint_rule) + + # **Fixed Objective Function Extraction** + obj_expr = sum(coeff * var_map[var] for var, coeff in m.objective.expr.items()) # FIXED + + # Add the constant term if it exists + obj_expr += m.objective.expr.get("const", 0) # Ensure it doesn't crash + + # Set objective function (minimize or maximize) + if m.sense == mip.MINIMIZE: + model.objective = Objective(expr=obj_expr, sense=minimize) + else: + model.objective = Objective(expr=obj_expr, sense=maximize) + + return model + +# Example usage: +# pyomo_model = mip_to_pyomo("delme.mps") +pyomo_model = mip_to_pyomo("delme.mps") +pyomo_model.pprint() From 04472addbc37a931374e4ba6f2f7b17d9524e67b Mon Sep 17 00:00:00 2001 From: "David L. Woodruff" Date: Sun, 16 Mar 2025 17:21:02 -0700 Subject: [PATCH 05/22] major corrections by dlw --- mpisppy/utils/mps_coin_mip.py | 90 ++++++++++++++++++++++------------- 1 file changed, 56 insertions(+), 34 deletions(-) diff --git a/mpisppy/utils/mps_coin_mip.py b/mpisppy/utils/mps_coin_mip.py index f0c629c3f..2bf103641 100644 --- a/mpisppy/utils/mps_coin_mip.py +++ b/mpisppy/utils/mps_coin_mip.py @@ -1,67 +1,89 @@ import mip # from coin-or -from pyomo.environ import ConcreteModel, Var, Objective, Constraint, NonNegativeReals, minimize, maximize +import pyomo.environ as pyo +from pyomo.environ import NonNegativeReals, minimize, maximize def mip_to_pyomo(mps_path): """ Reads an MPS file using mip and converts it into a Pyomo ConcreteModel. - This function extracts the model structure without solving it. :param mps_path: Path to the MPS file. :return: Pyomo ConcreteModel. + + aside: Chatgpt was almost negative help with this function... """ - # Read the MPS file + + def _domain_lookup(v): + # given a mip var, return its Pyomo domain + if v.var_type == 'C': + if v.lb == 0.0: + return pyo.NonNegativeReals + else: + return pyo.Reals + elif v.var_type == 'B': + return pyo.Binary + elif v.var_type == 'I': + return pyo.Integers + else: + raise RuntimeError(f"Unknown type from coin mip {v.var_type=}") + + # Read the MPS file and call the coin-mip model m m = mip.Model(solver_name="cbc") m.read(mps_path) # Create a Pyomo model - model = ConcreteModel() + model = pyo.ConcreteModel() - # Add variables to Pyomo model with their bounds - model.variables = Var( - range(len(m.vars)), - domain=NonNegativeReals # Assuming all vars are non-negative unless modified below - ) - - # Map mip variables to Pyomo variables - var_map = {v: model.variables[i] for i, v in enumerate(m.vars)} - - # Adjust variable bounds - for i, v in enumerate(m.vars): - if v.lb is not None: - model.variables[i].setlb(v.lb) - if v.ub is not None: - model.variables[i].setub(v.ub) + varDict = dict() # coin mip var to pyomo var + # Add variables to Pyomo model with their bounds and domains + for v in m.vars: + vname = v.name + print(f"Processing variable with name {vname} and type {v.var_type=}") + varDict[v] = pyo.Var(domain=_domain_lookup(v), bounds=(v.lb, v.ub)) + setattr(model, vname, varDict[v]) + #print(f"{dir(v)=}") # Add constraints - def constraint_rule(model, i): - c = m.constrs[i] - + for c in m.constrs: + cname = c.name # Extract terms correctly from LinExpr - expr = sum(coeff * var_map[var] for var, coeff in c.expr.expr.items()) # FIXED + body = sum(coeff * varDict[var] for var, coeff in c.expr.expr.items()) # Add the constant term if it exists - expr += c.expr.const if hasattr(c.expr, "const") else 0 + body += c.expr.const if hasattr(c.expr, "const") else 0 + if c.expr.sense == "=": + pyomoC = pyo.Constraint(expr=body == c.rhs) + elif c.expr.sense == ">": + pyomoC = pyo.Constraint(expr=body >= c.rhs) + elif c.expr.sense == "<": + pyomoC = pyo.Constraint(expr=body <= c.rhs) + elif c.expr.sense == "": + raise RuntimeError(f"Unexpected empty sense for constraint {c.name}" + f" from file {mps_path}") + else: + raise RuntimeError(f"Unexpected sense {c.expr.sense=}" + f" for constraint {c.name} from file {mps_path}") + setattr(model, c.name, pyomoC) - # Return Pyomo constraint expression directly - return expr == c.rhs # Pyomo needs a symbolic equation, not an if-statement - - model.constraints = Constraint(range(len(m.constrs)), rule=constraint_rule) - - # **Fixed Objective Function Extraction** - obj_expr = sum(coeff * var_map[var] for var, coeff in m.objective.expr.items()) # FIXED + # objective function + obj_expr = sum(coeff * varDict[v] for v, coeff in m.objective.expr.items()) # Add the constant term if it exists obj_expr += m.objective.expr.get("const", 0) # Ensure it doesn't crash # Set objective function (minimize or maximize) if m.sense == mip.MINIMIZE: - model.objective = Objective(expr=obj_expr, sense=minimize) + model.objective = pyo.Objective(expr=obj_expr, sense=minimize) else: - model.objective = Objective(expr=obj_expr, sense=maximize) + model.objective = pyo.Objective(expr=obj_expr, sense=maximize) return model # Example usage: -# pyomo_model = mip_to_pyomo("delme.mps") pyomo_model = mip_to_pyomo("delme.mps") +# pyomo_model = mip_to_pyomo("test1.mps") pyomo_model.pprint() + +opt = pyo.SolverFactory('cplex') + +opt.solve(pyomo_model) + From af84d031a5fc22c79320a0cb8ef16f8c6f76d31a Mon Sep 17 00:00:00 2001 From: "David L. Woodruff" Date: Sun, 16 Mar 2025 17:32:35 -0700 Subject: [PATCH 06/22] mps_coin_mip.py executes, but the solutions don't match --- mpisppy/utils/mps_coin_mip.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/mpisppy/utils/mps_coin_mip.py b/mpisppy/utils/mps_coin_mip.py index 2bf103641..3960290e7 100644 --- a/mpisppy/utils/mps_coin_mip.py +++ b/mpisppy/utils/mps_coin_mip.py @@ -78,12 +78,17 @@ def _domain_lookup(v): return model -# Example usage: -pyomo_model = mip_to_pyomo("delme.mps") -# pyomo_model = mip_to_pyomo("test1.mps") +fname = "delme.mps" +#fname = "test1.mps" +pyomo_model = mip_to_pyomo(fname) pyomo_model.pprint() opt = pyo.SolverFactory('cplex') - opt.solve(pyomo_model) +pyomo_obj = pyo.value(pyomo_model.objective) +m = mip.Model(solver_name="cbc") +m.read(fname) +cbcstatus = m.optimize() +cbc_obj = m.objective_value +print(f"{cbcstatus=}, {cbc_obj=}, {pyomo_obj=}") From de8a5cca298bdddf4f08f4e5565b09b7df806a4a Mon Sep 17 00:00:00 2001 From: "David L. Woodruff" Date: Mon, 17 Mar 2025 12:12:15 -0700 Subject: [PATCH 07/22] the mps reader is ready for testing --- mpisppy/utils/mps_coin_mip.py | 63 +++++++++----- mpisppy/utils/mps_reader.py | 158 +++++++++++++++++++--------------- 2 files changed, 128 insertions(+), 93 deletions(-) diff --git a/mpisppy/utils/mps_coin_mip.py b/mpisppy/utils/mps_coin_mip.py index 3960290e7..726bf5a68 100644 --- a/mpisppy/utils/mps_coin_mip.py +++ b/mpisppy/utils/mps_coin_mip.py @@ -1,8 +1,17 @@ -import mip # from coin-or +############################################################################### +# mpi-sppy: MPI-based Stochastic Programming in PYthon +# +# Copyright (c) 2025, 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. +############################################################################### + +import mip # from coin-or (pip install mip) import pyomo.environ as pyo from pyomo.environ import NonNegativeReals, minimize, maximize -def mip_to_pyomo(mps_path): +def read_mps_and_create_pyomo_model(mps_file): """ Reads an MPS file using mip and converts it into a Pyomo ConcreteModel. @@ -25,6 +34,8 @@ def _domain_lookup(v): return pyo.Integers else: raise RuntimeError(f"Unknown type from coin mip {v.var_type=}") + # BTW: I don't know how to query the mip object for SOS sets + # (maybe it transforms them?) # Read the MPS file and call the coin-mip model m m = mip.Model(solver_name="cbc") @@ -44,12 +55,9 @@ def _domain_lookup(v): # Add constraints for c in m.constrs: - cname = c.name # Extract terms correctly from LinExpr body = sum(coeff * varDict[var] for var, coeff in c.expr.expr.items()) - # Add the constant term if it exists - body += c.expr.const if hasattr(c.expr, "const") else 0 if c.expr.sense == "=": pyomoC = pyo.Constraint(expr=body == c.rhs) elif c.expr.sense == ">": @@ -66,11 +74,6 @@ def _domain_lookup(v): # objective function obj_expr = sum(coeff * varDict[v] for v, coeff in m.objective.expr.items()) - - # Add the constant term if it exists - obj_expr += m.objective.expr.get("const", 0) # Ensure it doesn't crash - - # Set objective function (minimize or maximize) if m.sense == mip.MINIMIZE: model.objective = pyo.Objective(expr=obj_expr, sense=minimize) else: @@ -78,17 +81,33 @@ def _domain_lookup(v): return model -fname = "delme.mps" -#fname = "test1.mps" -pyomo_model = mip_to_pyomo(fname) -pyomo_model.pprint() -opt = pyo.SolverFactory('cplex') -opt.solve(pyomo_model) -pyomo_obj = pyo.value(pyomo_model.objective) +if __name__ == "__main__": + # for testing + solver_name = "cplex" + fname = "delme.mps" + #fname = "test1.mps" + pyomo_model = def read_mps_and_create_pyomo_model(fname) + pyomo_model.pprint() + + opt = pyo.SolverFactory(solver_name) + opt.solve(pyomo_model) + pyomo_obj = pyo.value(pyomo_model.objective) + + m = mip.Model(solver_name=solver_name) + m.read(fname) + coinstatus = m.optimize() + coin_obj = m.objective_value + print(f"{coinstatus=}, {coin_obj=}, {pyomo_obj=}") -m = mip.Model(solver_name="cbc") -m.read(fname) -cbcstatus = m.optimize() -cbc_obj = m.objective_value -print(f"{cbcstatus=}, {cbc_obj=}, {pyomo_obj=}") + print("\ncoin var values") + for v in m.vars: + print(f"{v.name}: {v.x}") + + print("\npyomo var values") + for v in pyomo_model.component_objects(pyo.Var, active=True): + print(f"Variable: {v.name}") + for index in v: + print(f" Index: {index}, Value: {v[index].value}") + + diff --git a/mpisppy/utils/mps_reader.py b/mpisppy/utils/mps_reader.py index 400cdc4cd..7c89d48b2 100644 --- a/mpisppy/utils/mps_reader.py +++ b/mpisppy/utils/mps_reader.py @@ -6,91 +6,107 @@ # All rights reserved. Please see the files COPYRIGHT.md and LICENSE.md for # full copyright and license information. ############################################################################### -# written using many iterations with chatpgt -import pulp -import re -from pyomo.environ import ConcreteModel, Var, Objective, Constraint, NonNegativeReals - -def sanitize_name(name): - """Ensure variable names and constraint names are valid Pyomo indices.""" - if not isinstance(name, str): - name = str(name) # Convert to string if not already - return re.sub(r"[^\w]", "_", name.strip()) # Strip spaces and replace special characters with underscores +import mip # from coin-or (pip install mip) +import pyomo.environ as pyo def read_mps_and_create_pyomo_model(mps_file): """ - Reads an MPS file using PuLP and converts it into a Pyomo model. - - Parameters: - mps_file (str): Path to the MPS file. + Reads an MPS file using mip and converts it into a Pyomo ConcreteModel. + + :param mps_path: Path to the MPS file. + :return: Pyomo ConcreteModel. - Returns: - Pyomo ConcreteModel: A Pyomo model equivalent to the one described in the MPS file. + aside: Chatgpt was almost negative help with this function... """ + + def _domain_lookup(v): + # given a mip var, return its Pyomo domain + if v.var_type == 'C': + if v.lb == 0.0: + return pyo.NonNegativeReals + else: + return pyo.Reals + elif v.var_type == 'B': + return pyo.Binary + elif v.var_type == 'I': + return pyo.Integers + else: + raise RuntimeError(f"Unknown type from coin mip {v.var_type=}") + # BTW: I don't know how to query the mip object for SOS sets + # (maybe it transforms them?) - # Read the MPS file using PuLP - varNames, lp_problem = pulp.LpProblem.fromMPS(mps_file) # Ensure we only use the problem object - + # Read the MPS file and call the coin-mip model m + m = mip.Model(solver_name="cbc") + m.read(mps_path) + # Create a Pyomo model - model = ConcreteModel() - - # Define variables in Pyomo with sanitized names - var_names = [sanitize_name(v.name) for v in lp_problem.variables()] - model.variables = Var(var_names, domain=NonNegativeReals) # Indexed by sanitized names - - # Define the objective function correctly - if lp_problem.sense == pulp.LpMinimize: - model.obj = Objective( - expr=sum(coeff * model.variables[sanitize_name(var.name)] for var, coeff in lp_problem.objective.items()), - sense=1 # Minimize - ) - else: - model.obj = Objective( - expr=sum(coeff * model.variables[sanitize_name(var.name)] for var, coeff in lp_problem.objective.items()), - sense=-1 # Maximize - ) - - # Define constraints - def get_constraint_expr(constraint): - return sum(model.variables[sanitize_name(var)] * coeff for var, coeff in constraint) - - def constraint_rule(m, cname): - """Use the original constraint name for lookup but the sanitized name for Pyomo indexing.""" - constraint = lp_problem.constraints[cname] # Keep original constraint name for lookup - lhs_expr = get_constraint_expr(constraint.items()) + model = pyo.ConcreteModel() + + varDict = dict() # coin mip var to pyomo var + # Add variables to Pyomo model with their bounds and domains + for v in m.vars: + vname = v.name + print(f"Processing variable with name {vname} and type {v.var_type=}") + varDict[v] = pyo.Var(domain=_domain_lookup(v), bounds=(v.lb, v.ub)) + setattr(model, vname, varDict[v]) + #print(f"{dir(v)=}") + + # Add constraints + for c in m.constrs: + # Extract terms correctly from LinExpr + body = sum(coeff * varDict[var] for var, coeff in c.expr.expr.items()) - if constraint.sense == pulp.LpConstraintEQ: - return lhs_expr == constraint.constant - elif constraint.sense == pulp.LpConstraintLE: - return lhs_expr <= constraint.constant - elif constraint.sense == pulp.LpConstraintGE: - return lhs_expr >= constraint.constant + if c.expr.sense == "=": + pyomoC = pyo.Constraint(expr=body == c.rhs) + elif c.expr.sense == ">": + pyomoC = pyo.Constraint(expr=body >= c.rhs) + elif c.expr.sense == "<": + pyomoC = pyo.Constraint(expr=body <= c.rhs) + elif c.expr.sense == "": + raise RuntimeError(f"Unexpected empty sense for constraint {c.name}" + f" from file {mps_path}") + else: + raise RuntimeError(f"Unexpected sense {c.expr.sense=}" + f" for constraint {c.name} from file {mps_path}") + setattr(model, c.name, pyomoC) - # Create constraints with sanitized names, but keep original names for lookup - constraint_mapping = {sanitize_name(cname): cname for cname in lp_problem.constraints.keys()} - model.constraints = Constraint( - constraint_mapping.keys(), - rule=lambda m, sanitized_cname: constraint_rule(m, constraint_mapping[sanitized_cname]) - ) - - return model + # objective function + obj_expr = sum(coeff * varDict[v] for v, coeff in m.objective.expr.items()) + if m.sense == mip.MINIMIZE: + model.objective = pyo.Objective(expr=obj_expr, sense=pyo.minimize) + else: + model.objective = pyo.Objective(expr=obj_expr, sense=pyo.maximize) + return model -########### main for debugging ########## if __name__ == "__main__": + # for testing + solver_name = "cplex" fname = "delme.mps" - print(f"about to read {fname}") - model = read_mps_and_create_pyomo_model(fname) + #fname = "test1.mps" + pyomo_model = def read_mps_and_create_pyomo_model(fname) + pyomo_model.pprint() + + opt = pyo.SolverFactory(solver_name) + opt.solve(pyomo_model) + pyomo_obj = pyo.value(pyomo_model.objective) + + m = mip.Model(solver_name=solver_name) + m.read(fname) + coinstatus = m.optimize() + coin_obj = m.objective_value + print(f"{coinstatus=}, {coin_obj=}, {pyomo_obj=}") - for cname in model.constraints: - print(f"Constraint: {cname}") - print(model.constraints[cname].expr) - print("-" * 80) - model.constraints[cname].pprint() - print("+" * 80) - for v in model.variables: - print(f"Variable Name: '{v}'") # Quoting ensures visibility of leading/trailing spaces - model.pprint() + print("\ncoin var values") + for v in m.vars: + print(f"{v.name}: {v.x}") + + print("\npyomo var values") + for v in pyomo_model.component_objects(pyo.Var, active=True): + print(f"Variable: {v.name}") + for index in v: + print(f" Index: {index}, Value: {v[index].value}") + From 038675e86548b90a93a7192275947a91b5a0220e Mon Sep 17 00:00:00 2001 From: "David L. Woodruff" Date: Mon, 17 Mar 2025 12:47:08 -0700 Subject: [PATCH 08/22] added a test for the mps_reader --- mpisppy/tests/examples/sizes1.mps | 911 ++++++++++++++++++++++++++++++ mpisppy/tests/examples/test1.mps | 30 + mpisppy/tests/test_mps.py | 46 ++ mpisppy/utils/mps_reader.py | 4 +- 4 files changed, 989 insertions(+), 2 deletions(-) create mode 100644 mpisppy/tests/examples/sizes1.mps create mode 100644 mpisppy/tests/examples/test1.mps create mode 100644 mpisppy/tests/test_mps.py diff --git a/mpisppy/tests/examples/sizes1.mps b/mpisppy/tests/examples/sizes1.mps new file mode 100644 index 000000000..13f3ebb2a --- /dev/null +++ b/mpisppy/tests/examples/sizes1.mps @@ -0,0 +1,911 @@ +* Source: Pyomo MPS Writer +* Format: Free MPS +* +NAME unknown +OBJSENSE + MIN +ROWS + N Total_Cost_Objective + G c_l_DemandSatisfiedFirstStage(1)_ + G c_l_DemandSatisfiedFirstStage(2)_ + G c_l_DemandSatisfiedFirstStage(3)_ + G c_l_DemandSatisfiedFirstStage(4)_ + G c_l_DemandSatisfiedFirstStage(5)_ + G c_l_DemandSatisfiedFirstStage(6)_ + G c_l_DemandSatisfiedFirstStage(7)_ + G c_l_DemandSatisfiedFirstStage(8)_ + G c_l_DemandSatisfiedFirstStage(9)_ + G c_l_DemandSatisfiedFirstStage(10)_ + G c_l_DemandSatisfiedSecondStage(1)_ + G c_l_DemandSatisfiedSecondStage(2)_ + G c_l_DemandSatisfiedSecondStage(3)_ + G c_l_DemandSatisfiedSecondStage(4)_ + G c_l_DemandSatisfiedSecondStage(5)_ + G c_l_DemandSatisfiedSecondStage(6)_ + G c_l_DemandSatisfiedSecondStage(7)_ + G c_l_DemandSatisfiedSecondStage(8)_ + G c_l_DemandSatisfiedSecondStage(9)_ + G c_l_DemandSatisfiedSecondStage(10)_ + L c_u_EnforceProductionBinaryFirstStage(1)_ + L c_u_EnforceProductionBinaryFirstStage(2)_ + L c_u_EnforceProductionBinaryFirstStage(3)_ + L c_u_EnforceProductionBinaryFirstStage(4)_ + L c_u_EnforceProductionBinaryFirstStage(5)_ + L c_u_EnforceProductionBinaryFirstStage(6)_ + L c_u_EnforceProductionBinaryFirstStage(7)_ + L c_u_EnforceProductionBinaryFirstStage(8)_ + L c_u_EnforceProductionBinaryFirstStage(9)_ + L c_u_EnforceProductionBinaryFirstStage(10)_ + L c_u_EnforceProductionBinarySecondStage(1)_ + L c_u_EnforceProductionBinarySecondStage(2)_ + L c_u_EnforceProductionBinarySecondStage(3)_ + L c_u_EnforceProductionBinarySecondStage(4)_ + L c_u_EnforceProductionBinarySecondStage(5)_ + L c_u_EnforceProductionBinarySecondStage(6)_ + L c_u_EnforceProductionBinarySecondStage(7)_ + L c_u_EnforceProductionBinarySecondStage(8)_ + L c_u_EnforceProductionBinarySecondStage(9)_ + L c_u_EnforceProductionBinarySecondStage(10)_ + L c_u_EnforceCapacityLimitFirstStage_ + L c_u_EnforceCapacityLimitSecondStage_ + L c_u_EnforceInventoryFirstStage(1)_ + L c_u_EnforceInventoryFirstStage(2)_ + L c_u_EnforceInventoryFirstStage(3)_ + L c_u_EnforceInventoryFirstStage(4)_ + L c_u_EnforceInventoryFirstStage(5)_ + L c_u_EnforceInventoryFirstStage(6)_ + L c_u_EnforceInventoryFirstStage(7)_ + L c_u_EnforceInventoryFirstStage(8)_ + L c_u_EnforceInventoryFirstStage(9)_ + L c_u_EnforceInventoryFirstStage(10)_ + L c_u_EnforceInventorySecondStage(1)_ + L c_u_EnforceInventorySecondStage(2)_ + L c_u_EnforceInventorySecondStage(3)_ + L c_u_EnforceInventorySecondStage(4)_ + L c_u_EnforceInventorySecondStage(5)_ + L c_u_EnforceInventorySecondStage(6)_ + L c_u_EnforceInventorySecondStage(7)_ + L c_u_EnforceInventorySecondStage(8)_ + L c_u_EnforceInventorySecondStage(9)_ + L c_u_EnforceInventorySecondStage(10)_ +COLUMNS + ProduceSizeFirstStage(1) Total_Cost_Objective 453 + ProduceSizeFirstStage(1) c_u_EnforceProductionBinaryFirstStage(1)_ -200000 + ProduceSizeFirstStage(2) Total_Cost_Objective 453 + ProduceSizeFirstStage(2) c_u_EnforceProductionBinaryFirstStage(2)_ -200000 + ProduceSizeFirstStage(3) Total_Cost_Objective 453 + ProduceSizeFirstStage(3) c_u_EnforceProductionBinaryFirstStage(3)_ -200000 + ProduceSizeFirstStage(4) Total_Cost_Objective 453 + ProduceSizeFirstStage(4) c_u_EnforceProductionBinaryFirstStage(4)_ -200000 + ProduceSizeFirstStage(5) Total_Cost_Objective 453 + ProduceSizeFirstStage(5) c_u_EnforceProductionBinaryFirstStage(5)_ -200000 + ProduceSizeFirstStage(6) Total_Cost_Objective 453 + ProduceSizeFirstStage(6) c_u_EnforceProductionBinaryFirstStage(6)_ -200000 + ProduceSizeFirstStage(7) Total_Cost_Objective 453 + ProduceSizeFirstStage(7) c_u_EnforceProductionBinaryFirstStage(7)_ -200000 + ProduceSizeFirstStage(8) Total_Cost_Objective 453 + ProduceSizeFirstStage(8) c_u_EnforceProductionBinaryFirstStage(8)_ -200000 + ProduceSizeFirstStage(9) Total_Cost_Objective 453 + ProduceSizeFirstStage(9) c_u_EnforceProductionBinaryFirstStage(9)_ -200000 + ProduceSizeFirstStage(10) Total_Cost_Objective 453 + ProduceSizeFirstStage(10) c_u_EnforceProductionBinaryFirstStage(10)_ -200000 + ProduceSizeSecondStage(1) Total_Cost_Objective 453 + ProduceSizeSecondStage(1) c_u_EnforceProductionBinarySecondStage(1)_ -200000 + ProduceSizeSecondStage(2) Total_Cost_Objective 453 + ProduceSizeSecondStage(2) c_u_EnforceProductionBinarySecondStage(2)_ -200000 + ProduceSizeSecondStage(3) Total_Cost_Objective 453 + ProduceSizeSecondStage(3) c_u_EnforceProductionBinarySecondStage(3)_ -200000 + ProduceSizeSecondStage(4) Total_Cost_Objective 453 + ProduceSizeSecondStage(4) c_u_EnforceProductionBinarySecondStage(4)_ -200000 + ProduceSizeSecondStage(5) Total_Cost_Objective 453 + ProduceSizeSecondStage(5) c_u_EnforceProductionBinarySecondStage(5)_ -200000 + ProduceSizeSecondStage(6) Total_Cost_Objective 453 + ProduceSizeSecondStage(6) c_u_EnforceProductionBinarySecondStage(6)_ -200000 + ProduceSizeSecondStage(7) Total_Cost_Objective 453 + ProduceSizeSecondStage(7) c_u_EnforceProductionBinarySecondStage(7)_ -200000 + ProduceSizeSecondStage(8) Total_Cost_Objective 453 + ProduceSizeSecondStage(8) c_u_EnforceProductionBinarySecondStage(8)_ -200000 + ProduceSizeSecondStage(9) Total_Cost_Objective 453 + ProduceSizeSecondStage(9) c_u_EnforceProductionBinarySecondStage(9)_ -200000 + ProduceSizeSecondStage(10) Total_Cost_Objective 453 + ProduceSizeSecondStage(10) c_u_EnforceProductionBinarySecondStage(10)_ -200000 + NumProducedFirstStage(1) Total_Cost_Objective 0.748 + NumProducedFirstStage(1) c_u_EnforceProductionBinaryFirstStage(1)_ 1 + NumProducedFirstStage(1) c_u_EnforceCapacityLimitFirstStage_ 1 + NumProducedFirstStage(1) c_u_EnforceInventoryFirstStage(1)_ -1 + NumProducedFirstStage(1) c_u_EnforceInventorySecondStage(1)_ -1 + NumProducedFirstStage(2) Total_Cost_Objective 0.75839999999999996 + NumProducedFirstStage(2) c_u_EnforceProductionBinaryFirstStage(2)_ 1 + NumProducedFirstStage(2) c_u_EnforceCapacityLimitFirstStage_ 1 + NumProducedFirstStage(2) c_u_EnforceInventoryFirstStage(2)_ -1 + NumProducedFirstStage(2) c_u_EnforceInventorySecondStage(2)_ -1 + NumProducedFirstStage(3) Total_Cost_Objective 0.76880000000000004 + NumProducedFirstStage(3) c_u_EnforceProductionBinaryFirstStage(3)_ 1 + NumProducedFirstStage(3) c_u_EnforceCapacityLimitFirstStage_ 1 + NumProducedFirstStage(3) c_u_EnforceInventoryFirstStage(3)_ -1 + NumProducedFirstStage(3) c_u_EnforceInventorySecondStage(3)_ -1 + NumProducedFirstStage(4) Total_Cost_Objective 0.7792 + NumProducedFirstStage(4) c_u_EnforceProductionBinaryFirstStage(4)_ 1 + NumProducedFirstStage(4) c_u_EnforceCapacityLimitFirstStage_ 1 + NumProducedFirstStage(4) c_u_EnforceInventoryFirstStage(4)_ -1 + NumProducedFirstStage(4) c_u_EnforceInventorySecondStage(4)_ -1 + NumProducedFirstStage(5) Total_Cost_Objective 0.78959999999999997 + NumProducedFirstStage(5) c_u_EnforceProductionBinaryFirstStage(5)_ 1 + NumProducedFirstStage(5) c_u_EnforceCapacityLimitFirstStage_ 1 + NumProducedFirstStage(5) c_u_EnforceInventoryFirstStage(5)_ -1 + NumProducedFirstStage(5) c_u_EnforceInventorySecondStage(5)_ -1 + NumProducedFirstStage(6) Total_Cost_Objective 0.80000000000000004 + NumProducedFirstStage(6) c_u_EnforceProductionBinaryFirstStage(6)_ 1 + NumProducedFirstStage(6) c_u_EnforceCapacityLimitFirstStage_ 1 + NumProducedFirstStage(6) c_u_EnforceInventoryFirstStage(6)_ -1 + NumProducedFirstStage(6) c_u_EnforceInventorySecondStage(6)_ -1 + NumProducedFirstStage(7) Total_Cost_Objective 0.81040000000000001 + NumProducedFirstStage(7) c_u_EnforceProductionBinaryFirstStage(7)_ 1 + NumProducedFirstStage(7) c_u_EnforceCapacityLimitFirstStage_ 1 + NumProducedFirstStage(7) c_u_EnforceInventoryFirstStage(7)_ -1 + NumProducedFirstStage(7) c_u_EnforceInventorySecondStage(7)_ -1 + NumProducedFirstStage(8) Total_Cost_Objective 0.82079999999999997 + NumProducedFirstStage(8) c_u_EnforceProductionBinaryFirstStage(8)_ 1 + NumProducedFirstStage(8) c_u_EnforceCapacityLimitFirstStage_ 1 + NumProducedFirstStage(8) c_u_EnforceInventoryFirstStage(8)_ -1 + NumProducedFirstStage(8) c_u_EnforceInventorySecondStage(8)_ -1 + NumProducedFirstStage(9) Total_Cost_Objective 0.83120000000000005 + NumProducedFirstStage(9) c_u_EnforceProductionBinaryFirstStage(9)_ 1 + NumProducedFirstStage(9) c_u_EnforceCapacityLimitFirstStage_ 1 + NumProducedFirstStage(9) c_u_EnforceInventoryFirstStage(9)_ -1 + NumProducedFirstStage(9) c_u_EnforceInventorySecondStage(9)_ -1 + NumProducedFirstStage(10) Total_Cost_Objective 0.84160000000000001 + NumProducedFirstStage(10) c_u_EnforceProductionBinaryFirstStage(10)_ 1 + NumProducedFirstStage(10) c_u_EnforceCapacityLimitFirstStage_ 1 + NumProducedFirstStage(10) c_u_EnforceInventoryFirstStage(10)_ -1 + NumProducedFirstStage(10) c_u_EnforceInventorySecondStage(10)_ -1 + NumProducedSecondStage(1) Total_Cost_Objective 0.748 + NumProducedSecondStage(1) c_u_EnforceProductionBinarySecondStage(1)_ 1 + NumProducedSecondStage(1) c_u_EnforceCapacityLimitSecondStage_ 1 + NumProducedSecondStage(1) c_u_EnforceInventorySecondStage(1)_ -1 + NumProducedSecondStage(2) Total_Cost_Objective 0.75839999999999996 + NumProducedSecondStage(2) c_u_EnforceProductionBinarySecondStage(2)_ 1 + NumProducedSecondStage(2) c_u_EnforceCapacityLimitSecondStage_ 1 + NumProducedSecondStage(2) c_u_EnforceInventorySecondStage(2)_ -1 + NumProducedSecondStage(3) Total_Cost_Objective 0.76880000000000004 + NumProducedSecondStage(3) c_u_EnforceProductionBinarySecondStage(3)_ 1 + NumProducedSecondStage(3) c_u_EnforceCapacityLimitSecondStage_ 1 + NumProducedSecondStage(3) c_u_EnforceInventorySecondStage(3)_ -1 + NumProducedSecondStage(4) Total_Cost_Objective 0.7792 + NumProducedSecondStage(4) c_u_EnforceProductionBinarySecondStage(4)_ 1 + NumProducedSecondStage(4) c_u_EnforceCapacityLimitSecondStage_ 1 + NumProducedSecondStage(4) c_u_EnforceInventorySecondStage(4)_ -1 + NumProducedSecondStage(5) Total_Cost_Objective 0.78959999999999997 + NumProducedSecondStage(5) c_u_EnforceProductionBinarySecondStage(5)_ 1 + NumProducedSecondStage(5) c_u_EnforceCapacityLimitSecondStage_ 1 + NumProducedSecondStage(5) c_u_EnforceInventorySecondStage(5)_ -1 + NumProducedSecondStage(6) Total_Cost_Objective 0.80000000000000004 + NumProducedSecondStage(6) c_u_EnforceProductionBinarySecondStage(6)_ 1 + NumProducedSecondStage(6) c_u_EnforceCapacityLimitSecondStage_ 1 + NumProducedSecondStage(6) c_u_EnforceInventorySecondStage(6)_ -1 + NumProducedSecondStage(7) Total_Cost_Objective 0.81040000000000001 + NumProducedSecondStage(7) c_u_EnforceProductionBinarySecondStage(7)_ 1 + NumProducedSecondStage(7) c_u_EnforceCapacityLimitSecondStage_ 1 + NumProducedSecondStage(7) c_u_EnforceInventorySecondStage(7)_ -1 + NumProducedSecondStage(8) Total_Cost_Objective 0.82079999999999997 + NumProducedSecondStage(8) c_u_EnforceProductionBinarySecondStage(8)_ 1 + NumProducedSecondStage(8) c_u_EnforceCapacityLimitSecondStage_ 1 + NumProducedSecondStage(8) c_u_EnforceInventorySecondStage(8)_ -1 + NumProducedSecondStage(9) Total_Cost_Objective 0.83120000000000005 + NumProducedSecondStage(9) c_u_EnforceProductionBinarySecondStage(9)_ 1 + NumProducedSecondStage(9) c_u_EnforceCapacityLimitSecondStage_ 1 + NumProducedSecondStage(9) c_u_EnforceInventorySecondStage(9)_ -1 + NumProducedSecondStage(10) Total_Cost_Objective 0.84160000000000001 + NumProducedSecondStage(10) c_u_EnforceProductionBinarySecondStage(10)_ 1 + NumProducedSecondStage(10) c_u_EnforceCapacityLimitSecondStage_ 1 + NumProducedSecondStage(10) c_u_EnforceInventorySecondStage(10)_ -1 + NumUnitsCutFirstStage(1_1) c_l_DemandSatisfiedFirstStage(1)_ 1 + NumUnitsCutFirstStage(1_1) c_u_EnforceInventoryFirstStage(1)_ 1 + NumUnitsCutFirstStage(1_1) c_u_EnforceInventorySecondStage(1)_ 1 + NumUnitsCutFirstStage(2_1) Total_Cost_Objective -0.0080000000000000002 + NumUnitsCutFirstStage(2_1) c_l_DemandSatisfiedFirstStage(1)_ 1 + NumUnitsCutFirstStage(2_1) c_u_EnforceInventoryFirstStage(2)_ 1 + NumUnitsCutFirstStage(2_1) c_u_EnforceInventorySecondStage(2)_ 1 + NumUnitsCutFirstStage(2_2) c_l_DemandSatisfiedFirstStage(2)_ 1 + NumUnitsCutFirstStage(2_2) c_u_EnforceInventoryFirstStage(2)_ 1 + NumUnitsCutFirstStage(2_2) c_u_EnforceInventorySecondStage(2)_ 1 + NumUnitsCutFirstStage(3_1) Total_Cost_Objective -0.0080000000000000002 + NumUnitsCutFirstStage(3_1) c_l_DemandSatisfiedFirstStage(1)_ 1 + NumUnitsCutFirstStage(3_1) c_u_EnforceInventoryFirstStage(3)_ 1 + NumUnitsCutFirstStage(3_1) c_u_EnforceInventorySecondStage(3)_ 1 + NumUnitsCutFirstStage(3_2) Total_Cost_Objective -0.0080000000000000002 + NumUnitsCutFirstStage(3_2) c_l_DemandSatisfiedFirstStage(2)_ 1 + NumUnitsCutFirstStage(3_2) c_u_EnforceInventoryFirstStage(3)_ 1 + NumUnitsCutFirstStage(3_2) c_u_EnforceInventorySecondStage(3)_ 1 + NumUnitsCutFirstStage(3_3) c_l_DemandSatisfiedFirstStage(3)_ 1 + NumUnitsCutFirstStage(3_3) c_u_EnforceInventoryFirstStage(3)_ 1 + NumUnitsCutFirstStage(3_3) c_u_EnforceInventorySecondStage(3)_ 1 + NumUnitsCutFirstStage(4_1) Total_Cost_Objective -0.0080000000000000002 + NumUnitsCutFirstStage(4_1) c_l_DemandSatisfiedFirstStage(1)_ 1 + NumUnitsCutFirstStage(4_1) c_u_EnforceInventoryFirstStage(4)_ 1 + NumUnitsCutFirstStage(4_1) c_u_EnforceInventorySecondStage(4)_ 1 + NumUnitsCutFirstStage(4_2) Total_Cost_Objective -0.0080000000000000002 + NumUnitsCutFirstStage(4_2) c_l_DemandSatisfiedFirstStage(2)_ 1 + NumUnitsCutFirstStage(4_2) c_u_EnforceInventoryFirstStage(4)_ 1 + NumUnitsCutFirstStage(4_2) c_u_EnforceInventorySecondStage(4)_ 1 + NumUnitsCutFirstStage(4_3) Total_Cost_Objective -0.0080000000000000002 + NumUnitsCutFirstStage(4_3) c_l_DemandSatisfiedFirstStage(3)_ 1 + NumUnitsCutFirstStage(4_3) c_u_EnforceInventoryFirstStage(4)_ 1 + NumUnitsCutFirstStage(4_3) c_u_EnforceInventorySecondStage(4)_ 1 + NumUnitsCutFirstStage(4_4) c_l_DemandSatisfiedFirstStage(4)_ 1 + NumUnitsCutFirstStage(4_4) c_u_EnforceInventoryFirstStage(4)_ 1 + NumUnitsCutFirstStage(4_4) c_u_EnforceInventorySecondStage(4)_ 1 + NumUnitsCutFirstStage(5_1) Total_Cost_Objective -0.0080000000000000002 + NumUnitsCutFirstStage(5_1) c_l_DemandSatisfiedFirstStage(1)_ 1 + NumUnitsCutFirstStage(5_1) c_u_EnforceInventoryFirstStage(5)_ 1 + NumUnitsCutFirstStage(5_1) c_u_EnforceInventorySecondStage(5)_ 1 + NumUnitsCutFirstStage(5_2) Total_Cost_Objective -0.0080000000000000002 + NumUnitsCutFirstStage(5_2) c_l_DemandSatisfiedFirstStage(2)_ 1 + NumUnitsCutFirstStage(5_2) c_u_EnforceInventoryFirstStage(5)_ 1 + NumUnitsCutFirstStage(5_2) c_u_EnforceInventorySecondStage(5)_ 1 + NumUnitsCutFirstStage(5_3) Total_Cost_Objective -0.0080000000000000002 + NumUnitsCutFirstStage(5_3) c_l_DemandSatisfiedFirstStage(3)_ 1 + NumUnitsCutFirstStage(5_3) c_u_EnforceInventoryFirstStage(5)_ 1 + NumUnitsCutFirstStage(5_3) c_u_EnforceInventorySecondStage(5)_ 1 + NumUnitsCutFirstStage(5_4) Total_Cost_Objective -0.0080000000000000002 + NumUnitsCutFirstStage(5_4) c_l_DemandSatisfiedFirstStage(4)_ 1 + NumUnitsCutFirstStage(5_4) c_u_EnforceInventoryFirstStage(5)_ 1 + NumUnitsCutFirstStage(5_4) c_u_EnforceInventorySecondStage(5)_ 1 + NumUnitsCutFirstStage(5_5) c_l_DemandSatisfiedFirstStage(5)_ 1 + NumUnitsCutFirstStage(5_5) c_u_EnforceInventoryFirstStage(5)_ 1 + NumUnitsCutFirstStage(5_5) c_u_EnforceInventorySecondStage(5)_ 1 + NumUnitsCutFirstStage(6_1) Total_Cost_Objective -0.0080000000000000002 + NumUnitsCutFirstStage(6_1) c_l_DemandSatisfiedFirstStage(1)_ 1 + NumUnitsCutFirstStage(6_1) c_u_EnforceInventoryFirstStage(6)_ 1 + NumUnitsCutFirstStage(6_1) c_u_EnforceInventorySecondStage(6)_ 1 + NumUnitsCutFirstStage(6_2) Total_Cost_Objective -0.0080000000000000002 + NumUnitsCutFirstStage(6_2) c_l_DemandSatisfiedFirstStage(2)_ 1 + NumUnitsCutFirstStage(6_2) c_u_EnforceInventoryFirstStage(6)_ 1 + NumUnitsCutFirstStage(6_2) c_u_EnforceInventorySecondStage(6)_ 1 + NumUnitsCutFirstStage(6_3) Total_Cost_Objective -0.0080000000000000002 + NumUnitsCutFirstStage(6_3) c_l_DemandSatisfiedFirstStage(3)_ 1 + NumUnitsCutFirstStage(6_3) c_u_EnforceInventoryFirstStage(6)_ 1 + NumUnitsCutFirstStage(6_3) c_u_EnforceInventorySecondStage(6)_ 1 + NumUnitsCutFirstStage(6_4) Total_Cost_Objective -0.0080000000000000002 + NumUnitsCutFirstStage(6_4) c_l_DemandSatisfiedFirstStage(4)_ 1 + NumUnitsCutFirstStage(6_4) c_u_EnforceInventoryFirstStage(6)_ 1 + NumUnitsCutFirstStage(6_4) c_u_EnforceInventorySecondStage(6)_ 1 + NumUnitsCutFirstStage(6_5) Total_Cost_Objective -0.0080000000000000002 + NumUnitsCutFirstStage(6_5) c_l_DemandSatisfiedFirstStage(5)_ 1 + NumUnitsCutFirstStage(6_5) c_u_EnforceInventoryFirstStage(6)_ 1 + NumUnitsCutFirstStage(6_5) c_u_EnforceInventorySecondStage(6)_ 1 + NumUnitsCutFirstStage(6_6) c_l_DemandSatisfiedFirstStage(6)_ 1 + NumUnitsCutFirstStage(6_6) c_u_EnforceInventoryFirstStage(6)_ 1 + NumUnitsCutFirstStage(6_6) c_u_EnforceInventorySecondStage(6)_ 1 + NumUnitsCutFirstStage(7_1) Total_Cost_Objective -0.0080000000000000002 + NumUnitsCutFirstStage(7_1) c_l_DemandSatisfiedFirstStage(1)_ 1 + NumUnitsCutFirstStage(7_1) c_u_EnforceInventoryFirstStage(7)_ 1 + NumUnitsCutFirstStage(7_1) c_u_EnforceInventorySecondStage(7)_ 1 + NumUnitsCutFirstStage(7_2) Total_Cost_Objective -0.0080000000000000002 + NumUnitsCutFirstStage(7_2) c_l_DemandSatisfiedFirstStage(2)_ 1 + NumUnitsCutFirstStage(7_2) c_u_EnforceInventoryFirstStage(7)_ 1 + NumUnitsCutFirstStage(7_2) c_u_EnforceInventorySecondStage(7)_ 1 + NumUnitsCutFirstStage(7_3) Total_Cost_Objective -0.0080000000000000002 + NumUnitsCutFirstStage(7_3) c_l_DemandSatisfiedFirstStage(3)_ 1 + NumUnitsCutFirstStage(7_3) c_u_EnforceInventoryFirstStage(7)_ 1 + NumUnitsCutFirstStage(7_3) c_u_EnforceInventorySecondStage(7)_ 1 + NumUnitsCutFirstStage(7_4) Total_Cost_Objective -0.0080000000000000002 + NumUnitsCutFirstStage(7_4) c_l_DemandSatisfiedFirstStage(4)_ 1 + NumUnitsCutFirstStage(7_4) c_u_EnforceInventoryFirstStage(7)_ 1 + NumUnitsCutFirstStage(7_4) c_u_EnforceInventorySecondStage(7)_ 1 + NumUnitsCutFirstStage(7_5) Total_Cost_Objective -0.0080000000000000002 + NumUnitsCutFirstStage(7_5) c_l_DemandSatisfiedFirstStage(5)_ 1 + NumUnitsCutFirstStage(7_5) c_u_EnforceInventoryFirstStage(7)_ 1 + NumUnitsCutFirstStage(7_5) c_u_EnforceInventorySecondStage(7)_ 1 + NumUnitsCutFirstStage(7_6) Total_Cost_Objective -0.0080000000000000002 + NumUnitsCutFirstStage(7_6) c_l_DemandSatisfiedFirstStage(6)_ 1 + NumUnitsCutFirstStage(7_6) c_u_EnforceInventoryFirstStage(7)_ 1 + NumUnitsCutFirstStage(7_6) c_u_EnforceInventorySecondStage(7)_ 1 + NumUnitsCutFirstStage(7_7) c_l_DemandSatisfiedFirstStage(7)_ 1 + NumUnitsCutFirstStage(7_7) c_u_EnforceInventoryFirstStage(7)_ 1 + NumUnitsCutFirstStage(7_7) c_u_EnforceInventorySecondStage(7)_ 1 + NumUnitsCutFirstStage(8_1) Total_Cost_Objective -0.0080000000000000002 + NumUnitsCutFirstStage(8_1) c_l_DemandSatisfiedFirstStage(1)_ 1 + NumUnitsCutFirstStage(8_1) c_u_EnforceInventoryFirstStage(8)_ 1 + NumUnitsCutFirstStage(8_1) c_u_EnforceInventorySecondStage(8)_ 1 + NumUnitsCutFirstStage(8_2) Total_Cost_Objective -0.0080000000000000002 + NumUnitsCutFirstStage(8_2) c_l_DemandSatisfiedFirstStage(2)_ 1 + NumUnitsCutFirstStage(8_2) c_u_EnforceInventoryFirstStage(8)_ 1 + NumUnitsCutFirstStage(8_2) c_u_EnforceInventorySecondStage(8)_ 1 + NumUnitsCutFirstStage(8_3) Total_Cost_Objective -0.0080000000000000002 + NumUnitsCutFirstStage(8_3) c_l_DemandSatisfiedFirstStage(3)_ 1 + NumUnitsCutFirstStage(8_3) c_u_EnforceInventoryFirstStage(8)_ 1 + NumUnitsCutFirstStage(8_3) c_u_EnforceInventorySecondStage(8)_ 1 + NumUnitsCutFirstStage(8_4) Total_Cost_Objective -0.0080000000000000002 + NumUnitsCutFirstStage(8_4) c_l_DemandSatisfiedFirstStage(4)_ 1 + NumUnitsCutFirstStage(8_4) c_u_EnforceInventoryFirstStage(8)_ 1 + NumUnitsCutFirstStage(8_4) c_u_EnforceInventorySecondStage(8)_ 1 + NumUnitsCutFirstStage(8_5) Total_Cost_Objective -0.0080000000000000002 + NumUnitsCutFirstStage(8_5) c_l_DemandSatisfiedFirstStage(5)_ 1 + NumUnitsCutFirstStage(8_5) c_u_EnforceInventoryFirstStage(8)_ 1 + NumUnitsCutFirstStage(8_5) c_u_EnforceInventorySecondStage(8)_ 1 + NumUnitsCutFirstStage(8_6) Total_Cost_Objective -0.0080000000000000002 + NumUnitsCutFirstStage(8_6) c_l_DemandSatisfiedFirstStage(6)_ 1 + NumUnitsCutFirstStage(8_6) c_u_EnforceInventoryFirstStage(8)_ 1 + NumUnitsCutFirstStage(8_6) c_u_EnforceInventorySecondStage(8)_ 1 + NumUnitsCutFirstStage(8_7) Total_Cost_Objective -0.0080000000000000002 + NumUnitsCutFirstStage(8_7) c_l_DemandSatisfiedFirstStage(7)_ 1 + NumUnitsCutFirstStage(8_7) c_u_EnforceInventoryFirstStage(8)_ 1 + NumUnitsCutFirstStage(8_7) c_u_EnforceInventorySecondStage(8)_ 1 + NumUnitsCutFirstStage(8_8) c_l_DemandSatisfiedFirstStage(8)_ 1 + NumUnitsCutFirstStage(8_8) c_u_EnforceInventoryFirstStage(8)_ 1 + NumUnitsCutFirstStage(8_8) c_u_EnforceInventorySecondStage(8)_ 1 + NumUnitsCutFirstStage(9_1) Total_Cost_Objective -0.0080000000000000002 + NumUnitsCutFirstStage(9_1) c_l_DemandSatisfiedFirstStage(1)_ 1 + NumUnitsCutFirstStage(9_1) c_u_EnforceInventoryFirstStage(9)_ 1 + NumUnitsCutFirstStage(9_1) c_u_EnforceInventorySecondStage(9)_ 1 + NumUnitsCutFirstStage(9_2) Total_Cost_Objective -0.0080000000000000002 + NumUnitsCutFirstStage(9_2) c_l_DemandSatisfiedFirstStage(2)_ 1 + NumUnitsCutFirstStage(9_2) c_u_EnforceInventoryFirstStage(9)_ 1 + NumUnitsCutFirstStage(9_2) c_u_EnforceInventorySecondStage(9)_ 1 + NumUnitsCutFirstStage(9_3) Total_Cost_Objective -0.0080000000000000002 + NumUnitsCutFirstStage(9_3) c_l_DemandSatisfiedFirstStage(3)_ 1 + NumUnitsCutFirstStage(9_3) c_u_EnforceInventoryFirstStage(9)_ 1 + NumUnitsCutFirstStage(9_3) c_u_EnforceInventorySecondStage(9)_ 1 + NumUnitsCutFirstStage(9_4) Total_Cost_Objective -0.0080000000000000002 + NumUnitsCutFirstStage(9_4) c_l_DemandSatisfiedFirstStage(4)_ 1 + NumUnitsCutFirstStage(9_4) c_u_EnforceInventoryFirstStage(9)_ 1 + NumUnitsCutFirstStage(9_4) c_u_EnforceInventorySecondStage(9)_ 1 + NumUnitsCutFirstStage(9_5) Total_Cost_Objective -0.0080000000000000002 + NumUnitsCutFirstStage(9_5) c_l_DemandSatisfiedFirstStage(5)_ 1 + NumUnitsCutFirstStage(9_5) c_u_EnforceInventoryFirstStage(9)_ 1 + NumUnitsCutFirstStage(9_5) c_u_EnforceInventorySecondStage(9)_ 1 + NumUnitsCutFirstStage(9_6) Total_Cost_Objective -0.0080000000000000002 + NumUnitsCutFirstStage(9_6) c_l_DemandSatisfiedFirstStage(6)_ 1 + NumUnitsCutFirstStage(9_6) c_u_EnforceInventoryFirstStage(9)_ 1 + NumUnitsCutFirstStage(9_6) c_u_EnforceInventorySecondStage(9)_ 1 + NumUnitsCutFirstStage(9_7) Total_Cost_Objective -0.0080000000000000002 + NumUnitsCutFirstStage(9_7) c_l_DemandSatisfiedFirstStage(7)_ 1 + NumUnitsCutFirstStage(9_7) c_u_EnforceInventoryFirstStage(9)_ 1 + NumUnitsCutFirstStage(9_7) c_u_EnforceInventorySecondStage(9)_ 1 + NumUnitsCutFirstStage(9_8) Total_Cost_Objective -0.0080000000000000002 + NumUnitsCutFirstStage(9_8) c_l_DemandSatisfiedFirstStage(8)_ 1 + NumUnitsCutFirstStage(9_8) c_u_EnforceInventoryFirstStage(9)_ 1 + NumUnitsCutFirstStage(9_8) c_u_EnforceInventorySecondStage(9)_ 1 + NumUnitsCutFirstStage(9_9) c_l_DemandSatisfiedFirstStage(9)_ 1 + NumUnitsCutFirstStage(9_9) c_u_EnforceInventoryFirstStage(9)_ 1 + NumUnitsCutFirstStage(9_9) c_u_EnforceInventorySecondStage(9)_ 1 + NumUnitsCutFirstStage(10_1) Total_Cost_Objective -0.0080000000000000002 + NumUnitsCutFirstStage(10_1) c_l_DemandSatisfiedFirstStage(1)_ 1 + NumUnitsCutFirstStage(10_1) c_u_EnforceInventoryFirstStage(10)_ 1 + NumUnitsCutFirstStage(10_1) c_u_EnforceInventorySecondStage(10)_ 1 + NumUnitsCutFirstStage(10_2) Total_Cost_Objective -0.0080000000000000002 + NumUnitsCutFirstStage(10_2) c_l_DemandSatisfiedFirstStage(2)_ 1 + NumUnitsCutFirstStage(10_2) c_u_EnforceInventoryFirstStage(10)_ 1 + NumUnitsCutFirstStage(10_2) c_u_EnforceInventorySecondStage(10)_ 1 + NumUnitsCutFirstStage(10_3) Total_Cost_Objective -0.0080000000000000002 + NumUnitsCutFirstStage(10_3) c_l_DemandSatisfiedFirstStage(3)_ 1 + NumUnitsCutFirstStage(10_3) c_u_EnforceInventoryFirstStage(10)_ 1 + NumUnitsCutFirstStage(10_3) c_u_EnforceInventorySecondStage(10)_ 1 + NumUnitsCutFirstStage(10_4) Total_Cost_Objective -0.0080000000000000002 + NumUnitsCutFirstStage(10_4) c_l_DemandSatisfiedFirstStage(4)_ 1 + NumUnitsCutFirstStage(10_4) c_u_EnforceInventoryFirstStage(10)_ 1 + NumUnitsCutFirstStage(10_4) c_u_EnforceInventorySecondStage(10)_ 1 + NumUnitsCutFirstStage(10_5) Total_Cost_Objective -0.0080000000000000002 + NumUnitsCutFirstStage(10_5) c_l_DemandSatisfiedFirstStage(5)_ 1 + NumUnitsCutFirstStage(10_5) c_u_EnforceInventoryFirstStage(10)_ 1 + NumUnitsCutFirstStage(10_5) c_u_EnforceInventorySecondStage(10)_ 1 + NumUnitsCutFirstStage(10_6) Total_Cost_Objective -0.0080000000000000002 + NumUnitsCutFirstStage(10_6) c_l_DemandSatisfiedFirstStage(6)_ 1 + NumUnitsCutFirstStage(10_6) c_u_EnforceInventoryFirstStage(10)_ 1 + NumUnitsCutFirstStage(10_6) c_u_EnforceInventorySecondStage(10)_ 1 + NumUnitsCutFirstStage(10_7) Total_Cost_Objective -0.0080000000000000002 + NumUnitsCutFirstStage(10_7) c_l_DemandSatisfiedFirstStage(7)_ 1 + NumUnitsCutFirstStage(10_7) c_u_EnforceInventoryFirstStage(10)_ 1 + NumUnitsCutFirstStage(10_7) c_u_EnforceInventorySecondStage(10)_ 1 + NumUnitsCutFirstStage(10_8) Total_Cost_Objective -0.0080000000000000002 + NumUnitsCutFirstStage(10_8) c_l_DemandSatisfiedFirstStage(8)_ 1 + NumUnitsCutFirstStage(10_8) c_u_EnforceInventoryFirstStage(10)_ 1 + NumUnitsCutFirstStage(10_8) c_u_EnforceInventorySecondStage(10)_ 1 + NumUnitsCutFirstStage(10_9) Total_Cost_Objective -0.0080000000000000002 + NumUnitsCutFirstStage(10_9) c_l_DemandSatisfiedFirstStage(9)_ 1 + NumUnitsCutFirstStage(10_9) c_u_EnforceInventoryFirstStage(10)_ 1 + NumUnitsCutFirstStage(10_9) c_u_EnforceInventorySecondStage(10)_ 1 + NumUnitsCutFirstStage(10_10) c_l_DemandSatisfiedFirstStage(10)_ 1 + NumUnitsCutFirstStage(10_10) c_u_EnforceInventoryFirstStage(10)_ 1 + NumUnitsCutFirstStage(10_10) c_u_EnforceInventorySecondStage(10)_ 1 + NumUnitsCutSecondStage(1_1) c_l_DemandSatisfiedSecondStage(1)_ 1 + NumUnitsCutSecondStage(1_1) c_u_EnforceInventorySecondStage(1)_ 1 + NumUnitsCutSecondStage(2_1) Total_Cost_Objective -0.0080000000000000002 + NumUnitsCutSecondStage(2_1) c_l_DemandSatisfiedSecondStage(1)_ 1 + NumUnitsCutSecondStage(2_1) c_u_EnforceInventorySecondStage(2)_ 1 + NumUnitsCutSecondStage(2_2) c_l_DemandSatisfiedSecondStage(2)_ 1 + NumUnitsCutSecondStage(2_2) c_u_EnforceInventorySecondStage(2)_ 1 + NumUnitsCutSecondStage(3_1) Total_Cost_Objective -0.0080000000000000002 + NumUnitsCutSecondStage(3_1) c_l_DemandSatisfiedSecondStage(1)_ 1 + NumUnitsCutSecondStage(3_1) c_u_EnforceInventorySecondStage(3)_ 1 + NumUnitsCutSecondStage(3_2) Total_Cost_Objective -0.0080000000000000002 + NumUnitsCutSecondStage(3_2) c_l_DemandSatisfiedSecondStage(2)_ 1 + NumUnitsCutSecondStage(3_2) c_u_EnforceInventorySecondStage(3)_ 1 + NumUnitsCutSecondStage(3_3) c_l_DemandSatisfiedSecondStage(3)_ 1 + NumUnitsCutSecondStage(3_3) c_u_EnforceInventorySecondStage(3)_ 1 + NumUnitsCutSecondStage(4_1) Total_Cost_Objective -0.0080000000000000002 + NumUnitsCutSecondStage(4_1) c_l_DemandSatisfiedSecondStage(1)_ 1 + NumUnitsCutSecondStage(4_1) c_u_EnforceInventorySecondStage(4)_ 1 + NumUnitsCutSecondStage(4_2) Total_Cost_Objective -0.0080000000000000002 + NumUnitsCutSecondStage(4_2) c_l_DemandSatisfiedSecondStage(2)_ 1 + NumUnitsCutSecondStage(4_2) c_u_EnforceInventorySecondStage(4)_ 1 + NumUnitsCutSecondStage(4_3) Total_Cost_Objective -0.0080000000000000002 + NumUnitsCutSecondStage(4_3) c_l_DemandSatisfiedSecondStage(3)_ 1 + NumUnitsCutSecondStage(4_3) c_u_EnforceInventorySecondStage(4)_ 1 + NumUnitsCutSecondStage(4_4) c_l_DemandSatisfiedSecondStage(4)_ 1 + NumUnitsCutSecondStage(4_4) c_u_EnforceInventorySecondStage(4)_ 1 + NumUnitsCutSecondStage(5_1) Total_Cost_Objective -0.0080000000000000002 + NumUnitsCutSecondStage(5_1) c_l_DemandSatisfiedSecondStage(1)_ 1 + NumUnitsCutSecondStage(5_1) c_u_EnforceInventorySecondStage(5)_ 1 + NumUnitsCutSecondStage(5_2) Total_Cost_Objective -0.0080000000000000002 + NumUnitsCutSecondStage(5_2) c_l_DemandSatisfiedSecondStage(2)_ 1 + NumUnitsCutSecondStage(5_2) c_u_EnforceInventorySecondStage(5)_ 1 + NumUnitsCutSecondStage(5_3) Total_Cost_Objective -0.0080000000000000002 + NumUnitsCutSecondStage(5_3) c_l_DemandSatisfiedSecondStage(3)_ 1 + NumUnitsCutSecondStage(5_3) c_u_EnforceInventorySecondStage(5)_ 1 + NumUnitsCutSecondStage(5_4) Total_Cost_Objective -0.0080000000000000002 + NumUnitsCutSecondStage(5_4) c_l_DemandSatisfiedSecondStage(4)_ 1 + NumUnitsCutSecondStage(5_4) c_u_EnforceInventorySecondStage(5)_ 1 + NumUnitsCutSecondStage(5_5) c_l_DemandSatisfiedSecondStage(5)_ 1 + NumUnitsCutSecondStage(5_5) c_u_EnforceInventorySecondStage(5)_ 1 + NumUnitsCutSecondStage(6_1) Total_Cost_Objective -0.0080000000000000002 + NumUnitsCutSecondStage(6_1) c_l_DemandSatisfiedSecondStage(1)_ 1 + NumUnitsCutSecondStage(6_1) c_u_EnforceInventorySecondStage(6)_ 1 + NumUnitsCutSecondStage(6_2) Total_Cost_Objective -0.0080000000000000002 + NumUnitsCutSecondStage(6_2) c_l_DemandSatisfiedSecondStage(2)_ 1 + NumUnitsCutSecondStage(6_2) c_u_EnforceInventorySecondStage(6)_ 1 + NumUnitsCutSecondStage(6_3) Total_Cost_Objective -0.0080000000000000002 + NumUnitsCutSecondStage(6_3) c_l_DemandSatisfiedSecondStage(3)_ 1 + NumUnitsCutSecondStage(6_3) c_u_EnforceInventorySecondStage(6)_ 1 + NumUnitsCutSecondStage(6_4) Total_Cost_Objective -0.0080000000000000002 + NumUnitsCutSecondStage(6_4) c_l_DemandSatisfiedSecondStage(4)_ 1 + NumUnitsCutSecondStage(6_4) c_u_EnforceInventorySecondStage(6)_ 1 + NumUnitsCutSecondStage(6_5) Total_Cost_Objective -0.0080000000000000002 + NumUnitsCutSecondStage(6_5) c_l_DemandSatisfiedSecondStage(5)_ 1 + NumUnitsCutSecondStage(6_5) c_u_EnforceInventorySecondStage(6)_ 1 + NumUnitsCutSecondStage(6_6) c_l_DemandSatisfiedSecondStage(6)_ 1 + NumUnitsCutSecondStage(6_6) c_u_EnforceInventorySecondStage(6)_ 1 + NumUnitsCutSecondStage(7_1) Total_Cost_Objective -0.0080000000000000002 + NumUnitsCutSecondStage(7_1) c_l_DemandSatisfiedSecondStage(1)_ 1 + NumUnitsCutSecondStage(7_1) c_u_EnforceInventorySecondStage(7)_ 1 + NumUnitsCutSecondStage(7_2) Total_Cost_Objective -0.0080000000000000002 + NumUnitsCutSecondStage(7_2) c_l_DemandSatisfiedSecondStage(2)_ 1 + NumUnitsCutSecondStage(7_2) c_u_EnforceInventorySecondStage(7)_ 1 + NumUnitsCutSecondStage(7_3) Total_Cost_Objective -0.0080000000000000002 + NumUnitsCutSecondStage(7_3) c_l_DemandSatisfiedSecondStage(3)_ 1 + NumUnitsCutSecondStage(7_3) c_u_EnforceInventorySecondStage(7)_ 1 + NumUnitsCutSecondStage(7_4) Total_Cost_Objective -0.0080000000000000002 + NumUnitsCutSecondStage(7_4) c_l_DemandSatisfiedSecondStage(4)_ 1 + NumUnitsCutSecondStage(7_4) c_u_EnforceInventorySecondStage(7)_ 1 + NumUnitsCutSecondStage(7_5) Total_Cost_Objective -0.0080000000000000002 + NumUnitsCutSecondStage(7_5) c_l_DemandSatisfiedSecondStage(5)_ 1 + NumUnitsCutSecondStage(7_5) c_u_EnforceInventorySecondStage(7)_ 1 + NumUnitsCutSecondStage(7_6) Total_Cost_Objective -0.0080000000000000002 + NumUnitsCutSecondStage(7_6) c_l_DemandSatisfiedSecondStage(6)_ 1 + NumUnitsCutSecondStage(7_6) c_u_EnforceInventorySecondStage(7)_ 1 + NumUnitsCutSecondStage(7_7) c_l_DemandSatisfiedSecondStage(7)_ 1 + NumUnitsCutSecondStage(7_7) c_u_EnforceInventorySecondStage(7)_ 1 + NumUnitsCutSecondStage(8_1) Total_Cost_Objective -0.0080000000000000002 + NumUnitsCutSecondStage(8_1) c_l_DemandSatisfiedSecondStage(1)_ 1 + NumUnitsCutSecondStage(8_1) c_u_EnforceInventorySecondStage(8)_ 1 + NumUnitsCutSecondStage(8_2) Total_Cost_Objective -0.0080000000000000002 + NumUnitsCutSecondStage(8_2) c_l_DemandSatisfiedSecondStage(2)_ 1 + NumUnitsCutSecondStage(8_2) c_u_EnforceInventorySecondStage(8)_ 1 + NumUnitsCutSecondStage(8_3) Total_Cost_Objective -0.0080000000000000002 + NumUnitsCutSecondStage(8_3) c_l_DemandSatisfiedSecondStage(3)_ 1 + NumUnitsCutSecondStage(8_3) c_u_EnforceInventorySecondStage(8)_ 1 + NumUnitsCutSecondStage(8_4) Total_Cost_Objective -0.0080000000000000002 + NumUnitsCutSecondStage(8_4) c_l_DemandSatisfiedSecondStage(4)_ 1 + NumUnitsCutSecondStage(8_4) c_u_EnforceInventorySecondStage(8)_ 1 + NumUnitsCutSecondStage(8_5) Total_Cost_Objective -0.0080000000000000002 + NumUnitsCutSecondStage(8_5) c_l_DemandSatisfiedSecondStage(5)_ 1 + NumUnitsCutSecondStage(8_5) c_u_EnforceInventorySecondStage(8)_ 1 + NumUnitsCutSecondStage(8_6) Total_Cost_Objective -0.0080000000000000002 + NumUnitsCutSecondStage(8_6) c_l_DemandSatisfiedSecondStage(6)_ 1 + NumUnitsCutSecondStage(8_6) c_u_EnforceInventorySecondStage(8)_ 1 + NumUnitsCutSecondStage(8_7) Total_Cost_Objective -0.0080000000000000002 + NumUnitsCutSecondStage(8_7) c_l_DemandSatisfiedSecondStage(7)_ 1 + NumUnitsCutSecondStage(8_7) c_u_EnforceInventorySecondStage(8)_ 1 + NumUnitsCutSecondStage(8_8) c_l_DemandSatisfiedSecondStage(8)_ 1 + NumUnitsCutSecondStage(8_8) c_u_EnforceInventorySecondStage(8)_ 1 + NumUnitsCutSecondStage(9_1) Total_Cost_Objective -0.0080000000000000002 + NumUnitsCutSecondStage(9_1) c_l_DemandSatisfiedSecondStage(1)_ 1 + NumUnitsCutSecondStage(9_1) c_u_EnforceInventorySecondStage(9)_ 1 + NumUnitsCutSecondStage(9_2) Total_Cost_Objective -0.0080000000000000002 + NumUnitsCutSecondStage(9_2) c_l_DemandSatisfiedSecondStage(2)_ 1 + NumUnitsCutSecondStage(9_2) c_u_EnforceInventorySecondStage(9)_ 1 + NumUnitsCutSecondStage(9_3) Total_Cost_Objective -0.0080000000000000002 + NumUnitsCutSecondStage(9_3) c_l_DemandSatisfiedSecondStage(3)_ 1 + NumUnitsCutSecondStage(9_3) c_u_EnforceInventorySecondStage(9)_ 1 + NumUnitsCutSecondStage(9_4) Total_Cost_Objective -0.0080000000000000002 + NumUnitsCutSecondStage(9_4) c_l_DemandSatisfiedSecondStage(4)_ 1 + NumUnitsCutSecondStage(9_4) c_u_EnforceInventorySecondStage(9)_ 1 + NumUnitsCutSecondStage(9_5) Total_Cost_Objective -0.0080000000000000002 + NumUnitsCutSecondStage(9_5) c_l_DemandSatisfiedSecondStage(5)_ 1 + NumUnitsCutSecondStage(9_5) c_u_EnforceInventorySecondStage(9)_ 1 + NumUnitsCutSecondStage(9_6) Total_Cost_Objective -0.0080000000000000002 + NumUnitsCutSecondStage(9_6) c_l_DemandSatisfiedSecondStage(6)_ 1 + NumUnitsCutSecondStage(9_6) c_u_EnforceInventorySecondStage(9)_ 1 + NumUnitsCutSecondStage(9_7) Total_Cost_Objective -0.0080000000000000002 + NumUnitsCutSecondStage(9_7) c_l_DemandSatisfiedSecondStage(7)_ 1 + NumUnitsCutSecondStage(9_7) c_u_EnforceInventorySecondStage(9)_ 1 + NumUnitsCutSecondStage(9_8) Total_Cost_Objective -0.0080000000000000002 + NumUnitsCutSecondStage(9_8) c_l_DemandSatisfiedSecondStage(8)_ 1 + NumUnitsCutSecondStage(9_8) c_u_EnforceInventorySecondStage(9)_ 1 + NumUnitsCutSecondStage(9_9) c_l_DemandSatisfiedSecondStage(9)_ 1 + NumUnitsCutSecondStage(9_9) c_u_EnforceInventorySecondStage(9)_ 1 + NumUnitsCutSecondStage(10_1) Total_Cost_Objective -0.0080000000000000002 + NumUnitsCutSecondStage(10_1) c_l_DemandSatisfiedSecondStage(1)_ 1 + NumUnitsCutSecondStage(10_1) c_u_EnforceInventorySecondStage(10)_ 1 + NumUnitsCutSecondStage(10_2) Total_Cost_Objective -0.0080000000000000002 + NumUnitsCutSecondStage(10_2) c_l_DemandSatisfiedSecondStage(2)_ 1 + NumUnitsCutSecondStage(10_2) c_u_EnforceInventorySecondStage(10)_ 1 + NumUnitsCutSecondStage(10_3) Total_Cost_Objective -0.0080000000000000002 + NumUnitsCutSecondStage(10_3) c_l_DemandSatisfiedSecondStage(3)_ 1 + NumUnitsCutSecondStage(10_3) c_u_EnforceInventorySecondStage(10)_ 1 + NumUnitsCutSecondStage(10_4) Total_Cost_Objective -0.0080000000000000002 + NumUnitsCutSecondStage(10_4) c_l_DemandSatisfiedSecondStage(4)_ 1 + NumUnitsCutSecondStage(10_4) c_u_EnforceInventorySecondStage(10)_ 1 + NumUnitsCutSecondStage(10_5) Total_Cost_Objective -0.0080000000000000002 + NumUnitsCutSecondStage(10_5) c_l_DemandSatisfiedSecondStage(5)_ 1 + NumUnitsCutSecondStage(10_5) c_u_EnforceInventorySecondStage(10)_ 1 + NumUnitsCutSecondStage(10_6) Total_Cost_Objective -0.0080000000000000002 + NumUnitsCutSecondStage(10_6) c_l_DemandSatisfiedSecondStage(6)_ 1 + NumUnitsCutSecondStage(10_6) c_u_EnforceInventorySecondStage(10)_ 1 + NumUnitsCutSecondStage(10_7) Total_Cost_Objective -0.0080000000000000002 + NumUnitsCutSecondStage(10_7) c_l_DemandSatisfiedSecondStage(7)_ 1 + NumUnitsCutSecondStage(10_7) c_u_EnforceInventorySecondStage(10)_ 1 + NumUnitsCutSecondStage(10_8) Total_Cost_Objective -0.0080000000000000002 + NumUnitsCutSecondStage(10_8) c_l_DemandSatisfiedSecondStage(8)_ 1 + NumUnitsCutSecondStage(10_8) c_u_EnforceInventorySecondStage(10)_ 1 + NumUnitsCutSecondStage(10_9) Total_Cost_Objective -0.0080000000000000002 + NumUnitsCutSecondStage(10_9) c_l_DemandSatisfiedSecondStage(9)_ 1 + NumUnitsCutSecondStage(10_9) c_u_EnforceInventorySecondStage(10)_ 1 + NumUnitsCutSecondStage(10_10) c_l_DemandSatisfiedSecondStage(10)_ 1 + NumUnitsCutSecondStage(10_10) c_u_EnforceInventorySecondStage(10)_ 1 +RHS + RHS c_l_DemandSatisfiedFirstStage(1)_ 2500 + RHS c_l_DemandSatisfiedFirstStage(2)_ 7500 + RHS c_l_DemandSatisfiedFirstStage(3)_ 12500 + RHS c_l_DemandSatisfiedFirstStage(4)_ 10000 + RHS c_l_DemandSatisfiedFirstStage(5)_ 35000 + RHS c_l_DemandSatisfiedFirstStage(6)_ 25000 + RHS c_l_DemandSatisfiedFirstStage(7)_ 15000 + RHS c_l_DemandSatisfiedFirstStage(8)_ 12500 + RHS c_l_DemandSatisfiedFirstStage(9)_ 12500 + RHS c_l_DemandSatisfiedFirstStage(10)_ 5000 + RHS c_l_DemandSatisfiedSecondStage(1)_ 1750 + RHS c_l_DemandSatisfiedSecondStage(2)_ 5250 + RHS c_l_DemandSatisfiedSecondStage(3)_ 8750 + RHS c_l_DemandSatisfiedSecondStage(4)_ 7000 + RHS c_l_DemandSatisfiedSecondStage(5)_ 24500 + RHS c_l_DemandSatisfiedSecondStage(6)_ 17500 + RHS c_l_DemandSatisfiedSecondStage(7)_ 10500 + RHS c_l_DemandSatisfiedSecondStage(8)_ 8750 + RHS c_l_DemandSatisfiedSecondStage(9)_ 8750 + RHS c_l_DemandSatisfiedSecondStage(10)_ 3500 + RHS c_u_EnforceProductionBinaryFirstStage(1)_ 0 + RHS c_u_EnforceProductionBinaryFirstStage(2)_ 0 + RHS c_u_EnforceProductionBinaryFirstStage(3)_ 0 + RHS c_u_EnforceProductionBinaryFirstStage(4)_ 0 + RHS c_u_EnforceProductionBinaryFirstStage(5)_ 0 + RHS c_u_EnforceProductionBinaryFirstStage(6)_ 0 + RHS c_u_EnforceProductionBinaryFirstStage(7)_ 0 + RHS c_u_EnforceProductionBinaryFirstStage(8)_ 0 + RHS c_u_EnforceProductionBinaryFirstStage(9)_ 0 + RHS c_u_EnforceProductionBinaryFirstStage(10)_ 0 + RHS c_u_EnforceProductionBinarySecondStage(1)_ 0 + RHS c_u_EnforceProductionBinarySecondStage(2)_ 0 + RHS c_u_EnforceProductionBinarySecondStage(3)_ 0 + RHS c_u_EnforceProductionBinarySecondStage(4)_ 0 + RHS c_u_EnforceProductionBinarySecondStage(5)_ 0 + RHS c_u_EnforceProductionBinarySecondStage(6)_ 0 + RHS c_u_EnforceProductionBinarySecondStage(7)_ 0 + RHS c_u_EnforceProductionBinarySecondStage(8)_ 0 + RHS c_u_EnforceProductionBinarySecondStage(9)_ 0 + RHS c_u_EnforceProductionBinarySecondStage(10)_ 0 + RHS c_u_EnforceCapacityLimitFirstStage_ 200000 + RHS c_u_EnforceCapacityLimitSecondStage_ 200000 + RHS c_u_EnforceInventoryFirstStage(1)_ 0 + RHS c_u_EnforceInventoryFirstStage(2)_ 0 + RHS c_u_EnforceInventoryFirstStage(3)_ 0 + RHS c_u_EnforceInventoryFirstStage(4)_ 0 + RHS c_u_EnforceInventoryFirstStage(5)_ 0 + RHS c_u_EnforceInventoryFirstStage(6)_ 0 + RHS c_u_EnforceInventoryFirstStage(7)_ 0 + RHS c_u_EnforceInventoryFirstStage(8)_ 0 + RHS c_u_EnforceInventoryFirstStage(9)_ 0 + RHS c_u_EnforceInventoryFirstStage(10)_ 0 + RHS c_u_EnforceInventorySecondStage(1)_ 0 + RHS c_u_EnforceInventorySecondStage(2)_ 0 + RHS c_u_EnforceInventorySecondStage(3)_ 0 + RHS c_u_EnforceInventorySecondStage(4)_ 0 + RHS c_u_EnforceInventorySecondStage(5)_ 0 + RHS c_u_EnforceInventorySecondStage(6)_ 0 + RHS c_u_EnforceInventorySecondStage(7)_ 0 + RHS c_u_EnforceInventorySecondStage(8)_ 0 + RHS c_u_EnforceInventorySecondStage(9)_ 0 + RHS c_u_EnforceInventorySecondStage(10)_ 0 +BOUNDS + BV BOUND ProduceSizeFirstStage(1) + BV BOUND ProduceSizeFirstStage(2) + BV BOUND ProduceSizeFirstStage(3) + BV BOUND ProduceSizeFirstStage(4) + BV BOUND ProduceSizeFirstStage(5) + BV BOUND ProduceSizeFirstStage(6) + BV BOUND ProduceSizeFirstStage(7) + BV BOUND ProduceSizeFirstStage(8) + BV BOUND ProduceSizeFirstStage(9) + BV BOUND ProduceSizeFirstStage(10) + BV BOUND ProduceSizeSecondStage(1) + BV BOUND ProduceSizeSecondStage(2) + BV BOUND ProduceSizeSecondStage(3) + BV BOUND ProduceSizeSecondStage(4) + BV BOUND ProduceSizeSecondStage(5) + BV BOUND ProduceSizeSecondStage(6) + BV BOUND ProduceSizeSecondStage(7) + BV BOUND ProduceSizeSecondStage(8) + BV BOUND ProduceSizeSecondStage(9) + BV BOUND ProduceSizeSecondStage(10) + LI BOUND NumProducedFirstStage(1) 0 + UI BOUND NumProducedFirstStage(1) 200000 + LI BOUND NumProducedFirstStage(2) 0 + UI BOUND NumProducedFirstStage(2) 200000 + LI BOUND NumProducedFirstStage(3) 0 + UI BOUND NumProducedFirstStage(3) 200000 + LI BOUND NumProducedFirstStage(4) 0 + UI BOUND NumProducedFirstStage(4) 200000 + LI BOUND NumProducedFirstStage(5) 0 + UI BOUND NumProducedFirstStage(5) 200000 + LI BOUND NumProducedFirstStage(6) 0 + UI BOUND NumProducedFirstStage(6) 200000 + LI BOUND NumProducedFirstStage(7) 0 + UI BOUND NumProducedFirstStage(7) 200000 + LI BOUND NumProducedFirstStage(8) 0 + UI BOUND NumProducedFirstStage(8) 200000 + LI BOUND NumProducedFirstStage(9) 0 + UI BOUND NumProducedFirstStage(9) 200000 + LI BOUND NumProducedFirstStage(10) 0 + UI BOUND NumProducedFirstStage(10) 200000 + LI BOUND NumProducedSecondStage(1) 0 + UI BOUND NumProducedSecondStage(1) 200000 + LI BOUND NumProducedSecondStage(2) 0 + UI BOUND NumProducedSecondStage(2) 200000 + LI BOUND NumProducedSecondStage(3) 0 + UI BOUND NumProducedSecondStage(3) 200000 + LI BOUND NumProducedSecondStage(4) 0 + UI BOUND NumProducedSecondStage(4) 200000 + LI BOUND NumProducedSecondStage(5) 0 + UI BOUND NumProducedSecondStage(5) 200000 + LI BOUND NumProducedSecondStage(6) 0 + UI BOUND NumProducedSecondStage(6) 200000 + LI BOUND NumProducedSecondStage(7) 0 + UI BOUND NumProducedSecondStage(7) 200000 + LI BOUND NumProducedSecondStage(8) 0 + UI BOUND NumProducedSecondStage(8) 200000 + LI BOUND NumProducedSecondStage(9) 0 + UI BOUND NumProducedSecondStage(9) 200000 + LI BOUND NumProducedSecondStage(10) 0 + UI BOUND NumProducedSecondStage(10) 200000 + LI BOUND NumUnitsCutFirstStage(1_1) 0 + UI BOUND NumUnitsCutFirstStage(1_1) 200000 + LI BOUND NumUnitsCutFirstStage(2_1) 0 + UI BOUND NumUnitsCutFirstStage(2_1) 200000 + LI BOUND NumUnitsCutFirstStage(2_2) 0 + UI BOUND NumUnitsCutFirstStage(2_2) 200000 + LI BOUND NumUnitsCutFirstStage(3_1) 0 + UI BOUND NumUnitsCutFirstStage(3_1) 200000 + LI BOUND NumUnitsCutFirstStage(3_2) 0 + UI BOUND NumUnitsCutFirstStage(3_2) 200000 + LI BOUND NumUnitsCutFirstStage(3_3) 0 + UI BOUND NumUnitsCutFirstStage(3_3) 200000 + LI BOUND NumUnitsCutFirstStage(4_1) 0 + UI BOUND NumUnitsCutFirstStage(4_1) 200000 + LI BOUND NumUnitsCutFirstStage(4_2) 0 + UI BOUND NumUnitsCutFirstStage(4_2) 200000 + LI BOUND NumUnitsCutFirstStage(4_3) 0 + UI BOUND NumUnitsCutFirstStage(4_3) 200000 + LI BOUND NumUnitsCutFirstStage(4_4) 0 + UI BOUND NumUnitsCutFirstStage(4_4) 200000 + LI BOUND NumUnitsCutFirstStage(5_1) 0 + UI BOUND NumUnitsCutFirstStage(5_1) 200000 + LI BOUND NumUnitsCutFirstStage(5_2) 0 + UI BOUND NumUnitsCutFirstStage(5_2) 200000 + LI BOUND NumUnitsCutFirstStage(5_3) 0 + UI BOUND NumUnitsCutFirstStage(5_3) 200000 + LI BOUND NumUnitsCutFirstStage(5_4) 0 + UI BOUND NumUnitsCutFirstStage(5_4) 200000 + LI BOUND NumUnitsCutFirstStage(5_5) 0 + UI BOUND NumUnitsCutFirstStage(5_5) 200000 + LI BOUND NumUnitsCutFirstStage(6_1) 0 + UI BOUND NumUnitsCutFirstStage(6_1) 200000 + LI BOUND NumUnitsCutFirstStage(6_2) 0 + UI BOUND NumUnitsCutFirstStage(6_2) 200000 + LI BOUND NumUnitsCutFirstStage(6_3) 0 + UI BOUND NumUnitsCutFirstStage(6_3) 200000 + LI BOUND NumUnitsCutFirstStage(6_4) 0 + UI BOUND NumUnitsCutFirstStage(6_4) 200000 + LI BOUND NumUnitsCutFirstStage(6_5) 0 + UI BOUND NumUnitsCutFirstStage(6_5) 200000 + LI BOUND NumUnitsCutFirstStage(6_6) 0 + UI BOUND NumUnitsCutFirstStage(6_6) 200000 + LI BOUND NumUnitsCutFirstStage(7_1) 0 + UI BOUND NumUnitsCutFirstStage(7_1) 200000 + LI BOUND NumUnitsCutFirstStage(7_2) 0 + UI BOUND NumUnitsCutFirstStage(7_2) 200000 + LI BOUND NumUnitsCutFirstStage(7_3) 0 + UI BOUND NumUnitsCutFirstStage(7_3) 200000 + LI BOUND NumUnitsCutFirstStage(7_4) 0 + UI BOUND NumUnitsCutFirstStage(7_4) 200000 + LI BOUND NumUnitsCutFirstStage(7_5) 0 + UI BOUND NumUnitsCutFirstStage(7_5) 200000 + LI BOUND NumUnitsCutFirstStage(7_6) 0 + UI BOUND NumUnitsCutFirstStage(7_6) 200000 + LI BOUND NumUnitsCutFirstStage(7_7) 0 + UI BOUND NumUnitsCutFirstStage(7_7) 200000 + LI BOUND NumUnitsCutFirstStage(8_1) 0 + UI BOUND NumUnitsCutFirstStage(8_1) 200000 + LI BOUND NumUnitsCutFirstStage(8_2) 0 + UI BOUND NumUnitsCutFirstStage(8_2) 200000 + LI BOUND NumUnitsCutFirstStage(8_3) 0 + UI BOUND NumUnitsCutFirstStage(8_3) 200000 + LI BOUND NumUnitsCutFirstStage(8_4) 0 + UI BOUND NumUnitsCutFirstStage(8_4) 200000 + LI BOUND NumUnitsCutFirstStage(8_5) 0 + UI BOUND NumUnitsCutFirstStage(8_5) 200000 + LI BOUND NumUnitsCutFirstStage(8_6) 0 + UI BOUND NumUnitsCutFirstStage(8_6) 200000 + LI BOUND NumUnitsCutFirstStage(8_7) 0 + UI BOUND NumUnitsCutFirstStage(8_7) 200000 + LI BOUND NumUnitsCutFirstStage(8_8) 0 + UI BOUND NumUnitsCutFirstStage(8_8) 200000 + LI BOUND NumUnitsCutFirstStage(9_1) 0 + UI BOUND NumUnitsCutFirstStage(9_1) 200000 + LI BOUND NumUnitsCutFirstStage(9_2) 0 + UI BOUND NumUnitsCutFirstStage(9_2) 200000 + LI BOUND NumUnitsCutFirstStage(9_3) 0 + UI BOUND NumUnitsCutFirstStage(9_3) 200000 + LI BOUND NumUnitsCutFirstStage(9_4) 0 + UI BOUND NumUnitsCutFirstStage(9_4) 200000 + LI BOUND NumUnitsCutFirstStage(9_5) 0 + UI BOUND NumUnitsCutFirstStage(9_5) 200000 + LI BOUND NumUnitsCutFirstStage(9_6) 0 + UI BOUND NumUnitsCutFirstStage(9_6) 200000 + LI BOUND NumUnitsCutFirstStage(9_7) 0 + UI BOUND NumUnitsCutFirstStage(9_7) 200000 + LI BOUND NumUnitsCutFirstStage(9_8) 0 + UI BOUND NumUnitsCutFirstStage(9_8) 200000 + LI BOUND NumUnitsCutFirstStage(9_9) 0 + UI BOUND NumUnitsCutFirstStage(9_9) 200000 + LI BOUND NumUnitsCutFirstStage(10_1) 0 + UI BOUND NumUnitsCutFirstStage(10_1) 200000 + LI BOUND NumUnitsCutFirstStage(10_2) 0 + UI BOUND NumUnitsCutFirstStage(10_2) 200000 + LI BOUND NumUnitsCutFirstStage(10_3) 0 + UI BOUND NumUnitsCutFirstStage(10_3) 200000 + LI BOUND NumUnitsCutFirstStage(10_4) 0 + UI BOUND NumUnitsCutFirstStage(10_4) 200000 + LI BOUND NumUnitsCutFirstStage(10_5) 0 + UI BOUND NumUnitsCutFirstStage(10_5) 200000 + LI BOUND NumUnitsCutFirstStage(10_6) 0 + UI BOUND NumUnitsCutFirstStage(10_6) 200000 + LI BOUND NumUnitsCutFirstStage(10_7) 0 + UI BOUND NumUnitsCutFirstStage(10_7) 200000 + LI BOUND NumUnitsCutFirstStage(10_8) 0 + UI BOUND NumUnitsCutFirstStage(10_8) 200000 + LI BOUND NumUnitsCutFirstStage(10_9) 0 + UI BOUND NumUnitsCutFirstStage(10_9) 200000 + LI BOUND NumUnitsCutFirstStage(10_10) 0 + UI BOUND NumUnitsCutFirstStage(10_10) 200000 + LI BOUND NumUnitsCutSecondStage(1_1) 0 + UI BOUND NumUnitsCutSecondStage(1_1) 200000 + LI BOUND NumUnitsCutSecondStage(2_1) 0 + UI BOUND NumUnitsCutSecondStage(2_1) 200000 + LI BOUND NumUnitsCutSecondStage(2_2) 0 + UI BOUND NumUnitsCutSecondStage(2_2) 200000 + LI BOUND NumUnitsCutSecondStage(3_1) 0 + UI BOUND NumUnitsCutSecondStage(3_1) 200000 + LI BOUND NumUnitsCutSecondStage(3_2) 0 + UI BOUND NumUnitsCutSecondStage(3_2) 200000 + LI BOUND NumUnitsCutSecondStage(3_3) 0 + UI BOUND NumUnitsCutSecondStage(3_3) 200000 + LI BOUND NumUnitsCutSecondStage(4_1) 0 + UI BOUND NumUnitsCutSecondStage(4_1) 200000 + LI BOUND NumUnitsCutSecondStage(4_2) 0 + UI BOUND NumUnitsCutSecondStage(4_2) 200000 + LI BOUND NumUnitsCutSecondStage(4_3) 0 + UI BOUND NumUnitsCutSecondStage(4_3) 200000 + LI BOUND NumUnitsCutSecondStage(4_4) 0 + UI BOUND NumUnitsCutSecondStage(4_4) 200000 + LI BOUND NumUnitsCutSecondStage(5_1) 0 + UI BOUND NumUnitsCutSecondStage(5_1) 200000 + LI BOUND NumUnitsCutSecondStage(5_2) 0 + UI BOUND NumUnitsCutSecondStage(5_2) 200000 + LI BOUND NumUnitsCutSecondStage(5_3) 0 + UI BOUND NumUnitsCutSecondStage(5_3) 200000 + LI BOUND NumUnitsCutSecondStage(5_4) 0 + UI BOUND NumUnitsCutSecondStage(5_4) 200000 + LI BOUND NumUnitsCutSecondStage(5_5) 0 + UI BOUND NumUnitsCutSecondStage(5_5) 200000 + LI BOUND NumUnitsCutSecondStage(6_1) 0 + UI BOUND NumUnitsCutSecondStage(6_1) 200000 + LI BOUND NumUnitsCutSecondStage(6_2) 0 + UI BOUND NumUnitsCutSecondStage(6_2) 200000 + LI BOUND NumUnitsCutSecondStage(6_3) 0 + UI BOUND NumUnitsCutSecondStage(6_3) 200000 + LI BOUND NumUnitsCutSecondStage(6_4) 0 + UI BOUND NumUnitsCutSecondStage(6_4) 200000 + LI BOUND NumUnitsCutSecondStage(6_5) 0 + UI BOUND NumUnitsCutSecondStage(6_5) 200000 + LI BOUND NumUnitsCutSecondStage(6_6) 0 + UI BOUND NumUnitsCutSecondStage(6_6) 200000 + LI BOUND NumUnitsCutSecondStage(7_1) 0 + UI BOUND NumUnitsCutSecondStage(7_1) 200000 + LI BOUND NumUnitsCutSecondStage(7_2) 0 + UI BOUND NumUnitsCutSecondStage(7_2) 200000 + LI BOUND NumUnitsCutSecondStage(7_3) 0 + UI BOUND NumUnitsCutSecondStage(7_3) 200000 + LI BOUND NumUnitsCutSecondStage(7_4) 0 + UI BOUND NumUnitsCutSecondStage(7_4) 200000 + LI BOUND NumUnitsCutSecondStage(7_5) 0 + UI BOUND NumUnitsCutSecondStage(7_5) 200000 + LI BOUND NumUnitsCutSecondStage(7_6) 0 + UI BOUND NumUnitsCutSecondStage(7_6) 200000 + LI BOUND NumUnitsCutSecondStage(7_7) 0 + UI BOUND NumUnitsCutSecondStage(7_7) 200000 + LI BOUND NumUnitsCutSecondStage(8_1) 0 + UI BOUND NumUnitsCutSecondStage(8_1) 200000 + LI BOUND NumUnitsCutSecondStage(8_2) 0 + UI BOUND NumUnitsCutSecondStage(8_2) 200000 + LI BOUND NumUnitsCutSecondStage(8_3) 0 + UI BOUND NumUnitsCutSecondStage(8_3) 200000 + LI BOUND NumUnitsCutSecondStage(8_4) 0 + UI BOUND NumUnitsCutSecondStage(8_4) 200000 + LI BOUND NumUnitsCutSecondStage(8_5) 0 + UI BOUND NumUnitsCutSecondStage(8_5) 200000 + LI BOUND NumUnitsCutSecondStage(8_6) 0 + UI BOUND NumUnitsCutSecondStage(8_6) 200000 + LI BOUND NumUnitsCutSecondStage(8_7) 0 + UI BOUND NumUnitsCutSecondStage(8_7) 200000 + LI BOUND NumUnitsCutSecondStage(8_8) 0 + UI BOUND NumUnitsCutSecondStage(8_8) 200000 + LI BOUND NumUnitsCutSecondStage(9_1) 0 + UI BOUND NumUnitsCutSecondStage(9_1) 200000 + LI BOUND NumUnitsCutSecondStage(9_2) 0 + UI BOUND NumUnitsCutSecondStage(9_2) 200000 + LI BOUND NumUnitsCutSecondStage(9_3) 0 + UI BOUND NumUnitsCutSecondStage(9_3) 200000 + LI BOUND NumUnitsCutSecondStage(9_4) 0 + UI BOUND NumUnitsCutSecondStage(9_4) 200000 + LI BOUND NumUnitsCutSecondStage(9_5) 0 + UI BOUND NumUnitsCutSecondStage(9_5) 200000 + LI BOUND NumUnitsCutSecondStage(9_6) 0 + UI BOUND NumUnitsCutSecondStage(9_6) 200000 + LI BOUND NumUnitsCutSecondStage(9_7) 0 + UI BOUND NumUnitsCutSecondStage(9_7) 200000 + LI BOUND NumUnitsCutSecondStage(9_8) 0 + UI BOUND NumUnitsCutSecondStage(9_8) 200000 + LI BOUND NumUnitsCutSecondStage(9_9) 0 + UI BOUND NumUnitsCutSecondStage(9_9) 200000 + LI BOUND NumUnitsCutSecondStage(10_1) 0 + UI BOUND NumUnitsCutSecondStage(10_1) 200000 + LI BOUND NumUnitsCutSecondStage(10_2) 0 + UI BOUND NumUnitsCutSecondStage(10_2) 200000 + LI BOUND NumUnitsCutSecondStage(10_3) 0 + UI BOUND NumUnitsCutSecondStage(10_3) 200000 + LI BOUND NumUnitsCutSecondStage(10_4) 0 + UI BOUND NumUnitsCutSecondStage(10_4) 200000 + LI BOUND NumUnitsCutSecondStage(10_5) 0 + UI BOUND NumUnitsCutSecondStage(10_5) 200000 + LI BOUND NumUnitsCutSecondStage(10_6) 0 + UI BOUND NumUnitsCutSecondStage(10_6) 200000 + LI BOUND NumUnitsCutSecondStage(10_7) 0 + UI BOUND NumUnitsCutSecondStage(10_7) 200000 + LI BOUND NumUnitsCutSecondStage(10_8) 0 + UI BOUND NumUnitsCutSecondStage(10_8) 200000 + LI BOUND NumUnitsCutSecondStage(10_9) 0 + UI BOUND NumUnitsCutSecondStage(10_9) 200000 + LI BOUND NumUnitsCutSecondStage(10_10) 0 + UI BOUND NumUnitsCutSecondStage(10_10) 200000 +ENDATA diff --git a/mpisppy/tests/examples/test1.mps b/mpisppy/tests/examples/test1.mps new file mode 100644 index 000000000..c1c4e9f58 --- /dev/null +++ b/mpisppy/tests/examples/test1.mps @@ -0,0 +1,30 @@ +NAME TINYMATCHING +ROWS + N OBJ + E NODEA1 + E NODEA2 + E NODEA3 +COLUMNS + X11 OBJ 1 NODEA1 1 + X12 OBJ 2 NODEA1 1 + X13 OBJ 3 NODEA1 1 + X21 OBJ 2 NODEA2 1 + X22 OBJ 3 NODEA2 1 + X23 OBJ 1 NODEA2 1 + X31 OBJ 3 NODEA3 1 + X32 OBJ 1 NODEA3 1 + X33 OBJ 2 NODEA3 1 +RHS + rhs NODEA1 1 NODEA2 1 + rhs NODEA3 1 +BOUNDS + UP BOUND X11 1 + UP BOUND X12 1 + UP BOUND X13 1 + UP BOUND X21 1 + UP BOUND X22 1 + UP BOUND X23 1 + UP BOUND X31 1 + UP BOUND X32 1 + UP BOUND X33 1 +ENDATA diff --git a/mpisppy/tests/test_mps.py b/mpisppy/tests/test_mps.py new file mode 100644 index 000000000..df939b732 --- /dev/null +++ b/mpisppy/tests/test_mps.py @@ -0,0 +1,46 @@ +############################################################################### +# 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. +############################################################################### +# test mps utilities +import unittest +import mpisppy.utils.mps_reader as mps_reader +from mpisppy.tests.utils import get_solver +import pyomo.environ as pyo +import mip # pip install mip (from coin-or) + +solver_available, solver_name, persistent_available, persistent_solver_name= get_solver(persistent_OK=False) + +class TestStochAdmmWrapper(unittest.TestCase): + def setUp(self): + pass + + def tearDown(self): + pass + + def _reader_body(self, fname): + pyomo_model = mps_reader.read_mps_and_create_pyomo_model(fname) + + opt = pyo.SolverFactory(solver_name) + opt.solve(pyomo_model) + pyomo_obj = pyo.value(pyomo_model.objective) + + m = mip.Model(solver_name=solver_name) + m.read(fname) + m.optimize() # returns a status, btw + coin_obj = m.objective_value + self.assertAlmostEqual(coin_obj, pyomo_obj, places=3, + delta=None, msg=None) + + def test_mps_reader_test1(self): + self._reader_body("examples/test1.mps") + + def test_mps_reader_sizes1(self): + self._reader_body("examples/test1.mps") + +if __name__ == '__main__': + unittest.main() diff --git a/mpisppy/utils/mps_reader.py b/mpisppy/utils/mps_reader.py index 7c89d48b2..08524aa8e 100644 --- a/mpisppy/utils/mps_reader.py +++ b/mpisppy/utils/mps_reader.py @@ -10,7 +10,7 @@ import mip # from coin-or (pip install mip) import pyomo.environ as pyo -def read_mps_and_create_pyomo_model(mps_file): +def read_mps_and_create_pyomo_model(mps_path): """ Reads an MPS file using mip and converts it into a Pyomo ConcreteModel. @@ -86,7 +86,7 @@ def _domain_lookup(v): solver_name = "cplex" fname = "delme.mps" #fname = "test1.mps" - pyomo_model = def read_mps_and_create_pyomo_model(fname) + pyomo_model = read_mps_and_create_pyomo_model(fname) pyomo_model.pprint() opt = pyo.SolverFactory(solver_name) From 43d5d0cee14e559364e87ae4f01c2ebaa4ff044d Mon Sep 17 00:00:00 2001 From: "David L. Woodruff" Date: Mon, 17 Mar 2025 14:44:50 -0700 Subject: [PATCH 09/22] [WIP] getting start on testing for the mps module option in generic cylinders --- doc/src/extensions.rst | 8 +++++--- mpisppy/extensions/scenario_lp_mps_files.py | 3 +-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/doc/src/extensions.rst b/doc/src/extensions.rst index ff35ed65d..a4aae4d47 100644 --- a/doc/src/extensions.rst +++ b/doc/src/extensions.rst @@ -277,12 +277,14 @@ only for the Vars you expect before setting it to True. Scenario_lp_mps_writer ---------------------- -This extension writes an lp file and an mps file with the model and +This extension writes an lp file and an mps file with the model as well as a json file with (a) list(s) of scenario tree node names and nonanticaptive variables for each scenario before the iteration zero solve of PH or APH. Note that for two-stage problems, all json files will be the same. See ``mpisppy.generic_cylinders.py`` for an example of use. In that program it is activated with the -``--scenario-lp-mps-writer`` option. +``--scenario-lp-mps-writer`` option. Note that it +writes the files to the current working directory and for each scenario +the base name of the three files written is the scenario name. -Unless you know why you need this, you probably don't. +Unless you know exactly why you need this, you probably don't. diff --git a/mpisppy/extensions/scenario_lp_mps_files.py b/mpisppy/extensions/scenario_lp_mps_files.py index c2eab4bf9..11e280d57 100644 --- a/mpisppy/extensions/scenario_lp_mps_files.py +++ b/mpisppy/extensions/scenario_lp_mps_files.py @@ -17,8 +17,7 @@ def lpize(varname): - # convert varname to the string that will appear in the lp file - # return varname.replace("[", "(").replace("]", ")").replace(",", "_").replace(".","_") + # convert varname to the string that will appear in the lp and mps files return pyomo_label.cpxlp_label_from_name(varname) From 4c039b175a3b3773d18d7eb1e9b9308e4df8713e Mon Sep 17 00:00:00 2001 From: Dave Woodruff Date: Mon, 17 Mar 2025 19:27:50 -0700 Subject: [PATCH 10/22] [WIP] working on testin generic tester and fixing bugs (still need to work on cfg.num_scens --- doc/src/agnostic.rst | 20 ++++++++++-- mpisppy/extensions/scenario_lp_mps_files.py | 6 ++-- mpisppy/generic_cylinders.py | 4 ++- mpisppy/utils/mps_module.py | 36 ++++++++++++--------- 4 files changed, 44 insertions(+), 22 deletions(-) diff --git a/doc/src/agnostic.rst b/doc/src/agnostic.rst index b00ae9500..e443ca307 100644 --- a/doc/src/agnostic.rst +++ b/doc/src/agnostic.rst @@ -7,9 +7,23 @@ as `guest` languages (we refer to mpi-sppy as the `host`). This code is in an alpha-release state; use with extreme caution. This is referred to as `tight` integration with the guest. It is also possible to simply read scenario data from an mps file and the mps file (and the associated json -nonant file) that can be created however you like. Code for creating a -Pyomo model from an mps file is in ``mpisppy.utils.mps_reader.py``. We now -return to a discussion of tight integration with a guest AML. +nonant file) that can be created however you like. + +Loose integration +^^^^^^^^^^^^^^^^^ + +Code for creating a +Pyomo model from an mps file is in ``mpisppy.utils.mps_reader.py``, +but you can also just use ``generic_cylinders.py`` and give +it the module ``mpisppy.utils.mps_module`` (you will need to specify +that path to this module) and the ``--mps-files-directory`` +option. Note +that at the time of this writing, the number of scenarios is obtained +by counting the mps files in the directory given. + + +Tight integration +^^^^^^^^^^^^^^^^^ From the end-user's perspective ------------------------------- diff --git a/mpisppy/extensions/scenario_lp_mps_files.py b/mpisppy/extensions/scenario_lp_mps_files.py index 11e280d57..2dbb6881c 100644 --- a/mpisppy/extensions/scenario_lp_mps_files.py +++ b/mpisppy/extensions/scenario_lp_mps_files.py @@ -33,11 +33,11 @@ def pre_iter0(self): scenDict = {"scenProb": s._mpisppy_probability} # to be added to nodeDict = dict() for nd in s._mpisppy_node_list: - nodeDict[nd] = {"condProb": nd.cond_prob} - nodeDict[nd].update({"nonAnts": [lpize(var.name) for var in nd.nonant_vardata_list]}) + nodeDict[nd.name] = {"condProb": nd.cond_prob} + nodeDict[nd.name].update({"nonAnts": [lpize(var.name) for var in nd.nonant_vardata_list]}) scenDict.update(nodeDict) with open(f"{k}_nonants.json", "w") as jfile: - json.dump(scenDict, jfile) + json.dump(scenDict, jfile, indent=2) def post_iter0(self): return diff --git a/mpisppy/generic_cylinders.py b/mpisppy/generic_cylinders.py index d78e4be76..9011f1099 100644 --- a/mpisppy/generic_cylinders.py +++ b/mpisppy/generic_cylinders.py @@ -64,6 +64,8 @@ def _parse_args(m): # many models, e.g., farmer, need num_scens_required # in which case, it should go in the inparser_adder function # cfg.num_scens_required() + # On the other hand, this program really wants cfg.num_scens somehow so + # maybe it should just require it. cfg.EF_base() # If EF is slected, most other options will be moot # There are some arguments here that will not make sense for all models @@ -120,7 +122,7 @@ def _name_lists(module, cfg, bundle_wrapper=None): "For now, stage2EFsolvern is required for multistage xhat" else: all_nodenames = None - num_scens = cfg.num_scens + num_scens = cfg.get("num_scens") # maybe None is OK # proper bundles should be almost magic if cfg.unpickle_bundles_dir or cfg.scenarios_per_bundle is not None: diff --git a/mpisppy/utils/mps_module.py b/mpisppy/utils/mps_module.py index c59e49016..084c8c156 100644 --- a/mpisppy/utils/mps_module.py +++ b/mpisppy/utils/mps_module.py @@ -7,6 +7,7 @@ # full copyright and license information. ############################################################################### # WARNING: the scenario_creator is very dependent on how the mps_reader works. +# This is designed for use with generic_cylinders.py """You pass in a path to a special mps_files_directory in the config and this module will become a valid mpi-sppy module. The directory has to have file pairs for each scenario where one file @@ -19,12 +20,12 @@ note to dlw from dlw: You could offer the option to split up the objective by stages in the lp file - You could also offer other classes of nonants in the json + You could also offer other types of nonants in the json """ import os import re -import path +import glob import json import mpisppy.utils.mps_reader as mps_reader @@ -41,7 +42,7 @@ def scenario_creator(sname, cfg=None): """ sharedPath = os.path.join(cfg.mps_files_directory, sname) - mpsPath = sharedPath + ".lp" + mpsPath = sharedPath + ".mps" model = mps_reader(mpsPath) # now read the JSON file and attach the tree information. jsonPath = sharedPath + "_nonants.json" @@ -57,7 +58,7 @@ def scenario_creator(sname, cfg=None): for ndn in nonantDict: cp = nonantDict[ndn]["condProb"] nonant_list = nonantDict[ndn]["nonAnts"] - assert parent_ndn == sputils.parent_ndn, + assert parent_ndn == sputils.parent_ndn,\ f"bad node names or parent order in {jsonPath} detected at {ndn}" treeNodes.append(scenario_tree.\ ScenarioNode(name=ndn, @@ -78,21 +79,25 @@ def scenario_creator(sname, cfg=None): #========= -def scenario_names_creator(self, num_scens, start=None): +def scenario_names_creator(num_scens, start=None): # validate the directory and use it to get names (that have to be numbered) + # IMPORTANT: start is zero-based even if the names are one-based! mps_files = [os.path.basename(f) for f in glob.glob(os.path.join(mps_files_directory, "*.mps"))] - if start = None: + if start == None: start = 0 + if num_scens == None: + num_scens = len(mps_files) - start first = re.search(r"\d+$",mps_files[0][:-4]) # first scenario number - assert first == 0, "first scenario number should be zero," - f" found {first} for file {os.path.join(mps_files_directory, lpfiles[0])}" - - retval = [fn[:-4] for fn in mps_files[start, start+num_scens-1]] + print("WARNING: one-based senario names might cause trouble" + f" found {first} for file {os.path.join(mps_files_directory, mps_files[0])}") + assert start+num_scens <= len(mps_files),\ + f"Trying to create scenarios names with {start=}, {num_scens=} but {len(mps_files)=}" + retval = [fn[:-4] for fn in mps_files[start:start+num_scens-1]] return retval #========= -def inparser_adder(self, cfg): +def inparser_adder(cfg): # verify that that the mps_files_directory is there, or add it if "mps_files_directory" not in cfg: cfg.add_to_config("mps_files_directory", @@ -102,21 +107,22 @@ def inparser_adder(self, cfg): argparse=True) #========= -def kw_creator(self, cfg): +def kw_creator(cfg): # creates keywords for scenario creator # SIDE EFFECT: A bit of hack to get the directory path - global mps_files_directory = cfg.mps_files_directory + global mps_files_directory + mps_files_directory = cfg.mps_files_directory return {"cfg": cfg} # This is only needed for sampling -def sample_tree_scen_creator(self, sname, stage, sample_branching_factors, seed, +def sample_tree_scen_creator(sname, stage, sample_branching_factors, seed, given_scenario=None, **scenario_creator_kwargs): # assert two stage, then do the usual for two stages? pass #============================ -def scenario_denouement(self, rank, scenario_name, scenario): +def scenario_denouement(rank, scenario_name, scenario): pass From cd6d7592fac88c0558f34019a363aedf739cc3e7 Mon Sep 17 00:00:00 2001 From: Dave Woodruff Date: Tue, 18 Mar 2025 11:18:47 -0700 Subject: [PATCH 11/22] working on generic tester to get cfg.num_scens taken care of --- examples/generic_tester.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/examples/generic_tester.py b/examples/generic_tester.py index b06866b2d..584fc2585 100644 --- a/examples/generic_tester.py +++ b/examples/generic_tester.py @@ -177,12 +177,20 @@ def do_one(dirname, modname, np, argstring, xhat_baseline_dir=None, tol=1e-6): #rebaseline_xhat("farmer", "farmer", 3, farmer_rd, "test_data/farmer_rd_baseline") do_one("farmer", "farmer", 3, farmer_rd, xhat_baseline_dir="test_data/farmer_rd_baseline") -# Just a smoke test to make sure sizes_expression still exists and -# that lpfiles still executes. +### combined runs to test mps files #### +# Make sure sizes_expression still exists and lpfiles still executes. sizese = ("--module-name sizes_expression --num-scens 3 --default-rho 1" f" --solver-name {solver_name} --max-iterations 0" - " --scenario-lpfiles") -do_one("sizes", "sizes", 3, sizese, xhat_baseline_dir=None) + " --scenario-lp-mps-files") +do_one("sizes", "sizes_expression", 3, sizese, xhat_baseline_dir=None) + +sizesMPS = ("--module-name ../../mpisppy/utils/mps_module --default-rho 1" + f" --solver-name {solver_name} --max-iterations 10" + " --mps-files-directory=.") # we will be in the sizes dir +do_one("sizes", "../../mpisppy/utils/mps_module", 3, sizesMPS, xhat_baseline_dir=None) + + +### end combine mps file runs ### quit() From 0095e0589bb965c9e805a317549220b7a0b07a0e Mon Sep 17 00:00:00 2001 From: Dave Woodruff Date: Tue, 18 Mar 2025 15:38:14 -0700 Subject: [PATCH 12/22] there is now a test for mps reader and mps module in generic tester, but it needs to be easier for xpress --- examples/generic_tester.py | 2 +- mpisppy/spbase.py | 3 ++- mpisppy/utils/mps_module.py | 28 +++++++++++++++++++--------- mpisppy/utils/mps_reader.py | 5 ++--- 4 files changed, 24 insertions(+), 14 deletions(-) diff --git a/examples/generic_tester.py b/examples/generic_tester.py index 584fc2585..625dd4256 100644 --- a/examples/generic_tester.py +++ b/examples/generic_tester.py @@ -187,7 +187,7 @@ def do_one(dirname, modname, np, argstring, xhat_baseline_dir=None, tol=1e-6): sizesMPS = ("--module-name ../../mpisppy/utils/mps_module --default-rho 1" f" --solver-name {solver_name} --max-iterations 10" " --mps-files-directory=.") # we will be in the sizes dir -do_one("sizes", "../../mpisppy/utils/mps_module", 3, sizesMPS, xhat_baseline_dir=None) +do_one("sizes", "../../mpisppy/utils/mps_module", 1, sizesMPS, xhat_baseline_dir=None) ### end combine mps file runs ### diff --git a/mpisppy/spbase.py b/mpisppy/spbase.py index 11479e824..ba1c328b3 100644 --- a/mpisppy/spbase.py +++ b/mpisppy/spbase.py @@ -105,7 +105,8 @@ def __init__( global_toc("Initializing SPBase") if self.n_proc > len(self.all_scenario_names): - raise RuntimeError("More ranks than scenarios") + raise RuntimeError(f"More ranks ({self.n_proc}) than scenarios" + f" ({len(self.all_scenario_names)})") self._calculate_scenario_ranks() # Put the deprecation message in the init so they should only see it once per rank diff --git a/mpisppy/utils/mps_module.py b/mpisppy/utils/mps_module.py index 084c8c156..44d59d577 100644 --- a/mpisppy/utils/mps_module.py +++ b/mpisppy/utils/mps_module.py @@ -18,17 +18,20 @@ must end in a serial number (unless you can't, you should start with 0; otherwise, start with 1). +Parenthesis in variable names in the json file must become underscores + note to dlw from dlw: You could offer the option to split up the objective by stages in the lp file - You could also offer other types of nonants in the json + You should also offer other types of nonants in the json """ import os import re import glob import json +import mpisppy.scenario_tree as scenario_tree +import mpisppy.utils.sputils as sputils import mpisppy.utils.mps_reader as mps_reader - # assume you can get the path from config, set in kw_creator as a side-effect mps_files_directory = None @@ -43,7 +46,8 @@ def scenario_creator(sname, cfg=None): sharedPath = os.path.join(cfg.mps_files_directory, sname) mpsPath = sharedPath + ".mps" - model = mps_reader(mpsPath) + model = mps_reader.read_mps_and_create_pyomo_model(mpsPath) + # now read the JSON file and attach the tree information. jsonPath = sharedPath + "_nonants.json" with open(jsonPath) as f: @@ -55,23 +59,29 @@ def scenario_creator(sname, cfg=None): assert "ROOT" in nonantDict, f'"ROOT" must be top node in {jsonPath}' treeNodes = list() parent_ndn = None # counting on the json file to have ordered nodes + stage = 1 for ndn in nonantDict: + if ndn == "scenProb": # non-nodename at top level of json + continue cp = nonantDict[ndn]["condProb"] - nonant_list = nonantDict[ndn]["nonAnts"] - assert parent_ndn == sputils.parent_ndn,\ - f"bad node names or parent order in {jsonPath} detected at {ndn}" + nonants = [model.\ + find_component(var_name.replace('(','_').replace(')','_')) + for var_name in nonantDict[ndn]["nonAnts"]] + assert parent_ndn == sputils.parent_ndn(ndn),\ + f"bad node names or parent order in {jsonPath} detected at {ndn}" treeNodes.append(scenario_tree.\ ScenarioNode(name=ndn, cond_prob=cp, stage=stage, cost_expression=0.0, - nonant_list=nonant_list, + nonant_list=nonants, scen_model=model, nonant_ef_suppl_list = None, parent_name = parent_ndn ) ) - parent_ndn = ndName + parent_ndn = ndn + stage += 1 model._mpisppy_probability = scenProb model._mpisppy_node_list = treeNodes @@ -93,7 +103,7 @@ def scenario_names_creator(num_scens, start=None): f" found {first} for file {os.path.join(mps_files_directory, mps_files[0])}") assert start+num_scens <= len(mps_files),\ f"Trying to create scenarios names with {start=}, {num_scens=} but {len(mps_files)=}" - retval = [fn[:-4] for fn in mps_files[start:start+num_scens-1]] + retval = [fn[:-4] for fn in mps_files[start:start+num_scens]] return retval #========= diff --git a/mpisppy/utils/mps_reader.py b/mpisppy/utils/mps_reader.py index 08524aa8e..7d78c05f8 100644 --- a/mpisppy/utils/mps_reader.py +++ b/mpisppy/utils/mps_reader.py @@ -6,7 +6,7 @@ # All rights reserved. Please see the files COPYRIGHT.md and LICENSE.md for # full copyright and license information. ############################################################################### - +# IMPORTANT: parens in variable names will become underscore (_) import mip # from coin-or (pip install mip) import pyomo.environ as pyo @@ -46,8 +46,7 @@ def _domain_lookup(v): varDict = dict() # coin mip var to pyomo var # Add variables to Pyomo model with their bounds and domains for v in m.vars: - vname = v.name - print(f"Processing variable with name {vname} and type {v.var_type=}") + vname = v.name.replace("(","_").replace(")","_") varDict[v] = pyo.Var(domain=_domain_lookup(v), bounds=(v.lb, v.ub)) setattr(model, vname, varDict[v]) #print(f"{dir(v)=}") From ed4be65c4dc84b0c930d7dc8db29f1ecaabad965 Mon Sep 17 00:00:00 2001 From: Dave Woodruff Date: Tue, 18 Mar 2025 16:01:44 -0700 Subject: [PATCH 13/22] touch up tests --- examples/generic_tester.py | 7 +++---- mpisppy/tests/test_mps.py | 4 ++-- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/examples/generic_tester.py b/examples/generic_tester.py index 625dd4256..66a43c928 100644 --- a/examples/generic_tester.py +++ b/examples/generic_tester.py @@ -183,14 +183,13 @@ def do_one(dirname, modname, np, argstring, xhat_baseline_dir=None, tol=1e-6): f" --solver-name {solver_name} --max-iterations 0" " --scenario-lp-mps-files") do_one("sizes", "sizes_expression", 3, sizese, xhat_baseline_dir=None) - +# just smoke for now sizesMPS = ("--module-name ../../mpisppy/utils/mps_module --default-rho 1" - f" --solver-name {solver_name} --max-iterations 10" + f" --solver-name {solver_name} --max-iterations 0" " --mps-files-directory=.") # we will be in the sizes dir do_one("sizes", "../../mpisppy/utils/mps_module", 1, sizesMPS, xhat_baseline_dir=None) - -### end combine mps file runs ### +### end combined mps file runs ### quit() diff --git a/mpisppy/tests/test_mps.py b/mpisppy/tests/test_mps.py index df939b732..7ee7297b2 100644 --- a/mpisppy/tests/test_mps.py +++ b/mpisppy/tests/test_mps.py @@ -29,7 +29,7 @@ def _reader_body(self, fname): opt.solve(pyomo_model) pyomo_obj = pyo.value(pyomo_model.objective) - m = mip.Model(solver_name=solver_name) + m = mip.Model(solver_name="CBC") m.read(fname) m.optimize() # returns a status, btw coin_obj = m.objective_value @@ -40,7 +40,7 @@ def test_mps_reader_test1(self): self._reader_body("examples/test1.mps") def test_mps_reader_sizes1(self): - self._reader_body("examples/test1.mps") + self._reader_body("examples/sizes1.mps") if __name__ == '__main__': unittest.main() From 79072bdc9ed57e6dd3141863c5e88ec0adaf7ff6 Mon Sep 17 00:00:00 2001 From: Dave Woodruff Date: Tue, 18 Mar 2025 18:20:40 -0700 Subject: [PATCH 14/22] added a demonstration and some documentation. --- doc/src/agnostic.rst | 8 ++++++++ examples/sizes/mps_demo.bash | 23 +++++++++++++++++++++++ 2 files changed, 31 insertions(+) create mode 100644 examples/sizes/mps_demo.bash diff --git a/doc/src/agnostic.rst b/doc/src/agnostic.rst index e443ca307..7179e4483 100644 --- a/doc/src/agnostic.rst +++ b/doc/src/agnostic.rst @@ -21,6 +21,14 @@ option. Note that at the time of this writing, the number of scenarios is obtained by counting the mps files in the directory given. +The file ``examples.sizes.mps_demo.txt`` has two commands. The second illustrates +how to instruction ``MPI-SPPY`` to read mps/json file pairs for each scenario from a +directory. The first command illustrates how to use ``MPI-SPPY`` to write +them in the first place (but if ``MPI-SPPY`` can get your scenarios, there +is probably no reason to write them and then read them again!). This +functionality is intended to be used by users of other AMLs or other +scenario-based stochastic programming applications. + Tight integration ^^^^^^^^^^^^^^^^^ diff --git a/examples/sizes/mps_demo.bash b/examples/sizes/mps_demo.bash new file mode 100644 index 000000000..50dd6fd48 --- /dev/null +++ b/examples/sizes/mps_demo.bash @@ -0,0 +1,23 @@ +#!/bin/bash +# This is mainly to demonstrate what loose agnostic files look like and +# how to use them with agnostic_cylinders.py. +# To do that, we write the files based on a Pyomo model, then +# read them in. +# Note: if you actually have a Pyomo model, you probably don't want to do +# it this way since you would have had to have written most of the +# functions (e.g. scenario_creator) anyway. +# If you are using some other AML, then you migth want to use the second +# command line to read the files you wrote with your AML and +# you can use the first command to write files as an example of the format +# for the json files. + +set -e + +SOLVER=cplex + +# assumes we are in the sizes directory and don't mind polluting it with 6 files +python ../../mpisppy/generic_cylinders.py --module-name sizes_expression --num-scens 3 --default-rho 1 --solver-name ${SOLVER} --max-iterations 0 --scenario-lp-mps-files + +# By specifying the module to be mps_module we will read files for the problem +# from the specified mps-files-directory. +mpiexec -np 3 python -m mpi4py ../../mpisppy/generic_cylinders.py --module-name ../../mpisppy/utils/mps_module --xhatshuffle --lagrangian --default-rho 1 --solver-name ${SOLVER} --max-iterations 10 --mps-files-directory=. From a8aba600fffa5cfed9d06f63dc066d7994031ede Mon Sep 17 00:00:00 2001 From: Dave Woodruff Date: Tue, 18 Mar 2025 19:23:15 -0700 Subject: [PATCH 15/22] tests are ready to run? --- .github/workflows/test_pr_and_main.yml | 30 ++++++++++++++++++++- mpisppy/extensions/scenario_lp_mps_files.py | 9 +++---- mpisppy/utils/mps_module.py | 13 ++++++--- 3 files changed, 43 insertions(+), 9 deletions(-) diff --git a/.github/workflows/test_pr_and_main.yml b/.github/workflows/test_pr_and_main.yml index 2e96054c3..cd9e75cec 100644 --- a/.github/workflows/test_pr_and_main.yml +++ b/.github/workflows/test_pr_and_main.yml @@ -310,6 +310,34 @@ jobs: python test_pickle_bundle.py + mps: + name: MPS tests + runs-on: ubuntu-latest + needs: [ruff] + + steps: + - uses: actions/checkout@v3 + - uses: conda-incubator/setup-miniconda@v2 + with: + activate-environment: test_env + python-version: 3.11 + auto-activate-base: false + - name: Install dependencies + run: | + conda install mpi4py pandas setuptools + pip install pyomo xpress cplex mip + + - name: setup the program + run: | + pip install -e . + + - name: run MPS tests + timeout-minutes: 2 + run: | + cd mpisppy/tests + python test_mps.py + + confidence-intervals: name: confidence intervals tests runs-on: ubuntu-latest @@ -494,7 +522,7 @@ jobs: timeout-minutes: 10 run: | cd mpisppy/tests - mpiexec -np 2 python -m mpi4py test_with_cylinders.py + mpiexec -np 2 python -m mpi4py test_with_cylinders.py test-agnostic: name: tests on agnostic diff --git a/mpisppy/extensions/scenario_lp_mps_files.py b/mpisppy/extensions/scenario_lp_mps_files.py index 2dbb6881c..73bcbb46b 100644 --- a/mpisppy/extensions/scenario_lp_mps_files.py +++ b/mpisppy/extensions/scenario_lp_mps_files.py @@ -30,12 +30,11 @@ def pre_iter0(self): for k, s in self.ph.local_subproblems.items(): s.write(f"{k}.lp", io_options={'symbolic_solver_labels': True}) s.write(f"{k}.mps", io_options={'symbolic_solver_labels': True}) - scenDict = {"scenProb": s._mpisppy_probability} # to be added to - nodeDict = dict() + scenData = {"name": s.name, "scenProb": s._mpisppy_probability} + scenDict = {"scenarioData": scenData} for nd in s._mpisppy_node_list: - nodeDict[nd.name] = {"condProb": nd.cond_prob} - nodeDict[nd.name].update({"nonAnts": [lpize(var.name) for var in nd.nonant_vardata_list]}) - scenDict.update(nodeDict) + scenDict[nd.name] = {"condProb": nd.cond_prob} + scenDict[nd.name].update({"nonAnts": [lpize(var.name) for var in nd.nonant_vardata_list]}) with open(f"{k}_nonants.json", "w") as jfile: json.dump(scenDict, jfile, indent=2) diff --git a/mpisppy/utils/mps_module.py b/mpisppy/utils/mps_module.py index 44d59d577..538374607 100644 --- a/mpisppy/utils/mps_module.py +++ b/mpisppy/utils/mps_module.py @@ -53,7 +53,7 @@ def scenario_creator(sname, cfg=None): with open(jsonPath) as f: nonantDict = json.load(f) try: - scenProb = nonantDict["scenProb"] + scenProb = nonantDict["scenarioData"]["scenProb"] except Exception as e: raise RuntimeError(f'Error getting scenProb from {jsonPath}: {e}') assert "ROOT" in nonantDict, f'"ROOT" must be top node in {jsonPath}' @@ -61,7 +61,7 @@ def scenario_creator(sname, cfg=None): parent_ndn = None # counting on the json file to have ordered nodes stage = 1 for ndn in nonantDict: - if ndn == "scenProb": # non-nodename at top level of json + if ndn == "scenarioData": # non-nodename at top level of json continue cp = nonantDict[ndn]["condProb"] nonants = [model.\ @@ -94,13 +94,20 @@ def scenario_names_creator(num_scens, start=None): # IMPORTANT: start is zero-based even if the names are one-based! mps_files = [os.path.basename(f) for f in glob.glob(os.path.join(mps_files_directory, "*.mps"))] + mps_files.sort() if start == None: start = 0 if num_scens == None: num_scens = len(mps_files) - start first = re.search(r"\d+$",mps_files[0][:-4]) # first scenario number + try: + first = int(first.group()) + except Exception as e: + raise RuntimeError(f'mps files in {mps_files_directory} must end with an integer' + f'found file {mps_files[0]} (error was: {e})') + print("WARNING: one-based senario names might cause trouble" - f" found {first} for file {os.path.join(mps_files_directory, mps_files[0])}") + f" found {first} for dir {mps_files_directory}") assert start+num_scens <= len(mps_files),\ f"Trying to create scenarios names with {start=}, {num_scens=} but {len(mps_files)=}" retval = [fn[:-4] for fn in mps_files[start:start+num_scens]] From ea8ad8c82a8e0cec50cb76db0c1f419aaf18b246 Mon Sep 17 00:00:00 2001 From: Dave Woodruff Date: Wed, 19 Mar 2025 11:04:10 -0700 Subject: [PATCH 16/22] cleanup --- mpisppy/utils/mps_coin_mip.py | 113 ---------------------------------- mpisppy/utils/mps_module.py | 6 +- 2 files changed, 3 insertions(+), 116 deletions(-) delete mode 100644 mpisppy/utils/mps_coin_mip.py diff --git a/mpisppy/utils/mps_coin_mip.py b/mpisppy/utils/mps_coin_mip.py deleted file mode 100644 index 726bf5a68..000000000 --- a/mpisppy/utils/mps_coin_mip.py +++ /dev/null @@ -1,113 +0,0 @@ -############################################################################### -# mpi-sppy: MPI-based Stochastic Programming in PYthon -# -# Copyright (c) 2025, 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. -############################################################################### - -import mip # from coin-or (pip install mip) -import pyomo.environ as pyo -from pyomo.environ import NonNegativeReals, minimize, maximize - -def read_mps_and_create_pyomo_model(mps_file): - """ - Reads an MPS file using mip and converts it into a Pyomo ConcreteModel. - - :param mps_path: Path to the MPS file. - :return: Pyomo ConcreteModel. - - aside: Chatgpt was almost negative help with this function... - """ - - def _domain_lookup(v): - # given a mip var, return its Pyomo domain - if v.var_type == 'C': - if v.lb == 0.0: - return pyo.NonNegativeReals - else: - return pyo.Reals - elif v.var_type == 'B': - return pyo.Binary - elif v.var_type == 'I': - return pyo.Integers - else: - raise RuntimeError(f"Unknown type from coin mip {v.var_type=}") - # BTW: I don't know how to query the mip object for SOS sets - # (maybe it transforms them?) - - # Read the MPS file and call the coin-mip model m - m = mip.Model(solver_name="cbc") - m.read(mps_path) - - # Create a Pyomo model - model = pyo.ConcreteModel() - - varDict = dict() # coin mip var to pyomo var - # Add variables to Pyomo model with their bounds and domains - for v in m.vars: - vname = v.name - print(f"Processing variable with name {vname} and type {v.var_type=}") - varDict[v] = pyo.Var(domain=_domain_lookup(v), bounds=(v.lb, v.ub)) - setattr(model, vname, varDict[v]) - #print(f"{dir(v)=}") - - # Add constraints - for c in m.constrs: - # Extract terms correctly from LinExpr - body = sum(coeff * varDict[var] for var, coeff in c.expr.expr.items()) - - if c.expr.sense == "=": - pyomoC = pyo.Constraint(expr=body == c.rhs) - elif c.expr.sense == ">": - pyomoC = pyo.Constraint(expr=body >= c.rhs) - elif c.expr.sense == "<": - pyomoC = pyo.Constraint(expr=body <= c.rhs) - elif c.expr.sense == "": - raise RuntimeError(f"Unexpected empty sense for constraint {c.name}" - f" from file {mps_path}") - else: - raise RuntimeError(f"Unexpected sense {c.expr.sense=}" - f" for constraint {c.name} from file {mps_path}") - setattr(model, c.name, pyomoC) - - # objective function - obj_expr = sum(coeff * varDict[v] for v, coeff in m.objective.expr.items()) - if m.sense == mip.MINIMIZE: - model.objective = pyo.Objective(expr=obj_expr, sense=minimize) - else: - model.objective = pyo.Objective(expr=obj_expr, sense=maximize) - - return model - - -if __name__ == "__main__": - # for testing - solver_name = "cplex" - fname = "delme.mps" - #fname = "test1.mps" - pyomo_model = def read_mps_and_create_pyomo_model(fname) - pyomo_model.pprint() - - opt = pyo.SolverFactory(solver_name) - opt.solve(pyomo_model) - pyomo_obj = pyo.value(pyomo_model.objective) - - m = mip.Model(solver_name=solver_name) - m.read(fname) - coinstatus = m.optimize() - coin_obj = m.objective_value - print(f"{coinstatus=}, {coin_obj=}, {pyomo_obj=}") - - print("\ncoin var values") - for v in m.vars: - print(f"{v.name}: {v.x}") - - print("\npyomo var values") - for v in pyomo_model.component_objects(pyo.Var, active=True): - print(f"Variable: {v.name}") - for index in v: - print(f" Index: {index}, Value: {v[index].value}") - - diff --git a/mpisppy/utils/mps_module.py b/mpisppy/utils/mps_module.py index 538374607..8e522d2eb 100644 --- a/mpisppy/utils/mps_module.py +++ b/mpisppy/utils/mps_module.py @@ -1,4 +1,4 @@ -##r########################################################################### +############################################################################## # mpi-sppy: MPI-based Stochastic Programming in PYthon # # Copyright (c) 2025, Lawrence Livermore National Security, LLC, Alliance for @@ -95,9 +95,9 @@ def scenario_names_creator(num_scens, start=None): mps_files = [os.path.basename(f) for f in glob.glob(os.path.join(mps_files_directory, "*.mps"))] mps_files.sort() - if start == None: + if start is None: start = 0 - if num_scens == None: + if num_scens is None: num_scens = len(mps_files) - start first = re.search(r"\d+$",mps_files[0][:-4]) # first scenario number try: From 93bdd0c1d084379e3b8b3e9324e5c651d38f8a50 Mon Sep 17 00:00:00 2001 From: Dave Woodruff Date: Wed, 19 Mar 2025 17:44:56 -0700 Subject: [PATCH 17/22] trying to debug the admm_wrapper test --- mpisppy/tests/test_admmWrapper.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/mpisppy/tests/test_admmWrapper.py b/mpisppy/tests/test_admmWrapper.py index 1371f2653..b2652519b 100644 --- a/mpisppy/tests/test_admmWrapper.py +++ b/mpisppy/tests/test_admmWrapper.py @@ -6,6 +6,7 @@ # All rights reserved. Please see the files COPYRIGHT.md and LICENSE.md for # full copyright and license information. ############################################################################### +# TBD: make these tests less fragile import unittest import mpisppy.utils.admmWrapper as admmWrapper import examples.distr as distr @@ -16,6 +17,7 @@ import os solver_available, solver_name, persistent_available, persistent_solver_name= get_solver() +solver_name = 'cplex' class TestAdmmWrapper(unittest.TestCase): def setUp(self): @@ -95,7 +97,8 @@ def _extracting_output(self, line): inner_bound = match.group(2) return float(outer_bound), float(inner_bound) else: - raise RuntimeError("The test is probably not correctly adapted: can't match the format of the line") + raise RuntimeError("Cannot find outer and inner bounds in pattern" + f" in this output {line=}") def test_values(self): command_line_pairs = [(f"mpiexec -np 3 python -u -m mpi4py distr_admm_cylinders.py --num-scens 3 --default-rho 10 --solver-name {solver_name} --max-iterations 50 --xhatxbar --lagrangian --rel-gap 0.01 --ensure-xhat-feas" \ @@ -112,11 +115,16 @@ def test_values(self): result = subprocess.run(command, capture_output=True, text=True) if result.stderr: print("Error output:") - print(result.stderr) + raise RuntimeError(result.stderr) + # Check the standard output if result.stdout: result_by_line = result.stdout.strip().split('\n') + else: + print(f"{result.stdout=}, {result.returncode=}") + raise RuntimeError(f"Cannot get output from {command=}") + target_line = "Iter. Best Bound Best Incumbent Rel. Gap Abs. Gap" precedent_line_target = False From d16a7d40943f71caff6ef8526d84a1715c753f44 Mon Sep 17 00:00:00 2001 From: David L Woodruff Date: Wed, 19 Mar 2025 18:12:29 -0700 Subject: [PATCH 18/22] Update test_admmWrapper.py --- mpisppy/tests/test_admmWrapper.py | 1 - 1 file changed, 1 deletion(-) diff --git a/mpisppy/tests/test_admmWrapper.py b/mpisppy/tests/test_admmWrapper.py index b2652519b..2118effb9 100644 --- a/mpisppy/tests/test_admmWrapper.py +++ b/mpisppy/tests/test_admmWrapper.py @@ -17,7 +17,6 @@ import os solver_available, solver_name, persistent_available, persistent_solver_name= get_solver() -solver_name = 'cplex' class TestAdmmWrapper(unittest.TestCase): def setUp(self): From 6f2c4d180d94318a9f44d7ded7f834406890a5bd Mon Sep 17 00:00:00 2001 From: Dave Woodruff Date: Thu, 20 Mar 2025 17:46:21 -0700 Subject: [PATCH 19/22] make the top level of the json file schema symmetric --- mpisppy/extensions/scenario_lp_mps_files.py | 6 ++++-- mpisppy/utils/mps_module.py | 9 ++++----- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/mpisppy/extensions/scenario_lp_mps_files.py b/mpisppy/extensions/scenario_lp_mps_files.py index 73bcbb46b..807e623c1 100644 --- a/mpisppy/extensions/scenario_lp_mps_files.py +++ b/mpisppy/extensions/scenario_lp_mps_files.py @@ -32,9 +32,11 @@ def pre_iter0(self): s.write(f"{k}.mps", io_options={'symbolic_solver_labels': True}) scenData = {"name": s.name, "scenProb": s._mpisppy_probability} scenDict = {"scenarioData": scenData} + treeData = dict() for nd in s._mpisppy_node_list: - scenDict[nd.name] = {"condProb": nd.cond_prob} - scenDict[nd.name].update({"nonAnts": [lpize(var.name) for var in nd.nonant_vardata_list]}) + treeData[nd.name] = {"condProb": nd.cond_prob} + treeData[nd.name].update({"nonAnts": [lpize(var.name) for var in nd.nonant_vardata_list]}) + scenDict["treeData"] = treeData with open(f"{k}_nonants.json", "w") as jfile: json.dump(scenDict, jfile, indent=2) diff --git a/mpisppy/utils/mps_module.py b/mpisppy/utils/mps_module.py index 8e522d2eb..b7c182d17 100644 --- a/mpisppy/utils/mps_module.py +++ b/mpisppy/utils/mps_module.py @@ -60,13 +60,12 @@ def scenario_creator(sname, cfg=None): treeNodes = list() parent_ndn = None # counting on the json file to have ordered nodes stage = 1 - for ndn in nonantDict: - if ndn == "scenarioData": # non-nodename at top level of json - continue - cp = nonantDict[ndn]["condProb"] + treeDict = nonantDict["treeData"] + for ndn in treeDict: + cp = treeDict[ndn]["condProb"] nonants = [model.\ find_component(var_name.replace('(','_').replace(')','_')) - for var_name in nonantDict[ndn]["nonAnts"]] + for var_name in treeDict[ndn]["nonAnts"]] assert parent_ndn == sputils.parent_ndn(ndn),\ f"bad node names or parent order in {jsonPath} detected at {ndn}" treeNodes.append(scenario_tree.\ From 34dde2d576a3430452d773e231292794cebc3088 Mon Sep 17 00:00:00 2001 From: David L Woodruff Date: Thu, 3 Apr 2025 11:17:29 -0700 Subject: [PATCH 20/22] Update doc/src/agnostic.rst Co-authored-by: bknueven <30801372+bknueven@users.noreply.github.com> --- doc/src/agnostic.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/src/agnostic.rst b/doc/src/agnostic.rst index 7179e4483..fd0aec27c 100644 --- a/doc/src/agnostic.rst +++ b/doc/src/agnostic.rst @@ -21,7 +21,7 @@ option. Note that at the time of this writing, the number of scenarios is obtained by counting the mps files in the directory given. -The file ``examples.sizes.mps_demo.txt`` has two commands. The second illustrates +The file ``examples.sizes.mps_demo.bash`` has two commands. The second illustrates how to instruction ``MPI-SPPY`` to read mps/json file pairs for each scenario from a directory. The first command illustrates how to use ``MPI-SPPY`` to write them in the first place (but if ``MPI-SPPY`` can get your scenarios, there From 63340f75f08ba72e1305dd2039b4a5fd1a6e6dc6 Mon Sep 17 00:00:00 2001 From: David L Woodruff Date: Thu, 3 Apr 2025 11:27:12 -0700 Subject: [PATCH 21/22] Update mpisppy/utils/mps_reader.py Co-authored-by: bknueven <30801372+bknueven@users.noreply.github.com> --- mpisppy/utils/mps_reader.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mpisppy/utils/mps_reader.py b/mpisppy/utils/mps_reader.py index 7d78c05f8..28c1a2655 100644 --- a/mpisppy/utils/mps_reader.py +++ b/mpisppy/utils/mps_reader.py @@ -57,11 +57,11 @@ def _domain_lookup(v): body = sum(coeff * varDict[var] for var, coeff in c.expr.expr.items()) if c.expr.sense == "=": - pyomoC = pyo.Constraint(expr=body == c.rhs) + pyomoC = pyo.Constraint(expr=(c.rhs, body, c.rhs)) elif c.expr.sense == ">": - pyomoC = pyo.Constraint(expr=body >= c.rhs) + pyomoC = pyo.Constraint(expr=(c.rhs, body, None)) elif c.expr.sense == "<": - pyomoC = pyo.Constraint(expr=body <= c.rhs) + pyomoC = pyo.Constraint(expr=(None, body, c.rhs)) elif c.expr.sense == "": raise RuntimeError(f"Unexpected empty sense for constraint {c.name}" f" from file {mps_path}") From a551134d10d20e6659cf5c3f9025e8da57d50615 Mon Sep 17 00:00:00 2001 From: Dave Woodruff Date: Thu, 3 Apr 2025 11:33:35 -0700 Subject: [PATCH 22/22] change the name of the mps write opion in generic_cylinders --- examples/generic_tester.py | 2 +- mpisppy/generic_cylinders.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/generic_tester.py b/examples/generic_tester.py index 66a43c928..4329d4e42 100644 --- a/examples/generic_tester.py +++ b/examples/generic_tester.py @@ -181,7 +181,7 @@ def do_one(dirname, modname, np, argstring, xhat_baseline_dir=None, tol=1e-6): # Make sure sizes_expression still exists and lpfiles still executes. sizese = ("--module-name sizes_expression --num-scens 3 --default-rho 1" f" --solver-name {solver_name} --max-iterations 0" - " --scenario-lp-mps-files") + " --write-scenario-lp-mps-files") do_one("sizes", "sizes_expression", 3, sizese, xhat_baseline_dir=None) # just smoke for now sizesMPS = ("--module-name ../../mpisppy/utils/mps_module --default-rho 1" diff --git a/mpisppy/generic_cylinders.py b/mpisppy/generic_cylinders.py index b83debaf5..f39199e0c 100644 --- a/mpisppy/generic_cylinders.py +++ b/mpisppy/generic_cylinders.py @@ -55,7 +55,7 @@ def _parse_args(m): description="The string used for a directory of ouput along with a csv and an npv file (default None, which means no soltion output)", domain=str, default=None) - cfg.add_to_config(name="scenario_lp_mps_files", + cfg.add_to_config(name="write_scenario_lp_mps_files", description="Invokes an extension that writes an model lp file, mps file and a nonants json file for each scenario before iteration 0", domain=bool, default=False) @@ -232,7 +232,7 @@ def _do_decomp(module, cfg, scenario_creator, scenario_creator_kwargs, scenario_ ext_classes.append(Gradient_extension) hub_dict['opt_kwargs']['options']['gradient_extension_options'] = {'cfg': cfg} - if cfg.scenario_lp_mps_files: + if cfg.write_scenario_lp_mps_files: ext_classes.append(Scenario_lp_mps_files) if cfg.W_and_xbar_reader: