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__ = ['Param']
12
13import sys
14import types
15import logging
16from weakref import ref as weakref_ref
17
18from pyomo.common.deprecation import deprecation_warning, RenamedClass
19from pyomo.common.log import is_debug_set
20from pyomo.common.modeling import NOTSET
21from pyomo.common.timing import ConstructionTimer
22from pyomo.core.base.component import ComponentData, ModelComponentFactory
23from pyomo.core.base.indexed_component import (
24    IndexedComponent, UnindexedComponent_set, IndexedComponent_NDArrayMixin
25)
26from pyomo.core.base.initializer import Initializer
27from pyomo.core.base.misc import apply_indexed_rule, apply_parameterized_indexed_rule
28from pyomo.core.base.numvalue import (
29    NumericValue, native_types, value as expr_value
30)
31from pyomo.core.base.set_types import Any, Reals
32from pyomo.core.base.units_container import units
33
34logger = logging.getLogger('pyomo.core')
35
36def _raise_modifying_immutable_error(obj, index):
37    if obj.is_indexed():
38        name = "%s[%s]" % (obj.name, index)
39    else:
40        name = obj.name
41    raise TypeError(
42        "Attempting to set the value of the immutable parameter "
43        "%s after the parameter has been constructed.  If you intend "
44        "to change the value of this parameter dynamically, please "
45        "declare the parameter as mutable [i.e., Param(mutable=True)]"
46        % (name,))
47
48class _ImplicitAny(Any.__class__):
49    """An Any that issues a deprecation warning for non-Real values.
50
51    This is a helper class to implement the deprecation warnings for the
52    change of Param's implicit domain from Any to Reals.
53
54    """
55    def __new__(cls, **kwds):
56        return super(_ImplicitAny, cls).__new__(cls)
57
58    def __init__(self, owner, **kwds):
59        super(_ImplicitAny, self).__init__(**kwds)
60        self._owner = weakref_ref(owner)
61        self._component = weakref_ref(self)
62        self.construct()
63
64    def __getstate__(self):
65        state = super(_ImplicitAny, self).__getstate__()
66        state['_owner'] = None if self._owner is None else self._owner()
67        return state
68
69    def __setstate__(self, state):
70        _owner = state.pop('_owner')
71        super(_ImplicitAny, self).__setstate__(state)
72        self._owner = None if _owner is None else weakref_ref(_owner)
73
74    def __deepcopy__(self, memo):
75        return super(Any.__class__, self).__deepcopy__(memo)
76
77    def __contains__(self, val):
78        if val not in Reals:
79            deprecation_warning(
80                "The default domain for Param objects is 'Any'.  However, "
81                "we will be changing that default to 'Reals' in the "
82                "future.  If you really intend the domain of this Param (%s) "
83                "to be 'Any', you can suppress this warning by explicitly "
84                "specifying 'within=Any' to the Param constructor."
85                % ('Unknown' if self._owner is None else self._owner().name,),
86                version='5.6.9', remove_in='6.0')
87        return True
88
89
90class _ParamData(ComponentData, NumericValue):
91    """
92    This class defines the data for a mutable parameter.
93
94    Constructor Arguments:
95        owner       The Param object that owns this data.
96        value       The value of this parameter.
97
98    Public Class Attributes:
99        value       The numeric value of this variable.
100    """
101
102    __slots__ = ('_value',)
103
104    def __init__(self, component):
105        #
106        # The following is equivalent to calling
107        # the base ComponentData constructor.
108        #
109        self._component = weakref_ref(component)
110        #
111        # The following is equivalent to calling the
112        # base NumericValue constructor.
113        #
114        self._value = Param.NoValue
115
116    def __getstate__(self):
117        """
118        This method must be defined because this class uses slots.
119        """
120        state = super(_ParamData, self).__getstate__()
121        for i in _ParamData.__slots__:
122            state[i] = getattr(self, i)
123        return state
124
125    # Note: because NONE of the slots on this class need to be edited,
126    # we don't need to implement a specialized __setstate__ method.
127
128    def clear(self):
129        """Clear the data in this component"""
130        self._value = Param.NoValue
131
132    # FIXME: ComponentData need to have pointers to their index to make
133    # operations like validation efficient.  As it stands now, if
134    # set_value is called without specifying an index, this call
135    # involves a linear scan of the _data dict.
136    def set_value(self, value, idx=NOTSET):
137        #
138        # If this param has units, then we need to check the incoming
139        # value and see if it is "units compatible".  We only need to
140        # check here in set_value, because all united Params are
141        # required to be mutable.
142        #
143        _comp = self.parent_component()
144        if type(value) in native_types:
145            # TODO: warn/error: check if this Param has units: assigning
146            # a dimensionless value to a united param should be an error
147            pass
148        elif _comp._units is not None:
149            _src_magnitude = expr_value(value)
150            _src_units = units.get_units(value)
151            value = units.convert_value(
152                num_value=_src_magnitude, from_units=_src_units,
153                to_units=_comp._units)
154
155        old_value, self._value = self._value, value
156        try:
157            _comp._validate_value(idx, value, data=self)
158        except:
159            self._value = old_value
160            raise
161
162    def __call__(self, exception=True):
163        """
164        Return the value of this object.
165        """
166        if self._value is Param.NoValue:
167            if exception:
168                raise ValueError(
169                    "Error evaluating Param value (%s):\n\tThe Param value is "
170                    "currently set to an invalid value.  This is\n\ttypically "
171                    "from a scalar Param or mutable Indexed Param without\n"
172                    "\tan initial or default value."
173                    % ( self.name, ))
174            else:
175                return None
176        return self._value
177
178    @property
179    def value(self):
180        """Return the value for this variable."""
181        return self()
182    @value.setter
183    def value(self, val):
184        """Set the value for this variable."""
185        self.set_value(val)
186
187    def get_units(self):
188        """Return the units for this ParamData"""
189        return self.parent_component()._units
190
191    def is_fixed(self):
192        """
193        Returns True because this value is fixed.
194        """
195        return True
196
197    def is_constant(self):
198        """
199        Returns False because this is not a constant in an expression.
200        """
201        return False
202
203    def is_parameter_type(self):
204        """
205        Returns True because this is a parameter object.
206        """
207        return True
208
209    def _compute_polynomial_degree(self, result):
210        """
211        Returns 0 because this object can never reference variables.
212        """
213        return 0
214
215    def __nonzero__(self):
216        """Return True if the value is defined and non-zero."""
217        return bool(self())
218
219    __bool__ = __nonzero__
220
221
222@ModelComponentFactory.register("Parameter data that is used to define a model instance.")
223class Param(IndexedComponent, IndexedComponent_NDArrayMixin):
224    """
225    A parameter value, which may be defined over an index.
226
227    Constructor Arguments:
228        name
229            The name of this parameter
230        index
231            The index set that defines the distinct parameters. By default,
232            this is None, indicating that there is a single parameter.
233        domain
234            A set that defines the type of values that each parameter must be.
235        within
236            A set that defines the type of values that each parameter must be.
237        validate
238            A rule for validating this parameter w.r.t. data that exists in
239            the model
240        default
241            A scalar, rule, or dictionary that defines default values for
242            this parameter
243        initialize
244            A dictionary or rule for setting up this parameter with existing
245            model data
246        unit: pyomo unit expression
247            An expression containing the units for the parameter
248        mutable: `boolean`
249            Flag indicating if the value of the parameter may change between
250            calls to a solver. Defaults to `False`
251    """
252
253    DefaultMutable = False
254
255    class NoValue(object):
256        """A dummy type that is pickle-safe that we can use as the default
257        value for Params to indicate that no valid value is present."""
258        pass
259
260    def __new__(cls, *args, **kwds):
261        if cls != Param:
262            return super(Param, cls).__new__(cls)
263        if not args or (args[0] is UnindexedComponent_set and len(args) == 1):
264            return super(Param, cls).__new__(ScalarParam)
265        else:
266            return super(Param, cls).__new__(IndexedParam)
267
268    def __init__(self, *args, **kwd):
269        _init = self._pop_from_kwargs(
270            'Param', kwd, ('rule', 'initialize'), NOTSET)
271        self._rule = Initializer(_init,
272                                 treat_sequences_as_mappings=False,
273                                 arg_not_specified=NOTSET)
274        self.domain = self._pop_from_kwargs('Param', kwd, ('domain', 'within'))
275        if self.domain is None:
276            self.domain = _ImplicitAny(owner=self, name='Any')
277
278        self._validate      = kwd.pop('validate', None )
279        self._mutable       = kwd.pop('mutable', Param.DefaultMutable )
280        self._default_val   = kwd.pop('default', Param.NoValue )
281        self._dense_initialize = kwd.pop('initialize_as_dense', False)
282        self._units         = kwd.pop('units', None)
283        if self._units is not None:
284            self._units = units.get_units(self._units)
285            self._mutable = True
286
287        kwd.setdefault('ctype', Param)
288        IndexedComponent.__init__(self, *args, **kwd)
289
290    def __len__(self):
291        """
292        Return the number of component data objects stored by this
293        component.  If a default value is specified, then the
294        length equals the number of items in the component index.
295        """
296        if self._default_val is Param.NoValue:
297            return len(self._data)
298        return len(self._index)
299
300    def __contains__(self, idx):
301        """
302        Return true if the index is in the dictionary.  If the default value
303        is specified, then all members of the component index are valid.
304        """
305        if self._default_val is Param.NoValue:
306            return idx in self._data
307        return idx in self._index
308
309    def keys(self):
310        """
311        Iterate over the keys in the dictionary.  If the default value is
312        specified, then iterate over all keys in the component index.
313        """
314        if self._default_val is Param.NoValue:
315            return self._data.__iter__()
316        return self._index.__iter__()
317
318    @property
319    def mutable(self):
320        return self._mutable
321
322    def get_units(self):
323        """Return the units for this ParamData"""
324        return self._units
325
326    #
327    # These are "sparse equivalent" access / iteration methods that
328    # only loop over the defined data.
329    #
330
331    def sparse_keys(self):
332        """Return a list of keys in the defined parameters"""
333        return list(self._data.keys())
334
335    def sparse_values(self):
336        """Return a list of the defined param data objects"""
337        return list(self._data.values())
338
339    def sparse_items(self):
340        """Return a list (index,data) tuples for defined parameters"""
341        return list(self._data.items())
342
343    def sparse_iterkeys(self):
344        """Return an iterator for the keys in the defined parameters"""
345        return self._data.keys()
346
347    def sparse_itervalues(self):
348        """Return an iterator for the defined param data objects"""
349        return self._data.values()
350
351    def sparse_iteritems(self):
352        """Return an iterator of (index,data) tuples for defined parameters"""
353        return self._data.items()
354
355    def extract_values(self):
356        """
357        A utility to extract all index-value pairs defined for this
358        parameter, returned as a dictionary.
359
360        This method is useful in contexts where key iteration and
361        repeated __getitem__ calls are too expensive to extract
362        the contents of a parameter.
363        """
364        if self._mutable:
365            #
366            # The parameter is mutable, parameter data are ParamData types.
367            # Thus, we need to create a temporary dictionary that contains the
368            # values from the ParamData objects.
369            #
370            return {key:param_value() for key,param_value in self.items()}
371        elif not self.is_indexed():
372            #
373            # The parameter is a scalar, so we need to create a temporary
374            # dictionary using the value for this parameter.
375            #
376            return { None: self() }
377        else:
378            #
379            # The parameter is not mutable, so iteritems() can be
380            # converted into a dictionary containing parameter values.
381            #
382            return dict( self.items() )
383
384    def extract_values_sparse(self):
385        """
386        A utility to extract all index-value pairs defined with non-default
387        values, returned as a dictionary.
388
389        This method is useful in contexts where key iteration and
390        repeated __getitem__ calls are too expensive to extract
391        the contents of a parameter.
392        """
393        if self._mutable:
394            #
395            # The parameter is mutable, parameter data are ParamData types.
396            # Thus, we need to create a temporary dictionary that contains the
397            # values from the ParamData objects.
398            #
399            ans = {}
400            for key, param_value in self.sparse_iteritems():
401                ans[key] = param_value()
402            return ans
403        elif not self.is_indexed():
404            #
405            # The parameter is a scalar, so we need to create a temporary
406            # dictionary using the value for this parameter.
407            #
408            return { None: self() }
409        else:
410            #
411            # The parameter is not mutable, so sparse_iteritems() can be
412            # converted into a dictionary containing parameter values.
413            #
414            return dict( self.sparse_iteritems() )
415
416    def store_values(self, new_values, check=True):
417        """
418        A utility to update a Param with a dictionary or scalar.
419
420        If check=True, then both the index and value
421        are checked through the __getitem__ method.  Using check=False
422        should only be used by developers!
423        """
424        if not self._mutable:
425            _raise_modifying_immutable_error(self, '*')
426        #
427        _srcType = type(new_values)
428        _isDict = _srcType is dict or ( \
429            hasattr(_srcType, '__getitem__')
430            and not isinstance(new_values, NumericValue) )
431        #
432        if check:
433            if _isDict:
434                for index, new_value in new_values.items():
435                    self[index] = new_value
436            else:
437                for index in self._index:
438                    self[index] = new_values
439            return
440        #
441        # The argument check is False, so we bypass almost all of the
442        # Param logic for ensuring data integrity.
443        #
444        if self.is_indexed():
445            if _isDict:
446                # It is possible that the Param is sparse and that the
447                # index is not already in the _data dict.  As these
448                # cases are rare, we will recover from the exception
449                # instead of incurring the penalty of checking.
450                for index, new_value in new_values.items():
451                    if index not in self._data:
452                        self._data[index] = _ParamData(self)
453                    self._data[index]._value = new_value
454            else:
455                # For scalars, we will choose an approach based on
456                # how "dense" the Param is
457                if not self._data: # empty
458                    for index in self._index:
459                        p = self._data[index] = _ParamData(self)
460                        p._value = new_values
461                elif len(self._data) == len(self._index):
462                    for index in self._index:
463                        self._data[index]._value = new_values
464                else:
465                    for index in self._index:
466                        if index not in self._data:
467                            self._data[index] = _ParamData(self)
468                        self._data[index]._value = new_values
469        else:
470            #
471            # Initialize a scalar
472            #
473            if _isDict:
474                if None not in new_values:
475                    raise RuntimeError(
476                        "Cannot store value for scalar Param %s:\n\tNo value "
477                        "with index None in the new values dict."
478                        % (self.name,))
479                new_values = new_values[None]
480            # scalars have to be handled differently
481            self[None] = new_values
482
483    def set_default(self, val):
484        """
485        Perform error checks and then set the default value for this parameter.
486
487        NOTE: this test will not validate the value of function return values.
488        """
489        if self._constructed \
490                and val is not Param.NoValue \
491                and type(val) in native_types \
492                and val not in self.domain:
493            raise ValueError(
494                "Default value (%s) is not valid for Param %s domain %s" %
495                (str(val), self.name, self.domain.name))
496        self._default_val = val
497
498    def default(self):
499        """
500        Return the value of the parameter default.
501
502        Possible values:
503            Param.NoValue
504                No default value is provided.
505            Numeric
506                A constant value that is the default value for all undefined
507                parameters.
508            Function
509                f(model, i) returns the value for the default value for
510                parameter i
511        """
512        return self._default_val
513
514    def _getitem_when_not_present(self, index):
515        """
516        Returns the default component data value
517        """
518        #
519        # Local values
520        #
521        val = self._default_val
522        if val is Param.NoValue:
523            # We should allow the creation of mutable params without
524            # a default value, as long as *solving* a model without
525            # reasonable values produces an informative error.
526            if self._mutable:
527                # Note: _ParamData defaults to Param.NoValue
528                if self.is_indexed():
529                    ans = self._data[index] = _ParamData(self)
530                else:
531                    ans = self._data[index] = self
532                return ans
533            if self.is_indexed():
534                idx_str = '%s[%s]' % (self.name, index,)
535            else:
536                idx_str = '%s' % (self.name,)
537            raise ValueError(
538                "Error retrieving immutable Param value (%s):\n\tThe Param "
539                "value is undefined and no default value is specified."
540                % ( idx_str,) )
541
542        _default_type = type(val)
543        _check_value_domain = True
544        if _default_type in native_types:
545            #
546            # The set_default() method validates the domain of native types, so
547            # we can skip the check on the value domain.
548            #
549            _check_value_domain = False
550        elif _default_type is types.FunctionType:
551            val = apply_indexed_rule(self, val, self.parent_block(), index)
552        elif hasattr(val, '__getitem__') and (
553                not isinstance(val, NumericValue) or val.is_indexed() ):
554            # Things that look like Dictionaries should be allowable.  This
555            # includes other IndexedComponent objects.
556            val = val[index]
557        else:
558            # this is something simple like a non-indexed component
559            pass
560
561        #
562        # If the user wants to validate values, we need to validate the
563        # default value as well. For Mutable Params, this is easy:
564        # _setitem_impl will inject the value into _data and
565        # then call validate.
566        #
567        if self._mutable:
568            return self._setitem_when_not_present(index, val)
569        #
570        # For immutable params, we never inject the default into the data
571        # dictionary.  This will break validation, as the validation rule is
572        # allowed to assume the data is already present (actually, it will
573        # die on infinite recursion, as Param.__getitem__() will re-call
574        # _getitem_when_not_present).
575        #
576        # So, we will do something very inefficient: if we are
577        # validating, we will inject the value into the dictionary,
578        # call validate, and remove it.
579        #
580        if _check_value_domain or self._validate:
581            try:
582                self._data[index] = val
583                self._validate_value(index, val, _check_value_domain)
584            finally:
585                del self._data[index]
586
587        return val
588
589    def _setitem_impl(self, index, obj, value):
590        """The __setitem__ method performs significant validation around the
591        input indices, particularly when the index value is new.  In
592        various contexts, we don't need to incur this overhead
593        (e.g. during initialization).  The _setitem_impl
594        assumes the input value is in the set native_types
595
596        """
597        #
598        # We need to ensure that users don't override the value for immutable
599        # parameters.
600        #
601        if self._constructed and not self._mutable:
602            _raise_modifying_immutable_error(self, index)
603        #
604        # Params should contain *values*.  Note that if we just call
605        # value(), then that forces the value to be a numeric value.
606        # Notably, we allow Params with domain==Any to hold strings, tuples,
607        # etc.  The following lets us use NumericValues to initialize
608        # Params, but is optimized to check for "known" native types to
609        # bypass a potentially expensive isinstance()==False call.
610        #
611        if value.__class__ not in native_types:
612            if isinstance(value, NumericValue):
613                value = value()
614        #
615        # Set the value depending on the type of param value.
616        #
617        if self._mutable:
618            obj.set_value(value, index)
619            return obj
620        else:
621            old_value, self._data[index] = self._data[index], value
622            # Because we do not have a _ParamData, we cannot rely on the
623            # validation that occurs in _ParamData.set_value()
624            try:
625                self._validate_value(index, value)
626                return value
627            except:
628                self._data[index] = old_value
629                raise
630
631    def _setitem_when_not_present(self, index, value, _check_domain=True):
632        #
633        # We need to ensure that users don't override the value for immutable
634        # parameters.
635        #
636        if self._constructed and not self._mutable:
637            _raise_modifying_immutable_error(self, index)
638        #
639        # Params should contain *values*.  Note that if we just call
640        # value(), then that forces the value to be a numeric value.
641        # Notably, we allow Params with domain==Any to hold strings, tuples,
642        # etc.  The following lets us use NumericValues to initialize
643        # Params, but is optimized to check for "known" native types to
644        # bypass a potentially expensive isinstance()==False call.
645        #
646        if value.__class__ not in native_types:
647            if isinstance(value, NumericValue):
648                value = value()
649
650        #
651        # Set the value depending on the type of param value.
652        #
653        try:
654            if index is None and not self.is_indexed():
655                self._data[None] = self
656                self.set_value(value, index)
657                return self
658            elif self._mutable:
659                obj = self._data[index] = _ParamData(self)
660                obj.set_value(value, index)
661                return obj
662            else:
663                self._data[index] = value
664                # Because we do not have a _ParamData, we cannot rely on the
665                # validation that occurs in _ParamData.set_value()
666                self._validate_value(index, value, _check_domain)
667                return value
668        except:
669            del self._data[index]
670            raise
671
672
673    def _validate_value(self, index, value, validate_domain=True, data=None):
674        """
675        Validate a given input/value pair.
676        """
677        #
678        # Check if the value is valid within the current domain
679        #
680        if validate_domain and not value in self.domain:
681            if index is NOTSET:
682                index = data.index()
683            raise ValueError(
684                "Invalid parameter value: %s[%s] = '%s', value type=%s.\n"
685                "\tValue not in parameter domain %s" %
686                (self.name, index, value, type(value), self.domain.name))
687        if self._validate:
688            if index is NOTSET:
689                index = data.index()
690            valid = apply_parameterized_indexed_rule(
691                self, self._validate, self.parent_block(), value, index )
692            if not valid:
693                raise ValueError(
694                    "Invalid parameter value: %s[%s] = '%s', value type=%s.\n"
695                    "\tValue failed parameter validation rule" %
696                    ( self.name, index, value, type(value) ) )
697
698
699    def construct(self, data=None):
700        """
701        Initialize this component.
702
703        A parameter is constructed using the initial data or
704        the data loaded from an external source.  We first
705        set all the values based on self._rule, and then
706        allow the data dictionary to overwrite anything.
707
708        Note that we allow an undefined Param value to be
709        constructed.  We throw an exception if a user tries
710        to use an uninitialized Param.
711        """
712        if self._constructed:
713            return
714
715        timer = ConstructionTimer(self)
716        if is_debug_set(logger):   #pragma:nocover
717            logger.debug("Constructing Param, name=%s, from data=%s"
718                         % ( self.name, str(data) ))
719
720        try:
721            #
722            # If the default value is a simple type, we check it versus
723            # the domain.
724            #
725            val = self._default_val
726            if val is not Param.NoValue \
727               and type(val) in native_types \
728               and val not in self.domain:
729                raise ValueError(
730                    "Default value (%s) is not valid for Param %s domain %s" %
731                    (str(val), self.name, self.domain.name))
732            #
733            # Flag that we are in the "during construction" phase
734            #
735            self._constructed = None
736            #
737            # Step #1: initialize data from rule value
738            #
739            self._construct_from_rule_using_setitem()
740            #
741            # Step #2: allow any user-specified (external) data to override
742            # the initialization
743            #
744            if data is not None:
745                try:
746                    data_items = data.items()
747                except AttributeError:
748                    raise ValueError(
749                        "Attempting to initialize parameter=%s with data=%s.\n"
750                        "\tData type is not a mapping type, and a Mapping is "
751                        "expected." % (self.name, str(data)) )
752            else:
753                data_items = iter(())
754
755            try:
756                for key, val in data_items:
757                    self._setitem_when_not_present(
758                        self._validate_index(key), val)
759            except:
760                msg = sys.exc_info()[1]
761                raise RuntimeError(
762                    "Failed to set value for param=%s, index=%s, value=%s.\n"
763                    "\tsource error message=%s"
764                    % (self.name, str(key), str(val), str(msg)) )
765            #
766            # Flag that things are fully constructed now (and changing an
767            # immutable Param is now an exception).
768            #
769            self._constructed = True
770
771            # populate all other indices with default data
772            # (avoids calling _set_contains on self._index at runtime)
773            if self._dense_initialize:
774                self.to_dense_data()
775        finally:
776            timer.report()
777
778    def _pprint(self):
779        """
780        Return data that will be printed for this component.
781        """
782        if self._default_val is Param.NoValue:
783            default = "None" # for backwards compatibility in reporting
784        elif type(self._default_val) is types.FunctionType:
785            default = "(function)"
786        else:
787            default = str(self._default_val)
788        if self._mutable or not self.is_indexed():
789            dataGen = lambda k, v: [ v._value, ]
790        else:
791            dataGen = lambda k, v: [ v, ]
792        headers = [
793            ("Size", len(self)),
794            ("Index", self._index if self.is_indexed() else None),
795            ("Domain", self.domain.name),
796            ("Default", default),
797            ("Mutable", self._mutable),
798        ]
799        if self._units is not None:
800            headers.append(('Units', str(self._units)))
801        return ( headers,
802                 self.sparse_iteritems(),
803                 ("Value",),
804                 dataGen,
805                 )
806
807
808class ScalarParam(_ParamData, Param):
809
810    def __init__(self, *args, **kwds):
811        Param.__init__(self, *args, **kwds)
812        _ParamData.__init__(self, component=self)
813
814    #
815    # Since this class derives from Component and Component.__getstate__
816    # just packs up the entire __dict__ into the state dict, there s
817    # nothng special that we need to do here.  We will just defer to the
818    # super() get/set state.  Since all of our get/set state methods
819    # rely on super() to traverse the MRO, this will automatically pick
820    # up both the Component and Data base classes.
821    #
822
823    def __call__(self, exception=True):
824        """
825        Return the value of this parameter.
826        """
827        if self._constructed:
828            if not self._data:
829                if self._mutable:
830                    # This will trigger populating the _data dict and setting
831                    # the _default, if applicable
832                    self[None]
833                else:
834                    # Immutable Param defaults never get added to the
835                    # _data dict
836                    return self[None]
837            return super(ScalarParam, self).__call__(exception=exception)
838        if exception:
839            raise ValueError(
840                "Evaluating the numeric value of parameter '%s' before\n\t"
841                "the Param has been constructed (there is currently no "
842                "value to return)." % (self.name,) )
843
844    def set_value(self, value, index=NOTSET):
845        if index is NOTSET:
846            index = None
847        if self._constructed and not self._mutable:
848            _raise_modifying_immutable_error(self, index)
849        if not self._data:
850            self._data[index] = self
851        super(ScalarParam, self).set_value(value, index)
852
853    def is_constant(self):
854        """Determine if this ScalarParam is constant (and can be eliminated)
855
856        Returns False if either unconstructed or mutable, as it must be kept
857        in expressions (as it either doesn't have a value yet or the value
858        can change later.
859        """
860        return self._constructed and not self._mutable
861
862
863class SimpleParam(metaclass=RenamedClass):
864    __renamed__new_class__ = ScalarParam
865    __renamed__version__ = '6.0'
866
867
868class IndexedParam(Param):
869
870    def __call__(self, exception=True):
871        """Compute the value of the parameter"""
872        if exception:
873            raise TypeError('Cannot compute the value of an indexed Param (%s)'
874                            % (self.name,) )
875
876