Skip to content

Commit

Permalink
Remove solve.patch_pyomo()
Browse files Browse the repository at this point in the history
This code previously patched Pyomo to remove a quadratic dependency
on the number of variables around Pyomo 5.4. This dependency was
fixed in [Pyomo 6.4.1](Pyomo/pyomo#2351) but
testing indicates that Pyomo 6.0.0+ don't go down that path when
using our typical solvers (cplex, gurobi, cplexamp, appsi_highs,
glpk, cbc). This patch has also caused problems for some users
with new versions of Pyomo that install without source code. So we
are removing the patch now.
  • Loading branch information
mfripp committed Apr 25, 2024
1 parent ace3c3f commit 1fb85d9
Showing 1 changed file with 0 additions and 80 deletions.
80 changes: 0 additions & 80 deletions switch_model/solve.py
Original file line number Diff line number Diff line change
Expand Up @@ -167,10 +167,6 @@ def main(args=None, return_model=False, return_instance=False):
# the current module (to register define_arguments callback)
modules = get_module_list(args)

# Patch pyomo if needed, to allow reconstruction of expressions.
# This must be done before the model is constructed.
patch_pyomo()

# Define the model
model = create_model(modules, args=args, logger=logger)
# Add any suffixes specified on the command line (usually only iis)
Expand Down Expand Up @@ -391,82 +387,6 @@ def reload_prior_solution_from_pickle(instance, pickle_file):
return instance


patched_pyomo = False


def patch_pyomo():
# patch Pyomo if needed
global patched_pyomo
if patched_pyomo:
return
patched_pyomo = True

# Pyomo 5.1.1 (and maybe others) is very slow to load prior solutions
# because it does a full-component search for each component name as it
# assigns the data. This ends up taking longer than solving the model. So we
# micro- patch pyomo.core.base.PyomoModel.ModelSolutions.add_solution to use
# Pyomo's built-in caching system for component names.
# TODO: create a pull request for Pyomo to do this
# NOTE: space inside the long quotes is significant; must match the Pyomo code
old_code = """
for obj in instance.component_data_objects(Var):
cache[obj.name] = obj
for obj in instance.component_data_objects(Objective, active=True):
cache[obj.name] = obj
for obj in instance.component_data_objects(Constraint, active=True):
cache[obj.name] = obj"""
new_code = """
# use buffer to avoid full search of component for data object
# which introduces a delay that is quadratic in model size
buf=dict()
for obj in instance.component_data_objects(Var):
cache[obj.getname(fully_qualified=True, name_buffer=buf)] = obj
for obj in instance.component_data_objects(Objective, active=True):
cache[obj.getname(fully_qualified=True, name_buffer=buf)] = obj
for obj in instance.component_data_objects(Constraint, active=True):
cache[obj.getname(fully_qualified=True, name_buffer=buf)] = obj"""

from pyomo.core.base.PyomoModel import ModelSolutions

add_solution_code = inspect.getsource(ModelSolutions.add_solution)
if old_code in add_solution_code:
# create and inject a new version of the method
add_solution_code = add_solution_code.replace(old_code, new_code)
replace_method(ModelSolutions, "add_solution", add_solution_code)
elif pyomo.version.version_info[:2] >= (5, 0):
# We don't allow later versions of Pyomo than we've tested with, so
# this should only show up during testing when preparing to release a
# new version.
print(
"NOTE: The patch to pyomo.core.base.PyomoModel.ModelSolutions.add_solution "
"has been deactivated because the Pyomo source code has changed. "
f"Check whether this patch is still needed and edit {__file__} "
"accordingly."
)


def replace_method(class_ref, method_name, new_source_code):
"""
Replace specified class method with a compiled version of new_source_code.
"""
orig_method = getattr(class_ref, method_name)
# compile code into a function
workspace = dict()
exec(textwrap.dedent(new_source_code), workspace)
new_method = workspace[method_name]
# create a new function with the same body, but using the old method's namespace
new_func = types.FunctionType(
new_method.__code__,
orig_method.__globals__,
orig_method.__name__,
orig_method.__defaults__,
orig_method.__closure__,
)
# note: this normal function will be automatically converted to an unbound
# method when it is assigned as an attribute of a class
setattr(class_ref, method_name, new_func)


def reload_prior_solution_from_csvs(instance):
"""
Assign values to all model variables from <variable>.csv files saved after
Expand Down

0 comments on commit 1fb85d9

Please sign in to comment.