Skip to content
Merged
Show file tree
Hide file tree
Changes from 59 commits
Commits
Show all changes
64 commits
Select commit Hold shift + click to select a range
b57ab07
updating solution loader
michaelbynum Aug 9, 2025
710807b
Merge remote-tracking branch 'origin/main' into solver_api
michaelbynum Aug 12, 2025
438b9b5
Merge remote-tracking branch 'michaelbynum/observer_gurobi_refactor' …
michaelbynum Aug 12, 2025
ac42345
updating solution loader
michaelbynum Aug 12, 2025
70ca6e7
updating solution loader
michaelbynum Aug 13, 2025
5ec0421
Merge branch 'observer_gurobi_refactor' into solver_api
michaelbynum Aug 13, 2025
2885f42
update solution loader
michaelbynum Aug 13, 2025
c62a7b3
Merge branch 'observer_gurobi_refactor' into solver_api
michaelbynum Aug 14, 2025
a4e2b81
run black
michaelbynum Aug 14, 2025
1750fc5
merge in observer_gurobi_refactor
michaelbynum Aug 16, 2025
413d63d
Merge branch 'observer_gurobi_refactor' into solver_api
michaelbynum Aug 16, 2025
0bbdd70
Merge branch 'observer_gurobi_refactor' into solver_api
michaelbynum Aug 18, 2025
a96b518
merge observer_gurobi_refactor into solver_api
michaelbynum Oct 5, 2025
ce4e77c
merge main
michaelbynum Dec 12, 2025
c3f2d48
solution loader updates
michaelbynum Dec 12, 2025
ba4b29c
run black
michaelbynum Dec 12, 2025
0792800
fix typo
michaelbynum Dec 18, 2025
f3370f3
Merge branch 'observer_gurobi_refactor' into solver_api
michaelbynum Jan 29, 2026
3a44486
merge main
michaelbynum Feb 11, 2026
a125456
run black
michaelbynum Feb 11, 2026
b5d16d9
fix tests
michaelbynum Feb 11, 2026
9473f29
run black
michaelbynum Feb 12, 2026
7557036
Merge branch 'main' into solver_api
jsiirola Feb 13, 2026
a710be3
Merge remote-tracking branch 'origin/main' into solver_api
michaelbynum Mar 17, 2026
60d4160
better handling of suffixes for the sol file solution loader
michaelbynum Mar 17, 2026
0911727
run black
michaelbynum Mar 17, 2026
0338bcc
update gams solution loader
michaelbynum Mar 17, 2026
5ed9722
Merge remote-tracking branch 'origin/main' into solver_api
michaelbynum Mar 19, 2026
c9b042b
typo
michaelbynum Mar 19, 2026
2874fe9
rename method
michaelbynum Mar 19, 2026
26ef10d
update gams solution loader
michaelbynum Mar 19, 2026
845e586
update tests
michaelbynum Mar 19, 2026
3c4716c
update tests
michaelbynum Mar 23, 2026
12cfd2f
run black
michaelbynum Mar 23, 2026
c5af43d
update highs solution loader
michaelbynum Mar 23, 2026
4f44d3b
run black
michaelbynum Mar 23, 2026
8f1e2b0
Merge remote-tracking branch 'origin/main' into solver_api
michaelbynum Mar 23, 2026
1a26eae
update knitro solution loader
michaelbynum Mar 23, 2026
9819a04
Merge branch 'main' into solver_api
michaelbynum Mar 26, 2026
91f24a0
Merge branch 'main' into solver_api
blnicho Apr 2, 2026
5a71a82
Merge remote-tracking branch 'origin/main' into solver_api
michaelbynum Apr 15, 2026
1cf3b10
Merge remote-tracking branch 'origin/main' into solver_api
michaelbynum Apr 17, 2026
2fb8126
solution loader updates
michaelbynum Apr 17, 2026
4da0b1e
Merge branch 'main' into solver_api
jsiirola Apr 22, 2026
4bd9abd
NFC: apply black
jsiirola Apr 22, 2026
dbb3b3b
Merge branch 'main' into solver_api
jsiirola Apr 22, 2026
d98a821
Update type hints to Python 3.10+
jsiirola Apr 22, 2026
d3b6825
Update tests now that suffix warnings are not fatal
jsiirola Apr 22, 2026
eeb167c
Rework suffix loader to ensure import suffixes are located and cleared
jsiirola Apr 22, 2026
ef4800a
Remove solution_id from SolutionLoader API; create a View context man…
jsiirola Apr 23, 2026
20e70db
Rename SolutionLoaderBase -> SolutionLoader
jsiirola Apr 23, 2026
a370db6
NFC: update documentation
jsiirola Apr 23, 2026
f4ba61b
NFC: apply black
jsiirola Apr 23, 2026
908c620
Resolve bad git merge
jsiirola Apr 23, 2026
d034dc4
NFC: update docs
jsiirola Apr 23, 2026
885fe2a
Expand SolutionLoaderView testing
jsiirola Apr 23, 2026
dd5f2f4
NFC: typo
jsiirola Apr 23, 2026
068f876
Fix calculation of ASLSolFileSolutionLoader.get_number_of_solutions
jsiirola Apr 23, 2026
018c93b
Additional testing
jsiirola Apr 23, 2026
622239d
Test copy_docstrings; update docs
jsiirola Apr 23, 2026
50fe51c
Remove unused data structure, correct type hint
jsiirola Apr 23, 2026
bdb9517
Improve ALS suffix loader testing
jsiirola Apr 23, 2026
d35d539
SolutionLoader: raise NotImplementedError instead of returning NotImp…
jsiirola Apr 23, 2026
cafea40
remove duplicate method
michaelbynum Apr 26, 2026
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
53 changes: 53 additions & 0 deletions pyomo/common/docutils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# ____________________________________________________________________________________
#
# Pyomo: Python Optimization Modeling Objects
# Copyright (c) 2008-2026 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.
# ____________________________________________________________________________________

import inspect


def copy_docstrings(reference_class: type, methods: list[str] | None = None):
"""Copy docstrings from a reference class to this class.

Note that only docstrings for methods, generators, and functions are
copied.

Parameters
----------
reference_class: type
The source class to copy docstrings from

methods: list[str] | None
The list of attributes from the `reference_class` to copy
docstrings from. If empty or None, then all method docstrings
are checked / copied.

"""
if not methods:
method_list = dir(reference_class)
else:
method_list = list(methods)

def wrapper(cls):
for method_name in method_list:
method = getattr(reference_class, method_name)
if not inspect.isfunction(method) and not inspect.ismethod(method):
# Skip attributes that are not functions / generators / methods
continue
old_doc = getattr(method, '__doc__', None)
if not old_doc:
# Skip methods where there isn't a docstring to copy
continue
new_method = getattr(cls, method_name, None)
if new_method is None or getattr(new_method, '__doc__', None):
# Skip methods that don't exist, or ones that have
# docstrings defined
continue
new_method.__doc__ = old_doc
return cls

return wrapper
49 changes: 22 additions & 27 deletions pyomo/contrib/solver/common/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
# software. This software is distributed under the 3-clause BSD License.
# ____________________________________________________________________________________

from typing import Sequence, Dict, Optional, Mapping, List, Tuple
from typing import Sequence, Mapping
import os

from pyomo.core.base.constraint import ConstraintData
Expand Down Expand Up @@ -163,7 +163,7 @@ def available(self) -> Availability:
f"Derived class {self.__class__.__name__} failed to implement required method 'available'."
)

def version(self) -> Tuple:
def version(self) -> tuple:
"""Return the solver version found on the system.

Returns
Expand Down Expand Up @@ -231,7 +231,7 @@ def is_persistent(self) -> bool:
"""
return True

def _load_vars(self, vars_to_load: Optional[Sequence[VarData]] = None) -> None:
def _load_vars(self, vars_to_load: Sequence[VarData] | None = None) -> None:
"""
Load the solution of the primal variables into the value attribute of the variables.

Expand All @@ -246,14 +246,14 @@ def _load_vars(self, vars_to_load: Optional[Sequence[VarData]] = None) -> None:
StaleFlagManager.mark_all_as_stale(delayed=True)

def _get_primals(
self, vars_to_load: Optional[Sequence[VarData]] = None
self, vars_to_load: Sequence[VarData] | None = None
) -> Mapping[VarData, float]:
"""
Get mapping of variables to primals.

Parameters
----------
vars_to_load : Optional[Sequence[VarData]], optional
vars_to_load : Sequence[VarData] | None
Which vars to be populated into the map. The default is None.

Returns
Expand All @@ -266,14 +266,14 @@ def _get_primals(
)

def _get_duals(
self, cons_to_load: Optional[Sequence[ConstraintData]] = None
) -> Dict[ConstraintData, float]:
self, cons_to_load: Sequence[ConstraintData] | None = None
) -> dict[ConstraintData, float]:
"""
Declare sign convention in docstring here.

Parameters
----------
cons_to_load: list
cons_to_load: Sequence[VarData] | None
A list of the constraints whose duals should be loaded. If cons_to_load
is None, then the duals for all constraints will be loaded.

Expand All @@ -285,7 +285,7 @@ def _get_duals(
raise NotImplementedError(f'{type(self)} does not support the get_duals method')

def _get_reduced_costs(
self, vars_to_load: Optional[Sequence[VarData]] = None
self, vars_to_load: Sequence[VarData] | None = None
) -> Mapping[VarData, float]:
"""
Parameters
Expand Down Expand Up @@ -319,7 +319,7 @@ def set_objective(self, obj: ObjectiveData):
f"Derived class {self.__class__.__name__} failed to implement required method 'set_objective'."
)

def add_constraints(self, cons: List[ConstraintData]):
def add_constraints(self, cons: list[ConstraintData]):
"""
Add constraints to the model.
"""
Expand All @@ -335,7 +335,7 @@ def add_block(self, block: BlockData):
f"Derived class {self.__class__.__name__} failed to implement required method 'add_block'."
)

def remove_constraints(self, cons: List[ConstraintData]):
def remove_constraints(self, cons: list[ConstraintData]):
"""
Remove constraints from the model.
"""
Expand All @@ -351,7 +351,7 @@ def remove_block(self, block: BlockData):
f"Derived class {self.__class__.__name__} failed to implement required method 'remove_block'."
)

def update_variables(self, variables: List[VarData]):
def update_variables(self, variables: list[VarData]):
"""
Update variables on the model.
"""
Expand Down Expand Up @@ -562,14 +562,9 @@ def _solution_handler(
legacy_results._smap_id = None

if load_solutions:
if hasattr(model, 'dual') and model.dual.import_enabled():
for con, val in results.solution_loader.get_duals().items():
model.dual[con] = val
if hasattr(model, 'rc') and model.rc.import_enabled():
for var, val in results.solution_loader.get_reduced_costs().items():
model.rc[var] = val
results.solution_loader.load_import_suffixes()
elif results.incumbent_objective is not None:
for var, val in results.solution_loader.get_primals().items():
for var, val in results.solution_loader.get_vars().items():
legacy_soln.variable[symbol_map.getSymbol(var)] = {'Value': val}
if hasattr(model, 'dual') and model.dual.import_enabled():
for con, val in results.solution_loader.get_duals().items():
Expand All @@ -595,19 +590,19 @@ def solve(
model: BlockData,
tee: bool = False,
load_solutions: bool = True,
logfile: Optional[str] = None,
solnfile: Optional[str] = None,
timelimit: Optional[float] = None,
logfile: str | None = None,
solnfile: str | None = None,
timelimit: float | None = None,
report_timing: bool = False,
solver_io: Optional[str] = None,
suffixes: Optional[Sequence] = None,
options: Optional[Dict] = None,
solver_io: str | None = None,
suffixes: Sequence | None = None,
options: dict | None = None,
keepfiles: bool = False,
symbolic_solver_labels: bool = False,
# These are for forward-compatibility
raise_exception_on_nonoptimal_result: bool = False,
solver_options: Optional[Dict] = None,
writer_config: Optional[Dict] = None,
solver_options: dict | None = None,
writer_config: dict | None = None,
):
"""
Solve method: maps new solve method style to backwards compatible version.
Expand Down
41 changes: 20 additions & 21 deletions pyomo/contrib/solver/common/persistent.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
import abc
import datetime
import time
from typing import List

from pyomo.core.base.constraint import ConstraintData, Constraint
from pyomo.core.base.sos import SOSConstraintData, SOSConstraint
Expand Down Expand Up @@ -73,10 +72,10 @@ def set_instance(self, model):
self.set_objective(None)

@abc.abstractmethod
def _add_variables(self, variables: List[VarData]):
def _add_variables(self, variables: list[VarData]):
pass

def add_variables(self, variables: List[VarData]):
def add_variables(self, variables: list[VarData]):
for v in variables:
if id(v) in self._referenced_variables:
raise ValueError(f'Variable {v.name} has already been added')
Expand All @@ -92,27 +91,27 @@ def add_variables(self, variables: List[VarData]):
self._add_variables(variables)

@abc.abstractmethod
def _add_parameters(self, params: List[ParamData]):
def _add_parameters(self, params: list[ParamData]):
pass

def add_parameters(self, params: List[ParamData]):
def add_parameters(self, params: list[ParamData]):
for p in params:
self._params[id(p)] = p
self._add_parameters(params)

@abc.abstractmethod
def _add_constraints(self, cons: List[ConstraintData]):
def _add_constraints(self, cons: list[ConstraintData]):
pass

def _check_for_new_vars(self, variables: List[VarData]):
def _check_for_new_vars(self, variables: list[VarData]):
new_vars = {}
for v in variables:
v_id = id(v)
if v_id not in self._referenced_variables:
new_vars[v_id] = v
self.add_variables(list(new_vars.values()))

def _check_to_remove_vars(self, variables: List[VarData]):
def _check_to_remove_vars(self, variables: list[VarData]):
vars_to_remove = {}
for v in variables:
v_id = id(v)
Expand All @@ -121,7 +120,7 @@ def _check_to_remove_vars(self, variables: List[VarData]):
vars_to_remove[v_id] = v
self.remove_variables(list(vars_to_remove.values()))

def add_constraints(self, cons: List[ConstraintData]):
def add_constraints(self, cons: list[ConstraintData]):
all_fixed_vars = {}
for con in cons:
if con in self._named_expressions:
Expand All @@ -145,10 +144,10 @@ def add_constraints(self, cons: List[ConstraintData]):
v.fix()

@abc.abstractmethod
def _add_sos_constraints(self, cons: List[SOSConstraintData]):
def _add_sos_constraints(self, cons: list[SOSConstraintData]):
pass

def add_sos_constraints(self, cons: List[SOSConstraintData]):
def add_sos_constraints(self, cons: list[SOSConstraintData]):
for con in cons:
if con in self._vars_referenced_by_con:
raise ValueError(f'Constraint {con.name} has already been added')
Expand Down Expand Up @@ -222,10 +221,10 @@ def add_block(self, block):
self.set_objective(obj)

@abc.abstractmethod
def _remove_constraints(self, cons: List[ConstraintData]):
def _remove_constraints(self, cons: list[ConstraintData]):
pass

def remove_constraints(self, cons: List[ConstraintData]):
def remove_constraints(self, cons: list[ConstraintData]):
self._remove_constraints(cons)
for con in cons:
if con not in self._named_expressions:
Expand All @@ -241,10 +240,10 @@ def remove_constraints(self, cons: List[ConstraintData]):
del self._vars_referenced_by_con[con]

@abc.abstractmethod
def _remove_sos_constraints(self, cons: List[SOSConstraintData]):
def _remove_sos_constraints(self, cons: list[SOSConstraintData]):
pass

def remove_sos_constraints(self, cons: List[SOSConstraintData]):
def remove_sos_constraints(self, cons: list[SOSConstraintData]):
self._remove_sos_constraints(cons)
for con in cons:
if con not in self._vars_referenced_by_con:
Expand All @@ -259,10 +258,10 @@ def remove_sos_constraints(self, cons: List[SOSConstraintData]):
del self._vars_referenced_by_con[con]

@abc.abstractmethod
def _remove_variables(self, variables: List[VarData]):
def _remove_variables(self, variables: list[VarData]):
pass

def remove_variables(self, variables: List[VarData]):
def remove_variables(self, variables: list[VarData]):
self._remove_variables(variables)
for v in variables:
v_id = id(v)
Expand All @@ -279,10 +278,10 @@ def remove_variables(self, variables: List[VarData]):
del self._vars[v_id]

@abc.abstractmethod
def _remove_parameters(self, params: List[ParamData]):
def _remove_parameters(self, params: list[ParamData]):
pass

def remove_parameters(self, params: List[ParamData]):
def remove_parameters(self, params: list[ParamData]):
self._remove_parameters(params)
for p in params:
del self._params[id(p)]
Expand Down Expand Up @@ -314,10 +313,10 @@ def remove_block(self, block):
)

@abc.abstractmethod
def _update_variables(self, variables: List[VarData]):
def _update_variables(self, variables: list[VarData]):
pass

def update_variables(self, variables: List[VarData]):
def update_variables(self, variables: list[VarData]):
for v in variables:
self._vars[id(v)] = (
v,
Expand Down
Loading