Repository: scipy/scipy
File: scipy/stats/_distribution_infrastructure.py (Python)

Code before:
        parameters that will define the endpoint). If ``endpoints`` is a
        callable, it should take parameters used to define the endpoints of
        the domain as keyword arguments and return a 2-tuple of floats giving
        the numerical values of the endpoints of the domain.
    inclusive : 2-tuple of bools
        A tuple with two boolean values; each indicates whether the
        corresponding endpoint is included within the domain or not.

    Methods
    -------
    define_parameters(*parameters)
        Records any parameters used to define the endpoints of the domain
    get_numerical_endpoints(parameter_values)
        Gets the numerical values of the domain endpoints, which may have been
        defined symbolically or through a callable.
    contains(item, parameter_values)
        Determines whether the argument is contained within the domain

    """
    def __init__(self, endpoints=(-inf, inf), inclusive=(False, False)):
        self.symbols = super().symbols.copy()
        if callable(endpoints):
            self.endpoints = endpoints
        else:
            a, b = endpoints
            self.endpoints = np.asarray(a)[()], np.asarray(b)[()]
        self.inclusive = inclusive

    def define_parameters(self, *parameters):
        r""" Records any parameters used to define the endpoints of the domain.

        Adds the keyword name of each parameter and its text representation
        to the  `symbols` attribute as key:value pairs.
        For instance, a parameter may be passed into to a distribution's
        initializer using the keyword `log_a`, and the corresponding
        string representation may be '\log(a)'. To form the text
        representation of the domain for use in documentation, the
        _Domain object needs to map from the keyword name used in the code
        to the string representation.

        Returns None, but updates the `symbols` attribute.

        Parameters
        ----------
        *parameters : _Parameter objects
            Parameters that may define the endpoints of the domain.

        """
        new_symbols = {param.name: param.symbol for param in parameters}
        self.symbols.update(new_symbols)


Review comment (performance):
I think what I had in mind is for `endpoints` to always be a tuple, but the tuple can contain floats, strings, or callables. That way you can mix and match, and you wouldn't need the `if callable` branching here. (It's OK if they become object arrays for now; that's what's happening to strings, I think.) The downside is that it *could* require two separate function calls instead of one to evaluate both endpoints, but I think it will be rare for that to be a real performance hit.

Code after:
        parameters that will define the endpoint), or a callable taking the
        parameters used to define the endpoints of the domain as keyword only
        arguments and returning a numerical value for the endpoint.
    inclusive : 2-tuple of bools
        A tuple with two boolean values; each indicates whether the
        corresponding endpoint is included within the domain or not.

    Methods
    -------
    define_parameters(*parameters)
        Records any parameters used to define the endpoints of the domain
    get_numerical_endpoints(parameter_values)
        Gets the numerical values of the domain endpoints, which may have been
        defined symbolically or through a callable.
    contains(item, parameter_values)
        Determines whether the argument is contained within the domain

    """
    def __init__(self, endpoints=(-inf, inf), inclusive=(False, False)):
        self.symbols = super().symbols.copy()
        a, b = endpoints
        self.endpoints = np.asarray(a)[()], np.asarray(b)[()]
        self.inclusive = inclusive

    def define_parameters(self, *parameters):
        r""" Records any parameters used to define the endpoints of the domain.

        Adds the keyword name of each parameter and its text representation
        to the  `symbols` attribute as key:value pairs.
        For instance, a parameter may be passed into to a distribution's
        initializer using the keyword `log_a`, and the corresponding
        string representation may be '\log(a)'. To form the text
        representation of the domain for use in documentation, the
        _Domain object needs to map from the keyword name used in the code
        to the string representation.

        Returns None, but updates the `symbols` attribute.

        Parameters
        ----------
        *parameters : _Parameter objects
            Parameters that may define the endpoints of the domain.

        """
        new_symbols = {param.name: param.symbol for param in parameters}
        self.symbols.update(new_symbols)

    def get_numerical_endpoints(self, parameter_values):
        r""" Get the numerical values of the domain endpoints.

        Domain endpoints may be defined symbolically or through a callable.