<commit_before>__author__ = 'giacomov'

__doc__ = """"""

import collections
import copy
import exceptions
import warnings

import astropy.units as u
import scipy.stats

# The following import is necessary so that min_value and max_value can be specified as things like
# '2 * np.pi'
import numpy as np

from astromodels.tree import Node


def _behaves_like_a_number(obj):
    """

    :param obj:
    :return: True if obj supports addition, subtraction, multiplication and division. False otherwise.
    """

    try:

        obj + 1
        obj * 2
        obj / 2
        obj - 1

    except TypeError:

        return False

    else:

        return True


# Exception for when a parameter is out of its bounds
class SettingOutOfBounds(exceptions.Exception):
    pass


# Exception for when an object should be callable but it isn't
class NotCallableOrErrorInCall(exceptions.Exception):
    pass


class WarningUnitsAreSlow(Warning):
    pass


class IndependentVariableCannotBeLinked(exceptions.Exception):
    pass


class CannotUnderstandUnit(exceptions.Exception):
    pass


class CannotConvertValueToNewUnits(Warning):
    pass


def accept_quantity(input_type=float, allow_none=False):
    """
        A class-method decorator which allow a given method (typically the set_value method) to receive both a
        astropy.Quantity or a simple float, but to be coded like it's always receiving a pure float in the right units.
        This is to give a way to avoid the huge bottleneck that are astropy.units

        :param input_type: the expected type for the input (float, int)
        :param allow_none : whether to allow or not the passage of None as argument (default: False)
        :return: a decorator for the particular type
        """

    def accept_quantity_wrapper(method):

        def handle_quantity(instance, value, *args, **kwargs):

            # For speed reasons, first run the case where the input is not a quantity, and fall back to the handling
            # of quantities if that fails. The parts that fails if the input is a Quantity is the conversion
            # input_type(value). This could have been handled more elegantly with a "finally" clause, but that would
            # have a 40 percent speed impact...

            try:

                new_value = input_type(value)

                return method(instance, new_value, *args, **kwargs)

            except TypeError:

                # Slow for slow, check that we actually have a quantity or None (if allowed)

                if isinstance(value, u.Quantity):

                    new_value = value.to(instance.unit).value

                    return method(instance, new_value, *args, **kwargs)

                elif value is None:

                    if allow_none:

                        return method(instance, None, *args, **kwargs)

                    else:

                        raise TypeError("You cannot pass None as argument for "
                                        "method %s of %s" % (method.__name__, instance.name))

                else:

                    raise TypeError("You need to pass either a %s or a astropy.Quantity "
                                    "to method %s of %s" % (input_type.__name__, method.__name__, instance.name))

        return handle_quantity

    return accept_quantity_wrapper


class ParameterBase(Node):

    def __init__(self, name, value, min_value=None, max_value=None, desc=None, unit=u.dimensionless_unscaled):

        # Make this a node

        Node.__init__(self, name)

        # Callbacks are executed any time the value for the parameter changes (i.e., its value changes)

        # We start from a empty list of callbacks.
        self._callbacks = []

        # Assign to members

        # Store the units as an astropy.units.Unit instance

        self._unit = u.Unit(unit)

        # Let's store the init value

        # If the value is a Quantity, deal with that

        if isinstance(value, u.Quantity):

            # If the user did not specify an ad-hoc unit, use the unit
            # of the Quantity

            if self._unit == u.dimensionless_unscaled:

                self._unit = value.unit

            # Convert the value to the provided unit (if necessary)

            self._value = value.to(self._unit).value

        else:

            self._value = value

        # Set minimum if provided, otherwise use default
        # (use the property so the checks that are there are performed also on construction)

        self._min_value = None # this will be overwritten immediately in the next line
        self.min_value = min_value

        # Set maximum if provided, otherwise use default

        self._max_value = None  # this will be overwritten immediately in the next line
        self.max_value = max_value

        # Store description

        self._desc = desc

        # Make the description the documentation as well

        self.__doc__ = desc

        # Now perform a very lazy check that we can perform math operations on value, minimum and maximum
        # (i.e., they are numbers)

        if not _behaves_like_a_number(self._value):

            raise TypeError("The provided initial value is not a number")

        if self._min_value is not None:

            if not _behaves_like_a_number(self._min_value):
                raise TypeError("The provided minimum value is not a number")

        if self._max_value is not None:

            if not _behaves_like_a_number(self._max_value):

                raise TypeError("The provided maximum value is not a number")

    def _repr__base(self, rich_output):

        raise NotImplementedError("You need to implement this for the actual Parameter class")

    # Define the property 'description' and make it read-only

    @property
    def description(self):
        """
        Return a description of this parameter

        :return: a string cointaining a description of the meaning of this parameter
        """
        return self._desc

    # Define the property 'unit'
    def _set_unit(self, input_unit):

        # This will fail if the input is not valid

        new_unit = u.Unit(input_unit)

        # Now transform the current _value in the new unit, unless the current unit is dimensionless, in which
        # case there is no transformation to make

        # (self._unit is the OLD unit here)

        if self._unit != u.dimensionless_unscaled:

            # This will fail if the new unit is not compatible with the old one

            try:

                self._value = (self._value * self._unit).to(new_unit).value

            except u.UnitConversionError:

                if new_unit == u.dimensionless_unscaled:

                    new_unit_name = '(dimensionless)'

                else:

                    new_unit_name = new_unit

                warnings.warn("Cannot convert the value %s from %s to the new units %s" % (self._value,
                                                                                           self._unit,
                                                                                           new_unit_name),
                              CannotConvertValueToNewUnits)

        else:

            # No need to transform the value
            pass

        # Finally store the new unit

        self._unit = new_unit

    def _get_unit(self):

        return self._unit

    unit = property(_get_unit, _set_unit,
                    doc="""Gets or sets the unit for the parameter""")

    # Define the property "value" with a control that the parameter cannot be set
    # outside of its bounds

    @property
    def value(self):
        """Return current parameter value"""

        return self._value

    def has_auxiliary_variable(self):

        # ParameterBase cannot have auxiliary variable (only Parameter has)

        return False

    # I use the decorator here (instead of the usual style for getters and setters) because
    # children classes need to override the getter, and using decorators is the only way
    # to achieve that
    @value.setter
    @accept_quantity(float, allow_none=False)
    def value(self, value):
        """Sets the current value of the parameter, ensuring that it is within the allowed range."""

        if self._min_value is not None and value < self._min_value:

            raise SettingOutOfBounds(
                "Trying to set parameter {0} = {1}, which is less than the minimum allowed {2}".format(
                    self.name, value, self.min_value))

        if self._max_value is not None and value > self._max_value:
            raise SettingOutOfBounds(
                "Trying to set parameter {0} = {1}, which is more than the maximum allowed {2}".format(
                    self.name, value, self.max_value))

        # Call the callbacks (if any)

        for callback in self._callbacks:

            try:

                callback(value)

            except:

                raise NotCallableOrErrorInCall(
                    "Could not use callback for parameter %s with value %s" % (self.name, value))

        # Issue a warning if there is an auxiliary variable, as the setting does not have any effect
        if self.has_auxiliary_variable():

            with warnings.catch_warnings():

                warnings.simplefilter("always", RuntimeWarning)

                warnings.warn("You are trying to assign to a parameter which is either linked or "
                              "has auxiliary variables. The assignment has no effect.", RuntimeWarning)

        else:

            # Save the value as a pure floating point to avoid the overhead of the astropy.units machinery when
            # not needed

            self._value = value

    @property
    def as_quantity(self):
        """
        Return the current value with its units (as an astropy.Quantity instance)

        :return: an instance of astropy.Quantity)
        """
        return self._value * self._unit

    def in_unit_of(self, unit, as_quantity=False):
        """
        Return the current value transformed to the new units

        :param unit: either an astropy.Unit instance, or a string which can be converted to an astropy.Unit
        instance, like "1 / (erg cm**2 s)"
        :param as_quantity: if True, the method return an astropy.Quantity, if False just a floating point number.
        Default is False
        :return: either a floating point or a astropy.Quantity depending on the value of "as_quantity"
        """

        new_unit = u.Unit(unit)

        new_quantity = self.as_quantity.to(new_unit)

        if as_quantity:

            return new_quantity

        else:

            return new_quantity.value

    # Define the property "min_value"

    def _get_min_value(self):
        """Return current minimum allowed value"""

        return self._min_value

    @accept_quantity(float, allow_none=True)
    def _set_min_value(self, min_value):

        """Sets current minimum allowed value"""

        # Store the minimum as a pure float

        self._min_value = min_value

        # Check that the current value of the parameter is still within the boundaries. If not, issue a warning

        if self._min_value is not None and self.value < self._min_value:

            warnings.warn("The current value of the parameter %s (%s) "
                          "was below the new minimum %s." % (self.name, self.value, self._min_value),
                          exceptions.RuntimeWarning)

            self._value = self._min_value

    min_value = property(_get_min_value, _set_min_value,
                         doc='Gets or sets the minimum allowed value for the parameter')

    def remove_minimum(self):
        """
        Remove the minimum from this parameter (i.e., it becomes boundless in the negative direction)
        """
        self._min_value = None

    # Define the property "max_value"

    def _get_max_value(self):

        """Return current minimum allowed value"""

        return self._max_value

    @accept_quantity(float, allow_none=True)
    def _set_max_value(self, max_value):
        """Sets current minimum allowed value"""

        self._max_value = max_value

        # Check that the current value of the parameter is still within the boundaries. If not, issue a warning

        if self._max_value is not None and self.value > self._max_value:

            warnings.warn("The current value of the parameter %s (%s) "
                          "was above the new maximum %s." % (self.name, self.value, self._max_value),
                          exceptions.RuntimeWarning)
            self._value = self._max_value

    max_value = property(_get_max_value, _set_max_value,
                         doc='Gets or sets the maximum allowed value for the parameter')

    def remove_maximum(self):
        """
        Remove the maximum from this parameter (i.e., it becomes boundless in the positive direction)
        """
        self._max_value = None

    def _set_bounds(self, bounds):
        """Sets the boundaries for this parameter to min_value and max_value"""

        # Use the properties so that the checks and the handling of units are made automatically

        min_value, max_value = bounds

        self.min_value = min_value

        self.max_value = max_value

    def _get_bounds(self):
        """Returns the current boundaries for the parameter"""

        return self.min_value, self.max_value

    bounds = property(_get_bounds, _set_bounds, doc="Gets or sets the boundaries (minimum and maximum) for this "
                                                    "parameter")

    def _change_name(self, new_name):
        """
        Change the name of this parameter. Use with caution!

        :return: none
        """
        self._name = new_name

    def add_callback(self, callback):
        """Add a callback to the list of functions which are called whenever the value of the parameter is changed.
        The callback must be a function accepting the current value as input. The return value of the callback is
        ignored. More than one callback can be specified. In that case, the callbacks will be called in the same order
        they have been entered."""

        self._callbacks.append(callback)

    def empty_callbacks(self):
        """Remove all callbacks for this parameter"""
        self._callbacks = []

    def duplicate(self):
        """
        Returns an exact copy of the current parameter
        """

        # Deep copy everything to make sure that there are no ties between the new instance and the old one

        new_parameter = copy.deepcopy(self)

        return new_parameter

    def to_dict(self, minimal=False):

        """Returns the representation for serialization"""

        data = collections.OrderedDict()

        if minimal:<commit_msg>Corrected the error message when it is not possible to call a callback<commit_after>__author__ = 'giacomov'

__doc__ = """"""

import collections
import copy
import exceptions
import warnings

import astropy.units as u
import scipy.stats

# The following import is necessary so that min_value and max_value can be specified as things like
# '2 * np.pi'
import numpy as np

from astromodels.tree import Node


def _behaves_like_a_number(obj):
    """

    :param obj:
    :return: True if obj supports addition, subtraction, multiplication and division. False otherwise.
    """

    try:

        obj + 1
        obj * 2
        obj / 2
        obj - 1

    except TypeError:

        return False

    else:

        return True


# Exception for when a parameter is out of its bounds
class SettingOutOfBounds(exceptions.Exception):
    pass


# Exception for when an object should be callable but it isn't
class NotCallableOrErrorInCall(exceptions.Exception):
    pass


class WarningUnitsAreSlow(Warning):
    pass


class IndependentVariableCannotBeLinked(exceptions.Exception):
    pass


class CannotUnderstandUnit(exceptions.Exception):
    pass


class CannotConvertValueToNewUnits(Warning):
    pass


def accept_quantity(input_type=float, allow_none=False):
    """
        A class-method decorator which allow a given method (typically the set_value method) to receive both a
        astropy.Quantity or a simple float, but to be coded like it's always receiving a pure float in the right units.
        This is to give a way to avoid the huge bottleneck that are astropy.units

        :param input_type: the expected type for the input (float, int)
        :param allow_none : whether to allow or not the passage of None as argument (default: False)
        :return: a decorator for the particular type
        """

    def accept_quantity_wrapper(method):

        def handle_quantity(instance, value, *args, **kwargs):

            # For speed reasons, first run the case where the input is not a quantity, and fall back to the handling
            # of quantities if that fails. The parts that fails if the input is a Quantity is the conversion
            # input_type(value). This could have been handled more elegantly with a "finally" clause, but that would
            # have a 40 percent speed impact...

            try:

                new_value = input_type(value)

                return method(instance, new_value, *args, **kwargs)

            except TypeError:

                # Slow for slow, check that we actually have a quantity or None (if allowed)

                if isinstance(value, u.Quantity):

                    new_value = value.to(instance.unit).value

                    return method(instance, new_value, *args, **kwargs)

                elif value is None:

                    if allow_none:

                        return method(instance, None, *args, **kwargs)

                    else:

                        raise TypeError("You cannot pass None as argument for "
                                        "method %s of %s" % (method.__name__, instance.name))

                else:

                    raise TypeError("You need to pass either a %s or a astropy.Quantity "
                                    "to method %s of %s" % (input_type.__name__, method.__name__, instance.name))

        return handle_quantity

    return accept_quantity_wrapper


class ParameterBase(Node):

    def __init__(self, name, value, min_value=None, max_value=None, desc=None, unit=u.dimensionless_unscaled):

        # Make this a node

        Node.__init__(self, name)

        # Callbacks are executed any time the value for the parameter changes (i.e., its value changes)

        # We start from a empty list of callbacks.
        self._callbacks = []

        # Assign to members

        # Store the units as an astropy.units.Unit instance

        self._unit = u.Unit(unit)

        # Let's store the init value

        # If the value is a Quantity, deal with that

        if isinstance(value, u.Quantity):

            # If the user did not specify an ad-hoc unit, use the unit
            # of the Quantity

            if self._unit == u.dimensionless_unscaled:

                self._unit = value.unit

            # Convert the value to the provided unit (if necessary)

            self._value = value.to(self._unit).value

        else:

            self._value = value

        # Set minimum if provided, otherwise use default
        # (use the property so the checks that are there are performed also on construction)

        self._min_value = None # this will be overwritten immediately in the next line
        self.min_value = min_value

        # Set maximum if provided, otherwise use default

        self._max_value = None  # this will be overwritten immediately in the next line
        self.max_value = max_value

        # Store description

        self._desc = desc

        # Make the description the documentation as well

        self.__doc__ = desc

        # Now perform a very lazy check that we can perform math operations on value, minimum and maximum
        # (i.e., they are numbers)

        if not _behaves_like_a_number(self._value):

            raise TypeError("The provided initial value is not a number")

        if self._min_value is not None:

            if not _behaves_like_a_number(self._min_value):
                raise TypeError("The provided minimum value is not a number")

        if self._max_value is not None:

            if not _behaves_like_a_number(self._max_value):

                raise TypeError("The provided maximum value is not a number")

    def _repr__base(self, rich_output):

        raise NotImplementedError("You need to implement this for the actual Parameter class")

    # Define the property 'description' and make it read-only

    @property
    def description(self):
        """
        Return a description of this parameter

        :return: a string cointaining a description of the meaning of this parameter
        """
        return self._desc

    # Define the property 'unit'
    def _set_unit(self, input_unit):

        # This will fail if the input is not valid

        new_unit = u.Unit(input_unit)

        # Now transform the current _value in the new unit, unless the current unit is dimensionless, in which
        # case there is no transformation to make

        # (self._unit is the OLD unit here)

        if self._unit != u.dimensionless_unscaled:

            # This will fail if the new unit is not compatible with the old one

            try:

                self._value = (self._value * self._unit).to(new_unit).value

            except u.UnitConversionError:

                if new_unit == u.dimensionless_unscaled:

                    new_unit_name = '(dimensionless)'

                else:

                    new_unit_name = new_unit

                warnings.warn("Cannot convert the value %s from %s to the new units %s" % (self._value,
                                                                                           self._unit,
                                                                                           new_unit_name),
                              CannotConvertValueToNewUnits)

        else:

            # No need to transform the value
            pass

        # Finally store the new unit

        self._unit = new_unit

    def _get_unit(self):

        return self._unit

    unit = property(_get_unit, _set_unit,
                    doc="""Gets or sets the unit for the parameter""")

    # Define the property "value" with a control that the parameter cannot be set
    # outside of its bounds

    @property
    def value(self):
        """Return current parameter value"""

        return self._value

    def has_auxiliary_variable(self):

        # ParameterBase cannot have auxiliary variable (only Parameter has)

        return False

    # I use the decorator here (instead of the usual style for getters and setters) because
    # children classes need to override the getter, and using decorators is the only way
    # to achieve that
    @value.setter
    @accept_quantity(float, allow_none=False)
    def value(self, value):
        """Sets the current value of the parameter, ensuring that it is within the allowed range."""

        if self._min_value is not None and value < self._min_value:

            raise SettingOutOfBounds(
                "Trying to set parameter {0} = {1}, which is less than the minimum allowed {2}".format(
                    self.name, value, self.min_value))

        if self._max_value is not None and value > self._max_value:
            raise SettingOutOfBounds(
                "Trying to set parameter {0} = {1}, which is more than the maximum allowed {2}".format(
                    self.name, value, self.max_value))

        # Issue a warning if there is an auxiliary variable, as the setting does not have any effect
        if self.has_auxiliary_variable():

            with warnings.catch_warnings():

                warnings.simplefilter("always", RuntimeWarning)

                warnings.warn("You are trying to assign to a parameter which is either linked or "
                              "has auxiliary variables. The assignment has no effect.", RuntimeWarning)

        else:

            # Save the value as a pure floating point to avoid the overhead of the astropy.units machinery when
            # not needed

            self._value = value

        # Call the callbacks (if any)

        for callback in self._callbacks:

            try:

                callback(self)

            except:

                raise NotCallableOrErrorInCall(
                    "Could not use callback for parameter %s with value %s" % (self.name, value))

    @property
    def as_quantity(self):
        """
        Return the current value with its units (as an astropy.Quantity instance)

        :return: an instance of astropy.Quantity)
        """
        return self._value * self._unit

    def in_unit_of(self, unit, as_quantity=False):
        """
        Return the current value transformed to the new units

        :param unit: either an astropy.Unit instance, or a string which can be converted to an astropy.Unit
        instance, like "1 / (erg cm**2 s)"
        :param as_quantity: if True, the method return an astropy.Quantity, if False just a floating point number.
        Default is False
        :return: either a floating point or a astropy.Quantity depending on the value of "as_quantity"
        """

        new_unit = u.Unit(unit)

        new_quantity = self.as_quantity.to(new_unit)

        if as_quantity:

            return new_quantity

        else:

            return new_quantity.value

    # Define the property "min_value"

    def _get_min_value(self):
        """Return current minimum allowed value"""

        return self._min_value

    @accept_quantity(float, allow_none=True)
    def _set_min_value(self, min_value):

        """Sets current minimum allowed value"""

        # Store the minimum as a pure float

        self._min_value = min_value

        # Check that the current value of the parameter is still within the boundaries. If not, issue a warning

        if self._min_value is not None and self.value < self._min_value:

            warnings.warn("The current value of the parameter %s (%s) "
                          "was below the new minimum %s." % (self.name, self.value, self._min_value),
                          exceptions.RuntimeWarning)

            self._value = self._min_value

    min_value = property(_get_min_value, _set_min_value,
                         doc='Gets or sets the minimum allowed value for the parameter')

    def remove_minimum(self):
        """
        Remove the minimum from this parameter (i.e., it becomes boundless in the negative direction)
        """
        self._min_value = None

    # Define the property "max_value"

    def _get_max_value(self):

        """Return current minimum allowed value"""

        return self._max_value

    @accept_quantity(float, allow_none=True)
    def _set_max_value(self, max_value):
        """Sets current minimum allowed value"""

        self._max_value = max_value

        # Check that the current value of the parameter is still within the boundaries. If not, issue a warning

        if self._max_value is not None and self.value > self._max_value:

            warnings.warn("The current value of the parameter %s (%s) "
                          "was above the new maximum %s." % (self.name, self.value, self._max_value),
                          exceptions.RuntimeWarning)
            self._value = self._max_value

    max_value = property(_get_max_value, _set_max_value,
                         doc='Gets or sets the maximum allowed value for the parameter')

    def remove_maximum(self):
        """
        Remove the maximum from this parameter (i.e., it becomes boundless in the positive direction)
        """
        self._max_value = None

    def _set_bounds(self, bounds):
        """Sets the boundaries for this parameter to min_value and max_value"""

        # Use the properties so that the checks and the handling of units are made automatically

        min_value, max_value = bounds

        self.min_value = min_value

        self.max_value = max_value

    def _get_bounds(self):
        """Returns the current boundaries for the parameter"""

        return self.min_value, self.max_value

    bounds = property(_get_bounds, _set_bounds, doc="Gets or sets the boundaries (minimum and maximum) for this "
                                                    "parameter")

    def _change_name(self, new_name):
        """
        Change the name of this parameter. Use with caution!

        :return: none
        """
        self._name = new_name

    def add_callback(self, callback):
        """Add a callback to the list of functions which are called immediately after the value of the parameter
        is changed. The callback must be a function accepting the current parameter as input. The return value of the
        callback is ignored. More than one callback can be specified. In that case, the callbacks will be called in the
        same order they have been entered."""

        self._callbacks.append(callback)

    def empty_callbacks(self):
        """Remove all callbacks for this parameter"""
        self._callbacks = []

    def duplicate(self):
        """
        Returns an exact copy of the current parameter
        """

        # Deep copy everything to make sure that there are no ties between the new instance and the old one

        new_parameter = copy.deepcopy(self)

        return new_parameter

    def to_dict(self, minimal=False):

        """Returns the representation for serialization"""

        data = collections.OrderedDict()

        if minimal:
{'fraction_non_alphanumeric': 0.06496597499245751, 'fraction_numerical': 0.0010056652475612618, 'mean_word_length': 2.074829931972789, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 7, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 24, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7023884', 'n_tokens_mistral': 7752, 'n_tokens_neox': 7227, 'n_words': 3024}