Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

pyomo.contrib.alternative_solutions #3270

Merged
merged 107 commits into from
Aug 14, 2024
Merged
Show file tree
Hide file tree
Changes from 76 commits
Commits
Show all changes
107 commits
Select commit Hold shift + click to select a range
466442d
- Adding alternative solutions code from CI-MOR to the alternative_so…
jlgearh Feb 26, 2023
15db08e
- Completed an initial version of the solnpool wrapper.
jlgearh Feb 28, 2023
58d18b9
- Refactoring the aos code to be more general for inclusion as a Pyom…
jlgearh Apr 10, 2023
7861cda
- Cleaning up aos code and adding test cases
jlgearh Aug 30, 2023
ac8eccf
Merge branch 'main' of https://github.com/Pyomo/pyomo into aos
jlgearh Aug 30, 2023
a698d31
- Updating OBBT
jlgearh Sep 5, 2023
f36faa1
- Finalizing OBBT code
jlgearh Sep 13, 2023
af46d29
- Adding test files
jlgearh Sep 13, 2023
ef2c135
- Updating aos_utils.py and adding test cases
jlgearh Oct 3, 2023
0b72a21
- Added test cases for AOS utils
jlgearh Oct 5, 2023
3a98410
- Finished aos_utils and solution test cases
jlgearh Oct 5, 2023
be7d07e
- Updated balas.py to match the pattern in obbt.py and solnpool.py
jlgearh Oct 6, 2023
53b8bb1
- Updating test cases
jlgearh Oct 9, 2023
407e7c8
- Simplified solnpool.py by setting pool mode to 2
jlgearh Oct 9, 2023
6b60993
- Added code to enumerate the discrete feasiable points for the diamo…
jlgearh Oct 10, 2023
ed4b04e
added some tests
barguel Oct 17, 2023
b512c4c
- Updating LP Enumeration Code
jlgearh Oct 19, 2023
8cf9c95
- Renamed the canonical_lp.py file to shifted_lp.py, and completed in…
jlgearh Oct 20, 2023
546959f
- Added missing constraint from Lee paper
jlgearh Oct 20, 2023
0641565
- Added script to run lp enumeration
jlgearh Oct 25, 2023
3c241df
adding testes
barguel Jan 8, 2024
5e15a7f
- Added an additional test case
jlgearh Jan 10, 2024
9ea9c06
Merge branch 'aos' of https://github.com/jlgearh/pyomo into aos
jlgearh Jan 10, 2024
ac7f3a2
making sure tests run and changing hexagonal cases to pentagonal cases
barguel Jan 12, 2024
d18c117
Merge branch 'aos' of https://github.com/jlgearh/pyomo into aos
jlgearh Jan 17, 2024
7065b05
Eliminted a double counting issues with constant terms in objectives …
jlgearh Jan 17, 2024
0dff860
- Fixed issue where constants in expressions were being double counte…
jlgearh Jan 23, 2024
42eadd2
Merge branch 'aos' of https://github.com/jlgearh/pyomo into aos
jlgearh Jan 23, 2024
db92d2c
- Updated gitignore file
jlgearh Feb 8, 2024
7569e78
Merge branch 'main' of https://github.com/jlgearh/pyomo into aos
jlgearh Feb 8, 2024
194ddd2
Working through tests
whart222 Apr 8, 2024
d5a2b57
Merge remote-tracking branch 'up/main' into aos
whart222 Apr 8, 2024
bb24237
Two changes
whart222 Apr 9, 2024
654a2d2
Adding test to improve coverage
whart222 Apr 9, 2024
a8c4fcc
Testing output with strings
whart222 Apr 9, 2024
d10da79
Adding solnpool tests
whart222 Apr 9, 2024
c49af49
Reformatting with black
whart222 Apr 9, 2024
f162e97
Enable seeding of numpy RNG
whart222 Apr 9, 2024
28e31a0
Changes to enable top-level imports
whart222 Apr 9, 2024
c3ae9e7
Various changes needed to make tests work
whart222 Apr 9, 2024
8f563e3
Adding Balas tests
whart222 Apr 9, 2024
146a05b
Adding top-level imports
whart222 Apr 9, 2024
98be8f7
Reformatting with black
whart222 Apr 9, 2024
e055273
Fixing some solver interface issues
whart222 Apr 9, 2024
4ddcb5b
Various changes to improve test coverage
whart222 Apr 10, 2024
d0cef46
Various updates to improve coverage
whart222 Apr 12, 2024
5abdce0
Adding some documentation
whart222 May 21, 2024
8ac2c6e
Adding a shifted pentagonal test
whart222 May 21, 2024
da840b1
Reformatting with black
whart222 May 21, 2024
f912d7f
Configure Gurobi to avoid using MIP heuristics
whart222 May 22, 2024
45ddc79
Fixing misspellings
whart222 May 22, 2024
46b4f11
Remove strong dependence on gurobipy
whart222 May 22, 2024
5b13115
Merge branch 'main' into aos
whart222 May 22, 2024
6f85aeb
Reformatting
whart222 Jun 26, 2024
2b11d19
Updating Balas test
whart222 Jun 27, 2024
83800ae
Unreformatting.
whart222 Jun 27, 2024
7ac7560
Fixing tests to work without gurobi
whart222 Jul 3, 2024
61f90b5
Reformatting
whart222 Jul 3, 2024
0727b3c
Merge branch 'main' into aos
whart222 Jul 3, 2024
6637a75
Merge branch 'main' into aos
whart222 Jul 7, 2024
f55e712
Merge branch 'main' into aos
blnicho Jul 8, 2024
8274809
Adding guards for when numpy is not installed
whart222 Jul 8, 2024
209fb66
Reformatting with black
whart222 Jul 8, 2024
439b1f8
Removing the "no_time" tests
whart222 Jul 8, 2024
570f7da
- Updated the lp_enum_solnpool so that lp enumeration can be done usi…
jlgearh Jul 9, 2024
b4b7a85
Merge branch 'main' into aos
whart222 Jul 10, 2024
317c321
Reformatting and adding numpy guards
whart222 Jul 10, 2024
9d1d355
Reformatting
whart222 Jul 10, 2024
caec738
Fixing spelling
whart222 Jul 10, 2024
c6674b6
Merge branch 'main' into aos
mrmundt Jul 10, 2024
45ed7c3
Commenting out test that fails
whart222 Jul 10, 2024
6853e62
Adding guards for gurobipy
whart222 Jul 10, 2024
a1c14ab
Resolving PR issues
whart222 Jul 29, 2024
a28b7b4
Further updates for the PR
whart222 Jul 29, 2024
1ab6d2a
Fix test errors when gurobi not installed
whart222 Jul 29, 2024
4d1e346
Removing pytest.mark.skipif logic
whart222 Jul 29, 2024
dbf8408
Fixing doc
whart222 Jul 29, 2024
21e3a02
Merge branch 'main' into aos
blnicho Jul 31, 2024
67f0cf9
Adding online documentation
whart222 Aug 2, 2024
a2639e5
Fixing docs
whart222 Aug 2, 2024
5f3d9fc
Typo fix
whart222 Aug 2, 2024
6345ece
Using glpk for doctests
whart222 Aug 5, 2024
c3f12da
Resolving PR concerns
whart222 Aug 6, 2024
15b47bc
Reformatting
whart222 Aug 6, 2024
7ade8ac
Raising exceptions when a solver is not available
whart222 Aug 6, 2024
835733b
Using logging instead of quiet/debug
whart222 Aug 6, 2024
d4c4266
Changed objective re-initialization
whart222 Aug 6, 2024
b86ec2d
Reverting a recent change
whart222 Aug 7, 2024
ebb2296
Several changes
whart222 Aug 8, 2024
ead2c59
Removing deprecated TODO
whart222 Aug 8, 2024
8e22108
Raise an exception for models with binary vars
whart222 Aug 8, 2024
1a20276
- Updated logic used to tighten bounds for OBBT to use a constraint l…
jlgearh Aug 12, 2024
65c37a8
Several changes
whart222 Aug 12, 2024
866284b
Merge branch 'aos' of github.com:jlgearh/pyomo into aos
whart222 Aug 12, 2024
91d2562
Spelling fix
whart222 Aug 12, 2024
a1233de
Better exception handling
whart222 Aug 12, 2024
49b89fc
Revising text describing the simple example here
whart222 Aug 12, 2024
a47ecab
Removing an unneeded comment
emma58 Aug 12, 2024
7c597e6
Setting exception_flag to False for opt.available when aos is trying …
emma58 Aug 12, 2024
ac8b744
- Added terminate functionality
jlgearh Aug 12, 2024
baccf1c
Merge branch 'aos' of https://github.com/jlgearh/pyomo into aos
jlgearh Aug 12, 2024
60e725a
Merge branch 'main' into aos
emma58 Aug 13, 2024
38a48b8
Black
emma58 Aug 13, 2024
4cff6ff
Setting objective expr and sense in obbt rather than having to check …
emma58 Aug 13, 2024
ea13e94
- Removed the variables argument from the lp_enum scripts since we on…
jlgearh Aug 13, 2024
c54e58b
Merge branch 'aos' of https://github.com/jlgearh/pyomo into aos
jlgearh Aug 13, 2024
c107616
Fixing typos
whart222 Aug 13, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions pyomo/contrib/alternative_solutions/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# alternative_solutions

pyomo.contrib.alternative_solutions is a collection of functions that
that generate a set of alternative (near-)optimal solutions
(AOS). These functions rely on a pyomo solver to search for solutions,
and they iteratively adapt the search process to find a variety of
alternative solutions.

19 changes: 19 additions & 0 deletions pyomo/contrib/alternative_solutions/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# ___________________________________________________________________________
#
# Pyomo: Python Optimization Modeling Objects
# Copyright (c) 2008-2024
# National Technology and Engineering Solutions of Sandia, LLC
# Under the terms of Contract DE-NA0003525 with National Technology and
# Engineering Solutions of Sandia, LLC, the U.S. Government retains certain
# rights in this software.
# This software is distributed under the 3-clause BSD License.
# ___________________________________________________________________________

from pyomo.contrib.alternative_solutions.solution import Solution
whart222 marked this conversation as resolved.
Show resolved Hide resolved
from pyomo.contrib.alternative_solutions.solnpool import gurobi_generate_solutions
from pyomo.contrib.alternative_solutions.balas import enumerate_binary_solutions
from pyomo.contrib.alternative_solutions.obbt import (
obbt_analysis,
obbt_analysis_bounds_and_solutions,
)
from pyomo.contrib.alternative_solutions.lp_enum import enumerate_linear_solutions
289 changes: 289 additions & 0 deletions pyomo/contrib/alternative_solutions/aos_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,289 @@
# ___________________________________________________________________________
#
# Pyomo: Python Optimization Modeling Objects
# Copyright (c) 2008-2024
# National Technology and Engineering Solutions of Sandia, LLC
# Under the terms of Contract DE-NA0003525 with National Technology and
# Engineering Solutions of Sandia, LLC, the U.S. Government retains certain
# rights in this software.
# This software is distributed under the 3-clause BSD License.
# ___________________________________________________________________________

from pyomo.common.dependencies import numpy as numpy, numpy_available

if numpy_available:
import numpy.random
from numpy.linalg import norm

import pyomo.environ as pe
from pyomo.common.modeling import unique_component_name
from pyomo.common.collections import ComponentSet
import pyomo.util.vars_from_expressions as vfe


def get_active_objective(model):
"""
Finds and returns the active objective function for a model. Currently
assume that there is exactly one active objective.
"""

active_objs = []
for o in model.component_data_objects(pe.Objective, active=True):
objs = o.values() if o.is_indexed() else (o,)
for obj in objs:
whart222 marked this conversation as resolved.
Show resolved Hide resolved
active_objs.append(obj)
assert (
len(active_objs) == 1
), "Model has {} active objective functions, exactly one is required.".format(
len(active_objs)
)

return active_objs[0]


def _add_aos_block(model, name="_aos_block"):
"""Adds an alternative optimal solution block with a unique name."""
aos_block = pe.Block()
model.add_component(unique_component_name(model, name), aos_block)
return aos_block


def _add_objective_constraint(
aos_block, objective, objective_value, rel_opt_gap, abs_opt_gap
):
"""
Adds a relative and/or absolute objective function constraint to the
specified block.
"""

assert (
rel_opt_gap is None or rel_opt_gap >= 0.0
), "rel_opt_gap must be None of >= 0.0"
whart222 marked this conversation as resolved.
Show resolved Hide resolved
assert (
abs_opt_gap is None or abs_opt_gap >= 0.0
), "abs_opt_gap must be None of >= 0.0"
whart222 marked this conversation as resolved.
Show resolved Hide resolved

objective_constraints = []

objective_is_min = objective.is_minimizing()
objective_expr = objective.expr

objective_sense = -1
if objective_is_min:
objective_sense = 1

if rel_opt_gap is not None:
objective_cutoff = objective_value + objective_sense * rel_opt_gap * abs(
objective_value
)

if objective_is_min:
aos_block.optimality_tol_rel = pe.Constraint(
expr=objective_expr <= objective_cutoff
)
else:
aos_block.optimality_tol_rel = pe.Constraint(
expr=objective_expr >= objective_cutoff
)
objective_constraints.append(aos_block.optimality_tol_rel)

if abs_opt_gap is not None:
objective_cutoff = objective_value + objective_sense * abs_opt_gap

if objective_is_min:
aos_block.optimality_tol_abs = pe.Constraint(
expr=objective_expr <= objective_cutoff
)
else:
aos_block.optimality_tol_abs = pe.Constraint(
expr=objective_expr >= objective_cutoff
)
objective_constraints.append(aos_block.optimality_tol_abs)

return objective_constraints


if numpy_available:
rng = numpy.random.default_rng(9283749387)
else:
rng = None


def _set_numpy_rng(seed):
global rng
rng = numpy.random.default_rng(seed)


def _get_random_direction(num_dimensions):
"""
Get a unit vector of dimension num_dimensions by sampling from and
normalizing a standard multivariate Gaussian distribution.
"""
iterations = 1000
min_norm = 1e-4
whart222 marked this conversation as resolved.
Show resolved Hide resolved
idx = 0
while idx < iterations:
samples = rng.normal(size=num_dimensions)
samples_norm = norm(samples)
if samples_norm > min_norm:
return samples / samples_norm
idx += 1 # pragma: no cover
whart222 marked this conversation as resolved.
Show resolved Hide resolved
raise Exception( # pragma: no cover
(
"Generated {} sequential Gaussian draws with a norm of "
"less than {}.".format(iterations, min_norm)
)
)


def _filter_model_variables(
variable_set,
var_generator,
include_continuous=True,
include_binary=True,
include_integer=True,
include_fixed=False,
):
"""
Filters variables from a variable generator and adds them to a set.
"""
for var in var_generator:
if var in variable_set or (var.is_fixed() and not include_fixed):
continue
if (
(var.is_continuous() and include_continuous)
or (var.is_binary() and include_binary)
or (var.is_integer() and include_integer)
):
variable_set.add(var)


def get_model_variables(
model,
components="all",
include_continuous=True,
include_binary=True,
include_integer=True,
include_fixed=False,
quiet=True,
):
"""
Gathers and returns all variables or a subset of variables from a Pyomo
model.

Parameters
----------
model : ConcreteModel
A concrete Pyomo model.
components: 'all' or a collection Pyomo components
whart222 marked this conversation as resolved.
Show resolved Hide resolved
The components from which variables should be collected. 'all'
indicates that all variables will be included. Alternatively, a
collection of Pyomo Blocks, Constraints, or Variables (indexed or
non-indexed) from which variables will be gathered can be provided.
If a Block is provided, all variables associated with constraints
in that that block and its sub-blocks will be returned. To exclude
sub-blocks, a tuple element with the format (Block, False) can be
used.
include_continuous : boolean
Boolean indicating that continuous variables should be included.
include_binary : boolean
Boolean indicating that binary variables should be included.
include_integer : boolean
Boolean indicating that integer variables should be included.
include_fixed : boolean
Boolean indicating that fixed variables should be included.
whart222 marked this conversation as resolved.
Show resolved Hide resolved
quiet : boolean
Boolean that is True if all output is suppressed.

Returns
-------
variable_set
A Pyomo ComponentSet containing _GeneralVarData variables.
"""

component_list = (pe.Objective, pe.Constraint)
variable_set = ComponentSet()
if components == "all":
var_generator = vfe.get_vars_from_components(
model, component_list, include_fixed=include_fixed
)
_filter_model_variables(
variable_set,
var_generator,
include_continuous,
include_binary,
include_integer,
include_fixed,
)
else:
for comp in components:
if hasattr(comp, "ctype") and comp.ctype == pe.Block:
blocks = comp.values() if comp.is_indexed() else (comp,)
for item in blocks:
variables = vfe.get_vars_from_components(
item, component_list, include_fixed=include_fixed
)
_filter_model_variables(
variable_set,
variables,
include_continuous,
include_binary,
include_integer,
include_fixed,
)
elif (
isinstance(comp, tuple)
and hasattr(comp[0], "ctype")
and comp[0].ctype == pe.Block
):
block = comp[0]
descend_into = pe.Block if comp[1] else False
blocks = block.values() if block.is_indexed() else (block,)
for item in blocks:
variables = vfe.get_vars_from_components(
item,
component_list,
include_fixed=include_fixed,
descend_into=descend_into,
)
_filter_model_variables(
variable_set,
variables,
include_continuous,
include_binary,
include_integer,
include_fixed,
)
elif hasattr(comp, "ctype") and comp.ctype in component_list:
constraints = comp.values() if comp.is_indexed() else (comp,)
for item in constraints:
variables = pe.expr.identify_variables(
item.expr, include_fixed=include_fixed
)
_filter_model_variables(
variable_set,
variables,
include_continuous,
include_binary,
include_integer,
include_fixed,
)
elif hasattr(comp, "ctype") and comp.ctype == pe.Var:
variables = comp.values() if comp.is_indexed() else (comp,)
_filter_model_variables(
variable_set,
variables,
include_continuous,
include_binary,
include_integer,
include_fixed,
)
emma58 marked this conversation as resolved.
Show resolved Hide resolved
else: # pragma: no cover
if not quiet:
print(
("No variables added for unrecognized component {}.").format(
comp
)
)
whart222 marked this conversation as resolved.
Show resolved Hide resolved

return variable_set
Loading
Loading