1# ___________________________________________________________________________ 2# 3# Pyomo: Python Optimization Modeling Objects 4# Copyright 2017 National Technology and Engineering Solutions of Sandia, LLC 5# Under the terms of Contract DE-NA0003525 with National Technology and 6# Engineering Solutions of Sandia, LLC, the U.S. Government retains certain 7# rights in this software. 8# This software is distributed under the 3-clause BSD License. 9# ___________________________________________________________________________ 10 11__all__ = ('Suffix', 12 'active_export_suffix_generator', 13 'active_import_suffix_generator') 14 15import logging 16 17from pyomo.common.collections import ComponentMap 18from pyomo.common.log import is_debug_set 19from pyomo.common.timing import ConstructionTimer 20from pyomo.core.base.component import ActiveComponent, ModelComponentFactory 21 22from pyomo.common.deprecation import deprecated 23 24logger = logging.getLogger('pyomo.core') 25 26# A list of convenient suffix generators, including: 27# - active_export_suffix_generator 28# **(used by problem writers) 29# - export_suffix_generator 30# - active_import_suffix_generator 31# **(used by OptSolver and PyomoModel._load_solution) 32# - import_suffix_generator 33# - active_local_suffix_generator 34# - local_suffix_generator 35# - active_suffix_generator 36# - suffix_generator 37 38 39def active_export_suffix_generator(a_block, datatype=False): 40 if (datatype is False): 41 for name, suffix in a_block.component_map(Suffix, active=True).items(): 42 if suffix.export_enabled() is True: 43 yield name, suffix 44 else: 45 for name, suffix in a_block.component_map(Suffix, active=True).items(): 46 if (suffix.export_enabled() is True) and \ 47 (suffix.get_datatype() is datatype): 48 yield name, suffix 49 50 51def export_suffix_generator(a_block, datatype=False): 52 if (datatype is False): 53 for name, suffix in a_block.component_map(Suffix).items(): 54 if suffix.export_enabled() is True: 55 yield name, suffix 56 else: 57 for name, suffix in a_block.component_map(Suffix).items(): 58 if (suffix.export_enabled() is True) and \ 59 (suffix.get_datatype() is datatype): 60 yield name, suffix 61 62 63def active_import_suffix_generator(a_block, datatype=False): 64 if (datatype is False): 65 for name, suffix in a_block.component_map(Suffix, active=True).items(): 66 if suffix.import_enabled() is True: 67 yield name, suffix 68 else: 69 for name, suffix in a_block.component_map(Suffix, active=True).items(): 70 if (suffix.import_enabled() is True) and \ 71 (suffix.get_datatype() is datatype): 72 yield name, suffix 73 74 75def import_suffix_generator(a_block, datatype=False): 76 if (datatype is False): 77 for name, suffix in a_block.component_map(Suffix).items(): 78 if suffix.import_enabled() is True: 79 yield name, suffix 80 else: 81 for name, suffix in a_block.component_map(Suffix).items(): 82 if (suffix.import_enabled() is True) and \ 83 (suffix.get_datatype() is datatype): 84 yield name, suffix 85 86 87def active_local_suffix_generator(a_block, datatype=False): 88 if (datatype is False): 89 for name, suffix in a_block.component_map(Suffix, active=True).items(): 90 if suffix.get_direction() is Suffix.LOCAL: 91 yield name, suffix 92 else: 93 for name, suffix in a_block.component_map(Suffix, active=True).items(): 94 if (suffix.get_direction() is Suffix.LOCAL) and \ 95 (suffix.get_datatype() is datatype): 96 yield name, suffix 97 98 99def local_suffix_generator(a_block, datatype=False): 100 if (datatype is False): 101 for name, suffix in a_block.component_map(Suffix).items(): 102 if suffix.get_direction() is Suffix.LOCAL: 103 yield name, suffix 104 else: 105 for name, suffix in a_block.component_map(Suffix).items(): 106 if (suffix.get_direction() is Suffix.LOCAL) and \ 107 (suffix.get_datatype() is datatype): 108 yield name, suffix 109 110 111def active_suffix_generator(a_block, datatype=False): 112 if (datatype is False): 113 for name, suffix in a_block.component_map(Suffix, active=True).items(): 114 yield name, suffix 115 else: 116 for name, suffix in a_block.component_map(Suffix, active=True).items(): 117 if suffix.get_datatype() is datatype: 118 yield name, suffix 119 120 121def suffix_generator(a_block, datatype=False): 122 if (datatype is False): 123 for name, suffix in a_block.component_map(Suffix).items(): 124 yield name, suffix 125 else: 126 for name, suffix in a_block.component_map(Suffix).items(): 127 if suffix.get_datatype() is datatype: 128 yield name, suffix 129 130# Note: The order of inheritance here is important so that 131# __setstate__ works correctly on the ActiveComponent base class. 132 133 134@ModelComponentFactory.register("Declare a container for extraneous model data") 135class Suffix(ComponentMap, ActiveComponent): 136 """A model suffix, representing extraneous model data""" 137 138 """ 139 Constructor Arguments: 140 direction The direction of information flow for this suffix. 141 By default, this is LOCAL, indicating that no 142 suffix data is exported or imported. 143 datatype A variable type associated with all values of this 144 suffix. 145 """ 146 147 # Suffix Directions: 148 # If more directions are added be sure to update the error message 149 # in the setDirection method 150 # neither sent to solver or received from solver 151 LOCAL = 0 152 # sent to solver or other external location 153 EXPORT = 1 154 # obtained from solver or other external source 155 IMPORT = 2 156 IMPORT_EXPORT = 3 # both 157 158 SuffixDirections = (LOCAL, EXPORT, IMPORT, IMPORT_EXPORT) 159 SuffixDirectionToStr = {LOCAL: 'Suffix.LOCAL', 160 EXPORT: 'Suffix.EXPORT', 161 IMPORT: 'Suffix.IMPORT', 162 IMPORT_EXPORT: 'Suffix.IMPORT_EXPORT'} 163 # Suffix Datatypes 164 FLOAT = 4 165 INT = 0 166 SuffixDatatypes = (FLOAT, INT, None) 167 SuffixDatatypeToStr = {FLOAT: 'Suffix.FLOAT', 168 INT: 'Suffix.INT', 169 None: str(None)} 170 171 def __init__(self, **kwds): 172 173 # Suffix type information 174 self._direction = None 175 self._datatype = None 176 self._rule = None 177 178 # The suffix direction 179 direction = kwds.pop('direction', Suffix.LOCAL) 180 181 # The suffix datatype 182 datatype = kwds.pop('datatype', Suffix.FLOAT) 183 184 # The suffix construction rule 185 # TODO: deprecate the use of 'rule' 186 self._rule = kwds.pop('rule', None) 187 self._rule = kwds.pop('initialize', self._rule) 188 189 # Check that keyword values make sense (these function have 190 # internal error checking). 191 self.set_direction(direction) 192 self.set_datatype(datatype) 193 194 # Initialize base classes 195 kwds.setdefault('ctype', Suffix) 196 ActiveComponent.__init__(self, **kwds) 197 ComponentMap.__init__(self) 198 199 if self._rule is None: 200 self.construct() 201 202 def __setstate__(self, state): 203 """ 204 This method must be defined for deepcopy/pickling because this 205 class relies on component ids. 206 """ 207 ActiveComponent.__setstate__(self, state) 208 ComponentMap.__setstate__(self, state) 209 210 def construct(self, data=None): 211 """ 212 Constructs this component, applying rule if it exists. 213 """ 214 if is_debug_set(logger): 215 logger.debug("Constructing suffix %s", self.name) 216 217 if self._constructed is True: 218 return 219 220 timer = ConstructionTimer(self) 221 self._constructed = True 222 223 if self._rule is not None: 224 self.update_values(self._rule(self._parent())) 225 timer.report() 226 227 @deprecated('Suffix.exportEnabled is replaced with Suffix.export_enabled.', 228 version='4.1.10486') 229 def exportEnabled(self): 230 return self.export_enabled() 231 232 def export_enabled(self): 233 """ 234 Returns True when this suffix is enabled for export to 235 solvers. 236 """ 237 return bool(self._direction & Suffix.EXPORT) 238 239 @deprecated('Suffix.importEnabled is replaced with Suffix.import_enabled.', 240 version='4.1.10486') 241 def importEnabled(self): 242 return self.import_enabled() 243 244 def import_enabled(self): 245 """ 246 Returns True when this suffix is enabled for import from 247 solutions. 248 """ 249 return bool(self._direction & Suffix.IMPORT) 250 251 @deprecated('Suffix.updateValues is replaced with Suffix.update_values.', 252 version='4.1.10486') 253 def updateValues(self, data, expand=True): 254 return self.update_values(data, expand) 255 256 def update_values(self, data, expand=True): 257 """ 258 Updates the suffix data given a list of component,value 259 tuples. Provides an improvement in efficiency over calling 260 set_value on every component. 261 """ 262 if expand: 263 264 try: 265 items = data.items() 266 except AttributeError: 267 items = data 268 269 for component, value in items: 270 self.set_value(component, value, expand=expand) 271 272 else: 273 274 # As implemented by MutableMapping 275 self.update(data) 276 277 @deprecated('Suffix.setValue is replaced with Suffix.set_value.', 278 version='4.1.10486') 279 def setValue(self, component, value, expand=True): 280 return self.set_value(component, value, expand) 281 282 def set_value(self, component, value, expand=True): 283 """ 284 Sets the value of this suffix on the specified component. 285 286 When expand is True (default), array components are handled by 287 storing a reference and value for each index, with no 288 reference being stored for the array component itself. When 289 expand is False (this is the case for __setitem__), this 290 behavior is disabled and a reference to the array component 291 itself is kept. 292 """ 293 if expand and component.is_indexed(): 294 for component_ in component.values(): 295 self[component_] = value 296 else: 297 self[component] = value 298 299 @deprecated('Suffix.setAllValues is replaced with Suffix.set_all_values.', 300 version='4.1.10486') 301 def setAllValues(self, value): 302 return self.set_all_values(value) 303 304 def set_all_values(self, value): 305 """ 306 Sets the value of this suffix on all components. 307 """ 308 for ndx in self: 309 self[ndx] = value 310 311 @deprecated('Suffix.clearValue is replaced with Suffix.clear_value.', 312 version='4.1.10486') 313 def clearValue(self, component, expand=True): 314 return self.clear_value(component, expand) 315 316 def clear_value(self, component, expand=True): 317 """ 318 Clears suffix information for a component. 319 """ 320 if expand and component.is_indexed(): 321 for component_ in component.values(): 322 try: 323 del self[component_] 324 except KeyError: 325 pass 326 else: 327 try: 328 del self[component] 329 except KeyError: 330 pass 331 332 @deprecated('Suffix.clearAllValues is replaced with ' 333 'Suffix.clear_all_values.', 334 version='4.1.10486') 335 def clearAllValues(self): 336 return self.clear_all_values() 337 338 def clear_all_values(self): 339 """ 340 Clears all suffix data. 341 """ 342 self.clear() 343 344 @deprecated('Suffix.setDatatype is replaced with Suffix.set_datatype.', 345 version='4.1.10486') 346 def setDatatype(self, datatype): 347 return self.set_datatype(datatype) 348 349 def set_datatype(self, datatype): 350 """ 351 Set the suffix datatype. 352 """ 353 if datatype not in self.SuffixDatatypes: 354 raise ValueError("Suffix datatype must be one of: %s. \n" 355 "Value given: %s" 356 % (list(Suffix.SuffixDatatypeToStr.values()), 357 datatype)) 358 self._datatype = datatype 359 360 @deprecated('Suffix.getDatatype is replaced with Suffix.get_datatype.', 361 version='4.1.10486') 362 def getDatatype(self): 363 return self.get_datatype() 364 365 def get_datatype(self): 366 """ 367 Return the suffix datatype. 368 """ 369 return self._datatype 370 371 @deprecated('Suffix.setDirection is replaced with Suffix.set_direction.', 372 version='4.1.10486') 373 def setDirection(self, direction): 374 return self.set_direction(direction) 375 376 def set_direction(self, direction): 377 """ 378 Set the suffix direction. 379 """ 380 if direction not in self.SuffixDirections: 381 raise ValueError("Suffix direction must be one of: %s. \n" 382 "Value given: %s" 383 % (list(self.SuffixDirectionToStr.values()), 384 direction)) 385 self._direction = direction 386 387 @deprecated('Suffix.getDirection is replaced with Suffix.get_direction.', 388 version='4.1.10486') 389 def getDirection(self): 390 return self.get_direction() 391 392 def get_direction(self): 393 """ 394 Return the suffix direction. 395 """ 396 return self._direction 397 398 def __str__(self): 399 """ 400 Return a string representation of the suffix. If the name 401 attribute is None, then return '' 402 """ 403 name = self.name 404 if name is None: 405 return '' 406 return name 407 408 def _pprint(self): 409 return ( 410 [('Direction', self.SuffixDirectionToStr[self._direction]), 411 ('Datatype', self.SuffixDatatypeToStr[self._datatype]), 412 ], 413 ((str(k), v) for k, v in self._dict.values()), 414 ("Value",), 415 lambda k, v: [v] 416 ) 417 418 # TODO: delete 419 @deprecated('Suffix.getValue is replaced with ' 420 'the dict-interface method Suffix.get.', 421 version='4.1.10486') 422 def getValue(self, component, *args): 423 """ 424 Returns the current value of this suffix for the specified 425 component. 426 """ 427 # As implemented by MutableMapping 428 return self.get(component, *args) 429 430 # TODO: delete 431 @deprecated('Suffix.extractValues() is replaced with ' 432 'the dict-interface method Suffix.items().', 433 version='4.1.10486') 434 def extractValues(self): 435 """ 436 Extract all data stored on this Suffix into a list of 437 component, value tuples. 438 """ 439 # As implemented by MutableMapping 440 return list(self.items()) 441 442 # 443 # Override a few methods to make sure the ActiveComponent versions are 444 # called. We can't just switch the inheritance order due to 445 # complications with __setstate__ 446 # 447 448 def pprint(self, *args, **kwds): 449 return ActiveComponent.pprint(self, *args, **kwds) 450 451 def __str__(self): 452 return ActiveComponent.__str__(self) 453 454 # 455 # Override NotImplementedError messages on ComponentMap base class 456 # 457 458 def __eq__(self, other): 459 """Not implemented.""" 460 raise NotImplementedError("Suffix components are not comparable") 461 462 def __ne__(self, other): 463 """Not implemented.""" 464 raise NotImplementedError("Suffix components are not comparable") 465 466