# ___________________________________________________________________________
#
# Pyomo: Python Optimization Modeling Objects
# Copyright (c) 2008-2025
# 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 enum
from io import StringIO
from math import inf
from pyomo.common.collections import Bunch, Sequence, Mapping
[docs]
class ScalarType(str, enum.Enum):
int = 'int'
time = 'time'
string = 'string'
float = 'float'
enum = 'enum'
undefined = 'undefined'
# 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.value
default_print_options = Bunch(schema=False, ignore_time=False)
strict = False
[docs]
class UndefinedData(object):
singleton = {}
def __new__(cls, name='undefined'):
if name not in UndefinedData.singleton:
UndefinedData.singleton[name] = super().__new__(cls)
UndefinedData.singleton[name].name = name
return UndefinedData.singleton[name]
def __deepcopy__(self, memo):
# Prevent deepcopy from duplicating this object
return self
def __reduce__(self):
return self.__class__, (self.name,)
def __str__(self):
return f"<{self.name}>"
undefined = UndefinedData('undefined')
ignore = UndefinedData('ignore')
[docs]
class ScalarData(object):
[docs]
def __init__(
self,
value=undefined,
description=None,
units=None,
scalar_description=None,
type=ScalarType.undefined,
required=False,
):
self.value = value
self.description = description
self.units = units
self.scalar_description = scalar_description
self.scalar_type = type
self._required = required
self._active = False
def __eq__(self, other):
return self.__dict__ == other.__dict__
def get_value(self):
if isinstance(self.value, enum.Enum):
value = str(self.value)
elif type(self.value) is UndefinedData:
value = '<undefined>'
else:
value = self.value
return value
def _repn_(self, option):
if not option.schema and not self._required and self.value is undefined:
return ignore
if option.ignore_time and str(self.scalar_type) == str(ScalarType.time):
return ignore
value = self.get_value()
if option.schema:
tmp = {'value': value}
if not self.description is None:
tmp['description'] = self.description
if not self.units is None:
tmp['units'] = self.units
if not self.scalar_description is None:
tmp['description'] = self.scalar_description
if not self.scalar_type is ScalarType.undefined:
tmp['type'] = self.scalar_type
return tmp
if not (self.description is None and self.units is None):
tmp = {'value': value}
if not self.description is None:
tmp['description'] = self.description
if not self.units is None:
tmp['units'] = self.units
return tmp
return value
def pprint(self, ostream, option, prefix="", repn=None):
if not option.schema and not self._required and self.value is undefined:
return ignore
if option.ignore_time and str(self.scalar_type) == str(ScalarType.time):
return ignore
value = self.yaml_fix(self.get_value())
if value == inf:
value = '.inf'
elif value == -inf:
value = '-.inf'
if not option.schema and self.description is None and self.units is None:
ostream.write(str(value) + '\n')
else:
ostream.write("\n")
ostream.write(prefix + 'Value: ' + str(value) + '\n')
if not option.schema:
if not self.description is None:
ostream.write(
prefix
+ 'Description: '
+ self.yaml_fix(self.description)
+ '\n'
)
if not self.units is None:
ostream.write(prefix + 'Units: ' + str(self.units) + '\n')
else:
if not self.scalar_description is None:
ostream.write(
prefix
+ 'Description: '
+ self.yaml_fix(self.scalar_description)
+ '\n'
)
if not self.scalar_type is ScalarType.undefined:
ostream.write(
prefix + 'Type: ' + self.yaml_fix(self.scalar_type) + '\n'
)
def yaml_fix(self, val):
if not isinstance(val, str):
return val
return val.replace(':', '\\x3a')
def load(self, repn):
if type(repn) is dict:
for key, val in repn.items():
setattr(self, key, val)
else:
self.value = repn
#
# This class manages a list of MapContainer objects.
#
[docs]
class ListContainer(object):
[docs]
def __init__(self, cls):
self._cls = cls
self._list = []
self._active = True
self._required = False
def __len__(self):
if '_list' in self.__dict__:
return len(self._list)
return 0
def __getitem__(self, i):
return self._list[i]
def __eq__(self, other):
return self.__dict__ == other.__dict__
def clear(self):
self._list = []
def delete(self, i):
del self._list[i]
def __call__(self, i=0):
return self._list[i]
def __getattr__(self, name):
if name[0] == "_":
super().__getattr__(name)
if len(self) == 0:
self.add()
return getattr(self._list[0], name)
def __setattr__(self, name, val):
if name[0] == "_":
return super().__setattr__(name, val)
if len(self) == 0:
self.add()
setattr(self._list[0], name, val)
def insert(self, obj):
self._active = True
self._list.append(obj)
def add(self):
self._active = True
obj = self._cls()
self._list.append(obj)
return obj
def _repn_(self, option):
if not option.schema and not self._active and not self._required:
return ignore
if option.schema and len(self) == 0:
self.add()
tmp = []
for item in self._list:
tmp.append(item._repn_(option))
return tmp
def pprint(self, ostream, option, prefix="", repn=None):
if not option.schema and not self._active and not self._required:
return ignore
ostream.write("\n")
i = 0
for i in range(len(self._list)):
item = self._list[i]
ostream.write(prefix + '- ')
item.pprint(
ostream, option, from_list=True, prefix=prefix + " ", repn=repn[i]
)
def load(self, repn):
for data in repn:
item = self.add()
item.load(data)
def __str__(self):
ostream = StringIO()
option = default_print_options
self.pprint(ostream, option, repn=self._repn_(option))
return ostream.getvalue()
#
# This class manages use-defined attributes in
# a dictionary. Attributes are translated into
# a string where '_' is replaced by ' ', and where the
# first letter is capitalized.
#
[docs]
class MapContainer(dict):
[docs]
def __init__(self, ordered=False):
super().__init__()
self._active = True
self._required = False
self._option = default_print_options
def __eq__(self, other):
# We need to check both our __dict__ (local attributes) and the
# underlying dict data (which doesn't show up in the __dict__).
# So we will use the base __eq__ in addition to checking
# __dict__.
return super().__eq__(other) and self.__dict__ == other.__dict__
def __getattr__(self, name):
try:
self._active = True
return self[self._convert(name)]
except Exception:
pass
raise AttributeError(
"Unknown attribute `"
+ str(name)
+ "' for object with type "
+ str(type(self))
)
def __setattr__(self, name, val):
if name[0] == "_":
return super().__setattr__(name, val)
self._active = True
tmp = self._convert(name)
if tmp not in self:
if strict:
raise AttributeError(
"Unknown attribute `"
+ str(name)
+ "' for object with type "
+ str(type(self))
)
self.declare(tmp)
self._set_value(tmp, val)
def __setitem__(self, name, val):
self._active = True
tmp = self._convert(name)
if tmp not in self:
if strict:
raise AttributeError(
"Unknown attribute `"
+ str(name)
+ "' for object with type "
+ str(type(self))
)
self.declare(tmp)
self._set_value(tmp, val)
def _set_value(self, name, val):
if isinstance(val, (ListContainer, MapContainer)):
super().__setitem__(name, val)
elif isinstance(val, ScalarData):
data = super().__getitem__(name)
data.value = val.value
data._active = val._active
data._required = val._required
data.scalar_type = val.scalar_type
else:
data = super().__getitem__(name)
data.value = val
data._active = True
def __getitem__(self, name):
tmp = self._convert(name)
if tmp not in self:
raise AttributeError(
"Unknown attribute `"
+ str(name)
+ "' for object with type "
+ str(type(self))
)
item = super().__getitem__(tmp)
if isinstance(item, (ListContainer, MapContainer)):
return item
return item.value
def declare(self, name, **kwds):
if name in self or type(name) is int:
return
data = kwds.get('value', None)
if isinstance(data, (MapContainer, ListContainer)):
if 'active' in kwds:
data._active = kwds['active']
if 'required' in kwds and kwds['required'] is True:
data._required = True
super().__setitem__(self._convert(name), data)
else:
data = ScalarData(**kwds)
if 'required' in kwds and kwds['required'] is True:
data._required = True
#
# This logic would setup a '_default' value, which copies the
# initial value of an attribute. I don't think we need this,
# but for now I'm going to leave this logic in the code.
#
# if 'value' in kwds:
# data._default = kwds['value']
super().__setitem__(self._convert(name), data)
def _repn_(self, option):
if not option.schema and not self._active and not self._required:
return ignore
tmp = {}
for key, val in self.items():
rep = val._repn_(option)
if not rep == ignore:
tmp[key] = rep
return tmp
def _convert(self, name):
if not isinstance(name, str):
return name
tmp = name.replace('_', ' ')
return tmp[0].upper() + tmp[1:]
def __repr__(self):
return str(self._repn_(self._option))
def __str__(self):
ostream = StringIO()
self.pprint(ostream, self._option, repn=self._repn_(self._option))
return ostream.getvalue()
def pprint(self, ostream, option, from_list=False, prefix="", repn=None):
if from_list:
_prefix = ""
else:
_prefix = prefix
ostream.write('\n')
for key, item in self.items():
if not key in repn:
continue
ostream.write(_prefix + key + ": ")
_prefix = prefix
if isinstance(item, ListContainer):
item.pprint(ostream, option, prefix=_prefix, repn=repn[key])
else:
item.pprint(ostream, option, prefix=_prefix + " ", repn=repn[key])
def load(self, repn):
for key, val in repn.items():
tmp = self._convert(key)
if tmp not in self:
self.declare(tmp)
item = super().__getitem__(tmp)
item._active = True
item.load(val)
# Register these as sequence / mapping types (so things like
# assertStructuredAlmostEqual will process them correctly)
Sequence.register(ListContainer)
Mapping.register(MapContainer)