Source code for pyomo.core.base.var

#  ___________________________________________________________________________
#
#  Pyomo: Python Optimization Modeling Objects
#  Copyright 2017 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.
#  ___________________________________________________________________________

__all__ = ['Var', '_VarData', '_GeneralVarData', 'VarList', 'SimpleVar']

import logging
from weakref import ref as weakref_ref

from pyomo.common.modeling import NoArgumentGiven
from pyomo.common.timing import ConstructionTimer
from pyomo.core.base.numvalue import NumericValue, value, is_fixed
from pyomo.core.base.set_types import Reals, Binary
from pyomo.core.base.plugin import ModelComponentFactory
from pyomo.core.base.component import ComponentData
from pyomo.core.base.indexed_component import IndexedComponent, UnindexedComponent_set
from pyomo.core.base.misc import apply_indexed_rule
from pyomo.core.base.set import Set, _SetDataBase
from pyomo.core.base.util import is_functor

from six import iteritems, itervalues
from six.moves import xrange

logger = logging.getLogger('pyomo.core')

class _VarData(ComponentData, NumericValue):
    """
    This class defines the data for a single variable.

    Constructor Arguments:
        component   The Var object that owns this data.

    Public Class Attributes:
        domain      The domain of this variable.
        bounds      A tuple (lower,upper) that defines the variable bounds.
        fixed       If True, then this variable is treated as a
                        fixed constant in the model.
        lb          A lower bound for this variable.  The lower bound can be
                        either numeric constants, parameter values, expressions
                        or any object that can be called with no arguments.
        ub          A upper bound for this variable.  The upper bound can be either
                        numeric constants, parameter values, expressions or any
                        object that can be called with no arguments.
        stale       A Boolean indicating whether the value of this variable is
                        legitimiate.  This value is true if the value should
                        be considered legitimate for purposes of reporting or
                        other interrogation.
        value       The numeric value of this variable.

    The domain, lb, and ub attributes are properties because they
    are too widely accessed directly to enforce explicit getter/setter
    methods and we need to deter directly modifying or accessing
    these attributes in certain cases.
    """

    __slots__ = ()

    def __init__(self, component=None):
        #
        # These lines represent in-lining of the
        # following constructors:
        #   - ComponentData
        #   - NumericValue
        self._component = weakref_ref(component) if (component is not None) \
                          else None

    #
    # Interface
    #

    def has_lb(self):
        """Returns :const:`False` when the lower bound is
        :const:`None` or negative infinity"""
        lb = self.lb
        return (lb is not None) and \
            (value(lb) != float('-inf'))

    def has_ub(self):
        """Returns :const:`False` when the upper bound is
        :const:`None` or positive infinity"""
        ub = self.ub
        return (ub is not None) and \
            (value(ub) != float('inf'))

    @property
    def bounds(self):
        """Returns the tuple (lower bound, upper bound)."""
        return (self.lb, self.ub)
    @bounds.setter
    def bounds(self, val):
        raise AttributeError("Assignment not allowed. Use the setub and setlb methods")

    def is_integer(self):
        """Returns True when the domain is a contiguous integer range."""
        # optimization: Reals and Binary are the most common cases, so
        # we will explicitly test that before generating the interval
        if self.domain is Reals:
            return False
        elif self.domain is Binary:
            return True
        _interval = self.domain.get_interval()
        return _interval is not None and _interval[2] == 1

    def is_binary(self):
        """Returns True when the domain is restricted to Binary values."""
        # optimization: Reals and Binary are the most common cases, so
        # we will explicitly test that before generating the interval
        if self.domain is Reals:
            return False
        elif self.domain is Binary:
            return True
        return self.domain.get_interval() == (0,1,1)

# TODO?
#    def is_semicontinuous(self):
#        """
#        Returns True when the domain class is SemiContinuous.
#        """
#        return self.domain.__class__ is SemiContinuousSet
#    def is_semiinteger(self):
#        """
#        Returns True when the domain class is SemiInteger.
#        """
#        return self.domain.__class__ is SemiIntegerSet

    def is_continuous(self):
        """Returns True when the domain is a continuous real range"""
        # optimization: Reals and Binary are the most common cases, so
        # we will explicitly test that before generating the interval
        if self.domain is Reals:
            return True
        elif self.domain is Binary:
            return False
        _interval = self.domain.get_interval()
        return _interval is not None and _interval[2] == 0

    def is_fixed(self):
        """Returns True if this variable is fixed, otherwise returns False."""
        return self.fixed

    def is_constant(self):
        """Returns False because this is not a constant in an expression."""
        return False

    def is_variable_type(self):
        """Returns True because this is a variable."""
        return True

    def is_potentially_variable(self):
        """Returns True because this is a variable."""
        return True

    def _compute_polynomial_degree(self, result):
        """
        If the variable is fixed, it represents a constant
        is a polynomial with degree 0. Otherwise, it has
        degree 1. This method is used in expressions to
        compute polynomial degree.
        """
        if self.fixed:
            return 0
        return 1

    def set_value(self, val, valid=False):
        """
        Set the value of this numeric object, after
        validating its value. If the 'valid' flag is True,
        then the validation step is skipped.
        """
        if valid or self._valid_value(val):
            self.value = val
            self.stale = False

    def _valid_value(self, val, use_exception=True):
        """
        Validate the value.  If use_exception is True, then raise an
        exception.
        """
        ans = val is None or val in self.domain
        if not ans and use_exception:
            raise ValueError("Numeric value `%s` (%s) is not in "
                             "domain %s for Var %s" %
                             (val, type(val), self.domain, self.name))
        return ans

    def clear(self):
        self.value = None

    def __nonzero__(self):
        """
        Return True if the value is defined and non-zero.
        """
        if self.value:
            return True
        if self.value is None:
            raise ValueError("Var value is undefined")
        return False

    def __call__(self, exception=True):
        """Compute the value of this variable."""
        return self.value

    __bool__ = __nonzero__


    #
    # Abstract Interface
    #

    @property
    def value(self):
        """Return the value for this variable."""
        raise NotImplementedError

    @property
    def domain(self):
        """Return the domain for this variable."""
        raise NotImplementedError

    @property
    def lb(self):
        """Return the lower bound for this variable."""
        raise NotImplementedError

    @property
    def ub(self):
        """Return the upper bound for this variable."""
        raise NotImplementedError

    @property
    def fixed(self):
        """Return the fixed indicator for this variable."""
        raise NotImplementedError

    @property
    def stale(self):
        """Return the stale indicator for this variable."""
        raise NotImplementedError

    def setlb(self, val):
        """
        Set the lower bound for this variable after validating that
        the value is fixed (or None).
        """
        raise NotImplementedError

    def setub(self, val):
        """
        Set the upper bound for this variable after validating that
        the value is fixed (or None).
        """
        raise NotImplementedError

    def fix(self, value=NoArgumentGiven):
        """
        Set the fixed indicator to True. Value argument is optional,
        indicating the variable should be fixed at its current value.
        """
        raise NotImplementedError

    def unfix(self):
        """Sets the fixed indicator to False."""
        raise NotImplementedError

    free=unfix

    def to_string(self, verbose=None, labeler=None, smap=None, compute_values=False):
        """Return the component name"""
        if self.fixed and compute_values:
            try:
                return str(self())
            except:
                pass
        if smap:
            return smap.getSymbol(self, labeler)
        return self.name


class _GeneralVarData(_VarData):
    """
    This class defines the data for a single variable.

    Constructor Arguments:
        component   The Var object that owns this data.

    Public Class Attributes:
        domain      The domain of this variable.
        bounds      A tuple (lower,upper) that defines the variable bounds.
        fixed       If True, then this variable is treated as a
                        fixed constant in the model.
        lb          A lower bound for this variable.  The lower bound can be
                        either numeric constants, parameter values, expressions
                        or any object that can be called with no arguments.
        ub          A upper bound for this variable.  The upper bound can be either
                        numeric constants, parameter values, expressions or any
                        object that can be called with no arguments.
        stale       A Boolean indicating whether the value of this variable is
                        legitimiate.  This value is true if the value should
                        be considered legitimate for purposes of reporting or
                        other interrogation.
        value       The numeric value of this variable.

    The domain, lb, and ub attributes are properties because they
    are too widely accessed directly to enforce explicit getter/setter
    methods and we need to deter directly modifying or accessing
    these attributes in certain cases.
    """

    __slots__ = ('_value', '_lb', '_ub', '_domain', 'fixed', 'stale')

    def __init__(self, domain=Reals, component=None):
        #
        # These lines represent in-lining of the
        # following constructors:
        #   - _VarData
        #   - ComponentData
        #   - NumericValue
        self._component = weakref_ref(component) if (component is not None) \
                          else None
        self._value = None
        #
        # The type of the lower and upper bound attributes can either
        # be atomic numeric types in Python, expressions, etc.
        # Basically, they can be anything that passes an "is_fixed" test.
        #
        self._lb = None
        self._ub = None
        self._domain = None
        self.fixed = False
        self.stale = True
        # don't call the property setter here because
        # the SimplVar constructor will fail
        #
        # TODO: this should be migrated over to using a SetInitializer
        # to handle the checking / conversion of the argument to a
        # proper Pyomo Set and not use isinstance() of a private class.
        if isinstance(domain, _SetDataBase):
            self._domain = domain
        elif domain is not None:
            raise ValueError(
                "%s is not a valid domain. Variable domains must be an "
                "instance of a Pyomo Set.  Examples: NonNegativeReals, "
                "Integers, Binary" % (domain,))

    def __getstate__(self):
        state = super(_GeneralVarData, self).__getstate__()
        for i in _GeneralVarData.__slots__:
            state[i] = getattr(self, i)
        return state

    # Note: None of the slots on this class need to be edited, so we
    # don't need to implement a specialized __setstate__ method, and
    # can quietly rely on the super() class's implementation.

    #
    # Abstract Interface
    #

    # value is an attribute

    @property
    def value(self):
        """Return the value for this variable."""
        return self._value
    @value.setter
    def value(self, val):
        """Set the value for this variable."""
        self._value = val

    @property
    def domain(self):
        """Return the domain for this variable."""
        return self._domain
    @domain.setter
    def domain(self, domain):
        """Set the domain for this variable."""
        # TODO: this should be migrated over to using a SetInitializer
        # to handle the checking / conversion of the argument to a
        # proper Pyomo Set and not use isinstance() of a private class.
        if isinstance(domain, _SetDataBase):
            self._domain = domain
        else:
            raise ValueError(
                "%s is not a valid domain. Variable domains must be an "
                "instance of a Pyomo Set.  Examples: NonNegativeReals, "
                "Integers, Binary" % (domain,))

    @property
    def lb(self):
        """Return the lower bound for this variable."""
        dlb, _ = self.domain.bounds()
        if self._lb is None:
            return dlb
        elif dlb is None:
            return value(self._lb)
        return max(value(self._lb), dlb)
    @lb.setter
    def lb(self, val):
        raise AttributeError("Assignment not allowed. Use the setlb method")

    @property
    def ub(self):
        """Return the upper bound for this variable."""
        _, dub = self.domain.bounds()
        if self._ub is None:
            return dub
        elif dub is None:
            return value(self._ub)
        return min(value(self._ub), dub)
    @ub.setter
    def ub(self, val):
        raise AttributeError("Assignment not allowed. Use the setub method")

    def get_units(self):
        """Return the units for this variable entry."""
        # parent_component() returns self if this is scalar, or the owning
        # component if not scalar
        return self.parent_component()._units

    # fixed is an attribute

    # stale is an attribute

    def setlb(self, val):
        """
        Set the lower bound for this variable after validating that
        the value is fixed (or None).
        """
        # Note: is_fixed(None) returns True
        if is_fixed(val):
            self._lb = val
        else:
            raise ValueError(
                "Non-fixed input of type '%s' supplied as variable lower "
                "bound - legal types must be fixed expressions or variables."
                % (type(val),))

    def setub(self, val):
        """
        Set the upper bound for this variable after validating that
        the value is fixed (or None).
        """
        # Note: is_fixed(None) returns True
        if is_fixed(val):
            self._ub = val
        else:
            raise ValueError(
                "Non-fixed input of type '%s' supplied as variable upper "
                "bound - legal types are fixed expressions or variables."
                "parameters"
                % (type(val),))

    def fix(self, value=NoArgumentGiven):
        """
        Set the fixed indicator to True. Value argument is optional,
        indicating the variable should be fixed at its current value.
        """
        self.fixed = True
        if value is not NoArgumentGiven:
            self.value = value

    def unfix(self):
        """Sets the fixed indicator to False."""
        self.fixed = False

    free = unfix


[docs]@ModelComponentFactory.register("Decision variables.") class Var(IndexedComponent): """A numeric variable, which may be defined over an index. Args: domain (Set or function, optional): A Set that defines valid values for the variable (e.g., `Reals`, `NonNegativeReals`, `Binary`), or a rule that returns Sets. Defaults to `Reals`. within (Set or function, optional): An alias for `domain`. bounds (tuple or function, optional): A tuple of (lower, upper) bounds for the variable, or a rule that returns tuples. Defaults to (None, None). initialize (float or function, optional): The initial value for the variable, or a rule that returns initial values. rule (float or function, optional): An alias for `initialize`. dense (bool, optional): Instantiate all elements from `index_set()` when constructing the Var (True) or just the variables returned by `initialize`/`rule` (False). Defaults to True. units (pyomo units expression, optional): Set the units corresponding to the entries in this variable. """ _ComponentDataClass = _GeneralVarData def __new__(cls, *args, **kwds): if cls != Var: return super(Var, cls).__new__(cls) if not args or (args[0] is UnindexedComponent_set and len(args)==1): return SimpleVar.__new__(SimpleVar) else: return IndexedVar.__new__(IndexedVar) def __init__(self, *args, **kwd): # # Default keyword values # initialize = kwd.pop('initialize', None) initialize = kwd.pop('rule', initialize) domain = kwd.pop('within', Reals) domain = kwd.pop('domain', domain) bounds = kwd.pop('bounds', None) self._dense = kwd.pop('dense', True) self._units = kwd.pop('units', None) # # Initialize the base class # kwd.setdefault('ctype', Var) IndexedComponent.__init__(self, *args, **kwd) # # Determine if the domain argument is a functor or other object # self._domain_init_value = None self._domain_init_rule = None if is_functor(domain): self._domain_init_rule = domain else: self._domain_init_value = domain # # Allow for functions or functors for value initialization, # without confusing with Params, etc (which have a __call__ method). # self._value_init_value = None self._value_init_rule = None if is_functor(initialize) and (not isinstance(initialize,NumericValue)): self._value_init_rule = initialize else: self._value_init_value = initialize # # Determine if the bound argument is a functor or other object # self._bounds_init_rule = None self._bounds_init_value = None if is_functor(bounds): self._bounds_init_rule = bounds elif type(bounds) is tuple: self._bounds_init_value = bounds elif bounds is not None: raise ValueError("Variable 'bounds' keyword must be a tuple or function")
[docs] def flag_as_stale(self): """ Set the 'stale' attribute of every variable data object to True. """ for var_data in itervalues(self._data): var_data.stale = True
[docs] def get_values(self, include_fixed_values=True): """ Return a dictionary of index-value pairs. """ if include_fixed_values: return {idx:vardata.value for idx,vardata in iteritems(self._data)} return {idx:vardata.value for idx, vardata in iteritems(self._data) if not vardata.fixed}
extract_values = get_values
[docs] def set_values(self, new_values, valid=False): """ Set the values of a dictionary. The default behavior is to validate the values in the dictionary. """ for index, new_value in iteritems(new_values): self[index].set_value(new_value, valid)
[docs] def get_units(self): """Return the units expression for this Var.""" return self._units
[docs] def construct(self, data=None): """Construct this component.""" if __debug__ and logger.isEnabledFor(logging.DEBUG): #pragma:nocover try: name = str(self.name) except: # Some Var components don't have a name yet, so just use # the type name = type(self) if logger.isEnabledFor(logging.DEBUG): logger.debug( "Constructing Variable, name=%s, from data=%s" % (name, str(data))) if self._constructed: return timer = ConstructionTimer(self) self._constructed=True # # Construct _VarData objects for all index values # if not self.is_indexed(): self._data[None] = self self._initialize_members((None,)) elif self._dense: # This loop is optimized for speed with pypy. # Calling dict.update((...) for ...) is roughly # 30% slower self_weakref = weakref_ref(self) for ndx in self._index: cdata = self._ComponentDataClass( domain=self._domain_init_value, component=None) cdata._component = self_weakref self._data[ndx] = cdata #self._initialize_members((ndx,)) self._initialize_members(self._index) timer.report()
[docs] def add(self, index): """Add a variable with a particular index.""" return self[index]
# # This method must be defined on subclasses of # IndexedComponent that support implicit definition # def _getitem_when_not_present(self, index): """Returns the default component data value.""" if index is None and not self.is_indexed(): obj = self._data[index] = self else: obj = self._data[index] = self._ComponentDataClass( self._domain_init_value, component=self) self._initialize_members((index,)) return obj def _setitem_when_not_present(self, index, value): """Perform the fundamental component item creation and storage. Var overrides the default implementation from IndexedComponent to enforce the call to _initialize_members. """ obj = self._getitem_when_not_present(index) try: return obj.set_value(value) except: del self._data[index] raise def _initialize_members(self, init_set): """Initialize variable data for all indices in a set.""" # TODO: determine if there is any advantage to supporting init_set. # Preliminary tests indicate that there isn't a significant speed # difference to using the set form (used in dense vector # construction). Getting rid of it could simplify _setitem and # this method. # # Initialize domains # if self._domain_init_rule is not None: # # Initialize domains with a rule # if self.is_indexed(): for ndx in init_set: self._data[ndx].domain = \ apply_indexed_rule(self, self._domain_init_rule, self._parent(), ndx) else: self.domain = self._domain_init_rule(self._parent()) else: if self.is_indexed(): # Optimization: It is assumed self._domain_init_value # is used when the _GeneralVarData objects # are created. This avoids an unnecessary # loop over init_set, which can significantly # speed up construction of variables with large # index sets. pass else: # the above optimization does not apply for # singleton objects (trying to do so breaks # some of the pickle tests) self.domain = self._domain_init_value # # Initialize values # if self._value_init_rule is not None: # # Initialize values with a rule # if self.is_indexed(): for key in init_set: vardata = self._data[key] val = apply_indexed_rule(self, self._value_init_rule, self._parent(), key) val = value(val) vardata.set_value(val) else: val = self._value_init_rule(self._parent()) val = value(val) self.set_value(val) elif self._value_init_value is not None: # # Initialize values with a value # if self._value_init_value.__class__ is dict: for key in init_set: # Skip indices that are not in the # dictionary. This arises when # initializing VarList objects with a # dictionary. if not key in self._value_init_value: continue val = self._value_init_value[key] vardata = self._data[key] vardata.set_value(val) else: val = value(self._value_init_value) for key in init_set: vardata = self._data[key] vardata.set_value(val) # # Initialize bounds # if self._bounds_init_rule is not None: # # Initialize bounds with a rule # if self.is_indexed(): for key in init_set: vardata = self._data[key] (lb, ub) = apply_indexed_rule(self, self._bounds_init_rule, self._parent(), key) vardata.setlb(lb) vardata.setub(ub) else: (lb, ub) = self._bounds_init_rule(self._parent()) self.setlb(lb) self.setub(ub) elif self._bounds_init_value is not None: # # Initialize bounds with a value # (lb, ub) = self._bounds_init_value for key in init_set: vardata = self._data[key] vardata.setlb(lb) vardata.setub(ub) def _pprint(self): """Print component information.""" return ( [("Size", len(self)), ("Index", self._index if self.is_indexed() else None), ], iteritems(self._data), ( "Lower","Value","Upper","Fixed","Stale","Domain"), lambda k, v: [ value(v.lb), v.value, value(v.ub), v.fixed, v.stale, v.domain ] )
class SimpleVar(_GeneralVarData, Var): """A single variable.""" def __init__(self, *args, **kwd): _GeneralVarData.__init__(self, domain=None, component=self) Var.__init__(self, *args, **kwd) # # Since this class derives from Component and Component.__getstate__ # just packs up the entire __dict__ into the state dict, we do not # need to define the __getstate__ or __setstate__ methods. # We just defer to the super() get/set state. Since all of our # get/set state methods rely on super() to traverse the MRO, this # will automatically pick up both the Component and Data base classes. # # # Override abstract interface methods to first check for # construction # # NOTE: that we can't provide these errors for # fixed and stale because they are attributes @property def value(self): """Return the value for this variable.""" if self._constructed: return _GeneralVarData.value.fget(self) raise ValueError( "Accessing the value of variable '%s' " "before the Var has been constructed (there " "is currently no value to return)." % (self.name)) @value.setter def value(self, val): """Set the value for this variable.""" if self._constructed: return _GeneralVarData.value.fset(self, val) raise ValueError( "Setting the value of variable '%s' " "before the Var has been constructed (there " "is currently nothing to set." % (self.name)) @property def domain(self): """Return the domain for this variable.""" # NOTE: we can't do the right thing here because # of the behavior of block._add_temporary_set return _GeneralVarData.domain.fget(self) #if self._constructed: # return _GeneralVarData.domain.fget(self) #raise ValueError( # "Accessing the domain of variable '%s' " # "before the Var has been constructed (there " # "is currently no domain to return)." # % (self.name)) @domain.setter def domain(self, domain): """Set the domain for this variable.""" if self._constructed: return _GeneralVarData.domain.fset(self, domain) raise ValueError( "Setting the domain of variable '%s' " "before the Var has been constructed (there " "is currently nothing to set." % (self.name)) @property def lb(self): """Return the lower bound for this variable.""" if self._constructed: return _GeneralVarData.lb.fget(self) raise ValueError( "Accessing the lower bound of variable '%s' " "before the Var has been constructed (there " "is currently nothing to return)." % (self.name)) @lb.setter def lb(self, lb): raise AttributeError("Assignment not allowed. Use the setlb method") @property def ub(self): """Return the upper bound for this variable.""" if self._constructed: return _GeneralVarData.ub.fget(self) raise ValueError( "Accessing the upper bound of variable '%s' " "before the Var has been constructed (there " "is currently nothing to return)." % (self.name)) @ub.setter def ub(self, ub): raise AttributeError("Assignment not allowed. Use the setub method") def setlb(self, val): """ Set the lower bound for this variable after validating that the value is fixed (or None). """ if self._constructed: return _GeneralVarData.setlb(self, val) raise ValueError( "Setting the lower bound of variable '%s' " "before the Var has been constructed (there " "is currently nothing to set)." % (self.name)) def setub(self, val): """ Set the upper bound for this variable after validating that the value is fixed (or None). """ if self._constructed: return _GeneralVarData.setub(self, val) raise ValueError( "Setting the upper bound of variable '%s' " "before the Var has been constructed (there " "is currently nothing to set)." % (self.name)) def fix(self, value=NoArgumentGiven): """ Set the fixed indicator to True. Value argument is optional, indicating the variable should be fixed at its current value. """ if self._constructed: return _GeneralVarData.fix(self, value) raise ValueError( "Fixing variable '%s' " "before the Var has been constructed (there " "is currently nothing to set)." % (self.name)) def unfix(self): """Sets the fixed indicator to False.""" if self._constructed: return _GeneralVarData.unfix(self) raise ValueError( "Freeing variable '%s' " "before the Var has been constructed (there " "is currently nothing to set)." % (self.name)) free=unfix class IndexedVar(Var): """An array of variables.""" def setlb(self, val): """ Set the lower bound for this variable. """ for vardata in itervalues(self): vardata.setlb(val) def setub(self, val): """ Set the upper bound for this variable. """ for vardata in itervalues(self): vardata.setub(val) def fix(self, value=NoArgumentGiven): """ Set the fixed indicator to True. Value argument is optional, indicating the variable should be fixed at its current value. """ for vardata in itervalues(self): vardata.fix(value=value) def unfix(self): """Sets the fixed indicator to False.""" for vardata in itervalues(self): vardata.unfix() @property def domain(self): raise AttributeError( "The domain is not an attribute for IndexedVar. It " "can be set for all indices using this property setter, " "but must be accessed for individual variables in this container.") @domain.setter def domain(self, domain): """Sets the domain for all variables in this container.""" for vardata in itervalues(self): vardata.domain = domain free=unfix @ModelComponentFactory.register("List of decision variables.") class VarList(IndexedVar): """ Variable-length indexed variable objects used to construct Pyomo models. """ def __init__(self, **kwds): #kwds['dense'] = False args = (Set(dimen=1),) IndexedVar.__init__(self, *args, **kwds) def construct(self, data=None): """Construct this component.""" if __debug__ and logger.isEnabledFor(logging.DEBUG): logger.debug("Constructing variable list %s", self.name) if self._constructed: return # Note: do not set _constructed here, or the super() call will # not actually construct the component. self.index_set().construct() # We need to ensure that the indices needed for initialization are # added to the underlying implicit set. We *could* verify that the # indices in the initialization dict are all sequential integers, # OR we can just add the correct number of sequential integers and # then let _validate_index complain when we set the value. if self._value_init_value.__class__ is dict: for i in xrange(len(self._value_init_value)): self._index.add(i+1) super(VarList,self).construct(data) # Note that the current Var initializer silently ignores # initialization data that is not in the underlying index set. To # ensure that at least here all initialization data is added to the # VarList (so we get potential domain errors), we will re-set # everything. if self._value_init_value.__class__ is dict: for k,v in iteritems(self._value_init_value): self[k] = v def add(self): """Add a variable to this list.""" next_idx = len(self._index) + 1 self._index.add(next_idx) return self[next_idx]