Source code for pyomo.common.enums

# ____________________________________________________________________________________
#
# 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.
# ____________________________________________________________________________________

"""This module provides standard :py:class:`enum.Enum` definitions used in
Pyomo, along with additional utilities for working with custom Enums

Utilities:

.. autosummary::

   ExtendedEnumType
   NamedIntEnum

Standard Enums:

.. autosummary::

   ObjectiveSense
   SolverAPIVersion

"""

import enum
import itertools
import re
import sys

# Aliases for enum members so that clients can use
# :py:mod:`pyomo.common.enums` like they would :py:mod:`enum`.
from enum import Enum, Flag

if sys.version_info[:2] < (3, 11):
    EnumType = enum.EnumMeta
else:
    EnumType = enum.EnumType
if sys.version_info[:2] < (3, 13):
    import inspect

    # prior to 3.13 the int.{to,from}_bytes docstrings had LaTeX-like
    # "`..'" quotations, which Sphinx can't parse correctly.
    def _fix_doc(ref):
        def _rewrite(func):
            func.__doc__ = re.sub(r"`(\S+)'", r"`\1`", ref.__doc__)
            return func

        return _rewrite

    class _int_bytes_doc_wrapper:
        """Class to wrap to_bytes/from_bytes and rewrite the docstring"""

        @_fix_doc(int.to_bytes)
        def to_bytes(self, /, length=1, byteorder='big', *, signed=False):
            return super().to_bytes(length=length, byteorder=byteorder, signed=signed)

        # Note: we need to use a decorator to set the __doc__ *before*
        # the @classmethod (which makes __doc__ read-only).
        @classmethod
        @_fix_doc(int.from_bytes)
        def from_bytes(cls, bytes, byteorder='big', *, signed=False):
            return super(_int_bytes_doc_wrapper, cls).from_bytes(
                bytes, byteorder=byteorder, signed=signed
            )

    def _doc_updater(base):
        return (
            inspect.cleandoc(
                f"""A compatibility wrapper around :class:`enum.{base.__name__}`

                This wrapper class updates the :meth:`to_bytes` and
                :meth:`from_bytes` docstrings in Python <= 3.12 to
                suppress warnings generated by Sphinx.

                .. rubric:: {base.__name__}

            """
            )
            + "\n\n"
            # There are environments where IntEnum.__doc__ is None (see #3710)
            + inspect.cleandoc(getattr(base, "__doc__", "") or "")
        )

    class IntEnum(_int_bytes_doc_wrapper, enum.IntEnum):
        __doc__ = _doc_updater(enum.IntEnum)

    class IntFlag(_int_bytes_doc_wrapper, enum.IntFlag):
        __doc__ = _doc_updater(enum.IntFlag)

else:
    IntEnum = enum.IntEnum
    IntFlag = enum.IntFlag


[docs] class ExtendedEnumType(EnumType): """Metaclass for creating an :py:class:`enum.Enum` that extends another Enum In general, :py:class:`enum.Enum` classes are not extensible: that is, they are frozen when defined and cannot be the base class of another Enum. This Metaclass provides a workaround for creating a new Enum that extends an existing enum. Members in the base Enum are all present as members on the extended enum. Example ------- .. testcode:: :hide: import enum from pyomo.common.enums import ExtendedEnumType .. testcode:: class ObjectiveSense(enum.IntEnum): minimize = 1 maximize = -1 class ProblemSense(enum.IntEnum, metaclass=ExtendedEnumType): __base_enum__ = ObjectiveSense unknown = 0 .. doctest:: >>> list(ProblemSense) [<ProblemSense.unknown: 0>, <ObjectiveSense.minimize: 1>, <ObjectiveSense.maximize: -1>] >>> ProblemSense.unknown <ProblemSense.unknown: 0> >>> ProblemSense.maximize <ObjectiveSense.maximize: -1> >>> ProblemSense(0) <ProblemSense.unknown: 0> >>> ProblemSense(1) <ObjectiveSense.minimize: 1> >>> ProblemSense('unknown') <ProblemSense.unknown: 0> >>> ProblemSense('maximize') <ObjectiveSense.maximize: -1> >>> hasattr(ProblemSense, 'minimize') True >>> ProblemSense.minimize is ObjectiveSense.minimize True >>> ProblemSense.minimize in ProblemSense True """ def __getattr__(cls, attr): try: return getattr(cls.__base_enum__, attr) except: return super().__getattr__(attr) def __iter__(cls): # The members of this Enum are the base enum members joined with # the local members return itertools.chain(super().__iter__(), cls.__base_enum__.__iter__()) def __contains__(cls, member): # This enum "contains" both its local members and the members in # the __base_enum__ (necessary for good auto-enum[sphinx] docs) return super().__contains__(member) or member in cls.__base_enum__ def __dir__(self): _dir = set(super().__dir__()) _dir.update(e.name for e in self) return _dir def __instancecheck__(cls, instance): if cls.__subclasscheck__(type(instance)): return True # Also pretend that members of the extended enum are subclasses # of the __base_enum__. This is needed to circumvent error # checking in enum.__new__ (e.g., for `ProblemSense('minimize')`) return cls.__base_enum__.__subclasscheck__(type(instance)) def _missing_(cls, value): # Support attribute lookup by value or name for attr in ('value', 'name'): for member in cls: if getattr(member, attr) == value: return member return None def __new__(metacls, cls, bases, classdict, **kwds): # Support lookup by name - but only if the new Enum doesn't # specify its own implementation of _missing_ if '_missing_' not in classdict: classdict['_missing_'] = classmethod(ExtendedEnumType._missing_) return super().__new__(metacls, cls, bases, classdict, **kwds)
[docs] class NamedIntEnum(IntEnum): """An extended version of :py:class:`~pyomo.common.enums.IntEnum` that supports creating members by name as well as value. """ @classmethod def _missing_(cls, value): for member in cls: if member.name == value: return member return None
[docs] class ObjectiveSense(NamedIntEnum): """Flag indicating if an objective is minimizing (1) or maximizing (-1). While the numeric values are arbitrary, there are parts of Pyomo that rely on this particular choice of value. These values are also consistent with some solvers (notably Gurobi). """ minimize = 1 maximize = -1 # Overloading __str__ is needed to match the behavior of the old # pyutilib.enum class (removed June 2020). There are spots in the # code base that expect the string representation for items in the # enum to not include the class name. New uses of enum shouldn't # need to do this. def __str__(self): return self.name
[docs] class SolverAPIVersion(NamedIntEnum): """ Enum identifying Pyomo solver API version The numeric values are intentionally a bit odd because APPSI came between the official V1 and V2. We still want it to be chronologically in order without sacrificing the human-logic of v1 vs. v2. """ #: Original Coopr/Pyomo solver interface V1 = 10 #: Automatic Persistent Pyomo Solver Interface (experimental) APPSI = 15 #: Redesigned solver interface (circa 2024) V2 = 20
minimize = ObjectiveSense.minimize maximize = ObjectiveSense.maximize