# Source code for pyomo.dae.contset

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

import logging
from pyomo.common.timing import ConstructionTimer
from pyomo.core import *
from pyomo.core.base.plugin import ModelComponentFactory
from pyomo.core.base.set import SortedSimpleSet
from pyomo.core.base.numvalue import native_numeric_types

logger = logging.getLogger('pyomo.dae')
__all__ = ['ContinuousSet']

[docs]@ModelComponentFactory.register(
"A bounded continuous numerical range optionally containing"
" discrete points of interest.")
class ContinuousSet(SortedSimpleSet):
""" Represents a bounded continuous domain

Minimally, this set must contain two numeric values defining the
bounds of a continuous range. Discrete points of interest may
be added to the continuous set. A continuous set is one
dimensional and may only contain numerical values.

Parameters
----------
initialize : list
Default discretization points to be included

bounds : tuple
The bounding points for the continuous domain. The bounds will
be included as discrete points in the :py:class:ContinuousSet
and will be used to bound the points added to the
:py:class:ContinuousSet through the 'initialize' argument,
a data file, or the add() method

Attributes
----------
_changed : boolean
This keeps track of whether or not the ContinuousSet was changed
during discretization. If the user specifies all of the needed
discretization points before the discretization then there is no
need to go back through the model and reconstruct things indexed
by the :py:class:ContinuousSet

_fe : list
This is a sorted list of the finite element points in the
:py:class:ContinuousSet. i.e. this list contains all the
discrete points in the :py:class:ContinuousSet that are not
collocation points. Points that are both finite element points
and collocation points will be included in this list.

_discretization_info : dict
This is a dictionary which contains information on the
discretization transformation which has been applied to the
:py:class:ContinuousSet.
"""

def __init__(self, *args, **kwds):
""" Constructor """
if kwds.pop("filter", None) is not None:
raise TypeError("'filter' is not a valid keyword argument for "
"ContinuousSet")
# if kwds.pop("within", None) is not None:
#    raise TypeError("'within' is not a valid keyword argument for "
#  ContinuousSet")
kwds.setdefault('dimen', 1)
if kwds["dimen"] != 1:
raise TypeError("'dimen' is not a valid keyword argument for "
"ContinuousSet")
if kwds.pop("virtual", None) is not None:
raise TypeError("'virtual' is not a valid keyword argument for "
"ContinuousSet")
if kwds.pop("validate", None) is not None:
raise TypeError("'validate' is not a valid keyword argument for "
"ContinuousSet")
if len(args) != 0:
raise TypeError("A ContinuousSet expects no arguments")

kwds.setdefault('ctype', ContinuousSet)
self._changed = False
self._fe = []
self._discretization_info = {}
super(ContinuousSet, self).__init__(**kwds)

[docs]    def get_finite_elements(self):
""" Returns the finite element points

If the :py:class:ContinuousSet <pyomo.dae.ContinuousSet> has been
discretizaed using a collocation scheme, this method will return a
list of the finite element discretization points but not the
collocation points within each finite element. If the
:py:class:ContinuousSet <pyomo.dae.ContinuousSet> has not been
discretized or a finite difference discretization was used,
this method returns a list of all the discretization points in the
:py:class:ContinuousSet <pyomo.dae.ContinuousSet>.

Returns
-------
list of floats
"""
return self._fe

[docs]    def get_discretization_info(self):
"""Returns a dict with information on the discretization scheme
that has been applied to the :py:class:ContinuousSet.

Returns
-------
dict
"""
return self._discretization_info

[docs]    def get_changed(self):
""" Returns flag indicating if the :py:class:ContinuousSet was
changed during discretization

:py:class:ContinuousSet <pyomo.dae.ContinuousSet> while applying a
discretization scheme

Returns
-------
boolean
"""
return self._changed

[docs]    def set_changed(self, newvalue):
""" Sets the _changed flag to 'newvalue'

Parameters
----------
newvalue : boolean

"""
# TODO: Check this if-statement
if newvalue is not True and newvalue is not False:
raise ValueError("The _changed attribute on a ContinuousSet may "
"only be set to True or False")
self._changed = newvalue

[docs]    def get_upper_element_boundary(self, point):
""" Returns the first finite element point that is greater or equal
to 'point'

Parameters
----------
point : float

Returns
-------
float
"""
if point in self._fe:
return point
elif point > max(self._fe):
logger.warn("The point '%s' exceeds the upper bound "
"of the ContinuousSet '%s'. Returning the upper bound"
% (str(point), self.name))
return max(self._fe)
else:
for i in self._fe:
# This works because the list _fe is always sorted
if i > point:
return i

[docs]    def get_lower_element_boundary(self, point):
""" Returns the first finite element point that is less than or
equal to 'point'

Parameters
----------
point : float

Returns
-------
float
"""
if point in self._fe:
if 'scheme' in self._discretization_info:
# Because Radau Collocation has a collocation point on the
# upper finite element bound this if statement ensures that
# the desired finite element bound is returned
tmp = self._fe.index(point)
if tmp != 0:
return self._fe[tmp - 1]
return point
elif point < min(self._fe):
logger.warn("The point '%s' is less than the lower bound "
"of the ContinuousSet '%s'. Returning the lower bound "
% (str(point), self.name))
return min(self._fe)
else:
rev_fe = list(self._fe)
rev_fe.reverse()
for i in rev_fe:
if i < point:
return i

[docs]    def construct(self, values=None):
""" Constructs a :py:class:ContinuousSet component

"""
if self._constructed:
return
timer = ConstructionTimer(self)
super(ContinuousSet, self).construct(values)

for val in self:
if type(val) is tuple:
raise ValueError("ContinuousSet cannot contain tuples")
if val.__class__ not in native_numeric_types:
raise ValueError("ContinuousSet can only contain numeric "
"values")

# TBD: If a user specifies bounds they will be added to the set
# unless the user specified bounds have been overwritten during
# OrderedSimpleSet construction. This can lead to some unintuitive
# behavior when the ContinuousSet is both initialized with values and
# bounds are specified. The current implementation is consistent
# with how 'Set' treats this situation.
for bnd in self.domain.bounds():
# Note: the base class constructor ensures that any declared
# set members are already within the bounds.
if bnd is not None and bnd not in self:

if None in self.bounds():
raise ValueError("ContinuousSet '%s' must have at least two values"
" indicating the range over which a differential "
"equation is to be discretized" % self.name)

if len(self) < 2:
# (reachable if lb==ub)
raise ValueError("ContinuousSet '%s' must have at least two values"
" indicating the range over which a differential "
"equation is to be discretized" % self.name)
self._fe = sorted(self)
timer.report()