Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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):
"""Decorator to copy docstrings from a reference class to the decorated 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 methods 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
77 changes: 77 additions & 0 deletions pyomo/common/tests/test_docutils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# ____________________________________________________________________________________
#
# 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 pyomo.common.unittest as unittest

from pyomo.common.docutils import copy_docstrings


class TestDocutils(unittest.TestCase):
def test_copy_docstrings(self):
class Base:
#: This isn't really a docstring
attr = '1'

def method0(self):
"Docstring from Base.method0"

def method1(self):
"Docstring from Base.method1"

def method2(self):
"Docstring from Base.method2"

def method3(self):
pass

@copy_docstrings(Base)
class Test1:
attr = 2

def method1(self):
"Docstring from Test1.method1"

def method2(self):
pass

def method3(self):
"Docstring from Test1.method3"

def method4(self):
pass

self.assertFalse(hasattr(Test1, 'method0'))
self.assertEqual(Test1.attr.__doc__, int.__doc__)
self.assertEqual(Test1.method1.__doc__, "Docstring from Test1.method1")
self.assertEqual(Test1.method2.__doc__, "Docstring from Base.method2")
self.assertEqual(Test1.method3.__doc__, "Docstring from Test1.method3")
self.assertEqual(Test1.method4.__doc__, None)

@copy_docstrings(Base, ['method2', 'method3'])
class Test2:
attr = 2.0

def method1(self):
pass

def method2(self):
pass

def method3(self):
"Docstring from Test2.method3"

def method4(self):
pass

self.assertFalse(hasattr(Test2, 'method0'))
self.assertEqual(Test2.attr.__doc__, float.__doc__)
self.assertEqual(Test2.method1.__doc__, None)
self.assertEqual(Test2.method2.__doc__, "Docstring from Base.method2")
self.assertEqual(Test2.method3.__doc__, "Docstring from Test2.method3")
self.assertEqual(Test2.method4.__doc__, None)
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
Loading
Loading