Example #1
0
 def __init__(self, name, default_constraint=None, *a, **kw):
     super(Indexable, self).__init__(name=name, *a, **kw)
     self._default_constraint_ = default_constraint
     from index_operations import ParameterIndexOperations
     self.constraints = ParameterIndexOperations()
     self.priors = ParameterIndexOperations()
     if self._default_constraint_ is not None:
         self.constrain(self._default_constraint_)
Example #2
0
 def __init__(self, name, default_constraint=None, *a, **kw):
     super(Indexable, self).__init__(name=name, *a, **kw)
     self._default_constraint_ = default_constraint
     from index_operations import ParameterIndexOperations
     self.constraints = ParameterIndexOperations()
     self.priors = ParameterIndexOperations()
     if self._default_constraint_ is not None:
         self.constrain(self._default_constraint_)
Example #3
0
    def _parent_changed(self, parent):
        """
        From Parentable:
        Called when the parent changed

        update the constraints and priors view, so that
        constraining is automized for the parent.
        """
        from index_operations import ParameterIndexOperationsView
        #if getattr(self, "_in_init_"):
            #import ipdb;ipdb.set_trace()
            #self.constraints.update(param.constraints, start)
            #self.priors.update(param.priors, start)
        offset = parent._offset_for(self)
        self.constraints = ParameterIndexOperationsView(parent.constraints, offset, self.size)
        self.priors = ParameterIndexOperationsView(parent.priors, offset, self.size)
        self._fixes_ = None
        for p in self.parameters:
            p._parent_changed(parent)
Example #4
0
    def _parent_changed(self, parent):
        """
        From Parentable:
        Called when the parent changed

        update the constraints and priors view, so that
        constraining is automized for the parent.
        """
        from index_operations import ParameterIndexOperationsView
        #if getattr(self, "_in_init_"):
        #import ipdb;ipdb.set_trace()
        #self.constraints.update(param.constraints, start)
        #self.priors.update(param.priors, start)
        offset = parent._offset_for(self)
        self.constraints = ParameterIndexOperationsView(
            parent.constraints, offset, self.size)
        self.priors = ParameterIndexOperationsView(parent.priors, offset,
                                                   self.size)
        self._fixes_ = None
        for p in self.parameters:
            p._parent_changed(parent)
Example #5
0
class Indexable(Nameable, Updateable):
    """
    Make an object constrainable with Priors and Transformations.
    TODO: Mappings!!
    Adding a constraint to a Parameter means to tell the highest parent that
    the constraint was added and making sure that all parameters covered
    by this object are indeed conforming to the constraint.

    :func:`constrain()` and :func:`unconstrain()` are main methods here
    """
    def __init__(self, name, default_constraint=None, *a, **kw):
        super(Indexable, self).__init__(name=name, *a, **kw)
        self._default_constraint_ = default_constraint
        from index_operations import ParameterIndexOperations
        self.constraints = ParameterIndexOperations()
        self.priors = ParameterIndexOperations()
        if self._default_constraint_ is not None:
            self.constrain(self._default_constraint_)

    def _disconnect_parent(self, constr=None, *args, **kw):
        """
        From Parentable:
        disconnect the parent and set the new constraints to constr
        """
        if constr is None:
            constr = self.constraints.copy()
        self.constraints.clear()
        self.constraints = constr
        self._parent_ = None
        self._parent_index_ = None
        self._connect_fixes()
        self._notify_parent_change()

    #===========================================================================
    # Indexable
    #===========================================================================
    def _offset_for(self, param):
        """
        Return the offset of the param inside this parameterized object.
        This does not need to account for shaped parameters, as it
        basically just sums up the parameter sizes which come before param.
        """
        if param.has_parent():
            p = param._parent_._get_original(param)
            if p in self.parameters:
                return reduce(lambda a,b: a + b.size, self.parameters[:p._parent_index_], 0)
            return self._offset_for(param._parent_) + param._parent_._offset_for(param)
        return 0

    def _raveled_index_for(self, param):
        """
        get the raveled index for a param
        that is an int array, containing the indexes for the flattened
        param inside this parameterized logic.
        """
        from param import ParamConcatenation
        if isinstance(param, ParamConcatenation):
            return np.hstack((self._raveled_index_for(p) for p in param.params))
        return param._raveled_index() + self._offset_for(param)

    def _raveled_index(self):
        """
        Flattened array of ints, specifying the index of this object.
        This has to account for shaped parameters!
        """
        return np.r_[:self.size]

    #===========================================================================
    # Fixing Parameters:
    #===========================================================================
    def constrain_fixed(self, value=None, warning=True, trigger_parent=True):
        """
        Constrain this parameter to be fixed to the current value it carries.

        :param warning: print a warning for overwriting constraints.
        """
        if value is not None:
            self[:] = value

        index = self.unconstrain()
        index = self._add_to_index_operations(self.constraints, index, __fixed__, warning)
        self._highest_parent_._set_fixed(self, index)
        self.notify_observers(self, None if trigger_parent else -np.inf)
        return index
    fix = constrain_fixed

    def unconstrain_fixed(self):
        """
        This parameter will no longer be fixed.
        """
        unconstrained = self.unconstrain(__fixed__)
        self._highest_parent_._set_unfixed(self, unconstrained)
        return unconstrained
    unfix = unconstrain_fixed

    def _ensure_fixes(self):
        # Ensure that the fixes array is set:
        # Parameterized: ones(self.size)
        # Param: ones(self._realsize_
        if not self._has_fixes(): self._fixes_ = np.ones(self.size, dtype=bool)

    def _set_fixed(self, param, index):
        self._ensure_fixes()
        offset = self._offset_for(param)
        self._fixes_[index+offset] = FIXED
        if np.all(self._fixes_): self._fixes_ = None  # ==UNFIXED

    def _set_unfixed(self, param, index):
        self._ensure_fixes()
        offset = self._offset_for(param)
        self._fixes_[index+offset] = UNFIXED
        if np.all(self._fixes_): self._fixes_ = None  # ==UNFIXED

    def _connect_fixes(self):
        fixed_indices = self.constraints[__fixed__]
        if fixed_indices.size > 0:
            self._ensure_fixes()
            self._fixes_[fixed_indices] = FIXED
        else:
            self._fixes_ = None
            del self.constraints[__fixed__]

    #===========================================================================
    # Convenience for fixed
    #===========================================================================
    def _has_fixes(self):
        return hasattr(self, "_fixes_") and self._fixes_ is not None and self._fixes_.size == self.size

    @property
    def is_fixed(self):
        for p in self.parameters:
            if not p.is_fixed: return False
        return True

    def _get_original(self, param):
        # if advanced indexing is activated it happens that the array is a copy
        # you can retrieve the original param through this method, by passing
        # the copy here
        return self.parameters[param._parent_index_]

    #===========================================================================
    # Prior Operations
    #===========================================================================
    def set_prior(self, prior, warning=True):
        """
        Set the prior for this object to prior.
        :param :class:`~GPy.priors.Prior` prior: a prior to set for this parameter
        :param bool warning: whether to warn if another prior was set for this parameter
        """
        repriorized = self.unset_priors()
        self._add_to_index_operations(self.priors, repriorized, prior, warning)

        from domains import _REAL, _POSITIVE, _NEGATIVE
        if prior.domain is _POSITIVE:
            self.constrain_positive(warning)
        elif prior.domain is _NEGATIVE:
            self.constrain_negative(warning)
        elif prior.domain is _REAL:
            rav_i = self._raveled_index()
            assert all(all(False if c is __fixed__ else c.domain is _REAL for c in con) for con in self.constraints.properties_for(rav_i)), 'Domain of prior and constraint have to match, please unconstrain if you REALLY wish to use this prior'

    def unset_priors(self, *priors):
        """
        Un-set all priors given (in *priors) from this parameter handle.
        """
        return self._remove_from_index_operations(self.priors, priors)

    def log_prior(self):
        """evaluate the prior"""
        if self.priors.size > 0:
            x = self.param_array
            return reduce(lambda a, b: a + b, (p.lnpdf(x[ind]).sum() for p, ind in self.priors.iteritems()), 0)
        return 0.

    def _log_prior_gradients(self):
        """evaluate the gradients of the priors"""
        if self.priors.size > 0:
            x = self.param_array
            ret = np.zeros(x.size)
            [np.put(ret, ind, p.lnpdf_grad(x[ind])) for p, ind in self.priors.iteritems()]
            return ret
        return 0.

    #===========================================================================
    # Tie parameters together
    #===========================================================================

    def _has_ties(self):
        if self._highest_parent_.tie.tied_param is None:
            return False
        if self.has_parent():
            return self._highest_parent_.tie.label_buf[self._highest_parent_._raveled_index_for(self)].sum()>0
        return True

    def tie_together(self):
        self._highest_parent_.tie.add_tied_parameter(self)
        self._highest_parent_._set_fixed(self,self._raveled_index())
        self._trigger_params_changed()

    #===========================================================================
    # Constrain operations -> done
    #===========================================================================

    def constrain(self, transform, warning=True, trigger_parent=True):
        """
        :param transform: the :py:class:`GPy.core.transformations.Transformation`
                          to constrain the this parameter to.
        :param warning: print a warning if re-constraining parameters.

        Constrain the parameter to the given
        :py:class:`GPy.core.transformations.Transformation`.
        """
        if isinstance(transform, Transformation):
            self.param_array[...] = transform.initialize(self.param_array)
        reconstrained = self.unconstrain()
        added = self._add_to_index_operations(self.constraints, reconstrained, transform, warning)
        self.notify_observers(self, None if trigger_parent else -np.inf)
        return added

    def unconstrain(self, *transforms):
        """
        :param transforms: The transformations to unconstrain from.

        remove all :py:class:`GPy.core.transformations.Transformation`
        transformats of this parameter object.
        """
        return self._remove_from_index_operations(self.constraints, transforms)

    def constrain_positive(self, warning=True, trigger_parent=True):
        """
        :param warning: print a warning if re-constraining parameters.

        Constrain this parameter to the default positive constraint.
        """
        self.constrain(Logexp(), warning=warning, trigger_parent=trigger_parent)

    def constrain_negative(self, warning=True, trigger_parent=True):
        """
        :param warning: print a warning if re-constraining parameters.

        Constrain this parameter to the default negative constraint.
        """
        self.constrain(NegativeLogexp(), warning=warning, trigger_parent=trigger_parent)

    def constrain_bounded(self, lower, upper, warning=True, trigger_parent=True):
        """
        :param lower, upper: the limits to bound this parameter to
        :param warning: print a warning if re-constraining parameters.

        Constrain this parameter to lie within the given range.
        """
        self.constrain(Logistic(lower, upper), warning=warning, trigger_parent=trigger_parent)

    def unconstrain_positive(self):
        """
        Remove positive constraint of this parameter.
        """
        self.unconstrain(Logexp())

    def unconstrain_negative(self):
        """
        Remove negative constraint of this parameter.
        """
        self.unconstrain(NegativeLogexp())

    def unconstrain_bounded(self, lower, upper):
        """
        :param lower, upper: the limits to unbound this parameter from

        Remove (lower, upper) bounded constrain from this parameter/
        """
        self.unconstrain(Logistic(lower, upper))

    def _parent_changed(self, parent):
        """
        From Parentable:
        Called when the parent changed

        update the constraints and priors view, so that
        constraining is automized for the parent.
        """
        from index_operations import ParameterIndexOperationsView
        #if getattr(self, "_in_init_"):
            #import ipdb;ipdb.set_trace()
            #self.constraints.update(param.constraints, start)
            #self.priors.update(param.priors, start)
        offset = parent._offset_for(self)
        self.constraints = ParameterIndexOperationsView(parent.constraints, offset, self.size)
        self.priors = ParameterIndexOperationsView(parent.priors, offset, self.size)
        self._fixes_ = None
        for p in self.parameters:
            p._parent_changed(parent)

    def _add_to_index_operations(self, which, reconstrained, what, warning):
        """
        Helper preventing copy code.
        This adds the given what (transformation, prior etc) to parameter index operations which.
        reconstrained are reconstrained indices.
        warn when reconstraining parameters if warning is True.
        TODO: find out which parameters have changed specifically
        """
        if warning and reconstrained.size > 0:
            # TODO: figure out which parameters have changed and only print those
            print "WARNING: reconstraining parameters {}".format(self.hierarchy_name() or self.name)
        index = self._raveled_index()
        which.add(what, index)
        return index

    def _remove_from_index_operations(self, which, transforms):
        """
        Helper preventing copy code.
        Remove given what (transform prior etc) from which param index ops.
        """
        if len(transforms) == 0:
            transforms = which.properties()
        removed = np.empty((0,), dtype=int)
        for t in transforms:
            unconstrained = which.remove(t, self._raveled_index())
            removed = np.union1d(removed, unconstrained)
            if t is __fixed__:
                self._highest_parent_._set_unfixed(self, unconstrained)

        return removed
Example #6
0
class Indexable(Nameable, Updateable):
    """
    Make an object constrainable with Priors and Transformations.
    TODO: Mappings!!
    Adding a constraint to a Parameter means to tell the highest parent that
    the constraint was added and making sure that all parameters covered
    by this object are indeed conforming to the constraint.

    :func:`constrain()` and :func:`unconstrain()` are main methods here
    """
    def __init__(self, name, default_constraint=None, *a, **kw):
        super(Indexable, self).__init__(name=name, *a, **kw)
        self._default_constraint_ = default_constraint
        from index_operations import ParameterIndexOperations
        self.constraints = ParameterIndexOperations()
        self.priors = ParameterIndexOperations()
        if self._default_constraint_ is not None:
            self.constrain(self._default_constraint_)

    def _disconnect_parent(self, constr=None, *args, **kw):
        """
        From Parentable:
        disconnect the parent and set the new constraints to constr
        """
        if constr is None:
            constr = self.constraints.copy()
        self.constraints.clear()
        self.constraints = constr
        self._parent_ = None
        self._parent_index_ = None
        self._connect_fixes()
        self._notify_parent_change()

    #===========================================================================
    # Indexable
    #===========================================================================
    def _offset_for(self, param):
        """
        Return the offset of the param inside this parameterized object.
        This does not need to account for shaped parameters, as it
        basically just sums up the parameter sizes which come before param.
        """
        if param.has_parent():
            p = param._parent_._get_original(param)
            if p in self.parameters:
                return reduce(lambda a, b: a + b.size,
                              self.parameters[:p._parent_index_], 0)
            return self._offset_for(
                param._parent_) + param._parent_._offset_for(param)
        return 0

    def _raveled_index_for(self, param):
        """
        get the raveled index for a param
        that is an int array, containing the indexes for the flattened
        param inside this parameterized logic.
        """
        from param import ParamConcatenation
        if isinstance(param, ParamConcatenation):
            return np.hstack(
                (self._raveled_index_for(p) for p in param.params))
        return param._raveled_index() + self._offset_for(param)

    def _raveled_index(self):
        """
        Flattened array of ints, specifying the index of this object.
        This has to account for shaped parameters!
        """
        return np.r_[:self.size]

    #===========================================================================
    # Fixing Parameters:
    #===========================================================================
    def constrain_fixed(self, value=None, warning=True, trigger_parent=True):
        """
        Constrain this parameter to be fixed to the current value it carries.

        :param warning: print a warning for overwriting constraints.
        """
        if value is not None:
            self[:] = value

        index = self.unconstrain()
        index = self._add_to_index_operations(self.constraints, index,
                                              __fixed__, warning)
        self._highest_parent_._set_fixed(self, index)
        self.notify_observers(self, None if trigger_parent else -np.inf)
        return index

    fix = constrain_fixed

    def unconstrain_fixed(self):
        """
        This parameter will no longer be fixed.
        """
        unconstrained = self.unconstrain(__fixed__)
        self._highest_parent_._set_unfixed(self, unconstrained)
        return unconstrained

    unfix = unconstrain_fixed

    def _ensure_fixes(self):
        # Ensure that the fixes array is set:
        # Parameterized: ones(self.size)
        # Param: ones(self._realsize_
        if not self._has_fixes(): self._fixes_ = np.ones(self.size, dtype=bool)

    def _set_fixed(self, param, index):
        self._ensure_fixes()
        offset = self._offset_for(param)
        self._fixes_[index + offset] = FIXED
        if np.all(self._fixes_): self._fixes_ = None  # ==UNFIXED

    def _set_unfixed(self, param, index):
        self._ensure_fixes()
        offset = self._offset_for(param)
        self._fixes_[index + offset] = UNFIXED
        if np.all(self._fixes_): self._fixes_ = None  # ==UNFIXED

    def _connect_fixes(self):
        fixed_indices = self.constraints[__fixed__]
        if fixed_indices.size > 0:
            self._ensure_fixes()
            self._fixes_[fixed_indices] = FIXED
        else:
            self._fixes_ = None
            del self.constraints[__fixed__]

    #===========================================================================
    # Convenience for fixed
    #===========================================================================
    def _has_fixes(self):
        return hasattr(
            self, "_fixes_"
        ) and self._fixes_ is not None and self._fixes_.size == self.size

    @property
    def is_fixed(self):
        for p in self.parameters:
            if not p.is_fixed: return False
        return True

    def _get_original(self, param):
        # if advanced indexing is activated it happens that the array is a copy
        # you can retrieve the original param through this method, by passing
        # the copy here
        return self.parameters[param._parent_index_]

    #===========================================================================
    # Prior Operations
    #===========================================================================
    def set_prior(self, prior, warning=True):
        """
        Set the prior for this object to prior.
        :param :class:`~GPy.priors.Prior` prior: a prior to set for this parameter
        :param bool warning: whether to warn if another prior was set for this parameter
        """
        repriorized = self.unset_priors()
        self._add_to_index_operations(self.priors, repriorized, prior, warning)

        from domains import _REAL, _POSITIVE, _NEGATIVE
        if prior.domain is _POSITIVE:
            self.constrain_positive(warning)
        elif prior.domain is _NEGATIVE:
            self.constrain_negative(warning)
        elif prior.domain is _REAL:
            rav_i = self._raveled_index()
            assert all(
                all(False if c is __fixed__ else c.domain is _REAL
                    for c in con)
                for con in self.constraints.properties_for(rav_i)
            ), 'Domain of prior and constraint have to match, please unconstrain if you REALLY wish to use this prior'

    def unset_priors(self, *priors):
        """
        Un-set all priors given (in *priors) from this parameter handle.
        """
        return self._remove_from_index_operations(self.priors, priors)

    def log_prior(self):
        """evaluate the prior"""
        if self.priors.size > 0:
            x = self.param_array
            return reduce(lambda a, b: a + b,
                          (p.lnpdf(x[ind]).sum()
                           for p, ind in self.priors.iteritems()), 0)
        return 0.

    def _log_prior_gradients(self):
        """evaluate the gradients of the priors"""
        if self.priors.size > 0:
            x = self.param_array
            ret = np.zeros(x.size)
            [
                np.put(ret, ind, p.lnpdf_grad(x[ind]))
                for p, ind in self.priors.iteritems()
            ]
            return ret
        return 0.

    #===========================================================================
    # Tie parameters together
    #===========================================================================

    def _has_ties(self):
        if self._highest_parent_.tie.tied_param is None:
            return False
        if self.has_parent():
            return self._highest_parent_.tie.label_buf[
                self._highest_parent_._raveled_index_for(self)].sum() > 0
        return True

    def tie_together(self):
        self._highest_parent_.tie.add_tied_parameter(self)
        self._highest_parent_._set_fixed(self, self._raveled_index())
        self._trigger_params_changed()

    #===========================================================================
    # Constrain operations -> done
    #===========================================================================

    def constrain(self, transform, warning=True, trigger_parent=True):
        """
        :param transform: the :py:class:`GPy.core.transformations.Transformation`
                          to constrain the this parameter to.
        :param warning: print a warning if re-constraining parameters.

        Constrain the parameter to the given
        :py:class:`GPy.core.transformations.Transformation`.
        """
        if isinstance(transform, Transformation):
            self.param_array[...] = transform.initialize(self.param_array)
        reconstrained = self.unconstrain()
        added = self._add_to_index_operations(self.constraints, reconstrained,
                                              transform, warning)
        self.notify_observers(self, None if trigger_parent else -np.inf)
        return added

    def unconstrain(self, *transforms):
        """
        :param transforms: The transformations to unconstrain from.

        remove all :py:class:`GPy.core.transformations.Transformation`
        transformats of this parameter object.
        """
        return self._remove_from_index_operations(self.constraints, transforms)

    def constrain_positive(self, warning=True, trigger_parent=True):
        """
        :param warning: print a warning if re-constraining parameters.

        Constrain this parameter to the default positive constraint.
        """
        self.constrain(Logexp(),
                       warning=warning,
                       trigger_parent=trigger_parent)

    def constrain_negative(self, warning=True, trigger_parent=True):
        """
        :param warning: print a warning if re-constraining parameters.

        Constrain this parameter to the default negative constraint.
        """
        self.constrain(NegativeLogexp(),
                       warning=warning,
                       trigger_parent=trigger_parent)

    def constrain_bounded(self,
                          lower,
                          upper,
                          warning=True,
                          trigger_parent=True):
        """
        :param lower, upper: the limits to bound this parameter to
        :param warning: print a warning if re-constraining parameters.

        Constrain this parameter to lie within the given range.
        """
        self.constrain(Logistic(lower, upper),
                       warning=warning,
                       trigger_parent=trigger_parent)

    def unconstrain_positive(self):
        """
        Remove positive constraint of this parameter.
        """
        self.unconstrain(Logexp())

    def unconstrain_negative(self):
        """
        Remove negative constraint of this parameter.
        """
        self.unconstrain(NegativeLogexp())

    def unconstrain_bounded(self, lower, upper):
        """
        :param lower, upper: the limits to unbound this parameter from

        Remove (lower, upper) bounded constrain from this parameter/
        """
        self.unconstrain(Logistic(lower, upper))

    def _parent_changed(self, parent):
        """
        From Parentable:
        Called when the parent changed

        update the constraints and priors view, so that
        constraining is automized for the parent.
        """
        from index_operations import ParameterIndexOperationsView
        #if getattr(self, "_in_init_"):
        #import ipdb;ipdb.set_trace()
        #self.constraints.update(param.constraints, start)
        #self.priors.update(param.priors, start)
        offset = parent._offset_for(self)
        self.constraints = ParameterIndexOperationsView(
            parent.constraints, offset, self.size)
        self.priors = ParameterIndexOperationsView(parent.priors, offset,
                                                   self.size)
        self._fixes_ = None
        for p in self.parameters:
            p._parent_changed(parent)

    def _add_to_index_operations(self, which, reconstrained, what, warning):
        """
        Helper preventing copy code.
        This adds the given what (transformation, prior etc) to parameter index operations which.
        reconstrained are reconstrained indices.
        warn when reconstraining parameters if warning is True.
        TODO: find out which parameters have changed specifically
        """
        if warning and reconstrained.size > 0:
            # TODO: figure out which parameters have changed and only print those
            print "WARNING: reconstraining parameters {}".format(
                self.hierarchy_name() or self.name)
        index = self._raveled_index()
        which.add(what, index)
        return index

    def _remove_from_index_operations(self, which, transforms):
        """
        Helper preventing copy code.
        Remove given what (transform prior etc) from which param index ops.
        """
        if len(transforms) == 0:
            transforms = which.properties()
        removed = np.empty((0, ), dtype=int)
        for t in transforms:
            unconstrained = which.remove(t, self._raveled_index())
            removed = np.union1d(removed, unconstrained)
            if t is __fixed__:
                self._highest_parent_._set_unfixed(self, unconstrained)

        return removed